input: Fixes and VCD.
This commit is contained in:
parent
0f3dbc9530
commit
7db0639495
|
@ -45,9 +45,10 @@ libsigrok_la_SOURCES = \
|
||||||
# src/input/binary.c \
|
# src/input/binary.c \
|
||||||
# src/input/chronovu_la8.c \
|
# src/input/chronovu_la8.c \
|
||||||
# src/input/csv.c \
|
# src/input/csv.c \
|
||||||
# src/input/vcd.c
|
#
|
||||||
libsigrok_la_SOURCES += \
|
libsigrok_la_SOURCES += \
|
||||||
src/input/input.c \
|
src/input/input.c \
|
||||||
|
src/input/vcd.c \
|
||||||
src/input/wav.c
|
src/input/wav.c
|
||||||
|
|
||||||
# Output modules
|
# Output modules
|
||||||
|
|
|
@ -66,7 +66,7 @@ extern SR_PRIV struct sr_input_module input_wav;
|
||||||
/* @endcond */
|
/* @endcond */
|
||||||
|
|
||||||
static const struct sr_input_module *input_module_list[] = {
|
static const struct sr_input_module *input_module_list[] = {
|
||||||
// &input_vcd,
|
&input_vcd,
|
||||||
// &input_chronovu_la8,
|
// &input_chronovu_la8,
|
||||||
&input_wav,
|
&input_wav,
|
||||||
// &input_csv,
|
// &input_csv,
|
||||||
|
@ -219,13 +219,13 @@ SR_API struct sr_input *sr_input_new(const struct sr_input_module *imod,
|
||||||
in = g_malloc0(sizeof(struct sr_input));
|
in = g_malloc0(sizeof(struct sr_input));
|
||||||
in->module = imod;
|
in->module = imod;
|
||||||
|
|
||||||
if (options) {
|
new_opts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
|
||||||
new_opts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
|
(GDestroyNotify)g_variant_unref);
|
||||||
(GDestroyNotify)g_variant_unref);
|
if (imod->options) {
|
||||||
mod_opts = imod->options();
|
mod_opts = imod->options();
|
||||||
for (i = 0; mod_opts[i].id; i++) {
|
for (i = 0; mod_opts[i].id; i++) {
|
||||||
if (g_hash_table_lookup_extended(options, mod_opts[i].id,
|
if (options && g_hash_table_lookup_extended(options,
|
||||||
&key, &value)) {
|
mod_opts[i].id, &key, &value)) {
|
||||||
/* Option not given: insert the default value. */
|
/* Option not given: insert the default value. */
|
||||||
gvt = g_variant_get_type(mod_opts[i].def);
|
gvt = g_variant_get_type(mod_opts[i].def);
|
||||||
if (!g_variant_is_of_type(value, gvt)) {
|
if (!g_variant_is_of_type(value, gvt)) {
|
||||||
|
@ -243,17 +243,18 @@ SR_API struct sr_input *sr_input_new(const struct sr_input_module *imod,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure no invalid options were given. */
|
/* Make sure no invalid options were given. */
|
||||||
g_hash_table_iter_init(&iter, options);
|
if (options) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
g_hash_table_iter_init(&iter, options);
|
||||||
if (!g_hash_table_lookup(new_opts, key)) {
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
||||||
sr_err("Input module '%s' has no option '%s'", imod->id, key);
|
if (!g_hash_table_lookup(new_opts, key)) {
|
||||||
g_hash_table_destroy(new_opts);
|
sr_err("Input module '%s' has no option '%s'", imod->id, key);
|
||||||
g_free(in);
|
g_hash_table_destroy(new_opts);
|
||||||
return NULL;
|
g_free(in);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else
|
}
|
||||||
new_opts = NULL;
|
|
||||||
|
|
||||||
if (in->module->init && in->module->init(in, new_opts) != SR_OK) {
|
if (in->module->init && in->module->init(in, new_opts) != SR_OK) {
|
||||||
g_hash_table_destroy(new_opts);
|
g_hash_table_destroy(new_opts);
|
||||||
|
@ -273,7 +274,7 @@ static gboolean check_required_metadata(const uint8_t *metadata, uint8_t *avail)
|
||||||
uint8_t reqd;
|
uint8_t reqd;
|
||||||
|
|
||||||
for (m = 0; metadata[m]; m++) {
|
for (m = 0; metadata[m]; m++) {
|
||||||
if (!metadata[m] & SR_INPUT_META_REQUIRED)
|
if (!(metadata[m] & SR_INPUT_META_REQUIRED))
|
||||||
continue;
|
continue;
|
||||||
reqd = metadata[m] & ~SR_INPUT_META_REQUIRED;
|
reqd = metadata[m] & ~SR_INPUT_META_REQUIRED;
|
||||||
for (a = 0; avail[a]; a++) {
|
for (a = 0; avail[a]; a++) {
|
||||||
|
@ -490,6 +491,8 @@ SR_API int sr_input_free(const struct sr_input *in)
|
||||||
ret = SR_OK;
|
ret = SR_OK;
|
||||||
if (in->module->cleanup)
|
if (in->module->cleanup)
|
||||||
ret = in->module->cleanup((struct sr_input *)in);
|
ret = in->module->cleanup((struct sr_input *)in);
|
||||||
|
if (in->sdi)
|
||||||
|
sr_dev_inst_free(in->sdi);
|
||||||
if (in->buf)
|
if (in->buf)
|
||||||
g_string_free(in->buf, TRUE);
|
g_string_free(in->buf, TRUE);
|
||||||
g_free((gpointer)in);
|
g_free((gpointer)in);
|
||||||
|
|
533
src/input/vcd.c
533
src/input/vcd.c
|
@ -2,6 +2,7 @@
|
||||||
* This file is part of the libsigrok project.
|
* This file is part of the libsigrok project.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2012 Petteri Aimonen <jpa@sr.mail.kapsi.fi>
|
* Copyright (C) 2012 Petteri Aimonen <jpa@sr.mail.kapsi.fi>
|
||||||
|
* Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -69,12 +70,14 @@
|
||||||
#define CHUNKSIZE 1024
|
#define CHUNKSIZE 1024
|
||||||
|
|
||||||
struct context {
|
struct context {
|
||||||
|
gboolean got_header;
|
||||||
uint64_t samplerate;
|
uint64_t samplerate;
|
||||||
int maxchannels;
|
unsigned int maxchannels;
|
||||||
int channelcount;
|
unsigned int channelcount;
|
||||||
int downsample;
|
int downsample;
|
||||||
unsigned compress;
|
unsigned compress;
|
||||||
int64_t skip;
|
int64_t skip;
|
||||||
|
gboolean skip_until_end;
|
||||||
GSList *channels;
|
GSList *channels;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,81 +87,56 @@ struct vcd_channel {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* 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.
|
* Reads a single VCD section from input file and parses it to name/contents.
|
||||||
* e.g. $timescale 1ps $end => "timescale" "1ps"
|
* 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;
|
gboolean status;
|
||||||
GString *sname, *scontents;
|
unsigned int pos;
|
||||||
|
|
||||||
/* Skip any initial white-space */
|
*name = *contents = NULL;
|
||||||
if (!read_until(file, NULL, 'N')) return FALSE;
|
status = FALSE;
|
||||||
|
pos = 0;
|
||||||
|
|
||||||
|
/* Skip any initial white-space. */
|
||||||
|
while (pos < buf->len && g_ascii_isspace(buf->str[pos]))
|
||||||
|
pos++;
|
||||||
|
|
||||||
/* Section tag should start with $. */
|
/* Section tag should start with $. */
|
||||||
if (fgetc(file) != '$')
|
if (buf->str[pos++] != '$')
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
/* Read the section tag */
|
|
||||||
sname = g_string_sized_new(32);
|
sname = g_string_sized_new(32);
|
||||||
status = read_until(file, sname, 'W');
|
scontent = g_string_sized_new(128);
|
||||||
|
|
||||||
/* Skip whitespace before content */
|
/* Read the section tag. */
|
||||||
status = status && read_until(file, NULL, 'N');
|
while (pos < buf->len && !g_ascii_isspace(buf->str[pos]))
|
||||||
|
g_string_append_c(sname, buf->str[pos++]);
|
||||||
|
|
||||||
/* Read the content */
|
/* Skip whitespace before content. */
|
||||||
scontents = g_string_sized_new(128);
|
while (pos < buf->len && g_ascii_isspace(buf->str[pos]))
|
||||||
status = status && read_until(file, scontents, '$');
|
pos++;
|
||||||
g_strchomp(scontents->str);
|
|
||||||
|
/* 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);
|
*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;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,12 +148,6 @@ static void free_channel(void *data)
|
||||||
g_free(vcd_ch);
|
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. */
|
/* Remove empty parts from an array returned by g_strsplit. */
|
||||||
static void remove_empty_parts(gchar **parts)
|
static void remove_empty_parts(gchar **parts)
|
||||||
{
|
{
|
||||||
|
@ -194,14 +166,18 @@ static void remove_empty_parts(gchar **parts)
|
||||||
* Parse VCD header to get values for context structure.
|
* Parse VCD header to get values for context structure.
|
||||||
* The context structure should be zeroed before calling this.
|
* 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;
|
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);
|
sr_dbg("Section '%s', contents '%s'.", name, contents);
|
||||||
|
|
||||||
if (g_strcmp0(name, "enddefinitions") == 0) {
|
if (g_strcmp0(name, "enddefinitions") == 0) {
|
||||||
|
@ -211,22 +187,22 @@ static gboolean parse_header(FILE *file, struct context *ctx)
|
||||||
/*
|
/*
|
||||||
* The standard allows for values 1, 10 or 100
|
* The standard allows for values 1, 10 or 100
|
||||||
* and units s, ms, us, ns, ps and fs.
|
* and units s, ms, us, ns, ps and fs.
|
||||||
* */
|
*/
|
||||||
if (sr_parse_period(contents, &p, &q) == SR_OK) {
|
if (sr_parse_period(contents, &p, &q) == SR_OK) {
|
||||||
ctx->samplerate = q / p;
|
inc->samplerate = q / p;
|
||||||
if (q % p != 0) {
|
if (q % p != 0) {
|
||||||
/* Does not happen unless time value is non-standard */
|
/* Does not happen unless time value is non-standard */
|
||||||
sr_warn("Inexact rounding of samplerate, %" PRIu64 " / %" PRIu64 " to %" PRIu64 " Hz.",
|
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 {
|
} else {
|
||||||
sr_err("Parsing timescale failed.");
|
sr_err("Parsing timescale failed.");
|
||||||
}
|
}
|
||||||
} else if (g_strcmp0(name, "var") == 0) {
|
} else if (g_strcmp0(name, "var") == 0) {
|
||||||
/* Format: $var type size identifier reference $end */
|
/* Format: $var type size identifier reference $end */
|
||||||
gchar **parts = g_strsplit_set(contents, " \r\n\t", 0);
|
parts = g_strsplit_set(contents, " \r\n\t", 0);
|
||||||
remove_empty_parts(parts);
|
remove_empty_parts(parts);
|
||||||
|
|
||||||
if (g_strv_length(parts) != 4)
|
if (g_strv_length(parts) != 4)
|
||||||
|
@ -235,15 +211,17 @@ static gboolean parse_header(FILE *file, struct context *ctx)
|
||||||
sr_info("Unsupported signal type: '%s'", parts[0]);
|
sr_info("Unsupported signal type: '%s'", parts[0]);
|
||||||
else if (strtol(parts[1], NULL, 10) != 1)
|
else if (strtol(parts[1], NULL, 10) != 1)
|
||||||
sr_info("Unsupported signal size: '%s'", parts[1]);
|
sr_info("Unsupported signal size: '%s'", parts[1]);
|
||||||
else if (ctx->channelcount >= ctx->maxchannels)
|
else if (inc->channelcount >= inc->maxchannels)
|
||||||
sr_warn("Skipping '%s' because only %d channels requested.", parts[3], ctx->maxchannels);
|
sr_warn("Skipping '%s' because only %d channels requested.",
|
||||||
|
parts[3], inc->maxchannels);
|
||||||
else {
|
else {
|
||||||
sr_info("Channel %d is '%s' identified by '%s'.", ctx->channelcount, parts[3], parts[2]);
|
sr_info("Channel %d is '%s' identified by '%s'.",
|
||||||
|
inc->channelcount, parts[3], parts[2]);
|
||||||
vcd_ch = g_malloc(sizeof(struct vcd_channel));
|
vcd_ch = g_malloc(sizeof(struct vcd_channel));
|
||||||
vcd_ch->identifier = g_strdup(parts[2]);
|
vcd_ch->identifier = g_strdup(parts[2]);
|
||||||
vcd_ch->name = g_strdup(parts[3]);
|
vcd_ch->name = g_strdup(parts[3]);
|
||||||
ctx->channels = g_slist_append(ctx->channels, vcd_ch);
|
inc->channels = g_slist_append(inc->channels, vcd_ch);
|
||||||
ctx->channelcount++;
|
inc->channelcount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_strfreev(parts);
|
g_strfreev(parts);
|
||||||
|
@ -252,107 +230,35 @@ static gboolean parse_header(FILE *file, struct context *ctx)
|
||||||
g_free(name); name = NULL;
|
g_free(name); name = NULL;
|
||||||
g_free(contents); contents = NULL;
|
g_free(contents); contents = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(name);
|
g_free(name);
|
||||||
g_free(contents);
|
g_free(contents);
|
||||||
|
|
||||||
|
inc->got_header = status;
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int format_match(const char *filename)
|
static int format_match(GHashTable *metadata)
|
||||||
{
|
{
|
||||||
FILE *file;
|
GString *buf, *tmpbuf;
|
||||||
gchar *name = NULL, *contents = NULL;
|
|
||||||
gboolean status;
|
gboolean status;
|
||||||
|
gchar *name, *contents;
|
||||||
|
|
||||||
file = fopen(filename, "r");
|
buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER));
|
||||||
if (file == NULL)
|
tmpbuf = g_string_new_len(buf->str, buf->len);
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we can parse the first section correctly,
|
* If we can parse the first section correctly,
|
||||||
* then it is assumed to be a VCD file.
|
* then it is assumed to be a VCD file.
|
||||||
*/
|
*/
|
||||||
status = parse_section(file, &name, &contents);
|
status = parse_section(tmpbuf, &name, &contents);
|
||||||
status = status && (*name != '\0');
|
g_string_free(tmpbuf, TRUE);
|
||||||
|
|
||||||
g_free(name);
|
g_free(name);
|
||||||
g_free(contents);
|
g_free(contents);
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int init(struct sr_input *in, const char *filename)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
param = g_hash_table_lookup(in->param, "downsample");
|
|
||||||
if (param) {
|
|
||||||
ctx->downsample = strtoul(param, NULL, 10);
|
|
||||||
if (ctx->downsample < 1)
|
|
||||||
ctx->downsample = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
param = g_hash_table_lookup(in->param, "compress");
|
|
||||||
if (param)
|
|
||||||
ctx->compress = strtoul(param, NULL, 10);
|
|
||||||
|
|
||||||
param = g_hash_table_lookup(in->param, "skip");
|
|
||||||
if (param)
|
|
||||||
ctx->skip = strtoul(param, NULL, 10) / ctx->downsample;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Maximum number of channels to parse from the VCD */
|
|
||||||
ctx->maxchannels = num_channels;
|
|
||||||
|
|
||||||
/* Create a virtual device. */
|
|
||||||
in->sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, NULL, NULL, NULL);
|
|
||||||
in->internal = ctx;
|
|
||||||
|
|
||||||
for (i = 0; i < num_channels; i++) {
|
|
||||||
snprintf(name, SR_MAX_CHANNELNAME_LEN, "%d", i);
|
|
||||||
|
|
||||||
if (!(ch = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, name))) {
|
|
||||||
release_context(ctx);
|
|
||||||
return SR_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
in->sdi->channels = g_slist_append(in->sdi->channels, ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Send N samples of the given value. */
|
/* Send N samples of the given value. */
|
||||||
static void send_samples(const struct sr_dev_inst *sdi, uint64_t sample, uint64_t count)
|
static void send_samples(const struct sr_dev_inst *sdi, uint64_t sample, uint64_t count)
|
||||||
{
|
{
|
||||||
|
@ -384,161 +290,262 @@ static void send_samples(const struct sr_dev_inst *sdi, uint64_t sample, uint64_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse the data section of VCD */
|
/* Parse a set of lines from the data section. */
|
||||||
static void parse_contents(FILE *file, const struct sr_dev_inst *sdi, struct context *ctx)
|
static void parse_contents(const struct sr_input *in, char *data)
|
||||||
{
|
{
|
||||||
GString *token = g_string_sized_new(32);
|
struct context *inc;
|
||||||
|
struct vcd_channel *vcd_ch;
|
||||||
|
GSList *l;
|
||||||
|
uint64_t timestamp, prev_timestamp, prev_values;
|
||||||
|
unsigned int bit, i, j;
|
||||||
|
char **tokens;
|
||||||
|
|
||||||
uint64_t prev_timestamp = 0;
|
inc = in->priv;
|
||||||
uint64_t prev_values = 0;
|
prev_timestamp = prev_values = 0;
|
||||||
|
|
||||||
/* Read one space-delimited token at a time. */
|
/* Read one space-delimited token at a time. */
|
||||||
while (read_until(file, NULL, 'N') && read_until(file, token, 'W')) {
|
tokens = g_strsplit_set(data, " \t\r\n", 0);
|
||||||
if (token->str[0] == '#' && g_ascii_isdigit(token->str[1])) {
|
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 */
|
/* Numeric value beginning with # is a new timestamp value */
|
||||||
uint64_t timestamp;
|
timestamp = strtoull(tokens[i] + 1, NULL, 10);
|
||||||
timestamp = strtoull(token->str + 1, NULL, 10);
|
|
||||||
|
|
||||||
if (ctx->downsample > 1)
|
if (inc->downsample > 1)
|
||||||
timestamp /= ctx->downsample;
|
timestamp /= inc->downsample;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Skip < 0 => skip until first timestamp.
|
* Skip < 0 => skip until first timestamp.
|
||||||
* Skip = 0 => don't skip
|
* Skip = 0 => don't skip
|
||||||
* Skip > 0 => skip until timestamp >= skip.
|
* Skip > 0 => skip until timestamp >= skip.
|
||||||
*/
|
*/
|
||||||
if (ctx->skip < 0) {
|
if (inc->skip < 0) {
|
||||||
ctx->skip = timestamp;
|
inc->skip = timestamp;
|
||||||
prev_timestamp = timestamp;
|
prev_timestamp = timestamp;
|
||||||
} else if (ctx->skip > 0 && timestamp < (uint64_t)ctx->skip) {
|
} else if (inc->skip > 0 && timestamp < (uint64_t)inc->skip) {
|
||||||
prev_timestamp = ctx->skip;
|
prev_timestamp = inc->skip;
|
||||||
}
|
} else if (timestamp == prev_timestamp) {
|
||||||
else if (timestamp == prev_timestamp) {
|
|
||||||
/* Ignore repeated timestamps (e.g. sigrok outputs these) */
|
/* Ignore repeated timestamps (e.g. sigrok outputs these) */
|
||||||
}
|
} else {
|
||||||
else {
|
if (inc->compress != 0 && timestamp - prev_timestamp > inc->compress) {
|
||||||
if (ctx->compress != 0 && timestamp - prev_timestamp > ctx->compress)
|
|
||||||
{
|
|
||||||
/* Compress long idle periods */
|
/* Compress long idle periods */
|
||||||
prev_timestamp = timestamp - ctx->compress;
|
prev_timestamp = timestamp - inc->compress;
|
||||||
}
|
}
|
||||||
|
|
||||||
sr_dbg("New timestamp: %" PRIu64, timestamp);
|
sr_dbg("New timestamp: %" PRIu64, timestamp);
|
||||||
|
|
||||||
/* Generate samples from prev_timestamp up to timestamp - 1. */
|
/* Generate samples from prev_timestamp up to timestamp - 1. */
|
||||||
send_samples(sdi, prev_values, timestamp - prev_timestamp);
|
send_samples(in->sdi, prev_values, timestamp - prev_timestamp);
|
||||||
prev_timestamp = timestamp;
|
prev_timestamp = timestamp;
|
||||||
}
|
}
|
||||||
} else if (token->str[0] == '$' && token->len > 1) {
|
} else if (tokens[i][0] == '$' && tokens[i][1] != '\0') {
|
||||||
/* This is probably a $dumpvars, $comment or similar.
|
/*
|
||||||
* $dump* contain useful data, but other tags will be skipped until $end. */
|
* This is probably a $dumpvars, $comment or similar.
|
||||||
if (g_strcmp0(token->str, "$dumpvars") == 0
|
* $dump* contain useful data.
|
||||||
|| g_strcmp0(token->str, "$dumpon") == 0
|
*/
|
||||||
|| g_strcmp0(token->str, "$dumpoff") == 0
|
if (g_strcmp0(tokens[i], "$dumpvars") == 0
|
||||||
|| g_strcmp0(token->str, "$end") == 0) {
|
|| g_strcmp0(tokens[i], "$dumpon") == 0
|
||||||
|
|| g_strcmp0(tokens[i], "$dumpoff") == 0
|
||||||
|
|| g_strcmp0(tokens[i], "$end") == 0) {
|
||||||
/* Ignore, parse contents as normally. */
|
/* Ignore, parse contents as normally. */
|
||||||
} else {
|
} else {
|
||||||
/* Skip until $end */
|
/* Ignore this and future lines until $end. */
|
||||||
read_until(file, NULL, '$');
|
inc->skip_until_end = TRUE;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else if (strchr("bBrR", tokens[i][0]) != NULL) {
|
||||||
else if (strchr("bBrR", token->str[0]) != NULL) {
|
/* A vector value, not supported yet. */
|
||||||
/* A vector value. Skip it and also the following identifier. */
|
break;
|
||||||
read_until(file, NULL, 'N');
|
} else if (strchr("01xXzZ", tokens[i][0]) != NULL) {
|
||||||
read_until(file, NULL, 'W');
|
|
||||||
} else if (strchr("01xXzZ", token->str[0]) != NULL) {
|
|
||||||
/* A new 1-bit sample value */
|
/* A new 1-bit sample value */
|
||||||
int i, bit;
|
bit = (tokens[i][0] == '1');
|
||||||
GSList *l;
|
|
||||||
struct vcd_channel *vcd_ch;
|
|
||||||
|
|
||||||
bit = (token->str[0] == '1');
|
/*
|
||||||
|
* The identifier is either the next character, or, if
|
||||||
g_string_erase(token, 0, 1);
|
* there was whitespace after the bit, the next token.
|
||||||
if (token->len == 0) {
|
*/
|
||||||
/* There was a space between value and identifier.
|
if (tokens[i][1] == '\0') {
|
||||||
* Read in the rest.
|
if (!tokens[++i])
|
||||||
*/
|
/* Missing identifier */
|
||||||
read_until(file, NULL, 'N');
|
continue;
|
||||||
read_until(file, token, 'W');
|
} else {
|
||||||
|
for (j = 1; tokens[i][j]; j++)
|
||||||
|
tokens[i][j - 1] = tokens[i][j];
|
||||||
|
tokens[i][j - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0, l = ctx->channels; i < ctx->channelcount && l; i++, l = l->next) {
|
for (j = 0, l = inc->channels; j < inc->channelcount && l; j++, l = l->next) {
|
||||||
vcd_ch = l->data;
|
vcd_ch = l->data;
|
||||||
|
if (g_strcmp0(tokens[i], vcd_ch->identifier) == 0) {
|
||||||
if (g_strcmp0(token->str, vcd_ch->identifier) == 0) {
|
|
||||||
/* Found our channel */
|
/* Found our channel */
|
||||||
if (bit)
|
if (bit)
|
||||||
prev_values |= (uint64_t)1 << i;
|
prev_values |= (uint64_t)1 << j;
|
||||||
else
|
else
|
||||||
prev_values &= ~((uint64_t)1 << i);
|
prev_values &= ~((uint64_t)1 << j);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (j == inc->channelcount)
|
||||||
if (i == ctx->channelcount)
|
sr_dbg("Did not find channel for identifier '%s'.", tokens[i]);
|
||||||
sr_dbg("Did not find channel for identifier '%s'.", token->str);
|
|
||||||
} else {
|
} 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);
|
||||||
g_string_free(token, TRUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int loadfile(struct sr_input *in, const char *filename)
|
static int init(struct sr_input *in, GHashTable *options)
|
||||||
{
|
{
|
||||||
struct sr_datafeed_packet packet;
|
struct sr_channel *ch;
|
||||||
struct sr_datafeed_meta meta;
|
int num_channels, i;
|
||||||
struct sr_config *src;
|
char name[16];
|
||||||
FILE *file;
|
struct context *inc;
|
||||||
struct context *ctx;
|
|
||||||
uint64_t samplerate;
|
|
||||||
|
|
||||||
ctx = in->internal;
|
inc = g_malloc0(sizeof(struct context));
|
||||||
|
|
||||||
if ((file = fopen(filename, "r")) == NULL)
|
num_channels = g_variant_get_int32(g_hash_table_lookup(options, "numchannels"));
|
||||||
return SR_ERR;
|
if (num_channels < 1) {
|
||||||
|
sr_err("Invalid value for numchannels: must be at least 1.");
|
||||||
if (!parse_header(file, ctx)) {
|
return SR_ERR_ARG;
|
||||||
sr_err("VCD parsing failed");
|
|
||||||
fclose(file);
|
|
||||||
return SR_ERR;
|
|
||||||
}
|
}
|
||||||
|
if (num_channels > 64) {
|
||||||
|
sr_err("No more than 64 channels supported.");
|
||||||
|
return SR_ERR_ARG;
|
||||||
|
}
|
||||||
|
inc->maxchannels = num_channels;
|
||||||
|
|
||||||
/* Send header packet to the session bus. */
|
inc->downsample = g_variant_get_int32(g_hash_table_lookup(options, "downsample"));
|
||||||
std_session_send_df_header(in->sdi, LOG_PREFIX);
|
if (inc->downsample < 1)
|
||||||
|
inc->downsample = 1;
|
||||||
|
|
||||||
/* Send metadata about the SR_DF_LOGIC packets to come. */
|
inc->compress = g_variant_get_int32(g_hash_table_lookup(options, "compress"));
|
||||||
packet.type = SR_DF_META;
|
inc->skip = g_variant_get_int32(g_hash_table_lookup(options, "skip"));
|
||||||
packet.payload = &meta;
|
inc->skip /= inc->downsample;
|
||||||
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);
|
|
||||||
|
|
||||||
/* Parse the contents of the VCD file */
|
/* Create a virtual device. */
|
||||||
parse_contents(file, in->sdi, ctx);
|
in->sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, NULL, NULL, NULL);
|
||||||
|
in->priv = inc;
|
||||||
|
|
||||||
/* Send end packet to the session bus. */
|
for (i = 0; i < num_channels; i++) {
|
||||||
packet.type = SR_DF_END;
|
snprintf(name, 16, "%d", i);
|
||||||
sr_session_send(in->sdi, &packet);
|
ch = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, name);
|
||||||
|
in->sdi->channels = g_slist_append(in->sdi->channels, ch);
|
||||||
fclose(file);
|
}
|
||||||
release_context(ctx);
|
|
||||||
in->internal = NULL;
|
|
||||||
|
|
||||||
return SR_OK;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 receive(const struct sr_input *in, GString *buf)
|
||||||
|
{
|
||||||
|
struct sr_datafeed_packet packet;
|
||||||
|
struct sr_datafeed_meta meta;
|
||||||
|
struct sr_config *src;
|
||||||
|
struct context *inc;
|
||||||
|
uint64_t samplerate;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (buf->len == 0) {
|
||||||
|
/* End of stream. */
|
||||||
|
packet.type = SR_DF_END;
|
||||||
|
sr_session_send(in->sdi, &packet);
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) != SR_OK)
|
||||||
|
/* There was a header in there, but it was malformed. */
|
||||||
|
return SR_ERR;
|
||||||
|
|
||||||
|
std_session_send_df_header(in->sdi, LOG_PREFIX);
|
||||||
|
|
||||||
|
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);
|
||||||
|
sr_config_free(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cleanup(struct sr_input *in)
|
||||||
|
{
|
||||||
|
struct context *inc;
|
||||||
|
|
||||||
|
inc = in->priv;
|
||||||
|
g_slist_free_full(inc->channels, free_channel);
|
||||||
|
g_free(inc);
|
||||||
|
in->priv = NULL;
|
||||||
|
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sr_option options[] = {
|
||||||
|
{ "numchannels", "Max channels", "Maximum number of channels", NULL, NULL },
|
||||||
|
{ "skip", "Skip", "Skip until timestamp", NULL, NULL },
|
||||||
|
{ "downsample", "Downsample", "Divide samplerate by factor", NULL, NULL },
|
||||||
|
{ "compress", "Compress", "Compress idle periods longer than this value", NULL, NULL },
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sr_option *get_options(void)
|
||||||
|
{
|
||||||
|
if (!options[0].def) {
|
||||||
|
options[0].def = g_variant_ref_sink(g_variant_new_int32(DEFAULT_NUM_CHANNELS));
|
||||||
|
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 = {
|
SR_PRIV struct sr_input_module input_vcd = {
|
||||||
.id = "vcd",
|
.id = "vcd",
|
||||||
|
.name = "VCD",
|
||||||
.desc = "Value Change Dump",
|
.desc = "Value Change Dump",
|
||||||
|
.metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
|
||||||
|
.options = get_options,
|
||||||
.format_match = format_match,
|
.format_match = format_match,
|
||||||
.init = init,
|
.init = init,
|
||||||
.loadfile = loadfile,
|
.receive = receive,
|
||||||
|
.cleanup = cleanup,
|
||||||
};
|
};
|
||||||
|
|
|
@ -145,7 +145,7 @@ static int find_data_chunk(GString *buf, int initial_offset)
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int initial_receive(const struct sr_input *in)
|
static int initial_receive(struct sr_input *in)
|
||||||
{
|
{
|
||||||
struct sr_datafeed_packet packet;
|
struct sr_datafeed_packet packet;
|
||||||
struct sr_datafeed_meta meta;
|
struct sr_datafeed_meta meta;
|
||||||
|
@ -159,7 +159,7 @@ static int initial_receive(const struct sr_input *in)
|
||||||
/* Shouldn't happen. */
|
/* Shouldn't happen. */
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
|
|
||||||
inc = in->sdi->priv = g_malloc(sizeof(struct context));
|
inc = in->priv = g_malloc(sizeof(struct context));
|
||||||
if (parse_wav_header(in->buf, inc) != SR_OK)
|
if (parse_wav_header(in->buf, inc) != SR_OK)
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ static void send_chunk(const struct sr_input *in, int offset, int num_samples)
|
||||||
int total_samples, samplenum;
|
int total_samples, samplenum;
|
||||||
char *s, *d;
|
char *s, *d;
|
||||||
|
|
||||||
inc = in->sdi->priv;
|
inc = in->priv;
|
||||||
|
|
||||||
s = in->buf->str + offset;
|
s = in->buf->str + offset;
|
||||||
d = (char *)fdata;
|
d = (char *)fdata;
|
||||||
|
@ -251,8 +251,8 @@ static int receive(const struct sr_input *in, GString *buf)
|
||||||
|
|
||||||
g_string_append_len(in->buf, buf->str, buf->len);
|
g_string_append_len(in->buf, buf->str, buf->len);
|
||||||
|
|
||||||
if (!in->sdi->priv) {
|
if (!in->priv) {
|
||||||
if (initial_receive(in) != SR_OK)
|
if (initial_receive((struct sr_input *)in) != SR_OK)
|
||||||
return SR_ERR;
|
return SR_ERR;
|
||||||
if (in->buf->len < MIN_DATA_CHUNK_OFFSET) {
|
if (in->buf->len < MIN_DATA_CHUNK_OFFSET) {
|
||||||
/*
|
/*
|
||||||
|
@ -262,7 +262,7 @@ static int receive(const struct sr_input *in, GString *buf)
|
||||||
return SR_OK;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inc = in->sdi->priv;
|
inc = in->priv;
|
||||||
|
|
||||||
if (!inc->found_data) {
|
if (!inc->found_data) {
|
||||||
/* Skip past size of 'fmt ' chunk. */
|
/* Skip past size of 'fmt ' chunk. */
|
||||||
|
@ -306,6 +306,14 @@ static int receive(const struct sr_input *in, GString *buf)
|
||||||
return SR_OK;
|
return SR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int cleanup(struct sr_input *in)
|
||||||
|
{
|
||||||
|
g_free(in->priv);
|
||||||
|
in->priv = NULL;
|
||||||
|
|
||||||
|
return SR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
SR_PRIV struct sr_input_module input_wav = {
|
SR_PRIV struct sr_input_module input_wav = {
|
||||||
.id = "wav",
|
.id = "wav",
|
||||||
.name = "WAV",
|
.name = "WAV",
|
||||||
|
@ -314,5 +322,6 @@ SR_PRIV struct sr_input_module input_wav = {
|
||||||
.format_match = format_match,
|
.format_match = format_match,
|
||||||
.init = init,
|
.init = init,
|
||||||
.receive = receive,
|
.receive = receive,
|
||||||
|
.cleanup = cleanup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue