Re: [net-next 1/6] net: marvell: prestera: Add driver for Prestera family ASIC devices

From: Ido Schimmel
Date: Sat May 30 2020 - 11:48:25 EST


On Thu, May 28, 2020 at 06:12:40PM +0300, Vadym Kochan wrote:
> Marvell Prestera 98DX326x integrates up to 24 ports of 1GbE with 8
> ports of 10GbE uplinks or 2 ports of 40Gbps stacking for a largely
> wireless SMB deployment.
>
> The current implementation supports only boards designed for the Marvell
> Switchdev solution and requires special firmware.
>
> The core Prestera switching logic is implemented in prestera_main.c,
> there is an intermediate hw layer between core logic and firmware. It is
> implemented in prestera_hw.c, the purpose of it is to encapsulate hw
> related logic, in future there is a plan to support more devices with
> different HW related configurations.
>
> This patch contains only basic switch initialization and RX/TX support
> over SDMA mechanism.
>
> Currently supported devices have DMA access range <= 32bit and require
> ZONE_DMA to be enabled, for such cases SDMA driver checks if the skb
> allocated in proper range supported by the Prestera device.
>
> Also meanwhile there is no TX interrupt support in current firmware
> version so recycling work is scheduled on each xmit.
>
> Port's mac address is generated from the switch base mac which may be
> provided via device-tree (static one or as nvme cell), or randomly
> generated.
>
> Signed-off-by: Andrii Savka <andrii.savka@xxxxxxxxxxx>
> Signed-off-by: Oleksandr Mazur <oleksandr.mazur@xxxxxxxxxxx>
> Signed-off-by: Serhiy Boiko <serhiy.boiko@xxxxxxxxxxx>
> Signed-off-by: Serhiy Pshyk <serhiy.pshyk@xxxxxxxxxxx>
> Signed-off-by: Taras Chornyi <taras.chornyi@xxxxxxxxxxx>
> Signed-off-by: Volodymyr Mytnyk <volodymyr.mytnyk@xxxxxxxxxxx>
> Signed-off-by: Vadym Kochan <vadym.kochan@xxxxxxxxxxx>
> ---
> drivers/net/ethernet/marvell/Kconfig | 1 +
> drivers/net/ethernet/marvell/Makefile | 1 +
> drivers/net/ethernet/marvell/prestera/Kconfig | 13 +
> .../net/ethernet/marvell/prestera/Makefile | 4 +
> .../net/ethernet/marvell/prestera/prestera.h | 172 ++++
> .../ethernet/marvell/prestera/prestera_dsa.c | 134 +++
> .../ethernet/marvell/prestera/prestera_dsa.h | 37 +
> .../ethernet/marvell/prestera/prestera_hw.c | 610 +++++++++++++
> .../ethernet/marvell/prestera/prestera_hw.h | 71 ++
> .../ethernet/marvell/prestera/prestera_main.c | 506 +++++++++++
> .../ethernet/marvell/prestera/prestera_rxtx.c | 860 ++++++++++++++++++
> .../ethernet/marvell/prestera/prestera_rxtx.h | 21 +
> 12 files changed, 2430 insertions(+)
> create mode 100644 drivers/net/ethernet/marvell/prestera/Kconfig
> create mode 100644 drivers/net/ethernet/marvell/prestera/Makefile
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera.h
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera_dsa.c
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera_dsa.h
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera_hw.c
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera_hw.h
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera_main.c
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera_rxtx.c
> create mode 100644 drivers/net/ethernet/marvell/prestera/prestera_rxtx.h
>
> diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig
> index 3d5caea096fb..74313d9e1fc0 100644
> --- a/drivers/net/ethernet/marvell/Kconfig
> +++ b/drivers/net/ethernet/marvell/Kconfig
> @@ -171,5 +171,6 @@ config SKY2_DEBUG
>
>
> source "drivers/net/ethernet/marvell/octeontx2/Kconfig"
> +source "drivers/net/ethernet/marvell/prestera/Kconfig"
>
> endif # NET_VENDOR_MARVELL
> diff --git a/drivers/net/ethernet/marvell/Makefile b/drivers/net/ethernet/marvell/Makefile
> index 89dea7284d5b..9f88fe822555 100644
> --- a/drivers/net/ethernet/marvell/Makefile
> +++ b/drivers/net/ethernet/marvell/Makefile
> @@ -12,3 +12,4 @@ obj-$(CONFIG_PXA168_ETH) += pxa168_eth.o
> obj-$(CONFIG_SKGE) += skge.o
> obj-$(CONFIG_SKY2) += sky2.o
> obj-y += octeontx2/
> +obj-y += prestera/
> diff --git a/drivers/net/ethernet/marvell/prestera/Kconfig b/drivers/net/ethernet/marvell/prestera/Kconfig
> new file mode 100644
> index 000000000000..76b68613ea7a
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Marvell Prestera drivers configuration
> +#
> +
> +config PRESTERA
> + tristate "Marvell Prestera Switch ASICs support"
> + depends on NET_SWITCHDEV && VLAN_8021Q
> + help
> + This driver supports Marvell Prestera Switch ASICs family.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called prestera.
> diff --git a/drivers/net/ethernet/marvell/prestera/Makefile b/drivers/net/ethernet/marvell/prestera/Makefile
> new file mode 100644
> index 000000000000..610d75032b78
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0
> +obj-$(CONFIG_PRESTERA) += prestera.o
> +prestera-objs := prestera_main.o prestera_hw.o prestera_dsa.o \
> + prestera_rxtx.o
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera.h b/drivers/net/ethernet/marvell/prestera/prestera.h
> new file mode 100644
> index 000000000000..5079d872e18a
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera.h
> @@ -0,0 +1,172 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> + *
> + * Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved.
> + *
> + */
> +
> +#ifndef _PRESTERA_H_
> +#define _PRESTERA_H_
> +
> +#include <linux/skbuff.h>
> +#include <linux/notifier.h>
> +#include <uapi/linux/if_ether.h>
> +#include <linux/workqueue.h>
> +
> +struct prestera_fw_rev {
> + u16 maj;
> + u16 min;
> + u16 sub;
> +};
> +
> +struct prestera_port_stats {
> + u64 good_octets_received;
> + u64 bad_octets_received;
> + u64 mac_trans_error;
> + u64 broadcast_frames_received;
> + u64 multicast_frames_received;
> + u64 frames_64_octets;
> + u64 frames_65_to_127_octets;
> + u64 frames_128_to_255_octets;
> + u64 frames_256_to_511_octets;
> + u64 frames_512_to_1023_octets;
> + u64 frames_1024_to_max_octets;
> + u64 excessive_collision;
> + u64 multicast_frames_sent;
> + u64 broadcast_frames_sent;
> + u64 fc_sent;
> + u64 fc_received;
> + u64 buffer_overrun;
> + u64 undersize;
> + u64 fragments;
> + u64 oversize;
> + u64 jabber;
> + u64 rx_error_frame_received;
> + u64 bad_crc;
> + u64 collisions;
> + u64 late_collision;
> + u64 unicast_frames_received;
> + u64 unicast_frames_sent;
> + u64 sent_multiple;
> + u64 sent_deferred;
> + u64 frames_1024_to_1518_octets;
> + u64 frames_1519_to_max_octets;
> + u64 good_octets_sent;
> +};
> +
> +struct prestera_port_caps {
> + u64 supp_link_modes;
> + u8 supp_fec;
> + u8 type;
> + u8 transceiver;
> +};
> +
> +struct prestera_port {
> + struct net_device *dev;
> + struct prestera_switch *sw;
> + u32 id;
> + u32 hw_id;
> + u32 dev_id;
> + u16 fp_id;
> + bool autoneg;
> + u64 adver_link_modes;
> + u8 adver_fec;
> + struct prestera_port_caps caps;
> + struct list_head list;
> + struct {
> + struct prestera_port_stats stats;
> + struct delayed_work caching_dw;
> + } cached_hw_stats;
> +};
> +
> +struct prestera_device {
> + struct device *dev;
> + u8 __iomem *ctl_regs;
> + u8 __iomem *pp_regs;
> + struct prestera_fw_rev fw_rev;
> + void *priv;
> +
> + /* called by device driver to handle received packets */
> + void (*recv_pkt)(struct prestera_device *dev);
> +
> + /* called by device driver to pass event up to the higher layer */
> + int (*recv_msg)(struct prestera_device *dev, u8 *msg, size_t size);
> +
> + /* called by higher layer to send request to the firmware */
> + int (*send_req)(struct prestera_device *dev, u8 *in_msg,
> + size_t in_size, u8 *out_msg, size_t out_size,
> + unsigned int wait);
> +};
> +
> +enum prestera_event_type {
> + PRESTERA_EVENT_TYPE_UNSPEC,
> +
> + PRESTERA_EVENT_TYPE_PORT,
> + PRESTERA_EVENT_TYPE_RXTX,
> +
> + PRESTERA_EVENT_TYPE_MAX,
> +};
> +
> +enum prestera_rxtx_event_id {
> + PRESTERA_RXTX_EVENT_UNSPEC,
> + PRESTERA_RXTX_EVENT_RCV_PKT,
> +};
> +
> +enum prestera_port_event_id {
> + PRESTERA_PORT_EVENT_UNSPEC,
> + PRESTERA_PORT_EVENT_STATE_CHANGED,
> +};
> +
> +struct prestera_port_event {
> + u32 port_id;
> + union {
> + u32 oper_state;
> + } data;
> +};
> +
> +struct prestera_event {
> + u16 id;
> + union {
> + struct prestera_port_event port_evt;
> + };
> +};
> +
> +struct prestera_rxtx;
> +
> +struct prestera_switch {
> + struct prestera_device *dev;
> + struct prestera_rxtx *rxtx;
> + struct list_head event_handlers;
> + char base_mac[ETH_ALEN];
> + struct list_head port_list;
> + u32 port_count;
> + u32 mtu_min;
> + u32 mtu_max;
> + u8 id;
> +};
> +
> +struct prestera_rxtx_params {
> + bool use_sdma;
> + u32 map_addr;
> +};
> +
> +#define prestera_dev(sw) ((sw)->dev->dev)
> +
> +static inline void prestera_write(const struct prestera_switch *sw,
> + unsigned int reg, u32 val)
> +{
> + writel(val, sw->dev->pp_regs + reg);
> +}
> +
> +static inline u32 prestera_read(const struct prestera_switch *sw,
> + unsigned int reg)
> +{
> + return readl(sw->dev->pp_regs + reg);
> +}
> +
> +int prestera_device_register(struct prestera_device *dev);
> +void prestera_device_unregister(struct prestera_device *dev);
> +
> +struct prestera_port *prestera_port_find_by_hwid(struct prestera_switch *sw,
> + u32 dev_id, u32 hw_id);
> +
> +#endif /* _PRESTERA_H_ */
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_dsa.c b/drivers/net/ethernet/marvell/prestera/prestera_dsa.c
> new file mode 100644
> index 000000000000..1d95604507a1
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_dsa.c
> @@ -0,0 +1,134 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/* Copyright (c) 2020 Marvell International Ltd. All rights reserved */
> +
> +#include "prestera_dsa.h"
> +
> +#include <linux/string.h>
> +#include <linux/bitops.h>
> +#include <linux/bitfield.h>
> +#include <linux/errno.h>
> +
> +#define PRESTERA_W0_IS_TAGGED BIT(29)
> +
> +/* TrgDev[4:0] = {Word0[28:24]} */
> +#define PRESTERA_W0_HW_DEV_NUM GENMASK(28, 24)
> +
> +/* SrcPort/TrgPort extended to 8b
> + * SrcPort/TrgPort[7:0] = {Word2[20], Word1[11:10], Word0[23:19]}
> + */
> +#define PRESTERA_W0_IFACE_PORT_NUM GENMASK(23, 19)
> +
> +/* bits 30:31 - TagCommand 1 = FROM_CPU */
> +#define PRESTERA_W0_DSA_CMD GENMASK(31, 30)
> +
> +/* bits 13:15 -- UP */
> +#define PRESTERA_W0_VPT GENMASK(15, 13)
> +
> +#define PRESTERA_W0_EXT_BIT BIT(12)
> +
> +/* bits 0:11 -- VID */
> +#define PRESTERA_W0_VID GENMASK(11, 0)
> +
> +/* SrcPort/TrgPort extended to 8b
> + * SrcPort/TrgPort[7:0] = {Word2[20], Word1[11:10], Word0[23:19]}
> + */
> +#define PRESTERA_W1_IFACE_PORT_NUM GENMASK(11, 10)
> +
> +#define PRESTERA_W1_EXT_BIT BIT(31)
> +#define PRESTERA_W1_CFI_BIT BIT(30)
> +
> +/* SrcPort/TrgPort extended to 8b
> + * SrcPort/TrgPort[7:0] = {Word2[20], Word1[11:10], Word0[23:19]}
> + */
> +#define PRESTERA_W2_IFACE_PORT_NUM BIT(20)
> +
> +#define PRESTERA_W2_EXT_BIT BIT(31)
> +
> +/* trgHwDev and trgPort
> + * TrgDev[11:5] = {Word3[6:0]}
> + */
> +#define PRESTERA_W3_HW_DEV_NUM GENMASK(6, 0)
> +
> +/* VID 16b [15:0] = {Word3[30:27], Word0[11:0]} */
> +#define PRESTERA_W3_VID GENMASK(30, 27)
> +
> +/* TRGePort[16:0] = {Word3[23:7]} */
> +#define PRESTERA_W3_DST_EPORT GENMASK(23, 7)
> +
> +#define PRESTERA_DEV_NUM_MASK GENMASK(11, 5)
> +#define PRESTERA_VID_MASK GENMASK(15, 12)
> +
> +int prestera_dsa_parse(struct prestera_dsa *dsa, const u8 *dsa_buf)
> +{
> + u32 *dsa_words = (u32 *)dsa_buf;
> + enum prestera_dsa_cmd cmd;
> + u32 words[4] = { 0 };
> + u32 field;
> +
> + words[0] = ntohl((__force __be32)dsa_words[0]);
> + words[1] = ntohl((__force __be32)dsa_words[1]);
> + words[2] = ntohl((__force __be32)dsa_words[2]);
> + words[3] = ntohl((__force __be32)dsa_words[3]);
> +
> + /* set the common parameters */
> + cmd = (enum prestera_dsa_cmd)FIELD_GET(PRESTERA_W0_DSA_CMD, words[0]);
> +
> + /* only to CPU is supported */
> + if (unlikely(cmd != PRESTERA_DSA_CMD_TO_CPU))
> + return -EINVAL;
> +
> + if (FIELD_GET(PRESTERA_W0_EXT_BIT, words[0]) == 0)
> + return -EINVAL;
> + if (FIELD_GET(PRESTERA_W1_EXT_BIT, words[1]) == 0)
> + return -EINVAL;
> + if (FIELD_GET(PRESTERA_W2_EXT_BIT, words[2]) == 0)
> + return -EINVAL;
> +
> + field = FIELD_GET(PRESTERA_W3_VID, words[3]);
> +
> + dsa->vlan.is_tagged = (bool)FIELD_GET(PRESTERA_W0_IS_TAGGED, words[0]);
> + dsa->vlan.cfi_bit = (u8)FIELD_GET(PRESTERA_W1_CFI_BIT, words[1]);
> + dsa->vlan.vpt = (u8)FIELD_GET(PRESTERA_W0_VPT, words[0]);
> + dsa->vlan.vid = (u16)FIELD_GET(PRESTERA_W0_VID, words[0]);
> + dsa->vlan.vid &= ~PRESTERA_VID_MASK;
> + dsa->vlan.vid |= FIELD_PREP(PRESTERA_VID_MASK, field);
> +
> + field = FIELD_GET(PRESTERA_W3_HW_DEV_NUM, words[3]);
> +
> + dsa->hw_dev_num = FIELD_GET(PRESTERA_W0_HW_DEV_NUM, words[0]);
> + dsa->hw_dev_num &= PRESTERA_W3_HW_DEV_NUM;
> + dsa->hw_dev_num |= FIELD_PREP(PRESTERA_DEV_NUM_MASK, field);
> +
> + dsa->port_num = (FIELD_GET(PRESTERA_W0_IFACE_PORT_NUM, words[0]) << 0) |
> + (FIELD_GET(PRESTERA_W1_IFACE_PORT_NUM, words[1]) << 5) |
> + (FIELD_GET(PRESTERA_W2_IFACE_PORT_NUM, words[2]) << 7);
> + return 0;
> +}
> +
> +int prestera_dsa_build(const struct prestera_dsa *dsa, u8 *dsa_buf)
> +{
> + __be32 *dsa_words = (__be32 *)dsa_buf;
> + u32 words[4] = { 0 };
> +
> + if (dsa->hw_dev_num >= BIT(12))
> + return -EINVAL;
> + if (dsa->port_num >= BIT(17))
> + return -EINVAL;
> +
> + words[0] |= FIELD_PREP(PRESTERA_W0_DSA_CMD, PRESTERA_DSA_CMD_FROM_CPU);
> +
> + words[0] |= FIELD_PREP(PRESTERA_W0_HW_DEV_NUM, dsa->hw_dev_num);
> + words[3] |= FIELD_PREP(PRESTERA_W3_HW_DEV_NUM, (dsa->hw_dev_num >> 5));
> + words[3] |= FIELD_PREP(PRESTERA_W3_DST_EPORT, dsa->port_num);
> +
> + words[0] |= FIELD_PREP(PRESTERA_W0_EXT_BIT, 1);
> + words[1] |= FIELD_PREP(PRESTERA_W1_EXT_BIT, 1);
> + words[2] |= FIELD_PREP(PRESTERA_W2_EXT_BIT, 1);
> +
> + dsa_words[0] = htonl(words[0]);
> + dsa_words[1] = htonl(words[1]);
> + dsa_words[2] = htonl(words[2]);
> + dsa_words[3] = htonl(words[3]);
> +
> + return 0;
> +}
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_dsa.h b/drivers/net/ethernet/marvell/prestera/prestera_dsa.h
> new file mode 100644
> index 000000000000..d653e426dd71
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_dsa.h
> @@ -0,0 +1,37 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> + *
> + * Copyright (c) 2020 Marvell International Ltd. All rights reserved.
> + *
> + */
> +#ifndef __PRESTERA_DSA_H_
> +#define __PRESTERA_DSA_H_
> +
> +#include <linux/types.h>
> +
> +#define PRESTERA_DSA_HLEN 16
> +
> +enum prestera_dsa_cmd {
> + /* DSA command is "To CPU" */
> + PRESTERA_DSA_CMD_TO_CPU = 0,
> +
> + /* DSA command is "FROM CPU" */

Nit: "From" ?

> + PRESTERA_DSA_CMD_FROM_CPU,
> +};
> +
> +struct prestera_dsa_vlan {
> + u16 vid;
> + u8 vpt;
> + u8 cfi_bit;
> + bool is_tagged;
> +};
> +
> +struct prestera_dsa {
> + struct prestera_dsa_vlan vlan;
> + u32 hw_dev_num;
> + u32 port_num;
> +};
> +
> +int prestera_dsa_parse(struct prestera_dsa *dsa, const u8 *dsa_buf);
> +int prestera_dsa_build(const struct prestera_dsa *dsa, u8 *dsa_buf);
> +
> +#endif /* _PRESTERA_DSA_H_ */
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_hw.c b/drivers/net/ethernet/marvell/prestera/prestera_hw.c
> new file mode 100644
> index 000000000000..3aa3974f957a
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_hw.c
> @@ -0,0 +1,610 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */
> +
> +#include <linux/etherdevice.h>
> +#include <linux/ethtool.h>
> +#include <linux/netdevice.h>
> +#include <linux/list.h>
> +
> +#include "prestera.h"
> +#include "prestera_hw.h"
> +
> +#define PRESTERA_SWITCH_INIT_TIMEOUT 30000000 /* 30sec */

