developer | 4e8a3fd | 2023-04-10 18:05:44 +0800 | [diff] [blame] | 1 | diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c |
| 2 | index 67f34ed..ead9b37 100644 |
| 3 | --- a/drivers/net/phy/phylink.c |
| 4 | +++ b/drivers/net/phy/phylink.c |
| 5 | @@ -40,8 +40,9 @@ enum { |
| 6 | struct phylink { |
| 7 | /* private: */ |
| 8 | struct net_device *netdev; |
| 9 | - const struct phylink_mac_ops *ops; |
| 10 | + const struct phylink_mac_ops *mac_ops; |
| 11 | struct phylink_config *config; |
| 12 | + struct phylink_pcs *pcs; |
| 13 | struct device *dev; |
| 14 | unsigned int old_link_state:1; |
| 15 | |
| 16 | @@ -70,6 +71,7 @@ struct phylink { |
| 17 | struct work_struct resolve; |
| 18 | |
| 19 | bool mac_link_dropped; |
| 20 | + bool using_mac_select_pcs; |
| 21 | |
| 22 | struct sfp_bus *sfp_bus; |
| 23 | bool sfp_may_have_phy; |
| 24 | @@ -153,14 +155,60 @@ static const char *phylink_an_mode_str(unsigned int mode) |
| 25 | return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown"; |
| 26 | } |
| 27 | |
| 28 | -static int phylink_validate(struct phylink *pl, unsigned long *supported, |
| 29 | - struct phylink_link_state *state) |
| 30 | +static int phylink_validate_mac_and_pcs(struct phylink *pl, |
| 31 | + unsigned long *supported, |
| 32 | + struct phylink_link_state *state) |
| 33 | { |
| 34 | - pl->ops->validate(pl->config, supported, state); |
| 35 | + struct phylink_pcs *pcs; |
| 36 | + int ret; |
| 37 | + |
| 38 | + /* Get the PCS for this interface mode */ |
| 39 | + if (pl->using_mac_select_pcs) { |
| 40 | + pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface); |
| 41 | + if (IS_ERR(pcs)) |
| 42 | + return PTR_ERR(pcs); |
| 43 | + } else { |
| 44 | + pcs = pl->pcs; |
| 45 | + } |
| 46 | + |
| 47 | + if (pcs) { |
| 48 | + /* The PCS, if present, must be setup before phylink_create() |
| 49 | + * has been called. If the ops is not initialised, print an |
| 50 | + * error and backtrace rather than oopsing the kernel. |
| 51 | + */ |
| 52 | + if (!pcs->ops) { |
| 53 | + phylink_err(pl, "interface %s: uninitialised PCS\n", |
| 54 | + phy_modes(state->interface)); |
| 55 | + dump_stack(); |
| 56 | + return -EINVAL; |
| 57 | + } |
| 58 | + |
| 59 | + /* Validate the link parameters with the PCS */ |
| 60 | + if (pcs->ops->pcs_validate) { |
| 61 | + ret = pcs->ops->pcs_validate(pcs, supported, state); |
| 62 | + if (ret < 0 || phylink_is_empty_linkmode(supported)) |
| 63 | + return -EINVAL; |
| 64 | + |
| 65 | + /* Ensure the advertising mask is a subset of the |
| 66 | + * supported mask. |
| 67 | + */ |
| 68 | + linkmode_and(state->advertising, state->advertising, |
| 69 | + supported); |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + /* Then validate the link parameters with the MAC */ |
| 74 | + pl->mac_ops->validate(pl->config, supported, state); |
| 75 | |
| 76 | return phylink_is_empty_linkmode(supported) ? -EINVAL : 0; |
| 77 | } |
| 78 | |
| 79 | +static int phylink_validate(struct phylink *pl, unsigned long *supported, |
| 80 | + struct phylink_link_state *state) |
| 81 | +{ |
| 82 | + return phylink_validate_mac_and_pcs(pl, supported, state); |
| 83 | +} |
| 84 | + |
| 85 | static int phylink_parse_fixedlink(struct phylink *pl, |
| 86 | struct fwnode_handle *fwnode) |
| 87 | { |
| 88 | @@ -338,6 +386,18 @@ static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode) |
| 89 | return 0; |
| 90 | } |
| 91 | |
| 92 | +static void phylink_pcs_poll_stop(struct phylink *pl) |
| 93 | +{ |
| 94 | + if (pl->cfg_link_an_mode == MLO_AN_INBAND) |
| 95 | + del_timer(&pl->link_poll); |
| 96 | +} |
| 97 | + |
| 98 | +static void phylink_pcs_poll_start(struct phylink *pl) |
| 99 | +{ |
| 100 | + if (pl->pcs && pl->pcs->poll && pl->cfg_link_an_mode == MLO_AN_INBAND) |
| 101 | + mod_timer(&pl->link_poll, jiffies + HZ); |
| 102 | +} |
| 103 | + |
| 104 | static void phylink_mac_config(struct phylink *pl, |
| 105 | const struct phylink_link_state *state) |
| 106 | { |
| 107 | @@ -350,37 +410,113 @@ static void phylink_mac_config(struct phylink *pl, |
| 108 | __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising, |
| 109 | state->pause, state->link, state->an_enabled); |
| 110 | |
| 111 | - pl->ops->mac_config(pl->config, pl->cur_link_an_mode, state); |
| 112 | + pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, state); |
| 113 | } |
| 114 | |
| 115 | -static void phylink_mac_config_up(struct phylink *pl, |
| 116 | - const struct phylink_link_state *state) |
| 117 | +static void phylink_mac_pcs_an_restart(struct phylink *pl) |
| 118 | { |
| 119 | - if (state->link) |
| 120 | - phylink_mac_config(pl, state); |
| 121 | + if (pl->link_config.an_enabled && |
| 122 | + phy_interface_mode_is_8023z(pl->link_config.interface) && |
| 123 | + phylink_autoneg_inband(pl->cur_link_an_mode)) { |
| 124 | + if (pl->pcs) |
| 125 | + pl->pcs->ops->pcs_an_restart(pl->pcs); |
| 126 | + else if (pl->mac_ops->mac_an_restart) |
| 127 | + pl->mac_ops->mac_an_restart(pl->config); |
| 128 | + } |
| 129 | } |
| 130 | |
| 131 | -static void phylink_mac_an_restart(struct phylink *pl) |
| 132 | +static void phylink_major_config(struct phylink *pl, bool restart, |
| 133 | + const struct phylink_link_state *state) |
| 134 | { |
| 135 | - if (pl->link_config.an_enabled && |
| 136 | - phy_interface_mode_is_8023z(pl->link_config.interface)) |
| 137 | - pl->ops->mac_an_restart(pl->config); |
| 138 | + struct phylink_pcs *pcs = NULL; |
| 139 | + bool pcs_changed = false; |
| 140 | + int err; |
| 141 | + |
| 142 | + phylink_dbg(pl, "major config %s\n", phy_modes(state->interface)); |
| 143 | + |
| 144 | + if (pl->using_mac_select_pcs) { |
| 145 | + pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface); |
| 146 | + if (IS_ERR(pcs)) { |
| 147 | + phylink_err(pl, |
| 148 | + "mac_select_pcs unexpectedly failed: %pe\n", |
| 149 | + pcs); |
| 150 | + return; |
| 151 | + } |
| 152 | + |
| 153 | + pcs_changed = pcs && pl->pcs != pcs; |
| 154 | + } |
| 155 | + |
| 156 | + phylink_pcs_poll_stop(pl); |
| 157 | + |
| 158 | + if (pl->mac_ops->mac_prepare) { |
| 159 | + err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode, |
| 160 | + state->interface); |
| 161 | + if (err < 0) { |
| 162 | + phylink_err(pl, "mac_prepare failed: %pe\n", |
| 163 | + ERR_PTR(err)); |
| 164 | + return; |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + /* If we have a new PCS, switch to the new PCS after preparing the MAC |
| 169 | + * for the change. |
| 170 | + */ |
| 171 | + if (pcs_changed) |
| 172 | + pl->pcs = pcs; |
| 173 | + |
| 174 | + phylink_mac_config(pl, state); |
| 175 | + |
| 176 | + if (pl->pcs) { |
| 177 | + err = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode, |
| 178 | + state->interface, |
| 179 | + state->advertising, |
| 180 | + !!(pl->link_config.pause & |
| 181 | + MLO_PAUSE_AN)); |
| 182 | + if (err < 0) |
| 183 | + phylink_err(pl, "pcs_config failed: %pe\n", |
| 184 | + ERR_PTR(err)); |
| 185 | + if (err > 0) |
| 186 | + restart = true; |
| 187 | + } |
| 188 | + if (restart) |
| 189 | + phylink_mac_pcs_an_restart(pl); |
| 190 | + |
| 191 | + if (pl->mac_ops->mac_finish) { |
| 192 | + err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode, |
| 193 | + state->interface); |
| 194 | + if (err < 0) |
| 195 | + phylink_err(pl, "mac_finish failed: %pe\n", |
| 196 | + ERR_PTR(err)); |
| 197 | + } |
| 198 | + |
| 199 | + phylink_pcs_poll_start(pl); |
| 200 | } |
| 201 | |
| 202 | -static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state) |
| 203 | +static void phylink_mac_pcs_get_state(struct phylink *pl, |
| 204 | + struct phylink_link_state *state) |
| 205 | { |
| 206 | - |
| 207 | linkmode_copy(state->advertising, pl->link_config.advertising); |
| 208 | linkmode_zero(state->lp_advertising); |
| 209 | state->interface = pl->link_config.interface; |
| 210 | state->an_enabled = pl->link_config.an_enabled; |
| 211 | - state->speed = SPEED_UNKNOWN; |
| 212 | - state->duplex = DUPLEX_UNKNOWN; |
| 213 | - state->pause = MLO_PAUSE_NONE; |
| 214 | + if (state->an_enabled) { |
| 215 | + state->speed = SPEED_UNKNOWN; |
| 216 | + state->duplex = DUPLEX_UNKNOWN; |
| 217 | + state->pause = MLO_PAUSE_NONE; |
| 218 | + } else { |
| 219 | + state->speed = pl->link_config.speed; |
| 220 | + state->duplex = pl->link_config.duplex; |
| 221 | + state->pause = pl->link_config.pause; |
| 222 | + } |
| 223 | state->an_complete = 0; |
| 224 | state->link = 1; |
| 225 | |
| 226 | - return pl->ops->mac_link_state(pl->config, state); |
| 227 | + if (pl->pcs) |
| 228 | + pl->pcs->ops->pcs_get_state(pl->pcs, state); |
| 229 | + else if (pl->mac_ops->mac_link_state) |
| 230 | + pl->mac_ops->mac_link_state(pl->config, state); |
| 231 | + else |
| 232 | + state->link = 0; |
| 233 | } |
| 234 | |
| 235 | /* The fixed state is... fixed except for the link state, |
| 236 | @@ -395,6 +531,34 @@ static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_stat |
| 237 | state->link = !!gpiod_get_value_cansleep(pl->link_gpio); |
| 238 | } |
| 239 | |
| 240 | +static void phylink_mac_initial_config(struct phylink *pl, bool force_restart) |
| 241 | +{ |
| 242 | + struct phylink_link_state link_state; |
| 243 | + |
| 244 | + switch (pl->cur_link_an_mode) { |
| 245 | + case MLO_AN_PHY: |
| 246 | + link_state = pl->phy_state; |
| 247 | + break; |
| 248 | + |
| 249 | + case MLO_AN_FIXED: |
| 250 | + phylink_get_fixed_state(pl, &link_state); |
| 251 | + break; |
| 252 | + |
| 253 | + case MLO_AN_INBAND: |
| 254 | + link_state = pl->link_config; |
| 255 | + if (link_state.interface == PHY_INTERFACE_MODE_SGMII) |
| 256 | + link_state.pause = MLO_PAUSE_NONE; |
| 257 | + break; |
| 258 | + |
| 259 | + default: /* can't happen */ |
| 260 | + return; |
| 261 | + } |
| 262 | + |
| 263 | + link_state.link = false; |
| 264 | + |
| 265 | + phylink_major_config(pl, force_restart, &link_state); |
| 266 | +} |
| 267 | + |
| 268 | /* Flow control is resolved according to our and the link partners |
| 269 | * advertisements using the following drawn from the 802.3 specs: |
| 270 | * Local device Link partner |
| 271 | @@ -445,17 +609,25 @@ static const char *phylink_pause_to_str(int pause) |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | -static void phylink_mac_link_up(struct phylink *pl, |
| 276 | - struct phylink_link_state link_state) |
| 277 | +static void phylink_link_up(struct phylink *pl, |
| 278 | + struct phylink_link_state link_state) |
| 279 | { |
| 280 | struct net_device *ndev = pl->netdev; |
| 281 | + int speed, duplex; |
| 282 | + |
| 283 | + speed = link_state.speed; |
| 284 | + duplex = link_state.duplex; |
| 285 | |
| 286 | pl->cur_interface = link_state.interface; |
| 287 | - pl->ops->mac_link_up(pl->config, pl->phydev, |
| 288 | - pl->cur_link_an_mode, pl->cur_interface, |
| 289 | - link_state.speed, link_state.duplex, |
| 290 | - !!(link_state.pause & MLO_PAUSE_TX), |
| 291 | - !!(link_state.pause & MLO_PAUSE_RX)); |
| 292 | + |
| 293 | + if (pl->pcs && pl->pcs->ops->pcs_link_up) |
| 294 | + pl->pcs->ops->pcs_link_up(pl->pcs, pl->cur_link_an_mode, |
| 295 | + pl->cur_interface, speed, duplex); |
| 296 | + |
| 297 | + pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode, |
| 298 | + pl->cur_interface, speed, duplex, |
| 299 | + !!(link_state.pause & MLO_PAUSE_TX), |
| 300 | + !!(link_state.pause & MLO_PAUSE_RX)); |
| 301 | |
| 302 | if (ndev) |
| 303 | netif_carrier_on(ndev); |
| 304 | @@ -467,14 +639,14 @@ static void phylink_mac_link_up(struct phylink *pl, |
| 305 | phylink_pause_to_str(link_state.pause)); |
| 306 | } |
| 307 | |
| 308 | -static void phylink_mac_link_down(struct phylink *pl) |
| 309 | +static void phylink_link_down(struct phylink *pl) |
| 310 | { |
| 311 | struct net_device *ndev = pl->netdev; |
| 312 | |
| 313 | if (ndev) |
| 314 | netif_carrier_off(ndev); |
| 315 | - pl->ops->mac_link_down(pl->config, pl->cur_link_an_mode, |
| 316 | - pl->cur_interface); |
| 317 | + pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode, |
| 318 | + pl->cur_interface); |
| 319 | phylink_info(pl, "Link is Down\n"); |
| 320 | } |
| 321 | |
| 322 | @@ -513,7 +685,7 @@ static void phylink_resolve(struct work_struct *w) |
| 323 | break; |
| 324 | |
| 325 | case MLO_AN_INBAND: |
| 326 | - phylink_get_mac_state(pl, &link_state); |
| 327 | + phylink_mac_pcs_get_state(pl, &link_state); |
| 328 | |
| 329 | /* The PCS may have a latching link-fail indicator. |
| 330 | * If the link was up, bring the link down and |
| 331 | @@ -524,8 +696,8 @@ static void phylink_resolve(struct work_struct *w) |
| 332 | if (cur_link_state) |
| 333 | retrigger = true; |
| 334 | else |
| 335 | - phylink_get_mac_state(pl, |
| 336 | - &link_state); |
| 337 | + phylink_mac_pcs_get_state(pl, |
| 338 | + &link_state); |
| 339 | } |
| 340 | |
| 341 | /* If we have a phy, the "up" state is the union of |
| 342 | @@ -564,12 +736,17 @@ static void phylink_resolve(struct work_struct *w) |
| 343 | * then reconfigure. |
| 344 | */ |
| 345 | if (cur_link_state) { |
| 346 | - phylink_mac_link_down(pl); |
| 347 | + phylink_link_down(pl); |
| 348 | cur_link_state = false; |
| 349 | } |
| 350 | - phylink_mac_config(pl, &link_state); |
| 351 | + phylink_major_config(pl, false, &link_state); |
| 352 | pl->link_config.interface = link_state.interface; |
| 353 | - } else { |
| 354 | + } else if (!pl->pcs) { |
| 355 | + /* The interface remains unchanged, only the speed, |
| 356 | + * duplex or pause settings have changed. Call the |
| 357 | + * old mac_config() method to configure the MAC/PCS |
| 358 | + * only if we do not have a legacy MAC driver. |
| 359 | + */ |
| 360 | phylink_mac_config(pl, &link_state); |
| 361 | } |
| 362 | } |
| 363 | @@ -577,9 +754,9 @@ static void phylink_resolve(struct work_struct *w) |
| 364 | if (link_state.link != cur_link_state) { |
| 365 | pl->old_link_state = link_state.link; |
| 366 | if (!link_state.link) |
| 367 | - phylink_mac_link_down(pl); |
| 368 | + phylink_link_down(pl); |
| 369 | else |
| 370 | - phylink_mac_link_up(pl, link_state); |
| 371 | + phylink_link_up(pl, link_state); |
| 372 | } |
| 373 | if (!link_state.link && retrigger) { |
| 374 | pl->mac_link_dropped = false; |
| 375 | @@ -643,7 +820,7 @@ static int phylink_register_sfp(struct phylink *pl, |
| 376 | * @fwnode: a pointer to a &struct fwnode_handle describing the network |
| 377 | * interface |
| 378 | * @iface: the desired link mode defined by &typedef phy_interface_t |
| 379 | - * @ops: a pointer to a &struct phylink_mac_ops for the MAC. |
| 380 | + * @mac_ops: a pointer to a &struct phylink_mac_ops for the MAC. |
| 381 | * |
| 382 | * Create a new phylink instance, and parse the link parameters found in @np. |
| 383 | * This will parse in-band modes, fixed-link or SFP configuration. |
| 384 | @@ -656,11 +833,17 @@ static int phylink_register_sfp(struct phylink *pl, |
| 385 | struct phylink *phylink_create(struct phylink_config *config, |
| 386 | struct fwnode_handle *fwnode, |
| 387 | phy_interface_t iface, |
| 388 | - const struct phylink_mac_ops *ops) |
| 389 | + const struct phylink_mac_ops *mac_ops) |
| 390 | { |
| 391 | + bool using_mac_select_pcs = false; |
| 392 | struct phylink *pl; |
| 393 | int ret; |
| 394 | |
| 395 | + if (mac_ops->mac_select_pcs && |
| 396 | + mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) != |
| 397 | + ERR_PTR(-EOPNOTSUPP)) |
| 398 | + using_mac_select_pcs = true; |
| 399 | + |
| 400 | pl = kzalloc(sizeof(*pl), GFP_KERNEL); |
| 401 | if (!pl) |
| 402 | return ERR_PTR(-ENOMEM); |
| 403 | @@ -678,6 +861,7 @@ struct phylink *phylink_create(struct phylink_config *config, |
| 404 | return ERR_PTR(-EINVAL); |
| 405 | } |
| 406 | |
| 407 | + pl->using_mac_select_pcs = using_mac_select_pcs; |
| 408 | pl->phy_state.interface = iface; |
| 409 | pl->link_interface = iface; |
| 410 | if (iface == PHY_INTERFACE_MODE_MOCA) |
| 411 | @@ -689,7 +873,7 @@ struct phylink *phylink_create(struct phylink_config *config, |
| 412 | pl->link_config.speed = SPEED_UNKNOWN; |
| 413 | pl->link_config.duplex = DUPLEX_UNKNOWN; |
| 414 | pl->link_config.an_enabled = true; |
| 415 | - pl->ops = ops; |
| 416 | + pl->mac_ops = mac_ops; |
| 417 | __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); |
| 418 | timer_setup(&pl->link_poll, phylink_fixed_poll, 0); |
| 419 | |
| 420 | @@ -1016,6 +1200,8 @@ static irqreturn_t phylink_link_handler(int irq, void *data) |
| 421 | */ |
| 422 | void phylink_start(struct phylink *pl) |
| 423 | { |
| 424 | + bool poll = false; |
| 425 | + |
| 426 | ASSERT_RTNL(); |
| 427 | |
| 428 | phylink_info(pl, "configuring for %s/%s link mode\n", |
developer | 2b4a015 | 2023-04-26 16:29:53 +0800 | [diff] [blame] | 429 | @@ -1029,15 +1215,13 @@ void phylink_start(struct phylink *pl) |
developer | 4e8a3fd | 2023-04-10 18:05:44 +0800 | [diff] [blame] | 430 | /* Apply the link configuration to the MAC when starting. This allows |
| 431 | * a fixed-link to start with the correct parameters, and also |
| 432 | * ensures that we set the appropriate advertisement for Serdes links. |
| 433 | - */ |
| 434 | - phylink_resolve_flow(pl, &pl->link_config); |
| 435 | - phylink_mac_config(pl, &pl->link_config); |
| 436 | - |
| 437 | - /* Restart autonegotiation if using 802.3z to ensure that the link |
| 438 | + * |
| 439 | + * Restart autonegotiation if using 802.3z to ensure that the link |
| 440 | * parameters are properly negotiated. This is necessary for DSA |
| 441 | * switches using 802.3z negotiation to ensure they see our modes. |
| 442 | */ |
| 443 | - phylink_mac_an_restart(pl); |
developer | 2b4a015 | 2023-04-26 16:29:53 +0800 | [diff] [blame] | 444 | + phylink_resolve_flow(pl, &pl->link_config); |
developer | 4e8a3fd | 2023-04-10 18:05:44 +0800 | [diff] [blame] | 445 | + phylink_mac_initial_config(pl, true); |
| 446 | |
| 447 | clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); |
| 448 | phylink_run_resolve(pl); |
| 449 | @@ -1055,10 +1238,19 @@ void phylink_start(struct phylink *pl) |
| 450 | irq = 0; |
| 451 | } |
| 452 | if (irq <= 0) |
| 453 | - mod_timer(&pl->link_poll, jiffies + HZ); |
| 454 | + poll = true; |
| 455 | } |
| 456 | - if ((pl->cfg_link_an_mode == MLO_AN_FIXED && pl->get_fixed_state) || |
| 457 | - (pl->cfg_link_an_mode == MLO_AN_INBAND)) |
| 458 | + |
| 459 | + switch (pl->cfg_link_an_mode) { |
| 460 | + case MLO_AN_FIXED: |
| 461 | + poll |= pl->config->poll_fixed_state; |
| 462 | + break; |
| 463 | + case MLO_AN_INBAND: |
| 464 | + if (pl->pcs) |
| 465 | + poll |= pl->pcs->poll; |
| 466 | + break; |
| 467 | + } |
| 468 | + if (poll) |
| 469 | mod_timer(&pl->link_poll, jiffies + HZ); |
| 470 | if (pl->phydev) |
| 471 | phy_start(pl->phydev); |
| 472 | @@ -1202,7 +1394,7 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, |
| 473 | if (pl->phydev) |
| 474 | break; |
| 475 | |
| 476 | - phylink_get_mac_state(pl, &link_state); |
| 477 | + phylink_mac_pcs_get_state(pl, &link_state); |
| 478 | |
| 479 | /* The MAC is reporting the link results from its own PCS |
| 480 | * layer via in-band status. Report these as the current |
| 481 | @@ -1314,7 +1506,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, |
| 482 | if (pl->cur_link_an_mode == MLO_AN_INBAND && |
| 483 | !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { |
| 484 | phylink_mac_config(pl, &pl->link_config); |
| 485 | - phylink_mac_an_restart(pl); |
| 486 | + phylink_mac_pcs_an_restart(pl); |
| 487 | } |
| 488 | mutex_unlock(&pl->state_mutex); |
| 489 | |
| 490 | @@ -1341,7 +1533,7 @@ int phylink_ethtool_nway_reset(struct phylink *pl) |
| 491 | |
| 492 | if (pl->phydev) |
| 493 | ret = phy_restart_aneg(pl->phydev); |
| 494 | - phylink_mac_an_restart(pl); |
| 495 | + phylink_mac_pcs_an_restart(pl); |
| 496 | |
| 497 | return ret; |
| 498 | } |
| 499 | @@ -1410,7 +1602,7 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, |
| 500 | |
| 501 | case MLO_AN_INBAND: |
| 502 | phylink_mac_config(pl, config); |
| 503 | - phylink_mac_an_restart(pl); |
| 504 | + phylink_mac_pcs_an_restart(pl); |
| 505 | break; |
| 506 | } |
| 507 | } |
| 508 | @@ -1621,10 +1813,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, |
| 509 | |
| 510 | case MLO_AN_INBAND: |
| 511 | if (phy_id == 0) { |
| 512 | - val = phylink_get_mac_state(pl, &state); |
| 513 | - if (val < 0) |
| 514 | - return val; |
| 515 | - |
| 516 | + phylink_mac_pcs_get_state(pl, &state); |
| 517 | val = phylink_mii_emul_read(reg, &state); |
| 518 | } |
| 519 | break; |
developer | 8900c0a | 2023-05-09 10:37:33 +0800 | [diff] [blame] | 520 | @@ -2010,7 +2010,7 @@ static int phylink_sfp_config(struct phylink *pl, u8 mode, |
| 521 | |
| 522 | if (changed && !test_bit(PHYLINK_DISABLE_STOPPED, |
| 523 | &pl->phylink_disable_state)) |
| 524 | - phylink_mac_config(pl, &pl->link_config); |
| 525 | + phylink_mac_initial_config(pl, false); |
| 526 | |
| 527 | return ret; |
| 528 | } |
developer | 4e8a3fd | 2023-04-10 18:05:44 +0800 | [diff] [blame] | 529 | diff --git a/include/linux/phylink.h b/include/linux/phylink.h |
| 530 | index 8229f56..ba0f09d 100644 |
| 531 | --- a/include/linux/phylink.h |
| 532 | +++ b/include/linux/phylink.h |
| 533 | @@ -63,17 +63,23 @@ enum phylink_op_type { |
| 534 | * struct phylink_config - PHYLINK configuration structure |
| 535 | * @dev: a pointer to a struct device associated with the MAC |
| 536 | * @type: operation type of PHYLINK instance |
| 537 | + * @poll_fixed_state: if true, starts link_poll, |
| 538 | + * if MAC link is at %MLO_AN_FIXED mode. |
| 539 | */ |
| 540 | struct phylink_config { |
| 541 | struct device *dev; |
| 542 | enum phylink_op_type type; |
| 543 | + bool poll_fixed_state; |
| 544 | }; |
| 545 | |
| 546 | /** |
| 547 | * struct phylink_mac_ops - MAC operations structure. |
| 548 | * @validate: Validate and update the link configuration. |
| 549 | + * @mac_select_pcs: Select a PCS for the interface mode. |
| 550 | * @mac_link_state: Read the current link state from the hardware. |
| 551 | + * @mac_prepare: prepare for a major reconfiguration of the interface. |
| 552 | * @mac_config: configure the MAC for the selected mode and state. |
| 553 | + * @mac_finish: finish a major reconfiguration of the interface. |
| 554 | * @mac_an_restart: restart 802.3z BaseX autonegotiation. |
| 555 | * @mac_link_down: take the link down. |
| 556 | * @mac_link_up: allow the link to come up. |
| 557 | @@ -84,10 +90,16 @@ struct phylink_mac_ops { |
| 558 | void (*validate)(struct phylink_config *config, |
| 559 | unsigned long *supported, |
| 560 | struct phylink_link_state *state); |
| 561 | + struct phylink_pcs *(*mac_select_pcs)(struct phylink_config *config, |
| 562 | + phy_interface_t interface); |
| 563 | int (*mac_link_state)(struct phylink_config *config, |
| 564 | struct phylink_link_state *state); |
| 565 | + int (*mac_prepare)(struct phylink_config *config, unsigned int mode, |
| 566 | + phy_interface_t iface); |
| 567 | void (*mac_config)(struct phylink_config *config, unsigned int mode, |
| 568 | const struct phylink_link_state *state); |
| 569 | + int (*mac_finish)(struct phylink_config *config, unsigned int mode, |
| 570 | + phy_interface_t iface); |
| 571 | void (*mac_an_restart)(struct phylink_config *config); |
| 572 | void (*mac_link_down)(struct phylink_config *config, unsigned int mode, |
| 573 | phy_interface_t interface); |
| 574 | @@ -126,6 +138,21 @@ struct phylink_mac_ops { |
| 575 | */ |
| 576 | void validate(struct phylink_config *config, unsigned long *supported, |
| 577 | struct phylink_link_state *state); |
| 578 | +/** |
| 579 | + * mac_select_pcs: Select a PCS for the interface mode. |
| 580 | + * @config: a pointer to a &struct phylink_config. |
| 581 | + * @interface: PHY interface mode for PCS |
| 582 | + * |
| 583 | + * Return the &struct phylink_pcs for the specified interface mode, or |
| 584 | + * NULL if none is required, or an error pointer on error. |
| 585 | + * |
| 586 | + * This must not modify any state. It is used to query which PCS should |
| 587 | + * be used. Phylink will use this during validation to ensure that the |
| 588 | + * configuration is valid, and when setting a configuration to internally |
| 589 | + * set the PCS that will be used. |
| 590 | + */ |
| 591 | +struct phylink_pcs *mac_select_pcs(struct phylink_config *config, |
| 592 | + phy_interface_t interface); |
| 593 | |
| 594 | /** |
| 595 | * mac_link_state() - Read the current link state from the hardware |
| 596 | @@ -141,6 +168,31 @@ void validate(struct phylink_config *config, unsigned long *supported, |
| 597 | int mac_link_state(struct phylink_config *config, |
| 598 | struct phylink_link_state *state); |
| 599 | |
| 600 | +/** |
| 601 | + * mac_prepare() - prepare to change the PHY interface mode |
| 602 | + * @config: a pointer to a &struct phylink_config. |
| 603 | + * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND. |
| 604 | + * @iface: interface mode to switch to |
| 605 | + * |
| 606 | + * phylink will call this method at the beginning of a full initialisation |
| 607 | + * of the link, which includes changing the interface mode or at initial |
| 608 | + * startup time. It may be called for the current mode. The MAC driver |
| 609 | + * should perform whatever actions are required, e.g. disabling the |
| 610 | + * Serdes PHY. |
| 611 | + * |
| 612 | + * This will be the first call in the sequence: |
| 613 | + * - mac_prepare() |
| 614 | + * - mac_config() |
| 615 | + * - pcs_config() |
| 616 | + * - possible pcs_an_restart() |
| 617 | + * - mac_finish() |
| 618 | + * |
| 619 | + * Returns zero on success, or negative errno on failure which will be |
| 620 | + * reported to the kernel log. |
| 621 | + */ |
| 622 | +int mac_prepare(struct phylink_config *config, unsigned int mode, |
| 623 | + phy_interface_t iface); |
| 624 | + |
| 625 | /** |
| 626 | * mac_config() - configure the MAC for the selected mode and state |
| 627 | * @config: a pointer to a &struct phylink_config. |
| 628 | @@ -195,6 +247,23 @@ int mac_link_state(struct phylink_config *config, |
| 629 | void mac_config(struct phylink_config *config, unsigned int mode, |
| 630 | const struct phylink_link_state *state); |
| 631 | |
| 632 | +/** |
| 633 | + * mac_finish() - finish a to change the PHY interface mode |
| 634 | + * @config: a pointer to a &struct phylink_config. |
| 635 | + * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND. |
| 636 | + * @iface: interface mode to switch to |
| 637 | + * |
| 638 | + * phylink will call this if it called mac_prepare() to allow the MAC to |
| 639 | + * complete any necessary steps after the MAC and PCS have been configured |
| 640 | + * for the @mode and @iface. E.g. a MAC driver may wish to re-enable the |
| 641 | + * Serdes PHY here if it was previously disabled by mac_prepare(). |
| 642 | + * |
| 643 | + * Returns zero on success, or negative errno on failure which will be |
| 644 | + * reported to the kernel log. |
| 645 | + */ |
| 646 | +int mac_finish(struct phylink_config *config, unsigned int mode, |
| 647 | + phy_interface_t iface); |
| 648 | + |
| 649 | /** |
| 650 | * mac_an_restart() - restart 802.3z BaseX autonegotiation |
| 651 | * @config: a pointer to a &struct phylink_config. |
| 652 | @@ -248,6 +317,132 @@ void mac_link_up(struct phylink_config *config, struct phy_device *phy, |
| 653 | int speed, int duplex, bool tx_pause, bool rx_pause); |
| 654 | #endif |
| 655 | |
| 656 | +struct phylink_pcs_ops; |
| 657 | + |
| 658 | +/** |
| 659 | + * struct phylink_pcs - PHYLINK PCS instance |
| 660 | + * @ops: a pointer to the &struct phylink_pcs_ops structure |
| 661 | + * @poll: poll the PCS for link changes |
| 662 | + * |
| 663 | + * This structure is designed to be embedded within the PCS private data, |
| 664 | + * and will be passed between phylink and the PCS. |
| 665 | + */ |
| 666 | +struct phylink_pcs { |
| 667 | + const struct phylink_pcs_ops *ops; |
| 668 | + bool poll; |
| 669 | +}; |
| 670 | + |
| 671 | +/** |
| 672 | + * struct phylink_pcs_ops - MAC PCS operations structure. |
| 673 | + * @pcs_validate: validate the link configuration. |
| 674 | + * @pcs_get_state: read the current MAC PCS link state from the hardware. |
| 675 | + * @pcs_config: configure the MAC PCS for the selected mode and state. |
| 676 | + * @pcs_an_restart: restart 802.3z BaseX autonegotiation. |
| 677 | + * @pcs_link_up: program the PCS for the resolved link configuration |
| 678 | + * (where necessary). |
| 679 | + */ |
| 680 | +struct phylink_pcs_ops { |
| 681 | + int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported, |
| 682 | + const struct phylink_link_state *state); |
| 683 | + void (*pcs_get_state)(struct phylink_pcs *pcs, |
| 684 | + struct phylink_link_state *state); |
| 685 | + int (*pcs_config)(struct phylink_pcs *pcs, unsigned int mode, |
| 686 | + phy_interface_t interface, |
| 687 | + const unsigned long *advertising, |
| 688 | + bool permit_pause_to_mac); |
| 689 | + void (*pcs_an_restart)(struct phylink_pcs *pcs); |
| 690 | + void (*pcs_link_up)(struct phylink_pcs *pcs, unsigned int mode, |
| 691 | + phy_interface_t interface, int speed, int duplex); |
| 692 | +}; |
| 693 | + |
| 694 | +#if 0 /* For kernel-doc purposes only. */ |
| 695 | +/** |
| 696 | + * pcs_validate() - validate the link configuration. |
| 697 | + * @pcs: a pointer to a &struct phylink_pcs. |
| 698 | + * @supported: ethtool bitmask for supported link modes. |
| 699 | + * @state: a const pointer to a &struct phylink_link_state. |
| 700 | + * |
| 701 | + * Validate the interface mode, and advertising's autoneg bit, removing any |
| 702 | + * media ethtool link modes that would not be supportable from the supported |
| 703 | + * mask. Phylink will propagate the changes to the advertising mask. See the |
| 704 | + * &struct phylink_mac_ops validate() method. |
| 705 | + * |
| 706 | + * Returns -EINVAL if the interface mode/autoneg mode is not supported. |
| 707 | + * Returns non-zero positive if the link state can be supported. |
| 708 | + */ |
| 709 | +int pcs_validate(struct phylink_pcs *pcs, unsigned long *supported, |
| 710 | + const struct phylink_link_state *state); |
| 711 | + |
| 712 | +/** |
| 713 | + * pcs_get_state() - Read the current inband link state from the hardware |
| 714 | + * @pcs: a pointer to a &struct phylink_pcs. |
| 715 | + * @state: a pointer to a &struct phylink_link_state. |
| 716 | + * |
| 717 | + * Read the current inband link state from the MAC PCS, reporting the |
| 718 | + * current speed in @state->speed, duplex mode in @state->duplex, pause |
| 719 | + * mode in @state->pause using the %MLO_PAUSE_RX and %MLO_PAUSE_TX bits, |
| 720 | + * negotiation completion state in @state->an_complete, and link up state |
| 721 | + * in @state->link. If possible, @state->lp_advertising should also be |
| 722 | + * populated. |
| 723 | + * |
| 724 | + * When present, this overrides mac_pcs_get_state() in &struct |
| 725 | + * phylink_mac_ops. |
| 726 | + */ |
| 727 | +void pcs_get_state(struct phylink_pcs *pcs, |
| 728 | + struct phylink_link_state *state); |
| 729 | + |
| 730 | +/** |
| 731 | + * pcs_config() - Configure the PCS mode and advertisement |
| 732 | + * @pcs: a pointer to a &struct phylink_pcs. |
| 733 | + * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND. |
| 734 | + * @interface: interface mode to be used |
| 735 | + * @advertising: adertisement ethtool link mode mask |
| 736 | + * @permit_pause_to_mac: permit forwarding pause resolution to MAC |
| 737 | + * |
| 738 | + * Configure the PCS for the operating mode, the interface mode, and set |
| 739 | + * the advertisement mask. @permit_pause_to_mac indicates whether the |
| 740 | + * hardware may forward the pause mode resolution to the MAC. |
| 741 | + * |
| 742 | + * When operating in %MLO_AN_INBAND, inband should always be enabled, |
| 743 | + * otherwise inband should be disabled. |
| 744 | + * |
| 745 | + * For SGMII, there is no advertisement from the MAC side, the PCS should |
| 746 | + * be programmed to acknowledge the inband word from the PHY. |
| 747 | + * |
| 748 | + * For 1000BASE-X, the advertisement should be programmed into the PCS. |
| 749 | + * |
| 750 | + * For most 10GBASE-R, there is no advertisement. |
| 751 | + */ |
| 752 | +int pcs_config(struct phylink_pcs *pcs, unsigned int mode, |
| 753 | + phy_interface_t interface, const unsigned long *advertising, |
| 754 | + bool permit_pause_to_mac); |
| 755 | + |
| 756 | +/** |
| 757 | + * pcs_an_restart() - restart 802.3z BaseX autonegotiation |
| 758 | + * @pcs: a pointer to a &struct phylink_pcs. |
| 759 | + * |
| 760 | + * When PCS ops are present, this overrides mac_an_restart() in &struct |
| 761 | + * phylink_mac_ops. |
| 762 | + */ |
| 763 | +void pcs_an_restart(struct phylink_pcs *pcs); |
| 764 | + |
| 765 | +/** |
| 766 | + * pcs_link_up() - program the PCS for the resolved link configuration |
| 767 | + * @pcs: a pointer to a &struct phylink_pcs. |
| 768 | + * @mode: link autonegotiation mode |
| 769 | + * @interface: link &typedef phy_interface_t mode |
| 770 | + * @speed: link speed |
| 771 | + * @duplex: link duplex |
| 772 | + * |
| 773 | + * This call will be made just before mac_link_up() to inform the PCS of |
| 774 | + * the resolved link parameters. For example, a PCS operating in SGMII |
| 775 | + * mode without in-band AN needs to be manually configured for the link |
| 776 | + * and duplex setting. Otherwise, this should be a no-op. |
| 777 | + */ |
| 778 | +void pcs_link_up(struct phylink_pcs *pcs, unsigned int mode, |
| 779 | + phy_interface_t interface, int speed, int duplex); |
| 780 | +#endif |
| 781 | + |
| 782 | struct phylink *phylink_create(struct phylink_config *, struct fwnode_handle *, |
| 783 | phy_interface_t iface, |
| 784 | const struct phylink_mac_ops *ops); |