Skip to content

Commit e7a9870

Browse files
rmandradfrank-w
authored andcommitted
net: ethernet: mediatek: mtkmux: harden mux/phylink lifecycle
1 parent 5248d48 commit e7a9870

8 files changed

Lines changed: 293 additions & 66 deletions

File tree

drivers/net/ethernet/mediatek/mtk_eth_soc.c

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5360,65 +5360,77 @@ static void mux_poll(struct work_struct *work)
53605360
struct mtk_eth *eth = mac->hw;
53615361
struct net_device *dev = eth->netdev[mac->id];
53625362
unsigned int new_channel;
5363-
struct phylink *tmp_pl;
5363+
struct phylink *old_pl;
53645364
int sfp_present;
5365+
int err;
5366+
bool running;
5367+
bool transitional_old_pl;
53655368

53665369
//dev_info(eth->dev, "ethernet mux: %s:%d\n",__func__,__LINE__);
53675370
if (IS_ERR(mux->mod_def0_gpio) || IS_ERR(mux->chan_sel_gpio))
53685371
goto reschedule;
53695372

53705373
sfp_present = gpiod_get_value_cansleep(mux->mod_def0_gpio);
5374+
if (sfp_present < 0)
5375+
goto reschedule;
53715376
new_channel = sfp_present ? mux->sfp_present_channel : !mux->sfp_present_channel;
53725377

5373-
if (mux->channel == new_channel || !netif_running(dev))
5378+
if (mux->channel == new_channel)
53745379
goto reschedule;
53755380

5381+
running = netif_running(dev);
5382+
53765383
dev_info(eth->dev, "ethernet mux: line:%d new channel:%d,sfp:%d\n",__LINE__, new_channel,sfp_present);
53775384

5385+
if (mux->data[mux->channel] && mux->data[mux->channel]->phylink)
5386+
old_pl = mux->data[mux->channel]->phylink;
5387+
else
5388+
old_pl = mac->phylink;
5389+
transitional_old_pl = old_pl && old_pl == mux->initial_phylink;
5390+
53785391
rtnl_lock();
5379-
mtk_stop(dev);
5392+
if (running)
5393+
mtk_stop(dev);
53805394
rtnl_unlock();
53815395

5382-
/* Destroy old phylink if it exists */
5383-
if (mux->data[mux->channel] && mux->data[mux->channel]->phylink) {
5384-
tmp_pl = mux->data[mux->channel]->phylink;
5396+
if (old_pl) {
53855397
dev_info(eth->dev, "Destroying phylink for channel %u\n", mux->channel);
5386-
} else {
5387-
/* phylink was created by mtk_add_mac,
5388-
we need to release the reference to available PCS from phylink config
5389-
*/
5390-
tmp_pl = mac->phylink;
5398+
if (mux->data[mux->channel] &&
5399+
mux->data[mux->channel]->phylink == old_pl)
5400+
mux->data[mux->channel]->phylink = NULL;
5401+
phylink_destroy(old_pl);
5402+
if (transitional_old_pl)
5403+
mux->initial_phylink = NULL;
53915404
}
5392-
if (tmp_pl) {
5393-
phylink_destroy(tmp_pl);
5394-
mux->data[mux->channel]->phylink = NULL;
5395-
}
5396-
5397-
dev_info(eth->dev, "ethernet mux: switch to channel%d\n", new_channel);
53985405

5399-
/* Create new phylink if not yet present */
5406+
/* Build target phylink after tearing down the old one, since both
5407+
* instances share the same PCS objects in mac->available_pcs.
5408+
*/
54005409
if (!mux->data[new_channel]->phylink) {
54015410
mux->data[new_channel]->phylink = mtk_mux_create_phylink(mux, new_channel);
54025411
if (IS_ERR(mux->data[new_channel]->phylink)) {
54035412
dev_err(eth->dev, "Failed to create new phylink\n");
5404-
mux->data[new_channel]->phylink=NULL;
5405-
goto out_unlock;
5413+
mux->data[new_channel]->phylink = NULL;
5414+
goto reschedule;
54065415
}
54075416
}
54085417

5418+
rtnl_lock();
5419+
dev_info(eth->dev, "ethernet mux: switch to channel%d\n", new_channel);
54095420
mac->of_node = mux->data[new_channel]->of_node;
54105421
mac->phylink = mux->data[new_channel]->phylink;
54115422

5412-
rtnl_lock();
5413-
mtk_open(dev);
5414-
rtnl_unlock();
5423+
if (running) {
5424+
err = mtk_open(dev);
5425+
if (err)
5426+
dev_err(eth->dev, "ethernet mux: failed to open dev on channel %u: %d\n",
5427+
new_channel, err);
5428+
}
54155429

54165430
gpiod_set_value_cansleep(mux->chan_sel_gpio, new_channel);
54175431
mux->channel = new_channel;
5418-
goto reschedule;
5419-
5420-
out_unlock:
54215432
rtnl_unlock();
5433+
54225434
reschedule:
54235435
mod_delayed_work(system_wq, &mux->poll, msecs_to_jiffies(100));
54245436
}
@@ -5499,6 +5511,13 @@ static void mtk_release_mux(struct mtk_eth *eth, int id)
54995511
kfree(mux->data[i]);
55005512
}
55015513
}
5514+
5515+
/* Destroy transitional phylink created by mtk_add_mac() when it was
5516+
* never promoted into a mux channel instance.
5517+
*/
5518+
if (mux->initial_phylink)
5519+
phylink_destroy(mux->initial_phylink);
5520+
55025521
kfree(mux);
55035522
eth->mux[id] = NULL;
55045523
}
@@ -5516,7 +5535,8 @@ static int mtk_add_mux(struct mtk_eth *eth, struct device_node *np)
55165535
struct device_node *child;
55175536
struct mtk_mux *mux;
55185537
unsigned int id;
5519-
int err;
5538+
unsigned int initial_channel;
5539+
int err, sfp_present;
55205540

