restructure: code+libs under src/, runtime resources under data/
Separate the two concerns the repo root was mixing:
- src/ — bs/, modules/, libs/ (code + vendored libs)
- data/ — fpga_registry.yaml, probes.yaml, bsdl_files/, bscan_proxies/,
scripts/ (everything the tool reads at runtime, CWD-relative)
- doc/ — kept at the root
CMake: repoint DIR_MODULES/DIR_LIBS and add_subdirectory at src/; emit
the binary at the build/ root (build/bs) via CMAKE_RUNTIME_OUTPUT_DIRECTORY
instead of the nested build/src/bs/. The jtag_core ../../libs path still
resolves since modules and libs moved together.
Runtime default paths now point under data/ (fpga.c, probes.c, script.c
bsdl_files lookup, init.c config.script). Docs (README/tutorial/CLAUDE)
updated for the new layout, src/ module paths, and ./build/bs.
Validated on the IGLOO2/FlashPro: profiles, autoinit, and svf_play all
work run from the repo root.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
12
src/modules/probes/CMakeLists.txt
Normal file
12
src/modules/probes/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
file(GLOB_RECURSE ALL_SOURCES "*.c")
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
add_library(probes ${ALL_SOURCES})
|
||||
|
||||
# probes.yaml is parsed at runtime via libyaml (same as the fpga module).
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-0.1)
|
||||
target_link_libraries(probes PUBLIC PkgConfig::YAML)
|
||||
213
src/modules/probes/probes.c
Normal file
213
src/modules/probes/probes.c
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <yaml.h>
|
||||
|
||||
#include "probes.h"
|
||||
|
||||
/*
|
||||
* probes.yaml parser. Structure:
|
||||
*
|
||||
* defaults:
|
||||
* VAR: value
|
||||
* ...
|
||||
* profiles:
|
||||
* name:
|
||||
* VAR: value
|
||||
* other: {}
|
||||
*
|
||||
* Parsed once and cached. See probes.h for semantics.
|
||||
*/
|
||||
|
||||
#define DEFAULT_PROBES_FILE "data/probes.yaml"
|
||||
|
||||
typedef struct { char *key; char *val; } kv;
|
||||
typedef struct { char *name; kv *vars; int nvars; } profile;
|
||||
|
||||
static kv *g_def = NULL; static int g_ndef = 0;
|
||||
static profile *g_prof = NULL; static int g_nprof = 0;
|
||||
static int g_loaded = 0;
|
||||
static char g_source[1024] = "";
|
||||
|
||||
static char *xstrdup(const char *s)
|
||||
{
|
||||
char *p;
|
||||
size_t n;
|
||||
if (!s) return NULL;
|
||||
n = strlen(s) + 1;
|
||||
p = (char *)malloc(n);
|
||||
if (p) memcpy(p, s, n);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void add_kv(kv **arr, int *n, const char *k, const char *v)
|
||||
{
|
||||
kv *t = (kv *)realloc(*arr, (*n + 1) * sizeof(kv));
|
||||
if (!t) { fprintf(stderr, "probes: out of memory\n"); return; }
|
||||
*arr = t;
|
||||
t[*n].key = xstrdup(k);
|
||||
t[*n].val = xstrdup(v);
|
||||
(*n)++;
|
||||
}
|
||||
|
||||
static profile *add_profile(const char *name)
|
||||
{
|
||||
profile *t = (profile *)realloc(g_prof, (g_nprof + 1) * sizeof(profile));
|
||||
if (!t) { fprintf(stderr, "probes: out of memory\n"); return NULL; }
|
||||
g_prof = t;
|
||||
g_prof[g_nprof].name = xstrdup(name);
|
||||
g_prof[g_nprof].vars = NULL;
|
||||
g_prof[g_nprof].nvars = 0;
|
||||
return &g_prof[g_nprof++];
|
||||
}
|
||||
|
||||
/* Mapping context at each nesting depth. */
|
||||
enum { C_NONE = 0, C_ROOT, C_DEFAULTS, C_PROFILES, C_PROFBODY };
|
||||
|
||||
#define MAX_DEPTH 16
|
||||
|
||||
static int load(const char *path)
|
||||
{
|
||||
FILE *f;
|
||||
yaml_parser_t parser;
|
||||
yaml_event_t ev;
|
||||
int depth = 0, done = 0, rc = 0;
|
||||
int ctx[MAX_DEPTH];
|
||||
char *key[MAX_DEPTH];
|
||||
profile *cur = NULL;
|
||||
int i;
|
||||
|
||||
f = fopen(path, "rb");
|
||||
if (!f) return -1;
|
||||
if (!yaml_parser_initialize(&parser)) { fclose(f); return -1; }
|
||||
yaml_parser_set_input_file(&parser, f);
|
||||
|
||||
memset(ctx, 0, sizeof(ctx));
|
||||
memset(key, 0, sizeof(key));
|
||||
|
||||
while (!done) {
|
||||
if (!yaml_parser_parse(&parser, &ev)) {
|
||||
fprintf(stderr, "probes: YAML parse error in %s: %s (line %lu)\n",
|
||||
path, parser.problem ? parser.problem : "?",
|
||||
(unsigned long)parser.problem_mark.line + 1);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ev.type) {
|
||||
case YAML_MAPPING_START_EVENT: {
|
||||
int newc = C_NONE;
|
||||
if (depth == 0) {
|
||||
newc = C_ROOT;
|
||||
} else {
|
||||
int c = ctx[depth];
|
||||
const char *k = key[depth];
|
||||
if (c == C_ROOT) {
|
||||
if (k && !strcmp(k, "defaults")) newc = C_DEFAULTS;
|
||||
else if (k && !strcmp(k, "profiles")) newc = C_PROFILES;
|
||||
} else if (c == C_PROFILES) {
|
||||
cur = k ? add_profile(k) : NULL;
|
||||
newc = C_PROFBODY;
|
||||
}
|
||||
}
|
||||
if (depth < MAX_DEPTH - 1) depth++;
|
||||
ctx[depth] = newc;
|
||||
free(key[depth]);
|
||||
key[depth] = NULL;
|
||||
break;
|
||||
}
|
||||
case YAML_MAPPING_END_EVENT:
|
||||
free(key[depth]);
|
||||
key[depth] = NULL;
|
||||
if (depth > 0) depth--;
|
||||
break;
|
||||
case YAML_SCALAR_EVENT: {
|
||||
const char *v = (const char *)ev.data.scalar.value;
|
||||
int c = ctx[depth];
|
||||
if (c == C_DEFAULTS) {
|
||||
if (!key[depth]) { key[depth] = xstrdup(v); }
|
||||
else { add_kv(&g_def, &g_ndef, key[depth], v); free(key[depth]); key[depth] = NULL; }
|
||||
} else if (c == C_PROFBODY) {
|
||||
if (!key[depth]) { key[depth] = xstrdup(v); }
|
||||
else { if (cur) add_kv(&cur->vars, &cur->nvars, key[depth], v); free(key[depth]); key[depth] = NULL; }
|
||||
} else {
|
||||
/* C_ROOT / C_PROFILES: the scalar is a key/name whose
|
||||
* value is the next mapping. */
|
||||
free(key[depth]);
|
||||
key[depth] = xstrdup(v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case YAML_STREAM_END_EVENT:
|
||||
done = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
yaml_event_delete(&ev);
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_DEPTH; i++) free(key[i]);
|
||||
yaml_parser_delete(&parser);
|
||||
fclose(f);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void ensure_loaded(void)
|
||||
{
|
||||
const char *path;
|
||||
|
||||
if (g_loaded) return;
|
||||
g_loaded = 1;
|
||||
|
||||
path = getenv("BS_PROBES");
|
||||
if (!path || !*path) path = DEFAULT_PROBES_FILE;
|
||||
|
||||
if (load(path) == 0 && (g_ndef > 0 || g_nprof > 0)) {
|
||||
strncpy(g_source, path, sizeof(g_source) - 1);
|
||||
g_source[sizeof(g_source) - 1] = '\0';
|
||||
}
|
||||
/* Silent when absent: probe profiles are optional. */
|
||||
}
|
||||
|
||||
int probe_apply_defaults(probe_set_fn set, void *user)
|
||||
{
|
||||
int i;
|
||||
ensure_loaded();
|
||||
for (i = 0; i < g_ndef; i++) set(user, g_def[i].key, g_def[i].val);
|
||||
return g_ndef;
|
||||
}
|
||||
|
||||
int probe_apply_profile(const char *name, probe_set_fn set, void *user)
|
||||
{
|
||||
int i, j;
|
||||
ensure_loaded();
|
||||
for (i = 0; i < g_nprof; i++) {
|
||||
if (!strcmp(g_prof[i].name, name)) {
|
||||
for (j = 0; j < g_prof[i].nvars; j++)
|
||||
set(user, g_prof[i].vars[j].key, g_prof[i].vars[j].val);
|
||||
return g_prof[i].nvars;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int probe_profile_count(void)
|
||||
{
|
||||
ensure_loaded();
|
||||
return g_nprof;
|
||||
}
|
||||
|
||||
const char *probe_profile_name(int index)
|
||||
{
|
||||
ensure_loaded();
|
||||
return (index >= 0 && index < g_nprof) ? g_prof[index].name : NULL;
|
||||
}
|
||||
|
||||
const char *probe_config_source(void)
|
||||
{
|
||||
ensure_loaded();
|
||||
return g_source[0] ? g_source : NULL;
|
||||
}
|
||||
40
src/modules/probes/probes.h
Normal file
40
src/modules/probes/probes.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef _PROBES_H
|
||||
#define _PROBES_H
|
||||
|
||||
/*
|
||||
* Probe configuration profiles, loaded at runtime from probes.yaml.
|
||||
*
|
||||
* The built-in config.script (and an optional CWD config.script) set the
|
||||
* baseline probe variables. probes.yaml layers on top of that:
|
||||
* - a `defaults:` map applied on every jtag_open (so opening without a
|
||||
* profile restores a known baseline), then
|
||||
* - a named `profiles:` map whose vars override the defaults for one
|
||||
* probe (e.g. an embedded FlashPro that needs ADBUS4 high-Z).
|
||||
*
|
||||
* Each key/value is pushed into the script envvar store via the caller's
|
||||
* callback — the same vars config.script sets and the driver reads at
|
||||
* open time (jtagcore_getEnvVarValue). This keeps the parser decoupled
|
||||
* from the script context.
|
||||
*
|
||||
* File lookup: $BS_PROBES, else "probes.yaml" relative to the current
|
||||
* directory (same convention as fpga_registry.yaml). Absence is silent —
|
||||
* profiles are optional.
|
||||
*/
|
||||
|
||||
/* Callback used to set one envvar. `user` is opaque to this module. */
|
||||
typedef void (*probe_set_fn)(void *user, const char *key, const char *value);
|
||||
|
||||
/* Apply the defaults: map. Returns the number of vars applied (0 if none
|
||||
* or no file). */
|
||||
int probe_apply_defaults(probe_set_fn set, void *user);
|
||||
|
||||
/* Apply the named profile's vars on top of the defaults. Returns the
|
||||
* number of vars applied, or -1 if no such profile exists. */
|
||||
int probe_apply_profile(const char *name, probe_set_fn set, void *user);
|
||||
|
||||
/* Listing / diagnostics. */
|
||||
int probe_profile_count(void);
|
||||
const char *probe_profile_name(int index);
|
||||
const char *probe_config_source(void); /* path loaded, or NULL */
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user