probes: add probe-config profiles loaded from probes.yaml

- new modules/probes/ parses probes.yaml (libyaml): a defaults: map
  applied on every jtag_open + named profiles: selected with
  `jtag_open <idx> <profile>` (jtag_profiles lists them); each value is
  pushed into the script envvar store the driver reads at open time
- ships a flashpro profile (ADBUS4 high-Z) that lets the IGLOO2 kit's
  embedded FlashPro (FT4232H, port 0) detect the chain
- CLAUDE.md: decision entry for probes.yaml + a design note on the
  probe / JTAG-link / device config strategy (driver-neutral link layer)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 11:22:19 +02:00
parent 00320d87ec
commit 4ee1c2b631
7 changed files with 419 additions and 2 deletions

View 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
modules/probes/probes.c Normal file
View 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 "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
modules/probes/probes.h Normal file
View 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

View File

@@ -38,6 +38,7 @@
#include "bsdl_parser/bsdl_loader.h"
#include "os_interface/os_interface.h"
#include "fpga/fpga.h"
#include "probes/probes.h"
#include "bscan_spi/bscan_spi.h"
#include "spi_flash/spi_flash.h"
@@ -1802,10 +1803,21 @@ static int cmd_print_probes_list(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR;
}
/* Push one probes.yaml key/value into the script envvar store — the same
* store config.script writes and the driver reads at open time. */
static void probe_envvar_set(void *user, const char *key, const char *value)
{
script_ctx *ctx = (script_ctx *)user;
setEnvVarDat((envvar_entry *)ctx->env, (char *)key, (char *)value);
}
const char *cmd_open_probe_help[] = {
"<probe>(int)", // Arguments "1<arg>(type) ..."
"<probe>(int) [profile](str)", // Arguments "1<arg>(type) ..."
"Open a probe by its index from jtag_probes (0, 1, 2, ...).",
"A raw 0x-prefixed probe id (as printed after the index) also works.",
"Optional [profile] applies a probe-config profile from probes.yaml",
"(e.g. 'flashpro'); jtag_profiles lists them. The profile's variables",
"override the probes.yaml defaults for this open.",
""
};
static int cmd_open_probe(script_ctx *ctx, char *line)
@@ -1813,6 +1825,7 @@ static int cmd_open_probe(script_ctx *ctx, char *line)
int ret;
int id;
char probe_id[64];
char profile[64];
jtag_core *jc;
jc = (jtag_core *)ctx->app_ctx;
@@ -1836,6 +1849,20 @@ static int cmd_open_probe(script_ctx *ctx, char *line)
}
}
// Apply the probes.yaml defaults, then the optional named profile,
// into the envvar store the driver reads when it initialises.
probe_apply_defaults(probe_envvar_set, ctx);
if (get_param(ctx, line, 2, profile) > 0)
{
if (probe_apply_profile(profile, probe_envvar_set, ctx) < 0)
{
ctx->script_printf(ctx, MSG_ERROR,
"Unknown probe profile '%s'. Run jtag_profiles to list them.\n", profile);
return JTAG_CORE_BAD_PARAMETER;
}
ctx->script_printf(ctx, MSG_INFO_0, "Applied probe profile '%s'.\n", profile);
}
ret = jtagcore_select_and_open_probe(jc, id);
if (ret != JTAG_CORE_NO_ERROR)
{
@@ -1889,6 +1916,32 @@ static int cmd_close_probe(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR;
}
const char *cmd_profiles_help[] = {
"",
"List the probe-config profiles defined in probes.yaml.",
"Apply one when opening: jtag_open <index> <profile>.",
""
};
static int cmd_profiles(script_ctx *ctx, char *line)
{
int i, n;
const char *src;
(void)line;
n = probe_profile_count();
src = probe_config_source();
if (!src)
{
ctx->script_printf(ctx, MSG_INFO_0,
"No probes.yaml loaded; using built-in / config.script defaults.\n");
return JTAG_CORE_NO_ERROR;
}
ctx->script_printf(ctx, MSG_INFO_0, "%d probe profile(s) in %s:\n", n, src);
for (i = 0; i < n; i++)
ctx->script_printf(ctx, MSG_NONE, " %s\n", probe_profile_name(i));
return JTAG_CORE_NO_ERROR;
}
const char *cmd_load_bsdl_help[] = {
"<file>(string) <device>(int)",
"Load/attach a bsdl file to a device into the chain.",
@@ -3368,6 +3421,7 @@ cmd_list script_commands_list[] =
{"jtag_probes", cmd_print_probes_list, cmd_print_probes_list_help},
{"jtag_open", cmd_open_probe, cmd_open_probe_help},
{"jtag_close", cmd_close_probe, cmd_close_probe_help},
{"jtag_profiles", cmd_profiles, cmd_profiles_help},
{"jtag_autoinit", cmd_autoinit, cmd_autoinit_help},
{"jtag_scan", cmd_init_and_scan, cmd_init_and_scan_help},
{"jtag_ndev", cmd_print_nb_dev, cmd_print_nb_dev_help},