From ba5fe7f7daa9ee487df8950134896a8df35a54cb Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 18 Oct 2025 23:08:12 +0100 Subject: [PATCH 01/11] Rename usb_bulk_buffer to usb_samp_buffer. --- firmware/common/LPC43xx_M4_memory.ld | 4 ++-- firmware/common/fpga_selftest.c | 20 +++++++++---------- firmware/hackrf_usb/usb_api_sweep.c | 4 ++-- firmware/hackrf_usb/usb_api_transceiver.c | 10 +++++----- .../{usb_bulk_buffer.h => usb_buffer.h} | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) rename firmware/hackrf_usb/{usb_bulk_buffer.h => usb_buffer.h} (85%) diff --git a/firmware/common/LPC43xx_M4_memory.ld b/firmware/common/LPC43xx_M4_memory.ld index 933fa854e..d37542f9e 100644 --- a/firmware/common/LPC43xx_M4_memory.ld +++ b/firmware/common/LPC43xx_M4_memory.ld @@ -35,11 +35,11 @@ MEMORY * to get performance benefit of having two USB buffers addressable * simultaneously (on two different buses of the AHB multilayer matrix) */ - ram_usb (rwx) : ORIGIN = 0x20008000, LENGTH = 32K + ram_samp (rwx) : ORIGIN = 0x20008000, LENGTH = 32K } -usb_bulk_buffer = ORIGIN(ram_usb); lz4_in_buf = ORIGIN(ram_lz4_in); lz4_out_buf = ORIGIN(ram_lz4_out); +usb_samp_buffer = ORIGIN(ram_samp); m0_state = ORIGIN(ram_shared); PROVIDE(__ram_m0_start__ = ORIGIN(ram_m0)); diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index 30e70ea27..b23be5255 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -37,8 +37,8 @@ #include "streaming.h" // USB buffer used during selftests. -#define USB_BULK_BUFFER_SIZE 0x8000 -extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE]; +#define USB_SAMP_BUFFER_SIZE 0x8000 +extern uint8_t usb_samp_buffer[USB_SAMP_BUFFER_SIZE]; static int rx_samples(const unsigned int num_samples, uint32_t max_cycles) { @@ -115,10 +115,10 @@ bool fpga_sgpio_selftest(void) fpga_set_prbs_enable(&fpga, false); // Generate sequence from first value and compare. - bool seq_in_sync = (usb_bulk_buffer[0] != 0); - uint8_t seq = lfsr_advance(usb_bulk_buffer[0]); + bool seq_in_sync = (usb_samp_buffer[0] != 0); + uint8_t seq = lfsr_advance(usb_samp_buffer[0]); for (int i = 1; i < 512; ++i) { - if (usb_bulk_buffer[i] != seq) { + if (usb_samp_buffer[i] != seq) { seq_in_sync = false; break; } @@ -189,7 +189,7 @@ bool fpga_if_xcvr_selftest(void) return false; } - const size_t num_samples = USB_BULK_BUFFER_SIZE / 2; + const size_t num_samples = USB_SAMP_BUFFER_SIZE / 2; // Set common RX path and gateware settings for the measurements. fpga_set_tx_nco_pstep(&fpga, 64); // NCO phase increment @@ -206,7 +206,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[0]); @@ -217,7 +217,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[1]); @@ -230,7 +230,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[2]); @@ -241,7 +241,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[3]); diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index ae3b399d1..eee742282 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -40,7 +40,7 @@ #include "usb_api_sweep.h" #include "usb_api_transceiver.h" -#include "usb_bulk_buffer.h" +#include "usb_buffer.h" #include "usb_endpoint.h" #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -207,7 +207,7 @@ void sweep_mode(uint32_t seq) m0_state.next_mode = M0_MODE_RX; // Write metadata to buffer. - buffer = &usb_bulk_buffer[phase * 0x4000]; + buffer = &usb_samp_buffer[phase * 0x4000]; *buffer = 0x7f; *(buffer + 1) = 0x7f; *(buffer + 2) = sweep_freq & 0xff; diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index b6c6782be..f272dac57 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -41,7 +41,7 @@ #include #include -#include "usb_bulk_buffer.h" +#include "usb_buffer.h" #include "usb_endpoint.h" #define USB_TRANSFER_SIZE 0x4000 @@ -442,7 +442,7 @@ void rx_mode(uint32_t seq) if ((m0_state.m0_count - usb_count) >= USB_TRANSFER_SIZE) { usb_transfer_schedule_block( &usb_endpoint_bulk_in, - &usb_bulk_buffer[usb_count & USB_BULK_BUFFER_MASK], + &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], USB_TRANSFER_SIZE, transceiver_bulk_transfer_complete, NULL); @@ -464,14 +464,14 @@ void tx_mode(uint32_t seq) // Set up OUT transfer of buffer 0. usb_transfer_schedule_block( &usb_endpoint_bulk_out, - &usb_bulk_buffer[0x0000], + &usb_samp_buffer[0x0000], USB_TRANSFER_SIZE, transceiver_bulk_transfer_complete, NULL); usb_count += USB_TRANSFER_SIZE; while (transceiver_request.seq == seq) { - if (!started && (m0_state.m4_count == USB_BULK_BUFFER_SIZE)) { + if (!started && (m0_state.m4_count == USB_SAMP_BUFFER_SIZE)) { // Buffer is now full, start streaming. baseband_streaming_enable(&sgpio_config); started = true; @@ -479,7 +479,7 @@ void tx_mode(uint32_t seq) if ((usb_count - m0_state.m0_count) <= USB_TRANSFER_SIZE) { usb_transfer_schedule_block( &usb_endpoint_bulk_out, - &usb_bulk_buffer[usb_count & USB_BULK_BUFFER_MASK], + &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], USB_TRANSFER_SIZE, transceiver_bulk_transfer_complete, NULL); diff --git a/firmware/hackrf_usb/usb_bulk_buffer.h b/firmware/hackrf_usb/usb_buffer.h similarity index 85% rename from firmware/hackrf_usb/usb_bulk_buffer.h rename to firmware/hackrf_usb/usb_buffer.h index e21a84ffb..5aecdb679 100644 --- a/firmware/hackrf_usb/usb_bulk_buffer.h +++ b/firmware/hackrf_usb/usb_buffer.h @@ -25,11 +25,11 @@ #include -#define USB_BULK_BUFFER_SIZE 0x8000 -#define USB_BULK_BUFFER_MASK 0x7FFF +#define USB_SAMP_BUFFER_SIZE 0x8000 +#define USB_SAMP_BUFFER_MASK 0x7FFF -/* Address of usb_bulk_buffer is set in ldscripts. If you change the name of this +/* Address of usb_samp_buffer is set in ldscripts. If you change the name of this * variable, it won't be where it needs to be in the processor's address space, * unless you also adjust the ldscripts. */ -extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE]; +extern uint8_t usb_samp_buffer[USB_SAMP_BUFFER_SIZE]; From d222e0188e3bf0ecdcde15f30cc6fb0251ba8978 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 22:39:10 +0100 Subject: [PATCH 02/11] Add new usb_bulk_buffer. --- firmware/common/LPC4320_M4_memory.ld | 3 ++- firmware/common/LPC4330_M4_memory.ld | 3 ++- firmware/common/LPC43xx_M4_memory.ld | 1 + firmware/hackrf_usb/usb_buffer.h | 10 +++++++--- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/firmware/common/LPC4320_M4_memory.ld b/firmware/common/LPC4320_M4_memory.ld index a5997f321..48be94da2 100644 --- a/firmware/common/LPC4320_M4_memory.ld +++ b/firmware/common/LPC4320_M4_memory.ld @@ -26,7 +26,8 @@ MEMORY { /* rom is really the shadow region that points to SPI flash or elsewhere */ rom (rx) : ORIGIN = 0x00000000, LENGTH = 1M - ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 96K + ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 64K + ram_usb (rw) : ORIGIN = 0x10010000, LENGTH = 32K ram_local2 (rwx) : ORIGIN = 0x10080000, LENGTH = 32K ram_sleep (rwx) : ORIGIN = 0x10088000, LENGTH = 8K } diff --git a/firmware/common/LPC4330_M4_memory.ld b/firmware/common/LPC4330_M4_memory.ld index 1e886d3a9..1d42ebee9 100644 --- a/firmware/common/LPC4330_M4_memory.ld +++ b/firmware/common/LPC4330_M4_memory.ld @@ -26,7 +26,8 @@ MEMORY { /* rom is really the shadow region that points to SPI flash or elsewhere */ rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K - ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 128K + ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 96K + ram_usb(rw) : ORIGIN = 0x10018000, LENGTH = 32K ram_local2 (rwx) : ORIGIN = 0x10080000, LENGTH = 64K ram_sleep (rwx) : ORIGIN = 0x10090000, LENGTH = 8K } diff --git a/firmware/common/LPC43xx_M4_memory.ld b/firmware/common/LPC43xx_M4_memory.ld index d37542f9e..fc7dcee7f 100644 --- a/firmware/common/LPC43xx_M4_memory.ld +++ b/firmware/common/LPC43xx_M4_memory.ld @@ -41,5 +41,6 @@ MEMORY lz4_in_buf = ORIGIN(ram_lz4_in); lz4_out_buf = ORIGIN(ram_lz4_out); usb_samp_buffer = ORIGIN(ram_samp); +usb_bulk_buffer = ORIGIN(ram_usb); m0_state = ORIGIN(ram_shared); PROVIDE(__ram_m0_start__ = ORIGIN(ram_m0)); diff --git a/firmware/hackrf_usb/usb_buffer.h b/firmware/hackrf_usb/usb_buffer.h index 5aecdb679..075430253 100644 --- a/firmware/hackrf_usb/usb_buffer.h +++ b/firmware/hackrf_usb/usb_buffer.h @@ -28,8 +28,12 @@ #define USB_SAMP_BUFFER_SIZE 0x8000 #define USB_SAMP_BUFFER_MASK 0x7FFF -/* Address of usb_samp_buffer is set in ldscripts. If you change the name of this - * variable, it won't be where it needs to be in the processor's address space, - * unless you also adjust the ldscripts. +#define USB_BULK_BUFFER_SIZE 0x8000 +#define USB_BULK_BUFFER_MASK 0x7FFF + +/* Addresses of usb_samp_buffer and usb_bulk_buffer are set in ldscripts. If + * you change the name of these variables, they won't be where they need to + * be in the processor's address space, unless you also adjust the ldscripts. */ extern uint8_t usb_samp_buffer[USB_SAMP_BUFFER_SIZE]; +extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE]; From 40a8dc53dc9f0f7f858c911eac47186f16127ce8 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 22:39:23 +0100 Subject: [PATCH 03/11] Use both buffers, with memcpy in place of DMA for now. --- firmware/hackrf_usb/usb_api_transceiver.c | 179 +++++++++++++++++----- 1 file changed, 142 insertions(+), 37 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index f272dac57..148741eb9 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -45,6 +46,11 @@ #include "usb_endpoint.h" #define USB_TRANSFER_SIZE 0x4000 +#define DMA_TRANSFER_SIZE 0x4000 + +#define BUF_HALF_MASK (USB_SAMP_BUFFER_SIZE >> 1) + +volatile uint32_t dma_started, dma_pending, usb_started, usb_completed; typedef struct { uint32_t freq_mhz; @@ -68,6 +74,8 @@ typedef struct { set_sample_r_params_t set_sample_r_params; +void dma_isr(void); + usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) @@ -340,6 +348,11 @@ void transceiver_shutdown(void) void transceiver_startup(const transceiver_mode_t mode) { + dma_started = 0; + dma_pending = 0; + usb_started = 0; + usb_completed = 0; + radio_switch_opmode(&radio, mode); switch (mode) { @@ -424,30 +437,115 @@ usb_request_status_t usb_vendor_request_set_rx_overrun_limit( return USB_REQUEST_STATUS_OK; } +void transceiver_start_dma(void* src, void* dest, size_t size) +{ + dma_pending = size; + memcpy(dest, src, size); + dma_isr(); +} + +void dma_isr(void) +{ + m0_state.m4_count += dma_pending; + dma_pending = 0; +} + void transceiver_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) { (void) user_data; - m0_state.m4_count += bytes_transferred; + usb_completed += bytes_transferred; } -void rx_mode(uint32_t seq) +typedef enum { + DIRECTION_RX, + DIRECTION_TX, +} direction_t; + +void start_dma_if_possible(direction_t direction, size_t size) +{ + if (dma_pending) { + return; + } + + uint32_t sampling_completed = m0_state.m0_count; + uint32_t dma_completed = m0_state.m4_count; + uint32_t samp_offset = dma_started & USB_SAMP_BUFFER_MASK; + uint32_t bulk_offset = dma_started & USB_BULK_BUFFER_MASK; + uint32_t data_available, space_in_use, space_available, samp_buf_margin; + uint8_t *dest, *src; + + if (direction == DIRECTION_RX) { + data_available = sampling_completed - dma_started; + space_in_use = usb_completed - dma_completed; + space_available = USB_BULK_BUFFER_SIZE - space_in_use; + samp_buf_margin = USB_SAMP_BUFFER_SIZE - data_available; + src = &usb_samp_buffer[samp_offset]; + dest = &usb_bulk_buffer[bulk_offset]; + } else { + data_available = usb_completed - dma_started; + space_in_use = dma_completed - sampling_completed; + space_available = USB_SAMP_BUFFER_SIZE - space_in_use; + samp_buf_margin = space_in_use; + src = &usb_bulk_buffer[bulk_offset]; + dest = &usb_samp_buffer[samp_offset]; + } + + if (data_available < size || size > space_available) { + return; + } + + uint32_t m0_buf_half = sampling_completed & BUF_HALF_MASK; + uint32_t dma_buf_half = dma_started & BUF_HALF_MASK; + bool same_buf_half = m0_buf_half == dma_buf_half; + + if (same_buf_half && samp_buf_margin >= (USB_SAMP_BUFFER_SIZE / 2)) { + return; + } + + transceiver_start_dma(src, dest, size); + + dma_started += size; +} + +void start_usb_if_possible(direction_t direction) { - uint32_t usb_count = 0; + uint32_t bulk_offset = usb_started & USB_BULK_BUFFER_MASK; + uint32_t dma_completed = m0_state.m4_count; + uint32_t bytes_available; + usb_endpoint_t* usb_endpoint; + + if (direction == DIRECTION_RX) { + bytes_available = dma_completed - usb_started; + usb_endpoint = &usb_endpoint_bulk_in; + } else { + uint32_t space_used = usb_started - dma_completed; + bytes_available = USB_BULK_BUFFER_SIZE - space_used; + usb_endpoint = &usb_endpoint_bulk_out; + } + if (bytes_available < USB_TRANSFER_SIZE) { + return; + } + + usb_transfer_schedule_block( + usb_endpoint, + &usb_bulk_buffer[bulk_offset], + USB_TRANSFER_SIZE, + transceiver_bulk_transfer_complete, + NULL); + + usb_started += USB_TRANSFER_SIZE; +} + +void rx_mode(uint32_t seq) +{ transceiver_startup(TRANSCEIVER_MODE_RX); baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { - if ((m0_state.m0_count - usb_count) >= USB_TRANSFER_SIZE) { - usb_transfer_schedule_block( - &usb_endpoint_bulk_in, - &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], - USB_TRANSFER_SIZE, - transceiver_bulk_transfer_complete, - NULL); - usb_count += USB_TRANSFER_SIZE; - } + start_dma_if_possible(DIRECTION_RX, DMA_TRANSFER_SIZE); + start_usb_if_possible(DIRECTION_RX); radio_update(&radio); } @@ -456,35 +554,42 @@ void rx_mode(uint32_t seq) void tx_mode(uint32_t seq) { - unsigned int usb_count = 0; - bool started = false; - transceiver_startup(TRANSCEIVER_MODE_TX); - // Set up OUT transfer of buffer 0. - usb_transfer_schedule_block( - &usb_endpoint_bulk_out, - &usb_samp_buffer[0x0000], - USB_TRANSFER_SIZE, - transceiver_bulk_transfer_complete, - NULL); - usb_count += USB_TRANSFER_SIZE; + // First, make transfers directly into the sample buffer to fill it. + for (int i = 0; i < (USB_SAMP_BUFFER_SIZE / USB_TRANSFER_SIZE); i++) { + // Set up transfer. + usb_transfer_schedule_block( + &usb_endpoint_bulk_out, + &usb_samp_buffer[usb_started], + USB_TRANSFER_SIZE, + transceiver_bulk_transfer_complete, + NULL); + usb_started += USB_TRANSFER_SIZE; - while (transceiver_request.seq == seq) { - if (!started && (m0_state.m4_count == USB_SAMP_BUFFER_SIZE)) { - // Buffer is now full, start streaming. - baseband_streaming_enable(&sgpio_config); - started = true; - } - if ((usb_count - m0_state.m0_count) <= USB_TRANSFER_SIZE) { - usb_transfer_schedule_block( - &usb_endpoint_bulk_out, - &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], - USB_TRANSFER_SIZE, - transceiver_bulk_transfer_complete, - NULL); - usb_count += USB_TRANSFER_SIZE; + // Wait for the transfer to complete. + while (usb_completed < usb_started) { + // Handle the host switching modes before filling the buffer. + if (transceiver_request.seq != seq) { + transceiver_shutdown(); + return; + } + + radio_update(&radio); } + } + + // Sample buffer is now full. Update DMA counters accordingly. + dma_started = USB_SAMP_BUFFER_SIZE; + m0_state.m4_count = USB_SAMP_BUFFER_SIZE; + + // Start transmitting samples. + baseband_streaming_enable(&sgpio_config); + + // Continue feeding samples to the sample buffer. + while (transceiver_request.seq == seq) { + start_dma_if_possible(DIRECTION_TX, DMA_TRANSFER_SIZE); + start_usb_if_possible(DIRECTION_TX); radio_update(&radio); } From 0fb7b1c8c0ecb7854fa2d8a5b45bda05b8a84040 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 19:40:28 +0100 Subject: [PATCH 04/11] Reduce memory transfer size to 8KB. --- firmware/hackrf_usb/usb_api_transceiver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 148741eb9..32efbc717 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -46,7 +46,7 @@ #include "usb_endpoint.h" #define USB_TRANSFER_SIZE 0x4000 -#define DMA_TRANSFER_SIZE 0x4000 +#define DMA_TRANSFER_SIZE 0x2000 #define BUF_HALF_MASK (USB_SAMP_BUFFER_SIZE >> 1) From cebf0c7ce1ef4d2c1699408fc7723d883f2886e1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 1 Dec 2025 23:24:34 +0000 Subject: [PATCH 05/11] Flush both buffers before leaving TX mode. --- firmware/common/usb.h | 2 + firmware/hackrf_usb/usb_api_transceiver.c | 58 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/firmware/common/usb.h b/firmware/common/usb.h index 9445728a3..2b8c6c8ce 100644 --- a/firmware/common/usb.h +++ b/firmware/common/usb.h @@ -31,6 +31,8 @@ #include "usb_type.h" +usb_queue_head_t* usb_queue_head(const uint_fast8_t endpoint_address); + void usb_peripheral_reset(void); void usb_phy_enable(void); diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 32efbc717..2112fe079 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -27,6 +27,8 @@ #include #include +#include + #include #include #include @@ -593,6 +595,62 @@ void tx_mode(uint32_t seq) radio_update(&radio); } + // Host has now requested to stop TX. We won't initiate any further USB + // transfers into the bulk buffer. However, we should make sure all + // data currently in the USB bulk buffer reaches the sample buffer. + + if ((usb_started - usb_completed) > 0) { + // We were part way through a 16KB firmware-side transfer when + // the transceiver mode change request to stop TX was received. + // + // We want to include the contents of that partial transfer in + // the data we move to the sample buffer. + // + // The transfer was already stopped by usb_endpoint_flush(), + // which was called from request_transceiver_mode(). + // + // We will not have had a callback, and the transfer descriptor + // (dTD) will not have been updated, since the transfer did not + // complete. + // + // However, as long as we haven't started a new transfer, we + // can retrieve the partial byte count from the transfer + // overlay in the endpoint queue head (dQH) (UM10503 25.9.1). + + usb_queue_head_t* const qh = + usb_queue_head(usb_endpoint_bulk_out.address); + unsigned int bytes_remaining = + (qh->total_bytes & USB_TD_DTD_TOKEN_TOTAL_BYTES_MASK) >> + USB_TD_DTD_TOKEN_TOTAL_BYTES_SHIFT; + unsigned int bytes_transferred = USB_TRANSFER_SIZE - bytes_remaining; + usb_completed += bytes_transferred; + } + + // Feed the remaining data from the bulk buffer to the sample buffer. + // At this point, we also need to handle the case where there is less data + // to be transferred to the sample buffer than a full-sized DMA transfer. + + // Any remainder of less than 4 bytes will be ignored; this is the chunk + // size of our DMA transfers. + while ((usb_completed - m0_state.m4_count) >= 4) { + uint32_t data_available = usb_completed - dma_started; + if (data_available > DMA_TRANSFER_SIZE) { + start_dma_if_possible(DIRECTION_TX, DMA_TRANSFER_SIZE); + } else { + start_dma_if_possible(DIRECTION_TX, data_available); + } + radio_update(&radio); + } + + // Wait for the data in the sample buffer to be transmitted. + + // Any remainder of less than 32 bytes will be ignored; this is + // the chunk size used by the M0 core to transfer samples to SGPIO. + while ((m0_state.m4_count - m0_state.m0_count) >= 32) { + radio_update(&radio); + } + + // All data received from the host has now been transmitted. transceiver_shutdown(); } From ebe5a5acdffbe8e2ec8a3a273bf4b7e87317ee25 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 8 Dec 2025 16:05:09 +0000 Subject: [PATCH 06/11] Read USB API version in hackrf_open() and cache it. --- host/libhackrf/src/hackrf.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 48a25868a..14c34ab2c 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -143,6 +143,7 @@ typedef enum { struct hackrf_device { libusb_device_handle* usb_device; + uint16_t usb_api_version; struct libusb_transfer** transfers; hackrf_sample_block_cb_fn callback; volatile bool @@ -187,10 +188,8 @@ static const max2837_ft_t max2837_ft[] = { {28000000}, {0}}; -#define USB_API_REQUIRED(device, version) \ - uint16_t usb_version = 0; \ - hackrf_usb_api_version_read(device, &usb_version); \ - if (usb_version < version) \ +#define USB_API_REQUIRED(device, version) \ + if (device->usb_api_version < version) \ return HACKRF_ERROR_USB_API_VERSION; static const uint16_t hackrf_usb_vid = 0x1d50; @@ -713,6 +712,13 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d { int result; hackrf_device* lib_device; + struct libusb_device_descriptor device_descriptor; + libusb_device* dev = libusb_get_device(usb_device); + result = libusb_get_device_descriptor(dev, &device_descriptor); + if (result < 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } //int speed = libusb_get_device_speed(usb_device); // TODO: Error or warning if not high speed USB? @@ -746,6 +752,7 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d #endif lib_device->usb_device = usb_device; + lib_device->usb_api_version = device_descriptor.bcdDevice; lib_device->transfers = NULL; lib_device->callback = NULL; lib_device->transfer_thread_started = false; @@ -1700,17 +1707,7 @@ extern ADDAPI int ADDCALL hackrf_usb_api_version_read( hackrf_device* device, uint16_t* version) { - int result; - libusb_device* dev; - struct libusb_device_descriptor desc; - dev = libusb_get_device(device->usb_device); - result = libusb_get_device_descriptor(dev, &desc); - if (result < 0) { - last_libusb_error = result; - return HACKRF_ERROR_LIBUSB; - } - - *version = desc.bcdDevice; + *version = device->usb_api_version; return HACKRF_SUCCESS; } From 3517d0adfc3bdb34f806a997a862d2d75e3508ff Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 9 Dec 2025 16:32:14 +0000 Subject: [PATCH 07/11] NAK the request that takes us out of TX mode until buffer flushed. --- firmware/hackrf_usb/usb_api_transceiver.c | 14 ++++++++++++++ firmware/hackrf_usb/usb_api_transceiver.h | 2 ++ firmware/hackrf_usb/usb_endpoint.c | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 2112fe079..cad75cd38 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -324,6 +324,17 @@ volatile transceiver_request_t transceiver_request = { .seq = 0, }; +void transceiver_usb_setup_complete(usb_endpoint_t* const endpoint) +{ + if (transceiver_request.mode == TRANSCEIVER_MODE_TX && + endpoint->setup.request == 1) { + // This is a request to leave TX mode. Do so but NAK for now. + request_transceiver_mode(endpoint->setup.value); + } else { + usb_setup_complete(endpoint); + } +} + // Must be called from an atomic context (normally USB ISR) void request_transceiver_mode(transceiver_mode_t mode) { @@ -651,6 +662,9 @@ void tx_mode(uint32_t seq) } // All data received from the host has now been transmitted. + // Now we can ACK the control request that took us out of TX mode. + usb_transfer_schedule_ack(usb_endpoint_control_in.in); + transceiver_shutdown(); } diff --git a/firmware/hackrf_usb/usb_api_transceiver.h b/firmware/hackrf_usb/usb_api_transceiver.h index df981c1ea..383be18c1 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.h +++ b/firmware/hackrf_usb/usb_api_transceiver.h @@ -36,6 +36,8 @@ typedef struct { extern volatile transceiver_request_t transceiver_request; +void transceiver_usb_setup_complete(usb_endpoint_t* const endpoint); + usb_request_status_t usb_vendor_request_set_transceiver_mode( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/usb_endpoint.c b/firmware/hackrf_usb/usb_endpoint.c index a8a35f0d9..81251f1ec 100644 --- a/firmware/hackrf_usb/usb_endpoint.c +++ b/firmware/hackrf_usb/usb_endpoint.c @@ -28,13 +28,14 @@ #include #include "usb_device.h" +#include "usb_api_transceiver.h" usb_endpoint_t usb_endpoint_control_out = { .address = 0x00, .device = &usb_device, .in = &usb_endpoint_control_in, .out = &usb_endpoint_control_out, - .setup_complete = usb_setup_complete, + .setup_complete = transceiver_usb_setup_complete, .transfer_complete = usb_control_out_complete, }; USB_DEFINE_QUEUE(usb_endpoint_control_out, 4); From 9584416b49ecd5e3b792288d2ffd5a64f7cfc60f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 18 Dec 2025 07:21:58 +0000 Subject: [PATCH 08/11] Add vendor request to retrieve buffer size. --- firmware/hackrf_usb/hackrf_usb.c | 1 + firmware/hackrf_usb/usb_api_transceiver.c | 22 ++++++++++++++++++++++ firmware/hackrf_usb/usb_api_transceiver.h | 3 +++ firmware/hackrf_usb/usb_descriptor.c | 2 +- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 54431ff3c..cabf0d114 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -183,6 +183,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_test_rtc_osc, usb_vendor_request_write_radio_reg, usb_vendor_request_read_radio_reg, + usb_vendor_request_get_buffer_size, }; static const uint32_t vendor_request_handler_count = diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index cad75cd38..1aca654e2 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -450,6 +450,28 @@ usb_request_status_t usb_vendor_request_set_rx_overrun_limit( return USB_REQUEST_STATUS_OK; } +usb_request_status_t usb_vendor_request_get_buffer_size( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + uint32_t value = USB_SAMP_BUFFER_SIZE + USB_BULK_BUFFER_SIZE; + endpoint->buffer[0] = value & 0xff; + endpoint->buffer[1] = (value & 0xff00) >> 8; + endpoint->buffer[2] = (value & 0xff0000) >> 16; + endpoint->buffer[3] = (value & 0xff000000) >> 24; + usb_transfer_schedule_block( + endpoint->in, + &endpoint->buffer, + 4, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + return USB_REQUEST_STATUS_OK; + } + return USB_REQUEST_STATUS_OK; +} + void transceiver_start_dma(void* src, void* dest, size_t size) { dma_pending = size; diff --git a/firmware/hackrf_usb/usb_api_transceiver.h b/firmware/hackrf_usb/usb_api_transceiver.h index 383be18c1..a29dec118 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.h +++ b/firmware/hackrf_usb/usb_api_transceiver.h @@ -77,6 +77,9 @@ usb_request_status_t usb_vendor_request_set_tx_underrun_limit( usb_request_status_t usb_vendor_request_set_rx_overrun_limit( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); +usb_request_status_t usb_vendor_request_get_buffer_size( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); void request_transceiver_mode(transceiver_mode_t mode); void transceiver_startup(transceiver_mode_t mode); diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 36531ebcb..067317b31 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -30,7 +30,7 @@ #define USB_VENDOR_ID (0x1D50) -#define USB_API_VERSION (0x0111) +#define USB_API_VERSION (0x0112) #define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF) From 90625818adc367759a94e90a3007beecd91e64aa Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 18 Dec 2025 07:36:58 +0000 Subject: [PATCH 09/11] For API >= 1.12, get buffer size when opening device. --- host/libhackrf/src/hackrf.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 14c34ab2c..da3c0f152 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -115,6 +115,7 @@ typedef enum { HACKRF_VENDOR_REQUEST_TEST_RTC_OSC = 58, HACKRF_VENDOR_REQUEST_RADIO_WRITE_REG = 59, HACKRF_VENDOR_REQUEST_RADIO_READ_REG = 60, + HACKRF_VENDOR_REQUEST_GET_BUFFER_SIZE = 61, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -138,7 +139,6 @@ typedef enum { #define TRANSFER_COUNT 4 #define TRANSFER_BUFFER_SIZE 262144 -#define DEVICE_BUFFER_SIZE 32768 #define USB_MAX_SERIAL_LENGTH 32 struct hackrf_device { @@ -163,6 +163,7 @@ struct hackrf_device { hackrf_flush_cb_fn flush_callback; hackrf_tx_block_complete_cb_fn tx_completion_callback; void* flush_ctx; + uint32_t buffer_size; }; typedef struct { @@ -712,6 +713,7 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d { int result; hackrf_device* lib_device; + uint32_t buffer_size; struct libusb_device_descriptor device_descriptor; libusb_device* dev = libusb_get_device(usb_device); result = libusb_get_device_descriptor(dev, &device_descriptor); @@ -765,6 +767,30 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d lib_device->flush_ctx = NULL; lib_device->tx_completion_callback = NULL; + if (lib_device->usb_api_version >= 0x0112) { + // Fetch buffer size from device so we know how many bytes to flush TX with. + result = libusb_control_transfer( + lib_device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_GET_BUFFER_SIZE, + 0, + 0, + (unsigned char*) &buffer_size, + 4, + 0); + + if (result < 4) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } + + lib_device->buffer_size = FROM_LE32(buffer_size); + } else { + // All older firmware uses a fixed 32KB buffer size. + lib_device->buffer_size = 32768; + } + result = pthread_mutex_init(&lib_device->transfer_lock, NULL); if (result != 0) { free(lib_device); @@ -2371,8 +2397,8 @@ ADDAPI int ADDCALL hackrf_enable_tx_flush( device->flush_transfer, device->usb_device, TX_ENDPOINT_ADDRESS, - calloc(1, DEVICE_BUFFER_SIZE), - DEVICE_BUFFER_SIZE, + calloc(1, device->buffer_size), + device->buffer_size, hackrf_libusb_flush_callback, device, 0); From 5afdd6dfef9f7d83885e09750d3a46acab0e9e8a Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 18 Dec 2025 07:49:31 +0000 Subject: [PATCH 10/11] If the host knows our buffer size, don't auto-flush. --- firmware/hackrf_usb/usb_api_transceiver.c | 26 +++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 1aca654e2..ca9445d19 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -52,6 +52,11 @@ #define BUF_HALF_MASK (USB_SAMP_BUFFER_SIZE >> 1) +// Unless we know the host knows our buffer size, we'll avoid leaving TX +// until we've transmitted all bytes sent by the host. This flag is cleared +// when the host requests our buffer size. +bool auto_tx_flush = true; + volatile uint32_t dma_started, dma_pending, usb_started, usb_completed; typedef struct { @@ -327,7 +332,7 @@ volatile transceiver_request_t transceiver_request = { void transceiver_usb_setup_complete(usb_endpoint_t* const endpoint) { if (transceiver_request.mode == TRANSCEIVER_MODE_TX && - endpoint->setup.request == 1) { + endpoint->setup.request == 1 && auto_tx_flush) { // This is a request to leave TX mode. Do so but NAK for now. request_transceiver_mode(endpoint->setup.value); } else { @@ -467,6 +472,11 @@ usb_request_status_t usb_vendor_request_get_buffer_size( NULL, NULL); usb_transfer_schedule_ack(endpoint->out); + + // We now know the host is aware of our buffer size, so it + // can make its own decisions about flushing the buffer. + auto_tx_flush = false; + return USB_REQUEST_STATUS_OK; } return USB_REQUEST_STATUS_OK; @@ -628,9 +638,17 @@ void tx_mode(uint32_t seq) radio_update(&radio); } - // Host has now requested to stop TX. We won't initiate any further USB - // transfers into the bulk buffer. However, we should make sure all - // data currently in the USB bulk buffer reaches the sample buffer. + // Host has now requested to stop TX. If we're not auto-flushing, we + // should now stop TX immediately. + + if (!auto_tx_flush) { + transceiver_shutdown(); + return; + } + + // Otherwise, we should now ensure all bytes sent by the host are + // transmitted before we leave TX. First, we should make sure all data + // currently in the USB bulk buffer reaches the sample buffer. if ((usb_started - usb_completed) > 0) { // We were part way through a 16KB firmware-side transfer when From 03119000a4a8543f9f39f5727cad42ce32df6a61 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 16:41:34 +0100 Subject: [PATCH 11/11] Use DMA for transfers. --- firmware/hackrf_usb/usb_api_transceiver.c | 60 +++++++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index ca9445d19..fe2b54057 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -25,13 +25,15 @@ #include #include -#include +#include +#include #include #include #include #include +#include #include #include #include @@ -81,7 +83,7 @@ typedef struct { set_sample_r_params_t set_sample_r_params; -void dma_isr(void); +void transceiver_dma_setup(void); usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth( usb_endpoint_t* const endpoint, @@ -371,6 +373,8 @@ void transceiver_startup(const transceiver_mode_t mode) usb_started = 0; usb_completed = 0; + transceiver_dma_setup(); + radio_switch_opmode(&radio, mode); switch (mode) { @@ -482,15 +486,63 @@ usb_request_status_t usb_vendor_request_get_buffer_size( return USB_REQUEST_STATUS_OK; } +/* clang-format off */ + +// Which GPDMA channel to use. +const uint32_t DMA_CHANNEL = 1; + +// GPDMA CCONFIG register setting. +const uint32_t DMA_CONFIG = + GPDMA_CCONFIG_FLOWCNTRL(0) // memory-to-memory + | GPDMA_CCONFIG_IE(0) // no error interrupt + | GPDMA_CCONFIG_ITC(1) // terminal count interrupt + | GPDMA_CCONFIG_L(0) // do not lock + | GPDMA_CCONFIG_H(0); // do not halt + +// GPDMA CCONTROL register setting (excluding TRANSFERSIZE field). +const uint32_t DMA_CONTROL = + GPDMA_CCONTROL_SBSIZE(7) // 256-transfer src bursts + | GPDMA_CCONTROL_DBSIZE(7) // 256-transfer dst bursts + | GPDMA_CCONTROL_SWIDTH(2) // 32-bit src transfers + | GPDMA_CCONTROL_DWIDTH(2) // 32-bit dst transfers + | GPDMA_CCONTROL_S(0) // AHB Master 0 + | GPDMA_CCONTROL_D(1) // AHB Master 1 + | GPDMA_CCONTROL_SI(1) // increment source + | GPDMA_CCONTROL_DI(1) // increment destination + | GPDMA_CCONTROL_PROT1(0) // user mode + | GPDMA_CCONTROL_PROT2(0) // not bufferable + | GPDMA_CCONTROL_PROT3(0) // not cacheable + | GPDMA_CCONTROL_I(1); // interrupt enabled + +/* clang-format on */ + +// Called before any sequence of DMA transfers. +void transceiver_dma_setup(void) +{ + gpdma_controller_enable(); + GPDMA_CCONFIG(DMA_CHANNEL) = DMA_CONFIG; + GPDMA_CCONTROL(DMA_CHANNEL) = DMA_CONTROL; + GPDMA_CLLI(DMA_CHANNEL) = 0; + GPDMA_INTTCCLEAR = (1 << DMA_CHANNEL); + nvic_enable_irq(NVIC_DMA_IRQ); +} + +// Called to start each DMA transfer. void transceiver_start_dma(void* src, void* dest, size_t size) { + uint32_t num_transfers = size >> 2; + GPDMA_CCONTROL(DMA_CHANNEL) = DMA_CONTROL | num_transfers; + GPDMA_CSRCADDR(DMA_CHANNEL) = (uint32_t) src; + GPDMA_CDESTADDR(DMA_CHANNEL) = (uint32_t) dest; dma_pending = size; - memcpy(dest, src, size); - dma_isr(); + gpdma_channel_enable(DMA_CHANNEL); } +// Called when a DMA transfer completes. void dma_isr(void) { + gpdma_channel_disable(DMA_CHANNEL); + GPDMA_INTTCCLEAR = (1 << DMA_CHANNEL); m0_state.m4_count += dma_pending; dma_pending = 0; }