* [PATCH net-next v5 0/3] TSN endpoint Ethernet MAC driver
@ 2021-11-15 20:50 Gerhard Engleder
2021-11-15 20:50 ` [PATCH net-next v5 1/3] dt-bindings: Add vendor prefix for Engleder Gerhard Engleder
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Gerhard Engleder @ 2021-11-15 20:50 UTC (permalink / raw)
To: davem, kuba, andrew; +Cc: netdev, Gerhard Engleder
v5:
- rebase net-next/master
v4:
- fix sparse __iomem warnings (Jakub Kicinski, Andrew Lunn)
- fix sparse endian warnings (Jakub Kicinski, Andrew Lunn)
v3:
- set MAC mode based on PHY information (Andrew Lunn)
- remove/postpone loopback mode interface (Andrew Lunn)
- add suppress_preamble node support (Andrew Lunn)
- add mdio timeout (Andrew Lunn)
- no need to call phy_start_aneg (Andrew Lunn)
- remove unreachable code (Andrew Lunn)
- move 'struct napi_struct' closer to queues (Vinicius Costa Gomes)
- remove unused variable (kernel test robot)
- switch from mdio interrupt to polling
- mdio register without PHY address flag
- thread safe interrupt enable register
- add PTP_1588_CLOCK_OPTIONAL dependency to Kconfig
- introduce dmadev for DMA allocation
- mdiobus for platforms without device tree
- prepare MAC address support for platforms without device tree
- add missing interrupt disable to probe error path
v2:
- add C45 check (Andrew Lunn)
- forward phy_connect_direct() return value (Andrew Lunn)
- use phy_remove_link_mode() (Andrew Lunn)
- do not touch PHY directly, use PHY subsystem (Andrew Lunn)
- remove management data lock (Andrew Lunn)
- use phy_loopback (Andrew Lunn)
- remove GMII2RGMII handling, use xgmiitorgmii (Andrew Lunn)
- remove char device for direct TX/RX queue access (Andrew Lunn)
- mdio node for mdiobus (Rob Herring)
- simplify compatible node (Rob Herring)
- limit number of items of reg and interrupts nodes (Rob Herring)
- restrict phy-connection-type node (Rob Herring)
- reference to mdio.yaml under mdio node (Rob Herring)
- remove device tree (Michal Simek)
- fix %llx warning (kernel test robot)
- fix unused tmp variable warning (kernel test robot)
- add missing of_node_put() for of_parse_phandle()
- use devm_mdiobus_alloc()
- simplify mdiobus read/write
- reduce required nodes
- ethtool priv flags interface for loopback
- add missing static for some functions
- remove obsolete hardware defines
Gerhard Engleder (3):
dt-bindings: Add vendor prefix for Engleder
dt-bindings: net: Add tsnep Ethernet controller
tsnep: Add TSN endpoint Ethernet MAC driver
.../bindings/net/engleder,tsnep.yaml | 79 ++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/engleder/Kconfig | 29 +
drivers/net/ethernet/engleder/Makefile | 9 +
drivers/net/ethernet/engleder/tsnep.h | 171 +++
drivers/net/ethernet/engleder/tsnep_ethtool.c | 288 ++++
drivers/net/ethernet/engleder/tsnep_hw.h | 230 +++
drivers/net/ethernet/engleder/tsnep_main.c | 1255 +++++++++++++++++
drivers/net/ethernet/engleder/tsnep_ptp.c | 221 +++
drivers/net/ethernet/engleder/tsnep_tc.c | 443 ++++++
drivers/net/ethernet/engleder/tsnep_test.c | 811 +++++++++++
13 files changed, 3540 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/engleder,tsnep.yaml
create mode 100644 drivers/net/ethernet/engleder/Kconfig
create mode 100644 drivers/net/ethernet/engleder/Makefile
create mode 100644 drivers/net/ethernet/engleder/tsnep.h
create mode 100644 drivers/net/ethernet/engleder/tsnep_ethtool.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_hw.h
create mode 100644 drivers/net/ethernet/engleder/tsnep_main.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_ptp.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_tc.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_test.c
--
2.20.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net-next v5 1/3] dt-bindings: Add vendor prefix for Engleder
2021-11-15 20:50 [PATCH net-next v5 0/3] TSN endpoint Ethernet MAC driver Gerhard Engleder
@ 2021-11-15 20:50 ` Gerhard Engleder
2021-11-15 22:24 ` Andrew Lunn
2021-11-15 20:50 ` [PATCH net-next v5 2/3] dt-bindings: net: Add tsnep Ethernet controller Gerhard Engleder
2021-11-15 20:50 ` [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver Gerhard Engleder
2 siblings, 1 reply; 13+ messages in thread
From: Gerhard Engleder @ 2021-11-15 20:50 UTC (permalink / raw)
To: davem, kuba, andrew; +Cc: netdev, Gerhard Engleder, Rob Herring
Engleder develops FPGA based controllers for real-time communication.
Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com>
Acked-by: Rob Herring <robh@kernel.org>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 66d6432fd781..5f4ca46bfb13 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -379,6 +379,8 @@ patternProperties:
description: Silicon Laboratories (formerly Energy Micro AS)
"^engicam,.*":
description: Engicam S.r.l.
+ "^engleder,.*":
+ description: Engleder
"^epcos,.*":
description: EPCOS AG
"^epfl,.*":
--
2.20.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next v5 2/3] dt-bindings: net: Add tsnep Ethernet controller
2021-11-15 20:50 [PATCH net-next v5 0/3] TSN endpoint Ethernet MAC driver Gerhard Engleder
2021-11-15 20:50 ` [PATCH net-next v5 1/3] dt-bindings: Add vendor prefix for Engleder Gerhard Engleder
@ 2021-11-15 20:50 ` Gerhard Engleder
2021-11-15 22:25 ` Andrew Lunn
2021-11-15 20:50 ` [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver Gerhard Engleder
2 siblings, 1 reply; 13+ messages in thread
From: Gerhard Engleder @ 2021-11-15 20:50 UTC (permalink / raw)
To: davem, kuba, andrew; +Cc: netdev, Gerhard Engleder, Rob Herring
The TSN endpoint Ethernet MAC is a FPGA based network device for
real-time communication.
It is integrated as normal Ethernet controller with
ethernet-controller.yaml and mdio.yaml.
Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
.../bindings/net/engleder,tsnep.yaml | 79 +++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/engleder,tsnep.yaml
diff --git a/Documentation/devicetree/bindings/net/engleder,tsnep.yaml b/Documentation/devicetree/bindings/net/engleder,tsnep.yaml
new file mode 100644
index 000000000000..d0e1476e15b5
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/engleder,tsnep.yaml
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/engleder,tsnep.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TSN endpoint Ethernet MAC binding
+
+maintainers:
+ - Gerhard Engleder <gerhard@engleder-embedded.com>
+
+allOf:
+ - $ref: ethernet-controller.yaml#
+
+properties:
+ compatible:
+ const: engleder,tsnep
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ local-mac-address: true
+
+ mac-address: true
+
+ nvmem-cells: true
+
+ nvmem-cells-names: true
+
+ phy-connection-type:
+ enum:
+ - mii
+ - gmii
+ - rgmii
+ - rgmii-id
+
+ phy-mode: true
+
+ phy-handle: true
+
+ mdio:
+ type: object
+ $ref: "mdio.yaml#"
+ description: optional node for embedded MDIO controller
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ axi {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ tnsep0: ethernet@a0000000 {
+ compatible = "engleder,tsnep";
+ reg = <0x0 0xa0000000 0x0 0x10000>;
+ interrupts = <0 89 1>;
+ interrupt-parent = <&gic>;
+ local-mac-address = [00 00 00 00 00 00];
+ phy-mode = "rgmii";
+ phy-handle = <&phy0>;
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ suppress-preamble;
+ phy0: ethernet-phy@1 {
+ reg = <1>;
+ rxc-skew-ps = <1080>;
+ };
+ };
+ };
+ };
--
2.20.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
2021-11-15 20:50 [PATCH net-next v5 0/3] TSN endpoint Ethernet MAC driver Gerhard Engleder
2021-11-15 20:50 ` [PATCH net-next v5 1/3] dt-bindings: Add vendor prefix for Engleder Gerhard Engleder
2021-11-15 20:50 ` [PATCH net-next v5 2/3] dt-bindings: net: Add tsnep Ethernet controller Gerhard Engleder
@ 2021-11-15 20:50 ` Gerhard Engleder
2021-11-15 22:21 ` Heiner Kallweit
` (2 more replies)
2 siblings, 3 replies; 13+ messages in thread
From: Gerhard Engleder @ 2021-11-15 20:50 UTC (permalink / raw)
To: davem, kuba, andrew; +Cc: netdev, Gerhard Engleder
The TSN endpoint Ethernet MAC is a FPGA based network device for
real-time communication.
It is integrated as Ethernet controller with ethtool and PTP support.
For real-time communcation TC_SETUP_QDISC_TAPRIO is supported.
Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com>
---
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/engleder/Kconfig | 29 +
drivers/net/ethernet/engleder/Makefile | 9 +
drivers/net/ethernet/engleder/tsnep.h | 171 +++
drivers/net/ethernet/engleder/tsnep_ethtool.c | 288 ++++
drivers/net/ethernet/engleder/tsnep_hw.h | 230 +++
drivers/net/ethernet/engleder/tsnep_main.c | 1255 +++++++++++++++++
drivers/net/ethernet/engleder/tsnep_ptp.c | 221 +++
drivers/net/ethernet/engleder/tsnep_tc.c | 443 ++++++
drivers/net/ethernet/engleder/tsnep_test.c | 811 +++++++++++
11 files changed, 3459 insertions(+)
create mode 100644 drivers/net/ethernet/engleder/Kconfig
create mode 100644 drivers/net/ethernet/engleder/Makefile
create mode 100644 drivers/net/ethernet/engleder/tsnep.h
create mode 100644 drivers/net/ethernet/engleder/tsnep_ethtool.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_hw.h
create mode 100644 drivers/net/ethernet/engleder/tsnep_main.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_ptp.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_tc.c
create mode 100644 drivers/net/ethernet/engleder/tsnep_test.c
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 4601b38f532a..027cbacca1c9 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -73,6 +73,7 @@ config DNET
source "drivers/net/ethernet/dec/Kconfig"
source "drivers/net/ethernet/dlink/Kconfig"
source "drivers/net/ethernet/emulex/Kconfig"
+source "drivers/net/ethernet/engleder/Kconfig"
source "drivers/net/ethernet/ezchip/Kconfig"
source "drivers/net/ethernet/faraday/Kconfig"
source "drivers/net/ethernet/freescale/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index fdd8c6c17451..33d30b619e00 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_DNET) += dnet.o
obj-$(CONFIG_NET_VENDOR_DEC) += dec/
obj-$(CONFIG_NET_VENDOR_DLINK) += dlink/
obj-$(CONFIG_NET_VENDOR_EMULEX) += emulex/
+obj-$(CONFIG_NET_VENDOR_ENGLEDER) += engleder/
obj-$(CONFIG_NET_VENDOR_EZCHIP) += ezchip/
obj-$(CONFIG_NET_VENDOR_FARADAY) += faraday/
obj-$(CONFIG_NET_VENDOR_FREESCALE) += freescale/
diff --git a/drivers/net/ethernet/engleder/Kconfig b/drivers/net/ethernet/engleder/Kconfig
new file mode 100644
index 000000000000..26c2a8e0acc0
--- /dev/null
+++ b/drivers/net/ethernet/engleder/Kconfig
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Engleder network device configuration
+#
+
+config NET_VENDOR_ENGLEDER
+ bool "Engleder devices"
+ default y
+ help
+ If you have a network (Ethernet) card belonging to this class, say Y.
+
+ 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 Engleder devices. If you say Y, you will be asked
+ for your specific card in the following questions.
+
+if NET_VENDOR_ENGLEDER
+
+config TSNEP
+ tristate "TSN endpoint support"
+ depends on PTP_1588_CLOCK_OPTIONAL
+ select PHYLIB
+ help
+ Support for the Engleder TSN endpoint Ethernet MAC IP Core.
+
+ To compile this driver as a module, choose M here. The module will be
+ called tsnep.
+
+endif # NET_VENDOR_ENGLEDER
diff --git a/drivers/net/ethernet/engleder/Makefile b/drivers/net/ethernet/engleder/Makefile
new file mode 100644
index 000000000000..fbaecbfb0944
--- /dev/null
+++ b/drivers/net/ethernet/engleder/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the Engleder Ethernet drivers
+#
+
+obj-$(CONFIG_TSNEP) += tsnep.o
+
+tsnep-objs := tsnep_main.o tsnep_ethtool.o tsnep_ptp.o tsnep_tc.o \
+ tsnep_test.o
diff --git a/drivers/net/ethernet/engleder/tsnep.h b/drivers/net/ethernet/engleder/tsnep.h
new file mode 100644
index 000000000000..edd6fa7dafd7
--- /dev/null
+++ b/drivers/net/ethernet/engleder/tsnep.h
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
+
+#ifndef _TSNEP_H
+#define _TSNEP_H
+
+#include "tsnep_hw.h"
+
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/phy.h>
+#include <linux/ethtool.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/miscdevice.h>
+
+#define TSNEP "tsnep"
+
+#define TSNEP_RING_SIZE 256
+#define TSNEP_RING_ENTRIES_PER_PAGE (PAGE_SIZE / TSNEP_DESC_SIZE)
+#define TSNEP_RING_PAGE_COUNT (TSNEP_RING_SIZE / TSNEP_RING_ENTRIES_PER_PAGE)
+
+#define TSNEP_QUEUES 1
+
+struct tsnep_gcl {
+ void __iomem *addr;
+
+ u64 base_time;
+ u64 cycle_time;
+ u64 cycle_time_extension;
+
+ struct tsnep_gcl_operation operation[TSNEP_GCL_COUNT];
+ int count;
+
+ u64 change_limit;
+
+ u64 start_time;
+ bool change;
+};
+
+struct tsnep_tx_entry {
+ struct tsnep_tx_desc *desc;
+ struct tsnep_tx_desc_wb *desc_wb;
+ dma_addr_t desc_dma;
+ bool owner_user_flag;
+
+ u32 properties;
+
+ struct sk_buff *skb;
+ DEFINE_DMA_UNMAP_ADDR(dma);
+ DEFINE_DMA_UNMAP_LEN(len);
+};
+
+struct tsnep_tx {
+ struct tsnep_adapter *adapter;
+ void __iomem *addr;
+
+ void *page[TSNEP_RING_PAGE_COUNT];
+ dma_addr_t page_dma[TSNEP_RING_PAGE_COUNT];
+
+ /* TX ring lock */
+ spinlock_t lock;
+ struct tsnep_tx_entry entry[TSNEP_RING_SIZE];
+ int write;
+ int read;
+ u32 owner_counter;
+ int increment_owner_counter;
+
+ u32 packets;
+ u32 bytes;
+ u32 dropped;
+};
+
+struct tsnep_rx_entry {
+ struct tsnep_rx_desc *desc;
+ struct tsnep_rx_desc_wb *desc_wb;
+ dma_addr_t desc_dma;
+
+ u32 properties;
+
+ struct sk_buff *skb;
+ DEFINE_DMA_UNMAP_ADDR(dma);
+ DEFINE_DMA_UNMAP_LEN(len);
+};
+
+struct tsnep_rx {
+ struct tsnep_adapter *adapter;
+ void __iomem *addr;
+
+ void *page[TSNEP_RING_PAGE_COUNT];
+ dma_addr_t page_dma[TSNEP_RING_PAGE_COUNT];
+
+ struct tsnep_rx_entry entry[TSNEP_RING_SIZE];
+ int read;
+ u32 owner_counter;
+ int increment_owner_counter;
+
+ u32 packets;
+ u32 bytes;
+ u32 dropped;
+ u32 multicast;
+};
+
+struct tsnep_queue {
+ struct tsnep_adapter *adapter;
+
+ struct tsnep_tx *tx;
+ struct tsnep_rx *rx;
+
+ struct napi_struct napi;
+
+ u32 irq_mask;
+};
+
+struct tsnep_adapter {
+ struct net_device *netdev;
+ u8 mac_address[ETH_ALEN];
+ struct mii_bus *mdiobus;
+ bool suppress_preamble;
+ phy_interface_t phy_mode;
+ struct phy_device *phydev;
+ int msg_enable;
+
+ struct platform_device *pdev;
+ struct device *dmadev;
+ void __iomem *addr;
+ unsigned long size;
+ int irq;
+
+ bool gate_control;
+ /* gate control lock */
+ struct mutex gate_control_lock;
+ bool gate_control_active;
+ struct tsnep_gcl gcl[2];
+ int next_gcl;
+
+ struct hwtstamp_config hwtstamp_config;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_info;
+ /* ptp clock lock */
+ spinlock_t ptp_lock;
+
+ int num_tx_queues;
+ struct tsnep_tx tx[TSNEP_MAX_QUEUES];
+ int num_rx_queues;
+ struct tsnep_rx rx[TSNEP_MAX_QUEUES];
+
+ int num_queues;
+ struct tsnep_queue queue[TSNEP_MAX_QUEUES];
+};
+
+extern const struct ethtool_ops tsnep_ethtool_ops;
+
+int tsnep_ptp_init(struct tsnep_adapter *adapter);
+void tsnep_ptp_cleanup(struct tsnep_adapter *adapter);
+int tsnep_ptp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd);
+
+int tsnep_tc_init(struct tsnep_adapter *adapter);
+void tsnep_tc_cleanup(struct tsnep_adapter *adapter);
+int tsnep_tc_setup(struct net_device *netdev, enum tc_setup_type type,
+ void *type_data);
+
+int tsnep_ethtool_get_test_count(void);
+void tsnep_ethtool_get_test_strings(u8 *data);
+void tsnep_ethtool_self_test(struct net_device *netdev,
+ struct ethtool_test *eth_test, u64 *data);
+
+void tsnep_get_system_time(struct tsnep_adapter *adapter, u64 *time);
+
+#endif /* _TSNEP_H */
diff --git a/drivers/net/ethernet/engleder/tsnep_ethtool.c b/drivers/net/ethernet/engleder/tsnep_ethtool.c
new file mode 100644
index 000000000000..f9abcaab1c7c
--- /dev/null
+++ b/drivers/net/ethernet/engleder/tsnep_ethtool.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
+
+#include "tsnep.h"
+
+static const char tsnep_stats_strings[][ETH_GSTRING_LEN] = {
+ "rx_packets",
+ "rx_bytes",
+ "rx_dropped",
+ "rx_multicast",
+ "rx_phy_errors",
+ "rx_forwarded_phy_errors",
+ "rx_invalid_frame_errors",
+ "tx_packets",
+ "tx_bytes",
+ "tx_dropped",
+};
+
+struct tsnep_stats {
+ u64 rx_packets;
+ u64 rx_bytes;
+ u64 rx_dropped;
+ u64 rx_multicast;
+ u64 rx_phy_errors;
+ u64 rx_forwarded_phy_errors;
+ u64 rx_invalid_frame_errors;
+ u64 tx_packets;
+ u64 tx_bytes;
+ u64 tx_dropped;
+};
+
+#define TSNEP_STATS_COUNT (sizeof(struct tsnep_stats) / sizeof(u64))
+
+static const char tsnep_rx_queue_stats_strings[][ETH_GSTRING_LEN] = {
+ "rx_%d_packets",
+ "rx_%d_bytes",
+ "rx_%d_dropped",
+ "rx_%d_multicast",
+ "rx_%d_no_descriptor_errors",
+ "rx_%d_buffer_too_small_errors",
+ "rx_%d_fifo_overflow_errors",
+ "rx_%d_invalid_frame_errors",
+};
+
+struct tsnep_rx_queue_stats {
+ u64 rx_packets;
+ u64 rx_bytes;
+ u64 rx_dropped;
+ u64 rx_multicast;
+ u64 rx_no_descriptor_errors;
+ u64 rx_buffer_too_small_errors;
+ u64 rx_fifo_overflow_errors;
+ u64 rx_invalid_frame_errors;
+};
+
+#define TSNEP_RX_QUEUE_STATS_COUNT (sizeof(struct tsnep_rx_queue_stats) / \
+ sizeof(u64))
+
+static const char tsnep_tx_queue_stats_strings[][ETH_GSTRING_LEN] = {
+ "tx_%d_packets",
+ "tx_%d_bytes",
+ "tx_%d_dropped",
+};
+
+struct tsnep_tx_queue_stats {
+ u64 tx_packets;
+ u64 tx_bytes;
+ u64 tx_dropped;
+};
+
+#define TSNEP_TX_QUEUE_STATS_COUNT (sizeof(struct tsnep_tx_queue_stats) / \
+ sizeof(u64))
+
+static void tsnep_ethtool_get_drvinfo(struct net_device *netdev,
+ struct ethtool_drvinfo *drvinfo)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+
+ strscpy(drvinfo->driver, TSNEP, sizeof(drvinfo->driver));
+ strscpy(drvinfo->bus_info, dev_name(&adapter->pdev->dev),
+ sizeof(drvinfo->bus_info));
+}
+
+static int tsnep_ethtool_get_regs_len(struct net_device *netdev)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ int len;
+ int num_queues;
+
+ len = TSNEP_MAC_SIZE;
+ num_queues = max(adapter->num_tx_queues, adapter->num_rx_queues);
+ len += TSNEP_QUEUE_SIZE * (num_queues - 1);
+
+ return len;
+}
+
+static void tsnep_ethtool_get_regs(struct net_device *netdev,
+ struct ethtool_regs *regs,
+ void *p)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+
+ regs->version = 1;
+
+ memcpy_fromio(p, adapter->addr, regs->len);
+}
+
+static u32 tsnep_ethtool_get_msglevel(struct net_device *netdev)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+
+ return adapter->msg_enable;
+}
+
+static void tsnep_ethtool_set_msglevel(struct net_device *netdev, u32 data)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+
+ adapter->msg_enable = data;
+}
+
+static void tsnep_ethtool_get_strings(struct net_device *netdev, u32 stringset,
+ u8 *data)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ int rx_count = adapter->num_rx_queues;
+ int tx_count = adapter->num_tx_queues;
+ int i, j;
+
+ switch (stringset) {
+ case ETH_SS_STATS:
+ memcpy(data, tsnep_stats_strings, sizeof(tsnep_stats_strings));
+ data += sizeof(tsnep_stats_strings);
+
+ for (i = 0; i < rx_count; i++) {
+ for (j = 0; j < TSNEP_RX_QUEUE_STATS_COUNT; j++) {
+ snprintf(data, ETH_GSTRING_LEN,
+ tsnep_rx_queue_stats_strings[j], i);
+ data += ETH_GSTRING_LEN;
+ }
+ }
+
+ for (i = 0; i < tx_count; i++) {
+ for (j = 0; j < TSNEP_TX_QUEUE_STATS_COUNT; j++) {
+ snprintf(data, ETH_GSTRING_LEN,
+ tsnep_tx_queue_stats_strings[j], i);
+ data += ETH_GSTRING_LEN;
+ }
+ }
+ break;
+ case ETH_SS_TEST:
+ tsnep_ethtool_get_test_strings(data);
+ break;
+ }
+}
+
+static void tsnep_ethtool_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats,
+ u64 *data)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ int rx_count = adapter->num_rx_queues;
+ int tx_count = adapter->num_tx_queues;
+ struct tsnep_stats tsnep_stats;
+ struct tsnep_rx_queue_stats tsnep_rx_queue_stats;
+ struct tsnep_tx_queue_stats tsnep_tx_queue_stats;
+ u32 reg;
+ int i;
+
+ memset(&tsnep_stats, 0, sizeof(tsnep_stats));
+ for (i = 0; i < adapter->num_rx_queues; i++) {
+ tsnep_stats.rx_packets += adapter->rx[i].packets;
+ tsnep_stats.rx_bytes += adapter->rx[i].bytes;
+ tsnep_stats.rx_dropped += adapter->rx[i].dropped;
+ tsnep_stats.rx_multicast += adapter->rx[i].multicast;
+ }
+ reg = ioread32(adapter->addr + ECM_STAT);
+ tsnep_stats.rx_phy_errors =
+ (reg & ECM_STAT_RX_ERR_MASK) >> ECM_STAT_RX_ERR_SHIFT;
+ tsnep_stats.rx_forwarded_phy_errors =
+ (reg & ECM_STAT_FWD_RX_ERR_MASK) >> ECM_STAT_FWD_RX_ERR_SHIFT;
+ tsnep_stats.rx_invalid_frame_errors =
+ (reg & ECM_STAT_INV_FRM_MASK) >> ECM_STAT_INV_FRM_SHIFT;
+ for (i = 0; i < adapter->num_tx_queues; i++) {
+ tsnep_stats.tx_packets += adapter->tx[i].packets;
+ tsnep_stats.tx_bytes += adapter->tx[i].bytes;
+ tsnep_stats.tx_dropped += adapter->tx[i].dropped;
+ }
+ memcpy(data, &tsnep_stats, sizeof(tsnep_stats));
+ data += TSNEP_STATS_COUNT;
+
+ for (i = 0; i < rx_count; i++) {
+ memset(&tsnep_rx_queue_stats, 0, sizeof(tsnep_rx_queue_stats));
+ tsnep_rx_queue_stats.rx_packets = adapter->rx[i].packets;
+ tsnep_rx_queue_stats.rx_bytes = adapter->rx[i].bytes;
+ tsnep_rx_queue_stats.rx_dropped = adapter->rx[i].dropped;
+ tsnep_rx_queue_stats.rx_multicast = adapter->rx[i].multicast;
+ reg = ioread32(adapter->addr + TSNEP_QUEUE(i) +
+ TSNEP_RX_STATISTIC);
+ tsnep_rx_queue_stats.rx_no_descriptor_errors =
+ (reg & TSNEP_RX_STATISTIC_NO_DESC_MASK) >>
+ TSNEP_RX_STATISTIC_NO_DESC_SHIFT;
+ tsnep_rx_queue_stats.rx_buffer_too_small_errors =
+ (reg & TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_MASK) >>
+ TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_SHIFT;
+ tsnep_rx_queue_stats.rx_fifo_overflow_errors =
+ (reg & TSNEP_RX_STATISTIC_FIFO_OVERFLOW_MASK) >>
+ TSNEP_RX_STATISTIC_FIFO_OVERFLOW_SHIFT;
+ tsnep_rx_queue_stats.rx_invalid_frame_errors =
+ (reg & TSNEP_RX_STATISTIC_INVALID_FRAME_MASK) >>
+ TSNEP_RX_STATISTIC_INVALID_FRAME_SHIFT;
+ memcpy(data, &tsnep_rx_queue_stats,
+ sizeof(tsnep_rx_queue_stats));
+ data += TSNEP_RX_QUEUE_STATS_COUNT;
+ }
+
+ for (i = 0; i < tx_count; i++) {
+ memset(&tsnep_tx_queue_stats, 0, sizeof(tsnep_tx_queue_stats));
+ tsnep_tx_queue_stats.tx_packets += adapter->tx[i].packets;
+ tsnep_tx_queue_stats.tx_bytes += adapter->tx[i].bytes;
+ tsnep_tx_queue_stats.tx_dropped += adapter->tx[i].dropped;
+ memcpy(data, &tsnep_tx_queue_stats,
+ sizeof(tsnep_tx_queue_stats));
+ data += TSNEP_TX_QUEUE_STATS_COUNT;
+ }
+}
+
+static int tsnep_ethtool_get_sset_count(struct net_device *netdev, int sset)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ int rx_count;
+ int tx_count;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ rx_count = adapter->num_rx_queues;
+ tx_count = adapter->num_tx_queues;
+ return TSNEP_STATS_COUNT +
+ TSNEP_RX_QUEUE_STATS_COUNT * rx_count +
+ TSNEP_TX_QUEUE_STATS_COUNT * tx_count;
+ case ETH_SS_TEST:
+ return tsnep_ethtool_get_test_count();
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int tsnep_ethtool_get_ts_info(struct net_device *dev,
+ struct ethtool_ts_info *info)
+{
+ struct tsnep_adapter *adapter = netdev_priv(dev);
+
+ info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_RX_SOFTWARE |
+ SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ if (adapter->ptp_clock)
+ info->phc_index = ptp_clock_index(adapter->ptp_clock);
+ else
+ info->phc_index = -1;
+
+ info->tx_types = BIT(HWTSTAMP_TX_OFF) |
+ BIT(HWTSTAMP_TX_ON);
+ info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
+ BIT(HWTSTAMP_FILTER_ALL);
+
+ return 0;
+}
+
+const struct ethtool_ops tsnep_ethtool_ops = {
+ .get_drvinfo = tsnep_ethtool_get_drvinfo,
+ .get_regs_len = tsnep_ethtool_get_regs_len,
+ .get_regs = tsnep_ethtool_get_regs,
+ .get_msglevel = tsnep_ethtool_get_msglevel,
+ .set_msglevel = tsnep_ethtool_set_msglevel,
+ .nway_reset = phy_ethtool_nway_reset,
+ .get_link = ethtool_op_get_link,
+ .self_test = tsnep_ethtool_self_test,
+ .get_strings = tsnep_ethtool_get_strings,
+ .get_ethtool_stats = tsnep_ethtool_get_ethtool_stats,
+ .get_sset_count = tsnep_ethtool_get_sset_count,
+ .get_ts_info = tsnep_ethtool_get_ts_info,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
+};
diff --git a/drivers/net/ethernet/engleder/tsnep_hw.h b/drivers/net/ethernet/engleder/tsnep_hw.h
new file mode 100644
index 000000000000..71cc8577d640
--- /dev/null
+++ b/drivers/net/ethernet/engleder/tsnep_hw.h
@@ -0,0 +1,230 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
+
+/* Hardware definition of TSNEP and EtherCAT MAC device */
+
+#ifndef _TSNEP_HW_H
+#define _TSNEP_HW_H
+
+#include <linux/types.h>
+
+/* type */
+#define ECM_TYPE 0x0000
+#define ECM_REVISION_MASK 0x000000FF
+#define ECM_REVISION_SHIFT 0
+#define ECM_VERSION_MASK 0x0000FF00
+#define ECM_VERSION_SHIFT 8
+#define ECM_QUEUE_COUNT_MASK 0x00070000
+#define ECM_QUEUE_COUNT_SHIFT 16
+#define ECM_GATE_CONTROL 0x02000000
+
+/* system time */
+#define ECM_SYSTEM_TIME_LOW 0x0008
+#define ECM_SYSTEM_TIME_HIGH 0x000C
+
+/* clock */
+#define ECM_CLOCK_RATE 0x0010
+#define ECM_CLOCK_RATE_OFFSET_MASK 0x7FFFFFFF
+#define ECM_CLOCK_RATE_OFFSET_SIGN 0x80000000
+
+/* interrupt */
+#define ECM_INT_ENABLE 0x0018
+#define ECM_INT_ACTIVE 0x001C
+#define ECM_INT_ACKNOWLEDGE 0x001C
+#define ECM_INT_LINK 0x00000020
+#define ECM_INT_TX_0 0x00000100
+#define ECM_INT_RX_0 0x00000200
+#define ECM_INT_ALL 0x7FFFFFFF
+#define ECM_INT_DISABLE 0x80000000
+
+/* reset */
+#define ECM_RESET 0x0020
+#define ECM_RESET_COMMON 0x00000001
+#define ECM_RESET_CHANNEL 0x00000100
+#define ECM_RESET_TXRX 0x00010000
+
+/* control and status */
+#define ECM_STATUS 0x0080
+#define ECM_LINK_MODE_OFF 0x01000000
+#define ECM_LINK_MODE_100 0x02000000
+#define ECM_LINK_MODE_1000 0x04000000
+#define ECM_NO_LINK 0x01000000
+#define ECM_LINK_MODE_MASK 0x06000000
+
+/* management data */
+#define ECM_MD_CONTROL 0x0084
+#define ECM_MD_STATUS 0x0084
+#define ECM_MD_PREAMBLE 0x00000001
+#define ECM_MD_READ 0x00000004
+#define ECM_MD_WRITE 0x00000002
+#define ECM_MD_ADDR_MASK 0x000000F8
+#define ECM_MD_ADDR_SHIFT 3
+#define ECM_MD_PHY_ADDR_MASK 0x00001F00
+#define ECM_MD_PHY_ADDR_SHIFT 8
+#define ECM_MD_BUSY 0x00000001
+#define ECM_MD_DATA_MASK 0xFFFF0000
+#define ECM_MD_DATA_SHIFT 16
+
+/* statistic */
+#define ECM_STAT 0x00B0
+#define ECM_STAT_RX_ERR_MASK 0x000000FF
+#define ECM_STAT_RX_ERR_SHIFT 0
+#define ECM_STAT_INV_FRM_MASK 0x0000FF00
+#define ECM_STAT_INV_FRM_SHIFT 8
+#define ECM_STAT_FWD_RX_ERR_MASK 0x00FF0000
+#define ECM_STAT_FWD_RX_ERR_SHIFT 16
+
+/* tsnep */
+#define TSNEP_MAC_SIZE 0x4000
+#define TSNEP_QUEUE_SIZE 0x1000
+#define TSNEP_QUEUE(n) ({ typeof(n) __n = (n); \
+ (__n) == 0 ? \
+ 0 : \
+ TSNEP_MAC_SIZE + TSNEP_QUEUE_SIZE * ((__n) - 1); })
+#define TSNEP_MAX_QUEUES 8
+#define TSNEP_MAX_FRAME_SIZE (2 * 1024) /* hardware supports actually 16k */
+#define TSNEP_DESC_SIZE 256
+#define TSNEP_DESC_OFFSET 128
+
+/* tsnep register */
+#define TSNEP_INFO 0x0100
+#define TSNEP_INFO_RX_ASSIGN 0x00010000
+#define TSNEP_INFO_TX_TIME 0x00020000
+#define TSNEP_CONTROL 0x0108
+#define TSNEP_CONTROL_TX_RESET 0x00000001
+#define TSNEP_CONTROL_TX_ENABLE 0x00000002
+#define TSNEP_CONTROL_TX_DMA_ERROR 0x00000010
+#define TSNEP_CONTROL_TX_DESC_ERROR 0x00000020
+#define TSNEP_CONTROL_RX_RESET 0x00000100
+#define TSNEP_CONTROL_RX_ENABLE 0x00000200
+#define TSNEP_CONTROL_RX_DISABLE 0x00000400
+#define TSNEP_CONTROL_RX_DMA_ERROR 0x00001000
+#define TSNEP_CONTROL_RX_DESC_ERROR 0x00002000
+#define TSNEP_TX_DESC_ADDR_LOW 0x0140
+#define TSNEP_TX_DESC_ADDR_HIGH 0x0144
+#define TSNEP_RX_DESC_ADDR_LOW 0x0180
+#define TSNEP_RX_DESC_ADDR_HIGH 0x0184
+#define TSNEP_RESET_OWNER_COUNTER 0x01
+#define TSNEP_RX_STATISTIC 0x0190
+#define TSNEP_RX_STATISTIC_NO_DESC_MASK 0x000000FF
+#define TSNEP_RX_STATISTIC_NO_DESC_SHIFT 0
+#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_MASK 0x0000FF00
+#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_SHIFT 8
+#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW_MASK 0x00FF0000
+#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW_SHIFT 16
+#define TSNEP_RX_STATISTIC_INVALID_FRAME_MASK 0xFF000000
+#define TSNEP_RX_STATISTIC_INVALID_FRAME_SHIFT 24
+#define TSNEP_RX_STATISTIC_NO_DESC 0x0190
+#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL 0x0191
+#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW 0x0192
+#define TSNEP_RX_STATISTIC_INVALID_FRAME 0x0193
+#define TSNEP_RX_ASSIGN 0x01A0
+#define TSNEP_RX_ASSIGN_ETHER_TYPE_ACTIVE 0x00000001
+#define TSNEP_RX_ASSIGN_ETHER_TYPE_MASK 0xFFFF0000
+#define TSNEP_RX_ASSIGN_ETHER_TYPE_SHIFT 16
+#define TSNEP_MAC_ADDRESS_LOW 0x0800
+#define TSNEP_MAC_ADDRESS_HIGH 0x0804
+#define TSNEP_RX_FILTER 0x0806
+#define TSNEP_RX_FILTER_ACCEPT_ALL_MULTICASTS 0x0001
+#define TSNEP_RX_FILTER_ACCEPT_ALL_UNICASTS 0x0002
+#define TSNEP_GC 0x0808
+#define TSNEP_GC_ENABLE_A 0x00000002
+#define TSNEP_GC_ENABLE_B 0x00000004
+#define TSNEP_GC_DISABLE 0x00000008
+#define TSNEP_GC_ENABLE_TIMEOUT 0x00000010
+#define TSNEP_GC_ACTIVE_A 0x00000002
+#define TSNEP_GC_ACTIVE_B 0x00000004
+#define TSNEP_GC_CHANGE_AB 0x00000008
+#define TSNEP_GC_TIMEOUT_ACTIVE 0x00000010
+#define TSNEP_GC_TIMEOUT_SIGNAL 0x00000020
+#define TSNEP_GC_LIST_ERROR 0x00000080
+#define TSNEP_GC_OPEN 0x00FF0000
+#define TSNEP_GC_OPEN_SHIFT 16
+#define TSNEP_GC_NEXT_OPEN 0xFF000000
+#define TSNEP_GC_NEXT_OPEN_SHIFT 24
+#define TSNEP_GC_TIMEOUT 131072
+#define TSNEP_GC_TIME 0x080C
+#define TSNEP_GC_CHANGE 0x0810
+#define TSNEP_GCL_A 0x2000
+#define TSNEP_GCL_B 0x2800
+#define TSNEP_GCL_SIZE SZ_2K
+
+/* tsnep gate control list operation */
+struct tsnep_gcl_operation {
+ u32 properties;
+ u32 interval;
+};
+
+#define TSNEP_GCL_COUNT (TSNEP_GCL_SIZE / sizeof(struct tsnep_gcl_operation))
+#define TSNEP_GCL_MASK 0x000000FF
+#define TSNEP_GCL_INSERT 0x20000000
+#define TSNEP_GCL_CHANGE 0x40000000
+#define TSNEP_GCL_LAST 0x80000000
+#define TSNEP_GCL_MIN_INTERVAL 32
+
+/* tsnep TX/RX descriptor */
+#define TSNEP_DESC_SIZE 256
+#define TSNEP_DESC_SIZE_DATA_AFTER 2048
+#define TSNEP_DESC_OFFSET 128
+#define TSNEP_DESC_OWNER_COUNTER_MASK 0xC0000000
+#define TSNEP_DESC_OWNER_COUNTER_SHIFT 30
+#define TSNEP_DESC_LENGTH_MASK 0x00003FFF
+#define TSNEP_DESC_INTERRUPT_FLAG 0x00040000
+#define TSNEP_DESC_EXTENDED_WRITEBACK_FLAG 0x00080000
+#define TSNEP_DESC_NO_LINK_FLAG 0x01000000
+
+/* tsnep TX descriptor */
+struct tsnep_tx_desc {
+ __le32 properties;
+ __le32 more_properties;
+ __le32 reserved[2];
+ __le64 next;
+ __le64 tx;
+};
+
+#define TSNEP_TX_DESC_OWNER_MASK 0xE0000000
+#define TSNEP_TX_DESC_OWNER_USER_FLAG 0x20000000
+#define TSNEP_TX_DESC_LAST_FRAGMENT_FLAG 0x00010000
+#define TSNEP_TX_DESC_DATA_AFTER_DESC_FLAG 0x00020000
+
+/* tsnep TX descriptor writeback */
+struct tsnep_tx_desc_wb {
+ __le32 properties;
+ __le32 reserved1[3];
+ __le64 timestamp;
+ __le32 dma_delay;
+ __le32 reserved2;
+};
+
+#define TSNEP_TX_DESC_UNDERRUN_ERROR_FLAG 0x00010000
+#define TSNEP_TX_DESC_DMA_DELAY_FIRST_DATA_MASK 0x0000FFFC
+#define TSNEP_TX_DESC_DMA_DELAY_FIRST_DATA_SHIFT 2
+#define TSNEP_TX_DESC_DMA_DELAY_LAST_DATA_MASK 0xFFFC0000
+#define TSNEP_TX_DESC_DMA_DELAY_LAST_DATA_SHIFT 18
+#define TSNEP_TX_DESC_DMA_DELAY_NS 64
+
+/* tsnep RX descriptor */
+struct tsnep_rx_desc {
+ __le32 properties;
+ __le32 reserved[3];
+ __le64 next;
+ __le64 rx;
+};
+
+#define TSNEP_RX_DESC_BUFFER_SIZE_MASK 0x00003FFC
+
+/* tsnep RX descriptor writeback */
+struct tsnep_rx_desc_wb {
+ __le32 properties;
+ __le32 reserved[7];
+};
+
+/* tsnep RX inline meta */
+struct tsnep_rx_inline {
+ __le64 reserved;
+ __le64 timestamp;
+};
+
+#define TSNEP_RX_INLINE_METADATA_SIZE (sizeof(struct tsnep_rx_inline))
+
+#endif /* _TSNEP_HW_H */
diff --git a/drivers/net/ethernet/engleder/tsnep_main.c b/drivers/net/ethernet/engleder/tsnep_main.c
new file mode 100644
index 000000000000..86d488821a65
--- /dev/null
+++ b/drivers/net/ethernet/engleder/tsnep_main.c
@@ -0,0 +1,1255 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
+
+/* TSN endpoint Ethernet MAC driver
+ *
+ * The TSN endpoint Ethernet MAC is a FPGA based network device for real-time
+ * communication. It is designed for endpoints within TSN (Time Sensitive
+ * Networking) networks; e.g., for PLCs in the industrial automation case.
+ *
+ * It supports multiple TX/RX queue pairs. The first TX/RX queue pair is used
+ * by the driver.
+ *
+ * More information can be found here:
+ * - www.embedded-experts.at/tsn
+ * - www.engleder-embedded.com
+ */
+
+#include "tsnep.h"
+#include "tsnep_hw.h"
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
+#include <linux/interrupt.h>
+#include <linux/etherdevice.h>
+#include <linux/phy.h>
+#include <linux/iopoll.h>
+
+#define RX_SKB_LENGTH (round_up(TSNEP_RX_INLINE_METADATA_SIZE + ETH_HLEN + \
+ TSNEP_MAX_FRAME_SIZE + ETH_FCS_LEN, 4))
+#define RX_SKB_RESERVE ((16 - TSNEP_RX_INLINE_METADATA_SIZE) + NET_IP_ALIGN)
+#define RX_SKB_ALLOC_LENGTH (RX_SKB_RESERVE + RX_SKB_LENGTH)
+
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+#define DMA_ADDR_HIGH(dma_addr) ((u32)(((dma_addr) >> 32) & 0xFFFFFFFF))
+#else
+#define DMA_ADDR_HIGH(dma_addr) ((u32)(0))
+#endif
+#define DMA_ADDR_LOW(dma_addr) ((u32)((dma_addr) & 0xFFFFFFFF))
+
+static void tsnep_enable_irq(struct tsnep_adapter *adapter, u32 mask)
+{
+ iowrite32(mask, adapter->addr + ECM_INT_ENABLE);
+}
+
+static void tsnep_disable_irq(struct tsnep_adapter *adapter, u32 mask)
+{
+ mask |= ECM_INT_DISABLE;
+ iowrite32(mask, adapter->addr + ECM_INT_ENABLE);
+}
+
+static irqreturn_t tsnep_irq(int irq, void *arg)
+{
+ struct tsnep_adapter *adapter = arg;
+ u32 active = ioread32(adapter->addr + ECM_INT_ACTIVE);
+
+ /* acknowledge interrupt */
+ if (active != 0)
+ iowrite32(active, adapter->addr + ECM_INT_ACKNOWLEDGE);
+
+ /* handle link interrupt */
+ if ((active & ECM_INT_LINK) != 0) {
+ if (adapter->netdev->phydev)
+ phy_mac_interrupt(adapter->netdev->phydev);
+ }
+
+ /* handle TX/RX queue 0 interrupt */
+ if ((active & adapter->queue[0].irq_mask) != 0) {
+ if (adapter->netdev) {
+ tsnep_disable_irq(adapter, adapter->queue[0].irq_mask);
+ napi_schedule(&adapter->queue[0].napi);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int tsnep_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct tsnep_adapter *adapter = bus->priv;
+ u32 md;
+ int retval;
+
+ if (regnum & MII_ADDR_C45)
+ return -EOPNOTSUPP;
+
+ md = ECM_MD_READ;
+ if (!adapter->suppress_preamble)
+ md |= ECM_MD_PREAMBLE;
+ md |= (regnum << ECM_MD_ADDR_SHIFT) & ECM_MD_ADDR_MASK;
+ md |= (addr << ECM_MD_PHY_ADDR_SHIFT) & ECM_MD_PHY_ADDR_MASK;
+ iowrite32(md, adapter->addr + ECM_MD_CONTROL);
+ retval = readl_poll_timeout_atomic(adapter->addr + ECM_MD_STATUS, md,
+ !(md & ECM_MD_BUSY), 16, 1000);
+ if (retval != 0)
+ return retval;
+
+ return (md & ECM_MD_DATA_MASK) >> ECM_MD_DATA_SHIFT;
+}
+
+static int tsnep_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
+ u16 val)
+{
+ struct tsnep_adapter *adapter = bus->priv;
+ u32 md;
+ int retval;
+
+ if (regnum & MII_ADDR_C45)
+ return -EOPNOTSUPP;
+
+ md = ECM_MD_WRITE;
+ if (!adapter->suppress_preamble)
+ md |= ECM_MD_PREAMBLE;
+ md |= (regnum << ECM_MD_ADDR_SHIFT) & ECM_MD_ADDR_MASK;
+ md |= (addr << ECM_MD_PHY_ADDR_SHIFT) & ECM_MD_PHY_ADDR_MASK;
+ md |= ((u32)val << ECM_MD_DATA_SHIFT) & ECM_MD_DATA_MASK;
+ iowrite32(md, adapter->addr + ECM_MD_CONTROL);
+ retval = readl_poll_timeout_atomic(adapter->addr + ECM_MD_STATUS, md,
+ !(md & ECM_MD_BUSY), 16, 1000);
+ if (retval != 0)
+ return retval;
+
+ return 0;
+}
+
+static void tsnep_phy_link_status_change(struct net_device *netdev)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ struct phy_device *phydev = netdev->phydev;
+ u32 mode;
+
+ if (phydev->link) {
+ switch (phydev->speed) {
+ case SPEED_100:
+ mode = ECM_LINK_MODE_100;
+ break;
+ case SPEED_1000:
+ mode = ECM_LINK_MODE_1000;
+ break;
+ default:
+ mode = ECM_LINK_MODE_OFF;
+ break;
+ }
+ iowrite32(mode, adapter->addr + ECM_STATUS);
+ }
+
+ phy_print_status(netdev->phydev);
+}
+
+static int tsnep_phy_open(struct tsnep_adapter *adapter)
+{
+ struct phy_device *phydev;
+ struct ethtool_eee ethtool_eee;
+ int retval;
+
+ retval = phy_connect_direct(adapter->netdev, adapter->phydev,
+ tsnep_phy_link_status_change,
+ adapter->phy_mode);
+ if (retval)
+ return retval;
+ phydev = adapter->netdev->phydev;
+
+ /* MAC supports only 100Mbps|1000Mbps full duplex
+ * SPE (Single Pair Ethernet) is also an option but not implemented yet
+ */
+ phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
+ phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Full_BIT);
+ phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
+ phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
+
+ /* disable EEE autoneg, EEE not supported by TSNEP */
+ memset(ðtool_eee, 0, sizeof(ethtool_eee));
+ phy_ethtool_set_eee(adapter->phydev, ðtool_eee);
+
+ adapter->phydev->irq = PHY_MAC_INTERRUPT;
+ phy_start(adapter->phydev);
+
+ return 0;
+}
+
+static void tsnep_phy_close(struct tsnep_adapter *adapter)
+{
+ phy_stop(adapter->netdev->phydev);
+ phy_disconnect(adapter->netdev->phydev);
+ adapter->netdev->phydev = NULL;
+}
+
+static void tsnep_tx_ring_cleanup(struct tsnep_tx *tx)
+{
+ struct device *dmadev = tx->adapter->dmadev;
+ int i;
+
+ memset(tx->entry, 0, sizeof(tx->entry));
+
+ for (i = 0; i < TSNEP_RING_PAGE_COUNT; i++) {
+ if (tx->page[i]) {
+ dma_free_coherent(dmadev, PAGE_SIZE, tx->page[i],
+ tx->page_dma[i]);
+ tx->page[i] = NULL;
+ tx->page_dma[i] = 0;
+ }
+ }
+}
+
+static int tsnep_tx_ring_init(struct tsnep_tx *tx)
+{
+ struct device *dmadev = tx->adapter->dmadev;
+ struct tsnep_tx_entry *entry;
+ struct tsnep_tx_entry *next_entry;
+ int i, j;
+ int retval;
+
+ for (i = 0; i < TSNEP_RING_PAGE_COUNT; i++) {
+ tx->page[i] =
+ dma_alloc_coherent(dmadev, PAGE_SIZE, &tx->page_dma[i],
+ GFP_KERNEL);
+ if (!tx->page[i]) {
+ retval = -ENOMEM;
+ goto alloc_failed;
+ }
+ for (j = 0; j < TSNEP_RING_ENTRIES_PER_PAGE; j++) {
+ entry = &tx->entry[TSNEP_RING_ENTRIES_PER_PAGE * i + j];
+ entry->desc_wb = (struct tsnep_tx_desc_wb *)
+ (((u8 *)tx->page[i]) + TSNEP_DESC_SIZE * j);
+ entry->desc = (struct tsnep_tx_desc *)
+ (((u8 *)entry->desc_wb) + TSNEP_DESC_OFFSET);
+ entry->desc_dma = tx->page_dma[i] + TSNEP_DESC_SIZE * j;
+ }
+ }
+ for (i = 0; i < TSNEP_RING_SIZE; i++) {
+ entry = &tx->entry[i];
+ next_entry = &tx->entry[(i + 1) % TSNEP_RING_SIZE];
+ entry->desc->next = __cpu_to_le64(next_entry->desc_dma);
+ }
+
+ return 0;
+
+alloc_failed:
+ tsnep_tx_ring_cleanup(tx);
+ return retval;
+}
+
+static void tsnep_tx_activate(struct tsnep_tx *tx, int index, bool last)
+{
+ struct tsnep_tx_entry *entry = &tx->entry[index];
+
+ entry->properties = 0;
+ if (entry->skb) {
+ entry->properties =
+ skb_pagelen(entry->skb) & TSNEP_DESC_LENGTH_MASK;
+ entry->properties |= TSNEP_DESC_INTERRUPT_FLAG;
+ if (skb_shinfo(entry->skb)->tx_flags & SKBTX_IN_PROGRESS)
+ entry->properties |= TSNEP_DESC_EXTENDED_WRITEBACK_FLAG;
+
+ /* toggle user flag to prevent false acknowledge
+ *
+ * Only the first fragment is acknowledged. For all other
+ * fragments no acknowledge is done and the last written owner
+ * counter stays in the writeback descriptor. Therefore, it is
+ * possible that the last written owner counter is identical to
+ * the new incremented owner counter and a false acknowledge is
+ * detected before the real acknowledge has been done by
+ * hardware.
+ *
+ * The user flag is used to prevent this situation. The user
+ * flag is copied to the writeback descriptor by the hardware
+ * and is used as additional acknowledge data. By toggeling the
+ * user flag only for the first fragment (which is
+ * acknowledged), it is guaranteed that the last acknowledge
+ * done for this descriptor has used a different user flag and
+ * cannot be detected as false acknowledge.
+ */
+ entry->owner_user_flag = !entry->owner_user_flag;
+ }
+ if (last)
+ entry->properties |= TSNEP_TX_DESC_LAST_FRAGMENT_FLAG;
+ if (index == tx->increment_owner_counter) {
+ tx->owner_counter++;
+ if (tx->owner_counter == 4)
+ tx->owner_counter = 1;
+ tx->increment_owner_counter--;
+ if (tx->increment_owner_counter < 0)
+ tx->increment_owner_counter = TSNEP_RING_SIZE - 1;
+ }
+ entry->properties |=
+ (tx->owner_counter << TSNEP_DESC_OWNER_COUNTER_SHIFT) &
+ TSNEP_DESC_OWNER_COUNTER_MASK;
+ if (entry->owner_user_flag)
+ entry->properties |= TSNEP_TX_DESC_OWNER_USER_FLAG;
+ entry->desc->more_properties =
+ __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
+
+ dma_wmb();
+
+ entry->desc->properties = __cpu_to_le32(entry->properties);
+}
+
+static int tsnep_tx_desc_available(struct tsnep_tx *tx)
+{
+ if (tx->read <= tx->write)
+ return TSNEP_RING_SIZE - tx->write + tx->read - 1;
+ else
+ return tx->read - tx->write - 1;
+}
+
+static int tsnep_tx_map(struct sk_buff *skb, struct tsnep_tx *tx, int count)
+{
+ struct device *dmadev = tx->adapter->dmadev;
+ struct tsnep_tx_entry *entry;
+ unsigned int len;
+ dma_addr_t dma;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ entry = &tx->entry[(tx->write + i) % TSNEP_RING_SIZE];
+
+ if (i == 0) {
+ len = skb_headlen(skb);
+ dma = dma_map_single(dmadev, skb->data, len,
+ DMA_TO_DEVICE);
+ } else {
+ len = skb_frag_size(&skb_shinfo(skb)->frags[i - 1]);
+ dma = skb_frag_dma_map(dmadev,
+ &skb_shinfo(skb)->frags[i - 1],
+ 0, len, DMA_TO_DEVICE);
+ }
+ if (dma_mapping_error(dmadev, dma))
+ return -ENOMEM;
+
+ dma_unmap_len_set(entry, len, len);
+ dma_unmap_addr_set(entry, dma, dma);
+
+ entry->desc->tx = __cpu_to_le64(dma);
+ }
+
+ return 0;
+}
+
+static void tsnep_tx_unmap(struct tsnep_tx *tx, int count)
+{
+ struct device *dmadev = tx->adapter->dmadev;
+ struct tsnep_tx_entry *entry;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ entry = &tx->entry[(tx->read + i) % TSNEP_RING_SIZE];
+
+ if (dma_unmap_len(entry, len)) {
+ if (i == 0)
+ dma_unmap_single(dmadev,
+ dma_unmap_addr(entry, dma),
+ dma_unmap_len(entry, len),
+ DMA_TO_DEVICE);
+ else
+ dma_unmap_page(dmadev,
+ dma_unmap_addr(entry, dma),
+ dma_unmap_len(entry, len),
+ DMA_TO_DEVICE);
+ dma_unmap_len_set(entry, len, 0);
+ }
+ }
+}
+
+static netdev_tx_t tsnep_xmit_frame_ring(struct sk_buff *skb,
+ struct tsnep_tx *tx)
+{
+ unsigned long flags;
+ int count = 1;
+ struct tsnep_tx_entry *entry;
+ int i;
+ int retval;
+
+ if (skb_shinfo(skb)->nr_frags > 0)
+ count += skb_shinfo(skb)->nr_frags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ if (tsnep_tx_desc_available(tx) < count) {
+ /* ring full, shall not happen because queue is stopped if full
+ * below
+ */
+ netif_stop_queue(tx->adapter->netdev);
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ return NETDEV_TX_BUSY;
+ }
+
+ entry = &tx->entry[tx->write];
+ entry->skb = skb;
+
+ retval = tsnep_tx_map(skb, tx, count);
+ if (retval != 0) {
+ tsnep_tx_unmap(tx, count);
+ dev_kfree_skb_any(entry->skb);
+ entry->skb = NULL;
+
+ tx->dropped++;
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ netdev_err(tx->adapter->netdev, "TX DMA map failed\n");
+
+ return NETDEV_TX_OK;
+ }
+
+ if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+
+ for (i = 0; i < count; i++)
+ tsnep_tx_activate(tx, (tx->write + i) % TSNEP_RING_SIZE,
+ i == (count - 1));
+ skb_tx_timestamp(skb);
+
+ /* entry->properties shall be valid before write pointer is
+ * incrememted
+ */
+ wmb();
+ tx->write = (tx->write + count) % TSNEP_RING_SIZE;
+
+ iowrite32(TSNEP_CONTROL_TX_ENABLE, tx->addr + TSNEP_CONTROL);
+
+ if (tsnep_tx_desc_available(tx) < (MAX_SKB_FRAGS + 1)) {
+ /* ring can get full with next frame */
+ netif_stop_queue(tx->adapter->netdev);
+ }
+
+ tx->packets++;
+ tx->bytes += skb_pagelen(entry->skb) + ETH_FCS_LEN;
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ return NETDEV_TX_OK;
+}
+
+static bool tsnep_tx_poll(struct tsnep_tx *tx, int napi_budget)
+{
+ unsigned long flags;
+ int budget = 128;
+ struct tsnep_tx_entry *entry;
+ int count;
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ do {
+ if (tx->read == tx->write)
+ break;
+
+ entry = &tx->entry[tx->read];
+ if ((__le32_to_cpu(entry->desc_wb->properties) &
+ TSNEP_TX_DESC_OWNER_MASK) !=
+ (entry->properties & TSNEP_TX_DESC_OWNER_MASK))
+ break;
+
+ dma_rmb();
+
+ count = 1;
+ if (skb_shinfo(entry->skb)->nr_frags > 0)
+ count += skb_shinfo(entry->skb)->nr_frags;
+
+ tsnep_tx_unmap(tx, count);
+
+ if ((skb_shinfo(entry->skb)->tx_flags & SKBTX_IN_PROGRESS) &&
+ (__le32_to_cpu(entry->desc_wb->properties) &
+ TSNEP_DESC_EXTENDED_WRITEBACK_FLAG)) {
+ struct skb_shared_hwtstamps hwtstamps;
+ u64 timestamp =
+ __le64_to_cpu(entry->desc_wb->timestamp);
+
+ memset(&hwtstamps, 0, sizeof(hwtstamps));
+ hwtstamps.hwtstamp = ns_to_ktime(timestamp);
+
+ skb_tstamp_tx(entry->skb, &hwtstamps);
+ }
+
+ napi_consume_skb(entry->skb, budget);
+ entry->skb = NULL;
+
+ /* descriptor shall be free before read pointer is incremented
+ */
+ wmb();
+
+ tx->read = (tx->read + count) % TSNEP_RING_SIZE;
+
+ budget--;
+ } while (likely(budget));
+
+ if ((tsnep_tx_desc_available(tx) >= ((MAX_SKB_FRAGS + 1) * 2)) &&
+ netif_queue_stopped(tx->adapter->netdev)) {
+ netif_wake_queue(tx->adapter->netdev);
+ }
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ return (budget != 0);
+}
+
+static int tsnep_tx_open(struct tsnep_adapter *adapter, void __iomem *addr,
+ struct tsnep_tx *tx)
+{
+ dma_addr_t dma;
+ int retval;
+
+ memset(tx, 0, sizeof(*tx));
+ tx->adapter = adapter;
+ tx->addr = addr;
+
+ retval = tsnep_tx_ring_init(tx);
+ if (retval)
+ return retval;
+
+ dma = tx->entry[0].desc_dma | TSNEP_RESET_OWNER_COUNTER;
+ iowrite32(DMA_ADDR_LOW(dma), tx->addr + TSNEP_TX_DESC_ADDR_LOW);
+ iowrite32(DMA_ADDR_HIGH(dma), tx->addr + TSNEP_TX_DESC_ADDR_HIGH);
+ tx->owner_counter = 1;
+ tx->increment_owner_counter = TSNEP_RING_SIZE - 1;
+
+ spin_lock_init(&tx->lock);
+
+ return 0;
+}
+
+static void tsnep_tx_close(struct tsnep_tx *tx)
+{
+ u32 val;
+
+ readx_poll_timeout(ioread32, tx->addr + TSNEP_CONTROL, val,
+ ((val & TSNEP_CONTROL_TX_ENABLE) == 0), 10000,
+ 1000000);
+
+ tsnep_tx_ring_cleanup(tx);
+}
+
+static void tsnep_rx_ring_cleanup(struct tsnep_rx *rx)
+{
+ struct device *dmadev = rx->adapter->dmadev;
+ struct tsnep_rx_entry *entry;
+ int i;
+
+ for (i = 0; i < TSNEP_RING_SIZE; i++) {
+ entry = &rx->entry[i];
+ if (entry->dma)
+ dma_unmap_single(dmadev, entry->dma, entry->len,
+ DMA_FROM_DEVICE);
+ if (entry->skb)
+ dev_kfree_skb(entry->skb);
+ }
+
+ memset(rx->entry, 0, sizeof(rx->entry));
+
+ for (i = 0; i < TSNEP_RING_PAGE_COUNT; i++) {
+ if (rx->page[i]) {
+ dma_free_coherent(dmadev, PAGE_SIZE, rx->page[i],
+ rx->page_dma[i]);
+ rx->page[i] = NULL;
+ rx->page_dma[i] = 0;
+ }
+ }
+}
+
+static int tsnep_rx_alloc_and_map_skb(struct tsnep_rx *rx,
+ struct tsnep_rx_entry *entry)
+{
+ struct device *dmadev = rx->adapter->dmadev;
+ struct sk_buff *skb;
+ dma_addr_t dma;
+
+ skb = __netdev_alloc_skb(rx->adapter->netdev, RX_SKB_ALLOC_LENGTH,
+ GFP_ATOMIC | GFP_DMA);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_reserve(skb, RX_SKB_RESERVE);
+
+ dma = dma_map_single(dmadev, skb->data, RX_SKB_LENGTH,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(dmadev, dma)) {
+ dev_kfree_skb(skb);
+ return -ENOMEM;
+ }
+
+ entry->skb = skb;
+ dma_unmap_len_set(entry, len, RX_SKB_LENGTH);
+ dma_unmap_addr_set(entry, dma, dma);
+ entry->desc->rx = __cpu_to_le64(dma);
+
+ return 0;
+}
+
+static int tsnep_rx_ring_init(struct tsnep_rx *rx)
+{
+ struct device *dmadev = rx->adapter->dmadev;
+ struct tsnep_rx_entry *entry;
+ struct tsnep_rx_entry *next_entry;
+ int i, j;
+ int retval;
+
+ for (i = 0; i < TSNEP_RING_PAGE_COUNT; i++) {
+ rx->page[i] =
+ dma_alloc_coherent(dmadev, PAGE_SIZE, &rx->page_dma[i],
+ GFP_KERNEL);
+ if (!rx->page[i]) {
+ retval = -ENOMEM;
+ goto failed;
+ }
+ for (j = 0; j < TSNEP_RING_ENTRIES_PER_PAGE; j++) {
+ entry = &rx->entry[TSNEP_RING_ENTRIES_PER_PAGE * i + j];
+ entry->desc_wb = (struct tsnep_rx_desc_wb *)
+ (((u8 *)rx->page[i]) + TSNEP_DESC_SIZE * j);
+ entry->desc = (struct tsnep_rx_desc *)
+ (((u8 *)entry->desc_wb) + TSNEP_DESC_OFFSET);
+ entry->desc_dma = rx->page_dma[i] + TSNEP_DESC_SIZE * j;
+ }
+ }
+ for (i = 0; i < TSNEP_RING_SIZE; i++) {
+ entry = &rx->entry[i];
+ next_entry = &rx->entry[(i + 1) % TSNEP_RING_SIZE];
+ entry->desc->next = __cpu_to_le64(next_entry->desc_dma);
+
+ retval = tsnep_rx_alloc_and_map_skb(rx, entry);
+ if (retval)
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ tsnep_rx_ring_cleanup(rx);
+ return retval;
+}
+
+static void tsnep_rx_activate(struct tsnep_rx *rx, int index)
+{
+ struct tsnep_rx_entry *entry = &rx->entry[index];
+
+ /* RX_SKB_LENGTH is a multiple of 4 */
+ entry->properties = entry->len & TSNEP_DESC_LENGTH_MASK;
+ entry->properties |= TSNEP_DESC_INTERRUPT_FLAG;
+ if (index == rx->increment_owner_counter) {
+ rx->owner_counter++;
+ if (rx->owner_counter == 4)
+ rx->owner_counter = 1;
+ rx->increment_owner_counter--;
+ if (rx->increment_owner_counter < 0)
+ rx->increment_owner_counter = TSNEP_RING_SIZE - 1;
+ }
+ entry->properties |=
+ (rx->owner_counter << TSNEP_DESC_OWNER_COUNTER_SHIFT) &
+ TSNEP_DESC_OWNER_COUNTER_MASK;
+
+ dma_wmb();
+
+ entry->desc->properties = __cpu_to_le32(entry->properties);
+}
+
+static int tsnep_rx_poll(struct tsnep_rx *rx, struct napi_struct *napi,
+ int budget)
+{
+ struct device *dmadev = rx->adapter->dmadev;
+ int done = 0;
+ struct tsnep_rx_entry *entry;
+ struct sk_buff *skb;
+ DEFINE_DMA_UNMAP_ADDR(dma);
+ DEFINE_DMA_UNMAP_LEN(len);
+ int length;
+ int retval;
+
+ while (likely(done < budget)) {
+ entry = &rx->entry[rx->read];
+ if ((__le32_to_cpu(entry->desc_wb->properties) &
+ TSNEP_DESC_OWNER_COUNTER_MASK) !=
+ (entry->properties & TSNEP_DESC_OWNER_COUNTER_MASK))
+ break;
+
+ dma_rmb();
+
+ skb = entry->skb;
+ dma = entry->dma;
+ len = entry->len;
+
+ /* forward skb only if allocation is successful, otherwise
+ * skb is reused and frame dropped
+ */
+ retval = tsnep_rx_alloc_and_map_skb(rx, entry);
+ if (!retval) {
+ dma_unmap_single(dmadev, dma, len, DMA_FROM_DEVICE);
+
+ length = __le32_to_cpu(entry->desc_wb->properties) &
+ TSNEP_DESC_LENGTH_MASK;
+ skb_put(skb, length - ETH_FCS_LEN);
+ if (rx->adapter->hwtstamp_config.rx_filter ==
+ HWTSTAMP_FILTER_ALL) {
+ struct skb_shared_hwtstamps *hwtstamps =
+ skb_hwtstamps(skb);
+ struct tsnep_rx_inline *rx_inline =
+ (struct tsnep_rx_inline *)skb->data;
+ u64 timestamp =
+ __le64_to_cpu(rx_inline->timestamp);
+
+ memset(hwtstamps, 0, sizeof(*hwtstamps));
+ hwtstamps->hwtstamp = ns_to_ktime(timestamp);
+ }
+ skb_pull(skb, TSNEP_RX_INLINE_METADATA_SIZE);
+ skb->protocol = eth_type_trans(skb,
+ rx->adapter->netdev);
+
+ rx->packets++;
+ rx->bytes += length - TSNEP_RX_INLINE_METADATA_SIZE;
+ if (skb->pkt_type == PACKET_MULTICAST)
+ rx->multicast++;
+
+ napi_gro_receive(napi, skb);
+ done++;
+ } else {
+ rx->dropped++;
+ }
+
+ tsnep_rx_activate(rx, rx->read);
+ iowrite32(TSNEP_CONTROL_RX_ENABLE, rx->addr + TSNEP_CONTROL);
+
+ rx->read = (rx->read + 1) % TSNEP_RING_SIZE;
+ }
+
+ return done;
+}
+
+static int tsnep_rx_open(struct tsnep_adapter *adapter, void __iomem *addr,
+ struct tsnep_rx *rx)
+{
+ dma_addr_t dma;
+ int i;
+ int retval;
+
+ memset(rx, 0, sizeof(*rx));
+ rx->adapter = adapter;
+ rx->addr = addr;
+
+ retval = tsnep_rx_ring_init(rx);
+ if (retval)
+ return retval;
+
+ dma = rx->entry[0].desc_dma | TSNEP_RESET_OWNER_COUNTER;
+ iowrite32(DMA_ADDR_LOW(dma), rx->addr + TSNEP_RX_DESC_ADDR_LOW);
+ iowrite32(DMA_ADDR_HIGH(dma), rx->addr + TSNEP_RX_DESC_ADDR_HIGH);
+ rx->owner_counter = 1;
+ rx->increment_owner_counter = TSNEP_RING_SIZE - 1;
+
+ for (i = 0; i < TSNEP_RING_SIZE; i++)
+ tsnep_rx_activate(rx, i);
+
+ iowrite32(TSNEP_CONTROL_RX_ENABLE, rx->addr + TSNEP_CONTROL);
+
+ return 0;
+}
+
+static void tsnep_rx_close(struct tsnep_rx *rx)
+{
+ u32 val;
+
+ iowrite32(TSNEP_CONTROL_RX_DISABLE, rx->addr + TSNEP_CONTROL);
+ readx_poll_timeout(ioread32, rx->addr + TSNEP_CONTROL, val,
+ ((val & TSNEP_CONTROL_RX_ENABLE) == 0), 10000,
+ 1000000);
+
+ tsnep_rx_ring_cleanup(rx);
+}
+
+static int tsnep_poll(struct napi_struct *napi, int budget)
+{
+ struct tsnep_queue *queue = container_of(napi, struct tsnep_queue,
+ napi);
+ bool complete = true;
+ int done = 0;
+
+ if (queue->tx)
+ complete = tsnep_tx_poll(queue->tx, budget);
+
+ if (queue->rx) {
+ done = tsnep_rx_poll(queue->rx, napi, budget);
+ if (done >= budget)
+ complete = false;
+ }
+
+ /* if all work not completed, return budget and keep polling */
+ if (!complete)
+ return budget;
+
+ if (likely(napi_complete_done(napi, done)))
+ tsnep_enable_irq(queue->adapter, queue->irq_mask);
+
+ return min(done, budget - 1);
+}
+
+static int tsnep_netdev_open(struct net_device *netdev)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ int i;
+ void __iomem *addr;
+ int tx_queue_index = 0;
+ int rx_queue_index = 0;
+ int retval;
+
+ retval = tsnep_phy_open(adapter);
+ if (retval)
+ return retval;
+
+ for (i = 0; i < adapter->num_queues; i++) {
+ adapter->queue[i].adapter = adapter;
+ if (adapter->queue[i].tx) {
+ addr = adapter->addr + TSNEP_QUEUE(tx_queue_index);
+ retval = tsnep_tx_open(adapter, addr,
+ adapter->queue[i].tx);
+ if (retval)
+ goto failed;
+ tx_queue_index++;
+ }
+ if (adapter->queue[i].rx) {
+ addr = adapter->addr + TSNEP_QUEUE(rx_queue_index);
+ retval = tsnep_rx_open(adapter, addr,
+ adapter->queue[i].rx);
+ if (retval)
+ goto failed;
+ rx_queue_index++;
+ }
+ }
+
+ retval = netif_set_real_num_tx_queues(adapter->netdev,
+ adapter->num_tx_queues);
+ if (retval)
+ goto failed;
+ retval = netif_set_real_num_rx_queues(adapter->netdev,
+ adapter->num_rx_queues);
+ if (retval)
+ goto failed;
+
+ for (i = 0; i < adapter->num_queues; i++) {
+ netif_napi_add(adapter->netdev, &adapter->queue[i].napi,
+ tsnep_poll, 64);
+ napi_enable(&adapter->queue[i].napi);
+
+ tsnep_enable_irq(adapter, adapter->queue[i].irq_mask);
+ }
+
+ return 0;
+
+failed:
+ for (i = 0; i < adapter->num_queues; i++) {
+ if (adapter->queue[i].rx)
+ tsnep_rx_close(adapter->queue[i].rx);
+ if (adapter->queue[i].tx)
+ tsnep_tx_close(adapter->queue[i].tx);
+ }
+ tsnep_phy_close(adapter);
+ return retval;
+}
+
+static int tsnep_netdev_close(struct net_device *netdev)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ int i;
+
+ for (i = 0; i < adapter->num_queues; i++) {
+ tsnep_disable_irq(adapter, adapter->queue[i].irq_mask);
+
+ napi_disable(&adapter->queue[i].napi);
+ netif_napi_del(&adapter->queue[i].napi);
+
+ if (adapter->queue[i].rx)
+ tsnep_rx_close(adapter->queue[i].rx);
+ if (adapter->queue[i].tx)
+ tsnep_tx_close(adapter->queue[i].tx);
+ }
+
+ tsnep_phy_close(adapter);
+
+ return 0;
+}
+
+static netdev_tx_t tsnep_netdev_xmit_frame(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ u16 queue_mapping = skb_get_queue_mapping(skb);
+
+ if (queue_mapping >= adapter->num_tx_queues)
+ queue_mapping = 0;
+
+ return tsnep_xmit_frame_ring(skb, &adapter->tx[queue_mapping]);
+}
+
+static int tsnep_netdev_ioctl(struct net_device *netdev, struct ifreq *ifr,
+ int cmd)
+{
+ if (!netif_running(netdev))
+ return -EINVAL;
+ if (cmd == SIOCSHWTSTAMP || cmd == SIOCGHWTSTAMP)
+ return tsnep_ptp_ioctl(netdev, ifr, cmd);
+ return phy_mii_ioctl(netdev->phydev, ifr, cmd);
+}
+
+static void tsnep_netdev_set_multicast(struct net_device *netdev)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+
+ u16 rx_filter = 0;
+
+ /* configured MAC address and broadcasts are never filtered */
+ if (netdev->flags & IFF_PROMISC) {
+ rx_filter |= TSNEP_RX_FILTER_ACCEPT_ALL_MULTICASTS;
+ rx_filter |= TSNEP_RX_FILTER_ACCEPT_ALL_UNICASTS;
+ } else if (!netdev_mc_empty(netdev) || (netdev->flags & IFF_ALLMULTI)) {
+ rx_filter |= TSNEP_RX_FILTER_ACCEPT_ALL_MULTICASTS;
+ }
+ iowrite16(rx_filter, adapter->addr + TSNEP_RX_FILTER);
+}
+
+static void tsnep_netdev_get_stats64(struct net_device *netdev,
+ struct rtnl_link_stats64 *stats)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ u32 reg;
+ u32 val;
+ int i;
+
+ for (i = 0; i < adapter->num_tx_queues; i++) {
+ stats->tx_packets += adapter->tx[i].packets;
+ stats->tx_bytes += adapter->tx[i].bytes;
+ stats->tx_dropped += adapter->tx[i].dropped;
+ }
+ for (i = 0; i < adapter->num_rx_queues; i++) {
+ stats->rx_packets += adapter->rx[i].packets;
+ stats->rx_bytes += adapter->rx[i].bytes;
+ stats->rx_dropped += adapter->rx[i].dropped;
+ stats->multicast += adapter->rx[i].multicast;
+
+ reg = ioread32(adapter->addr + TSNEP_QUEUE(i) +
+ TSNEP_RX_STATISTIC);
+ val = (reg & TSNEP_RX_STATISTIC_NO_DESC_MASK) >>
+ TSNEP_RX_STATISTIC_NO_DESC_SHIFT;
+ stats->rx_dropped += val;
+ val = (reg & TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_MASK) >>
+ TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_SHIFT;
+ stats->rx_dropped += val;
+ val = (reg & TSNEP_RX_STATISTIC_FIFO_OVERFLOW_MASK) >>
+ TSNEP_RX_STATISTIC_FIFO_OVERFLOW_SHIFT;
+ stats->rx_errors += val;
+ stats->rx_fifo_errors += val;
+ val = (reg & TSNEP_RX_STATISTIC_INVALID_FRAME_MASK) >>
+ TSNEP_RX_STATISTIC_INVALID_FRAME_SHIFT;
+ stats->rx_errors += val;
+ stats->rx_frame_errors += val;
+ }
+
+ reg = ioread32(adapter->addr + ECM_STAT);
+ val = (reg & ECM_STAT_RX_ERR_MASK) >> ECM_STAT_RX_ERR_SHIFT;
+ stats->rx_errors += val;
+ val = (reg & ECM_STAT_INV_FRM_MASK) >> ECM_STAT_INV_FRM_SHIFT;
+ stats->rx_errors += val;
+ stats->rx_crc_errors += val;
+ val = (reg & ECM_STAT_FWD_RX_ERR_MASK) >> ECM_STAT_FWD_RX_ERR_SHIFT;
+ stats->rx_errors += val;
+}
+
+static void tsnep_mac_set_address(struct tsnep_adapter *adapter, u8 *addr)
+{
+ iowrite32(*(u32 *)addr, adapter->addr + TSNEP_MAC_ADDRESS_LOW);
+ iowrite16(*(u16 *)(addr + sizeof(u32)),
+ adapter->addr + TSNEP_MAC_ADDRESS_HIGH);
+
+ ether_addr_copy(adapter->mac_address, addr);
+ netif_info(adapter, drv, adapter->netdev, "MAC address set to %pM\n",
+ addr);
+}
+
+static int tsnep_netdev_set_mac_address(struct net_device *netdev, void *addr)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ struct sockaddr *sock_addr = addr;
+ int retval;
+
+ retval = eth_prepare_mac_addr_change(netdev, sock_addr);
+ if (retval)
+ return retval;
+ ether_addr_copy(netdev->dev_addr, sock_addr->sa_data);
+ tsnep_mac_set_address(adapter, sock_addr->sa_data);
+
+ return 0;
+}
+
+static const struct net_device_ops tsnep_netdev_ops = {
+ .ndo_open = tsnep_netdev_open,
+ .ndo_stop = tsnep_netdev_close,
+ .ndo_start_xmit = tsnep_netdev_xmit_frame,
+ .ndo_eth_ioctl = tsnep_netdev_ioctl,
+ .ndo_set_rx_mode = tsnep_netdev_set_multicast,
+
+ .ndo_get_stats64 = tsnep_netdev_get_stats64,
+ .ndo_set_mac_address = tsnep_netdev_set_mac_address,
+ .ndo_setup_tc = tsnep_tc_setup,
+};
+
+static int tsnep_mac_init(struct tsnep_adapter *adapter)
+{
+ int retval;
+
+ /* initialize RX filtering, at least configured MAC address and
+ * broadcast are not filtered
+ */
+ iowrite16(0, adapter->addr + TSNEP_RX_FILTER);
+
+ /* try to get MAC address in the following order:
+ * - device tree
+ * - valid MAC address already set
+ * - MAC address register if valid
+ * - random MAC address
+ */
+ retval = of_get_mac_address(adapter->pdev->dev.of_node,
+ adapter->mac_address);
+ if (retval == -EPROBE_DEFER)
+ return retval;
+ if (retval && !is_valid_ether_addr(adapter->mac_address)) {
+ *(u32 *)adapter->mac_address =
+ ioread32(adapter->addr + TSNEP_MAC_ADDRESS_LOW);
+ *(u16 *)(adapter->mac_address + sizeof(u32)) =
+ ioread16(adapter->addr + TSNEP_MAC_ADDRESS_HIGH);
+ if (!is_valid_ether_addr(adapter->mac_address))
+ eth_random_addr(adapter->mac_address);
+ }
+
+ tsnep_mac_set_address(adapter, adapter->mac_address);
+ ether_addr_copy(adapter->netdev->dev_addr, adapter->mac_address);
+
+ return 0;
+}
+
+static int tsnep_mdio_init(struct tsnep_adapter *adapter)
+{
+ struct device_node *np = adapter->pdev->dev.of_node;
+ int retval;
+
+ if (np) {
+ np = of_get_child_by_name(np, "mdio");
+ if (!np)
+ return 0;
+
+ adapter->suppress_preamble =
+ of_property_read_bool(np, "suppress-preamble");
+ }
+
+ adapter->mdiobus = devm_mdiobus_alloc(&adapter->pdev->dev);
+ if (!adapter->mdiobus) {
+ retval = -ENOMEM;
+
+ goto out;
+ }
+
+ adapter->mdiobus->priv = (void *)adapter;
+ adapter->mdiobus->parent = &adapter->pdev->dev;
+ adapter->mdiobus->read = tsnep_mdiobus_read;
+ adapter->mdiobus->write = tsnep_mdiobus_write;
+ adapter->mdiobus->name = TSNEP "-mdiobus";
+ snprintf(adapter->mdiobus->id, MII_BUS_ID_SIZE, "%s",
+ adapter->pdev->name);
+
+ if (np) {
+ retval = of_mdiobus_register(adapter->mdiobus, np);
+
+ of_node_put(np);
+ } else {
+ /* do not scan broadcast address */
+ adapter->mdiobus->phy_mask = 0x0000001;
+
+ retval = mdiobus_register(adapter->mdiobus);
+ }
+out:
+
+ return retval;
+}
+
+static int tsnep_phy_init(struct tsnep_adapter *adapter)
+{
+ struct device_node *phy_node;
+ int retval;
+
+ retval = of_get_phy_mode(adapter->pdev->dev.of_node,
+ &adapter->phy_mode);
+ if (retval)
+ adapter->phy_mode = PHY_INTERFACE_MODE_GMII;
+
+ phy_node = of_parse_phandle(adapter->pdev->dev.of_node, "phy-handle",
+ 0);
+ adapter->phydev = of_phy_find_device(phy_node);
+ of_node_put(phy_node);
+ if (!adapter->phydev && adapter->mdiobus)
+ adapter->phydev = phy_find_first(adapter->mdiobus);
+ if (!adapter->phydev)
+ return -EIO;
+
+ return 0;
+}
+
+static int tsnep_probe(struct platform_device *pdev)
+{
+ struct tsnep_adapter *adapter;
+ struct net_device *netdev;
+ struct resource *io;
+ u32 type;
+ int revision;
+ int version;
+ int retval;
+
+ netdev = devm_alloc_etherdev_mqs(&pdev->dev,
+ sizeof(struct tsnep_adapter),
+ TSNEP_MAX_QUEUES, TSNEP_MAX_QUEUES);
+ if (!netdev)
+ return -ENODEV;
+ SET_NETDEV_DEV(netdev, &pdev->dev);
+ adapter = netdev_priv(netdev);
+ platform_set_drvdata(pdev, adapter);
+ adapter->pdev = pdev;
+ adapter->dmadev = &pdev->dev;
+ adapter->netdev = netdev;
+ adapter->msg_enable = NETIF_MSG_DRV | NETIF_MSG_PROBE |
+ NETIF_MSG_LINK | NETIF_MSG_IFUP |
+ NETIF_MSG_IFDOWN | NETIF_MSG_TX_QUEUED;
+
+ netdev->min_mtu = ETH_MIN_MTU;
+ netdev->max_mtu = TSNEP_MAX_FRAME_SIZE;
+
+ mutex_init(&adapter->gate_control_lock);
+
+ io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ adapter->addr = devm_ioremap_resource(&pdev->dev, io);
+ if (IS_ERR(adapter->addr))
+ return PTR_ERR(adapter->addr);
+ adapter->size = io->end - io->start + 1;
+ adapter->irq = platform_get_irq(pdev, 0);
+ netdev->mem_start = io->start;
+ netdev->mem_end = io->end;
+ netdev->irq = adapter->irq;
+
+ type = ioread32(adapter->addr + ECM_TYPE);
+ revision = (type & ECM_REVISION_MASK) >> ECM_REVISION_SHIFT;
+ version = (type & ECM_VERSION_MASK) >> ECM_VERSION_SHIFT;
+ adapter->gate_control = type & ECM_GATE_CONTROL;
+
+ adapter->num_tx_queues = TSNEP_QUEUES;
+ adapter->num_rx_queues = TSNEP_QUEUES;
+ adapter->num_queues = TSNEP_QUEUES;
+ adapter->queue[0].tx = &adapter->tx[0];
+ adapter->queue[0].rx = &adapter->rx[0];
+ adapter->queue[0].irq_mask = ECM_INT_TX_0 | ECM_INT_RX_0;
+
+ tsnep_disable_irq(adapter, ECM_INT_ALL);
+ retval = devm_request_irq(&adapter->pdev->dev, adapter->irq, tsnep_irq,
+ 0, TSNEP, adapter);
+ if (retval != 0) {
+ dev_err(&adapter->pdev->dev, "can't get assigned irq %d.\n",
+ adapter->irq);
+ return retval;
+ }
+ tsnep_enable_irq(adapter, ECM_INT_LINK);
+
+ retval = tsnep_mac_init(adapter);
+ if (retval)
+ goto mac_init_failed;
+
+ retval = tsnep_mdio_init(adapter);
+ if (retval)
+ goto mdio_init_failed;
+
+ retval = tsnep_phy_init(adapter);
+ if (retval)
+ goto phy_init_failed;
+
+ retval = tsnep_ptp_init(adapter);
+ if (retval)
+ goto ptp_init_failed;
+
+ retval = tsnep_tc_init(adapter);
+ if (retval)
+ goto tc_init_failed;
+
+ netdev->netdev_ops = &tsnep_netdev_ops;
+ netdev->ethtool_ops = &tsnep_ethtool_ops;
+ netdev->features = NETIF_F_SG;
+ netdev->hw_features = netdev->features;
+
+ /* carrier off reporting is important to ethtool even BEFORE open */
+ netif_carrier_off(netdev);
+
+ retval = register_netdev(netdev);
+ if (retval)
+ goto register_failed;
+
+ dev_info(&adapter->pdev->dev, "device version %d.%02d\n", version,
+ revision);
+ if (adapter->gate_control)
+ dev_info(&adapter->pdev->dev, "gate control detected\n");
+
+ return 0;
+
+register_failed:
+ tsnep_tc_cleanup(adapter);
+tc_init_failed:
+ tsnep_ptp_cleanup(adapter);
+ptp_init_failed:
+phy_init_failed:
+ if (adapter->mdiobus)
+ mdiobus_unregister(adapter->mdiobus);
+mdio_init_failed:
+mac_init_failed:
+ tsnep_disable_irq(adapter, ECM_INT_ALL);
+ return retval;
+}
+
+static int tsnep_remove(struct platform_device *pdev)
+{
+ struct tsnep_adapter *adapter = platform_get_drvdata(pdev);
+
+ unregister_netdev(adapter->netdev);
+
+ tsnep_tc_cleanup(adapter);
+
+ tsnep_ptp_cleanup(adapter);
+
+ if (adapter->mdiobus)
+ mdiobus_unregister(adapter->mdiobus);
+
+ tsnep_disable_irq(adapter, ECM_INT_ALL);
+
+ return 0;
+}
+
+static const struct of_device_id tsnep_of_match[] = {
+ { .compatible = "engleder,tsnep", },
+{ },
+};
+MODULE_DEVICE_TABLE(of, tsnep_of_match);
+
+static struct platform_driver tsnep_driver = {
+ .driver = {
+ .name = TSNEP,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(tsnep_of_match),
+ },
+ .probe = tsnep_probe,
+ .remove = tsnep_remove,
+};
+module_platform_driver(tsnep_driver);
+
+MODULE_AUTHOR("Gerhard Engleder <gerhard@engleder-embedded.com>");
+MODULE_DESCRIPTION("TSN endpoint Ethernet MAC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/engleder/tsnep_ptp.c b/drivers/net/ethernet/engleder/tsnep_ptp.c
new file mode 100644
index 000000000000..4bfb4d8508f5
--- /dev/null
+++ b/drivers/net/ethernet/engleder/tsnep_ptp.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
+
+#include "tsnep.h"
+
+void tsnep_get_system_time(struct tsnep_adapter *adapter, u64 *time)
+{
+ u32 high_before;
+ u32 low;
+ u32 high;
+
+ /* read high dword twice to detect overrun */
+ high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ do {
+ low = ioread32(adapter->addr + ECM_SYSTEM_TIME_LOW);
+ high_before = high;
+ high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ } while (high != high_before);
+ *time = (((u64)high) << 32) | ((u64)low);
+}
+
+int tsnep_ptp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+ struct hwtstamp_config config;
+
+ if (!ifr)
+ return -EINVAL;
+
+ if (cmd == SIOCSHWTSTAMP) {
+ if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
+ return -EFAULT;
+
+ if (config.flags)
+ return -EINVAL;
+
+ switch (config.tx_type) {
+ case HWTSTAMP_TX_OFF:
+ case HWTSTAMP_TX_ON:
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (config.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ break;
+ case HWTSTAMP_FILTER_ALL:
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ case HWTSTAMP_FILTER_NTP_ALL:
+ config.rx_filter = HWTSTAMP_FILTER_ALL;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ memcpy(&adapter->hwtstamp_config, &config,
+ sizeof(adapter->hwtstamp_config));
+ }
+
+ if (copy_to_user(ifr->ifr_data, &adapter->hwtstamp_config,
+ sizeof(adapter->hwtstamp_config)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int tsnep_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
+ ptp_clock_info);
+ bool negative = false;
+ u64 rate_offset;
+
+ if (scaled_ppm < 0) {
+ scaled_ppm = -scaled_ppm;
+ negative = true;
+ }
+
+ /* convert from 16 bit to 32 bit binary fractional, divide by 1000000 to
+ * eliminate ppm, multiply with 8 to compensate 8ns clock cycle time,
+ * simplify calculation because 15625 * 8 = 1000000 / 8
+ */
+ rate_offset = scaled_ppm;
+ rate_offset <<= 16 - 3;
+ rate_offset = div_u64(rate_offset, 15625);
+
+ rate_offset &= ECM_CLOCK_RATE_OFFSET_MASK;
+ if (negative)
+ rate_offset |= ECM_CLOCK_RATE_OFFSET_SIGN;
+ iowrite32(rate_offset & 0xFFFFFFFF, adapter->addr + ECM_CLOCK_RATE);
+
+ return 0;
+}
+
+static int tsnep_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
+ ptp_clock_info);
+ u64 system_time;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->ptp_lock, flags);
+
+ tsnep_get_system_time(adapter, &system_time);
+
+ system_time += delta;
+
+ /* high dword is buffered in hardware and synchronously written to
+ * system time when low dword is written
+ */
+ iowrite32(system_time >> 32, adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ iowrite32(system_time & 0xFFFFFFFF,
+ adapter->addr + ECM_SYSTEM_TIME_LOW);
+
+ spin_unlock_irqrestore(&adapter->ptp_lock, flags);
+
+ return 0;
+}
+
+static int tsnep_ptp_gettimex64(struct ptp_clock_info *ptp,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
+ ptp_clock_info);
+ u32 high_before;
+ u32 low;
+ u32 high;
+ u64 system_time;
+
+ /* read high dword twice to detect overrun */
+ high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ do {
+ ptp_read_system_prets(sts);
+ low = ioread32(adapter->addr + ECM_SYSTEM_TIME_LOW);
+ ptp_read_system_postts(sts);
+ high_before = high;
+ high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ } while (high != high_before);
+ system_time = (((u64)high) << 32) | ((u64)low);
+
+ *ts = ns_to_timespec64(system_time);
+
+ return 0;
+}
+
+static int tsnep_ptp_settime64(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct tsnep_adapter *adapter = container_of(ptp, struct tsnep_adapter,
+ ptp_clock_info);
+ u64 system_time = timespec64_to_ns(ts);
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->ptp_lock, flags);
+
+ /* high dword is buffered in hardware and synchronously written to
+ * system time when low dword is written
+ */
+ iowrite32(system_time >> 32, adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ iowrite32(system_time & 0xFFFFFFFF,
+ adapter->addr + ECM_SYSTEM_TIME_LOW);
+
+ spin_unlock_irqrestore(&adapter->ptp_lock, flags);
+
+ return 0;
+}
+
+int tsnep_ptp_init(struct tsnep_adapter *adapter)
+{
+ int retval = 0;
+
+ adapter->hwtstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
+ adapter->hwtstamp_config.tx_type = HWTSTAMP_TX_OFF;
+
+ snprintf(adapter->ptp_clock_info.name, 16, "%s", TSNEP);
+ adapter->ptp_clock_info.owner = THIS_MODULE;
+ /* at most 2^-1ns adjustment every clock cycle for 8ns clock cycle time,
+ * stay slightly below because only bits below 2^-1ns are supported
+ */
+ adapter->ptp_clock_info.max_adj = (500000000 / 8 - 1);
+ adapter->ptp_clock_info.adjfine = tsnep_ptp_adjfine;
+ adapter->ptp_clock_info.adjtime = tsnep_ptp_adjtime;
+ adapter->ptp_clock_info.gettimex64 = tsnep_ptp_gettimex64;
+ adapter->ptp_clock_info.settime64 = tsnep_ptp_settime64;
+
+ spin_lock_init(&adapter->ptp_lock);
+
+ adapter->ptp_clock = ptp_clock_register(&adapter->ptp_clock_info,
+ &adapter->pdev->dev);
+ if (IS_ERR(adapter->ptp_clock)) {
+ netdev_err(adapter->netdev, "ptp_clock_register failed\n");
+
+ retval = PTR_ERR(adapter->ptp_clock);
+ adapter->ptp_clock = NULL;
+ } else if (adapter->ptp_clock) {
+ netdev_info(adapter->netdev, "PHC added\n");
+ }
+
+ return retval;
+}
+
+void tsnep_ptp_cleanup(struct tsnep_adapter *adapter)
+{
+ if (adapter->ptp_clock) {
+ ptp_clock_unregister(adapter->ptp_clock);
+ netdev_info(adapter->netdev, "PHC removed\n");
+ }
+}
diff --git a/drivers/net/ethernet/engleder/tsnep_tc.c b/drivers/net/ethernet/engleder/tsnep_tc.c
new file mode 100644
index 000000000000..c4c6e1357317
--- /dev/null
+++ b/drivers/net/ethernet/engleder/tsnep_tc.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
+
+#include "tsnep.h"
+
+#include <net/pkt_sched.h>
+
+/* save one operation at the end for additional operation at list change */
+#define TSNEP_MAX_GCL_NUM (TSNEP_GCL_COUNT - 1)
+
+static int tsnep_validate_gcl(struct tc_taprio_qopt_offload *qopt)
+{
+ int i;
+ u64 cycle_time;
+
+ if (!qopt->cycle_time)
+ return -ERANGE;
+ if (qopt->num_entries > TSNEP_MAX_GCL_NUM)
+ return -EINVAL;
+ cycle_time = 0;
+ for (i = 0; i < qopt->num_entries; i++) {
+ if (qopt->entries[i].command != TC_TAPRIO_CMD_SET_GATES)
+ return -EINVAL;
+ if (qopt->entries[i].gate_mask & ~TSNEP_GCL_MASK)
+ return -EINVAL;
+ if (qopt->entries[i].interval < TSNEP_GCL_MIN_INTERVAL)
+ return -EINVAL;
+ cycle_time += qopt->entries[i].interval;
+ }
+ if (qopt->cycle_time != cycle_time)
+ return -EINVAL;
+ if (qopt->cycle_time_extension >= qopt->cycle_time)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void tsnep_write_gcl_operation(struct tsnep_gcl *gcl, int index,
+ u32 properties, u32 interval, bool flush)
+{
+ void __iomem *addr = gcl->addr +
+ sizeof(struct tsnep_gcl_operation) * index;
+
+ gcl->operation[index].properties = properties;
+ gcl->operation[index].interval = interval;
+
+ iowrite32(properties, addr);
+ iowrite32(interval, addr + sizeof(u32));
+
+ if (flush) {
+ /* flush write with read access */
+ ioread32(addr);
+ }
+}
+
+static u64 tsnep_change_duration(struct tsnep_gcl *gcl, int index)
+{
+ u64 duration;
+ int count;
+
+ /* change needs to be triggered one or two operations before start of
+ * new gate control list
+ * - change is triggered at start of operation (minimum one operation)
+ * - operation with adjusted interval is inserted on demand to exactly
+ * meet the start of the new gate control list (optional)
+ *
+ * additionally properties are read directly after start of previous
+ * operation
+ *
+ * therefore, three operations needs to be considered for the limit
+ */
+ duration = 0;
+ count = 3;
+ while (count) {
+ duration += gcl->operation[index].interval;
+
+ index--;
+ if (index < 0)
+ index = gcl->count - 1;
+
+ count--;
+ }
+
+ return duration;
+}
+
+static void tsnep_write_gcl(struct tsnep_gcl *gcl,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ int i;
+ u32 properties;
+ u64 extend;
+ u64 cut;
+
+ gcl->base_time = ktime_to_ns(qopt->base_time);
+ gcl->cycle_time = qopt->cycle_time;
+ gcl->cycle_time_extension = qopt->cycle_time_extension;
+
+ for (i = 0; i < qopt->num_entries; i++) {
+ properties = qopt->entries[i].gate_mask;
+ if (i == (qopt->num_entries - 1))
+ properties |= TSNEP_GCL_LAST;
+
+ tsnep_write_gcl_operation(gcl, i, properties,
+ qopt->entries[i].interval, true);
+ }
+ gcl->count = qopt->num_entries;
+
+ /* calculate change limit; i.e., the time needed between enable and
+ * start of new gate control list
+ */
+
+ /* case 1: extend cycle time for change
+ * - change duration of last operation
+ * - cycle time extension
+ */
+ extend = tsnep_change_duration(gcl, gcl->count - 1);
+ extend += gcl->cycle_time_extension;
+
+ /* case 2: cut cycle time for change
+ * - maximum change duration
+ */
+ cut = 0;
+ for (i = 0; i < gcl->count; i++)
+ cut = max(cut, tsnep_change_duration(gcl, i));
+
+ /* use maximum, because the actual case (extend or cut) can be
+ * determined only after limit is known (chicken-and-egg problem)
+ */
+ gcl->change_limit = max(extend, cut);
+}
+
+static u64 tsnep_gcl_start_after(struct tsnep_gcl *gcl, u64 limit)
+{
+ u64 start = gcl->base_time;
+ u64 n;
+
+ if (start <= limit) {
+ n = div64_u64(limit - start, gcl->cycle_time);
+ start += (n + 1) * gcl->cycle_time;
+ }
+
+ return start;
+}
+
+static u64 tsnep_gcl_start_before(struct tsnep_gcl *gcl, u64 limit)
+{
+ u64 start = gcl->base_time;
+ u64 n;
+
+ n = div64_u64(limit - start, gcl->cycle_time);
+ start += n * gcl->cycle_time;
+ if (start == limit)
+ start -= gcl->cycle_time;
+
+ return start;
+}
+
+static u64 tsnep_set_gcl_change(struct tsnep_gcl *gcl, int index, u64 change,
+ bool insert)
+{
+ /* previous operation triggers change and properties are evaluated at
+ * start of operation
+ */
+ if (index == 0)
+ index = gcl->count - 1;
+ else
+ index = index - 1;
+ change -= gcl->operation[index].interval;
+
+ /* optionally change to new list with additional operation in between */
+ if (insert) {
+ void __iomem *addr = gcl->addr +
+ sizeof(struct tsnep_gcl_operation) * index;
+
+ gcl->operation[index].properties |= TSNEP_GCL_INSERT;
+ iowrite32(gcl->operation[index].properties, addr);
+ }
+
+ return change;
+}
+
+static void tsnep_clean_gcl(struct tsnep_gcl *gcl)
+{
+ int i;
+ u32 mask = TSNEP_GCL_LAST | TSNEP_GCL_MASK;
+ void __iomem *addr;
+
+ /* search for insert operation and reset properties */
+ for (i = 0; i < gcl->count; i++) {
+ if (gcl->operation[i].properties & ~mask) {
+ addr = gcl->addr +
+ sizeof(struct tsnep_gcl_operation) * i;
+
+ gcl->operation[i].properties &= mask;
+ iowrite32(gcl->operation[i].properties, addr);
+
+ break;
+ }
+ }
+}
+
+static u64 tsnep_insert_gcl_operation(struct tsnep_gcl *gcl, int ref,
+ u64 change, u32 interval)
+{
+ u32 properties;
+
+ properties = gcl->operation[ref].properties & TSNEP_GCL_MASK;
+ /* change to new list directly after inserted operation */
+ properties |= TSNEP_GCL_CHANGE;
+
+ /* last operation of list is reserved to insert operation */
+ tsnep_write_gcl_operation(gcl, TSNEP_GCL_COUNT - 1, properties,
+ interval, false);
+
+ return tsnep_set_gcl_change(gcl, ref, change, true);
+}
+
+static u64 tsnep_extend_gcl(struct tsnep_gcl *gcl, u64 start, u32 extension)
+{
+ int ref = gcl->count - 1;
+ u32 interval = gcl->operation[ref].interval + extension;
+
+ start -= gcl->operation[ref].interval;
+
+ return tsnep_insert_gcl_operation(gcl, ref, start, interval);
+}
+
+static u64 tsnep_cut_gcl(struct tsnep_gcl *gcl, u64 start, u64 cycle_time)
+{
+ u64 sum = 0;
+ int i;
+
+ /* find operation which shall be cutted */
+ for (i = 0; i < gcl->count; i++) {
+ u64 sum_tmp = sum + gcl->operation[i].interval;
+ u64 interval;
+
+ /* sum up operations as long as cycle time is not exceeded */
+ if (sum_tmp > cycle_time)
+ break;
+
+ /* remaining interval must be big enough for hardware */
+ interval = cycle_time - sum_tmp;
+ if (interval > 0 && interval < TSNEP_GCL_MIN_INTERVAL)
+ break;
+
+ sum = sum_tmp;
+ }
+ if (sum == cycle_time) {
+ /* no need to cut operation itself or whole cycle
+ * => change exactly at operation
+ */
+ return tsnep_set_gcl_change(gcl, i, start + sum, false);
+ }
+ return tsnep_insert_gcl_operation(gcl, i, start + sum,
+ cycle_time - sum);
+}
+
+static int tsnep_enable_gcl(struct tsnep_adapter *adapter,
+ struct tsnep_gcl *gcl, struct tsnep_gcl *curr)
+{
+ u64 system_time;
+ u64 timeout;
+ u64 limit;
+
+ /* estimate timeout limit after timeout enable, actually timeout limit
+ * in hardware will be earlier than estimate so we are on the safe side
+ */
+ tsnep_get_system_time(adapter, &system_time);
+ timeout = system_time + TSNEP_GC_TIMEOUT;
+
+ if (curr)
+ limit = timeout + curr->change_limit;
+ else
+ limit = timeout;
+
+ gcl->start_time = tsnep_gcl_start_after(gcl, limit);
+
+ /* gate control time register is only 32bit => time shall be in the near
+ * future (no driver support for far future implemented)
+ */
+ if ((gcl->start_time - system_time) >= U32_MAX)
+ return -EAGAIN;
+
+ if (curr) {
+ /* change gate control list */
+ u64 last;
+ u64 change;
+
+ last = tsnep_gcl_start_before(curr, gcl->start_time);
+ if ((last + curr->cycle_time) == gcl->start_time)
+ change = tsnep_cut_gcl(curr, last,
+ gcl->start_time - last);
+ else if (((gcl->start_time - last) <=
+ curr->cycle_time_extension) ||
+ ((gcl->start_time - last) <= TSNEP_GCL_MIN_INTERVAL))
+ change = tsnep_extend_gcl(curr, last,
+ gcl->start_time - last);
+ else
+ change = tsnep_cut_gcl(curr, last,
+ gcl->start_time - last);
+
+ WARN_ON(change <= timeout);
+ gcl->change = true;
+ iowrite32(change & 0xFFFFFFFF, adapter->addr + TSNEP_GC_CHANGE);
+ } else {
+ /* start gate control list */
+ WARN_ON(gcl->start_time <= timeout);
+ gcl->change = false;
+ iowrite32(gcl->start_time & 0xFFFFFFFF,
+ adapter->addr + TSNEP_GC_TIME);
+ }
+
+ return 0;
+}
+
+static int tsnep_taprio(struct tsnep_adapter *adapter,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ struct tsnep_gcl *gcl;
+ struct tsnep_gcl *curr;
+ int retval;
+
+ if (!adapter->gate_control)
+ return -EOPNOTSUPP;
+
+ if (!qopt->enable) {
+ /* disable gate control if active */
+ mutex_lock(&adapter->gate_control_lock);
+
+ if (adapter->gate_control_active) {
+ iowrite8(TSNEP_GC_DISABLE, adapter->addr + TSNEP_GC);
+ adapter->gate_control_active = false;
+ }
+
+ mutex_unlock(&adapter->gate_control_lock);
+
+ return 0;
+ }
+
+ retval = tsnep_validate_gcl(qopt);
+ if (retval)
+ return retval;
+
+ mutex_lock(&adapter->gate_control_lock);
+
+ gcl = &adapter->gcl[adapter->next_gcl];
+ tsnep_write_gcl(gcl, qopt);
+
+ /* select current gate control list if active */
+ if (adapter->gate_control_active) {
+ if (adapter->next_gcl == 0)
+ curr = &adapter->gcl[1];
+ else
+ curr = &adapter->gcl[0];
+ } else {
+ curr = NULL;
+ }
+
+ for (;;) {
+ /* start timeout which discards late enable, this helps ensuring
+ * that start/change time are in the future at enable
+ */
+ iowrite8(TSNEP_GC_ENABLE_TIMEOUT, adapter->addr + TSNEP_GC);
+
+ retval = tsnep_enable_gcl(adapter, gcl, curr);
+ if (retval) {
+ mutex_unlock(&adapter->gate_control_lock);
+
+ return retval;
+ }
+
+ /* enable gate control list */
+ if (adapter->next_gcl == 0)
+ iowrite8(TSNEP_GC_ENABLE_A, adapter->addr + TSNEP_GC);
+ else
+ iowrite8(TSNEP_GC_ENABLE_B, adapter->addr + TSNEP_GC);
+
+ /* done if timeout did not happen */
+ if (!(ioread32(adapter->addr + TSNEP_GC) &
+ TSNEP_GC_TIMEOUT_SIGNAL))
+ break;
+
+ /* timeout is acknowledged with any enable */
+ iowrite8(TSNEP_GC_ENABLE_A, adapter->addr + TSNEP_GC);
+
+ if (curr)
+ tsnep_clean_gcl(curr);
+
+ /* retry because of timeout */
+ }
+
+ adapter->gate_control_active = true;
+
+ if (adapter->next_gcl == 0)
+ adapter->next_gcl = 1;
+ else
+ adapter->next_gcl = 0;
+
+ mutex_unlock(&adapter->gate_control_lock);
+
+ return 0;
+}
+
+int tsnep_tc_setup(struct net_device *netdev, enum tc_setup_type type,
+ void *type_data)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+
+ switch (type) {
+ case TC_SETUP_QDISC_TAPRIO:
+ return tsnep_taprio(adapter, type_data);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+int tsnep_tc_init(struct tsnep_adapter *adapter)
+{
+ if (!adapter->gate_control)
+ return 0;
+
+ /* open all gates */
+ iowrite8(TSNEP_GC_DISABLE, adapter->addr + TSNEP_GC);
+ iowrite32(TSNEP_GC_OPEN | TSNEP_GC_NEXT_OPEN, adapter->addr + TSNEP_GC);
+
+ adapter->gcl[0].addr = adapter->addr + TSNEP_GCL_A;
+ adapter->gcl[1].addr = adapter->addr + TSNEP_GCL_B;
+
+ return 0;
+}
+
+void tsnep_tc_cleanup(struct tsnep_adapter *adapter)
+{
+ if (!adapter->gate_control)
+ return;
+
+ if (adapter->gate_control_active) {
+ iowrite8(TSNEP_GC_DISABLE, adapter->addr + TSNEP_GC);
+ adapter->gate_control_active = false;
+ }
+}
diff --git a/drivers/net/ethernet/engleder/tsnep_test.c b/drivers/net/ethernet/engleder/tsnep_test.c
new file mode 100644
index 000000000000..1581d6b22232
--- /dev/null
+++ b/drivers/net/ethernet/engleder/tsnep_test.c
@@ -0,0 +1,811 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
+
+#include "tsnep.h"
+
+#include <net/pkt_sched.h>
+
+enum tsnep_test {
+ TSNEP_TEST_ENABLE = 0,
+ TSNEP_TEST_TAPRIO,
+ TSNEP_TEST_TAPRIO_CHANGE,
+ TSNEP_TEST_TAPRIO_EXTENSION,
+};
+
+static const char tsnep_test_strings[][ETH_GSTRING_LEN] = {
+ "Enable timeout (offline)",
+ "TAPRIO (offline)",
+ "TAPRIO change (offline)",
+ "TAPRIO extension (offline)",
+};
+
+#define TSNEP_TEST_COUNT (sizeof(tsnep_test_strings) / ETH_GSTRING_LEN)
+
+static bool enable_gc_timeout(struct tsnep_adapter *adapter)
+{
+ iowrite8(TSNEP_GC_ENABLE_TIMEOUT, adapter->addr + TSNEP_GC);
+ if (!(ioread32(adapter->addr + TSNEP_GC) & TSNEP_GC_TIMEOUT_ACTIVE))
+ return false;
+
+ return true;
+}
+
+static bool gc_timeout_signaled(struct tsnep_adapter *adapter)
+{
+ if (ioread32(adapter->addr + TSNEP_GC) & TSNEP_GC_TIMEOUT_SIGNAL)
+ return true;
+
+ return false;
+}
+
+static bool ack_gc_timeout(struct tsnep_adapter *adapter)
+{
+ iowrite8(TSNEP_GC_ENABLE_TIMEOUT, adapter->addr + TSNEP_GC);
+ if (ioread32(adapter->addr + TSNEP_GC) &
+ (TSNEP_GC_TIMEOUT_ACTIVE | TSNEP_GC_TIMEOUT_SIGNAL))
+ return false;
+ return true;
+}
+
+static bool enable_gc(struct tsnep_adapter *adapter, bool a)
+{
+ u8 enable;
+ u8 active;
+
+ if (a) {
+ enable = TSNEP_GC_ENABLE_A;
+ active = TSNEP_GC_ACTIVE_A;
+ } else {
+ enable = TSNEP_GC_ENABLE_B;
+ active = TSNEP_GC_ACTIVE_B;
+ }
+
+ iowrite8(enable, adapter->addr + TSNEP_GC);
+ if (!(ioread32(adapter->addr + TSNEP_GC) & active))
+ return false;
+
+ return true;
+}
+
+static bool disable_gc(struct tsnep_adapter *adapter)
+{
+ iowrite8(TSNEP_GC_DISABLE, adapter->addr + TSNEP_GC);
+ if (ioread32(adapter->addr + TSNEP_GC) &
+ (TSNEP_GC_ACTIVE_A | TSNEP_GC_ACTIVE_B))
+ return false;
+
+ return true;
+}
+
+static bool gc_delayed_enable(struct tsnep_adapter *adapter, bool a, int delay)
+{
+ u64 before, after;
+ u32 time;
+ bool enabled;
+
+ if (!disable_gc(adapter))
+ return false;
+
+ before = ktime_get_ns();
+
+ if (!enable_gc_timeout(adapter))
+ return false;
+
+ /* for start time after timeout, the timeout can guarantee, that enable
+ * is blocked if too late
+ */
+ time = ioread32(adapter->addr + ECM_SYSTEM_TIME_LOW);
+ time += TSNEP_GC_TIMEOUT;
+ iowrite32(time, adapter->addr + TSNEP_GC_TIME);
+
+ ndelay(delay);
+
+ enabled = enable_gc(adapter, a);
+ after = ktime_get_ns();
+
+ if (delay > TSNEP_GC_TIMEOUT) {
+ /* timeout must have blocked enable */
+ if (enabled)
+ return false;
+ } else if ((after - before) < TSNEP_GC_TIMEOUT * 14 / 16) {
+ /* timeout must not have blocked enable */
+ if (!enabled)
+ return false;
+ }
+
+ if (enabled) {
+ if (gc_timeout_signaled(adapter))
+ return false;
+ } else {
+ if (!gc_timeout_signaled(adapter))
+ return false;
+ if (!ack_gc_timeout(adapter))
+ return false;
+ }
+
+ if (!disable_gc(adapter))
+ return false;
+
+ return true;
+}
+
+static bool tsnep_test_gc_enable(struct tsnep_adapter *adapter)
+{
+ int i;
+
+ iowrite32(0x80000001, adapter->addr + TSNEP_GCL_A + 0);
+ iowrite32(100000, adapter->addr + TSNEP_GCL_A + 4);
+
+ for (i = 0; i < 200000; i += 100) {
+ if (!gc_delayed_enable(adapter, true, i))
+ return false;
+ }
+
+ iowrite32(0x80000001, adapter->addr + TSNEP_GCL_B + 0);
+ iowrite32(100000, adapter->addr + TSNEP_GCL_B + 4);
+
+ for (i = 0; i < 200000; i += 100) {
+ if (!gc_delayed_enable(adapter, false, i))
+ return false;
+ }
+
+ return true;
+}
+
+static void delay_base_time(struct tsnep_adapter *adapter,
+ struct tc_taprio_qopt_offload *qopt, s64 ms)
+{
+ u64 system_time;
+ u64 base_time = ktime_to_ns(qopt->base_time);
+ u64 n;
+
+ tsnep_get_system_time(adapter, &system_time);
+ system_time += ms * 1000000;
+ n = div64_u64(system_time - base_time, qopt->cycle_time);
+
+ qopt->base_time = ktime_add_ns(qopt->base_time,
+ (n + 1) * qopt->cycle_time);
+}
+
+static void get_gate_state(struct tsnep_adapter *adapter, u32 *gc, u32 *gc_time,
+ u64 *system_time)
+{
+ u32 time_high_before;
+ u32 time_low;
+ u32 time_high;
+ u32 gc_time_before;
+
+ time_high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ *gc_time = ioread32(adapter->addr + TSNEP_GC_TIME);
+ do {
+ time_low = ioread32(adapter->addr + ECM_SYSTEM_TIME_LOW);
+ *gc = ioread32(adapter->addr + TSNEP_GC);
+
+ gc_time_before = *gc_time;
+ *gc_time = ioread32(adapter->addr + TSNEP_GC_TIME);
+ time_high_before = time_high;
+ time_high = ioread32(adapter->addr + ECM_SYSTEM_TIME_HIGH);
+ } while ((time_high != time_high_before) ||
+ (*gc_time != gc_time_before));
+
+ *system_time = (((u64)time_high) << 32) | ((u64)time_low);
+}
+
+static int get_operation(struct tsnep_gcl *gcl, u64 system_time, u64 *next)
+{
+ u64 n = div64_u64(system_time - gcl->base_time, gcl->cycle_time);
+ u64 cycle_start = gcl->base_time + gcl->cycle_time * n;
+ int i;
+
+ *next = cycle_start;
+ for (i = 0; i < gcl->count; i++) {
+ *next += gcl->operation[i].interval;
+ if (*next > system_time)
+ break;
+ }
+
+ return i;
+}
+
+static bool check_gate(struct tsnep_adapter *adapter)
+{
+ u32 gc_time;
+ u32 gc;
+ u64 system_time;
+ struct tsnep_gcl *curr;
+ struct tsnep_gcl *prev;
+ u64 next_time;
+ u8 gate_open;
+ u8 next_gate_open;
+
+ get_gate_state(adapter, &gc, &gc_time, &system_time);
+
+ if (gc & TSNEP_GC_ACTIVE_A) {
+ curr = &adapter->gcl[0];
+ prev = &adapter->gcl[1];
+ } else if (gc & TSNEP_GC_ACTIVE_B) {
+ curr = &adapter->gcl[1];
+ prev = &adapter->gcl[0];
+ } else {
+ return false;
+ }
+ if (curr->start_time <= system_time) {
+ /* GCL is already active */
+ int index;
+
+ index = get_operation(curr, system_time, &next_time);
+ gate_open = curr->operation[index].properties & TSNEP_GCL_MASK;
+ if (index == curr->count - 1)
+ index = 0;
+ else
+ index++;
+ next_gate_open =
+ curr->operation[index].properties & TSNEP_GCL_MASK;
+ } else if (curr->change) {
+ /* operation of previous GCL is active */
+ int index;
+ u64 start_before;
+ u64 n;
+
+ index = get_operation(prev, system_time, &next_time);
+ next_time = curr->start_time;
+ start_before = prev->base_time;
+ n = div64_u64(curr->start_time - start_before,
+ prev->cycle_time);
+ start_before += n * prev->cycle_time;
+ if (curr->start_time == start_before)
+ start_before -= prev->cycle_time;
+ if (((start_before + prev->cycle_time_extension) >=
+ curr->start_time) &&
+ (curr->start_time - prev->cycle_time_extension <=
+ system_time)) {
+ /* extend */
+ index = prev->count - 1;
+ }
+ gate_open = prev->operation[index].properties & TSNEP_GCL_MASK;
+ next_gate_open =
+ curr->operation[0].properties & TSNEP_GCL_MASK;
+ } else {
+ /* GCL is waiting for start */
+ next_time = curr->start_time;
+ gate_open = 0xFF;
+ next_gate_open = curr->operation[0].properties & TSNEP_GCL_MASK;
+ }
+
+ if (gc_time != (next_time & 0xFFFFFFFF)) {
+ dev_err(&adapter->pdev->dev, "gate control time 0x%x!=0x%llx\n",
+ gc_time, next_time);
+ return false;
+ }
+ if (((gc & TSNEP_GC_OPEN) >> TSNEP_GC_OPEN_SHIFT) != gate_open) {
+ dev_err(&adapter->pdev->dev,
+ "gate control open 0x%02x!=0x%02x\n",
+ ((gc & TSNEP_GC_OPEN) >> TSNEP_GC_OPEN_SHIFT),
+ gate_open);
+ return false;
+ }
+ if (((gc & TSNEP_GC_NEXT_OPEN) >> TSNEP_GC_NEXT_OPEN_SHIFT) !=
+ next_gate_open) {
+ dev_err(&adapter->pdev->dev,
+ "gate control next open 0x%02x!=0x%02x\n",
+ ((gc & TSNEP_GC_NEXT_OPEN) >> TSNEP_GC_NEXT_OPEN_SHIFT),
+ next_gate_open);
+ return false;
+ }
+
+ return true;
+}
+
+static bool check_gate_duration(struct tsnep_adapter *adapter, s64 ms)
+{
+ ktime_t start = ktime_get();
+
+ do {
+ if (!check_gate(adapter))
+ return false;
+ } while (ktime_ms_delta(ktime_get(), start) < ms);
+
+ return true;
+}
+
+static bool enable_check_taprio(struct tsnep_adapter *adapter,
+ struct tc_taprio_qopt_offload *qopt, s64 ms)
+{
+ int retval;
+
+ retval = tsnep_tc_setup(adapter->netdev, TC_SETUP_QDISC_TAPRIO, qopt);
+ if (retval)
+ return false;
+
+ if (!check_gate_duration(adapter, ms))
+ return false;
+
+ return true;
+}
+
+static bool disable_taprio(struct tsnep_adapter *adapter)
+{
+ struct tc_taprio_qopt_offload qopt;
+ int retval;
+
+ memset(&qopt, 0, sizeof(qopt));
+ qopt.enable = 0;
+ retval = tsnep_tc_setup(adapter->netdev, TC_SETUP_QDISC_TAPRIO, &qopt);
+ if (retval)
+ return false;
+
+ return true;
+}
+
+static bool run_taprio(struct tsnep_adapter *adapter,
+ struct tc_taprio_qopt_offload *qopt, s64 ms)
+{
+ if (!enable_check_taprio(adapter, qopt, ms))
+ return false;
+
+ if (!disable_taprio(adapter))
+ return false;
+
+ return true;
+}
+
+static bool tsnep_test_taprio(struct tsnep_adapter *adapter)
+{
+ struct tc_taprio_qopt_offload *qopt;
+ int i;
+
+ qopt = kzalloc(struct_size(qopt, entries, 255), GFP_KERNEL);
+ if (!qopt)
+ return false;
+ for (i = 0; i < 255; i++)
+ qopt->entries[i].command = TC_TAPRIO_CMD_SET_GATES;
+
+ qopt->enable = 1;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 1500000;
+ qopt->cycle_time_extension = 0;
+ qopt->entries[0].gate_mask = 0x02;
+ qopt->entries[0].interval = 200000;
+ qopt->entries[1].gate_mask = 0x03;
+ qopt->entries[1].interval = 800000;
+ qopt->entries[2].gate_mask = 0x07;
+ qopt->entries[2].interval = 240000;
+ qopt->entries[3].gate_mask = 0x01;
+ qopt->entries[3].interval = 80000;
+ qopt->entries[4].gate_mask = 0x04;
+ qopt->entries[4].interval = 70000;
+ qopt->entries[5].gate_mask = 0x06;
+ qopt->entries[5].interval = 60000;
+ qopt->entries[6].gate_mask = 0x0F;
+ qopt->entries[6].interval = 50000;
+ qopt->num_entries = 7;
+ if (!run_taprio(adapter, qopt, 100))
+ goto failed;
+
+ qopt->enable = 1;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 411854;
+ qopt->cycle_time_extension = 0;
+ qopt->entries[0].gate_mask = 0x17;
+ qopt->entries[0].interval = 23842;
+ qopt->entries[1].gate_mask = 0x16;
+ qopt->entries[1].interval = 13482;
+ qopt->entries[2].gate_mask = 0x15;
+ qopt->entries[2].interval = 49428;
+ qopt->entries[3].gate_mask = 0x14;
+ qopt->entries[3].interval = 38189;
+ qopt->entries[4].gate_mask = 0x13;
+ qopt->entries[4].interval = 92321;
+ qopt->entries[5].gate_mask = 0x12;
+ qopt->entries[5].interval = 71239;
+ qopt->entries[6].gate_mask = 0x11;
+ qopt->entries[6].interval = 69932;
+ qopt->entries[7].gate_mask = 0x10;
+ qopt->entries[7].interval = 53421;
+ qopt->num_entries = 8;
+ if (!run_taprio(adapter, qopt, 100))
+ goto failed;
+
+ qopt->enable = 1;
+ qopt->base_time = ktime_set(0, 0);
+ delay_base_time(adapter, qopt, 12);
+ qopt->cycle_time = 125000;
+ qopt->cycle_time_extension = 0;
+ qopt->entries[0].gate_mask = 0x27;
+ qopt->entries[0].interval = 15000;
+ qopt->entries[1].gate_mask = 0x26;
+ qopt->entries[1].interval = 15000;
+ qopt->entries[2].gate_mask = 0x25;
+ qopt->entries[2].interval = 12500;
+ qopt->entries[3].gate_mask = 0x24;
+ qopt->entries[3].interval = 17500;
+ qopt->entries[4].gate_mask = 0x23;
+ qopt->entries[4].interval = 10000;
+ qopt->entries[5].gate_mask = 0x22;
+ qopt->entries[5].interval = 11000;
+ qopt->entries[6].gate_mask = 0x21;
+ qopt->entries[6].interval = 9000;
+ qopt->entries[7].gate_mask = 0x20;
+ qopt->entries[7].interval = 10000;
+ qopt->entries[8].gate_mask = 0x20;
+ qopt->entries[8].interval = 12500;
+ qopt->entries[9].gate_mask = 0x20;
+ qopt->entries[9].interval = 12500;
+ qopt->num_entries = 10;
+ if (!run_taprio(adapter, qopt, 100))
+ goto failed;
+
+ kfree(qopt);
+
+ return true;
+
+failed:
+ disable_taprio(adapter);
+ kfree(qopt);
+
+ return false;
+}
+
+static bool tsnep_test_taprio_change(struct tsnep_adapter *adapter)
+{
+ struct tc_taprio_qopt_offload *qopt;
+ int i;
+
+ qopt = kzalloc(struct_size(qopt, entries, 255), GFP_KERNEL);
+ if (!qopt)
+ return false;
+ for (i = 0; i < 255; i++)
+ qopt->entries[i].command = TC_TAPRIO_CMD_SET_GATES;
+
+ qopt->enable = 1;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 100000;
+ qopt->cycle_time_extension = 0;
+ qopt->entries[0].gate_mask = 0x30;
+ qopt->entries[0].interval = 20000;
+ qopt->entries[1].gate_mask = 0x31;
+ qopt->entries[1].interval = 80000;
+ qopt->num_entries = 2;
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to identical */
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ delay_base_time(adapter, qopt, 17);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to same cycle time */
+ qopt->base_time = ktime_set(0, 0);
+ qopt->entries[0].gate_mask = 0x42;
+ qopt->entries[1].gate_mask = 0x43;
+ delay_base_time(adapter, qopt, 2);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->entries[0].gate_mask = 0x54;
+ qopt->entries[0].interval = 33333;
+ qopt->entries[1].gate_mask = 0x55;
+ qopt->entries[1].interval = 66667;
+ delay_base_time(adapter, qopt, 23);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->entries[0].gate_mask = 0x66;
+ qopt->entries[0].interval = 50000;
+ qopt->entries[1].gate_mask = 0x67;
+ qopt->entries[1].interval = 25000;
+ qopt->entries[2].gate_mask = 0x68;
+ qopt->entries[2].interval = 25000;
+ qopt->num_entries = 3;
+ delay_base_time(adapter, qopt, 11);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to multiple of cycle time */
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 200000;
+ qopt->entries[0].gate_mask = 0x79;
+ qopt->entries[0].interval = 50000;
+ qopt->entries[1].gate_mask = 0x7A;
+ qopt->entries[1].interval = 150000;
+ qopt->num_entries = 2;
+ delay_base_time(adapter, qopt, 11);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 1000000;
+ qopt->entries[0].gate_mask = 0x7B;
+ qopt->entries[0].interval = 125000;
+ qopt->entries[1].gate_mask = 0x7C;
+ qopt->entries[1].interval = 250000;
+ qopt->entries[2].gate_mask = 0x7D;
+ qopt->entries[2].interval = 375000;
+ qopt->entries[3].gate_mask = 0x7E;
+ qopt->entries[3].interval = 250000;
+ qopt->num_entries = 4;
+ delay_base_time(adapter, qopt, 3);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to shorter cycle time */
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 333333;
+ qopt->entries[0].gate_mask = 0x8F;
+ qopt->entries[0].interval = 166666;
+ qopt->entries[1].gate_mask = 0x80;
+ qopt->entries[1].interval = 166667;
+ qopt->num_entries = 2;
+ delay_base_time(adapter, qopt, 11);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 62500;
+ qopt->entries[0].gate_mask = 0x81;
+ qopt->entries[0].interval = 31250;
+ qopt->entries[1].gate_mask = 0x82;
+ qopt->entries[1].interval = 15625;
+ qopt->entries[2].gate_mask = 0x83;
+ qopt->entries[2].interval = 15625;
+ qopt->num_entries = 3;
+ delay_base_time(adapter, qopt, 1);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to longer cycle time */
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 400000;
+ qopt->entries[0].gate_mask = 0x84;
+ qopt->entries[0].interval = 100000;
+ qopt->entries[1].gate_mask = 0x85;
+ qopt->entries[1].interval = 100000;
+ qopt->entries[2].gate_mask = 0x86;
+ qopt->entries[2].interval = 100000;
+ qopt->entries[3].gate_mask = 0x87;
+ qopt->entries[3].interval = 100000;
+ qopt->num_entries = 4;
+ delay_base_time(adapter, qopt, 7);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 1700000;
+ qopt->entries[0].gate_mask = 0x88;
+ qopt->entries[0].interval = 200000;
+ qopt->entries[1].gate_mask = 0x89;
+ qopt->entries[1].interval = 300000;
+ qopt->entries[2].gate_mask = 0x8A;
+ qopt->entries[2].interval = 600000;
+ qopt->entries[3].gate_mask = 0x8B;
+ qopt->entries[3].interval = 100000;
+ qopt->entries[4].gate_mask = 0x8C;
+ qopt->entries[4].interval = 500000;
+ qopt->num_entries = 5;
+ delay_base_time(adapter, qopt, 6);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ if (!disable_taprio(adapter))
+ goto failed;
+
+ kfree(qopt);
+
+ return true;
+
+failed:
+ disable_taprio(adapter);
+ kfree(qopt);
+
+ return false;
+}
+
+static bool tsnep_test_taprio_extension(struct tsnep_adapter *adapter)
+{
+ struct tc_taprio_qopt_offload *qopt;
+ int i;
+
+ qopt = kzalloc(struct_size(qopt, entries, 255), GFP_KERNEL);
+ if (!qopt)
+ return false;
+ for (i = 0; i < 255; i++)
+ qopt->entries[i].command = TC_TAPRIO_CMD_SET_GATES;
+
+ qopt->enable = 1;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 100000;
+ qopt->cycle_time_extension = 50000;
+ qopt->entries[0].gate_mask = 0x90;
+ qopt->entries[0].interval = 20000;
+ qopt->entries[1].gate_mask = 0x91;
+ qopt->entries[1].interval = 80000;
+ qopt->num_entries = 2;
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to different phase */
+ qopt->base_time = ktime_set(0, 50000);
+ qopt->entries[0].gate_mask = 0x92;
+ qopt->entries[0].interval = 33000;
+ qopt->entries[1].gate_mask = 0x93;
+ qopt->entries[1].interval = 67000;
+ qopt->num_entries = 2;
+ delay_base_time(adapter, qopt, 2);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to different phase and longer cycle time */
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 1000000;
+ qopt->cycle_time_extension = 700000;
+ qopt->entries[0].gate_mask = 0x94;
+ qopt->entries[0].interval = 400000;
+ qopt->entries[1].gate_mask = 0x95;
+ qopt->entries[1].interval = 600000;
+ qopt->num_entries = 2;
+ delay_base_time(adapter, qopt, 7);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 700000);
+ qopt->cycle_time = 2000000;
+ qopt->cycle_time_extension = 1900000;
+ qopt->entries[0].gate_mask = 0x96;
+ qopt->entries[0].interval = 400000;
+ qopt->entries[1].gate_mask = 0x97;
+ qopt->entries[1].interval = 1600000;
+ qopt->num_entries = 2;
+ delay_base_time(adapter, qopt, 9);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to different phase and shorter cycle time */
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 1500000;
+ qopt->cycle_time_extension = 700000;
+ qopt->entries[0].gate_mask = 0x98;
+ qopt->entries[0].interval = 400000;
+ qopt->entries[1].gate_mask = 0x99;
+ qopt->entries[1].interval = 600000;
+ qopt->entries[2].gate_mask = 0x9A;
+ qopt->entries[2].interval = 500000;
+ qopt->num_entries = 3;
+ delay_base_time(adapter, qopt, 3);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 100000);
+ qopt->cycle_time = 500000;
+ qopt->cycle_time_extension = 300000;
+ qopt->entries[0].gate_mask = 0x9B;
+ qopt->entries[0].interval = 150000;
+ qopt->entries[1].gate_mask = 0x9C;
+ qopt->entries[1].interval = 350000;
+ qopt->num_entries = 2;
+ delay_base_time(adapter, qopt, 9);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ /* change to different cycle time */
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 1000000;
+ qopt->cycle_time_extension = 700000;
+ qopt->entries[0].gate_mask = 0xAD;
+ qopt->entries[0].interval = 400000;
+ qopt->entries[1].gate_mask = 0xAE;
+ qopt->entries[1].interval = 300000;
+ qopt->entries[2].gate_mask = 0xAF;
+ qopt->entries[2].interval = 300000;
+ qopt->num_entries = 3;
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 400000;
+ qopt->cycle_time_extension = 100000;
+ qopt->entries[0].gate_mask = 0xA0;
+ qopt->entries[0].interval = 200000;
+ qopt->entries[1].gate_mask = 0xA1;
+ qopt->entries[1].interval = 200000;
+ qopt->num_entries = 2;
+ delay_base_time(adapter, qopt, 19);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 500000;
+ qopt->cycle_time_extension = 499999;
+ qopt->entries[0].gate_mask = 0xB2;
+ qopt->entries[0].interval = 100000;
+ qopt->entries[1].gate_mask = 0xB3;
+ qopt->entries[1].interval = 100000;
+ qopt->entries[2].gate_mask = 0xB4;
+ qopt->entries[2].interval = 100000;
+ qopt->entries[3].gate_mask = 0xB5;
+ qopt->entries[3].interval = 200000;
+ qopt->num_entries = 4;
+ delay_base_time(adapter, qopt, 19);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+ qopt->base_time = ktime_set(0, 0);
+ qopt->cycle_time = 6000000;
+ qopt->cycle_time_extension = 5999999;
+ qopt->entries[0].gate_mask = 0xC6;
+ qopt->entries[0].interval = 1000000;
+ qopt->entries[1].gate_mask = 0xC7;
+ qopt->entries[1].interval = 1000000;
+ qopt->entries[2].gate_mask = 0xC8;
+ qopt->entries[2].interval = 1000000;
+ qopt->entries[3].gate_mask = 0xC9;
+ qopt->entries[3].interval = 1500000;
+ qopt->entries[4].gate_mask = 0xCA;
+ qopt->entries[4].interval = 1500000;
+ qopt->num_entries = 5;
+ delay_base_time(adapter, qopt, 1);
+ if (!enable_check_taprio(adapter, qopt, 100))
+ goto failed;
+
+ if (!disable_taprio(adapter))
+ goto failed;
+
+ kfree(qopt);
+
+ return true;
+
+failed:
+ disable_taprio(adapter);
+ kfree(qopt);
+
+ return false;
+}
+
+int tsnep_ethtool_get_test_count(void)
+{
+ return TSNEP_TEST_COUNT;
+}
+
+void tsnep_ethtool_get_test_strings(u8 *data)
+{
+ memcpy(data, tsnep_test_strings, sizeof(tsnep_test_strings));
+}
+
+void tsnep_ethtool_self_test(struct net_device *netdev,
+ struct ethtool_test *eth_test, u64 *data)
+{
+ struct tsnep_adapter *adapter = netdev_priv(netdev);
+
+ eth_test->len = TSNEP_TEST_COUNT;
+
+ if (eth_test->flags != ETH_TEST_FL_OFFLINE) {
+ /* no tests are done online */
+ data[TSNEP_TEST_ENABLE] = 0;
+ data[TSNEP_TEST_TAPRIO] = 0;
+ data[TSNEP_TEST_TAPRIO_CHANGE] = 0;
+ data[TSNEP_TEST_TAPRIO_EXTENSION] = 0;
+
+ return;
+ }
+
+ if (tsnep_test_gc_enable(adapter)) {
+ data[TSNEP_TEST_ENABLE] = 0;
+ } else {
+ eth_test->flags |= ETH_TEST_FL_FAILED;
+ data[TSNEP_TEST_ENABLE] = 1;
+ }
+
+ if (tsnep_test_taprio(adapter)) {
+ data[TSNEP_TEST_TAPRIO] = 0;
+ } else {
+ eth_test->flags |= ETH_TEST_FL_FAILED;
+ data[TSNEP_TEST_TAPRIO] = 1;
+ }
+
+ if (tsnep_test_taprio_change(adapter)) {
+ data[TSNEP_TEST_TAPRIO_CHANGE] = 0;
+ } else {
+ eth_test->flags |= ETH_TEST_FL_FAILED;
+ data[TSNEP_TEST_TAPRIO_CHANGE] = 1;
+ }
+
+ if (tsnep_test_taprio_extension(adapter)) {
+ data[TSNEP_TEST_TAPRIO_EXTENSION] = 0;
+ } else {
+ eth_test->flags |= ETH_TEST_FL_FAILED;
+ data[TSNEP_TEST_TAPRIO_EXTENSION] = 1;
+ }
+}
--
2.20.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
2021-11-15 20:50 ` [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver Gerhard Engleder
@ 2021-11-15 22:21 ` Heiner Kallweit
2021-11-16 21:47 ` Gerhard Engleder
2021-11-15 23:04 ` Andrew Lunn
2021-11-18 17:31 ` kernel test robot
2 siblings, 1 reply; 13+ messages in thread
From: Heiner Kallweit @ 2021-11-15 22:21 UTC (permalink / raw)
To: Gerhard Engleder, davem, kuba, andrew; +Cc: netdev
On 15.11.2021 21:50, Gerhard Engleder wrote:
> The TSN endpoint Ethernet MAC is a FPGA based network device for
> real-time communication.
>
> It is integrated as Ethernet controller with ethtool and PTP support.
> For real-time communcation TC_SETUP_QDISC_TAPRIO is supported.
>
> Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com>
> ---
> drivers/net/ethernet/Kconfig | 1 +
> drivers/net/ethernet/Makefile | 1 +
> drivers/net/ethernet/engleder/Kconfig | 29 +
> drivers/net/ethernet/engleder/Makefile | 9 +
> drivers/net/ethernet/engleder/tsnep.h | 171 +++
> drivers/net/ethernet/engleder/tsnep_ethtool.c | 288 ++++
> drivers/net/ethernet/engleder/tsnep_hw.h | 230 +++
> drivers/net/ethernet/engleder/tsnep_main.c | 1255 +++++++++++++++++
> drivers/net/ethernet/engleder/tsnep_ptp.c | 221 +++
> drivers/net/ethernet/engleder/tsnep_tc.c | 443 ++++++
> drivers/net/ethernet/engleder/tsnep_test.c | 811 +++++++++++
> 11 files changed, 3459 insertions(+)
> create mode 100644 drivers/net/ethernet/engleder/Kconfig
> create mode 100644 drivers/net/ethernet/engleder/Makefile
> create mode 100644 drivers/net/ethernet/engleder/tsnep.h
> create mode 100644 drivers/net/ethernet/engleder/tsnep_ethtool.c
> create mode 100644 drivers/net/ethernet/engleder/tsnep_hw.h
> create mode 100644 drivers/net/ethernet/engleder/tsnep_main.c
> create mode 100644 drivers/net/ethernet/engleder/tsnep_ptp.c
> create mode 100644 drivers/net/ethernet/engleder/tsnep_tc.c
> create mode 100644 drivers/net/ethernet/engleder/tsnep_test.c
>
> diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
> index 4601b38f532a..027cbacca1c9 100644
> --- a/drivers/net/ethernet/Kconfig
> +++ b/drivers/net/ethernet/Kconfig
> @@ -73,6 +73,7 @@ config DNET
> source "drivers/net/ethernet/dec/Kconfig"
> source "drivers/net/ethernet/dlink/Kconfig"
> source "drivers/net/ethernet/emulex/Kconfig"
> +source "drivers/net/ethernet/engleder/Kconfig"
> source "drivers/net/ethernet/ezchip/Kconfig"
> source "drivers/net/ethernet/faraday/Kconfig"
> source "drivers/net/ethernet/freescale/Kconfig"
> diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
> index fdd8c6c17451..33d30b619e00 100644
> --- a/drivers/net/ethernet/Makefile
> +++ b/drivers/net/ethernet/Makefile
> @@ -36,6 +36,7 @@ obj-$(CONFIG_DNET) += dnet.o
> obj-$(CONFIG_NET_VENDOR_DEC) += dec/
> obj-$(CONFIG_NET_VENDOR_DLINK) += dlink/
> obj-$(CONFIG_NET_VENDOR_EMULEX) += emulex/
> +obj-$(CONFIG_NET_VENDOR_ENGLEDER) += engleder/
> obj-$(CONFIG_NET_VENDOR_EZCHIP) += ezchip/
> obj-$(CONFIG_NET_VENDOR_FARADAY) += faraday/
> obj-$(CONFIG_NET_VENDOR_FREESCALE) += freescale/
> diff --git a/drivers/net/ethernet/engleder/Kconfig b/drivers/net/ethernet/engleder/Kconfig
> new file mode 100644
> index 000000000000..26c2a8e0acc0
> --- /dev/null
> +++ b/drivers/net/ethernet/engleder/Kconfig
> @@ -0,0 +1,29 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Engleder network device configuration
> +#
> +
> +config NET_VENDOR_ENGLEDER
> + bool "Engleder devices"
> + default y
> + help
> + If you have a network (Ethernet) card belonging to this class, say Y.
> +
> + 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 Engleder devices. If you say Y, you will be asked
> + for your specific card in the following questions.
> +
> +if NET_VENDOR_ENGLEDER
> +
> +config TSNEP
> + tristate "TSN endpoint support"
> + depends on PTP_1588_CLOCK_OPTIONAL
> + select PHYLIB
> + help
> + Support for the Engleder TSN endpoint Ethernet MAC IP Core.
> +
> + To compile this driver as a module, choose M here. The module will be
> + called tsnep.
> +
> +endif # NET_VENDOR_ENGLEDER
> diff --git a/drivers/net/ethernet/engleder/Makefile b/drivers/net/ethernet/engleder/Makefile
> new file mode 100644
> index 000000000000..fbaecbfb0944
> --- /dev/null
> +++ b/drivers/net/ethernet/engleder/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for the Engleder Ethernet drivers
> +#
> +
> +obj-$(CONFIG_TSNEP) += tsnep.o
> +
> +tsnep-objs := tsnep_main.o tsnep_ethtool.o tsnep_ptp.o tsnep_tc.o \
> + tsnep_test.o
> diff --git a/drivers/net/ethernet/engleder/tsnep.h b/drivers/net/ethernet/engleder/tsnep.h
> new file mode 100644
> index 000000000000..edd6fa7dafd7
> --- /dev/null
> +++ b/drivers/net/ethernet/engleder/tsnep.h
> @@ -0,0 +1,171 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
> +
> +#ifndef _TSNEP_H
> +#define _TSNEP_H
> +
> +#include "tsnep_hw.h"
> +
> +#include <linux/platform_device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/etherdevice.h>
> +#include <linux/phy.h>
> +#include <linux/ethtool.h>
> +#include <linux/net_tstamp.h>
> +#include <linux/ptp_clock_kernel.h>
> +#include <linux/miscdevice.h>
> +
> +#define TSNEP "tsnep"
> +
> +#define TSNEP_RING_SIZE 256
> +#define TSNEP_RING_ENTRIES_PER_PAGE (PAGE_SIZE / TSNEP_DESC_SIZE)
> +#define TSNEP_RING_PAGE_COUNT (TSNEP_RING_SIZE / TSNEP_RING_ENTRIES_PER_PAGE)
> +
> +#define TSNEP_QUEUES 1
> +
> +struct tsnep_gcl {
> + void __iomem *addr;
> +
> + u64 base_time;
> + u64 cycle_time;
> + u64 cycle_time_extension;
> +
> + struct tsnep_gcl_operation operation[TSNEP_GCL_COUNT];
> + int count;
> +
> + u64 change_limit;
> +
> + u64 start_time;
> + bool change;
> +};
> +
> +struct tsnep_tx_entry {
> + struct tsnep_tx_desc *desc;
> + struct tsnep_tx_desc_wb *desc_wb;
> + dma_addr_t desc_dma;
> + bool owner_user_flag;
> +
> + u32 properties;
> +
> + struct sk_buff *skb;
> + DEFINE_DMA_UNMAP_ADDR(dma);
> + DEFINE_DMA_UNMAP_LEN(len);
> +};
> +
> +struct tsnep_tx {
> + struct tsnep_adapter *adapter;
> + void __iomem *addr;
> +
> + void *page[TSNEP_RING_PAGE_COUNT];
> + dma_addr_t page_dma[TSNEP_RING_PAGE_COUNT];
> +
> + /* TX ring lock */
> + spinlock_t lock;
> + struct tsnep_tx_entry entry[TSNEP_RING_SIZE];
> + int write;
> + int read;
> + u32 owner_counter;
> + int increment_owner_counter;
> +
> + u32 packets;
> + u32 bytes;
> + u32 dropped;
> +};
> +
> +struct tsnep_rx_entry {
> + struct tsnep_rx_desc *desc;
> + struct tsnep_rx_desc_wb *desc_wb;
> + dma_addr_t desc_dma;
> +
> + u32 properties;
> +
> + struct sk_buff *skb;
> + DEFINE_DMA_UNMAP_ADDR(dma);
> + DEFINE_DMA_UNMAP_LEN(len);
> +};
> +
> +struct tsnep_rx {
> + struct tsnep_adapter *adapter;
> + void __iomem *addr;
> +
> + void *page[TSNEP_RING_PAGE_COUNT];
> + dma_addr_t page_dma[TSNEP_RING_PAGE_COUNT];
> +
> + struct tsnep_rx_entry entry[TSNEP_RING_SIZE];
> + int read;
> + u32 owner_counter;
> + int increment_owner_counter;
> +
> + u32 packets;
> + u32 bytes;
> + u32 dropped;
> + u32 multicast;
> +};
> +
> +struct tsnep_queue {
> + struct tsnep_adapter *adapter;
> +
> + struct tsnep_tx *tx;
> + struct tsnep_rx *rx;
> +
> + struct napi_struct napi;
> +
> + u32 irq_mask;
> +};
> +
> +struct tsnep_adapter {
> + struct net_device *netdev;
> + u8 mac_address[ETH_ALEN];
> + struct mii_bus *mdiobus;
> + bool suppress_preamble;
> + phy_interface_t phy_mode;
> + struct phy_device *phydev;
> + int msg_enable;
> +
> + struct platform_device *pdev;
> + struct device *dmadev;
> + void __iomem *addr;
> + unsigned long size;
> + int irq;
> +
> + bool gate_control;
> + /* gate control lock */
> + struct mutex gate_control_lock;
> + bool gate_control_active;
> + struct tsnep_gcl gcl[2];
> + int next_gcl;
> +
> + struct hwtstamp_config hwtstamp_config;
> + struct ptp_clock *ptp_clock;
> + struct ptp_clock_info ptp_clock_info;
> + /* ptp clock lock */
> + spinlock_t ptp_lock;
> +
> + int num_tx_queues;
> + struct tsnep_tx tx[TSNEP_MAX_QUEUES];
> + int num_rx_queues;
> + struct tsnep_rx rx[TSNEP_MAX_QUEUES];
> +
> + int num_queues;
> + struct tsnep_queue queue[TSNEP_MAX_QUEUES];
> +};
> +
> +extern const struct ethtool_ops tsnep_ethtool_ops;
> +
> +int tsnep_ptp_init(struct tsnep_adapter *adapter);
> +void tsnep_ptp_cleanup(struct tsnep_adapter *adapter);
> +int tsnep_ptp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd);
> +
> +int tsnep_tc_init(struct tsnep_adapter *adapter);
> +void tsnep_tc_cleanup(struct tsnep_adapter *adapter);
> +int tsnep_tc_setup(struct net_device *netdev, enum tc_setup_type type,
> + void *type_data);
> +
> +int tsnep_ethtool_get_test_count(void);
> +void tsnep_ethtool_get_test_strings(u8 *data);
> +void tsnep_ethtool_self_test(struct net_device *netdev,
> + struct ethtool_test *eth_test, u64 *data);
> +
> +void tsnep_get_system_time(struct tsnep_adapter *adapter, u64 *time);
> +
> +#endif /* _TSNEP_H */
> diff --git a/drivers/net/ethernet/engleder/tsnep_ethtool.c b/drivers/net/ethernet/engleder/tsnep_ethtool.c
> new file mode 100644
> index 000000000000..f9abcaab1c7c
> --- /dev/null
> +++ b/drivers/net/ethernet/engleder/tsnep_ethtool.c
> @@ -0,0 +1,288 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
> +
> +#include "tsnep.h"
> +
> +static const char tsnep_stats_strings[][ETH_GSTRING_LEN] = {
> + "rx_packets",
> + "rx_bytes",
> + "rx_dropped",
> + "rx_multicast",
> + "rx_phy_errors",
> + "rx_forwarded_phy_errors",
> + "rx_invalid_frame_errors",
> + "tx_packets",
> + "tx_bytes",
> + "tx_dropped",
> +};
> +
> +struct tsnep_stats {
> + u64 rx_packets;
> + u64 rx_bytes;
> + u64 rx_dropped;
> + u64 rx_multicast;
> + u64 rx_phy_errors;
> + u64 rx_forwarded_phy_errors;
> + u64 rx_invalid_frame_errors;
> + u64 tx_packets;
> + u64 tx_bytes;
> + u64 tx_dropped;
> +};
> +
> +#define TSNEP_STATS_COUNT (sizeof(struct tsnep_stats) / sizeof(u64))
> +
> +static const char tsnep_rx_queue_stats_strings[][ETH_GSTRING_LEN] = {
> + "rx_%d_packets",
> + "rx_%d_bytes",
> + "rx_%d_dropped",
> + "rx_%d_multicast",
> + "rx_%d_no_descriptor_errors",
> + "rx_%d_buffer_too_small_errors",
> + "rx_%d_fifo_overflow_errors",
> + "rx_%d_invalid_frame_errors",
> +};
> +
> +struct tsnep_rx_queue_stats {
> + u64 rx_packets;
> + u64 rx_bytes;
> + u64 rx_dropped;
> + u64 rx_multicast;
> + u64 rx_no_descriptor_errors;
> + u64 rx_buffer_too_small_errors;
> + u64 rx_fifo_overflow_errors;
> + u64 rx_invalid_frame_errors;
> +};
> +
> +#define TSNEP_RX_QUEUE_STATS_COUNT (sizeof(struct tsnep_rx_queue_stats) / \
> + sizeof(u64))
> +
> +static const char tsnep_tx_queue_stats_strings[][ETH_GSTRING_LEN] = {
> + "tx_%d_packets",
> + "tx_%d_bytes",
> + "tx_%d_dropped",
> +};
> +
> +struct tsnep_tx_queue_stats {
> + u64 tx_packets;
> + u64 tx_bytes;
> + u64 tx_dropped;
> +};
> +
> +#define TSNEP_TX_QUEUE_STATS_COUNT (sizeof(struct tsnep_tx_queue_stats) / \
> + sizeof(u64))
> +
> +static void tsnep_ethtool_get_drvinfo(struct net_device *netdev,
> + struct ethtool_drvinfo *drvinfo)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> +
> + strscpy(drvinfo->driver, TSNEP, sizeof(drvinfo->driver));
> + strscpy(drvinfo->bus_info, dev_name(&adapter->pdev->dev),
> + sizeof(drvinfo->bus_info));
> +}
> +
> +static int tsnep_ethtool_get_regs_len(struct net_device *netdev)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> + int len;
> + int num_queues;
> +
> + len = TSNEP_MAC_SIZE;
> + num_queues = max(adapter->num_tx_queues, adapter->num_rx_queues);
> + len += TSNEP_QUEUE_SIZE * (num_queues - 1);
> +
> + return len;
> +}
> +
> +static void tsnep_ethtool_get_regs(struct net_device *netdev,
> + struct ethtool_regs *regs,
> + void *p)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> +
> + regs->version = 1;
> +
> + memcpy_fromio(p, adapter->addr, regs->len);
> +}
> +
> +static u32 tsnep_ethtool_get_msglevel(struct net_device *netdev)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> +
> + return adapter->msg_enable;
> +}
> +
> +static void tsnep_ethtool_set_msglevel(struct net_device *netdev, u32 data)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> +
> + adapter->msg_enable = data;
> +}
> +
> +static void tsnep_ethtool_get_strings(struct net_device *netdev, u32 stringset,
> + u8 *data)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> + int rx_count = adapter->num_rx_queues;
> + int tx_count = adapter->num_tx_queues;
> + int i, j;
> +
> + switch (stringset) {
> + case ETH_SS_STATS:
> + memcpy(data, tsnep_stats_strings, sizeof(tsnep_stats_strings));
> + data += sizeof(tsnep_stats_strings);
> +
> + for (i = 0; i < rx_count; i++) {
> + for (j = 0; j < TSNEP_RX_QUEUE_STATS_COUNT; j++) {
> + snprintf(data, ETH_GSTRING_LEN,
> + tsnep_rx_queue_stats_strings[j], i);
> + data += ETH_GSTRING_LEN;
> + }
> + }
> +
> + for (i = 0; i < tx_count; i++) {
> + for (j = 0; j < TSNEP_TX_QUEUE_STATS_COUNT; j++) {
> + snprintf(data, ETH_GSTRING_LEN,
> + tsnep_tx_queue_stats_strings[j], i);
> + data += ETH_GSTRING_LEN;
> + }
> + }
> + break;
> + case ETH_SS_TEST:
> + tsnep_ethtool_get_test_strings(data);
> + break;
> + }
> +}
> +
> +static void tsnep_ethtool_get_ethtool_stats(struct net_device *netdev,
> + struct ethtool_stats *stats,
> + u64 *data)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> + int rx_count = adapter->num_rx_queues;
> + int tx_count = adapter->num_tx_queues;
> + struct tsnep_stats tsnep_stats;
> + struct tsnep_rx_queue_stats tsnep_rx_queue_stats;
> + struct tsnep_tx_queue_stats tsnep_tx_queue_stats;
> + u32 reg;
> + int i;
> +
> + memset(&tsnep_stats, 0, sizeof(tsnep_stats));
> + for (i = 0; i < adapter->num_rx_queues; i++) {
> + tsnep_stats.rx_packets += adapter->rx[i].packets;
> + tsnep_stats.rx_bytes += adapter->rx[i].bytes;
> + tsnep_stats.rx_dropped += adapter->rx[i].dropped;
> + tsnep_stats.rx_multicast += adapter->rx[i].multicast;
> + }
> + reg = ioread32(adapter->addr + ECM_STAT);
> + tsnep_stats.rx_phy_errors =
> + (reg & ECM_STAT_RX_ERR_MASK) >> ECM_STAT_RX_ERR_SHIFT;
> + tsnep_stats.rx_forwarded_phy_errors =
> + (reg & ECM_STAT_FWD_RX_ERR_MASK) >> ECM_STAT_FWD_RX_ERR_SHIFT;
> + tsnep_stats.rx_invalid_frame_errors =
> + (reg & ECM_STAT_INV_FRM_MASK) >> ECM_STAT_INV_FRM_SHIFT;
> + for (i = 0; i < adapter->num_tx_queues; i++) {
> + tsnep_stats.tx_packets += adapter->tx[i].packets;
> + tsnep_stats.tx_bytes += adapter->tx[i].bytes;
> + tsnep_stats.tx_dropped += adapter->tx[i].dropped;
> + }
> + memcpy(data, &tsnep_stats, sizeof(tsnep_stats));
> + data += TSNEP_STATS_COUNT;
> +
> + for (i = 0; i < rx_count; i++) {
> + memset(&tsnep_rx_queue_stats, 0, sizeof(tsnep_rx_queue_stats));
> + tsnep_rx_queue_stats.rx_packets = adapter->rx[i].packets;
> + tsnep_rx_queue_stats.rx_bytes = adapter->rx[i].bytes;
> + tsnep_rx_queue_stats.rx_dropped = adapter->rx[i].dropped;
> + tsnep_rx_queue_stats.rx_multicast = adapter->rx[i].multicast;
> + reg = ioread32(adapter->addr + TSNEP_QUEUE(i) +
> + TSNEP_RX_STATISTIC);
> + tsnep_rx_queue_stats.rx_no_descriptor_errors =
> + (reg & TSNEP_RX_STATISTIC_NO_DESC_MASK) >>
> + TSNEP_RX_STATISTIC_NO_DESC_SHIFT;
> + tsnep_rx_queue_stats.rx_buffer_too_small_errors =
> + (reg & TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_MASK) >>
> + TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_SHIFT;
> + tsnep_rx_queue_stats.rx_fifo_overflow_errors =
> + (reg & TSNEP_RX_STATISTIC_FIFO_OVERFLOW_MASK) >>
> + TSNEP_RX_STATISTIC_FIFO_OVERFLOW_SHIFT;
> + tsnep_rx_queue_stats.rx_invalid_frame_errors =
> + (reg & TSNEP_RX_STATISTIC_INVALID_FRAME_MASK) >>
> + TSNEP_RX_STATISTIC_INVALID_FRAME_SHIFT;
> + memcpy(data, &tsnep_rx_queue_stats,
> + sizeof(tsnep_rx_queue_stats));
> + data += TSNEP_RX_QUEUE_STATS_COUNT;
> + }
> +
> + for (i = 0; i < tx_count; i++) {
> + memset(&tsnep_tx_queue_stats, 0, sizeof(tsnep_tx_queue_stats));
> + tsnep_tx_queue_stats.tx_packets += adapter->tx[i].packets;
> + tsnep_tx_queue_stats.tx_bytes += adapter->tx[i].bytes;
> + tsnep_tx_queue_stats.tx_dropped += adapter->tx[i].dropped;
> + memcpy(data, &tsnep_tx_queue_stats,
> + sizeof(tsnep_tx_queue_stats));
> + data += TSNEP_TX_QUEUE_STATS_COUNT;
> + }
> +}
> +
> +static int tsnep_ethtool_get_sset_count(struct net_device *netdev, int sset)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> + int rx_count;
> + int tx_count;
> +
> + switch (sset) {
> + case ETH_SS_STATS:
> + rx_count = adapter->num_rx_queues;
> + tx_count = adapter->num_tx_queues;
> + return TSNEP_STATS_COUNT +
> + TSNEP_RX_QUEUE_STATS_COUNT * rx_count +
> + TSNEP_TX_QUEUE_STATS_COUNT * tx_count;
> + case ETH_SS_TEST:
> + return tsnep_ethtool_get_test_count();
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int tsnep_ethtool_get_ts_info(struct net_device *dev,
> + struct ethtool_ts_info *info)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(dev);
> +
> + info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
> + SOF_TIMESTAMPING_RX_SOFTWARE |
> + SOF_TIMESTAMPING_SOFTWARE |
> + SOF_TIMESTAMPING_TX_HARDWARE |
> + SOF_TIMESTAMPING_RX_HARDWARE |
> + SOF_TIMESTAMPING_RAW_HARDWARE;
> +
> + if (adapter->ptp_clock)
> + info->phc_index = ptp_clock_index(adapter->ptp_clock);
> + else
> + info->phc_index = -1;
> +
> + info->tx_types = BIT(HWTSTAMP_TX_OFF) |
> + BIT(HWTSTAMP_TX_ON);
> + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
> + BIT(HWTSTAMP_FILTER_ALL);
> +
> + return 0;
> +}
> +
> +const struct ethtool_ops tsnep_ethtool_ops = {
> + .get_drvinfo = tsnep_ethtool_get_drvinfo,
> + .get_regs_len = tsnep_ethtool_get_regs_len,
> + .get_regs = tsnep_ethtool_get_regs,
> + .get_msglevel = tsnep_ethtool_get_msglevel,
> + .set_msglevel = tsnep_ethtool_set_msglevel,
> + .nway_reset = phy_ethtool_nway_reset,
> + .get_link = ethtool_op_get_link,
> + .self_test = tsnep_ethtool_self_test,
> + .get_strings = tsnep_ethtool_get_strings,
> + .get_ethtool_stats = tsnep_ethtool_get_ethtool_stats,
> + .get_sset_count = tsnep_ethtool_get_sset_count,
> + .get_ts_info = tsnep_ethtool_get_ts_info,
> + .get_link_ksettings = phy_ethtool_get_link_ksettings,
> + .set_link_ksettings = phy_ethtool_set_link_ksettings,
> +};
> diff --git a/drivers/net/ethernet/engleder/tsnep_hw.h b/drivers/net/ethernet/engleder/tsnep_hw.h
> new file mode 100644
> index 000000000000..71cc8577d640
> --- /dev/null
> +++ b/drivers/net/ethernet/engleder/tsnep_hw.h
> @@ -0,0 +1,230 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
> +
> +/* Hardware definition of TSNEP and EtherCAT MAC device */
> +
> +#ifndef _TSNEP_HW_H
> +#define _TSNEP_HW_H
> +
> +#include <linux/types.h>
> +
> +/* type */
> +#define ECM_TYPE 0x0000
> +#define ECM_REVISION_MASK 0x000000FF
> +#define ECM_REVISION_SHIFT 0
> +#define ECM_VERSION_MASK 0x0000FF00
> +#define ECM_VERSION_SHIFT 8
> +#define ECM_QUEUE_COUNT_MASK 0x00070000
> +#define ECM_QUEUE_COUNT_SHIFT 16
> +#define ECM_GATE_CONTROL 0x02000000
> +
> +/* system time */
> +#define ECM_SYSTEM_TIME_LOW 0x0008
> +#define ECM_SYSTEM_TIME_HIGH 0x000C
> +
> +/* clock */
> +#define ECM_CLOCK_RATE 0x0010
> +#define ECM_CLOCK_RATE_OFFSET_MASK 0x7FFFFFFF
> +#define ECM_CLOCK_RATE_OFFSET_SIGN 0x80000000
> +
> +/* interrupt */
> +#define ECM_INT_ENABLE 0x0018
> +#define ECM_INT_ACTIVE 0x001C
> +#define ECM_INT_ACKNOWLEDGE 0x001C
> +#define ECM_INT_LINK 0x00000020
> +#define ECM_INT_TX_0 0x00000100
> +#define ECM_INT_RX_0 0x00000200
> +#define ECM_INT_ALL 0x7FFFFFFF
> +#define ECM_INT_DISABLE 0x80000000
> +
> +/* reset */
> +#define ECM_RESET 0x0020
> +#define ECM_RESET_COMMON 0x00000001
> +#define ECM_RESET_CHANNEL 0x00000100
> +#define ECM_RESET_TXRX 0x00010000
> +
> +/* control and status */
> +#define ECM_STATUS 0x0080
> +#define ECM_LINK_MODE_OFF 0x01000000
> +#define ECM_LINK_MODE_100 0x02000000
> +#define ECM_LINK_MODE_1000 0x04000000
> +#define ECM_NO_LINK 0x01000000
> +#define ECM_LINK_MODE_MASK 0x06000000
> +
> +/* management data */
> +#define ECM_MD_CONTROL 0x0084
> +#define ECM_MD_STATUS 0x0084
> +#define ECM_MD_PREAMBLE 0x00000001
> +#define ECM_MD_READ 0x00000004
> +#define ECM_MD_WRITE 0x00000002
> +#define ECM_MD_ADDR_MASK 0x000000F8
> +#define ECM_MD_ADDR_SHIFT 3
> +#define ECM_MD_PHY_ADDR_MASK 0x00001F00
> +#define ECM_MD_PHY_ADDR_SHIFT 8
> +#define ECM_MD_BUSY 0x00000001
> +#define ECM_MD_DATA_MASK 0xFFFF0000
> +#define ECM_MD_DATA_SHIFT 16
> +
> +/* statistic */
> +#define ECM_STAT 0x00B0
> +#define ECM_STAT_RX_ERR_MASK 0x000000FF
> +#define ECM_STAT_RX_ERR_SHIFT 0
> +#define ECM_STAT_INV_FRM_MASK 0x0000FF00
> +#define ECM_STAT_INV_FRM_SHIFT 8
> +#define ECM_STAT_FWD_RX_ERR_MASK 0x00FF0000
> +#define ECM_STAT_FWD_RX_ERR_SHIFT 16
> +
> +/* tsnep */
> +#define TSNEP_MAC_SIZE 0x4000
> +#define TSNEP_QUEUE_SIZE 0x1000
> +#define TSNEP_QUEUE(n) ({ typeof(n) __n = (n); \
> + (__n) == 0 ? \
> + 0 : \
> + TSNEP_MAC_SIZE + TSNEP_QUEUE_SIZE * ((__n) - 1); })
> +#define TSNEP_MAX_QUEUES 8
> +#define TSNEP_MAX_FRAME_SIZE (2 * 1024) /* hardware supports actually 16k */
> +#define TSNEP_DESC_SIZE 256
> +#define TSNEP_DESC_OFFSET 128
> +
> +/* tsnep register */
> +#define TSNEP_INFO 0x0100
> +#define TSNEP_INFO_RX_ASSIGN 0x00010000
> +#define TSNEP_INFO_TX_TIME 0x00020000
> +#define TSNEP_CONTROL 0x0108
> +#define TSNEP_CONTROL_TX_RESET 0x00000001
> +#define TSNEP_CONTROL_TX_ENABLE 0x00000002
> +#define TSNEP_CONTROL_TX_DMA_ERROR 0x00000010
> +#define TSNEP_CONTROL_TX_DESC_ERROR 0x00000020
> +#define TSNEP_CONTROL_RX_RESET 0x00000100
> +#define TSNEP_CONTROL_RX_ENABLE 0x00000200
> +#define TSNEP_CONTROL_RX_DISABLE 0x00000400
> +#define TSNEP_CONTROL_RX_DMA_ERROR 0x00001000
> +#define TSNEP_CONTROL_RX_DESC_ERROR 0x00002000
> +#define TSNEP_TX_DESC_ADDR_LOW 0x0140
> +#define TSNEP_TX_DESC_ADDR_HIGH 0x0144
> +#define TSNEP_RX_DESC_ADDR_LOW 0x0180
> +#define TSNEP_RX_DESC_ADDR_HIGH 0x0184
> +#define TSNEP_RESET_OWNER_COUNTER 0x01
> +#define TSNEP_RX_STATISTIC 0x0190
> +#define TSNEP_RX_STATISTIC_NO_DESC_MASK 0x000000FF
> +#define TSNEP_RX_STATISTIC_NO_DESC_SHIFT 0
> +#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_MASK 0x0000FF00
> +#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL_SHIFT 8
> +#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW_MASK 0x00FF0000
> +#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW_SHIFT 16
> +#define TSNEP_RX_STATISTIC_INVALID_FRAME_MASK 0xFF000000
> +#define TSNEP_RX_STATISTIC_INVALID_FRAME_SHIFT 24
> +#define TSNEP_RX_STATISTIC_NO_DESC 0x0190
> +#define TSNEP_RX_STATISTIC_BUFFER_TOO_SMALL 0x0191
> +#define TSNEP_RX_STATISTIC_FIFO_OVERFLOW 0x0192
> +#define TSNEP_RX_STATISTIC_INVALID_FRAME 0x0193
> +#define TSNEP_RX_ASSIGN 0x01A0
> +#define TSNEP_RX_ASSIGN_ETHER_TYPE_ACTIVE 0x00000001
> +#define TSNEP_RX_ASSIGN_ETHER_TYPE_MASK 0xFFFF0000
> +#define TSNEP_RX_ASSIGN_ETHER_TYPE_SHIFT 16
> +#define TSNEP_MAC_ADDRESS_LOW 0x0800
> +#define TSNEP_MAC_ADDRESS_HIGH 0x0804
> +#define TSNEP_RX_FILTER 0x0806
> +#define TSNEP_RX_FILTER_ACCEPT_ALL_MULTICASTS 0x0001
> +#define TSNEP_RX_FILTER_ACCEPT_ALL_UNICASTS 0x0002
> +#define TSNEP_GC 0x0808
> +#define TSNEP_GC_ENABLE_A 0x00000002
> +#define TSNEP_GC_ENABLE_B 0x00000004
> +#define TSNEP_GC_DISABLE 0x00000008
> +#define TSNEP_GC_ENABLE_TIMEOUT 0x00000010
> +#define TSNEP_GC_ACTIVE_A 0x00000002
> +#define TSNEP_GC_ACTIVE_B 0x00000004
> +#define TSNEP_GC_CHANGE_AB 0x00000008
> +#define TSNEP_GC_TIMEOUT_ACTIVE 0x00000010
> +#define TSNEP_GC_TIMEOUT_SIGNAL 0x00000020
> +#define TSNEP_GC_LIST_ERROR 0x00000080
> +#define TSNEP_GC_OPEN 0x00FF0000
> +#define TSNEP_GC_OPEN_SHIFT 16
> +#define TSNEP_GC_NEXT_OPEN 0xFF000000
> +#define TSNEP_GC_NEXT_OPEN_SHIFT 24
> +#define TSNEP_GC_TIMEOUT 131072
> +#define TSNEP_GC_TIME 0x080C
> +#define TSNEP_GC_CHANGE 0x0810
> +#define TSNEP_GCL_A 0x2000
> +#define TSNEP_GCL_B 0x2800
> +#define TSNEP_GCL_SIZE SZ_2K
> +
> +/* tsnep gate control list operation */
> +struct tsnep_gcl_operation {
> + u32 properties;
> + u32 interval;
> +};
> +
> +#define TSNEP_GCL_COUNT (TSNEP_GCL_SIZE / sizeof(struct tsnep_gcl_operation))
> +#define TSNEP_GCL_MASK 0x000000FF
> +#define TSNEP_GCL_INSERT 0x20000000
> +#define TSNEP_GCL_CHANGE 0x40000000
> +#define TSNEP_GCL_LAST 0x80000000
> +#define TSNEP_GCL_MIN_INTERVAL 32
> +
> +/* tsnep TX/RX descriptor */
> +#define TSNEP_DESC_SIZE 256
> +#define TSNEP_DESC_SIZE_DATA_AFTER 2048
> +#define TSNEP_DESC_OFFSET 128
> +#define TSNEP_DESC_OWNER_COUNTER_MASK 0xC0000000
> +#define TSNEP_DESC_OWNER_COUNTER_SHIFT 30
> +#define TSNEP_DESC_LENGTH_MASK 0x00003FFF
> +#define TSNEP_DESC_INTERRUPT_FLAG 0x00040000
> +#define TSNEP_DESC_EXTENDED_WRITEBACK_FLAG 0x00080000
> +#define TSNEP_DESC_NO_LINK_FLAG 0x01000000
> +
> +/* tsnep TX descriptor */
> +struct tsnep_tx_desc {
> + __le32 properties;
> + __le32 more_properties;
> + __le32 reserved[2];
> + __le64 next;
> + __le64 tx;
> +};
> +
> +#define TSNEP_TX_DESC_OWNER_MASK 0xE0000000
> +#define TSNEP_TX_DESC_OWNER_USER_FLAG 0x20000000
> +#define TSNEP_TX_DESC_LAST_FRAGMENT_FLAG 0x00010000
> +#define TSNEP_TX_DESC_DATA_AFTER_DESC_FLAG 0x00020000
> +
> +/* tsnep TX descriptor writeback */
> +struct tsnep_tx_desc_wb {
> + __le32 properties;
> + __le32 reserved1[3];
> + __le64 timestamp;
> + __le32 dma_delay;
> + __le32 reserved2;
> +};
> +
> +#define TSNEP_TX_DESC_UNDERRUN_ERROR_FLAG 0x00010000
> +#define TSNEP_TX_DESC_DMA_DELAY_FIRST_DATA_MASK 0x0000FFFC
> +#define TSNEP_TX_DESC_DMA_DELAY_FIRST_DATA_SHIFT 2
> +#define TSNEP_TX_DESC_DMA_DELAY_LAST_DATA_MASK 0xFFFC0000
> +#define TSNEP_TX_DESC_DMA_DELAY_LAST_DATA_SHIFT 18
> +#define TSNEP_TX_DESC_DMA_DELAY_NS 64
> +
> +/* tsnep RX descriptor */
> +struct tsnep_rx_desc {
> + __le32 properties;
> + __le32 reserved[3];
> + __le64 next;
> + __le64 rx;
> +};
> +
> +#define TSNEP_RX_DESC_BUFFER_SIZE_MASK 0x00003FFC
> +
> +/* tsnep RX descriptor writeback */
> +struct tsnep_rx_desc_wb {
> + __le32 properties;
> + __le32 reserved[7];
> +};
> +
> +/* tsnep RX inline meta */
> +struct tsnep_rx_inline {
> + __le64 reserved;
> + __le64 timestamp;
> +};
> +
> +#define TSNEP_RX_INLINE_METADATA_SIZE (sizeof(struct tsnep_rx_inline))
> +
> +#endif /* _TSNEP_HW_H */
> diff --git a/drivers/net/ethernet/engleder/tsnep_main.c b/drivers/net/ethernet/engleder/tsnep_main.c
> new file mode 100644
> index 000000000000..86d488821a65
> --- /dev/null
> +++ b/drivers/net/ethernet/engleder/tsnep_main.c
> @@ -0,0 +1,1255 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */
> +
> +/* TSN endpoint Ethernet MAC driver
> + *
> + * The TSN endpoint Ethernet MAC is a FPGA based network device for real-time
> + * communication. It is designed for endpoints within TSN (Time Sensitive
> + * Networking) networks; e.g., for PLCs in the industrial automation case.
> + *
> + * It supports multiple TX/RX queue pairs. The first TX/RX queue pair is used
> + * by the driver.
> + *
> + * More information can be found here:
> + * - www.embedded-experts.at/tsn
> + * - www.engleder-embedded.com
> + */
> +
> +#include "tsnep.h"
> +#include "tsnep_hw.h"
> +
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_net.h>
> +#include <linux/of_mdio.h>
> +#include <linux/interrupt.h>
> +#include <linux/etherdevice.h>
> +#include <linux/phy.h>
> +#include <linux/iopoll.h>
> +
> +#define RX_SKB_LENGTH (round_up(TSNEP_RX_INLINE_METADATA_SIZE + ETH_HLEN + \
> + TSNEP_MAX_FRAME_SIZE + ETH_FCS_LEN, 4))
> +#define RX_SKB_RESERVE ((16 - TSNEP_RX_INLINE_METADATA_SIZE) + NET_IP_ALIGN)
> +#define RX_SKB_ALLOC_LENGTH (RX_SKB_RESERVE + RX_SKB_LENGTH)
> +
> +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
> +#define DMA_ADDR_HIGH(dma_addr) ((u32)(((dma_addr) >> 32) & 0xFFFFFFFF))
> +#else
> +#define DMA_ADDR_HIGH(dma_addr) ((u32)(0))
> +#endif
> +#define DMA_ADDR_LOW(dma_addr) ((u32)((dma_addr) & 0xFFFFFFFF))
> +
> +static void tsnep_enable_irq(struct tsnep_adapter *adapter, u32 mask)
> +{
> + iowrite32(mask, adapter->addr + ECM_INT_ENABLE);
> +}
> +
> +static void tsnep_disable_irq(struct tsnep_adapter *adapter, u32 mask)
> +{
> + mask |= ECM_INT_DISABLE;
> + iowrite32(mask, adapter->addr + ECM_INT_ENABLE);
> +}
> +
> +static irqreturn_t tsnep_irq(int irq, void *arg)
> +{
> + struct tsnep_adapter *adapter = arg;
> + u32 active = ioread32(adapter->addr + ECM_INT_ACTIVE);
> +
> + /* acknowledge interrupt */
> + if (active != 0)
> + iowrite32(active, adapter->addr + ECM_INT_ACKNOWLEDGE);
> +
> + /* handle link interrupt */
> + if ((active & ECM_INT_LINK) != 0) {
> + if (adapter->netdev->phydev)
> + phy_mac_interrupt(adapter->netdev->phydev);
> + }
> +
> + /* handle TX/RX queue 0 interrupt */
> + if ((active & adapter->queue[0].irq_mask) != 0) {
> + if (adapter->netdev) {
> + tsnep_disable_irq(adapter, adapter->queue[0].irq_mask);
> + napi_schedule(&adapter->queue[0].napi);
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int tsnep_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
> +{
> + struct tsnep_adapter *adapter = bus->priv;
> + u32 md;
> + int retval;
> +
> + if (regnum & MII_ADDR_C45)
> + return -EOPNOTSUPP;
> +
> + md = ECM_MD_READ;
> + if (!adapter->suppress_preamble)
> + md |= ECM_MD_PREAMBLE;
> + md |= (regnum << ECM_MD_ADDR_SHIFT) & ECM_MD_ADDR_MASK;
> + md |= (addr << ECM_MD_PHY_ADDR_SHIFT) & ECM_MD_PHY_ADDR_MASK;
> + iowrite32(md, adapter->addr + ECM_MD_CONTROL);
> + retval = readl_poll_timeout_atomic(adapter->addr + ECM_MD_STATUS, md,
> + !(md & ECM_MD_BUSY), 16, 1000);
> + if (retval != 0)
> + return retval;
> +
> + return (md & ECM_MD_DATA_MASK) >> ECM_MD_DATA_SHIFT;
> +}
> +
> +static int tsnep_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
> + u16 val)
> +{
> + struct tsnep_adapter *adapter = bus->priv;
> + u32 md;
> + int retval;
> +
> + if (regnum & MII_ADDR_C45)
> + return -EOPNOTSUPP;
> +
> + md = ECM_MD_WRITE;
> + if (!adapter->suppress_preamble)
> + md |= ECM_MD_PREAMBLE;
> + md |= (regnum << ECM_MD_ADDR_SHIFT) & ECM_MD_ADDR_MASK;
> + md |= (addr << ECM_MD_PHY_ADDR_SHIFT) & ECM_MD_PHY_ADDR_MASK;
> + md |= ((u32)val << ECM_MD_DATA_SHIFT) & ECM_MD_DATA_MASK;
> + iowrite32(md, adapter->addr + ECM_MD_CONTROL);
> + retval = readl_poll_timeout_atomic(adapter->addr + ECM_MD_STATUS, md,
> + !(md & ECM_MD_BUSY), 16, 1000);
> + if (retval != 0)
> + return retval;
> +
> + return 0;
> +}
> +
> +static void tsnep_phy_link_status_change(struct net_device *netdev)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> + struct phy_device *phydev = netdev->phydev;
> + u32 mode;
> +
> + if (phydev->link) {
> + switch (phydev->speed) {
> + case SPEED_100:
> + mode = ECM_LINK_MODE_100;
> + break;
> + case SPEED_1000:
> + mode = ECM_LINK_MODE_1000;
> + break;
> + default:
> + mode = ECM_LINK_MODE_OFF;
> + break;
> + }
> + iowrite32(mode, adapter->addr + ECM_STATUS);
> + }
> +
> + phy_print_status(netdev->phydev);
> +}
> +
> +static int tsnep_phy_open(struct tsnep_adapter *adapter)
> +{
> + struct phy_device *phydev;
> + struct ethtool_eee ethtool_eee;
> + int retval;
> +
> + retval = phy_connect_direct(adapter->netdev, adapter->phydev,
> + tsnep_phy_link_status_change,
> + adapter->phy_mode);
> + if (retval)
> + return retval;
> + phydev = adapter->netdev->phydev;
> +
> + /* MAC supports only 100Mbps|1000Mbps full duplex
> + * SPE (Single Pair Ethernet) is also an option but not implemented yet
> + */
> + phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
> + phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Full_BIT);
> + phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
> + phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
> +
> + /* disable EEE autoneg, EEE not supported by TSNEP */
> + memset(ðtool_eee, 0, sizeof(ethtool_eee));
> + phy_ethtool_set_eee(adapter->phydev, ðtool_eee);
> +
> + adapter->phydev->irq = PHY_MAC_INTERRUPT;
> + phy_start(adapter->phydev);
> +
> + return 0;
> +}
> +
> +static void tsnep_phy_close(struct tsnep_adapter *adapter)
> +{
> + phy_stop(adapter->netdev->phydev);
> + phy_disconnect(adapter->netdev->phydev);
> + adapter->netdev->phydev = NULL;
> +}
> +
> +static void tsnep_tx_ring_cleanup(struct tsnep_tx *tx)
> +{
> + struct device *dmadev = tx->adapter->dmadev;
> + int i;
> +
> + memset(tx->entry, 0, sizeof(tx->entry));
> +
> + for (i = 0; i < TSNEP_RING_PAGE_COUNT; i++) {
> + if (tx->page[i]) {
> + dma_free_coherent(dmadev, PAGE_SIZE, tx->page[i],
> + tx->page_dma[i]);
> + tx->page[i] = NULL;
> + tx->page_dma[i] = 0;
> + }
> + }
> +}
> +
> +static int tsnep_tx_ring_init(struct tsnep_tx *tx)
> +{
> + struct device *dmadev = tx->adapter->dmadev;
> + struct tsnep_tx_entry *entry;
> + struct tsnep_tx_entry *next_entry;
> + int i, j;
> + int retval;
> +
> + for (i = 0; i < TSNEP_RING_PAGE_COUNT; i++) {
> + tx->page[i] =
> + dma_alloc_coherent(dmadev, PAGE_SIZE, &tx->page_dma[i],
> + GFP_KERNEL);
> + if (!tx->page[i]) {
> + retval = -ENOMEM;
> + goto alloc_failed;
> + }
> + for (j = 0; j < TSNEP_RING_ENTRIES_PER_PAGE; j++) {
> + entry = &tx->entry[TSNEP_RING_ENTRIES_PER_PAGE * i + j];
> + entry->desc_wb = (struct tsnep_tx_desc_wb *)
> + (((u8 *)tx->page[i]) + TSNEP_DESC_SIZE * j);
> + entry->desc = (struct tsnep_tx_desc *)
> + (((u8 *)entry->desc_wb) + TSNEP_DESC_OFFSET);
> + entry->desc_dma = tx->page_dma[i] + TSNEP_DESC_SIZE * j;
> + }
> + }
> + for (i = 0; i < TSNEP_RING_SIZE; i++) {
> + entry = &tx->entry[i];
> + next_entry = &tx->entry[(i + 1) % TSNEP_RING_SIZE];
> + entry->desc->next = __cpu_to_le64(next_entry->desc_dma);
> + }
> +
> + return 0;
> +
> +alloc_failed:
> + tsnep_tx_ring_cleanup(tx);
> + return retval;
> +}
> +
> +static void tsnep_tx_activate(struct tsnep_tx *tx, int index, bool last)
> +{
> + struct tsnep_tx_entry *entry = &tx->entry[index];
> +
> + entry->properties = 0;
> + if (entry->skb) {
> + entry->properties =
> + skb_pagelen(entry->skb) & TSNEP_DESC_LENGTH_MASK;
> + entry->properties |= TSNEP_DESC_INTERRUPT_FLAG;
> + if (skb_shinfo(entry->skb)->tx_flags & SKBTX_IN_PROGRESS)
> + entry->properties |= TSNEP_DESC_EXTENDED_WRITEBACK_FLAG;
> +
> + /* toggle user flag to prevent false acknowledge
> + *
> + * Only the first fragment is acknowledged. For all other
> + * fragments no acknowledge is done and the last written owner
> + * counter stays in the writeback descriptor. Therefore, it is
> + * possible that the last written owner counter is identical to
> + * the new incremented owner counter and a false acknowledge is
> + * detected before the real acknowledge has been done by
> + * hardware.
> + *
> + * The user flag is used to prevent this situation. The user
> + * flag is copied to the writeback descriptor by the hardware
> + * and is used as additional acknowledge data. By toggeling the
> + * user flag only for the first fragment (which is
> + * acknowledged), it is guaranteed that the last acknowledge
> + * done for this descriptor has used a different user flag and
> + * cannot be detected as false acknowledge.
> + */
> + entry->owner_user_flag = !entry->owner_user_flag;
> + }
> + if (last)
> + entry->properties |= TSNEP_TX_DESC_LAST_FRAGMENT_FLAG;
> + if (index == tx->increment_owner_counter) {
> + tx->owner_counter++;
> + if (tx->owner_counter == 4)
> + tx->owner_counter = 1;
> + tx->increment_owner_counter--;
> + if (tx->increment_owner_counter < 0)
> + tx->increment_owner_counter = TSNEP_RING_SIZE - 1;
> + }
> + entry->properties |=
> + (tx->owner_counter << TSNEP_DESC_OWNER_COUNTER_SHIFT) &
> + TSNEP_DESC_OWNER_COUNTER_MASK;
> + if (entry->owner_user_flag)
> + entry->properties |= TSNEP_TX_DESC_OWNER_USER_FLAG;
> + entry->desc->more_properties =
> + __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
> +
> + dma_wmb();
> +
> + entry->desc->properties = __cpu_to_le32(entry->properties);
> +}
> +
> +static int tsnep_tx_desc_available(struct tsnep_tx *tx)
> +{
> + if (tx->read <= tx->write)
> + return TSNEP_RING_SIZE - tx->write + tx->read - 1;
> + else
> + return tx->read - tx->write - 1;
> +}
> +
> +static int tsnep_tx_map(struct sk_buff *skb, struct tsnep_tx *tx, int count)
> +{
> + struct device *dmadev = tx->adapter->dmadev;
> + struct tsnep_tx_entry *entry;
> + unsigned int len;
> + dma_addr_t dma;
> + int i;
> +
> + for (i = 0; i < count; i++) {
> + entry = &tx->entry[(tx->write + i) % TSNEP_RING_SIZE];
> +
> + if (i == 0) {
> + len = skb_headlen(skb);
> + dma = dma_map_single(dmadev, skb->data, len,
> + DMA_TO_DEVICE);
> + } else {
> + len = skb_frag_size(&skb_shinfo(skb)->frags[i - 1]);
> + dma = skb_frag_dma_map(dmadev,
> + &skb_shinfo(skb)->frags[i - 1],
> + 0, len, DMA_TO_DEVICE);
> + }
> + if (dma_mapping_error(dmadev, dma))
> + return -ENOMEM;
> +
> + dma_unmap_len_set(entry, len, len);
> + dma_unmap_addr_set(entry, dma, dma);
> +
> + entry->desc->tx = __cpu_to_le64(dma);
> + }
> +
> + return 0;
> +}
> +
> +static void tsnep_tx_unmap(struct tsnep_tx *tx, int count)
> +{
> + struct device *dmadev = tx->adapter->dmadev;
> + struct tsnep_tx_entry *entry;
> + int i;
> +
> + for (i = 0; i < count; i++) {
> + entry = &tx->entry[(tx->read + i) % TSNEP_RING_SIZE];
> +
> + if (dma_unmap_len(entry, len)) {
> + if (i == 0)
> + dma_unmap_single(dmadev,
> + dma_unmap_addr(entry, dma),
> + dma_unmap_len(entry, len),
> + DMA_TO_DEVICE);
> + else
> + dma_unmap_page(dmadev,
> + dma_unmap_addr(entry, dma),
> + dma_unmap_len(entry, len),
> + DMA_TO_DEVICE);
> + dma_unmap_len_set(entry, len, 0);
> + }
> + }
> +}
> +
> +static netdev_tx_t tsnep_xmit_frame_ring(struct sk_buff *skb,
> + struct tsnep_tx *tx)
> +{
> + unsigned long flags;
> + int count = 1;
> + struct tsnep_tx_entry *entry;
> + int i;
> + int retval;
> +
> + if (skb_shinfo(skb)->nr_frags > 0)
> + count += skb_shinfo(skb)->nr_frags;
> +
> + spin_lock_irqsave(&tx->lock, flags);
> +
> + if (tsnep_tx_desc_available(tx) < count) {
> + /* ring full, shall not happen because queue is stopped if full
> + * below
> + */
> + netif_stop_queue(tx->adapter->netdev);
> +
> + spin_unlock_irqrestore(&tx->lock, flags);
> +
> + return NETDEV_TX_BUSY;
> + }
> +
> + entry = &tx->entry[tx->write];
> + entry->skb = skb;
> +
> + retval = tsnep_tx_map(skb, tx, count);
> + if (retval != 0) {
> + tsnep_tx_unmap(tx, count);
> + dev_kfree_skb_any(entry->skb);
> + entry->skb = NULL;
> +
> + tx->dropped++;
> +
> + spin_unlock_irqrestore(&tx->lock, flags);
> +
> + netdev_err(tx->adapter->netdev, "TX DMA map failed\n");
> +
> + return NETDEV_TX_OK;
> + }
> +
> + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)
> + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
> +
> + for (i = 0; i < count; i++)
> + tsnep_tx_activate(tx, (tx->write + i) % TSNEP_RING_SIZE,
> + i == (count - 1));
> + skb_tx_timestamp(skb);
> +
> + /* entry->properties shall be valid before write pointer is
> + * incrememted
> + */
> + wmb();
A lighter variant like smp_wmb() is not sufficient here?
> + tx->write = (tx->write + count) % TSNEP_RING_SIZE;
> +
> + iowrite32(TSNEP_CONTROL_TX_ENABLE, tx->addr + TSNEP_CONTROL);
> +
> + if (tsnep_tx_desc_available(tx) < (MAX_SKB_FRAGS + 1)) {
> + /* ring can get full with next frame */
> + netif_stop_queue(tx->adapter->netdev);
> + }
> +
> + tx->packets++;
> + tx->bytes += skb_pagelen(entry->skb) + ETH_FCS_LEN;
> +
> + spin_unlock_irqrestore(&tx->lock, flags);
> +
> + return NETDEV_TX_OK;
> +}
> +
> +static bool tsnep_tx_poll(struct tsnep_tx *tx, int napi_budget)
> +{
> + unsigned long flags;
> + int budget = 128;
> + struct tsnep_tx_entry *entry;
> + int count;
> +
> + spin_lock_irqsave(&tx->lock, flags);
> +
> + do {
> + if (tx->read == tx->write)
> + break;
> +
> + entry = &tx->entry[tx->read];
> + if ((__le32_to_cpu(entry->desc_wb->properties) &
> + TSNEP_TX_DESC_OWNER_MASK) !=
> + (entry->properties & TSNEP_TX_DESC_OWNER_MASK))
> + break;
> +
> + dma_rmb();
> +
Memory barriers should always have a comment explaining why
they are needed. I think checkpatch would complain here.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 1/3] dt-bindings: Add vendor prefix for Engleder
2021-11-15 20:50 ` [PATCH net-next v5 1/3] dt-bindings: Add vendor prefix for Engleder Gerhard Engleder
@ 2021-11-15 22:24 ` Andrew Lunn
0 siblings, 0 replies; 13+ messages in thread
From: Andrew Lunn @ 2021-11-15 22:24 UTC (permalink / raw)
To: Gerhard Engleder; +Cc: davem, kuba, netdev, Rob Herring
On Mon, Nov 15, 2021 at 09:50:03PM +0100, Gerhard Engleder wrote:
> Engleder develops FPGA based controllers for real-time communication.
>
> Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com>
> Acked-by: Rob Herring <robh@kernel.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 2/3] dt-bindings: net: Add tsnep Ethernet controller
2021-11-15 20:50 ` [PATCH net-next v5 2/3] dt-bindings: net: Add tsnep Ethernet controller Gerhard Engleder
@ 2021-11-15 22:25 ` Andrew Lunn
0 siblings, 0 replies; 13+ messages in thread
From: Andrew Lunn @ 2021-11-15 22:25 UTC (permalink / raw)
To: Gerhard Engleder; +Cc: davem, kuba, netdev, Rob Herring
On Mon, Nov 15, 2021 at 09:50:04PM +0100, Gerhard Engleder wrote:
> The TSN endpoint Ethernet MAC is a FPGA based network device for
> real-time communication.
>
> It is integrated as normal Ethernet controller with
> ethernet-controller.yaml and mdio.yaml.
>
> Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
2021-11-15 20:50 ` [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver Gerhard Engleder
2021-11-15 22:21 ` Heiner Kallweit
@ 2021-11-15 23:04 ` Andrew Lunn
2021-11-16 22:16 ` Gerhard Engleder
2021-11-18 17:31 ` kernel test robot
2 siblings, 1 reply; 13+ messages in thread
From: Andrew Lunn @ 2021-11-15 23:04 UTC (permalink / raw)
To: Gerhard Engleder; +Cc: davem, kuba, netdev
> +static int tsnep_ethtool_get_regs_len(struct net_device *netdev)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> + int len;
> + int num_queues;
> +
> + len = TSNEP_MAC_SIZE;
> + num_queues = max(adapter->num_tx_queues, adapter->num_rx_queues);
> + len += TSNEP_QUEUE_SIZE * (num_queues - 1);
Why the num_queues - 1 ? A comment here might be good explaining it.
> +
> + return len;
> +}
> +
> +static void tsnep_ethtool_get_regs(struct net_device *netdev,
> + struct ethtool_regs *regs,
> + void *p)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(netdev);
> +
> + regs->version = 1;
> +
> + memcpy_fromio(p, adapter->addr, regs->len);
> +}
So the registers and the queues are contiguous?
> +static int tsnep_ethtool_get_ts_info(struct net_device *dev,
> + struct ethtool_ts_info *info)
> +{
> + struct tsnep_adapter *adapter = netdev_priv(dev);
> +
> + info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
> + SOF_TIMESTAMPING_RX_SOFTWARE |
> + SOF_TIMESTAMPING_SOFTWARE |
> + SOF_TIMESTAMPING_TX_HARDWARE |
> + SOF_TIMESTAMPING_RX_HARDWARE |
> + SOF_TIMESTAMPING_RAW_HARDWARE;
> +
> + if (adapter->ptp_clock)
> + info->phc_index = ptp_clock_index(adapter->ptp_clock);
> + else
> + info->phc_index = -1;
> +
> + info->tx_types = BIT(HWTSTAMP_TX_OFF) |
> + BIT(HWTSTAMP_TX_ON);
> + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
> + BIT(HWTSTAMP_FILTER_ALL);
> +
> + return 0;
> +}
You should Cc: Richard Cochran <richardcochran@gmail.com> for the PTP
parts.
> +static int tsnep_mdio_init(struct tsnep_adapter *adapter)
> +{
> + struct device_node *np = adapter->pdev->dev.of_node;
> + int retval;
> +
> + if (np) {
> + np = of_get_child_by_name(np, "mdio");
> + if (!np)
> + return 0;
> +
> + adapter->suppress_preamble =
> + of_property_read_bool(np, "suppress-preamble");
> + }
> +
> + adapter->mdiobus = devm_mdiobus_alloc(&adapter->pdev->dev);
> + if (!adapter->mdiobus) {
> + retval = -ENOMEM;
> +
> + goto out;
> + }
> +
> + adapter->mdiobus->priv = (void *)adapter;
> + adapter->mdiobus->parent = &adapter->pdev->dev;
> + adapter->mdiobus->read = tsnep_mdiobus_read;
> + adapter->mdiobus->write = tsnep_mdiobus_write;
> + adapter->mdiobus->name = TSNEP "-mdiobus";
> + snprintf(adapter->mdiobus->id, MII_BUS_ID_SIZE, "%s",
> + adapter->pdev->name);
> +
> + if (np) {
> + retval = of_mdiobus_register(adapter->mdiobus, np);
> +
> + of_node_put(np);
> + } else {
> + /* do not scan broadcast address */
> + adapter->mdiobus->phy_mask = 0x0000001;
> +
> + retval = mdiobus_register(adapter->mdiobus);
You can probably simply this. of_mdiobus_register() is happy to take a
NULL pointer for np, and will fall back to mdiobus_register().
> diff --git a/drivers/net/ethernet/engleder/tsnep_test.c b/drivers/net/ethernet/engleder/tsnep_test.c
You have quite a lot of code in this file. Could it either be
1) A loadable module which extends the base driver?
2) A build time configuration option?
What percentage of the overall driver binary does this test code take
up?
Apart from the minor comments above, ethtool, mdio, phy all looks
good.
Andrew
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
2021-11-15 22:21 ` Heiner Kallweit
@ 2021-11-16 21:47 ` Gerhard Engleder
0 siblings, 0 replies; 13+ messages in thread
From: Gerhard Engleder @ 2021-11-16 21:47 UTC (permalink / raw)
To: Heiner Kallweit; +Cc: David Miller, Jakub Kicinski, Andrew Lunn, netdev
On Mon, Nov 15, 2021 at 11:21 PM Heiner Kallweit <hkallweit1@gmail.com> wrote:
> On 15.11.2021 21:50, Gerhard Engleder wrote:
> > + /* entry->properties shall be valid before write pointer is
> > + * incrememted
> > + */
> > + wmb();
>
> A lighter variant like smp_wmb() is not sufficient here?
This is some leftover from lockless descriptor ring processing, which has
been replaced by a spinlock. I will remove it.
> > + dma_rmb();
> > +
>
> Memory barriers should always have a comment explaining why
> they are needed. I think checkpatch would complain here.
I don't see any checkpatch warning, but you are right. I will document
all DMA memory barriers.
Gerhard
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
2021-11-15 23:04 ` Andrew Lunn
@ 2021-11-16 22:16 ` Gerhard Engleder
2021-11-16 23:41 ` Andrew Lunn
0 siblings, 1 reply; 13+ messages in thread
From: Gerhard Engleder @ 2021-11-16 22:16 UTC (permalink / raw)
To: Andrew Lunn; +Cc: David Miller, Jakub Kicinski, netdev
> > +static int tsnep_ethtool_get_regs_len(struct net_device *netdev)
> > +{
> > + struct tsnep_adapter *adapter = netdev_priv(netdev);
> > + int len;
> > + int num_queues;
> > +
> > + len = TSNEP_MAC_SIZE;
> > + num_queues = max(adapter->num_tx_queues, adapter->num_rx_queues);
> > + len += TSNEP_QUEUE_SIZE * (num_queues - 1);
>
> Why the num_queues - 1 ? A comment here might be good explaining it.
First queue is within TSNEP_MAC_SIZE. I will add a comment.
>
> > +
> > + return len;
> > +}
> > +
> > +static void tsnep_ethtool_get_regs(struct net_device *netdev,
> > + struct ethtool_regs *regs,
> > + void *p)
> > +{
> > + struct tsnep_adapter *adapter = netdev_priv(netdev);
> > +
> > + regs->version = 1;
> > +
> > + memcpy_fromio(p, adapter->addr, regs->len);
> > +}
>
> So the registers and the queues are contiguous?
Yes, unused addresses in between are defined to be readable (zero).
>
> > +static int tsnep_ethtool_get_ts_info(struct net_device *dev,
> > + struct ethtool_ts_info *info)
> > +{
> > + struct tsnep_adapter *adapter = netdev_priv(dev);
> > +
> > + info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
> > + SOF_TIMESTAMPING_RX_SOFTWARE |
> > + SOF_TIMESTAMPING_SOFTWARE |
> > + SOF_TIMESTAMPING_TX_HARDWARE |
> > + SOF_TIMESTAMPING_RX_HARDWARE |
> > + SOF_TIMESTAMPING_RAW_HARDWARE;
> > +
> > + if (adapter->ptp_clock)
> > + info->phc_index = ptp_clock_index(adapter->ptp_clock);
> > + else
> > + info->phc_index = -1;
> > +
> > + info->tx_types = BIT(HWTSTAMP_TX_OFF) |er is 484kb with test c
> > + BIT(HWTSTAMP_TX_ON);
> > + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
> > + BIT(HWTSTAMP_FILTER_ALL);
> > +
> > + return 0;
> > +}
>
> You should Cc: Richard Cochran <richardcochran@gmail.com> for the PTP
> parts.
Thanks for the hint. I will add him.
>
> > +static int tsnep_mdio_init(struct tsnep_adapter *adapter)
> > +{
> > + struct device_node *np = adapter->pdev->dev.of_node;
> > + int retval;
> > +
> > + if (np) {
> > + np = of_get_child_by_name(np, "mdio");
> > + if (!np)
> > + return 0;
> > +
> > + adapter->suppress_preamble =
> > + of_property_read_bool(np, "suppress-preamble");
> > + }
> > +
> > + adapter->mdiobus = devm_mdiobus_alloc(&adapter->pdev->dev);
> > + if (!adapter->mdiobus) {
> > + retval = -ENOMEM;
> > +
> > + goto out;
> > + }
> > +
> > + adapter->mdiobus->priv = (void *)adapter;
> > + adapter->mdiobus->parent = &adapter->pdev->dev;
> > + adapter->mdiobus->read = tsnep_mdiobus_read;
> > + adapter->mdiobus->write = tsnep_mdiobus_write;
> > + adapter->mdiobus->name = TSNEP "-mdiobus";
> > + snprintf(adapter->mdiobus->id, MII_BUS_ID_SIZE, "%s",
> > + adapter->pdev->name);
> > +
> > + if (np) {
> > + retval = of_mdiobus_register(adapter->mdiobus, np);
> > +
> > + of_node_put(np);
> > + } else {
> > + /* do not scan broadcast address */
> > + adapter->mdiobus->phy_mask = 0x0000001;
> > +
> > + retval = mdiobus_register(adapter->mdiobus);
>
> You can probably simply this. of_mdiobus_register() is happy to take a
> NULL pointer for np, and will fall back to mdiobus_register().
I will think about it. Driver is also in use with v4.9 and this fallback
does not exist there.
> > diff --git a/drivers/net/ethernet/engleder/tsnep_test.c b/drivers/net/ethernet/engleder/tsnep_test.c
>
> You have quite a lot of code in this file. Could it either be
>
> 1) A loadable module which extends the base driver?
> 2) A build time configuration option?
>
> What percentage of the overall driver binary does this test code take
> up?
Driver is 484kB with test code and 396kB without. So test code is roughly
20% currently. In my opinion a configuration option makes more sense,
because a loadable module would require exported symbols. Shall I add a
build time configuration option?
> Apart from the minor comments above, ethtool, mdio, phy all looks
> good.
Thanks!
Gerhard
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
2021-11-16 22:16 ` Gerhard Engleder
@ 2021-11-16 23:41 ` Andrew Lunn
0 siblings, 0 replies; 13+ messages in thread
From: Andrew Lunn @ 2021-11-16 23:41 UTC (permalink / raw)
To: Gerhard Engleder; +Cc: David Miller, Jakub Kicinski, netdev
> > > diff --git a/drivers/net/ethernet/engleder/tsnep_test.c b/drivers/net/ethernet/engleder/tsnep_test.c
> >
> > You have quite a lot of code in this file. Could it either be
> >
> > 1) A loadable module which extends the base driver?
> > 2) A build time configuration option?
> >
> > What percentage of the overall driver binary does this test code take
> > up?
>
> Driver is 484kB with test code and 396kB without. So test code is roughly
> 20% currently. In my opinion a configuration option makes more sense,
Yes, that is good, please add one.
Andrew
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
2021-11-15 20:50 ` [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver Gerhard Engleder
@ 2021-11-18 17:31 ` kernel test robot
2021-11-15 23:04 ` Andrew Lunn
2021-11-18 17:31 ` kernel test robot
2 siblings, 0 replies; 13+ messages in thread
From: kernel test robot @ 2021-11-18 17:31 UTC (permalink / raw)
To: Gerhard Engleder, davem, kuba, andrew
Cc: kbuild-all, netdev, Gerhard Engleder
[-- Attachment #1: Type: text/plain, Size: 15810 bytes --]
Hi Gerhard,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on net-next/master]
url: https://github.com/0day-ci/linux/commits/Gerhard-Engleder/TSN-endpoint-Ethernet-MAC-driver/20211116-052158
base: https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git a5bdc36354cbf1a1a91396f4da548ff484686305
config: m68k-randconfig-r004-20211118 (attached as .config)
compiler: m68k-linux-gcc (GCC) 11.2.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/0day-ci/linux/commit/3252efb6502fdfc25aa357f7dca6290ba9b140e9
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Gerhard-Engleder/TSN-endpoint-Ethernet-MAC-driver/20211116-052158
git checkout 3252efb6502fdfc25aa357f7dca6290ba9b140e9
# save the attached .config to linux build tree
mkdir build_dir
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross O=build_dir ARCH=m68k SHELL=/bin/bash drivers/net/ethernet/engleder/
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>
All error/warnings (new ones prefixed by >>):
In file included from include/linux/swab.h:5,
from include/uapi/linux/byteorder/big_endian.h:13,
from include/linux/byteorder/big_endian.h:5,
from arch/m68k/include/uapi/asm/byteorder.h:5,
from include/asm-generic/bitops/le.h:7,
from arch/m68k/include/asm/bitops.h:529,
from include/linux/bitops.h:33,
from include/linux/log2.h:12,
from include/asm-generic/div64.h:55,
from arch/m68k/include/asm/div64.h:6,
from include/linux/math.h:5,
from include/linux/math64.h:6,
from include/linux/time64.h:5,
from include/linux/restart_block.h:10,
from include/linux/thread_info.h:14,
from include/asm-generic/preempt.h:5,
from ./arch/m68k/include/generated/asm/preempt.h:1,
from include/linux/preempt.h:78,
from arch/m68k/include/asm/irqflags.h:6,
from include/linux/irqflags.h:16,
from arch/m68k/include/asm/atomic.h:6,
from include/linux/atomic.h:7,
from include/linux/rcupdate.h:25,
from include/linux/rculist.h:11,
from include/linux/pid.h:5,
from include/linux/sched.h:14,
from include/linux/ratelimit.h:6,
from include/linux/dev_printk.h:16,
from include/linux/device.h:15,
from include/linux/platform_device.h:13,
from drivers/net/ethernet/engleder/tsnep.h:9,
from drivers/net/ethernet/engleder/tsnep_main.c:18:
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_tx_activate':
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:118:39: note: in definition of macro '__swab32'
118 | (__builtin_constant_p((__u32)(x)) ? \
| ^
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:19:19: note: in definition of macro '___constant_swab32'
19 | (((__u32)(x) & (__u32)0x000000ffUL) << 24) | \
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:20:19: note: in definition of macro '___constant_swab32'
20 | (((__u32)(x) & (__u32)0x0000ff00UL) << 8) | \
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:21:19: note: in definition of macro '___constant_swab32'
21 | (((__u32)(x) & (__u32)0x00ff0000UL) >> 8) | \
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:22:19: note: in definition of macro '___constant_swab32'
22 | (((__u32)(x) & (__u32)0xff000000UL) >> 24)))
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:120:19: note: in definition of macro '__swab32'
120 | __fswab32(x))
| ^
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_tx_unmap':
>> drivers/net/ethernet/engleder/tsnep_main.c:343:32: warning: variable 'entry' set but not used [-Wunused-but-set-variable]
343 | struct tsnep_tx_entry *entry;
| ^~~~~
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_rx_ring_cleanup':
>> drivers/net/ethernet/engleder/tsnep_main.c:543:26: error: 'struct tsnep_rx_entry' has no member named 'dma'
543 | if (entry->dma)
| ^~
In file included from drivers/net/ethernet/engleder/tsnep.h:10,
from drivers/net/ethernet/engleder/tsnep_main.c:18:
drivers/net/ethernet/engleder/tsnep_main.c:544:55: error: 'struct tsnep_rx_entry' has no member named 'dma'
544 | dma_unmap_single(dmadev, entry->dma, entry->len,
| ^~
include/linux/dma-mapping.h:407:64: note: in definition of macro 'dma_unmap_single'
407 | #define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
| ^
>> drivers/net/ethernet/engleder/tsnep_main.c:544:67: error: 'struct tsnep_rx_entry' has no member named 'len'
544 | dma_unmap_single(dmadev, entry->dma, entry->len,
| ^~
include/linux/dma-mapping.h:407:67: note: in definition of macro 'dma_unmap_single'
407 | #define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
| ^
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_rx_activate':
drivers/net/ethernet/engleder/tsnep_main.c:638:34: error: 'struct tsnep_rx_entry' has no member named 'len'
638 | entry->properties = entry->len & TSNEP_DESC_LENGTH_MASK;
| ^~
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_rx_poll':
>> drivers/net/ethernet/engleder/tsnep_main.c:666:9: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
666 | int length;
| ^~~
>> drivers/net/ethernet/engleder/tsnep_main.c:679:17: error: 'dma' undeclared (first use in this function)
679 | dma = entry->dma;
| ^~~
drivers/net/ethernet/engleder/tsnep_main.c:679:17: note: each undeclared identifier is reported only once for each function it appears in
drivers/net/ethernet/engleder/tsnep_main.c:679:28: error: 'struct tsnep_rx_entry' has no member named 'dma'
679 | dma = entry->dma;
| ^~
>> drivers/net/ethernet/engleder/tsnep_main.c:680:17: error: 'len' undeclared (first use in this function)
680 | len = entry->len;
| ^~~
drivers/net/ethernet/engleder/tsnep_main.c:680:28: error: 'struct tsnep_rx_entry' has no member named 'len'
680 | len = entry->len;
| ^~
vim +292 drivers/net/ethernet/engleder/tsnep_main.c
243
244 static void tsnep_tx_activate(struct tsnep_tx *tx, int index, bool last)
245 {
246 struct tsnep_tx_entry *entry = &tx->entry[index];
247
248 entry->properties = 0;
249 if (entry->skb) {
250 entry->properties =
251 skb_pagelen(entry->skb) & TSNEP_DESC_LENGTH_MASK;
252 entry->properties |= TSNEP_DESC_INTERRUPT_FLAG;
253 if (skb_shinfo(entry->skb)->tx_flags & SKBTX_IN_PROGRESS)
254 entry->properties |= TSNEP_DESC_EXTENDED_WRITEBACK_FLAG;
255
256 /* toggle user flag to prevent false acknowledge
257 *
258 * Only the first fragment is acknowledged. For all other
259 * fragments no acknowledge is done and the last written owner
260 * counter stays in the writeback descriptor. Therefore, it is
261 * possible that the last written owner counter is identical to
262 * the new incremented owner counter and a false acknowledge is
263 * detected before the real acknowledge has been done by
264 * hardware.
265 *
266 * The user flag is used to prevent this situation. The user
267 * flag is copied to the writeback descriptor by the hardware
268 * and is used as additional acknowledge data. By toggeling the
269 * user flag only for the first fragment (which is
270 * acknowledged), it is guaranteed that the last acknowledge
271 * done for this descriptor has used a different user flag and
272 * cannot be detected as false acknowledge.
273 */
274 entry->owner_user_flag = !entry->owner_user_flag;
275 }
276 if (last)
277 entry->properties |= TSNEP_TX_DESC_LAST_FRAGMENT_FLAG;
278 if (index == tx->increment_owner_counter) {
279 tx->owner_counter++;
280 if (tx->owner_counter == 4)
281 tx->owner_counter = 1;
282 tx->increment_owner_counter--;
283 if (tx->increment_owner_counter < 0)
284 tx->increment_owner_counter = TSNEP_RING_SIZE - 1;
285 }
286 entry->properties |=
287 (tx->owner_counter << TSNEP_DESC_OWNER_COUNTER_SHIFT) &
288 TSNEP_DESC_OWNER_COUNTER_MASK;
289 if (entry->owner_user_flag)
290 entry->properties |= TSNEP_TX_DESC_OWNER_USER_FLAG;
291 entry->desc->more_properties =
> 292 __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
293
294 dma_wmb();
295
296 entry->desc->properties = __cpu_to_le32(entry->properties);
297 }
298
299 static int tsnep_tx_desc_available(struct tsnep_tx *tx)
300 {
301 if (tx->read <= tx->write)
302 return TSNEP_RING_SIZE - tx->write + tx->read - 1;
303 else
304 return tx->read - tx->write - 1;
305 }
306
307 static int tsnep_tx_map(struct sk_buff *skb, struct tsnep_tx *tx, int count)
308 {
309 struct device *dmadev = tx->adapter->dmadev;
310 struct tsnep_tx_entry *entry;
311 unsigned int len;
312 dma_addr_t dma;
313 int i;
314
315 for (i = 0; i < count; i++) {
316 entry = &tx->entry[(tx->write + i) % TSNEP_RING_SIZE];
317
318 if (i == 0) {
319 len = skb_headlen(skb);
320 dma = dma_map_single(dmadev, skb->data, len,
321 DMA_TO_DEVICE);
322 } else {
323 len = skb_frag_size(&skb_shinfo(skb)->frags[i - 1]);
324 dma = skb_frag_dma_map(dmadev,
325 &skb_shinfo(skb)->frags[i - 1],
326 0, len, DMA_TO_DEVICE);
327 }
328 if (dma_mapping_error(dmadev, dma))
329 return -ENOMEM;
330
331 dma_unmap_len_set(entry, len, len);
332 dma_unmap_addr_set(entry, dma, dma);
333
334 entry->desc->tx = __cpu_to_le64(dma);
335 }
336
337 return 0;
338 }
339
340 static void tsnep_tx_unmap(struct tsnep_tx *tx, int count)
341 {
342 struct device *dmadev = tx->adapter->dmadev;
> 343 struct tsnep_tx_entry *entry;
344 int i;
345
346 for (i = 0; i < count; i++) {
347 entry = &tx->entry[(tx->read + i) % TSNEP_RING_SIZE];
348
349 if (dma_unmap_len(entry, len)) {
350 if (i == 0)
351 dma_unmap_single(dmadev,
352 dma_unmap_addr(entry, dma),
353 dma_unmap_len(entry, len),
354 DMA_TO_DEVICE);
355 else
356 dma_unmap_page(dmadev,
357 dma_unmap_addr(entry, dma),
358 dma_unmap_len(entry, len),
359 DMA_TO_DEVICE);
360 dma_unmap_len_set(entry, len, 0);
361 }
362 }
363 }
364
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 40489 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver
@ 2021-11-18 17:31 ` kernel test robot
0 siblings, 0 replies; 13+ messages in thread
From: kernel test robot @ 2021-11-18 17:31 UTC (permalink / raw)
To: kbuild-all
[-- Attachment #1: Type: text/plain, Size: 16110 bytes --]
Hi Gerhard,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on net-next/master]
url: https://github.com/0day-ci/linux/commits/Gerhard-Engleder/TSN-endpoint-Ethernet-MAC-driver/20211116-052158
base: https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git a5bdc36354cbf1a1a91396f4da548ff484686305
config: m68k-randconfig-r004-20211118 (attached as .config)
compiler: m68k-linux-gcc (GCC) 11.2.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/0day-ci/linux/commit/3252efb6502fdfc25aa357f7dca6290ba9b140e9
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Gerhard-Engleder/TSN-endpoint-Ethernet-MAC-driver/20211116-052158
git checkout 3252efb6502fdfc25aa357f7dca6290ba9b140e9
# save the attached .config to linux build tree
mkdir build_dir
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross O=build_dir ARCH=m68k SHELL=/bin/bash drivers/net/ethernet/engleder/
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>
All error/warnings (new ones prefixed by >>):
In file included from include/linux/swab.h:5,
from include/uapi/linux/byteorder/big_endian.h:13,
from include/linux/byteorder/big_endian.h:5,
from arch/m68k/include/uapi/asm/byteorder.h:5,
from include/asm-generic/bitops/le.h:7,
from arch/m68k/include/asm/bitops.h:529,
from include/linux/bitops.h:33,
from include/linux/log2.h:12,
from include/asm-generic/div64.h:55,
from arch/m68k/include/asm/div64.h:6,
from include/linux/math.h:5,
from include/linux/math64.h:6,
from include/linux/time64.h:5,
from include/linux/restart_block.h:10,
from include/linux/thread_info.h:14,
from include/asm-generic/preempt.h:5,
from ./arch/m68k/include/generated/asm/preempt.h:1,
from include/linux/preempt.h:78,
from arch/m68k/include/asm/irqflags.h:6,
from include/linux/irqflags.h:16,
from arch/m68k/include/asm/atomic.h:6,
from include/linux/atomic.h:7,
from include/linux/rcupdate.h:25,
from include/linux/rculist.h:11,
from include/linux/pid.h:5,
from include/linux/sched.h:14,
from include/linux/ratelimit.h:6,
from include/linux/dev_printk.h:16,
from include/linux/device.h:15,
from include/linux/platform_device.h:13,
from drivers/net/ethernet/engleder/tsnep.h:9,
from drivers/net/ethernet/engleder/tsnep_main.c:18:
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_tx_activate':
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:118:39: note: in definition of macro '__swab32'
118 | (__builtin_constant_p((__u32)(x)) ? \
| ^
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:19:19: note: in definition of macro '___constant_swab32'
19 | (((__u32)(x) & (__u32)0x000000ffUL) << 24) | \
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:20:19: note: in definition of macro '___constant_swab32'
20 | (((__u32)(x) & (__u32)0x0000ff00UL) << 8) | \
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:21:19: note: in definition of macro '___constant_swab32'
21 | (((__u32)(x) & (__u32)0x00ff0000UL) >> 8) | \
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:22:19: note: in definition of macro '___constant_swab32'
22 | (((__u32)(x) & (__u32)0xff000000UL) >> 24)))
| ^
include/uapi/linux/byteorder/big_endian.h:33:43: note: in expansion of macro '__swab32'
33 | #define __cpu_to_le32(x) ((__force __le32)__swab32((x)))
| ^~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
>> drivers/net/ethernet/engleder/tsnep_main.c:292:36: error: 'struct tsnep_tx_entry' has no member named 'len'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~
include/uapi/linux/swab.h:120:19: note: in definition of macro '__swab32'
120 | __fswab32(x))
| ^
drivers/net/ethernet/engleder/tsnep_main.c:292:17: note: in expansion of macro '__cpu_to_le32'
292 | __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
| ^~~~~~~~~~~~~
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_tx_unmap':
>> drivers/net/ethernet/engleder/tsnep_main.c:343:32: warning: variable 'entry' set but not used [-Wunused-but-set-variable]
343 | struct tsnep_tx_entry *entry;
| ^~~~~
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_rx_ring_cleanup':
>> drivers/net/ethernet/engleder/tsnep_main.c:543:26: error: 'struct tsnep_rx_entry' has no member named 'dma'
543 | if (entry->dma)
| ^~
In file included from drivers/net/ethernet/engleder/tsnep.h:10,
from drivers/net/ethernet/engleder/tsnep_main.c:18:
drivers/net/ethernet/engleder/tsnep_main.c:544:55: error: 'struct tsnep_rx_entry' has no member named 'dma'
544 | dma_unmap_single(dmadev, entry->dma, entry->len,
| ^~
include/linux/dma-mapping.h:407:64: note: in definition of macro 'dma_unmap_single'
407 | #define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
| ^
>> drivers/net/ethernet/engleder/tsnep_main.c:544:67: error: 'struct tsnep_rx_entry' has no member named 'len'
544 | dma_unmap_single(dmadev, entry->dma, entry->len,
| ^~
include/linux/dma-mapping.h:407:67: note: in definition of macro 'dma_unmap_single'
407 | #define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
| ^
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_rx_activate':
drivers/net/ethernet/engleder/tsnep_main.c:638:34: error: 'struct tsnep_rx_entry' has no member named 'len'
638 | entry->properties = entry->len & TSNEP_DESC_LENGTH_MASK;
| ^~
drivers/net/ethernet/engleder/tsnep_main.c: In function 'tsnep_rx_poll':
>> drivers/net/ethernet/engleder/tsnep_main.c:666:9: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
666 | int length;
| ^~~
>> drivers/net/ethernet/engleder/tsnep_main.c:679:17: error: 'dma' undeclared (first use in this function)
679 | dma = entry->dma;
| ^~~
drivers/net/ethernet/engleder/tsnep_main.c:679:17: note: each undeclared identifier is reported only once for each function it appears in
drivers/net/ethernet/engleder/tsnep_main.c:679:28: error: 'struct tsnep_rx_entry' has no member named 'dma'
679 | dma = entry->dma;
| ^~
>> drivers/net/ethernet/engleder/tsnep_main.c:680:17: error: 'len' undeclared (first use in this function)
680 | len = entry->len;
| ^~~
drivers/net/ethernet/engleder/tsnep_main.c:680:28: error: 'struct tsnep_rx_entry' has no member named 'len'
680 | len = entry->len;
| ^~
vim +292 drivers/net/ethernet/engleder/tsnep_main.c
243
244 static void tsnep_tx_activate(struct tsnep_tx *tx, int index, bool last)
245 {
246 struct tsnep_tx_entry *entry = &tx->entry[index];
247
248 entry->properties = 0;
249 if (entry->skb) {
250 entry->properties =
251 skb_pagelen(entry->skb) & TSNEP_DESC_LENGTH_MASK;
252 entry->properties |= TSNEP_DESC_INTERRUPT_FLAG;
253 if (skb_shinfo(entry->skb)->tx_flags & SKBTX_IN_PROGRESS)
254 entry->properties |= TSNEP_DESC_EXTENDED_WRITEBACK_FLAG;
255
256 /* toggle user flag to prevent false acknowledge
257 *
258 * Only the first fragment is acknowledged. For all other
259 * fragments no acknowledge is done and the last written owner
260 * counter stays in the writeback descriptor. Therefore, it is
261 * possible that the last written owner counter is identical to
262 * the new incremented owner counter and a false acknowledge is
263 * detected before the real acknowledge has been done by
264 * hardware.
265 *
266 * The user flag is used to prevent this situation. The user
267 * flag is copied to the writeback descriptor by the hardware
268 * and is used as additional acknowledge data. By toggeling the
269 * user flag only for the first fragment (which is
270 * acknowledged), it is guaranteed that the last acknowledge
271 * done for this descriptor has used a different user flag and
272 * cannot be detected as false acknowledge.
273 */
274 entry->owner_user_flag = !entry->owner_user_flag;
275 }
276 if (last)
277 entry->properties |= TSNEP_TX_DESC_LAST_FRAGMENT_FLAG;
278 if (index == tx->increment_owner_counter) {
279 tx->owner_counter++;
280 if (tx->owner_counter == 4)
281 tx->owner_counter = 1;
282 tx->increment_owner_counter--;
283 if (tx->increment_owner_counter < 0)
284 tx->increment_owner_counter = TSNEP_RING_SIZE - 1;
285 }
286 entry->properties |=
287 (tx->owner_counter << TSNEP_DESC_OWNER_COUNTER_SHIFT) &
288 TSNEP_DESC_OWNER_COUNTER_MASK;
289 if (entry->owner_user_flag)
290 entry->properties |= TSNEP_TX_DESC_OWNER_USER_FLAG;
291 entry->desc->more_properties =
> 292 __cpu_to_le32(entry->len & TSNEP_DESC_LENGTH_MASK);
293
294 dma_wmb();
295
296 entry->desc->properties = __cpu_to_le32(entry->properties);
297 }
298
299 static int tsnep_tx_desc_available(struct tsnep_tx *tx)
300 {
301 if (tx->read <= tx->write)
302 return TSNEP_RING_SIZE - tx->write + tx->read - 1;
303 else
304 return tx->read - tx->write - 1;
305 }
306
307 static int tsnep_tx_map(struct sk_buff *skb, struct tsnep_tx *tx, int count)
308 {
309 struct device *dmadev = tx->adapter->dmadev;
310 struct tsnep_tx_entry *entry;
311 unsigned int len;
312 dma_addr_t dma;
313 int i;
314
315 for (i = 0; i < count; i++) {
316 entry = &tx->entry[(tx->write + i) % TSNEP_RING_SIZE];
317
318 if (i == 0) {
319 len = skb_headlen(skb);
320 dma = dma_map_single(dmadev, skb->data, len,
321 DMA_TO_DEVICE);
322 } else {
323 len = skb_frag_size(&skb_shinfo(skb)->frags[i - 1]);
324 dma = skb_frag_dma_map(dmadev,
325 &skb_shinfo(skb)->frags[i - 1],
326 0, len, DMA_TO_DEVICE);
327 }
328 if (dma_mapping_error(dmadev, dma))
329 return -ENOMEM;
330
331 dma_unmap_len_set(entry, len, len);
332 dma_unmap_addr_set(entry, dma, dma);
333
334 entry->desc->tx = __cpu_to_le64(dma);
335 }
336
337 return 0;
338 }
339
340 static void tsnep_tx_unmap(struct tsnep_tx *tx, int count)
341 {
342 struct device *dmadev = tx->adapter->dmadev;
> 343 struct tsnep_tx_entry *entry;
344 int i;
345
346 for (i = 0; i < count; i++) {
347 entry = &tx->entry[(tx->read + i) % TSNEP_RING_SIZE];
348
349 if (dma_unmap_len(entry, len)) {
350 if (i == 0)
351 dma_unmap_single(dmadev,
352 dma_unmap_addr(entry, dma),
353 dma_unmap_len(entry, len),
354 DMA_TO_DEVICE);
355 else
356 dma_unmap_page(dmadev,
357 dma_unmap_addr(entry, dma),
358 dma_unmap_len(entry, len),
359 DMA_TO_DEVICE);
360 dma_unmap_len_set(entry, len, 0);
361 }
362 }
363 }
364
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org
[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 40489 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2021-11-18 17:32 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-15 20:50 [PATCH net-next v5 0/3] TSN endpoint Ethernet MAC driver Gerhard Engleder
2021-11-15 20:50 ` [PATCH net-next v5 1/3] dt-bindings: Add vendor prefix for Engleder Gerhard Engleder
2021-11-15 22:24 ` Andrew Lunn
2021-11-15 20:50 ` [PATCH net-next v5 2/3] dt-bindings: net: Add tsnep Ethernet controller Gerhard Engleder
2021-11-15 22:25 ` Andrew Lunn
2021-11-15 20:50 ` [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC driver Gerhard Engleder
2021-11-15 22:21 ` Heiner Kallweit
2021-11-16 21:47 ` Gerhard Engleder
2021-11-15 23:04 ` Andrew Lunn
2021-11-16 22:16 ` Gerhard Engleder
2021-11-16 23:41 ` Andrew Lunn
2021-11-18 17:31 ` kernel test robot
2021-11-18 17:31 ` kernel test robot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.