55215541
if (!_id) {
55225542
dev_err(eth->dev, "missing attach mac id\n");
@@ -5529,15 +5549,15 @@ static int mtk_add_mux(struct mtk_eth *eth, struct device_node *np)
55295549
return -EINVAL;
55305550
}
55315551

5532-
mux = kmalloc(sizeof(struct mtk_mux), GFP_KERNEL);
5552+
mux = kzalloc(sizeof(struct mtk_mux), GFP_KERNEL);
55335553
if (unlikely(!mux)) {
55345554
dev_err(eth->dev, "failed to create mux structure\n");
55355555
return -ENOMEM;
55365556
}
55375557

55385558
eth->mux[id] = mux;
55395559
mux->mac = eth->mac[id];
5540-
mux->channel = 0;//more than channels, just to make current channel invalid for switching the first time the gpio is read
5560+
mux->initial_phylink = mux->mac ? mux->mac->phylink : NULL;
55415561

55425562
mux->mod_def0_gpio = fwnode_gpiod_get_index(of_fwnode_handle(np),
55435563
"mod-def0", 0, GPIOD_IN |
@@ -5558,8 +5578,11 @@ static int mtk_add_mux(struct mtk_eth *eth, struct device_node *np)
55585578
goto err_put_mod_def0;
55595579
}
55605580

5561-
of_property_read_u32(np, "sfp-present-channel",
5562-
&mux->sfp_present_channel);
5581+
if (of_property_read_u32(np, "sfp-present-channel",
5582+
&mux->sfp_present_channel))
5583+
mux->sfp_present_channel = 0;
5584+
else if (mux->sfp_present_channel > 1)
5585+
mux->sfp_present_channel = 1;
55635586

55645587
for_each_child_of_node(np, child) {
55655588
err = mtk_add_mux_channel(mux, child);
@@ -5571,7 +5594,18 @@ static int mtk_add_mux(struct mtk_eth *eth, struct device_node *np)
55715594
//should set initial mux->channel be set if ! mux->sfp_present_channel?
55725595
}
55735596

5574-
gpiod_set_value_cansleep(mux->chan_sel_gpio, mux->sfp_present_channel ? 0 : 1);
5597+
/* Configure initial mux channel based on current module presence. */
5598+
sfp_present = gpiod_get_value_cansleep(mux->mod_def0_gpio);
5599+
if (sfp_present < 0) {
5600+
dev_warn(eth->dev, "failed to read mod-def0 gpio, defaulting to non-SFP channel\n");
5601+
initial_channel = !mux->sfp_present_channel;
5602+
} else {
5603+
initial_channel = sfp_present ? mux->sfp_present_channel :
5604+
!mux->sfp_present_channel;
5605+
}
5606+
gpiod_set_value_cansleep(mux->chan_sel_gpio, initial_channel);
5607+
/* Force first poll pass to (re)build phylink for selected channel. */
5608+
mux->channel = !initial_channel;
55755609

55765610
dev_info(eth->dev, "ethernet mux: line:%d added new mux\n",__LINE__);
55775611
INIT_DELAYED_WORK(&mux->poll, mux_poll);

drivers/net/ethernet/mediatek/mtk_eth_soc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,7 @@ struct mtk_mux {
15741574
struct gpio_desc *chan_sel_gpio;
15751575
struct mtk_mux_data *data[2];
15761576
struct mtk_mac *mac;
1577+
struct phylink *initial_phylink;
15771578
unsigned int channel;
15781579
unsigned int sfp_present_channel;
15791580
};

drivers/net/pcs/pcs.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ int register_fwnode_pcs_notifier(struct notifier_block *nb)
3030
}
3131
EXPORT_SYMBOL_GPL(register_fwnode_pcs_notifier);
3232

