lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <577ffb42-d74e-9da7-d921-b7f62c26b596@gmail.com>
Date:   Mon, 15 Nov 2021 23:21:21 +0100
From:   Heiner Kallweit <hkallweit1@...il.com>
To:     Gerhard Engleder <gerhard@...leder-embedded.com>,
        davem@...emloft.net, kuba@...nel.org, andrew@...n.ch
Cc:     netdev@...r.kernel.org
Subject: Re: [PATCH net-next v5 3/3] tsnep: Add TSN endpoint Ethernet MAC
 driver

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@...leder-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@...leder-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@...leder-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@...leder-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@...leder-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(&ethtool_eee, 0, sizeof(ethtool_eee));
> +	phy_ethtool_set_eee(adapter->phydev, &ethtool_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.

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