diff mbox series

[RFC,3/4] r8169: support CMAC

Message ID 20211129101315.16372-384-nic_swsd@realtek.com (mailing list archive)
State RFC
Delegated to: Netdev Maintainers
Headers show
Series r8169: support dash | expand

Checks

Context Check Description
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix warning Target tree name not specified in the subject
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit fail Errors and warnings before: 1 this patch: 5
netdev/cc_maintainers warning 2 maintainers not CCed: kuba@kernel.org davem@davemloft.net
netdev/build_clang success Errors and warnings before: 2 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1 this patch: 1
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline fail Was 0 now: 1
netdev/tree_selection success Guessing tree name failed - patch did not apply

Commit Message

Hayes Wang Nov. 29, 2021, 10:13 a.m. UTC
Support CMAC for RTL8111EP and RTL8111FP.

CMAC is the major interface to configure the firmware when dash is
enabled.

Signed-off-by: Hayes Wang <hayeswang@realtek.com>
---
 drivers/net/ethernet/realtek/Makefile     |   2 +-
 drivers/net/ethernet/realtek/r8169.h      |   2 +
 drivers/net/ethernet/realtek/r8169_dash.c | 756 ++++++++++++++++++++++
 drivers/net/ethernet/realtek/r8169_dash.h |  22 +
 drivers/net/ethernet/realtek/r8169_main.c |  54 +-
 5 files changed, 824 insertions(+), 12 deletions(-)
 create mode 100644 drivers/net/ethernet/realtek/r8169_dash.c
 create mode 100644 drivers/net/ethernet/realtek/r8169_dash.h

Comments

