/******************************************************************************* Copyright(c) 1999 - 2004 Intel Corporation. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. The full GNU General Public License is included in this distribution in the file called LICENSE. Contact Information: Linux NICS Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 *******************************************************************************/ #include "e100_phy.h" #include "e100_config.h" extern u16 e100_eeprom_read(struct e100_private *, u16); extern int e100_wait_exec_cmplx(struct e100_private *, u32,u8, u8); extern void e100_phy_reset(struct e100_private *bdp); extern void e100_phy_autoneg(struct e100_private *bdp); extern void e100_phy_set_loopback(struct e100_private *bdp); extern void e100_force_speed_duplex(struct e100_private *bdp); static u8 e100_diag_selftest(struct net_device *); static u8 e100_diag_eeprom(struct net_device *); static u8 e100_diag_loopback(struct net_device *); static u8 e100_diag_one_loopback (struct net_device *, u8); static u8 e100_diag_rcv_loopback_pkt(struct e100_private *); static void e100_diag_config_loopback(struct e100_private *, u8, u8, u8 *,u8 *); static u8 e100_diag_loopback_alloc(struct e100_private *); static void e100_diag_loopback_cu_ru_exec(struct e100_private *); static u8 e100_diag_check_pkt(u8 *); static void e100_diag_loopback_free(struct e100_private *); static int e100_cable_diag(struct e100_private *bdp); #define LB_PACKET_SIZE 1500 /** * e100_run_diag - main test execution handler - checks mask of requests and calls the diag routines * @dev: atapter's net device data struct * @test_info: array with test request mask also used to store test results * * RETURNS: updated flags field of struct ethtool_test */ u32 e100_run_diag(struct net_device *dev, u64 *test_info, u32 flags) { struct e100_private* bdp = dev->priv; u8 test_result = 0; if (!e100_get_link_state(bdp)) { test_result = ETH_TEST_FL_FAILED; test_info[test_link] = true; } if (!e100_diag_eeprom(dev)) { test_result = ETH_TEST_FL_FAILED; test_info[test_eeprom] = true; } if (flags & ETH_TEST_FL_OFFLINE) { u8 fail_mask; if (netif_running(dev)) { spin_lock_bh(&dev->xmit_lock); e100_close(dev); spin_unlock_bh(&dev->xmit_lock); } if (e100_diag_selftest(dev)) { test_result = ETH_TEST_FL_FAILED; test_info[test_self_test] = true; } fail_mask = e100_diag_loopback(dev); if (fail_mask) { test_result = ETH_TEST_FL_FAILED; if (fail_mask & PHY_LOOPBACK) test_info[test_loopback_phy] = true; if (fail_mask & MAC_LOOPBACK) test_info[test_loopback_mac] = true; } test_info[cable_diag] = e100_cable_diag(bdp); /* Need hw init regardless of netif_running */ e100_hw_init(bdp); if (netif_running(dev)) { e100_open(dev); } } else { test_info[test_self_test] = false; test_info[test_loopback_phy] = false; test_info[test_loopback_mac] = false; test_info[cable_diag] = false; } return flags | test_result; } /** * e100_diag_selftest - run hardware selftest * @dev: atapter's net device data struct */ static u8 e100_diag_selftest(struct net_device *dev) { struct e100_private *bdp = dev->priv; u32 st_timeout, st_result; u8 retval = 0; if (!e100_selftest(bdp, &st_timeout, &st_result)) { if (!st_timeout) { if (st_result & CB_SELFTEST_REGISTER_BIT) retval |= REGISTER_TEST_FAIL; if (st_result & CB_SELFTEST_DIAG_BIT) retval |= SELF_TEST_FAIL; if (st_result & CB_SELFTEST_ROM_BIT) retval |= ROM_TEST_FAIL; } else { retval = TEST_TIMEOUT; } } return retval; } /** * e100_diag_eeprom - validate eeprom checksum correctness * @dev: atapter's net device data struct * */ static u8 e100_diag_eeprom (struct net_device *dev) { struct e100_private *bdp = dev->priv; u16 i, eeprom_sum, eeprom_actual_csm; for (i = 0, eeprom_sum = 0; i < (bdp->eeprom_size - 1); i++) { eeprom_sum += e100_eeprom_read(bdp, i); } eeprom_actual_csm = e100_eeprom_read(bdp, bdp->eeprom_size - 1); if (eeprom_actual_csm == (u16)(EEPROM_SUM - eeprom_sum)) { return true; } return false; } /** * e100_diag_loopback - performs loopback test * @dev: atapter's net device data struct */ static u8 e100_diag_loopback (struct net_device *dev) { u8 rc = 0; printk(KERN_DEBUG "%s: PHY loopback test starts\n", dev->name); e100_hw_init(dev->priv); if (!e100_diag_one_loopback(dev, PHY_LOOPBACK)) { rc |= PHY_LOOPBACK; } printk(KERN_DEBUG "%s: PHY loopback test ends\n", dev->name); printk(KERN_DEBUG "%s: MAC loopback test starts\n", dev->name); e100_hw_init(dev->priv); if (!e100_diag_one_loopback(dev, MAC_LOOPBACK)) { rc |= MAC_LOOPBACK; } printk(KERN_DEBUG "%s: MAC loopback test ends\n", dev->name); return rc; } /** * e100_diag_loopback - performs loopback test * @dev: atapter's net device data struct * @mode: lopback test type */ static u8 e100_diag_one_loopback (struct net_device *dev, u8 mode) { struct e100_private *bdp = dev->priv; u8 res = false; u8 saved_dynamic_tbd = false; u8 saved_extended_tcb = false; if (!e100_diag_loopback_alloc(bdp)) return false; /* change the config block to standard tcb and the correct loopback */ e100_diag_config_loopback(bdp, true, mode, &saved_extended_tcb, &saved_dynamic_tbd); e100_diag_loopback_cu_ru_exec(bdp); if (e100_diag_rcv_loopback_pkt(bdp)) { res = true; } e100_diag_loopback_free(bdp); /* change the config block to previous tcb mode and the no loopback */ e100_diag_config_loopback(bdp, false, mode, &saved_extended_tcb, &saved_dynamic_tbd); return res; } /** * e100_diag_config_loopback - setup/clear loopback before/after lpbk test * @bdp: atapter's private data struct * @set_loopback: true if the function is called to set lb * @loopback_mode: the loopback mode(MAC or PHY) * @tcb_extended: true if need to set extended tcb mode after clean loopback * @dynamic_tbd: true if needed to set dynamic tbd mode after clean loopback * */ void e100_diag_config_loopback(struct e100_private* bdp, u8 set_loopback, u8 loopback_mode, u8* tcb_extended, u8* dynamic_tbd) { /* if set_loopback == true - we want to clear tcb_extended/dynamic_tbd. * the previous values are saved in the params tcb_extended/dynamic_tbd * if set_loopback == false - we want to restore previous value. */ if (set_loopback || (*tcb_extended)) *tcb_extended = e100_config_tcb_ext_enable(bdp,*tcb_extended); if (set_loopback || (*dynamic_tbd)) *dynamic_tbd = e100_config_dynamic_tbd(bdp,*dynamic_tbd); if (set_loopback) { /* ICH PHY loopback is broken */ if (bdp->flags & IS_ICH && loopback_mode == PHY_LOOPBACK) loopback_mode = MAC_LOOPBACK; /* Configure loopback on MAC */ e100_config_loopback_mode(bdp,loopback_mode); } else { e100_config_loopback_mode(bdp,NO_LOOPBACK); } e100_config(bdp); if (loopback_mode == PHY_LOOPBACK) { if (set_loopback) /* Set PHY loopback mode */ e100_phy_set_loopback(bdp); else /* Reset PHY loopback mode */ e100_phy_reset(bdp); /* Wait for PHY state change */ set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ); } else { /* For MAC loopback wait 500 msec to take effect */ set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ / 2); } } /** * e100_diag_loopback_alloc - alloc & initate tcb and rfd for the loopback * @bdp: atapter's private data struct * */ static u8 e100_diag_loopback_alloc(struct e100_private *bdp) { dma_addr_t dma_handle; tcb_t *tcb; rfd_t *rfd; tbd_t *tbd; /* tcb, tbd and transmit buffer are allocated */ tcb = pci_alloc_consistent(bdp->pdev, (sizeof (tcb_t) + sizeof (tbd_t) + LB_PACKET_SIZE), &dma_handle); if (tcb == NULL) return false; memset(tcb, 0x00, sizeof (tcb_t) + sizeof (tbd_t) + LB_PACKET_SIZE); tcb->tcb_phys = dma_handle; tcb->tcb_hdr.cb_status = 0; tcb->tcb_hdr.cb_cmd = cpu_to_le16(CB_EL_BIT | CB_TRANSMIT | CB_TX_SF_BIT); /* Next command is null */ tcb->tcb_hdr.cb_lnk_ptr = cpu_to_le32(0xffffffff); tcb->tcb_cnt = 0; tcb->tcb_thrshld = bdp->tx_thld; tcb->tcb_tbd_num = 1; /* Set up tcb tbd pointer */ tcb->tcb_tbd_ptr = cpu_to_le32(tcb->tcb_phys + sizeof (tcb_t)); tbd = (tbd_t *) ((u8 *) tcb + sizeof (tcb_t)); /* Set up tbd transmit buffer */ tbd->tbd_buf_addr = cpu_to_le32(le32_to_cpu(tcb->tcb_tbd_ptr) + sizeof (tbd_t)); tbd->tbd_buf_cnt = __constant_cpu_to_le16(1024); /* The value of first 512 bytes is FF */ memset((void *) ((u8 *) tbd + sizeof (tbd_t)), 0xFF, 512); /* The value of second 512 bytes is BA */ memset((void *) ((u8 *) tbd + sizeof (tbd_t) + 512), 0xBA, 512); wmb(); rfd = pci_alloc_consistent(bdp->pdev, sizeof (rfd_t), &dma_handle); if (rfd == NULL) { pci_free_consistent(bdp->pdev, sizeof (tcb_t) + sizeof (tbd_t) + LB_PACKET_SIZE, tcb, tcb->tcb_phys); return false; } memset(rfd, 0x00, sizeof (rfd_t)); /* init all fields in rfd */ rfd->rfd_header.cb_cmd = cpu_to_le16(RFD_EL_BIT); rfd->rfd_sz = cpu_to_le16(ETH_FRAME_LEN + CHKSUM_SIZE); /* dma_handle is physical address of rfd */ bdp->loopback.dma_handle = dma_handle; bdp->loopback.tcb = tcb; bdp->loopback.rfd = rfd; wmb(); return true; } /** * e100_diag_loopback_cu_ru_exec - activates cu and ru to send & receive the pkt * @bdp: atapter's private data struct * */ static void e100_diag_loopback_cu_ru_exec(struct e100_private *bdp) { /*load CU & RU base */ if(!e100_wait_exec_cmplx(bdp, bdp->loopback.dma_handle, SCB_RUC_START, 0)) printk(KERN_ERR "e100: SCB_RUC_START failed!\n"); bdp->next_cu_cmd = START_WAIT; e100_start_cu(bdp, bdp->loopback.tcb); bdp->last_tcb = NULL; rmb(); } /** * e100_diag_check_pkt - checks if a given packet is a loopback packet * @bdp: atapter's private data struct * * Returns true if OK false otherwise. */ static u8 e100_diag_check_pkt(u8 *datap) { int i; for (i = 0; i<512; i++) { if( !((*datap)==0xFF && (*(datap + 512) == 0xBA)) ) { printk (KERN_ERR "e100: check loopback packet failed at: %x\n", i); return false; } } printk (KERN_DEBUG "e100: Check received loopback packet OK\n"); return true; } /** * e100_diag_rcv_loopback_pkt - waits for receive and checks lpbk packet * @bdp: atapter's private data struct * * Returns true if OK false otherwise. */ static u8 e100_diag_rcv_loopback_pkt(struct e100_private* bdp) { rfd_t *rfdp; u16 rfd_status; unsigned long expires = jiffies + HZ * 2; rfdp =bdp->loopback.rfd; rfd_status = le16_to_cpu(rfdp->rfd_header.cb_status); while (!(rfd_status & RFD_STATUS_COMPLETE)) { if (time_before(jiffies, expires)) { yield(); rmb(); rfd_status = le16_to_cpu(rfdp->rfd_header.cb_status); } else { break; } } if (rfd_status & RFD_STATUS_COMPLETE) { printk(KERN_DEBUG "e100: Loopback packet received\n"); return e100_diag_check_pkt(((u8 *)rfdp+bdp->rfd_size)); } else { printk(KERN_ERR "e100: Loopback packet not received\n"); return false; } } /** * e100_diag_loopback_free - free data allocated for loopback pkt send/receive * @bdp: atapter's private data struct * */ static void e100_diag_loopback_free (struct e100_private *bdp) { pci_free_consistent(bdp->pdev, sizeof(tcb_t) + sizeof(tbd_t) + LB_PACKET_SIZE, bdp->loopback.tcb, bdp->loopback.tcb->tcb_phys); pci_free_consistent(bdp->pdev, sizeof(rfd_t), bdp->loopback.rfd, bdp->loopback.dma_handle); } static int e100_cable_diag(struct e100_private *bdp) { int saved_open_circut = 0xffff; int saved_short_circut = 0xffff; int saved_distance = 0xffff; int saved_same = 0; int cable_status = E100_CABLE_UNKNOWN; int i; /* If we have link, */ if (e100_get_link_state(bdp)) return E100_CABLE_OK; if (bdp->rev_id < D102_REV_ID) return E100_CABLE_UNKNOWN; /* Disable MDI/MDI-X auto switching */ e100_mdi_write(bdp, MII_NCONFIG, bdp->phy_addr, MDI_MDIX_RESET_ALL_MASK); /* Set to 100 Full as required by cable test */ e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr, BMCR_SPEED100 | BMCR_FULLDPLX); /* Test up to 100 times */ for (i = 0; i < 100; i++) { u16 ctrl_reg; int distance, open_circut, short_circut, near_end; /* Enable and execute cable test */ e100_mdi_write(bdp, HWI_CONTROL_REG, bdp->phy_addr, (HWI_TEST_ENABLE | HWI_TEST_EXECUTE)); /* Wait for cable test finished */ set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ/100 + 1); /* Read results */ e100_mdi_read(bdp, HWI_CONTROL_REG, bdp->phy_addr, &ctrl_reg); distance = ctrl_reg & HWI_TEST_DISTANCE; open_circut = ctrl_reg & HWI_TEST_HIGHZ_PROBLEM; short_circut = ctrl_reg & HWI_TEST_LOWZ_PROBLEM; if ((distance == saved_distance) && (open_circut == saved_open_circut) && (short_circut == saved_short_circut)) saved_same++; else { saved_same = 0; saved_distance = distance; saved_open_circut = open_circut; saved_short_circut = short_circut; } /* If results are the same 3 times */ if (saved_same == 3) { near_end = ((distance * HWI_REGISTER_GRANULARITY) < HWI_NEAR_END_BOUNDARY); if (open_circut) cable_status = (near_end) ? E100_CABLE_OPEN_NEAR : E100_CABLE_OPEN_FAR; if (short_circut) cable_status = (near_end) ? E100_CABLE_SHORT_NEAR : E100_CABLE_SHORT_FAR; break; } } /* Reset cable test */ e100_mdi_write(bdp, HWI_CONTROL_REG, bdp->phy_addr, HWI_RESET_ALL_MASK); return cable_status; }