target: generalize the registry to FPGAs + CPUs, add program dispatch

Restructure in anticipation of programming ARM CPUs (ARM7/9 via
EmbeddedICE, e.g. over an Olimex ARM-USB-OCD); FPGA path unchanged.

- modules/fpga -> modules/target; fpga_target -> jtag_target with a
  `kind` (fpga|cpu) and grouped fpga/cpu sub-structs; data/targets.yaml
  (env BS_TARGETS); API target_*; commands target_list/target_info
  (kind-aware). Add arm7/arm9 families, arm_flash prog, embeddedice
  debug, and cpu fields (ram_base/size, flash_base/size).
- new program/: `program <dev> <file>` dispatches by the target's prog
  (svf wired; proxy_spi points at the flash workflow; arm_flash -> arm_debug).
- new arm_debug/: EmbeddedICE halt/resume/mem + arm_flash backend
  declared, not implemented yet.
- bscan_* take const jtag_target* and read the fpga sub-struct.
- data/probes.yaml: arm-usb-ocd profile slot; data/targets.yaml: an ARM7
  example entry. Docs + an ARM-debug design note in CLAUDE.md.

Builds; FPGA path re-validated on the IGLOO2 (target_list shows the CPU
example; jtag_open/autoinit/program 0 <svf> all work).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 15:33:58 +02:00
parent d1bdce91dc
commit 9ad776268e
20 changed files with 973 additions and 582 deletions

View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(arm_debug ${ALL_SOURCES})

View File

@@ -0,0 +1,56 @@
#include <stdio.h>
#include "arm_debug.h"
/* Not implemented yet — every entry point reports failure for now. The
* real work — EmbeddedICE scan chains, halt/resume, memory access over
* the bscan_* primitives, and a per-MCU RAM flash loader — slots in
* behind these signatures without touching callers. */
int arm_debug_halt(jtag_core *jc, const jtag_target *t)
{
(void)jc; (void)t;
fprintf(stderr, "arm_debug: halt not implemented yet\n");
return -1;
}
int arm_debug_resume(jtag_core *jc, const jtag_target *t)
{
(void)jc; (void)t;
fprintf(stderr, "arm_debug: resume not implemented yet\n");
return -1;
}
int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,
unsigned long addr, void *buf, unsigned long len)
{
(void)jc; (void)t; (void)addr; (void)buf; (void)len;
fprintf(stderr, "arm_debug: mem_read not implemented yet\n");
return -1;
}
int arm_debug_mem_write(jtag_core *jc, const jtag_target *t,
unsigned long addr, const void *buf, unsigned long len)
{
(void)jc; (void)t; (void)addr; (void)buf; (void)len;
fprintf(stderr, "arm_debug: mem_write not implemented yet\n");
return -1;
}
int arm_flash_program(jtag_core *jc, const jtag_target *t, const char *file,
arm_log_fn log, void *user)
{
char msg[256];
(void)jc; (void)file;
if (log) {
snprintf(msg, sizeof(msg),
"arm_flash: backend not implemented yet. "
"Target '%s' debug=%d ram=0x%lX+0x%lX flash=0x%lX+0x%lX.",
t ? t->name : "?",
t ? (int)t->cpu.debug : 0,
t ? t->cpu.ram_base : 0UL, t ? t->cpu.ram_size : 0UL,
t ? t->cpu.flash_base : 0UL, t ? t->cpu.flash_size : 0UL);
log(user, 1, msg);
}
return -1;
}

View File

@@ -0,0 +1,38 @@
#ifndef _ARM_DEBUG_H
#define _ARM_DEBUG_H
/*
* ARM debug over JTAG (EmbeddedICE, ARM7/ARM9) — not implemented yet.
*
* The debug transport and the RAM-loader flash backend are not yet
* implemented; these declarations are the seams the implementation will
* fill. Planned flow (the OpenOCD approach):
*
* halt the core -> write a small flash loader into the target's
* work-RAM -> run it to program on-chip flash -> verify.
*
* It needs EmbeddedICE scan-chain access (DSCR/DCC) built on the
* bscan_* IR/DR primitives, plus per-MCU flash parameters from the
* registry (cpu.ram_base/size, cpu.flash_base/size). Cortex-M (ADIv5/
* DAP) would be a second debug transport behind the same seam.
*/
#include "jtag_core/jtag_core.h"
#include "target/target.h"
typedef void (*arm_log_fn)(void *user, int is_error, const char *msg);
/* Low-level debug primitives (stubs for now). */
int arm_debug_halt(jtag_core *jc, const jtag_target *t);
int arm_debug_resume(jtag_core *jc, const jtag_target *t);
int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,
unsigned long addr, void *buf, unsigned long len);
int arm_debug_mem_write(jtag_core *jc, const jtag_target *t,
unsigned long addr, const void *buf, unsigned long len);
/* Program on-chip flash from `file` via the RAM loader (stub). Returns
* 0 on success, < 0 on error / not-implemented. */
int arm_flash_program(jtag_core *jc, const jtag_target *t, const char *file,
arm_log_fn log, void *user);
#endif

View File

@@ -206,7 +206,7 @@ static uint8_t reverse_bits(uint8_t b)
return b;
}
int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
int bscan_load_bitstream(jtag_core *jc, const jtag_target *t,
const uint8_t *data, size_t nbytes)
{
uint8_t *reversed;
@@ -214,7 +214,7 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
size_t i;
if (!drv_ok(jc) || !t || !data || nbytes == 0) return -1;
if (!t->ir_jprogram || !t->ir_cfg_in || !t->ir_jstart) {
if (!t->fpga.ir_jprogram || !t->fpga.ir_cfg_in || !t->fpga.ir_jstart) {
/* No configuration opcodes known for this family. */
return -1;
}
@@ -222,11 +222,11 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
/* JPROGRAM clears the configuration memory. Min ~10k TCK cycles
* to wait for INIT_B to go high before CFG_IN.
* TODO: poll INIT_B via SAMPLE instead of fixed wait. */
if (bscan_set_ir(jc, t->ir_jprogram, t->ir_length) < 0) return -1;
if (bscan_set_ir(jc, t->fpga.ir_jprogram, t->ir_length) < 0) return -1;
bscan_idle_cycles(jc, 10000);
/* CFG_IN routes DR shifts to the configuration interface. */
if (bscan_set_ir(jc, t->ir_cfg_in, t->ir_length) < 0) return -1;
if (bscan_set_ir(jc, t->fpga.ir_cfg_in, t->ir_length) < 0) return -1;
/* Xilinx bitstream bytes must be bit-reversed before JTAG shift
* (configuration interface latches MSB first, JTAG shifts LSB first). */
@@ -243,7 +243,7 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
/* JSTART triggers the fabric startup. UG470/UG570: ≥12 cycles in
* Idle to complete the sequence. Use 2000 for margin. */
if (bscan_set_ir(jc, t->ir_jstart, t->ir_length) < 0) return -1;
if (bscan_set_ir(jc, t->fpga.ir_jstart, t->ir_length) < 0) return -1;
bscan_idle_cycles(jc, 2000);
/* Park on BYPASS (all 1s) so other operations don't trip on a
@@ -297,7 +297,7 @@ static int xilinx_bit_payload(const uint8_t *buf, size_t buflen,
return -1;
}
int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
int bscan_load_bitstream_file(jtag_core *jc, const jtag_target *t,
const char *path)
{
FILE *f;
@@ -339,7 +339,7 @@ int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
* OpenOCD's jtagspi. The header asserts a single-device chain. */
#define BSCAN_SPI_READ_LATENCY 1
int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
int bscan_spi_xfer(jtag_core *jc, const jtag_target *t,
const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen)
{
@@ -356,7 +356,7 @@ int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
size_t i;
uint8_t *dr_out, *dr_in;
if (!drv_ok(jc) || !t || !t->ir_user1 || spi_bytes == 0) return -1;
if (!drv_ok(jc) || !t || !t->fpga.ir_user1 || spi_bytes == 0) return -1;
if (txlen && !tx) return -1;
if (rxlen && !rx) return -1;
@@ -395,7 +395,7 @@ int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
bit += (int)rxlen * 8; /* MISO region (MOSI=0) */
}
if (bscan_set_ir(jc, t->ir_user1, t->ir_length) < 0 ||
if (bscan_set_ir(jc, t->fpga.ir_user1, t->ir_length) < 0 ||
bscan_shift_dr(jc, dr_out, dr_in, total_bits) < 0) {
free(dr_out); free(dr_in);
return -1;

View File

@@ -24,7 +24,7 @@
#include <stdint.h>
#include "jtag_core/jtag_core.h"
#include "fpga/fpga.h"
#include "target/target.h"
/* --- Low-level primitives (single-device chain) -------------------- */
@@ -51,12 +51,12 @@ int bscan_tap_reset(jtag_core *jc);
/* Load a raw bitstream payload (no .bit container header) into the
* FPGA via JPROGRAM -> CFG_IN -> shift -> JSTART. Bit-reverses each
* byte before shifting (Xilinx convention). */
int bscan_load_bitstream(jtag_core *jc, const fpga_target *t,
int bscan_load_bitstream(jtag_core *jc, const jtag_target *t,
const uint8_t *data, size_t nbytes);
/* Convenience wrapper: read a file and load it. Detects the Xilinx
* .bit header and skips it; otherwise treats the file as raw payload. */
int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
int bscan_load_bitstream_file(jtag_core *jc, const jtag_target *t,
const char *path);
/* One CS-framed SPI transaction through the BSCAN proxy (USER1 DR):
@@ -65,7 +65,7 @@ int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t,
* Bytes are MSB-first on the wire; bit-order juggling is internal.
* Follows the quartiq/OpenOCD jtagspi proxy framing. Single-device
* chain only. Requires a proxy bitstream already loaded (USER1 live). */
int bscan_spi_xfer(jtag_core *jc, const fpga_target *t,
int bscan_spi_xfer(jtag_core *jc, const jtag_target *t,
const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen);

View File

@@ -1,311 +0,0 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yaml.h>
#include "fpga.h"
/*
* FPGA registry loaded at runtime from a YAML file (no longer a
* compile-time array). Lookup order for the file:
* 1. $BS_FPGA_REGISTRY (if set and non-empty)
* 2. "fpga_registry.yaml" relative to the current directory
* — the same CWD-relative convention as bsdl_files/ and bscan_proxies/,
* so run bs_explorer from the repository root.
*
* Loaded lazily on first access and cached for the process lifetime.
* The schema is one flat mapping per entry; see fpga_registry.yaml.
*/
#define DEFAULT_REGISTRY_FILE "data/fpga_registry.yaml"
static fpga_target *g_registry = NULL;
static int g_count = 0;
static int g_loaded = 0; /* load attempted (success or not) */
static char g_source[1024] = "";
/* ---- small helpers ----------------------------------------------- */
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 fpga_family parse_family(const char *s)
{
if (!s) return FPGA_FAMILY_UNKNOWN;
if (!strcmp(s, "xilinx_7")) return FPGA_FAMILY_XILINX_7;
if (!strcmp(s, "xilinx_us")) return FPGA_FAMILY_XILINX_US;
if (!strcmp(s, "xilinx_usp")) return FPGA_FAMILY_XILINX_USP;
if (!strcmp(s, "microsemi_igloo2")) return FPGA_FAMILY_MICROSEMI_IGLOO2;
if (!strcmp(s, "microsemi_smartfusion2")) return FPGA_FAMILY_MICROSEMI_SMARTFUSION2;
if (!strcmp(s, "lattice_machxo2")) return FPGA_FAMILY_LATTICE_MACHXO2;
if (!strcmp(s, "lattice_machxo3")) return FPGA_FAMILY_LATTICE_MACHXO3;
fprintf(stderr, "fpga: unknown family '%s'\n", s);
return FPGA_FAMILY_UNKNOWN;
}
static fpga_prog_method parse_prog(const char *s)
{
if (!s) return FPGA_PROG_NONE;
if (!strcmp(s, "proxy_spi")) return FPGA_PROG_PROXY_SPI;
if (!strcmp(s, "svf")) return FPGA_PROG_SVF;
if (!strcmp(s, "none")) return FPGA_PROG_NONE;
fprintf(stderr, "fpga: unknown prog method '%s'\n", s);
return FPGA_PROG_NONE;
}
/* Guess the programming method when the entry doesn't state one. */
static fpga_prog_method infer_prog(const fpga_target *t)
{
if (t->proxy_bitstream) return FPGA_PROG_PROXY_SPI;
switch (t->family) {
case FPGA_FAMILY_MICROSEMI_IGLOO2:
case FPGA_FAMILY_MICROSEMI_SMARTFUSION2:
case FPGA_FAMILY_LATTICE_MACHXO2:
case FPGA_FAMILY_LATTICE_MACHXO3: return FPGA_PROG_SVF;
default: return FPGA_PROG_NONE;
}
}
static unsigned int parse_caveats(const char *s)
{
unsigned int flags = 0;
char buf[256];
char *tok;
if (!s) return 0;
strncpy(buf, s, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
for (tok = strtok(buf, " ,|"); tok; tok = strtok(NULL, " ,|")) {
if (!strcmp(tok, "cclk_via_startup"))
flags |= FPGA_CAVEAT_CCLK_VIA_STARTUP;
else
fprintf(stderr, "fpga: unknown caveat '%s'\n", tok);
}
return flags;
}
/* Apply one "key: value" pair (both scalars) to the entry being built. */
static void set_field(fpga_target *t, const char *key, const char *val)
{
if (!strcmp(key, "name")) { free((void *)t->name); t->name = xstrdup(val); }
else if (!strcmp(key, "idcode")) t->idcode = strtoul(val, NULL, 0);
else if (!strcmp(key, "idcode_mask")) t->idcode_mask = strtoul(val, NULL, 0);
else if (!strcmp(key, "family")) t->family = parse_family(val);
else if (!strcmp(key, "bsdl")) { free((void *)t->bsdl_filename); t->bsdl_filename = xstrdup(val); }
else if (!strcmp(key, "ir_length")) t->ir_length = (int)strtol(val, NULL, 0);
else if (!strcmp(key, "ir_cfg_in")) t->ir_cfg_in = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_user1")) t->ir_user1 = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jprogram")) t->ir_jprogram = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jstart")) t->ir_jstart = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jshutdown")) t->ir_jshutdown = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_isc_disable")) t->ir_isc_disable = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "proxy_bitstream")) { free((void *)t->proxy_bitstream); t->proxy_bitstream = xstrdup(val); }
else if (!strcmp(key, "caveats")) t->caveats = parse_caveats(val);
else if (!strcmp(key, "max_tck_khz")) t->max_tck_khz = (int)strtol(val, NULL, 0);
else if (!strcmp(key, "prog")) t->prog = parse_prog(val);
else fprintf(stderr, "fpga: unknown key '%s'\n", key);
}
static int commit_entry(const fpga_target *cur)
{
fpga_target *tmp = (fpga_target *)realloc(g_registry,
(g_count + 1) * sizeof(*g_registry));
if (!tmp) {
fprintf(stderr, "fpga: out of memory loading registry\n");
return -1;
}
g_registry = tmp;
g_registry[g_count] = *cur;
if (g_registry[g_count].prog == FPGA_PROG_NONE)
g_registry[g_count].prog = infer_prog(&g_registry[g_count]);
g_count++;
return 0;
}
/* Walk the YAML event stream. The document is a mapping with a single
* "fpgas" key holding a sequence of flat (scalar-only) mappings. */
static int load_registry(const char *path)
{
FILE *f;
yaml_parser_t parser;
yaml_event_t ev;
int done = 0;
int rc = 0;
int in_fpgas = 0; /* inside the fpgas: sequence */
int in_item = 0; /* inside one entry mapping */
int expect_fpgas_seq = 0; /* last top-level key was "fpgas" */
char *pending_key = NULL;
fpga_target cur;
f = fopen(path, "rb");
if (!f) return -1;
if (!yaml_parser_initialize(&parser)) { fclose(f); return -1; }
yaml_parser_set_input_file(&parser, f);
while (!done) {
if (!yaml_parser_parse(&parser, &ev)) {
fprintf(stderr, "fpga: 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_SCALAR_EVENT: {
const char *v = (const char *)ev.data.scalar.value;
if (!in_fpgas) {
if (!strcmp(v, "fpgas"))
expect_fpgas_seq = 1;
} else if (in_item) {
if (!pending_key) {
pending_key = xstrdup(v);
} else {
set_field(&cur, pending_key, v);
free(pending_key);
pending_key = NULL;
}
}
break;
}
case YAML_SEQUENCE_START_EVENT:
if (expect_fpgas_seq && !in_fpgas) {
in_fpgas = 1;
expect_fpgas_seq = 0;
}
break;
case YAML_SEQUENCE_END_EVENT:
if (in_fpgas && !in_item)
in_fpgas = 0; /* end of the fpgas list */
break;
case YAML_MAPPING_START_EVENT:
if (in_fpgas && !in_item) {
memset(&cur, 0, sizeof(cur));
cur.idcode_mask = 0xFFFFFFFFUL; /* safe default: exact match */
in_item = 1;
}
break;
case YAML_MAPPING_END_EVENT:
if (in_item) {
in_item = 0;
free(pending_key);
pending_key = NULL;
if (commit_entry(&cur) != 0) {
rc = -1;
yaml_event_delete(&ev);
done = 1;
continue;
}
}
break;
case YAML_STREAM_END_EVENT:
done = 1;
break;
default:
break;
}
yaml_event_delete(&ev);
}
free(pending_key);
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_FPGA_REGISTRY");
if (!path || !*path) path = DEFAULT_REGISTRY_FILE;
load_registry(path); /* fills g_registry/g_count; warns on its own errors */
if (g_count > 0) {
strncpy(g_source, path, sizeof(g_source) - 1);
g_source[sizeof(g_source) - 1] = '\0';
} else {
fprintf(stderr,
"fpga: no targets loaded from '%s' "
"(set BS_FPGA_REGISTRY, or run from the directory containing it)\n",
path);
}
}
/* ---- public API -------------------------------------------------- */
int fpga_get_target_count(void)
{
ensure_loaded();
return g_count;
}
const fpga_target *fpga_get_target_by_index(int index)
{
ensure_loaded();
if (index < 0 || index >= g_count) {
return NULL;
}
return &g_registry[index];
}
const fpga_target *fpga_lookup_by_idcode(unsigned long idcode)
{
int i;
ensure_loaded();
for (i = 0; i < g_count; i++) {
const fpga_target *t = &g_registry[i];
if ((idcode & t->idcode_mask) == (t->idcode & t->idcode_mask)) {
return t;
}
}
return NULL;
}
const char *fpga_family_name(fpga_family f)
{
switch (f) {
case FPGA_FAMILY_XILINX_7: return "Xilinx 7-Series";
case FPGA_FAMILY_XILINX_US: return "Xilinx UltraScale";
case FPGA_FAMILY_XILINX_USP: return "Xilinx UltraScale+";
case FPGA_FAMILY_MICROSEMI_IGLOO2: return "Microsemi IGLOO2";
case FPGA_FAMILY_MICROSEMI_SMARTFUSION2: return "Microsemi SmartFusion2";
case FPGA_FAMILY_LATTICE_MACHXO2: return "Lattice MachXO2";
case FPGA_FAMILY_LATTICE_MACHXO3: return "Lattice MachXO3";
case FPGA_FAMILY_UNKNOWN:
default: return "Unknown";
}
}
const char *fpga_prog_method_name(fpga_prog_method m)
{
switch (m) {
case FPGA_PROG_PROXY_SPI: return "proxy_spi";
case FPGA_PROG_SVF: return "svf";
case FPGA_PROG_NONE:
default: return "none";
}
}
const char *fpga_registry_source(void)
{
ensure_loaded();
return g_source[0] ? g_source : NULL;
}

View File

@@ -1,78 +0,0 @@
#ifndef _FPGA_H
#define _FPGA_H
/*
* Per-target FPGA descriptor and registry.
*
* Holds the facts that cannot be derived from the BSDL alone:
* - IDCODE pattern to match on the chain
* - private IR opcodes (USER1, CFG_IN, JPROGRAM, …) needed for
* configuration and for the BSCAN proxy bridge (Phase 2.5)
* - path to the BSCAN proxy bitstream
* - per-target caveats (known hardware gotchas)
*
* The registry is loaded at runtime from a YAML file (no longer a
* compile-time array). Adding an FPGA = one entry in the YAML +
* its .bsd in bsdl_files/ + (optionally) its proxy .bit in
* bscan_proxies/. See fpga_registry.yaml for the format.
*/
#include "jtag_core/jtag_core.h"
typedef enum {
FPGA_FAMILY_UNKNOWN = 0,
FPGA_FAMILY_XILINX_7,
FPGA_FAMILY_XILINX_US,
FPGA_FAMILY_XILINX_USP,
FPGA_FAMILY_MICROSEMI_IGLOO2,
FPGA_FAMILY_MICROSEMI_SMARTFUSION2,
FPGA_FAMILY_LATTICE_MACHXO2,
FPGA_FAMILY_LATTICE_MACHXO3,
} fpga_family;
/* Programming method — which backend drives this part. */
typedef enum {
FPGA_PROG_NONE = 0, /* no known method */
FPGA_PROG_PROXY_SPI, /* Xilinx external SPI flash via the BSCAN proxy */
FPGA_PROG_SVF, /* play a vendor-exported SVF (Lattice/Microsemi/…) */
} fpga_prog_method;
/* Caveat flags: known hardware gotchas for a part. */
#define FPGA_CAVEAT_CCLK_VIA_STARTUP (1u << 0) /* CCLK not directly drivable in EXTEST */
typedef struct {
const char *name; /* human-readable part name */
unsigned long idcode; /* IDCODE pattern */
unsigned long idcode_mask; /* bits to ignore (typically 0x0FFFFFFF for Xilinx — version masked) */
fpga_family family;
const char *bsdl_filename; /* basename within bsdl_files/ */
int ir_length; /* IR width in bits */
/* Private IR opcodes (0 = N/A for this family).
* For Xilinx, these are read from the BSDL INSTRUCTION_OPCODE block. */
unsigned int ir_cfg_in;
unsigned int ir_user1;
unsigned int ir_jprogram;
unsigned int ir_jstart;
unsigned int ir_jshutdown;
unsigned int ir_isc_disable;
const char *proxy_bitstream; /* path under bscan_proxies/, NULL if not yet available */
unsigned int caveats; /* FPGA_CAVEAT_* flags */
int max_tck_khz; /* max safe JTAG TCK for this part/board, 0 = unspecified */
fpga_prog_method prog; /* programming backend; inferred when omitted */
} fpga_target;
/* Registry access. The YAML file is loaded lazily on first call to any
* of these, and cached for the process lifetime. */
int fpga_get_target_count(void);
const fpga_target * fpga_get_target_by_index(int index);
const fpga_target * fpga_lookup_by_idcode(unsigned long idcode);
const char * fpga_family_name(fpga_family f);
const char * fpga_prog_method_name(fpga_prog_method m);
/* Path the registry was loaded from, or NULL if nothing loaded
* (file missing / parse error). For diagnostics. */
const char * fpga_registry_source(void);
#endif

View File

@@ -0,0 +1,9 @@
file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(program ${ALL_SOURCES})
# program dispatches to these backends; declare the deps so the static
# libs are ordered correctly on the link line (program before svf/arm_debug).
target_link_libraries(program PUBLIC svf arm_debug)

View File

@@ -0,0 +1,36 @@
#include <stddef.h>
#include "program.h"
#include "svf/svf.h"
#include "arm_debug/arm_debug.h"
/* program_log_fn, svf_log_fn and arm_log_fn are the same shape
* (void(*)(void*,int,const char*)); pass the caller's log through. */
int program_dispatch(jtag_core *jc, const jtag_target *t, const char *file,
program_log_fn log, void *user)
{
if (!t) {
if (log) log(user, 1, "no matching target on the chain");
return -1;
}
switch (t->prog) {
case TARGET_PROG_SVF:
return svf_play_file(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);
case TARGET_PROG_PROXY_SPI:
if (log) log(user, 1,
"proxy_spi: use the flash workflow instead "
"(bscan_load_bitstream, then flash_write / flash_verify)");
return -1;
case TARGET_PROG_NONE:
default:
if (log) log(user, 1, "no programming method known for this target");
return -1;
}
}

View File

@@ -0,0 +1,25 @@
#ifndef _PROGRAM_H
#define _PROGRAM_H
/*
* Programming-backend dispatch.
*
* One entry point routes a target to the right backend by its `prog`
* method (set/inferred in the registry):
* svf -> play the file through the SVF player;
* arm_flash -> ARM RAM-loader flash (not implemented yet);
* proxy_spi -> Xilinx external flash; a one-shot here is multi-step,
* so this points at the flash_* workflow for now.
*
* This is the seam new backends plug into — add a `prog` value + a case.
*/
#include "jtag_core/jtag_core.h"
#include "target/target.h"
typedef void (*program_log_fn)(void *user, int is_error, const char *msg);
int program_dispatch(jtag_core *jc, const jtag_target *t, const char *file,
program_log_fn log, void *user);
#endif

View File

@@ -37,10 +37,11 @@
#include "bsdl_parser/bsdl_loader.h"
#include "os_interface/os_interface.h"
#include "fpga/fpga.h"
#include "target/target.h"
#include "probes/probes.h"
#include "bscan/bscan.h"
#include "svf/svf.h"
#include "program/program.h"
#include "spi_flash/spi_flash.h"
#include "env.h"
@@ -1603,11 +1604,11 @@ static int autoinit_run(script_ctx *ctx)
static int chain_max_tck_khz(jtag_core *jc)
{
int i, n, cap = 0;
const fpga_target *t;
const jtag_target *t;
n = jtagcore_get_number_of_devices(jc);
for (i = 0; i < n; i++) {
t = fpga_lookup_by_idcode(jtagcore_get_dev_id(jc, i));
t = target_lookup_by_idcode(jtagcore_get_dev_id(jc, i));
if (t && t->max_tck_khz > 0 && (cap == 0 || t->max_tck_khz < cap))
cap = t->max_tck_khz;
}
@@ -2981,48 +2982,55 @@ static int cmd_get_pins_list(script_ctx *ctx, char *line)
return JTAG_CORE_BAD_PARAMETER;
}
const char *cmd_fpga_list_help[] = {
const char *cmd_target_list_help[] = {
"",
"Lists the FPGA targets in the registry (loaded from fpga_registry.yaml).",
"Lists the JTAG targets in the registry (loaded from data/targets.yaml).",
""};
static int cmd_fpga_list(script_ctx *ctx, char *line)
static int cmd_target_list(script_ctx *ctx, char *line)
{
int i, n;
const fpga_target *t;
const jtag_target *t;
const char *src;
(void)line;
n = fpga_get_target_count();
src = fpga_registry_source();
ctx->script_printf(ctx, MSG_INFO_0, "%d FPGA target(s) registered (from %s):\n",
n = target_get_count();
src = target_registry_source();
ctx->script_printf(ctx, MSG_INFO_0, "%d target(s) registered (from %s):\n",
n, src ? src : "<no registry loaded>");
for (i = 0; i < n; i++) {
t = fpga_get_target_by_index(i);
t = target_get_by_index(i);
ctx->script_printf(ctx, MSG_NONE,
" [%d] IDCODE %.8lX/%.8lX %s (%s)\n",
i, t->idcode, t->idcode_mask,
t->name, fpga_family_name(t->family));
ctx->script_printf(ctx, MSG_NONE,
" bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz prog=%s\n",
t->bsdl_filename, t->ir_length,
t->proxy_bitstream ? t->proxy_bitstream : "(none yet)",
t->caveats, t->max_tck_khz, fpga_prog_method_name(t->prog));
" [%d] IDCODE %.8lX/%.8lX %s (%s, %s, prog=%s)\n",
i, t->idcode, t->idcode_mask, t->name,
target_kind_name(t->kind), target_family_name(t->family),
target_prog_name(t->prog));
if (t->kind == TARGET_CPU)
ctx->script_printf(ctx, MSG_NONE,
" ram=0x%lX+0x%lX flash=0x%lX+0x%lX ir=%d maxtck=%dkHz\n",
t->cpu.ram_base, t->cpu.ram_size,
t->cpu.flash_base, t->cpu.flash_size, t->ir_length, t->max_tck_khz);
else
ctx->script_printf(ctx, MSG_NONE,
" bsdl=%s ir=%d proxy=%s caveats=0x%x maxtck=%dkHz\n",
t->fpga.bsdl_filename ? t->fpga.bsdl_filename : "(none)", t->ir_length,
t->fpga.proxy_bitstream ? t->fpga.proxy_bitstream : "(none)",
t->fpga.caveats, t->max_tck_khz);
}
return JTAG_CORE_NO_ERROR;
}
const char *cmd_fpga_info_help[] = {
const char *cmd_target_info_help[] = {
"",
"Reports, for each device on the JTAG chain, whether its IDCODE",
"matches a known FPGA target. Requires jtag_scan or jtag_autoinit first.",
"matches a known target. Requires jtag_scan or jtag_autoinit first.",
""};
static int cmd_fpga_info(script_ctx *ctx, char *line)
static int cmd_target_info(script_ctx *ctx, char *line)
{
jtag_core *jc;
int i, n;
unsigned long idcode;
const fpga_target *t;
const jtag_target *t;
(void)line;
jc = (jtag_core *)ctx->app_ctx;
@@ -3035,14 +3043,14 @@ static int cmd_fpga_info(script_ctx *ctx, char *line)
for (i = 0; i < n; i++) {
idcode = jtagcore_get_dev_id(jc, i);
t = fpga_lookup_by_idcode(idcode);
t = target_lookup_by_idcode(idcode);
if (t) {
ctx->script_printf(ctx, MSG_INFO_0,
"Device %d IDCODE 0x%.8lX -> %s [%s]\n",
i, idcode, t->name, fpga_family_name(t->family));
"Device %d IDCODE 0x%.8lX -> %s [%s, %s]\n",
i, idcode, t->name, target_kind_name(t->kind), target_family_name(t->family));
ctx->script_printf(ctx, MSG_NONE,
" prog: %s\n", fpga_prog_method_name(t->prog));
if (t->caveats & FPGA_CAVEAT_CCLK_VIA_STARTUP) {
" prog: %s\n", target_prog_name(t->prog));
if (t->kind == TARGET_FPGA && (t->fpga.caveats & TARGET_CAVEAT_CCLK_VIA_STARTUP)) {
ctx->script_printf(ctx, MSG_NONE,
" caveat: CCLK routed via STARTUP primitive (not drivable in EXTEST)\n");
}
@@ -3127,7 +3135,7 @@ static int cmd_bscan_shift_dr(script_ctx *ctx, char *line)
const char *cmd_bscan_load_bitstream_help[] = {
"<device> <path>",
"Loads a bitstream (.bit or raw .bin) into device <device> via JPROGRAM/CFG_IN/JSTART.",
"Device must be matched in the FPGA registry (run fpga_info to check).",
"Device must be matched in the FPGA registry (run target_info to check).",
""};
static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line)
{
@@ -3135,7 +3143,7 @@ static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line)
char path[DEFAULT_BUFLEN];
int device, ret;
unsigned long idcode;
const fpga_target *t;
const jtag_target *t;
jtag_core *jc = (jtag_core *)ctx->app_ctx;
if (get_param(ctx, line, 1, dev_txt) < 0 || get_param(ctx, line, 2, path) < 0) {
@@ -3149,7 +3157,7 @@ static int cmd_bscan_load_bitstream(script_ctx *ctx, char *line)
return JTAG_CORE_NOT_FOUND;
}
idcode = jtagcore_get_dev_id(jc, device);
t = fpga_lookup_by_idcode(idcode);
t = target_lookup_by_idcode(idcode);
if (!t) {
ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode);
return JTAG_CORE_NOT_FOUND;
@@ -3177,7 +3185,7 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line)
char dev_txt[DEFAULT_BUFLEN];
int device;
unsigned long idcode;
const fpga_target *t;
const jtag_target *t;
uint8_t tx = 0x9F;
uint8_t rx[3] = {0};
jtag_core *jc = (jtag_core *)ctx->app_ctx;
@@ -3193,7 +3201,7 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line)
return JTAG_CORE_NOT_FOUND;
}
idcode = jtagcore_get_dev_id(jc, device);
t = fpga_lookup_by_idcode(idcode);
t = target_lookup_by_idcode(idcode);
if (!t) {
ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode);
return JTAG_CORE_NOT_FOUND;
@@ -3214,7 +3222,7 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line)
/* Adapt the BSCAN proxy to the spi_flash transport callback. */
struct flash_proxy_ctx {
jtag_core *jc;
const fpga_target *t;
const jtag_target *t;
};
static int flash_proxy_xfer(void *c, const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen)
@@ -3231,14 +3239,14 @@ static int flash_setup(script_ctx *ctx, int device,
{
jtag_core *jc = (jtag_core *)ctx->app_ctx;
unsigned long idcode;
const fpga_target *t;
const jtag_target *t;
if (jtagcore_get_number_of_devices(jc) <= 0) {
ctx->script_printf(ctx, MSG_ERROR, "No device on the chain. Run jtag_autoinit first.\n");
return JTAG_CORE_NOT_FOUND;
}
idcode = jtagcore_get_dev_id(jc, device);
t = fpga_lookup_by_idcode(idcode);
t = target_lookup_by_idcode(idcode);
if (!t) {
ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode);
return JTAG_CORE_NOT_FOUND;
@@ -3542,6 +3550,42 @@ static int cmd_svf_play(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR;
}
const char *cmd_program_help[] = {
"<dev>(int) <file>(str)",
"Program a chain device from <file>, dispatching on the target's",
"registry programming method: svf -> play the SVF; proxy_spi -> use",
"the flash workflow (bscan_load_bitstream + flash_write/verify);",
"arm_flash -> ARM RAM-loader flash (not implemented yet).",
"Run jtag_autoinit first so the device is identified.",
""
};
static int cmd_program(script_ctx *ctx, char *line)
{
jtag_core *jc;
char dev_s[32], path[MAX_PATH + 1];
int dev;
unsigned long idcode;
const jtag_target *t;
jc = (jtag_core *)ctx->app_ctx;
if (get_param(ctx, line, 1, dev_s) <= 0 || get_param(ctx, line, 2, path) <= 0) {
ctx->script_printf(ctx, MSG_ERROR, "Usage: program <dev> <file>\n");
return JTAG_CORE_BAD_PARAMETER;
}
dev = (int)strtol(dev_s, NULL, 0);
idcode = jtagcore_get_dev_id(jc, dev);
t = target_lookup_by_idcode(idcode);
if (!t) {
ctx->script_printf(ctx, MSG_ERROR,
"Device %d (IDCODE 0x%.8lX) not in the registry. Run jtag_autoinit.\n",
dev, idcode);
return JTAG_CORE_NOT_FOUND;
}
if (program_dispatch(jc, t, path, svf_log_cb, ctx) < 0)
return JTAG_CORE_ACCESS_ERROR;
return JTAG_CORE_NO_ERROR;
}
cmd_list script_commands_list[] =
{
{"print", cmd_print, cmd_print_help},
@@ -3586,8 +3630,8 @@ cmd_list script_commands_list[] =
{"jtag_spi_miso", cmd_set_spi_miso_pin, cmd_set_spi_miso_pin_help},
{"jtag_spi_clk", cmd_set_spi_clk_pin, cmd_set_spi_clk_pin_help},
{"jtag_spi_xfer", cmd_spi_rd_wr, cmd_spi_rd_wr_help},
{"fpga_list", cmd_fpga_list, cmd_fpga_list_help},
{"fpga_info", cmd_fpga_info, cmd_fpga_info_help},
{"target_list", cmd_target_list, cmd_target_list_help},
{"target_info", cmd_target_info, cmd_target_info_help},
{"bscan_set_ir", cmd_bscan_set_ir, cmd_bscan_set_ir_help},
{"bscan_shift_dr", cmd_bscan_shift_dr, cmd_bscan_shift_dr_help},
{"bscan_load_bitstream", cmd_bscan_load_bitstream, cmd_bscan_load_bitstream_help},
@@ -3598,6 +3642,7 @@ cmd_list script_commands_list[] =
{"flash_write", cmd_flash_write, cmd_flash_write_help},
{"flash_verify", cmd_flash_verify, cmd_flash_verify_help},
{"svf_play", cmd_svf_play, cmd_svf_play_help},
{"program", cmd_program, cmd_program_help},
{0, 0}};
///////////////////////////////////////////////////////////////////////////////

View File

@@ -4,11 +4,11 @@ file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(fpga ${ALL_SOURCES})
add_library(target ${ALL_SOURCES})
# The registry is parsed from YAML at runtime via libyaml (the canonical
# C YAML parser). Found through pkg-config; PUBLIC so the executable that
# links fpga picks up the dependency transitively.
# links target picks up the dependency transitively.
find_package(PkgConfig REQUIRED)
pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-0.1)
target_link_libraries(fpga PUBLIC PkgConfig::YAML)
target_link_libraries(target PUBLIC PkgConfig::YAML)

344
src/modules/target/target.c Normal file
View File

@@ -0,0 +1,344 @@
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yaml.h>
#include "target.h"
/*
* JTAG target registry loaded at runtime from a YAML file. Lookup order:
* 1. $BS_TARGETS (if set and non-empty)
* 2. "data/targets.yaml" relative to the current directory
* — same CWD-relative convention as data/bsdl_files/, so run bs_explorer
* from the repository root.
*
* Loaded lazily on first access, cached for the process lifetime. The
* schema is one flat mapping per entry under a top-level `targets:`
* sequence; `kind` (fpga|cpu) selects which fields apply. See
* data/targets.yaml.
*/
#define DEFAULT_REGISTRY_FILE "data/targets.yaml"
static jtag_target *g_registry = NULL;
static int g_count = 0;
static int g_loaded = 0; /* load attempted (success or not) */
static char g_source[1024] = "";
/* ---- small helpers ----------------------------------------------- */
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 target_kind parse_kind(const char *s)
{
if (s && !strcmp(s, "cpu")) return TARGET_CPU;
return TARGET_FPGA; /* default / "fpga" */
}
static target_family parse_family(const char *s)
{
if (!s) return TARGET_FAMILY_UNKNOWN;
if (!strcmp(s, "xilinx_7")) return TARGET_FAMILY_XILINX_7;
if (!strcmp(s, "xilinx_us")) return TARGET_FAMILY_XILINX_US;
if (!strcmp(s, "xilinx_usp")) return TARGET_FAMILY_XILINX_USP;
if (!strcmp(s, "microsemi_igloo2")) return TARGET_FAMILY_MICROSEMI_IGLOO2;
if (!strcmp(s, "microsemi_smartfusion2")) return TARGET_FAMILY_MICROSEMI_SMARTFUSION2;
if (!strcmp(s, "lattice_machxo2")) return TARGET_FAMILY_LATTICE_MACHXO2;
if (!strcmp(s, "lattice_machxo3")) return TARGET_FAMILY_LATTICE_MACHXO3;
if (!strcmp(s, "arm7")) return TARGET_FAMILY_ARM7;
if (!strcmp(s, "arm9")) return TARGET_FAMILY_ARM9;
fprintf(stderr, "target: unknown family '%s'\n", s);
return TARGET_FAMILY_UNKNOWN;
}
static target_prog parse_prog(const char *s)
{
if (!s) return TARGET_PROG_NONE;
if (!strcmp(s, "proxy_spi")) return TARGET_PROG_PROXY_SPI;
if (!strcmp(s, "svf")) return TARGET_PROG_SVF;
if (!strcmp(s, "arm_flash")) return TARGET_PROG_ARM_FLASH;
if (!strcmp(s, "none")) return TARGET_PROG_NONE;
fprintf(stderr, "target: unknown prog method '%s'\n", s);
return TARGET_PROG_NONE;
}
static target_debug parse_debug(const char *s)
{
if (!s) return TARGET_DEBUG_NONE;
if (!strcmp(s, "embeddedice")) return TARGET_DEBUG_EMBEDDEDICE;
if (!strcmp(s, "none")) return TARGET_DEBUG_NONE;
fprintf(stderr, "target: unknown debug iface '%s'\n", s);
return TARGET_DEBUG_NONE;
}
/* Guess the programming method when the entry doesn't state one. */
static target_prog infer_prog(const jtag_target *t)
{
if (t->kind == TARGET_CPU)
return (t->cpu.debug != TARGET_DEBUG_NONE) ? TARGET_PROG_ARM_FLASH : TARGET_PROG_NONE;
if (t->fpga.proxy_bitstream) return TARGET_PROG_PROXY_SPI;
switch (t->family) {
case TARGET_FAMILY_MICROSEMI_IGLOO2:
case TARGET_FAMILY_MICROSEMI_SMARTFUSION2:
case TARGET_FAMILY_LATTICE_MACHXO2:
case TARGET_FAMILY_LATTICE_MACHXO3: return TARGET_PROG_SVF;
default: return TARGET_PROG_NONE;
}
}
static unsigned int parse_caveats(const char *s)
{
unsigned int flags = 0;
char buf[256];
char *tok;
if (!s) return 0;
strncpy(buf, s, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
for (tok = strtok(buf, " ,|"); tok; tok = strtok(NULL, " ,|")) {
if (!strcmp(tok, "cclk_via_startup"))
flags |= TARGET_CAVEAT_CCLK_VIA_STARTUP;
else
fprintf(stderr, "target: unknown caveat '%s'\n", tok);
}
return flags;
}
/* Apply one "key: value" pair (both scalars) to the entry being built.
* FPGA keys route into t->fpga, CPU keys into t->cpu; the rest is common. */
static void set_field(jtag_target *t, const char *key, const char *val)
{
if (!strcmp(key, "name")) { free((void *)t->name); t->name = xstrdup(val); }
else if (!strcmp(key, "idcode")) t->idcode = strtoul(val, NULL, 0);
else if (!strcmp(key, "idcode_mask")) t->idcode_mask = strtoul(val, NULL, 0);
else if (!strcmp(key, "kind")) t->kind = parse_kind(val);
else if (!strcmp(key, "family")) t->family = parse_family(val);
else if (!strcmp(key, "ir_length")) t->ir_length = (int)strtol(val, NULL, 0);
else if (!strcmp(key, "prog")) t->prog = parse_prog(val);
else if (!strcmp(key, "max_tck_khz")) t->max_tck_khz = (int)strtol(val, NULL, 0);
/* FPGA */
else if (!strcmp(key, "bsdl")) { free((void *)t->fpga.bsdl_filename); t->fpga.bsdl_filename = xstrdup(val); }
else if (!strcmp(key, "ir_cfg_in")) t->fpga.ir_cfg_in = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_user1")) t->fpga.ir_user1 = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jprogram")) t->fpga.ir_jprogram = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jstart")) t->fpga.ir_jstart = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_jshutdown")) t->fpga.ir_jshutdown = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "ir_isc_disable")) t->fpga.ir_isc_disable = (unsigned int)strtoul(val, NULL, 0);
else if (!strcmp(key, "proxy_bitstream")) { free((void *)t->fpga.proxy_bitstream); t->fpga.proxy_bitstream = xstrdup(val); }
else if (!strcmp(key, "caveats")) t->fpga.caveats = parse_caveats(val);
/* CPU */
else if (!strcmp(key, "debug")) t->cpu.debug = parse_debug(val);
else if (!strcmp(key, "ram_base")) t->cpu.ram_base = strtoul(val, NULL, 0);
else if (!strcmp(key, "ram_size")) t->cpu.ram_size = strtoul(val, NULL, 0);
else if (!strcmp(key, "flash_base")) t->cpu.flash_base = strtoul(val, NULL, 0);
else if (!strcmp(key, "flash_size")) t->cpu.flash_size = strtoul(val, NULL, 0);
else fprintf(stderr, "target: unknown key '%s'\n", key);
}
static int commit_entry(const jtag_target *cur)
{
jtag_target *tmp = (jtag_target *)realloc(g_registry,
(g_count + 1) * sizeof(*g_registry));
if (!tmp) {
fprintf(stderr, "target: out of memory loading registry\n");
return -1;
}
g_registry = tmp;
g_registry[g_count] = *cur;
if (g_registry[g_count].prog == TARGET_PROG_NONE)
g_registry[g_count].prog = infer_prog(&g_registry[g_count]);
g_count++;
return 0;
}
/* Walk the YAML event stream. A mapping with a single `targets:` key
* holding a sequence of flat (scalar-only) mappings. */
static int load_registry(const char *path)
{
FILE *f;
yaml_parser_t parser;
yaml_event_t ev;
int done = 0, rc = 0;
int in_seq = 0; /* inside the targets: sequence */
int in_item = 0; /* inside one entry mapping */
int expect_seq = 0; /* last top-level key was "targets" */
char *pending_key = NULL;
jtag_target cur;
f = fopen(path, "rb");
if (!f) return -1;
if (!yaml_parser_initialize(&parser)) { fclose(f); return -1; }
yaml_parser_set_input_file(&parser, f);
while (!done) {
if (!yaml_parser_parse(&parser, &ev)) {
fprintf(stderr, "target: 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_SCALAR_EVENT: {
const char *v = (const char *)ev.data.scalar.value;
if (!in_seq) {
if (!strcmp(v, "targets"))
expect_seq = 1;
} else if (in_item) {
if (!pending_key) {
pending_key = xstrdup(v);
} else {
set_field(&cur, pending_key, v);
free(pending_key);
pending_key = NULL;
}
}
break;
}
case YAML_SEQUENCE_START_EVENT:
if (expect_seq && !in_seq) { in_seq = 1; expect_seq = 0; }
break;
case YAML_SEQUENCE_END_EVENT:
if (in_seq && !in_item) in_seq = 0;
break;
case YAML_MAPPING_START_EVENT:
if (in_seq && !in_item) {
memset(&cur, 0, sizeof(cur));
cur.idcode_mask = 0xFFFFFFFFUL; /* safe default: exact match */
in_item = 1;
}
break;
case YAML_MAPPING_END_EVENT:
if (in_item) {
in_item = 0;
free(pending_key);
pending_key = NULL;
if (commit_entry(&cur) != 0) {
rc = -1;
yaml_event_delete(&ev);
done = 1;
continue;
}
}
break;
case YAML_STREAM_END_EVENT:
done = 1;
break;
default:
break;
}
yaml_event_delete(&ev);
}
free(pending_key);
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_TARGETS");
if (!path || !*path) path = DEFAULT_REGISTRY_FILE;
load_registry(path);
if (g_count > 0) {
strncpy(g_source, path, sizeof(g_source) - 1);
g_source[sizeof(g_source) - 1] = '\0';
} else {
fprintf(stderr,
"target: no targets loaded from '%s' "
"(set BS_TARGETS, or run from the directory containing it)\n",
path);
}
}
/* ---- public API -------------------------------------------------- */
int target_get_count(void)
{
ensure_loaded();
return g_count;
}
const jtag_target *target_get_by_index(int index)
{
ensure_loaded();
if (index < 0 || index >= g_count) return NULL;
return &g_registry[index];
}
const jtag_target *target_lookup_by_idcode(unsigned long idcode)
{
int i;
ensure_loaded();
for (i = 0; i < g_count; i++) {
const jtag_target *t = &g_registry[i];
if ((idcode & t->idcode_mask) == (t->idcode & t->idcode_mask))
return t;
}
return NULL;
}
const char *target_family_name(target_family f)
{
switch (f) {
case TARGET_FAMILY_XILINX_7: return "Xilinx 7-Series";
case TARGET_FAMILY_XILINX_US: return "Xilinx UltraScale";
case TARGET_FAMILY_XILINX_USP: return "Xilinx UltraScale+";
case TARGET_FAMILY_MICROSEMI_IGLOO2: return "Microsemi IGLOO2";
case TARGET_FAMILY_MICROSEMI_SMARTFUSION2: return "Microsemi SmartFusion2";
case TARGET_FAMILY_LATTICE_MACHXO2: return "Lattice MachXO2";
case TARGET_FAMILY_LATTICE_MACHXO3: return "Lattice MachXO3";
case TARGET_FAMILY_ARM7: return "ARM7";
case TARGET_FAMILY_ARM9: return "ARM9";
case TARGET_FAMILY_UNKNOWN:
default: return "Unknown";
}
}
const char *target_prog_name(target_prog p)
{
switch (p) {
case TARGET_PROG_PROXY_SPI: return "proxy_spi";
case TARGET_PROG_SVF: return "svf";
case TARGET_PROG_ARM_FLASH: return "arm_flash";
case TARGET_PROG_NONE:
default: return "none";
}
}
const char *target_kind_name(target_kind k)
{
switch (k) {
case TARGET_CPU: return "cpu";
case TARGET_FPGA:
default: return "fpga";
}
}
const char *target_registry_source(void)
{
ensure_loaded();
return g_source[0] ? g_source : NULL;
}

112
src/modules/target/target.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef _TARGET_H
#define _TARGET_H
/*
* JTAG target registry — FPGAs and CPUs.
*
* A `jtag_target` describes one programmable device on the chain: the
* facts that can't be derived from the IDCODE/BSDL alone. Shared fields
* (IDCODE, IR length, family, programming method, max TCK) sit at the
* top; kind-specific facts live in the `fpga` or `cpu` sub-struct
* selected by `kind`.
*
* - FPGA: BSDL, BSCAN proxy bitstream, private config IR opcodes
* (CFG_IN/USER1/JPROGRAM/…), caveats.
* - CPU: debug interface (EmbeddedICE for ARM7/9), work-RAM for a
* flash loader, on-chip flash region. (The CPU programming backend
* itself is not implemented yet — see arm_debug/ and the design note.)
*
* The registry is loaded at runtime from data/targets.yaml (libyaml).
* Adding a target = one flat YAML entry (+ a .bsd for FPGAs). The
* `kind` selects which fields are meaningful.
*/
#include "jtag_core/jtag_core.h"
typedef enum {
TARGET_FPGA = 0,
TARGET_CPU,
} target_kind;
typedef enum {
TARGET_FAMILY_UNKNOWN = 0,
TARGET_FAMILY_XILINX_7,
TARGET_FAMILY_XILINX_US,
TARGET_FAMILY_XILINX_USP,
TARGET_FAMILY_MICROSEMI_IGLOO2,
TARGET_FAMILY_MICROSEMI_SMARTFUSION2,
TARGET_FAMILY_LATTICE_MACHXO2,
TARGET_FAMILY_LATTICE_MACHXO3,
TARGET_FAMILY_ARM7, /* ARM7TDMI(-S): LPC2xxx, AT91SAM7, … */
TARGET_FAMILY_ARM9, /* ARM9: also EmbeddedICE */
} target_family;
/* Programming backend — which handler drives this target (dispatched in
* the program/ module by this tag). */
typedef enum {
TARGET_PROG_NONE = 0, /* no known method */
TARGET_PROG_PROXY_SPI, /* Xilinx external SPI flash via the BSCAN proxy */
TARGET_PROG_SVF, /* play a vendor-exported SVF (Lattice/Microsemi/…) */
TARGET_PROG_ARM_FLASH, /* halt over JTAG debug, RAM loader -> on-chip flash (not implemented yet) */
} target_prog;
/* CPU debug transport (over JTAG). */
typedef enum {
TARGET_DEBUG_NONE = 0,
TARGET_DEBUG_EMBEDDEDICE, /* ARM7/ARM9 */
} target_debug;
/* Caveat flags: known hardware gotchas for a target. */
#define TARGET_CAVEAT_CCLK_VIA_STARTUP (1u << 0) /* CCLK not directly drivable in EXTEST */
/* FPGA-specific facts (valid when kind == TARGET_FPGA). */
typedef struct {
const char *bsdl_filename; /* basename within data/bsdl_files/ */
/* Private IR opcodes (0 = N/A). For Xilinx, from the BSDL. */
unsigned int ir_cfg_in;
unsigned int ir_user1;
unsigned int ir_jprogram;
unsigned int ir_jstart;
unsigned int ir_jshutdown;
unsigned int ir_isc_disable;
const char *proxy_bitstream; /* basename under data/bscan_proxies/, NULL if none */
unsigned int caveats; /* TARGET_CAVEAT_* flags */
} target_fpga;
/* CPU-specific facts (valid when kind == TARGET_CPU). */
typedef struct {
target_debug debug; /* debug transport */
unsigned long ram_base; /* work-RAM for the flash loader */
unsigned long ram_size;
unsigned long flash_base; /* on-chip flash region */
unsigned long flash_size;
} target_cpu;
typedef struct {
const char *name; /* human-readable part name */
unsigned long idcode; /* IDCODE pattern */
unsigned long idcode_mask; /* bits compared (e.g. 0x0FFFFFFF masks the version nibble) */
target_kind kind;
target_family family;
int ir_length; /* IR width in bits (chain property, all kinds) */
target_prog prog; /* programming backend; inferred when omitted */
int max_tck_khz; /* max safe JTAG TCK, 0 = unspecified */
target_fpga fpga; /* kind == TARGET_FPGA */
target_cpu cpu; /* kind == TARGET_CPU */
} jtag_target;
/* Registry access. data/targets.yaml is loaded lazily on first call and
* cached for the process lifetime. */
int target_get_count(void);
const jtag_target * target_get_by_index(int index);
const jtag_target * target_lookup_by_idcode(unsigned long idcode);
const char * target_family_name(target_family f);
const char * target_prog_name(target_prog p);
const char * target_kind_name(target_kind k);
/* Path the registry was loaded from, or NULL if nothing loaded. */
const char * target_registry_source(void);
#endif