From 17c2782a4d2977a026f7652f62b8fe0841779807 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Mon, 1 Jun 2026 14:39:45 +0200 Subject: [PATCH 1/7] radio: Add support for locking radio registers --- firmware/common/radio.c | 24 ++++++++++ firmware/common/radio.h | 11 +++++ firmware/hackrf_usb/hackrf_usb.c | 1 + firmware/hackrf_usb/usb_api_register.c | 17 +++++++ firmware/hackrf_usb/usb_api_register.h | 3 ++ firmware/hackrf_usb/usb_descriptor.c | 2 +- host/hackrf-tools/src/hackrf_debug.c | 65 ++++++++++++++++++++++++-- host/libhackrf/src/hackrf.c | 28 +++++++++++ host/libhackrf/src/hackrf.h | 14 ++++++ 9 files changed, 159 insertions(+), 6 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index f8b2e5141..3dd997d63 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -59,6 +59,7 @@ void radio_init(radio_t* const radio) radio->config[RADIO_BANK_TX][RADIO_OPMODE] = TRANSCEIVER_MODE_TX; radio->config[RADIO_BANK_IDLE][RADIO_BIAS_TEE] = false; radio->regs_dirty = 0; + radio->regs_locked = 0; } static inline void mark_dirty(radio_t* const radio, radio_register_t reg) @@ -66,6 +67,11 @@ static inline void mark_dirty(radio_t* const radio, radio_register_t reg) radio->regs_dirty |= (1 << reg); } +static inline bool check_locked(radio_t* const radio, radio_register_t reg) +{ + return radio->regs_locked & (1 << reg); +} + radio_error_t radio_reg_write( radio_t* const radio, const radio_register_bank_t bank, @@ -76,6 +82,10 @@ radio_error_t radio_reg_write( return RADIO_ERR_INVALID_REGISTER; } + if (check_locked(radio, reg)) { + return RADIO_ERR_LOCKED_REGISTER; + } + switch (bank) { case RADIO_BANK_ACTIVE: mark_dirty(radio, reg); @@ -106,6 +116,20 @@ uint64_t radio_reg_read( return radio->config[bank][reg]; } +radio_error_t radio_reg_lock( + radio_t* const radio, + const radio_register_t reg, + const bool locked) +{ + if (reg > RADIO_NUM_REGS) { + return RADIO_ERR_INVALID_REGISTER; + } + + radio->regs_locked = (radio->regs_locked & ~(1 << reg)) | (locked << reg); + + return RADIO_OK; +} + static uint32_t radio_update_direction(radio_t* const radio, uint64_t* bank) { const uint64_t requested = bank[RADIO_OPMODE]; diff --git a/firmware/common/radio.h b/firmware/common/radio.h index 821c7fead..6724ad4a7 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -34,6 +34,7 @@ typedef enum { RADIO_ERR_INVALID_PARAM = -2, RADIO_ERR_INVALID_BANK = -3, RADIO_ERR_INVALID_REGISTER = -4, + RADIO_ERR_LOCKED_REGISTER = -5, RADIO_ERR_UNSUPPORTED_OPERATION = -10, RADIO_ERR_UNIMPLEMENTED = -19, RADIO_ERR_OTHER = -9999, @@ -225,6 +226,7 @@ typedef struct { radio_config_mode_t config_mode; uint64_t config[RADIO_NUM_BANKS][RADIO_NUM_REGS]; volatile uint32_t regs_dirty; + uint32_t regs_locked; update_fn update_cb; } radio_t; @@ -248,6 +250,15 @@ uint64_t radio_reg_read( const radio_register_bank_t bank, const radio_register_t reg); +/** + * Lock a register. Prevents any future calls to `radio_reg_write` + * from overwriting the current stored value of the register. + */ +radio_error_t radio_reg_lock( + radio_t* const radio, + const radio_register_t reg, + const bool locked); + /** * Apply changes requested in RADIO_BANK_ACTIVE. * Return true if any changes were applied. diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 813a94539..4cdfbd14e 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -182,6 +182,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_write_radio_reg, usb_vendor_request_read_radio_reg, usb_vendor_request_get_buffer_size, + usb_vendor_request_lock_radio_reg, }; static const uint32_t vendor_request_handler_count = diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 626c75089..a46a22404 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -416,3 +416,20 @@ usb_request_status_t usb_vendor_request_read_radio_reg( } return USB_REQUEST_STATUS_OK; } + +usb_request_status_t usb_vendor_request_lock_radio_reg( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + uint8_t reg = endpoint->setup.index; + bool locked = endpoint->setup.value != 0 ? true : false; + if (reg >= RADIO_NUM_REGS) { + return USB_REQUEST_STATUS_STALL; + } + radio_reg_lock(&radio, reg, locked); + usb_transfer_schedule_ack(endpoint->in); + } + + return USB_REQUEST_STATUS_OK; +} diff --git a/firmware/hackrf_usb/usb_api_register.h b/firmware/hackrf_usb/usb_api_register.h index a5a36550d..7e434c1a7 100644 --- a/firmware/hackrf_usb/usb_api_register.h +++ b/firmware/hackrf_usb/usb_api_register.h @@ -70,3 +70,6 @@ usb_request_status_t usb_vendor_request_write_radio_reg( usb_request_status_t usb_vendor_request_read_radio_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); +usb_request_status_t usb_vendor_request_lock_radio_reg( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 005babb35..dde22e6e3 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -28,7 +28,7 @@ #define USB_VENDOR_ID (0x1D50) -#define USB_API_VERSION (0x0112) +#define USB_API_VERSION (0x0113) #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 52ede9cb7..49bc31ed1 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -545,6 +545,30 @@ int radio_write_register( return result; } +int radio_lock_register( + hackrf_device* device, + const uint16_t register_number, + const bool register_locked) +{ + int result = HACKRF_SUCCESS; + result = hackrf_radio_lock_register( + device, + (uint8_t) register_number, + register_locked); + + if (result == HACKRF_SUCCESS) { + printf("register [%2d] -> %s\n", + register_number, + register_locked ? "locked" : "unlocked"); + } else { + printf("hackrf_radio_lock_register() failed: %s (%d)\n", + hackrf_error_name(result), + result); + } + + return result; +} + int read_register( hackrf_device* device, uint8_t part, @@ -621,6 +645,20 @@ int write_register( return HACKRF_ERROR_INVALID_PARAM; } +int lock_register( + hackrf_device* device, + uint8_t part, + const uint16_t register_number, + const bool register_locked) +{ + switch (part) { + case PART_RADIO: + return radio_lock_register(device, register_number, register_locked); + default: + return HACKRF_ERROR_INVALID_PARAM; + } +} + static const char* mode_name(uint32_t mode) { const char* mode_names[] = {"IDLE", "WAIT", "RX", "TX_START", "TX_RUN"}; @@ -675,6 +713,7 @@ static void usage() printf("\t-n, --register : set register number for read/write operations\n"); printf("\t-r, --read: read register specified by last -n argument, or all registers\n"); printf("\t-w, --write : write register specified by last -n argument with value \n"); + printf("\t-L, --lock : lock register specified by last -n argument (0 for unlocked, 1 for locked)\n"); printf("\t-c, --config: print SI5351C multisynth configuration information\n"); printf("\t-d, --device : specify a particular device by serial number\n"); printf("\t-m, --max283x: target MAX283x\n"); @@ -709,6 +748,7 @@ static struct option long_options[] = { {"register", required_argument, 0, 'n'}, {"write", required_argument, 0, 'w'}, {"read", no_argument, 0, 'r'}, + {"lock", required_argument, 0, 'L'}, {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {"max2837", no_argument, 0, 'm'}, @@ -740,10 +780,12 @@ int main(int argc, char** argv) int bank = -1; uint64_t register_number = REGISTER_INVALID; uint64_t register_value; + uint64_t register_locked; hackrf_device* device = NULL; int option_index = 0; bool read = false; bool write = false; + bool lock = false; bool dump_config = false; bool dump_state = false; uint8_t part = PART_NONE; @@ -782,7 +824,7 @@ int main(int argc, char** argv) while ((opt = getopt_long( argc, argv, - "b:n:rw:d:cmsfgi1:2:C:N:P:ST:R:h?u:l:ta:o", + "b:n:rw:l:d:cmsfgi1:2:C:N:P:ST:R:h?u:l:ta:o", long_options, &option_index)) != EOF) { switch (opt) { @@ -805,6 +847,11 @@ int main(int argc, char** argv) read = true; break; + case 'L': + lock = true; + result = parse_int(optarg, ®ister_locked); + break; + case 'c': dump_config = true; break; @@ -931,8 +978,8 @@ int main(int argc, char** argv) } } - if (write && read) { - fprintf(stderr, "Read and write options are mutually exclusive.\n"); + if ((write && read) || (lock && write) || (lock && read)) { + fprintf(stderr, "Read, write and lock options are mutually exclusive.\n"); usage(); return EXIT_FAILURE; } @@ -949,6 +996,10 @@ int main(int argc, char** argv) return EXIT_FAILURE; } + if (lock && part != PART_RADIO) { + fprintf(stderr, "Lock option is only valid for radio.\n"); + } + if ((bank > -1) && (part != PART_RADIO)) { fprintf(stderr, "Bank valid only for radio.\n"); usage(); @@ -959,11 +1010,11 @@ int main(int argc, char** argv) bank = 0; } - if (!(write || read || dump_config || dump_state || set_tx_limit || + if (!(write || read || lock || dump_config || dump_state || set_tx_limit || set_rx_limit || set_ui || set_leds || set_p1 || set_p2 || set_clkin || set_narrowband || set_fpga_bitstream || read_selftest || test_rtc_osc || read_adc)) { - fprintf(stderr, "Specify read, write, or config option.\n"); + fprintf(stderr, "Specify read, write, lock, or config option.\n"); usage(); return EXIT_FAILURE; } @@ -1016,6 +1067,10 @@ int main(int argc, char** argv) } } + if (lock) { + result = lock_register(device, part, register_number, register_locked); + } + if (dump_config) { si5351c_read_configuration(device); } diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 7bafd5f39..a3b90163f 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -120,6 +120,7 @@ typedef enum { HACKRF_VENDOR_REQUEST_RADIO_WRITE_REG = 59, HACKRF_VENDOR_REQUEST_RADIO_READ_REG = 60, HACKRF_VENDOR_REQUEST_GET_BUFFER_SIZE = 61, + HACKRF_VENDOR_REQUEST_RADIO_LOCK_REG = 62, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -3574,6 +3575,33 @@ int ADDCALL hackrf_radio_write_register( } } +int ADDCALL hackrf_radio_lock_register( + hackrf_device* device, + const uint8_t register_number, + const bool register_locked) +{ + USB_API_REQUIRED(device, 0x0113); + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_RADIO_LOCK_REG, + register_locked, + register_number, + NULL, + 0, + DEFAULT_REQUEST_TIMEOUT); + + if (result != 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } + + return HACKRF_SUCCESS; +} + #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index eeca65e03..6cf71dd91 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -2385,6 +2385,20 @@ extern ADDAPI int ADDCALL hackrf_radio_write_register( const uint8_t register_number, const uint64_t value); +/** + * Lock or unlock a radio configuration register. + * + * @param[in] device device to write + * @param[in] register_number register number to mask + * @param[out] locked locked state for the register + * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant + * @ingroup debug + */ +extern ADDAPI int ADDCALL hackrf_radio_lock_register( + hackrf_device* device, + const uint8_t register_number, + const bool register_locked); + #ifdef __cplusplus } // __cplusplus defined. #endif From 335fd6a99e8d23994291e6c088fef7fb556124b8 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Wed, 24 Jun 2026 11:01:46 +0200 Subject: [PATCH 2/7] radio: add radio_set_config_mode() --- firmware/common/fpga.h | 8 +++++ firmware/common/radio.c | 67 +++++++++++++++++++++++++++++++++++++++++ firmware/common/radio.h | 9 ++++++ 3 files changed, 84 insertions(+) diff --git a/firmware/common/fpga.h b/firmware/common/fpga.h index 1765fcdd1..58152e687 100644 --- a/firmware/common/fpga.h +++ b/firmware/common/fpga.h @@ -26,6 +26,14 @@ #include "ice40_spi.h" +/* Supported Bitstreams. */ +typedef enum { + FPGA_BITSTREAM_STANDARD = 0, + FPGA_BITSTREAM_HALFPREC = 1, + FPGA_BITSTREAM_EXTPREC_RX = 2, + FPGA_BITSTREAM_EXTPREC_TX = 3, +} fpga_bitstream_index_t; + /* Up to 7 registers, each containing up to 8 bits of data */ #define FPGA_NUM_REGS 7 #define FPGA_DATA_REGS_MAX_VALUE 255 diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 3dd997d63..9755a02db 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -952,3 +952,70 @@ void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode) nvic_enable_irq(NVIC_USB0_IRQ); radio_update(radio); } + +bool radio_set_config_mode(radio_t* const radio, const radio_config_mode_t mode) +{ + // Check if the requested mode is supported. + switch (mode) { + case RADIO_CONFIG_LEGACY: + case RADIO_CONFIG_STANDARD: + // supported on all boards + break; +#ifdef IS_PRALINE + case RADIO_CONFIG_HALF_PRECISION: + case RADIO_CONFIG_EXT_PRECISION_RX: + case RADIO_CONFIG_EXT_PRECISION_TX: + // only supported on praline + if (!IS_PRALINE) { + return false; + } + break; +#endif + default: + return false; + } + + // Don't do anything if we're already in the requested mode. + if (mode == radio->config_mode) { + return true; + } + +#if defined(IS_PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE)) + if (IS_PRALINE) { + fpga_bitstream_index_t bitstream_index; + switch (mode) { + case RADIO_CONFIG_LEGACY: + case RADIO_CONFIG_STANDARD: + bitstream_index = FPGA_BITSTREAM_STANDARD; + break; + case RADIO_CONFIG_HALF_PRECISION: + bitstream_index = FPGA_BITSTREAM_HALFPREC; + break; + case RADIO_CONFIG_EXT_PRECISION_RX: + bitstream_index = FPGA_BITSTREAM_EXTPREC_RX; + break; + case RADIO_CONFIG_EXT_PRECISION_TX: + bitstream_index = FPGA_BITSTREAM_EXTPREC_TX; + break; + default: + return false; + } + + // Reset radio state. + for (uint8_t reg = 0; reg < RADIO_NUM_REGS; reg++) { + radio_reg_write(radio, RADIO_BANK_ALL, reg, RADIO_UNSET); + } + + // Load bitstream. + extern struct fpga_loader_t fpga_loader; + if (!fpga_image_load(&fpga_loader, bitstream_index)) { + return false; + } + } +#endif + + // Update radio config mode. + radio->config_mode = mode; + + return true; +} diff --git a/firmware/common/radio.h b/firmware/common/radio.h index 6724ad4a7..ba4ced388 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -41,12 +41,15 @@ typedef enum { } radio_error_t; /* radio configuration modes */ +// TODO are we sure we need a legacy mode? typedef enum { RADIO_CONFIG_LEGACY = 0, RADIO_CONFIG_STANDARD = 1, +#ifdef IS_PRALINE RADIO_CONFIG_EXT_PRECISION_RX = 2, RADIO_CONFIG_EXT_PRECISION_TX = 3, RADIO_CONFIG_HALF_PRECISION = 4, +#endif } radio_config_mode_t; typedef struct { @@ -271,6 +274,12 @@ bool radio_update(radio_t* const radio); */ void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode); +/** + * Switch to a new configuration mode. + * Return true if the mode was successfully switched. + */ +bool radio_set_config_mode(radio_t* const radio, const radio_config_mode_t mode); + /** * Driver instance. */ From e57fbe6ee6f1f15233bd30c19851455d48daf566 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Mon, 22 Jun 2026 16:40:10 +0200 Subject: [PATCH 3/7] Add 'hackrf_open_mode()', 'hackrf_open_mode_by_serial()' and 'hackrf_device_list_open_mode()' --- firmware/hackrf_usb/hackrf_usb.c | 2 + firmware/hackrf_usb/usb_api_transceiver.c | 36 +++++++++ firmware/hackrf_usb/usb_api_transceiver.h | 6 ++ host/libhackrf/src/hackrf.c | 95 ++++++++++++++++++++--- host/libhackrf/src/hackrf.h | 64 +++++++++++++++ 5 files changed, 193 insertions(+), 10 deletions(-) diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 4cdfbd14e..a228e6bb4 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -183,6 +183,8 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_radio_reg, usb_vendor_request_get_buffer_size, usb_vendor_request_lock_radio_reg, + usb_vendor_request_open, + usb_vendor_request_close, }; 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 dbbbf661a..c694d9d43 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -486,6 +486,42 @@ usb_request_status_t usb_vendor_request_get_buffer_size( return USB_REQUEST_STATUS_OK; } +usb_request_status_t usb_vendor_request_open( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + uint16_t usb_api_version; + radio_config_mode_t radio_config_mode; + + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_api_version = endpoint->setup.value; + radio_config_mode = (radio_config_mode_t) endpoint->setup.index; + + // TODO let device know we have a new libhackrf connection and its supported usb_api_version + (void) usb_api_version; + + // switch bitstreams and update radio mode + if (!radio_set_config_mode(&radio, radio_config_mode)) { + return USB_REQUEST_STATUS_STALL; + } + usb_transfer_schedule_ack(endpoint->in); + } + + return USB_REQUEST_STATUS_OK; +} + +usb_request_status_t usb_vendor_request_close( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + // TODO do nothing for now + usb_transfer_schedule_ack(endpoint->in); + } + + return USB_REQUEST_STATUS_OK; +} + /* clang-format off */ // Which GPDMA channel to use. diff --git a/firmware/hackrf_usb/usb_api_transceiver.h b/firmware/hackrf_usb/usb_api_transceiver.h index a29dec118..a28b8514e 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.h +++ b/firmware/hackrf_usb/usb_api_transceiver.h @@ -80,6 +80,12 @@ usb_request_status_t usb_vendor_request_set_rx_overrun_limit( usb_request_status_t usb_vendor_request_get_buffer_size( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); +usb_request_status_t usb_vendor_request_open( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); +usb_request_status_t usb_vendor_request_close( + 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/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index a3b90163f..fa6c7f84d 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -121,6 +121,8 @@ typedef enum { HACKRF_VENDOR_REQUEST_RADIO_READ_REG = 60, HACKRF_VENDOR_REQUEST_GET_BUFFER_SIZE = 61, HACKRF_VENDOR_REQUEST_RADIO_LOCK_REG = 62, + HACKRF_VENDOR_REQUEST_OPEN = 63, + HACKRF_VENDOR_REQUEST_CLOSE = 64, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -714,7 +716,10 @@ libusb_device_handle* hackrf_open_usb(const char* const desired_serial_number) return usb_device; } -static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** device) +static int hackrf_open_setup( + enum radio_config_mode mode, + libusb_device_handle* usb_device, + hackrf_device** device) { int result; hackrf_device* lib_device; @@ -796,6 +801,24 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d lib_device->buffer_size = 32768; } + if (lib_device->usb_api_version >= 0x0113) { + // Send supported usb api version and requested configuration mode to device. + result = libusb_control_transfer( + lib_device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_OPEN, + lib_device->usb_api_version, + (uint16_t) mode, + NULL, + 0, + DEFAULT_REQUEST_TIMEOUT); + if (result != 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } + } + result = pthread_mutex_init(&lib_device->transfer_lock, NULL); if (result != 0) { free(lib_device); @@ -834,7 +857,7 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d return HACKRF_SUCCESS; } -int ADDCALL hackrf_open(hackrf_device** device) +int ADDCALL hackrf_open_mode(const enum radio_config_mode mode, hackrf_device** device) { libusb_device_handle* usb_device; @@ -865,17 +888,18 @@ int ADDCALL hackrf_open(hackrf_device** device) return HACKRF_ERROR_NOT_FOUND; } - return hackrf_open_setup(usb_device, device); + return hackrf_open_setup(mode, usb_device, device); } -int ADDCALL hackrf_open_by_serial( +int ADDCALL hackrf_open_mode_by_serial( + const enum radio_config_mode mode, const char* const desired_serial_number, hackrf_device** device) { libusb_device_handle* usb_device; if (desired_serial_number == NULL) { - return hackrf_open(device); + return hackrf_open_mode(mode, device); } if (device == NULL) { @@ -888,10 +912,11 @@ int ADDCALL hackrf_open_by_serial( return HACKRF_ERROR_NOT_FOUND; } - return hackrf_open_setup(usb_device, device); + return hackrf_open_setup(mode, usb_device, device); } -int ADDCALL hackrf_device_list_open( +int ADDCALL hackrf_device_list_open_mode( + const enum radio_config_mode mode, hackrf_device_list_t* list, int idx, hackrf_device** device) @@ -912,7 +937,30 @@ int ADDCALL hackrf_device_list_open( return HACKRF_ERROR_LIBUSB; } - return hackrf_open_setup(usb_device, device); + return hackrf_open_setup(mode, usb_device, device); +} + +int ADDCALL hackrf_open(hackrf_device** device) +{ + return hackrf_open_mode(RADIO_CONFIG_STANDARD, device); +} + +int ADDCALL hackrf_open_by_serial( + const char* const desired_serial_number, + hackrf_device** device) +{ + return hackrf_open_mode_by_serial( + RADIO_CONFIG_STANDARD, + desired_serial_number, + device); +} + +int ADDCALL hackrf_device_list_open( + hackrf_device_list_t* list, + int idx, + hackrf_device** device) +{ + return hackrf_device_list_open_mode(RADIO_CONFIG_STANDARD, list, idx, device); } int ADDCALL hackrf_device_list_bus_sharing(hackrf_device_list_t* list, int idx) @@ -2443,10 +2491,11 @@ int ADDCALL hackrf_stop_tx(hackrf_device* device) int ADDCALL hackrf_close(hackrf_device* device) { - int result1, result2; + int result1, result2, result3; result1 = HACKRF_SUCCESS; result2 = HACKRF_SUCCESS; + result3 = HACKRF_SUCCESS; if (device != NULL) { result1 = hackrf_stop_cmd(device); @@ -2456,6 +2505,27 @@ int ADDCALL hackrf_close(hackrf_device* device) * also cancel any pending transmit/receive transfers. */ result2 = kill_transfer_thread(device); + + if (device->usb_api_version >= 0x0113) { + /* + * Let the device know it's been closed. + */ + result3 = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_CLOSE, + 0, + 0, + NULL, + 0, + DEFAULT_REQUEST_TIMEOUT); + if (result3 != 0) { + last_libusb_error = result3; + result3 = HACKRF_ERROR_LIBUSB; + } + } + if (device->usb_device != NULL) { libusb_release_interface(device->usb_device, 0); libusb_close(device->usb_device); @@ -2474,7 +2544,12 @@ int ADDCALL hackrf_close(hackrf_device* device) if (result2 != HACKRF_SUCCESS) { return result2; } - return result1; + + if (result1 != HACKRF_SUCCESS) { + return result1; + } + + return result3; } const char* ADDCALL hackrf_error_name(enum hackrf_error errcode) diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 6cf71dd91..88035c909 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -946,6 +946,19 @@ enum clkin_ctrl_signal { CLKIN_SIGNAL_P22 = 1, }; +/** + * HackRF Pro Radio Configuration Mode. + * + * Used by @ref hackrf_open, @ref hackrf_open_mode_by_serial and @ref hackrf_device_list_open_mode to set the active configuration mode. + */ +enum radio_config_mode { + RADIO_CONFIG_LEGACY = 0, + RADIO_CONFIG_STANDARD = 1, + RADIO_CONFIG_EXT_PRECISION_RX = 2, + RADIO_CONFIG_EXT_PRECISION_TX = 3, + RADIO_CONFIG_HALF_PRECISION = 4, +}; + /** * Opaque struct for hackrf device info. Object can be created via @ref hackrf_open, @ref hackrf_device_list_open or @ref hackrf_open_by_serial and be destroyed via @ref hackrf_close * @ingroup device @@ -1199,6 +1212,7 @@ extern ADDAPI hackrf_device_list_t* ADDCALL hackrf_device_list(); /** * Open a @ref hackrf_device from a device list + * @deprecated this function has been replaced by @ref hackrf_device_list_open_mode * @param[in] list device list to open device from * @param[in] idx index of the device to open * @param[out] device device handle to open @@ -1210,6 +1224,24 @@ extern ADDAPI int ADDCALL hackrf_device_list_open( int idx, hackrf_device** device); +/** + * Open a @ref hackrf_device from a device list and initialize it to the given radio configuration mode. + * + * Modes other than RADIO_CONFIG_STANDARD are only supported on HackRF Pro hardware. + * + * @param[in] mode configuration mode. Defaults to RADIO_CONFIG_STANDARD. Available modes are defined in @ref radio_config_mode. + * @param[in] list device list to open device from + * @param[in] idx index of the device to open + * @param[out] device device handle to open + * @return @ref HACKRF_SUCCESS on success, @ref HACKRF_ERROR_INVALID_PARAM on invalid parameters or other @ref hackrf_error variant + * @ingroup device + */ +extern ADDAPI int ADDCALL hackrf_device_list_open_mode( + const enum radio_config_mode mode, + hackrf_device_list_t* list, + int idx, + hackrf_device** device); + /** * Check if a listed HackRF device is sharing its USB bus with other devices. * @@ -1231,14 +1263,30 @@ extern ADDAPI void ADDCALL hackrf_device_list_free(hackrf_device_list_t* list); /** * Open first available HackRF device + * @deprecated this function has been replaced by @ref hackrf_open_mode * @param[out] device device handle * @return @ref HACKRF_SUCCESS on success, @ref HACKRF_ERROR_INVALID_PARAM if @p device is NULL, @ref HACKRF_ERROR_NOT_FOUND if no HackRF devices are found or other @ref hackrf_error variant * @ingroup device */ extern ADDAPI int ADDCALL hackrf_open(hackrf_device** device); +/** + * Open first available HackRF device and initialize it to the given radio configuration mode. + * + * Modes other than RADIO_CONFIG_STANDARD are only supported on HackRF Pro hardware. + * + * @param[in] mode configuration mode. Defaults to RADIO_CONFIG_STANDARD. Available modes are defined in @ref radio_config_mode. + * @param[out] device device handle + * @return @ref HACKRF_SUCCESS on success, @ref HACKRF_ERROR_INVALID_PARAM if @p device is NULL, @ref HACKRF_ERROR_NOT_FOUND if no HackRF devices are found or other @ref hackrf_error variant + * @ingroup device + */ +extern ADDAPI int ADDCALL hackrf_open_mode( + const enum radio_config_mode mode, + hackrf_device** device); + /** * Open HackRF device by serial number + * @deprecated this function has been replaced by @ref hackrf_open_mode_by_serial * @param[in] desired_serial_number serial number of device to open. If NULL then default to first device found. * @param[out] device device handle * @return @ref HACKRF_SUCCESS on success, @ref HACKRF_ERROR_INVALID_PARAM if @p device is NULL, @ref HACKRF_ERROR_NOT_FOUND if no HackRF devices are found or other @ref hackrf_error variant @@ -1248,6 +1296,22 @@ extern ADDAPI int ADDCALL hackrf_open_by_serial( const char* const desired_serial_number, hackrf_device** device); +/** + * Open HackRF device by serial number and initialize it to the given radio configuration mode. + * + * Modes other than RADIO_CONFIG_STANDARD are only supported on HackRF Pro hardware. + * + * @param[in] mode configuration mode. Defaults to RADIO_CONFIG_STANDARD. Available modes are defined in @ref radio_config_mode. + * @param[in] desired_serial_number serial number of device to open. If NULL then default to first device found. + * @param[out] device device handle + * @return @ref HACKRF_SUCCESS on success, @ref HACKRF_ERROR_INVALID_PARAM if @p device is NULL, @ref HACKRF_ERROR_NOT_FOUND if no HackRF devices are found or other @ref hackrf_error variant + * @ingroup device + */ +extern ADDAPI int ADDCALL hackrf_open_mode_by_serial( + const enum radio_config_mode mode, + const char* const desired_serial_number, + hackrf_device** device); + /** * Close a previously opened device * @param[in] device device to close From e03d4a14ef302467952d50346287f8f4d0508414 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Wed, 24 Jun 2026 11:03:24 +0200 Subject: [PATCH 4/7] Add 'hackrf_radio_set_mode()' --- firmware/hackrf_usb/CMakeLists.txt | 1 + firmware/hackrf_usb/hackrf_usb.c | 2 ++ firmware/hackrf_usb/usb_api_radio.c | 41 +++++++++++++++++++++++++++++ firmware/hackrf_usb/usb_api_radio.h | 29 ++++++++++++++++++++ host/libhackrf/src/hackrf.c | 25 ++++++++++++++++++ host/libhackrf/src/hackrf.h | 12 +++++++++ 6 files changed, 110 insertions(+) create mode 100644 firmware/hackrf_usb/usb_api_radio.c create mode 100644 firmware/hackrf_usb/usb_api_radio.h diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 871e0909f..027fc97a8 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -60,6 +60,7 @@ set(SRC_M4 usb_api_spiflash.c usb_api_transceiver.c usb_api_operacake.c + usb_api_radio.c usb_api_sweep.c usb_api_selftest.c usb_api_ui.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index a228e6bb4..8cfeabe85 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -75,6 +75,7 @@ #include "usb_api_board_info.h" #include "usb_api_m0_state.h" #include "usb_api_operacake.h" +#include "usb_api_radio.h" #include "usb_api_register.h" #include "usb_api_selftest.h" #include "usb_api_spiflash.h" @@ -185,6 +186,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_lock_radio_reg, usb_vendor_request_open, usb_vendor_request_close, + usb_vendor_request_set_radio_mode, }; static const uint32_t vendor_request_handler_count = diff --git a/firmware/hackrf_usb/usb_api_radio.c b/firmware/hackrf_usb/usb_api_radio.c new file mode 100644 index 000000000..e85c7d987 --- /dev/null +++ b/firmware/hackrf_usb/usb_api_radio.c @@ -0,0 +1,41 @@ +/* + * Copyright 2026 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 +#include +#include + +#include "usb_queue.h" + +usb_request_status_t usb_vendor_request_set_radio_mode( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + radio_config_mode_t mode = endpoint->setup.value; + if (!radio_set_config_mode(&radio, mode)) { + return USB_REQUEST_STATUS_STALL; + } + usb_transfer_schedule_ack(endpoint->in); + } + + return USB_REQUEST_STATUS_OK; +} diff --git a/firmware/hackrf_usb/usb_api_radio.h b/firmware/hackrf_usb/usb_api_radio.h new file mode 100644 index 000000000..9eb098d0d --- /dev/null +++ b/firmware/hackrf_usb/usb_api_radio.h @@ -0,0 +1,29 @@ +/* + * Copyright 2026 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. + */ + +#pragma once + +#include +#include + +usb_request_status_t usb_vendor_request_set_radio_mode( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index fa6c7f84d..c9d67c905 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -123,6 +123,7 @@ typedef enum { HACKRF_VENDOR_REQUEST_RADIO_LOCK_REG = 62, HACKRF_VENDOR_REQUEST_OPEN = 63, HACKRF_VENDOR_REQUEST_CLOSE = 64, + HACKRF_VENDOR_REQUEST_RADIO_SET_MODE = 65, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -3677,6 +3678,30 @@ int ADDCALL hackrf_radio_lock_register( return HACKRF_SUCCESS; } +int ADDCALL hackrf_radio_set_mode(hackrf_device* device, const enum radio_config_mode mode) +{ + USB_API_REQUIRED(device, 0x0113); + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_RADIO_SET_MODE, + mode, + 0, + NULL, + 0, + DEFAULT_REQUEST_TIMEOUT); + + if (result != 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } + + return HACKRF_SUCCESS; +} + #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 88035c909..5d2a27a2c 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -2463,6 +2463,18 @@ extern ADDAPI int ADDCALL hackrf_radio_lock_register( const uint8_t register_number, const bool register_locked); +/** + * Switches the radio configuration mode. + * + * @param[in] device device to configure + * @param[in] mode configuration mode. Defaults to RADIO_CONFIG_LEGACY. Available modes are defined in @ref radio_config_mode. + * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant + * @ingroup configuration + */ +extern ADDAPI int ADDCALL hackrf_radio_set_mode( + hackrf_device* device, + const enum radio_config_mode mode); + #ifdef __cplusplus } // __cplusplus defined. #endif From 161f6275f6a36d1658c041635676f3051b4f1ba2 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Mon, 29 Jun 2026 15:03:12 +0200 Subject: [PATCH 5/7] Remove 'RADIO_CONFIG_LEGACY' --- firmware/common/radio.c | 2 -- firmware/common/radio.h | 10 ++++------ host/libhackrf/src/hackrf.h | 11 +++++------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 9755a02db..d9a9f80b7 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -957,7 +957,6 @@ bool radio_set_config_mode(radio_t* const radio, const radio_config_mode_t mode) { // Check if the requested mode is supported. switch (mode) { - case RADIO_CONFIG_LEGACY: case RADIO_CONFIG_STANDARD: // supported on all boards break; @@ -984,7 +983,6 @@ bool radio_set_config_mode(radio_t* const radio, const radio_config_mode_t mode) if (IS_PRALINE) { fpga_bitstream_index_t bitstream_index; switch (mode) { - case RADIO_CONFIG_LEGACY: case RADIO_CONFIG_STANDARD: bitstream_index = FPGA_BITSTREAM_STANDARD; break; diff --git a/firmware/common/radio.h b/firmware/common/radio.h index ba4ced388..df7b6462d 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -41,14 +41,12 @@ typedef enum { } radio_error_t; /* radio configuration modes */ -// TODO are we sure we need a legacy mode? typedef enum { - RADIO_CONFIG_LEGACY = 0, - RADIO_CONFIG_STANDARD = 1, + RADIO_CONFIG_STANDARD = 0, #ifdef IS_PRALINE - RADIO_CONFIG_EXT_PRECISION_RX = 2, - RADIO_CONFIG_EXT_PRECISION_TX = 3, - RADIO_CONFIG_HALF_PRECISION = 4, + RADIO_CONFIG_EXT_PRECISION_RX = 1, + RADIO_CONFIG_EXT_PRECISION_TX = 2, + RADIO_CONFIG_HALF_PRECISION = 3, #endif } radio_config_mode_t; diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 5d2a27a2c..60666a3a3 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -952,11 +952,10 @@ enum clkin_ctrl_signal { * Used by @ref hackrf_open, @ref hackrf_open_mode_by_serial and @ref hackrf_device_list_open_mode to set the active configuration mode. */ enum radio_config_mode { - RADIO_CONFIG_LEGACY = 0, - RADIO_CONFIG_STANDARD = 1, - RADIO_CONFIG_EXT_PRECISION_RX = 2, - RADIO_CONFIG_EXT_PRECISION_TX = 3, - RADIO_CONFIG_HALF_PRECISION = 4, + RADIO_CONFIG_STANDARD = 0, + RADIO_CONFIG_EXT_PRECISION_RX = 1, + RADIO_CONFIG_EXT_PRECISION_TX = 2, + RADIO_CONFIG_HALF_PRECISION = 3, }; /** @@ -2467,7 +2466,7 @@ extern ADDAPI int ADDCALL hackrf_radio_lock_register( * Switches the radio configuration mode. * * @param[in] device device to configure - * @param[in] mode configuration mode. Defaults to RADIO_CONFIG_LEGACY. Available modes are defined in @ref radio_config_mode. + * @param[in] mode configuration mode. Defaults to RADIO_CONFIG_STANDARD. Available modes are defined in @ref radio_config_mode. * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant * @ingroup configuration */ From 2f4fa473fc0fd08f94dfede5cc72ff5ac817bc87 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 16 Jun 2026 14:35:57 +0200 Subject: [PATCH 6/7] libhackrf: add 'hackrf_radio_set_frequency()', 'hackrf_radio_set_frequency_explicit()' and 'hackrf_radio_set_sample_rate()' --- firmware/hackrf_usb/hackrf_usb.c | 3 + firmware/hackrf_usb/usb_api_radio.c | 100 ++++++++++++++++++ firmware/hackrf_usb/usb_api_radio.h | 12 +++ host/libhackrf/src/hackrf.c | 156 ++++++++++++++++++++++++++++ host/libhackrf/src/hackrf.h | 107 ++++++++++++++++++- 5 files changed, 375 insertions(+), 3 deletions(-) diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 8cfeabe85..95bfb6e52 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -187,6 +187,9 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_open, usb_vendor_request_close, usb_vendor_request_set_radio_mode, + usb_vendor_request_set_radio_frequency, + usb_vendor_request_set_radio_frequency_explicit, + usb_vendor_request_set_radio_sample_rate, }; static const uint32_t vendor_request_handler_count = diff --git a/firmware/hackrf_usb/usb_api_radio.c b/firmware/hackrf_usb/usb_api_radio.c index e85c7d987..13c547cfe 100644 --- a/firmware/hackrf_usb/usb_api_radio.c +++ b/firmware/hackrf_usb/usb_api_radio.c @@ -19,6 +19,10 @@ * Boston, MA 02110-1301, USA. */ +#include +#include + +#include #include #include #include @@ -39,3 +43,99 @@ usb_request_status_t usb_vendor_request_set_radio_mode( return USB_REQUEST_STATUS_OK; } + +usb_request_status_t usb_vendor_request_set_radio_frequency( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static fp_40_24_t hz_param; + + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + &hz_param, + sizeof(fp_40_24_t), + NULL, + NULL); + } else if (stage == USB_TRANSFER_STAGE_DATA) { + radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_FREQUENCY_RF, hz_param); + radio_reg_write( + &radio, + RADIO_BANK_ACTIVE, + RADIO_FREQUENCY_IF, + RADIO_UNSET); + radio_reg_write( + &radio, + RADIO_BANK_ACTIVE, + RADIO_FREQUENCY_LO, + RADIO_UNSET); + radio_reg_write( + &radio, + RADIO_BANK_ACTIVE, + RADIO_IMAGE_REJECT, + RADIO_UNSET); + usb_transfer_schedule_ack(endpoint->in); + } + + return USB_REQUEST_STATUS_OK; +} + +usb_request_status_t usb_vendor_request_set_radio_frequency_explicit( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static struct { + fp_40_24_t if_freq_hz; + fp_40_24_t lo_freq_hz; + uint8_t path; + } params; + + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + ¶ms, + sizeof(params), + NULL, + NULL); + } else if (stage == USB_TRANSFER_STAGE_DATA) { + radio_reg_write( + &radio, + RADIO_BANK_ACTIVE, + RADIO_FREQUENCY_IF, + params.if_freq_hz); + radio_reg_write( + &radio, + RADIO_BANK_ACTIVE, + RADIO_FREQUENCY_LO, + params.lo_freq_hz); + radio_reg_write( + &radio, + RADIO_BANK_ACTIVE, + RADIO_IMAGE_REJECT, + params.path); + usb_transfer_schedule_ack(endpoint->in); + } + + return USB_REQUEST_STATUS_OK; +} + +usb_request_status_t usb_vendor_request_set_radio_sample_rate( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + static fp_28_36_t sps_param; + + if (stage == USB_TRANSFER_STAGE_SETUP) { + usb_transfer_schedule_block( + endpoint->out, + &sps_param, + sizeof(fp_28_36_t), + NULL, + NULL); + } else if (stage == USB_TRANSFER_STAGE_DATA) { + radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_SAMPLE_RATE, sps_param); + usb_transfer_schedule_ack(endpoint->in); + } + + return USB_REQUEST_STATUS_OK; +} diff --git a/firmware/hackrf_usb/usb_api_radio.h b/firmware/hackrf_usb/usb_api_radio.h index 9eb098d0d..c8940c112 100644 --- a/firmware/hackrf_usb/usb_api_radio.h +++ b/firmware/hackrf_usb/usb_api_radio.h @@ -27,3 +27,15 @@ usb_request_status_t usb_vendor_request_set_radio_mode( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_set_radio_frequency( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_set_radio_frequency_explicit( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); + +usb_request_status_t usb_vendor_request_set_radio_sample_rate( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index c9d67c905..06c96f838 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -124,6 +124,9 @@ typedef enum { HACKRF_VENDOR_REQUEST_OPEN = 63, HACKRF_VENDOR_REQUEST_CLOSE = 64, HACKRF_VENDOR_REQUEST_RADIO_SET_MODE = 65, + HACKRF_VENDOR_REQUEST_RADIO_SET_FREQUENCY = 66, + HACKRF_VENDOR_REQUEST_RADIO_SET_FREQUENCY_EXPLICIT = 67, + HACKRF_VENDOR_REQUEST_RADIO_SET_SAMPLE_RATE = 68, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -3702,6 +3705,159 @@ int ADDCALL hackrf_radio_set_mode(hackrf_device* device, const enum radio_config return HACKRF_SUCCESS; } +int ADDCALL hackrf_str_to_fp64(const uint8_t Qn, const char* str, uint64_t* const value) +{ + uint64_t m = 0; + uint64_t n = 0; + uint64_t div = 1; + uint64_t div_max = ceil((double) Qn * log10(2.0)) * 10; + + // validate input + char* endptr; + strtod(str, &endptr); + if (endptr == str || *endptr != '\0') { + return HACKRF_ERROR_INVALID_PARAM; + } + + // parse integer component + for (; (*str >= '0') && (*str <= '9'); str++) { + m = (m * 10) + (*str - '0'); + } + *value = (m << Qn); + + if (*str != '.') { + return HACKRF_SUCCESS; + } + str++; + + // parse fractional component + for (; (*str >= '0') && (*str <= '9') && (div < div_max); str++) { + n = (n * 10) + (*str - '0'); + div *= 10; + } + + if (div == 1) { + return HACKRF_SUCCESS; + } + + *value += ((n << Qn) + (div / 2)) / div; + + return HACKRF_SUCCESS; +} + +int ADDCALL hackrf_radio_set_frequency(hackrf_device* device, const fp_40_24_t freq_hz) +{ + USB_API_REQUIRED(device, 0x0113); + uint64_t set_freq_fp_param; + uint8_t length; + int result; + + // serialize parameters + set_freq_fp_param = TO_LE64(freq_hz); + length = sizeof(uint64_t); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_RADIO_SET_FREQUENCY, + 0, + 0, + (unsigned char*) &set_freq_fp_param, + length, + DEFAULT_REQUEST_TIMEOUT); + + if (result < length) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } + + return HACKRF_SUCCESS; +} + +int ADDCALL hackrf_radio_set_frequency_explicit( + hackrf_device* device, + const fp_40_24_t if_freq_hz, + const fp_40_24_t lo_freq_hz, + const enum rf_path_filter path) +{ + struct { + fp_40_24_t if_freq_hz; + fp_40_24_t lo_freq_hz; + uint8_t path; + } params; + + uint8_t length; + int result; + + // TODO are these values still correct for HackRF Pro ? + if (FP_FREQ_HZ(if_freq_hz) < 2000000000 || FP_FREQ_HZ(if_freq_hz) > 3000000000) { + return HACKRF_ERROR_INVALID_PARAM; + } + + // TODO are these values still correct for HackRF Pro ? + if ((path != RF_PATH_FILTER_BYPASS) && + (FP_FREQ_HZ(lo_freq_hz) < 84375000 || FP_FREQ_HZ(lo_freq_hz) > 5400000000)) { + return HACKRF_ERROR_INVALID_PARAM; + } + + if (path > 2) { + return HACKRF_ERROR_INVALID_PARAM; + } + + params.if_freq_hz = TO_LE(if_freq_hz); + params.lo_freq_hz = TO_LE(lo_freq_hz); + params.path = (uint8_t) path; + length = sizeof(params); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_RADIO_SET_FREQUENCY_EXPLICIT, + 0, + 0, + (unsigned char*) ¶ms, + length, + DEFAULT_REQUEST_TIMEOUT); + + if (result < length) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } else { + return HACKRF_SUCCESS; + } +} + +int ADDCALL hackrf_radio_set_sample_rate(hackrf_device* device, const fp_28_36_t freq_hz) +{ + USB_API_REQUIRED(device, 0x0113); + uint64_t set_sr_fp_param; + uint8_t length; + int result; + + set_sr_fp_param = TO_LE64(freq_hz); + length = sizeof(uint64_t); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_RADIO_SET_SAMPLE_RATE, + 0, + 0, + (unsigned char*) &set_sr_fp_param, + length, + DEFAULT_REQUEST_TIMEOUT); + + if (result < length) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } + + return HACKRF_SUCCESS; +} + #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/host/libhackrf/src/hackrf.h b/host/libhackrf/src/hackrf.h index 60666a3a3..699f5d141 100644 --- a/host/libhackrf/src/hackrf.h +++ b/host/libhackrf/src/hackrf.h @@ -23,6 +23,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSI #pragma once +#include #include #include #include // for bool @@ -1815,7 +1816,8 @@ extern ADDAPI int ADDCALL hackrf_usb_api_version_read( * Simple (auto) tuning via specifying a center frequency in Hz * * This setting is not exact and depends on the PLL settings. Exact resolution is not determined, but the actual tuned frequency will be queryable in the future. - * + * + * @deprecated this function has been replaced by @ref hackrf_radio_set_frequency * @param device device to tune * @param freq_hz center frequency in Hz. Defaults to 900MHz. Should be in range 1-6000MHz, but 0-7250MHz is possible. The resolution is ~50Hz, I could not find the exact number. * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant @@ -1827,7 +1829,8 @@ extern ADDAPI int ADDCALL hackrf_set_freq(hackrf_device* device, const uint64_t * Set the center frequency via explicit tuning * * Center frequency is set to \f$f_{center} = f_{IF} + k\cdot f_{LO}\f$ where \f$k\in\left\{-1; 0; 1\right\}\f$, depending on the value of @p path. See the documentation of @ref rf_path_filter for details - * + * + * @deprecated this function has been replaced by @ref hackrf_radio_set_frequency_explicit * @param device device to tune * @param if_freq_hz tuning frequency of the MAX2837 transceiver IC in Hz. Must be in the range of 2150-2750MHz * @param lo_freq_hz tuning frequency of the RFFC5072 mixer/synthesizer IC in Hz. Must be in the range 84.375-5400MHz, defaults to 1000MHz. No effect if @p path is set to @ref RF_PATH_FILTER_BYPASS @@ -1848,7 +1851,8 @@ extern ADDAPI int ADDCALL hackrf_set_freq_explicit( * * This function sets the sample rate by specifying a clock frequency in Hz and a divider, so the resulting sample rate will be @p freq_hz / @p divider. * This function also sets the baseband filter bandwidth to a value \f$ \le 0.75 \cdot F_s \f$, so any calls to @ref hackrf_set_baseband_filter_bandwidth should only be made after this. - * + * + * @deprecated this function has been replaced by @ref hackrf_radio_set_sample_rate * @param device device to configure * @param freq_hz sample rate base frequency in Hz * @param divider frequency divider. Must be in the range 1-31 @@ -1866,6 +1870,7 @@ extern ADDAPI int ADDCALL hackrf_set_sample_rate_manual( * Sample rate should be in the range 2-20MHz, with the default being 10MHz. Lower & higher values are technically possible, but the performance is not guaranteed. * This function also sets the baseband filter bandwidth to a value \f$ \le 0.75 \cdot F_s \f$, so any calls to @ref hackrf_set_baseband_filter_bandwidth should only be made after this. * + * @deprecated this function has been replaced by @ref hackrf_radio_set_sample_rate * @param device device to configure * @param freq_hz sample rate frequency in Hz. Should be in the range 2-20MHz * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant @@ -2474,6 +2479,102 @@ extern ADDAPI int ADDCALL hackrf_radio_set_mode( hackrf_device* device, const enum radio_config_mode mode); +/** + * 40.24 Fixed-point type. + * + * Used by @ref hackrf_radio_set_frequency and @ref hackrf_radio_set_frequency_explicit + * to represent a fractional tuning frequency. + */ +typedef uint64_t fp_40_24_t; + +/** + * 28.36 Fixed-point type. + * + * Used by @ref hackrf_radio_set_sample_rate to represent a fractional sample rate. + */ +typedef uint64_t fp_28_36_t; + +/** + * Convert an integer frequency value to its corresponding fixed-point value. + */ +#define FREQ_FP_HZ(u64) ((uint64_t) u64 << 24) + +/** + * Convert an integer sample rate value to its corresponding fixed-point value. + */ +#define SR_FP_HZ(u64) ((uint64_t) u64 << 36) + +/** + * Convert a fixed-point frequency value to its corresponding integer value, rounding up to the nearest integer. + * + */ +#define FP_FREQ_HZ(u64) (((uint64_t) u64 + ((1ULL << 24) - 1)) >> 24) + +/** + * Convert a fixed-point sample rate value to its corresponding integer value, rounding up to the nearest integer. + */ +#define FP_SR_HZ(u64) (((uint64_t) u64 + ((1ULL << 36) - 1)) >> 36) + +/** + * Convert ASCII string to a 64 bit fixed-point number with a given number of bits of accuracy. + * + * @param[in] str string to parse + * @param[in] Qn number of bits to use for fractional part + * @param[out] value converted value + * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant + * @ingroup configuration + */ +extern ADDAPI int ADDCALL hackrf_str_to_fp64( + const uint8_t Qn, + const char* str, + uint64_t* const value); + +/** + * Set the radio center frequency to a fractional, fixed-point value. + * + * @param[in] device device to tune + * @param[in] hz center frequency in Hz + * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant + * @ingroup configuration + */ +extern ADDAPI int ADDCALL hackrf_radio_set_frequency( + hackrf_device* device, + const fp_40_24_t freq_hz); + +/** + * Set the radio center frequency to via explicit tuning. + * + * Center frequency is set to \f$f_{center} = f_{IF} + k\cdot f_{LO}\f$ where \f$k\in\left\{-1; 0; 1\right\}\f$, depending on the value of @p path. See the documentation of @ref rf_path_filter for details + * + * @param device device to tune + * TODO are these values still applicable for HackRF Pro ? + * @param if_freq_hz tuning frequency of the MAX2837 transceiver IC in Hz. Must be in the range of 2150-2750MHz + * TODO are these values still applicable for HackRF Pro ? + * @param lo_freq_hz tuning frequency of the RFFC5072 mixer/synthesizer IC in Hz. Must be in the range 84.375-5400MHz, defaults to 1000MHz. No effect if @p path is set to @ref RF_PATH_FILTER_BYPASS + * @param path filter path for mixer. See the documentation for @ref rf_path_filter for details + * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant + * @ingroup configuration + */ +extern ADDAPI int ADDCALL hackrf_radio_set_frequency_explicit( + hackrf_device* device, + const fp_40_24_t if_freq_hz, + const fp_40_24_t lo_freq_hz, + const enum rf_path_filter path); + +/** + * Set the radio sample rate to a fractional, fixed-point value. + * + * This function does not automatically configure the baseband filter bandwidth, so any calls to this function should be followed by @ref hackrf_set_baseband_filter_bandwidth should it require adjustment. + * + * @param[in] device device to configure + * @param[in] sps samples per second + * @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant + * @ingroup configuration + */ +extern ADDAPI int ADDCALL hackrf_radio_set_sample_rate( + hackrf_device* device, + const fp_28_36_t freq_hz); + #ifdef __cplusplus } // __cplusplus defined. #endif From 754425aa3501c22f95f17278c2f3d511aba849ee Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 23 Jun 2026 12:04:17 +0200 Subject: [PATCH 7/7] libhackrf: fallback to original code path for new_host -> old_firmware --- host/libhackrf/src/hackrf.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 06c96f838..dc59adc7b 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -204,6 +204,8 @@ static const max2837_ft_t max2837_ft[] = { if (device->usb_api_version < version) \ return HACKRF_ERROR_USB_API_VERSION; +#define USB_API_REQUIRED_OR(device, version) if (device->usb_api_version < version) + static const uint16_t hackrf_usb_vid = 0x1d50; static const uint16_t hackrf_jawbreaker_usb_pid = 0x604b; static const uint16_t hackrf_one_usb_pid = 0x6089; @@ -3747,7 +3749,11 @@ int ADDCALL hackrf_str_to_fp64(const uint8_t Qn, const char* str, uint64_t* cons int ADDCALL hackrf_radio_set_frequency(hackrf_device* device, const fp_40_24_t freq_hz) { - USB_API_REQUIRED(device, 0x0113); + USB_API_REQUIRED_OR(device, 0x0113) + { + return hackrf_set_freq(device, FP_FREQ_HZ(freq_hz)); + } + uint64_t set_freq_fp_param; uint8_t length; int result; @@ -3781,6 +3787,15 @@ int ADDCALL hackrf_radio_set_frequency_explicit( const fp_40_24_t lo_freq_hz, const enum rf_path_filter path) { + USB_API_REQUIRED_OR(device, 0x0113) + { + return hackrf_set_freq_explicit( + device, + FP_FREQ_HZ(if_freq_hz), + FP_FREQ_HZ(lo_freq_hz), + path); + } + struct { fp_40_24_t if_freq_hz; fp_40_24_t lo_freq_hz; @@ -3831,7 +3846,11 @@ int ADDCALL hackrf_radio_set_frequency_explicit( int ADDCALL hackrf_radio_set_sample_rate(hackrf_device* device, const fp_28_36_t freq_hz) { - USB_API_REQUIRED(device, 0x0113); + USB_API_REQUIRED_OR(device, 0x0113) + { + return hackrf_set_sample_rate(device, (double) freq_hz / (1ULL << 36)); + } + uint64_t set_sr_fp_param; uint8_t length; int result;