diff mbox series

[RFC,4/4] r8169: add sysfs for dash

Message ID 20211129101315.16372-385-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 success Errors and warnings before: 5 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: 0 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 success total: 0 errors, 0 warnings, 0 checks, 366 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/tree_selection success Guessing tree name failed - patch did not apply

Commit Message

Hayes Wang Nov. 29, 2021, 10:13 a.m. UTC
Add the sysfs for dash. Then the application could configure the
firmware through them.

Signed-off-by: Hayes Wang <hayeswang@realtek.com>
---
 drivers/net/ethernet/realtek/r8169_dash.c | 131 +++++++++++++++
 drivers/net/ethernet/realtek/r8169_dash.h |   8 +
 drivers/net/ethernet/realtek/r8169_main.c | 188 +++++++++++++++++++++-
 3 files changed, 326 insertions(+), 1 deletion(-)

Comments

Heiner Kallweit Dec. 3, 2021, 3:15 p.m. UTC | #1
On 29.11.2021 11:13, Hayes Wang wrote:
> Add the sysfs for dash. Then the application could configure the
> firmware through them.
> 
> Signed-off-by: Hayes Wang <hayeswang@realtek.com>
> ---
>  drivers/net/ethernet/realtek/r8169_dash.c | 131 +++++++++++++++
>  drivers/net/ethernet/realtek/r8169_dash.h |   8 +
>  drivers/net/ethernet/realtek/r8169_main.c | 188 +++++++++++++++++++++-
>  3 files changed, 326 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/ethernet/realtek/r8169_dash.c b/drivers/net/ethernet/realtek/r8169_dash.c
> index acee7519e9f1..197feb2c4a23 100644
> --- a/drivers/net/ethernet/realtek/r8169_dash.c
> +++ b/drivers/net/ethernet/realtek/r8169_dash.c
> @@ -754,3 +754,134 @@ void rtl_dash_interrupt(struct rtl_dash *dash)
>  	tasklet_schedule(&dash->tl);
>  }
>  
> +int rtl_dash_to_fw(struct rtl_dash *dash, u8 *src, int size)
> +{
> +	int ret;
> +	long t;
> +
> +	if (dash->cmac_state != CMAC_STATE_RUNNING)
> +		return -ENETDOWN;
> +
> +	spin_lock_bh(&dash->cmac_lock);
> +	ret = dash_rx_data(dash, NULL, 0);
> +	spin_unlock_bh(&dash->cmac_lock);
> +	if (ret < 0)
> +		return -EUCLEAN;
> +
> +	reinit_completion(&dash->cmac_tx);
> +	reinit_completion(&dash->fw_ack);
> +
> +	spin_lock_bh(&dash->cmac_lock);
> +	ret = cmac_start_xmit(dash, src, size, false);
> +	spin_unlock_bh(&dash->cmac_lock);
> +
> +	if (ret < 0)
> +		goto err;
> +
> +	t = wait_for_completion_interruptible_timeout(&dash->cmac_tx,
> +						      CMAC_TIMEOUT);
> +	if (!t) {
> +		ret = -ETIMEDOUT;
> +		dev_err(&dash->pdev_cmac->dev, "CMAC tx timeout\n");
> +		goto err;
> +	} else if (t < 0) {
> +		ret = t;
> +		dev_err(&dash->pdev_cmac->dev, "CMAC tx fail %ld\n", t);
> +		goto err;
> +	}
> +
> +	t = wait_for_completion_interruptible_timeout(&dash->fw_ack,
> +						      CMAC_TIMEOUT);
> +	if (!t) {
> +		ret = -ETIMEDOUT;
> +		dev_err(&dash->pdev_cmac->dev, "FW ACK timeout\n");
> +	} else if (t < 0) {
> +		ret = t;
> +		dev_err(&dash->pdev_cmac->dev, "FW ACK fail %ld\n", t);
> +	}
> +
> +err:
> +	return ret;
> +}
> +
> +int rtl_dash_from_fw(struct rtl_dash *dash, u8 *src, int size)
> +{
> +	int ret;
> +	long t;
> +
> +	if (dash->cmac_state != CMAC_STATE_RUNNING)
> +		return -ENETDOWN;
> +
> +	reinit_completion(&dash->cmac_rx);
> +
> +	spin_lock_bh(&dash->cmac_lock);
> +	ret = dash_rx_data(dash, src, size);
> +	spin_unlock_bh(&dash->cmac_lock);
> +
> +	if (ret)
> +		goto out;
> +
> +	t = wait_for_completion_interruptible_timeout(&dash->cmac_rx,
> +						      CMAC_TIMEOUT);
> +	if (!t) {
> +		dev_warn(&dash->pdev_cmac->dev, "CMAC data timeout\n");
> +	} else if (t < 0) {
> +		ret = t;
> +		dev_err(&dash->pdev_cmac->dev, "Wait CMAC data fail %ld\n", t);
> +		goto out;
> +	}
> +
> +	spin_lock_bh(&dash->cmac_lock);
> +	ret = dash_rx_data(dash, src, size);
> +	spin_unlock_bh(&dash->cmac_lock);
> +
> +out:
> +	return ret;
> +}
> +
> +void rtl_dash_set_ap_ready(struct rtl_dash *dash, bool enable)
> +{
> +	struct rtl8169_private *tp = dash->tp;
> +	u32 data;
> +
> +	data = r8168ep_ocp_read(tp, 0x124);
> +	if (enable)
> +		data |= BIT(1);
> +	else
> +		data &= ~BIT(1);
> +	r8168ep_ocp_write(tp, 0x1, 0x124, data);
> +}
> +
> +bool rtl_dash_get_ap_ready(struct rtl_dash *dash)
> +{
> +	struct rtl8169_private *tp = dash->tp;
> +
> +	if (r8168ep_ocp_read(tp, 0x124) & BIT(1))
> +		return true;
> +	else
> +		return false;
> +}
> +
> +ssize_t rtl_dash_info(struct rtl_dash *dash, char *buf)
> +{
> +	struct rtl8169_private *tp = dash->tp;
> +	char *dest = buf;
> +	int size;
> +	u32 data;
> +
> +	data = r8168ep_ocp_read(tp, 0x120);
> +	size = sprintf(dest, "FW_VERSION=0x%08X\n", data);
> +	if (size > 0)
> +		dest += size;
> +	else
> +		return size;
> +
> +	data = r8168ep_ocp_read(tp, 0x174);
> +	size = sprintf(dest, "FW_BUILD=0x%08X\n", data);
> +	if (size > 0)
> +		dest += size;
> +	else
> +		return size;
> +
> +	return strlen(buf) + 1;
> +}
> diff --git a/drivers/net/ethernet/realtek/r8169_dash.h b/drivers/net/ethernet/realtek/r8169_dash.h
> index 1e9a54a3df1b..b33f3adeef13 100644
> --- a/drivers/net/ethernet/realtek/r8169_dash.h
> +++ b/drivers/net/ethernet/realtek/r8169_dash.h
> @@ -16,6 +16,14 @@ 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);
> +void rtl_dash_set_ap_ready(struct rtl_dash *dash, bool enable);
> +
> +int rtl_dash_to_fw(struct rtl_dash *dash, u8 *src, int size);
> +int rtl_dash_from_fw(struct rtl_dash *dash, u8 *src, int size);
> +
> +bool rtl_dash_get_ap_ready(struct rtl_dash *dash);
> +
> +ssize_t rtl_dash_info(struct rtl_dash *dash, char *buf);
>  
>  struct rtl_dash *rtl_request_dash(struct rtl8169_private *tp,
>  				  struct pci_dev *pci_dev, enum mac_version ver,
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index 83da05e5769e..4c8439d3ae4d 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
> @@ -4654,6 +4654,184 @@ static int r8169_phy_connect(struct rtl8169_private *tp)
>  	return 0;
>  }
>  
> +static ssize_t
> +information_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	struct net_device *net = to_net_dev(dev);
> +	struct rtl8169_private *tp = netdev_priv(net);
> +	ssize_t ret;
> +
> +	if (rtnl_lock_killable())
> +		return -EINTR;
> +
> +	ret = rtl_dash_info(tp->rtl_dash, buf);
> +
> +	rtnl_unlock();
> +
> +	return ret;
> +}
> +
> +static DEVICE_ATTR_RO(information);
> +
> +static ssize_t
> +ap_ready_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	struct net_device *net = to_net_dev(dev);
> +	struct rtl8169_private *tp = netdev_priv(net);
> +	bool enable;
> +
> +	if (rtnl_lock_killable())
> +		return -EINTR;
> +
> +	enable = rtl_dash_get_ap_ready(tp->rtl_dash);
> +
> +	rtnl_unlock();
> +
> +	if (enable)
> +		strcat(buf, "enable\n");
> +	else
> +		strcat(buf, "disable\n");
> +
> +	return (strlen(buf) + 1);
> +}
> +
> +static ssize_t ap_ready_store(struct device *dev, struct device_attribute *attr,
> +			      const char *buf, size_t count)
> +{
> +	struct net_device *net = to_net_dev(dev);
> +	struct rtl8169_private *tp = netdev_priv(net);
> +	ssize_t len = strlen(buf);
> +	bool enable;
> +
> +	if (buf[len - 1] <= ' ')
> +		len--;
> +
> +	/* strlen("enable") = 6, and strlen("disable") = 7 */
> +	if (len != 6 && len != 7)
> +		return -EINVAL;
> +
> +	if (len == 6 && !strncmp(buf, "enable", 6))
> +		enable = true;
> +	else if (len == 7 && !strncmp(buf, "disable", 7))
> +		enable = false;
> +	else
> +		return -EINVAL;
> +
> +	if (rtnl_lock_killable())
> +		return -EINTR;
> +
> +	rtl_dash_set_ap_ready(tp->rtl_dash, enable);
> +
> +	rtnl_unlock();
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RW(ap_ready);
> +
> +static struct attribute *rtl_dash_attrs[] = {
> +	&dev_attr_ap_ready.attr,
> +	&dev_attr_information.attr,
> +	NULL
> +};
> +
> +static ssize_t cmac_data_write(struct file *fp, struct kobject *kobj,
> +			       struct bin_attribute *attr, char *buf,
> +			       loff_t offset, size_t size)
> +{
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct net_device *net = to_net_dev(dev);
> +	struct rtl8169_private *tp = netdev_priv(net);
> +	int ret;
> +
> +	if (IS_ERR_OR_NULL(tp->rtl_dash))
> +		return -ENODEV;
> +
> +	if (size > CMAC_BUF_SIZE)
> +		return -EFBIG;
> +
> +	if (offset)
> +		return -EINVAL;
> +
> +	if (rtnl_lock_killable())
> +		return -EINTR;
> +
> +	ret = rtl_dash_to_fw(tp->rtl_dash, buf, size);
> +
> +	rtnl_unlock();
> +
> +	return ret;
> +}
> +
> +static ssize_t cmac_data_read(struct file *fp, struct kobject *kobj,
> +			      struct bin_attribute *attr, char *buf,
> +			      loff_t offset, size_t size)
> +{
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct net_device *net = to_net_dev(dev);
> +	struct rtl8169_private *tp = netdev_priv(net);
> +	int ret;
> +
> +	if (IS_ERR_OR_NULL(tp->rtl_dash))
> +		return -ENODEV;
> +
> +	if (offset)
> +		return -EINVAL;
> +
> +	if (rtnl_lock_killable())
> +		return -EINTR;
> +
> +	ret = rtl_dash_from_fw(tp->rtl_dash, buf, size);
> +
> +	rtnl_unlock();
> +
> +	return ret;
> +}
> +
> +static BIN_ATTR_RW(cmac_data, CMAC_BUF_SIZE);
> +
> +static struct bin_attribute *rtl_dash_bin_attrs[] = {
> +	&bin_attr_cmac_data,
> +	NULL
> +};
> +
> +static umode_t is_dash_visible(struct kobject *kobj, struct attribute *attr,
> +			       int n)
> +{
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct net_device *net = to_net_dev(dev);
> +	struct rtl8169_private *tp = netdev_priv(net);
> +
> +	if (IS_ERR_OR_NULL(tp->rtl_dash))
> +		return 0;
> +
> +	if (attr == &dev_attr_information.attr)
> +		return 0440;
> +
> +	return 0660;
> +}
> +
> +static umode_t is_dash_bin_visible(struct kobject *kobj,
> +				   struct bin_attribute *attr, int n)
> +{
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct net_device *net = to_net_dev(dev);
> +	struct rtl8169_private *tp = netdev_priv(net);
> +
> +	if (IS_ERR_OR_NULL(tp->rtl_dash))
> +		return 0;
> +
> +	return 0660;
> +}
> +
> +static struct attribute_group rtl_dash_grp = {
> +	.name = "dash",
> +	.is_visible	= is_dash_visible,
> +	.attrs		= rtl_dash_attrs,
> +	.is_bin_visible = is_dash_bin_visible,
> +	.bin_attrs = rtl_dash_bin_attrs,
> +};
> +
>  static void rtl8169_dash_release(struct rtl8169_private *tp)
>  {
>  	if (tp->dash_type > RTL_DASH_DP) {
> @@ -4717,6 +4895,8 @@ static int rtl8169_close(struct net_device *dev)
>  
>  	phy_disconnect(tp->phydev);
>  
> +	sysfs_remove_group(&dev->dev.kobj, &rtl_dash_grp);
> +
>  	dma_free_coherent(&pdev->dev, R8169_RX_RING_BYTES, tp->RxDescArray,
>  			  tp->RxPhyAddr);
>  	dma_free_coherent(&pdev->dev, R8169_TX_RING_BYTES, tp->TxDescArray,
> @@ -4775,6 +4955,10 @@ static int rtl_open(struct net_device *dev)
>  		if (retval)
>  			goto err_release_fw_2;
>  
> +		retval = sysfs_create_group(&dev->dev.kobj, &rtl_dash_grp);
> +		if (retval < 0)
> +			goto err_release_dash;
> +
>  		tp->irq_mask |= DashCMAC | DashIntr;
>  	}
>  
> @@ -4782,7 +4966,7 @@ static int rtl_open(struct net_device *dev)
>  	retval = request_irq(pci_irq_vector(pdev, 0), rtl8169_interrupt,
>  			     irqflags, dev->name, tp);
>  	if (retval < 0)
> -		goto err_release_dash;
> +		goto err_remove_group;
>  
>  	retval = r8169_phy_connect(tp);
>  	if (retval)
> @@ -4798,6 +4982,8 @@ static int rtl_open(struct net_device *dev)
>  
>  err_free_irq:
>  	free_irq(pci_irq_vector(pdev, 0), tp);
> +err_remove_group:
> +	sysfs_remove_group(&dev->dev.kobj, &rtl_dash_grp);
>  err_release_dash:
>  	rtl8169_dash_release(tp);
>  err_release_fw_2:
> 

With regard to sysfs usage:
- attributes should be documented under /Documentation/ABI/testing
- attributes should be defined statically (driver.dev_groups instead
  of sysfs_create_group)
- for printing info there's sysfs_emit()
- is really RTNL needed? Or would a lighter mutex do?
Hayes Wang Dec. 7, 2021, 6:53 a.m. UTC | #2
Heiner Kallweit <hkallweit1@gmail.com>
> Sent: Friday, December 3, 2021 11:15 PM
[...]
> With regard to sysfs usage:
> - attributes should be documented under /Documentation/ABI/testing
> - attributes should be defined statically (driver.dev_groups instead
>   of sysfs_create_group)
> - for printing info there's sysfs_emit()
> - is really RTNL needed? Or would a lighter mutex do?

In addition to protect the critical section, RTNL is used to avoid
calling close() before CMAC is finished. The transfer of CMAC
may contain several steps. And close() would disable CMAC.
I don't wish the CMAC stays at strange state. It may influence
the firmware or hardware. Besides, I find the original driver only
use RTNL to protect critical section. Is there a better way for it?

Best Regards,
Hayes
Heiner Kallweit Dec. 7, 2021, 7:38 a.m. UTC | #3
On 07.12.2021 07:53, Hayes Wang wrote:
> Heiner Kallweit <hkallweit1@gmail.com>
>> Sent: Friday, December 3, 2021 11:15 PM
> [...]
>> With regard to sysfs usage:
>> - attributes should be documented under /Documentation/ABI/testing
>> - attributes should be defined statically (driver.dev_groups instead
>>   of sysfs_create_group)
>> - for printing info there's sysfs_emit()
>> - is really RTNL needed? Or would a lighter mutex do?
> 
> In addition to protect the critical section, RTNL is used to avoid
> calling close() before CMAC is finished. The transfer of CMAC
> may contain several steps. And close() would disable CMAC.
> I don't wish the CMAC stays at strange state. It may influence
> the firmware or hardware. Besides, I find the original driver only
> use RTNL to protect critical section. Is there a better way for it?
> 

The main issue I see is that you call rtl_dash_from_fw() under RTNL,
and this function may sleep up to 5s. Holding a system-wide mutex
for that long isn't too nice.

In rtl_dash_info() you just print FW version and build number.
Wouldn't it be sufficient to print this info once to syslog
instead of exporting it via sysfs?

> Best Regards,
> Hayes
>
Hayes Wang Dec. 7, 2021, 8:20 a.m. UTC | #4
Heiner Kallweit <hkallweit1@gmail.com>
> Sent: Tuesday, December 7, 2021 3:38 PM
[...]
> > In addition to protect the critical section, RTNL is used to avoid
> > calling close() before CMAC is finished. The transfer of CMAC
> > may contain several steps. And close() would disable CMAC.
> > I don't wish the CMAC stays at strange state. It may influence
> > the firmware or hardware. Besides, I find the original driver only
> > use RTNL to protect critical section. Is there a better way for it?
> >
> 
> The main issue I see is that you call rtl_dash_from_fw() under RTNL,
> and this function may sleep up to 5s. Holding a system-wide mutex
> for that long isn't too nice.

I would think if there is a better way to replace current one.
Thanks.

> In rtl_dash_info() you just print FW version and build number.
> Wouldn't it be sufficient to print this info once to syslog
> instead of exporting it via sysfs?

It is the information which our user space tool need.
I think it would be replaced with devlink param.

Best Regards,
Hayes
diff mbox series

Patch

diff --git a/drivers/net/ethernet/realtek/r8169_dash.c b/drivers/net/ethernet/realtek/r8169_dash.c
index acee7519e9f1..197feb2c4a23 100644
--- a/drivers/net/ethernet/realtek/r8169_dash.c
+++ b/drivers/net/ethernet/realtek/r8169_dash.c
@@ -754,3 +754,134 @@  void rtl_dash_interrupt(struct rtl_dash *dash)
 	tasklet_schedule(&dash->tl);
 }
 
+int rtl_dash_to_fw(struct rtl_dash *dash, u8 *src, int size)
+{
+	int ret;
+	long t;
+
+	if (dash->cmac_state != CMAC_STATE_RUNNING)
+		return -ENETDOWN;
+
+	spin_lock_bh(&dash->cmac_lock);
+	ret = dash_rx_data(dash, NULL, 0);
+	spin_unlock_bh(&dash->cmac_lock);
+	if (ret < 0)
+		return -EUCLEAN;
+
+	reinit_completion(&dash->cmac_tx);
+	reinit_completion(&dash->fw_ack);
+
+	spin_lock_bh(&dash->cmac_lock);
+	ret = cmac_start_xmit(dash, src, size, false);
+	spin_unlock_bh(&dash->cmac_lock);
+
+	if (ret < 0)
+		goto err;
+
+	t = wait_for_completion_interruptible_timeout(&dash->cmac_tx,
+						      CMAC_TIMEOUT);
+	if (!t) {
+		ret = -ETIMEDOUT;
+		dev_err(&dash->pdev_cmac->dev, "CMAC tx timeout\n");
+		goto err;
+	} else if (t < 0) {
+		ret = t;
+		dev_err(&dash->pdev_cmac->dev, "CMAC tx fail %ld\n", t);
+		goto err;
+	}
+
+	t = wait_for_completion_interruptible_timeout(&dash->fw_ack,
+						      CMAC_TIMEOUT);
+	if (!t) {
+		ret = -ETIMEDOUT;
+		dev_err(&dash->pdev_cmac->dev, "FW ACK timeout\n");
+	} else if (t < 0) {
+		ret = t;
+		dev_err(&dash->pdev_cmac->dev, "FW ACK fail %ld\n", t);
+	}
+
+err:
+	return ret;
+}
+
+int rtl_dash_from_fw(struct rtl_dash *dash, u8 *src, int size)
+{
+	int ret;
+	long t;
+
+	if (dash->cmac_state != CMAC_STATE_RUNNING)
+		return -ENETDOWN;
+
+	reinit_completion(&dash->cmac_rx);
+
+	spin_lock_bh(&dash->cmac_lock);
+	ret = dash_rx_data(dash, src, size);
+	spin_unlock_bh(&dash->cmac_lock);
+
+	if (ret)
+		goto out;
+
+	t = wait_for_completion_interruptible_timeout(&dash->cmac_rx,
+						      CMAC_TIMEOUT);
+	if (!t) {
+		dev_warn(&dash->pdev_cmac->dev, "CMAC data timeout\n");
+	} else if (t < 0) {
+		ret = t;
+		dev_err(&dash->pdev_cmac->dev, "Wait CMAC data fail %ld\n", t);
+		goto out;
+	}
+
+	spin_lock_bh(&dash->cmac_lock);
+	ret = dash_rx_data(dash, src, size);
+	spin_unlock_bh(&dash->cmac_lock);
+
+out:
+	return ret;
+}
+
+void rtl_dash_set_ap_ready(struct rtl_dash *dash, bool enable)
+{
+	struct rtl8169_private *tp = dash->tp;
+	u32 data;
+
+	data = r8168ep_ocp_read(tp, 0x124);
+	if (enable)
+		data |= BIT(1);
+	else
+		data &= ~BIT(1);
+	r8168ep_ocp_write(tp, 0x1, 0x124, data);
+}
+
+bool rtl_dash_get_ap_ready(struct rtl_dash *dash)
+{
+	struct rtl8169_private *tp = dash->tp;
+
+	if (r8168ep_ocp_read(tp, 0x124) & BIT(1))
+		return true;
+	else
+		return false;
+}
+
+ssize_t rtl_dash_info(struct rtl_dash *dash, char *buf)
+{
+	struct rtl8169_private *tp = dash->tp;
+	char *dest = buf;
+	int size;
+	u32 data;
+
+	data = r8168ep_ocp_read(tp, 0x120);
+	size = sprintf(dest, "FW_VERSION=0x%08X\n", data);
+	if (size > 0)
+		dest += size;
+	else
+		return size;
+
+	data = r8168ep_ocp_read(tp, 0x174);
+	size = sprintf(dest, "FW_BUILD=0x%08X\n", data);
+	if (size > 0)
+		dest += size;
+	else
+		return size;
+
+	return strlen(buf) + 1;
+}
diff --git a/drivers/net/ethernet/realtek/r8169_dash.h b/drivers/net/ethernet/realtek/r8169_dash.h
index 1e9a54a3df1b..b33f3adeef13 100644
--- a/drivers/net/ethernet/realtek/r8169_dash.h
+++ b/drivers/net/ethernet/realtek/r8169_dash.h
@@ -16,6 +16,14 @@  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);
+void rtl_dash_set_ap_ready(struct rtl_dash *dash, bool enable);
+
+int rtl_dash_to_fw(struct rtl_dash *dash, u8 *src, int size);
+int rtl_dash_from_fw(struct rtl_dash *dash, u8 *src, int size);
+
+bool rtl_dash_get_ap_ready(struct rtl_dash *dash);
+
+ssize_t rtl_dash_info(struct rtl_dash *dash, char *buf);
 
 struct rtl_dash *rtl_request_dash(struct rtl8169_private *tp,
 				  struct pci_dev *pci_dev, enum mac_version ver,
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 83da05e5769e..4c8439d3ae4d 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -4654,6 +4654,184 @@  static int r8169_phy_connect(struct rtl8169_private *tp)
 	return 0;
 }
 
+static ssize_t
+information_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct net_device *net = to_net_dev(dev);
+	struct rtl8169_private *tp = netdev_priv(net);
+	ssize_t ret;
+
+	if (rtnl_lock_killable())
+		return -EINTR;
+
+	ret = rtl_dash_info(tp->rtl_dash, buf);
+
+	rtnl_unlock();
+
+	return ret;
+}
+
+static DEVICE_ATTR_RO(information);
+
+static ssize_t
+ap_ready_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct net_device *net = to_net_dev(dev);
+	struct rtl8169_private *tp = netdev_priv(net);
+	bool enable;
+
+	if (rtnl_lock_killable())
+		return -EINTR;
+
+	enable = rtl_dash_get_ap_ready(tp->rtl_dash);
+
+	rtnl_unlock();
+
+	if (enable)
+		strcat(buf, "enable\n");
+	else
+		strcat(buf, "disable\n");
+
+	return (strlen(buf) + 1);
+}
+
+static ssize_t ap_ready_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct net_device *net = to_net_dev(dev);
+	struct rtl8169_private *tp = netdev_priv(net);
+	ssize_t len = strlen(buf);
+	bool enable;
+
+	if (buf[len - 1] <= ' ')
+		len--;
+
+	/* strlen("enable") = 6, and strlen("disable") = 7 */
+	if (len != 6 && len != 7)
+		return -EINVAL;
+
+	if (len == 6 && !strncmp(buf, "enable", 6))
+		enable = true;
+	else if (len == 7 && !strncmp(buf, "disable", 7))
+		enable = false;
+	else
+		return -EINVAL;
+
+	if (rtnl_lock_killable())
+		return -EINTR;
+
+	rtl_dash_set_ap_ready(tp->rtl_dash, enable);
+
+	rtnl_unlock();
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(ap_ready);
+
+static struct attribute *rtl_dash_attrs[] = {
+	&dev_attr_ap_ready.attr,
+	&dev_attr_information.attr,
+	NULL
+};
+
+static ssize_t cmac_data_write(struct file *fp, struct kobject *kobj,
+			       struct bin_attribute *attr, char *buf,
+			       loff_t offset, size_t size)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct net_device *net = to_net_dev(dev);
+	struct rtl8169_private *tp = netdev_priv(net);
+	int ret;
+
+	if (IS_ERR_OR_NULL(tp->rtl_dash))
+		return -ENODEV;
+
+	if (size > CMAC_BUF_SIZE)
+		return -EFBIG;
+
+	if (offset)
+		return -EINVAL;
+
+	if (rtnl_lock_killable())
+		return -EINTR;
+
+	ret = rtl_dash_to_fw(tp->rtl_dash, buf, size);
+
+	rtnl_unlock();
+
+	return ret;
+}
+
+static ssize_t cmac_data_read(struct file *fp, struct kobject *kobj,
+			      struct bin_attribute *attr, char *buf,
+			      loff_t offset, size_t size)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct net_device *net = to_net_dev(dev);
+	struct rtl8169_private *tp = netdev_priv(net);
+	int ret;
+
+	if (IS_ERR_OR_NULL(tp->rtl_dash))
+		return -ENODEV;
+
+	if (offset)
+		return -EINVAL;
+
+	if (rtnl_lock_killable())
+		return -EINTR;
+
+	ret = rtl_dash_from_fw(tp->rtl_dash, buf, size);
+
+	rtnl_unlock();
+
+	return ret;
+}
+
+static BIN_ATTR_RW(cmac_data, CMAC_BUF_SIZE);
+
+static struct bin_attribute *rtl_dash_bin_attrs[] = {
+	&bin_attr_cmac_data,
+	NULL
+};
+
+static umode_t is_dash_visible(struct kobject *kobj, struct attribute *attr,
+			       int n)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct net_device *net = to_net_dev(dev);
+	struct rtl8169_private *tp = netdev_priv(net);
+
+	if (IS_ERR_OR_NULL(tp->rtl_dash))
+		return 0;
+
+	if (attr == &dev_attr_information.attr)
+		return 0440;
+
+	return 0660;
+}
+
+static umode_t is_dash_bin_visible(struct kobject *kobj,
+				   struct bin_attribute *attr, int n)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct net_device *net = to_net_dev(dev);
+	struct rtl8169_private *tp = netdev_priv(net);
+
+	if (IS_ERR_OR_NULL(tp->rtl_dash))
+		return 0;
+
+	return 0660;
+}
+
+static struct attribute_group rtl_dash_grp = {
+	.name = "dash",
+	.is_visible	= is_dash_visible,
+	.attrs		= rtl_dash_attrs,
+	.is_bin_visible = is_dash_bin_visible,
+	.bin_attrs = rtl_dash_bin_attrs,
+};
+
 static void rtl8169_dash_release(struct rtl8169_private *tp)
 {
 	if (tp->dash_type > RTL_DASH_DP) {
@@ -4717,6 +4895,8 @@  static int rtl8169_close(struct net_device *dev)
 
 	phy_disconnect(tp->phydev);
 
+	sysfs_remove_group(&dev->dev.kobj, &rtl_dash_grp);
+
 	dma_free_coherent(&pdev->dev, R8169_RX_RING_BYTES, tp->RxDescArray,
 			  tp->RxPhyAddr);
 	dma_free_coherent(&pdev->dev, R8169_TX_RING_BYTES, tp->TxDescArray,
@@ -4775,6 +4955,10 @@  static int rtl_open(struct net_device *dev)
 		if (retval)
 			goto err_release_fw_2;
 
+		retval = sysfs_create_group(&dev->dev.kobj, &rtl_dash_grp);
+		if (retval < 0)
+			goto err_release_dash;
+
 		tp->irq_mask |= DashCMAC | DashIntr;
 	}
 
@@ -4782,7 +4966,7 @@  static int rtl_open(struct net_device *dev)
 	retval = request_irq(pci_irq_vector(pdev, 0), rtl8169_interrupt,
 			     irqflags, dev->name, tp);
 	if (retval < 0)
-		goto err_release_dash;
+		goto err_remove_group;
 
 	retval = r8169_phy_connect(tp);
 	if (retval)
@@ -4798,6 +4982,8 @@  static int rtl_open(struct net_device *dev)
 
 err_free_irq:
 	free_irq(pci_irq_vector(pdev, 0), tp);
+err_remove_group:
+	sysfs_remove_group(&dev->dev.kobj, &rtl_dash_grp);
 err_release_dash:
 	rtl8169_dash_release(tp);
 err_release_fw_2: