Merge pull request #982 from martinling/bug-180

Firmware sample buffer management overhaul, including safe handling of TX underruns
This commit is contained in:
Michael Ossmann
2022-03-01 19:07:07 -05:00
committed by GitHub
15 changed files with 1086 additions and 156 deletions

View File

@ -43,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

View File

@ -49,6 +49,7 @@
#include "usb_api_transceiver.h"
#include "usb_api_ui.h"
#include "usb_bulk_buffer.h"
#include "usb_api_m0_state.h"
#include "cpld_xc2c.h"
#include "portapack.h"
@ -114,6 +115,9 @@ 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,
usb_vendor_request_set_tx_underrun_limit,
usb_vendor_request_set_rx_overrun_limit,
};
static const uint32_t vendor_request_handler_count =

View File

@ -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
======
@ -64,10 +98,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: 140 cycles
TX: 125 cycles
RX, normal: 152 cycles
RX, overrun: 76 cycles
TX, normal: 140 cycles
TX, underrun: 145 cycles
Design
======
@ -87,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.
@ -99,14 +207,38 @@ 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.
.equ STATE_BASE, 0x20007000
// Offsets into the state structure.
.equ OFFSET, 0x00
.equ TX, 0x04
.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, 0x28
// Operating modes.
.equ MODE_IDLE, 0
.equ MODE_WAIT, 1
.equ MODE_RX, 2
.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
@ -123,38 +255,26 @@ 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
shortfall_length .req r10
hi_zero .req r9
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,
// we don't actually use any of that. All the code in this file would work
// fine if the M0 jumped straight to main at reset.
.global main
.thumb_func
main: // Cycle counts:
// Initialise registers used for constant values.
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_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
mov buf_mask, value // buf_mask = value // 1
ldr value, =STATE_BASE // value = STATE_BASE // 2
mov state, value // state = value // 1
/* Macros */
// Initialise state.
zero .req r0
mov zero, #0 // zero = 0 // 1
str zero, [state, #OFFSET] // state.offset = zero // 2
str zero, [state, #TX] // state.tx = zero // 2
.macro await_sgpio name
// Wait for, then clear, SGPIO exchange interrupt flag.
//
// Clobbers:
int_status .req r0
scratch .req r1
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.
@ -169,76 +289,410 @@ loop:
// relying on any assumptions about the timing details of a read over
// the SGPIO to AHB bridge.
int_status .req r0
scratch .req r1
// Spin until we're ready to handle an SGPIO packet:
// Grab the exchange interrupt status...
\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
// ... 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
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
.endm
// ... 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
.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
and buf_ptr, count // buf_ptr &= count // 1
add buf_ptr, buf_base // buf_ptr += buf_base // 1
.endm
tx .req r0
.macro update_counts
// Update counts after successful SGPIO operation.
// Load direction (TX or RX)
ldr tx, [state, #TX] // tx = state.tx // 2
// 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
// TX?
lsr tx, #1 // tx >>= 1 // 1
bcc direction_rx // if !carry: goto direction_rx // 1 thru, 3 taken
// We didn't have a shortfall, so the current shortfall length is zero.
mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1
.endm
direction_tx:
.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, #ACTIVE_MODE] // state.active_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
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
.macro handle_shortfall name
// Handle a shortfall.
//
// Clobbers:
length .req r0
num .req r1
prev .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 \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
// Back up previous longest shortfall.
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.
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 \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 \name\()_loop // goto loop // 1 thru, 3 taken
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, 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
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, #ACTIVE_MODE] // state.active_mode = mode // 2
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<cond> 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_loop, wait_loop
tx_zeros tx_loop
checked_rollback idle
tx_loop tx_zeros, checked_rollback, rx_loop, wait_loop
wait_loop rx_loop, tx_loop
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
// fine if the M0 jumped straight to main at reset.
.global main
.thumb_func
main: // Cycle counts:
// Initialise registers used for constant values.
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
mov buf_mask, value // buf_mask = value // 1
ldr value, =STATE_BASE // value = STATE_BASE // 2
mov state, value // state = value // 1
zero .req r0
mov zero, #0 // zero = 0 // 1
mov hi_zero, zero // hi_zero = zero // 1
// Initialise state.
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
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
str zero, [state, #ERROR] // state.error = zero // 2
idle:
// 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_loop is the
// unconditional one - which is necessary since it's too far away to
// use a conditional branch instruction.
cmp mode, #MODE_WAIT // if mode < WAIT: // 1
blt idle // goto idle // 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_loop // goto tx_loop // 1 thru, 3 taken
b rx_loop // goto rx_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.
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
// 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
// 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_loop:
// Wait for and clear SGPIO interrupt.
await_sgpio tx // await_sgpio() // 34
// Check if there is a mode change request.
// If so, we may need to roll back shortfall stats.
on_request checked_rollback // 4
// 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
// Update buffer pointer.
update_buf_ptr // update_buf_ptr() // 3
// At this point we know there is TX data available.
// Set active mode to TX_RUN (it might still be TX_START).
mov mode, #MODE_TX_RUN // mode = TX_RUN // 1
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
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
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
direction_rx:
// Jump to next mode if threshold reached, or back to TX loop start.
jump_next_mode tx // jump_next_mode() // 13
wait_loop:
// Wait for and clear SGPIO interrupt.
await_sgpio wait // await_sgpio() // 34
// Check if there is a mode change request.
// If so, return to idle.
on_request idle // 4
// 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_loop:
// Wait for and clear SGPIO interrupt.
await_sgpio rx // await_sgpio() // 34
// Check if there is a mode change request.
// If so, we may need to roll back shortfall stats.
on_request checked_rollback // 4
// 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 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 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
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
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:
offset .req r0
// Update counts.
update_counts // update_counts() // 4
// Finally, update the buffer location...
mov offset, buf_mask // offset = buf_mask // 1
and offset, buf_ptr // offset &= buf_ptr // 1
// Jump to next mode if threshold reached, or back to RX loop start.
jump_next_mode rx // jump_next_mode() // 12
// ... and store the new position.
str offset, [state, #OFFSET] // state.offset = offset // 2
rx_shortfall:
b loop // goto loop // 3
// 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:

View File

@ -0,0 +1,60 @@
/*
* 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 "usb_api_m0_state.h"
#include <libopencm3/lpc43xx/sgpio.h>
#include <stddef.h>
#include <usb_request.h>
#include <usb_queue.h>
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
) {
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;
}
}

View File

@ -22,9 +22,36 @@
#ifndef __M0_STATE_H__
#define __M0_STATE_H__
#include <stdint.h>
#include <usb_request.h>
#define M0_REQUEST_FLAG (1 << 16)
struct m0_state {
uint32_t offset;
uint32_t tx;
uint32_t requested_mode;
uint32_t active_mode;
uint32_t m0_count;
uint32_t m4_count;
uint32_t num_shortfalls;
uint32_t longest_shortfall;
uint32_t shortfall_limit;
uint32_t threshold;
uint32_t next_mode;
uint32_t error;
};
enum m0_mode {
M0_MODE_IDLE = 0,
M0_MODE_WAIT = 1,
M0_MODE_RX = 2,
M0_MODE_TX_START = 3,
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
@ -33,4 +60,9 @@ struct m0_state {
*/
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);
#endif/*__M0_STATE_H__*/

View File

@ -25,7 +25,7 @@
#include <hackrf_core.h>
#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"
@ -87,86 +87,124 @@ 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;
(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;
baseband_streaming_enable(&sgpio_config);
while (transceiver_request.seq == seq) {
// Set up IN transfer of buffer 0.
if ( m0_state.offset >= 16384 && phase == 1) {
transfer = true;
buffer = &usb_bulk_buffer[0x0000];
phase = 0;
blocks_queued++;
}
// Set up IN transfer of buffer 1.
if ( m0_state.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.active_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,
NULL, NULL
);
}
transfer = false;
}
// Set M0 to switch back to RX after two more buffers.
m0_state.threshold += 0x8000;
m0_state.next_mode = M0_MODE_RX;
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;
// 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;
// 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.active_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();
}

View File

@ -27,7 +27,7 @@
#include <libopencm3/cm3/vector.h>
#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
@ -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;
@ -269,7 +271,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_set_mode(M0_MODE_IDLE);
}
void transceiver_startup(const transceiver_mode_t mode) {
@ -282,13 +284,15 @@ 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_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);
m0_state.tx = true;
m0_set_mode(M0_MODE_TX_START);
m0_state.shortfall_limit = _tx_underrun_limit;
break;
default:
break;
@ -296,7 +300,6 @@ void transceiver_startup(const transceiver_mode_t mode) {
activate_best_clock_source();
hw_sync_enable(_hw_sync_mode);
m0_state.offset = 0;
}
usb_request_status_t usb_vendor_request_set_transceiver_mode(
@ -334,6 +337,36 @@ 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;
m0_state.m4_count += bytes_transferred;
}
void rx_mode(uint32_t seq) {
unsigned int phase = 1;
@ -342,23 +375,26 @@ 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],
0x4000,
NULL, NULL
transceiver_bulk_transfer_complete,
NULL
);
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],
0x4000,
NULL, NULL
transceiver_bulk_transfer_complete,
NULL
);
phase = 1;
}
@ -368,39 +404,46 @@ 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);
// 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,
NULL, NULL
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) {
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],
0x4000,
NULL, NULL
transceiver_bulk_transfer_complete,
NULL
);
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],
0x4000,
NULL, NULL
transceiver_bulk_transfer_complete,
NULL
);
phase = 1;
}

