Skip to content

Commit 3dea3b1

Browse files
committed
Kernel/Net: Support the GEM ethernet adapter in the RP1
This means we now have ethernet support on the Raspberry Pi 5! The generic Cadence Gigabit Ethernet MAC (GEM) driver only supports the IP revision built into the RP1 for now, which is 1p09 according to the datasheet. Since the RP1 datasheet doesn't provide any detailed documentation about this IP block, I referenced the AMD Zynq UltraScale+ docs, as those devices also use the Cadence GEM (but a different version).
1 parent 03f4582 commit 3dea3b1

8 files changed

Lines changed: 848 additions & 1 deletion

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2026, Sönke Holz <soenke.holz@serenityos.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <Kernel/Arch/aarch64/RPi/RP1/GEM.h>
8+
#include <Kernel/Arch/aarch64/RPi/RP1/RP1.h>
9+
#include <Kernel/Firmware/DeviceTree/DeviceTree.h>
10+
#include <Kernel/Net/Cadence/GEMRegisters.h>
11+
12+
namespace Kernel::RPi {
13+
14+
ErrorOr<NonnullRefPtr<RP1GEMNetworkAdapter>> RP1GEMNetworkAdapter::create(RP1& rp1, StringView interface_name, PhysicalAddress paddr, InterruptNumber interrupt_number)
15+
{
16+
auto registers_mapping = TRY(Memory::map_typed_writable<CadenceGEMNetworkAdapter::Registers volatile>(paddr));
17+
18+
MACAddress mac_address;
19+
20+
// Since this isn't a devicetree driver, we unfortunately have to hardcode this absolute path here
21+
// and hope that none of these node names will be different in future firmware updates.
22+
auto mac_addr_prop = DeviceTree::get().resolve_property("/axi/pcie@1000120000/rp1/ethernet@100000/local-mac-address"sv);
23+
if (mac_addr_prop.has_value() && mac_addr_prop->size() == 6) {
24+
mac_address = MACAddress { mac_addr_prop->as<Array<u8, 6>>() };
25+
} else {
26+
dmesgln("Failed to retrieve RP1 GEM MAC address from the devicetree");
27+
// TODO: We could fall back to a random MAC address here.
28+
return EINVAL;
29+
}
30+
31+
auto adapter = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) RP1GEMNetworkAdapter(rp1, interface_name, mac_address, move(registers_mapping), interrupt_number)));
32+
TRY(adapter->initialize());
33+
34+
return adapter;
35+
}
36+
37+
RP1GEMNetworkAdapter::RP1GEMNetworkAdapter(RP1& rp1, StringView interface_name, MACAddress mac_address, Memory::TypedMapping<CadenceGEMNetworkAdapter::Registers volatile> registers, InterruptNumber interrupt_number)
38+
: CadenceGEMNetworkAdapter(interface_name, mac_address, move(registers))
39+
, m_rp1(rp1)
40+
, m_interrupt_number(interrupt_number)
41+
{
42+
}
43+
44+
void RP1GEMNetworkAdapter::register_and_enable_interrupt()
45+
{
46+
// 6.2. MSIx configuration registers
47+
// "The only true edge-level interrupts in RP1 are the set of vectors assigned to USBHOST0 and USBHOST1."
48+
// So the ethernet controller has a level-triggered interrupt.
49+
m_rp1.register_interrupt_handler(m_interrupt_number, RP1::InterruptTriggerMode::Level, [this](InterruptNumber) {
50+
return handle_interrupt();
51+
});
52+
}
53+
54+
u32 RP1GEMNetworkAdapter::mdio_clock_input_frequency()
55+
{
56+
// The MDC clock divider uses clk_sys as the input clock on the RP1. clk_sys runs at 200 MHz by default.
57+
// FIXME: Ideally, we should derive clock routing from the devicetree, which would require some
58+
// generic infrastructure for devicetree clocks.
59+
return 200'000'000;
60+
}
61+
62+
}

