diff --git a/drivers/net/pcs/pcs.c b/drivers/net/pcs/pcs.c
index 25b3248e8b93..fef560eddf2a 100644
--- a/drivers/net/pcs/pcs.c
+++ b/drivers/net/pcs/pcs.c
@@ -69,6 +69,10 @@ fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
mutex_unlock(&fwnode_pcs_mutex);
+ blocking_notifier_call_chain(&fwnode_pcs_notify_list,
+ FWNODE_PCS_PROVIDER_ADD,
+ pp);
+
pr_debug("Added pcs provider from %pfwf\n", fwnode);
return pp;@@ -201,6 +205,24 @@ struct phylink_pcs *fwnode_pcs_get(const struct fwnode_handle *fwnode, unsigned
}
EXPORT_SYMBOL_GPL(fwnode_pcs_get);
+struct phylink_pcs *fwnode_pcs_get_from_provider(struct fwnode_pcs_provider *provider,
+ const struct fwnode_handle *fwnode,
+ int index)
+{
+ struct fwnode_reference_args pcsspec;
+ struct phylink_pcs *pcs;
+ int ret;
+
+ ret = fwnode_parse_pcsspec(fwnode, index, NULL, &pcsspec);
+ if (ret)
+ return ERR_PTR(ret);
+
+ pcs = __fwnode_pcs_get_from_pcsspec_provider(&pcsspec, provider);
+ fwnode_handle_put(pcsspec.fwnode);
+ return pcs;
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_get_from_provider);
+
bool fwnode_pcs_matches_provider(struct fwnode_pcs_provider *provider,
const struct fwnode_handle *fwnode,
struct phylink_pcs *pl_pcs)diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index dbf0e8292f7b..39d742c90db7 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1926,6 +1926,27 @@ int phylink_set_fixed_link(struct phylink *pl,
}
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
+static void phylink_add_pcs(struct phylink *pl, struct phylink_pcs *pcs)
+{
+ struct phylink_pcs *tmp;
+
+ /*
+ * Make sure state mutex is locked to protect concurrent
+ * access to phylink instance PCS list from
+ * initial fill_available_pcs and late PCS attach
+ */
+ lockdep_assert_held(&pl->state_mutex);
+
+ list_for_each_entry(tmp, &pl->pcs_list, list)
+ if (tmp == pcs)
+ return;
+
+ list_add_tail(&pcs->list, &pl->pcs_list);
+
+ /* Link PCS to phylink */
+ pcs->phylink = pl;
+}
+
static int phylink_fill_available_pcs(struct phylink *pl,
struct phylink_config *config)
{@@ -1958,7 +1979,7 @@ static int phylink_fill_available_pcs(struct phylink *pl,
if (!pcs)
continue;
- list_add_tail(&pcs->list, &pl->pcs_list);
+ phylink_add_pcs(pl, pcs);
}
mutex_unlock(&pl->state_mutex);
@@ -2004,27 +2025,57 @@ static int pcs_provider_notify(struct notifier_block *self,
struct fwnode_pcs_provider *pp = data;
struct phylink_pcs *pcs, *tmp;
bool resolve = false;
+ int count, i;
- rtnl_lock();
+ /*
+ * On PCS provider deletion hold rtnl lock as one of
+ * PCS can be currently in use by the phylink instance
+ * and ethtool OPs can reference it.
+ */
+ if (val == FWNODE_PCS_PROVIDER_DEL)
+ rtnl_lock();
mutex_lock(&pl->state_mutex);
- /*
- * Loop all the PCS for phylink instance and check if
- * this notification is relevant for some of them.
- */
- list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list) {
- if (!fwnode_pcs_matches_provider(pp, pl->fwnode, pcs))
- continue;
+ switch (val) {
+ case FWNODE_PCS_PROVIDER_ADD:
+ count = fwnode_phylink_pcs_count(pl->fwnode);
+ for (i = 0; i < count; i++) {
+ pcs = fwnode_pcs_get_from_provider(pp, pl->fwnode, i);
+ if (IS_ERR(pcs))
+ continue;
+
+ phylink_add_pcs(pl, pcs);
+ resolve = true;
+ }
+
+ /* Force an interface reconfig if major config fail */
+ if (resolve && pl->major_config_failed)
+ pl->force_major_config = true;
- phylink_del_pcs(pl, pcs);
- resolve = true;
+ break;
+ case FWNODE_PCS_PROVIDER_DEL:
+ /*
+ * Loop all the PCS for phylink instance and check if
+ * this notification is relevant for some of them.
+ */
+ list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list) {
+ if (!fwnode_pcs_matches_provider(pp, pl->fwnode, pcs))
+ continue;
+
+ phylink_del_pcs(pl, pcs);
+ resolve = true;
+ }
+ break;
}
/* Exit early if nothing has changed */
if (!resolve) {
mutex_unlock(&pl->state_mutex);
- rtnl_unlock();
+
+ if (val == FWNODE_PCS_PROVIDER_DEL)
+ rtnl_unlock();
+
return NOTIFY_DONE;
}
@@ -2038,7 +2089,8 @@ static int pcs_provider_notify(struct notifier_block *self,
mutex_unlock(&pl->state_mutex);
- rtnl_unlock();
+ if (val == FWNODE_PCS_PROVIDER_DEL)
+ rtnl_unlock();
phylink_run_resolve(pl);
@@ -2121,10 +2173,6 @@ struct phylink *phylink_create(struct phylink_config *config,
mutex_lock(&pl->state_mutex);
- /* Link available PCS to phylink */
- list_for_each_entry(pcs, &pl->pcs_list, list)
- pcs->phylink = pl;
-
phy_interface_copy(pl->supported_interfaces,
pl->config->supported_interfaces);
diff --git a/include/linux/pcs/pcs.h b/include/linux/pcs/pcs.h
index 83e0a2ad4b09..00a636c82c65 100644
--- a/include/linux/pcs/pcs.h
+++ b/include/linux/pcs/pcs.h
@@ -5,6 +5,7 @@
#include <linux/phylink.h>
enum fwnode_pcs_notify_event {
+ FWNODE_PCS_PROVIDER_ADD,
FWNODE_PCS_PROVIDER_DEL,
};
@@ -50,6 +51,28 @@ int unregister_fwnode_pcs_notifier(struct notifier_block *nb);
struct phylink_pcs *fwnode_pcs_get(const struct fwnode_handle *fwnode,
unsigned int index);
+/**
+ * fwnode_pcs_get_from_provider() - Retrieve a PCS from a specific provider
+ * @provider: PCS provider to use
+ * @fwnode: firmware node
+ * @index: index fwnode PCS handle in firmware node
+ *
+ * Get a PCS from the firmware node at index specifically provided by
+ * passed PCS provider.
+ *
+ * Unlike fwnode_pcs_get(), this function does not search the global list of
+ * PCS providers. The caller must provide the provider responsible for the
+ * referenced PCS.
+ *
+ * Returns: a pointer to the phylink_pcs or a negative error pointer. Can
+ * return -ENODEV if the PCS is not present in global providers list (either
+ * due to driver still needs to be probed or it failed to probe/removed) or
+ * can return -EINVAL if the PCS provider doesn't expose any PCS for the fwnode.
+ */
+struct phylink_pcs *fwnode_pcs_get_from_provider(struct fwnode_pcs_provider *provider,
+ const struct fwnode_handle *fwnode,
+ int index);
+
/**
* fwnode_pcs_matches_provider() - Check whether a PCS belongs to a provider
* @provider: PCS provider to check
@@ -120,6 +143,14 @@ static inline struct phylink_pcs *fwnode_pcs_get(const struct fwnode_handle *fwn
return ERR_PTR(-ENOENT);
}
+static inline struct phylink_pcs *
+fwnode_pcs_get_from_provider(struct fwnode_pcs_provider *provider,
+ const struct fwnode_handle *fwnode,
+ int index)
+{
+ return ERR_PTR(-ENOENT);
+}
+
static inline bool fwnode_pcs_matches_provider(struct fwnode_pcs_provider *provider,
const struct fwnode_handle *fwnode,
struct phylink_pcs *pl_pcs)--
2.53.0