[PATCH v3 3/5] net: Let the active time stamping layer be selectable.

Köry Maincent
2023-03-08T14:02:33Z

From: Richard Cochran <richardcochran@gmail.com>

Add the ETHTOOL_SET_PTP ethtool ioctl, and add checks in the ioctl and time
stamping paths to respect the currently selected time stamping layer.

Add a preferred-timestamp devicetree binding to select the preferred
hardware timestamp layer between PHY and MAC. The choice of using
devicetree binding has been made as the PTP precision and quality depends
of external things, like adjustable clock, or the lack of a temperature
compensated crystal or specific features. Even if the preferred timestamp
is a configuration it is hardly related to the design oh the board.

Signed-off-by: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
---

Notes:
Changes in v2:
- Move selected_timestamping_layer introduction in this patch.
- Replace strmcmp by sysfs_streq.
- Use the PHY timestamp only if available.

Changes in v3:
- Added a devicetree binding to select the preferred timestamp
- Replace the way to select timestamp through ethtool instead of sysfs
You can test it with the ethtool source on branch feature_ptp of:
https://github.com/kmaincent/ethtool

Documentation/networking/ethtool-netlink.rst | 1 +
drivers/net/phy/phy_device.c | 34 ++++++++++++++++
include/linux/netdevice.h | 6 +++
include/uapi/linux/ethtool.h | 1 +
net/core/dev_ioctl.c | 43 ++++++++++++++++++--
net/core/timestamping.c | 6 +++
net/ethtool/common.c | 16 ++++++--
net/ethtool/ioctl.c | 41 ++++++++++++++-----
8 files changed, 131 insertions(+), 17 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index ca8e1182bc8e..4a1153dd4859 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -1789,4 +1789,5 @@ are netlink only.
n/a ``ETHTOOL_MSG_MODULE_SET``
``ETHTOOL_LIST_PTP`` n/a
``ETHTOOL_GET_PTP`` n/a
+ ``ETHTOOL_SET_PTP`` n/a
=================================== =====================================
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 8cff61dbc4b5..5e120452a358 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -34,6 +34,9 @@
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>
+#include <linux/of.h>
+#include <uapi/linux/net_tstamp.h>
+

MODULE_DESCRIPTION("PHY library");
MODULE_AUTHOR("Andy Fleming");
@@ -1378,6 +1381,34 @@ int phy_sfp_probe(struct phy_device *phydev,
}
EXPORT_SYMBOL(phy_sfp_probe);

+void of_set_timestamp(struct net_device *netdev, struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ const struct ethtool_ops *ops = netdev->ethtool_ops;
+ const char *s;
+ enum timestamping_layer ts_layer = 0;
+
+ if (phy_has_hwtstamp(phydev))
+ ts_layer = PHY_TIMESTAMPING;
+ else if (ops->get_ts_info)
+ ts_layer = MAC_TIMESTAMPING;
+
+ if (of_property_read_string(node, "preferred-timestamp", &s))
+ goto out;
+
+ if (!s)
+ goto out;
+
+ if (phy_has_hwtstamp(phydev) && !strcmp(s, "phy"))
+ ts_layer = PHY_TIMESTAMPING;
+
+ if (ops->get_ts_info && !strcmp(s, "mac"))
+ ts_layer = MAC_TIMESTAMPING;
+
+out:
+ netdev->selected_timestamping_layer = ts_layer;
+}
+
/**
* phy_attach_direct - attach a network device to a given PHY device pointer
* @dev: network device to attach
@@ -1451,6 +1482,8 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,

phydev->phy_link_change = phy_link_change;
if (dev) {
+ of_set_timestamp(dev, phydev);
+
phydev->attached_dev = dev;
dev->phydev = phydev;

@@ -1762,6 +1795,7 @@ void phy_detach(struct phy_device *phydev)

phy_suspend(phydev);
if (dev) {
+ dev->selected_timestamping_layer = MAC_TIMESTAMPING;
phydev->attached_dev->phydev = NULL;
phydev->attached_dev = NULL;
}
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index ba2bd604359d..d9a1c12fc43c 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -47,6 +47,7 @@
#include <uapi/linux/netdevice.h>
#include <uapi/linux/if_bonding.h>
#include <uapi/linux/pkt_cls.h>
+#include <uapi/linux/net_tstamp.h>
#include <linux/hashtable.h>
#include <linux/rbtree.h>
#include <net/net_trackers.h>
@@ -1981,6 +1982,9 @@ enum netdev_ml_priv_type {
*
* @threaded: napi threaded mode is enabled
*
+ * @selected_timestamping_layer: Tracks whether the MAC or the PHY
+ * performs packet time stamping.
+ *
* @net_notifier_list: List of per-net netdev notifier block
* that follow this device when it is moved
* to another network namespace.
@@ -2339,6 +2343,8 @@ struct net_device {
unsigned wol_enabled:1;
unsigned threaded:1;

+ enum timestamping_layer selected_timestamping_layer;
+
struct list_head net_notifier_list;

#if IS_ENABLED(CONFIG_MACSEC)
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 56cf24388290..d3a41b6e9eb0 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -1631,6 +1631,7 @@ enum ethtool_fec_config_bits {
#define ETHTOOL_SFECPARAM 0x00000051 /* Set FEC settings */
#define ETHTOOL_LIST_PTP 0x00000052 /* List PTP providers */
#define ETHTOOL_GET_PTP 0x00000053 /* Get current PTP provider */
+#define ETHTOOL_SET_PTP 0x00000054 /* Set PTP provider */

