From 431becb1904de80e963c96d0ceb38c2a78140355 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 16 Mar 2026 15:29:15 +0000 Subject: [PATCH] Support customization of FPGA image loading mechanism. --- firmware/common/fpga.h | 15 +++++++++- firmware/common/fpga_image.c | 43 +++++++++++---------------- firmware/common/ice40_spi.c | 7 +++-- firmware/common/ice40_spi.h | 3 +- firmware/hackrf_usb/hackrf_usb.c | 31 ++++++++++++++++++- firmware/hackrf_usb/usb_api_praline.c | 15 +++++++--- 6 files changed, 78 insertions(+), 36 deletions(-) diff --git a/firmware/common/fpga.h b/firmware/common/fpga.h index b7bb90bb..383444c5 100644 --- a/firmware/common/fpga.h +++ b/firmware/common/fpga.h @@ -44,6 +44,19 @@ struct fpga_driver_t { uint8_t regs_dirty; }; +struct fpga_loader_t { + /* Start address added as an offset to all read() calls. */ + uint32_t start_addr; + /* Any one-off setup needed before calling read(). May be NULL. */ + void (*setup)(void); + /* Read data from the specified address. */ + void (*read)(uint32_t addr, uint32_t len, uint8_t* const data); + /* Buffer to use for compressed data (4096 bytes). */ + uint8_t* in_buffer; + /* Buffer to use for decompressed data (4096 bytes). */ + uint8_t* out_buffer; +}; + /* Initialize the loaded bitstream's registers to their default values. */ extern void fpga_init(fpga_driver_t* const drv); @@ -75,7 +88,7 @@ void fpga_set_prbs_enable(fpga_driver_t* const drv, const bool enable); void fpga_set_tx_nco_enable(fpga_driver_t* const drv, const bool enable); void fpga_set_tx_nco_pstep(fpga_driver_t* const drv, const uint8_t phase_increment); -bool fpga_image_load(unsigned int index); +bool fpga_image_load(struct fpga_loader_t* loader, unsigned int index); bool fpga_spi_selftest(void); bool fpga_sgpio_selftest(void); bool fpga_if_xcvr_selftest(void); diff --git a/firmware/common/fpga_image.c b/firmware/common/fpga_image.c index a70906b8..8d0c1932 100644 --- a/firmware/common/fpga_image.c +++ b/firmware/common/fpga_image.c @@ -21,31 +21,28 @@ #include "hackrf_core.h" #include "lz4_blk.h" -#include "lz4_buf.h" #include "selftest.h" - -// FPGA bitstreams blob. -extern uint32_t _binary_fpga_bin_start; -extern uint32_t _binary_fpga_bin_end; -extern uint32_t _binary_fpga_bin_size; +#include "fpga.h" struct fpga_image_read_ctx { + struct fpga_loader_t* loader; uint32_t addr; size_t next_block_sz; uint8_t init_flag; }; -static size_t fpga_image_read_block_cb(void* _ctx, uint8_t* out_buffer) +static size_t fpga_image_read_block_cb(void* _ctx) { // Assume out_buffer is 4KB struct fpga_image_read_ctx* ctx = _ctx; + struct fpga_loader_t* loader = ctx->loader; size_t block_sz = ctx->next_block_sz; uint8_t block_sz_buf[2]; // first iteration: read first block size if (ctx->init_flag == 0) { - w25q80bv_read(&spi_flash, ctx->addr, 2, block_sz_buf); + loader->read(ctx->addr, 2, block_sz_buf); block_sz = block_sz_buf[0] | block_sz_buf[1] << 8; ctx->addr += 2; ctx->init_flag = 1; @@ -56,48 +53,42 @@ static size_t fpga_image_read_block_cb(void* _ctx, uint8_t* out_buffer) return 0; // Read compressed block (and the next block size) from flash. - w25q80bv_read(&spi_flash, ctx->addr, block_sz, lz4_in_buf); + loader->read(ctx->addr, block_sz, loader->in_buffer); ctx->addr += block_sz; - w25q80bv_read(&spi_flash, ctx->addr, 2, block_sz_buf); + loader->read(ctx->addr, 2, block_sz_buf); ctx->next_block_sz = block_sz_buf[0] | block_sz_buf[1] << 8; ctx->addr += 2; // Decompress block. - return lz4_blk_decompress(lz4_in_buf, out_buffer, block_sz); + return lz4_blk_decompress(loader->in_buffer, loader->out_buffer, block_sz); } -bool fpga_image_load(unsigned int index) +bool fpga_image_load(struct fpga_loader_t* loader, unsigned int index) { -#if defined(DFU_MODE) || defined(RAM_MODE) - selftest.fpga_image_load = SKIPPED; - selftest.report.pass = false; - return false; -#endif - // TODO: do SPI setup and read number of bitstreams once! - - // Prepare for SPI flash access. - spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv); - w25q80bv_setup(&spi_flash); + if (loader->setup != NULL) + loader->setup(); // Read number of bitstreams from flash. // Check the bitstream exists, and extract its offset. - uint32_t addr = (uint32_t) &_binary_fpga_bin_start; + uint32_t addr = loader->start_addr; uint32_t num_bitstreams, bitstream_offset; - w25q80bv_read(&spi_flash, addr, 4, (uint8_t*) &num_bitstreams); + loader->read(addr, 4, (uint8_t*) &num_bitstreams); if (index >= num_bitstreams) return false; - w25q80bv_read(&spi_flash, addr + 4 * (index + 1), 4, (uint8_t*) &bitstream_offset); + loader->read(addr + 4 * (index + 1), 4, (uint8_t*) &bitstream_offset); // A callback function is used by the FPGA programmer // to obtain consecutive gateware chunks. ice40_spi_target_init(&ice40); ssp1_set_mode_ice40(); struct fpga_image_read_ctx fpga_image_ctx = { - .addr = (uint32_t) &_binary_fpga_bin_start + bitstream_offset, + .loader = loader, + .addr = loader->start_addr + bitstream_offset, }; const bool success = ice40_spi_syscfg_program( &ice40, + loader->out_buffer, fpga_image_read_block_cb, &fpga_image_ctx); ssp1_set_mode_max283x(); diff --git a/firmware/common/ice40_spi.c b/firmware/common/ice40_spi.c index 344bb0e6..57abd73d 100644 --- a/firmware/common/ice40_spi.c +++ b/firmware/common/ice40_spi.c @@ -82,7 +82,8 @@ static uint32_t spi_ssp1_transfer_word(const uint32_t data) bool ice40_spi_syscfg_program( ice40_spi_driver_t* const drv, - size_t (*read_block_cb)(void* ctx, uint8_t* buffer), + uint8_t* buf, + size_t (*read_block_cb)(void* ctx), void* read_ctx) { // Drive CRESET_B = 0, SPI_SS = 0, SPI_SCK = 1. @@ -107,11 +108,11 @@ bool ice40_spi_syscfg_program( // first, on falling edge of SPI_SCK. gpio_clear(drv->gpio_select); for (;;) { - size_t read_sz = read_block_cb(read_ctx, lz4_out_buf); + size_t read_sz = read_block_cb(read_ctx); if (read_sz == 0) break; for (size_t j = 0; j < read_sz; j++) { - spi_ssp1_transfer_word(lz4_out_buf[j]); + spi_ssp1_transfer_word(buf[j]); } } diff --git a/firmware/common/ice40_spi.h b/firmware/common/ice40_spi.h index 93c5adc6..2a0cdac5 100644 --- a/firmware/common/ice40_spi.h +++ b/firmware/common/ice40_spi.h @@ -42,7 +42,8 @@ uint8_t ice40_spi_read(ice40_spi_driver_t* const drv, uint8_t r); void ice40_spi_write(ice40_spi_driver_t* const drv, uint8_t r, uint16_t v); bool ice40_spi_syscfg_program( ice40_spi_driver_t* const drv, - size_t (*read_block_cb)(void* ctx, uint8_t* buffer), + uint8_t* buf, + size_t (*read_block_cb)(void* ctx), void* read_ctx); #endif // __ICE40_SPI_H diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index d8934514..64457261 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -63,6 +63,7 @@ #include "fpga.h" #include "selftest.h" #include "delay.h" +#include "lz4_buf.h" extern uint32_t __m0_start__; extern uint32_t __m0_end__; @@ -256,6 +257,29 @@ static void m0_rom_to_ram(void) memcpy(dest, (uint32_t*) (base + src), len); } +#if defined(PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE)) +extern uint32_t _binary_fpga_bin_start; + +void fpga_loader_setup(void) +{ + spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv); + w25q80bv_setup(&spi_flash); +} + +void fpga_loader_read(uint32_t addr, uint32_t size, uint8_t* buf) +{ + w25q80bv_read(&spi_flash, addr, size, buf); +} + +struct fpga_loader_t fpga_loader = { + .start_addr = (uint32_t) &_binary_fpga_bin_start, + .setup = fpga_loader_setup, + .read = fpga_loader_read, + .in_buffer = lz4_in_buf, + .out_buffer = lz4_out_buf, +}; +#endif + int main(void) { // Copy M0 image from ROM before SPIFI is disabled @@ -315,7 +339,12 @@ int main(void) halt_and_flash(6000000); } #else - fpga_image_load(0); + #if defined(DFU_MODE) || defined(RAM_MODE) + selftest.fpga_image_load = SKIPPED; + selftest.report.pass = false; + #else + fpga_image_load(&fpga_loader, 0); + #endif delay_us_at_mhz(100, 204); fpga_spi_selftest(); fpga_sgpio_selftest(); diff --git a/firmware/hackrf_usb/usb_api_praline.c b/firmware/hackrf_usb/usb_api_praline.c index 9fcde4b1..c5b8fd30 100644 --- a/firmware/hackrf_usb/usb_api_praline.c +++ b/firmware/hackrf_usb/usb_api_praline.c @@ -24,6 +24,7 @@ #include "usb_api_praline.h" #include "usb_queue.h" #include +#include #include @@ -71,17 +72,23 @@ usb_request_status_t usb_vendor_request_set_narrowband_filter( return USB_REQUEST_STATUS_OK; } -bool fpga_image_load(unsigned int index); - usb_request_status_t usb_vendor_request_set_fpga_bitstream( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { +#if defined(DFU_MODE) || defined(RAM_MODE) + (void) endpoint; + (void) stage; + return USB_REQUEST_STATUS_STALL; +#else + extern struct fpga_loader_t fpga_loader; + if (stage == USB_TRANSFER_STAGE_SETUP) { - if (!fpga_image_load(endpoint->setup.value)) { + if (!fpga_image_load(&fpga_loader, endpoint->setup.value)) { return USB_REQUEST_STATUS_STALL; } usb_transfer_schedule_ack(endpoint->in); } return USB_REQUEST_STATUS_OK; -} \ No newline at end of file +#endif +}