[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250729104528.1984928-1-arkadiusz.kubalewski@intel.com>
Date: Tue, 29 Jul 2025 12:45:28 +0200
From: Arkadiusz Kubalewski <arkadiusz.kubalewski@...el.com>
To: anthony.l.nguyen@...el.com,
przemyslaw.kitszel@...el.com,
andrew+netdev@...n.ch,
davem@...emloft.net,
edumazet@...gle.com,
kuba@...nel.org,
pabeni@...hat.com,
horms@...nel.org,
sdf@...ichev.me,
almasrymina@...gle.com,
asml.silence@...il.com,
leitao@...ian.org,
kuniyu@...gle.com
Cc: linux-kernel@...r.kernel.org,
intel-wired-lan@...ts.osuosl.org,
netdev@...r.kernel.org,
Arkadiusz Kubalewski <arkadiusz.kubalewski@...el.com>
Subject: [RFC PATCH] net: add net-device TX clock source selection framework
Add support for user-space control over network device transmit clock
sources through a new sysfs interface.
A network device may support multiple TX clock sources (OCXO, SyncE
reference, external reference clocks) which are critical for
time-sensitive networking applications and synchronization protocols.
This patch introduces:
1. Core TX clock framework (net/core/tx_clk.c):
- per net-device clock source registration and management
- sysfs interface under /sys/class/net/<device>/tx_clk/
- thread-safe clock switching by using mutex locking
2. Generic netdev integration:
- new netdev_tx_clk_ops structure for driver callbacks
- TX clock list and kobject directory in struct net_device
- registration/cleanup functions for driver use
3. Intel ICE driver implementation:
- support for E825 series network cards
- three clock sources: OCXO (default), SyncE_ref, ext_ref
- per-PF clock state management
4. Kconfig option NET_TX_CLK:
- optional feature + user documentation
User interface:
- Read /sys/class/net/<device>/tx_clk/<clock_name> to get status (0/1)
- Write "1" to switch to that clock source
- Writing "0" is not supported (one clock must always be active)
Example usage:
# Check current clock status
$ cat /sys/class/net/eth0/tx_clk/*
# Switch to external reference clock
$ echo 1 > /sys/class/net/eth0/tx_clk/ext_ref
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@...el.com>
---
drivers/net/ethernet/intel/ice/Makefile | 1 +
drivers/net/ethernet/intel/ice/ice.h | 5 +
drivers/net/ethernet/intel/ice/ice_lib.c | 6 +
drivers/net/ethernet/intel/ice/ice_main.c | 6 +
drivers/net/ethernet/intel/ice/ice_tx_clk.c | 113 +++++++++++++++
drivers/net/ethernet/intel/ice/ice_tx_clk.h | 17 +++
include/linux/netdev_tx_clk.h | 43 ++++++
include/linux/netdevice.h | 6 +
net/Kconfig | 21 +++
net/Makefile | 1 +
net/core/Makefile | 1 +
net/core/tx_clk.c | 150 ++++++++++++++++++++
12 files changed, 370 insertions(+)
create mode 100644 drivers/net/ethernet/intel/ice/ice_tx_clk.c
create mode 100644 drivers/net/ethernet/intel/ice/ice_tx_clk.h
create mode 100644 include/linux/netdev_tx_clk.h
create mode 100644 net/core/tx_clk.c
diff --git a/drivers/net/ethernet/intel/ice/Makefile b/drivers/net/ethernet/intel/ice/Makefile
index d0f9c9492363..31f0dc580900 100644
--- a/drivers/net/ethernet/intel/ice/Makefile
+++ b/drivers/net/ethernet/intel/ice/Makefile
@@ -60,3 +60,4 @@ ice-$(CONFIG_XDP_SOCKETS) += ice_xsk.o
ice-$(CONFIG_ICE_SWITCHDEV) += ice_eswitch.o ice_eswitch_br.o
ice-$(CONFIG_GNSS) += ice_gnss.o
ice-$(CONFIG_ICE_HWMON) += ice_hwmon.o
+ice-$(CONFIG_NET_TX_CLK) += ice_tx_clk.o
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index 2098f00b3cd3..116c8668f504 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -204,6 +204,7 @@ enum ice_feature {
ICE_F_ROCE_LAG,
ICE_F_SRIOV_LAG,
ICE_F_MBX_LIMIT,
+ ICE_F_TX_CLK,
ICE_F_MAX
};
@@ -661,6 +662,10 @@ struct ice_pf {
struct device *hwmon_dev;
struct ice_health health_reporters;
struct iidc_rdma_core_dev_info *cdev_info;
+#ifdef CONFIG_NET_TX_CLK
+ void *tx_clk_data; /* Private clock data */
+ u8 tx_clk_active; /* Currently active TX clock ID */
+#endif
u8 num_quanta_prof_used;
};
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index a439b5a61a56..34efba93a450 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -3943,6 +3943,12 @@ void ice_init_feature_support(struct ice_pf *pf)
if (ice_gnss_is_module_present(&pf->hw))
ice_set_feature_support(pf, ICE_F_GNSS);
break;
+ case ICE_DEV_ID_E825C_BACKPLANE:
+ case ICE_DEV_ID_E825C_QSFP:
+ case ICE_DEV_ID_E825C_SFP:
+ case ICE_DEV_ID_E825C_SGMII:
+ ice_set_feature_support(pf, ICE_F_TX_CLK);
+ break;
default:
break;
}
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index 8e0b06c1e02b..80ed03d7b02e 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -27,6 +27,7 @@
#include "ice_tc_lib.h"
#include "ice_vsi_vlan_ops.h"
#include <net/xdp_sock_drv.h>
+#include "ice_tx_clk.h"
#define DRV_SUMMARY "Intel(R) Ethernet Connection E800 Series Linux Driver"
static const char ice_driver_string[] = DRV_SUMMARY;
@@ -4854,6 +4855,9 @@ static void ice_init_features(struct ice_pf *pf)
if (ice_init_lag(pf))
dev_warn(dev, "Failed to init link aggregation support\n");
+ if (ice_is_feature_supported(pf, ICE_F_TX_CLK))
+ ice_tx_clk_init(pf);
+
ice_hwmon_init(pf);
}
@@ -4874,6 +4878,8 @@ static void ice_deinit_features(struct ice_pf *pf)
ice_dpll_deinit(pf);
if (pf->eswitch_mode == DEVLINK_ESWITCH_MODE_SWITCHDEV)
xa_destroy(&pf->eswitch.reprs);
+ if (ice_is_feature_supported(pf, ICE_F_TX_CLK))
+ ice_tx_clk_deinit(pf);
}
static void ice_init_wakeup(struct ice_pf *pf)
diff --git a/drivers/net/ethernet/intel/ice/ice_tx_clk.c b/drivers/net/ethernet/intel/ice/ice_tx_clk.c
new file mode 100644
index 000000000000..121e9fa0c146
--- /dev/null
+++ b/drivers/net/ethernet/intel/ice/ice_tx_clk.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (C) 2025, Intel Corporation. */
+
+#include <linux/netdev_tx_clk.h>
+#include "ice_tx_clk.h"
+
+enum ice_clk_type {
+ ICE_TX_CLK_OCXO = 0,
+ ICE_TX_CLK_SYNCE_REF,
+ ICE_TX_CLK_EXT_REF,
+
+ ICE_TX_CLK_COUNT /* always last */
+};
+
+static const char *ice_clk_names[ICE_TX_CLK_COUNT] = {
+ "ocxo",
+ "SyncE_ref",
+ "ext_ref"
+};
+
+struct ice_tx_clk_data {
+ struct ice_pf *pf;
+ u8 clk_id;
+};
+
+static const struct netdev_tx_clk_ops ice_tx_clk_ops;
+
+static int ice_tx_clk_enable(void *priv_data)
+{
+ struct ice_tx_clk_data *clk_data = priv_data;
+ struct ice_pf *pf = clk_data->pf;
+ u8 clk_id = clk_data->clk_id;
+
+ if (clk_id >= ICE_TX_CLK_COUNT) {
+ dev_err(ice_pf_to_dev(pf), "Invalid clock ID: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ if (pf->tx_clk_active != clk_id) {
+ dev_dbg(ice_pf_to_dev(pf), "PF%d switching from %s to %s clock\n",
+ pf->hw.pf_id, ice_clk_names[pf->tx_clk_active],
+ ice_clk_names[clk_id]);
+
+ pf->tx_clk_active = clk_id;
+ /* TODO: add TX clock switching logic */
+ }
+
+ return 0;
+}
+
+static int ice_tx_clk_is_enabled(void *priv_data)
+{
+ struct ice_tx_clk_data *clk_data = priv_data;
+ struct ice_pf *pf = clk_data->pf;
+ u8 clk_id = clk_data->clk_id;
+
+ return (pf->tx_clk_active == clk_id) ? 1 : 0;
+}
+
+static const struct netdev_tx_clk_ops ice_tx_clk_ops = {
+ .enable = ice_tx_clk_enable,
+ .is_enabled = ice_tx_clk_is_enabled,
+};
+
+void ice_tx_clk_init(struct ice_pf *pf)
+{
+ struct ice_vsi *vsi = ice_get_main_vsi(pf);
+ struct ice_tx_clk_data *clk_data[ICE_TX_CLK_COUNT];
+ int i, ret;
+
+ if (!vsi || !vsi->netdev)
+ return;
+
+ for (i = 0; i < ICE_TX_CLK_COUNT; i++) {
+ clk_data[i] = kzalloc(sizeof(*clk_data[i]), GFP_KERNEL);
+ if (!clk_data[i]) {
+ while (--i >= 0)
+ kfree(clk_data[i]);
+ return;
+ }
+
+ clk_data[i]->pf = pf;
+ clk_data[i]->clk_id = i;
+ }
+
+ pf->tx_clk_active = ICE_TX_CLK_OCXO;
+
+ for (i = 0; i < ICE_TX_CLK_COUNT; i++) {
+ ret = netdev_tx_clk_register(vsi->netdev, ice_clk_names[i],
+ &ice_tx_clk_ops, clk_data[i]);
+ if (ret) {
+ dev_err(ice_pf_to_dev(pf),
+ "Failed to register %s clock: %d\n",
+ ice_clk_names[i], ret);
+ }
+ }
+
+ dev_dbg(ice_pf_to_dev(pf), "ICE TX clocks initialized for PF%d (default: %s)\n",
+ pf->hw.pf_id, ice_clk_names[ICE_TX_CLK_OCXO]);
+}
+
+void ice_tx_clk_deinit(struct ice_pf *pf)
+{
+ struct ice_vsi *vsi = ice_get_main_vsi(pf);
+
+ if (!vsi || !vsi->netdev)
+ return;
+
+ netdev_tx_clk_cleanup(vsi->netdev);
+
+ dev_dbg(ice_pf_to_dev(pf), "ICE TX clocks deinitialized for PF%d\n",
+ pf->hw.pf_id);
+}
diff --git a/drivers/net/ethernet/intel/ice/ice_tx_clk.h b/drivers/net/ethernet/intel/ice/ice_tx_clk.h
new file mode 100644
index 000000000000..02ede41dfefa
--- /dev/null
+++ b/drivers/net/ethernet/intel/ice/ice_tx_clk.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2025, Intel Corporation. */
+
+#ifndef _ICE_TX_CLK_H_
+#define _ICE_TX_CLK_H_
+
+#include "ice.h"
+
+#if IS_ENABLED(CONFIG_NET_TX_CLK)
+void ice_tx_clk_init(struct ice_pf *pf);
+void ice_tx_clk_deinit(struct ice_pf *pf);
+#else
+static inline void ice_tx_clk_init(struct ice_pf *pf) { }
+static inline void ice_tx_clk_deinit(struct ice_pf *pf) { }
+#endif
+
+#endif /* _ICE_TX_CLK_H_ */
diff --git a/include/linux/netdev_tx_clk.h b/include/linux/netdev_tx_clk.h
new file mode 100644
index 000000000000..3ba820b40fed
--- /dev/null
+++ b/include/linux/netdev_tx_clk.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * netdev_tx_clk.h - allow net_device TX clock control
+ * Author: Arkadiusz Kubalewski <arkadiusz.kubalewski@...el.com>
+ */
+
+#ifndef __NETDEV_TX_CLK_H
+#define __NETDEV_TX_CLK_H
+
+#include <linux/netdevice.h>
+
+/**
+ * struct netdev_tx_clk_ops - TX clock operations
+ * @enable: switch to this clock (called when user writes "1" to sysfs)
+ * @is_enabled: check if this clock is currently active
+ *
+ * Note: one clock must always be active, writing "0" to disable is not
+ * supported.
+ */
+struct netdev_tx_clk_ops {
+ int (*enable)(void *priv_data);
+ int (*is_enabled)(void *priv_data);
+};
+#if IS_ENABLED(CONFIG_NET_TX_CLK)
+
+int netdev_tx_clk_register(struct net_device *ndev, const char *clk_name,
+ const struct netdev_tx_clk_ops *ops,
+ void *priv_data);
+
+void netdev_tx_clk_cleanup(struct net_device *ndev);
+#else
+
+static inline int netdev_tx_clk_register(struct net_device *ndev, const char *clk_name,
+ const struct netdev_tx_clk_ops *ops,
+ void *priv_data)
+{
+ return 0;
+}
+
+static inline void netdev_tx_clk_cleanup(struct net_device *ndev) { }
+#endif
+
+#endif /* __NETDEV_TX_CLK_H */
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 5e5de4b0a433..b09a1eff4d4d 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -23,6 +23,7 @@
#include <linux/timer.h>
#include <linux/bug.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/prefetch.h>
@@ -2552,6 +2553,11 @@ struct net_device {
struct hwtstamp_provider __rcu *hwprov;
+#if IS_ENABLED(CONFIG_NET_TX_CLK)
+ struct list_head tx_clk_list;
+ struct kobject *tx_clk_dir;
+
+#endif
u8 priv[] ____cacheline_aligned
__counted_by(priv_len);
} ____cacheline_aligned;
diff --git a/net/Kconfig b/net/Kconfig
index d5865cf19799..b0c7ce6ce046 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -541,4 +541,25 @@ config NET_TEST
If unsure, say N.
+config NET_TX_CLK
+ bool "Control over source of TX clock per network device"
+ depends on COMMON_CLK && NET
+ default n
+ help
+ This feature enables per-device control over TX clock sources in
+ networking hardware. Network devices may support multiple transmit
+ clock sources such as different oscillators, OCXOs, PLLs, or
+ external clock signals provided via onboard pins.
+
+ When enabled, supported network devices will create a tx_clk
+ directory under their sysfs entry (/sys/class/net/<device>/tx_clk/).
+ Users can select an available clock source by writing '1' to the
+ corresponding file in this directory. The status of each clock
+ source can be checked by reading the file (0 = disabled, 1 = enabled).
+
+ This is useful for applications requiring precise timing control,
+ such as time-sensitive networking (TSN) or synchronization protocols.
+
+ If unsure, say N.
+
endif # if NET
diff --git a/net/Makefile b/net/Makefile
index aac960c41db6..f78f46444efa 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -79,3 +79,4 @@ obj-$(CONFIG_MPTCP) += mptcp/
obj-$(CONFIG_MCTP) += mctp/
obj-$(CONFIG_NET_HANDSHAKE) += handshake/
obj-$(CONFIG_NET_SHAPER) += shaper/
+obj-$(CONFIG_PHY_CLK_USER) += phy_clk/
diff --git a/net/core/Makefile b/net/core/Makefile
index b2a76ce33932..7e4031813b18 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_NET_TEST) += net_test.o
obj-$(CONFIG_NET_DEVMEM) += devmem.o
obj-$(CONFIG_DEBUG_NET) += lock_debug.o
obj-$(CONFIG_FAIL_SKB_REALLOC) += skb_fault_injection.o
+obj-$(CONFIG_NET_TX_CLK) += tx_clk.o
diff --git a/net/core/tx_clk.c b/net/core/tx_clk.c
new file mode 100644
index 000000000000..fbbc6497e563
--- /dev/null
+++ b/net/core/tx_clk.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Network device TX clock control framework
+ * Simple sysfs interface for userspace TX clock management
+ * Author: Arkadiusz Kubalewski <arkadiusz.kubalewski@...el.com>
+ */
+
+#include <linux/netdevice.h>
+#include <linux/netdev_tx_clk.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+/* Simple clock entry structure */
+struct tx_clk_entry {
+ char name[32];
+ const struct netdev_tx_clk_ops *ops;
+ void *priv_data;
+ struct kobj_attribute attr;
+ struct list_head list;
+};
+
+static DEFINE_MUTEX(tx_clk_mutex);
+
+static ssize_t tx_clk_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct tx_clk_entry *entry = container_of(attr, struct tx_clk_entry,
+ attr);
+ int ret = entry->ops->is_enabled(entry->priv_data);
+
+ if (ret != 0 && ret != 1)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t tx_clk_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tx_clk_entry *entry = container_of(attr, struct tx_clk_entry,
+ attr);
+ int val, ret;
+
+ ret = kstrtoint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ /* Cannot disable - one clock must always be active */
+ if (val != 1)
+ return -EINVAL;
+
+ mutex_lock(&tx_clk_mutex);
+ ret = entry->ops->enable(entry->priv_data);
+ mutex_unlock(&tx_clk_mutex);
+
+ return ret ? ret : count;
+}
+
+/**
+ * netdev_tx_clk_register - register a TX clock for a network device
+ * @ndev: network device
+ * @clk_name: clock name (visible in sysfs)
+ * @ops: clock operations
+ * @priv_data: private data for callbacks
+ *
+ * Returns 0 on success, negative error code on failure
+ */
+int netdev_tx_clk_register(struct net_device *ndev, const char *clk_name,
+ const struct netdev_tx_clk_ops *ops,
+ void *priv_data)
+{
+ struct tx_clk_entry *entry;
+ int ret;
+
+ if (WARN_ON(!ndev || !clk_name || !ops))
+ return -EINVAL;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ strscpy(entry->name, clk_name, sizeof(entry->name) - 1);
+ entry->ops = ops;
+ entry->priv_data = priv_data;
+ INIT_LIST_HEAD(&entry->list);
+
+ /* Setup sysfs attribute */
+ entry->attr.attr.name = entry->name;
+ entry->attr.attr.mode = 0644;
+ entry->attr.show = tx_clk_show;
+ entry->attr.store = tx_clk_store;
+
+ mutex_lock(&tx_clk_mutex);
+
+ if (!ndev->tx_clk_dir) {
+ INIT_LIST_HEAD(&ndev->tx_clk_list);
+ ndev->tx_clk_dir = kobject_create_and_add("tx_clk", &ndev->dev.kobj);
+ if (!ndev->tx_clk_dir) {
+ kfree(entry);
+ mutex_unlock(&tx_clk_mutex);
+ return -ENOMEM;
+ }
+ }
+
+ /* Add to device's clock list */
+ list_add_tail(&entry->list, &ndev->tx_clk_list);
+
+ /* Create sysfs file */
+ ret = sysfs_create_file(ndev->tx_clk_dir, &entry->attr.attr);
+ if (ret) {
+ list_del(&entry->list);
+ kfree(entry);
+ mutex_unlock(&tx_clk_mutex);
+ return ret;
+ }
+
+ mutex_unlock(&tx_clk_mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(netdev_tx_clk_register);
+
+/**
+ * netdev_tx_clk_cleanup - cleanup all TX clocks for a network device
+ * @ndev: network device
+ */
+void netdev_tx_clk_cleanup(struct net_device *ndev)
+{
+ struct tx_clk_entry *entry, *tmp;
+
+ if (!ndev)
+ return;
+
+ mutex_lock(&tx_clk_mutex);
+
+ list_for_each_entry_safe(entry, tmp, &ndev->tx_clk_list, list) {
+ sysfs_remove_file(ndev->tx_clk_dir, &entry->attr.attr);
+ list_del(&entry->list);
+ kfree(entry);
+ }
+
+ if (ndev->tx_clk_dir) {
+ kobject_put(ndev->tx_clk_dir);
+ ndev->tx_clk_dir = NULL;
+ }
+
+ mutex_unlock(&tx_clk_mutex);
+}
+EXPORT_SYMBOL_GPL(netdev_tx_clk_cleanup);
base-commit: fa582ca7e187a15e772e6a72fe035f649b387a60
--
2.38.1
Powered by blists - more mailing lists