162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <linux/phy.h> 462306a36Sopenharmony_ci#include <linux/ethtool_netlink.h> 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include "netlink.h" 762306a36Sopenharmony_ci#include "common.h" 862306a36Sopenharmony_ci 962306a36Sopenharmony_cistruct plca_req_info { 1062306a36Sopenharmony_ci struct ethnl_req_info base; 1162306a36Sopenharmony_ci}; 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistruct plca_reply_data { 1462306a36Sopenharmony_ci struct ethnl_reply_data base; 1562306a36Sopenharmony_ci struct phy_plca_cfg plca_cfg; 1662306a36Sopenharmony_ci struct phy_plca_status plca_st; 1762306a36Sopenharmony_ci}; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci// Helpers ------------------------------------------------------------------ // 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define PLCA_REPDATA(__reply_base) \ 2262306a36Sopenharmony_ci container_of(__reply_base, struct plca_reply_data, base) 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci// PLCA get configuration message ------------------------------------------- // 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ciconst struct nla_policy ethnl_plca_get_cfg_policy[] = { 2762306a36Sopenharmony_ci [ETHTOOL_A_PLCA_HEADER] = 2862306a36Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic void plca_update_sint(int *dst, struct nlattr **tb, u32 attrid, 3262306a36Sopenharmony_ci bool *mod) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci const struct nlattr *attr = tb[attrid]; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (!attr || 3762306a36Sopenharmony_ci WARN_ON_ONCE(attrid >= ARRAY_SIZE(ethnl_plca_set_cfg_policy))) 3862306a36Sopenharmony_ci return; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci switch (ethnl_plca_set_cfg_policy[attrid].type) { 4162306a36Sopenharmony_ci case NLA_U8: 4262306a36Sopenharmony_ci *dst = nla_get_u8(attr); 4362306a36Sopenharmony_ci break; 4462306a36Sopenharmony_ci case NLA_U32: 4562306a36Sopenharmony_ci *dst = nla_get_u32(attr); 4662306a36Sopenharmony_ci break; 4762306a36Sopenharmony_ci default: 4862306a36Sopenharmony_ci WARN_ON_ONCE(1); 4962306a36Sopenharmony_ci } 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci *mod = true; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic int plca_get_cfg_prepare_data(const struct ethnl_req_info *req_base, 5562306a36Sopenharmony_ci struct ethnl_reply_data *reply_base, 5662306a36Sopenharmony_ci const struct genl_info *info) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct plca_reply_data *data = PLCA_REPDATA(reply_base); 5962306a36Sopenharmony_ci struct net_device *dev = reply_base->dev; 6062306a36Sopenharmony_ci const struct ethtool_phy_ops *ops; 6162306a36Sopenharmony_ci int ret; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci // check that the PHY device is available and connected 6462306a36Sopenharmony_ci if (!dev->phydev) { 6562306a36Sopenharmony_ci ret = -EOPNOTSUPP; 6662306a36Sopenharmony_ci goto out; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci // note: rtnl_lock is held already by ethnl_default_doit 7062306a36Sopenharmony_ci ops = ethtool_phy_ops; 7162306a36Sopenharmony_ci if (!ops || !ops->get_plca_cfg) { 7262306a36Sopenharmony_ci ret = -EOPNOTSUPP; 7362306a36Sopenharmony_ci goto out; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci ret = ethnl_ops_begin(dev); 7762306a36Sopenharmony_ci if (ret < 0) 7862306a36Sopenharmony_ci goto out; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci memset(&data->plca_cfg, 0xff, 8162306a36Sopenharmony_ci sizeof_field(struct plca_reply_data, plca_cfg)); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci ret = ops->get_plca_cfg(dev->phydev, &data->plca_cfg); 8462306a36Sopenharmony_ci ethnl_ops_complete(dev); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ciout: 8762306a36Sopenharmony_ci return ret; 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic int plca_get_cfg_reply_size(const struct ethnl_req_info *req_base, 9162306a36Sopenharmony_ci const struct ethnl_reply_data *reply_base) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci return nla_total_size(sizeof(u16)) + /* _VERSION */ 9462306a36Sopenharmony_ci nla_total_size(sizeof(u8)) + /* _ENABLED */ 9562306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _NODE_CNT */ 9662306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _NODE_ID */ 9762306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _TO_TIMER */ 9862306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _BURST_COUNT */ 9962306a36Sopenharmony_ci nla_total_size(sizeof(u32)); /* _BURST_TIMER */ 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic int plca_get_cfg_fill_reply(struct sk_buff *skb, 10362306a36Sopenharmony_ci const struct ethnl_req_info *req_base, 10462306a36Sopenharmony_ci const struct ethnl_reply_data *reply_base) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci const struct plca_reply_data *data = PLCA_REPDATA(reply_base); 10762306a36Sopenharmony_ci const struct phy_plca_cfg *plca = &data->plca_cfg; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if ((plca->version >= 0 && 11062306a36Sopenharmony_ci nla_put_u16(skb, ETHTOOL_A_PLCA_VERSION, plca->version)) || 11162306a36Sopenharmony_ci (plca->enabled >= 0 && 11262306a36Sopenharmony_ci nla_put_u8(skb, ETHTOOL_A_PLCA_ENABLED, !!plca->enabled)) || 11362306a36Sopenharmony_ci (plca->node_id >= 0 && 11462306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_PLCA_NODE_ID, plca->node_id)) || 11562306a36Sopenharmony_ci (plca->node_cnt >= 0 && 11662306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_PLCA_NODE_CNT, plca->node_cnt)) || 11762306a36Sopenharmony_ci (plca->to_tmr >= 0 && 11862306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_PLCA_TO_TMR, plca->to_tmr)) || 11962306a36Sopenharmony_ci (plca->burst_cnt >= 0 && 12062306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_PLCA_BURST_CNT, plca->burst_cnt)) || 12162306a36Sopenharmony_ci (plca->burst_tmr >= 0 && 12262306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_PLCA_BURST_TMR, plca->burst_tmr))) 12362306a36Sopenharmony_ci return -EMSGSIZE; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci}; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci// PLCA set configuration message ------------------------------------------- // 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ciconst struct nla_policy ethnl_plca_set_cfg_policy[] = { 13162306a36Sopenharmony_ci [ETHTOOL_A_PLCA_HEADER] = 13262306a36Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 13362306a36Sopenharmony_ci [ETHTOOL_A_PLCA_ENABLED] = NLA_POLICY_MAX(NLA_U8, 1), 13462306a36Sopenharmony_ci [ETHTOOL_A_PLCA_NODE_ID] = NLA_POLICY_MAX(NLA_U32, 255), 13562306a36Sopenharmony_ci [ETHTOOL_A_PLCA_NODE_CNT] = NLA_POLICY_RANGE(NLA_U32, 1, 255), 13662306a36Sopenharmony_ci [ETHTOOL_A_PLCA_TO_TMR] = NLA_POLICY_MAX(NLA_U32, 255), 13762306a36Sopenharmony_ci [ETHTOOL_A_PLCA_BURST_CNT] = NLA_POLICY_MAX(NLA_U32, 255), 13862306a36Sopenharmony_ci [ETHTOOL_A_PLCA_BURST_TMR] = NLA_POLICY_MAX(NLA_U32, 255), 13962306a36Sopenharmony_ci}; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic int 14262306a36Sopenharmony_ciethnl_set_plca(struct ethnl_req_info *req_info, struct genl_info *info) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct net_device *dev = req_info->dev; 14562306a36Sopenharmony_ci const struct ethtool_phy_ops *ops; 14662306a36Sopenharmony_ci struct nlattr **tb = info->attrs; 14762306a36Sopenharmony_ci struct phy_plca_cfg plca_cfg; 14862306a36Sopenharmony_ci bool mod = false; 14962306a36Sopenharmony_ci int ret; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci // check that the PHY device is available and connected 15262306a36Sopenharmony_ci if (!dev->phydev) 15362306a36Sopenharmony_ci return -EOPNOTSUPP; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci ops = ethtool_phy_ops; 15662306a36Sopenharmony_ci if (!ops || !ops->set_plca_cfg) 15762306a36Sopenharmony_ci return -EOPNOTSUPP; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci memset(&plca_cfg, 0xff, sizeof(plca_cfg)); 16062306a36Sopenharmony_ci plca_update_sint(&plca_cfg.enabled, tb, ETHTOOL_A_PLCA_ENABLED, &mod); 16162306a36Sopenharmony_ci plca_update_sint(&plca_cfg.node_id, tb, ETHTOOL_A_PLCA_NODE_ID, &mod); 16262306a36Sopenharmony_ci plca_update_sint(&plca_cfg.node_cnt, tb, ETHTOOL_A_PLCA_NODE_CNT, &mod); 16362306a36Sopenharmony_ci plca_update_sint(&plca_cfg.to_tmr, tb, ETHTOOL_A_PLCA_TO_TMR, &mod); 16462306a36Sopenharmony_ci plca_update_sint(&plca_cfg.burst_cnt, tb, ETHTOOL_A_PLCA_BURST_CNT, 16562306a36Sopenharmony_ci &mod); 16662306a36Sopenharmony_ci plca_update_sint(&plca_cfg.burst_tmr, tb, ETHTOOL_A_PLCA_BURST_TMR, 16762306a36Sopenharmony_ci &mod); 16862306a36Sopenharmony_ci if (!mod) 16962306a36Sopenharmony_ci return 0; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci ret = ops->set_plca_cfg(dev->phydev, &plca_cfg, info->extack); 17262306a36Sopenharmony_ci return ret < 0 ? ret : 1; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ciconst struct ethnl_request_ops ethnl_plca_cfg_request_ops = { 17662306a36Sopenharmony_ci .request_cmd = ETHTOOL_MSG_PLCA_GET_CFG, 17762306a36Sopenharmony_ci .reply_cmd = ETHTOOL_MSG_PLCA_GET_CFG_REPLY, 17862306a36Sopenharmony_ci .hdr_attr = ETHTOOL_A_PLCA_HEADER, 17962306a36Sopenharmony_ci .req_info_size = sizeof(struct plca_req_info), 18062306a36Sopenharmony_ci .reply_data_size = sizeof(struct plca_reply_data), 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci .prepare_data = plca_get_cfg_prepare_data, 18362306a36Sopenharmony_ci .reply_size = plca_get_cfg_reply_size, 18462306a36Sopenharmony_ci .fill_reply = plca_get_cfg_fill_reply, 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci .set = ethnl_set_plca, 18762306a36Sopenharmony_ci .set_ntf_cmd = ETHTOOL_MSG_PLCA_NTF, 18862306a36Sopenharmony_ci}; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci// PLCA get status message -------------------------------------------------- // 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ciconst struct nla_policy ethnl_plca_get_status_policy[] = { 19362306a36Sopenharmony_ci [ETHTOOL_A_PLCA_HEADER] = 19462306a36Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 19562306a36Sopenharmony_ci}; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic int plca_get_status_prepare_data(const struct ethnl_req_info *req_base, 19862306a36Sopenharmony_ci struct ethnl_reply_data *reply_base, 19962306a36Sopenharmony_ci const struct genl_info *info) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct plca_reply_data *data = PLCA_REPDATA(reply_base); 20262306a36Sopenharmony_ci struct net_device *dev = reply_base->dev; 20362306a36Sopenharmony_ci const struct ethtool_phy_ops *ops; 20462306a36Sopenharmony_ci int ret; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci // check that the PHY device is available and connected 20762306a36Sopenharmony_ci if (!dev->phydev) { 20862306a36Sopenharmony_ci ret = -EOPNOTSUPP; 20962306a36Sopenharmony_ci goto out; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci // note: rtnl_lock is held already by ethnl_default_doit 21362306a36Sopenharmony_ci ops = ethtool_phy_ops; 21462306a36Sopenharmony_ci if (!ops || !ops->get_plca_status) { 21562306a36Sopenharmony_ci ret = -EOPNOTSUPP; 21662306a36Sopenharmony_ci goto out; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci ret = ethnl_ops_begin(dev); 22062306a36Sopenharmony_ci if (ret < 0) 22162306a36Sopenharmony_ci goto out; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci memset(&data->plca_st, 0xff, 22462306a36Sopenharmony_ci sizeof_field(struct plca_reply_data, plca_st)); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci ret = ops->get_plca_status(dev->phydev, &data->plca_st); 22762306a36Sopenharmony_ci ethnl_ops_complete(dev); 22862306a36Sopenharmony_ciout: 22962306a36Sopenharmony_ci return ret; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int plca_get_status_reply_size(const struct ethnl_req_info *req_base, 23362306a36Sopenharmony_ci const struct ethnl_reply_data *reply_base) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci return nla_total_size(sizeof(u8)); /* _STATUS */ 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic int plca_get_status_fill_reply(struct sk_buff *skb, 23962306a36Sopenharmony_ci const struct ethnl_req_info *req_base, 24062306a36Sopenharmony_ci const struct ethnl_reply_data *reply_base) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci const struct plca_reply_data *data = PLCA_REPDATA(reply_base); 24362306a36Sopenharmony_ci const u8 status = data->plca_st.pst; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci if (nla_put_u8(skb, ETHTOOL_A_PLCA_STATUS, !!status)) 24662306a36Sopenharmony_ci return -EMSGSIZE; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci}; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ciconst struct ethnl_request_ops ethnl_plca_status_request_ops = { 25262306a36Sopenharmony_ci .request_cmd = ETHTOOL_MSG_PLCA_GET_STATUS, 25362306a36Sopenharmony_ci .reply_cmd = ETHTOOL_MSG_PLCA_GET_STATUS_REPLY, 25462306a36Sopenharmony_ci .hdr_attr = ETHTOOL_A_PLCA_HEADER, 25562306a36Sopenharmony_ci .req_info_size = sizeof(struct plca_req_info), 25662306a36Sopenharmony_ci .reply_data_size = sizeof(struct plca_reply_data), 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci .prepare_data = plca_get_status_prepare_data, 25962306a36Sopenharmony_ci .reply_size = plca_get_status_reply_size, 26062306a36Sopenharmony_ci .fill_reply = plca_get_status_fill_reply, 26162306a36Sopenharmony_ci}; 262