Out of curiosity, how long does it actually take you to initialize the
hardware?

Also, I find it useful to note the units in the name, so:

#define PRESTERA_SWITCH_INIT_TIMEOUT_US (30 * 1000 * 1000)

BTW, it says 30 seconds in comment, but the call chain where it is used
is:

prestera_cmd_ret_wait(, PRESTERA_SWITCH_INIT_TIMEOUT)
__prestera_cmd_ret(..., wait)
prestera_fw_send_req(..., waitms)
prestera_fw_cmd_send(..., waitms)
prestera_fw_wait_reg32(..., waitms)
readl_poll_timeout(..., waitms * 1000)

So I think you should actually define it as:

#define PRESTERA_SWITCH_INIT_TIMEOUT_MS (30 * 1000)

And rename all these 'wait' arguments to 'waitms' so it's clearer which
unit they expect.

> +#define PRESTERA_MIN_MTU 64
> +
> +enum prestera_cmd_type_t {
> + PRESTERA_CMD_TYPE_SWITCH_INIT = 0x1,
> + PRESTERA_CMD_TYPE_SWITCH_ATTR_SET = 0x2,
> +
> + PRESTERA_CMD_TYPE_PORT_ATTR_SET = 0x100,
> + PRESTERA_CMD_TYPE_PORT_ATTR_GET = 0x101,
> + PRESTERA_CMD_TYPE_PORT_INFO_GET = 0x110,
> +
> + PRESTERA_CMD_TYPE_RXTX_INIT = 0x800,
> + PRESTERA_CMD_TYPE_RXTX_PORT_INIT = 0x801,
> +
> + PRESTERA_CMD_TYPE_ACK = 0x10000,
> + PRESTERA_CMD_TYPE_MAX
> +};
> +
> +enum {
> + PRESTERA_CMD_PORT_ATTR_ADMIN_STATE = 1,
> + PRESTERA_CMD_PORT_ATTR_MTU = 3,
> + PRESTERA_CMD_PORT_ATTR_MAC = 4,
> + PRESTERA_CMD_PORT_ATTR_CAPABILITY = 9,
> + PRESTERA_CMD_PORT_ATTR_AUTONEG = 15,
> + PRESTERA_CMD_PORT_ATTR_STATS = 17,
> +};
> +
> +enum {
> + PRESTERA_CMD_SWITCH_ATTR_MAC = 1,
> +};
> +
> +enum {
> + PRESTERA_CMD_ACK_OK,
> + PRESTERA_CMD_ACK_FAILED,
> +
> + PRESTERA_CMD_ACK_MAX
> +};
> +
> +enum {
> + PRESTERA_PORT_GOOD_OCTETS_RCV_CNT,
> + PRESTERA_PORT_BAD_OCTETS_RCV_CNT,
> + PRESTERA_PORT_MAC_TRANSMIT_ERR_CNT,
> + PRESTERA_PORT_BRDC_PKTS_RCV_CNT,
> + PRESTERA_PORT_MC_PKTS_RCV_CNT,
> + PRESTERA_PORT_PKTS_64L_CNT,
> + PRESTERA_PORT_PKTS_65TO127L_CNT,
> + PRESTERA_PORT_PKTS_128TO255L_CNT,
> + PRESTERA_PORT_PKTS_256TO511L_CNT,
> + PRESTERA_PORT_PKTS_512TO1023L_CNT,
> + PRESTERA_PORT_PKTS_1024TOMAXL_CNT,
> + PRESTERA_PORT_EXCESSIVE_COLLISIONS_CNT,
> + PRESTERA_PORT_MC_PKTS_SENT_CNT,
> + PRESTERA_PORT_BRDC_PKTS_SENT_CNT,
> + PRESTERA_PORT_FC_SENT_CNT,
> + PRESTERA_PORT_GOOD_FC_RCV_CNT,
> + PRESTERA_PORT_DROP_EVENTS_CNT,
> + PRESTERA_PORT_UNDERSIZE_PKTS_CNT,
> + PRESTERA_PORT_FRAGMENTS_PKTS_CNT,
> + PRESTERA_PORT_OVERSIZE_PKTS_CNT,
> + PRESTERA_PORT_JABBER_PKTS_CNT,
> + PRESTERA_PORT_MAC_RCV_ERROR_CNT,
> + PRESTERA_PORT_BAD_CRC_CNT,
> + PRESTERA_PORT_COLLISIONS_CNT,
> + PRESTERA_PORT_LATE_COLLISIONS_CNT,
> + PRESTERA_PORT_GOOD_UC_PKTS_RCV_CNT,
> + PRESTERA_PORT_GOOD_UC_PKTS_SENT_CNT,
> + PRESTERA_PORT_MULTIPLE_PKTS_SENT_CNT,
> + PRESTERA_PORT_DEFERRED_PKTS_SENT_CNT,
> + PRESTERA_PORT_GOOD_OCTETS_SENT_CNT,
> +
> + PRESTERA_PORT_CNT_MAX,
> +};
> +
> +struct prestera_fw_event_handler {
> + struct list_head list;
> + enum prestera_event_type type;
> + prestera_event_cb_t func;
> + void *arg;
> +};
> +
> +struct prestera_msg_cmd {
> + u32 type;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_ret {
> + struct prestera_msg_cmd cmd;
> + u32 status;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_common_req {
> + struct prestera_msg_cmd cmd;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_common_resp {
> + struct prestera_msg_ret ret;
> +} __packed __aligned(4);
> +
> +union prestera_msg_switch_param {
> + u8 mac[ETH_ALEN];
> +};
> +
> +struct prestera_msg_switch_attr_req {
> + struct prestera_msg_cmd cmd;
> + u32 attr;
> + union prestera_msg_switch_param param;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_switch_init_resp {
> + struct prestera_msg_ret ret;
> + u32 port_count;
> + u32 mtu_max;
> + u8 switch_id;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_port_autoneg_param {
> + u64 link_mode;
> + u8 enable;
> + u8 fec;
> +};
> +
> +struct prestera_msg_port_cap_param {
> + u64 link_mode;
> + u8 type;
> + u8 fec;
> + u8 transceiver;
> +};
> +
> +union prestera_msg_port_param {
> + u8 admin_state;
> + u8 oper_state;
> + u32 mtu;
> + u8 mac[ETH_ALEN];
> + struct prestera_msg_port_autoneg_param autoneg;
> + struct prestera_msg_port_cap_param cap;
> +};
> +
> +struct prestera_msg_port_attr_req {
> + struct prestera_msg_cmd cmd;
> + u32 attr;
> + u32 port;
> + u32 dev;
> + union prestera_msg_port_param param;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_port_attr_resp {
> + struct prestera_msg_ret ret;
> + union prestera_msg_port_param param;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_port_stats_resp {
> + struct prestera_msg_ret ret;
> + u64 stats[PRESTERA_PORT_CNT_MAX];
> +} __packed __aligned(4);
> +
> +struct prestera_msg_port_info_req {
> + struct prestera_msg_cmd cmd;
> + u32 port;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_port_info_resp {
> + struct prestera_msg_ret ret;
> + u32 hw_id;
> + u32 dev_id;
> + u16 fp_id;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_rxtx_req {
> + struct prestera_msg_cmd cmd;
> + u8 use_sdma;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_rxtx_resp {
> + struct prestera_msg_ret ret;
> + u32 map_addr;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_rxtx_port_req {
> + struct prestera_msg_cmd cmd;
> + u32 port;
> + u32 dev;
> +} __packed __aligned(4);
> +
> +struct prestera_msg_event {
> + u16 type;
> + u16 id;
> +} __packed __aligned(4);
> +
> +union prestera_msg_event_port_param {
> + u32 oper_state;
> +};
> +
> +struct prestera_msg_event_port {
> + struct prestera_msg_event id;
> + u32 port_id;
> + union prestera_msg_event_port_param param;
> +} __packed __aligned(4);
> +
> +static int __prestera_cmd_ret(struct prestera_switch *sw,
> + enum prestera_cmd_type_t type,
> + struct prestera_msg_cmd *cmd, size_t clen,
> + struct prestera_msg_ret *ret, size_t rlen,
> + int wait)
> +{
> + struct prestera_device *dev = sw->dev;
> + int err;
> +
> + cmd->type = type;
> +
> + err = dev->send_req(dev, (u8 *)cmd, clen, (u8 *)ret, rlen, wait);
> + if (err)
> + return err;
> +
> + if (ret->cmd.type != PRESTERA_CMD_TYPE_ACK)
> + return -EBADE;
> + if (ret->status != PRESTERA_CMD_ACK_OK)

You don't have more states here other than OK / FAIL ? It might help you
in debugging if you include them. You might find trace_devlink_hwerr()
useful.

> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int prestera_cmd_ret(struct prestera_switch *sw,
> + enum prestera_cmd_type_t type,
> + struct prestera_msg_cmd *cmd, size_t clen,
> + struct prestera_msg_ret *ret, size_t rlen)
> +{
> + return __prestera_cmd_ret(sw, type, cmd, clen, ret, rlen, 0);
> +}
> +
> +static int prestera_cmd_ret_wait(struct prestera_switch *sw,
> + enum prestera_cmd_type_t type,
> + struct prestera_msg_cmd *cmd, size_t clen,
> + struct prestera_msg_ret *ret, size_t rlen,
> + int wait)
> +{
> + return __prestera_cmd_ret(sw, type, cmd, clen, ret, rlen, wait);
> +}
> +
> +static int prestera_cmd(struct prestera_switch *sw,
> + enum prestera_cmd_type_t type,
> + struct prestera_msg_cmd *cmd, size_t clen)
> +{
> + struct prestera_msg_common_resp resp;
> +
> + return prestera_cmd_ret(sw, type, cmd, clen, &resp.ret, sizeof(resp));
> +}
> +
> +static int prestera_fw_parse_port_evt(u8 *msg, struct prestera_event *evt)
> +{
> + struct prestera_msg_event_port *hw_evt;
> +
> + hw_evt = (struct prestera_msg_event_port *)msg;
> +
> + evt->port_evt.port_id = hw_evt->port_id;
> +
> + if (evt->id == PRESTERA_PORT_EVENT_STATE_CHANGED)
> + evt->port_evt.data.oper_state = hw_evt->param.oper_state;
> + else
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static struct prestera_fw_evt_parser {
> + int (*func)(u8 *msg, struct prestera_event *evt);
> +} fw_event_parsers[PRESTERA_EVENT_TYPE_MAX] = {
> + [PRESTERA_EVENT_TYPE_PORT] = {.func = prestera_fw_parse_port_evt},
> +};
> +
> +static struct prestera_fw_event_handler *
> +__find_event_handler(const struct prestera_switch *sw,
> + enum prestera_event_type type)
> +{
> + struct prestera_fw_event_handler *eh;
> +
> + list_for_each_entry_rcu(eh, &sw->event_handlers, list) {

It does not look that this is always called under RCU which will result
in various splats. For example in the following call path:

prestera_device_register()
prestera_switch_init()
prestera_event_handlers_register()
prestera_hw_event_handler_register()
__find_event_handler()

You want to make sure that you are testing with various debug options.
For example:

# Debug options
## General debug options
config_enable CONFIG_PREEMPT
config_enable CONFIG_DEBUG_PREEMPT
config_enable CONFIG_DEBUG_INFO
config_enable CONFIG_UNWINDER_ORC
config_enable CONFIG_DYNAMIC_DEBUG
config_enable CONFIG_DEBUG_NOTIFIERS
## Lock debugging
config_enable CONFIG_LOCKDEP
config_enable CONFIG_PROVE_LOCKING
config_enable CONFIG_DEBUG_ATOMIC_SLEEP
config_enable CONFIG_PROVE_RCU
config_enable CONFIG_DEBUG_MUTEXES
config_enable CONFIG_DEBUG_SPINLOCK
config_enable CONFIG_LOCK_STAT
## Memory debugging
config_enable CONFIG_DEBUG_VM
config_enable CONFIG_FORTIFY_SOURCE
config_enable CONFIG_KASAN
config_enable CONFIG_KASAN_EXTRA
config_enable CONFIG_KASAN_INLINE
## Reference counting debugging
config_enable CONFIG_REFCOUNT_FULL
## Lockups debugging
config_enable CONFIG_LOCKUP_DETECTOR
config_enable CONFIG_SOFTLOCKUP_DETECTOR
config_enable CONFIG_HARDLOCKUP_DETECTOR
config_enable CONFIG_DETECT_HUNG_TASK
config_enable CONFIG_WQ_WATCHDOG
config_enable CONFIG_DETECT_HUNG_TASK
config_set_val CONFIG_DEFAULT_HUNG_TASK_TIMEOUT 120
## Undefined behavior debugging
config_enable CONFIG_UBSAN
config_enable CONFIG_UBSAN_SANITIZE_ALL
config_disable CONFIG_UBSAN_ALIGNMENT
config_disable CONFIG_UBSAN_NULL
## Memory debugging
config_enable CONFIG_SLUB_DEBUG
config_enable CONFIG_SLUB_DEBUG_ON
config_enable CONFIG_DEBUG_PAGEALLOC
config_enable CONFIG_DEBUG_KMEMLEAK
config_disable CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF
config_set_val CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE 8192
config_enable CONFIG_DEBUG_STACKOVERFLOW
config_enable CONFIG_DEBUG_LIST
config_enable CONFIG_DEBUG_PER_CPU_MAPS
config_set_val CONFIG_DEBUG_OBJECTS_ENABLE_DEFAULT 1
config_enable CONFIG_DEBUG_OBJECTS
config_enable CONFIG_DEBUG_OBJECTS_FREE
config_enable CONFIG_DEBUG_OBJECTS_TIMERS
config_enable CONFIG_DEBUG_OBJECTS_WORK
config_enable CONFIG_DEBUG_OBJECTS_PERCPU_COUNTER
config_enable CONFIG_DMA_API_DEBUG
## Lock debugging
config_enable CONFIG_DEBUG_LOCK_ALLOC
config_enable CONFIG_PROVE_LOCKING
config_enable CONFIG_LOCK_STAT
config_enable CONFIG_DEBUG_OBJECTS_RCU_HEAD
config_enable CONFIG_SPARSE_RCU_POINTER

> + if (eh->type == type)
> + return eh;
> + }
> +
> + return NULL;
> +}
> +
> +static int prestera_find_event_handler(const struct prestera_switch *sw,
> + enum prestera_event_type type,
> + struct prestera_fw_event_handler *eh)
> +{
> + struct prestera_fw_event_handler *tmp;
> + int err = 0;
> +
> + rcu_read_lock();
> + tmp = __find_event_handler(sw, type);
> + if (tmp)
> + *eh = *tmp;
> + else
> + err = -EEXIST;
> + rcu_read_unlock();
> +
> + return err;
> +}
> +
> +static int prestera_evt_recv(struct prestera_device *dev, u8 *buf, size_t size)
> +{
> + struct prestera_msg_event *msg = (struct prestera_msg_event *)buf;
> + struct prestera_switch *sw = dev->priv;
> + struct prestera_fw_event_handler eh;
> + struct prestera_event evt;
> + int err;
> +
> + if (msg->type >= PRESTERA_EVENT_TYPE_MAX)
> + return -EINVAL;
> +
> + err = prestera_find_event_handler(sw, msg->type, &eh);
> +
> + if (err || !fw_event_parsers[msg->type].func)
> + return 0;
> +
> + evt.id = msg->id;
> +
> + err = fw_event_parsers[msg->type].func(buf, &evt);
> + if (!err)
> + eh.func(sw, &evt, eh.arg);
> +
> + return err;
> +}
> +
> +static void prestera_pkt_recv(struct prestera_device *dev)
> +{
> + struct prestera_switch *sw = dev->priv;
> + struct prestera_fw_event_handler eh;
> + struct prestera_event ev;
> + int err;
> +
> + ev.id = PRESTERA_RXTX_EVENT_RCV_PKT;
> +
> + err = prestera_find_event_handler(sw, PRESTERA_EVENT_TYPE_RXTX, &eh);
> + if (err)
> + return;
> +
> + eh.func(sw, &ev, eh.arg);
> +}
> +
> +int prestera_hw_port_info_get(const struct prestera_port *port,
> + u16 *fp_id, u32 *hw_id, u32 *dev_id)
> +{
> + struct prestera_msg_port_info_resp resp;
> + struct prestera_msg_port_info_req req = {
> + .port = port->id
> + };
> + int err;
> +
> + err = prestera_cmd_ret(port->sw, PRESTERA_CMD_TYPE_PORT_INFO_GET,
> + &req.cmd, sizeof(req), &resp.ret, sizeof(resp));
> + if (err)
> + return err;
> +
> + *hw_id = resp.hw_id;
> + *dev_id = resp.dev_id;
> + *fp_id = resp.fp_id;
> +
> + return 0;
> +}
> +
> +int prestera_hw_switch_mac_set(struct prestera_switch *sw, char *mac)
> +{
> + struct prestera_msg_switch_attr_req req = {
> + .attr = PRESTERA_CMD_SWITCH_ATTR_MAC,
> + };
> +
> + memcpy(req.param.mac, mac, sizeof(req.param.mac));
> +
> + return prestera_cmd(sw, PRESTERA_CMD_TYPE_SWITCH_ATTR_SET,
> + &req.cmd, sizeof(req));
> +}
> +
> +int prestera_hw_switch_init(struct prestera_switch *sw)
> +{
> + struct prestera_msg_switch_init_resp resp;
> + struct prestera_msg_common_req req;
> + int err;
> +
> + INIT_LIST_HEAD(&sw->event_handlers);
> +
> + err = prestera_cmd_ret_wait(sw, PRESTERA_CMD_TYPE_SWITCH_INIT,
> + &req.cmd, sizeof(req),
> + &resp.ret, sizeof(resp),
> + PRESTERA_SWITCH_INIT_TIMEOUT);
> + if (err)
> + return err;
> +
> + sw->id = resp.switch_id;
> + sw->port_count = resp.port_count;
> + sw->mtu_min = PRESTERA_MIN_MTU;
> + sw->mtu_max = resp.mtu_max;
> + sw->dev->recv_msg = prestera_evt_recv;
> + sw->dev->recv_pkt = prestera_pkt_recv;
> +
> + return 0;
> +}

Consider adding prestera_hw_switch_fini() that verifies that
'&sw->event_handlers' is empty.

> +
> +int prestera_hw_port_state_set(const struct prestera_port *port,
> + bool admin_state)
> +{
> + struct prestera_msg_port_attr_req req = {
> + .attr = PRESTERA_CMD_PORT_ATTR_ADMIN_STATE,
> + .port = port->hw_id,
> + .dev = port->dev_id,
> + .param = {.admin_state = admin_state}
> + };
> +
> + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET,
> + &req.cmd, sizeof(req));
> +}
> +
> +int prestera_hw_port_mtu_set(const struct prestera_port *port, u32 mtu)
> +{
> + struct prestera_msg_port_attr_req req = {
> + .attr = PRESTERA_CMD_PORT_ATTR_MTU,
> + .port = port->hw_id,
> + .dev = port->dev_id,
> + .param = {.mtu = mtu}
> + };
> +
> + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET,
> + &req.cmd, sizeof(req));
> +}
> +
> +int prestera_hw_port_mac_set(const struct prestera_port *port, char *mac)
> +{
> + struct prestera_msg_port_attr_req req = {
> + .attr = PRESTERA_CMD_PORT_ATTR_MAC,
> + .port = port->hw_id,
> + .dev = port->dev_id
> + };
> + memcpy(&req.param.mac, mac, sizeof(req.param.mac));
> +
> + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET,
> + &req.cmd, sizeof(req));
> +}
> +
> +int prestera_hw_port_cap_get(const struct prestera_port *port,
> + struct prestera_port_caps *caps)
> +{
> + struct prestera_msg_port_attr_resp resp;
> + struct prestera_msg_port_attr_req req = {
> + .attr = PRESTERA_CMD_PORT_ATTR_CAPABILITY,
> + .port = port->hw_id,
> + .dev = port->dev_id
> + };
> + int err;
> +
> + err = prestera_cmd_ret(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_GET,
> + &req.cmd, sizeof(req), &resp.ret, sizeof(resp));
> + if (err)
> + return err;
> +
> + caps->supp_link_modes = resp.param.cap.link_mode;
> + caps->supp_fec = resp.param.cap.fec;
> + caps->type = resp.param.cap.type;
> + caps->transceiver = resp.param.cap.transceiver;
> +
> + return err;
> +}
> +
> +int prestera_hw_port_autoneg_set(const struct prestera_port *port,
> + bool autoneg, u64 link_modes, u8 fec)
> +{
> + struct prestera_msg_port_attr_req req = {
> + .attr = PRESTERA_CMD_PORT_ATTR_AUTONEG,
> + .port = port->hw_id,
> + .dev = port->dev_id,
> + .param = {.autoneg = {.link_mode = link_modes,
> + .enable = autoneg,
> + .fec = fec}
> + }
> + };
> +
> + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET,
> + &req.cmd, sizeof(req));
> +}
> +
> +int prestera_hw_port_stats_get(const struct prestera_port *port,
> + struct prestera_port_stats *st)
> +{
> + struct prestera_msg_port_stats_resp resp;
> + struct prestera_msg_port_attr_req req = {
> + .attr = PRESTERA_CMD_PORT_ATTR_STATS,
> + .port = port->hw_id,
> + .dev = port->dev_id
> + };
> + u64 *hw = resp.stats;
> + int err;
> +
> + err = prestera_cmd_ret(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_GET,
> + &req.cmd, sizeof(req), &resp.ret, sizeof(resp));
> + if (err)
> + return err;
> +
> + st->good_octets_received = hw[PRESTERA_PORT_GOOD_OCTETS_RCV_CNT];
> + st->bad_octets_received = hw[PRESTERA_PORT_BAD_OCTETS_RCV_CNT];
> + st->mac_trans_error = hw[PRESTERA_PORT_MAC_TRANSMIT_ERR_CNT];
> + st->broadcast_frames_received = hw[PRESTERA_PORT_BRDC_PKTS_RCV_CNT];
> + st->multicast_frames_received = hw[PRESTERA_PORT_MC_PKTS_RCV_CNT];
> + st->frames_64_octets = hw[PRESTERA_PORT_PKTS_64L_CNT];
> + st->frames_65_to_127_octets = hw[PRESTERA_PORT_PKTS_65TO127L_CNT];
> + st->frames_128_to_255_octets = hw[PRESTERA_PORT_PKTS_128TO255L_CNT];
> + st->frames_256_to_511_octets = hw[PRESTERA_PORT_PKTS_256TO511L_CNT];
> + st->frames_512_to_1023_octets = hw[PRESTERA_PORT_PKTS_512TO1023L_CNT];
> + st->frames_1024_to_max_octets = hw[PRESTERA_PORT_PKTS_1024TOMAXL_CNT];
> + st->excessive_collision = hw[PRESTERA_PORT_EXCESSIVE_COLLISIONS_CNT];
> + st->multicast_frames_sent = hw[PRESTERA_PORT_MC_PKTS_SENT_CNT];
> + st->broadcast_frames_sent = hw[PRESTERA_PORT_BRDC_PKTS_SENT_CNT];
> + st->fc_sent = hw[PRESTERA_PORT_FC_SENT_CNT];
> + st->fc_received = hw[PRESTERA_PORT_GOOD_FC_RCV_CNT];
> + st->buffer_overrun = hw[PRESTERA_PORT_DROP_EVENTS_CNT];
> + st->undersize = hw[PRESTERA_PORT_UNDERSIZE_PKTS_CNT];
> + st->fragments = hw[PRESTERA_PORT_FRAGMENTS_PKTS_CNT];
> + st->oversize = hw[PRESTERA_PORT_OVERSIZE_PKTS_CNT];
> + st->jabber = hw[PRESTERA_PORT_JABBER_PKTS_CNT];
> + st->rx_error_frame_received = hw[PRESTERA_PORT_MAC_RCV_ERROR_CNT];
> + st->bad_crc = hw[PRESTERA_PORT_BAD_CRC_CNT];
> + st->collisions = hw[PRESTERA_PORT_COLLISIONS_CNT];
> + st->late_collision = hw[PRESTERA_PORT_LATE_COLLISIONS_CNT];
> + st->unicast_frames_received = hw[PRESTERA_PORT_GOOD_UC_PKTS_RCV_CNT];
> + st->unicast_frames_sent = hw[PRESTERA_PORT_GOOD_UC_PKTS_SENT_CNT];
> + st->sent_multiple = hw[PRESTERA_PORT_MULTIPLE_PKTS_SENT_CNT];
> + st->sent_deferred = hw[PRESTERA_PORT_DEFERRED_PKTS_SENT_CNT];
> + st->good_octets_sent = hw[PRESTERA_PORT_GOOD_OCTETS_SENT_CNT];
> +
> + return 0;
> +}
> +
> +int prestera_hw_rxtx_init(struct prestera_switch *sw,
> + struct prestera_rxtx_params *params)
> +{
> + struct prestera_msg_rxtx_resp resp;
> + struct prestera_msg_rxtx_req req;
> + int err;
> +
> + req.use_sdma = params->use_sdma;
> +
> + err = prestera_cmd_ret(sw, PRESTERA_CMD_TYPE_RXTX_INIT,
> + &req.cmd, sizeof(req), &resp.ret, sizeof(resp));
> + if (err)
> + return err;
> +
> + params->map_addr = resp.map_addr;
> + return 0;
> +}
> +
> +int prestera_hw_rxtx_port_init(struct prestera_port *port)
> +{
> + struct prestera_msg_rxtx_port_req req = {
> + .port = port->hw_id,
> + .dev = port->dev_id,
> + };
> +
> + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_RXTX_PORT_INIT,
> + &req.cmd, sizeof(req));
> +}
> +
> +int prestera_hw_event_handler_register(struct prestera_switch *sw,
> + enum prestera_event_type type,
> + prestera_event_cb_t fn,
> + void *arg)
> +{
> + struct prestera_fw_event_handler *eh;
> +
> + eh = __find_event_handler(sw, type);
> + if (eh)
> + return -EEXIST;
> + eh = kmalloc(sizeof(*eh), GFP_KERNEL);
> + if (!eh)
> + return -ENOMEM;
> +
> + eh->type = type;
> + eh->func = fn;
> + eh->arg = arg;
> +
> + INIT_LIST_HEAD(&eh->list);
> +
> + list_add_rcu(&eh->list, &sw->event_handlers);
> +
> + return 0;
> +}
> +
> +void prestera_hw_event_handler_unregister(struct prestera_switch *sw,
> + enum prestera_event_type type,
> + prestera_event_cb_t fn)
> +{
> + struct prestera_fw_event_handler *eh;
> +
> + eh = __find_event_handler(sw, type);
> + if (!eh)
> + return;
> +
> + list_del_rcu(&eh->list);
> + synchronize_rcu();
> + kfree(eh);
> +}
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_hw.h b/drivers/net/ethernet/marvell/prestera/prestera_hw.h
> new file mode 100644
> index 000000000000..acb0e31d6684
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_hw.h
> @@ -0,0 +1,71 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> + *
> + * Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved.
> + *
> + */
> +
> +#ifndef _PRESTERA_HW_H_
> +#define _PRESTERA_HW_H_
> +
> +#include <linux/types.h>
> +
> +enum {
> + PRESTERA_PORT_TYPE_NONE,
> + PRESTERA_PORT_TYPE_TP,
> +
> + PRESTERA_PORT_TYPE_MAX,
> +};
> +
> +enum {
> + PRESTERA_PORT_FEC_OFF,
> +
> + PRESTERA_PORT_FEC_MAX,
> +};
> +
> +struct prestera_switch;
> +struct prestera_port;
> +struct prestera_port_stats;
> +struct prestera_port_caps;
> +enum prestera_event_type;
> +struct prestera_event;
> +
> +typedef void (*prestera_event_cb_t)
> + (struct prestera_switch *sw, struct prestera_event *evt, void *arg);
> +
> +struct prestera_rxtx_params;
> +
> +/* Switch API */
> +int prestera_hw_switch_init(struct prestera_switch *sw);
> +int prestera_hw_switch_mac_set(struct prestera_switch *sw, char *mac);
> +
> +/* Port API */
> +int prestera_hw_port_info_get(const struct prestera_port *port,
> + u16 *fp_id, u32 *hw_id, u32 *dev_id);
> +int prestera_hw_port_state_set(const struct prestera_port *port,
> + bool admin_state);
> +int prestera_hw_port_mtu_set(const struct prestera_port *port, u32 mtu);
> +int prestera_hw_port_mtu_get(const struct prestera_port *port, u32 *mtu);
> +int prestera_hw_port_mac_set(const struct prestera_port *port, char *mac);
> +int prestera_hw_port_mac_get(const struct prestera_port *port, char *mac);
> +int prestera_hw_port_cap_get(const struct prestera_port *port,
> + struct prestera_port_caps *caps);
> +int prestera_hw_port_autoneg_set(const struct prestera_port *port,
> + bool autoneg, u64 link_modes, u8 fec);
> +int prestera_hw_port_stats_get(const struct prestera_port *port,
> + struct prestera_port_stats *stats);
> +
> +/* Event handlers */
> +int prestera_hw_event_handler_register(struct prestera_switch *sw,
> + enum prestera_event_type type,
> + prestera_event_cb_t fn,
> + void *arg);
> +void prestera_hw_event_handler_unregister(struct prestera_switch *sw,
> + enum prestera_event_type type,
> + prestera_event_cb_t fn);
> +
> +/* RX/TX */
> +int prestera_hw_rxtx_init(struct prestera_switch *sw,
> + struct prestera_rxtx_params *params);
> +int prestera_hw_rxtx_port_init(struct prestera_port *port);
> +
> +#endif /* _PRESTERA_HW_H_ */
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_main.c b/drivers/net/ethernet/marvell/prestera/prestera_main.c
> new file mode 100644
> index 000000000000..b5241e9b784a
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_main.c
> @@ -0,0 +1,506 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/list.h>
> +#include <linux/netdevice.h>
> +#include <linux/netdev_features.h>
> +#include <linux/etherdevice.h>
> +#include <linux/jiffies.h>
> +#include <linux/of.h>
> +#include <linux/of_net.h>
> +
> +#include "prestera.h"
> +#include "prestera_hw.h"
> +#include "prestera_rxtx.h"
> +
> +#define PRESTERA_MTU_DEFAULT 1536
> +
> +#define PRESTERA_STATS_DELAY_MS msecs_to_jiffies(1000)
> +
> +static struct workqueue_struct *prestera_wq;
> +
> +struct prestera_port *prestera_port_find_by_hwid(struct prestera_switch *sw,
> + u32 dev_id, u32 hw_id)
> +{
> + struct prestera_port *port;
> +
> + rcu_read_lock();
> +
> + list_for_each_entry_rcu(port, &sw->port_list, list) {
> + if (port->dev_id == dev_id && port->hw_id == hw_id) {
> + rcu_read_unlock();
> + return port;

This does not look correct. You call rcu_read_unlock(), but do not take
a reference on the object, so nothing prevents it from being freed.

> + }
> + }
> +
> + rcu_read_unlock();
> +
> + return NULL;
> +}
> +
> +static struct prestera_port *prestera_find_port(struct prestera_switch *sw,
> + u32 port_id)
> +{
> + struct prestera_port *port;
> +
> + rcu_read_lock();
> +
> + list_for_each_entry_rcu(port, &sw->port_list, list) {
> + if (port->id == port_id)
> + break;
> + }
> +
> + rcu_read_unlock();
> +
> + return port;

Same here.

> +}
> +
> +static int prestera_port_state_set(struct net_device *dev, bool is_up)
> +{
> + struct prestera_port *port = netdev_priv(dev);
> + int err;
> +
> + if (!is_up)
> + netif_stop_queue(dev);
> +
> + err = prestera_hw_port_state_set(port, is_up);
> +
> + if (is_up && !err)
> + netif_start_queue(dev);
> +
> + return err;
> +}
> +
> +static int prestera_port_open(struct net_device *dev)
> +{
> + return prestera_port_state_set(dev, true);
> +}
> +
> +static int prestera_port_close(struct net_device *dev)
> +{
> + return prestera_port_state_set(dev, false);
> +}
> +
> +static netdev_tx_t prestera_port_xmit(struct sk_buff *skb,
> + struct net_device *dev)
> +{
> + return prestera_rxtx_xmit(netdev_priv(dev), skb);
> +}
> +
> +static int prestera_is_valid_mac_addr(struct prestera_port *port, u8 *addr)
> +{
> + if (!is_valid_ether_addr(addr))
> + return -EADDRNOTAVAIL;
> +
> + if (memcmp(port->sw->base_mac, addr, ETH_ALEN - 1))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int prestera_port_set_mac_address(struct net_device *dev, void *p)
> +{
> + struct prestera_port *port = netdev_priv(dev);
> + struct sockaddr *addr = p;
> + int err;
> +
> + err = prestera_is_valid_mac_addr(port, addr->sa_data);
> + if (err)
> + return err;
> +
> + err = prestera_hw_port_mac_set(port, addr->sa_data);
> + if (err)
> + return err;
> +
> + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
> + return 0;
> +}
> +
> +static int prestera_port_change_mtu(struct net_device *dev, int mtu)
> +{
> + struct prestera_port *port = netdev_priv(dev);
> + int err;
> +
> + err = prestera_hw_port_mtu_set(port, mtu);
> + if (err)
> + return err;
> +
> + dev->mtu = mtu;
> + return 0;
> +}
> +
> +static void prestera_port_get_stats64(struct net_device *dev,
> + struct rtnl_link_stats64 *stats)
> +{
> + struct prestera_port *port = netdev_priv(dev);
> + struct prestera_port_stats *port_stats = &port->cached_hw_stats.stats;
> +
> + stats->rx_packets = port_stats->broadcast_frames_received +
> + port_stats->multicast_frames_received +
> + port_stats->unicast_frames_received;
> +
> + stats->tx_packets = port_stats->broadcast_frames_sent +
> + port_stats->multicast_frames_sent +
> + port_stats->unicast_frames_sent;
> +
> + stats->rx_bytes = port_stats->good_octets_received;
> +
> + stats->tx_bytes = port_stats->good_octets_sent;
> +
> + stats->rx_errors = port_stats->rx_error_frame_received;
> + stats->tx_errors = port_stats->mac_trans_error;
> +
> + stats->rx_dropped = port_stats->buffer_overrun;
> + stats->tx_dropped = 0;
> +
> + stats->multicast = port_stats->multicast_frames_received;
> + stats->collisions = port_stats->excessive_collision;
> +
> + stats->rx_crc_errors = port_stats->bad_crc;
> +}
> +
> +static void prestera_port_get_hw_stats(struct prestera_port *port)
> +{
> + prestera_hw_port_stats_get(port, &port->cached_hw_stats.stats);
> +}
> +
> +static void prestera_port_stats_update(struct work_struct *work)
> +{
> + struct prestera_port *port =
> + container_of(work, struct prestera_port,
> + cached_hw_stats.caching_dw.work);
> +
> + prestera_port_get_hw_stats(port);
> +
> + queue_delayed_work(prestera_wq, &port->cached_hw_stats.caching_dw,
> + PRESTERA_STATS_DELAY_MS);
> +}
> +
> +static const struct net_device_ops netdev_ops = {
> + .ndo_open = prestera_port_open,
> + .ndo_stop = prestera_port_close,
> + .ndo_start_xmit = prestera_port_xmit,
> + .ndo_change_mtu = prestera_port_change_mtu,
> + .ndo_get_stats64 = prestera_port_get_stats64,
> + .ndo_set_mac_address = prestera_port_set_mac_address,
> +};
> +
> +static int prestera_port_autoneg_set(struct prestera_port *port, bool enable,
> + u64 link_modes, u8 fec)
> +{
> + bool refresh = false;
> + int err = 0;
> +
> + if (port->caps.type != PRESTERA_PORT_TYPE_TP)
> + return enable ? -EINVAL : 0;
> +
> + if (port->adver_link_modes != link_modes || port->adver_fec != fec) {
> + port->adver_fec = fec ?: BIT(PRESTERA_PORT_FEC_OFF);
> + port->adver_link_modes = link_modes;
> + refresh = true;
> + }
> +
> + if (port->autoneg == enable && !(port->autoneg && refresh))
> + return 0;
> +
> + err = prestera_hw_port_autoneg_set(port, enable, port->adver_link_modes,
> + port->adver_fec);
> + if (err)
> + return -EINVAL;
> +
> + port->autoneg = enable;
> + return 0;
> +}
> +
> +static int prestera_port_create(struct prestera_switch *sw, u32 id)
> +{
> + struct prestera_port *port;
> + struct net_device *dev;
> + int err;
> +
> + dev = alloc_etherdev(sizeof(*port));
> + if (!dev)
> + return -ENOMEM;
> +
> + port = netdev_priv(dev);
> +
> + port->dev = dev;
> + port->id = id;
> + port->sw = sw;
> +
> + err = prestera_hw_port_info_get(port, &port->fp_id,
> + &port->hw_id, &port->dev_id);
> + if (err) {
> + dev_err(prestera_dev(sw), "Failed to get port(%u) info\n", id);
> + goto err_port_init;
> + }
> +
> + dev->features |= NETIF_F_NETNS_LOCAL;
> + dev->netdev_ops = &netdev_ops;
> +
> + netif_carrier_off(dev);
> +
> + dev->mtu = min_t(unsigned int, sw->mtu_max, PRESTERA_MTU_DEFAULT);
> + dev->min_mtu = sw->mtu_min;
> + dev->max_mtu = sw->mtu_max;
> +
> + err = prestera_hw_port_mtu_set(port, dev->mtu);
> + if (err) {
> + dev_err(prestera_dev(sw), "Failed to set port(%u) mtu(%d)\n",
> + id, dev->mtu);
> + goto err_port_init;
> + }
> +
> + /* Only 0xFF mac addrs are supported */
> + if (port->fp_id >= 0xFF)
> + goto err_port_init;
> +
> + memcpy(dev->dev_addr, sw->base_mac, dev->addr_len - 1);
> + dev->dev_addr[dev->addr_len - 1] = (char)port->fp_id;
> +
> + err = prestera_hw_port_mac_set(port, dev->dev_addr);
> + if (err) {
> + dev_err(prestera_dev(sw), "Failed to set port(%u) mac addr\n", id);
> + goto err_port_init;
> + }
> +
> + err = prestera_hw_port_cap_get(port, &port->caps);
> + if (err) {
> + dev_err(prestera_dev(sw), "Failed to get port(%u) caps\n", id);
> + goto err_port_init;
> + }
> +
> + port->adver_fec = BIT(PRESTERA_PORT_FEC_OFF);
> + prestera_port_autoneg_set(port, true, port->caps.supp_link_modes,
> + port->caps.supp_fec);
> +
> + err = prestera_hw_port_state_set(port, false);
> + if (err) {
> + dev_err(prestera_dev(sw), "Failed to set port(%u) down\n", id);
> + goto err_port_init;
> + }
> +
> + err = prestera_rxtx_port_init(port);
> + if (err)
> + goto err_port_init;
> +
> + INIT_DELAYED_WORK(&port->cached_hw_stats.caching_dw,
> + &prestera_port_stats_update);
> +
> + list_add_rcu(&port->list, &sw->port_list);
> +
> + err = register_netdev(dev);
> + if (err)
> + goto err_register_netdev;
> +
> + return 0;
> +
> +err_register_netdev:
> + list_del_rcu(&port->list);
> +err_port_init:
> + free_netdev(dev);
> + return err;
> +}
> +
> +static void prestera_port_destroy(struct prestera_port *port)
> +{
> + struct net_device *dev = port->dev;
> +
> + cancel_delayed_work_sync(&port->cached_hw_stats.caching_dw);
> + unregister_netdev(dev);
> +
> + list_del_rcu(&port->list);
> +

I'm not sure what is the point of these blank lines. Best to remove
them.

> + free_netdev(dev);
> +}
> +
> +static void prestera_destroy_ports(struct prestera_switch *sw)
> +{
> + struct prestera_port *port, *tmp;
> + struct list_head remove_list;
> +
> + INIT_LIST_HEAD(&remove_list);
> +
> + list_splice_init(&sw->port_list, &remove_list);
> +
> + list_for_each_entry_safe(port, tmp, &remove_list, list)
> + prestera_port_destroy(port);
> +}
> +
> +static int prestera_create_ports(struct prestera_switch *sw)
> +{
> + u32 port;
> + int err;
> +
> + for (port = 0; port < sw->port_count; port++) {
> + err = prestera_port_create(sw, port);
> + if (err)
> + goto err_ports_init;

err_port_create ?

> + }
> +
> + return 0;
> +
> +err_ports_init:
> + prestera_destroy_ports(sw);

I'm not a fan of this construct. I find it best to always do proper
rollback in the error path. Then you can always maintain init() being
followed by fini() which is much easier to review.

> + return err;
> +}
> +
> +static void prestera_port_handle_event(struct prestera_switch *sw,
> + struct prestera_event *evt, void *arg)
> +{
> + struct delayed_work *caching_dw;
> + struct prestera_port *port;
> +
> + port = prestera_find_port(sw, evt->port_evt.port_id);
> + if (!port)
> + return;
> +
> + caching_dw = &port->cached_hw_stats.caching_dw;
> +
> + if (evt->id == PRESTERA_PORT_EVENT_STATE_CHANGED) {
> + if (evt->port_evt.data.oper_state) {
> + netif_carrier_on(port->dev);
> + if (!delayed_work_pending(caching_dw))
> + queue_delayed_work(prestera_wq, caching_dw, 0);
> + } else {
> + netif_carrier_off(port->dev);
> + if (delayed_work_pending(caching_dw))
> + cancel_delayed_work(caching_dw);
> + }
> + }
> +}
> +
> +static void prestera_event_handlers_unregister(struct prestera_switch *sw)
> +{
> + prestera_hw_event_handler_unregister(sw, PRESTERA_EVENT_TYPE_PORT,
> + prestera_port_handle_event);
> +}

Please reverse the order so that register() is first.

> +
> +static int prestera_event_handlers_register(struct prestera_switch *sw)
> +{
> + return prestera_hw_event_handler_register(sw, PRESTERA_EVENT_TYPE_PORT,
> + prestera_port_handle_event,
> + NULL);
> +}
> +
> +static int prestera_switch_set_base_mac_addr(struct prestera_switch *sw)
> +{
> + struct device_node *base_mac_np;
> + struct device_node *np;
> +
> + np = of_find_compatible_node(NULL, NULL, "marvell,prestera");
> + if (np) {
> + base_mac_np = of_parse_phandle(np, "base-mac-provider", 0);
> + if (base_mac_np) {
> + const char *base_mac;
> +
> + base_mac = of_get_mac_address(base_mac_np);
> + of_node_put(base_mac_np);
> + if (!IS_ERR(base_mac))
> + ether_addr_copy(sw->base_mac, base_mac);
> + }
> + }
> +
> + if (!is_valid_ether_addr(sw->base_mac)) {
> + eth_random_addr(sw->base_mac);
> + dev_info(sw->dev->dev, "using random base mac address\n");
> + }
> +
> + return prestera_hw_switch_mac_set(sw, sw->base_mac);
> +}
> +
> +static int prestera_switch_init(struct prestera_switch *sw)
> +{
> + int err;
> +
> + err = prestera_hw_switch_init(sw);
> + if (err) {
> + dev_err(prestera_dev(sw), "Failed to init Switch device\n");
> + return err;
> + }
> +
> + INIT_LIST_HEAD(&sw->port_list);
> +
> + err = prestera_switch_set_base_mac_addr(sw);
> + if (err)
> + return err;
> +
> + err = prestera_rxtx_switch_init(sw);
> + if (err)
> + return err;
> +
> + err = prestera_event_handlers_register(sw);
> + if (err)
> + return err;
> +
> + err = prestera_create_ports(sw);
> + if (err)
> + goto err_ports_create;
> +
> + return 0;
> +
> +err_ports_create:
> + prestera_event_handlers_unregister(sw);
> +

You are missing prestera_rxtx_switch_fini() here... With init() always
followed by fini() you can easily tell that the error path is not
symmetric with fini().

> + return err;
> +}
> +
> +static void prestera_switch_fini(struct prestera_switch *sw)
> +{
> + prestera_destroy_ports(sw);
> + prestera_event_handlers_unregister(sw);
> + prestera_rxtx_switch_fini(sw);
> +}
> +
> +int prestera_device_register(struct prestera_device *dev)
> +{
> + struct prestera_switch *sw;
> + int err;
> +
> + sw = kzalloc(sizeof(*sw), GFP_KERNEL);
> + if (!sw)
> + return -ENOMEM;
> +
> + dev->priv = sw;
> + sw->dev = dev;
> +
> + err = prestera_switch_init(sw);
> + if (err) {
> + kfree(sw);
> + return err;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(prestera_device_register);
> +
> +void prestera_device_unregister(struct prestera_device *dev)
> +{
> + struct prestera_switch *sw = dev->priv;
> +
> + prestera_switch_fini(sw);
> + kfree(sw);
> +}
> +EXPORT_SYMBOL(prestera_device_unregister);
> +
> +static int __init prestera_module_init(void)
> +{
> + prestera_wq = alloc_workqueue("prestera", 0, 0);
> + if (!prestera_wq)
> + return -ENOMEM;
> +
> + return 0;
> +}
> +
> +static void __exit prestera_module_exit(void)
> +{
> + destroy_workqueue(prestera_wq);
> +}
> +
> +module_init(prestera_module_init);
> +module_exit(prestera_module_exit);
> +
> +MODULE_LICENSE("Dual BSD/GPL");
> +MODULE_DESCRIPTION("Marvell Prestera switch driver");
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_rxtx.c b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.c
> new file mode 100644
> index 000000000000..78f1b7dfdc2e
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.c
> @@ -0,0 +1,860 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */
> +
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/dmapool.h>
> +#include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
> +#include <linux/if_vlan.h>
> +
> +#include "prestera.h"
> +#include "prestera_hw.h"
> +#include "prestera_dsa.h"
> +#include "prestera_rxtx.h"
> +
> +struct prestera_sdma_desc {
> + __le32 word1;
> + __le32 word2;
> + __le32 buff;
> + __le32 next;
> +} __packed __aligned(16);
> +
> +#define PRESTERA_SDMA_BUFF_SIZE_MAX 1544
> +
> +#define PRESTERA_SDMA_RX_DESC_PKT_LEN(desc) \
> + ((le32_to_cpu((desc)->word2) >> 16) & 0x3FFF)
> +
> +#define PRESTERA_SDMA_RX_DESC_OWNER(desc) \
> + ((le32_to_cpu((desc)->word1) & BIT(31)) >> 31)
> +
> +#define PRESTERA_SDMA_RX_DESC_IS_RCVD(desc) \
> + (PRESTERA_SDMA_RX_DESC_OWNER((desc)) == PRESTERA_SDMA_RX_DESC_CPU_OWN)
> +
> +#define PRESTERA_SDMA_RX_DESC_CPU_OWN 0
> +#define PRESTERA_SDMA_RX_DESC_DMA_OWN 1
> +
> +#define PRESTERA_SDMA_RX_QUEUE_NUM 8
> +
> +#define PRESTERA_SDMA_RX_DESC_PER_Q 1000
> +
> +#define PRESTERA_SDMA_TX_DESC_PER_Q 1000
> +#define PRESTERA_SDMA_TX_MAX_BURST 64
> +
> +#define PRESTERA_SDMA_TX_DESC_OWNER(desc) \
> + ((le32_to_cpu((desc)->word1) & BIT(31)) >> 31)
> +
> +#define PRESTERA_SDMA_TX_DESC_CPU_OWN 0
> +#define PRESTERA_SDMA_TX_DESC_DMA_OWN 1
> +
> +#define PRESTERA_SDMA_TX_DESC_IS_SENT(desc) \
> + (PRESTERA_SDMA_TX_DESC_OWNER(desc) == PRESTERA_SDMA_TX_DESC_CPU_OWN)
> +
> +#define PRESTERA_SDMA_TX_DESC_LAST BIT(20)
> +#define PRESTERA_SDMA_TX_DESC_FIRST BIT(21)
> +#define PRESTERA_SDMA_TX_DESC_CALC_CRC BIT(12)
> +
> +#define PRESTERA_SDMA_TX_DESC_SINGLE \
> + (PRESTERA_SDMA_TX_DESC_FIRST | PRESTERA_SDMA_TX_DESC_LAST)
> +
> +#define PRESTERA_SDMA_TX_DESC_INIT \
> + (PRESTERA_SDMA_TX_DESC_SINGLE | PRESTERA_SDMA_TX_DESC_CALC_CRC)
> +
> +#define PRESTERA_SDMA_RX_INTR_MASK_REG 0x2814
> +#define PRESTERA_SDMA_RX_QUEUE_STATUS_REG 0x2680
> +#define PRESTERA_SDMA_RX_QUEUE_DESC_REG(n) (0x260C + (n) * 16)
> +
> +#define PRESTERA_SDMA_TX_QUEUE_DESC_REG 0x26C0
> +#define PRESTERA_SDMA_TX_QUEUE_START_REG 0x2868
> +
> +struct prestera_sdma_buf {
> + struct prestera_sdma_desc *desc;
> + dma_addr_t desc_dma;
> + struct sk_buff *skb;
> + dma_addr_t buf_dma;
> + bool is_used;
> +};
> +
> +struct prestera_rx_ring {
> + struct prestera_sdma_buf *bufs;
> + int next_rx;
> +};
> +
> +struct prestera_tx_ring {
> + struct prestera_sdma_buf *bufs;
> + int next_tx;
> + int max_burst;
> + int burst;
> +};
> +
> +struct prestera_sdma {
> + struct prestera_rx_ring rx_ring[PRESTERA_SDMA_RX_QUEUE_NUM];
> + struct prestera_tx_ring tx_ring;
> + struct prestera_switch *sw;
> + struct dma_pool *desc_pool;
> + struct work_struct tx_work;
> + struct napi_struct rx_napi;
> + struct net_device napi_dev;
> + u32 map_addr;
> + u64 dma_mask;
> + /* protect SDMA with concurrrent access from multiple CPUs */
> + spinlock_t tx_lock;
> +};
> +
> +struct prestera_rxtx {
> + struct prestera_sdma sdma;
> +};
> +
> +static int prestera_sdma_buf_init(struct prestera_sdma *sdma,
> + struct prestera_sdma_buf *buf)
> +{
> + struct device *dma_dev = sdma->sw->dev->dev;
> + struct prestera_sdma_desc *desc;
> + dma_addr_t dma;
> +
> + desc = dma_pool_alloc(sdma->desc_pool, GFP_DMA | GFP_KERNEL, &dma);
> + if (!desc)
> + return -ENOMEM;
> +
> + if (dma + sizeof(struct prestera_sdma_desc) > sdma->dma_mask) {
> + dev_err(dma_dev, "failed to alloc desc\n");
> + dma_pool_free(sdma->desc_pool, desc, dma);
> + return -ENOMEM;
> + }
> +
> + buf->buf_dma = DMA_MAPPING_ERROR;
> + buf->desc_dma = dma;
> + buf->desc = desc;
> + buf->skb = NULL;
> +
> + return 0;
> +}
> +
> +static u32 prestera_sdma_map(struct prestera_sdma *sdma, dma_addr_t pa)
> +{
> + return sdma->map_addr + pa;
> +}
> +
> +static void prestera_sdma_rx_desc_set_len(struct prestera_sdma_desc *desc,
> + size_t val)
> +{
> + u32 word = le32_to_cpu(desc->word2);
> +
> + word = (word & ~GENMASK(15, 0)) | val;
> + desc->word2 = cpu_to_le32(word);
> +}
> +
> +static void prestera_sdma_rx_desc_init(struct prestera_sdma *sdma,
> + struct prestera_sdma_desc *desc,
> + dma_addr_t buf)
> +{
> + prestera_sdma_rx_desc_set_len(desc, PRESTERA_SDMA_BUFF_SIZE_MAX);
> + desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf));
> +
> + /* make sure buffer is set before reset the descriptor */
> + wmb();
> +
> + desc->word1 = cpu_to_le32(0xA0000000);
> +}
> +
> +static void prestera_sdma_rx_desc_set_next(struct prestera_sdma *sdma,
> + struct prestera_sdma_desc *desc,
> + dma_addr_t next)
> +{
> + desc->next = cpu_to_le32(prestera_sdma_map(sdma, next));
> +}
> +
> +static int prestera_sdma_rx_skb_alloc(struct prestera_sdma *sdma,
> + struct prestera_sdma_buf *buf)
> +{
> + struct device *dev = sdma->sw->dev->dev;
> + struct sk_buff *skb;
> + dma_addr_t dma;
> +
> + skb = alloc_skb(PRESTERA_SDMA_BUFF_SIZE_MAX, GFP_DMA | GFP_ATOMIC);
> + if (!skb)
> + return -ENOMEM;
> +
> + dma = dma_map_single(dev, skb->data, skb->len, DMA_FROM_DEVICE);
> +
> + if (dma_mapping_error(dev, dma))
> + goto err_dma_map;
> + if (dma + skb->len > sdma->dma_mask)
> + goto err_dma_range;
> +
> + if (buf->skb)
> + dma_unmap_single(dev, buf->buf_dma, buf->skb->len,
> + DMA_FROM_DEVICE);
> +
> + buf->buf_dma = dma;
> + buf->skb = skb;
> + return 0;
> +
> +err_dma_range:
> + dma_unmap_single(dev, dma, skb->len, DMA_FROM_DEVICE);
> +err_dma_map:
> + kfree_skb(skb);
> +
> + return -ENOMEM;
> +}
> +
> +static struct sk_buff *prestera_sdma_rx_skb_get(struct prestera_sdma *sdma,
> + struct prestera_sdma_buf *buf)
> +{
> + dma_addr_t buf_dma = buf->buf_dma;
> + struct sk_buff *skb = buf->skb;
> + u32 len = skb->len;
> + int err;
> +
> + err = prestera_sdma_rx_skb_alloc(sdma, buf);
> + if (err) {
> + buf->buf_dma = buf_dma;
> + buf->skb = skb;
> +
> + skb = alloc_skb(skb->len, GFP_ATOMIC);
> + if (skb) {
> + skb_put(skb, len);
> + skb_copy_from_linear_data(buf->skb, skb->data, len);
> + }
> + }
> +
> + prestera_sdma_rx_desc_init(sdma, buf->desc, buf->buf_dma);
> +
> + return skb;
> +}
> +
> +static int prestera_rxtx_process_skb(struct prestera_sdma *sdma,
> + struct sk_buff *skb)
> +{
> + const struct prestera_port *port;
> + struct prestera_dsa dsa;
> + u32 hw_port, hw_id;
> + int err;
> +
> + skb_pull(skb, ETH_HLEN);
> +
> + /* ethertype field is part of the dsa header */
> + err = prestera_dsa_parse(&dsa, skb->data - ETH_TLEN);
> + if (err)
> + return err;
> +
> + hw_port = dsa.port_num;
> + hw_id = dsa.hw_dev_num;
> +
> + port = prestera_port_find_by_hwid(sdma->sw, hw_id, hw_port);
> + if (unlikely(!port)) {
> + pr_warn_ratelimited("prestera: received pkt for non-existent port(%u, %u)\n",
> + hw_id, hw_port);
> + return -EEXIST;
> + }
> +
> + if (unlikely(!pskb_may_pull(skb, PRESTERA_DSA_HLEN)))
> + return -EINVAL;
> +
> + /* remove DSA tag and update checksum */
> + skb_pull_rcsum(skb, PRESTERA_DSA_HLEN);
> +
> + memmove(skb->data - ETH_HLEN, skb->data - ETH_HLEN - PRESTERA_DSA_HLEN,
> + ETH_ALEN * 2);
> +
> + skb_push(skb, ETH_HLEN);
> +
> + skb->protocol = eth_type_trans(skb, port->dev);
> +
> + if (dsa.vlan.is_tagged) {
> + u16 tci = dsa.vlan.vid & VLAN_VID_MASK;
> +
> + tci |= dsa.vlan.vpt << VLAN_PRIO_SHIFT;
> + if (dsa.vlan.cfi_bit)
> + tci |= VLAN_CFI_MASK;
> +
> + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci);
> + }
> +
> + return 0;
> +}
> +
> +static int prestera_sdma_next_rx_buf_idx(int buf_idx)
> +{
> + return (buf_idx + 1) % PRESTERA_SDMA_RX_DESC_PER_Q;
> +}
> +
> +static int prestera_sdma_rx_poll(struct napi_struct *napi, int budget)
> +{
> + int qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
> + unsigned int rxq_done_map = 0;
> + struct prestera_sdma *sdma;
> + struct list_head rx_list;
> + unsigned int qmask;
> + int pkts_done = 0;
> + int q;
> +
> + qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
> + qmask = GENMASK(qnum - 1, 0);
> +
> + INIT_LIST_HEAD(&rx_list);
> +
> + sdma = container_of(napi, struct prestera_sdma, rx_napi);
> +
> + while (pkts_done < budget && rxq_done_map != qmask) {
> + for (q = 0; q < qnum && pkts_done < budget; q++) {
> + struct prestera_rx_ring *ring = &sdma->rx_ring[q];
> + struct prestera_sdma_desc *desc;
> + struct prestera_sdma_buf *buf;
> + int buf_idx = ring->next_rx;
> + struct sk_buff *skb;
> +
> + buf = &ring->bufs[buf_idx];
> + desc = buf->desc;
> +
> + if (PRESTERA_SDMA_RX_DESC_IS_RCVD(desc)) {
> + rxq_done_map &= ~BIT(q);
> + } else {
> + rxq_done_map |= BIT(q);
> + continue;
> + }
> +
> + pkts_done++;
> +
> + __skb_trim(buf->skb, PRESTERA_SDMA_RX_DESC_PKT_LEN(desc));
> +
> + skb = prestera_sdma_rx_skb_get(sdma, buf);
> + if (!skb)
> + goto rx_next_buf;
> +
> + if (unlikely(prestera_rxtx_process_skb(sdma, skb)))
> + goto rx_next_buf;
> +
> + list_add_tail(&skb->list, &rx_list);
> +rx_next_buf:
> + ring->next_rx = prestera_sdma_next_rx_buf_idx(buf_idx);
> + }
> + }
> +
> + if (pkts_done < budget && napi_complete_done(napi, pkts_done))
> + prestera_write(sdma->sw, PRESTERA_SDMA_RX_INTR_MASK_REG,
> + 0xff << 2);
> +
> + netif_receive_skb_list(&rx_list);
> +
> + return pkts_done;
> +}
> +
> +static void prestera_sdma_rx_fini(struct prestera_sdma *sdma)
> +{
> + int qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
> + int q, b;
> +
> + /* disable all rx queues */
> + prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG, 0xff00);
> +
> + for (q = 0; q < qnum; q++) {
> + struct prestera_rx_ring *ring = &sdma->rx_ring[q];
> +
> + if (!ring->bufs)
> + break;
> +
> + for (b = 0; b < PRESTERA_SDMA_RX_DESC_PER_Q; b++) {
> + struct prestera_sdma_buf *buf = &ring->bufs[b];
> +
> + if (buf->desc_dma)
> + dma_pool_free(sdma->desc_pool, buf->desc,
> + buf->desc_dma);
> +
> + if (!buf->skb)
> + continue;
> +
> + if (buf->buf_dma != DMA_MAPPING_ERROR)
> + dma_unmap_single(sdma->sw->dev->dev,
> + buf->buf_dma, buf->skb->len,
> + DMA_FROM_DEVICE);
> + kfree_skb(buf->skb);
> + }
> + }
> +}
> +
> +static int prestera_sdma_rx_init(struct prestera_sdma *sdma)
> +{
> + int bnum = PRESTERA_SDMA_RX_DESC_PER_Q;
> + int qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
> + int q, b;
> + int err;
> +
> + /* disable all rx queues */
> + prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG, 0xff00);
> +
> + for (q = 0; q < qnum; q++) {
> + struct prestera_rx_ring *ring = &sdma->rx_ring[q];
> + struct prestera_sdma_buf *head;
> +
> + ring->bufs = kmalloc_array(bnum, sizeof(*head), GFP_KERNEL);
> + if (!ring->bufs)
> + return -ENOMEM;
> +
> + head = &ring->bufs[0];
> + ring->next_rx = 0;
> +
> + for (b = 0; b < bnum; b++) {
> + struct prestera_sdma_buf *buf = &ring->bufs[b];
> +
> + err = prestera_sdma_buf_init(sdma, buf);
> + if (err)
> + return err;
> +
> + err = prestera_sdma_rx_skb_alloc(sdma, buf);
> + if (err)
> + return err;
> +
> + prestera_sdma_rx_desc_init(sdma, buf->desc,
> + buf->buf_dma);
> +
> + if (b == 0)
> + continue;
> +
> + prestera_sdma_rx_desc_set_next(sdma,
> + ring->bufs[b - 1].desc,
> + buf->desc_dma);
> +
> + if (b == PRESTERA_SDMA_RX_DESC_PER_Q - 1)
> + prestera_sdma_rx_desc_set_next(sdma, buf->desc,
> + head->desc_dma);
> + }
> +
> + prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_DESC_REG(q),
> + prestera_sdma_map(sdma, head->desc_dma));
> + }
> +
> + /* make sure all rx descs are filled before enabling all rx queues */
> + wmb();
> +
> + prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG, 0xff);
> +
> + return 0;
> +}
> +
> +static void prestera_sdma_tx_desc_init(struct prestera_sdma *sdma,
> + struct prestera_sdma_desc *desc)
> +{
> + desc->word1 = cpu_to_le32(PRESTERA_SDMA_TX_DESC_INIT);
> + desc->word2 = 0;
> +}
> +
> +static void prestera_sdma_tx_desc_set_next(struct prestera_sdma *sdma,
> + struct prestera_sdma_desc *desc,
> + dma_addr_t next)
> +{
> + desc->next = cpu_to_le32(prestera_sdma_map(sdma, next));
> +}
> +
> +static void prestera_sdma_tx_desc_set_buf(struct prestera_sdma *sdma,
> + struct prestera_sdma_desc *desc,
> + dma_addr_t buf, size_t len)
> +{
> + u32 word = le32_to_cpu(desc->word2);
> +
> + word = (word & ~GENMASK(30, 16)) | ((len + ETH_FCS_LEN) << 16);
> +
> + desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf));
> + desc->word2 = cpu_to_le32(word);
> +}
> +
> +static void prestera_sdma_tx_desc_xmit(struct prestera_sdma_desc *desc)
> +{
> + u32 word = le32_to_cpu(desc->word1);
> +
> + word |= PRESTERA_SDMA_TX_DESC_DMA_OWN << 31;
> +
> + /* make sure everything is written before enable xmit */
> + wmb();
> +
> + desc->word1 = cpu_to_le32(word);
> +}
> +
> +static int prestera_sdma_tx_buf_map(struct prestera_sdma *sdma,
> + struct prestera_sdma_buf *buf,
> + struct sk_buff *skb)
> +{
> + struct device *dma_dev = sdma->sw->dev->dev;
> + struct sk_buff *new_skb;
> + size_t len = skb->len;
> + dma_addr_t dma;
> +
> + dma = dma_map_single(dma_dev, skb->data, len, DMA_TO_DEVICE);
> + if (!dma_mapping_error(dma_dev, dma) && dma + len <= sdma->dma_mask) {
> + buf->buf_dma = dma;
> + buf->skb = skb;
> + return 0;
> + }
> +
> + if (!dma_mapping_error(dma_dev, dma))
> + dma_unmap_single(dma_dev, dma, len, DMA_TO_DEVICE);
> +
> + new_skb = alloc_skb(len, GFP_ATOMIC | GFP_DMA);
> + if (!new_skb)
> + goto err_alloc_skb;
> +
> + dma = dma_map_single(dma_dev, new_skb->data, len, DMA_TO_DEVICE);
> + if (dma_mapping_error(dma_dev, dma))
> + goto err_dma_map;
> + if (dma + len > sdma->dma_mask)
> + goto err_dma_range;
> +
> + skb_copy_from_linear_data(skb, skb_put(new_skb, len), len);
> +
> + dev_consume_skb_any(skb);
> +
> + buf->skb = new_skb;
> + buf->buf_dma = dma;
> +
> + return 0;
> +
> +err_dma_range:
> + dma_unmap_single(dma_dev, dma, len, DMA_TO_DEVICE);
> +err_dma_map:
> + dev_kfree_skb(new_skb);
> +err_alloc_skb:
> + dev_kfree_skb(skb);
> +
> + return -ENOMEM;
> +}
> +
> +static void prestera_sdma_tx_buf_unmap(struct prestera_sdma *sdma,
> + struct prestera_sdma_buf *buf)
> +{
> + struct device *dma_dev = sdma->sw->dev->dev;
> +
> + dma_unmap_single(dma_dev, buf->buf_dma, buf->skb->len, DMA_TO_DEVICE);
> +}
> +
> +static void prestera_sdma_tx_recycle_work_fn(struct work_struct *work)
> +{
> + int bnum = PRESTERA_SDMA_TX_DESC_PER_Q;
> + struct prestera_tx_ring *tx_ring;
> + struct prestera_sdma *sdma;
> + int b;
> +
> + sdma = container_of(work, struct prestera_sdma, tx_work);
> +
> + tx_ring = &sdma->tx_ring;
> +
> + for (b = 0; b < bnum; b++) {
> + struct prestera_sdma_buf *buf = &tx_ring->bufs[b];
> +
> + if (!buf->is_used)
> + continue;
> +
> + if (!PRESTERA_SDMA_TX_DESC_IS_SENT(buf->desc))
> + continue;
> +
> + prestera_sdma_tx_buf_unmap(sdma, buf);
> + dev_consume_skb_any(buf->skb);
> + buf->skb = NULL;
> +
> + /* make sure everything is cleaned up */
> + wmb();
> +
> + buf->is_used = false;
> + }
> +}
> +
> +static int prestera_sdma_tx_init(struct prestera_sdma *sdma)
> +{
> + struct prestera_tx_ring *tx_ring = &sdma->tx_ring;
> + int bnum = PRESTERA_SDMA_TX_DESC_PER_Q;
> + struct prestera_sdma_buf *head;
> + int err;
> + int b;
> +
> + INIT_WORK(&sdma->tx_work, prestera_sdma_tx_recycle_work_fn);
> + spin_lock_init(&sdma->tx_lock);
> +
> + tx_ring->bufs = kmalloc_array(bnum, sizeof(*head), GFP_KERNEL);
> + if (!tx_ring->bufs)
> + return -ENOMEM;
> +
> + head = &tx_ring->bufs[0];
> +
> + tx_ring->max_burst = PRESTERA_SDMA_TX_MAX_BURST;
> + tx_ring->burst = tx_ring->max_burst;
> + tx_ring->next_tx = 0;
> +
> + for (b = 0; b < bnum; b++) {
> + struct prestera_sdma_buf *buf = &tx_ring->bufs[b];
> +
> + err = prestera_sdma_buf_init(sdma, buf);
> + if (err)
> + return err;
> +
> + prestera_sdma_tx_desc_init(sdma, buf->desc);
> +
> + buf->is_used = false;
> +
> + if (b == 0)
> + continue;
> +
> + prestera_sdma_tx_desc_set_next(sdma, tx_ring->bufs[b - 1].desc,
> + buf->desc_dma);
> +
> + if (b == PRESTERA_SDMA_TX_DESC_PER_Q - 1)
> + prestera_sdma_tx_desc_set_next(sdma, buf->desc,
> + head->desc_dma);
> + }
> +
> + /* make sure descriptors are written */
> + wmb();
> +
> + prestera_write(sdma->sw, PRESTERA_SDMA_TX_QUEUE_DESC_REG,
> + prestera_sdma_map(sdma, head->desc_dma));
> +
> + return 0;
> +}
> +
> +static void prestera_sdma_tx_fini(struct prestera_sdma *sdma)
> +{
> + struct prestera_tx_ring *ring = &sdma->tx_ring;
> + int bnum = PRESTERA_SDMA_TX_DESC_PER_Q;
> + int b;
> +
> + cancel_work_sync(&sdma->tx_work);
> +
> + if (!ring->bufs)
> + return;
> +
> + for (b = 0; b < bnum; b++) {
> + struct prestera_sdma_buf *buf = &ring->bufs[b];
> +
> + if (buf->desc)
> + dma_pool_free(sdma->desc_pool, buf->desc,
> + buf->desc_dma);
> +
> + if (!buf->skb)
> + continue;
> +
> + dma_unmap_single(sdma->sw->dev->dev, buf->buf_dma,
> + buf->skb->len, DMA_TO_DEVICE);
> +
> + dev_consume_skb_any(buf->skb);
> + }
> +}
> +
> +static void prestera_rxtx_handle_event(struct prestera_switch *sw,
> + struct prestera_event *evt,
> + void *arg)
> +{
> + struct prestera_sdma *sdma = arg;
> +
> + if (evt->id != PRESTERA_RXTX_EVENT_RCV_PKT)
> + return;
> +
> + prestera_write(sdma->sw, PRESTERA_SDMA_RX_INTR_MASK_REG, 0);
> + napi_schedule(&sdma->rx_napi);
> +}
> +
> +static int prestera_sdma_switch_init(struct prestera_switch *sw)
> +{
> + struct prestera_sdma *sdma = &sw->rxtx->sdma;
> + struct device *dev = sw->dev->dev;
> + struct prestera_rxtx_params p;
> + int err;
> +
> + p.use_sdma = true;
> +
> + err = prestera_hw_rxtx_init(sw, &p);
> + if (err) {
> + dev_err(dev, "failed to init rxtx by hw\n");
> + return err;
> + }
> +
> + sdma->dma_mask = dma_get_mask(dev);
> + sdma->map_addr = p.map_addr;
> + sdma->sw = sw;
> +
> + sdma->desc_pool = dma_pool_create("desc_pool", dev,
> + sizeof(struct prestera_sdma_desc),
> + 16, 0);
> + if (!sdma->desc_pool)
> + return -ENOMEM;
> +
> + err = prestera_sdma_rx_init(sdma);
> + if (err) {
> + dev_err(dev, "failed to init rx ring\n");
> + goto err_rx_init;
> + }
> +
> + err = prestera_sdma_tx_init(sdma);
> + if (err) {
> + dev_err(dev, "failed to init tx ring\n");
> + goto err_tx_init;
> + }
> +
> + err = prestera_hw_event_handler_register(sw, PRESTERA_EVENT_TYPE_RXTX,
> + prestera_rxtx_handle_event,
> + sdma);
> + if (err)
> + goto err_evt_register;
> +
> + init_dummy_netdev(&sdma->napi_dev);
> +
> + netif_napi_add(&sdma->napi_dev, &sdma->rx_napi, prestera_sdma_rx_poll, 64);
> + napi_enable(&sdma->rx_napi);
> +
> + return 0;
> +
> +err_evt_register:
> +err_tx_init:
> + prestera_sdma_tx_fini(sdma);
> +err_rx_init:
> + prestera_sdma_rx_fini(sdma);
> +
> + dma_pool_destroy(sdma->desc_pool);
> + return err;
> +}
> +
> +static void prestera_sdma_switch_fini(struct prestera_switch *sw)
> +{
> + struct prestera_sdma *sdma = &sw->rxtx->sdma;
> +
> + prestera_hw_event_handler_unregister(sw, PRESTERA_EVENT_TYPE_RXTX,
> + prestera_rxtx_handle_event);
> + napi_disable(&sdma->rx_napi);
> + netif_napi_del(&sdma->rx_napi);
> + prestera_sdma_rx_fini(sdma);
> + prestera_sdma_tx_fini(sdma);
> + dma_pool_destroy(sdma->desc_pool);

Does not look like the order here is correct.

> +}
> +
> +static bool prestera_sdma_is_ready(struct prestera_sdma *sdma)
> +{
> + return !(prestera_read(sdma->sw, PRESTERA_SDMA_TX_QUEUE_START_REG) & 1);
> +}
> +
> +static int prestera_sdma_tx_wait(struct prestera_sdma *sdma,
> + struct prestera_tx_ring *tx_ring)
> +{
> + int tx_retry_num = 10 * tx_ring->max_burst;
> +
> + while (--tx_retry_num) {
> + if (prestera_sdma_is_ready(sdma))
> + return 0;
> +
> + udelay(1);
> + }
> +
> + return -EBUSY;
> +}
> +
> +static void prestera_sdma_tx_start(struct prestera_sdma *sdma)
> +{
> + prestera_write(sdma->sw, PRESTERA_SDMA_TX_QUEUE_START_REG, 1);
> + schedule_work(&sdma->tx_work);
> +}
> +
> +static netdev_tx_t prestera_sdma_xmit(struct prestera_sdma *sdma,
> + struct sk_buff *skb)
> +{
> + struct device *dma_dev = sdma->sw->dev->dev;
> + struct net_device *dev = skb->dev;
> + struct prestera_tx_ring *tx_ring;
> + struct prestera_sdma_buf *buf;
> + int err;
> +
> + spin_lock(&sdma->tx_lock);
> +
> + tx_ring = &sdma->tx_ring;
> +
> + buf = &tx_ring->bufs[tx_ring->next_tx];
> + if (buf->is_used) {
> + schedule_work(&sdma->tx_work);
> + goto drop_skb;
> + }
> +
> + if (unlikely(eth_skb_pad(skb)))
> + goto drop_skb_nofree;
> +
> + err = prestera_sdma_tx_buf_map(sdma, buf, skb);
> + if (err)
> + goto drop_skb;
> +
> + prestera_sdma_tx_desc_set_buf(sdma, buf->desc, buf->buf_dma, skb->len);
> +
> + dma_sync_single_for_device(dma_dev, buf->buf_dma, skb->len,
> + DMA_TO_DEVICE);
> +
> + if (!tx_ring->burst--) {
> + tx_ring->burst = tx_ring->max_burst;
> +
> + err = prestera_sdma_tx_wait(sdma, tx_ring);
> + if (err)
> + goto drop_skb_unmap;
> + }
> +
> + tx_ring->next_tx = (tx_ring->next_tx + 1) % PRESTERA_SDMA_TX_DESC_PER_Q;
> + prestera_sdma_tx_desc_xmit(buf->desc);
> + buf->is_used = true;
> +
> + prestera_sdma_tx_start(sdma);
> +
> + goto tx_done;
> +
> +drop_skb_unmap:
> + prestera_sdma_tx_buf_unmap(sdma, buf);
> +drop_skb:
> + dev_consume_skb_any(skb);
> +drop_skb_nofree:
> + dev->stats.tx_dropped++;
> +tx_done:
> + spin_unlock(&sdma->tx_lock);
> + return NETDEV_TX_OK;
> +}
> +
> +int prestera_rxtx_switch_init(struct prestera_switch *sw)
> +{
> + struct prestera_rxtx *rxtx;
> +
> + rxtx = kzalloc(sizeof(*rxtx), GFP_KERNEL);
> + if (!rxtx)
> + return -ENOMEM;
> +
> + sw->rxtx = rxtx;
> +
> + return prestera_sdma_switch_init(sw);
> +}
> +
> +void prestera_rxtx_switch_fini(struct prestera_switch *sw)
> +{
> + prestera_sdma_switch_fini(sw);
> + kfree(sw->rxtx);
> +}
> +
> +int prestera_rxtx_port_init(struct prestera_port *port)
> +{
> + int err;
> +
> + err = prestera_hw_rxtx_port_init(port);
> + if (err)
> + return err;
> +
> + port->dev->needed_headroom = PRESTERA_DSA_HLEN + ETH_FCS_LEN;

Why do you need headroom for FCS?

> + return 0;
> +}
> +
> +netdev_tx_t prestera_rxtx_xmit(struct prestera_port *port, struct sk_buff *skb)
> +{
> + struct prestera_dsa dsa;
> +
> + dsa.hw_dev_num = port->dev_id;
> + dsa.port_num = port->hw_id;
> +
> + if (skb_cow_head(skb, PRESTERA_DSA_HLEN) < 0)
> + return NET_XMIT_DROP;
> +
> + skb_push(skb, PRESTERA_DSA_HLEN);
> + memmove(skb->data, skb->data + PRESTERA_DSA_HLEN, 2 * ETH_ALEN);
> +
> + if (prestera_dsa_build(&dsa, skb->data + 2 * ETH_ALEN) != 0)
> + return NET_XMIT_DROP;
> +
> + return prestera_sdma_xmit(&port->sw->rxtx->sdma, skb);
> +}
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_rxtx.h b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.h
> new file mode 100644
> index 000000000000..bbbadfa5accf
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.h
> @@ -0,0 +1,21 @@
> +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> + *
> + * Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved.
> + *
> + */
> +
> +#ifndef _PRESTERA_RXTX_H_
> +#define _PRESTERA_RXTX_H_
> +
> +#include <linux/netdevice.h>
> +
> +#include "prestera.h"
> +
> +int prestera_rxtx_switch_init(struct prestera_switch *sw);
> +void prestera_rxtx_switch_fini(struct prestera_switch *sw);
> +
> +int prestera_rxtx_port_init(struct prestera_port *port);
> +
> +netdev_tx_t prestera_rxtx_xmit(struct prestera_port *port, struct sk_buff *skb);
> +
> +#endif /* _PRESTERA_RXTX_H_ */
> --
> 2.17.1
>