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