feat: add GENET v5 Ethernet driver

This commit is contained in:
daniel committed 2026-10-26 08:10:20 +01:00
1 parent 3310b3d6de
commit 1d00c1c016
10 files changed
+1080 -11

No files matched your search

+32 -3
View File
@@ -103,6 +103,7 @@ PL011 on GPIO 14/15: header pins 8 (TX) and 10 (RX), ground on pin 6, 115200
| Bluetooth (CYW43455 on the 4B, CYW43456 on the 400) | `BCM2EA6` (`\_SB.GDV0.BTH0`) on the mini UART (`BCM2836`) | `Drivers/Bluetooth/Broadcom.cpp`, `Drivers/Serial/BtUart.cpp` |
| VideoCore firmware mailbox (radio power, clocks, board MAC) | `BCM2849` (`RPIQ`) | `Drivers/Platform/RpiFirmware.cpp` |
| GPIO pin functions | `BCM2845` (`GPI0`) | `Drivers/Platform/RpiGpio.cpp` |
| Ethernet (GENET v5 MAC, BCM54213PE PHY) | `BCM6E4E` (`\_SB.ETH0`) | `Drivers/Net/Genet.cpp` |
The VL805 sits behind the BCM2711's PCIe, whose config space is not ECAM
(the root bridge's `_DSD` says `linux-ecam-quirk-id = "bcm2711"`), so the
@@ -157,9 +158,36 @@ The CYW43456 reports a chip-wide default address (`43:45:C5:00:1F:AC`);
the driver replaces it with the board's Ethernet MAC from the firmware.
`0:/config/bluetooth.toml` still overrides both (Broadcom `0xFC01`).
## Ethernet
The BCM2711's own Gigabit MAC, GENET v5 (a UniMAC behind a DMA engine),
talks RGMII to a BCM54213PE PHY at MDIO address 1. The firmware describes
it as `BCM6E4E`: a 64 KiB register block, two interrupts (the driver uses
the first, which carries the default queue), `_CCA = 0`, and a `_DSD` with
`phy-mode` (`rgmii-rxid`: the PHY delays the receive clock, the MAC the
transmit clock) and `brcm,max-dma-burst-size`.
The driver uses only the default queue (ring 16), with all 256 descriptors
of each direction. The descriptors live in the MAC's own SRAM, so only the
2 KiB packet buffers need cache maintenance. Received frames start two
bytes into their buffer (`RBUF_ALIGN_2B`); neither direction uses the
64-byte status blocks, so there is no checksum offload. The UniMAC accepts
broadcast and its own address only (the MDF filter).
The interrupt only acknowledges and masks; frames are dispatched from the
idle bottom half, which also checks the ring itself, so receive works if
the interrupt never arrives. The PHY is polled once a second (its interrupt
pin is not described); a link change reprograms the RGMII block and the
UniMAC's speed and duplex. A link the firmware already negotiated is kept,
otherwise the PHY is reset and advertises 10/100 and 1000 full duplex.
The MAC address is the board's, read from OTP through the firmware mailbox
(the one the Bluetooth driver uses too); the address the firmware's own
driver programmed is the fallback. `dhcp.elf` waits up to 10 s for a wired
link, since autonegotiation takes about 3 s after the PHY reset.
## Not supported yet
- Ethernet (BCM54213 via GENET v5, `BCM6E4E`).
- SD high-speed mode: the driver transfers by polled PIO at 25 MHz on a
4-bit bus (multi-block CMD18/CMD25 with Auto CMD12), at most 12.5 MB/s;
switching the card to 50 MHz (CMD6) would double that.
@@ -168,8 +196,9 @@ the driver replaces it with the board's Ethernet MAC from the firmware.
## Status
Bluetooth has not run yet: QEMU has no model of the radio, so it is
untested on any machine.
Bluetooth works on a Pi 400 (CYW43456), including A2DP music to
headphones. Ethernet has not run yet: QEMU has no GENET model. It is untested on a Pi 4B (CYW43455), and QEMU has no model of
the radio.
Everything else above is verified on QEMU only, including the Pi-specific path:
the firmware's own XHC0 table, retargeted at QEMU's xHCI and loaded with the
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 99
#define MONTAUK_BUILD_NUMBER 103
+8
View File
@@ -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)"
+5
View File
@@ -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");
}
}
+94
View File
@@ -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();
}
+713
View File
@@ -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);
}
}
+197
View File
@@ -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();
}
+13
View File
@@ -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()) {
+5 -1
View File
@@ -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.
+12 -6
View File
@@ -250,16 +250,22 @@ extern "C" void _start() {
montauk::service_log("MontaukOS DHCP Client");
// 0. Check if any device with an up link even exists.
// 0. Check if any device with an up link even exists. A wired PHY
// still autonegotiating at boot (a few seconds after its reset) gets
// a grace period; with no wired interface at all there is nothing to
// wait for.
montauk::abi::NetIfInfo ifs[4];
int n = montauk::net_interfaces(ifs, 4);
bool link = false;
for (int i = 0; i < n; i++) {
if (ifs[i].linkUp == true) {
link = true;
for (int attempt = 0; attempt < 40 && !link; attempt++) {
int n = montauk::net_interfaces(ifs, 4);
bool wired = false;
for (int i = 0; i < n; i++) {
if (ifs[i].linkUp) link = true;
if (ifs[i].kind == montauk::abi::NETIF_KIND_ETHERNET) wired = true;
}
if (link || !wired) break;
montauk::sleep_ms(250);
}
if (link == false) {