* [PATCH net-next v38 7/8] eea: introduce ethtool support
From: Xuan Zhuo @ 2026-04-07 12:20 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li, Andrew Lunn
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit introduces ethtool support.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/Makefile | 1 +
.../net/ethernet/alibaba/eea/eea_ethtool.c | 252 ++++++++++++++++++
.../net/ethernet/alibaba/eea/eea_ethtool.h | 49 ++++
drivers/net/ethernet/alibaba/eea/eea_net.c | 1 +
drivers/net/ethernet/alibaba/eea/eea_net.h | 5 +
drivers/net/ethernet/alibaba/eea/eea_rx.c | 32 ++-
drivers/net/ethernet/alibaba/eea/eea_tx.c | 27 +-
7 files changed, 362 insertions(+), 5 deletions(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.h
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index fa34a005fa01..8f8fbb8d2d9a 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -4,5 +4,6 @@ eea-y := eea_ring.o \
eea_net.o \
eea_pci.o \
eea_adminq.o \
+ eea_ethtool.o \
eea_tx.o \
eea_rx.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ethtool.c b/drivers/net/ethernet/alibaba/eea/eea_ethtool.c
new file mode 100644
index 000000000000..91255a72ddc6
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ethtool.c
@@ -0,0 +1,252 @@
+// 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"
+
+#define EEA_NET_MIN_DEPTH 64
+
+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_MIN_DEPTH ||
+ ring->tx_pending < EEA_NET_MIN_DEPTH)
+ 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 = kernel_ring->tcp_data_split == ETHTOOL_TCP_DATA_SPLIT_ENABLED;
+ 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));
+}
+
+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_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,
+};
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ethtool.h b/drivers/net/ethernet/alibaba/eea/eea_ethtool.h
new file mode 100644
index 000000000000..a437065d1cab
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ethtool.h
@@ -0,0 +1,49 @@
+/* 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 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
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index c84b2d9297a0..bc319c4c2983 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -614,6 +614,7 @@ static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
}
netdev->netdev_ops = &eea_netdev;
+ netdev->ethtool_ops = &eea_ethtool_ops;
SET_NETDEV_DEV(netdev, edev->dma_dev);
enet = netdev_priv(netdev);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index 4b10246d1a6f..84d26df79e59 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -12,6 +12,7 @@
#include <linux/netdevice.h>
#include "eea_adminq.h"
+#include "eea_ethtool.h"
#include "eea_ring.h"
#define EEA_VER_MAJOR 1
@@ -33,6 +34,8 @@ struct eea_net_tx {
u32 index;
char name[16];
+
+ struct eea_tx_stats stats;
};
struct eea_rx_meta {
@@ -85,6 +88,8 @@ struct eea_net_rx {
struct napi_struct *napi;
+ struct eea_rx_stats stats;
+
char name[16];
struct eea_net_rx_pkt_ctx pkt;
diff --git a/drivers/net/ethernet/alibaba/eea/eea_rx.c b/drivers/net/ethernet/alibaba/eea/eea_rx.c
index ada89bd9f177..9f63e5d8c1f3 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_rx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_rx.c
@@ -29,6 +29,8 @@ struct eea_rx_ctx {
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)
@@ -232,6 +234,7 @@ static int eea_harden_check_overflow(struct eea_rx_ctx *ctx,
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;
}
@@ -250,6 +253,7 @@ static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
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;
}
@@ -257,6 +261,7 @@ static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
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;
}
@@ -265,6 +270,7 @@ static int eea_harden_check_size(struct eea_rx_ctx *ctx, struct eea_net *enet)
if (unlikely(ctx->len < ETH_HLEN)) {
pr_debug("%s: short packet %u\n", enet->netdev->name, ctx->len);
+ ++ctx->stats.length_errors;
return -EINVAL;
}
@@ -373,6 +379,7 @@ static void process_remain_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
err:
dev_kfree_skb(rx->pkt.head_skb);
+ ++ctx->stats.drops;
rx->pkt.do_drop = true;
rx->pkt.head_skb = NULL;
}
@@ -400,6 +407,7 @@ static void process_first_buf(struct eea_net_rx *rx, struct eea_rx_ctx *ctx)
return;
err:
+ ++ctx->stats.drops;
rx->pkt.do_drop = true;
}
@@ -449,6 +457,8 @@ static int eea_rx_desc_to_ctx(struct eea_net_rx *rx,
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;
@@ -473,8 +483,10 @@ static int eea_cleanrx(struct eea_net_rx *rx, int budget,
if (ctx->meta)
eea_rx_meta_put(rx, ctx->meta);
- if (rx->pkt.idx)
+ if (rx->pkt.head_skb) {
dev_kfree_skb(rx->pkt.head_skb);
+ ++ctx->stats.drops;
+ }
ctx->more = false;
goto ack;
@@ -497,13 +509,17 @@ static int eea_cleanrx(struct eea_net_rx *rx, int budget,
++rx->pkt.idx;
- if (!ctx->more && rx->pkt.head_skb)
+ 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:
ering_cq_ack_desc(rx->ering, 1);
+ ++ctx->stats.descs;
if (!ctx->more) {
memset(&rx->pkt, 0, sizeof(rx->pkt));
@@ -528,6 +544,7 @@ static bool eea_rx_post(struct eea_net_rx *rx)
struct eea_rx_meta *meta;
struct eea_rx_desc *desc;
int err = 0, num = 0;
+ unsigned long flags;
dma_addr_t addr;
tailroom = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
@@ -568,9 +585,14 @@ static bool eea_rx_post(struct eea_net_rx *rx)
++num;
}
- if (num)
+ if (num) {
ering_kick(rx->ering);
+ flags = u64_stats_update_begin_irqsave(&rx->stats.syncp);
+ u64_stats_inc(&rx->stats.kicks);
+ u64_stats_update_end_irqrestore(&rx->stats.syncp, flags);
+ }
+
/* true means busy, napi should be called again. */
return !!err;
}
@@ -591,6 +613,8 @@ static int eea_poll(struct napi_struct *napi, int budget)
if (rx->ering->num_free > budget)
busy |= eea_rx_post(rx);
+ eea_update_rx_stats(&rx->stats, &ctx.stats);
+
busy |= received >= budget;
if (busy)
@@ -728,6 +752,8 @@ struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx)
rx->index = idx;
snprintf(rx->name, sizeof(rx->name), "rx.%u", idx);
+ u64_stats_init(&rx->stats.syncp);
+
/* ering */
ering = ering_alloc(idx * 2, ctx->cfg.rx_ring_depth, ctx->edev,
ctx->cfg.rx_sq_desc_size,
diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c
index acfe4c8ae59e..d7670650db15 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_tx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_tx.c
@@ -89,6 +89,7 @@ static void eea_meta_free_xmit(struct eea_net_tx *tx,
}
++stats->packets;
+ stats->bytes += meta->skb->len;
napi_consume_skb(meta->skb, budget);
meta->data = NULL;
@@ -130,6 +131,13 @@ static bool eea_clean_tx(struct eea_net_tx *tx, int budget)
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 == budget;
}
@@ -301,6 +309,10 @@ static int eea_tx_post_skb(struct eea_net_tx *tx, struct sk_buff *skb)
meta->num = shinfo->nr_frags + 1;
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:
@@ -313,6 +325,10 @@ static int eea_tx_post_skb(struct eea_net_tx *tx, struct sk_buff *skb)
static void eea_tx_kick(struct eea_net_tx *tx)
{
ering_kick(tx->ering);
+
+ u64_stats_update_begin(&tx->stats.syncp);
+ u64_stats_inc(&tx->stats.kicks);
+ u64_stats_update_end(&tx->stats.syncp);
}
netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
@@ -326,10 +342,15 @@ netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
txq = netdev_get_tx_queue(netdev, qnum);
err = eea_tx_post_skb(tx, skb);
- if (unlikely(err))
+ 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
+ } else {
skb_tx_timestamp(skb);
+ }
/* NETDEV_TX_BUSY is expensive. So stop advancing the TX queue.
* MAX_SKB_FRAGS + 1: Covers the skb linear head and all paged fragments
@@ -389,6 +410,8 @@ int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx)
struct eea_ring *ering;
u32 i;
+ u64_stats_init(&tx->stats.syncp);
+
snprintf(tx->name, sizeof(tx->name), "tx.%u", idx);
ering = ering_alloc(idx * 2 + 1, ctx->cfg.tx_ring_depth, ctx->edev,
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 1/8] eea: introduce PCI framework
From: Xuan Zhuo @ 2026-04-07 12:19 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit implements the EEA PCI probe functionality.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
MAINTAINERS | 8 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/alibaba/Kconfig | 28 ++
drivers/net/ethernet/alibaba/Makefile | 5 +
drivers/net/ethernet/alibaba/eea/Makefile | 3 +
drivers/net/ethernet/alibaba/eea/eea_pci.c | 447 +++++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_pci.h | 50 +++
8 files changed, 543 insertions(+)
create mode 100644 drivers/net/ethernet/alibaba/Kconfig
create mode 100644 drivers/net/ethernet/alibaba/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 9d1e6d3acbac..b78a31ecd0d4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -804,6 +804,14 @@ S: Maintained
F: Documentation/i2c/busses/i2c-ali1563.rst
F: drivers/i2c/busses/i2c-ali1563.c
+ALIBABA ELASTIC ETHERNET ADAPTER DRIVER
+M: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
+M: Wen Gu <guwen@linux.alibaba.com>
+R: Philo Lu <lulie@linux.alibaba.com>
+L: netdev@vger.kernel.org
+S: Maintained
+F: drivers/net/ethernet/alibaba/eea
+
ALIBABA ELASTIC RDMA DRIVER
M: Cheng Xu <chengyou@linux.alibaba.com>
M: Kai Shen <kaishen@linux.alibaba.com>
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index bdc29d143160..63e7ddfd4aaa 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -22,6 +22,7 @@ source "drivers/net/ethernet/aeroflex/Kconfig"
source "drivers/net/ethernet/agere/Kconfig"
source "drivers/net/ethernet/airoha/Kconfig"
source "drivers/net/ethernet/alacritech/Kconfig"
+source "drivers/net/ethernet/alibaba/Kconfig"
source "drivers/net/ethernet/allwinner/Kconfig"
source "drivers/net/ethernet/altera/Kconfig"
source "drivers/net/ethernet/amazon/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index 6bffb60ba644..b1eb69814333 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_NET_VENDOR_ADI) += adi/
obj-$(CONFIG_NET_VENDOR_AGERE) += agere/
obj-$(CONFIG_NET_VENDOR_AIROHA) += airoha/
obj-$(CONFIG_NET_VENDOR_ALACRITECH) += alacritech/
+obj-$(CONFIG_NET_VENDOR_ALIBABA) += alibaba/
obj-$(CONFIG_NET_VENDOR_ALLWINNER) += allwinner/
obj-$(CONFIG_ALTERA_TSE) += altera/
obj-$(CONFIG_NET_VENDOR_AMAZON) += amazon/
diff --git a/drivers/net/ethernet/alibaba/Kconfig b/drivers/net/ethernet/alibaba/Kconfig
new file mode 100644
index 000000000000..9bd8cc9fd203
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/Kconfig
@@ -0,0 +1,28 @@
+#
+# Alibaba network device configuration
+#
+
+config NET_VENDOR_ALIBABA
+ bool "Alibaba Devices"
+ default y
+ help
+ If you have a network (Ethernet) device belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about Alibaba devices. If you say Y, you will be asked
+ for your specific device in the following questions.
+
+if NET_VENDOR_ALIBABA
+
+config EEA
+ tristate "Alibaba Elastic Ethernet Adapter support"
+ depends on PCI_MSI
+ depends on 64BIT
+ select PAGE_POOL
+ help
+ This driver supports Alibaba Elastic Ethernet Adapter.
+
+ To compile this driver as a module, choose M here.
+
+endif #NET_VENDOR_ALIBABA
diff --git a/drivers/net/ethernet/alibaba/Makefile b/drivers/net/ethernet/alibaba/Makefile
new file mode 100644
index 000000000000..7980525cb086
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the Alibaba network device drivers.
+#
+
+obj-$(CONFIG_EEA) += eea/
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
new file mode 100644
index 000000000000..cf2acf1733fd
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -0,0 +1,3 @@
+
+obj-$(CONFIG_EEA) += eea.o
+eea-y := eea_pci.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
new file mode 100644
index 000000000000..3f49a2e53223
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.c
@@ -0,0 +1,447 @@
+// 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_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
+
+struct eea_pci_cfg {
+ __le32 reserve0;
+ __le32 reserve1;
+ __le32 drv_f_idx;
+ __le32 drv_f;
+
+#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;
+
+ char ha_irq_name[32];
+ u8 reset_pos;
+};
+
+#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_write32(reg, item, val) iowrite32(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_readq(reg, item) readq(cfg_pointer(reg, item))
+
+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_dev_id(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);
+
+ /* A longer timeout is set here to handle edge cases, though it should
+ * return promptly in most scenarios.
+ */
+ 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.
+ */
+ err = read_poll_timeout(cfg_read8, val, val & EEA_S_OK, 20,
+ EEA_RESET_TIMEOUT_US,
+ false, ep_dev->reg, device_status);
+
+ /* Surprise PCIe Removal */
+ if (val == 0xFF)
+ 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);
+ 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;
+
+ 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_release_regions(pci_dev);
+ pci_disable_device(pci_dev);
+}
+
+static int eea_pci_setup(struct pci_dev *pci_dev, struct eea_pci_device *ep_dev)
+{
+ int err, n, ret, len;
+
+ 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;
+
+ pci_set_master(pci_dev);
+
+ 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_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;
+ }
+
+ 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_unmap_reg;
+ }
+
+ ep_dev->edev.db_blk_size = cfg_read32(ep_dev->reg, db_blk_size);
+ if (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_unmap_reg;
+ }
+
+ 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",
+ pci_resource_len(pci_dev, 0));
+ err = -EINVAL;
+ goto err_unmap_reg;
+ }
+
+ /* 2: adminq, error handle*/
+ n = ep_dev->edev.rx_num + ep_dev->edev.tx_num + 2;
+ ret = pci_alloc_irq_vectors(ep_dev->pci_dev, n, n, PCI_IRQ_MSIX);
+ if (ret != n) {
+ err = ret;
+ goto err_unmap_reg;
+ }
+
+ ep_dev->msix_vec_n = ret;
+
+ return 0;
+
+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;
+}
+
+u64 eea_pci_device_ts(struct eea_device *edev)
+{
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+
+ return cfg_readq(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, BIT(0) | BIT(1));
+
+ err = eea_negotiate(edev);
+ if (err)
+ goto err;
+
+ /* do net device probe ... */
+
+ 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)
+{
+ int err;
+
+ pci_set_drvdata(pci_dev, ep_dev);
+
+ 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;
+
+ return 0;
+
+err_pci_rel:
+ eea_pci_release_resource(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 device *dev = get_device(&ep_dev->pci_dev->dev);
+
+ pci_disable_sriov(pci_dev);
+
+ 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;
+
+ err = __eea_pci_probe(pci_dev, ep_dev);
+ if (err) {
+ 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);
+
+ __eea_pci_remove(pci_dev);
+
+ pci_set_drvdata(pci_dev, NULL);
+ kfree(ep_dev);
+}
+
+static int eea_pci_sriov_configure(struct pci_dev *pci_dev, int num_vfs)
+{
+ int ret;
+
+ if (pci_vfs_assigned(pci_dev))
+ return -EPERM;
+
+ if (num_vfs == 0) {
+ pci_disable_sriov(pci_dev);
+ return 0;
+ }
+
+ ret = pci_enable_sriov(pci_dev, num_vfs);
+ if (ret < 0)
+ return ret;
+
+ return num_vfs;
+}
+
+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 = "eea",
+ .id_table = eea_pci_id_table,
+ .probe = eea_pci_probe,
+ .remove = eea_pci_remove,
+ .sriov_configure = eea_pci_sriov_configure,
+};
+
+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");
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
new file mode 100644
index 000000000000..be4e75b4ed2f
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.h
@@ -0,0 +1,50 @@
+/* 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>
+
+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;
+
+ 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_dev_id(struct eea_device *edev);
+
+int eea_device_reset(struct eea_device *dev);
+int eea_pci_set_aq_up(struct eea_device *dev);
+
+u64 eea_pci_device_ts(struct eea_device *edev);
+
+void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off);
+#endif
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 8/8] eea: introduce callback for ndo_get_stats64
From: Xuan Zhuo @ 2026-04-07 12:20 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
This commit introduces ndo_get_stats64 support.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/eea_net.c | 57 ++++++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 5 ++
2 files changed, 62 insertions(+)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index bc319c4c2983..3585b5e74805 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -113,6 +113,8 @@ static void eea_bind_q_and_cfg(struct eea_net *enet,
struct eea_net_tx *tx;
int i;
+ spin_lock(&enet->stats_lock);
+
enet->cfg = ctx->cfg;
enet->rx = ctx->rx;
enet->tx = ctx->tx;
@@ -132,6 +134,8 @@ static void eea_bind_q_and_cfg(struct eea_net *enet,
blk->rx = rx;
}
+
+ spin_unlock(&enet->stats_lock);
}
static void eea_unbind_q_and_cfg(struct eea_net *enet,
@@ -141,6 +145,8 @@ static void eea_unbind_q_and_cfg(struct eea_net *enet,
struct eea_net_rx *rx;
int i;
+ spin_lock(&enet->stats_lock);
+
ctx->cfg = enet->cfg;
ctx->rx = enet->rx;
ctx->tx = enet->tx;
@@ -157,6 +163,8 @@ static void eea_unbind_q_and_cfg(struct eea_net *enet,
blk->rx = NULL;
}
+
+ spin_unlock(&enet->stats_lock);
}
static void eea_free_rxtx_q_mem(struct eea_net_init_ctx *ctx)
@@ -342,6 +350,50 @@ static int eea_netdev_open(struct net_device *netdev)
return err;
}
+static void eea_stats(struct net_device *netdev, struct rtnl_link_stats64 *tot)
+{
+ struct eea_net *enet = netdev_priv(netdev);
+ u64 packets, bytes;
+ 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);
+ } while (u64_stats_fetch_retry(&rx->stats.syncp,
+ start));
+
+ tot->rx_packets += packets;
+ tot->rx_bytes += bytes;
+ }
+ }
+
+ 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);
+ } while (u64_stats_fetch_retry(&tx->stats.syncp,
+ start));
+
+ tot->tx_packets += packets;
+ tot->tx_bytes += bytes;
+ }
+ }
+
+ spin_unlock(&enet->stats_lock);
+}
+
/* resources: ring, buffers, irq */
int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx)
{
@@ -349,7 +401,9 @@ int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx)
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;
}
@@ -597,6 +651,7 @@ static const struct net_device_ops eea_netdev = {
.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,
};
@@ -630,6 +685,8 @@ static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
return NULL;
}
+ spin_lock_init(&enet->stats_lock);
+
return enet;
}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index 84d26df79e59..9bf05088e87c 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -163,6 +163,11 @@ struct eea_net {
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);
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 2/8] eea: introduce ring and descriptor structures
From: Xuan Zhuo @ 2026-04-07 12:19 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit introduces the ring and descriptor implementations.
These structures and ring APIs are used by the RX, TX, and admin queues.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/Makefile | 3 +-
drivers/net/ethernet/alibaba/eea/eea_desc.h | 131 +++++++++++
drivers/net/ethernet/alibaba/eea/eea_ring.c | 243 ++++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_ring.h | 86 +++++++
4 files changed, 462 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_desc.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.h
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index cf2acf1733fd..e5e4007810a6 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_EEA) += eea.o
-eea-y := eea_pci.o
+eea-y := eea_ring.o \
+ eea_pci.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_desc.h b/drivers/net/ethernet/alibaba/eea/eea_desc.h
new file mode 100644
index 000000000000..4ce1e57c906b
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_desc.h
@@ -0,0 +1,131 @@
+/* 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 {
+ __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;
+ __le64 reserved2;
+ __le64 reserved3;
+};
+
+#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
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ring.c b/drivers/net/ethernet/alibaba/eea/eea_ring.c
new file mode 100644
index 000000000000..b5003924264c
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ring.c
@@ -0,0 +1,243 @@
+// 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 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, (void __iomem *)ering->db);
+}
+
+void *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 *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 *ering_aq_alloc_desc(struct eea_ring *ering)
+{
+ struct eea_ring_sq *sq = &ering->sq;
+ struct eea_common_desc *desc;
+
+ 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 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 ering_sq_cancel(struct eea_ring *ering)
+{
+ ering->sq.shadow_num = 0;
+}
+
+/* cq api */
+void 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 */
+bool 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, (void __iomem *)ering->db);
+
+ return true;
+}
+
+/* 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 *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 (!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 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);
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_ring.h b/drivers/net/ethernet/alibaba/eea/eea_ring.h
new file mode 100644
index 000000000000..8e4ba3032699
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_ring.h
@@ -0,0 +1,86 @@
+/* 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)
+
+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 *ering_alloc(u32 index, u32 num, struct eea_device *edev,
+ u8 sq_desc_size, u8 cq_desc_size,
+ const char *name);
+void ering_free(struct eea_ring *ering);
+bool ering_kick(struct eea_ring *ering);
+
+void *ering_sq_alloc_desc(struct eea_ring *ering, u16 id,
+ bool is_last, u16 flags);
+void *ering_aq_alloc_desc(struct eea_ring *ering);
+void ering_sq_commit_desc(struct eea_ring *ering);
+void ering_sq_cancel(struct eea_ring *ering);
+
+void ering_cq_ack_desc(struct eea_ring *ering, u32 num);
+
+void ering_irq_active(struct eea_ring *ering, struct eea_ring *tx_ering);
+void *ering_cq_get_desc(const struct eea_ring *ering);
+#endif
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 4/8] eea: create/destroy rx,tx queues for netdevice open and stop
From: Xuan Zhuo @ 2026-04-07 12:19 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit introduces the implementation for the netdevice open and
stop.
This commit introduces HA to restore the device when error occurs,
but in HA scenarios the driver can't ensure to restore the status
correctly.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/Makefile | 4 +-
drivers/net/ethernet/alibaba/eea/eea_net.c | 570 ++++++++++++++++++++-
drivers/net/ethernet/alibaba/eea/eea_net.h | 48 +-
drivers/net/ethernet/alibaba/eea/eea_pci.c | 228 ++++++++-
drivers/net/ethernet/alibaba/eea/eea_pci.h | 20 +
drivers/net/ethernet/alibaba/eea/eea_rx.c | 264 ++++++++++
drivers/net/ethernet/alibaba/eea/eea_tx.c | 102 ++++
7 files changed, 1225 insertions(+), 11 deletions(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_rx.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_tx.c
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index 91f318e8e046..fa34a005fa01 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -3,4 +3,6 @@ obj-$(CONFIG_EEA) += eea.o
eea-y := eea_ring.o \
eea_net.o \
eea_pci.o \
- eea_adminq.o
+ eea_adminq.o \
+ eea_tx.o \
+ eea_rx.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index c30b32aa40ba..1ae512a0afdc 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -19,6 +19,462 @@
#define EEA_SPLIT_HDR_SIZE 128
#define EEA_NET_IO_RING_DEPTH 32000
+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;
+
+ 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;
+ }
+}
+
+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;
+
+ 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;
+ }
+}
+
+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);
+
+ ctx->cfg.tx_cq_desc_size /= 2;
+
+ if (!ctx->cfg.split_hdr)
+ ctx->cfg.rx_sq_desc_size /= 2;
+
+ 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_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;
+}
+
+/* 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) {
+ enet->cfg = ctx->cfg;
+ 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_queue_status *qstatus;
+ struct eea_aq_dev_status *dstatus;
+ struct eea_aq_queue_status *qs;
+ struct eea_net_init_ctx ctx;
+ bool need_reset = false;
+ int num, 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;
+
+ num = edev->enet->cfg.rx_ring_num + edev->enet->cfg.tx_ring_num + 1;
+
+ dstatus = eea_adminq_dev_status(edev->enet);
+ if (!dstatus) {
+ netdev_warn(edev->enet->netdev, "query queue status failed.\n");
+ err = -ENOMEM;
+ goto err_unlock;
+ }
+
+ if (le16_to_cpu(dstatus->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->q_status;
+
+ for (i = 0; i < 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);
+
+err_unlock:
+ rtnl_unlock();
+ return err;
+}
+
static int eea_update_cfg(struct eea_net *enet,
struct eea_device *edev,
struct eea_aq_cfg *hwcfg)
@@ -137,6 +593,9 @@ static int eea_netdev_init_features(struct net_device *netdev,
}
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_features_check = passthru_features_check,
};
@@ -145,6 +604,7 @@ 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) {
@@ -161,14 +621,83 @@ static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
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;
+ }
+
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;
@@ -185,6 +714,8 @@ int eea_net_probe(struct eea_device *edev)
if (err)
goto err_reset_dev;
+ eea_update_ts_off(edev, enet);
+
netdev_dbg(enet->netdev, "eea probe success.\n");
return 0;
@@ -194,11 +725,34 @@ int eea_net_probe(struct eea_device *edev)
eea_destroy_adminq(enet);
err_free_netdev:
+ eea_free_irq_blk(enet);
free_netdev(enet->netdev);
return err;
}
-void eea_net_remove(struct eea_device *edev)
+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;
@@ -206,11 +760,19 @@ void eea_net_remove(struct eea_device *edev)
enet = edev->enet;
netdev = enet->netdev;
- netdev_dbg(enet->netdev, "eea removed.\n");
+ if (ha) {
+ if (enet->wait_pci_ready)
+ return;
- eea_device_reset(edev);
+ eea_net_ha_reset_remove(enet, edev);
+ return;
+ }
- eea_destroy_adminq(enet);
+ if (!enet->wait_pci_ready) {
+ eea_device_reset(edev);
+ eea_destroy_adminq(enet);
+ eea_free_irq_blk(enet);
+ }
free_netdev(netdev);
}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index 239312456c5b..ca35d28211fc 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -18,6 +18,8 @@
#define EEA_VER_MINOR 0
#define EEA_VER_SUB_MINOR 0
+struct eea_tx_meta;
+
struct eea_net_tx {
struct eea_net *enet;
@@ -100,6 +102,18 @@ struct eea_net_cfg {
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 {
@@ -108,6 +122,17 @@ enum {
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;
@@ -120,9 +145,12 @@ struct eea_net {
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;
@@ -131,6 +159,24 @@ struct eea_net {
};
int eea_net_probe(struct eea_device *edev);
-void eea_net_remove(struct eea_device *edev);
+void eea_net_remove(struct eea_device *edev, bool ha);
+
+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 */
+bool 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
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
index ab06d633d8cc..2a8b46117708 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.c
@@ -16,6 +16,9 @@
#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;
@@ -57,8 +60,10 @@ struct eea_pci_device {
void __iomem *db_base;
void __iomem *db_end;
+ struct work_struct ha_handle_work;
char ha_irq_name[32];
u8 reset_pos;
+ bool ha_ready;
};
#define cfg_pointer(reg, item) \
@@ -73,6 +78,11 @@ struct eea_pci_device {
#define cfg_read32(reg, item) ioread32(cfg_pointer(reg, item))
#define cfg_readq(reg, item) readq(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);
@@ -179,6 +189,12 @@ static int eea_negotiate(struct eea_device *edev)
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);
@@ -192,12 +208,16 @@ static void eea_pci_release_resource(struct eea_pci_device *ep_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);
@@ -268,6 +288,8 @@ static int eea_pci_setup(struct pci_dev *pci_dev, struct eea_pci_device *ep_dev)
ep_dev->msix_vec_n = ret;
+ ep_dev->edev.status = EEA_PCI_STATUS_READY;
+
return 0;
err_unmap_reg:
@@ -317,6 +339,174 @@ int eea_pci_active_aq(struct eea_ring *ering, int msix_vec)
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 bit */
+ 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)
+{
+ u8 pos, cfg_type_off, type, cfg_drv_off, cfg_dev_off;
+ struct eea_pci_device *ep_dev = edev->ep_dev;
+ int irq, err;
+
+ 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, 0, ep_dev->ha_irq_name, edev);
+ if (err)
+ return err;
+
+ 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;
@@ -349,12 +539,16 @@ static int eea_init_device(struct eea_device *edev)
}
static int __eea_pci_probe(struct pci_dev *pci_dev,
- struct eea_pci_device *ep_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;
@@ -363,20 +557,34 @@ static int __eea_pci_probe(struct pci_dev *pci_dev,
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;
+
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)
+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_net_remove(edev);
+ eea_pci_free_ha_irq(edev);
+
+ if (pci_remove)
+ flush_work(&ep_dev->ha_handle_work);
+
+ eea_net_remove(edev, !pci_remove);
pci_disable_sriov(pci_dev);
@@ -403,8 +611,11 @@ static int eea_pci_probe(struct pci_dev *pci_dev,
ep_dev->pci_dev = pci_dev;
- err = __eea_pci_probe(pci_dev, ep_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);
}
@@ -415,10 +626,17 @@ static int eea_pci_probe(struct pci_dev *pci_dev,
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;
- __eea_pci_remove(pci_dev);
+ 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);
}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
index d0094c419f59..575d0f89169d 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.h
@@ -10,8 +10,18 @@
#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;
@@ -34,6 +44,12 @@ struct eea_device {
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;
@@ -47,6 +63,10 @@ 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);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_rx.c b/drivers/net/ethernet/alibaba/eea/eea_rx.c
new file mode 100644
index 000000000000..8019b01a4b24
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_rx.c
@@ -0,0 +1,264 @@
+// 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
+
+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_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 = dev_alloc_page();
+ 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 int eea_poll(struct napi_struct *napi, int budget)
+{
+ /* Empty function; will be implemented in a subsequent commit. */
+ return 0;
+}
+
+static void eea_free_rx_buffers(struct eea_net_rx *rx, struct eea_net_cfg *cfg)
+{
+ struct eea_rx_meta *meta;
+ u32 i;
+
+ 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) {
+ 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);
+
+ /* ering */
+ ering = 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;
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c
new file mode 100644
index 000000000000..4a2f1aef39ea
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_tx.c
@@ -0,0 +1,102 @@
+// 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_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;
+ u16 dma_len;
+};
+
+bool eea_poll_tx(struct eea_net_tx *tx, int budget)
+{
+ /* Empty function; will be implemented in a subsequent commit. */
+ return true;
+}
+
+netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+ /* Empty function; will be implemented in a subsequent commit. */
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+}
+
+static void eea_free_meta(struct eea_net_tx *tx, struct eea_net_cfg *cfg)
+{
+ 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) {
+ 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;
+
+ snprintf(tx->name, sizeof(tx->name), "tx.%u", idx);
+
+ ering = 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;
+}
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 6/8] eea: implement packet transmit logic
From: Xuan Zhuo @ 2026-04-07 12:19 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
Implement the core logic for transmitting packets in the EEA TX path,
including packet preparation and submission to the underlying transport.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/eea_net.c | 8 +
drivers/net/ethernet/alibaba/eea/eea_tx.c | 329 ++++++++++++++++++++-
2 files changed, 333 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
index 1ae512a0afdc..c84b2d9297a0 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -716,6 +716,12 @@ int eea_net_probe(struct eea_device *edev)
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;
@@ -768,6 +774,8 @@ void eea_net_remove(struct eea_device *edev, bool ha)
return;
}
+ unregister_netdev(netdev);
+
if (!enet->wait_pci_ready) {
eea_device_reset(edev);
eea_destroy_adminq(enet);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c
index 4a2f1aef39ea..acfe4c8ae59e 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_tx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_tx.c
@@ -11,6 +11,11 @@
#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;
@@ -28,21 +33,337 @@ struct eea_tx_meta {
u16 dma_len;
};
+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 (head == meta)
+ 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;
+ napi_consume_skb(meta->skb, budget);
+
+ meta->data = NULL;
+}
+
+static bool 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 = ering_cq_get_desc(tx->ering);
+ if (!desc)
+ break;
+
+ id = le16_to_cpu(desc->id);
+ if (unlikely(id >= tx->ering->num)) {
+ netdev_err(tx->enet->netdev, "tx invalid id %d\n", id);
+ 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 {
+ netdev_err(tx->enet->netdev,
+ "tx meta->data is null. id %d num: %d\n",
+ meta->id, meta->num);
+ desc_n = 1;
+ }
+
+ ering_cq_ack_desc(tx->ering, desc_n);
+ }
+
+ return stats.packets == budget;
+}
+
bool eea_poll_tx(struct eea_net_tx *tx, int budget)
{
- /* Empty function; will be implemented in a subsequent commit. */
- return true;
+ struct eea_net *enet = tx->enet;
+ u32 index = tx - enet->tx;
+ struct netdev_queue *txq;
+ bool busy;
+
+ txq = netdev_get_tx_queue(enet->netdev, index);
+
+ __netif_tx_lock(txq, smp_processor_id());
+
+ busy = 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 busy;
+}
+
+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 len,
+ bool last, void *data, u16 flags)
+{
+ struct eea_tx_meta *meta;
+ struct eea_tx_desc *desc;
+
+ meta = eea_tx_meta_get(tx);
+
+ desc = ering_sq_alloc_desc(tx->ering, meta->id, last, flags);
+ desc->addr = cpu_to_le64(addr);
+ desc->len = cpu_to_le16(len);
+
+ meta->next = NULL;
+ meta->dma_len = len;
+ meta->dma_addr = addr;
+ meta->data = data;
+ meta->num = 1;
+ meta->desc = desc;
+
+ if (head_meta) {
+ meta->next = head_meta->next;
+ head_meta->next = meta;
+ }
+
+ 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;
+
+ last = is_last;
+
+ /* 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;
+ }
+
+ meta = __eea_tx_desc_fill(tx, head_meta, addr, len, last, data, flags);
+
+ if (length > USHRT_MAX) {
+ if (!head_meta)
+ head_meta = meta;
+
+ __eea_tx_desc_fill(tx, head_meta, addr + USHRT_MAX,
+ length - USHRT_MAX, is_last, NULL, 0);
+ }
+
+ 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;
+ dma_addr_t addr;
+ int i, err;
+ u16 flags;
+
+ addr = dma_map_single(tx->dma_dev, skb->data, hlen, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(tx->dma_dev, addr)))
+ return -ENOMEM;
+
+ flags = skb->ip_summed == CHECKSUM_PARTIAL ? EEA_DESC_F_DO_CSUM : 0;
+
+ meta = eea_tx_desc_fill(tx, NULL, addr, hlen, !shinfo->nr_frags,
+ skb, flags);
+
+ err = eea_fill_desc_from_skb(skb, meta->desc);
+ if (err)
+ goto err_cancel;
+
+ for (i = 0; i < shinfo->nr_frags; i++) {
+ const skb_frag_t *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;
+ }
+
+ meta->num = shinfo->nr_frags + 1;
+ ering_sq_commit_desc(tx->ering);
+
+ return 0;
+
+err_cancel:
+ 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)
+{
+ ering_kick(tx->ering);
}
netdev_tx_t eea_tx_xmit(struct sk_buff *skb, struct net_device *netdev)
{
- /* Empty function; will be implemented in a subsequent commit. */
- dev_kfree_skb_any(skb);
+ 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, n;
+
+ txq = netdev_get_tx_queue(netdev, qnum);
+
+ err = eea_tx_post_skb(tx, skb);
+ if (unlikely(err))
+ dev_kfree_skb_any(skb);
+ else
+ skb_tx_timestamp(skb);
+
+ /* NETDEV_TX_BUSY is expensive. So stop advancing the TX queue.
+ * 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;
+ netif_txq_maybe_stop(txq, tx->ering->num_free, n, n);
+
+ 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;
}
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 5/8] eea: implement packet receive logic
From: Xuan Zhuo @ 2026-04-07 12:19 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
Implement the core logic for receiving packets in the EEA RX path,
including packet buffering and basic validation.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/eea_net.h | 3 +
drivers/net/ethernet/alibaba/eea/eea_rx.c | 509 ++++++++++++++++++++-
2 files changed, 510 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
index ca35d28211fc..4b10246d1a6f 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_net.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -41,6 +41,7 @@ struct eea_rx_meta {
struct page *page;
dma_addr_t dma;
u32 offset;
+ u32 sync_for_cpu;
u32 frags;
struct page *hdr_page;
@@ -62,6 +63,7 @@ struct eea_net_rx_pkt_ctx {
bool data_valid;
bool do_drop;
+ u32 recv_len;
struct sk_buff *head_skb;
};
@@ -166,6 +168,7 @@ 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);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_rx.c b/drivers/net/ethernet/alibaba/eea/eea_rx.c
index 8019b01a4b24..ada89bd9f177 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_rx.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_rx.c
@@ -17,6 +17,39 @@
#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;
+};
+
+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)
{
@@ -31,6 +64,89 @@ static void eea_free_rx_buffer(struct eea_net_rx *rx, struct eea_rx_meta *meta,
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 += 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;
@@ -96,17 +212,406 @@ static int eea_alloc_rx_hdr(struct eea_net_init_ctx *ctx, struct eea_net_rx *rx)
return -ENOMEM;
}
-static int eea_poll(struct napi_struct *napi, int budget)
+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);
+ 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);
+ 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);
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ if (unlikely(ctx->len < ETH_HLEN)) {
+ pr_debug("%s: short packet %u\n", enet->netdev->name, ctx->len);
+ 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 + 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)
{
- /* Empty function; will be implemented in a subsequent commit. */
+ 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);
+ 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:
+ 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)) {
+ netdev_err(rx->enet->netdev, "rx invalid id %d\n", id);
+ return -EINVAL;
+ }
+
+ ctx->meta = &rx->meta[id];
+ ctx->len = le16_to_cpu(desc->len);
+ if (unlikely(ctx->len > ctx->meta->len)) {
+ 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->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 = 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.idx)
+ dev_kfree_skb(rx->pkt.head_skb);
+
+ 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);
+
+skip:
+ eea_rx_meta_put(rx, meta);
+ack:
+ ering_cq_ack_desc(rx->ering, 1);
+
+ 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 bool eea_rx_post(struct eea_net_rx *rx)
+{
+ 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);
+
+ addr = meta->dma + meta->offset + headroom;
+
+ desc = 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);
+ }
+
+ ering_sq_commit_desc(rx->ering);
+
+ meta->truesize = len + room;
+ meta->headroom = headroom;
+ meta->tailroom = tailroom;
+ meta->len = len;
+ ++num;
+ }
+
+ if (num)
+ ering_kick(rx->ering);
+
+ /* true means busy, napi should be called again. */
+ return !!err;
+}
+
+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);
+
+ received = eea_cleanrx(rx, budget, &ctx);
+
+ if (rx->ering->num_free > budget)
+ busy |= eea_rx_post(rx);
+
+ busy |= received >= budget;
+
+ if (busy)
+ return budget;
+
+ if (napi_complete_done(napi, received))
+ 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)
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 3/8] eea: probe the netdevice and create adminq
From: Xuan Zhuo @ 2026-04-07 12:19 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
In-Reply-To: <20260407122001.22265-1-xuanzhuo@linux.alibaba.com>
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).
This commit creates the netdevice after PCI probe,
and initializes the admin queue to send commands to the device.
Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
---
drivers/net/ethernet/alibaba/eea/Makefile | 6 +-
drivers/net/ethernet/alibaba/eea/eea_adminq.c | 475 ++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_adminq.h | 74 +++
drivers/net/ethernet/alibaba/eea/eea_net.c | 216 ++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 136 +++++
drivers/net/ethernet/alibaba/eea/eea_pci.c | 29 +-
drivers/net/ethernet/alibaba/eea/eea_pci.h | 3 +
7 files changed, 936 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.h
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
index e5e4007810a6..91f318e8e046 100644
--- a/drivers/net/ethernet/alibaba/eea/Makefile
+++ b/drivers/net/ethernet/alibaba/eea/Makefile
@@ -1,4 +1,6 @@
obj-$(CONFIG_EEA) += eea.o
-eea-y := eea_ring.o \
- eea_pci.o
+eea-y := eea_ring.o \
+ eea_net.o \
+ eea_pci.o \
+ eea_adminq.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_adminq.c b/drivers/net/ethernet/alibaba/eea/eea_adminq.c
new file mode 100644
index 000000000000..bd61b47c9895
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_adminq.c
@@ -0,0 +1,475 @@
+// 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_REJECT 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 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)
+{
+ struct eea_aq_cdesc *cdesc;
+ struct eea_aq_desc *desc;
+ int ret;
+
+ desc = 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);
+
+ ering_sq_commit_desc(enet->adminq.ring);
+
+ 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(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_reset(enet->edev);
+ enet->adminq.broken = true;
+ return ret;
+ }
+
+ /* Returns 0 on success, or a negative error code on failure. */
+ ret = le32_to_cpu(cdesc->status);
+
+ ering_cq_ack_desc(enet->adminq.ring, 1);
+
+ if (ret)
+ netdev_err(enet->netdev,
+ "adminq exec failed. cmd: %d ret %d\n", cmd, ret);
+
+ return ret;
+}
+
+static int eea_adminq_exec(struct eea_net *enet, u16 cmd,
+ void *req, u32 req_size, void *res, u32 res_size)
+{
+ dma_addr_t req_addr = 0, res_addr = 0;
+ struct device *dma;
+ int ret;
+
+ if (enet->adminq.broken)
+ return -EIO;
+
+ 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;
+ }
+ }
+
+ ret = eea_adminq_submit(enet, cmd, req_addr, res_addr,
+ req_size, res_size);
+ if (res)
+ dma_unmap_single(dma, res_addr, res_size, DMA_FROM_DEVICE);
+
+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) {
+ 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 = 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;
+
+ 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;
+
+ err = eea_pci_set_aq_up(enet->edev);
+ if (err)
+ goto err;
+
+ aq->broken = false;
+
+ return 0;
+
+err:
+ /* For the adminq, we can safely free the ring before setting it up. */
+ 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));
+}
+
+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;
+
+ 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);
+ if (err)
+ return err;
+
+ 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)
+{
+ return eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_DESTROY_ALL, NULL, 0,
+ NULL, 0);
+}
+
+struct eea_aq_dev_status *eea_adminq_dev_status(struct eea_net *enet)
+{
+ 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 NULL;
+
+ size = struct_size(dev_status, q_status, q_num);
+
+ rep = kzalloc(size, GFP_KERNEL);
+ if (!rep) {
+ kfree(req);
+ return NULL;
+ }
+
+ 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);
+ kfree(req);
+ if (err) {
+ kfree(rep);
+ return NULL;
+ }
+
+ return rep;
+}
+
+int 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 rc;
+
+ 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_dev_id(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));
+
+ if (!rc) {
+ if (rep->op_code == EEA_HINFO_REP_REJECT) {
+ dev_err(dev, "Device has refused the initialization due to provided host information\n");
+ rc = -ENODEV;
+ }
+ if (rep->has_reply) {
+ rep->reply_str[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
+ dev_warn(dev, "Device replied: %s\n",
+ rep->reply_str);
+ }
+ }
+
+ kfree(rep);
+err_free_cfg:
+ kfree(cfg);
+ return rc;
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_adminq.h b/drivers/net/ethernet/alibaba/eea/eea_adminq.h
new file mode 100644
index 000000000000..816059193b3b
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_adminq.h
@@ -0,0 +1,74 @@
+/* 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 {
+ struct eea_ring *ring;
+ u32 num;
+ bool broken;
+ u16 phase;
+
+ 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);
+struct eea_aq_dev_status *eea_adminq_dev_status(struct eea_net *enet);
+int eea_adminq_config_host_info(struct eea_net *enet);
+#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c
new file mode 100644
index 000000000000..c30b32aa40ba
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.c
@@ -0,0 +1,216 @@
+// 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 128
+#define EEA_NET_IO_RING_DEPTH 32000
+
+static int eea_update_cfg(struct eea_net *enet,
+ struct eea_device *edev,
+ struct eea_aq_cfg *hwcfg)
+{
+ enet->cfg_hw.rx_ring_depth = le32_to_cpu(hwcfg->rx_depth_max);
+ enet->cfg_hw.tx_ring_depth = le32_to_cpu(hwcfg->tx_depth_max);
+
+ enet->cfg_hw.rx_ring_num = edev->rx_num;
+ enet->cfg_hw.tx_ring_num = edev->tx_num;
+
+ enet->cfg.rx_ring_depth = le32_to_cpu(hwcfg->rx_depth_def);
+ enet->cfg.tx_ring_depth = le32_to_cpu(hwcfg->tx_depth_def);
+
+ enet->cfg.rx_ring_num = edev->rx_num;
+ enet->cfg.tx_ring_num = edev->tx_num;
+
+ enet->cfg_hw.split_hdr = EEA_SPLIT_HDR_SIZE;
+
+ /* Now, we assert that the rx ring num is equal to the tx ring num. */
+ if (enet->cfg_hw.rx_ring_num != enet->cfg_hw.tx_ring_num) {
+ dev_err(edev->dma_dev, "The device gave us invalid rx,tx ring num. %d %d\n",
+ enet->cfg.rx_ring_num,
+ enet->cfg.tx_ring_num);
+ return -EINVAL;
+ }
+
+ if (enet->cfg_hw.rx_ring_depth > EEA_NET_IO_RING_DEPTH) {
+ dev_err(edev->dma_dev, "The device gave us an invalid rx depth. %d\n",
+ enet->cfg_hw.rx_ring_depth);
+ return -EINVAL;
+ }
+
+ if (enet->cfg_hw.tx_ring_depth > EEA_NET_IO_RING_DEPTH) {
+ dev_err(edev->dma_dev, "The device gave us an invalid tx depth. %d\n",
+ enet->cfg_hw.tx_ring_depth);
+ return -EINVAL;
+ }
+
+ 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_validate_addr = eth_validate_addr,
+ .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;
+
+ 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;
+ SET_NETDEV_DEV(netdev, edev->dma_dev);
+
+ enet = netdev_priv(netdev);
+ enet->netdev = netdev;
+ enet->edev = edev;
+ edev->enet = enet;
+
+ return enet;
+}
+
+int eea_net_probe(struct eea_device *edev)
+{
+ struct eea_net *enet;
+ int err = -ENOMEM;
+
+ 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;
+
+ err = eea_adminq_config_host_info(enet);
+ if (err)
+ goto err_reset_dev;
+
+ err = eea_netdev_init_features(enet->netdev, enet, edev);
+ 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:
+ free_netdev(enet->netdev);
+ return err;
+}
+
+void eea_net_remove(struct eea_device *edev)
+{
+ struct net_device *netdev;
+ struct eea_net *enet;
+
+ enet = edev->enet;
+ netdev = enet->netdev;
+
+ netdev_dbg(enet->netdev, "eea removed.\n");
+
+ eea_device_reset(edev);
+
+ eea_destroy_adminq(enet);
+
+ free_netdev(netdev);
+}
diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.h b/drivers/net/ethernet/alibaba/eea/eea_net.h
new file mode 100644
index 000000000000..239312456c5b
--- /dev/null
+++ b/drivers/net/ethernet/alibaba/eea/eea_net.h
@@ -0,0 +1,136 @@
+/* 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_ring.h"
+
+#define EEA_VER_MAJOR 1
+#define EEA_VER_MINOR 0
+#define EEA_VER_SUB_MINOR 0
+
+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_rx_meta {
+ struct eea_rx_meta *next;
+
+ struct page *page;
+ dma_addr_t dma;
+ u32 offset;
+ u32 frags;
+
+ struct page *hdr_page;
+ void *hdr_addr;
+ dma_addr_t hdr_dma;
+
+ u32 id;
+
+ u32 truesize;
+ u32 headroom;
+ u32 tailroom;
+
+ u32 len;
+};
+
+struct eea_net_rx_pkt_ctx {
+ u16 idx;
+
+ bool data_valid;
+ bool do_drop;
+
+ 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;
+
+ 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;
+};
+
+enum {
+ EEA_LINK_ERR_NONE,
+ EEA_LINK_ERR_HA_RESET_DEV,
+ EEA_LINK_ERR_LINK_DOWN,
+};
+
+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;
+
+ u32 link_err;
+
+ bool started;
+
+ u8 duplex;
+ u32 speed;
+
+ u64 hw_ts_offset;
+};
+
+int eea_net_probe(struct eea_device *edev);
+void eea_net_remove(struct eea_device *edev);
+
+#endif
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
index 3f49a2e53223..ab06d633d8cc 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.c
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.c
@@ -8,6 +8,7 @@
#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
@@ -64,7 +65,9 @@ struct eea_pci_device {
((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))
@@ -295,6 +298,25 @@ void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off)
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;
+}
+
u64 eea_pci_device_ts(struct eea_device *edev)
{
struct eea_pci_device *ep_dev = edev->ep_dev;
@@ -316,7 +338,9 @@ static int eea_init_device(struct eea_device *edev)
if (err)
goto err;
- /* do net device probe ... */
+ err = eea_net_probe(edev);
+ if (err)
+ goto err;
return 0;
err:
@@ -350,6 +374,9 @@ static void __eea_pci_remove(struct pci_dev *pci_dev)
{
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_net_remove(edev);
pci_disable_sriov(pci_dev);
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
index be4e75b4ed2f..d0094c419f59 100644
--- a/drivers/net/ethernet/alibaba/eea/eea_pci.h
+++ b/drivers/net/ethernet/alibaba/eea/eea_pci.h
@@ -10,6 +10,8 @@
#include <linux/pci.h>
+#include "eea_ring.h"
+
struct eea_pci_cap {
__u8 cap_vndr;
__u8 cap_next;
@@ -43,6 +45,7 @@ u16 eea_pci_dev_id(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);
u64 eea_pci_device_ts(struct eea_device *edev);
--
2.32.0.3.g01195cf9f
^ permalink raw reply related
* [PATCH net-next v38 0/8] eea: Add basic driver framework for Alibaba Elastic Ethernet Adaptor
From: Xuan Zhuo @ 2026-04-07 12:19 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Xuan Zhuo, Wen Gu, Philo Lu, Vadim Fedorenko,
Dong Yibo, Jes Sorensen, Heiner Kallweit, Dust Li
Add a driver framework for EEA that will be available in the future.
This driver is currently quite minimal, implementing only fundamental
core functionalities. Key features include: I/O queue management via
adminq, basic PCI-layer operations, and essential RX/TX data
communication capabilities. It also supports the creation,
initialization, and management of network devices (netdev). Furthermore,
the ring structures for both I/O queues and adminq have been abstracted
into a simple, unified, and reusable library implementation,
facilitating future extension and maintenance.
v38:
1. fix comments from https://sashiko.dev/#/patchset/20260404135144.33166-1-xuanzhuo%40linux.alibaba.com
v36:
1. fix comments from https://sashiko.dev/#/patchset/20260323074441.91691-1-xuanzhuo%40linux.alibaba.com
v35:
1. fix comments from https://netdev-ai.bots.linux.dev/ai-review.html?id=24371ffc-a1ea-46e1-a6b3-b8cbcbb52efb
v34:
1. fix the comments from https://sashiko.dev/#/patchset/20260317015257.79494-1-xuanzhuo%40linux.alibaba.com
v33:
1. fix the comments. old version refers enet_bind_new_q_and_cfg, but that is
changed to eea_bind_q_and_cfg.
v32:
1. remove unused parameters from eea_net_ha_reset_remove and eea_create_pp
2. call skb_mark_for_recycle for new skb for frag_list skbs
v31:
1. remove unused parameter "enet" from eex_rx_post()
v30:
1. fix some small problems
v29:
Address AI-generated review suggestions from the previous version.
v28:
Address AI-generated review suggestions from the previous version.
v27:
Address AI-generated review suggestions from the previous version.
v26:
Adopting suggestions from the previous AI review, another significant
change is the introduction of an IRQ block to implement IRQ proxying. With
this design, when an IRQ occurs, we no longer invoke the RX data structure
directly -- instead, the IRQ block serves as an intermediary proxy. This
approach offers several advantages: IRQ resources no longer need to be
reallocated during reset operations, and IRQs are decoupled from RX
structures. Consequently, when certain errors occur, we can fall back and
safely reuse the original memory resources.
v25:
I have adopted most of the suggestions from the AI's feedback. However, I
believe some of the feedback is incorrect. I have already replied in the
previous thread. http://lore.kernel.org/all/1770002612.3297296-2-xuanzhuo@linux.alibaba.com
v24:
1. Add null checks for enet->rx and enet->tx in eea_get_ethtool_stat to
prevent errors when reading rx = enet->rx[i] in case enet->rx is null.
tx is similar. With rtnl protection in place, this check is sufficient.
2. Use 'received' as the return value in eea_poll.
v23:
I have moved netif_set_real_num_queues() out of eea_start_rxtx(), so
eea_start_rxtx() is now a void function. I believe enet_bind_new_q_and_cfg()
is a more suitable place to include netif_set_real_num_queues(). In
eea_active_ring_and_irq(), I first execute request_irq() before interacting
with the hardware to create queues. Therefore, during the NIC setup process,
all driver-internal operations (memory allocation, IRQ initialization, sysfs
configuration, etc.) will be completed before the final notification to the
hardware.
v22:
1. Use the budget from the NAPI poll function as the parameter for
napi_consume_skb.
2. Stop the TX queue when the remaining ring slots cannot hold an SKB.
v21:
Fix two issues from the previous version:
1, a DMA unmap operation was missing.
2, RCU APIs were not used in eea_stats. Although the standard practice when
using RCU would require adding the __rcu annotation to both the rx and
tx fields, in many cases these fields are read without needing RCU
protection. Therefore, I do not want to add the __rcu annotation.
Instead, I use a spin lock to protect modifications to rx and tx.
v20:
Fix the partially initialized structure passed to db. @Jakub
http://lore.kernel.org/all/20260113172353.2ae6ef81@kernel.org
v19:
fix the comments from @Simon Horman
v18:
v17 with [PATCH] prefix.
v17:
1. In `eea_adminq_dev_status`, uniformly use `enet->cfg.rx_ring_num`.
2. Add a `struct eea_net_cfg *cfg` parameter to `eea_free_rx` and
`eea_free_tx`. When called in the normal path, pass `enet->cfg` as
the argument; when called during initialization, pass the temporary
`cfg` instead.
3. Move the `.ndo_get_stats64` callback into `eea_net.c`.
4. In the `.ndo_get_stats64` callback, add a comment explaining how the TX
and RX statistics are protected by RCU.
/* This function is protected by RCU. Here uses enet->tx and enet->rx
* to check whether the TX and RX structures are safe to access. In
* eea_free_rxtx_q_mem, before freeing the TX and RX resources, enet->rx
* and enet->tx are set to NULL, and synchronize_net is called.
*/
v16:
1. follow the advices from @ALOK TIWARI
http://lore.kernel.org/all/5ff95a71-69e5-4cb6-9b2a-5224c983bdc2@oracle.com
v15:
1. remove 'default m' from eea kconfig
2. free the resources when open failed.
v14:
1. some tiny fixes
v13:
1. fix some tiny fixes @Simon
v12:
I encountered some issues with sending the v11 patches, as they were quite
messy. Therefore, I'm resending them as v12.
v11:
1. remove auto clean __free(kfree)
2. some tiny fixes
v10:
1. name the jump labels after the target @Jakub
2. rm __GFP_ZERO from dma_alloc_coherent @Jakub
v9:
1. some fixes for ethtool from http://lore.kernel.org/all/20251027183754.52fe2a2c@kernel.org
v8: 1. rename eea_net_tmp to eea_net_init_ctx
2. rm code that allocs memory to destroy queues
3. some other minor changes
v7: 1. remove the irrelative code from ethtool commit
2. build every commits with W12
v6: Split the big one commit to five commits
v5: Thanks for the comments from Kalesh Anakkur Purayil, ALOK TIWARI
v4: Thanks for the comments from Troy Mitchell, Przemek Kitszel, Andrew Lunn, Kalesh Anakkur Purayil
v3: Thanks for the comments from Paolo Abenchi
v2: Thanks for the comments from Simon Horman and Andrew Lunn
v1: Thanks for the comments from Simon Horman and Andrew Lunn
Xuan Zhuo (8):
eea: introduce PCI framework
eea: introduce ring and descriptor structures
eea: probe the netdevice and create adminq
eea: create/destroy rx,tx queues for netdevice open and stop
eea: implement packet receive logic
eea: implement packet transmit logic
eea: introduce ethtool support
eea: introduce callback for ndo_get_stats64
MAINTAINERS | 8 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/alibaba/Kconfig | 28 +
drivers/net/ethernet/alibaba/Makefile | 5 +
drivers/net/ethernet/alibaba/eea/Makefile | 9 +
drivers/net/ethernet/alibaba/eea/eea_adminq.c | 475 ++++++++++
drivers/net/ethernet/alibaba/eea/eea_adminq.h | 74 ++
drivers/net/ethernet/alibaba/eea/eea_desc.h | 131 +++
.../net/ethernet/alibaba/eea/eea_ethtool.c | 252 ++++++
.../net/ethernet/alibaba/eea/eea_ethtool.h | 49 +
drivers/net/ethernet/alibaba/eea/eea_net.c | 844 ++++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_net.h | 195 ++++
drivers/net/ethernet/alibaba/eea/eea_pci.c | 692 ++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_pci.h | 73 ++
drivers/net/ethernet/alibaba/eea/eea_ring.c | 243 +++++
drivers/net/ethernet/alibaba/eea/eea_ring.h | 86 ++
drivers/net/ethernet/alibaba/eea/eea_rx.c | 795 +++++++++++++++++
drivers/net/ethernet/alibaba/eea/eea_tx.c | 446 +++++++++
19 files changed, 4407 insertions(+)
create mode 100644 drivers/net/ethernet/alibaba/Kconfig
create mode 100644 drivers/net/ethernet/alibaba/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/Makefile
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_adminq.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_desc.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ethtool.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_net.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_pci.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_ring.h
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_rx.c
create mode 100644 drivers/net/ethernet/alibaba/eea/eea_tx.c
--
2.32.0.3.g01195cf9f
^ permalink raw reply
* [PATCH] Bluetooth: Add Broadcom channel priority commands
From: Sasha Finkelstein via B4 Relay @ 2026-04-07 12:09 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Marcel Holtmann,
Luiz Augusto von Dentz, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: linux-kernel, asahi, linux-arm-kernel, linux-bluetooth, netdev,
Sasha Finkelstein
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Certain Broadcom bluetooth chips (bcm4377/bcm4378/bcm438) need ACL
streams carrying audio to be set as "high priority" using a vendor
specific command to prevent 10-ish second-long dropouts whenever
something does a device scan. This series adds an ioctl to control
the priorities and hooks it up for the relevant chips.
Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
MAINTAINERS | 2 ++
drivers/bluetooth/hci_bcm4377.c | 2 ++
include/net/bluetooth/hci_core.h | 12 ++++++++++++
include/net/bluetooth/hci_sock.h | 7 +++++++
net/bluetooth/Kconfig | 7 +++++++
net/bluetooth/Makefile | 1 +
net/bluetooth/brcm.c | 29 +++++++++++++++++++++++++++++
net/bluetooth/brcm.h | 17 +++++++++++++++++
net/bluetooth/hci_conn.c | 11 +++++++++++
net/bluetooth/hci_sock.c | 4 ++++
10 files changed, 92 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c3fe46d7c4bc..81be021367ec 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2562,6 +2562,8 @@ F: include/dt-bindings/pinctrl/apple.h
F: include/linux/mfd/macsmc.h
F: include/linux/soc/apple/*
F: include/uapi/drm/asahi_drm.h
+F: net/bluetooth/brcm.c
+F: net/bluetooth/brcm.h
ARM/ARTPEC MACHINE SUPPORT
M: Jesper Nilsson <jesper.nilsson@axis.com>
diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c
index 925d0a635945..5f79920c0306 100644
--- a/drivers/bluetooth/hci_bcm4377.c
+++ b/drivers/bluetooth/hci_bcm4377.c
@@ -2397,6 +2397,8 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (bcm4377->hw->broken_le_ext_adv_report_phy)
hci_set_quirk(hdev, HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_PHY);
+ hci_set_brcm_capable(hdev);
+
pci_set_drvdata(pdev, bcm4377);
hci_set_drvdata(hdev, bcm4377);
SET_HCIDEV_DEV(hdev, &pdev->dev);
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index a7bffb908c1e..ef3b5433203c 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -642,6 +642,10 @@ struct hci_dev {
bool aosp_quality_report;
#endif
+#if IS_ENABLED(CONFIG_BT_BRCMEXT)
+ bool brcm_capable;
+#endif
+
int (*open)(struct hci_dev *hdev);
int (*close)(struct hci_dev *hdev);
int (*flush)(struct hci_dev *hdev);
@@ -1791,6 +1795,13 @@ static inline void hci_set_aosp_capable(struct hci_dev *hdev)
#endif
}
+static inline void hci_set_brcm_capable(struct hci_dev *hdev)
+{
+#if IS_ENABLED(CONFIG_BT_BRCMEXT)
+ hdev->brcm_capable = true;
+#endif
+}
+
static inline void hci_devcd_setup(struct hci_dev *hdev)
{
#ifdef CONFIG_DEV_COREDUMP
@@ -1812,6 +1823,7 @@ int hci_get_conn_list(void __user *arg);
int hci_get_conn_info(struct hci_dev *hdev, void __user *arg);
int hci_get_auth_info(struct hci_dev *hdev, void __user *arg);
int hci_inquiry(void __user *arg);
+int hci_set_acl_prio(struct hci_dev *hdev, void __user *arg);
struct bdaddr_list *hci_bdaddr_list_lookup(struct list_head *list,
bdaddr_t *bdaddr, u8 type);
diff --git a/include/net/bluetooth/hci_sock.h b/include/net/bluetooth/hci_sock.h
index 13e8cd4414a1..95d156ac4cae 100644
--- a/include/net/bluetooth/hci_sock.h
+++ b/include/net/bluetooth/hci_sock.h
@@ -91,6 +91,8 @@ struct hci_ufilter {
#define HCIINQUIRY _IOR('H', 240, int)
+#define HCISETACLPRIO _IOW('H', 250, int)
+
/* Ioctl requests structures */
struct hci_dev_stats {
__u32 err_rx;
@@ -171,6 +173,11 @@ struct hci_inquiry_req {
__u8 length;
__u8 num_rsp;
};
+
+struct hci_acl_prio_req {
+ __u16 handle;
+ __u8 high_prio;
+};
#define IREQ_CACHE_FLUSH 0x0001
#endif /* __HCI_SOCK_H */
diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig
index 6b2b65a66700..0f2a5fbcafc5 100644
--- a/net/bluetooth/Kconfig
+++ b/net/bluetooth/Kconfig
@@ -110,6 +110,13 @@ config BT_AOSPEXT
This options enables support for the Android Open Source
Project defined HCI vendor extensions.
+config BT_BRCMEXT
+ bool "Enable Broadcom extensions"
+ depends on BT
+ help
+ This option enables support for the Broadcom defined HCI
+ vendor extensions.
+
config BT_DEBUGFS
bool "Export Bluetooth internals in debugfs"
depends on BT && DEBUG_FS
diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
index a7eede7616d8..b4c9013a46ce 100644
--- a/net/bluetooth/Makefile
+++ b/net/bluetooth/Makefile
@@ -24,5 +24,6 @@ bluetooth-$(CONFIG_BT_LE) += iso.o
bluetooth-$(CONFIG_BT_LEDS) += leds.o
bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o
bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o
+bluetooth-$(CONFIG_BT_BRCMEXT) += brcm.o
bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o
bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o
diff --git a/net/bluetooth/brcm.c b/net/bluetooth/brcm.c
new file mode 100644
index 000000000000..d03d2af5dc7e
--- /dev/null
+++ b/net/bluetooth/brcm.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 The Asahi Linux Contributors
+ */
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "brcm.h"
+
+int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable)
+{
+ struct sk_buff *skb;
+ u8 cmd[3];
+
+ if (!hdev->brcm_capable)
+ return -EOPNOTSUPP;
+
+ cmd[0] = handle;
+ cmd[1] = handle >> 8;
+ cmd[2] = !!enable;
+
+ skb = hci_cmd_sync(hdev, 0xfc57, sizeof(cmd), cmd, HCI_CMD_TIMEOUT);
+ if (IS_ERR(skb))
+ return PTR_ERR(skb);
+
+ kfree_skb(skb);
+ return 0;
+}
diff --git a/net/bluetooth/brcm.h b/net/bluetooth/brcm.h
new file mode 100644
index 000000000000..a501f2988a96
--- /dev/null
+++ b/net/bluetooth/brcm.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 The Asahi Linux Contributors
+ */
+
+#if IS_ENABLED(CONFIG_BT_BRCMEXT)
+
+int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable);
+
+#else
+
+static inline int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 11d3ad8d2551..b2c7414a9c5b 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -35,6 +35,7 @@
#include <net/bluetooth/iso.h>
#include <net/bluetooth/mgmt.h>
+#include "brcm.h"
#include "smp.h"
#include "eir.h"
@@ -2775,6 +2776,16 @@ int hci_get_auth_info(struct hci_dev *hdev, void __user *arg)
return copy_to_user(arg, &req, sizeof(req)) ? -EFAULT : 0;
}
+int hci_set_acl_prio(struct hci_dev *hdev, void __user *arg)
+{
+ struct hci_acl_prio_req req;
+
+ if (copy_from_user(&req, arg, sizeof(req)))
+ return -EFAULT;
+
+ return brcm_set_high_priority(hdev, req.handle, req.high_prio);
+}
+
struct hci_chan *hci_chan_create(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
index 0290dea081f6..4be6aeeb6bad 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -1035,6 +1035,9 @@ static int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd,
if (!capable(CAP_NET_ADMIN))
return -EPERM;
return hci_sock_reject_list_del(hdev, (void __user *)arg);
+
+ case HCISETACLPRIO:
+ return hci_set_acl_prio(hdev, (void __user *)arg);
}
return -ENOIOCTLCMD;
@@ -1072,6 +1075,7 @@ static int hci_sock_ioctl(struct socket *sock, unsigned int cmd,
case HCIGETAUTHINFO:
case HCIBLOCKADDR:
case HCIUNBLOCKADDR:
+ case HCISETACLPRIO:
break;
default:
return -ENOIOCTLCMD;
---
base-commit: bfe62a454542cfad3379f6ef5680b125f41e20f4
change-id: 20260407-brcm-prio-b630e6cc3834
Best regards,
--
Sasha Finkelstein <fnkl.kernel@gmail.com>
^ permalink raw reply related
* [PATCH net-next v2] net: openvswitch: decouple flow_table from ovs_mutex
From: Adrian Moreno @ 2026-04-07 12:04 UTC (permalink / raw)
To: netdev
Cc: Adrian Moreno, Aaron Conole, Eelco Chaudron, Ilya Maximets,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, open list:OPENVSWITCH, open list
Currently the entire ovs module is write-protected using the global
ovs_mutex. While this simple approach works fine for control-plane
operations (such as vport configurations), requiring the global mutex
for flow modifications can be problematic.
During periods of high control-plane operations, e.g: netdevs (vports)
coming and going, RTNL can suffer contention. This contention is easily
transferred to the ovs_mutex as RTNL nests inside ovs_mutex. Flow
modifications, however, are done as part of packet processing and having
them wait for RTNL pressure to go away can lead to packet drops.
This patch decouples flow_table modifications from ovs_mutex by means of
the following:
1 - Make flow_table an rcu-protected pointer inside the datapath.
This allows both objects to be protected independently while reducing the
amount of changes required in "flow_table.c".
2 - Create a new mutex inside the flow_table that protects it from
concurrent modifications.
Putting the mutex inside flow_table makes it easier to consume for
functions inside flow_table.c that do not currently take pointers to the
datapath.
Some function signatures need to be changed to accept flow_table so that
lockdep checks can be performed.
3 - Create a reference count to temporarily extend rcu protection from
the datapath to the flow_table.
In order to use the flow_table without locking ovs_mutex, the flow_table
pointer must be first dereferenced within an rcu-protected region.
Next, the table->mutex needs to be locked to protect it from
concurrent writes but mutexes must not be locked inside an rcu-protected
region, so the rcu-protected region must be left at which point the
datapath can be concurrently freed.
To extend the protection beyond the rcu region, a reference count is used.
One reference is held by the datapath, the other is temporarily
increased during flow modifications. For example:
Datapath deletion:
ovs_lock();
table = rcu_dereference_protected(dp->table, ...);
rcu_assign_pointer(dp->table, NULL);
ovs_flow_tbl_put(table);
ovs_unlock();
Flow modification:
rcu_read_lock();
dp = get_dp(...);
table = rcu_dereference(dp->table);
ovs_flow_tbl_get(table);
rcu_read_unlock();
mutex_lock(&table->lock);
/* Perform modifications on the flow_table */
mutex_unlock(&table->lock);
ovs_flow_tbl_put(table);
Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
v2: Fix argument in ovs_flow_tbl_put (sparse)
Remove rcu checks in ovs_dp_masks_rebalance
---
net/openvswitch/datapath.c | 285 ++++++++++++++++++++++++-----------
net/openvswitch/datapath.h | 2 +-
net/openvswitch/flow.c | 13 +-
net/openvswitch/flow.h | 9 +-
net/openvswitch/flow_table.c | 180 ++++++++++++++--------
net/openvswitch/flow_table.h | 51 ++++++-
6 files changed, 380 insertions(+), 160 deletions(-)
diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index e209099218b4..9c234993520c 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -88,13 +88,17 @@ static void ovs_notify(struct genl_family *family,
* DOC: Locking:
*
* All writes e.g. Writes to device state (add/remove datapath, port, set
- * operations on vports, etc.), Writes to other state (flow table
- * modifications, set miscellaneous datapath parameters, etc.) are protected
- * by ovs_lock.
+ * operations on vports, etc.) and writes to other datapath parameters
+ * are protected by ovs_lock.
+ *
+ * Writes to the flow table are NOT protected by ovs_lock. Instead, a per-table
+ * mutex and reference count are used (see comment above "struct flow_table"
+ * definition). On some few occasions, the per-flow table mutex is nested
+ * inside ovs_mutex.
*
* Reads are protected by RCU.
*
- * There are a few special cases (mostly stats) that have their own
+ * There are a few other special cases (mostly stats) that have their own
* synchronization but they nest under all of above and don't interact with
* each other.
*
@@ -166,7 +170,6 @@ static void destroy_dp_rcu(struct rcu_head *rcu)
{
struct datapath *dp = container_of(rcu, struct datapath, rcu);
- ovs_flow_tbl_destroy(&dp->table);
free_percpu(dp->stats_percpu);
kfree(dp->ports);
ovs_meters_exit(dp);
@@ -247,6 +250,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
struct ovs_pcpu_storage *ovs_pcpu = this_cpu_ptr(ovs_pcpu_storage);
const struct vport *p = OVS_CB(skb)->input_vport;
struct datapath *dp = p->dp;
+ struct flow_table *table;
struct sw_flow *flow;
struct sw_flow_actions *sf_acts;
struct dp_stats_percpu *stats;
@@ -257,9 +261,16 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
int error;
stats = this_cpu_ptr(dp->stats_percpu);
+ table = rcu_dereference(dp->table);
+ if (!table) {
+ net_dbg_ratelimited("ovs: no flow table on datapath %s\n",
+ ovs_dp_name(dp));
+ kfree_skb(skb);
+ return;
+ }
/* Look up flow. */
- flow = ovs_flow_tbl_lookup_stats(&dp->table, key, skb_get_hash(skb),
+ flow = ovs_flow_tbl_lookup_stats(table, key, skb_get_hash(skb),
&n_mask_hit, &n_cache_hit);
if (unlikely(!flow)) {
struct dp_upcall_info upcall;
@@ -752,12 +763,16 @@ static struct genl_family dp_packet_genl_family __ro_after_init = {
static void get_dp_stats(const struct datapath *dp, struct ovs_dp_stats *stats,
struct ovs_dp_megaflow_stats *mega_stats)
{
+ struct flow_table *table = ovsl_dereference(dp->table);
int i;
memset(mega_stats, 0, sizeof(*mega_stats));
- stats->n_flows = ovs_flow_tbl_count(&dp->table);
- mega_stats->n_masks = ovs_flow_tbl_num_masks(&dp->table);
+ if (table) {
+ stats->n_flows = ovs_flow_tbl_count(table);
+ mega_stats->n_masks = ovs_flow_tbl_num_masks(table);
+ }
+
stats->n_hit = stats->n_missed = stats->n_lost = 0;
@@ -829,15 +844,16 @@ static size_t ovs_flow_cmd_msg_size(const struct sw_flow_actions *acts,
+ nla_total_size_64bit(8); /* OVS_FLOW_ATTR_USED */
}
-/* Called with ovs_mutex or RCU read lock. */
+/* Called with table->lock or RCU read lock. */
static int ovs_flow_cmd_fill_stats(const struct sw_flow *flow,
+ const struct flow_table *table,
struct sk_buff *skb)
{
struct ovs_flow_stats stats;
__be16 tcp_flags;
unsigned long used;
- ovs_flow_stats_get(flow, &stats, &used, &tcp_flags);
+ ovs_flow_stats_get(flow, table, &stats, &used, &tcp_flags);
if (used &&
nla_put_u64_64bit(skb, OVS_FLOW_ATTR_USED, ovs_flow_used_time(used),
@@ -857,8 +873,9 @@ static int ovs_flow_cmd_fill_stats(const struct sw_flow *flow,
return 0;
}
-/* Called with ovs_mutex or RCU read lock. */
+/* Called with RCU read lock or table->lock held. */
static int ovs_flow_cmd_fill_actions(const struct sw_flow *flow,
+ const struct flow_table *table,
struct sk_buff *skb, int skb_orig_len)
{
struct nlattr *start;
@@ -878,7 +895,7 @@ static int ovs_flow_cmd_fill_actions(const struct sw_flow *flow,
if (start) {
const struct sw_flow_actions *sf_acts;
- sf_acts = rcu_dereference_ovsl(flow->sf_acts);
+ sf_acts = rcu_dereference_ovs_tbl(flow->sf_acts, table);
err = ovs_nla_put_actions(sf_acts->actions,
sf_acts->actions_len, skb);
@@ -897,8 +914,10 @@ static int ovs_flow_cmd_fill_actions(const struct sw_flow *flow,
return 0;
}
-/* Called with ovs_mutex or RCU read lock. */
-static int ovs_flow_cmd_fill_info(const struct sw_flow *flow, int dp_ifindex,
+/* Called with table->lock or RCU read lock. */
+static int ovs_flow_cmd_fill_info(const struct sw_flow *flow,
+ const struct flow_table *table,
+ int dp_ifindex,
struct sk_buff *skb, u32 portid,
u32 seq, u32 flags, u8 cmd, u32 ufid_flags)
{
@@ -929,12 +948,12 @@ static int ovs_flow_cmd_fill_info(const struct sw_flow *flow, int dp_ifindex,
goto error;
}
- err = ovs_flow_cmd_fill_stats(flow, skb);
+ err = ovs_flow_cmd_fill_stats(flow, table, skb);
if (err)
goto error;
if (should_fill_actions(ufid_flags)) {
- err = ovs_flow_cmd_fill_actions(flow, skb, skb_orig_len);
+ err = ovs_flow_cmd_fill_actions(flow, table, skb, skb_orig_len);
if (err)
goto error;
}
@@ -968,8 +987,9 @@ static struct sk_buff *ovs_flow_cmd_alloc_info(const struct sw_flow_actions *act
return skb;
}
-/* Called with ovs_mutex. */
+/* Called with table->lock. */
static struct sk_buff *ovs_flow_cmd_build_info(const struct sw_flow *flow,
+ const struct flow_table *table,
int dp_ifindex,
struct genl_info *info, u8 cmd,
bool always, u32 ufid_flags)
@@ -977,12 +997,12 @@ static struct sk_buff *ovs_flow_cmd_build_info(const struct sw_flow *flow,
struct sk_buff *skb;
int retval;
- skb = ovs_flow_cmd_alloc_info(ovsl_dereference(flow->sf_acts),
+ skb = ovs_flow_cmd_alloc_info(ovs_tbl_dereference(flow->sf_acts, table),
&flow->id, info, always, ufid_flags);
if (IS_ERR_OR_NULL(skb))
return skb;
- retval = ovs_flow_cmd_fill_info(flow, dp_ifindex, skb,
+ retval = ovs_flow_cmd_fill_info(flow, table, dp_ifindex, skb,
info->snd_portid, info->snd_seq, 0,
cmd, ufid_flags);
if (WARN_ON_ONCE(retval < 0)) {
@@ -998,6 +1018,7 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
struct nlattr **a = info->attrs;
struct ovs_header *ovs_header = genl_info_userhdr(info);
struct sw_flow *flow = NULL, *new_flow;
+ struct flow_table *table;
struct sw_flow_mask mask;
struct sk_buff *reply;
struct datapath *dp;
@@ -1064,30 +1085,43 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
goto err_kfree_acts;
}
- ovs_lock();
+ rcu_read_lock();
dp = get_dp(net, ovs_header->dp_ifindex);
if (unlikely(!dp)) {
error = -ENODEV;
- goto err_unlock_ovs;
+ rcu_read_unlock();
+ goto err_kfree_reply;
}
+ table = rcu_dereference(dp->table);
+ if (!table || !ovs_flow_tbl_get(table)) {
+ error = -ENODEV;
+ rcu_read_unlock();
+ goto err_kfree_reply;
+ }
+ rcu_read_unlock();
+
+ /* It is safe to dereference "table" after leaving rcu read-protected
+ * region because it's pinned by refcount.
+ */
+ mutex_lock(&table->lock);
/* Check if this is a duplicate flow */
if (ovs_identifier_is_ufid(&new_flow->id))
- flow = ovs_flow_tbl_lookup_ufid(&dp->table, &new_flow->id);
+ flow = ovs_flow_tbl_lookup_ufid(table, &new_flow->id);
if (!flow)
- flow = ovs_flow_tbl_lookup(&dp->table, key);
+ flow = ovs_flow_tbl_lookup(table, key);
if (likely(!flow)) {
rcu_assign_pointer(new_flow->sf_acts, acts);
/* Put flow in bucket. */
- error = ovs_flow_tbl_insert(&dp->table, new_flow, &mask);
+ error = ovs_flow_tbl_insert(table, new_flow, &mask);
if (unlikely(error)) {
acts = NULL;
- goto err_unlock_ovs;
+ goto err_unlock_tbl;
}
if (unlikely(reply)) {
- error = ovs_flow_cmd_fill_info(new_flow,
+ error = ovs_flow_cmd_fill_info(new_flow, table,
ovs_header->dp_ifindex,
reply, info->snd_portid,
info->snd_seq, 0,
@@ -1095,7 +1129,8 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
ufid_flags);
BUG_ON(error < 0);
}
- ovs_unlock();
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
} else {
struct sw_flow_actions *old_acts;
@@ -1108,28 +1143,28 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
if (unlikely(info->nlhdr->nlmsg_flags & (NLM_F_CREATE
| NLM_F_EXCL))) {
error = -EEXIST;
- goto err_unlock_ovs;
+ goto err_unlock_tbl;
}
/* The flow identifier has to be the same for flow updates.
* Look for any overlapping flow.
*/
if (unlikely(!ovs_flow_cmp(flow, &match))) {
if (ovs_identifier_is_key(&flow->id))
- flow = ovs_flow_tbl_lookup_exact(&dp->table,
+ flow = ovs_flow_tbl_lookup_exact(table,
&match);
else /* UFID matches but key is different */
flow = NULL;
if (!flow) {
error = -ENOENT;
- goto err_unlock_ovs;
+ goto err_unlock_tbl;
}
}
/* Update actions. */
- old_acts = ovsl_dereference(flow->sf_acts);
+ old_acts = ovs_tbl_dereference(flow->sf_acts, table);
rcu_assign_pointer(flow->sf_acts, acts);
if (unlikely(reply)) {
- error = ovs_flow_cmd_fill_info(flow,
+ error = ovs_flow_cmd_fill_info(flow, table,
ovs_header->dp_ifindex,
reply, info->snd_portid,
info->snd_seq, 0,
@@ -1137,7 +1172,8 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
ufid_flags);
BUG_ON(error < 0);
}
- ovs_unlock();
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
ovs_nla_free_flow_actions_rcu(old_acts);
ovs_flow_free(new_flow, false);
@@ -1149,8 +1185,10 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)
kfree(key);
return 0;
-err_unlock_ovs:
- ovs_unlock();
+err_unlock_tbl:
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
+err_kfree_reply:
kfree_skb(reply);
err_kfree_acts:
ovs_nla_free_flow_actions(acts);
@@ -1244,6 +1282,7 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
struct net *net = sock_net(skb->sk);
struct nlattr **a = info->attrs;
struct ovs_header *ovs_header = genl_info_userhdr(info);
+ struct flow_table *table;
struct sw_flow_key key;
struct sw_flow *flow;
struct sk_buff *reply = NULL;
@@ -1278,29 +1317,43 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
}
}
- ovs_lock();
+ rcu_read_lock();
dp = get_dp(net, ovs_header->dp_ifindex);
if (unlikely(!dp)) {
error = -ENODEV;
- goto err_unlock_ovs;
+ rcu_read_unlock();
+ goto err_free_reply;
}
+ table = rcu_dereference(dp->table);
+ if (!table || !ovs_flow_tbl_get(table)) {
+ rcu_read_unlock();
+ error = -ENODEV;
+ goto err_free_reply;
+ }
+ rcu_read_unlock();
+
+ /* It is safe to dereference "table" after leaving rcu read-protected
+ * region because it's pinned by refcount.
+ */
+ mutex_lock(&table->lock);
+
/* Check that the flow exists. */
if (ufid_present)
- flow = ovs_flow_tbl_lookup_ufid(&dp->table, &sfid);
+ flow = ovs_flow_tbl_lookup_ufid(table, &sfid);
else
- flow = ovs_flow_tbl_lookup_exact(&dp->table, &match);
+ flow = ovs_flow_tbl_lookup_exact(table, &match);
if (unlikely(!flow)) {
error = -ENOENT;
- goto err_unlock_ovs;
+ goto err_unlock_tbl;
}
/* Update actions, if present. */
if (likely(acts)) {
- old_acts = ovsl_dereference(flow->sf_acts);
+ old_acts = ovs_tbl_dereference(flow->sf_acts, table);
rcu_assign_pointer(flow->sf_acts, acts);
if (unlikely(reply)) {
- error = ovs_flow_cmd_fill_info(flow,
+ error = ovs_flow_cmd_fill_info(flow, table,
ovs_header->dp_ifindex,
reply, info->snd_portid,
info->snd_seq, 0,
@@ -1310,20 +1363,22 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
}
} else {
/* Could not alloc without acts before locking. */
- reply = ovs_flow_cmd_build_info(flow, ovs_header->dp_ifindex,
+ reply = ovs_flow_cmd_build_info(flow, table,
+ ovs_header->dp_ifindex,
info, OVS_FLOW_CMD_SET, false,
ufid_flags);
if (IS_ERR(reply)) {
error = PTR_ERR(reply);
- goto err_unlock_ovs;
+ goto err_unlock_tbl;
}
}
/* Clear stats. */
if (a[OVS_FLOW_ATTR_CLEAR])
- ovs_flow_stats_clear(flow);
- ovs_unlock();
+ ovs_flow_stats_clear(flow, table);
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
if (reply)
ovs_notify(&dp_flow_genl_family, reply, info);
@@ -1332,8 +1387,10 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)
return 0;
-err_unlock_ovs:
- ovs_unlock();
+err_unlock_tbl:
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
+err_free_reply:
kfree_skb(reply);
err_kfree_acts:
ovs_nla_free_flow_actions(acts);
@@ -1346,6 +1403,7 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
struct nlattr **a = info->attrs;
struct ovs_header *ovs_header = genl_info_userhdr(info);
struct net *net = sock_net(skb->sk);
+ struct flow_table *table;
struct sw_flow_key key;
struct sk_buff *reply;
struct sw_flow *flow;
@@ -1370,33 +1428,48 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
if (err)
return err;
- ovs_lock();
+ rcu_read_lock();
dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex);
if (!dp) {
- err = -ENODEV;
- goto unlock;
+ rcu_read_unlock();
+ return -ENODEV;
}
+ table = rcu_dereference(dp->table);
+ if (!table || !ovs_flow_tbl_get(table)) {
+ rcu_read_unlock();
+ return -ENODEV;
+ }
+ rcu_read_unlock();
+
+ /* It is safe to dereference "table" after leaving rcu read-protected
+ * region because it's pinned by refcount.
+ */
+ mutex_lock(&table->lock);
+
if (ufid_present)
- flow = ovs_flow_tbl_lookup_ufid(&dp->table, &ufid);
+ flow = ovs_flow_tbl_lookup_ufid(table, &ufid);
else
- flow = ovs_flow_tbl_lookup_exact(&dp->table, &match);
+ flow = ovs_flow_tbl_lookup_exact(table, &match);
if (!flow) {
err = -ENOENT;
goto unlock;
}
- reply = ovs_flow_cmd_build_info(flow, ovs_header->dp_ifindex, info,
- OVS_FLOW_CMD_GET, true, ufid_flags);
+ reply = ovs_flow_cmd_build_info(flow, table, ovs_header->dp_ifindex,
+ info, OVS_FLOW_CMD_GET, true,
+ ufid_flags);
if (IS_ERR(reply)) {
err = PTR_ERR(reply);
goto unlock;
}
- ovs_unlock();
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
return genlmsg_reply(reply, info);
unlock:
- ovs_unlock();
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
return err;
}
@@ -1405,6 +1478,7 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
struct nlattr **a = info->attrs;
struct ovs_header *ovs_header = genl_info_userhdr(info);
struct net *net = sock_net(skb->sk);
+ struct flow_table *table;
struct sw_flow_key key;
struct sk_buff *reply;
struct sw_flow *flow = NULL;
@@ -1425,36 +1499,49 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
return err;
}
- ovs_lock();
+ rcu_read_lock();
dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex);
if (unlikely(!dp)) {
- err = -ENODEV;
- goto unlock;
+ rcu_read_unlock();
+ return -ENODEV;
}
+ table = rcu_dereference(dp->table);
+ if (!table || !ovs_flow_tbl_get(table)) {
+ rcu_read_unlock();
+ return -ENODEV;
+ }
+ rcu_read_unlock();
+
+ /* It is safe to dereference "table" after leaving rcu read-protected
+ * region because it's pinned by refcount.
+ */
+ mutex_lock(&table->lock);
+
if (unlikely(!a[OVS_FLOW_ATTR_KEY] && !ufid_present)) {
- err = ovs_flow_tbl_flush(&dp->table);
+ err = ovs_flow_tbl_flush(table);
goto unlock;
}
if (ufid_present)
- flow = ovs_flow_tbl_lookup_ufid(&dp->table, &ufid);
+ flow = ovs_flow_tbl_lookup_ufid(table, &ufid);
else
- flow = ovs_flow_tbl_lookup_exact(&dp->table, &match);
+ flow = ovs_flow_tbl_lookup_exact(table, &match);
if (unlikely(!flow)) {
err = -ENOENT;
goto unlock;
}
- ovs_flow_tbl_remove(&dp->table, flow);
- ovs_unlock();
+ ovs_flow_tbl_remove(table, flow);
+ mutex_unlock(&table->lock);
reply = ovs_flow_cmd_alloc_info((const struct sw_flow_actions __force *) flow->sf_acts,
&flow->id, info, false, ufid_flags);
if (likely(reply)) {
if (!IS_ERR(reply)) {
rcu_read_lock(); /*To keep RCU checker happy. */
- err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex,
+ err = ovs_flow_cmd_fill_info(flow, table,
+ ovs_header->dp_ifindex,
reply, info->snd_portid,
info->snd_seq, 0,
OVS_FLOW_CMD_DEL,
@@ -1473,10 +1560,12 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
}
out_free:
+ ovs_flow_tbl_put(table);
ovs_flow_free(flow, true);
return 0;
unlock:
- ovs_unlock();
+ mutex_unlock(&table->lock);
+ ovs_flow_tbl_put(table);
return err;
}
@@ -1485,6 +1574,7 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
struct nlattr *a[__OVS_FLOW_ATTR_MAX];
struct ovs_header *ovs_header = genlmsg_data(nlmsg_data(cb->nlh));
struct table_instance *ti;
+ struct flow_table *table;
struct datapath *dp;
u32 ufid_flags;
int err;
@@ -1501,8 +1591,13 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
rcu_read_unlock();
return -ENODEV;
}
+ table = rcu_dereference(dp->table);
+ if (!table) {
+ rcu_read_unlock();
+ return -ENODEV;
+ }
- ti = rcu_dereference(dp->table.ti);
+ ti = rcu_dereference(table->ti);
for (;;) {
struct sw_flow *flow;
u32 bucket, obj;
@@ -1513,8 +1608,8 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
if (!flow)
break;
- if (ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, skb,
- NETLINK_CB(cb->skb).portid,
+ if (ovs_flow_cmd_fill_info(flow, table, ovs_header->dp_ifindex,
+ skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
OVS_FLOW_CMD_GET, ufid_flags) < 0)
break;
@@ -1598,8 +1693,13 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
struct ovs_dp_stats dp_stats;
struct ovs_dp_megaflow_stats dp_megaflow_stats;
struct dp_nlsk_pids *pids = ovsl_dereference(dp->upcall_portids);
+ struct flow_table *table;
int err, pids_len;
+ table = ovsl_dereference(dp->table);
+ if (!table)
+ return -ENODEV;
+
ovs_header = genlmsg_put(skb, portid, seq, &dp_datapath_genl_family,
flags, cmd);
if (!ovs_header)
@@ -1625,7 +1725,7 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
goto nla_put_failure;
if (nla_put_u32(skb, OVS_DP_ATTR_MASKS_CACHE_SIZE,
- ovs_flow_tbl_masks_cache_size(&dp->table)))
+ ovs_flow_tbl_masks_cache_size(table)))
goto nla_put_failure;
if (dp->user_features & OVS_DP_F_DISPATCH_UPCALL_PER_CPU && pids) {
@@ -1736,6 +1836,7 @@ u32 ovs_dp_get_upcall_portid(const struct datapath *dp, uint32_t cpu_id)
static int ovs_dp_change(struct datapath *dp, struct nlattr *a[])
{
u32 user_features = 0, old_features = dp->user_features;
+ struct flow_table *table;
int err;
if (a[OVS_DP_ATTR_USER_FEATURES]) {
@@ -1757,8 +1858,12 @@ static int ovs_dp_change(struct datapath *dp, struct nlattr *a[])
int err;
u32 cache_size;
+ table = ovsl_dereference(dp->table);
+ if (!table)
+ return -ENODEV;
+
cache_size = nla_get_u32(a[OVS_DP_ATTR_MASKS_CACHE_SIZE]);
- err = ovs_flow_tbl_masks_cache_resize(&dp->table, cache_size);
+ err = ovs_flow_tbl_masks_cache_resize(table, cache_size);
if (err)
return err;
}
@@ -1810,6 +1915,7 @@ static int ovs_dp_vport_init(struct datapath *dp)
static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr **a = info->attrs;
+ struct flow_table *table;
struct vport_parms parms;
struct sk_buff *reply;
struct datapath *dp;
@@ -1833,9 +1939,12 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
ovs_dp_set_net(dp, sock_net(skb->sk));
/* Allocate table. */
- err = ovs_flow_tbl_init(&dp->table);
- if (err)
+ table = ovs_flow_tbl_alloc();
+ if (IS_ERR(table)) {
+ err = PTR_ERR(table);
goto err_destroy_dp;
+ }
+ rcu_assign_pointer(dp->table, table);
err = ovs_dp_stats_init(dp);
if (err)
@@ -1905,7 +2014,7 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
err_destroy_stats:
free_percpu(dp->stats_percpu);
err_destroy_table:
- ovs_flow_tbl_destroy(&dp->table);
+ ovs_flow_tbl_put(table);
err_destroy_dp:
kfree(dp);
err_destroy_reply:
@@ -1917,7 +2026,8 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
/* Called with ovs_mutex. */
static void __dp_destroy(struct datapath *dp)
{
- struct flow_table *table = &dp->table;
+ struct flow_table *table = rcu_dereference_protected(dp->table,
+ lockdep_ovsl_is_held());
int i;
if (dp->user_features & OVS_DP_F_TC_RECIRC_SHARING)
@@ -1939,14 +2049,10 @@ static void __dp_destroy(struct datapath *dp)
*/
ovs_dp_detach_port(ovs_vport_ovsl(dp, OVSP_LOCAL));
- /* Flush sw_flow in the tables. RCU cb only releases resource
- * such as dp, ports and tables. That may avoid some issues
- * such as RCU usage warning.
- */
- table_instance_flow_flush(table, ovsl_dereference(table->ti),
- ovsl_dereference(table->ufid_ti));
+ rcu_assign_pointer(dp->table, NULL);
+ ovs_flow_tbl_put(table);
- /* RCU destroy the ports, meters and flow tables. */
+ /* RCU destroy the ports and meters. */
call_rcu(&dp->rcu, destroy_dp_rcu);
}
@@ -2554,13 +2660,18 @@ static void ovs_dp_masks_rebalance(struct work_struct *work)
{
struct ovs_net *ovs_net = container_of(work, struct ovs_net,
masks_rebalance.work);
+ struct flow_table *table;
struct datapath *dp;
ovs_lock();
-
- list_for_each_entry(dp, &ovs_net->dps, list_node)
- ovs_flow_masks_rebalance(&dp->table);
-
+ list_for_each_entry(dp, &ovs_net->dps, list_node) {
+ table = ovsl_dereference(dp->table);
+ if (!table)
+ continue;
+ mutex_lock(&table->lock);
+ ovs_flow_masks_rebalance(table);
+ mutex_unlock(&table->lock);
+ }
ovs_unlock();
schedule_delayed_work(&ovs_net->masks_rebalance,
diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
index db0c3e69d66c..44773bf9f645 100644
--- a/net/openvswitch/datapath.h
+++ b/net/openvswitch/datapath.h
@@ -90,7 +90,7 @@ struct datapath {
struct list_head list_node;
/* Flow table. */
- struct flow_table table;
+ struct flow_table __rcu *table;
/* Switch ports. */
struct hlist_head *ports;
diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c
index 66366982f604..0a748cf20f53 100644
--- a/net/openvswitch/flow.c
+++ b/net/openvswitch/flow.c
@@ -124,8 +124,9 @@ void ovs_flow_stats_update(struct sw_flow *flow, __be16 tcp_flags,
spin_unlock(&stats->lock);
}
-/* Must be called with rcu_read_lock or ovs_mutex. */
+/* Must be called with rcu_read_lock or table->lock held. */
void ovs_flow_stats_get(const struct sw_flow *flow,
+ const struct flow_table *table,
struct ovs_flow_stats *ovs_stats,
unsigned long *used, __be16 *tcp_flags)
{
@@ -136,7 +137,8 @@ void ovs_flow_stats_get(const struct sw_flow *flow,
memset(ovs_stats, 0, sizeof(*ovs_stats));
for_each_cpu(cpu, flow->cpu_used_mask) {
- struct sw_flow_stats *stats = rcu_dereference_ovsl(flow->stats[cpu]);
+ struct sw_flow_stats *stats =
+ rcu_dereference_ovs_tbl(flow->stats[cpu], table);
if (stats) {
/* Local CPU may write on non-local stats, so we must
@@ -153,13 +155,14 @@ void ovs_flow_stats_get(const struct sw_flow *flow,
}
}
-/* Called with ovs_mutex. */
-void ovs_flow_stats_clear(struct sw_flow *flow)
+/* Called with table->lock held. */
+void ovs_flow_stats_clear(struct sw_flow *flow, struct flow_table *table)
{
unsigned int cpu;
for_each_cpu(cpu, flow->cpu_used_mask) {
- struct sw_flow_stats *stats = ovsl_dereference(flow->stats[cpu]);
+ struct sw_flow_stats *stats =
+ ovs_tbl_dereference(flow->stats[cpu], table);
if (stats) {
spin_lock_bh(&stats->lock);
diff --git a/net/openvswitch/flow.h b/net/openvswitch/flow.h
index b5711aff6e76..e05ed6796e4e 100644
--- a/net/openvswitch/flow.h
+++ b/net/openvswitch/flow.h
@@ -23,6 +23,7 @@
#include <net/dst_metadata.h>
#include <net/nsh.h>
+struct flow_table;
struct sk_buff;
enum sw_flow_mac_proto {
@@ -280,9 +281,11 @@ static inline bool ovs_identifier_is_key(const struct sw_flow_id *sfid)
void ovs_flow_stats_update(struct sw_flow *, __be16 tcp_flags,
const struct sk_buff *);
-void ovs_flow_stats_get(const struct sw_flow *, struct ovs_flow_stats *,
- unsigned long *used, __be16 *tcp_flags);
-void ovs_flow_stats_clear(struct sw_flow *);
+void ovs_flow_stats_get(const struct sw_flow *flow,
+ const struct flow_table *table,
+ struct ovs_flow_stats *stats, unsigned long *used,
+ __be16 *tcp_flags);
+void ovs_flow_stats_clear(struct sw_flow *flow, struct flow_table *table);
u64 ovs_flow_used_time(unsigned long flow_jiffies);
int ovs_flow_key_update(struct sk_buff *skb, struct sw_flow_key *key);
diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c
index 61c6a5f77c2e..d9dbe4b4807c 100644
--- a/net/openvswitch/flow_table.c
+++ b/net/openvswitch/flow_table.c
@@ -45,6 +45,16 @@
static struct kmem_cache *flow_cache;
struct kmem_cache *flow_stats_cache __read_mostly;
+#ifdef CONFIG_LOCKDEP
+int lockdep_ovs_tbl_is_held(const struct flow_table *table)
+{
+ if (debug_locks)
+ return lockdep_is_held(&table->lock);
+ else
+ return 1;
+}
+#endif
+
static u16 range_n_bytes(const struct sw_flow_key_range *range)
{
return range->end - range->start;
@@ -249,12 +259,12 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size)
if (!new)
return -ENOMEM;
- old = ovsl_dereference(tbl->mask_array);
+ old = ovs_tbl_dereference(tbl->mask_array, tbl);
if (old) {
int i;
for (i = 0; i < old->max; i++) {
- if (ovsl_dereference(old->masks[i]))
+ if (ovs_tbl_dereference(old->masks[i], tbl))
new->masks[new->count++] = old->masks[i];
}
call_rcu(&old->rcu, mask_array_rcu_cb);
@@ -268,7 +278,7 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size)
static int tbl_mask_array_add_mask(struct flow_table *tbl,
struct sw_flow_mask *new)
{
- struct mask_array *ma = ovsl_dereference(tbl->mask_array);
+ struct mask_array *ma = ovs_tbl_dereference(tbl->mask_array, tbl);
int err, ma_count = READ_ONCE(ma->count);
if (ma_count >= ma->max) {
@@ -277,7 +287,7 @@ static int tbl_mask_array_add_mask(struct flow_table *tbl,
if (err)
return err;
- ma = ovsl_dereference(tbl->mask_array);
+ ma = ovs_tbl_dereference(tbl->mask_array, tbl);
} else {
/* On every add or delete we need to reset the counters so
* every new mask gets a fair chance of being prioritized.
@@ -285,7 +295,7 @@ static int tbl_mask_array_add_mask(struct flow_table *tbl,
tbl_mask_array_reset_counters(ma);
}
- BUG_ON(ovsl_dereference(ma->masks[ma_count]));
+ WARN_ON_ONCE(ovs_tbl_dereference(ma->masks[ma_count], tbl));
rcu_assign_pointer(ma->masks[ma_count], new);
WRITE_ONCE(ma->count, ma_count + 1);
@@ -296,12 +306,12 @@ static int tbl_mask_array_add_mask(struct flow_table *tbl,
static void tbl_mask_array_del_mask(struct flow_table *tbl,
struct sw_flow_mask *mask)
{
- struct mask_array *ma = ovsl_dereference(tbl->mask_array);
+ struct mask_array *ma = ovs_tbl_dereference(tbl->mask_array, tbl);
int i, ma_count = READ_ONCE(ma->count);
/* Remove the deleted mask pointers from the array */
for (i = 0; i < ma_count; i++) {
- if (mask == ovsl_dereference(ma->masks[i]))
+ if (mask == ovs_tbl_dereference(ma->masks[i], tbl))
goto found;
}
@@ -329,10 +339,10 @@ static void tbl_mask_array_del_mask(struct flow_table *tbl,
static void flow_mask_remove(struct flow_table *tbl, struct sw_flow_mask *mask)
{
if (mask) {
- /* ovs-lock is required to protect mask-refcount and
+ /* table lock is required to protect mask-refcount and
* mask list.
*/
- ASSERT_OVSL();
+ ASSERT_OVS_TBL(tbl);
BUG_ON(!mask->ref_count);
mask->ref_count--;
@@ -386,7 +396,8 @@ static struct mask_cache *tbl_mask_cache_alloc(u32 size)
}
int ovs_flow_tbl_masks_cache_resize(struct flow_table *table, u32 size)
{
- struct mask_cache *mc = rcu_dereference_ovsl(table->mask_cache);
+ struct mask_cache *mc = rcu_dereference_ovs_tbl(table->mask_cache,
+ table);
struct mask_cache *new;
if (size == mc->cache_size)
@@ -406,15 +417,23 @@ int ovs_flow_tbl_masks_cache_resize(struct flow_table *table, u32 size)
return 0;
}
-int ovs_flow_tbl_init(struct flow_table *table)
+struct flow_table *ovs_flow_tbl_alloc(void)
{
struct table_instance *ti, *ufid_ti;
+ struct flow_table *table;
struct mask_cache *mc;
struct mask_array *ma;
+ table = kzalloc_obj(*table, GFP_KERNEL);
+ if (!table)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&table->lock);
+ refcount_set(&table->refcnt, 1);
+
mc = tbl_mask_cache_alloc(MC_DEFAULT_HASH_ENTRIES);
if (!mc)
- return -ENOMEM;
+ goto free_table;
ma = tbl_mask_array_alloc(MASK_ARRAY_SIZE_MIN);
if (!ma)
@@ -435,7 +454,7 @@ int ovs_flow_tbl_init(struct flow_table *table)
table->last_rehash = jiffies;
table->count = 0;
table->ufid_count = 0;
- return 0;
+ return table;
free_ti:
__table_instance_destroy(ti);
@@ -443,7 +462,10 @@ int ovs_flow_tbl_init(struct flow_table *table)
__mask_array_destroy(ma);
free_mask_cache:
__mask_cache_destroy(mc);
- return -ENOMEM;
+free_table:
+ mutex_destroy(&table->lock);
+ kfree(table);
+ return ERR_PTR(-ENOMEM);
}
static void flow_tbl_destroy_rcu_cb(struct rcu_head *rcu)
@@ -470,7 +492,7 @@ static void table_instance_flow_free(struct flow_table *table,
flow_mask_remove(table, flow->mask);
}
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
void table_instance_flow_flush(struct flow_table *table,
struct table_instance *ti,
struct table_instance *ufid_ti)
@@ -505,11 +527,11 @@ static void table_instance_destroy(struct table_instance *ti,
call_rcu(&ufid_ti->rcu, flow_tbl_destroy_rcu_cb);
}
-/* No need for locking this function is called from RCU callback or
- * error path.
- */
-void ovs_flow_tbl_destroy(struct flow_table *table)
+/* No need for locking this function is called from RCU callback. */
+static void ovs_flow_tbl_destroy_rcu(struct rcu_head *rcu)
{
+ struct flow_table *table = container_of(rcu, struct flow_table, rcu);
+
struct table_instance *ti = rcu_dereference_raw(table->ti);
struct table_instance *ufid_ti = rcu_dereference_raw(table->ufid_ti);
struct mask_cache *mc = rcu_dereference_raw(table->mask_cache);
@@ -518,6 +540,20 @@ void ovs_flow_tbl_destroy(struct flow_table *table)
call_rcu(&mc->rcu, mask_cache_rcu_cb);
call_rcu(&ma->rcu, mask_array_rcu_cb);
table_instance_destroy(ti, ufid_ti);
+ mutex_destroy(&table->lock);
+ kfree(table);
+}
+
+void ovs_flow_tbl_put(struct flow_table *table)
+{
+ if (refcount_dec_and_test(&table->refcnt)) {
+ mutex_lock(&table->lock);
+ table_instance_flow_flush(table,
+ ovs_tbl_dereference(table->ti, table),
+ ovs_tbl_dereference(table->ufid_ti, table));
+ mutex_unlock(&table->lock);
+ call_rcu(&table->rcu, ovs_flow_tbl_destroy_rcu);
+ }
}
struct sw_flow *ovs_flow_tbl_dump_next(struct table_instance *ti,
@@ -571,7 +607,8 @@ static void ufid_table_instance_insert(struct table_instance *ti,
hlist_add_head_rcu(&flow->ufid_table.node[ti->node_ver], head);
}
-static void flow_table_copy_flows(struct table_instance *old,
+static void flow_table_copy_flows(struct flow_table *table,
+ struct table_instance *old,
struct table_instance *new, bool ufid)
{
int old_ver;
@@ -588,17 +625,18 @@ static void flow_table_copy_flows(struct table_instance *old,
if (ufid)
hlist_for_each_entry_rcu(flow, head,
ufid_table.node[old_ver],
- lockdep_ovsl_is_held())
+ lockdep_ovs_tbl_is_held(table))
ufid_table_instance_insert(new, flow);
else
hlist_for_each_entry_rcu(flow, head,
flow_table.node[old_ver],
- lockdep_ovsl_is_held())
+ lockdep_ovs_tbl_is_held(table))
table_instance_insert(new, flow);
}
}
-static struct table_instance *table_instance_rehash(struct table_instance *ti,
+static struct table_instance *table_instance_rehash(struct flow_table *table,
+ struct table_instance *ti,
int n_buckets, bool ufid)
{
struct table_instance *new_ti;
@@ -607,16 +645,19 @@ static struct table_instance *table_instance_rehash(struct table_instance *ti,
if (!new_ti)
return NULL;
- flow_table_copy_flows(ti, new_ti, ufid);
+ flow_table_copy_flows(table, ti, new_ti, ufid);
return new_ti;
}
+/* Must be called with flow_table->lock held. */
int ovs_flow_tbl_flush(struct flow_table *flow_table)
{
struct table_instance *old_ti, *new_ti;
struct table_instance *old_ufid_ti, *new_ufid_ti;
+ ASSERT_OVS_TBL(flow_table);
+
new_ti = table_instance_alloc(TBL_MIN_BUCKETS);
if (!new_ti)
return -ENOMEM;
@@ -624,8 +665,8 @@ int ovs_flow_tbl_flush(struct flow_table *flow_table)
if (!new_ufid_ti)
goto err_free_ti;
- old_ti = ovsl_dereference(flow_table->ti);
- old_ufid_ti = ovsl_dereference(flow_table->ufid_ti);
+ old_ti = ovs_tbl_dereference(flow_table->ti, flow_table);
+ old_ufid_ti = ovs_tbl_dereference(flow_table->ufid_ti, flow_table);
rcu_assign_pointer(flow_table->ti, new_ti);
rcu_assign_pointer(flow_table->ufid_ti, new_ufid_ti);
@@ -693,7 +734,8 @@ static bool ovs_flow_cmp_unmasked_key(const struct sw_flow *flow,
return cmp_key(flow->id.unmasked_key, key, key_start, key_end);
}
-static struct sw_flow *masked_flow_lookup(struct table_instance *ti,
+static struct sw_flow *masked_flow_lookup(struct flow_table *tbl,
+ struct table_instance *ti,
const struct sw_flow_key *unmasked,
const struct sw_flow_mask *mask,
u32 *n_mask_hit)
@@ -709,7 +751,7 @@ static struct sw_flow *masked_flow_lookup(struct table_instance *ti,
(*n_mask_hit)++;
hlist_for_each_entry_rcu(flow, head, flow_table.node[ti->node_ver],
- lockdep_ovsl_is_held()) {
+ lockdep_ovs_tbl_is_held(tbl)) {
if (flow->mask == mask && flow->flow_table.hash == hash &&
flow_cmp_masked_key(flow, &masked_key, &mask->range))
return flow;
@@ -736,9 +778,9 @@ static struct sw_flow *flow_lookup(struct flow_table *tbl,
int i;
if (likely(*index < ma->max)) {
- mask = rcu_dereference_ovsl(ma->masks[*index]);
+ mask = rcu_dereference_ovs_tbl(ma->masks[*index], tbl);
if (mask) {
- flow = masked_flow_lookup(ti, key, mask, n_mask_hit);
+ flow = masked_flow_lookup(tbl, ti, key, mask, n_mask_hit);
if (flow) {
u64_stats_update_begin(&stats->syncp);
stats->usage_cntrs[*index]++;
@@ -754,11 +796,11 @@ static struct sw_flow *flow_lookup(struct flow_table *tbl,
if (i == *index)
continue;
- mask = rcu_dereference_ovsl(ma->masks[i]);
+ mask = rcu_dereference_ovs_tbl(ma->masks[i], tbl);
if (unlikely(!mask))
break;
- flow = masked_flow_lookup(ti, key, mask, n_mask_hit);
+ flow = masked_flow_lookup(tbl, ti, key, mask, n_mask_hit);
if (flow) { /* Found */
*index = i;
u64_stats_update_begin(&stats->syncp);
@@ -845,8 +887,8 @@ struct sw_flow *ovs_flow_tbl_lookup_stats(struct flow_table *tbl,
struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *tbl,
const struct sw_flow_key *key)
{
- struct table_instance *ti = rcu_dereference_ovsl(tbl->ti);
- struct mask_array *ma = rcu_dereference_ovsl(tbl->mask_array);
+ struct table_instance *ti = rcu_dereference_ovs_tbl(tbl->ti, tbl);
+ struct mask_array *ma = rcu_dereference_ovs_tbl(tbl->mask_array, tbl);
u32 __always_unused n_mask_hit;
u32 __always_unused n_cache_hit;
struct sw_flow *flow;
@@ -865,21 +907,22 @@ struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *tbl,
struct sw_flow *ovs_flow_tbl_lookup_exact(struct flow_table *tbl,
const struct sw_flow_match *match)
{
- struct mask_array *ma = ovsl_dereference(tbl->mask_array);
+ struct mask_array *ma = ovs_tbl_dereference(tbl->mask_array, tbl);
int i;
- /* Always called under ovs-mutex. */
+ /* Always called under tbl->lock. */
for (i = 0; i < ma->max; i++) {
- struct table_instance *ti = rcu_dereference_ovsl(tbl->ti);
+ struct table_instance *ti =
+ rcu_dereference_ovs_tbl(tbl->ti, tbl);
u32 __always_unused n_mask_hit;
struct sw_flow_mask *mask;
struct sw_flow *flow;
- mask = ovsl_dereference(ma->masks[i]);
+ mask = ovs_tbl_dereference(ma->masks[i], tbl);
if (!mask)
continue;
- flow = masked_flow_lookup(ti, match->key, mask, &n_mask_hit);
+ flow = masked_flow_lookup(tbl, ti, match->key, mask, &n_mask_hit);
if (flow && ovs_identifier_is_key(&flow->id) &&
ovs_flow_cmp_unmasked_key(flow, match)) {
return flow;
@@ -915,7 +958,7 @@ bool ovs_flow_cmp(const struct sw_flow *flow,
struct sw_flow *ovs_flow_tbl_lookup_ufid(struct flow_table *tbl,
const struct sw_flow_id *ufid)
{
- struct table_instance *ti = rcu_dereference_ovsl(tbl->ufid_ti);
+ struct table_instance *ti = rcu_dereference_ovs_tbl(tbl->ufid_ti, tbl);
struct sw_flow *flow;
struct hlist_head *head;
u32 hash;
@@ -923,7 +966,7 @@ struct sw_flow *ovs_flow_tbl_lookup_ufid(struct flow_table *tbl,
hash = ufid_hash(ufid);
head = find_bucket(ti, hash);
hlist_for_each_entry_rcu(flow, head, ufid_table.node[ti->node_ver],
- lockdep_ovsl_is_held()) {
+ lockdep_ovs_tbl_is_held(tbl)) {
if (flow->ufid_table.hash == hash &&
ovs_flow_cmp_ufid(flow, ufid))
return flow;
@@ -933,28 +976,33 @@ struct sw_flow *ovs_flow_tbl_lookup_ufid(struct flow_table *tbl,
int ovs_flow_tbl_num_masks(const struct flow_table *table)
{
- struct mask_array *ma = rcu_dereference_ovsl(table->mask_array);
+ struct mask_array *ma = rcu_dereference_ovs_tbl(table->mask_array,
+ table);
return READ_ONCE(ma->count);
}
u32 ovs_flow_tbl_masks_cache_size(const struct flow_table *table)
{
- struct mask_cache *mc = rcu_dereference_ovsl(table->mask_cache);
+ struct mask_cache *mc = rcu_dereference_ovs_tbl(table->mask_cache,
+ table);
return READ_ONCE(mc->cache_size);
}
-static struct table_instance *table_instance_expand(struct table_instance *ti,
+static struct table_instance *table_instance_expand(struct flow_table *table,
+ struct table_instance *ti,
bool ufid)
{
- return table_instance_rehash(ti, ti->n_buckets * 2, ufid);
+ return table_instance_rehash(table, ti, ti->n_buckets * 2, ufid);
}
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
void ovs_flow_tbl_remove(struct flow_table *table, struct sw_flow *flow)
{
- struct table_instance *ti = ovsl_dereference(table->ti);
- struct table_instance *ufid_ti = ovsl_dereference(table->ufid_ti);
+ struct table_instance *ti = ovs_tbl_dereference(table->ti,
+ table);
+ struct table_instance *ufid_ti = ovs_tbl_dereference(table->ufid_ti,
+ table);
BUG_ON(table->count == 0);
table_instance_flow_free(table, ti, ufid_ti, flow);
@@ -988,10 +1036,10 @@ static struct sw_flow_mask *flow_mask_find(const struct flow_table *tbl,
struct mask_array *ma;
int i;
- ma = ovsl_dereference(tbl->mask_array);
+ ma = ovs_tbl_dereference(tbl->mask_array, tbl);
for (i = 0; i < ma->max; i++) {
struct sw_flow_mask *t;
- t = ovsl_dereference(ma->masks[i]);
+ t = ovs_tbl_dereference(ma->masks[i], tbl);
if (t && mask_equal(mask, t))
return t;
@@ -1029,22 +1077,25 @@ static int flow_mask_insert(struct flow_table *tbl, struct sw_flow *flow,
return 0;
}
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
static void flow_key_insert(struct flow_table *table, struct sw_flow *flow)
{
struct table_instance *new_ti = NULL;
struct table_instance *ti;
+ ASSERT_OVS_TBL(table);
+
flow->flow_table.hash = flow_hash(&flow->key, &flow->mask->range);
- ti = ovsl_dereference(table->ti);
+ ti = ovs_tbl_dereference(table->ti, table);
table_instance_insert(ti, flow);
table->count++;
/* Expand table, if necessary, to make room. */
if (table->count > ti->n_buckets)
- new_ti = table_instance_expand(ti, false);
+ new_ti = table_instance_expand(table, ti, false);
else if (time_after(jiffies, table->last_rehash + REHASH_INTERVAL))
- new_ti = table_instance_rehash(ti, ti->n_buckets, false);
+ new_ti = table_instance_rehash(table, ti, ti->n_buckets,
+ false);
if (new_ti) {
rcu_assign_pointer(table->ti, new_ti);
@@ -1053,13 +1104,15 @@ static void flow_key_insert(struct flow_table *table, struct sw_flow *flow)
}
}
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
static void flow_ufid_insert(struct flow_table *table, struct sw_flow *flow)
{
struct table_instance *ti;
+ ASSERT_OVS_TBL(table);
+
flow->ufid_table.hash = ufid_hash(&flow->id);
- ti = ovsl_dereference(table->ufid_ti);
+ ti = ovs_tbl_dereference(table->ufid_ti, table);
ufid_table_instance_insert(ti, flow);
table->ufid_count++;
@@ -1067,7 +1120,7 @@ static void flow_ufid_insert(struct flow_table *table, struct sw_flow *flow)
if (table->ufid_count > ti->n_buckets) {
struct table_instance *new_ti;
- new_ti = table_instance_expand(ti, true);
+ new_ti = table_instance_expand(table, ti, true);
if (new_ti) {
rcu_assign_pointer(table->ufid_ti, new_ti);
call_rcu(&ti->rcu, flow_tbl_destroy_rcu_cb);
@@ -1075,12 +1128,14 @@ static void flow_ufid_insert(struct flow_table *table, struct sw_flow *flow)
}
}
-/* Must be called with OVS mutex held. */
+/* Must be called with table mutex held. */
int ovs_flow_tbl_insert(struct flow_table *table, struct sw_flow *flow,
const struct sw_flow_mask *mask)
{
int err;
+ ASSERT_OVS_TBL(table);
+
err = flow_mask_insert(table, flow, mask);
if (err)
return err;
@@ -1099,10 +1154,11 @@ static int compare_mask_and_count(const void *a, const void *b)
return (s64)mc_b->counter - (s64)mc_a->counter;
}
-/* Must be called with OVS mutex held. */
+/* Must be called with table->lock held. */
void ovs_flow_masks_rebalance(struct flow_table *table)
{
- struct mask_array *ma = rcu_dereference_ovsl(table->mask_array);
+ struct mask_array *ma = rcu_dereference_ovs_tbl(table->mask_array,
+ table);
struct mask_count *masks_and_count;
struct mask_array *new;
int masks_entries = 0;
@@ -1117,7 +1173,7 @@ void ovs_flow_masks_rebalance(struct flow_table *table)
struct sw_flow_mask *mask;
int cpu;
- mask = rcu_dereference_ovsl(ma->masks[i]);
+ mask = rcu_dereference_ovs_tbl(ma->masks[i], table);
if (unlikely(!mask))
break;
@@ -1171,7 +1227,7 @@ void ovs_flow_masks_rebalance(struct flow_table *table)
for (i = 0; i < masks_entries; i++) {
int index = masks_and_count[i].index;
- if (ovsl_dereference(ma->masks[index]))
+ if (ovs_tbl_dereference(ma->masks[index], table))
new->masks[new->count++] = ma->masks[index];
}
diff --git a/net/openvswitch/flow_table.h b/net/openvswitch/flow_table.h
index f524dc3e4862..cffd412c9045 100644
--- a/net/openvswitch/flow_table.h
+++ b/net/openvswitch/flow_table.h
@@ -59,7 +59,29 @@ struct table_instance {
u32 hash_seed;
};
+/* Locking:
+ *
+ * flow_table is _not_ protected by ovs_lock (see comment above ovs_mutex
+ * in datapath.c).
+ *
+ * All writes to flow_table are protected by the embedded "lock".
+ * In order to ensure datapath destruction does not trigger the destruction
+ * of the flow_table, "refcnt" is used. Therefore, writers must:
+ * 1 - Enter rcu read-protected section
+ * 2 - Increase "table->refcnt"
+ * 3 - Leave rcu read-protected section (to avoid using mutexes inside rcu)
+ * 4 - Lock "table->lock"
+ * 5 - Perform modifications
+ * 6 - Release "table->lock"
+ * 7 - Decrease "table->refcnt"
+ *
+ * Reads are protected by RCU.
+ */
struct flow_table {
+ /* Locks flow table writes. */
+ struct mutex lock;
+ refcount_t refcnt;
+ struct rcu_head rcu;
struct table_instance __rcu *ti;
struct table_instance __rcu *ufid_ti;
struct mask_cache __rcu *mask_cache;
@@ -71,15 +93,40 @@ struct flow_table {
extern struct kmem_cache *flow_stats_cache;
+#ifdef CONFIG_LOCKDEP
+int lockdep_ovs_tbl_is_held(const struct flow_table *table);
+#else
+static inline int lockdep_ovs_tbl_is_held(const struct flow_table *table)
+{
+ (void)table;
+ return 1;
+}
+#endif
+
+#define ASSERT_OVS_TBL(tbl) WARN_ON(!lockdep_ovs_tbl_is_held(tbl))
+
+/* Lock-protected update-allowed dereferences.*/
+#define ovs_tbl_dereference(p, tbl) \
+ rcu_dereference_protected(p, lockdep_ovs_tbl_is_held(tbl))
+
+/* Read dereferences can be protected by either RCU, table lock or ovs_mutex. */
+#define rcu_dereference_ovs_tbl(p, tbl) \
+ rcu_dereference_check(p, \
+ lockdep_ovs_tbl_is_held(tbl) || lockdep_ovsl_is_held())
+
int ovs_flow_init(void);
void ovs_flow_exit(void);
struct sw_flow *ovs_flow_alloc(void);
void ovs_flow_free(struct sw_flow *, bool deferred);
-int ovs_flow_tbl_init(struct flow_table *);
+struct flow_table *ovs_flow_tbl_alloc(void);
+void ovs_flow_tbl_put(struct flow_table *table);
+static inline bool ovs_flow_tbl_get(struct flow_table *table)
+{
+ return refcount_inc_not_zero(&table->refcnt);
+}
int ovs_flow_tbl_count(const struct flow_table *table);
-void ovs_flow_tbl_destroy(struct flow_table *table);
int ovs_flow_tbl_flush(struct flow_table *flow_table);
int ovs_flow_tbl_insert(struct flow_table *table, struct sw_flow *flow,
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net-next v5 09/10] net: team: Add new tx_enabled team port option
From: Jiri Pirko @ 2026-04-07 11:57 UTC (permalink / raw)
To: Marc Harvey
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260406-teaming-driver-internal-v5-9-e8a3f348a1c5@google.com>
Mon, Apr 06, 2026 at 05:03:45AM +0200, marcharvey@google.com wrote:
>This option allows independent control over tx enablement without
>affecting rx enablement. Like the rx_enabled option, this also
>implicitly affects the enabled option.
>
>If this option is not used, then the enabled option will continue to
>behave as it did before.
>
>Tested in a follow-up patch with a new selftest.
>
>Signed-off-by: Marc Harvey <marcharvey@google.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
^ permalink raw reply
* Re: [PATCH net-next v5 08/10] net: team: Add new rx_enabled team port option
From: Jiri Pirko @ 2026-04-07 11:57 UTC (permalink / raw)
To: Marc Harvey
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260406-teaming-driver-internal-v5-8-e8a3f348a1c5@google.com>
Mon, Apr 06, 2026 at 05:03:44AM +0200, marcharvey@google.com wrote:
>Allow independent control over rx enablement via the rx_enabled option
>without affecting tx enablement. This affects the normal enabled
>option since a port is only considered enabled if both tx and rx are
>enabled.
>
>If this option is not used, then the enabled option will continue to
>behave exactly as it did before.
>
>Tested in a follow-up patch with a new selftest.
>
>Signed-off-by: Marc Harvey <marcharvey@google.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
^ permalink raw reply
* linux-next: manual merge of the bpf-next tree with the powerpc tree
From: Mark Brown @ 2026-04-07 11:56 UTC (permalink / raw)
To: Daniel Borkmann, Alexei Starovoitov, Andrii Nakryiko, bpf,
Networking
Cc: Abhishek Dubey, Linux Kernel Mailing List,
Linux Next Mailing List, Madhavan Srinivasan
[-- Attachment #1: Type: text/plain, Size: 1365 bytes --]
Hi all,
Today's linux-next merge of the bpf-next tree got a conflict in:
tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
between commit:
66cad93ad325b ("selftest/bpf: Enable instruction array test for powerpc")
from the powerpc tree and commit:
99726ece0c8ad ("selftests/bpf: Enable test for instruction array on arm64")
from the bpf-next tree.
I fixed it up (see below) and can carry the fix as necessary. This
is now fixed as far as linux-next is concerned, but any non trivial
conflicts should be mentioned to your upstream maintainer when your tree
is submitted for merging. You may also want to consider cooperating
with the maintainer of the conflicting tree to minimise any particularly
complex conflicts.
diff --cc tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
index 482d38b9c29e1,93166c2da8e7b..0000000000000
--- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c
@@@ -3,7 -3,7 +3,7 @@@
#include <bpf/bpf.h>
#include <test_progs.h>
- #if defined(__x86_64__) || defined(__powerpc__)
-#if defined(__x86_64__) || defined(__aarch64__)
++#if defined(__x86_64__) || defined(__powerpc__) || defined(__aarch64__)
static int map_create(__u32 map_type, __u32 max_entries)
{
const char *map_name = "insn_array";
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH net-next v5 07/10] net: team: Track rx enablement separately from tx enablement
From: Jiri Pirko @ 2026-04-07 11:56 UTC (permalink / raw)
To: Marc Harvey
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260406-teaming-driver-internal-v5-7-e8a3f348a1c5@google.com>
Mon, Apr 06, 2026 at 05:03:43AM +0200, marcharvey@google.com wrote:
>Separate the rx and tx enablement/disablement into different
>functions so that it is easier to interact with them independently
>later.
>
>Although this patch changes receive and transmit paths, the actual
>behavior of the teaming driver should remain unchanged, since there
>is no option introduced yet to change rx or tx enablement
>independently. Those options will be added in follow-up patches.
>
>Signed-off-by: Marc Harvey <marcharvey@google.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
^ permalink raw reply
* Re: [PATCH net-next v5 06/10] net: team: Rename enablement functions and struct members to tx
From: Jiri Pirko @ 2026-04-07 11:56 UTC (permalink / raw)
To: Marc Harvey
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260406-teaming-driver-internal-v5-6-e8a3f348a1c5@google.com>
Mon, Apr 06, 2026 at 05:03:42AM +0200, marcharvey@google.com wrote:
>Add no functional changes, but rename enablement functions, variables
>etc. that are used in teaming driver transmit decisions.
>
>Since rx and tx enablement are still coupled, some of the variables
>renamed in this patch are still used for the rx path, but that will
>change in a follow-up patch.
>
>Signed-off-by: Marc Harvey <marcharvey@google.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
^ permalink raw reply
* Re: [PATCH net-next v5 02/10] net: team: Remove unused team_mode_op, port_enabled
From: Jiri Pirko @ 2026-04-07 11:56 UTC (permalink / raw)
To: Marc Harvey
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260406-teaming-driver-internal-v5-2-e8a3f348a1c5@google.com>
Mon, Apr 06, 2026 at 05:03:38AM +0200, marcharvey@google.com wrote:
>This team_mode_op wasn't used by any of the team modes, so remove it.
>
>Signed-off-by: Marc Harvey <marcharvey@google.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
^ permalink raw reply
* Re: [PATCH net-next v5 01/10] net: team: Annotate reads and writes for mixed lock accessed values
From: Jiri Pirko @ 2026-04-07 11:56 UTC (permalink / raw)
To: Marc Harvey
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260406-teaming-driver-internal-v5-1-e8a3f348a1c5@google.com>
Mon, Apr 06, 2026 at 05:03:37AM +0200, marcharvey@google.com wrote:
>The team_port's "index" and the team's "en_port_count" are read in
>the hot transmit path, but are only written to when holding the rtnl
>lock.
>
>Use READ_ONCE() for all lockless reads of these values, and use
>WRITE_ONCE() for all writes.
>
>Signed-off-by: Marc Harvey <marcharvey@google.com>
>---
>Changes in v5:
>- None
>
>Changes in v4:
>- None
>
>Changes in v3:
>- None
>
>Changes in v2:
>- None
Nit: I don't think you need to add such record when there is no change.
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
^ permalink raw reply
* Re: [PATCH net-next v5 00/10] Decouple receive and transmit enablement in team driver
From: Jiri Pirko @ 2026-04-07 11:55 UTC (permalink / raw)
To: Marc Harvey
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260406-teaming-driver-internal-v5-0-e8a3f348a1c5@google.com>
Mon, Apr 06, 2026 at 05:03:36AM +0200, marcharvey@google.com wrote:
>Allow independent control over receive and transmit enablement states
>for aggregated ports in the team driver.
>
>The motivation is that IEE 802.3ad LACP "independent control" can't
>be implemented for the team driver currently. This was added to the
>bonding driver in commit 240fd405528b ("bonding: Add independent
>control state machine").
>
>This series also has a few patches that add tests to show that the old
>coupled enablement still works and that the new decoupled enablement
>works as intended (4, 5, and 10).
>
>There are three patches with small fixes as well, with the goal of
>making the final decoupling patch clearer (1, 2, and 3).
Looks fine to me now. Do you have libteam/teamd counterpart?
^ permalink raw reply
* Re: [PATCH bpf-next v3 5/6] bpf: clear decap tunnel GSO state in skb_adjust_room
From: bot+bpf-ci @ 2026-04-07 11:52 UTC (permalink / raw)
To: nhudson, bpf, netdev, willemb, martin.lau
Cc: nhudson, mtottenh, aglasgal, ast, daniel, andrii, eddyz87, memxor,
davem, edumazet, kuba, pabeni, linux-kernel, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260407105005.1639815-6-nhudson@akamai.com>
[-- Attachment #1: Type: text/plain, Size: 2139 bytes --]
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 7f8d43420afb..04059d07d368 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -3667,6 +3667,46 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff,
> if (!(flags & BPF_F_ADJ_ROOM_FIXED_GSO))
> skb_increase_gso_size(shinfo, len_diff);
>
> + /* Selective GSO flag clearing based on decap type.
> + * Only clear the flags for the tunnel layer being removed.
> + */
> + if ((flags & BPF_F_ADJ_ROOM_DECAP_L4_UDP) &&
> + (shinfo->gso_type & (SKB_GSO_UDP_TUNNEL |
> + SKB_GSO_UDP_TUNNEL_CSUM |
> + SKB_GSO_TUNNEL_REMCSUM)))
> + shinfo->gso_type &= ~(SKB_GSO_UDP_TUNNEL |
> + SKB_GSO_UDP_TUNNEL_CSUM |
> + SKB_GSO_TUNNEL_REMCSUM);
[ ... ]
> +
> + /* Clear encapsulation flag only when no tunnel GSO flags remain */
> + if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
> + BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) {
[ ... ]
> +
> + if (flags & BPF_F_ADJ_ROOM_DECAP_L4_UDP) {
> + skb->encap_hdr_csum = !!(shinfo->gso_type & SKB_GSO_UDP_TUNNEL_CSUM);
> + skb->remcsum_offload = !!(shinfo->gso_type & SKB_GSO_TUNNEL_REMCSUM);
The L4_UDP clearing block above unconditionally clears
SKB_GSO_UDP_TUNNEL_CSUM and SKB_GSO_TUNNEL_REMCSUM from
shinfo->gso_type. By the time these two assignments execute,
both flags are always zero, so these expressions always
evaluate to 0.
The kernel's own __skb_udp_tunnel_segment() in udp_offload.c
uses the same !!() pattern:
need_csum = !!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM);
skb->encap_hdr_csum = need_csum;
remcsum = !!(skb_shinfo(skb)->gso_type & SKB_GSO_TUNNEL_REMCSUM);
skb->remcsum_offload = remcsum;
but there the flags are still live. Here they have already been
cleared, so the conditional form is misleading. Would it be
clearer to just assign 0 directly?
> + }
> + }
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/24079111128
^ permalink raw reply
* Re: [PATCH net-next V2 0/5] net/mlx5e: XDP, Add support for multi-packet per page
From: patchwork-bot+netdevbpf @ 2026-04-07 11:50 UTC (permalink / raw)
To: Tariq Toukan
Cc: edumazet, kuba, pabeni, andrew+netdev, davem, saeedm, leon,
mbloch, ast, daniel, hawk, john.fastabend, sdf, dtatulea, cratiu,
horms, jacob.e.keller, lkayal, michal.swiatkowski, cjubran,
nathan, daniel.zahka, rrameshbabu, raeds, netdev, linux-rdma,
linux-kernel, bpf, gal
In-Reply-To: <20260403090927.139042-1-tariqt@nvidia.com>
Hello:
This series was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Fri, 3 Apr 2026 12:09:22 +0300 you wrote:
> Hi,
>
> This series removes the limitation of having one packet per page in XDP
> mode. This has the following implications:
>
> - XDP in Striding RQ mode can now be used on 64K page systems.
>
> [...]
Here is the summary with links:
- [net-next,V2,1/5] net/mlx5e: XSK, Increase size for chunk_size param
https://git.kernel.org/netdev/net-next/c/1047e14b44ed
- [net-next,V2,2/5] net/mlx5e: XDP, Improve dma address calculation of linear part for XDP_TX
https://git.kernel.org/netdev/net-next/c/833e72645aac
- [net-next,V2,3/5] net/mlx5e: XDP, Remove stride size limitation
https://git.kernel.org/netdev/net-next/c/2dfaa0238774
- [net-next,V2,4/5] net/mlx5e: XDP, Use a single linear page per rq
https://git.kernel.org/netdev/net-next/c/ebd4ad29cc82
- [net-next,V2,5/5] net/mlx5e: XDP, Use page fragments for linear data in multibuf-mode
https://git.kernel.org/netdev/net-next/c/25b8c9b6d731
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [RFC net-next 15/15] Documentation: networking: add ipxlat translator guide
From: Daniel Gröber @ 2026-04-07 11:30 UTC (permalink / raw)
To: Xavier Hsinyuan
Cc: ralf, antonio, corbet, davem, edumazet, horms, kuba, linux-doc,
linux-kernel, netdev, pabeni, skhan
In-Reply-To: <TYRPR01MB12666EEA0B8007166ED446088CA5DA@TYRPR01MB12666.jpnprd01.prod.outlook.com>
On Mon, Apr 06, 2026 at 10:50:44PM +0800, Xavier Hsinyuan wrote:
> >+ $ ./tools/net/ynl/pyynl/cli.py --family ipxlat --json '{"ifindex": $IID, \
> >+ "config": {"xlat-prefix6": "'$HEX_ADDR'", "prefix-len": 96} }'
> Should this be like:
> $ python3 /extends/pyynl/cli.py --spec ipxlat.yaml --do dev-set --json \
> '{"ifindex": "'$IID'", "config": {"xlat-prefix6": \
> {"prefix":"'$ADDR_HEX'", "prefix-len": 96}}}'
Indeed, the JSON is just wrong and --do dev-set is missing. However
`--family ipxlat` works for me and looking at the code is basically the
same as specifying --spec.
Could you try this:
$ JSON='{"ifindex": '"$IID"', "config": {"xlat-prefix6": { "prefix": "'$ADDR_HEX'", "prefix-len": 96}}}'
$ ./tools/net/ynl/pyynl/cli.py --family ipxlat --do dev-set --json "$JSON"
> >+Address Translation
> >+-------------------
> >+
> >+The ipxlat address translation algorithm is stateless, per RFC-ADDR_, all
> >+possible IPv4 addressess are mapped one-to-one into the translation prefix,
> >+optionally including a non-standard "suffix". See `RFC-ADDR Section 2.2
> >+<https://datatracker.ietf.org/doc/html/rfc6052#section-2.2>`_.
> >+
> >+.. _RFC-ADDR: https://datatracker.ietf.org/doc/html/rfc6052
> >+
> >+IPv6 addressess outside this prefix are rejected with ICMPv6 errors with
> >+the notable exception of ICMPv6 errors originating from untranslatable
> >+source addressess. These are translated to be sourced from the IPv4 Dummy
> >+Address ``192.0.0.8`` (per I-D-dummy_) instead to maintain IPv4 traceroute
> >+visibility.
>
> Would it help to add a few example? For instance,
> - Interface ipxlat0 with prefix6=64:ff9b::/96.
> - A IPv6 packet with src=64:ff9b::192.0.2.1 dst=64:ff9b::198.51.100.1,
> was send to ipxlat0.
> - Then a IPv4 packet with src=192.0.2.1 dst=198.51.100.1 was received from
> ipxlat0.
I worry once we start with that we're really just re-stating what's already
extensively documented in the RFCs.
How about a reference to RFC 7915 Appendix A? This has a full bidirectional
end-to-end example of how translation operates:
https://datatracker.ietf.org/doc/html/rfc7915#appendix-A
Admittedly using a /96 prefix (which the appendix doesn't) would make it
easier to grok whats going on. Not sure that's reason enough to get into
more detailed examples here.
WDYT?
Thanks,
--Daniel
^ permalink raw reply
* Re: stmmac: TX stalls with head OWN=1; requiring manual DMA kick (6.12.63)
From: Russell King (Oracle) @ 2026-04-07 11:45 UTC (permalink / raw)
To: Tomer Maimon; +Cc: Andrew Lunn, Giuseppe Cavallaro, netdev
In-Reply-To: <CAP6Zq1hKd19aLjWBnx3pkfeGyqrudqSHd0PTxUJhn7r9BG0oSA@mail.gmail.com>
On Thu, Mar 12, 2026 at 04:02:45PM +0200, Tomer Maimon wrote:
> Hi Andrew and Russell,
>
> On Wed, 11 Mar 2026 at 22:52, Russell King (Oracle)
> <linux@armlinux.org.uk> wrote:
> >
> > On Wed, Mar 11, 2026 at 08:46:04PM +0100, Andrew Lunn wrote:
> > > On Wed, Mar 11, 2026 at 08:34:36PM +0200, Tomer Maimon wrote:
> > > > Hi Giuseppe,
> > > >
> > > > On kernel 6.12.63 (GMAC, normal 16‑byte TX descriptors), we observe
> > > > brief TX stalls when sending small packets.
> > >
> > > You might want to look at Russell Kings patches from this week to
> > > rework the descriptor handling.
>
> Sorry, I mean 6.12 LTS.
> >
> > Yep.
> >
> > Not sure they'll materially affect any problem, because I try to avoid
> > functional changes in stuff like those.
> >
> > What's missing from the original report is a description of the
> > hardware. stmmac drives multiple different versions of the Synopsys
> > IP which have significant changes between them. Which platform glue
> > is being used, which core driver is being used (dwmac1000? dwmac4?
> > etc.)
> stmmac IP version is 3.73a
> The platform is glue is "snps,dwmac" and the core driver is "dwmac1000"
Thanks.
> > Also what isn't mentioned is whether this is a regression, or whether
> > it's something that has never worked. If it's a regression, which
> > kernel version previously worked?
> We ran the test with kernel 5.10 LTS, and it didn't work.
So it isn't a regression, but a persistent issue.
You mentioned in your original email:
> If the head descriptor still has OWN=1, issue a Transmit Poll Demand
> via stmmac_enable_dma_transmission().
> This immediately resumes the DMA and clears the stall.
This will be dwmac_lib.c::dwmac_enable_dma_transmission() which writes
to the transmit poll demand register. This write is prefixed on arm64
by a "dmb oshst" instruction which should ensure that previous writes
to the descriptors are visible to stmmac.
dwmac1000 doesn't have TSO and in any case the packet is too small for
TSO, so we can ignore stmmac_tso_xmit(). I'm also guessing your not
using xdp either, so we can rule out stmmac_xdp_xmit_zc() and
stmmac_xdp_xmit_xdpf().
So, we're looking at the normal path through stmmac_xmit().
The code at this point is:
/* Set the OWN bit on the first descriptor now that all descriptors
* for this skb are populated.
*/
stmmac_set_tx_owner(priv, first_desc);
netdev_tx_sent_queue(netdev_get_tx_queue(dev, queue), skb->len);
stmmac_enable_dma_transmission(priv, priv->ioaddr, queue);
stmmac_set_tx_owner() will be calling norm_desc.c::ndesc_set_tx_owner()
which quite simply sets bit 31 in the descriptor via a
read-modify-write:
0000000000000000 <ndesc_set_tx_owner>:
0: b9400001 ldr w1, [x0]
4: 32010021 orr w1, w1, #0x80000000
8: b9000001 str w1, [x0]
c: d65f03c0 ret
and as I say, dwmac_enable_dma_transmission() includes the necessary
barrier to ensure that _that_ write should be visible to stmmac:
0000000000000120 <dwmac_enable_dma_transmission>:
120: 52820082 mov w2, #0x1004 // #4100
124: 0b012041 add w1, w2, w1, lsl #8
128: 8b214001 add x1, x0, w1, uxtw
12c: d50332bf dmb oshst
130: 52800020 mov w0, #0x1 // #1
134: b9000020 str w0, [x1]
138: d65f03c0 ret
However, I'm wondering if we have an issue with the descriptor writes
becoming visible in the wrong order. Please can you try adding a
dma_wmb() immediately before stmmac_set_tx_owner() in stmmac_xmit()
to ensure that all previous descriptor settings are fully visible
before we update the OWN bit on the first descriptor and make that
update visible?
Thanks.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply
* Re: [PATCH net v2] ipvs: fix MTU check for GSO packets in tunnel mode
From: kernel test robot @ 2026-04-07 11:43 UTC (permalink / raw)
To: Yingnan Zhang, horms, ja
Cc: oe-kbuild-all, pablo, fw, phil, davem, edumazet, kuba, pabeni,
netdev, lvs-devel, netfilter-devel, coreteam, linux-kernel,
Yingnan Zhang
In-Reply-To: <tencent_CA2C1C219C99D315086BE55E8654AF7E6009@qq.com>
Hi Yingnan,
kernel test robot noticed the following build errors:
[auto build test ERROR on net/main]
url: https://github.com/intel-lab-lkp/linux/commits/Yingnan-Zhang/ipvs-fix-MTU-check-for-GSO-packets-in-tunnel-mode/20260407-141549
base: net/main
patch link: https://lore.kernel.org/r/tencent_CA2C1C219C99D315086BE55E8654AF7E6009%40qq.com
patch subject: [PATCH net v2] ipvs: fix MTU check for GSO packets in tunnel mode
config: x86_64-rhel-9.4-ltp (https://download.01.org/0day-ci/archive/20260407/202604071309.RskiawHA-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260407/202604071309.RskiawHA-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604071309.RskiawHA-lkp@intel.com/
All error/warnings (new ones prefixed by >>):
>> net/netfilter/ipvs/ip_vs_xmit.c:115:11: error: expected identifier or '(' before 'else'
115 | } else if (skb->len > mtu &&
| ^~~~
>> net/netfilter/ipvs/ip_vs_xmit.c:119:9: error: expected identifier or '(' before 'return'
119 | return false;
| ^~~~~~
>> net/netfilter/ipvs/ip_vs_xmit.c:120:1: error: expected identifier or '(' before '}' token
120 | }
| ^
net/netfilter/ipvs/ip_vs_xmit.c: In function '__mtu_check_toobig_v6':
>> net/netfilter/ipvs/ip_vs_xmit.c:115:9: warning: control reaches end of non-void function [-Wreturn-type]
115 | } else if (skb->len > mtu &&
| ^
vim +115 net/netfilter/ipvs/ip_vs_xmit.c
104
105 static inline bool
106 __mtu_check_toobig_v6(const struct sk_buff *skb, u32 mtu)
107 {
108 if (IP6CB(skb)->frag_max_size) {
109 /* frag_max_size tell us that, this packet have been
110 * defragmented by netfilter IPv6 conntrack module.
111 */
112 if (IP6CB(skb)->frag_max_size > mtu)
113 return true; /* largest fragment violate MTU */
114 }
> 115 } else if (skb->len > mtu &&
116 !(skb_is_gso(skb) && skb_gso_validate_network_len(skb, mtu))) {
117 return true; /* Packet size violate MTU size */
118 }
> 119 return false;
> 120 }
121
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH v4 5/5] arm64: dts: ti: k3-j722s-main: use J722S compatibles for WIZ, gmii-sel and CPSW3G
From: Nora Schiffer @ 2026-04-07 11:42 UTC (permalink / raw)
To: Nishanth Menon, Vignesh Raghavendra, Tero Kristo, Vinod Koul,
Neil Armstrong
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Siddharth Vadapalli, Roger Quadros, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, devicetree,
linux-kernel, linux-phy, linux-arm-kernel, linux, Nora Schiffer
In-Reply-To: <cover.1775559102.git.nora.schiffer@ew.tq-group.com>
Update WIZ, gmii-sel and CPSW3G to use the J722S-specific compatible
strings, enabling SGMII support. The fallback compatibles preserve
compatibility of the updated Device Trees with older kernels.
Signed-off-by: Nora Schiffer <nora.schiffer@ew.tq-group.com>
---
arch/arm64/boot/dts/ti/k3-j722s-main.dtsi | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/ti/k3-j722s-main.dtsi b/arch/arm64/boot/dts/ti/k3-j722s-main.dtsi
index 9ee5d0c8ffd1e..70f430aa3a944 100644
--- a/arch/arm64/boot/dts/ti/k3-j722s-main.dtsi
+++ b/arch/arm64/boot/dts/ti/k3-j722s-main.dtsi
@@ -18,7 +18,7 @@ serdes_refclk: clk-0 {
&cbass_main {
serdes_wiz0: phy@f000000 {
- compatible = "ti,am64-wiz-10g";
+ compatible = "ti,j722s-wiz-10g", "ti,am64-wiz-10g";
ranges = <0x0f000000 0x0 0x0f000000 0x00010000>;
#address-cells = <1>;
#size-cells = <1>;
@@ -56,7 +56,7 @@ serdes0: serdes@f000000 {
};
serdes_wiz1: phy@f010000 {
- compatible = "ti,am64-wiz-10g";
+ compatible = "ti,j722s-wiz-10g", "ti,am64-wiz-10g";
ranges = <0x0f010000 0x0 0x0f010000 0x00010000>;
#address-cells = <1>;
#size-cells = <1>;
@@ -451,6 +451,14 @@ pcie0_ctrl: pcie0-ctrl@4070 {
};
};
+&cpsw3g {
+ compatible = "ti,j722s-cpsw-nuss", "ti,am642-cpsw-nuss";
+};
+
+&phy_gmii_sel {
+ compatible = "ti,j722s-phy-gmii-sel", "ti,am654-phy-gmii-sel";
+};
+
&oc_sram {
reg = <0x00 0x70000000 0x00 0x40000>;
ranges = <0x00 0x00 0x70000000 0x40000>;
--
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox