Convert to threaded operation by default

Rather than polling each interface in turn, execute the interface
handling periodically.

Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
diff --git a/src/cdc_uart.c b/src/cdc_uart.c
index 95124ff..45942cf 100644
--- a/src/cdc_uart.c
+++ b/src/cdc_uart.c
@@ -24,11 +24,16 @@
  */
 
 #include <pico/stdlib.h>
+#include "FreeRTOS.h"
+#include "task.h"
 
 #include "tusb.h"
 
 #include "picoprobe_config.h"
 
+TaskHandle_t uart_taskhandle;
+TickType_t last_wake, interval = 100;
+
 static uint8_t tx_buf[CFG_TUD_CDC_TX_BUFSIZE];
 static uint8_t rx_buf[CFG_TUD_CDC_RX_BUFSIZE];
 
@@ -40,7 +45,9 @@
     uart_init(PICOPROBE_UART_INTERFACE, PICOPROBE_UART_BAUDRATE);
 }
 
-void cdc_task(void) {
+void cdc_task(void)
+{
+    static int was_connected = 0;
     uint rx_len = 0;
 
     // Consume uart fifo regardless even if not connected
@@ -49,6 +56,7 @@
     }
 
     if (tud_cdc_connected()) {
+        was_connected = 1;
         int written = 0;
         /* Implicit overflow if we don't write all the bytes to the host.
          * Also throw away bytes if we can't write... */
@@ -69,17 +77,50 @@
         tx_len = tud_cdc_read(tx_buf, watermark);
         uart_write_blocking(PICOPROBE_UART_INTERFACE, tx_buf, tx_len);
       }
-    } else {
-      /* Clear out characters that may be left
-        - note that this doesn't include buffers already under hardware control */
+    } else if (was_connected) {
       tud_cdc_write_clear();
+      was_connected = 0;
     }
 }
 
-void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding) {
-    picoprobe_info("New baud rate %d\n", line_coding->bit_rate);
-    uart_deinit(PICOPROBE_UART_INTERFACE);
-    tud_cdc_write_clear();
-    tud_cdc_read_flush();
-    uart_init(PICOPROBE_UART_INTERFACE, line_coding->bit_rate);
+void cdc_thread(void *ptr)
+{
+  BaseType_t delayed;
+  last_wake = xTaskGetTickCount();
+  /* Threaded with a polling interval that scales according to linerate */
+  while (1) {
+    cdc_task();
+    delayed = xTaskDelayUntil(&last_wake, interval);
+    if (delayed == pdFALSE)
+      last_wake = xTaskGetTickCount();
+  }
+}
+
+void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding)
+{
+  /* Set the tick thread interval to the amount of time it takes to
+   * fill up half a FIFO. Millis is too coarse for integer divide.
+   */
+  uint32_t micros = (1000 * 1000 * 16 * 10) / MAX(line_coding->bit_rate, 1);
+
+  /* Modifying state, so park the thread before changing it. */
+  vTaskSuspend(uart_taskhandle);
+  interval = MAX(1, micros / ((1000 * 1000) / configTICK_RATE_HZ));
+  picoprobe_info("New baud rate %d micros %d interval %u\n",
+                  line_coding->bit_rate, micros, interval);
+  uart_deinit(PICOPROBE_UART_INTERFACE);
+  tud_cdc_write_clear();
+  tud_cdc_read_flush();
+  uart_init(PICOPROBE_UART_INTERFACE, line_coding->bit_rate);
+  vTaskResume(uart_taskhandle);
+}
+
+void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
+{
+  /* CDC drivers use linestate as a bodge to activate/deactivate the interface.
+   * Resume our UART polling on activate, stop on deactivate */
+  if (!dtr && !rts)
+    vTaskSuspend(uart_taskhandle);
+  else
+    vTaskResume(uart_taskhandle);
 }