33+
int unregister_fwnode_pcs_notifier(struct notifier_block *nb)
34+
{
35+
return blocking_notifier_chain_unregister(&fwnode_pcs_notify_list, nb);
36+
}
37+
EXPORT_SYMBOL_GPL(unregister_fwnode_pcs_notifier);
38+
3339
struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
3440
void *data)
3541
{

drivers/net/phy/phylink.c

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct phylink {
6363
/* List of available PCS */
6464
struct list_head pcs_list;
6565
struct notifier_block fwnode_pcs_nb;
66+
bool fwnode_pcs_nb_registered;
6667

6768
/* What interface are supported by the current link.
6869
* Can change on removal or addition of new PCS.
@@ -2061,7 +2062,12 @@ struct phylink *phylink_create(struct phylink_config *config,
20612062

20622063
if (!phy_interface_empty(config->pcs_interfaces)) {
20632064
pl->fwnode_pcs_nb.notifier_call = pcs_provider_notify;
2064-
register_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
2065+
ret = register_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
2066+
if (ret && ret != -EOPNOTSUPP) {
2067+
kfree(pl);
2068+
return ERR_PTR(ret);
2069+
}
2070+
pl->fwnode_pcs_nb_registered = !ret;
20652071
}
20662072

20672073
pl->config = config;
@@ -2071,8 +2077,8 @@ struct phylink *phylink_create(struct phylink_config *config,
20712077
} else if (config->type == PHYLINK_DEV) {
20722078
pl->dev = config->dev;
20732079
} else {
2074-
kfree(pl);
2075-
return ERR_PTR(-EINVAL);
2080+
ret = -EINVAL;
2081+
goto err_unregister_fwnode_pcs_notifier;
20762082
}
20772083

20782084
pl->mac_supports_eee_ops = phylink_mac_implements_lpi(mac_ops);
@@ -2105,28 +2111,28 @@ struct phylink *phylink_create(struct phylink_config *config,
21052111
phylink_validate(pl, pl->supported, &pl->link_config);
21062112

21072113
ret = phylink_parse_mode(pl, fwnode);
2108-
if (ret < 0) {
2109-
kfree(pl);
2110-
return ERR_PTR(ret);
2111-
}
2114+
if (ret < 0)
2115+
goto err_unregister_fwnode_pcs_notifier;
21122116

21132117
if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
21142118
ret = phylink_parse_fixedlink(pl, fwnode);
2115-
if (ret < 0) {
2116-
kfree(pl);
2117-
return ERR_PTR(ret);
2118-
}
2119+
if (ret < 0)
2120+
goto err_unregister_fwnode_pcs_notifier;
21192121
}
21202122

21212123
pl->req_link_an_mode = pl->cfg_link_an_mode;
21222124

21232125
ret = phylink_register_sfp(pl, fwnode);
2124-
if (ret < 0) {
2125-
kfree(pl);
2126-
return ERR_PTR(ret);
2127-
}
2126+
if (ret < 0)
2127+
goto err_unregister_fwnode_pcs_notifier;
21282128

21292129
return pl;
2130+
2131+
err_unregister_fwnode_pcs_notifier:
2132+
if (pl->fwnode_pcs_nb_registered)
2133+
unregister_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
2134+
kfree(pl);
2135+
return ERR_PTR(ret);
21302136
}
21312137
EXPORT_SYMBOL_GPL(phylink_create);
21322138

@@ -2151,6 +2157,9 @@ void phylink_destroy(struct phylink *pl)
21512157
list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list)
21522158
list_del(&pcs->list);
21532159

2160+
if (pl->fwnode_pcs_nb_registered)
2161+
unregister_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
2162+
21542163
cancel_work_sync(&pl->resolve);
21552164
kfree(pl);
21562165
}

include/linux/pcs/pcs.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ enum fwnode_pcs_notify_event {
2222
*/
2323
int register_fwnode_pcs_notifier(struct notifier_block *nb);
2424

25+
/**
26+
* unregister_fwnode_pcs_notifier - Unregister a notifier block for fwnode
27+
* PCS events
28+
* @nb: pointer to the notifier block
29+
*
30+
* Unregisters a notifier block previously registered with
31+
* register_fwnode_pcs_notifier().
32+
*
33+
* Returns 0 or a negative error.
34+
*/
35+
int unregister_fwnode_pcs_notifier(struct notifier_block *nb);
36+
2537
/**
2638
* fwnode_pcs_get - Retrieves a PCS from a firmware node
2739
* @fwnode: firmware node
@@ -80,6 +92,11 @@ static int register_fwnode_pcs_notifier(struct notifier_block *nb)
8092
return -EOPNOTSUPP;
8193
}
8294

95+
static int unregister_fwnode_pcs_notifier(struct notifier_block *nb)
96+
{
97+
return -EOPNOTSUPP;
98+
}
99+
83100
static inline struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
84101
int index)
85102
{

0 commit comments

Comments
 (0)