sonata: ethernet driver now uses SPI block CS lines
The SPI block now has built-in chip select outputs; one no longer has to
rely on a seperate, and possibly shared, GPIO block.
Note, the lock around the CS line changes is likely superflous now, but
has been left in for the time being.
Co-authored-by: Alex Jones <alex.jones@lowrisc.org>
diff --git a/sdk/include/platform/sunburst/platform-ethernet.hh b/sdk/include/platform/sunburst/platform-ethernet.hh
new file mode 100644
index 0000000..64a4f95
--- /dev/null
+++ b/sdk/include/platform/sunburst/platform-ethernet.hh
@@ -0,0 +1,795 @@
+#pragma once
+#include <array>
+#include <cheri.hh>
+#include <cstddef>
+#include <cstdint>
+#include <debug.hh>
+#include <futex.h>
+#include <interrupt.h>
+#include <locks.hh>
+#include <optional>
+#include <platform/concepts/ethernet.hh>
+#include <platform/sunburst/platform-spi.hh>
+#include <thread.h>
+#include <type_traits>
+
+DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(EthernetInterruptCapability,
+ InterruptName::EthernetInterrupt,
+ true,
+ true);
+
+/**
+ * The driver for KSZ8851 SPI Ethernet MAC.
+ */
+class Ksz8851Ethernet
+{
+ /**
+ * Flag set when we're debugging this driver.
+ */
+ static constexpr bool DebugEthernet = false;
+
+ /**
+ * Flag set to log messages when frames are dropped.
+ */
+ static constexpr bool DebugDroppedFrames = true;
+
+ /**
+ * Maxmium size of a single Ethernet frame.
+ */
+ static constexpr uint16_t MaxFrameSize = 1500;
+
+ /**
+ * Helper for conditional debug logs and assertions.
+ */
+ using Debug = ConditionalDebug<DebugEthernet, "Ethernet driver">;
+
+ /**
+ * Helper for conditional debug logs and assertions for dropped frames.
+ */
+ using DebugFrameDrops =
+ ConditionalDebug<DebugDroppedFrames, "Ethernet driver">;
+
+ /**
+ * Import the Capability helper from the CHERI namespace.
+ */
+ template<typename T>
+ using Capability = CHERI::Capability<T>;
+
+ /**
+ * SPI commands
+ */
+ enum class SpiCommand : uint8_t
+ {
+ ReadRegister = 0b00,
+ WriteRegister = 0b01,
+ // DMA in this context means that the Ethernet MAC is DMA directly
+ // from the SPI interface into its internal buffer, so it takes single
+ // SPI transaction for the entire frame. It is unrelated to whether
+ // SPI driver uses PIO or DMA for the SPI transaction.
+ ReadDma = 0b10,
+ WriteDma = 0b11,
+ };
+
+ /**
+ * The location of registers
+ */
+ enum class RegisterOffset : uint8_t
+ {
+ ChipConfiguration = 0x08,
+ MacAddressLow = 0x10,
+ MacAdressMiddle = 0x12,
+ MacAddressHigh = 0x14,
+ OnChipBusControl = 0x20,
+ EepromControl = 0x22,
+ MemoryBistInfo = 0x24,
+ GlobalReset = 0x26,
+
+ /* Wakeup frame registers omitted */
+
+ TransmitControl = 0x70,
+ TransmitStatus = 0x72,
+ ReceiveControl1 = 0x74,
+ ReceiveControl2 = 0x76,
+ TransmitQueueMemoryInfo = 0x78,
+ ReceiveFrameHeaderStatus = 0x7C,
+ ReceiveFrameHeaderByteCount = 0x7E,
+ TransmitQueueCommand = 0x80,
+ ReceiveQueueCommand = 0x82,
+ TransmitFrameDataPointer = 0x84,
+ ReceiveFrameDataPointer = 0x86,
+ ReceiveDurationTimerThreshold = 0x8C,
+ ReceiveDataByteCountThreshold = 0x8E,
+ InterruptEnable = 0x90,
+ InterruptStatus = 0x92,
+ ReceiveFrameCountThreshold = 0x9c,
+ TransmitNextTotalFrameSize = 0x9E,
+
+ /* MAC address hash table registers omitted */
+
+ FlowControlLowWatermark = 0xB0,
+ FlowControlHighWatermark = 0xB2,
+ FlowControlOverrunWatermark = 0xB4,
+ ChipIdEnable = 0xC0,
+ ChipGlobalControl = 0xC6,
+ IndirectAccessControl = 0xC8,
+ IndirectAccessDataLow = 0xD0,
+ IndirectAccessDataHigh = 0xD2,
+ PowerManagementEventControl = 0xD4,
+ GoSleepWakeUp = 0xD4,
+ PhyReset = 0xD4,
+ Phy1MiiBasicControl = 0xE4,
+ Phy1MiiBasicStatus = 0xE6,
+ Phy1IdLow = 0xE8,
+ Phy1High = 0xEA,
+ Phy1AutoNegotiationAdvertisement = 0xEC,
+ Phy1AutoNegotiationLinkPartnerAbility = 0xEE,
+ Phy1SpecialControlStatus = 0xF4,
+ Port1Control = 0xF6,
+ Port1Status = 0xF8,
+ };
+
+ using MACAddress = std::array<uint8_t, 6>;
+
+ /**
+ * Flag bits of the TransmitControl register.
+ */
+ enum [[clang::flag_enum]] TransmitControl : uint16_t{
+ TransmitEnable = 1 << 0,
+ TransmitCrcEnable = 1 << 1,
+ TransmitPaddingEnable = 1 << 2,
+ TransmitFlowControlEnable = 1 << 3,
+ FlushTransmitQueue = 1 << 4,
+ TransmitChecksumGenerationIp = 1 << 5,
+ TransmitChecksumGenerationTcp = 1 << 6,
+ TransmitChecksumGenerationIcmp = 1 << 9,
+ };
+
+ /**
+ * Flag bits of the ReceiveControl1 register.
+ */
+ enum [[clang::flag_enum]] ReceiveControl1 : uint16_t{
+ ReceiveEnable = 1 << 0,
+ ReceiveInverseFilter = 1 << 1,
+ ReceiveAllEnable = 1 << 4,
+ ReceiveUnicastEnable = 1 << 5,
+ ReceiveMulticastEnable = 1 << 6,
+ ReceiveBroadcastEnable = 1 << 7,
+ ReceiveMulticastAddressFilteringWithMacAddressEnable = 1 << 8,
+ ReceiveErrorFrameEnable = 1 << 9,
+ ReceiveFlowControlEnable = 1 << 10,
+ ReceivePhysicalAddressFilteringWithMacAddressEnable = 1 << 11,
+ ReceiveIpFrameChecksumCheckEnable = 1 << 12,
+ ReceiveTcpFrameChecksumCheckEnable = 1 << 13,
+ ReceiveUdpFrameChecksumCheckEnable = 1 << 14,
+ FlushReceiveQueue = 1 << 15,
+ };
+
+ /**
+ * Flag bits of the ReceiveControl2 register.
+ */
+ enum [[clang::flag_enum]] ReceiveControl2 : uint16_t{
+ ReceiveSourceAddressFiltering = 1 << 0,
+ ReceiveIcmpFrameChecksumEnable = 1 << 1,
+ UdpLiteFrameEnable = 1 << 2,
+ ReceiveIpv4Ipv6UdpFrameChecksumEqualZero = 1 << 3,
+ ReceiveIpv4Ipv6FragmentFramePass = 1 << 4,
+ DataBurst4Bytes = 0b000 << 5,
+ DataBurst8Bytes = 0b001 << 5,
+ DataBurst16Bytes = 0b010 << 5,
+ DataBurst32Bytes = 0b011 << 5,
+ DataBurstSingleFrame = 0b100 << 5,
+ };
+
+ /**
+ * Flag bits of the ReceiveFrameHeaderStatus register.
+ */
+ enum [[clang::flag_enum]] ReceiveFrameHeaderStatus : uint16_t{
+ ReceiveCrcError = 1 << 0,
+ ReceiveRuntFrame = 1 << 1,
+ ReceiveFrameTooLong = 1 << 2,
+ ReceiveFrameType = 1 << 3,
+ ReceiveMiiError = 1 << 4,
+ ReceiveUnicastFrame = 1 << 5,
+ ReceiveMulticastFrame = 1 << 6,
+ ReceiveBroadcastFrame = 1 << 7,
+ ReceiveUdpFrameChecksumStatus = 1 << 10,
+ ReceiveTcpFrameChecksumStatus = 1 << 11,
+ ReceiveIpFrameChecksumStatus = 1 << 12,
+ ReceiveIcmpFrameChecksumStatus = 1 << 13,
+ ReceiveFrameValid = 1 << 15,
+ };
+
+ /**
+ * Flag bits of the ReceiveQueueCommand register.
+ */
+ enum [[clang::flag_enum]] ReceiveQueueCommand : uint16_t{
+ ReleaseReceiveErrorFrame = 1 << 0,
+ StartDmaAccess = 1 << 3,
+ AutoDequeueReceiveQueueFrameEnable = 1 << 4,
+ ReceiveFrameCountThresholdEnable = 1 << 5,
+ ReceiveDataByteCountThresholdEnable = 1 << 6,
+ ReceiveDurationTimerThresholdEnable = 1 << 7,
+ ReceiveIpHeaderTwoByteOffsetEnable = 1 << 9,
+ ReceiveFrameCountThresholdStatus = 1 << 10,
+ ReceiveDataByteCountThresholdstatus = 1 << 11,
+ ReceiveDurationTimerThresholdStatus = 1 << 12,
+ };
+
+ /**
+ * Flag bits of the TransmitQueueCommand register.
+ */
+ enum [[clang::flag_enum]] TransmitQueueCommand : uint16_t{
+ ManualEnqueueTransmitQueueFrameEnable = 1 << 0,
+ TransmitQueueMemoryAvailableMonitor = 1 << 1,
+ AutoEnqueueTransmitQueueFrameEnable = 1 << 2,
+ };
+
+ /**
+ * Flag bits of the TransmitFrameDataPointer and ReceiveFrameDataPointer
+ * register.
+ */
+ enum [[clang::flag_enum]] FrameDataPointer : uint16_t{
+ /**
+ * When this bit is set, the frame data pointer register increments
+ * automatically on accesses to the data register.
+ */
+ FrameDataPointerAutoIncrement = 1 << 14,
+ };
+
+ /**
+ * Flags bits of the InterruptStatus and InterruptEnable registers.
+ */
+ enum [[clang::flag_enum]] Interrupt : uint16_t{
+ EnergyDetectInterrupt = 1 << 2,
+ LinkupDetectInterrupt = 1 << 3,
+ ReceiveMagicPacketDetectInterrupt = 1 << 4,
+ ReceiveWakeupFrameDetectInterrupt = 1 << 5,
+ TransmitSpaceAvailableInterrupt = 1 << 6,
+ ReceiveProcessStoppedInterrupt = 1 << 7,
+ TransmitProcessStoppedInterrupt = 1 << 8,
+ ReceiveOverrunInterrupt = 1 << 11,
+ ReceiveInterrupt = 1 << 13,
+ TransmitInterrupt = 1 << 14,
+ LinkChangeInterruptStatus = 1 << 15,
+ };
+
+ /**
+ * Flags bits of the Port1Control register.
+ */
+ enum [[clang::flag_enum]] Port1Control : uint16_t{
+ Advertised10BTHalfDuplexCapability = 1 << 0,
+ Advertised10BTFullDuplexCapability = 1 << 1,
+ Advertised100BTHalfDuplexCapability = 1 << 2,
+ Advertised100BTFullDuplexCapability = 1 << 3,
+ AdvertisedFlowControlCapability = 1 << 4,
+ ForceDuplex = 1 << 5,
+ ForceSpeed = 1 << 6,
+ AutoNegotiationEnable = 1 << 7,
+ ForceMDIX = 1 << 9,
+ DisableAutoMDIMDIX = 1 << 10,
+ RestartAutoNegotiation = 1 << 13,
+ TransmitterDisable = 1 << 14,
+ LedOff = 1 << 15,
+ };
+
+ /**
+ * Flags bits of the Port1Status register.
+ */
+ enum [[clang::flag_enum]] Port1Status : uint16_t{
+ Partner10BTHalfDuplexCapability = 1 << 0,
+ Partner10BTFullDuplexCapability = 1 << 1,
+ Partner100BTHalfDuplexCapability = 1 << 2,
+ Partner100BTFullDuplexCapability = 1 << 3,
+ PartnerFlowControlCapability = 1 << 4,
+ LinkGood = 1 << 5,
+ AutoNegotiationDone = 1 << 6,
+ MDIXStatus = 1 << 7,
+ OperationDuplex = 1 << 9,
+ OperationSpeed = 1 << 10,
+ PolarityReverse = 1 << 13,
+ HPMDIX = 1 << 15,
+ };
+
+ /**
+ * The futex used to wait for interrupts when packets are available to
+ * receive.
+ */
+ const uint32_t *receiveInterruptFutex;
+
+ /**
+ * Read a register from the KSZ8851.
+ */
+ [[nodiscard]] uint16_t register_read(RegisterOffset reg) const
+ {
+ // KSZ8851 command have the following format:
+ //
+ // First byte:
+ // +---------+-------------+-------------------+
+ // | 7 6 | 5 2 | 1 0 |
+ // +---------+-------------+-------------------+
+ // | Command | Byte Enable | Address (bit 7-6) |
+ // +---------+-------------+-------------------+
+ //
+ // Second byte (for register read/write only):
+ // +-------------------+--------+
+ // | 7 4 | 3 0 |
+ // +-------------------+--------+
+ // | Address (bit 5-2) | Unused |
+ // +-------------------+--------+
+ //
+ // Note that the access is 32-bit since bit 1 & 0 of the address is not
+ // included. KSZ8851 have 16-bit registers so byte enable is used to
+ // determine which register to access from the 32 bits specified by the
+ // address.
+ uint8_t addr = static_cast<uint8_t>(reg);
+ uint8_t byteEnable = (addr & 0x2) == 0 ? 0b0011 : 0b1100;
+ uint8_t bytes[2];
+ bytes[0] = (static_cast<uint8_t>(SpiCommand::ReadRegister) << 6) |
+ (byteEnable << 2) | (addr >> 6);
+ bytes[1] = (addr << 2) & 0b11110000;
+
+ spi()->chip_select_assert(true);
+ spi()->blocking_write(bytes, sizeof(bytes));
+ uint16_t val;
+ spi()->blocking_read(reinterpret_cast<uint8_t *>(&val), sizeof(val));
+ spi()->chip_select_assert(false);
+ return val;
+ }
+
+ /**
+ * Write a register to KSZ8851.
+ */
+ void register_write(RegisterOffset reg, uint16_t val) const
+ {
+ // See register_read for command format.
+ uint8_t addr = static_cast<uint8_t>(reg);
+ uint8_t byteEnable = (addr & 0x2) == 0 ? 0b0011 : 0b1100;
+ uint8_t bytes[2];
+ bytes[0] = (static_cast<uint8_t>(SpiCommand::WriteRegister) << 6) |
+ (byteEnable << 2) | (addr >> 6);
+ bytes[1] = (addr << 2) & 0b11110000;
+
+ spi()->chip_select_assert(true);
+ spi()->blocking_write(bytes, sizeof(bytes));
+ spi()->blocking_write(reinterpret_cast<uint8_t *>(&val), sizeof(val));
+ spi()->wait_idle();
+ spi()->chip_select_assert(false);
+ }
+
+ /**
+ * Set bits in a KSZ8851 register.
+ */
+ void register_set(RegisterOffset reg, uint16_t mask) const
+ {
+ uint16_t old = register_read(reg);
+ register_write(reg, old | mask);
+ }
+
+ /**
+ * Clear bits in a KSZ8851 register.
+ */
+ void register_clear(RegisterOffset reg, uint16_t mask) const
+ {
+ uint16_t old = register_read(reg);
+ register_write(reg, old & ~mask);
+ }
+
+ /**
+ * Helper. Returns a pointer to the SPI device.
+ */
+ [[nodiscard,
+ gnu::always_inline]] Capability<volatile SonataSpi::EthernetMac>
+ spi() const
+ {
+ return MMIO_CAPABILITY(SonataSpi::EthernetMac, spi_ethmac);
+ }
+
+ /**
+ * Number of frames yet to be received since last interrupt acknowledgement.
+ */
+ uint16_t framesToProcess = 0;
+
+ /**
+ * Mutex protecting transmitBuffer if send_frame is reentered.
+ */
+ RecursiveMutex transmitBufferMutex;
+
+ /**
+ * Buffer used by send_frame.
+ */
+ std::unique_ptr<uint8_t[]> transmitBuffer;
+
+ /**
+ * Mutex protecting receiveBuffer if receive_frame is called before a
+ * previous returned frame is dropped.
+ */
+ RecursiveMutex receiveBufferMutex;
+
+ /**
+ * Lock to protect reads/writes to the SPI Chip Selects, which use the
+ * same bits of the MMIO region and thus need to be protected.
+ */
+ FlagLockPriorityInherited chipSelectLock;
+
+ /**
+ * Buffer used by receive_frame.
+ */
+ std::unique_ptr<uint8_t[]> receiveBuffer;
+
+ public:
+ /**
+ * Initialise a reference to the Ethernet device.
+ */
+ Ksz8851Ethernet()
+ {
+ transmitBuffer = std::make_unique<uint8_t[]>(MaxFrameSize);
+ receiveBuffer = std::make_unique<uint8_t[]>(MaxFrameSize);
+
+ // Reset chip. It needs to be hold in reset for at least 10ms.
+ spi()->reset_assert(true);
+ thread_millisecond_wait(20);
+ spi()->reset_assert(false);
+
+ uint16_t chipId = register_read(RegisterOffset::ChipIdEnable);
+ Debug::log("Chip ID is {}", chipId);
+
+ // Check the chip ID. The last nibble is revision ID and can be ignored.
+ Debug::Assert((chipId & 0xFFF0) == 0x8870, "Unexpected Chip ID");
+
+ // This is the initialisation sequence suggested by the programmer's
+ // guide.
+ register_write(RegisterOffset::TransmitFrameDataPointer,
+ FrameDataPointer::FrameDataPointerAutoIncrement);
+ register_write(RegisterOffset::TransmitControl,
+ TransmitControl::TransmitCrcEnable |
+ TransmitControl::TransmitPaddingEnable |
+ TransmitControl::TransmitFlowControlEnable |
+ TransmitControl::TransmitChecksumGenerationIp |
+ TransmitControl::TransmitChecksumGenerationTcp |
+ TransmitControl::TransmitChecksumGenerationIcmp);
+ register_write(RegisterOffset::ReceiveFrameDataPointer,
+ FrameDataPointer::FrameDataPointerAutoIncrement);
+ // Configure Receive Frame Threshold for one frame.
+ register_write(RegisterOffset::ReceiveFrameCountThreshold, 0x0001);
+ register_write(RegisterOffset::ReceiveControl1,
+ ReceiveControl1::ReceiveUnicastEnable |
+ ReceiveControl1::ReceiveMulticastEnable |
+ ReceiveControl1::ReceiveBroadcastEnable |
+ ReceiveControl1::ReceiveFlowControlEnable |
+ ReceiveControl1::
+ ReceivePhysicalAddressFilteringWithMacAddressEnable |
+ ReceiveControl1::ReceiveIpFrameChecksumCheckEnable |
+ ReceiveControl1::ReceiveTcpFrameChecksumCheckEnable |
+ ReceiveControl1::ReceiveUdpFrameChecksumCheckEnable);
+ // The frame data burst field in this register controls how many data
+ // from a frame is read per DMA operation. The programmer's guide has a
+ // 4 byte burst, but to reduce SPI transactions and improve performance
+ // we choose to use single-frame data burst which reads the entire
+ // Ethernet frame in a single SPI DMA.
+ register_write(
+ RegisterOffset::ReceiveControl2,
+ ReceiveControl2::UdpLiteFrameEnable |
+ ReceiveControl2::ReceiveIpv4Ipv6UdpFrameChecksumEqualZero |
+ ReceiveControl2::ReceiveIpv4Ipv6FragmentFramePass |
+ ReceiveControl2::DataBurstSingleFrame);
+ register_write(
+ RegisterOffset::ReceiveQueueCommand,
+ ReceiveQueueCommand::ReceiveFrameCountThresholdEnable |
+ ReceiveQueueCommand::AutoDequeueReceiveQueueFrameEnable);
+
+ // Programmer's guide have a step to set the chip in half-duplex when
+ // negotiation failed, but we omit the step since non-switching hubs and
+ // half-duplex Ethernet is rarely used these days.
+
+ register_set(RegisterOffset::Port1Control,
+ Port1Control::RestartAutoNegotiation);
+
+ // Configure Low Watermark to 6KByte available buffer space out of
+ // 12KByte (unit is 4 bytes).
+ register_write(RegisterOffset::FlowControlLowWatermark, 0x0600);
+ // Configure High Watermark to 4KByte available buffer space out of
+ // 12KByte (unit is 4 bytes).
+ register_write(RegisterOffset::FlowControlHighWatermark, 0x0400);
+
+ // Clear the interrupt status
+ register_write(RegisterOffset::InterruptStatus, 0xFFFF);
+ receiveInterruptFutex =
+ interrupt_futex_get(STATIC_SEALED_VALUE(EthernetInterruptCapability));
+ // Enable Receive interrupt
+ register_write(RegisterOffset::InterruptEnable, ReceiveInterrupt);
+
+ // Enable QMU Transmit.
+ register_set(RegisterOffset::TransmitControl,
+ TransmitControl::TransmitEnable);
+ // Enable QMU Receive.
+ register_set(RegisterOffset::ReceiveControl1,
+ ReceiveControl1::ReceiveEnable);
+ }
+
+ Ksz8851Ethernet(const Ksz8851Ethernet &) = delete;
+ Ksz8851Ethernet(Ksz8851Ethernet &&) = delete;
+
+ /**
+ * This device does not have a unique MAC address and so users must provide
+ * a locally administered MAC address if more than one device is present on
+ * the same network.
+ */
+ static constexpr bool has_unique_mac_address()
+ {
+ return false;
+ }
+
+ static constexpr MACAddress mac_address_default()
+ {
+ return {0x3a, 0x30, 0x25, 0x24, 0xfe, 0x7a};
+ }
+
+ void mac_address_set(MACAddress address = mac_address_default())
+ {
+ register_write(RegisterOffset::MacAddressHigh,
+ (address[0] << 8) | address[1]);
+ register_write(RegisterOffset::MacAdressMiddle,
+ (address[2] << 8) | address[3]);
+ register_write(RegisterOffset::MacAddressLow,
+ (address[4] << 8) | address[5]);
+ }
+
+ uint32_t receive_interrupt_value()
+ {
+ return *receiveInterruptFutex;
+ }
+
+ int receive_interrupt_complete(Timeout *timeout,
+ uint32_t lastInterruptValue)
+ {
+ // If there are frames to process, do not enter wait.
+ if (framesToProcess)
+ {
+ return 0;
+ }
+
+ // Our interrupt is level-triggered; if a frame happens to arrive
+ // between `receive_frame` call and we marking interrupt as received,
+ // it will trigger again immediately after we acknowledge it.
+
+ // Acknowledge the interrupt in the scheduler.
+ interrupt_complete(STATIC_SEALED_VALUE(EthernetInterruptCapability));
+ if (*receiveInterruptFutex == lastInterruptValue)
+ {
+ Debug::log("Acknowledged interrupt, sleeping on futex {}",
+ receiveInterruptFutex);
+ return futex_timed_wait(
+ timeout, receiveInterruptFutex, lastInterruptValue);
+ }
+ Debug::log("Scheduler announces interrupt has fired");
+ return 0;
+ }
+
+ /**
+ * Simple class representing a received Ethernet frame.
+ */
+ class Frame
+ {
+ public:
+ uint16_t length;
+ Capability<uint8_t> buffer;
+
+ private:
+ friend class Ksz8851Ethernet;
+ LockGuard<RecursiveMutex> guard;
+
+ Frame(LockGuard<RecursiveMutex> &&guard,
+ Capability<uint8_t> buffer,
+ uint16_t length)
+ : guard(std::move(guard)), buffer(buffer), length(length)
+ {
+ }
+ };
+
+ /**
+ * Check the link status of the PHY.
+ */
+ bool phy_link_status()
+ {
+ uint16_t status = register_read(RegisterOffset::Port1Status);
+ return (status & Port1Status::LinkGood) != 0;
+ }
+
+ std::optional<Frame> receive_frame()
+ {
+ LockGuard g{chipSelectLock};
+ if (framesToProcess == 0)
+ {
+ uint16_t isr = register_read(RegisterOffset::InterruptStatus);
+ if (!(isr & ReceiveInterrupt))
+ {
+ return std::nullopt;
+ }
+
+ // Acknowledge the interrupt
+ register_write(RegisterOffset::InterruptStatus, ReceiveInterrupt);
+
+ // Read number of frames pending.
+ // Note that this is only updated when we acknowledge the interrupt.
+ framesToProcess =
+ register_read(RegisterOffset::ReceiveFrameCountThreshold) >> 8;
+ }
+
+ // Get number of frames pending
+ for (; framesToProcess; framesToProcess--)
+ {
+ uint16_t status =
+ register_read(RegisterOffset::ReceiveFrameHeaderStatus);
+ uint16_t length =
+ register_read(RegisterOffset::ReceiveFrameHeaderByteCount) &
+ 0xFFF;
+ bool valid =
+ (status & ReceiveFrameValid) &&
+ !(status &
+ (ReceiveCrcError | ReceiveRuntFrame | ReceiveFrameTooLong |
+ ReceiveMiiError | ReceiveUdpFrameChecksumStatus |
+ ReceiveTcpFrameChecksumStatus | ReceiveIpFrameChecksumStatus |
+ ReceiveIcmpFrameChecksumStatus));
+
+ if (!valid)
+ {
+ DebugFrameDrops::log("Dropping frame with status: {}", status);
+
+ drop_error_frame();
+ continue;
+ }
+
+ if (length == 0)
+ {
+ DebugFrameDrops::log("Dropping frame with zero length");
+
+ drop_error_frame();
+ continue;
+ }
+
+ // The DMA transfer to the Ethernet MAC must be a multiple of 4
+ // bytes.
+ uint16_t paddedLength = (length + 3) & ~0x3;
+ if (paddedLength > MaxFrameSize)
+ {
+ DebugFrameDrops::log("Dropping frame that is too large: {}",
+ length);
+
+ drop_error_frame();
+ continue;
+ }
+
+ Debug::log("Receiving frame of length {}", length);
+
+ LockGuard guard{receiveBufferMutex};
+
+ // Reset receive frame pointer to zero and start DMA transfer
+ // operation.
+ register_write(RegisterOffset::ReceiveFrameDataPointer,
+ FrameDataPointer::FrameDataPointerAutoIncrement);
+ register_set(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+
+ // Start receiving via SPI.
+ uint8_t cmd = static_cast<uint8_t>(SpiCommand::ReadDma) << 6;
+ spi()->chip_select_assert(true);
+ spi()->blocking_write(&cmd, 1);
+
+ // Initial words are ReceiveFrameHeaderStatus and
+ // ReceiveFrameHeaderByteCount which we have already know the value.
+ uint8_t dummy[8];
+ spi()->blocking_read(dummy, sizeof(dummy));
+
+ spi()->blocking_read(receiveBuffer.get(), paddedLength);
+
+ spi()->chip_select_assert(false);
+
+ register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+ framesToProcess -= 1;
+
+ Capability<uint8_t> boundedBuffer{receiveBuffer.get()};
+ boundedBuffer.bounds().set_inexact(length);
+ // Remove all permissions except load. This also removes global, so
+ // that this cannot be captured.
+ boundedBuffer.permissions() &=
+ CHERI::PermissionSet{CHERI::Permission::Load};
+
+ return Frame{std::move(guard), boundedBuffer, length};
+ }
+
+ return std::nullopt;
+ }
+
+ /**
+ * Send a packet. This will block if no buffer space is available on
+ * device.
+ *
+ * The third argument is a callback that allows the caller to check the
+ * frame before it's sent but after it's copied into memory that isn't
+ * shared with other compartments.
+ */
+ bool send_frame(const uint8_t *buffer, uint16_t length, auto &&check)
+ {
+ // The DMA transfer to the Ethernet MAC must be a multiple of 4 bytes.
+ uint16_t paddedLength = (length + 3) & ~0x3;
+ if (paddedLength > MaxFrameSize)
+ {
+ Debug::log("Frame size {} is larger than the maximum size", length);
+ return false;
+ }
+
+ LockGuard guard{transmitBufferMutex};
+
+ // We must check the frame pointer and its length. Although it
+ // is supplied by the firewall which is trusted, the firewall
+ // does not check the pointer which is coming from external
+ // untrusted components.
+ Timeout t{10};
+ if ((heap_claim_fast(&t, buffer) < 0) ||
+ (!CHERI::check_pointer<CHERI::PermissionSet{
+ CHERI::Permission::Load}>(buffer, length)))
+ {
+ return false;
+ }
+
+ memcpy(transmitBuffer.get(), buffer, length);
+ if (!check(transmitBuffer.get(), length))
+ {
+ return false;
+ }
+
+ LockGuard g{chipSelectLock};
+
+ // Wait for the transmit buffer to be available on the device side.
+ // This needs to include the header.
+ while ((register_read(RegisterOffset::TransmitQueueMemoryInfo) &
+ 0xFFF) < length + 4)
+ {
+ }
+
+ Debug::log("Sending frame of length {}", length);
+
+ // Start DMA transfer operation.
+ register_set(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+
+ // Start sending via SPI.
+ uint8_t cmd = static_cast<uint8_t>(SpiCommand::WriteDma) << 6;
+ spi()->chip_select_assert(true);
+ spi()->blocking_write(&cmd, 1);
+
+ uint32_t header = static_cast<uint32_t>(length) << 16;
+ spi()->blocking_write(reinterpret_cast<uint8_t *>(&header),
+ sizeof(header));
+
+ spi()->blocking_write(transmitBuffer.get(), paddedLength);
+
+ spi()->wait_idle();
+ spi()->chip_select_assert(false);
+
+ // Stop QMU DMA transfer operation.
+ register_clear(RegisterOffset::ReceiveQueueCommand, StartDmaAccess);
+
+ // Enqueue the frame for transmission.
+ register_set(
+ RegisterOffset::TransmitQueueCommand,
+ TransmitQueueCommand::ManualEnqueueTransmitQueueFrameEnable);
+
+ return true;
+ }
+
+ private:
+ void drop_error_frame()
+ {
+ register_set(RegisterOffset::ReceiveQueueCommand,
+ ReleaseReceiveErrorFrame);
+ // Wait for confirmation of frame release before attempting to process
+ // next frame.
+ while (register_read(RegisterOffset::ReceiveQueueCommand) &
+ ReleaseReceiveErrorFrame)
+ {
+ }
+ }
+};
+
+using EthernetDevice = Ksz8851Ethernet;
+
+static_assert(EthernetAdaptor<EthernetDevice>);