feat: add GENET v5 Ethernet driver
This commit is contained in:
1 parent
3310b3d6de
commit
1d00c1c016
10 files changed
+1080
-11
No files matched your search
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 99
|
||||
#define MONTAUK_BUILD_NUMBER 103
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Drivers/Bluetooth/Bluetooth.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Net/Genet.hpp>
|
||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Drivers/Storage/Ahci.hpp>
|
||||
@@ -122,6 +123,13 @@ namespace montauk::abi {
|
||||
if (Drivers::Net::E1000E::IsInitialized()) {
|
||||
add(5, "Intel E1000E", "Gigabit Ethernet (82574L)");
|
||||
}
|
||||
if (Drivers::Net::Genet::IsInitialized()) {
|
||||
uint32_t speed = Drivers::Net::Genet::GetLinkSpeed();
|
||||
add(5, "Broadcom GENET", speed == 1000 ? "Gigabit Ethernet (1000 Mbit/s)"
|
||||
: speed == 100 ? "Gigabit Ethernet (100 Mbit/s)"
|
||||
: speed == 10 ? "Gigabit Ethernet (10 Mbit/s)"
|
||||
: "Gigabit Ethernet (no link)");
|
||||
}
|
||||
if (Drivers::Net::Wifi::IsPresent()) {
|
||||
add(5, "Intel Wi-Fi", Drivers::Net::Wifi::IsLinkUp()
|
||||
? "802.11 wireless (connected)"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <Net/NetIf.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Net/Genet.hpp>
|
||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||
|
||||
#include "Syscall.hpp"
|
||||
@@ -157,6 +158,10 @@ namespace montauk::abi {
|
||||
out->rxPackets = Drivers::Net::E1000E::GetRxPacketCount();
|
||||
out->txPackets = Drivers::Net::E1000E::GetTxPacketCount();
|
||||
CopyNetStatusDriver(out->driver, "Intel e1000e");
|
||||
} else if (Drivers::Net::Genet::IsInitialized()) {
|
||||
out->rxPackets = Drivers::Net::Genet::GetRxPacketCount();
|
||||
out->txPackets = Drivers::Net::Genet::GetTxPacketCount();
|
||||
CopyNetStatusDriver(out->driver, "Broadcom GENET");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <Drivers/Graphics/DgpuPower.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Net/Genet.hpp>
|
||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||
#include <Drivers/USB/Xhci.hpp>
|
||||
#include <Drivers/Storage/Ahci.hpp>
|
||||
@@ -356,10 +357,103 @@ namespace Drivers {
|
||||
Storage::Sdhci::ProbePlatform(base, info.Path);
|
||||
}
|
||||
|
||||
// A device property (_DSD: { UUID, Package { Package { key, value },
|
||||
// ... } }) by key.
|
||||
static bool FindDsdProperty(uint32_t nodeIndex, const char* key, Hal::AML::Object& value) {
|
||||
auto& interp = Hal::AML::GetInterpreter();
|
||||
Hal::AML::Object dsd, properties;
|
||||
if (!interp.EvaluateChild(nodeIndex, "_DSD", nullptr, 0, dsd) ||
|
||||
dsd.Type != Hal::AML::ObjectType::Package ||
|
||||
!interp.GetPackageElement(dsd, 1, properties) ||
|
||||
properties.Type != Hal::AML::ObjectType::Package) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int count = interp.GetPackageCount(properties);
|
||||
for (int i = 0; i < count; i++) {
|
||||
Hal::AML::Object pair, name;
|
||||
if (!interp.GetPackageElement(properties, i, pair) || pair.Type != Hal::AML::ObjectType::Package ||
|
||||
!interp.GetPackageElement(pair, 0, name) || name.Type != Hal::AML::ObjectType::String) {
|
||||
continue;
|
||||
}
|
||||
int c = 0;
|
||||
while (key[c] != '\0' && name.String.Data[c] == key[c]) c++;
|
||||
if (key[c] != '\0' || name.String.Data[c] != '\0') continue;
|
||||
return interp.GetPackageElement(pair, 1, value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool StringEquals(const char* a, const char* b) {
|
||||
while (*a != '\0' && *a == *b) { a++; b++; }
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
// The Raspberry Pi 4/400's GENET v5 Ethernet MAC (BCM6E4E). The first
|
||||
// of its two interrupts carries the default queue.
|
||||
static void ProbePlatformGenet() {
|
||||
if (Net::Genet::IsInitialized()) return;
|
||||
|
||||
Hal::AcpiDevices::DeviceInfo info{};
|
||||
if (!Hal::AcpiDevices::FindDeviceByHid("BCM6E4E", info)) return;
|
||||
|
||||
Hal::AML::ResourceList resources;
|
||||
uint64_t base = 0, length = 0;
|
||||
if (!Hal::AcpiDevices::EvaluateCrs(info.NodeIndex, resources) ||
|
||||
!resources.GetMemory(base, length) || base == 0) {
|
||||
Kt::KernelLogStream(Kt::WARNING, "Drivers") << "GENET " << info.Path
|
||||
<< ": no register block in _CRS";
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t gsi = 0;
|
||||
bool level = true, activeLow = false;
|
||||
if (auto* irq = resources.FindFirst(Hal::AML::ResourceType::ExtendedIrq)) {
|
||||
gsi = irq->ExtendedIrq.Interrupt;
|
||||
level = (irq->ExtendedIrq.Flags & 1) == 0;
|
||||
activeLow = (irq->ExtendedIrq.Flags & 2) != 0;
|
||||
}
|
||||
|
||||
bool coherent = false;
|
||||
Hal::AML::Object cca;
|
||||
if (Hal::AML::GetInterpreter().EvaluateChild(info.NodeIndex, "_CCA", nullptr, 0, cca) &&
|
||||
cca.Type == Hal::AML::ObjectType::Integer) {
|
||||
coherent = cca.Integer != 0;
|
||||
}
|
||||
|
||||
// The Pi 4's PHY delays the receive clock and expects the MAC to
|
||||
// delay transmit: "rgmii-rxid", also when the table says nothing.
|
||||
auto phyMode = Net::Genet::PhyMode::RgmiiRxid;
|
||||
Hal::AML::Object mode;
|
||||
if (FindDsdProperty(info.NodeIndex, "phy-mode", mode) && mode.Type == Hal::AML::ObjectType::String) {
|
||||
const char* m = mode.String.Data;
|
||||
if (StringEquals(m, "rgmii")) phyMode = Net::Genet::PhyMode::Rgmii;
|
||||
else if (StringEquals(m, "rgmii-id")) phyMode = Net::Genet::PhyMode::RgmiiId;
|
||||
else if (StringEquals(m, "rgmii-txid")) phyMode = Net::Genet::PhyMode::RgmiiTxid;
|
||||
else if (!StringEquals(m, "rgmii-rxid")) {
|
||||
Kt::KernelLogStream(Kt::WARNING, "Drivers") << "GENET " << info.Path
|
||||
<< ": unsupported phy-mode " << m << ", trying rgmii-rxid";
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t burst = 0;
|
||||
Hal::AML::Object burstObj;
|
||||
if (FindDsdProperty(info.NodeIndex, "brcm,max-dma-burst-size", burstObj) &&
|
||||
burstObj.Type == Hal::AML::ObjectType::Integer) {
|
||||
burst = (uint32_t)burstObj.Integer;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::INFO, "Drivers") << "GENET platform device " << info.Path
|
||||
<< " at 0x" << base::hex << base << ", GSI " << base::dec << (uint64_t)gsi;
|
||||
WarnIfRamBeyondDma(info.Path);
|
||||
Net::Genet::ProbePlatform(base, length, gsi, level, activeLow, coherent, phyMode, burst);
|
||||
}
|
||||
|
||||
void ProbeNormal() {
|
||||
Pci::ProbeAll(g_driverTable, g_driverTableCount, Pci::ProbePhase::Normal);
|
||||
ProbePlatformXhci();
|
||||
ProbePlatformSdhci();
|
||||
ProbePlatformGenet();
|
||||
Bluetooth::ProbePlatformAdapter();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,713 @@
|
||||
/*
|
||||
* Genet.cpp
|
||||
* Broadcom GENET v5 Ethernet driver (Raspberry Pi 4 / 400)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Genet.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/Interrupts.hpp>
|
||||
#include <ACPI/GsiRouting.hpp>
|
||||
#include <Drivers/Platform/RpiFirmware.hpp>
|
||||
#include <Memory/Dma.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <atomic>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Net::Genet {
|
||||
|
||||
// ====================================================================
|
||||
// State
|
||||
// ====================================================================
|
||||
|
||||
static bool g_initialized = false;
|
||||
static bool g_coherent = false;
|
||||
static PhyMode g_phyMode = PhyMode::RgmiiRxid;
|
||||
static volatile uint8_t* g_regs = nullptr;
|
||||
static uint8_t g_mac[6] = {};
|
||||
static uint32_t g_phyAddr = 1;
|
||||
|
||||
// Packet buffers, BUF_LENGTH each, two to a page. Slot n of a ring
|
||||
// always uses buffer n.
|
||||
static uint8_t* g_rxBufs[RX_DESCS] = {};
|
||||
static uint8_t* g_txBufs[TX_DESCS] = {};
|
||||
|
||||
// Software copies of the 16-bit ring counters: the RX consumer index we
|
||||
// last wrote and the TX producer index. Slot = counter % ring size.
|
||||
static std::atomic<uint32_t> g_rxCons{0};
|
||||
static uint32_t g_txProd = 0;
|
||||
static kcp::Spinlock g_txLock;
|
||||
|
||||
static std::atomic<bool> g_rxWorkPending{false};
|
||||
static std::atomic<bool> g_processing{false};
|
||||
|
||||
// The PHY is polled: an external RGMII PHY raises no MAC interrupt on
|
||||
// link changes, and its own interrupt pin is not wired to anything
|
||||
// ACPI describes.
|
||||
static constexpr uint64_t LINK_POLL_MS = 1000;
|
||||
static std::atomic<uint64_t> g_nextLinkPollMs{0};
|
||||
static std::atomic<bool> g_linkUp{false};
|
||||
static uint32_t g_linkSpeed = 0;
|
||||
static bool g_linkFullDuplex = false;
|
||||
|
||||
static uint64_t g_rxPacketCount = 0;
|
||||
static uint64_t g_txPacketCount = 0;
|
||||
static uint64_t g_rxErrorCount = 0;
|
||||
|
||||
static RxCallback g_rxCallback = nullptr;
|
||||
|
||||
// ====================================================================
|
||||
// Register access
|
||||
// ====================================================================
|
||||
|
||||
static uint32_t Read(uint32_t reg) {
|
||||
return *(volatile uint32_t*)(g_regs + reg);
|
||||
}
|
||||
|
||||
static void Write(uint32_t reg, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_regs + reg) = value;
|
||||
}
|
||||
|
||||
static void SetBits(uint32_t reg, uint32_t bits) { Write(reg, Read(reg) | bits); }
|
||||
static void ClearBits(uint32_t reg, uint32_t bits) { Write(reg, Read(reg) & ~bits); }
|
||||
|
||||
static uint32_t DescReg(uint32_t base, uint32_t slot, uint32_t word) {
|
||||
return base + slot * DESC_SIZE + word * 4;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// MDIO (the UniMAC's clause 22 master)
|
||||
// ====================================================================
|
||||
|
||||
// Standard PHY registers and bits.
|
||||
static constexpr uint32_t MII_BMCR = 0x00;
|
||||
static constexpr uint32_t MII_BMSR = 0x01;
|
||||
static constexpr uint32_t MII_PHYSID1 = 0x02;
|
||||
static constexpr uint32_t MII_PHYSID2 = 0x03;
|
||||
static constexpr uint32_t MII_ADVERTISE = 0x04;
|
||||
static constexpr uint32_t MII_LPA = 0x05;
|
||||
static constexpr uint32_t MII_CTRL1000 = 0x09;
|
||||
static constexpr uint32_t MII_STAT1000 = 0x0A;
|
||||
|
||||
static constexpr uint16_t BMCR_SPEED1000 = 1u << 6;
|
||||
static constexpr uint16_t BMCR_FULLDPLX = 1u << 8;
|
||||
static constexpr uint16_t BMCR_ANRESTART = 1u << 9;
|
||||
static constexpr uint16_t BMCR_ANENABLE = 1u << 12;
|
||||
static constexpr uint16_t BMCR_SPEED100 = 1u << 13;
|
||||
static constexpr uint16_t BMCR_RESET = 1u << 15;
|
||||
static constexpr uint16_t BMSR_LSTATUS = 1u << 2;
|
||||
static constexpr uint16_t BMSR_ANEGCOMPLETE = 1u << 5;
|
||||
static constexpr uint16_t ADVERTISE_CSMA = 0x0001;
|
||||
static constexpr uint16_t ADVERTISE_10HALF = 1u << 5;
|
||||
static constexpr uint16_t ADVERTISE_10FULL = 1u << 6;
|
||||
static constexpr uint16_t ADVERTISE_100HALF = 1u << 7;
|
||||
static constexpr uint16_t ADVERTISE_100FULL = 1u << 8;
|
||||
static constexpr uint16_t ADVERTISE_1000HALF = 1u << 8; // MII_CTRL1000
|
||||
static constexpr uint16_t ADVERTISE_1000FULL = 1u << 9; // MII_CTRL1000
|
||||
static constexpr uint16_t LPA_1000HALF = 1u << 10; // MII_STAT1000
|
||||
static constexpr uint16_t LPA_1000FULL = 1u << 11; // MII_STAT1000
|
||||
|
||||
static bool MdioRun(uint32_t command) {
|
||||
Write(MDIO_CMD, command);
|
||||
Write(MDIO_CMD, command | MDIO_START_BUSY);
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (Read(MDIO_CMD) & MDIO_START_BUSY) {
|
||||
if (Timekeeping::GetMilliseconds() - start > 20) return false;
|
||||
Arch::Pause();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool MdioRead(uint32_t phy, uint32_t reg, uint16_t& value) {
|
||||
if (!MdioRun(MDIO_RD | (phy << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT))) return false;
|
||||
uint32_t result = Read(MDIO_CMD);
|
||||
if (result & MDIO_READ_FAIL) return false;
|
||||
value = (uint16_t)result;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool MdioWrite(uint32_t phy, uint32_t reg, uint16_t value) {
|
||||
return MdioRun(MDIO_WR | (phy << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT) | value);
|
||||
}
|
||||
|
||||
static uint16_t PhyRead(uint32_t reg) {
|
||||
uint16_t value = 0;
|
||||
if (!MdioRead(g_phyAddr, reg, value)) return 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool PhyPresent(uint32_t addr, uint32_t& id) {
|
||||
uint16_t id1 = 0, id2 = 0;
|
||||
if (!MdioRead(addr, MII_PHYSID1, id1) || !MdioRead(addr, MII_PHYSID2, id2)) return false;
|
||||
if ((id1 == 0xFFFF && id2 == 0xFFFF) || (id1 == 0 && id2 == 0)) return false;
|
||||
id = ((uint32_t)id1 << 16) | id2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The board puts the PHY at address 1; scan the bus if it is not there.
|
||||
static bool FindPhy(uint32_t& id) {
|
||||
if (PhyPresent(1, id)) { g_phyAddr = 1; return true; }
|
||||
for (uint32_t addr = 0; addr < 32; addr++) {
|
||||
if (addr != 1 && PhyPresent(addr, id)) { g_phyAddr = addr; return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Link
|
||||
// ====================================================================
|
||||
|
||||
// BMSR latches link failures: the first read reports any loss since the
|
||||
// last one, the second the current state.
|
||||
static bool PhyLinkUp() {
|
||||
(void)PhyRead(MII_BMSR);
|
||||
uint16_t bmsr = PhyRead(MII_BMSR);
|
||||
if (!(bmsr & BMSR_LSTATUS)) return false;
|
||||
uint16_t bmcr = PhyRead(MII_BMCR);
|
||||
return !(bmcr & BMCR_ANENABLE) || (bmsr & BMSR_ANEGCOMPLETE);
|
||||
}
|
||||
|
||||
// The highest mode both ends advertised (or the forced mode).
|
||||
static void ResolveLink(uint32_t& speed, bool& fullDuplex) {
|
||||
uint16_t bmcr = PhyRead(MII_BMCR);
|
||||
if (!(bmcr & BMCR_ANENABLE)) {
|
||||
speed = (bmcr & BMCR_SPEED1000) ? 1000 : (bmcr & BMCR_SPEED100) ? 100 : 10;
|
||||
fullDuplex = (bmcr & BMCR_FULLDPLX) != 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t ctrl1000 = PhyRead(MII_CTRL1000);
|
||||
uint16_t stat1000 = PhyRead(MII_STAT1000);
|
||||
if ((ctrl1000 & ADVERTISE_1000FULL) && (stat1000 & LPA_1000FULL)) {
|
||||
speed = 1000; fullDuplex = true; return;
|
||||
}
|
||||
if ((ctrl1000 & ADVERTISE_1000HALF) && (stat1000 & LPA_1000HALF)) {
|
||||
speed = 1000; fullDuplex = false; return;
|
||||
}
|
||||
|
||||
uint16_t common = PhyRead(MII_ADVERTISE) & PhyRead(MII_LPA);
|
||||
if (common & ADVERTISE_100FULL) { speed = 100; fullDuplex = true; }
|
||||
else if (common & ADVERTISE_100HALF) { speed = 100; fullDuplex = false; }
|
||||
else if (common & ADVERTISE_10FULL) { speed = 10; fullDuplex = true; }
|
||||
else { speed = 10; fullDuplex = false; }
|
||||
}
|
||||
|
||||
// Point the RGMII block and the UniMAC at the negotiated mode and start
|
||||
// both directions.
|
||||
static void ConfigureMac(uint32_t speed, bool fullDuplex) {
|
||||
uint32_t oob = Read(EXT_RGMII_OOB_CTRL);
|
||||
oob &= ~OOB_DISABLE;
|
||||
oob |= RGMII_LINK | RGMII_MODE_EN;
|
||||
if (g_phyMode == PhyMode::Rgmii || g_phyMode == PhyMode::RgmiiId) oob |= ID_MODE_DIS;
|
||||
else oob &= ~ID_MODE_DIS;
|
||||
Write(EXT_RGMII_OOB_CTRL, oob);
|
||||
|
||||
uint32_t cmd = (speed == 1000 ? CMD_SPEED_1000 : speed == 100 ? CMD_SPEED_100 : CMD_SPEED_10)
|
||||
<< CMD_SPEED_SHIFT;
|
||||
if (!fullDuplex) cmd |= CMD_HD_EN;
|
||||
Write(UMAC_CMD, cmd);
|
||||
Write(UMAC_CMD, cmd | CMD_TX_EN | CMD_RX_EN);
|
||||
}
|
||||
|
||||
static void PollLink() {
|
||||
bool up = PhyLinkUp();
|
||||
bool wasUp = g_linkUp.load(std::memory_order_relaxed);
|
||||
|
||||
if (up) {
|
||||
uint32_t speed = 0;
|
||||
bool fullDuplex = false;
|
||||
ResolveLink(speed, fullDuplex);
|
||||
if (wasUp && speed == g_linkSpeed && fullDuplex == g_linkFullDuplex) return;
|
||||
|
||||
ConfigureMac(speed, fullDuplex);
|
||||
g_linkSpeed = speed;
|
||||
g_linkFullDuplex = fullDuplex;
|
||||
g_linkUp.store(true, std::memory_order_release);
|
||||
KernelLogStream(OK, "GENET") << "Link up: " << base::dec << (uint64_t)speed
|
||||
<< " Mbit/s " << (fullDuplex ? "full" : "half") << " duplex";
|
||||
} else if (wasUp) {
|
||||
g_linkUp.store(false, std::memory_order_release);
|
||||
g_linkSpeed = 0;
|
||||
ClearBits(EXT_RGMII_OOB_CTRL, RGMII_LINK);
|
||||
KernelLogStream(INFO, "GENET") << "Link down";
|
||||
}
|
||||
}
|
||||
|
||||
// Leave a link the firmware already negotiated alone (renegotiating
|
||||
// costs seconds); otherwise reset the PHY and advertise 10/100 and
|
||||
// 1000 full duplex (the MAC has no 1000 half duplex).
|
||||
static void StartPhy() {
|
||||
if (PhyLinkUp()) {
|
||||
KernelLogStream(INFO, "GENET") << "PHY already has a link, keeping it";
|
||||
return;
|
||||
}
|
||||
|
||||
MdioWrite(g_phyAddr, MII_BMCR, BMCR_RESET);
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (PhyRead(MII_BMCR) & BMCR_RESET) {
|
||||
if (Timekeeping::GetMilliseconds() - start > 500) {
|
||||
KernelLogStream(WARNING, "GENET") << "PHY reset timed out";
|
||||
break;
|
||||
}
|
||||
Timekeeping::BusyWaitUs(100);
|
||||
}
|
||||
|
||||
MdioWrite(g_phyAddr, MII_ADVERTISE, ADVERTISE_CSMA | ADVERTISE_10HALF | ADVERTISE_10FULL |
|
||||
ADVERTISE_100HALF | ADVERTISE_100FULL);
|
||||
uint16_t ctrl1000 = PhyRead(MII_CTRL1000);
|
||||
ctrl1000 &= ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL);
|
||||
MdioWrite(g_phyAddr, MII_CTRL1000, ctrl1000 | ADVERTISE_1000FULL);
|
||||
MdioWrite(g_phyAddr, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// MAC bring-up
|
||||
// ====================================================================
|
||||
|
||||
static bool WaitDmaDisabled(uint32_t engineRegs) {
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (!(Read(engineRegs + DMA_STATUS) & DMA_DISABLED)) {
|
||||
if (Timekeeping::GetMilliseconds() - start > 5) return false;
|
||||
Arch::Pause();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop both DMA engines (the firmware's network stack may have left
|
||||
// them running) and drop whatever the buffers still hold.
|
||||
static void StopDma() {
|
||||
Write(TDMA_REGS + DMA_CTRL, 0);
|
||||
Write(RDMA_REGS + DMA_CTRL, 0);
|
||||
if (!WaitDmaDisabled(TDMA_REGS)) KernelLogStream(WARNING, "GENET") << "TX DMA did not stop";
|
||||
if (!WaitDmaDisabled(RDMA_REGS)) KernelLogStream(WARNING, "GENET") << "RX DMA did not stop";
|
||||
|
||||
Write(UMAC_TX_FLUSH, 1);
|
||||
Timekeeping::BusyWaitUs(10);
|
||||
Write(UMAC_TX_FLUSH, 0);
|
||||
|
||||
SetBits(SYS_RBUF_FLUSH_CTRL, 1u << 0);
|
||||
Timekeeping::BusyWaitUs(10);
|
||||
ClearBits(SYS_RBUF_FLUSH_CTRL, 1u << 0);
|
||||
}
|
||||
|
||||
// Reset the UniMAC with RGMII loopback on, so its receive clock stays
|
||||
// stable through the reset, and leave it disabled until the link is up.
|
||||
static void ResetUmac() {
|
||||
SetBits(SYS_RBUF_FLUSH_CTRL, 1u << 1);
|
||||
Timekeeping::BusyWaitUs(10);
|
||||
ClearBits(SYS_RBUF_FLUSH_CTRL, 1u << 1);
|
||||
Timekeeping::BusyWaitUs(10);
|
||||
Write(SYS_RBUF_FLUSH_CTRL, 0);
|
||||
Timekeeping::BusyWaitUs(10);
|
||||
|
||||
Write(UMAC_CMD, 0);
|
||||
Write(UMAC_CMD, CMD_SW_RESET | CMD_LCL_LOOP_EN);
|
||||
Timekeeping::BusyWaitUs(2);
|
||||
Write(UMAC_CMD, 0);
|
||||
|
||||
Write(UMAC_MIB_CTRL, MIB_RESET_RX | MIB_RESET_TX | MIB_RESET_RUNT);
|
||||
Write(UMAC_MIB_CTRL, 0);
|
||||
|
||||
Write(UMAC_MAX_FRAME_LEN, MAX_FRAME_LEN);
|
||||
|
||||
// No status blocks in front of frames; received frames start two
|
||||
// bytes into their buffer, so the IP header lands 4-byte aligned.
|
||||
uint32_t rbuf = Read(RBUF_CTRL);
|
||||
rbuf &= ~RBUF_64B_EN;
|
||||
rbuf |= RBUF_ALIGN_2B;
|
||||
Write(RBUF_CTRL, rbuf);
|
||||
ClearBits(TBUF_CTRL, TBUF_64B_EN);
|
||||
Write(RBUF_TBUF_SIZE_CTRL, 1);
|
||||
}
|
||||
|
||||
static void ReadFirmwareMac(uint8_t mac[6]) {
|
||||
uint32_t mac0 = Read(UMAC_MAC0);
|
||||
uint32_t mac1 = Read(UMAC_MAC1);
|
||||
mac[0] = (uint8_t)(mac0 >> 24);
|
||||
mac[1] = (uint8_t)(mac0 >> 16);
|
||||
mac[2] = (uint8_t)(mac0 >> 8);
|
||||
mac[3] = (uint8_t)mac0;
|
||||
mac[4] = (uint8_t)(mac1 >> 8);
|
||||
mac[5] = (uint8_t)mac1;
|
||||
}
|
||||
|
||||
static bool MacUsable(const uint8_t mac[6]) {
|
||||
bool allZero = true, allOnes = true;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (mac[i] != 0x00) allZero = false;
|
||||
if (mac[i] != 0xFF) allOnes = false;
|
||||
}
|
||||
return !allZero && !allOnes && !(mac[0] & 1);
|
||||
}
|
||||
|
||||
static void SetMdfEntry(uint32_t& index, const uint8_t mac[6]) {
|
||||
Write(UMAC_MDF_ADDR + index * 4, ((uint32_t)mac[0] << 8) | mac[1]);
|
||||
Write(UMAC_MDF_ADDR + (index + 1) * 4, ((uint32_t)mac[2] << 24) | ((uint32_t)mac[3] << 16) |
|
||||
((uint32_t)mac[4] << 8) | mac[5]);
|
||||
index += 2;
|
||||
}
|
||||
|
||||
// Station address, and the destination filter: broadcast and our own
|
||||
// address. Filter n is enabled by bit (16 - n).
|
||||
static void ProgramAddress() {
|
||||
Write(UMAC_MAC0, ((uint32_t)g_mac[0] << 24) | ((uint32_t)g_mac[1] << 16) |
|
||||
((uint32_t)g_mac[2] << 8) | g_mac[3]);
|
||||
Write(UMAC_MAC1, ((uint32_t)g_mac[4] << 8) | g_mac[5]);
|
||||
|
||||
static constexpr uint8_t Broadcast[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
uint32_t index = 0;
|
||||
SetMdfEntry(index, Broadcast);
|
||||
SetMdfEntry(index, g_mac);
|
||||
uint32_t filters = index / 2;
|
||||
uint32_t enable = 0;
|
||||
for (uint32_t i = 0; i < filters; i++) enable |= 1u << (MAX_MDF_FILTER - 1 - i);
|
||||
Write(UMAC_MDF_CTRL, enable);
|
||||
}
|
||||
|
||||
static bool AllocateBuffers(uint8_t** bufs, uint32_t count) {
|
||||
constexpr uint32_t PerPage = 0x1000 / BUF_LENGTH;
|
||||
for (uint32_t i = 0; i < count; i += PerPage) {
|
||||
uint8_t* page = (uint8_t*)Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) return false;
|
||||
// The zeroes sit dirty in the cache; write them back now so no
|
||||
// eviction lands on top of a frame the MAC wrote later.
|
||||
Memory::Dma::SyncForDevice(page, 0x1000, g_coherent);
|
||||
for (uint32_t j = 0; j < PerPage && i + j < count; j++) bufs[i + j] = page + j * BUF_LENGTH;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ring 16 spans all 256 descriptors (START_ADDR/END_ADDR count 32-bit
|
||||
// words). The producer/consumer counters are zeroed, but if the MAC
|
||||
// keeps an old value the software side follows it instead: the slot is
|
||||
// always counter % 256.
|
||||
static void InitRxRing(uint32_t burst) {
|
||||
Write(RDMA_REGS + DMA_SCB_BURST_SIZE, burst);
|
||||
|
||||
Write(RDMA_RING + RDMA_PROD_INDEX, 0);
|
||||
Write(RDMA_RING + RDMA_CONS_INDEX, 0);
|
||||
Write(RDMA_RING + DMA_START_ADDR, 0);
|
||||
Write(RDMA_RING + RDMA_READ_PTR, 0);
|
||||
Write(RDMA_RING + RDMA_WRITE_PTR, 0);
|
||||
Write(RDMA_RING + DMA_END_ADDR, RX_DESCS * DESC_SIZE / 4 - 1);
|
||||
Write(RDMA_RING + DMA_RING_BUF_SIZE, (RX_DESCS << 16) | BUF_LENGTH);
|
||||
Write(RDMA_RING + RDMA_XON_XOFF_THRESH, (5u << 16) | (RX_DESCS >> 4));
|
||||
Write(RDMA_RING + DMA_MBUF_DONE_THRESH, 1);
|
||||
|
||||
uint32_t cons = Read(RDMA_RING + RDMA_PROD_INDEX) & 0xFFFF;
|
||||
Write(RDMA_RING + RDMA_CONS_INDEX, cons);
|
||||
g_rxCons.store(cons, std::memory_order_relaxed);
|
||||
if (cons != 0) {
|
||||
KernelLogStream(INFO, "GENET") << "RX ring resumes at index " << base::dec << (uint64_t)cons;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < RX_DESCS; i++) {
|
||||
uint64_t phys = Memory::SubHHDM(g_rxBufs[i]);
|
||||
Write(DescReg(RX_DESC_BASE, i, 1), (uint32_t)phys);
|
||||
Write(DescReg(RX_DESC_BASE, i, 2), (uint32_t)(phys >> 32));
|
||||
Write(DescReg(RX_DESC_BASE, i, 0), (BUF_LENGTH << DMA_BUFLENGTH_SHIFT) | DMA_OWN);
|
||||
}
|
||||
|
||||
Write(RDMA_REGS + DMA_RING_CFG, 1u << DEFAULT_Q);
|
||||
}
|
||||
|
||||
static void InitTxRing(uint32_t burst) {
|
||||
Write(TDMA_REGS + DMA_SCB_BURST_SIZE, burst);
|
||||
|
||||
Write(TDMA_RING + TDMA_PROD_INDEX, 0);
|
||||
Write(TDMA_RING + TDMA_CONS_INDEX, 0);
|
||||
Write(TDMA_RING + DMA_START_ADDR, 0);
|
||||
Write(TDMA_RING + TDMA_READ_PTR, 0);
|
||||
Write(TDMA_RING + TDMA_WRITE_PTR, 0);
|
||||
Write(TDMA_RING + DMA_END_ADDR, TX_DESCS * DESC_SIZE / 4 - 1);
|
||||
Write(TDMA_RING + DMA_RING_BUF_SIZE, (TX_DESCS << 16) | BUF_LENGTH);
|
||||
Write(TDMA_RING + DMA_MBUF_DONE_THRESH, 1);
|
||||
Write(TDMA_RING + TDMA_FLOW_PERIOD, 0);
|
||||
|
||||
g_txProd = Read(TDMA_RING + TDMA_CONS_INDEX) & 0xFFFF;
|
||||
Write(TDMA_RING + TDMA_PROD_INDEX, g_txProd);
|
||||
if (g_txProd != 0) {
|
||||
KernelLogStream(INFO, "GENET") << "TX ring resumes at index " << base::dec << (uint64_t)g_txProd;
|
||||
}
|
||||
|
||||
Write(TDMA_REGS + DMA_RING_CFG, 1u << DEFAULT_Q);
|
||||
}
|
||||
|
||||
static void StartDma() {
|
||||
uint32_t ctrl = (1u << (DEFAULT_Q + DMA_RING_BUF_EN_SHIFT)) | DMA_EN;
|
||||
Write(TDMA_REGS + DMA_CTRL, ctrl);
|
||||
Write(RDMA_REGS + DMA_CTRL, ctrl);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Interrupt
|
||||
// ====================================================================
|
||||
|
||||
// Acknowledge and mask; the frames are dispatched from the bottom half,
|
||||
// which takes process-context locks.
|
||||
static void HandleInterrupt(uint8_t, bool) {
|
||||
if (!g_initialized) return;
|
||||
uint32_t status = Read(INTRL2_0 + INTRL2_CPU_STAT) & ~Read(INTRL2_0 + INTRL2_CPU_MASK_STATUS);
|
||||
if (status == 0) return;
|
||||
Write(INTRL2_0 + INTRL2_CPU_CLEAR, status);
|
||||
|
||||
if (status & UMAC_IRQ_RXDMA_DONE) {
|
||||
Write(INTRL2_0 + INTRL2_CPU_MASK_SET, UMAC_IRQ_RXDMA_DONE);
|
||||
g_rxWorkPending.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Receive
|
||||
// ====================================================================
|
||||
|
||||
static void PollRx() {
|
||||
uint32_t cons = g_rxCons.load(std::memory_order_relaxed);
|
||||
uint32_t prod = Read(RDMA_RING + RDMA_PROD_INDEX) & 0xFFFF;
|
||||
|
||||
// At most one revolution per pass; the bottom half comes back for
|
||||
// the rest.
|
||||
uint32_t processed = 0;
|
||||
while (cons != prod && processed < RX_DESCS) {
|
||||
Arch::DmaReadFence();
|
||||
uint32_t slot = cons % RX_DESCS;
|
||||
uint32_t lenStat = Read(DescReg(RX_DESC_BASE, slot, 0));
|
||||
uint32_t length = (lenStat >> DMA_BUFLENGTH_SHIFT) & DMA_BUFLENGTH_MASK;
|
||||
uint8_t* buffer = g_rxBufs[slot];
|
||||
|
||||
bool whole = (lenStat & (DMA_SOP | DMA_EOP)) == (DMA_SOP | DMA_EOP);
|
||||
if (!whole || (lenStat & DMA_RX_ERRORS) || length <= RX_BUF_OFFSET || length > BUF_LENGTH) {
|
||||
g_rxErrorCount++;
|
||||
} else {
|
||||
Memory::Dma::SyncForCpu(buffer, length, g_coherent);
|
||||
g_rxPacketCount++;
|
||||
if (g_rxCallback != nullptr) {
|
||||
g_rxCallback(buffer + RX_BUF_OFFSET, (uint16_t)(length - RX_BUF_OFFSET));
|
||||
}
|
||||
}
|
||||
|
||||
cons = (cons + 1) & 0xFFFF;
|
||||
g_rxCons.store(cons, std::memory_order_relaxed);
|
||||
Write(RDMA_RING + RDMA_CONS_INDEX, cons);
|
||||
processed++;
|
||||
if (cons == prod) prod = Read(RDMA_RING + RDMA_PROD_INDEX) & 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
static bool RxPending() {
|
||||
uint32_t prod = Read(RDMA_RING + RDMA_PROD_INDEX) & 0xFFFF;
|
||||
return prod != g_rxCons.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Public API
|
||||
// ====================================================================
|
||||
|
||||
bool ProbePlatform(uint64_t mmioPhys, uint64_t mmioSize, uint32_t gsi, bool levelTriggered,
|
||||
bool activeLow, bool dmaCoherent, PhyMode phyMode, uint32_t burst) {
|
||||
if (g_initialized) return false;
|
||||
|
||||
if (mmioSize < 0x10000) mmioSize = 0x10000;
|
||||
for (uint64_t offset = 0; offset < mmioSize; offset += 0x1000) {
|
||||
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
|
||||
}
|
||||
g_regs = (volatile uint8_t*)Memory::HHDM(mmioPhys);
|
||||
g_coherent = dmaCoherent;
|
||||
g_phyMode = phyMode;
|
||||
if (burst == 0) burst = 8;
|
||||
|
||||
// GENET v5 reports major revision 6 (v4 reports 5).
|
||||
uint32_t rev = Read(SYS_REV_CTRL);
|
||||
uint32_t major = (rev >> 24) & 0x0F;
|
||||
if (major != 6) {
|
||||
KernelLogStream(WARNING, "GENET") << "Unsupported revision 0x" << base::hex << (uint64_t)rev;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mask everything while the controller is reconfigured.
|
||||
Write(INTRL2_0 + INTRL2_CPU_MASK_SET, 0xFFFFFFFF);
|
||||
Write(INTRL2_0 + INTRL2_CPU_CLEAR, 0xFFFFFFFF);
|
||||
Write(INTRL2_1 + INTRL2_CPU_MASK_SET, 0xFFFFFFFF);
|
||||
Write(INTRL2_1 + INTRL2_CPU_CLEAR, 0xFFFFFFFF);
|
||||
|
||||
// The board's address comes from OTP through the firmware; else
|
||||
// whatever the firmware's own driver programmed.
|
||||
uint8_t firmwareMac[6];
|
||||
ReadFirmwareMac(firmwareMac);
|
||||
const char* macSource = "OTP";
|
||||
if (!Platform::RpiFirmware::GetBoardMacAddress(g_mac) || !MacUsable(g_mac)) {
|
||||
if (MacUsable(firmwareMac)) {
|
||||
for (int i = 0; i < 6; i++) g_mac[i] = firmwareMac[i];
|
||||
macSource = "firmware";
|
||||
} else {
|
||||
// Locally administered, fixed so DHCP leases survive reboots.
|
||||
static constexpr uint8_t Fallback[6] = { 0x02, 0x4D, 0x54, 0x4B, 0x00, 0x01 };
|
||||
for (int i = 0; i < 6; i++) g_mac[i] = Fallback[i];
|
||||
macSource = "fallback";
|
||||
}
|
||||
}
|
||||
|
||||
Write(SYS_PORT_CTRL, PORT_MODE_EXT_GPHY);
|
||||
StopDma();
|
||||
ResetUmac();
|
||||
ProgramAddress();
|
||||
|
||||
if (!AllocateBuffers(g_rxBufs, RX_DESCS) || !AllocateBuffers(g_txBufs, TX_DESCS)) {
|
||||
KernelLogStream(ERROR, "GENET") << "Out of memory for packet buffers";
|
||||
return false;
|
||||
}
|
||||
InitRxRing(burst);
|
||||
InitTxRing(burst);
|
||||
StartDma();
|
||||
|
||||
uint32_t phyId = 0;
|
||||
if (!FindPhy(phyId)) {
|
||||
KernelLogStream(ERROR, "GENET") << "No PHY answers on MDIO";
|
||||
StopDma();
|
||||
return false;
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "GENET") << "GENET v5." << base::dec << (uint64_t)((rev >> 16) & 0x0F)
|
||||
<< " at 0x" << base::hex << mmioPhys << ", PHY 0x" << (uint64_t)phyId
|
||||
<< " at MDIO " << base::dec << (uint64_t)g_phyAddr
|
||||
<< (dmaCoherent ? ", coherent DMA" : ", non-coherent DMA");
|
||||
KernelLogStream(OK, "GENET") << "MAC " << base::hex
|
||||
<< (uint64_t)g_mac[0] << ":" << (uint64_t)g_mac[1] << ":" << (uint64_t)g_mac[2] << ":"
|
||||
<< (uint64_t)g_mac[3] << ":" << (uint64_t)g_mac[4] << ":" << (uint64_t)g_mac[5]
|
||||
<< " (" << macSource << ")";
|
||||
|
||||
// RGMII on with the board's delays; the link (RGMII_LINK, speed,
|
||||
// TX/RX enable) follows from the PHY poll.
|
||||
uint32_t oob = Read(EXT_RGMII_OOB_CTRL);
|
||||
oob &= ~(OOB_DISABLE | RGMII_LINK);
|
||||
oob |= RGMII_MODE_EN;
|
||||
if (phyMode == PhyMode::Rgmii || phyMode == PhyMode::RgmiiId) oob |= ID_MODE_DIS;
|
||||
else oob &= ~ID_MODE_DIS;
|
||||
Write(EXT_RGMII_OOB_CTRL, oob);
|
||||
|
||||
StartPhy();
|
||||
|
||||
uint8_t line = gsi != 0 ? Hal::GsiRouting::LineForGsi(gsi, levelTriggered, activeLow) : 0;
|
||||
if (line != 0) {
|
||||
Arch::RegisterIrqHandler(line, HandleInterrupt);
|
||||
Arch::EnableLegacyIrq(line);
|
||||
KernelLogStream(OK, "GENET") << "Interrupt: GSI " << base::dec << (uint64_t)gsi
|
||||
<< " on platform line " << (uint64_t)line;
|
||||
} else {
|
||||
KernelLogStream(WARNING, "GENET") << "No interrupt, receive is polled";
|
||||
}
|
||||
|
||||
g_initialized = true;
|
||||
Write(INTRL2_0 + INTRL2_CPU_MASK_CLEAR, UMAC_IRQ_RXDMA_DONE);
|
||||
|
||||
PollLink();
|
||||
g_nextLinkPollMs.store(Timekeeping::GetMilliseconds() + LINK_POLL_MS, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Frames are copied into the slot's buffer, so the caller's buffer is
|
||||
// free again on return. Fails while the ring is full or the link down.
|
||||
bool SendPacket(const uint8_t* data, uint16_t length) {
|
||||
if (!g_initialized || data == nullptr || length == 0 || length > MAX_FRAME_LEN) return false;
|
||||
if (!g_linkUp.load(std::memory_order_acquire)) return false;
|
||||
|
||||
g_txLock.Acquire();
|
||||
|
||||
uint32_t cons = Read(TDMA_RING + TDMA_CONS_INDEX) & 0xFFFF;
|
||||
if (((g_txProd - cons) & 0xFFFF) >= TX_DESCS - 1) {
|
||||
g_txLock.Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t slot = g_txProd % TX_DESCS;
|
||||
uint8_t* buffer = g_txBufs[slot];
|
||||
memcpy(buffer, data, length);
|
||||
// The MAC sends short frames as given; pad to the 60-byte minimum.
|
||||
uint32_t frameLength = length;
|
||||
if (frameLength < 60) {
|
||||
memset(buffer + frameLength, 0, 60 - frameLength);
|
||||
frameLength = 60;
|
||||
}
|
||||
Memory::Dma::SyncForDevice(buffer, frameLength, g_coherent);
|
||||
|
||||
uint64_t phys = Memory::SubHHDM(buffer);
|
||||
Write(DescReg(TX_DESC_BASE, slot, 1), (uint32_t)phys);
|
||||
Write(DescReg(TX_DESC_BASE, slot, 2), (uint32_t)(phys >> 32));
|
||||
Write(DescReg(TX_DESC_BASE, slot, 0), (frameLength << DMA_BUFLENGTH_SHIFT) |
|
||||
(DMA_TX_QTAG_MASK << DMA_TX_QTAG_SHIFT) | DMA_TX_APPEND_CRC | DMA_SOP | DMA_EOP);
|
||||
|
||||
g_txProd = (g_txProd + 1) & 0xFFFF;
|
||||
Arch::DmaWriteFence();
|
||||
Write(TDMA_RING + TDMA_PROD_INDEX, g_txProd);
|
||||
g_txPacketCount++;
|
||||
|
||||
g_txLock.Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t* GetMacAddress() {
|
||||
return g_mac;
|
||||
}
|
||||
|
||||
bool IsInitialized() {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
bool IsLinkUp() {
|
||||
return g_initialized && g_linkUp.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
uint32_t GetLinkSpeed() {
|
||||
return IsLinkUp() ? g_linkSpeed : 0;
|
||||
}
|
||||
|
||||
uint64_t GetRxPacketCount() {
|
||||
return g_rxPacketCount;
|
||||
}
|
||||
|
||||
uint64_t GetTxPacketCount() {
|
||||
return g_txPacketCount;
|
||||
}
|
||||
|
||||
void SetRxCallback(RxCallback callback) {
|
||||
g_rxCallback = callback;
|
||||
}
|
||||
|
||||
bool HasDeferredWork() {
|
||||
if (!g_initialized) return false;
|
||||
return g_rxWorkPending.load(std::memory_order_acquire) || RxPending() ||
|
||||
Timekeeping::GetMilliseconds() >= g_nextLinkPollMs.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void ProcessDeferredWork() {
|
||||
if (!g_initialized) return;
|
||||
|
||||
bool expected = false;
|
||||
if (!g_processing.compare_exchange_strong(expected, true,
|
||||
std::memory_order_acquire, std::memory_order_relaxed)) return;
|
||||
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
if (now >= g_nextLinkPollMs.load(std::memory_order_relaxed)) {
|
||||
g_nextLinkPollMs.store(now + LINK_POLL_MS, std::memory_order_relaxed);
|
||||
PollLink();
|
||||
}
|
||||
|
||||
g_rxWorkPending.store(false, std::memory_order_release);
|
||||
PollRx();
|
||||
|
||||
// A frame that arrived while masked has latched the status bit, so
|
||||
// unmasking raises the interrupt again rather than losing it.
|
||||
Write(INTRL2_0 + INTRL2_CPU_MASK_CLEAR, UMAC_IRQ_RXDMA_DONE);
|
||||
|
||||
g_processing.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Genet.hpp
|
||||
* Broadcom GENET v5 Ethernet driver (Raspberry Pi 4 / 400)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// The BCM2711's on-chip Gigabit Ethernet MAC (GENET v5, a UniMAC behind a
|
||||
// DMA engine), wired over RGMII to a BCM54213PE PHY at MDIO address 1. The
|
||||
// Pi Firmware Task Force UEFI firmware describes it as BCM6E4E
|
||||
// (\_SB.ETH0): one register block, two interrupts (the first carries the
|
||||
// default queue), _CCA = 0 (buffers do not snoop the CPU caches) and a
|
||||
// _DSD with "phy-mode" and "brcm,max-dma-burst-size".
|
||||
//
|
||||
// Only the default queue (ring 16) is used, with all 256 descriptors of
|
||||
// each direction. The descriptors live in the MAC's own SRAM, so only the
|
||||
// packet buffers need cache maintenance.
|
||||
|
||||
namespace Drivers::Net::Genet {
|
||||
|
||||
// ---- Register map (offsets from the block base) ----
|
||||
|
||||
constexpr uint32_t SYS_REV_CTRL = 0x0000;
|
||||
constexpr uint32_t SYS_PORT_CTRL = 0x0004;
|
||||
constexpr uint32_t SYS_RBUF_FLUSH_CTRL = 0x0008;
|
||||
constexpr uint32_t SYS_TBUF_FLUSH_CTRL = 0x000C;
|
||||
|
||||
constexpr uint32_t EXT_RGMII_OOB_CTRL = 0x008C;
|
||||
|
||||
constexpr uint32_t INTRL2_0 = 0x0200; // default queue, MAC events
|
||||
constexpr uint32_t INTRL2_1 = 0x0240; // priority queues (unused)
|
||||
constexpr uint32_t INTRL2_CPU_STAT = 0x00;
|
||||
constexpr uint32_t INTRL2_CPU_CLEAR = 0x08;
|
||||
constexpr uint32_t INTRL2_CPU_MASK_STATUS = 0x0C;
|
||||
constexpr uint32_t INTRL2_CPU_MASK_SET = 0x10;
|
||||
constexpr uint32_t INTRL2_CPU_MASK_CLEAR = 0x14;
|
||||
|
||||
constexpr uint32_t RBUF_CTRL = 0x0300;
|
||||
constexpr uint32_t RBUF_TBUF_SIZE_CTRL = 0x03B4;
|
||||
constexpr uint32_t TBUF_CTRL = 0x0600;
|
||||
|
||||
constexpr uint32_t UMAC_CMD = 0x0808;
|
||||
constexpr uint32_t UMAC_MAC0 = 0x080C;
|
||||
constexpr uint32_t UMAC_MAC1 = 0x0810;
|
||||
constexpr uint32_t UMAC_MAX_FRAME_LEN = 0x0814;
|
||||
constexpr uint32_t UMAC_TX_FLUSH = 0x0B34;
|
||||
constexpr uint32_t UMAC_MIB_CTRL = 0x0D80;
|
||||
constexpr uint32_t MDIO_CMD = 0x0E14;
|
||||
constexpr uint32_t UMAC_MDF_CTRL = 0x0E50;
|
||||
constexpr uint32_t UMAC_MDF_ADDR = 0x0E54;
|
||||
|
||||
// Descriptor SRAM, then the DMA registers: 17 rings of 0x40, then the
|
||||
// engine's own registers.
|
||||
constexpr uint32_t TOTAL_DESCS = 256;
|
||||
constexpr uint32_t DESC_SIZE = 12; // length/status, address lo, address hi
|
||||
constexpr uint32_t RX_DESC_BASE = 0x2000;
|
||||
constexpr uint32_t TX_DESC_BASE = 0x4000;
|
||||
constexpr uint32_t RDMA_BASE = RX_DESC_BASE + TOTAL_DESCS * DESC_SIZE;
|
||||
constexpr uint32_t TDMA_BASE = TX_DESC_BASE + TOTAL_DESCS * DESC_SIZE;
|
||||
constexpr uint32_t DEFAULT_Q = 16;
|
||||
constexpr uint32_t DMA_RING_SIZE = 0x40;
|
||||
constexpr uint32_t RDMA_RING = RDMA_BASE + DEFAULT_Q * DMA_RING_SIZE;
|
||||
constexpr uint32_t TDMA_RING = TDMA_BASE + DEFAULT_Q * DMA_RING_SIZE;
|
||||
constexpr uint32_t RDMA_REGS = RDMA_BASE + (DEFAULT_Q + 1) * DMA_RING_SIZE;
|
||||
constexpr uint32_t TDMA_REGS = TDMA_BASE + (DEFAULT_Q + 1) * DMA_RING_SIZE;
|
||||
|
||||
// Per-ring registers. RX: the MAC produces, software consumes; TX the
|
||||
// other way round. Both indices are 16-bit counters.
|
||||
constexpr uint32_t RDMA_WRITE_PTR = 0x00;
|
||||
constexpr uint32_t RDMA_PROD_INDEX = 0x08;
|
||||
constexpr uint32_t RDMA_CONS_INDEX = 0x0C;
|
||||
constexpr uint32_t RDMA_XON_XOFF_THRESH = 0x28;
|
||||
constexpr uint32_t RDMA_READ_PTR = 0x2C;
|
||||
constexpr uint32_t TDMA_READ_PTR = 0x00;
|
||||
constexpr uint32_t TDMA_CONS_INDEX = 0x08;
|
||||
constexpr uint32_t TDMA_PROD_INDEX = 0x0C;
|
||||
constexpr uint32_t TDMA_FLOW_PERIOD = 0x28;
|
||||
constexpr uint32_t TDMA_WRITE_PTR = 0x2C;
|
||||
constexpr uint32_t DMA_RING_BUF_SIZE = 0x10;
|
||||
constexpr uint32_t DMA_START_ADDR = 0x14;
|
||||
constexpr uint32_t DMA_END_ADDR = 0x1C;
|
||||
constexpr uint32_t DMA_MBUF_DONE_THRESH = 0x24;
|
||||
|
||||
// Engine registers.
|
||||
constexpr uint32_t DMA_RING_CFG = 0x00;
|
||||
constexpr uint32_t DMA_CTRL = 0x04;
|
||||
constexpr uint32_t DMA_STATUS = 0x08;
|
||||
constexpr uint32_t DMA_SCB_BURST_SIZE = 0x0C;
|
||||
|
||||
// ---- Register bits ----
|
||||
|
||||
constexpr uint32_t PORT_MODE_EXT_GPHY = 3;
|
||||
|
||||
constexpr uint32_t RGMII_LINK = 1u << 4;
|
||||
constexpr uint32_t OOB_DISABLE = 1u << 5;
|
||||
constexpr uint32_t RGMII_MODE_EN = 1u << 6;
|
||||
constexpr uint32_t ID_MODE_DIS = 1u << 16; // no MAC-side TX clock delay
|
||||
|
||||
constexpr uint32_t RBUF_64B_EN = 1u << 0; // receive status block
|
||||
constexpr uint32_t RBUF_ALIGN_2B = 1u << 1; // 2 pad bytes before each frame
|
||||
constexpr uint32_t TBUF_64B_EN = 1u << 0; // transmit status block
|
||||
|
||||
constexpr uint32_t CMD_TX_EN = 1u << 0;
|
||||
constexpr uint32_t CMD_RX_EN = 1u << 1;
|
||||
constexpr uint32_t CMD_SPEED_SHIFT = 2;
|
||||
constexpr uint32_t CMD_SPEED_10 = 0;
|
||||
constexpr uint32_t CMD_SPEED_100 = 1;
|
||||
constexpr uint32_t CMD_SPEED_1000 = 2;
|
||||
constexpr uint32_t CMD_PROMISC = 1u << 4;
|
||||
constexpr uint32_t CMD_HD_EN = 1u << 10;
|
||||
constexpr uint32_t CMD_SW_RESET = 1u << 13;
|
||||
constexpr uint32_t CMD_LCL_LOOP_EN = 1u << 15;
|
||||
|
||||
constexpr uint32_t MIB_RESET_RX = 1u << 0;
|
||||
constexpr uint32_t MIB_RESET_RUNT = 1u << 1;
|
||||
constexpr uint32_t MIB_RESET_TX = 1u << 2;
|
||||
|
||||
constexpr uint32_t MDIO_START_BUSY = 1u << 29;
|
||||
constexpr uint32_t MDIO_READ_FAIL = 1u << 28;
|
||||
constexpr uint32_t MDIO_RD = 2u << 26;
|
||||
constexpr uint32_t MDIO_WR = 1u << 26;
|
||||
constexpr uint32_t MDIO_PMD_SHIFT = 21;
|
||||
constexpr uint32_t MDIO_REG_SHIFT = 16;
|
||||
|
||||
constexpr uint32_t MAX_MDF_FILTER = 17;
|
||||
|
||||
constexpr uint32_t DMA_EN = 1u << 0;
|
||||
constexpr uint32_t DMA_RING_BUF_EN_SHIFT = 1;
|
||||
constexpr uint32_t DMA_DISABLED = 1u << 0; // DMA_STATUS
|
||||
|
||||
constexpr uint32_t UMAC_IRQ_RXDMA_DONE = 1u << 13; // ring 16 reached its done threshold
|
||||
|
||||
// Descriptor length/status word: length in 27:16, flags in 15:0.
|
||||
constexpr uint32_t DMA_BUFLENGTH_SHIFT = 16;
|
||||
constexpr uint32_t DMA_BUFLENGTH_MASK = 0x0FFF;
|
||||
constexpr uint32_t DMA_OWN = 0x8000;
|
||||
constexpr uint32_t DMA_EOP = 0x4000;
|
||||
constexpr uint32_t DMA_SOP = 0x2000;
|
||||
constexpr uint32_t DMA_TX_APPEND_CRC = 0x0040;
|
||||
constexpr uint32_t DMA_TX_QTAG_SHIFT = 7;
|
||||
constexpr uint32_t DMA_TX_QTAG_MASK = 0x3F;
|
||||
constexpr uint32_t DMA_RX_LG = 0x0010;
|
||||
constexpr uint32_t DMA_RX_NO = 0x0008;
|
||||
constexpr uint32_t DMA_RX_RXER = 0x0004;
|
||||
constexpr uint32_t DMA_RX_CRC_ERROR = 0x0002;
|
||||
constexpr uint32_t DMA_RX_OV = 0x0001;
|
||||
constexpr uint32_t DMA_RX_ERRORS = DMA_RX_LG | DMA_RX_NO | DMA_RX_RXER |
|
||||
DMA_RX_CRC_ERROR | DMA_RX_OV;
|
||||
|
||||
// ---- Driver parameters ----
|
||||
|
||||
constexpr uint32_t RX_DESCS = TOTAL_DESCS;
|
||||
constexpr uint32_t TX_DESCS = TOTAL_DESCS;
|
||||
constexpr uint32_t BUF_LENGTH = 2048;
|
||||
constexpr uint32_t RX_BUF_OFFSET = 2; // RBUF_ALIGN_2B padding
|
||||
// Payload 1500 + header 14 + VLAN 4 + Broadcom tag 6 + FCS 4 + pad 8.
|
||||
constexpr uint32_t MAX_FRAME_LEN = 1536;
|
||||
|
||||
// How the board wires RGMII clock delays (ACPI _DSD "phy-mode").
|
||||
enum class PhyMode : uint8_t {
|
||||
Rgmii, // no delays from the PHY or the MAC
|
||||
RgmiiId, // the PHY delays both clocks
|
||||
RgmiiRxid, // the PHY delays RX, the MAC delays TX (Raspberry Pi 4)
|
||||
RgmiiTxid,
|
||||
};
|
||||
|
||||
// Take over the controller at `mmioPhys`. `gsi` is the default-queue
|
||||
// interrupt (0: poll only). `burst` is the DMA burst size in 32-bit
|
||||
// words from "brcm,max-dma-burst-size" (0: 8).
|
||||
bool ProbePlatform(uint64_t mmioPhys, uint64_t mmioSize, uint32_t gsi, bool levelTriggered,
|
||||
bool activeLow, bool dmaCoherent, PhyMode phyMode, uint32_t burst);
|
||||
|
||||
bool SendPacket(const uint8_t* data, uint16_t length);
|
||||
const uint8_t* GetMacAddress();
|
||||
bool IsInitialized();
|
||||
|
||||
// Carrier as of the last PHY poll (once a second from deferred work).
|
||||
bool IsLinkUp();
|
||||
|
||||
// Negotiated speed in Mbit/s, 0 while the link is down.
|
||||
uint32_t GetLinkSpeed();
|
||||
|
||||
uint64_t GetRxPacketCount();
|
||||
uint64_t GetTxPacketCount();
|
||||
|
||||
using RxCallback = void(*)(const uint8_t* data, uint16_t length);
|
||||
void SetRxCallback(RxCallback callback);
|
||||
|
||||
// The interrupt only masks and flags; frames are dispatched and the PHY
|
||||
// polled from the idle bottom half. HasDeferredWork also looks at the
|
||||
// ring itself, so receive keeps working if the interrupt never fires.
|
||||
bool HasDeferredWork();
|
||||
void ProcessDeferredWork();
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <Net/NetConfig.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Net/Genet.hpp>
|
||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -56,6 +57,18 @@ namespace Net {
|
||||
Drivers::Net::E1000E::SetRxCallback(Ethernet::OnFrameReceived);
|
||||
}
|
||||
|
||||
// Raspberry Pi 4/400 on-board Ethernet. Genet::IsLinkUp already
|
||||
// reports no link for a controller that never initialized.
|
||||
if (Drivers::Net::Genet::IsInitialized()) {
|
||||
NetIf::Register({
|
||||
"eth0", NetIf::Kind::Ethernet,
|
||||
Drivers::Net::Genet::GetMacAddress,
|
||||
Drivers::Net::Genet::SendPacket,
|
||||
Drivers::Net::Genet::IsLinkUp,
|
||||
});
|
||||
Drivers::Net::Genet::SetRxCallback(Ethernet::OnFrameReceived);
|
||||
}
|
||||
|
||||
// Wi-Fi registers whenever the adapter exists; its link only comes up
|
||||
// once a network has been joined and keyed.
|
||||
if (Drivers::Net::Wifi::IsPresent()) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Net/Genet.hpp>
|
||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||
#include <Drivers/Audio/IntelHda.hpp>
|
||||
#include <Drivers/USB/Xhci.hpp>
|
||||
@@ -65,7 +66,8 @@ namespace Timekeeping {
|
||||
Drivers::Serial::BtUart::HasRxData();
|
||||
bool audioWork = Drivers::Audio::IntelHda::HasDeferredWork();
|
||||
bool netWork = Drivers::Net::E1000::HasDeferredWork() ||
|
||||
Drivers::Net::E1000E::HasDeferredWork();
|
||||
Drivers::Net::E1000E::HasDeferredWork() ||
|
||||
Drivers::Net::Genet::HasDeferredWork();
|
||||
// A forced trip into the idle bottom-half context is safe only when
|
||||
// this timer interrupted ring 3. If it interrupted a syscall, that
|
||||
// suspended kernel stack may own one of the process-context mutexes
|
||||
@@ -176,6 +178,8 @@ namespace Timekeeping {
|
||||
Drivers::Net::E1000::ProcessDeferredWork();
|
||||
if (Drivers::Net::E1000E::HasDeferredWork())
|
||||
Drivers::Net::E1000E::ProcessDeferredWork();
|
||||
if (Drivers::Net::Genet::HasDeferredWork())
|
||||
Drivers::Net::Genet::ProcessDeferredWork();
|
||||
|
||||
// HDA completion IRQs only acknowledge/mask. Resampling and DMA-ring
|
||||
// refill are far too expensive for hard interrupt context.
|
||||
|
||||
Reference in new issue
Block a user