Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,9 @@
obj-$(CONFIG_ALIBABA_EEA) += eea.o
eea-y := eea_ring.o \
eea_net.o \
eea_pci.o \
eea_adminq.o \
eea_ethtool.o \
eea_tx.o \
eea_rx.o
@@ -0,0 +1,542 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <linux/etherdevice.h>
#include <linux/iopoll.h>
#include <linux/utsname.h>
#include <linux/version.h>
#include "eea_adminq.h"
#include "eea_net.h"
#include "eea_pci.h"
#include "eea_ring.h"
#define EEA_AQ_CMD_CFG_QUERY ((0 << 8) | 0)
#define EEA_AQ_CMD_QUEUE_CREATE ((1 << 8) | 0)
#define EEA_AQ_CMD_QUEUE_DESTROY_ALL ((1 << 8) | 1)
#define EEA_AQ_CMD_HOST_INFO ((2 << 8) | 0)
#define EEA_AQ_CMD_DEV_STATUS ((3 << 8) | 0)
#define EEA_RING_DESC_F_AQ_PHASE (BIT(15) | BIT(7))
#define EEA_QUEUE_FLAGS_HW_SPLIT_HDR BIT(0)
#define EEA_QUEUE_FLAGS_SQCQ BIT(1)
#define EEA_QUEUE_FLAGS_HWTS BIT(2)
struct eea_aq_create {
__le32 flags;
/* queue index.
* rx: 0 == qidx % 2
* tx: 1 == qidx % 2
*/
__le16 qidx;
/* the depth of the queue */
__le16 depth;
/* 0: without SPLIT HDR
* 1: 128B
* 2: 256B
* 3: 512B
*/
u8 hdr_buf_size;
u8 sq_desc_size;
u8 cq_desc_size;
u8 reserve0;
/* The vector for the irq. rx,tx share the same vector */
__le16 msix_vector;
__le16 reserve;
/* sq ring cfg. */
__le32 sq_addr_low;
__le32 sq_addr_high;
/* cq ring cfg. Just valid when flags include EEA_QUEUE_FLAGS_SQCQ. */
__le32 cq_addr_low;
__le32 cq_addr_high;
};
struct eea_aq_queue_drv_status {
__le16 qidx;
__le16 sq_head;
__le16 cq_head;
__le16 reserved;
};
#define EEA_OS_DISTRO 0
#define EEA_DRV_TYPE 0
#define EEA_OS_LINUX 1
#define EEA_SPEC_VER_MAJOR 1
#define EEA_SPEC_VER_MINOR 0
struct eea_aq_host_info_cfg {
__le16 os_type;
__le16 os_dist;
__le16 drv_type;
__le16 kern_ver_major;
__le16 kern_ver_minor;
__le16 kern_ver_sub_minor;
__le16 drv_ver_major;
__le16 drv_ver_minor;
__le16 drv_ver_sub_minor;
__le16 spec_ver_major;
__le16 spec_ver_minor;
__le16 pci_bdf;
__le32 pci_domain;
u8 os_ver_str[64];
u8 isa_str[64];
};
#define EEA_HINFO_MAX_REP_LEN 1024
#define EEA_HINFO_REP_BAD 2
struct eea_aq_host_info_rep {
u8 op_code;
u8 has_reply;
u8 reply_str[EEA_HINFO_MAX_REP_LEN];
};
static struct eea_ring *qid_to_ering(struct eea_net *enet, u32 qid)
{
struct eea_ring *ering;
if (qid % 2 == 0)
ering = enet->rx[qid / 2]->ering;
else
ering = enet->tx[qid / 2].ering;
return ering;
}
#define EEA_AQ_TIMEOUT_US (60 * 1000 * 1000)
static void eea_device_broken(struct eea_net *enet)
{
if (enet->adminq.broken)
return;
eea_device_reset(enet->edev);
enet->adminq.broken = true;
}
static int eea_adminq_submit(struct eea_net *enet, u16 cmd,
dma_addr_t req_addr, dma_addr_t res_addr,
u32 req_size, u32 res_size, u32 *reply_len)
{
struct eea_aq_cdesc *cdesc;
struct eea_aq_desc *desc;
int ret;
if (enet->adminq.broken)
return -EIO;
desc = eea_ering_aq_alloc_desc(enet->adminq.ring);
desc->classid = cmd >> 8;
desc->command = cmd & 0xff;
desc->data_addr = cpu_to_le64(req_addr);
desc->data_len = cpu_to_le32(req_size);
desc->reply_addr = cpu_to_le64(res_addr);
desc->reply_len = cpu_to_le32(res_size);
/* for update flags */
dma_wmb();
desc->flags = cpu_to_le16(enet->adminq.phase);
eea_ering_sq_commit_desc(enet->adminq.ring);
eea_ering_kick(enet->adminq.ring);
++enet->adminq.num;
if ((enet->adminq.num % enet->adminq.ring->num) == 0)
enet->adminq.phase ^= EEA_RING_DESC_F_AQ_PHASE;
ret = read_poll_timeout(eea_ering_cq_get_desc, cdesc, cdesc, 10,
EEA_AQ_TIMEOUT_US, false, enet->adminq.ring);
if (ret) {
netdev_err(enet->netdev,
"adminq exec timeout. cmd: %d reset device.\n",
cmd);
/* The device must be reset before unmapping buffers to avoid
* potential DMA writes after the memory is freed.
*/
eea_device_broken(enet);
return ret;
}
/* Returns 0 on success, or a negative error code on failure. */
ret = le32_to_cpu(cdesc->status);
eea_ering_cq_ack_desc(enet->adminq.ring, 1);
if (ret)
netdev_err(enet->netdev,
"adminq exec failed. cmd: %d ret %d\n", cmd, ret);
else
*reply_len = le32_to_cpu(cdesc->reply_len);
return ret;
}
static int eea_adminq_exec(struct eea_net *enet, u16 cmd,
void *req, u32 req_size,
void *res, u32 res_size,
u32 *reply)
{
dma_addr_t req_addr = 0, res_addr = 0;
struct device *dma;
u32 reply_len = 0;
int ret;
if (reply)
*reply = 0;
dma = enet->edev->dma_dev;
if (req) {
req_addr = dma_map_single(dma, req, req_size, DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(dma, req_addr)))
return -ENOMEM;
}
if (res) {
res_addr = dma_map_single(dma, res, res_size, DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(dma, res_addr))) {
ret = -ENOMEM;
goto err_unmap_req;
}
}
mutex_lock(&enet->adminq.lock);
ret = eea_adminq_submit(enet, cmd, req_addr, res_addr,
req_size, res_size, &reply_len);
mutex_unlock(&enet->adminq.lock);
if (res) {
dma_unmap_single(dma, res_addr, res_size, DMA_FROM_DEVICE);
if (ret)
memset(res, 0, res_size);
else if (res_size > reply_len)
memset(res + reply_len, 0, res_size - reply_len);
if (reply)
*reply = reply_len;
}
err_unmap_req:
if (req)
dma_unmap_single(dma, req_addr, req_size, DMA_TO_DEVICE);
return ret;
}
void eea_destroy_adminq(struct eea_net *enet)
{
struct eea_aq *aq;
aq = &enet->adminq;
if (aq->ring) {
eea_ering_free(aq->ring);
aq->ring = NULL;
aq->phase = 0;
}
kfree(aq->q_req_buf);
kfree(aq->q_res_buf);
aq->q_req_buf = NULL;
aq->q_res_buf = NULL;
}
int eea_create_adminq(struct eea_net *enet, u32 qid)
{
u32 db_size, q_size, num;
struct eea_ring *ering;
struct eea_aq *aq;
int err = -ENOMEM;
num = enet->edev->rx_num + enet->edev->tx_num;
aq = &enet->adminq;
ering = eea_ering_alloc(qid, 64, enet->edev, sizeof(struct eea_aq_desc),
sizeof(struct eea_aq_cdesc), "adminq");
if (!ering)
return -ENOMEM;
aq->ring = ering;
err = eea_pci_active_aq(ering, qid / 2 + 1);
if (err)
goto err;
aq->phase = BIT(7);
aq->num = 0;
q_size = sizeof(*aq->q_req_buf) * num;
db_size = sizeof(*aq->q_res_buf) * num;
aq->q_req_size = q_size;
aq->q_res_size = db_size;
err = -ENOMEM;
aq->q_req_buf = kzalloc(q_size, GFP_KERNEL);
if (!aq->q_req_buf)
goto err;
aq->q_res_buf = kzalloc(db_size, GFP_KERNEL);
if (!aq->q_res_buf)
goto err;
/* Before we set up the AQ, the device remains in an inactive state, so
* there will be no DMA operations. If the 'set up AQ' process fails, we
* can safely free the DMA-related memory.
*/
err = eea_pci_set_aq_up(enet->edev);
if (err)
goto err;
aq->broken = false;
mutex_init(&aq->lock);
return 0;
err:
eea_destroy_adminq(enet);
return err;
}
int eea_adminq_query_cfg(struct eea_net *enet, struct eea_aq_cfg *cfg)
{
return eea_adminq_exec(enet, EEA_AQ_CMD_CFG_QUERY, NULL, 0, cfg,
sizeof(*cfg), NULL);
}
static void qcfg_fill(struct eea_aq_create *qcfg, struct eea_ring *ering,
u32 flags)
{
qcfg->flags = cpu_to_le32(flags);
qcfg->qidx = cpu_to_le16(ering->index);
qcfg->depth = cpu_to_le16(ering->num);
qcfg->hdr_buf_size = flags & EEA_QUEUE_FLAGS_HW_SPLIT_HDR ? 1 : 0;
qcfg->sq_desc_size = ering->sq.desc_size;
qcfg->cq_desc_size = ering->cq.desc_size;
qcfg->msix_vector = cpu_to_le16(ering->msix_vec);
qcfg->sq_addr_low = cpu_to_le32(lower_32_bits(ering->sq.dma_addr));
qcfg->sq_addr_high = cpu_to_le32(upper_32_bits(ering->sq.dma_addr));
qcfg->cq_addr_low = cpu_to_le32(lower_32_bits(ering->cq.dma_addr));
qcfg->cq_addr_high = cpu_to_le32(upper_32_bits(ering->cq.dma_addr));
}
int eea_adminq_create_q(struct eea_net *enet, u32 num, u32 flags)
{
int i, db_size, q_size, err = -ENOMEM;
struct eea_net_cfg *cfg;
struct eea_ring *ering;
struct eea_aq *aq;
u32 reply_len;
cfg = &enet->cfg;
aq = &enet->adminq;
if (cfg->split_hdr)
flags |= EEA_QUEUE_FLAGS_HW_SPLIT_HDR;
flags |= EEA_QUEUE_FLAGS_SQCQ;
flags |= EEA_QUEUE_FLAGS_HWTS;
q_size = sizeof(*aq->q_req_buf) * num;
db_size = sizeof(*aq->q_res_buf) * num;
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
qcfg_fill(aq->q_req_buf + i, ering, flags);
}
err = eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_CREATE,
aq->q_req_buf, q_size,
aq->q_res_buf, db_size,
&reply_len);
if (err)
return err;
if (reply_len != db_size) {
eea_adminq_destroy_all_q(enet);
netdev_err(enet->netdev, "invalid reply len %u\n", reply_len);
return -EINVAL;
}
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
ering->db = eea_pci_db_addr(ering->edev,
le32_to_cpu(aq->q_res_buf[i]));
if (!ering->db) {
netdev_err(enet->netdev, "invalid db off %u\n",
le32_to_cpu(aq->q_res_buf[i]));
goto err;
}
}
return err;
err:
eea_adminq_destroy_all_q(enet);
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
ering->db = NULL;
}
return -EIO;
}
int eea_adminq_destroy_all_q(struct eea_net *enet)
{
int err;
err = eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_DESTROY_ALL, NULL, 0,
NULL, 0, NULL);
if (err) {
/* The device must be reset before unmapping buffers to avoid
* potential DMA writes after the memory is freed.
*/
mutex_lock(&enet->adminq.lock);
eea_device_broken(enet);
mutex_unlock(&enet->adminq.lock);
netdev_err(enet->netdev, "QUEUE_DESTROY fail: reset device.\n");
}
return err;
}
/* The caller must ensure that both the 'rx' and 'tx' arrays are valid. */
int eea_adminq_dev_status(struct eea_net *enet,
struct eea_aq_dev_status *dstatus)
{
struct eea_aq_queue_drv_status *drv_status;
struct __eea_aq_dev_status *dev_status;
int err, i, io_num, size, q_num;
struct eea_ring *ering;
void *rep, *req;
q_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num + 1;
io_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num;
req = kcalloc(q_num, sizeof(struct eea_aq_queue_drv_status),
GFP_KERNEL);
if (!req)
return -ENOMEM;
size = struct_size(dev_status, q_status, q_num);
rep = kzalloc(size, GFP_KERNEL);
if (!rep) {
kfree(req);
return -ENOMEM;
}
drv_status = req;
for (i = 0; i < io_num; ++i, ++drv_status) {
ering = qid_to_ering(enet, i);
drv_status->qidx = cpu_to_le16(i);
drv_status->cq_head = cpu_to_le16(ering->cq.head);
drv_status->sq_head = cpu_to_le16(ering->sq.head);
}
drv_status->qidx = cpu_to_le16(i);
drv_status->cq_head = cpu_to_le16(enet->adminq.ring->cq.head);
drv_status->sq_head = cpu_to_le16(enet->adminq.ring->sq.head);
err = eea_adminq_exec(enet, EEA_AQ_CMD_DEV_STATUS, req,
q_num * sizeof(struct eea_aq_queue_drv_status),
rep, size, NULL);
kfree(req);
if (err) {
kfree(rep);
return err;
}
dstatus->num = q_num;
dstatus->status = rep;
return 0;
}
void eea_adminq_config_host_info(struct eea_net *enet)
{
struct device *dev = enet->edev->dma_dev;
struct eea_aq_host_info_cfg *cfg;
struct eea_aq_host_info_rep *rep;
int rc = -ENOMEM;
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg)
return;
rep = kzalloc(sizeof(*rep), GFP_KERNEL);
if (!rep)
goto err_free_cfg;
cfg->os_type = cpu_to_le16(EEA_OS_LINUX);
cfg->os_dist = cpu_to_le16(EEA_OS_DISTRO);
cfg->drv_type = cpu_to_le16(EEA_DRV_TYPE);
cfg->kern_ver_major = cpu_to_le16(LINUX_VERSION_MAJOR);
cfg->kern_ver_minor = cpu_to_le16(LINUX_VERSION_PATCHLEVEL);
cfg->kern_ver_sub_minor = cpu_to_le16(LINUX_VERSION_SUBLEVEL);
cfg->drv_ver_major = cpu_to_le16(EEA_VER_MAJOR);
cfg->drv_ver_minor = cpu_to_le16(EEA_VER_MINOR);
cfg->drv_ver_sub_minor = cpu_to_le16(EEA_VER_SUB_MINOR);
cfg->spec_ver_major = cpu_to_le16(EEA_SPEC_VER_MAJOR);
cfg->spec_ver_minor = cpu_to_le16(EEA_SPEC_VER_MINOR);
cfg->pci_bdf = cpu_to_le16(eea_pci_bdf(enet->edev));
cfg->pci_domain = cpu_to_le32(eea_pci_domain_nr(enet->edev));
strscpy(cfg->os_ver_str, utsname()->release, sizeof(cfg->os_ver_str));
strscpy(cfg->isa_str, utsname()->machine, sizeof(cfg->isa_str));
rc = eea_adminq_exec(enet, EEA_AQ_CMD_HOST_INFO,
cfg, sizeof(*cfg), rep, sizeof(*rep), NULL);
if (!rc) {
if (rep->op_code == EEA_HINFO_REP_BAD)
dev_warn(dev, "The hardware-driven state validation may be abnormal.\n");
if (rep->has_reply) {
char buf[EEA_HINFO_MAX_REP_LEN] = {0};
rep->reply_str[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
string_escape_str(rep->reply_str, buf, sizeof(buf),
ESCAPE_NP, NULL);
buf[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
dev_warn(dev, "Device replied: %s\n", buf);
}
}
kfree(rep);
err_free_cfg:
kfree(cfg);
}
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#ifndef __EEA_ADMINQ_H__
#define __EEA_ADMINQ_H__
struct eea_aq_cfg {
__le32 rx_depth_max;
__le32 rx_depth_def;
__le32 tx_depth_max;
__le32 tx_depth_def;
__le32 max_tso_size;
__le32 max_tso_segs;
u8 mac[ETH_ALEN];
__le16 status;
__le16 mtu;
__le16 reserved0;
__le16 reserved1;
u8 reserved2;
u8 reserved3;
__le16 reserved4;
__le16 reserved5;
__le16 reserved6;
};
struct eea_aq_queue_status {
__le16 qidx;
#define EEA_QUEUE_STATUS_OK 0
#define EEA_QUEUE_STATUS_NEED_RESET 1
__le16 status;
};
struct __eea_aq_dev_status {
#define EEA_LINK_DOWN_STATUS 0
#define EEA_LINK_UP_STATUS 1
__le16 link_status;
__le16 reserved;
struct eea_aq_queue_status q_status[];
};
struct eea_aq_dev_status {
u32 num;
struct __eea_aq_dev_status *status;
};
struct eea_aq {
struct eea_ring *ring;
u32 num;
bool broken;
u16 phase;
/* lock for adminq exec */
struct mutex lock;
u32 q_req_size;
u32 q_res_size;
struct eea_aq_create *q_req_buf;
__le32 *q_res_buf;
};
struct eea_net;
int eea_create_adminq(struct eea_net *enet, u32 qid);
void eea_destroy_adminq(struct eea_net *enet);
int eea_adminq_query_cfg(struct eea_net *enet, struct eea_aq_cfg *cfg);
int eea_adminq_create_q(struct eea_net *enet, u32 num, u32 flags);
int eea_adminq_destroy_all_q(struct eea_net *enet);
int eea_adminq_dev_status(struct eea_net *enet,
struct eea_aq_dev_status *dstatus);
void eea_adminq_config_host_info(struct eea_net *enet);
#endif
@@ -0,0 +1,138 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#ifndef __EEA_DESC_H__
#define __EEA_DESC_H__
#define EEA_DESC_TS_MASK GENMASK_ULL(47, 0)
#define EEA_DESC_TS(desc) (le64_to_cpu((desc)->ts) & EEA_DESC_TS_MASK)
struct eea_aq_desc {
__le16 flags;
__le16 id;
__le16 reserved;
u8 classid;
u8 command;
__le64 data_addr;
__le64 reply_addr;
__le32 data_len;
__le32 reply_len;
};
struct eea_aq_cdesc {
__le16 flags;
__le16 id;
#define EEA_OK 0
#define EEA_ERR 0xffffffff
__le32 status;
__le32 reply_len;
__le32 reserved1;
__le64 reserved2;
__le64 reserved3;
};
struct eea_rx_desc_no_hdr {
__le16 flags;
__le16 id;
__le16 len;
__le16 reserved1;
__le64 addr;
};
struct eea_rx_desc {
__le16 flags;
__le16 id;
__le16 len;
__le16 reserved1;
__le64 addr;
__le64 hdr_addr;
__le32 reserved2;
__le32 reserved3;
};
#define EEA_RX_CDESC_HDR_LEN_MASK GENMASK_ULL(9, 0)
struct eea_rx_cdesc {
#define EEA_DESC_F_DATA_VALID BIT(6)
#define EEA_DESC_F_SPLIT_HDR BIT(5)
__le16 flags;
__le16 id;
__le16 len;
#define EEA_NET_PT_NONE 0
#define EEA_NET_PT_IPv4 1
#define EEA_NET_PT_TCPv4 2
#define EEA_NET_PT_UDPv4 3
#define EEA_NET_PT_IPv6 4
#define EEA_NET_PT_TCPv6 5
#define EEA_NET_PT_UDPv6 6
#define EEA_NET_PT_IPv6_EX 7
#define EEA_NET_PT_TCPv6_EX 8
#define EEA_NET_PT_UDPv6_EX 9
/* [9:0] is packet type. */
__le16 type;
/* hw timestamp [0:47]: ts */
__le64 ts;
__le32 hash;
/* 0-9: hdr_len split header
* 10-15: reserved1
*/
__le16 len_ex;
__le16 reserved2;
__le32 reserved3;
__le32 reserved4;
};
#define EEA_TX_GSO_NONE 0
#define EEA_TX_GSO_TCPV4 1
#define EEA_TX_GSO_TCPV6 4
#define EEA_TX_GSO_UDP_L4 5
#define EEA_TX_GSO_ECN 0x80
struct eea_tx_desc {
#define EEA_DESC_F_DO_CSUM BIT(6)
__le16 flags;
__le16 id;
__le16 len;
__le16 reserved1;
__le64 addr;
__le16 csum_start;
__le16 csum_offset;
u8 gso_type;
u8 reserved2;
__le16 gso_size;
__le64 reserved3;
};
struct eea_tx_cdesc {
__le16 flags;
__le16 id;
__le16 len;
__le16 reserved1;
/* hw timestamp [0:47]: ts */
__le64 ts;
};
#define EEA_DB_FLAGS_OFF 0
#define EEA_DB_IDX_OFF (2 * 8)
#define EEA_DB_TX_CQ_HEAD_OFF (4 * 8)
#define EEA_DB_RX_CQ_HEAD_OFF (6 * 8)
#define EEA_IDX_PRESENT BIT(0)
#define EEA_IRQ_MASK BIT(1)
#define EEA_IRQ_UNMASK BIT(2)
#endif
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/rtnetlink.h>
#include "eea_adminq.h"
#include "eea_net.h"
#include "eea_pci.h"
struct eea_stat_desc {
char desc[ETH_GSTRING_LEN];
size_t offset;
};
#define EEA_TX_STAT(m) {#m, offsetof(struct eea_tx_stats, m)}
#define EEA_RX_STAT(m) {#m, offsetof(struct eea_rx_stats, m)}
static const struct eea_stat_desc eea_rx_stats_desc[] = {
EEA_RX_STAT(descs),
EEA_RX_STAT(kicks),
};
static const struct eea_stat_desc eea_tx_stats_desc[] = {
EEA_TX_STAT(descs),
EEA_TX_STAT(kicks),
};
#define EEA_TX_STATS_LEN ARRAY_SIZE(eea_tx_stats_desc)
#define EEA_RX_STATS_LEN ARRAY_SIZE(eea_rx_stats_desc)
static void eea_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *info)
{
struct eea_net *enet = netdev_priv(netdev);
struct eea_device *edev = enet->edev;
strscpy(info->driver, KBUILD_MODNAME, sizeof(info->driver));
strscpy(info->bus_info, eea_pci_name(edev), sizeof(info->bus_info));
}
static void eea_get_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct eea_net *enet = netdev_priv(netdev);
ring->rx_max_pending = enet->cfg_hw.rx_ring_depth;
ring->tx_max_pending = enet->cfg_hw.tx_ring_depth;
ring->rx_pending = enet->cfg.rx_ring_depth;
ring->tx_pending = enet->cfg.tx_ring_depth;
kernel_ring->tcp_data_split = enet->cfg.split_hdr ?
ETHTOOL_TCP_DATA_SPLIT_ENABLED :
ETHTOOL_TCP_DATA_SPLIT_DISABLED;
}
static int eea_set_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct eea_net *enet = netdev_priv(netdev);
struct eea_net_init_ctx ctx;
bool need_update = false;
struct eea_net_cfg *cfg;
bool sh;
if (ring->rx_pending < EEA_NET_IO_RING_DEPTH_MIN ||
ring->tx_pending < EEA_NET_IO_RING_DEPTH_MIN)
return -EINVAL;
if (!is_power_of_2(ring->rx_pending) ||
!is_power_of_2(ring->tx_pending))
return -EINVAL;
eea_init_ctx(enet, &ctx);
cfg = &ctx.cfg;
if (ring->rx_pending != cfg->rx_ring_depth)
need_update = true;
if (ring->tx_pending != cfg->tx_ring_depth)
need_update = true;
sh = false;
switch (kernel_ring->tcp_data_split) {
case ETHTOOL_TCP_DATA_SPLIT_ENABLED:
sh = true;
break;
case ETHTOOL_TCP_DATA_SPLIT_DISABLED:
sh = false;
break;
case ETHTOOL_TCP_DATA_SPLIT_UNKNOWN:
sh = !!cfg->split_hdr;
break;
}
if (sh != !!(cfg->split_hdr))
need_update = true;
if (!need_update)
return 0;
cfg->rx_ring_depth = ring->rx_pending;
cfg->tx_ring_depth = ring->tx_pending;
/* By default, enet->cfg_hw.split_hdr is 128. */
cfg->split_hdr = sh ? enet->cfg_hw.split_hdr : 0;
return eea_reset_hw_resources(enet, &ctx);
}
static int eea_set_channels(struct net_device *netdev,
struct ethtool_channels *channels)
{
struct eea_net *enet = netdev_priv(netdev);
u16 queue_pairs = channels->combined_count;
struct eea_net_init_ctx ctx;
struct eea_net_cfg *cfg;
eea_init_ctx(enet, &ctx);
cfg = &ctx.cfg;
cfg->rx_ring_num = queue_pairs;
cfg->tx_ring_num = queue_pairs;
return eea_reset_hw_resources(enet, &ctx);
}
static void eea_get_channels(struct net_device *netdev,
struct ethtool_channels *channels)
{
struct eea_net *enet = netdev_priv(netdev);
channels->combined_count = enet->cfg.rx_ring_num;
channels->max_combined = enet->cfg_hw.rx_ring_num;
}
static void eea_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
{
struct eea_net *enet = netdev_priv(netdev);
u8 *p = data;
u32 i, j;
if (stringset != ETH_SS_STATS)
return;
for (i = 0; i < enet->cfg.rx_ring_num; i++) {
for (j = 0; j < EEA_RX_STATS_LEN; j++)
ethtool_sprintf(&p, "rx%u_%s", i,
eea_rx_stats_desc[j].desc);
}
for (i = 0; i < enet->cfg.tx_ring_num; i++) {
for (j = 0; j < EEA_TX_STATS_LEN; j++)
ethtool_sprintf(&p, "tx%u_%s", i,
eea_tx_stats_desc[j].desc);
}
}
static int eea_get_sset_count(struct net_device *netdev, int sset)
{
struct eea_net *enet = netdev_priv(netdev);
if (sset != ETH_SS_STATS)
return -EOPNOTSUPP;
return enet->cfg.rx_ring_num * EEA_RX_STATS_LEN +
enet->cfg.tx_ring_num * EEA_TX_STATS_LEN;
}
static void eea_stats_fill_for_q(struct u64_stats_sync *syncp, u32 num,
const struct eea_stat_desc *desc,
u64 *data, u32 idx)
{
void *stats_base = syncp;
u32 start, i;
do {
start = u64_stats_fetch_begin(syncp);
for (i = 0; i < num; i++)
data[idx + i] =
u64_stats_read(stats_base + desc[i].offset);
} while (u64_stats_fetch_retry(syncp, start));
BUILD_BUG_ON(offsetof(struct eea_tx_stats, syncp));
BUILD_BUG_ON(offsetof(struct eea_rx_stats, syncp));
}
static void eea_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats *stats, u64 *data)
{
struct eea_net *enet = netdev_priv(netdev);
u32 i, idx = 0;
ASSERT_RTNL();
if (enet->rx) {
for (i = 0; i < enet->cfg.rx_ring_num; i++) {
struct eea_net_rx *rx = enet->rx[i];
eea_stats_fill_for_q(&rx->stats.syncp, EEA_RX_STATS_LEN,
eea_rx_stats_desc, data, idx);
idx += EEA_RX_STATS_LEN;
}
}
if (enet->tx) {
for (i = 0; i < enet->cfg.tx_ring_num; i++) {
struct eea_net_tx *tx = &enet->tx[i];
eea_stats_fill_for_q(&tx->stats.syncp, EEA_TX_STATS_LEN,
eea_tx_stats_desc, data, idx);
idx += EEA_TX_STATS_LEN;
}
}
}
void eea_update_rx_stats(struct eea_rx_stats *rx_stats,
struct eea_rx_ctx_stats *stats)
{
u64_stats_update_begin(&rx_stats->syncp);
u64_stats_add(&rx_stats->descs, stats->descs);
u64_stats_add(&rx_stats->packets, stats->packets);
u64_stats_add(&rx_stats->bytes, stats->bytes);
u64_stats_add(&rx_stats->drops, stats->drops);
u64_stats_add(&rx_stats->split_hdr_bytes, stats->split_hdr_bytes);
u64_stats_add(&rx_stats->split_hdr_packets, stats->split_hdr_packets);
u64_stats_add(&rx_stats->length_errors, stats->length_errors);
u64_stats_add(&rx_stats->kicks, stats->kicks);
u64_stats_update_end(&rx_stats->syncp);
}
static int eea_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct eea_net *enet = netdev_priv(netdev);
cmd->base.speed = enet->speed;
cmd->base.duplex = enet->duplex;
cmd->base.port = PORT_OTHER;
return 0;
}
const struct ethtool_ops eea_ethtool_ops = {
.supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT,
.get_drvinfo = eea_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_ringparam = eea_get_ringparam,
.set_ringparam = eea_set_ringparam,
.set_channels = eea_set_channels,
.get_channels = eea_get_channels,
.get_strings = eea_get_strings,
.get_sset_count = eea_get_sset_count,
.get_ethtool_stats = eea_get_ethtool_stats,
.get_link_ksettings = eea_get_link_ksettings,
};
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#ifndef __EEA_ETHTOOL_H__
#define __EEA_ETHTOOL_H__
struct eea_tx_stats {
struct u64_stats_sync syncp;
u64_stats_t descs;
u64_stats_t packets;
u64_stats_t bytes;
u64_stats_t drops;
u64_stats_t kicks;
};
struct eea_rx_ctx_stats {
u64 descs;
u64 packets;
u64 bytes;
u64 drops;
u64 split_hdr_bytes;
u64 split_hdr_packets;
u64 kicks;
u64 length_errors;
};
struct eea_rx_stats {
struct u64_stats_sync syncp;
u64_stats_t descs;
u64_stats_t packets;
u64_stats_t bytes;
u64_stats_t drops;
u64_stats_t kicks;
u64_stats_t split_hdr_bytes;
u64_stats_t split_hdr_packets;
u64_stats_t length_errors;
};
void eea_update_rx_stats(struct eea_rx_stats *rx_stats,
struct eea_rx_ctx_stats *stats);
extern const struct ethtool_ops eea_ethtool_ops;
#endif
@@ -0,0 +1,887 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <net/netdev_queues.h>
#include "eea_adminq.h"
#include "eea_net.h"
#include "eea_pci.h"
#include "eea_ring.h"
#define EEA_SPLIT_HDR_SIZE ALIGN(128, L1_CACHE_BYTES)
static irqreturn_t eea_irq_handler(int irq, void *data)
{
struct eea_irq_blk *blk = data;
napi_schedule_irqoff(&blk->napi);
return IRQ_HANDLED;
}
static void eea_free_irq_blk(struct eea_net *enet)
{
struct eea_irq_blk *blk;
u32 num;
int i;
if (!enet->irq_blks)
return;
num = enet->edev->rx_num;
for (i = 0; i < num; i++) {
blk = &enet->irq_blks[i];
if (blk->ready)
eea_pci_free_irq(blk);
blk->ready = false;
}
kvfree(enet->irq_blks);
enet->irq_blks = NULL;
}
/* The driver will always attempt to allocate IRQ blocks based on the maximum
* possible queue num.
*/
static int eea_alloc_irq_blks(struct eea_net *enet)
{
struct eea_device *edev = enet->edev;
struct eea_irq_blk *blk, *irq_blks;
int i, err, num;
num = enet->edev->rx_num;
irq_blks = kvcalloc(num, sizeof(*blk), GFP_KERNEL);
if (!irq_blks)
return -ENOMEM;
enet->irq_blks = irq_blks;
for (i = 0; i < num; i++) {
blk = &irq_blks[i];
blk->idx = i;
/* vec 0 is for error notify. */
blk->msix_vec = i + 1;
err = eea_pci_request_irq(edev, blk, eea_irq_handler);
if (err)
goto err_free_irq_blk;
blk->ready = true;
}
return 0;
err_free_irq_blk:
eea_free_irq_blk(enet);
return err;
}
static int eea_update_queues(struct eea_net *enet)
{
return netif_set_real_num_queues(enet->netdev, enet->cfg.tx_ring_num,
enet->cfg.rx_ring_num);
}
void eea_init_ctx(struct eea_net *enet, struct eea_net_init_ctx *ctx)
{
memset(ctx, 0, sizeof(*ctx));
ctx->netdev = enet->netdev;
ctx->edev = enet->edev;
ctx->cfg = enet->cfg;
}
static void eea_bind_q_and_cfg(struct eea_net *enet,
struct eea_net_init_ctx *ctx)
{
struct eea_irq_blk *blk;
struct eea_net_rx *rx;
struct eea_net_tx *tx;
int i;
/* Since 'ndo_get_stats64' is not called in softirq context, there is no
* need to use 'spin_lock_bh'.
*/
spin_lock(&enet->stats_lock);
enet->cfg = ctx->cfg;
enet->rx = ctx->rx;
enet->tx = ctx->tx;
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
blk = &enet->irq_blks[i];
rx = ctx->rx[i];
tx = &ctx->tx[i];
rx->enet = enet;
rx->napi = &blk->napi;
rx->ering->msix_vec = blk->msix_vec;
tx->enet = enet;
tx->ering->msix_vec = blk->msix_vec;
blk->rx = rx;
}
spin_unlock(&enet->stats_lock);
}
static void eea_unbind_q_and_cfg(struct eea_net *enet,
struct eea_net_init_ctx *ctx)
{
struct eea_irq_blk *blk;
struct eea_net_rx *rx;
int i;
spin_lock(&enet->stats_lock);
ctx->cfg = enet->cfg;
ctx->rx = enet->rx;
ctx->tx = enet->tx;
enet->rx = NULL;
enet->tx = NULL;
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
blk = &enet->irq_blks[i];
rx = ctx->rx[i];
rx->napi = NULL;
blk->rx = NULL;
}
spin_unlock(&enet->stats_lock);
}
static void eea_free_rxtx_q_mem(struct eea_net_init_ctx *ctx)
{
struct eea_net_rx *rx;
struct eea_net_tx *tx;
int i;
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
rx = ctx->rx[i];
tx = &ctx->tx[i];
eea_free_rx(rx, &ctx->cfg);
eea_free_tx(tx, &ctx->cfg);
}
kvfree(ctx->rx);
kvfree(ctx->tx);
}
/* alloc tx/rx: struct, ring, meta, pp, napi */
static int eea_alloc_rxtx_q_mem(struct eea_net_init_ctx *ctx)
{
struct eea_net_rx *rx;
struct eea_net_tx *tx;
int err, i;
ctx->tx = kvcalloc(ctx->cfg.tx_ring_num, sizeof(*ctx->tx), GFP_KERNEL);
if (!ctx->tx)
return -ENOMEM;
ctx->rx = kvcalloc(ctx->cfg.rx_ring_num, sizeof(*ctx->rx), GFP_KERNEL);
if (!ctx->rx)
goto err_free_tx;
ctx->cfg.rx_sq_desc_size = sizeof(struct eea_rx_desc);
ctx->cfg.rx_cq_desc_size = sizeof(struct eea_rx_cdesc);
ctx->cfg.tx_sq_desc_size = sizeof(struct eea_tx_desc);
ctx->cfg.tx_cq_desc_size = sizeof(struct eea_tx_cdesc);
/* ethtool may config this. */
if (!ctx->cfg.split_hdr)
ctx->cfg.rx_sq_desc_size = sizeof(struct eea_rx_desc_no_hdr);
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
rx = eea_alloc_rx(ctx, i);
if (!rx)
goto err_free;
ctx->rx[i] = rx;
tx = ctx->tx + i;
err = eea_alloc_tx(ctx, tx, i);
if (err)
goto err_free;
}
return 0;
err_free:
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
rx = ctx->rx[i];
tx = ctx->tx + i;
eea_free_rx(rx, &ctx->cfg);
eea_free_tx(tx, &ctx->cfg);
}
kvfree(ctx->rx);
err_free_tx:
kvfree(ctx->tx);
return -ENOMEM;
}
static int eea_hw_active_ring(struct eea_net *enet)
{
return eea_adminq_create_q(enet, enet->cfg.rx_ring_num
+ enet->cfg.tx_ring_num, 0);
}
static int eea_hw_unactive_ring(struct eea_net *enet)
{
int err;
err = eea_adminq_destroy_all_q(enet);
if (err)
netdev_warn(enet->netdev, "unactive rxtx ring failed.\n");
return err;
}
/* stop rx napi, stop tx queue. */
static void eea_stop_rxtx(struct net_device *netdev)
{
struct eea_net *enet = netdev_priv(netdev);
int i;
netif_tx_disable(netdev);
for (i = 0; i < enet->cfg.rx_ring_num; i++)
enet_rx_stop(enet->rx[i]);
netif_carrier_off(netdev);
}
static void eea_start_rxtx(struct eea_net *enet)
{
int i;
for (i = 0; i < enet->cfg.rx_ring_num; i++)
enet_rx_start(enet->rx[i]);
netif_tx_start_all_queues(enet->netdev);
netif_carrier_on(enet->netdev);
enet->started = true;
}
static int eea_netdev_stop(struct net_device *netdev)
{
struct eea_net *enet = netdev_priv(netdev);
struct eea_net_init_ctx ctx;
/* This function can be called during device anomaly recovery. To
* prevent duplicate stop operations, the `started` flag is introduced
* for checking.
*/
if (!enet->started) {
netdev_warn(netdev, "eea netdev stop: but dev is not started.\n");
return 0;
}
eea_init_ctx(enet, &ctx);
eea_stop_rxtx(netdev);
eea_hw_unactive_ring(enet);
eea_unbind_q_and_cfg(enet, &ctx);
eea_free_rxtx_q_mem(&ctx);
enet->started = false;
return 0;
}
static int eea_netdev_open(struct net_device *netdev)
{
struct eea_net *enet = netdev_priv(netdev);
struct eea_net_init_ctx ctx;
int err;
if (enet->link_err) {
netdev_err(netdev, "netdev open err, because link error: %d\n",
enet->link_err);
return -EBUSY;
}
eea_init_ctx(enet, &ctx);
err = eea_alloc_rxtx_q_mem(&ctx);
if (err)
goto err_done;
eea_bind_q_and_cfg(enet, &ctx);
err = eea_update_queues(enet);
if (err)
goto err_free_q;
err = eea_hw_active_ring(enet);
if (err)
goto err_free_q;
eea_start_rxtx(enet);
return 0;
err_free_q:
eea_unbind_q_and_cfg(enet, &ctx);
eea_free_rxtx_q_mem(&ctx);
err_done:
return err;
}
/* Statistics may be reset to zero upon device reset. This is expected behavior
* for now and will be addressed in the future.
*/
static void eea_stats(struct net_device *netdev, struct rtnl_link_stats64 *tot)
{
struct eea_net *enet = netdev_priv(netdev);
u64 packets, bytes, drop, lerr;
u32 start;
int i;
spin_lock(&enet->stats_lock);
if (enet->rx) {
for (i = 0; i < enet->cfg.rx_ring_num; i++) {
struct eea_net_rx *rx = enet->rx[i];
do {
start = u64_stats_fetch_begin(&rx->stats.syncp);
packets = u64_stats_read(&rx->stats.packets);
bytes = u64_stats_read(&rx->stats.bytes);
drop = u64_stats_read(&rx->stats.drops);
lerr = u64_stats_read(&rx->stats.length_errors);
} while (u64_stats_fetch_retry(&rx->stats.syncp,
start));
tot->rx_packets += packets;
tot->rx_bytes += bytes;
tot->rx_dropped += drop;
tot->rx_length_errors += lerr;
tot->rx_errors += lerr;
}
}
if (enet->tx) {
for (i = 0; i < enet->cfg.tx_ring_num; i++) {
struct eea_net_tx *tx = &enet->tx[i];
do {
start = u64_stats_fetch_begin(&tx->stats.syncp);
packets = u64_stats_read(&tx->stats.packets);
bytes = u64_stats_read(&tx->stats.bytes);
drop = u64_stats_read(&tx->stats.drops);
} while (u64_stats_fetch_retry(&tx->stats.syncp,
start));
tot->tx_packets += packets;
tot->tx_bytes += bytes;
tot->tx_dropped += drop;
}
}
spin_unlock(&enet->stats_lock);
}
/* resources: ring, buffers, irq */
int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx)
{
struct eea_net_init_ctx ctx_old = {0};
int err, error;
if (!netif_running(enet->netdev) || !enet->started) {
spin_lock(&enet->stats_lock);
enet->cfg = ctx->cfg;
spin_unlock(&enet->stats_lock);
return 0;
}
err = eea_alloc_rxtx_q_mem(ctx);
if (err) {
netdev_warn(enet->netdev,
"eea reset: alloc q failed. stop reset. err %d\n",
err);
return err;
}
eea_stop_rxtx(enet->netdev);
eea_hw_unactive_ring(enet);
eea_unbind_q_and_cfg(enet, &ctx_old);
eea_bind_q_and_cfg(enet, ctx);
err = eea_update_queues(enet);
if (err) {
netdev_err(enet->netdev,
"eea reset: set real num queues failed. err %d\n",
err);
goto err_bind_old;
}
err = eea_hw_active_ring(enet);
if (err) {
netdev_err(enet->netdev, "eea reset: active new ring. err %d\n",
err);
eea_unbind_q_and_cfg(enet, ctx);
goto err_free_q;
}
eea_start_rxtx(enet);
eea_free_rxtx_q_mem(&ctx_old);
return 0;
err_bind_old:
eea_unbind_q_and_cfg(enet, ctx);
eea_bind_q_and_cfg(enet, &ctx_old);
error = eea_hw_active_ring(enet);
if (error) {
netdev_err(enet->netdev, "eea reset: active old ring. err %d\n",
error);
eea_unbind_q_and_cfg(enet, &ctx_old);
err = error;
goto err_free_q;
}
eea_start_rxtx(enet);
eea_free_rxtx_q_mem(ctx);
return err;
err_free_q:
/* An exception occurred at the hardware level, and there's not much we
* can do about it -- we can only release the resources first.
*/
eea_free_rxtx_q_mem(ctx);
eea_free_rxtx_q_mem(&ctx_old);
enet->started = false;
return err;
}
int eea_queues_check_and_reset(struct eea_device *edev)
{
struct eea_aq_dev_status dstatus = {0};
struct eea_aq_queue_status *qstatus;
struct eea_aq_queue_status *qs;
struct eea_net_init_ctx ctx;
bool need_reset = false;
int i, err = 0;
rtnl_lock();
if (!netif_running(edev->enet->netdev))
goto err_unlock;
/* Maybe stopped by ha. */
if (!edev->enet->started || edev->enet->link_err)
goto err_unlock;
err = eea_adminq_dev_status(edev->enet, &dstatus);
if (err) {
netdev_warn(edev->enet->netdev, "query queue status failed.\n");
goto err_unlock;
}
if (le16_to_cpu(dstatus.status->link_status) == EEA_LINK_DOWN_STATUS) {
/* The device is broken, can not be up. */
eea_netdev_stop(edev->enet->netdev);
edev->enet->link_err = EEA_LINK_ERR_LINK_DOWN;
netdev_warn(edev->enet->netdev, "device link is down. stop device.\n");
goto err_free;
}
qstatus = dstatus.status->q_status;
for (i = 0; i < dstatus.num; ++i) {
qs = &qstatus[i];
if (le16_to_cpu(qs->status) == EEA_QUEUE_STATUS_NEED_RESET) {
netdev_warn(edev->enet->netdev,
"queue status: queue %u needs to reset\n",
le16_to_cpu(qs->qidx));
need_reset = true;
}
}
if (need_reset) {
eea_init_ctx(edev->enet, &ctx);
err = eea_reset_hw_resources(edev->enet, &ctx);
}
err_free:
kfree(dstatus.status);
err_unlock:
rtnl_unlock();
return err;
}
static int eea_update_cfg(struct eea_net *enet,
struct eea_device *edev,
struct eea_aq_cfg *hwcfg)
{
u32 rx_max = le32_to_cpu(hwcfg->rx_depth_max);
u32 tx_max = le32_to_cpu(hwcfg->tx_depth_max);
u32 rx_def = le32_to_cpu(hwcfg->rx_depth_def);
u32 tx_def = le32_to_cpu(hwcfg->tx_depth_def);
/* Now, we assert that the rx ring num is equal to the tx ring num. */
if (edev->rx_num != edev->tx_num) {
dev_err(edev->dma_dev, "Inconsistent ring num: RX %u, TX %u\n",
edev->rx_num, edev->tx_num);
return -EINVAL;
}
if (rx_max > EEA_NET_IO_HW_RING_DEPTH_MAX ||
rx_max < EEA_NET_IO_HW_RING_DEPTH_MIN ||
tx_max > EEA_NET_IO_HW_RING_DEPTH_MAX ||
tx_max < EEA_NET_IO_HW_RING_DEPTH_MIN) {
dev_err(edev->dma_dev, "Invalid HW max depth: RX %u, TX %u\n",
rx_max, tx_max);
return -EINVAL;
}
if (rx_def > rx_max ||
tx_def > tx_max ||
rx_def < EEA_NET_IO_HW_RING_DEPTH_MIN ||
tx_def < EEA_NET_IO_HW_RING_DEPTH_MIN) {
dev_err(edev->dma_dev, "Invalid default depth: RX %u (max %u), TX %u (max %u)\n",
rx_def, rx_max, tx_def, tx_max);
return -EINVAL;
}
if (!is_power_of_2(rx_max) || !is_power_of_2(tx_max) ||
!is_power_of_2(rx_def) || !is_power_of_2(tx_def)) {
dev_err(edev->dma_dev, "Ring depth must be power of 2\n");
return -EINVAL;
}
enet->cfg_hw.rx_ring_depth = rx_max;
enet->cfg_hw.tx_ring_depth = tx_max;
enet->cfg_hw.rx_ring_num = edev->rx_num;
enet->cfg_hw.tx_ring_num = edev->tx_num;
enet->cfg_hw.split_hdr = EEA_SPLIT_HDR_SIZE;
enet->cfg.rx_ring_depth = rx_def;
enet->cfg.tx_ring_depth = tx_def;
enet->cfg.rx_ring_num = edev->rx_num;
enet->cfg.tx_ring_num = edev->tx_num;
return 0;
}
static int eea_netdev_init_features(struct net_device *netdev,
struct eea_net *enet,
struct eea_device *edev)
{
struct eea_aq_cfg *cfg;
int err;
u32 mtu;
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg)
return -ENOMEM;
err = eea_adminq_query_cfg(enet, cfg);
if (err)
goto err_free;
mtu = le16_to_cpu(cfg->mtu);
if (mtu < ETH_MIN_MTU) {
dev_err(edev->dma_dev, "The device gave us an invalid MTU. Here we can only exit the initialization. %u < %u\n",
mtu, ETH_MIN_MTU);
err = -EINVAL;
goto err_free;
}
err = eea_update_cfg(enet, edev, cfg);
if (err)
goto err_free;
netdev->priv_flags |= IFF_UNICAST_FLT;
netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
netdev->hw_features |= NETIF_F_HW_CSUM;
netdev->hw_features |= NETIF_F_GRO_HW;
netdev->hw_features |= NETIF_F_SG;
netdev->hw_features |= NETIF_F_TSO;
netdev->hw_features |= NETIF_F_TSO_ECN;
netdev->hw_features |= NETIF_F_TSO6;
netdev->hw_features |= NETIF_F_GSO_UDP_L4;
netdev->features |= NETIF_F_HIGHDMA;
netdev->features |= NETIF_F_HW_CSUM;
netdev->features |= NETIF_F_SG;
netdev->features |= NETIF_F_GSO_ROBUST;
netdev->features |= netdev->hw_features & NETIF_F_ALL_TSO;
netdev->features |= NETIF_F_RXCSUM;
netdev->features |= NETIF_F_GRO_HW;
netdev->vlan_features = netdev->features;
if (!is_valid_ether_addr(cfg->mac)) {
dev_err(edev->dma_dev, "The device gave invalid mac %pM\n",
cfg->mac);
err = -EINVAL;
goto err_free;
}
eth_hw_addr_set(netdev, cfg->mac);
enet->speed = SPEED_UNKNOWN;
enet->duplex = DUPLEX_UNKNOWN;
netdev->min_mtu = ETH_MIN_MTU;
netdev->mtu = mtu;
/* If jumbo frames are already enabled, then the returned MTU will be a
* jumbo MTU, and the driver will automatically enable jumbo frame
* support by default.
*/
netdev->max_mtu = mtu;
err_free:
kfree(cfg);
return err;
}
static const struct net_device_ops eea_netdev = {
.ndo_open = eea_netdev_open,
.ndo_stop = eea_netdev_stop,
.ndo_start_xmit = eea_tx_xmit,
.ndo_validate_addr = eth_validate_addr,
.ndo_get_stats64 = eea_stats,
.ndo_features_check = passthru_features_check,
};
static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
{
struct net_device *netdev;
struct eea_net *enet;
int err;
netdev = alloc_etherdev_mq(sizeof(struct eea_net), pairs);
if (!netdev) {
dev_err(edev->dma_dev,
"alloc_etherdev_mq failed with pairs %d\n", pairs);
return NULL;
}
netdev->netdev_ops = &eea_netdev;
netdev->ethtool_ops = &eea_ethtool_ops;
SET_NETDEV_DEV(netdev, edev->dma_dev);
enet = netdev_priv(netdev);
enet->netdev = netdev;
enet->edev = edev;
edev->enet = enet;
err = eea_alloc_irq_blks(enet);
if (err) {
dev_err(edev->dma_dev,
"eea_alloc_irq_blks failed with pairs %d\n", pairs);
free_netdev(netdev);
return NULL;
}
spin_lock_init(&enet->stats_lock);
return enet;
}
static void eea_update_ts_off(struct eea_device *edev, struct eea_net *enet)
{
u64 ts;
ts = eea_pci_device_ts(edev);
enet->hw_ts_offset = ktime_get_real() - ts;
}
static int eea_net_reprobe(struct eea_device *edev)
{
struct eea_net *enet = edev->enet;
int err = 0;
enet->edev = edev;
if (!enet->adminq.ring) {
err = eea_create_adminq(enet, edev->rx_num + edev->tx_num);
if (err)
return err;
}
err = eea_alloc_irq_blks(enet);
if (err)
goto err_destroy_aq;
eea_update_ts_off(edev, enet);
rtnl_lock();
enet->link_err = 0;
if (edev->ha_reset_netdev_running &&
netif_running(edev->enet->netdev)) {
err = eea_netdev_open(enet->netdev);
if (err) {
enet->link_err = EEA_LINK_ERR_HA_RESET_DEV;
rtnl_unlock();
goto err_free_irq_blks;
}
}
rtnl_unlock();
enet->wait_pci_ready = false;
return 0;
err_free_irq_blks:
eea_free_irq_blk(enet);
err_destroy_aq:
eea_destroy_adminq(enet);
return err;
}
int eea_net_probe(struct eea_device *edev)
{
struct eea_net *enet;
int err = -ENOMEM;
/* If edev->enet is not null, then this is called from ha reset worker.
* Call eea_net_reprobe() directly.
*/
if (edev->enet)
return eea_net_reprobe(edev);
enet = eea_netdev_alloc(edev, edev->rx_num);
if (!enet)
return -ENOMEM;
err = eea_create_adminq(enet, edev->rx_num + edev->tx_num);
if (err)
goto err_free_netdev;
eea_adminq_config_host_info(enet);
err = eea_netdev_init_features(enet->netdev, enet, edev);
if (err)
goto err_reset_dev;
eea_update_ts_off(edev, enet);
netif_carrier_off(enet->netdev);
err = register_netdev(enet->netdev);
if (err)
goto err_reset_dev;
netdev_dbg(enet->netdev, "eea probe success.\n");
return 0;
err_reset_dev:
eea_device_reset(edev);
eea_destroy_adminq(enet);
err_free_netdev:
eea_free_irq_blk(enet);
free_netdev(enet->netdev);
return err;
}
static void eea_net_ha_reset_remove(struct eea_net *enet,
struct eea_device *edev)
{
rtnl_lock();
edev->ha_reset_netdev_running = false;
if (netif_running(enet->netdev)) {
eea_netdev_stop(enet->netdev);
edev->ha_reset_netdev_running = true;
}
/* Prevent that the user set up the net device. */
enet->link_err = EEA_LINK_ERR_HA_RESET_DEV;
rtnl_unlock();
eea_device_reset(edev);
eea_destroy_adminq(enet);
eea_free_irq_blk(enet);
enet->wait_pci_ready = true;
}
void eea_net_remove(struct eea_device *edev, bool ha)
{
struct net_device *netdev;
struct eea_net *enet;
enet = edev->enet;
netdev = enet->netdev;
if (ha) {
if (enet->wait_pci_ready)
return;
eea_net_ha_reset_remove(enet, edev);
return;
}
unregister_netdev(netdev);
if (!enet->wait_pci_ready) {
eea_device_reset(edev);
eea_destroy_adminq(enet);
eea_free_irq_blk(enet);
}
free_netdev(netdev);
}
void eea_net_shutdown(struct eea_device *edev)
{
struct net_device *netdev;
struct eea_net *enet;
enet = edev->enet;
netdev = enet->netdev;
rtnl_lock();
netif_device_detach(netdev);
dev_close(netdev);
if (!enet->wait_pci_ready) {
eea_device_reset(edev);
eea_destroy_adminq(enet);
eea_free_irq_blk(enet);
}
rtnl_unlock();
}
@@ -0,0 +1,198 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#ifndef __EEA_NET_H__
#define __EEA_NET_H__
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include "eea_adminq.h"
#include "eea_ethtool.h"
#include "eea_ring.h"
#define EEA_VER_MAJOR 1
#define EEA_VER_MINOR 0
#define EEA_VER_SUB_MINOR 0
struct eea_tx_meta;
struct eea_net_tx {
struct eea_net *enet;
struct eea_ring *ering;
struct eea_tx_meta *meta;
struct eea_tx_meta *free;
struct device *dma_dev;
u32 index;
char name[16];
struct eea_tx_stats stats;
};
struct eea_rx_meta {
struct eea_rx_meta *next;
struct page *page;
dma_addr_t dma;
u32 offset;
u32 sync_for_cpu;
u32 frags;
struct page *hdr_page;
void *hdr_addr;
dma_addr_t hdr_dma;
u32 id;
u32 truesize;
u32 headroom;
u32 tailroom;
u32 len;
bool in_use;
};
struct eea_net_rx_pkt_ctx {
u16 idx;
bool data_valid;
bool do_drop;
u32 recv_len;
struct sk_buff *head_skb;
};
struct eea_net_rx {
struct eea_net *enet;
struct eea_ring *ering;
struct eea_rx_meta *meta;
struct eea_rx_meta *free;
struct device *dma_dev;
u32 index;
u32 flags;
u32 headroom;
struct napi_struct *napi;
struct eea_rx_stats stats;
char name[16];
struct eea_net_rx_pkt_ctx pkt;
struct page_pool *pp;
};
struct eea_net_cfg {
u32 rx_ring_depth;
u32 tx_ring_depth;
u32 rx_ring_num;
u32 tx_ring_num;
u8 rx_sq_desc_size;
u8 rx_cq_desc_size;
u8 tx_sq_desc_size;
u8 tx_cq_desc_size;
u32 split_hdr;
struct hwtstamp_config ts_cfg;
};
struct eea_net_init_ctx {
struct eea_net_cfg cfg;
struct eea_net_tx *tx;
struct eea_net_rx **rx;
struct net_device *netdev;
struct eea_device *edev;
};
enum {
EEA_LINK_ERR_NONE,
EEA_LINK_ERR_HA_RESET_DEV,
EEA_LINK_ERR_LINK_DOWN,
};
struct eea_irq_blk {
struct napi_struct napi;
u16 msix_vec;
bool ready;
struct eea_net_rx *rx;
char irq_name[32];
int irq;
int idx;
};
struct eea_net {
struct eea_device *edev;
struct net_device *netdev;
struct eea_aq adminq;
struct eea_net_tx *tx;
struct eea_net_rx **rx;
struct eea_net_cfg cfg;
struct eea_net_cfg cfg_hw;
struct eea_irq_blk *irq_blks;
u32 link_err;
bool started;
bool wait_pci_ready;
u8 duplex;
u32 speed;
u64 hw_ts_offset;
/* Protect the tx and rx of struct eea_net, when eea_stats accesses the
* stats from rx and tx queues.
*/
spinlock_t stats_lock;
};
int eea_net_probe(struct eea_device *edev);
void eea_net_remove(struct eea_device *edev, bool ha);
void eea_net_shutdown(struct eea_device *edev);
int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx);
void eea_init_ctx(struct eea_net *enet, struct eea_net_init_ctx *ctx);
int eea_queues_check_and_reset(struct eea_device *edev);
/* rx apis */
void enet_rx_stop(struct eea_net_rx *rx);
void enet_rx_start(struct eea_net_rx *rx);
void eea_free_rx(struct eea_net_rx *rx, struct eea_net_cfg *cfg);
struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx);
/* tx apis */
int eea_poll_tx(struct eea_net_tx *tx, int budget);
netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev);
void eea_free_tx(struct eea_net_tx *tx, struct eea_net_cfg *cfg);
int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx);
#endif
@@ -0,0 +1,744 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/iopoll.h>
#include "eea_net.h"
#include "eea_pci.h"
#define EEA_PCI_DB_OFFSET 4096
#define EEA_PCI_DB_MIN_SIZE 8
#define EEA_PCI_DB_MAX_SIZE 512
#define EEA_PCI_Q_MAX_NUM 1000
#define EEA_PCI_CAP_RESET_DEVICE 0xFA
#define EEA_PCI_CAP_RESET_FLAG BIT(1)
struct eea_pci_cfg {
__le32 reserve0;
__le32 reserve1;
__le32 drv_f_idx;
__le32 drv_f;
#define EEA_S_INIT (BIT(0) | BIT(1))
#define EEA_S_OK BIT(2)
#define EEA_S_FEATURE_DONE BIT(3)
#define EEA_S_FAILED BIT(7)
u8 device_status;
u8 reserved[7];
__le32 rx_num_max;
__le32 tx_num_max;
__le32 db_blk_size;
/* admin queue cfg */
__le16 aq_size;
__le16 aq_msix_vector;
__le32 aq_db_off;
__le32 aq_sq_addr;
__le32 aq_sq_addr_hi;
__le32 aq_cq_addr;
__le32 aq_cq_addr_hi;
__le32 reserved1;
__le64 hw_ts;
};
struct eea_pci_device {
struct eea_device edev;
struct pci_dev *pci_dev;
u32 msix_vec_n;
u32 db_len;
void __iomem *reg;
void __iomem *db_base;
void __iomem *db_end;
int ha_irq;
struct work_struct ha_handle_work;
char ha_irq_name[32];
int reset_pos;
bool ha_ready;
bool shutdown;
};
#define cfg_pointer(reg, item) \
((void __iomem *)((reg) + offsetof(struct eea_pci_cfg, item)))
#define cfg_write8(reg, item, val) iowrite8(val, cfg_pointer(reg, item))
#define cfg_write16(reg, item, val) iowrite16(val, cfg_pointer(reg, item))
#define cfg_write32(reg, item, val) iowrite32(val, cfg_pointer(reg, item))
#define cfg_write64(reg, item, val) iowrite64_lo_hi(val, cfg_pointer(reg, item))
#define cfg_read8(reg, item) ioread8(cfg_pointer(reg, item))
#define cfg_read32(reg, item) ioread32(cfg_pointer(reg, item))
#define cfg_read64(reg, item) ioread64(cfg_pointer(reg, item))
/* Due to circular references, we have to add function definitions here. */
static int __eea_pci_probe(struct pci_dev *pci_dev,
struct eea_pci_device *ep_dev, bool pci_probe);
static void __eea_pci_remove(struct pci_dev *pci_dev, bool pci_remove);
const char *eea_pci_name(struct eea_device *edev)
{
return pci_name(edev->ep_dev->pci_dev);
}
int eea_pci_domain_nr(struct eea_device *edev)
{
return pci_domain_nr(edev->ep_dev->pci_dev->bus);
}
u16 eea_pci_bdf(struct eea_device *edev)
{
return pci_dev_id(edev->ep_dev->pci_dev);
}
static void eea_pci_io_set_status(struct eea_device *edev, u8 status)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
cfg_write8(ep_dev->reg, device_status, status);
}
static u8 eea_pci_io_get_status(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
return cfg_read8(ep_dev->reg, device_status);
}
static void eea_add_status(struct eea_device *dev, u32 status)
{
eea_pci_io_set_status(dev, eea_pci_io_get_status(dev) | status);
}
#define EEA_RESET_TIMEOUT_US (60 * 1000 * 1000)
int eea_device_reset(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
int err;
u8 val;
eea_pci_io_set_status(edev, 0);
/* We are no longer waiting for device ack during the shutdown flow. */
if (ep_dev->shutdown)
return 0;
/* A longer timeout is set here to handle edge cases, though it should
* return promptly in most scenarios.
*
* In our case, all replies are handled by the DPU software, so there is
* no race condition between the hardware processes and the register.
*/
err = read_poll_timeout(cfg_read8, val, (!val || val == 0xFF), 20,
EEA_RESET_TIMEOUT_US,
false, ep_dev->reg, device_status);
/* Surprise PCIe Removal */
if (val == 0xFF)
return -EINVAL;
return err;
}
int eea_pci_set_aq_up(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
u8 status = eea_pci_io_get_status(edev);
int err;
u8 val;
eea_pci_io_set_status(edev, status | EEA_S_OK);
/* A longer timeout is set here to handle edge cases, though it should
* return promptly in most scenarios.
*
* In our case, all replies are handled by the DPU software, so there is
* no race condition between the hardware processes and the register.
*/
err = read_poll_timeout(cfg_read8, val,
val & (EEA_S_OK | EEA_S_FAILED),
20, EEA_RESET_TIMEOUT_US,
false, ep_dev->reg, device_status);
/* Surprise PCIe Removal */
if (val == 0xFF)
return -EINVAL;
/* device fail */
if (val & EEA_S_FAILED)
return -EINVAL;
return err;
}
static int eea_negotiate(struct eea_device *edev)
{
struct eea_pci_device *ep_dev;
u32 status;
ep_dev = edev->ep_dev;
edev->features = 0;
cfg_write32(ep_dev->reg, drv_f_idx, 0);
cfg_write32(ep_dev->reg, drv_f, lower_32_bits(edev->features));
cfg_write32(ep_dev->reg, drv_f_idx, 1);
cfg_write32(ep_dev->reg, drv_f, upper_32_bits(edev->features));
eea_add_status(edev, EEA_S_FEATURE_DONE);
status = eea_pci_io_get_status(edev);
/* Surprise PCIe Removal */
if (status == 0xFF)
return -EINVAL;
if (!(status & EEA_S_FEATURE_DONE))
return -ENODEV;
return 0;
}
static void eea_pci_release_resource(struct eea_pci_device *ep_dev)
{
struct pci_dev *pci_dev = ep_dev->pci_dev;
struct eea_device *edev;
edev = &ep_dev->edev;
if (edev->status < EEA_PCI_STATUS_READY)
return;
if (ep_dev->reg) {
pci_iounmap(pci_dev, ep_dev->reg);
ep_dev->reg = NULL;
}
if (ep_dev->msix_vec_n) {
ep_dev->msix_vec_n = 0;
pci_free_irq_vectors(ep_dev->pci_dev);
}
pci_clear_master(pci_dev);
pci_release_regions(pci_dev);
pci_disable_device(pci_dev);
edev->status = EEA_PCI_STATUS_NONE;
}
static int eea_pci_setup(struct pci_dev *pci_dev, struct eea_pci_device *ep_dev)
{
int err, n, ret, len;
ep_dev->edev.status = EEA_PCI_STATUS_ERR;
ep_dev->pci_dev = pci_dev;
err = pci_enable_device(pci_dev);
if (err)
return err;
err = pci_request_regions(pci_dev, "EEA");
if (err)
goto err_disable_dev;
if (pci_resource_len(pci_dev, 0) < EEA_PCI_DB_OFFSET) {
dev_err(&pci_dev->dev, "Bar 0 is too small %llu\n",
(u64)pci_resource_len(pci_dev, 0));
err = -EINVAL;
goto err_release_regions;
}
ep_dev->reg = pci_iomap(pci_dev, 0, 0);
if (!ep_dev->reg) {
dev_err(&pci_dev->dev, "Failed to map pci bar!\n");
err = -ENOMEM;
goto err_release_regions;
}
err = eea_device_reset(&ep_dev->edev);
if (err) {
dev_err(&pci_dev->dev, "Failed to reset device for setup!\n");
goto err_unmap_reg;
}
err = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));
if (err) {
dev_warn(&pci_dev->dev, "Failed to enable 64-bit DMA.\n");
goto err_unmap_reg;
}
pci_set_master(pci_dev);
ep_dev->edev.rx_num = cfg_read32(ep_dev->reg, rx_num_max);
ep_dev->edev.tx_num = cfg_read32(ep_dev->reg, tx_num_max);
if (ep_dev->edev.rx_num > EEA_PCI_Q_MAX_NUM ||
ep_dev->edev.tx_num > EEA_PCI_Q_MAX_NUM) {
dev_err(&pci_dev->dev, "Invalid queue num %u %u\n",
ep_dev->edev.rx_num,
ep_dev->edev.tx_num);
err = -EINVAL;
goto err_clear_master;
}
ep_dev->edev.db_blk_size = cfg_read32(ep_dev->reg, db_blk_size);
if (!IS_ALIGNED(ep_dev->edev.db_blk_size, 8) ||
ep_dev->edev.db_blk_size > EEA_PCI_DB_MAX_SIZE ||
ep_dev->edev.db_blk_size < EEA_PCI_DB_MIN_SIZE) {
dev_err(&pci_dev->dev, "Invalid db size %u\n",
ep_dev->edev.db_blk_size);
err = -EINVAL;
goto err_clear_master;
}
ep_dev->db_len = ep_dev->edev.db_blk_size * (ep_dev->edev.rx_num +
ep_dev->edev.tx_num + 1);
ep_dev->db_base = ep_dev->reg + EEA_PCI_DB_OFFSET;
ep_dev->db_end = ep_dev->db_base + ep_dev->db_len;
len = ep_dev->db_end - ep_dev->reg;
if (pci_resource_len(pci_dev, 0) < len) {
dev_err(&pci_dev->dev, "Bar 0 is too small %llu\n",
(u64)pci_resource_len(pci_dev, 0));
err = -EINVAL;
goto err_clear_master;
}
/* In our design, the number of hardware interrupts matches the maximum
* number of queues. If pci_alloc_irq_vectors failed, return directly.
*
* 2: adminq, error handle
*/
n = ep_dev->edev.rx_num + 2;
ret = pci_alloc_irq_vectors(ep_dev->pci_dev, n, n, PCI_IRQ_MSIX);
if (ret != n) {
err = ret;
goto err_clear_master;
}
ep_dev->msix_vec_n = ret;
ep_dev->edev.status = EEA_PCI_STATUS_READY;
return 0;
err_clear_master:
pci_clear_master(pci_dev);
err_unmap_reg:
pci_iounmap(pci_dev, ep_dev->reg);
ep_dev->reg = NULL;
err_release_regions:
pci_release_regions(pci_dev);
err_disable_dev:
pci_disable_device(pci_dev);
return err;
}
void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off)
{
u32 max_off;
if (!IS_ALIGNED(off, 8))
return NULL;
max_off = edev->ep_dev->db_len - edev->db_blk_size;
if (off > max_off)
return NULL;
return edev->ep_dev->db_base + off;
}
int eea_pci_active_aq(struct eea_ring *ering, int msix_vec)
{
struct eea_pci_device *ep_dev = ering->edev->ep_dev;
cfg_write16(ep_dev->reg, aq_size, ering->num);
cfg_write16(ep_dev->reg, aq_msix_vector, msix_vec);
cfg_write64(ep_dev->reg, aq_sq_addr, ering->sq.dma_addr);
cfg_write64(ep_dev->reg, aq_cq_addr, ering->cq.dma_addr);
ering->db = eea_pci_db_addr(ering->edev,
cfg_read32(ep_dev->reg, aq_db_off));
if (!ering->db)
return -EIO;
return 0;
}
void eea_pci_free_irq(struct eea_irq_blk *blk)
{
irq_update_affinity_hint(blk->irq, NULL);
free_irq(blk->irq, blk);
}
int eea_pci_request_irq(struct eea_device *edev, struct eea_irq_blk *blk,
irqreturn_t (*callback)(int irq, void *data))
{
struct eea_pci_device *ep_dev = edev->ep_dev;
int irq;
snprintf(blk->irq_name, sizeof(blk->irq_name), "eea-q%d@%s", blk->idx,
pci_name(ep_dev->pci_dev));
irq = pci_irq_vector(ep_dev->pci_dev, blk->msix_vec);
blk->irq = irq;
return request_irq(irq, callback, IRQF_NO_AUTOEN, blk->irq_name, blk);
}
static void eea_ha_handle_reset(struct eea_pci_device *ep_dev)
{
struct eea_device *edev;
struct pci_dev *pci_dev;
u16 reset;
int err;
if (!ep_dev->reset_pos) {
eea_queues_check_and_reset(&ep_dev->edev);
return;
}
edev = &ep_dev->edev;
pci_read_config_word(ep_dev->pci_dev, ep_dev->reset_pos, &reset);
/* Clear bits using 0xFFFF and ignore all previous messages. */
pci_write_config_word(ep_dev->pci_dev, ep_dev->reset_pos, 0xFFFF);
if (reset & EEA_PCI_CAP_RESET_FLAG) {
dev_warn(&ep_dev->pci_dev->dev, "recv device reset request.\n");
pci_dev = ep_dev->pci_dev;
/* The pci remove callback may hold this lock. If the
* pci remove callback is called, then we can ignore the
* ha interrupt.
*/
if (mutex_trylock(&edev->ha_lock)) {
if (edev->status != EEA_PCI_STATUS_DONE) {
dev_err(&ep_dev->pci_dev->dev, "ha: reset device: pci status is %d. skip it.\n",
edev->status);
mutex_unlock(&edev->ha_lock);
return;
}
__eea_pci_remove(pci_dev, false);
err = __eea_pci_probe(pci_dev, ep_dev, false);
if (err)
/* Currently, for some reason, PCI
* initialization or network device re-probing
* has failed. Waiting for the PCI subsystem to
* call the remove callback to release the
* remaining resources.
*/
dev_err(&ep_dev->pci_dev->dev,
"ha: re-setup failed.\n");
mutex_unlock(&edev->ha_lock);
} else {
/* Device removal is in progress, so return directly. */
dev_warn(&ep_dev->pci_dev->dev,
"ha device reset: trylock failed.\n");
}
return;
}
eea_queues_check_and_reset(&ep_dev->edev);
}
/* ha handle code */
static void eea_ha_handle_work(struct work_struct *work)
{
struct eea_pci_device *ep_dev;
ep_dev = container_of(work, struct eea_pci_device, ha_handle_work);
/* Ha interrupt is triggered, so there maybe some error, we may need to
* reset the device or reset some queues.
*/
dev_warn(&ep_dev->pci_dev->dev, "recv ha interrupt.\n");
eea_ha_handle_reset(ep_dev);
}
static irqreturn_t eea_pci_ha_handle(int irq, void *data)
{
struct eea_device *edev = data;
schedule_work(&edev->ep_dev->ha_handle_work);
return IRQ_HANDLED;
}
static void eea_pci_free_ha_irq(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
int irq;
if (ep_dev->ha_ready) {
irq = pci_irq_vector(ep_dev->pci_dev, 0);
free_irq(irq, edev);
ep_dev->ha_ready = false;
}
}
static int eea_pci_ha_init(struct eea_device *edev, struct pci_dev *pci_dev,
bool pci_probe)
{
int pos, cfg_type_off, cfg_drv_off, cfg_dev_off;
struct eea_pci_device *ep_dev = edev->ep_dev;
int irq, err;
u8 type;
snprintf(ep_dev->ha_irq_name, sizeof(ep_dev->ha_irq_name), "eea-ha@%s",
pci_name(ep_dev->pci_dev));
irq = pci_irq_vector(ep_dev->pci_dev, 0);
if (pci_probe)
INIT_WORK(&ep_dev->ha_handle_work, eea_ha_handle_work);
/* This irq is not only work for ha, so request it always. */
err = request_irq(irq, eea_pci_ha_handle, IRQF_NO_AUTOEN,
ep_dev->ha_irq_name, edev);
if (err)
return err;
ep_dev->ha_irq = irq;
ep_dev->ha_ready = true;
ep_dev->reset_pos = 0;
cfg_type_off = offsetof(struct eea_pci_cap, cfg_type);
cfg_drv_off = offsetof(struct eea_pci_reset_reg, driver);
cfg_dev_off = offsetof(struct eea_pci_reset_reg, device);
for (pos = pci_find_capability(pci_dev, PCI_CAP_ID_VNDR);
pos > 0;
pos = pci_find_next_capability(pci_dev, pos, PCI_CAP_ID_VNDR)) {
pci_read_config_byte(pci_dev, pos + cfg_type_off, &type);
if (type == EEA_PCI_CAP_RESET_DEVICE) {
/* notify device, driver support this feature. */
pci_write_config_word(pci_dev, pos + cfg_drv_off,
EEA_PCI_CAP_RESET_FLAG);
pci_write_config_word(pci_dev, pos + cfg_dev_off,
0xFFFF);
edev->ep_dev->reset_pos = pos + cfg_dev_off;
return 0;
}
}
/* irq just for event notify */
dev_warn(&edev->ep_dev->pci_dev->dev, "Not Found reset cap.\n");
return 0;
}
u64 eea_pci_device_ts(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
return cfg_read64(ep_dev->reg, hw_ts);
}
static int eea_init_device(struct eea_device *edev)
{
int err;
err = eea_device_reset(edev);
if (err)
return err;
eea_pci_io_set_status(edev, EEA_S_INIT);
err = eea_negotiate(edev);
if (err)
goto err;
err = eea_net_probe(edev);
if (err)
goto err;
return 0;
err:
eea_add_status(edev, EEA_S_FAILED);
return err;
}
static int __eea_pci_probe(struct pci_dev *pci_dev,
struct eea_pci_device *ep_dev,
bool pci_probe)
{
struct eea_device *edev;
int err;
pci_set_drvdata(pci_dev, ep_dev);
edev = &ep_dev->edev;
err = eea_pci_setup(pci_dev, ep_dev);
if (err)
return err;
err = eea_init_device(&ep_dev->edev);
if (err)
goto err_pci_rel;
err = eea_pci_ha_init(edev, pci_dev, pci_probe);
if (err)
goto err_net_rm;
edev->status = EEA_PCI_STATUS_DONE;
enable_irq(ep_dev->ha_irq);
return 0;
err_net_rm:
eea_net_remove(edev, !pci_probe);
err_pci_rel:
eea_pci_release_resource(ep_dev);
return err;
}
static void __eea_pci_remove(struct pci_dev *pci_dev, bool pci_remove)
{
struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
struct device *dev = get_device(&ep_dev->pci_dev->dev);
struct eea_device *edev = &ep_dev->edev;
eea_pci_free_ha_irq(edev);
if (pci_remove)
flush_work(&ep_dev->ha_handle_work);
eea_net_remove(edev, !pci_remove);
eea_pci_release_resource(ep_dev);
put_device(dev);
}
static int eea_pci_probe(struct pci_dev *pci_dev,
const struct pci_device_id *id)
{
struct eea_pci_device *ep_dev;
struct eea_device *edev;
int err;
ep_dev = kzalloc(sizeof(*ep_dev), GFP_KERNEL);
if (!ep_dev)
return -ENOMEM;
edev = &ep_dev->edev;
edev->ep_dev = ep_dev;
edev->dma_dev = &pci_dev->dev;
ep_dev->pci_dev = pci_dev;
mutex_init(&edev->ha_lock);
err = __eea_pci_probe(pci_dev, ep_dev, true);
if (err) {
mutex_destroy(&edev->ha_lock);
pci_set_drvdata(pci_dev, NULL);
kfree(ep_dev);
}
return err;
}
static void eea_pci_remove(struct pci_dev *pci_dev)
{
struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
struct eea_device *edev;
edev = &ep_dev->edev;
mutex_lock(&edev->ha_lock);
__eea_pci_remove(pci_dev, true);
mutex_unlock(&edev->ha_lock);
pci_set_drvdata(pci_dev, NULL);
mutex_destroy(&edev->ha_lock);
kfree(ep_dev);
}
static void eea_pci_shutdown(struct pci_dev *pci_dev)
{
struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
struct eea_device *edev;
edev = &ep_dev->edev;
ep_dev->shutdown = true;
mutex_lock(&edev->ha_lock);
eea_pci_free_ha_irq(edev);
flush_work(&ep_dev->ha_handle_work);
mutex_unlock(&edev->ha_lock);
eea_net_shutdown(edev);
pci_clear_master(pci_dev);
}
static const struct pci_device_id eea_pci_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_ALIBABA, 0x500B) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, eea_pci_id_table);
static struct pci_driver eea_pci_driver = {
.name = "alibaba_eea",
.id_table = eea_pci_id_table,
.probe = eea_pci_probe,
.remove = eea_pci_remove,
.shutdown = eea_pci_shutdown,
.sriov_configure = pci_sriov_configure_simple,
};
static __init int eea_pci_init(void)
{
return pci_register_driver(&eea_pci_driver);
}
static __exit void eea_pci_exit(void)
{
pci_unregister_driver(&eea_pci_driver);
}
module_init(eea_pci_init);
module_exit(eea_pci_exit);
MODULE_DESCRIPTION("Driver for Alibaba Elastic Ethernet Adapter");
MODULE_AUTHOR("Xuan Zhuo <xuanzhuo@linux.alibaba.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#ifndef __EEA_PCI_H__
#define __EEA_PCI_H__
#include <linux/pci.h>
#include "eea_net.h"
#include "eea_ring.h"
enum eea_pci_status {
EEA_PCI_STATUS_NONE,
EEA_PCI_STATUS_ERR,
EEA_PCI_STATUS_READY,
EEA_PCI_STATUS_DONE,
};
struct eea_irq_blk;
struct eea_pci_cap {
__u8 cap_vndr;
__u8 cap_next;
__u8 cap_len;
__u8 cfg_type;
};
struct eea_pci_reset_reg {
struct eea_pci_cap cap;
__le16 driver;
__le16 device;
};
struct eea_pci_device;
struct eea_device {
struct eea_pci_device *ep_dev;
struct device *dma_dev;
struct eea_net *enet;
u64 features;
enum eea_pci_status status;
bool ha_reset_netdev_running;
/* ha lock for the race between ha work and pci remove */
struct mutex ha_lock;
u32 rx_num;
u32 tx_num;
u32 db_blk_size;
};
const char *eea_pci_name(struct eea_device *edev);
int eea_pci_domain_nr(struct eea_device *edev);
u16 eea_pci_bdf(struct eea_device *edev);
int eea_device_reset(struct eea_device *dev);
int eea_pci_set_aq_up(struct eea_device *dev);
int eea_pci_active_aq(struct eea_ring *ering, int msix_vec);
int eea_pci_request_irq(struct eea_device *edev, struct eea_irq_blk *blk,
irqreturn_t (*callback)(int irq, void *data));
void eea_pci_free_irq(struct eea_irq_blk *blk);
u64 eea_pci_device_ts(struct eea_device *edev);
void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off);
#endif
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include "eea_pci.h"
#include "eea_ring.h"
void eea_ering_irq_active(struct eea_ring *ering, struct eea_ring *tx_ering)
{
u64 value = 0, rx_idx, tx_idx;
tx_idx = (u64)tx_ering->cq.hw_idx;
rx_idx = (u64)ering->cq.hw_idx;
value |= EEA_IRQ_UNMASK << EEA_DB_FLAGS_OFF;
value |= tx_idx << EEA_DB_TX_CQ_HEAD_OFF;
value |= rx_idx << EEA_DB_RX_CQ_HEAD_OFF;
writeq(value, ering->db);
}
void *eea_ering_cq_get_desc(const struct eea_ring *ering)
{
u8 phase;
u8 *desc;
desc = ering->cq.desc + (ering->cq.head << ering->cq.desc_size_shift);
phase = READ_ONCE(*(u8 *)(desc + ering->cq.desc_size - 1));
if ((phase & EEA_RING_DESC_F_CQ_PHASE) == ering->cq.phase) {
dma_rmb();
return desc;
}
return NULL;
}
/* sq api */
void *eea_ering_sq_alloc_desc(struct eea_ring *ering, u16 id, bool is_last,
u16 flags)
{
struct eea_ring_sq *sq = &ering->sq;
struct eea_common_desc *desc;
if (!sq->shadow_num) {
sq->shadow_idx = sq->head;
sq->shadow_id = cpu_to_le16(id);
}
if (!is_last)
flags |= EEA_RING_DESC_F_MORE;
desc = sq->desc + (sq->shadow_idx << sq->desc_size_shift);
desc->flags = cpu_to_le16(flags);
desc->id = sq->shadow_id;
if (unlikely(++sq->shadow_idx >= ering->num))
sq->shadow_idx = 0;
++sq->shadow_num;
return desc;
}
/* This is an allocation API for admin Q. For each call to admin Q, only one
* desc will be allocated.
*/
void *eea_ering_aq_alloc_desc(struct eea_ring *ering)
{
struct eea_ring_sq *sq = &ering->sq;
struct eea_common_desc *desc;
if (!sq->shadow_num)
sq->shadow_idx = sq->head;
desc = sq->desc + (sq->shadow_idx << sq->desc_size_shift);
if (unlikely(++sq->shadow_idx >= ering->num))
sq->shadow_idx = 0;
++sq->shadow_num;
return desc;
}
void eea_ering_sq_commit_desc(struct eea_ring *ering)
{
struct eea_ring_sq *sq = &ering->sq;
int num;
num = sq->shadow_num;
ering->num_free -= num;
sq->head = sq->shadow_idx;
sq->hw_idx += num;
sq->shadow_num = 0;
}
void eea_ering_sq_cancel(struct eea_ring *ering)
{
ering->sq.shadow_num = 0;
}
/* cq api */
void eea_ering_cq_ack_desc(struct eea_ring *ering, u32 num)
{
struct eea_ring_cq *cq = &ering->cq;
cq->head += num;
cq->hw_idx += num;
if (unlikely(cq->head >= ering->num)) {
cq->head -= ering->num;
cq->phase ^= EEA_RING_DESC_F_CQ_PHASE;
}
ering->num_free += num;
}
/* notify */
void eea_ering_kick(struct eea_ring *ering)
{
u64 value = 0, idx;
idx = (u64)ering->sq.hw_idx;
value |= EEA_IDX_PRESENT << EEA_DB_FLAGS_OFF;
value |= idx << EEA_DB_IDX_OFF;
writeq(value, ering->db);
}
/* ering alloc/free */
static void ering_free_queue(struct eea_device *edev, size_t size,
void *queue, dma_addr_t dma_handle)
{
dma_free_coherent(edev->dma_dev, size, queue, dma_handle);
}
static void *ering_alloc_queue(struct eea_device *edev, size_t size,
dma_addr_t *dma_handle)
{
gfp_t flags = GFP_KERNEL | __GFP_NOWARN;
return dma_alloc_coherent(edev->dma_dev, size, dma_handle, flags);
}
static int ering_alloc_queues(struct eea_ring *ering, struct eea_device *edev,
size_t num, u8 sq_desc_size, u8 cq_desc_size)
{
dma_addr_t addr;
size_t size;
void *ring;
size = num * sq_desc_size;
ring = ering_alloc_queue(edev, size, &addr);
if (!ring)
return -ENOMEM;
ering->sq.desc = ring;
ering->sq.dma_addr = addr;
ering->sq.dma_size = size;
ering->sq.desc_size = sq_desc_size;
ering->sq.desc_size_shift = fls(sq_desc_size) - 1;
size = num * cq_desc_size;
ring = ering_alloc_queue(edev, size, &addr);
if (!ring)
goto err_free_sq;
ering->cq.desc = ring;
ering->cq.dma_addr = addr;
ering->cq.dma_size = size;
ering->cq.desc_size = cq_desc_size;
ering->cq.desc_size_shift = fls(cq_desc_size) - 1;
ering->num = num;
return 0;
err_free_sq:
ering_free_queue(ering->edev, ering->sq.dma_size,
ering->sq.desc, ering->sq.dma_addr);
return -ENOMEM;
}
static void ering_init(struct eea_ring *ering)
{
ering->cq.phase = EEA_RING_DESC_F_CQ_PHASE;
ering->num_free = ering->num;
}
struct eea_ring *eea_ering_alloc(u32 index, u32 num, struct eea_device *edev,
u8 sq_desc_size, u8 cq_desc_size,
const char *name)
{
struct eea_ring *ering;
if (num > EEA_NET_IO_HW_RING_DEPTH_MAX ||
num < EEA_NET_IO_RING_DEPTH_MIN)
return NULL;
if (!is_power_of_2(num))
return NULL;
if (!sq_desc_size || !is_power_of_2(sq_desc_size))
return NULL;
if (!cq_desc_size || !is_power_of_2(cq_desc_size))
return NULL;
ering = kzalloc(sizeof(*ering), GFP_KERNEL);
if (!ering)
return NULL;
ering->edev = edev;
ering->name = name;
ering->index = index;
if (ering_alloc_queues(ering, edev, num, sq_desc_size, cq_desc_size))
goto err_free;
ering_init(ering);
return ering;
err_free:
kfree(ering);
return NULL;
}
void eea_ering_free(struct eea_ring *ering)
{
ering_free_queue(ering->edev, ering->cq.dma_size,
ering->cq.desc, ering->cq.dma_addr);
ering_free_queue(ering->edev, ering->sq.dma_size,
ering->sq.desc, ering->sq.dma_addr);
kfree(ering);
}
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#ifndef __EEA_RING_H__
#define __EEA_RING_H__
#include <linux/dma-mapping.h>
#include "eea_desc.h"
#define EEA_RING_DESC_F_MORE BIT(0)
#define EEA_RING_DESC_F_CQ_PHASE BIT(7)
/* These two values define the bounds for the queue depth returned by the
* hardware.
*/
#define EEA_NET_IO_HW_RING_DEPTH_MAX (32 * 1024)
#define EEA_NET_IO_HW_RING_DEPTH_MIN 128
/* This value constrains the minimum queue depth that the driver configures for
* the hardware, which typically applies to user-provided settings. Naturally,
* the configured depth must also not exceed the maximum capacity supported by
* the hardware.
*/
#define EEA_NET_IO_RING_DEPTH_MIN 64
struct eea_common_desc {
__le16 flags;
__le16 id;
};
struct eea_device;
struct eea_ring_sq {
void *desc;
u16 head;
u16 hw_idx;
u16 shadow_idx;
__le16 shadow_id;
u16 shadow_num;
u8 desc_size;
u8 desc_size_shift;
dma_addr_t dma_addr;
u32 dma_size;
};
struct eea_ring_cq {
void *desc;
u16 head;
u16 hw_idx;
u8 phase;
u8 desc_size_shift;
u8 desc_size;
dma_addr_t dma_addr;
u32 dma_size;
};
struct eea_ring {
const char *name;
struct eea_device *edev;
u32 index;
void __iomem *db;
u16 msix_vec;
u32 num;
u32 num_free;
struct eea_ring_sq sq;
struct eea_ring_cq cq;
};
struct eea_ring *eea_ering_alloc(u32 index, u32 num, struct eea_device *edev,
u8 sq_desc_size, u8 cq_desc_size,
const char *name);
void eea_ering_free(struct eea_ring *ering);
void eea_ering_kick(struct eea_ring *ering);
void *eea_ering_sq_alloc_desc(struct eea_ring *ering, u16 id,
bool is_last, u16 flags);
void *eea_ering_aq_alloc_desc(struct eea_ring *ering);
void eea_ering_sq_commit_desc(struct eea_ring *ering);
void eea_ering_sq_cancel(struct eea_ring *ering);
void eea_ering_cq_ack_desc(struct eea_ring *ering, u32 num);
void eea_ering_irq_active(struct eea_ring *ering, struct eea_ring *tx_ering);
void *eea_ering_cq_get_desc(const struct eea_ring *ering);
#endif
@@ -0,0 +1,814 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <net/netdev_rx_queue.h>
#include <net/page_pool/helpers.h>
#include "eea_adminq.h"
#include "eea_net.h"
#include "eea_pci.h"
#include "eea_ring.h"
#define EEA_ENABLE_F_NAPI BIT(0)
#define EEA_PAGE_FRAGS_NUM 1024
#define EEA_RX_BUF_ALIGN 128
#define EEA_RX_BUF_MAX_LEN (10 * 1024)
struct eea_rx_ctx {
u32 len;
u32 hdr_len;
u16 flags;
bool more;
struct eea_rx_meta *meta;
struct eea_rx_ctx_stats stats;
};
static struct eea_rx_meta *eea_rx_meta_get(struct eea_net_rx *rx)
{
struct eea_rx_meta *meta;
if (!rx->free)
return NULL;
meta = rx->free;
rx->free = meta->next;
return meta;
}
static void eea_rx_meta_put(struct eea_net_rx *rx, struct eea_rx_meta *meta)
{
meta->next = rx->free;
rx->free = meta;
}
static void eea_free_rx_buffer(struct eea_net_rx *rx, struct eea_rx_meta *meta,
bool allow_direct)
{
u32 drain_count;
drain_count = EEA_PAGE_FRAGS_NUM - meta->frags;
if (page_pool_unref_page(meta->page, drain_count) == 0)
page_pool_put_unrefed_page(rx->pp, meta->page, -1,
allow_direct);
meta->page = NULL;
}
static void eea_rx_meta_dma_sync_for_device(struct eea_net_rx *rx,
struct eea_rx_meta *meta)
{
u32 len;
if (meta->sync_for_cpu <= meta->offset + rx->headroom)
return;
len = meta->sync_for_cpu - meta->offset - rx->headroom;
dma_sync_single_for_device(rx->enet->edev->dma_dev,
meta->dma + meta->offset + rx->headroom,
len, DMA_FROM_DEVICE);
meta->sync_for_cpu = 0;
}
static void meta_align_offset(struct eea_net_rx *rx, struct eea_rx_meta *meta)
{
int h, b;
h = rx->headroom;
b = meta->offset + h;
/* For better performance, we align the buffer address to
* EEA_RX_BUF_ALIGN, as required by the device design.
*/
b = ALIGN(b, EEA_RX_BUF_ALIGN);
meta->offset = b - h;
}
static int eea_alloc_rx_buffer(struct eea_net_rx *rx, struct eea_rx_meta *meta)
{
struct page *page;
if (meta->page) {
eea_rx_meta_dma_sync_for_device(rx, meta);
return 0;
}
page = page_pool_dev_alloc_pages(rx->pp);
if (!page)
return -ENOMEM;
page_pool_fragment_page(page, EEA_PAGE_FRAGS_NUM);
meta->page = page;
meta->dma = page_pool_get_dma_addr(page);
meta->offset = 0;
meta->frags = 0;
meta->sync_for_cpu = 0;
meta_align_offset(rx, meta);
return 0;
}
static u32 eea_consume_rx_buffer(struct eea_net_rx *rx,
struct eea_rx_meta *meta,
u32 consumed)
{
u32 offset;
int min;
offset = meta->offset;
meta->offset += consumed;
++meta->frags;
min = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
min += rx->headroom;
min += SKB_DATA_ALIGN(ETH_DATA_LEN);
meta_align_offset(rx, meta);
if (min + meta->offset > PAGE_SIZE) {
eea_free_rx_buffer(rx, meta, true);
return PAGE_SIZE - offset;
}
return meta->offset - offset;
}
static void eea_free_rx_hdr(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
{
struct eea_rx_meta *meta;
int i;
for (i = 0; i < cfg->rx_ring_depth; ++i) {
meta = &rx->meta[i];
meta->hdr_addr = NULL;
if (!meta->hdr_page)
continue;
dma_unmap_page(rx->dma_dev, meta->hdr_dma, PAGE_SIZE,
DMA_FROM_DEVICE);
put_page(meta->hdr_page);
meta->hdr_page = NULL;
}
}
static int eea_alloc_rx_hdr(struct eea_net_init_ctx *ctx, struct eea_net_rx *rx)
{
struct page *hdr_page = NULL;
struct eea_rx_meta *meta;
u32 offset = 0, hdrsize;
struct device *dmadev;
dma_addr_t dma;
int i;
dmadev = ctx->edev->dma_dev;
hdrsize = ctx->cfg.split_hdr;
for (i = 0; i < ctx->cfg.rx_ring_depth; ++i) {
meta = &rx->meta[i];
meta->hdr_page = NULL;
if (!hdr_page || offset + hdrsize > PAGE_SIZE) {
hdr_page = alloc_page(GFP_KERNEL);
if (!hdr_page)
goto err;
dma = dma_map_page(dmadev, hdr_page, 0, PAGE_SIZE,
DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(dmadev, dma))) {
put_page(hdr_page);
goto err;
}
offset = 0;
meta->hdr_page = hdr_page;
}
meta->hdr_dma = dma + offset;
meta->hdr_addr = page_address(hdr_page) + offset;
offset += hdrsize;
}
return 0;
err:
eea_free_rx_hdr(rx, &ctx->cfg);
return -ENOMEM;
}
static void eea_rx_meta_dma_sync_for_cpu(struct eea_net_rx *rx,
struct eea_rx_meta *meta, u32 len)
{
dma_sync_single_for_cpu(rx->enet->edev->dma_dev,
meta->dma + meta->offset + meta->headroom,
len, DMA_FROM_DEVICE);
meta->sync_for_cpu = meta->offset + meta->headroom + len;
}
static int eea_harden_check_overflow(struct eea_rx_ctx *ctx,
struct eea_net *enet)
{
u32 max_len;
max_len = ctx->meta->truesize - ctx->meta->headroom -
ctx->meta->tailroom;
if (unlikely(ctx->len > max_len)) {
pr_debug("%s: rx error: len %u exceeds truesize %u\n",
enet->netdev->name, ctx->len, max_len);
++ctx->stats.length_errors;
return -EINVAL;
}
return 0;
}
static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
{
int err;
err = eea_harden_check_overflow(ctx, enet);
if (err)
return err;
if (ctx->hdr_len) {
if (unlikely(ctx->hdr_len < ETH_HLEN)) {
pr_debug("%s: short hdr %u\n", enet->netdev->name,
ctx->hdr_len);
++ctx->stats.length_errors;
return -EINVAL;
}
if (unlikely(ctx->hdr_len > enet->cfg.split_hdr)) {
pr_debug("%s: rx error: hdr len %u exceeds hdr buffer size %u\n",
enet->netdev->name, ctx->hdr_len,
enet->cfg.split_hdr);
++ctx->stats.length_errors;
return -EINVAL;
}
return 0;
}
if (unlikely(ctx->len < ETH_HLEN)) {
pr_debug("%s: short packet %u\n", enet->netdev->name, ctx->len);
++ctx->stats.length_errors;
return -EINVAL;
}
return 0;
}
static struct sk_buff *eea_build_skb(void *buf, u32 buflen, u32 headroom,
u32 len)
{
struct sk_buff *skb;
skb = build_skb(buf, buflen);
if (unlikely(!skb))
return NULL;
skb_reserve(skb, headroom);
skb_put(skb, len);
return skb;
}
static struct sk_buff *eea_rx_build_split_hdr_skb(struct eea_net_rx *rx,
struct eea_rx_ctx *ctx)
{
struct eea_rx_meta *meta = ctx->meta;
u32 truesize, offset;
struct sk_buff *skb;
struct page *page;
dma_sync_single_for_cpu(rx->enet->edev->dma_dev, meta->hdr_dma,
ctx->hdr_len, DMA_FROM_DEVICE);
skb = napi_alloc_skb(rx->napi, ctx->hdr_len);
if (unlikely(!skb))
return NULL;
skb_put_data(skb, ctx->meta->hdr_addr, ctx->hdr_len);
if (ctx->len) {
page = meta->page;
offset = meta->offset + meta->headroom;
truesize = eea_consume_rx_buffer(rx, meta,
meta->headroom + ctx->len);
skb_add_rx_frag(skb, 0, page, offset, ctx->len, truesize);
}
skb_mark_for_recycle(skb);
return skb;
}
static struct sk_buff *eea_rx_build_skb(struct eea_net_rx *rx,
struct eea_rx_ctx *ctx)
{
struct eea_rx_meta *meta = ctx->meta;
u32 shinfo_size, bufsize, truesize;
struct sk_buff *skb;
struct page *page;
void *buf;
page = meta->page;
shinfo_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
buf = page_address(page) + meta->offset;
bufsize = meta->headroom + SKB_DATA_ALIGN(ctx->len) + shinfo_size;
skb = eea_build_skb(buf, bufsize, meta->headroom, ctx->len);
if (unlikely(!skb))
return NULL;
truesize = eea_consume_rx_buffer(rx, meta, bufsize);
skb_mark_for_recycle(skb);
skb->truesize += truesize - bufsize;
return skb;
}
static void process_remain_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
{
struct eea_net *enet = rx->enet;
struct sk_buff *head_skb;
u32 offset, truesize, nr_frags;
struct page *page;
if (eea_harden_check_overflow(ctx, enet))
goto err;
head_skb = rx->pkt.head_skb;
nr_frags = skb_shinfo(head_skb)->nr_frags;
if (unlikely(nr_frags >= MAX_SKB_FRAGS))
goto err;
offset = ctx->meta->offset + ctx->meta->headroom;
page = ctx->meta->page;
truesize = eea_consume_rx_buffer(rx, ctx->meta,
ctx->meta->headroom + ctx->len);
skb_add_rx_frag(head_skb, nr_frags, page, offset, ctx->len, truesize);
return;
err:
dev_kfree_skb(rx->pkt.head_skb);
++ctx->stats.drops;
rx->pkt.do_drop = true;
rx->pkt.head_skb = NULL;
}
static void process_first_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
{
struct eea_net *enet = rx->enet;
struct sk_buff *skb = NULL;
if (eea_harden_check_size(ctx, enet))
goto err;
rx->pkt.data_valid = ctx->flags & EEA_DESC_F_DATA_VALID;
if (ctx->hdr_len)
skb = eea_rx_build_split_hdr_skb(rx, ctx);
else
skb = eea_rx_build_skb(rx, ctx);
if (unlikely(!skb))
goto err;
rx->pkt.head_skb = skb;
return;
err:
++ctx->stats.drops;
rx->pkt.do_drop = true;
}
static void eea_submit_skb(struct eea_net_rx *rx, struct sk_buff *skb,
struct eea_rx_cdesc *desc)
{
struct eea_net *enet = rx->enet;
if (rx->pkt.data_valid)
skb->ip_summed = CHECKSUM_UNNECESSARY;
if (enet->cfg.ts_cfg.rx_filter == HWTSTAMP_FILTER_ALL)
skb_hwtstamps(skb)->hwtstamp = EEA_DESC_TS(desc) +
enet->hw_ts_offset;
skb_record_rx_queue(skb, rx->index);
skb->protocol = eth_type_trans(skb, enet->netdev);
napi_gro_receive(rx->napi, skb);
}
static int eea_rx_desc_to_ctx(struct eea_net_rx *rx,
struct eea_rx_ctx *ctx,
struct eea_rx_cdesc *desc)
{
u16 id;
ctx->meta = NULL;
id = le16_to_cpu(desc->id);
if (unlikely(id >= rx->ering->num)) {
if (net_ratelimit())
netdev_err(rx->enet->netdev, "rx invalid id %d\n", id);
return -EINVAL;
}
ctx->meta = &rx->meta[id];
if (!ctx->meta->in_use) {
if (net_ratelimit())
netdev_err(rx->enet->netdev, "rx invalid id %d\n", id);
ctx->meta = NULL;
return -EINVAL;
}
ctx->meta->in_use = false;
ctx->len = le16_to_cpu(desc->len);
if (unlikely(ctx->len > ctx->meta->len)) {
if (net_ratelimit())
netdev_err(rx->enet->netdev, "rx invalid len(%d) id:%d\n",
ctx->len, id);
return -EINVAL;
}
ctx->flags = le16_to_cpu(desc->flags);
ctx->hdr_len = 0;
if (ctx->flags & EEA_DESC_F_SPLIT_HDR) {
ctx->hdr_len = le16_to_cpu(desc->len_ex) &
EEA_RX_CDESC_HDR_LEN_MASK;
ctx->stats.split_hdr_bytes += ctx->hdr_len;
++ctx->stats.split_hdr_packets;
}
ctx->more = ctx->flags & EEA_RING_DESC_F_MORE;
return 0;
}
static int eea_cleanrx(struct eea_net_rx *rx, int budget,
struct eea_rx_ctx *ctx)
{
struct eea_rx_cdesc *desc;
struct eea_rx_meta *meta;
int recv, err;
for (recv = 0; recv < budget; ) {
desc = eea_ering_cq_get_desc(rx->ering);
if (!desc)
break;
err = eea_rx_desc_to_ctx(rx, ctx, desc);
if (unlikely(err)) {
if (ctx->meta)
eea_rx_meta_put(rx, ctx->meta);
if (rx->pkt.head_skb) {
dev_kfree_skb(rx->pkt.head_skb);
++ctx->stats.drops;
}
/* A hardware error occurred; we are attempting to
* mitigate the impact. Subsequent packets may be
* corrupted.
*/
ctx->more = false;
goto ack;
}
meta = ctx->meta;
if (unlikely(rx->pkt.do_drop))
goto skip;
eea_rx_meta_dma_sync_for_cpu(rx, meta, ctx->len);
rx->pkt.recv_len += ctx->len;
rx->pkt.recv_len += ctx->hdr_len;
if (!rx->pkt.idx)
process_first_buf(rx, ctx);
else
process_remain_buf(rx, ctx);
++rx->pkt.idx;
if (!ctx->more && rx->pkt.head_skb) {
eea_submit_skb(rx, rx->pkt.head_skb, desc);
ctx->stats.bytes += rx->pkt.recv_len;
++ctx->stats.packets;
}
skip:
eea_rx_meta_put(rx, meta);
ack:
eea_ering_cq_ack_desc(rx->ering, 1);
++ctx->stats.descs;
if (!ctx->more) {
memset(&rx->pkt, 0, sizeof(rx->pkt));
++recv;
}
}
return recv;
}
static void eea_rx_dma_sync_hdr(struct eea_net_rx *rx, dma_addr_t addr)
{
dma_sync_single_for_device(rx->dma_dev, addr,
rx->enet->cfg.split_hdr,
DMA_FROM_DEVICE);
}
/* Only be called from napi. */
static void eea_rx_post(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
{
u32 tailroom, headroom, room, len;
struct eea_rx_meta *meta;
struct eea_rx_desc *desc;
int err = 0, num = 0;
dma_addr_t addr;
tailroom = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
headroom = rx->headroom;
room = headroom + tailroom;
while (true) {
meta = eea_rx_meta_get(rx);
if (!meta)
break;
err = eea_alloc_rx_buffer(rx, meta);
if (err) {
eea_rx_meta_put(rx, meta);
break;
}
len = min_t(u32, PAGE_SIZE - meta->offset - room,
EEA_RX_BUF_MAX_LEN);
len = ALIGN_DOWN(len, SMP_CACHE_BYTES);
addr = meta->dma + meta->offset + headroom;
desc = eea_ering_sq_alloc_desc(rx->ering, meta->id, true, 0);
desc->addr = cpu_to_le64(addr);
desc->len = cpu_to_le16(len);
if (meta->hdr_addr) {
eea_rx_dma_sync_hdr(rx, meta->hdr_dma);
desc->hdr_addr = cpu_to_le64(meta->hdr_dma);
}
eea_ering_sq_commit_desc(rx->ering);
meta->truesize = len + room;
meta->headroom = headroom;
meta->tailroom = tailroom;
meta->len = len;
meta->in_use = true;
++num;
}
if (num) {
eea_ering_kick(rx->ering);
++ctx->stats.kicks;
}
}
static int eea_poll(struct napi_struct *napi, int budget)
{
struct eea_irq_blk *blk = container_of(napi, struct eea_irq_blk, napi);
struct eea_net_rx *rx = blk->rx;
struct eea_net_tx *tx = &rx->enet->tx[rx->index];
struct eea_rx_ctx ctx = {};
bool busy = false;
u32 received;
busy |= eea_poll_tx(tx, budget) >= budget;
received = eea_cleanrx(rx, budget, &ctx);
if (rx->ering->num_free > budget) {
/* Due to the hardware design, there is no notification when
* buffers are exhausted. Therefore, we should proactively
* pre-fill the buffers to avoid starvation.
*/
eea_rx_post(rx, &ctx);
if (rx->ering->num - rx->ering->num_free < budget)
busy = true;
}
eea_update_rx_stats(&rx->stats, &ctx.stats);
busy |= received >= budget;
if (busy)
return budget;
if (napi_complete_done(napi, received))
eea_ering_irq_active(rx->ering, tx->ering);
return received;
}
static void eea_free_rx_buffers(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
{
struct eea_rx_meta *meta;
u32 i;
if (rx->pkt.head_skb) {
dev_kfree_skb(rx->pkt.head_skb);
rx->pkt.head_skb = NULL;
}
for (i = 0; i < cfg->rx_ring_depth; ++i) {
meta = &rx->meta[i];
if (!meta->page)
continue;
eea_free_rx_buffer(rx, meta, false);
}
}
static struct page_pool *eea_create_pp(struct eea_net_init_ctx *ctx, u32 idx)
{
struct page_pool_params pp_params = {0};
pp_params.order = 0;
pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
pp_params.pool_size = ctx->cfg.rx_ring_depth;
pp_params.nid = dev_to_node(ctx->edev->dma_dev);
pp_params.dev = ctx->edev->dma_dev;
pp_params.netdev = ctx->netdev;
pp_params.dma_dir = DMA_FROM_DEVICE;
pp_params.max_len = PAGE_SIZE;
pp_params.queue_idx = idx;
return page_pool_create(&pp_params);
}
static void eea_destroy_page_pool(struct eea_net_rx *rx)
{
if (rx->pp)
page_pool_destroy(rx->pp);
}
void enet_rx_stop(struct eea_net_rx *rx)
{
if (rx->flags & EEA_ENABLE_F_NAPI) {
rx->flags &= ~EEA_ENABLE_F_NAPI;
disable_irq(rx->enet->irq_blks[rx->index].irq);
napi_disable(rx->napi);
page_pool_disable_direct_recycling(rx->pp);
netif_napi_del(rx->napi);
}
}
void enet_rx_start(struct eea_net_rx *rx)
{
netif_napi_add(rx->enet->netdev, rx->napi, eea_poll);
page_pool_enable_direct_recycling(rx->pp, rx->napi);
napi_enable(rx->napi);
rx->flags |= EEA_ENABLE_F_NAPI;
local_bh_disable();
napi_schedule(rx->napi);
local_bh_enable();
enable_irq(rx->enet->irq_blks[rx->index].irq);
}
/* Maybe called before eea_bind_q_and_cfg. So the cfg must be passed. */
void eea_free_rx(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
{
if (!rx)
return;
if (rx->ering) {
eea_ering_free(rx->ering);
rx->ering = NULL;
}
if (rx->meta) {
eea_free_rx_buffers(rx, cfg);
eea_free_rx_hdr(rx, cfg);
kvfree(rx->meta);
rx->meta = NULL;
}
if (rx->pp) {
eea_destroy_page_pool(rx);
rx->pp = NULL;
}
kfree(rx);
}
static void eea_rx_meta_init(struct eea_net_rx *rx, u32 num)
{
struct eea_rx_meta *meta;
int i;
rx->free = NULL;
for (i = 0; i < num; ++i) {
meta = &rx->meta[i];
meta->id = i;
meta->next = rx->free;
rx->free = meta;
}
}
struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx)
{
struct eea_ring *ering;
struct eea_net_rx *rx;
int err;
rx = kzalloc(sizeof(*rx), GFP_KERNEL);
if (!rx)
return rx;
rx->index = idx;
snprintf(rx->name, sizeof(rx->name), "rx.%u", idx);
u64_stats_init(&rx->stats.syncp);
/* ering */
ering = eea_ering_alloc(idx * 2, ctx->cfg.rx_ring_depth, ctx->edev,
ctx->cfg.rx_sq_desc_size,
ctx->cfg.rx_cq_desc_size,
rx->name);
if (!ering)
goto err_free_rx;
rx->ering = ering;
rx->dma_dev = ctx->edev->dma_dev;
/* meta */
rx->meta = kvcalloc(ctx->cfg.rx_ring_depth,
sizeof(*rx->meta), GFP_KERNEL);
if (!rx->meta)
goto err_free_rx;
eea_rx_meta_init(rx, ctx->cfg.rx_ring_depth);
if (ctx->cfg.split_hdr) {
err = eea_alloc_rx_hdr(ctx, rx);
if (err)
goto err_free_rx;
}
rx->pp = eea_create_pp(ctx, idx);
if (IS_ERR(rx->pp)) {
err = PTR_ERR(rx->pp);
rx->pp = NULL;
goto err_free_rx;
}
return rx;
err_free_rx:
eea_free_rx(rx, &ctx->cfg);
return NULL;
}
@@ -0,0 +1,500 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <net/netdev_queues.h>
#include "eea_net.h"
#include "eea_pci.h"
#include "eea_ring.h"
struct eea_sq_free_stats {
u64 packets;
u64 bytes;
};
struct eea_tx_meta {
struct eea_tx_meta *next;
u32 id;
union {
struct sk_buff *skb;
void *data;
};
u32 num;
dma_addr_t dma_addr;
struct eea_tx_desc *desc;
u32 dma_len;
bool unmap;
bool unmap_single;
};
static struct eea_tx_meta *eea_tx_meta_get(struct eea_net_tx *tx)
{
struct eea_tx_meta *meta;
if (!tx->free)
return NULL;
meta = tx->free;
tx->free = meta->next;
return meta;
}
static void eea_tx_meta_put_and_unmap(struct eea_net_tx *tx,
struct eea_tx_meta *meta)
{
struct eea_tx_meta *head;
head = meta;
while (true) {
if (meta->unmap) {
if (meta->unmap_single)
dma_unmap_single(tx->dma_dev, meta->dma_addr,
meta->dma_len, DMA_TO_DEVICE);
else
dma_unmap_page(tx->dma_dev, meta->dma_addr,
meta->dma_len, DMA_TO_DEVICE);
}
if (meta->next) {
meta = meta->next;
continue;
}
break;
}
meta->next = tx->free;
tx->free = head;
}
static void eea_meta_free_xmit(struct eea_net_tx *tx,
struct eea_tx_meta *meta,
int budget,
struct eea_tx_cdesc *desc,
struct eea_sq_free_stats *stats)
{
struct sk_buff *skb = meta->skb;
if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && desc)) {
struct skb_shared_hwtstamps ts = {};
ts.hwtstamp = EEA_DESC_TS(desc) + tx->enet->hw_ts_offset;
skb_tstamp_tx(skb, &ts);
}
++stats->packets;
stats->bytes += meta->skb->len;
napi_consume_skb(meta->skb, budget);
meta->data = NULL;
}
static int eea_clean_tx(struct eea_net_tx *tx, int budget)
{
struct eea_sq_free_stats stats = {0};
struct eea_tx_cdesc *desc;
struct eea_tx_meta *meta;
int desc_n;
u16 id;
while (stats.packets < budget) {
desc = eea_ering_cq_get_desc(tx->ering);
if (!desc)
break;
id = le16_to_cpu(desc->id);
if (unlikely(id >= tx->ering->num)) {
if (net_ratelimit())
netdev_err(tx->enet->netdev, "tx invalid id %d\n",
id);
eea_ering_cq_ack_desc(tx->ering, 1);
continue;
}
meta = &tx->meta[id];
if (meta->data) {
eea_tx_meta_put_and_unmap(tx, meta);
eea_meta_free_xmit(tx, meta, budget, desc, &stats);
desc_n = meta->num;
} else {
if (net_ratelimit())
netdev_err(tx->enet->netdev,
"tx meta->data is null. id %d num: %d\n",
meta->id, meta->num);
desc_n = 1;
}
eea_ering_cq_ack_desc(tx->ering, desc_n);
}
if (stats.packets) {
u64_stats_update_begin(&tx->stats.syncp);
u64_stats_add(&tx->stats.bytes, stats.bytes);
u64_stats_add(&tx->stats.packets, stats.packets);
u64_stats_update_end(&tx->stats.syncp);
}
return stats.packets;
}
int eea_poll_tx(struct eea_net_tx *tx, int budget)
{
struct eea_net *enet = tx->enet;
u32 index = tx - enet->tx;
struct netdev_queue *txq;
int num;
txq = netdev_get_tx_queue(enet->netdev, index);
__netif_tx_lock(txq, smp_processor_id());
num = eea_clean_tx(tx, budget);
if (netif_tx_queue_stopped(txq) &&
tx->ering->num_free >= MAX_SKB_FRAGS + 2)
netif_tx_wake_queue(txq);
__netif_tx_unlock(txq);
return num;
}
static int eea_fill_desc_from_skb(const struct sk_buff *skb,
struct eea_tx_desc *desc)
{
if (skb_is_gso(skb)) {
struct skb_shared_info *sinfo = skb_shinfo(skb);
desc->gso_size = cpu_to_le16(sinfo->gso_size);
if (sinfo->gso_type & SKB_GSO_TCPV4)
desc->gso_type = EEA_TX_GSO_TCPV4;
else if (sinfo->gso_type & SKB_GSO_TCPV6)
desc->gso_type = EEA_TX_GSO_TCPV6;
else if (sinfo->gso_type & SKB_GSO_UDP_L4)
desc->gso_type = EEA_TX_GSO_UDP_L4;
else
return -EINVAL;
if (sinfo->gso_type & SKB_GSO_TCP_ECN)
desc->gso_type |= EEA_TX_GSO_ECN;
} else {
desc->gso_type = EEA_TX_GSO_NONE;
}
if (skb->ip_summed == CHECKSUM_PARTIAL) {
desc->csum_start = cpu_to_le16(skb_checksum_start_offset(skb));
desc->csum_offset = cpu_to_le16(skb->csum_offset);
}
return 0;
}
static struct eea_tx_meta *__eea_tx_desc_fill(struct eea_net_tx *tx,
struct eea_tx_meta *head_meta,
dma_addr_t addr, u32 data_len,
u32 dma_len, bool last,
void *data, u16 flags,
bool unmap)
{
struct eea_tx_meta *meta;
struct eea_tx_desc *desc;
meta = eea_tx_meta_get(tx);
desc = eea_ering_sq_alloc_desc(tx->ering, meta->id, last, flags);
desc->addr = cpu_to_le64(addr);
desc->len = cpu_to_le16(data_len);
meta->next = NULL;
meta->dma_len = dma_len;
meta->dma_addr = addr;
meta->data = data;
meta->num = 1;
meta->desc = desc;
meta->unmap = unmap;
meta->unmap_single = false;
if (head_meta) {
meta->next = head_meta->next;
head_meta->next = meta;
++head_meta->num;
}
return meta;
}
static struct eea_tx_meta *eea_tx_desc_fill(struct eea_net_tx *tx,
struct eea_tx_meta *head_meta,
dma_addr_t addr, u32 length,
bool is_last, void *data, u16 flags)
{
struct eea_tx_meta *meta;
u16 len, last;
WARN_ON_ONCE(length >= 2 * USHRT_MAX);
/* Since eea does not support BIG TCP, the maximum GSO size is capped at
* 64KB. Consequently, a single skb buffer (head or fragment) will not
* require more than two descriptors
*/
if (length > USHRT_MAX) {
len = USHRT_MAX;
last = false;
} else {
len = length;
last = is_last;
}
meta = __eea_tx_desc_fill(tx, head_meta, addr, len, length,
last, data, flags, true);
if (length > USHRT_MAX) {
if (!head_meta)
head_meta = meta;
addr += USHRT_MAX;
len = length - USHRT_MAX;
__eea_tx_desc_fill(tx, head_meta, addr, len, 0, is_last,
NULL, 0, false);
}
return meta;
}
static int eea_tx_add_skb_frag(struct eea_net_tx *tx,
struct eea_tx_meta *head_meta,
const skb_frag_t *frag, bool is_last)
{
u32 len = skb_frag_size(frag);
dma_addr_t addr;
addr = skb_frag_dma_map(tx->dma_dev, frag, 0, len, DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
return -ENOMEM;
eea_tx_desc_fill(tx, head_meta, addr, len, is_last, NULL, 0);
return 0;
}
static int eea_tx_post_skb(struct eea_net_tx *tx, struct sk_buff *skb)
{
const struct skb_shared_info *shinfo = skb_shinfo(skb);
u32 hlen = skb_headlen(skb);
struct eea_tx_meta *meta;
const skb_frag_t *frag;
dma_addr_t addr;
u32 len = hlen;
int i, err;
u16 flags;
bool last;
if (len) {
addr = dma_map_single(tx->dma_dev, skb->data, len,
DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
return -ENOMEM;
last = !shinfo->nr_frags;
i = 0;
} else {
/* The net stack will never submit an skb with an skb->len of
* 0. If the head len is 0, the number of frags must be greater
* than 0.
*/
frag = &shinfo->frags[0];
len = skb_frag_size(frag);
addr = skb_frag_dma_map(tx->dma_dev, frag, 0, len,
DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
return -ENOMEM;
last = shinfo->nr_frags == 1;
i = 1;
}
flags = skb->ip_summed == CHECKSUM_PARTIAL ? EEA_DESC_F_DO_CSUM : 0;
meta = eea_tx_desc_fill(tx, NULL, addr, len, last, skb, flags);
meta->unmap_single = !!hlen;
err = eea_fill_desc_from_skb(skb, meta->desc);
if (err)
goto err_cancel;
for (; i < shinfo->nr_frags; i++) {
frag = &shinfo->frags[i];
bool is_last = i == (shinfo->nr_frags - 1);
err = eea_tx_add_skb_frag(tx, meta, frag, is_last);
if (err)
goto err_cancel;
}
eea_ering_sq_commit_desc(tx->ering);
u64_stats_update_begin(&tx->stats.syncp);
u64_stats_add(&tx->stats.descs, meta->num);
u64_stats_update_end(&tx->stats.syncp);
return 0;
err_cancel:
eea_ering_sq_cancel(tx->ering);
eea_tx_meta_put_and_unmap(tx, meta);
meta->data = NULL;
return err;
}
static void eea_tx_kick(struct eea_net_tx *tx)
{
eea_ering_kick(tx->ering);
u64_stats_update_begin(&tx->stats.syncp);
u64_stats_inc(&tx->stats.kicks);
u64_stats_update_end(&tx->stats.syncp);
}
static int eea_tx_check_free_num(struct eea_net_tx *tx,
struct netdev_queue *txq)
{
int n;
/* MAX_SKB_FRAGS + 1: Covers the skb linear head and all paged fragments
* 1: Extra slot for a head or fragment that exceeds 64KB.
*/
n = MAX_SKB_FRAGS + 2;
return netif_txq_maybe_stop(txq, tx->ering->num_free, n, n);
}
netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
{
struct eea_net *enet = netdev_priv(netdev);
int qnum = skb_get_queue_mapping(skb);
struct eea_net_tx *tx = &enet->tx[qnum];
struct netdev_queue *txq;
int err, enable;
txq = netdev_get_tx_queue(netdev, qnum);
enable = eea_tx_check_free_num(tx, txq);
if (!enable)
return NETDEV_TX_BUSY;
err = eea_tx_post_skb(tx, skb);
if (unlikely(err)) {
u64_stats_update_begin(&tx->stats.syncp);
u64_stats_inc(&tx->stats.drops);
u64_stats_update_end(&tx->stats.syncp);
dev_kfree_skb_any(skb);
} else {
if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP))
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
skb_tx_timestamp(skb);
}
/* NETDEV_TX_BUSY is expensive. So stop advancing the TX queue. */
eea_tx_check_free_num(tx, txq);
if (!netdev_xmit_more() || netif_xmit_stopped(txq))
eea_tx_kick(tx);
return NETDEV_TX_OK;
}
static void eea_free_meta(struct eea_net_tx *tx, struct eea_net_cfg *cfg)
{
struct eea_sq_free_stats stats = {0};
struct eea_tx_meta *meta;
int i;
while ((meta = eea_tx_meta_get(tx)))
meta->skb = NULL;
for (i = 0; i < cfg->tx_ring_depth; i++) {
meta = &tx->meta[i];
if (!meta->skb)
continue;
eea_tx_meta_put_and_unmap(tx, meta);
eea_meta_free_xmit(tx, meta, 0, NULL, &stats);
}
kvfree(tx->meta);
tx->meta = NULL;
}
/* Maybe called before eea_bind_q_and_cfg. So the cfg must be passed. */
void eea_free_tx(struct eea_net_tx *tx, struct eea_net_cfg *cfg)
{
if (!tx)
return;
if (tx->ering) {
eea_ering_free(tx->ering);
tx->ering = NULL;
}
if (tx->meta)
eea_free_meta(tx, cfg);
}
int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx)
{
struct eea_tx_meta *meta;
struct eea_ring *ering;
u32 i;
u64_stats_init(&tx->stats.syncp);
snprintf(tx->name, sizeof(tx->name), "tx.%u", idx);
ering = eea_ering_alloc(idx * 2 + 1, ctx->cfg.tx_ring_depth, ctx->edev,
ctx->cfg.tx_sq_desc_size,
ctx->cfg.tx_cq_desc_size,
tx->name);
if (!ering)
goto err_free_tx;
tx->ering = ering;
tx->index = idx;
tx->dma_dev = ctx->edev->dma_dev;
/* meta */
tx->meta = kvcalloc(ctx->cfg.tx_ring_depth,
sizeof(*tx->meta), GFP_KERNEL);
if (!tx->meta)
goto err_free_tx;
for (i = 0; i < ctx->cfg.tx_ring_depth; ++i) {
meta = &tx->meta[i];
meta->id = i;
meta->next = tx->free;
tx->free = meta;
}
return 0;
err_free_tx:
eea_free_tx(tx, &ctx->cfg);
return -ENOMEM;
}