#include #include #include #include #include #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 "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 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 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; 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_registry_source(void) { ensure_loaded(); return g_source[0] ? g_source : NULL; }