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

Generated by: LCOV version 1.15