From 0c9cc679f1745c99cbba6464c450740e0c61b90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 23 May 2026 17:16:19 +0200 Subject: [PATCH] bscan_spi: implement bscan_spi_xfer over the jtagspi proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One CS-framed transaction: marker + 32-bit count + MOSI + read-latency skip + MISO, MSB-first on the wire, matching OpenOCD's jtagspi so the quartiq proxy bitstreams work unchanged. Half-duplex (tx,txlen,rx,rxlen) signature, single-device chain. NOT yet validated on hardware — protocol follows the OpenOCD reference but has not been confirmed against a live proxy + flash. Validation (read JEDEC ID on the KCU105) is the next step. --- modules/bscan_spi/bscan_spi.c | 91 ++++++++++++++++++++++++++++++----- modules/bscan_spi/bscan_spi.h | 12 +++-- 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/modules/bscan_spi/bscan_spi.c b/modules/bscan_spi/bscan_spi.c index cec6638..f767055 100644 --- a/modules/bscan_spi/bscan_spi.c +++ b/modules/bscan_spi/bscan_spi.c @@ -261,17 +261,86 @@ int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t, return ret; } +/* Pipeline latency between a MOSI bit going in and its MISO bit + * appearing on TDO, in TCK cycles. For a single-device chain this is + * one (the proxy registers TDO); equals jtag_tap_count_enabled() in + * 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, - const uint8_t *tx, uint8_t *rx, size_t nbytes) + const uint8_t *tx, size_t txlen, + uint8_t *rx, size_t rxlen) { - /* Skeleton — to be completed once we can validate the protocol - * against an actual quartiq jtagspi proxy bitstream. - * - * Expected outline: - * - bscan_set_ir(jc, t->ir_user1, t->ir_length) - * - bscan_shift_dr with
, where the header - * encodes the bit count and CS state per the proxy convention. - * - extract MISO bytes from the TDO data. */ - (void)jc; (void)t; (void)tx; (void)rx; (void)nbytes; - return -1; + /* DR frame (quartiq/OpenOCD jtagspi proxy, single device): + * marker(1)=1 | count(32, MSB-first) | MOSI(txlen*8, MSB-first/byte) + * | latency skip | MISO capture(rxlen*8, MSB-first/byte) + * count = total SPI bits - 1. The skip absorbs the TDO pipeline + * delay so the captured MISO aligns to byte boundaries. + * Bits are placed LSB-first per byte, the layout bscan_shift_dr + * shifts in order; ordering them here gives MSB-first on the wire. */ + size_t spi_bytes = txlen + rxlen; + uint32_t count; + int total_bits, dr_bytes, capture_start, bit, j; + size_t i; + uint8_t *dr_out, *dr_in; + + if (!drv_ok(jc) || !t || !t->ir_user1 || spi_bytes == 0) return -1; + if (txlen && !tx) return -1; + if (rxlen && !rx) return -1; + + count = (uint32_t)(spi_bytes * 8u) - 1u; + + total_bits = 1 + 32 + (int)txlen * 8; + if (rxlen) total_bits += BSCAN_SPI_READ_LATENCY + (int)rxlen * 8; + dr_bytes = (total_bits + 7) / 8; + + dr_out = calloc(1, (size_t)dr_bytes); + dr_in = rxlen ? calloc(1, (size_t)dr_bytes) : NULL; + if (!dr_out || (rxlen && !dr_in)) { free(dr_out); free(dr_in); return -1; } + +#define BS_SET(buf, pos) ((buf)[(pos) >> 3] |= (uint8_t)(1u << ((pos) & 7))) +#define BS_GET(buf, pos) (((buf)[(pos) >> 3] >> ((pos) & 7)) & 1u) + + bit = 0; + BS_SET(dr_out, bit); bit++; /* marker = 1 */ + + for (j = 31; j >= 0; j--) { /* count, MSB-first */ + if (count & (1u << j)) BS_SET(dr_out, bit); + bit++; + } + + for (i = 0; i < txlen; i++) { /* MOSI, MSB-first/byte */ + for (j = 7; j >= 0; j--) { + if (tx[i] & (1u << j)) BS_SET(dr_out, bit); + bit++; + } + } + + capture_start = -1; + if (rxlen) { + bit += BSCAN_SPI_READ_LATENCY; /* skip pipeline delay */ + capture_start = bit; + bit += (int)rxlen * 8; /* MISO region (MOSI=0) */ + } + + if (bscan_set_ir(jc, t->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; + } + + if (rxlen) { + memset(rx, 0, rxlen); + for (i = 0; i < rxlen * 8; i++) { + if (BS_GET(dr_in, capture_start + (int)i)) { + rx[i >> 3] |= (uint8_t)(1u << (7 - (i & 7))); /* MSB-first */ + } + } + } + +#undef BS_SET +#undef BS_GET + free(dr_out); + free(dr_in); + return 0; } diff --git a/modules/bscan_spi/bscan_spi.h b/modules/bscan_spi/bscan_spi.h index af20e88..3b4d117 100644 --- a/modules/bscan_spi/bscan_spi.h +++ b/modules/bscan_spi/bscan_spi.h @@ -50,10 +50,14 @@ int bscan_load_bitstream(jtag_core *jc, const fpga_target *t, int bscan_load_bitstream_file(jtag_core *jc, const fpga_target *t, const char *path); -/* Transfer `nbytes` of SPI data through the BSCAN proxy (USER1 DR). - * Placeholder: protocol details deferred until an actual proxy - * bitstream is available for testing. */ +/* One CS-framed SPI transaction through the BSCAN proxy (USER1 DR): + * clock out `txlen` MOSI bytes (e.g. command + address + write data), + * then read `rxlen` MISO bytes into `rx`. Either length may be 0. + * 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, - const uint8_t *tx, uint8_t *rx, size_t nbytes); + const uint8_t *tx, size_t txlen, + uint8_t *rx, size_t rxlen); #endif