Add radio register access from host

This commit is contained in:
Michael Ossmann
2026-02-11 11:21:43 -05:00
parent 547a851fba
commit b4041dd550
8 changed files with 357 additions and 37 deletions

View File

@@ -152,6 +152,8 @@ static usb_request_handler_fn vendor_request_handler[] = {
usb_vendor_request_read_selftest,
usb_vendor_request_adc_read,
usb_vendor_request_test_rtc_osc,
usb_vendor_request_write_radio_reg,
usb_vendor_request_read_radio_reg,
};
static const uint32_t vendor_request_handler_count =

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
* Copyright 2013 Benjamin Vernoux
*
@@ -283,3 +283,84 @@ usb_request_status_t usb_vendor_request_read_fpga_reg(
return USB_REQUEST_STATUS_OK;
}
#endif
/*
* Each register is transferred as a uint8_t register number followed by a
* little-endian uint64_t value for a total of 9 bytes.
*/
static uint8_t radio_reg_buf[RADIO_NUM_REGS * 9];
usb_request_status_t usb_vendor_request_write_radio_reg(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
uint8_t bank;
if (stage == USB_TRANSFER_STAGE_SETUP) {
bank = endpoint->setup.index;
if (bank >= RADIO_NUM_BANKS) {
return USB_REQUEST_STATUS_STALL;
}
uint8_t num_regs = endpoint->setup.length / 9;
if ((num_regs == 0) || (num_regs > RADIO_NUM_REGS)) {
return USB_REQUEST_STATUS_STALL;
}
usb_transfer_schedule_block(
endpoint->out,
&radio_reg_buf,
endpoint->setup.length,
NULL,
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
uint8_t address, i;
uint64_t value;
bank = endpoint->setup.index;
for (i = 0; i < endpoint->setup.length; i += 9) {
address = radio_reg_buf[i];
value = radio_reg_buf[i + 1] |
((uint64_t) radio_reg_buf[i + 2] << 8) |
((uint64_t) radio_reg_buf[i + 3] << 16) |
((uint64_t) radio_reg_buf[i + 4] << 24) |
((uint64_t) radio_reg_buf[i + 5] << 32) |
((uint64_t) radio_reg_buf[i + 6] << 40) |
((uint64_t) radio_reg_buf[i + 7] << 48) |
((uint64_t) radio_reg_buf[i + 8] << 56);
radio_error_t result =
radio_reg_write(&radio, bank, address, value);
if (result != RADIO_OK) {
return USB_REQUEST_STATUS_STALL;
}
}
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;
}
usb_request_status_t usb_vendor_request_read_radio_reg(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
uint8_t bank = endpoint->setup.index;
uint8_t address = endpoint->setup.value;
if ((bank >= RADIO_NUM_BANKS) || (address >= RADIO_NUM_REGS)) {
return USB_REQUEST_STATUS_STALL;
}
uint64_t value = radio_reg_read(&radio, bank, address);
endpoint->buffer[0] = value & 0xff;
endpoint->buffer[1] = (value >> 8) & 0xff;
endpoint->buffer[2] = (value >> 16) & 0xff;
endpoint->buffer[3] = (value >> 24) & 0xff;
endpoint->buffer[4] = (value >> 32) & 0xff;
endpoint->buffer[5] = (value >> 40) & 0xff;
endpoint->buffer[6] = (value >> 48) & 0xff;
endpoint->buffer[7] = (value >> 56) & 0xff;
usb_transfer_schedule_block(
endpoint->in,
&endpoint->buffer,
8,
NULL,
NULL);
usb_transfer_schedule_ack(endpoint->out);
}
return USB_REQUEST_STATUS_OK;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
* Copyright 2013 Benjamin Vernoux
*
@@ -65,5 +65,11 @@ usb_request_status_t usb_vendor_request_read_fpga_reg(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
#endif
usb_request_status_t usb_vendor_request_write_radio_reg(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
usb_request_status_t usb_vendor_request_read_radio_reg(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage);
#endif /* end of include guard: __USB_API_REGISTER_H__ */

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone
*
* This file is part of HackRF.
@@ -37,7 +37,7 @@
#define USB_PRODUCT_ID (0xFFFF)
#endif
#define USB_API_VERSION (0x0110)
#define USB_API_VERSION (0x0111)
#define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
* Copyright 2012 Jared Boone <jared@sharebrained.com>
* Copyright 2013 Benjamin Vernoux <titanmkd@gmail.com>
* Copyright 2017 Dominic Spill <dominicgs@gmail.com>
@@ -29,6 +29,7 @@
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <inttypes.h>
#define REGISTER_INVALID 32767
@@ -39,13 +40,14 @@ enum parts {
PART_RFFC5072 = 3,
PART_MAX2831 = 4,
PART_GATEWARE = 5,
PART_RADIO = 6,
};
int parse_int(char* s, uint32_t* const value)
int parse_int(char* s, uint64_t* const value)
{
uint_fast8_t base = 10;
char* s_end;
long long_value;
long long ll_value;
if (strlen(s) > 2) {
if (s[0] == '0') {
@@ -60,9 +62,9 @@ int parse_int(char* s, uint32_t* const value)
}
s_end = s;
long_value = strtol(s, &s_end, base);
ll_value = strtoull(s, &s_end, base);
if ((s != s_end) && (*s_end == 0)) {
*value = (uint32_t) long_value;
*value = (uint64_t) ll_value;
return HACKRF_SUCCESS;
} else {
return HACKRF_ERROR_INVALID_PARAM;
@@ -473,7 +475,81 @@ int fpga_write_register(
return result;
}
int read_register(hackrf_device* device, uint8_t part, const uint16_t register_number)
int radio_read_register(
hackrf_device* device,
const uint8_t bank,
const uint16_t register_number)
{
uint64_t register_value;
int result = hackrf_radio_read_register(
device,
bank,
(uint8_t) register_number,
&register_value);
if (result == HACKRF_SUCCESS) {
printf("bank %d register [%2d] -> 0x%016" PRIx64 "\n",
bank,
register_number,
register_value);
} else {
printf("hackrf_radio_read_register() failed: %s (%d)\n",
hackrf_error_name(result),
result);
}
return result;
}
#define RADIO_NUM_REGS (24)
int radio_read_registers(hackrf_device* device, const uint8_t bank)
{
uint16_t register_number;
int result = HACKRF_SUCCESS;
for (register_number = 0; register_number < RADIO_NUM_REGS; register_number++) {
result = radio_read_register(device, bank, register_number);
if (result != HACKRF_SUCCESS) {
break;
}
}
return result;
}
int radio_write_register(
hackrf_device* device,
const uint8_t bank,
const uint16_t register_number,
const uint64_t register_value)
{
int result = HACKRF_SUCCESS;
result = hackrf_radio_write_register(
device,
bank,
(uint8_t) register_number,
register_value);
if (result == HACKRF_SUCCESS) {
printf("bank %d 0x%016" PRIx64 " -> [%2d]\n",
bank,
register_value,
register_number);
} else {
printf("hackrf_radio_write_register() failed: %s (%d)\n",
hackrf_error_name(result),
result);
}
return result;
}
int read_register(
hackrf_device* device,
uint8_t part,
const uint8_t bank,
const uint16_t register_number)
{
switch (part) {
case PART_MAX2837:
@@ -485,11 +561,13 @@ int read_register(hackrf_device* device, uint8_t part, const uint16_t register_n
return rffc5072_read_register(device, register_number);
case PART_GATEWARE:
return fpga_read_register(device, register_number);
case PART_RADIO:
return radio_read_register(device, bank, register_number);
}
return HACKRF_ERROR_INVALID_PARAM;
}
int read_registers(hackrf_device* device, uint8_t part)
int read_registers(hackrf_device* device, uint8_t part, const uint8_t bank)
{
switch (part) {
case PART_MAX2837:
@@ -501,6 +579,8 @@ int read_registers(hackrf_device* device, uint8_t part)
return rffc5072_read_registers(device);
case PART_GATEWARE:
return fpga_read_registers(device);
case PART_RADIO:
return radio_read_registers(device, bank);
}
return HACKRF_ERROR_INVALID_PARAM;
}
@@ -508,8 +588,9 @@ int read_registers(hackrf_device* device, uint8_t part)
int write_register(
hackrf_device* device,
uint8_t part,
const uint8_t bank,
const uint16_t register_number,
const uint16_t register_value)
const uint64_t register_value)
{
switch (part) {
case PART_MAX2837:
@@ -517,14 +598,25 @@ int write_register(
return max283x_write_register(
device,
register_number,
register_value,
(uint16_t) register_value,
part);
case PART_SI5351C:
return si5351c_write_register(device, register_number, register_value);
return si5351c_write_register(
device,
register_number,
(uint16_t) register_value);
case PART_RFFC5072:
return rffc5072_write_register(device, register_number, register_value);
return rffc5072_write_register(
device,
register_number,
(uint16_t) register_value);
case PART_GATEWARE:
return fpga_write_register(device, register_number, register_value);
return fpga_write_register(
device,
register_number,
(uint16_t) register_value);
case PART_RADIO:
return radio_write_register(device, bank, register_number, register_value);
}
return HACKRF_ERROR_INVALID_PARAM;
}
@@ -579,6 +671,7 @@ static void usage()
{
printf("\nUsage:\n");
printf("\t-h, --help: this help\n");
printf("\t-b, --bank <n>: set register bank for read/write operations (default 0)\n");
printf("\t-n, --register <n>: 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 <v>: write register specified by last -n argument with value <v>\n");
@@ -588,6 +681,7 @@ static void usage()
printf("\t-s, --si5351c: target SI5351C\n");
printf("\t-f, --rffc5072: target RFFC5072\n");
printf("\t-g, --gateware: target gateware registers\n");
printf("\t-i, --radio: target radio registers\n");
printf("\t-P, --fpga <n>: load the n-th bitstream to the FPGA\n");
printf("\t-1, --p1 <n>: P1 control\n");
printf("\t-2, --p2 <n>: P2 control\n");
@@ -611,6 +705,7 @@ static void usage()
static struct option long_options[] = {
{"config", no_argument, 0, 'c'},
{"bank", required_argument, 0, 'b'},
{"register", required_argument, 0, 'n'},
{"write", required_argument, 0, 'w'},
{"read", no_argument, 0, 'r'},
@@ -621,6 +716,7 @@ static struct option long_options[] = {
{"si5351c", no_argument, 0, 's'},
{"rffc5072", no_argument, 0, 'f'},
{"gateware", no_argument, 0, 'g'},
{"radio", no_argument, 0, 'i'},
{"fpga", required_argument, 0, 'P'},
{"p1", required_argument, 0, '1'},
{"p2", required_argument, 0, '2'},
@@ -641,8 +737,9 @@ int main(int argc, char** argv)
{
int opt;
uint8_t board_id = BOARD_ID_UNDETECTED;
uint32_t register_number = REGISTER_INVALID;
uint32_t register_value;
int bank = -1;
uint64_t register_number = REGISTER_INVALID;
uint64_t register_value;
hackrf_device* device = NULL;
int option_index = 0;
bool read = false;
@@ -652,17 +749,17 @@ int main(int argc, char** argv)
uint8_t part = PART_NONE;
const char* serial_number = NULL;
bool set_ui = false;
uint32_t ui_enable;
uint64_t ui_enable;
bool set_leds = false;
uint32_t led_state;
uint32_t tx_limit;
uint32_t rx_limit;
uint32_t p1_state;
uint32_t p2_state;
uint32_t clkin_state;
uint32_t narrowband_state;
uint32_t bitstream_index;
uint32_t adc_channel;
uint64_t led_state;
uint64_t tx_limit;
uint64_t rx_limit;
uint64_t p1_state;
uint64_t p2_state;
uint64_t clkin_state;
uint64_t narrowband_state;
uint64_t bitstream_index;
uint64_t adc_channel;
bool set_tx_limit = false;
bool set_rx_limit = false;
bool set_p1 = false;
@@ -685,10 +782,16 @@ int main(int argc, char** argv)
while ((opt = getopt_long(
argc,
argv,
"n:rw:d:cmsfg1:2:C:N:P:ST:R:h?u:l:ta:o",
"b:n:rw:d:cmsfgi1:2:C:N:P:ST:R:h?u:l:ta:o",
long_options,
&option_index)) != EOF) {
switch (opt) {
case 'b':
uint64_t bank_arg;
result = parse_int(optarg, &bank_arg);
bank = (int) bank_arg;
break;
case 'n':
result = parse_int(optarg, &register_number);
break;
@@ -755,6 +858,14 @@ int main(int argc, char** argv)
part = PART_GATEWARE;
break;
case 'i':
if (part != PART_NONE) {
fprintf(stderr, "Only one part can be specified.'\n");
return EXIT_FAILURE;
}
part = PART_RADIO;
break;
case '1':
set_p1 = true;
result = parse_int(optarg, &p1_state);
@@ -838,6 +949,16 @@ int main(int argc, char** argv)
return EXIT_FAILURE;
}
if ((bank > -1) && (part != PART_RADIO)) {
fprintf(stderr, "Bank valid only for radio.\n");
usage();
return EXIT_FAILURE;
}
if ((bank == -1) && (part == PART_RADIO)) {
bank = 0;
}
if (!(write || read || 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 ||
@@ -879,14 +1000,19 @@ int main(int argc, char** argv)
}
if (write) {
result = write_register(device, part, register_number, register_value);
result = write_register(
device,
part,
bank,
register_number,
register_value);
}
if (read) {
if (register_number == REGISTER_INVALID) {
result = read_registers(device, part);
result = read_registers(device, part, bank);
} else {
result = read_register(device, part, register_number);
result = read_register(device, part, bank, register_number);
}
}
@@ -1025,8 +1151,8 @@ int main(int argc, char** argv)
return EXIT_FAILURE;
}
printf("ADC0_%d (%s pin): %d\n",
adc_channel & 0x7,
adc_channel & 0x80 ? "alternate" : "dedicated",
((uint8_t) adc_channel) & 0x7,
((uint8_t) adc_channel) & 0x80 ? "alternate" : "dedicated",
value);
}

View File

@@ -1,3 +1,4 @@
# Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
# Copyright 2012 Jared Boone
# Copyright 2013 Benjamin Vernoux
#
@@ -20,7 +21,7 @@ cmake_minimum_required(VERSION 3.10.0)
project(
libhackrf
LANGUAGES C
VERSION 0.9.2)
VERSION 0.10.0)
include(GNUInstallDirs)
include(${PROJECT_SOURCE_DIR}/../cmake/set_release.cmake)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../cmake/modules)

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
Copyright (c) 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
Copyright (c) 2013, Benjamin Vernoux <titanmkd@gmail.com>
@@ -45,11 +45,13 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSI
#define TO_LE64(x) __builtin_bswap64(x)
#define FROM_LE16(x) __builtin_bswap16(x)
#define FROM_LE32(x) __builtin_bswap32(x)
#define FROM_LE64(x) __builtin_bswap64(x)
#else
#define TO_LE(x) x
#define TO_LE64(x) x
#define FROM_LE16(x) x
#define FROM_LE32(x) x
#define FROM_LE64(x) x
#endif
// TODO: Factor this into a shared #include so that firmware can use
@@ -111,6 +113,8 @@ typedef enum {
HACKRF_VENDOR_REQUEST_READ_SELFTEST = 56,
HACKRF_VENDOR_REQUEST_READ_ADC = 57,
HACKRF_VENDOR_REQUEST_TEST_RTC_OSC = 58,
HACKRF_VENDOR_REQUEST_RADIO_WRITE_REG = 59,
HACKRF_VENDOR_REQUEST_RADIO_READ_REG = 60,
} hackrf_vendor_request;
#define USB_CONFIG_STANDARD 0x1
@@ -3476,6 +3480,74 @@ int ADDCALL hackrf_set_fpga_bitstream(hackrf_device* device, const uint8_t index
}
}
int ADDCALL hackrf_radio_read_register(
hackrf_device* device,
const uint8_t bank,
const uint8_t register_number,
uint64_t* const value)
{
USB_API_REQUIRED(device, 0x0111);
int result;
const uint8_t length = sizeof(value);
result = libusb_control_transfer(
device->usb_device,
LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
HACKRF_VENDOR_REQUEST_RADIO_READ_REG,
register_number,
bank,
(unsigned char*) value,
length,
0);
*value = FROM_LE64(*value);
if (result < length) {
last_libusb_error = result;
return HACKRF_ERROR_LIBUSB;
} else {
return HACKRF_SUCCESS;
}
}
int ADDCALL hackrf_radio_write_register(
hackrf_device* device,
const uint8_t bank,
const uint8_t register_number,
const uint64_t value)
{
USB_API_REQUIRED(device, 0x0111);
int result;
unsigned char data[9];
data[0] = register_number;
data[1] = value & 0xff;
data[2] = (value >> 8) & 0xff;
data[3] = (value >> 16) & 0xff;
data[4] = (value >> 24) & 0xff;
data[5] = (value >> 32) & 0xff;
data[6] = (value >> 40) & 0xff;
data[7] = (value >> 48) & 0xff;
data[8] = (value >> 56) & 0xff;
result = libusb_control_transfer(
device->usb_device,
LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR |
LIBUSB_RECIPIENT_DEVICE,
HACKRF_VENDOR_REQUEST_RADIO_WRITE_REG,
0,
bank,
&data[0],
sizeof(data),
0);
if (result < 9) {
last_libusb_error = result;
return HACKRF_ERROR_LIBUSB;
} else {
return HACKRF_SUCCESS;
}
}
#ifdef __cplusplus
} // __cplusplus defined.
#endif

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com>
Copyright (c) 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com>
Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
Copyright (c) 2013, Benjamin Vernoux <titanmkd@gmail.com>
@@ -2354,6 +2354,38 @@ extern ADDAPI int ADDCALL hackrf_set_fpga_bitstream(
hackrf_device* device,
const uint8_t index);
/**
* Read a radio configuration register
*
* @param[in] device device to query
* @param[in] bank bank number to read
* @param[in] register_number register number to read
* @param[out] value value of the specified register
* @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant
* @ingroup debug
*/
extern ADDAPI int ADDCALL hackrf_radio_read_register(
hackrf_device* device,
const uint8_t bank,
const uint8_t register_number,
uint64_t* const value);
/**
* Write to a radio configuration register
*
* @param[in] device device to write
* @param[in] bank bank number to write
* @param[in] register_number register number to write
* @param[out] value value to write in the specified register
* @return @ref HACKRF_SUCCESS on success or @ref hackrf_error variant
* @ingroup debug
*/
extern ADDAPI int ADDCALL hackrf_radio_write_register(
hackrf_device* device,
const uint8_t bank,
const uint8_t register_number,
const uint64_t value);
#ifdef __cplusplus
} // __cplusplus defined.
#endif