From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-ej1-f50.google.com (mail-ej1-f50.google.com [209.85.218.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B0C7048AE29 for ; Tue, 9 Jun 2026 15:13:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.50 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781017995; cv=none; b=lyQ1Zbl1ILTsTAs4bi5+5F5CZpVGY9jr5CFJJZsS441vruq5NnjsxmPswK0C58MuFWZgmb/Qa9+oPaCLUay8LqD/ZDgpiLVt9X5bmmzmxkij5bR9Gc3NQIAOfaDknZnS8OrOVMJ6ZDUj7uVsKia/diaa+ZdNRjsUkb1I5VymdaU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781017995; c=relaxed/simple; bh=F/Bsvgfz+zBfW+DqnER8c5CtlOfN/fNyiTx+sVceXFs=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=G7s4DtAFEH5YZxU7A6Qk05GuS3hrcvHGQ8txWyS/VFi4r4r0bNnK09zrYkt9mzhPvrU7S3YFKU5E+wJb8iAk825jaF4d8FG/MYxbzLVMaoqc8+XqTFbfTqaHQMzoQV+m2nE1fnMKPMfgLTi41p+EceDWjXQxDk1TJBCtOZ69TsY= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ZhA03vG1; arc=none smtp.client-ip=209.85.218.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ZhA03vG1" Received: by mail-ej1-f50.google.com with SMTP id a640c23a62f3a-bebb72b845aso873659566b.3 for ; Tue, 09 Jun 2026 08:13:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781017988; x=1781622788; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=yD+X95TIdYn7ZOgP83VP4rEhgGKsWIZ2S+pKboHBRfE=; b=ZhA03vG18/VWNIy3oz8aMz+eK19OqDG123GRQ2HDyy18kw3daVQTrs96z8kq8G9QIF T18J/1jbR4FjxIWjSltrRIunah+i/a+ClQtw29L2fSKTTQrShdyjJusaNdYzvSqOoA0i PJPda7mmdbnHYcv/Z8WFuhGDFiUXV9PMhbmr4jUb45Vy6nP2YcFMWCuoiJWTHR1AdBWF BG97gKzxDgp0MsUO3+d21WVOmynyDO5FY8cUTGJdHo0KlnEDL0+Rvlj1wnoHCppt+i8K ryLEUfZP7WIyzm004X3yJoeJeo4dsXpGW6ij1AuUIxJUw4jVqXbiyh+wnGvPwm6lY60r iTew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781017988; x=1781622788; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=yD+X95TIdYn7ZOgP83VP4rEhgGKsWIZ2S+pKboHBRfE=; b=pMfGawKvr6Ma97mFte9KIRUJJvYsWqI7Hck+EaVU1TW+/A//TeONEzZzV1kZIwwNcx id9+zb8A/8mi9BZAccaLgVY+AXrGsrpWq/oaP8Z51Y+8w5Jl1fRoOWrMM0NsRdM8nZb1 0yS1brcZgMTahrCZGcUV/Jnf5HZo7xGxDKd57ZKsqa+MQhTFC8REFSRBlz3eInZeK+fd iXhDzOMPCxgLy1jJ4EgXQ51gozj2BRqpEAlVYMNuZxFSt238MvZmwda5URGMujn5pq6f 9J1qcdtTyJJ1mNeXGVX2VNjLkca9Z3etzsoaTt9bOAvgaPDsD5XfyfKSMFEMdANYRX6p IhPQ== X-Forwarded-Encrypted: i=1; AFNElJ9gYbuVQqhTkTQnxIN417bhlgX9dNHILuRLlVQvU7x852eglhFoA3wihmRuVPWVCoJbXNi+NBs=@vger.kernel.org X-Gm-Message-State: AOJu0YwyT2aNHEkxfmKG0x5CGwEU39MMM7+j2u1a4/KTwKwxFcVqcDET B+7TntEGSm5EkyT5uiy4apXTCGI1gJN28+ghVxg9vNcqetluAqaziw9X X-Gm-Gg: Acq92OEfTmjmOuS3wFKTDZJaZuCuzt5VxUFG0fMCnjp1PIeeNOQy3KGc1f9Xz/asPOh fMNuDy2WDqGsDzNa5kUubm2RffDc6S+SjPT+ZaVM8c2jpEVZRyls1zidxZO9/pPT7U6O2RwvUDt Ba/aEd4LqRcS8aHxYyrvgRYXF/y0ODeasPRiSo09NNqljUmVWfcNT8k6oeuwDjwg8bvBhGUrWQF 2TS5Z92sIZ3UATP5orZb1YynkiPrBZYEHRGONkCfsAi1I2TNznjJRT0y4clxbIhG2JGIK/UbEFp jHvb0zOrqW09pI6/fWvdrilcIXPMHNQtDUT7O4AX/Ge3oFCN4WzWgj2ATA5+s2gXzJ58VDgyF8J cbUY/pVM3lWRsJVBHU1MStG95RoDET4U5CPjcwrPyY5+PKtf3HrI2l1+UvOKWsjG7uREwCeZs/L qBCj66rOYEFU6NdiHeJRAV1OtGnPp987z/ X-Received: by 2002:a17:907:980b:b0:bed:8f3f:eb7d with SMTP id a640c23a62f3a-bf3703794bcmr1112927966b.4.1781017987205; Tue, 09 Jun 2026 08:13:07 -0700 (PDT) Received: from Ansuel-XPS24 ([2.195.136.12]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-bf0517721e5sm1073637866b.9.2026.06.09.08.13.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 09 Jun 2026 08:13:06 -0700 (PDT) From: Christian Marangi To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Simon Horman , Jonathan Corbet , Shuah Khan , Christian Marangi , Lorenzo Bianconi , Heiner Kallweit , Russell King , Saravana Kannan , Philipp Zabel , Nathan Chancellor , Nick Desaulniers , Bill Wendling , Justin Stitt , netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-mediatek@lists.infradead.org, llvm@lists.linux.dev Subject: [PATCH net-next v6 02/12] net: phylink: introduce internal phylink PCS handling Date: Tue, 9 Jun 2026 17:11:58 +0200 Message-ID: <20260609151212.29469-3-ansuelsmth@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260609151212.29469-1-ansuelsmth@gmail.com> References: <20260609151212.29469-1-ansuelsmth@gmail.com> Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 number 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 --- drivers/net/phy/phylink.c | 185 ++++++++++++++++++++++++++++++++++---- include/linux/phylink.h | 16 ++++ 2 files changed, 183 insertions(+), 18 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 4d59c0dd78db..4d6ffda0cdd6 100644 --- 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. */ @@ -154,6 +157,8 @@ static const phy_interface_t phylink_sfp_interface_preference[] = { 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 @@ -518,22 +523,59 @@ static void phylink_validate_mask_caps(unsigned long *supported, 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. @@ -545,13 +587,10 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl, 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; } @@ -965,12 +1004,22 @@ static unsigned int phylink_inband_caps(struct phylink *pl, 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); @@ -1265,10 +1314,36 @@ static void phylink_major_config(struct phylink *pl, bool restart, 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; + + list_for_each_entry(pcs, &pl->pcs_list, list) { + if (!phylink_validate_pcs_interface(pcs, + state->interface)) { + pcs_found = true; + break; + } + } - pcs_changed = pl->pcs != pcs; + 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", @@ -1295,11 +1370,13 @@ static void phylink_major_config(struct phylink *pl, bool restart, 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; } @@ -1834,6 +1911,44 @@ int phylink_set_fixed_link(struct phylink *pl, } 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 = kzalloc_objs(*pcss, config->num_available_pcs); + 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 @@ -1855,6 +1970,7 @@ struct phylink *phylink_create(struct phylink_config *config, phy_interface_t iface, const struct phylink_mac_ops *mac_ops) { + struct phylink_pcs *pcs; struct phylink *pl; int ret; @@ -1872,9 +1988,21 @@ struct phylink *phylink_create(struct phylink_config *config, 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) { @@ -1953,10 +2081,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); } @@ -2437,6 +2571,7 @@ static irqreturn_t phylink_link_handler(int irq, void *data) */ void phylink_start(struct phylink *pl) { + struct phylink_pcs *pcs; bool poll = false; ASSERT_RTNL(); @@ -2463,6 +2598,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) { @@ -2507,6 +2646,8 @@ EXPORT_SYMBOL_GPL(phylink_start); */ void phylink_stop(struct phylink *pl) { + struct phylink_pcs *pcs; + ASSERT_RTNL(); if (pl->sfp_bus) @@ -2524,6 +2665,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); diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 2bc0db3d52ac..3387d308c4ad 100644 --- 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, @@ -151,6 +152,8 @@ 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. @@ -160,6 +163,10 @@ enum phylink_op_type { * @wol_phy_legacy: Use Wake-on-Lan with PHY even if phy_can_wakeup() is false * @wol_phy_speed_ctrl: Use phy speed control on suspend/resume * @wol_mac_support: Bitmask of MAC supported %WAKE_* options + * @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; @@ -172,6 +179,7 @@ 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; @@ -182,6 +190,11 @@ struct phylink_config { bool wol_phy_legacy; bool wol_phy_speed_ctrl; u32 wol_mac_support; + + 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); @@ -497,6 +510,9 @@ struct phylink_pcs { struct phylink *phylink; bool poll; bool rxc_always_on; + + /* private: */ + struct list_head list; }; /** -- 2.53.0