From 7c0cc53aed7744f6beeee74837c162c196cb3df0 Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Fri, 21 Feb 2025 17:49:44 -0800
Subject: [PATCH 1/9] Handle Notification Endpoint, can send CTS/DSR/DCD/RI.
 #491

---
 src/arduino/Adafruit_USBD_CDC.cpp | 44 +++++++++++++++++++++++++
 src/arduino/Adafruit_USBD_CDC.h   | 15 ++++++++-
 src/class/cdc/cdc.h               | 42 +++++++++++++++++++++++
 src/class/cdc/cdc_device.c        | 55 ++++++++++++++++++++++++++++---
 src/class/cdc/cdc_device.h        | 14 ++++++++
 src/common/tusb_types.h           | 21 ++++++++++++
 6 files changed, 185 insertions(+), 6 deletions(-)

diff --git a/src/arduino/Adafruit_USBD_CDC.cpp b/src/arduino/Adafruit_USBD_CDC.cpp
index 893db83e..ce0583b7 100644
--- a/src/arduino/Adafruit_USBD_CDC.cpp
+++ b/src/arduino/Adafruit_USBD_CDC.cpp
@@ -170,6 +170,50 @@ int Adafruit_USBD_CDC::dtr(void) {
   return tud_cdc_n_connected(_instance);
 }
 
