|
@@ -9,6 +9,7 @@
|
|
|
#include <linux/version.h>
|
|
|
#include <linux/types.h>
|
|
|
#include <linux/netdevice.h>
|
|
|
+#include <linux/etherdevice.h>
|
|
|
#include <linux/ethtool.h>
|
|
|
#include <linux/string.h>
|
|
|
#include <linux/pci.h>
|
|
@@ -27,6 +28,9 @@
|
|
|
#define QEDE_RQSTAT_STRING(stat_name) (#stat_name)
|
|
|
#define QEDE_RQSTAT(stat_name) \
|
|
|
{QEDE_RQSTAT_OFFSET(stat_name), QEDE_RQSTAT_STRING(stat_name)}
|
|
|
+
|
|
|
+#define QEDE_SELFTEST_POLL_COUNT 100
|
|
|
+
|
|
|
static const struct {
|
|
|
u64 offset;
|
|
|
char string[ETH_GSTRING_LEN];
|
|
@@ -125,6 +129,23 @@ static const char qede_private_arr[QEDE_PRI_FLAG_LEN][ETH_GSTRING_LEN] = {
|
|
|
"Coupled-Function",
|
|
|
};
|
|
|
|
|
|
+enum qede_ethtool_tests {
|
|
|
+ QEDE_ETHTOOL_INT_LOOPBACK,
|
|
|
+ QEDE_ETHTOOL_INTERRUPT_TEST,
|
|
|
+ QEDE_ETHTOOL_MEMORY_TEST,
|
|
|
+ QEDE_ETHTOOL_REGISTER_TEST,
|
|
|
+ QEDE_ETHTOOL_CLOCK_TEST,
|
|
|
+ QEDE_ETHTOOL_TEST_MAX
|
|
|
+};
|
|
|
+
|
|
|
+static const char qede_tests_str_arr[QEDE_ETHTOOL_TEST_MAX][ETH_GSTRING_LEN] = {
|
|
|
+ "Internal loopback (offline)",
|
|
|
+ "Interrupt (online)\t",
|
|
|
+ "Memory (online)\t\t",
|
|
|
+ "Register (online)\t",
|
|
|
+ "Clock (online)\t\t",
|
|
|
+};
|
|
|
+
|
|
|
static void qede_get_strings_stats(struct qede_dev *edev, u8 *buf)
|
|
|
{
|
|
|
int i, j, k;
|
|
@@ -152,6 +173,10 @@ static void qede_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
|
|
|
memcpy(buf, qede_private_arr,
|
|
|
ETH_GSTRING_LEN * QEDE_PRI_FLAG_LEN);
|
|
|
break;
|
|
|
+ case ETH_SS_TEST:
|
|
|
+ memcpy(buf, qede_tests_str_arr,
|
|
|
+ ETH_GSTRING_LEN * QEDE_ETHTOOL_TEST_MAX);
|
|
|
+ break;
|
|
|
default:
|
|
|
DP_VERBOSE(edev, QED_MSG_DEBUG,
|
|
|
"Unsupported stringset 0x%08x\n", stringset);
|
|
@@ -192,7 +217,8 @@ static int qede_get_sset_count(struct net_device *dev, int stringset)
|
|
|
return num_stats + QEDE_NUM_RQSTATS;
|
|
|
case ETH_SS_PRIV_FLAGS:
|
|
|
return QEDE_PRI_FLAG_LEN;
|
|
|
-
|
|
|
+ case ETH_SS_TEST:
|
|
|
+ return QEDE_ETHTOOL_TEST_MAX;
|
|
|
default:
|
|
|
DP_VERBOSE(edev, QED_MSG_DEBUG,
|
|
|
"Unsupported stringset 0x%08x\n", stringset);
|
|
@@ -827,6 +853,267 @@ static int qede_set_rxfh(struct net_device *dev, const u32 *indir,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/* This function enables the interrupt generation and the NAPI on the device */
|
|
|
+static void qede_netif_start(struct qede_dev *edev)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (!netif_running(edev->ndev))
|
|
|
+ return;
|
|
|
+
|
|
|
+ for_each_rss(i) {
|
|
|
+ /* Update and reenable interrupts */
|
|
|
+ qed_sb_ack(edev->fp_array[i].sb_info, IGU_INT_ENABLE, 1);
|
|
|
+ napi_enable(&edev->fp_array[i].napi);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* This function disables the NAPI and the interrupt generation on the device */
|
|
|
+static void qede_netif_stop(struct qede_dev *edev)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for_each_rss(i) {
|
|
|
+ napi_disable(&edev->fp_array[i].napi);
|
|
|
+ /* Disable interrupts */
|
|
|
+ qed_sb_ack(edev->fp_array[i].sb_info, IGU_INT_DISABLE, 0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int qede_selftest_transmit_traffic(struct qede_dev *edev,
|
|
|
+ struct sk_buff *skb)
|
|
|
+{
|
|
|
+ struct qede_tx_queue *txq = &edev->fp_array[0].txqs[0];
|
|
|
+ struct eth_tx_1st_bd *first_bd;
|
|
|
+ dma_addr_t mapping;
|
|
|
+ int i, idx, val;
|
|
|
+
|
|
|
+ /* Fill the entry in the SW ring and the BDs in the FW ring */
|
|
|
+ idx = txq->sw_tx_prod & NUM_TX_BDS_MAX;
|
|
|
+ txq->sw_tx_ring[idx].skb = skb;
|
|
|
+ first_bd = qed_chain_produce(&txq->tx_pbl);
|
|
|
+ memset(first_bd, 0, sizeof(*first_bd));
|
|
|
+ val = 1 << ETH_TX_1ST_BD_FLAGS_START_BD_SHIFT;
|
|
|
+ first_bd->data.bd_flags.bitfields = val;
|
|
|
+
|
|
|
+ /* Map skb linear data for DMA and set in the first BD */
|
|
|
+ mapping = dma_map_single(&edev->pdev->dev, skb->data,
|
|
|
+ skb_headlen(skb), DMA_TO_DEVICE);
|
|
|
+ if (unlikely(dma_mapping_error(&edev->pdev->dev, mapping))) {
|
|
|
+ DP_NOTICE(edev, "SKB mapping failed\n");
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+ BD_SET_UNMAP_ADDR_LEN(first_bd, mapping, skb_headlen(skb));
|
|
|
+
|
|
|
+ /* update the first BD with the actual num BDs */
|
|
|
+ first_bd->data.nbds = 1;
|
|
|
+ txq->sw_tx_prod++;
|
|
|
+ /* 'next page' entries are counted in the producer value */
|
|
|
+ val = cpu_to_le16(qed_chain_get_prod_idx(&txq->tx_pbl));
|
|
|
+ txq->tx_db.data.bd_prod = val;
|
|
|
+
|
|
|
+ /* wmb makes sure that the BDs data is updated before updating the
|
|
|
+ * producer, otherwise FW may read old data from the BDs.
|
|
|
+ */
|
|
|
+ wmb();
|
|
|
+ barrier();
|
|
|
+ writel(txq->tx_db.raw, txq->doorbell_addr);
|
|
|
+
|
|
|
+ /* mmiowb is needed to synchronize doorbell writes from more than one
|
|
|
+ * processor. It guarantees that the write arrives to the device before
|
|
|
+ * the queue lock is released and another start_xmit is called (possibly
|
|
|
+ * on another CPU). Without this barrier, the next doorbell can bypass
|
|
|
+ * this doorbell. This is applicable to IA64/Altix systems.
|
|
|
+ */
|
|
|
+ mmiowb();
|
|
|
+
|
|
|
+ for (i = 0; i < QEDE_SELFTEST_POLL_COUNT; i++) {
|
|
|
+ if (qede_txq_has_work(txq))
|
|
|
+ break;
|
|
|
+ usleep_range(100, 200);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!qede_txq_has_work(txq)) {
|
|
|
+ DP_NOTICE(edev, "Tx completion didn't happen\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ first_bd = (struct eth_tx_1st_bd *)qed_chain_consume(&txq->tx_pbl);
|
|
|
+ dma_unmap_page(&edev->pdev->dev, BD_UNMAP_ADDR(first_bd),
|
|
|
+ BD_UNMAP_LEN(first_bd), DMA_TO_DEVICE);
|
|
|
+ txq->sw_tx_cons++;
|
|
|
+ txq->sw_tx_ring[idx].skb = NULL;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int qede_selftest_receive_traffic(struct qede_dev *edev)
|
|
|
+{
|
|
|
+ struct qede_rx_queue *rxq = edev->fp_array[0].rxq;
|
|
|
+ u16 hw_comp_cons, sw_comp_cons, sw_rx_index, len;
|
|
|
+ struct eth_fast_path_rx_reg_cqe *fp_cqe;
|
|
|
+ struct sw_rx_data *sw_rx_data;
|
|
|
+ union eth_rx_cqe *cqe;
|
|
|
+ u8 *data_ptr;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* The packet is expected to receive on rx-queue 0 even though RSS is
|
|
|
+ * enabled. This is because the queue 0 is configured as the default
|
|
|
+ * queue and that the loopback traffic is not IP.
|
|
|
+ */
|
|
|
+ for (i = 0; i < QEDE_SELFTEST_POLL_COUNT; i++) {
|
|
|
+ if (qede_has_rx_work(rxq))
|
|
|
+ break;
|
|
|
+ usleep_range(100, 200);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!qede_has_rx_work(rxq)) {
|
|
|
+ DP_NOTICE(edev, "Failed to receive the traffic\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ hw_comp_cons = le16_to_cpu(*rxq->hw_cons_ptr);
|
|
|
+ sw_comp_cons = qed_chain_get_cons_idx(&rxq->rx_comp_ring);
|
|
|
+
|
|
|
+ /* Memory barrier to prevent the CPU from doing speculative reads of CQE
|
|
|
+ * / BD before reading hw_comp_cons. If the CQE is read before it is
|
|
|
+ * written by FW, then FW writes CQE and SB, and then the CPU reads the
|
|
|
+ * hw_comp_cons, it will use an old CQE.
|
|
|
+ */
|
|
|
+ rmb();
|
|
|
+
|
|
|
+ /* Get the CQE from the completion ring */
|
|
|
+ cqe = (union eth_rx_cqe *)qed_chain_consume(&rxq->rx_comp_ring);
|
|
|
+
|
|
|
+ /* Get the data from the SW ring */
|
|
|
+ sw_rx_index = rxq->sw_rx_cons & NUM_RX_BDS_MAX;
|
|
|
+ sw_rx_data = &rxq->sw_rx_ring[sw_rx_index];
|
|
|
+ fp_cqe = &cqe->fast_path_regular;
|
|
|
+ len = le16_to_cpu(fp_cqe->len_on_first_bd);
|
|
|
+ data_ptr = (u8 *)(page_address(sw_rx_data->data) +
|
|
|
+ fp_cqe->placement_offset + sw_rx_data->page_offset);
|
|
|
+ for (i = ETH_HLEN; i < len; i++)
|
|
|
+ if (data_ptr[i] != (unsigned char)(i & 0xff)) {
|
|
|
+ DP_NOTICE(edev, "Loopback test failed\n");
|
|
|
+ qede_recycle_rx_bd_ring(rxq, edev, 1);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ qede_recycle_rx_bd_ring(rxq, edev, 1);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int qede_selftest_run_loopback(struct qede_dev *edev, u32 loopback_mode)
|
|
|
+{
|
|
|
+ struct qed_link_params link_params;
|
|
|
+ struct sk_buff *skb = NULL;
|
|
|
+ int rc = 0, i;
|
|
|
+ u32 pkt_size;
|
|
|
+ u8 *packet;
|
|
|
+
|
|
|
+ if (!netif_running(edev->ndev)) {
|
|
|
+ DP_NOTICE(edev, "Interface is down\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ qede_netif_stop(edev);
|
|
|
+
|
|
|
+ /* Bring up the link in Loopback mode */
|
|
|
+ memset(&link_params, 0, sizeof(link_params));
|
|
|
+ link_params.link_up = true;
|
|
|
+ link_params.override_flags = QED_LINK_OVERRIDE_LOOPBACK_MODE;
|
|
|
+ link_params.loopback_mode = loopback_mode;
|
|
|
+ edev->ops->common->set_link(edev->cdev, &link_params);
|
|
|
+
|
|
|
+ /* Wait for loopback configuration to apply */
|
|
|
+ msleep_interruptible(500);
|
|
|
+
|
|
|
+ /* prepare the loopback packet */
|
|
|
+ pkt_size = edev->ndev->mtu + ETH_HLEN;
|
|
|
+
|
|
|
+ skb = netdev_alloc_skb(edev->ndev, pkt_size);
|
|
|
+ if (!skb) {
|
|
|
+ DP_INFO(edev, "Can't allocate skb\n");
|
|
|
+ rc = -ENOMEM;
|
|
|
+ goto test_loopback_exit;
|
|
|
+ }
|
|
|
+ packet = skb_put(skb, pkt_size);
|
|
|
+ ether_addr_copy(packet, edev->ndev->dev_addr);
|
|
|
+ ether_addr_copy(packet + ETH_ALEN, edev->ndev->dev_addr);
|
|
|
+ memset(packet + (2 * ETH_ALEN), 0x77, (ETH_HLEN - (2 * ETH_ALEN)));
|
|
|
+ for (i = ETH_HLEN; i < pkt_size; i++)
|
|
|
+ packet[i] = (unsigned char)(i & 0xff);
|
|
|
+
|
|
|
+ rc = qede_selftest_transmit_traffic(edev, skb);
|
|
|
+ if (rc)
|
|
|
+ goto test_loopback_exit;
|
|
|
+
|
|
|
+ rc = qede_selftest_receive_traffic(edev);
|
|
|
+ if (rc)
|
|
|
+ goto test_loopback_exit;
|
|
|
+
|
|
|
+ DP_VERBOSE(edev, NETIF_MSG_RX_STATUS, "Loopback test successful\n");
|
|
|
+
|
|
|
+test_loopback_exit:
|
|
|
+ dev_kfree_skb(skb);
|
|
|
+
|
|
|
+ /* Bring up the link in Normal mode */
|
|
|
+ memset(&link_params, 0, sizeof(link_params));
|
|
|
+ link_params.link_up = true;
|
|
|
+ link_params.override_flags = QED_LINK_OVERRIDE_LOOPBACK_MODE;
|
|
|
+ link_params.loopback_mode = QED_LINK_LOOPBACK_NONE;
|
|
|
+ edev->ops->common->set_link(edev->cdev, &link_params);
|
|
|
+
|
|
|
+ /* Wait for loopback configuration to apply */
|
|
|
+ msleep_interruptible(500);
|
|
|
+
|
|
|
+ qede_netif_start(edev);
|
|
|
+
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static void qede_self_test(struct net_device *dev,
|
|
|
+ struct ethtool_test *etest, u64 *buf)
|
|
|
+{
|
|
|
+ struct qede_dev *edev = netdev_priv(dev);
|
|
|
+
|
|
|
+ DP_VERBOSE(edev, QED_MSG_DEBUG,
|
|
|
+ "Self-test command parameters: offline = %d, external_lb = %d\n",
|
|
|
+ (etest->flags & ETH_TEST_FL_OFFLINE),
|
|
|
+ (etest->flags & ETH_TEST_FL_EXTERNAL_LB) >> 2);
|
|
|
+
|
|
|
+ memset(buf, 0, sizeof(u64) * QEDE_ETHTOOL_TEST_MAX);
|
|
|
+
|
|
|
+ if (etest->flags & ETH_TEST_FL_OFFLINE) {
|
|
|
+ if (qede_selftest_run_loopback(edev,
|
|
|
+ QED_LINK_LOOPBACK_INT_PHY)) {
|
|
|
+ buf[QEDE_ETHTOOL_INT_LOOPBACK] = 1;
|
|
|
+ etest->flags |= ETH_TEST_FL_FAILED;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (edev->ops->common->selftest->selftest_interrupt(edev->cdev)) {
|
|
|
+ buf[QEDE_ETHTOOL_INTERRUPT_TEST] = 1;
|
|
|
+ etest->flags |= ETH_TEST_FL_FAILED;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (edev->ops->common->selftest->selftest_memory(edev->cdev)) {
|
|
|
+ buf[QEDE_ETHTOOL_MEMORY_TEST] = 1;
|
|
|
+ etest->flags |= ETH_TEST_FL_FAILED;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (edev->ops->common->selftest->selftest_register(edev->cdev)) {
|
|
|
+ buf[QEDE_ETHTOOL_REGISTER_TEST] = 1;
|
|
|
+ etest->flags |= ETH_TEST_FL_FAILED;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (edev->ops->common->selftest->selftest_clock(edev->cdev)) {
|
|
|
+ buf[QEDE_ETHTOOL_CLOCK_TEST] = 1;
|
|
|
+ etest->flags |= ETH_TEST_FL_FAILED;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static const struct ethtool_ops qede_ethtool_ops = {
|
|
|
.get_settings = qede_get_settings,
|
|
|
.set_settings = qede_set_settings,
|
|
@@ -852,6 +1139,7 @@ static const struct ethtool_ops qede_ethtool_ops = {
|
|
|
.set_rxfh = qede_set_rxfh,
|
|
|
.get_channels = qede_get_channels,
|
|
|
.set_channels = qede_set_channels,
|
|
|
+ .self_test = qede_self_test,
|
|
|
};
|
|
|
|
|
|
void qede_set_ethtool_ops(struct net_device *dev)
|