View File

@ -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);

View File

@ -26,10 +26,13 @@
#include <stdbool.h>
#include <stdint.h>
#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__*/

View File

@ -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)

View File

@ -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;
@ -377,6 +377,40 @@ int write_register(hackrf_device* device, uint8_t part,
return HACKRF_ERROR_INVALID_PARAM;
}
static const char * mode_name(uint32_t mode) {
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];
else
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("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->next_mode, mode_name(state->next_mode));
printf("Error: %u (%s)\n", state->error, error_name(state->error));
}
static void usage() {
printf("\nUsage:\n");
printf("\t-h, --help: this help\n");
@ -388,12 +422,16 @@ 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-T, --tx-underrun-limit <n>: set TX underrun limit in bytes (0 for no limit)\n");
printf("\t-R, --rx-overrun-limit <n>: 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");
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,23 +444,31 @@ 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' },
{ "tx-underrun-limit", required_argument, 0, 'T' },
{ "rx-overrun-limit", required_argument, 0, 'R' },
{ "ui", required_argument, 0, 'u' },
{ 0, 0, 0, 0 },
};
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;
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;
uint16_t ui_enable;
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) {
@ -430,7 +476,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:cmsfST:R:h?u:", long_options, &option_index)) != EOF ) {
switch( opt ) {
case 'n':
result = parse_int(optarg, &register_number);
@ -449,6 +495,19 @@ int main(int argc, char** argv) {
dump_config = true;
break;
case 'S':
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;
@ -517,13 +576,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_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) {
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;
@ -551,6 +610,32 @@ 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);
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);
}

