13d2b847fSMichal Kubecek // SPDX-License-Identifier: GPL-2.0-only 23d2b847fSMichal Kubecek 33d2b847fSMichal Kubecek #include "netlink.h" 43d2b847fSMichal Kubecek #include "common.h" 580660219SOleksij Rempel #include <linux/phy.h> 63d2b847fSMichal Kubecek 73d2b847fSMichal Kubecek struct linkstate_req_info { 83d2b847fSMichal Kubecek struct ethnl_req_info base; 93d2b847fSMichal Kubecek }; 103d2b847fSMichal Kubecek 113d2b847fSMichal Kubecek struct linkstate_reply_data { 123d2b847fSMichal Kubecek struct ethnl_reply_data base; 133d2b847fSMichal Kubecek int link; 1480660219SOleksij Rempel int sqi; 1580660219SOleksij Rempel int sqi_max; 169a0f830fSJakub Kicinski struct ethtool_link_ext_stats link_stats; 17ecc31c60SAmit Cohen bool link_ext_state_provided; 18ecc31c60SAmit Cohen struct ethtool_link_ext_state_info ethtool_link_ext_state_info; 193d2b847fSMichal Kubecek }; 203d2b847fSMichal Kubecek 213d2b847fSMichal Kubecek #define LINKSTATE_REPDATA(__reply_base) \ 223d2b847fSMichal Kubecek container_of(__reply_base, struct linkstate_reply_data, base) 233d2b847fSMichal Kubecek 24ff419afaSJakub Kicinski const struct nla_policy ethnl_linkstate_get_policy[] = { 25329d9c33SJakub Kicinski [ETHTOOL_A_LINKSTATE_HEADER] = 269a0f830fSJakub Kicinski NLA_POLICY_NESTED(ethnl_header_policy_stats), 273d2b847fSMichal Kubecek }; 283d2b847fSMichal Kubecek 2980660219SOleksij Rempel static int linkstate_get_sqi(struct net_device *dev) 3080660219SOleksij Rempel { 3180660219SOleksij Rempel struct phy_device *phydev = dev->phydev; 3280660219SOleksij Rempel int ret; 3380660219SOleksij Rempel 3480660219SOleksij Rempel if (!phydev) 3580660219SOleksij Rempel return -EOPNOTSUPP; 3680660219SOleksij Rempel 3780660219SOleksij Rempel mutex_lock(&phydev->lock); 3880660219SOleksij Rempel if (!phydev->drv || !phydev->drv->get_sqi) 3980660219SOleksij Rempel ret = -EOPNOTSUPP; 40*feeeeb4cSOleksij Rempel else if (!phydev->link) 41*feeeeb4cSOleksij Rempel ret = -ENETDOWN; 4280660219SOleksij Rempel else 4380660219SOleksij Rempel ret = phydev->drv->get_sqi(phydev); 4480660219SOleksij Rempel mutex_unlock(&phydev->lock); 4580660219SOleksij Rempel 4680660219SOleksij Rempel return ret; 4780660219SOleksij Rempel } 4880660219SOleksij Rempel 4980660219SOleksij Rempel static int linkstate_get_sqi_max(struct net_device *dev) 5080660219SOleksij Rempel { 5180660219SOleksij Rempel struct phy_device *phydev = dev->phydev; 5280660219SOleksij Rempel int ret; 5380660219SOleksij Rempel 5480660219SOleksij Rempel if (!phydev) 5580660219SOleksij Rempel return -EOPNOTSUPP; 5680660219SOleksij Rempel 5780660219SOleksij Rempel mutex_lock(&phydev->lock); 5880660219SOleksij Rempel if (!phydev->drv || !phydev->drv->get_sqi_max) 5980660219SOleksij Rempel ret = -EOPNOTSUPP; 60*feeeeb4cSOleksij Rempel else if (!phydev->link) 61*feeeeb4cSOleksij Rempel ret = -ENETDOWN; 6280660219SOleksij Rempel else 6380660219SOleksij Rempel ret = phydev->drv->get_sqi_max(phydev); 6480660219SOleksij Rempel mutex_unlock(&phydev->lock); 6580660219SOleksij Rempel 6680660219SOleksij Rempel return ret; 67ecc31c60SAmit Cohen }; 68ecc31c60SAmit Cohen 69*feeeeb4cSOleksij Rempel static bool linkstate_sqi_critical_error(int sqi) 70*feeeeb4cSOleksij Rempel { 71*feeeeb4cSOleksij Rempel return sqi < 0 && sqi != -EOPNOTSUPP && sqi != -ENETDOWN; 72*feeeeb4cSOleksij Rempel } 73*feeeeb4cSOleksij Rempel 74*feeeeb4cSOleksij Rempel static bool linkstate_sqi_valid(struct linkstate_reply_data *data) 75*feeeeb4cSOleksij Rempel { 76*feeeeb4cSOleksij Rempel return data->sqi >= 0 && data->sqi_max >= 0 && 77*feeeeb4cSOleksij Rempel data->sqi <= data->sqi_max; 78*feeeeb4cSOleksij Rempel } 79*feeeeb4cSOleksij Rempel 80ecc31c60SAmit Cohen static int linkstate_get_link_ext_state(struct net_device *dev, 81ecc31c60SAmit Cohen struct linkstate_reply_data *data) 82ecc31c60SAmit Cohen { 83ecc31c60SAmit Cohen int err; 84ecc31c60SAmit Cohen 85ecc31c60SAmit Cohen if (!dev->ethtool_ops->get_link_ext_state) 86ecc31c60SAmit Cohen return -EOPNOTSUPP; 87ecc31c60SAmit Cohen 88ecc31c60SAmit Cohen err = dev->ethtool_ops->get_link_ext_state(dev, &data->ethtool_link_ext_state_info); 89ecc31c60SAmit Cohen if (err) 90ecc31c60SAmit Cohen return err; 91ecc31c60SAmit Cohen 92ecc31c60SAmit Cohen data->link_ext_state_provided = true; 93ecc31c60SAmit Cohen 94ecc31c60SAmit Cohen return 0; 9580660219SOleksij Rempel } 9680660219SOleksij Rempel 973d2b847fSMichal Kubecek static int linkstate_prepare_data(const struct ethnl_req_info *req_base, 983d2b847fSMichal Kubecek struct ethnl_reply_data *reply_base, 99f946270dSJakub Kicinski const struct genl_info *info) 1003d2b847fSMichal Kubecek { 1013d2b847fSMichal Kubecek struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 1023d2b847fSMichal Kubecek struct net_device *dev = reply_base->dev; 1033d2b847fSMichal Kubecek int ret; 1043d2b847fSMichal Kubecek 1053d2b847fSMichal Kubecek ret = ethnl_ops_begin(dev); 1063d2b847fSMichal Kubecek if (ret < 0) 1073d2b847fSMichal Kubecek return ret; 1083d2b847fSMichal Kubecek data->link = __ethtool_get_link(dev); 10980660219SOleksij Rempel 11080660219SOleksij Rempel ret = linkstate_get_sqi(dev); 111*feeeeb4cSOleksij Rempel if (linkstate_sqi_critical_error(ret)) 1121ae71d99SMichal Kubecek goto out; 11380660219SOleksij Rempel data->sqi = ret; 11480660219SOleksij Rempel 11580660219SOleksij Rempel ret = linkstate_get_sqi_max(dev); 116*feeeeb4cSOleksij Rempel if (linkstate_sqi_critical_error(ret)) 1171ae71d99SMichal Kubecek goto out; 11880660219SOleksij Rempel data->sqi_max = ret; 11980660219SOleksij Rempel 120ecc31c60SAmit Cohen if (dev->flags & IFF_UP) { 121ecc31c60SAmit Cohen ret = linkstate_get_link_ext_state(dev, data); 122ecc31c60SAmit Cohen if (ret < 0 && ret != -EOPNOTSUPP && ret != -ENODATA) 123ecc31c60SAmit Cohen goto out; 124ecc31c60SAmit Cohen } 125ecc31c60SAmit Cohen 1269a0f830fSJakub Kicinski ethtool_stats_init((u64 *)&data->link_stats, 1279a0f830fSJakub Kicinski sizeof(data->link_stats) / 8); 1289a0f830fSJakub Kicinski 1299a0f830fSJakub Kicinski if (req_base->flags & ETHTOOL_FLAG_STATS) { 1309a0f830fSJakub Kicinski if (dev->phydev) 1319a0f830fSJakub Kicinski data->link_stats.link_down_events = 1329a0f830fSJakub Kicinski READ_ONCE(dev->phydev->link_down_events); 1339a0f830fSJakub Kicinski 1349a0f830fSJakub Kicinski if (dev->ethtool_ops->get_link_ext_stats) 1359a0f830fSJakub Kicinski dev->ethtool_ops->get_link_ext_stats(dev, 1369a0f830fSJakub Kicinski &data->link_stats); 1379a0f830fSJakub Kicinski } 1389a0f830fSJakub Kicinski 1391ae71d99SMichal Kubecek ret = 0; 1401ae71d99SMichal Kubecek out: 1413d2b847fSMichal Kubecek ethnl_ops_complete(dev); 1421ae71d99SMichal Kubecek return ret; 1433d2b847fSMichal Kubecek } 1443d2b847fSMichal Kubecek 1453d2b847fSMichal Kubecek static int linkstate_reply_size(const struct ethnl_req_info *req_base, 1463d2b847fSMichal Kubecek const struct ethnl_reply_data *reply_base) 1473d2b847fSMichal Kubecek { 14880660219SOleksij Rempel struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 14980660219SOleksij Rempel int len; 15080660219SOleksij Rempel 15180660219SOleksij Rempel len = nla_total_size(sizeof(u8)) /* LINKSTATE_LINK */ 1523d2b847fSMichal Kubecek + 0; 15380660219SOleksij Rempel 154*feeeeb4cSOleksij Rempel if (linkstate_sqi_valid(data)) { 155*feeeeb4cSOleksij Rempel len += nla_total_size(sizeof(u32)); /* LINKSTATE_SQI */ 156*feeeeb4cSOleksij Rempel len += nla_total_size(sizeof(u32)); /* LINKSTATE_SQI_MAX */ 157*feeeeb4cSOleksij Rempel } 15880660219SOleksij Rempel 159ecc31c60SAmit Cohen if (data->link_ext_state_provided) 160ecc31c60SAmit Cohen len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_STATE */ 161ecc31c60SAmit Cohen 162ecc31c60SAmit Cohen if (data->ethtool_link_ext_state_info.__link_ext_substate) 163ecc31c60SAmit Cohen len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_SUBSTATE */ 164ecc31c60SAmit Cohen 1659a0f830fSJakub Kicinski if (data->link_stats.link_down_events != ETHTOOL_STAT_NOT_SET) 1669a0f830fSJakub Kicinski len += nla_total_size(sizeof(u32)); 1679a0f830fSJakub Kicinski 16880660219SOleksij Rempel return len; 1693d2b847fSMichal Kubecek } 1703d2b847fSMichal Kubecek 1713d2b847fSMichal Kubecek static int linkstate_fill_reply(struct sk_buff *skb, 1723d2b847fSMichal Kubecek const struct ethnl_req_info *req_base, 1733d2b847fSMichal Kubecek const struct ethnl_reply_data *reply_base) 1743d2b847fSMichal Kubecek { 1753d2b847fSMichal Kubecek struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 1763d2b847fSMichal Kubecek 1773d2b847fSMichal Kubecek if (data->link >= 0 && 1783d2b847fSMichal Kubecek nla_put_u8(skb, ETHTOOL_A_LINKSTATE_LINK, !!data->link)) 1793d2b847fSMichal Kubecek return -EMSGSIZE; 1803d2b847fSMichal Kubecek 181*feeeeb4cSOleksij Rempel if (linkstate_sqi_valid(data)) { 182*feeeeb4cSOleksij Rempel if (nla_put_u32(skb, ETHTOOL_A_LINKSTATE_SQI, data->sqi)) 18380660219SOleksij Rempel return -EMSGSIZE; 18480660219SOleksij Rempel 185*feeeeb4cSOleksij Rempel if (nla_put_u32(skb, ETHTOOL_A_LINKSTATE_SQI_MAX, 186*feeeeb4cSOleksij Rempel data->sqi_max)) 18780660219SOleksij Rempel return -EMSGSIZE; 188*feeeeb4cSOleksij Rempel } 18980660219SOleksij Rempel 190ecc31c60SAmit Cohen if (data->link_ext_state_provided) { 191ecc31c60SAmit Cohen if (nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_STATE, 192ecc31c60SAmit Cohen data->ethtool_link_ext_state_info.link_ext_state)) 193ecc31c60SAmit Cohen return -EMSGSIZE; 194ecc31c60SAmit Cohen 195ecc31c60SAmit Cohen if (data->ethtool_link_ext_state_info.__link_ext_substate && 196ecc31c60SAmit Cohen nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_SUBSTATE, 197ecc31c60SAmit Cohen data->ethtool_link_ext_state_info.__link_ext_substate)) 198ecc31c60SAmit Cohen return -EMSGSIZE; 199ecc31c60SAmit Cohen } 200ecc31c60SAmit Cohen 2019a0f830fSJakub Kicinski if (data->link_stats.link_down_events != ETHTOOL_STAT_NOT_SET) 2029a0f830fSJakub Kicinski if (nla_put_u32(skb, ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT, 2039a0f830fSJakub Kicinski data->link_stats.link_down_events)) 2049a0f830fSJakub Kicinski return -EMSGSIZE; 2059a0f830fSJakub Kicinski 2063d2b847fSMichal Kubecek return 0; 2073d2b847fSMichal Kubecek } 2083d2b847fSMichal Kubecek 2093d2b847fSMichal Kubecek const struct ethnl_request_ops ethnl_linkstate_request_ops = { 2103d2b847fSMichal Kubecek .request_cmd = ETHTOOL_MSG_LINKSTATE_GET, 2113d2b847fSMichal Kubecek .reply_cmd = ETHTOOL_MSG_LINKSTATE_GET_REPLY, 2123d2b847fSMichal Kubecek .hdr_attr = ETHTOOL_A_LINKSTATE_HEADER, 2133d2b847fSMichal Kubecek .req_info_size = sizeof(struct linkstate_req_info), 2143d2b847fSMichal Kubecek .reply_data_size = sizeof(struct linkstate_reply_data), 2153d2b847fSMichal Kubecek 2163d2b847fSMichal Kubecek .prepare_data = linkstate_prepare_data, 2173d2b847fSMichal Kubecek .reply_size = linkstate_reply_size, 2183d2b847fSMichal Kubecek .fill_reply = linkstate_fill_reply, 2193d2b847fSMichal Kubecek }; 220