+inline bool Adafruit_USBD_CDC::rts(void) {
+  return tud_cdc_n_get_line_state(_instance) & CDC_CONTROL_LINE_STATE_RTS;
+}
+
+inline bool Adafruit_USBD_CDC::cts(void) {
+  return tud_cdc_n_get_serial_state(_instance).cts;
+}
+
+inline bool Adafruit_USBD_CDC::dsr(void) {
+  return tud_cdc_n_get_serial_state(_instance).dsr;
+}
+
+inline bool Adafruit_USBD_CDC::dcd(void) {
+  return tud_cdc_n_get_serial_state(_instance).dcd;
+}
+
+inline bool Adafruit_USBD_CDC::ri(void) {
+  return tud_cdc_n_get_serial_state(_instance).ri;
+}
+
+void Adafruit_USBD_CDC::cts(bool c) {
+  cdc_serial_state_t serial_state = tud_cdc_n_get_serial_state(_instance);
+  serial_state.cts = c;
+  tud_cdc_n_set_serial_state(_instance, serial_state);
+}
+
+void Adafruit_USBD_CDC::dsr(bool c) {
+  cdc_serial_state_t serial_state = tud_cdc_n_get_serial_state(_instance);
+  serial_state.dsr = c;
+  tud_cdc_n_set_serial_state(_instance, serial_state);
+}
+
+void Adafruit_USBD_CDC::dcd(bool c) {
+  cdc_serial_state_t serial_state = tud_cdc_n_get_serial_state(_instance);
+  serial_state.dcd = c;
+  tud_cdc_n_set_serial_state(_instance, serial_state);
+}
+
+void Adafruit_USBD_CDC::ri(bool c) {
+  cdc_serial_state_t serial_state = tud_cdc_n_get_serial_state(_instance);
+  serial_state.ri = c;
+  tud_cdc_n_set_serial_state(_instance, serial_state);
+}
+
 Adafruit_USBD_CDC::operator bool() {
   if (!isValid()) {
     return false;
diff --git a/src/arduino/Adafruit_USBD_CDC.h b/src/arduino/Adafruit_USBD_CDC.h
index 9c0952f3..2fcd71ab 100644
--- a/src/arduino/Adafruit_USBD_CDC.h
+++ b/src/arduino/Adafruit_USBD_CDC.h
@@ -59,7 +59,20 @@ class Adafruit_USBD_CDC : public Stream, public Adafruit_USBD_Interface {
   uint8_t stopbits(void);
   uint8_t paritytype(void);
   uint8_t numbits(void);
-  int dtr(void);
+
+  // Flow control bit getters.
+  int dtr(void);   // pre-existing, I don't want to change the return type.
+  bool rts(void);
+  bool cts(void);
+  bool dsr(void);
+  bool dcd(void);
+  bool ri(void);
+
+  // Flow control bit setters.
+  void cts(bool c);
+  void dsr(bool c);
+  void dcd(bool c);
+  void ri(bool c);
 
   // Stream API
   virtual int available(void);
diff --git a/src/class/cdc/cdc.h b/src/class/cdc/cdc.h
index da490a6d..cb31fdff 100644
--- a/src/class/cdc/cdc.h
+++ b/src/class/cdc/cdc.h
@@ -412,6 +412,48 @@ typedef struct TU_ATTR_PACKED
 
 TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 2, "size is not correct");
 
+//--------------------------------------------------------------------+
+// Notifications
+//--------------------------------------------------------------------+
+
+typedef struct TU_ATTR_PACKED {
+  uint16_t dcd         :1;
+  uint16_t dsr         :1;
+  uint16_t break_err   :1;
+  uint16_t ri          :1;
+  uint16_t frame_err   :1;
+  uint16_t parity_err  :1;
+  uint16_t overrun_err :1;
+  uint16_t cts         :1;  // https://community.st.com/t5/stm32-mcus-products/cts-signal-on-usb-cdc/td-p/325800
+  uint16_t             :8;
+} cdc_serial_state_t;
+
+TU_VERIFY_STATIC(sizeof(cdc_serial_state_t) == 2, "size is not correct");
+
+// Add more notifications here.  PSTN120.pdf, Section 6.5
+typedef struct TU_ATTR_PACKED {
+  tusb_notification_t header;
+  union {
+    cdc_serial_state_t serial_state;
+    // Add more Notification "Data" payloads here as more are implemented.
+  };
+} cdc_notify_struct_t;
+
+TU_VERIFY_STATIC(sizeof(cdc_notify_struct_t) == 10, "size is not correct");
+
+tu_static const cdc_notify_struct_t cdc_notify_serial_status = {
+  .header = {
+    .bmRequestType_bit = {
+      .recipient = TUSB_REQ_RCPT_INTERFACE,
+      .type = TUSB_REQ_TYPE_CLASS,
+      .direction = TUSB_DIR_IN,
+    },
+    .bNotification = CDC_NOTIF_SERIAL_STATE,
+    .wValue = 0,
+    .wLength = sizeof(cdc_serial_state_t),
+  }
+};
+
 TU_ATTR_PACKED_END  // End of all packed definitions
 TU_ATTR_BIT_FIELD_ORDER_END
 
diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c
index 6fccab6d..18fd3fda 100644
--- a/src/class/cdc/cdc_device.c
+++ b/src/class/cdc/cdc_device.c
@@ -59,6 +59,10 @@ typedef struct {
   // Bit 0:  DTR (Data Terminal Ready), Bit 1: RTS (Request to Send)
   uint8_t line_state;
 
+  // Notify host of flow control bits: CTS, DSR, DCD, RI, and some error flags.
+  cdc_serial_state_t serial_state;
+  bool serial_state_changed;
+
   /*------------- From this point, data is not cleared by bus reset -------------*/
   char wanted_char;
   TU_ATTR_ALIGNED(4) cdc_line_coding_t line_coding;
@@ -76,6 +80,7 @@ typedef struct {
   // Endpoint Transfer buffer
   CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_CDC_EP_BUFSIZE];
   CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_CDC_EP_BUFSIZE];
+  CFG_TUSB_MEM_ALIGN cdc_notify_struct_t epnotif_buf;
 } cdcd_interface_t;
 
 #define ITF_MEM_RESET_SIZE   offsetof(cdcd_interface_t, wanted_char)
@@ -138,6 +143,18 @@ uint8_t tud_cdc_n_get_line_state(uint8_t itf) {
   return _cdcd_itf[itf].line_state;
 }
 
+cdc_serial_state_t tud_cdc_n_get_serial_state(uint8_t itf) {
+  return _cdcd_itf[itf].serial_state;
+}
+
+void tud_cdc_n_set_serial_state(uint8_t itf, cdc_serial_state_t serial_state) {
+  if (memcmp(&(_cdcd_itf[itf].serial_state), &serial_state, sizeof(serial_state)) != 0) {
+    TU_LOG_DRV("  Serial State Changed: %x -> %x\r\n", _cdcd_itf[itf].serial_state, serial_state);
+    _cdcd_itf[itf].serial_state_changed = true;
+  }
+  _cdcd_itf[itf].serial_state = serial_state;
+}
+
 void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding) {
   (*coding) = _cdcd_itf[itf].line_coding;
 }
