Replace direct setting of M0 mode with a request/ack mechanism.

This change avoids various possible races in which an autonomous mode
change by the M0 might clobber a mode change made from the M4, as well
as related races on other state fields that can be written by the M4.

The previous mode field is replaced by two separate ones:

- active_mode, which is written only by the M0, and indicates the
  current operating mode.

- requested_mode, which is written by the M4 to request a change.
  This field includes both the requested mode, and a flag bit. The M4
  writes the field with the flag bit set, and must then wait for the
  M0 to signal completion of the request by clearing the flag bit.

Whilst the M4 is blocked waiting for the flag bit to be cleared, the
M0 can safely make all the required changes to the state that are
needed for the transition to the requested mode. Once the transition
is complete, the M0 clears the flag bit and the M4 continues execution.

Request handling is implemented in the idle loop. To handle requests,
mode-specific loops simply need to check the request flag and branch to
idle if it is set.

A request from the M4 to change modes will always require passing
through the idle loop, and is not subject to timing guarantees. Only
transitions made autonomously by the M0 have guaranteed timing
constraints.

The work previously done in reset_counts is now implemented as part of
the request handling, so the tx_start, rx_start and wait_start labels
are no longer required.

An extra two cycles are required in the TX shortfall path because we
must now load the active mode to check whether we are in TX_START.

Two cycles are saved in the normal TX path because updating the active
mode to TX_RUN can now be done without checking the previous value.
This commit is contained in:
Martin Ling
2022-02-09 17:32:17 +00:00
parent 137f2481e5
commit f3633e285f
7 changed files with 123 additions and 104 deletions

View File

@ -21,10 +21,26 @@
#include "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

View File