/* compatibility with older code */
#define SPARC_ETH_GSET ETHTOOL_GSET
diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index 7674bb9f3076..a75cff331495 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -262,6 +262,42 @@ static int dev_eth_ioctl(struct net_device *dev,
return err;
}

+static int dev_hwtstamp_ioctl(struct net_device *dev,
+ struct ifreq *ifr, unsigned int cmd)
+{
+ const struct net_device_ops *ops = dev->netdev_ops;
+ int err;
+
+ err = dsa_ndo_eth_ioctl(dev, ifr, cmd);
+ if (err == 0 || err != -EOPNOTSUPP)
+ return err;
+
+ if (!netif_device_present(dev))
+ return -ENODEV;
+
+ switch (dev->selected_timestamping_layer) {
+ case MAC_TIMESTAMPING:
+ if (ops->ndo_do_ioctl == phy_do_ioctl) {
+ /* Some drivers set .ndo_do_ioctl to phy_do_ioctl. */
+ err = -EOPNOTSUPP;
+ } else {
+ err = ops->ndo_eth_ioctl(dev, ifr, cmd);
+ }
+ break;
+
+ case PHY_TIMESTAMPING:
+ if (phy_has_hwtstamp(dev->phydev)) {
+ err = phy_mii_ioctl(dev->phydev, ifr, cmd);
+ } else {
+ err = -ENODEV;
+ WARN_ON(1);
+ }
+ break;
+ }
+
+ return err;
+}
+
static int dev_siocbond(struct net_device *dev,
struct ifreq *ifr, unsigned int cmd)
{
@@ -397,6 +433,9 @@ static int dev_ifsioc(struct net *net, struct ifreq *ifr, void __user *data,
return err;
fallthrough;

+ case SIOCGHWTSTAMP:
+ return dev_hwtstamp_ioctl(dev, ifr, cmd);
+
/*
* Unknown or private ioctl
*/
@@ -407,9 +446,7 @@ static int dev_ifsioc(struct net *net, struct ifreq *ifr, void __user *data,

if (cmd == SIOCGMIIPHY ||
cmd == SIOCGMIIREG ||
- cmd == SIOCSMIIREG ||
- cmd == SIOCSHWTSTAMP ||
- cmd == SIOCGHWTSTAMP) {
+ cmd == SIOCSMIIREG) {
err = dev_eth_ioctl(dev, ifr, cmd);
} else if (cmd == SIOCBONDENSLAVE ||
cmd == SIOCBONDRELEASE ||
diff --git a/net/core/timestamping.c b/net/core/timestamping.c
index 04840697fe79..31c3142787b7 100644
--- a/net/core/timestamping.c
+++ b/net/core/timestamping.c
@@ -28,6 +28,9 @@ void skb_clone_tx_timestamp(struct sk_buff *skb)
if (!skb->sk)
return;

+ if (skb->dev->selected_timestamping_layer != PHY_TIMESTAMPING)
+ return;
+
type = classify(skb);
if (type == PTP_CLASS_NONE)
return;
@@ -50,6 +53,9 @@ bool skb_defer_rx_timestamp(struct sk_buff *skb)
if (!skb->dev || !skb->dev->phydev || !skb->dev->phydev->mii_ts)
return false;

+ if (skb->dev->selected_timestamping_layer != PHY_TIMESTAMPING)
+ return false;
+
if (skb_headroom(skb) < ETH_HLEN)
return false;

diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 64a7e05cf2c2..e55e70bdbb3c 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -548,10 +548,18 @@ int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info)
memset(info, 0, sizeof(*info));
info->cmd = ETHTOOL_GET_TS_INFO;

- if (phy_has_tsinfo(phydev))
- return phy_ts_info(phydev, info);
- if (ops->get_ts_info)
- return ops->get_ts_info(dev, info);
+ switch (dev->selected_timestamping_layer) {
+ case MAC_TIMESTAMPING:
+ if (ops->get_ts_info)
+ return ops->get_ts_info(dev, info);
+ break;
+
+ case PHY_TIMESTAMPING:
+ if (phy_has_tsinfo(phydev))
+ return phy_ts_info(phydev, info);
+ WARN_ON(1);
+ return -ENODEV;
+ }

info->so_timestamping = SOF_TIMESTAMPING_RX_SOFTWARE |
SOF_TIMESTAMPING_SOFTWARE;
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index d8a0a5d991e0..85a074bef17d 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -2343,17 +2343,8 @@ static int ethtool_get_ptp(struct net_device *dev, void __user *useraddr)
{
struct ethtool_value edata = {
.cmd = ETHTOOL_GET_PTP,
- .data = 0,
+ .data = dev->selected_timestamping_layer,
};
- struct phy_device *phydev = dev->phydev;
- const struct ethtool_ops *ops = dev->ethtool_ops;
-
- if (phy_has_tsinfo(phydev))
- edata.data = PHY_TIMESTAMPING;
- else if (ops->get_ts_info)
- edata.data = MAC_TIMESTAMPING;
- else
- return -EOPNOTSUPP;

if (copy_to_user(useraddr, &edata, sizeof(edata)))
return -EFAULT;
@@ -2361,6 +2352,33 @@ static int ethtool_get_ptp(struct net_device *dev, void __user *useraddr)
return 0;
}

+static int ethtool_set_ptp(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_value edata;
+ enum timestamping_layer flavor;
+
+ if (copy_from_user(&edata, useraddr, sizeof(edata)))
+ return -EFAULT;
+
+ flavor = edata.data;
+
+ if (!dev->phydev)
+ return 0;
+
+ if (dev->selected_timestamping_layer != flavor) {
+ const struct net_device_ops *ops = dev->netdev_ops;
+ struct ifreq ifr = {0};
+
+ /* Disable time stamping in the current layer. */
+ if (netif_device_present(dev) && ops->ndo_eth_ioctl)
+ ops->ndo_eth_ioctl(dev, &ifr, SIOCSHWTSTAMP);
+
+ dev->selected_timestamping_layer = flavor;
+ }
+
+ return 0;
+}
+
int ethtool_get_module_info_call(struct net_device *dev,
struct ethtool_modinfo *modinfo)
{
@@ -3047,6 +3065,9 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
case ETHTOOL_GET_PTP:
rc = ethtool_get_ptp(dev, useraddr);
break;
+ case ETHTOOL_SET_PTP:
+ rc = ethtool_set_ptp(dev, useraddr);
+ break;
default:
rc = -EOPNOTSUPP;
}
--
2.25.1