From 831bd7c12919b154f57d7b90b61d264deb7200d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 13 Jun 2026 00:14:07 +0200 Subject: [PATCH] svf: extract player core into standalone libsvf, keep bscan glue The SVF player now lives in electronics/libsvf behind a five-function JTAG ops table, shared with the wifi_jtag_programmer Zephyr firmware. src/modules/svf/ shrinks to the bscan_*-backed port (bs_svf_play), which also keeps the FTDI stale-FIFO warm-up. Local checkout override: -DFETCHCONTENT_SOURCE_DIR_SVF=. --- CLAUDE.md | 17 +- CMakeLists.txt | 9 + src/modules/program/program.c | 4 +- src/modules/script/CMakeLists.txt | 2 +- src/modules/script/script.c | 4 +- src/modules/svf/CMakeLists.txt | 6 +- src/modules/svf/svf.c | 447 ------------------------------ src/modules/svf/svf.h | 40 --- src/modules/svf/svf_glue.c | 58 ++++ src/modules/svf/svf_glue.h | 20 ++ 10 files changed, 106 insertions(+), 501 deletions(-) delete mode 100644 src/modules/svf/svf.c delete mode 100644 src/modules/svf/svf.h create mode 100644 src/modules/svf/svf_glue.c create mode 100644 src/modules/svf/svf_glue.h diff --git a/CLAUDE.md b/CLAUDE.md index 7b0a823..bbf8431 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,9 +23,13 @@ new is in `src/bs/` (the REPL) and the project modules (`target/`, sitting alongside the Viveris ones. The BSDL parser was extracted into a standalone library, **[libbsdl](ssh://gitea@git.beafrancois.fr:8329/electronics/libbsdl.git)** (LGPL, seeded from the same Viveris loader), and is now pulled in via -CMake `FetchContent` instead of vendored. To build against a local -checkout (e.g. side-by-side dev): configure with -`-DFETCHCONTENT_SOURCE_DIR_BSDL=/path/to/libbsdl`. +CMake `FetchContent` instead of vendored. The SVF player core moved the +same way into **[libsvf](ssh://gitea@git.beafrancois.fr:8329/electronics/libsvf.git)** +(shared with the `wifi_jtag_programmer` Zephyr firmware, which plays SVF +through an embedded JTAG port); `src/modules/svf/` is now only the +bscan-backed glue. To build against local checkouts (side-by-side dev): +configure with `-DFETCHCONTENT_SOURCE_DIR_BSDL=/path/to/libbsdl` and/or +`-DFETCHCONTENT_SOURCE_DIR_SVF=/path/to/libsvf`. ## Architecture @@ -41,14 +45,15 @@ src/ ├── config/ Built-in config.script ├── os_interface/ Portable fs/network wrappers └── natsort/ Natural pin-name sorting - — pulled via FetchContent (LGPL, separate repo) — - └── (libbsdl) BSDL (.bsd) parser; struct + JSON; stable C ABI + — pulled via FetchContent (LGPL, separate repos) — + ├── (libbsdl) BSDL (.bsd) parser; struct + JSON; stable C ABI + └── (libsvf) portable SVF player core (svf_play_buf/file over a 5-op JTAG port) — new (this project) — ├── target/ Target registry: FPGAs + CPUs (parses data/targets.yaml) ├── bscan/ JTAG TAP primitives (set_ir/shift_ir/shift_dr/tap_reset/ │ idle_cycles) + BSCAN proxy (bitstream load, SPI-over-USER1) ├── spi_flash/ SPI NOR chip DB + read/erase/program/verify over a callback - ├── svf/ SVF player (svf_play): SIR/SDR/RUNTEST/STATE, masked compare + ├── svf/ glue: bscan_*-backed svf_jtag_ops port for libsvf (bs_svf_play) ├── probes/ Probe-config profiles loader (parses data/probes.yaml, libyaml) ├── program/ `program` dispatch: routes a target to its backend by `prog` └── arm_debug/ ARM7TDMI (EmbeddedICE) debug: halt/resume, Thumb->ARM, memory read (works); flash-write backend TODO diff --git a/CMakeLists.txt b/CMakeLists.txt index 32811d9..21beff7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,15 @@ FetchContent_Declare(bsdl ) FetchContent_MakeAvailable(bsdl) +# ---- libsvf (SVF player, extracted from this repo and now standalone) ---- +# Same pattern as libbsdl. Local checkout override: +# -DFETCHCONTENT_SOURCE_DIR_SVF=/path/to/libsvf +FetchContent_Declare(svf + GIT_REPOSITORY ssh://gitea@git.beafrancois.fr:8329/electronics/libsvf.git + GIT_TAG main +) +FetchContent_MakeAvailable(svf) + # Digilent JTAG-SMT* backend. The driver dlopen's libdjtg/libdmgr at # runtime and degrades to "no probe" if they're absent, so building it # in costs nothing — default ON. Needs , so UNIX only. diff --git a/src/modules/program/program.c b/src/modules/program/program.c index cac2b7a..7e7065a 100644 --- a/src/modules/program/program.c +++ b/src/modules/program/program.c @@ -1,7 +1,7 @@ #include #include "program.h" -#include "svf/svf.h" +#include "svf/svf_glue.h" #include "arm_debug/arm_debug.h" /* program_log_fn, svf_log_fn and arm_log_fn are the same shape @@ -17,7 +17,7 @@ int program_dispatch(jtag_core *jc, const jtag_target *t, const char *file, switch (t->prog) { case TARGET_PROG_SVF: - return svf_play_file(jc, file, (svf_log_fn)log, user, NULL); + return bs_svf_play(jc, file, (svf_log_fn)log, user, NULL); case TARGET_PROG_ARM_FLASH: return arm_flash_program(jc, t, file, (arm_log_fn)log, user); diff --git a/src/modules/script/CMakeLists.txt b/src/modules/script/CMakeLists.txt index 391cabf..928a315 100644 --- a/src/modules/script/CMakeLists.txt +++ b/src/modules/script/CMakeLists.txt @@ -6,4 +6,4 @@ include_directories(${DIR_MODULES}) include_directories(${DIR_LIBS}) add_library(script ${ALL_SOURCES}) -target_link_libraries(script PUBLIC bsdl::bsdl) +target_link_libraries(script PUBLIC bsdl::bsdl svf) diff --git a/src/modules/script/script.c b/src/modules/script/script.c index 0542869..6b3a7d5 100644 --- a/src/modules/script/script.c +++ b/src/modules/script/script.c @@ -40,7 +40,7 @@ #include "target/target.h" #include "probes/probes.h" #include "bscan/bscan.h" -#include "svf/svf.h" +#include "svf/svf_glue.h" #include "program/program.h" #include "arm_debug/arm_debug.h" #include "spi_flash/spi_flash.h" @@ -3546,7 +3546,7 @@ static int cmd_svf_play(script_ctx *ctx, char *line) ctx->script_printf(ctx, MSG_ERROR, "Usage: svf_play \n"); return JTAG_CORE_BAD_PARAMETER; } - if (svf_play_file(jc, path, svf_log_cb, ctx, NULL) < 0) + if (bs_svf_play(jc, path, svf_log_cb, ctx, NULL) < 0) return JTAG_CORE_ACCESS_ERROR; return JTAG_CORE_NO_ERROR; } diff --git a/src/modules/svf/CMakeLists.txt b/src/modules/svf/CMakeLists.txt index a80f4fb..eaf4cd3 100644 --- a/src/modules/svf/CMakeLists.txt +++ b/src/modules/svf/CMakeLists.txt @@ -1,7 +1,7 @@ set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") -file(GLOB_RECURSE ALL_SOURCES "*.c") - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) -add_library(svf ${ALL_SOURCES}) +# Glue over libsvf (FetchContent'd at top level): bscan_* backed JTAG port. +add_library(svf svf_glue.c) +target_link_libraries(svf PUBLIC svf::svf) diff --git a/src/modules/svf/svf.c b/src/modules/svf/svf.c deleted file mode 100644 index 4697892..0000000 --- a/src/modules/svf/svf.c +++ /dev/null @@ -1,447 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "bscan/bscan.h" -#include "svf.h" - -/* ------------------------------------------------------------------ * - * Player state - * ------------------------------------------------------------------ */ - -typedef struct { - jtag_core *jc; - svf_log_fn log; - void *user; - long line; /* 1-based, for diagnostics */ - svf_stats stats; - - /* Sticky TDI / MASK per scan type ([0] = DR, [1] = IR). TDO is not - * sticky: a compare happens only when TDO is given on that scan. */ - struct { - int len; - uint8_t *tdi; - uint8_t *mask; - } st[2]; -} svf_player; - -static void slog(svf_player *p, int is_error, const char *fmt, ...) -{ - char buf[256]; - va_list ap; - if (!p->log) return; - va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - p->log(p->user, is_error, buf); -} - -/* ------------------------------------------------------------------ * - * Hex / token helpers - * ------------------------------------------------------------------ */ - -static int hexval(int c) -{ - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} - -/* Parse `hexlen` chars of hex (whitespace allowed) representing `nbits` - * bits, LSB shifted first, into buf (LSB-first per byte, as the bscan_* - * shifters expect). Returns 0, or -1 on a stray character. */ -static int parse_hex_bits(const char *hex, int hexlen, int nbits, uint8_t *buf) -{ - int nbytes = (nbits + 7) / 8; - int i, nib = 0; - - memset(buf, 0, (size_t)nbytes); - for (i = hexlen - 1; i >= 0; i--) { - int v; - char c = hex[i]; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - continue; - v = hexval((unsigned char)c); - if (v < 0) return -1; - if (v) { - int k; - for (k = 0; k < 4; k++) { - int bit = nib * 4 + k; - if (bit < nbits && (v & (1 << k))) - buf[bit / 8] |= (uint8_t)(1u << (bit & 7)); - } - } - nib++; - } - return 0; -} - -typedef struct { const char *p, *end; } svf_cur; - -enum { TK_END = 0, TK_WORD, TK_HEX }; - -/* Next token: TK_WORD copies into word[wordsz]; TK_HEX returns a pointer - * and length into the source (no copy — bodies can be large). */ -static int svf_next(svf_cur *c, char *word, int wordsz, - const char **hp, int *hlen) -{ - while (c->p < c->end && - (*c->p == ' ' || *c->p == '\t' || *c->p == '\r' || *c->p == '\n')) - c->p++; - if (c->p >= c->end) return TK_END; - - if (*c->p == '(') { - const char *start = ++c->p; - while (c->p < c->end && *c->p != ')') c->p++; - *hp = start; - *hlen = (int)(c->p - start); - if (c->p < c->end) c->p++; /* skip ')' */ - return TK_HEX; - } - - { - int n = 0; - while (c->p < c->end && *c->p != ' ' && *c->p != '\t' && - *c->p != '\r' && *c->p != '\n' && *c->p != '(') { - if (n < wordsz - 1) word[n++] = *c->p; - c->p++; - } - word[n] = '\0'; - } - return TK_WORD; -} - -static int ci_eq(const char *a, const char *b) -{ - while (*a && *b) { - int ca = *a, cb = *b; - if (ca >= 'a' && ca <= 'z') ca -= 32; - if (cb >= 'a' && cb <= 'z') cb -= 32; - if (ca != cb) return 0; - a++; b++; - } - return *a == *b; -} - -/* ------------------------------------------------------------------ * - * Commands - * ------------------------------------------------------------------ */ - -/* SIR / SDR: shift IR/DR with optional masked TDO compare. */ -static int do_scan(svf_player *p, int is_ir, svf_cur *c) -{ - char word[64]; - const char *hex; - int hlen, t; - int len = -1; - int nbytes; - uint8_t *tdi = NULL, *tdo = NULL, *mask = NULL, *cap = NULL; - int have_tdi = 0, have_tdo = 0, have_mask = 0; - int idx = is_ir ? 1 : 0; - int rc = -1, i; - - /* length */ - if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_WORD) { - slog(p, 1, "line %ld: %s missing length", p->line, is_ir ? "SIR" : "SDR"); - return -1; - } - len = atoi(word); - if (len <= 0) { - slog(p, 1, "line %ld: %s bad length '%s'", p->line, is_ir ? "SIR" : "SDR", word); - return -1; - } - nbytes = (len + 7) / 8; - - /* fields */ - while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END) { - const char *fld = word; - uint8_t **dst; - int *flag; - if (t != TK_WORD) goto out; - if (ci_eq(fld, "TDI")) { dst = &tdi; flag = &have_tdi; } - else if (ci_eq(fld, "TDO")) { dst = &tdo; flag = &have_tdo; } - else if (ci_eq(fld, "MASK")) { dst = &mask; flag = &have_mask; } - else if (ci_eq(fld, "SMASK")) { dst = NULL; flag = NULL; } - else { slog(p, 1, "line %ld: unexpected '%s' in scan", p->line, fld); goto out; } - - if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_HEX) { - slog(p, 1, "line %ld: '%s' without (hex)", p->line, fld); - goto out; - } - if (!dst) continue; /* SMASK: parsed, ignored */ - *dst = malloc((size_t)nbytes); - if (!*dst) { slog(p, 1, "out of memory"); goto out; } - if (parse_hex_bits(hex, hlen, len, *dst) < 0) { - slog(p, 1, "line %ld: bad hex in %s", p->line, fld); - goto out; - } - *flag = 1; - } - - /* Resolve sticky TDI: reuse last value of same length when omitted. */ - if (!have_tdi) { - if (p->st[idx].tdi && p->st[idx].len == len) { - tdi = malloc((size_t)nbytes); - if (!tdi) { slog(p, 1, "out of memory"); goto out; } - memcpy(tdi, p->st[idx].tdi, (size_t)nbytes); - } else { - tdi = calloc(1, (size_t)nbytes); /* default: shift zeros */ - if (!tdi) { slog(p, 1, "out of memory"); goto out; } - } - } - /* Resolve sticky MASK when a compare is wanted but no MASK given. */ - if (have_tdo && !have_mask) { - mask = malloc((size_t)nbytes); - if (!mask) { slog(p, 1, "out of memory"); goto out; } - if (p->st[idx].mask && p->st[idx].len == len) - memcpy(mask, p->st[idx].mask, (size_t)nbytes); - else - memset(mask, 0xFF, (size_t)nbytes); /* default: all care */ - } - - /* Shift. */ - if (have_tdo) { - cap = calloc(1, (size_t)nbytes); - if (!cap) { slog(p, 1, "out of memory"); goto out; } - } - if ((is_ir ? bscan_shift_ir(p->jc, tdi, cap, len) - : bscan_shift_dr(p->jc, tdi, cap, len)) < 0) { - slog(p, 1, "line %ld: shift %s failed (probe ok?)", p->line, is_ir ? "IR" : "DR"); - goto out; - } - p->stats.scans++; - - /* Masked compare. */ - if (have_tdo) { - p->stats.compares++; - for (i = 0; i < len; i++) { - int m = (mask[i / 8] >> (i & 7)) & 1u; - if (!m) continue; - if (((cap[i / 8] >> (i & 7)) & 1u) != ((tdo[i / 8] >> (i & 7)) & 1u)) { - slog(p, 1, "line %ld: %s TDO mismatch at bit %d (len %d)", - p->line, is_ir ? "SIR" : "SDR", i, len); - goto out; - } - } - } - - /* Update sticky state. */ - if (have_tdi || p->st[idx].len != len) { - free(p->st[idx].tdi); - p->st[idx].tdi = malloc((size_t)nbytes); - if (p->st[idx].tdi) memcpy(p->st[idx].tdi, tdi, (size_t)nbytes); - } - if (have_mask) { - free(p->st[idx].mask); - p->st[idx].mask = malloc((size_t)nbytes); - if (p->st[idx].mask) memcpy(p->st[idx].mask, mask, (size_t)nbytes); - } - p->st[idx].len = len; - - rc = 0; -out: - free(tdi); free(tdo); free(mask); free(cap); - return rc; -} - -/* HIR/HDR/TIR/TDR: header/trailer bits. Single-device only -> must be 0. */ -static int do_header(svf_player *p, const char *kw, svf_cur *c) -{ - char word[64]; - const char *hex; int hlen, t, len; - - if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_WORD) { - slog(p, 1, "line %ld: %s missing length", p->line, kw); - return -1; - } - len = atoi(word); - if (len != 0) { - slog(p, 1, "line %ld: %s %d — multi-device chains not supported", - p->line, kw, len); - return -1; - } - /* drain any TDI()/TDO()/... that may accompany a 0-length header */ - while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END) - ; - return 0; -} - -static int do_endstate(svf_player *p, const char *kw, svf_cur *c) -{ - char word[64]; const char *hex; int hlen; - if (svf_next(c, word, sizeof(word), &hex, &hlen) != TK_WORD) { - slog(p, 1, "line %ld: %s missing state", p->line, kw); - return -1; - } - if (!ci_eq(word, "IDLE")) { - slog(p, 1, "line %ld: %s %s — only IDLE end state supported", - p->line, kw, word); - return -1; - } - return 0; /* scans already finish in Run-Test/Idle */ -} - -static int do_state(svf_player *p, svf_cur *c) -{ - char word[64]; const char *hex; int hlen, t; - int want_reset = 0, only_idle = 1; - - while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END) { - if (t != TK_WORD) continue; - if (ci_eq(word, "RESET")) want_reset = 1; - else if (ci_eq(word, "IDLE")) /* ok */; - else only_idle = 0; - } - if (want_reset) - return bscan_tap_reset(p->jc); - if (only_idle) - return 0; /* already in Idle between commands */ - slog(p, 1, "line %ld: STATE — only RESET / IDLE supported", p->line); - return -1; -} - -static int do_runtest(svf_player *p, svf_cur *c) -{ - char word[64]; const char *hex; int hlen, t; - long cycles = 0; - double secs = 0.0; - char pending[64]; - int have_pending = 0; - - while ((t = svf_next(c, word, sizeof(word), &hex, &hlen)) != TK_END) { - if (t != TK_WORD) continue; - if (have_pending && (ci_eq(word, "TCK") || ci_eq(word, "SCK"))) { - cycles = atol(pending); - have_pending = 0; - } else if (have_pending && ci_eq(word, "SEC")) { - secs = atof(pending); - have_pending = 0; - } else if (word[0] == '.' || (word[0] >= '0' && word[0] <= '9')) { - strncpy(pending, word, sizeof(pending) - 1); - pending[sizeof(pending) - 1] = '\0'; - have_pending = 1; - } else { - have_pending = 0; /* MAXIMUM / ENDSTATE / state names, etc. */ - } - } - - while (cycles > 0) { - int chunk = (cycles > 1000000) ? 1000000 : (int)cycles; - if (bscan_idle_cycles(p->jc, chunk) < 0) { - slog(p, 1, "line %ld: RUNTEST idle failed", p->line); - return -1; - } - cycles -= chunk; - } - if (secs > 0.0) { - if (secs > 60.0) secs = 60.0; /* sanity cap */ - usleep((useconds_t)(secs * 1e6)); - } - return 0; -} - -/* ------------------------------------------------------------------ * - * Top-level - * ------------------------------------------------------------------ */ - -/* Blank out '!' and '//' comments to end-of-line (keep newlines). */ -static void strip_comments(char *buf, long len) -{ - long i; - for (i = 0; i < len; i++) { - if (buf[i] == '!' || (buf[i] == '/' && i + 1 < len && buf[i + 1] == '/')) { - while (i < len && buf[i] != '\n') buf[i++] = ' '; - } - } -} - -static int dispatch(svf_player *p, char *cmd, long cmdlen) -{ - svf_cur c; - char kw[64]; - const char *hex; int hlen; - - c.p = cmd; c.end = cmd + cmdlen; - if (svf_next(&c, kw, sizeof(kw), &hex, &hlen) != TK_WORD) - return 0; /* blank */ - - p->stats.commands++; - - if (ci_eq(kw, "SDR")) return do_scan(p, 0, &c); - else if (ci_eq(kw, "SIR")) return do_scan(p, 1, &c); - else if (ci_eq(kw, "RUNTEST")) return do_runtest(p, &c); - else if (ci_eq(kw, "STATE")) return do_state(p, &c); - else if (ci_eq(kw, "ENDIR") || ci_eq(kw, "ENDDR")) return do_endstate(p, kw, &c); - else if (ci_eq(kw, "HIR") || ci_eq(kw, "HDR") || - ci_eq(kw, "TIR") || ci_eq(kw, "TDR")) return do_header(p, kw, &c); - else if (ci_eq(kw, "TRST") || ci_eq(kw, "FREQUENCY")) - return 0; /* accepted; not acted on (single-device, clock set at open) */ - - slog(p, 1, "line %ld: unsupported command '%s'", p->line, kw); - return -1; -} - -int svf_play_file(jtag_core *jc, const char *path, - svf_log_fn log, void *user, svf_stats *stats) -{ - FILE *f; - long size, i, cmd_start; - char *buf; - svf_player p; - int rc = 0; - - memset(&p, 0, sizeof(p)); - p.jc = jc; p.log = log; p.user = user; p.line = 1; - - f = fopen(path, "rb"); - if (!f) { slog(&p, 1, "cannot open %s", path); return -1; } - if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; } - size = ftell(f); - if (size <= 0) { fclose(f); slog(&p, 1, "empty file"); return -1; } - rewind(f); - buf = malloc((size_t)size + 1); - if (!buf) { fclose(f); slog(&p, 1, "out of memory"); return -1; } - if (fread(buf, 1, (size_t)size, f) != (size_t)size) { - free(buf); fclose(f); slog(&p, 1, "read error"); return -1; - } - buf[size] = '\0'; - fclose(f); - - strip_comments(buf, size); - - /* Warm up the link before the first real scan: the FTDI MPSSE's first - * data read after a fresh open returns stale FIFO content. The normal - * Viveris flow hides this (jtag_scan/autoinit runs first); do a - * throwaway reset + DR read so a standalone svf_play is reliable. */ - { - uint8_t junk[4]; - bscan_tap_reset(jc); - bscan_shift_dr(jc, NULL, junk, 32); - } - - /* Split on ';' and dispatch each command, tracking the line number. */ - cmd_start = 0; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') p.line++; - if (buf[i] == ';') { - rc = dispatch(&p, buf + cmd_start, i - cmd_start); - if (rc < 0) break; - cmd_start = i + 1; - } - } - - free(buf); - free(p.st[0].tdi); free(p.st[0].mask); - free(p.st[1].tdi); free(p.st[1].mask); - - if (stats) *stats = p.stats; - if (rc == 0) - slog(&p, 0, "SVF done: %ld commands, %ld scans, %ld compares", - p.stats.commands, p.stats.scans, p.stats.compares); - return rc; -} diff --git a/src/modules/svf/svf.h b/src/modules/svf/svf.h deleted file mode 100644 index 4b1f875..0000000 --- a/src/modules/svf/svf.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _SVF_H -#define _SVF_H - -#include "jtag_core/jtag_core.h" - -/* - * SVF player (single-device chain). - * - * Plays a standard subset of Serial Vector Format over the currently - * open probe, using the bscan_* TAP primitives. SVF is what Libero, - * Diamond/Radiant, Vivado, … export with the vendor programming - * algorithm already baked in, so one player programs many targets with - * no per-vendor code. - * - * Supported: SIR / SDR with TDI/TDO/MASK/SMASK and a masked TDO compare; - * RUNTEST (TCK/SCK counts and SEC delays); STATE (RESET / IDLE); - * ENDIR / ENDDR (IDLE only); HIR/HDR/TIR/TDR (length 0 only); TRST; - * FREQUENCY. SMASK is parsed but not applied. - * - * Not supported (single-device tool): non-zero header/trailer scans - * (multi-device chains) and non-IDLE stable end states — both rejected - * with a clear error. - * - * log() receives progress / error lines (is_error != 0 on failure). - * Returns 0 on success, < 0 on parse error or a TDO compare mismatch. - * stats may be NULL. - */ - -typedef void (*svf_log_fn)(void *user, int is_error, const char *msg); - -typedef struct { - long commands; /* commands executed */ - long scans; /* SIR + SDR */ - long compares; /* TDO compares performed */ -} svf_stats; - -int svf_play_file(jtag_core *jc, const char *path, - svf_log_fn log, void *user, svf_stats *stats); - -#endif diff --git a/src/modules/svf/svf_glue.c b/src/modules/svf/svf_glue.c new file mode 100644 index 0000000..576bd17 --- /dev/null +++ b/src/modules/svf/svf_glue.c @@ -0,0 +1,58 @@ +/* + * bs_explorer glue for libsvf: maps the svf_jtag_ops port onto the + * bscan_* TAP primitives. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#include "svf_glue.h" +#include "bscan/bscan.h" + +static int ops_tap_reset(void *ctx) +{ + return bscan_tap_reset((jtag_core *)ctx); +} + +static int ops_shift_ir(void *ctx, const uint8_t *tdi, uint8_t *tdo, int nbits) +{ + return bscan_shift_ir((jtag_core *)ctx, tdi, tdo, nbits); +} + +static int ops_shift_dr(void *ctx, const uint8_t *tdi, uint8_t *tdo, int nbits) +{ + return bscan_shift_dr((jtag_core *)ctx, tdi, tdo, nbits); +} + +static int ops_idle_cycles(void *ctx, int ncycles) +{ + return bscan_idle_cycles((jtag_core *)ctx, ncycles); +} + +static void ops_delay_us(void *ctx, unsigned long us) +{ + (void)ctx; + usleep((useconds_t)us); +} + +static const svf_jtag_ops bscan_svf_ops = { + .tap_reset = ops_tap_reset, + .shift_ir = ops_shift_ir, + .shift_dr = ops_shift_dr, + .idle_cycles = ops_idle_cycles, + .delay_us = ops_delay_us, +}; + +int bs_svf_play(jtag_core *jc, const char *path, + svf_log_fn log, void *user, svf_stats *stats) +{ + /* Warm up the link before the first real scan: the FTDI MPSSE's first + * data read after a fresh open returns stale FIFO content. The normal + * Viveris flow hides this (jtag_scan/autoinit runs first); do a + * throwaway reset + DR read so a standalone svf_play is reliable. */ + uint8_t junk[4]; + bscan_tap_reset(jc); + bscan_shift_dr(jc, NULL, junk, 32); + + return svf_play_file(&bscan_svf_ops, jc, path, log, user, stats); +} diff --git a/src/modules/svf/svf_glue.h b/src/modules/svf/svf_glue.h new file mode 100644 index 0000000..17f7e3a --- /dev/null +++ b/src/modules/svf/svf_glue.h @@ -0,0 +1,20 @@ +/* + * bs_explorer glue for libsvf: plays SVF through the bscan_* TAP + * primitives of the currently open probe. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef _SVF_GLUE_H +#define _SVF_GLUE_H + +#include + +#include "jtag_core/jtag_core.h" + +/* Plays `path` over the probe held by `jc` (single-device chain). + * Warms the link up first (stale-FIFO probes like FTDI MPSSE). + * Returns 0 on success, < 0 on error; stats may be NULL. */ +int bs_svf_play(jtag_core *jc, const char *path, + svf_log_fn log, void *user, svf_stats *stats); + +#endif