LCOV - code coverage report
Current view: top level - src/MIDI_Interfaces/USBMIDI/LowLevel - BulkTX.ipp (source / functions) Hit Total Coverage
Test: c22d7811c47f5ebc3c5b30825a508cc45759f5c0 Lines: 99 133 74.4 %
Date: 2024-10-27 14:39:27 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #include "BulkTX.hpp"
       2             : 
       3             : #include <AH/Arduino-Wrapper.h>
       4             : #include <AH/Containers/CRTP.hpp>
       5             : 
       6             : #include <algorithm>
       7             : 
       8             : #include <cassert>
       9             : #include <cstring>
      10             : 
      11             : #ifdef FATAL_ERRORS
      12             : #define CS_MIDI_USB_ASSERT(a) assert((a))
      13             : #else
      14             : #define CS_MIDI_USB_ASSERT(a)
      15             : #endif
      16             : 
      17             : BEGIN_CS_NAMESPACE
      18             : 
      19             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
      20             : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::write(MessageType msg) {
      21             :     write(&msg, 1);
      22             : }
      23             : 
      24             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
      25       67496 : bool BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::wait_connect() {
      26       67496 :     if (CRTP(Derived).connectedForWrite()) {
      27       67496 :         disconnected = false;
      28       67496 :         return true; // connection is okay
      29           0 :     } else if (disconnected) {
      30           0 :         return false; // don't retry
      31             :     }
      32             : #ifdef ARDUINO
      33             :     // Wait for up to half a second (or until connected)
      34             :     for (int i = 0; i < 100; ++i) {
      35             :         delay(5);
      36             :         if (CRTP(Derived).connectedForWrite()) {
      37             :             disconnected = false;
      38             :             return true;
      39             :         }
      40             :     }
      41             : #endif
      42           0 :     disconnected = true;
      43           0 :     return false;
      44             : }
      45             : 
      46             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
      47       67496 : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::write(
      48             :     const MessageType *msgs, uint32_t num_msgs) {
      49       67496 :     if (!wait_connect()) {
      50           0 :         writing.error.fetch_add(num_msgs, mo_rlx);
      51           0 :         return;
      52             :     }
      53       67496 :     const uint32_t *end = msgs + num_msgs;
      54      170413 :     while (msgs != end) {
      55      102917 :         auto sent = write_impl(msgs, end - msgs);
      56      102917 :         if (sent == 0 && !wait_connect()) {
      57           0 :             writing.error.fetch_add(end - msgs, mo_rlx);
      58           0 :             return;
      59             :         }
      60      102917 :         msgs += sent;
      61             :     }
      62             : }
      63             : 
      64             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
      65             : uint32_t BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::write_nonblock(
      66             :     const MessageType *msgs, uint32_t num_msgs) {
      67             :     if (!CRTP(Derived).connectedForWrite())
      68             :         return 0;
      69             :     uint32_t total_sent = 0, sent = 1;
      70             :     while (total_sent < num_msgs && sent != 0) {
      71             :         sent = write_impl(msgs + total_sent, num_msgs - total_sent, true);
      72             :         total_sent += sent;
      73             :     }
      74             :     return total_sent;
      75             : }
      76             : 
      77             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
      78        8333 : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::send_now() {
      79        8333 :     auto buffer = writing.send_later.exchange(nullptr, mo_acq);
      80        8333 :     if (buffer == nullptr)
      81             :         // Either the write function or the timeout_handler already cleared
      82             :         // the send_later flag.
      83        1827 :         return;
      84        6506 :     CRTP(Derived).cancel_timeout();
      85             : 
      86             :     // Indicate to any handlers interrupting us that we intend to send a buffer.
      87        6506 :     writing.send_now.store(buffer, mo_seq); //                               (5)
      88             : 
      89             :     // Try to acquire the sending lock.
      90        6506 :     wbuffer_t *old = nullptr;
      91        6506 :     if (!writing.sending.compare_exchange_strong(old, buffer, mo_seq)) //    (6)
      92             :         // If we couldn't get the lock, whoever has the lock will send the
      93             :         // data and clear send-now.
      94           0 :         return;
      95             : 
      96             :     // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq >>>sending<<<
      97             :     // We now own the sending lock
      98             : 
      99             :     // It is possible that the tx_callback ran between (5) and (6), so check
     100             :     // the send-now flag again.
     101        6506 :     auto send_now = writing.send_now.load(mo_seq);
     102        6506 :     if (send_now == nullptr) {
     103             :         // If the buffer was already sent, release the sending lock and return.
     104           0 :         writing.sending.store(nullptr, mo_seq); // ▲▲▲
     105           0 :         return;
     106             :     }
     107             : 
     108             :     // Us having the sending lock also means that the timeout and the
     109             :     // tx_callback cannot have concurrent access to writing.active_writebuffer
     110             :     // Therefore, acquiring it must succeed.
     111        6506 :     auto send_buffer = writing.active_writebuffer.exchange(nullptr, mo_seq);
     112             :     // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq <<<active_writebuffer>>>
     113             :     CS_MIDI_USB_ASSERT(send_buffer == buffer);
     114             :     CS_MIDI_USB_ASSERT(send_now == buffer);
     115             :     // Now that we own both the buffer and the sending lock, we can send it
     116        6506 :     writing.send_now.store(nullptr, mo_rlx);
     117             :     // Prepare the other buffer to be filled
     118        6506 :     auto next_buffer = other_buf(send_buffer);
     119        6506 :     next_buffer->size = 0;
     120             :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     121        6506 :     writing.active_writebuffer.store(next_buffer, mo_rel);
     122             :     // Send the current buffer
     123        6506 :     CRTP(Derived).tx_start(send_buffer->buffer, send_buffer->size);
     124             :     // ------------------------------------------------------------------------- (sending lock is released by the tx_callback)
     125        6506 :     return;
     126             : }
     127             : 
     128             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
     129             : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::reset(
     130             :     uint16_t packet_size) {
     131             :     writing.packet_size = packet_size;
     132             :     writing.buffers[0].size = 0;
     133             :     writing.buffers[1].size = 0;
     134             :     writing.active_writebuffer.store(&writing.buffers[0], mo_rlx);
     135             :     writing.send_later.store(nullptr, mo_rlx);
     136             :     writing.send_now.store(nullptr, mo_rlx);
     137             :     writing.sending.store(nullptr, mo_rlx);
     138             : }
     139             : 
     140             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
     141           1 : bool BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::is_done() const {
     142           2 :     return writing.sending.load(mo_acq) == nullptr &&
     143           2 :            writing.send_later.load(mo_acq) == nullptr &&
     144           2 :            writing.send_now.load(mo_acq) == nullptr;
     145             : }
     146             : 
     147             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
     148      102917 : uint32_t BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::write_impl(
     149             :     const MessageType *msgs, uint32_t num_msgs) {
     150      102917 :     if (num_msgs == 0)
     151           0 :         return 0;
     152             : 
     153             :     // Try to get access to an available buffer
     154      102917 :     wbuffer_t *buffer = writing.active_writebuffer.exchange(nullptr, mo_acq);
     155             :     // If that failed, return without blocking, caller may retry until we get
     156             :     // a buffer we can use
     157      102917 :     if (buffer == nullptr)
     158           0 :         return 0;
     159             : 
     160             :     // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq <<<active_writebuffer>>>
     161             :     // At this point we have a buffer, and we have exclusive access to it,
     162             :     // but it may still be full
     163             :     // CS_MIDI_USB_ASSERT(writing.send_now.load(mo_rlx) == nullptr);
     164             : 
     165      102917 :     auto size = buffer->size;
     166             :     CS_MIDI_USB_ASSERT(size != SizeReserved);
     167      102917 :     size_t avail_size = writing.packet_size - size;
     168      102917 :     auto copy_size_zu = std::min<size_t>(avail_size, num_msgs * sizeof(*msgs));
     169      102917 :     auto copy_size = static_cast<uint16_t>(copy_size_zu);
     170      102917 :     if (copy_size > 0) {
     171      102917 :         std::memcpy(buffer->buffer + size, msgs, copy_size);
     172      102917 :         buffer->size = size + copy_size;
     173             :     }
     174             :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     175             :     // Release access to the buffer
     176      102917 :     writing.active_writebuffer.store(buffer, mo_seq);
     177             : 
     178      102917 :     if (copy_size > 0) {
     179             :         // If we completely filled this buffer in one go, send it now.
     180      102917 :         if (size == 0 && copy_size == writing.packet_size) {
     181             :             CS_MIDI_USB_ASSERT(writing.send_later.load(mo_rlx) == nullptr);
     182             :             CS_MIDI_USB_ASSERT(writing.send_now.load(mo_rlx) == nullptr);
     183       31751 :             writing.send_now.store(buffer, mo_rel); // TODO: can be relaxed
     184             :         }
     185             :         // If this is the first data in the buffer, schedule it to be sent later.
     186       71166 :         else if (size == 0) {
     187             :             CS_MIDI_USB_ASSERT(writing.send_later.load(mo_rlx) == nullptr);
     188       63187 :             writing.send_later.store(buffer, mo_rel);
     189       63187 :             CRTP(Derived).start_timeout();
     190             :         }
     191             :         // If this buffer was partially filled before and now full, send it now.
     192        7979 :         else if (size + copy_size == writing.packet_size) {
     193        5983 :             auto send_buffer = writing.send_later.exchange(nullptr, mo_acq);
     194        5983 :             if (send_buffer != nullptr) {
     195             :                 CS_MIDI_USB_ASSERT(writing.send_now.load(mo_rlx) == nullptr);
     196             :                 CS_MIDI_USB_ASSERT(send_buffer == buffer);
     197        5982 :                 CRTP(Derived).cancel_timeout();
     198        5982 :                 writing.send_now.store(buffer, mo_rel); // TODO: can be relaxed
     199             :             }
     200             :         }
     201             :     }
     202             : 
     203             :     // An interrupt may have attempted to send the buffer while we owned it.
     204      102917 :     if (writing.send_now.load(mo_seq) == nullptr) //                         (1)
     205             :         // If that's not the case, we can safely return.
     206       65184 :         return copy_size / sizeof(*msgs);
     207             : 
     208             :     // Otherwise, it's our job to send the data that the interrupt failed to
     209             :     // send.
     210             : 
     211             :     // Try to acquire the sending lock.
     212       37733 :     wbuffer_t *old = nullptr;
     213       37733 :     if (!writing.sending.compare_exchange_strong(old, buffer, mo_seq)) //    (2)
     214             :         // If we couldn't get the lock, whoever has the lock will send the
     215             :         // data and clear send-now.
     216           0 :         return copy_size / sizeof(*msgs);
     217             :     // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq >>>sending<<<
     218             :     // We now own the sending lock
     219             : 
     220             :     // It is possible that the tx_callback ran between (1) and (2), so check
     221             :     // the send-now flag again.
     222       37733 :     auto send_now = writing.send_now.load(mo_seq);
     223       37733 :     if (send_now == nullptr) {
     224             :         // If the buffer was already sent, release the sending lock and return.
     225           0 :         writing.sending.store(nullptr, mo_seq); // ▲▲▲
     226           0 :         return copy_size / sizeof(*msgs);
     227             :     }
     228             : 
     229             :     // Us having the sending lock also means that the timeout and the
     230             :     // tx_callback cannot have concurrent access to writing.active_writebuffer
     231             :     // Therefore, acquiring it must succeed.
     232       37733 :     auto send_buffer = writing.active_writebuffer.exchange(nullptr, mo_seq);
     233             :     // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq <<<active_writebuffer>>>
     234             :     CS_MIDI_USB_ASSERT(send_buffer == buffer);
     235             :     CS_MIDI_USB_ASSERT(send_now == buffer);
     236             :     // Now that we own both the buffer and the sending lock, we can send it
     237       37733 :     writing.send_now.store(nullptr, mo_rlx);
     238             :     // Prepare the other buffer to be filled
     239       37733 :     auto next_buffer = other_buf(send_buffer);
     240       37733 :     next_buffer->size = 0;
     241             :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     242       37733 :     writing.active_writebuffer.store(next_buffer, mo_rel);
     243             :     // Send the current buffer
     244       37733 :     CRTP(Derived).tx_start(send_buffer->buffer, send_buffer->size);
     245             :     // ------------------------------------------------------------------------- (sending lock is released by the tx_callback)
     246             : 
     247             :     // TODO: if copy_size == 0, we could retry here
     248             : 
     249       37733 :     return copy_size / sizeof(*msgs);
     250             : }
     251             : 
     252             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
     253       50699 : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::timeout_callback() {
     254       50699 :     auto buffer = writing.send_later.exchange(nullptr, mo_acq);
     255       50699 :     if (buffer == nullptr)
     256             :         // Either the write function or the send_now function already cleared
     257             :         // the send_later flag.
     258           0 :         return;
     259             : 
     260             :     // Indicate to any handlers interrupting us that we intend to send a buffer.
     261       50699 :     writing.send_now.store(buffer, mo_seq); //                               (7)
     262             : 
     263             :     // Try to acquire the sending lock.
     264       50699 :     wbuffer_t *old = nullptr;
     265       50699 :     if (!writing.sending.compare_exchange_strong(old, buffer, mo_seq)) //    (8)
     266             :         // If we couldn't get the lock, whoever has the lock will send the
     267             :         // data and clear send-now.
     268           1 :         return;
     269             :     // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq >>>sending<<<
     270             :     // We now own the sending lock
     271             : 
     272             :     // It is possible that the tx_callback ran between (7) and (8), so check
     273             :     // the send-now flag again.
     274       50698 :     auto send_now = writing.send_now.load(mo_seq);
     275       50698 :     if (send_now == nullptr) {
     276             :         // If the buffer was already sent, release the sending lock and return.
     277           0 :         writing.sending.store(nullptr, mo_seq); // ▲▲▲
     278           0 :         return;
     279             :     }
     280             : 
     281             :     // Us having the sending lock also means that the timeout and the
     282             :     // tx_callback cannot have concurrent access to writing.active_writebuffer
     283             :     // Therefore, acquiring it must succeed.
     284       50698 :     if (auto act = writing.active_writebuffer.exchange(nullptr, mo_seq)) {
     285             :         // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq <<<active_writebuffer>>>
     286             :         CS_MIDI_USB_ASSERT(act == buffer);
     287             :         CS_MIDI_USB_ASSERT(send_now == buffer);
     288             :         // Now that we own both the buffer and the sending lock, we can send it
     289       50698 :         writing.send_now.store(nullptr, mo_rlx);
     290             :         // Prepare the other buffer to be filled
     291       50698 :         auto next_buffer = other_buf(act);
     292       50698 :         next_buffer->size = 0;
     293             :         // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     294       50698 :         writing.active_writebuffer.store(next_buffer, mo_rel);
     295             :         // Send the current buffer
     296       50698 :         CRTP(Derived).tx_start_timeout(buffer->buffer, buffer->size);
     297             :         // --------------------------------------------------------------------- (sending lock is released by the tx_callback)
     298       50698 :         return;
     299             :     }
     300             :     // The write function owns the buffer.
     301             :     // TODO: only valid if this is an interrupt handler, see tx_callback comment
     302             :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel >>>sending<<<
     303           0 :     writing.sending.store(nullptr, mo_seq);
     304           0 :     return;
     305             : }
     306             : 
     307             : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
     308       94938 : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::tx_callback() {
     309             :     // ------------------------------------------------------------------------- (we still own the sending lock)
     310       94938 :     wbuffer_t *sent_buffer = writing.sending.load(mo_acq);
     311             :     CS_MIDI_USB_ASSERT(sent_buffer != nullptr);
     312             : 
     313             :     // Check if anyone tried to send the next buffer while the previous one
     314             :     // was still being sent
     315       94938 :     wbuffer_t *send_next = writing.send_now.load(mo_seq);
     316       94938 :     if (send_next) {
     317             :         CS_MIDI_USB_ASSERT(send_next != sent_buffer);
     318             :         // We already own the sending lock.
     319           1 :         if (auto act = writing.active_writebuffer.exchange(nullptr, mo_seq)) {
     320             :             // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq <<<active_writebuffer>>>
     321             :             CS_MIDI_USB_ASSERT(act == send_next);
     322           1 :             writing.send_now.store(nullptr, mo_rlx);
     323           1 :             sent_buffer->size = 0;
     324           1 :             writing.sending.store(send_next, mo_seq);
     325             :             // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     326           1 :             writing.active_writebuffer.store(sent_buffer, mo_seq);
     327           1 :             CRTP(Derived).tx_start_isr(send_next->buffer, send_next->size);
     328           1 :             return;
     329             :             // ----------------------------------------------------------------- (sending lock is released by the next tx_callback)
     330             :         }
     331             :         // Someone else already holds the active_buffer lock.
     332             :         // We own the sending lock, but not the buffer to be sent. Since the
     333             :         // timeout and send_now can only own the buffer if they also own the
     334             :         // sending lock, it must be the write function that owns the buffer.
     335             :         // The write function always checks the send-now flag after releasing
     336             :         // the buffer, so we can safely release our sending lock and return.
     337             :         // In the case of interrupts/signal handlers, our release of the lock
     338             :         // happens-before the release of the release of the active buffer in
     339             :         // the write function, so there would be no need to check send_now
     340             :         // again after releasing. In different threads, we would need to check
     341             :         // again, because the write thread could try to acquire the sending
     342             :         // lock before we released it.
     343             :         // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel >>>sending<<<
     344           0 :         writing.sending.store(nullptr, mo_seq);
     345           0 :         return;
     346             : 
     347             :         // TODO: can we do it for the threaded case? At first sight, we should
     348             :         //       try to acquire the active write buffer first, and then try
     349             :         //       to acquire the sending lock again.
     350             :         //       Alternatively, we could add a “main-should-wait” flag to
     351             :         //       indicate that the write function should do a busy wait because
     352             :         //       we'll release the sending lock soon.
     353             :     }
     354             : 
     355             :     // Release the “sending” lock
     356             :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel >>>sending<<<
     357       94937 :     writing.sending.store(nullptr, mo_seq);
     358             : 
     359             :     // Someone may have tried sending the other buffer while we were still
     360             :     // holding the sending lock, so check the send_now flag (again).
     361       94937 :     send_next = writing.send_now.load(mo_seq); //                            (3)
     362       94937 :     if (send_next == nullptr)
     363             :         // No one tried to send before, and if they try to send later, they will
     364             :         // be able to get the lock to do so.
     365       94937 :         return;
     366             : 
     367             :     // Someone tried to send. We must try to send now.
     368           0 :     wbuffer_t *old = nullptr;
     369           0 :     if (!writing.sending.compare_exchange_strong(old, send_next, mo_seq)) // (4)
     370             :         // If this fails, someone else must have been able to acquire the
     371             :         // sending lock in the meantime, and will be able to make progress.
     372           0 :         return;
     373             : 
     374             :     // The send_now flag must still be true: either the timeout fired between
     375             :     // (3) and (4) and clears it, in which case it holds on to the sending lock,
     376             :     // or it failed to get both the sending lock and the buffer, and in that
     377             :     // case it doesn't clear send-now. Similar for the main program.
     378             :     CS_MIDI_USB_ASSERT(writing.send_now.load(mo_seq) == send_next);
     379             : 
     380             :     // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq >>>sending<<<
     381             :     // We now hold the sending lock (again)
     382             : 
     383             :     // This is the same as earlier
     384           0 :     if (auto act = writing.active_writebuffer.exchange(nullptr, mo_seq)) {
     385             :         // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq <<<active_writebuffer>>>
     386             :         CS_MIDI_USB_ASSERT(act == send_next);
     387           0 :         writing.send_now.store(nullptr, mo_rlx);
     388           0 :         sent_buffer->size = 0;
     389           0 :         writing.sending.store(send_next, mo_seq);
     390             :         // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     391           0 :         writing.active_writebuffer.store(sent_buffer, mo_seq);
     392           0 :         CRTP(Derived).tx_start_isr(send_next->buffer, send_next->size);
     393           0 :         return;
     394             :         // --------------------------------------------------------------------- (sending lock is released by the next tx_callback)
     395             :     }
     396             :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel >>>sending<<<
     397           0 :     writing.sending.store(nullptr, mo_seq);
     398             : 
     399             :     // TODO: same as above
     400             : }
     401             : 
     402             : END_CS_NAMESPACE
     403             : 
     404             : #undef CS_MIDI_USB_ASSERT

Generated by: LCOV version 1.15