diff --git a/src/cdc_uart.h b/src/cdc_uart.h
index 186ad50..8782b64 100644
--- a/src/cdc_uart.h
+++ b/src/cdc_uart.h
@@ -26,7 +26,10 @@
 #ifndef CDC_UART_H
 #define CDC_UART_H
 
+void cdc_thread(void *ptr);
 void cdc_uart_init(void);
 void cdc_task(void);
 
+extern TaskHandle_t uart_taskhandle;
+
 #endif
\ No newline at end of file
diff --git a/src/main.c b/src/main.c
index fb4247d..d3ee842 100644
--- a/src/main.c
+++ b/src/main.c
@@ -47,9 +47,40 @@
 static uint8_t TxDataBuffer[CFG_TUD_HID_EP_BUFSIZE];
 static uint8_t RxDataBuffer[CFG_TUD_HID_EP_BUFSIZE];
 
-void dap_task(void);
+#define THREADED 1
+
+#define UART_TASK_PRIO (tskIDLE_PRIORITY + 3)
+#define TUD_TASK_PRIO  (tskIDLE_PRIORITY + 2)
+#define DAP_TASK_PRIO  (tskIDLE_PRIORITY + 1)
+
+static TaskHandle_t dap_taskhandle, tud_taskhandle;
+
+void usb_thread(void *ptr)
+{
+    do {
+        tud_task();
+        // Trivial delay to save power
+        vTaskDelay(1);
+    } while (1);
+}
+
+void dap_thread(void *ptr)
+{
+    uint32_t resp_len;
+    do {
+        if (tud_vendor_available()) {
+            tud_vendor_read(RxDataBuffer, sizeof(RxDataBuffer));
+            resp_len = DAP_ProcessCommand(RxDataBuffer, TxDataBuffer);
+            tud_vendor_write(TxDataBuffer, resp_len);
+        } else {
+            // Trivial delay to save power
+            vTaskDelay(2);
+        }
+    } while (1);
+}
 
 int main(void) {
+    uint32_t resp_len;
 
     board_init();
     usb_serial_init();
@@ -64,31 +95,33 @@
 
     picoprobe_info("Welcome to Picoprobe!\n");
 
-    while (1) {
-        tud_task(); // tinyusb device task
+    if (THREADED) {
+        /* UART needs to preempt USB as if we don't, characters get lost */
+        xTaskCreate(cdc_thread, "UART", configMINIMAL_STACK_SIZE, NULL, UART_TASK_PRIO, &uart_taskhandle);
+        xTaskCreate(usb_thread, "TUD", configMINIMAL_STACK_SIZE, NULL, TUD_TASK_PRIO, &tud_taskhandle);
+        /* Lowest priority thread is debug - need to shuffle buffers before we can toggle swd... */
+        xTaskCreate(dap_thread, "DAP", configMINIMAL_STACK_SIZE, NULL, DAP_TASK_PRIO, &dap_taskhandle);
+        vTaskStartScheduler();
+    }
+
+    while (!THREADED) {
+        tud_task();
         cdc_task();
 #if (PICOPROBE_DEBUG_PROTOCOL == PROTO_OPENOCD_CUSTOM)
         probe_task();
         led_task();
 #elif (PICOPROBE_DEBUG_PROTOCOL == PROTO_DAP_V2)
-        dap_task();
+        if (tud_vendor_available()) {
+            tud_vendor_read(RxDataBuffer, sizeof(RxDataBuffer));
+            resp_len = DAP_ProcessCommand(RxDataBuffer, TxDataBuffer);
+            tud_vendor_write(TxDataBuffer, resp_len);
+        }
 #endif
     }
 
     return 0;
 }
 
-void dap_task(void)
-{
-  uint32_t count;
-  uint32_t resp_len;
-  if (tud_vendor_available()) {
-     count = tud_vendor_read(RxDataBuffer, sizeof(RxDataBuffer));
-     resp_len = DAP_ProcessCommand(RxDataBuffer, TxDataBuffer);
-     tud_vendor_write(TxDataBuffer, resp_len);
-  }
-}
-
 uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
 {
   // TODO not Implemented