Heiner Kallweit Nov. 29, 2021, 8:46 p.m. UTC | #1
On 29.11.2021 11:13, Hayes Wang wrote:
> Support CMAC for RTL8111EP and RTL8111FP.
> 
> CMAC is the major interface to configure the firmware when dash is
> enabled.
> 
> Signed-off-by: Hayes Wang <hayeswang@realtek.com>
> ---
>  drivers/net/ethernet/realtek/Makefile     |   2 +-
>  drivers/net/ethernet/realtek/r8169.h      |   2 +
>  drivers/net/ethernet/realtek/r8169_dash.c | 756 ++++++++++++++++++++++
>  drivers/net/ethernet/realtek/r8169_dash.h |  22 +
>  drivers/net/ethernet/realtek/r8169_main.c |  54 +-
>  5 files changed, 824 insertions(+), 12 deletions(-)
>  create mode 100644 drivers/net/ethernet/realtek/r8169_dash.c
>  create mode 100644 drivers/net/ethernet/realtek/r8169_dash.h
> 
> diff --git a/drivers/net/ethernet/realtek/Makefile b/drivers/net/ethernet/realtek/Makefile
> index 2e1d78b106b0..8f3372ee92ff 100644
> --- a/drivers/net/ethernet/realtek/Makefile
> +++ b/drivers/net/ethernet/realtek/Makefile
> @@ -6,5 +6,5 @@
>  obj-$(CONFIG_8139CP) += 8139cp.o
>  obj-$(CONFIG_8139TOO) += 8139too.o
>  obj-$(CONFIG_ATP) += atp.o
> -r8169-objs += r8169_main.o r8169_firmware.o r8169_phy_config.o
> +r8169-objs += r8169_main.o r8169_firmware.o r8169_phy_config.o r8169_dash.o
>  obj-$(CONFIG_R8169) += r8169.o
> diff --git a/drivers/net/ethernet/realtek/r8169.h b/drivers/net/ethernet/realtek/r8169.h
> index 7db647b4796f..b75484a2a580 100644
> --- a/drivers/net/ethernet/realtek/r8169.h
> +++ b/drivers/net/ethernet/realtek/r8169.h
> @@ -78,5 +78,7 @@ u8 rtl8168d_efuse_read(struct rtl8169_private *tp, int reg_addr);
>  void r8169_hw_phy_config(struct rtl8169_private *tp, struct phy_device *phydev,
>  			 enum mac_version ver);
>  
> +u32 r8168ep_ocp_read(struct rtl8169_private *tp, u16 reg);
>  u32 r8168_type2_read(struct rtl8169_private *tp, u32 addr);
> +void r8168ep_ocp_write(struct rtl8169_private *tp, u8 mask, u16 reg, u32 data);
>  void r8168_type2_write(struct rtl8169_private *tp, u8 mask, u32 addr, u32 val);
> diff --git a/drivers/net/ethernet/realtek/r8169_dash.c b/drivers/net/ethernet/realtek/r8169_dash.c
> new file mode 100644
> index 000000000000..acee7519e9f1
> --- /dev/null
> +++ b/drivers/net/ethernet/realtek/r8169_dash.c
> @@ -0,0 +1,756 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/pci.h>
> +#include <linux/workqueue.h>
> +#include <linux/rtnetlink.h>
> +#include <linux/string.h>
> +#include "r8169.h"
> +#include "r8169_dash.h"
> +
> +#define IBCR0				0xf8
> +#define IBCR2				0xf9
> +#define IBIMR0				0xfa
> +#define IBISR0				0xfb
> +
> +#define RTXS_LS				BIT(12)
> +#define RTXS_FS				BIT(13)
> +#define RTXS_EOR			BIT(14)
> +#define RTXS_OWN			BIT(15)
> +
> +#define DASH_ISR_ROK			BIT(0)
> +#define DASH_ISR_RDU			BIT(1)
> +#define DASH_ISR_TOK			BIT(2)
> +#define DASH_ISR_TDU			BIT(3)
> +#define DASH_ISR_TX_DISABLE_IDLE	BIT(5)
> +#define DASH_ISR_RX_DISABLE_IDLE	BIT(6)
> +
> +#define CMAC_DESC_NUM		4
> +#define CMAC_DESC_SIZE		(CMAC_DESC_NUM * sizeof(struct cmac_desc))
> +#define CMAC_TIMEOUT		(HZ * 5)
> +
> +#define OOB_CMD_DRIVER_START		0x05
> +#define OOB_CMD_DRIVER_STOP		0x06
> +#define OOB_CMD_CMAC_STOP		0x25
> +#define OOB_CMD_CMAC_INIT		0x26
> +#define OOB_CMD_CMAC_RESET		0x2a
> +
> +enum dash_cmac_state {
> +	CMAC_STATE_STOP = 0,
> +	CMAC_STATE_READY,
> +	CMAC_STATE_RUNNING,
> +};
> +
> +enum dash_flag {
> +	DASH_FLAG_CHECK_CMAC = 0,
> +	DASH_FLAG_MAX
> +};
> +
> +#pragma pack(push)
> +#pragma pack(1)
> +
> +struct cmac_desc {
> +	__le16 length;
> +	__le16 status;
> +	__le32 resv;
> +	__le64 dma_addr;
> +};
> +
> +struct oob_hdr {
> +	__le32 len;
> +	u8 type;
> +	u8 flag;
> +	u8 host_req;
> +	u8 res;
> +};
> +
> +#pragma pack(pop)
> +
> +struct dash_tx_info {
> +	u8 *buf;
> +	u32 len;
> +	bool ack;
> +};
> +
> +struct rtl_dash {
> +	struct rtl8169_private *tp;
> +	struct pci_dev *pdev_cmac;
> +	void __iomem *cmac_ioaddr;
> +	struct cmac_desc *tx_desc, *rx_desc;
> +	struct page *tx_buf, *rx_buf;
> +	struct dash_tx_info tx_info[CMAC_DESC_NUM];
> +	struct tasklet_struct tl;

Please see the following in include/linux/interrupt.h:

/* Tasklets --- multithreaded analogue of BHs.

   This API is deprecated. Please consider using threaded IRQs instead:
   https://lore.kernel.org/lkml/20200716081538.2sivhkj4hcyrusem@linutronix.de
Hayes Wang Dec. 3, 2021, 7:57 a.m. UTC | #2
Heiner Kallweit <hkallweit1@gmail.com>
> Sent: Tuesday, November 30, 2021 4:47 AM
> To: Hayes Wang <hayeswang@realtek.com>; Jakub Kicinski <kuba@kernel.org>;
[...]
> > +struct rtl_dash {
> > +	struct rtl8169_private *tp;
> > +	struct pci_dev *pdev_cmac;
> > +	void __iomem *cmac_ioaddr;
> > +	struct cmac_desc *tx_desc, *rx_desc;
> > +	struct page *tx_buf, *rx_buf;
> > +	struct dash_tx_info tx_info[CMAC_DESC_NUM];
> > +	struct tasklet_struct tl;
> 
> Please see the following in include/linux/interrupt.h:
> 
> /* Tasklets --- multithreaded analogue of BHs.
> 
>    This API is deprecated. Please consider using threaded IRQs instead:

How about replacing the tasklet with work?
It seems that the bottom half of threaded IRQ
is the work, too.

Best Regards,
Hayes
Heiner Kallweit Dec. 3, 2021, 11:37 a.m. UTC | #3
On 03.12.2021 08:57, Hayes Wang wrote:
> Heiner Kallweit <hkallweit1@gmail.com>
>> Sent: Tuesday, November 30, 2021 4:47 AM
>> To: Hayes Wang <hayeswang@realtek.com>; Jakub Kicinski <kuba@kernel.org>;
> [...]
>>> +struct rtl_dash {
>>> +	struct rtl8169_private *tp;
>>> +	struct pci_dev *pdev_cmac;
>>> +	void __iomem *cmac_ioaddr;
>>> +	struct cmac_desc *tx_desc, *rx_desc;
>>> +	struct page *tx_buf, *rx_buf;
>>> +	struct dash_tx_info tx_info[CMAC_DESC_NUM];
>>> +	struct tasklet_struct tl;
>>
>> Please see the following in include/linux/interrupt.h:
>>
>> /* Tasklets --- multithreaded analogue of BHs.
>>
>>    This API is deprecated. Please consider using threaded IRQs instead:
> 
> How about replacing the tasklet with work?
> It seems that the bottom half of threaded IRQ
> is the work, too.
> 
Right, it's running in process context. It's to a certain extent
comparable with the efforts to move NAPI processing from softirg
context to threads.

> Best Regards,
> Hayes
> 
>
diff mbox series

Patch

diff --git a/drivers/net/ethernet/realtek/Makefile b/drivers/net/ethernet/realtek/Makefile
index 2e1d78b106b0..8f3372ee92ff 100644
--- a/drivers/net/ethernet/realtek/Makefile
+++ b/drivers/net/ethernet/realtek/Makefile
@@ -6,5 +6,5 @@ 
 obj-$(CONFIG_8139CP) += 8139cp.o
 obj-$(CONFIG_8139TOO) += 8139too.o
 obj-$(CONFIG_ATP) += atp.o
-r8169-objs += r8169_main.o r8169_firmware.o r8169_phy_config.o
+r8169-objs += r8169_main.o r8169_firmware.o r8169_phy_config.o r8169_dash.o
 obj-$(CONFIG_R8169) += r8169.o
diff --git a/drivers/net/ethernet/realtek/r8169.h b/drivers/net/ethernet/realtek/r8169.h
index 7db647b4796f..b75484a2a580 100644
--- a/drivers/net/ethernet/realtek/r8169.h
+++ b/drivers/net/ethernet/realtek/r8169.h
@@ -78,5 +78,7 @@  u8 rtl8168d_efuse_read(struct rtl8169_private *tp, int reg_addr);
 void r8169_hw_phy_config(struct rtl8169_private *tp, struct phy_device *phydev,
 			 enum mac_version ver);
 
+u32 r8168ep_ocp_read(struct rtl8169_private *tp, u16 reg);
 u32 r8168_type2_read(struct rtl8169_private *tp, u32 addr);
+void r8168ep_ocp_write(struct rtl8169_private *tp, u8 mask, u16 reg, u32 data);
 void r8168_type2_write(struct rtl8169_private *tp, u8 mask, u32 addr, u32 val);
diff --git a/drivers/net/ethernet/realtek/r8169_dash.c b/drivers/net/ethernet/realtek/r8169_dash.c
new file mode 100644
index 000000000000..acee7519e9f1
--- /dev/null
+++ b/drivers/net/ethernet/realtek/r8169_dash.c
@@ -0,0 +1,756 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/pci.h>
+#include <linux/workqueue.h>
+#include <linux/rtnetlink.h>
+#include <linux/string.h>
+#include "r8169.h"
+#include "r8169_dash.h"
+
+#define IBCR0				0xf8
+#define IBCR2				0xf9
+#define IBIMR0				0xfa
+#define IBISR0				0xfb
+
+#define RTXS_LS				BIT(12)
+#define RTXS_FS				BIT(13)
+#define RTXS_EOR			BIT(14)
+#define RTXS_OWN			BIT(15)
+
+#define DASH_ISR_ROK			BIT(0)
+#define DASH_ISR_RDU			BIT(1)
+#define DASH_ISR_TOK			BIT(2)
+#define DASH_ISR_TDU			BIT(3)
+#define DASH_ISR_TX_DISABLE_IDLE	BIT(5)
+#define DASH_ISR_RX_DISABLE_IDLE	BIT(6)
+
+#define CMAC_DESC_NUM		4
+#define CMAC_DESC_SIZE		(CMAC_DESC_NUM * sizeof(struct cmac_desc))
+#define CMAC_TIMEOUT		(HZ * 5)
+
+#define OOB_CMD_DRIVER_START		0x05
+#define OOB_CMD_DRIVER_STOP		0x06
+#define OOB_CMD_CMAC_STOP		0x25
+#define OOB_CMD_CMAC_INIT		0x26
+#define OOB_CMD_CMAC_RESET		0x2a
+
+enum dash_cmac_state {
+	CMAC_STATE_STOP = 0,
+	CMAC_STATE_READY,
+	CMAC_STATE_RUNNING,
+};
+
+enum dash_flag {
+	DASH_FLAG_CHECK_CMAC = 0,
+	DASH_FLAG_MAX
+};
+
+#pragma pack(push)
+#pragma pack(1)
+
+struct cmac_desc {
+	__le16 length;
+	__le16 status;
+	__le32 resv;
+	__le64 dma_addr;
+};
+
+struct oob_hdr {
+	__le32 len;
+	u8 type;
+	u8 flag;
+	u8 host_req;
+	u8 res;
+};
+
+#pragma pack(pop)
+
+struct dash_tx_info {
+	u8 *buf;
+	u32 len;
+	bool ack;
+};
+
+struct rtl_dash {
+	struct rtl8169_private *tp;
+	struct pci_dev *pdev_cmac;
+	void __iomem *cmac_ioaddr;
+	struct cmac_desc *tx_desc, *rx_desc;
+	struct page *tx_buf, *rx_buf;
+	struct dash_tx_info tx_info[CMAC_DESC_NUM];
+	struct tasklet_struct tl;
+	struct completion cmac_tx, cmac_rx, fw_ack;
+	spinlock_t cmac_lock; /* spin lock for CMAC */
+	dma_addr_t tx_desc_dma, rx_desc_dma;
+	enum rtl_dash_type hw_dash_ver;
+	enum dash_cmac_state cmac_state;
+
+	struct {
+		DECLARE_BITMAP(flags, DASH_FLAG_MAX);
+		struct work_struct work;
+	} wk;
+
+	/* u64 */
+
+	/* u32 */
+	u32 tx_free, tx_used;
+	u32 rx_cur;
+
+	/* u16 */
+
+	/* u8 */
+};
+
+/* cmac write/read MMIO register */
+#define RTL_CMAC_W8(d, reg, v8)		writeb((v8), (d)->cmac_ioaddr + (reg))
+#define RTL_CMAC_W16(d, reg, v16)	writew((v16), (d)->cmac_ioaddr + (reg))
+#define RTL_CMAC_W32(d, reg, v32)	writel((v32), (d)->cmac_ioaddr + (reg))
+#define RTL_CMAC_R8(d, reg)		readb((d)->cmac_ioaddr + (reg))
+#define RTL_CMAC_R16(d, reg)		readw((d)->cmac_ioaddr + (reg))
+#define RTL_CMAC_R32(d, reg)		readl((d)->cmac_ioaddr + (reg))
+
+struct rtl_dash_cond {
+	bool (*check)(struct rtl_dash *dash);
+	const char *msg;
+};
+
+static bool rtl_dash_loop_wait(struct rtl_dash *dash,
+			       const struct rtl_dash_cond *c,
+			       unsigned long usecs, int n, bool high)
+{
+	int i;
+
+	for (i = 0; i < n; i++) {
+		if (c->check(dash) == high)
+			return true;
+		fsleep(usecs);
+	}
+
+	if (net_ratelimit())
+		dev_err(&dash->pdev_cmac->dev,
+			"%s == %d (loop: %d, delay: %lu).\n",
+			c->msg, !high, n, usecs);
+	return false;
+}
+
+static bool rtl_dash_loop_wait_high(struct rtl_dash *dash,
+				    const struct rtl_dash_cond *c,
+				    unsigned long d, int n)
+{
+	return rtl_dash_loop_wait(dash, c, d, n, true);
+}
+
+static bool rtl_dash_loop_wait_low(struct rtl_dash *dash,
+				   const struct rtl_dash_cond *c,
+				   unsigned long d, int n)
+{
+	return rtl_dash_loop_wait(dash, c, d, n, false);
+}
+
+#define DECLARE_RTL_DASH_COND(name)			\
+static bool name ## _check(struct rtl_dash *dash);	\
+							\
+static const struct rtl_dash_cond name = {		\
+	.check	= name ## _check,			\
+	.msg	= #name					\
+};							\
+							\
+static bool name ## _check(struct rtl_dash *dash)
+
+static inline void rtl_dash_intr_en(struct rtl_dash *dash)
+{
+	RTL_CMAC_W8(dash, IBIMR0, DASH_ISR_ROK | DASH_ISR_RDU | DASH_ISR_TOK |
+				  DASH_ISR_TDU | DASH_ISR_RX_DISABLE_IDLE);
+}
+
+static void dash_tx_bottom(struct rtl_dash *dash)
+{
+	u32 tx_used = dash->tx_used;
+
+	while (tx_used != dash->tx_free) {
+		struct device *d = &dash->pdev_cmac->dev;
+		u32 index = tx_used % CMAC_DESC_NUM;
+		struct cmac_desc *tx_desc = dash->tx_desc + index;
+		struct dash_tx_info *info = dash->tx_info + index;
+
+		if (le16_to_cpu(tx_desc->status) & RTXS_OWN)
+			break;
+
+		dma_unmap_single(d, le64_to_cpu(tx_desc->dma_addr), info->len,
+				 DMA_TO_DEVICE);
+
+		if (!info->ack) {
+			complete(&dash->cmac_tx);
+			dev_dbg(d, "CMAC send TX OK\n");
+		}
+
+		info->len = 0;
+		tx_used++;
+	}
+
+	if (dash->tx_used != tx_used)
+		dash->tx_used = tx_used;
+}
+
+static int cmac_start_xmit(struct rtl_dash *dash, u8 *data, u32 size, bool ack)
+{
+	struct device *d = &dash->pdev_cmac->dev;
+	u32 index = dash->tx_free % CMAC_DESC_NUM;
+	struct cmac_desc *desc = dash->tx_desc + index;
+	struct dash_tx_info *info = dash->tx_info + index;
+	dma_addr_t mapping;
+	int ret;
+
+	if (dash->cmac_state != CMAC_STATE_RUNNING)
+		return -ENETDOWN;
+
+	if ((dash->tx_free - dash->tx_used) >= CMAC_DESC_NUM)
+		return -EBUSY;
+
+	if (IS_ERR_OR_NULL(data) || size > CMAC_BUF_SIZE)
+		return -EINVAL;
+
+	if (unlikely(le16_to_cpu(desc->status) & RTXS_OWN))
+		return -EFAULT;
+
+	memcpy(info->buf, data, size);
+	if (ack) {
+		struct oob_hdr *hdr = (struct oob_hdr *)info->buf;
+
+		hdr->len = 0;
+	}
+
+	mapping = dma_map_single(d, info->buf, size, DMA_TO_DEVICE);
+	ret = dma_mapping_error(d, mapping);
+	if (unlikely(ret)) {
+		dev_err(d, "Failed to map TX DMA!\n");
+		goto err;
+	}
+
+	info->len = size;
+	info->ack = ack;
+
+	desc->dma_addr = cpu_to_le64(mapping);
+	desc->length = cpu_to_le16(size);
+	dma_wmb();
+	desc->status |= cpu_to_le16(RTXS_OWN);
+	dma_wmb();
+	RTL_CMAC_W8(dash, IBCR2, RTL_CMAC_R8(dash, IBCR2) | BIT(1));
+
+	dash->tx_free++;
+	ret = size;
+
+err:
+	return ret;
+}
+
+static int dash_rx_data(struct rtl_dash *dash, u8 *target, u32 size)
+{
+	u32 cur = dash->rx_cur;
+	int i, ret = 0;
+
+	for (i = 0; i < CMAC_DESC_NUM; i++) {
+		u32 index = cur % CMAC_DESC_NUM;
+		struct cmac_desc *desc = dash->rx_desc + index;
+		struct device *d = &dash->pdev_cmac->dev;
+		struct oob_hdr *hdr;
+		dma_addr_t mapping;
+		u8 *addr;
+		u16 pkt_size;
+
+		if (le16_to_cpu(desc->status) & RTXS_OWN)
+			break;
+
+		addr = page_address(dash->rx_buf) + index * CMAC_BUF_SIZE;
+		pkt_size = le16_to_cpu(desc->length);
+
+		mapping = le64_to_cpu(desc->dma_addr);
+		if (mapping) {
+			dma_unmap_single(d, mapping, pkt_size, DMA_FROM_DEVICE);
+			desc->dma_addr = 0;
+		}
+
+		if (pkt_size < sizeof(*hdr))
+			goto re_map;
+
+		hdr = (struct oob_hdr *)addr;
+		switch (hdr->host_req) {
+		case 0x91:
+			dev_dbg(d, "CMAC RX DATA\n");
+			if (mapping &&
+			    cmac_start_xmit(dash, addr, sizeof(struct oob_hdr),
+					    true) < 0)
+				dev_err(d, "send ACK fail\n");
+
+			complete(&dash->cmac_rx);
+
+			if (!target) {
+				ret = -EDESTADDRREQ;
+				break;
+			} else if (size < pkt_size) {
+				ret = -EMSGSIZE;
+				break;
+			}
+			memcpy(target, addr, pkt_size);
+			ret = pkt_size;
+			break;
+		case 0x92:
+			dev_dbg(d, "CMAC RX ACK\n");
+			complete(&dash->fw_ack);
+			break;
+		default:
+			break;
+		}
+
+		if (ret < 0)
+			break;
+
+re_map:
+		mapping = dma_map_single(d, addr, CMAC_BUF_SIZE,
+					 DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(d, mapping))) {
+			dev_err(d, "Failed to map RX DMA!\n");
+			desc->length = 0;
+			break;
+		}
+
+		desc->dma_addr = cpu_to_le64(mapping);
+		desc->length = cpu_to_le16(CMAC_BUF_SIZE);
+		dma_wmb();
+		desc->status |= cpu_to_le16(RTXS_OWN);
+
+		cur++;
+
+		if (ret == pkt_size)
+			break;
+	}
+
+	if (dash->rx_cur != cur)
+		dash->rx_cur = cur;
+
+	return ret;
+}
+
+static void dash_half(struct tasklet_struct *t)
+{
+	struct rtl_dash *dash = from_tasklet(dash, t, tl);
+
+	dash_tx_bottom(dash);
+
+	spin_lock(&dash->cmac_lock);
+	dash_rx_data(dash, NULL, 0);
+	spin_unlock(&dash->cmac_lock);
+
+	rtl_dash_intr_en(dash);
+}
+
+static void rtl_dash_change_cmac_state(struct rtl_dash *dash, u8 state);
+
+DECLARE_RTL_DASH_COND(rtl_cmac_tx_cond)
+{
+	return RTL_CMAC_R8(dash, IBISR0) & DASH_ISR_TX_DISABLE_IDLE;
+}
+
+static void rtl_cmac_disable(struct rtl_dash *dash)
+{
+	u8 status;
+	int i;
+
+	if (dash->cmac_state == CMAC_STATE_RUNNING)
+		tasklet_disable(&dash->tl);
+
+	dash->cmac_state = CMAC_STATE_STOP;
+
+	status = RTL_CMAC_R8(dash, IBCR2);
+	if (status & 0x01) {
+		RTL_CMAC_W8(dash, IBCR2, status & ~0x01);
+		rtl_dash_loop_wait_high(dash, &rtl_cmac_tx_cond, 5000, 2000);
+	}
+
+	status = RTL_CMAC_R8(dash, IBCR0);
+	if (status & 0x01)
+		RTL_CMAC_W8(dash, IBCR0, status & ~0x01);
+
+	for (i = 0; i < CMAC_DESC_NUM; i++) {
+		struct device *d = &dash->pdev_cmac->dev;
+		struct cmac_desc *tx_desc = dash->tx_desc + i;
+		struct cmac_desc *rx_desc = dash->rx_desc + i;
+
+		if (dash->tx_info[i].len)
+			dma_unmap_single(d, le64_to_cpu(tx_desc->dma_addr),
+					 dash->tx_info[i].len, DMA_TO_DEVICE);
+
+		if (rx_desc->dma_addr)
+			dma_unmap_single(d, le64_to_cpu(rx_desc->dma_addr),
+					 CMAC_BUF_SIZE, DMA_TO_DEVICE);
+	}
+
+	memset(dash->tx_desc, 0, CMAC_DESC_SIZE);
+	memset(dash->rx_desc, 0, CMAC_DESC_SIZE);
+	memset(dash->tx_info, 0, sizeof(dash->tx_info));
+
+	RTL_CMAC_W8(dash, IBIMR0, 0);
+	RTL_CMAC_W8(dash, IBISR0, RTL_CMAC_R8(dash, IBISR0));
+}
+
+static int rtl_cmac_enable(struct rtl_dash *dash)
+{
+	u32 desc_addr;
+	int i;
+
+	for (i = 0; i < CMAC_DESC_NUM; i++) {
+		struct dash_tx_info *info = dash->tx_info + i;
+		struct cmac_desc *rx_desc = dash->rx_desc + i;
+		struct cmac_desc *tx_desc = dash->tx_desc + i;
+		struct device *d = &dash->pdev_cmac->dev;
+		dma_addr_t mapping;
+		u8 *addr;
+		u16 ops_rx, ops_tx;
+
+		info->buf = page_address(dash->tx_buf) + i * CMAC_BUF_SIZE;
+		info->len = 0;
+		info->ack = false;
+
+		addr = page_address(dash->rx_buf) + i * CMAC_BUF_SIZE;
+		mapping = dma_map_single(d, addr, CMAC_BUF_SIZE,
+					 DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(d, mapping))) {
+			dev_err(d, "Failed to map RX DMA!\n");
+			rtl_dash_change_cmac_state(dash, OOB_CMD_CMAC_STOP);
+			return -ENOMEM;
+		}
+
+		rx_desc->dma_addr = cpu_to_le64(mapping);
+		rx_desc->length = CMAC_BUF_SIZE;
+		rx_desc->resv = 0;
+
+		if (i == (CMAC_DESC_NUM - 1)) {
+			ops_rx = RTXS_OWN | RTXS_EOR;
+			ops_tx = RTXS_FS | RTXS_LS | RTXS_EOR;
+		} else {
+			ops_rx = RTXS_OWN;
+			ops_tx = RTXS_FS | RTXS_LS;
+		}
+
+		rx_desc->status = cpu_to_le16(ops_rx);
+		tx_desc->status = cpu_to_le16(ops_tx);
+	}
+
+	dash->tx_free = 0;
+	dash->tx_used = 0;
+	dash->rx_cur = 0;
+
+	switch (dash->hw_dash_ver) {
+	case RTL_DASH_EP:
+		desc_addr = 0x890;
+		break;
+	case RTL_DASH_FP:
+		desc_addr = 0xf20090;
+		break;
+	default:
+		desc_addr = 0xf20090;
+		WARN_ON_ONCE(1);
+		break;
+	}
+
+	r8168_type2_write(dash->tp, 0xf, desc_addr,
+			  dash->rx_desc_dma & DMA_BIT_MASK(32));
+	r8168_type2_write(dash->tp, 0xf, desc_addr + 4,
+			  dash->rx_desc_dma >> 32);
+	r8168_type2_write(dash->tp, 0xf, desc_addr + 8,
+			  dash->tx_desc_dma & DMA_BIT_MASK(32));
+	r8168_type2_write(dash->tp, 0xf, desc_addr + 12,
+			  dash->tx_desc_dma >> 32);
+
+	RTL_CMAC_W8(dash, IBCR2, RTL_CMAC_R8(dash, IBCR2) | 0x01);
+	RTL_CMAC_W8(dash, IBCR0, RTL_CMAC_R8(dash, IBCR0) | 0x01);
+
+	tasklet_enable(&dash->tl);
+	dash->cmac_state = CMAC_STATE_RUNNING;
+
+	rtl_dash_intr_en(dash);
+
+	return 0;
+}
+
+static void rtl_dash_oob_notify(struct rtl_dash *dash, u8 cmd)
+{
+	struct rtl8169_private *tp = dash->tp;
+
+	r8168ep_ocp_write(tp, 0x01, 0x180, cmd);
+	r8168ep_ocp_write(tp, 0x01, 0x30, r8168ep_ocp_read(tp, 0x30) | 0x01);
+}
+
+static void rtl_cmac_hw_reset(struct rtl_dash *dash)
+{
+	struct rtl8169_private *tp = dash->tp;
+	u32 TmpUlong;
+
+	TmpUlong = r8168ep_ocp_read(tp, 0x150);
+	r8168ep_ocp_write(tp, 0xf, 0x150, TmpUlong | BIT(5));
+
+	switch (dash->hw_dash_ver) {
+	case RTL_DASH_EP:
+		r8168ep_ocp_write(tp, 0xf, 0x150, TmpUlong & ~BIT(5));
+		RTL_CMAC_W8(dash, IBISR0,
+			    RTL_CMAC_R8(dash, IBISR0) | DASH_ISR_ROK);
+
+		TmpUlong = r8168ep_ocp_read(tp, 0x80c);
+		r8168ep_ocp_write(tp, 0xf, 0x80c, TmpUlong | BIT(24));
+		break;
+	case RTL_DASH_FP:
+		fsleep(1);
+		RTL_CMAC_W8(dash, IBISR0,
+			    RTL_CMAC_R8(dash, IBISR0) | DASH_ISR_ROK);
+		break;
+	default:
+		break;
+	}
+
+	dash->cmac_state = CMAC_STATE_READY;
+}
+
+static void rtl_dash_change_cmac_state(struct rtl_dash *dash, u8 state)
+{
+	switch (state) {
+	case OOB_CMD_CMAC_INIT:
+		if (likely(dash->cmac_state != CMAC_STATE_RUNNING)) {
+			rtl_cmac_hw_reset(dash);
+			break;
+		}
+		state = OOB_CMD_CMAC_STOP;
+		WARN_ON_ONCE(1);
+		fallthrough;
+	case OOB_CMD_CMAC_STOP:
+		rtl_cmac_disable(dash);
+		break;
+
+	case OOB_CMD_CMAC_RESET:
+	default:
+		WARN_ON_ONCE(1);
+		return;
+	}
+
+	rtl_dash_oob_notify(dash, state);
+}
+
+static void rtl_dash_cmac_reset_routine(struct rtl_dash *dash)
+{
+	u32 reg, state;
+
+	switch (dash->hw_dash_ver) {
+	case RTL_DASH_EP:
+		reg = 0x2c20;
+		break;
+	case RTL_DASH_FP:
+		reg = 0xf80420;
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		reg = 0xf80420;
+		break;
+	}
+
+	state = r8168_type2_read(dash->tp, reg);
+	r8168_type2_write(dash->tp, 0xf, reg, 0);
+
+	switch (state) {
+	case OOB_CMD_CMAC_RESET:
+		dev_dbg(&dash->pdev_cmac->dev, "OOB_CMD_CMAC_RESET\n");
+		rtl_dash_change_cmac_state(dash, OOB_CMD_CMAC_STOP);
+		break;
+	case OOB_CMD_CMAC_STOP:
+		dev_dbg(&dash->pdev_cmac->dev, "OOB_CMD_CMAC_STOP\n");
+		rtl_dash_change_cmac_state(dash, OOB_CMD_CMAC_INIT);
+		break;
+	case OOB_CMD_CMAC_INIT:
+		dev_dbg(&dash->pdev_cmac->dev, "OOB_CMD_CMAC_INIT\n");
+		rtl_cmac_enable(dash);
+		break;
+	default:
+		WARN_ON_ONCE(1);
+		return;
+	}
+}
+
+static void rtl_dash_schedule_work(struct rtl_dash *dash, enum dash_flag flag)
+{
+	set_bit(flag, dash->wk.flags);
+	queue_work(system_long_wq, &dash->wk.work);
+}
+
+static void rtl_dash_task(struct work_struct *work)
+{
+	struct rtl_dash *dash = container_of(work, struct rtl_dash, wk.work);
+
+	rtnl_lock();
+
+	if (test_and_clear_bit(DASH_FLAG_CHECK_CMAC, dash->wk.flags))
+		rtl_dash_cmac_reset_routine(dash);
+
+	rtnl_unlock();
+}
+
+DECLARE_RTL_DASH_COND(rtl_dash_state_cond)
+{
+	return r8168ep_ocp_read(dash->tp, 0x124) & 0x00000001;
+}
+
+static void rtl_driver_start(struct rtl_dash *dash)
+{
+	rtl_dash_oob_notify(dash, OOB_CMD_DRIVER_START);
+	rtl_dash_loop_wait_high(dash, &rtl_dash_state_cond, 10000, 10);
+}
+
+static void rtl_driver_stop(struct rtl_dash *dash)
+{
+	rtl_dash_oob_notify(dash, OOB_CMD_DRIVER_STOP);
+	rtl_dash_loop_wait_low(dash, &rtl_dash_state_cond, 10000, 10);
+}
+
+static int rtl_get_cmac_resource(struct rtl_dash **pdash, struct pci_dev *pdev)
+{
+	struct pci_dev *pdev_cmac;
+	void __iomem *cmac_ioaddr;
+
+	pdev_cmac = pci_get_slot(pdev->bus,
+				 PCI_DEVFN(PCI_SLOT(pdev->devfn), 0));
+	cmac_ioaddr = ioremap(pci_resource_start((*pdash)->pdev_cmac, 2), 256);
+	if (cmac_ioaddr) {
+		(*pdash)->pdev_cmac = pdev_cmac;
+		(*pdash)->cmac_ioaddr = cmac_ioaddr;
+		return 0;
+	} else {
+		return -ENOMEM;
+	}
+}
+
+struct rtl_dash *rtl_request_dash(struct rtl8169_private *tp,
+				  struct pci_dev *pci_dev, enum mac_version ver,
+				  void __iomem *mmio_addr)
+{
+	unsigned int order = get_order(CMAC_DESC_NUM * CMAC_BUF_SIZE);
+	struct rtl_dash *dash;
+	int node, ret = -ENOMEM;
+
+	dash = kzalloc(sizeof(*dash), GFP_KERNEL);
+	if (!dash)
+		goto err1;
+
+	dash->tp = tp;
+
+	switch (ver) {
+	case RTL_GIGA_MAC_VER_49 ... RTL_GIGA_MAC_VER_51:
+		dash->pdev_cmac = pci_dev;
+		dash->cmac_ioaddr = mmio_addr;
+		dash->hw_dash_ver = RTL_DASH_EP;
+		break;
+	case RTL_GIGA_MAC_VER_52 ... RTL_GIGA_MAC_VER_53:
+		if (rtl_get_cmac_resource(&dash, pci_dev) < 0)
+			goto err2;
+		dash->hw_dash_ver = RTL_DASH_FP;
+		break;
+	default:
+		ret = -ENODEV;
+		goto err2;
+	}
+
+	dash->tx_desc = dma_alloc_coherent(&dash->pdev_cmac->dev,
+					   CMAC_DESC_SIZE, &dash->tx_desc_dma,
+					   GFP_KERNEL);
+	if (!dash->tx_desc)
+		goto err2;
+
+	dash->rx_desc = dma_alloc_coherent(&dash->pdev_cmac->dev,
+					   CMAC_DESC_SIZE, &dash->rx_desc_dma,
+					   GFP_KERNEL);
+	if (!dash->rx_desc)
+		goto free_tx_desc;
+
+	node = dev_to_node(&dash->pdev_cmac->dev);
+
+	dash->tx_buf = alloc_pages_node(node, GFP_KERNEL, order);
+	if (!dash->tx_buf)
+		goto free_rx_desc;
+
+	dash->rx_buf = alloc_pages_node(node, GFP_KERNEL, order);
+	if (!dash->rx_buf)
+		goto free_tx_fuf;
+
+	memset(dash->tx_desc, 0, CMAC_DESC_SIZE);
+	memset(dash->rx_desc, 0, CMAC_DESC_SIZE);
+	memset(dash->tx_info, 0, sizeof(dash->tx_info));
+
+	INIT_WORK(&dash->wk.work, rtl_dash_task);
+	tasklet_setup(&dash->tl, dash_half);
+	tasklet_disable(&dash->tl);
+	init_completion(&dash->cmac_tx);
+	init_completion(&dash->cmac_rx);
+	init_completion(&dash->fw_ack);
+	spin_lock_init(&dash->cmac_lock);
+
+	return dash;
+
+free_tx_fuf:
+	__free_pages(dash->tx_buf, order);
+free_rx_desc:
+	dma_free_coherent(&dash->pdev_cmac->dev, CMAC_DESC_SIZE, dash->rx_desc,
+			  dash->rx_desc_dma);
+free_tx_desc:
+	dma_free_coherent(&dash->pdev_cmac->dev, CMAC_DESC_SIZE, dash->tx_desc,
+			  dash->tx_desc_dma);
+err2:
+	kfree(dash);
+err1:
+	return ERR_PTR(ret);
+}
+
+void rtl_release_dash(struct rtl_dash *dash)
+{
+	unsigned int order = get_order(CMAC_DESC_NUM * CMAC_BUF_SIZE);
+
+	if (IS_ERR_OR_NULL(dash))
+		return;
+
+	tasklet_kill(&dash->tl);
+
+	__free_pages(dash->rx_buf, order);
+	__free_pages(dash->tx_buf, order);
+	dma_free_coherent(&dash->pdev_cmac->dev, CMAC_DESC_SIZE, dash->rx_desc,
+			  dash->rx_desc_dma);
+	dma_free_coherent(&dash->pdev_cmac->dev, CMAC_DESC_SIZE, dash->tx_desc,
+			  dash->tx_desc_dma);
+
+	if (dash->hw_dash_ver != RTL_DASH_EP)
+		iounmap(dash->cmac_ioaddr);
+
+	kfree(dash);
+}
+
+void rtl_dash_up(struct rtl_dash *dash)
+{
+	rtl_driver_start(dash);
+	rtl_dash_change_cmac_state(dash, OOB_CMD_CMAC_STOP);
+}
+
+void rtl_dash_down(struct rtl_dash *dash)
+{
+	bitmap_zero(dash->wk.flags, DASH_FLAG_MAX);
+	cancel_work_sync(&dash->wk.work);
+
+	rtl_cmac_disable(dash);
+	rtl_driver_stop(dash);
+}
+
+void rtl_dash_cmac_reset_indicate(struct rtl_dash *dash)
+{
+	rtl_dash_schedule_work(dash, DASH_FLAG_CHECK_CMAC);
+}
+
+void rtl_dash_interrupt(struct rtl_dash *dash)
+{
+	RTL_CMAC_W8(dash, IBIMR0, 0);
+	RTL_CMAC_W8(dash, IBISR0, RTL_CMAC_R8(dash, IBISR0));
+	tasklet_schedule(&dash->tl);
+}
+
diff --git a/drivers/net/ethernet/realtek/r8169_dash.h b/drivers/net/ethernet/realtek/r8169_dash.h
new file mode 100644
index 000000000000..1e9a54a3df1b
--- /dev/null
+++ b/drivers/net/ethernet/realtek/r8169_dash.h
@@ -0,0 +1,22 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#define CMAC_BUF_SIZE		2048
+
+enum rtl_dash_type {
+	RTL_DASH_NONE,
+	RTL_DASH_DP,
+	RTL_DASH_EP,
+	RTL_DASH_FP,
+};
+
+struct rtl_dash;
+
+void rtl_release_dash(struct rtl_dash *dash);
+void rtl_dash_up(struct rtl_dash *dash);
+void rtl_dash_down(struct rtl_dash *dash);
+void rtl_dash_cmac_reset_indicate(struct rtl_dash *dash);
+void rtl_dash_interrupt(struct rtl_dash *dash);
+
+struct rtl_dash *rtl_request_dash(struct rtl8169_private *tp,
+				  struct pci_dev *pci_dev, enum mac_version ver,
+				  void __iomem *mmio_addr);
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index eb56b91fe41b..83da05e5769e 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -33,6 +33,7 @@ 
 
 #include "r8169.h"
 #include "r8169_firmware.h"
