diff options
Diffstat (limited to 'drivers/net/qlcnic/qlcnic_ethtool.c')
-rw-r--r-- | drivers/net/qlcnic/qlcnic_ethtool.c | 123 |
1 files changed, 122 insertions, 1 deletions
diff --git a/drivers/net/qlcnic/qlcnic_ethtool.c b/drivers/net/qlcnic/qlcnic_ethtool.c index 31f5cba62223..743035e4538d 100644 --- a/drivers/net/qlcnic/qlcnic_ethtool.c +++ b/drivers/net/qlcnic/qlcnic_ethtool.c @@ -84,7 +84,8 @@ static const char qlcnic_device_gstrings_stats[][ETH_GSTRING_LEN] = { static const char qlcnic_gstrings_test[][ETH_GSTRING_LEN] = { "Register_Test_on_offline", "Link_Test_on_offline", - "Interrupt_Test_offline" + "Interrupt_Test_offline", + "Loopback_Test_offline" }; #define QLCNIC_TEST_LEN ARRAY_SIZE(qlcnic_gstrings_test) @@ -685,6 +686,123 @@ clear_it: return ret; } +#define QLCNIC_ILB_PKT_SIZE 64 +#define QLCNIC_NUM_ILB_PKT 16 +#define QLCNIC_ILB_MAX_RCV_LOOP 10 + +static void qlcnic_create_loopback_buff(unsigned char *data, u8 mac[]) +{ + unsigned char random_data[] = {0xa8, 0x06, 0x45, 0x00}; + + memset(data, 0x4e, QLCNIC_ILB_PKT_SIZE); + + memcpy(data, mac, ETH_ALEN); + memcpy(data + ETH_ALEN, mac, ETH_ALEN); + + memcpy(data + 2 * ETH_ALEN, random_data, sizeof(random_data)); +} + +int qlcnic_check_loopback_buff(unsigned char *data, u8 mac[]) +{ + unsigned char buff[QLCNIC_ILB_PKT_SIZE]; + qlcnic_create_loopback_buff(buff, mac); + return memcmp(data, buff, QLCNIC_ILB_PKT_SIZE); +} + +static int qlcnic_do_ilb_test(struct qlcnic_adapter *adapter) +{ + struct qlcnic_recv_context *recv_ctx = adapter->recv_ctx; + struct qlcnic_host_sds_ring *sds_ring = &recv_ctx->sds_rings[0]; + struct sk_buff *skb; + int i, loop, cnt = 0; + + for (i = 0; i < QLCNIC_NUM_ILB_PKT; i++) { + skb = dev_alloc_skb(QLCNIC_ILB_PKT_SIZE); + qlcnic_create_loopback_buff(skb->data, adapter->mac_addr); + skb_put(skb, QLCNIC_ILB_PKT_SIZE); + + adapter->diag_cnt = 0; + qlcnic_xmit_frame(skb, adapter->netdev); + + loop = 0; + do { + msleep(1); + qlcnic_process_rcv_ring_diag(sds_ring); + if (loop++ > QLCNIC_ILB_MAX_RCV_LOOP) + break; + } while (!adapter->diag_cnt); + + dev_kfree_skb_any(skb); + + if (!adapter->diag_cnt) + dev_warn(&adapter->pdev->dev, "ILB Test: %dth packet" + " not recevied\n", i + 1); + else + cnt++; + } + if (cnt != i) { + dev_warn(&adapter->pdev->dev, "ILB Test failed\n"); + return -1; + } + return 0; +} + +static int qlcnic_iloopback_test(struct net_device *netdev) +{ + struct qlcnic_adapter *adapter = netdev_priv(netdev); + int max_sds_rings = adapter->max_sds_rings; + struct qlcnic_host_sds_ring *sds_ring; + int loop = 0; + int ret; + + netdev_info(netdev, "%s: in progress\n", __func__); + if (adapter->op_mode == QLCNIC_NON_PRIV_FUNC) { + netdev_warn(netdev, "Loopback test not supported for non " + "privilege function\n"); + return 0; + } + + if (test_and_set_bit(__QLCNIC_RESETTING, &adapter->state)) + return -EIO; + + + ret = qlcnic_diag_alloc_res(netdev, QLCNIC_LOOPBACK_TEST); + if (ret) + goto clear_it; + + sds_ring = &adapter->recv_ctx->sds_rings[0]; + + ret = qlcnic_set_lb_mode(adapter, QLCNIC_ILB_MODE); + if (ret) + goto free_res; + + do { + msleep(500); + qlcnic_process_rcv_ring_diag(sds_ring); + if (loop++ > QLCNIC_ILB_MAX_RCV_LOOP) + break; + } while (!QLCNIC_IS_LB_CONFIGURED(adapter->ahw->loopback_state)); + + if (!QLCNIC_IS_LB_CONFIGURED(adapter->ahw->loopback_state)) { + netdev_info(netdev, "firmware didnt respond to loopback " + "configure request\n"); + ret = adapter->ahw->loopback_state; + goto free_res; + } + + ret = qlcnic_do_ilb_test(adapter); + + qlcnic_clear_lb_mode(adapter); + + free_res: + qlcnic_diag_free_res(netdev, max_sds_rings); + + clear_it: + adapter->max_sds_rings = max_sds_rings; + clear_bit(__QLCNIC_RESETTING, &adapter->state); + return ret; +} + static void qlcnic_diag_test(struct net_device *dev, struct ethtool_test *eth_test, u64 *data) @@ -704,6 +822,9 @@ qlcnic_diag_test(struct net_device *dev, struct ethtool_test *eth_test, if (data[2]) eth_test->flags |= ETH_TEST_FL_FAILED; + data[3] = qlcnic_iloopback_test(dev); + if (data[3]) + eth_test->flags |= ETH_TEST_FL_FAILED; } } |