12b30f829SVladimir Oltean // SPDX-License-Identifier: GPL-2.0-only 22b30f829SVladimir Oltean /* 32b30f829SVladimir Oltean * Copyright 2022-2023 NXP 42b30f829SVladimir Oltean */ 52b30f829SVladimir Oltean #include "common.h" 62b30f829SVladimir Oltean #include "netlink.h" 72b30f829SVladimir Oltean 82b30f829SVladimir Oltean struct mm_req_info { 92b30f829SVladimir Oltean struct ethnl_req_info base; 102b30f829SVladimir Oltean }; 112b30f829SVladimir Oltean 122b30f829SVladimir Oltean struct mm_reply_data { 132b30f829SVladimir Oltean struct ethnl_reply_data base; 142b30f829SVladimir Oltean struct ethtool_mm_state state; 152b30f829SVladimir Oltean struct ethtool_mm_stats stats; 162b30f829SVladimir Oltean }; 172b30f829SVladimir Oltean 182b30f829SVladimir Oltean #define MM_REPDATA(__reply_base) \ 192b30f829SVladimir Oltean container_of(__reply_base, struct mm_reply_data, base) 202b30f829SVladimir Oltean 212b30f829SVladimir Oltean #define ETHTOOL_MM_STAT_CNT \ 222b30f829SVladimir Oltean (__ETHTOOL_A_MM_STAT_CNT - (ETHTOOL_A_MM_STAT_PAD + 1)) 232b30f829SVladimir Oltean 242b30f829SVladimir Oltean const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1] = { 252b30f829SVladimir Oltean [ETHTOOL_A_MM_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_stats), 262b30f829SVladimir Oltean }; 272b30f829SVladimir Oltean 282b30f829SVladimir Oltean static int mm_prepare_data(const struct ethnl_req_info *req_base, 292b30f829SVladimir Oltean struct ethnl_reply_data *reply_base, 302b30f829SVladimir Oltean struct genl_info *info) 312b30f829SVladimir Oltean { 322b30f829SVladimir Oltean struct mm_reply_data *data = MM_REPDATA(reply_base); 332b30f829SVladimir Oltean struct net_device *dev = reply_base->dev; 342b30f829SVladimir Oltean const struct ethtool_ops *ops; 352b30f829SVladimir Oltean int ret; 362b30f829SVladimir Oltean 372b30f829SVladimir Oltean ops = dev->ethtool_ops; 382b30f829SVladimir Oltean 392b30f829SVladimir Oltean if (!ops->get_mm) 402b30f829SVladimir Oltean return -EOPNOTSUPP; 412b30f829SVladimir Oltean 422b30f829SVladimir Oltean ethtool_stats_init((u64 *)&data->stats, 432b30f829SVladimir Oltean sizeof(data->stats) / sizeof(u64)); 442b30f829SVladimir Oltean 452b30f829SVladimir Oltean ret = ethnl_ops_begin(dev); 462b30f829SVladimir Oltean if (ret < 0) 472b30f829SVladimir Oltean return ret; 482b30f829SVladimir Oltean 492b30f829SVladimir Oltean ret = ops->get_mm(dev, &data->state); 502b30f829SVladimir Oltean if (ret) 512b30f829SVladimir Oltean goto out_complete; 522b30f829SVladimir Oltean 532b30f829SVladimir Oltean if (ops->get_mm_stats && (req_base->flags & ETHTOOL_FLAG_STATS)) 542b30f829SVladimir Oltean ops->get_mm_stats(dev, &data->stats); 552b30f829SVladimir Oltean 562b30f829SVladimir Oltean out_complete: 572b30f829SVladimir Oltean ethnl_ops_complete(dev); 582b30f829SVladimir Oltean 592b30f829SVladimir Oltean return 0; 602b30f829SVladimir Oltean } 612b30f829SVladimir Oltean 622b30f829SVladimir Oltean static int mm_reply_size(const struct ethnl_req_info *req_base, 632b30f829SVladimir Oltean const struct ethnl_reply_data *reply_base) 642b30f829SVladimir Oltean { 652b30f829SVladimir Oltean int len = 0; 662b30f829SVladimir Oltean 672b30f829SVladimir Oltean len += nla_total_size(sizeof(u8)); /* _MM_PMAC_ENABLED */ 682b30f829SVladimir Oltean len += nla_total_size(sizeof(u8)); /* _MM_TX_ENABLED */ 692b30f829SVladimir Oltean len += nla_total_size(sizeof(u8)); /* _MM_TX_ACTIVE */ 702b30f829SVladimir Oltean len += nla_total_size(sizeof(u8)); /* _MM_VERIFY_ENABLED */ 712b30f829SVladimir Oltean len += nla_total_size(sizeof(u8)); /* _MM_VERIFY_STATUS */ 722b30f829SVladimir Oltean len += nla_total_size(sizeof(u32)); /* _MM_VERIFY_TIME */ 732b30f829SVladimir Oltean len += nla_total_size(sizeof(u32)); /* _MM_MAX_VERIFY_TIME */ 742b30f829SVladimir Oltean len += nla_total_size(sizeof(u32)); /* _MM_TX_MIN_FRAG_SIZE */ 752b30f829SVladimir Oltean len += nla_total_size(sizeof(u32)); /* _MM_RX_MIN_FRAG_SIZE */ 762b30f829SVladimir Oltean 772b30f829SVladimir Oltean if (req_base->flags & ETHTOOL_FLAG_STATS) 782b30f829SVladimir Oltean len += nla_total_size(0) + /* _MM_STATS */ 792b30f829SVladimir Oltean nla_total_size_64bit(sizeof(u64)) * ETHTOOL_MM_STAT_CNT; 802b30f829SVladimir Oltean 812b30f829SVladimir Oltean return len; 822b30f829SVladimir Oltean } 832b30f829SVladimir Oltean 842b30f829SVladimir Oltean static int mm_put_stat(struct sk_buff *skb, u64 val, u16 attrtype) 852b30f829SVladimir Oltean { 862b30f829SVladimir Oltean if (val == ETHTOOL_STAT_NOT_SET) 872b30f829SVladimir Oltean return 0; 882b30f829SVladimir Oltean if (nla_put_u64_64bit(skb, attrtype, val, ETHTOOL_A_MM_STAT_PAD)) 892b30f829SVladimir Oltean return -EMSGSIZE; 902b30f829SVladimir Oltean return 0; 912b30f829SVladimir Oltean } 922b30f829SVladimir Oltean 932b30f829SVladimir Oltean static int mm_put_stats(struct sk_buff *skb, 942b30f829SVladimir Oltean const struct ethtool_mm_stats *stats) 952b30f829SVladimir Oltean { 962b30f829SVladimir Oltean struct nlattr *nest; 972b30f829SVladimir Oltean 982b30f829SVladimir Oltean nest = nla_nest_start(skb, ETHTOOL_A_MM_STATS); 992b30f829SVladimir Oltean if (!nest) 1002b30f829SVladimir Oltean return -EMSGSIZE; 1012b30f829SVladimir Oltean 1022b30f829SVladimir Oltean if (mm_put_stat(skb, stats->MACMergeFrameAssErrorCount, 1032b30f829SVladimir Oltean ETHTOOL_A_MM_STAT_REASSEMBLY_ERRORS) || 1042b30f829SVladimir Oltean mm_put_stat(skb, stats->MACMergeFrameSmdErrorCount, 1052b30f829SVladimir Oltean ETHTOOL_A_MM_STAT_SMD_ERRORS) || 1062b30f829SVladimir Oltean mm_put_stat(skb, stats->MACMergeFrameAssOkCount, 1072b30f829SVladimir Oltean ETHTOOL_A_MM_STAT_REASSEMBLY_OK) || 1082b30f829SVladimir Oltean mm_put_stat(skb, stats->MACMergeFragCountRx, 1092b30f829SVladimir Oltean ETHTOOL_A_MM_STAT_RX_FRAG_COUNT) || 1102b30f829SVladimir Oltean mm_put_stat(skb, stats->MACMergeFragCountTx, 1112b30f829SVladimir Oltean ETHTOOL_A_MM_STAT_TX_FRAG_COUNT) || 1122b30f829SVladimir Oltean mm_put_stat(skb, stats->MACMergeHoldCount, 1132b30f829SVladimir Oltean ETHTOOL_A_MM_STAT_HOLD_COUNT)) 1142b30f829SVladimir Oltean goto err_cancel; 1152b30f829SVladimir Oltean 1162b30f829SVladimir Oltean nla_nest_end(skb, nest); 1172b30f829SVladimir Oltean return 0; 1182b30f829SVladimir Oltean 1192b30f829SVladimir Oltean err_cancel: 1202b30f829SVladimir Oltean nla_nest_cancel(skb, nest); 1212b30f829SVladimir Oltean return -EMSGSIZE; 1222b30f829SVladimir Oltean } 1232b30f829SVladimir Oltean 1242b30f829SVladimir Oltean static int mm_fill_reply(struct sk_buff *skb, 1252b30f829SVladimir Oltean const struct ethnl_req_info *req_base, 1262b30f829SVladimir Oltean const struct ethnl_reply_data *reply_base) 1272b30f829SVladimir Oltean { 1282b30f829SVladimir Oltean const struct mm_reply_data *data = MM_REPDATA(reply_base); 1292b30f829SVladimir Oltean const struct ethtool_mm_state *state = &data->state; 1302b30f829SVladimir Oltean 1312b30f829SVladimir Oltean if (nla_put_u8(skb, ETHTOOL_A_MM_TX_ENABLED, state->tx_enabled) || 1322b30f829SVladimir Oltean nla_put_u8(skb, ETHTOOL_A_MM_TX_ACTIVE, state->tx_active) || 1332b30f829SVladimir Oltean nla_put_u8(skb, ETHTOOL_A_MM_PMAC_ENABLED, state->pmac_enabled) || 1342b30f829SVladimir Oltean nla_put_u8(skb, ETHTOOL_A_MM_VERIFY_ENABLED, state->verify_enabled) || 1352b30f829SVladimir Oltean nla_put_u8(skb, ETHTOOL_A_MM_VERIFY_STATUS, state->verify_status) || 1362b30f829SVladimir Oltean nla_put_u32(skb, ETHTOOL_A_MM_VERIFY_TIME, state->verify_time) || 1372b30f829SVladimir Oltean nla_put_u32(skb, ETHTOOL_A_MM_MAX_VERIFY_TIME, state->max_verify_time) || 1382b30f829SVladimir Oltean nla_put_u32(skb, ETHTOOL_A_MM_TX_MIN_FRAG_SIZE, state->tx_min_frag_size) || 1392b30f829SVladimir Oltean nla_put_u32(skb, ETHTOOL_A_MM_RX_MIN_FRAG_SIZE, state->rx_min_frag_size)) 1402b30f829SVladimir Oltean return -EMSGSIZE; 1412b30f829SVladimir Oltean 1422b30f829SVladimir Oltean if (req_base->flags & ETHTOOL_FLAG_STATS && 1432b30f829SVladimir Oltean mm_put_stats(skb, &data->stats)) 1442b30f829SVladimir Oltean return -EMSGSIZE; 1452b30f829SVladimir Oltean 1462b30f829SVladimir Oltean return 0; 1472b30f829SVladimir Oltean } 1482b30f829SVladimir Oltean 1492b30f829SVladimir Oltean const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1] = { 1502b30f829SVladimir Oltean [ETHTOOL_A_MM_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), 1512b30f829SVladimir Oltean [ETHTOOL_A_MM_VERIFY_ENABLED] = NLA_POLICY_MAX(NLA_U8, 1), 1522b30f829SVladimir Oltean [ETHTOOL_A_MM_VERIFY_TIME] = NLA_POLICY_RANGE(NLA_U32, 1, 128), 1532b30f829SVladimir Oltean [ETHTOOL_A_MM_TX_ENABLED] = NLA_POLICY_MAX(NLA_U8, 1), 1542b30f829SVladimir Oltean [ETHTOOL_A_MM_PMAC_ENABLED] = NLA_POLICY_MAX(NLA_U8, 1), 1552b30f829SVladimir Oltean [ETHTOOL_A_MM_TX_MIN_FRAG_SIZE] = NLA_POLICY_RANGE(NLA_U32, 60, 252), 1562b30f829SVladimir Oltean }; 1572b30f829SVladimir Oltean 1582b30f829SVladimir Oltean static void mm_state_to_cfg(const struct ethtool_mm_state *state, 1592b30f829SVladimir Oltean struct ethtool_mm_cfg *cfg) 1602b30f829SVladimir Oltean { 1612b30f829SVladimir Oltean /* We could also compare state->verify_status against 1622b30f829SVladimir Oltean * ETHTOOL_MM_VERIFY_STATUS_DISABLED, but state->verify_enabled 1632b30f829SVladimir Oltean * is more like an administrative state which should be seen in 1642b30f829SVladimir Oltean * ETHTOOL_MSG_MM_GET replies. For example, a port with verification 1652b30f829SVladimir Oltean * disabled might be in the ETHTOOL_MM_VERIFY_STATUS_INITIAL 1662b30f829SVladimir Oltean * if it's down. 1672b30f829SVladimir Oltean */ 1682b30f829SVladimir Oltean cfg->verify_enabled = state->verify_enabled; 1692b30f829SVladimir Oltean cfg->verify_time = state->verify_time; 1702b30f829SVladimir Oltean cfg->tx_enabled = state->tx_enabled; 1712b30f829SVladimir Oltean cfg->pmac_enabled = state->pmac_enabled; 1722b30f829SVladimir Oltean cfg->tx_min_frag_size = state->tx_min_frag_size; 1732b30f829SVladimir Oltean } 1742b30f829SVladimir Oltean 175*04007961SJakub Kicinski static int 176*04007961SJakub Kicinski ethnl_set_mm_validate(struct ethnl_req_info *req_info, struct genl_info *info) 177*04007961SJakub Kicinski { 178*04007961SJakub Kicinski const struct ethtool_ops *ops = req_info->dev->ethtool_ops; 179*04007961SJakub Kicinski 180*04007961SJakub Kicinski return ops->get_mm && ops->set_mm ? 1 : -EOPNOTSUPP; 181*04007961SJakub Kicinski } 182*04007961SJakub Kicinski 183*04007961SJakub Kicinski static int ethnl_set_mm(struct ethnl_req_info *req_info, struct genl_info *info) 1842b30f829SVladimir Oltean { 1852b30f829SVladimir Oltean struct netlink_ext_ack *extack = info->extack; 186*04007961SJakub Kicinski struct net_device *dev = req_info->dev; 1872b30f829SVladimir Oltean struct ethtool_mm_state state = {}; 1882b30f829SVladimir Oltean struct nlattr **tb = info->attrs; 1892b30f829SVladimir Oltean struct ethtool_mm_cfg cfg = {}; 1902b30f829SVladimir Oltean bool mod = false; 1912b30f829SVladimir Oltean int ret; 1922b30f829SVladimir Oltean 193*04007961SJakub Kicinski ret = dev->ethtool_ops->get_mm(dev, &state); 1942b30f829SVladimir Oltean if (ret) 1952b30f829SVladimir Oltean return ret; 1962b30f829SVladimir Oltean 1972b30f829SVladimir Oltean mm_state_to_cfg(&state, &cfg); 1982b30f829SVladimir Oltean 1992b30f829SVladimir Oltean ethnl_update_bool(&cfg.verify_enabled, tb[ETHTOOL_A_MM_VERIFY_ENABLED], 2002b30f829SVladimir Oltean &mod); 2012b30f829SVladimir Oltean ethnl_update_u32(&cfg.verify_time, tb[ETHTOOL_A_MM_VERIFY_TIME], &mod); 2022b30f829SVladimir Oltean ethnl_update_bool(&cfg.tx_enabled, tb[ETHTOOL_A_MM_TX_ENABLED], &mod); 2032b30f829SVladimir Oltean ethnl_update_bool(&cfg.pmac_enabled, tb[ETHTOOL_A_MM_PMAC_ENABLED], 2042b30f829SVladimir Oltean &mod); 2052b30f829SVladimir Oltean ethnl_update_u32(&cfg.tx_min_frag_size, 2062b30f829SVladimir Oltean tb[ETHTOOL_A_MM_TX_MIN_FRAG_SIZE], &mod); 2072b30f829SVladimir Oltean 2082b30f829SVladimir Oltean if (!mod) 209*04007961SJakub Kicinski return 0; 2102b30f829SVladimir Oltean 2112b30f829SVladimir Oltean if (cfg.verify_time > state.max_verify_time) { 2122b30f829SVladimir Oltean NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_MM_VERIFY_TIME], 2132b30f829SVladimir Oltean "verifyTime exceeds device maximum"); 214*04007961SJakub Kicinski return -ERANGE; 2152b30f829SVladimir Oltean } 2162b30f829SVladimir Oltean 217*04007961SJakub Kicinski ret = dev->ethtool_ops->set_mm(dev, &cfg, extack); 218*04007961SJakub Kicinski return ret < 0 ? ret : 1; 2192b30f829SVladimir Oltean } 22004692c90SVladimir Oltean 221*04007961SJakub Kicinski const struct ethnl_request_ops ethnl_mm_request_ops = { 222*04007961SJakub Kicinski .request_cmd = ETHTOOL_MSG_MM_GET, 223*04007961SJakub Kicinski .reply_cmd = ETHTOOL_MSG_MM_GET_REPLY, 224*04007961SJakub Kicinski .hdr_attr = ETHTOOL_A_MM_HEADER, 225*04007961SJakub Kicinski .req_info_size = sizeof(struct mm_req_info), 226*04007961SJakub Kicinski .reply_data_size = sizeof(struct mm_reply_data), 227*04007961SJakub Kicinski 228*04007961SJakub Kicinski .prepare_data = mm_prepare_data, 229*04007961SJakub Kicinski .reply_size = mm_reply_size, 230*04007961SJakub Kicinski .fill_reply = mm_fill_reply, 231*04007961SJakub Kicinski 232*04007961SJakub Kicinski .set_validate = ethnl_set_mm_validate, 233*04007961SJakub Kicinski .set = ethnl_set_mm, 234*04007961SJakub Kicinski .set_ntf_cmd = ETHTOOL_MSG_MM_NTF, 235*04007961SJakub Kicinski }; 236*04007961SJakub Kicinski 23704692c90SVladimir Oltean /* Returns whether a given device supports the MAC merge layer 23804692c90SVladimir Oltean * (has an eMAC and a pMAC). Must be called under rtnl_lock() and 23904692c90SVladimir Oltean * ethnl_ops_begin(). 24004692c90SVladimir Oltean */ 24104692c90SVladimir Oltean bool __ethtool_dev_mm_supported(struct net_device *dev) 24204692c90SVladimir Oltean { 24304692c90SVladimir Oltean const struct ethtool_ops *ops = dev->ethtool_ops; 24404692c90SVladimir Oltean struct ethtool_mm_state state = {}; 24504692c90SVladimir Oltean int ret = -EOPNOTSUPP; 24604692c90SVladimir Oltean 24704692c90SVladimir Oltean if (ops && ops->get_mm) 24804692c90SVladimir Oltean ret = ops->get_mm(dev, &state); 24904692c90SVladimir Oltean 25004692c90SVladimir Oltean return !!ret; 25104692c90SVladimir Oltean } 252