+#include "r8169_dash.h"
 
 #define FIRMWARE_8168D_1	"rtl_nic/rtl8168d-1.fw"
 #define FIRMWARE_8168D_2	"rtl_nic/rtl8168d-2.fw"
@@ -348,8 +349,10 @@  enum rtl8125_registers {
 
 enum rtl_register_content {
 	/* InterruptStatusBits */
+	DashCMAC	= 0x8000,
 	SYSErr		= 0x8000,
 	PCSTimeout	= 0x4000,
+	DashIntr	= 0x1000,
 	SWInt		= 0x0100,
 	TxDescUnavail	= 0x0080,
 	RxFIFOOver	= 0x0040,
@@ -586,12 +589,6 @@  enum rtl_flag {
 	RTL_FLAG_MAX
 };
 
-enum rtl_dash_type {
-	RTL_DASH_NONE,
-	RTL_DASH_DP,
-	RTL_DASH_EP,
-};
-
 struct rtl8169_private {
 	void __iomem *mmio_addr;	/* memory map physical address */
 	struct pci_dev *pci_dev;
@@ -628,6 +625,7 @@  struct rtl8169_private {
 
 	const char *fw_name;
 	struct rtl_fw *rtl_fw;
+	struct rtl_dash *rtl_dash;
 
 	u32 ocp_base;
 };
@@ -1117,7 +1115,7 @@  static u32 r8168dp_ocp_read(struct rtl8169_private *tp, u16 reg)
 		RTL_R32(tp, OCPDR) : ~0;
 }
 
-static u32 r8168ep_ocp_read(struct rtl8169_private *tp, u16 reg)
+u32 r8168ep_ocp_read(struct rtl8169_private *tp, u16 reg)
 {
 	return _rtl_eri_read(tp, reg, ERIAR_OOB);
 }
@@ -1130,8 +1128,7 @@  static void r8168dp_ocp_write(struct rtl8169_private *tp, u8 mask, u16 reg,
 	rtl_loop_wait_low(tp, &rtl_ocpar_cond, 100, 20);
 }
 
-static void r8168ep_ocp_write(struct rtl8169_private *tp, u8 mask, u16 reg,
-			      u32 data)
+void r8168ep_ocp_write(struct rtl8169_private *tp, u8 mask, u16 reg, u32 data)
 {
 	_rtl_eri_write(tp, reg, ((u32)mask & 0x0f) << ERIAR_MASK_SHIFT,
 		       data, ERIAR_OOB);
@@ -4555,7 +4552,13 @@  static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
 	if ((status & 0xffff) == 0xffff || !(status & tp->irq_mask))
 		return IRQ_NONE;
 
-	if (unlikely(status & SYSErr)) {
+	if (tp->rtl_dash) {
+		if (status & DashCMAC)
+			rtl_dash_cmac_reset_indicate(tp->rtl_dash);
+
+		if (status & DashIntr)
+			rtl_dash_interrupt(tp->rtl_dash);
+	} else if (unlikely(status & SYSErr)) {
 		rtl8169_pcierr_interrupt(tp->dev);
 		goto out;
 	}
@@ -4651,6 +4654,15 @@  static int r8169_phy_connect(struct rtl8169_private *tp)
 	return 0;
 }
 
+static void rtl8169_dash_release(struct rtl8169_private *tp)
+{
+	if (tp->dash_type > RTL_DASH_DP) {
+		tp->irq_mask &= ~(DashCMAC | DashIntr);
+		rtl_release_dash(tp->rtl_dash);
+		tp->rtl_dash = NULL;
+	}
+}
+
 static void rtl8169_down(struct rtl8169_private *tp)
 {
 	/* Clear all task flags */
@@ -4665,6 +4677,9 @@  static void rtl8169_down(struct rtl8169_private *tp)
 
 	rtl8169_cleanup(tp, true);
 
+	if (tp->rtl_dash)
+		rtl_dash_down(tp->rtl_dash);
+
 	rtl_prepare_power_down(tp);
 }
 
@@ -4678,6 +4693,9 @@  static void rtl8169_up(struct rtl8169_private *tp)
 	set_bit(RTL_FLAG_TASK_ENABLED, tp->wk.flags);
 	rtl_reset_work(tp);
 
+	if (tp->rtl_dash)
+		rtl_dash_up(tp->rtl_dash);
+
 	phy_start(tp->phydev);
 }
 
@@ -4693,6 +4711,7 @@  static int rtl8169_close(struct net_device *dev)
 	rtl8169_rx_clear(tp);
 
 	cancel_work_sync(&tp->wk.work);
+	rtl8169_dash_release(tp);
 
 	free_irq(pci_irq_vector(pdev, 0), tp);
 
@@ -4748,11 +4767,22 @@  static int rtl_open(struct net_device *dev)
 
 	rtl_request_firmware(tp);
 
+	if (tp->dash_type > RTL_DASH_DP) {
+		netdev_info(dev, "DASH enabled\n");
+		tp->rtl_dash = rtl_request_dash(tp, pdev, tp->mac_version,
+						tp->mmio_addr);
+		retval = PTR_ERR_OR_ZERO(tp->rtl_dash);
+		if (retval)
+			goto err_release_fw_2;
+
+		tp->irq_mask |= DashCMAC | DashIntr;
+	}
+
 	irqflags = pci_dev_msi_enabled(pdev) ? IRQF_NO_THREAD : IRQF_SHARED;
 	retval = request_irq(pci_irq_vector(pdev, 0), rtl8169_interrupt,
 			     irqflags, dev->name, tp);
 	if (retval < 0)
-		goto err_release_fw_2;
+		goto err_release_dash;
 
 	retval = r8169_phy_connect(tp);
 	if (retval)
@@ -4768,6 +4798,8 @@  static int rtl_open(struct net_device *dev)
 
 err_free_irq:
 	free_irq(pci_irq_vector(pdev, 0), tp);
+err_release_dash:
+	rtl8169_dash_release(tp);
 err_release_fw_2:
 	rtl_release_firmware(tp);
 	rtl8169_rx_clear(tp);