/* 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; }