diff options
author | Amit Kumar Salecha <amit.salecha@qlogic.com> | 2010-02-01 00:25:00 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-02-02 18:55:46 -0500 |
commit | cdaff1854f32ac9ddb4733530f617d32188665ed (patch) | |
tree | 5d1e7b5bcf656d0071840431f1f3cbc94b828e21 /drivers/net/qlcnic | |
parent | 7eb9855d68faabe0004ed18c2af1f0974d3c2c63 (diff) |
qlcnic: add loopback diagnostic test
Loopback test (offline) added in ethtool self test.
o Set device in loopback mode
o Send packets
o Process receive packets in qlcnic_process_rcv_ring_diag()
o Compare packets
o Reset device in normal mode.
Signed-off-by: Amit Kumar Salecha <amit.salecha@qlogic.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/qlcnic')
-rw-r--r-- | drivers/net/qlcnic/qlcnic.h | 6 | ||||
-rw-r--r-- | drivers/net/qlcnic/qlcnic_ethtool.c | 82 | ||||
-rw-r--r-- | drivers/net/qlcnic/qlcnic_hw.c | 52 | ||||
-rw-r--r-- | drivers/net/qlcnic/qlcnic_init.c | 75 | ||||
-rw-r--r-- | drivers/net/qlcnic/qlcnic_main.c | 20 |
5 files changed, 225 insertions, 10 deletions
diff --git a/drivers/net/qlcnic/qlcnic.h b/drivers/net/qlcnic/qlcnic.h index a5a67e9b5e10..b40a851ec7d1 100644 --- a/drivers/net/qlcnic/qlcnic.h +++ b/drivers/net/qlcnic/qlcnic.h | |||
@@ -897,6 +897,7 @@ struct qlcnic_mac_req { | |||
897 | #define __QLCNIC_START_FW 4 | 897 | #define __QLCNIC_START_FW 4 |
898 | 898 | ||
899 | #define QLCNIC_INTERRUPT_TEST 1 | 899 | #define QLCNIC_INTERRUPT_TEST 1 |
900 | #define QLCNIC_LOOPBACK_TEST 2 | ||
900 | 901 | ||
901 | struct qlcnic_adapter { | 902 | struct qlcnic_adapter { |
902 | struct qlcnic_hardware_context ahw; | 903 | struct qlcnic_hardware_context ahw; |
@@ -1066,6 +1067,8 @@ int qlcnic_send_lro_cleanup(struct qlcnic_adapter *adapter); | |||
1066 | void qlcnic_update_cmd_producer(struct qlcnic_adapter *adapter, | 1067 | void qlcnic_update_cmd_producer(struct qlcnic_adapter *adapter, |
1067 | struct qlcnic_host_tx_ring *tx_ring); | 1068 | struct qlcnic_host_tx_ring *tx_ring); |
1068 | int qlcnic_get_mac_addr(struct qlcnic_adapter *adapter, u64 *mac); | 1069 | int qlcnic_get_mac_addr(struct qlcnic_adapter *adapter, u64 *mac); |
1070 | void qlcnic_clear_ilb_mode(struct qlcnic_adapter *adapter); | ||
1071 | int qlcnic_set_ilb_mode(struct qlcnic_adapter *adapter); | ||
1069 | 1072 | ||
1070 | /* Functions from qlcnic_main.c */ | 1073 | /* Functions from qlcnic_main.c */ |
1071 | int qlcnic_reset_context(struct qlcnic_adapter *); | 1074 | int qlcnic_reset_context(struct qlcnic_adapter *); |
@@ -1073,6 +1076,9 @@ u32 qlcnic_issue_cmd(struct qlcnic_adapter *adapter, | |||
1073 | u32 pci_fn, u32 version, u32 arg1, u32 arg2, u32 arg3, u32 cmd); | 1076 | u32 pci_fn, u32 version, u32 arg1, u32 arg2, u32 arg3, u32 cmd); |
1074 | void qlcnic_diag_free_res(struct net_device *netdev, int max_sds_rings); | 1077 | void qlcnic_diag_free_res(struct net_device *netdev, int max_sds_rings); |
1075 | int qlcnic_diag_alloc_res(struct net_device *netdev, int test); | 1078 | int qlcnic_diag_alloc_res(struct net_device *netdev, int test); |
1079 | int qlcnic_check_loopback_buff(unsigned char *data); | ||
1080 | netdev_tx_t qlcnic_xmit_frame(struct sk_buff *skb, struct net_device *netdev); | ||
1081 | void qlcnic_process_rcv_ring_diag(struct qlcnic_host_sds_ring *sds_ring); | ||
1076 | 1082 | ||
1077 | /* | 1083 | /* |
1078 | * QLOGIC Board information | 1084 | * QLOGIC Board information |
diff --git a/drivers/net/qlcnic/qlcnic_ethtool.c b/drivers/net/qlcnic/qlcnic_ethtool.c index 58c50ed791de..8da6ec8c13b9 100644 --- a/drivers/net/qlcnic/qlcnic_ethtool.c +++ b/drivers/net/qlcnic/qlcnic_ethtool.c | |||
@@ -66,7 +66,8 @@ static const struct qlcnic_stats qlcnic_gstrings_stats[] = { | |||
66 | static const char qlcnic_gstrings_test[][ETH_GSTRING_LEN] = { | 66 | static const char qlcnic_gstrings_test[][ETH_GSTRING_LEN] = { |
67 | "Register_Test_on_offline", | 67 | "Register_Test_on_offline", |
68 | "Link_Test_on_offline", | 68 | "Link_Test_on_offline", |
69 | "Interrupt_Test_offline" | 69 | "Interrupt_Test_offline", |
70 | "Loopback_Test_offline" | ||
70 | }; | 71 | }; |
71 | 72 | ||
72 | #define QLCNIC_TEST_LEN ARRAY_SIZE(qlcnic_gstrings_test) | 73 | #define QLCNIC_TEST_LEN ARRAY_SIZE(qlcnic_gstrings_test) |
@@ -614,6 +615,80 @@ static int qlcnic_get_sset_count(struct net_device *dev, int sset) | |||
614 | } | 615 | } |
615 | } | 616 | } |
616 | 617 | ||
618 | #define QLC_ILB_PKT_SIZE 64 | ||
619 | |||
620 | static void qlcnic_create_loopback_buff(unsigned char *data) | ||
621 | { | ||
622 | unsigned char random_data[] = {0xa8, 0x06, 0x45, 0x00}; | ||
623 | memset(data, 0x4e, QLC_ILB_PKT_SIZE); | ||
624 | memset(data, 0xff, 12); | ||
625 | memcpy(data + 12, random_data, sizeof(random_data)); | ||
626 | } | ||
627 | |||
628 | int qlcnic_check_loopback_buff(unsigned char *data) | ||
629 | { | ||
630 | unsigned char buff[QLC_ILB_PKT_SIZE]; | ||
631 | qlcnic_create_loopback_buff(buff); | ||
632 | return memcmp(data, buff, QLC_ILB_PKT_SIZE); | ||
633 | } | ||
634 | |||
635 | static int qlcnic_do_ilb_test(struct qlcnic_adapter *adapter) | ||
636 | { | ||
637 | struct qlcnic_recv_context *recv_ctx = &adapter->recv_ctx; | ||
638 | struct qlcnic_host_sds_ring *sds_ring = &recv_ctx->sds_rings[0]; | ||
639 | struct sk_buff *skb; | ||
640 | int i; | ||
641 | |||
642 | for (i = 0; i < 16; i++) { | ||
643 | skb = dev_alloc_skb(QLC_ILB_PKT_SIZE); | ||
644 | qlcnic_create_loopback_buff(skb->data); | ||
645 | skb_put(skb, QLC_ILB_PKT_SIZE); | ||
646 | |||
647 | adapter->diag_cnt = 0; | ||
648 | |||
649 | qlcnic_xmit_frame(skb, adapter->netdev); | ||
650 | |||
651 | msleep(5); | ||
652 | |||
653 | qlcnic_process_rcv_ring_diag(sds_ring); | ||
654 | |||
655 | dev_kfree_skb_any(skb); | ||
656 | if (!adapter->diag_cnt) | ||
657 | return -1; | ||
658 | } | ||
659 | return 0; | ||
660 | } | ||
661 | |||
662 | static int qlcnic_loopback_test(struct net_device *netdev) | ||
663 | { | ||
664 | struct qlcnic_adapter *adapter = netdev_priv(netdev); | ||
665 | int max_sds_rings = adapter->max_sds_rings; | ||
666 | int ret; | ||
667 | |||
668 | if (test_and_set_bit(__QLCNIC_RESETTING, &adapter->state)) | ||
669 | return -EIO; | ||
670 | |||
671 | ret = qlcnic_diag_alloc_res(netdev, QLCNIC_LOOPBACK_TEST); | ||
672 | if (ret) | ||
673 | goto clear_it; | ||
674 | |||
675 | ret = qlcnic_set_ilb_mode(adapter); | ||
676 | if (ret) | ||
677 | goto done; | ||
678 | |||
679 | ret = qlcnic_do_ilb_test(adapter); | ||
680 | |||
681 | qlcnic_clear_ilb_mode(adapter); | ||
682 | |||
683 | done: | ||
684 | qlcnic_diag_free_res(netdev, max_sds_rings); | ||
685 | |||
686 | clear_it: | ||
687 | adapter->max_sds_rings = max_sds_rings; | ||
688 | clear_bit(__QLCNIC_RESETTING, &adapter->state); | ||
689 | return ret; | ||
690 | } | ||
691 | |||
617 | static int qlcnic_irq_test(struct net_device *netdev) | 692 | static int qlcnic_irq_test(struct net_device *netdev) |
618 | { | 693 | { |
619 | struct qlcnic_adapter *adapter = netdev_priv(netdev); | 694 | struct qlcnic_adapter *adapter = netdev_priv(netdev); |
@@ -656,6 +731,11 @@ qlcnic_diag_test(struct net_device *dev, struct ethtool_test *eth_test, | |||
656 | data[2] = qlcnic_irq_test(dev); | 731 | data[2] = qlcnic_irq_test(dev); |
657 | if (data[2]) | 732 | if (data[2]) |
658 | eth_test->flags |= ETH_TEST_FL_FAILED; | 733 | eth_test->flags |= ETH_TEST_FL_FAILED; |
734 | |||
735 | data[3] = qlcnic_loopback_test(dev); | ||
736 | if (data[3]) | ||
737 | eth_test->flags |= ETH_TEST_FL_FAILED; | ||
738 | |||
659 | } | 739 | } |
660 | 740 | ||
661 | data[0] = qlcnic_reg_test(dev); | 741 | data[0] = qlcnic_reg_test(dev); |
diff --git a/drivers/net/qlcnic/qlcnic_hw.c b/drivers/net/qlcnic/qlcnic_hw.c index 8724e561ed72..dc6cd69d6d93 100644 --- a/drivers/net/qlcnic/qlcnic_hw.c +++ b/drivers/net/qlcnic/qlcnic_hw.c | |||
@@ -1221,3 +1221,55 @@ int qlcnic_config_led(struct qlcnic_adapter *adapter, u32 state, u32 rate) | |||
1221 | 1221 | ||
1222 | return rv; | 1222 | return rv; |
1223 | } | 1223 | } |
1224 | |||
1225 | static int qlcnic_set_fw_loopback(struct qlcnic_adapter *adapter, u32 flag) | ||
1226 | { | ||
1227 | struct qlcnic_nic_req req; | ||
1228 | int rv; | ||
1229 | u64 word; | ||
1230 | |||
1231 | memset(&req, 0, sizeof(struct qlcnic_nic_req)); | ||
1232 | req.qhdr = cpu_to_le64(QLCNIC_HOST_REQUEST << 23); | ||
1233 | |||
1234 | word = QLCNIC_H2C_OPCODE_CONFIG_LOOPBACK | | ||
1235 | ((u64)adapter->portnum << 16); | ||
1236 | req.req_hdr = cpu_to_le64(word); | ||
1237 | req.words[0] = cpu_to_le64(flag); | ||
1238 | |||
1239 | rv = qlcnic_send_cmd_descs(adapter, (struct cmd_desc_type0 *)&req, 1); | ||
1240 | if (rv) | ||
1241 | dev_err(&adapter->pdev->dev, | ||
1242 | "%sting loopback mode failed.\n", | ||
1243 | flag ? "Set" : "Reset"); | ||
1244 | return rv; | ||
1245 | } | ||
1246 | |||
1247 | int qlcnic_set_ilb_mode(struct qlcnic_adapter *adapter) | ||
1248 | { | ||
1249 | if (qlcnic_set_fw_loopback(adapter, 1)) | ||
1250 | return -EIO; | ||
1251 | |||
1252 | if (qlcnic_nic_set_promisc(adapter, | ||
1253 | VPORT_MISS_MODE_ACCEPT_ALL)) { | ||
1254 | qlcnic_set_fw_loopback(adapter, 0); | ||
1255 | return -EIO; | ||
1256 | } | ||
1257 | |||
1258 | msleep(1000); | ||
1259 | return 0; | ||
1260 | } | ||
1261 | |||
1262 | void qlcnic_clear_ilb_mode(struct qlcnic_adapter *adapter) | ||
1263 | { | ||
1264 | int mode = VPORT_MISS_MODE_DROP; | ||
1265 | struct net_device *netdev = adapter->netdev; | ||
1266 | |||
1267 | qlcnic_set_fw_loopback(adapter, 0); | ||
1268 | |||
1269 | if (netdev->flags & IFF_PROMISC) | ||
1270 | mode = VPORT_MISS_MODE_ACCEPT_ALL; | ||
1271 | else if (netdev->flags & IFF_ALLMULTI) | ||
1272 | mode = VPORT_MISS_MODE_ACCEPT_MULTI; | ||
1273 | |||
1274 | qlcnic_nic_set_promisc(adapter, mode); | ||
1275 | } | ||
diff --git a/drivers/net/qlcnic/qlcnic_init.c b/drivers/net/qlcnic/qlcnic_init.c index 7ae8bcc1e439..ea00ab4d4feb 100644 --- a/drivers/net/qlcnic/qlcnic_init.c +++ b/drivers/net/qlcnic/qlcnic_init.c | |||
@@ -1464,3 +1464,78 @@ qlcnic_post_rx_buffers_nodb(struct qlcnic_adapter *adapter, | |||
1464 | spin_unlock(&rds_ring->lock); | 1464 | spin_unlock(&rds_ring->lock); |
1465 | } | 1465 | } |
1466 | 1466 | ||
1467 | static struct qlcnic_rx_buffer * | ||
1468 | qlcnic_process_rcv_diag(struct qlcnic_adapter *adapter, | ||
1469 | struct qlcnic_host_sds_ring *sds_ring, | ||
1470 | int ring, u64 sts_data0) | ||
1471 | { | ||
1472 | struct qlcnic_recv_context *recv_ctx = &adapter->recv_ctx; | ||
1473 | struct qlcnic_rx_buffer *buffer; | ||
1474 | struct sk_buff *skb; | ||
1475 | struct qlcnic_host_rds_ring *rds_ring; | ||
1476 | int index, length, cksum, pkt_offset; | ||
1477 | |||
1478 | if (unlikely(ring >= adapter->max_rds_rings)) | ||
1479 | return NULL; | ||
1480 | |||
1481 | rds_ring = &recv_ctx->rds_rings[ring]; | ||
1482 | |||
1483 | index = qlcnic_get_sts_refhandle(sts_data0); | ||
1484 | if (unlikely(index >= rds_ring->num_desc)) | ||
1485 | return NULL; | ||
1486 | |||
1487 | buffer = &rds_ring->rx_buf_arr[index]; | ||
1488 | |||
1489 | length = qlcnic_get_sts_totallength(sts_data0); | ||
1490 | cksum = qlcnic_get_sts_status(sts_data0); | ||
1491 | pkt_offset = qlcnic_get_sts_pkt_offset(sts_data0); | ||
1492 | |||
1493 | skb = qlcnic_process_rxbuf(adapter, rds_ring, index, cksum); | ||
1494 | if (!skb) | ||
1495 | return buffer; | ||
1496 | |||
1497 | skb_put(skb, rds_ring->skb_size); | ||
1498 | |||
1499 | if (pkt_offset) | ||
1500 | skb_pull(skb, pkt_offset); | ||
1501 | |||
1502 | skb->truesize = skb->len + sizeof(struct sk_buff); | ||
1503 | |||
1504 | if (!qlcnic_check_loopback_buff(skb->data)) | ||
1505 | adapter->diag_cnt++; | ||
1506 | |||
1507 | dev_kfree_skb_any(skb); | ||
1508 | |||
1509 | return buffer; | ||
1510 | } | ||
1511 | |||
1512 | void | ||
1513 | qlcnic_process_rcv_ring_diag(struct qlcnic_host_sds_ring *sds_ring) | ||
1514 | { | ||
1515 | struct qlcnic_adapter *adapter = sds_ring->adapter; | ||
1516 | struct status_desc *desc; | ||
1517 | struct qlcnic_rx_buffer *rxbuf; | ||
1518 | u64 sts_data0; | ||
1519 | |||
1520 | int opcode, ring, desc_cnt; | ||
1521 | u32 consumer = sds_ring->consumer; | ||
1522 | |||
1523 | desc = &sds_ring->desc_head[consumer]; | ||
1524 | sts_data0 = le64_to_cpu(desc->status_desc_data[0]); | ||
1525 | |||
1526 | if (!(sts_data0 & STATUS_OWNER_HOST)) | ||
1527 | return; | ||
1528 | |||
1529 | desc_cnt = qlcnic_get_sts_desc_cnt(sts_data0); | ||
1530 | opcode = qlcnic_get_sts_opcode(sts_data0); | ||
1531 | |||
1532 | ring = qlcnic_get_sts_type(sts_data0); | ||
1533 | rxbuf = qlcnic_process_rcv_diag(adapter, sds_ring, | ||
1534 | ring, sts_data0); | ||
1535 | |||
1536 | desc->status_desc_data[0] = cpu_to_le64(STATUS_OWNER_PHANTOM); | ||
1537 | consumer = get_next_index(consumer, sds_ring->num_desc); | ||
1538 | |||
1539 | sds_ring->consumer = consumer; | ||
1540 | writel(consumer, sds_ring->crb_sts_consumer); | ||
1541 | } | ||
diff --git a/drivers/net/qlcnic/qlcnic_main.c b/drivers/net/qlcnic/qlcnic_main.c index a8b07120d362..665e8e56b6a8 100644 --- a/drivers/net/qlcnic/qlcnic_main.c +++ b/drivers/net/qlcnic/qlcnic_main.c | |||
@@ -65,8 +65,6 @@ static int __devinit qlcnic_probe(struct pci_dev *pdev, | |||
65 | static void __devexit qlcnic_remove(struct pci_dev *pdev); | 65 | static void __devexit qlcnic_remove(struct pci_dev *pdev); |
66 | static int qlcnic_open(struct net_device *netdev); | 66 | static int qlcnic_open(struct net_device *netdev); |
67 | static int qlcnic_close(struct net_device *netdev); | 67 | static int qlcnic_close(struct net_device *netdev); |
68 | static netdev_tx_t qlcnic_xmit_frame(struct sk_buff *, | ||
69 | struct net_device *); | ||
70 | static void qlcnic_tx_timeout(struct net_device *netdev); | 68 | static void qlcnic_tx_timeout(struct net_device *netdev); |
71 | static void qlcnic_tx_timeout_task(struct work_struct *work); | 69 | static void qlcnic_tx_timeout_task(struct work_struct *work); |
72 | static void qlcnic_attach_work(struct work_struct *work); | 70 | static void qlcnic_attach_work(struct work_struct *work); |
@@ -937,9 +935,11 @@ void qlcnic_diag_free_res(struct net_device *netdev, int max_sds_rings) | |||
937 | struct qlcnic_host_sds_ring *sds_ring; | 935 | struct qlcnic_host_sds_ring *sds_ring; |
938 | int ring; | 936 | int ring; |
939 | 937 | ||
940 | for (ring = 0; ring < adapter->max_sds_rings; ring++) { | 938 | if (adapter->diag_test == QLCNIC_INTERRUPT_TEST) { |
941 | sds_ring = &adapter->recv_ctx.sds_rings[ring]; | 939 | for (ring = 0; ring < adapter->max_sds_rings; ring++) { |
942 | qlcnic_disable_int(sds_ring); | 940 | sds_ring = &adapter->recv_ctx.sds_rings[ring]; |
941 | qlcnic_disable_int(sds_ring); | ||
942 | } | ||
943 | } | 943 | } |
944 | 944 | ||
945 | qlcnic_detach(adapter); | 945 | qlcnic_detach(adapter); |
@@ -977,9 +977,11 @@ int qlcnic_diag_alloc_res(struct net_device *netdev, int test) | |||
977 | if (ret) | 977 | if (ret) |
978 | return ret; | 978 | return ret; |
979 | 979 | ||
980 | for (ring = 0; ring < adapter->max_sds_rings; ring++) { | 980 | if (adapter->diag_test == QLCNIC_INTERRUPT_TEST) { |
981 | sds_ring = &adapter->recv_ctx.sds_rings[ring]; | 981 | for (ring = 0; ring < adapter->max_sds_rings; ring++) { |
982 | qlcnic_enable_int(sds_ring); | 982 | sds_ring = &adapter->recv_ctx.sds_rings[ring]; |
983 | qlcnic_enable_int(sds_ring); | ||
984 | } | ||
983 | } | 985 | } |
984 | 986 | ||
985 | return 0; | 987 | return 0; |
@@ -1549,7 +1551,7 @@ qlcnic_clear_cmddesc(u64 *desc) | |||
1549 | desc[2] = 0ULL; | 1551 | desc[2] = 0ULL; |
1550 | } | 1552 | } |
1551 | 1553 | ||
1552 | static netdev_tx_t | 1554 | netdev_tx_t |
1553 | qlcnic_xmit_frame(struct sk_buff *skb, struct net_device *netdev) | 1555 | qlcnic_xmit_frame(struct sk_buff *skb, struct net_device *netdev) |
1554 | { | 1556 | { |
1555 | struct qlcnic_adapter *adapter = netdev_priv(netdev); | 1557 | struct qlcnic_adapter *adapter = netdev_priv(netdev); |