Transceiver52M: Implement POWEROFF command

Add stop and restart capability through the POWEROFF and POWERON
commands. Calling stop causes receive streaming to cease, and I/O
threads to shutdown leaving only the control handling thread running.
Upon receiving a POWERON command, I/O threads and device streaming are
restarted.

Proper shutdown of the transceiver is now initiated by the destructor,
which calls the stop command internally to wind down and deallocate
threads.

Signed-off-by: Tom Tsou <tom@tsou.cc>
diff --git a/Transceiver52M/UHDDevice.cpp b/Transceiver52M/UHDDevice.cpp
index c914868..cbfc2e4 100644
--- a/Transceiver52M/UHDDevice.cpp
+++ b/Transceiver52M/UHDDevice.cpp
@@ -40,6 +40,14 @@
 #define TX_AMPL          0.3
 #define SAMPLE_BUF_SZ    (1 << 20)
 
+/*
+ * UHD timeout value on streaming (re)start
+ *
+ * Allow some time for streaming to commence after the start command is issued,
+ * but consider a wait beyond one second to be a definite error condition.
+ */
+#define UHD_RESTART_TIMEOUT     1.0
+
 enum uhd_dev_type {
 	USRP1,
 	USRP2,
@@ -268,7 +276,7 @@
 	int open(const std::string &args, bool extref);
 	bool start();
 	bool stop();
-	void restart();
+	bool restart();
 	void setPriority(float prio);
 	enum TxWindowType getWindowType() { return tx_window; }
 
@@ -313,8 +321,9 @@
 
 	enum err_code {
 		ERROR_TIMING = -1,
-		ERROR_UNRECOVERABLE = -2,
-		ERROR_UNHANDLED = -3,
+		ERROR_TIMEOUT = -2,
+		ERROR_UNRECOVERABLE = -3,
+		ERROR_UNHANDLED = -4,
 	};
 
 private:
@@ -358,7 +367,7 @@
 	uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx);
 	bool set_freq(double freq, size_t chan, bool tx);
 
-	Thread async_event_thrd;
+	Thread *async_event_thrd;
 	bool diversity;
 };
 
@@ -396,6 +405,12 @@
 	}
 }
 
+static void thread_enable_cancel(bool cancel)
+{
+	cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) :
+		 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+}
+
 uhd_device::uhd_device(size_t sps, size_t chans, bool diversity, double offset)
 	: tx_gain_min(0.0), tx_gain_max(0.0),
 	  rx_gain_min(0.0), rx_gain_max(0.0),
@@ -727,7 +742,7 @@
 {
 	uhd::rx_metadata_t md;
 	size_t num_smpls;
-	float timeout = 0.1f;
+	float timeout = UHD_RESTART_TIMEOUT;
 
 	std::vector<std::vector<short> >
 		pkt_bufs(chans, std::vector<short>(2 * rx_spp));
@@ -743,6 +758,8 @@
 		if (!num_smpls) {
 			switch (md.error_code) {
 			case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
+				LOG(ALERT) << "Device timed out";
+				return false;
 			default:
 				continue;
 			}
@@ -756,7 +773,7 @@
 	return true;
 }
 
-void uhd_device::restart()
+bool uhd_device::restart()
 {
 	/* Allow 100 ms delay to align multi-channel streams */
 	double delay = 0.1;
@@ -771,7 +788,7 @@
 
 	usrp_dev->issue_stream_cmd(cmd);
 
-	flush_recv(1);
+	return flush_recv(10);
 }
 
 bool uhd_device::start()
@@ -787,10 +804,12 @@
 	uhd::msg::register_handler(&uhd_msg_handler);
 
 	// Start asynchronous event (underrun check) loop
-	async_event_thrd.start((void * (*)(void*))async_event_loop, (void*)this);
+	async_event_thrd = new Thread();
+	async_event_thrd->start((void * (*)(void*))async_event_loop, (void*)this);
 
 	// Start streaming
-	restart();
+	if (!restart())
+		return false;
 
 	// Display usrp time
 	double time_now = usrp_dev->get_time_now().get_real_secs();
@@ -810,6 +829,10 @@
 
 	usrp_dev->issue_stream_cmd(stream_cmd);
 
+	async_event_thrd->cancel();
+	async_event_thrd->join();
+	delete async_event_thrd;
+
 	started = false;
 	return true;
 }
@@ -830,6 +853,7 @@
 		switch (md.error_code) {
 		case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
 			LOG(ALERT) << "UHD: Receive timed out";
+			return ERROR_TIMEOUT;
 		case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW:
 		case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND:
 		case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN:
@@ -899,8 +923,11 @@
 
 	// Receive samples from the usrp until we have enough
 	while (rx_buffers[0]->avail_smpls(timestamp) < len) {
+		thread_enable_cancel(false);
 		size_t num_smpls = rx_stream->recv(pkt_ptrs, rx_spp,
 						   metadata, 0.1, true);
+		thread_enable_cancel(true);
+
 		rx_pkt_cnt++;
 
 		// Check for errors 
@@ -910,6 +937,9 @@
 			LOG(ALERT) << "UHD: Version " << uhd::get_version_string();
 			LOG(ALERT) << "UHD: Unrecoverable error, exiting...";
 			exit(-1);
+		case ERROR_TIMEOUT:
+			// Assume stopping condition
+			return 0;
 		case ERROR_TIMING:
 			restart();
 		case ERROR_UNHANDLED:
@@ -988,7 +1018,10 @@
 		}
 	}
 
+	thread_enable_cancel(false);
 	size_t num_smpls = tx_stream->send(bufs, len, metadata);
+	thread_enable_cancel(true);
+
 	if (num_smpls != (unsigned) len) {
 		LOG(ALERT) << "UHD: Device send timed out";
 	}
@@ -1124,7 +1157,11 @@
 bool uhd_device::recv_async_msg()
 {
 	uhd::async_metadata_t md;
-	if (!usrp_dev->get_device()->recv_async_msg(md))
+
+	thread_enable_cancel(false);
+	bool rc = usrp_dev->get_device()->recv_async_msg(md);
+	thread_enable_cancel(true);
+	if (!rc)
 		return false;
 
 	// Assume that any error requires resynchronization