mirror of
https://github.com/openwrt/openwrt.git
synced 2026-06-17 12:40:16 +04:00
627cd79e1c
While implementing standalone PCS support for DSA, it was found that making the MAC driver passing the available_pcs array is limiting and problematic for memory handling and allocation. To better handle this, change the logic and make phylink allocate the struct and make the MAC driver implement a function in phylink_config .fill_available_pcs to fill the PCS array. Update the Airoha and Mediatek driver to reflect this new implementation. Link: https://github.com/openwrt/openwrt/pull/23413 Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
447 lines
14 KiB
Diff
447 lines
14 KiB
Diff
From 2606833af0ba47d4740b004afbcae2ad10f2efb5 Mon Sep 17 00:00:00 2001
|
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
|
Date: Mon, 31 Mar 2025 16:03:26 +0200
|
|
Subject: [PATCH 1/9] net: phylink: introduce internal phylink PCS handling
|
|
|
|
Introduce internal handling of PCS for phylink. This is an alternative
|
|
way to .mac_select_pcs that moves the selection logic of the PCS entirely
|
|
to phylink with the usage of the supported_interface value in the PCS
|
|
struct.
|
|
|
|
MAC should now provide a callback to fill the available PCS in
|
|
phylink_config in .fill_available_pcs and fill the .num_available_pcs with
|
|
the number of elements in the array. MAC should also define a new bitmap,
|
|
pcs_interfaces, in phylink_config to define for what interface mode a
|
|
dedicated PCS is required.
|
|
|
|
On phylink_create(), an array of PCS pointer is allocated of size
|
|
.num_available_pcs from phylink_config and .fill_available_pcs from
|
|
phylink_config is called passing as args the just allocated array and
|
|
the numer of available element in it.
|
|
|
|
MAC will fill this passed array with all the available PCS.
|
|
|
|
This array is then parsed and a linked list of PCS is created based on
|
|
the allocated PCS array filled by MAC via .fill_available_pcs().
|
|
Also the supported_interface value in phylink struct is updated with the
|
|
new supported_interface from the provided PCS.
|
|
|
|
On phylink_start() every PCS in phylink PCS list gets attached to the
|
|
phylink instance. This is done by setting the phylink value in
|
|
phylink_pcs struct to the phylink instance.
|
|
|
|
On phylink_stop(), every PCS in phylink PCS list is detached from the
|
|
phylink instance. This is done by setting the phylink value in
|
|
phylink_pcs struct to NULL.
|
|
|
|
phylink_validate_mac_and_pcs(), phylink_major_config() and
|
|
phylink_inband_caps() are updated to support this new implementation
|
|
with the PCS list stored in phylink.
|
|
|
|
They will make use of phylink_validate_pcs_interface() that will loop
|
|
for every PCS in the phylink PCS available list and find one that supports
|
|
the passed interface.
|
|
|
|
phylink_validate_pcs_interface() applies the same logic of .mac_select_pcs
|
|
where if a supported_interface value is not set for the PCS struct, then
|
|
it's assumed every interface is supported.
|
|
|
|
A MAC is required to implement either a .mac_select_pcs or make use of
|
|
the PCS list implementation. Implementing both will result in a fail
|
|
on MAC/PCS validation.
|
|
|
|
A MAC defining .num_available_pcs in phylink_config MUST also define a
|
|
.fill_available_pcs or phylink_create() will fail with an negative error.
|
|
|
|
phylink value in phylink_pcs struct with this implementation is used to
|
|
track from PCS side when it's attached to a phylink instance. PCS driver
|
|
will make use of this information to correctly detach from a phylink
|
|
instance if needed.
|
|
|
|
The .mac_select_pcs implementation is not changed but it's expected that
|
|
every MAC driver migrates to the new implementation to later deprecate
|
|
and remove .mac_select_pcs.
|
|
|
|
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
|
---
|
|
drivers/net/phy/phylink.c | 185 ++++++++++++++++++++++++++++++++++----
|
|
include/linux/phylink.h | 16 ++++
|
|
2 files changed, 183 insertions(+), 18 deletions(-)
|
|
|
|
--- a/drivers/net/phy/phylink.c
|
|
+++ b/drivers/net/phy/phylink.c
|
|
@@ -60,6 +60,9 @@ struct phylink {
|
|
/* The link configuration settings */
|
|
struct phylink_link_state link_config;
|
|
|
|
+ /* List of available PCS */
|
|
+ struct list_head pcs_list;
|
|
+
|
|
/* What interface are supported by the current link.
|
|
* Can change on removal or addition of new PCS.
|
|
*/
|
|
@@ -151,6 +154,8 @@ static const phy_interface_t phylink_sfp
|
|
|
|
static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
|
|
|
|
+static void phylink_run_resolve(struct phylink *pl);
|
|
+
|
|
/**
|
|
* phylink_set_port_modes() - set the port type modes in the ethtool mask
|
|
* @mask: ethtool link mode mask
|
|
@@ -514,22 +519,59 @@ static void phylink_validate_mask_caps(u
|
|
linkmode_and(state->advertising, state->advertising, mask);
|
|
}
|
|
|
|
+static int phylink_validate_pcs_interface(struct phylink_pcs *pcs,
|
|
+ phy_interface_t interface)
|
|
+{
|
|
+ /* If PCS define an empty supported_interfaces value, assume
|
|
+ * all interface are supported.
|
|
+ */
|
|
+ if (phy_interface_empty(pcs->supported_interfaces))
|
|
+ return 0;
|
|
+
|
|
+ /* Ensure that this PCS supports the interface mode */
|
|
+ if (!test_bit(interface, pcs->supported_interfaces))
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int phylink_validate_mac_and_pcs(struct phylink *pl,
|
|
unsigned long *supported,
|
|
struct phylink_link_state *state)
|
|
{
|
|
- struct phylink_pcs *pcs = NULL;
|
|
unsigned long capabilities;
|
|
+ struct phylink_pcs *pcs;
|
|
+ bool pcs_found = false;
|
|
int ret;
|
|
|
|
/* Get the PCS for this interface mode */
|
|
if (pl->mac_ops->mac_select_pcs) {
|
|
+ /* Make sure either PCS internal validation or .mac_select_pcs
|
|
+ * is used. Return error if both are defined.
|
|
+ */
|
|
+ if (!list_empty(&pl->pcs_list)) {
|
|
+ phylink_err(pl, "either phylink_pcs_add() or .mac_select_pcs must be used\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
|
|
if (IS_ERR(pcs))
|
|
return PTR_ERR(pcs);
|
|
+
|
|
+ pcs_found = !!pcs;
|
|
+ } else {
|
|
+ /* Check every assigned PCS and search for one that supports
|
|
+ * the interface.
|
|
+ */
|
|
+ list_for_each_entry(pcs, &pl->pcs_list, list) {
|
|
+ if (!phylink_validate_pcs_interface(pcs, state->interface)) {
|
|
+ pcs_found = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
- if (pcs) {
|
|
+ if (pcs_found) {
|
|
/* The PCS, if present, must be setup before phylink_create()
|
|
* has been called. If the ops is not initialised, print an
|
|
* error and backtrace rather than oopsing the kernel.
|
|
@@ -541,13 +583,10 @@ static int phylink_validate_mac_and_pcs(
|
|
return -EINVAL;
|
|
}
|
|
|
|
- /* Ensure that this PCS supports the interface which the MAC
|
|
- * returned it for. It is an error for the MAC to return a PCS
|
|
- * that does not support the interface mode.
|
|
- */
|
|
- if (!phy_interface_empty(pcs->supported_interfaces) &&
|
|
- !test_bit(state->interface, pcs->supported_interfaces)) {
|
|
- phylink_err(pl, "MAC returned PCS which does not support %s\n",
|
|
+ /* Recheck PCS to handle legacy way for .mac_select_pcs */
|
|
+ ret = phylink_validate_pcs_interface(pcs, state->interface);
|
|
+ if (ret) {
|
|
+ phylink_err(pl, "selected PCS does not support %s\n",
|
|
phy_modes(state->interface));
|
|
return -EINVAL;
|
|
}
|
|
@@ -961,12 +1000,22 @@ static unsigned int phylink_inband_caps(
|
|
phy_interface_t interface)
|
|
{
|
|
struct phylink_pcs *pcs;
|
|
+ bool pcs_found = false;
|
|
|
|
- if (!pl->mac_ops->mac_select_pcs)
|
|
- return 0;
|
|
+ if (pl->mac_ops->mac_select_pcs) {
|
|
+ pcs = pl->mac_ops->mac_select_pcs(pl->config,
|
|
+ interface);
|
|
+ pcs_found = !!pcs;
|
|
+ } else {
|
|
+ list_for_each_entry(pcs, &pl->pcs_list, list) {
|
|
+ if (!phylink_validate_pcs_interface(pcs, interface)) {
|
|
+ pcs_found = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
|
|
- pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
|
|
- if (!pcs)
|
|
+ if (!pcs_found)
|
|
return 0;
|
|
|
|
return phylink_pcs_inband_caps(pcs, interface);
|
|
@@ -1261,10 +1310,36 @@ static void phylink_major_config(struct
|
|
pl->major_config_failed = true;
|
|
return;
|
|
}
|
|
+ /* Find a PCS in available PCS list for the requested interface.
|
|
+ * This doesn't overwrite the previous .mac_select_pcs as either
|
|
+ * .mac_select_pcs or PCS list implementation are permitted.
|
|
+ *
|
|
+ * Skip searching if the MAC doesn't require a dedicaed PCS for
|
|
+ * the requested interface.
|
|
+ */
|
|
+ } else if (test_bit(state->interface, pl->config->pcs_interfaces)) {
|
|
+ bool pcs_found = false;
|
|
|
|
- pcs_changed = pl->pcs != pcs;
|
|
+ list_for_each_entry(pcs, &pl->pcs_list, list) {
|
|
+ if (!phylink_validate_pcs_interface(pcs,
|
|
+ state->interface)) {
|
|
+ pcs_found = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!pcs_found) {
|
|
+ phylink_err(pl,
|
|
+ "couldn't find a PCS for %s\n",
|
|
+ phy_modes(state->interface));
|
|
+
|
|
+ pl->major_config_failed = true;
|
|
+ return;
|
|
+ }
|
|
}
|
|
|
|
+ pcs_changed = pl->pcs != pcs;
|
|
+
|
|
phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
|
|
|
|
phylink_dbg(pl, "major config, active %s/%s/%s\n",
|
|
@@ -1291,11 +1366,13 @@ static void phylink_major_config(struct
|
|
if (pcs_changed) {
|
|
phylink_pcs_disable(pl->pcs);
|
|
|
|
- if (pl->pcs)
|
|
- pl->pcs->phylink = NULL;
|
|
+ if (pl->mac_ops->mac_select_pcs) {
|
|
+ if (pl->pcs)
|
|
+ pl->pcs->phylink = NULL;
|
|
|
|
- if (pcs)
|
|
- pcs->phylink = pl;
|
|
+ if (pcs)
|
|
+ pcs->phylink = pl;
|
|
+ }
|
|
|
|
pl->pcs = pcs;
|
|
}
|
|
@@ -1823,6 +1900,44 @@ int phylink_set_fixed_link(struct phylin
|
|
}
|
|
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
|
|
|
|
+static int phylink_fill_available_pcs(struct phylink *pl,
|
|
+ struct phylink_config *config)
|
|
+{
|
|
+ struct phylink_pcs **pcss;
|
|
+ int i, ret;
|
|
+
|
|
+ if (!config->num_available_pcs)
|
|
+ return 0;
|
|
+
|
|
+ if (!config->fill_available_pcs) {
|
|
+ dev_err(config->dev,
|
|
+ "phylink: error: num_available_pcs defined but no fill_available_pcs\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ pcss = kcalloc(config->num_available_pcs, sizeof(*pcss), GFP_KERNEL);
|
|
+ if (!pcss)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ret = config->fill_available_pcs(config, pcss, config->num_available_pcs);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ for (i = 0; i < config->num_available_pcs; i++) {
|
|
+ struct phylink_pcs *pcs = pcss[i];
|
|
+
|
|
+ if (!pcs)
|
|
+ continue;
|
|
+
|
|
+ list_add(&pcs->list, &pl->pcs_list);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ kfree(pcss);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
/**
|
|
* phylink_create() - create a phylink instance
|
|
* @config: a pointer to the target &struct phylink_config
|
|
@@ -1844,6 +1959,7 @@ struct phylink *phylink_create(struct ph
|
|
phy_interface_t iface,
|
|
const struct phylink_mac_ops *mac_ops)
|
|
{
|
|
+ struct phylink_pcs *pcs;
|
|
struct phylink *pl;
|
|
int ret;
|
|
|
|
@@ -1861,9 +1977,21 @@ struct phylink *phylink_create(struct ph
|
|
mutex_init(&pl->phydev_mutex);
|
|
mutex_init(&pl->state_mutex);
|
|
INIT_WORK(&pl->resolve, phylink_resolve);
|
|
+ INIT_LIST_HEAD(&pl->pcs_list);
|
|
+
|
|
+ /* Fill the PCS list with available PCS from phylink config */
|
|
+ ret = phylink_fill_available_pcs(pl, config);
|
|
+ if (ret) {
|
|
+ kfree(pl);
|
|
+ return ERR_PTR(ret);
|
|
+ }
|
|
|
|
phy_interface_copy(pl->supported_interfaces,
|
|
config->supported_interfaces);
|
|
+ list_for_each_entry(pcs, &pl->pcs_list, list)
|
|
+ phy_interface_or(pl->supported_interfaces,
|
|
+ pl->supported_interfaces,
|
|
+ pcs->supported_interfaces);
|
|
|
|
pl->config = config;
|
|
if (config->type == PHYLINK_NETDEV) {
|
|
@@ -1942,10 +2070,16 @@ EXPORT_SYMBOL_GPL(phylink_create);
|
|
*/
|
|
void phylink_destroy(struct phylink *pl)
|
|
{
|
|
+ struct phylink_pcs *pcs, *tmp;
|
|
+
|
|
sfp_bus_del_upstream(pl->sfp_bus);
|
|
if (pl->link_gpio)
|
|
gpiod_put(pl->link_gpio);
|
|
|
|
+ /* Remove every PCS from phylink PCS list */
|
|
+ list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list)
|
|
+ list_del(&pcs->list);
|
|
+
|
|
cancel_work_sync(&pl->resolve);
|
|
kfree(pl);
|
|
}
|
|
@@ -2447,6 +2581,7 @@ static irqreturn_t phylink_link_handler(
|
|
*/
|
|
void phylink_start(struct phylink *pl)
|
|
{
|
|
+ struct phylink_pcs *pcs;
|
|
bool poll = false;
|
|
|
|
ASSERT_RTNL();
|
|
@@ -2473,6 +2608,10 @@ void phylink_start(struct phylink *pl)
|
|
|
|
pl->pcs_state = PCS_STATE_STARTED;
|
|
|
|
+ /* link available PCS to phylink struct */
|
|
+ list_for_each_entry(pcs, &pl->pcs_list, list)
|
|
+ pcs->phylink = pl;
|
|
+
|
|
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
|
|
|
|
if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
|
|
@@ -2517,6 +2656,8 @@ EXPORT_SYMBOL_GPL(phylink_start);
|
|
*/
|
|
void phylink_stop(struct phylink *pl)
|
|
{
|
|
+ struct phylink_pcs *pcs;
|
|
+
|
|
ASSERT_RTNL();
|
|
|
|
if (pl->sfp_bus)
|
|
@@ -2534,6 +2675,14 @@ void phylink_stop(struct phylink *pl)
|
|
pl->pcs_state = PCS_STATE_DOWN;
|
|
|
|
phylink_pcs_disable(pl->pcs);
|
|
+
|
|
+ /* Drop link between phylink and PCS */
|
|
+ list_for_each_entry(pcs, &pl->pcs_list, list)
|
|
+ pcs->phylink = NULL;
|
|
+
|
|
+ /* Restore original supported interfaces */
|
|
+ phy_interface_copy(pl->supported_interfaces,
|
|
+ pl->config->supported_interfaces);
|
|
}
|
|
EXPORT_SYMBOL_GPL(phylink_stop);
|
|
|
|
--- a/include/linux/phylink.h
|
|
+++ b/include/linux/phylink.h
|
|
@@ -12,6 +12,7 @@ struct ethtool_cmd;
|
|
struct fwnode_handle;
|
|
struct net_device;
|
|
struct phylink;
|
|
+struct phylink_pcs;
|
|
|
|
enum {
|
|
MLO_PAUSE_NONE,
|
|
@@ -150,12 +151,18 @@ enum phylink_op_type {
|
|
* if MAC link is at %MLO_AN_FIXED mode.
|
|
* @supported_interfaces: bitmap describing which PHY_INTERFACE_MODE_xxx
|
|
* are supported by the MAC/PCS.
|
|
+ * @pcs_interfaces: bitmap describing for which PHY_INTERFACE_MODE_xxx a
|
|
+ * dedicated PCS is required.
|
|
* @lpi_interfaces: bitmap describing which PHY interface modes can support
|
|
* LPI signalling.
|
|
* @mac_capabilities: MAC pause/speed/duplex capabilities.
|
|
* @lpi_capabilities: MAC speeds which can support LPI signalling
|
|
* @lpi_timer_default: Default EEE LPI timer setting.
|
|
* @eee_enabled_default: If set, EEE will be enabled by phylink at creation time
|
|
+ * @num_available_pcs: num of available phylink_pcs PCS
|
|
+ * @fill_available_pcs: callback to fill the available PCS in the passed
|
|
+ * array struct of phylink_pcs PCS available_pcs up to
|
|
+ * num_available_pcs.
|
|
*/
|
|
struct phylink_config {
|
|
struct device *dev;
|
|
@@ -168,11 +175,17 @@ struct phylink_config {
|
|
void (*get_fixed_state)(struct phylink_config *config,
|
|
struct phylink_link_state *state);
|
|
DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
|
|
+ DECLARE_PHY_INTERFACE_MASK(pcs_interfaces);
|
|
DECLARE_PHY_INTERFACE_MASK(lpi_interfaces);
|
|
unsigned long mac_capabilities;
|
|
unsigned long lpi_capabilities;
|
|
u32 lpi_timer_default;
|
|
bool eee_enabled_default;
|
|
+
|
|
+ unsigned int num_available_pcs;
|
|
+ int (*fill_available_pcs)(struct phylink_config *config,
|
|
+ struct phylink_pcs **available_pcs,
|
|
+ unsigned int num_available_pcs);
|
|
};
|
|
|
|
void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed);
|
|
@@ -468,6 +481,9 @@ struct phylink_pcs {
|
|
struct phylink *phylink;
|
|
bool poll;
|
|
bool rxc_always_on;
|
|
+
|
|
+ /* private: */
|
|
+ struct list_head list;
|
|
};
|
|
|
|
/**
|