Rather than using sleep() for 1s at a time, set up an interval timer
that will fire once per second, and wait in the main loop for either
this or some other event.
On POSIX, the timing is set up with setitimer(), which generates a
SIGALRM signal each time the timer fires. The main loop runs pause() to
wait for any signal.
On Windows, the timing is set up using CreateWaitableTimer, which
provides an event handle that is set each time the timer fires. The main
loop runs WaitForMultipleObjects() to wait on this and an interrupt
event.
The TX and RX callbacks can now stop the main loop immediately when they
stop streaming. This fixes#1019.
Using _MSC_VER here means that the choice of signal() versus
SetConsoleCtrlHandler depends on the compiler being used, rather
than the OS being targeted. When built with MinGW rather than MSVC,
this happens to work because MinGW's signal emulation is used, but
that emulation is quite limited.
Instead, be consistent and use the Win32 API when building for that
platform, regardless of compiler.
Note that if building for Cygwin, _WIN32 is not defined and POSIX
APIs are used.
I believe this was safe before, because this code is only called from
the transfer thread, and the condition being protected is just whether
the count is zero, not the actual value of the count.
However, this isn't performance critical and it's a lot easier to
reason about the code if we just hold the lock for this whole section.
This simplifies the code required to wait for cancellations to complete.
The condition variable now reflects whether `active_transfers == 0`, and
the associated lock must be held to decrement that count to zero.
These were added in #805, as a workaround to prevent their parent
functions from returning before transfer cancellations had completed.
This has since been fixed properly in #1029.
There are now only two possible outcomes to this function: either we
successfully resubmitted a transfer, or the transfer is finished and we
end up calling transfer_finished().
So we can go ahead and simplify it accordingly.
If a transfer was cancelled, we are on our way to shutdown.
If hackrf_stop_tx() or hackrf_stop_rx() were called, they will already
have cleared this flag, but it is not cleared in hackrf_close(), and
for consistency with other paths it makes sense to clear it here.
If result < 0 here, libusb_submit_transfer returned an error, so we
need to shut down.
If !resubmit, then cancel_transfers() was already called by one of the
stop or close functions, so streaming is already false.
In the case of a libusb error, we still need the transfer thread
running, in order to handle outstanding cancellations and to signal the
condition variable when that is done.
transfer_threadproc has a timeout of half a second, so
when kill_transfer_thread tries to pthread_join, it often
has to wait until the timeout kicks in.
With this fix, we ensure that a final request is made after
request_exit has been called, so that transfer_threadproc can exit its
loop in a fast and clean manner.
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.