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; 16*9a0f830fSJakub 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] = 26*9a0f830fSJakub 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; 4080660219SOleksij Rempel else 4180660219SOleksij Rempel ret = phydev->drv->get_sqi(phydev); 4280660219SOleksij Rempel mutex_unlock(&phydev->lock); 4380660219SOleksij Rempel 4480660219SOleksij Rempel return ret; 4580660219SOleksij Rempel } 4680660219SOleksij Rempel 4780660219SOleksij Rempel static int linkstate_get_sqi_max(struct net_device *dev) 4880660219SOleksij Rempel { 4980660219SOleksij Rempel struct phy_device *phydev = dev->phydev; 5080660219SOleksij Rempel int ret; 5180660219SOleksij Rempel 5280660219SOleksij Rempel if (!phydev) 5380660219SOleksij Rempel return -EOPNOTSUPP; 5480660219SOleksij Rempel 5580660219SOleksij Rempel mutex_lock(&phydev->lock); 5680660219SOleksij Rempel if (!phydev->drv || !phydev->drv->get_sqi_max) 5780660219SOleksij Rempel ret = -EOPNOTSUPP; 5880660219SOleksij Rempel else 5980660219SOleksij Rempel ret = phydev->drv->get_sqi_max(phydev); 6080660219SOleksij Rempel mutex_unlock(&phydev->lock); 6180660219SOleksij Rempel 6280660219SOleksij Rempel return ret; 63ecc31c60SAmit Cohen }; 64ecc31c60SAmit Cohen 65ecc31c60SAmit Cohen static int linkstate_get_link_ext_state(struct net_device *dev, 66ecc31c60SAmit Cohen struct linkstate_reply_data *data) 67ecc31c60SAmit Cohen { 68ecc31c60SAmit Cohen int err; 69ecc31c60SAmit Cohen 70ecc31c60SAmit Cohen if (!dev->ethtool_ops->get_link_ext_state) 71ecc31c60SAmit Cohen return -EOPNOTSUPP; 72ecc31c60SAmit Cohen 73ecc31c60SAmit Cohen err = dev->ethtool_ops->get_link_ext_state(dev, &data->ethtool_link_ext_state_info); 74ecc31c60SAmit Cohen if (err) 75ecc31c60SAmit Cohen return err; 76ecc31c60SAmit Cohen 77ecc31c60SAmit Cohen data->link_ext_state_provided = true; 78ecc31c60SAmit Cohen 79ecc31c60SAmit Cohen return 0; 8080660219SOleksij Rempel } 8180660219SOleksij Rempel 823d2b847fSMichal Kubecek static int linkstate_prepare_data(const struct ethnl_req_info *req_base, 833d2b847fSMichal Kubecek struct ethnl_reply_data *reply_base, 843d2b847fSMichal Kubecek struct genl_info *info) 853d2b847fSMichal Kubecek { 863d2b847fSMichal Kubecek struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 873d2b847fSMichal Kubecek struct net_device *dev = reply_base->dev; 883d2b847fSMichal Kubecek int ret; 893d2b847fSMichal Kubecek 903d2b847fSMichal Kubecek ret = ethnl_ops_begin(dev); 913d2b847fSMichal Kubecek if (ret < 0) 923d2b847fSMichal Kubecek return ret; 933d2b847fSMichal Kubecek data->link = __ethtool_get_link(dev); 9480660219SOleksij Rempel 9580660219SOleksij Rempel ret = linkstate_get_sqi(dev); 9680660219SOleksij Rempel if (ret < 0 && ret != -EOPNOTSUPP) 971ae71d99SMichal Kubecek goto out; 9880660219SOleksij Rempel data->sqi = ret; 9980660219SOleksij Rempel 10080660219SOleksij Rempel ret = linkstate_get_sqi_max(dev); 10180660219SOleksij Rempel if (ret < 0 && ret != -EOPNOTSUPP) 1021ae71d99SMichal Kubecek goto out; 10380660219SOleksij Rempel data->sqi_max = ret; 10480660219SOleksij Rempel 105ecc31c60SAmit Cohen if (dev->flags & IFF_UP) { 106ecc31c60SAmit Cohen ret = linkstate_get_link_ext_state(dev, data); 107ecc31c60SAmit Cohen if (ret < 0 && ret != -EOPNOTSUPP && ret != -ENODATA) 108ecc31c60SAmit Cohen goto out; 109ecc31c60SAmit Cohen } 110ecc31c60SAmit Cohen 111*9a0f830fSJakub Kicinski ethtool_stats_init((u64 *)&data->link_stats, 112*9a0f830fSJakub Kicinski sizeof(data->link_stats) / 8); 113*9a0f830fSJakub Kicinski 114*9a0f830fSJakub Kicinski if (req_base->flags & ETHTOOL_FLAG_STATS) { 115*9a0f830fSJakub Kicinski if (dev->phydev) 116*9a0f830fSJakub Kicinski data->link_stats.link_down_events = 117*9a0f830fSJakub Kicinski READ_ONCE(dev->phydev->link_down_events); 118*9a0f830fSJakub Kicinski 119*9a0f830fSJakub Kicinski if (dev->ethtool_ops->get_link_ext_stats) 120*9a0f830fSJakub Kicinski dev->ethtool_ops->get_link_ext_stats(dev, 121*9a0f830fSJakub Kicinski &data->link_stats); 122*9a0f830fSJakub Kicinski } 123*9a0f830fSJakub Kicinski 1241ae71d99SMichal Kubecek ret = 0; 1251ae71d99SMichal Kubecek out: 1263d2b847fSMichal Kubecek ethnl_ops_complete(dev); 1271ae71d99SMichal Kubecek return ret; 1283d2b847fSMichal Kubecek } 1293d2b847fSMichal Kubecek 1303d2b847fSMichal Kubecek static int linkstate_reply_size(const struct ethnl_req_info *req_base, 1313d2b847fSMichal Kubecek const struct ethnl_reply_data *reply_base) 1323d2b847fSMichal Kubecek { 13380660219SOleksij Rempel struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 13480660219SOleksij Rempel int len; 13580660219SOleksij Rempel 13680660219SOleksij Rempel len = nla_total_size(sizeof(u8)) /* LINKSTATE_LINK */ 1373d2b847fSMichal Kubecek + 0; 13880660219SOleksij Rempel 13980660219SOleksij Rempel if (data->sqi != -EOPNOTSUPP) 14080660219SOleksij Rempel len += nla_total_size(sizeof(u32)); 14180660219SOleksij Rempel 14280660219SOleksij Rempel if (data->sqi_max != -EOPNOTSUPP) 14380660219SOleksij Rempel len += nla_total_size(sizeof(u32)); 14480660219SOleksij Rempel 145ecc31c60SAmit Cohen if (data->link_ext_state_provided) 146ecc31c60SAmit Cohen len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_STATE */ 147ecc31c60SAmit Cohen 148ecc31c60SAmit Cohen if (data->ethtool_link_ext_state_info.__link_ext_substate) 149ecc31c60SAmit Cohen len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_SUBSTATE */ 150ecc31c60SAmit Cohen 151*9a0f830fSJakub Kicinski if (data->link_stats.link_down_events != ETHTOOL_STAT_NOT_SET) 152*9a0f830fSJakub Kicinski len += nla_total_size(sizeof(u32)); 153*9a0f830fSJakub Kicinski 15480660219SOleksij Rempel return len; 1553d2b847fSMichal Kubecek } 1563d2b847fSMichal Kubecek 1573d2b847fSMichal Kubecek static int linkstate_fill_reply(struct sk_buff *skb, 1583d2b847fSMichal Kubecek const struct ethnl_req_info *req_base, 1593d2b847fSMichal Kubecek const struct ethnl_reply_data *reply_base) 1603d2b847fSMichal Kubecek { 1613d2b847fSMichal Kubecek struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 1623d2b847fSMichal Kubecek 1633d2b847fSMichal Kubecek if (data->link >= 0 && 1643d2b847fSMichal Kubecek nla_put_u8(skb, ETHTOOL_A_LINKSTATE_LINK, !!data->link)) 1653d2b847fSMichal Kubecek return -EMSGSIZE; 1663d2b847fSMichal Kubecek 16780660219SOleksij Rempel if (data->sqi != -EOPNOTSUPP && 16880660219SOleksij Rempel nla_put_u32(skb, ETHTOOL_A_LINKSTATE_SQI, data->sqi)) 16980660219SOleksij Rempel return -EMSGSIZE; 17080660219SOleksij Rempel 17180660219SOleksij Rempel if (data->sqi_max != -EOPNOTSUPP && 17280660219SOleksij Rempel nla_put_u32(skb, ETHTOOL_A_LINKSTATE_SQI_MAX, data->sqi_max)) 17380660219SOleksij Rempel return -EMSGSIZE; 17480660219SOleksij Rempel 175ecc31c60SAmit Cohen if (data->link_ext_state_provided) { 176ecc31c60SAmit Cohen if (nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_STATE, 177ecc31c60SAmit Cohen data->ethtool_link_ext_state_info.link_ext_state)) 178ecc31c60SAmit Cohen return -EMSGSIZE; 179ecc31c60SAmit Cohen 180ecc31c60SAmit Cohen if (data->ethtool_link_ext_state_info.__link_ext_substate && 181ecc31c60SAmit Cohen nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_SUBSTATE, 182ecc31c60SAmit Cohen data->ethtool_link_ext_state_info.__link_ext_substate)) 183ecc31c60SAmit Cohen return -EMSGSIZE; 184ecc31c60SAmit Cohen } 185ecc31c60SAmit Cohen 186*9a0f830fSJakub Kicinski if (data->link_stats.link_down_events != ETHTOOL_STAT_NOT_SET) 187*9a0f830fSJakub Kicinski if (nla_put_u32(skb, ETHTOOL_A_LINKSTATE_EXT_DOWN_CNT, 188*9a0f830fSJakub Kicinski data->link_stats.link_down_events)) 189*9a0f830fSJakub Kicinski return -EMSGSIZE; 190*9a0f830fSJakub Kicinski 1913d2b847fSMichal Kubecek return 0; 1923d2b847fSMichal Kubecek } 1933d2b847fSMichal Kubecek 1943d2b847fSMichal Kubecek const struct ethnl_request_ops ethnl_linkstate_request_ops = { 1953d2b847fSMichal Kubecek .request_cmd = ETHTOOL_MSG_LINKSTATE_GET, 1963d2b847fSMichal Kubecek .reply_cmd = ETHTOOL_MSG_LINKSTATE_GET_REPLY, 1973d2b847fSMichal Kubecek .hdr_attr = ETHTOOL_A_LINKSTATE_HEADER, 1983d2b847fSMichal Kubecek .req_info_size = sizeof(struct linkstate_req_info), 1993d2b847fSMichal Kubecek .reply_data_size = sizeof(struct linkstate_reply_data), 2003d2b847fSMichal Kubecek 2013d2b847fSMichal Kubecek .prepare_data = linkstate_prepare_data, 2023d2b847fSMichal Kubecek .reply_size = linkstate_reply_size, 2033d2b847fSMichal Kubecek .fill_reply = linkstate_fill_reply, 2043d2b847fSMichal Kubecek }; 205