| #pragma once |
| #include <array> |
| #include <cheri.hh> |
| #include <cstddef> |
| #include <cstdint> |
| #include <debug.hh> |
| #include <futex.h> |
| #include <interrupt.h> |
| #include <optional> |
| #include <platform/concepts/ethernet.hh> |
| #include <thread.h> |
| #include <type_traits> |
| |
| DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(EthernetReceive, |
| EthernetReceiveInterrupt, |
| true, |
| true); |
| |
| /** |
| * The driver for Kunyan Liu's custom Ethernet MAC for the Arty A7. This is |
| * intended to run at 10Mb/s. It provides two send and two receive buffers in |
| * shared SRAM. |
| * |
| * WARNING: This is currently evolving and is not yet stable. |
| */ |
| class KunyanEthernet |
| { |
| /** |
| * 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 = false; |
| |
| /** |
| * Helper for conditional debug logs and assertions. |
| */ |
| using Debug = ConditionalDebug<DebugEthernet, "Ethernet driver">; |
| |
| /** |
| * Import the Capability helper from the CHERI namespace. |
| */ |
| template<typename T> |
| using Capability = CHERI::Capability<T>; |
| |
| /** |
| * The location of registers |
| */ |
| enum class RegisterOffset : size_t |
| { |
| /** |
| * MAC control register. Higher bits control filtering modes: |
| * |
| * 12: Drop frames with invalid CRC. |
| * 11: Allow incoming IPv6 multicast filtering |
| * 10: Allow incoming IPv4 multicast filtering |
| * 9: Allow incoming broadcast filtering |
| * 8: Allow incoming unicast filtering |
| * |
| * |
| * Write a 1 to reset the PHY. This takes 167 ms. |
| */ |
| MACControl = 0, |
| /** |
| * High 4 bytes of the MAC address. Byte 2 is in bits 31:24, byte 5 is |
| * in 7:0. |
| */ |
| MACAddressHigh = 4, |
| /** |
| * Low 2 bytes of the MAC address. Byte 1 in bits 15:8, byte 0 in 7:0. |
| */ |
| MACAddressLow = 8, |
| /** |
| * Scratch register, unused. |
| */ |
| Scratch = 12, |
| /** |
| * MDIO address, controls which PHY register will be read or written. |
| */ |
| MDIOAddress = 0x10, |
| /** |
| * MDIO Write, written to set the value to write to a PHY register. |
| */ |
| MDIODataWrite = 0x14, |
| /** |
| * MDIO Read, set by the device to the value of a PHY register in |
| * response to an MDIO read. |
| */ |
| MDIODataRead = 0x18, |
| /** |
| * MDIO control. Used to control and report status of MDIO |
| * transactions. |
| */ |
| MDIOControl = 0x1c, |
| /** |
| * Length of the frame written to the ping buffer for sending. |
| */ |
| TransmitFrameLengthPing = 0x20, |
| /** |
| * Transmit control for the ping buffer. |
| */ |
| TransmitControlPing = 0x24, |
| /** |
| * Length of the frame written to the pong buffer for sending. |
| */ |
| TransmitFrameLengthPong = 0x28, |
| /** |
| * Transmit control for the pong buffer. |
| */ |
| TransmitControlPong = 0x2c, |
| /** |
| * Receive control for the ping buffer. |
| */ |
| ReceiveControlPing = 0x30, |
| /** |
| * Receive control for the pong buffer. |
| */ |
| ReceiveControlPong = 0x34, |
| /** |
| * Interrupt enable. Write 1 to enable, 0 to disable interrupts for |
| * each direction. Bit 0 is transmit, bit 1 is receive. |
| */ |
| GlobalInterruptEnable = 0x38, |
| /** |
| * Current interrupt status. Bit 0 is transmit, bit 1 is receive. |
| * |
| * Write a 1 to each bit to clear. The device will keep the interrupt |
| * asserted until it is explicitly cleared. |
| */ |
| InterruptStatus = 0x3c, |
| /** |
| * Length of the frames in the ping and pong buffer, in the low and |
| * high 16 bits respectively. |
| */ |
| ReceiveFrameLength = 0x40, |
| /** |
| * Saturating counter of the number of frames received. |
| */ |
| ReceivedFramesCount = 0x44, |
| /** |
| * Saturating counter of the number of frames passed to software. |
| */ |
| ReceivedFramesPassedToSoftware = 0x48, |
| /** |
| * Saturating counter of the number of frames dropped because the |
| * receive buffers were both full. |
| */ |
| ReceiveDroppedFramesBuffersFull = 0x4c, |
| /** |
| * Saturating counter of the number of frames dropped because they had |
| * failed FCS checks. |
| */ |
| ReceiveDroppedFramesFCSFailed = 0x50, |
| /** |
| * Saturating counter of the number of frames dropped because the |
| * target address was invalid. |
| */ |
| ReceiveDroppedFramesInvalidAddress = 0x54, |
| }; |
| |
| using MACAddress = std::array<uint8_t, 6>; |
| |
| /** |
| * MAC filtering modes. |
| */ |
| enum class FilterMode : uint32_t |
| { |
| /** |
| * Allow incoming frames without doing any address filtering. |
| */ |
| EnableAddressFiltering = 1 << 13, |
| /** |
| * Drop frames with invalid CRC. |
| */ |
| DropInvalidCRC = 1 << 12, |
| /** |
| * Allow incoming IPv6 multicast filtering |
| */ |
| AllowIPv6Multicast = 1 << 11, |
| /** |
| * Allow incoming IPv4 multicast filtering |
| */ |
| AllowIPv4Multicast = 1 << 10, |
| /** |
| * Allow incoming broadcast filtering |
| */ |
| AllowBroadcast = 1 << 9, |
| /** |
| * Allow incoming unicast filtering |
| */ |
| AllowUnicast = 1 << 8, |
| }; |
| |
| /** |
| * The futex used to wait for interrupts when packets are available to |
| * receive. |
| */ |
| const uint32_t *receiveInterruptFutex; |
| |
| /** |
| * Counter for dropped frames, intended to be extensible to support |
| * different debugging registers. |
| */ |
| struct DroppedFrameCount |
| { |
| /** |
| * The old value of the counter. |
| */ |
| uint32_t droppedFrameCount[3]; |
| /** |
| * Get a reference to one of the recorded counters. |
| */ |
| template<RegisterOffset Reg> |
| uint32_t &get(); |
| template<> |
| uint32_t &get<RegisterOffset::ReceiveDroppedFramesBuffersFull>() |
| { |
| return droppedFrameCount[0]; |
| } |
| |
| template<> |
| uint32_t &get<RegisterOffset::ReceiveDroppedFramesFCSFailed>() |
| { |
| return droppedFrameCount[1]; |
| } |
| |
| template<> |
| uint32_t &get<RegisterOffset::ReceiveDroppedFramesInvalidAddress>() |
| { |
| return droppedFrameCount[2]; |
| } |
| }; |
| |
| /** |
| * Empty class, for use with `std::conditional_t` to conditionally add a |
| * field. |
| */ |
| struct Empty |
| { |
| }; |
| |
| /** |
| * If we're building with debugging support for dropped-frame counters, |
| * reserve space for them. |
| */ |
| [[no_unique_address]] std:: |
| conditional_t<DebugDroppedFrames, DroppedFrameCount, Empty> |
| droppedFrames; |
| |
| /** |
| * Log a message if the dropped-frame counter identified by `Reg` has |
| * changed. |
| */ |
| template<RegisterOffset Reg, typename T = decltype(droppedFrames)> |
| void dropped_frames_log_if_changed(T &droppedFrames) |
| { |
| if constexpr (std::is_same_v<T, DroppedFrameCount>) |
| { |
| auto &lastCount = droppedFrames.template get<Reg>(); |
| auto count = mmio_register<Reg>(); |
| if (count != lastCount) |
| { |
| ConditionalDebug<true, "Ethernet driver">::log( |
| "Dropped frames in counter {}: {}", Reg, count); |
| lastCount = count; |
| } |
| } |
| } |
| |
| /** |
| * Set the state of one of the MAC filtering modes. Returns the previous |
| * value of the mode. |
| */ |
| bool filter_mode_update(FilterMode mode, bool enable) |
| { |
| auto &macControl = mmio_register<RegisterOffset::MACControl>(); |
| uint32_t modeBit = static_cast<uint32_t>(mode); |
| uint32_t oldMode = macControl; |
| Debug::log("Old filter mode {}: {}", mode, oldMode); |
| if (enable) |
| { |
| macControl = oldMode | modeBit; |
| } |
| else |
| { |
| macControl = oldMode & ~modeBit; |
| } |
| return oldMode | modeBit; |
| } |
| |
| /** |
| * The PHY registers are accessed through the MDIO interface. |
| */ |
| enum class PHYRegister |
| { |
| BasicControlRegister = 0, |
| BasicStatusRegister = 1, |
| Identifier1 = 2, |
| Identifier2 = 3, |
| AutoNegotiationAdvertisement = 4, |
| AutoNegotiationLinkPartnerAbility = 5, |
| }; |
| |
| enum BufferID |
| { |
| Ping = 0, |
| Pong = 1, |
| }; |
| |
| bool receiveBufferInUse[2] = {false, false}; |
| bool sendBufferInUse[2] = {false, false}; |
| |
| BufferID nextReceiveBuffer = BufferID::Ping; |
| |
| constexpr static BufferID next_buffer_id(BufferID current) |
| { |
| return current == BufferID::Ping ? BufferID::Pong : BufferID::Ping; |
| } |
| |
| /** |
| * Reset the PHY. This must be done on initialisation. |
| */ |
| void phy_reset() |
| { |
| auto &macControl = mmio_register<RegisterOffset::MACControl>(); |
| macControl = 1; |
| // Wait for the PHY to reset. |
| thread_millisecond_wait(167); |
| // Initialise MDIO again after the PHY has been reset. |
| mmio_register<RegisterOffset::MDIOControl>() = 0x10; |
| // Wait to make sure MDIO initialisation has completed. |
| thread_microsecond_spin(1); |
| } |
| |
| /** |
| * Helper. Returns a pointer to the device. |
| */ |
| [[nodiscard]] __always_inline Capability<volatile uint32_t> |
| mmio_region() const |
| { |
| return MMIO_CAPABILITY(uint32_t, kunyan_ethernet); |
| } |
| |
| /** |
| * Helper. Returns a reference to a register in this device's MMIO region. |
| * The register is identified by a `RegisterOffset` value. |
| */ |
| template<RegisterOffset Offset> |
| [[nodiscard]] volatile uint32_t &mmio_register() const |
| { |
| auto reg = mmio_region(); |
| reg.address() += static_cast<size_t>(Offset); |
| reg.bounds() = sizeof(uint32_t); |
| return *reg; |
| } |
| |
| /** |
| * Poll the MDIO control register until a transaction is done. |
| * |
| * NOTE: This does no error handling. It can infinite loop if the device |
| * is broken. Normally, MDIO operations complete in fewer cycles than it |
| * takes to call this method and so it will return almost instantly. |
| */ |
| void mdio_wait_for_ready() |
| { |
| auto &mdioControl = mmio_register<RegisterOffset::MDIOControl>(); |
| while (mdioControl & 1) {} |
| } |
| |
| /** |
| * Start an MDIO transaction. This assumes that the MDIO control register |
| * and, for write operations, the MDIO data write register have been set up. |
| * A call to `mdio_wait_for_ready` is required for read transactions to |
| * ensure that the operation has completed before reading the MIDO data read |
| * register. |
| */ |
| void mdio_start_transaction() |
| { |
| auto &mdioControl = mmio_register<RegisterOffset::MDIOControl>(); |
| Debug::Assert(((mdioControl & ((1 << 3) | 1)) == 0), "MDIO is busy"); |
| // Write the MDIO enable bit and the start bit. |
| mdioControl = (1 << 3) | 1; |
| } |
| |
| /** |
| * Returns a pointer to one of the receive buffers, identified by the buffer |
| * identifier (ping or pong). |
| */ |
| uint8_t *receive_buffer_pointer(BufferID index = BufferID::Ping) |
| { |
| auto buffer = mmio_region(); |
| buffer.address() += |
| static_cast<size_t>(index == BufferID::Ping ? 0x2000 : 0x2800); |
| buffer.bounds() = 0x800; |
| return const_cast<uint8_t *>( |
| reinterpret_cast<volatile uint8_t *>((buffer.get()))); |
| } |
| |
| /** |
| * Returns a pointer to one of the receive buffers, identified by the buffer |
| * identifier (ping or pong). |
| */ |
| uint8_t *transmit_buffer_pointer(BufferID index = BufferID::Ping) |
| { |
| auto buffer = mmio_region(); |
| buffer.address() += |
| static_cast<size_t>(index == BufferID::Ping ? 0x1000 : 0x1800); |
| buffer.bounds() = 0x800; |
| return const_cast<uint8_t *>( |
| reinterpret_cast<volatile uint8_t *>((buffer.get()))); |
| } |
| |
| /** |
| * Write a value to a PHY register via MDIO. |
| */ |
| void |
| mdio_write(uint8_t phyAddress, PHYRegister registerAddress, uint16_t data) |
| { |
| mdio_wait_for_ready(); |
| auto &mdioAddress = mmio_register<RegisterOffset::MDIOAddress>(); |
| auto &mdioWrite = mmio_register<RegisterOffset::MDIODataWrite>(); |
| uint32_t writeCommand = |
| (0 << 10) | (phyAddress << 5) | uint32_t(registerAddress); |
| mdioAddress = writeCommand; |
| mdioWrite = data; |
| mdio_start_transaction(); |
| } |
| |
| /** |
| * Read a value from a PHY register via MDIO. |
| */ |
| uint16_t mdio_read(uint8_t phyAddress, PHYRegister registerAddress) |
| { |
| mdio_wait_for_ready(); |
| auto &mdioAddress = mmio_register<RegisterOffset::MDIOAddress>(); |
| uint32_t readCommand = |
| (1 << 10) | (phyAddress << 5) | uint8_t(registerAddress); |
| mdioAddress = readCommand; |
| mdio_start_transaction(); |
| mdio_wait_for_ready(); |
| return mmio_register<RegisterOffset::MDIODataRead>(); |
| } |
| |
| public: |
| /** |
| * Initialise a reference to the Ethernet device. This will check the ID |
| * registers to make sure that this is the kind of device that we're |
| * expecting. |
| */ |
| KunyanEthernet() |
| { |
| phy_reset(); |
| // Check that this is the device that we're looking for |
| Debug::Assert( |
| [&]() { return mdio_read(1, PHYRegister::Identifier1) == 0x2000; }, |
| "PHY identifier 1 is not 0x2000"); |
| Debug::Assert( |
| [&]() { |
| return (mdio_read(1, PHYRegister::Identifier2) & 0xFFF0) == |
| 0x5c90; |
| }, |
| "PHY identifier 1 is not 0x5c9x"); |
| autonegotiation_enable(); |
| filter_mode_update(FilterMode::DropInvalidCRC, true); |
| filter_mode_update(FilterMode::EnableAddressFiltering, false); |
| filter_mode_update(FilterMode::AllowUnicast, false); |
| filter_mode_update(FilterMode::AllowIPv4Multicast, false); |
| filter_mode_update(FilterMode::AllowIPv6Multicast, false); |
| receiveInterruptFutex = |
| interrupt_futex_get(STATIC_SEALED_VALUE(EthernetReceive)); |
| // Enable receive interrupts |
| mmio_register<RegisterOffset::GlobalInterruptEnable>() = 0b10; |
| // Clear pending receive interrupts. |
| mmio_register<RegisterOffset::InterruptStatus>() = 0b10; |
| } |
| |
| KunyanEthernet(const KunyanEthernet &) = delete; |
| KunyanEthernet(KunyanEthernet &&) = 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 {0x60, 0x6A, 0x6A, 0x37, 0x47, 0x88}; |
| } |
| |
| void mac_address_set(MACAddress address = mac_address_default()) |
| { |
| auto &macAddressHigh = mmio_register<RegisterOffset::MACAddressHigh>(); |
| auto &macAddressLow = mmio_register<RegisterOffset::MACAddressLow>(); |
| macAddressHigh = (address[2] << 24) | (address[3] << 16) | |
| (address[4] << 8) | address[5]; |
| macAddressLow = (address[1] << 8) | address[0]; |
| } |
| |
| uint32_t receive_interrupt_value() |
| { |
| return *receiveInterruptFutex; |
| } |
| |
| int receive_interrupt_complete(Timeout *timeout, |
| uint32_t lastInterruptValue) |
| { |
| // Clear the interrupt on the device. |
| auto &interruptStatus = |
| mmio_register<RegisterOffset::InterruptStatus>(); |
| interruptStatus = 0b10; |
| // There's a small window in between finishing checking the receive |
| // buffers and clearing the interrupt where we could miss the |
| // interrupt. Check that the receive buffers are empty *after* |
| // reenabling the interrupt to ensure that we don't sleep in this |
| // period. |
| if (check_frame(BufferID::Ping) || check_frame(BufferID::Pong)) |
| { |
| Debug::log("Packets already ready, not waiting for interrupt"); |
| return 0; |
| } |
| // Acknowledge the interrupt in the scheduler. |
| interrupt_complete(STATIC_SEALED_VALUE(EthernetReceive)); |
| 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; |
| } |
| |
| /** |
| * Enable autonegotiation on the PHY. |
| * |
| * FIXME: This blocks forever if the cable is disconnected! |
| */ |
| void autonegotiation_enable(uint8_t phyAddress = 1) |
| { |
| Debug::log("Starting autonegotiation"); |
| // Advertise 802.3, 10Base-T full and half duplex |
| mdio_write(phyAddress, |
| PHYRegister::AutoNegotiationAdvertisement, |
| (1 << 5) | (1 << 6) | 1); |
| // Enable and restart autonegitiation |
| mdio_write( |
| phyAddress, PHYRegister::BasicControlRegister, (1 << 12) | (1 << 9)); |
| Debug::log("Waiting for autonegotiation to complete"); |
| uint32_t loops = 0; |
| while ((mdio_read(phyAddress, PHYRegister::BasicStatusRegister) & |
| (1 << 5)) == 0) |
| { |
| if ((loops++ & 0xfffff) == 0) |
| { |
| Debug::log( |
| "Waiting for autonegotiation to complete ({} loops): status: " |
| "{}", |
| loops, |
| mdio_read(phyAddress, PHYRegister::BasicStatusRegister)); |
| } |
| yield(); |
| } |
| Debug::Assert( |
| [&]() { |
| return (mdio_read(phyAddress, PHYRegister::BasicStatusRegister) & |
| (1 << 2)) != 0; |
| }, |
| "Autonegotiation completed but link is down"); |
| Debug::log("Autonegotiation complete, status: {}", |
| mdio_read(phyAddress, PHYRegister::BasicStatusRegister)); |
| // Write 1 to clear the receive status. |
| mmio_register<RegisterOffset::ReceiveControlPing>() = 1; |
| mmio_register<RegisterOffset::ReceiveControlPong>() = 1; |
| } |
| |
| /** |
| * Check the link status of the PHY. |
| */ |
| bool phy_link_status() |
| { |
| return (mdio_read(1, PHYRegister::BasicStatusRegister) & (1 << 2)) != 0; |
| } |
| |
| std::optional<uint16_t> check_frame(BufferID index = BufferID::Ping) |
| { |
| // Debug::log("Checking for frame in buffer {}", index); |
| auto receiveControl = |
| index == BufferID::Ping |
| ? mmio_register<RegisterOffset::ReceiveControlPing>() |
| : mmio_register<RegisterOffset::ReceiveControlPong>(); |
| if ((receiveControl & 1) == 0) |
| { |
| return std::nullopt; |
| } |
| Debug::log("Buffer has a frame. Error? {}", receiveControl & 2); |
| Debug::log("Buffer length: {}", receiveControl >> 16); |
| return {receiveControl >> 16}; |
| } |
| |
| void complete_receive(BufferID index = BufferID::Ping) |
| { |
| Debug::log("Completing receive in buffer {}", index); |
| // This buffer is now free to receive another frame. |
| receiveBufferInUse[index] = false; |
| if (index == BufferID::Ping) |
| { |
| mmio_register<RegisterOffset::ReceiveControlPing>() = 1; |
| } |
| else |
| { |
| mmio_register<RegisterOffset::ReceiveControlPong>() = 1; |
| } |
| } |
| |
| /** |
| * Class encapsulating a frame that has been received. This holds a |
| * reference to the internal state of the buffer and will mark the buffer |
| * as free when it is destroyed. |
| */ |
| class BufferedFrame |
| { |
| friend class KunyanEthernet; |
| KunyanEthernet ðernetAdaptor; |
| BufferID owningBuffer; |
| |
| private: |
| BufferedFrame(KunyanEthernet ðernetAdaptor, |
| BufferID owningBuffer, |
| Capability<uint8_t> buffer, |
| uint16_t length) |
| |
| : ethernetAdaptor(ethernetAdaptor), |
| owningBuffer(owningBuffer), |
| buffer(buffer), |
| length(length) |
| { |
| // Mark this buffer as in use and try to advance the next buffer |
| ethernetAdaptor.receiveBufferInUse[owningBuffer] = true; |
| if (!ethernetAdaptor.receiveBufferInUse[next_buffer_id( |
| ethernetAdaptor.nextReceiveBuffer)]) |
| { |
| ethernetAdaptor.nextReceiveBuffer = |
| next_buffer_id(ethernetAdaptor.nextReceiveBuffer); |
| } |
| } |
| |
| public: |
| uint16_t length; |
| Capability<uint8_t> buffer; |
| BufferedFrame(const BufferedFrame &) = delete; |
| BufferedFrame(BufferedFrame &&other) |
| : ethernetAdaptor(other.ethernetAdaptor), |
| owningBuffer(other.owningBuffer), |
| buffer(other.buffer), |
| length(other.length) |
| { |
| other.buffer = nullptr; |
| other.length = 0; |
| } |
| ~BufferedFrame() |
| { |
| if (buffer != nullptr) |
| { |
| ethernetAdaptor.complete_receive(owningBuffer); |
| } |
| } |
| }; |
| |
| std::optional<BufferedFrame> receive_frame() |
| { |
| // To avoid processing the same packet twice, we must not use |
| // the current receive buffer if it is already in use. In that |
| // case, check the next receive buffer. If it is not in use, |
| // rotate the buffers. Otherwise, abandon. |
| if (receiveBufferInUse[nextReceiveBuffer]) |
| { |
| if (!receiveBufferInUse[next_buffer_id(nextReceiveBuffer)]) |
| { |
| // The next receive buffer is not in use. Rotate. |
| nextReceiveBuffer = next_buffer_id(nextReceiveBuffer); |
| } |
| else |
| { |
| // The next receive buffer is in use too. Abandon. |
| return std::nullopt; |
| } |
| } |
| |
| auto maybeLength = check_frame(nextReceiveBuffer); |
| |
| if (!maybeLength) |
| { |
| // The current receive buffer is empty. However, the next |
| // receive buffer, if not in use, may have a packet ready for |
| // us. Check that one as well. |
| |
| if (!receiveBufferInUse[next_buffer_id(nextReceiveBuffer)]) |
| { |
| // The next receive buffer is not in use. Check |
| // for packets there as well. |
| nextReceiveBuffer = next_buffer_id(nextReceiveBuffer); |
| maybeLength = check_frame(nextReceiveBuffer); |
| } |
| |
| if (!maybeLength) |
| { |
| // None of the buffers has packets that we can |
| // process. Abandon. |
| return std::nullopt; |
| } |
| } |
| |
| auto length = *maybeLength; |
| // Strip the FCS from the length. |
| length -= 4; |
| auto buffer = receive_buffer_pointer(nextReceiveBuffer); |
| Capability<uint8_t> boundedBuffer{buffer}; |
| boundedBuffer.bounds() = length; |
| // Remove all permissions except load. This also removes global, so |
| // that this cannot be captured. |
| boundedBuffer.permissions() &= |
| CHERI::PermissionSet{CHERI::Permission::Load}; |
| Debug::log("Received frame from buffer {}", nextReceiveBuffer); |
| return {{*this, nextReceiveBuffer, buffer, length}}; |
| } |
| |
| /** |
| * Send a packet. This is synchronous and will block until the packet has |
| * been sent. A better version would use the next available ping or pong |
| * buffer and return as soon as the send operation had been enqueued. |
| * |
| * 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) |
| { |
| auto &transmitControl = |
| mmio_register<RegisterOffset::TransmitControlPing>(); |
| // Spin waiting for the transmit buffer to be free. |
| while (transmitControl & 1) {} |
| // Write the frame to the transmit buffer. |
| auto transmitBuffer = transmit_buffer_pointer(); |
| // 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, buffer, length); |
| if (!check(transmitBuffer, length)) |
| { |
| return false; |
| } |
| // The Ethernet standard requires frames to be at least 60 bytes long. |
| // If we're asked to send anything shorter, pad it with zeroes. |
| // (It would be nice if the MAC did this automatically). |
| if (length < 60) |
| { |
| memset(transmitBuffer + length, 0, 60 - length); |
| length = 60; |
| } |
| // Write the length of the frame to the transmit length register. |
| mmio_register<RegisterOffset::TransmitFrameLengthPing>() = length; |
| // Start the transmit. |
| transmitControl = 1; |
| Debug::log("Sent frame, waiting for completion"); |
| while (transmitControl & 1) {} |
| // Return if the frame was sent successfully. |
| Debug::log("Transmit control register: {}", transmitControl); |
| if ((transmitControl & 2) != 0) |
| { |
| Debug::log("Error sending frame"); |
| } |
| return (transmitControl & 2) == 0; |
| } |
| |
| /** |
| * If debugging dropped frames is enabled, log any counter values that have |
| * changed since the last call to this function. |
| */ |
| void dropped_frames_log_all_if_changed() |
| { |
| if constexpr (DebugDroppedFrames) |
| { |
| dropped_frames_log_if_changed< |
| RegisterOffset::ReceiveDroppedFramesBuffersFull>(droppedFrames); |
| dropped_frames_log_if_changed< |
| RegisterOffset::ReceiveDroppedFramesFCSFailed>(droppedFrames); |
| dropped_frames_log_if_changed< |
| RegisterOffset::ReceiveDroppedFramesInvalidAddress>( |
| droppedFrames); |
| } |
| } |
| |
| void received_frames_log() |
| { |
| auto count = mmio_register<RegisterOffset::ReceivedFramesCount>(); |
| ConditionalDebug<true, "Ethernet driver">::log("Received frames: {}", |
| count); |
| } |
| }; |
| |
| using EthernetDevice = KunyanEthernet; |
| |
| static_assert(EthernetAdaptor<EthernetDevice>); |