View File

@ -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
{
@ -377,6 +382,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;
@ -503,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");
@ -533,6 +576,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" );
@ -579,8 +623,10 @@ 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: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 +714,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 +1147,27 @@ 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;
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
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",
state.longest_shortfall);
} else {
fprintf(stderr, "\n");
}
}
time_start = time_now;
@ -1146,6 +1211,24 @@ 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"
"%u %s, longest %u bytes\n",
stats.m0_total,
stats.m4_total,
state.num_shortfalls,
(transmit || signalsource) ? "underruns" : "overruns",
state.longest_shortfall);
}
}
result = hackrf_close(device);
if(result != HACKRF_SUCCESS) {
fprintf(stderr, "hackrf_close() failed: %s (%d)\n", hackrf_error_name(result), result);

View File

@ -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})

View File

@ -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
@ -92,6 +96,9 @@ 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_SET_TX_UNDERRUN_LIMIT = 42,
HACKRF_VENDOR_REQUEST_SET_RX_OVERRUN_LIMIT = 43,
} hackrf_vendor_request;
#define USB_CONFIG_STANDARD 0x1
@ -1007,6 +1014,92 @@ 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 {
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;
}
}
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;

View File

@ -155,6 +155,32 @@ typedef struct {
uint8_t port;
} hackrf_operacake_freq_range;
/** State of the SGPIO loop running on the M0 core. */
typedef struct {
/** 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. */
uint32_t m0_count;
/** Number of bytes transferred by the M4. */
uint32_t m4_count;
/** Number of shortfalls. */
uint32_t num_shortfalls;
/** Longest shortfall. */
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;
/** Error, if any, that caused the M0 to revert to IDLE mode. */
uint32_t error;
} hackrf_m0_state;
struct hackrf_device_list {
char **serial_numbers;
enum hackrf_usb_board_id *usb_board_ids;
@ -193,6 +219,10 @@ 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);
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);