From nobody Fri Sep 25 09:20:48 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (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 065B91FECBA for ; Fri, 25 Sep 2026 00:12:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790295145; cv=none; b=dpVXsbKxV05OS8zWbRFGnpcPwXJfZx6SThvb6j1E91K1DN3guKLe94H+Qzcc8hJqZFSGxpaiWSkXsbRW2pYXm58+SCIBxQVZWs+WrEElQH7aWKjooxpXrYiQHi5KItCEcZlXZ/dTOzz6abuKCugT1qOmkLHd9+f3vHt+7dAwEh4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790295145; c=relaxed/simple; bh=MO4DCzOiJvahncAwtOSB6K819MkQDQH4Q0Oaw+W51tY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=LLl6hVO3ArXrKZ4d2qH9+3AaZ7Wy95mSUfkLPYQ3Uk9PdTjMlU9Nrp0IA/3it4IJA7aYVTNYbDzh/6llQ49l/oldjn663n3KeldQC2Af2RQ+Wycvms3nNWxVBtBU9vHoZvHNp0figXl2Oopn2zP7U/d3Wxx4qdlqh2msEvf6lu0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=MhTl4yyV; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="MhTl4yyV" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-4980fe6b3beso8573585e9.0 for ; Thu, 24 Sep 2026 17:12:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1790295138; x=1790899938; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=ekkpP5b06Snvbo4ruQYW5bvGILgj8Lx5k8thlgPN0w8=; b=MhTl4yyVl5ha7wKP47uPI7ljVaoa7qGi/LbXWE1L1mcNCAEuWVqn4IFjP25b49wGYO epGOQtJYcYRM3womKWf8OmkScAWCzkb0oygVXpgXOnbrENuBUuW1uWf2DF81jLZpfNmw mc7mYdx5z3KRBtAhcUNbPQKVFdQKVgi4fNuLLlh7WRxSQiou5C/eYbwSgEw/LLH1hrYU WT1ZW82d3/fo3oNvz+bTr0dUAi5meTAPXCict2Hdi0NbKAMzsOH/Kj/omxUiGt58Xsnb m/wTNZCLxLJnDbbJHPq3iI7ssJdpdmzOx6FW/a+ma5RpevlbmIL/2Ee19DxbwzewF8M7 UOVA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790295138; x=1790899938; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=ekkpP5b06Snvbo4ruQYW5bvGILgj8Lx5k8thlgPN0w8=; b=yBsB2DplXh6Jq+FA9Qm/NBqlRWD+dWO6hZTaEhafUrJP0WB2HBRG6KRLz7uMLi0Qi2 js0wJ7Fq78fBDmhbBfMN6RhTOmJZSHcRlYWc0ENdHSRnEsaIopaaXGWnr9nMHOr/l8+5 5SdOT7gSYIVCthgvKh4AFjqDS0uN1Is6HA12r1eDWxEyDnWls50162EaJj7scrIqZg0V rP5QRW7MC9IufyHKngGObDZ/NBgfw6c5lrl7iGEW27eJbWV8cRf0EMDFEDNEAA5vKpka pO9YF0afGl6jAMWe27icOx1mVLn2h87SfqIZbmEISNRr69QegnSw9SDFTf3J2ZhY2qXi JvDw== X-Forwarded-Encrypted: i=1; AKwUvBxg9ou874JYUPpR1sTEzKorV2rzDCm3yKk/541AzOn0eynFQ7Zv+CISO3Wlw0FHq6GYcp5c/nmmzKCS+Bs=@vger.kernel.org X-Gm-Message-State: AFuF++kwkbM48pGIVI1V7jmaSgRlTcyYnuIHYZ0+UdbIBgIli5JK/cbP /siNzPzSn8xQUhjlhG7OYrQ7VRbZDlaELBUu+5mJeMjU0Em1KN0L8TLBK9B5dnt4vxU= X-Gm-Gg: AYBFou3TWIQUOJNw0gfVjzaCb6YCH0p0qRn6pxomWoohGEk/x5FE6WtLpNERn6OLTmq aOBGg402aQjmalMkRjpEe90sM4yShxRl+PYpaTtBTBqxO1GURq4I43aUSSyCOXbbUpJraKC/rR6 XhKk181oVkX+w6RJVG0rdy0U1qbtkR/q+ecJOkpqaI9pbCjwfxvD0kiFymZhXVmZjkV7H3pJRtQ ZQWu6/nTfDNRcY+SBmxltGB6bN74BsZrLNt2prJkKk6Ey6BpdwiGKICFxhD84oQOsPft2vWWrJ6 yK6xQ7l5VvM6YtBTbO8a9uqQuTKiECt0gjE7qTRWZXW2rzMx21bjX8BY4U5CnaHYsv3UmVbemQ6 owg0R1jzDiavyzv6k39YA26tvNxS2uGE+2Na0N2UTL77YLeDnCDsYDJVYd4VPNYGlZo12g1OWs4 DxrhGFOI/ea9R7DxbsU0nFwPf/efW4y0ZgApOsHJRq6y8x2tYfEFaRYmUcJ+Jx X-Received: by 2002:a05:600c:2254:b0:49c:f13d:ffed with SMTP id 5b1f17b1804b1-49fe845cbeemr50823335e9.17.1790295137252; Thu, 24 Sep 2026 17:12:17 -0700 (PDT) Received: from remote-01 ([84.17.55.134]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4887a30b9aesm2331621f8f.5.2026.09.24.17.12.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Sep 2026 17:12:16 -0700 (PDT) From: Aleksei Sviridkin To: netdev@vger.kernel.org Cc: Russell King , Andrew Lunn , Andrew Lunn , Heiner Kallweit , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Conor Dooley , Florian Fainelli , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Date: Fri, 25 Sep 2026 03:12:08 +0300 Message-ID: <20260925001209.2334139-2-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260925001209.2334139-1-f@lex.la> References: <20260925001209.2334139-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A PHY can be one that the host has to load firmware into before it can be driven at all. A controller that connects to such a PHY at setup, before its driver has loaded, gets the generic driver or no PHY at all, and one that connects only once gets no working PHY on that port for the rest of the uptime, even though the PHY works seconds later. The flag declares that. A consumer that sees it keeps the port and connects the PHY once its driver binds. It describes the PHY, so it sits on the PHY node and needs no prefix naming one. firmware-name is not used for this: it names the file to load, and the EN8811H driver keeps its two blob names in code, so it would only be read as a presence flag. The need is not derived from the compatible because the knowledge that an ID needs host firmware lives in the PHY driver, and that driver is a module not yet loaded when the MAC connects, so it has to come from the device tree. Found on a Keenetic KN-1012, where the EN8811H behind lan4 has its driver on the root filesystem and the switch sets its ports up before that is mounted, so lan4 stayed dead for the uptime. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- Documentation/devicetree/bindings/net/ethernet-phy.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/net/ethernet-phy.yaml b/Docu= mentation/devicetree/bindings/net/ethernet-phy.yaml index c3ebb3af8b52..73827662ddce 100644 --- a/Documentation/devicetree/bindings/net/ethernet-phy.yaml +++ b/Documentation/devicetree/bindings/net/ethernet-phy.yaml @@ -215,6 +215,12 @@ properties: used. The absence of this property indicates the muxers should be configured so that the external PHY is used. =20 + needs-host-firmware: + $ref: /schemas/types.yaml#/definitions/flag + description: + This PHY runs firmware that the host must load before it can be + driven, and is not usable until then. + resets: maxItems: 1 =20 --=20 2.53.0 From nobody Fri Sep 25 09:20:48 2026 Received: from mail-wr2-f12.google.com (mail-wr2-f12.google.com [74.125.225.76]) (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 C0F4618E025 for ; Fri, 25 Sep 2026 00:12:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.76 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790295146; cv=none; b=Y074gdjgwZEAvuZzLXc1cALgThPz0uszX/yTlCxAHzXQHwzLloan+lFaBhTFhXuTkf2NY2DmPcGGLmuX9X1NfiQw6AcU+rw+XOuFpj25+oV0v6keDhrOVeibeuL9RanFaZ0ghImIblaZ7q1LCnQ7O5gUyGreb0hAL2ImgvF5uTU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790295146; c=relaxed/simple; bh=EsggK23lqfuqzU6JUlKfFVIqlo6ZH9gRODmMrkvpYL4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dcnY+lYHXNfNVpa/3clF2DmH0+XD/0c9xY5NoQXiGgjNF57o1I44e93B0LsHA+O6gW6qttW5oJ7/DvR3h0rn2xhh8nG3d5uhblMLU1UWcll6K4Og56yt5JSzrcMZbYawhLdmVi5N0JxtIWYloC2gXDAaPuo0Kc3VW0f4pQjUZdg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=YswlRs1y; arc=none smtp.client-ip=74.125.225.76 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="YswlRs1y" Received: by mail-wr2-f12.google.com with SMTP id ffacd0b85a97d-485933b2522so267290f8f.0 for ; Thu, 24 Sep 2026 17:12:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1790295139; x=1790899939; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=VYMJ8VthwiH6f38r1drtQkDeJrS04p8cVgXl/nXAnZg=; b=YswlRs1yLKISOLkl7afwzewz9IYhU/GyBGh05lKrPx+PyleE2arXpqZcB4d3X9oeny m9AGdntJTWiRLaV4akUjNy+lBMLLmWcqlkBb+pQCeyD2YDBBog3EX80y7HEmT/vmyFn7 1e1GigHXLpWc67gbn5eG0rBfBGXYYdj1jHRHAxSkNvVvKGe+NXYQgUdHiGqgNRG6sYrj WFgRB2gVaAyFcm6eswxPGTzyJ81O1Lj+ICsKbR96JgRN8bfHWnwMxA3lDTcWPgHpmWfp gWWdytJLGCEhhRWl4szcxwiAdTpWCGzFmWeeMhwBw98INM9YUbXhvoyVOYwCvXUekU/E SEjw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790295139; x=1790899939; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=VYMJ8VthwiH6f38r1drtQkDeJrS04p8cVgXl/nXAnZg=; b=E48chHcB7beZKRHqYCVx1gXAPTLGoSCK1scLtRZUa1qWhY0pLYdKDTvbef8YGvwxPu mWP1kLqvs75iTX3Um5DdYAhPVFNSxUSy/LBZbx39NvCDHX4dA5bIa1UZhyg9a33ABXRy YVNLvw5UJ+nthbJTeHFH6nzT8BRtaD4Wx/hL/twhL2WzPRj0G4tu3Rnn1K89VGRw/Rh/ 2FrjRr8UmSgO66BmzxNa+lmswO7WD9QbqjpiiyPU80ynzqAfATn8DWzjLMlYeTR18bmH vFTxuqSeOIbYhyfKYhEYx+8/Dc+BS6PEJNFPfm6VE8q7X4GYIavUWUkYCrxlyvJ/lFcD 4skQ== X-Forwarded-Encrypted: i=1; AKwUvBxg44tMLY+OIcsP5K7RIdcz//0Yfqxc4oTzgVQni58u/z/ZO3yXICdFYJL7txqjAqsVQxMlEOiBT1unWIE=@vger.kernel.org X-Gm-Message-State: AFuF++mOel1fHAY2AV+QBLa7fU93AacuX9JWErVYqdj/339riIxEDk3Q NNInx4Zr9ZVlsnEcBih3Kg38FnoEx+Lqq6a0p42T+v2YNLSLJwAFeQbIvSRT00BXFtk= X-Gm-Gg: AYBFou1PP5RYNtli0FD0xxdExN6UY1iP/BDg58mOpf5J3apXgjdxzZbvxBoiyFJI6z5 BP7Il58zzu4LAmrbGnFjmY5Yepw7wI337R+LD9vXPX+CBeIUphtd2K2x9sHzs7QLmjR9hTWJw/M MP0M1suLdKfbyVtPvrFwgPvWkYJlUjyl1TP1LETV3lS8Bd1IlV4+DA66x+uLD8UnMt+qIUMYwEe DFkIeLVpFB31JGPozBG4hwpL9cTH5nz7ub/Ep8aecxXLxk7xvC6LCntD76quUkkJnHbdltzjKEF nMDgrUu4T2qj7HhW0b978ez0dmlc3lG+8Rgav0ifFFyuiqiIjw3Xf+0SbRWSWHPnoqDbJf88rQA VtaHP8p1iBh6+rBtYdw5Wug5EA9pBhTaWYfD/wwO/DXkxb+upsQgtVqsiAJwgZwV4ktFEIWMzZ5 PIShI4j0eI4fYlKKk2KClVfaVCMFLrOEf0odV4ENZpiu1sVQW/wgklAe7ue1tR X-Received: by 2002:a05:6000:2908:b0:487:27f9:828 with SMTP id ffacd0b85a97d-488716f76acmr6884154f8f.29.1790295138975; Thu, 24 Sep 2026 17:12:18 -0700 (PDT) Received: from remote-01 ([84.17.55.134]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4887a30b9aesm2331621f8f.5.2026.09.24.17.12.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Sep 2026 17:12:18 -0700 (PDT) From: Aleksei Sviridkin To: netdev@vger.kernel.org Cc: Russell King , Andrew Lunn , Andrew Lunn , Heiner Kallweit , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Conor Dooley , Florian Fainelli , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH net-next v4 2/2] net: phylink: wait for PHYs that are known to probe late Date: Fri, 25 Sep 2026 03:12:09 +0300 Message-ID: <20260925001209.2334139-3-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260925001209.2334139-1-f@lex.la> References: <20260925001209.2334139-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A PHY that needs firmware from the host and whose driver has not bound when the MAC sets up its port is either taken by the generic driver, which cannot drive it, or not found at all; a MAC that connects once at setup, as DSA does, gets no working PHY on that port for the rest of the uptime. The case this reaches is a driver built as a module on a filesystem that is mounted after the MAC probes. Let the PHY declare it with needs-host-firmware and poll until the driver binds instead of failing. A driver that has bound is not covered, whatever it does about firmware afterwards. Neither is one whose probe has already failed: the driver core does not retry it, and the poller cannot tell that apart from a driver that has yet to load, so it keeps polling. Deferring the MAC's own probe is not an option: it keeps every port of that MAC down until the module loads, and forever if it never does, and those ports can include the one needed to mount the filesystem that holds the module. Return 0 rather than -ENODEV, because DSA reads -ENODEV as permission to look for the PHY on the switch's internal MDIO bus, which is the wrong device. Wait for a driver that has bound, not for a device that exists, because the generic driver would otherwise bind and cannot drive such a PHY. If the real driver goes away between that test and the attach, the generic one binds instead; the poll detaches it and keeps waiting. The attach-versus-unbind window itself is phylib's to close and is not closed here. A connect that fails with the real driver bound is retried a few times and then given up on with one line, because silence from a poller reads like success. Each retry re-runs the PHY's init and, on boards whose DT gives it a reset line, pulses that reset, at a cost that depends on the board and the PHY, so the retries are bounded. Stopping after the first failure would leave a DSA port, which connects once, dead until the switch driver is rebound. Until a PHY attaches, report no link modes and refuse the ethtool settings that would configure the MAC alone for a link that cannot come up. Found on a Keenetic KN-1012 (MT7981B with an MT7531 switch): the EN8811H behind lan4 has its driver on the root filesystem, the switch sets its ports up before that is mounted, and lan4 was lost for the uptime. With this change lan4 attaches once the module loads. The retry path was driven there by a local debug parameter that fails the connect after a successful attach: two injected failures were retried a second apart and the third attempt attached, and with failures that never stop, four attempts ended in one "giving up" line and no further polls. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- drivers/net/phy/phylink.c | 220 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 213 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a7d086cdc9b2..4a5b98b55889 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -98,6 +98,15 @@ struct phylink { =20 u32 wolopts_mac; u8 wol_sopass[SOPASS_MAX]; + + /* The poller writes these while it runs; arming cancels it first. */ + struct fwnode_handle *late_phy_fwnode; + u32 late_phy_flags; + struct delayed_work late_phy_poll; + unsigned int late_phy_poll_ms; + unsigned int late_phy_waited_ms; + u8 late_phy_retries; + bool late_phy_warned; }; =20 #define phylink_printk(level, pl, fmt, ...) \ @@ -1831,6 +1840,18 @@ int phylink_set_fixed_link(struct phylink *pl, } EXPORT_SYMBOL_GPL(phylink_set_fixed_link); =20 +static void phylink_late_phy_poll(struct work_struct *work); + +/* Synchronous: the poller reads the node put here. It only trylocks + * rtnl, so a caller holding rtnl cannot deadlock on it. + */ +static void phylink_late_phy_cancel(struct phylink *pl) +{ + cancel_delayed_work_sync(&pl->late_phy_poll); + fwnode_handle_put(pl->late_phy_fwnode); + pl->late_phy_fwnode =3D NULL; +} + /** * phylink_update_pause_state() - Update the phylink pause frame configura= tion * @pl: a pointer to a &struct phylink instance @@ -1989,6 +2010,7 @@ 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_DELAYED_WORK(&pl->late_phy_poll, phylink_late_phy_poll); =20 pl->config =3D config; if (config->type =3D=3D PHYLINK_NETDEV) { @@ -2068,6 +2090,8 @@ EXPORT_SYMBOL_GPL(phylink_create); */ void phylink_destroy(struct phylink *pl) { + phylink_late_phy_cancel(pl); + sfp_bus_del_upstream(pl->sfp_bus); if (pl->link_gpio) gpiod_put(pl->link_gpio); @@ -2337,10 +2361,8 @@ static int phylink_bringup_phy(struct phylink *pl, s= truct phy_device *phy, } =20 static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, - phy_interface_t interface) + phy_interface_t interface, u32 flags) { - u32 flags =3D 0; - if (WARN_ON(pl->cfg_link_an_mode =3D=3D MLO_AN_FIXED)) return -EINVAL; =20 @@ -2378,7 +2400,7 @@ int phylink_connect_phy(struct phylink *pl, struct ph= y_device *phy) pl->link_config.interface =3D pl->link_interface; } =20 - ret =3D phylink_attach_phy(pl, phy, pl->link_interface); + ret =3D phylink_attach_phy(pl, phy, pl->link_interface, 0); if (ret < 0) return ret; =20 @@ -2390,6 +2412,143 @@ int phylink_connect_phy(struct phylink *pl, struct = phy_device *phy) } EXPORT_SYMBOL_GPL(phylink_connect_phy); =20 +#define PHYLINK_LATE_PHY_POLL_MS 1000 +#define PHYLINK_LATE_PHY_WARN_MS 60000 +#define PHYLINK_LATE_PHY_POLL_MAX_MS 30000 +#define PHYLINK_LATE_PHY_RETRIES 3 + +static bool phylink_late_phy_pending(struct phylink *pl) +{ + return pl->late_phy_fwnode && !pl->phydev; +} + +/* Stale the moment it returns: the device lock this wants cannot be held + * across the attach, whose own failure path takes it again. + */ +static bool phylink_phy_is_usable(struct phy_device *phy_dev) +{ + return phy_dev && device_is_bound(&phy_dev->mdio.dev) && phy_dev->drv; +} + +static void phylink_late_phy_backoff(struct phylink *pl) +{ + pl->late_phy_poll_ms =3D min_t(unsigned int, pl->late_phy_poll_ms * 2, + PHYLINK_LATE_PHY_POLL_MAX_MS); +} + +static void phylink_late_phy_poll(struct work_struct *work) +{ + struct phylink *pl =3D container_of(to_delayed_work(work), struct phylink, + late_phy_poll); + struct phy_device *phy_dev; + bool again =3D false, lost_race =3D false; + int ret; + + if (!rtnl_trylock()) { + pl->late_phy_waited_ms +=3D pl->late_phy_poll_ms; + goto requeue; + } + + /* A PHY arrived by another path, an SFP for one, while queued. */ + if (!phylink_late_phy_pending(pl)) { + rtnl_unlock(); + return; + } + + /* Stable here: whoever clears it waits for this work first. */ + phy_dev =3D fwnode_phy_find_device(pl->late_phy_fwnode); + if (!phylink_phy_is_usable(phy_dev)) { + if (phy_dev) + phy_device_free(phy_dev); + + if (!pl->late_phy_warned && + pl->late_phy_waited_ms >=3D PHYLINK_LATE_PHY_WARN_MS) { + pl->late_phy_warned =3D true; + phylink_warn(pl, + "still waiting for %pfw (needs-host-firmware)\n", + pl->late_phy_fwnode); + } + /* Past the warn it may never come: stop paying 1 Hz for it. */ + if (pl->late_phy_waited_ms >=3D PHYLINK_LATE_PHY_WARN_MS) + phylink_late_phy_backoff(pl); + /* The first run is immediate, so count the sleep ahead. */ + pl->late_phy_waited_ms +=3D pl->late_phy_poll_ms; + rtnl_unlock(); + goto requeue; + } + + /* Under the mutex, unlike at connect: this port may be live. */ + if (pl->link_interface =3D=3D PHY_INTERFACE_MODE_NA) { + mutex_lock(&pl->state_mutex); + pl->link_interface =3D phy_dev->interface; + pl->link_config.interface =3D pl->link_interface; + mutex_unlock(&pl->state_mutex); + } + + ret =3D phylink_attach_phy(pl, phy_dev, pl->link_interface, + pl->late_phy_flags); + if (!ret && phy_driver_is_genphy(phy_dev)) { + /* Lost the race: the attach bound the generic driver, which + * is the outcome this poller exists to avoid. + */ + phy_detach(phy_dev); + lost_race =3D true; + ret =3D -EAGAIN; + } + if (!ret) { + ret =3D phylink_bringup_phy(pl, phy_dev, + pl->link_config.interface); + if (ret) { + phy_detach(phy_dev); + } else { + /* Only a major config programs the masks bringup + * narrowed. + */ + if (!test_bit(PHYLINK_DISABLE_STOPPED, + &pl->phylink_disable_state)) { + mutex_lock(&pl->state_mutex); + pl->force_major_config =3D true; + mutex_unlock(&pl->state_mutex); + /* MAC before the PHY, the order a start + * uses. + */ + phylink_run_resolve(pl); + flush_work(&pl->resolve); + phy_start(phy_dev); + } + } + } + if (lost_race) { + /* Not a failed connect: the next poll waits for the real + * driver. + */ + again =3D true; + } else if (ret) { + phylink_err(pl, "failed to connect late PHY: %pe\n", + ERR_PTR(ret)); + /* Bounded: each retry re-runs the PHY's init, maybe its reset. */ + if (pl->late_phy_retries) { + pl->late_phy_retries--; + again =3D true; + } else { + /* Silence from here reads as success otherwise. */ + phylink_err(pl, "giving up on %pfw after %u attempts\n", + pl->late_phy_fwnode, + PHYLINK_LATE_PHY_RETRIES + 1); + } + } + phy_device_free(phy_dev); + rtnl_unlock(); + + if (!again) + return; + +requeue: + queue_delayed_work(system_freezable_power_efficient_wq, + &pl->late_phy_poll, + msecs_to_jiffies(pl->late_phy_poll_ms)); +} + /** * phylink_of_phy_connect() - connect the PHY specified in the DT mode. * @pl: a pointer to a &struct phylink returned from phylink_create() @@ -2400,7 +2559,8 @@ EXPORT_SYMBOL_GPL(phylink_connect_phy); * specified by @pl. Actions specified in phylink_connect_phy() will be * performed. * - * Returns 0 on success or a negative errno. + * Returns what phylink_fwnode_phy_connect() returns, including 0 for a + * deferred connect with no PHY attached yet. */ int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn, u32 flags) @@ -2418,7 +2578,13 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect); * Connect the phy specified @fwnode to the phylink instance specified * by @pl. * - * Returns 0 on success or a negative errno. + * If the PHY node carries the needs-host-firmware property and the + * PHY is not usable yet, 0 is returned with no PHY connected: a poller + * connects it once its driver has probed. Until then the MAC runs + * without a PHY and ethtool reports no link modes. + * + * Returns 0 on success - the PHY connected, or the deferred connect + * armed - or a negative errno. */ int phylink_fwnode_phy_connect(struct phylink *pl, const struct fwnode_handle *fwnode, @@ -2428,6 +2594,8 @@ int phylink_fwnode_phy_connect(struct phylink *pl, struct phy_device *phy_dev; int ret; =20 + phylink_late_phy_cancel(pl); + if (!phylink_expects_phy(pl)) return 0; =20 @@ -2440,6 +2608,23 @@ int phylink_fwnode_phy_connect(struct phylink *pl, } =20 phy_dev =3D fwnode_phy_find_device(phy_fwnode); + if (fwnode_property_present(phy_fwnode, "needs-host-firmware") && + !phylink_phy_is_usable(phy_dev)) { + /* -ENODEV here would also send DSA to the switch's own bus. */ + if (phy_dev) + phy_device_free(phy_dev); + + pl->late_phy_fwnode =3D phy_fwnode; + pl->late_phy_flags =3D flags; + pl->late_phy_poll_ms =3D PHYLINK_LATE_PHY_POLL_MS; + pl->late_phy_waited_ms =3D 0; + pl->late_phy_retries =3D PHYLINK_LATE_PHY_RETRIES; + pl->late_phy_warned =3D false; + queue_delayed_work(system_freezable_power_efficient_wq, + &pl->late_phy_poll, 0); + return 0; + } + /* We're done with the phy_node handle */ fwnode_handle_put(phy_fwnode); if (!phy_dev) @@ -2481,6 +2666,8 @@ void phylink_disconnect_phy(struct phylink *pl) =20 ASSERT_RTNL(); =20 + phylink_late_phy_cancel(pl); + mutex_lock(&pl->phydev_mutex); phy =3D pl->phydev; if (phy) { @@ -3047,6 +3234,14 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, =20 ASSERT_RTNL(); =20 + /* No PHY yet: the port supports nothing, not what the MAC alone can. */ + if (phylink_late_phy_pending(pl)) { + kset->base.port =3D pl->link_port; + kset->base.speed =3D SPEED_UNKNOWN; + kset->base.duplex =3D DUPLEX_UNKNOWN; + return 0; + } + if (pl->phydev) phy_ethtool_ksettings_get(pl->phydev, kset); else @@ -3119,6 +3314,10 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, =20 ASSERT_RTNL(); =20 + /* Would configure the MAC alone, for a link that cannot come up. */ + if (phylink_late_phy_pending(pl)) + return -EOPNOTSUPP; + if (pl->phydev) { struct ethtool_link_ksettings phy_kset =3D *kset; =20 @@ -3292,6 +3491,9 @@ int phylink_ethtool_nway_reset(struct phylink *pl) =20 ASSERT_RTNL(); =20 + if (phylink_late_phy_pending(pl)) + return -EOPNOTSUPP; + if (pl->phydev) ret =3D phy_restart_aneg(pl->phydev); phylink_pcs_an_restart(pl); @@ -3331,6 +3533,10 @@ int phylink_ethtool_set_pauseparam(struct phylink *p= l, if (pl->req_link_an_mode =3D=3D MLO_AN_FIXED) return -EOPNOTSUPP; =20 + /* pl->supported still describes the MAC, so the test below passes. */ + if (phylink_late_phy_pending(pl)) + return -EOPNOTSUPP; + if (!phylink_test(pl->supported, Pause) && !phylink_test(pl->supported, Asym_Pause)) return -EOPNOTSUPP; @@ -3817,7 +4023,7 @@ static int phylink_sfp_config_phy(struct phylink *pl,= struct phy_device *phy) /* Attach the PHY so that the PHY is present when we do the major * configuration step. */ - ret =3D phylink_attach_phy(pl, phy, config.interface); + ret =3D phylink_attach_phy(pl, phy, config.interface, 0); if (ret < 0) return ret; =20 --=20 2.53.0