From 5ce7b147117826dd75c2d7b347df80b0177e7252 Mon Sep 17 00:00:00 2001
From: Tommie Gannert <tommie@gannert.se>
Date: Mon, 26 Feb 2024 11:41:50 +0100
Subject: [PATCH] add notification support for device class USBTMC.

The ep_int_in is already used for responding to USB488
READ_STATUS_BYTE requests, but that EP is defined for all of USBTMC.
This extends the functionality to let callers send notifications and
receive ACKs.
---
 src/class/usbtmc/usbtmc.h        | 25 +++++++++++++++++++++++++
 src/class/usbtmc/usbtmc_device.c | 16 +++++++++++++++-
 src/class/usbtmc/usbtmc_device.h | 20 +++++++++++++++-----
 3 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/src/class/usbtmc/usbtmc.h b/src/class/usbtmc/usbtmc.h
index 090ab3c4a..327de087c 100644
--- a/src/class/usbtmc/usbtmc.h
+++ b/src/class/usbtmc/usbtmc.h
@@ -183,6 +183,23 @@ typedef enum {
 
 } usmtmc_request_type_enum;
 
+typedef enum {
+  // The last and first valid bNotify1 for use by the USBTMC class specification.
+  USBTMC_bNOTIFY1_USBTMC_FIRST          = 0x00,
+  USBTMC_bNOTIFY1_USBTMC_LAST           = 0x3F,
+
+  // The last and first valid bNotify1 for use by vendors.
+  USBTMC_bNOTIFY1_VENDOR_SPECIFIC_FIRST = 0x40,
+  USBTMC_bNOTIFY1_VENDOR_SPECIFIC_LAST  = 0x7F,
+
+  // The last and first valid bNotify1 for use by USBTMC subclass specifications.
+  USBTMC_bNOTIFY1_SUBCLASS_FIRST        = 0x80,
+  USBTMC_bNOTIFY1_SUBCLASS_LAST         = 0xFF,
+
+  // From the USB488 Subclass Specification, Section 3.4.
+  USB488_bNOTIFY1_SRQ                   = 0x81,
+} usbtmc_int_in_payload_format;
+
 typedef enum {
   USBTMC_STATUS_SUCCESS = 0x01,
   USBTMC_STATUS_PENDING = 0x02,
@@ -303,6 +320,14 @@ typedef struct TU_ATTR_PACKED
 
 TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_rsp_488_t) == 3u, "struct wrong length");
 
+typedef struct TU_ATTR_PACKED
+{
+  uint8_t bNotify1; // Must be USB488_bNOTIFY1_SRQ
+  uint8_t StatusByte;
+} usbtmc_srq_interrupt_488_t;
+
+TU_VERIFY_STATIC(sizeof(usbtmc_srq_interrupt_488_t) == 2u, "struct wrong length");
+
 typedef struct TU_ATTR_PACKED
 {
   struct TU_ATTR_PACKED
diff --git a/src/class/usbtmc/usbtmc_device.c b/src/class/usbtmc/usbtmc_device.c
index 573654d58..debe445fd 100644
--- a/src/class/usbtmc/usbtmc_device.c
+++ b/src/class/usbtmc/usbtmc_device.c
@@ -240,6 +240,18 @@ bool tud_usbtmc_transmit_dev_msg_data(
   return true;
 }
 
+bool tud_usbtmc_transmit_notification_data(const void * data, size_t len)
+{
+#ifndef NDEBUG
+  TU_ASSERT(len >= 1);
+  TU_ASSERT(usbtmc_state.ep_int_in != 0);
+#endif
+  if (usbd_edpt_busy(usbtmc_state.rhport, usbtmc_state.ep_int_in)) return false;
+
+  TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_int_in, (void *)data, len));
+  return true;
+}
+
 void usbtmcd_init_cb(void)
 {
   usbtmc_state.capabilities = tud_usbtmc_get_capabilities_cb();
@@ -578,7 +590,9 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
     }
   }
   else if (ep_addr == usbtmc_state.ep_int_in) {
-    // Good?
+    if (tud_usbtmc_notification_complete_cb) {
+      TU_VERIFY(tud_usbtmc_notification_complete_cb());
+    }
     return true;
   }
   return false;
diff --git a/src/class/usbtmc/usbtmc_device.h b/src/class/usbtmc/usbtmc_device.h
index c1298ddb8..2f3c91dbd 100644
--- a/src/class/usbtmc/usbtmc_device.h
+++ b/src/class/usbtmc/usbtmc_device.h
@@ -73,6 +73,10 @@ bool tud_usbtmc_check_abort_bulk_in_cb(usbtmc_check_abort_bulk_rsp_t *rsp);
 bool tud_usbtmc_check_abort_bulk_out_cb(usbtmc_check_abort_bulk_rsp_t *rsp);
 bool tud_usbtmc_check_clear_cb(usbtmc_get_clear_status_rsp_t *rsp);
 
+// The interrupt-IN endpoint buffer was transmitted to the host. Use
+// tud_usbtmc_transmit_notification_data to send another notification.
+TU_ATTR_WEAK bool tud_usbtmc_notification_complete_cb(void);
+
 // Indicator pulse should be 0.5 to 1.0 seconds long
 TU_ATTR_WEAK bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult);
 
@@ -93,6 +97,17 @@ bool tud_usbtmc_transmit_dev_msg_data(
     const void * data, size_t len,
     bool endOfMessage, bool usingTermChar);
 
+// Buffers a notification to be sent to the host. The buffer must be
+// valid until the tud_usbtmc_notification_complete_cb callback. The
+// data starts with the bNotify1 field, see the USBTMC Specification,
+// Table 13.
+//
+// If the previous notification data has not yet been sent, this
+// returns false.
+//
+// Requires an interrupt endpoint in the interface.
+bool tud_usbtmc_transmit_notification_data(const void * data, size_t len);
+
 bool tud_usbtmc_start_bus_read(void);
 
 
@@ -104,9 +119,4 @@ bool     usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result,
 bool     usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
 void     usbtmcd_init_cb(void);
 
-/************************************************************
- * USBTMC Descriptor Templates
- *************************************************************/
-
-
 #endif /* CLASS_USBTMC_USBTMC_DEVICE_H_ */