* [PATCH v7 0/3] add hisilicon hip04 ethernet driver
@ 2014-04-05 4:35 Zhangfei Gao
2014-04-05 4:35 ` [PATCH 1/3] Documentation: add Device tree bindings for Hisilicon hip04 ethernet Zhangfei Gao
` (2 more replies)
0 siblings, 3 replies; 64+ messages in thread
From: Zhangfei Gao @ 2014-04-05 4:35 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
v7:
Remove select NET_CORE in 0002
v6:
Suggest by Russell: Use netdev_sent_queue & netdev_completed_queue to solve latency issue
Also shorten the period of timer, which is used to wakeup the queue since no
tx completed interrupt.
v5:
no big change, fix typo
v4:
Modify accoringly to the suggetion from Arnd, Florian, Eric, David
Use of_parse_phandle_with_fixed_args & syscon_node_to_regmap get ppe info
Add skb_orphan() and tx_timer for reclaim since no tx_finished interrupt
Update timeout, and move of_phy_connect to probe to reuse open/stop
v3:
Suggest from Arnd, use syscon & regmap_write/read to replace static void __iomem *ppebase.
Modify hisilicon-hip04-net.txt accrordingly to suggestion from Florian and Sergei.
v2:
Got many suggestions from Russell, Arnd, Florian, Mark and Sergei
Remove memcpy, use dma_map/unmap_single, use dma_alloc_coherent rather than dma_pool, etc.
Refer property in ethernet.txt, change ppe description, etc.
Zhangfei Gao (3):
Documentation: add Device tree bindings for Hisilicon hip04 ethernet
net: hisilicon: new hip04 MDIO driver
net: hisilicon: new hip04 ethernet driver
.../bindings/net/hisilicon-hip04-net.txt | 88 +++
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 31 +
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_eth.c | 777 ++++++++++++++++++++
drivers/net/ethernet/hisilicon/hip04_mdio.c | 186 +++++
7 files changed, 1089 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
--
1.7.9.5
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 1/3] Documentation: add Device tree bindings for Hisilicon hip04 ethernet
2014-04-05 4:35 [PATCH v7 0/3] add hisilicon hip04 ethernet driver Zhangfei Gao
@ 2014-04-05 4:35 ` Zhangfei Gao
2014-04-05 4:35 ` [PATCH 2/3] net: hisilicon: new hip04 MDIO driver Zhangfei Gao
2014-04-05 4:35 ` [PATCH 3/3] net: hisilicon: new hip04 ethernet driver Zhangfei Gao
2 siblings, 0 replies; 64+ messages in thread
From: Zhangfei Gao @ 2014-04-05 4:35 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
This patch adds the Device Tree bindings for the Hisilicon hip04
Ethernet controller, including 100M / 1000M controller.
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
---
.../bindings/net/hisilicon-hip04-net.txt | 88 ++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
diff --git a/Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt b/Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
new file mode 100644
index 0000000..988fc69
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
@@ -0,0 +1,88 @@
+Hisilicon hip04 Ethernet Controller
+
+* Ethernet controller node
+
+Required properties:
+- compatible: should be "hisilicon,hip04-mac".
+- reg: address and length of the register set for the device.
+- interrupts: interrupt for the device.
+- port-handle: <phandle port channel>
+ phandle, specifies a reference to the syscon ppe node
+ port, port number connected to the controller
+ channel, recv channel start from channel * number (RX_DESC_NUM)
+- phy-mode: see ethernet.txt [1].
+
+Optional properties:
+- phy-handle: see ethernet.txt [1].
+
+[1] Documentation/devicetree/bindings/net/ethernet.txt
+
+
+* Ethernet ppe node:
+Control rx & tx fifos of all ethernet controllers.
+Have 2048 recv channels shared by all ethernet controllers, only if no overlap.
+Each controller's recv channel start from channel * number (RX_DESC_NUM).
+
+Required properties:
+- compatible: "hisilicon,hip04-ppe", "syscon".
+- reg: address and length of the register set for the device.
+
+
+* MDIO bus node:
+
+Required properties:
+
+- compatible: should be "hisilicon,hip04-mdio".
+- Inherits from MDIO bus node binding [2]
+[2] Documentation/devicetree/bindings/net/phy.txt
+
+Example:
+ mdio {
+ compatible = "hisilicon,hip04-mdio";
+ reg = <0x28f1000 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ phy0: ethernet-phy@0 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0>;
+ marvell,reg-init = <18 0x14 0 0x8001>;
+ };
+
+ phy1: ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <1>;
+ marvell,reg-init = <18 0x14 0 0x8001>;
+ };
+ };
+
+ ppe: ppe@28c0000 {
+ compatible = "hisilicon,hip04-ppe", "syscon";
+ reg = <0x28c0000 0x10000>;
+ };
+
+ fe: ethernet@28b0000 {
+ compatible = "hisilicon,hip04-mac";
+ reg = <0x28b0000 0x10000>;
+ interrupts = <0 413 4>;
+ phy-mode = "mii";
+ port-handle = <&ppe 31 0>;
+ };
+
+ ge0: ethernet@2800000 {
+ compatible = "hisilicon,hip04-mac";
+ reg = <0x2800000 0x10000>;
+ interrupts = <0 402 4>;
+ phy-mode = "sgmii";
+ port-handle = <&ppe 0 1>;
+ phy-handle = <&phy0>;
+ };
+
+ ge8: ethernet@2880000 {
+ compatible = "hisilicon,hip04-mac";
+ reg = <0x2880000 0x10000>;
+ interrupts = <0 410 4>;
+ phy-mode = "sgmii";
+ port-handle = <&ppe 8 2>;
+ phy-handle = <&phy1>;
+ };
--
1.7.9.5
^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH 2/3] net: hisilicon: new hip04 MDIO driver
2014-04-05 4:35 [PATCH v7 0/3] add hisilicon hip04 ethernet driver Zhangfei Gao
2014-04-05 4:35 ` [PATCH 1/3] Documentation: add Device tree bindings for Hisilicon hip04 ethernet Zhangfei Gao
@ 2014-04-05 4:35 ` Zhangfei Gao
2014-04-07 18:47 ` David Miller
2014-04-05 4:35 ` [PATCH 3/3] net: hisilicon: new hip04 ethernet driver Zhangfei Gao
2 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-04-05 4:35 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
Hisilicon hip04 platform mdio driver
Reuse Marvell phy drivers/net/phy/marvell.c
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
---
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 31 +++++
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_mdio.c | 186 +++++++++++++++++++++++++++
5 files changed, 224 insertions(+)
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 506b024..1c8dc3d 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -54,6 +54,7 @@ source "drivers/net/ethernet/neterion/Kconfig"
source "drivers/net/ethernet/faraday/Kconfig"
source "drivers/net/ethernet/freescale/Kconfig"
source "drivers/net/ethernet/fujitsu/Kconfig"
+source "drivers/net/ethernet/hisilicon/Kconfig"
source "drivers/net/ethernet/hp/Kconfig"
source "drivers/net/ethernet/ibm/Kconfig"
source "drivers/net/ethernet/intel/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index c0b8789..da1a435 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_NET_VENDOR_EXAR) += neterion/
obj-$(CONFIG_NET_VENDOR_FARADAY) += faraday/
obj-$(CONFIG_NET_VENDOR_FREESCALE) += freescale/
obj-$(CONFIG_NET_VENDOR_FUJITSU) += fujitsu/
+obj-$(CONFIG_NET_VENDOR_HISILICON) += hisilicon/
obj-$(CONFIG_NET_VENDOR_HP) += hp/
obj-$(CONFIG_NET_VENDOR_IBM) += ibm/
obj-$(CONFIG_NET_VENDOR_INTEL) += intel/
diff --git a/drivers/net/ethernet/hisilicon/Kconfig b/drivers/net/ethernet/hisilicon/Kconfig
new file mode 100644
index 0000000..628537f
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/Kconfig
@@ -0,0 +1,31 @@
+#
+# HISILICON device configuration
+#
+
+config NET_VENDOR_HISILICON
+ bool "Hisilicon devices"
+ default y
+ depends on ARM
+ ---help---
+ If you have a network (Ethernet) card belonging to this class, say Y
+ and read the Ethernet-HOWTO, available from
+ <http://www.tldp.org/docs.html#howto>.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about Hisilicon devices. If you say Y, you will be asked
+ for your specific card in the following questions.
+
+if NET_VENDOR_HISILICON
+
+config HIP04_ETH
+ tristate "HISILICON P04 Ethernet support"
+ select PHYLIB
+ select MARVELL_PHY
+ select MFD_SYSCON
+ ---help---
+ If you wish to compile a kernel for a hardware with hisilicon p04 SoC and
+ want to use the internal ethernet then you should answer Y to this.
+
+
+endif # NET_VENDOR_HISILICON
diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile
new file mode 100644
index 0000000..1d6eb6e
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the HISILICON network device drivers.
+#
+
+obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o
diff --git a/drivers/net/ethernet/hisilicon/hip04_mdio.c b/drivers/net/ethernet/hisilicon/hip04_mdio.c
new file mode 100644
index 0000000..e4f67ca
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hip04_mdio.c
@@ -0,0 +1,186 @@
+
+/* Copyright (c) 2014 Linaro Ltd.
+ * Copyright (c) 2014 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of_mdio.h>
+#include <linux/delay.h>
+
+#define MDIO_CMD_REG 0x0
+#define MDIO_ADDR_REG 0x4
+#define MDIO_WDATA_REG 0x8
+#define MDIO_RDATA_REG 0xc
+#define MDIO_STA_REG 0x10
+
+#define MDIO_START BIT(14)
+#define MDIO_R_VALID BIT(1)
+#define MDIO_READ (BIT(12) | BIT(11) | MDIO_START)
+#define MDIO_WRITE (BIT(12) | BIT(10) | MDIO_START)
+
+struct hip04_mdio_priv {
+ void __iomem *base;
+};
+
+#define WAIT_TIMEOUT 10
+static int hip04_mdio_wait_ready(struct mii_bus *bus)
+{
+ struct hip04_mdio_priv *priv = bus->priv;
+ int i;
+
+ for (i = 0; readl_relaxed(priv->base + MDIO_CMD_REG) & MDIO_START; i++) {
+ if (i == WAIT_TIMEOUT)
+ return -ETIMEDOUT;
+ msleep(20);
+ }
+
+ return 0;
+}
+
+static int hip04_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+ struct hip04_mdio_priv *priv = bus->priv;
+ u32 val;
+ int ret;
+
+ ret = hip04_mdio_wait_ready(bus);
+ if (ret < 0)
+ goto out;
+
+ val = regnum | (mii_id << 5) | MDIO_READ;
+ writel_relaxed(val, priv->base + MDIO_CMD_REG);
+
+ ret = hip04_mdio_wait_ready(bus);
+ if (ret < 0)
+ goto out;
+
+ val = readl_relaxed(priv->base + MDIO_STA_REG);
+ if (val & MDIO_R_VALID) {
+ dev_err(bus->parent, "SMI bus read not valid\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ val = readl_relaxed(priv->base + MDIO_RDATA_REG);
+ ret = val & 0xFFFF;
+out:
+ return ret;
+}
+
+static int hip04_mdio_write(struct mii_bus *bus, int mii_id,
+ int regnum, u16 value)
+{
+ struct hip04_mdio_priv *priv = bus->priv;
+ u32 val;
+ int ret;
+
+ ret = hip04_mdio_wait_ready(bus);
+ if (ret < 0)
+ goto out;
+
+ writel_relaxed(value, priv->base + MDIO_WDATA_REG);
+ val = regnum | (mii_id << 5) | MDIO_WRITE;
+ writel_relaxed(val, priv->base + MDIO_CMD_REG);
+out:
+ return ret;
+}
+
+
+static int hip04_mdio_reset(struct mii_bus *bus)
+{
+ int temp, err, i;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ hip04_mdio_write(bus, i, 22, 0);
+ temp = hip04_mdio_read(bus, i, MII_BMCR);
+ temp |= BMCR_RESET;
+ err = hip04_mdio_write(bus, i, MII_BMCR, temp);
+ if (err < 0)
+ return err;
+ }
+
+ mdelay(500);
+ return 0;
+}
+
+static int hip04_mdio_probe(struct platform_device *pdev)
+{
+ struct resource *r;
+ struct mii_bus *bus;
+ struct hip04_mdio_priv *priv;
+ int ret;
+
+ bus = mdiobus_alloc_size(sizeof(struct hip04_mdio_priv));
+ if (!bus) {
+ dev_err(&pdev->dev, "Cannot allocate MDIO bus\n");
+ return -ENOMEM;
+ }
+
+ bus->name = "hip04_mdio_bus";
+ bus->read = hip04_mdio_read;
+ bus->write = hip04_mdio_write;
+ bus->reset = hip04_mdio_reset;
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
+ bus->parent = &pdev->dev;
+ priv = bus->priv;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto out_mdio;
+ }
+
+ ret = of_mdiobus_register(bus, pdev->dev.of_node);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
+ goto out_mdio;
+ }
+
+ platform_set_drvdata(pdev, bus);
+
+ return 0;
+
+out_mdio:
+ mdiobus_free(bus);
+ return ret;
+}
+
+static int hip04_mdio_remove(struct platform_device *pdev)
+{
+ struct mii_bus *bus = platform_get_drvdata(pdev);
+
+ mdiobus_unregister(bus);
+ mdiobus_free(bus);
+
+ return 0;
+}
+
+static const struct of_device_id hip04_mdio_match[] = {
+ { .compatible = "hisilicon,hip04-mdio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hip04_mdio_match);
+
+static struct platform_driver hip04_mdio_driver = {
+ .probe = hip04_mdio_probe,
+ .remove = hip04_mdio_remove,
+ .driver = {
+ .name = "hip04-mdio",
+ .owner = THIS_MODULE,
+ .of_match_table = hip04_mdio_match,
+ },
+};
+
+module_platform_driver(hip04_mdio_driver);
+
+MODULE_DESCRIPTION("HISILICON P04 MDIO interface driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:hip04-mdio");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 2/3] net: hisilicon: new hip04 MDIO driver
2014-04-05 4:35 ` [PATCH 2/3] net: hisilicon: new hip04 MDIO driver Zhangfei Gao
@ 2014-04-07 18:47 ` David Miller
0 siblings, 0 replies; 64+ messages in thread
From: David Miller @ 2014-04-07 18:47 UTC (permalink / raw)
To: zhangfei.gao
Cc: linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet, linux-arm-kernel, netdev, devicetree
From: Zhangfei Gao <zhangfei.gao@linaro.org>
Date: Sat, 5 Apr 2014 12:35:05 +0800
> +}
> +
> +
> +static int hip04_mdio_reset(struct mii_bus *bus)
Please one empty line between functions, not two.
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-04-05 4:35 [PATCH v7 0/3] add hisilicon hip04 ethernet driver Zhangfei Gao
2014-04-05 4:35 ` [PATCH 1/3] Documentation: add Device tree bindings for Hisilicon hip04 ethernet Zhangfei Gao
2014-04-05 4:35 ` [PATCH 2/3] net: hisilicon: new hip04 MDIO driver Zhangfei Gao
@ 2014-04-05 4:35 ` Zhangfei Gao
2014-04-07 18:53 ` David Miller
2014-04-07 18:56 ` David Miller
2 siblings, 2 replies; 64+ messages in thread
From: Zhangfei Gao @ 2014-04-05 4:35 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
Support Hisilicon hip04 ethernet driver, including 100M / 1000M controller
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
---
drivers/net/ethernet/hisilicon/Makefile | 2 +-
drivers/net/ethernet/hisilicon/hip04_eth.c | 777 ++++++++++++++++++++++++++++
2 files changed, 778 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile
index 1d6eb6e..17dec03 100644
--- a/drivers/net/ethernet/hisilicon/Makefile
+++ b/drivers/net/ethernet/hisilicon/Makefile
@@ -2,4 +2,4 @@
# Makefile for the HISILICON network device drivers.
#
-obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o
+obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o hip04_eth.o
diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c
new file mode 100644
index 0000000..29549a5
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hip04_eth.c
@@ -0,0 +1,777 @@
+
+/* Copyright (c) 2014 Linaro Ltd.
+ * Copyright (c) 2014 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/phy.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#define PPE_CFG_RX_ADDR 0x100
+#define PPE_CFG_POOL_GRP 0x300
+#define PPE_CFG_RX_BUF_SIZE 0x400
+#define PPE_CFG_RX_FIFO_SIZE 0x500
+#define PPE_CURR_BUF_CNT 0xa200
+
+#define GE_DUPLEX_TYPE 0x8
+#define GE_MAX_FRM_SIZE_REG 0x3c
+#define GE_PORT_MODE 0x40
+#define GE_PORT_EN 0x44
+#define GE_SHORT_RUNTS_THR_REG 0x50
+#define GE_TX_LOCAL_PAGE_REG 0x5c
+#define GE_TRANSMIT_CONTROL_REG 0x60
+#define GE_CF_CRC_STRIP_REG 0x1b0
+#define GE_MODE_CHANGE_EN 0x1b4
+#define GE_RECV_CONTROL_REG 0x1e0
+#define GE_STATION_MAC_ADDRESS 0x210
+#define PPE_CFG_TX_PKT_BD_ADDR 0x420
+#define PPE_CFG_MAX_FRAME_LEN_REG 0x408
+#define PPE_CFG_BUS_CTRL_REG 0x424
+#define PPE_CFG_RX_CTRL_REG 0x428
+#define PPE_CFG_RX_PKT_MODE_REG 0x438
+#define PPE_CFG_QOS_VMID_GEN 0x500
+#define PPE_CFG_RX_PKT_INT 0x538
+#define PPE_INTEN 0x600
+#define PPE_INTSTS 0x608
+#define PPE_RINT 0x604
+#define PPE_CFG_STS_MODE 0x700
+#define PPE_HIS_RX_PKT_CNT 0x804
+
+/* REG_INTERRUPT */
+#define RCV_INT BIT(10)
+#define RCV_NOBUF BIT(8)
+#define DEF_INT_MASK (RCV_INT | RCV_NOBUF)
+
+#define RX_DESC_NUM 64
+#define TX_DESC_NUM 64
+#define TX_NEXT(N) (((N) + 1) & (TX_DESC_NUM-1))
+#define RX_NEXT(N) (((N) + 1) & (RX_DESC_NUM-1))
+
+#define GMAC_PPE_RX_PKT_MAX_LEN 379
+#define GMAC_MAX_PKT_LEN 1516
+#define DESC_DEF_CFG 0x14
+#define RX_BUF_SIZE 1600
+#define RX_PKT_ERR 0x3
+#define TX_TIMEOUT (6 * HZ)
+#define RECLAIM_PERIOD (HZ / 15)
+
+#define DRV_NAME "hip04-ether"
+
+struct tx_desc {
+ u32 send_addr;
+ u16 reserved_16;
+ u16 send_size;
+ u32 reserved_32;
+ u32 cfg;
+ u32 wb_addr;
+} ____cacheline_aligned;
+
+struct rx_desc {
+ u16 reserved_16;
+ u16 pkt_len;
+ u32 reserve1[3];
+ u32 pkt_err;
+ u32 reserve2[4];
+};
+
+struct hip04_priv {
+ void __iomem *base;
+ int phy_mode;
+ int chan;
+ unsigned int port;
+ unsigned int speed;
+ unsigned int duplex;
+ unsigned int reg_inten;
+
+ struct napi_struct napi;
+ struct net_device *ndev;
+
+ struct tx_desc *tx_desc;
+ dma_addr_t tx_desc_dma;
+ struct sk_buff *tx_skb[TX_DESC_NUM];
+ dma_addr_t tx_phys[TX_DESC_NUM];
+ spinlock_t lock;
+ unsigned int tx_head;
+ unsigned int tx_tail;
+ unsigned int tx_count;
+
+ unsigned char *rx_buf[RX_DESC_NUM];
+ dma_addr_t rx_phys[RX_DESC_NUM];
+ unsigned int rx_head;
+ unsigned int rx_buf_size;
+
+ struct device_node *phy_node;
+ struct phy_device *phy;
+ struct regmap *map;
+ struct timer_list txtimer;
+};
+
+static void hip04_config_port(struct net_device *ndev, u32 speed, u32 duplex)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ priv->speed = speed;
+ priv->duplex = duplex;
+
+ switch (priv->phy_mode) {
+ case PHY_INTERFACE_MODE_SGMII:
+ if (speed == SPEED_1000)
+ val = 8;
+ else if (speed == SPEED_100)
+ val = 7;
+ else
+ val = 6; /* SPEED_10 */
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ if (speed == SPEED_100)
+ val = 1;
+ else
+ val = 0; /* SPEED_10 */
+ break;
+ default:
+ netdev_warn(ndev, "not supported mode\n");
+ val = 0;
+ break;
+ }
+ writel_relaxed(val, priv->base + GE_PORT_MODE);
+
+ val = (duplex) ? BIT(0) : 0;
+ writel_relaxed(val, priv->base + GE_DUPLEX_TYPE);
+
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_MODE_CHANGE_EN);
+}
+
+static void hip04_reset_ppe(struct hip04_priv *priv)
+{
+ u32 val, tmp;
+
+ do {
+ regmap_read(priv->map, priv->port * 4 + PPE_CURR_BUF_CNT, &val);
+ regmap_read(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, &tmp);
+ } while (val & 0xfff);
+}
+
+static void hip04_config_fifo(struct hip04_priv *priv)
+{
+ u32 val;
+
+ val = readl_relaxed(priv->base + PPE_CFG_STS_MODE);
+ val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */
+ writel_relaxed(val, priv->base + PPE_CFG_STS_MODE);
+
+ val = BIT(priv->port);
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_POOL_GRP, val);
+
+ val = priv->port << 8;
+ val |= BIT(14);
+ writel_relaxed(val, priv->base + PPE_CFG_QOS_VMID_GEN);
+
+ val = RX_BUF_SIZE;
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_BUF_SIZE, val);
+
+ val = RX_DESC_NUM << 16; /* depth */
+ val |= BIT(11); /* seq: first set first ues */
+ val |= RX_DESC_NUM * priv->chan; /* start_addr */
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_FIFO_SIZE, val);
+
+ /* pkt store format */
+ val = NET_IP_ALIGN << 11; /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_CTRL_REG);
+
+ /* following cfg required for 1000M */
+ /* pkt mode */
+ val = BIT(18); /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_MODE_REG);
+
+ /* set bus ctrl */
+ val = BIT(14); /* buffer locally release */
+ val |= BIT(0); /* big endian */
+ writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG);
+
+ /* set max pkt len, curtail if exceed */
+ val = GMAC_PPE_RX_PKT_MAX_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + PPE_CFG_MAX_FRAME_LEN_REG);
+
+ /* set max len of each pkt */
+ val = GMAC_MAX_PKT_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + GE_MAX_FRM_SIZE_REG);
+
+ /* set min len of each pkt */
+ val = 31; /* min buffer len */
+ writel_relaxed(val, priv->base + GE_SHORT_RUNTS_THR_REG);
+
+ /* tx */
+ val = readl_relaxed(priv->base + GE_TRANSMIT_CONTROL_REG);
+ val |= BIT(5); /* tx auto neg */
+ val |= BIT(6); /* tx add crc */
+ val |= BIT(7); /* tx short pad through */
+ writel_relaxed(val, priv->base + GE_TRANSMIT_CONTROL_REG);
+
+ /* rx crc */
+ val = BIT(0); /* rx strip crc */
+ writel_relaxed(val, priv->base + GE_CF_CRC_STRIP_REG);
+
+ /* rx */
+ val = readl_relaxed(priv->base + GE_RECV_CONTROL_REG);
+ val |= BIT(3); /* rx strip pad */
+ val |= BIT(4); /* run pkt en */
+ writel_relaxed(val, priv->base + GE_RECV_CONTROL_REG);
+
+ /* auto neg control */
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_TX_LOCAL_PAGE_REG);
+}
+
+static void hip04_mac_enable(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ /* enable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val |= BIT(1); /* rx */
+ val |= BIT(2); /* tx */
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+
+ /* clear rx int */
+ val = RCV_INT;
+ writel_relaxed(val, priv->base + PPE_RINT);
+
+ /* config recv int */
+ val = BIT(6); /* int threshold 1 package */
+ val |= 0x4; /* recv timeout */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT);
+
+ /* enable interrupt */
+ priv->reg_inten = DEF_INT_MASK;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+}
+
+static void hip04_mac_disable(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ /* disable int */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+
+ /* disable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val &= ~(BIT(1)); /* rx */
+ val &= ~(BIT(2)); /* tx */
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+}
+
+static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ writel(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR);
+}
+
+static void hip04_set_recv_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, phys);
+}
+
+static u32 hip04_recv_cnt(struct hip04_priv *priv)
+{
+ return readl(priv->base + PPE_HIS_RX_PKT_CNT);
+}
+
+static void hip04_update_mac_address(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+
+ writel_relaxed(((ndev->dev_addr[0] << 8) | (ndev->dev_addr[1])),
+ priv->base + GE_STATION_MAC_ADDRESS);
+ writel_relaxed(((ndev->dev_addr[2] << 24) | (ndev->dev_addr[3] << 16) |
+ (ndev->dev_addr[4] << 8) | (ndev->dev_addr[5])),
+ priv->base + GE_STATION_MAC_ADDRESS + 4);
+}
+
+static int hip04_set_mac_address(struct net_device *ndev, void *addr)
+{
+ eth_mac_addr(ndev, addr);
+ hip04_update_mac_address(ndev);
+ return 0;
+}
+
+static void hip04_tx_reclaim(struct net_device *ndev, bool force)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ unsigned tx_head = priv->tx_head;
+ unsigned tx_tail = priv->tx_tail;
+ struct tx_desc *desc;
+ unsigned int bytes_compl = 0, pkts_compl = 0;
+
+ spin_lock_bh(&priv->lock);
+ while ((tx_tail != tx_head) || (priv->tx_count == TX_DESC_NUM)) {
+ desc = &priv->tx_desc[priv->tx_tail];
+ if (desc->send_addr != 0) {
+ if (force)
+ desc->send_addr = 0;
+ else
+ break;
+ }
+ if (priv->tx_phys[tx_tail]) {
+ dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
+ priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
+ priv->tx_phys[tx_tail] = 0;
+ }
+ pkts_compl++;
+ bytes_compl += priv->tx_skb[tx_tail]->len;
+ dev_kfree_skb(priv->tx_skb[tx_tail]);
+ priv->tx_skb[tx_tail] = NULL;
+ tx_tail = TX_NEXT(tx_tail);
+ priv->tx_count--;
+ }
+ priv->tx_tail = tx_tail;
+ spin_unlock_bh(&priv->lock);
+
+ netdev_completed_queue(ndev, pkts_compl, bytes_compl);
+
+ if (priv->tx_count)
+ mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
+
+ if (unlikely(netif_queue_stopped(ndev)) &&
+ (priv->tx_count < TX_DESC_NUM))
+ netif_wake_queue(ndev);
+}
+
+static void hip04_xmit_timer(unsigned long data)
+{
+ struct net_device *ndev = (void *) data;
+
+ hip04_tx_reclaim(ndev, false);
+}
+
+static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int tx_head = priv->tx_head;
+ struct tx_desc *desc = &priv->tx_desc[tx_head];
+ dma_addr_t phys;
+
+ hip04_tx_reclaim(ndev, false);
+ mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
+
+ if (priv->tx_count >= TX_DESC_NUM) {
+ netif_stop_queue(ndev);
+ return NETDEV_TX_BUSY;
+ }
+
+ phys = dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys)) {
+ dev_kfree_skb(skb);
+ return NETDEV_TX_OK;
+ }
+
+ priv->tx_skb[tx_head] = skb;
+ priv->tx_phys[tx_head] = phys;
+ desc->send_addr = cpu_to_be32(phys);
+ desc->send_size = cpu_to_be16(skb->len);
+ desc->cfg = cpu_to_be32(DESC_DEF_CFG);
+ phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
+ desc->wb_addr = cpu_to_be32(phys);
+ skb_tx_timestamp(skb);
+
+ /* Don't wait up for transmitted skbs to be freed. */
+ skb_orphan(skb);
+
+ hip04_set_xmit_desc(priv, phys);
+ priv->tx_head = TX_NEXT(tx_head);
+ netdev_sent_queue(ndev, skb->len);
+
+ stats->tx_bytes += skb->len;
+ stats->tx_packets++;
+ priv->tx_count++;
+
+ return NETDEV_TX_OK;
+}
+
+static int hip04_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct hip04_priv *priv = container_of(napi, struct hip04_priv, napi);
+ struct net_device *ndev = priv->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int cnt = hip04_recv_cnt(priv);
+ struct rx_desc *desc;
+ struct sk_buff *skb;
+ unsigned char *buf;
+ bool last = false;
+ dma_addr_t phys;
+ int rx = 0;
+ u16 len;
+ u32 err;
+
+ while (cnt && !last) {
+ buf = priv->rx_buf[priv->rx_head];
+ skb = build_skb(buf, priv->rx_buf_size);
+ if (unlikely(!skb))
+ net_dbg_ratelimited("build_skb failed\n");
+
+ dma_unmap_single(&ndev->dev, priv->rx_phys[priv->rx_head],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[priv->rx_head] = 0;
+
+ desc = (struct rx_desc *)skb->data;
+ len = be16_to_cpu(desc->pkt_len);
+ err = be32_to_cpu(desc->pkt_err);
+
+ if (len > RX_BUF_SIZE)
+ len = RX_BUF_SIZE;
+
+ if (0 == len) {
+ dev_kfree_skb_any(skb);
+ last = true;
+ } else if (err & RX_PKT_ERR) {
+ dev_kfree_skb_any(skb);
+ stats->rx_dropped++;
+ stats->rx_errors++;
+ } else {
+ skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
+ skb_put(skb, len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ napi_gro_receive(&priv->napi, skb);
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+ rx++;
+ }
+
+ buf = netdev_alloc_frag(priv->rx_buf_size);
+ if (!buf)
+ return -ENOMEM;
+ phys = dma_map_single(&ndev->dev, buf,
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys))
+ return -EIO;
+ priv->rx_buf[priv->rx_head] = buf;
+ priv->rx_phys[priv->rx_head] = phys;
+ hip04_set_recv_desc(priv, phys);
+
+ priv->rx_head = RX_NEXT(priv->rx_head);
+ if (rx >= budget)
+ break;
+
+ if (--cnt == 0)
+ cnt = hip04_recv_cnt(priv);
+ }
+
+ if (rx < budget) {
+ napi_complete(napi);
+
+ /* enable rx interrupt */
+ priv->reg_inten |= RCV_INT | RCV_NOBUF;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ }
+
+ return rx;
+}
+
+static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *) dev_id;
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 ists = readl_relaxed(priv->base + PPE_INTSTS);
+
+ writel_relaxed(DEF_INT_MASK, priv->base + PPE_RINT);
+
+ if (ists & (RCV_INT | RCV_NOBUF)) {
+ /* disable rx interrupt */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ napi_schedule(&priv->napi);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hip04_adjust_link(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = priv->phy;
+
+ if ((priv->speed != phy->speed) || (priv->duplex != phy->duplex)) {
+ hip04_config_port(ndev, phy->speed, phy->duplex);
+ phy_print_status(phy);
+ }
+}
+
+static int hip04_mac_open(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->rx_head = 0;
+ priv->tx_head = 0;
+ priv->tx_tail = 0;
+ priv->tx_count = 0;
+
+ hip04_reset_ppe(priv);
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ dma_addr_t phys;
+
+ phys = dma_map_single(&ndev->dev, priv->rx_buf[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys))
+ return -EIO;
+
+ priv->rx_phys[i] = phys;
+ hip04_set_recv_desc(priv, phys);
+ }
+
+ if (priv->phy)
+ phy_start(priv->phy);
+
+ netif_start_queue(ndev);
+ hip04_mac_enable(ndev);
+ napi_enable(&priv->napi);
+
+ return 0;
+}
+
+static int hip04_mac_stop(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ napi_disable(&priv->napi);
+ netif_stop_queue(ndev);
+ hip04_mac_disable(ndev);
+ hip04_tx_reclaim(ndev, true);
+ hip04_reset_ppe(priv);
+
+ if (priv->phy)
+ phy_stop(priv->phy);
+
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ if (priv->rx_phys[i]) {
+ dma_unmap_single(&ndev->dev, priv->rx_phys[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[i] = 0;
+ }
+ }
+
+ return 0;
+}
+
+static void hip04_timeout(struct net_device *ndev)
+{
+ hip04_mac_stop(ndev);
+ hip04_mac_open(ndev);
+ return;
+}
+
+static struct net_device_ops hip04_netdev_ops = {
+ .ndo_open = hip04_mac_open,
+ .ndo_stop = hip04_mac_stop,
+ .ndo_start_xmit = hip04_mac_start_xmit,
+ .ndo_set_mac_address = hip04_set_mac_address,
+ .ndo_tx_timeout = hip04_timeout,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_change_mtu = eth_change_mtu,
+};
+
+static int hip04_alloc_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->tx_desc = dma_alloc_coherent(d,
+ TX_DESC_NUM * sizeof(struct tx_desc),
+ &priv->tx_desc_dma, GFP_KERNEL);
+ if (!priv->tx_desc)
+ return -ENOMEM;
+
+ priv->rx_buf_size = RX_BUF_SIZE +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ priv->rx_buf[i] = netdev_alloc_frag(priv->rx_buf_size);
+ if (!priv->rx_buf[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void hip04_free_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ for (i = 0; i < RX_DESC_NUM; i++)
+ if (priv->rx_buf[i])
+ put_page(virt_to_head_page(priv->rx_buf[i]));
+
+ for (i = 0; i < TX_DESC_NUM; i++)
+ if (priv->tx_skb[i])
+ dev_kfree_skb_any(priv->tx_skb[i]);
+
+ dma_free_coherent(d, TX_DESC_NUM * sizeof(struct tx_desc),
+ priv->tx_desc, priv->tx_desc_dma);
+}
+
+static int hip04_mac_probe(struct platform_device *pdev)
+{
+ struct device *d = &pdev->dev;
+ struct device_node *node = d->of_node;
+ struct of_phandle_args arg;
+ struct net_device *ndev;
+ struct hip04_priv *priv;
+ struct resource *res;
+ unsigned int irq;
+ int ret;
+
+ ndev = alloc_etherdev(sizeof(struct hip04_priv));
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+ platform_set_drvdata(pdev, ndev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(d, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto init_fail;
+ }
+
+ ret = of_parse_phandle_with_fixed_args(node, "port-handle", 2, 0, &arg);
+ if (ret < 0) {
+ dev_warn(d, "no port-handle\n");
+ goto init_fail;
+ }
+
+ priv->port = arg.args[0];
+ priv->chan = arg.args[1];
+
+ priv->map = syscon_node_to_regmap(arg.np);
+ if (IS_ERR(priv->map)) {
+ dev_warn(d, "no syscon hisilicon,hip04-ppe\n");
+ ret = PTR_ERR(priv->map);
+ goto init_fail;
+ }
+
+ priv->phy_mode = of_get_phy_mode(node);
+ if (priv->phy_mode < 0) {
+ dev_warn(d, "not find phy-mode\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ ret = devm_request_irq(d, irq, hip04_mac_interrupt,
+ 0, pdev->name, ndev);
+ if (ret) {
+ netdev_err(ndev, "devm_request_irq failed\n");
+ goto init_fail;
+ }
+
+ priv->phy_node = of_parse_phandle(node, "phy-handle", 0);
+ if (priv->phy_node) {
+ priv->phy = of_phy_connect(ndev, priv->phy_node,
+ &hip04_adjust_link, 0, priv->phy_mode);
+ if (!priv->phy) {
+ ret = -EPROBE_DEFER;
+ goto init_fail;
+ }
+ }
+
+ ether_setup(ndev);
+ ndev->netdev_ops = &hip04_netdev_ops;
+ ndev->watchdog_timeo = TX_TIMEOUT;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+ ndev->irq = irq;
+ netif_napi_add(ndev, &priv->napi, hip04_rx_poll, RX_DESC_NUM);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ hip04_reset_ppe(priv);
+ if (priv->phy_mode == PHY_INTERFACE_MODE_MII)
+ hip04_config_port(ndev, SPEED_100, DUPLEX_FULL);
+
+ hip04_config_fifo(priv);
+ random_ether_addr(ndev->dev_addr);
+ hip04_update_mac_address(ndev);
+
+ ret = hip04_alloc_ring(ndev, d);
+ if (ret) {
+ netdev_err(ndev, "alloc ring fail\n");
+ goto alloc_fail;
+ }
+
+ setup_timer(&priv->txtimer, hip04_xmit_timer, (unsigned long) ndev);
+ ret = register_netdev(ndev);
+ if (ret) {
+ free_netdev(ndev);
+ goto alloc_fail;
+ }
+
+ return 0;
+
+alloc_fail:
+ hip04_free_ring(ndev, d);
+init_fail:
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+ return ret;
+}
+
+static int hip04_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct device *d = &pdev->dev;
+
+ if (priv->phy)
+ phy_disconnect(priv->phy);
+
+ del_timer_sync(&priv->txtimer);
+ hip04_free_ring(ndev, d);
+ unregister_netdev(ndev);
+ free_irq(ndev->irq, ndev);
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+
+ return 0;
+}
+
+static const struct of_device_id hip04_mac_match[] = {
+ { .compatible = "hisilicon,hip04-mac" },
+ { }
+};
+
+static struct platform_driver hip04_mac_driver = {
+ .probe = hip04_mac_probe,
+ .remove = hip04_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = hip04_mac_match,
+ },
+};
+module_platform_driver(hip04_mac_driver);
+
+MODULE_DESCRIPTION("HISILICON P04 Ethernet driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:hip04-ether");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-04-05 4:35 ` [PATCH 3/3] net: hisilicon: new hip04 ethernet driver Zhangfei Gao
@ 2014-04-07 18:53 ` David Miller
2014-04-08 8:07 ` zhangfei
2014-04-18 13:17 ` zhangfei
2014-04-07 18:56 ` David Miller
1 sibling, 2 replies; 64+ messages in thread
From: David Miller @ 2014-04-07 18:53 UTC (permalink / raw)
To: zhangfei.gao
Cc: linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet, linux-arm-kernel, netdev, devicetree
From: Zhangfei Gao <zhangfei.gao@linaro.org>
Date: Sat, 5 Apr 2014 12:35:06 +0800
> +struct tx_desc {
> + u32 send_addr;
> + u16 reserved_16;
> + u16 send_size;
> + u32 reserved_32;
> + u32 cfg;
> + u32 wb_addr;
> +} ____cacheline_aligned;
I do not think that ____cacheline_aligned is appropriate at all here.
First of all, this is a hardware descriptor, so it has a fixed layout
and therefore size.
Secondly, unless you declare this object statically in the data section
of the object file, the alignment doesn't matter. These descriptors
are always dynamically allocated, rather than instantiated in the
kernel/driver image.
> + val = (duplex) ? BIT(0) : 0;
Parenthesis around duplex is not necessary, please remove.
> +static void hip04_reset_ppe(struct hip04_priv *priv)
> +{
> + u32 val, tmp;
> +
> + do {
> + regmap_read(priv->map, priv->port * 4 + PPE_CURR_BUF_CNT, &val);
> + regmap_read(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, &tmp);
> + } while (val & 0xfff);
> +}
This polling loop can loop forever, if the condition never triggers it will
loop forever. You must add some kind of limit or timeout, and subsequent
error handing up the call chain to handle this.
> + val = readl_relaxed(priv->base + PPE_CFG_STS_MODE);
> + val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */
> + writel_relaxed(val, priv->base + PPE_CFG_STS_MODE);
...
> + /* set bus ctrl */
> + val = BIT(14); /* buffer locally release */
> + val |= BIT(0); /* big endian */
> + writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG);
Instead of having to set only one bit at a time in every register and
adding comments here, just define these register values using macros
properly in a header file or similar.
Then you can go val |= PPE_CFG_BUS_CTRL_VAL_THIS | PPE_CFG_BUS_CTRL_VAL_THAT
on one line.
Document the registers where you define the macros, that way people can learn
what other bits are in these register and what they mean, even if you don't
currently use them in the driver itself.
> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
...
> +static void hip04_xmit_timer(unsigned long data)
> +{
> + struct net_device *ndev = (void *) data;
> +
> + hip04_tx_reclaim(ndev, false);
> +}
...
> + mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
And this is where I stop reading your driver, I've stated already that this
kind of reclaim scheme is unacceptable.
The kernel timers lack the granularity necessary to service TX reclaim
with a reasonable amount of latency.
You must use some kind of hardware notification of TX slots becomming
available, I find it totally impossible that a modern ethernet controller
was created without a TX done interrupt.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-04-07 18:53 ` David Miller
@ 2014-04-08 8:07 ` zhangfei
2014-04-08 8:30 ` David Laight
2014-04-18 13:17 ` zhangfei
1 sibling, 1 reply; 64+ messages in thread
From: zhangfei @ 2014-04-08 8:07 UTC (permalink / raw)
To: David Miller
Cc: linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet, linux-arm-kernel, netdev, devicetree
Dear David
On 04/08/2014 02:53 AM, David Miller wrote:
> From: Zhangfei Gao <zhangfei.gao@linaro.org>
> Date: Sat, 5 Apr 2014 12:35:06 +0800
>
>> +struct tx_desc {
>> + u32 send_addr;
>> + u16 reserved_16;
>> + u16 send_size;
>> + u32 reserved_32;
>> + u32 cfg;
>> + u32 wb_addr;
>> +} ____cacheline_aligned;
>
> I do not think that ____cacheline_aligned is appropriate at all here.
>
> First of all, this is a hardware descriptor, so it has a fixed layout
> and therefore size.
>
> Secondly, unless you declare this object statically in the data section
> of the object file, the alignment doesn't matter. These descriptors
> are always dynamically allocated, rather than instantiated in the
> kernel/driver image.
The ____cacheline_aligned used here is only for the requirement of
alignment, and use dma_alloc_coherent, while at first dma_pool is used
for the requirement of alignment.
Otherwise desc[1] is not aligned and can not be used directly, the
structure is smaller.
>
>> + val = (duplex) ? BIT(0) : 0;
>
> Parenthesis around duplex is not necessary, please remove.
OK
>
>> +static void hip04_reset_ppe(struct hip04_priv *priv)
>> +{
>> + u32 val, tmp;
>> +
>> + do {
>> + regmap_read(priv->map, priv->port * 4 + PPE_CURR_BUF_CNT, &val);
>> + regmap_read(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, &tmp);
>> + } while (val & 0xfff);
>> +}
>
> This polling loop can loop forever, if the condition never triggers it will
> loop forever. You must add some kind of limit or timeout, and subsequent
> error handing up the call chain to handle this.
OK
>
>> + val = readl_relaxed(priv->base + PPE_CFG_STS_MODE);
>> + val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */
>> + writel_relaxed(val, priv->base + PPE_CFG_STS_MODE);
> ...
>> + /* set bus ctrl */
>> + val = BIT(14); /* buffer locally release */
>> + val |= BIT(0); /* big endian */
>> + writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG);
>
> Instead of having to set only one bit at a time in every register and
> adding comments here, just define these register values using macros
> properly in a header file or similar.
>
> Then you can go val |= PPE_CFG_BUS_CTRL_VAL_THIS | PPE_CFG_BUS_CTRL_VAL_THAT
> on one line.
>
> Document the registers where you define the macros, that way people can learn
> what other bits are in these register and what they mean, even if you don't
> currently use them in the driver itself.
OK, got it.
However, some bits is not planed to open since used internally and may
be removed later.
>
>> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> ...
>> +static void hip04_xmit_timer(unsigned long data)
>> +{
>> + struct net_device *ndev = (void *) data;
>> +
>> + hip04_tx_reclaim(ndev, false);
>> +}
> ...
>> + mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
>
> And this is where I stop reading your driver, I've stated already that this
> kind of reclaim scheme is unacceptable.
>
> The kernel timers lack the granularity necessary to service TX reclaim
> with a reasonable amount of latency.
>
> You must use some kind of hardware notification of TX slots becomming
> available, I find it totally impossible that a modern ethernet controller
> was created without a TX done interrupt.
>
I am sorry, but unfortunately this series really does NOT have TX done
interrupt after checked with hardware guy many times.
And next series will add TX done interrupt according to the feedback.
There are two reasons of removing the TX done interrupt when the chip is
designed.
1. The specific product does not care the latency, only care the throughput.
2. When doing many experiment, the tx done interrupt will impact the
throughput, as a result reclaim is moved to xmit as one of
optimizations, then finally tx done interrupt is removed at all.
Is it acceptable of removing timer as well as latency handling, or any
other work around of this kind of hardware?
Thanks
^ permalink raw reply [flat|nested] 64+ messages in thread
* RE: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-04-08 8:07 ` zhangfei
@ 2014-04-08 8:30 ` David Laight
[not found] ` <063D6719AE5E284EB5DD2968C1650D6D0F6F1434-VkEWCZq2GCInGFn1LkZF6NBPR1lH4CV8@public.gmane.org>
0 siblings, 1 reply; 64+ messages in thread
From: David Laight @ 2014-04-08 8:30 UTC (permalink / raw)
To: 'zhangfei', David Miller
Cc: linux@arm.linux.org.uk, arnd@arndb.de, f.fainelli@gmail.com,
sergei.shtylyov@cogentembedded.com, mark.rutland@arm.com,
eric.dumazet@gmail.com, linux-arm-kernel@lists.infradead.org,
netdev@vger.kernel.org, devicetree@vger.kernel.org
From: zhangfei [mailto:zhangfei.gao@linaro.org]
> On 04/08/2014 02:53 AM, David Miller wrote:
> > From: Zhangfei Gao <zhangfei.gao@linaro.org>
> > Date: Sat, 5 Apr 2014 12:35:06 +0800
> >
> >> +struct tx_desc {
> >> + u32 send_addr;
> >> + u16 reserved_16;
> >> + u16 send_size;
The above doesn't look right for endianness independence.
I'd guess the hardware spec shows a 32bit word with the 'send size'
in one half - that is what you need to define.
Since this is a tx descriptor (and written by the host) you
can't have 'reserved' field - the host has to write it.
probably these are 'must be zero' fields.
> >> + u32 reserved_32;
> >> + u32 cfg;
> >> + u32 wb_addr;
> >> +} ____cacheline_aligned;
> >
> > I do not think that ____cacheline_aligned is appropriate at all here.
> >
> > First of all, this is a hardware descriptor, so it has a fixed layout
> > and therefore size.
The structure also isn't even a multiple of a power of two.
So there will be implicit padding at the end.
Since there isn't a 'pointer to next' I presume the hardware accesses
the descriptors from adjacent physical addresses.
So you need to explicitly pad to that size.
If the cache line size were 128 byte the above wouldn't work at all.
> >
> > Secondly, unless you declare this object statically in the data section
> > of the object file, the alignment doesn't matter. These descriptors
> > are always dynamically allocated, rather than instantiated in the
> > kernel/driver image.
>
> The ____cacheline_aligned used here is only for the requirement of
> alignment, and use dma_alloc_coherent, while at first dma_pool is used
> for the requirement of alignment.
> Otherwise desc[1] is not aligned and can not be used directly, the
> structure is smaller.
It sounds like you should be explicitly padding the structure
to 32 bytes - whether or not that is the cache line size.
...
> I am sorry, but unfortunately this series really does NOT have TX done
> interrupt after checked with hardware guy many times.
> And next series will add TX done interrupt according to the feedback.
>
> There are two reasons of removing the TX done interrupt when the chip is
> designed.
> 1. The specific product does not care the latency, only care the throughput.
> 2. When doing many experiment, the tx done interrupt will impact the
> throughput, as a result reclaim is moved to xmit as one of
> optimizations, then finally tx done interrupt is removed at all.
>
> Is it acceptable of removing timer as well as latency handling, or any
> other work around of this kind of hardware?
If you don't have a global 'TX done' interrupt, you need a per
descriptor one.
Otherwise you cannot send at maximum rate in the absence of
receive traffic.
David
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-04-07 18:53 ` David Miller
2014-04-08 8:07 ` zhangfei
@ 2014-04-18 13:17 ` zhangfei
1 sibling, 0 replies; 64+ messages in thread
From: zhangfei @ 2014-04-18 13:17 UTC (permalink / raw)
To: David Miller
Cc: linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet, linux-arm-kernel, netdev, devicetree
Dear David
On 04/08/2014 02:53 AM, David Miller wrote:
>> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> ...
>> +static void hip04_xmit_timer(unsigned long data)
>> +{
>> + struct net_device *ndev = (void *) data;
>> +
>> + hip04_tx_reclaim(ndev, false);
>> +}
> ...
>> + mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
>
> And this is where I stop reading your driver, I've stated already that this
> kind of reclaim scheme is unacceptable.
>
> The kernel timers lack the granularity necessary to service TX reclaim
> with a reasonable amount of latency.
>
> You must use some kind of hardware notification of TX slots becomming
> available, I find it totally impossible that a modern ethernet controller
> was created without a TX done interrupt.
>
There is no tx_done interrupt, we may need some workaround.
Is it acceptable to use poll to reclaim the xmitted buffer.
And in the xmit calling napi_schedule.
Thanks
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-04-05 4:35 ` [PATCH 3/3] net: hisilicon: new hip04 ethernet driver Zhangfei Gao
2014-04-07 18:53 ` David Miller
@ 2014-04-07 18:56 ` David Miller
1 sibling, 0 replies; 64+ messages in thread
From: David Miller @ 2014-04-07 18:56 UTC (permalink / raw)
To: zhangfei.gao
Cc: linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet, linux-arm-kernel, netdev, devicetree
From: Zhangfei Gao <zhangfei.gao@linaro.org>
Date: Sat, 5 Apr 2014 12:35:06 +0800
> +#define DESC_DEF_CFG 0x14
You absolutely cannot do this.
You must document what the bits in the TX descriptor config field
mean, all of them.
I bet there is a bit in there somewhere which tells the chip to signal
an interrupt when the packet has been sent.
But since you haven't documented the descriptor fields properly with
a full set of macro defines, we can't know what bit that is.
I really am very disappointed in the quality of this driver, and you
can expect that there will be a lot of push back and requests for
changes before this driver will be even close to being ready for
inclusion.
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH v6 0/3] add hisilicon hip04 ethernet driver
@ 2014-04-04 15:16 Zhangfei Gao
[not found] ` <1396624597-390-1-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-04-04 15:16 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
v6:
Suggest by Russell: Use netdev_sent_queue & netdev_completed_queue to solve latency issue
Also shorten the period of timer, which is used to wakeup the queue since no
tx completed interrupt.
v5:
no big change, fix typo
v4:
Modify accoringly to the suggetion from Arnd, Florian, Eric, David
Use of_parse_phandle_with_fixed_args & syscon_node_to_regmap get ppe info
Add skb_orphan() and tx_timer for reclaim since no tx_finished interrupt
Update timeout, and move of_phy_connect to probe to reuse open/stop
v3:
Suggest from Arnd, use syscon & regmap_write/read to replace static void __iomem *ppebase.
Modify hisilicon-hip04-net.txt accrordingly to suggestion from Florian and Sergei.
v2:
Got many suggestions from Russell, Arnd, Florian, Mark and Sergei
Remove memcpy, use dma_map/unmap_single, use dma_alloc_coherent rather than dma_pool, etc.
Refer property in ethernet.txt, change ppe description, etc.
Zhangfei Gao (3):
Documentation: add Device tree bindings for Hisilicon hip04 ethernet
net: hisilicon: new hip04 MDIO driver
net: hisilicon: new hip04 ethernet driver
.../bindings/net/hisilicon-hip04-net.txt | 88 +++
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 32 +
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_eth.c | 777 ++++++++++++++++++++
drivers/net/ethernet/hisilicon/hip04_mdio.c | 186 +++++
7 files changed, 1090 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
--
1.7.9.5
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH v5 0/3] add hisilicon hip04 ethernet driver
@ 2014-04-01 13:27 Zhangfei Gao
2014-04-01 13:27 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-04-01 13:27 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
v5:
no big change, fix typo
v4:
Modify accoringly to the suggetion from Arnd, Florian, Eric, David
Use of_parse_phandle_with_fixed_args & syscon_node_to_regmap get ppe info
Add skb_orphan() and tx_timer for reclaim since no tx_finished interrupt
Update timeout, and move of_phy_connect to probe to reuse open/stop
v3:
Suggest from Arnd, use syscon & regmap_write/read to replace static void __iomem *ppebase.
Modify hisilicon-hip04-net.txt accrordingly to suggestion from Florian and Sergei.
v2:
Got many suggestions from Russell, Arnd, Florian, Mark and Sergei
Remove memcpy, use dma_map/unmap_single, use dma_alloc_coherent rather than dma_pool, etc.
Refer property in ethernet.txt, change ppe description, etc.
Zhangfei Gao (3):
Documentation: add Device tree bindings for Hisilicon hip04 ethernet
net: hisilicon: new hip04 MDIO driver
net: hisilicon: new hip04 ethernet driver
.../bindings/net/hisilicon-hip04-net.txt | 88 +++
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 32 +
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_eth.c | 771 ++++++++++++++++++++
drivers/net/ethernet/hisilicon/hip04_mdio.c | 186 +++++
7 files changed, 1084 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
--
1.7.9.5
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-04-01 13:27 [PATCH v5 0/3] add hisilicon " Zhangfei Gao
@ 2014-04-01 13:27 ` Zhangfei Gao
[not found] ` <1396358832-15828-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-04-01 13:27 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
Support Hisilicon hip04 ethernet driver, including 100M / 1000M controller
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
---
drivers/net/ethernet/hisilicon/Makefile | 2 +-
drivers/net/ethernet/hisilicon/hip04_eth.c | 771 ++++++++++++++++++++++++++++
2 files changed, 772 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile
index 1d6eb6e..17dec03 100644
--- a/drivers/net/ethernet/hisilicon/Makefile
+++ b/drivers/net/ethernet/hisilicon/Makefile
@@ -2,4 +2,4 @@
# Makefile for the HISILICON network device drivers.
#
-obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o
+obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o hip04_eth.o
diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c
new file mode 100644
index 0000000..db00337
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hip04_eth.c
@@ -0,0 +1,771 @@
+
+/* Copyright (c) 2014 Linaro Ltd.
+ * Copyright (c) 2014 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/phy.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#define PPE_CFG_RX_ADDR 0x100
+#define PPE_CFG_POOL_GRP 0x300
+#define PPE_CFG_RX_BUF_SIZE 0x400
+#define PPE_CFG_RX_FIFO_SIZE 0x500
+#define PPE_CURR_BUF_CNT 0xa200
+
+#define GE_DUPLEX_TYPE 0x8
+#define GE_MAX_FRM_SIZE_REG 0x3c
+#define GE_PORT_MODE 0x40
+#define GE_PORT_EN 0x44
+#define GE_SHORT_RUNTS_THR_REG 0x50
+#define GE_TX_LOCAL_PAGE_REG 0x5c
+#define GE_TRANSMIT_CONTROL_REG 0x60
+#define GE_CF_CRC_STRIP_REG 0x1b0
+#define GE_MODE_CHANGE_EN 0x1b4
+#define GE_RECV_CONTROL_REG 0x1e0
+#define GE_STATION_MAC_ADDRESS 0x210
+#define PPE_CFG_TX_PKT_BD_ADDR 0x420
+#define PPE_CFG_MAX_FRAME_LEN_REG 0x408
+#define PPE_CFG_BUS_CTRL_REG 0x424
+#define PPE_CFG_RX_CTRL_REG 0x428
+#define PPE_CFG_RX_PKT_MODE_REG 0x438
+#define PPE_CFG_QOS_VMID_GEN 0x500
+#define PPE_CFG_RX_PKT_INT 0x538
+#define PPE_INTEN 0x600
+#define PPE_INTSTS 0x608
+#define PPE_RINT 0x604
+#define PPE_CFG_STS_MODE 0x700
+#define PPE_HIS_RX_PKT_CNT 0x804
+
+/* REG_INTERRUPT */
+#define RCV_INT BIT(10)
+#define RCV_NOBUF BIT(8)
+#define DEF_INT_MASK (RCV_INT | RCV_NOBUF)
+
+#define RX_DESC_NUM 64
+#define TX_DESC_NUM 64
+#define TX_NEXT(N) (((N) + 1) & (TX_DESC_NUM-1))
+#define RX_NEXT(N) (((N) + 1) & (RX_DESC_NUM-1))
+
+#define GMAC_PPE_RX_PKT_MAX_LEN 379
+#define GMAC_MAX_PKT_LEN 1516
+#define DESC_DEF_CFG 0x14
+#define RX_BUF_SIZE 1600
+#define RX_PKT_ERR 0x3
+#define TX_TIMEOUT (6 * HZ)
+#define RECLAIM_PERIOD HZ
+
+#define DRV_NAME "hip04-ether"
+
+struct tx_desc {
+ u32 send_addr;
+ u16 reserved_16;
+ u16 send_size;
+ u32 reserved_32;
+ u32 cfg;
+ u32 wb_addr;
+} ____cacheline_aligned;
+
+struct rx_desc {
+ u16 reserved_16;
+ u16 pkt_len;
+ u32 reserve1[3];
+ u32 pkt_err;
+ u32 reserve2[4];
+};
+
+struct hip04_priv {
+ void __iomem *base;
+ int phy_mode;
+ int chan;
+ unsigned int port;
+ unsigned int speed;
+ unsigned int duplex;
+ unsigned int reg_inten;
+
+ struct napi_struct napi;
+ struct net_device *ndev;
+
+ struct tx_desc *tx_desc;
+ dma_addr_t tx_desc_dma;
+ struct sk_buff *tx_skb[TX_DESC_NUM];
+ dma_addr_t tx_phys[TX_DESC_NUM];
+ spinlock_t lock;
+ unsigned int tx_head;
+ unsigned int tx_tail;
+ unsigned int tx_count;
+
+ unsigned char *rx_buf[RX_DESC_NUM];
+ dma_addr_t rx_phys[RX_DESC_NUM];
+ unsigned int rx_head;
+ unsigned int rx_buf_size;
+
+ struct device_node *phy_node;
+ struct phy_device *phy;
+ struct regmap *map;
+ struct timer_list txtimer;
+};
+
+static void hip04_config_port(struct net_device *ndev, u32 speed, u32 duplex)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ priv->speed = speed;
+ priv->duplex = duplex;
+
+ switch (priv->phy_mode) {
+ case PHY_INTERFACE_MODE_SGMII:
+ if (speed == SPEED_1000)
+ val = 8;
+ else if (speed == SPEED_100)
+ val = 7;
+ else
+ val = 6; /* SPEED_10 */
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ if (speed == SPEED_100)
+ val = 1;
+ else
+ val = 0; /* SPEED_10 */
+ break;
+ default:
+ netdev_warn(ndev, "not supported mode\n");
+ val = 0;
+ break;
+ }
+ writel_relaxed(val, priv->base + GE_PORT_MODE);
+
+ val = (duplex) ? BIT(0) : 0;
+ writel_relaxed(val, priv->base + GE_DUPLEX_TYPE);
+
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_MODE_CHANGE_EN);
+}
+
+static void hip04_reset_ppe(struct hip04_priv *priv)
+{
+ u32 val, tmp;
+
+ do {
+ regmap_read(priv->map, priv->port * 4 + PPE_CURR_BUF_CNT, &val);
+ regmap_read(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, &tmp);
+ } while (val & 0xfff);
+}
+
+static void hip04_config_fifo(struct hip04_priv *priv)
+{
+ u32 val;
+
+ val = readl_relaxed(priv->base + PPE_CFG_STS_MODE);
+ val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */
+ writel_relaxed(val, priv->base + PPE_CFG_STS_MODE);
+
+ val = BIT(priv->port);
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_POOL_GRP, val);
+
+ val = priv->port << 8;
+ val |= BIT(14);
+ writel_relaxed(val, priv->base + PPE_CFG_QOS_VMID_GEN);
+
+ val = RX_BUF_SIZE;
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_BUF_SIZE, val);
+
+ val = RX_DESC_NUM << 16; /* depth */
+ val |= BIT(11); /* seq: first set first ues */
+ val |= RX_DESC_NUM * priv->chan; /* start_addr */
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_FIFO_SIZE, val);
+
+ /* pkt store format */
+ val = NET_IP_ALIGN << 11; /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_CTRL_REG);
+
+ /* following cfg required for 1000M */
+ /* pkt mode */
+ val = BIT(18); /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_MODE_REG);
+
+ /* set bus ctrl */
+ val = BIT(14); /* buffer locally release */
+ val |= BIT(0); /* big endian */
+ writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG);
+
+ /* set max pkt len, curtail if exceed */
+ val = GMAC_PPE_RX_PKT_MAX_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + PPE_CFG_MAX_FRAME_LEN_REG);
+
+ /* set max len of each pkt */
+ val = GMAC_MAX_PKT_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + GE_MAX_FRM_SIZE_REG);
+
+ /* set min len of each pkt */
+ val = 31; /* min buffer len */
+ writel_relaxed(val, priv->base + GE_SHORT_RUNTS_THR_REG);
+
+ /* tx */
+ val = readl_relaxed(priv->base + GE_TRANSMIT_CONTROL_REG);
+ val |= BIT(5); /* tx auto neg */
+ val |= BIT(6); /* tx add crc */
+ val |= BIT(7); /* tx short pad through */
+ writel_relaxed(val, priv->base + GE_TRANSMIT_CONTROL_REG);
+
+ /* rx crc */
+ val = BIT(0); /* rx strip crc */
+ writel_relaxed(val, priv->base + GE_CF_CRC_STRIP_REG);
+
+ /* rx */
+ val = readl_relaxed(priv->base + GE_RECV_CONTROL_REG);
+ val |= BIT(3); /* rx strip pad */
+ val |= BIT(4); /* run pkt en */
+ writel_relaxed(val, priv->base + GE_RECV_CONTROL_REG);
+
+ /* auto neg control */
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_TX_LOCAL_PAGE_REG);
+}
+
+static void hip04_mac_enable(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ /* enable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val |= BIT(1); /* rx */
+ val |= BIT(2); /* tx */
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+
+ /* clear rx int */
+ val = RCV_INT;
+ writel_relaxed(val, priv->base + PPE_RINT);
+
+ /* config recv int */
+ val = BIT(6); /* int threshold 1 package */
+ val |= 0x4; /* recv timeout */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT);
+
+ /* enable interrupt */
+ priv->reg_inten = DEF_INT_MASK;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+}
+
+static void hip04_mac_disable(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ /* disable int */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+
+ /* disable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val &= ~(BIT(1)); /* rx */
+ val &= ~(BIT(2)); /* tx */
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+}
+
+static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ writel(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR);
+}
+
+static void hip04_set_recv_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, phys);
+}
+
+static u32 hip04_recv_cnt(struct hip04_priv *priv)
+{
+ return readl(priv->base + PPE_HIS_RX_PKT_CNT);
+}
+
+static void hip04_update_mac_address(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+
+ writel_relaxed(((ndev->dev_addr[0] << 8) | (ndev->dev_addr[1])),
+ priv->base + GE_STATION_MAC_ADDRESS);
+ writel_relaxed(((ndev->dev_addr[2] << 24) | (ndev->dev_addr[3] << 16) |
+ (ndev->dev_addr[4] << 8) | (ndev->dev_addr[5])),
+ priv->base + GE_STATION_MAC_ADDRESS + 4);
+}
+
+static int hip04_set_mac_address(struct net_device *ndev, void *addr)
+{
+ eth_mac_addr(ndev, addr);
+ hip04_update_mac_address(ndev);
+ return 0;
+}
+
+static void hip04_tx_reclaim(struct net_device *ndev, bool force)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ unsigned tx_head = priv->tx_head;
+ unsigned tx_tail = priv->tx_tail;
+ struct tx_desc *desc;
+
+ spin_lock_bh(&priv->lock);
+ while ((tx_tail != tx_head) || (priv->tx_count == TX_DESC_NUM)) {
+ desc = &priv->tx_desc[priv->tx_tail];
+ if (desc->send_addr != 0) {
+ if (force)
+ desc->send_addr = 0;
+ else
+ break;
+ }
+ if (priv->tx_phys[tx_tail]) {
+ dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
+ priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
+ priv->tx_phys[tx_tail] = 0;
+ }
+ dev_kfree_skb(priv->tx_skb[tx_tail]);
+ priv->tx_skb[tx_tail] = NULL;
+ tx_tail = TX_NEXT(tx_tail);
+ priv->tx_count--;
+ }
+ priv->tx_tail = tx_tail;
+ spin_unlock_bh(&priv->lock);
+
+ if (priv->tx_count)
+ mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
+
+ if (unlikely(netif_queue_stopped(ndev)) &&
+ (priv->tx_count < TX_DESC_NUM))
+ netif_wake_queue(ndev);
+}
+
+static void hip04_xmit_timer(unsigned long data)
+{
+ struct net_device *ndev = (void *) data;
+
+ hip04_tx_reclaim(ndev, false);
+}
+
+static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int tx_head = priv->tx_head;
+ struct tx_desc *desc = &priv->tx_desc[tx_head];
+ dma_addr_t phys;
+
+ hip04_tx_reclaim(ndev, false);
+ mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
+
+ if (priv->tx_count >= TX_DESC_NUM) {
+ netif_stop_queue(ndev);
+ return NETDEV_TX_BUSY;
+ }
+
+ phys = dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys)) {
+ dev_kfree_skb(skb);
+ return NETDEV_TX_OK;
+ }
+
+ priv->tx_skb[tx_head] = skb;
+ priv->tx_phys[tx_head] = phys;
+ desc->send_addr = cpu_to_be32(phys);
+ desc->send_size = cpu_to_be16(skb->len);
+ desc->cfg = cpu_to_be32(DESC_DEF_CFG);
+ phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
+ desc->wb_addr = cpu_to_be32(phys);
+ skb_tx_timestamp(skb);
+
+ /* Don't wait up for transmitted skbs to be freed. */
+ skb_orphan(skb);
+
+ hip04_set_xmit_desc(priv, phys);
+ priv->tx_head = TX_NEXT(tx_head);
+
+ stats->tx_bytes += skb->len;
+ stats->tx_packets++;
+ priv->tx_count++;
+
+ return NETDEV_TX_OK;
+}
+
+static int hip04_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct hip04_priv *priv = container_of(napi, struct hip04_priv, napi);
+ struct net_device *ndev = priv->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int cnt = hip04_recv_cnt(priv);
+ struct rx_desc *desc;
+ struct sk_buff *skb;
+ unsigned char *buf;
+ bool last = false;
+ dma_addr_t phys;
+ int rx = 0;
+ u16 len;
+ u32 err;
+
+ while (cnt && !last) {
+ buf = priv->rx_buf[priv->rx_head];
+ skb = build_skb(buf, priv->rx_buf_size);
+ if (unlikely(!skb))
+ net_dbg_ratelimited("build_skb failed\n");
+
+ dma_unmap_single(&ndev->dev, priv->rx_phys[priv->rx_head],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[priv->rx_head] = 0;
+
+ desc = (struct rx_desc *)skb->data;
+ len = be16_to_cpu(desc->pkt_len);
+ err = be32_to_cpu(desc->pkt_err);
+
+ if (len > RX_BUF_SIZE)
+ len = RX_BUF_SIZE;
+
+ if (0 == len) {
+ dev_kfree_skb_any(skb);
+ last = true;
+ } else if (err & RX_PKT_ERR) {
+ dev_kfree_skb_any(skb);
+ stats->rx_dropped++;
+ stats->rx_errors++;
+ } else {
+ skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
+ skb_put(skb, len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ napi_gro_receive(&priv->napi, skb);
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+ rx++;
+ }
+
+ buf = netdev_alloc_frag(priv->rx_buf_size);
+ if (!buf)
+ return -ENOMEM;
+ phys = dma_map_single(&ndev->dev, buf,
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys))
+ return -EIO;
+ priv->rx_buf[priv->rx_head] = buf;
+ priv->rx_phys[priv->rx_head] = phys;
+ hip04_set_recv_desc(priv, phys);
+
+ priv->rx_head = RX_NEXT(priv->rx_head);
+ if (rx >= budget)
+ break;
+
+ if (--cnt == 0)
+ cnt = hip04_recv_cnt(priv);
+ }
+
+ if (rx < budget) {
+ napi_complete(napi);
+
+ /* enable rx interrupt */
+ priv->reg_inten |= RCV_INT | RCV_NOBUF;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ }
+
+ return rx;
+}
+
+static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *) dev_id;
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 ists = readl_relaxed(priv->base + PPE_INTSTS);
+
+ writel_relaxed(DEF_INT_MASK, priv->base + PPE_RINT);
+
+ if (ists & (RCV_INT | RCV_NOBUF)) {
+ /* disable rx interrupt */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ napi_schedule(&priv->napi);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hip04_adjust_link(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = priv->phy;
+
+ if ((priv->speed != phy->speed) || (priv->duplex != phy->duplex)) {
+ hip04_config_port(ndev, phy->speed, phy->duplex);
+ phy_print_status(phy);
+ }
+}
+
+static int hip04_mac_open(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->rx_head = 0;
+ priv->tx_head = 0;
+ priv->tx_tail = 0;
+ priv->tx_count = 0;
+
+ hip04_reset_ppe(priv);
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ dma_addr_t phys;
+
+ phys = dma_map_single(&ndev->dev, priv->rx_buf[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys))
+ return -EIO;
+
+ priv->rx_phys[i] = phys;
+ hip04_set_recv_desc(priv, phys);
+ }
+
+ if (priv->phy)
+ phy_start(priv->phy);
+
+ netif_start_queue(ndev);
+ hip04_mac_enable(ndev);
+ napi_enable(&priv->napi);
+
+ return 0;
+}
+
+static int hip04_mac_stop(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ napi_disable(&priv->napi);
+ netif_stop_queue(ndev);
+ hip04_mac_disable(ndev);
+ hip04_tx_reclaim(ndev, true);
+ hip04_reset_ppe(priv);
+
+ if (priv->phy)
+ phy_stop(priv->phy);
+
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ if (priv->rx_phys[i]) {
+ dma_unmap_single(&ndev->dev, priv->rx_phys[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[i] = 0;
+ }
+ }
+
+ return 0;
+}
+
+static void hip04_timeout(struct net_device *ndev)
+{
+ hip04_mac_stop(ndev);
+ hip04_mac_open(ndev);
+ return;
+}
+
+static struct net_device_ops hip04_netdev_ops = {
+ .ndo_open = hip04_mac_open,
+ .ndo_stop = hip04_mac_stop,
+ .ndo_start_xmit = hip04_mac_start_xmit,
+ .ndo_set_mac_address = hip04_set_mac_address,
+ .ndo_tx_timeout = hip04_timeout,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_change_mtu = eth_change_mtu,
+};
+
+static int hip04_alloc_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->tx_desc = dma_alloc_coherent(d,
+ TX_DESC_NUM * sizeof(struct tx_desc),
+ &priv->tx_desc_dma, GFP_KERNEL);
+ if (!priv->tx_desc)
+ return -ENOMEM;
+
+ priv->rx_buf_size = RX_BUF_SIZE +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ priv->rx_buf[i] = netdev_alloc_frag(priv->rx_buf_size);
+ if (!priv->rx_buf[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void hip04_free_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ for (i = 0; i < RX_DESC_NUM; i++)
+ if (priv->rx_buf[i])
+ put_page(virt_to_head_page(priv->rx_buf[i]));
+
+ for (i = 0; i < TX_DESC_NUM; i++)
+ if (priv->tx_skb[i])
+ dev_kfree_skb_any(priv->tx_skb[i]);
+
+ dma_free_coherent(d, TX_DESC_NUM * sizeof(struct tx_desc),
+ priv->tx_desc, priv->tx_desc_dma);
+}
+
+static int hip04_mac_probe(struct platform_device *pdev)
+{
+ struct device *d = &pdev->dev;
+ struct device_node *node = d->of_node;
+ struct of_phandle_args arg;
+ struct net_device *ndev;
+ struct hip04_priv *priv;
+ struct resource *res;
+ unsigned int irq;
+ int ret;
+
+ ndev = alloc_etherdev(sizeof(struct hip04_priv));
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+ platform_set_drvdata(pdev, ndev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(d, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto init_fail;
+ }
+
+ ret = of_parse_phandle_with_fixed_args(node, "port-handle", 2, 0, &arg);
+ if (ret < 0) {
+ dev_warn(d, "no port-handle\n");
+ goto init_fail;
+ }
+
+ priv->port = arg.args[0];
+ priv->chan = arg.args[1];
+
+ priv->map = syscon_node_to_regmap(arg.np);
+ if (IS_ERR(priv->map)) {
+ dev_warn(d, "no syscon hisilicon,hip04-ppe\n");
+ ret = PTR_ERR(priv->map);
+ goto init_fail;
+ }
+
+ priv->phy_mode = of_get_phy_mode(node);
+ if (priv->phy_mode < 0) {
+ dev_warn(d, "not find phy-mode\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ ret = devm_request_irq(d, irq, hip04_mac_interrupt,
+ 0, pdev->name, ndev);
+ if (ret) {
+ netdev_err(ndev, "devm_request_irq failed\n");
+ goto init_fail;
+ }
+
+ priv->phy_node = of_parse_phandle(node, "phy-handle", 0);
+ if (priv->phy_node) {
+ priv->phy = of_phy_connect(ndev, priv->phy_node,
+ &hip04_adjust_link, 0, priv->phy_mode);
+ if (!priv->phy) {
+ ret = -EPROBE_DEFER;
+ goto init_fail;
+ }
+ }
+
+ ether_setup(ndev);
+ ndev->netdev_ops = &hip04_netdev_ops;
+ ndev->watchdog_timeo = TX_TIMEOUT;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+ ndev->irq = irq;
+ netif_napi_add(ndev, &priv->napi, hip04_rx_poll, RX_DESC_NUM);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ hip04_reset_ppe(priv);
+ if (priv->phy_mode == PHY_INTERFACE_MODE_MII)
+ hip04_config_port(ndev, SPEED_100, DUPLEX_FULL);
+
+ hip04_config_fifo(priv);
+ random_ether_addr(ndev->dev_addr);
+ hip04_update_mac_address(ndev);
+
+ ret = hip04_alloc_ring(ndev, d);
+ if (ret) {
+ netdev_err(ndev, "alloc ring fail\n");
+ goto alloc_fail;
+ }
+
+ setup_timer(&priv->txtimer, hip04_xmit_timer, (unsigned long) ndev);
+ ret = register_netdev(ndev);
+ if (ret) {
+ free_netdev(ndev);
+ goto alloc_fail;
+ }
+
+ return 0;
+
+alloc_fail:
+ hip04_free_ring(ndev, d);
+init_fail:
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+ return ret;
+}
+
+static int hip04_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct device *d = &pdev->dev;
+
+ if (priv->phy)
+ phy_disconnect(priv->phy);
+
+ del_timer_sync(&priv->txtimer);
+ hip04_free_ring(ndev, d);
+ unregister_netdev(ndev);
+ free_irq(ndev->irq, ndev);
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+
+ return 0;
+}
+
+static const struct of_device_id hip04_mac_match[] = {
+ { .compatible = "hisilicon,hip04-mac" },
+ { }
+};
+
+static struct platform_driver hip04_mac_driver = {
+ .probe = hip04_mac_probe,
+ .remove = hip04_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = hip04_mac_match,
+ },
+};
+module_platform_driver(hip04_mac_driver);
+
+MODULE_DESCRIPTION("HISILICON P04 Ethernet driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:hip04-ether");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH v4 0/3] add hisilicon hip04 ethernet driver
@ 2014-03-28 15:35 Zhangfei Gao
2014-03-28 15:36 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-28 15:35 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
v4:
Modify accoringly to the suggetion from Arnd, Florian, Eric, David
Use of_parse_phandle_with_fixed_args & syscon_node_to_regmap get ppe info
Add skb_orphan() and tx_timer for reclaim since no tx_finished interrupt
Update timeout, and move of_phy_connect to probe to reuse open/stop
v3:
Suggest from Arnd, use syscon & regmap_write/read to replace static void __iomem *ppebase.
Modify hisilicon-hip04-net.txt accrordingly to suggestion from Florian and Sergei.
v2:
Got many suggestions from Russell, Arnd, Florian, Mark and Sergei
Remove memcpy, use dma_map/unmap_single, use dma_alloc_coherent rather than dma_pool, etc.
Refer property in ethernet.txt, change ppe description, etc.
Zhangfei Gao (3):
Documentation: add Device tree bindings for Hisilicon hip04 ethernet
net: hisilicon: new hip04 MDIO driver
net: hisilicon: new hip04 ethernet driver
.../bindings/net/hisilicon-hip04-net.txt | 88 +++
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 32 +
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_eth.c | 764 ++++++++++++++++++++
drivers/net/ethernet/hisilicon/hip04_mdio.c | 186 +++++
7 files changed, 1077 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
--
1.7.9.5
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-28 15:35 [PATCH v4 0/3] add hisilicon " Zhangfei Gao
@ 2014-03-28 15:36 ` Zhangfei Gao
0 siblings, 0 replies; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-28 15:36 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland,
David.Laight, eric.dumazet
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
Support Hisilicon hip04 ethernet driver, including 100M / 1000M controller
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
---
drivers/net/ethernet/hisilicon/Makefile | 2 +-
drivers/net/ethernet/hisilicon/hip04_eth.c | 769 ++++++++++++++++++++++++++++
2 files changed, 770 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile
index 1d6eb6e..5af9b54 100644
--- a/drivers/net/ethernet/hisilicon/Makefile
+++ b/drivers/net/ethernet/hisilicon/Makefile
@@ -2,4 +2,4 @@
# Makefile for the HISILICON network device drivers.
#
-obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o
+obj-$(CONFIG_HIP04_ETH) +=hip04_mdio.o hip04_eth.o
diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c
new file mode 100644
index 0000000..efff711
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hip04_eth.c
@@ -0,0 +1,769 @@
+
+/* Copyright (c) 2014 Linaro Ltd.
+ * Copyright (c) 2014 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/phy.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#define PPE_CFG_RX_ADDR 0x100
+#define PPE_CFG_POOL_GRP 0x300
+#define PPE_CFG_RX_BUF_SIZE 0x400
+#define PPE_CFG_RX_FIFO_SIZE 0x500
+#define PPE_CURR_BUF_CNT 0xa200
+
+#define GE_DUPLEX_TYPE 0x8
+#define GE_MAX_FRM_SIZE_REG 0x3c
+#define GE_PORT_MODE 0x40
+#define GE_PORT_EN 0x44
+#define GE_SHORT_RUNTS_THR_REG 0x50
+#define GE_TX_LOCAL_PAGE_REG 0x5c
+#define GE_TRANSMIT_CONTROL_REG 0x60
+#define GE_CF_CRC_STRIP_REG 0x1b0
+#define GE_MODE_CHANGE_EN 0x1b4
+#define GE_RECV_CONTROL_REG 0x1e0
+#define GE_STATION_MAC_ADDRESS 0x210
+#define PPE_CFG_TX_PKT_BD_ADDR 0x420
+#define PPE_CFG_MAX_FRAME_LEN_REG 0x408
+#define PPE_CFG_BUS_CTRL_REG 0x424
+#define PPE_CFG_RX_CTRL_REG 0x428
+#define PPE_CFG_RX_PKT_MODE_REG 0x438
+#define PPE_CFG_QOS_VMID_GEN 0x500
+#define PPE_CFG_RX_PKT_INT 0x538
+#define PPE_INTEN 0x600
+#define PPE_INTSTS 0x608
+#define PPE_RINT 0x604
+#define PPE_CFG_STS_MODE 0x700
+#define PPE_HIS_RX_PKT_CNT 0x804
+
+/* REG_INTERRUPT */
+#define RCV_INT BIT(10)
+#define RCV_NOBUF BIT(8)
+#define DEF_INT_MASK (RCV_INT | RCV_NOBUF)
+
+#define RX_DESC_NUM 64
+#define TX_DESC_NUM 64
+#define TX_NEXT(N) (((N) + 1) & (TX_DESC_NUM-1))
+#define RX_NEXT(N) (((N) + 1) & (RX_DESC_NUM-1))
+
+#define GMAC_PPE_RX_PKT_MAX_LEN 379
+#define GMAC_MAX_PKT_LEN 1516
+#define DESC_DEF_CFG 0x14
+#define RX_BUF_SIZE 1600
+#define RX_PKT_ERR 0x3
+#define TX_TIMEOUT (6 * HZ)
+#define RECLAIM_PERIOD HZ
+
+#define DRV_NAME "hip04-ether"
+
+struct tx_desc {
+ u32 send_addr;
+ u16 reserved_16;
+ u16 send_size;
+ u32 reserved_32;
+ u32 cfg;
+ u32 wb_addr;
+} ____cacheline_aligned;
+
+struct rx_desc {
+ u16 reserved_16;
+ u16 pkt_len;
+ u32 reserve1[3];
+ u32 pkt_err;
+ u32 reserve2[4];
+};
+
+struct hip04_priv {
+ void __iomem *base;
+ int phy_mode;
+ int chan;
+ unsigned int port;
+ unsigned int speed;
+ unsigned int duplex;
+ unsigned int reg_inten;
+
+ struct napi_struct napi;
+ struct net_device *ndev;
+
+ struct tx_desc *tx_desc;
+ dma_addr_t tx_desc_dma;
+ struct sk_buff *tx_skb[TX_DESC_NUM];
+ dma_addr_t tx_phys[TX_DESC_NUM];
+ spinlock_t lock;
+ unsigned int tx_head;
+ unsigned int tx_tail;
+ unsigned int tx_count;
+
+ unsigned char *rx_buf[RX_DESC_NUM];
+ dma_addr_t rx_phys[RX_DESC_NUM];
+ unsigned int rx_head;
+ unsigned int rx_buf_size;
+
+ struct device_node *phy_node;
+ struct phy_device *phy;
+ struct regmap *map;
+ struct timer_list txtimer;
+};
+
+static void hip04_config_port(struct net_device *ndev, u32 speed, u32 duplex)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ priv->speed = speed;
+ priv->duplex = duplex;
+
+ switch (priv->phy_mode) {
+ case PHY_INTERFACE_MODE_SGMII:
+ if (speed == SPEED_1000)
+ val = 8;
+ else if (speed == SPEED_100)
+ val = 7;
+ else
+ val = 6; /* SPEED_10 */
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ if (speed == SPEED_100)
+ val = 1;
+ else
+ val = 0; /* SPEED_10 */
+ break;
+ default:
+ netdev_warn(ndev, "not supported mode\n");
+ val = 0;
+ break;
+ }
+ writel_relaxed(val, priv->base + GE_PORT_MODE);
+
+ val = (duplex) ? BIT(0) : 0;
+ writel_relaxed(val, priv->base + GE_DUPLEX_TYPE);
+
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_MODE_CHANGE_EN);
+}
+
+static void hip04_reset_ppe(struct hip04_priv *priv)
+{
+ u32 val, tmp;
+
+ do {
+ regmap_read(priv->map, priv->port * 4 + PPE_CURR_BUF_CNT, &val);
+ regmap_read(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, &tmp);
+ } while (val & 0xfff);
+}
+
+static void hip04_config_fifo(struct hip04_priv *priv)
+{
+ u32 val;
+
+ val = readl_relaxed(priv->base + PPE_CFG_STS_MODE);
+ val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */
+ writel_relaxed(val, priv->base + PPE_CFG_STS_MODE);
+
+ val = BIT(priv->port);
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_POOL_GRP, val);
+
+ val = priv->port << 8;
+ val |= BIT(14);
+ writel_relaxed(val, priv->base + PPE_CFG_QOS_VMID_GEN);
+
+ val = RX_BUF_SIZE;
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_BUF_SIZE, val);
+
+ val = RX_DESC_NUM << 16; /* depth */
+ val |= BIT(11); /* seq: first set first ues */
+ val |= RX_DESC_NUM * priv->chan; /* start_addr */
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_FIFO_SIZE, val);
+
+ /* pkt store format */
+ val = NET_IP_ALIGN << 11; /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_CTRL_REG);
+
+ /* following cfg required for 1000M */
+ /* pkt mode */
+ val = BIT(18); /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_MODE_REG);
+
+ /* set bus ctrl */
+ val = BIT(14); /* buffer locally release */
+ val |= BIT(0); /* big endian */
+ writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG);
+
+ /* set max pkt len, curtail if exceed */
+ val = GMAC_PPE_RX_PKT_MAX_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + PPE_CFG_MAX_FRAME_LEN_REG);
+
+ /* set max len of each pkt */
+ val = GMAC_MAX_PKT_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + GE_MAX_FRM_SIZE_REG);
+
+ /* set min len of each pkt */
+ val = 31; /* min buffer len */
+ writel_relaxed(val, priv->base + GE_SHORT_RUNTS_THR_REG);
+
+ /* tx */
+ val = readl_relaxed(priv->base + GE_TRANSMIT_CONTROL_REG);
+ val |= BIT(5); /* tx auto neg */
+ val |= BIT(6); /* tx add crc */
+ val |= BIT(7); /* tx short pad through */
+ writel_relaxed(val, priv->base + GE_TRANSMIT_CONTROL_REG);
+
+ /* rx crc */
+ val = BIT(0); /* rx strip crc */
+ writel_relaxed(val, priv->base + GE_CF_CRC_STRIP_REG);
+
+ /* rx */
+ val = readl_relaxed(priv->base + GE_RECV_CONTROL_REG);
+ val |= BIT(3); /* rx strip pad */
+ val |= BIT(4); /* run pkt en */
+ writel_relaxed(val, priv->base + GE_RECV_CONTROL_REG);
+
+ /* auto neg control */
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_TX_LOCAL_PAGE_REG);
+}
+
+static void hip04_mac_enable(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ /* enable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val |= BIT(1); /* rx*/
+ val |= BIT(2); /* tx*/
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+
+ /* clear rx int */
+ val = RCV_INT;
+ writel_relaxed(val, priv->base + PPE_RINT);
+
+ /* config recv int*/
+ val = BIT(6); /* int threshold 1 package */
+ val |= 0x4; /* recv timeout */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT);
+
+ /* enable interrupt */
+ priv->reg_inten = DEF_INT_MASK;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+}
+
+static void hip04_mac_disable(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ /* disable int */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+
+ /* disable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val &= ~(BIT(1)); /* rx*/
+ val &= ~(BIT(2)); /* tx*/
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+}
+
+static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ writel(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR);
+}
+
+static void hip04_set_recv_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, phys);
+}
+
+static u32 hip04_recv_cnt(struct hip04_priv *priv)
+{
+ return readl(priv->base + PPE_HIS_RX_PKT_CNT);
+}
+
+static void hip04_update_mac_address(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+
+ writel_relaxed(((ndev->dev_addr[0] << 8) | (ndev->dev_addr[1])),
+ priv->base + GE_STATION_MAC_ADDRESS);
+ writel_relaxed(((ndev->dev_addr[2] << 24) | (ndev->dev_addr[3] << 16) |
+ (ndev->dev_addr[4] << 8) | (ndev->dev_addr[5])),
+ priv->base + GE_STATION_MAC_ADDRESS + 4);
+}
+
+static int hip04_set_mac_address(struct net_device *ndev, void *addr)
+{
+ eth_mac_addr(ndev, addr);
+ hip04_update_mac_address(ndev);
+ return 0;
+}
+
+static void hip04_tx_reclaim(struct net_device *ndev, bool force)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ unsigned tx_head = priv->tx_head;
+ unsigned tx_tail = priv->tx_tail;
+ struct tx_desc *desc;
+
+ spin_lock_bh(&priv->lock);
+ while ((tx_tail != tx_head) || (priv->tx_count == TX_DESC_NUM)) {
+ if (priv->tx_phys[tx_tail]) {
+ dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
+ priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
+ priv->tx_phys[tx_tail] = 0;
+ }
+ desc = &priv->tx_desc[priv->tx_tail];
+ if (desc->send_addr != 0) {
+ if (force)
+ desc->send_addr = 0;
+ else
+ break;
+ }
+ dev_kfree_skb(priv->tx_skb[tx_tail]);
+ priv->tx_skb[tx_tail] = NULL;
+ tx_tail = TX_NEXT(tx_tail);
+ priv->tx_count--;
+ }
+ priv->tx_tail = tx_tail;
+ spin_unlock_bh(&priv->lock);
+
+ if (priv->tx_count)
+ mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
+
+ if (unlikely(netif_queue_stopped(ndev)) &&
+ (priv->tx_count < TX_DESC_NUM))
+ netif_wake_queue(ndev);
+}
+
+static void hip04_xmit_timer(unsigned long data)
+{
+ struct net_device *ndev = (void *)data;
+
+ hip04_tx_reclaim(ndev, false);
+}
+
+static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int tx_head = priv->tx_head;
+ struct tx_desc *desc = &priv->tx_desc[tx_head];
+ dma_addr_t phys;
+
+ hip04_tx_reclaim(ndev, false);
+ mod_timer(&priv->txtimer, jiffies + RECLAIM_PERIOD);
+
+ if (priv->tx_count >= TX_DESC_NUM) {
+ netif_stop_queue(ndev);
+ return NETDEV_TX_BUSY;
+ }
+
+ phys = dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys)) {
+ dev_kfree_skb(skb);
+ return NETDEV_TX_OK;
+ }
+
+ priv->tx_skb[tx_head] = skb;
+ priv->tx_phys[tx_head] = phys;
+ desc->send_addr = cpu_to_be32(phys);
+ desc->send_size = cpu_to_be16(skb->len);
+ desc->cfg = cpu_to_be32(DESC_DEF_CFG);
+ phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
+ desc->wb_addr = cpu_to_be32(phys);
+ skb_tx_timestamp(skb);
+
+ /* Don't wait up for transmitted skbs to be freed. */
+ skb_orphan(skb);
+
+ hip04_set_xmit_desc(priv, phys);
+ priv->tx_head = TX_NEXT(tx_head);
+
+ stats->tx_bytes += skb->len;
+ stats->tx_packets++;
+ priv->tx_count++;
+
+ return NETDEV_TX_OK;
+}
+
+static int hip04_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct hip04_priv *priv = container_of(napi, struct hip04_priv, napi);
+ struct net_device *ndev = priv->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int cnt = hip04_recv_cnt(priv);
+ struct rx_desc *desc;
+ struct sk_buff *skb;
+ unsigned char *buf;
+ bool last = false;
+ dma_addr_t phys;
+ int rx = 0;
+ u16 len;
+ u32 err;
+
+ while (cnt && !last) {
+ buf = priv->rx_buf[priv->rx_head];
+ skb = build_skb(buf, priv->rx_buf_size);
+ if (unlikely(!skb))
+ net_dbg_ratelimited("build_skb failed\n");
+
+ dma_unmap_single(&ndev->dev, priv->rx_phys[priv->rx_head],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[priv->rx_head] = 0;
+
+ desc = (struct rx_desc *)skb->data;
+ len = be16_to_cpu(desc->pkt_len);
+ err = be32_to_cpu(desc->pkt_err);
+
+ if (len > RX_BUF_SIZE)
+ len = RX_BUF_SIZE;
+
+ if (0 == len) {
+ dev_kfree_skb_any(skb);
+ last = true;
+ } else if (err & RX_PKT_ERR) {
+ dev_kfree_skb_any(skb);
+ stats->rx_dropped++;
+ stats->rx_errors++;
+ } else {
+ skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
+ skb_put(skb, len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ napi_gro_receive(&priv->napi, skb);
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+ rx++;
+ }
+
+ buf = netdev_alloc_frag(priv->rx_buf_size);
+ if (!buf)
+ return -ENOMEM;
+ phys = dma_map_single(&ndev->dev, buf,
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys))
+ return -EIO;
+ priv->rx_buf[priv->rx_head] = buf;
+ priv->rx_phys[priv->rx_head] = phys;
+ hip04_set_recv_desc(priv, phys);
+
+ priv->rx_head = RX_NEXT(priv->rx_head);
+ if (rx >= budget)
+ break;
+
+ if (--cnt == 0)
+ cnt = hip04_recv_cnt(priv);
+ }
+
+ if (rx < budget) {
+ napi_complete(napi);
+
+ /* enable rx interrupt */
+ priv->reg_inten |= RCV_INT | RCV_NOBUF;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ }
+
+ return rx;
+}
+
+static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *) dev_id;
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 ists = readl_relaxed(priv->base + PPE_INTSTS);
+
+ writel_relaxed(DEF_INT_MASK, priv->base + PPE_RINT);
+
+ if (ists & (RCV_INT | RCV_NOBUF)) {
+ /* disable rx interrupt */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ napi_schedule(&priv->napi);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hip04_adjust_link(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = priv->phy;
+
+ if ((priv->speed != phy->speed) || (priv->duplex != phy->duplex)) {
+ hip04_config_port(ndev, phy->speed, phy->duplex);
+ phy_print_status(phy);
+ }
+}
+
+static int hip04_mac_open(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->rx_head = 0;
+ priv->tx_head = 0;
+ priv->tx_tail = 0;
+ priv->tx_count = 0;
+
+ hip04_reset_ppe(priv);
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ dma_addr_t phys;
+
+ phys = dma_map_single(&ndev->dev, priv->rx_buf[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&ndev->dev, phys))
+ return -EIO;
+
+ priv->rx_phys[i] = phys;
+ hip04_set_recv_desc(priv, phys);
+ }
+
+ if (priv->phy)
+ phy_start(priv->phy);
+
+ netif_start_queue(ndev);
+ hip04_mac_enable(ndev);
+ napi_enable(&priv->napi);
+
+ return 0;
+}
+
+static int hip04_mac_stop(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ napi_disable(&priv->napi);
+ netif_stop_queue(ndev);
+ hip04_mac_disable(ndev);
+ hip04_tx_reclaim(ndev, true);
+ hip04_reset_ppe(priv);
+
+ if (priv->phy)
+ phy_stop(priv->phy);
+
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ if (priv->rx_phys[i]) {
+ dma_unmap_single(&ndev->dev, priv->rx_phys[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[i] = 0;
+ }
+ }
+
+ return 0;
+}
+
+static void hip04_timeout(struct net_device *ndev)
+{
+ hip04_mac_stop(ndev);
+ hip04_mac_open(ndev);
+ return;
+}
+
+static struct net_device_ops hip04_netdev_ops = {
+ .ndo_open = hip04_mac_open,
+ .ndo_stop = hip04_mac_stop,
+ .ndo_start_xmit = hip04_mac_start_xmit,
+ .ndo_set_mac_address = hip04_set_mac_address,
+ .ndo_tx_timeout = hip04_timeout,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_change_mtu = eth_change_mtu,
+};
+
+static int hip04_alloc_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->tx_desc = dma_alloc_coherent(d,
+ TX_DESC_NUM * sizeof(struct tx_desc),
+ &priv->tx_desc_dma, GFP_KERNEL);
+ if (!priv->tx_desc)
+ return -ENOMEM;
+
+ priv->rx_buf_size = RX_BUF_SIZE +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ priv->rx_buf[i] = netdev_alloc_frag(priv->rx_buf_size);
+ if (!priv->rx_buf[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void hip04_free_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ for (i = 0; i < RX_DESC_NUM; i++)
+ if (priv->rx_buf[i])
+ put_page(virt_to_head_page(priv->rx_buf[i]));
+
+ for (i = 0; i < TX_DESC_NUM; i++)
+ if (priv->tx_skb[i])
+ dev_kfree_skb_any(priv->tx_skb[i]);
+
+ dma_free_coherent(d, TX_DESC_NUM * sizeof(struct tx_desc),
+ priv->tx_desc, priv->tx_desc_dma);
+}
+
+static int hip04_mac_probe(struct platform_device *pdev)
+{
+ struct device *d = &pdev->dev;
+ struct device_node *node = d->of_node;
+ struct of_phandle_args arg;
+ struct net_device *ndev;
+ struct hip04_priv *priv;
+ struct resource *res;
+ unsigned int irq;
+ int ret;
+
+ ndev = alloc_etherdev(sizeof(struct hip04_priv));
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+ platform_set_drvdata(pdev, ndev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(d, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto init_fail;
+ }
+
+ ret = of_parse_phandle_with_fixed_args(node, "port-handle", 2, 0, &arg);
+ if (ret < 0) {
+ dev_warn(d, "no port-handle\n");
+ goto init_fail;
+ }
+
+ priv->port = arg.args[0];
+ priv->chan = arg.args[1];
+
+ priv->map = syscon_node_to_regmap(arg.np);
+ if (IS_ERR(priv->map)) {
+ dev_warn(d, "no syscon hisilicon,hip04-ppe\n");
+ ret = PTR_ERR(priv->map);
+ goto init_fail;
+ }
+
+ priv->phy_mode = of_get_phy_mode(node);
+ if (priv->phy_mode < 0) {
+ dev_warn(d, "not find phy-mode\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ ether_setup(ndev);
+ ndev->netdev_ops = &hip04_netdev_ops;
+ ndev->watchdog_timeo = TX_TIMEOUT;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+ ndev->irq = irq;
+ netif_napi_add(ndev, &priv->napi, hip04_rx_poll, RX_DESC_NUM);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ hip04_reset_ppe(priv);
+ if (priv->phy_mode == PHY_INTERFACE_MODE_MII)
+ hip04_config_port(ndev, SPEED_100, DUPLEX_FULL);
+
+ hip04_config_fifo(priv);
+ random_ether_addr(ndev->dev_addr);
+ hip04_update_mac_address(ndev);
+
+ ret = hip04_alloc_ring(ndev, d);
+ if (ret) {
+ netdev_err(ndev, "alloc ring fail\n");
+ goto alloc_fail;
+ }
+
+ ret = devm_request_irq(d, irq, hip04_mac_interrupt,
+ 0, pdev->name, ndev);
+ if (ret) {
+ netdev_err(ndev, "devm_request_irq failed\n");
+ goto alloc_fail;
+ }
+
+ priv->phy_node = of_parse_phandle(node, "phy-handle", 0);
+ if (priv->phy_node) {
+ priv->phy = of_phy_connect(ndev, priv->phy_node,
+ &hip04_adjust_link, 0, priv->phy_mode);
+ if (!priv->phy)
+ return -EPROBE_DEFER;
+ }
+
+ setup_timer(&priv->txtimer, hip04_xmit_timer, (unsigned long) ndev);
+ ret = register_netdev(ndev);
+ if (ret) {
+ free_netdev(ndev);
+ goto alloc_fail;
+ }
+
+ return 0;
+
+alloc_fail:
+ hip04_free_ring(ndev, d);
+init_fail:
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+ return ret;
+}
+
+static int hip04_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct device *d = &pdev->dev;
+
+ if (priv->phy)
+ phy_disconnect(priv->phy);
+
+ del_timer_sync(&priv->txtimer);
+ hip04_free_ring(ndev, d);
+ unregister_netdev(ndev);
+ free_irq(ndev->irq, ndev);
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+
+ return 0;
+}
+
+static const struct of_device_id hip04_mac_match[] = {
+ { .compatible = "hisilicon,hip04-mac" },
+ { }
+};
+
+static struct platform_driver hip04_mac_driver = {
+ .probe = hip04_mac_probe,
+ .remove = hip04_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = hip04_mac_match,
+ },
+};
+module_platform_driver(hip04_mac_driver);
+
+MODULE_DESCRIPTION("HISILICON P04 Ethernet driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:hip04-ether");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH v3 0/3] add hisilicon hip04 ethernet driver
@ 2014-03-24 14:14 Zhangfei Gao
2014-03-24 14:14 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-24 14:14 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
v3:
Suggest from Arnd, use syscon & regmap_write/read to replace static void __iomem *ppebase.
Modify hisilicon-hip04-net.txt accrordingly to suggestion from Florian and Sergei.
v2:
Got many suggestions from Russell, Arnd, Florian, Mark and Sergei
Remove memcpy, use dma_map/unmap_single, use dma_alloc_coherent rather than dma_pool, etc.
Refer property in ethernet.txt, change ppe description, etc.
Zhangfei Gao (3):
Documentation: add Device tree bindings for Hisilicon hip04 ethernet
net: hisilicon: new hip04 MDIO driver
net: hisilicon: new hip04 ethernet driver
.../bindings/net/hisilicon-hip04-net.txt | 110 +++
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 32 +
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_eth.c | 728 ++++++++++++++++++++
drivers/net/ethernet/hisilicon/hip04_mdio.c | 186 +++++
7 files changed, 1063 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
--
1.7.9.5
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-24 14:14 [PATCH v3 0/3] add hisilicon " Zhangfei Gao
@ 2014-03-24 14:14 ` Zhangfei Gao
[not found] ` <1395670496-17381-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2014-03-24 16:32 ` Florian Fainelli
0 siblings, 2 replies; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-24 14:14 UTC (permalink / raw)
To: davem, linux, arnd, f.fainelli, sergei.shtylyov, mark.rutland
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
Support Hisilicon hip04 ethernet driver, including 100M / 1000M controller
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
---
drivers/net/ethernet/hisilicon/Makefile | 2 +-
drivers/net/ethernet/hisilicon/hip04_eth.c | 728 ++++++++++++++++++++++++++++
2 files changed, 729 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile
index 1d6eb6e..e6fe7af 100644
--- a/drivers/net/ethernet/hisilicon/Makefile
+++ b/drivers/net/ethernet/hisilicon/Makefile
@@ -2,4 +2,4 @@
# Makefile for the HISILICON network device drivers.
#
-obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o
+obj-$(CONFIG_HIP04_ETH) += hip04_eth.o hip04_mdio.o
diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c
new file mode 100644
index 0000000..c1131b2
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hip04_eth.c
@@ -0,0 +1,728 @@
+
+/* Copyright (c) 2014 Linaro Ltd.
+ * Copyright (c) 2014 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/phy.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#define PPE_CFG_RX_ADDR 0x100
+#define PPE_CFG_POOL_GRP 0x300
+#define PPE_CFG_RX_BUF_SIZE 0x400
+#define PPE_CFG_RX_FIFO_SIZE 0x500
+#define PPE_CURR_BUF_CNT 0xa200
+
+#define GE_DUPLEX_TYPE 0x8
+#define GE_MAX_FRM_SIZE_REG 0x3c
+#define GE_PORT_MODE 0x40
+#define GE_PORT_EN 0x44
+#define GE_SHORT_RUNTS_THR_REG 0x50
+#define GE_TX_LOCAL_PAGE_REG 0x5c
+#define GE_TRANSMIT_CONTROL_REG 0x60
+#define GE_CF_CRC_STRIP_REG 0x1b0
+#define GE_MODE_CHANGE_EN 0x1b4
+#define GE_RECV_CONTROL_REG 0x1e0
+#define GE_STATION_MAC_ADDRESS 0x210
+#define PPE_CFG_TX_PKT_BD_ADDR 0x420
+#define PPE_CFG_MAX_FRAME_LEN_REG 0x408
+#define PPE_CFG_BUS_CTRL_REG 0x424
+#define PPE_CFG_RX_CTRL_REG 0x428
+#define PPE_CFG_RX_PKT_MODE_REG 0x438
+#define PPE_CFG_QOS_VMID_GEN 0x500
+#define PPE_CFG_RX_PKT_INT 0x538
+#define PPE_INTEN 0x600
+#define PPE_INTSTS 0x608
+#define PPE_RINT 0x604
+#define PPE_CFG_STS_MODE 0x700
+#define PPE_HIS_RX_PKT_CNT 0x804
+
+/* REG_INTERRUPT */
+#define RCV_INT BIT(10)
+#define RCV_NOBUF BIT(8)
+#define DEF_INT_MASK 0x41fdf
+
+#define RX_DESC_NUM 64
+#define TX_DESC_NUM 64
+#define TX_NEXT(N) (((N) + 1) & (TX_DESC_NUM-1))
+#define RX_NEXT(N) (((N) + 1) & (RX_DESC_NUM-1))
+
+#define GMAC_PPE_RX_PKT_MAX_LEN 379
+#define GMAC_MAX_PKT_LEN 1516
+#define DESC_DEF_CFG 0x14
+#define RX_BUF_SIZE 1600
+#define RX_PKT_ERR 0x3
+#define TX_TIMEOUT (6 * HZ)
+
+#define DRV_NAME "hip04-ether"
+
+struct tx_desc {
+ u32 send_addr;
+ u16 reserved_16;
+ u16 send_size;
+ u32 reserved_32;
+ u32 cfg;
+ u32 wb_addr;
+} ____cacheline_aligned;
+
+struct rx_desc {
+ u16 reserved_16;
+ u16 pkt_len;
+ u32 reserve1[3];
+ u32 pkt_err;
+ u32 reserve2[4];
+};
+
+struct hip04_priv {
+ void __iomem *base;
+ int phy_mode;
+ int chan;
+ unsigned int port;
+ unsigned int speed;
+ unsigned int duplex;
+ unsigned int reg_inten;
+
+ struct napi_struct napi;
+ struct net_device *ndev;
+
+ struct tx_desc *tx_desc;
+ dma_addr_t tx_desc_dma;
+ struct sk_buff *tx_skb[TX_DESC_NUM];
+ dma_addr_t tx_phys[TX_DESC_NUM];
+ unsigned int tx_head;
+ unsigned int tx_tail;
+ unsigned int tx_count;
+
+ unsigned char *rx_buf[RX_DESC_NUM];
+ dma_addr_t rx_phys[RX_DESC_NUM];
+ unsigned int rx_head;
+ unsigned int rx_buf_size;
+
+ struct device_node *phy_node;
+ struct phy_device *phy;
+ struct regmap *map;
+};
+
+static void hip04_config_port(struct hip04_priv *priv, u32 speed, u32 duplex)
+{
+ u32 val;
+
+ priv->speed = speed;
+ priv->duplex = duplex;
+
+ switch (priv->phy_mode) {
+ case PHY_INTERFACE_MODE_SGMII:
+ if (speed == SPEED_1000)
+ val = 8;
+ else
+ val = 7;
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ val = 1; /* SPEED_100 */
+ break;
+ default:
+ val = 0;
+ break;
+ }
+ writel_relaxed(val, priv->base + GE_PORT_MODE);
+
+ val = (duplex) ? BIT(0) : 0;
+ writel_relaxed(val, priv->base + GE_DUPLEX_TYPE);
+
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_MODE_CHANGE_EN);
+}
+
+static void hip04_reset_ppe(struct hip04_priv *priv)
+{
+ u32 val, tmp;
+
+ do {
+ regmap_read(priv->map, priv->port * 4 + PPE_CURR_BUF_CNT, &val);
+ regmap_read(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, &tmp);
+ } while (val & 0xfff);
+}
+
+static void hip04_config_fifo(struct hip04_priv *priv)
+{
+ u32 val;
+
+ val = readl_relaxed(priv->base + PPE_CFG_STS_MODE);
+ val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */
+ writel_relaxed(val, priv->base + PPE_CFG_STS_MODE);
+
+ val = BIT(priv->port);
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_POOL_GRP, val);
+
+ val = priv->port << 8;
+ val |= BIT(14);
+ writel_relaxed(val, priv->base + PPE_CFG_QOS_VMID_GEN);
+
+ val = RX_BUF_SIZE;
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_BUF_SIZE, val);
+
+ val = RX_DESC_NUM << 16; /* depth */
+ val |= BIT(11); /* seq: first set first ues */
+ val |= RX_DESC_NUM * priv->chan; /* start_addr */
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_FIFO_SIZE, val);
+
+ /* pkt store format */
+ val = NET_IP_ALIGN << 11; /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_CTRL_REG);
+
+ /* following cfg required for 1000M */
+ /* pkt mode */
+ val = BIT(18); /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_MODE_REG);
+
+ /* set bus ctrl */
+ val = BIT(14); /* buffer locally release */
+ val |= BIT(0); /* big endian */
+ writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG);
+
+ /* set max pkt len, curtail if exceed */
+ val = GMAC_PPE_RX_PKT_MAX_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + PPE_CFG_MAX_FRAME_LEN_REG);
+
+ /* set max len of each pkt */
+ val = GMAC_MAX_PKT_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + GE_MAX_FRM_SIZE_REG);
+
+ /* set min len of each pkt */
+ val = 31; /* min buffer len */
+ writel_relaxed(val, priv->base + GE_SHORT_RUNTS_THR_REG);
+
+ /* tx */
+ val = readl_relaxed(priv->base + GE_TRANSMIT_CONTROL_REG);
+ val |= BIT(5); /* tx auto neg */
+ val |= BIT(6); /* tx add crc */
+ val |= BIT(7); /* tx short pad through */
+ writel_relaxed(val, priv->base + GE_TRANSMIT_CONTROL_REG);
+
+ /* rx crc */
+ val = BIT(0); /* rx strip crc */
+ writel_relaxed(val, priv->base + GE_CF_CRC_STRIP_REG);
+
+ /* rx */
+ val = readl_relaxed(priv->base + GE_RECV_CONTROL_REG);
+ val |= BIT(3); /* rx strip pad */
+ val |= BIT(4); /* run pkt en */
+ writel_relaxed(val, priv->base + GE_RECV_CONTROL_REG);
+
+ /* auto neg control */
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_TX_LOCAL_PAGE_REG);
+}
+
+static void hip04_mac_enable(struct net_device *ndev, bool enable)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ if (enable) {
+ /* enable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val |= BIT(1); /* rx*/
+ val |= BIT(2); /* tx*/
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+
+ /* enable interrupt */
+ priv->reg_inten = DEF_INT_MASK;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+
+ /* clear rx int */
+ val = RCV_INT;
+ writel_relaxed(val, priv->base + PPE_RINT);
+
+ /* config recv int*/
+ val = BIT(6); /* int threshold 1 package */
+ val |= 0x4; /* recv timeout */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT);
+ } else {
+ /* disable int */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+
+ /* disable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val &= ~(BIT(1)); /* rx*/
+ val &= ~(BIT(2)); /* tx*/
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+ }
+}
+
+static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ writel(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR);
+}
+
+static void hip04_set_recv_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ regmap_write(priv->map, priv->port * 4 + PPE_CFG_RX_ADDR, phys);
+}
+
+static u32 hip04_recv_cnt(struct hip04_priv *priv)
+{
+ return readl(priv->base + PPE_HIS_RX_PKT_CNT);
+}
+
+static void hip04_update_mac_address(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+
+ writel_relaxed(((ndev->dev_addr[0] << 8) | (ndev->dev_addr[1])),
+ priv->base + GE_STATION_MAC_ADDRESS);
+ writel_relaxed(((ndev->dev_addr[2] << 24) | (ndev->dev_addr[3] << 16) |
+ (ndev->dev_addr[4] << 8) | (ndev->dev_addr[5])),
+ priv->base + GE_STATION_MAC_ADDRESS + 4);
+}
+
+static int hip04_set_mac_address(struct net_device *ndev, void *addr)
+{
+ eth_mac_addr(ndev, addr);
+ hip04_update_mac_address(ndev);
+ return 0;
+}
+
+static int hip04_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct hip04_priv *priv = container_of(napi, struct hip04_priv, napi);
+ struct net_device *ndev = priv->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int cnt = hip04_recv_cnt(priv);
+ struct sk_buff *skb;
+ struct rx_desc *desc;
+ unsigned char *buf;
+ dma_addr_t phys;
+ int rx = 0;
+ u16 len;
+ u32 err;
+
+ while (cnt) {
+ buf = priv->rx_buf[priv->rx_head];
+ skb = build_skb(buf, priv->rx_buf_size);
+ if (unlikely(!skb))
+ net_dbg_ratelimited("build_skb failed\n");
+
+ dma_unmap_single(&ndev->dev, priv->rx_phys[priv->rx_head],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[priv->rx_head] = 0;
+
+ desc = (struct rx_desc *)skb->data;
+ len = be16_to_cpu(desc->pkt_len);
+ err = be32_to_cpu(desc->pkt_err);
+
+ if (len > RX_BUF_SIZE)
+ len = RX_BUF_SIZE;
+ if (0 == len)
+ break;
+
+ if (err & RX_PKT_ERR) {
+ dev_kfree_skb_any(skb);
+ stats->rx_dropped++;
+ stats->rx_errors++;
+ continue;
+ }
+
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+
+ skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
+ skb_put(skb, len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ napi_gro_receive(&priv->napi, skb);
+
+ buf = netdev_alloc_frag(priv->rx_buf_size);
+ if (!buf)
+ return -ENOMEM;
+ phys = dma_map_single(&ndev->dev, buf,
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_buf[priv->rx_head] = buf;
+ priv->rx_phys[priv->rx_head] = phys;
+ hip04_set_recv_desc(priv, phys);
+
+ priv->rx_head = RX_NEXT(priv->rx_head);
+ if (rx++ >= budget)
+ break;
+
+ if (--cnt == 0)
+ cnt = hip04_recv_cnt(priv);
+ }
+
+ if (rx < budget) {
+ napi_complete(napi);
+
+ /* enable rx interrupt */
+ priv->reg_inten |= RCV_INT | RCV_NOBUF;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ }
+
+ return rx;
+}
+
+static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *) dev_id;
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 ists = readl_relaxed(priv->base + PPE_INTSTS);
+ u32 val = DEF_INT_MASK;
+
+ writel_relaxed(val, priv->base + PPE_RINT);
+
+ if (ists & (RCV_INT | RCV_NOBUF)) {
+ if (napi_schedule_prep(&priv->napi)) {
+ /* disable rx interrupt */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ __napi_schedule(&priv->napi);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hip04_tx_reclaim(struct net_device *ndev, bool force)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ unsigned tx_head = priv->tx_head;
+ unsigned tx_tail = priv->tx_tail;
+ struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
+
+ while (tx_tail != tx_head) {
+ if (desc->send_addr != 0) {
+ if (force)
+ desc->send_addr = 0;
+ else
+ break;
+ }
+ if (priv->tx_phys[tx_tail]) {
+ dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
+ priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
+ priv->tx_phys[tx_tail] = 0;
+ }
+ dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
+ priv->tx_skb[tx_tail] = NULL;
+ tx_tail = TX_NEXT(tx_tail);
+ priv->tx_count--;
+ }
+ priv->tx_tail = tx_tail;
+}
+
+static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int tx_head = priv->tx_head;
+ struct tx_desc *desc = &priv->tx_desc[tx_head];
+ dma_addr_t phys;
+
+ hip04_tx_reclaim(ndev, false);
+
+ if (priv->tx_count++ >= TX_DESC_NUM) {
+ net_dbg_ratelimited("no TX space for packet\n");
+ netif_stop_queue(ndev);
+ return NETDEV_TX_BUSY;
+ }
+
+ phys = dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE);
+ priv->tx_skb[tx_head] = skb;
+ priv->tx_phys[tx_head] = phys;
+ desc->send_addr = cpu_to_be32(phys);
+ desc->send_size = cpu_to_be16(skb->len);
+ desc->cfg = cpu_to_be32(DESC_DEF_CFG);
+ phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
+ desc->wb_addr = cpu_to_be32(phys);
+ skb_tx_timestamp(skb);
+ hip04_set_xmit_desc(priv, phys);
+ priv->tx_head = TX_NEXT(tx_head);
+
+ stats->tx_bytes += skb->len;
+ stats->tx_packets++;
+
+ return NETDEV_TX_OK;
+}
+
+static void hip04_adjust_link(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = priv->phy;
+
+ if ((priv->speed != phy->speed) || (priv->duplex != phy->duplex)) {
+ hip04_config_port(priv, phy->speed, phy->duplex);
+ phy_print_status(phy);
+ }
+}
+
+static int hip04_mac_open(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->rx_head = 0;
+ priv->tx_head = 0;
+ priv->tx_tail = 0;
+ priv->tx_count = 0;
+
+ hip04_reset_ppe(priv);
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ dma_addr_t phys;
+
+ phys = dma_map_single(&ndev->dev, priv->rx_buf[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[i] = phys;
+ hip04_set_recv_desc(priv, phys);
+ }
+
+ if (priv->phy_node) {
+ priv->phy = of_phy_connect(ndev, priv->phy_node,
+ &hip04_adjust_link, 0, priv->phy_mode);
+ if (!priv->phy)
+ return -ENODEV;
+ phy_start(priv->phy);
+ }
+
+ netif_start_queue(ndev);
+ hip04_mac_enable(ndev, true);
+ napi_enable(&priv->napi);
+
+ return 0;
+}
+
+static int hip04_mac_stop(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ if (priv->phy)
+ phy_disconnect(priv->phy);
+
+ priv->phy = NULL;
+ napi_disable(&priv->napi);
+ netif_stop_queue(ndev);
+ hip04_mac_enable(ndev, false);
+ hip04_tx_reclaim(ndev, true);
+ hip04_reset_ppe(priv);
+
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ if (priv->rx_phys[i]) {
+ dma_unmap_single(&ndev->dev, priv->rx_phys[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[i] = 0;
+ }
+ }
+
+ return 0;
+}
+
+static void hip04_timeout(struct net_device *ndev)
+{
+ netif_wake_queue(ndev);
+ return;
+}
+
+static struct net_device_ops hip04_netdev_ops = {
+ .ndo_open = hip04_mac_open,
+ .ndo_stop = hip04_mac_stop,
+ .ndo_start_xmit = hip04_mac_start_xmit,
+ .ndo_set_mac_address = hip04_set_mac_address,
+ .ndo_tx_timeout = hip04_timeout,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_change_mtu = eth_change_mtu,
+};
+
+static int hip04_alloc_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->tx_desc = dma_alloc_coherent(d,
+ TX_DESC_NUM * sizeof(struct tx_desc),
+ &priv->tx_desc_dma, GFP_KERNEL);
+ if (!priv->tx_desc)
+ return -ENOMEM;
+
+ priv->rx_buf_size = RX_BUF_SIZE +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ priv->rx_buf[i] = netdev_alloc_frag(priv->rx_buf_size);
+ if (!priv->rx_buf[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void hip04_free_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ for (i = 0; i < RX_DESC_NUM; i++)
+ if (priv->rx_buf[i])
+ put_page(virt_to_head_page(priv->rx_buf[i]));
+
+ for (i = 0; i < TX_DESC_NUM; i++)
+ if (priv->tx_skb[i])
+ dev_kfree_skb_any(priv->tx_skb[i]);
+
+ dma_free_coherent(d, TX_DESC_NUM * sizeof(struct tx_desc),
+ priv->tx_desc, priv->tx_desc_dma);
+}
+
+static int hip04_mac_probe(struct platform_device *pdev)
+{
+ struct device *d = &pdev->dev;
+ struct device_node *n, *node = d->of_node;
+ struct net_device *ndev;
+ struct hip04_priv *priv;
+ struct resource *res;
+ unsigned int irq;
+ int ret;
+
+ ndev = alloc_etherdev(sizeof(struct hip04_priv));
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+ platform_set_drvdata(pdev, ndev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(d, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto init_fail;
+ }
+
+ priv->map = syscon_regmap_lookup_by_compatible("hisilicon,hip04-ppe");
+ if (IS_ERR(priv->map)) {
+ dev_warn(d, "no hisilicon,hip04-ppe\n");
+ ret = PTR_ERR(priv->map);
+ goto init_fail;
+ }
+
+ n = of_parse_phandle(node, "port-handle", 0);
+ if (n) {
+ ret = of_property_read_u32(n, "reg", &priv->port);
+ if (ret) {
+ dev_warn(d, "no reg info\n");
+ goto init_fail;
+ }
+
+ ret = of_property_read_u32(n, "channel", &priv->chan);
+ if (ret) {
+ dev_warn(d, "no channel info\n");
+ goto init_fail;
+ }
+ } else {
+ dev_warn(d, "no port-handle\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ priv->phy_mode = of_get_phy_mode(node);
+ if (priv->phy_mode < 0) {
+ dev_warn(d, "not find phy-mode\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ ether_setup(ndev);
+ ndev->netdev_ops = &hip04_netdev_ops;
+ ndev->watchdog_timeo = TX_TIMEOUT;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+ ndev->irq = irq;
+ netif_napi_add(ndev, &priv->napi, hip04_rx_poll, RX_DESC_NUM);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ hip04_reset_ppe(priv);
+ if (priv->phy_mode == PHY_INTERFACE_MODE_MII)
+ hip04_config_port(priv, SPEED_100, DUPLEX_FULL);
+
+ hip04_config_fifo(priv);
+ random_ether_addr(ndev->dev_addr);
+ hip04_update_mac_address(ndev);
+
+ ret = hip04_alloc_ring(ndev, d);
+ if (ret) {
+ netdev_err(ndev, "alloc ring fail\n");
+ goto alloc_fail;
+ }
+
+ ret = devm_request_irq(d, irq, hip04_mac_interrupt,
+ 0, pdev->name, ndev);
+ if (ret) {
+ netdev_err(ndev, "devm_request_irq failed\n");
+ goto alloc_fail;
+ }
+
+ priv->phy_node = of_parse_phandle(node, "phy-handle", 0);
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ free_netdev(ndev);
+ goto alloc_fail;
+ }
+
+ return 0;
+
+alloc_fail:
+ hip04_free_ring(ndev, d);
+init_fail:
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+ return ret;
+}
+
+static int hip04_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct device *d = &pdev->dev;
+
+ hip04_free_ring(ndev, d);
+ unregister_netdev(ndev);
+ free_irq(ndev->irq, ndev);
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+
+ return 0;
+}
+
+static const struct of_device_id hip04_mac_match[] = {
+ { .compatible = "hisilicon,hip04-mac" },
+ { }
+};
+
+static struct platform_driver hip04_mac_driver = {
+ .probe = hip04_mac_probe,
+ .remove = hip04_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = hip04_mac_match,
+ },
+};
+module_platform_driver(hip04_mac_driver);
+
+MODULE_DESCRIPTION("HISILICON P04 Ethernet driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:hip04-ether");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 64+ messages in thread
[parent not found: <1395670496-17381-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>]
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
[not found] ` <1395670496-17381-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
@ 2014-03-24 15:18 ` Arnd Bergmann
2014-03-25 4:06 ` Zhangfei Gao
0 siblings, 1 reply; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-24 15:18 UTC (permalink / raw)
To: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Zhangfei Gao, davem-fT/PcQaiUtIeIZ0/mPfg9Q,
linux-lFZ/pmaqli7XmaaqVzeoHQ, f.fainelli-Re5JQEeQqe8AvxtiuMwx3w,
sergei.shtylyov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8,
mark.rutland-5wv7dgnIgG8, netdev-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA
On Monday 24 March 2014 22:14:56 Zhangfei Gao wrote:
> +
> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> +{
> + struct hip04_priv *priv = netdev_priv(ndev);
> + unsigned tx_head = priv->tx_head;
> + unsigned tx_tail = priv->tx_tail;
> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
> +
> + while (tx_tail != tx_head) {
> + if (desc->send_addr != 0) {
> + if (force)
> + desc->send_addr = 0;
> + else
> + break;
> + }
> + if (priv->tx_phys[tx_tail]) {
> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
> + priv->tx_phys[tx_tail] = 0;
> + }
> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
> + priv->tx_skb[tx_tail] = NULL;
> + tx_tail = TX_NEXT(tx_tail);
> + priv->tx_count--;
> + }
> + priv->tx_tail = tx_tail;
> +}
I think you still need to find a solution to ensure that the tx reclaim is
called eventually through a method other than start_xmit.
> +
> + priv->map = syscon_regmap_lookup_by_compatible("hisilicon,hip04-ppe");
> + if (IS_ERR(priv->map)) {
> + dev_warn(d, "no hisilicon,hip04-ppe\n");
> + ret = PTR_ERR(priv->map);
> + goto init_fail;
> + }
> +
> + n = of_parse_phandle(node, "port-handle", 0);
> + if (n) {
> + ret = of_property_read_u32(n, "reg", &priv->port);
> + if (ret) {
> + dev_warn(d, "no reg info\n");
> + goto init_fail;
> + }
> +
> + ret = of_property_read_u32(n, "channel", &priv->chan);
> + if (ret) {
> + dev_warn(d, "no channel info\n");
> + goto init_fail;
> + }
> + } else {
> + dev_warn(d, "no port-handle\n");
> + ret = -EINVAL;
> + goto init_fail;
> + }
Doing the lookup by "compatible" string doesn't really help you
at solve the problem of single-instance ppe at all, because that
function will only ever look at the first one. Use
syscon_regmap_lookup_by_phandle instead and pass the phandle
you get from the "port-handle" property.
Also, since you decided to treat the ppe as a dump regmap, I would
recommend moving the 'reg' and 'channel' properties into arguments
of the port-handle link, and retting rid of the child nodes of
the ppe, like:
+ ppe: ppe@28c0000 {
+ compatible = "hisilicon,hip04-ppe", "syscon";
+ reg = <0x28c0000 0x10000>;
+ };
+
+ fe: ethernet@28b0000 {
+ compatible = "hisilicon,hip04-mac";
+ reg = <0x28b0000 0x10000>;
+ interrupts = <0 413 4>;
+ phy-mode = "mii";
+ port-handle = <&ppe 0xf1 0>;
+ };
Arnd
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-24 15:18 ` Arnd Bergmann
@ 2014-03-25 4:06 ` Zhangfei Gao
2014-03-25 8:12 ` Arnd Bergmann
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-25 4:06 UTC (permalink / raw)
To: Arnd Bergmann
Cc: linux-arm-kernel, Mark Rutland,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Florian Fainelli, Russell King - ARM Linux, Sergei Shtylyov,
netdev, Zhangfei Gao, David S. Miller
Dear Arnd
On Mon, Mar 24, 2014 at 11:18 PM, Arnd Bergmann <arnd-r2nGTMty4D4@public.gmane.org> wrote:
> On Monday 24 March 2014 22:14:56 Zhangfei Gao wrote:
>
>> +
>> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
>> +{
>> + struct hip04_priv *priv = netdev_priv(ndev);
>> + unsigned tx_head = priv->tx_head;
>> + unsigned tx_tail = priv->tx_tail;
>> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
>> +
>> + while (tx_tail != tx_head) {
>> + if (desc->send_addr != 0) {
>> + if (force)
>> + desc->send_addr = 0;
>> + else
>> + break;
>> + }
>> + if (priv->tx_phys[tx_tail]) {
>> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
>> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
>> + priv->tx_phys[tx_tail] = 0;
>> + }
>> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
>> + priv->tx_skb[tx_tail] = NULL;
>> + tx_tail = TX_NEXT(tx_tail);
>> + priv->tx_count--;
>> + }
>> + priv->tx_tail = tx_tail;
>> +}
>
> I think you still need to find a solution to ensure that the tx reclaim is
> called eventually through a method other than start_xmit.
In the iperf stress test, if move reclaim to poll, there is some
error, sometimes sending zero packets.
While keep reclaim in the xmit to reclaim transmitted packets looks
stable in the test,
There TX_DESC_NUM desc can be used.
>
>> +
>> + priv->map = syscon_regmap_lookup_by_compatible("hisilicon,hip04-ppe");
>> + if (IS_ERR(priv->map)) {
>> + dev_warn(d, "no hisilicon,hip04-ppe\n");
>> + ret = PTR_ERR(priv->map);
>> + goto init_fail;
>> + }
>> +
>> + n = of_parse_phandle(node, "port-handle", 0);
>> + if (n) {
>> + ret = of_property_read_u32(n, "reg", &priv->port);
>> + if (ret) {
>> + dev_warn(d, "no reg info\n");
>> + goto init_fail;
>> + }
>> +
>> + ret = of_property_read_u32(n, "channel", &priv->chan);
>> + if (ret) {
>> + dev_warn(d, "no channel info\n");
>> + goto init_fail;
>> + }
>> + } else {
>> + dev_warn(d, "no port-handle\n");
>> + ret = -EINVAL;
>> + goto init_fail;
>> + }
>
> Doing the lookup by "compatible" string doesn't really help you
> at solve the problem of single-instance ppe at all, because that
> function will only ever look at the first one. Use
> syscon_regmap_lookup_by_phandle instead and pass the phandle
> you get from the "port-handle" property.
Did some experiment, the only ppe base is got from syscon probe.
And looking up by "compatible" did work for three controllers at the same time.
>
> Also, since you decided to treat the ppe as a dump regmap, I would
> recommend moving the 'reg' and 'channel' properties into arguments
> of the port-handle link, and retting rid of the child nodes of
> the ppe, like:
>
> + ppe: ppe@28c0000 {
> + compatible = "hisilicon,hip04-ppe", "syscon";
> + reg = <0x28c0000 0x10000>;
> + };
> +
> + fe: ethernet@28b0000 {
> + compatible = "hisilicon,hip04-mac";
> + reg = <0x28b0000 0x10000>;
> + interrupts = <0 413 4>;
> + phy-mode = "mii";
> + port-handle = <&ppe 0xf1 0>;
> + };
>
Would you mind giving more hints about how to parse the args.
I am thinking of using of_parse_phandle_with_args, is that correct method?
Thanks
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 4:06 ` Zhangfei Gao
@ 2014-03-25 8:12 ` Arnd Bergmann
2014-03-25 17:00 ` Florian Fainelli
0 siblings, 1 reply; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-25 8:12 UTC (permalink / raw)
To: Zhangfei Gao
Cc: Mark Rutland, devicetree@vger.kernel.org, Florian Fainelli,
Russell King - ARM Linux, Sergei Shtylyov, netdev, Zhangfei Gao,
David S. Miller, linux-arm-kernel
On Tuesday 25 March 2014 12:06:31 Zhangfei Gao wrote:
> Dear Arnd
>
> On Mon, Mar 24, 2014 at 11:18 PM, Arnd Bergmann <arnd@arndb.de> wrote:
> > On Monday 24 March 2014 22:14:56 Zhangfei Gao wrote:
> >
> >> +
> >> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> >> +{
> >> + struct hip04_priv *priv = netdev_priv(ndev);
> >> + unsigned tx_head = priv->tx_head;
> >> + unsigned tx_tail = priv->tx_tail;
> >> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
> >> +
> >> + while (tx_tail != tx_head) {
> >> + if (desc->send_addr != 0) {
> >> + if (force)
> >> + desc->send_addr = 0;
> >> + else
> >> + break;
> >> + }
> >> + if (priv->tx_phys[tx_tail]) {
> >> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
> >> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
> >> + priv->tx_phys[tx_tail] = 0;
> >> + }
> >> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
> >> + priv->tx_skb[tx_tail] = NULL;
> >> + tx_tail = TX_NEXT(tx_tail);
> >> + priv->tx_count--;
> >> + }
> >> + priv->tx_tail = tx_tail;
> >> +}
> >
> > I think you still need to find a solution to ensure that the tx reclaim is
> > called eventually through a method other than start_xmit.
>
> In the iperf stress test, if move reclaim to poll, there is some
> error, sometimes sending zero packets.
> While keep reclaim in the xmit to reclaim transmitted packets looks
> stable in the test,
> There TX_DESC_NUM desc can be used.
What I meant is that you need a correct implementation, presumably
you added a bug when you moved the function to poll(), and also you
forgot to add a timer.
> >> + priv->map = syscon_regmap_lookup_by_compatible("hisilicon,hip04-ppe");
> >> + if (IS_ERR(priv->map)) {
> >> + dev_warn(d, "no hisilicon,hip04-ppe\n");
> >> + ret = PTR_ERR(priv->map);
> >> + goto init_fail;
> >> + }
> >> +
> >> + n = of_parse_phandle(node, "port-handle", 0);
> >> + if (n) {
> >> + ret = of_property_read_u32(n, "reg", &priv->port);
> >> + if (ret) {
> >> + dev_warn(d, "no reg info\n");
> >> + goto init_fail;
> >> + }
> >> +
> >> + ret = of_property_read_u32(n, "channel", &priv->chan);
> >> + if (ret) {
> >> + dev_warn(d, "no channel info\n");
> >> + goto init_fail;
> >> + }
> >> + } else {
> >> + dev_warn(d, "no port-handle\n");
> >> + ret = -EINVAL;
> >> + goto init_fail;
> >> + }
> >
> > Doing the lookup by "compatible" string doesn't really help you
> > at solve the problem of single-instance ppe at all, because that
> > function will only ever look at the first one. Use
> > syscon_regmap_lookup_by_phandle instead and pass the phandle
> > you get from the "port-handle" property.
>
> Did some experiment, the only ppe base is got from syscon probe.
> And looking up by "compatible" did work for three controllers at the same time.
The point is that it won't work for more than one ppe instance.
> > Also, since you decided to treat the ppe as a dump regmap, I would
> > recommend moving the 'reg' and 'channel' properties into arguments
> > of the port-handle link, and retting rid of the child nodes of
> > the ppe, like:
> >
> > + ppe: ppe@28c0000 {
> > + compatible = "hisilicon,hip04-ppe", "syscon";
> > + reg = <0x28c0000 0x10000>;
> > + };
> > +
> > + fe: ethernet@28b0000 {
> > + compatible = "hisilicon,hip04-mac";
> > + reg = <0x28b0000 0x10000>;
> > + interrupts = <0 413 4>;
> > + phy-mode = "mii";
> > + port-handle = <&ppe 0xf1 0>;
> > + };
> >
>
> Would you mind giving more hints about how to parse the args.
> I am thinking of using of_parse_phandle_with_args, is that correct method?
I would just use of_property_read_u32_array() to read all three cells,
then pass the first cell entry into syscon_regmap_lookup_by_phandle.
of_parse_phandle_with_args() is what you would use if you were registering
a higher-level driver for the ppe rather than using syscon.
Arnd
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 8:12 ` Arnd Bergmann
@ 2014-03-25 17:00 ` Florian Fainelli
2014-03-25 17:05 ` Arnd Bergmann
0 siblings, 1 reply; 64+ messages in thread
From: Florian Fainelli @ 2014-03-25 17:00 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Zhangfei Gao, linux-arm-kernel, Mark Rutland,
devicetree@vger.kernel.org, Russell King - ARM Linux,
Sergei Shtylyov, netdev, Zhangfei Gao, David S. Miller
2014-03-25 1:12 GMT-07:00 Arnd Bergmann <arnd@arndb.de>:
> On Tuesday 25 March 2014 12:06:31 Zhangfei Gao wrote:
>> Dear Arnd
>>
>> On Mon, Mar 24, 2014 at 11:18 PM, Arnd Bergmann <arnd@arndb.de> wrote:
>> > On Monday 24 March 2014 22:14:56 Zhangfei Gao wrote:
>> >
>> >> +
>> >> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
>> >> +{
>> >> + struct hip04_priv *priv = netdev_priv(ndev);
>> >> + unsigned tx_head = priv->tx_head;
>> >> + unsigned tx_tail = priv->tx_tail;
>> >> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
>> >> +
>> >> + while (tx_tail != tx_head) {
>> >> + if (desc->send_addr != 0) {
>> >> + if (force)
>> >> + desc->send_addr = 0;
>> >> + else
>> >> + break;
>> >> + }
>> >> + if (priv->tx_phys[tx_tail]) {
>> >> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
>> >> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
>> >> + priv->tx_phys[tx_tail] = 0;
>> >> + }
>> >> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
>> >> + priv->tx_skb[tx_tail] = NULL;
>> >> + tx_tail = TX_NEXT(tx_tail);
>> >> + priv->tx_count--;
>> >> + }
>> >> + priv->tx_tail = tx_tail;
>> >> +}
>> >
>> > I think you still need to find a solution to ensure that the tx reclaim is
>> > called eventually through a method other than start_xmit.
>>
>> In the iperf stress test, if move reclaim to poll, there is some
>> error, sometimes sending zero packets.
>> While keep reclaim in the xmit to reclaim transmitted packets looks
>> stable in the test,
>> There TX_DESC_NUM desc can be used.
>
> What I meant is that you need a correct implementation, presumably
> you added a bug when you moved the function to poll(), and also you
> forgot to add a timer.
Using a timer to ensure completion of TX packets is a trick that
worked in the past, but now that the networking stack got smarter,
this might artificially increase the processing time of packets in the
transmit path, and this will defeat features like TCP small queues
etc.. as could be seen with the mvneta driver [1]. The best way really
is to rely on TX completion interrupts when those exist as they cannot
lie about the hardware status (in theory) and they should provide the
fastest way to complete TX packets.
--
Florian
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:00 ` Florian Fainelli
@ 2014-03-25 17:05 ` Arnd Bergmann
2014-03-25 17:16 ` Florian Fainelli
` (2 more replies)
0 siblings, 3 replies; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-25 17:05 UTC (permalink / raw)
To: Florian Fainelli
Cc: Zhangfei Gao, linux-arm-kernel, Mark Rutland,
devicetree@vger.kernel.org, Russell King - ARM Linux,
Sergei Shtylyov, netdev, Zhangfei Gao, David S. Miller
On Tuesday 25 March 2014 10:00:30 Florian Fainelli wrote:
> 2014-03-25 1:12 GMT-07:00 Arnd Bergmann <arnd@arndb.de>:
> > On Tuesday 25 March 2014 12:06:31 Zhangfei Gao wrote:
> >> Dear Arnd
> >>
> >> On Mon, Mar 24, 2014 at 11:18 PM, Arnd Bergmann <arnd@arndb.de> wrote:
> >> > On Monday 24 March 2014 22:14:56 Zhangfei Gao wrote:
> >> >
> >> >> +
> >> >> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> >> >> +{
> >> >> + struct hip04_priv *priv = netdev_priv(ndev);
> >> >> + unsigned tx_head = priv->tx_head;
> >> >> + unsigned tx_tail = priv->tx_tail;
> >> >> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
> >> >> +
> >> >> + while (tx_tail != tx_head) {
> >> >> + if (desc->send_addr != 0) {
> >> >> + if (force)
> >> >> + desc->send_addr = 0;
> >> >> + else
> >> >> + break;
> >> >> + }
> >> >> + if (priv->tx_phys[tx_tail]) {
> >> >> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
> >> >> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
> >> >> + priv->tx_phys[tx_tail] = 0;
> >> >> + }
> >> >> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
> >> >> + priv->tx_skb[tx_tail] = NULL;
> >> >> + tx_tail = TX_NEXT(tx_tail);
> >> >> + priv->tx_count--;
> >> >> + }
> >> >> + priv->tx_tail = tx_tail;
> >> >> +}
> >> >
> >> > I think you still need to find a solution to ensure that the tx reclaim is
> >> > called eventually through a method other than start_xmit.
> >>
> >> In the iperf stress test, if move reclaim to poll, there is some
> >> error, sometimes sending zero packets.
> >> While keep reclaim in the xmit to reclaim transmitted packets looks
> >> stable in the test,
> >> There TX_DESC_NUM desc can be used.
> >
> > What I meant is that you need a correct implementation, presumably
> > you added a bug when you moved the function to poll(), and also you
> > forgot to add a timer.
>
> Using a timer to ensure completion of TX packets is a trick that
> worked in the past, but now that the networking stack got smarter,
> this might artificially increase the processing time of packets in the
> transmit path, and this will defeat features like TCP small queues
> etc.. as could be seen with the mvneta driver [1]. The best way really
> is to rely on TX completion interrupts when those exist as they cannot
> lie about the hardware status (in theory) and they should provide the
> fastest way to complete TX packets.
By as Zhangfei Gao pointed out, this hardware does not have a working
TX completion interrupt. Using timers to do this has always just been
a workaround for broken hardware IMHO.
Arnd
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:05 ` Arnd Bergmann
@ 2014-03-25 17:16 ` Florian Fainelli
2014-03-25 17:57 ` Arnd Bergmann
2014-03-25 17:17 ` David Laight
2014-03-25 17:21 ` Eric Dumazet
2 siblings, 1 reply; 64+ messages in thread
From: Florian Fainelli @ 2014-03-25 17:16 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Zhangfei Gao, linux-arm-kernel, Mark Rutland,
devicetree@vger.kernel.org, Russell King - ARM Linux,
Sergei Shtylyov, netdev, Zhangfei Gao, David S. Miller
2014-03-25 10:05 GMT-07:00 Arnd Bergmann <arnd@arndb.de>:
> On Tuesday 25 March 2014 10:00:30 Florian Fainelli wrote:
>> 2014-03-25 1:12 GMT-07:00 Arnd Bergmann <arnd@arndb.de>:
>> > On Tuesday 25 March 2014 12:06:31 Zhangfei Gao wrote:
>> >> Dear Arnd
>> >>
>> >> On Mon, Mar 24, 2014 at 11:18 PM, Arnd Bergmann <arnd@arndb.de> wrote:
>> >> > On Monday 24 March 2014 22:14:56 Zhangfei Gao wrote:
>> >> >
>> >> >> +
>> >> >> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
>> >> >> +{
>> >> >> + struct hip04_priv *priv = netdev_priv(ndev);
>> >> >> + unsigned tx_head = priv->tx_head;
>> >> >> + unsigned tx_tail = priv->tx_tail;
>> >> >> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
>> >> >> +
>> >> >> + while (tx_tail != tx_head) {
>> >> >> + if (desc->send_addr != 0) {
>> >> >> + if (force)
>> >> >> + desc->send_addr = 0;
>> >> >> + else
>> >> >> + break;
>> >> >> + }
>> >> >> + if (priv->tx_phys[tx_tail]) {
>> >> >> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
>> >> >> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
>> >> >> + priv->tx_phys[tx_tail] = 0;
>> >> >> + }
>> >> >> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
>> >> >> + priv->tx_skb[tx_tail] = NULL;
>> >> >> + tx_tail = TX_NEXT(tx_tail);
>> >> >> + priv->tx_count--;
>> >> >> + }
>> >> >> + priv->tx_tail = tx_tail;
>> >> >> +}
>> >> >
>> >> > I think you still need to find a solution to ensure that the tx reclaim is
>> >> > called eventually through a method other than start_xmit.
>> >>
>> >> In the iperf stress test, if move reclaim to poll, there is some
>> >> error, sometimes sending zero packets.
>> >> While keep reclaim in the xmit to reclaim transmitted packets looks
>> >> stable in the test,
>> >> There TX_DESC_NUM desc can be used.
>> >
>> > What I meant is that you need a correct implementation, presumably
>> > you added a bug when you moved the function to poll(), and also you
>> > forgot to add a timer.
>>
>> Using a timer to ensure completion of TX packets is a trick that
>> worked in the past, but now that the networking stack got smarter,
>> this might artificially increase the processing time of packets in the
>> transmit path, and this will defeat features like TCP small queues
>> etc.. as could be seen with the mvneta driver [1]. The best way really
>> is to rely on TX completion interrupts when those exist as they cannot
>> lie about the hardware status (in theory) and they should provide the
>> fastest way to complete TX packets.
>
> By as Zhangfei Gao pointed out, this hardware does not have a working
> TX completion interrupt. Using timers to do this has always just been
> a workaround for broken hardware IMHO.
Ok, well that's really unfortunate, to achieve the best of everything,
the workaround should probably look like:
- keep reclaiming TX buffers in ndo_start_xmit() in case you push more
packets to the NICs than your timer can free
- reclaim TX buffers in NAPI poll() context for "symetrical" workloads
where e.g: TCP ACKs received allow you to complete TX buffers
- have a timer like you suggest which should help with transmit only
workloads at a slow rate
--
Florian
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:16 ` Florian Fainelli
@ 2014-03-25 17:57 ` Arnd Bergmann
2014-03-26 9:55 ` David Laight
0 siblings, 1 reply; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-25 17:57 UTC (permalink / raw)
To: Florian Fainelli
Cc: Mark Rutland, devicetree@vger.kernel.org,
Russell King - ARM Linux, Sergei Shtylyov, netdev, Zhangfei Gao,
Zhangfei Gao, David S. Miller, linux-arm-kernel
On Tuesday 25 March 2014 10:16:28 Florian Fainelli wrote:
>
> Ok, well that's really unfortunate, to achieve the best of everything,
> the workaround should probably look like:
>
> - keep reclaiming TX buffers in ndo_start_xmit() in case you push more
> packets to the NICs than your timer can free
> - reclaim TX buffers in NAPI poll() context for "symetrical" workloads
> where e.g: TCP ACKs received allow you to complete TX buffers
> - have a timer like you suggest which should help with transmit only
> workloads at a slow rate
Yes, that is what I was thinking, but with orphaning the tx skbs,
we can probably be a little smarter. Note that in order to check
the state of the queue, we have to do a read from uncached memory,
since the hardware also doesn't support cache coherent DMA.
We don't want to do that too often.
Arnd
^ permalink raw reply [flat|nested] 64+ messages in thread
* RE: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:57 ` Arnd Bergmann
@ 2014-03-26 9:55 ` David Laight
0 siblings, 0 replies; 64+ messages in thread
From: David Laight @ 2014-03-26 9:55 UTC (permalink / raw)
To: 'Arnd Bergmann', Florian Fainelli
Cc: Zhangfei Gao, linux-arm-kernel, Mark Rutland,
devicetree@vger.kernel.org, Russell King - ARM Linux,
Sergei Shtylyov, netdev, Zhangfei Gao, David S. Miller
From: Arnd Bergmann
> On Tuesday 25 March 2014 10:16:28 Florian Fainelli wrote:
> >
> > Ok, well that's really unfortunate, to achieve the best of everything,
> > the workaround should probably look like:
> >
> > - keep reclaiming TX buffers in ndo_start_xmit() in case you push more
> > packets to the NICs than your timer can free
> > - reclaim TX buffers in NAPI poll() context for "symetrical" workloads
> > where e.g: TCP ACKs received allow you to complete TX buffers
> > - have a timer like you suggest which should help with transmit only
> > workloads at a slow rate
>
> Yes, that is what I was thinking, but with orphaning the tx skbs,
> we can probably be a little smarter. Note that in order to check
> the state of the queue, we have to do a read from uncached memory,
> since the hardware also doesn't support cache coherent DMA.
> We don't want to do that too often.
Possibly you can check for all the pending transmits having
completed - the most likely case. Instead of checking them
individually?
You should limit the number of tx bytes as well as the number
of tx frames - buffering a ring full of large frames subverts
some of the algorithms higher up the stack.
David
^ permalink raw reply [flat|nested] 64+ messages in thread
* RE: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:05 ` Arnd Bergmann
2014-03-25 17:16 ` Florian Fainelli
@ 2014-03-25 17:17 ` David Laight
2014-03-25 17:21 ` Eric Dumazet
2 siblings, 0 replies; 64+ messages in thread
From: David Laight @ 2014-03-25 17:17 UTC (permalink / raw)
To: 'Arnd Bergmann', Florian Fainelli
Cc: Zhangfei Gao, linux-arm-kernel, Mark Rutland,
devicetree@vger.kernel.org, Russell King - ARM Linux,
Sergei Shtylyov, netdev, Zhangfei Gao, David S. Miller
From: Arnd Bergmann
> > Using a timer to ensure completion of TX packets is a trick that
> > worked in the past, but now that the networking stack got smarter,
> > this might artificially increase the processing time of packets in the
> > transmit path, and this will defeat features like TCP small queues
> > etc.. as could be seen with the mvneta driver [1]. The best way really
> > is to rely on TX completion interrupts when those exist as they cannot
> > lie about the hardware status (in theory) and they should provide the
> > fastest way to complete TX packets.
>
> By as Zhangfei Gao pointed out, this hardware does not have a working
> TX completion interrupt. Using timers to do this has always just been
> a workaround for broken hardware IMHO.
I remember disabling the 'tx done' interrupt (unless the tx ring
was full) in order to get a significant increase in throughput
due to the reduced interrupt load.
The 'interrupt mitigation' logic on modern hardware probably makes
this less of a problem.
It might be possible to orphan the skb when they are put into the
tx ring, and to significantly limit the number of bytes in the
tx ring (BQL?).
That might upset TCP small queues less than delaying the actual
tx completions.
David
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:05 ` Arnd Bergmann
2014-03-25 17:16 ` Florian Fainelli
2014-03-25 17:17 ` David Laight
@ 2014-03-25 17:21 ` Eric Dumazet
2014-03-25 17:54 ` Arnd Bergmann
2 siblings, 1 reply; 64+ messages in thread
From: Eric Dumazet @ 2014-03-25 17:21 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Mark Rutland, devicetree@vger.kernel.org, Florian Fainelli,
Russell King - ARM Linux, Sergei Shtylyov, netdev, Zhangfei Gao,
Zhangfei Gao, David S. Miller, linux-arm-kernel
On Tue, 2014-03-25 at 18:05 +0100, Arnd Bergmann wrote:
> On Tuesday 25 March 2014 10:00:30 Florian Fainelli wrote:
> > 2014-03-25 1:12 GMT-07:00 Arnd Bergmann <arnd@arndb.de>:
> > > On Tuesday 25 March 2014 12:06:31 Zhangfei Gao wrote:
> > >> Dear Arnd
> > >>
> > >> On Mon, Mar 24, 2014 at 11:18 PM, Arnd Bergmann <arnd@arndb.de> wrote:
> > >> > On Monday 24 March 2014 22:14:56 Zhangfei Gao wrote:
> > >> >
...
> > >> > I think you still need to find a solution to ensure that the tx reclaim is
> > >> > called eventually through a method other than start_xmit.
> > >>
> > >> In the iperf stress test, if move reclaim to poll, there is some
> > >> error, sometimes sending zero packets.
> > >> While keep reclaim in the xmit to reclaim transmitted packets looks
> > >> stable in the test,
> > >> There TX_DESC_NUM desc can be used.
> > >
> > > What I meant is that you need a correct implementation, presumably
> > > you added a bug when you moved the function to poll(), and also you
> > > forgot to add a timer.
> >
> > Using a timer to ensure completion of TX packets is a trick that
> > worked in the past, but now that the networking stack got smarter,
> > this might artificially increase the processing time of packets in the
> > transmit path, and this will defeat features like TCP small queues
> > etc.. as could be seen with the mvneta driver [1]. The best way really
> > is to rely on TX completion interrupts when those exist as they cannot
> > lie about the hardware status (in theory) and they should provide the
> > fastest way to complete TX packets.
>
> By as Zhangfei Gao pointed out, this hardware does not have a working
> TX completion interrupt. Using timers to do this has always just been
> a workaround for broken hardware IMHO.
For this kind of drivers, calling skb_orphan() from ndo_start_xmit() is
mandatory.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:21 ` Eric Dumazet
@ 2014-03-25 17:54 ` Arnd Bergmann
2014-03-27 12:53 ` zhangfei
0 siblings, 1 reply; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-25 17:54 UTC (permalink / raw)
To: Eric Dumazet
Cc: Florian Fainelli, Zhangfei Gao, linux-arm-kernel, Mark Rutland,
devicetree@vger.kernel.org, Russell King - ARM Linux,
Sergei Shtylyov, netdev, Zhangfei Gao, David S. Miller
On Tuesday 25 March 2014 10:21:42 Eric Dumazet wrote:
> On Tue, 2014-03-25 at 18:05 +0100, Arnd Bergmann wrote:
> > On Tuesday 25 March 2014 10:00:30 Florian Fainelli wrote:
> >
> > > Using a timer to ensure completion of TX packets is a trick that
> > > worked in the past, but now that the networking stack got smarter,
> > > this might artificially increase the processing time of packets in the
> > > transmit path, and this will defeat features like TCP small queues
> > > etc.. as could be seen with the mvneta driver [1]. The best way really
> > > is to rely on TX completion interrupts when those exist as they cannot
> > > lie about the hardware status (in theory) and they should provide the
> > > fastest way to complete TX packets.
> >
> > By as Zhangfei Gao pointed out, this hardware does not have a working
> > TX completion interrupt. Using timers to do this has always just been
> > a workaround for broken hardware IMHO.
>
> For this kind of drivers, calling skb_orphan() from ndo_start_xmit() is
> mandatory.
Cool, thanks for the information, I was wondering already if there was
a way to deal with hardware like this.
Arnd
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-25 17:54 ` Arnd Bergmann
@ 2014-03-27 12:53 ` zhangfei
0 siblings, 0 replies; 64+ messages in thread
From: zhangfei @ 2014-03-27 12:53 UTC (permalink / raw)
To: Arnd Bergmann, Eric Dumazet
Cc: Florian Fainelli, Zhangfei Gao, linux-arm-kernel, Mark Rutland,
devicetree@vger.kernel.org, Russell King - ARM Linux,
Sergei Shtylyov, netdev, David S. Miller
On 03/26/2014 01:54 AM, Arnd Bergmann wrote:
> On Tuesday 25 March 2014 10:21:42 Eric Dumazet wrote:
>> On Tue, 2014-03-25 at 18:05 +0100, Arnd Bergmann wrote:
>>> On Tuesday 25 March 2014 10:00:30 Florian Fainelli wrote:
>>>
>>>> Using a timer to ensure completion of TX packets is a trick that
>>>> worked in the past, but now that the networking stack got smarter,
>>>> this might artificially increase the processing time of packets in the
>>>> transmit path, and this will defeat features like TCP small queues
>>>> etc.. as could be seen with the mvneta driver [1]. The best way really
>>>> is to rely on TX completion interrupts when those exist as they cannot
>>>> lie about the hardware status (in theory) and they should provide the
>>>> fastest way to complete TX packets.
>>>
>>> By as Zhangfei Gao pointed out, this hardware does not have a working
>>> TX completion interrupt. Using timers to do this has always just been
>>> a workaround for broken hardware IMHO.
>>
>> For this kind of drivers, calling skb_orphan() from ndo_start_xmit() is
>> mandatory.
>
> Cool, thanks for the information, I was wondering already if there was
> a way to deal with hardware like this.
>
That's great,
In the experiment, keeping reclaim in the ndo_start_xmit always get the
best throughput, also simpler, even no requirement of spin_lock.
By the way, still have confusion about build_skb.
At first, I thought we can malloc n*buffers as a ring and keep using
them for dma, every time when packet coming, using build_skb adds a
head, send to upper layer.
After data is consumed, we can continue reuse the buffer next time.
However, in the iperf stress test, always error happen.
The buffer is released in fact, and we need alloc new buffer for the
next transfer.
So the build_skb is not used for reusing buffers, but only for keeping
hot data in cache, right?
Thanks
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-24 14:14 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
[not found] ` <1395670496-17381-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
@ 2014-03-24 16:32 ` Florian Fainelli
2014-03-24 17:23 ` Arnd Bergmann
2014-03-27 6:27 ` Zhangfei Gao
1 sibling, 2 replies; 64+ messages in thread
From: Florian Fainelli @ 2014-03-24 16:32 UTC (permalink / raw)
To: Zhangfei Gao
Cc: David Miller, Russell King, Arnd Bergmann, Sergei Shtylyov,
Mark Rutland, linux-arm-kernel@lists.infradead.org, netdev,
devicetree@vger.kernel.org
2014-03-24 7:14 GMT-07:00 Zhangfei Gao <zhangfei.gao@linaro.org>:
> Support Hisilicon hip04 ethernet driver, including 100M / 1000M controller
>
> Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
> ---
> drivers/net/ethernet/hisilicon/Makefile | 2 +-
> drivers/net/ethernet/hisilicon/hip04_eth.c | 728 ++++++++++++++++++++++++++++
> 2 files changed, 729 insertions(+), 1 deletion(-)
> create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
[snip]
> +static void hip04_config_port(struct hip04_priv *priv, u32 speed, u32 duplex)
> +{
> + u32 val;
> +
> + priv->speed = speed;
> + priv->duplex = duplex;
> +
> + switch (priv->phy_mode) {
> + case PHY_INTERFACE_MODE_SGMII:
> + if (speed == SPEED_1000)
> + val = 8;
> + else
> + val = 7;
> + break;
> + case PHY_INTERFACE_MODE_MII:
> + val = 1; /* SPEED_100 */
> + break;
> + default:
> + val = 0;
> + break;
Is 0 valid for e.g: 10Mbits/sec, regardless of the phy_mode?
[snip]
> +
> +static void hip04_mac_enable(struct net_device *ndev, bool enable)
> +{
> + struct hip04_priv *priv = netdev_priv(ndev);
> + u32 val;
> +
> + if (enable) {
> + /* enable tx & rx */
> + val = readl_relaxed(priv->base + GE_PORT_EN);
> + val |= BIT(1); /* rx*/
> + val |= BIT(2); /* tx*/
> + writel_relaxed(val, priv->base + GE_PORT_EN);
> +
> + /* enable interrupt */
> + priv->reg_inten = DEF_INT_MASK;
> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
> +
> + /* clear rx int */
> + val = RCV_INT;
> + writel_relaxed(val, priv->base + PPE_RINT);
Should not you first clear the interrupt and then DEF_INT_MASK? Why is
there a RCV_INT applied to PPE_RINT register in the enable path, but
there is no such thing in the "disable" branch of your function?
> +
> + /* config recv int*/
> + val = BIT(6); /* int threshold 1 package */
> + val |= 0x4; /* recv timeout */
> + writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT);
> + } else {
> + /* disable int */
> + priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
> +
> + /* disable tx & rx */
> + val = readl_relaxed(priv->base + GE_PORT_EN);
> + val &= ~(BIT(1)); /* rx*/
> + val &= ~(BIT(2)); /* tx*/
> + writel_relaxed(val, priv->base + GE_PORT_EN);
> + }
There is little to no sharing between the two branches, I would have
created separate helper functions for the enable/disable logic.
> +}
> +
> +static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys)
> +{
> + writel(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR);
This is not 64-bits/LPAE safe, do you have a High address part and a
Low address part for your address in the buffer descriptor address, if
so, better use it now.
[snip]
> +
> +static int hip04_rx_poll(struct napi_struct *napi, int budget)
> +{
> + struct hip04_priv *priv = container_of(napi, struct hip04_priv, napi);
> + struct net_device *ndev = priv->ndev;
> + struct net_device_stats *stats = &ndev->stats;
> + unsigned int cnt = hip04_recv_cnt(priv);
> + struct sk_buff *skb;
> + struct rx_desc *desc;
> + unsigned char *buf;
> + dma_addr_t phys;
> + int rx = 0;
> + u16 len;
> + u32 err;
> +
> + while (cnt) {
> + buf = priv->rx_buf[priv->rx_head];
> + skb = build_skb(buf, priv->rx_buf_size);
> + if (unlikely(!skb))
> + net_dbg_ratelimited("build_skb failed\n");
> +
> + dma_unmap_single(&ndev->dev, priv->rx_phys[priv->rx_head],
> + RX_BUF_SIZE, DMA_FROM_DEVICE);
> + priv->rx_phys[priv->rx_head] = 0;
> +
> + desc = (struct rx_desc *)skb->data;
> + len = be16_to_cpu(desc->pkt_len);
> + err = be32_to_cpu(desc->pkt_err);
> +
> + if (len > RX_BUF_SIZE)
> + len = RX_BUF_SIZE;
> + if (0 == len)
> + break;
Should not this be a continue? This is an error packet, so you should
keep on processing the others, or does this have a special meaning?
> +
> + if (err & RX_PKT_ERR) {
> + dev_kfree_skb_any(skb);
> + stats->rx_dropped++;
> + stats->rx_errors++;
> + continue;
> + }
> +
> + stats->rx_packets++;
> + stats->rx_bytes += len;
> +
> + skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
> + skb_put(skb, len);
> + skb->protocol = eth_type_trans(skb, ndev);
> + napi_gro_receive(&priv->napi, skb);
> +
> + buf = netdev_alloc_frag(priv->rx_buf_size);
> + if (!buf)
> + return -ENOMEM;
> + phys = dma_map_single(&ndev->dev, buf,
> + RX_BUF_SIZE, DMA_FROM_DEVICE);
Missing dma_mapping_error() check here.
> + priv->rx_buf[priv->rx_head] = buf;
> + priv->rx_phys[priv->rx_head] = phys;
> + hip04_set_recv_desc(priv, phys);
> +
> + priv->rx_head = RX_NEXT(priv->rx_head);
> + if (rx++ >= budget)
> + break;
> +
> + if (--cnt == 0)
> + cnt = hip04_recv_cnt(priv);
> + }
> +
> + if (rx < budget) {
> + napi_complete(napi);
> +
> + /* enable rx interrupt */
> + priv->reg_inten |= RCV_INT | RCV_NOBUF;
> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
> + }
> +
> + return rx;
> +}
> +
> +static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id)
> +{
> + struct net_device *ndev = (struct net_device *) dev_id;
> + struct hip04_priv *priv = netdev_priv(ndev);
> + u32 ists = readl_relaxed(priv->base + PPE_INTSTS);
> + u32 val = DEF_INT_MASK;
> +
> + writel_relaxed(val, priv->base + PPE_RINT);
> +
> + if (ists & (RCV_INT | RCV_NOBUF)) {
> + if (napi_schedule_prep(&priv->napi)) {
> + /* disable rx interrupt */
> + priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
> + __napi_schedule(&priv->napi);
> + }
> + }
You should also process TX completion interrupts here
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> +{
> + struct hip04_priv *priv = netdev_priv(ndev);
> + unsigned tx_head = priv->tx_head;
> + unsigned tx_tail = priv->tx_tail;
> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
> +
> + while (tx_tail != tx_head) {
> + if (desc->send_addr != 0) {
> + if (force)
> + desc->send_addr = 0;
> + else
> + break;
> + }
> + if (priv->tx_phys[tx_tail]) {
> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
> + priv->tx_phys[tx_tail] = 0;
> + }
> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
dev_kfree_skb_irq() bypasses all sort of SKB tracking, you might want
to use kfree_skb() here instead.
> + priv->tx_skb[tx_tail] = NULL;
> + tx_tail = TX_NEXT(tx_tail);
> + priv->tx_count--;
> + }
> + priv->tx_tail = tx_tail;
> +}
> +
> +static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> + struct hip04_priv *priv = netdev_priv(ndev);
> + struct net_device_stats *stats = &ndev->stats;
> + unsigned int tx_head = priv->tx_head;
> + struct tx_desc *desc = &priv->tx_desc[tx_head];
> + dma_addr_t phys;
> +
> + hip04_tx_reclaim(ndev, false);
> +
> + if (priv->tx_count++ >= TX_DESC_NUM) {
> + net_dbg_ratelimited("no TX space for packet\n");
> + netif_stop_queue(ndev);
> + return NETDEV_TX_BUSY;
> + }
> +
> + phys = dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE);
Missing dma_mapping_error() check here
> + priv->tx_skb[tx_head] = skb;
> + priv->tx_phys[tx_head] = phys;
> + desc->send_addr = cpu_to_be32(phys);
> + desc->send_size = cpu_to_be16(skb->len);
> + desc->cfg = cpu_to_be32(DESC_DEF_CFG);
> + phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
> + desc->wb_addr = cpu_to_be32(phys);
Don't we need a barrier here to ensure that all stores are completed
before we hand this descriptor address to hip40_set_xmit_desc() which
should make DMA start processing it?
> + skb_tx_timestamp(skb);
> + hip04_set_xmit_desc(priv, phys);
> + priv->tx_head = TX_NEXT(tx_head);
> +
> + stats->tx_bytes += skb->len;
> + stats->tx_packets++;
You cannot update the transmit stats here, what start_xmit() does it
just queue packets for the DMA engine to process them, but that does
not mean DMA has completed those. You should update statistics in the
tx_reclaim() function.
--
Florian
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-24 16:32 ` Florian Fainelli
@ 2014-03-24 17:23 ` Arnd Bergmann
2014-03-24 17:35 ` Florian Fainelli
2014-03-27 6:27 ` Zhangfei Gao
1 sibling, 1 reply; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-24 17:23 UTC (permalink / raw)
To: Florian Fainelli
Cc: Zhangfei Gao, David Miller, Russell King, Sergei Shtylyov,
Mark Rutland, linux-arm-kernel@lists.infradead.org, netdev,
devicetree@vger.kernel.org
On Monday 24 March 2014 09:32:17 Florian Fainelli wrote:
> > + priv->tx_skb[tx_head] = skb;
> > + priv->tx_phys[tx_head] = phys;
> > + desc->send_addr = cpu_to_be32(phys);
> > + desc->send_size = cpu_to_be16(skb->len);
> > + desc->cfg = cpu_to_be32(DESC_DEF_CFG);
> > + phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
> > + desc->wb_addr = cpu_to_be32(phys);
>
> Don't we need a barrier here to ensure that all stores are completed
> before we hand this descriptor address to hip40_set_xmit_desc() which
> should make DMA start processing it?
I would think the writel() in set_xmit_desc() implies the barrier.
Arnd
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-24 17:23 ` Arnd Bergmann
@ 2014-03-24 17:35 ` Florian Fainelli
0 siblings, 0 replies; 64+ messages in thread
From: Florian Fainelli @ 2014-03-24 17:35 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Mark Rutland, devicetree@vger.kernel.org, Russell King,
Sergei Shtylyov, netdev, Zhangfei Gao, David Miller,
linux-arm-kernel@lists.infradead.org
2014-03-24 10:23 GMT-07:00 Arnd Bergmann <arnd@arndb.de>:
> On Monday 24 March 2014 09:32:17 Florian Fainelli wrote:
>> > + priv->tx_skb[tx_head] = skb;
>> > + priv->tx_phys[tx_head] = phys;
>> > + desc->send_addr = cpu_to_be32(phys);
>> > + desc->send_size = cpu_to_be16(skb->len);
>> > + desc->cfg = cpu_to_be32(DESC_DEF_CFG);
>> > + phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
>> > + desc->wb_addr = cpu_to_be32(phys);
>>
>> Don't we need a barrier here to ensure that all stores are completed
>> before we hand this descriptor address to hip40_set_xmit_desc() which
>> should make DMA start processing it?
>
> I would think the writel() in set_xmit_desc() implies the barrier.
Right, which means that this should be properly documented to make
sure that this simplification is well understood and produces the
expected result.
--
Florian
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-24 16:32 ` Florian Fainelli
2014-03-24 17:23 ` Arnd Bergmann
@ 2014-03-27 6:27 ` Zhangfei Gao
1 sibling, 0 replies; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-27 6:27 UTC (permalink / raw)
To: Florian Fainelli
Cc: Zhangfei Gao, Mark Rutland, devicetree@vger.kernel.org,
Russell King, Sergei Shtylyov, Arnd Bergmann, netdev,
David Miller, linux-arm-kernel@lists.infradead.org
Dear Florian
Thanks for the kind suggestion.
On Tue, Mar 25, 2014 at 12:32 AM, Florian Fainelli <f.fainelli@gmail.com> wrote:
> 2014-03-24 7:14 GMT-07:00 Zhangfei Gao <zhangfei.gao@linaro.org>:
>> Support Hisilicon hip04 ethernet driver, including 100M / 1000M controller
>>
>> Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
>> ---
>> drivers/net/ethernet/hisilicon/Makefile | 2 +-
>> drivers/net/ethernet/hisilicon/hip04_eth.c | 728 ++++++++++++++++++++++++++++
>> 2 files changed, 729 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
>
> [snip]
>
>> +static void hip04_config_port(struct hip04_priv *priv, u32 speed, u32 duplex)
>> +{
>> + u32 val;
>> +
>> + priv->speed = speed;
>> + priv->duplex = duplex;
>> +
>> + switch (priv->phy_mode) {
>> + case PHY_INTERFACE_MODE_SGMII:
>> + if (speed == SPEED_1000)
>> + val = 8;
>> + else
>> + val = 7;
>> + break;
>> + case PHY_INTERFACE_MODE_MII:
>> + val = 1; /* SPEED_100 */
>> + break;
>> + default:
>> + val = 0;
>> + break;
>
> Is 0 valid for e.g: 10Mbits/sec, regardless of the phy_mode?
0 is only 10M for MII mode, will add warning here.
switch (priv->phy_mode) {
case PHY_INTERFACE_MODE_SGMII:
if (speed == SPEED_1000)
val = 8;
else if (speed == SPEED_100)
val = 7;
else
val = 6; /* SPEED_10 */
break;
case PHY_INTERFACE_MODE_MII:
if (speed == SPEED_100)
val = 1;
else
val = 0; /* SPEED_10 */
break;
default:
netdev_warn(ndev, "not supported mode\n");
val = 0;
break;
}
>> +
>> +static void hip04_mac_enable(struct net_device *ndev, bool enable)
>> +{
>> + struct hip04_priv *priv = netdev_priv(ndev);
>> + u32 val;
>> +
>> + if (enable) {
>> + /* enable tx & rx */
>> + val = readl_relaxed(priv->base + GE_PORT_EN);
>> + val |= BIT(1); /* rx*/
>> + val |= BIT(2); /* tx*/
>> + writel_relaxed(val, priv->base + GE_PORT_EN);
>> +
>> + /* enable interrupt */
>> + priv->reg_inten = DEF_INT_MASK;
>> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
>> +
>> + /* clear rx int */
>> + val = RCV_INT;
>> + writel_relaxed(val, priv->base + PPE_RINT);
>
> Should not you first clear the interrupt and then DEF_INT_MASK? Why is
OK, got it.
> there a RCV_INT applied to PPE_RINT register in the enable path, but
> there is no such thing in the "disable" branch of your function?
This required here since setting the following cmd, /* config recv int*/
Otherwise, the setting does not take effect.
>
>> +
>> + /* config recv int*/
>> + val = BIT(6); /* int threshold 1 package */
>> + val |= 0x4; /* recv timeout */
>> + writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT);
>> + } else {
>> + /* disable int */
>> + priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
>> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
>> +
>> + /* disable tx & rx */
>> + val = readl_relaxed(priv->base + GE_PORT_EN);
>> + val &= ~(BIT(1)); /* rx*/
>> + val &= ~(BIT(2)); /* tx*/
>> + writel_relaxed(val, priv->base + GE_PORT_EN);
>> + }
>
> There is little to no sharing between the two branches, I would have
> created separate helper functions for the enable/disable logic.
OK, got it.
>
>> +}
>> +
>> +static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys)
>> +{
>> + writel(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR);
>
> This is not 64-bits/LPAE safe, do you have a High address part and a
> Low address part for your address in the buffer descriptor address, if
> so, better use it now.
Unfortunately it is true, only 32bytes for this controller on A15.
Bits [33:32] of desc can be set in [5:4], but it may be ignored,
RX register is only have 32bits too.
So the controller is only for 32 bits.
The next version can be used on 64bits, and there is high address part.
Still not get spec yet.
>> +
>> +static int hip04_rx_poll(struct napi_struct *napi, int budget)
>> +{
>> + struct hip04_priv *priv = container_of(napi, struct hip04_priv, napi);
>> + struct net_device *ndev = priv->ndev;
>> + struct net_device_stats *stats = &ndev->stats;
>> + unsigned int cnt = hip04_recv_cnt(priv);
>> + struct sk_buff *skb;
>> + struct rx_desc *desc;
>> + unsigned char *buf;
>> + dma_addr_t phys;
>> + int rx = 0;
>> + u16 len;
>> + u32 err;
>> +
>> + while (cnt) {
>> + buf = priv->rx_buf[priv->rx_head];
>> + skb = build_skb(buf, priv->rx_buf_size);
>> + if (unlikely(!skb))
>> + net_dbg_ratelimited("build_skb failed\n");
>> +
>> + dma_unmap_single(&ndev->dev, priv->rx_phys[priv->rx_head],
>> + RX_BUF_SIZE, DMA_FROM_DEVICE);
>> + priv->rx_phys[priv->rx_head] = 0;
>> +
>> + desc = (struct rx_desc *)skb->data;
>> + len = be16_to_cpu(desc->pkt_len);
>> + err = be32_to_cpu(desc->pkt_err);
>> +
>> + if (len > RX_BUF_SIZE)
>> + len = RX_BUF_SIZE;
>> + if (0 == len)
>> + break;
>
> Should not this be a continue? This is an error packet, so you should
> keep on processing the others, or does this have a special meaning?
len=0 indicates the last packet.
Will change the behavior here.
if (0 == len) {
dev_kfree_skb_any(skb);
last = true;
} else if (err & RX_PKT_ERR) {
dev_kfree_skb_any(skb);
stats->rx_dropped++;
stats->rx_errors++;
} else {
skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
skb_put(skb, len);
skb->protocol = eth_type_trans(skb, ndev);
napi_gro_receive(&priv->napi, skb);
stats->rx_packets++;
stats->rx_bytes += len;
}
>
>> +
>> + if (err & RX_PKT_ERR) {
>> + dev_kfree_skb_any(skb);
>> + stats->rx_dropped++;
>> + stats->rx_errors++;
>> + continue;
>> + }
>> +
>> + stats->rx_packets++;
>> + stats->rx_bytes += len;
>> +
>> + skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
>> + skb_put(skb, len);
>> + skb->protocol = eth_type_trans(skb, ndev);
>> + napi_gro_receive(&priv->napi, skb);
>> +
>> + buf = netdev_alloc_frag(priv->rx_buf_size);
>> + if (!buf)
>> + return -ENOMEM;
>> + phys = dma_map_single(&ndev->dev, buf,
>> + RX_BUF_SIZE, DMA_FROM_DEVICE);
>
> Missing dma_mapping_error() check here.
Yes, thanks
>
>> + priv->rx_buf[priv->rx_head] = buf;
>> + priv->rx_phys[priv->rx_head] = phys;
>> + hip04_set_recv_desc(priv, phys);
>> +
>> + priv->rx_head = RX_NEXT(priv->rx_head);
>> + if (rx++ >= budget)
>> + break;
>> +
>> + if (--cnt == 0)
>> + cnt = hip04_recv_cnt(priv);
>
>> + }
>> +
>> + if (rx < budget) {
>> + napi_complete(napi);
>> +
>> + /* enable rx interrupt */
>> + priv->reg_inten |= RCV_INT | RCV_NOBUF;
>> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
>> + }
>> +
>> + return rx;
>> +}
>> +
>> +static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id)
>> +{
>> + struct net_device *ndev = (struct net_device *) dev_id;
>> + struct hip04_priv *priv = netdev_priv(ndev);
>> + u32 ists = readl_relaxed(priv->base + PPE_INTSTS);
>> + u32 val = DEF_INT_MASK;
>> +
>> + writel_relaxed(val, priv->base + PPE_RINT);
>> +
>> + if (ists & (RCV_INT | RCV_NOBUF)) {
>> + if (napi_schedule_prep(&priv->napi)) {
>> + /* disable rx interrupt */
>> + priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
>> + writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
>> + __napi_schedule(&priv->napi);
>> + }
>> + }
>
> You should also process TX completion interrupts here
There is no such interrupt.
>
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
>> +{
>> + struct hip04_priv *priv = netdev_priv(ndev);
>> + unsigned tx_head = priv->tx_head;
>> + unsigned tx_tail = priv->tx_tail;
>> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
>> +
>> + while (tx_tail != tx_head) {
>> + if (desc->send_addr != 0) {
>> + if (force)
>> + desc->send_addr = 0;
>> + else
>> + break;
>> + }
>> + if (priv->tx_phys[tx_tail]) {
>> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
>> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
>> + priv->tx_phys[tx_tail] = 0;
>> + }
>> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
>
> dev_kfree_skb_irq() bypasses all sort of SKB tracking, you might want
> to use kfree_skb() here instead.
OK, will use dev_kfree_skb instead.
>
>> + priv->tx_skb[tx_tail] = NULL;
>> + tx_tail = TX_NEXT(tx_tail);
>> + priv->tx_count--;
>> + }
>> + priv->tx_tail = tx_tail;
>> +}
>> +
>> +static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
>> +{
>> + struct hip04_priv *priv = netdev_priv(ndev);
>> + struct net_device_stats *stats = &ndev->stats;
>> + unsigned int tx_head = priv->tx_head;
>> + struct tx_desc *desc = &priv->tx_desc[tx_head];
>> + dma_addr_t phys;
>> +
>> + hip04_tx_reclaim(ndev, false);
>> +
>> + if (priv->tx_count++ >= TX_DESC_NUM) {
>> + net_dbg_ratelimited("no TX space for packet\n");
>> + netif_stop_queue(ndev);
>> + return NETDEV_TX_BUSY;
>> + }
>> +
>> + phys = dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE);
>
> Missing dma_mapping_error() check here
>
>> + priv->tx_skb[tx_head] = skb;
>> + priv->tx_phys[tx_head] = phys;
>> + desc->send_addr = cpu_to_be32(phys);
>> + desc->send_size = cpu_to_be16(skb->len);
>> + desc->cfg = cpu_to_be32(DESC_DEF_CFG);
>> + phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
>> + desc->wb_addr = cpu_to_be32(phys);
>
> Don't we need a barrier here to ensure that all stores are completed
> before we hand this descriptor address to hip40_set_xmit_desc() which
> should make DMA start processing it?
>
>> + skb_tx_timestamp(skb);
>> + hip04_set_xmit_desc(priv, phys);
>> + priv->tx_head = TX_NEXT(tx_head);
>> +
>> + stats->tx_bytes += skb->len;
>> + stats->tx_packets++;
>
> You cannot update the transmit stats here, what start_xmit() does it
> just queue packets for the DMA engine to process them, but that does
> not mean DMA has completed those. You should update statistics in the
> tx_reclaim() function.
Yes, however, since no TX complete interrupt, tx_reclaim may be called
rather late, it may be more suitable to put here.
Thanks
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH v2 0/3] add hisilicon hip04 ethernet driver
@ 2014-03-21 15:09 Zhangfei Gao
2014-03-21 15:09 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-21 15:09 UTC (permalink / raw)
To: David S. Miller, linux-lFZ/pmaqli7XmaaqVzeoHQ, arnd-r2nGTMty4D4,
f.fainelli-Re5JQEeQqe8AvxtiuMwx3w, mark.rutland-5wv7dgnIgG8,
sergei.shtylyov-M4DtvfQ/ZS1MRgGoP+s0PdBPR1lH4CV8
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
netdev-u79uwXL29TY76Z2rM5mHXA, devicetree-u79uwXL29TY76Z2rM5mHXA,
Zhangfei Gao
V2:
Got many suggestions from Russell, Arnd, Florian, Mark and Sergei
Remove memcpy, use dma_map/unmap_single, use dma_alloc_coherent rather than dma_pool, etc.
Refer property in ethernet.txt, change ppe description, etc.
Zhangfei Gao (3):
Documentation: add Device tree bindings for Hisilicon hip04 ethernet
net: hisilicon: new hip04 MDIO driver
net: hisilicon: new hip04 ethernet driver
.../bindings/net/hisilicon-hip04-net.txt | 107 +++
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 31 +
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_eth.c | 730 ++++++++++++++++++++
drivers/net/ethernet/hisilicon/hip04_mdio.c | 190 +++++
7 files changed, 1065 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-21 15:09 [PATCH v2 0/3] add hisilicon " Zhangfei Gao
@ 2014-03-21 15:09 ` Zhangfei Gao
2014-03-21 15:27 ` Arnd Bergmann
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-21 15:09 UTC (permalink / raw)
To: David S. Miller, linux, arnd, f.fainelli, mark.rutland,
sergei.shtylyov
Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
Support Hisilicon hip04 ethernet driver, including 100M / 1000M controller
Signed-off-by: Zhangfei Gao <zhangfei.gao@linaro.org>
---
drivers/net/ethernet/hisilicon/Makefile | 2 +-
drivers/net/ethernet/hisilicon/hip04_eth.c | 730 ++++++++++++++++++++++++++++
2 files changed, 731 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
diff --git a/drivers/net/ethernet/hisilicon/Makefile b/drivers/net/ethernet/hisilicon/Makefile
index 1d6eb6e..e6fe7af 100644
--- a/drivers/net/ethernet/hisilicon/Makefile
+++ b/drivers/net/ethernet/hisilicon/Makefile
@@ -2,4 +2,4 @@
# Makefile for the HISILICON network device drivers.
#
-obj-$(CONFIG_HIP04_ETH) += hip04_mdio.o
+obj-$(CONFIG_HIP04_ETH) += hip04_eth.o hip04_mdio.o
diff --git a/drivers/net/ethernet/hisilicon/hip04_eth.c b/drivers/net/ethernet/hisilicon/hip04_eth.c
new file mode 100644
index 0000000..1211fb4
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hip04_eth.c
@@ -0,0 +1,730 @@
+
+/* Copyright (c) 2014 Linaro Ltd.
+ * Copyright (c) 2014 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/phy.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+
+#define PPE_CFG_RX_CFF_ADDR 0x100
+#define PPE_CFG_POOL_GRP 0x300
+#define PPE_CFG_RX_BUF_SIZE 0x400
+#define PPE_CFG_RX_FIFO_SIZE 0x500
+#define PPE_CURR_BUF_CNT_REG 0xa200
+
+#define GE_DUPLEX_TYPE 0x8
+#define GE_MAX_FRM_SIZE_REG 0x3c
+#define GE_PORT_MODE 0x40
+#define GE_PORT_EN 0x44
+#define GE_SHORT_RUNTS_THR_REG 0x50
+#define GE_TX_LOCAL_PAGE_REG 0x5c
+#define GE_TRANSMIT_CONTROL_REG 0x60
+#define GE_CF_CRC_STRIP_REG 0x1b0
+#define GE_MODE_CHANGE_EN 0x1b4
+#define GE_RECV_CONTROL_REG 0x1e0
+#define GE_STATION_MAC_ADDRESS 0x210
+#define PPE_CFG_TX_PKT_BD_ADDR 0x420
+#define PPE_CFG_MAX_FRAME_LEN_REG 0x408
+#define PPE_CFG_BUS_CTRL_REG 0x424
+#define PPE_CFG_RX_CTRL_REG 0x428
+#define PPE_CFG_RX_PKT_MODE_REG 0x438
+#define PPE_CFG_QOS_VMID_GEN 0x500
+#define PPE_CFG_RX_PKT_INT 0x538
+#define PPE_INTEN 0x600
+#define PPE_INTSTS 0x608
+#define PPE_RINT 0x604
+#define PPE_CFG_STS_MODE 0x700
+#define PPE_HIS_RX_PKT_CNT 0x804
+
+/* REG_INTERRUPT */
+#define RCV_INT BIT(10)
+#define RCV_NOBUF BIT(8)
+#define DEF_INT_MASK 0x41fdf
+
+#define RX_DESC_NUM 64
+#define TX_DESC_NUM 64
+#define TX_NEXT(N) (((N) + 1) & (TX_DESC_NUM-1))
+#define RX_NEXT(N) (((N) + 1) & (RX_DESC_NUM-1))
+
+#define GMAC_PPE_RX_PKT_MAX_LEN 379
+#define GMAC_MAX_PKT_LEN 1516
+#define DESC_DEF_CFG 0x14
+#define RX_BUF_SIZE 1600
+#define RX_PKT_ERR 0x3
+#define TX_TIMEOUT (6 * HZ)
+
+#define DRV_NAME "hip04-ether"
+
+struct tx_desc {
+ u32 send_addr;
+ u16 reserved_16;
+ u16 send_size;
+ u32 reserved_32;
+ u32 cfg;
+ u32 wb_addr;
+} ____cacheline_aligned;
+
+struct rx_desc {
+ u16 reserved_16;
+ u16 pkt_len;
+ u32 reserve1[3];
+ u32 pkt_err;
+ u32 reserve2[4];
+};
+
+struct hip04_priv {
+ void __iomem *base;
+ int phy_mode;
+ int id;
+ unsigned int port;
+ unsigned int speed;
+ unsigned int duplex;
+ unsigned int reg_inten;
+
+ struct napi_struct napi;
+ struct net_device *ndev;
+
+ struct tx_desc *tx_desc;
+ dma_addr_t tx_desc_dma;
+ struct sk_buff *tx_skb[TX_DESC_NUM];
+ dma_addr_t tx_phys[TX_DESC_NUM];
+ unsigned int tx_head;
+ unsigned int tx_tail;
+ unsigned int tx_count;
+
+ unsigned char *rx_buf[RX_DESC_NUM];
+ dma_addr_t rx_phys[RX_DESC_NUM];
+ unsigned int rx_head;
+ unsigned int rx_buf_size;
+
+ struct device_node *phy_node;
+ struct phy_device *phy;
+};
+
+static void __iomem *ppebase;
+
+static void hip04_config_port(struct hip04_priv *priv, u32 speed, u32 duplex)
+{
+ u32 val;
+
+ priv->speed = speed;
+ priv->duplex = duplex;
+
+ switch (priv->phy_mode) {
+ case PHY_INTERFACE_MODE_SGMII:
+ if (speed == SPEED_1000)
+ val = 8;
+ else
+ val = 7;
+ break;
+ case PHY_INTERFACE_MODE_MII:
+ val = 1; /* SPEED_100 */
+ break;
+ default:
+ val = 0;
+ break;
+ }
+ writel_relaxed(val, priv->base + GE_PORT_MODE);
+
+ val = (duplex) ? BIT(0) : 0;
+ writel_relaxed(val, priv->base + GE_DUPLEX_TYPE);
+
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_MODE_CHANGE_EN);
+}
+
+static void hip04_reset_ppe(struct hip04_priv *priv)
+{
+ u32 val;
+
+ do {
+ val =
+ readl_relaxed(ppebase + priv->port * 4 + PPE_CURR_BUF_CNT_REG);
+ readl_relaxed(ppebase + priv->port * 4 + PPE_CFG_RX_CFF_ADDR);
+ } while (val & 0xfff);
+}
+
+static void hip04_config_fifo(struct hip04_priv *priv)
+{
+ u32 val;
+
+ val = readl_relaxed(priv->base + PPE_CFG_STS_MODE);
+ val |= BIT(12); /* PPE_HIS_RX_PKT_CNT read clear */
+ writel_relaxed(val, priv->base + PPE_CFG_STS_MODE);
+
+ val = BIT(priv->port);
+ writel_relaxed(val, ppebase + priv->port * 4 + PPE_CFG_POOL_GRP);
+
+ val = priv->port << 8;
+ val |= BIT(14);
+ writel_relaxed(val, priv->base + PPE_CFG_QOS_VMID_GEN);
+
+ val = RX_BUF_SIZE;
+ writel_relaxed(val, ppebase + priv->port * 4 + PPE_CFG_RX_BUF_SIZE);
+
+ val = RX_DESC_NUM << 16; /* depth */
+ val |= BIT(11); /* seq: first set first ues */
+ val |= RX_DESC_NUM * priv->id; /* start_addr */
+ writel_relaxed(val, ppebase + priv->port * 4 + PPE_CFG_RX_FIFO_SIZE);
+
+ /* pkt store format */
+ val = NET_IP_ALIGN << 11; /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_CTRL_REG);
+
+ /* following cfg required for 1000M */
+ /* pkt mode */
+ val = BIT(18); /* align */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_MODE_REG);
+
+ /* set bus ctrl */
+ val = BIT(14); /* buffer locally release */
+ val |= BIT(0); /* big endian */
+ writel_relaxed(val, priv->base + PPE_CFG_BUS_CTRL_REG);
+
+ /* set max pkt len, curtail if exceed */
+ val = GMAC_PPE_RX_PKT_MAX_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + PPE_CFG_MAX_FRAME_LEN_REG);
+
+ /* set max len of each pkt */
+ val = GMAC_MAX_PKT_LEN; /* max buffer len */
+ writel_relaxed(val, priv->base + GE_MAX_FRM_SIZE_REG);
+
+ /* set min len of each pkt */
+ val = 31; /* min buffer len */
+ writel_relaxed(val, priv->base + GE_SHORT_RUNTS_THR_REG);
+
+ /* tx */
+ val = readl_relaxed(priv->base + GE_TRANSMIT_CONTROL_REG);
+ val |= BIT(5); /* tx auto neg */
+ val |= BIT(6); /* tx add crc */
+ val |= BIT(7); /* tx short pad through */
+ writel_relaxed(val, priv->base + GE_TRANSMIT_CONTROL_REG);
+
+ /* rx crc */
+ val = BIT(0); /* rx strip crc */
+ writel_relaxed(val, priv->base + GE_CF_CRC_STRIP_REG);
+
+ /* rx */
+ val = readl_relaxed(priv->base + GE_RECV_CONTROL_REG);
+ val |= BIT(3); /* rx strip pad */
+ val |= BIT(4); /* run pkt en */
+ writel_relaxed(val, priv->base + GE_RECV_CONTROL_REG);
+
+ /* auto neg control */
+ val = BIT(0);
+ writel_relaxed(val, priv->base + GE_TX_LOCAL_PAGE_REG);
+}
+
+static void hip04_mac_enable(struct net_device *ndev, bool enable)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 val;
+
+ if (enable) {
+ /* enable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val |= BIT(1); /* rx*/
+ val |= BIT(2); /* tx*/
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+
+ /* enable interrupt */
+ priv->reg_inten = DEF_INT_MASK;
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+
+ /* clear rx int */
+ val = RCV_INT;
+ writel_relaxed(val, priv->base + PPE_RINT);
+
+ /* config recv int*/
+ val = BIT(6); /* int threshold 1 package */
+ val |= 0x4; /* recv timeout */
+ writel_relaxed(val, priv->base + PPE_CFG_RX_PKT_INT);
+ } else {
+ /* disable int */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+
+ /* disable tx & rx */
+ val = readl_relaxed(priv->base + GE_PORT_EN);
+ val &= ~(BIT(1)); /* rx*/
+ val &= ~(BIT(2)); /* tx*/
+ writel_relaxed(val, priv->base + GE_PORT_EN);
+ }
+}
+
+static void hip04_set_xmit_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ writel(phys, priv->base + PPE_CFG_TX_PKT_BD_ADDR);
+}
+
+static void hip04_set_recv_desc(struct hip04_priv *priv, dma_addr_t phys)
+{
+ writel(phys, ppebase + priv->port * 4 + PPE_CFG_RX_CFF_ADDR);
+}
+
+static u32 hip04_recv_cnt(struct hip04_priv *priv)
+{
+ return readl(priv->base + PPE_HIS_RX_PKT_CNT);
+}
+
+static void hip04_update_mac_address(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+
+ writel_relaxed(((ndev->dev_addr[0] << 8) | (ndev->dev_addr[1])),
+ priv->base + GE_STATION_MAC_ADDRESS);
+ writel_relaxed(((ndev->dev_addr[2] << 24) | (ndev->dev_addr[3] << 16) |
+ (ndev->dev_addr[4] << 8) | (ndev->dev_addr[5])),
+ priv->base + GE_STATION_MAC_ADDRESS + 4);
+}
+
+static int hip04_set_mac_address(struct net_device *ndev, void *addr)
+{
+ eth_mac_addr(ndev, addr);
+ hip04_update_mac_address(ndev);
+ return 0;
+}
+
+static int hip04_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct hip04_priv *priv = container_of(napi, struct hip04_priv, napi);
+ struct net_device *ndev = priv->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int cnt = hip04_recv_cnt(priv);
+ struct sk_buff *skb;
+ struct rx_desc *desc;
+ unsigned char *buf;
+ dma_addr_t phys;
+ int rx = 0;
+ u16 len;
+ u32 err;
+
+ while (cnt) {
+ buf = priv->rx_buf[priv->rx_head];
+ skb = build_skb(buf, priv->rx_buf_size);
+ if (unlikely(!skb))
+ net_dbg_ratelimited("build_skb failed\n");
+
+ dma_unmap_single(&ndev->dev, priv->rx_phys[priv->rx_head],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[priv->rx_head] = 0;
+
+ desc = (struct rx_desc *)skb->data;
+ len = be16_to_cpu(desc->pkt_len);
+ err = be32_to_cpu(desc->pkt_err);
+
+ if (len > RX_BUF_SIZE)
+ len = RX_BUF_SIZE;
+ if (0 == len)
+ break;
+
+ if (err & RX_PKT_ERR) {
+ dev_kfree_skb_any(skb);
+ stats->rx_dropped++;
+ stats->rx_errors++;
+ continue;
+ }
+
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+
+ skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
+ skb_put(skb, len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ napi_gro_receive(&priv->napi, skb);
+
+ buf = netdev_alloc_frag(priv->rx_buf_size);
+ if (!buf)
+ return -ENOMEM;
+ phys = dma_map_single(&ndev->dev, buf,
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_buf[priv->rx_head] = buf;
+ priv->rx_phys[priv->rx_head] = phys;
+ hip04_set_recv_desc(priv, phys);
+
+ priv->rx_head = RX_NEXT(priv->rx_head);
+ if (rx++ >= budget)
+ break;
+
+ if (--cnt == 0)
+ cnt = hip04_recv_cnt(priv);
+ }
+
+ if (rx < budget) {
+ napi_complete(napi);
+
+ /* enable rx interrupt */
+ priv->reg_inten |= RCV_INT | RCV_NOBUF;
+ writel(priv->reg_inten, priv->base + PPE_INTEN);
+ }
+
+ return rx;
+}
+
+static irqreturn_t hip04_mac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *) dev_id;
+ struct hip04_priv *priv = netdev_priv(ndev);
+ u32 ists = readl_relaxed(priv->base + PPE_INTSTS);
+ u32 val = DEF_INT_MASK;
+
+ writel_relaxed(val, priv->base + PPE_RINT);
+
+ if (ists & (RCV_INT | RCV_NOBUF)) {
+ if (napi_schedule_prep(&priv->napi)) {
+ /* disable rx interrupt */
+ priv->reg_inten &= ~(RCV_INT | RCV_NOBUF);
+ writel_relaxed(priv->reg_inten, priv->base + PPE_INTEN);
+ __napi_schedule(&priv->napi);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hip04_tx_reclaim(struct net_device *ndev, bool force)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ unsigned tx_head = priv->tx_head;
+ unsigned tx_tail = priv->tx_tail;
+ struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
+
+ while (tx_tail != tx_head) {
+ if (desc->send_addr != 0) {
+ if (force)
+ desc->send_addr = 0;
+ else
+ break;
+ }
+ if (priv->tx_phys[tx_tail]) {
+ dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
+ priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
+ priv->tx_phys[tx_tail] = 0;
+ }
+ dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
+ priv->tx_skb[tx_tail] = NULL;
+ tx_tail = TX_NEXT(tx_tail);
+ priv->tx_count--;
+ }
+ priv->tx_tail = tx_tail;
+}
+
+static int hip04_mac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ unsigned int tx_head = priv->tx_head;
+ struct tx_desc *desc = &priv->tx_desc[tx_head];
+ dma_addr_t phys;
+
+ hip04_tx_reclaim(ndev, false);
+
+ if (priv->tx_count++ >= TX_DESC_NUM) {
+ net_dbg_ratelimited("no TX space for packet\n");
+ netif_stop_queue(ndev);
+ return NETDEV_TX_BUSY;
+ }
+
+ phys = dma_map_single(&ndev->dev, skb->data, skb->len, DMA_TO_DEVICE);
+ priv->tx_skb[tx_head] = skb;
+ priv->tx_phys[tx_head] = phys;
+ desc->send_addr = cpu_to_be32(phys);
+ desc->send_size = cpu_to_be16(skb->len);
+ desc->cfg = cpu_to_be32(DESC_DEF_CFG);
+ phys = priv->tx_desc_dma + tx_head * sizeof(struct tx_desc);
+ desc->wb_addr = cpu_to_be32(phys);
+ skb_tx_timestamp(skb);
+ hip04_set_xmit_desc(priv, phys);
+ priv->tx_head = TX_NEXT(tx_head);
+
+ stats->tx_bytes += skb->len;
+ stats->tx_packets++;
+
+ return NETDEV_TX_OK;
+}
+
+static void hip04_adjust_link(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = priv->phy;
+
+ if ((priv->speed != phy->speed) || (priv->duplex != phy->duplex)) {
+ hip04_config_port(priv, phy->speed, phy->duplex);
+ phy_print_status(phy);
+ }
+}
+
+static int hip04_mac_open(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->rx_head = 0;
+ priv->tx_head = 0;
+ priv->tx_tail = 0;
+ priv->tx_count = 0;
+
+ hip04_reset_ppe(priv);
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ dma_addr_t phys;
+
+ phys = dma_map_single(&ndev->dev, priv->rx_buf[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[i] = phys;
+ hip04_set_recv_desc(priv, phys);
+ }
+
+ if (priv->phy_node) {
+ priv->phy = of_phy_connect(ndev, priv->phy_node,
+ &hip04_adjust_link, 0, priv->phy_mode);
+ if (!priv->phy)
+ return -ENODEV;
+ phy_start(priv->phy);
+ }
+
+ netif_start_queue(ndev);
+ hip04_mac_enable(ndev, true);
+ napi_enable(&priv->napi);
+ return 0;
+}
+
+static int hip04_mac_stop(struct net_device *ndev)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ if (priv->phy)
+ phy_disconnect(priv->phy);
+
+ priv->phy = NULL;
+ napi_disable(&priv->napi);
+ netif_stop_queue(ndev);
+ hip04_mac_enable(ndev, false);
+ hip04_tx_reclaim(ndev, true);
+ hip04_reset_ppe(priv);
+
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ if (priv->rx_phys[i]) {
+ dma_unmap_single(&ndev->dev, priv->rx_phys[i],
+ RX_BUF_SIZE, DMA_FROM_DEVICE);
+ priv->rx_phys[i] = 0;
+ }
+ }
+
+ return 0;
+}
+
+static void hip04_timeout(struct net_device *ndev)
+{
+ netif_wake_queue(ndev);
+ return;
+}
+
+static struct net_device_ops hip04_netdev_ops = {
+ .ndo_open = hip04_mac_open,
+ .ndo_stop = hip04_mac_stop,
+ .ndo_start_xmit = hip04_mac_start_xmit,
+ .ndo_set_mac_address = hip04_set_mac_address,
+ .ndo_tx_timeout = hip04_timeout,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_change_mtu = eth_change_mtu,
+};
+
+static int hip04_alloc_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ priv->tx_desc = dma_alloc_coherent(d,
+ TX_DESC_NUM * sizeof(struct tx_desc),
+ &priv->tx_desc_dma, GFP_KERNEL);
+ if (!priv->tx_desc)
+ return -ENOMEM;
+
+ priv->rx_buf_size = RX_BUF_SIZE +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ for (i = 0; i < RX_DESC_NUM; i++) {
+ priv->rx_buf[i] = netdev_alloc_frag(priv->rx_buf_size);
+ if (!priv->rx_buf[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void hip04_free_ring(struct net_device *ndev, struct device *d)
+{
+ struct hip04_priv *priv = netdev_priv(ndev);
+ int i;
+
+ for (i = 0; i < RX_DESC_NUM; i++)
+ if (priv->rx_buf[i])
+ put_page(virt_to_head_page(priv->rx_buf[i]));
+
+ for (i = 0; i < TX_DESC_NUM; i++)
+ if (priv->tx_skb[i])
+ dev_kfree_skb_any(priv->tx_skb[i]);
+
+ dma_free_coherent(d, TX_DESC_NUM * sizeof(struct tx_desc),
+ priv->tx_desc, priv->tx_desc_dma);
+}
+
+static int hip04_mac_probe(struct platform_device *pdev)
+{
+ struct device *d = &pdev->dev;
+ struct device_node *n, *node = d->of_node;
+ struct net_device *ndev;
+ struct hip04_priv *priv;
+ struct resource *res;
+ unsigned int irq;
+ int ret;
+
+ ndev = alloc_etherdev(sizeof(struct hip04_priv));
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+ platform_set_drvdata(pdev, ndev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(d, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto init_fail;
+ }
+
+ if (!ppebase) {
+ n = of_find_compatible_node(NULL, NULL, "hisilicon,hip04-ppe");
+ if (!n) {
+ ret = -EINVAL;
+ netdev_err(ndev, "not find hisilicon,ppe\n");
+ goto init_fail;
+ }
+ ppebase = of_iomap(n, 0);
+ }
+
+ n = of_parse_phandle(node, "port-handle", 0);
+ if (n) {
+ ret = of_property_read_u32(n, "reg", &priv->port);
+ if (ret) {
+ dev_warn(d, "no reg info\n");
+ goto init_fail;
+ }
+ } else {
+ dev_warn(d, "no port-handle\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ priv->id = of_alias_get_id(node, "ethernet");
+ if (priv->id < 0) {
+ dev_warn(d, "no ethernet alias\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ priv->phy_mode = of_get_phy_mode(node);
+ if (priv->phy_mode < 0) {
+ dev_warn(d, "not find phy-mode\n");
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ ret = -EINVAL;
+ goto init_fail;
+ }
+
+ ether_setup(ndev);
+ ndev->netdev_ops = &hip04_netdev_ops;
+ ndev->watchdog_timeo = TX_TIMEOUT;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+ ndev->irq = irq;
+ netif_napi_add(ndev, &priv->napi, hip04_rx_poll, RX_DESC_NUM);
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ hip04_reset_ppe(priv);
+ if (priv->phy_mode == PHY_INTERFACE_MODE_MII)
+ hip04_config_port(priv, SPEED_100, DUPLEX_FULL);
+
+ hip04_config_fifo(priv);
+ random_ether_addr(ndev->dev_addr);
+ hip04_update_mac_address(ndev);
+
+ ret = hip04_alloc_ring(ndev, d);
+ if (ret) {
+ netdev_err(ndev, "alloc ring fail\n");
+ goto alloc_fail;
+ }
+
+ ret = devm_request_irq(d, irq, hip04_mac_interrupt,
+ 0, pdev->name, ndev);
+ if (ret) {
+ netdev_err(ndev, "devm_request_irq failed\n");
+ goto alloc_fail;
+ }
+
+ priv->phy_node = of_parse_phandle(node, "phy-handle", 0);
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ free_netdev(ndev);
+ goto alloc_fail;
+ }
+
+ return 0;
+alloc_fail:
+ hip04_free_ring(ndev, d);
+init_fail:
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+ return ret;
+}
+
+static int hip04_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct hip04_priv *priv = netdev_priv(ndev);
+ struct device *d = &pdev->dev;
+
+ hip04_free_ring(ndev, d);
+ unregister_netdev(ndev);
+ free_irq(ndev->irq, ndev);
+ of_node_put(priv->phy_node);
+ free_netdev(ndev);
+
+ return 0;
+}
+
+static const struct of_device_id hip04_mac_match[] = {
+ { .compatible = "hisilicon,hip04-mac" },
+ { }
+};
+
+static struct platform_driver hip04_mac_driver = {
+ .probe = hip04_mac_probe,
+ .remove = hip04_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = hip04_mac_match,
+ },
+};
+module_platform_driver(hip04_mac_driver);
+
+MODULE_DESCRIPTION("HISILICON P04 Ethernet driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:hip04-ether");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-21 15:09 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
@ 2014-03-21 15:27 ` Arnd Bergmann
2014-03-22 1:18 ` zhangfei
0 siblings, 1 reply; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-21 15:27 UTC (permalink / raw)
To: Zhangfei Gao
Cc: mark.rutland, devicetree, f.fainelli, linux, sergei.shtylyov,
netdev, David S. Miller, linux-arm-kernel
On Friday 21 March 2014 23:09:30 Zhangfei Gao wrote:
> +
> +static void __iomem *ppebase;
Any reason why you still have this, rather than using a separate
driver for it as we discussed? If you have comments that you still
plan to address, please mention those in the introductory mail,
so you don't get the same review comments multiple times.
> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> +{
> + struct hip04_priv *priv = netdev_priv(ndev);
> + unsigned tx_head = priv->tx_head;
> + unsigned tx_tail = priv->tx_tail;
> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
> +
> + while (tx_tail != tx_head) {
> + if (desc->send_addr != 0) {
> + if (force)
> + desc->send_addr = 0;
> + else
> + break;
> + }
> + if (priv->tx_phys[tx_tail]) {
> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
> + priv->tx_phys[tx_tail] = 0;
> + }
> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
> + priv->tx_skb[tx_tail] = NULL;
> + tx_tail = TX_NEXT(tx_tail);
> + priv->tx_count--;
> + }
> + priv->tx_tail = tx_tail;
> +}
You call this function from start_xmit(), which may be too early, causing the
dma_unmap_single() and dev_kfree_skb_irq() functions to be called while the
device is still accessing the data. This is bad.
You have to ensure that you only ever clean up tx buffers that have been
successfully transmitted. Also, you should use an interrupt to notify you
of this in case there is no further xmit packet. Otherwise you may have
a user space program waiting indefinitely for a single packet to get sent
on a socket.
It's ok to also call the cleanup from start_xmit, but calling it from the
poll() function or another appropriate place is required.
> + priv->id = of_alias_get_id(node, "ethernet");
> + if (priv->id < 0) {
> + dev_warn(d, "no ethernet alias\n");
> + ret = -EINVAL;
> + goto init_fail;
> + }
Apparently you try to rely on the alias to refer to a specific piece
of hardware, which is not correct. The alias is meant to be selectable
to match e.g. the numbering written on the external connector, which
is totally independent of the internal hardware.
Arnd
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-21 15:27 ` Arnd Bergmann
@ 2014-03-22 1:18 ` zhangfei
2014-03-22 8:08 ` Arnd Bergmann
0 siblings, 1 reply; 64+ messages in thread
From: zhangfei @ 2014-03-22 1:18 UTC (permalink / raw)
To: Arnd Bergmann
Cc: David S. Miller, linux, f.fainelli, mark.rutland, sergei.shtylyov,
linux-arm-kernel, netdev, devicetree
Dear Arnd
On 03/21/2014 11:27 PM, Arnd Bergmann wrote:
> On Friday 21 March 2014 23:09:30 Zhangfei Gao wrote:
>
>> +
>> +static void __iomem *ppebase;
>
> Any reason why you still have this, rather than using a separate
> driver for it as we discussed? If you have comments that you still
> plan to address, please mention those in the introductory mail,
> so you don't get the same review comments multiple times.
Sorry for my bad understanding.
I thought you agreed to use this.
Will check your earlier comments more carefully.
>
>
>> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
>> +{
>> + struct hip04_priv *priv = netdev_priv(ndev);
>> + unsigned tx_head = priv->tx_head;
>> + unsigned tx_tail = priv->tx_tail;
>> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
>> +
>> + while (tx_tail != tx_head) {
>> + if (desc->send_addr != 0) {
>> + if (force)
>> + desc->send_addr = 0;
>> + else
>> + break;
>> + }
>> + if (priv->tx_phys[tx_tail]) {
>> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
>> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
>> + priv->tx_phys[tx_tail] = 0;
>> + }
>> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
>> + priv->tx_skb[tx_tail] = NULL;
>> + tx_tail = TX_NEXT(tx_tail);
>> + priv->tx_count--;
>> + }
>> + priv->tx_tail = tx_tail;
>> +}
>
> You call this function from start_xmit(), which may be too early, causing the
> dma_unmap_single() and dev_kfree_skb_irq() functions to be called while the
> device is still accessing the data. This is bad.
There is a protection.
Only after xmit done, desc->send_addr = 0, which is cleared by hardware.
>
> You have to ensure that you only ever clean up tx buffers that have been
> successfully transmitted. Also, you should use an interrupt to notify you
> of this in case there is no further xmit packet. Otherwise you may have
> a user space program waiting indefinitely for a single packet to get sent
> on a socket.
>
> It's ok to also call the cleanup from start_xmit, but calling it from the
> poll() function or another appropriate place is required.
There is no transmit done interrupt, so relying on every xmit to reclaim
finished buffer.
I thought it would be enough, since there are TX_DESC_NUM descs.
It is a good idea also put reclaim in poll, then will add spin lock etc.
>
>> + priv->id = of_alias_get_id(node, "ethernet");
>> + if (priv->id < 0) {
>> + dev_warn(d, "no ethernet alias\n");
>> + ret = -EINVAL;
>> + goto init_fail;
>> + }
>
> Apparently you try to rely on the alias to refer to a specific piece
> of hardware, which is not correct. The alias is meant to be selectable
> to match e.g. the numbering written on the external connector, which
> is totally independent of the internal hardware.
Thanks for clarifying alisa.
The id will be used for start channel in ppe, RX_DESC_NUM * priv->id;
Is it suitable directly use id in the dts, or other name such as start-chan?
Thanks
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/3] net: hisilicon: new hip04 ethernet driver
2014-03-22 1:18 ` zhangfei
@ 2014-03-22 8:08 ` Arnd Bergmann
0 siblings, 0 replies; 64+ messages in thread
From: Arnd Bergmann @ 2014-03-22 8:08 UTC (permalink / raw)
To: zhangfei
Cc: David S. Miller, linux, f.fainelli, mark.rutland, sergei.shtylyov,
linux-arm-kernel, netdev, devicetree
On Saturday 22 March 2014 09:18:35 zhangfei wrote:
> >> +static void hip04_tx_reclaim(struct net_device *ndev, bool force)
> >> +{
> >> + struct hip04_priv *priv = netdev_priv(ndev);
> >> + unsigned tx_head = priv->tx_head;
> >> + unsigned tx_tail = priv->tx_tail;
> >> + struct tx_desc *desc = &priv->tx_desc[priv->tx_tail];
> >> +
> >> + while (tx_tail != tx_head) {
> >> + if (desc->send_addr != 0) {
> >> + if (force)
> >> + desc->send_addr = 0;
> >> + else
> >> + break;
> >> + }
> >> + if (priv->tx_phys[tx_tail]) {
> >> + dma_unmap_single(&ndev->dev, priv->tx_phys[tx_tail],
> >> + priv->tx_skb[tx_tail]->len, DMA_TO_DEVICE);
> >> + priv->tx_phys[tx_tail] = 0;
> >> + }
> >> + dev_kfree_skb_irq(priv->tx_skb[tx_tail]);
> >> + priv->tx_skb[tx_tail] = NULL;
> >> + tx_tail = TX_NEXT(tx_tail);
> >> + priv->tx_count--;
> >> + }
> >> + priv->tx_tail = tx_tail;
> >> +}
> >
> > You call this function from start_xmit(), which may be too early, causing the
> > dma_unmap_single() and dev_kfree_skb_irq() functions to be called while the
> > device is still accessing the data. This is bad.
>
> There is a protection.
> Only after xmit done, desc->send_addr = 0, which is cleared by hardware.
Ok, I see.
> > You have to ensure that you only ever clean up tx buffers that have been
> > successfully transmitted. Also, you should use an interrupt to notify you
> > of this in case there is no further xmit packet. Otherwise you may have
> > a user space program waiting indefinitely for a single packet to get sent
> > on a socket.
> >
> > It's ok to also call the cleanup from start_xmit, but calling it from the
> > poll() function or another appropriate place is required.
>
> There is no transmit done interrupt, so relying on every xmit to reclaim
> finished buffer.
> I thought it would be enough, since there are TX_DESC_NUM descs.
> It is a good idea also put reclaim in poll, then will add spin lock etc.
It may be simpler to call napi_schedule() when you want to do tx descriptor
cleanup and have the function always be called in one place. If you do this
carefully, you can probably avoid most of the locking.
> >> + priv->id = of_alias_get_id(node, "ethernet");
> >> + if (priv->id < 0) {
> >> + dev_warn(d, "no ethernet alias\n");
> >> + ret = -EINVAL;
> >> + goto init_fail;
> >> + }
> >
> > Apparently you try to rely on the alias to refer to a specific piece
> > of hardware, which is not correct. The alias is meant to be selectable
> > to match e.g. the numbering written on the external connector, which
> > is totally independent of the internal hardware.
>
> Thanks for clarifying alisa.
> The id will be used for start channel in ppe, RX_DESC_NUM * priv->id;
> Is it suitable directly use id in the dts, or other name such as start-chan?
Yes, I think it's best to just pass the id the same way as the channel
number.
Arnd
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 0/3] add hisilicon hip04 ethernet driver
@ 2014-03-18 8:40 Zhangfei Gao
[not found] ` <1395132017-15928-1-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
0 siblings, 1 reply; 64+ messages in thread
From: Zhangfei Gao @ 2014-03-18 8:40 UTC (permalink / raw)
To: David S. Miller; +Cc: linux-arm-kernel, netdev, devicetree, Zhangfei Gao
Zhangfei Gao (3):
Documentation: add Device tree bindings for Hisilicon hip04 ethernet
net: hisilicon: new hip04 MDIO driver
net: hisilicon: new hip04 ethernet driver
.../bindings/net/hisilicon-hip04-net.txt | 74 ++
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/hisilicon/Kconfig | 31 +
drivers/net/ethernet/hisilicon/Makefile | 5 +
drivers/net/ethernet/hisilicon/hip04_eth.c | 717 ++++++++++++++++++++
drivers/net/ethernet/hisilicon/hip04_mdio.c | 190 ++++++
7 files changed, 1019 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/hisilicon-hip04-net.txt
create mode 100644 drivers/net/ethernet/hisilicon/Kconfig
create mode 100644 drivers/net/ethernet/hisilicon/Makefile
create mode 100644 drivers/net/ethernet/hisilicon/hip04_eth.c
create mode 100644 drivers/net/ethernet/hisilicon/hip04_mdio.c
--
1.7.9.5
^ permalink raw reply [flat|nested] 64+ messages in thread
end of thread, other threads:[~2014-04-18 13:17 UTC | newest]
Thread overview: 64+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-04-05 4:35 [PATCH v7 0/3] add hisilicon hip04 ethernet driver Zhangfei Gao
2014-04-05 4:35 ` [PATCH 1/3] Documentation: add Device tree bindings for Hisilicon hip04 ethernet Zhangfei Gao
2014-04-05 4:35 ` [PATCH 2/3] net: hisilicon: new hip04 MDIO driver Zhangfei Gao
2014-04-07 18:47 ` David Miller
2014-04-05 4:35 ` [PATCH 3/3] net: hisilicon: new hip04 ethernet driver Zhangfei Gao
2014-04-07 18:53 ` David Miller
2014-04-08 8:07 ` zhangfei
2014-04-08 8:30 ` David Laight
[not found] ` <063D6719AE5E284EB5DD2968C1650D6D0F6F1434-VkEWCZq2GCInGFn1LkZF6NBPR1lH4CV8@public.gmane.org>
2014-04-08 9:42 ` Arnd Bergmann
2014-04-08 14:47 ` zhangfei
2014-04-18 13:17 ` zhangfei
2014-04-07 18:56 ` David Miller
-- strict thread matches above, loose matches on Subject: below --
2014-04-04 15:16 [PATCH v6 0/3] add hisilicon " Zhangfei Gao
[not found] ` <1396624597-390-1-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2014-04-04 15:16 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
2014-04-01 13:27 [PATCH v5 0/3] add hisilicon " Zhangfei Gao
2014-04-01 13:27 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
[not found] ` <1396358832-15828-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2014-04-02 9:21 ` Arnd Bergmann
2014-04-02 9:51 ` zhangfei
2014-04-02 15:24 ` Arnd Bergmann
2014-04-02 10:04 ` David Laight
2014-04-02 15:49 ` Arnd Bergmann
2014-04-03 6:24 ` Zhangfei Gao
[not found] ` <CAMj5BkgfwE1hHpVeqH9WRitwCB30x3c4w0qw7sXT3PiOV-QcPQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2014-04-03 8:35 ` Arnd Bergmann
2014-04-03 15:22 ` David Miller
2014-04-03 15:38 ` zhangfei
2014-04-03 15:27 ` Russell King - ARM Linux
2014-04-03 15:42 ` David Laight
2014-04-03 15:50 ` Russell King - ARM Linux
2014-04-03 17:57 ` Arnd Bergmann
2014-04-04 6:52 ` Zhangfei Gao
2014-03-28 15:35 [PATCH v4 0/3] add hisilicon " Zhangfei Gao
2014-03-28 15:36 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
2014-03-24 14:14 [PATCH v3 0/3] add hisilicon " Zhangfei Gao
2014-03-24 14:14 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
[not found] ` <1395670496-17381-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2014-03-24 15:18 ` Arnd Bergmann
2014-03-25 4:06 ` Zhangfei Gao
2014-03-25 8:12 ` Arnd Bergmann
2014-03-25 17:00 ` Florian Fainelli
2014-03-25 17:05 ` Arnd Bergmann
2014-03-25 17:16 ` Florian Fainelli
2014-03-25 17:57 ` Arnd Bergmann
2014-03-26 9:55 ` David Laight
2014-03-25 17:17 ` David Laight
2014-03-25 17:21 ` Eric Dumazet
2014-03-25 17:54 ` Arnd Bergmann
2014-03-27 12:53 ` zhangfei
2014-03-24 16:32 ` Florian Fainelli
2014-03-24 17:23 ` Arnd Bergmann
2014-03-24 17:35 ` Florian Fainelli
2014-03-27 6:27 ` Zhangfei Gao
2014-03-21 15:09 [PATCH v2 0/3] add hisilicon " Zhangfei Gao
2014-03-21 15:09 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
2014-03-21 15:27 ` Arnd Bergmann
2014-03-22 1:18 ` zhangfei
2014-03-22 8:08 ` Arnd Bergmann
2014-03-18 8:40 [PATCH 0/3] add hisilicon " Zhangfei Gao
[not found] ` <1395132017-15928-1-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2014-03-18 8:40 ` [PATCH 3/3] net: hisilicon: new " Zhangfei Gao
[not found] ` <1395132017-15928-4-git-send-email-zhangfei.gao-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
2014-03-18 10:46 ` Russell King - ARM Linux
2014-03-20 9:51 ` Zhangfei Gao
2014-03-24 14:17 ` Rob Herring
2014-03-26 14:22 ` Zhangfei Gao
2014-03-18 11:25 ` Arnd Bergmann
2014-03-20 14:00 ` Zhangfei Gao
2014-03-20 14:31 ` Arnd Bergmann
[not found] ` <201403201531.20416.arnd-r2nGTMty4D4@public.gmane.org>
2014-03-21 5:19 ` Zhangfei Gao
2014-03-21 7:37 ` Arnd Bergmann
2014-03-21 7:56 ` Zhangfei Gao
2014-03-24 8:17 ` Zhangfei Gao
2014-03-24 10:02 ` Arnd Bergmann
2014-03-24 13:23 ` Zhangfei Gao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).