mirror of
https://github.com/SerenityOS/serenity.git
synced 2026-11-07 04:29:30 -10:00
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).
This commit is contained in:
1 parent
20295abc11
commit
3d41262ab2
8 files changed
+850
-1
No files matched your search
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Sönke Holz <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/GEM.h>
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/RP1.h>
|
||||
#include <Kernel/Firmware/DeviceTree/DeviceTree.h>
|
||||
#include <Kernel/Net/Cadence/GEMRegisters.h>
|
||||
|
||||
namespace Kernel::RPi {
|
||||
|
||||
ErrorOr<NonnullRefPtr<RP1GEMNetworkAdapter>> RP1GEMNetworkAdapter::create(RP1& rp1, StringView interface_name, PhysicalAddress paddr, InterruptNumber interrupt_number)
|
||||
{
|
||||
auto registers_mapping = TRY(Memory::map_typed_writable<CadenceGEMNetworkAdapter::Registers volatile>(paddr));
|
||||
|
||||
MACAddress mac_address;
|
||||
|
||||
// Since this isn't a devicetree driver, we unfortunately have to hardcode this absolute path here
|
||||
// and hope that none of these node names will be different in future firmware updates.
|
||||
auto mac_addr_prop = DeviceTree::get().resolve_property("/axi/pcie@1000120000/rp1/ethernet@100000/local-mac-address"sv);
|
||||
if (mac_addr_prop.has_value() && mac_addr_prop->size() == 6) {
|
||||
mac_address = MACAddress { mac_addr_prop->as<Array<u8, 6>>() };
|
||||
} else {
|
||||
dmesgln("Failed to retrieve RP1 GEM MAC address from the devicetree");
|
||||
// TODO: We could fall back to a random MAC address here.
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
auto adapter = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) RP1GEMNetworkAdapter(rp1, interface_name, mac_address, move(registers_mapping), interrupt_number)));
|
||||
TRY(adapter->initialize());
|
||||
|
||||
return adapter;
|
||||
}
|
||||
|
||||
RP1GEMNetworkAdapter::RP1GEMNetworkAdapter(RP1& rp1, StringView interface_name, MACAddress mac_address, Memory::TypedMapping<CadenceGEMNetworkAdapter::Registers volatile> registers, InterruptNumber interrupt_number)
|
||||
: CadenceGEMNetworkAdapter(interface_name, mac_address, move(registers))
|
||||
, m_rp1(rp1)
|
||||
, m_interrupt_number(interrupt_number)
|
||||
{
|
||||
}
|
||||
|
||||
void RP1GEMNetworkAdapter::register_and_enable_interrupt()
|
||||
{
|
||||
// 6.2. MSIx configuration registers
|
||||
// "The only true edge-level interrupts in RP1 are the set of vectors assigned to USBHOST0 and USBHOST1."
|
||||
// So the ethernet controller has a level-triggered interrupt.
|
||||
m_rp1.register_interrupt_handler(m_interrupt_number, RP1::InterruptTriggerMode::Level, [this](InterruptNumber) {
|
||||
return handle_interrupt();
|
||||
});
|
||||
}
|
||||
|
||||
u32 RP1GEMNetworkAdapter::mdio_clock_input_frequency()
|
||||
{
|
||||
// The MDC clock divider uses clk_sys as the input clock on the RP1. clk_sys runs at 200 MHz by default.
|
||||
// FIXME: Ideally, we should derive clock routing from the devicetree, which would require some
|
||||
// generic infrastructure for devicetree clocks.
|
||||
return 200'000'000;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Sönke Holz <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Kernel/Interrupts/Interrupts.h>
|
||||
#include <Kernel/Net/Cadence/GEM.h>
|
||||
|
||||
namespace Kernel::RPi {
|
||||
|
||||
class RP1;
|
||||
|
||||
class RP1GEMNetworkAdapter final : public CadenceGEMNetworkAdapter {
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<RP1GEMNetworkAdapter>> create(RP1&, StringView interface_name, PhysicalAddress, InterruptNumber);
|
||||
|
||||
private:
|
||||
RP1GEMNetworkAdapter(RP1&, StringView interface_name, MACAddress, Memory::TypedMapping<CadenceGEMNetworkAdapter::Registers volatile>, InterruptNumber);
|
||||
|
||||
virtual void register_and_enable_interrupt() override;
|
||||
virtual u32 mdio_clock_input_frequency() override;
|
||||
|
||||
RP1& m_rp1;
|
||||
InterruptNumber m_interrupt_number { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/Clocks.h>
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/Fan.h>
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/GEM.h>
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/GPIO.h>
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/PWM.h>
|
||||
#include <Kernel/Arch/aarch64/RPi/RP1/RP1.h>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <Kernel/Bus/USB/USBManagement.h>
|
||||
#include <Kernel/Firmware/DeviceTree/DeviceTree.h>
|
||||
#include <Kernel/Library/Panic.h>
|
||||
#include <Kernel/Net/NetworkingManagement.h>
|
||||
|
||||
// https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf
|
||||
|
||||
@@ -53,6 +55,8 @@ ErrorOr<void> RP1::initialize()
|
||||
enable_irq();
|
||||
enable_pin_based_interrupts();
|
||||
|
||||
// All interrupt numbers are taken from the devicetree.
|
||||
|
||||
// Section 2.5. Clocks
|
||||
auto clocks = TRY(RP1Clocks::create(*this, bar1_address.offset(0x1'8000)));
|
||||
|
||||
@@ -64,7 +68,6 @@ ErrorOr<void> RP1::initialize()
|
||||
|
||||
if (!kernel_command_line().disable_usb()) {
|
||||
// Chapter 5. USB
|
||||
// The interrupt numbers are taken from the devicetree.
|
||||
auto usbhost0 = TRY(RP1xHCIController::try_to_initialize(*this, bar1_address.offset(0x20'0000), 0, 31));
|
||||
auto usbhost1 = TRY(RP1xHCIController::try_to_initialize(*this, bar1_address.offset(0x30'0000), 1, 36));
|
||||
|
||||
@@ -72,6 +75,12 @@ ErrorOr<void> RP1::initialize()
|
||||
USB::USBManagement::the().add_controller(usbhost1);
|
||||
}
|
||||
|
||||
// Chapter 7. Ethernet
|
||||
auto gem_interface_name = TRY(NetworkingManagement::generate_interface_name_from_pci_address(device_identifier()));
|
||||
auto gem = TRY(RP1GEMNetworkAdapter::create(*this, gem_interface_name.representable_view(), bar1_address.offset(0x10'0000), 6));
|
||||
|
||||
NetworkingManagement::the().register_adapter(gem).release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
auto cooling_fan_status = DeviceTree::get().resolve_property("/cooling_fan/status"sv);
|
||||
if (cooling_fan_status.has_value() && cooling_fan_status->as_string() == "okay"sv) {
|
||||
auto fan = TRY(RP1Fan::create(*this, gpio, pwm1));
|
||||
|
||||
@@ -285,6 +285,7 @@ set(KERNEL_SOURCES
|
||||
Library/KLexicalPath.cpp
|
||||
Library/KString.cpp
|
||||
Library/UserOrKernelBuffer.cpp
|
||||
Net/Cadence/GEM.cpp
|
||||
Net/Intel/E1000NetworkAdapter.cpp
|
||||
Net/Realtek/RTL8168NetworkAdapter.cpp
|
||||
Net/VirtIO/VirtIONetworkAdapter.cpp
|
||||
@@ -484,6 +485,7 @@ elseif("${SERENITY_ARCH}" STREQUAL "aarch64")
|
||||
Arch/aarch64/RPi/MiniUART.cpp
|
||||
Arch/aarch64/RPi/RP1/Clocks.cpp
|
||||
Arch/aarch64/RPi/RP1/Fan.cpp
|
||||
Arch/aarch64/RPi/RP1/GEM.cpp
|
||||
Arch/aarch64/RPi/RP1/GPIO.cpp
|
||||
Arch/aarch64/RPi/RP1/PWM.cpp
|
||||
Arch/aarch64/RPi/RP1/RP1.cpp
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* Copyright (c) 2025-2026, Sönke Holz <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Arch/MemoryFences.h>
|
||||
#include <Kernel/Net/Cadence/GEM.h>
|
||||
#include <Kernel/Net/Cadence/GEMBufferDescriptors.h>
|
||||
#include <Kernel/Net/Cadence/GEMRegisters.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
// FIXME: This likely isn't always correct. Alternatively, we could automatically detect the correct PHY address
|
||||
// by reading some register(s) (like the PHY Identifier in register 2 and 3) for each PHY address
|
||||
// and checking if the contents are valid.
|
||||
// Or we could just hardcode the correct PHY address for each specific model.
|
||||
static constexpr size_t PHY_ID = 1;
|
||||
|
||||
CadenceGEMNetworkAdapter::~CadenceGEMNetworkAdapter()
|
||||
{
|
||||
if (m_mdio_handling_process) {
|
||||
m_mdio_handling_process->die();
|
||||
// Block until all threads exited to prevent UAF.
|
||||
ErrorOr<siginfo_t> result = siginfo_t {};
|
||||
(void)Thread::current()->block<Thread::WaitBlocker>({}, WEXITED, m_mdio_handling_process.release_nonnull(), result);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorOr<void> CadenceGEMNetworkAdapter::initialize(Badge<NetworkingManagement>)
|
||||
{
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
i32 CadenceGEMNetworkAdapter::link_speed()
|
||||
{
|
||||
return 1000; // FIXME
|
||||
}
|
||||
|
||||
bool CadenceGEMNetworkAdapter::link_full_duplex()
|
||||
{
|
||||
return true; // FIXME
|
||||
}
|
||||
|
||||
void CadenceGEMNetworkAdapter::send_raw(ReadonlyBytes data)
|
||||
{
|
||||
MutexLocker locker { m_transmit_mutex };
|
||||
|
||||
auto* descriptor = tx_descriptor_entry(m_tx_enqueue_index);
|
||||
|
||||
ErrorOr<void> maybe_error {};
|
||||
do {
|
||||
maybe_error = m_wait_queue.wait_until([&descriptor]() {
|
||||
// Inform the compiler that the contents of DMA regions might have changed,
|
||||
// even if it didn't see any writes to them.
|
||||
optimizer_fence();
|
||||
return descriptor->is_software_owned;
|
||||
});
|
||||
} while (maybe_error.is_error() && maybe_error.error().code() == EINTR);
|
||||
VERIFY(!maybe_error.is_error());
|
||||
|
||||
// Ensure that the software owned check is done before writing the descriptor and buffer data.
|
||||
full_memory_fence();
|
||||
|
||||
auto& tx_buffer = m_tx_buffers[m_tx_enqueue_index];
|
||||
m_tx_enqueue_index = (m_tx_enqueue_index + 1) % TX_DESCRIPTOR_COUNT;
|
||||
|
||||
VERIFY(data.size() <= TRANSMIT_BUFFER_SIZE);
|
||||
memcpy(tx_buffer->vaddr().as_ptr(), data.data(), data.size());
|
||||
|
||||
auto buffer_paddr = tx_buffer->physical_page(0)->paddr().get();
|
||||
|
||||
// Make sure that all unused fields are in a known state (zeroed).
|
||||
TxBufferDescriptorEntry new_descriptor = {};
|
||||
new_descriptor.is_software_owned = 1;
|
||||
new_descriptor.is_last_descriptor = descriptor->is_last_descriptor;
|
||||
new_descriptor.buffer_address_63_32 = buffer_paddr >> 32;
|
||||
new_descriptor.buffer_address_31_0 = buffer_paddr & 0xffff'ffff;
|
||||
new_descriptor.buffer_length = data.size();
|
||||
new_descriptor.last_buffer = 1; // We currently don't use scatter-gather transfers, and therefore only need one descriptor.
|
||||
|
||||
*descriptor = new_descriptor;
|
||||
|
||||
// Ensure that the buffer and buffer descriptor writes are visible before giving away ownership.
|
||||
store_memory_fence();
|
||||
|
||||
descriptor->is_software_owned = 0;
|
||||
|
||||
// Ensure that us giving away ownership is visible before requesting transmission start.
|
||||
store_memory_fence();
|
||||
|
||||
m_registers->network_control |= Registers::NetworkControl::StartTransmission;
|
||||
}
|
||||
|
||||
ErrorOr<void> CadenceGEMNetworkAdapter::initialize()
|
||||
{
|
||||
// "Initialize the Controller"
|
||||
|
||||
// "1. Clear the network control register. Write 0x0 to the gem.network_control register."
|
||||
m_registers->network_control = static_cast<Registers::NetworkControl>(0);
|
||||
|
||||
// "2. Clear the statistics registers. Write a 1 to the gem.network_control[clear_all_stats_regs]."
|
||||
m_registers->network_control |= Registers::NetworkControl::ClearStatisticsRegisters;
|
||||
|
||||
// "3. Clear the status registers. Write a 1 to the status registers. gem.receive_status = 0x0F and gem.transmit_status = 0xFF."
|
||||
m_registers->receive_status = static_cast<Registers::ReceiveStatus>(0x0f);
|
||||
m_registers->transmit_status = static_cast<Registers::TransmitStatus>(0xff);
|
||||
|
||||
// "4. Disable all interrupts. Write 0x7FF_FEFF to the gem.int_disable register."
|
||||
m_registers->interrupt_disable = static_cast<Registers::Interrupt>(0x7ff'feff);
|
||||
|
||||
// "5. Clear the buffer queues. Write 0x0 to the gem.receive_q{,1}_ptr and gem.transmit_q{,1}_ptr registers."
|
||||
m_registers->receive_buffer_queue_base_address = 0;
|
||||
m_registers->receive_buffer_queue_1_base_address = 0;
|
||||
m_registers->transmit_buffer_queue_base_address = 0;
|
||||
m_registers->transmit_buffer_queue_1_base_address = 0;
|
||||
|
||||
// This roughly follows the steps described in the "Configure the Controller" section.
|
||||
|
||||
auto mdc_divisor_field = determine_mdc_clock_divisor(mdio_clock_input_frequency());
|
||||
if (!mdc_divisor_field.has_value()) {
|
||||
dmesgln("Failed to find a suitable MDC clock divisor");
|
||||
return ENOTSUP;
|
||||
}
|
||||
|
||||
m_registers->network_configuration &= ~static_cast<Registers::NetworkConfiguration>(Registers::NETWORK_CONFIGURATION_MDC_DIVISION_MASK);
|
||||
m_registers->network_configuration |= static_cast<Registers::NetworkConfiguration>(*mdc_divisor_field << Registers::NETWORK_CONFIGURATION_MDC_DIVISION_OFFSET);
|
||||
|
||||
m_registers->network_configuration |= Registers::NetworkConfiguration::FullDuplex
|
||||
| Registers::NetworkConfiguration::GigabitModeEnable
|
||||
| Registers::NetworkConfiguration::DisableCopyOfPauseFrames;
|
||||
|
||||
m_registers->network_configuration &= ~(Registers::NetworkConfiguration::NoBroadcast
|
||||
| Registers::NetworkConfiguration::CopyAllFrames
|
||||
| Registers::NetworkConfiguration::JumboFrames
|
||||
| Registers::NetworkConfiguration::Receive1536ByteFrames);
|
||||
|
||||
m_registers->specific_address_1_bottom = (static_cast<u32>(mac_address()[0]) << 0uz)
|
||||
| (static_cast<u32>(mac_address()[1]) << 8uz)
|
||||
| (static_cast<u32>(mac_address()[2]) << 16uz)
|
||||
| (static_cast<u32>(mac_address()[3]) << 24uz);
|
||||
|
||||
m_registers->specific_address_1_top = (static_cast<u32>(mac_address()[4]) << 0uz)
|
||||
| (static_cast<u32>(mac_address()[5]) << 8uz);
|
||||
|
||||
VERIFY(RECEIVE_BUFFER_SIZE >= mtu() + sizeof(EthernetFrameHeader) + 4); // + 4 for the CRC
|
||||
static_assert((RECEIVE_BUFFER_SIZE % 64) == 0);
|
||||
auto receive_buffer_size_field = RECEIVE_BUFFER_SIZE / 64;
|
||||
|
||||
m_registers->dma_configuration &= ~static_cast<Registers::DMAConfiguration>(Registers::DMA_CONFIGURATION_RECEIVE_BUFFER_SIZE_MASK);
|
||||
m_registers->dma_configuration |= static_cast<Registers::DMAConfiguration>(receive_buffer_size_field << Registers::DMA_CONFIGURATION_RECEIVE_BUFFER_SIZE_OFFSET);
|
||||
m_registers->dma_configuration |= Registers::DMAConfiguration::AddressBusWidth64;
|
||||
|
||||
m_registers->network_control |= Registers::NetworkControl::EnableManagementPort;
|
||||
|
||||
m_mdio_handling_process = TRY(spawn_mdio_handling_task(PHY_ID));
|
||||
|
||||
TRY(initialize_rx_descriptors());
|
||||
TRY(initialize_tx_descriptors());
|
||||
|
||||
register_and_enable_interrupt();
|
||||
|
||||
m_registers->interrupt_enable |= Registers::Interrupt::ReceiveComplete
|
||||
| Registers::Interrupt::TransmitComplete;
|
||||
|
||||
m_registers->network_control |= Registers::NetworkControl::EnableTransmit
|
||||
| Registers::NetworkControl::EnableReceive;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> CadenceGEMNetworkAdapter::initialize_rx_descriptors()
|
||||
{
|
||||
m_rx_descriptor_list_region = TRY(MM.allocate_dma_buffer_pages(MUST(Memory::page_round_up(RX_DESCRIPTOR_COUNT * sizeof(RxBufferDescriptorEntry))), "Cadence GEM Rx Descriptor List"sv, Memory::Region::Access::ReadWrite));
|
||||
|
||||
for (size_t i = 0; i < RX_DESCRIPTOR_COUNT; i++) {
|
||||
auto* entry = rx_descriptor_entry(i);
|
||||
|
||||
auto buffer = TRY(MM.allocate_dma_buffer_pages(MUST(Memory::page_round_up(RECEIVE_BUFFER_SIZE)), "Cadence GEM Rx Buffer"sv, Memory::Region::Access::ReadOnly));
|
||||
auto buffer_paddr = buffer->physical_page(0)->paddr().get();
|
||||
m_rx_buffers[i] = move(buffer);
|
||||
|
||||
entry->is_software_owned = 0;
|
||||
entry->buffer_address_31_2 = buffer_paddr >> 2;
|
||||
entry->buffer_address_63_32 = buffer_paddr >> 32;
|
||||
}
|
||||
|
||||
rx_descriptor_entry(RX_DESCRIPTOR_COUNT - 1)->is_last_descriptor = 1;
|
||||
|
||||
auto rx_descriptor_list_paddr = m_rx_descriptor_list_region->physical_page(0)->paddr().get();
|
||||
|
||||
// Ensure that the initialization of the descriptors and the zeroing of the buffers is visible
|
||||
// before giving their addresses to the controller.
|
||||
store_memory_fence();
|
||||
|
||||
m_registers->receive_buffer_queue_base_address = rx_descriptor_list_paddr & 0xffff'ffff;
|
||||
m_registers->receive_buffer_queue_base_address_high = rx_descriptor_list_paddr >> 32;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> CadenceGEMNetworkAdapter::initialize_tx_descriptors()
|
||||
{
|
||||
m_tx_descriptor_list_region = TRY(MM.allocate_dma_buffer_pages(MUST(Memory::page_round_up(TX_DESCRIPTOR_COUNT * sizeof(TxBufferDescriptorEntry))), "Cadence GEM Tx Descriptor List"sv, Memory::Region::Access::ReadWrite));
|
||||
|
||||
for (size_t i = 0; i < TX_DESCRIPTOR_COUNT; i++) {
|
||||
auto* entry = tx_descriptor_entry(i);
|
||||
|
||||
auto buffer = TRY(MM.allocate_dma_buffer_pages(MUST(Memory::page_round_up(TRANSMIT_BUFFER_SIZE)), "Cadence GEM Tx Buffer"sv, Memory::Region::Access::Write));
|
||||
auto buffer_paddr = buffer->physical_page(0)->paddr().get();
|
||||
m_tx_buffers[i] = move(buffer);
|
||||
|
||||
entry->is_software_owned = 1;
|
||||
entry->buffer_address_31_0 = buffer_paddr & 0xffff'ffff;
|
||||
entry->buffer_address_63_32 = buffer_paddr >> 32;
|
||||
}
|
||||
|
||||
tx_descriptor_entry(TX_DESCRIPTOR_COUNT - 1)->is_last_descriptor = 1;
|
||||
|
||||
auto tx_descriptor_list_paddr = m_tx_descriptor_list_region->physical_page(0)->paddr().get();
|
||||
|
||||
// Ensure that the initialization of the descriptors and the zeroing of the buffers is visible
|
||||
// before giving their addresses to the controller.
|
||||
store_memory_fence();
|
||||
|
||||
m_registers->transmit_buffer_queue_base_address = tx_descriptor_list_paddr & 0xffff'ffff;
|
||||
m_registers->transmit_buffer_queue_base_address_high = tx_descriptor_list_paddr >> 32;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool CadenceGEMNetworkAdapter::handle_interrupt()
|
||||
{
|
||||
auto interrupt_status = m_registers->interrupt_status;
|
||||
|
||||
// Acknowledge the interrupt(s).
|
||||
m_registers->interrupt_status = interrupt_status;
|
||||
|
||||
if (has_flag(interrupt_status, Registers::Interrupt::ReceiveComplete)) {
|
||||
auto receive_status = m_registers->receive_status;
|
||||
|
||||
// Acknowledge the receive status.
|
||||
m_registers->receive_status = receive_status;
|
||||
|
||||
for (;;) {
|
||||
// Inform the compiler that the contents of DMA regions might have changed,
|
||||
// even if it didn't see any writes to them.
|
||||
optimizer_fence();
|
||||
|
||||
auto* descriptor = rx_descriptor_entry(m_rx_dequeue_index);
|
||||
if (!descriptor->is_software_owned)
|
||||
break;
|
||||
|
||||
// Ensure the software owned check is done before reading the descriptor and buffer data.
|
||||
load_memory_fence();
|
||||
|
||||
auto length = descriptor->received_frame_length;
|
||||
if (length > RECEIVE_BUFFER_SIZE) {
|
||||
dbgln("CadenceGEM: Controller set a bogus received frame length: {} (max is {})", length, RECEIVE_BUFFER_SIZE);
|
||||
} else {
|
||||
ReadonlyBytes buffer { m_rx_buffers[m_rx_dequeue_index]->vaddr().as_ptr(), length };
|
||||
did_receive(buffer);
|
||||
|
||||
// did_receive() copies the packet, so it's safe for us to give back ownership to the controller.
|
||||
}
|
||||
|
||||
// Ensure that the descriptor and buffer are fully read before giving back ownership.
|
||||
full_memory_fence();
|
||||
|
||||
descriptor->is_software_owned = 0;
|
||||
|
||||
m_rx_dequeue_index = (m_rx_dequeue_index + 1) % RX_DESCRIPTOR_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_flag(interrupt_status, Registers::Interrupt::TransmitComplete)) {
|
||||
auto transmit_status = m_registers->transmit_status;
|
||||
|
||||
// Acknowledge the transmit status.
|
||||
m_registers->transmit_status = transmit_status;
|
||||
|
||||
// Some transmit descriptors might now be free; notify any waiters about this.
|
||||
m_wait_queue.notify_all();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CadenceGEMNetworkAdapter::on_phy_link_status_change(MDIO::LinkStatus link_status)
|
||||
{
|
||||
m_link_up = link_status == MDIO::LinkStatus::Up;
|
||||
}
|
||||
|
||||
u16 CadenceGEMNetworkAdapter::read_phy_register(u8 phy_id, MDIO::Clause22::RegisterAddress address)
|
||||
{
|
||||
// The address and PHY ID are 5 bits.
|
||||
VERIFY((phy_id & ~0x1f) == 0);
|
||||
VERIFY((to_underlying(address) & ~0x1f) == 0);
|
||||
|
||||
// "Example: PHY Read/Write Operation"
|
||||
|
||||
// "1. Check to see that no MDIO operation is in progress. Read until gem.net_status[man_done] = 1."
|
||||
while (!has_flag(m_registers->network_status, Registers::NetworkStatus::ManagementLogicIdle))
|
||||
Processor::wait_check();
|
||||
|
||||
// "2. Write data to the PHY management register (gem.phy_management). This initiates the data shift operation over MDIO."
|
||||
m_registers->phy_maintenance = 0b10 << Registers::PHY_MAINTENANCE_WRITE_10_OFFSET
|
||||
| to_underlying(address) << Registers::PHY_MAINTENANCE_REGISTER_ADDRESS_OFFSET
|
||||
| phy_id << Registers::PHY_MAINTENANCE_PHY_ADDRESS_OFFSET
|
||||
| to_underlying(Registers::PhyMaintenanceOperation::Clause22Read) << Registers::PHY_MAINTENANCE_OPERATION_OFFSET
|
||||
| 1 << Registers::PHY_MAINTENANCE_CLAUSE_22_FRAME_OFFSET;
|
||||
|
||||
// "3. Wait for completion of operation. Read until gem.net_status[man_done] = 1."
|
||||
while (!has_flag(m_registers->network_status, Registers::NetworkStatus::ManagementLogicIdle))
|
||||
Processor::wait_check();
|
||||
|
||||
// "4. Read data bits for a read operation. The PHY register data is available in gem.phy_management[phy_write_read_data]."
|
||||
return (m_registers->phy_maintenance >> Registers::PHY_MAINTENANCE_WRITE_OR_READ_DATA_OFFSET)
|
||||
& Registers::PHY_MAINTENANCE_WRITE_OR_READ_DATA_MASK;
|
||||
}
|
||||
|
||||
void CadenceGEMNetworkAdapter::write_phy_register(u8 phy_id, MDIO::Clause22::RegisterAddress address, u16 value)
|
||||
{
|
||||
// The address and PHY ID are 5 bits.
|
||||
VERIFY((phy_id & ~0x1f) == 0);
|
||||
VERIFY((to_underlying(address) & ~0x1f) == 0);
|
||||
|
||||
// "Example: PHY Read/Write Operation"
|
||||
|
||||
// "1. Check to see that no MDIO operation is in progress. Read until gem.net_status[man_done] = 1."
|
||||
while (!has_flag(m_registers->network_status, Registers::NetworkStatus::ManagementLogicIdle))
|
||||
Processor::wait_check();
|
||||
|
||||
// "2. Write data to the PHY management register (gem.phy_management). This initiates the data shift operation over MDIO."
|
||||
m_registers->phy_maintenance = value << Registers::PHY_MAINTENANCE_WRITE_OR_READ_DATA_OFFSET
|
||||
| 0b10 << Registers::PHY_MAINTENANCE_WRITE_10_OFFSET
|
||||
| to_underlying(address) << Registers::PHY_MAINTENANCE_REGISTER_ADDRESS_OFFSET
|
||||
| phy_id << Registers::PHY_MAINTENANCE_PHY_ADDRESS_OFFSET
|
||||
| to_underlying(Registers::PhyMaintenanceOperation::Clause22Write) << Registers::PHY_MAINTENANCE_OPERATION_OFFSET
|
||||
| 1 << Registers::PHY_MAINTENANCE_CLAUSE_22_FRAME_OFFSET;
|
||||
|
||||
// "3. Wait for completion of operation. Read until gem.net_status[man_done] = 1."
|
||||
while (!has_flag(m_registers->network_status, Registers::NetworkStatus::ManagementLogicIdle))
|
||||
Processor::wait_check();
|
||||
}
|
||||
|
||||
CadenceGEMNetworkAdapter::RxBufferDescriptorEntry* CadenceGEMNetworkAdapter::rx_descriptor_entry(size_t index)
|
||||
{
|
||||
size_t offset = index * sizeof(RxBufferDescriptorEntry);
|
||||
return reinterpret_cast<RxBufferDescriptorEntry*>(m_rx_descriptor_list_region->vaddr().offset(offset).as_ptr());
|
||||
}
|
||||
|
||||
CadenceGEMNetworkAdapter::TxBufferDescriptorEntry* CadenceGEMNetworkAdapter::tx_descriptor_entry(size_t index)
|
||||
{
|
||||
size_t offset = index * sizeof(TxBufferDescriptorEntry);
|
||||
return reinterpret_cast<TxBufferDescriptorEntry*>(m_tx_descriptor_list_region->vaddr().offset(offset).as_ptr());
|
||||
}
|
||||
|
||||
Optional<u8> CadenceGEMNetworkAdapter::determine_mdc_clock_divisor(u32 input_frequency)
|
||||
{
|
||||
// IEEE 802.3, 22.2.2.13 MDC (management data clock)
|
||||
// "[T]he minimum period for MDC shall be 400 ns."
|
||||
// 1 / 400 ns = 2.5 MHz, so the frequency should be below that value.
|
||||
u32 max_mdc_frequency = 2'500'000;
|
||||
|
||||
struct Divisor {
|
||||
u8 divisor;
|
||||
u8 register_field_value;
|
||||
};
|
||||
|
||||
static constexpr auto DIVISORS = to_array<Divisor>({
|
||||
{ 8, 0b000 },
|
||||
{ 16, 0b001 },
|
||||
{ 32, 0b010 },
|
||||
{ 48, 0b011 },
|
||||
{ 64, 0b100 },
|
||||
{ 96, 0b101 },
|
||||
{ 128, 0b110 },
|
||||
{ 224, 0b111 },
|
||||
});
|
||||
|
||||
for (auto [divisor, register_field_value] : DIVISORS) {
|
||||
if (input_frequency / divisor <= max_mdc_frequency) {
|
||||
return register_field_value;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CadenceGEMNetworkAdapter::CadenceGEMNetworkAdapter(StringView interface_name, MACAddress mac_address, Memory::TypedMapping<Registers volatile> registers)
|
||||
: NetworkAdapter(interface_name)
|
||||
, m_registers(move(registers))
|
||||
{
|
||||
set_mac_address(mac_address);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2025-2026, Sönke Holz <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// The Cadence GEM ethernet controller is used in e.g. Zynq UltraScale+ devices, for which some docs about the GEM are available:
|
||||
// - Technical reference manual: Chapter 34 (GEM Ethernet), https://docs.amd.com/v/u/en-US/ug1085-zynq-ultrascale-trm
|
||||
// - Register reference: https://docs.amd.com/r/en-US/ug1087-zynq-ultrascale-registers/GEM-Module
|
||||
|
||||
#include <Kernel/Memory/TypedMapping.h>
|
||||
#include <Kernel/Net/MDIO.h>
|
||||
#include <Kernel/Net/NetworkAdapter.h>
|
||||
#include <Kernel/Tasks/Process.h>
|
||||
#include <Kernel/Tasks/WaitQueue.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class CadenceGEMNetworkAdapter
|
||||
: public NetworkAdapter
|
||||
, public MDIO::Clause22::Interface {
|
||||
public:
|
||||
virtual ~CadenceGEMNetworkAdapter();
|
||||
|
||||
static ErrorOr<NonnullRefPtr<NetworkAdapter>> create(PCI::DeviceIdentifier const&);
|
||||
|
||||
virtual StringView class_name() const override { return "CadenceGEMNetworkAdapter"sv; }
|
||||
virtual Type adapter_type() const override { return Type::Ethernet; }
|
||||
|
||||
virtual ErrorOr<void> initialize(Badge<NetworkingManagement>) override;
|
||||
|
||||
virtual bool link_up() override { return m_link_up; }
|
||||
virtual i32 link_speed() override;
|
||||
virtual bool link_full_duplex() override;
|
||||
|
||||
struct Registers;
|
||||
|
||||
struct RxBufferDescriptorEntry;
|
||||
struct TxBufferDescriptorEntry;
|
||||
|
||||
protected:
|
||||
CadenceGEMNetworkAdapter(StringView interface_name, MACAddress, Memory::TypedMapping<Registers volatile>);
|
||||
|
||||
virtual void send_raw(ReadonlyBytes) override;
|
||||
|
||||
ErrorOr<void> initialize();
|
||||
ErrorOr<void> initialize_rx_descriptors();
|
||||
ErrorOr<void> initialize_tx_descriptors();
|
||||
|
||||
virtual void register_and_enable_interrupt() = 0;
|
||||
virtual u32 mdio_clock_input_frequency() = 0;
|
||||
|
||||
bool handle_interrupt();
|
||||
|
||||
// ^MDIO::Clause22::Interface
|
||||
virtual void on_phy_link_status_change(MDIO::LinkStatus) override;
|
||||
virtual u16 read_phy_register(u8 phy_id, MDIO::Clause22::RegisterAddress address) override;
|
||||
virtual void write_phy_register(u8 phy_id, MDIO::Clause22::RegisterAddress address, u16 value) override;
|
||||
|
||||
private:
|
||||
bool m_link_up { false };
|
||||
|
||||
RxBufferDescriptorEntry* rx_descriptor_entry(size_t index);
|
||||
TxBufferDescriptorEntry* tx_descriptor_entry(size_t index);
|
||||
|
||||
static Optional<u8> determine_mdc_clock_divisor(u32 input_frequency);
|
||||
|
||||
Memory::TypedMapping<Registers volatile> m_registers;
|
||||
|
||||
static constexpr size_t RECEIVE_BUFFER_SIZE = 1536;
|
||||
static constexpr size_t TRANSMIT_BUFFER_SIZE = 1536;
|
||||
|
||||
static constexpr size_t RX_DESCRIPTOR_COUNT = 128;
|
||||
static constexpr size_t TX_DESCRIPTOR_COUNT = 128;
|
||||
|
||||
Array<OwnPtr<Memory::Region>, RX_DESCRIPTOR_COUNT> m_rx_buffers;
|
||||
OwnPtr<Memory::Region> m_rx_descriptor_list_region;
|
||||
|
||||
Array<OwnPtr<Memory::Region>, TX_DESCRIPTOR_COUNT> m_tx_buffers;
|
||||
OwnPtr<Memory::Region> m_tx_descriptor_list_region;
|
||||
|
||||
size_t m_rx_dequeue_index { 0 };
|
||||
size_t m_tx_enqueue_index { 0 };
|
||||
|
||||
WaitQueue m_wait_queue;
|
||||
Mutex m_transmit_mutex;
|
||||
|
||||
RefPtr<Process> m_mdio_handling_process;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Sönke Holz <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Kernel/Net/Cadence/GEM.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
struct CadenceGEMNetworkAdapter::RxBufferDescriptorEntry {
|
||||
// Word 0
|
||||
u32 is_software_owned : 1;
|
||||
u32 is_last_descriptor : 1;
|
||||
u32 buffer_address_31_2 : 30;
|
||||
|
||||
// Word 1
|
||||
u32 received_frame_length : 12;
|
||||
u32 frame_has_bad_fcs : 1;
|
||||
u32 buffer_contains_start_of_frame : 1;
|
||||
u32 buffer_contains_end_of_frame : 1;
|
||||
u32 canonical_format_indicator_bit : 1;
|
||||
u32 vlan_priority : 3;
|
||||
u32 priority_tag_detected : 1;
|
||||
u32 vlan_tag_detected : 1;
|
||||
u32 type_id_register_match_or_rx_checksum_offloading_status : 2;
|
||||
u32 : 1;
|
||||
u32 specific_address_register_match : 2;
|
||||
u32 specific_address_register_match_found : 1;
|
||||
u32 io_address_match : 1;
|
||||
u32 unicast_hash_match : 1;
|
||||
u32 multicast_hash_match : 1;
|
||||
u32 global_all_ones_broadcast_address_detected : 1;
|
||||
|
||||
// Word 2
|
||||
u32 buffer_address_63_32;
|
||||
|
||||
// Word 3
|
||||
u32 _;
|
||||
};
|
||||
static_assert(AssertSize<CadenceGEMNetworkAdapter::RxBufferDescriptorEntry, 4 * sizeof(u32)>());
|
||||
|
||||
struct CadenceGEMNetworkAdapter::TxBufferDescriptorEntry {
|
||||
// Word 0
|
||||
u32 buffer_address_31_0;
|
||||
|
||||
// Word 1
|
||||
u32 buffer_length : 14;
|
||||
u32 : 1;
|
||||
u32 last_buffer : 1;
|
||||
u32 no_crc_to_be_appended_by_mac : 1;
|
||||
u32 : 3;
|
||||
u32 checksum_offload_error_status : 3;
|
||||
u32 : 3;
|
||||
u32 late_collision : 1;
|
||||
u32 transmit_frame_corruption_due_to_axi_error : 1;
|
||||
u32 : 1;
|
||||
u32 retry_limit_exceeded : 1;
|
||||
u32 is_last_descriptor : 1;
|
||||
u32 is_software_owned : 1;
|
||||
|
||||
// Word 2
|
||||
u32 buffer_address_63_32;
|
||||
|
||||
// Word 3
|
||||
u32 _;
|
||||
};
|
||||
static_assert(AssertSize<CadenceGEMNetworkAdapter::TxBufferDescriptorEntry, 4 * sizeof(u32)>());
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Sönke Holz <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Kernel/Net/Cadence/GEM.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
// https://docs.amd.com/r/en-US/ug1087-zynq-ultrascale-registers/GEM-Module
|
||||
struct CadenceGEMNetworkAdapter::Registers {
|
||||
enum class NetworkControl : u32 {
|
||||
EnableReceive = 1u << 2,
|
||||
EnableTransmit = 1u << 3,
|
||||
EnableManagementPort = 1u << 4,
|
||||
ClearStatisticsRegisters = 1u << 5,
|
||||
StartTransmission = 1u << 9,
|
||||
};
|
||||
NetworkControl network_control;
|
||||
|
||||
enum class NetworkConfiguration : u32 {
|
||||
Speed = 1u << 0,
|
||||
FullDuplex = 1u << 1,
|
||||
DiscardNonVLANFrames = 1u << 2,
|
||||
JumboFrames = 1u << 3,
|
||||
CopyAllFrames = 1u << 4,
|
||||
NoBroadcast = 1u << 5,
|
||||
MulticastHashEnable = 1u << 6,
|
||||
UnicastHashEnable = 1u << 7,
|
||||
Receive1536ByteFrames = 1u << 8,
|
||||
ExternAddressMatchEnable = 1u << 9,
|
||||
GigabitModeEnable = 1u << 10,
|
||||
PCSSelect = 1u << 11,
|
||||
RetryTest = 1u << 12,
|
||||
PauseEnable = 1u << 13,
|
||||
|
||||
ReceiveBufferOffsetMask = (1u << 15) | (1u << 14),
|
||||
ReceiveBufferOffset0 = 0b00u << 14,
|
||||
ReceiveBufferOffset1 = 0b01u << 14,
|
||||
ReceiveBufferOffset2 = 0b10u << 14,
|
||||
ReceiveBufferOffset3 = 0b11u << 14,
|
||||
|
||||
LengthFieldErrorFrameDiscard = 1u << 16,
|
||||
FCSRemove = 1u << 17,
|
||||
|
||||
MDCClockDivisionMask = (1u << 20) | (1u << 19) | (1u << 18),
|
||||
MDCClockDivision8 = 0b000u << 18,
|
||||
MDCClockDivision16 = 0b001u << 18,
|
||||
MDCClockDivision32 = 0b010u << 18,
|
||||
MDCClockDivision48 = 0b011u << 18,
|
||||
MDCClockDivision64 = 0b100u << 18,
|
||||
MDCClockDivision96 = 0b101u << 18,
|
||||
MDCClockDivision128 = 0b110u << 18,
|
||||
MDCClockDivision224 = 0b111u << 18,
|
||||
|
||||
DataBusWidthMask = (1u << 22) | (1u << 21),
|
||||
DataBusWidth32 = 0b00u << 21,
|
||||
DataBusWidth64 = 0b01u << 21,
|
||||
|
||||
DisableCopyOfPauseFrames = 1u << 23,
|
||||
ReceiveChecksumOffloadEnable = 1u << 24,
|
||||
EnableHalfDuplexRX = 1u << 25,
|
||||
IgnoreRXFCS = 1u << 26,
|
||||
SGMIIModeEnable = 1u << 27,
|
||||
IPGStretchEnable = 1u << 28,
|
||||
ReceiveBadPreamble = 1u << 29,
|
||||
IgnoreIPGRX_ER = 1u << 30,
|
||||
UniDirectionEnable = 1u << 31,
|
||||
};
|
||||
|
||||
static constexpr size_t NETWORK_CONFIGURATION_MDC_DIVISION_MASK = 0b111 << 18;
|
||||
static constexpr size_t NETWORK_CONFIGURATION_MDC_DIVISION_OFFSET = 18;
|
||||
|
||||
NetworkConfiguration network_configuration;
|
||||
|
||||
enum class NetworkStatus : u32 {
|
||||
ManagementLogicIdle = 1u << 2,
|
||||
};
|
||||
NetworkStatus network_status;
|
||||
u32 _;
|
||||
|
||||
enum class DMAConfiguration {
|
||||
AddressBusWidth64 = 1u << 30,
|
||||
};
|
||||
|
||||
static constexpr size_t DMA_CONFIGURATION_RECEIVE_BUFFER_SIZE_MASK = 0xff << 16;
|
||||
static constexpr size_t DMA_CONFIGURATION_RECEIVE_BUFFER_SIZE_OFFSET = 16;
|
||||
static constexpr size_t DMA_CONFIGURATION_64_BIT_DMA_OFFSET = 30;
|
||||
static constexpr size_t DMA_CONFIGURATION_CHECKSUM_OFFLOAD_ENABLE_OFFSET = 11;
|
||||
static constexpr size_t DMA_CONFIGURATION_TRANSMIT_PACKET_BUFFER_MEMORY_SIZE_OFFSET = 10;
|
||||
static constexpr size_t DMA_CONFIGURATION_RECEIVE_PACKET_BUFFER_MEMORY_SIZE_OFFSET = 8;
|
||||
static constexpr size_t DMA_CONFIGURATION_SWAP_PACKET_BYTES_ENABLE_OFFSET = 7;
|
||||
static constexpr size_t DMA_CONFIGURATION_AHB_FIXED_BURST_LENGTH_OFFSET = 0;
|
||||
|
||||
DMAConfiguration dma_configuration;
|
||||
|
||||
enum class TransmitStatus : u32 {
|
||||
};
|
||||
TransmitStatus transmit_status;
|
||||
|
||||
u32 receive_buffer_queue_base_address;
|
||||
u32 transmit_buffer_queue_base_address;
|
||||
|
||||
enum class ReceiveStatus : u32 {
|
||||
};
|
||||
ReceiveStatus receive_status;
|
||||
|
||||
enum class Interrupt : u32 {
|
||||
ReceiveComplete = 1 << 1,
|
||||
TransmitComplete = 1 << 7,
|
||||
};
|
||||
|
||||
Interrupt interrupt_status;
|
||||
Interrupt interrupt_enable;
|
||||
Interrupt interrupt_disable;
|
||||
Interrupt interrupt_mask_status;
|
||||
|
||||
enum class PhyMaintenanceOperation {
|
||||
// For IEEE 802.3 Clause 22
|
||||
Clause22Write = 0b01,
|
||||
Clause22Read = 0b10,
|
||||
|
||||
// For IEEE 802.3 Clause 45
|
||||
Clause45Address = 0b00,
|
||||
Clause45Write = 0b01,
|
||||
Clause45ReadPostIncrement = 0b10,
|
||||
Clause45Read = 0b11,
|
||||
};
|
||||
static constexpr size_t PHY_MAINTENANCE_WRITE_OR_READ_DATA_OFFSET = 0;
|
||||
static constexpr size_t PHY_MAINTENANCE_WRITE_OR_READ_DATA_MASK = 0xffff;
|
||||
|
||||
static constexpr size_t PHY_MAINTENANCE_WRITE_10_OFFSET = 16;
|
||||
static constexpr size_t PHY_MAINTENANCE_REGISTER_ADDRESS_OFFSET = 18;
|
||||
static constexpr size_t PHY_MAINTENANCE_PHY_ADDRESS_OFFSET = 23;
|
||||
static constexpr size_t PHY_MAINTENANCE_OPERATION_OFFSET = 28;
|
||||
static constexpr size_t PHY_MAINTENANCE_CLAUSE_22_FRAME_OFFSET = 30;
|
||||
static constexpr size_t PHY_MAINTENANCE_WRITE_0_OFFSET = 31;
|
||||
|
||||
u32 phy_maintenance;
|
||||
|
||||
u32 receive_pause_quantum;
|
||||
u32 transmit_pause_quantum;
|
||||
|
||||
u8 _[0x80 - (0x3c + 4)];
|
||||
|
||||
u32 hash_register_bottom;
|
||||
u32 hash_register_top;
|
||||
u32 specific_address_1_bottom;
|
||||
u32 specific_address_1_top;
|
||||
u32 specific_address_2_bottom;
|
||||
u32 specific_address_2_top;
|
||||
u32 specific_address_3_bottom;
|
||||
u32 specific_address_3_top;
|
||||
u32 specific_address_4_bottom;
|
||||
u32 specific_address_4_top;
|
||||
|
||||
u8 _[0x440 - (0xa4 + 4)];
|
||||
|
||||
u32 transmit_buffer_queue_1_base_address;
|
||||
u32 _[15];
|
||||
u32 receive_buffer_queue_1_base_address;
|
||||
|
||||
u8 _[0x4c8 - (0x480 + 4)];
|
||||
|
||||
u32 transmit_buffer_queue_base_address_high;
|
||||
u32 _;
|
||||
u32 _;
|
||||
u32 receive_buffer_queue_base_address_high;
|
||||
};
|
||||
static_assert(AssertSize<CadenceGEMNetworkAdapter::Registers, 0x4d8>());
|
||||
|
||||
AK_ENUM_BITWISE_OPERATORS(CadenceGEMNetworkAdapter::Registers::NetworkControl);
|
||||
AK_ENUM_BITWISE_OPERATORS(CadenceGEMNetworkAdapter::Registers::NetworkConfiguration);
|
||||
AK_ENUM_BITWISE_OPERATORS(CadenceGEMNetworkAdapter::Registers::NetworkStatus);
|
||||
AK_ENUM_BITWISE_OPERATORS(CadenceGEMNetworkAdapter::Registers::DMAConfiguration);
|
||||
AK_ENUM_BITWISE_OPERATORS(CadenceGEMNetworkAdapter::Registers::TransmitStatus);
|
||||
AK_ENUM_BITWISE_OPERATORS(CadenceGEMNetworkAdapter::Registers::ReceiveStatus);
|
||||
AK_ENUM_BITWISE_OPERATORS(CadenceGEMNetworkAdapter::Registers::Interrupt);
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user