From 6475b3f92cc9f3b296b9bd03d76cc92607a0b2be Mon Sep 17 00:00:00 2001 From: Pieter Pas Date: Thu, 8 Feb 2024 23:01:33 +0100 Subject: [PATCH] Merge Mbed OS and Mbed OS Cortex-M0 USB MIDI backends --- src/MIDI_Interfaces/USBMIDI/USBMIDI.hpp | 7 - .../USBMIDI/USBMIDI_RP2040.hpp | 18 - .../mbed-m0/PluggableUSBMIDI-descr.cpp | 102 ---- .../USBMIDI/mbed-m0/PluggableUSBMIDI.cpp | 477 ------------------ .../USBMIDI/mbed-m0/PluggableUSBMIDI.hpp | 171 ------- .../USBMIDI/mbed/PluggableUSBMIDI-descr.cpp | 2 +- .../USBMIDI/mbed/PluggableUSBMIDI.cpp | 2 +- .../USBMIDI/mbed/PluggableUSBMIDI.hpp | 22 +- src/MIDI_Interfaces/USBMIDI/util/Atomic.hpp | 226 +++++++++ 9 files changed, 239 insertions(+), 788 deletions(-) delete mode 100644 src/MIDI_Interfaces/USBMIDI/USBMIDI_RP2040.hpp delete mode 100644 src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI-descr.cpp delete mode 100644 src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.cpp delete mode 100644 src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.hpp create mode 100644 src/MIDI_Interfaces/USBMIDI/util/Atomic.hpp diff --git a/src/MIDI_Interfaces/USBMIDI/USBMIDI.hpp b/src/MIDI_Interfaces/USBMIDI/USBMIDI.hpp index 30a83ef0fe..80b7a9615f 100644 --- a/src/MIDI_Interfaces/USBMIDI/USBMIDI.hpp +++ b/src/MIDI_Interfaces/USBMIDI/USBMIDI.hpp @@ -94,13 +94,6 @@ END_CS_NAMESPACE "ESP32S3: USB MIDI not enabled. Set the Tools > USB Type setting to \"USB-OTG\" to enable it.") #endif -#elif defined(ARDUINO_ARCH_MBED_RP2040) - -#include "USBMIDI_RP2040.hpp" -BEGIN_CS_NAMESPACE -using USBDeviceMIDIBackend = RP2040_USBDeviceMIDIBackend; -END_CS_NAMESPACE - #elif defined(ARDUINO_ARCH_MBED) #include "USBMIDI_Arduino_mbed.hpp" diff --git a/src/MIDI_Interfaces/USBMIDI/USBMIDI_RP2040.hpp b/src/MIDI_Interfaces/USBMIDI/USBMIDI_RP2040.hpp deleted file mode 100644 index 03563edec3..0000000000 --- a/src/MIDI_Interfaces/USBMIDI/USBMIDI_RP2040.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -#include "mbed-m0/PluggableUSBMIDI.hpp" - -BEGIN_CS_NAMESPACE - -struct RP2040_USBDeviceMIDIBackend { - using MIDIUSBPacket_t = AH::Array; - MIDIUSBPacket_t read() { return u32_to_bytes(backend.read()); } - void write(MIDIUSBPacket_t data) { backend.write(bytes_to_u32(data)); } - void sendNow() { backend.send_now(); } - bool preferImmediateSend() { return false; } // TODO - - PluggableUSBMIDI backend; -}; - -END_CS_NAMESPACE \ No newline at end of file diff --git a/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI-descr.cpp b/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI-descr.cpp deleted file mode 100644 index 9d8bf66fa5..0000000000 --- a/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI-descr.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// Based on https://github.com/ARMmbed/mbed-os/blob/eff0d4c8b93c5a165af9f2e5e42c321a7f83eed9/drivers/usb/source/USBMIDI.cpp#L261 - -/* - * Copyright (c) 2018-2019, Arm Limited and affiliates. - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifdef ARDUINO_ARCH_MBED_RP2040 - -#include "PluggableUSBMIDI.hpp" - -#include // memcpy - -BEGIN_CS_NAMESPACE - -const uint8_t *PluggableUSBMIDI::string_iinterface_desc() { - // clang-format off - static const uint8_t string_iinterface_descriptor[] { - 0x0c, // bLength - STRING_DESCRIPTOR, // bDescriptorType 0x03 - 'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0 // bString iInterface - Audio - }; - // clang-format on - return string_iinterface_descriptor; -} - -const uint8_t *PluggableUSBMIDI::configuration_desc(uint8_t index) { - if (index != 0) { - return nullptr; - } - - const uint8_t wmaxpkt_L = (PacketSize >> 0) & 0xFF; - const uint8_t wmaxpkt_M = (PacketSize >> 8) & 0xFF; - - // clang-format off - uint8_t config_descriptor_temp[] { - // configuration descriptor - 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0xc0, 0x50, - - // The Audio Interface Collection - 0x09, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, // Standard AC Interface Descriptor - 0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x01, // Class-specific AC Interface Descriptor - 0x09, 0x04, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00, // MIDIStreaming Interface Descriptors - 0x07, 0x24, 0x01, 0x00, 0x01, 0x41, 0x00, // Class-Specific MS Interface Header Descriptor - - // MIDI IN JACKS - 0x06, 0x24, 0x02, 0x01, 0x01, 0x00, - 0x06, 0x24, 0x02, 0x02, 0x02, 0x00, - - // MIDI OUT JACKS - 0x09, 0x24, 0x03, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00, - 0x09, 0x24, 0x03, 0x02, 0x06, 0x01, 0x01, 0x01, 0x00, - - // OUT endpoint - Standard MS Bulk Data Endpoint Descriptor - 0x09, // bLength - 0x05, // bDescriptorType - bulk_out_ep, // bEndpointAddress - 0x02, // bmAttributes - wmaxpkt_L, // wMaxPacketSize (LSB) - wmaxpkt_M, // wMaxPacketSize (MSB) - 0x00, // bInterval (milliseconds) - 0x00, // bRefresh - 0x00, // bSynchAddress - - 0x05, 0x25, 0x01, 0x01, 0x01, - - // IN endpoint - Standard MS Bulk Data Endpoint Descriptor - 0x09, // bLength - 0x05, // bDescriptorType - bulk_in_ep, // bEndpointAddress - 0x02, // bmAttributes - wmaxpkt_L, // wMaxPacketSize (LSB) - wmaxpkt_M, // wMaxPacketSize (MSB) - 0x00, // bInterval (milliseconds) - 0x00, // bRefresh - 0x00, // bSynchAddress - - 0x05, 0x25, 0x01, 0x01, 0x03, - }; - // clang-format on - static_assert(sizeof(config_descriptor_temp) == sizeof(config_descriptor), - "Descriptor size error"); - memcpy(config_descriptor, config_descriptor_temp, - sizeof(config_descriptor_temp)); - return config_descriptor; -} - -END_CS_NAMESPACE - -#endif \ No newline at end of file diff --git a/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.cpp b/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.cpp deleted file mode 100644 index 6e196bfeee..0000000000 --- a/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.cpp +++ /dev/null @@ -1,477 +0,0 @@ -#if defined(ARDUINO_ARCH_MBED_RP2040) - -#include "PluggableUSBMIDI.hpp" -#include - -#include // memcpy - -BEGIN_CS_NAMESPACE - -#ifdef FATAL_ERRORS -#define CS_MIDI_USB_ASSERT(a) MBED_ASSERT((a)) -#else -#define CS_MIDI_USB_ASSERT(a) (void)(a) -#endif - -using std::tie; - -// ------------------------- CONSTRUCTOR/DESTRUCTOR ------------------------- // - -PluggableUSBMIDI::PluggableUSBMIDI() : PluggableUSBModule(2) { - PluggableUSBD().plug(this); -} - -PluggableUSBMIDI::~PluggableUSBMIDI() { PluggableUSBD().deinit(); } - -// ----------------------------- INITIALIZATION ----------------------------- // - -void PluggableUSBMIDI::init(EndpointResolver &resolver) { - bulk_in_ep = resolver.endpoint_in(USB_EP_TYPE_BULK, PacketSize); - bulk_out_ep = resolver.endpoint_out(USB_EP_TYPE_BULK, PacketSize); - CS_MIDI_USB_ASSERT(resolver.valid()); -} - -bool PluggableUSBMIDI::connected() const { - return usb_connected.load(std::memory_order_relaxed); -} - -// --------------------------------- USB API -------------------------------- // - -void PluggableUSBMIDI::callback_state_change(DeviceState new_state) { - assert_locked(); - usb_connected.store(new_state == USBDevice::Configured, - std::memory_order_relaxed); -} - -uint32_t PluggableUSBMIDI::callback_request(const setup_packet_t *setup, - USBDevice::RequestResult *result, - uint8_t **data) { - assert_locked(); - *result = USBDevice::PassThrough; - *data = nullptr; - return 0; -} - -bool PluggableUSBMIDI::callback_request_xfer_done(const setup_packet_t *setup, - bool aborted) { - assert_locked(); - return false; -} - -bool PluggableUSBMIDI::callback_set_configuration(uint8_t configuration) { - assert_locked(); - - PluggableUSBD().endpoint_add( - bulk_in_ep, PacketSize, USB_EP_TYPE_BULK, - mbed::callback(this, &PluggableUSBMIDI::in_callback)); - PluggableUSBD().endpoint_add( - bulk_out_ep, PacketSize, USB_EP_TYPE_BULK, - mbed::callback(this, &PluggableUSBMIDI::out_callback)); - - writing.send_timeout = nullptr; - writing.timeout.detach(); - writing.buffers[0].size = 0; - writing.buffers[0].ready_to_send = false; - writing.buffers[1].size = 0; - writing.buffers[1].ready_to_send = false; - writing.active_writebuffer = 0; - writing.sending = nullptr; - - reading.available = 0; - reading.read_idx = 0; - reading.write_idx = 0; - reading.reading = true; - read_start(bulk_out_ep, reading.buffers[0].buffer, PacketSize); - - return true; -} - -void PluggableUSBMIDI::callback_set_interface(uint16_t interface, - uint8_t alternate) { - assert_locked(); -} - -// ---------------------------------- WRITING ---------------------------------- - -void PluggableUSBMIDI::write(uint32_t msg) { - write_impl(&msg, 1, false); // blocking -} - -void PluggableUSBMIDI::write(const uint32_t *msgs, uint32_t num_msgs) { - const uint32_t *end = msgs + num_msgs; - while (msgs != end) - msgs += write_impl(msgs, end - msgs, false); // blocking -} - -uint32_t PluggableUSBMIDI::write_nonblock(const uint32_t *msgs, - uint32_t num_msgs) { - uint32_t total_sent = 0, sent = 1; - while (total_sent < num_msgs && sent != 0) { - sent = write_impl(msgs + total_sent, num_msgs - total_sent, true); - total_sent += sent; - } - return total_sent; -} - -void PluggableUSBMIDI::send_now() { - this->lock(); - uint32_t active_idx, size; - wbuffer_t *writebuf; - tie(active_idx, writebuf, size) = read_writebuf_size(); - if (size > 0) - send_now_impl_nonblock(active_idx); - else - this->unlock(); -} - -/* - * write(...) - * - called by user - * - adds data to the transmit buffer - * - starts a timeout to send the data (even if the buffer isn't full yet) - * - sends the transmit buffer to the USB stack when full - * - blocks if there's no space left in the transmit buffers - * - * timeout_callback() - * - called from a timer interrupt, a fixed time after the first write to a - * buffer - * - sends the transmit buffer even if it isn't full yet - * - if a previous transmission is still in progress, schedules it to be sent - * immediately after the current transmission finishes. - * - * in_callback() - * - called by the USB stack when a transmission is complete - * - finishes the transmission - * - releases the “sending” lock - * - starts a new transmission if it was scheduled while the previous - * transmission was in progress - */ - -/* - * - Both send buffers start out empty (size = 0), the first buffer is active - * (`writebuffer = 0`, which means that the first buffer is the one being - * filled with the outgoing data). - * - The `write()` function is called. - * - The message is added to the active buffer and the size is incremented. - * (Writing the data happens inside of a critical section.) - * - If the active buffer is now full, the packet is sent to the USB stack, and - * the buffers are swapped. If the previous buffer wasn't completely sent yet, - * it is scheduled to be sent when the previous transmission is complete. - * - If this was the first message in the buffer, a timeout is started. - * - If the active buffer wasn't full, nothing is actually sent, and the - * `write()` function exits. - * - When the timeout fires, `timeout_callback()` is called (from a timer - * interrupt). - * - If there is data in the active buffer, it's sent over USB. If the previous - * buffer wasn't completely sent yet, it's not possible to send now, and it's - * scheduled to be sent later. - * - When a buffer has been sent, `in_callback()` is called (from a USB - * interrupt). - * - The write is finished, the “sending” lock is released and the buffer size - * is reset to zero to let the main `write()` function know that it can write - * to this buffer. If the other buffer was scheduled to be sent while we were - * sending this packet, immediately send the next one. - */ - -std::tuple -PluggableUSBMIDI::read_writebuf_size() { - this->assert_locked(); - uint32_t active_idx = writing.active_writebuffer; - wbuffer_t *writebuffer = &writing.buffers[active_idx]; - uint32_t size = writebuffer->size; - CS_MIDI_USB_ASSERT(size != SizeReserved); - return std::make_tuple(active_idx, writebuffer, size); -} - -uint32_t PluggableUSBMIDI::write_impl(const uint32_t *msgs, uint32_t num_msgs, - bool nonblocking) { - // Index of the buffer currently being written to. - uint32_t active_idx; - // Pointer to buffer currently being written to. - wbuffer_t *writebuf; - // Current size of that buffer (i.e. the first index we can write to). - uint32_t size; - - this->lock(); - tie(active_idx, writebuf, size) = read_writebuf_size(); - - // Make sure that there's space in the buffer for us to write data - if (size >= PacketSize) { - // If there's no space - if (nonblocking) { - // either return without blocking - return this->unlock(), 0; - } else { - // Or wait until the active writing buffer changes to an empty one - auto old_idx = active_idx; - do { - this->unlock(); - yield(); // spin - this->lock(); - tie(active_idx, writebuf, size) = read_writebuf_size(); - } while (size >= PacketSize); - CS_MIDI_USB_ASSERT(old_idx != active_idx); - CS_MIDI_USB_ASSERT(size == 0); - } - } - CS_MIDI_USB_ASSERT(size < PacketSize); - - // Copy the data into the active buffer - uint32_t free_size = std::min(PacketSize - size, num_msgs * 4u); - CS_MIDI_USB_ASSERT(free_size % 4 == 0); - CS_MIDI_USB_ASSERT(size % 4 == 0); - size = (size / 4) * 4; - free_size = (free_size / 4) * 4; - memcpy(&writebuf->buffer[size], msgs, free_size); - uint32_t newsize = size + free_size; - writebuf->size = newsize; - this->unlock(); - - // If the buffer is now full, send it immediately (but don't block) - if (newsize == PacketSize) { - send_now_impl_nonblock(active_idx); - } - // If this is the first data in the buffer, start a timer that will send - // the buffer after a given timeout - else if (size == 0) { - this->lock(); - wbuffer_t *old = std::exchange(writing.send_timeout, writebuf); - this->unlock(); - CS_MIDI_USB_ASSERT(old == nullptr); - std::atomic_signal_fence(std::memory_order_release); - /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Timeout] ▼ */ - auto cb = mbed::callback(this, &PluggableUSBMIDI::timeout_callback); - writing.timeout.attach(std::move(cb), writing.timeout_duration); - } - - // How many messages were added to the transmit buffer - return free_size / 4u; -} - -/** - * @pre not locked - * @post not locked - */ -bool PluggableUSBMIDI::send_now_impl_nonblock(uint32_t active_idx) { - this->lock(); - // Disable the timeout - auto old_timeout = std::exchange(writing.send_timeout, nullptr); - - wbuffer_t *writebuffer = &writing.buffers[active_idx]; - - // Schedule the active buffer to be sent immediately after the previous one - bool old_ready = std::exchange(writebuffer->ready_to_send, true); - if (old_ready) - // If the send flag was already set, there's nothing left for us to do - return this->unlock(), true; - - // If we were the ones who set the send flag, try to send the packet now - - // Try to acquire the “sending” lock: - if (writing.sending != nullptr) - // If we failed to get the lock, the previous transmission is still in - // progress, so we can't send now, but sending of our buffer is - // scheduled using the ready_to_send flag, so we can just return. - return this->unlock(), false; - writing.sending = writebuffer; - - // Swap the active write buffer - CS_MIDI_USB_ASSERT(writing.buffers[!active_idx].size == 0); - writing.active_writebuffer = !active_idx; - - // Get the size of the buffer to send and atomically set it to reserved. - // This buffer cannot be sent because writing.sending == nullptr, so no - // previous transmission was in progress that could have started sending - // this buffer from an ISR. - // Similarly, the timeout callback couldn't have started sending it either - // because we own the writing.sending lock. - uint32_t size = std::exchange(writebuffer->size, SizeReserved); - CS_MIDI_USB_ASSERT(size != SizeReserved); - CS_MIDI_USB_ASSERT(size != 0); - - // Actually send the buffer - write_start_sync(writebuffer->buffer, size); - // “sending” lock is released in USB callback - - this->unlock(); - if (old_timeout != nullptr) - writing.timeout.detach(); - - return true; -} - -void PluggableUSBMIDI::send_in_callback(uint32_t sendbuf_idx) { - // Only called in scenarios where we already own “sending” lock - - // Get the size of the buffer to send and atomically set it to reserved - wbuffer_t *sendbuffer = &writing.buffers[sendbuf_idx]; - uint32_t size = std::exchange(sendbuffer->size, SizeReserved); - CS_MIDI_USB_ASSERT(size != SizeReserved); - CS_MIDI_USB_ASSERT(size != 0); - - // Actually send the buffer - write_start_sync(sendbuffer->buffer, size); -} - -void PluggableUSBMIDI::timeout_callback() { - this->lock(); // Note: we're in an ISR - std::atomic_signal_fence(std::memory_order_acquire); - /* ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ [Timeout] ▲ */ - - // Check which buffer (if any) to send - wbuffer_t *sendbuffer = std::exchange(writing.send_timeout, nullptr); - if (sendbuffer == nullptr) - return this->unlock(); - - // Schedule the active buffer to be sent immediately after the previous one - bool old_ready = std::exchange(sendbuffer->ready_to_send, true); - if (old_ready) - // If the send flag was already set, there's nothing left for us to do - return this->unlock(); - - // Try to get the send lock - if (writing.sending != nullptr) - // If we failed to get the lock, he previous transmission is still in - // progress, so we can't send now, but sending of our buffer is - // scheduled using the ready_to_send flag, so we can just return. - return this->unlock(); - writing.sending = sendbuffer; - - // Swap the buffers - uint32_t sendbuf_idx = sendbuffer - writing.buffers; - CS_MIDI_USB_ASSERT(writing.buffers[!sendbuf_idx].size == 0); - writing.active_writebuffer = !sendbuf_idx; - - // Reserve and send the buffer - send_in_callback(sendbuf_idx); - return this->unlock(); -} - -void PluggableUSBMIDI::write_start_sync(uint8_t *buffer, uint32_t size) { - CS_MIDI_USB_ASSERT(size <= PacketSize); - // digitalWrite(LED_BUILTIN, HIGH); - std::atomic_signal_fence(std::memory_order_release); - /* ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼ [Write-Start] ▼ */ - write_start(bulk_in_ep, buffer, size); -} - -void PluggableUSBMIDI::in_callback() { - std::atomic_signal_fence(std::memory_order_acquire); - /* ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ [Write-Start] ▲ */ - - assert_locked(); - // digitalWrite(LED_BUILTIN, LOW); - write_finish(bulk_in_ep); - - // Release the “sending” lock - wbuffer_t *sent = std::exchange(writing.sending, nullptr); - uint32_t sent_idx = sent - writing.buffers; - uint32_t next_idx = !sent_idx; - wbuffer_t *next = &writing.buffers[next_idx]; - - // Check if the other buffer was scheduled to be sent immediately after the - // one we just sent - uint32_t next_ready = next->ready_to_send; - if (next_ready) { - // Acquire the “sending” lock again - wbuffer_t *old_sending = std::exchange(writing.sending, next); - // Since interrupt handlers are atomic blocks, acquiring the lock cannot - // fail. If multiple threads are used, an extra flag is needed to - // prevent the main thread and the timeout thread from stealing the lock - // after we released it. - CS_MIDI_USB_ASSERT(old_sending == nullptr); - - // The transmission is done, reset the size of the buffer to 0 so the - // main thread can write to it again. - sent->ready_to_send = false; - uint32_t oldsize = std::exchange(sent->size, 0); - CS_MIDI_USB_ASSERT(oldsize == SizeReserved); - - // Swap the buffers - writing.active_writebuffer = !next_idx; - - // Send it - send_in_callback(next_idx); - } else { - // The transmission is done, reset the size of the buffer to 0 so the - // main thread can write to it again. - sent->ready_to_send = false; - uint32_t oldsize = std::exchange(sent->size, 0); - CS_MIDI_USB_ASSERT(oldsize == SizeReserved); - } -} - -// ---------------------------------- READING ---------------------------------- - -uint32_t PluggableUSBMIDI::read() { - this->lock(); - // Check if there are any bytes available for reading - uint32_t available = reading.available; - if (available == 0) - return this->unlock(), 0; - - // Get the buffer with received data - uint32_t r = reading.read_idx; - rbuffer_t &readbuffer = reading.buffers[r]; - - // Read the data from the buffer (data is at least as new as available) - uint32_t data; - memcpy(&data, &readbuffer.buffer[readbuffer.index], 4); - - readbuffer.index += 4; - // If we've read all messages from this buffer - if (readbuffer.index == readbuffer.size) { - // Increment the read index (and wrap around) - r = (r + 1 == NumRxPackets) ? 0 : r + 1; - reading.read_idx = r; - reading.available -= 1; - // There is now space in the queue - // Check if the next read is already in progress - bool inprogress = std::exchange(reading.reading, true); - if (!inprogress) { - // If not, start the next read now - uint32_t w = reading.write_idx; - read_start(bulk_out_ep, reading.buffers[w].buffer, PacketSize); - } - } - return this->unlock(), data; -} - -void PluggableUSBMIDI::out_callback() { - assert_locked(); - CS_MIDI_USB_ASSERT(reading.reading == true); - // Check how many bytes were read - uint32_t num_bytes_read = read_finish(bulk_out_ep); - CS_MIDI_USB_ASSERT(num_bytes_read % 4 == 0); - - // If no bytes were read, start a new read into the same buffer - uint32_t w = reading.write_idx; - if (num_bytes_read == 0) { - read_start(bulk_out_ep, reading.buffers[w].buffer, PacketSize); - return; - } - - // Otherwise, store how many bytes were read - rbuffer_t &writebuffer = reading.buffers[w]; - writebuffer.index = 0; - writebuffer.size = num_bytes_read; - // Increment the write index (and wrap around) - w = (w + 1 == NumRxPackets) ? 0 : w + 1; - reading.write_idx = w; - - // Update number of available buffers in the queue - uint32_t available = (reading.available += 1); - // If there's still space left in the queue, start the next read - if (available < NumRxPackets) - read_start(bulk_out_ep, reading.buffers[w].buffer, PacketSize); - // Otherwise, release the “reading” lock - else - reading.reading = false; -} - -constexpr uint32_t PluggableUSBMIDI::PacketSize; -constexpr uint32_t PluggableUSBMIDI::SizeReserved; - -END_CS_NAMESPACE - -#endif \ No newline at end of file diff --git a/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.hpp b/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.hpp deleted file mode 100644 index 08a91f5ab6..0000000000 --- a/src/MIDI_Interfaces/USBMIDI/mbed-m0/PluggableUSBMIDI.hpp +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include - -BEGIN_CS_NAMESPACE - -class PluggableUSBMIDI : protected arduino::internal::PluggableUSBModule { - public: - PluggableUSBMIDI(); - ~PluggableUSBMIDI(); - - public: - using setup_packet_t = USBDevice::setup_packet_t; - using DeviceState = USBDevice::DeviceState; - using microseconds = std::chrono::microseconds; - - public: - /// Check if this class is connected and ready. - bool connected() const; - - /// Send a MIDI USB message. May block. - /// - /// @param msg - /// The 4-byte MIDI USB message to send. - void write(uint32_t msg); - - /// Send multiple MIDI USB messages. May block. - /// - /// @param msgs - /// An array of 4-byte MIDI USB messages to send. - /// @param num_msgs - /// The number of messages in the array. - void write(const uint32_t *msgs, uint32_t num_msgs); - - /// Send multiple MIDI USB messages. May block. - template - void write(const uint32_t (&msgs)[N]) { - write(msgs, N); - } - - /// Send multiple MIDI USB messages without blocking. - /// - /// @param msgs - /// An array of 4-byte MIDI USB messages to send. - /// @param num_msgs - /// The number of messages in the array. - /// @return The number of messages that were actually sent. - uint32_t write_nonblock(const uint32_t *msgs, uint32_t num_msgs); - - /// Try reading a 4-byte MIDI USB message. - /// - /// @return The message or 0x00000000 if no messages available. - uint32_t read(); - - /// Try sending the buffered data now. - /// Start transmitting the latest packet if possible, even if it isn't full - /// yet. If the latest packet is empty, this function has no effect. - void send_now(); - - /// Set the timeout, the number of microseconds to buffer the outgoing MIDI - /// messages. A shorter timeout usually results in lower latency, but also - /// causes more overhead, because more packets might be required. - void setTimeout(microseconds timeout) { - writing.timeout_duration = timeout; - } - /// @todo Actually implement this timeout - void setErrorTimeout(microseconds timeout) { - writing.error_timeout_duration = timeout; - } - - /// Count how many USB packets were dropped. - uint32_t getWriteError() const { return writing.errors; } - /// Clear the counter of how many USB packets were dropped. - void clearWriteError() { writing.errors = 0; } - - protected: - void init(EndpointResolver &resolver) override; - void callback_state_change(DeviceState new_state) override; - 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; - bool callback_set_configuration(uint8_t configuration) override; - void callback_set_interface(uint16_t interface, uint8_t alternate) override; - - const uint8_t *string_iinterface_desc() override; - const uint8_t *configuration_desc(uint8_t index) override; - uint8_t getProductVersion() override { return 16; } - - protected: - std::atomic usb_connected {false}; - - public: - /// USB packet size. Must be a power of two. - /// @todo Why does increasing the packet size beyond 64 not work? - static constexpr uint32_t PacketSize = 64; - - protected: - static constexpr uint32_t SizeReserved = PacketSize + 1; - static constexpr uint32_t NumRxPackets = 2; - - /// State for reading incoming USB-MIDI data. - struct Reading { - struct Buffer { - uint32_t size = 0; - uint32_t index = 0; - alignas(uint32_t) uint8_t buffer[PacketSize]; - } buffers[NumRxPackets]; - uint32_t available {0}; - uint32_t read_idx {0}; - uint32_t write_idx {0}; - bool reading {false}; - } reading; - using rbuffer_t = Reading::Buffer; - - /// State for writing outgoing USB-MIDI data. - struct Writing { - struct Buffer { - /// How many bytes are in the buffer. - uint32_t size {0}; - /// Indicates that this buffer can be sent as soon as the previous - /// one has been sent. - /// This flag is changed from false to true when the buffer is - /// scheduled to be sent, either because it is full or because the - /// timeout was triggered. If the flag is true, no further efforts - /// should be made to send it. It is cleared in the USB ISR, after - /// the buffer has actually been sent. - bool ready_to_send {false}; - alignas(uint32_t) uint8_t buffer[PacketSize]; - } buffers[2]; - /// The index of the buffer that is currently being written to. - uint32_t active_writebuffer {0}; - /// Buffer that is being sent. - Buffer *sending {nullptr}; - /// Buffer to be sent in the timeout callback - Buffer *send_timeout {nullptr}; - microseconds timeout_duration {1'000}; - microseconds error_timeout_duration {40'000}; - mbed::Timeout timeout; - uint32_t errors {0}; - } writing; - using wbuffer_t = Writing::Buffer; - - usb_ep_t bulk_in_ep; - usb_ep_t bulk_out_ep; - uint8_t config_descriptor[0x65]; - - uint32_t write_impl(const uint32_t *msgs, uint32_t num_msgs, - bool nonblocking); - std::tuple read_writebuf_size(); - void write_start_sync(uint8_t *buffer, uint32_t size); - void send_in_callback(uint32_t activebuf_idx); - bool send_now_impl_nonblock(uint32_t activebuf_idx); - void timeout_callback(); - void in_callback(); - void out_callback(); -}; - -END_CS_NAMESPACE diff --git a/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI-descr.cpp b/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI-descr.cpp index 15def8ee56..828b455cb2 100644 --- a/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI-descr.cpp +++ b/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI-descr.cpp @@ -17,7 +17,7 @@ * limitations under the License. */ -#if defined(ARDUINO_ARCH_MBED) && !defined(ARDUINO_ARCH_MBED_RP2040) +#if defined(ARDUINO_ARCH_MBED) #include "PluggableUSBMIDI.hpp" diff --git a/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.cpp b/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.cpp index 5991676868..13e530b762 100644 --- a/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.cpp +++ b/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.cpp @@ -1,4 +1,4 @@ -#if defined(ARDUINO_ARCH_MBED) && !defined(ARDUINO_ARCH_MBED_RP2040) +#if defined(ARDUINO_ARCH_MBED) #include "PluggableUSBMIDI.hpp" #include diff --git a/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.hpp b/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.hpp index 9c82fa8490..c6d06d17b9 100644 --- a/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.hpp +++ b/src/MIDI_Interfaces/USBMIDI/mbed/PluggableUSBMIDI.hpp @@ -2,7 +2,6 @@ #include -#include #include #include @@ -11,6 +10,7 @@ #include #include +#include #include BEGIN_CS_NAMESPACE @@ -100,7 +100,7 @@ class PluggableUSBMIDI : protected arduino::internal::PluggableUSBModule { uint8_t getProductVersion() override { return 16; } protected: - std::atomic usb_connected {false}; + interrupt_atomic usb_connected {false}; public: /// USB packet size. Must be a power of two. @@ -117,23 +117,23 @@ class PluggableUSBMIDI : protected arduino::internal::PluggableUSBModule { uint32_t index = 0; alignas(uint32_t) uint8_t buffer[PacketSize]; } buffers[NumRxPackets]; - std::atomic available {0}; - std::atomic read_idx {0}; - std::atomic write_idx {0}; - std::atomic reading {false}; + interrupt_atomic available {0}; + interrupt_atomic read_idx {0}; + interrupt_atomic write_idx {0}; + interrupt_atomic reading {false}; } reading; using rbuffer_t = std::remove_reference_t; /// State for writing outgoing USB-MIDI data. struct Writing { struct Buffer { - std::atomic size {0}; + interrupt_atomic size {0}; alignas(uint32_t) uint8_t buffer[PacketSize]; } buffers[2]; - std::atomic active_writebuffer {&buffers[0]}; - std::atomic sending {nullptr}; - std::atomic send_later {nullptr}; - std::atomic send_now {nullptr}; + interrupt_atomic active_writebuffer {&buffers[0]}; + interrupt_atomic sending {nullptr}; + interrupt_atomic send_later {nullptr}; + interrupt_atomic send_now {nullptr}; microseconds timeout_duration {1'000}; microseconds error_timeout_duration {40'000}; mbed::Timeout timeout; diff --git a/src/MIDI_Interfaces/USBMIDI/util/Atomic.hpp b/src/MIDI_Interfaces/USBMIDI/util/Atomic.hpp new file mode 100644 index 0000000000..34162529f7 --- /dev/null +++ b/src/MIDI_Interfaces/USBMIDI/util/Atomic.hpp @@ -0,0 +1,226 @@ +#pragma once + +#include + +#include + +#if defined(ARDUINO_ARCH_RP2040) +#include +#elif defined(ARDUINO_ARCH_MBED) +#include +#endif + +BEGIN_CS_NAMESPACE + +#if defined(ARDUINO_ARCH_RP2040) + +class ScopedInterruptDisabler { + public: + ScopedInterruptDisabler() : state {save_and_disable_interrupts()} {} + ~ScopedInterruptDisabler() { restore_interrupts(state); } + ScopedInterruptDisabler(const ScopedInterruptDisabler &) = delete; + ScopedInterruptDisabler & + operator=(const ScopedInterruptDisabler &) = delete; + ScopedInterruptDisabler(ScopedInterruptDisabler &&) = delete; + ScopedInterruptDisabler &operator=(ScopedInterruptDisabler &&) = delete; + + private: + uint32_t state; +}; + +#elif defined(ARDUINO_ARCH_MBED) + +/// Temporarily disables interrupts and then restores them. +class ScopedInterruptDisabler { + public: + ScopedInterruptDisabler() { core_util_critical_section_enter(); } + ~ScopedInterruptDisabler() { core_util_critical_section_exit(); } + ScopedInterruptDisabler(const ScopedInterruptDisabler &) = delete; + ScopedInterruptDisabler & + operator=(const ScopedInterruptDisabler &) = delete; + ScopedInterruptDisabler(ScopedInterruptDisabler &&) = delete; + ScopedInterruptDisabler &operator=(ScopedInterruptDisabler &&) = delete; +}; + +#else +#error "Unknown platform, I don't know how to disable and restore interrupts" +#endif + +// Some boards with a Cortex-M3, Cortex-M4 or Cortex-M7 have exclusive LD/STREX +// instructions, so they can implement read-modify-write operations without +// disabling interrupts, which turns out to be faster. + +#if defined(DARDUINO_ARCH_NRF52840) || defined(DARDUINO_ARCH_MBED_GIGA) +#define CS_USE_ATOMIC_RMW 1 +#else +#define CS_USE_ATOMIC_RMW 0 +#endif + +// #define CS_USE_REAL_ATOMIC +#ifdef CS_USE_REAL_ATOMIC +template +using interrupt_atomic = std::atomic; +#else + +/// Wrapper that provides atomic access to variables shared between the main +/// program and interrupt handlers, by inserting the appropriate compile-time +/// fences. On chips that don't implement any atomic instructions in hardware, +/// like the Cortex-M0, interrupts may be disabled to ensure atomicity of read- +/// modify-write operations. +/// Interface derived from `std::atomic`. +template +class interrupt_atomic { + public: + interrupt_atomic() noexcept = default; + explicit interrupt_atomic(T t) noexcept : value {t} {} + + [[gnu::always_inline]] static void + after_load_fence(std::memory_order o) noexcept { + switch (o) { + case std::memory_order_consume: // fallthrough + case std::memory_order_acq_rel: // fallthrough + case std::memory_order_acquire: + std::atomic_signal_fence(std::memory_order_acquire); + break; + case std::memory_order_seq_cst: + std::atomic_signal_fence(std::memory_order_seq_cst); + break; + case std::memory_order_relaxed: // fallthrough + case std::memory_order_release: + // no fence needed + break; + default:; + } + } + + [[gnu::always_inline]] static void + before_store_fence(std::memory_order o) noexcept { + switch (o) { + case std::memory_order_consume: // fallthrough + case std::memory_order_acq_rel: // fallthrough + case std::memory_order_acquire: + std::atomic_signal_fence(std::memory_order_acquire); + break; + case std::memory_order_seq_cst: + std::atomic_signal_fence(std::memory_order_seq_cst); + break; + case std::memory_order_relaxed: // fallthrough + case std::memory_order_release: + // no fence needed + break; + default:; + } + } + + [[gnu::always_inline]] T load(std::memory_order o) const { + if (o == std::memory_order_seq_cst) + std::atomic_signal_fence(std::memory_order_seq_cst); + auto t = value.load(std::memory_order_relaxed); + after_load_fence(o); + return t; + } + + [[gnu::always_inline]] void store(T t, std::memory_order o) noexcept { + before_store_fence(o); + value.store(t, std::memory_order_relaxed); + if (o == std::memory_order_seq_cst) + std::atomic_signal_fence(std::memory_order_seq_cst); + } + +#if CS_USE_ATOMIC_RMW + [[gnu::always_inline]] T exchange(T arg, std::memory_order o) { + before_store_fence(o); + auto t = value.exchange(arg, std::memory_order_relaxed); + after_load_fence(o); + return t; + } + + [[gnu::always_inline]] bool + compare_exchange_strong(T &expected, T desired, + std::memory_order o) noexcept { + before_store_fence(o); + bool success = value.compare_exchange_strong(expected, desired, + std::memory_order_relaxed); + after_load_fence(o); + return success; + } + + [[gnu::always_inline]] bool + compare_exchange_weak(T &expected, T desired, + std::memory_order o) noexcept { + before_store_fence(o); + bool success = value.compare_exchange_weak(expected, desired, + std::memory_order_relaxed); + after_load_fence(o); + return success; + } + + [[gnu::always_inline]] T fetch_add(T arg, std::memory_order o) { + before_store_fence(o); + auto t = value.fetch_add(arg, std::memory_order_relaxed); + after_load_fence(o); + return t; + } + + [[gnu::always_inline]] T fetch_sub(T arg, std::memory_order o) { + before_store_fence(o); + auto t = value.fetch_sub(arg, std::memory_order_relaxed); + after_load_fence(o); + return t; + } +#else + [[gnu::always_inline]] T exchange(T arg, std::memory_order o) { + ScopedInterruptDisabler disable_interrupts; + before_store_fence(o); + auto t = value.load(std::memory_order_relaxed); + value.store(arg, std::memory_order_relaxed); + after_load_fence(o); + return t; + } + + [[gnu::always_inline]] bool + compare_exchange_strong(T &expected, T desired, + std::memory_order o) noexcept { + ScopedInterruptDisabler disable_interrupts; + if (o == std::memory_order_seq_cst) + std::atomic_signal_fence(std::memory_order_seq_cst); + auto t = value.load(std::memory_order_relaxed); + bool success = t == expected; + if (success) { + before_store_fence(o); + value.store(desired, std::memory_order_relaxed); + } else { + expected = t; + } + after_load_fence(o); + return success; + } + + [[gnu::always_inline]] bool + compare_exchange_weak(T &expected, T desired, + std::memory_order o) noexcept { + return compare_exchange_strong(expected, desired, o); + } + + [[gnu::always_inline]] T fetch_add(T arg, std::memory_order o) { + ScopedInterruptDisabler disable_interrupts; + auto t = load(o); + store(t + arg, o); + return t; + } + + [[gnu::always_inline]] T fetch_sub(T arg, std::memory_order o) { + ScopedInterruptDisabler disable_interrupts; + auto t = load(o); + store(t - arg, o); + return t; + } +#endif + + private: + std::atomic value; +}; + +#endif + +END_CS_NAMESPACE