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
|