1333 lines
39 KiB
C
1333 lines
39 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright(c) 2021 Huawei Technologies Co., Ltd */
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/ethtool.h>
|
|
|
|
#include "ossl_knl.h"
|
|
#include "hinic3_hw.h"
|
|
#include "hinic3_crm.h"
|
|
#include "hinic3_nic_dev.h"
|
|
#include "hinic3_tx.h"
|
|
#include "hinic3_rx.h"
|
|
#include "hinic3_rss.h"
|
|
|
|
#define COALESCE_ALL_QUEUE 0xFFFF
|
|
#define COALESCE_PENDING_LIMIT_UNIT 8
|
|
#define COALESCE_TIMER_CFG_UNIT 5
|
|
#define COALESCE_MAX_PENDING_LIMIT (255 * COALESCE_PENDING_LIMIT_UNIT)
|
|
#define COALESCE_MAX_TIMER_CFG (255 * COALESCE_TIMER_CFG_UNIT)
|
|
#define HINIC3_WAIT_PKTS_TO_RX_BUFFER 200
|
|
#define HINIC3_WAIT_CLEAR_LP_TEST 100
|
|
|
|
#ifndef SET_ETHTOOL_OPS
|
|
#define SET_ETHTOOL_OPS(netdev, ops) \
|
|
((netdev)->ethtool_ops = (ops))
|
|
#endif
|
|
|
|
static void hinic3_get_drvinfo(struct net_device *netdev,
|
|
struct ethtool_drvinfo *info)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct pci_dev *pdev = nic_dev->pdev;
|
|
u8 mgmt_ver[HINIC3_MGMT_VERSION_MAX_LEN] = {0};
|
|
int err;
|
|
|
|
strscpy(info->driver, HINIC3_NIC_DRV_NAME, sizeof(info->driver));
|
|
strscpy(info->version, HINIC3_NIC_DRV_VERSION, sizeof(info->version));
|
|
strscpy(info->bus_info, pci_name(pdev), sizeof(info->bus_info));
|
|
|
|
err = hinic3_get_mgmt_version(nic_dev->hwdev, mgmt_ver,
|
|
HINIC3_MGMT_VERSION_MAX_LEN,
|
|
HINIC3_CHANNEL_NIC);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to get fw version\n");
|
|
return;
|
|
}
|
|
|
|
err = snprintf(info->fw_version, sizeof(info->fw_version), "%s", mgmt_ver);
|
|
if (err < 0)
|
|
nicif_err(nic_dev, drv, netdev, "Failed to snprintf fw version\n");
|
|
}
|
|
|
|
static u32 hinic3_get_msglevel(struct net_device *netdev)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
return nic_dev->msg_enable;
|
|
}
|
|
|
|
static void hinic3_set_msglevel(struct net_device *netdev, u32 data)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
nic_dev->msg_enable = data;
|
|
|
|
nicif_info(nic_dev, drv, netdev, "Set message level: 0x%x\n", data);
|
|
}
|
|
|
|
static int hinic3_nway_reset(struct net_device *netdev)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct nic_port_info port_info = {0};
|
|
int err;
|
|
|
|
while (test_and_set_bit(HINIC3_AUTONEG_RESET, &nic_dev->flags))
|
|
msleep(100); /* sleep 100 ms, waiting for another autoneg restart progress done */
|
|
|
|
err = hinic3_get_port_info(nic_dev->hwdev, &port_info, HINIC3_CHANNEL_NIC);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Get port info failed\n");
|
|
err = -EFAULT;
|
|
goto reset_err;
|
|
}
|
|
|
|
if (port_info.autoneg_state != PORT_CFG_AN_ON) {
|
|
nicif_err(nic_dev, drv, netdev, "Autonegotiation is not on, don't support to restart it\n");
|
|
err = -EOPNOTSUPP;
|
|
goto reset_err;
|
|
}
|
|
|
|
err = hinic3_set_autoneg(nic_dev->hwdev, false);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Set autonegotiation off failed\n");
|
|
err = -EFAULT;
|
|
goto reset_err;
|
|
}
|
|
|
|
msleep(200); /* sleep 200 ms, waiting for status polling finished */
|
|
|
|
err = hinic3_set_autoneg(nic_dev->hwdev, true);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Set autonegotiation on failed\n");
|
|
err = -EFAULT;
|
|
goto reset_err;
|
|
}
|
|
|
|
msleep(200); /* sleep 200 ms, waiting for status polling finished */
|
|
nicif_info(nic_dev, drv, netdev, "Restart autonegotiation successfully\n");
|
|
|
|
reset_err:
|
|
clear_bit(HINIC3_AUTONEG_RESET, &nic_dev->flags);
|
|
return err;
|
|
}
|
|
|
|
static void hinic3_get_ringparam(struct net_device *netdev,
|
|
struct ethtool_ringparam *ring,
|
|
struct kernel_ethtool_ringparam *kernel_ring,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
ring->rx_max_pending = HINIC3_MAX_RX_QUEUE_DEPTH;
|
|
ring->tx_max_pending = HINIC3_MAX_TX_QUEUE_DEPTH;
|
|
ring->rx_pending = nic_dev->rxqs[0].q_depth;
|
|
ring->tx_pending = nic_dev->txqs[0].q_depth;
|
|
}
|
|
|
|
static void hinic3_update_qp_depth(struct hinic3_nic_dev *nic_dev,
|
|
u32 sq_depth, u32 rq_depth)
|
|
{
|
|
u16 i;
|
|
|
|
nic_dev->q_params.sq_depth = sq_depth;
|
|
nic_dev->q_params.rq_depth = rq_depth;
|
|
for (i = 0; i < nic_dev->max_qps; i++) {
|
|
nic_dev->txqs[i].q_depth = sq_depth;
|
|
nic_dev->txqs[i].q_mask = sq_depth - 1;
|
|
nic_dev->rxqs[i].q_depth = rq_depth;
|
|
nic_dev->rxqs[i].q_mask = rq_depth - 1;
|
|
}
|
|
}
|
|
|
|
static int check_ringparam_valid(struct net_device *netdev,
|
|
const struct ethtool_ringparam *ring)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
if (ring->rx_jumbo_pending || ring->rx_mini_pending) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Unsupported rx_jumbo_pending/rx_mini_pending\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ring->tx_pending > HINIC3_MAX_TX_QUEUE_DEPTH ||
|
|
ring->tx_pending < HINIC3_MIN_QUEUE_DEPTH ||
|
|
ring->rx_pending > HINIC3_MAX_RX_QUEUE_DEPTH ||
|
|
ring->rx_pending < HINIC3_MIN_QUEUE_DEPTH) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Queue depth out of rang tx[%d-%d] rx[%d-%d]\n",
|
|
HINIC3_MIN_QUEUE_DEPTH, HINIC3_MAX_TX_QUEUE_DEPTH,
|
|
HINIC3_MIN_QUEUE_DEPTH, HINIC3_MAX_RX_QUEUE_DEPTH);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_set_ringparam(struct net_device *netdev,
|
|
struct ethtool_ringparam *ring,
|
|
struct kernel_ethtool_ringparam *kernel_ring,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic3_dyna_txrxq_params q_params = {0};
|
|
u32 new_sq_depth, new_rq_depth;
|
|
int err;
|
|
|
|
err = check_ringparam_valid(netdev, ring);
|
|
if (err)
|
|
return err;
|
|
|
|
new_sq_depth = (u32)(1U << (u16)ilog2(ring->tx_pending));
|
|
new_rq_depth = (u32)(1U << (u16)ilog2(ring->rx_pending));
|
|
if (new_sq_depth == nic_dev->q_params.sq_depth &&
|
|
new_rq_depth == nic_dev->q_params.rq_depth)
|
|
return 0; /* nothing to do */
|
|
|
|
nicif_info(nic_dev, drv, netdev,
|
|
"Change Tx/Rx ring depth from %u/%u to %u/%u\n",
|
|
nic_dev->q_params.sq_depth, nic_dev->q_params.rq_depth,
|
|
new_sq_depth, new_rq_depth);
|
|
|
|
if (!netif_running(netdev)) {
|
|
hinic3_update_qp_depth(nic_dev, new_sq_depth, new_rq_depth);
|
|
} else {
|
|
q_params = nic_dev->q_params;
|
|
q_params.sq_depth = new_sq_depth;
|
|
q_params.rq_depth = new_rq_depth;
|
|
q_params.txqs_res = NULL;
|
|
q_params.rxqs_res = NULL;
|
|
q_params.irq_cfg = NULL;
|
|
|
|
nicif_info(nic_dev, drv, netdev, "Restarting channel\n");
|
|
err = hinic3_change_channel_settings(nic_dev, &q_params,
|
|
NULL, NULL);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to change channel settings\n");
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *coal, u16 queue)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic3_intr_coal_info *interrupt_info = NULL;
|
|
|
|
if (queue == COALESCE_ALL_QUEUE) {
|
|
/* get tx/rx irq0 as default parameters */
|
|
interrupt_info = &nic_dev->intr_coalesce[0];
|
|
} else {
|
|
if (queue >= nic_dev->q_params.num_qps) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Invalid queue_id: %u\n", queue);
|
|
return -EINVAL;
|
|
}
|
|
interrupt_info = &nic_dev->intr_coalesce[queue];
|
|
}
|
|
|
|
/* coalescs_timer is in unit of 5us */
|
|
coal->rx_coalesce_usecs = interrupt_info->coalesce_timer_cfg *
|
|
COALESCE_TIMER_CFG_UNIT;
|
|
/* coalescs_frams is in unit of 8 */
|
|
coal->rx_max_coalesced_frames = interrupt_info->pending_limt *
|
|
COALESCE_PENDING_LIMIT_UNIT;
|
|
|
|
/* tx/rx use the same interrupt */
|
|
coal->tx_coalesce_usecs = coal->rx_coalesce_usecs;
|
|
coal->tx_max_coalesced_frames = coal->rx_max_coalesced_frames;
|
|
coal->use_adaptive_rx_coalesce = nic_dev->adaptive_rx_coal;
|
|
|
|
coal->pkt_rate_high = (u32)interrupt_info->pkt_rate_high;
|
|
coal->rx_coalesce_usecs_high = interrupt_info->rx_usecs_high *
|
|
COALESCE_TIMER_CFG_UNIT;
|
|
coal->rx_max_coalesced_frames_high =
|
|
interrupt_info->rx_pending_limt_high *
|
|
COALESCE_PENDING_LIMIT_UNIT;
|
|
|
|
coal->pkt_rate_low = (u32)interrupt_info->pkt_rate_low;
|
|
coal->rx_coalesce_usecs_low = interrupt_info->rx_usecs_low *
|
|
COALESCE_TIMER_CFG_UNIT;
|
|
coal->rx_max_coalesced_frames_low =
|
|
interrupt_info->rx_pending_limt_low *
|
|
COALESCE_PENDING_LIMIT_UNIT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_queue_coalesce(struct hinic3_nic_dev *nic_dev, u16 q_id,
|
|
struct hinic3_intr_coal_info *coal)
|
|
{
|
|
struct hinic3_intr_coal_info *intr_coal;
|
|
struct interrupt_info info = {0};
|
|
struct net_device *netdev = nic_dev->netdev;
|
|
int err;
|
|
|
|
intr_coal = &nic_dev->intr_coalesce[q_id];
|
|
if (intr_coal->coalesce_timer_cfg != coal->coalesce_timer_cfg ||
|
|
intr_coal->pending_limt != coal->pending_limt)
|
|
intr_coal->user_set_intr_coal_flag = 1;
|
|
|
|
intr_coal->coalesce_timer_cfg = coal->coalesce_timer_cfg;
|
|
intr_coal->pending_limt = coal->pending_limt;
|
|
intr_coal->pkt_rate_low = coal->pkt_rate_low;
|
|
intr_coal->rx_usecs_low = coal->rx_usecs_low;
|
|
intr_coal->rx_pending_limt_low = coal->rx_pending_limt_low;
|
|
intr_coal->pkt_rate_high = coal->pkt_rate_high;
|
|
intr_coal->rx_usecs_high = coal->rx_usecs_high;
|
|
intr_coal->rx_pending_limt_high = coal->rx_pending_limt_high;
|
|
|
|
/* netdev not running or qp not in using,
|
|
* don't need to set coalesce to hw
|
|
*/
|
|
if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags) ||
|
|
q_id >= nic_dev->q_params.num_qps || nic_dev->adaptive_rx_coal)
|
|
return 0;
|
|
|
|
info.msix_index = nic_dev->q_params.irq_cfg[q_id].msix_entry_idx;
|
|
info.lli_set = 0;
|
|
info.interrupt_coalesc_set = 1;
|
|
info.coalesc_timer_cfg = intr_coal->coalesce_timer_cfg;
|
|
info.pending_limt = intr_coal->pending_limt;
|
|
info.resend_timer_cfg = intr_coal->resend_timer_cfg;
|
|
nic_dev->rxqs[q_id].last_coalesc_timer_cfg =
|
|
intr_coal->coalesce_timer_cfg;
|
|
nic_dev->rxqs[q_id].last_pending_limt = intr_coal->pending_limt;
|
|
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info,
|
|
HINIC3_CHANNEL_NIC);
|
|
if (err)
|
|
nicif_warn(nic_dev, drv, netdev,
|
|
"Failed to set queue%u coalesce", q_id);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int is_coalesce_exceed_limit(struct net_device *netdev,
|
|
const struct ethtool_coalesce *coal)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
if (coal->rx_coalesce_usecs > COALESCE_MAX_TIMER_CFG) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"rx_coalesce_usecs out of range[%d-%d]\n", 0,
|
|
COALESCE_MAX_TIMER_CFG);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (coal->rx_max_coalesced_frames > COALESCE_MAX_PENDING_LIMIT) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"rx_max_coalesced_frames out of range[%d-%d]\n", 0,
|
|
COALESCE_MAX_PENDING_LIMIT);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (coal->rx_coalesce_usecs_low > COALESCE_MAX_TIMER_CFG) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"rx_coalesce_usecs_low out of range[%d-%d]\n", 0,
|
|
COALESCE_MAX_TIMER_CFG);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (coal->rx_max_coalesced_frames_low > COALESCE_MAX_PENDING_LIMIT) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"rx_max_coalesced_frames_low out of range[%d-%d]\n",
|
|
0, COALESCE_MAX_PENDING_LIMIT);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (coal->rx_coalesce_usecs_high > COALESCE_MAX_TIMER_CFG) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"rx_coalesce_usecs_high out of range[%d-%d]\n", 0,
|
|
COALESCE_MAX_TIMER_CFG);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (coal->rx_max_coalesced_frames_high > COALESCE_MAX_PENDING_LIMIT) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"rx_max_coalesced_frames_high out of range[%d-%d]\n",
|
|
0, COALESCE_MAX_PENDING_LIMIT);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int is_coalesce_legal(struct net_device *netdev,
|
|
const struct ethtool_coalesce *coal)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct ethtool_coalesce tmp_coal = {0};
|
|
int err;
|
|
|
|
if (coal->rx_coalesce_usecs != coal->tx_coalesce_usecs) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"tx-usecs must be equal to rx-usecs\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (coal->rx_max_coalesced_frames != coal->tx_max_coalesced_frames) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"tx-frames must be equal to rx-frames\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tmp_coal.cmd = coal->cmd;
|
|
tmp_coal.rx_coalesce_usecs = coal->rx_coalesce_usecs;
|
|
tmp_coal.rx_max_coalesced_frames = coal->rx_max_coalesced_frames;
|
|
tmp_coal.tx_coalesce_usecs = coal->tx_coalesce_usecs;
|
|
tmp_coal.tx_max_coalesced_frames = coal->tx_max_coalesced_frames;
|
|
tmp_coal.use_adaptive_rx_coalesce = coal->use_adaptive_rx_coalesce;
|
|
|
|
tmp_coal.pkt_rate_low = coal->pkt_rate_low;
|
|
tmp_coal.rx_coalesce_usecs_low = coal->rx_coalesce_usecs_low;
|
|
tmp_coal.rx_max_coalesced_frames_low =
|
|
coal->rx_max_coalesced_frames_low;
|
|
|
|
tmp_coal.pkt_rate_high = coal->pkt_rate_high;
|
|
tmp_coal.rx_coalesce_usecs_high = coal->rx_coalesce_usecs_high;
|
|
tmp_coal.rx_max_coalesced_frames_high =
|
|
coal->rx_max_coalesced_frames_high;
|
|
|
|
if (memcmp(coal, &tmp_coal, sizeof(struct ethtool_coalesce))) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Only support to change rx/tx-usecs and rx/tx-frames\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
err = is_coalesce_exceed_limit(netdev, coal);
|
|
if (err)
|
|
return err;
|
|
|
|
if (coal->rx_coalesce_usecs_low / COALESCE_TIMER_CFG_UNIT >=
|
|
coal->rx_coalesce_usecs_high / COALESCE_TIMER_CFG_UNIT) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"coalesce_usecs_high(%u) must more than coalesce_usecs_low(%u), after dividing %d usecs unit\n",
|
|
coal->rx_coalesce_usecs_high,
|
|
coal->rx_coalesce_usecs_low,
|
|
COALESCE_TIMER_CFG_UNIT);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (coal->rx_max_coalesced_frames_low / COALESCE_PENDING_LIMIT_UNIT >=
|
|
coal->rx_max_coalesced_frames_high / COALESCE_PENDING_LIMIT_UNIT) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"coalesced_frames_high(%u) must more than coalesced_frames_low(%u),after dividing %d frames unit\n",
|
|
coal->rx_max_coalesced_frames_high,
|
|
coal->rx_max_coalesced_frames_low,
|
|
COALESCE_PENDING_LIMIT_UNIT);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (coal->pkt_rate_low >= coal->pkt_rate_high) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"pkt_rate_high(%u) must more than pkt_rate_low(%u)\n",
|
|
coal->pkt_rate_high,
|
|
coal->pkt_rate_low);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void check_coalesce_align(struct hinic3_nic_dev *nic_dev, struct net_device *netdev,
|
|
u32 item, u32 unit, char *str)
|
|
{
|
|
if (item % unit)
|
|
nicif_warn(nic_dev, drv, netdev, "%s in %d units, change to %u\n",
|
|
str, unit, item - item % unit);
|
|
}
|
|
|
|
#define CHECK_COALESCE_ALIGN(member, unit) \
|
|
check_coalesce_align(nic_dev, netdev, member, unit, #member)
|
|
|
|
static inline void check_coalesce_changed(struct hinic3_nic_dev *nic_dev, struct net_device *netdev,
|
|
u32 item, u32 unit, u32 ori_val, char *obj_str, char *str)
|
|
{
|
|
if ((item / unit) != ori_val)
|
|
nicif_info(nic_dev, drv, netdev, "Change %s from %d to %u %s\n",
|
|
str, ori_val * unit, item - item % unit, obj_str);
|
|
}
|
|
|
|
#define CHECK_COALESCE_CHANGED(member, unit, ori_val, obj_str) \
|
|
check_coalesce_changed(nic_dev, netdev, member, unit, ori_val, obj_str, #member)
|
|
|
|
static inline void check_pkt_rate_changed(struct hinic3_nic_dev *nic_dev, struct net_device *netdev,
|
|
u32 item, u32 ori_val, char *obj_str, char *str)
|
|
{
|
|
if (item != ori_val)
|
|
nicif_info(nic_dev, drv, netdev, "Change %s from %d to %u %s\n",
|
|
str, ori_val, item, obj_str);
|
|
}
|
|
|
|
#define CHECK_PKT_RATE_CHANGED(member, ori_val, obj_str) \
|
|
check_pkt_rate_changed(nic_dev, netdev, member, ori_val, obj_str, #member)
|
|
|
|
static int set_hw_coal_param(struct hinic3_nic_dev *nic_dev,
|
|
struct hinic3_intr_coal_info *intr_coal, u16 queue)
|
|
{
|
|
u16 i;
|
|
|
|
if (queue == COALESCE_ALL_QUEUE) {
|
|
for (i = 0; i < nic_dev->max_qps; i++)
|
|
set_queue_coalesce(nic_dev, i, intr_coal);
|
|
} else {
|
|
if (queue >= nic_dev->q_params.num_qps) {
|
|
nicif_err(nic_dev, drv, nic_dev->netdev,
|
|
"Invalid queue_id: %u\n", queue);
|
|
return -EINVAL;
|
|
}
|
|
set_queue_coalesce(nic_dev, queue, intr_coal);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *coal, u16 queue)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct hinic3_intr_coal_info intr_coal = {0};
|
|
struct hinic3_intr_coal_info *ori_intr_coal = NULL;
|
|
u32 last_adaptive_rx;
|
|
char obj_str[32] = {0};
|
|
int err = 0;
|
|
|
|
err = is_coalesce_legal(netdev, coal);
|
|
if (err)
|
|
return err;
|
|
|
|
CHECK_COALESCE_ALIGN(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT);
|
|
CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames,
|
|
COALESCE_PENDING_LIMIT_UNIT);
|
|
CHECK_COALESCE_ALIGN(coal->rx_coalesce_usecs_high,
|
|
COALESCE_TIMER_CFG_UNIT);
|
|
CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_high,
|
|
COALESCE_PENDING_LIMIT_UNIT);
|
|
CHECK_COALESCE_ALIGN(coal->rx_coalesce_usecs_low,
|
|
COALESCE_TIMER_CFG_UNIT);
|
|
CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_low,
|
|
COALESCE_PENDING_LIMIT_UNIT);
|
|
|
|
if (queue == COALESCE_ALL_QUEUE) {
|
|
ori_intr_coal = &nic_dev->intr_coalesce[0];
|
|
snprintf(obj_str, sizeof(obj_str), "for netdev");
|
|
} else {
|
|
ori_intr_coal = &nic_dev->intr_coalesce[queue];
|
|
snprintf(obj_str, sizeof(obj_str), "for queue %u", queue);
|
|
}
|
|
CHECK_COALESCE_CHANGED(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT,
|
|
ori_intr_coal->coalesce_timer_cfg, obj_str);
|
|
CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames,
|
|
COALESCE_PENDING_LIMIT_UNIT,
|
|
ori_intr_coal->pending_limt, obj_str);
|
|
CHECK_PKT_RATE_CHANGED(coal->pkt_rate_high,
|
|
ori_intr_coal->pkt_rate_high, obj_str);
|
|
CHECK_COALESCE_CHANGED(coal->rx_coalesce_usecs_high,
|
|
COALESCE_TIMER_CFG_UNIT,
|
|
ori_intr_coal->rx_usecs_high, obj_str);
|
|
CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_high,
|
|
COALESCE_PENDING_LIMIT_UNIT,
|
|
ori_intr_coal->rx_pending_limt_high, obj_str);
|
|
CHECK_PKT_RATE_CHANGED(coal->pkt_rate_low,
|
|
ori_intr_coal->pkt_rate_low, obj_str);
|
|
CHECK_COALESCE_CHANGED(coal->rx_coalesce_usecs_low,
|
|
COALESCE_TIMER_CFG_UNIT,
|
|
ori_intr_coal->rx_usecs_low, obj_str);
|
|
CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_low,
|
|
COALESCE_PENDING_LIMIT_UNIT,
|
|
ori_intr_coal->rx_pending_limt_low, obj_str);
|
|
|
|
intr_coal.coalesce_timer_cfg =
|
|
(u8)(coal->rx_coalesce_usecs / COALESCE_TIMER_CFG_UNIT);
|
|
intr_coal.pending_limt = (u8)(coal->rx_max_coalesced_frames /
|
|
COALESCE_PENDING_LIMIT_UNIT);
|
|
|
|
last_adaptive_rx = nic_dev->adaptive_rx_coal;
|
|
nic_dev->adaptive_rx_coal = coal->use_adaptive_rx_coalesce;
|
|
|
|
intr_coal.pkt_rate_high = coal->pkt_rate_high;
|
|
intr_coal.rx_usecs_high =
|
|
(u8)(coal->rx_coalesce_usecs_high / COALESCE_TIMER_CFG_UNIT);
|
|
intr_coal.rx_pending_limt_high =
|
|
(u8)(coal->rx_max_coalesced_frames_high /
|
|
COALESCE_PENDING_LIMIT_UNIT);
|
|
|
|
intr_coal.pkt_rate_low = coal->pkt_rate_low;
|
|
intr_coal.rx_usecs_low =
|
|
(u8)(coal->rx_coalesce_usecs_low / COALESCE_TIMER_CFG_UNIT);
|
|
intr_coal.rx_pending_limt_low =
|
|
(u8)(coal->rx_max_coalesced_frames_low /
|
|
COALESCE_PENDING_LIMIT_UNIT);
|
|
|
|
/* coalesce timer or pending set to zero will disable coalesce */
|
|
if (!nic_dev->adaptive_rx_coal &&
|
|
(!intr_coal.coalesce_timer_cfg || !intr_coal.pending_limt))
|
|
nicif_warn(nic_dev, drv, netdev, "Coalesce will be disabled\n");
|
|
|
|
/* ensure coalesce paramester will not be changed in auto
|
|
* moderation work
|
|
*/
|
|
if (HINIC3_CHANNEL_RES_VALID(nic_dev)) {
|
|
if (!nic_dev->adaptive_rx_coal)
|
|
cancel_delayed_work_sync(&nic_dev->moderation_task);
|
|
else if (!last_adaptive_rx)
|
|
queue_delayed_work(nic_dev->workq,
|
|
&nic_dev->moderation_task,
|
|
HINIC3_MODERATONE_DELAY);
|
|
}
|
|
|
|
return set_hw_coal_param(nic_dev, &intr_coal, queue);
|
|
}
|
|
|
|
static int hinic3_get_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *coal,
|
|
struct kernel_ethtool_coalesce *kernel_coal,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
return get_coalesce(netdev, coal, COALESCE_ALL_QUEUE);
|
|
}
|
|
|
|
static int hinic3_set_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *coal,
|
|
struct kernel_ethtool_coalesce *kernel_coal,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
return set_coalesce(netdev, coal, COALESCE_ALL_QUEUE);
|
|
}
|
|
|
|
#if defined(ETHTOOL_PERQUEUE) && defined(ETHTOOL_GCOALESCE)
|
|
static int hinic3_get_per_queue_coalesce(struct net_device *netdev, u32 queue,
|
|
struct ethtool_coalesce *coal)
|
|
{
|
|
return get_coalesce(netdev, coal, (u16)queue);
|
|
}
|
|
|
|
static int hinic3_set_per_queue_coalesce(struct net_device *netdev, u32 queue,
|
|
struct ethtool_coalesce *coal)
|
|
{
|
|
return set_coalesce(netdev, coal, (u16)queue);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_ETHTOOL_SET_PHYS_ID
|
|
static int hinic3_set_phys_id(struct net_device *netdev,
|
|
enum ethtool_phys_id_state state)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
int err;
|
|
|
|
switch (state) {
|
|
case ETHTOOL_ID_ACTIVE:
|
|
err = hinic3_set_led_status(nic_dev->hwdev,
|
|
MAG_CMD_LED_TYPE_ALARM,
|
|
MAG_CMD_LED_MODE_FORCE_BLINK_2HZ);
|
|
if (err)
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Set LED blinking in 2HZ failed\n");
|
|
else
|
|
nicif_info(nic_dev, drv, netdev,
|
|
"Set LED blinking in 2HZ success\n");
|
|
break;
|
|
|
|
case ETHTOOL_ID_INACTIVE:
|
|
err = hinic3_set_led_status(nic_dev->hwdev,
|
|
MAG_CMD_LED_TYPE_ALARM,
|
|
MAG_CMD_LED_MODE_DEFAULT);
|
|
if (err)
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Reset LED to original status failed\n");
|
|
else
|
|
nicif_info(nic_dev, drv, netdev,
|
|
"Reset LED to original status success\n");
|
|
break;
|
|
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
#else
|
|
static int hinic3_phys_id(struct net_device *netdev, u32 data)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
nicif_err(nic_dev, drv, netdev, "Not support to set phys id\n");
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
#endif
|
|
|
|
static void hinic3_get_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct nic_pause_config nic_pause = {0};
|
|
int err;
|
|
|
|
err = hinic3_get_pause_info(nic_dev->hwdev, &nic_pause);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Failed to get pauseparam from hw\n");
|
|
} else {
|
|
pause->autoneg = nic_pause.auto_neg == PORT_CFG_AN_ON ?
|
|
AUTONEG_ENABLE : AUTONEG_DISABLE;
|
|
pause->rx_pause = nic_pause.rx_pause;
|
|
pause->tx_pause = nic_pause.tx_pause;
|
|
}
|
|
}
|
|
|
|
static int hinic3_set_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
struct nic_pause_config nic_pause = {0};
|
|
struct nic_port_info port_info = {0};
|
|
u32 auto_neg;
|
|
int err;
|
|
|
|
err = hinic3_get_port_info(nic_dev->hwdev, &port_info,
|
|
HINIC3_CHANNEL_NIC);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Failed to get auto-negotiation state\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
auto_neg = port_info.autoneg_state == PORT_CFG_AN_ON ? AUTONEG_ENABLE : AUTONEG_DISABLE;
|
|
if (pause->autoneg != auto_neg) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"To change autoneg please use: ethtool -s <dev> autoneg <on|off>\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
nic_pause.auto_neg = pause->autoneg == AUTONEG_ENABLE ? PORT_CFG_AN_ON : PORT_CFG_AN_OFF;
|
|
nic_pause.rx_pause = (u8)pause->rx_pause;
|
|
nic_pause.tx_pause = (u8)pause->tx_pause;
|
|
|
|
err = hinic3_set_pause_info(nic_dev->hwdev, nic_pause);
|
|
if (err) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to set pauseparam\n");
|
|
return err;
|
|
}
|
|
|
|
nicif_info(nic_dev, drv, netdev, "Set pause options, tx: %s, rx: %s\n",
|
|
pause->tx_pause ? "on" : "off",
|
|
pause->rx_pause ? "on" : "off");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef ETHTOOL_GMODULEEEPROM
|
|
static int hinic3_get_module_info(struct net_device *netdev,
|
|
struct ethtool_modinfo *modinfo)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u8 sfp_type = 0;
|
|
u8 sfp_type_ext = 0;
|
|
int err;
|
|
|
|
err = hinic3_get_sfp_type(nic_dev->hwdev, &sfp_type, &sfp_type_ext);
|
|
if (err)
|
|
return err;
|
|
|
|
switch (sfp_type) {
|
|
case MODULE_TYPE_SFP:
|
|
modinfo->type = ETH_MODULE_SFF_8472;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
|
|
break;
|
|
case MODULE_TYPE_QSFP:
|
|
modinfo->type = ETH_MODULE_SFF_8436;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN;
|
|
break;
|
|
case MODULE_TYPE_QSFP_PLUS:
|
|
if (sfp_type_ext >= 0x3) {
|
|
modinfo->type = ETH_MODULE_SFF_8636;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8636_MAX_LEN;
|
|
} else {
|
|
modinfo->type = ETH_MODULE_SFF_8436;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN;
|
|
}
|
|
break;
|
|
case MODULE_TYPE_QSFP28:
|
|
modinfo->type = ETH_MODULE_SFF_8636;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8636_MAX_LEN;
|
|
break;
|
|
default:
|
|
nicif_warn(nic_dev, drv, netdev,
|
|
"Optical module unknown: 0x%x\n", sfp_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_get_module_eeprom(struct net_device *netdev,
|
|
struct ethtool_eeprom *ee, u8 *data)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u8 sfp_data[STD_SFP_INFO_MAX_SIZE];
|
|
int err;
|
|
|
|
if (!ee->len || ((ee->len + ee->offset) > STD_SFP_INFO_MAX_SIZE))
|
|
return -EINVAL;
|
|
|
|
memset(data, 0, ee->len);
|
|
|
|
err = hinic3_get_sfp_eeprom(nic_dev->hwdev, (u8 *)sfp_data, ee->len);
|
|
if (err)
|
|
return err;
|
|
|
|
memcpy(data, sfp_data + ee->offset, ee->len);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* ETHTOOL_GMODULEEEPROM */
|
|
|
|
#define HINIC3_PRIV_FLAGS_SYMM_RSS BIT(0)
|
|
#define HINIC3_PRIV_FLAGS_LINK_UP BIT(1)
|
|
#define HINIC3_PRIV_FLAGS_RXQ_RECOVERY BIT(2)
|
|
|
|
static u32 hinic3_get_priv_flags(struct net_device *netdev)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u32 priv_flags = 0;
|
|
|
|
if (test_bit(HINIC3_SAME_RXTX, &nic_dev->flags))
|
|
priv_flags |= HINIC3_PRIV_FLAGS_SYMM_RSS;
|
|
|
|
if (test_bit(HINIC3_FORCE_LINK_UP, &nic_dev->flags))
|
|
priv_flags |= HINIC3_PRIV_FLAGS_LINK_UP;
|
|
|
|
if (test_bit(HINIC3_RXQ_RECOVERY, &nic_dev->flags))
|
|
priv_flags |= HINIC3_PRIV_FLAGS_RXQ_RECOVERY;
|
|
|
|
return priv_flags;
|
|
}
|
|
|
|
int hinic3_set_rxq_recovery_flag(struct net_device *netdev, u32 priv_flags)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
if (priv_flags & HINIC3_PRIV_FLAGS_RXQ_RECOVERY) {
|
|
if (!HINIC3_SUPPORT_RXQ_RECOVERY(nic_dev->hwdev)) {
|
|
nicif_info(nic_dev, drv, netdev, "Unsupport open rxq recovery\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (test_and_set_bit(HINIC3_RXQ_RECOVERY, &nic_dev->flags))
|
|
return 0;
|
|
queue_delayed_work(nic_dev->workq, &nic_dev->rxq_check_work, HZ);
|
|
nicif_info(nic_dev, drv, netdev, "open rxq recovery\n");
|
|
} else {
|
|
if (!test_and_clear_bit(HINIC3_RXQ_RECOVERY, &nic_dev->flags))
|
|
return 0;
|
|
cancel_delayed_work_sync(&nic_dev->rxq_check_work);
|
|
nicif_info(nic_dev, drv, netdev, "close rxq recovery\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_set_symm_rss_flag(struct net_device *netdev, u32 priv_flags)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
|
|
if (priv_flags & HINIC3_PRIV_FLAGS_SYMM_RSS) {
|
|
if (test_bit(HINIC3_DCB_ENABLE, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to open Symmetric RSS while DCB is enabled\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, netdev, "Failed to open Symmetric RSS while RSS is disabled\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
set_bit(HINIC3_SAME_RXTX, &nic_dev->flags);
|
|
} else {
|
|
clear_bit(HINIC3_SAME_RXTX, &nic_dev->flags);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_set_force_link_flag(struct net_device *netdev, u32 priv_flags)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
u8 link_status = 0;
|
|
int err;
|
|
|
|
if (priv_flags & HINIC3_PRIV_FLAGS_LINK_UP) {
|
|
if (test_and_set_bit(HINIC3_FORCE_LINK_UP, &nic_dev->flags))
|
|
return 0;
|
|
|
|
if (!HINIC3_CHANNEL_RES_VALID(nic_dev))
|
|
return 0;
|
|
|
|
if (netif_carrier_ok(netdev))
|
|
return 0;
|
|
|
|
nic_dev->link_status = true;
|
|
netif_carrier_on(netdev);
|
|
nicif_info(nic_dev, link, netdev, "Set link up\n");
|
|
|
|
if (!HINIC3_FUNC_IS_VF(nic_dev->hwdev))
|
|
hinic3_notify_all_vfs_link_changed(nic_dev->hwdev, nic_dev->link_status);
|
|
} else {
|
|
if (!test_and_clear_bit(HINIC3_FORCE_LINK_UP, &nic_dev->flags))
|
|
return 0;
|
|
|
|
if (!HINIC3_CHANNEL_RES_VALID(nic_dev))
|
|
return 0;
|
|
|
|
err = hinic3_get_link_state(nic_dev->hwdev, &link_status);
|
|
if (err) {
|
|
nicif_err(nic_dev, link, netdev, "Get link state err: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
nic_dev->link_status = link_status;
|
|
|
|
if (link_status) {
|
|
if (netif_carrier_ok(netdev))
|
|
return 0;
|
|
|
|
netif_carrier_on(netdev);
|
|
nicif_info(nic_dev, link, netdev, "Link state is up\n");
|
|
} else {
|
|
if (!netif_carrier_ok(netdev))
|
|
return 0;
|
|
|
|
netif_carrier_off(netdev);
|
|
nicif_info(nic_dev, link, netdev, "Link state is down\n");
|
|
}
|
|
|
|
if (!HINIC3_FUNC_IS_VF(nic_dev->hwdev))
|
|
hinic3_notify_all_vfs_link_changed(nic_dev->hwdev, nic_dev->link_status);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hinic3_set_priv_flags(struct net_device *netdev, u32 priv_flags)
|
|
{
|
|
int err;
|
|
|
|
err = hinic3_set_symm_rss_flag(netdev, priv_flags);
|
|
if (err)
|
|
return err;
|
|
|
|
err = hinic3_set_rxq_recovery_flag(netdev, priv_flags);
|
|
if (err)
|
|
return err;
|
|
|
|
return hinic3_set_force_link_flag(netdev, priv_flags);
|
|
}
|
|
|
|
#define PORT_DOWN_ERR_IDX 0
|
|
#define LP_DEFAULT_TIME 5 /* seconds */
|
|
#define LP_PKT_LEN 60
|
|
|
|
#define TEST_TIME_MULTIPLE 5
|
|
static int hinic3_run_lp_test(struct hinic3_nic_dev *nic_dev, u32 test_time)
|
|
{
|
|
u8 *lb_test_rx_buf = nic_dev->lb_test_rx_buf;
|
|
struct net_device *netdev = nic_dev->netdev;
|
|
u32 cnt = test_time * TEST_TIME_MULTIPLE;
|
|
struct sk_buff *skb_tmp = NULL;
|
|
struct ethhdr *eth_hdr = NULL;
|
|
struct sk_buff *skb = NULL;
|
|
u8 *test_data = NULL;
|
|
u32 i;
|
|
u8 j;
|
|
|
|
skb_tmp = alloc_skb(LP_PKT_LEN, GFP_ATOMIC);
|
|
if (!skb_tmp)
|
|
return -ENOMEM;
|
|
|
|
eth_hdr = __skb_put(skb_tmp, ETH_HLEN);
|
|
eth_hdr->h_proto = htons(ETH_P_ARP);
|
|
ether_addr_copy(eth_hdr->h_dest, nic_dev->netdev->dev_addr);
|
|
eth_zero_addr(eth_hdr->h_source);
|
|
skb_reset_mac_header(skb_tmp);
|
|
|
|
test_data = __skb_put(skb_tmp, LP_PKT_LEN - ETH_HLEN);
|
|
for (i = ETH_HLEN; i < LP_PKT_LEN; i++)
|
|
test_data[i] = i & 0xFF;
|
|
|
|
skb_tmp->queue_mapping = 0;
|
|
skb_tmp->dev = netdev;
|
|
skb_tmp->protocol = htons(ETH_P_ARP);
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
nic_dev->lb_test_rx_idx = 0;
|
|
memset(lb_test_rx_buf, 0, LP_PKT_CNT * LP_PKT_LEN);
|
|
|
|
for (j = 0; j < LP_PKT_CNT; j++) {
|
|
skb = pskb_copy(skb_tmp, GFP_ATOMIC);
|
|
if (!skb) {
|
|
dev_kfree_skb_any(skb_tmp);
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Copy skb failed for loopback test\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* mark index for every pkt */
|
|
skb->data[LP_PKT_LEN - 1] = j;
|
|
|
|
if (hinic3_lb_xmit_frame(skb, netdev)) {
|
|
dev_kfree_skb_any(skb);
|
|
dev_kfree_skb_any(skb_tmp);
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Xmit pkt failed for loopback test\n");
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
/* wait till all pkts received to RX buffer */
|
|
msleep(HINIC3_WAIT_PKTS_TO_RX_BUFFER);
|
|
|
|
for (j = 0; j < LP_PKT_CNT; j++) {
|
|
if (memcmp((lb_test_rx_buf + (j * LP_PKT_LEN)),
|
|
skb_tmp->data, (LP_PKT_LEN - 1)) ||
|
|
(*(lb_test_rx_buf + ((j * LP_PKT_LEN) +
|
|
(LP_PKT_LEN - 1))) != j)) {
|
|
dev_kfree_skb_any(skb_tmp);
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Compare pkt failed in loopback test(index=0x%02x, data[%d]=0x%02x)\n",
|
|
(j + (i * LP_PKT_CNT)),
|
|
(LP_PKT_LEN - 1),
|
|
*(lb_test_rx_buf +
|
|
(((j * LP_PKT_LEN) +
|
|
(LP_PKT_LEN - 1)))));
|
|
return -EIO;
|
|
}
|
|
}
|
|
}
|
|
|
|
dev_kfree_skb_any(skb_tmp);
|
|
nicif_info(nic_dev, drv, netdev, "Loopback test succeed.\n");
|
|
return 0;
|
|
}
|
|
|
|
enum diag_test_index {
|
|
INTERNAL_LP_TEST = 0,
|
|
EXTERNAL_LP_TEST = 1,
|
|
DIAG_TEST_MAX = 2,
|
|
};
|
|
|
|
#define HINIC3_INTERNAL_LP_MODE 5
|
|
static int do_lp_test(struct hinic3_nic_dev *nic_dev, u32 *flags, u32 test_time,
|
|
enum diag_test_index *test_index)
|
|
{
|
|
struct net_device *netdev = nic_dev->netdev;
|
|
u8 *lb_test_rx_buf = NULL;
|
|
int err = 0;
|
|
|
|
if (!(*flags & ETH_TEST_FL_EXTERNAL_LB)) {
|
|
*test_index = INTERNAL_LP_TEST;
|
|
if (hinic3_set_loopback_mode(nic_dev->hwdev,
|
|
HINIC3_INTERNAL_LP_MODE, true)) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Failed to set port loopback mode before loopback test\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* suspend 5000 ms, waiting for port to stop receiving frames */
|
|
msleep(5000);
|
|
} else {
|
|
*test_index = EXTERNAL_LP_TEST;
|
|
}
|
|
|
|
lb_test_rx_buf = vmalloc(LP_PKT_CNT * LP_PKT_LEN);
|
|
if (!lb_test_rx_buf) {
|
|
err = -ENOMEM;
|
|
} else {
|
|
nic_dev->lb_test_rx_buf = lb_test_rx_buf;
|
|
nic_dev->lb_pkt_len = LP_PKT_LEN;
|
|
set_bit(HINIC3_LP_TEST, &nic_dev->flags);
|
|
|
|
if (hinic3_run_lp_test(nic_dev, test_time))
|
|
err = -EFAULT;
|
|
|
|
clear_bit(HINIC3_LP_TEST, &nic_dev->flags);
|
|
msleep(HINIC3_WAIT_CLEAR_LP_TEST);
|
|
vfree(lb_test_rx_buf);
|
|
nic_dev->lb_test_rx_buf = NULL;
|
|
}
|
|
|
|
if (!(*flags & ETH_TEST_FL_EXTERNAL_LB)) {
|
|
if (hinic3_set_loopback_mode(nic_dev->hwdev,
|
|
HINIC3_INTERNAL_LP_MODE, false)) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Failed to cancel port loopback mode after loopback test\n");
|
|
err = -EFAULT;
|
|
}
|
|
} else {
|
|
*flags |= ETH_TEST_FL_EXTERNAL_LB_DONE;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void hinic3_lp_test(struct net_device *netdev, struct ethtool_test *eth_test,
|
|
u64 *data, u32 test_time)
|
|
{
|
|
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
|
|
enum diag_test_index test_index = 0;
|
|
u8 link_status = 0;
|
|
int err;
|
|
u32 test_time_real = test_time;
|
|
|
|
/* don't support loopback test when netdev is closed. */
|
|
if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags)) {
|
|
nicif_err(nic_dev, drv, netdev,
|
|
"Do not support loopback test when netdev is closed\n");
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
data[PORT_DOWN_ERR_IDX] = 1;
|
|
return;
|
|
}
|
|
if (test_time_real == 0)
|
|
test_time_real = LP_DEFAULT_TIME;
|
|
|
|
netif_carrier_off(netdev);
|
|
netif_tx_disable(netdev);
|
|
|
|
err = do_lp_test(nic_dev, ð_test->flags, test_time_real, &test_index);
|
|
if (err) {
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
data[test_index] = 1;
|
|
}
|
|
|
|
netif_tx_wake_all_queues(netdev);
|
|
|
|
err = hinic3_get_link_state(nic_dev->hwdev, &link_status);
|
|
if (!err && link_status)
|
|
netif_carrier_on(netdev);
|
|
}
|
|
|
|
static void hinic3_diag_test(struct net_device *netdev,
|
|
struct ethtool_test *eth_test, u64 *data)
|
|
{
|
|
memset(data, 0, DIAG_TEST_MAX * sizeof(u64));
|
|
|
|
hinic3_lp_test(netdev, eth_test, data, 0);
|
|
}
|
|
|
|
static const struct ethtool_ops hinic3_ethtool_ops = {
|
|
#ifdef SUPPORTED_COALESCE_PARAMS
|
|
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
|
|
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
|
|
#endif
|
|
#ifdef ETHTOOL_GLINKSETTINGS
|
|
#ifndef XENSERVER_HAVE_NEW_ETHTOOL_OPS
|
|
.get_link_ksettings = hinic3_get_link_ksettings,
|
|
.set_link_ksettings = hinic3_set_link_ksettings,
|
|
#endif
|
|
#endif
|
|
#ifndef HAVE_NEW_ETHTOOL_LINK_SETTINGS_ONLY
|
|
.get_settings = hinic3_get_settings,
|
|
.set_settings = hinic3_set_settings,
|
|
#endif
|
|
|
|
.get_drvinfo = hinic3_get_drvinfo,
|
|
.get_msglevel = hinic3_get_msglevel,
|
|
.set_msglevel = hinic3_set_msglevel,
|
|
.nway_reset = hinic3_nway_reset,
|
|
#ifdef CONFIG_MODULE_PROF
|
|
.get_link = hinic3_get_link,
|
|
#else
|
|
.get_link = ethtool_op_get_link,
|
|
#endif
|
|
.get_ringparam = hinic3_get_ringparam,
|
|
.set_ringparam = hinic3_set_ringparam,
|
|
.get_pauseparam = hinic3_get_pauseparam,
|
|
.set_pauseparam = hinic3_set_pauseparam,
|
|
.get_sset_count = hinic3_get_sset_count,
|
|
.get_ethtool_stats = hinic3_get_ethtool_stats,
|
|
.get_strings = hinic3_get_strings,
|
|
|
|
.self_test = hinic3_diag_test,
|
|
|
|
#ifndef HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT
|
|
#ifdef HAVE_ETHTOOL_SET_PHYS_ID
|
|
.set_phys_id = hinic3_set_phys_id,
|
|
#else
|
|
.phys_id = hinic3_phys_id,
|
|
#endif
|
|
#endif
|
|
|
|
.get_coalesce = hinic3_get_coalesce,
|
|
.set_coalesce = hinic3_set_coalesce,
|
|
#if defined(ETHTOOL_PERQUEUE) && defined(ETHTOOL_GCOALESCE)
|
|
.get_per_queue_coalesce = hinic3_get_per_queue_coalesce,
|
|
.set_per_queue_coalesce = hinic3_set_per_queue_coalesce,
|
|
#endif
|
|
|
|
.get_rxnfc = hinic3_get_rxnfc,
|
|
.set_rxnfc = hinic3_set_rxnfc,
|
|
.get_priv_flags = hinic3_get_priv_flags,
|
|
.set_priv_flags = hinic3_set_priv_flags,
|
|
|
|
#ifndef HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT
|
|
.get_channels = hinic3_get_channels,
|
|
.set_channels = hinic3_set_channels,
|
|
|
|
#ifdef ETHTOOL_GMODULEEEPROM
|
|
.get_module_info = hinic3_get_module_info,
|
|
.get_module_eeprom = hinic3_get_module_eeprom,
|
|
#endif
|
|
|
|
#ifndef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
.get_rxfh_indir_size = hinic3_get_rxfh_indir_size,
|
|
#endif
|
|
|
|
#if defined(ETHTOOL_GRSSH) && defined(ETHTOOL_SRSSH)
|
|
.get_rxfh_key_size = hinic3_get_rxfh_key_size,
|
|
.get_rxfh = hinic3_get_rxfh,
|
|
.set_rxfh = hinic3_set_rxfh,
|
|
#else
|
|
.get_rxfh_indir = hinic3_get_rxfh_indir,
|
|
.set_rxfh_indir = hinic3_set_rxfh_indir,
|
|
#endif
|
|
|
|
#endif /* HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT */
|
|
};
|
|
|
|
#ifdef HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT
|
|
static const struct ethtool_ops_ext hinic3_ethtool_ops_ext = {
|
|
.size = sizeof(struct ethtool_ops_ext),
|
|
.set_phys_id = hinic3_set_phys_id,
|
|
.get_channels = hinic3_get_channels,
|
|
.set_channels = hinic3_set_channels,
|
|
#ifdef ETHTOOL_GMODULEEEPROM
|
|
.get_module_info = hinic3_get_module_info,
|
|
.get_module_eeprom = hinic3_get_module_eeprom,
|
|
#endif
|
|
|
|
#ifndef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
.get_rxfh_indir_size = hinic3_get_rxfh_indir_size,
|
|
#endif
|
|
|
|
#if defined(ETHTOOL_GRSSH) && defined(ETHTOOL_SRSSH)
|
|
.get_rxfh_key_size = hinic3_get_rxfh_key_size,
|
|
.get_rxfh = hinic3_get_rxfh,
|
|
.set_rxfh = hinic3_set_rxfh,
|
|
#else
|
|
.get_rxfh_indir = hinic3_get_rxfh_indir,
|
|
.set_rxfh_indir = hinic3_set_rxfh_indir,
|
|
#endif
|
|
|
|
};
|
|
#endif /* HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT */
|
|
|
|
static const struct ethtool_ops hinic3vf_ethtool_ops = {
|
|
#ifdef SUPPORTED_COALESCE_PARAMS
|
|
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
|
|
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
|
|
#endif
|
|
#ifdef ETHTOOL_GLINKSETTINGS
|
|
#ifndef XENSERVER_HAVE_NEW_ETHTOOL_OPS
|
|
.get_link_ksettings = hinic3_get_link_ksettings,
|
|
#endif
|
|
#else
|
|
.get_settings = hinic3_get_settings,
|
|
#endif
|
|
.get_drvinfo = hinic3_get_drvinfo,
|
|
.get_msglevel = hinic3_get_msglevel,
|
|
.set_msglevel = hinic3_set_msglevel,
|
|
.get_link = ethtool_op_get_link,
|
|
.get_ringparam = hinic3_get_ringparam,
|
|
|
|
.set_ringparam = hinic3_set_ringparam,
|
|
.get_sset_count = hinic3_get_sset_count,
|
|
.get_ethtool_stats = hinic3_get_ethtool_stats,
|
|
.get_strings = hinic3_get_strings,
|
|
|
|
.get_coalesce = hinic3_get_coalesce,
|
|
.set_coalesce = hinic3_set_coalesce,
|
|
#if defined(ETHTOOL_PERQUEUE) && defined(ETHTOOL_GCOALESCE)
|
|
.get_per_queue_coalesce = hinic3_get_per_queue_coalesce,
|
|
.set_per_queue_coalesce = hinic3_set_per_queue_coalesce,
|
|
#endif
|
|
|
|
.get_rxnfc = hinic3_get_rxnfc,
|
|
.set_rxnfc = hinic3_set_rxnfc,
|
|
.get_priv_flags = hinic3_get_priv_flags,
|
|
.set_priv_flags = hinic3_set_priv_flags,
|
|
|
|
#ifndef HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT
|
|
.get_channels = hinic3_get_channels,
|
|
.set_channels = hinic3_set_channels,
|
|
|
|
#ifndef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
.get_rxfh_indir_size = hinic3_get_rxfh_indir_size,
|
|
#endif
|
|
|
|
#if defined(ETHTOOL_GRSSH) && defined(ETHTOOL_SRSSH)
|
|
.get_rxfh_key_size = hinic3_get_rxfh_key_size,
|
|
.get_rxfh = hinic3_get_rxfh,
|
|
.set_rxfh = hinic3_set_rxfh,
|
|
#else
|
|
.get_rxfh_indir = hinic3_get_rxfh_indir,
|
|
.set_rxfh_indir = hinic3_set_rxfh_indir,
|
|
#endif
|
|
|
|
#endif /* HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT */
|
|
};
|
|
|
|
#ifdef HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT
|
|
static const struct ethtool_ops_ext hinic3vf_ethtool_ops_ext = {
|
|
.size = sizeof(struct ethtool_ops_ext),
|
|
.get_channels = hinic3_get_channels,
|
|
.set_channels = hinic3_set_channels,
|
|
|
|
#ifndef NOT_HAVE_GET_RXFH_INDIR_SIZE
|
|
.get_rxfh_indir_size = hinic3_get_rxfh_indir_size,
|
|
#endif
|
|
|
|
#if defined(ETHTOOL_GRSSH) && defined(ETHTOOL_SRSSH)
|
|
.get_rxfh_key_size = hinic3_get_rxfh_key_size,
|
|
.get_rxfh = hinic3_get_rxfh,
|
|
.set_rxfh = hinic3_set_rxfh,
|
|
#else
|
|
.get_rxfh_indir = hinic3_get_rxfh_indir,
|
|
.set_rxfh_indir = hinic3_set_rxfh_indir,
|
|
#endif
|
|
|
|
};
|
|
#endif /* HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT */
|
|
|
|
void hinic3_set_ethtool_ops(struct net_device *netdev)
|
|
{
|
|
SET_ETHTOOL_OPS(netdev, &hinic3_ethtool_ops);
|
|
#ifdef HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT
|
|
set_ethtool_ops_ext(netdev, &hinic3_ethtool_ops_ext);
|
|
#endif /* HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT */
|
|
}
|
|
|
|
void hinic3vf_set_ethtool_ops(struct net_device *netdev)
|
|
{
|
|
SET_ETHTOOL_OPS(netdev, &hinic3vf_ethtool_ops);
|
|
#ifdef HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT
|
|
set_ethtool_ops_ext(netdev, &hinic3vf_ethtool_ops_ext);
|
|
#endif /* HAVE_RHEL6_ETHTOOL_OPS_EXT_STRUCT */
|
|
}
|
|
|