As we have the ability to track the PHYs connected to a net_device through the link_topology, we can expose this list to userspace. This allows userspace to use these identifiers for phy-specific commands and take the decision of which PHY to target by knowing the link topology. Add PHY_GET and PHY_DUMP, which can be a filtered DUMP operation to list devices on only one interface.
Unified · Split
| @@ -2191,6 +2191,46 @@ string. · 2190 unmodified lines | |||
| 2191 | The ``ETHTOOL_A_MODULE_FW_FLASH_DONE`` and ``ETHTOOL_A_MODULE_FW_FLASH_TOTAL`` | 2191 | The ``ETHTOOL_A_MODULE_FW_FLASH_DONE`` and ``ETHTOOL_A_MODULE_FW_FLASH_TOTAL`` |
| 2192 | attributes encode the completed and total amount of work, respectively. | 2192 | attributes encode the completed and total amount of work, respectively. |
| 2193 | 2193 | ||
| 2194 | PHY_GET | ||
| 2195 | ======= | ||
| 2196 | |||
| 2197 | Retrieve information about a given Ethernet PHY sitting on the link. The DO | ||
| 2198 | operation returns all available information about dev->phydev. User can also | ||
| 2199 | specify a PHY_INDEX, in which case the DO request returns information about that | ||
| 2200 | specific PHY. | ||
| 2201 | As there can be more than one PHY, the DUMP operation can be used to list the PHYs | ||
| 2202 | present on a given interface, by passing an interface index or name in | ||
| 2203 | the dump request. | ||
| 2204 | |||
| 2205 | Request contents: | ||
| 2206 | |||
| 2207 | ==================================== ====== ========================== | ||
| 2208 | ``ETHTOOL_A_PHY_HEADER`` nested request header | ||
| 2209 | ==================================== ====== ========================== | ||
| 2210 | |||
| 2211 | Kernel response contents: | ||
| 2212 | |||
| 2213 | ===================================== ====== =============================== | ||
| 2214 | ``ETHTOOL_A_PHY_HEADER`` nested request header | ||
| 2215 | ``ETHTOOL_A_PHY_INDEX`` u32 the phy's unique index, that can | ||
| 2216 | be used for phy-specific | ||
| 2217 | requests | ||
| 2218 | ``ETHTOOL_A_PHY_DRVNAME`` string the phy driver name | ||
| 2219 | ``ETHTOOL_A_PHY_NAME`` string the phy device name | ||
| 2220 | ``ETHTOOL_A_PHY_UPSTREAM_TYPE`` u32 the type of device this phy is | ||
| 2221 | connected to | ||
| 2222 | ``ETHTOOL_A_PHY_UPSTREAM_INDEX`` u32 the PHY index of the upstream | ||
| 2223 | PHY | ||
| 2224 | ``ETHTOOL_A_PHY_UPSTREAM_SFP_NAME`` string if this PHY is connected to | ||
| 2225 | its parent PHY through an SFP | ||
| 2226 | bus, the name of this sfp bus | ||
| 2227 | ``ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME`` string if the phy controls an sfp bus, | ||
| 2228 | the name of the sfp bus | ||
| 2229 | ===================================== ====== =============================== | ||
| 2230 | |||
| 2231 | When ``ETHTOOL_A_PHY_UPSTREAM_TYPE`` is PHY_UPSTREAM_PHY, the PHY's parent is | ||
| 2232 | another PHY. | ||
| 2233 | |||
| 2194 | Request translation | 2234 | Request translation |
| 2195 | =================== | 2235 | =================== |
| 2196 | 2236 | ||
| @@ -2298,4 +2338,5 @@ are netlink only. · 101 unmodified lines | |||
| 2298 | n/a ``ETHTOOL_MSG_MM_GET`` | 2338 | n/a ``ETHTOOL_MSG_MM_GET`` |
| 2299 | n/a ``ETHTOOL_MSG_MM_SET`` | 2339 | n/a ``ETHTOOL_MSG_MM_SET`` |
| 2300 | n/a ``ETHTOOL_MSG_MODULE_FW_FLASH_ACT`` | 2340 | n/a ``ETHTOOL_MSG_MODULE_FW_FLASH_ACT`` |
| 2341 | n/a ``ETHTOOL_MSG_PHY_GET`` | ||
| 2301 | =================================== ===================================== | 2342 | =================================== ===================================== |
| @@ -58,6 +58,7 @@ enum { · 57 unmodified lines | |||
| 58 | ETHTOOL_MSG_MM_GET, | 58 | ETHTOOL_MSG_MM_GET, |
| 59 | ETHTOOL_MSG_MM_SET, | 59 | ETHTOOL_MSG_MM_SET, |
| 60 | ETHTOOL_MSG_MODULE_FW_FLASH_ACT, | 60 | ETHTOOL_MSG_MODULE_FW_FLASH_ACT, |
| 61 | ETHTOOL_MSG_PHY_GET, | ||
| 61 | 62 | ||
| 62 | /* add new constants above here */ | 63 | /* add new constants above here */ |
| 63 | __ETHTOOL_MSG_USER_CNT, | 64 | __ETHTOOL_MSG_USER_CNT, |
| @@ -111,6 +112,8 @@ enum { · 47 unmodified lines | |||
| 111 | ETHTOOL_MSG_MM_GET_REPLY, | 112 | ETHTOOL_MSG_MM_GET_REPLY, |
| 112 | ETHTOOL_MSG_MM_NTF, | 113 | ETHTOOL_MSG_MM_NTF, |
| 113 | ETHTOOL_MSG_MODULE_FW_FLASH_NTF, | 114 | ETHTOOL_MSG_MODULE_FW_FLASH_NTF, |
| 115 | ETHTOOL_MSG_PHY_GET_REPLY, | ||
| 116 | ETHTOOL_MSG_PHY_NTF, | ||
| 114 | 117 | ||
| 115 | /* add new constants above here */ | 118 | /* add new constants above here */ |
| 116 | __ETHTOOL_MSG_KERNEL_CNT, | 119 | __ETHTOOL_MSG_KERNEL_CNT, |
| @@ -1055,6 +1058,22 @@ enum { · 938 unmodified lines | |||
| 1055 | ETHTOOL_A_MODULE_FW_FLASH_MAX = (__ETHTOOL_A_MODULE_FW_FLASH_CNT - 1) | 1058 | ETHTOOL_A_MODULE_FW_FLASH_MAX = (__ETHTOOL_A_MODULE_FW_FLASH_CNT - 1) |
| 1056 | }; | 1059 | }; |
| 1057 | 1060 | ||
| 1061 | enum { | ||
| 1062 | ETHTOOL_A_PHY_UNSPEC, | ||
| 1063 | ETHTOOL_A_PHY_HEADER, /* nest - _A_HEADER_* */ | ||
| 1064 | ETHTOOL_A_PHY_INDEX, /* u32 */ | ||
| 1065 | ETHTOOL_A_PHY_DRVNAME, /* string */ | ||
| 1066 | ETHTOOL_A_PHY_NAME, /* string */ | ||
| 1067 | ETHTOOL_A_PHY_UPSTREAM_TYPE, /* u32 */ | ||
| 1068 | ETHTOOL_A_PHY_UPSTREAM_INDEX, /* u32 */ | ||
| 1069 | ETHTOOL_A_PHY_UPSTREAM_SFP_NAME, /* string */ | ||
| 1070 | ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME, /* string */ | ||
| 1071 | |||
| 1072 | /* add new constants above here */ | ||
| 1073 | __ETHTOOL_A_PHY_CNT, | ||
| 1074 | ETHTOOL_A_PHY_MAX = (__ETHTOOL_A_PHY_CNT - 1) | ||
| 1075 | }; | ||
| 1076 | |||
| 1058 | /* generic netlink info */ | 1077 | /* generic netlink info */ |
| 1059 | #define ETHTOOL_GENL_NAME "ethtool" | 1078 | #define ETHTOOL_GENL_NAME "ethtool" |
| 1060 | #define ETHTOOL_GENL_VERSION 1 | 1079 | #define ETHTOOL_GENL_VERSION 1 |
| @@ -8,4 +8,5 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.o \ · 7 unmodified lines | |||
| 8 | linkstate.o debug.o wol.o features.o privflags.o rings.o \ | 8 | linkstate.o debug.o wol.o features.o privflags.o rings.o \ |
| 9 | channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \ | 9 | channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \ |
| 10 | tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \ | 10 | tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \ |
| 11 | module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o mm.o | 11 | module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o mm.o \ |
| 12 | phy.o | ||
| @@ -1234,6 +1234,15 @@ static const struct genl_ops ethtool_genl_ops[] = { · 1233 unmodified lines | |||
| 1234 | .policy = ethnl_module_fw_flash_act_policy, | 1234 | .policy = ethnl_module_fw_flash_act_policy, |
| 1235 | .maxattr = ARRAY_SIZE(ethnl_module_fw_flash_act_policy) - 1, | 1235 | .maxattr = ARRAY_SIZE(ethnl_module_fw_flash_act_policy) - 1, |
| 1236 | }, | 1236 | }, |
| 1237 | { | ||
| 1238 | .cmd = ETHTOOL_MSG_PHY_GET, | ||
| 1239 | .doit = ethnl_phy_doit, | ||
| 1240 | .start = ethnl_phy_start, | ||
| 1241 | .dumpit = ethnl_phy_dumpit, | ||
| 1242 | .done = ethnl_phy_done, | ||
| 1243 | .policy = ethnl_phy_get_policy, | ||
| 1244 | .maxattr = ARRAY_SIZE(ethnl_phy_get_policy) - 1, | ||
| 1245 | }, | ||
| 1237 | }; | 1246 | }; |
| 1238 | 1247 | ||
| 1239 | static const struct genl_multicast_group ethtool_nl_mcgrps[] = { | 1248 | static const struct genl_multicast_group ethtool_nl_mcgrps[] = { |
| @@ -484,6 +484,7 @@ extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADE · 483 unmodified lines | |||
| 484 | extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1]; | 484 | extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1]; |
| 485 | extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1]; | 485 | extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1]; |
| 486 | extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1]; | 486 | extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE_FW_FLASH_PASSWORD + 1]; |
| 487 | extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1]; | ||
| 487 | 488 | ||
| 488 | int ethnl_set_features(struct sk_buff *skb, struct genl_info *info); | 489 | int ethnl_set_features(struct sk_buff *skb, struct genl_info *info); |
| 489 | int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info); | 490 | int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info); |
| @@ -494,6 +495,10 @@ int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb); · 4 unmodified lines | |||
| 494 | int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info); | 495 | int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info); |
| 495 | int ethnl_rss_dump_start(struct netlink_callback *cb); | 496 | int ethnl_rss_dump_start(struct netlink_callback *cb); |
| 496 | int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb); | 497 | int ethnl_rss_dumpit(struct sk_buff *skb, struct netlink_callback *cb); |
| 498 | int ethnl_phy_start(struct netlink_callback *cb); | ||
| 499 | int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info); | ||
| 500 | int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb); | ||
| 501 | int ethnl_phy_done(struct netlink_callback *cb); | ||
| 497 | 502 | ||
| 498 | extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN]; | 503 | extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN]; |
| 499 | extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN]; | 504 | extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN]; |
| @@ -0,0 +1,308 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Copyright 2023 Bootlin | ||
| 4 | * | ||
| 5 | */ | ||
| 6 | #include "common.h" | ||
| 7 | #include "netlink.h" | ||
| 8 | |||
| 9 | #include <linux/phy.h> | ||
| 10 | #include <linux/phy_link_topology.h> | ||
| 11 | #include <linux/sfp.h> | ||
| 12 | |||
| 13 | struct phy_req_info { | ||
| 14 | struct ethnl_req_info base; | ||
| 15 | struct phy_device_node *pdn; | ||
| 16 | }; | ||
| 17 | |||
| 18 | #define PHY_REQINFO(__req_base) \ | ||
| 19 | container_of(__req_base, struct phy_req_info, base) | ||
| 20 | |||
| 21 | const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1] = { | ||
| 22 | [ETHTOOL_A_PHY_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy), | ||
| 23 | }; | ||
| 24 | |||
| 25 | /* Caller holds rtnl */ | ||
| 26 | static ssize_t | ||
| 27 | ethnl_phy_reply_size(const struct ethnl_req_info *req_base, | ||
| 28 | struct netlink_ext_ack *extack) | ||
| 29 | { | ||
| 30 | struct phy_req_info *req_info = PHY_REQINFO(req_base); | ||
| 31 | struct phy_device_node *pdn = req_info->pdn; | ||
| 32 | struct phy_device *phydev = pdn->phy; | ||
| 33 | size_t size = 0; | ||
| 34 | |||
| 35 | ASSERT_RTNL(); | ||
| 36 | |||
| 37 | /* ETHTOOL_A_PHY_INDEX */ | ||
| 38 | size += nla_total_size(sizeof(u32)); | ||
| 39 | |||
| 40 | /* ETHTOOL_A_DRVNAME */ | ||
| 41 | if (phydev->drv) | ||
| 42 | size += nla_total_size(strlen(phydev->drv->name) + 1); | ||
| 43 | |||
| 44 | /* ETHTOOL_A_NAME */ | ||
| 45 | size += nla_total_size(strlen(dev_name(&phydev->mdio.dev)) + 1); | ||
| 46 | |||
| 47 | /* ETHTOOL_A_PHY_UPSTREAM_TYPE */ | ||
| 48 | size += nla_total_size(sizeof(u32)); | ||
| 49 | |||
| 50 | if (phy_on_sfp(phydev)) { | ||
| 51 | const char *upstream_sfp_name = sfp_get_name(pdn->parent_sfp_bus); | ||
| 52 | |||
| 53 | /* ETHTOOL_A_PHY_UPSTREAM_SFP_NAME */ | ||
| 54 | if (upstream_sfp_name) | ||
| 55 | size += nla_total_size(strlen(upstream_sfp_name) + 1); | ||
| 56 | |||
| 57 | /* ETHTOOL_A_PHY_UPSTREAM_INDEX */ | ||
| 58 | size += nla_total_size(sizeof(u32)); | ||
| 59 | } | ||
| 60 | |||
| 61 | /* ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME */ | ||
| 62 | if (phydev->sfp_bus) { | ||
| 63 | const char *sfp_name = sfp_get_name(phydev->sfp_bus); | ||
| 64 | |||
| 65 | if (sfp_name) | ||
| 66 | size += nla_total_size(strlen(sfp_name) + 1); | ||
| 67 | } | ||
| 68 | |||
| 69 | return size; | ||
| 70 | } | ||
| 71 | |||
| 72 | static int | ||
| 73 | ethnl_phy_fill_reply(const struct ethnl_req_info *req_base, struct sk_buff *skb) | ||
| 74 | { | ||
| 75 | struct phy_req_info *req_info = PHY_REQINFO(req_base); | ||
| 76 | struct phy_device_node *pdn = req_info->pdn; | ||
| 77 | struct phy_device *phydev = pdn->phy; | ||
| 78 | enum phy_upstream ptype; | ||
| 79 | |||
| 80 | ptype = pdn->upstream_type; | ||
| 81 | |||
| 82 | if (nla_put_u32(skb, ETHTOOL_A_PHY_INDEX, phydev->phyindex) || | ||
| 83 | nla_put_string(skb, ETHTOOL_A_PHY_NAME, dev_name(&phydev->mdio.dev)) || | ||
| 84 | nla_put_u32(skb, ETHTOOL_A_PHY_UPSTREAM_TYPE, ptype)) | ||
| 85 | return -EMSGSIZE; | ||
| 86 | |||
| 87 | if (phydev->drv && | ||
| 88 | nla_put_string(skb, ETHTOOL_A_PHY_DRVNAME, phydev->drv->name)) | ||
| 89 | return -EMSGSIZE; | ||
| 90 | |||
| 91 | if (ptype == PHY_UPSTREAM_PHY) { | ||
| 92 | struct phy_device *upstream = pdn->upstream.phydev; | ||
| 93 | const char *sfp_upstream_name; | ||
| 94 | |||
| 95 | /* Parent index */ | ||
| 96 | if (nla_put_u32(skb, ETHTOOL_A_PHY_UPSTREAM_INDEX, upstream->phyindex)) | ||
| 97 | return -EMSGSIZE; | ||
| 98 | |||
| 99 | if (pdn->parent_sfp_bus) { | ||
| 100 | sfp_upstream_name = sfp_get_name(pdn->parent_sfp_bus); | ||
| 101 | if (sfp_upstream_name && | ||
| 102 | nla_put_string(skb, ETHTOOL_A_PHY_UPSTREAM_SFP_NAME, | ||
| 103 | sfp_upstream_name)) | ||
| 104 | return -EMSGSIZE; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | if (phydev->sfp_bus) { | ||
| 109 | const char *sfp_name = sfp_get_name(phydev->sfp_bus); | ||
| 110 | |||
| 111 | if (sfp_name && | ||
| 112 | nla_put_string(skb, ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME, | ||
| 113 | sfp_name)) | ||
| 114 | return -EMSGSIZE; | ||
| 115 | } | ||
| 116 | |||
| 117 | return 0; | ||
| 118 | } | ||
| 119 | |||
| 120 | static int ethnl_phy_parse_request(struct ethnl_req_info *req_base, | ||
| 121 | struct nlattr **tb, | ||
| 122 | struct netlink_ext_ack *extack) | ||
| 123 | { | ||
| 124 | struct phy_link_topology *topo = req_base->dev->link_topo; | ||
| 125 | struct phy_req_info *req_info = PHY_REQINFO(req_base); | ||
| 126 | struct phy_device *phydev; | ||
| 127 | |||
| 128 | phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_PHY_HEADER], | ||
| 129 | extack); | ||
| 130 | if (!phydev) | ||
| 131 | return 0; | ||
| 132 | |||
| 133 | if (IS_ERR(phydev)) | ||
| 134 | return PTR_ERR(phydev); | ||
| 135 | |||
| 136 | if (!topo) | ||
| 137 | return 0; | ||
| 138 | |||
| 139 | req_info->pdn = xa_load(&topo->phys, phydev->phyindex); | ||
| 140 | |||
| 141 | return 0; | ||
| 142 | } | ||
| 143 | |||
| 144 | int ethnl_phy_doit(struct sk_buff *skb, struct genl_info *info) | ||
| 145 | { | ||
| 146 | struct phy_req_info req_info = {}; | ||
| 147 | struct nlattr **tb = info->attrs; | ||
| 148 | struct sk_buff *rskb; | ||
| 149 | void *reply_payload; | ||
| 150 | int reply_len; | ||
| 151 | int ret; | ||
| 152 | |||
| 153 | ret = ethnl_parse_header_dev_get(&req_info.base, | ||
| 154 | tb[ETHTOOL_A_PHY_HEADER], | ||
| 155 | genl_info_net(info), info->extack, | ||
| 156 | true); | ||
| 157 | if (ret < 0) | ||
| 158 | return ret; | ||
| 159 | |||
| 160 | rtnl_lock(); | ||
| 161 | |||
| 162 | ret = ethnl_phy_parse_request(&req_info.base, tb, info->extack); | ||
| 163 | if (ret < 0) | ||
| 164 | goto err_unlock_rtnl; | ||
| 165 | |||
| 166 | /* No PHY, return early */ | ||
| 167 | if (!req_info.pdn->phy) | ||
| 168 | goto err_unlock_rtnl; | ||
| 169 | |||
| 170 | ret = ethnl_phy_reply_size(&req_info.base, info->extack); | ||
| 171 | if (ret < 0) | ||
| 172 | goto err_unlock_rtnl; | ||
| 173 | reply_len = ret + ethnl_reply_header_size(); | ||
| 174 | |||
| 175 | rskb = ethnl_reply_init(reply_len, req_info.base.dev, | ||
| 176 | ETHTOOL_MSG_PHY_GET_REPLY, | ||
| 177 | ETHTOOL_A_PHY_HEADER, | ||
| 178 | info, &reply_payload); | ||
| 179 | if (!rskb) { | ||
| 180 | ret = -ENOMEM; | ||
| 181 | goto err_unlock_rtnl; | ||
| 182 | } | ||
| 183 | |||
| 184 | ret = ethnl_phy_fill_reply(&req_info.base, rskb); | ||
| 185 | if (ret) | ||
| 186 | goto err_free_msg; | ||
| 187 | |||
| 188 | rtnl_unlock(); | ||
| 189 | ethnl_parse_header_dev_put(&req_info.base); | ||
| 190 | genlmsg_end(rskb, reply_payload); | ||
| 191 | |||
| 192 | return genlmsg_reply(rskb, info); | ||
| 193 | |||
| 194 | err_free_msg: | ||
| 195 | nlmsg_free(rskb); | ||
| 196 | err_unlock_rtnl: | ||
| 197 | rtnl_unlock(); | ||
| 198 | ethnl_parse_header_dev_put(&req_info.base); | ||
| 199 | return ret; | ||
| 200 | } | ||
| 201 | |||
| 202 | struct ethnl_phy_dump_ctx { | ||
| 203 | struct phy_req_info *phy_req_info; | ||
| 204 | unsigned long ifindex; | ||
| 205 | unsigned long phy_index; | ||
| 206 | }; | ||
| 207 | |||
| 208 | int ethnl_phy_start(struct netlink_callback *cb) | ||
| 209 | { | ||
| 210 | const struct genl_info *info = genl_info_dump(cb); | ||
| 211 | struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; | ||
| 212 | int ret; | ||
| 213 | |||
| 214 | BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); | ||
| 215 | |||
| 216 | ctx->phy_req_info = kzalloc(sizeof(*ctx->phy_req_info), GFP_KERNEL); | ||
| 217 | if (!ctx->phy_req_info) | ||
| 218 | return -ENOMEM; | ||
| 219 | |||
| 220 | ret = ethnl_parse_header_dev_get(&ctx->phy_req_info->base, | ||
| 221 | info->attrs[ETHTOOL_A_PHY_HEADER], | ||
| 222 | sock_net(cb->skb->sk), cb->extack, | ||
| 223 | false); | ||
| 224 | ctx->ifindex = 0; | ||
| 225 | ctx->phy_index = 0; | ||
| 226 | |||
| 227 | if (ret) | ||
| 228 | kfree(ctx->phy_req_info); | ||
| 229 | |||
| 230 | return ret; | ||
| 231 | } | ||
| 232 | |||
| 233 | int ethnl_phy_done(struct netlink_callback *cb) | ||
| 234 | { | ||
| 235 | struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; | ||
| 236 | |||
| 237 | if (ctx->phy_req_info->base.dev) | ||
| 238 | ethnl_parse_header_dev_put(&ctx->phy_req_info->base); | ||
| 239 | |||
| 240 | kfree(ctx->phy_req_info); | ||
| 241 | |||
| 242 | return 0; | ||
| 243 | } | ||
| 244 | |||
| 245 | static int ethnl_phy_dump_one_dev(struct sk_buff *skb, struct net_device *dev, | ||
| 246 | struct netlink_callback *cb) | ||
| 247 | { | ||
| 248 | struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; | ||
| 249 | struct phy_req_info *pri = ctx->phy_req_info; | ||
| 250 | struct phy_device_node *pdn; | ||
| 251 | int ret = 0; | ||
| 252 | void *ehdr; | ||
| 253 | |||
| 254 | pri->base.dev = dev; | ||
| 255 | |||
| 256 | if (!dev->link_topo) | ||
| 257 | return 0; | ||
| 258 | |||
| 259 | xa_for_each_start(&dev->link_topo->phys, ctx->phy_index, pdn, ctx->phy_index) { | ||
| 260 | ehdr = ethnl_dump_put(skb, cb, ETHTOOL_MSG_PHY_GET_REPLY); | ||
| 261 | if (!ehdr) { | ||
| 262 | ret = -EMSGSIZE; | ||
| 263 | break; | ||
| 264 | } | ||
| 265 | |||
| 266 | ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_PHY_HEADER); | ||
| 267 | if (ret < 0) { | ||
| 268 | genlmsg_cancel(skb, ehdr); | ||
| 269 | break; | ||
| 270 | } | ||
| 271 | |||
| 272 | pri->pdn = pdn; | ||
| 273 | ret = ethnl_phy_fill_reply(&pri->base, skb); | ||
| 274 | if (ret < 0) { | ||
| 275 | genlmsg_cancel(skb, ehdr); | ||
| 276 | break; | ||
| 277 | } | ||
| 278 | |||
| 279 | genlmsg_end(skb, ehdr); | ||
| 280 | } | ||
| 281 | |||
| 282 | return ret; | ||
| 283 | } | ||
| 284 | |||
| 285 | int ethnl_phy_dumpit(struct sk_buff *skb, struct netlink_callback *cb) | ||
| 286 | { | ||
| 287 | struct ethnl_phy_dump_ctx *ctx = (void *)cb->ctx; | ||
| 288 | struct net *net = sock_net(skb->sk); | ||
| 289 | struct net_device *dev; | ||
| 290 | int ret = 0; | ||
| 291 | |||
| 292 | rtnl_lock(); | ||
| 293 | |||
| 294 | if (ctx->phy_req_info->base.dev) { | ||
| 295 | ret = ethnl_phy_dump_one_dev(skb, ctx->phy_req_info->base.dev, cb); | ||
| 296 | } else { | ||
| 297 | for_each_netdev_dump(net, dev, ctx->ifindex) { | ||
| 298 | ret = ethnl_phy_dump_one_dev(skb, dev, cb); | ||
| 299 | if (ret) | ||
| 300 | break; | ||
| 301 | |||
| 302 | ctx->phy_index = 0; | ||
| 303 | } | ||
| 304 | } | ||
| 305 | rtnl_unlock(); | ||
| 306 | |||
| 307 | return ret; | ||
| 308 | } | ||