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>);