From f0edb70dbee2f6ce89fd7c8e0db8536553382392 Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Mon, 9 Nov 2026 02:03:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D1=8F=D0=B4=D1=80=D0=BE=20=D1=86=D0=B8=D1=84=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20Scope=20=D0=B8=20=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D0=BF=D1=82=D0=B5=D1=80=20=D0=B4=D0=B5=D0=BA=D0=BE=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D0=B2=20DSView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- c/logic-scope/README.md | 21 +++ c/logic-scope/include/logic_scope.h | 60 ++++++++ c/logic-scope/include/scope_decoders.h | 75 ++++++++++ tools/scope-decoders/README.md | 44 ++++++ tools/scope-decoders/build_arm.py | 39 +++++ tools/scope-decoders/scope-decode | 7 + tools/scope-decoders/scope_decode.c | 200 +++++++++++++++++++++++++ tools/scope-decoders/test_worker.py | 28 ++++ 8 files changed, 474 insertions(+) create mode 100644 c/logic-scope/README.md create mode 100644 c/logic-scope/include/logic_scope.h create mode 100644 c/logic-scope/include/scope_decoders.h create mode 100644 tools/scope-decoders/README.md create mode 100644 tools/scope-decoders/build_arm.py create mode 100644 tools/scope-decoders/scope-decode create mode 100644 tools/scope-decoders/scope_decode.c create mode 100644 tools/scope-decoders/test_worker.py diff --git a/c/logic-scope/README.md b/c/logic-scope/README.md new file mode 100644 index 0000000..96c4403 --- /dev/null +++ b/c/logic-scope/README.md @@ -0,0 +1,21 @@ +# Logic inputs in a native Scope UI + +`include/logic_scope.h` is the portable, allocation-free C model for eight +logic inputs and PWM duty reconstruction. The FPGA register mapping and +framebuffer UI belong to the board application. + +- `LogicScope`: bounded sample buffer, per-channel OFF/LOGIC/PWM mode, + sample clock/divider, capture origin, and retained snapshot. +- `logic_scope_plan`: chooses a count/divider for a requested duration; + limits are 65,536 samples and divider 65,535. Longer requested intervals + produce shorter captures; the UI must not stretch them to fill the view. +- `logic_scope_pwm`: emits midpoint/value pairs for complete active-high + PWM periods. Value is `2 * high_samples / period_samples - 1`. Initial + and final incomplete periods are omitted; constant levels have no PWM + period and emit no points. This is normalized modulation, not voltage. + +The RK7020 consumer is `zynq_7020_OSC/demo/hdmi_usb_menu/`. +Its `tests/test_logic_scope.c` checks the capture plan, phase-shifted PWM, +constant inputs, menu selection, retained snapshots, and renderer bounds. + +`include/scope_decoders.h` contains the portable DSView catalogue/configuration/annotation model; the isolated ARM adapter is in `tools/scope-decoders`. The framebuffer UI and UIO acquisition live in the Zynq board project. diff --git a/c/logic-scope/include/logic_scope.h b/c/logic-scope/include/logic_scope.h new file mode 100644 index 0000000..a6069d7 --- /dev/null +++ b/c/logic-scope/include/logic_scope.h @@ -0,0 +1,60 @@ +#ifndef SET_LOGIC_SCOPE_H +#define SET_LOGIC_SCOPE_H +#include +#include +#include + +#define LOGIC_SCOPE_MAX 65536u +typedef struct { + uint8_t samples[LOGIC_SCOPE_MAX]; + unsigned mode[8]; /* 0 off, 1 logic, 2 PWM envelope */ + unsigned count, clock, divider, running, single, generation; + double origin_s; + char status[96]; +} LogicScope; + +static inline unsigned logic_scope_channels(const LogicScope *s) +{ + unsigned n=0; + for (unsigned i=0;i<8;++i) n+=s->mode[i]!=0; + return n; +} + +/* Capture the requested interval with the finest representable sample period. */ +static inline int logic_scope_plan(double span, unsigned clock, unsigned depth, + unsigned *count, unsigned *divider) +{ + if (!isfinite(span) || span<=0 || !clock || depth<2 || depth>LOGIC_SCOPE_MAX) return 0; + double ticks=ceil(span*clock/depth); + ticks=fmax(1,fmin(65536,ticks)); + *divider=(unsigned)ticks-1; + *count=(unsigned)fmax(2,fmin(depth,ceil(span*clock/ticks))); + return 1; +} + +typedef void (*LogicScopePoint)(void *context, double time, double value); +/* Ignore clipped first/last cycles. A constant level has no measurable period. */ +static inline unsigned logic_scope_pwm(const LogicScope *s, unsigned ch, + LogicScopePoint point, void *context) +{ + if (ch>=8 || !s->clock || s->count<2) return 0; + unsigned rise=0,fall=0,have_rise=0,have_fall=0,n=0; + unsigned previous=(s->samples[0]>>ch)&1u; + double dt=(s->divider+1.0)/s->clock; + for (unsigned i=1;icount;++i) { + unsigned level=(s->samples[i]>>ch)&1u; + if (level!=previous) { + if (level) { + if (have_rise && have_fall) { + point(context,s->origin_s+(rise+(i-rise)*0.5)*dt, + 2.0*(fall-rise)/(i-rise)-1.0); + ++n; + } + rise=i; have_rise=1; have_fall=0; + } else if (have_rise) { fall=i; have_fall=1; } + } + previous=level; + } + return n; +} +#endif diff --git a/c/logic-scope/include/scope_decoders.h b/c/logic-scope/include/scope_decoders.h new file mode 100644 index 0000000..f67c8ae --- /dev/null +++ b/c/logic-scope/include/scope_decoders.h @@ -0,0 +1,75 @@ +#ifndef SCOPE_DECODERS_H +#define SCOPE_DECODERS_H +#include +#include +#include +#include +#define SD_ITEMS 256 +#define SD_FIELDS 2048 +#define SD_RESULTS 4096 +typedef struct { char id[64],name[96],inputs[128],outputs[128]; int unavailable; } ScopeDecoder; +typedef struct { int owner,channel,mandatory; char id[64],name[128],value[128],choices[512]; } ScopeDecoderField; +typedef struct { uint64_t start,end; char decoder[64],row[96],text[256]; } ScopeAnnotation; +typedef struct { + ScopeDecoder items[SD_ITEMS]; ScopeDecoderField fields[SD_FIELDS]; ScopeAnnotation results[SD_RESULTS]; + unsigned order[SD_ITEMS],count,field_count,result_count; int open,view,page,selected,edit,request,busy; + unsigned chain[8],chain_count,visible_fields[128],visible_count; double rate; + char status[160],edit_text[128]; +} ScopeDecoders; +static inline void sd_copy(char *to,size_t size,const char *from) { size_t n=strlen(from); if(n>=size)n=size-1; memcpy(to,from,n);to[n]=0; } +static inline unsigned sd_split(char *line,char **out,unsigned cap) { + unsigned n=0; char *p=line; while(ncount;++i)if(!strcmp(s->items[i].id,id))return (int)i;return -1; } +static inline int sd_has(const char *list,const char *token) { + size_t n=strlen(token);for(const char *p=list;*p;) {const char *e=strchr(p,'|');size_t k=e?(size_t)(e-p):strlen(p);if(k==n&&!strncmp(p,token,n))return 1;if(!e)break;p=e+1;}return 0; +} +static inline int sd_catalog(ScopeDecoders *s,const char *path) { + FILE *f=fopen(path,"r");if(!f)return 0;s->count=s->field_count=0;char line[4096]; + while(fgets(line,sizeof(line),f)) {char *v[8];unsigned n=sd_split(line,v,8); + if((!strcmp(v[0],"D")||!strcmp(v[0],"E"))&&n>=3&&s->countitems[s->count++];memset(d,0,sizeof(*d));sd_copy(d->id,sizeof(d->id),v[1]);sd_copy(d->name,sizeof(d->name),v[2]);d->unavailable=v[0][0]=='E'; + if(n>=5){sd_copy(d->inputs,sizeof(d->inputs),v[3]);sd_copy(d->outputs,sizeof(d->outputs),v[4]);} + } else if((v[0][0]=='C'||v[0][0]=='O')&&n>=5&&s->field_countfields[s->field_count++];memset(d,0,sizeof(*d));d->owner=owner;d->channel=v[0][0]=='C'; + sd_copy(d->id,sizeof(d->id),v[2]);sd_copy(d->name,sizeof(d->name),v[3]);d->mandatory=d->channel&&atoi(v[4]); + sd_copy(d->value,sizeof(d->value),d->channel?"-1":n>5?v[5]:"");if(n>6)sd_copy(d->choices,sizeof(d->choices),v[6]); + } + }fclose(f);for(unsigned i=0;icount;++i){unsigned j=i;s->order[i]=i;while(j>0&&strcmp(s->items[s->order[j-1]].id,s->items[i].id)>0){s->order[j]=s->order[j-1];--j;}s->order[j]=i;}snprintf(s->status,sizeof(s->status),"%u DSView decoders / D0-D7 inputs",s->count);return s->count!=0; +} +static inline int sd_resolve(ScopeDecoders *s,unsigned id,unsigned depth) { + if(depth>=8||id>=s->count||s->items[id].unavailable)return 0; + ScopeDecoder *d=&s->items[id];if(sd_has(d->inputs,"logic")){s->chain[s->chain_count++]=id;return 1;} + char inputs[128];sd_copy(inputs,sizeof(inputs),d->inputs);char *p=inputs; + while(*p) {char *end=strchr(p,'|');if(end)*end=0;int base=sd_find(s,p); + if(base>=0&&(unsigned)base!=id&&sd_resolve(s,(unsigned)base,depth+1)){s->chain[s->chain_count++]=id;return 1;} + for(int direct=1;direct>=0;--direct)for(unsigned i=0;icount;++i)if(i!=id&&sd_has(s->items[i].inputs,"logic")==direct&&sd_has(s->items[i].outputs,p)&&sd_resolve(s,i,depth+1)){s->chain[s->chain_count++]=id;return 1;} + if(!end)break;p=end+1; + }return 0; +} +static inline void sd_select(ScopeDecoders *s,unsigned id) { + s->selected=(int)id;s->page=0;s->view=1;s->edit=-1;s->chain_count=s->visible_count=0; + if(!sd_resolve(s,id,0)){sd_copy(s->status,sizeof(s->status),"No supported logic input stack / import failed");return;} + unsigned next=0; + for(unsigned c=0;cchain_count;++c)for(unsigned f=0;ffield_count;++f)if(s->fields[f].owner==(int)s->chain[c]&&s->visible_count<128){ + s->visible_fields[s->visible_count++]=f;ScopeDecoderField *field=&s->fields[f]; + if(field->channel&&field->mandatory&&atoi(field->value)<0&&next<8)snprintf(field->value,sizeof(field->value),"%u",next++); + } + sd_copy(s->status,sizeof(s->status),"Tap a field to change. DECODE uses the last digital snapshot."); +} +static inline int sd_config(const ScopeDecoders *s,const char *path) { + FILE *f=fopen(path,"w");if(!f)return 0; + for(unsigned c=0;cchain_count;++c){unsigned id=s->chain[c];fprintf(f,"[%s]\n",s->items[id].id); + for(unsigned i=0;ifield_count;++i){const ScopeDecoderField *d=&s->fields[i];if(d->owner==(int)id)fprintf(f,"%s.%s=%s\n",d->channel?"ch":"opt",d->id,d->value);}} + int error=ferror(f);return fclose(f)==0&&!error; +} +static inline int sd_results(ScopeDecoders *s,const char *path) { + FILE *f=fopen(path,"r");if(!f)return 0;char line[2048];s->result_count=0;sd_copy(s->status,sizeof(s->status),"No annotations / check channel mapping and sample rate"); + while(fgets(line,sizeof(line),f)){char *v[7];unsigned n=sd_split(line,v,7); + if(n>=6&&!strcmp(v[0],"A")&&s->result_countresults[s->result_count++];a->start=strtoull(v[1],NULL,10);a->end=strtoull(v[2],NULL,10);sd_copy(a->decoder,sizeof(a->decoder),v[3]);sd_copy(a->row,sizeof(a->row),v[4]);sd_copy(a->text,sizeof(a->text),v[5]);} + else if(n>=2&&!strcmp(v[0],"ERROR"))sd_copy(s->status,sizeof(s->status),v[1]); + else if(n>=3&&!strcmp(v[0],"DONE")&&s->result_count)snprintf(s->status,sizeof(s->status),"%u annotations / %s",s->result_count,v[2]); + }fclose(f);s->view=2;s->page=0;return 1; +} +#endif diff --git a/tools/scope-decoders/README.md b/tools/scope-decoders/README.md new file mode 100644 index 0000000..5dff54f --- /dev/null +++ b/tools/scope-decoders/README.md @@ -0,0 +1,44 @@ +# Scope DSView decoder worker + +Headless adapter for the DSView `libsigrokdecode4DSL` fork. Runs as an isolated +process using the existing ARM DSView Python 3.11/GLib runtime. It does not +start Qt, own the FPGA or modify output pins. + +`build_arm.py --help` lists toolchain/sysroot/source/output arguments. Prepare +the patched DSView sources and runtime sysroot using the board project's DSView +build first. The script compiles the decoder library from source, not cached +DSView objects. Deploy `scope-decode-arm.elf` and `scope-decode` beside `dsview/`. +The wrapper configures Python and the shared SETProtocol library. + +``` +./scope-decode --catalog /path/to/decoders catalog.tsv +./scope-decode --decode /path/to/decoders result.tsv 1:uart config.ini capture.bin +python3 test_worker.py ./scope-decode /path/to/decoders +``` + +Snapshot: `LDS1`, three little-endian uint32 values (clock Hz, divider, count), +then count bytes, bits D0-D7. Sample rate = clock / (divider + 1). +Maximum 65536 samples. Config is INI, one section per decoder ID, e.g.: + +```ini +[1:uart] +ch.rxtx=0 +opt.baudrate=9600 +``` + +Missing optional channels are disconnected. Mandatory channels must be mapped +to D0-D7. The worker resolves protocol stacks, prefers direct logic producers, +and passes options to each layer. It emits tab-separated D/C/O catalogue records, +E import errors, and A annotation records with start/end samples, decoder, row, +text. Module names and decoder IDs are intentionally distinct (e.g. `0-uart` +and `0:uart`). Numeric DSView annotations are exported as hexadecimal text. +Results retain up to 4096 annotations and explicitly report truncation. + +Catalogue and options come from the installed decoders, not a hand-written list. +The board UI must enforce a process timeout; the included integration uses 30 s. +`test_worker.py` checks both DSView UART variants with known bytes and timestamps. +This does not imply every protocol or electrical interface has been tested. + +The adapter is GPL-2.0-or-later; linking with DSView's GPL-3.0-or-later decoder +library produces a GPL-3.0-or-later executable. Retain DSView/libsigrokdecode and +Python/runtime notices when redistributing the runtime. diff --git a/tools/scope-decoders/build_arm.py b/tools/scope-decoders/build_arm.py new file mode 100644 index 0000000..428e70a --- /dev/null +++ b/tools/scope-decoders/build_arm.py @@ -0,0 +1,39 @@ +"""Build the headless DSView decoder worker against the prepared ARM runtime sysroot.""" +import argparse +from pathlib import Path +import subprocess + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--cc', type=Path, required=True) + parser.add_argument('--sysroot', type=Path, required=True) + parser.add_argument('--dsview-source', type=Path, required=True) + parser.add_argument('--fallback-include', type=Path) + parser.add_argument('--output', type=Path, required=True) + args = parser.parse_args() + cc, sysroot, source, out = [p.resolve() for p in (args.cc, args.sysroot, args.dsview_source, args.output)] + out.mkdir(parents=True, exist_ok=True) + includes = [source/'common', source/'libsigrokdecode4DSL', sysroot/'usr/include/python3.11', sysroot/'usr/include/glib-2.0', sysroot/'usr/lib/arm-linux-gnueabihf/glib-2.0/include'] + flags = [str(cc), '--sysroot='+str(sysroot), '-mcpu=cortex-a9', '-O2', '-std=c11', '-D_DEFAULT_SOURCE', '-pthread', '-isystem', str(sysroot/'usr/include/arm-linux-gnueabihf')] + if args.fallback_include: + flags += ['-idirafter', str(args.fallback_include.resolve())] + for path in includes: + flags += ['-I', str(path)] + objects = [] + files = sorted((source/'libsigrokdecode4DSL').glob('*.c')) + [source/'common/log/xlog.c', Path(__file__).with_name('scope_decode.c')] + if len(files) < 10: + raise RuntimeError('Prepare the DSView fork sources and ARM sysroot first') + for path in files: + target = out/('decoder-'+path.stem+'.o') + subprocess.run(flags + ['-c', str(path), '-o', str(target)], check=True) + objects.append(str(target)) + link = flags + objects + for path in (sysroot/'usr/lib/arm-linux-gnueabihf', sysroot/'lib/arm-linux-gnueabihf', sysroot/'usr/lib/gcc/arm-linux-gnueabihf/12'): + link += ['-B'+str(path)+'/', '-L'+str(path), '-Wl,-rpath-link,'+str(path)] + subprocess.run(link + ['-lglib-2.0', '-lpython3.11', '-lm', '-lpthread', '-o', str(out/'scope-decode-arm.elf')], check=True) + (out/'scope-decode').write_bytes(Path(__file__).with_name('scope-decode').read_bytes().replace(b'\r\n', b'\n')) + + +if __name__ == '__main__': + main() diff --git a/tools/scope-decoders/scope-decode b/tools/scope-decoders/scope-decode new file mode 100644 index 0000000..52d817d --- /dev/null +++ b/tools/scope-decoders/scope-decode @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu +here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +export PYTHONHOME="$here/dsview" +export PYTHONDONTWRITEBYTECODE=1 +export SETPROTOCOL_LIBRARY="$here/dsview/lib/libsetprotocol.so" +exec "$here/dsview/lib/ld-linux-armhf.so.3" --library-path "$here/dsview/lib" "$here/scope-decode-arm.elf" "$@" diff --git a/tools/scope-decoders/scope_decode.c b/tools/scope-decoders/scope_decode.c new file mode 100644 index 0000000..1655d85 --- /dev/null +++ b/tools/scope-decoders/scope_decode.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Headless adapter for the DSView libsigrokdecode fork. No Qt dependency. + * Input snapshots use LDS1 + LE32 clock/divider/count + count byte samples. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static FILE *output; +static unsigned annotations; +static void field(const char *text) +{ + if (!text) return; + for (;*text;++text) fputc(*text=='\t'||*text=='\n'||*text=='\r'?' ':*text,output); +} +static void variant(GVariant *v) +{ + if (g_variant_is_of_type(v,G_VARIANT_TYPE_STRING)) field(g_variant_get_string(v,NULL)); + else { char *s=g_variant_print(v,FALSE); field(s); g_free(s); } +} +static void string_list(GSList *items) +{ + for (GSList *p=items;p;p=p->next) { if (p!=items) fputc('|',output); field(p->data); } +} +static void catalog(const char *path) +{ + PyGILState_STATE gil=PyGILState_Ensure(); + for (const GSList *p=srd_decoder_list();p;p=p->next) { + struct srd_decoder *d=p->data; + fputs("D\t",output); field(d->id); fputc('\t',output); field(d->name); + fputc('\t',output); string_list(d->inputs); fputc('\t',output); string_list(d->outputs); fputc('\n',output); + for (int optional=0;optional<2;++optional) for (GSList *c=optional?d->opt_channels:d->channels;c;c=c->next) { + struct srd_channel *ch=c->data; + fprintf(output,"C\t%s\t",d->id); field(ch->id); fputc('\t',output); field(ch->name); + fprintf(output,"\t%d\n",!optional); + } + for (GSList *o=d->options;o;o=o->next) { + struct srd_decoder_option *opt=o->data; + fprintf(output,"O\t%s\t",d->id); field(opt->id); fputc('\t',output); field(opt->desc); + fprintf(output,"\t%s\t",g_variant_get_type_string(opt->def)); variant(opt->def); fputc('\t',output); + for (GSList *v=opt->values;v;v=v->next) { if (v!=opt->values) fputc('|',output); variant(v->data); } + fputc('\n',output); + } + } + DIR *dir=opendir(path); struct dirent *entry; + if (dir) { + while ((entry=readdir(dir))) { + if (entry->d_name[0]=='.') continue; + int loaded=0; + for (const GSList *p=srd_decoder_list();p;p=p->next) { struct srd_decoder *d=p->data; const char *name=PyModule_GetName((PyObject *)d->py_mod); if(name && !strcmp(name,entry->d_name)) loaded=1; } + if(loaded) continue; + char *file=g_build_filename(path,entry->d_name,"pd.py",NULL); + if (g_file_test(file,G_FILE_TEST_IS_REGULAR)) { + PyObject *module=PyImport_ImportModule(entry->d_name); if(!module)PyErr_Print(); else Py_DECREF(module); + fputs("E\t",output); field(entry->d_name); fputs("\tImport failed; see decoder log\n",output); + } + g_free(file); + } + closedir(dir); + } + PyGILState_Release(gil); +} +static struct srd_decoder *chain[8]; +static unsigned chain_count; +static int resolve(struct srd_decoder *d,unsigned depth) +{ + if (!d || depth>=8) return 0; + for (unsigned i=0;iinputs;i;i=i->next) if (!strcmp(i->data,"logic")) { chain[chain_count++]=d; return 1; } + for (GSList *i=d->inputs;i;i=i->next) { + /* Prefer the canonical decoder named after its output protocol. */ + struct srd_decoder *base=srd_decoder_get_by_id(i->data); + if (base && base!=d && resolve(base,depth+1)) { chain[chain_count++]=d; return 1; } + for (int direct=1;direct>=0;--direct) for (const GSList *p=srd_decoder_list();p;p=p->next) { + base=p->data; + int logic=0; for(GSList *q=base->inputs;q;q=q->next) if(!strcmp(q->data,"logic"))logic=1; + if(logic!=direct)continue; + if (base==d) continue; + for (GSList *o=base->outputs;o;o=o->next) if (!strcmp(o->data,i->data) && resolve(base,depth+1)) { + chain[chain_count++]=d; return 1; + } + } + } + return 0; +} +static void annotation(struct srd_proto_data *data,void *context) +{ + (void)context; + struct srd_proto_data_annotation *a=data->data; + if (++annotations>4096) return; + const struct srd_decoder *d=data->pdo->di->decoder; + const char *row="Data"; + for (GSList *r=d->annotation_rows;r;r=r->next) { + struct srd_decoder_annotation_row *item=r->data; + for (GSList *c=item->ann_classes;c;c=c->next) + if (GPOINTER_TO_INT(c->data)==a->ann_class) row=item->desc; + } + fprintf(output,"A\t%llu\t%llu\t%s\t",(unsigned long long)data->start_sample,(unsigned long long)data->end_sample,d->id); + field(row); fputc('\t',output); + if (a->str_number_hex[0]) { fputs("0x",output); field(a->str_number_hex); } + else if (a->ann_text && a->ann_text[0]) field(a->ann_text[0]); + fputc('\n',output); +} +static GVariant *option_value(GVariant *original,const char *text) +{ + if (g_variant_is_of_type(original,G_VARIANT_TYPE_STRING)) return g_variant_new_string(text); + if (g_variant_is_of_type(original,G_VARIANT_TYPE_INT64)) { + char *end; gint64 v=g_ascii_strtoll(text,&end,0); return *end?NULL:g_variant_new_int64(v); + } + if (g_variant_is_of_type(original,G_VARIANT_TYPE_DOUBLE)) { + char *end; double v=g_ascii_strtod(text,&end); return *end||!isfinite(v)?NULL:g_variant_new_double(v); + } + if (g_variant_is_of_type(original,G_VARIANT_TYPE_BOOLEAN)) { + if (!strcmp(text,"true") || !strcmp(text,"1")) return g_variant_new_boolean(TRUE); + if (!strcmp(text,"false") || !strcmp(text,"0")) return g_variant_new_boolean(FALSE); + } + return NULL; +} +static uint32_t le32(const unsigned char *p) { return (uint32_t)p[0]|(uint32_t)p[1]<<8|(uint32_t)p[2]<<16|(uint32_t)p[3]<<24; } +static int decode(const char *id,const char *config,const char *snapshot) +{ + if (!resolve(srd_decoder_get_by_id(id),0)) { fputs("ERROR\tNo logic input stack for this decoder\n",output); return 1; } + GKeyFile *settings=g_key_file_new(); + if (!g_key_file_load_from_file(settings,config,G_KEY_FILE_NONE,NULL)) return 1; + unsigned char header[16],samples[65536]; + FILE *input=fopen(snapshot,"rb"); if (!input) return 1; + if (fread(header,1,16,input)!=16 || memcmp(header,"LDS1",4)) { fclose(input); return 1; } + unsigned clock=le32(header+4),divider=le32(header+8),count=le32(header+12); + if (!clock || divider>65535 || !count || count>65536 || fread(samples,1,count,input)!=count) { fclose(input); return 1; } + fclose(input); + struct srd_session *session=NULL; + struct srd_decoder_inst *previous=NULL,*base=NULL; + if (srd_session_new(&session)!=SRD_OK) return 1; + int result=1; + for (unsigned index=0;indexoptions;p;p=p->next) { + struct srd_decoder_option *o=p->data; + char *key=g_strconcat("opt.",o->id,NULL),*text=g_key_file_get_string(settings,d->id,key,NULL); g_free(key); + if (text) { + GVariant *value=option_value(o->def,text); g_free(text); + if (!value) { fprintf(output,"ERROR\tInvalid option %s/%s\n",d->id,o->id); g_hash_table_destroy(options); goto done; } + g_hash_table_insert(options,g_strdup(o->id),g_variant_ref_sink(value)); + } + } + struct srd_decoder_inst *di=srd_inst_new(session,d->id,options); + g_hash_table_destroy(options); if (!di) goto done; + if (!index) base=di; + if (previous && srd_inst_stack(session,previous,di)!=SRD_OK) goto done; + previous=di; + if (!index) { + GHashTable *channels=g_hash_table_new_full(g_str_hash,g_str_equal,g_free,(GDestroyNotify)g_variant_unref); + for (int optional=0;optional<2;++optional) for (GSList *p=optional?d->opt_channels:d->channels;p;p=p->next) { + struct srd_channel *ch=p->data; char *key=g_strconcat("ch.",ch->id,NULL); + GError *error=NULL; int physical=g_key_file_get_integer(settings,d->id,key,&error); g_free(key); + if (error) { g_error_free(error); physical=-1; } + if (physical<0 && optional) continue; + if (physical<0 || physical>7) { fprintf(output,"ERROR\tAssign %s/%s to D0-D7\n",d->id,ch->name); g_hash_table_destroy(channels); goto done; } + g_hash_table_insert(channels,g_strdup(ch->id),g_variant_ref_sink(g_variant_new_int32(physical))); + } + int ret=srd_inst_channel_set_all(di,channels); g_hash_table_destroy(channels); + if (ret!=SRD_OK) goto done; + } + } + srd_session_metadata_set(session,SRD_CONF_SAMPLERATE,g_variant_new_uint64((uint64_t)llround(clock/(divider+1.0)))); + srd_pd_output_callback_add(session,SRD_OUTPUT_ANN,annotation,NULL); + unsigned char planes[8][8192]; memset(planes,0,sizeof(planes)); + for (unsigned i=0;idec_num_channels); + uint8_t *constants=g_new0(uint8_t,base->dec_num_channels); + for (int ch=0;chdec_num_channels;++ch) if (base->dec_channelmap[ch]>=0) buffers[ch]=planes[base->dec_channelmap[ch]]; + char *error=NULL; + if (srd_session_start(session,&error)==SRD_OK && srd_session_send(session,0,count,buffers,constants,count,&error)==SRD_OK && srd_session_end(session,&error)==SRD_OK) result=0; + if (error) { fputs("ERROR\t",output); field(error); fputc('\n',output); g_free(error); } + /* Keep bitplanes alive until the decoder thread is joined. */ + srd_session_destroy(session); session=NULL; g_free(buffers); g_free(constants); + fprintf(output,"DONE\t%u\t%s\n",annotations,annotations>4096?"truncated to 4096 annotations":"complete"); +done: + if (session) srd_session_destroy(session); + g_key_file_free(settings); return result; +} +int main(int argc,char **argv) +{ + if (argc!=4 && argc!=7) return 2; + output=fopen(argv[3],"w"); if (!output) return 2; + srd_log_level(0); + if (srd_init(argv[2])!=SRD_OK) { fclose(output); return 3; } + srd_decoder_load_all(); + int result=0; + if (argc==4 && !strcmp(argv[1],"--catalog")) catalog(argv[2]); + else if (argc==7 && !strcmp(argv[1],"--decode")) result=decode(argv[4],argv[5],argv[6]); + else result=2; + fclose(output); srd_exit(); return result; +} diff --git a/tools/scope-decoders/test_worker.py b/tools/scope-decoders/test_worker.py new file mode 100644 index 0000000..4a75fa1 --- /dev/null +++ b/tools/scope-decoders/test_worker.py @@ -0,0 +1,28 @@ +"""Run a known UART vector through the real ARM DSView worker (on the board).""" +from pathlib import Path +import subprocess +import struct +import sys +import tempfile + +worker = Path(sys.argv[1]).resolve() +decoders = sys.argv[2] +with tempfile.TemporaryDirectory(prefix='scope-worker-test-') as temp: + root = Path(temp) + samples = bytearray([1]*32) + for value in (0x55, 0x41, 0xA3): + for bit in [0]+[(value>>i)&1 for i in range(8)]+[1, 1]: + samples.extend([bit]*16) + samples.extend([1]*64) + snapshot = root/'uart.bin' + snapshot.write_bytes(b'LDS1'+struct.pack('=6 and r[0]=='A' and r[4]=='RX/TX'] + assert [r[5] for r in data] == ['0x55', '0x41', '0xA3'], data + assert [int(r[1]) for r in data] == [32, 208, 384], data + print('PASS ARM worker: DSView 0:UART and 1:UART, 55 41 A3, exact sample timestamps')