Control Surface pin-t-adl
MIDI Control Surface library for Arduino
mbed/PluggableUSBMIDI.cpp
Go to the documentation of this file.
1#if defined(ARDUINO_ARCH_MBED) && !defined(ARDUINO_ARCH_MBED_RP2040)
2
3#include "PluggableUSBMIDI.hpp"
4#include <AH/Debug/Debug.hpp>
5
6#include <cstring> // memcpy
7
9
10#ifdef FATAL_ERRORS
11#define CS_MIDI_USB_ASSERT(a) MBED_ASSERT((a))
12#else
13#define CS_MIDI_USB_ASSERT(a) (void)(a)
14#endif
15
16constexpr static auto mo_rel = std::memory_order_release;
17constexpr static auto mo_acq = std::memory_order_acquire;
18constexpr static auto mo_rlx = std::memory_order_relaxed;
19constexpr static auto mo_acq_rel = std::memory_order_acq_rel;
20
21using std::tie;
22
23// ------------------------- CONSTRUCTOR/DESTRUCTOR ------------------------- //
24
25PluggableUSBMIDI::PluggableUSBMIDI() : PluggableUSBModule(2) {
26 PluggableUSBD().plug(this);
27}
28
29PluggableUSBMIDI::~PluggableUSBMIDI() { PluggableUSBD().deinit(); }
30
31// ----------------------------- INITIALIZATION ----------------------------- //
32
33void PluggableUSBMIDI::init(EndpointResolver &resolver) {
34 bulk_in_ep = resolver.endpoint_in(USB_EP_TYPE_BULK, PacketSize);
35 bulk_out_ep = resolver.endpoint_out(USB_EP_TYPE_BULK, PacketSize);
36 CS_MIDI_USB_ASSERT(resolver.valid());
37}
38
39bool PluggableUSBMIDI::connected() const { return usb_connected.load(mo_rlx); }
40
41// --------------------------------- USB API -------------------------------- //
42
43void PluggableUSBMIDI::callback_state_change(DeviceState new_state) {
44 assert_locked();
45 usb_connected.store(new_state == USBDevice::Configured, mo_rlx);
46}
47
48uint32_t PluggableUSBMIDI::callback_request(const setup_packet_t *setup,
49 USBDevice::RequestResult *result,
50 uint8_t **data) {
51 assert_locked();
52 *result = USBDevice::PassThrough;
53 *data = nullptr;
54 return 0;
55}
56
57bool PluggableUSBMIDI::callback_request_xfer_done(const setup_packet_t *setup,
58 bool aborted) {
59 assert_locked();
60 return false;
61}
62
63bool PluggableUSBMIDI::callback_set_configuration(uint8_t configuration) {
64 assert_locked();
65
66 PluggableUSBD().endpoint_add(
67 bulk_in_ep, PacketSize, USB_EP_TYPE_BULK,
68 mbed::callback(this, &PluggableUSBMIDI::in_callback));
69 PluggableUSBD().endpoint_add(
70 bulk_out_ep, PacketSize, USB_EP_TYPE_BULK,
71 mbed::callback(this, &PluggableUSBMIDI::out_callback));
72
73 writing.send_timeout.store(nullptr, mo_rlx);
74 writing.timeout.detach();
75 writing.buffers[0].size.store(0, mo_rlx);
76 writing.buffers[0].ready_to_send.store(false, mo_rlx);
77 writing.buffers[1].size.store(0, mo_rlx);
78 writing.buffers[1].ready_to_send.store(false, mo_rlx);
80 writing.sending.store(nullptr, mo_rlx);
81
82 reading.available.store(0, mo_rlx);
83 reading.read_idx.store(0, mo_rlx);
84 reading.write_idx.store(0, mo_rlx);
85 reading.reading.store(true, mo_rlx);
87
88 return true;
89}
90
91void PluggableUSBMIDI::callback_set_interface(uint16_t interface,
92 uint8_t alternate) {
93 assert_locked();
94}
95
96// ---------------------------------- WRITING ----------------------------------
97
98void PluggableUSBMIDI::write(uint32_t msg) {
99 write_impl(&msg, 1, false); // blocking
100}
101
102void PluggableUSBMIDI::write(const uint32_t *msgs, uint32_t num_msgs) {
103 const uint32_t *end = msgs + num_msgs;
104 while (msgs != end)
105 msgs += write_impl(msgs, end - msgs, false); // blocking
106}
107
108uint32_t PluggableUSBMIDI::write_nonblock(const uint32_t *msgs,
109 uint32_t num_msgs) {
110 uint32_t total_sent = 0, sent = 1;
111 while (total_sent < num_msgs && sent != 0) {
112 sent = write_impl(msgs + total_sent, num_msgs - total_sent, true);
113 total_sent += sent;
114 }
115 return total_sent;
116}
117
119 uint32_t active_idx, size;
120 wbuffer_t *writebuf;
121 tie(active_idx, writebuf, size) = read_writebuf_size();
122 if (size > 0)
123 send_now_impl_nonblock(active_idx);
124}
125
126/*
127 * write(...)
128 * - called by user
129 * - adds data to the transmit buffer
130 * - starts a timeout to send the data (even if the buffer isn't full yet)
131 * - sends the transmit buffer to the USB stack when full
132 * - blocks if there's no space left in the transmit buffers
133 *
134 * timeout_callback()
135 * - called from a timer interrupt, a fixed time after the first write to a
136 * buffer
137 * - sends the transmit buffer even if it isn't full yet
138 * - if a previous transmission is still in progress, schedules it to be sent
139 * immediately after the current transmission finishes.
140 *
141 * in_callback()
142 * - called by the USB stack when a transmission is complete
143 * - finishes the transmission
144 * - releases the “sending” lock
145 * - starts a new transmission if it was scheduled while the previous
146 * transmission was in progress
147 */
148
149/*
150 * - Both send buffers start out empty (size = 0), the first buffer is active
151 * (`writebuffer = 0`, which means that the first buffer is the one being
152 * filled with the outgoing data).
153 * - The `write()` function is called.
154 * - The message is added to the active buffer and the size is incremented.
155 * (Writing the data happens inside of an atomic compare-and-swap loop, if in
156 * the meantime an interrupts occurs that sends the buffer we just wrote to,
157 * we write the data again to the new writing buffer.)
158 * - If the active buffer is now full, the packet is sent to the USB stack, and
159 * the buffers are swapped. If the previous buffer wasn't completely sent yet,
160 * it is scheduled to be sent when the previous transmission is complete.
161 * - If this was the first message in the buffer, a timeout is started.
162 * - If the active buffer wasn't full, nothing is actually sent, and the
163 * `write()` function exits.
164 * - When the timeout fires, `timeout_callback()` is called (from a timer
165 * interrupt).
166 * - If there is data in the active buffer, it's sent over USB. If the previous
167 * buffer wasn't completely sent yet, it's not possible to send now, and it's
168 * scheduled to be sent later.
169 * - When a buffer has been sent, `in_callback()` is called (from a USB
170 * interrupt).
171 * - The write is finished, the “sending” lock is released and the buffer size
172 * is reset to zero to let the main `write()` function know that it can write
173 * to this buffer. If the other buffer was scheduled to be sent while we were
174 * sending this packet, immediately send the next one.
175 */
176
177std::tuple<uint32_t, PluggableUSBMIDI::wbuffer_t *, uint32_t>
179 uint32_t active_idx;
180 wbuffer_t *writebuffer;
181 uint32_t size;
182 // active_idx must be newer than active size.
183 active_idx = writing.active_writebuffer.load(mo_rlx);
184 writebuffer = &writing.buffers[active_idx];
185 size = writebuffer->size.load(mo_acq);
186 // If the size was set to reserved, the active buffer must have been
187 // swapped as well. If both buffers are empty, the active buffer might have
188 // been swapped as well (but not necessarily).
189 if (size == SizeReserved || size == 0) {
190 uint32_t old_idx = active_idx;
191 active_idx = writing.active_writebuffer.load(mo_rlx);
192 if (old_idx != active_idx) {
193 writebuffer = &writing.buffers[active_idx];
194 size = writebuffer->size.load(mo_acq);
195 }
196 }
198 return std::make_tuple(active_idx, writebuffer, size);
199}
200
201uint32_t PluggableUSBMIDI::write_impl(const uint32_t *msgs, uint32_t num_msgs,
202 bool nonblocking) {
203 uint32_t active_idx; // Index of the buffer currently being written to.
204 wbuffer_t *writebuf; // Pointer to buffer currently being written to.
205 uint32_t size; // Current size of that buffer (i.e. the first index we
206 // can write to).
207
208 // Read the active write buffer and its current size
209 tie(active_idx, writebuf, size) = read_writebuf_size();
210 // Make sure that there's space in the buffer for us to write data
211 if (size >= PacketSize) {
212 // If there's no space
213 if (nonblocking) {
214 // either return without blocking
215 return 0;
216 } else {
217 // Or wait until the active writing buffer changes to an empty one
218 auto old_idx = active_idx;
219 do {
220 yield(); // spin
221 tie(active_idx, writebuf, size) = read_writebuf_size();
222 } while (size >= PacketSize);
223 CS_MIDI_USB_ASSERT(old_idx != active_idx);
224 CS_MIDI_USB_ASSERT(size == 0);
225 }
226 }
228
229 // Copy the data into the active buffer
230 uint32_t free_size = std::min(PacketSize - size, num_msgs * 4u);
231 CS_MIDI_USB_ASSERT(free_size % 4 == 0);
232 memcpy(&writebuf->buffer[size], msgs, free_size);
233 uint32_t newsize = size + free_size;
234
235 // Update the size of the buffer
236 while (!writebuf->size.compare_exchange_weak(size, newsize, mo_rel)) {
237 // If the size changed while we were writing the data, this means
238 // that a sender in a different thread or ISR was/is sending the active
239 // buffer (so it is no longer the active buffer).
240
241 // Read the new write buffer and its size.
242 tie(active_idx, writebuf, size) = read_writebuf_size();
243 // CS_MIDI_USB_ASSERT(size == 0); // Only true with cmp_xchg_strong
244
245 // Copy the data into the new buffer
246 free_size = std::min(PacketSize - size, num_msgs * 4u);
247 memcpy(&writebuf->buffer[size], msgs, free_size);
248 newsize = size + free_size;
249 }
250
251 // If the buffer is now full, send it immediately (but don't block)
252 if (newsize == PacketSize) {
253 send_now_impl_nonblock(active_idx);
254 }
255 // If this is the first data in the buffer, start a timer that will send
256 // the buffer after a given timeout
257 else if (size == 0) {
258 wbuffer_t *old = writing.send_timeout.exchange(writebuf, mo_rel);
259 CS_MIDI_USB_ASSERT(old == nullptr);
260 std::atomic_signal_fence(mo_rel);
261 /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Timeout] ▼ */
262 auto cb = mbed::callback(this, &PluggableUSBMIDI::timeout_callback);
263 writing.timeout.attach(std::move(cb), writing.timeout_duration);
264 }
265
266 // How many messages were added to the transmit buffer
267 return free_size / 4u;
268}
269
270bool PluggableUSBMIDI::send_now_impl_nonblock(uint32_t active_idx) {
271 // Disable the timeout
272 auto old_timeout = writing.send_timeout.exchange(nullptr, mo_acq);
273 if (old_timeout != nullptr)
274 writing.timeout.detach();
275
276 wbuffer_t *writebuffer = &writing.buffers[active_idx];
277
278 // Schedule the active buffer to be sent immediately after the previous one
279 bool old_ready = writebuffer->ready_to_send.exchange(true, mo_rlx);
280 if (old_ready)
281 // If the send flag was already set, there's nothing left for us to do
282 return true;
283
284 // If we were the ones who set the send flag, try to send the packet now
285
286 // Try to acquire the “sending” lock:
287 wbuffer_t *send = nullptr;
288 if (!writing.sending.compare_exchange_strong(send, writebuffer, mo_acq))
289 // If we failed to get the lock, he previous transmission is still in
290 // progress, so we can't send now, but sending of our buffer is
291 // scheduled using the send flag, so we can just return.
292 return false;
293
294 // Swap the active write buffer
295 CS_MIDI_USB_ASSERT(writing.buffers[!active_idx].size.load(mo_rlx) == 0);
296 writing.active_writebuffer.store(!active_idx, mo_rlx);
297
298 // Get the size of the buffer to send and atomically set it to reserved
299 uint32_t size = writebuffer->size.exchange(SizeReserved, mo_acq_rel);
301 CS_MIDI_USB_ASSERT(size != 0);
302
303 // Actually send the buffer
304 write_start_sync(writebuffer->buffer, size);
305 // “sending” lock is released in USB callback
306 return true;
307}
308
309void PluggableUSBMIDI::send_in_callback(uint32_t sendbuf_idx) {
310 // Only called in scenarios where we already own “sending” lock
311
312 // Get the size of the buffer to send and atomically set it to reserved
313 wbuffer_t *sendbuffer = &writing.buffers[sendbuf_idx];
314 uint32_t size = sendbuffer->size.exchange(SizeReserved, mo_acq_rel);
316 CS_MIDI_USB_ASSERT(size != 0);
317
318 // Actually send the buffer
319 write_start_sync(sendbuffer->buffer, size);
320}
321
323 std::atomic_signal_fence(mo_acq);
324 /* ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ [Timeout] ▲ */
325
326 // Check which buffer (if any) to send
327 wbuffer_t *sendbuffer = writing.send_timeout.exchange(nullptr, mo_acq);
328 if (!sendbuffer)
329 return;
330
331 // Schedule the active buffer to be sent immediately after the previous one
332 bool old_ready = sendbuffer->ready_to_send.exchange(true, mo_rlx);
333 if (old_ready)
334 // If the send flag was already set, there's nothing left for us to do
335 return;
336
337 // Try to get the send lock
338 wbuffer_t *expected = nullptr;
339 if (!writing.sending.compare_exchange_strong(expected, sendbuffer, mo_acq))
340 // If we failed to get the lock, he previous transmission is still in
341 // progress, so we can't send now, but sending of our buffer is
342 // scheduled using the send flag, so we can just return.
343 return;
344
345 // Swap the buffers
346 uint32_t sendbuf_idx = sendbuffer - writing.buffers;
347 CS_MIDI_USB_ASSERT(writing.buffers[!sendbuf_idx].size.load(mo_rlx) == 0);
348 writing.active_writebuffer.store(!sendbuf_idx, mo_rlx);
349
350 // Reserve and send the buffer
351 send_in_callback(sendbuf_idx);
352}
353
354void PluggableUSBMIDI::write_start_sync(uint8_t *buffer, uint32_t size) {
356 // digitalWrite(LED_BUILTIN, HIGH);
357 std::atomic_signal_fence(mo_rel);
358 /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Write-Start] ▼ */
359 write_start(bulk_in_ep, buffer, size);
360}
361
363 std::atomic_signal_fence(mo_acq);
364 /* ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ [Write-Start] ▲ */
365
366 assert_locked();
367 // digitalWrite(LED_BUILTIN, LOW);
368 write_finish(bulk_in_ep);
369
370 // Release the “sending” lock
371 wbuffer_t *sent = writing.sending.exchange(nullptr, mo_rel);
372 uint32_t sent_idx = sent - writing.buffers;
373 uint32_t next_idx = !sent_idx;
374 wbuffer_t *next = &writing.buffers[next_idx];
375
376 // Check if the other buffer was scheduled to be sent immediately after the
377 // one we just sent
378 uint32_t next_ready = next->ready_to_send.load(mo_rlx);
379 if (next_ready) {
380 // Acquire the “sending” lock again
381 wbuffer_t *old_sending = writing.sending.exchange(next, mo_rlx);
382 // Since interrupt handlers are atomic blocks, acquiring the lock cannot
383 // fail. If multiple threads are used, an extra flag is needed to
384 // prevent the main thread and the timeout thread from stealing the lock
385 // after we released it.
386 CS_MIDI_USB_ASSERT(old_sending == nullptr);
387
388 // The transmission is done, reset the size of the buffer to 0 so the
389 // main thread can write to it again.
390 sent->ready_to_send.store(false, mo_rlx);
391 uint32_t oldsize = sent->size.exchange(0, mo_rel);
393
394 // Swap the buffers
395 writing.active_writebuffer.store(!next_idx, mo_rlx);
396
397 // Send it
398 send_in_callback(next_idx);
399 } else {
400 // The transmission is done, reset the size of the buffer to 0 so the
401 // main thread can write to it again.
402 sent->ready_to_send.store(false, mo_rlx);
403 uint32_t oldsize = sent->size.exchange(0, mo_rel);
405 }
406}
407
408// ---------------------------------- READING ----------------------------------
409
410uint32_t PluggableUSBMIDI::read() {
411 // Check if there are any bytes available for reading
412 uint32_t available = reading.available.load(mo_acq);
413 if (available == 0)
414 return 0;
415
416 // Get the buffer with received data
417 uint32_t r = reading.read_idx.load(mo_rlx);
418 rbuffer_t &readbuffer = reading.buffers[r];
419
420 // Read the data from the buffer (data is at least as new as available)
421 uint32_t data;
422 memcpy(&data, &readbuffer.buffer[readbuffer.index], 4);
423
424 readbuffer.index += 4;
425 // If we've read all messages from this buffer
426 if (readbuffer.index == readbuffer.size) {
427 // Increment the read index (and wrap around)
428 r = (r + 1 == NumRxPackets) ? 0 : r + 1;
429 reading.read_idx.store(r, mo_rlx);
430 reading.available.fetch_sub(1, mo_rel);
431 // There is now space in the queue
432 // Check if the next read is already in progress
433 if (reading.reading.exchange(true, mo_acq) == false) {
434 // If not, start the next read now
435 uint32_t w = reading.write_idx.load(mo_rlx);
437 }
438 }
439
440 return data;
441}
442
444 assert_locked();
446 // Check how many bytes were read
447 uint32_t num_bytes_read = read_finish(bulk_out_ep);
448 CS_MIDI_USB_ASSERT(num_bytes_read % 4 == 0);
449
450 // If no bytes were read, start a new read into the same buffer
451 uint32_t w = reading.write_idx.load(mo_rlx);
452 if (num_bytes_read == 0) {
454 return;
455 }
456
457 // Otherwise, store how many bytes were read
458 rbuffer_t &writebuffer = reading.buffers[w];
459 writebuffer.index = 0;
460 writebuffer.size = num_bytes_read;
461 // Increment the write index (and wrap around)
462 w = (w + 1 == NumRxPackets) ? 0 : w + 1;
463 reading.write_idx.store(w, mo_rlx);
464
465 // Update number of available buffers in the queue
466 uint32_t available = reading.available.fetch_add(1, mo_acq_rel) + 1;
467 // If there's still space left in the queue, start the next read
468 if (available < NumRxPackets)
470 // Otherwise, release the “reading” lock
471 else
472 reading.reading.store(false, mo_rel);
473}
474
475constexpr uint32_t PluggableUSBMIDI::PacketSize;
476constexpr uint32_t PluggableUSBMIDI::SizeReserved;
477
479
480#endif
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
static constexpr auto mo_rel
#define CS_MIDI_USB_ASSERT(a)
static constexpr auto mo_rlx
static constexpr auto mo_acq_rel
static constexpr auto mo_acq
static constexpr uint32_t NumRxPackets
static constexpr uint32_t PacketSize
USB packet size.
uint32_t active_writebuffer
The index of the buffer that is currently being written to.
bool ready_to_send
Indicates that this buffer can be sent as soon as the previous one has been sent.
void send_in_callback(uint32_t activebuf_idx)
static constexpr uint32_t SizeReserved
struct PluggableUSBMIDI::Reading reading
struct PluggableUSBMIDI::Writing writing
uint32_t write_nonblock(const uint32_t *msgs, uint32_t num_msgs)
Send multiple MIDI USB messages without blocking.
void timeout_callback()
uint32_t read()
Try reading a 4-byte MIDI USB message.
void callback_state_change(DeviceState new_state) override
void send_now()
Try sending the buffered data now.
void write_start_sync(uint8_t *buffer, uint32_t size)
Buffer * send_timeout
Buffer to be sent in the timeout callback.
void callback_set_interface(uint16_t interface, uint8_t alternate) override
uint32_t write_impl(const uint32_t *msgs, uint32_t num_msgs, bool nonblocking)
Buffer * sending
Buffer that is being sent.
bool callback_set_configuration(uint8_t configuration) override
bool connected() const
Check if this class is connected and ready.
void init(EndpointResolver &resolver) override
struct PluggableUSBMIDI::Reading::Buffer buffers[NumRxPackets]
std::tuple< uint32_t, wbuffer_t *, uint32_t > read_writebuf_size()
std::atomic< bool > usb_connected
uint32_t size
How many bytes are in the buffer.
bool send_now_impl_nonblock(uint32_t activebuf_idx)
uint32_t callback_request(const setup_packet_t *setup, USBDevice::RequestResult *result, uint8_t **data) override
bool callback_request_xfer_done(const setup_packet_t *setup, bool aborted) override
struct PluggableUSBMIDI::Writing::Buffer buffers[2]
void write(uint32_t msg)
Send a MIDI USB message.
constexpr auto min(const T &a, const U &b) -> decltype(b< a ? b :a)
Return the smaller of two numbers/objects.
Definition: MinMaxFix.hpp:15