1 // SPDX-License-Identifier: GPL-2.0-only 2 3 #include "netlink.h" 4 #include "common.h" 5 #include "bitset.h" 6 7 struct privflags_req_info { 8 struct ethnl_req_info base; 9 }; 10 11 struct privflags_reply_data { 12 struct ethnl_reply_data base; 13 const char (*priv_flag_names)[ETH_GSTRING_LEN]; 14 unsigned int n_priv_flags; 15 u32 priv_flags; 16 }; 17 18 #define PRIVFLAGS_REPDATA(__reply_base) \ 19 container_of(__reply_base, struct privflags_reply_data, base) 20 21 static const struct nla_policy 22 privflags_get_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = { 23 [ETHTOOL_A_PRIVFLAGS_UNSPEC] = { .type = NLA_REJECT }, 24 [ETHTOOL_A_PRIVFLAGS_HEADER] = { .type = NLA_NESTED }, 25 [ETHTOOL_A_PRIVFLAGS_FLAGS] = { .type = NLA_REJECT }, 26 }; 27 28 static int ethnl_get_priv_flags_info(struct net_device *dev, 29 unsigned int *count, 30 const char (**names)[ETH_GSTRING_LEN]) 31 { 32 const struct ethtool_ops *ops = dev->ethtool_ops; 33 int nflags; 34 35 nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS); 36 if (nflags < 0) 37 return nflags; 38 39 if (names) { 40 *names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL); 41 if (!*names) 42 return -ENOMEM; 43 ops->get_strings(dev, ETH_SS_PRIV_FLAGS, (u8 *)*names); 44 } 45 46 /* We can pass more than 32 private flags to userspace via netlink but 47 * we cannot get more with ethtool_ops::get_priv_flags(). Note that we 48 * must not adjust nflags before allocating the space for flag names 49 * as the buffer must be large enough for all flags. 50 */ 51 if (WARN_ONCE(nflags > 32, 52 "device %s reports more than 32 private flags (%d)\n", 53 netdev_name(dev), nflags)) 54 nflags = 32; 55 *count = nflags; 56 57 return 0; 58 } 59 60 static int privflags_prepare_data(const struct ethnl_req_info *req_base, 61 struct ethnl_reply_data *reply_base, 62 struct genl_info *info) 63 { 64 struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base); 65 struct net_device *dev = reply_base->dev; 66 const char (*names)[ETH_GSTRING_LEN]; 67 const struct ethtool_ops *ops; 68 unsigned int nflags; 69 int ret; 70 71 ops = dev->ethtool_ops; 72 if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings) 73 return -EOPNOTSUPP; 74 ret = ethnl_ops_begin(dev); 75 if (ret < 0) 76 return ret; 77 78 ret = ethnl_get_priv_flags_info(dev, &nflags, &names); 79 if (ret < 0) 80 goto out_ops; 81 data->priv_flags = ops->get_priv_flags(dev); 82 data->priv_flag_names = names; 83 data->n_priv_flags = nflags; 84 85 out_ops: 86 ethnl_ops_complete(dev); 87 return ret; 88 } 89 90 static int privflags_reply_size(const struct ethnl_req_info *req_base, 91 const struct ethnl_reply_data *reply_base) 92 { 93 const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base); 94 bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; 95 const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags); 96 97 return ethnl_bitset32_size(&data->priv_flags, &all_flags, 98 data->n_priv_flags, 99 data->priv_flag_names, compact); 100 } 101 102 static int privflags_fill_reply(struct sk_buff *skb, 103 const struct ethnl_req_info *req_base, 104 const struct ethnl_reply_data *reply_base) 105 { 106 const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base); 107 bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; 108 const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags); 109 110 return ethnl_put_bitset32(skb, ETHTOOL_A_PRIVFLAGS_FLAGS, 111 &data->priv_flags, &all_flags, 112 data->n_priv_flags, data->priv_flag_names, 113 compact); 114 } 115 116 static void privflags_cleanup_data(struct ethnl_reply_data *reply_data) 117 { 118 struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_data); 119 120 kfree(data->priv_flag_names); 121 } 122 123 const struct ethnl_request_ops ethnl_privflags_request_ops = { 124 .request_cmd = ETHTOOL_MSG_PRIVFLAGS_GET, 125 .reply_cmd = ETHTOOL_MSG_PRIVFLAGS_GET_REPLY, 126 .hdr_attr = ETHTOOL_A_PRIVFLAGS_HEADER, 127 .max_attr = ETHTOOL_A_PRIVFLAGS_MAX, 128 .req_info_size = sizeof(struct privflags_req_info), 129 .reply_data_size = sizeof(struct privflags_reply_data), 130 .request_policy = privflags_get_policy, 131 132 .prepare_data = privflags_prepare_data, 133 .reply_size = privflags_reply_size, 134 .fill_reply = privflags_fill_reply, 135 .cleanup_data = privflags_cleanup_data, 136 }; 137 138 /* PRIVFLAGS_SET */ 139 140 static const struct nla_policy 141 privflags_set_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = { 142 [ETHTOOL_A_PRIVFLAGS_UNSPEC] = { .type = NLA_REJECT }, 143 [ETHTOOL_A_PRIVFLAGS_HEADER] = { .type = NLA_NESTED }, 144 [ETHTOOL_A_PRIVFLAGS_FLAGS] = { .type = NLA_NESTED }, 145 }; 146 147 int ethnl_set_privflags(struct sk_buff *skb, struct genl_info *info) 148 { 149 struct nlattr *tb[ETHTOOL_A_PRIVFLAGS_MAX + 1]; 150 const char (*names)[ETH_GSTRING_LEN] = NULL; 151 struct ethnl_req_info req_info = {}; 152 const struct ethtool_ops *ops; 153 struct net_device *dev; 154 unsigned int nflags; 155 bool mod = false; 156 bool compact; 157 u32 flags; 158 int ret; 159 160 ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb, 161 ETHTOOL_A_PRIVFLAGS_MAX, privflags_set_policy, 162 info->extack); 163 if (ret < 0) 164 return ret; 165 if (!tb[ETHTOOL_A_PRIVFLAGS_FLAGS]) 166 return -EINVAL; 167 ret = ethnl_bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], &compact); 168 if (ret < 0) 169 return ret; 170 ret = ethnl_parse_header_dev_get(&req_info, 171 tb[ETHTOOL_A_PRIVFLAGS_HEADER], 172 genl_info_net(info), info->extack, 173 true); 174 if (ret < 0) 175 return ret; 176 dev = req_info.dev; 177 ops = dev->ethtool_ops; 178 ret = -EOPNOTSUPP; 179 if (!ops->get_priv_flags || !ops->set_priv_flags || 180 !ops->get_sset_count || !ops->get_strings) 181 goto out_dev; 182 183 rtnl_lock(); 184 ret = ethnl_ops_begin(dev); 185 if (ret < 0) 186 goto out_rtnl; 187 ret = ethnl_get_priv_flags_info(dev, &nflags, compact ? NULL : &names); 188 if (ret < 0) 189 goto out_ops; 190 flags = ops->get_priv_flags(dev); 191 192 ret = ethnl_update_bitset32(&flags, nflags, 193 tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names, 194 info->extack, &mod); 195 if (ret < 0 || !mod) 196 goto out_free; 197 ret = ops->set_priv_flags(dev, flags); 198 if (ret < 0) 199 goto out_free; 200 ethtool_notify(dev, ETHTOOL_MSG_PRIVFLAGS_NTF, NULL); 201 202 out_free: 203 kfree(names); 204 out_ops: 205 ethnl_ops_complete(dev); 206 out_rtnl: 207 rtnl_unlock(); 208 out_dev: 209 dev_put(dev); 210 return ret; 211 } 212