From patchwork Mon Jun 27 13:35:13 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sven Eckelmann X-Patchwork-Id: 641029 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from arrakis.dune.hu (caladan.dune.hu [78.24.191.180]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3rdVLZ2kLcz9t1c for ; Mon, 27 Jun 2016 23:35:33 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=open-mesh-com.20150623.gappssmtp.com header.i=@open-mesh-com.20150623.gappssmtp.com header.b=CedsWm+z; dkim-atps=neutral Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id 0796AB92328; Mon, 27 Jun 2016 15:35:26 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on arrakis.dune.hu X-Spam-Level: X-Spam-Status: No, score=-1.5 required=5.0 tests=BAYES_00,T_DKIM_INVALID autolearn=unavailable autolearn_force=no version=3.4.1 Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP; Mon, 27 Jun 2016 15:35:26 +0200 (CEST) Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id 79B22B92320 for ; Mon, 27 Jun 2016 15:35:23 +0200 (CEST) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 CL_IP_EQ_HELO_IP=-2 (check from: .open-mesh. - helo: .mail-wm0-f44.google. - helo-domain: .google.) FROM/MX_MATCHES_HELO(DOMAIN)=-2; rate: -7 Received: from mail-wm0-f44.google.com (mail-wm0-f44.google.com [74.125.82.44]) by arrakis.dune.hu (Postfix) with ESMTPS for ; Mon, 27 Jun 2016 15:35:22 +0200 (CEST) Received: by mail-wm0-f44.google.com with SMTP id a66so115774593wme.0 for ; Mon, 27 Jun 2016 06:35:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=open-mesh-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:user-agent:in-reply-to :references:mime-version; bh=6piCkzJJ//NkpU9CeCd9T6Hwx23Lxx9yJXsRoiF8Vj0=; b=CedsWm+zYo5bK2YpLu+EdGP/MxBQFfBQCk4F6V0wIcbJmKqM+ZD7w9ij0hztch+3Z5 DS/mWZ1jq+f8gZqtWdn60qFc6DNzy53QIbZTz+LXVFu/VEhmoVjUKo8U41RT/F5XAzCh 5wjfsZPeNXpU8X0W2DkVFVKvO9D/XPxObSsPfvTEp18cKC0ymnMoeSaFXGbZD6TASAy9 OKiTLZkDNaH0/dcMLFz8xpTnQhPOezJg9hYLGXdDqOVvjdNXlx6sCnU/sYnUgje5zYPr GXDSF52hpWIMxwDEtNuVudPdNjPDj4qgNBptG4woVq3OpxcD9KcmTiQyEKPvD0Wce+vC aBdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:user-agent :in-reply-to:references:mime-version; bh=6piCkzJJ//NkpU9CeCd9T6Hwx23Lxx9yJXsRoiF8Vj0=; b=kiFdgbtP5j+02DE/ciTTsa9CjgrOdVgYtuIA135migwbwbnl1bGbsrVtdQdqRYltdj iZoe94MMxJAFO4y2Qnuy5e//p/Tmuz4yDGHlQ4wtrW3ZkQKdTwOgOJKdyXtWvHqHSmwo 6AeKjNevE4JJ0vdoryh7kre+YR6iFeBWyz/0m/fdD7NfLJJm2joEU4t9d8PVE+5ik2nG Ev1zWCGYbUj/JNg1ZXHGVaXC7W9ciz7YQmlTZ9KkgyGc0m5ZdL14Kq421xAJLKvCudAT hy2k9rDHIk/7KZNfSWW2+F+RqWBpH64z+kdRbv9Q/YEcmkFdkXpTgRjMSmqFVIbF1Vct 3qIA== X-Gm-Message-State: ALyK8tJ5HM2PXI+nz5tjuKuJcVDSyhQzv6/LW8bUQLRL4Qde6sciJegWruaau0+owtj9YUUE X-Received: by 10.194.82.161 with SMTP id j1mr964400wjy.65.1467034522053; Mon, 27 Jun 2016 06:35:22 -0700 (PDT) Received: from bentobox.localnet (p2003007C6F7338FE995A64B45B6D02AF.dip0.t-ipconnect.de. [2003:7c:6f73:38fe:995a:64b4:5b6d:2af]) by smtp.gmail.com with ESMTPSA id f73sm3286416wmg.1.2016.06.27.06.35.20 (version=TLS1 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 27 Jun 2016 06:35:21 -0700 (PDT) From: Sven Eckelmann To: openwrt-devel@lists.openwrt.org Date: Mon, 27 Jun 2016 15:35:13 +0200 Message-ID: <3616023.0JfsJ0htxN@bentobox> User-Agent: KMail/4.14.10 (Linux/4.5.0-2-amd64; KDE/4.14.21; x86_64; ; ) In-Reply-To: <1459863133-26810-6-git-send-email-sven@open-mesh.com> References: <1459863133-26810-1-git-send-email-sven@open-mesh.com> <1459863133-26810-6-git-send-email-sven@open-mesh.com> MIME-Version: 1.0 Subject: Re: [OpenWrt-Devel] [RFC v2 6/6] ar71xx: Reset QCA955x SGMII link on speed change X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: OpenWrt Development List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Antonio Quartulli Errors-To: openwrt-devel-bounces@lists.openwrt.org Sender: "openwrt-devel" On Tuesday 05 April 2016 15:32:13 Sven Eckelmann wrote: > From: Sven Eckelmann > > The SGMII link of the QCA955x seems to be unstable when the PHY changes the > link speed. Reseting the SGMII and the PHY management control seems to > resolve this problem. > > This was observed with an AR8033 and QCA9558 > > Signed-off-by: Sven Eckelmann > --- > v2: > - Split into multiple patches and adjust slightly to look more like an > OpenWrt patch > > The code of this RFC is not meant to be an actual patch. It should show > what the u-boot for QCA955x does and what seemed to work(tm) in my limited > tests. It would be interesting to know whether this was also noticed by > other people and how they fixed it (when they fixed it). Just because asked if this is also required for the RGMII - short answer: yes, it is. Slightly longer answer: Yes, the link seems to desync(?) too when RGMII changes the link speed. Unfortunately, there is also no real fix and the workaround is even more terrible. The basic idea behind it is that the PHY has to be changed into its digital loopback mode on each link change. And then the SoC has to transfer some ethernet frames to the PHY and check if it receives these packets again. If not, then it has to toggle the TX_INVERT bit of ETH_CFG and retest. If it still doesn't work then the only solution for the SoC is to jump out of the window (this part is not yet implemented). I have just implemented a PoC in case somebody wants to play around with it. Not that I would recommend that to anyone. Kind regards, Sven From: Sven Eckelmann Date: Tue, 26 Apr 2016 16:14:47 +0200 Subject: [RFC 7/6] ag71xx: Test link on QCA955x for PHY connectivity problems The link between MAC and PHY on a QCA955x tends to break down after carrier changes. This can be worked around by setting the PHY (AR803x only supported) into digital loopback mode and then sending packets via this link. If no data is returned then the TX_INVERT bit has to be toggled to get the link working again. No information was received from QCA what actually is broken. --- .../linux/ar71xx/files/arch/mips/ath79/dev-eth.c | 44 +++- .../ar71xx/files/arch/mips/ath79/mach-mr1750.c | 1 + .../ar71xx/files/arch/mips/ath79/mach-om5pac.c | 2 + .../ar71xx/files/arch/mips/ath79/mach-om5pacv2.c | 2 + .../mips/include/asm/mach-ath79/ag71xx_platform.h | 2 + .../drivers/net/ethernet/atheros/ag71xx/ag71xx.h | 13 + .../net/ethernet/atheros/ag71xx/ag71xx_main.c | 283 +++++++++++++++++++++ .../net/ethernet/atheros/ag71xx/ag71xx_phy.c | 17 +- .../999-dont-set-down-on-loopback.patch | 30 +++ .../999-dont-set-down-on-loopback.patch | 30 +++ .../999-dont-set-down-on-loopback.patch | 30 +++ 11 files changed, 451 insertions(+), 3 deletions(-) create mode 100644 target/linux/generic/patches-3.18/999-dont-set-down-on-loopback.patch create mode 100644 target/linux/generic/patches-4.1/999-dont-set-down-on-loopback.patch create mode 100644 target/linux/generic/patches-4.4/999-dont-set-down-on-loopback.patch diff --git a/target/linux/ar71xx/files/arch/mips/ath79/dev-eth.c b/target/linux/ar71xx/files/arch/mips/ath79/dev-eth.c index b43c80a..d1d3be7 100644 --- a/target/linux/ar71xx/files/arch/mips/ath79/dev-eth.c +++ b/target/linux/ar71xx/files/arch/mips/ath79/dev-eth.c @@ -373,10 +373,50 @@ static void ar934x_set_speed_ge0(int speed) iounmap(base); } +static u32 qca955x_get_eth_pll(unsigned int mac, int speed) +{ + struct ag71xx_platform_data *pdata; + struct ath79_eth_pll_data *pll_data; + u32 pll_val; + u32 tx_invert = 0; + + switch (mac) { + case 0: + pll_data = &ath79_eth0_pll_data; + pdata = &ath79_eth0_data; + break; + case 1: + pll_data = &ath79_eth1_pll_data; + pdata = &ath79_eth1_data; + break; + default: + BUG(); + } + + switch (speed) { + case SPEED_10: + pll_val = pll_data->pll_10; + break; + case SPEED_100: + pll_val = pll_data->pll_100; + break; + case SPEED_1000: + pll_val = pll_data->pll_1000; + break; + default: + BUG(); + } + + /* toggle TX_INVERT when required by ag71xx */ + if (pdata->tx_invert_war && pdata->tx_invert_active) + tx_invert = BIT(31); + + return pll_val ^ tx_invert; +} static void qca955x_set_speed_xmii(int speed) { void __iomem *base; - u32 val = ath79_get_eth_pll(0, speed); + u32 val = qca955x_get_eth_pll(0, speed); base = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); __raw_writel(val, base + QCA955X_PLL_ETH_XMII_CONTROL_REG); @@ -386,7 +426,7 @@ static void qca955x_set_speed_xmii(int speed) static void qca955x_set_speed_sgmii(int speed) { void __iomem *base; - u32 val = ath79_get_eth_pll(1, speed); + u32 val = qca955x_get_eth_pll(1, speed); base = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); __raw_writel(val, base + QCA955X_PLL_ETH_SGMII_CONTROL_REG); diff --git a/target/linux/ar71xx/files/arch/mips/ath79/mach-mr1750.c b/target/linux/ar71xx/files/arch/mips/ath79/mach-mr1750.c index e3c04e7..97b5bf4 100644 --- a/target/linux/ar71xx/files/arch/mips/ath79/mach-mr1750.c +++ b/target/linux/ar71xx/files/arch/mips/ath79/mach-mr1750.c @@ -162,6 +162,7 @@ static void __init mr1750_setup(void) /* GMAC0 is connected to the RMGII interface */ ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; ath79_eth0_data.phy_mask = BIT(5); + ath79_eth0_data.tx_invert_war = 1; ath79_eth0_data.mii_bus_dev = &ath79_mdio0_device.dev; ath79_register_eth(0); diff --git a/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pac.c b/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pac.c index f6974af..9387f26 100644 --- a/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pac.c +++ b/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pac.c @@ -171,6 +171,7 @@ static void __init om5p_ac_setup(void) ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; ath79_eth0_data.mii_bus_dev = &ath79_mdio0_device.dev; ath79_eth0_data.phy_mask = BIT(1); + ath79_eth0_data.tx_invert_war = 1; ath79_eth0_pll_data.pll_1000 = 0x82000101; ath79_eth0_pll_data.pll_100 = 0x80000101; ath79_eth0_pll_data.pll_10 = 0x80001313; @@ -180,6 +181,7 @@ static void __init om5p_ac_setup(void) ath79_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_SGMII; ath79_eth1_data.mii_bus_dev = &ath79_mdio0_device.dev; ath79_eth1_data.phy_mask = BIT(2); + ath79_eth1_data.tx_invert_war = 1; ath79_eth1_pll_data.pll_1000 = 0x03000101; ath79_eth1_pll_data.pll_100 = 0x80000101; ath79_eth1_pll_data.pll_10 = 0x80001313; diff --git a/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pacv2.c b/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pacv2.c index 0480d01..820a10a 100644 --- a/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pacv2.c +++ b/target/linux/ar71xx/files/arch/mips/ath79/mach-om5pacv2.c @@ -199,6 +199,7 @@ static void __init om5p_acv2_setup(void) ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; ath79_eth0_data.mii_bus_dev = &ath79_mdio0_device.dev; ath79_eth0_data.phy_mask = BIT(4); + ath79_eth0_data.tx_invert_war = 1; ath79_eth0_pll_data.pll_1000 = 0x82000101; ath79_eth0_pll_data.pll_100 = 0x80000101; ath79_eth0_pll_data.pll_10 = 0x80001313; @@ -208,6 +209,7 @@ static void __init om5p_acv2_setup(void) ath79_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_SGMII; ath79_eth1_data.mii_bus_dev = &ath79_mdio1_device.dev; ath79_eth1_data.phy_mask = BIT(1); + ath79_eth1_data.tx_invert_war = 0; ath79_eth1_pll_data.pll_1000 = 0x03000101; ath79_eth1_pll_data.pll_100 = 0x80000101; ath79_eth1_pll_data.pll_10 = 0x80001313; diff --git a/target/linux/ar71xx/files/arch/mips/include/asm/mach-ath79/ag71xx_platform.h b/target/linux/ar71xx/files/arch/mips/include/asm/mach-ath79/ag71xx_platform.h index d46dc4e..33575ed 100644 --- a/target/linux/ar71xx/files/arch/mips/include/asm/mach-ath79/ag71xx_platform.h +++ b/target/linux/ar71xx/files/arch/mips/include/asm/mach-ath79/ag71xx_platform.h @@ -31,6 +31,8 @@ struct ag71xx_platform_data { u8 mac_addr[ETH_ALEN]; struct device *mii_bus_dev; + u8 tx_invert_war:1; + u8 tx_invert_active:1; u8 has_gbit:1; u8 is_ar91xx:1; u8 is_ar7240:1; diff --git a/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h b/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h index 5d03dcf..b6b7794 100644 --- a/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h +++ b/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h @@ -147,6 +147,13 @@ struct ag71xx_debug { struct ag71xx_napi_stats napi_stats; }; +enum ag71xx_lpb_test { + AG71XX_LPB_TEST_DISABLED, + AG71XX_LPB_TEST_ENABLED, + AG71XX_LPB_TEST_SUCCESS, + AG71XX_LPB_TEST_POST_LINKRESET, +}; + struct ag71xx { void __iomem *mac_base; @@ -174,8 +181,11 @@ struct ag71xx { unsigned int desc_pktlen_mask; unsigned int rx_buf_size; + enum ag71xx_lpb_test loopback; + struct work_struct restart_work; struct delayed_work link_work; + struct work_struct tx_invert_work; struct timer_list oom_timer; #ifdef CONFIG_AG71XX_DEBUG_FS @@ -477,6 +487,9 @@ static inline void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, int rx, int tx) {} #endif /* CONFIG_AG71XX_DEBUG_FS */ +void enable_loopback_selftest(struct ag71xx *ag); +void disable_loopback_selftest(struct ag71xx *ag); + void ag71xx_ar7240_start(struct ag71xx *ag); void ag71xx_ar7240_stop(struct ag71xx *ag); int ag71xx_ar7240_init(struct ag71xx *ag); diff --git a/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c b/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c index 38226cf..6d17cbd 100644 --- a/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c +++ b/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c @@ -920,6 +920,197 @@ static void ag71xx_restart_work_func(struct work_struct *work) rtnl_unlock(); } +static void ag71xx_set_digital_loopback(struct ag71xx *ag, bool enable, + unsigned int speed) +{ + struct phy_device *phydev = ag->phy_dev; + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + u32 val; + u32 speed_val; + u32 linktests = 100; + + netif_carrier_off(ag->dev); + netif_stop_queue(ag->dev); + + /* this is bad because it will cause even more link changes + * but this is all we got from QCA + */ + if (enable) { + switch (speed) { + case SPEED_1000: + speed_val = 0x4140; + break; + case SPEED_100: + speed_val = 0x6100; + break; + case SPEED_10: + speed_val = 0x4100; + break; + } + } else { + /* autoneg */ + speed_val = 0x1000; + } + + phy_write(phydev, MII_BMCR, speed_val); + if (enable) { + do { + udelay(10); + val = phy_read(phydev, 0x11); + } while (linktests-- > 0 && !(val & 0x0400)); + + ag->link = 1; + ag->duplex = DUPLEX_FULL; /* force full duplex for test */ + ag->speed = speed; + ag71xx_link_adjust(ag); + } +} + +static int send_looback_packets(struct ag71xx *ag) +{ + unsigned int i; + struct sk_buff *skb; + struct ethhdr *ethhdr; + size_t skb_len = 64; + size_t payload_len = skb_len - sizeof(*ethhdr); + u8 *payload; + int ret; + + for (i = 0; i < 10; i++) { + skb = dev_alloc_skb(skb_len); + if (!skb) + return -ENOMEM; + + ethhdr = (struct ethhdr *)skb_put(skb, sizeof(*ethhdr)); + ether_addr_copy(ethhdr->h_source, ag->dev->dev_addr); + memset(ethhdr->h_dest, 0xff, sizeof(ethhdr->h_dest)); + ethhdr->h_proto = htons(payload_len); + + payload = skb_put(skb, payload_len); + memset(payload, 0x1, payload_len); + + ret = ag71xx_hard_start_xmit(skb, ag->dev); + if (ret != NETDEV_TX_OK) + return -EIO; + } + + return 0; +} + +static bool loopback_packet_check(struct ag71xx *ag, struct sk_buff *skb, + int pktlen) +{ + struct ethhdr *ethhdr; + size_t skb_len = 64; + size_t payload_len = skb_len - sizeof(*ethhdr); + u8 *payload; + unsigned int i; + + if (pktlen < skb_len) + return false; + + ethhdr = (struct ethhdr *)skb->data; + if (!ether_addr_equal(ethhdr->h_source, ag->dev->dev_addr)) + return false; + + if (!is_broadcast_ether_addr(ethhdr->h_dest)) + return false; + + if (ethhdr->h_proto != htons(payload_len)) + return false; + + payload = &skb->data[sizeof(*ethhdr)]; + for (i = 0; i < payload_len; i++) { + if (payload[i] != 0x1) + return false; + } + + return true; +} + +static int ag71xx_rx_packets(struct ag71xx *ag, int limit); + +static void ag71xx_tx_invert_work_func(struct work_struct *work) +{ + unsigned long flags; + struct ag71xx *ag = container_of(work, struct ag71xx, tx_invert_work); + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + bool require_link_adjust = false; + int ret; + int rx_done; + int testruns; + + pr_info("start loopback test\n"); + + testruns = 5; + do { + ret = send_looback_packets(ag); + if (ret < 0) { + pr_err("Failed to send loopback packets\n"); + goto out; + } + + rx_done = ag71xx_rx_packets(ag, 10); + mdelay(10); + } while (testruns-- > 0 && ag->loopback != AG71XX_LPB_TEST_SUCCESS); + + /* test is enabled but was not successful */ + spin_lock_irqsave(&ag->lock, flags); + if (ag->loopback == AG71XX_LPB_TEST_ENABLED) { + /* have to toggle tx_invert but first test if it works */ + pdata->tx_invert_active = !pdata->tx_invert_active; + require_link_adjust = true; + } + spin_unlock_irqrestore(&ag->lock, flags); + + if (require_link_adjust) { + ag71xx_link_adjust(ag); + require_link_adjust = false; + } else { + goto finish; + } + + /* RETEST */ + testruns = 5; + do { + ret = send_looback_packets(ag); + if (ret < 0) { + pr_err("Failed to send loopback packets\n"); + goto out; + } + + rx_done = ag71xx_rx_packets(ag, 10); + mdelay(10); + } while (testruns-- > 0 && ag->loopback != AG71XX_LPB_TEST_SUCCESS); + + /* test is enabled but was not successful */ + spin_lock_irqsave(&ag->lock, flags); + if (ag->loopback == AG71XX_LPB_TEST_ENABLED) { + pr_err("no valid TX_INVERT setting found in loopback test\n"); + pdata->tx_invert_active = !pdata->tx_invert_active; + require_link_adjust = true; + } + spin_unlock_irqrestore(&ag->lock, flags); + + if (require_link_adjust) { + ag71xx_link_adjust(ag); + require_link_adjust = false; + } else { + goto finish; + } + +finish: + /* damn, the hardware often hangs after the link changes - kick it */ + schedule_work(&ag->restart_work); + +out: + /* disable loopback test again */ + ag71xx_set_digital_loopback(ag, false, 0); + spin_lock_irqsave(&ag->lock, flags); + ag->loopback = AG71XX_LPB_TEST_POST_LINKRESET; + spin_unlock_irqrestore(&ag->lock, flags); +} + static bool ag71xx_check_dma_stuck(struct ag71xx *ag, unsigned long timestamp) { u32 rx_sm, tx_sm, rx_fd; @@ -1014,10 +1205,21 @@ static int ag71xx_rx_packets(struct ag71xx *ag, int limit) struct sk_buff_head queue; struct sk_buff *skb; int done = 0; + bool testloopback; + bool loopback_found = false; + unsigned long flags; DBG("%s: rx packets, limit=%d, curr=%u, dirty=%u\n", dev->name, limit, ring->curr, ring->dirty); + /* check if loopback test is currently started */ + spin_lock_irqsave(&ag->lock, flags); + if (ag->loopback == AG71XX_LPB_TEST_ENABLED) + testloopback = true; + else + testloopback = false; + spin_unlock_irqrestore(&ag->lock, flags); + skb_queue_head_init(&queue); while (done < limit) { @@ -1057,6 +1259,14 @@ static int ag71xx_rx_packets(struct ag71xx *ag, int limit) if (ag71xx_has_ar8216(ag)) err = ag71xx_remove_ar8216_header(ag, skb, pktlen); + if (!err && testloopback && + loopback_packet_check(ag, skb, pktlen)) { + loopback_found = true; + + /* drop this packet below */ + err = 1; + } + if (err) { dev->stats.rx_dropped++; kfree_skb(skb); @@ -1083,6 +1293,14 @@ next: DBG("%s: rx finish, curr=%u, dirty=%u, done=%d\n", dev->name, ring->curr, ring->dirty, done); + /* mark loopback test as successful */ + if (loopback_found) { + spin_lock_irqsave(&ag->lock, flags); + if (ag->loopback == AG71XX_LPB_TEST_ENABLED) + ag->loopback = AG71XX_LPB_TEST_SUCCESS; + spin_unlock_irqrestore(&ag->lock, flags); + } + return done; } @@ -1099,6 +1317,16 @@ static int ag71xx_poll(struct napi_struct *napi, int limit) int rx_done; pdata->ddr_flush(); + + /* receive via loopback code */ + spin_lock_irqsave(&ag->lock, flags); + if (ag->loopback == AG71XX_LPB_TEST_ENABLED) { + spin_unlock_irqrestore(&ag->lock, flags); + napi_complete(napi); + return 0; + } + spin_unlock_irqrestore(&ag->lock, flags); + tx_done = ag71xx_tx_packets(ag, false); DBG("%s: processing RX ring\n", dev->name); @@ -1250,6 +1478,59 @@ static const char *ag71xx_get_phy_if_mode_name(phy_interface_t mode) return "unknown"; } +void enable_loopback_selftest(struct ag71xx *ag) +{ + unsigned long flags; + unsigned int speed; + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + + if (!pdata->tx_invert_war) + return; + + spin_lock_irqsave(&ag->lock, flags); + /* check for valid speeds */ + switch (ag->speed) { + case SPEED_1000: + case SPEED_100: + case SPEED_10: + speed = ag->speed; + break; + default: + spin_unlock_irqrestore(&ag->lock, flags); + return; + } + + if (ag->loopback != AG71XX_LPB_TEST_DISABLED) { + spin_unlock_irqrestore(&ag->lock, flags); + return; + } + ag->loopback = AG71XX_LPB_TEST_ENABLED; + spin_unlock_irqrestore(&ag->lock, flags); + + ag71xx_set_digital_loopback(ag, true, speed); + schedule_work(&ag->tx_invert_work); +} + +void disable_loopback_selftest(struct ag71xx *ag) +{ + unsigned long flags; + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + + if (!pdata->tx_invert_war) + return; + + spin_lock_irqsave(&ag->lock, flags); + if (ag->loopback == AG71XX_LPB_TEST_DISABLED) { + spin_unlock_irqrestore(&ag->lock, flags); + return; + } + ag->loopback = AG71XX_LPB_TEST_DISABLED; + spin_unlock_irqrestore(&ag->lock, flags); + + ag71xx_set_digital_loopback(ag, false, 0); + cancel_work_sync(&ag->tx_invert_work); +} + static int ag71xx_probe(struct platform_device *pdev) { @@ -1319,6 +1600,7 @@ static int ag71xx_probe(struct platform_device *pdev) dev->ethtool_ops = &ag71xx_ethtool_ops; INIT_WORK(&ag->restart_work, ag71xx_restart_work_func); + INIT_WORK(&ag->tx_invert_work, ag71xx_tx_invert_work_func); init_timer(&ag->oom_timer); ag->oom_timer.data = (unsigned long) dev; @@ -1403,6 +1685,7 @@ static int ag71xx_remove(struct platform_device *pdev) if (dev) { struct ag71xx *ag = netdev_priv(dev); + disable_loopback_selftest(ag); ag71xx_debugfs_exit(ag); ag71xx_phy_disconnect(ag); unregister_netdev(dev); diff --git a/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c b/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c index 9de77e9..0eae954 100644 --- a/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c +++ b/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c @@ -22,6 +22,11 @@ static void ag71xx_phy_link_adjust(struct net_device *dev) spin_lock_irqsave(&ag->lock, flags); + if (ag->loopback != AG71XX_LPB_TEST_DISABLED && + ag->loopback != AG71XX_LPB_TEST_POST_LINKRESET) { + goto out; + } + if (phydev->link) { if (ag->duplex != phydev->duplex || ag->speed != phydev->speed) { @@ -36,9 +41,19 @@ static void ag71xx_phy_link_adjust(struct net_device *dev) ag->duplex = phydev->duplex; ag->speed = phydev->speed; - if (status_change) + if (status_change) { ag71xx_link_adjust(ag); + + if (!phydev->link) + goto out; + if (ag->loopback == AG71XX_LPB_TEST_POST_LINKRESET) + ag->loopback = AG71XX_LPB_TEST_DISABLED; + else + enable_loopback_selftest(ag); + } + +out: spin_unlock_irqrestore(&ag->lock, flags); } diff --git a/target/linux/generic/patches-3.18/999-dont-set-down-on-loopback.patch b/target/linux/generic/patches-3.18/999-dont-set-down-on-loopback.patch new file mode 100644 index 0000000..eda560a --- /dev/null +++ b/target/linux/generic/patches-3.18/999-dont-set-down-on-loopback.patch @@ -0,0 +1,30 @@ +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -916,6 +916,8 @@ static int gen10g_config_aneg(struct phy + int genphy_update_link(struct phy_device *phydev) + { + int status; ++ u32 loopback; ++ u32 bmcr; + + if (phydev->drv->update_link) + return phydev->drv->update_link(phydev); +@@ -924,13 +926,17 @@ int genphy_update_link(struct phy_device + status = phy_read(phydev, MII_BMSR); + if (status < 0) + return status; ++ /* HACK: check for loopback mode */ ++ bmcr = phy_read(phydev, MII_BMCR); ++ bmcr = phy_read(phydev, MII_BMCR); ++ loopback = bmcr & BMCR_LOOPBACK; + + /* Read link and autonegotiation status */ + status = phy_read(phydev, MII_BMSR); + if (status < 0) + return status; + +- if ((status & BMSR_LSTATUS) == 0) ++ if ((status & BMSR_LSTATUS) == 0 && !loopback) + phydev->link = 0; + else + phydev->link = 1; diff --git a/target/linux/generic/patches-4.1/999-dont-set-down-on-loopback.patch b/target/linux/generic/patches-4.1/999-dont-set-down-on-loopback.patch new file mode 100644 index 0000000..eda560a --- /dev/null +++ b/target/linux/generic/patches-4.1/999-dont-set-down-on-loopback.patch @@ -0,0 +1,30 @@ +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -916,6 +916,8 @@ static int gen10g_config_aneg(struct phy + int genphy_update_link(struct phy_device *phydev) + { + int status; ++ u32 loopback; ++ u32 bmcr; + + if (phydev->drv->update_link) + return phydev->drv->update_link(phydev); +@@ -924,13 +926,17 @@ int genphy_update_link(struct phy_device + status = phy_read(phydev, MII_BMSR); + if (status < 0) + return status; ++ /* HACK: check for loopback mode */ ++ bmcr = phy_read(phydev, MII_BMCR); ++ bmcr = phy_read(phydev, MII_BMCR); ++ loopback = bmcr & BMCR_LOOPBACK; + + /* Read link and autonegotiation status */ + status = phy_read(phydev, MII_BMSR); + if (status < 0) + return status; + +- if ((status & BMSR_LSTATUS) == 0) ++ if ((status & BMSR_LSTATUS) == 0 && !loopback) + phydev->link = 0; + else + phydev->link = 1; diff --git a/target/linux/generic/patches-4.4/999-dont-set-down-on-loopback.patch b/target/linux/generic/patches-4.4/999-dont-set-down-on-loopback.patch new file mode 100644 index 0000000..eda560a --- /dev/null +++ b/target/linux/generic/patches-4.4/999-dont-set-down-on-loopback.patch @@ -0,0 +1,30 @@ +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -916,6 +916,8 @@ static int gen10g_config_aneg(struct phy + int genphy_update_link(struct phy_device *phydev) + { + int status; ++ u32 loopback; ++ u32 bmcr; + + if (phydev->drv->update_link) + return phydev->drv->update_link(phydev); +@@ -924,13 +926,17 @@ int genphy_update_link(struct phy_device + status = phy_read(phydev, MII_BMSR); + if (status < 0) + return status; ++ /* HACK: check for loopback mode */ ++ bmcr = phy_read(phydev, MII_BMCR); ++ bmcr = phy_read(phydev, MII_BMCR); ++ loopback = bmcr & BMCR_LOOPBACK; + + /* Read link and autonegotiation status */ + status = phy_read(phydev, MII_BMSR); + if (status < 0) + return status; + +- if ((status & BMSR_LSTATUS) == 0) ++ if ((status & BMSR_LSTATUS) == 0 && !loopback) + phydev->link = 0; + else + phydev->link = 1;