LCOV - code coverage report
Current view: top level - src/MIDI_Interfaces/USBMIDI/LowLevel - BulkTX.ipp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 73.5 % 132 97
Test Date: 2026-06-06 17:44:35 Functions: 100.0 % 7 7
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       170409 :     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         1828 :         return;
      77         6505 :     CRTP(Derived).cancel_timeout();
      78              : 
      79              :     // Indicate to any handlers interrupting us that we intend to send a buffer.
      80         6505 :     writing.send_now.store(buffer, mo_seq); //                               (5)
      81              : 
      82              :     // Try to acquire the sending lock.
      83         6505 :     wbuffer_t *old = nullptr;
      84         6505 :     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         6505 :     auto send_now = writing.send_now.load(mo_seq);
      95         6505 :     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         6505 :     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         6505 :     writing.send_now.store(nullptr, mo_rlx);
     110              :     // Prepare the other buffer to be filled
     111         6505 :     auto next_buffer = other_buf(send_buffer);
     112         6505 :     next_buffer->size = 0;
     113              :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     114         6505 :     writing.active_writebuffer.store(next_buffer, mo_rel);
     115              :     // Send the current buffer
     116         6505 :     CRTP(Derived).tx_start(send_buffer->buffer, send_buffer->size);
     117              :     // ------------------------------------------------------------------------- (sending lock is released by the tx_callback)
     118         6505 :     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       102913 : uint32_t BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::write_impl(
     142              :     const MessageType *msgs, uint32_t num_msgs) {
     143       102913 :     if (num_msgs == 0)
     144            0 :         return 0;
     145              : 
     146              :     // Try to get access to an available buffer
     147       102913 :     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       102913 :     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       102913 :     auto size = buffer->size;
     159              :     CS_MIDI_USB_ASSERT(size != SizeReserved);
     160       102913 :     size_t avail_size = writing.packet_size - size;
     161       102913 :     auto copy_size_zu = std::min<size_t>(avail_size, num_msgs * sizeof(*msgs));
     162       102913 :     auto copy_size = static_cast<uint16_t>(copy_size_zu);
     163       102913 :     if (copy_size > 0) {
     164       102912 :         std::memcpy(buffer->buffer + size, msgs, copy_size);
     165       102912 :         buffer->size = size + copy_size;
     166              :     }
     167              :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     168              :     // Release access to the buffer
     169       102913 :     writing.active_writebuffer.store(buffer, mo_seq);
     170              : 
     171       102913 :     if (copy_size > 0) {
     172              :         // If we completely filled this buffer in one go, send it now.
     173       102912 :         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        31753 :             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        71159 :         else if (size == 0) {
     180              :             CS_MIDI_USB_ASSERT(writing.send_later.load(mo_rlx) == nullptr);
     181        63187 :             writing.send_later.store(buffer, mo_rel);
     182        63187 :             CRTP(Derived).start_timeout();
     183              :         }
     184              :         // If this buffer was partially filled before and now full, send it now.
     185         7972 :         else if (size + copy_size == writing.packet_size) {
     186         5978 :             auto send_buffer = writing.send_later.exchange(nullptr, mo_acq);
     187         5978 :             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         5978 :                 CRTP(Derived).cancel_timeout();
     191         5978 :                 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       102913 :     if (writing.send_now.load(mo_seq) == nullptr) //                         (1)
     198              :         // If that's not the case, we can safely return.
     199        65181 :         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        37732 :     wbuffer_t *old = nullptr;
     206        37732 :     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            4 :         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        37728 :     auto send_now = writing.send_now.load(mo_seq);
     216        37728 :     if (send_now == nullptr) {
     217              :         // If the buffer was already sent, release the sending lock and return.
     218            0 :         writing.sending.store(nullptr, mo_seq); // ▲▲▲
     219            0 :         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        37728 :     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        37728 :     writing.send_now.store(nullptr, mo_rlx);
     231              :     // Prepare the other buffer to be filled
     232        37728 :     auto next_buffer = other_buf(send_buffer);
     233        37728 :     next_buffer->size = 0;
     234              :     // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     235        37728 :     writing.active_writebuffer.store(next_buffer, mo_rel);
     236              :     // Send the current buffer
     237        37728 :     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        37728 :     return copy_size / sizeof(*msgs);
     243              : }
     244              : 
     245              : template <class Derived, class MessageTypeT, uint16_t MaxPacketSizeV>
     246        50704 : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::timeout_callback() {
     247        50704 :     auto buffer = writing.send_later.exchange(nullptr, mo_acq);
     248        50704 :     if (buffer == nullptr)
     249              :         // Either the write function or the send_now function already cleared
     250              :         // the send_later flag.
     251            0 :         return;
     252              : 
     253              :     // Indicate to any handlers interrupting us that we intend to send a buffer.
     254        50704 :     writing.send_now.store(buffer, mo_seq); //                               (7)
     255              : 
     256              :     // Try to acquire the sending lock.
     257        50704 :     wbuffer_t *old = nullptr;
     258        50704 :     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        50703 :     auto send_now = writing.send_now.load(mo_seq);
     268        50703 :     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        50703 :     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        50703 :         writing.send_now.store(nullptr, mo_rlx);
     283              :         // Prepare the other buffer to be filled
     284        50703 :         auto next_buffer = other_buf(act);
     285        50703 :         next_buffer->size = 0;
     286              :         // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     287        50703 :         writing.active_writebuffer.store(next_buffer, mo_rel);
     288              :         // Send the current buffer
     289        50703 :         CRTP(Derived).tx_start_timeout(buffer->buffer, buffer->size);
     290              :         // --------------------------------------------------------------------- (sending lock is released by the tx_callback)
     291        50703 :         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        94940 : void BulkTX<Derived, MessageTypeT, MaxPacketSizeV>::tx_callback() {
     302              :     // ------------------------------------------------------------------------- (we still own the sending lock)
     303        94940 :     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        94940 :     wbuffer_t *send_next = writing.send_now.load(mo_seq);
     309        94940 :     if (send_next) {
     310              :         CS_MIDI_USB_ASSERT(send_next != sent_buffer);
     311              :         // We already own the sending lock.
     312            4 :         if (auto act = writing.active_writebuffer.exchange(nullptr, mo_seq)) {
     313              :             // ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ acq <<<active_writebuffer>>>
     314              :             CS_MIDI_USB_ASSERT(act == send_next);
     315            4 :             writing.send_now.store(nullptr, mo_rlx);
     316            4 :             sent_buffer->size = 0;
     317            4 :             writing.sending.store(send_next, mo_seq);
     318              :             // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ rel <<<active_writebuffer>>>
     319            4 :             writing.active_writebuffer.store(sent_buffer, mo_seq);
     320            4 :             CRTP(Derived).tx_start_isr(send_next->buffer, send_next->size);
     321            4 :             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        94936 :     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        94936 :     send_next = writing.send_now.load(mo_seq); //                            (3)
     355        94936 :     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        94936 :         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 2.4-beta