@@ -2234,6 +2234,8 @@ Kernel response contents:
bus, the name of this sfp bus
``ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME`` string if the phy controls an sfp bus,
the name of the sfp bus
+ ``ETHTOOL_A_PHY_ISOLATE`` u8 The PHY Isolate status
+ ``ETHTOOL_A_PHY_LOOPBACK`` u8 The PHY Loopback status
===================================== ====== ===============================
When ``ETHTOOL_A_PHY_UPSTREAM_TYPE`` is PHY_UPSTREAM_PHY, the PHY's parent is
@@ -59,6 +59,7 @@ enum {
ETHTOOL_MSG_MM_SET,
ETHTOOL_MSG_MODULE_FW_FLASH_ACT,
ETHTOOL_MSG_PHY_GET,
+ ETHTOOL_MSG_PHY_SET,
/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,
@@ -1079,6 +1080,8 @@ enum {
ETHTOOL_A_PHY_UPSTREAM_INDEX, /* u32 */
ETHTOOL_A_PHY_UPSTREAM_SFP_NAME, /* string */
ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME, /* string */
+ ETHTOOL_A_PHY_ISOLATE, /* u8 */
+ ETHTOOL_A_PHY_LOOPBACK, /* u8 */
/* add new constants above here */
__ETHTOOL_A_PHY_CNT,
@@ -394,6 +394,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_PLCA_GET_STATUS] = ðnl_plca_status_request_ops,
[ETHTOOL_MSG_MM_GET] = ðnl_mm_request_ops,
[ETHTOOL_MSG_MM_SET] = ðnl_mm_request_ops,
+ [ETHTOOL_MSG_PHY_SET] = ðnl_phy_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -1243,6 +1244,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_phy_get_policy,
.maxattr = ARRAY_SIZE(ethnl_phy_get_policy) - 1,
},
+ {
+ .cmd = ETHTOOL_MSG_PHY_SET,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .doit = ethnl_default_set_doit,
+ .policy = ethnl_phy_set_policy,
+ .maxattr = ARRAY_SIZE(ethnl_phy_set_policy) - 1,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
@@ -485,6 +485,7 @@ extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1];
extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1];
extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1];
extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1];
+extern const struct nla_policy ethnl_phy_set_policy[ETHTOOL_A_PHY_MAX + 1];
int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);
int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);
@@ -30,10 +30,13 @@ ethnl_phy_reply_size(const struct ethnl_req_info *req_base,
struct phy_req_info *req_info = PHY_REQINFO(req_base);
struct phy_device_node *pdn = req_info->pdn;
struct phy_device *phydev = pdn->phy;
+ const struct ethtool_phy_ops *ops;
size_t size = 0;
ASSERT_RTNL();
+ ops = ethtool_phy_ops;
+
/* ETHTOOL_A_PHY_INDEX */
size += nla_total_size(sizeof(u32));
@@ -66,6 +69,14 @@ ethnl_phy_reply_size(const struct ethnl_req_info *req_base,
size += nla_total_size(strlen(sfp_name) + 1);
}
+ if (ops && ops->get_config) {
+ /* ETHTOOL_A_PHY_ISOLATE */
+ size += nla_total_size(sizeof(u8));
+
+ /* ETHTOOL_A_PHY_LOOPBACK */
+ size += nla_total_size(sizeof(u8));
+ }
+
return size;
}
@@ -75,10 +86,20 @@ ethnl_phy_fill_reply(const struct ethnl_req_info *req_base, struct sk_buff *skb)
struct phy_req_info *req_info = PHY_REQINFO(req_base);
struct phy_device_node *pdn = req_info->pdn;
struct phy_device *phydev = pdn->phy;
+ const struct ethtool_phy_ops *ops;
+ struct phy_device_config cfg;
enum phy_upstream ptype;
+ int ret;
ptype = pdn->upstream_type;
+ ops = ethtool_phy_ops;
+ if (ops && ops->get_config) {
+ ret = ops->get_config(phydev, &cfg);
+ if (ret)
+ return ret;
+ }
+
if (nla_put_u32(skb, ETHTOOL_A_PHY_INDEX, phydev->phyindex) ||
nla_put_string(skb, ETHTOOL_A_PHY_NAME, dev_name(&phydev->mdio.dev)) ||
nla_put_u32(skb, ETHTOOL_A_PHY_UPSTREAM_TYPE, ptype))
@@ -114,6 +135,14 @@ ethnl_phy_fill_reply(const struct ethnl_req_info *req_base, struct sk_buff *skb)
return -EMSGSIZE;
}
+ /* Append PHY configuration, if possible */
+ if (!ops || !ops->get_config)
+ return 0;
+
+ if (nla_put_u8(skb, ETHTOOL_A_PHY_ISOLATE, cfg.isolate) ||
+ nla_put_u8(skb, ETHTOOL_A_PHY_LOOPBACK, cfg.loopback))
+ return -EMSGSIZE;
+
return 0;
}
@@ -311,3 +340,49 @@ int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
return ret;
}
+
+const struct nla_policy ethnl_phy_set_policy[] = {
+ [ETHTOOL_A_PHY_HEADER] =
+ NLA_POLICY_NESTED(ethnl_header_policy_phy),
+ [ETHTOOL_A_PHY_ISOLATE] = NLA_POLICY_MAX(NLA_U8, 1),
+ [ETHTOOL_A_PHY_LOOPBACK] = NLA_POLICY_MAX(NLA_U8, 1),
+};
+
+static int ethnl_set_phy(struct ethnl_req_info *req_info, struct genl_info *info)
+{
+ struct netlink_ext_ack *extack = info->extack;
+ const struct ethtool_phy_ops *ops;
+ struct nlattr **tb = info->attrs;
+ struct phy_device_config cfg;
+ struct phy_device *phydev;
+ bool mod = false;
+ int ret;
+
+ ops = ethtool_phy_ops;
+ if (!ops || !ops->set_config || !ops->get_config)
+ return -EOPNOTSUPP;
+
+ /* We're running under rtnl */
+ phydev = ethnl_req_get_phydev(req_info, tb[ETHTOOL_A_PHY_HEADER],
+ extack);
+ if (IS_ERR_OR_NULL(phydev))
+ return -ENODEV;
+
+ ret = ops->get_config(phydev, &cfg);
+ if (ret)
+ return ret;
+
+ ethnl_update_bool(&cfg.isolate, tb[ETHTOOL_A_PHY_ISOLATE], &mod);
+ ethnl_update_bool(&cfg.loopback, tb[ETHTOOL_A_PHY_LOOPBACK], &mod);
+
+ if (!mod)
+ return 0;
+
+ /* Returning 0 is fine as we don't have a notification */
+ return ops->set_config(phydev, &cfg, extack);
+}
+
+const struct ethnl_request_ops ethnl_phy_request_ops = {
+ .hdr_attr = ETHTOOL_A_PHY_HEADER,
+ .set = ethnl_set_phy,
+};
Add the isolate and loopback status information to the ETHTOOL_PHY_GET netlink command attributes, and allow changing these parameters from a newly-introduced ETHTOOL_PHY_SET command. Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com> --- Documentation/networking/ethtool-netlink.rst | 2 + include/uapi/linux/ethtool_netlink.h | 3 + net/ethtool/netlink.c | 8 +++ net/ethtool/netlink.h | 1 + net/ethtool/phy.c | 75 ++++++++++++++++++++ 5 files changed, 89 insertions(+)