@@ -326,10 +343,13 @@ uint16_t cdcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1
 
   if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) {
     // notification endpoint
-    tusb_desc_endpoint_t const* desc_ep = (tusb_desc_endpoint_t const*) p_desc;
+    tusb_desc_endpoint_t desc_ep = *(tusb_desc_endpoint_t const*) p_desc;
+    TU_LOG_DRV("  Interval before: %d\r\n", desc_ep.bInterval);
+
+    desc_ep.bInterval = 1;  // Query every frame, 1ms at Full Speed.
 
-    TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
-    p_cdc->ep_notif = desc_ep->bEndpointAddress;
+    TU_ASSERT(usbd_edpt_open(rhport, &desc_ep), 0);
+    p_cdc->ep_notif = desc_ep.bEndpointAddress;
 
     drv_len += tu_desc_len(p_desc);
     p_desc = tu_desc_next(p_desc);
@@ -437,12 +457,13 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
   // Identify which interface to use
   for (itf = 0; itf < CFG_TUD_CDC; itf++) {
     p_cdc = &_cdcd_itf[itf];
-    if ((ep_addr == p_cdc->ep_out) || (ep_addr == p_cdc->ep_in)) break;
+    if ((ep_addr == p_cdc->ep_out) || (ep_addr == p_cdc->ep_in) || (ep_addr == p_cdc->ep_notif)) break;
   }
   TU_ASSERT(itf < CFG_TUD_CDC);
 
   // Received new data
   if (ep_addr == p_cdc->ep_out) {
+    TU_LOG_DRV("  XFer Out\r\n");
     tu_fifo_write_n(&p_cdc->rx_ff, p_cdc->epout_buf, (uint16_t) xferred_bytes);
 
     // Check for wanted char and invoke callback if needed
@@ -465,6 +486,7 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
   // Note: This will cause incorrect baudrate set in line coding.
   //       Though maybe the baudrate is not really important !!!
   if (ep_addr == p_cdc->ep_in) {
+    TU_LOG_DRV("  XFer In\r\n");
     // invoke transmit callback to possibly refill tx fifo
     if (tud_cdc_tx_complete_cb) tud_cdc_tx_complete_cb(itf);
 
@@ -479,7 +501,30 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
     }
   }
 
-  // nothing to do with notif endpoint for now
+  // Notifications
+  if (ep_addr == p_cdc->ep_notif) {
+    TU_LOG_DRV("  XFer Notification\r\n");
+    uint8_t const rhport = 0;
+
+    // SERIAL_STATE notification. Send flow control signals.
+    if (p_cdc->serial_state_changed) {
+      p_cdc->serial_state_changed = false;
+
+      // Build the notification
+      p_cdc->epnotif_buf = cdc_notify_serial_status;
+      p_cdc->epnotif_buf.header.wIndex = p_cdc->itf_num;
+      p_cdc->epnotif_buf.serial_state = p_cdc->serial_state;
+
+      // claim endpoint
+      TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_notif), 0);
+
+      // Send notification
+      return usbd_edpt_xfer(rhport, p_cdc->ep_notif, (uint8_t *) &(p_cdc->epnotif_buf), sizeof(p_cdc->epnotif_buf));
+    }
+    else {
+      // Send a NAK?
+    }
+  }
 
   return true;
 }
diff --git a/src/class/cdc/cdc_device.h b/src/class/cdc/cdc_device.h
index 463feedf..f7608237 100644
--- a/src/class/cdc/cdc_device.h
+++ b/src/class/cdc/cdc_device.h
@@ -70,6 +70,12 @@ bool tud_cdc_n_connected(uint8_t itf);
 // Get current line state. Bit 0:  DTR (Data Terminal Ready), Bit 1: RTS (Request to Send)
 uint8_t tud_cdc_n_get_line_state(uint8_t itf);
 
+// Get current serial state.
+cdc_serial_state_t tud_cdc_n_get_serial_state(uint8_t itf);
+
+// Set current serial state.
+void tud_cdc_n_set_serial_state(uint8_t itf, cdc_serial_state_t ser_state);
+
 // Get current line encoding: bit rate, stop bits parity etc ..
 void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding);
 
@@ -132,6 +138,14 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_cdc_get_line_state(void) {
   return tud_cdc_n_get_line_state(0);
 }
 
