blob: 0e9b3a78a7349ce9c98d85b2f61fbb6e8171fbbb [file] [log] [blame]
#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>);