From 23e6b2dc93630b60a6c33bd2a1289686bc33e272 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:05 -0600 Subject: net: axienet: Fix casting of pointers to u32 This driver was casting skb pointers to u32 and storing them as such in the DMA buffer descriptor, which is obviously broken on 64-bit. The area of the buffer descriptor being used is not accessed by the hardware and has sufficient room for a 32 or 64-bit pointer, so just store the skb pointer as such. Signed-off-by: Robert Hancock Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 11 +++------- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 26 ++++++++++++----------- 2 files changed, 17 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index 011adae32b89..e09dc1433187 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -356,9 +356,6 @@ * @app2: MM2S/S2MM User Application Field 2. * @app3: MM2S/S2MM User Application Field 3. * @app4: MM2S/S2MM User Application Field 4. - * @sw_id_offset: MM2S/S2MM Sw ID - * @reserved5: Reserved and not used - * @reserved6: Reserved and not used */ struct axidma_bd { u32 next; /* Physical address of next buffer descriptor */ @@ -373,11 +370,9 @@ struct axidma_bd { u32 app1; /* TX start << 16 | insert */ u32 app2; /* TX csum seed */ u32 app3; - u32 app4; - u32 sw_id_offset; - u32 reserved5; - u32 reserved6; -}; + u32 app4; /* Last field used by HW */ + struct sk_buff *skb; +} __aligned(XAXIDMA_BD_MINIMUM_ALIGNMENT); /** * struct axienet_local - axienet private per device data diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 831967f6eff8..1bace60de2e5 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -159,8 +159,7 @@ static void axienet_dma_bd_release(struct net_device *ndev) for (i = 0; i < RX_BD_NUM; i++) { dma_unmap_single(ndev->dev.parent, lp->rx_bd_v[i].phys, lp->max_frm_size, DMA_FROM_DEVICE); - dev_kfree_skb((struct sk_buff *) - (lp->rx_bd_v[i].sw_id_offset)); + dev_kfree_skb(lp->rx_bd_v[i].skb); } if (lp->rx_bd_v) { @@ -227,7 +226,7 @@ static int axienet_dma_bd_init(struct net_device *ndev) if (!skb) goto out; - lp->rx_bd_v[i].sw_id_offset = (u32) skb; + lp->rx_bd_v[i].skb = skb; lp->rx_bd_v[i].phys = dma_map_single(ndev->dev.parent, skb->data, lp->max_frm_size, @@ -595,14 +594,15 @@ static void axienet_start_xmit_done(struct net_device *ndev) dma_unmap_single(ndev->dev.parent, cur_p->phys, (cur_p->cntrl & XAXIDMA_BD_CTRL_LENGTH_MASK), DMA_TO_DEVICE); - if (cur_p->app4) - dev_consume_skb_irq((struct sk_buff *)cur_p->app4); + if (cur_p->skb) + dev_consume_skb_irq(cur_p->skb); /*cur_p->phys = 0;*/ cur_p->app0 = 0; cur_p->app1 = 0; cur_p->app2 = 0; cur_p->app4 = 0; cur_p->status = 0; + cur_p->skb = NULL; size += status & XAXIDMA_BD_STS_ACTUAL_LEN_MASK; packets++; @@ -707,7 +707,7 @@ axienet_start_xmit(struct sk_buff *skb, struct net_device *ndev) } cur_p->cntrl |= XAXIDMA_BD_CTRL_TXEOF_MASK; - cur_p->app4 = (unsigned long)skb; + cur_p->skb = skb; tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail; /* Start the transfer */ @@ -742,13 +742,15 @@ static void axienet_recv(struct net_device *ndev) while ((cur_p->status & XAXIDMA_BD_STS_COMPLETE_MASK)) { tail_p = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_ci; - skb = (struct sk_buff *) (cur_p->sw_id_offset); - length = cur_p->app4 & 0x0000FFFF; dma_unmap_single(ndev->dev.parent, cur_p->phys, lp->max_frm_size, DMA_FROM_DEVICE); + skb = cur_p->skb; + cur_p->skb = NULL; + length = cur_p->app4 & 0x0000FFFF; + skb_put(skb, length); skb->protocol = eth_type_trans(skb, ndev); /*skb_checksum_none_assert(skb);*/ @@ -783,7 +785,7 @@ static void axienet_recv(struct net_device *ndev) DMA_FROM_DEVICE); cur_p->cntrl = lp->max_frm_size; cur_p->status = 0; - cur_p->sw_id_offset = (u32) new_skb; + cur_p->skb = new_skb; ++lp->rx_bd_ci; lp->rx_bd_ci %= RX_BD_NUM; @@ -1343,8 +1345,8 @@ static void axienet_dma_err_handler(unsigned long data) (cur_p->cntrl & XAXIDMA_BD_CTRL_LENGTH_MASK), DMA_TO_DEVICE); - if (cur_p->app4) - dev_kfree_skb_irq((struct sk_buff *) cur_p->app4); + if (cur_p->skb) + dev_kfree_skb_irq(cur_p->skb); cur_p->phys = 0; cur_p->cntrl = 0; cur_p->status = 0; @@ -1353,7 +1355,7 @@ static void axienet_dma_err_handler(unsigned long data) cur_p->app2 = 0; cur_p->app3 = 0; cur_p->app4 = 0; - cur_p->sw_id_offset = 0; + cur_p->skb = NULL; } for (i = 0; i < RX_BD_NUM; i++) { -- cgit From d85f5f3e4eb38300642d2b54c5740b351c0bdcc5 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:06 -0600 Subject: net: axienet: Use standard IO accessors This driver was using in_be32 and out_be32 IO accessors which do not exist on most platforms. Also, the use of big-endian accessors does not seem correct as this hardware is accessed over an AXI bus which, to the extent it has an endian-ness, is little-endian. Switch to standard ioread32/iowrite32 accessors. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 4 ++-- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index e09dc1433187..d82e3b63d442 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -476,7 +476,7 @@ struct axienet_option { */ static inline u32 axienet_ior(struct axienet_local *lp, off_t offset) { - return in_be32(lp->regs + offset); + return ioread32(lp->regs + offset); } static inline u32 axinet_ior_read_mcr(struct axienet_local *lp) @@ -496,7 +496,7 @@ static inline u32 axinet_ior_read_mcr(struct axienet_local *lp) static inline void axienet_iow(struct axienet_local *lp, off_t offset, u32 value) { - out_be32((lp->regs + offset), value); + iowrite32(value, lp->regs + offset); } /* Function prototypes visible in xilinx_axienet_mdio.c for other files */ diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 1bace60de2e5..55beca100999 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -125,7 +125,7 @@ static struct axienet_option axienet_options[] = { */ static inline u32 axienet_dma_in32(struct axienet_local *lp, off_t reg) { - return in_be32(lp->dma_regs + reg); + return ioread32(lp->dma_regs + reg); } /** @@ -140,7 +140,7 @@ static inline u32 axienet_dma_in32(struct axienet_local *lp, off_t reg) static inline void axienet_dma_out32(struct axienet_local *lp, off_t reg, u32 value) { - out_be32((lp->dma_regs + reg), value); + iowrite32(value, lp->dma_regs + reg); } /** -- cgit From 88a972d74ea9172a156cdffb6bb30fbb4b1e27c6 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:07 -0600 Subject: net: axienet: fix MDIO bus naming The MDIO bus for this driver was being named using the result of of_address_to_resource on a node which may not have any resource on it, but the return value of that call was not checked so it was using some random value in the bus name. Change to name the MDIO bus based on the resource start of the actual Ethernet register block. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 2 ++ drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 1 + drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 11 +++++------ 3 files changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index d82e3b63d442..f9078bd7f86f 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -380,6 +380,7 @@ struct axidma_bd { * @dev: Pointer to device structure * @phy_node: Pointer to device node structure * @mii_bus: Pointer to MII bus structure + * @regs_start: Resource start for axienet device addresses * @regs: Base address for the axienet_local device address space * @dma_regs: Base address for the axidma device address space * @dma_err_tasklet: Tasklet structure to process Axi DMA errors @@ -421,6 +422,7 @@ struct axienet_local { struct mii_bus *mii_bus; /* MII bus reference */ /* IO registers, dma functions and IRQs */ + resource_size_t regs_start; void __iomem *regs; void __iomem *dma_regs; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 55beca100999..ffbd4d762534 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -1480,6 +1480,7 @@ static int axienet_probe(struct platform_device *pdev) lp->options = XAE_OPTION_DEFAULTS; /* Map device registers */ ethres = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lp->regs_start = ethres->start; lp->regs = devm_ioremap_resource(&pdev->dev, ethres); if (IS_ERR(lp->regs)) { dev_err(&pdev->dev, "could not map Axi Ethernet regs.\n"); diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index 704babdbc8a2..665ae1d16f1e 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -127,7 +127,7 @@ int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) int ret; u32 clk_div, host_clock; struct mii_bus *bus; - struct resource res; + struct device_node *mdio_node; struct device_node *np1; /* clk_div can be calculated by deriving it from the equation: @@ -199,10 +199,9 @@ issue: if (!bus) return -ENOMEM; - np1 = of_get_parent(lp->phy_node); - of_address_to_resource(np1, 0, &res); - snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", - (unsigned long long) res.start); + mdio_node = of_get_parent(lp->phy_node); + snprintf(bus->id, MII_BUS_ID_SIZE, "axienet-%.8llx", + (unsigned long long)lp->regs_start); bus->priv = lp; bus->name = "Xilinx Axi Ethernet MDIO"; @@ -211,7 +210,7 @@ issue: bus->parent = lp->dev; lp->mii_bus = bus; - ret = of_mdiobus_register(bus, np1); + ret = of_mdiobus_register(bus, mdio_node); if (ret) { mdiobus_free(bus); lp->mii_bus = NULL; -- cgit From 2190cd1974abbafeeb17ce654e679c5d646842e1 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:08 -0600 Subject: net: axienet: add X86 and ARM as supported platforms This driver should now build on (at least) X86 and ARM platforms, so add them as supported platforms for the driver in Kconfig. Signed-off-by: Robert Hancock Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig index af96e05c5bcd..5f50764ea255 100644 --- a/drivers/net/ethernet/xilinx/Kconfig +++ b/drivers/net/ethernet/xilinx/Kconfig @@ -6,7 +6,7 @@ config NET_VENDOR_XILINX bool "Xilinx devices" default y - depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS || X86 || COMPILE_TEST + depends on PPC || PPC32 || MICROBLAZE || ARCH_ZYNQ || MIPS || X86 || ARM || COMPILE_TEST ---help--- If you have a network (Ethernet) card belonging to this class, say Y. @@ -26,7 +26,7 @@ config XILINX_EMACLITE config XILINX_AXI_EMAC tristate "Xilinx 10/100/1000 AXI Ethernet support" - depends on MICROBLAZE + depends on MICROBLAZE || X86 || ARM || COMPILE_TEST select PHYLIB ---help--- This driver supports the 10/100/1000 Ethernet from Xilinx for the -- cgit From 09a0354cadec267be7f5249c89eb998b3474263a Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:09 -0600 Subject: net: axienet: Use clock framework to get device clock rate This driver was previously always calculating the MDIO clock divisor (from AXI bus clock to MDIO bus clock) based on the CPU clock frequency, assuming that it is the same as the AXI bus frequency, but that simplistic method only works on the MicroBlaze platform. Add support for specifying the clock used for the device in the device tree using the clock framework. If the clock is specified then it will be used when calculating the clock divisor. The previous CPU clock detection method is left for backward compatibility if no clock is specified. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 5 ++- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 23 +++++++++- drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 53 ++++++++++++----------- 3 files changed, 53 insertions(+), 28 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index f9078bd7f86f..f240ff1b7296 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -418,6 +418,9 @@ struct axienet_local { /* Connection to PHY device */ struct device_node *phy_node; + /* Clock for AXI bus */ + struct clk *clk; + /* MDIO bus data */ struct mii_bus *mii_bus; /* MII bus reference */ @@ -502,7 +505,7 @@ static inline void axienet_iow(struct axienet_local *lp, off_t offset, } /* Function prototypes visible in xilinx_axienet_mdio.c for other files */ -int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np); +int axienet_mdio_setup(struct axienet_local *lp); int axienet_mdio_wait_until_ready(struct axienet_local *lp); void axienet_mdio_teardown(struct axienet_local *lp); diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index ffbd4d762534..42b343cb3e2b 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -21,6 +21,7 @@ * - Add support for extended VLAN support. */ +#include #include #include #include @@ -1611,9 +1612,24 @@ static int axienet_probe(struct platform_device *pdev) lp->phy_node = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0); if (lp->phy_node) { - ret = axienet_mdio_setup(lp, pdev->dev.of_node); + lp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lp->clk)) { + dev_warn(&pdev->dev, "Failed to get clock: %ld\n", + PTR_ERR(lp->clk)); + lp->clk = NULL; + } else { + ret = clk_prepare_enable(lp->clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock: %d\n", + ret); + goto free_netdev; + } + } + + ret = axienet_mdio_setup(lp); if (ret) - dev_warn(&pdev->dev, "error registering MDIO bus\n"); + dev_warn(&pdev->dev, + "error registering MDIO bus: %d\n", ret); } ret = register_netdev(lp->ndev); @@ -1638,6 +1654,9 @@ static int axienet_remove(struct platform_device *pdev) axienet_mdio_teardown(lp); unregister_netdev(ndev); + if (lp->clk) + clk_disable_unprepare(lp->clk); + of_node_put(lp->phy_node); lp->phy_node = NULL; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index 665ae1d16f1e..cce0dc42583e 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -8,6 +8,7 @@ * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. */ +#include #include #include #include @@ -16,7 +17,7 @@ #include "xilinx_axienet.h" #define MAX_MDIO_FREQ 2500000 /* 2.5 MHz */ -#define DEFAULT_CLOCK_DIVISOR XAE_MDIO_DIV_DFT +#define DEFAULT_HOST_CLOCK 150000000 /* 150 MHz */ /* Wait till MDIO interface is ready to accept a new transaction.*/ int axienet_mdio_wait_until_ready(struct axienet_local *lp) @@ -114,7 +115,6 @@ static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, /** * axienet_mdio_setup - MDIO setup function * @lp: Pointer to axienet local data structure. - * @np: Pointer to device node * * Return: 0 on success, -ETIMEDOUT on a timeout, -ENOMEM when * mdiobus_alloc (to allocate memory for mii bus structure) fails. @@ -122,13 +122,37 @@ static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, * Sets up the MDIO interface by initializing the MDIO clock and enabling the * MDIO interface in hardware. Register the MDIO interface. **/ -int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) +int axienet_mdio_setup(struct axienet_local *lp) { int ret; u32 clk_div, host_clock; struct mii_bus *bus; struct device_node *mdio_node; - struct device_node *np1; + + if (lp->clk) { + host_clock = clk_get_rate(lp->clk); + } else { + struct device_node *np1; + + /* Legacy fallback: detect CPU clock frequency and use as AXI + * bus clock frequency. This only works on certain platforms. + */ + np1 = of_find_node_by_name(NULL, "cpu"); + if (!np1) { + netdev_warn(lp->ndev, "Could not find CPU device node.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } else { + ret = of_property_read_u32(np1, "clock-frequency", + &host_clock); + if (ret) { + netdev_warn(lp->ndev, "CPU clock-frequency property not found.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } + of_node_put(np1); + } + netdev_info(lp->ndev, "Setting assumed host clock to %u\n", + host_clock); + } /* clk_div can be calculated by deriving it from the equation: * fMDIO = fHOST / ((1 + clk_div) * 2) @@ -155,25 +179,6 @@ int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) * "clock-frequency" from the CPU */ - np1 = of_find_node_by_name(NULL, "cpu"); - if (!np1) { - netdev_warn(lp->ndev, "Could not find CPU device node.\n"); - netdev_warn(lp->ndev, - "Setting MDIO clock divisor to default %d\n", - DEFAULT_CLOCK_DIVISOR); - clk_div = DEFAULT_CLOCK_DIVISOR; - goto issue; - } - if (of_property_read_u32(np1, "clock-frequency", &host_clock)) { - netdev_warn(lp->ndev, "clock-frequency property not found.\n"); - netdev_warn(lp->ndev, - "Setting MDIO clock divisor to default %d\n", - DEFAULT_CLOCK_DIVISOR); - clk_div = DEFAULT_CLOCK_DIVISOR; - of_node_put(np1); - goto issue; - } - clk_div = (host_clock / (MAX_MDIO_FREQ * 2)) - 1; /* If there is any remainder from the division of * fHOST / (MAX_MDIO_FREQ * 2), then we need to add @@ -186,8 +191,6 @@ int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) "Setting MDIO clock divisor to %u/%u Hz host clock.\n", clk_div, host_clock); - of_node_put(np1); -issue: axienet_iow(lp, XAE_MDIO_MC_OFFSET, (((u32) clk_div) | XAE_MDIO_MC_MDIOEN_MASK)); -- cgit From e7a3d116fa6fd1b898f6d7421a07f01fa38394b9 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:10 -0600 Subject: net: axienet: fix teardown order of MDIO bus Since the MDIO is is brought up before the netdev is registered, it should be torn down after the netdev is removed. Otherwise, PHY accesses can potentially access freed MDIO bus references and cause a crash. Signed-off-by: Robert Hancock Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 42b343cb3e2b..5cb39deca8d2 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -1651,8 +1651,8 @@ static int axienet_remove(struct platform_device *pdev) struct net_device *ndev = platform_get_drvdata(pdev); struct axienet_local *lp = netdev_priv(ndev); - axienet_mdio_teardown(lp); unregister_netdev(ndev); + axienet_mdio_teardown(lp); if (lp->clk) clk_disable_unprepare(lp->clk); -- cgit From 7789e9ed0591bab4d8a419bf3b9b18327d75128f Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:11 -0600 Subject: net: axienet: Re-initialize MDIO registers properly after reset The MDIO clock divisor register setting was only applied on the initial startup when the driver was loaded. However, this setting is cleared when the device is reset, such as would occur when the interface was taken down and brought up again, and so the MDIO bus would be non-functional afterwards. Split up the MDIO bus setup and enable into separate functions and re-enable the bus after a device reset, to ensure that the MDIO registers are set properly. This also allows us to remove direct access to MDIO registers in xilinx_axienet_main.c and centralize them all in xilinx_axienet_mdio.c. Also, lock the MDIO bus lock around the device reset process, to avoid MDIO accesses from occurring while the MDIO is disabled during the reset. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 3 +- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 38 +++++++--------- drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 54 +++++++++++++++++------ 3 files changed, 57 insertions(+), 38 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index f240ff1b7296..4a135ed6e8a3 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -505,8 +505,9 @@ static inline void axienet_iow(struct axienet_local *lp, off_t offset, } /* Function prototypes visible in xilinx_axienet_mdio.c for other files */ +int axienet_mdio_enable(struct axienet_local *lp); +void axienet_mdio_disable(struct axienet_local *lp); int axienet_mdio_setup(struct axienet_local *lp); -int axienet_mdio_wait_until_ready(struct axienet_local *lp); void axienet_mdio_teardown(struct axienet_local *lp); #endif /* XILINX_AXI_ENET_H */ diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 5cb39deca8d2..e735ca7b5df0 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -914,27 +914,23 @@ static void axienet_dma_err_handler(unsigned long data); */ static int axienet_open(struct net_device *ndev) { - int ret, mdio_mcreg; + int ret; struct axienet_local *lp = netdev_priv(ndev); struct phy_device *phydev = NULL; dev_dbg(&ndev->dev, "axienet_open()\n"); - mdio_mcreg = axienet_ior(lp, XAE_MDIO_MC_OFFSET); - ret = axienet_mdio_wait_until_ready(lp); - if (ret < 0) - return ret; /* Disable the MDIO interface till Axi Ethernet Reset is completed. * When we do an Axi Ethernet reset, it resets the complete core - * including the MDIO. If MDIO is not disabled when the reset - * process is started, MDIO will be broken afterwards. + * including the MDIO. MDIO must be disabled before resetting + * and re-enabled afterwards. + * Hold MDIO bus lock to avoid MDIO accesses during the reset. */ - axienet_iow(lp, XAE_MDIO_MC_OFFSET, - (mdio_mcreg & (~XAE_MDIO_MC_MDIOEN_MASK))); + mutex_lock(&lp->mii_bus->mdio_lock); + axienet_mdio_disable(lp); axienet_device_reset(ndev); - /* Enable the MDIO */ - axienet_iow(lp, XAE_MDIO_MC_OFFSET, mdio_mcreg); - ret = axienet_mdio_wait_until_ready(lp); + ret = axienet_mdio_enable(lp); + mutex_unlock(&lp->mii_bus->mdio_lock); if (ret < 0) return ret; @@ -1316,28 +1312,24 @@ static void axienet_dma_err_handler(unsigned long data) { u32 axienet_status; u32 cr, i; - int mdio_mcreg; struct axienet_local *lp = (struct axienet_local *) data; struct net_device *ndev = lp->ndev; struct axidma_bd *cur_p; axienet_setoptions(ndev, lp->options & ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); - mdio_mcreg = axienet_ior(lp, XAE_MDIO_MC_OFFSET); - axienet_mdio_wait_until_ready(lp); /* Disable the MDIO interface till Axi Ethernet Reset is completed. * When we do an Axi Ethernet reset, it resets the complete core - * including the MDIO. So if MDIO is not disabled when the reset - * process is started, MDIO will be broken afterwards. + * including the MDIO. MDIO must be disabled before resetting + * and re-enabled afterwards. + * Hold MDIO bus lock to avoid MDIO accesses during the reset. */ - axienet_iow(lp, XAE_MDIO_MC_OFFSET, (mdio_mcreg & - ~XAE_MDIO_MC_MDIOEN_MASK)); - + mutex_lock(&lp->mii_bus->mdio_lock); + axienet_mdio_disable(lp); __axienet_device_reset(lp, XAXIDMA_TX_CR_OFFSET); __axienet_device_reset(lp, XAXIDMA_RX_CR_OFFSET); - - axienet_iow(lp, XAE_MDIO_MC_OFFSET, mdio_mcreg); - axienet_mdio_wait_until_ready(lp); + axienet_mdio_enable(lp); + mutex_unlock(&lp->mii_bus->mdio_lock); for (i = 0; i < TX_BD_NUM; i++) { cur_p = &lp->tx_bd_v[i]; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index cce0dc42583e..7106810793d5 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -5,6 +5,7 @@ * Copyright (c) 2009 Secret Lab Technologies, Ltd. * Copyright (c) 2010 - 2011 Michal Simek * Copyright (c) 2010 - 2011 PetaLogix + * Copyright (c) 2019 SED Systems, a division of Calian Ltd. * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. */ @@ -20,7 +21,7 @@ #define DEFAULT_HOST_CLOCK 150000000 /* 150 MHz */ /* Wait till MDIO interface is ready to accept a new transaction.*/ -int axienet_mdio_wait_until_ready(struct axienet_local *lp) +static int axienet_mdio_wait_until_ready(struct axienet_local *lp) { u32 val; @@ -113,21 +114,17 @@ static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, } /** - * axienet_mdio_setup - MDIO setup function + * axienet_mdio_enable - MDIO hardware setup function * @lp: Pointer to axienet local data structure. * - * Return: 0 on success, -ETIMEDOUT on a timeout, -ENOMEM when - * mdiobus_alloc (to allocate memory for mii bus structure) fails. + * Return: 0 on success, -ETIMEDOUT on a timeout. * * Sets up the MDIO interface by initializing the MDIO clock and enabling the - * MDIO interface in hardware. Register the MDIO interface. + * MDIO interface in hardware. **/ -int axienet_mdio_setup(struct axienet_local *lp) +int axienet_mdio_enable(struct axienet_local *lp) { - int ret; u32 clk_div, host_clock; - struct mii_bus *bus; - struct device_node *mdio_node; if (lp->clk) { host_clock = clk_get_rate(lp->clk); @@ -142,8 +139,8 @@ int axienet_mdio_setup(struct axienet_local *lp) netdev_warn(lp->ndev, "Could not find CPU device node.\n"); host_clock = DEFAULT_HOST_CLOCK; } else { - ret = of_property_read_u32(np1, "clock-frequency", - &host_clock); + int ret = of_property_read_u32(np1, "clock-frequency", + &host_clock); if (ret) { netdev_warn(lp->ndev, "CPU clock-frequency property not found.\n"); host_clock = DEFAULT_HOST_CLOCK; @@ -191,10 +188,39 @@ int axienet_mdio_setup(struct axienet_local *lp) "Setting MDIO clock divisor to %u/%u Hz host clock.\n", clk_div, host_clock); - axienet_iow(lp, XAE_MDIO_MC_OFFSET, - (((u32) clk_div) | XAE_MDIO_MC_MDIOEN_MASK)); + axienet_iow(lp, XAE_MDIO_MC_OFFSET, clk_div | XAE_MDIO_MC_MDIOEN_MASK); - ret = axienet_mdio_wait_until_ready(lp); + return axienet_mdio_wait_until_ready(lp); +} + +/** + * axienet_mdio_disable - MDIO hardware disable function + * @lp: Pointer to axienet local data structure. + * + * Disable the MDIO interface in hardware. + **/ +void axienet_mdio_disable(struct axienet_local *lp) +{ + axienet_iow(lp, XAE_MDIO_MC_OFFSET, 0); +} + +/** + * axienet_mdio_setup - MDIO setup function + * @lp: Pointer to axienet local data structure. + * + * Return: 0 on success, -ETIMEDOUT on a timeout, -ENOMEM when + * mdiobus_alloc (to allocate memory for mii bus structure) fails. + * + * Sets up the MDIO interface by initializing the MDIO clock and enabling the + * MDIO interface in hardware. Register the MDIO interface. + **/ +int axienet_mdio_setup(struct axienet_local *lp) +{ + struct device_node *mdio_node; + struct mii_bus *bus; + int ret; + + ret = axienet_mdio_enable(lp); if (ret < 0) return ret; -- cgit From 489d4d7713d1cad40b1049720b44d08f53918b30 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:12 -0600 Subject: net: axienet: Cleanup DMA device reset and halt process The Xilinx DMA blocks each have their own reset register, but they both reset the entire DMA engine, so only one of them needs to be reset. Also, when stopping the device, we need to not just command the DMA blocks to stop, but wait for them to stop, and trigger a device reset to ensure that they are completely stopped. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 2 + drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 54 +++++++++++++++++------ 2 files changed, 42 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index 4a135ed6e8a3..1ffb113e2c29 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -83,6 +83,8 @@ #define XAXIDMA_CR_RUNSTOP_MASK 0x00000001 /* Start/stop DMA channel */ #define XAXIDMA_CR_RESET_MASK 0x00000004 /* Reset DMA engine */ +#define XAXIDMA_SR_HALT_MASK 0x00000001 /* Indicates DMA channel halted */ + #define XAXIDMA_BD_NDESC_OFFSET 0x00 /* Next descriptor pointer */ #define XAXIDMA_BD_BUFA_OFFSET 0x08 /* Buffer address */ #define XAXIDMA_BD_CTRL_LEN_OFFSET 0x18 /* Control/buffer length */ diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index e735ca7b5df0..bdc6e8047959 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -434,17 +434,20 @@ static void axienet_setoptions(struct net_device *ndev, u32 options) lp->options |= options; } -static void __axienet_device_reset(struct axienet_local *lp, off_t offset) +static void __axienet_device_reset(struct axienet_local *lp) { u32 timeout; /* Reset Axi DMA. This would reset Axi Ethernet core as well. The reset * process of Axi DMA takes a while to complete as all pending * commands/transfers will be flushed or completed during this * reset process. + * Note that even though both TX and RX have their own reset register, + * they both reset the entire DMA core, so only one needs to be used. */ - axienet_dma_out32(lp, offset, XAXIDMA_CR_RESET_MASK); + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, XAXIDMA_CR_RESET_MASK); timeout = DELAY_OF_ONE_MILLISEC; - while (axienet_dma_in32(lp, offset) & XAXIDMA_CR_RESET_MASK) { + while (axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET) & + XAXIDMA_CR_RESET_MASK) { udelay(1); if (--timeout == 0) { netdev_err(lp->ndev, "%s: DMA reset timeout!\n", @@ -470,8 +473,7 @@ static void axienet_device_reset(struct net_device *ndev) u32 axienet_status; struct axienet_local *lp = netdev_priv(ndev); - __axienet_device_reset(lp, XAXIDMA_TX_CR_OFFSET); - __axienet_device_reset(lp, XAXIDMA_RX_CR_OFFSET); + __axienet_device_reset(lp); lp->max_frm_size = XAE_MAX_VLAN_FRAME_SIZE; lp->options |= XAE_OPTION_VLAN; @@ -981,20 +983,45 @@ err_tx_irq: */ static int axienet_stop(struct net_device *ndev) { - u32 cr; + u32 cr, sr; + int count; struct axienet_local *lp = netdev_priv(ndev); dev_dbg(&ndev->dev, "axienet_close()\n"); - cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); - axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, - cr & (~XAXIDMA_CR_RUNSTOP_MASK)); - cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); - axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, - cr & (~XAXIDMA_CR_RUNSTOP_MASK)); axienet_setoptions(ndev, lp->options & ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); + cr = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); + cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); + axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr); + + cr = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); + cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); + axienet_dma_out32(lp, XAXIDMA_TX_CR_OFFSET, cr); + + axienet_iow(lp, XAE_IE_OFFSET, 0); + + /* Give DMAs a chance to halt gracefully */ + sr = axienet_dma_in32(lp, XAXIDMA_RX_SR_OFFSET); + for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { + msleep(20); + sr = axienet_dma_in32(lp, XAXIDMA_RX_SR_OFFSET); + } + + sr = axienet_dma_in32(lp, XAXIDMA_TX_SR_OFFSET); + for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { + msleep(20); + sr = axienet_dma_in32(lp, XAXIDMA_TX_SR_OFFSET); + } + + /* Do a reset to ensure DMA is really stopped */ + mutex_lock(&lp->mii_bus->mdio_lock); + axienet_mdio_disable(lp); + __axienet_device_reset(lp); + axienet_mdio_enable(lp); + mutex_unlock(&lp->mii_bus->mdio_lock); + tasklet_kill(&lp->dma_err_tasklet); free_irq(lp->tx_irq, ndev); @@ -1326,8 +1353,7 @@ static void axienet_dma_err_handler(unsigned long data) */ mutex_lock(&lp->mii_bus->mdio_lock); axienet_mdio_disable(lp); - __axienet_device_reset(lp, XAXIDMA_TX_CR_OFFSET); - __axienet_device_reset(lp, XAXIDMA_RX_CR_OFFSET); + __axienet_device_reset(lp); axienet_mdio_enable(lp); mutex_unlock(&lp->mii_bus->mdio_lock); -- cgit From 8b09ca823ffb4eee44cc1e41af2935e511eb98f1 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:13 -0600 Subject: net: axienet: Make RX/TX ring sizes configurable Add support for setting the RX and TX ring sizes for this driver using ethtool. Also increase the default RX ring size as the previous default was far too low for good performance in some configurations. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 2 + drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 90 ++++++++++++++++------- 2 files changed, 67 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index 1ffb113e2c29..6b6d28ff014f 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -444,8 +444,10 @@ struct axienet_local { /* Buffer descriptors */ struct axidma_bd *tx_bd_v; dma_addr_t tx_bd_p; + u32 tx_bd_num; struct axidma_bd *rx_bd_v; dma_addr_t rx_bd_p; + u32 rx_bd_num; u32 tx_bd_ci; u32 tx_bd_tail; u32 rx_bd_ci; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index bdc6e8047959..2c2d626c12a7 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -39,9 +39,11 @@ #include "xilinx_axienet.h" -/* Descriptors defines for Tx and Rx DMA - 2^n for the best performance */ -#define TX_BD_NUM 64 -#define RX_BD_NUM 128 +/* Descriptors defines for Tx and Rx DMA */ +#define TX_BD_NUM_DEFAULT 64 +#define RX_BD_NUM_DEFAULT 1024 +#define TX_BD_NUM_MAX 4096 +#define RX_BD_NUM_MAX 4096 /* Must be shorter than length of ethtool_drvinfo.driver field to fit */ #define DRIVER_NAME "xaxienet" @@ -157,7 +159,7 @@ static void axienet_dma_bd_release(struct net_device *ndev) int i; struct axienet_local *lp = netdev_priv(ndev); - for (i = 0; i < RX_BD_NUM; i++) { + for (i = 0; i < lp->rx_bd_num; i++) { dma_unmap_single(ndev->dev.parent, lp->rx_bd_v[i].phys, lp->max_frm_size, DMA_FROM_DEVICE); dev_kfree_skb(lp->rx_bd_v[i].skb); @@ -165,13 +167,13 @@ static void axienet_dma_bd_release(struct net_device *ndev) if (lp->rx_bd_v) { dma_free_coherent(ndev->dev.parent, - sizeof(*lp->rx_bd_v) * RX_BD_NUM, + sizeof(*lp->rx_bd_v) * lp->rx_bd_num, lp->rx_bd_v, lp->rx_bd_p); } if (lp->tx_bd_v) { dma_free_coherent(ndev->dev.parent, - sizeof(*lp->tx_bd_v) * TX_BD_NUM, + sizeof(*lp->tx_bd_v) * lp->tx_bd_num, lp->tx_bd_v, lp->tx_bd_p); } @@ -201,27 +203,27 @@ static int axienet_dma_bd_init(struct net_device *ndev) /* Allocate the Tx and Rx buffer descriptors. */ lp->tx_bd_v = dma_alloc_coherent(ndev->dev.parent, - sizeof(*lp->tx_bd_v) * TX_BD_NUM, + sizeof(*lp->tx_bd_v) * lp->tx_bd_num, &lp->tx_bd_p, GFP_KERNEL); if (!lp->tx_bd_v) goto out; lp->rx_bd_v = dma_alloc_coherent(ndev->dev.parent, - sizeof(*lp->rx_bd_v) * RX_BD_NUM, + sizeof(*lp->rx_bd_v) * lp->rx_bd_num, &lp->rx_bd_p, GFP_KERNEL); if (!lp->rx_bd_v) goto out; - for (i = 0; i < TX_BD_NUM; i++) { + for (i = 0; i < lp->tx_bd_num; i++) { lp->tx_bd_v[i].next = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * - ((i + 1) % TX_BD_NUM); + ((i + 1) % lp->tx_bd_num); } - for (i = 0; i < RX_BD_NUM; i++) { + for (i = 0; i < lp->rx_bd_num; i++) { lp->rx_bd_v[i].next = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * - ((i + 1) % RX_BD_NUM); + ((i + 1) % lp->rx_bd_num); skb = netdev_alloc_skb_ip_align(ndev, lp->max_frm_size); if (!skb) @@ -269,7 +271,7 @@ static int axienet_dma_bd_init(struct net_device *ndev) axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr | XAXIDMA_CR_RUNSTOP_MASK); axienet_dma_out32(lp, XAXIDMA_RX_TDESC_OFFSET, lp->rx_bd_p + - (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1))); + (sizeof(*lp->rx_bd_v) * (lp->rx_bd_num - 1))); /* Write to the RS (Run-stop) bit in the Tx channel control register. * Tx channel is now ready to run. But only after we write to the @@ -610,8 +612,8 @@ static void axienet_start_xmit_done(struct net_device *ndev) size += status & XAXIDMA_BD_STS_ACTUAL_LEN_MASK; packets++; - ++lp->tx_bd_ci; - lp->tx_bd_ci %= TX_BD_NUM; + if (++lp->tx_bd_ci >= lp->tx_bd_num) + lp->tx_bd_ci = 0; cur_p = &lp->tx_bd_v[lp->tx_bd_ci]; status = cur_p->status; } @@ -638,7 +640,7 @@ static inline int axienet_check_tx_bd_space(struct axienet_local *lp, int num_frag) { struct axidma_bd *cur_p; - cur_p = &lp->tx_bd_v[(lp->tx_bd_tail + num_frag) % TX_BD_NUM]; + cur_p = &lp->tx_bd_v[(lp->tx_bd_tail + num_frag) % lp->tx_bd_num]; if (cur_p->status & XAXIDMA_BD_STS_ALL_MASK) return NETDEV_TX_BUSY; return 0; @@ -698,8 +700,8 @@ axienet_start_xmit(struct sk_buff *skb, struct net_device *ndev) skb_headlen(skb), DMA_TO_DEVICE); for (ii = 0; ii < num_frag; ii++) { - ++lp->tx_bd_tail; - lp->tx_bd_tail %= TX_BD_NUM; + if (++lp->tx_bd_tail >= lp->tx_bd_num) + lp->tx_bd_tail = 0; cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; frag = &skb_shinfo(skb)->frags[ii]; cur_p->phys = dma_map_single(ndev->dev.parent, @@ -715,8 +717,8 @@ axienet_start_xmit(struct sk_buff *skb, struct net_device *ndev) tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail; /* Start the transfer */ axienet_dma_out32(lp, XAXIDMA_TX_TDESC_OFFSET, tail_p); - ++lp->tx_bd_tail; - lp->tx_bd_tail %= TX_BD_NUM; + if (++lp->tx_bd_tail >= lp->tx_bd_num) + lp->tx_bd_tail = 0; return NETDEV_TX_OK; } @@ -790,8 +792,8 @@ static void axienet_recv(struct net_device *ndev) cur_p->status = 0; cur_p->skb = new_skb; - ++lp->rx_bd_ci; - lp->rx_bd_ci %= RX_BD_NUM; + if (++lp->rx_bd_ci >= lp->rx_bd_num) + lp->rx_bd_ci = 0; cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; } @@ -1179,6 +1181,40 @@ static void axienet_ethtools_get_regs(struct net_device *ndev, data[31] = axienet_ior(lp, XAE_AF1_OFFSET); } +static void axienet_ethtools_get_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ering) +{ + struct axienet_local *lp = netdev_priv(ndev); + + ering->rx_max_pending = RX_BD_NUM_MAX; + ering->rx_mini_max_pending = 0; + ering->rx_jumbo_max_pending = 0; + ering->tx_max_pending = TX_BD_NUM_MAX; + ering->rx_pending = lp->rx_bd_num; + ering->rx_mini_pending = 0; + ering->rx_jumbo_pending = 0; + ering->tx_pending = lp->tx_bd_num; +} + +static int axienet_ethtools_set_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ering) +{ + struct axienet_local *lp = netdev_priv(ndev); + + if (ering->rx_pending > RX_BD_NUM_MAX || + ering->rx_mini_pending || + ering->rx_jumbo_pending || + ering->rx_pending > TX_BD_NUM_MAX) + return -EINVAL; + + if (netif_running(ndev)) + return -EBUSY; + + lp->rx_bd_num = ering->rx_pending; + lp->tx_bd_num = ering->tx_pending; + return 0; +} + /** * axienet_ethtools_get_pauseparam - Get the pause parameter setting for * Tx and Rx paths. @@ -1320,6 +1356,8 @@ static const struct ethtool_ops axienet_ethtool_ops = { .get_regs_len = axienet_ethtools_get_regs_len, .get_regs = axienet_ethtools_get_regs, .get_link = ethtool_op_get_link, + .get_ringparam = axienet_ethtools_get_ringparam, + .set_ringparam = axienet_ethtools_set_ringparam, .get_pauseparam = axienet_ethtools_get_pauseparam, .set_pauseparam = axienet_ethtools_set_pauseparam, .get_coalesce = axienet_ethtools_get_coalesce, @@ -1357,7 +1395,7 @@ static void axienet_dma_err_handler(unsigned long data) axienet_mdio_enable(lp); mutex_unlock(&lp->mii_bus->mdio_lock); - for (i = 0; i < TX_BD_NUM; i++) { + for (i = 0; i < lp->tx_bd_num; i++) { cur_p = &lp->tx_bd_v[i]; if (cur_p->phys) dma_unmap_single(ndev->dev.parent, cur_p->phys, @@ -1377,7 +1415,7 @@ static void axienet_dma_err_handler(unsigned long data) cur_p->skb = NULL; } - for (i = 0; i < RX_BD_NUM; i++) { + for (i = 0; i < lp->rx_bd_num; i++) { cur_p = &lp->rx_bd_v[i]; cur_p->status = 0; cur_p->app0 = 0; @@ -1425,7 +1463,7 @@ static void axienet_dma_err_handler(unsigned long data) axienet_dma_out32(lp, XAXIDMA_RX_CR_OFFSET, cr | XAXIDMA_CR_RUNSTOP_MASK); axienet_dma_out32(lp, XAXIDMA_RX_TDESC_OFFSET, lp->rx_bd_p + - (sizeof(*lp->rx_bd_v) * (RX_BD_NUM - 1))); + (sizeof(*lp->rx_bd_v) * (lp->rx_bd_num - 1))); /* Write to the RS (Run-stop) bit in the Tx channel control register. * Tx channel is now ready to run. But only after we write to the @@ -1497,6 +1535,8 @@ static int axienet_probe(struct platform_device *pdev) lp->ndev = ndev; lp->dev = &pdev->dev; lp->options = XAE_OPTION_DEFAULTS; + lp->rx_bd_num = RX_BD_NUM_DEFAULT; + lp->tx_bd_num = TX_BD_NUM_DEFAULT; /* Map device registers */ ethres = platform_get_resource(pdev, IORESOURCE_MEM, 0); lp->regs_start = ethres->start; -- cgit From 867d03bc238f62fcd28f287b9da8af5e483baeab Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:14 -0600 Subject: net: axienet: Add DMA registers to ethtool register dump These registers are important for troubleshooting the state of the DMA cores. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 2c2d626c12a7..4df424c42224 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -50,7 +50,7 @@ #define DRIVER_DESCRIPTION "Xilinx Axi Ethernet driver" #define DRIVER_VERSION "1.00a" -#define AXIENET_REGS_N 32 +#define AXIENET_REGS_N 40 /* Match table for of_platform binding */ static const struct of_device_id axienet_of_match[] = { @@ -1179,6 +1179,14 @@ static void axienet_ethtools_get_regs(struct net_device *ndev, data[29] = axienet_ior(lp, XAE_FMI_OFFSET); data[30] = axienet_ior(lp, XAE_AF0_OFFSET); data[31] = axienet_ior(lp, XAE_AF1_OFFSET); + data[32] = axienet_dma_in32(lp, XAXIDMA_TX_CR_OFFSET); + data[33] = axienet_dma_in32(lp, XAXIDMA_TX_SR_OFFSET); + data[34] = axienet_dma_in32(lp, XAXIDMA_TX_CDESC_OFFSET); + data[35] = axienet_dma_in32(lp, XAXIDMA_TX_TDESC_OFFSET); + data[36] = axienet_dma_in32(lp, XAXIDMA_RX_CR_OFFSET); + data[37] = axienet_dma_in32(lp, XAXIDMA_RX_SR_OFFSET); + data[38] = axienet_dma_in32(lp, XAXIDMA_RX_CDESC_OFFSET); + data[39] = axienet_dma_in32(lp, XAXIDMA_RX_TDESC_OFFSET); } static void axienet_ethtools_get_ringparam(struct net_device *ndev, -- cgit From 9cbc1b6834dc6a6877271c829e9396dc2f26ae76 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:15 -0600 Subject: net: axienet: Support shared interrupts Specify IRQF_SHARED to support shared interrupts. If the interrupt handler is called and the device is not indicating an interrupt, just return IRQ_NONE rather than spewing error messages. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 4df424c42224..f733a7a1d07a 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -809,7 +809,7 @@ static void axienet_recv(struct net_device *ndev) * @irq: irq number * @_ndev: net_device pointer * - * Return: IRQ_HANDLED for all cases. + * Return: IRQ_HANDLED if device generated a TX interrupt, IRQ_NONE otherwise. * * This is the Axi DMA Tx done Isr. It invokes "axienet_start_xmit_done" * to complete the BD processing. @@ -828,7 +828,7 @@ static irqreturn_t axienet_tx_irq(int irq, void *_ndev) goto out; } if (!(status & XAXIDMA_IRQ_ALL_MASK)) - dev_err(&ndev->dev, "No interrupts asserted in Tx path\n"); + return IRQ_NONE; if (status & XAXIDMA_IRQ_ERROR_MASK) { dev_err(&ndev->dev, "DMA Tx error 0x%x\n", status); dev_err(&ndev->dev, "Current BD is at: 0x%x\n", @@ -858,7 +858,7 @@ out: * @irq: irq number * @_ndev: net_device pointer * - * Return: IRQ_HANDLED for all cases. + * Return: IRQ_HANDLED if device generated a RX interrupt, IRQ_NONE otherwise. * * This is the Axi DMA Rx Isr. It invokes "axienet_recv" to complete the BD * processing. @@ -877,7 +877,7 @@ static irqreturn_t axienet_rx_irq(int irq, void *_ndev) goto out; } if (!(status & XAXIDMA_IRQ_ALL_MASK)) - dev_err(&ndev->dev, "No interrupts asserted in Rx path\n"); + return IRQ_NONE; if (status & XAXIDMA_IRQ_ERROR_MASK) { dev_err(&ndev->dev, "DMA Rx error 0x%x\n", status); dev_err(&ndev->dev, "Current BD is at: 0x%x\n", @@ -953,11 +953,13 @@ static int axienet_open(struct net_device *ndev) (unsigned long) lp); /* Enable interrupts for Axi DMA Tx */ - ret = request_irq(lp->tx_irq, axienet_tx_irq, 0, ndev->name, ndev); + ret = request_irq(lp->tx_irq, axienet_tx_irq, IRQF_SHARED, + ndev->name, ndev); if (ret) goto err_tx_irq; /* Enable interrupts for Axi DMA Rx */ - ret = request_irq(lp->rx_irq, axienet_rx_irq, 0, ndev->name, ndev); + ret = request_irq(lp->rx_irq, axienet_rx_irq, IRQF_SHARED, + ndev->name, ndev); if (ret) goto err_rx_irq; -- cgit From 522856cefaf09d1a06ddc02535c7e1e81730c278 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:16 -0600 Subject: net: axienet: Add optional support for Ethernet core interrupt Previously this driver only handled interrupts from the DMA RX and TX blocks, not from the Ethernet core itself. Add optional support for the Ethernet core interrupt, which is used to detect rx_missed and framing errors signalled by the hardware. In order to use this interrupt, a third interrupt needs to be specified in the device tree. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 1 + drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 49 +++++++++++++++++++++++ 2 files changed, 50 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index 6b6d28ff014f..8e605a84b24d 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -435,6 +435,7 @@ struct axienet_local { int tx_irq; int rx_irq; + int eth_irq; phy_interface_t phy_mode; u32 options; /* Current options word */ diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index f733a7a1d07a..aa51a6eed15a 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -502,6 +502,8 @@ static void axienet_device_reset(struct net_device *ndev) axienet_status = axienet_ior(lp, XAE_IP_OFFSET); if (axienet_status & XAE_INT_RXRJECT_MASK) axienet_iow(lp, XAE_IS_OFFSET, XAE_INT_RXRJECT_MASK); + axienet_iow(lp, XAE_IE_OFFSET, lp->eth_irq > 0 ? + XAE_INT_RECV_ERROR_MASK : 0); axienet_iow(lp, XAE_FCC_OFFSET, XAE_FCC_FCRX_MASK); @@ -902,6 +904,35 @@ out: return IRQ_HANDLED; } +/** + * axienet_eth_irq - Ethernet core Isr. + * @irq: irq number + * @_ndev: net_device pointer + * + * Return: IRQ_HANDLED if device generated a core interrupt, IRQ_NONE otherwise. + * + * Handle miscellaneous conditions indicated by Ethernet core IRQ. + */ +static irqreturn_t axienet_eth_irq(int irq, void *_ndev) +{ + struct net_device *ndev = _ndev; + struct axienet_local *lp = netdev_priv(ndev); + unsigned int pending; + + pending = axienet_ior(lp, XAE_IP_OFFSET); + if (!pending) + return IRQ_NONE; + + if (pending & XAE_INT_RXFIFOOVR_MASK) + ndev->stats.rx_missed_errors++; + + if (pending & XAE_INT_RXRJECT_MASK) + ndev->stats.rx_frame_errors++; + + axienet_iow(lp, XAE_IS_OFFSET, pending); + return IRQ_HANDLED; +} + static void axienet_dma_err_handler(unsigned long data); /** @@ -962,9 +993,18 @@ static int axienet_open(struct net_device *ndev) ndev->name, ndev); if (ret) goto err_rx_irq; + /* Enable interrupts for Axi Ethernet core (if defined) */ + if (lp->eth_irq > 0) { + ret = request_irq(lp->eth_irq, axienet_eth_irq, IRQF_SHARED, + ndev->name, ndev); + if (ret) + goto err_eth_irq; + } return 0; +err_eth_irq: + free_irq(lp->rx_irq, ndev); err_rx_irq: free_irq(lp->tx_irq, ndev); err_tx_irq: @@ -1028,6 +1068,8 @@ static int axienet_stop(struct net_device *ndev) tasklet_kill(&lp->dma_err_tasklet); + if (lp->eth_irq > 0) + free_irq(lp->eth_irq, ndev); free_irq(lp->tx_irq, ndev); free_irq(lp->rx_irq, ndev); @@ -1491,6 +1533,8 @@ static void axienet_dma_err_handler(unsigned long data) axienet_status = axienet_ior(lp, XAE_IP_OFFSET); if (axienet_status & XAE_INT_RXRJECT_MASK) axienet_iow(lp, XAE_IS_OFFSET, XAE_INT_RXRJECT_MASK); + axienet_iow(lp, XAE_IE_OFFSET, lp->eth_irq > 0 ? + XAE_INT_RECV_ERROR_MASK : 0); axienet_iow(lp, XAE_FCC_OFFSET, XAE_FCC_FCRX_MASK); /* Sync default options with HW but leave receiver and @@ -1660,6 +1704,7 @@ static int axienet_probe(struct platform_device *pdev) } lp->rx_irq = irq_of_parse_and_map(np, 1); lp->tx_irq = irq_of_parse_and_map(np, 0); + lp->eth_irq = irq_of_parse_and_map(np, 2); of_node_put(np); if ((lp->rx_irq <= 0) || (lp->tx_irq <= 0)) { dev_err(&pdev->dev, "could not determine irqs\n"); @@ -1667,6 +1712,10 @@ static int axienet_probe(struct platform_device *pdev) goto free_netdev; } + /* Check for Ethernet core IRQ (optional) */ + if (lp->eth_irq <= 0) + dev_info(&pdev->dev, "Ethernet core IRQ not defined\n"); + /* Retrieve the MAC address */ mac_addr = of_get_mac_address(pdev->dev.of_node); if (IS_ERR(mac_addr)) { -- cgit From 7de44285c1f69ccfbe8be1d6a16fcd956681fee6 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:17 -0600 Subject: net: axienet: Fix race condition causing TX hang It is possible that the interrupt handler fires and frees up space in the TX ring in between checking for sufficient TX ring space and stopping the TX queue in axienet_start_xmit. If this happens, the queue wake from the interrupt handler will occur before the queue is stopped, causing a lost wakeup and the adapter's transmit hanging. To avoid this, after stopping the queue, check again whether there is sufficient space in the TX ring. If so, wake up the queue again. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index aa51a6eed15a..834fafdff219 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -622,6 +622,10 @@ static void axienet_start_xmit_done(struct net_device *ndev) ndev->stats.tx_packets += packets; ndev->stats.tx_bytes += size; + + /* Matches barrier in axienet_start_xmit */ + smp_mb(); + netif_wake_queue(ndev); } @@ -677,9 +681,19 @@ axienet_start_xmit(struct sk_buff *skb, struct net_device *ndev) cur_p = &lp->tx_bd_v[lp->tx_bd_tail]; if (axienet_check_tx_bd_space(lp, num_frag)) { - if (!netif_queue_stopped(ndev)) - netif_stop_queue(ndev); - return NETDEV_TX_BUSY; + if (netif_queue_stopped(ndev)) + return NETDEV_TX_BUSY; + + netif_stop_queue(ndev); + + /* Matches barrier in axienet_start_xmit_done */ + smp_mb(); + + /* Space might have just been freed - check again */ + if (axienet_check_tx_bd_space(lp, num_frag)) + return NETDEV_TX_BUSY; + + netif_wake_queue(ndev); } if (skb->ip_summed == CHECKSUM_PARTIAL) { -- cgit From d05a9ed5c3a7739b7ece9ea81617c6fc74de10f0 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:18 -0600 Subject: net: axienet: Make missing MAC address non-fatal Failing initialization on a missing MAC address property is excessive. We can just fall back to using a random MAC instead, which at least leaves the interface in a functioning state. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 834fafdff219..6e75c4320f80 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -1733,8 +1733,9 @@ static int axienet_probe(struct platform_device *pdev) /* Retrieve the MAC address */ mac_addr = of_get_mac_address(pdev->dev.of_node); if (IS_ERR(mac_addr)) { - dev_err(&pdev->dev, "could not find MAC address\n"); - goto free_netdev; + dev_warn(&pdev->dev, "could not find MAC address property: %ld\n", + PTR_ERR(mac_addr)); + mac_addr = NULL; } axienet_set_mac_address(ndev, mac_addr); -- cgit From 70c50265c46b7f44823313b87e7e3a3808489c5d Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:19 -0600 Subject: net: axienet: stop interface during shutdown On some platforms, such as iMX6 with PCIe devices, crashes or hangs can occur if the axienet device continues to perform DMA transfers after parent devices/busses have been shut down. Shut down the axienet interface during its shutdown callback in order to avoid this. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 6e75c4320f80..d138db8d6e7a 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -1797,9 +1797,23 @@ static int axienet_remove(struct platform_device *pdev) return 0; } +static void axienet_shutdown(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + + rtnl_lock(); + netif_device_detach(ndev); + + if (netif_running(ndev)) + dev_close(ndev); + + rtnl_unlock(); +} + static struct platform_driver axienet_driver = { .probe = axienet_probe, .remove = axienet_remove, + .shutdown = axienet_shutdown, .driver = { .name = "xilinx_axienet", .of_match_table = axienet_of_match, -- cgit From 3b09a3fb35079106fd490241bdc8e0c31434be00 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:21 -0600 Subject: net: axienet: Fix MDIO bus parent node detection This driver was previously using the parent node of the specified PHY node as the device node to register the MDIO bus on. Andrew Lunn pointed out this is wrong as the PHY node is potentially not even underneath the MDIO bus for the current device instance. Find the MDIO node explicitly by looking it up by name under the controller's device node instead. This could potentially break existing device trees if they don't use "mdio" as the name for the MDIO bus, but I did not find any with various searches and Xilinx's examples all use mdio as the name so it seems like this should be relatively safe. Signed-off-by: Robert Hancock Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index 7106810793d5..435ed308d990 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -228,7 +228,6 @@ int axienet_mdio_setup(struct axienet_local *lp) if (!bus) return -ENOMEM; - mdio_node = of_get_parent(lp->phy_node); snprintf(bus->id, MII_BUS_ID_SIZE, "axienet-%.8llx", (unsigned long long)lp->regs_start); @@ -239,7 +238,9 @@ int axienet_mdio_setup(struct axienet_local *lp) bus->parent = lp->dev; lp->mii_bus = bus; + mdio_node = of_get_child_by_name(lp->dev->of_node, "mdio"); ret = of_mdiobus_register(bus, mdio_node); + of_node_put(mdio_node); if (ret) { mdiobus_free(bus); lp->mii_bus = NULL; -- cgit From 28ef9ebdb64c6f45079d8be45ab47bd0c62d078c Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:23 -0600 Subject: net: axienet: make use of axistream-connected attribute optional Currently the axienet driver requires the use of a second devicetree node, referenced by an axistream-connected attribute on the Ethernet device node, which contains the resources for the AXI DMA block used by the device. This setup is problematic for a use case we have where the Ethernet and DMA cores are behind a PCIe to AXI bridge and the memory resources for the nodes are injected into the platform devices using the multifunction device subsystem - it's not easily possible for the driver to obtain the platform-level resources from the linked device. In order to simplify that usage model, and simplify the overall use of this driver in general, allow for all of the resources to be kept on one node where the resources are retrieved using platform device APIs rather than device-tree-specific ones. The previous usage setup is still supported if the axistream-connected attribute is specified. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 43 +++++++++++++++-------- 1 file changed, 28 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index d138db8d6e7a..898eabffc515 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -1580,7 +1580,7 @@ static int axienet_probe(struct platform_device *pdev) struct axienet_local *lp; struct net_device *ndev; const void *mac_addr; - struct resource *ethres, dmares; + struct resource *ethres; u32 value; ndev = alloc_etherdev(sizeof(*lp)); @@ -1698,28 +1698,41 @@ static int axienet_probe(struct platform_device *pdev) /* Find the DMA node, map the DMA registers, and decode the DMA IRQs */ np = of_parse_phandle(pdev->dev.of_node, "axistream-connected", 0); - if (!np) { - dev_err(&pdev->dev, "could not find DMA node\n"); - ret = -ENODEV; - goto free_netdev; - } - ret = of_address_to_resource(np, 0, &dmares); - if (ret) { - dev_err(&pdev->dev, "unable to get DMA resource\n"); + if (np) { + struct resource dmares; + + ret = of_address_to_resource(np, 0, &dmares); + if (ret) { + dev_err(&pdev->dev, + "unable to get DMA resource\n"); + of_node_put(np); + goto free_netdev; + } + lp->dma_regs = devm_ioremap_resource(&pdev->dev, + &dmares); + lp->rx_irq = irq_of_parse_and_map(np, 1); + lp->tx_irq = irq_of_parse_and_map(np, 0); of_node_put(np); - goto free_netdev; + lp->eth_irq = platform_get_irq(pdev, 0); + } else { + /* Check for these resources directly on the Ethernet node. */ + struct resource *res = platform_get_resource(pdev, + IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "unable to get DMA memory resource\n"); + goto free_netdev; + } + lp->dma_regs = devm_ioremap_resource(&pdev->dev, res); + lp->rx_irq = platform_get_irq(pdev, 1); + lp->tx_irq = platform_get_irq(pdev, 0); + lp->eth_irq = platform_get_irq(pdev, 2); } - lp->dma_regs = devm_ioremap_resource(&pdev->dev, &dmares); if (IS_ERR(lp->dma_regs)) { dev_err(&pdev->dev, "could not map DMA regs\n"); ret = PTR_ERR(lp->dma_regs); of_node_put(np); goto free_netdev; } - lp->rx_irq = irq_of_parse_and_map(np, 1); - lp->tx_irq = irq_of_parse_and_map(np, 0); - lp->eth_irq = irq_of_parse_and_map(np, 2); - of_node_put(np); if ((lp->rx_irq <= 0) || (lp->tx_irq <= 0)) { dev_err(&pdev->dev, "could not determine irqs\n"); ret = -ENOMEM; -- cgit From f5203a3d9b25fa28a40bfc27c05a4020c6430f06 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:24 -0600 Subject: net: axienet: convert to phylink API Convert this driver to use the phylink API rather than the legacy PHY API. This allows for better support for SFP modules connected using a 1000BaseX or SGMII interface. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/Kconfig | 2 +- drivers/net/ethernet/xilinx/xilinx_axienet.h | 5 +- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 286 ++++++++++++++-------- 3 files changed, 192 insertions(+), 101 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig index 5f50764ea255..8d994cebb6b0 100644 --- a/drivers/net/ethernet/xilinx/Kconfig +++ b/drivers/net/ethernet/xilinx/Kconfig @@ -27,7 +27,7 @@ config XILINX_EMACLITE config XILINX_AXI_EMAC tristate "Xilinx 10/100/1000 AXI Ethernet support" depends on MICROBLAZE || X86 || ARM || COMPILE_TEST - select PHYLIB + select PHYLINK ---help--- This driver supports the 10/100/1000 Ethernet from Xilinx for the AXI bus interface used in Xilinx Virtex FPGAs. diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index 8e605a84b24d..2dacfc85b3ba 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -13,6 +13,7 @@ #include #include #include +#include /* Packet size info */ #define XAE_HDR_SIZE 14 /* Size of Ethernet header */ @@ -420,6 +421,9 @@ struct axienet_local { /* Connection to PHY device */ struct device_node *phy_node; + struct phylink *phylink; + struct phylink_config phylink_config; + /* Clock for AXI bus */ struct clk *clk; @@ -439,7 +443,6 @@ struct axienet_local { phy_interface_t phy_mode; u32 options; /* Current options word */ - u32 last_link; u32 features; /* Buffer descriptors */ diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 898eabffc515..da420c881662 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -7,6 +7,7 @@ * Copyright (c) 2008-2009 Secret Lab Technologies Ltd. * Copyright (c) 2010 - 2011 Michal Simek * Copyright (c) 2010 - 2011 PetaLogix + * Copyright (c) 2019 SED Systems, a division of Calian Ltd. * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. * * This is a driver for the Xilinx Axi Ethernet which is used in the Virtex6 @@ -519,63 +520,6 @@ static void axienet_device_reset(struct net_device *ndev) netif_trans_update(ndev); } -/** - * axienet_adjust_link - Adjust the PHY link speed/duplex. - * @ndev: Pointer to the net_device structure - * - * This function is called to change the speed and duplex setting after - * auto negotiation is done by the PHY. This is the function that gets - * registered with the PHY interface through the "of_phy_connect" call. - */ -static void axienet_adjust_link(struct net_device *ndev) -{ - u32 emmc_reg; - u32 link_state; - u32 setspeed = 1; - struct axienet_local *lp = netdev_priv(ndev); - struct phy_device *phy = ndev->phydev; - - link_state = phy->speed | (phy->duplex << 1) | phy->link; - if (lp->last_link != link_state) { - if ((phy->speed == SPEED_10) || (phy->speed == SPEED_100)) { - if (lp->phy_mode == PHY_INTERFACE_MODE_1000BASEX) - setspeed = 0; - } else { - if ((phy->speed == SPEED_1000) && - (lp->phy_mode == PHY_INTERFACE_MODE_MII)) - setspeed = 0; - } - - if (setspeed == 1) { - emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET); - emmc_reg &= ~XAE_EMMC_LINKSPEED_MASK; - - switch (phy->speed) { - case SPEED_1000: - emmc_reg |= XAE_EMMC_LINKSPD_1000; - break; - case SPEED_100: - emmc_reg |= XAE_EMMC_LINKSPD_100; - break; - case SPEED_10: - emmc_reg |= XAE_EMMC_LINKSPD_10; - break; - default: - dev_err(&ndev->dev, "Speed other than 10, 100 " - "or 1Gbps is not supported\n"); - break; - } - - axienet_iow(lp, XAE_EMMC_OFFSET, emmc_reg); - lp->last_link = link_state; - phy_print_status(phy); - } else { - netdev_err(ndev, - "Error setting Axi Ethernet mac speed\n"); - } - } -} - /** * axienet_start_xmit_done - Invoked once a transmit is completed by the * Axi DMA Tx channel. @@ -956,7 +900,8 @@ static void axienet_dma_err_handler(unsigned long data); * Return: 0, on success. * non-zero error value on failure * - * This is the driver open routine. It calls phy_start to start the PHY device. + * This is the driver open routine. It calls phylink_start to start the + * PHY device. * It also allocates interrupt service routines, enables the interrupt lines * and ISR handling. Axi Ethernet core is reset through Axi DMA core. Buffer * descriptors are initialized. @@ -965,7 +910,6 @@ static int axienet_open(struct net_device *ndev) { int ret; struct axienet_local *lp = netdev_priv(ndev); - struct phy_device *phydev = NULL; dev_dbg(&ndev->dev, "axienet_open()\n"); @@ -983,16 +927,14 @@ static int axienet_open(struct net_device *ndev) if (ret < 0) return ret; - if (lp->phy_node) { - phydev = of_phy_connect(lp->ndev, lp->phy_node, - axienet_adjust_link, 0, lp->phy_mode); - - if (!phydev) - dev_err(lp->dev, "of_phy_connect() failed\n"); - else - phy_start(phydev); + ret = phylink_of_phy_connect(lp->phylink, lp->dev->of_node, 0); + if (ret) { + dev_err(lp->dev, "phylink_of_phy_connect() failed: %d\n", ret); + return ret; } + phylink_start(lp->phylink); + /* Enable tasklets for Axi DMA error handling */ tasklet_init(&lp->dma_err_tasklet, axienet_dma_err_handler, (unsigned long) lp); @@ -1022,8 +964,8 @@ err_eth_irq: err_rx_irq: free_irq(lp->tx_irq, ndev); err_tx_irq: - if (phydev) - phy_disconnect(phydev); + phylink_stop(lp->phylink); + phylink_disconnect_phy(lp->phylink); tasklet_kill(&lp->dma_err_tasklet); dev_err(lp->dev, "request_irq() failed\n"); return ret; @@ -1035,7 +977,7 @@ err_tx_irq: * * Return: 0, on success. * - * This is the driver stop routine. It calls phy_disconnect to stop the PHY + * This is the driver stop routine. It calls phylink_disconnect to stop the PHY * device. It also removes the interrupt handlers and disables the interrupts. * The Axi DMA Tx/Rx BDs are released. */ @@ -1047,6 +989,9 @@ static int axienet_stop(struct net_device *ndev) dev_dbg(&ndev->dev, "axienet_close()\n"); + phylink_stop(lp->phylink); + phylink_disconnect_phy(lp->phylink); + axienet_setoptions(ndev, lp->options & ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); @@ -1087,9 +1032,6 @@ static int axienet_stop(struct net_device *ndev) free_irq(lp->tx_irq, ndev); free_irq(lp->rx_irq, ndev); - if (ndev->phydev) - phy_disconnect(ndev->phydev); - axienet_dma_bd_release(ndev); return 0; } @@ -1294,12 +1236,9 @@ static void axienet_ethtools_get_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *epauseparm) { - u32 regval; struct axienet_local *lp = netdev_priv(ndev); - epauseparm->autoneg = 0; - regval = axienet_ior(lp, XAE_FCC_OFFSET); - epauseparm->tx_pause = regval & XAE_FCC_FCTX_MASK; - epauseparm->rx_pause = regval & XAE_FCC_FCRX_MASK; + + phylink_ethtool_get_pauseparam(lp->phylink, epauseparm); } /** @@ -1318,27 +1257,9 @@ static int axienet_ethtools_set_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *epauseparm) { - u32 regval = 0; struct axienet_local *lp = netdev_priv(ndev); - if (netif_running(ndev)) { - netdev_err(ndev, - "Please stop netif before applying configuration\n"); - return -EFAULT; - } - - regval = axienet_ior(lp, XAE_FCC_OFFSET); - if (epauseparm->tx_pause) - regval |= XAE_FCC_FCTX_MASK; - else - regval &= ~XAE_FCC_FCTX_MASK; - if (epauseparm->rx_pause) - regval |= XAE_FCC_FCRX_MASK; - else - regval &= ~XAE_FCC_FCRX_MASK; - axienet_iow(lp, XAE_FCC_OFFSET, regval); - - return 0; + return phylink_ethtool_set_pauseparam(lp->phylink, epauseparm); } /** @@ -1417,6 +1338,24 @@ static int axienet_ethtools_set_coalesce(struct net_device *ndev, return 0; } +static int +axienet_ethtools_get_link_ksettings(struct net_device *ndev, + struct ethtool_link_ksettings *cmd) +{ + struct axienet_local *lp = netdev_priv(ndev); + + return phylink_ethtool_ksettings_get(lp->phylink, cmd); +} + +static int +axienet_ethtools_set_link_ksettings(struct net_device *ndev, + const struct ethtool_link_ksettings *cmd) +{ + struct axienet_local *lp = netdev_priv(ndev); + + return phylink_ethtool_ksettings_set(lp->phylink, cmd); +} + static const struct ethtool_ops axienet_ethtool_ops = { .get_drvinfo = axienet_ethtools_get_drvinfo, .get_regs_len = axienet_ethtools_get_regs_len, @@ -1428,8 +1367,141 @@ static const struct ethtool_ops axienet_ethtool_ops = { .set_pauseparam = axienet_ethtools_set_pauseparam, .get_coalesce = axienet_ethtools_get_coalesce, .set_coalesce = axienet_ethtools_set_coalesce, - .get_link_ksettings = phy_ethtool_get_link_ksettings, - .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_link_ksettings = axienet_ethtools_get_link_ksettings, + .set_link_ksettings = axienet_ethtools_set_link_ksettings, +}; + +static void axienet_validate(struct phylink_config *config, + unsigned long *supported, + struct phylink_link_state *state) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct axienet_local *lp = netdev_priv(ndev); + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + + /* Only support the mode we are configured for */ + if (state->interface != PHY_INTERFACE_MODE_NA && + state->interface != lp->phy_mode) { + netdev_warn(ndev, "Cannot use PHY mode %s, supported: %s\n", + phy_modes(state->interface), + phy_modes(lp->phy_mode)); + bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + return; + } + + phylink_set(mask, Autoneg); + phylink_set_port_modes(mask); + + phylink_set(mask, Asym_Pause); + phylink_set(mask, Pause); + phylink_set(mask, 1000baseX_Full); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Full); + phylink_set(mask, 1000baseT_Full); + + bitmap_and(supported, supported, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); + bitmap_and(state->advertising, state->advertising, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static int axienet_mac_link_state(struct phylink_config *config, + struct phylink_link_state *state) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct axienet_local *lp = netdev_priv(ndev); + u32 emmc_reg, fcc_reg; + + state->interface = lp->phy_mode; + + emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET); + if (emmc_reg & XAE_EMMC_LINKSPD_1000) + state->speed = SPEED_1000; + else if (emmc_reg & XAE_EMMC_LINKSPD_100) + state->speed = SPEED_100; + else + state->speed = SPEED_10; + + state->pause = 0; + fcc_reg = axienet_ior(lp, XAE_FCC_OFFSET); + if (fcc_reg & XAE_FCC_FCTX_MASK) + state->pause |= MLO_PAUSE_TX; + if (fcc_reg & XAE_FCC_FCRX_MASK) + state->pause |= MLO_PAUSE_RX; + + state->an_complete = 0; + state->duplex = 1; + + return 1; +} + +static void axienet_mac_an_restart(struct phylink_config *config) +{ + /* Unsupported, do nothing */ +} + +static void axienet_mac_config(struct phylink_config *config, unsigned int mode, + const struct phylink_link_state *state) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct axienet_local *lp = netdev_priv(ndev); + u32 emmc_reg, fcc_reg; + + emmc_reg = axienet_ior(lp, XAE_EMMC_OFFSET); + emmc_reg &= ~XAE_EMMC_LINKSPEED_MASK; + + switch (state->speed) { + case SPEED_1000: + emmc_reg |= XAE_EMMC_LINKSPD_1000; + break; + case SPEED_100: + emmc_reg |= XAE_EMMC_LINKSPD_100; + break; + case SPEED_10: + emmc_reg |= XAE_EMMC_LINKSPD_10; + break; + default: + dev_err(&ndev->dev, + "Speed other than 10, 100 or 1Gbps is not supported\n"); + break; + } + + axienet_iow(lp, XAE_EMMC_OFFSET, emmc_reg); + + fcc_reg = axienet_ior(lp, XAE_FCC_OFFSET); + if (state->pause & MLO_PAUSE_TX) + fcc_reg |= XAE_FCC_FCTX_MASK; + else + fcc_reg &= ~XAE_FCC_FCTX_MASK; + if (state->pause & MLO_PAUSE_RX) + fcc_reg |= XAE_FCC_FCRX_MASK; + else + fcc_reg &= ~XAE_FCC_FCRX_MASK; + axienet_iow(lp, XAE_FCC_OFFSET, fcc_reg); +} + +static void axienet_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ + /* nothing meaningful to do */ +} + +static void axienet_mac_link_up(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface, + struct phy_device *phy) +{ + /* nothing meaningful to do */ +} + +static const struct phylink_mac_ops axienet_phylink_ops = { + .validate = axienet_validate, + .mac_link_state = axienet_mac_link_state, + .mac_an_restart = axienet_mac_an_restart, + .mac_config = axienet_mac_config, + .mac_link_down = axienet_mac_link_down, + .mac_link_up = axienet_mac_link_up, }; /** @@ -1777,6 +1849,18 @@ static int axienet_probe(struct platform_device *pdev) "error registering MDIO bus: %d\n", ret); } + lp->phylink_config.dev = &ndev->dev; + lp->phylink_config.type = PHYLINK_NETDEV; + + lp->phylink = phylink_create(&lp->phylink_config, pdev->dev.fwnode, + lp->phy_mode, + &axienet_phylink_ops); + if (IS_ERR(lp->phylink)) { + ret = PTR_ERR(lp->phylink); + dev_err(&pdev->dev, "phylink_create error (%i)\n", ret); + goto free_netdev; + } + ret = register_netdev(lp->ndev); if (ret) { dev_err(lp->dev, "register_netdev() error (%i)\n", ret); @@ -1797,6 +1881,10 @@ static int axienet_remove(struct platform_device *pdev) struct axienet_local *lp = netdev_priv(ndev); unregister_netdev(ndev); + + if (lp->phylink) + phylink_destroy(lp->phylink); + axienet_mdio_teardown(lp); if (lp->clk) -- cgit