Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for concurrency bugs in start/stop operations #1029

Merged
merged 3 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions firmware/hackrf_usb/hackrf_usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,29 @@ int main(void) {
operacake_init(operacake_allow_gpio);

while(true) {
switch (transceiver_mode()) {

// Briefly disable USB interrupt so that we can
// atomically retrieve both the transceiver mode
// and the mode change sequence number. They are
// changed together by the set_transceiver_mode
// vendor request handler.

nvic_disable_irq(NVIC_USB0_IRQ);

transceiver_mode_t mode = transceiver_mode();
uint32_t seq = transceiver_mode_seq();

nvic_enable_irq(NVIC_USB0_IRQ);

switch (mode) {
case TRANSCEIVER_MODE_RX:
rx_mode();
rx_mode(seq);
break;
case TRANSCEIVER_MODE_TX:
tx_mode();
tx_mode(seq);
break;
case TRANSCEIVER_MODE_RX_SWEEP:
sweep_mode();
sweep_mode(seq);
break;
case TRANSCEIVER_MODE_CPLD_UPDATE:
cpld_update();
Expand Down
4 changes: 2 additions & 2 deletions firmware/hackrf_usb/usb_api_sweep.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ usb_request_status_t usb_vendor_request_init_sweep(
return USB_REQUEST_STATUS_OK;
}

void sweep_mode(void) {
void sweep_mode(uint32_t seq) {
unsigned int blocks_queued = 0;
unsigned int phase = 1;
bool odd = true;
Expand All @@ -98,7 +98,7 @@ void sweep_mode(void) {

baseband_streaming_enable(&sgpio_config);

while (TRANSCEIVER_MODE_RX_SWEEP == transceiver_mode()) {
while (transceiver_mode_seq() == seq) {
// Set up IN transfer of buffer 0.
if ( m0_state.offset >= 16384 && phase == 1) {
transfer = true;
Expand Down
2 changes: 1 addition & 1 deletion firmware/hackrf_usb/usb_api_sweep.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ enum sweep_style {
usb_request_status_t usb_vendor_request_init_sweep(
usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage);

void sweep_mode(void);
void sweep_mode(uint32_t seq);

#endif /* __USB_API_SWEEP_H__ */
15 changes: 11 additions & 4 deletions firmware/hackrf_usb/usb_api_transceiver.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ usb_request_status_t usb_vendor_request_set_freq_explicit(
}

static volatile transceiver_mode_t _transceiver_mode = TRANSCEIVER_MODE_OFF;
static volatile uint32_t _transceiver_mode_seq = 0;
static volatile hw_sync_mode_t _hw_sync_mode = HW_SYNC_MODE_OFF;

void set_hw_sync_mode(const hw_sync_mode_t new_hw_sync_mode) {
Expand All @@ -248,6 +249,10 @@ transceiver_mode_t transceiver_mode(void) {
return _transceiver_mode;
}

uint32_t transceiver_mode_seq(void) {
return _transceiver_mode_seq;
}

void set_transceiver_mode(const transceiver_mode_t new_transceiver_mode) {
baseband_streaming_disable(&sgpio_config);
operacake_sctimer_reset_state();
Expand Down Expand Up @@ -287,6 +292,8 @@ void set_transceiver_mode(const transceiver_mode_t new_transceiver_mode) {

m0_state.offset = 0;
}

_transceiver_mode_seq++;
}

usb_request_status_t usb_vendor_request_set_transceiver_mode(
Expand Down Expand Up @@ -324,12 +331,12 @@ usb_request_status_t usb_vendor_request_set_hw_sync_mode(
}
}

void rx_mode(void) {
void rx_mode(uint32_t seq) {
unsigned int phase = 1;

baseband_streaming_enable(&sgpio_config);

while (TRANSCEIVER_MODE_RX == _transceiver_mode) {
while (_transceiver_mode_seq == seq) {
// Set up IN transfer of buffer 0.
if (16384 <= m0_state.offset && 1 == phase) {
usb_transfer_schedule_block(
Expand All @@ -353,7 +360,7 @@ void rx_mode(void) {
}
}

void tx_mode(void) {
void tx_mode(uint32_t seq) {
unsigned int phase = 1;

memset(&usb_bulk_buffer[0x0000], 0, 0x8000);
Expand All @@ -367,7 +374,7 @@ void tx_mode(void) {
// Start transmitting zeros while the host fills buffer 1.
baseband_streaming_enable(&sgpio_config);

while (TRANSCEIVER_MODE_TX == _transceiver_mode) {
while (_transceiver_mode_seq == seq) {
// Set up OUT transfer of buffer 0.
if (16384 <= m0_state.offset && 1 == phase) {
usb_transfer_schedule_block(
Expand Down
5 changes: 3 additions & 2 deletions firmware/hackrf_usb/usb_api_transceiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ usb_request_status_t usb_vendor_request_set_hw_sync_mode(
usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage);

transceiver_mode_t transceiver_mode(void);
uint32_t transceiver_mode_seq(void);
void set_transceiver_mode(const transceiver_mode_t new_transceiver_mode);
void start_streaming_on_hw_sync();
void rx_mode(void);
void tx_mode(void);
void rx_mode(uint32_t seq);
void tx_mode(uint32_t seq);

#endif/*__USB_API_TRANSCEIVER_H__*/
135 changes: 116 additions & 19 deletions host/libhackrf/src/hackrf.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ struct hackrf_device {
volatile bool do_exit;
unsigned char buffer[TRANSFER_COUNT * TRANSFER_BUFFER_SIZE];
bool transfers_setup; /* true if the USB transfers have been setup */
pthread_mutex_t transfer_lock; /* must be held to cancel or restart transfers */
bool transfer_finished[TRANSFER_COUNT]; /* which transfers have finished */
volatile bool all_finished; /* whether all transfers have finished */
pthread_cond_t all_finished_cv; /* signalled when all transfers have finished */
pthread_mutex_t all_finished_lock; /* used to protect all_finished */
};

typedef struct {
Expand Down Expand Up @@ -204,17 +209,46 @@ static int transfers_check_setup(hackrf_device* device)
static int cancel_transfers(hackrf_device* device)
{
uint32_t transfer_index;
int i;

if(transfers_check_setup(device) == true)
{
// Take lock while cancelling transfers. This blocks the
// transfer completion callback from restarting a transfer
// while we're in the middle of trying to cancel them all.
pthread_mutex_lock(&device->transfer_lock);

for(transfer_index=0; transfer_index<TRANSFER_COUNT; transfer_index++)
{
if( device->transfers[transfer_index] != NULL )
{
libusb_cancel_transfer(device->transfers[transfer_index]);
}
}

device->transfers_setup = false;

// Now release the lock. It's possible that some transfers were
// already complete when we called libusb_cancel_transfer() on
// them, and they may still get a callback. But the callback
// won't restart a transfer now that the transfers_setup flag
// is set to false.
pthread_mutex_unlock(&device->transfer_lock);

// Now wait for the transfer thread to signal that all transfers
// have finished, either by completing or being fully cancelled.
pthread_mutex_lock(&device->all_finished_lock);
while (!device->all_finished) {
pthread_cond_wait(&device->all_finished_cv, &device->all_finished_lock);
}
pthread_mutex_unlock(&device->all_finished_lock);

// Now that all waiting and handling is completed, it's safe to
// reset these flags ready for the next time.
for (i = 0; i < TRANSFER_COUNT; i++)
device->transfer_finished[i] = false;
device->all_finished = false;

return HACKRF_SUCCESS;
} else {
return HACKRF_ERROR_OTHER;
Expand Down Expand Up @@ -577,7 +611,7 @@ libusb_device_handle* hackrf_open_usb(const char* const desired_serial_number)

static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** device)
{
int result;
int result, i;
hackrf_device* lib_device;

//int speed = libusb_get_device_speed(usb_device);
Expand Down Expand Up @@ -613,6 +647,36 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d
lib_device->transfer_thread_started = false;
lib_device->streaming = false;
lib_device->do_exit = false;
for (i = 0; i < TRANSFER_COUNT; i++)
lib_device->transfer_finished[i] = false;
lib_device->all_finished = false;

result = pthread_mutex_init(&lib_device->transfer_lock, NULL);
if( result != 0 )
{
free(lib_device);
libusb_release_interface(usb_device, 0);
libusb_close(usb_device);
return HACKRF_ERROR_THREAD;
}

result = pthread_mutex_init(&lib_device->all_finished_lock, NULL);
if( result != 0 )
{
free(lib_device);
libusb_release_interface(usb_device, 0);
libusb_close(usb_device);
return HACKRF_ERROR_THREAD;
}

result = pthread_cond_init(&lib_device->all_finished_cv, NULL);
if( result != 0 )
{
free(lib_device);
libusb_release_interface(usb_device, 0);
libusb_close(usb_device);
return HACKRF_ERROR_THREAD;
}

result = allocate_transfers(lib_device);
if( result != 0 )
Expand Down Expand Up @@ -1542,9 +1606,32 @@ static void* transfer_threadproc(void* arg)
return NULL;
}

static void transfer_finished(struct hackrf_device* device, struct libusb_transfer* finished_transfer)
{
int i;
bool all_finished = true;

for (i = 0; i < TRANSFER_COUNT; i++) {
if (device->transfers[i] == finished_transfer) {
device->transfer_finished[i] = true;
} else {
all_finished &= device->transfer_finished[i];
}
}

if (all_finished) {
pthread_mutex_lock(&device->all_finished_lock);
device->all_finished = true;
pthread_cond_signal(&device->all_finished_cv);
pthread_mutex_unlock(&device->all_finished_lock);
}
}

static void LIBUSB_CALL hackrf_libusb_transfer_callback(struct libusb_transfer* usb_transfer)
{
hackrf_device* device = (hackrf_device*)usb_transfer->user_data;
bool resubmit;
int result;

if(usb_transfer->status == LIBUSB_TRANSFER_COMPLETED)
{
Expand All @@ -1559,17 +1646,27 @@ static void LIBUSB_CALL hackrf_libusb_transfer_callback(struct libusb_transfer*

if( device->callback(&transfer) == 0 )
{
if( libusb_submit_transfer(usb_transfer) < 0)
{
request_exit(device);
}else {
return;
// Take lock to make sure that we don't restart a
// transfer whilst cancel_transfers() is in the middle
// of stopping them.
pthread_mutex_lock(&device->transfer_lock);

if ((resubmit = device->transfers_setup)) {
result = libusb_submit_transfer(usb_transfer);
}
}else {
request_exit(device);

// Now we can release the lock. Our transfer was either
// cancelled or restarted, not both.
pthread_mutex_unlock(&device->transfer_lock);

if (!resubmit || result < 0) {
transfer_finished(device, usb_transfer);
}
} else {
transfer_finished(device, usb_transfer);
}
} else if(usb_transfer->status == LIBUSB_TRANSFER_CANCELLED) {
/* Nothing; this will happen during shutdown */
transfer_finished(device, usb_transfer);
} else {
/* Other cases LIBUSB_TRANSFER_NO_DEVICE
LIBUSB_TRANSFER_ERROR, LIBUSB_TRANSFER_TIMED_OUT
Expand All @@ -1588,21 +1685,14 @@ static int kill_transfer_thread(hackrf_device* device)
if( device->transfer_thread_started != false )
{
/*
* Schedule cancelling transfers before halting the
* libusb thread. This should result in the transfers
* being properly marked as cancelled.
*
* Ideally this would wait for the cancellations to
* complete with the callback but for now that
* isn't super easy to do.
* Cancel transfers. This call will block until the transfer
* thread has handled all completion callbacks.
*/
cancel_transfers(device);

/*
* Now call request_exit() to halt the main loop.
*/
request_exit(device);

value = NULL;
result = pthread_join(device->transfer_thread, &value);
if( result != 0 )
Expand Down Expand Up @@ -1649,12 +1739,15 @@ static int prepare_setup_transfers(hackrf_device* device,

static int create_transfer_thread(hackrf_device* device)
{
int result;
int result, i;

if( device->transfer_thread_started == false )
{
device->streaming = false;
device->do_exit = false;
for (i = 0; i < TRANSFER_COUNT; i++)
device->transfer_finished[i] = false;
device->all_finished = false;
result = pthread_create(&device->transfer_thread, 0, transfer_threadproc, device);
if( result == 0 )
{
Expand Down Expand Up @@ -1821,6 +1914,10 @@ int ADDCALL hackrf_close(hackrf_device* device)

free_transfers(device);

pthread_mutex_destroy(&device->transfer_lock);
pthread_cond_destroy(&device->all_finished_cv);
pthread_mutex_destroy(&device->all_finished_lock);

free(device);
}
open_devices--;
Expand Down