| #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-gpio.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>; |
| |
| /** |
| * GPIO output pins to be used |
| */ |
| enum class GpioPin : uint8_t |
| { |
| EthernetChipSelect = 13, |
| EthernetReset = 14, |
| }; |
| |
| /** |
| * 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; |
| |
| /** |
| * Set value of a GPIO output. |
| */ |
| inline void set_gpio_output_bit(GpioPin pin, bool value) const |
| { |
| uint32_t shift = static_cast<uint8_t>(pin); |
| uint32_t output = gpio()->output; |
| output &= ~(1 << shift); |
| output |= value << shift; |
| gpio()->output = output; |
| } |
| |
| /** |
| * 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; |
| |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, false); |
| spi()->blocking_write(bytes, sizeof(bytes)); |
| uint16_t val; |
| spi()->blocking_read(reinterpret_cast<uint8_t *>(&val), sizeof(val)); |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, true); |
| 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; |
| |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, false); |
| spi()->blocking_write(bytes, sizeof(bytes)); |
| spi()->blocking_write(reinterpret_cast<uint8_t *>(&val), sizeof(val)); |
| spi()->wait_idle(); |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, true); |
| } |
| |
| /** |
| * 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> spi() const |
| { |
| return MMIO_CAPABILITY(SonataSpi, spi2); |
| } |
| |
| /** |
| * Helper. Returns a pointer to the GPIO device. |
| */ |
| [[nodiscard, gnu::always_inline]] Capability<volatile SonataGPIO> |
| gpio() const |
| { |
| return MMIO_CAPABILITY(SonataGPIO, gpio); |
| } |
| |
| /** |
| * 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; |
| |
| /** |
| * Reads and writes of the GPIO space use the same bits of the MMIO region |
| * and so need to be protected. |
| */ |
| FlagLockPriorityInherited gpioLock; |
| |
| /** |
| * 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. |
| set_gpio_output_bit(GpioPin::EthernetReset, false); |
| thread_millisecond_wait(20); |
| set_gpio_output_bit(GpioPin::EthernetReset, true); |
| |
| 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{gpioLock}; |
| 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; |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, false); |
| 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); |
| |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, true); |
| |
| 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{gpioLock}; |
| |
| // 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; |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, false); |
| 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(); |
| set_gpio_output_bit(GpioPin::EthernetChipSelect, true); |
| |
| // 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>); |