X-Git-Url: https://sigrok.org/gitweb/?a=blobdiff_plain;f=src%2Finput%2Fvcd.c;h=e3c2347ac0875ff6f44f99f000077a691ce3a455;hb=08f8421a9e82;hp=fe9375672d80048e39fb1de6ded1342da45df20a;hpb=d4c937749a92ce6defa2f0095b34692181afe597;p=libsigrok.git diff --git a/src/input/vcd.c b/src/input/vcd.c index fe937567..e3c2347a 100644 --- a/src/input/vcd.c +++ b/src/input/vcd.c @@ -2,6 +2,7 @@ * This file is part of the libsigrok project. * * Copyright (C) 2012 Petteri Aimonen + * Copyright (C) 2014 Bert Vermeulen * * 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 @@ -25,7 +26,7 @@ * * skip: Allows skipping until given timestamp in the file. * This can speed up analyzing of long captures. - * + * * Value < 0: Skip until first timestamp listed in * the file. (default) * @@ -56,26 +57,35 @@ * - more than 64 channels */ +#include #include #include #include #include -#include "libsigrok.h" +#include #include "libsigrok-internal.h" #define LOG_PREFIX "input/vcd" -#define DEFAULT_NUM_CHANNELS 8 -#define CHUNKSIZE 1024 +#define CHUNK_SIZE (4 * 1024 * 1024) struct context { + gboolean started; + gboolean got_header; + uint64_t prev_timestamp; uint64_t samplerate; - int maxchannels; - int channelcount; + unsigned int maxchannels; + unsigned int channelcount; int downsample; unsigned compress; int64_t skip; + gboolean skip_until_end; GSList *channels; + size_t bytes_per_sample; + size_t samples_in_buffer; + uint8_t *buffer; + uint8_t *current_levels; + GSList *prev_sr_channels; }; struct vcd_channel { @@ -83,99 +93,75 @@ struct vcd_channel { gchar *identifier; }; - -/* Read until specific type of character occurs in file. - * Skip input if dest is NULL. - * Modes: - * 'W' read until whitespace - * 'N' read until non-whitespace, and ungetc() the character - * '$' read until $end - */ -static gboolean read_until(FILE *file, GString *dest, char mode) -{ - int c; - char prev[4] = ""; - - for(;;) { - c = fgetc(file); - - if (c == EOF) { - if (mode == '$') - sr_err("Unexpected EOF."); - return FALSE; - } - - if (mode == 'W' && g_ascii_isspace(c)) - return TRUE; - - if (mode == 'N' && !g_ascii_isspace(c)) { - ungetc(c, file); - return TRUE; - } - - if (mode == '$') { - prev[0] = prev[1]; prev[1] = prev[2]; prev[2] = prev[3]; prev[3] = c; - if (prev[0] == '$' && prev[1] == 'e' && prev[2] == 'n' && prev[3] == 'd') { - if (dest != NULL) - g_string_truncate(dest, dest->len - 3); - - return TRUE; - } - } - - if (dest != NULL) - g_string_append_c(dest, c); - } -} - /* - * Reads a single VCD section from input file and parses it to structure. - * e.g. $timescale 1ps $end => "timescale" "1ps" + * Reads a single VCD section from input file and parses it to name/contents. + * e.g. $timescale 1ps $end => "timescale" "1ps" */ -static gboolean parse_section(FILE *file, gchar **name, gchar **contents) +static gboolean parse_section(GString *buf, gchar **name, gchar **contents) { + GString *sname, *scontent; gboolean status; - GString *sname, *scontents; + unsigned int pos; + + *name = *contents = NULL; + status = FALSE; + pos = 0; + + /* Skip UTF8 BOM */ + if (buf->len >= 3 && !strncmp(buf->str, "\xef\xbb\xbf", 3)) + pos = 3; - /* Skip any initial white-space */ - if (!read_until(file, NULL, 'N')) return FALSE; + /* Skip any initial white-space. */ + while (pos < buf->len && g_ascii_isspace(buf->str[pos])) + pos++; /* Section tag should start with $. */ - if (fgetc(file) != '$') + if (buf->str[pos++] != '$') return FALSE; - /* Read the section tag */ sname = g_string_sized_new(32); - status = read_until(file, sname, 'W'); - - /* Skip whitespace before content */ - status = status && read_until(file, NULL, 'N'); - - /* Read the content */ - scontents = g_string_sized_new(128); - status = status && read_until(file, scontents, '$'); - g_strchomp(scontents->str); + scontent = g_string_sized_new(128); + + /* Read the section tag. */ + while (pos < buf->len && !g_ascii_isspace(buf->str[pos])) + g_string_append_c(sname, buf->str[pos++]); + + /* Skip whitespace before content. */ + while (pos < buf->len && g_ascii_isspace(buf->str[pos])) + pos++; + + /* Read the content. */ + while (pos < buf->len - 4 && strncmp(buf->str + pos, "$end", 4)) + g_string_append_c(scontent, buf->str[pos++]); + + if (sname->len && pos < buf->len - 4 && !strncmp(buf->str + pos, "$end", 4)) { + status = TRUE; + pos += 4; + while (pos < buf->len && g_ascii_isspace(buf->str[pos])) + pos++; + g_string_erase(buf, 0, pos); + } - /* Release strings if status is FALSE, return them if status is TRUE */ *name = g_string_free(sname, !status); - *contents = g_string_free(scontents, !status); + *contents = g_string_free(scontent, !status); + if (*contents) + g_strchomp(*contents); + return status; } static void free_channel(void *data) { - struct vcd_channel *vcd_ch = data; + struct vcd_channel *vcd_ch; + + vcd_ch = data; + if (!vcd_ch) + return; g_free(vcd_ch->name); g_free(vcd_ch->identifier); g_free(vcd_ch); } -static void release_context(struct context *ctx) -{ - g_slist_free_full(ctx->channels, free_channel); - g_free(ctx); -} - /* Remove empty parts from an array returned by g_strsplit. */ static void remove_empty_parts(gchar **parts) { @@ -190,18 +176,72 @@ static void remove_empty_parts(gchar **parts) *dest = NULL; } +/* + * 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_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 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; +} + /* * Parse VCD header to get values for context structure. * The context structure should be zeroed before calling this. */ -static gboolean parse_header(FILE *file, struct context *ctx) +static gboolean parse_header(const struct sr_input *in, GString *buf) { - uint64_t p, q; - gchar *name = NULL, *contents = NULL; - gboolean status = FALSE; struct vcd_channel *vcd_ch; + uint64_t p, q; + struct context *inc; + gboolean status; + gchar *name, *contents, **parts; - while (parse_section(file, &name, &contents)) { + inc = in->priv; + name = contents = NULL; + status = FALSE; + while (parse_section(buf, &name, &contents)) { sr_dbg("Section '%s', contents '%s'.", name, contents); if (g_strcmp0(name, "enddefinitions") == 0) { @@ -211,334 +251,486 @@ static gboolean parse_header(FILE *file, struct context *ctx) /* * The standard allows for values 1, 10 or 100 * and units s, ms, us, ns, ps and fs. - * */ + */ if (sr_parse_period(contents, &p, &q) == SR_OK) { - ctx->samplerate = q / p; + inc->samplerate = q / p; if (q % p != 0) { /* Does not happen unless time value is non-standard */ sr_warn("Inexact rounding of samplerate, %" PRIu64 " / %" PRIu64 " to %" PRIu64 " Hz.", - q, p, ctx->samplerate); + q, p, inc->samplerate); } - sr_dbg("Samplerate: %" PRIu64, ctx->samplerate); + sr_dbg("Samplerate: %" PRIu64, inc->samplerate); } else { sr_err("Parsing timescale failed."); } } else if (g_strcmp0(name, "var") == 0) { - /* Format: $var type size identifier reference $end */ - gchar **parts = g_strsplit_set(contents, " \r\n\t", 0); + /* Format: $var type size identifier reference [opt. index] $end */ + unsigned int length; + + parts = g_strsplit_set(contents, " \r\n\t", 0); remove_empty_parts(parts); + length = g_strv_length(parts); - if (g_strv_length(parts) != 4) - sr_warn("$var section should have 4 items"); + if (length != 4 && length != 5) + sr_warn("$var section should have 4 or 5 items"); else if (g_strcmp0(parts[0], "reg") != 0 && g_strcmp0(parts[0], "wire") != 0) sr_info("Unsupported signal type: '%s'", parts[0]); else if (strtol(parts[1], NULL, 10) != 1) sr_info("Unsupported signal size: '%s'", parts[1]); - else if (ctx->channelcount >= ctx->maxchannels) - sr_warn("Skipping '%s' because only %d channels requested.", parts[3], ctx->maxchannels); + else if (inc->maxchannels && inc->channelcount >= inc->maxchannels) + sr_warn("Skipping '%s%s' because only %d channels requested.", + parts[3], parts[4] ? : "", inc->maxchannels); else { - sr_info("Channel %d is '%s' identified by '%s'.", ctx->channelcount, parts[3], parts[2]); vcd_ch = g_malloc(sizeof(struct vcd_channel)); vcd_ch->identifier = g_strdup(parts[2]); - vcd_ch->name = g_strdup(parts[3]); - ctx->channels = g_slist_append(ctx->channels, vcd_ch); - ctx->channelcount++; + if (length == 4) + vcd_ch->name = g_strdup(parts[3]); + else + vcd_ch->name = g_strconcat(parts[3], parts[4], NULL); + + sr_info("Channel %d is '%s' identified by '%s'.", + inc->channelcount, vcd_ch->name, vcd_ch->identifier); + + sr_channel_new(in->sdi, inc->channelcount++, SR_CHANNEL_LOGIC, TRUE, vcd_ch->name); + inc->channels = g_slist_append(inc->channels, vcd_ch); } g_strfreev(parts); } - g_free(name); name = NULL; - g_free(contents); contents = NULL; + g_free(name); + name = NULL; + g_free(contents); + contents = NULL; } - g_free(name); g_free(contents); + /* + * Compute how many bytes each sample will have and initialize the + * current levels. The current levels will be updated whenever VCD + * has changes. + */ + inc->bytes_per_sample = (inc->channelcount + 7) / 8; + inc->current_levels = g_malloc0(inc->bytes_per_sample); + + inc->got_header = status; + if (status) + status = check_header_in_reread(in); + return status; } -static int format_match(const char *filename) +static int format_match(GHashTable *metadata, unsigned int *confidence) { - FILE *file; - gchar *name = NULL, *contents = NULL; + GString *buf, *tmpbuf; gboolean status; + gchar *name, *contents; - file = fopen(filename, "r"); - if (file == NULL) - return FALSE; + buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER)); + tmpbuf = g_string_new_len(buf->str, buf->len); /* * If we can parse the first section correctly, * then it is assumed to be a VCD file. */ - status = parse_section(file, &name, &contents); - status = status && (*name != '\0'); - + status = parse_section(tmpbuf, &name, &contents); + g_string_free(tmpbuf, TRUE); g_free(name); g_free(contents); - fclose(file); - return status; + if (!status) + return SR_ERR; + *confidence = 1; + + return SR_OK; } -static int init(struct sr_input *in, const char *filename) +/* Send all accumulated bytes from inc->buffer. */ +static void send_buffer(const struct sr_input *in) { - struct sr_channel *ch; - int num_channels, i; - char name[SR_MAX_CHANNELNAME_LEN + 1]; - char *param; - struct context *ctx; - - (void)filename; - - if (!(ctx = g_try_malloc0(sizeof(*ctx)))) { - sr_err("Input format context malloc failed."); - return SR_ERR_MALLOC; - } + struct context *inc; + struct sr_datafeed_packet packet; + struct sr_datafeed_logic logic; - num_channels = DEFAULT_NUM_CHANNELS; - ctx->samplerate = 0; - ctx->downsample = 1; - ctx->skip = -1; - - if (in->param) { - param = g_hash_table_lookup(in->param, "numchannels"); - if (param) { - num_channels = strtoul(param, NULL, 10); - if (num_channels < 1) { - release_context(ctx); - return SR_ERR; - } else if (num_channels > 64) { - sr_err("No more than 64 channels supported."); - return SR_ERR; - } - } + inc = in->priv; - param = g_hash_table_lookup(in->param, "downsample"); - if (param) { - ctx->downsample = strtoul(param, NULL, 10); - if (ctx->downsample < 1) - ctx->downsample = 1; - } + if (inc->samples_in_buffer == 0) + return; - param = g_hash_table_lookup(in->param, "compress"); - if (param) - ctx->compress = strtoul(param, NULL, 10); + packet.type = SR_DF_LOGIC; + packet.payload = &logic; + logic.unitsize = inc->bytes_per_sample; + logic.data = inc->buffer; + logic.length = inc->bytes_per_sample * inc->samples_in_buffer; + sr_session_send(in->sdi, &packet); + inc->samples_in_buffer = 0; +} - param = g_hash_table_lookup(in->param, "skip"); - if (param) - ctx->skip = strtoul(param, NULL, 10) / ctx->downsample; - } +/* + * Add N copies of the current sample to buffer. + * When the buffer fills up, automatically send it. + */ +static void add_samples(const struct sr_input *in, size_t count) +{ + struct context *inc; + size_t samples_per_chunk; + size_t space_left, i; + uint8_t *p; - /* Maximum number of channels to parse from the VCD */ - ctx->maxchannels = num_channels; + inc = in->priv; + samples_per_chunk = CHUNK_SIZE / inc->bytes_per_sample; - /* Create a virtual device. */ - in->sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, NULL, NULL, NULL); - in->internal = ctx; + while (count) { + space_left = samples_per_chunk - inc->samples_in_buffer; - for (i = 0; i < num_channels; i++) { - snprintf(name, SR_MAX_CHANNELNAME_LEN, "%d", i); + if (space_left > count) + space_left = count; - if (!(ch = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, name))) { - release_context(ctx); - return SR_ERR; + p = inc->buffer + inc->samples_in_buffer * inc->bytes_per_sample; + for (i = 0; i < space_left; i++) { + memcpy(p, inc->current_levels, inc->bytes_per_sample); + p += inc->bytes_per_sample; + inc->samples_in_buffer++; + count--; } - in->sdi->channels = g_slist_append(in->sdi->channels, ch); + if (inc->samples_in_buffer == samples_per_chunk) + send_buffer(in); } - - return SR_OK; } -/* Send N samples of the given value. */ -static void send_samples(const struct sr_dev_inst *sdi, uint64_t sample, uint64_t count) +/* Set the channel level depending on the identifier and parsed value. */ +static void process_bit(struct context *inc, char *identifier, unsigned int bit) { - struct sr_datafeed_packet packet; - struct sr_datafeed_logic logic; - uint64_t buffer[CHUNKSIZE]; - uint64_t i; - unsigned chunksize = CHUNKSIZE; - - if (count < chunksize) - chunksize = count; - - for (i = 0; i < chunksize; i++) - buffer[i] = sample; - - packet.type = SR_DF_LOGIC; - packet.payload = &logic; - logic.unitsize = sizeof(uint64_t); - logic.data = buffer; - - while (count) { - if (count < chunksize) - chunksize = count; - - logic.length = sizeof(uint64_t) * chunksize; - - sr_session_send(sdi, &packet); - count -= chunksize; + GSList *l; + struct vcd_channel *vcd_ch; + unsigned int j; + + for (j = 0, l = inc->channels; j < inc->channelcount && l; j++, l = l->next) { + vcd_ch = l->data; + if (g_strcmp0(identifier, vcd_ch->identifier) == 0) { + /* Found our channel. */ + size_t byte_idx = (j / 8); + size_t bit_idx = j - 8 * byte_idx; + if (bit) + inc->current_levels[byte_idx] |= (uint8_t)1 << bit_idx; + else + inc->current_levels[byte_idx] &= ~((uint8_t)1 << bit_idx); + break; + } } + if (j == inc->channelcount) + sr_dbg("Did not find channel for identifier '%s'.", identifier); } -/* Parse the data section of VCD */ -static void parse_contents(FILE *file, const struct sr_dev_inst *sdi, struct context *ctx) +/* Parse a set of lines from the data section. */ +static void parse_contents(const struct sr_input *in, char *data) { - GString *token = g_string_sized_new(32); + struct context *inc; + uint64_t timestamp; + unsigned int bit, i; + char **tokens; - uint64_t prev_timestamp = 0; - uint64_t prev_values = 0; + inc = in->priv; /* Read one space-delimited token at a time. */ - while (read_until(file, NULL, 'N') && read_until(file, token, 'W')) { - if (token->str[0] == '#' && g_ascii_isdigit(token->str[1])) { + tokens = g_strsplit_set(data, " \t\r\n", 0); + remove_empty_parts(tokens); + for (i = 0; tokens[i]; i++) { + if (inc->skip_until_end) { + if (!strcmp(tokens[i], "$end")) { + /* Done with unhandled/unknown section. */ + inc->skip_until_end = FALSE; + break; + } + } + if (tokens[i][0] == '#' && g_ascii_isdigit(tokens[i][1])) { /* Numeric value beginning with # is a new timestamp value */ - uint64_t timestamp; - timestamp = strtoull(token->str + 1, NULL, 10); + timestamp = strtoull(tokens[i] + 1, NULL, 10); - if (ctx->downsample > 1) - timestamp /= ctx->downsample; + if (inc->downsample > 1) + timestamp /= inc->downsample; /* * Skip < 0 => skip until first timestamp. * Skip = 0 => don't skip * Skip > 0 => skip until timestamp >= skip. */ - if (ctx->skip < 0) { - ctx->skip = timestamp; - prev_timestamp = timestamp; - } else if (ctx->skip > 0 && timestamp < (uint64_t)ctx->skip) { - prev_timestamp = ctx->skip; - } - else if (timestamp == prev_timestamp) { + if (inc->skip < 0) { + inc->skip = timestamp; + inc->prev_timestamp = timestamp; + } else if (inc->skip > 0 && timestamp < (uint64_t)inc->skip) { + inc->prev_timestamp = inc->skip; + } else if (timestamp == inc->prev_timestamp) { /* Ignore repeated timestamps (e.g. sigrok outputs these) */ - } - else { - if (ctx->compress != 0 && timestamp - prev_timestamp > ctx->compress) - { + } else if (timestamp < inc->prev_timestamp) { + sr_err("Invalid timestamp: %" PRIu64 " (smaller than previous timestamp).", timestamp); + inc->skip_until_end = TRUE; + break; + } else { + if (inc->compress != 0 && timestamp - inc->prev_timestamp > inc->compress) { /* Compress long idle periods */ - prev_timestamp = timestamp - ctx->compress; + inc->prev_timestamp = timestamp - inc->compress; } sr_dbg("New timestamp: %" PRIu64, timestamp); /* Generate samples from prev_timestamp up to timestamp - 1. */ - send_samples(sdi, prev_values, timestamp - prev_timestamp); - prev_timestamp = timestamp; + add_samples(in, timestamp - inc->prev_timestamp); + inc->prev_timestamp = timestamp; } - } else if (token->str[0] == '$' && token->len > 1) { - /* This is probably a $dumpvars, $comment or similar. - * $dump* contain useful data, but other tags will be skipped until $end. */ - if (g_strcmp0(token->str, "$dumpvars") == 0 - || g_strcmp0(token->str, "$dumpon") == 0 - || g_strcmp0(token->str, "$dumpoff") == 0 - || g_strcmp0(token->str, "$end") == 0) { + } else if (tokens[i][0] == '$' && tokens[i][1] != '\0') { + /* + * This is probably a $dumpvars, $comment or similar. + * $dump* contain useful data. + */ + if (g_strcmp0(tokens[i], "$dumpvars") == 0 + || g_strcmp0(tokens[i], "$dumpon") == 0 + || g_strcmp0(tokens[i], "$dumpoff") == 0 + || g_strcmp0(tokens[i], "$end") == 0) { /* Ignore, parse contents as normally. */ } else { - /* Skip until $end */ - read_until(file, NULL, '$'); + /* Ignore this and future lines until $end. */ + inc->skip_until_end = TRUE; + break; } - } - else if (strchr("bBrR", token->str[0]) != NULL) { - /* A vector value. Skip it and also the following identifier. */ - read_until(file, NULL, 'N'); - read_until(file, NULL, 'W'); - } else if (strchr("01xXzZ", token->str[0]) != NULL) { - /* A new 1-bit sample value */ - int i, bit; - GSList *l; - struct vcd_channel *vcd_ch; - - bit = (token->str[0] == '1'); - - g_string_erase(token, 0, 1); - if (token->len == 0) { - /* There was a space between value and identifier. - * Read in the rest. - */ - read_until(file, NULL, 'N'); - read_until(file, token, 'W'); + } else if (strchr("rR", tokens[i][0]) != NULL) { + sr_dbg("Real type vector values not supported yet!"); + if (!tokens[++i]) + /* No tokens left, bail out */ + break; + else + /* Process next token */ + continue; + } else if (strchr("bB", tokens[i][0]) != NULL) { + bit = (tokens[i][1] == '1'); + + /* + * Bail out if a) char after 'b' is NUL, or b) there is + * a second character after 'b', or c) there is no + * identifier. + */ + if (!tokens[i][1] || tokens[i][2] || !tokens[++i]) { + sr_dbg("Unexpected vector format!"); + break; } - for (i = 0, l = ctx->channels; i < ctx->channelcount && l; i++, l = l->next) { - vcd_ch = l->data; + process_bit(inc, tokens[i], bit); + } else if (strchr("01xXzZ", tokens[i][0]) != NULL) { + char *identifier; - if (g_strcmp0(token->str, vcd_ch->identifier) == 0) { - /* Found our channel */ - if (bit) - prev_values |= (uint64_t)1 << i; - else - prev_values &= ~((uint64_t)1 << i); + /* A new 1-bit sample value */ + bit = (tokens[i][0] == '1'); + /* + * The identifier is either the next character, or, if + * there was whitespace after the bit, the next token. + */ + if (tokens[i][1] == '\0') { + if (!tokens[++i]) { + sr_dbg("Identifier missing!"); break; } + identifier = tokens[i]; + } else { + identifier = tokens[i] + 1; } - - if (i == ctx->channelcount) - sr_dbg("Did not find channel for identifier '%s'.", token->str); + process_bit(inc, identifier, bit); } else { - sr_warn("Skipping unknown token '%s'.", token->str); + sr_warn("Skipping unknown token '%s'.", tokens[i]); } - - g_string_truncate(token, 0); } + g_strfreev(tokens); +} + +static int init(struct sr_input *in, GHashTable *options) +{ + struct context *inc; + + inc = in->priv = g_malloc0(sizeof(struct context)); + + inc->maxchannels = g_variant_get_int32(g_hash_table_lookup(options, "numchannels")); + inc->downsample = g_variant_get_int32(g_hash_table_lookup(options, "downsample")); + if (inc->downsample < 1) + inc->downsample = 1; + + inc->compress = g_variant_get_int32(g_hash_table_lookup(options, "compress")); + inc->skip = g_variant_get_int32(g_hash_table_lookup(options, "skip")); + inc->skip /= inc->downsample; + + in->sdi = g_malloc0(sizeof(struct sr_dev_inst)); + in->priv = inc; + + inc->buffer = g_malloc(CHUNK_SIZE); - g_string_free(token, TRUE); + return SR_OK; } -static int loadfile(struct sr_input *in, const char *filename) +static gboolean have_header(GString *buf) +{ + unsigned int pos; + char *p; + + if (!(p = g_strstr_len(buf->str, buf->len, "$enddefinitions"))) + return FALSE; + pos = p - buf->str + 15; + while (pos < buf->len - 4 && g_ascii_isspace(buf->str[pos])) + pos++; + if (!strncmp(buf->str + pos, "$end", 4)) + return TRUE; + + return FALSE; +} + +static int process_buffer(struct sr_input *in) { struct sr_datafeed_packet packet; struct sr_datafeed_meta meta; struct sr_config *src; - FILE *file; - struct context *ctx; + struct context *inc; uint64_t samplerate; + char *p; + + inc = in->priv; + if (!inc->started) { + std_session_send_df_header(in->sdi); + + packet.type = SR_DF_META; + packet.payload = &meta; + samplerate = inc->samplerate / inc->downsample; + src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(samplerate)); + meta.config = g_slist_append(NULL, src); + sr_session_send(in->sdi, &packet); + g_slist_free(meta.config); + sr_config_free(src); + + inc->started = TRUE; + } - ctx = in->internal; + while ((p = g_strrstr_len(in->buf->str, in->buf->len, "\n"))) { + *p = '\0'; + g_strstrip(in->buf->str); + if (in->buf->str[0] != '\0') + parse_contents(in, in->buf->str); + g_string_erase(in->buf, 0, p - in->buf->str + 1); + } - if ((file = fopen(filename, "r")) == NULL) - return SR_ERR; + return SR_OK; +} - if (!parse_header(file, ctx)) { - sr_err("VCD parsing failed"); - fclose(file); - return SR_ERR; +static int receive(struct sr_input *in, GString *buf) +{ + struct context *inc; + int ret; + + g_string_append_len(in->buf, buf->str, buf->len); + + inc = in->priv; + if (!inc->got_header) { + if (!have_header(in->buf)) + return SR_OK; + if (!parse_header(in, in->buf)) + /* There was a header in there, but it was malformed. */ + return SR_ERR; + + in->sdi_ready = TRUE; + /* sdi is ready, notify frontend. */ + return SR_OK; } - /* Send header packet to the session bus. */ - std_session_send_df_header(in->sdi, LOG_PREFIX); + ret = process_buffer(in); - /* Send metadata about the SR_DF_LOGIC packets to come. */ - packet.type = SR_DF_META; - packet.payload = &meta; - samplerate = ctx->samplerate / ctx->downsample; - src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(samplerate)); - meta.config = g_slist_append(NULL, src); - sr_session_send(in->sdi, &packet); - sr_config_free(src); + return ret; +} + +static int end(struct sr_input *in) +{ + struct context *inc; + int ret; - /* Parse the contents of the VCD file */ - parse_contents(file, in->sdi, ctx); + inc = in->priv; - /* Send end packet to the session bus. */ - packet.type = SR_DF_END; - sr_session_send(in->sdi, &packet); + if (in->sdi_ready) + ret = process_buffer(in); + else + ret = SR_OK; + + /* Send any samples that haven't been sent yet. */ + send_buffer(in); - fclose(file); - release_context(ctx); - in->internal = NULL; + if (inc->started) + std_session_send_df_end(in->sdi); + + return ret; +} + +static void cleanup(struct sr_input *in) +{ + struct context *inc; + + inc = in->priv; + keep_header_for_reread(in); + g_slist_free_full(inc->channels, free_channel); + inc->channels = NULL; + + g_free(inc->buffer); + inc->buffer = NULL; + g_free(inc->current_levels); + inc->current_levels = NULL; +} + +static int reset(struct sr_input *in) +{ + struct context *inc = in->priv; + + cleanup(in); + g_string_truncate(in->buf, 0); + + inc->started = FALSE; + inc->got_header = FALSE; + inc->prev_timestamp = 0; + inc->skip_until_end = FALSE; + inc->channelcount = 0; + /* The inc->channels list was released in cleanup() above. */ + inc->buffer = g_malloc(CHUNK_SIZE); return SR_OK; } +static struct sr_option options[] = { + { "numchannels", "Number of logic channels", "The number of (logic) channels in the data", NULL, NULL }, + { "skip", "Skip samples until timestamp", "Skip samples until the specified timestamp; " + "< 0: Skip until first timestamp listed; 0: Don't skip", NULL, NULL }, + { "downsample", "Downsampling factor", "Downsample, i.e. divide the samplerate by the specified factor", NULL, NULL }, + { "compress", "Compress idle periods", "Compress idle periods longer than the specified value", NULL, NULL }, + ALL_ZERO +}; + +static const struct sr_option *get_options(void) +{ + 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(-1)); + options[2].def = g_variant_ref_sink(g_variant_new_int32(1)); + options[3].def = g_variant_ref_sink(g_variant_new_int32(0)); + } + + return options; +} + SR_PRIV struct sr_input_module input_vcd = { .id = "vcd", - .description = "Value Change Dump", + .name = "VCD", + .desc = "Value Change Dump data", + .exts = (const char*[]){"vcd", NULL}, + .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED }, + .options = get_options, .format_match = format_match, .init = init, - .loadfile = loadfile, + .receive = receive, + .end = end, + .cleanup = cleanup, + .reset = reset, };