201 lines
10 KiB
C
201 lines
10 KiB
C
/* 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 <Python.h>
|
|
#include <libsigrokdecode.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
#include <dirent.h>
|
|
|
|
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;i<chain_count;++i) if (chain[i]==d) return 0;
|
|
for (GSList *i=d->inputs;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;index<chain_count;++index) {
|
|
struct srd_decoder *d=chain[index];
|
|
GHashTable *options=g_hash_table_new_full(g_str_hash,g_str_equal,g_free,(GDestroyNotify)g_variant_unref);
|
|
for (GSList *p=d->options;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;i<count;++i) for (unsigned ch=0;ch<8;++ch) if (samples[i]&(1u<<ch)) planes[ch][i/8]|=1u<<(i%8);
|
|
const uint8_t **buffers=g_new0(const uint8_t *,base->dec_num_channels);
|
|
uint8_t *constants=g_new0(uint8_t,base->dec_num_channels);
|
|
for (int ch=0;ch<base->dec_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;
|
|
}
|