Zephyr AXI Ethernet Driver Implementation
This page gives an overview of the AXI Ethernet Zephyr driver which is available as part of the zephyr-amd repo.
Overview
The Xilinx AXI 1G/2.5G Ethernet Subsystem driver (eth_xilinx_axienet.c) provides network connectivity for Xilinx FPGA-based systems. The driver implements the Zephyr Ethernet API and integrates with the AXI DMA controller for high-performance packet transfer.
xlnx,axi-ethernet-1.00.aArchitecture Overview
Component Integration
┌─────────────────────────────────────────────────────────┐
│ Zephyr Network Stack (L2/L3) │
└────────────────┬────────────────────────────────────────┘
│ ethernet_api
┌────────────────▼────────────────────────────────────────┐
│ eth_xilinx_axienet.c (This Driver) │
│ ┌──────────────┐ ┌────────────────┐ ┌─────────────┐ │
│ │ TX Path │ │ RX Path │ │ Control │ │
│ │ (DMA-based) │ │ (DMA-based) │ │ & Config │ │
│ └──────┬───────┘ └────────┬───────┘ └──────┬──────┘ │
└─────────┼──────────────────┼──────────────────┼────────┘
│ │ │
┌─────────▼──────────────────▼──────────────────▼────────┐
│ Xilinx AXI DMA Driver (dma_xilinx_axi_dma.h) │
└────────────────┬───────────────────────────────────────┘
│ AXI Stream Interface
┌────────────────▼───────────────────────────────────────┐
│ AXI Ethernet Controller (Hardware) │
│ ┌─────────────┐ │
│ │ PHY Layer │ │
└─────────────────────┴─────────────┴─────────────────────┘
Data Structures
Device Data (struct xilinx_axienet_data)
Runtime state maintained per Ethernet instance:
struct xilinx_axienet_data {
struct xilinx_axienet_buffer tx_buffer[CONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_TX];
struct xilinx_axienet_buffer rx_buffer[CONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_RX];
size_t rx_populated_buffer_index; // Next buffer to populate for RX
size_t rx_completed_buffer_index; // Next buffer to process after RX completion
size_t tx_populated_buffer_index; // Next buffer to populate for TX
size_t tx_completed_buffer_index; // Next buffer to free after TX completion
struct net_if *interface; // Network interface handle
uint8_t mac_addr[NET_ETH_ADDR_LEN]; // Device MAC address
bool dma_is_configured_rx; // RX DMA initialization flag
bool dma_is_configured_tx; // TX DMA initialization flag
};
Device Configuration (struct xilinx_axienet_config)
Compile-time configuration per device instance:
struct xilinx_axienet_config {
void (*config_func)(const struct xilinx_axienet_data *dev);
const struct device *dma; // AXI DMA device reference
const struct device *phy; // PHY device reference
mem_addr_t reg; // Base register address
int irq_num; // Interrupt number
bool have_irq; // Interrupt support flag
bool have_rx_csum_offload; // RX checksum offload capability
bool have_tx_csum_offload; // TX checksum offload capability
bool have_random_mac; // Generate random MAC address
};
Buffer Structure (struct xilinx_axienet_buffer)
Cache-aligned DMA buffers:
struct xilinx_axienet_buffer {
uint8_t buffer[XILINX_AXIENET_ETH_BUFFER_SIZE];
} __aligned(XILINX_AXIENET_ETH_ALIGN);
CONFIG_DCACHE_LINE_SIZE (if D-cache enabled) or sizeof(void*) otherwiseNET_ETH_MAX_FRAME_SIZE rounded up to alignment boundaryInitialization Flow
#. Device Probe (xilinx_axienet_probe)
Called during system initialization:
Disable RX - Clear
XILINX_AXIENET_RECEIVER_CONFIGURATION_WORD_1_REG_RX_EN_MASKClear pending interrupts - Write to
XILINX_AXIENET_INTERRUPT_STATUS_OFFSETConfigure interrupts - Set
XILINX_AXIENET_INTERRUPT_ENABLE_RXREJ_MASKandXILINX_AXIENET_INTERRUPT_ENABLE_OVR_MASKEnable flow control - Set
XILINX_AXIENET_RECEIVER_CONFIGURATION_FLOW_CONTROL_EN_MASKSet MAC address - Call
xilinx_axienet_set_mac_address()(random or from DT)Pre-populate RX buffers - Call
setup_dma_rx_transfer()for(CONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_RX - 1)buffersEnable RX - Set
XILINX_AXIENET_RECEIVER_CONFIGURATION_WORD_1_REG_RX_EN_MASKEnable TX - Set
XILINX_AXIENET_TX_CONTROL_TX_EN_MASKConfigure IRQ handler - Call device-specific
config_func()
#. Interface Initialization (xilinx_axienet_iface_init)
Called when the network interface is created:
Store interface handle in
data->interfaceCall
ethernet_init(iface)to register with L2Set link address through
net_if_set_link_addr()Mark carrier off through
net_eth_carrier_off()Register PHY callback through
phy_link_callback_set()withphy_link_state_changed
Transmit (TX) Path
API Entry Point: xilinx_axienet_send()
static int xilinx_axienet_send(const struct device *dev, struct net_pkt *pkt)
Flow:
Get current TX buffer using
data->tx_populated_buffer_indexCopy packet data using
net_pkt_read()intodata->tx_buffer[current_descriptor].bufferCall
setup_dma_tx_transfer()to initiate DMA transfer
DMA Configuration: setup_dma_tx_transfer()
First Transfer (Initial Configuration):
struct dma_block_config head_block = {
.source_address = (uintptr_t)data->tx_buffer[current_descriptor].buffer,
.dest_address = 0x0, // AXI Stream (no memory destination)
.block_size = buffer_len,
.source_addr_adj = DMA_ADDR_ADJ_INCREMENT,
.dest_addr_adj = DMA_ADDR_ADJ_INCREMENT
};
struct dma_config dma_conf = {
.channel_direction = MEMORY_TO_PERIPHERAL,
.complete_callback_en = 1,
.dma_callback = xilinx_axienet_tx_callback,
.linked_channel = XILINX_AXI_DMA_LINKED_CHANNEL_*_CSUM_OFFLOAD
};
dma_config(config->dma, XILINX_AXI_DMA_TX_CHANNEL_NUM, &dma_conf);
Subsequent Transfers (Fast Path):
dma_reload(config->dma, XILINX_AXI_DMA_TX_CHANNEL_NUM,
(uintptr_t)data->tx_buffer[current_descriptor].buffer,
0x0, buffer_len);
Start Transfer:
dma_start(config->dma, XILINX_AXI_DMA_TX_CHANNEL_NUM);
Buffer Management:
Increment
data->tx_populated_buffer_index(moduloCONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_TX)Check for buffer exhaustion:
next_descriptor == data->tx_completed_buffer_index→ return-ENOSPC
TX Completion: xilinx_axienet_tx_callback()
DMA driver calls this on transmission complete:
Increment
data->tx_completed_buffer_index(moduloCONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_TX)Update error statistics if
status < 0througheth_stats_update_errors_tx()
Receive (RX) Path
DMA Configuration: setup_dma_rx_transfer()
Pre-populates RX buffers for incoming packets.
First Transfer (Initial Configuration):
struct dma_block_config head_block = {
.source_address = 0x0, // AXI Stream (no memory source)
.dest_address = (uintptr_t)data->rx_buffer[current_descriptor].buffer,
.block_size = sizeof(data->rx_buffer[current_descriptor].buffer),
.source_addr_adj = DMA_ADDR_ADJ_INCREMENT,
.dest_addr_adj = DMA_ADDR_ADJ_INCREMENT
};
struct dma_config dma_conf = {
.channel_direction = PERIPHERAL_TO_MEMORY,
.complete_callback_en = 1,
.dma_callback = xilinx_axienet_rx_callback,
.linked_channel = XILINX_AXI_DMA_LINKED_CHANNEL_*_CSUM_OFFLOAD
};
dma_config(config->dma, XILINX_AXI_DMA_RX_CHANNEL_NUM, &dma_conf);
Subsequent Transfers (Fast Path):
dma_reload(config->dma, XILINX_AXI_DMA_RX_CHANNEL_NUM, 0x0,
(uintptr_t)data->rx_buffer[current_descriptor].buffer,
sizeof(data->rx_buffer[current_descriptor].buffer));
RX Completion: xilinx_axienet_rx_callback()
DMA driver calls this when packet is received:
Flow:
Validate interface state - Check
net_if_is_up(data->interface)Handle errors - If
status < 0, updateeth_stats_update_errors_rx()and skip to setup new transferIncrement buffer index -
data->rx_completed_buffer_index = next_descriptorGet packet size - Call
dma_xilinx_axi_dma_last_received_frame_length(dma)Allocate net packet -
net_pkt_rx_alloc_with_buffer(data->interface, packet_size, ...)Copy data -
net_pkt_write(pkt, data->rx_buffer[current_descriptor].buffer, packet_size)Deliver to stack -
net_recv_data(data->interface, pkt)Setup next transfer - Call
setup_dma_rx_transfer()to replenish buffer
Buffer Management:
Increment
data->rx_completed_buffer_index(moduloCONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_RX)Check for buffer exhaustion:
next_descriptor == data->rx_completed_buffer_index→ return-ENOSPC
Interrupt Handling
IRQ Handler: xilinx_axienet_isr()
Handles Ethernet controller interrupts (not DMA interrupts):
Monitored Events:
RX FIFO Overrun -
XILINX_AXIENET_INTERRUPT_PENDING_RXFIFOOVR_MASKAction: Log warning, call
eth_stats_update_errors_rx()
RX Frame Rejected -
XILINX_AXIENET_INTERRUPT_PENDING_RXRJECT_MASKAction: Log warning, call
eth_stats_update_errors_rx()
Interrupt Clearance:
status = xilinx_axienet_read_register(config, XILINX_AXIENET_INTERRUPT_PENDING_OFFSET);
xilinx_axienet_write_register(config, XILINX_AXIENET_INTERRUPT_STATUS_OFFSET, status);
PHY Integration
Link State Callback: phy_link_state_changed()
Called by PHY driver when link state changes:
if (state->is_up) {
net_eth_carrier_on(data->interface);
} else {
net_eth_carrier_off(data->interface);
}
PHY Access: xilinx_axienet_get_phy()
Returns PHY device handle for upper layers:
return config->phy;
Configuration Management
Capabilities: xilinx_axienet_caps()
Reports hardware capabilities to network stack:
Base Capabilities:
ETHERNET_LINK_10BASEETHERNET_LINK_100BASEETHERNET_LINK_1000BASE
Conditional Capabilities:
ETHERNET_HW_RX_CHKSUM_OFFLOAD- Ifconfig->have_rx_csum_offloadETHERNET_HW_TX_CHKSUM_OFFLOAD- Ifconfig->have_tx_csum_offload
Get Configuration: xilinx_axienet_get_config()
Supported Configuration Types:
ETHERNET_CONFIG_TYPE_RX_CHECKSUM_SUPPORT:
config->chksum_support = ETHERNET_CHECKSUM_SUPPORT_IPV4_HEADER |
ETHERNET_CHECKSUM_SUPPORT_TCP |
ETHERNET_CHECKSUM_SUPPORT_UDP |
ETHERNET_CHECKSUM_SUPPORT_IPV6_HEADER;
ETHERNET_CONFIG_TYPE_TX_CHECKSUM_SUPPORT:
config->chksum_support = ETHERNET_CHECKSUM_SUPPORT_IPV4_HEADER |
ETHERNET_CHECKSUM_SUPPORT_TCP |
ETHERNET_CHECKSUM_SUPPORT_UDP |
ETHERNET_CHECKSUM_SUPPORT_IPV6_HEADER;
Set Configuration: xilinx_axienet_set_config()
Supported Configuration Types:
ETHERNET_CONFIG_TYPE_MAC_ADDRESS:
memcpy(data->mac_addr, config->mac_address.addr, sizeof(data->mac_addr));
xilinx_axienet_set_mac_address(dev_config, data);
net_if_set_link_addr(data->interface, data->mac_addr, ...);
MAC Address Programming: xilinx_axienet_set_mac_address()
Writes MAC address to hardware registers:
xilinx_axienet_write_register(config, XILINX_AXIENET_UNICAST_ADDRESS_WORD_0_OFFSET,
(data->mac_addr[0]) | (data->mac_addr[1] << 8) |
(data->mac_addr[2] << 16) | (data->mac_addr[3] << 24));
xilinx_axienet_write_register(config, XILINX_AXIENET_UNICAST_ADDRESS_WORD_1_OFFSET,
(data->mac_addr[4]) | (data->mac_addr[5] << 8));
MAC Address Source:
From device tree (
local-mac-addressproperty)Random generation using
gen_random_mac()with Xilinx OUI (00:0A:35:xx:xx:xx)
Register Access
Helper Functions
Write Register:
static void xilinx_axienet_write_register(const struct xilinx_axienet_config *config,
mem_addr_t reg_offset, uint32_t value)
{
sys_write32(value, config->reg + reg_offset);
}
Read Register:
static uint32_t xilinx_axienet_read_register(const struct xilinx_axienet_config *config,
mem_addr_t reg_offset)
{
return sys_read32(config->reg + reg_offset);
}
Key Registers Used
Offset |
Name |
Purpose |
|---|---|---|
|
|
Clear interrupts (write-1-to-clear) |
|
|
Read pending interrupt flags |
|
|
Enable/disable interrupts |
|
|
RX configuration |
|
|
RX enable control |
|
|
TX enable control |
|
|
Flow control configuration |
|
|
MAC address bytes 0-3 |
|
|
MAC address bytes 4-5 |
Ethernet API Implementation
API Structure: xilinx_axienet_api
static const struct ethernet_api xilinx_axienet_api = {
.iface_api.init = xilinx_axienet_iface_init,
.get_capabilities = xilinx_axienet_caps,
.get_config = xilinx_axienet_get_config,
.set_config = xilinx_axienet_set_config,
.get_phy = xilinx_axienet_get_phy,
.send = xilinx_axienet_send,
};
Device Tree Binding
Instantiation Macro: XILINX_AXIENET_INIT(inst)
Creates device instances from the device tree:
Configuration Extraction:
.dma = DEVICE_DT_GET(DT_INST_PHANDLE(inst, axistream_connected))
.phy = DEVICE_DT_GET(DT_INST_PHANDLE(inst, phy_handle))
.reg = DT_REG_ADDR(DT_INST_PARENT(inst))
.have_tx_csum_offload = DT_INST_PROP_OR(inst, xlnx_txcsum, 0x0) == 0x2
.have_rx_csum_offload = DT_INST_PROP_OR(inst, xlnx_rxcsum, 0x0) == 0x2
.have_random_mac = DT_INST_PROP(inst, zephyr_random_mac_address)
Device Registration:
ETH_NET_DEVICE_DT_INST_DEFINE(inst, xilinx_axienet_probe, NULL,
&data_##inst, &config_##inst,
CONFIG_ETH_INIT_PRIORITY,
&xilinx_axienet_api, NET_ETH_MTU);
Configuration Options (Kconfig)
CONFIG_ETH_XILINX_AXIENET
Type: Boolean
Default:
y(if DT enabled)Dependencies:
DT_HAS_XLNX_AXI_ETHERNET_1_00_A_ENABLED,DMA_XILINX_AXI_DMA
CONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_TX
Type: Integer
Default:
DMA_XILINX_AXI_DMA_SG_DESCRIPTOR_NUM_TXRange:
2toDMA_XILINX_AXI_DMA_SG_DESCRIPTOR_NUM_TXPurpose: Number of concurrent TX buffers (ring buffer depth)
CONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_RX
Type: Integer
Default:
DMA_XILINX_AXI_DMA_SG_DESCRIPTOR_NUM_RXRange:
2toDMA_XILINX_AXI_DMA_SG_DESCRIPTOR_NUM_RXPurpose: Number of concurrent RX buffers (ring buffer depth)
Buffer Management Strategy
Ring Buffer Architecture
Both the TX and RX use circular buffer management with two pointers:
TX Buffers:
tx_populated_buffer_index- Next buffer to fill with outgoing datatx_completed_buffer_index- Next buffer freed by DMA completion
RX Buffers:
rx_populated_buffer_index- Next buffer submitted to DMA for receptionrx_completed_buffer_index- Next buffer to process after DMA fills it
Full Condition Detection
Buffers are considered full when:
next_descriptor == data->xx_completed_buffer_index
This reserves one buffer slot to distinguish the full from the empty condition.
Cache Coherency
Buffers are cache-line aligned (XILINX_AXIENET_ETH_ALIGN) to enable
selective cache invalidation on the RX, proper cache flushing on the TX and
avoiding false sharing between buffers
DMA Integration
Channel Assignment
TX Channel:
XILINX_AXI_DMA_TX_CHANNEL_NUMRX Channel:
XILINX_AXI_DMA_RX_CHANNEL_NUM
Checksum Offload Configuration
With Offload:
dma_conf.linked_channel = XILINX_AXI_DMA_LINKED_CHANNEL_FULL_CSUM_OFFLOAD;
Without Offload:
dma_conf.linked_channel = XILINX_AXI_DMA_LINKED_CHANNEL_NO_CSUM_OFFLOAD;
DMA API Usage
dma_config(dma, channel, &dma_conf)dma_reload(dma, channel, src, dst, size) (faster)dma_start(dma, channel)dma_xilinx_axi_dma_last_received_frame_length(dma)Error Handling
RX Errors
DMA Error: Log error, update
eth_stats_update_errors_rx(), replenish bufferFIFO Overrun: ISR logs warning, updates error stats
Frame Rejected: ISR logs warning, updates error stats
Buffer Allocation Failure: Log error, discard packet, replenish buffer
Packet Write Failure: Log error, free packet, replenish buffer
Stack Delivery Failure: Log error, free packet, replenish buffer
TX Errors
DMA Error: Log error, update
eth_stats_update_errors_tx()Buffer Exhaustion: Return
-ENOSPCto callerPacket Read Failure: Return
-EIOto caller
Recovery Strategy
All RX errors trigger automatic buffer replenishment through
setup_dma_rx_transfer() to make sure continuous reception capability.
Performance Considerations
Fast Path Optimizations
DMA Reload API - After first transfer, use
dma_reload()instead of fulldma_config()Pre-populated RX Buffers -
(CONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_RX - 1)buffers ready before first packetRing Buffer Management - Modulo arithmetic for constant-time index updates
Cache-line Alignment - Minimize cache operations on buffer access
Bottleneck Analysis
Potential Bottlenecks:
Buffer copying in
net_pkt_read()/net_pkt_write()(no zero-copy)Limited by
CONFIG_ETH_XILINX_AXIENET_BUFFER_NUM_RX/TXdepthSingle-packet DMA transfers (no scatter-gather chaining in this implementation)
Known Limitations
No VLAN support - Tagged frames not handled
No PTP/timestamping - IEEE 1588 not implemented
No multicast filtering - All multicast accepted or rejected
No promiscuous mode - Cannot monitor all traffic
No jumbo frames - Limited to
NET_ETH_MAX_FRAME_SIZENo hardware statistics - Relies on software counters only
Single unicast address - No MAC address table
Future Enhancement Opportunities
These potential enhancements are based on hardware capabilities not yet used:
Implement VLAN tagging/filtering using appropriate registers
Add PTP support with timestamp registers
Expose hardware statistics counters
Implement multicast address table management
Add promiscuous mode configuration
Support jumbo frame configuration
Implement partial checksum offload modes
Add scatter-gather DMA descriptor chains for efficiency