@ -25,8 +25,11 @@
#include <stdint.h>
#include <usb_request.h>
#define M0_REQUEST_FLAG (1 << 16)
struct m0_state {
uint32_t mode;
uint32_t requested_mode;
uint32_t active_mode;
uint32_t m0_count;
uint32_t m4_count;
uint32_t num_shortfalls;
@ -57,6 +60,8 @@ enum m0_error {
*/
extern volatile struct m0_state m0_state;
void m0_set_mode(enum m0_mode mode);
usb_request_status_t usb_vendor_request_get_m0_state(
usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage);

View File

@ -102,8 +102,8 @@ There are four key code paths, with the following worst-case timings:
RX, normal: 152 cycles
RX, overrun: 76 cycles
TX, normal: 142 cycles
TX, underrun: 143 cycles
TX, normal: 140 cycles
TX, underrun: 145 cycles
Design
======
@ -214,18 +214,19 @@ The rest of this file is organised as follows:
.equ STATE_BASE, 0x20007000
// Offsets into the state structure.
.equ MODE, 0x00
.equ M0_COUNT, 0x04
.equ M4_COUNT, 0x08
.equ NUM_SHORTFALLS, 0x0C
.equ LONGEST_SHORTFALL, 0x10
.equ SHORTFALL_LIMIT, 0x14
.equ THRESHOLD, 0x18
.equ NEXT_MODE, 0x1C
.equ ERROR, 0x20
.equ REQUESTED_MODE, 0x00
.equ ACTIVE_MODE, 0x04
.equ M0_COUNT, 0x08
.equ M4_COUNT, 0x0C
.equ NUM_SHORTFALLS, 0x10
.equ LONGEST_SHORTFALL, 0x14
.equ SHORTFALL_LIMIT, 0x18
.equ THRESHOLD, 0x1C
.equ NEXT_MODE, 0x20
.equ ERROR, 0x24
// Private variables stored after state.
.equ PREV_LONGEST_SHORTFALL, 0x24
.equ PREV_LONGEST_SHORTFALL, 0x28
// Operating modes.
.equ MODE_IDLE, 0
@ -267,23 +268,6 @@ buf_ptr .req r4
/* Macros */
.macro reset_counts
// Reset counts.
//
// Clobbers:
zero .req r0
mov zero, #0 // zero = 0 // 1
str zero, [state, #M0_COUNT] // state.m0_count = zero // 2
str zero, [state, #M4_COUNT] // state.m4_count = zero // 2
str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2
str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2
str zero, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = zero // 2
str zero, [state, #ERROR] // state.error = zero // 2
mov shortfall_length, zero // shortfall_length = zero // 1
mov count, zero // count = zero // 1
.endm
.macro await_sgpio name
// Wait for, then clear, SGPIO exchange interrupt flag.
//
@ -315,6 +299,15 @@ buf_ptr .req r4
str int_status, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = int_status // 8
.endm
.macro on_request label
// Check if a new mode change request was made, and if so jump to the given label.
mode .req r3
flag .req r2
ldr mode, [state, #REQUESTED_MODE] // mode = state.requested_mode // 2
lsr flag, mode, #16 // flag = mode >> 16 // 1
bne \label // if flag != 0: goto label // 1 thru, 3 taken
.endm
.macro update_buf_ptr
// Update the address of the buffer segment we want to write to / read from.
mov buf_ptr, buf_mask // buf_ptr = buf_mask // 1
@ -347,7 +340,7 @@ buf_ptr .req r4
// Otherwise, load and set new mode.
ldr new_mode, [state, #NEXT_MODE] // new_mode = state.next_mode // 2
str new_mode, [state, #MODE] // state.mode = new_mode // 2
str new_mode, [state, #ACTIVE_MODE] // state.active_mode = new_mode // 2
// Branch according to new mode.
cmp new_mode, #MODE_RX // if new_mode == RX: // 1
@ -414,10 +407,11 @@ buf_ptr .req r4
mode .req r3
error .req r2
ldr mode, [state, #ACTIVE_MODE] // mode = state.active_mode // 2
lsr error, mode, #1 // error = mode >> 1 // 1
str error, [state, #ERROR] // state.error = error // 2
mov mode, #MODE_IDLE // mode = MODE_IDLE // 1
str mode, [state, #MODE] // state.mode = mode // 2
str mode, [state, #ACTIVE_MODE] // state.active_mode = mode // 2
b idle // goto idle // 3
.endm
@ -436,14 +430,11 @@ as follows:
Routine: Uses conditional branches to:
idle tx_start, wait_start
idle tx_loop, wait_loop
tx_zeros tx_loop
checked_rollback idle
tx_start
tx_loop tx_zeros, checked_rollback, rx_loop, wait_loop
wait_start
wait_loop rx_loop, tx_loop
rx_start
rx_loop rx_shortfall, checked_rollback, tx_loop, wait_loop
rx_shortfall rx_loop
@ -476,7 +467,8 @@ main:
mov hi_zero, zero // hi_zero = zero // 1
// Initialise state.
str zero, [state, #MODE] // state.mode = zero // 2
str zero, [state, #REQUESTED_MODE] // state.requested_mode = zero // 2
str zero, [state, #ACTIVE_MODE] // state.active_mode = zero // 2
str zero, [state, #M0_COUNT] // state.m0_count = zero // 2
str zero, [state, #M4_COUNT] // state.m4_count = zero // 2
str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2
@ -487,19 +479,59 @@ main:
str zero, [state, #ERROR] // state.error = zero // 2
idle:
// Wait for a mode to be set, then jump to the appropriate loop.
// Wait for a mode to be requested, then set up the new mode and acknowledge the request.
mode .req r3
flag .req r2
zero .req r0
// Read the requested mode and check flag to see if this is a new request. If not, ignore.
ldr mode, [state, #REQUESTED_MODE] // mode = state.requested_mode // 2
lsr flag, mode, #16 // flag = mode >> 16 // 1
beq idle // if flag == 0: goto idle // 1 thru, 3 taken
// Otherwise, this is a new request. The M4 is blocked at this point,
// waiting for us to clear the request flag. So we can safely write to
// all parts of the state.
// Set the new mode as both active & next.
uxth mode, mode // mode = mode & 0xFFFF // 1
str mode, [state, #ACTIVE_MODE] // state.active_mode = mode // 2
str mode, [state, #NEXT_MODE] // state.next_mode = mode // 2
// Don't reset counts on a transition to IDLE.
cmp mode, #MODE_IDLE // if mode == IDLE: // 1
beq ack_request // goto ack_request // 1 thru, 3 taken
// For all other transitions, reset counts.
mov zero, #0 // zero = 0 // 1
str zero, [state, #M0_COUNT] // state.m0_count = zero // 2
str zero, [state, #M4_COUNT] // state.m4_count = zero // 2
str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2
str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2
str zero, [state, #THRESHOLD] // state.threshold = zero // 2
str zero, [state, #PREV_LONGEST_SHORTFALL] // prev_longest_shortfall = zero // 2
str zero, [state, #ERROR] // state.error = zero // 2
mov shortfall_length, zero // shortfall_length = zero // 1
mov count, zero // count = zero // 1
ack_request:
// Clear SGPIO interrupt flag, which the M4 set to get our attention.
str flag, [sgpio_int, #INT_CLEAR] // SGPIO_CLR_STATUS_1 = flag // 8
// Write back requested mode with the flag cleared to acknowledge the request.
str mode, [state, #REQUESTED_MODE] // state.requested_mode = mode // 2
// Dispatch to appropriate loop.
//
// This code is arranged such that the branch to rx_start is the
// This code is arranged such that the branch to rx_loop is the
// unconditional one - which is necessary since it's too far away to
// use a conditional branch instruction.
mode .req r3
ldr mode, [state, #MODE] // mode = state.mode // 2
cmp mode, #MODE_WAIT // if mode < WAIT: // 1
blt idle // goto idle // 1 thru, 3 taken
beq wait_start // elif mode == WAIT: goto wait_start // 1 thru, 3 taken
beq wait_loop // elif mode == WAIT: goto wait_loop // 1 thru, 3 taken
cmp mode, #MODE_RX // if mode > RX: // 1
bgt tx_start // goto tx_start // 1 thru, 3 taken
b rx_start // goto rx_start // 3
bgt tx_loop // goto tx_loop // 1 thru, 3 taken
b rx_loop // goto rx_loop // 3
tx_zeros:
@ -515,6 +547,7 @@ tx_zeros:
str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8
// If in TX start mode, don't count this as a shortfall.
ldr mode, [state, #ACTIVE_MODE] // mode = state.active_mode // 2
cmp mode, #MODE_TX_START // if mode == TX_START: // 1
beq tx_loop // goto tx_loop // 1 thru, 3 taken
@ -542,22 +575,14 @@ checked_rollback:
b idle // goto idle // 3
tx_start:
// Reset counts.
reset_counts // reset_counts() // 12
tx_loop:
// Wait for and clear SGPIO interrupt.
await_sgpio tx // await_sgpio() // 34
// Check if a return to idle mode was requested.
// Check if there is a mode change request.
// If so, we may need to roll back shortfall stats.
mode .req r3
ldr mode, [state, #MODE] // mode = state.mode // 2
cmp mode, #MODE_IDLE // if mode == IDLE: // 1
beq checked_rollback // goto checked_rollback // 1 thru, 3 taken
on_request checked_rollback // 4
// Check if there is enough data in the buffer.
//
@ -578,13 +603,9 @@ tx_loop:
update_buf_ptr // update_buf_ptr() // 3
// At this point we know there is TX data available.
// If still in TX start mode, switch to TX run.
cmp mode, #MODE_TX_START // if mode != TX_START: // 1
bne tx_write // goto tx_write // 1 thru, 3 taken
// Set active mode to TX_RUN (it might still be TX_START).
mov mode, #MODE_TX_RUN // mode = TX_RUN // 1
str mode, [state, #MODE] // state.mode = mode // 2
tx_write:
str mode, [state, #ACTIVE_MODE] // state.active_mode = mode // 2
// Write data to SGPIO.
ldm buf_ptr!, {r0-r3} // r0-r3 = buf_ptr[0:16]; buf_ptr += 16 // 5
@ -604,21 +625,14 @@ tx_write:
// Jump to next mode if threshold reached, or back to TX loop start.
jump_next_mode tx // jump_next_mode() // 13
wait_start:
// Reset counts.
reset_counts // reset_counts() // 12
wait_loop:
// Wait for and clear SGPIO interrupt.
await_sgpio wait // await_sgpio() // 34
// Load mode, and return to idle if requested.
mode .req r3
ldr mode, [state, #MODE] // mode = state.mode // 2
cmp mode, #MODE_IDLE // if mode == IDLE: // 1
beq idle // goto idle // 1 thru, 3 taken
// Check if there is a mode change request.
// If so, return to idle.
on_request idle // 4
// Update counts.
update_counts // update_counts() // 4
@ -626,22 +640,14 @@ wait_loop:
// Jump to next mode if threshold reached, or back to wait loop start.
jump_next_mode wait // jump_next_mode() // 15
rx_start:
// Reset counts.
reset_counts // reset_counts() // 12
rx_loop:
// Wait for and clear SGPIO interrupt.
await_sgpio rx // await_sgpio() // 34
// Check if a return to idle mode was requested.
// Check if there is a mode change request.
// If so, we may need to roll back shortfall stats.
mode .req r3
ldr mode, [state, #MODE] // mode = state.mode // 2
cmp mode, #MODE_IDLE // if mode == IDLE: // 1
beq checked_rollback // goto checked_rollback // 1 thru, 3 taken
on_request checked_rollback // 4
// Check if there is enough space in the buffer.
//

View File

@ -132,14 +132,13 @@ void sweep_mode(uint32_t seq) {
// Set M0 to RX first buffer, then wait.
m0_state.threshold = 0x4000;
m0_state.next_mode = M0_MODE_WAIT;
m0_state.mode = M0_MODE_RX;
baseband_streaming_enable(&sgpio_config);
while (transceiver_request.seq == seq) {
// Wait for M0 to finish receiving a buffer.
while (m0_state.mode != M0_MODE_WAIT)
while (m0_state.active_mode != M0_MODE_WAIT)
if (transceiver_request.seq != seq)
goto end;
@ -198,7 +197,7 @@ void sweep_mode(uint32_t seq) {
nvic_enable_irq(NVIC_USB0_IRQ);
// Wait for M0 to resume RX.
while (m0_state.mode != M0_MODE_RX)
while (m0_state.active_mode != M0_MODE_RX)
if (transceiver_request.seq != seq)
goto end;

View File

@ -260,15 +260,6 @@ void request_transceiver_mode(transceiver_mode_t mode)
transceiver_request.seq++;
}
static void set_m0_mode(enum m0_mode mode) {
// All values of the threshold setting are valid. So setting a mode,
// it's necessary to set up a transition back to the same mode, for
// when the M0 counter wraps.
m0_state.mode = mode;
m0_state.next_mode = mode;
m0_state.threshold = 0;
}
void transceiver_shutdown(void)
{
baseband_streaming_disable(&sgpio_config);
@ -280,12 +271,7 @@ void transceiver_shutdown(void)
led_off(LED2);
led_off(LED3);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
set_m0_mode(M0_MODE_IDLE);
// The M0 may already be waiting for the next SGPIO
// exchange interrupt. In order to ensure that the M0
// switches over to its idle loop, we need to set the
// SGPIO exchange interrupt flag here.
SGPIO_SET_STATUS_1 = (1 << SGPIO_SLICE_A);
m0_set_mode(M0_MODE_IDLE);
}
void transceiver_startup(const transceiver_mode_t mode) {
@ -298,14 +284,14 @@ void transceiver_startup(const transceiver_mode_t mode) {
led_off(LED3);
led_on(LED2);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX);
set_m0_mode(M0_MODE_RX);
m0_set_mode(M0_MODE_RX);
m0_state.shortfall_limit = _rx_overrun_limit;
break;
case TRANSCEIVER_MODE_TX:
led_off(LED2);
led_on(LED3);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX);
set_m0_mode(M0_MODE_TX_START);
m0_set_mode(M0_MODE_TX_START);
m0_state.shortfall_limit = _tx_underrun_limit;
break;
default:

View File

@ -397,14 +397,17 @@ static const char * error_name(uint32_t error) {
static void print_state(hackrf_m0_state *state) {
printf("M0 state:\n");
printf("Mode: %u (%s)\n", state->mode, mode_name(state->mode));
printf("Requested mode: %u (%s) [%s]\n",
state->requested_mode, mode_name(state->requested_mode),
state->request_flag ? "pending" : "complete");
printf("Active mode: %u (%s)\n", state->active_mode, mode_name(state->active_mode));
printf("M0 count: %u bytes\n", state->m0_count);
printf("M4 count: %u bytes\n", state->m4_count);
printf("Number of shortfalls: %u\n", state->num_shortfalls);
printf("Longest shortfall: %u bytes\n", state->longest_shortfall);
printf("Shortfall limit: %u bytes\n", state->shortfall_limit);
printf("Mode change threshold: %u bytes\n", state->threshold);
printf("Next mode: %u (%s)\n", state->mode, mode_name(state->mode));
printf("Next mode: %u (%s)\n", state->next_mode, mode_name(state->next_mode));
printf("Error: %u (%s)\n", state->error, error_name(state->error));
}

View File

@ -157,8 +157,12 @@ typedef struct {
/** State of the SGPIO loop running on the M0 core. */
typedef struct {
/** Operating mode. */
uint32_t mode;
/** Request flag. */
uint16_t request_flag;
/** Requested mode. */
uint16_t requested_mode;
/** Active mode. */
uint32_t active_mode;
/** Number of bytes transferred by the M0. */
uint32_t m0_count;
/** Number of bytes transferred by the M4. */