From fd073e391f5682eb57c37232e08b25c89c60bf2e Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 20 Dec 2021 21:31:45 +0000 Subject: [PATCH 01/34] Add USB vendor request to read M0 state, and host support for doing so. This adds a `hackrf_debug [-S|--state]` option, and the necessary plumbing to libhackrf and the M4 firmware to support it. The USB API and libhackrf versions are bumped to reflect the changes. --- firmware/hackrf_usb/CMakeLists.txt | 1 + firmware/hackrf_usb/hackrf_usb.c | 2 ++ firmware/hackrf_usb/m0_state.c | 44 ++++++++++++++++++++++++++++ firmware/hackrf_usb/m0_state.h | 6 ++++ firmware/hackrf_usb/usb_descriptor.c | 2 +- host/hackrf-tools/src/hackrf_debug.c | 30 +++++++++++++++++-- host/libhackrf/CMakeLists.txt | 2 +- host/libhackrf/src/hackrf.c | 26 ++++++++++++++++ host/libhackrf/src/hackrf.h | 10 +++++++ 9 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 firmware/hackrf_usb/m0_state.c diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index ace67e50..04993725 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -35,6 +35,7 @@ set(SRC_M4 hackrf_usb.c "${PATH_HACKRF_FIRMWARE_COMMON}/tuning.c" "${PATH_HACKRF_FIRMWARE_COMMON}/streaming.c" + m0_state.c "${PATH_HACKRF_FIRMWARE_COMMON}/usb.c" "${PATH_HACKRF_FIRMWARE_COMMON}/usb_request.c" "${PATH_HACKRF_FIRMWARE_COMMON}/usb_standard_request.c" diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 7fa95c74..f34318f5 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -49,6 +49,7 @@ #include "usb_api_transceiver.h" #include "usb_api_ui.h" #include "usb_bulk_buffer.h" +#include "m0_state.h" #include "cpld_xc2c.h" #include "portapack.h" @@ -114,6 +115,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_operacake_set_mode, usb_vendor_request_operacake_get_mode, usb_vendor_request_operacake_set_dwell_times, + usb_vendor_request_get_m0_state, }; static const uint32_t vendor_request_handler_count = diff --git a/firmware/hackrf_usb/m0_state.c b/firmware/hackrf_usb/m0_state.c new file mode 100644 index 00000000..ec113a87 --- /dev/null +++ b/firmware/hackrf_usb/m0_state.c @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "m0_state.h" + +#include +#include +#include + +usb_request_status_t usb_vendor_request_get_m0_state( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage +) { + if( stage == USB_TRANSFER_STAGE_SETUP ) + { + usb_transfer_schedule_block( + endpoint->in, + (void*) &m0_state, + sizeof(m0_state), + NULL, NULL); + usb_transfer_schedule_ack(endpoint->out); + return USB_REQUEST_STATUS_OK; + } else { + return USB_REQUEST_STATUS_OK; + } +} diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index 102a3ada..30950560 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -22,6 +22,9 @@ #ifndef __M0_STATE_H__ #define __M0_STATE_H__ +#include +#include + struct m0_state { uint32_t offset; uint32_t tx; @@ -33,4 +36,7 @@ struct m0_state { */ extern volatile struct m0_state m0_state; +usb_request_status_t usb_vendor_request_get_m0_state( + usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); + #endif/*__M0_STATE_H__*/ diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 1d11c275..aa40db3f 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -36,7 +36,7 @@ #define USB_PRODUCT_ID (0xFFFF) #endif -#define USB_API_VERSION (0x0105) +#define USB_API_VERSION (0x0106) #define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF) diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 64623b6f..80863a88 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -377,6 +377,12 @@ int write_register(hackrf_device* device, uint8_t part, return HACKRF_ERROR_INVALID_PARAM; } +static void print_state(hackrf_m0_state *state) { + printf("M0 state:\n"); + printf("Offset: %u bytes\n", state->offset); + printf("TX: %u\n", state->tx); +} + static void usage() { printf("\nUsage:\n"); printf("\t-h, --help: this help\n"); @@ -388,12 +394,14 @@ static void usage() { printf("\t-m, --max2837: target MAX2837\n"); printf("\t-s, --si5351c: target SI5351C\n"); printf("\t-f, --rffc5072: target RFFC5072\n"); + printf("\t-S, --state: display M0 state\n"); printf("\t-u, --ui <1/0>: enable/disable UI\n"); printf("\nExamples:\n"); printf("\thackrf_debug --si5351c -n 0 -r # reads from si5351c register 0\n"); printf("\thackrf_debug --si5351c -c # displays si5351c multisynth configuration\n"); printf("\thackrf_debug --rffc5072 -r # reads all rffc5072 registers\n"); printf("\thackrf_debug --max2837 -n 10 -w 22 # writes max2837 register 10 with 22 decimal\n"); + printf("\thackrf_debug --state # displays M0 state\n"); } static struct option long_options[] = { @@ -406,6 +414,7 @@ static struct option long_options[] = { { "max2837", no_argument, 0, 'm' }, { "si5351c", no_argument, 0, 's' }, { "rffc5072", no_argument, 0, 'f' }, + { "state", no_argument, 0, 'S' }, { "ui", required_argument, 0, 'u' }, { 0, 0, 0, 0 }, }; @@ -419,6 +428,7 @@ int main(int argc, char** argv) { bool read = false; bool write = false; bool dump_config = false; + bool dump_state = false; uint8_t part = PART_NONE; const char* serial_number = NULL; bool set_ui = false; @@ -430,7 +440,7 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - while( (opt = getopt_long(argc, argv, "n:rw:d:cmsfh?u:", long_options, &option_index)) != EOF ) { + while( (opt = getopt_long(argc, argv, "n:rw:d:cmsfSh?u:", long_options, &option_index)) != EOF ) { switch( opt ) { case 'n': result = parse_int(optarg, ®ister_number); @@ -449,6 +459,10 @@ int main(int argc, char** argv) { dump_config = true; break; + case 'S': + dump_state = true; + break; + case 'd': serial_number = optarg; break; @@ -517,13 +531,13 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - if(!(write || read || dump_config || set_ui)) { + if(!(write || read || dump_config || dump_state || set_ui)) { fprintf(stderr, "Specify read, write, or config option.\n"); usage(); return EXIT_FAILURE; } - if(part == PART_NONE && !set_ui) { + if(part == PART_NONE && !set_ui && !dump_state) { fprintf(stderr, "Specify a part to read, write, or print config from.\n"); usage(); return EXIT_FAILURE; @@ -551,6 +565,16 @@ int main(int argc, char** argv) { si5351c_read_configuration(device); } + if(dump_state) { + hackrf_m0_state state; + result = hackrf_get_m0_state(device, &state); + if(result != HACKRF_SUCCESS) { + printf("hackrf_get_m0_state() failed: %s (%d)\n", hackrf_error_name(result), result); + return EXIT_FAILURE; + } + print_state(&state); + } + if(set_ui) { result = hackrf_set_ui_enable(device, ui_enable); } diff --git a/host/libhackrf/CMakeLists.txt b/host/libhackrf/CMakeLists.txt index a0642143..ea360184 100644 --- a/host/libhackrf/CMakeLists.txt +++ b/host/libhackrf/CMakeLists.txt @@ -24,7 +24,7 @@ cmake_minimum_required(VERSION 2.8) project(libhackrf C) set(MAJOR_VERSION 0) -set(MINOR_VERSION 6) +set(MINOR_VERSION 7) set(PACKAGE libhackrf) set(VERSION_STRING ${MAJOR_VERSION}.${MINOR_VERSION}) set(VERSION ${VERSION_STRING}) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 70017a05..07ac2c70 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -92,6 +92,7 @@ typedef enum { HACKRF_VENDOR_REQUEST_OPERACAKE_SET_MODE = 38, HACKRF_VENDOR_REQUEST_OPERACAKE_GET_MODE = 39, HACKRF_VENDOR_REQUEST_OPERACAKE_SET_DWELL_TIMES = 40, + HACKRF_VENDOR_REQUEST_GET_M0_STATE = 41, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -1007,6 +1008,31 @@ int ADDCALL hackrf_rffc5071_write(hackrf_device* device, uint8_t register_number } } +int ADDCALL hackrf_get_m0_state(hackrf_device* device, hackrf_m0_state* state) +{ + USB_API_REQUIRED(device, 0x0106) + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_GET_M0_STATE, + 0, + 0, + (unsigned char*)state, + sizeof(hackrf_m0_state), + 0 + ); + + if( result < sizeof(hackrf_m0_state) ) + { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + int ADDCALL hackrf_spiflash_erase(hackrf_device* device) { int result; diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 4fb56363..63795f0e 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -155,6 +155,14 @@ typedef struct { uint8_t port; } hackrf_operacake_freq_range; +/** State of the SGPIO loop running on the M0 core. */ +typedef struct { + /** Current offset in the buffer. */ + uint32_t offset; + /** TX flag. */ + uint32_t tx; +} hackrf_m0_state; + struct hackrf_device_list { char **serial_numbers; enum hackrf_usb_board_id *usb_board_ids; @@ -193,6 +201,8 @@ extern ADDAPI int ADDCALL hackrf_stop_rx(hackrf_device* device); extern ADDAPI int ADDCALL hackrf_start_tx(hackrf_device* device, hackrf_sample_block_cb_fn callback, void* tx_ctx); extern ADDAPI int ADDCALL hackrf_stop_tx(hackrf_device* device); +extern ADDAPI int ADDCALL hackrf_get_m0_state(hackrf_device* device, hackrf_m0_state* value); + /* return HACKRF_TRUE if success */ extern ADDAPI int ADDCALL hackrf_is_streaming(hackrf_device* device); From be060ab7539906e6d3a6de223a89da39cad0f9e1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 22 Dec 2021 01:58:10 +0000 Subject: [PATCH 02/34] Add defines for USB bulk buffer size & mask. --- firmware/hackrf_usb/usb_bulk_buffer.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/usb_bulk_buffer.h b/firmware/hackrf_usb/usb_bulk_buffer.h index 6e120714..25e710ee 100644 --- a/firmware/hackrf_usb/usb_bulk_buffer.h +++ b/firmware/hackrf_usb/usb_bulk_buffer.h @@ -26,10 +26,13 @@ #include #include +#define USB_BULK_BUFFER_SIZE 0x8000 +#define USB_BULK_BUFFER_MASK 0x7FFF + /* Address of usb_bulk_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[32768]; +extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE]; #endif/*__USB_BULK_BUFFER_H__*/ From 21dabc920f0841e129f1b6c8b09c9a2f1322504e Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 22 Dec 2021 02:29:41 +0000 Subject: [PATCH 03/34] Replace M0 state offset field with a byte count. Instead of this count wrapping at the buffer size, it now increments continuously. The offset within the buffer is now obtained from the lower bits of the count. This makes it possible to keep track of the total number of bytes transferred by the M0 core. The count will wrap at 2^32 bytes, which at 20Msps will occur every 107 seconds. --- firmware/hackrf_usb/m0_state.h | 2 +- firmware/hackrf_usb/sgpio_m0.s | 26 +++++++++++------------ firmware/hackrf_usb/usb_api_sweep.c | 5 +++-- firmware/hackrf_usb/usb_api_transceiver.c | 12 ++++++----- host/hackrf-tools/src/hackrf_debug.c | 2 +- host/libhackrf/src/hackrf.h | 4 ++-- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index 30950560..f3b4f90c 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -26,7 +26,7 @@ #include struct m0_state { - uint32_t offset; + uint32_t m0_count; uint32_t tx; }; diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 0d1ab215..d521c6de 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -66,8 +66,8 @@ shadow registers. There are two key code paths, with the following worst-case timings: -RX: 140 cycles -TX: 125 cycles +RX: 141 cycles +TX: 126 cycles Design ====== @@ -105,7 +105,7 @@ registers and fixed memory addresses. .equ STATE_BASE, 0x20007000 // Offsets into the state structure. -.equ OFFSET, 0x00 +.equ M0_COUNT, 0x00 .equ TX, 0x04 // Our slice chain is set up as follows (ascending data age; arrows are reversed for flow): @@ -128,7 +128,8 @@ buf_base .req r12 buf_mask .req r11 sgpio_data .req r7 sgpio_int .req r6 -buf_ptr .req r5 +count .req r5 +buf_ptr .req r4 // Entry point. At this point, the libopencm3 startup code has set things up as // normal; .data and .bss are initialised, the stack is set up, etc. However, @@ -151,7 +152,7 @@ main: // Initialise state. zero .req r0 mov zero, #0 // zero = 0 // 1 - str zero, [state, #OFFSET] // state.offset = zero // 2 + str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #TX] // state.tx = zero // 2 loop: @@ -186,7 +187,9 @@ loop: str int_status, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = int_status // 8 // ... and grab the address of the buffer segment we want to write to / read from. - ldr buf_ptr, [state, #OFFSET] // buf_ptr = state.offset // 2 + ldr count, [state, #M0_COUNT] // count = state.m0_count // 2 + mov buf_ptr, buf_mask // buf_ptr = buf_mask // 1 + and buf_ptr, count // buf_ptr &= count // 1 add buf_ptr, buf_base // buf_ptr += buf_base // 1 tx .req r0 @@ -229,14 +232,11 @@ direction_rx: stm buf_ptr!, {r0-r3} // buf_ptr[0:16] = r0-r3; buf_ptr += 16 // 5 done: - offset .req r0 + // Finally, update the count... + add count, #32 // count += 32 // 1 - // Finally, update the buffer location... - mov offset, buf_mask // offset = buf_mask // 1 - and offset, buf_ptr // offset &= buf_ptr // 1 - - // ... and store the new position. - str offset, [state, #OFFSET] // state.offset = offset // 2 + // ... and store the new count. + str count, [state, #M0_COUNT] // state.m0_count = count // 2 b loop // goto loop // 3 diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index f0ccad77..5fef5224 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -101,8 +101,9 @@ void sweep_mode(uint32_t seq) { baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { + uint32_t m0_offset = m0_state.m0_count & USB_BULK_BUFFER_MASK; // Set up IN transfer of buffer 0. - if ( m0_state.offset >= 16384 && phase == 1) { + if ( m0_offset >= 16384 && phase == 1) { transfer = true; buffer = &usb_bulk_buffer[0x0000]; phase = 0; @@ -110,7 +111,7 @@ void sweep_mode(uint32_t seq) { } // Set up IN transfer of buffer 1. - if ( m0_state.offset < 16384 && phase == 0) { + if ( m0_offset < 16384 && phase == 0) { transfer = true; buffer = &usb_bulk_buffer[0x4000]; phase = 1; diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index f795808a..4ee9b7ce 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -296,7 +296,7 @@ void transceiver_startup(const transceiver_mode_t mode) { activate_best_clock_source(); hw_sync_enable(_hw_sync_mode); - m0_state.offset = 0; + m0_state.m0_count = 0; } usb_request_status_t usb_vendor_request_set_transceiver_mode( @@ -342,8 +342,9 @@ void rx_mode(uint32_t seq) { baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { + uint32_t m0_offset = m0_state.m0_count & USB_BULK_BUFFER_MASK; // Set up IN transfer of buffer 0. - if (16384 <= m0_state.offset && 1 == phase) { + if (16384 <= m0_offset && 1 == phase) { usb_transfer_schedule_block( &usb_endpoint_bulk_in, &usb_bulk_buffer[0x0000], @@ -353,7 +354,7 @@ void rx_mode(uint32_t seq) { phase = 0; } // Set up IN transfer of buffer 1. - if (16384 > m0_state.offset && 0 == phase) { + if (16384 > m0_offset && 0 == phase) { usb_transfer_schedule_block( &usb_endpoint_bulk_in, &usb_bulk_buffer[0x4000], @@ -384,8 +385,9 @@ void tx_mode(uint32_t seq) { baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { + uint32_t m0_offset = m0_state.m0_count & USB_BULK_BUFFER_MASK; // Set up OUT transfer of buffer 0. - if (16384 <= m0_state.offset && 1 == phase) { + if (16384 <= m0_offset && 1 == phase) { usb_transfer_schedule_block( &usb_endpoint_bulk_out, &usb_bulk_buffer[0x0000], @@ -395,7 +397,7 @@ void tx_mode(uint32_t seq) { phase = 0; } // Set up OUT transfer of buffer 1. - if (16384 > m0_state.offset && 0 == phase) { + if (16384 > m0_offset && 0 == phase) { usb_transfer_schedule_block( &usb_endpoint_bulk_out, &usb_bulk_buffer[0x4000], diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 80863a88..d10cc605 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -379,7 +379,7 @@ int write_register(hackrf_device* device, uint8_t part, static void print_state(hackrf_m0_state *state) { printf("M0 state:\n"); - printf("Offset: %u bytes\n", state->offset); + printf("M0 count: %u bytes\n", state->m0_count); printf("TX: %u\n", state->tx); } diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 63795f0e..ba0f0841 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -157,8 +157,8 @@ typedef struct { /** State of the SGPIO loop running on the M0 core. */ typedef struct { - /** Current offset in the buffer. */ - uint32_t offset; + /** Number of bytes transferred by the M0. */ + uint32_t m0_count; /** TX flag. */ uint32_t tx; } hackrf_m0_state; From 79853d2b28d5252ad836e71f98f8ba998f15235f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 22 Dec 2021 02:50:30 +0000 Subject: [PATCH 04/34] Add a second counter to keep track of bytes transferred by the M4. With both counters in place, the number of bytes in the buffer is now indicated by the difference between the M0 and M4 counts. The M4 count needs to be increased whenever the M4 produces or consumes data in the USB bulk buffer, so that the two counts remain correctly synchronised. There are three places where this is done: 1. When a USB bulk transfer in or out of the buffer completes, the count is increased by the number of bytes transferred. This is the most common case. 2. At TX startup, the M4 effectively sends the M0 16K of zeroes to transmit, before the first host-provided data. This is done by zeroing the whole 32K buffer area, and then setting up the first bulk transfer to write to the second 16K, whilst the M0 begins transmission of the first 16K. The count is therefore increased by 16K during TX startup, to account for the initial 16K of zeros. 3. In sweep mode, some data is discarded. When this is done, the count is incremented by the size of the discarded data. The USB IRQ is masked whilst doing this, since a read-modify-write is required, and the bulk transfer completion callback may be called at any point, which also increases the count. --- firmware/hackrf_usb/m0_state.h | 1 + firmware/hackrf_usb/sgpio_m0.s | 4 +++- firmware/hackrf_usb/usb_api_sweep.c | 19 +++++++++++++++++- firmware/hackrf_usb/usb_api_transceiver.c | 24 ++++++++++++++++++----- host/hackrf-tools/src/hackrf_debug.c | 1 + host/libhackrf/src/hackrf.h | 2 ++ 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index f3b4f90c..ee6c7ba4 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -27,6 +27,7 @@ struct m0_state { uint32_t m0_count; + uint32_t m4_count; uint32_t tx; }; diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index d521c6de..983e7781 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -106,7 +106,8 @@ registers and fixed memory addresses. // Offsets into the state structure. .equ M0_COUNT, 0x00 -.equ TX, 0x04 +.equ M4_COUNT, 0x04 +.equ TX, 0x08 // Our slice chain is set up as follows (ascending data age; arrows are reversed for flow): // L -> F -> K -> C -> J -> E -> I -> A @@ -153,6 +154,7 @@ main: zero .req r0 mov zero, #0 // zero = 0 // 1 str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 + str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 str zero, [state, #TX] // state.tx = zero // 2 loop: diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index 5fef5224..78b2d758 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -87,6 +87,12 @@ usb_request_status_t usb_vendor_request_init_sweep( return USB_REQUEST_STATUS_OK; } +void sweep_bulk_transfer_complete(void *user_data, unsigned int bytes_transferred) +{ + (void) user_data; + m0_state.m4_count += bytes_transferred; +} + void sweep_mode(uint32_t seq) { unsigned int blocks_queued = 0; unsigned int phase = 1; @@ -134,10 +140,21 @@ void sweep_mode(uint32_t seq) { &usb_endpoint_bulk_in, buffer, 0x4000, - NULL, NULL + sweep_bulk_transfer_complete, + NULL ); } transfer = false; + } else { + // Account for having discarded a buffer. + + // Disable USB IRQ whilst doing so, since this requires + // a read-modify-write, and sweep_bulk_transfer_complete() + // might be called from the USB ISR while we are changing + // this count. + nvic_disable_irq(NVIC_USB0_IRQ); + m0_state.m4_count += 0x4000; + nvic_enable_irq(NVIC_USB0_IRQ); } if ((dwell_blocks + THROWAWAY_BUFFERS) <= blocks_queued) { diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 4ee9b7ce..945ed445 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -297,6 +297,7 @@ void transceiver_startup(const transceiver_mode_t mode) { activate_best_clock_source(); hw_sync_enable(_hw_sync_mode); m0_state.m0_count = 0; + m0_state.m4_count = 0; } usb_request_status_t usb_vendor_request_set_transceiver_mode( @@ -334,6 +335,12 @@ usb_request_status_t usb_vendor_request_set_hw_sync_mode( } } +void transceiver_bulk_transfer_complete(void *user_data, unsigned int bytes_transferred) +{ + (void) user_data; + m0_state.m4_count += bytes_transferred; +} + void rx_mode(uint32_t seq) { unsigned int phase = 1; @@ -349,7 +356,8 @@ void rx_mode(uint32_t seq) { &usb_endpoint_bulk_in, &usb_bulk_buffer[0x0000], 0x4000, - NULL, NULL + transceiver_bulk_transfer_complete, + NULL ); phase = 0; } @@ -359,7 +367,8 @@ void rx_mode(uint32_t seq) { &usb_endpoint_bulk_in, &usb_bulk_buffer[0x4000], 0x4000, - NULL, NULL + transceiver_bulk_transfer_complete, + NULL ); phase = 1; } @@ -374,12 +383,15 @@ void tx_mode(uint32_t seq) { transceiver_startup(TRANSCEIVER_MODE_TX); memset(&usb_bulk_buffer[0x0000], 0, 0x8000); + // Account for having filled buffer 0. + m0_state.m4_count += 0x4000; // Set up OUT transfer of buffer 1. usb_transfer_schedule_block( &usb_endpoint_bulk_out, &usb_bulk_buffer[0x4000], 0x4000, - NULL, NULL + transceiver_bulk_transfer_complete, + NULL ); // Start transmitting zeros while the host fills buffer 1. baseband_streaming_enable(&sgpio_config); @@ -392,7 +404,8 @@ void tx_mode(uint32_t seq) { &usb_endpoint_bulk_out, &usb_bulk_buffer[0x0000], 0x4000, - NULL, NULL + transceiver_bulk_transfer_complete, + NULL ); phase = 0; } @@ -402,7 +415,8 @@ void tx_mode(uint32_t seq) { &usb_endpoint_bulk_out, &usb_bulk_buffer[0x4000], 0x4000, - NULL, NULL + transceiver_bulk_transfer_complete, + NULL ); phase = 1; } diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index d10cc605..9d8f5475 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -380,6 +380,7 @@ int write_register(hackrf_device* device, uint8_t part, static void print_state(hackrf_m0_state *state) { printf("M0 state:\n"); printf("M0 count: %u bytes\n", state->m0_count); + printf("M4 count: %u bytes\n", state->m4_count); printf("TX: %u\n", state->tx); } diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index ba0f0841..0039c3f2 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -159,6 +159,8 @@ typedef struct { typedef struct { /** Number of bytes transferred by the M0. */ uint32_t m0_count; + /** Number of bytes transferred by the M4. */ + uint32_t m4_count; /** TX flag. */ uint32_t tx; } hackrf_m0_state; From eb2be7995c338908edecd8668ad016d3d2417f53 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 22 Dec 2021 03:29:13 +0000 Subject: [PATCH 05/34] Add hackrf_transfer option to display buffer stats. This adds the `hackrf_transfer -B` option, which displays the number of bytes currently in the buffer along with the existing per-second stats. The number of bytes in the buffer is indicated by the difference between the M0 and M4 byte counters. In TX, the M4 count should lead the M0 count. In RX, the M0 count should lead the M4 count. --- host/hackrf-tools/src/hackrf_transfer.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/host/hackrf-tools/src/hackrf_transfer.c b/host/hackrf-tools/src/hackrf_transfer.c index a4db1904..4b082f2f 100644 --- a/host/hackrf-tools/src/hackrf_transfer.c +++ b/host/hackrf-tools/src/hackrf_transfer.c @@ -377,6 +377,8 @@ bool limit_num_samples = false; uint64_t samples_to_xfer = 0; size_t bytes_to_xfer = 0; +bool display_stats = false; + bool baseband_filter_bw = false; uint32_t baseband_filter_bw_hz = 0; @@ -533,6 +535,7 @@ static void usage() { /* The required atomic load/store functions aren't available when using C with MSVC */ printf("\t[-S buf_size] # Enable receive streaming with buffer size buf_size.\n"); #endif + printf("\t[-B] # Print buffer statistics during transfer\n"); printf("\t[-c amplitude] # CW signal source mode, amplitude 0-127 (DC value to DAC).\n"); printf("\t[-R] # Repeat TX mode (default is off) \n"); printf("\t[-b baseband_filter_bw_hz] # Set baseband filter bandwidth in Hz.\n\tPossible values: 1.75/2.5/3.5/5/5.5/6/7/8/9/10/12/14/15/20/24/28MHz, default <= 0.75 * sample_rate_hz.\n" ); @@ -580,7 +583,7 @@ int main(int argc, char** argv) { float time_diff; unsigned int lna_gain=8, vga_gain=20, txvga_gain=0; - while( (opt = getopt(argc, argv, "H:wr:t:f:i:o:m:a:p:s:n:b:l:g:x:c:d:C:RS:h?")) != EOF ) + while( (opt = getopt(argc, argv, "H:wr:t:f:i:o:m:a:p:s:n:b:l:g:x:c:d:C:RS:Bh?")) != EOF ) { result = HACKRF_SUCCESS; switch( opt ) @@ -668,6 +671,10 @@ int main(int argc, char** argv) { bytes_to_xfer = samples_to_xfer * 2ull; break; + case 'B': + display_stats = true; + break; + case 'b': result = parse_frequency_u32(optarg, endptr, &baseband_filter_bw_hz); baseband_filter_bw = true; @@ -1097,12 +1104,25 @@ int main(int argc, char** argv) { double dB_full_scale_ratio = 10*log10(full_scale_ratio); if (dB_full_scale_ratio > 1) dB_full_scale_ratio = NAN; // Guard against ridiculous reports - fprintf(stderr, "%4.1f MiB / %5.3f sec = %4.1f MiB/second, amplitude %3.1f dBfs\n", + fprintf(stderr, "%4.1f MiB / %5.3f sec = %4.1f MiB/second, amplitude %3.1f dBfs", (byte_count_now / 1e6f), time_difference, (rate / 1e6f), dB_full_scale_ratio ); + if (display_stats) { + bool tx = transmit || signalsource; + hackrf_m0_state state; + result = hackrf_get_m0_state(device, &state); + if (result != HACKRF_SUCCESS) + fprintf(stderr, "\nhackrf_get_m0_state() failed: %s (%d)\n", hackrf_error_name(result), result); + else + fprintf(stderr, ", %d bytes %s in buffer\n", + tx ? state.m4_count - state.m0_count : state.m0_count - state.m4_count, + tx ? "filled" : "free"); + } else { + fprintf(stderr, "\n"); + } } time_start = time_now; From c8d120ff6c5113c6774728e978b13a1f72387708 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 22 Dec 2021 04:14:44 +0000 Subject: [PATCH 06/34] Display total M0 and M4 counts at end of hackrf_transfer. Doing this requires keeping track of when the 32-bit counters wrap, and updating 64-bit running totals. --- host/hackrf-tools/src/hackrf_transfer.c | 60 ++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/host/hackrf-tools/src/hackrf_transfer.c b/host/hackrf-tools/src/hackrf_transfer.c index 4b082f2f..15b93be8 100644 --- a/host/hackrf-tools/src/hackrf_transfer.c +++ b/host/hackrf-tools/src/hackrf_transfer.c @@ -121,6 +121,11 @@ typedef enum { HW_SYNC_MODE_ON = 1, } hw_sync_mode_t; +typedef struct { + uint64_t m0_total; + uint64_t m4_total; +} stats_t; + /* WAVE or RIFF WAVE file format containing IQ 2x8bits data for HackRF compatible with SDR# Wav IQ file */ typedef struct { @@ -505,6 +510,42 @@ int tx_callback(hackrf_transfer* transfer) { } } +static int update_stats(hackrf_device *device, hackrf_m0_state *state, stats_t *stats) +{ + int result = hackrf_get_m0_state(device, state); + + if (result == HACKRF_SUCCESS) { + /* + * Update 64-bit running totals, to handle wrapping of the 32-bit fields + * for M0 and M4 byte counts. + * + * The logic for handling wrapping works as follows: + * + * If a 32-bit count read from the HackRF is less than the lower 32 bits of + * the previous 64-bit running total, this indicates the 32-bit counter has + * wrapped since it was last read. Add 2^32 to the 64-bit total to account + * for this. + * + * Then, having accounted for the possible wrap, mask off the bottom 32 + * bits of the 64-bit total, and replace them with the new 32-bit count. + * + * This should result in correct results as long as the 32-bit counter + * cannot wrap more than once between reads. + * + * We read the M0 state every second, and the counters will wrap every 107 + * seconds at 20Msps, so this should be a safe assumption. + */ + if (state->m0_count < (stats->m0_total & 0xFFFFFFFF)) + stats->m0_total += 0x100000000; + if (state->m4_count < (stats->m4_total & 0xFFFFFFFF)) + stats->m4_total += 0x100000000; + stats->m0_total = (stats->m0_total & 0xFFFFFFFF00000000) | state->m0_count; + stats->m4_total = (stats->m4_total & 0xFFFFFFFF00000000) | state->m4_count; + } + + return result; +} + static void usage() { printf("Usage:\n"); printf("\t-h # this help\n"); @@ -582,6 +623,8 @@ int main(int argc, char** argv) { struct timeval t_end; float time_diff; unsigned int lna_gain=8, vga_gain=20, txvga_gain=0; + hackrf_m0_state state; + stats_t stats = {0, 0}; while( (opt = getopt(argc, argv, "H:wr:t:f:i:o:m:a:p:s:n:b:l:g:x:c:d:C:RS:Bh?")) != EOF ) { @@ -1112,8 +1155,7 @@ int main(int argc, char** argv) { ); if (display_stats) { bool tx = transmit || signalsource; - hackrf_m0_state state; - result = hackrf_get_m0_state(device, &state); + result = update_stats(device, &state, &stats); if (result != HACKRF_SUCCESS) fprintf(stderr, "\nhackrf_get_m0_state() failed: %s (%d)\n", hackrf_error_name(result), result); else @@ -1166,6 +1208,20 @@ int main(int argc, char** argv) { } } + if (display_stats) { + result = update_stats(device, &state, &stats); + if (result != HACKRF_SUCCESS) { + fprintf(stderr, "hackrf_get_m0_state() failed: %s (%d)\n", hackrf_error_name(result), result); + } else { + fprintf(stderr, + "Transfer statistics:\n" + "%lu bytes transferred by M0\n" + "%lu bytes transferred by M4\n", + stats.m0_total, + stats.m4_total); + } + } + result = hackrf_close(device); if(result != HACKRF_SUCCESS) { fprintf(stderr, "hackrf_close() failed: %s (%d)\n", hackrf_error_name(result), result); From c0d0cd2a1d0a667c9d65cf1c0555bdfda2788a47 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 22 Dec 2021 04:48:49 +0000 Subject: [PATCH 07/34] Check for sufficient bytes, or space in buffer, before proceeding. In TX, check if there are sufficient bytes in the buffer to write a block to SGPIO. If not, write zeros to SGPIO instead. In RX, check if there is sufficent space in the buffer to store a block read from SGPIO. If not, do nothing, which discards the data. In both of these shortfall cases, the M0 count is not incremented. This ensures that in TX, old data is never repeated. The M0 will not resume writing TX samples to SGPIO until the M4 count advances, indicating new data being ready in the buffer. This fixes bug #180. Similarly, in RX, old data is never overwritten. The M0 will not resume writing RX samples to the buffer until the M4 count advances, indicating new space being available in the buffer. --- firmware/hackrf_usb/sgpio_m0.s | 65 +++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 983e7781..3056c20b 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -64,10 +64,12 @@ These latencies are assumed to apply to all accesses to the SGPIO peripheral's address space, which includes its interrupt control registers as well as the shadow registers. -There are two key code paths, with the following worst-case timings: +There are four key code paths, with the following worst-case timings: -RX: 141 cycles -TX: 126 cycles +RX, normal: 146 cycles +RX, overrun: 52 cycles +TX, normal: 131 cycles +TX, underrun: 118 cycles Design ====== @@ -99,6 +101,7 @@ registers and fixed memory addresses. // Buffer that we're funneling data to/from. .equ TARGET_DATA_BUFFER, 0x20008000 +.equ TARGET_BUFFER_SIZE, 0x8000 .equ TARGET_BUFFER_MASK, 0x7fff // Base address of the state structure. @@ -124,6 +127,7 @@ registers and fixed memory addresses. /* Allocations of single-use registers */ +buf_size_minus_32 .req r14 state .req r13 buf_base .req r12 buf_mask .req r11 @@ -143,6 +147,8 @@ main: value .req r0 ldr sgpio_int, =SGPIO_EXCHANGE_INTERRUPT_BASE // sgpio_int = SGPIO_INT_BASE // 2 ldr sgpio_data, =SGPIO_SHADOW_REGISTERS_BASE // sgpio_data = SGPIO_REG_SS // 2 + ldr value, =(TARGET_BUFFER_SIZE - 32) // value = TARGET_BUFFER_SIZE - 32 // 2 + mov buf_size_minus_32, value // buf_size_minus_32 = value // 1 ldr value, =TARGET_DATA_BUFFER // value = TARGET_DATA_BUFFER // 2 mov buf_base, value // buf_base = value // 1 ldr value, =TARGET_BUFFER_MASK // value = TARGET_DATA_MASK // 2 @@ -205,12 +211,27 @@ loop: direction_tx: + // Check if there is enough data in the buffer. + // + // The number of bytes in the buffer is given by (m4_count - m0_count). + // We need 32 bytes available to proceed. So our margin, which we want + // to be positive or zero, is: + // + // buf_margin = m4_count - m0_count - 32 + // + // If there is insufficient data, transmit zeros instead. + buf_margin .req r0 + ldr buf_margin, [state, #M4_COUNT] // buf_margin = m4_count // 2 + sub buf_margin, count // buf_margin -= count // 1 + sub buf_margin, #32 // buf_margin -= 32 // 1 + bmi tx_zeros // if buf_margin < 0: goto tx_zeros // 1 thru, 3 taken + + // Write data to SGPIO. ldm buf_ptr!, {r0-r3} // r0-r3 = buf_ptr[0:16]; buf_ptr += 16 // 5 str r0, [sgpio_data, #SLICE0] // SGPIO_REG_SS[SLICE0] = r0 // 8 str r1, [sgpio_data, #SLICE1] // SGPIO_REG_SS[SLICE1] = r1 // 8 str r2, [sgpio_data, #SLICE2] // SGPIO_REG_SS[SLICE2] = r2 // 8 str r3, [sgpio_data, #SLICE3] // SGPIO_REG_SS[SLICE3] = r3 // 8 - ldm buf_ptr!, {r0-r3} // r0-r3 = buf_ptr[0:16]; buf_ptr += 16 // 5 str r0, [sgpio_data, #SLICE4] // SGPIO_REG_SS[SLICE4] = r0 // 8 str r1, [sgpio_data, #SLICE5] // SGPIO_REG_SS[SLICE5] = r1 // 8 @@ -219,14 +240,48 @@ direction_tx: b done // goto done // 3 +tx_zeros: + + // Write zeros to SGPIO. + mov zero, #0 // zero = 0 // 1 + str zero, [sgpio_data, #SLICE0] // SGPIO_REG_SS[SLICE0] = zero // 8 + str zero, [sgpio_data, #SLICE1] // SGPIO_REG_SS[SLICE1] = zero // 8 + str zero, [sgpio_data, #SLICE2] // SGPIO_REG_SS[SLICE2] = zero // 8 + str zero, [sgpio_data, #SLICE3] // SGPIO_REG_SS[SLICE3] = zero // 8 + str zero, [sgpio_data, #SLICE4] // SGPIO_REG_SS[SLICE4] = zero // 8 + str zero, [sgpio_data, #SLICE5] // SGPIO_REG_SS[SLICE5] = zero // 8 + str zero, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = zero // 8 + str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8 + + b loop // goto loop // 3 + direction_rx: + // Check if there is enough space in the buffer. + // + // The number of bytes in the buffer is given by (m0_count - m4_count). + // We need space for another 32 bytes to proceed. So our margin, which + // we want to be positive or zero, is: + // + // buf_margin = buf_size - (m0_count - state.m4_count) - 32 + // + // which can be rearranged for efficiency as: + // + // buf_margin = m4_count + (buf_size - 32) - m0_count + // + // If there is insufficient space, jump back to the start of the loop. + buf_margin .req r0 + ldr buf_margin, [state, #M4_COUNT] // buf_margin = state.m4_count // 2 + add buf_margin, buf_size_minus_32 // buf_margin += buf_size_minus_32 // 1 + sub buf_margin, count // buf_margin -= count // 1 + bmi loop // if buf_margin < 0: goto loop // 1 thru, 3 taken + + // Read data from SGPIO. ldr r0, [sgpio_data, #SLICE0] // r0 = SGPIO_REG_SS[SLICE0] // 10 ldr r1, [sgpio_data, #SLICE1] // r1 = SGPIO_REG_SS[SLICE1] // 10 ldr r2, [sgpio_data, #SLICE2] // r2 = SGPIO_REG_SS[SLICE2] // 10 ldr r3, [sgpio_data, #SLICE3] // r3 = SGPIO_REG_SS[SLICE3] // 10 stm buf_ptr!, {r0-r3} // buf_ptr[0:16] = r0-r3; buf_ptr += 16 // 5 - ldr r0, [sgpio_data, #SLICE4] // r0 = SGPIO_REG_SS[SLICE4] // 10 ldr r1, [sgpio_data, #SLICE5] // r1 = SGPIO_REG_SS[SLICE5] // 10 ldr r2, [sgpio_data, #SLICE6] // r2 = SGPIO_REG_SS[SLICE6] // 10 From 5b50b2dfac335001ebece6836670ab16b596acbd Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 07:44:18 +0000 Subject: [PATCH 08/34] Replace TX flag with a mode setting. This is to let us start adding new operatin modes for the M0. --- firmware/hackrf_usb/m0_state.h | 7 ++++++- firmware/hackrf_usb/sgpio_m0.s | 23 +++++++++++++---------- firmware/hackrf_usb/usb_api_transceiver.c | 6 +++--- host/hackrf-tools/src/hackrf_debug.c | 8 +++++++- host/libhackrf/src/hackrf.h | 4 ++-- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index ee6c7ba4..497f4323 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -26,9 +26,14 @@ #include struct m0_state { + uint32_t mode; uint32_t m0_count; uint32_t m4_count; - uint32_t tx; +}; + +enum m0_mode { + M0_MODE_RX = 0, + M0_MODE_TX = 1, }; /* Address of m0_state is set in ldscripts. If you change the name of this diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 3056c20b..5d60bb6c 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -108,9 +108,13 @@ registers and fixed memory addresses. .equ STATE_BASE, 0x20007000 // Offsets into the state structure. -.equ M0_COUNT, 0x00 -.equ M4_COUNT, 0x04 -.equ TX, 0x08 +.equ MODE, 0x00 +.equ M0_COUNT, 0x04 +.equ M4_COUNT, 0x08 + +// Operating modes. +.equ MODE_RX, 0 +.equ MODE_TX, 1 // Our slice chain is set up as follows (ascending data age; arrows are reversed for flow): // L -> F -> K -> C -> J -> E -> I -> A @@ -159,9 +163,9 @@ main: // Initialise state. zero .req r0 mov zero, #0 // zero = 0 // 1 + str zero, [state, #MODE] // state.mode = zero // 2 str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 - str zero, [state, #TX] // state.tx = zero // 2 loop: // The worst case timing is assumed to occur when reading the interrupt @@ -200,14 +204,13 @@ loop: and buf_ptr, count // buf_ptr &= count // 1 add buf_ptr, buf_base // buf_ptr += buf_base // 1 - tx .req r0 - - // Load direction (TX or RX) - ldr tx, [state, #TX] // tx = state.tx // 2 + // Load mode. + mode .req r0 + ldr mode, [state, #MODE] // mode = state.mode // 2 // TX? - lsr tx, #1 // tx >>= 1 // 1 - bcc direction_rx // if !carry: goto direction_rx // 1 thru, 3 taken + cmp mode, #MODE_RX // if mode == RX: // 1 + beq direction_rx // goto direction_rx // 1 thru, 3 taken direction_tx: diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 945ed445..c3584219 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -269,7 +269,7 @@ void transceiver_shutdown(void) led_off(LED2); led_off(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF); - m0_state.tx = false; + m0_state.mode = M0_MODE_RX; } void transceiver_startup(const transceiver_mode_t mode) { @@ -282,13 +282,13 @@ void transceiver_startup(const transceiver_mode_t mode) { led_off(LED3); led_on(LED2); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX); - m0_state.tx = false; + m0_state.mode = M0_MODE_RX; break; case TRANSCEIVER_MODE_TX: led_off(LED2); led_on(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX); - m0_state.tx = true; + m0_state.mode = M0_MODE_TX; break; default: break; diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 9d8f5475..40fa89e4 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -378,10 +378,16 @@ int write_register(hackrf_device* device, uint8_t part, } static void print_state(hackrf_m0_state *state) { + const char *mode_names[] = {"RX", "TX"}; + const uint32_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]); printf("M0 state:\n"); + printf("Mode: %u (%s)\n", + state->mode, + state->mode < num_modes ? + mode_names[state->mode] : + "UNKNOWN"); printf("M0 count: %u bytes\n", state->m0_count); printf("M4 count: %u bytes\n", state->m4_count); - printf("TX: %u\n", state->tx); } static void usage() { diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 0039c3f2..91d58d67 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -157,12 +157,12 @@ typedef struct { /** State of the SGPIO loop running on the M0 core. */ typedef struct { + /** Operating mode. */ + uint32_t mode; /** Number of bytes transferred by the M0. */ uint32_t m0_count; /** Number of bytes transferred by the M4. */ uint32_t m4_count; - /** TX flag. */ - uint32_t tx; } hackrf_m0_state; struct hackrf_device_list { From 32c725dd618ceb06433186ed3c8a7993945db791 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 08:27:34 +0000 Subject: [PATCH 09/34] Add an idle mode for the M0. In the idle mode, the M0 simply waits for a different mode to be set. No SGPIO access is done. One extra cycle is added to both TX code paths, to check whether the M0 should return to the idle loop based on the mode setting. The RX paths are unaffected as the branch to RX is handled first. --- firmware/hackrf_usb/m0_state.h | 5 +++-- firmware/hackrf_usb/sgpio_m0.s | 19 ++++++++++++++----- host/hackrf-tools/src/hackrf_debug.c | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index 497f4323..7928dd3f 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -32,8 +32,9 @@ struct m0_state { }; enum m0_mode { - M0_MODE_RX = 0, - M0_MODE_TX = 1, + M0_MODE_IDLE = 0, + M0_MODE_RX = 1, + M0_MODE_TX = 2, }; /* Address of m0_state is set in ldscripts. If you change the name of this diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 5d60bb6c..8df257c3 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -68,8 +68,8 @@ There are four key code paths, with the following worst-case timings: RX, normal: 146 cycles RX, overrun: 52 cycles -TX, normal: 131 cycles -TX, underrun: 118 cycles +TX, normal: 132 cycles +TX, underrun: 119 cycles Design ====== @@ -113,8 +113,9 @@ registers and fixed memory addresses. .equ M4_COUNT, 0x08 // Operating modes. -.equ MODE_RX, 0 -.equ MODE_TX, 1 +.equ MODE_IDLE, 0 +.equ MODE_RX, 1 +.equ MODE_TX, 2 // Our slice chain is set up as follows (ascending data age; arrows are reversed for flow): // L -> F -> K -> C -> J -> E -> I -> A @@ -167,6 +168,13 @@ main: str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 +idle: + // Wait for RX or TX mode to be set. + mode .req r0 + ldr mode, [state, #MODE] // mode = state.mode // 2 + cmp mode, #MODE_IDLE // if mode == IDLE: // 1 + beq idle // goto idle // 1 thru, 3 taken + loop: // The worst case timing is assumed to occur when reading the interrupt // status register *just* misses the flag being set - so we include the @@ -208,9 +216,10 @@ loop: mode .req r0 ldr mode, [state, #MODE] // mode = state.mode // 2 - // TX? + // Branch according to mode setting. cmp mode, #MODE_RX // if mode == RX: // 1 beq direction_rx // goto direction_rx // 1 thru, 3 taken + blt idle // elif mode < RX: goto idle // 1 thru, 3 taken direction_tx: diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 40fa89e4..a36120b3 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -378,7 +378,7 @@ int write_register(hackrf_device* device, uint8_t part, } static void print_state(hackrf_m0_state *state) { - const char *mode_names[] = {"RX", "TX"}; + const char *mode_names[] = {"IDLE", "RX", "TX"}; const uint32_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]); printf("M0 state:\n"); printf("Mode: %u (%s)\n", From 3fd3c7786e881fcc07d2fd18b5466cf8afb2a03b Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 08:32:07 +0000 Subject: [PATCH 10/34] Set M0 mode to IDLE when transceiver mode is OFF. At this point, streaming has been stopped, so there will be no further SGPIO interrupts. However, the M0 will still be spinning on the interrupt flag, waiting to proceed. To ensure that the M0 actually reaches its idle loop, we set the SGPIO interrupt flag once. The M0 will then finish spinning on the flag, clear the flag, see the new mode setting, and jump to the idle loop. --- firmware/hackrf_usb/usb_api_transceiver.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index c3584219..624fd4aa 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -269,7 +269,12 @@ void transceiver_shutdown(void) led_off(LED2); led_off(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF); - m0_state.mode = M0_MODE_RX; + m0_state.mode = M0_MODE_IDLE; + // The M0 may already be waiting for the next SGPIO + // exchange interrupt. In order to ensure that the M0 + // switches over to its idle loop, we need to set the + // SGPIO exchange interrupt flag here. + SGPIO_SET_STATUS_1 = (1 << SGPIO_SLICE_A); } void transceiver_startup(const transceiver_mode_t mode) { From 0f3069ee5ea0031a88b61cbf2a554bc063cc9f66 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 08:39:47 +0000 Subject: [PATCH 11/34] Move resetting of byte counts to the M0. Previously, these counts were zeroed by the M4 when leaving the OFF transceiver mode. Instead, do this on the M0 at the point where the M0 leaves IDLE mode. This avoids a potential race in which the M4 zeroes the M0 count after the M0 has already started incrementing it. --- firmware/hackrf_usb/sgpio_m0.s | 5 +++++ firmware/hackrf_usb/usb_api_transceiver.c | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 8df257c3..9965e9fb 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -175,6 +175,11 @@ idle: cmp mode, #MODE_IDLE // if mode == IDLE: // 1 beq idle // goto idle // 1 thru, 3 taken + // Reset counts. + mov zero, #0 // zero = 0 // 1 + str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 + str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 + loop: // The worst case timing is assumed to occur when reading the interrupt // status register *just* misses the flag being set - so we include the diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 624fd4aa..c5e0a43f 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -301,8 +301,6 @@ void transceiver_startup(const transceiver_mode_t mode) { activate_best_clock_source(); hw_sync_enable(_hw_sync_mode); - m0_state.m0_count = 0; - m0_state.m4_count = 0; } usb_request_status_t usb_vendor_request_set_transceiver_mode( From a7bd1e3ede9182330dfd8925940ea88776e157cf Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 09:09:16 +0000 Subject: [PATCH 12/34] Keep count of number of shortfalls. To enable this, we keep a count of the current shortfall length. Each time an SGPIO read/write cannot be completed due to a shortfall, we increase this length. Each time an SGPIO read/write is completed successfully, we reset the shortfall length to zero. When a shortfall occurs and the existing shortfall length is zero, this indicates a new shortfall, and the shortfall count is incremented. This change adds one cycle to the normal RX & TX paths, to zero the shortfall count. To enable this to be done in a single cycle, we keep a zero handy in a high register. The extra accounting adds 10 cycles to the TX and RX shortfall paths, plus an additional 3 cycles to the RX shortfall path since there are now two branches involved: one to the shortfall handler, and another back to the main loop. --- firmware/hackrf_usb/m0_state.h | 1 + firmware/hackrf_usb/sgpio_m0.s | 49 +++++++++++++++++++++---- host/hackrf-tools/src/hackrf_debug.c | 1 + host/hackrf-tools/src/hackrf_transfer.c | 13 +++++-- host/libhackrf/src/hackrf.h | 2 + 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index 7928dd3f..549780b4 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -29,6 +29,7 @@ struct m0_state { uint32_t mode; uint32_t m0_count; uint32_t m4_count; + uint32_t num_shortfalls; }; enum m0_mode { diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 9965e9fb..74060dfb 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -66,10 +66,10 @@ shadow registers. There are four key code paths, with the following worst-case timings: -RX, normal: 146 cycles -RX, overrun: 52 cycles -TX, normal: 132 cycles -TX, underrun: 119 cycles +RX, normal: 147 cycles +RX, overrun: 65 cycles +TX, normal: 133 cycles +TX, underrun: 129 cycles Design ====== @@ -111,6 +111,7 @@ registers and fixed memory addresses. .equ MODE, 0x00 .equ M0_COUNT, 0x04 .equ M4_COUNT, 0x08 +.equ NUM_SHORTFALLS, 0x0C // Operating modes. .equ MODE_IDLE, 0 @@ -136,6 +137,8 @@ buf_size_minus_32 .req r14 state .req r13 buf_base .req r12 buf_mask .req r11 +shortfall_length .req r10 +hi_zero .req r9 sgpio_data .req r7 sgpio_int .req r6 count .req r5 @@ -160,13 +163,15 @@ main: mov buf_mask, value // buf_mask = value // 1 ldr value, =STATE_BASE // value = STATE_BASE // 2 mov state, value // state = value // 1 - - // Initialise state. zero .req r0 mov zero, #0 // zero = 0 // 1 + mov hi_zero, zero // hi_zero = zero // 1 + + // Initialise state. str zero, [state, #MODE] // state.mode = zero // 2 str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 + str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 idle: // Wait for RX or TX mode to be set. @@ -179,6 +184,8 @@ idle: mov zero, #0 // zero = 0 // 1 str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 + str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 + mov shortfall_length, zero // shortfall_length = zero // 1 loop: // The worst case timing is assumed to occur when reading the interrupt @@ -270,6 +277,29 @@ tx_zeros: str zero, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = zero // 8 str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8 +shortfall: + + // Get current shortfall length from high register. + length .req r0 + mov length, shortfall_length // length = shortfall_length // 1 + + // Is this a new shortfall? + cmp length, #0 // if length > 0: // 1 + bgt extend_shortfall // goto extend_shortfall // 1 thru, 3 taken + + // If so, increase the shortfall count. + num .req r1 + ldr num, [state, #NUM_SHORTFALLS] // num = state.num_shortfalls // 2 + add num, #1 // num += 1 // 1 + str num, [state, #NUM_SHORTFALLS] // state.num_shortfalls = num // 2 + +extend_shortfall: + + // Extend the length of the current shortfall, and store back in high register. + add length, #32 // length += 32 // 1 + mov shortfall_length, length // shortfall_length = length // 1 + + // Return to main loop. b loop // goto loop // 3 direction_rx: @@ -286,12 +316,12 @@ direction_rx: // // buf_margin = m4_count + (buf_size - 32) - m0_count // - // If there is insufficient space, jump back to the start of the loop. + // If there is insufficient space, jump to shortfall handling. buf_margin .req r0 ldr buf_margin, [state, #M4_COUNT] // buf_margin = state.m4_count // 2 add buf_margin, buf_size_minus_32 // buf_margin += buf_size_minus_32 // 1 sub buf_margin, count // buf_margin -= count // 1 - bmi loop // if buf_margin < 0: goto loop // 1 thru, 3 taken + bmi shortfall // if buf_margin < 0: goto shortfall // 1 thru, 3 taken // Read data from SGPIO. ldr r0, [sgpio_data, #SLICE0] // r0 = SGPIO_REG_SS[SLICE0] // 10 @@ -312,6 +342,9 @@ done: // ... and store the new count. str count, [state, #M0_COUNT] // state.m0_count = count // 2 + // We didn't have a shortfall, so the current shortfall length is zero. + mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1 + b loop // goto loop // 3 // The linker will put a literal pool here, so add a label for clearer objdump output: diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index a36120b3..e65a70f2 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -388,6 +388,7 @@ static void print_state(hackrf_m0_state *state) { "UNKNOWN"); printf("M0 count: %u bytes\n", state->m0_count); printf("M4 count: %u bytes\n", state->m4_count); + printf("Number of shortfalls: %u\n", state->num_shortfalls); } static void usage() { diff --git a/host/hackrf-tools/src/hackrf_transfer.c b/host/hackrf-tools/src/hackrf_transfer.c index 15b93be8..6b97f40f 100644 --- a/host/hackrf-tools/src/hackrf_transfer.c +++ b/host/hackrf-tools/src/hackrf_transfer.c @@ -1159,9 +1159,11 @@ int main(int argc, char** argv) { if (result != HACKRF_SUCCESS) fprintf(stderr, "\nhackrf_get_m0_state() failed: %s (%d)\n", hackrf_error_name(result), result); else - fprintf(stderr, ", %d bytes %s in buffer\n", + fprintf(stderr, ", %d bytes %s in buffer, %u %s\n", tx ? state.m4_count - state.m0_count : state.m0_count - state.m4_count, - tx ? "filled" : "free"); + tx ? "filled" : "free", + state.num_shortfalls, + tx ? "underruns" : "overruns"); } else { fprintf(stderr, "\n"); } @@ -1216,9 +1218,12 @@ int main(int argc, char** argv) { fprintf(stderr, "Transfer statistics:\n" "%lu bytes transferred by M0\n" - "%lu bytes transferred by M4\n", + "%lu bytes transferred by M4\n" + "%u %s\n", stats.m0_total, - stats.m4_total); + stats.m4_total, + state.num_shortfalls, + (transmit || signalsource) ? "underruns" : "overruns"); } } diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 91d58d67..d1d049f5 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -163,6 +163,8 @@ typedef struct { uint32_t m0_count; /** Number of bytes transferred by the M4. */ uint32_t m4_count; + /** Number of shortfalls. */ + uint32_t num_shortfalls; } hackrf_m0_state; struct hackrf_device_list { From 2c86f493d9dcb4300a41f32e6c25b21e94048702 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 09:35:41 +0000 Subject: [PATCH 13/34] Keep track of longest shortfall. This adds six cycles to the TX and RX shortfall paths. --- firmware/hackrf_usb/m0_state.h | 1 + firmware/hackrf_usb/sgpio_m0.s | 14 ++++++++++++-- host/hackrf-tools/src/hackrf_debug.c | 1 + host/hackrf-tools/src/hackrf_transfer.c | 10 ++++++---- host/libhackrf/src/hackrf.h | 2 ++ 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index 549780b4..db79c6db 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -30,6 +30,7 @@ struct m0_state { uint32_t m0_count; uint32_t m4_count; uint32_t num_shortfalls; + uint32_t longest_shortfall; }; enum m0_mode { diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 74060dfb..ec26b2b3 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -67,9 +67,9 @@ shadow registers. There are four key code paths, with the following worst-case timings: RX, normal: 147 cycles -RX, overrun: 65 cycles +RX, overrun: 71 cycles TX, normal: 133 cycles -TX, underrun: 129 cycles +TX, underrun: 135 cycles Design ====== @@ -112,6 +112,7 @@ registers and fixed memory addresses. .equ M0_COUNT, 0x04 .equ M4_COUNT, 0x08 .equ NUM_SHORTFALLS, 0x0C +.equ LONGEST_SHORTFALL, 0x10 // Operating modes. .equ MODE_IDLE, 0 @@ -172,6 +173,7 @@ main: str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 + str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 idle: // Wait for RX or TX mode to be set. @@ -185,6 +187,7 @@ idle: str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 + str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 mov shortfall_length, zero // shortfall_length = zero // 1 loop: @@ -299,6 +302,13 @@ extend_shortfall: add length, #32 // length += 32 // 1 mov shortfall_length, length // shortfall_length = length // 1 + // Is this now the longest shortfall? + longest .req r1 + ldr longest, [state, #LONGEST_SHORTFALL] // longest = state.longest_shortfall // 2 + cmp length, longest // if length <= longest: // 1 + blt loop // goto loop // 1 thru, 3 taken + str length, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = length // 2 + // Return to main loop. b loop // goto loop // 3 diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index e65a70f2..96895e5d 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -389,6 +389,7 @@ static void print_state(hackrf_m0_state *state) { printf("M0 count: %u bytes\n", state->m0_count); printf("M4 count: %u bytes\n", state->m4_count); printf("Number of shortfalls: %u\n", state->num_shortfalls); + printf("Longest shortfall: %u bytes\n", state->longest_shortfall); } static void usage() { diff --git a/host/hackrf-tools/src/hackrf_transfer.c b/host/hackrf-tools/src/hackrf_transfer.c index 6b97f40f..20171225 100644 --- a/host/hackrf-tools/src/hackrf_transfer.c +++ b/host/hackrf-tools/src/hackrf_transfer.c @@ -1159,11 +1159,12 @@ int main(int argc, char** argv) { if (result != HACKRF_SUCCESS) fprintf(stderr, "\nhackrf_get_m0_state() failed: %s (%d)\n", hackrf_error_name(result), result); else - fprintf(stderr, ", %d bytes %s in buffer, %u %s\n", + fprintf(stderr, ", %d bytes %s in buffer, %u %s, longest %u bytes\n", tx ? state.m4_count - state.m0_count : state.m0_count - state.m4_count, tx ? "filled" : "free", state.num_shortfalls, - tx ? "underruns" : "overruns"); + tx ? "underruns" : "overruns", + state.longest_shortfall); } else { fprintf(stderr, "\n"); } @@ -1219,11 +1220,12 @@ int main(int argc, char** argv) { "Transfer statistics:\n" "%lu bytes transferred by M0\n" "%lu bytes transferred by M4\n" - "%u %s\n", + "%u %s, longest %u bytes\n", stats.m0_total, stats.m4_total, state.num_shortfalls, - (transmit || signalsource) ? "underruns" : "overruns"); + (transmit || signalsource) ? "underruns" : "overruns", + state.longest_shortfall); } } diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index d1d049f5..da5dded1 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -165,6 +165,8 @@ typedef struct { uint32_t m4_count; /** Number of shortfalls. */ uint32_t num_shortfalls; + /** Longest shortfall. */ + uint32_t longest_shortfall; } hackrf_m0_state; struct hackrf_device_list { From f0bc6eda30e0d0e32625499b820bf8f21762ed9d Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 09:47:27 +0000 Subject: [PATCH 14/34] Add a shortfall length limit. This limit allows implementing a timeout: if a TX underrun or RX overrun continues for the specified number of bytes, the M0 will revert to idle. A setting of zero disables the limit. This change adds 5 cycles to the TX & RX shortfall paths, to check if a limit is set and to check the shortfall length against the limit. --- firmware/hackrf_usb/m0_state.h | 1 + firmware/hackrf_usb/sgpio_m0.s | 21 +++++++++++++++++---- host/hackrf-tools/src/hackrf_debug.c | 1 + host/libhackrf/src/hackrf.h | 2 ++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index db79c6db..2ce24980 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -31,6 +31,7 @@ struct m0_state { uint32_t m4_count; uint32_t num_shortfalls; uint32_t longest_shortfall; + uint32_t shortfall_limit; }; enum m0_mode { diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index ec26b2b3..b2189c9c 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -67,9 +67,9 @@ shadow registers. There are four key code paths, with the following worst-case timings: RX, normal: 147 cycles -RX, overrun: 71 cycles +RX, overrun: 76 cycles TX, normal: 133 cycles -TX, underrun: 135 cycles +TX, underrun: 140 cycles Design ====== @@ -113,6 +113,7 @@ registers and fixed memory addresses. .equ M4_COUNT, 0x08 .equ NUM_SHORTFALLS, 0x0C .equ LONGEST_SHORTFALL, 0x10 +.equ SHORTFALL_LIMIT, 0x14 // Operating modes. .equ MODE_IDLE, 0 @@ -174,6 +175,7 @@ main: str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 + str zero, [state, #SHORTFALL_LIMIT] // state.shortfall_limit = zero // 2 idle: // Wait for RX or TX mode to be set. @@ -309,8 +311,19 @@ extend_shortfall: blt loop // goto loop // 1 thru, 3 taken str length, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = length // 2 - // Return to main loop. - b loop // goto loop // 3 + // Is this shortfall long enough to trigger a timeout? + limit .req r1 + ldr limit, [state, #SHORTFALL_LIMIT] // limit = state.shortfall_limit // 2 + cmp limit, #0 // if limit == 0: // 1 + beq loop // goto loop // 1 thru, 3 taken + cmp length, limit // if length < limit: // 1 + blt loop // goto loop // 1 thru, 3 taken + + // If so, reset mode to idle and return to idle loop. + mode .req r3 + mov mode, #MODE_IDLE // mode = MODE_IDLE // 1 + str mode, [state, #MODE] // state.mode = mode // 2 + b idle // goto idle // 3 direction_rx: diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 96895e5d..ee90435e 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -390,6 +390,7 @@ static void print_state(hackrf_m0_state *state) { printf("M4 count: %u bytes\n", state->m4_count); printf("Number of shortfalls: %u\n", state->num_shortfalls); printf("Longest shortfall: %u bytes\n", state->longest_shortfall); + printf("Shortfall limit: %u bytes\n", state->shortfall_limit); } static void usage() { diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index da5dded1..5bd23aed 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -167,6 +167,8 @@ typedef struct { uint32_t num_shortfalls; /** Longest shortfall. */ uint32_t longest_shortfall; + /** Shortfall limit in bytes. */ + uint32_t shortfall_limit; } hackrf_m0_state; struct hackrf_device_list { From 2f79c03b2c5b6d41267d77721de3e6968ae77502 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 09:53:28 +0000 Subject: [PATCH 15/34] hackrf_debug: allow parse_int() to handle 32-bit parameters. --- host/hackrf-tools/src/hackrf_debug.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index ee90435e..0077a3ec 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -36,7 +36,7 @@ typedef int bool; #define REGISTER_INVALID 32767 -int parse_int(char* s, uint16_t* const value) { +int parse_int(char* s, uint32_t* const value) { uint_fast8_t base = 10; char* s_end; long long_value; @@ -56,7 +56,7 @@ int parse_int(char* s, uint16_t* const value) { s_end = s; long_value = strtol(s, &s_end, base); if( (s != s_end) && (*s_end == 0) ) { - *value = (uint16_t)long_value; + *value = (uint32_t)long_value; return HACKRF_SUCCESS; } else { return HACKRF_ERROR_INVALID_PARAM; @@ -431,8 +431,8 @@ static struct option long_options[] = { int main(int argc, char** argv) { int opt; - uint16_t register_number = REGISTER_INVALID; - uint16_t register_value; + uint32_t register_number = REGISTER_INVALID; + uint32_t register_value; hackrf_device* device = NULL; int option_index = 0; bool read = false; @@ -442,7 +442,7 @@ int main(int argc, char** argv) { uint8_t part = PART_NONE; const char* serial_number = NULL; bool set_ui = false; - uint16_t ui_enable; + uint32_t ui_enable; int result = hackrf_init(); if(result) { From 5abc39c53aff9744290fc3bddf0e95dcd685dc8e Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 09:56:52 +0000 Subject: [PATCH 16/34] Add USB requests and host support to set TX/RX shortfall limits. This adds `-T` and `-R` options to `hackrf_debug`, which set the TX underrun and RX overrun limits in bytes. --- firmware/hackrf_usb/hackrf_usb.c | 2 + firmware/hackrf_usb/usb_api_transceiver.c | 28 ++++++++++++ firmware/hackrf_usb/usb_api_transceiver.h | 4 ++ host/hackrf-tools/src/hackrf_debug.c | 39 +++++++++++++++-- host/libhackrf/src/hackrf.c | 52 +++++++++++++++++++++++ host/libhackrf/src/hackrf.h | 2 + 6 files changed, 124 insertions(+), 3 deletions(-) diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index f34318f5..6dc6433a 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -116,6 +116,8 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_operacake_get_mode, usb_vendor_request_operacake_set_dwell_times, usb_vendor_request_get_m0_state, + usb_vendor_request_set_tx_underrun_limit, + usb_vendor_request_set_rx_overrun_limit, }; 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 c5e0a43f..f0f9b137 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -238,6 +238,8 @@ usb_request_status_t usb_vendor_request_set_freq_explicit( } static volatile hw_sync_mode_t _hw_sync_mode = HW_SYNC_MODE_OFF; +static volatile uint32_t _tx_underrun_limit; +static volatile uint32_t _rx_overrun_limit; void set_hw_sync_mode(const hw_sync_mode_t new_hw_sync_mode) { _hw_sync_mode = new_hw_sync_mode; @@ -288,12 +290,14 @@ void transceiver_startup(const transceiver_mode_t mode) { led_on(LED2); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX); m0_state.mode = M0_MODE_RX; + m0_state.shortfall_limit = _rx_overrun_limit; break; case TRANSCEIVER_MODE_TX: led_off(LED2); led_on(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX); m0_state.mode = M0_MODE_TX; + m0_state.shortfall_limit = _tx_underrun_limit; break; default: break; @@ -338,6 +342,30 @@ usb_request_status_t usb_vendor_request_set_hw_sync_mode( } } +usb_request_status_t usb_vendor_request_set_tx_underrun_limit( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage +) { + if( stage == USB_TRANSFER_STAGE_SETUP ) { + uint32_t value = (endpoint->setup.index << 16) + endpoint->setup.value; + _tx_underrun_limit = value; + usb_transfer_schedule_ack(endpoint->in); + } + return USB_REQUEST_STATUS_OK; +} + +usb_request_status_t usb_vendor_request_set_rx_overrun_limit( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage +) { + if( stage == USB_TRANSFER_STAGE_SETUP ) { + uint32_t value = (endpoint->setup.index << 16) + endpoint->setup.value; + _rx_overrun_limit = value; + usb_transfer_schedule_ack(endpoint->in); + } + return USB_REQUEST_STATUS_OK; +} + void transceiver_bulk_transfer_complete(void *user_data, unsigned int bytes_transferred) { (void) user_data; diff --git a/firmware/hackrf_usb/usb_api_transceiver.h b/firmware/hackrf_usb/usb_api_transceiver.h index 126e2f7a..ef865b63 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.h +++ b/firmware/hackrf_usb/usb_api_transceiver.h @@ -62,6 +62,10 @@ usb_request_status_t usb_vendor_request_set_freq_explicit( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); usb_request_status_t usb_vendor_request_set_hw_sync_mode( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); +usb_request_status_t usb_vendor_request_set_tx_underrun_limit( + usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); +usb_request_status_t usb_vendor_request_set_rx_overrun_limit( + 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/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 0077a3ec..38e832bd 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -405,6 +405,8 @@ static void usage() { printf("\t-s, --si5351c: target SI5351C\n"); printf("\t-f, --rffc5072: target RFFC5072\n"); printf("\t-S, --state: display M0 state\n"); + printf("\t-T, --tx-underrun-limit : set TX underrun limit in bytes (0 for no limit)\n"); + printf("\t-R, --rx-overrun-limit : set RX overrun limit in bytes (0 for no limit)\n"); printf("\t-u, --ui <1/0>: enable/disable UI\n"); printf("\nExamples:\n"); printf("\thackrf_debug --si5351c -n 0 -r # reads from si5351c register 0\n"); @@ -425,6 +427,8 @@ static struct option long_options[] = { { "si5351c", no_argument, 0, 's' }, { "rffc5072", no_argument, 0, 'f' }, { "state", no_argument, 0, 'S' }, + { "tx-underrun-limit", required_argument, 0, 'T' }, + { "rx-overrun-limit", required_argument, 0, 'R' }, { "ui", required_argument, 0, 'u' }, { 0, 0, 0, 0 }, }; @@ -443,6 +447,10 @@ int main(int argc, char** argv) { const char* serial_number = NULL; bool set_ui = false; uint32_t ui_enable; + uint32_t tx_limit; + uint32_t rx_limit; + bool set_tx_limit = false; + bool set_rx_limit = false; int result = hackrf_init(); if(result) { @@ -450,7 +458,7 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - while( (opt = getopt_long(argc, argv, "n:rw:d:cmsfSh?u:", long_options, &option_index)) != EOF ) { + while( (opt = getopt_long(argc, argv, "n:rw:d:cmsfST:R:h?u:", long_options, &option_index)) != EOF ) { switch( opt ) { case 'n': result = parse_int(optarg, ®ister_number); @@ -473,6 +481,15 @@ int main(int argc, char** argv) { dump_state = true; break; + case 'T': + set_tx_limit = true; + result = parse_int(optarg, &tx_limit); + break; + case 'R': + set_rx_limit = true; + result = parse_int(optarg, &rx_limit); + break; + case 'd': serial_number = optarg; break; @@ -541,13 +558,13 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - if(!(write || read || dump_config || dump_state || set_ui)) { + if(!(write || read || dump_config || dump_state || set_tx_limit || set_rx_limit || set_ui)) { fprintf(stderr, "Specify read, write, or config option.\n"); usage(); return EXIT_FAILURE; } - if(part == PART_NONE && !set_ui && !dump_state) { + if(part == PART_NONE && !set_ui && !dump_state && !set_tx_limit && !set_rx_limit) { fprintf(stderr, "Specify a part to read, write, or print config from.\n"); usage(); return EXIT_FAILURE; @@ -575,6 +592,22 @@ int main(int argc, char** argv) { si5351c_read_configuration(device); } + if (set_tx_limit) { + result = hackrf_set_tx_underrun_limit(device, tx_limit); + if(result != HACKRF_SUCCESS) { + printf("hackrf_set_tx_underrun_limit() failed: %s (%d)\n", hackrf_error_name(result), result); + return EXIT_FAILURE; + } + } + + if (set_rx_limit) { + result = hackrf_set_rx_overrun_limit(device, rx_limit); + if(result != HACKRF_SUCCESS) { + printf("hackrf_set_rx_overrun_limit() failed: %s (%d)\n", hackrf_error_name(result), result); + return EXIT_FAILURE; + } + } + if(dump_state) { hackrf_m0_state state; result = hackrf_get_m0_state(device, &state); diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 07ac2c70..0ad5557b 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -93,6 +93,8 @@ typedef enum { HACKRF_VENDOR_REQUEST_OPERACAKE_GET_MODE = 39, HACKRF_VENDOR_REQUEST_OPERACAKE_SET_DWELL_TIMES = 40, HACKRF_VENDOR_REQUEST_GET_M0_STATE = 41, + HACKRF_VENDOR_REQUEST_SET_TX_UNDERRUN_LIMIT = 42, + HACKRF_VENDOR_REQUEST_SET_RX_OVERRUN_LIMIT = 43, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -1033,6 +1035,56 @@ int ADDCALL hackrf_get_m0_state(hackrf_device* device, hackrf_m0_state* state) } } +int ADDCALL hackrf_set_tx_underrun_limit(hackrf_device* device, uint32_t value) +{ + USB_API_REQUIRED(device, 0x0106) + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_SET_TX_UNDERRUN_LIMIT, + value & 0xffff, + value >> 16, + NULL, + 0, + 0 + ); + + if( result != 0 ) + { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_set_rx_overrun_limit(hackrf_device* device, uint32_t value) +{ + USB_API_REQUIRED(device, 0x0106) + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_SET_RX_OVERRUN_LIMIT, + value & 0xffff, + value >> 16, + NULL, + 0, + 0 + ); + + if( result != 0 ) + { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + int ADDCALL hackrf_spiflash_erase(hackrf_device* device) { int result; diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 5bd23aed..bd74857d 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -210,6 +210,8 @@ extern ADDAPI int ADDCALL hackrf_start_tx(hackrf_device* device, hackrf_sample_b extern ADDAPI int ADDCALL hackrf_stop_tx(hackrf_device* device); extern ADDAPI int ADDCALL hackrf_get_m0_state(hackrf_device* device, hackrf_m0_state* value); +extern ADDAPI int ADDCALL hackrf_set_tx_underrun_limit(hackrf_device* device, uint32_t value); +extern ADDAPI int ADDCALL hackrf_set_rx_overrun_limit(hackrf_device* device, uint32_t value); /* return HACKRF_TRUE if success */ extern ADDAPI int ADDCALL hackrf_is_streaming(hackrf_device* device); From 00b5ed7d629612e2cc37c8983253d77bb279c46a Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 14:21:35 +0000 Subject: [PATCH 17/34] Add an M0 TX_START mode, in which zeroes are sent until data is ready. In TX_START mode, a lack of data to send is not treated as a shortfall. Zeroes are written to SGPIO, but no shortfall is recorded in the stats. Using this mode helps avoid spurious shortfalls at startup. As soon as there is data to transmit, the M0 switches to TX_RUN mode. This change adds five cycles to the normal TX path, in order to check for TX_START mode before sending data, and to switch to TX_RUN in that case. It also adds two cycles to the TX shortfall path, to check for TX_START mode and skip shortfall processing in that mode. Note the allocation of r3 to store the mode setting, such that this value is still available after the tx_zeros routine. --- firmware/hackrf_usb/m0_state.h | 3 ++- firmware/hackrf_usb/sgpio_m0.s | 24 ++++++++++++++++++----- firmware/hackrf_usb/usb_api_transceiver.c | 2 +- host/hackrf-tools/src/hackrf_debug.c | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index 2ce24980..ef28afaf 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -37,7 +37,8 @@ struct m0_state { enum m0_mode { M0_MODE_IDLE = 0, M0_MODE_RX = 1, - M0_MODE_TX = 2, + M0_MODE_TX_START = 2, + M0_MODE_TX_RUN = 3, }; /* Address of m0_state is set in ldscripts. If you change the name of this diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index b2189c9c..6f4613bb 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -68,8 +68,8 @@ There are four key code paths, with the following worst-case timings: RX, normal: 147 cycles RX, overrun: 76 cycles -TX, normal: 133 cycles -TX, underrun: 140 cycles +TX, normal: 138 cycles +TX, underrun: 142 cycles Design ====== @@ -118,7 +118,8 @@ registers and fixed memory addresses. // Operating modes. .equ MODE_IDLE, 0 .equ MODE_RX, 1 -.equ MODE_TX, 2 +.equ MODE_TX_START, 2 +.equ MODE_TX_RUN, 3 // Our slice chain is set up as follows (ascending data age; arrows are reversed for flow): // L -> F -> K -> C -> J -> E -> I -> A @@ -179,7 +180,7 @@ main: idle: // Wait for RX or TX mode to be set. - mode .req r0 + mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 cmp mode, #MODE_IDLE // if mode == IDLE: // 1 beq idle // goto idle // 1 thru, 3 taken @@ -230,7 +231,7 @@ loop: add buf_ptr, buf_base // buf_ptr += buf_base // 1 // Load mode. - mode .req r0 + mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 // Branch according to mode setting. @@ -255,6 +256,15 @@ direction_tx: sub buf_margin, #32 // buf_margin -= 32 // 1 bmi tx_zeros // if buf_margin < 0: goto tx_zeros // 1 thru, 3 taken + // At this point we know there is TX data available. + // If still in TX start mode, switch to TX run. + cmp mode, #MODE_TX_START // if mode != TX_START: // 1 + bne tx_write // goto tx_write // 1 thru, 3 taken + mov mode, #MODE_TX_RUN // mode = TX_RUN // 1 + str mode, [state, #MODE] // state.mode = mode // 2 + +tx_write: + // Write data to SGPIO. ldm buf_ptr!, {r0-r3} // r0-r3 = buf_ptr[0:16]; buf_ptr += 16 // 5 str r0, [sgpio_data, #SLICE0] // SGPIO_REG_SS[SLICE0] = r0 // 8 @@ -282,6 +292,10 @@ tx_zeros: str zero, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = zero // 8 str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8 + // If in TX start mode, don't count this as a shortfall. + cmp mode, #MODE_TX_START // if mode == TX_START: // 1 + beq loop // goto loop // 1 thru, 3 taken + shortfall: // Get current shortfall length from high register. diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index f0f9b137..03d99747 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -296,7 +296,7 @@ void transceiver_startup(const transceiver_mode_t mode) { led_off(LED2); led_on(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX); - m0_state.mode = M0_MODE_TX; + m0_state.mode = M0_MODE_TX_START; m0_state.shortfall_limit = _tx_underrun_limit; break; default: diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 38e832bd..3d107e06 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -378,7 +378,7 @@ int write_register(hackrf_device* device, uint8_t part, } static void print_state(hackrf_m0_state *state) { - const char *mode_names[] = {"IDLE", "RX", "TX"}; + const char *mode_names[] = {"IDLE", "RX", "TX_START", "TX_RUN"}; const uint32_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]); printf("M0 state:\n"); printf("Mode: %u (%s)\n", From 68688e0ec44d5c1381b4e47b133b986118a5a07c Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 13:03:19 +0000 Subject: [PATCH 18/34] Don't send 16K of zeroes to the M0 at TX startup. The M4 previously buffered 16K of zeroes for the M0 to transmit, whilst waiting for the first USB bulk transfer from the host to complete. The first bulk transfer was placed in the second 16K buffer. This avoided the M0 transmitting uninitialised data, but was not a reliable solution, and delayed the transmission of the first host-supplied samples. Now that the M0 is placed in TX_START mode, this trick is no longer necessary, because the M0 can automatically send zeroes until the first bulk transfer is completed. As such, the first bulk transfer now goes to the first 16K buffer. Once the M4 byte count is increased by the bulk transfer completion callback, the M0 will start transmitting the samples immediately. --- firmware/hackrf_usb/usb_api_transceiver.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 03d99747..403f2946 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -409,22 +409,23 @@ void rx_mode(uint32_t seq) { } void tx_mode(uint32_t seq) { - unsigned int phase = 1; + unsigned int phase = 0; transceiver_startup(TRANSCEIVER_MODE_TX); - memset(&usb_bulk_buffer[0x0000], 0, 0x8000); - // Account for having filled buffer 0. - m0_state.m4_count += 0x4000; - // Set up OUT transfer of buffer 1. + // Set up OUT transfer of buffer 0. usb_transfer_schedule_block( &usb_endpoint_bulk_out, - &usb_bulk_buffer[0x4000], + &usb_bulk_buffer[0x0000], 0x4000, transceiver_bulk_transfer_complete, NULL ); - // Start transmitting zeros while the host fills buffer 1. + + // Enable streaming. The M0 is in TX_START mode, and will automatically + // send zeroes until the host fills buffer 0. Once that buffer is filled, + // the bulk transfer completion handler will increase the M4 count, and + // the M0 will switch to TX_RUN mode and transmit the first data. baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { From 9d570cb558773fe410228093b3ef4c2313452aa4 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 13:40:00 +0000 Subject: [PATCH 19/34] Add macro versions of key parts of M0 code. This commit is separate from the following one which uses the macros, in order to make the diffs easier to read. --- firmware/hackrf_usb/sgpio_m0.s | 113 +++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 6f4613bb..7291363b 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -147,6 +147,119 @@ sgpio_int .req r6 count .req r5 buf_ptr .req r4 +/* Macros */ + +.macro reset_counts + // Reset counts. + // + // Clobbers: + zero .req r0 + + mov zero, #0 // zero = 0 // 1 + str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 + str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 + str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 + str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 + mov shortfall_length, zero // shortfall_length = zero // 1 +.endm + +.macro await_sgpio + // Wait for, then clear, SGPIO exchange interrupt flag. + // + // Clobbers: + int_status .req r0 + scratch .req r1 + + // The worst case timing is assumed to occur when reading the interrupt + // status register *just* misses the flag being set - so we include the + // cycles required to check it a second time. + // + // We also assume that we can spend a full 10 cycles doing an ldr from + // SGPIO the first time (2 for ldr, plus 8 for SGPIO-AHB bus latency), + // and still miss a flag that was set at the start of those 10 cycles. + // + // This latter asssumption is probably slightly pessimistic, since the + // sampling of the flag on the SGPIO side must occur some time after + // the ldr instruction begins executing on the M0. However, we avoid + // relying on any assumptions about the timing details of a read over + // the SGPIO to AHB bridge. + +int_wait: + // Spin on the exchange interrupt status, shifting the slice A flag to the carry flag. + ldr int_status, [sgpio_int, #INT_STATUS] // int_status = SGPIO_STATUS_1 // 10, twice + lsr scratch, int_status, #1 // scratch = int_status >> 1 // 1, twice + bcc int_wait // if !carry: goto int_wait // 3, then 1 + + // Clear the interrupt pending bits that were set. + str int_status, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = int_status // 8 +.endm + +.macro update_buf_ptr + // Update the address of the buffer segment we want to write to / read from. + ldr count, [state, #M0_COUNT] // count = state.m0_count // 2 + mov buf_ptr, buf_mask // buf_ptr = buf_mask // 1 + and buf_ptr, count // buf_ptr &= count // 1 + add buf_ptr, buf_base // buf_ptr += buf_base // 1 +.endm + +.macro update_counts + // Update counts after successful SGPIO operation. + + // Update the byte count and store the new value. + add count, #32 // count += 32 // 1 + str count, [state, #M0_COUNT] // state.m0_count = count // 2 + + // We didn't have a shortfall, so the current shortfall length is zero. + mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1 +.endm + +.macro handle_shortfall + // Handle a shortfall. + // + // Clobbers: + length .req r0 + num .req r1 + longest .req r1 + limit .req r1 + + // Get current shortfall length from high register. + mov length, shortfall_length // length = shortfall_length // 1 + + // Is this a new shortfall? + cmp length, #0 // if length > 0: // 1 + bgt extend_shortfall // goto extend_shortfall // 1 thru, 3 taken + + // If so, increase the shortfall count. + ldr num, [state, #NUM_SHORTFALLS] // num = state.num_shortfalls // 2 + add num, #1 // num += 1 // 1 + str num, [state, #NUM_SHORTFALLS] // state.num_shortfalls = num // 2 + +extend_shortfall: + + // Extend the length of the current shortfall, and store back in high register. + add length, #32 // length += 32 // 1 + mov shortfall_length, length // shortfall_length = length // 1 + + // Is this now the longest shortfall? + ldr longest, [state, #LONGEST_SHORTFALL] // longest = state.longest_shortfall // 2 + cmp length, longest // if length <= longest: // 1 + blt loop // goto loop // 1 thru, 3 taken + str length, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = length // 2 + + // Is this shortfall long enough to trigger a timeout? + ldr limit, [state, #SHORTFALL_LIMIT] // limit = state.shortfall_limit // 2 + cmp limit, #0 // if limit == 0: // 1 + beq loop // goto loop // 1 thru, 3 taken + cmp length, limit // if length < limit: // 1 + blt loop // goto loop // 1 thru, 3 taken + + // If so, reset mode to idle and return to idle loop. + mode .req r3 + mov mode, #MODE_IDLE // mode = MODE_IDLE // 1 + str mode, [state, #MODE] // state.mode = mode // 2 + b idle // goto idle // 3 +.endm + // Entry point. At this point, the libopencm3 startup code has set things up as // normal; .data and .bss are initialised, the stack is set up, etc. However, // we don't actually use any of that. All the code in this file would work From f08e0c17bf03fc507c33463542b93445a8488db1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 13:46:45 +0000 Subject: [PATCH 20/34] Use new macros in M0 code. This commit is separate from the previous one which adds the macros, in order to make the diffs easier to read. --- firmware/hackrf_usb/sgpio_m0.s | 95 +++------------------------------- 1 file changed, 7 insertions(+), 88 deletions(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 7291363b..b5d70640 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -299,49 +299,14 @@ idle: beq idle // goto idle // 1 thru, 3 taken // Reset counts. - mov zero, #0 // zero = 0 // 1 - str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 - str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 - str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 - str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 - mov shortfall_length, zero // shortfall_length = zero // 1 + reset_counts // reset_counts() // 10 loop: - // The worst case timing is assumed to occur when reading the interrupt - // status register *just* misses the flag being set - so we include the - // cycles required to check it a second time. - // - // We also assume that we can spend a full 10 cycles doing an ldr from - // SGPIO the first time (2 for ldr, plus 8 for SGPIO-AHB bus latency), - // and still miss a flag that was set at the start of those 10 cycles. - // - // This latter asssumption is probably slightly pessimistic, since the - // sampling of the flag on the SGPIO side must occur some time after - // the ldr instruction begins executing on the M0. However, we avoid - // relying on any assumptions about the timing details of a read over - // the SGPIO to AHB bridge. + // Wait for and clear SGPIO interrupt. + await_sgpio // await_sgpio() // 34 - int_status .req r0 - scratch .req r1 - - // Spin until we're ready to handle an SGPIO packet: - // Grab the exchange interrupt status... - ldr int_status, [sgpio_int, #INT_STATUS] // int_status = SGPIO_STATUS_1 // 10, twice - - // ... check to see if bit #0 (slice A) was set, by shifting it into the carry bit... - lsr scratch, int_status, #1 // scratch = int_status >> 1 // 1, twice - - // ... and if not, jump back to the beginning. - bcc loop // if !carry: goto loop // 3, then 1 - - // Clear the interrupt pending bits that were set. - str int_status, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = int_status // 8 - - // ... and grab the address of the buffer segment we want to write to / read from. - ldr count, [state, #M0_COUNT] // count = state.m0_count // 2 - mov buf_ptr, buf_mask // buf_ptr = buf_mask // 1 - and buf_ptr, count // buf_ptr &= count // 1 - add buf_ptr, buf_base // buf_ptr += buf_base // 1 + // Update buffer pointer. + update_buf_ptr // update_buf_ptr() // 5 // Load mode. mode .req r3 @@ -411,46 +376,7 @@ tx_zeros: shortfall: - // Get current shortfall length from high register. - length .req r0 - mov length, shortfall_length // length = shortfall_length // 1 - - // Is this a new shortfall? - cmp length, #0 // if length > 0: // 1 - bgt extend_shortfall // goto extend_shortfall // 1 thru, 3 taken - - // If so, increase the shortfall count. - num .req r1 - ldr num, [state, #NUM_SHORTFALLS] // num = state.num_shortfalls // 2 - add num, #1 // num += 1 // 1 - str num, [state, #NUM_SHORTFALLS] // state.num_shortfalls = num // 2 - -extend_shortfall: - - // Extend the length of the current shortfall, and store back in high register. - add length, #32 // length += 32 // 1 - mov shortfall_length, length // shortfall_length = length // 1 - - // Is this now the longest shortfall? - longest .req r1 - ldr longest, [state, #LONGEST_SHORTFALL] // longest = state.longest_shortfall // 2 - cmp length, longest // if length <= longest: // 1 - blt loop // goto loop // 1 thru, 3 taken - str length, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = length // 2 - - // Is this shortfall long enough to trigger a timeout? - limit .req r1 - ldr limit, [state, #SHORTFALL_LIMIT] // limit = state.shortfall_limit // 2 - cmp limit, #0 // if limit == 0: // 1 - beq loop // goto loop // 1 thru, 3 taken - cmp length, limit // if length < limit: // 1 - blt loop // goto loop // 1 thru, 3 taken - - // If so, reset mode to idle and return to idle loop. - mode .req r3 - mov mode, #MODE_IDLE // mode = MODE_IDLE // 1 - str mode, [state, #MODE] // state.mode = mode // 2 - b idle // goto idle // 3 + handle_shortfall // handle_shortfall() // 24 direction_rx: @@ -486,14 +412,7 @@ direction_rx: stm buf_ptr!, {r0-r3} // buf_ptr[0:16] = r0-r3; buf_ptr += 16 // 5 done: - // Finally, update the count... - add count, #32 // count += 32 // 1 - - // ... and store the new count. - str count, [state, #M0_COUNT] // state.m0_count = count // 2 - - // We didn't have a shortfall, so the current shortfall length is zero. - mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1 + update_counts // update_counts() // 4 b loop // goto loop // 3 From 4e205994e34e1e9c2ef34228ee92579247553b0f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 14:08:22 +0000 Subject: [PATCH 21/34] Use separate loops for RX and TX modes. Using our newly-defined macros, it's now straightforward to write separate loops for RX and TX, with the idle loop dispatching to them when a new mode setting is written by the M4. This saves some cycles by reducing branches needed within each loop, and makes it simpler to add new modes. For macros which use internal labels, a name parameter is added. This parameter is prefixed to the labels used, so that each mode's use of that macro produces its own label names. Similarly, where branches were taken in the handle_shortfall macro to the "loop" label, these are replaced with the appropriate tx_loop or rx_loop label. The syntax `\name\()_suffix` is necessary to perform concatenation in the GNU assembler. --- firmware/hackrf_usb/sgpio_m0.s | 92 ++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index b5d70640..d5b10e93 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -66,10 +66,10 @@ shadow registers. There are four key code paths, with the following worst-case timings: -RX, normal: 147 cycles -RX, overrun: 76 cycles -TX, normal: 138 cycles -TX, underrun: 142 cycles +RX, normal: 145 cycles +RX, overrun: 74 cycles +TX, normal: 134 cycles +TX, underrun: 141 cycles Design ====== @@ -163,7 +163,7 @@ buf_ptr .req r4 mov shortfall_length, zero // shortfall_length = zero // 1 .endm -.macro await_sgpio +.macro await_sgpio name // Wait for, then clear, SGPIO exchange interrupt flag. // // Clobbers: @@ -184,11 +184,11 @@ buf_ptr .req r4 // relying on any assumptions about the timing details of a read over // the SGPIO to AHB bridge. -int_wait: +\name\()_int_wait: // Spin on the exchange interrupt status, shifting the slice A flag to the carry flag. ldr int_status, [sgpio_int, #INT_STATUS] // int_status = SGPIO_STATUS_1 // 10, twice lsr scratch, int_status, #1 // scratch = int_status >> 1 // 1, twice - bcc int_wait // if !carry: goto int_wait // 3, then 1 + bcc \name\()_int_wait // if !carry: goto int_wait // 3, then 1 // Clear the interrupt pending bits that were set. str int_status, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = int_status // 8 @@ -213,7 +213,7 @@ int_wait: mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1 .endm -.macro handle_shortfall +.macro handle_shortfall name // Handle a shortfall. // // Clobbers: @@ -227,14 +227,14 @@ int_wait: // Is this a new shortfall? cmp length, #0 // if length > 0: // 1 - bgt extend_shortfall // goto extend_shortfall // 1 thru, 3 taken + bgt \name\()_extend_shortfall // goto extend_shortfall // 1 thru, 3 taken // If so, increase the shortfall count. ldr num, [state, #NUM_SHORTFALLS] // num = state.num_shortfalls // 2 add num, #1 // num += 1 // 1 str num, [state, #NUM_SHORTFALLS] // state.num_shortfalls = num // 2 -extend_shortfall: +\name\()_extend_shortfall: // Extend the length of the current shortfall, and store back in high register. add length, #32 // length += 32 // 1 @@ -243,15 +243,15 @@ extend_shortfall: // Is this now the longest shortfall? ldr longest, [state, #LONGEST_SHORTFALL] // longest = state.longest_shortfall // 2 cmp length, longest // if length <= longest: // 1 - blt loop // goto loop // 1 thru, 3 taken + blt \name\()_loop // goto loop // 1 thru, 3 taken str length, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = length // 2 // Is this shortfall long enough to trigger a timeout? ldr limit, [state, #SHORTFALL_LIMIT] // limit = state.shortfall_limit // 2 cmp limit, #0 // if limit == 0: // 1 - beq loop // goto loop // 1 thru, 3 taken + beq \name\()_loop // goto loop // 1 thru, 3 taken cmp length, limit // if length < limit: // 1 - blt loop // goto loop // 1 thru, 3 taken + blt \name\()_loop // goto loop // 1 thru, 3 taken // If so, reset mode to idle and return to idle loop. mode .req r3 @@ -295,29 +295,29 @@ idle: // Wait for RX or TX mode to be set. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 - cmp mode, #MODE_IDLE // if mode == IDLE: // 1 - beq idle // goto idle // 1 thru, 3 taken + cmp mode, #MODE_RX // if mode == RX: // 1 + beq rx_start // goto rx_start // 1 thru, 3 taken + bgt tx_start // elif mode > RX: goto tx_start // 1 thru, 3 taken + b idle // goto idle // 3 + +tx_start: // Reset counts. reset_counts // reset_counts() // 10 -loop: +tx_loop: + // Wait for and clear SGPIO interrupt. - await_sgpio // await_sgpio() // 34 + await_sgpio tx // await_sgpio() // 34 // Update buffer pointer. update_buf_ptr // update_buf_ptr() // 5 - // Load mode. + // Load mode, and return to idle if requested. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 - - // Branch according to mode setting. - cmp mode, #MODE_RX // if mode == RX: // 1 - beq direction_rx // goto direction_rx // 1 thru, 3 taken - blt idle // elif mode < RX: goto idle // 1 thru, 3 taken - -direction_tx: + cmp mode, #MODE_IDLE // if mode == IDLE: // 1 + beq idle // goto idle // 1 thru, 3 taken // Check if there is enough data in the buffer. // @@ -355,7 +355,11 @@ tx_write: str r2, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = r2 // 8 str r3, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = r3 // 8 - b done // goto done // 3 + // Update counts. + update_counts // update_counts() // 4 + + // Jump back to TX loop start. + b tx_loop // goto tx_loop // 3 tx_zeros: @@ -372,13 +376,29 @@ tx_zeros: // If in TX start mode, don't count this as a shortfall. cmp mode, #MODE_TX_START // if mode == TX_START: // 1 - beq loop // goto loop // 1 thru, 3 taken + beq tx_loop // goto tx_loop // 1 thru, 3 taken -shortfall: + // Run common shortfall handling and jump back to TX loop start. + handle_shortfall tx // handle_shortfall() // 24 - handle_shortfall // handle_shortfall() // 24 +rx_start: -direction_rx: + // Reset counts. + reset_counts // reset_counts() // 10 + +rx_loop: + + // Wait for and clear SGPIO interrupt. + await_sgpio rx // await_sgpio() // 34 + + // Update buffer pointer. + update_buf_ptr // update_buf_ptr() // 5 + + // Load mode, and return to idle if requested. + mode .req r3 + ldr mode, [state, #MODE] // mode = state.mode // 2 + cmp mode, #MODE_IDLE // if mode == IDLE: // 1 + beq idle // goto idle // 1 thru, 3 taken // Check if there is enough space in the buffer. // @@ -397,7 +417,7 @@ direction_rx: ldr buf_margin, [state, #M4_COUNT] // buf_margin = state.m4_count // 2 add buf_margin, buf_size_minus_32 // buf_margin += buf_size_minus_32 // 1 sub buf_margin, count // buf_margin -= count // 1 - bmi shortfall // if buf_margin < 0: goto shortfall // 1 thru, 3 taken + bmi rx_shortfall // if buf_margin < 0: goto rx_shortfall // 1 thru, 3 taken // Read data from SGPIO. ldr r0, [sgpio_data, #SLICE0] // r0 = SGPIO_REG_SS[SLICE0] // 10 @@ -411,10 +431,16 @@ direction_rx: ldr r3, [sgpio_data, #SLICE7] // r3 = SGPIO_REG_SS[SLICE7] // 10 stm buf_ptr!, {r0-r3} // buf_ptr[0:16] = r0-r3; buf_ptr += 16 // 5 -done: + // Update counts. update_counts // update_counts() // 4 - b loop // goto loop // 3 + // Jump back to RX loop start. + b rx_loop // goto rx_loop // 3 + +rx_shortfall: + + // Run common shortfall handling and jump back to RX loop. + handle_shortfall rx // handle_shortfall() // 24 // The linker will put a literal pool here, so add a label for clearer objdump output: constants: From 0e99419be28c249b5bf9ad3781353098d8d3f0df Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 14:50:22 +0000 Subject: [PATCH 22/34] Don't load M0 byte count from memory. This count is only written by the M0, so there's no need to reload it when the current value is already retained in a register. Removing this load saves two cycles in all code paths. --- firmware/hackrf_usb/sgpio_m0.s | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index d5b10e93..bb710936 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -66,10 +66,10 @@ shadow registers. There are four key code paths, with the following worst-case timings: -RX, normal: 145 cycles -RX, overrun: 74 cycles -TX, normal: 134 cycles -TX, underrun: 141 cycles +RX, normal: 143 cycles +RX, overrun: 72 cycles +TX, normal: 132 cycles +TX, underrun: 139 cycles Design ====== @@ -161,6 +161,7 @@ buf_ptr .req r4 str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 mov shortfall_length, zero // shortfall_length = zero // 1 + mov count, zero // count = zero // 1 .endm .macro await_sgpio name @@ -196,7 +197,6 @@ buf_ptr .req r4 .macro update_buf_ptr // Update the address of the buffer segment we want to write to / read from. - ldr count, [state, #M0_COUNT] // count = state.m0_count // 2 mov buf_ptr, buf_mask // buf_ptr = buf_mask // 1 and buf_ptr, count // buf_ptr &= count // 1 add buf_ptr, buf_base // buf_ptr += buf_base // 1 @@ -311,7 +311,7 @@ tx_loop: await_sgpio tx // await_sgpio() // 34 // Update buffer pointer. - update_buf_ptr // update_buf_ptr() // 5 + update_buf_ptr // update_buf_ptr() // 3 // Load mode, and return to idle if requested. mode .req r3 @@ -392,7 +392,7 @@ rx_loop: await_sgpio rx // await_sgpio() // 34 // Update buffer pointer. - update_buf_ptr // update_buf_ptr() // 5 + update_buf_ptr // update_buf_ptr() // 3 // Load mode, and return to idle if requested. mode .req r3 From a5e1521535e60ef17793e0bb4e565d8bd626bcd4 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 29 Dec 2021 23:13:10 +0000 Subject: [PATCH 23/34] Don't update buffer pointer until after checking for shortfall. The buffer pointer is not needed in the shortfall paths. Moving this update after the shortfall checks saves 3 cycles in each shortfall path. --- firmware/hackrf_usb/sgpio_m0.s | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index bb710936..d7a58699 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -67,9 +67,9 @@ shadow registers. There are four key code paths, with the following worst-case timings: RX, normal: 143 cycles -RX, overrun: 72 cycles +RX, overrun: 69 cycles TX, normal: 132 cycles -TX, underrun: 139 cycles +TX, underrun: 136 cycles Design ====== @@ -310,9 +310,6 @@ tx_loop: // Wait for and clear SGPIO interrupt. await_sgpio tx // await_sgpio() // 34 - // Update buffer pointer. - update_buf_ptr // update_buf_ptr() // 3 - // Load mode, and return to idle if requested. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 @@ -334,6 +331,9 @@ tx_loop: sub buf_margin, #32 // buf_margin -= 32 // 1 bmi tx_zeros // if buf_margin < 0: goto tx_zeros // 1 thru, 3 taken + // Update buffer pointer. + update_buf_ptr // update_buf_ptr() // 3 + // At this point we know there is TX data available. // If still in TX start mode, switch to TX run. cmp mode, #MODE_TX_START // if mode != TX_START: // 1 @@ -391,9 +391,6 @@ rx_loop: // Wait for and clear SGPIO interrupt. await_sgpio rx // await_sgpio() // 34 - // Update buffer pointer. - update_buf_ptr // update_buf_ptr() // 3 - // Load mode, and return to idle if requested. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 @@ -419,6 +416,9 @@ rx_loop: sub buf_margin, count // buf_margin -= count // 1 bmi rx_shortfall // if buf_margin < 0: goto rx_shortfall // 1 thru, 3 taken + // Update buffer pointer. + update_buf_ptr // update_buf_ptr() // 3 + // Read data from SGPIO. ldr r0, [sgpio_data, #SLICE0] // r0 = SGPIO_REG_SS[SLICE0] // 10 ldr r1, [sgpio_data, #SLICE1] // r1 = SGPIO_REG_SS[SLICE1] // 10 From 7124b7192b0507a51b925be1e8f625e5a8689ea2 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 29 Dec 2021 00:14:07 +0000 Subject: [PATCH 24/34] Roll back shortfall stats if switched to idle in a shortfall. During shutdown of TX or RX, the host may stop supplying or retrieving sample data some time before a stop request causes the M0 to be set back to idle mode. This makes it common for a spurious shortfall to occur during shutdown, giving the misleading impression that there has been a throughput problem. In fact, the final shortfall is simply an artifact. This commit detects when this happens, and excludes the spurious shortfall from the stats. To implement this, we back up the shortfall stats whenever a new shortfall begins. If the new shortfall later turns out to be spurious, as indicated by a transition to IDLE while it is ongoing, then we roll back the stats to their previous values. We actually only need to back up previous longest shortfall length. To get a previous shortfall count, can simply to subtract one from the current shortfall count. This change adds four cycles to the two shortfall paths - a load and store to back up the previous longest shortfall length. --- firmware/hackrf_usb/sgpio_m0.s | 44 +++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index d7a58699..9ae89018 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -67,9 +67,9 @@ shadow registers. There are four key code paths, with the following worst-case timings: RX, normal: 143 cycles -RX, overrun: 69 cycles +RX, overrun: 73 cycles TX, normal: 132 cycles -TX, underrun: 136 cycles +TX, underrun: 140 cycles Design ====== @@ -115,6 +115,9 @@ registers and fixed memory addresses. .equ LONGEST_SHORTFALL, 0x10 .equ SHORTFALL_LIMIT, 0x14 +// Private variables stored after state. +.equ PREV_LONGEST_SHORTFALL, 0x20 + // Operating modes. .equ MODE_IDLE, 0 .equ MODE_RX, 1 @@ -160,6 +163,7 @@ buf_ptr .req r4 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 + str zero, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = zero // 2 mov shortfall_length, zero // shortfall_length = zero // 1 mov count, zero // count = zero // 1 .endm @@ -234,6 +238,11 @@ buf_ptr .req r4 add num, #1 // num += 1 // 1 str num, [state, #NUM_SHORTFALLS] // state.num_shortfalls = num // 2 + // Back up previous longest shortfall. + prev .req r0 + ldr prev, [state, #LONGEST_SHORTFALL] // prev = state.longest_shortfall // 2 + str prev, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = prev // 2 + \name\()_extend_shortfall: // Extend the length of the current shortfall, and store back in high register. @@ -300,6 +309,27 @@ idle: bgt tx_start // elif mode > RX: goto tx_start // 1 thru, 3 taken b idle // goto idle // 3 +checked_rollback: + // Checked rollback handler. This code is run when the M0 is in a TX or RX mode, and is + // placed back into IDLE mode by the M4. If there is an ongoing shortfall at this point, + // it is assumed to be a shutdown artifact and rolled back. + + // If there is no ongoing shortfall, there's nothing to do - jump back to idle loop. + length .req r0 + mov length, shortfall_length // length = shortfall_length // 1 + cmp length, #0 // if length == 0: // 1 + beq idle // goto idle // 3 + + // Otherwise, roll back the state to ignore the current shortfall, then jump to idle. + prev .req r0 + ldr prev, [state, #PREV_LONGEST_SHORTFALL] // prev = prev_longest_shortfall // 2 + str prev, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = prev // 2 + ldr prev, [state, #NUM_SHORTFALLS] // prev = num_shortfalls // 2 + sub prev, #1 // prev -= 1 // 1 + str prev, [state, #NUM_SHORTFALLS] // state.num_shortfalls = prev // 2 + + b idle // goto idle // 3 + tx_start: // Reset counts. @@ -310,11 +340,12 @@ tx_loop: // Wait for and clear SGPIO interrupt. await_sgpio tx // await_sgpio() // 34 - // Load mode, and return to idle if requested. + // Check if a return to idle mode was requested. + // If so, we may need to roll back shortfall stats. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 cmp mode, #MODE_IDLE // if mode == IDLE: // 1 - beq idle // goto idle // 1 thru, 3 taken + beq checked_rollback // goto checked_rollback // 1 thru, 3 taken // Check if there is enough data in the buffer. // @@ -391,11 +422,12 @@ rx_loop: // Wait for and clear SGPIO interrupt. await_sgpio rx // await_sgpio() // 34 - // Load mode, and return to idle if requested. + // Check if a return to idle mode was requested. + // If so, we may need to roll back shortfall stats. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 cmp mode, #MODE_IDLE // if mode == IDLE: // 1 - beq idle // goto idle // 1 thru, 3 taken + beq checked_rollback // goto checked_rollback // 1 thru, 3 taken // Check if there is enough space in the buffer. // From 3618a5352f04ee30f6a3de8b459bc788ad98e293 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 15:27:14 +0000 Subject: [PATCH 25/34] Add a counter threshold at which the M0 will change to a new mode. This lays the groundwork for implementing timed operations (#86). The M0 can be configured to automatically change modes when its byte count reaches a specific value. Checking the counter against the threshold and dispatching to the next mode is handled by a new `jump_next_mode` macro, which replaces the unconditional branches back to the start of the TX and RX loops. Making this change work requires some rearrangement of the code, such that the destinations of all conditional branch instructions are within reach. These branch instructions (`b[cond] label`) have a range of -256 to +254 bytes from the current program counter. For this reason, the TX shortfall handling is moved earlier in the file, and branches in the idle loop are restructured to use an unconditional branch to rx_start, which is furthest away. The additional code for switching modes adds 9 cycles to the normal RX path, and 10 to the TX path (the difference is because the dispatch in `jump_next_mode` is optimised for the longer RX path). --- firmware/hackrf_usb/m0_state.h | 2 + firmware/hackrf_usb/sgpio_m0.s | 85 +++++++++++++++-------- firmware/hackrf_usb/usb_api_transceiver.c | 15 +++- host/hackrf-tools/src/hackrf_debug.c | 17 +++-- host/libhackrf/src/hackrf.h | 4 ++ 5 files changed, 85 insertions(+), 38 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index ef28afaf..b0c558ea 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -32,6 +32,8 @@ struct m0_state { uint32_t num_shortfalls; uint32_t longest_shortfall; uint32_t shortfall_limit; + uint32_t threshold; + uint32_t next_mode; }; enum m0_mode { diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 9ae89018..2a5eb31a 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -66,9 +66,9 @@ shadow registers. There are four key code paths, with the following worst-case timings: -RX, normal: 143 cycles +RX, normal: 152 cycles RX, overrun: 73 cycles -TX, normal: 132 cycles +TX, normal: 142 cycles TX, underrun: 140 cycles Design @@ -114,6 +114,8 @@ registers and fixed memory addresses. .equ NUM_SHORTFALLS, 0x0C .equ LONGEST_SHORTFALL, 0x10 .equ SHORTFALL_LIMIT, 0x14 +.equ THRESHOLD, 0x18 +.equ NEXT_MODE, 0x1C // Private variables stored after state. .equ PREV_LONGEST_SHORTFALL, 0x20 @@ -217,6 +219,29 @@ buf_ptr .req r4 mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1 .endm +.macro jump_next_mode name + // Jump to next mode if the byte count threshold has been reached. + // + // Clobbers: + threshold .req r0 + new_mode .req r1 + + // Check count against threshold. If not a match, return to start of current loop. + ldr threshold, [state, #THRESHOLD] // threshold = state.threshold // 2 + cmp count, threshold // if count != threshold: // 1 + bne \name\()_loop // goto loop // 1 thru, 3 taken + + // Otherwise, load and set new mode. + ldr new_mode, [state, #NEXT_MODE] // new_mode = state.next_mode // 2 + str new_mode, [state, #MODE] // state.mode = new_mode // 2 + + // Branch according to new mode. + cmp new_mode, #MODE_RX // if new_mode == RX: // 1 + beq rx_loop // goto rx_loop // 1 thru, 3 taken + bgt tx_loop // elif new_mode > RX: goto tx_loop // 1 thru, 3 taken + b idle // goto idle // 3 +.endm + .macro handle_shortfall name // Handle a shortfall. // @@ -299,15 +324,37 @@ main: str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 str zero, [state, #SHORTFALL_LIMIT] // state.shortfall_limit = zero // 2 + str zero, [state, #THRESHOLD] // state.threshold = zero // 2 + str zero, [state, #NEXT_MODE] // state.next_mode = zero // 2 idle: // Wait for RX or TX mode to be set. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 cmp mode, #MODE_RX // if mode == RX: // 1 - beq rx_start // goto rx_start // 1 thru, 3 taken - bgt tx_start // elif mode > RX: goto tx_start // 1 thru, 3 taken - b idle // goto idle // 3 + bgt tx_start // if mode > RX: goto tx_start // 1 thru, 3 taken + blt idle // elif mode < RX: goto idle // 1 thru, 3 taken + b rx_start // goto rx_start // 3 + +tx_zeros: + + // Write zeros to SGPIO. + mov zero, #0 // zero = 0 // 1 + str zero, [sgpio_data, #SLICE0] // SGPIO_REG_SS[SLICE0] = zero // 8 + str zero, [sgpio_data, #SLICE1] // SGPIO_REG_SS[SLICE1] = zero // 8 + str zero, [sgpio_data, #SLICE2] // SGPIO_REG_SS[SLICE2] = zero // 8 + str zero, [sgpio_data, #SLICE3] // SGPIO_REG_SS[SLICE3] = zero // 8 + str zero, [sgpio_data, #SLICE4] // SGPIO_REG_SS[SLICE4] = zero // 8 + str zero, [sgpio_data, #SLICE5] // SGPIO_REG_SS[SLICE5] = zero // 8 + str zero, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = zero // 8 + str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8 + + // If in TX start mode, don't count this as a shortfall. + cmp mode, #MODE_TX_START // if mode == TX_START: // 1 + beq tx_loop // goto tx_loop // 1 thru, 3 taken + + // Run common shortfall handling and jump back to TX loop start. + handle_shortfall tx // handle_shortfall() // 24 checked_rollback: // Checked rollback handler. This code is run when the M0 is in a TX or RX mode, and is @@ -389,28 +436,8 @@ tx_write: // Update counts. update_counts // update_counts() // 4 - // Jump back to TX loop start. - b tx_loop // goto tx_loop // 3 - -tx_zeros: - - // Write zeros to SGPIO. - mov zero, #0 // zero = 0 // 1 - str zero, [sgpio_data, #SLICE0] // SGPIO_REG_SS[SLICE0] = zero // 8 - str zero, [sgpio_data, #SLICE1] // SGPIO_REG_SS[SLICE1] = zero // 8 - str zero, [sgpio_data, #SLICE2] // SGPIO_REG_SS[SLICE2] = zero // 8 - str zero, [sgpio_data, #SLICE3] // SGPIO_REG_SS[SLICE3] = zero // 8 - str zero, [sgpio_data, #SLICE4] // SGPIO_REG_SS[SLICE4] = zero // 8 - str zero, [sgpio_data, #SLICE5] // SGPIO_REG_SS[SLICE5] = zero // 8 - str zero, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = zero // 8 - str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8 - - // If in TX start mode, don't count this as a shortfall. - cmp mode, #MODE_TX_START // if mode == TX_START: // 1 - beq tx_loop // goto tx_loop // 1 thru, 3 taken - - // Run common shortfall handling and jump back to TX loop start. - handle_shortfall tx // handle_shortfall() // 24 + // Jump to next mode if threshold reached, or back to TX loop start. + jump_next_mode tx // jump_next_mode() // 13 rx_start: @@ -466,8 +493,8 @@ rx_loop: // Update counts. update_counts // update_counts() // 4 - // Jump back to RX loop start. - b rx_loop // goto rx_loop // 3 + // Jump to next mode if threshold reached, or back to RX loop start. + jump_next_mode rx // jump_next_mode() // 12 rx_shortfall: diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 403f2946..7048e8bd 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -260,6 +260,15 @@ void request_transceiver_mode(transceiver_mode_t mode) transceiver_request.seq++; } +static void set_m0_mode(enum m0_mode mode) { + // All values of the threshold setting are valid. So setting a mode, + // it's necessary to set up a transition back to the same mode, for + // when the M0 counter wraps. + m0_state.mode = mode; + m0_state.next_mode = mode; + m0_state.threshold = 0; +} + void transceiver_shutdown(void) { baseband_streaming_disable(&sgpio_config); @@ -271,7 +280,7 @@ void transceiver_shutdown(void) led_off(LED2); led_off(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF); - m0_state.mode = M0_MODE_IDLE; + set_m0_mode(M0_MODE_IDLE); // The M0 may already be waiting for the next SGPIO // exchange interrupt. In order to ensure that the M0 // switches over to its idle loop, we need to set the @@ -289,14 +298,14 @@ void transceiver_startup(const transceiver_mode_t mode) { led_off(LED3); led_on(LED2); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX); - m0_state.mode = M0_MODE_RX; + set_m0_mode(M0_MODE_RX); m0_state.shortfall_limit = _rx_overrun_limit; break; case TRANSCEIVER_MODE_TX: led_off(LED2); led_on(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX); - m0_state.mode = M0_MODE_TX_START; + set_m0_mode(M0_MODE_TX_START); m0_state.shortfall_limit = _tx_underrun_limit; break; default: diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 3d107e06..c1f1b90c 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -377,20 +377,25 @@ int write_register(hackrf_device* device, uint8_t part, return HACKRF_ERROR_INVALID_PARAM; } -static void print_state(hackrf_m0_state *state) { +static const char * mode_name(uint32_t mode) { const char *mode_names[] = {"IDLE", "RX", "TX_START", "TX_RUN"}; const uint32_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]); + if (mode < num_modes) + return mode_names[mode]; + else + return "UNKNOWN"; +} + +static void print_state(hackrf_m0_state *state) { printf("M0 state:\n"); - printf("Mode: %u (%s)\n", - state->mode, - state->mode < num_modes ? - mode_names[state->mode] : - "UNKNOWN"); + printf("Mode: %u (%s)\n", state->mode, mode_name(state->mode)); printf("M0 count: %u bytes\n", state->m0_count); printf("M4 count: %u bytes\n", state->m4_count); printf("Number of shortfalls: %u\n", state->num_shortfalls); printf("Longest shortfall: %u bytes\n", state->longest_shortfall); printf("Shortfall limit: %u bytes\n", state->shortfall_limit); + printf("Mode change threshold: %u bytes\n", state->threshold); + printf("Next mode: %u (%s)\n", state->mode, mode_name(state->mode)); } static void usage() { diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index bd74857d..8bd7bd78 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -169,6 +169,10 @@ typedef struct { uint32_t longest_shortfall; /** Shortfall limit in bytes. */ uint32_t shortfall_limit; + /** Threshold m0_count value for next mode change. */ + uint32_t threshold; + /** Mode which will be switched to when threshold is reached. */ + uint32_t next_mode; } hackrf_m0_state; struct hackrf_device_list { From cca7320fe499d0f09237aa157cc3c6ad40a79bef Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 15:55:49 +0000 Subject: [PATCH 26/34] Add a wait mode for the M0. In wait mode, the byte counter is advanced, but no SGPIO read/writes are done. This mode is intended to be used for implementing timed operations. --- firmware/hackrf_usb/m0_state.h | 7 +++-- firmware/hackrf_usb/sgpio_m0.s | 41 +++++++++++++++++++++++----- host/hackrf-tools/src/hackrf_debug.c | 2 +- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index b0c558ea..ca6f057d 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -38,9 +38,10 @@ struct m0_state { enum m0_mode { M0_MODE_IDLE = 0, - M0_MODE_RX = 1, - M0_MODE_TX_START = 2, - M0_MODE_TX_RUN = 3, + M0_MODE_WAIT = 1, + M0_MODE_RX = 2, + M0_MODE_TX_START = 3, + M0_MODE_TX_RUN = 4, }; /* Address of m0_state is set in ldscripts. If you change the name of this diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 2a5eb31a..646738ca 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -122,9 +122,10 @@ registers and fixed memory addresses. // Operating modes. .equ MODE_IDLE, 0 -.equ MODE_RX, 1 -.equ MODE_TX_START, 2 -.equ MODE_TX_RUN, 3 +.equ MODE_WAIT, 1 +.equ MODE_RX, 2 +.equ MODE_TX_START, 3 +.equ MODE_TX_RUN, 4 // Our slice chain is set up as follows (ascending data age; arrows are reversed for flow): // L -> F -> K -> C -> J -> E -> I -> A @@ -239,6 +240,8 @@ buf_ptr .req r4 cmp new_mode, #MODE_RX // if new_mode == RX: // 1 beq rx_loop // goto rx_loop // 1 thru, 3 taken bgt tx_loop // elif new_mode > RX: goto tx_loop // 1 thru, 3 taken + cmp new_mode, #MODE_WAIT // if new_mode == WAIT: // 1 + beq wait_loop // goto wait_loop // 1 thru, 3 taken b idle // goto idle // 3 .endm @@ -331,10 +334,12 @@ idle: // Wait for RX or TX mode to be set. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 - cmp mode, #MODE_RX // if mode == RX: // 1 - bgt tx_start // if mode > RX: goto tx_start // 1 thru, 3 taken - blt idle // elif mode < RX: goto idle // 1 thru, 3 taken - b rx_start // goto rx_start // 3 + cmp mode, #MODE_WAIT // if mode < WAIT: // 1 + blt idle // goto idle // 1 thru, 3 taken + beq wait_start // elif mode == WAIT: goto wait_start // 1 thru, 3 taken + cmp mode, #MODE_RX // if mode > RX: // 1 + bgt tx_start // goto tx_start // 1 thru, 3 taken + b rx_start // goto rx_start // 3 tx_zeros: @@ -439,6 +444,28 @@ tx_write: // Jump to next mode if threshold reached, or back to TX loop start. jump_next_mode tx // jump_next_mode() // 13 +wait_start: + + // Reset counts. + reset_counts // reset_counts() // 10 + +wait_loop: + + // Wait for and clear SGPIO interrupt. + await_sgpio wait // await_sgpio() // 34 + + // Load mode, and return to idle if requested. + mode .req r3 + ldr mode, [state, #MODE] // mode = state.mode // 2 + cmp mode, #MODE_IDLE // if mode == IDLE: // 1 + beq idle // goto idle // 1 thru, 3 taken + + // Update counts. + update_counts // update_counts() // 4 + + // Jump to next mode if threshold reached, or back to wait loop start. + jump_next_mode wait // jump_next_mode() // 15 + rx_start: // Reset counts. diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index c1f1b90c..4cb5b41f 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -378,7 +378,7 @@ int write_register(hackrf_device* device, uint8_t part, } static const char * mode_name(uint32_t mode) { - const char *mode_names[] = {"IDLE", "RX", "TX_START", "TX_RUN"}; + const char *mode_names[] = {"IDLE", "WAIT", "RX", "TX_START", "TX_RUN"}; const uint32_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]); if (mode < num_modes) return mode_names[mode]; From 9f79a16b261136d89d4baeff958168b7af8a71e6 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 24 Dec 2021 16:00:17 +0000 Subject: [PATCH 27/34] Rewrite sweep mode using timed operations. The previous implementation of sweep mode had the M0 continuing to receive and buffer samples during retuning. To avoid using data affected by retuning, the code discarded two 16K blocks of samples after retuning, before transferring one 16K block to the host. However, retuning has to be done with the USB IRQ masked. The M4 byte count cannot be advanced by the bulk transfer completion callback whilst retuning is ongoing. This makes an RX buffer overrun likely, and overruns now stall the M0, causing sweep timing to become inconsistent. It makes much more sense to stop the M0 receiving data during retuning. Using scheduled M0 mode changes between the RX and WAIT modes, it's now possible to do this whilst retaining consistent sweep timing. The comment block added to the start of the `sleep_mode()` function explains the new implementation. The new scheme substantially reduces the timing constraints on the host retrieving the data. Previously, the host had to retrieve each sample block before the M0 overwrote it, which would occur midway through retuning for the next sweep, with samples that were going to be discarded anyway. With the new scheme, buffer space is used efficiently. No data is written to the buffer which will be discarded. The host does not need to finish retrieving each 16K block until its buffer space is due to be reused, which is not until two sweep steps later. A great deal more jitter in the bulk transfer timing can therefore now be tolerated, without affecting sweep timing. If the host does delay the retrieval of a block enough that its buffer space is about to be reused, the M0 now stalls. This in turn will stall the M4 sweep loop, causing the sweep to be paused until there is enough buffer space to continue. Previously, sweeping continued regardless, and the host received corrupted data if it did not keep up. --- firmware/hackrf_usb/usb_api_sweep.c | 167 ++++++++++++++++------------ 1 file changed, 94 insertions(+), 73 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index 78b2d758..750f2ba7 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -90,101 +90,122 @@ usb_request_status_t usb_vendor_request_init_sweep( void sweep_bulk_transfer_complete(void *user_data, unsigned int bytes_transferred) { (void) user_data; - m0_state.m4_count += bytes_transferred; + (void) bytes_transferred; + + // For each buffer transferred, we need to bump the count by three buffers + // worth of data, to allow for the discarded buffers. + m0_state.m4_count += 3 * 0x4000; } void sweep_mode(uint32_t seq) { - unsigned int blocks_queued = 0; - unsigned int phase = 1; + // Sweep mode is implemented using timed M0 operations, as follows: + // + // 0. M4 initially puts the M0 into RX mode, with an m0_count threshold + // of 16K and a next mode of WAIT. + // + // 1. M4 spins until the M0 switches to WAIT mode. + // + // 2. M0 captures one 16K block of samples, and switches to WAIT mode. + // + // 3. M4 sees the mode change, advances the m0_count target by 32K, and + // sets next mode to RX. + // + // 4. M4 adds the sweep metadata at the start of the block and + // schedules a bulk transfer for the block. + // + // 5. M4 retunes - this takes about 760us worst-case, so should be + // complete before the M0 goes back to RX. + // + // 6. M4 spins until the M0 mode changes to RX, then advances the + // m0_count limit by 16K and sets the next mode to WAIT. + // + // 7. Process repeats from step 1. + + unsigned int phase = 0; bool odd = true; uint16_t range = 0; uint8_t *buffer; - bool transfer = false; transceiver_startup(TRANSCEIVER_MODE_RX_SWEEP); + // Set M0 to RX first buffer, then wait. + m0_state.threshold = 0x4000; + m0_state.next_mode = M0_MODE_WAIT; + m0_state.mode = M0_MODE_RX; + baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { - uint32_t m0_offset = m0_state.m0_count & USB_BULK_BUFFER_MASK; - // Set up IN transfer of buffer 0. - if ( m0_offset >= 16384 && phase == 1) { - transfer = true; - buffer = &usb_bulk_buffer[0x0000]; - phase = 0; - blocks_queued++; - } - // Set up IN transfer of buffer 1. - if ( m0_offset < 16384 && phase == 0) { - transfer = true; - buffer = &usb_bulk_buffer[0x4000]; - phase = 1; - blocks_queued++; - } + // Wait for M0 to finish receiving a buffer. + while (m0_state.mode != M0_MODE_WAIT) + if (transceiver_request.seq != seq) + goto end; - if (transfer) { - *buffer = 0x7f; - *(buffer+1) = 0x7f; - *(buffer+2) = sweep_freq & 0xff; - *(buffer+3) = (sweep_freq >> 8) & 0xff; - *(buffer+4) = (sweep_freq >> 16) & 0xff; - *(buffer+5) = (sweep_freq >> 24) & 0xff; - *(buffer+6) = (sweep_freq >> 32) & 0xff; - *(buffer+7) = (sweep_freq >> 40) & 0xff; - *(buffer+8) = (sweep_freq >> 48) & 0xff; - *(buffer+9) = (sweep_freq >> 56) & 0xff; - if (blocks_queued > THROWAWAY_BUFFERS) { - usb_transfer_schedule_block( - &usb_endpoint_bulk_in, - buffer, - 0x4000, - sweep_bulk_transfer_complete, - NULL - ); - } - transfer = false; - } else { - // Account for having discarded a buffer. + // Set M0 to switch back to RX after two more buffers. + m0_state.threshold += 0x8000; + m0_state.next_mode = M0_MODE_RX; - // Disable USB IRQ whilst doing so, since this requires - // a read-modify-write, and sweep_bulk_transfer_complete() - // might be called from the USB ISR while we are changing - // this count. - nvic_disable_irq(NVIC_USB0_IRQ); - m0_state.m4_count += 0x4000; - nvic_enable_irq(NVIC_USB0_IRQ); - } + // Write metadata to buffer. + buffer = &usb_bulk_buffer[phase * 0x4000]; + *buffer = 0x7f; + *(buffer+1) = 0x7f; + *(buffer+2) = sweep_freq & 0xff; + *(buffer+3) = (sweep_freq >> 8) & 0xff; + *(buffer+4) = (sweep_freq >> 16) & 0xff; + *(buffer+5) = (sweep_freq >> 24) & 0xff; + *(buffer+6) = (sweep_freq >> 32) & 0xff; + *(buffer+7) = (sweep_freq >> 40) & 0xff; + *(buffer+8) = (sweep_freq >> 48) & 0xff; + *(buffer+9) = (sweep_freq >> 56) & 0xff; - if ((dwell_blocks + THROWAWAY_BUFFERS) <= blocks_queued) { - if(INTERLEAVED == style) { - if(!odd && ((sweep_freq + step_width) >= ((uint64_t)frequencies[1+range*2] * FREQ_GRANULARITY))) { - range = (range + 1) % num_ranges; - sweep_freq = (uint64_t)frequencies[range*2] * FREQ_GRANULARITY; - } else { - if(odd) { - sweep_freq += step_width/4; - } else { - sweep_freq += 3*step_width/4; - } - } - odd = !odd; + // Set up IN transfer of buffer. + usb_transfer_schedule_block( + &usb_endpoint_bulk_in, + buffer, + 0x4000, + sweep_bulk_transfer_complete, NULL + ); + + // Use other buffer next time. + phase = (phase + 1) % 2; + + // Calculate next sweep frequency. + if(INTERLEAVED == style) { + if(!odd && ((sweep_freq + step_width) >= ((uint64_t)frequencies[1+range*2] * FREQ_GRANULARITY))) { + range = (range + 1) % num_ranges; + sweep_freq = (uint64_t)frequencies[range*2] * FREQ_GRANULARITY; } else { - if((sweep_freq + step_width) >= ((uint64_t)frequencies[1+range*2] * FREQ_GRANULARITY)) { - range = (range + 1) % num_ranges; - sweep_freq = (uint64_t)frequencies[range*2] * FREQ_GRANULARITY; + if(odd) { + sweep_freq += step_width/4; } else { - sweep_freq += step_width; + sweep_freq += 3*step_width/4; } } - - nvic_disable_irq(NVIC_USB0_IRQ); - set_freq(sweep_freq + offset); - nvic_enable_irq(NVIC_USB0_IRQ); - blocks_queued = 0; + odd = !odd; + } else { + if((sweep_freq + step_width) >= ((uint64_t)frequencies[1+range*2] * FREQ_GRANULARITY)) { + range = (range + 1) % num_ranges; + sweep_freq = (uint64_t)frequencies[range*2] * FREQ_GRANULARITY; + } else { + sweep_freq += step_width; + } } - } + // Retune to new frequency. + nvic_disable_irq(NVIC_USB0_IRQ); + set_freq(sweep_freq + offset); + nvic_enable_irq(NVIC_USB0_IRQ); + // Wait for M0 to resume RX. + while (m0_state.mode != M0_MODE_RX) + if (transceiver_request.seq != seq) + goto end; + + // Set M0 to switch back to WAIT after filling next buffer. + m0_state.threshold += 0x4000; + m0_state.next_mode = M0_MODE_WAIT; + } +end: transceiver_shutdown(); } From 8bd3745253eba93e8f515fa517def0d13e6a448e Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 29 Dec 2021 01:05:58 +0000 Subject: [PATCH 28/34] Add some additional commentary. --- firmware/hackrf_usb/sgpio_m0.s | 143 ++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 646738ca..f12f062d 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -37,6 +37,40 @@ The SGPIO peripheral is set up and enabled by the M4 core. All the M0 needs to do is handle the SGPIO exchange interrupt, which indicates that new data can now be read from or written to the SGPIO shadow registers. +To implement the different functions of HackRF, the M0 operates in one of +five modes, configured by the M4: + +IDLE: Do nothing. +WAIT: Do nothing, but increment byte counter for timing purposes. +RX: Read data from SGPIO and write it to the buffer. +TX_START: Write zeroes to SGPIO until there is data in the buffer. +TX_RUN: Read data from the buffer and write it to SGPIO. + +In all modes except IDLE, the M0 advances a byte counter, which increases by +32 each time that many bytes are exchanged with the buffer (or skipped over, +in WAIT mode). + +As the M4 core produces or consumes these bytes, it advances its own counter. +The difference between the two counter values therefore indicates the number +of bytes available. + +If the M4 does not advance its count in time, a TX underrun or RX overrun +occurs. Collectively, these events are referred to as shortfalls, and the +handling is similar for both. + +In an RX shortfall, data is discarded. In TX mode, zeroes are written to +SGPIO. When in a shortfall, the byte counter does not advance. + +The M0 maintains statistics on the the number of shortfalls, and the length of +the longest shortfall. + +The M0 can be configured to abort TX or RX and return to IDLE mode, if the +length of a shortfall exceeds a configured limit. + +The M0 can also be configured to switch modes automatically when its byte +counter matches a threshold value. This feature can be used to implement +timed operations. + Timing ====== @@ -89,6 +123,78 @@ used to store values needed in the code, to minimise memory loads and stores. There are no function calls. There is no stack usage. All values are in registers and fixed memory addresses. +Structure +========= + +Each mode has its own loop routine. TX_START and TX_RUN use a single TX loop. + +Code shared between different modes is implemented in macros and duplicated +within each mode's own loop. + +At startup, the main routine sets up registers and memory, then falls through +to the idle loop. + +The idle loop waits for a mode to be set, then jumps to that mode's start +label. + +Code following the start label is executed only on a transition from IDLE. It +is at this point that the buffer statistics are reset. + +Each mode's start code then falls through to its loop label. + +The first step in each loop is to wait for an SGPIO interrupt and clear it, +which is implemented by the await_sgpio macro. + +Then, the mode setting is loaded from memory. If the M4 has reset the mode to +idle, control jumps back to the idle loop after handling any cleanup needed. + +Next, any SGPIO operations are carried out. For RX and TX, this begins with +calculating the buffer margin, and branching if there is a shortfall. Then +the pointer within the buffer is updated. + +SGPIO reads and writes are implemented in 16-byte chunks. The four lowest +registers, r0-r3, are used to temporarily hold the data for each chunk. Data +is stored in-order in the buffer, but out-of-order in the SGPIO shadow +registers, due to the SGPIO architecture. A combination of single and +multiple load/stores is used to reorder the data in each chunk. + +After completing SGPIO operations, counters are updated and the threshold +setting is checked. If the byte count has reached the threshold, the next +mode is set and a jump is made directly to the corresponding loop label. +Code at the start label of the new mode is not executed, so stats and +counters are maintained across a sequence of TX/RX/WAIT operations. + +When a shortfall occurs, a branch is taken to a separate handler routine, +which branches back to the mode's normal loop when complete. + +Most of the code for shortfall handling is common to RX and TX, and is +implemented in the handle_shortfall macro. This is primarily concerned with +updating statistics, but also handles switching back to IDLE mode if a +shortfall exceeds the configured limit. + +There is a rollback mechanism implemented in the shortfall handling. This is +necessary because it is common for a harmless shortfall to occur during +shutdown, which produces misleading statistics. The code detects this case +when the mode is changed to IDLE whilst a shortfall is ongoing. If this +happens, statistics are rolled back to their values at the beginning of the +shortfall. + +The backup of previous values is implemented in handle_shortfall when a new +shortfall is detected, and the rollback is implemented by the +checked_rollback routine. This routine is executed by the TX and RX loops +before returning to the idle loop. + +Organisation +============ + +The rest of this file is organised as follows: + +- Constant definitions +- Fixed register allocations +- Macros +- Ordering constraints +- Finally, the actual code! + */ // Constants that point to registers we'll need to modify in the SGPIO block. @@ -297,6 +403,37 @@ buf_ptr .req r4 b idle // goto idle // 3 .endm +/* + +Ordering constraints +==================== + +The following routines are in an unusual order, to preserve the ability to +use PC-relative conditional branches between them ("b label"). The +ordering has been chosen to ensure that all routines are close enough to each +other for the limited range of these instructions (−256 bytes to +254 bytes). + +The ordering of routines, and which others each needs to be able to reach, is +as follows: + +Routine: Uses conditional branches to: + +idle tx_start, wait_start +tx_zeros tx_loop +checked_rollback idle +tx_start +tx_loop tx_zeros, checked_rollback, rx_loop, wait_loop +wait_start +wait_loop rx_loop, tx_loop +rx_start +rx_loop rx_shortfall, checked_rollback, tx_loop, wait_loop +rx_shortfall rx_loop + +If any of these routines are reordered, or made longer, you may get an error +from the assembler saying that a branch is out of range. + +*/ + // Entry point. At this point, the libopencm3 startup code has set things up as // normal; .data and .bss are initialised, the stack is set up, etc. However, // we don't actually use any of that. All the code in this file would work @@ -331,7 +468,11 @@ main: str zero, [state, #NEXT_MODE] // state.next_mode = zero // 2 idle: - // Wait for RX or TX mode to be set. + // Wait for a mode to be set, then jump to the appropriate loop. + // + // This code is arranged such that the branch to rx_start is the + // unconditional one - which is necessary since it's too far away to + // use a conditional branch instruction. mode .req r3 ldr mode, [state, #MODE] // mode = state.mode // 2 cmp mode, #MODE_WAIT // if mode < WAIT: // 1 From 137f2481e5413912bb4010b2f164898fda7ee37b Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 9 Feb 2022 02:33:22 +0000 Subject: [PATCH 29/34] Make an error code available when a shortfall limit is hit. Previously, finding the M0 in IDLE mode was ambiguous; it could indicate either a normal outcome, or a shortfall limit having being hit. To disambiguate, we add an error field to the M0 state. The errors currently possible are an RX timeout or a TX timeout, both of which can be obtained efficiently from the current operating mode due to the values used. This adds 3 cycles to both shortfall paths, in order to shift down the mode to obtain the error code, and store it to the M0 state. --- firmware/hackrf_usb/m0_state.h | 7 ++++++ firmware/hackrf_usb/sgpio_m0.s | 33 ++++++++++++++++++++++------ host/hackrf-tools/src/hackrf_debug.c | 10 +++++++++ host/libhackrf/src/hackrf.h | 2 ++ 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index ca6f057d..895a10d0 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -34,6 +34,7 @@ struct m0_state { uint32_t shortfall_limit; uint32_t threshold; uint32_t next_mode; + uint32_t error; }; enum m0_mode { @@ -44,6 +45,12 @@ enum m0_mode { M0_MODE_TX_RUN = 4, }; +enum m0_error { + M0_ERROR_NONE = 0, + M0_ERROR_RX_TIMEOUT = 1, + M0_ERROR_TX_TIMEOUT = 2, +}; + /* Address of m0_state 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. diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index f12f062d..33ed3e81 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -101,9 +101,9 @@ shadow registers. There are four key code paths, with the following worst-case timings: RX, normal: 152 cycles -RX, overrun: 73 cycles +RX, overrun: 76 cycles TX, normal: 142 cycles -TX, underrun: 140 cycles +TX, underrun: 143 cycles Design ====== @@ -222,9 +222,10 @@ The rest of this file is organised as follows: .equ SHORTFALL_LIMIT, 0x14 .equ THRESHOLD, 0x18 .equ NEXT_MODE, 0x1C +.equ ERROR, 0x20 // Private variables stored after state. -.equ PREV_LONGEST_SHORTFALL, 0x20 +.equ PREV_LONGEST_SHORTFALL, 0x24 // Operating modes. .equ MODE_IDLE, 0 @@ -233,6 +234,11 @@ The rest of this file is organised as follows: .equ MODE_TX_START, 3 .equ MODE_TX_RUN, 4 +// Error codes. +.equ ERROR_NONE, 0 +.equ ERROR_RX_TIMEOUT, 1 +.equ ERROR_TX_TIMEOUT, 2 + // Our slice chain is set up as follows (ascending data age; arrows are reversed for flow): // L -> F -> K -> C -> J -> E -> I -> A // Which has equivalent shadow register offsets: @@ -273,6 +279,7 @@ buf_ptr .req r4 str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 str zero, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = zero // 2 + str zero, [state, #ERROR] // state.error = zero // 2 mov shortfall_length, zero // shortfall_length = zero // 1 mov count, zero // count = zero // 1 .endm @@ -396,8 +403,19 @@ buf_ptr .req r4 cmp length, limit // if length < limit: // 1 blt \name\()_loop // goto loop // 1 thru, 3 taken - // If so, reset mode to idle and return to idle loop. + // If so, reset mode to idle and return to idle loop, logging an error. + // + // Modes are mapped to errors as follows: + // + // MODE_RX (2) -> ERROR_RX_TIMEOUT (1) + // MODE_TX_RUN (4) -> ERROR_TX_TIMEOUT (2) + // + // As such, the error code can be obtained by shifting the mode right by 1 bit. + mode .req r3 + error .req r2 + lsr error, mode, #1 // error = mode >> 1 // 1 + str error, [state, #ERROR] // state.error = error // 2 mov mode, #MODE_IDLE // mode = MODE_IDLE // 1 str mode, [state, #MODE] // state.mode = mode // 2 b idle // goto idle // 3 @@ -466,6 +484,7 @@ main: str zero, [state, #SHORTFALL_LIMIT] // state.shortfall_limit = zero // 2 str zero, [state, #THRESHOLD] // state.threshold = zero // 2 str zero, [state, #NEXT_MODE] // state.next_mode = zero // 2 + str zero, [state, #ERROR] // state.error = zero // 2 idle: // Wait for a mode to be set, then jump to the appropriate loop. @@ -526,7 +545,7 @@ checked_rollback: tx_start: // Reset counts. - reset_counts // reset_counts() // 10 + reset_counts // reset_counts() // 12 tx_loop: @@ -588,7 +607,7 @@ tx_write: wait_start: // Reset counts. - reset_counts // reset_counts() // 10 + reset_counts // reset_counts() // 12 wait_loop: @@ -610,7 +629,7 @@ wait_loop: rx_start: // Reset counts. - reset_counts // reset_counts() // 10 + reset_counts // reset_counts() // 12 rx_loop: diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 4cb5b41f..a0a13668 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -386,6 +386,15 @@ static const char * mode_name(uint32_t mode) { return "UNKNOWN"; } +static const char * error_name(uint32_t error) { + const char *error_names[] = {"NONE", "RX_TIMEOUT", "TX_TIMEOUT"}; + const uint32_t num_errors = sizeof(error_names) / sizeof(error_names[0]); + if (error < num_errors) + return error_names[error]; + else + return "UNKNOWN"; +} + static void print_state(hackrf_m0_state *state) { printf("M0 state:\n"); printf("Mode: %u (%s)\n", state->mode, mode_name(state->mode)); @@ -396,6 +405,7 @@ static void print_state(hackrf_m0_state *state) { printf("Shortfall limit: %u bytes\n", state->shortfall_limit); printf("Mode change threshold: %u bytes\n", state->threshold); printf("Next mode: %u (%s)\n", state->mode, mode_name(state->mode)); + printf("Error: %u (%s)\n", state->error, error_name(state->error)); } static void usage() { diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 8bd7bd78..0ded0c0b 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -173,6 +173,8 @@ typedef struct { uint32_t threshold; /** Mode which will be switched to when threshold is reached. */ uint32_t next_mode; + /** Error, if any, that caused the M0 to revert to IDLE mode. */ + uint32_t error; } hackrf_m0_state; struct hackrf_device_list { From f3633e285f1f27f33305c4068753a3df91d3d346 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 9 Feb 2022 17:32:17 +0000 Subject: [PATCH 30/34] Replace direct setting of M0 mode with a request/ack mechanism. This change avoids various possible races in which an autonomous mode change by the M0 might clobber a mode change made from the M4, as well as related races on other state fields that can be written by the M4. The previous mode field is replaced by two separate ones: - active_mode, which is written only by the M0, and indicates the current operating mode. - requested_mode, which is written by the M4 to request a change. This field includes both the requested mode, and a flag bit. The M4 writes the field with the flag bit set, and must then wait for the M0 to signal completion of the request by clearing the flag bit. Whilst the M4 is blocked waiting for the flag bit to be cleared, the M0 can safely make all the required changes to the state that are needed for the transition to the requested mode. Once the transition is complete, the M0 clears the flag bit and the M4 continues execution. Request handling is implemented in the idle loop. To handle requests, mode-specific loops simply need to check the request flag and branch to idle if it is set. A request from the M4 to change modes will always require passing through the idle loop, and is not subject to timing guarantees. Only transitions made autonomously by the M0 have guaranteed timing constraints. The work previously done in reset_counts is now implemented as part of the request handling, so the tx_start, rx_start and wait_start labels are no longer required. An extra two cycles are required in the TX shortfall path because we must now load the active mode to check whether we are in TX_START. Two cycles are saved in the normal TX path because updating the active mode to TX_RUN can now be done without checking the previous value. --- firmware/hackrf_usb/m0_state.c | 16 +++ firmware/hackrf_usb/m0_state.h | 7 +- firmware/hackrf_usb/sgpio_m0.s | 164 +++++++++++----------- firmware/hackrf_usb/usb_api_sweep.c | 5 +- firmware/hackrf_usb/usb_api_transceiver.c | 20 +-- host/hackrf-tools/src/hackrf_debug.c | 7 +- host/libhackrf/src/hackrf.h | 8 +- 7 files changed, 123 insertions(+), 104 deletions(-) diff --git a/firmware/hackrf_usb/m0_state.c b/firmware/hackrf_usb/m0_state.c index ec113a87..d37e66ff 100644 --- a/firmware/hackrf_usb/m0_state.c +++ b/firmware/hackrf_usb/m0_state.c @@ -21,10 +21,26 @@ #include "m0_state.h" +#include #include #include #include +void m0_set_mode(enum m0_mode mode) +{ + // Set requested mode and flag bit. + m0_state.requested_mode = M0_REQUEST_FLAG | mode; + + // The M0 may be blocked waiting for the next SGPIO interrupt. + // In order to ensure that it sees our request, we need to set + // the interrupt flag here. The M0 will clear the flag again + // before acknowledging our request. + SGPIO_SET_STATUS_1 = (1 << SGPIO_SLICE_A); + + // Wait for M0 to acknowledge by clearing the flag. + while (m0_state.requested_mode & M0_REQUEST_FLAG); +} + usb_request_status_t usb_vendor_request_get_m0_state( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/m0_state.h index 895a10d0..db752829 100644 --- a/firmware/hackrf_usb/m0_state.h +++ b/firmware/hackrf_usb/m0_state.h @@ -25,8 +25,11 @@ #include #include +#define M0_REQUEST_FLAG (1 << 16) + struct m0_state { - uint32_t mode; + uint32_t requested_mode; + uint32_t active_mode; uint32_t m0_count; uint32_t m4_count; uint32_t num_shortfalls; @@ -57,6 +60,8 @@ enum m0_error { */ extern volatile struct m0_state m0_state; +void m0_set_mode(enum m0_mode mode); + usb_request_status_t usb_vendor_request_get_m0_state( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 33ed3e81..7179ece0 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -102,8 +102,8 @@ There are four key code paths, with the following worst-case timings: RX, normal: 152 cycles RX, overrun: 76 cycles -TX, normal: 142 cycles -TX, underrun: 143 cycles +TX, normal: 140 cycles +TX, underrun: 145 cycles Design ====== @@ -214,18 +214,19 @@ The rest of this file is organised as follows: .equ STATE_BASE, 0x20007000 // Offsets into the state structure. -.equ MODE, 0x00 -.equ M0_COUNT, 0x04 -.equ M4_COUNT, 0x08 -.equ NUM_SHORTFALLS, 0x0C -.equ LONGEST_SHORTFALL, 0x10 -.equ SHORTFALL_LIMIT, 0x14 -.equ THRESHOLD, 0x18 -.equ NEXT_MODE, 0x1C -.equ ERROR, 0x20 +.equ REQUESTED_MODE, 0x00 +.equ ACTIVE_MODE, 0x04 +.equ M0_COUNT, 0x08 +.equ M4_COUNT, 0x0C +.equ NUM_SHORTFALLS, 0x10 +.equ LONGEST_SHORTFALL, 0x14 +.equ SHORTFALL_LIMIT, 0x18 +.equ THRESHOLD, 0x1C +.equ NEXT_MODE, 0x20 +.equ ERROR, 0x24 // Private variables stored after state. -.equ PREV_LONGEST_SHORTFALL, 0x24 +.equ PREV_LONGEST_SHORTFALL, 0x28 // Operating modes. .equ MODE_IDLE, 0 @@ -267,23 +268,6 @@ buf_ptr .req r4 /* Macros */ -.macro reset_counts - // Reset counts. - // - // Clobbers: - zero .req r0 - - mov zero, #0 // zero = 0 // 1 - str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 - str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 - str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 - str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 - str zero, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = zero // 2 - str zero, [state, #ERROR] // state.error = zero // 2 - mov shortfall_length, zero // shortfall_length = zero // 1 - mov count, zero // count = zero // 1 -.endm - .macro await_sgpio name // Wait for, then clear, SGPIO exchange interrupt flag. // @@ -315,6 +299,15 @@ buf_ptr .req r4 str int_status, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = int_status // 8 .endm +.macro on_request label + // Check if a new mode change request was made, and if so jump to the given label. + mode .req r3 + flag .req r2 + ldr mode, [state, #REQUESTED_MODE] // mode = state.requested_mode // 2 + lsr flag, mode, #16 // flag = mode >> 16 // 1 + bne \label // if flag != 0: goto label // 1 thru, 3 taken +.endm + .macro update_buf_ptr // Update the address of the buffer segment we want to write to / read from. mov buf_ptr, buf_mask // buf_ptr = buf_mask // 1 @@ -347,7 +340,7 @@ buf_ptr .req r4 // Otherwise, load and set new mode. ldr new_mode, [state, #NEXT_MODE] // new_mode = state.next_mode // 2 - str new_mode, [state, #MODE] // state.mode = new_mode // 2 + str new_mode, [state, #ACTIVE_MODE] // state.active_mode = new_mode // 2 // Branch according to new mode. cmp new_mode, #MODE_RX // if new_mode == RX: // 1 @@ -414,10 +407,11 @@ buf_ptr .req r4 mode .req r3 error .req r2 + ldr mode, [state, #ACTIVE_MODE] // mode = state.active_mode // 2 lsr error, mode, #1 // error = mode >> 1 // 1 str error, [state, #ERROR] // state.error = error // 2 mov mode, #MODE_IDLE // mode = MODE_IDLE // 1 - str mode, [state, #MODE] // state.mode = mode // 2 + str mode, [state, #ACTIVE_MODE] // state.active_mode = mode // 2 b idle // goto idle // 3 .endm @@ -436,14 +430,11 @@ as follows: Routine: Uses conditional branches to: -idle tx_start, wait_start +idle tx_loop, wait_loop tx_zeros tx_loop checked_rollback idle -tx_start tx_loop tx_zeros, checked_rollback, rx_loop, wait_loop -wait_start wait_loop rx_loop, tx_loop -rx_start rx_loop rx_shortfall, checked_rollback, tx_loop, wait_loop rx_shortfall rx_loop @@ -476,7 +467,8 @@ main: mov hi_zero, zero // hi_zero = zero // 1 // Initialise state. - str zero, [state, #MODE] // state.mode = zero // 2 + str zero, [state, #REQUESTED_MODE] // state.requested_mode = zero // 2 + str zero, [state, #ACTIVE_MODE] // state.active_mode = zero // 2 str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 @@ -487,19 +479,59 @@ main: str zero, [state, #ERROR] // state.error = zero // 2 idle: - // Wait for a mode to be set, then jump to the appropriate loop. + // Wait for a mode to be requested, then set up the new mode and acknowledge the request. + mode .req r3 + flag .req r2 + zero .req r0 + + // Read the requested mode and check flag to see if this is a new request. If not, ignore. + ldr mode, [state, #REQUESTED_MODE] // mode = state.requested_mode // 2 + lsr flag, mode, #16 // flag = mode >> 16 // 1 + beq idle // if flag == 0: goto idle // 1 thru, 3 taken + + // Otherwise, this is a new request. The M4 is blocked at this point, + // waiting for us to clear the request flag. So we can safely write to + // all parts of the state. + + // Set the new mode as both active & next. + uxth mode, mode // mode = mode & 0xFFFF // 1 + str mode, [state, #ACTIVE_MODE] // state.active_mode = mode // 2 + str mode, [state, #NEXT_MODE] // state.next_mode = mode // 2 + + // Don't reset counts on a transition to IDLE. + cmp mode, #MODE_IDLE // if mode == IDLE: // 1 + beq ack_request // goto ack_request // 1 thru, 3 taken + + // For all other transitions, reset counts. + mov zero, #0 // zero = 0 // 1 + str zero, [state, #M0_COUNT] // state.m0_count = zero // 2 + str zero, [state, #M4_COUNT] // state.m4_count = zero // 2 + str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2 + str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2 + str zero, [state, #THRESHOLD] // state.threshold = zero // 2 + str zero, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = zero // 2 + str zero, [state, #ERROR] // state.error = zero // 2 + mov shortfall_length, zero // shortfall_length = zero // 1 + mov count, zero // count = zero // 1 + +ack_request: + // Clear SGPIO interrupt flag, which the M4 set to get our attention. + str flag, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = flag // 8 + + // Write back requested mode with the flag cleared to acknowledge the request. + str mode, [state, #REQUESTED_MODE] // state.requested_mode = mode // 2 + + // Dispatch to appropriate loop. // - // This code is arranged such that the branch to rx_start is the + // This code is arranged such that the branch to rx_loop is the // unconditional one - which is necessary since it's too far away to // use a conditional branch instruction. - mode .req r3 - ldr mode, [state, #MODE] // mode = state.mode // 2 cmp mode, #MODE_WAIT // if mode < WAIT: // 1 blt idle // goto idle // 1 thru, 3 taken - beq wait_start // elif mode == WAIT: goto wait_start // 1 thru, 3 taken + beq wait_loop // elif mode == WAIT: goto wait_loop // 1 thru, 3 taken cmp mode, #MODE_RX // if mode > RX: // 1 - bgt tx_start // goto tx_start // 1 thru, 3 taken - b rx_start // goto rx_start // 3 + bgt tx_loop // goto tx_loop // 1 thru, 3 taken + b rx_loop // goto rx_loop // 3 tx_zeros: @@ -515,6 +547,7 @@ tx_zeros: str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8 // If in TX start mode, don't count this as a shortfall. + ldr mode, [state, #ACTIVE_MODE] // mode = state.active_mode // 2 cmp mode, #MODE_TX_START // if mode == TX_START: // 1 beq tx_loop // goto tx_loop // 1 thru, 3 taken @@ -542,22 +575,14 @@ checked_rollback: b idle // goto idle // 3 -tx_start: - - // Reset counts. - reset_counts // reset_counts() // 12 - tx_loop: // Wait for and clear SGPIO interrupt. await_sgpio tx // await_sgpio() // 34 - // Check if a return to idle mode was requested. + // Check if there is a mode change request. // If so, we may need to roll back shortfall stats. - mode .req r3 - ldr mode, [state, #MODE] // mode = state.mode // 2 - cmp mode, #MODE_IDLE // if mode == IDLE: // 1 - beq checked_rollback // goto checked_rollback // 1 thru, 3 taken + on_request checked_rollback // 4 // Check if there is enough data in the buffer. // @@ -578,13 +603,9 @@ tx_loop: update_buf_ptr // update_buf_ptr() // 3 // At this point we know there is TX data available. - // If still in TX start mode, switch to TX run. - cmp mode, #MODE_TX_START // if mode != TX_START: // 1 - bne tx_write // goto tx_write // 1 thru, 3 taken + // Set active mode to TX_RUN (it might still be TX_START). mov mode, #MODE_TX_RUN // mode = TX_RUN // 1 - str mode, [state, #MODE] // state.mode = mode // 2 - -tx_write: + str mode, [state, #ACTIVE_MODE] // state.active_mode = mode // 2 // Write data to SGPIO. ldm buf_ptr!, {r0-r3} // r0-r3 = buf_ptr[0:16]; buf_ptr += 16 // 5 @@ -604,21 +625,14 @@ tx_write: // Jump to next mode if threshold reached, or back to TX loop start. jump_next_mode tx // jump_next_mode() // 13 -wait_start: - - // Reset counts. - reset_counts // reset_counts() // 12 - wait_loop: // Wait for and clear SGPIO interrupt. await_sgpio wait // await_sgpio() // 34 - // Load mode, and return to idle if requested. - mode .req r3 - ldr mode, [state, #MODE] // mode = state.mode // 2 - cmp mode, #MODE_IDLE // if mode == IDLE: // 1 - beq idle // goto idle // 1 thru, 3 taken + // Check if there is a mode change request. + // If so, return to idle. + on_request idle // 4 // Update counts. update_counts // update_counts() // 4 @@ -626,22 +640,14 @@ wait_loop: // Jump to next mode if threshold reached, or back to wait loop start. jump_next_mode wait // jump_next_mode() // 15 -rx_start: - - // Reset counts. - reset_counts // reset_counts() // 12 - rx_loop: // Wait for and clear SGPIO interrupt. await_sgpio rx // await_sgpio() // 34 - // Check if a return to idle mode was requested. + // Check if there is a mode change request. // If so, we may need to roll back shortfall stats. - mode .req r3 - ldr mode, [state, #MODE] // mode = state.mode // 2 - cmp mode, #MODE_IDLE // if mode == IDLE: // 1 - beq checked_rollback // goto checked_rollback // 1 thru, 3 taken + on_request checked_rollback // 4 // Check if there is enough space in the buffer. // diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index 750f2ba7..613d090c 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -132,14 +132,13 @@ void sweep_mode(uint32_t seq) { // Set M0 to RX first buffer, then wait. m0_state.threshold = 0x4000; m0_state.next_mode = M0_MODE_WAIT; - m0_state.mode = M0_MODE_RX; baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { // Wait for M0 to finish receiving a buffer. - while (m0_state.mode != M0_MODE_WAIT) + while (m0_state.active_mode != M0_MODE_WAIT) if (transceiver_request.seq != seq) goto end; @@ -198,7 +197,7 @@ void sweep_mode(uint32_t seq) { nvic_enable_irq(NVIC_USB0_IRQ); // Wait for M0 to resume RX. - while (m0_state.mode != M0_MODE_RX) + while (m0_state.active_mode != M0_MODE_RX) if (transceiver_request.seq != seq) goto end; diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 7048e8bd..ed4e9883 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -260,15 +260,6 @@ void request_transceiver_mode(transceiver_mode_t mode) transceiver_request.seq++; } -static void set_m0_mode(enum m0_mode mode) { - // All values of the threshold setting are valid. So setting a mode, - // it's necessary to set up a transition back to the same mode, for - // when the M0 counter wraps. - m0_state.mode = mode; - m0_state.next_mode = mode; - m0_state.threshold = 0; -} - void transceiver_shutdown(void) { baseband_streaming_disable(&sgpio_config); @@ -280,12 +271,7 @@ void transceiver_shutdown(void) led_off(LED2); led_off(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF); - set_m0_mode(M0_MODE_IDLE); - // The M0 may already be waiting for the next SGPIO - // exchange interrupt. In order to ensure that the M0 - // switches over to its idle loop, we need to set the - // SGPIO exchange interrupt flag here. - SGPIO_SET_STATUS_1 = (1 << SGPIO_SLICE_A); + m0_set_mode(M0_MODE_IDLE); } void transceiver_startup(const transceiver_mode_t mode) { @@ -298,14 +284,14 @@ void transceiver_startup(const transceiver_mode_t mode) { led_off(LED3); led_on(LED2); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX); - set_m0_mode(M0_MODE_RX); + m0_set_mode(M0_MODE_RX); m0_state.shortfall_limit = _rx_overrun_limit; break; case TRANSCEIVER_MODE_TX: led_off(LED2); led_on(LED3); rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX); - set_m0_mode(M0_MODE_TX_START); + m0_set_mode(M0_MODE_TX_START); m0_state.shortfall_limit = _tx_underrun_limit; break; default: diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index a0a13668..83030153 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -397,14 +397,17 @@ static const char * error_name(uint32_t error) { static void print_state(hackrf_m0_state *state) { printf("M0 state:\n"); - printf("Mode: %u (%s)\n", state->mode, mode_name(state->mode)); + printf("Requested mode: %u (%s) [%s]\n", + state->requested_mode, mode_name(state->requested_mode), + state->request_flag ? "pending" : "complete"); + printf("Active mode: %u (%s)\n", state->active_mode, mode_name(state->active_mode)); printf("M0 count: %u bytes\n", state->m0_count); printf("M4 count: %u bytes\n", state->m4_count); printf("Number of shortfalls: %u\n", state->num_shortfalls); printf("Longest shortfall: %u bytes\n", state->longest_shortfall); printf("Shortfall limit: %u bytes\n", state->shortfall_limit); printf("Mode change threshold: %u bytes\n", state->threshold); - printf("Next mode: %u (%s)\n", state->mode, mode_name(state->mode)); + printf("Next mode: %u (%s)\n", state->next_mode, mode_name(state->next_mode)); printf("Error: %u (%s)\n", state->error, error_name(state->error)); } diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 0ded0c0b..d82275c6 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -157,8 +157,12 @@ typedef struct { /** State of the SGPIO loop running on the M0 core. */ typedef struct { - /** Operating mode. */ - uint32_t mode; + /** Request flag. */ + uint16_t request_flag; + /** Requested mode. */ + uint16_t requested_mode; + /** Active mode. */ + uint32_t active_mode; /** Number of bytes transferred by the M0. */ uint32_t m0_count; /** Number of bytes transferred by the M4. */ From 1fe06b425a49ee50369b60878c2525cfcc6ab628 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 9 Feb 2022 20:00:00 +0000 Subject: [PATCH 31/34] Rename m0_state.{c,h} to usb_api_m0_state.{c,h} --- firmware/hackrf_usb/CMakeLists.txt | 2 +- firmware/hackrf_usb/hackrf_usb.c | 2 +- firmware/hackrf_usb/{m0_state.c => usb_api_m0_state.c} | 2 +- firmware/hackrf_usb/{m0_state.h => usb_api_m0_state.h} | 0 firmware/hackrf_usb/usb_api_sweep.c | 2 +- firmware/hackrf_usb/usb_api_transceiver.c | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename firmware/hackrf_usb/{m0_state.c => usb_api_m0_state.c} (98%) rename firmware/hackrf_usb/{m0_state.h => usb_api_m0_state.h} (100%) diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 04993725..d1cc8bbd 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -35,7 +35,6 @@ set(SRC_M4 hackrf_usb.c "${PATH_HACKRF_FIRMWARE_COMMON}/tuning.c" "${PATH_HACKRF_FIRMWARE_COMMON}/streaming.c" - m0_state.c "${PATH_HACKRF_FIRMWARE_COMMON}/usb.c" "${PATH_HACKRF_FIRMWARE_COMMON}/usb_request.c" "${PATH_HACKRF_FIRMWARE_COMMON}/usb_standard_request.c" @@ -44,6 +43,7 @@ set(SRC_M4 usb_endpoint.c usb_api_board_info.c usb_api_cpld.c + usb_api_m0_state.c usb_api_register.c usb_api_spiflash.c usb_api_transceiver.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 6dc6433a..5da07363 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -49,7 +49,7 @@ #include "usb_api_transceiver.h" #include "usb_api_ui.h" #include "usb_bulk_buffer.h" -#include "m0_state.h" +#include "usb_api_m0_state.h" #include "cpld_xc2c.h" #include "portapack.h" diff --git a/firmware/hackrf_usb/m0_state.c b/firmware/hackrf_usb/usb_api_m0_state.c similarity index 98% rename from firmware/hackrf_usb/m0_state.c rename to firmware/hackrf_usb/usb_api_m0_state.c index d37e66ff..dd1bbc19 100644 --- a/firmware/hackrf_usb/m0_state.c +++ b/firmware/hackrf_usb/usb_api_m0_state.c @@ -19,7 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#include "m0_state.h" +#include "usb_api_m0_state.h" #include #include diff --git a/firmware/hackrf_usb/m0_state.h b/firmware/hackrf_usb/usb_api_m0_state.h similarity index 100% rename from firmware/hackrf_usb/m0_state.h rename to firmware/hackrf_usb/usb_api_m0_state.h diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index 613d090c..0b3bb982 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -25,7 +25,7 @@ #include #include "usb_api_transceiver.h" #include "usb_bulk_buffer.h" -#include "m0_state.h" +#include "usb_api_m0_state.h" #include "tuning.h" #include "usb_endpoint.h" #include "streaming.h" diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index ed4e9883..7d2d7864 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -27,7 +27,7 @@ #include #include "usb_bulk_buffer.h" -#include "m0_state.h" +#include "usb_api_m0_state.h" #include "usb_api_cpld.h" // Remove when CPLD update is handled elsewhere From 779483b9bd6c012fd5d7387938a9a017d4f79ebb Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 9 Feb 2022 20:53:50 +0000 Subject: [PATCH 32/34] Make M0 state retrieval endian-safe. --- host/libhackrf/src/hackrf.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 0ad5557b..73cbade5 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -46,9 +46,13 @@ typedef int bool; #ifdef HACKRF_BIG_ENDIAN #define TO_LE(x) __builtin_bswap32(x) #define TO_LE64(x) __builtin_bswap64(x) +#define FROM_LE16(x) __builtin_bswap16(x) +#define FROM_LE32(x) __builtin_bswap32(x) #else #define TO_LE(x) x #define TO_LE64(x) x +#define FROM_LE16(x) x +#define FROM_LE32(x) x #endif // TODO: Factor this into a shared #include so that firmware can use @@ -1031,6 +1035,17 @@ int ADDCALL hackrf_get_m0_state(hackrf_device* device, hackrf_m0_state* state) last_libusb_error = result; return HACKRF_ERROR_LIBUSB; } else { + state->request_flag = FROM_LE16(state->request_flag); + state->requested_mode = FROM_LE16(state->requested_mode); + state->active_mode = FROM_LE32(state->active_mode); + state->m0_count = FROM_LE32(state->m0_count); + state->m4_count = FROM_LE32(state->m4_count); + state->num_shortfalls = FROM_LE32(state->num_shortfalls); + state->longest_shortfall = FROM_LE32(state->longest_shortfall); + state->shortfall_limit = FROM_LE32(state->shortfall_limit); + state->threshold = FROM_LE32(state->threshold); + state->next_mode = FROM_LE32(state->next_mode); + state->error = FROM_LE32(state->error); return HACKRF_SUCCESS; } } From d755f7a5c8a6143af740b11df8428d74edc4cfbb Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 28 Feb 2022 17:12:45 +0000 Subject: [PATCH 33/34] Correct order of requested mode and flag. --- host/libhackrf/src/hackrf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index d82275c6..fc76dd7f 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -157,10 +157,10 @@ typedef struct { /** State of the SGPIO loop running on the M0 core. */ typedef struct { - /** Request flag. */ - uint16_t request_flag; /** Requested mode. */ uint16_t requested_mode; + /** Request flag. */ + uint16_t request_flag; /** Active mode. */ uint32_t active_mode; /** Number of bytes transferred by the M0. */ From ad3216435aeffa2019b164339fddcab44f62a29f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 28 Feb 2022 23:02:34 +0000 Subject: [PATCH 34/34] Fix overlapping register allocations. --- firmware/hackrf_usb/sgpio_m0.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/sgpio_m0.s b/firmware/hackrf_usb/sgpio_m0.s index 7179ece0..960c9d6f 100644 --- a/firmware/hackrf_usb/sgpio_m0.s +++ b/firmware/hackrf_usb/sgpio_m0.s @@ -357,6 +357,7 @@ buf_ptr .req r4 // Clobbers: length .req r0 num .req r1 + prev .req r1 longest .req r1 limit .req r1 @@ -373,7 +374,6 @@ buf_ptr .req r4 str num, [state, #NUM_SHORTFALLS] // state.num_shortfalls = num // 2 // Back up previous longest shortfall. - prev .req r0 ldr prev, [state, #LONGEST_SHORTFALL] // prev = state.longest_shortfall // 2 str prev, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = prev // 2