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*ecc31c60SAmit Cohen bool link_ext_state_provided; 17*ecc31c60SAmit Cohen struct ethtool_link_ext_state_info ethtool_link_ext_state_info; 183d2b847fSMichal Kubecek }; 193d2b847fSMichal Kubecek 203d2b847fSMichal Kubecek #define LINKSTATE_REPDATA(__reply_base) \ 213d2b847fSMichal Kubecek container_of(__reply_base, struct linkstate_reply_data, base) 223d2b847fSMichal Kubecek 233d2b847fSMichal Kubecek static const struct nla_policy 243d2b847fSMichal Kubecek linkstate_get_policy[ETHTOOL_A_LINKSTATE_MAX + 1] = { 253d2b847fSMichal Kubecek [ETHTOOL_A_LINKSTATE_UNSPEC] = { .type = NLA_REJECT }, 263d2b847fSMichal Kubecek [ETHTOOL_A_LINKSTATE_HEADER] = { .type = NLA_NESTED }, 273d2b847fSMichal Kubecek [ETHTOOL_A_LINKSTATE_LINK] = { .type = NLA_REJECT }, 2880660219SOleksij Rempel [ETHTOOL_A_LINKSTATE_SQI] = { .type = NLA_REJECT }, 2980660219SOleksij Rempel [ETHTOOL_A_LINKSTATE_SQI_MAX] = { .type = NLA_REJECT }, 30*ecc31c60SAmit Cohen [ETHTOOL_A_LINKSTATE_EXT_STATE] = { .type = NLA_REJECT }, 31*ecc31c60SAmit Cohen [ETHTOOL_A_LINKSTATE_EXT_SUBSTATE] = { .type = NLA_REJECT }, 323d2b847fSMichal Kubecek }; 333d2b847fSMichal Kubecek 3480660219SOleksij Rempel static int linkstate_get_sqi(struct net_device *dev) 3580660219SOleksij Rempel { 3680660219SOleksij Rempel struct phy_device *phydev = dev->phydev; 3780660219SOleksij Rempel int ret; 3880660219SOleksij Rempel 3980660219SOleksij Rempel if (!phydev) 4080660219SOleksij Rempel return -EOPNOTSUPP; 4180660219SOleksij Rempel 4280660219SOleksij Rempel mutex_lock(&phydev->lock); 4380660219SOleksij Rempel if (!phydev->drv || !phydev->drv->get_sqi) 4480660219SOleksij Rempel ret = -EOPNOTSUPP; 4580660219SOleksij Rempel else 4680660219SOleksij Rempel ret = phydev->drv->get_sqi(phydev); 4780660219SOleksij Rempel mutex_unlock(&phydev->lock); 4880660219SOleksij Rempel 4980660219SOleksij Rempel return ret; 5080660219SOleksij Rempel } 5180660219SOleksij Rempel 5280660219SOleksij Rempel static int linkstate_get_sqi_max(struct net_device *dev) 5380660219SOleksij Rempel { 5480660219SOleksij Rempel struct phy_device *phydev = dev->phydev; 5580660219SOleksij Rempel int ret; 5680660219SOleksij Rempel 5780660219SOleksij Rempel if (!phydev) 5880660219SOleksij Rempel return -EOPNOTSUPP; 5980660219SOleksij Rempel 6080660219SOleksij Rempel mutex_lock(&phydev->lock); 6180660219SOleksij Rempel if (!phydev->drv || !phydev->drv->get_sqi_max) 6280660219SOleksij Rempel ret = -EOPNOTSUPP; 6380660219SOleksij Rempel else 6480660219SOleksij Rempel ret = phydev->drv->get_sqi_max(phydev); 6580660219SOleksij Rempel mutex_unlock(&phydev->lock); 6680660219SOleksij Rempel 6780660219SOleksij Rempel return ret; 68*ecc31c60SAmit Cohen }; 69*ecc31c60SAmit Cohen 70*ecc31c60SAmit Cohen static int linkstate_get_link_ext_state(struct net_device *dev, 71*ecc31c60SAmit Cohen struct linkstate_reply_data *data) 72*ecc31c60SAmit Cohen { 73*ecc31c60SAmit Cohen int err; 74*ecc31c60SAmit Cohen 75*ecc31c60SAmit Cohen if (!dev->ethtool_ops->get_link_ext_state) 76*ecc31c60SAmit Cohen return -EOPNOTSUPP; 77*ecc31c60SAmit Cohen 78*ecc31c60SAmit Cohen err = dev->ethtool_ops->get_link_ext_state(dev, &data->ethtool_link_ext_state_info); 79*ecc31c60SAmit Cohen if (err) 80*ecc31c60SAmit Cohen return err; 81*ecc31c60SAmit Cohen 82*ecc31c60SAmit Cohen data->link_ext_state_provided = true; 83*ecc31c60SAmit Cohen 84*ecc31c60SAmit Cohen return 0; 8580660219SOleksij Rempel } 8680660219SOleksij Rempel 873d2b847fSMichal Kubecek static int linkstate_prepare_data(const struct ethnl_req_info *req_base, 883d2b847fSMichal Kubecek struct ethnl_reply_data *reply_base, 893d2b847fSMichal Kubecek struct genl_info *info) 903d2b847fSMichal Kubecek { 913d2b847fSMichal Kubecek struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 923d2b847fSMichal Kubecek struct net_device *dev = reply_base->dev; 933d2b847fSMichal Kubecek int ret; 943d2b847fSMichal Kubecek 953d2b847fSMichal Kubecek ret = ethnl_ops_begin(dev); 963d2b847fSMichal Kubecek if (ret < 0) 973d2b847fSMichal Kubecek return ret; 983d2b847fSMichal Kubecek data->link = __ethtool_get_link(dev); 9980660219SOleksij Rempel 10080660219SOleksij Rempel ret = linkstate_get_sqi(dev); 10180660219SOleksij Rempel if (ret < 0 && ret != -EOPNOTSUPP) 1021ae71d99SMichal Kubecek goto out; 10380660219SOleksij Rempel data->sqi = ret; 10480660219SOleksij Rempel 10580660219SOleksij Rempel ret = linkstate_get_sqi_max(dev); 10680660219SOleksij Rempel if (ret < 0 && ret != -EOPNOTSUPP) 1071ae71d99SMichal Kubecek goto out; 10880660219SOleksij Rempel data->sqi_max = ret; 10980660219SOleksij Rempel 110*ecc31c60SAmit Cohen if (dev->flags & IFF_UP) { 111*ecc31c60SAmit Cohen ret = linkstate_get_link_ext_state(dev, data); 112*ecc31c60SAmit Cohen if (ret < 0 && ret != -EOPNOTSUPP && ret != -ENODATA) 113*ecc31c60SAmit Cohen goto out; 114*ecc31c60SAmit Cohen } 115*ecc31c60SAmit Cohen 1161ae71d99SMichal Kubecek ret = 0; 1171ae71d99SMichal Kubecek out: 1183d2b847fSMichal Kubecek ethnl_ops_complete(dev); 1191ae71d99SMichal Kubecek return ret; 1203d2b847fSMichal Kubecek } 1213d2b847fSMichal Kubecek 1223d2b847fSMichal Kubecek static int linkstate_reply_size(const struct ethnl_req_info *req_base, 1233d2b847fSMichal Kubecek const struct ethnl_reply_data *reply_base) 1243d2b847fSMichal Kubecek { 12580660219SOleksij Rempel struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 12680660219SOleksij Rempel int len; 12780660219SOleksij Rempel 12880660219SOleksij Rempel len = nla_total_size(sizeof(u8)) /* LINKSTATE_LINK */ 1293d2b847fSMichal Kubecek + 0; 13080660219SOleksij Rempel 13180660219SOleksij Rempel if (data->sqi != -EOPNOTSUPP) 13280660219SOleksij Rempel len += nla_total_size(sizeof(u32)); 13380660219SOleksij Rempel 13480660219SOleksij Rempel if (data->sqi_max != -EOPNOTSUPP) 13580660219SOleksij Rempel len += nla_total_size(sizeof(u32)); 13680660219SOleksij Rempel 137*ecc31c60SAmit Cohen if (data->link_ext_state_provided) 138*ecc31c60SAmit Cohen len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_STATE */ 139*ecc31c60SAmit Cohen 140*ecc31c60SAmit Cohen if (data->ethtool_link_ext_state_info.__link_ext_substate) 141*ecc31c60SAmit Cohen len += nla_total_size(sizeof(u8)); /* LINKSTATE_EXT_SUBSTATE */ 142*ecc31c60SAmit Cohen 14380660219SOleksij Rempel return len; 1443d2b847fSMichal Kubecek } 1453d2b847fSMichal Kubecek 1463d2b847fSMichal Kubecek static int linkstate_fill_reply(struct sk_buff *skb, 1473d2b847fSMichal Kubecek const struct ethnl_req_info *req_base, 1483d2b847fSMichal Kubecek const struct ethnl_reply_data *reply_base) 1493d2b847fSMichal Kubecek { 1503d2b847fSMichal Kubecek struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base); 1513d2b847fSMichal Kubecek 1523d2b847fSMichal Kubecek if (data->link >= 0 && 1533d2b847fSMichal Kubecek nla_put_u8(skb, ETHTOOL_A_LINKSTATE_LINK, !!data->link)) 1543d2b847fSMichal Kubecek return -EMSGSIZE; 1553d2b847fSMichal Kubecek 15680660219SOleksij Rempel if (data->sqi != -EOPNOTSUPP && 15780660219SOleksij Rempel nla_put_u32(skb, ETHTOOL_A_LINKSTATE_SQI, data->sqi)) 15880660219SOleksij Rempel return -EMSGSIZE; 15980660219SOleksij Rempel 16080660219SOleksij Rempel if (data->sqi_max != -EOPNOTSUPP && 16180660219SOleksij Rempel nla_put_u32(skb, ETHTOOL_A_LINKSTATE_SQI_MAX, data->sqi_max)) 16280660219SOleksij Rempel return -EMSGSIZE; 16380660219SOleksij Rempel 164*ecc31c60SAmit Cohen if (data->link_ext_state_provided) { 165*ecc31c60SAmit Cohen if (nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_STATE, 166*ecc31c60SAmit Cohen data->ethtool_link_ext_state_info.link_ext_state)) 167*ecc31c60SAmit Cohen return -EMSGSIZE; 168*ecc31c60SAmit Cohen 169*ecc31c60SAmit Cohen if (data->ethtool_link_ext_state_info.__link_ext_substate && 170*ecc31c60SAmit Cohen nla_put_u8(skb, ETHTOOL_A_LINKSTATE_EXT_SUBSTATE, 171*ecc31c60SAmit Cohen data->ethtool_link_ext_state_info.__link_ext_substate)) 172*ecc31c60SAmit Cohen return -EMSGSIZE; 173*ecc31c60SAmit Cohen } 174*ecc31c60SAmit Cohen 1753d2b847fSMichal Kubecek return 0; 1763d2b847fSMichal Kubecek } 1773d2b847fSMichal Kubecek 1783d2b847fSMichal Kubecek const struct ethnl_request_ops ethnl_linkstate_request_ops = { 1793d2b847fSMichal Kubecek .request_cmd = ETHTOOL_MSG_LINKSTATE_GET, 1803d2b847fSMichal Kubecek .reply_cmd = ETHTOOL_MSG_LINKSTATE_GET_REPLY, 1813d2b847fSMichal Kubecek .hdr_attr = ETHTOOL_A_LINKSTATE_HEADER, 1823d2b847fSMichal Kubecek .max_attr = ETHTOOL_A_LINKSTATE_MAX, 1833d2b847fSMichal Kubecek .req_info_size = sizeof(struct linkstate_req_info), 1843d2b847fSMichal Kubecek .reply_data_size = sizeof(struct linkstate_reply_data), 1853d2b847fSMichal Kubecek .request_policy = linkstate_get_policy, 1863d2b847fSMichal Kubecek 1873d2b847fSMichal Kubecek .prepare_data = linkstate_prepare_data, 1883d2b847fSMichal Kubecek .reply_size = linkstate_reply_size, 1893d2b847fSMichal Kubecek .fill_reply = linkstate_fill_reply, 1903d2b847fSMichal Kubecek }; 191