888 lines
18 KiB
C
888 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Driver for Alibaba Elastic Ethernet Adapter.
|
|
*
|
|
* Copyright (C) 2025 Alibaba Inc.
|
|
*/
|
|
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <net/netdev_queues.h>
|
|
|
|
#include "eea_adminq.h"
|
|
#include "eea_net.h"
|
|
#include "eea_pci.h"
|
|
#include "eea_ring.h"
|
|
|
|
#define EEA_SPLIT_HDR_SIZE ALIGN(128, L1_CACHE_BYTES)
|
|
|
|
static irqreturn_t eea_irq_handler(int irq, void *data)
|
|
{
|
|
struct eea_irq_blk *blk = data;
|
|
|
|
napi_schedule_irqoff(&blk->napi);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void eea_free_irq_blk(struct eea_net *enet)
|
|
{
|
|
struct eea_irq_blk *blk;
|
|
u32 num;
|
|
int i;
|
|
|
|
if (!enet->irq_blks)
|
|
return;
|
|
|
|
num = enet->edev->rx_num;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
blk = &enet->irq_blks[i];
|
|
|
|
if (blk->ready)
|
|
eea_pci_free_irq(blk);
|
|
|
|
blk->ready = false;
|
|
}
|
|
|
|
kvfree(enet->irq_blks);
|
|
enet->irq_blks = NULL;
|
|
}
|
|
|
|
/* The driver will always attempt to allocate IRQ blocks based on the maximum
|
|
* possible queue num.
|
|
*/
|
|
static int eea_alloc_irq_blks(struct eea_net *enet)
|
|
{
|
|
struct eea_device *edev = enet->edev;
|
|
struct eea_irq_blk *blk, *irq_blks;
|
|
int i, err, num;
|
|
|
|
num = enet->edev->rx_num;
|
|
|
|
irq_blks = kvcalloc(num, sizeof(*blk), GFP_KERNEL);
|
|
if (!irq_blks)
|
|
return -ENOMEM;
|
|
|
|
enet->irq_blks = irq_blks;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
blk = &irq_blks[i];
|
|
blk->idx = i;
|
|
|
|
/* vec 0 is for error notify. */
|
|
blk->msix_vec = i + 1;
|
|
|
|
err = eea_pci_request_irq(edev, blk, eea_irq_handler);
|
|
if (err)
|
|
goto err_free_irq_blk;
|
|
|
|
blk->ready = true;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_free_irq_blk:
|
|
eea_free_irq_blk(enet);
|
|
return err;
|
|
}
|
|
|
|
static int eea_update_queues(struct eea_net *enet)
|
|
{
|
|
return netif_set_real_num_queues(enet->netdev, enet->cfg.tx_ring_num,
|
|
enet->cfg.rx_ring_num);
|
|
}
|
|
|
|
void eea_init_ctx(struct eea_net *enet, struct eea_net_init_ctx *ctx)
|
|
{
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
|
ctx->netdev = enet->netdev;
|
|
ctx->edev = enet->edev;
|
|
ctx->cfg = enet->cfg;
|
|
}
|
|
|
|
static void eea_bind_q_and_cfg(struct eea_net *enet,
|
|
struct eea_net_init_ctx *ctx)
|
|
{
|
|
struct eea_irq_blk *blk;
|
|
struct eea_net_rx *rx;
|
|
struct eea_net_tx *tx;
|
|
int i;
|
|
|
|
/* Since 'ndo_get_stats64' is not called in softirq context, there is no
|
|
* need to use 'spin_lock_bh'.
|
|
*/
|
|
spin_lock(&enet->stats_lock);
|
|
|
|
enet->cfg = ctx->cfg;
|
|
enet->rx = ctx->rx;
|
|
enet->tx = ctx->tx;
|
|
|
|
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
|
|
blk = &enet->irq_blks[i];
|
|
|
|
rx = ctx->rx[i];
|
|
tx = &ctx->tx[i];
|
|
|
|
rx->enet = enet;
|
|
rx->napi = &blk->napi;
|
|
rx->ering->msix_vec = blk->msix_vec;
|
|
|
|
tx->enet = enet;
|
|
tx->ering->msix_vec = blk->msix_vec;
|
|
|
|
blk->rx = rx;
|
|
}
|
|
|
|
spin_unlock(&enet->stats_lock);
|
|
}
|
|
|
|
static void eea_unbind_q_and_cfg(struct eea_net *enet,
|
|
struct eea_net_init_ctx *ctx)
|
|
{
|
|
struct eea_irq_blk *blk;
|
|
struct eea_net_rx *rx;
|
|
int i;
|
|
|
|
spin_lock(&enet->stats_lock);
|
|
|
|
ctx->cfg = enet->cfg;
|
|
ctx->rx = enet->rx;
|
|
ctx->tx = enet->tx;
|
|
|
|
enet->rx = NULL;
|
|
enet->tx = NULL;
|
|
|
|
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
|
|
blk = &enet->irq_blks[i];
|
|
|
|
rx = ctx->rx[i];
|
|
|
|
rx->napi = NULL;
|
|
|
|
blk->rx = NULL;
|
|
}
|
|
|
|
spin_unlock(&enet->stats_lock);
|
|
}
|
|
|
|
static void eea_free_rxtx_q_mem(struct eea_net_init_ctx *ctx)
|
|
{
|
|
struct eea_net_rx *rx;
|
|
struct eea_net_tx *tx;
|
|
int i;
|
|
|
|
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
|
|
rx = ctx->rx[i];
|
|
tx = &ctx->tx[i];
|
|
|
|
eea_free_rx(rx, &ctx->cfg);
|
|
eea_free_tx(tx, &ctx->cfg);
|
|
}
|
|
|
|
kvfree(ctx->rx);
|
|
kvfree(ctx->tx);
|
|
}
|
|
|
|
/* alloc tx/rx: struct, ring, meta, pp, napi */
|
|
static int eea_alloc_rxtx_q_mem(struct eea_net_init_ctx *ctx)
|
|
{
|
|
struct eea_net_rx *rx;
|
|
struct eea_net_tx *tx;
|
|
int err, i;
|
|
|
|
ctx->tx = kvcalloc(ctx->cfg.tx_ring_num, sizeof(*ctx->tx), GFP_KERNEL);
|
|
if (!ctx->tx)
|
|
return -ENOMEM;
|
|
|
|
ctx->rx = kvcalloc(ctx->cfg.rx_ring_num, sizeof(*ctx->rx), GFP_KERNEL);
|
|
if (!ctx->rx)
|
|
goto err_free_tx;
|
|
|
|
ctx->cfg.rx_sq_desc_size = sizeof(struct eea_rx_desc);
|
|
ctx->cfg.rx_cq_desc_size = sizeof(struct eea_rx_cdesc);
|
|
ctx->cfg.tx_sq_desc_size = sizeof(struct eea_tx_desc);
|
|
ctx->cfg.tx_cq_desc_size = sizeof(struct eea_tx_cdesc);
|
|
|
|
/* ethtool may config this. */
|
|
if (!ctx->cfg.split_hdr)
|
|
ctx->cfg.rx_sq_desc_size = sizeof(struct eea_rx_desc_no_hdr);
|
|
|
|
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
|
|
rx = eea_alloc_rx(ctx, i);
|
|
if (!rx)
|
|
goto err_free;
|
|
|
|
ctx->rx[i] = rx;
|
|
|
|
tx = ctx->tx + i;
|
|
err = eea_alloc_tx(ctx, tx, i);
|
|
if (err)
|
|
goto err_free;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_free:
|
|
for (i = 0; i < ctx->cfg.rx_ring_num; i++) {
|
|
rx = ctx->rx[i];
|
|
tx = ctx->tx + i;
|
|
|
|
eea_free_rx(rx, &ctx->cfg);
|
|
eea_free_tx(tx, &ctx->cfg);
|
|
}
|
|
|
|
kvfree(ctx->rx);
|
|
|
|
err_free_tx:
|
|
kvfree(ctx->tx);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int eea_hw_active_ring(struct eea_net *enet)
|
|
{
|
|
return eea_adminq_create_q(enet, enet->cfg.rx_ring_num
|
|
+ enet->cfg.tx_ring_num, 0);
|
|
}
|
|
|
|
static int eea_hw_unactive_ring(struct eea_net *enet)
|
|
{
|
|
int err;
|
|
|
|
err = eea_adminq_destroy_all_q(enet);
|
|
if (err)
|
|
netdev_warn(enet->netdev, "unactive rxtx ring failed.\n");
|
|
|
|
return err;
|
|
}
|
|
|
|
/* stop rx napi, stop tx queue. */
|
|
static void eea_stop_rxtx(struct net_device *netdev)
|
|
{
|
|
struct eea_net *enet = netdev_priv(netdev);
|
|
int i;
|
|
|
|
netif_tx_disable(netdev);
|
|
|
|
for (i = 0; i < enet->cfg.rx_ring_num; i++)
|
|
enet_rx_stop(enet->rx[i]);
|
|
|
|
netif_carrier_off(netdev);
|
|
}
|
|
|
|
static void eea_start_rxtx(struct eea_net *enet)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < enet->cfg.rx_ring_num; i++)
|
|
enet_rx_start(enet->rx[i]);
|
|
|
|
netif_tx_start_all_queues(enet->netdev);
|
|
netif_carrier_on(enet->netdev);
|
|
|
|
enet->started = true;
|
|
}
|
|
|
|
static int eea_netdev_stop(struct net_device *netdev)
|
|
{
|
|
struct eea_net *enet = netdev_priv(netdev);
|
|
struct eea_net_init_ctx ctx;
|
|
|
|
/* This function can be called during device anomaly recovery. To
|
|
* prevent duplicate stop operations, the `started` flag is introduced
|
|
* for checking.
|
|
*/
|
|
|
|
if (!enet->started) {
|
|
netdev_warn(netdev, "eea netdev stop: but dev is not started.\n");
|
|
return 0;
|
|
}
|
|
|
|
eea_init_ctx(enet, &ctx);
|
|
|
|
eea_stop_rxtx(netdev);
|
|
eea_hw_unactive_ring(enet);
|
|
eea_unbind_q_and_cfg(enet, &ctx);
|
|
eea_free_rxtx_q_mem(&ctx);
|
|
|
|
enet->started = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int eea_netdev_open(struct net_device *netdev)
|
|
{
|
|
struct eea_net *enet = netdev_priv(netdev);
|
|
struct eea_net_init_ctx ctx;
|
|
int err;
|
|
|
|
if (enet->link_err) {
|
|
netdev_err(netdev, "netdev open err, because link error: %d\n",
|
|
enet->link_err);
|
|
return -EBUSY;
|
|
}
|
|
|
|
eea_init_ctx(enet, &ctx);
|
|
|
|
err = eea_alloc_rxtx_q_mem(&ctx);
|
|
if (err)
|
|
goto err_done;
|
|
|
|
eea_bind_q_and_cfg(enet, &ctx);
|
|
|
|
err = eea_update_queues(enet);
|
|
if (err)
|
|
goto err_free_q;
|
|
|
|
err = eea_hw_active_ring(enet);
|
|
if (err)
|
|
goto err_free_q;
|
|
|
|
eea_start_rxtx(enet);
|
|
|
|
return 0;
|
|
|
|
err_free_q:
|
|
eea_unbind_q_and_cfg(enet, &ctx);
|
|
eea_free_rxtx_q_mem(&ctx);
|
|
|
|
err_done:
|
|
return err;
|
|
}
|
|
|
|
/* Statistics may be reset to zero upon device reset. This is expected behavior
|
|
* for now and will be addressed in the future.
|
|
*/
|
|
static void eea_stats(struct net_device *netdev, struct rtnl_link_stats64 *tot)
|
|
{
|
|
struct eea_net *enet = netdev_priv(netdev);
|
|
u64 packets, bytes, drop, lerr;
|
|
u32 start;
|
|
int i;
|
|
|
|
spin_lock(&enet->stats_lock);
|
|
|
|
if (enet->rx) {
|
|
for (i = 0; i < enet->cfg.rx_ring_num; i++) {
|
|
struct eea_net_rx *rx = enet->rx[i];
|
|
|
|
do {
|
|
start = u64_stats_fetch_begin(&rx->stats.syncp);
|
|
packets = u64_stats_read(&rx->stats.packets);
|
|
bytes = u64_stats_read(&rx->stats.bytes);
|
|
drop = u64_stats_read(&rx->stats.drops);
|
|
lerr = u64_stats_read(&rx->stats.length_errors);
|
|
} while (u64_stats_fetch_retry(&rx->stats.syncp,
|
|
start));
|
|
|
|
tot->rx_packets += packets;
|
|
tot->rx_bytes += bytes;
|
|
tot->rx_dropped += drop;
|
|
tot->rx_length_errors += lerr;
|
|
tot->rx_errors += lerr;
|
|
}
|
|
}
|
|
|
|
if (enet->tx) {
|
|
for (i = 0; i < enet->cfg.tx_ring_num; i++) {
|
|
struct eea_net_tx *tx = &enet->tx[i];
|
|
|
|
do {
|
|
start = u64_stats_fetch_begin(&tx->stats.syncp);
|
|
packets = u64_stats_read(&tx->stats.packets);
|
|
bytes = u64_stats_read(&tx->stats.bytes);
|
|
drop = u64_stats_read(&tx->stats.drops);
|
|
} while (u64_stats_fetch_retry(&tx->stats.syncp,
|
|
start));
|
|
|
|
tot->tx_packets += packets;
|
|
tot->tx_bytes += bytes;
|
|
tot->tx_dropped += drop;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&enet->stats_lock);
|
|
}
|
|
|
|
/* resources: ring, buffers, irq */
|
|
int eea_reset_hw_resources(struct eea_net *enet, struct eea_net_init_ctx *ctx)
|
|
{
|
|
struct eea_net_init_ctx ctx_old = {0};
|
|
int err, error;
|
|
|
|
if (!netif_running(enet->netdev) || !enet->started) {
|
|
spin_lock(&enet->stats_lock);
|
|
enet->cfg = ctx->cfg;
|
|
spin_unlock(&enet->stats_lock);
|
|
return 0;
|
|
}
|
|
|
|
err = eea_alloc_rxtx_q_mem(ctx);
|
|
if (err) {
|
|
netdev_warn(enet->netdev,
|
|
"eea reset: alloc q failed. stop reset. err %d\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
eea_stop_rxtx(enet->netdev);
|
|
eea_hw_unactive_ring(enet);
|
|
|
|
eea_unbind_q_and_cfg(enet, &ctx_old);
|
|
eea_bind_q_and_cfg(enet, ctx);
|
|
|
|
err = eea_update_queues(enet);
|
|
if (err) {
|
|
netdev_err(enet->netdev,
|
|
"eea reset: set real num queues failed. err %d\n",
|
|
err);
|
|
goto err_bind_old;
|
|
}
|
|
|
|
err = eea_hw_active_ring(enet);
|
|
if (err) {
|
|
netdev_err(enet->netdev, "eea reset: active new ring. err %d\n",
|
|
err);
|
|
eea_unbind_q_and_cfg(enet, ctx);
|
|
goto err_free_q;
|
|
}
|
|
|
|
eea_start_rxtx(enet);
|
|
eea_free_rxtx_q_mem(&ctx_old);
|
|
return 0;
|
|
|
|
err_bind_old:
|
|
eea_unbind_q_and_cfg(enet, ctx);
|
|
eea_bind_q_and_cfg(enet, &ctx_old);
|
|
error = eea_hw_active_ring(enet);
|
|
if (error) {
|
|
netdev_err(enet->netdev, "eea reset: active old ring. err %d\n",
|
|
error);
|
|
eea_unbind_q_and_cfg(enet, &ctx_old);
|
|
err = error;
|
|
goto err_free_q;
|
|
}
|
|
|
|
eea_start_rxtx(enet);
|
|
eea_free_rxtx_q_mem(ctx);
|
|
return err;
|
|
|
|
err_free_q:
|
|
|
|
/* An exception occurred at the hardware level, and there's not much we
|
|
* can do about it -- we can only release the resources first.
|
|
*/
|
|
eea_free_rxtx_q_mem(ctx);
|
|
eea_free_rxtx_q_mem(&ctx_old);
|
|
enet->started = false;
|
|
return err;
|
|
}
|
|
|
|
int eea_queues_check_and_reset(struct eea_device *edev)
|
|
{
|
|
struct eea_aq_dev_status dstatus = {0};
|
|
struct eea_aq_queue_status *qstatus;
|
|
struct eea_aq_queue_status *qs;
|
|
struct eea_net_init_ctx ctx;
|
|
bool need_reset = false;
|
|
int i, err = 0;
|
|
|
|
rtnl_lock();
|
|
|
|
if (!netif_running(edev->enet->netdev))
|
|
goto err_unlock;
|
|
|
|
/* Maybe stopped by ha. */
|
|
if (!edev->enet->started || edev->enet->link_err)
|
|
goto err_unlock;
|
|
|
|
err = eea_adminq_dev_status(edev->enet, &dstatus);
|
|
if (err) {
|
|
netdev_warn(edev->enet->netdev, "query queue status failed.\n");
|
|
goto err_unlock;
|
|
}
|
|
|
|
if (le16_to_cpu(dstatus.status->link_status) == EEA_LINK_DOWN_STATUS) {
|
|
/* The device is broken, can not be up. */
|
|
eea_netdev_stop(edev->enet->netdev);
|
|
edev->enet->link_err = EEA_LINK_ERR_LINK_DOWN;
|
|
netdev_warn(edev->enet->netdev, "device link is down. stop device.\n");
|
|
goto err_free;
|
|
}
|
|
|
|
qstatus = dstatus.status->q_status;
|
|
|
|
for (i = 0; i < dstatus.num; ++i) {
|
|
qs = &qstatus[i];
|
|
|
|
if (le16_to_cpu(qs->status) == EEA_QUEUE_STATUS_NEED_RESET) {
|
|
netdev_warn(edev->enet->netdev,
|
|
"queue status: queue %u needs to reset\n",
|
|
le16_to_cpu(qs->qidx));
|
|
need_reset = true;
|
|
}
|
|
}
|
|
|
|
if (need_reset) {
|
|
eea_init_ctx(edev->enet, &ctx);
|
|
err = eea_reset_hw_resources(edev->enet, &ctx);
|
|
}
|
|
|
|
err_free:
|
|
kfree(dstatus.status);
|
|
|
|
err_unlock:
|
|
rtnl_unlock();
|
|
return err;
|
|
}
|
|
|
|
static int eea_update_cfg(struct eea_net *enet,
|
|
struct eea_device *edev,
|
|
struct eea_aq_cfg *hwcfg)
|
|
{
|
|
u32 rx_max = le32_to_cpu(hwcfg->rx_depth_max);
|
|
u32 tx_max = le32_to_cpu(hwcfg->tx_depth_max);
|
|
u32 rx_def = le32_to_cpu(hwcfg->rx_depth_def);
|
|
u32 tx_def = le32_to_cpu(hwcfg->tx_depth_def);
|
|
|
|
/* Now, we assert that the rx ring num is equal to the tx ring num. */
|
|
if (edev->rx_num != edev->tx_num) {
|
|
dev_err(edev->dma_dev, "Inconsistent ring num: RX %u, TX %u\n",
|
|
edev->rx_num, edev->tx_num);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (rx_max > EEA_NET_IO_HW_RING_DEPTH_MAX ||
|
|
rx_max < EEA_NET_IO_HW_RING_DEPTH_MIN ||
|
|
tx_max > EEA_NET_IO_HW_RING_DEPTH_MAX ||
|
|
tx_max < EEA_NET_IO_HW_RING_DEPTH_MIN) {
|
|
dev_err(edev->dma_dev, "Invalid HW max depth: RX %u, TX %u\n",
|
|
rx_max, tx_max);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (rx_def > rx_max ||
|
|
tx_def > tx_max ||
|
|
rx_def < EEA_NET_IO_HW_RING_DEPTH_MIN ||
|
|
tx_def < EEA_NET_IO_HW_RING_DEPTH_MIN) {
|
|
dev_err(edev->dma_dev, "Invalid default depth: RX %u (max %u), TX %u (max %u)\n",
|
|
rx_def, rx_max, tx_def, tx_max);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!is_power_of_2(rx_max) || !is_power_of_2(tx_max) ||
|
|
!is_power_of_2(rx_def) || !is_power_of_2(tx_def)) {
|
|
dev_err(edev->dma_dev, "Ring depth must be power of 2\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
enet->cfg_hw.rx_ring_depth = rx_max;
|
|
enet->cfg_hw.tx_ring_depth = tx_max;
|
|
enet->cfg_hw.rx_ring_num = edev->rx_num;
|
|
enet->cfg_hw.tx_ring_num = edev->tx_num;
|
|
enet->cfg_hw.split_hdr = EEA_SPLIT_HDR_SIZE;
|
|
|
|
enet->cfg.rx_ring_depth = rx_def;
|
|
enet->cfg.tx_ring_depth = tx_def;
|
|
enet->cfg.rx_ring_num = edev->rx_num;
|
|
enet->cfg.tx_ring_num = edev->tx_num;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int eea_netdev_init_features(struct net_device *netdev,
|
|
struct eea_net *enet,
|
|
struct eea_device *edev)
|
|
{
|
|
struct eea_aq_cfg *cfg;
|
|
int err;
|
|
u32 mtu;
|
|
|
|
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
|
|
if (!cfg)
|
|
return -ENOMEM;
|
|
|
|
err = eea_adminq_query_cfg(enet, cfg);
|
|
if (err)
|
|
goto err_free;
|
|
|
|
mtu = le16_to_cpu(cfg->mtu);
|
|
if (mtu < ETH_MIN_MTU) {
|
|
dev_err(edev->dma_dev, "The device gave us an invalid MTU. Here we can only exit the initialization. %u < %u\n",
|
|
mtu, ETH_MIN_MTU);
|
|
err = -EINVAL;
|
|
goto err_free;
|
|
}
|
|
|
|
err = eea_update_cfg(enet, edev, cfg);
|
|
if (err)
|
|
goto err_free;
|
|
|
|
netdev->priv_flags |= IFF_UNICAST_FLT;
|
|
netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
|
|
|
|
netdev->hw_features |= NETIF_F_HW_CSUM;
|
|
netdev->hw_features |= NETIF_F_GRO_HW;
|
|
netdev->hw_features |= NETIF_F_SG;
|
|
netdev->hw_features |= NETIF_F_TSO;
|
|
netdev->hw_features |= NETIF_F_TSO_ECN;
|
|
netdev->hw_features |= NETIF_F_TSO6;
|
|
netdev->hw_features |= NETIF_F_GSO_UDP_L4;
|
|
|
|
netdev->features |= NETIF_F_HIGHDMA;
|
|
netdev->features |= NETIF_F_HW_CSUM;
|
|
netdev->features |= NETIF_F_SG;
|
|
netdev->features |= NETIF_F_GSO_ROBUST;
|
|
netdev->features |= netdev->hw_features & NETIF_F_ALL_TSO;
|
|
netdev->features |= NETIF_F_RXCSUM;
|
|
netdev->features |= NETIF_F_GRO_HW;
|
|
|
|
netdev->vlan_features = netdev->features;
|
|
|
|
if (!is_valid_ether_addr(cfg->mac)) {
|
|
dev_err(edev->dma_dev, "The device gave invalid mac %pM\n",
|
|
cfg->mac);
|
|
err = -EINVAL;
|
|
goto err_free;
|
|
}
|
|
|
|
eth_hw_addr_set(netdev, cfg->mac);
|
|
|
|
enet->speed = SPEED_UNKNOWN;
|
|
enet->duplex = DUPLEX_UNKNOWN;
|
|
|
|
netdev->min_mtu = ETH_MIN_MTU;
|
|
|
|
netdev->mtu = mtu;
|
|
|
|
/* If jumbo frames are already enabled, then the returned MTU will be a
|
|
* jumbo MTU, and the driver will automatically enable jumbo frame
|
|
* support by default.
|
|
*/
|
|
netdev->max_mtu = mtu;
|
|
|
|
err_free:
|
|
kfree(cfg);
|
|
return err;
|
|
}
|
|
|
|
static const struct net_device_ops eea_netdev = {
|
|
.ndo_open = eea_netdev_open,
|
|
.ndo_stop = eea_netdev_stop,
|
|
.ndo_start_xmit = eea_tx_xmit,
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
.ndo_get_stats64 = eea_stats,
|
|
.ndo_features_check = passthru_features_check,
|
|
};
|
|
|
|
static struct eea_net *eea_netdev_alloc(struct eea_device *edev, u32 pairs)
|
|
{
|
|
struct net_device *netdev;
|
|
struct eea_net *enet;
|
|
int err;
|
|
|
|
netdev = alloc_etherdev_mq(sizeof(struct eea_net), pairs);
|
|
if (!netdev) {
|
|
dev_err(edev->dma_dev,
|
|
"alloc_etherdev_mq failed with pairs %d\n", pairs);
|
|
return NULL;
|
|
}
|
|
|
|
netdev->netdev_ops = &eea_netdev;
|
|
netdev->ethtool_ops = &eea_ethtool_ops;
|
|
SET_NETDEV_DEV(netdev, edev->dma_dev);
|
|
|
|
enet = netdev_priv(netdev);
|
|
enet->netdev = netdev;
|
|
enet->edev = edev;
|
|
edev->enet = enet;
|
|
|
|
err = eea_alloc_irq_blks(enet);
|
|
if (err) {
|
|
dev_err(edev->dma_dev,
|
|
"eea_alloc_irq_blks failed with pairs %d\n", pairs);
|
|
free_netdev(netdev);
|
|
return NULL;
|
|
}
|
|
|
|
spin_lock_init(&enet->stats_lock);
|
|
|
|
return enet;
|
|
}
|
|
|
|
static void eea_update_ts_off(struct eea_device *edev, struct eea_net *enet)
|
|
{
|
|
u64 ts;
|
|
|
|
ts = eea_pci_device_ts(edev);
|
|
|
|
enet->hw_ts_offset = ktime_get_real() - ts;
|
|
}
|
|
|
|
static int eea_net_reprobe(struct eea_device *edev)
|
|
{
|
|
struct eea_net *enet = edev->enet;
|
|
int err = 0;
|
|
|
|
enet->edev = edev;
|
|
|
|
if (!enet->adminq.ring) {
|
|
err = eea_create_adminq(enet, edev->rx_num + edev->tx_num);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
err = eea_alloc_irq_blks(enet);
|
|
if (err)
|
|
goto err_destroy_aq;
|
|
|
|
eea_update_ts_off(edev, enet);
|
|
|
|
rtnl_lock();
|
|
|
|
enet->link_err = 0;
|
|
if (edev->ha_reset_netdev_running &&
|
|
netif_running(edev->enet->netdev)) {
|
|
err = eea_netdev_open(enet->netdev);
|
|
if (err) {
|
|
enet->link_err = EEA_LINK_ERR_HA_RESET_DEV;
|
|
rtnl_unlock();
|
|
goto err_free_irq_blks;
|
|
}
|
|
}
|
|
|
|
rtnl_unlock();
|
|
|
|
enet->wait_pci_ready = false;
|
|
return 0;
|
|
|
|
err_free_irq_blks:
|
|
eea_free_irq_blk(enet);
|
|
|
|
err_destroy_aq:
|
|
eea_destroy_adminq(enet);
|
|
|
|
return err;
|
|
}
|
|
|
|
int eea_net_probe(struct eea_device *edev)
|
|
{
|
|
struct eea_net *enet;
|
|
int err = -ENOMEM;
|
|
|
|
/* If edev->enet is not null, then this is called from ha reset worker.
|
|
* Call eea_net_reprobe() directly.
|
|
*/
|
|
if (edev->enet)
|
|
return eea_net_reprobe(edev);
|
|
|
|
enet = eea_netdev_alloc(edev, edev->rx_num);
|
|
if (!enet)
|
|
return -ENOMEM;
|
|
|
|
err = eea_create_adminq(enet, edev->rx_num + edev->tx_num);
|
|
if (err)
|
|
goto err_free_netdev;
|
|
|
|
eea_adminq_config_host_info(enet);
|
|
|
|
err = eea_netdev_init_features(enet->netdev, enet, edev);
|
|
if (err)
|
|
goto err_reset_dev;
|
|
|
|
eea_update_ts_off(edev, enet);
|
|
|
|
netif_carrier_off(enet->netdev);
|
|
|
|
err = register_netdev(enet->netdev);
|
|
if (err)
|
|
goto err_reset_dev;
|
|
|
|
netdev_dbg(enet->netdev, "eea probe success.\n");
|
|
|
|
return 0;
|
|
|
|
err_reset_dev:
|
|
eea_device_reset(edev);
|
|
eea_destroy_adminq(enet);
|
|
|
|
err_free_netdev:
|
|
eea_free_irq_blk(enet);
|
|
free_netdev(enet->netdev);
|
|
return err;
|
|
}
|
|
|
|
static void eea_net_ha_reset_remove(struct eea_net *enet,
|
|
struct eea_device *edev)
|
|
{
|
|
rtnl_lock();
|
|
edev->ha_reset_netdev_running = false;
|
|
if (netif_running(enet->netdev)) {
|
|
eea_netdev_stop(enet->netdev);
|
|
edev->ha_reset_netdev_running = true;
|
|
}
|
|
|
|
/* Prevent that the user set up the net device. */
|
|
enet->link_err = EEA_LINK_ERR_HA_RESET_DEV;
|
|
|
|
rtnl_unlock();
|
|
|
|
eea_device_reset(edev);
|
|
eea_destroy_adminq(enet);
|
|
eea_free_irq_blk(enet);
|
|
|
|
enet->wait_pci_ready = true;
|
|
}
|
|
|
|
void eea_net_remove(struct eea_device *edev, bool ha)
|
|
{
|
|
struct net_device *netdev;
|
|
struct eea_net *enet;
|
|
|
|
enet = edev->enet;
|
|
netdev = enet->netdev;
|
|
|
|
if (ha) {
|
|
if (enet->wait_pci_ready)
|
|
return;
|
|
|
|
eea_net_ha_reset_remove(enet, edev);
|
|
return;
|
|
}
|
|
|
|
unregister_netdev(netdev);
|
|
|
|
if (!enet->wait_pci_ready) {
|
|
eea_device_reset(edev);
|
|
eea_destroy_adminq(enet);
|
|
eea_free_irq_blk(enet);
|
|
}
|
|
|
|
free_netdev(netdev);
|
|
}
|
|
|
|
void eea_net_shutdown(struct eea_device *edev)
|
|
{
|
|
struct net_device *netdev;
|
|
struct eea_net *enet;
|
|
|
|
enet = edev->enet;
|
|
netdev = enet->netdev;
|
|
|
|
rtnl_lock();
|
|
|
|
netif_device_detach(netdev);
|
|
dev_close(netdev);
|
|
|
|
if (!enet->wait_pci_ready) {
|
|
eea_device_reset(edev);
|
|
eea_destroy_adminq(enet);
|
|
eea_free_irq_blk(enet);
|
|
}
|
|
|
|
rtnl_unlock();
|
|
}
|