sonata: split the USB's packet_receive function

The function's functionality is handled by two more granular functions:
1. `packet_take` which removes the packet from the receive fifo
2. `packet_data_get` which transfers the packets data from it's buffer
   fifo.
diff --git a/sdk/include/platform/sunburst/platform-usbdev.hh b/sdk/include/platform/sunburst/platform-usbdev.hh
index c0792bf..1a279bc 100644
--- a/sdk/include/platform/sunburst/platform-usbdev.hh
+++ b/sdk/include/platform/sunburst/platform-usbdev.hh
@@ -3,6 +3,7 @@
 
 #pragma once
 #include <cdefs.h>
+#include <optional>
 #include <stdint.h>
 #include <utils.hh>
 
@@ -508,44 +509,66 @@
 		configIn[endpointId]        = configIn[endpointId] | ReadyBit;
 	}
 
+	/// The information associated with a received packet
+	struct ReceiveBufferInfo
+	{
+		uint32_t info;
+		/// The endpoint ID the received packet was received on
+		constexpr uint8_t endpoint_id()
+		{
+			return (info & uint32_t(ReceiveBufferField::EndpointId)) >> 20;
+		}
+		/// The size of the received packet
+		constexpr uint16_t size()
+		{
+			return (info & uint32_t(ReceiveBufferField::Size)) >> 8;
+		}
+		/// Whether the received packet was a setup packet
+		constexpr bool is_setup()
+		{
+			return (info & uint32_t(ReceiveBufferField::Setup)) != 0;
+		}
+		/// The buffer ID used to store the received packet
+		constexpr uint8_t buffer_id()
+		{
+			return (info & uint32_t(ReceiveBufferField::BufferId)) >> 0;
+		}
+	};
+
 	/**
-	 * Test for and collect the next received packet.
+	 * If a packet has been received, removes the packet's buffer from the
+	 * receive FIFO giving it's information and ownership to the user.
 	 *
-	 * @param bufferId An out-parameter storing the buffer ID used
-	 * @param endpointId An out-parameter storing the endpoint received on.
-	 * @param size An out-parameter storing the size of the received packet.
-	 * @param isSetup A boolean out-parameter. True means the received packet
-	 * was a SETUP packet, false means it was not.
-	 * @param destination A destination buffer to read the packet's data into.
+	 * `packet_data_get` can be used to retrieve the packet's data.
 	 *
-	 * @returns 0 if successful, or non-zero if not successful.
+	 * @returns Information about the received packet, if a packet had been
+	 * received.
 	 */
-	[[nodiscard]] int packet_receive(uint8_t  &bufferId,
-	                                 uint8_t  &endpointId,
-	                                 uint16_t &size,
-	                                 bool     &isSetup,
-	                                 uint32_t *destination) volatile
+	[[nodiscard]] std::optional<ReceiveBufferInfo> packet_take() volatile
 	{
 		if (!(usbStatus & uint32_t(UsbStatusField::ReceiveDepth)))
 		{
-			return -1; // No packets received
+			return {}; // No packets received
 		}
+		return ReceiveBufferInfo{receiveBuffer};
+	}
 
-		uint32_t received = receiveBuffer;
-
-		typedef ReceiveBufferField Reg;
-		endpointId = (received & uint32_t(Reg::EndpointId)) >> 20;
-		size       = (received & uint32_t(Reg::Size)) >> 8;
-		isSetup    = (received & uint32_t(Reg::Setup)) != 0;
-		bufferId   = (received & uint32_t(Reg::BufferId)) >> 0;
-
+	/**
+	 * Retrieves the data from a buffer containing a received packet.
+	 *
+	 * @param destination A destination buffer to read the packet's data into.
+	 */
+	void packet_data_get(ReceiveBufferInfo bufferInfo,
+	                     uint32_t         *destination) volatile
+	{
+		const auto [id, size] =
+		  std::pair{bufferInfo.buffer_id(), bufferInfo.size()};
 		// Reception of Zero Length Packets occurs in the Status Stage of IN
 		// Control Transfers.
 		if (size > 0)
 		{
-			usbdev_transfer(destination, buffer(bufferId), size, false);
+			usbdev_transfer(destination, buffer(id), size, false);
 		}
-		return 0;
 	}
 
 	private: