Assign a random mac address to the VF interface station
address if it boots with a zero mac address in order to match
similar behavior seen in other VF drivers. Handle the errors
where the older firmware does not allow the VF to set its own
station address.
Newer firmware will allow the VF to set the station mac address
if it hasn't already been set administratively through the PF.
Setting it will also be allowed if the VF has trust.
Fixes: fbb39807e9
("ionic: support sr-iov operations")
Signed-off-by: R Mohamed Shah <mohamed@pensando.io>
Signed-off-by: Shannon Nelson <snelson@pensando.io>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
3678 lines
96 KiB
C
3678 lines
96 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright(c) 2017 - 2019 Pensando Systems, Inc */
|
|
|
|
#include <linux/ethtool.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/dynamic_debug.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/crash_dump.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include "ionic.h"
|
|
#include "ionic_bus.h"
|
|
#include "ionic_lif.h"
|
|
#include "ionic_txrx.h"
|
|
#include "ionic_ethtool.h"
|
|
#include "ionic_debugfs.h"
|
|
|
|
/* queuetype support level */
|
|
static const u8 ionic_qtype_versions[IONIC_QTYPE_MAX] = {
|
|
[IONIC_QTYPE_ADMINQ] = 0, /* 0 = Base version with CQ support */
|
|
[IONIC_QTYPE_NOTIFYQ] = 0, /* 0 = Base version */
|
|
[IONIC_QTYPE_RXQ] = 0, /* 0 = Base version with CQ+SG support */
|
|
[IONIC_QTYPE_TXQ] = 1, /* 0 = Base version with CQ+SG support
|
|
* 1 = ... with Tx SG version 1
|
|
*/
|
|
};
|
|
|
|
static void ionic_link_status_check(struct ionic_lif *lif);
|
|
static void ionic_lif_handle_fw_down(struct ionic_lif *lif);
|
|
static void ionic_lif_handle_fw_up(struct ionic_lif *lif);
|
|
static void ionic_lif_set_netdev_info(struct ionic_lif *lif);
|
|
|
|
static void ionic_txrx_deinit(struct ionic_lif *lif);
|
|
static int ionic_txrx_init(struct ionic_lif *lif);
|
|
static int ionic_start_queues(struct ionic_lif *lif);
|
|
static void ionic_stop_queues(struct ionic_lif *lif);
|
|
static void ionic_lif_queue_identify(struct ionic_lif *lif);
|
|
|
|
static void ionic_dim_work(struct work_struct *work)
|
|
{
|
|
struct dim *dim = container_of(work, struct dim, work);
|
|
struct dim_cq_moder cur_moder;
|
|
struct ionic_qcq *qcq;
|
|
u32 new_coal;
|
|
|
|
cur_moder = net_dim_get_rx_moderation(dim->mode, dim->profile_ix);
|
|
qcq = container_of(dim, struct ionic_qcq, dim);
|
|
new_coal = ionic_coal_usec_to_hw(qcq->q.lif->ionic, cur_moder.usec);
|
|
new_coal = new_coal ? new_coal : 1;
|
|
|
|
if (qcq->intr.dim_coal_hw != new_coal) {
|
|
unsigned int qi = qcq->cq.bound_q->index;
|
|
struct ionic_lif *lif = qcq->q.lif;
|
|
|
|
qcq->intr.dim_coal_hw = new_coal;
|
|
|
|
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
|
|
lif->rxqcqs[qi]->intr.index,
|
|
qcq->intr.dim_coal_hw);
|
|
}
|
|
|
|
dim->state = DIM_START_MEASURE;
|
|
}
|
|
|
|
static void ionic_lif_deferred_work(struct work_struct *work)
|
|
{
|
|
struct ionic_lif *lif = container_of(work, struct ionic_lif, deferred.work);
|
|
struct ionic_deferred *def = &lif->deferred;
|
|
struct ionic_deferred_work *w = NULL;
|
|
|
|
do {
|
|
spin_lock_bh(&def->lock);
|
|
if (!list_empty(&def->list)) {
|
|
w = list_first_entry(&def->list,
|
|
struct ionic_deferred_work, list);
|
|
list_del(&w->list);
|
|
}
|
|
spin_unlock_bh(&def->lock);
|
|
|
|
if (!w)
|
|
break;
|
|
|
|
switch (w->type) {
|
|
case IONIC_DW_TYPE_RX_MODE:
|
|
ionic_lif_rx_mode(lif);
|
|
break;
|
|
case IONIC_DW_TYPE_LINK_STATUS:
|
|
ionic_link_status_check(lif);
|
|
break;
|
|
case IONIC_DW_TYPE_LIF_RESET:
|
|
if (w->fw_status) {
|
|
ionic_lif_handle_fw_up(lif);
|
|
} else {
|
|
ionic_lif_handle_fw_down(lif);
|
|
|
|
/* Fire off another watchdog to see
|
|
* if the FW is already back rather than
|
|
* waiting another whole cycle
|
|
*/
|
|
mod_timer(&lif->ionic->watchdog_timer, jiffies + 1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
kfree(w);
|
|
w = NULL;
|
|
} while (true);
|
|
}
|
|
|
|
void ionic_lif_deferred_enqueue(struct ionic_deferred *def,
|
|
struct ionic_deferred_work *work)
|
|
{
|
|
spin_lock_bh(&def->lock);
|
|
list_add_tail(&work->list, &def->list);
|
|
spin_unlock_bh(&def->lock);
|
|
schedule_work(&def->work);
|
|
}
|
|
|
|
static void ionic_link_status_check(struct ionic_lif *lif)
|
|
{
|
|
struct net_device *netdev = lif->netdev;
|
|
u16 link_status;
|
|
bool link_up;
|
|
|
|
if (!test_bit(IONIC_LIF_F_LINK_CHECK_REQUESTED, lif->state))
|
|
return;
|
|
|
|
/* Don't put carrier back up if we're in a broken state */
|
|
if (test_bit(IONIC_LIF_F_BROKEN, lif->state)) {
|
|
clear_bit(IONIC_LIF_F_LINK_CHECK_REQUESTED, lif->state);
|
|
return;
|
|
}
|
|
|
|
link_status = le16_to_cpu(lif->info->status.link_status);
|
|
link_up = link_status == IONIC_PORT_OPER_STATUS_UP;
|
|
|
|
if (link_up) {
|
|
int err = 0;
|
|
|
|
if (netdev->flags & IFF_UP && netif_running(netdev)) {
|
|
mutex_lock(&lif->queue_lock);
|
|
err = ionic_start_queues(lif);
|
|
if (err && err != -EBUSY) {
|
|
netdev_err(lif->netdev,
|
|
"Failed to start queues: %d\n", err);
|
|
set_bit(IONIC_LIF_F_BROKEN, lif->state);
|
|
netif_carrier_off(lif->netdev);
|
|
}
|
|
mutex_unlock(&lif->queue_lock);
|
|
}
|
|
|
|
if (!err && !netif_carrier_ok(netdev)) {
|
|
ionic_port_identify(lif->ionic);
|
|
netdev_info(netdev, "Link up - %d Gbps\n",
|
|
le32_to_cpu(lif->info->status.link_speed) / 1000);
|
|
netif_carrier_on(netdev);
|
|
}
|
|
} else {
|
|
if (netif_carrier_ok(netdev)) {
|
|
netdev_info(netdev, "Link down\n");
|
|
netif_carrier_off(netdev);
|
|
}
|
|
|
|
if (netdev->flags & IFF_UP && netif_running(netdev)) {
|
|
mutex_lock(&lif->queue_lock);
|
|
ionic_stop_queues(lif);
|
|
mutex_unlock(&lif->queue_lock);
|
|
}
|
|
}
|
|
|
|
clear_bit(IONIC_LIF_F_LINK_CHECK_REQUESTED, lif->state);
|
|
}
|
|
|
|
void ionic_link_status_check_request(struct ionic_lif *lif, bool can_sleep)
|
|
{
|
|
struct ionic_deferred_work *work;
|
|
|
|
/* we only need one request outstanding at a time */
|
|
if (test_and_set_bit(IONIC_LIF_F_LINK_CHECK_REQUESTED, lif->state))
|
|
return;
|
|
|
|
if (!can_sleep) {
|
|
work = kzalloc(sizeof(*work), GFP_ATOMIC);
|
|
if (!work) {
|
|
clear_bit(IONIC_LIF_F_LINK_CHECK_REQUESTED, lif->state);
|
|
return;
|
|
}
|
|
|
|
work->type = IONIC_DW_TYPE_LINK_STATUS;
|
|
ionic_lif_deferred_enqueue(&lif->deferred, work);
|
|
} else {
|
|
ionic_link_status_check(lif);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t ionic_isr(int irq, void *data)
|
|
{
|
|
struct napi_struct *napi = data;
|
|
|
|
napi_schedule_irqoff(napi);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int ionic_request_irq(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
{
|
|
struct ionic_intr_info *intr = &qcq->intr;
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_queue *q = &qcq->q;
|
|
const char *name;
|
|
|
|
if (lif->registered)
|
|
name = lif->netdev->name;
|
|
else
|
|
name = dev_name(dev);
|
|
|
|
snprintf(intr->name, sizeof(intr->name),
|
|
"%s-%s-%s", IONIC_DRV_NAME, name, q->name);
|
|
|
|
return devm_request_irq(dev, intr->vector, ionic_isr,
|
|
0, intr->name, &qcq->napi);
|
|
}
|
|
|
|
static int ionic_intr_alloc(struct ionic_lif *lif, struct ionic_intr_info *intr)
|
|
{
|
|
struct ionic *ionic = lif->ionic;
|
|
int index;
|
|
|
|
index = find_first_zero_bit(ionic->intrs, ionic->nintrs);
|
|
if (index == ionic->nintrs) {
|
|
netdev_warn(lif->netdev, "%s: no intr, index=%d nintrs=%d\n",
|
|
__func__, index, ionic->nintrs);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
set_bit(index, ionic->intrs);
|
|
ionic_intr_init(&ionic->idev, intr, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ionic_intr_free(struct ionic *ionic, int index)
|
|
{
|
|
if (index != IONIC_INTR_INDEX_NOT_ASSIGNED && index < ionic->nintrs)
|
|
clear_bit(index, ionic->intrs);
|
|
}
|
|
|
|
static int ionic_qcq_enable(struct ionic_qcq *qcq)
|
|
{
|
|
struct ionic_queue *q = &qcq->q;
|
|
struct ionic_lif *lif = q->lif;
|
|
struct ionic_dev *idev;
|
|
struct device *dev;
|
|
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.q_control = {
|
|
.opcode = IONIC_CMD_Q_CONTROL,
|
|
.lif_index = cpu_to_le16(lif->index),
|
|
.type = q->type,
|
|
.index = cpu_to_le32(q->index),
|
|
.oper = IONIC_Q_ENABLE,
|
|
},
|
|
};
|
|
|
|
idev = &lif->ionic->idev;
|
|
dev = lif->ionic->dev;
|
|
|
|
dev_dbg(dev, "q_enable.index %d q_enable.qtype %d\n",
|
|
ctx.cmd.q_control.index, ctx.cmd.q_control.type);
|
|
|
|
if (qcq->flags & IONIC_QCQ_F_INTR) {
|
|
irq_set_affinity_hint(qcq->intr.vector,
|
|
&qcq->intr.affinity_mask);
|
|
napi_enable(&qcq->napi);
|
|
ionic_intr_clean(idev->intr_ctrl, qcq->intr.index);
|
|
ionic_intr_mask(idev->intr_ctrl, qcq->intr.index,
|
|
IONIC_INTR_MASK_CLEAR);
|
|
}
|
|
|
|
return ionic_adminq_post_wait(lif, &ctx);
|
|
}
|
|
|
|
static int ionic_qcq_disable(struct ionic_lif *lif, struct ionic_qcq *qcq, int fw_err)
|
|
{
|
|
struct ionic_queue *q;
|
|
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.q_control = {
|
|
.opcode = IONIC_CMD_Q_CONTROL,
|
|
.oper = IONIC_Q_DISABLE,
|
|
},
|
|
};
|
|
|
|
if (!qcq) {
|
|
netdev_err(lif->netdev, "%s: bad qcq\n", __func__);
|
|
return -ENXIO;
|
|
}
|
|
|
|
q = &qcq->q;
|
|
|
|
if (qcq->flags & IONIC_QCQ_F_INTR) {
|
|
struct ionic_dev *idev = &lif->ionic->idev;
|
|
|
|
cancel_work_sync(&qcq->dim.work);
|
|
ionic_intr_mask(idev->intr_ctrl, qcq->intr.index,
|
|
IONIC_INTR_MASK_SET);
|
|
synchronize_irq(qcq->intr.vector);
|
|
irq_set_affinity_hint(qcq->intr.vector, NULL);
|
|
napi_disable(&qcq->napi);
|
|
}
|
|
|
|
/* If there was a previous fw communcation error, don't bother with
|
|
* sending the adminq command and just return the same error value.
|
|
*/
|
|
if (fw_err == -ETIMEDOUT || fw_err == -ENXIO)
|
|
return fw_err;
|
|
|
|
ctx.cmd.q_control.lif_index = cpu_to_le16(lif->index);
|
|
ctx.cmd.q_control.type = q->type;
|
|
ctx.cmd.q_control.index = cpu_to_le32(q->index);
|
|
dev_dbg(lif->ionic->dev, "q_disable.index %d q_disable.qtype %d\n",
|
|
ctx.cmd.q_control.index, ctx.cmd.q_control.type);
|
|
|
|
return ionic_adminq_post_wait(lif, &ctx);
|
|
}
|
|
|
|
static void ionic_lif_qcq_deinit(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
{
|
|
struct ionic_dev *idev = &lif->ionic->idev;
|
|
|
|
if (!qcq)
|
|
return;
|
|
|
|
if (!(qcq->flags & IONIC_QCQ_F_INITED))
|
|
return;
|
|
|
|
if (qcq->flags & IONIC_QCQ_F_INTR) {
|
|
ionic_intr_mask(idev->intr_ctrl, qcq->intr.index,
|
|
IONIC_INTR_MASK_SET);
|
|
netif_napi_del(&qcq->napi);
|
|
}
|
|
|
|
qcq->flags &= ~IONIC_QCQ_F_INITED;
|
|
}
|
|
|
|
static void ionic_qcq_intr_free(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
{
|
|
if (!(qcq->flags & IONIC_QCQ_F_INTR) || qcq->intr.vector == 0)
|
|
return;
|
|
|
|
irq_set_affinity_hint(qcq->intr.vector, NULL);
|
|
devm_free_irq(lif->ionic->dev, qcq->intr.vector, &qcq->napi);
|
|
qcq->intr.vector = 0;
|
|
ionic_intr_free(lif->ionic, qcq->intr.index);
|
|
qcq->intr.index = IONIC_INTR_INDEX_NOT_ASSIGNED;
|
|
}
|
|
|
|
static void ionic_qcq_free(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
|
|
if (!qcq)
|
|
return;
|
|
|
|
ionic_debugfs_del_qcq(qcq);
|
|
|
|
if (qcq->q_base) {
|
|
dma_free_coherent(dev, qcq->q_size, qcq->q_base, qcq->q_base_pa);
|
|
qcq->q_base = NULL;
|
|
qcq->q_base_pa = 0;
|
|
}
|
|
|
|
if (qcq->cq_base) {
|
|
dma_free_coherent(dev, qcq->cq_size, qcq->cq_base, qcq->cq_base_pa);
|
|
qcq->cq_base = NULL;
|
|
qcq->cq_base_pa = 0;
|
|
}
|
|
|
|
if (qcq->sg_base) {
|
|
dma_free_coherent(dev, qcq->sg_size, qcq->sg_base, qcq->sg_base_pa);
|
|
qcq->sg_base = NULL;
|
|
qcq->sg_base_pa = 0;
|
|
}
|
|
|
|
ionic_qcq_intr_free(lif, qcq);
|
|
|
|
if (qcq->cq.info) {
|
|
vfree(qcq->cq.info);
|
|
qcq->cq.info = NULL;
|
|
}
|
|
if (qcq->q.info) {
|
|
vfree(qcq->q.info);
|
|
qcq->q.info = NULL;
|
|
}
|
|
}
|
|
|
|
static void ionic_qcqs_free(struct ionic_lif *lif)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_qcq *adminqcq;
|
|
unsigned long irqflags;
|
|
|
|
if (lif->notifyqcq) {
|
|
ionic_qcq_free(lif, lif->notifyqcq);
|
|
devm_kfree(dev, lif->notifyqcq);
|
|
lif->notifyqcq = NULL;
|
|
}
|
|
|
|
if (lif->adminqcq) {
|
|
spin_lock_irqsave(&lif->adminq_lock, irqflags);
|
|
adminqcq = READ_ONCE(lif->adminqcq);
|
|
lif->adminqcq = NULL;
|
|
spin_unlock_irqrestore(&lif->adminq_lock, irqflags);
|
|
if (adminqcq) {
|
|
ionic_qcq_free(lif, adminqcq);
|
|
devm_kfree(dev, adminqcq);
|
|
}
|
|
}
|
|
|
|
if (lif->rxqcqs) {
|
|
devm_kfree(dev, lif->rxqstats);
|
|
lif->rxqstats = NULL;
|
|
devm_kfree(dev, lif->rxqcqs);
|
|
lif->rxqcqs = NULL;
|
|
}
|
|
|
|
if (lif->txqcqs) {
|
|
devm_kfree(dev, lif->txqstats);
|
|
lif->txqstats = NULL;
|
|
devm_kfree(dev, lif->txqcqs);
|
|
lif->txqcqs = NULL;
|
|
}
|
|
}
|
|
|
|
static void ionic_link_qcq_interrupts(struct ionic_qcq *src_qcq,
|
|
struct ionic_qcq *n_qcq)
|
|
{
|
|
if (WARN_ON(n_qcq->flags & IONIC_QCQ_F_INTR)) {
|
|
ionic_intr_free(n_qcq->cq.lif->ionic, n_qcq->intr.index);
|
|
n_qcq->flags &= ~IONIC_QCQ_F_INTR;
|
|
}
|
|
|
|
n_qcq->intr.vector = src_qcq->intr.vector;
|
|
n_qcq->intr.index = src_qcq->intr.index;
|
|
}
|
|
|
|
static int ionic_alloc_qcq_interrupt(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
{
|
|
int err;
|
|
|
|
if (!(qcq->flags & IONIC_QCQ_F_INTR)) {
|
|
qcq->intr.index = IONIC_INTR_INDEX_NOT_ASSIGNED;
|
|
return 0;
|
|
}
|
|
|
|
err = ionic_intr_alloc(lif, &qcq->intr);
|
|
if (err) {
|
|
netdev_warn(lif->netdev, "no intr for %s: %d\n",
|
|
qcq->q.name, err);
|
|
goto err_out;
|
|
}
|
|
|
|
err = ionic_bus_get_irq(lif->ionic, qcq->intr.index);
|
|
if (err < 0) {
|
|
netdev_warn(lif->netdev, "no vector for %s: %d\n",
|
|
qcq->q.name, err);
|
|
goto err_out_free_intr;
|
|
}
|
|
qcq->intr.vector = err;
|
|
ionic_intr_mask_assert(lif->ionic->idev.intr_ctrl, qcq->intr.index,
|
|
IONIC_INTR_MASK_SET);
|
|
|
|
err = ionic_request_irq(lif, qcq);
|
|
if (err) {
|
|
netdev_warn(lif->netdev, "irq request failed %d\n", err);
|
|
goto err_out_free_intr;
|
|
}
|
|
|
|
/* try to get the irq on the local numa node first */
|
|
qcq->intr.cpu = cpumask_local_spread(qcq->intr.index,
|
|
dev_to_node(lif->ionic->dev));
|
|
if (qcq->intr.cpu != -1)
|
|
cpumask_set_cpu(qcq->intr.cpu, &qcq->intr.affinity_mask);
|
|
|
|
netdev_dbg(lif->netdev, "%s: Interrupt index %d\n", qcq->q.name, qcq->intr.index);
|
|
return 0;
|
|
|
|
err_out_free_intr:
|
|
ionic_intr_free(lif->ionic, qcq->intr.index);
|
|
err_out:
|
|
return err;
|
|
}
|
|
|
|
static int ionic_qcq_alloc(struct ionic_lif *lif, unsigned int type,
|
|
unsigned int index,
|
|
const char *name, unsigned int flags,
|
|
unsigned int num_descs, unsigned int desc_size,
|
|
unsigned int cq_desc_size,
|
|
unsigned int sg_desc_size,
|
|
unsigned int pid, struct ionic_qcq **qcq)
|
|
{
|
|
struct ionic_dev *idev = &lif->ionic->idev;
|
|
struct device *dev = lif->ionic->dev;
|
|
void *q_base, *cq_base, *sg_base;
|
|
dma_addr_t cq_base_pa = 0;
|
|
dma_addr_t sg_base_pa = 0;
|
|
dma_addr_t q_base_pa = 0;
|
|
struct ionic_qcq *new;
|
|
int err;
|
|
|
|
*qcq = NULL;
|
|
|
|
new = devm_kzalloc(dev, sizeof(*new), GFP_KERNEL);
|
|
if (!new) {
|
|
netdev_err(lif->netdev, "Cannot allocate queue structure\n");
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
new->q.dev = dev;
|
|
new->flags = flags;
|
|
|
|
new->q.info = vzalloc(num_descs * sizeof(*new->q.info));
|
|
if (!new->q.info) {
|
|
netdev_err(lif->netdev, "Cannot allocate queue info\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_qcq;
|
|
}
|
|
|
|
new->q.type = type;
|
|
new->q.max_sg_elems = lif->qtype_info[type].max_sg_elems;
|
|
|
|
err = ionic_q_init(lif, idev, &new->q, index, name, num_descs,
|
|
desc_size, sg_desc_size, pid);
|
|
if (err) {
|
|
netdev_err(lif->netdev, "Cannot initialize queue\n");
|
|
goto err_out_free_q_info;
|
|
}
|
|
|
|
err = ionic_alloc_qcq_interrupt(lif, new);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
new->cq.info = vzalloc(num_descs * sizeof(*new->cq.info));
|
|
if (!new->cq.info) {
|
|
netdev_err(lif->netdev, "Cannot allocate completion queue info\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_irq;
|
|
}
|
|
|
|
err = ionic_cq_init(lif, &new->cq, &new->intr, num_descs, cq_desc_size);
|
|
if (err) {
|
|
netdev_err(lif->netdev, "Cannot initialize completion queue\n");
|
|
goto err_out_free_cq_info;
|
|
}
|
|
|
|
if (flags & IONIC_QCQ_F_NOTIFYQ) {
|
|
int q_size, cq_size;
|
|
|
|
/* q & cq need to be contiguous in case of notifyq */
|
|
q_size = ALIGN(num_descs * desc_size, PAGE_SIZE);
|
|
cq_size = ALIGN(num_descs * cq_desc_size, PAGE_SIZE);
|
|
|
|
new->q_size = PAGE_SIZE + q_size + cq_size;
|
|
new->q_base = dma_alloc_coherent(dev, new->q_size,
|
|
&new->q_base_pa, GFP_KERNEL);
|
|
if (!new->q_base) {
|
|
netdev_err(lif->netdev, "Cannot allocate qcq DMA memory\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_cq_info;
|
|
}
|
|
q_base = PTR_ALIGN(new->q_base, PAGE_SIZE);
|
|
q_base_pa = ALIGN(new->q_base_pa, PAGE_SIZE);
|
|
ionic_q_map(&new->q, q_base, q_base_pa);
|
|
|
|
cq_base = PTR_ALIGN(q_base + q_size, PAGE_SIZE);
|
|
cq_base_pa = ALIGN(new->q_base_pa + q_size, PAGE_SIZE);
|
|
ionic_cq_map(&new->cq, cq_base, cq_base_pa);
|
|
ionic_cq_bind(&new->cq, &new->q);
|
|
} else {
|
|
new->q_size = PAGE_SIZE + (num_descs * desc_size);
|
|
new->q_base = dma_alloc_coherent(dev, new->q_size, &new->q_base_pa,
|
|
GFP_KERNEL);
|
|
if (!new->q_base) {
|
|
netdev_err(lif->netdev, "Cannot allocate queue DMA memory\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_cq_info;
|
|
}
|
|
q_base = PTR_ALIGN(new->q_base, PAGE_SIZE);
|
|
q_base_pa = ALIGN(new->q_base_pa, PAGE_SIZE);
|
|
ionic_q_map(&new->q, q_base, q_base_pa);
|
|
|
|
new->cq_size = PAGE_SIZE + (num_descs * cq_desc_size);
|
|
new->cq_base = dma_alloc_coherent(dev, new->cq_size, &new->cq_base_pa,
|
|
GFP_KERNEL);
|
|
if (!new->cq_base) {
|
|
netdev_err(lif->netdev, "Cannot allocate cq DMA memory\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_q;
|
|
}
|
|
cq_base = PTR_ALIGN(new->cq_base, PAGE_SIZE);
|
|
cq_base_pa = ALIGN(new->cq_base_pa, PAGE_SIZE);
|
|
ionic_cq_map(&new->cq, cq_base, cq_base_pa);
|
|
ionic_cq_bind(&new->cq, &new->q);
|
|
}
|
|
|
|
if (flags & IONIC_QCQ_F_SG) {
|
|
new->sg_size = PAGE_SIZE + (num_descs * sg_desc_size);
|
|
new->sg_base = dma_alloc_coherent(dev, new->sg_size, &new->sg_base_pa,
|
|
GFP_KERNEL);
|
|
if (!new->sg_base) {
|
|
netdev_err(lif->netdev, "Cannot allocate sg DMA memory\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_cq;
|
|
}
|
|
sg_base = PTR_ALIGN(new->sg_base, PAGE_SIZE);
|
|
sg_base_pa = ALIGN(new->sg_base_pa, PAGE_SIZE);
|
|
ionic_q_sg_map(&new->q, sg_base, sg_base_pa);
|
|
}
|
|
|
|
INIT_WORK(&new->dim.work, ionic_dim_work);
|
|
new->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE;
|
|
|
|
*qcq = new;
|
|
|
|
return 0;
|
|
|
|
err_out_free_cq:
|
|
dma_free_coherent(dev, new->cq_size, new->cq_base, new->cq_base_pa);
|
|
err_out_free_q:
|
|
dma_free_coherent(dev, new->q_size, new->q_base, new->q_base_pa);
|
|
err_out_free_cq_info:
|
|
vfree(new->cq.info);
|
|
err_out_free_irq:
|
|
if (flags & IONIC_QCQ_F_INTR) {
|
|
devm_free_irq(dev, new->intr.vector, &new->napi);
|
|
ionic_intr_free(lif->ionic, new->intr.index);
|
|
}
|
|
err_out_free_q_info:
|
|
vfree(new->q.info);
|
|
err_out_free_qcq:
|
|
devm_kfree(dev, new);
|
|
err_out:
|
|
dev_err(dev, "qcq alloc of %s%d failed %d\n", name, index, err);
|
|
return err;
|
|
}
|
|
|
|
static int ionic_qcqs_alloc(struct ionic_lif *lif)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
unsigned int flags;
|
|
int err;
|
|
|
|
flags = IONIC_QCQ_F_INTR;
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_ADMINQ, 0, "admin", flags,
|
|
IONIC_ADMINQ_LENGTH,
|
|
sizeof(struct ionic_admin_cmd),
|
|
sizeof(struct ionic_admin_comp),
|
|
0, lif->kern_pid, &lif->adminqcq);
|
|
if (err)
|
|
return err;
|
|
ionic_debugfs_add_qcq(lif, lif->adminqcq);
|
|
|
|
if (lif->ionic->nnqs_per_lif) {
|
|
flags = IONIC_QCQ_F_NOTIFYQ;
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_NOTIFYQ, 0, "notifyq",
|
|
flags, IONIC_NOTIFYQ_LENGTH,
|
|
sizeof(struct ionic_notifyq_cmd),
|
|
sizeof(union ionic_notifyq_comp),
|
|
0, lif->kern_pid, &lif->notifyqcq);
|
|
if (err)
|
|
goto err_out;
|
|
ionic_debugfs_add_qcq(lif, lif->notifyqcq);
|
|
|
|
/* Let the notifyq ride on the adminq interrupt */
|
|
ionic_link_qcq_interrupts(lif->adminqcq, lif->notifyqcq);
|
|
}
|
|
|
|
err = -ENOMEM;
|
|
lif->txqcqs = devm_kcalloc(dev, lif->ionic->ntxqs_per_lif,
|
|
sizeof(*lif->txqcqs), GFP_KERNEL);
|
|
if (!lif->txqcqs)
|
|
goto err_out;
|
|
lif->rxqcqs = devm_kcalloc(dev, lif->ionic->nrxqs_per_lif,
|
|
sizeof(*lif->rxqcqs), GFP_KERNEL);
|
|
if (!lif->rxqcqs)
|
|
goto err_out;
|
|
|
|
lif->txqstats = devm_kcalloc(dev, lif->ionic->ntxqs_per_lif + 1,
|
|
sizeof(*lif->txqstats), GFP_KERNEL);
|
|
if (!lif->txqstats)
|
|
goto err_out;
|
|
lif->rxqstats = devm_kcalloc(dev, lif->ionic->nrxqs_per_lif + 1,
|
|
sizeof(*lif->rxqstats), GFP_KERNEL);
|
|
if (!lif->rxqstats)
|
|
goto err_out;
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
ionic_qcqs_free(lif);
|
|
return err;
|
|
}
|
|
|
|
static void ionic_qcq_sanitize(struct ionic_qcq *qcq)
|
|
{
|
|
qcq->q.tail_idx = 0;
|
|
qcq->q.head_idx = 0;
|
|
qcq->cq.tail_idx = 0;
|
|
qcq->cq.done_color = 1;
|
|
memset(qcq->q_base, 0, qcq->q_size);
|
|
memset(qcq->cq_base, 0, qcq->cq_size);
|
|
memset(qcq->sg_base, 0, qcq->sg_size);
|
|
}
|
|
|
|
static int ionic_lif_txq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_queue *q = &qcq->q;
|
|
struct ionic_cq *cq = &qcq->cq;
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.q_init = {
|
|
.opcode = IONIC_CMD_Q_INIT,
|
|
.lif_index = cpu_to_le16(lif->index),
|
|
.type = q->type,
|
|
.ver = lif->qtype_info[q->type].version,
|
|
.index = cpu_to_le32(q->index),
|
|
.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
|
|
IONIC_QINIT_F_SG),
|
|
.pid = cpu_to_le16(q->pid),
|
|
.ring_size = ilog2(q->num_descs),
|
|
.ring_base = cpu_to_le64(q->base_pa),
|
|
.cq_ring_base = cpu_to_le64(cq->base_pa),
|
|
.sg_ring_base = cpu_to_le64(q->sg_base_pa),
|
|
.features = cpu_to_le64(q->features),
|
|
},
|
|
};
|
|
unsigned int intr_index;
|
|
int err;
|
|
|
|
intr_index = qcq->intr.index;
|
|
|
|
ctx.cmd.q_init.intr_index = cpu_to_le16(intr_index);
|
|
|
|
dev_dbg(dev, "txq_init.pid %d\n", ctx.cmd.q_init.pid);
|
|
dev_dbg(dev, "txq_init.index %d\n", ctx.cmd.q_init.index);
|
|
dev_dbg(dev, "txq_init.ring_base 0x%llx\n", ctx.cmd.q_init.ring_base);
|
|
dev_dbg(dev, "txq_init.ring_size %d\n", ctx.cmd.q_init.ring_size);
|
|
dev_dbg(dev, "txq_init.flags 0x%x\n", ctx.cmd.q_init.flags);
|
|
dev_dbg(dev, "txq_init.ver %d\n", ctx.cmd.q_init.ver);
|
|
dev_dbg(dev, "txq_init.intr_index %d\n", ctx.cmd.q_init.intr_index);
|
|
|
|
ionic_qcq_sanitize(qcq);
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
return err;
|
|
|
|
q->hw_type = ctx.comp.q_init.hw_type;
|
|
q->hw_index = le32_to_cpu(ctx.comp.q_init.hw_index);
|
|
q->dbval = IONIC_DBELL_QID(q->hw_index);
|
|
|
|
dev_dbg(dev, "txq->hw_type %d\n", q->hw_type);
|
|
dev_dbg(dev, "txq->hw_index %d\n", q->hw_index);
|
|
|
|
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
|
|
netif_napi_add(lif->netdev, &qcq->napi, ionic_tx_napi,
|
|
NAPI_POLL_WEIGHT);
|
|
|
|
qcq->flags |= IONIC_QCQ_F_INITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_lif_rxq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_queue *q = &qcq->q;
|
|
struct ionic_cq *cq = &qcq->cq;
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.q_init = {
|
|
.opcode = IONIC_CMD_Q_INIT,
|
|
.lif_index = cpu_to_le16(lif->index),
|
|
.type = q->type,
|
|
.ver = lif->qtype_info[q->type].version,
|
|
.index = cpu_to_le32(q->index),
|
|
.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
|
|
IONIC_QINIT_F_SG),
|
|
.intr_index = cpu_to_le16(cq->bound_intr->index),
|
|
.pid = cpu_to_le16(q->pid),
|
|
.ring_size = ilog2(q->num_descs),
|
|
.ring_base = cpu_to_le64(q->base_pa),
|
|
.cq_ring_base = cpu_to_le64(cq->base_pa),
|
|
.sg_ring_base = cpu_to_le64(q->sg_base_pa),
|
|
.features = cpu_to_le64(q->features),
|
|
},
|
|
};
|
|
int err;
|
|
|
|
dev_dbg(dev, "rxq_init.pid %d\n", ctx.cmd.q_init.pid);
|
|
dev_dbg(dev, "rxq_init.index %d\n", ctx.cmd.q_init.index);
|
|
dev_dbg(dev, "rxq_init.ring_base 0x%llx\n", ctx.cmd.q_init.ring_base);
|
|
dev_dbg(dev, "rxq_init.ring_size %d\n", ctx.cmd.q_init.ring_size);
|
|
dev_dbg(dev, "rxq_init.flags 0x%x\n", ctx.cmd.q_init.flags);
|
|
dev_dbg(dev, "rxq_init.ver %d\n", ctx.cmd.q_init.ver);
|
|
dev_dbg(dev, "rxq_init.intr_index %d\n", ctx.cmd.q_init.intr_index);
|
|
|
|
ionic_qcq_sanitize(qcq);
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
return err;
|
|
|
|
q->hw_type = ctx.comp.q_init.hw_type;
|
|
q->hw_index = le32_to_cpu(ctx.comp.q_init.hw_index);
|
|
q->dbval = IONIC_DBELL_QID(q->hw_index);
|
|
|
|
dev_dbg(dev, "rxq->hw_type %d\n", q->hw_type);
|
|
dev_dbg(dev, "rxq->hw_index %d\n", q->hw_index);
|
|
|
|
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
|
|
netif_napi_add(lif->netdev, &qcq->napi, ionic_rx_napi,
|
|
NAPI_POLL_WEIGHT);
|
|
else
|
|
netif_napi_add(lif->netdev, &qcq->napi, ionic_txrx_napi,
|
|
NAPI_POLL_WEIGHT);
|
|
|
|
qcq->flags |= IONIC_QCQ_F_INITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ionic_lif_create_hwstamp_txq(struct ionic_lif *lif)
|
|
{
|
|
unsigned int num_desc, desc_sz, comp_sz, sg_desc_sz;
|
|
unsigned int txq_i, flags;
|
|
struct ionic_qcq *txq;
|
|
u64 features;
|
|
int err;
|
|
|
|
if (lif->hwstamp_txq)
|
|
return 0;
|
|
|
|
features = IONIC_Q_F_2X_CQ_DESC | IONIC_TXQ_F_HWSTAMP;
|
|
|
|
num_desc = IONIC_MIN_TXRX_DESC;
|
|
desc_sz = sizeof(struct ionic_txq_desc);
|
|
comp_sz = 2 * sizeof(struct ionic_txq_comp);
|
|
|
|
if (lif->qtype_info[IONIC_QTYPE_TXQ].version >= 1 &&
|
|
lif->qtype_info[IONIC_QTYPE_TXQ].sg_desc_sz == sizeof(struct ionic_txq_sg_desc_v1))
|
|
sg_desc_sz = sizeof(struct ionic_txq_sg_desc_v1);
|
|
else
|
|
sg_desc_sz = sizeof(struct ionic_txq_sg_desc);
|
|
|
|
txq_i = lif->ionic->ntxqs_per_lif;
|
|
flags = IONIC_QCQ_F_TX_STATS | IONIC_QCQ_F_SG;
|
|
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_TXQ, txq_i, "hwstamp_tx", flags,
|
|
num_desc, desc_sz, comp_sz, sg_desc_sz,
|
|
lif->kern_pid, &txq);
|
|
if (err)
|
|
goto err_qcq_alloc;
|
|
|
|
txq->q.features = features;
|
|
|
|
ionic_link_qcq_interrupts(lif->adminqcq, txq);
|
|
ionic_debugfs_add_qcq(lif, txq);
|
|
|
|
lif->hwstamp_txq = txq;
|
|
|
|
if (netif_running(lif->netdev)) {
|
|
err = ionic_lif_txq_init(lif, txq);
|
|
if (err)
|
|
goto err_qcq_init;
|
|
|
|
if (test_bit(IONIC_LIF_F_UP, lif->state)) {
|
|
err = ionic_qcq_enable(txq);
|
|
if (err)
|
|
goto err_qcq_enable;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_qcq_enable:
|
|
ionic_lif_qcq_deinit(lif, txq);
|
|
err_qcq_init:
|
|
lif->hwstamp_txq = NULL;
|
|
ionic_debugfs_del_qcq(txq);
|
|
ionic_qcq_free(lif, txq);
|
|
devm_kfree(lif->ionic->dev, txq);
|
|
err_qcq_alloc:
|
|
return err;
|
|
}
|
|
|
|
int ionic_lif_create_hwstamp_rxq(struct ionic_lif *lif)
|
|
{
|
|
unsigned int num_desc, desc_sz, comp_sz, sg_desc_sz;
|
|
unsigned int rxq_i, flags;
|
|
struct ionic_qcq *rxq;
|
|
u64 features;
|
|
int err;
|
|
|
|
if (lif->hwstamp_rxq)
|
|
return 0;
|
|
|
|
features = IONIC_Q_F_2X_CQ_DESC | IONIC_RXQ_F_HWSTAMP;
|
|
|
|
num_desc = IONIC_MIN_TXRX_DESC;
|
|
desc_sz = sizeof(struct ionic_rxq_desc);
|
|
comp_sz = 2 * sizeof(struct ionic_rxq_comp);
|
|
sg_desc_sz = sizeof(struct ionic_rxq_sg_desc);
|
|
|
|
rxq_i = lif->ionic->nrxqs_per_lif;
|
|
flags = IONIC_QCQ_F_RX_STATS | IONIC_QCQ_F_SG;
|
|
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_RXQ, rxq_i, "hwstamp_rx", flags,
|
|
num_desc, desc_sz, comp_sz, sg_desc_sz,
|
|
lif->kern_pid, &rxq);
|
|
if (err)
|
|
goto err_qcq_alloc;
|
|
|
|
rxq->q.features = features;
|
|
|
|
ionic_link_qcq_interrupts(lif->adminqcq, rxq);
|
|
ionic_debugfs_add_qcq(lif, rxq);
|
|
|
|
lif->hwstamp_rxq = rxq;
|
|
|
|
if (netif_running(lif->netdev)) {
|
|
err = ionic_lif_rxq_init(lif, rxq);
|
|
if (err)
|
|
goto err_qcq_init;
|
|
|
|
if (test_bit(IONIC_LIF_F_UP, lif->state)) {
|
|
ionic_rx_fill(&rxq->q);
|
|
err = ionic_qcq_enable(rxq);
|
|
if (err)
|
|
goto err_qcq_enable;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_qcq_enable:
|
|
ionic_lif_qcq_deinit(lif, rxq);
|
|
err_qcq_init:
|
|
lif->hwstamp_rxq = NULL;
|
|
ionic_debugfs_del_qcq(rxq);
|
|
ionic_qcq_free(lif, rxq);
|
|
devm_kfree(lif->ionic->dev, rxq);
|
|
err_qcq_alloc:
|
|
return err;
|
|
}
|
|
|
|
int ionic_lif_config_hwstamp_rxq_all(struct ionic_lif *lif, bool rx_all)
|
|
{
|
|
struct ionic_queue_params qparam;
|
|
|
|
ionic_init_queue_params(lif, &qparam);
|
|
|
|
if (rx_all)
|
|
qparam.rxq_features = IONIC_Q_F_2X_CQ_DESC | IONIC_RXQ_F_HWSTAMP;
|
|
else
|
|
qparam.rxq_features = 0;
|
|
|
|
/* if we're not running, just set the values and return */
|
|
if (!netif_running(lif->netdev)) {
|
|
lif->rxq_features = qparam.rxq_features;
|
|
return 0;
|
|
}
|
|
|
|
return ionic_reconfigure_queues(lif, &qparam);
|
|
}
|
|
|
|
int ionic_lif_set_hwstamp_txmode(struct ionic_lif *lif, u16 txstamp_mode)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_setattr = {
|
|
.opcode = IONIC_CMD_LIF_SETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_TXSTAMP,
|
|
.txstamp_mode = cpu_to_le16(txstamp_mode),
|
|
},
|
|
};
|
|
|
|
return ionic_adminq_post_wait(lif, &ctx);
|
|
}
|
|
|
|
static void ionic_lif_del_hwstamp_rxfilt(struct ionic_lif *lif)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.rx_filter_del = {
|
|
.opcode = IONIC_CMD_RX_FILTER_DEL,
|
|
.lif_index = cpu_to_le16(lif->index),
|
|
},
|
|
};
|
|
struct ionic_rx_filter *f;
|
|
u32 filter_id;
|
|
int err;
|
|
|
|
spin_lock_bh(&lif->rx_filters.lock);
|
|
|
|
f = ionic_rx_filter_rxsteer(lif);
|
|
if (!f) {
|
|
spin_unlock_bh(&lif->rx_filters.lock);
|
|
return;
|
|
}
|
|
|
|
filter_id = f->filter_id;
|
|
ionic_rx_filter_free(lif, f);
|
|
|
|
spin_unlock_bh(&lif->rx_filters.lock);
|
|
|
|
netdev_dbg(lif->netdev, "rx_filter del RXSTEER (id %d)\n", filter_id);
|
|
|
|
ctx.cmd.rx_filter_del.filter_id = cpu_to_le32(filter_id);
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err && err != -EEXIST)
|
|
netdev_dbg(lif->netdev, "failed to delete rx_filter RXSTEER (id %d)\n", filter_id);
|
|
}
|
|
|
|
static int ionic_lif_add_hwstamp_rxfilt(struct ionic_lif *lif, u64 pkt_class)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.rx_filter_add = {
|
|
.opcode = IONIC_CMD_RX_FILTER_ADD,
|
|
.lif_index = cpu_to_le16(lif->index),
|
|
.match = cpu_to_le16(IONIC_RX_FILTER_STEER_PKTCLASS),
|
|
.pkt_class = cpu_to_le64(pkt_class),
|
|
},
|
|
};
|
|
u8 qtype;
|
|
u32 qid;
|
|
int err;
|
|
|
|
if (!lif->hwstamp_rxq)
|
|
return -EINVAL;
|
|
|
|
qtype = lif->hwstamp_rxq->q.type;
|
|
ctx.cmd.rx_filter_add.qtype = qtype;
|
|
|
|
qid = lif->hwstamp_rxq->q.index;
|
|
ctx.cmd.rx_filter_add.qid = cpu_to_le32(qid);
|
|
|
|
netdev_dbg(lif->netdev, "rx_filter add RXSTEER\n");
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err && err != -EEXIST)
|
|
return err;
|
|
|
|
spin_lock_bh(&lif->rx_filters.lock);
|
|
err = ionic_rx_filter_save(lif, 0, qid, 0, &ctx, IONIC_FILTER_STATE_SYNCED);
|
|
spin_unlock_bh(&lif->rx_filters.lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
int ionic_lif_set_hwstamp_rxfilt(struct ionic_lif *lif, u64 pkt_class)
|
|
{
|
|
ionic_lif_del_hwstamp_rxfilt(lif);
|
|
|
|
if (!pkt_class)
|
|
return 0;
|
|
|
|
return ionic_lif_add_hwstamp_rxfilt(lif, pkt_class);
|
|
}
|
|
|
|
static bool ionic_notifyq_service(struct ionic_cq *cq,
|
|
struct ionic_cq_info *cq_info)
|
|
{
|
|
union ionic_notifyq_comp *comp = cq_info->cq_desc;
|
|
struct ionic_deferred_work *work;
|
|
struct net_device *netdev;
|
|
struct ionic_queue *q;
|
|
struct ionic_lif *lif;
|
|
u64 eid;
|
|
|
|
q = cq->bound_q;
|
|
lif = q->info[0].cb_arg;
|
|
netdev = lif->netdev;
|
|
eid = le64_to_cpu(comp->event.eid);
|
|
|
|
/* Have we run out of new completions to process? */
|
|
if ((s64)(eid - lif->last_eid) <= 0)
|
|
return false;
|
|
|
|
lif->last_eid = eid;
|
|
|
|
dev_dbg(lif->ionic->dev, "notifyq event:\n");
|
|
dynamic_hex_dump("event ", DUMP_PREFIX_OFFSET, 16, 1,
|
|
comp, sizeof(*comp), true);
|
|
|
|
switch (le16_to_cpu(comp->event.ecode)) {
|
|
case IONIC_EVENT_LINK_CHANGE:
|
|
ionic_link_status_check_request(lif, CAN_NOT_SLEEP);
|
|
break;
|
|
case IONIC_EVENT_RESET:
|
|
if (lif->ionic->idev.fw_status_ready &&
|
|
!test_bit(IONIC_LIF_F_FW_RESET, lif->state) &&
|
|
!test_and_set_bit(IONIC_LIF_F_FW_STOPPING, lif->state)) {
|
|
work = kzalloc(sizeof(*work), GFP_ATOMIC);
|
|
if (!work) {
|
|
netdev_err(lif->netdev, "Reset event dropped\n");
|
|
clear_bit(IONIC_LIF_F_FW_STOPPING, lif->state);
|
|
} else {
|
|
work->type = IONIC_DW_TYPE_LIF_RESET;
|
|
ionic_lif_deferred_enqueue(&lif->deferred, work);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
netdev_warn(netdev, "Notifyq event ecode=%d eid=%lld\n",
|
|
comp->event.ecode, eid);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ionic_adminq_service(struct ionic_cq *cq,
|
|
struct ionic_cq_info *cq_info)
|
|
{
|
|
struct ionic_admin_comp *comp = cq_info->cq_desc;
|
|
|
|
if (!color_match(comp->color, cq->done_color))
|
|
return false;
|
|
|
|
ionic_q_service(cq->bound_q, cq_info, le16_to_cpu(comp->comp_index));
|
|
|
|
return true;
|
|
}
|
|
|
|
static int ionic_adminq_napi(struct napi_struct *napi, int budget)
|
|
{
|
|
struct ionic_intr_info *intr = napi_to_cq(napi)->bound_intr;
|
|
struct ionic_lif *lif = napi_to_cq(napi)->lif;
|
|
struct ionic_dev *idev = &lif->ionic->idev;
|
|
unsigned long irqflags;
|
|
unsigned int flags = 0;
|
|
int rx_work = 0;
|
|
int tx_work = 0;
|
|
int n_work = 0;
|
|
int a_work = 0;
|
|
int work_done;
|
|
int credits;
|
|
|
|
if (lif->notifyqcq && lif->notifyqcq->flags & IONIC_QCQ_F_INITED)
|
|
n_work = ionic_cq_service(&lif->notifyqcq->cq, budget,
|
|
ionic_notifyq_service, NULL, NULL);
|
|
|
|
spin_lock_irqsave(&lif->adminq_lock, irqflags);
|
|
if (lif->adminqcq && lif->adminqcq->flags & IONIC_QCQ_F_INITED)
|
|
a_work = ionic_cq_service(&lif->adminqcq->cq, budget,
|
|
ionic_adminq_service, NULL, NULL);
|
|
spin_unlock_irqrestore(&lif->adminq_lock, irqflags);
|
|
|
|
if (lif->hwstamp_rxq)
|
|
rx_work = ionic_cq_service(&lif->hwstamp_rxq->cq, budget,
|
|
ionic_rx_service, NULL, NULL);
|
|
|
|
if (lif->hwstamp_txq)
|
|
tx_work = ionic_cq_service(&lif->hwstamp_txq->cq, budget,
|
|
ionic_tx_service, NULL, NULL);
|
|
|
|
work_done = max(max(n_work, a_work), max(rx_work, tx_work));
|
|
if (work_done < budget && napi_complete_done(napi, work_done)) {
|
|
flags |= IONIC_INTR_CRED_UNMASK;
|
|
intr->rearm_count++;
|
|
}
|
|
|
|
if (work_done || flags) {
|
|
flags |= IONIC_INTR_CRED_RESET_COALESCE;
|
|
credits = n_work + a_work + rx_work + tx_work;
|
|
ionic_intr_credits(idev->intr_ctrl, intr->index, credits, flags);
|
|
}
|
|
|
|
return work_done;
|
|
}
|
|
|
|
void ionic_get_stats64(struct net_device *netdev,
|
|
struct rtnl_link_stats64 *ns)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic_lif_stats *ls;
|
|
|
|
memset(ns, 0, sizeof(*ns));
|
|
ls = &lif->info->stats;
|
|
|
|
ns->rx_packets = le64_to_cpu(ls->rx_ucast_packets) +
|
|
le64_to_cpu(ls->rx_mcast_packets) +
|
|
le64_to_cpu(ls->rx_bcast_packets);
|
|
|
|
ns->tx_packets = le64_to_cpu(ls->tx_ucast_packets) +
|
|
le64_to_cpu(ls->tx_mcast_packets) +
|
|
le64_to_cpu(ls->tx_bcast_packets);
|
|
|
|
ns->rx_bytes = le64_to_cpu(ls->rx_ucast_bytes) +
|
|
le64_to_cpu(ls->rx_mcast_bytes) +
|
|
le64_to_cpu(ls->rx_bcast_bytes);
|
|
|
|
ns->tx_bytes = le64_to_cpu(ls->tx_ucast_bytes) +
|
|
le64_to_cpu(ls->tx_mcast_bytes) +
|
|
le64_to_cpu(ls->tx_bcast_bytes);
|
|
|
|
ns->rx_dropped = le64_to_cpu(ls->rx_ucast_drop_packets) +
|
|
le64_to_cpu(ls->rx_mcast_drop_packets) +
|
|
le64_to_cpu(ls->rx_bcast_drop_packets);
|
|
|
|
ns->tx_dropped = le64_to_cpu(ls->tx_ucast_drop_packets) +
|
|
le64_to_cpu(ls->tx_mcast_drop_packets) +
|
|
le64_to_cpu(ls->tx_bcast_drop_packets);
|
|
|
|
ns->multicast = le64_to_cpu(ls->rx_mcast_packets);
|
|
|
|
ns->rx_over_errors = le64_to_cpu(ls->rx_queue_empty);
|
|
|
|
ns->rx_missed_errors = le64_to_cpu(ls->rx_dma_error) +
|
|
le64_to_cpu(ls->rx_queue_disabled) +
|
|
le64_to_cpu(ls->rx_desc_fetch_error) +
|
|
le64_to_cpu(ls->rx_desc_data_error);
|
|
|
|
ns->tx_aborted_errors = le64_to_cpu(ls->tx_dma_error) +
|
|
le64_to_cpu(ls->tx_queue_disabled) +
|
|
le64_to_cpu(ls->tx_desc_fetch_error) +
|
|
le64_to_cpu(ls->tx_desc_data_error);
|
|
|
|
ns->rx_errors = ns->rx_over_errors +
|
|
ns->rx_missed_errors;
|
|
|
|
ns->tx_errors = ns->tx_aborted_errors;
|
|
}
|
|
|
|
static int ionic_addr_add(struct net_device *netdev, const u8 *addr)
|
|
{
|
|
return ionic_lif_list_addr(netdev_priv(netdev), addr, ADD_ADDR);
|
|
}
|
|
|
|
static int ionic_addr_del(struct net_device *netdev, const u8 *addr)
|
|
{
|
|
/* Don't delete our own address from the uc list */
|
|
if (ether_addr_equal(addr, netdev->dev_addr))
|
|
return 0;
|
|
|
|
return ionic_lif_list_addr(netdev_priv(netdev), addr, DEL_ADDR);
|
|
}
|
|
|
|
void ionic_lif_rx_mode(struct ionic_lif *lif)
|
|
{
|
|
struct net_device *netdev = lif->netdev;
|
|
unsigned int nfilters;
|
|
unsigned int nd_flags;
|
|
char buf[128];
|
|
u16 rx_mode;
|
|
int i;
|
|
#define REMAIN(__x) (sizeof(buf) - (__x))
|
|
|
|
mutex_lock(&lif->config_lock);
|
|
|
|
/* grab the flags once for local use */
|
|
nd_flags = netdev->flags;
|
|
|
|
rx_mode = IONIC_RX_MODE_F_UNICAST;
|
|
rx_mode |= (nd_flags & IFF_MULTICAST) ? IONIC_RX_MODE_F_MULTICAST : 0;
|
|
rx_mode |= (nd_flags & IFF_BROADCAST) ? IONIC_RX_MODE_F_BROADCAST : 0;
|
|
rx_mode |= (nd_flags & IFF_PROMISC) ? IONIC_RX_MODE_F_PROMISC : 0;
|
|
rx_mode |= (nd_flags & IFF_ALLMULTI) ? IONIC_RX_MODE_F_ALLMULTI : 0;
|
|
|
|
/* sync the filters */
|
|
ionic_rx_filter_sync(lif);
|
|
|
|
/* check for overflow state
|
|
* if so, we track that we overflowed and enable NIC PROMISC
|
|
* else if the overflow is set and not needed
|
|
* we remove our overflow flag and check the netdev flags
|
|
* to see if we can disable NIC PROMISC
|
|
*/
|
|
nfilters = le32_to_cpu(lif->identity->eth.max_ucast_filters);
|
|
|
|
if (((lif->nucast + lif->nmcast) >= nfilters) ||
|
|
(lif->max_vlans && lif->nvlans >= lif->max_vlans)) {
|
|
rx_mode |= IONIC_RX_MODE_F_PROMISC;
|
|
rx_mode |= IONIC_RX_MODE_F_ALLMULTI;
|
|
} else {
|
|
if (!(nd_flags & IFF_PROMISC))
|
|
rx_mode &= ~IONIC_RX_MODE_F_PROMISC;
|
|
if (!(nd_flags & IFF_ALLMULTI))
|
|
rx_mode &= ~IONIC_RX_MODE_F_ALLMULTI;
|
|
}
|
|
|
|
i = scnprintf(buf, sizeof(buf), "rx_mode 0x%04x -> 0x%04x:",
|
|
lif->rx_mode, rx_mode);
|
|
if (rx_mode & IONIC_RX_MODE_F_UNICAST)
|
|
i += scnprintf(&buf[i], REMAIN(i), " RX_MODE_F_UNICAST");
|
|
if (rx_mode & IONIC_RX_MODE_F_MULTICAST)
|
|
i += scnprintf(&buf[i], REMAIN(i), " RX_MODE_F_MULTICAST");
|
|
if (rx_mode & IONIC_RX_MODE_F_BROADCAST)
|
|
i += scnprintf(&buf[i], REMAIN(i), " RX_MODE_F_BROADCAST");
|
|
if (rx_mode & IONIC_RX_MODE_F_PROMISC)
|
|
i += scnprintf(&buf[i], REMAIN(i), " RX_MODE_F_PROMISC");
|
|
if (rx_mode & IONIC_RX_MODE_F_ALLMULTI)
|
|
i += scnprintf(&buf[i], REMAIN(i), " RX_MODE_F_ALLMULTI");
|
|
if (rx_mode & IONIC_RX_MODE_F_RDMA_SNIFFER)
|
|
i += scnprintf(&buf[i], REMAIN(i), " RX_MODE_F_RDMA_SNIFFER");
|
|
netdev_dbg(netdev, "lif%d %s\n", lif->index, buf);
|
|
|
|
if (lif->rx_mode != rx_mode) {
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.rx_mode_set = {
|
|
.opcode = IONIC_CMD_RX_MODE_SET,
|
|
.lif_index = cpu_to_le16(lif->index),
|
|
},
|
|
};
|
|
int err;
|
|
|
|
ctx.cmd.rx_mode_set.rx_mode = cpu_to_le16(rx_mode);
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
netdev_warn(netdev, "set rx_mode 0x%04x failed: %d\n",
|
|
rx_mode, err);
|
|
else
|
|
lif->rx_mode = rx_mode;
|
|
}
|
|
|
|
mutex_unlock(&lif->config_lock);
|
|
}
|
|
|
|
static void ionic_ndo_set_rx_mode(struct net_device *netdev)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic_deferred_work *work;
|
|
|
|
/* Sync the kernel filter list with the driver filter list */
|
|
__dev_uc_sync(netdev, ionic_addr_add, ionic_addr_del);
|
|
__dev_mc_sync(netdev, ionic_addr_add, ionic_addr_del);
|
|
|
|
/* Shove off the rest of the rxmode work to the work task
|
|
* which will include syncing the filters to the firmware.
|
|
*/
|
|
work = kzalloc(sizeof(*work), GFP_ATOMIC);
|
|
if (!work) {
|
|
netdev_err(lif->netdev, "rxmode change dropped\n");
|
|
return;
|
|
}
|
|
work->type = IONIC_DW_TYPE_RX_MODE;
|
|
netdev_dbg(lif->netdev, "deferred: rx_mode\n");
|
|
ionic_lif_deferred_enqueue(&lif->deferred, work);
|
|
}
|
|
|
|
static __le64 ionic_netdev_features_to_nic(netdev_features_t features)
|
|
{
|
|
u64 wanted = 0;
|
|
|
|
if (features & NETIF_F_HW_VLAN_CTAG_TX)
|
|
wanted |= IONIC_ETH_HW_VLAN_TX_TAG;
|
|
if (features & NETIF_F_HW_VLAN_CTAG_RX)
|
|
wanted |= IONIC_ETH_HW_VLAN_RX_STRIP;
|
|
if (features & NETIF_F_HW_VLAN_CTAG_FILTER)
|
|
wanted |= IONIC_ETH_HW_VLAN_RX_FILTER;
|
|
if (features & NETIF_F_RXHASH)
|
|
wanted |= IONIC_ETH_HW_RX_HASH;
|
|
if (features & NETIF_F_RXCSUM)
|
|
wanted |= IONIC_ETH_HW_RX_CSUM;
|
|
if (features & NETIF_F_SG)
|
|
wanted |= IONIC_ETH_HW_TX_SG;
|
|
if (features & NETIF_F_HW_CSUM)
|
|
wanted |= IONIC_ETH_HW_TX_CSUM;
|
|
if (features & NETIF_F_TSO)
|
|
wanted |= IONIC_ETH_HW_TSO;
|
|
if (features & NETIF_F_TSO6)
|
|
wanted |= IONIC_ETH_HW_TSO_IPV6;
|
|
if (features & NETIF_F_TSO_ECN)
|
|
wanted |= IONIC_ETH_HW_TSO_ECN;
|
|
if (features & NETIF_F_GSO_GRE)
|
|
wanted |= IONIC_ETH_HW_TSO_GRE;
|
|
if (features & NETIF_F_GSO_GRE_CSUM)
|
|
wanted |= IONIC_ETH_HW_TSO_GRE_CSUM;
|
|
if (features & NETIF_F_GSO_IPXIP4)
|
|
wanted |= IONIC_ETH_HW_TSO_IPXIP4;
|
|
if (features & NETIF_F_GSO_IPXIP6)
|
|
wanted |= IONIC_ETH_HW_TSO_IPXIP6;
|
|
if (features & NETIF_F_GSO_UDP_TUNNEL)
|
|
wanted |= IONIC_ETH_HW_TSO_UDP;
|
|
if (features & NETIF_F_GSO_UDP_TUNNEL_CSUM)
|
|
wanted |= IONIC_ETH_HW_TSO_UDP_CSUM;
|
|
|
|
return cpu_to_le64(wanted);
|
|
}
|
|
|
|
static int ionic_set_nic_features(struct ionic_lif *lif,
|
|
netdev_features_t features)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_setattr = {
|
|
.opcode = IONIC_CMD_LIF_SETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_FEATURES,
|
|
},
|
|
};
|
|
u64 vlan_flags = IONIC_ETH_HW_VLAN_TX_TAG |
|
|
IONIC_ETH_HW_VLAN_RX_STRIP |
|
|
IONIC_ETH_HW_VLAN_RX_FILTER;
|
|
u64 old_hw_features;
|
|
int err;
|
|
|
|
ctx.cmd.lif_setattr.features = ionic_netdev_features_to_nic(features);
|
|
|
|
if (lif->phc)
|
|
ctx.cmd.lif_setattr.features |= cpu_to_le64(IONIC_ETH_HW_TIMESTAMP);
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
return err;
|
|
|
|
old_hw_features = lif->hw_features;
|
|
lif->hw_features = le64_to_cpu(ctx.cmd.lif_setattr.features &
|
|
ctx.comp.lif_setattr.features);
|
|
|
|
if ((old_hw_features ^ lif->hw_features) & IONIC_ETH_HW_RX_HASH)
|
|
ionic_lif_rss_config(lif, lif->rss_types, NULL, NULL);
|
|
|
|
if ((vlan_flags & le64_to_cpu(ctx.cmd.lif_setattr.features)) &&
|
|
!(vlan_flags & le64_to_cpu(ctx.comp.lif_setattr.features)))
|
|
dev_info_once(lif->ionic->dev, "NIC is not supporting vlan offload, likely in SmartNIC mode\n");
|
|
|
|
if (lif->hw_features & IONIC_ETH_HW_VLAN_TX_TAG)
|
|
dev_dbg(dev, "feature ETH_HW_VLAN_TX_TAG\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_VLAN_RX_STRIP)
|
|
dev_dbg(dev, "feature ETH_HW_VLAN_RX_STRIP\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_VLAN_RX_FILTER)
|
|
dev_dbg(dev, "feature ETH_HW_VLAN_RX_FILTER\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_RX_HASH)
|
|
dev_dbg(dev, "feature ETH_HW_RX_HASH\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TX_SG)
|
|
dev_dbg(dev, "feature ETH_HW_TX_SG\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TX_CSUM)
|
|
dev_dbg(dev, "feature ETH_HW_TX_CSUM\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_RX_CSUM)
|
|
dev_dbg(dev, "feature ETH_HW_RX_CSUM\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO)
|
|
dev_dbg(dev, "feature ETH_HW_TSO\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_IPV6)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_IPV6\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_ECN)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_ECN\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_GRE)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_GRE\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_GRE_CSUM)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_GRE_CSUM\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_IPXIP4)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_IPXIP4\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_IPXIP6)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_IPXIP6\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_UDP)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_UDP\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_UDP_CSUM)
|
|
dev_dbg(dev, "feature ETH_HW_TSO_UDP_CSUM\n");
|
|
if (lif->hw_features & IONIC_ETH_HW_TIMESTAMP)
|
|
dev_dbg(dev, "feature ETH_HW_TIMESTAMP\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_init_nic_features(struct ionic_lif *lif)
|
|
{
|
|
struct net_device *netdev = lif->netdev;
|
|
netdev_features_t features;
|
|
int err;
|
|
|
|
/* set up what we expect to support by default */
|
|
features = NETIF_F_HW_VLAN_CTAG_TX |
|
|
NETIF_F_HW_VLAN_CTAG_RX |
|
|
NETIF_F_HW_VLAN_CTAG_FILTER |
|
|
NETIF_F_SG |
|
|
NETIF_F_HW_CSUM |
|
|
NETIF_F_RXCSUM |
|
|
NETIF_F_TSO |
|
|
NETIF_F_TSO6 |
|
|
NETIF_F_TSO_ECN;
|
|
|
|
if (lif->nxqs > 1)
|
|
features |= NETIF_F_RXHASH;
|
|
|
|
err = ionic_set_nic_features(lif, features);
|
|
if (err)
|
|
return err;
|
|
|
|
/* tell the netdev what we actually can support */
|
|
netdev->features |= NETIF_F_HIGHDMA;
|
|
|
|
if (lif->hw_features & IONIC_ETH_HW_VLAN_TX_TAG)
|
|
netdev->hw_features |= NETIF_F_HW_VLAN_CTAG_TX;
|
|
if (lif->hw_features & IONIC_ETH_HW_VLAN_RX_STRIP)
|
|
netdev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX;
|
|
if (lif->hw_features & IONIC_ETH_HW_VLAN_RX_FILTER)
|
|
netdev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER;
|
|
if (lif->hw_features & IONIC_ETH_HW_RX_HASH)
|
|
netdev->hw_features |= NETIF_F_RXHASH;
|
|
if (lif->hw_features & IONIC_ETH_HW_TX_SG)
|
|
netdev->hw_features |= NETIF_F_SG;
|
|
|
|
if (lif->hw_features & IONIC_ETH_HW_TX_CSUM)
|
|
netdev->hw_enc_features |= NETIF_F_HW_CSUM;
|
|
if (lif->hw_features & IONIC_ETH_HW_RX_CSUM)
|
|
netdev->hw_enc_features |= NETIF_F_RXCSUM;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO)
|
|
netdev->hw_enc_features |= NETIF_F_TSO;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_IPV6)
|
|
netdev->hw_enc_features |= NETIF_F_TSO6;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_ECN)
|
|
netdev->hw_enc_features |= NETIF_F_TSO_ECN;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_GRE)
|
|
netdev->hw_enc_features |= NETIF_F_GSO_GRE;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_GRE_CSUM)
|
|
netdev->hw_enc_features |= NETIF_F_GSO_GRE_CSUM;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_IPXIP4)
|
|
netdev->hw_enc_features |= NETIF_F_GSO_IPXIP4;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_IPXIP6)
|
|
netdev->hw_enc_features |= NETIF_F_GSO_IPXIP6;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_UDP)
|
|
netdev->hw_enc_features |= NETIF_F_GSO_UDP_TUNNEL;
|
|
if (lif->hw_features & IONIC_ETH_HW_TSO_UDP_CSUM)
|
|
netdev->hw_enc_features |= NETIF_F_GSO_UDP_TUNNEL_CSUM;
|
|
|
|
netdev->hw_features |= netdev->hw_enc_features;
|
|
netdev->features |= netdev->hw_features;
|
|
netdev->vlan_features |= netdev->features & ~NETIF_F_VLAN_FEATURES;
|
|
|
|
netdev->priv_flags |= IFF_UNICAST_FLT |
|
|
IFF_LIVE_ADDR_CHANGE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_set_features(struct net_device *netdev,
|
|
netdev_features_t features)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
int err;
|
|
|
|
netdev_dbg(netdev, "%s: lif->features=0x%08llx new_features=0x%08llx\n",
|
|
__func__, (u64)lif->netdev->features, (u64)features);
|
|
|
|
err = ionic_set_nic_features(lif, features);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ionic_set_attr_mac(struct ionic_lif *lif, u8 *mac)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_setattr = {
|
|
.opcode = IONIC_CMD_LIF_SETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_MAC,
|
|
},
|
|
};
|
|
|
|
ether_addr_copy(ctx.cmd.lif_setattr.mac, mac);
|
|
return ionic_adminq_post_wait(lif, &ctx);
|
|
}
|
|
|
|
static int ionic_get_attr_mac(struct ionic_lif *lif, u8 *mac_addr)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_getattr = {
|
|
.opcode = IONIC_CMD_LIF_GETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_MAC,
|
|
},
|
|
};
|
|
int err;
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
return err;
|
|
|
|
ether_addr_copy(mac_addr, ctx.comp.lif_getattr.mac);
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_program_mac(struct ionic_lif *lif, u8 *mac)
|
|
{
|
|
u8 get_mac[ETH_ALEN];
|
|
int err;
|
|
|
|
err = ionic_set_attr_mac(lif, mac);
|
|
if (err)
|
|
return err;
|
|
|
|
err = ionic_get_attr_mac(lif, get_mac);
|
|
if (err)
|
|
return err;
|
|
|
|
/* To deal with older firmware that silently ignores the set attr mac:
|
|
* doesn't actually change the mac and doesn't return an error, so we
|
|
* do the get attr to verify whether or not the set actually happened
|
|
*/
|
|
if (!ether_addr_equal(get_mac, mac))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_set_mac_address(struct net_device *netdev, void *sa)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct sockaddr *addr = sa;
|
|
u8 *mac;
|
|
int err;
|
|
|
|
mac = (u8 *)addr->sa_data;
|
|
if (ether_addr_equal(netdev->dev_addr, mac))
|
|
return 0;
|
|
|
|
err = ionic_program_mac(lif, mac);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (err > 0)
|
|
netdev_dbg(netdev, "%s: SET and GET ATTR Mac are not equal-due to old FW running\n",
|
|
__func__);
|
|
|
|
err = eth_prepare_mac_addr_change(netdev, addr);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!is_zero_ether_addr(netdev->dev_addr)) {
|
|
netdev_info(netdev, "deleting mac addr %pM\n",
|
|
netdev->dev_addr);
|
|
ionic_lif_addr_del(netdev_priv(netdev), netdev->dev_addr);
|
|
}
|
|
|
|
eth_commit_mac_addr_change(netdev, addr);
|
|
netdev_info(netdev, "updating mac addr %pM\n", mac);
|
|
|
|
return ionic_lif_addr_add(netdev_priv(netdev), mac);
|
|
}
|
|
|
|
static void ionic_stop_queues_reconfig(struct ionic_lif *lif)
|
|
{
|
|
/* Stop and clean the queues before reconfiguration */
|
|
netif_device_detach(lif->netdev);
|
|
ionic_stop_queues(lif);
|
|
ionic_txrx_deinit(lif);
|
|
}
|
|
|
|
static int ionic_start_queues_reconfig(struct ionic_lif *lif)
|
|
{
|
|
int err;
|
|
|
|
/* Re-init the queues after reconfiguration */
|
|
|
|
/* The only way txrx_init can fail here is if communication
|
|
* with FW is suddenly broken. There's not much we can do
|
|
* at this point - error messages have already been printed,
|
|
* so we can continue on and the user can eventually do a
|
|
* DOWN and UP to try to reset and clear the issue.
|
|
*/
|
|
err = ionic_txrx_init(lif);
|
|
ionic_link_status_check_request(lif, CAN_NOT_SLEEP);
|
|
netif_device_attach(lif->netdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ionic_change_mtu(struct net_device *netdev, int new_mtu)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_setattr = {
|
|
.opcode = IONIC_CMD_LIF_SETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_MTU,
|
|
.mtu = cpu_to_le32(new_mtu),
|
|
},
|
|
};
|
|
int err;
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
return err;
|
|
|
|
/* if we're not running, nothing more to do */
|
|
if (!netif_running(netdev)) {
|
|
netdev->mtu = new_mtu;
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&lif->queue_lock);
|
|
ionic_stop_queues_reconfig(lif);
|
|
netdev->mtu = new_mtu;
|
|
err = ionic_start_queues_reconfig(lif);
|
|
mutex_unlock(&lif->queue_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ionic_tx_timeout_work(struct work_struct *ws)
|
|
{
|
|
struct ionic_lif *lif = container_of(ws, struct ionic_lif, tx_timeout_work);
|
|
|
|
if (test_bit(IONIC_LIF_F_FW_RESET, lif->state))
|
|
return;
|
|
|
|
/* if we were stopped before this scheduled job was launched,
|
|
* don't bother the queues as they are already stopped.
|
|
*/
|
|
if (!netif_running(lif->netdev))
|
|
return;
|
|
|
|
mutex_lock(&lif->queue_lock);
|
|
ionic_stop_queues_reconfig(lif);
|
|
ionic_start_queues_reconfig(lif);
|
|
mutex_unlock(&lif->queue_lock);
|
|
}
|
|
|
|
static void ionic_tx_timeout(struct net_device *netdev, unsigned int txqueue)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
|
|
netdev_info(lif->netdev, "Tx Timeout triggered - txq %d\n", txqueue);
|
|
schedule_work(&lif->tx_timeout_work);
|
|
}
|
|
|
|
static int ionic_vlan_rx_add_vid(struct net_device *netdev, __be16 proto,
|
|
u16 vid)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
int err;
|
|
|
|
err = ionic_lif_vlan_add(lif, vid);
|
|
if (err)
|
|
return err;
|
|
|
|
ionic_lif_rx_mode(lif);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_vlan_rx_kill_vid(struct net_device *netdev, __be16 proto,
|
|
u16 vid)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
int err;
|
|
|
|
err = ionic_lif_vlan_del(lif, vid);
|
|
if (err)
|
|
return err;
|
|
|
|
ionic_lif_rx_mode(lif);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ionic_lif_rss_config(struct ionic_lif *lif, const u16 types,
|
|
const u8 *key, const u32 *indir)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_setattr = {
|
|
.opcode = IONIC_CMD_LIF_SETATTR,
|
|
.attr = IONIC_LIF_ATTR_RSS,
|
|
.rss.addr = cpu_to_le64(lif->rss_ind_tbl_pa),
|
|
},
|
|
};
|
|
unsigned int i, tbl_sz;
|
|
|
|
if (lif->hw_features & IONIC_ETH_HW_RX_HASH) {
|
|
lif->rss_types = types;
|
|
ctx.cmd.lif_setattr.rss.types = cpu_to_le16(types);
|
|
}
|
|
|
|
if (key)
|
|
memcpy(lif->rss_hash_key, key, IONIC_RSS_HASH_KEY_SIZE);
|
|
|
|
if (indir) {
|
|
tbl_sz = le16_to_cpu(lif->ionic->ident.lif.eth.rss_ind_tbl_sz);
|
|
for (i = 0; i < tbl_sz; i++)
|
|
lif->rss_ind_tbl[i] = indir[i];
|
|
}
|
|
|
|
memcpy(ctx.cmd.lif_setattr.rss.key, lif->rss_hash_key,
|
|
IONIC_RSS_HASH_KEY_SIZE);
|
|
|
|
return ionic_adminq_post_wait(lif, &ctx);
|
|
}
|
|
|
|
static int ionic_lif_rss_init(struct ionic_lif *lif)
|
|
{
|
|
unsigned int tbl_sz;
|
|
unsigned int i;
|
|
|
|
lif->rss_types = IONIC_RSS_TYPE_IPV4 |
|
|
IONIC_RSS_TYPE_IPV4_TCP |
|
|
IONIC_RSS_TYPE_IPV4_UDP |
|
|
IONIC_RSS_TYPE_IPV6 |
|
|
IONIC_RSS_TYPE_IPV6_TCP |
|
|
IONIC_RSS_TYPE_IPV6_UDP;
|
|
|
|
/* Fill indirection table with 'default' values */
|
|
tbl_sz = le16_to_cpu(lif->ionic->ident.lif.eth.rss_ind_tbl_sz);
|
|
for (i = 0; i < tbl_sz; i++)
|
|
lif->rss_ind_tbl[i] = ethtool_rxfh_indir_default(i, lif->nxqs);
|
|
|
|
return ionic_lif_rss_config(lif, lif->rss_types, NULL, NULL);
|
|
}
|
|
|
|
static void ionic_lif_rss_deinit(struct ionic_lif *lif)
|
|
{
|
|
int tbl_sz;
|
|
|
|
tbl_sz = le16_to_cpu(lif->ionic->ident.lif.eth.rss_ind_tbl_sz);
|
|
memset(lif->rss_ind_tbl, 0, tbl_sz);
|
|
memset(lif->rss_hash_key, 0, IONIC_RSS_HASH_KEY_SIZE);
|
|
|
|
ionic_lif_rss_config(lif, 0x0, NULL, NULL);
|
|
}
|
|
|
|
static void ionic_lif_quiesce(struct ionic_lif *lif)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_setattr = {
|
|
.opcode = IONIC_CMD_LIF_SETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_STATE,
|
|
.state = IONIC_LIF_QUIESCE,
|
|
},
|
|
};
|
|
int err;
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
netdev_dbg(lif->netdev, "lif quiesce failed %d\n", err);
|
|
}
|
|
|
|
static void ionic_txrx_disable(struct ionic_lif *lif)
|
|
{
|
|
unsigned int i;
|
|
int err = 0;
|
|
|
|
if (lif->txqcqs) {
|
|
for (i = 0; i < lif->nxqs; i++)
|
|
err = ionic_qcq_disable(lif, lif->txqcqs[i], err);
|
|
}
|
|
|
|
if (lif->hwstamp_txq)
|
|
err = ionic_qcq_disable(lif, lif->hwstamp_txq, err);
|
|
|
|
if (lif->rxqcqs) {
|
|
for (i = 0; i < lif->nxqs; i++)
|
|
err = ionic_qcq_disable(lif, lif->rxqcqs[i], err);
|
|
}
|
|
|
|
if (lif->hwstamp_rxq)
|
|
err = ionic_qcq_disable(lif, lif->hwstamp_rxq, err);
|
|
|
|
ionic_lif_quiesce(lif);
|
|
}
|
|
|
|
static void ionic_txrx_deinit(struct ionic_lif *lif)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (lif->txqcqs) {
|
|
for (i = 0; i < lif->nxqs && lif->txqcqs[i]; i++) {
|
|
ionic_lif_qcq_deinit(lif, lif->txqcqs[i]);
|
|
ionic_tx_flush(&lif->txqcqs[i]->cq);
|
|
ionic_tx_empty(&lif->txqcqs[i]->q);
|
|
}
|
|
}
|
|
|
|
if (lif->rxqcqs) {
|
|
for (i = 0; i < lif->nxqs && lif->rxqcqs[i]; i++) {
|
|
ionic_lif_qcq_deinit(lif, lif->rxqcqs[i]);
|
|
ionic_rx_empty(&lif->rxqcqs[i]->q);
|
|
}
|
|
}
|
|
lif->rx_mode = 0;
|
|
|
|
if (lif->hwstamp_txq) {
|
|
ionic_lif_qcq_deinit(lif, lif->hwstamp_txq);
|
|
ionic_tx_flush(&lif->hwstamp_txq->cq);
|
|
ionic_tx_empty(&lif->hwstamp_txq->q);
|
|
}
|
|
|
|
if (lif->hwstamp_rxq) {
|
|
ionic_lif_qcq_deinit(lif, lif->hwstamp_rxq);
|
|
ionic_rx_empty(&lif->hwstamp_rxq->q);
|
|
}
|
|
}
|
|
|
|
static void ionic_txrx_free(struct ionic_lif *lif)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (lif->txqcqs) {
|
|
for (i = 0; i < lif->ionic->ntxqs_per_lif && lif->txqcqs[i]; i++) {
|
|
ionic_qcq_free(lif, lif->txqcqs[i]);
|
|
devm_kfree(lif->ionic->dev, lif->txqcqs[i]);
|
|
lif->txqcqs[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (lif->rxqcqs) {
|
|
for (i = 0; i < lif->ionic->nrxqs_per_lif && lif->rxqcqs[i]; i++) {
|
|
ionic_qcq_free(lif, lif->rxqcqs[i]);
|
|
devm_kfree(lif->ionic->dev, lif->rxqcqs[i]);
|
|
lif->rxqcqs[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (lif->hwstamp_txq) {
|
|
ionic_qcq_free(lif, lif->hwstamp_txq);
|
|
devm_kfree(lif->ionic->dev, lif->hwstamp_txq);
|
|
lif->hwstamp_txq = NULL;
|
|
}
|
|
|
|
if (lif->hwstamp_rxq) {
|
|
ionic_qcq_free(lif, lif->hwstamp_rxq);
|
|
devm_kfree(lif->ionic->dev, lif->hwstamp_rxq);
|
|
lif->hwstamp_rxq = NULL;
|
|
}
|
|
}
|
|
|
|
static int ionic_txrx_alloc(struct ionic_lif *lif)
|
|
{
|
|
unsigned int comp_sz, desc_sz, num_desc, sg_desc_sz;
|
|
unsigned int flags, i;
|
|
int err = 0;
|
|
|
|
num_desc = lif->ntxq_descs;
|
|
desc_sz = sizeof(struct ionic_txq_desc);
|
|
comp_sz = sizeof(struct ionic_txq_comp);
|
|
|
|
if (lif->qtype_info[IONIC_QTYPE_TXQ].version >= 1 &&
|
|
lif->qtype_info[IONIC_QTYPE_TXQ].sg_desc_sz ==
|
|
sizeof(struct ionic_txq_sg_desc_v1))
|
|
sg_desc_sz = sizeof(struct ionic_txq_sg_desc_v1);
|
|
else
|
|
sg_desc_sz = sizeof(struct ionic_txq_sg_desc);
|
|
|
|
flags = IONIC_QCQ_F_TX_STATS | IONIC_QCQ_F_SG;
|
|
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
|
|
flags |= IONIC_QCQ_F_INTR;
|
|
for (i = 0; i < lif->nxqs; i++) {
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_TXQ, i, "tx", flags,
|
|
num_desc, desc_sz, comp_sz, sg_desc_sz,
|
|
lif->kern_pid, &lif->txqcqs[i]);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
if (flags & IONIC_QCQ_F_INTR) {
|
|
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
|
|
lif->txqcqs[i]->intr.index,
|
|
lif->tx_coalesce_hw);
|
|
if (test_bit(IONIC_LIF_F_TX_DIM_INTR, lif->state))
|
|
lif->txqcqs[i]->intr.dim_coal_hw = lif->tx_coalesce_hw;
|
|
}
|
|
|
|
ionic_debugfs_add_qcq(lif, lif->txqcqs[i]);
|
|
}
|
|
|
|
flags = IONIC_QCQ_F_RX_STATS | IONIC_QCQ_F_SG | IONIC_QCQ_F_INTR;
|
|
|
|
num_desc = lif->nrxq_descs;
|
|
desc_sz = sizeof(struct ionic_rxq_desc);
|
|
comp_sz = sizeof(struct ionic_rxq_comp);
|
|
sg_desc_sz = sizeof(struct ionic_rxq_sg_desc);
|
|
|
|
if (lif->rxq_features & IONIC_Q_F_2X_CQ_DESC)
|
|
comp_sz *= 2;
|
|
|
|
for (i = 0; i < lif->nxqs; i++) {
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_RXQ, i, "rx", flags,
|
|
num_desc, desc_sz, comp_sz, sg_desc_sz,
|
|
lif->kern_pid, &lif->rxqcqs[i]);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
lif->rxqcqs[i]->q.features = lif->rxq_features;
|
|
|
|
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
|
|
lif->rxqcqs[i]->intr.index,
|
|
lif->rx_coalesce_hw);
|
|
if (test_bit(IONIC_LIF_F_RX_DIM_INTR, lif->state))
|
|
lif->rxqcqs[i]->intr.dim_coal_hw = lif->rx_coalesce_hw;
|
|
|
|
if (!test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
|
|
ionic_link_qcq_interrupts(lif->rxqcqs[i],
|
|
lif->txqcqs[i]);
|
|
|
|
ionic_debugfs_add_qcq(lif, lif->rxqcqs[i]);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
ionic_txrx_free(lif);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ionic_txrx_init(struct ionic_lif *lif)
|
|
{
|
|
unsigned int i;
|
|
int err;
|
|
|
|
for (i = 0; i < lif->nxqs; i++) {
|
|
err = ionic_lif_txq_init(lif, lif->txqcqs[i]);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
err = ionic_lif_rxq_init(lif, lif->rxqcqs[i]);
|
|
if (err) {
|
|
ionic_lif_qcq_deinit(lif, lif->txqcqs[i]);
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
if (lif->netdev->features & NETIF_F_RXHASH)
|
|
ionic_lif_rss_init(lif);
|
|
|
|
ionic_lif_rx_mode(lif);
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
while (i--) {
|
|
ionic_lif_qcq_deinit(lif, lif->txqcqs[i]);
|
|
ionic_lif_qcq_deinit(lif, lif->rxqcqs[i]);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ionic_txrx_enable(struct ionic_lif *lif)
|
|
{
|
|
int derr = 0;
|
|
int i, err;
|
|
|
|
for (i = 0; i < lif->nxqs; i++) {
|
|
if (!(lif->rxqcqs[i] && lif->txqcqs[i])) {
|
|
dev_err(lif->ionic->dev, "%s: bad qcq %d\n", __func__, i);
|
|
err = -ENXIO;
|
|
goto err_out;
|
|
}
|
|
|
|
ionic_rx_fill(&lif->rxqcqs[i]->q);
|
|
err = ionic_qcq_enable(lif->rxqcqs[i]);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
err = ionic_qcq_enable(lif->txqcqs[i]);
|
|
if (err) {
|
|
derr = ionic_qcq_disable(lif, lif->rxqcqs[i], err);
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
if (lif->hwstamp_rxq) {
|
|
ionic_rx_fill(&lif->hwstamp_rxq->q);
|
|
err = ionic_qcq_enable(lif->hwstamp_rxq);
|
|
if (err)
|
|
goto err_out_hwstamp_rx;
|
|
}
|
|
|
|
if (lif->hwstamp_txq) {
|
|
err = ionic_qcq_enable(lif->hwstamp_txq);
|
|
if (err)
|
|
goto err_out_hwstamp_tx;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_out_hwstamp_tx:
|
|
if (lif->hwstamp_rxq)
|
|
derr = ionic_qcq_disable(lif, lif->hwstamp_rxq, derr);
|
|
err_out_hwstamp_rx:
|
|
i = lif->nxqs;
|
|
err_out:
|
|
while (i--) {
|
|
derr = ionic_qcq_disable(lif, lif->txqcqs[i], derr);
|
|
derr = ionic_qcq_disable(lif, lif->rxqcqs[i], derr);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ionic_start_queues(struct ionic_lif *lif)
|
|
{
|
|
int err;
|
|
|
|
if (test_bit(IONIC_LIF_F_BROKEN, lif->state))
|
|
return -EIO;
|
|
|
|
if (test_bit(IONIC_LIF_F_FW_RESET, lif->state))
|
|
return -EBUSY;
|
|
|
|
if (test_and_set_bit(IONIC_LIF_F_UP, lif->state))
|
|
return 0;
|
|
|
|
err = ionic_txrx_enable(lif);
|
|
if (err) {
|
|
clear_bit(IONIC_LIF_F_UP, lif->state);
|
|
return err;
|
|
}
|
|
netif_tx_wake_all_queues(lif->netdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_open(struct net_device *netdev)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
int err;
|
|
|
|
/* If recovering from a broken state, clear the bit and we'll try again */
|
|
if (test_and_clear_bit(IONIC_LIF_F_BROKEN, lif->state))
|
|
netdev_info(netdev, "clearing broken state\n");
|
|
|
|
mutex_lock(&lif->queue_lock);
|
|
|
|
err = ionic_txrx_alloc(lif);
|
|
if (err)
|
|
goto err_unlock;
|
|
|
|
err = ionic_txrx_init(lif);
|
|
if (err)
|
|
goto err_txrx_free;
|
|
|
|
err = netif_set_real_num_tx_queues(netdev, lif->nxqs);
|
|
if (err)
|
|
goto err_txrx_deinit;
|
|
|
|
err = netif_set_real_num_rx_queues(netdev, lif->nxqs);
|
|
if (err)
|
|
goto err_txrx_deinit;
|
|
|
|
/* don't start the queues until we have link */
|
|
if (netif_carrier_ok(netdev)) {
|
|
err = ionic_start_queues(lif);
|
|
if (err)
|
|
goto err_txrx_deinit;
|
|
}
|
|
|
|
/* If hardware timestamping is enabled, but the queues were freed by
|
|
* ionic_stop, those need to be reallocated and initialized, too.
|
|
*/
|
|
ionic_lif_hwstamp_recreate_queues(lif);
|
|
|
|
mutex_unlock(&lif->queue_lock);
|
|
|
|
return 0;
|
|
|
|
err_txrx_deinit:
|
|
ionic_txrx_deinit(lif);
|
|
err_txrx_free:
|
|
ionic_txrx_free(lif);
|
|
err_unlock:
|
|
mutex_unlock(&lif->queue_lock);
|
|
return err;
|
|
}
|
|
|
|
static void ionic_stop_queues(struct ionic_lif *lif)
|
|
{
|
|
if (!test_and_clear_bit(IONIC_LIF_F_UP, lif->state))
|
|
return;
|
|
|
|
netif_tx_disable(lif->netdev);
|
|
ionic_txrx_disable(lif);
|
|
}
|
|
|
|
static int ionic_stop(struct net_device *netdev)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
|
|
if (test_bit(IONIC_LIF_F_FW_RESET, lif->state))
|
|
return 0;
|
|
|
|
mutex_lock(&lif->queue_lock);
|
|
ionic_stop_queues(lif);
|
|
ionic_txrx_deinit(lif);
|
|
ionic_txrx_free(lif);
|
|
mutex_unlock(&lif->queue_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_eth_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
|
|
switch (cmd) {
|
|
case SIOCSHWTSTAMP:
|
|
return ionic_lif_hwstamp_set(lif, ifr);
|
|
case SIOCGHWTSTAMP:
|
|
return ionic_lif_hwstamp_get(lif, ifr);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static int ionic_update_cached_vf_config(struct ionic *ionic, int vf)
|
|
{
|
|
struct ionic_vf_getattr_comp comp = { 0 };
|
|
int err;
|
|
u8 attr;
|
|
|
|
attr = IONIC_VF_ATTR_VLAN;
|
|
err = ionic_dev_cmd_vf_getattr(ionic, vf, attr, &comp);
|
|
if (err && comp.status != IONIC_RC_ENOSUPP)
|
|
goto err_out;
|
|
if (!err)
|
|
ionic->vfs[vf].vlanid = comp.vlanid;
|
|
|
|
attr = IONIC_VF_ATTR_SPOOFCHK;
|
|
err = ionic_dev_cmd_vf_getattr(ionic, vf, attr, &comp);
|
|
if (err && comp.status != IONIC_RC_ENOSUPP)
|
|
goto err_out;
|
|
if (!err)
|
|
ionic->vfs[vf].spoofchk = comp.spoofchk;
|
|
|
|
attr = IONIC_VF_ATTR_LINKSTATE;
|
|
err = ionic_dev_cmd_vf_getattr(ionic, vf, attr, &comp);
|
|
if (err && comp.status != IONIC_RC_ENOSUPP)
|
|
goto err_out;
|
|
if (!err) {
|
|
switch (comp.linkstate) {
|
|
case IONIC_VF_LINK_STATUS_UP:
|
|
ionic->vfs[vf].linkstate = IFLA_VF_LINK_STATE_ENABLE;
|
|
break;
|
|
case IONIC_VF_LINK_STATUS_DOWN:
|
|
ionic->vfs[vf].linkstate = IFLA_VF_LINK_STATE_DISABLE;
|
|
break;
|
|
case IONIC_VF_LINK_STATUS_AUTO:
|
|
ionic->vfs[vf].linkstate = IFLA_VF_LINK_STATE_AUTO;
|
|
break;
|
|
default:
|
|
dev_warn(ionic->dev, "Unexpected link state %u\n", comp.linkstate);
|
|
break;
|
|
}
|
|
}
|
|
|
|
attr = IONIC_VF_ATTR_RATE;
|
|
err = ionic_dev_cmd_vf_getattr(ionic, vf, attr, &comp);
|
|
if (err && comp.status != IONIC_RC_ENOSUPP)
|
|
goto err_out;
|
|
if (!err)
|
|
ionic->vfs[vf].maxrate = comp.maxrate;
|
|
|
|
attr = IONIC_VF_ATTR_TRUST;
|
|
err = ionic_dev_cmd_vf_getattr(ionic, vf, attr, &comp);
|
|
if (err && comp.status != IONIC_RC_ENOSUPP)
|
|
goto err_out;
|
|
if (!err)
|
|
ionic->vfs[vf].trusted = comp.trust;
|
|
|
|
attr = IONIC_VF_ATTR_MAC;
|
|
err = ionic_dev_cmd_vf_getattr(ionic, vf, attr, &comp);
|
|
if (err && comp.status != IONIC_RC_ENOSUPP)
|
|
goto err_out;
|
|
if (!err)
|
|
ether_addr_copy(ionic->vfs[vf].macaddr, comp.macaddr);
|
|
|
|
err_out:
|
|
if (err)
|
|
dev_err(ionic->dev, "Failed to get %s for VF %d\n",
|
|
ionic_vf_attr_to_str(attr), vf);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ionic_get_vf_config(struct net_device *netdev,
|
|
int vf, struct ifla_vf_info *ivf)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
int ret = 0;
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_read(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
ivf->vf = vf;
|
|
ivf->qos = 0;
|
|
|
|
ret = ionic_update_cached_vf_config(ionic, vf);
|
|
if (!ret) {
|
|
ivf->vlan = le16_to_cpu(ionic->vfs[vf].vlanid);
|
|
ivf->spoofchk = ionic->vfs[vf].spoofchk;
|
|
ivf->linkstate = ionic->vfs[vf].linkstate;
|
|
ivf->max_tx_rate = le32_to_cpu(ionic->vfs[vf].maxrate);
|
|
ivf->trusted = ionic->vfs[vf].trusted;
|
|
ether_addr_copy(ivf->mac, ionic->vfs[vf].macaddr);
|
|
}
|
|
}
|
|
|
|
up_read(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int ionic_get_vf_stats(struct net_device *netdev, int vf,
|
|
struct ifla_vf_stats *vf_stats)
|
|
{
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
struct ionic_lif_stats *vs;
|
|
int ret = 0;
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_read(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
memset(vf_stats, 0, sizeof(*vf_stats));
|
|
vs = &ionic->vfs[vf].stats;
|
|
|
|
vf_stats->rx_packets = le64_to_cpu(vs->rx_ucast_packets);
|
|
vf_stats->tx_packets = le64_to_cpu(vs->tx_ucast_packets);
|
|
vf_stats->rx_bytes = le64_to_cpu(vs->rx_ucast_bytes);
|
|
vf_stats->tx_bytes = le64_to_cpu(vs->tx_ucast_bytes);
|
|
vf_stats->broadcast = le64_to_cpu(vs->rx_bcast_packets);
|
|
vf_stats->multicast = le64_to_cpu(vs->rx_mcast_packets);
|
|
vf_stats->rx_dropped = le64_to_cpu(vs->rx_ucast_drop_packets) +
|
|
le64_to_cpu(vs->rx_mcast_drop_packets) +
|
|
le64_to_cpu(vs->rx_bcast_drop_packets);
|
|
vf_stats->tx_dropped = le64_to_cpu(vs->tx_ucast_drop_packets) +
|
|
le64_to_cpu(vs->tx_mcast_drop_packets) +
|
|
le64_to_cpu(vs->tx_bcast_drop_packets);
|
|
}
|
|
|
|
up_read(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int ionic_set_vf_mac(struct net_device *netdev, int vf, u8 *mac)
|
|
{
|
|
struct ionic_vf_setattr_cmd vfc = { .attr = IONIC_VF_ATTR_MAC };
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
int ret;
|
|
|
|
if (!(is_zero_ether_addr(mac) || is_valid_ether_addr(mac)))
|
|
return -EINVAL;
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_write(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
ether_addr_copy(vfc.macaddr, mac);
|
|
dev_dbg(ionic->dev, "%s: vf %d macaddr %pM\n",
|
|
__func__, vf, vfc.macaddr);
|
|
|
|
ret = ionic_set_vf_config(ionic, vf, &vfc);
|
|
if (!ret)
|
|
ether_addr_copy(ionic->vfs[vf].macaddr, mac);
|
|
}
|
|
|
|
up_write(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int ionic_set_vf_vlan(struct net_device *netdev, int vf, u16 vlan,
|
|
u8 qos, __be16 proto)
|
|
{
|
|
struct ionic_vf_setattr_cmd vfc = { .attr = IONIC_VF_ATTR_VLAN };
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
int ret;
|
|
|
|
/* until someday when we support qos */
|
|
if (qos)
|
|
return -EINVAL;
|
|
|
|
if (vlan > 4095)
|
|
return -EINVAL;
|
|
|
|
if (proto != htons(ETH_P_8021Q))
|
|
return -EPROTONOSUPPORT;
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_write(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
vfc.vlanid = cpu_to_le16(vlan);
|
|
dev_dbg(ionic->dev, "%s: vf %d vlan %d\n",
|
|
__func__, vf, le16_to_cpu(vfc.vlanid));
|
|
|
|
ret = ionic_set_vf_config(ionic, vf, &vfc);
|
|
if (!ret)
|
|
ionic->vfs[vf].vlanid = cpu_to_le16(vlan);
|
|
}
|
|
|
|
up_write(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int ionic_set_vf_rate(struct net_device *netdev, int vf,
|
|
int tx_min, int tx_max)
|
|
{
|
|
struct ionic_vf_setattr_cmd vfc = { .attr = IONIC_VF_ATTR_RATE };
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
int ret;
|
|
|
|
/* setting the min just seems silly */
|
|
if (tx_min)
|
|
return -EINVAL;
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_write(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
vfc.maxrate = cpu_to_le32(tx_max);
|
|
dev_dbg(ionic->dev, "%s: vf %d maxrate %d\n",
|
|
__func__, vf, le32_to_cpu(vfc.maxrate));
|
|
|
|
ret = ionic_set_vf_config(ionic, vf, &vfc);
|
|
if (!ret)
|
|
lif->ionic->vfs[vf].maxrate = cpu_to_le32(tx_max);
|
|
}
|
|
|
|
up_write(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int ionic_set_vf_spoofchk(struct net_device *netdev, int vf, bool set)
|
|
{
|
|
struct ionic_vf_setattr_cmd vfc = { .attr = IONIC_VF_ATTR_SPOOFCHK };
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
int ret;
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_write(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
vfc.spoofchk = set;
|
|
dev_dbg(ionic->dev, "%s: vf %d spoof %d\n",
|
|
__func__, vf, vfc.spoofchk);
|
|
|
|
ret = ionic_set_vf_config(ionic, vf, &vfc);
|
|
if (!ret)
|
|
ionic->vfs[vf].spoofchk = set;
|
|
}
|
|
|
|
up_write(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int ionic_set_vf_trust(struct net_device *netdev, int vf, bool set)
|
|
{
|
|
struct ionic_vf_setattr_cmd vfc = { .attr = IONIC_VF_ATTR_TRUST };
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
int ret;
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_write(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
vfc.trust = set;
|
|
dev_dbg(ionic->dev, "%s: vf %d trust %d\n",
|
|
__func__, vf, vfc.trust);
|
|
|
|
ret = ionic_set_vf_config(ionic, vf, &vfc);
|
|
if (!ret)
|
|
ionic->vfs[vf].trusted = set;
|
|
}
|
|
|
|
up_write(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int ionic_set_vf_link_state(struct net_device *netdev, int vf, int set)
|
|
{
|
|
struct ionic_vf_setattr_cmd vfc = { .attr = IONIC_VF_ATTR_LINKSTATE };
|
|
struct ionic_lif *lif = netdev_priv(netdev);
|
|
struct ionic *ionic = lif->ionic;
|
|
u8 vfls;
|
|
int ret;
|
|
|
|
switch (set) {
|
|
case IFLA_VF_LINK_STATE_ENABLE:
|
|
vfls = IONIC_VF_LINK_STATUS_UP;
|
|
break;
|
|
case IFLA_VF_LINK_STATE_DISABLE:
|
|
vfls = IONIC_VF_LINK_STATUS_DOWN;
|
|
break;
|
|
case IFLA_VF_LINK_STATE_AUTO:
|
|
vfls = IONIC_VF_LINK_STATUS_AUTO;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!netif_device_present(netdev))
|
|
return -EBUSY;
|
|
|
|
down_write(&ionic->vf_op_lock);
|
|
|
|
if (vf >= pci_num_vf(ionic->pdev) || !ionic->vfs) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
vfc.linkstate = vfls;
|
|
dev_dbg(ionic->dev, "%s: vf %d linkstate %d\n",
|
|
__func__, vf, vfc.linkstate);
|
|
|
|
ret = ionic_set_vf_config(ionic, vf, &vfc);
|
|
if (!ret)
|
|
ionic->vfs[vf].linkstate = set;
|
|
}
|
|
|
|
up_write(&ionic->vf_op_lock);
|
|
return ret;
|
|
}
|
|
|
|
static const struct net_device_ops ionic_netdev_ops = {
|
|
.ndo_open = ionic_open,
|
|
.ndo_stop = ionic_stop,
|
|
.ndo_eth_ioctl = ionic_eth_ioctl,
|
|
.ndo_start_xmit = ionic_start_xmit,
|
|
.ndo_get_stats64 = ionic_get_stats64,
|
|
.ndo_set_rx_mode = ionic_ndo_set_rx_mode,
|
|
.ndo_set_features = ionic_set_features,
|
|
.ndo_set_mac_address = ionic_set_mac_address,
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
.ndo_tx_timeout = ionic_tx_timeout,
|
|
.ndo_change_mtu = ionic_change_mtu,
|
|
.ndo_vlan_rx_add_vid = ionic_vlan_rx_add_vid,
|
|
.ndo_vlan_rx_kill_vid = ionic_vlan_rx_kill_vid,
|
|
.ndo_set_vf_vlan = ionic_set_vf_vlan,
|
|
.ndo_set_vf_trust = ionic_set_vf_trust,
|
|
.ndo_set_vf_mac = ionic_set_vf_mac,
|
|
.ndo_set_vf_rate = ionic_set_vf_rate,
|
|
.ndo_set_vf_spoofchk = ionic_set_vf_spoofchk,
|
|
.ndo_get_vf_config = ionic_get_vf_config,
|
|
.ndo_set_vf_link_state = ionic_set_vf_link_state,
|
|
.ndo_get_vf_stats = ionic_get_vf_stats,
|
|
};
|
|
|
|
static void ionic_swap_queues(struct ionic_qcq *a, struct ionic_qcq *b)
|
|
{
|
|
/* only swapping the queues, not the napi, flags, or other stuff */
|
|
swap(a->q.features, b->q.features);
|
|
swap(a->q.num_descs, b->q.num_descs);
|
|
swap(a->q.desc_size, b->q.desc_size);
|
|
swap(a->q.base, b->q.base);
|
|
swap(a->q.base_pa, b->q.base_pa);
|
|
swap(a->q.info, b->q.info);
|
|
swap(a->q_base, b->q_base);
|
|
swap(a->q_base_pa, b->q_base_pa);
|
|
swap(a->q_size, b->q_size);
|
|
|
|
swap(a->q.sg_desc_size, b->q.sg_desc_size);
|
|
swap(a->q.sg_base, b->q.sg_base);
|
|
swap(a->q.sg_base_pa, b->q.sg_base_pa);
|
|
swap(a->sg_base, b->sg_base);
|
|
swap(a->sg_base_pa, b->sg_base_pa);
|
|
swap(a->sg_size, b->sg_size);
|
|
|
|
swap(a->cq.num_descs, b->cq.num_descs);
|
|
swap(a->cq.desc_size, b->cq.desc_size);
|
|
swap(a->cq.base, b->cq.base);
|
|
swap(a->cq.base_pa, b->cq.base_pa);
|
|
swap(a->cq.info, b->cq.info);
|
|
swap(a->cq_base, b->cq_base);
|
|
swap(a->cq_base_pa, b->cq_base_pa);
|
|
swap(a->cq_size, b->cq_size);
|
|
|
|
ionic_debugfs_del_qcq(a);
|
|
ionic_debugfs_add_qcq(a->q.lif, a);
|
|
}
|
|
|
|
int ionic_reconfigure_queues(struct ionic_lif *lif,
|
|
struct ionic_queue_params *qparam)
|
|
{
|
|
unsigned int comp_sz, desc_sz, num_desc, sg_desc_sz;
|
|
struct ionic_qcq **tx_qcqs = NULL;
|
|
struct ionic_qcq **rx_qcqs = NULL;
|
|
unsigned int flags, i;
|
|
int err = 0;
|
|
|
|
/* allocate temporary qcq arrays to hold new queue structs */
|
|
if (qparam->nxqs != lif->nxqs || qparam->ntxq_descs != lif->ntxq_descs) {
|
|
tx_qcqs = devm_kcalloc(lif->ionic->dev, lif->ionic->ntxqs_per_lif,
|
|
sizeof(struct ionic_qcq *), GFP_KERNEL);
|
|
if (!tx_qcqs) {
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
}
|
|
if (qparam->nxqs != lif->nxqs ||
|
|
qparam->nrxq_descs != lif->nrxq_descs ||
|
|
qparam->rxq_features != lif->rxq_features) {
|
|
rx_qcqs = devm_kcalloc(lif->ionic->dev, lif->ionic->nrxqs_per_lif,
|
|
sizeof(struct ionic_qcq *), GFP_KERNEL);
|
|
if (!rx_qcqs) {
|
|
err = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
/* allocate new desc_info and rings, but leave the interrupt setup
|
|
* until later so as to not mess with the still-running queues
|
|
*/
|
|
if (tx_qcqs) {
|
|
num_desc = qparam->ntxq_descs;
|
|
desc_sz = sizeof(struct ionic_txq_desc);
|
|
comp_sz = sizeof(struct ionic_txq_comp);
|
|
|
|
if (lif->qtype_info[IONIC_QTYPE_TXQ].version >= 1 &&
|
|
lif->qtype_info[IONIC_QTYPE_TXQ].sg_desc_sz ==
|
|
sizeof(struct ionic_txq_sg_desc_v1))
|
|
sg_desc_sz = sizeof(struct ionic_txq_sg_desc_v1);
|
|
else
|
|
sg_desc_sz = sizeof(struct ionic_txq_sg_desc);
|
|
|
|
for (i = 0; i < qparam->nxqs; i++) {
|
|
flags = lif->txqcqs[i]->flags & ~IONIC_QCQ_F_INTR;
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_TXQ, i, "tx", flags,
|
|
num_desc, desc_sz, comp_sz, sg_desc_sz,
|
|
lif->kern_pid, &tx_qcqs[i]);
|
|
if (err)
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
if (rx_qcqs) {
|
|
num_desc = qparam->nrxq_descs;
|
|
desc_sz = sizeof(struct ionic_rxq_desc);
|
|
comp_sz = sizeof(struct ionic_rxq_comp);
|
|
sg_desc_sz = sizeof(struct ionic_rxq_sg_desc);
|
|
|
|
if (qparam->rxq_features & IONIC_Q_F_2X_CQ_DESC)
|
|
comp_sz *= 2;
|
|
|
|
for (i = 0; i < qparam->nxqs; i++) {
|
|
flags = lif->rxqcqs[i]->flags & ~IONIC_QCQ_F_INTR;
|
|
err = ionic_qcq_alloc(lif, IONIC_QTYPE_RXQ, i, "rx", flags,
|
|
num_desc, desc_sz, comp_sz, sg_desc_sz,
|
|
lif->kern_pid, &rx_qcqs[i]);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
rx_qcqs[i]->q.features = qparam->rxq_features;
|
|
}
|
|
}
|
|
|
|
/* stop and clean the queues */
|
|
ionic_stop_queues_reconfig(lif);
|
|
|
|
if (qparam->nxqs != lif->nxqs) {
|
|
err = netif_set_real_num_tx_queues(lif->netdev, qparam->nxqs);
|
|
if (err)
|
|
goto err_out_reinit_unlock;
|
|
err = netif_set_real_num_rx_queues(lif->netdev, qparam->nxqs);
|
|
if (err) {
|
|
netif_set_real_num_tx_queues(lif->netdev, lif->nxqs);
|
|
goto err_out_reinit_unlock;
|
|
}
|
|
}
|
|
|
|
/* swap new desc_info and rings, keeping existing interrupt config */
|
|
if (tx_qcqs) {
|
|
lif->ntxq_descs = qparam->ntxq_descs;
|
|
for (i = 0; i < qparam->nxqs; i++)
|
|
ionic_swap_queues(lif->txqcqs[i], tx_qcqs[i]);
|
|
}
|
|
|
|
if (rx_qcqs) {
|
|
lif->nrxq_descs = qparam->nrxq_descs;
|
|
for (i = 0; i < qparam->nxqs; i++)
|
|
ionic_swap_queues(lif->rxqcqs[i], rx_qcqs[i]);
|
|
}
|
|
|
|
/* if we need to change the interrupt layout, this is the time */
|
|
if (qparam->intr_split != test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state) ||
|
|
qparam->nxqs != lif->nxqs) {
|
|
if (qparam->intr_split) {
|
|
set_bit(IONIC_LIF_F_SPLIT_INTR, lif->state);
|
|
} else {
|
|
clear_bit(IONIC_LIF_F_SPLIT_INTR, lif->state);
|
|
lif->tx_coalesce_usecs = lif->rx_coalesce_usecs;
|
|
lif->tx_coalesce_hw = lif->rx_coalesce_hw;
|
|
}
|
|
|
|
/* clear existing interrupt assignments */
|
|
for (i = 0; i < lif->ionic->ntxqs_per_lif; i++) {
|
|
ionic_qcq_intr_free(lif, lif->txqcqs[i]);
|
|
ionic_qcq_intr_free(lif, lif->rxqcqs[i]);
|
|
}
|
|
|
|
/* re-assign the interrupts */
|
|
for (i = 0; i < qparam->nxqs; i++) {
|
|
lif->rxqcqs[i]->flags |= IONIC_QCQ_F_INTR;
|
|
err = ionic_alloc_qcq_interrupt(lif, lif->rxqcqs[i]);
|
|
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
|
|
lif->rxqcqs[i]->intr.index,
|
|
lif->rx_coalesce_hw);
|
|
|
|
if (qparam->intr_split) {
|
|
lif->txqcqs[i]->flags |= IONIC_QCQ_F_INTR;
|
|
err = ionic_alloc_qcq_interrupt(lif, lif->txqcqs[i]);
|
|
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
|
|
lif->txqcqs[i]->intr.index,
|
|
lif->tx_coalesce_hw);
|
|
if (test_bit(IONIC_LIF_F_TX_DIM_INTR, lif->state))
|
|
lif->txqcqs[i]->intr.dim_coal_hw = lif->tx_coalesce_hw;
|
|
} else {
|
|
lif->txqcqs[i]->flags &= ~IONIC_QCQ_F_INTR;
|
|
ionic_link_qcq_interrupts(lif->rxqcqs[i], lif->txqcqs[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* now we can rework the debugfs mappings */
|
|
if (tx_qcqs) {
|
|
for (i = 0; i < qparam->nxqs; i++) {
|
|
ionic_debugfs_del_qcq(lif->txqcqs[i]);
|
|
ionic_debugfs_add_qcq(lif, lif->txqcqs[i]);
|
|
}
|
|
}
|
|
|
|
if (rx_qcqs) {
|
|
for (i = 0; i < qparam->nxqs; i++) {
|
|
ionic_debugfs_del_qcq(lif->rxqcqs[i]);
|
|
ionic_debugfs_add_qcq(lif, lif->rxqcqs[i]);
|
|
}
|
|
}
|
|
|
|
swap(lif->nxqs, qparam->nxqs);
|
|
swap(lif->rxq_features, qparam->rxq_features);
|
|
|
|
err_out_reinit_unlock:
|
|
/* re-init the queues, but don't lose an error code */
|
|
if (err)
|
|
ionic_start_queues_reconfig(lif);
|
|
else
|
|
err = ionic_start_queues_reconfig(lif);
|
|
|
|
err_out:
|
|
/* free old allocs without cleaning intr */
|
|
for (i = 0; i < qparam->nxqs; i++) {
|
|
if (tx_qcqs && tx_qcqs[i]) {
|
|
tx_qcqs[i]->flags &= ~IONIC_QCQ_F_INTR;
|
|
ionic_qcq_free(lif, tx_qcqs[i]);
|
|
devm_kfree(lif->ionic->dev, tx_qcqs[i]);
|
|
tx_qcqs[i] = NULL;
|
|
}
|
|
if (rx_qcqs && rx_qcqs[i]) {
|
|
rx_qcqs[i]->flags &= ~IONIC_QCQ_F_INTR;
|
|
ionic_qcq_free(lif, rx_qcqs[i]);
|
|
devm_kfree(lif->ionic->dev, rx_qcqs[i]);
|
|
rx_qcqs[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/* free q array */
|
|
if (rx_qcqs) {
|
|
devm_kfree(lif->ionic->dev, rx_qcqs);
|
|
rx_qcqs = NULL;
|
|
}
|
|
if (tx_qcqs) {
|
|
devm_kfree(lif->ionic->dev, tx_qcqs);
|
|
tx_qcqs = NULL;
|
|
}
|
|
|
|
/* clean the unused dma and info allocations when new set is smaller
|
|
* than the full array, but leave the qcq shells in place
|
|
*/
|
|
for (i = lif->nxqs; i < lif->ionic->ntxqs_per_lif; i++) {
|
|
lif->txqcqs[i]->flags &= ~IONIC_QCQ_F_INTR;
|
|
ionic_qcq_free(lif, lif->txqcqs[i]);
|
|
|
|
lif->rxqcqs[i]->flags &= ~IONIC_QCQ_F_INTR;
|
|
ionic_qcq_free(lif, lif->rxqcqs[i]);
|
|
}
|
|
|
|
if (err)
|
|
netdev_info(lif->netdev, "%s: failed %d\n", __func__, err);
|
|
|
|
return err;
|
|
}
|
|
|
|
int ionic_lif_alloc(struct ionic *ionic)
|
|
{
|
|
struct device *dev = ionic->dev;
|
|
union ionic_lif_identity *lid;
|
|
struct net_device *netdev;
|
|
struct ionic_lif *lif;
|
|
int tbl_sz;
|
|
int err;
|
|
|
|
lid = kzalloc(sizeof(*lid), GFP_KERNEL);
|
|
if (!lid)
|
|
return -ENOMEM;
|
|
|
|
netdev = alloc_etherdev_mqs(sizeof(*lif),
|
|
ionic->ntxqs_per_lif, ionic->ntxqs_per_lif);
|
|
if (!netdev) {
|
|
dev_err(dev, "Cannot allocate netdev, aborting\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_lid;
|
|
}
|
|
|
|
SET_NETDEV_DEV(netdev, dev);
|
|
|
|
lif = netdev_priv(netdev);
|
|
lif->netdev = netdev;
|
|
ionic->lif = lif;
|
|
netdev->netdev_ops = &ionic_netdev_ops;
|
|
ionic_ethtool_set_ops(netdev);
|
|
|
|
netdev->watchdog_timeo = 2 * HZ;
|
|
netif_carrier_off(netdev);
|
|
|
|
lif->identity = lid;
|
|
lif->lif_type = IONIC_LIF_TYPE_CLASSIC;
|
|
err = ionic_lif_identify(ionic, lif->lif_type, lif->identity);
|
|
if (err) {
|
|
dev_err(ionic->dev, "Cannot identify type %d: %d\n",
|
|
lif->lif_type, err);
|
|
goto err_out_free_netdev;
|
|
}
|
|
lif->netdev->min_mtu = max_t(unsigned int, ETH_MIN_MTU,
|
|
le32_to_cpu(lif->identity->eth.min_frame_size));
|
|
lif->netdev->max_mtu =
|
|
le32_to_cpu(lif->identity->eth.max_frame_size) - ETH_HLEN - VLAN_HLEN;
|
|
|
|
lif->neqs = ionic->neqs_per_lif;
|
|
lif->nxqs = ionic->ntxqs_per_lif;
|
|
|
|
lif->ionic = ionic;
|
|
lif->index = 0;
|
|
|
|
if (is_kdump_kernel()) {
|
|
lif->ntxq_descs = IONIC_MIN_TXRX_DESC;
|
|
lif->nrxq_descs = IONIC_MIN_TXRX_DESC;
|
|
} else {
|
|
lif->ntxq_descs = IONIC_DEF_TXRX_DESC;
|
|
lif->nrxq_descs = IONIC_DEF_TXRX_DESC;
|
|
}
|
|
|
|
/* Convert the default coalesce value to actual hw resolution */
|
|
lif->rx_coalesce_usecs = IONIC_ITR_COAL_USEC_DEFAULT;
|
|
lif->rx_coalesce_hw = ionic_coal_usec_to_hw(lif->ionic,
|
|
lif->rx_coalesce_usecs);
|
|
lif->tx_coalesce_usecs = lif->rx_coalesce_usecs;
|
|
lif->tx_coalesce_hw = lif->rx_coalesce_hw;
|
|
set_bit(IONIC_LIF_F_RX_DIM_INTR, lif->state);
|
|
set_bit(IONIC_LIF_F_TX_DIM_INTR, lif->state);
|
|
|
|
snprintf(lif->name, sizeof(lif->name), "lif%u", lif->index);
|
|
|
|
mutex_init(&lif->queue_lock);
|
|
mutex_init(&lif->config_lock);
|
|
|
|
spin_lock_init(&lif->adminq_lock);
|
|
|
|
spin_lock_init(&lif->deferred.lock);
|
|
INIT_LIST_HEAD(&lif->deferred.list);
|
|
INIT_WORK(&lif->deferred.work, ionic_lif_deferred_work);
|
|
|
|
/* allocate lif info */
|
|
lif->info_sz = ALIGN(sizeof(*lif->info), PAGE_SIZE);
|
|
lif->info = dma_alloc_coherent(dev, lif->info_sz,
|
|
&lif->info_pa, GFP_KERNEL);
|
|
if (!lif->info) {
|
|
dev_err(dev, "Failed to allocate lif info, aborting\n");
|
|
err = -ENOMEM;
|
|
goto err_out_free_mutex;
|
|
}
|
|
|
|
ionic_debugfs_add_lif(lif);
|
|
|
|
/* allocate control queues and txrx queue arrays */
|
|
ionic_lif_queue_identify(lif);
|
|
err = ionic_qcqs_alloc(lif);
|
|
if (err)
|
|
goto err_out_free_lif_info;
|
|
|
|
/* allocate rss indirection table */
|
|
tbl_sz = le16_to_cpu(lif->ionic->ident.lif.eth.rss_ind_tbl_sz);
|
|
lif->rss_ind_tbl_sz = sizeof(*lif->rss_ind_tbl) * tbl_sz;
|
|
lif->rss_ind_tbl = dma_alloc_coherent(dev, lif->rss_ind_tbl_sz,
|
|
&lif->rss_ind_tbl_pa,
|
|
GFP_KERNEL);
|
|
|
|
if (!lif->rss_ind_tbl) {
|
|
err = -ENOMEM;
|
|
dev_err(dev, "Failed to allocate rss indirection table, aborting\n");
|
|
goto err_out_free_qcqs;
|
|
}
|
|
netdev_rss_key_fill(lif->rss_hash_key, IONIC_RSS_HASH_KEY_SIZE);
|
|
|
|
ionic_lif_alloc_phc(lif);
|
|
|
|
return 0;
|
|
|
|
err_out_free_qcqs:
|
|
ionic_qcqs_free(lif);
|
|
err_out_free_lif_info:
|
|
dma_free_coherent(dev, lif->info_sz, lif->info, lif->info_pa);
|
|
lif->info = NULL;
|
|
lif->info_pa = 0;
|
|
err_out_free_mutex:
|
|
mutex_destroy(&lif->config_lock);
|
|
mutex_destroy(&lif->queue_lock);
|
|
err_out_free_netdev:
|
|
free_netdev(lif->netdev);
|
|
lif = NULL;
|
|
err_out_free_lid:
|
|
kfree(lid);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ionic_lif_reset(struct ionic_lif *lif)
|
|
{
|
|
struct ionic_dev *idev = &lif->ionic->idev;
|
|
|
|
mutex_lock(&lif->ionic->dev_cmd_lock);
|
|
ionic_dev_cmd_lif_reset(idev, lif->index);
|
|
ionic_dev_cmd_wait(lif->ionic, DEVCMD_TIMEOUT);
|
|
mutex_unlock(&lif->ionic->dev_cmd_lock);
|
|
}
|
|
|
|
static void ionic_lif_handle_fw_down(struct ionic_lif *lif)
|
|
{
|
|
struct ionic *ionic = lif->ionic;
|
|
|
|
if (test_and_set_bit(IONIC_LIF_F_FW_RESET, lif->state))
|
|
return;
|
|
|
|
dev_info(ionic->dev, "FW Down: Stopping LIFs\n");
|
|
|
|
netif_device_detach(lif->netdev);
|
|
|
|
mutex_lock(&lif->queue_lock);
|
|
if (test_bit(IONIC_LIF_F_UP, lif->state)) {
|
|
dev_info(ionic->dev, "Surprise FW stop, stopping queues\n");
|
|
ionic_stop_queues(lif);
|
|
}
|
|
|
|
if (netif_running(lif->netdev)) {
|
|
ionic_txrx_deinit(lif);
|
|
ionic_txrx_free(lif);
|
|
}
|
|
ionic_lif_deinit(lif);
|
|
ionic_reset(ionic);
|
|
ionic_qcqs_free(lif);
|
|
|
|
mutex_unlock(&lif->queue_lock);
|
|
|
|
clear_bit(IONIC_LIF_F_FW_STOPPING, lif->state);
|
|
dev_info(ionic->dev, "FW Down: LIFs stopped\n");
|
|
}
|
|
|
|
static void ionic_lif_handle_fw_up(struct ionic_lif *lif)
|
|
{
|
|
struct ionic *ionic = lif->ionic;
|
|
int err;
|
|
|
|
if (!test_bit(IONIC_LIF_F_FW_RESET, lif->state))
|
|
return;
|
|
|
|
dev_info(ionic->dev, "FW Up: restarting LIFs\n");
|
|
|
|
ionic_init_devinfo(ionic);
|
|
err = ionic_identify(ionic);
|
|
if (err)
|
|
goto err_out;
|
|
err = ionic_port_identify(ionic);
|
|
if (err)
|
|
goto err_out;
|
|
err = ionic_port_init(ionic);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
mutex_lock(&lif->queue_lock);
|
|
|
|
if (test_and_clear_bit(IONIC_LIF_F_BROKEN, lif->state))
|
|
dev_info(ionic->dev, "FW Up: clearing broken state\n");
|
|
|
|
err = ionic_qcqs_alloc(lif);
|
|
if (err)
|
|
goto err_unlock;
|
|
|
|
err = ionic_lif_init(lif);
|
|
if (err)
|
|
goto err_qcqs_free;
|
|
|
|
if (lif->registered)
|
|
ionic_lif_set_netdev_info(lif);
|
|
|
|
ionic_rx_filter_replay(lif);
|
|
|
|
if (netif_running(lif->netdev)) {
|
|
err = ionic_txrx_alloc(lif);
|
|
if (err)
|
|
goto err_lifs_deinit;
|
|
|
|
err = ionic_txrx_init(lif);
|
|
if (err)
|
|
goto err_txrx_free;
|
|
}
|
|
|
|
mutex_unlock(&lif->queue_lock);
|
|
|
|
clear_bit(IONIC_LIF_F_FW_RESET, lif->state);
|
|
ionic_link_status_check_request(lif, CAN_SLEEP);
|
|
netif_device_attach(lif->netdev);
|
|
dev_info(ionic->dev, "FW Up: LIFs restarted\n");
|
|
|
|
/* restore the hardware timestamping queues */
|
|
ionic_lif_hwstamp_replay(lif);
|
|
|
|
return;
|
|
|
|
err_txrx_free:
|
|
ionic_txrx_free(lif);
|
|
err_lifs_deinit:
|
|
ionic_lif_deinit(lif);
|
|
err_qcqs_free:
|
|
ionic_qcqs_free(lif);
|
|
err_unlock:
|
|
mutex_unlock(&lif->queue_lock);
|
|
err_out:
|
|
dev_err(ionic->dev, "FW Up: LIFs restart failed - err %d\n", err);
|
|
}
|
|
|
|
void ionic_lif_free(struct ionic_lif *lif)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
|
|
ionic_lif_free_phc(lif);
|
|
|
|
/* free rss indirection table */
|
|
dma_free_coherent(dev, lif->rss_ind_tbl_sz, lif->rss_ind_tbl,
|
|
lif->rss_ind_tbl_pa);
|
|
lif->rss_ind_tbl = NULL;
|
|
lif->rss_ind_tbl_pa = 0;
|
|
|
|
/* free queues */
|
|
ionic_qcqs_free(lif);
|
|
if (!test_bit(IONIC_LIF_F_FW_RESET, lif->state))
|
|
ionic_lif_reset(lif);
|
|
|
|
/* free lif info */
|
|
kfree(lif->identity);
|
|
dma_free_coherent(dev, lif->info_sz, lif->info, lif->info_pa);
|
|
lif->info = NULL;
|
|
lif->info_pa = 0;
|
|
|
|
/* unmap doorbell page */
|
|
ionic_bus_unmap_dbpage(lif->ionic, lif->kern_dbpage);
|
|
lif->kern_dbpage = NULL;
|
|
|
|
mutex_destroy(&lif->config_lock);
|
|
mutex_destroy(&lif->queue_lock);
|
|
|
|
/* free netdev & lif */
|
|
ionic_debugfs_del_lif(lif);
|
|
free_netdev(lif->netdev);
|
|
}
|
|
|
|
void ionic_lif_deinit(struct ionic_lif *lif)
|
|
{
|
|
if (!test_and_clear_bit(IONIC_LIF_F_INITED, lif->state))
|
|
return;
|
|
|
|
if (!test_bit(IONIC_LIF_F_FW_RESET, lif->state)) {
|
|
cancel_work_sync(&lif->deferred.work);
|
|
cancel_work_sync(&lif->tx_timeout_work);
|
|
ionic_rx_filters_deinit(lif);
|
|
if (lif->netdev->features & NETIF_F_RXHASH)
|
|
ionic_lif_rss_deinit(lif);
|
|
}
|
|
|
|
napi_disable(&lif->adminqcq->napi);
|
|
ionic_lif_qcq_deinit(lif, lif->notifyqcq);
|
|
ionic_lif_qcq_deinit(lif, lif->adminqcq);
|
|
|
|
ionic_lif_reset(lif);
|
|
}
|
|
|
|
static int ionic_lif_adminq_init(struct ionic_lif *lif)
|
|
{
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_q_init_comp comp;
|
|
struct ionic_dev *idev;
|
|
struct ionic_qcq *qcq;
|
|
struct ionic_queue *q;
|
|
int err;
|
|
|
|
idev = &lif->ionic->idev;
|
|
qcq = lif->adminqcq;
|
|
q = &qcq->q;
|
|
|
|
mutex_lock(&lif->ionic->dev_cmd_lock);
|
|
ionic_dev_cmd_adminq_init(idev, qcq, lif->index, qcq->intr.index);
|
|
err = ionic_dev_cmd_wait(lif->ionic, DEVCMD_TIMEOUT);
|
|
ionic_dev_cmd_comp(idev, (union ionic_dev_cmd_comp *)&comp);
|
|
mutex_unlock(&lif->ionic->dev_cmd_lock);
|
|
if (err) {
|
|
netdev_err(lif->netdev, "adminq init failed %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
q->hw_type = comp.hw_type;
|
|
q->hw_index = le32_to_cpu(comp.hw_index);
|
|
q->dbval = IONIC_DBELL_QID(q->hw_index);
|
|
|
|
dev_dbg(dev, "adminq->hw_type %d\n", q->hw_type);
|
|
dev_dbg(dev, "adminq->hw_index %d\n", q->hw_index);
|
|
|
|
netif_napi_add(lif->netdev, &qcq->napi, ionic_adminq_napi,
|
|
NAPI_POLL_WEIGHT);
|
|
|
|
napi_enable(&qcq->napi);
|
|
|
|
if (qcq->flags & IONIC_QCQ_F_INTR)
|
|
ionic_intr_mask(idev->intr_ctrl, qcq->intr.index,
|
|
IONIC_INTR_MASK_CLEAR);
|
|
|
|
qcq->flags |= IONIC_QCQ_F_INITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_lif_notifyq_init(struct ionic_lif *lif)
|
|
{
|
|
struct ionic_qcq *qcq = lif->notifyqcq;
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_queue *q = &qcq->q;
|
|
int err;
|
|
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.q_init = {
|
|
.opcode = IONIC_CMD_Q_INIT,
|
|
.lif_index = cpu_to_le16(lif->index),
|
|
.type = q->type,
|
|
.ver = lif->qtype_info[q->type].version,
|
|
.index = cpu_to_le32(q->index),
|
|
.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
|
|
IONIC_QINIT_F_ENA),
|
|
.intr_index = cpu_to_le16(lif->adminqcq->intr.index),
|
|
.pid = cpu_to_le16(q->pid),
|
|
.ring_size = ilog2(q->num_descs),
|
|
.ring_base = cpu_to_le64(q->base_pa),
|
|
}
|
|
};
|
|
|
|
dev_dbg(dev, "notifyq_init.pid %d\n", ctx.cmd.q_init.pid);
|
|
dev_dbg(dev, "notifyq_init.index %d\n", ctx.cmd.q_init.index);
|
|
dev_dbg(dev, "notifyq_init.ring_base 0x%llx\n", ctx.cmd.q_init.ring_base);
|
|
dev_dbg(dev, "notifyq_init.ring_size %d\n", ctx.cmd.q_init.ring_size);
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
return err;
|
|
|
|
lif->last_eid = 0;
|
|
q->hw_type = ctx.comp.q_init.hw_type;
|
|
q->hw_index = le32_to_cpu(ctx.comp.q_init.hw_index);
|
|
q->dbval = IONIC_DBELL_QID(q->hw_index);
|
|
|
|
dev_dbg(dev, "notifyq->hw_type %d\n", q->hw_type);
|
|
dev_dbg(dev, "notifyq->hw_index %d\n", q->hw_index);
|
|
|
|
/* preset the callback info */
|
|
q->info[0].cb_arg = lif;
|
|
|
|
qcq->flags |= IONIC_QCQ_F_INITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ionic_station_set(struct ionic_lif *lif)
|
|
{
|
|
struct net_device *netdev = lif->netdev;
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_getattr = {
|
|
.opcode = IONIC_CMD_LIF_GETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_MAC,
|
|
},
|
|
};
|
|
u8 mac_address[ETH_ALEN];
|
|
struct sockaddr addr;
|
|
int err;
|
|
|
|
err = ionic_adminq_post_wait(lif, &ctx);
|
|
if (err)
|
|
return err;
|
|
netdev_dbg(lif->netdev, "found initial MAC addr %pM\n",
|
|
ctx.comp.lif_getattr.mac);
|
|
ether_addr_copy(mac_address, ctx.comp.lif_getattr.mac);
|
|
|
|
if (is_zero_ether_addr(mac_address)) {
|
|
eth_hw_addr_random(netdev);
|
|
netdev_dbg(netdev, "Random Mac generated: %pM\n", netdev->dev_addr);
|
|
ether_addr_copy(mac_address, netdev->dev_addr);
|
|
|
|
err = ionic_program_mac(lif, mac_address);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (err > 0) {
|
|
netdev_dbg(netdev, "%s:SET/GET ATTR Mac are not same-due to old FW running\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!is_zero_ether_addr(netdev->dev_addr)) {
|
|
/* If the netdev mac is non-zero and doesn't match the default
|
|
* device address, it was set by something earlier and we're
|
|
* likely here again after a fw-upgrade reset. We need to be
|
|
* sure the netdev mac is in our filter list.
|
|
*/
|
|
if (!ether_addr_equal(mac_address, netdev->dev_addr))
|
|
ionic_lif_addr_add(lif, netdev->dev_addr);
|
|
} else {
|
|
/* Update the netdev mac with the device's mac */
|
|
ether_addr_copy(addr.sa_data, mac_address);
|
|
addr.sa_family = AF_INET;
|
|
err = eth_prepare_mac_addr_change(netdev, &addr);
|
|
if (err) {
|
|
netdev_warn(lif->netdev, "ignoring bad MAC addr from NIC %pM - err %d\n",
|
|
addr.sa_data, err);
|
|
return 0;
|
|
}
|
|
|
|
eth_commit_mac_addr_change(netdev, &addr);
|
|
}
|
|
|
|
netdev_dbg(lif->netdev, "adding station MAC addr %pM\n",
|
|
netdev->dev_addr);
|
|
ionic_lif_addr_add(lif, netdev->dev_addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ionic_lif_init(struct ionic_lif *lif)
|
|
{
|
|
struct ionic_dev *idev = &lif->ionic->idev;
|
|
struct device *dev = lif->ionic->dev;
|
|
struct ionic_lif_init_comp comp;
|
|
int dbpage_num;
|
|
int err;
|
|
|
|
mutex_lock(&lif->ionic->dev_cmd_lock);
|
|
ionic_dev_cmd_lif_init(idev, lif->index, lif->info_pa);
|
|
err = ionic_dev_cmd_wait(lif->ionic, DEVCMD_TIMEOUT);
|
|
ionic_dev_cmd_comp(idev, (union ionic_dev_cmd_comp *)&comp);
|
|
mutex_unlock(&lif->ionic->dev_cmd_lock);
|
|
if (err)
|
|
return err;
|
|
|
|
lif->hw_index = le16_to_cpu(comp.hw_index);
|
|
|
|
/* now that we have the hw_index we can figure out our doorbell page */
|
|
lif->dbid_count = le32_to_cpu(lif->ionic->ident.dev.ndbpgs_per_lif);
|
|
if (!lif->dbid_count) {
|
|
dev_err(dev, "No doorbell pages, aborting\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
lif->kern_pid = 0;
|
|
dbpage_num = ionic_db_page_num(lif, lif->kern_pid);
|
|
lif->kern_dbpage = ionic_bus_map_dbpage(lif->ionic, dbpage_num);
|
|
if (!lif->kern_dbpage) {
|
|
dev_err(dev, "Cannot map dbpage, aborting\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
err = ionic_lif_adminq_init(lif);
|
|
if (err)
|
|
goto err_out_adminq_deinit;
|
|
|
|
if (lif->ionic->nnqs_per_lif) {
|
|
err = ionic_lif_notifyq_init(lif);
|
|
if (err)
|
|
goto err_out_notifyq_deinit;
|
|
}
|
|
|
|
err = ionic_init_nic_features(lif);
|
|
if (err)
|
|
goto err_out_notifyq_deinit;
|
|
|
|
if (!test_bit(IONIC_LIF_F_FW_RESET, lif->state)) {
|
|
err = ionic_rx_filters_init(lif);
|
|
if (err)
|
|
goto err_out_notifyq_deinit;
|
|
}
|
|
|
|
err = ionic_station_set(lif);
|
|
if (err)
|
|
goto err_out_notifyq_deinit;
|
|
|
|
lif->rx_copybreak = IONIC_RX_COPYBREAK_DEFAULT;
|
|
|
|
set_bit(IONIC_LIF_F_INITED, lif->state);
|
|
|
|
INIT_WORK(&lif->tx_timeout_work, ionic_tx_timeout_work);
|
|
|
|
return 0;
|
|
|
|
err_out_notifyq_deinit:
|
|
napi_disable(&lif->adminqcq->napi);
|
|
ionic_lif_qcq_deinit(lif, lif->notifyqcq);
|
|
err_out_adminq_deinit:
|
|
ionic_lif_qcq_deinit(lif, lif->adminqcq);
|
|
ionic_lif_reset(lif);
|
|
ionic_bus_unmap_dbpage(lif->ionic, lif->kern_dbpage);
|
|
lif->kern_dbpage = NULL;
|
|
|
|
return err;
|
|
}
|
|
|
|
static void ionic_lif_notify_work(struct work_struct *ws)
|
|
{
|
|
}
|
|
|
|
static void ionic_lif_set_netdev_info(struct ionic_lif *lif)
|
|
{
|
|
struct ionic_admin_ctx ctx = {
|
|
.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
|
|
.cmd.lif_setattr = {
|
|
.opcode = IONIC_CMD_LIF_SETATTR,
|
|
.index = cpu_to_le16(lif->index),
|
|
.attr = IONIC_LIF_ATTR_NAME,
|
|
},
|
|
};
|
|
|
|
strscpy(ctx.cmd.lif_setattr.name, lif->netdev->name,
|
|
sizeof(ctx.cmd.lif_setattr.name));
|
|
|
|
ionic_adminq_post_wait(lif, &ctx);
|
|
}
|
|
|
|
static struct ionic_lif *ionic_netdev_lif(struct net_device *netdev)
|
|
{
|
|
if (!netdev || netdev->netdev_ops->ndo_start_xmit != ionic_start_xmit)
|
|
return NULL;
|
|
|
|
return netdev_priv(netdev);
|
|
}
|
|
|
|
static int ionic_lif_notify(struct notifier_block *nb,
|
|
unsigned long event, void *info)
|
|
{
|
|
struct net_device *ndev = netdev_notifier_info_to_dev(info);
|
|
struct ionic *ionic = container_of(nb, struct ionic, nb);
|
|
struct ionic_lif *lif = ionic_netdev_lif(ndev);
|
|
|
|
if (!lif || lif->ionic != ionic)
|
|
return NOTIFY_DONE;
|
|
|
|
switch (event) {
|
|
case NETDEV_CHANGENAME:
|
|
ionic_lif_set_netdev_info(lif);
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
int ionic_lif_register(struct ionic_lif *lif)
|
|
{
|
|
int err;
|
|
|
|
ionic_lif_register_phc(lif);
|
|
|
|
INIT_WORK(&lif->ionic->nb_work, ionic_lif_notify_work);
|
|
|
|
lif->ionic->nb.notifier_call = ionic_lif_notify;
|
|
|
|
err = register_netdevice_notifier(&lif->ionic->nb);
|
|
if (err)
|
|
lif->ionic->nb.notifier_call = NULL;
|
|
|
|
/* only register LIF0 for now */
|
|
err = register_netdev(lif->netdev);
|
|
if (err) {
|
|
dev_err(lif->ionic->dev, "Cannot register net device, aborting\n");
|
|
ionic_lif_unregister_phc(lif);
|
|
return err;
|
|
}
|
|
|
|
ionic_link_status_check_request(lif, CAN_SLEEP);
|
|
lif->registered = true;
|
|
ionic_lif_set_netdev_info(lif);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ionic_lif_unregister(struct ionic_lif *lif)
|
|
{
|
|
if (lif->ionic->nb.notifier_call) {
|
|
unregister_netdevice_notifier(&lif->ionic->nb);
|
|
cancel_work_sync(&lif->ionic->nb_work);
|
|
lif->ionic->nb.notifier_call = NULL;
|
|
}
|
|
|
|
if (lif->netdev->reg_state == NETREG_REGISTERED)
|
|
unregister_netdev(lif->netdev);
|
|
|
|
ionic_lif_unregister_phc(lif);
|
|
|
|
lif->registered = false;
|
|
}
|
|
|
|
static void ionic_lif_queue_identify(struct ionic_lif *lif)
|
|
{
|
|
union ionic_q_identity __iomem *q_ident;
|
|
struct ionic *ionic = lif->ionic;
|
|
struct ionic_dev *idev;
|
|
int qtype;
|
|
int err;
|
|
|
|
idev = &lif->ionic->idev;
|
|
q_ident = (union ionic_q_identity __iomem *)&idev->dev_cmd_regs->data;
|
|
|
|
for (qtype = 0; qtype < ARRAY_SIZE(ionic_qtype_versions); qtype++) {
|
|
struct ionic_qtype_info *qti = &lif->qtype_info[qtype];
|
|
|
|
/* filter out the ones we know about */
|
|
switch (qtype) {
|
|
case IONIC_QTYPE_ADMINQ:
|
|
case IONIC_QTYPE_NOTIFYQ:
|
|
case IONIC_QTYPE_RXQ:
|
|
case IONIC_QTYPE_TXQ:
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
memset(qti, 0, sizeof(*qti));
|
|
|
|
mutex_lock(&ionic->dev_cmd_lock);
|
|
ionic_dev_cmd_queue_identify(idev, lif->lif_type, qtype,
|
|
ionic_qtype_versions[qtype]);
|
|
err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT);
|
|
if (!err) {
|
|
qti->version = readb(&q_ident->version);
|
|
qti->supported = readb(&q_ident->supported);
|
|
qti->features = readq(&q_ident->features);
|
|
qti->desc_sz = readw(&q_ident->desc_sz);
|
|
qti->comp_sz = readw(&q_ident->comp_sz);
|
|
qti->sg_desc_sz = readw(&q_ident->sg_desc_sz);
|
|
qti->max_sg_elems = readw(&q_ident->max_sg_elems);
|
|
qti->sg_desc_stride = readw(&q_ident->sg_desc_stride);
|
|
}
|
|
mutex_unlock(&ionic->dev_cmd_lock);
|
|
|
|
if (err == -EINVAL) {
|
|
dev_err(ionic->dev, "qtype %d not supported\n", qtype);
|
|
continue;
|
|
} else if (err == -EIO) {
|
|
dev_err(ionic->dev, "q_ident failed, not supported on older FW\n");
|
|
return;
|
|
} else if (err) {
|
|
dev_err(ionic->dev, "q_ident failed, qtype %d: %d\n",
|
|
qtype, err);
|
|
return;
|
|
}
|
|
|
|
dev_dbg(ionic->dev, " qtype[%d].version = %d\n",
|
|
qtype, qti->version);
|
|
dev_dbg(ionic->dev, " qtype[%d].supported = 0x%02x\n",
|
|
qtype, qti->supported);
|
|
dev_dbg(ionic->dev, " qtype[%d].features = 0x%04llx\n",
|
|
qtype, qti->features);
|
|
dev_dbg(ionic->dev, " qtype[%d].desc_sz = %d\n",
|
|
qtype, qti->desc_sz);
|
|
dev_dbg(ionic->dev, " qtype[%d].comp_sz = %d\n",
|
|
qtype, qti->comp_sz);
|
|
dev_dbg(ionic->dev, " qtype[%d].sg_desc_sz = %d\n",
|
|
qtype, qti->sg_desc_sz);
|
|
dev_dbg(ionic->dev, " qtype[%d].max_sg_elems = %d\n",
|
|
qtype, qti->max_sg_elems);
|
|
dev_dbg(ionic->dev, " qtype[%d].sg_desc_stride = %d\n",
|
|
qtype, qti->sg_desc_stride);
|
|
}
|
|
}
|
|
|
|
int ionic_lif_identify(struct ionic *ionic, u8 lif_type,
|
|
union ionic_lif_identity *lid)
|
|
{
|
|
struct ionic_dev *idev = &ionic->idev;
|
|
size_t sz;
|
|
int err;
|
|
|
|
sz = min(sizeof(*lid), sizeof(idev->dev_cmd_regs->data));
|
|
|
|
mutex_lock(&ionic->dev_cmd_lock);
|
|
ionic_dev_cmd_lif_identify(idev, lif_type, IONIC_IDENTITY_VERSION_1);
|
|
err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT);
|
|
memcpy_fromio(lid, &idev->dev_cmd_regs->data, sz);
|
|
mutex_unlock(&ionic->dev_cmd_lock);
|
|
if (err)
|
|
return (err);
|
|
|
|
dev_dbg(ionic->dev, "capabilities 0x%llx\n",
|
|
le64_to_cpu(lid->capabilities));
|
|
|
|
dev_dbg(ionic->dev, "eth.max_ucast_filters %d\n",
|
|
le32_to_cpu(lid->eth.max_ucast_filters));
|
|
dev_dbg(ionic->dev, "eth.max_mcast_filters %d\n",
|
|
le32_to_cpu(lid->eth.max_mcast_filters));
|
|
dev_dbg(ionic->dev, "eth.features 0x%llx\n",
|
|
le64_to_cpu(lid->eth.config.features));
|
|
dev_dbg(ionic->dev, "eth.queue_count[IONIC_QTYPE_ADMINQ] %d\n",
|
|
le32_to_cpu(lid->eth.config.queue_count[IONIC_QTYPE_ADMINQ]));
|
|
dev_dbg(ionic->dev, "eth.queue_count[IONIC_QTYPE_NOTIFYQ] %d\n",
|
|
le32_to_cpu(lid->eth.config.queue_count[IONIC_QTYPE_NOTIFYQ]));
|
|
dev_dbg(ionic->dev, "eth.queue_count[IONIC_QTYPE_RXQ] %d\n",
|
|
le32_to_cpu(lid->eth.config.queue_count[IONIC_QTYPE_RXQ]));
|
|
dev_dbg(ionic->dev, "eth.queue_count[IONIC_QTYPE_TXQ] %d\n",
|
|
le32_to_cpu(lid->eth.config.queue_count[IONIC_QTYPE_TXQ]));
|
|
dev_dbg(ionic->dev, "eth.config.name %s\n", lid->eth.config.name);
|
|
dev_dbg(ionic->dev, "eth.config.mac %pM\n", lid->eth.config.mac);
|
|
dev_dbg(ionic->dev, "eth.config.mtu %d\n",
|
|
le32_to_cpu(lid->eth.config.mtu));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ionic_lif_size(struct ionic *ionic)
|
|
{
|
|
struct ionic_identity *ident = &ionic->ident;
|
|
unsigned int nintrs, dev_nintrs;
|
|
union ionic_lif_config *lc;
|
|
unsigned int ntxqs_per_lif;
|
|
unsigned int nrxqs_per_lif;
|
|
unsigned int neqs_per_lif;
|
|
unsigned int nnqs_per_lif;
|
|
unsigned int nxqs, neqs;
|
|
unsigned int min_intrs;
|
|
int err;
|
|
|
|
/* retrieve basic values from FW */
|
|
lc = &ident->lif.eth.config;
|
|
dev_nintrs = le32_to_cpu(ident->dev.nintrs);
|
|
neqs_per_lif = le32_to_cpu(ident->lif.rdma.eq_qtype.qid_count);
|
|
nnqs_per_lif = le32_to_cpu(lc->queue_count[IONIC_QTYPE_NOTIFYQ]);
|
|
ntxqs_per_lif = le32_to_cpu(lc->queue_count[IONIC_QTYPE_TXQ]);
|
|
nrxqs_per_lif = le32_to_cpu(lc->queue_count[IONIC_QTYPE_RXQ]);
|
|
|
|
/* limit values to play nice with kdump */
|
|
if (is_kdump_kernel()) {
|
|
dev_nintrs = 2;
|
|
neqs_per_lif = 0;
|
|
nnqs_per_lif = 0;
|
|
ntxqs_per_lif = 1;
|
|
nrxqs_per_lif = 1;
|
|
}
|
|
|
|
/* reserve last queue id for hardware timestamping */
|
|
if (lc->features & cpu_to_le64(IONIC_ETH_HW_TIMESTAMP)) {
|
|
if (ntxqs_per_lif <= 1 || nrxqs_per_lif <= 1) {
|
|
lc->features &= cpu_to_le64(~IONIC_ETH_HW_TIMESTAMP);
|
|
} else {
|
|
ntxqs_per_lif -= 1;
|
|
nrxqs_per_lif -= 1;
|
|
}
|
|
}
|
|
|
|
nxqs = min(ntxqs_per_lif, nrxqs_per_lif);
|
|
nxqs = min(nxqs, num_online_cpus());
|
|
neqs = min(neqs_per_lif, num_online_cpus());
|
|
|
|
try_again:
|
|
/* interrupt usage:
|
|
* 1 for master lif adminq/notifyq
|
|
* 1 for each CPU for master lif TxRx queue pairs
|
|
* whatever's left is for RDMA queues
|
|
*/
|
|
nintrs = 1 + nxqs + neqs;
|
|
min_intrs = 2; /* adminq + 1 TxRx queue pair */
|
|
|
|
if (nintrs > dev_nintrs)
|
|
goto try_fewer;
|
|
|
|
err = ionic_bus_alloc_irq_vectors(ionic, nintrs);
|
|
if (err < 0 && err != -ENOSPC) {
|
|
dev_err(ionic->dev, "Can't get intrs from OS: %d\n", err);
|
|
return err;
|
|
}
|
|
if (err == -ENOSPC)
|
|
goto try_fewer;
|
|
|
|
if (err != nintrs) {
|
|
ionic_bus_free_irq_vectors(ionic);
|
|
goto try_fewer;
|
|
}
|
|
|
|
ionic->nnqs_per_lif = nnqs_per_lif;
|
|
ionic->neqs_per_lif = neqs;
|
|
ionic->ntxqs_per_lif = nxqs;
|
|
ionic->nrxqs_per_lif = nxqs;
|
|
ionic->nintrs = nintrs;
|
|
|
|
ionic_debugfs_add_sizes(ionic);
|
|
|
|
return 0;
|
|
|
|
try_fewer:
|
|
if (nnqs_per_lif > 1) {
|
|
nnqs_per_lif >>= 1;
|
|
goto try_again;
|
|
}
|
|
if (neqs > 1) {
|
|
neqs >>= 1;
|
|
goto try_again;
|
|
}
|
|
if (nxqs > 1) {
|
|
nxqs >>= 1;
|
|
goto try_again;
|
|
}
|
|
dev_err(ionic->dev, "Can't get minimum %d intrs from OS\n", min_intrs);
|
|
return -ENOSPC;
|
|
}
|