+TU_ATTR_ALWAYS_INLINE static inline cdc_serial_state_t tud_cdc_get_serial_state(void) {
+  return tud_cdc_n_get_serial_state(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_set_serial_state(cdc_serial_state_t ser_state) {
+  return tud_cdc_n_set_serial_state(0, ser_state);
+}
+
 TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_get_line_coding(cdc_line_coding_t* coding) {
   tud_cdc_n_get_line_coding(0, coding);
 }
diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h
index d9c6ea75..d05c75d2 100644
--- a/src/common/tusb_types.h
+++ b/src/common/tusb_types.h
@@ -491,6 +491,27 @@ typedef struct TU_ATTR_PACKED {
 
 TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8, "size is not correct");
 
+
+typedef struct TU_ATTR_PACKED {
+  union {
+    struct TU_ATTR_PACKED {
+      uint8_t recipient :  5; ///< Recipient type tusb_request_recipient_t.
+      uint8_t type      :  2; ///< Request type tusb_request_type_t.
+      uint8_t direction :  1; ///< Direction type. tusb_dir_t
+    } bmRequestType_bit;
+
+    uint8_t bmRequestType;
+  };
+
+  uint8_t bNotification;
+  uint16_t wValue;
+  uint16_t wIndex;
+  uint16_t wLength;
+} tusb_notification_t;
+
+TU_VERIFY_STATIC( sizeof(tusb_notification_t) == 8, "size is not correct");
+
+
 TU_ATTR_PACKED_END  // End of all packed definitions
 TU_ATTR_BIT_FIELD_ORDER_END
 

From f4edc86969164c0f910d1cf24607505cba54bb65 Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Fri, 21 Feb 2025 18:19:23 -0800
Subject: [PATCH 2/9] Fixing clang-format, white space issue. #491 #493

---
 src/arduino/Adafruit_USBD_CDC.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/arduino/Adafruit_USBD_CDC.h b/src/arduino/Adafruit_USBD_CDC.h
index 2fcd71ab..9df29db9 100644
--- a/src/arduino/Adafruit_USBD_CDC.h
+++ b/src/arduino/Adafruit_USBD_CDC.h
@@ -61,7 +61,7 @@ class Adafruit_USBD_CDC : public Stream, public Adafruit_USBD_Interface {
   uint8_t numbits(void);
 
   // Flow control bit getters.
-  int dtr(void);   // pre-existing, I don't want to change the return type.
+  int dtr(void); // pre-existing, I don't want to change the return type.
   bool rts(void);
   bool cts(void);
   bool dsr(void);

From 0803d8e8858b9c55f53c56e54608d35d3403e0b4 Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Fri, 21 Feb 2025 18:28:58 -0800
Subject: [PATCH 3/9] Nope, can't inline in a .cpp. Nothing else is inline,
 just remove. #491 #493

---
 src/arduino/Adafruit_USBD_CDC.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/arduino/Adafruit_USBD_CDC.cpp b/src/arduino/Adafruit_USBD_CDC.cpp
index ce0583b7..0c1e971b 100644
--- a/src/arduino/Adafruit_USBD_CDC.cpp
+++ b/src/arduino/Adafruit_USBD_CDC.cpp
@@ -170,23 +170,23 @@ int Adafruit_USBD_CDC::dtr(void) {
   return tud_cdc_n_connected(_instance);
 }
 
-inline bool Adafruit_USBD_CDC::rts(void) {
+bool Adafruit_USBD_CDC::rts(void) {
   return tud_cdc_n_get_line_state(_instance) & CDC_CONTROL_LINE_STATE_RTS;
 }
 
-inline bool Adafruit_USBD_CDC::cts(void) {
+bool Adafruit_USBD_CDC::cts(void) {
   return tud_cdc_n_get_serial_state(_instance).cts;
 }
 
-inline bool Adafruit_USBD_CDC::dsr(void) {
+bool Adafruit_USBD_CDC::dsr(void) {
   return tud_cdc_n_get_serial_state(_instance).dsr;
 }
 
-inline bool Adafruit_USBD_CDC::dcd(void) {
+bool Adafruit_USBD_CDC::dcd(void) {
   return tud_cdc_n_get_serial_state(_instance).dcd;
 }
 
-inline bool Adafruit_USBD_CDC::ri(void) {
+bool Adafruit_USBD_CDC::ri(void) {
   return tud_cdc_n_get_serial_state(_instance).ri;
 }
 

From b0634ba347027d1415573f4bc37ecc3177e8fccc Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Sat, 1 Mar 2025 09:32:55 -0800
Subject: [PATCH 4/9] Queue Notification when setting, not waiting for the
 callback. #491

---
 src/arduino/Adafruit_USBD_CDC.cpp | 10 -----
 src/arduino/Adafruit_USBD_CDC.h   |  5 ++-
 src/class/cdc/cdc.h               | 23 +++++-----
 src/class/cdc/cdc_device.c        | 70 +++++++++++++++----------------
 src/class/cdc/cdc_device.h        |  2 +-
 5 files changed, 51 insertions(+), 59 deletions(-)

diff --git a/src/arduino/Adafruit_USBD_CDC.cpp b/src/arduino/Adafruit_USBD_CDC.cpp
index 0c1e971b..5328dda6 100644
--- a/src/arduino/Adafruit_USBD_CDC.cpp
+++ b/src/arduino/Adafruit_USBD_CDC.cpp
@@ -174,10 +174,6 @@ bool Adafruit_USBD_CDC::rts(void) {
   return tud_cdc_n_get_line_state(_instance) & CDC_CONTROL_LINE_STATE_RTS;
 }
 
-bool Adafruit_USBD_CDC::cts(void) {
-  return tud_cdc_n_get_serial_state(_instance).cts;
-}
-
 bool Adafruit_USBD_CDC::dsr(void) {
   return tud_cdc_n_get_serial_state(_instance).dsr;
 }
@@ -190,12 +186,6 @@ bool Adafruit_USBD_CDC::ri(void) {
   return tud_cdc_n_get_serial_state(_instance).ri;
 }
 
-void Adafruit_USBD_CDC::cts(bool c) {
-  cdc_serial_state_t serial_state = tud_cdc_n_get_serial_state(_instance);
-  serial_state.cts = c;
-  tud_cdc_n_set_serial_state(_instance, serial_state);
-}
-
 void Adafruit_USBD_CDC::dsr(bool c) {
   cdc_serial_state_t serial_state = tud_cdc_n_get_serial_state(_instance);
   serial_state.dsr = c;
diff --git a/src/arduino/Adafruit_USBD_CDC.h b/src/arduino/Adafruit_USBD_CDC.h
index 9df29db9..3fb61423 100644
--- a/src/arduino/Adafruit_USBD_CDC.h
+++ b/src/arduino/Adafruit_USBD_CDC.h
@@ -63,16 +63,17 @@ class Adafruit_USBD_CDC : public Stream, public Adafruit_USBD_Interface {
   // Flow control bit getters.
   int dtr(void); // pre-existing, I don't want to change the return type.
   bool rts(void);
-  bool cts(void);
+  // bool cts(void);  // NOT PART OF THE CDC ACM SPEC?!
   bool dsr(void);
   bool dcd(void);
   bool ri(void);
 
   // Flow control bit setters.
-  void cts(bool c);
+  // void cts(bool c);  // NOT PART OF CDC ACM SPEC?!
   void dsr(bool c);
   void dcd(bool c);
   void ri(bool c);
+  // Break is a little harder, it's an event, not a state.
 
   // Stream API
   virtual int available(void);
diff --git a/src/class/cdc/cdc.h b/src/class/cdc/cdc.h
index cb31fdff..a5a5c5f8 100644
--- a/src/class/cdc/cdc.h
+++ b/src/class/cdc/cdc.h
@@ -416,21 +416,22 @@ TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 2, "size is not correct");
 // Notifications
 //--------------------------------------------------------------------+
 
-typedef struct TU_ATTR_PACKED {
-  uint16_t dcd         :1;
-  uint16_t dsr         :1;
-  uint16_t break_err   :1;
-  uint16_t ri          :1;
-  uint16_t frame_err   :1;
-  uint16_t parity_err  :1;
-  uint16_t overrun_err :1;
-  uint16_t cts         :1;  // https://community.st.com/t5/stm32-mcus-products/cts-signal-on-usb-cdc/td-p/325800
-  uint16_t             :8;
+typedef union TU_ATTR_PACKED {
+  struct {
+    uint16_t dcd         :1;
+    uint16_t dsr         :1;
+    uint16_t break_err   :1;
+    uint16_t ri          :1;
+    uint16_t frame_err   :1;
+    uint16_t parity_err  :1;
+    uint16_t overrun_err :1;
+    uint16_t             :9;
+  };
+  uint16_t state;
 } cdc_serial_state_t;
 
 TU_VERIFY_STATIC(sizeof(cdc_serial_state_t) == 2, "size is not correct");
 
-// Add more notifications here.  PSTN120.pdf, Section 6.5
 typedef struct TU_ATTR_PACKED {
   tusb_notification_t header;
   union {
diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c
index 18fd3fda..0c439d59 100644
--- a/src/class/cdc/cdc_device.c
+++ b/src/class/cdc/cdc_device.c
@@ -59,7 +59,7 @@ typedef struct {
   // Bit 0:  DTR (Data Terminal Ready), Bit 1: RTS (Request to Send)
   uint8_t line_state;
 
-  // Notify host of flow control bits: CTS, DSR, DCD, RI, and some error flags.
+  // Notify host of flow control bits: DSR, DCD, RI, and some error flags.
   cdc_serial_state_t serial_state;
   bool serial_state_changed;
 
@@ -120,6 +120,29 @@ static bool _prep_out_transaction (cdcd_interface_t* p_cdc) {
   }
 }
 
+bool _send_serial_state_notification(cdcd_interface_t *p_cdc) {
+  const uint8_t rhport = 0;
+
+  if (!p_cdc->serial_state_changed) {
+    // Nothing to do.
+    return true;
+  }
+
+  if (!usbd_edpt_claim(rhport, p_cdc->ep_notif)) {
+    // If claim failed, we're already in the middle of a transaction.
+    // cdcd_xfer_cb() will pick up this change.
+    return true;
+  }
+  
+  // We have the end point.  Build and send the notification.
+  p_cdc->serial_state_changed = false;
+  
+  p_cdc->epnotif_buf = cdc_notify_serial_status;
+  p_cdc->epnotif_buf.header.wIndex = p_cdc->itf_num;
+  p_cdc->epnotif_buf.serial_state = p_cdc->serial_state;
+  return usbd_edpt_xfer(rhport, p_cdc->ep_notif, (uint8_t *) &(p_cdc->epnotif_buf), sizeof(p_cdc->epnotif_buf));
+}
+
 //--------------------------------------------------------------------+
 // APPLICATION API
 //--------------------------------------------------------------------+
@@ -148,11 +171,12 @@ cdc_serial_state_t tud_cdc_n_get_serial_state(uint8_t itf) {
 }
 
 void tud_cdc_n_set_serial_state(uint8_t itf, cdc_serial_state_t serial_state) {
-  if (memcmp(&(_cdcd_itf[itf].serial_state), &serial_state, sizeof(serial_state)) != 0) {
-    TU_LOG_DRV("  Serial State Changed: %x -> %x\r\n", _cdcd_itf[itf].serial_state, serial_state);
-    _cdcd_itf[itf].serial_state_changed = true;
+  cdcd_interface_t* p_cdc = &_cdcd_itf[itf];
+  if (p_cdc->serial_state.state != serial_state.state) {
+    p_cdc->serial_state_changed = true;
+    p_cdc->serial_state = serial_state;
+    _send_serial_state_notification(p_cdc);
   }
-  _cdcd_itf[itf].serial_state = serial_state;
 }
 
 void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding) {
@@ -343,13 +367,10 @@ uint16_t cdcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1
 
   if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) {
     // notification endpoint
-    tusb_desc_endpoint_t desc_ep = *(tusb_desc_endpoint_t const*) p_desc;
-    TU_LOG_DRV("  Interval before: %d\r\n", desc_ep.bInterval);
-
-    desc_ep.bInterval = 1;  // Query every frame, 1ms at Full Speed.
+    tusb_desc_endpoint_t const* desc_ep = (tusb_desc_endpoint_t const*) p_desc;
 
-    TU_ASSERT(usbd_edpt_open(rhport, &desc_ep), 0);
-    p_cdc->ep_notif = desc_ep.bEndpointAddress;
+    TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
+    p_cdc->ep_notif = desc_ep->bEndpointAddress;
 
     drv_len += tu_desc_len(p_desc);
     p_desc = tu_desc_next(p_desc);
@@ -460,10 +481,9 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
     if ((ep_addr == p_cdc->ep_out) || (ep_addr == p_cdc->ep_in) || (ep_addr == p_cdc->ep_notif)) break;
   }
   TU_ASSERT(itf < CFG_TUD_CDC);
-
+  
   // Received new data
   if (ep_addr == p_cdc->ep_out) {
-    TU_LOG_DRV("  XFer Out\r\n");
     tu_fifo_write_n(&p_cdc->rx_ff, p_cdc->epout_buf, (uint16_t) xferred_bytes);
 
     // Check for wanted char and invoke callback if needed
@@ -486,7 +506,6 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
   // Note: This will cause incorrect baudrate set in line coding.
   //       Though maybe the baudrate is not really important !!!
   if (ep_addr == p_cdc->ep_in) {
-    TU_LOG_DRV("  XFer In\r\n");
     // invoke transmit callback to possibly refill tx fifo
     if (tud_cdc_tx_complete_cb) tud_cdc_tx_complete_cb(itf);
 
@@ -503,27 +522,8 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
 
   // Notifications
   if (ep_addr == p_cdc->ep_notif) {
-    TU_LOG_DRV("  XFer Notification\r\n");
-    uint8_t const rhport = 0;
-
-    // SERIAL_STATE notification. Send flow control signals.
-    if (p_cdc->serial_state_changed) {
-      p_cdc->serial_state_changed = false;
-
-      // Build the notification
-      p_cdc->epnotif_buf = cdc_notify_serial_status;
-      p_cdc->epnotif_buf.header.wIndex = p_cdc->itf_num;
-      p_cdc->epnotif_buf.serial_state = p_cdc->serial_state;
-
-      // claim endpoint
-      TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_notif), 0);
-
-      // Send notification
-      return usbd_edpt_xfer(rhport, p_cdc->ep_notif, (uint8_t *) &(p_cdc->epnotif_buf), sizeof(p_cdc->epnotif_buf));
-    }
-    else {
-      // Send a NAK?
-    }
+    // Send any changes that may have come in while sending the previous change.
+    return _send_serial_state_notification(p_cdc);
   }
 
   return true;
diff --git a/src/class/cdc/cdc_device.h b/src/class/cdc/cdc_device.h
index f7608237..d5522028 100644
--- a/src/class/cdc/cdc_device.h
+++ b/src/class/cdc/cdc_device.h
@@ -143,7 +143,7 @@ TU_ATTR_ALWAYS_INLINE static inline cdc_serial_state_t tud_cdc_get_serial_state(
 }
 
 TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_set_serial_state(cdc_serial_state_t ser_state) {
-  return tud_cdc_n_set_serial_state(0, ser_state);
+  tud_cdc_n_set_serial_state(0, ser_state);
 }
 
 TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_get_line_coding(cdc_line_coding_t* coding) {

From f016dcf902f7dae598f16c659c8023b8cc8c773f Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Sat, 1 Mar 2025 10:01:23 -0800
Subject: [PATCH 5/9] nrf52 core is including the wrong Adafruit_USBD_CDC.h
 #491

---
 src/arduino/Adafruit_USBD_CDC.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/arduino/Adafruit_USBD_CDC.cpp b/src/arduino/Adafruit_USBD_CDC.cpp
index 5328dda6..ceacda87 100644
--- a/src/arduino/Adafruit_USBD_CDC.cpp
+++ b/src/arduino/Adafruit_USBD_CDC.cpp
@@ -27,11 +27,12 @@
 // esp32 use built-in core cdc
 #if CFG_TUD_CDC && !defined(ARDUINO_ARCH_ESP32)
 
+#include "Adafruit_USBD_CDC.h"
+
 #include "Arduino.h"
 
 #include "Adafruit_TinyUSB_API.h"
 
-#include "Adafruit_USBD_CDC.h"
 #include "Adafruit_USBD_Device.h"
 
 #ifndef TINYUSB_API_VERSION

From 5311fb83c4407041538d91791714457d66a81ae4 Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Sat, 1 Mar 2025 10:24:40 -0800
Subject: [PATCH 6/9] Tests are failing, using wrong SdFat library. #491

---
 examples/MassStorage/msc_external_flash/.skip.txt        | 1 +
 examples/MassStorage/msc_external_flash_sdcard/.skip.txt | 1 +
 examples/MassStorage/msc_sdfat/.skip.txt                 | 1 +
 src/arduino/Adafruit_USBD_CDC.cpp                        | 3 +++
 4 files changed, 6 insertions(+)

diff --git a/examples/MassStorage/msc_external_flash/.skip.txt b/examples/MassStorage/msc_external_flash/.skip.txt
index f8e761cb..66aeeee1 100644
--- a/examples/MassStorage/msc_external_flash/.skip.txt
+++ b/examples/MassStorage/msc_external_flash/.skip.txt
@@ -1,2 +1,3 @@
 pico_rp2040_tinyusb_host
 CH32V20x_EVT
+feather_rp2040_tinyusb
diff --git a/examples/MassStorage/msc_external_flash_sdcard/.skip.txt b/examples/MassStorage/msc_external_flash_sdcard/.skip.txt
index f8e761cb..66aeeee1 100644
--- a/examples/MassStorage/msc_external_flash_sdcard/.skip.txt
+++ b/examples/MassStorage/msc_external_flash_sdcard/.skip.txt
@@ -1,2 +1,3 @@
 pico_rp2040_tinyusb_host
 CH32V20x_EVT
+feather_rp2040_tinyusb
diff --git a/examples/MassStorage/msc_sdfat/.skip.txt b/examples/MassStorage/msc_sdfat/.skip.txt
index a0185409..77a312c7 100644
--- a/examples/MassStorage/msc_sdfat/.skip.txt
+++ b/examples/MassStorage/msc_sdfat/.skip.txt
@@ -1 +1,2 @@
 pico_rp2040_tinyusb_host
+feather_rp2040_tinyusb
diff --git a/src/arduino/Adafruit_USBD_CDC.cpp b/src/arduino/Adafruit_USBD_CDC.cpp
index ceacda87..afe27d5f 100644
--- a/src/arduino/Adafruit_USBD_CDC.cpp
+++ b/src/arduino/Adafruit_USBD_CDC.cpp
@@ -27,6 +27,9 @@
 // esp32 use built-in core cdc
 #if CFG_TUD_CDC && !defined(ARDUINO_ARCH_ESP32)
 
+// Include before "Arduino.h" because TinyUSB is part of platform cores
+// When developing TinyUSB, it needs to include the local version, not the
+// platform core version, which is what gets included by Arduino.h
 #include "Adafruit_USBD_CDC.h"
 
 #include "Arduino.h"

From b10719b7cd071a667c725aee3b7ceaa92e65ddb8 Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Thu, 20 Mar 2025 15:08:06 -0700
Subject: [PATCH 7/9] RP2040 only support full speed. Advertising as USB2.0
 triggers irrelevant negotiation.

---
 src/arduino/Adafruit_USBD_Device.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/arduino/Adafruit_USBD_Device.cpp b/src/arduino/Adafruit_USBD_Device.cpp
index 068a9890..410c01d3 100644
--- a/src/arduino/Adafruit_USBD_Device.cpp
+++ b/src/arduino/Adafruit_USBD_Device.cpp
@@ -176,7 +176,11 @@ bool Adafruit_USBD_Device::attach(void) { return tud_connect(); }
 void Adafruit_USBD_Device::clearConfiguration(void) {
   tusb_desc_device_t const desc_dev = {.bLength = sizeof(tusb_desc_device_t),
                                        .bDescriptorType = TUSB_DESC_DEVICE,
+#if CFG_TUSB_MCU==OPT_MCU_RP2040  // RP2040 only supports full speed
+                                       .bcdUSB = 0x0110,
+#else
                                        .bcdUSB = 0x0200,
+#endif
                                        .bDeviceClass = 0,
                                        .bDeviceSubClass = 0,
                                        .bDeviceProtocol = 0,

From 65dc1bdf87d85a3453b95fb1c4462189e77c957e Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Thu, 20 Mar 2025 15:40:42 -0700
Subject: [PATCH 8/9] pre-commit Clang format failure. #491

---
 src/arduino/Adafruit_USBD_Device.cpp | 29 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 15 deletions(-)

diff --git a/src/arduino/Adafruit_USBD_Device.cpp b/src/arduino/Adafruit_USBD_Device.cpp
index bb23f4db..d387a425 100644
--- a/src/arduino/Adafruit_USBD_Device.cpp
+++ b/src/arduino/Adafruit_USBD_Device.cpp
@@ -163,24 +163,23 @@ void Adafruit_USBD_Device::task(void) {
 
 void Adafruit_USBD_Device::clearConfiguration(void) {
   tusb_desc_device_t const desc_dev = {.bLength = sizeof(tusb_desc_device_t),
-                                       .bDescriptorType = TUSB_DESC_DEVICE,
+    .bDescriptorType = TUSB_DESC_DEVICE,
 #if CFG_TUSB_MCU==OPT_MCU_RP2040  // RP2040 only supports full speed
-                                       .bcdUSB = 0x0110,
+    .bcdUSB = 0x0110,
 #else
-                                       .bcdUSB = 0x0200,
+    .bcdUSB = 0x0200,
 #endif
-                                       .bDeviceClass = 0,
-                                       .bDeviceSubClass = 0,
-                                       .bDeviceProtocol = 0,
-                                       .bMaxPacketSize0 =
-                                           CFG_TUD_ENDPOINT0_SIZE,
-                                       .idVendor = USB_VID,
-                                       .idProduct = USB_PID,
-                                       .bcdDevice = 0x0100,
-                                       .iManufacturer = STRID_MANUFACTURER,
-                                       .iProduct = STRID_PRODUCT,
-                                       .iSerialNumber = STRID_SERIAL,
-                                       .bNumConfigurations = 0x01};
+    .bDeviceClass = 0,
+    .bDeviceSubClass = 0,
+    .bDeviceProtocol = 0,
+    .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+    .idVendor = USB_VID,
+    .idProduct = USB_PID,
+    .bcdDevice = 0x0100,
+    .iManufacturer = STRID_MANUFACTURER,
+    .iProduct = STRID_PRODUCT,
+    .iSerialNumber = STRID_SERIAL,
+    .bNumConfigurations = 0x01};
 
   _desc_device = desc_dev;
 

From 8d9ee2aa8f651614f13b84fd1f042c47b5bb6f85 Mon Sep 17 00:00:00 2001
From: Mark Smith <mark-github@halibut.com>
Date: Thu, 20 Mar 2025 15:46:16 -0700
Subject: [PATCH 9/9] More Clang format changes. #491

---
 src/arduino/Adafruit_USBD_Device.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/arduino/Adafruit_USBD_Device.cpp b/src/arduino/Adafruit_USBD_Device.cpp
index d387a425..246bf628 100644
--- a/src/arduino/Adafruit_USBD_Device.cpp
+++ b/src/arduino/Adafruit_USBD_Device.cpp
@@ -162,9 +162,10 @@ void Adafruit_USBD_Device::task(void) {
 }
 
 void Adafruit_USBD_Device::clearConfiguration(void) {
-  tusb_desc_device_t const desc_dev = {.bLength = sizeof(tusb_desc_device_t),
+  tusb_desc_device_t const desc_dev = {
+    .bLength = sizeof(tusb_desc_device_t),
     .bDescriptorType = TUSB_DESC_DEVICE,
-#if CFG_TUSB_MCU==OPT_MCU_RP2040  // RP2040 only supports full speed
+#if CFG_TUSB_MCU == OPT_MCU_RP2040 // RP2040 only supports full speed
     .bcdUSB = 0x0110,
 #else
     .bcdUSB = 0x0200,
@@ -179,7 +180,8 @@ void Adafruit_USBD_Device::clearConfiguration(void) {
     .iManufacturer = STRID_MANUFACTURER,
     .iProduct = STRID_PRODUCT,
     .iSerialNumber = STRID_SERIAL,
-    .bNumConfigurations = 0x01};
+    .bNumConfigurations = 0x01
+  };
 
   _desc_device = desc_dev;