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
|