Kernel/Arch/aarch64/RPi/RP1/GEM.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2026, Sönke Holz <soenke.holz@serenityos.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <Kernel/Interrupts/Interrupts.h>
10+
#include <Kernel/Net/Cadence/GEM.h>
11+
12+
namespace Kernel::RPi {
13+
14+
class RP1;
15+
16+
class RP1GEMNetworkAdapter final : public CadenceGEMNetworkAdapter {
17+
public:
18+
static ErrorOr<NonnullRefPtr<RP1GEMNetworkAdapter>> create(RP1&, StringView interface_name, PhysicalAddress, InterruptNumber);
19+
20+
private:
21+
RP1GEMNetworkAdapter(RP1&, StringView interface_name, MACAddress, Memory::TypedMapping<CadenceGEMNetworkAdapter::Registers volatile>, InterruptNumber);
22+
23+
virtual void register_and_enable_interrupt() override;
24+
virtual u32 mdio_clock_input_frequency() override;
25+
26+
RP1& m_rp1;
27+
InterruptNumber m_interrupt_number { 0 };
28+
};
29+
30+
}

Kernel/Arch/aarch64/RPi/RP1/RP1.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <Kernel/Arch/aarch64/RPi/RP1/Clocks.h>
88
#include <Kernel/Arch/aarch64/RPi/RP1/Fan.h>
9+
#include <Kernel/Arch/aarch64/RPi/RP1/GEM.h>
910
#include <Kernel/Arch/aarch64/RPi/RP1/GPIO.h>
1011
#include <Kernel/Arch/aarch64/RPi/RP1/PWM.h>
1112
#include <Kernel/Arch/aarch64/RPi/RP1/RP1.h>
@@ -17,6 +18,7 @@
1718
#include <Kernel/Bus/USB/USBManagement.h>
1819
#include <Kernel/Firmware/DeviceTree/DeviceTree.h>
1920
#include <Kernel/Library/Panic.h>
21+
#include <Kernel/Net/NetworkingManagement.h>
2022

2123
// https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf
2224

@@ -53,6 +55,8 @@ ErrorOr<void> RP1::initialize()
5355
enable_irq();
5456
enable_pin_based_interrupts();
5557

58+
// All interrupt numbers are taken from the devicetree.
59+
5660
// Section 2.5. Clocks
5761
auto clocks = TRY(RP1Clocks::create(*this, bar1_address.offset(0x1'8000)));
5862

@@ -64,14 +68,19 @@ ErrorOr<void> RP1::initialize()
6468

6569
if (!kernel_command_line().disable_usb()) {
6670
// Chapter 5. USB
67-
// The interrupt numbers are taken from the devicetree.
6871
auto usbhost0 = TRY(RP1xHCIController::try_to_initialize(*this, bar1_address.offset(0x20'0000), 0, 31));
6972
auto usbhost1 = TRY(RP1xHCIController::try_to_initialize(*this, bar1_address.offset(0x30'0000), 1, 36));
7073

7174
USB::USBManagement::the().add_controller(usbhost0);
7275
USB::USBManagement::the().add_controller(usbhost1);
7376
}
7477

78+
// Chapter 7. Ethernet
79+
auto gem_interface_name = TRY(NetworkingManagement::generate_interface_name_from_pci_address(device_identifier()));
80+
auto gem = TRY(RP1GEMNetworkAdapter::create(*this, gem_interface_name.representable_view(), bar1_address.offset(0x10'0000), 6));
81+
82+
NetworkingManagement::the().register_adapter(gem).release_value_but_fixme_should_propagate_errors();
83+
7584
auto cooling_fan_status = DeviceTree::get().resolve_property("/cooling_fan/status"sv);
7685
if (cooling_fan_status.has_value() && cooling_fan_status->as_string() == "okay"sv) {
7786
auto fan = TRY(RP1Fan::create(*this, gpio, pwm1));

Kernel/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ set(KERNEL_SOURCES
285285
Library/KLexicalPath.cpp
286286
Library/KString.cpp
287287
Library/UserOrKernelBuffer.cpp
288+
Net/Cadence/GEM.cpp
288289
Net/Intel/E1000NetworkAdapter.cpp
289290
Net/Realtek/RTL8168NetworkAdapter.cpp
290291
Net/VirtIO/VirtIONetworkAdapter.cpp
@@ -484,6 +485,7 @@ elseif("${SERENITY_ARCH}" STREQUAL "aarch64")
484485
Arch/aarch64/RPi/MiniUART.cpp
485486
Arch/aarch64/RPi/RP1/Clocks.cpp
486487
Arch/aarch64/RPi/RP1/Fan.cpp
488+
Arch/aarch64/RPi/RP1/GEM.cpp
487489
Arch/aarch64/RPi/RP1/GPIO.cpp
488490
Arch/aarch64/RPi/RP1/PWM.cpp
489491
Arch/aarch64/RPi/RP1/RP1.cpp

0 commit comments

Comments
 (0)