@@ -220,6 +220,8 @@ Userspace to kernel:
``ETHTOOL_MSG_PHC_VCLOCKS_GET`` get PHC virtual clocks info
``ETHTOOL_MSG_MODULE_SET`` set transceiver module parameters
``ETHTOOL_MSG_MODULE_GET`` get transceiver module parameters
+ ``ETHTOOL_RCLK_GET`` get recovered clock parameters
+ ``ETHTOOL_RCLK_SET`` set recovered clock parameters
===================================== =================================
Kernel to userspace:
@@ -260,6 +262,8 @@ Kernel to userspace:
``ETHTOOL_MSG_STATS_GET_REPLY`` standard statistics
``ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY`` PHC virtual clocks info
``ETHTOOL_MSG_MODULE_GET_REPLY`` transceiver module parameters
+ ``ETHTOOL_MSG_RCLK_GET_REPLY`` reference recovered clock config
+ ``ETHTOOL_MSG_RCLK_NTF`` reference recovered clock config
======================================== =================================
``GET`` requests are sent by userspace applications to retrieve device
@@ -1598,6 +1602,62 @@ For SFF-8636 modules, low power mode is forced by the host according to table
For CMIS modules, low power mode is forced by the host according to table 6-12
in revision 5.0 of the specification.
+RCLK_GET
+========
+
+Get status of an output pin for PHY recovered frequency clock signal.
+
+Request contents:
+
+ ====================================== ====== ==========================
+ ``ETHTOOL_A_RCLK_HEADER`` nested request header
+ ``ETHTOOL_A_RCLK_OUT_PIN_IDX`` u32 index of a pin
+ ====================================== ====== ==========================
+
+Kernel response contents:
+
+ ====================================== ====== ==========================
+ ``ETHTOOL_A_RCLK_OUT_PIN_IDX`` u32 index of a pin
+ ``ETHTOOL_A_RCLK_PIN_FLAGS`` u32 state of a pin
+ ``ETHTOOL_A_RCLK_RANGE_MIN_PIN`` u32 min index of RCLK pins
+ ``ETHTOOL_A_RCLK_RANGE_MAX_PIN`` u32 max index of RCLK pins
+ ====================================== ====== ==========================
+
+A device can support multiple recovered clock output pins. They typically are
+connected to the EEC and are used as a frequency reference.
+When a given pin on a given port is enabled, the PHY recovered frequency is
+fed onto that pin.
+Pins can be freely indexed and don't have to start with index 0.
+
+The ``ETHTOOL_A_RCLK_OUT_PIN_IDX`` is optional parameter. If present in
+the RCLK_GET request, the ``ETHTOOL_A_RCLK_PIN_FLAGS`` will be returned in a
+response, containing the state of the pin pointed by the index.
+
+If ``ETHTOOL_A_RCLK_OUT_PIN_IDX`` is not present in the RCLK_GET request,
+the range of available pins is returned:
+``ETHTOOL_A_RCLK_RANGE_MIN_PIN`` is the lowest recovered clock pin index.
+``ETHTOOL_A_RCLK_RANGE_MAX_PIN`` is the highest recovered clock pin index.
+
+RCLK_SET
+==========
+
+Set status of an output pin for PHY recovered frequency clock.
+
+Request contents:
+
+ ====================================== ====== ========================
+ ``ETHTOOL_A_RCLK_HEADER`` nested request header
+ ``ETHTOOL_A_RCLK_OUT_PIN_IDX`` u32 index of a pin
+ ``ETHTOOL_A_RCLK_PIN_FLAGS`` u32 requested state
+ ====================================== ====== ========================
+
+``ETHTOOL_A_RCLK_OUT_PIN_IDX`` is the index of the pin for which a change of
+state is requested.
+``ETHTOOL_A_RCLK_PIN_FLAGS`` if the ``ETHTOOL_RCLK_PIN_FLAGS_ENA`` flag
+is set, the given output pin will be enabled, if not - the pin will be disabled
+
+
+
Request translation
===================
@@ -1699,4 +1759,6 @@ are netlink only.
n/a ``ETHTOOL_MSG_PHC_VCLOCKS_GET``
n/a ``ETHTOOL_MSG_MODULE_GET``
n/a ``ETHTOOL_MSG_MODULE_SET``
+ n/a ``ETHTOOL_MSG_RCLK_GET``
+ n/a ``ETHTOOL_MSG_RCLK_SET``
=================================== =====================================
@@ -614,6 +614,9 @@ struct ethtool_module_power_mode_params {
* plugged-in.
* @set_module_power_mode: Set the power mode policy for the plug-in module
* used by the network device.
+ * @get_rclk_range: Get range of valid Reference Clock input pins for the port.
+ * @get_rclk_state: Get state of Reference Clock input signal pin.
+ * @set_rclk_out: Enable/disable Reference Clock input signal pin.
*
* All operations are optional (i.e. the function pointer may be set
* to %NULL) and callers must take this into account. Callers must
@@ -750,6 +753,12 @@ struct ethtool_ops {
int (*set_module_power_mode)(struct net_device *dev,
const struct ethtool_module_power_mode_params *params,
struct netlink_ext_ack *extack);
+ int (*get_rclk_range)(struct net_device *dev, u32 *min_idx,
+ u32 *max_idx, struct netlink_ext_ack *extack);
+ int (*get_rclk_state)(struct net_device *dev, u32 out_idx,
+ bool *ena, struct netlink_ext_ack *extack);
+ int (*set_rclk_out)(struct net_device *dev, u32 out_idx, bool ena,
+ struct netlink_ext_ack *extack);
};
int ethtool_check_ops(const struct ethtool_ops *ops);
@@ -49,6 +49,8 @@ enum {
ETHTOOL_MSG_PHC_VCLOCKS_GET,
ETHTOOL_MSG_MODULE_GET,
ETHTOOL_MSG_MODULE_SET,
+ ETHTOOL_MSG_RCLK_GET,
+ ETHTOOL_MSG_RCLK_SET,
/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,
@@ -94,6 +96,8 @@ enum {
ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY,
ETHTOOL_MSG_MODULE_GET_REPLY,
ETHTOOL_MSG_MODULE_NTF,
+ ETHTOOL_MSG_RCLK_GET_REPLY,
+ ETHTOOL_MSG_RCLK_NTF,
/* add new constants above here */
__ETHTOOL_MSG_KERNEL_CNT,
@@ -853,6 +857,23 @@ enum {
ETHTOOL_A_MODULE_MAX = (__ETHTOOL_A_MODULE_CNT - 1)
};
+/* REF CLK */
+
+enum {
+ ETHTOOL_A_RCLK_UNSPEC,
+ ETHTOOL_A_RCLK_HEADER, /* nest - _A_HEADER_* */
+ ETHTOOL_A_RCLK_OUT_PIN_IDX, /* u32 */
+ ETHTOOL_A_RCLK_PIN_FLAGS, /* u32 */
+ ETHTOOL_A_RCLK_PIN_MIN, /* u32 */
+ ETHTOOL_A_RCLK_PIN_MAX, /* u32 */
+
+ /* add new constants above here */
+ __ETHTOOL_A_RCLK_CNT,
+ ETHTOOL_A_RCLK_MAX = (__ETHTOOL_A_RCLK_CNT - 1)
+};
+
+#define ETHTOOL_RCLK_PIN_FLAGS_ENA (1 << 0)
+
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
@@ -7,4 +7,5 @@ obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
linkstate.o debug.o wol.o features.o privflags.o rings.o \
channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
- tunnels.o fec.o eeprom.o stats.o phc_vclocks.o module.o
+ tunnels.o fec.o eeprom.o stats.o phc_vclocks.o module.o \
+ synce.o
@@ -285,6 +285,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_STATS_GET] = ðnl_stats_request_ops,
[ETHTOOL_MSG_PHC_VCLOCKS_GET] = ðnl_phc_vclocks_request_ops,
[ETHTOOL_MSG_MODULE_GET] = ðnl_module_request_ops,
+ [ETHTOOL_MSG_RCLK_GET] = ðnl_rclk_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -597,6 +598,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = {
[ETHTOOL_MSG_EEE_NTF] = ðnl_eee_request_ops,
[ETHTOOL_MSG_FEC_NTF] = ðnl_fec_request_ops,
[ETHTOOL_MSG_MODULE_NTF] = ðnl_module_request_ops,
+ [ETHTOOL_MSG_RCLK_NTF] = ðnl_rclk_request_ops,
};
/* default notification handler */
@@ -692,6 +694,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
[ETHTOOL_MSG_EEE_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_FEC_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_MODULE_NTF] = ethnl_default_notify,
+ [ETHTOOL_MSG_RCLK_NTF] = ethnl_default_notify,
};
void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
@@ -1021,6 +1024,23 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_module_set_policy,
.maxattr = ARRAY_SIZE(ethnl_module_set_policy) - 1,
},
+ {
+ .cmd = ETHTOOL_MSG_RCLK_GET,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .doit = ethnl_default_doit,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
+ .policy = ethnl_rclk_get_policy,
+ .maxattr = ARRAY_SIZE(ethnl_rclk_get_policy) - 1,
+ },
+ {
+ .cmd = ETHTOOL_MSG_RCLK_SET,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .doit = ethnl_set_rclk,
+ .policy = ethnl_rclk_set_policy,
+ .maxattr = ARRAY_SIZE(ethnl_rclk_set_policy) - 1,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
@@ -340,6 +340,7 @@ extern const struct ethnl_request_ops ethnl_module_eeprom_request_ops;
extern const struct ethnl_request_ops ethnl_stats_request_ops;
extern const struct ethnl_request_ops ethnl_phc_vclocks_request_ops;
extern const struct ethnl_request_ops ethnl_module_request_ops;
+extern const struct ethnl_request_ops ethnl_rclk_request_ops;
extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];
extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];
@@ -378,6 +379,8 @@ extern const struct nla_policy ethnl_stats_get_policy[ETHTOOL_A_STATS_GROUPS + 1
extern const struct nla_policy ethnl_phc_vclocks_get_policy[ETHTOOL_A_PHC_VCLOCKS_HEADER + 1];
extern const struct nla_policy ethnl_module_get_policy[ETHTOOL_A_MODULE_HEADER + 1];
extern const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MODE_POLICY + 1];
+extern const struct nla_policy ethnl_rclk_get_policy[ETHTOOL_A_RCLK_OUT_PIN_IDX + 1];
+extern const struct nla_policy ethnl_rclk_set_policy[ETHTOOL_A_RCLK_PIN_FLAGS + 1];
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
@@ -397,6 +400,7 @@ int ethnl_tunnel_info_start(struct netlink_callback *cb);
int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_set_fec(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_module(struct sk_buff *skb, struct genl_info *info);
+int ethnl_set_rclk(struct sk_buff *skb, struct genl_info *info);
extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
new file mode 100644
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/ethtool.h>
+#include "netlink.h"
+
+struct rclk_out_pin_info {
+ u32 idx;
+ bool valid;
+};
+
+struct rclk_request_data {
+ struct ethnl_req_info base;
+ struct rclk_out_pin_info out_pin;
+};
+
+struct rclk_pin_state_info {
+ u32 range_min;
+ u32 range_max;
+ u32 flags;
+ u32 idx;
+};
+
+struct rclk_reply_data {
+ struct ethnl_reply_data base;
+ struct rclk_pin_state_info pin_state;
+};
+
+#define RCLK_REPDATA(__reply_base) \
+ container_of(__reply_base, struct rclk_reply_data, base)
+
+#define RCLK_REQDATA(__req_base) \
+ container_of(__req_base, struct rclk_request_data, base)
+
+/* RCLK_GET */
+
+const struct nla_policy
+ethnl_rclk_get_policy[ETHTOOL_A_RCLK_OUT_PIN_IDX + 1] = {
+ [ETHTOOL_A_RCLK_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+ [ETHTOOL_A_RCLK_OUT_PIN_IDX] = { .type = NLA_U32 },
+};
+
+static int rclk_parse_request(struct ethnl_req_info *req_base,
+ struct nlattr **tb,
+ struct netlink_ext_ack *extack)
+{
+ struct rclk_request_data *req = RCLK_REQDATA(req_base);
+
+ if (tb[ETHTOOL_A_RCLK_OUT_PIN_IDX]) {
+ req->out_pin.idx = nla_get_u32(tb[ETHTOOL_A_RCLK_OUT_PIN_IDX]);
+ req->out_pin.valid = true;
+ }
+
+ return 0;
+}
+
+static int rclk_state_get(struct net_device *dev,
+ struct rclk_reply_data *data,
+ struct netlink_ext_ack *extack,
+ u32 out_idx)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ bool pin_state;
+ int ret;
+
+ if (!ops->get_rclk_state)
+ return -EOPNOTSUPP;
+
+ ret = ops->get_rclk_state(dev, out_idx, &pin_state, extack);
+ if (ret)
+ return ret;
+
+ data->pin_state.flags = pin_state ? ETHTOOL_RCLK_PIN_FLAGS_ENA : 0;
+ data->pin_state.idx = out_idx;
+
+ return ret;
+}
+
+static int rclk_range_get(struct net_device *dev,
+ struct rclk_reply_data *data,
+ struct netlink_ext_ack *extack)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ u32 min_idx, max_idx;
+ int ret;
+
+ if (!ops->get_rclk_range)
+ return -EOPNOTSUPP;
+
+ ret = ops->get_rclk_range(dev, &min_idx, &max_idx, extack);
+ if (ret)
+ return ret;
+
+ data->pin_state.range_min = min_idx;
+ data->pin_state.range_max = max_idx;
+
+ return ret;
+}
+
+static int rclk_prepare_data(const struct ethnl_req_info *req_base,
+ struct ethnl_reply_data *reply_base,
+ struct genl_info *info)
+{
+ struct rclk_reply_data *reply = RCLK_REPDATA(reply_base);
+ struct rclk_request_data *request = RCLK_REQDATA(req_base);
+ struct netlink_ext_ack *extack = info ? info->extack : NULL;
+ struct net_device *dev = reply_base->dev;
+ int ret;
+
+ memset(&reply->pin_state, 0, sizeof(reply->pin_state));
+ ret = ethnl_ops_begin(dev);
+ if (ret < 0)
+ return ret;
+
+ if (request->out_pin.valid)
+ ret = rclk_state_get(dev, reply, extack,
+ request->out_pin.idx);
+ else
+ ret = rclk_range_get(dev, reply, extack);
+
+ ethnl_ops_complete(dev);
+
+ return ret;
+}
+
+static int rclk_fill_reply(struct sk_buff *skb,
+ const struct ethnl_req_info *req_base,
+ const struct ethnl_reply_data *reply_base)
+{
+ const struct rclk_reply_data *reply = RCLK_REPDATA(reply_base);
+ const struct rclk_request_data *request = RCLK_REQDATA(req_base);
+
+ if (request->out_pin.valid) {
+ if (nla_put_u32(skb, ETHTOOL_A_RCLK_PIN_FLAGS,
+ reply->pin_state.flags))
+ return -EMSGSIZE;
+ if (nla_put_u32(skb, ETHTOOL_A_RCLK_OUT_PIN_IDX,
+ reply->pin_state.idx))
+ return -EMSGSIZE;
+ } else {
+ if (nla_put_u32(skb, ETHTOOL_A_RCLK_PIN_MIN,
+ reply->pin_state.range_min))
+ return -EMSGSIZE;
+ if (nla_put_u32(skb, ETHTOOL_A_RCLK_PIN_MAX,
+ reply->pin_state.range_max))
+ return -EMSGSIZE;
+ }
+
+ return 0;
+}
+
+static int rclk_reply_size(const struct ethnl_req_info *req_base,
+ const struct ethnl_reply_data *reply_base)
+{
+ const struct rclk_request_data *request = RCLK_REQDATA(req_base);
+
+ if (request->out_pin.valid)
+ return nla_total_size(sizeof(u32)) + /* ETHTOOL_A_RCLK_PIN_FLAGS */
+ nla_total_size(sizeof(u32)); /* ETHTOOL_A_RCLK_OUT_PIN_IDX */
+ else
+ return nla_total_size(sizeof(u32)) + /* ETHTOOL_A_RCLK_PIN_MIN */
+ nla_total_size(sizeof(u32)); /* ETHTOOL_A_RCLK_PIN_MAX */
+}
+
+const struct ethnl_request_ops ethnl_rclk_request_ops = {
+ .request_cmd = ETHTOOL_MSG_RCLK_GET,
+ .reply_cmd = ETHTOOL_MSG_RCLK_GET_REPLY,
+ .hdr_attr = ETHTOOL_A_RCLK_HEADER,
+ .req_info_size = sizeof(struct rclk_request_data),
+ .reply_data_size = sizeof(struct rclk_reply_data),
+
+ .parse_request = rclk_parse_request,
+ .prepare_data = rclk_prepare_data,
+ .reply_size = rclk_reply_size,
+ .fill_reply = rclk_fill_reply,
+};
+
+/* RCLK SET */
+
+const struct nla_policy
+ethnl_rclk_set_policy[ETHTOOL_A_RCLK_PIN_FLAGS + 1] = {
+ [ETHTOOL_A_RCLK_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+ [ETHTOOL_A_RCLK_OUT_PIN_IDX] = { .type = NLA_U32 },
+ [ETHTOOL_A_RCLK_PIN_FLAGS] = { .type = NLA_U32 },
+};
+
+static int rclk_set_state(struct net_device *dev, struct nlattr **tb,
+ bool *p_mod, struct netlink_ext_ack *extack)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ bool old_state, new_state;
+ u32 min_idx, max_idx;
+ u32 out_idx;
+ int ret;
+
+ if (!tb[ETHTOOL_A_RCLK_PIN_FLAGS] &&
+ !tb[ETHTOOL_A_RCLK_OUT_PIN_IDX])
+ return 0;
+
+ if (!ops->set_rclk_out || !ops->get_rclk_range) {
+ NL_SET_ERR_MSG_ATTR(extack,
+ tb[ETHTOOL_A_RCLK_PIN_FLAGS],
+ "Setting recovered clock state is not supported by this device");
+ return -EOPNOTSUPP;
+ }
+
+ ret = ops->get_rclk_range(dev, &min_idx, &max_idx, extack);
+ if (ret)
+ return ret;
+
+ out_idx = nla_get_u32(tb[ETHTOOL_A_RCLK_OUT_PIN_IDX]);
+ if (out_idx < min_idx || out_idx > max_idx) {
+ NL_SET_ERR_MSG_ATTR(extack,
+ tb[ETHTOOL_A_RCLK_OUT_PIN_IDX],
+ "Requested recovered clock pin index is out of range");
+ return -EINVAL;
+ }
+
+ ret = ops->get_rclk_state(dev, out_idx, &old_state, extack);
+ if (ret < 0)
+ return ret;
+
+ new_state = !!(nla_get_u32(tb[ETHTOOL_A_RCLK_PIN_FLAGS]) &
+ ETHTOOL_RCLK_PIN_FLAGS_ENA);
+
+ /* If state changed - flag need for sending the notification */
+ *p_mod = old_state != new_state;
+
+ return ops->set_rclk_out(dev, out_idx, new_state, extack);
+}
+
+int ethnl_set_rclk(struct sk_buff *skb, struct genl_info *info)
+{
+ struct ethnl_req_info req_info = {};
+ struct nlattr **tb = info->attrs;
+ struct net_device *dev;
+ bool mod = false;
+ int ret;
+
+ ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_RCLK_HEADER],
+ genl_info_net(info), info->extack,
+ true);
+ if (ret < 0)
+ return ret;
+ dev = req_info.dev;
+
+ rtnl_lock();
+ ret = ethnl_ops_begin(dev);
+ if (ret < 0)
+ goto out_rtnl;
+
+ ret = rclk_set_state(dev, tb, &mod, info->extack);
+ if (ret < 0)
+ goto out_ops;
+
+ if (!mod)
+ goto out_ops;
+
+ ethtool_notify(dev, ETHTOOL_MSG_RCLK_NTF, NULL);
+
+out_ops:
+ ethnl_ops_complete(dev);
+out_rtnl:
+ rtnl_unlock();
+ dev_put(dev);
+ return ret;
+}
+