// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <net/netdev_queues.h>
#include "eea_adminq.h"
#include "eea_net.h"
#include "eea_pci.h"
#include "eea_ring.h"
#define EEA_SPLIT_HDR_SIZE ALIGN(128, L1_CACHE_BYTES)
static irqreturn_t eea_irq_handler(int irq, void *data)
{
struct eea_irq_blk *blk = data;
napi_schedule_irqoff(&blk->napi);
return IRQ_HANDLED;
}
static void eea_free_irq_blk(struct eea_net *enet)
{
struct eea_irq_blk *blk;
u32 num;
int i;
if (!enet->irq_blks)
return;
num = enet->edev->rx_num;
for (i = 0; i < num; i++) {
blk = &enet->irq_blks[i];
if (blk->ready)
eea_pci_free_irq(blk);
blk->ready = false;
}
kvfree(enet->irq_blks);
enet->irq_blks = NULL;
}
/* The driver will always attempt to allocate IRQ blocks based on the maximum
* possible queue num.
*/
static int eea_alloc_irq_blks(struct eea_net *enet)
{
struct eea_device *edev = enet->edev;
struct eea_irq_blk *blk, *irq_blks;
int i, err, num;
num = enet->edev->rx_num;
irq_blks = kvzalloc_objs(*blk, num);
if (!irq_blks)
return -ENOMEM;
enet->irq_blks = irq_blks;
for (i = 0; i < num; i++) {
blk = &irq_blks[i];
blk->idx = i;
/* vec 0 is for error notify. */
blk->msix_vec = i + 1;
err = eea_pci_request_irq(edev, blk, eea_irq_handler);
if (err)
goto err_free_irq_blk;
blk->ready = true;
}
return 0;
err_free_irq_blk:
eea_free_irq_blk(enet);
return err;
}
static int eea_update_queues(struct eea_net *enet)
{
return netif_set_real_num_queues(enet->netdev, enet->cfg.tx_ring_num,
enet->cfg.rx_ring_num);
}
void eea_init_ctx(struct eea_net *enet, struct eea_net_init_ctx *ctx)
{
memset(ctx, 0, sizeof(*ctx));
ctx->netdev = enet->netdev;
ctx->edev = enet->edev;
ctx->cfg = enet->cfg;
}
static void eea_bind_q_and_cfg(struct eea_net *enet,
struct eea_net_init_ctx *ctx)
{
struct eea_irq_blk *blk;
struct eea_net_rx *rx;
struct eea_net_tx *tx;
int i;
/* Since 'ndo_get_stats64' is not called in softirq context, there is no
* need to use 'spin_lock_bh'.
*/
spin_lock(&enet->stats_lock);
enet->cfg = ctx->cfg;
enet->rx = ctx->rx;
enet->tx = ctx->tx;
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
blk = &enet->irq_blks[i];
rx = ctx->rx[i];
tx = &ctx->tx[i];
rx->enet = enet;
rx->napi = &blk->napi;
rx->ering->msix_vec = blk->msix_vec;
tx->enet = enet;
tx->ering->msix_vec = blk->msix_vec;
blk->rx = rx;
}
spin_unlock(&enet->stats_lock);
}
static void eea_unbind_q_and_cfg(struct eea_net *enet,
struct eea_net_init_ctx *ctx)
{
struct eea_irq_blk *blk;
struct eea_net_rx *rx;
int i;
spin_lock(&enet->stats_lock);
ctx->cfg = enet->cfg;
ctx->rx = enet->rx;
ctx->tx = enet->tx;
enet->rx = NULL;
enet->tx = NULL;
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
blk = &enet->irq_blks[i];
rx = ctx->rx[i];
rx->napi = NULL;
blk->rx = NULL;
}
spin_unlock(&enet->stats_lock);
}
static void eea_free_rxtx_q_mem(struct eea_net_init_ctx *ctx)
{
struct eea_net_rx *rx;
struct eea_net_tx *tx;<