From nobody Mon Aug 24 23:46:15 2026 Received: from mail-oa1-f44.google.com (mail-oa1-f44.google.com [209.85.160.44]) (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 B5F7B3CC7EC for ; Wed, 5 Aug 2026 17:49:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952166; cv=none; b=lpCt2FdAxi5zuoMZQyUM0WcPKsGFNeqV9hnM/6tV3hX2mbyGtGrxhbGhCz8xxi2Zlsj1PSq9gv1a2YKvEctiq7EpvcmKzH2EvuAYEoql/ynRbM+HKPnORr0sOLotoLSxAZ+jgPzbZgBb5Euo1iXm3trvmcrC8oQFfjwdeOMH10U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952166; c=relaxed/simple; bh=X5btDcfQTdSHcpvlsmHeLvFsZZ7Fot+driL4QSWR/S8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=WE1LKEA2gI4eZ7Ujy/lN7rmg0GYPKxFpGc3cHwhLl7b8Zu9k61dSluE9uuEBcdelu43wMPSP5LtWYSdBc9yRxE1t7ptuhHc28MnzkukazOuv8vsQ6/ympVHVVyls0TqhwYA5XCac13bhSfNc13gjA45EPHt07pnHodHuy+zKtys= 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=Y+acojAo; arc=none smtp.client-ip=209.85.160.44 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="Y+acojAo" Received: by mail-oa1-f44.google.com with SMTP id 586e51a60fabf-44cedfaab6bso675671fac.3 for ; Wed, 05 Aug 2026 10:49:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785952164; x=1786556964; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=RNdkQ7aeXS5MjicAGAJeJZ2Ggh2c8nR9nvPFAS2N1c8=; b=Y+acojAor+hsyzhnx5PUipQaOk8Spjy42Z38oRQxRawJ+d3a89RdkO3H6WOBA/dHmx S9W9WTqz2qx/6gYNsFq2BrHImbpKFybzE9r4WPbtG2UWDtK6337jnxWdRsDXOLz0/iC2 SPtKuym/NDpLZ3SzkpAWdWESZP1UCjNxu6cXaaj5teYsPyrwxdK0g7Vr9tVQJG2oUuX1 quHsluiVvS5V4wapN1c1YOrua7wic4IHthZX+CeEVjTOCzJzM8ZOqG8qDDoV3ebUOBUN xkxXnAG1YRS7DN11hSkraNrJsUMxPZp0us/L2RbCfdLh8YuHEQh/Bw3iJWzVEi+KucNO SVdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785952164; x=1786556964; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=RNdkQ7aeXS5MjicAGAJeJZ2Ggh2c8nR9nvPFAS2N1c8=; b=Dh7ilAou2SZdEu+thYhkAeBkmx4t+TVOIoeBuNw6PRifCkNIP7ut8hSH+nmWfWrLEY 01GanAU75t9K1z+0BC2xZSvdkgqEAgjtePjRiOCdvesihNx0nTjr21+eGfPISRjyZOKB j4o1DA+Tx3R2SrP49rFy+zT9mQourBk2sm4HpfPVIaHlKMBt10WCuS04sNmSTRAwzQBA iHsJnexOQr427ml3JcIpcVYaq+NlrmS6TwGjolBU7i0KGH6K2cNC6xDKNW4Luut8qxJs EVAFz4Q9wGpECRIt8VvOTE6FwJ92trnQNJR5EMkS8dVjXdclfzj8iC1QQsYcW8DAQ+80 3IXQ== X-Forwarded-Encrypted: i=1; AHgh+RoWeKpl5zR8j1IdyRYwYTtOUsl36HASAl/he8DHDhFznfLB7P3h1qXqTXdaidQ9h/lK5Qh+RZeQISTppX0=@vger.kernel.org X-Gm-Message-State: AOJu0YzSV7XaLtL6xA3W37tMXOmPeXuoFQkjjLtRmbBduWBaUFotQFb9 V3s1I/E1F+BhGkGcNtLWy91F5dBkN86/RVueri4/gT620Wv+pGgUwIMm X-Gm-Gg: AR+sD1390udLMRxWbPY/7E0++UOe5JhHMH91tK6j0fxt6w3YN/lnCgApqQXEGFXKJ1x LETqZXR/dy7/ToUg681MvRNwqXfsrmdwu/l4s+l/jnX0fRCTCUWxuE6Vw/EBqqplTyLC26LQe/k GVbb4oCvpX/PN80DmXpvf0LfonrXeGKM6jFYLONXz0ogWrzRF5tj5l1zt63woeqJZ7dnYuTeZLb tsdn4N7cb/BPtIifzbmCyfLzQJFhuMj/geilNn7in+deku8nyJ9B47xeWJn6zaX1qagV3ujqiNJ p6Jt5SwHWgUaASBpFOgYdOAx2bab231e0I+ZX/aXo3kesI8FgyuSni6xM6/PhTCnDWZ+lwCFpGK rIVsSjr61XvzSUeHiipVA7AxyyLmpsWP5KCirWLOTKcTWN6NX1iay8kz67CbXTPxXII8Cd3AcvW WXv3VpIZTm2A7V77H7sOuezfhAWavc+UdTSoWtJFad3husBNgy1BcLFVOZjA2lnx0i5ORFWhvjs vF0tEzovlFY0uRXBh8Utf1reCER2VwRoST5nSNnUBjsPmsKCDVcFfdOQO3kWOmBE5q39jYhNArR ri7z6PxqRqCY6DCggqSbFJ6eGCynbVc3OYn1hilqRsxUnzWrJCSgQqJ2DuGAOwfzCiB8WmWCukq 2/+4DYy/E43mBRDx7 X-Received: by 2002:a05:6870:a40c:b0:456:3973:5a89 with SMTP id 586e51a60fabf-4599eda5c51mr4077499fac.8.1785952163618; Wed, 05 Aug 2026 10:49:23 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-4599dd615ecsm3281704fac.0.2026.08.05.10.49.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 05 Aug 2026 10:49:22 -0700 (PDT) From: James Hilliard Date: Wed, 05 Aug 2026 11:49:12 -0600 Subject: [PATCH net-next v3 1/4] net: phy: cache MDIO bus owner before dropping PHY reference Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260805-submit-mdio-of-dynamic-v2-v3-1-16f3cc893b0e@gmail.com> References: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> In-Reply-To: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> To: Andrew Lunn , Heiner Kallweit , Russell King , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Florian Fainelli , Richard Cochran , Rob Herring , Saravana Kannan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 phy_attach_direct() and phy_detach() cache the MDIO bus pointer, drop their PHY device reference, and then read bus->owner. If the put releases the PHY during concurrent teardown, the cached pointer does not itself keep the parent bus alive. Cache the module pointer before dropping the PHY reference. The existing module reference protects the cached pointer whenever the MDIO bus and network device have different owners. Fixes: ec988ad78ed6 ("phy: Don't increment MDIO bus refcount unless it's a = different owner") Signed-off-by: James Hilliard --- drivers/net/phy/phy_device.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 94b2e85e00a3..d65f28dd5773 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1752,7 +1752,7 @@ static bool phy_drv_supports_irq(const struct phy_dri= ver *phydrv) int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, u32 flags, phy_interface_t interface) { - struct mii_bus *bus =3D phydev->mdio.bus; + struct module *bus_owner =3D phydev->mdio.bus->owner; struct device *d =3D &phydev->mdio.dev; struct module *ndev_owner =3D NULL; int err; @@ -1764,7 +1764,7 @@ int phy_attach_direct(struct net_device *dev, struct = phy_device *phydev, */ if (dev) ndev_owner =3D dev->dev.parent->driver->owner; - if (ndev_owner !=3D bus->owner && !try_module_get(bus->owner)) { + if (ndev_owner !=3D bus_owner && !try_module_get(bus_owner)) { phydev_err(phydev, "failed to get the bus module\n"); return -EIO; } @@ -1900,8 +1900,8 @@ int phy_attach_direct(struct net_device *dev, struct = phy_device *phydev, d->driver =3D NULL; error_put_device: put_device(d); - if (ndev_owner !=3D bus->owner) - module_put(bus->owner); + if (ndev_owner !=3D bus_owner) + module_put(bus_owner); return err; } EXPORT_SYMBOL(phy_attach_direct); @@ -1917,6 +1917,7 @@ void phy_detach(struct phy_device *phydev) { struct net_device *dev =3D phydev->attached_dev; struct module *ndev_owner =3D NULL; + struct module *bus_owner; struct mii_bus *bus; =20 if (phydev->devlink) { @@ -1972,17 +1973,15 @@ void phy_detach(struct phy_device *phydev) /* Assert the reset signal */ phy_device_reset(phydev, 1); =20 - /* - * The phydev might go away on the put_device() below, so avoid - * a use-after-free bug by reading the underlying bus first. - */ + /* The PHY and its parent bus may be released by put_device() below. */ bus =3D phydev->mdio.bus; + bus_owner =3D bus->owner; =20 put_device(&phydev->mdio.dev); if (dev) ndev_owner =3D dev->dev.parent->driver->owner; - if (ndev_owner !=3D bus->owner) - module_put(bus->owner); + if (ndev_owner !=3D bus_owner) + module_put(bus_owner); } EXPORT_SYMBOL(phy_detach); =20 --=20 2.53.0 From nobody Mon Aug 24 23:46:15 2026 Received: from mail-oa1-f46.google.com (mail-oa1-f46.google.com [209.85.160.46]) (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 D049B371065 for ; Wed, 5 Aug 2026 17:49:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952168; cv=none; b=cMtQGp67Bfn7uzGdXIdQNNiUt3Mpe5S9eBCm0WYeilN3DKDK6ghwB80WjVe/18kSqX3xckHhqFK77ZEvPju8ldHstS4X0OjfNfOYQ6h8BiDbuduTRoW2mQ5ulFxmYW7v/wbUVvilk+xgWexFFQCNWNyJAI/s+g4ut/lnJqstwtQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952168; c=relaxed/simple; bh=PTx1xmaBWvPevkP6ZbmwzZ7YY+06PmbkiJMfuow2Mow=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=TIajFM+MjDkuSeg1av26XcGedcK791eh/qrP3yEsCJpbZRkOZlInz6Yj5eQN8vpF9aiSUVobtCfOpvnhRZTxAXOmU6B5rQYbKtKCimxIRDtrXOkbNdpeivKz9NmIpAvuIZCKIBbaXyObcxMb1bb01qWnskXuTDLRlD/eyVQS3nc= 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=ez79dmKZ; arc=none smtp.client-ip=209.85.160.46 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="ez79dmKZ" Received: by mail-oa1-f46.google.com with SMTP id 586e51a60fabf-448b89f700fso1208941fac.2 for ; Wed, 05 Aug 2026 10:49:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785952165; x=1786556965; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=387qnwbkMAAVcWYZO25x2XelYHqPuaJgYNRgF/uAO2o=; b=ez79dmKZNnevR49ifg1M8Cr4vLNpMwDtsWZQhqWy6eRnGZlr6+VjtF5xEPnFWWkt/0 ExbEDCWepI9doJCzDrL85jXYLBAB119w4XD7IgmU0WmespRqHfES9Nar/NeaCwir7bCJ szJ33hZIX+4cgH6Uh8MUhRBVpLMwx/KnMeFAtt2mUTf//IEWBXkrJObnUt9WHFHZ9cJQ RTCIDM75+0GCW+jBt8kvr7b26Y+LxmruiRrWCnmwwJGYG3Kc6I4w+TfY6A/df3aMHHm9 ahzRfpBPf0OZ/dBeaXcB/4rSNiDSg09+cCZeWr0uwf+YwVC+qf+pQYKPePaETH87w2ZW Y24w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785952165; x=1786556965; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=387qnwbkMAAVcWYZO25x2XelYHqPuaJgYNRgF/uAO2o=; b=mRRixqQhRSjrZxbR3zW0xLFu23c9OhisUj3gTBlyetj5fX5EnprUJYBl8kZZabTLiH PuA//xUHn6ymEvFLsxszPsL1p8JhF25ug/IR99CbzLNlghyBQWyeY7t04ug30qhkT7Af F17p5M/XuVp06DQsfcsgtVr61NpKt7T/7/GqErBXXxRoJgM2925POaHMIAhkMY5aF7tT RexqU+N7phgHwH/RM5w14NfMXfcjMrfMJfy37exED8ZiQzJWNT5WyVEdXoN7Jf5BzxVD ve5aJrKFGGntXhQShXJnWvOWJMxkz77TGrCpGfzYRj+Ujx36yguB00ytBlU1mc9v4577 WLCg== X-Forwarded-Encrypted: i=1; AHgh+RpSDsIcan1IlHQdXj30lq5Jh6Bh7/MG3l9SklNe32mH+1tTi8xj/sGWgNOoXuIRnRFZk4lRyYCn293iPp4=@vger.kernel.org X-Gm-Message-State: AOJu0YxWOYRzOZkonxociOf6y5xL7FfBDgAMJtsjWdmVMfujuT0teA4S 5d9OSZLZ0uRFPdweMz+0C3LXTiMyng4q4SGUe22ytGdP7GoXzJDm+XFa X-Gm-Gg: AR+sD13VCITMdG+vbwwwwTF8y1FK/g/F9Ke2+dh3uAzV/9wzhysjkbjVC8La5SuroqP 5UkEUD0MMvxwl9ieqtfZdH6LxZXFoEMZ9JdXKJEXRe+nVOvSuCVa7YuQumcXCVswo/T8J799hYI q62SFbvtsrBP+UvP84vPzsyUrUbLZKy+70H+Wb7RW8MkUc63MN9X86CUAiWGTHyCcMO5Cfz5qyM rFF7n8VfbsbxkVMheMQsGitxxp1+E/yOQ6fJ3mGEY6+r7JhvAiPxf6n+r4g0oy4qP1Siilh5Qru cTuuFd6sJL5GYIwZoYSZ3uDVehNXUvNQrl8urS+Iz/ZthQnFurkHsonl85Vlje77Ghhor3Dk0fC TVJlZA+1xFUFRBatcmbEXq23wTPVDWCuF3XvvYjxRVpsk2R85cpgI6EDriDGa1pyUg5LJ3cGDAx TEo0GMey9u2cSIBuD+B1DJ9LW9ANUTm8OW/1+Mb8K28q7n367N8La6G+6XqBFCjklLayRNsQjuf p96/ZvfsHNnsvAiJv9tye/2z689oUkrLhGRe7/aRYSIfQ0bZ2pU0wqWWAaK6nVhSI+7tReN+Z/Q NLH6GpyHwBbBdb9IOJ8KrOnhM64LDs9QHwfwleJcxDU7PSs9VsVIbclaxBI/sciMxBoUfusspLr hFxvu7hgcVzF4nGrJ X-Received: by 2002:a05:6870:f146:b0:447:2ad0:b36 with SMTP id 586e51a60fabf-4599f0eed12mr4304641fac.20.1785952164722; Wed, 05 Aug 2026 10:49:24 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-4599dd615ecsm3281704fac.0.2026.08.05.10.49.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 05 Aug 2026 10:49:24 -0700 (PDT) From: James Hilliard Date: Wed, 05 Aug 2026 11:49:13 -0600 Subject: [PATCH net-next v3 2/4] net: mdio: factor out OF child registration helpers Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260805-submit-mdio-of-dynamic-v2-v3-2-16f3cc893b0e@gmail.com> References: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> In-Reply-To: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> To: Andrew Lunn , Heiner Kallweit , Russell King , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Florian Fainelli , Richard Cochran , Rob Herring , Saravana Kannan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 Live OF reconfiguration needs to create the same PHY and generic MDIO devices as initial bus population, including legacy PHY nodes that need address scanning. Factor fixed-address child registration and PHY address scanning into helpers. The old scan loop also visited every free address for a child without a reg property even when the child was not a PHY. Reject that case before scanning while preserving enumeration of valid PHY and generic MDIO children. Signed-off-by: James Hilliard --- Changes v1 -> v2: - split from the ACx00 series without code changes --- drivers/net/mdio/of_mdio.c | 66 ++++++++++++++++++++++++++++--------------= ---- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c index b8d298c04d3f..051e449bbe7c 100644 --- a/drivers/net/mdio/of_mdio.c +++ b/drivers/net/mdio/of_mdio.c @@ -77,6 +77,15 @@ static int of_mdiobus_register_device(struct mii_bus *md= io, return 0; } =20 +static int of_mdiobus_register_child(struct mii_bus *mdio, + struct device_node *child, u32 addr) +{ + if (of_mdiobus_child_is_phy(child)) + return of_mdiobus_register_phy(mdio, child, addr); + + return of_mdiobus_register_device(mdio, child, addr); +} + /* The following is a list of PHY compatible strings which appear in * some DTBs. The compatible string is never matched against a PHY * driver, so is pointless. We only expect devices which are not PHYs @@ -136,6 +145,32 @@ bool of_mdiobus_child_is_phy(struct device_node *child) } EXPORT_SYMBOL(of_mdiobus_child_is_phy); =20 +static int of_mdiobus_scan_phy(struct mii_bus *mdio, + struct device_node *child) +{ + int addr, rc; + + if (!of_mdiobus_child_is_phy(child)) + return -ENODEV; + + for (addr =3D 0; addr < PHY_MAX_ADDR; addr++) { + if (mdiobus_is_registered_device(mdio, addr)) + continue; + + dev_info(&mdio->dev, "scan phy %pOFn at address %i\n", + child, addr); + + /* -ENODEV means that scanning should continue. */ + rc =3D of_mdiobus_register_child(mdio, child, addr); + if (!rc) + return 0; + if (rc !=3D -ENODEV) + return rc; + } + + return -ENODEV; +} + static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_nod= e *np, bool *scanphys) { @@ -164,10 +199,7 @@ static int __of_mdiobus_parse_phys(struct mii_bus *mdi= o, struct device_node *np, continue; } =20 - if (of_mdiobus_child_is_phy(child)) - rc =3D of_mdiobus_register_phy(mdio, child, addr); - else - rc =3D of_mdiobus_register_device(mdio, child, addr); + rc =3D of_mdiobus_register_child(mdio, child, addr); =20 if (rc =3D=3D -ENODEV) dev_err(&mdio->dev, @@ -197,7 +229,7 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct = device_node *np, { struct device_node *child; bool scanphys =3D false; - int addr, rc; + int rc; =20 if (!np) return __mdiobus_register(mdio, owner); @@ -238,27 +270,9 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct= device_node *np, of_node_name_eq(child, "ethernet-phy-package")) continue; =20 - for (addr =3D 0; addr < PHY_MAX_ADDR; addr++) { - /* skip already registered PHYs */ - if (mdiobus_is_registered_device(mdio, addr)) - continue; - - /* be noisy to encourage people to set reg property */ - dev_info(&mdio->dev, "scan phy %pOFn at address %i\n", - child, addr); - - if (of_mdiobus_child_is_phy(child)) { - /* -ENODEV is the return code that PHYLIB has - * standardized on to indicate that bus - * scanning should continue. - */ - rc =3D of_mdiobus_register_phy(mdio, child, addr); - if (!rc) - break; - if (rc !=3D -ENODEV) - goto put_unregister; - } - } + rc =3D of_mdiobus_scan_phy(mdio, child); + if (rc && rc !=3D -ENODEV) + goto put_unregister; } =20 return 0; --=20 2.53.0 From nobody Mon Aug 24 23:46:15 2026 Received: from mail-oo1-f44.google.com (mail-oo1-f44.google.com [209.85.161.44]) (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 2182D3D348C for ; Wed, 5 Aug 2026 17:49:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952171; cv=none; b=oX2u6Vj1yakLYSltz43abEUT32tAiYO8gvGUm1GZ7ftq26wWNAnwfm2aOMAwCY6NRdWzhouSF22eSPPWbIuzdailRNg00xWhwrOPHI/HbGOoCEqDIQMi6WIzSQad5LUHdG7BiqIW2Z/CAPtte99jK7dZP1NGvC3rtLsQ8vrbhwA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952171; c=relaxed/simple; bh=OZw9gVHMEi7AGJfsGZLMDrHUpxMAGdZep3AIzs99XbY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=NU0SrhcRb2Iip2/QSEUc+ESfVmxGdy57K9ZMzCyk6ARG5Zuhf/YLGUOjh22hLLKLTk2T7KvSJMQ9Z6mvpjXhpcrVnpTKyiumrMy2Q8swTCXna9+fQOgD0MhscUqLGl9TqRC0YoFKahPosCNXgVRrslTG+fw+rClxNjGT5lGMyVI= 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=kAC9Rviv; arc=none smtp.client-ip=209.85.161.44 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="kAC9Rviv" Received: by mail-oo1-f44.google.com with SMTP id 006d021491bc7-6aaf2f9ce3dso666981eaf.1 for ; Wed, 05 Aug 2026 10:49:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785952166; x=1786556966; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=0ThANLUDodsw1sPmCTcA3DzcnTNWodFf4ilIvEJQxIA=; b=kAC9RvivyJV2Zu5ClxiTy5c1ZkxcoxcEeBIPFfNSAQNUulcdvAlJIi6LD2XBDA7bY9 GSxhomOqKcReEykG8AG9HT4Jo7xhWmxYlXPHj7OMELG1EkbdWYWVsOa3Cop/rhd5kg42 lDR+jIPK2/nwzwGC555G5OLLOUPRYYJGfHx4l2sJGoNAvvi5kuhps2yf9afsT5Cs4anx 9lrhZXFFETEVeY2aDXHLCQXvKl9qmBstL7tqW+PjEXekMzLbWiGypxROD5HOvMOxov0a 4YLTL1hhQXItNsN7L76Ad4HJb5lyGzEGj0rifRLgYkYm4qhi3tIijBER8drTsucZ32MO jVRw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785952166; x=1786556966; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=0ThANLUDodsw1sPmCTcA3DzcnTNWodFf4ilIvEJQxIA=; b=pLOLetieUJmEL9jD2CkS2HAfEd1aTzn1Bz61OkqCr9GZOoAdBWn+nan7HyrWYVofQv m/KCOe6tOUoinGVyGQzWfpMCTHcb38drdYEtkUOsItovSlw3FW7MZ3V4VaHJ16gCnWJB NK1r/tPyyqK7lnBCpW+PpQIRzIZR4HrE7nSu4/HZkBaw6tdScqhzs9+8+B4FNDWVTlOd bfLQbQFiEgZgSajHLtir6lmFAiGcw32mXDm1b0i3iK0+87hKNrSQO0kSIT4IbWUvmVdL RkPFbzgRG7TlejdlC98tnEglUcHPfOf6dtFKsdX9iDMxIL0pnhaQEUpYpBmap1oX7Ggy upGg== X-Forwarded-Encrypted: i=1; AHgh+RpErmsV7NbJRKWcYQGDlAIclSy1FyZMRzHwX4/XpyoBKn784Ly/HuY1Bn8VXOT3vxMPabsQgvRvzie6GgM=@vger.kernel.org X-Gm-Message-State: AOJu0Yx51I6kOBqZLbceBfik+uLzltIhOnLfUYPk6DnX9402rFpX1NwB yC1sb27ubA8uG8p2+wDTE3q1JpkldEs+w/uCE01qs8fXXGfzS3G79Wno X-Gm-Gg: AR+sD13v3FOVYjyjG47llfM4OdRiMlr+xsJF8HKMQNW98rfHaYQGkLBWikgq6IqLT3x i8dHW+y51AQesHfzpM9S+L8QgHDeZAJuXy3BDz1Rx+a0Reqj7WhrT4PKAhNFEwMdbMeUUF67c90 XgTG9fngqcRVKeAQEIlFlFUrTk1ND17wuMMKAO8BiKPRHhA2hdvZi1UHqZ6GlVVkdSSf9tNh9I8 9o1kfXjXdpmLSYYtZt0BhvlVzsH3jVW/AGFg3qvHmHQMfJrCIl4PJlxmsSoCMRn+syH3HdFiUF2 O2eiBsdBr/OdvhZLs6We6BJsGD9axOxA15ndt64gZZ+SpCyir/e91GRnf8sS0n4P7QVHzlh4sZP xZ3W9qCOw0XPidNXH3yXtHeJCZKxmV5U889/ayeRZUdz0NPDkUCfMPHixayMuGXo4G3OcT9rNf/ UzN2O2lZrNx+WCyKplNM4zFjOyC7zvYarCQ6GkJWRRyW+wy/BOogVcineS2p6z67jg0acgCp0iT p8EZMNfZjNDq2RHkc0K79eQ2BhnOo7iTl2JKXX8NDjkOeziFg4IRjqjAhUoweLPuHrQHtqloaqm bKM/Z5uTYmZEzbMWa1CkaTRqMzzfjmK5hZdVrrq8QHuzsPLocbXWWWctvsylbYUujBAOw1yJIXY OT2i8BpLzs18q8uvLd65zJEWuoPk= X-Received: by 2002:a05:6820:c83:b0:6a3:955b:8474 with SMTP id 006d021491bc7-6ae96c389b7mr4850575eaf.4.1785952165791; Wed, 05 Aug 2026 10:49:25 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-4599dd615ecsm3281704fac.0.2026.08.05.10.49.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 05 Aug 2026 10:49:25 -0700 (PDT) From: James Hilliard Date: Wed, 05 Aug 2026 11:49:14 -0600 Subject: [PATCH net-next v3 3/4] net: mdio: make device map changes hotplug-safe Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260805-submit-mdio-of-dynamic-v2-v3-3-16f3cc893b0e@gmail.com> References: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> In-Reply-To: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> To: Andrew Lunn , Heiner Kallweit , Russell King , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Florian Fainelli , Richard Cochran , Rob Herring , Saravana Kannan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 MDIO device registration currently publishes directly into mdio_map without serializing address ownership, and removal frees the object immediately. That is sufficient while bus population is static, but dynamic firmware changes can race registration, PHY attachment and bus teardown. It also cannot safely preserve mdiobus_get_phy()'s borrowed-pointer convention. Serialize device-map state and reserve addresses while registration is in progress. Track active scans, registrations and firmware changes so bus teardown first blocks new work and waits for existing work before consuming the final map. Make firmware removal transactions exclusive with registration, scanning and other firmware changes: start one only when no tracked map operation is active, then block new firmware changes, registrations and PHY attachment until it ends. Keep an address reserved while device_add() and driver probing run, then publish the fully registered device with release ordering. Make PHY attachment atomic with map removal, including standalone PHY users. Have address scans atomically claim a matching OF node as well, so explicit OF population cannot instantiate the same device concurrently. Route the remaining in-tree direct map reader through mdiobus_get_phy() so every lockless reader observes the same publication ordering. Keep dynamically removed PHY devices pinned until the MDIO bus is torn down so existing borrowed pointers cannot become use-after-free references. Generic MDIO-device lookups already return referenced objects and do not require retirement. Drop a removed device's firmware-node reference after device_del() so this does not keep an overlay node alive. Make the internal removal callback report whether it unpublished the device. Bus teardown holds a temporary reference while dropping the map lock and releases the device's registration reference only when its removal attempt succeeded. When a concurrent normal removal has already unpublished the device, teardown therefore does not release the same registration reference again. Public MDIO removal and driver APIs remain unchanged. Clear an error-valued optional reset GPIO before the common registration rollback uses the reset helpers. Signed-off-by: James Hilliard --- Changes v2 -> v3: - hold a device reference across unlocked teardown removal - free only devices successfully claimed by the teardown path - consolidate normal and dynamic internal removal callbacks - publish map entries only after device registration completes - route the remaining direct map reader through mdiobus_get_phy() - retire only PHY devices which have a borrowed-pointer lookup API - make firmware removal exclusive with registration, scanning and other firmware changes - propagate only population conflicts from recursive package lookup Changes v1 -> v2: - split from the ACx00 series - rebase onto net-next after stopped including - move touched declarations to function scope for netdev style --- drivers/net/phy/mdio_bus.c | 14 ++- drivers/net/phy/mdio_bus_provider.c | 147 ++++++++++++++++------ drivers/net/phy/mdio_device.c | 236 ++++++++++++++++++++++++++++++++= ---- drivers/net/phy/mscc/mscc_ptp.c | 6 +- drivers/net/phy/phy_device.c | 101 ++++++++++----- drivers/net/phy/phylib-internal.h | 4 +- include/linux/mdio.h | 4 +- include/linux/phy.h | 20 +++ 8 files changed, 433 insertions(+), 99 deletions(-) diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 00d0e4159e9b..6eb3ebbbba57 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -34,7 +34,8 @@ static struct mdio_device *mdiobus_find_device(struct mii= _bus *bus, int addr) if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr)) return NULL; =20 - return bus->mdio_map[addr]; + /* Pair with map publication in mdiobus_registration_done(). */ + return smp_load_acquire(&bus->mdio_map[addr]); } =20 struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr) @@ -54,7 +55,16 @@ EXPORT_SYMBOL(mdiobus_get_phy); =20 bool mdiobus_is_registered_device(struct mii_bus *bus, int addr) { - return mdiobus_find_device(bus, addr) !=3D NULL; + bool addr_valid =3D addr >=3D 0 && addr < ARRAY_SIZE(bus->mdio_map); + bool registered; + + if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr)) + return false; + + registered =3D READ_ONCE(bus->mdio_map[addr]) || + (READ_ONCE(bus->mdio_map_pending) & BIT(addr)); + + return registered; } EXPORT_SYMBOL(mdiobus_is_registered_device); =20 diff --git a/drivers/net/phy/mdio_bus_provider.c b/drivers/net/phy/mdio_bus= _provider.c index ce3a607a40cb..ff7e8de88bb0 100644 --- a/drivers/net/phy/mdio_bus_provider.c +++ b/drivers/net/phy/mdio_bus_provider.c @@ -330,6 +330,9 @@ struct mii_bus *mdiobus_alloc_size(size_t size) return NULL; =20 bus->state =3D MDIOBUS_ALLOCATED; + mutex_init(&bus->mdio_map_lock); + init_waitqueue_head(&bus->mdio_map_wait); + INIT_LIST_HEAD(&bus->mdio_map_retired_phys); if (size) bus->priv =3D (void *)bus + aligned_size; =20 @@ -355,10 +358,9 @@ static int of_mdiobus_find_phy(struct device *dev, str= uct mdio_device *mdiodev, struct device_node *np) { struct device_node *child; + int addr, ret; =20 for_each_available_child_of_node(np, child) { - int addr; - if (of_node_name_eq(child, "ethernet-phy-package")) { /* Validate PHY package reg presence */ if (!of_property_present(child, "reg")) { @@ -366,12 +368,13 @@ static int of_mdiobus_find_phy(struct device *dev, st= ruct mdio_device *mdiodev, return -EINVAL; } =20 - if (!of_mdiobus_find_phy(dev, mdiodev, child)) { + ret =3D of_mdiobus_find_phy(dev, mdiodev, child); + if (!ret || ret =3D=3D -EBUSY) { /* The refcount for the PHY package will be * incremented later when PHY join the Package. */ of_node_put(child); - return 0; + return ret; } =20 continue; @@ -382,6 +385,11 @@ static int of_mdiobus_find_phy(struct device *dev, str= uct mdio_device *mdiodev, continue; =20 if (addr =3D=3D mdiodev->addr) { + if (of_node_test_and_set_flag(child, OF_POPULATED)) { + of_node_put(child); + return -EBUSY; + } + device_set_node(dev, of_fwnode_handle(child)); /* The refcount on "child" is passed to the mdio * device. Do _not_ use of_node_put(child) here. @@ -393,22 +401,26 @@ static int of_mdiobus_find_phy(struct device *dev, st= ruct mdio_device *mdiodev, return -ENODEV; } =20 -static void of_mdiobus_link_mdiodev(struct mii_bus *bus, - struct mdio_device *mdiodev) +static int of_mdiobus_link_mdiodev(struct mii_bus *bus, + struct mdio_device *mdiodev) { struct device *dev =3D &mdiodev->dev; =20 if (dev->of_node || !bus->dev.of_node) - return; + return 0; =20 - of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node); + return of_mdiobus_find_phy(dev, mdiodev, bus->dev.of_node); } #endif =20 -static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool= c45) +static struct phy_device *__mdiobus_scan(struct mii_bus *bus, int addr, + bool c45) { struct phy_device *phydev =3D ERR_PTR(-ENODEV); struct fwnode_handle *fwnode; +#if IS_ENABLED(CONFIG_OF_MDIO) + bool of_node_populated =3D false; +#endif char node_name[16]; int err; =20 @@ -420,7 +432,12 @@ static struct phy_device *mdiobus_scan(struct mii_bus = *bus, int addr, bool c45) /* For DT, see if the auto-probed phy has a corresponding child * in the bus node, and set the of_node pointer in this case. */ - of_mdiobus_link_mdiodev(bus, &phydev->mdio); + err =3D of_mdiobus_link_mdiodev(bus, &phydev->mdio); + if (err =3D=3D -EBUSY) { + phy_device_free(phydev); + return ERR_PTR(-ENODEV); + } + of_node_populated =3D !!phydev->mdio.dev.of_node; #endif =20 /* Search for a swnode for the phy in the swnode hierarchy of the bus. @@ -437,6 +454,11 @@ static struct phy_device *mdiobus_scan(struct mii_bus = *bus, int addr, bool c45) =20 err =3D phy_device_register(phydev); if (err) { +#if IS_ENABLED(CONFIG_OF_MDIO) + if (of_node_populated) + of_node_clear_flag(phydev->mdio.dev.of_node, + OF_POPULATED); +#endif phy_device_free(phydev); return ERR_PTR(-ENODEV); } @@ -458,7 +480,17 @@ static struct phy_device *mdiobus_scan(struct mii_bus = *bus, int addr, bool c45) */ struct phy_device *mdiobus_scan_c22(struct mii_bus *bus, int addr) { - return mdiobus_scan(bus, addr, false); + struct phy_device *phydev; + int err; + + err =3D mdiobus_device_change_begin(bus, false); + if (err) + return ERR_PTR(err); + + phydev =3D __mdiobus_scan(bus, addr, false); + mdiobus_device_change_end(bus, false); + + return phydev; } EXPORT_SYMBOL(mdiobus_scan_c22); =20 @@ -476,7 +508,7 @@ EXPORT_SYMBOL(mdiobus_scan_c22); */ static struct phy_device *mdiobus_scan_c45(struct mii_bus *bus, int addr) { - return mdiobus_scan(bus, addr, true); + return __mdiobus_scan(bus, addr, true); } =20 static int mdiobus_scan_bus_c22(struct mii_bus *bus) @@ -487,7 +519,7 @@ static int mdiobus_scan_bus_c22(struct mii_bus *bus) if ((bus->phy_mask & BIT(i)) =3D=3D 0) { struct phy_device *phydev; =20 - phydev =3D mdiobus_scan_c22(bus, i); + phydev =3D __mdiobus_scan(bus, i, false); if (IS_ERR(phydev) && (PTR_ERR(phydev) !=3D -ENODEV)) return PTR_ERR(phydev); } @@ -504,7 +536,7 @@ static int mdiobus_scan_bus_c45(struct mii_bus *bus) struct phy_device *phydev; =20 /* Don't scan C45 if we already have a C22 device */ - if (bus->mdio_map[i]) + if (mdiobus_is_registered_device(bus, i)) continue; =20 phydev =3D mdiobus_scan_c45(bus, i); @@ -536,6 +568,50 @@ static bool mdiobus_prevent_c45_scan(struct mii_bus *b= us) return false; } =20 +static void mdiobus_stop_device_changes(struct mii_bus *bus) +{ + mutex_lock(&bus->mdio_map_lock); + bus->state =3D MDIOBUS_UNREGISTERING; + mutex_unlock(&bus->mdio_map_lock); + + wait_event(bus->mdio_map_wait, !READ_ONCE(bus->mdio_map_ops)); +} + +static void mdiobus_remove_devices(struct mii_bus *bus) +{ + LIST_HEAD(removed); + struct mdio_device *mdiodev; + struct phy_device *phydev, *next; + int i; + + for (i =3D 0; i < PHY_MAX_ADDR; i++) { + mutex_lock(&bus->mdio_map_lock); + mdiodev =3D bus->mdio_map[i]; + if (mdiodev) + mdio_device_get(mdiodev); + mutex_unlock(&bus->mdio_map_lock); + if (!mdiodev) + continue; + + if (!mdiodev->device_remove(mdiodev, false)) + mdiodev->device_free(mdiodev); + mdio_device_put(mdiodev); + } + + mutex_lock(&bus->mdio_map_lock); + list_splice_init(&bus->mdio_map_retired_phys, &removed); + mutex_unlock(&bus->mdio_map_lock); + + list_for_each_entry_safe(phydev, next, &removed, retired_node) { + list_del_init(&phydev->retired_node); + mdio_device_put(&phydev->mdio); + } + + mutex_lock(&bus->mdio_map_lock); + bus->state =3D MDIOBUS_UNREGISTERED; + mutex_unlock(&bus->mdio_map_lock); +} + /** * __mdiobus_register - bring up all the PHYs on a given bus and attach th= em to bus * @bus: target mii_bus @@ -552,10 +628,9 @@ static bool mdiobus_prevent_c45_scan(struct mii_bus *b= us) */ int __mdiobus_register(struct mii_bus *bus, struct module *owner) { - struct mdio_device *mdiodev; struct gpio_desc *gpiod; bool prevent_c45_scan; - int i, err; + int err; =20 if (!bus || !bus->name) return -EINVAL; @@ -596,7 +671,9 @@ int __mdiobus_register(struct mii_bus *bus, struct modu= le *owner) * * State will be updated later in this function in case of success */ + mutex_lock(&bus->mdio_map_lock); bus->state =3D MDIOBUS_UNREGISTERED; + mutex_unlock(&bus->mdio_map_lock); =20 err =3D device_register(&bus->dev); if (err) { @@ -613,8 +690,7 @@ int __mdiobus_register(struct mii_bus *bus, struct modu= le *owner) err =3D dev_err_probe(&bus->dev, PTR_ERR(gpiod), "mii_bus %s couldn't get reset GPIO\n", bus->id); - device_del(&bus->dev); - return err; + goto error_reset_gpiod; } else if (gpiod) { bus->reset_gpiod =3D gpiod; fsleep(bus->reset_delay_us); @@ -629,6 +705,10 @@ int __mdiobus_register(struct mii_bus *bus, struct mod= ule *owner) goto error_reset_gpiod; } =20 + mutex_lock(&bus->mdio_map_lock); + bus->state =3D MDIOBUS_REGISTERING; + mutex_unlock(&bus->mdio_map_lock); + if (bus->read) { err =3D mdiobus_scan_bus_c22(bus); if (err) @@ -643,20 +723,17 @@ int __mdiobus_register(struct mii_bus *bus, struct mo= dule *owner) goto error; } =20 + mutex_lock(&bus->mdio_map_lock); bus->state =3D MDIOBUS_REGISTERED; + mutex_unlock(&bus->mdio_map_lock); dev_dbg(&bus->dev, "probed\n"); return 0; =20 error: - for (i =3D 0; i < PHY_MAX_ADDR; i++) { - mdiodev =3D bus->mdio_map[i]; - if (!mdiodev) - continue; - - mdiodev->device_remove(mdiodev); - mdiodev->device_free(mdiodev); - } error_reset_gpiod: + mdiobus_stop_device_changes(bus); + mdiobus_remove_devices(bus); + /* Put PHYs in RESET to save power */ if (bus->reset_gpiod) gpiod_set_value_cansleep(bus->reset_gpiod, 1); @@ -668,21 +745,11 @@ EXPORT_SYMBOL(__mdiobus_register); =20 void mdiobus_unregister(struct mii_bus *bus) { - struct mdio_device *mdiodev; - int i; - if (WARN_ON_ONCE(bus->state !=3D MDIOBUS_REGISTERED)) return; - bus->state =3D MDIOBUS_UNREGISTERED; =20 - for (i =3D 0; i < PHY_MAX_ADDR; i++) { - mdiodev =3D bus->mdio_map[i]; - if (!mdiodev) - continue; - - mdiodev->device_remove(mdiodev); - mdiodev->device_free(mdiodev); - } + mdiobus_stop_device_changes(bus); + mdiobus_remove_devices(bus); =20 /* Put PHYs in RESET to save power */ if (bus->reset_gpiod) @@ -702,8 +769,11 @@ EXPORT_SYMBOL(mdiobus_unregister); */ void mdiobus_free(struct mii_bus *bus) { + mutex_lock(&bus->mdio_map_lock); + /* For compatibility with error handling in drivers. */ if (bus->state =3D=3D MDIOBUS_ALLOCATED) { + mutex_unlock(&bus->mdio_map_lock); kfree(bus); return; } @@ -711,6 +781,7 @@ void mdiobus_free(struct mii_bus *bus) WARN(bus->state !=3D MDIOBUS_UNREGISTERED, "%s: not in UNREGISTERED state\n", bus->id); bus->state =3D MDIOBUS_RELEASED; + mutex_unlock(&bus->mdio_map_lock); =20 put_device(&bus->dev); } diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index 06151f207134..119fd2fbc12d 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -33,12 +34,16 @@ static int mdio_device_register_reset(struct mdio_device *mdiodev) { struct reset_control *reset; + int err; =20 /* Deassert the optional reset signal */ mdiodev->reset_gpio =3D gpiod_get_optional(&mdiodev->dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(mdiodev->reset_gpio)) - return PTR_ERR(mdiodev->reset_gpio); + if (IS_ERR(mdiodev->reset_gpio)) { + err =3D PTR_ERR(mdiodev->reset_gpio); + mdiodev->reset_gpio =3D NULL; + return err; + } =20 if (mdiodev->reset_gpio) gpiod_set_consumer_name(mdiodev->reset_gpio, "PHY reset"); @@ -116,6 +121,8 @@ static void mdio_device_release(struct device *dev) kfree(to_mdio_device(dev)); } =20 +static int __mdio_device_remove(struct mdio_device *mdiodev, bool dynamic); + struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr) { struct mdio_device *mdiodev; @@ -129,7 +136,7 @@ struct mdio_device *mdio_device_create(struct mii_bus *= bus, int addr) mdiodev->dev.parent =3D &bus->dev; mdiodev->dev.bus =3D &mdio_bus_type; mdiodev->device_free =3D mdio_device_free; - mdiodev->device_remove =3D mdio_device_remove; + mdiodev->device_remove =3D __mdio_device_remove; mdiodev->bus =3D bus; mdiodev->addr =3D addr; mdiodev->reset_state =3D -1; @@ -159,19 +166,27 @@ int mdio_device_register(struct mdio_device *mdiodev) return err; =20 err =3D device_add(&mdiodev->dev); - if (err) { + if (err) pr_err("MDIO %d failed to add\n", mdiodev->addr); - goto out; - } - - return 0; =20 - out: - mdiobus_unregister_device(mdiodev); - return err; + return mdiobus_registration_done(mdiodev, err); } EXPORT_SYMBOL(mdio_device_register); =20 +static int __mdio_device_remove(struct mdio_device *mdiodev, bool dynamic) +{ + int err; + + err =3D mdiobus_begin_remove(mdiodev, dynamic); + if (err) + return err; + + device_del(&mdiodev->dev); + mdiobus_finish_remove(mdiodev, dynamic); + + return 0; +} + /** * mdio_device_remove - Remove a previously registered mdio device from the * MDIO bus @@ -183,42 +198,215 @@ EXPORT_SYMBOL(mdio_device_register); */ void mdio_device_remove(struct mdio_device *mdiodev) { - device_del(&mdiodev->dev); - mdiobus_unregister_device(mdiodev); + __mdio_device_remove(mdiodev, false); } EXPORT_SYMBOL(mdio_device_remove); =20 int mdiobus_register_device(struct mdio_device *mdiodev) { + struct mii_bus *bus =3D mdiodev->bus; int err; =20 - if (mdiodev->bus->mdio_map[mdiodev->addr]) - return -EBUSY; + mutex_lock(&bus->mdio_map_lock); + if (bus->state !=3D MDIOBUS_REGISTERING && + bus->state !=3D MDIOBUS_REGISTERED) { + err =3D -ENODEV; + goto out_unlock; + } + if (bus->mdio_map_removing) { + err =3D -EBUSY; + goto out_unlock; + } + + if (bus->mdio_map[mdiodev->addr] || + bus->mdio_map_pending & BIT(mdiodev->addr)) { + err =3D -EBUSY; + goto out_unlock; + } + + bus->mdio_map_pending |=3D BIT(mdiodev->addr); + bus->mdio_map_ops++; + mutex_unlock(&bus->mdio_map_lock); =20 if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) { err =3D mdio_device_register_reset(mdiodev); - if (err) + if (err) { + mdiobus_registration_done(mdiodev, err); return err; + } =20 /* Assert the reset signal */ mdio_device_reset(mdiodev, 1); } =20 - mdiodev->bus->mdio_map[mdiodev->addr] =3D mdiodev; - return 0; + +out_unlock: + mutex_unlock(&bus->mdio_map_lock); + return err; } =20 -int mdiobus_unregister_device(struct mdio_device *mdiodev) +/** + * mdiobus_device_change_begin - start changing devices on a registered bus + * @bus: MDIO bus that will be scanned or changed + * @removing: whether to start an exclusive removal transaction + * + * Return: zero on success or a negative error code when the bus is unavai= lable + */ +int mdiobus_device_change_begin(struct mii_bus *bus, bool removing) { - if (mdiodev->bus->mdio_map[mdiodev->addr] !=3D mdiodev) - return -EINVAL; + int err =3D 0; =20 - mdio_device_unregister_reset(mdiodev); + mutex_lock(&bus->mdio_map_lock); + if (bus->state !=3D MDIOBUS_REGISTERED) { + err =3D -ENODEV; + } else if (bus->mdio_map_removing || + (removing && bus->mdio_map_ops)) { + err =3D -EBUSY; + } else { + bus->mdio_map_ops++; + if (removing) + bus->mdio_map_removing =3D true; + } + mutex_unlock(&bus->mdio_map_lock); =20 - mdiodev->bus->mdio_map[mdiodev->addr] =3D NULL; + return err; +} +EXPORT_SYMBOL_GPL(mdiobus_device_change_begin); =20 - return 0; +static void mdiobus_operation_done_locked(struct mii_bus *bus) +{ + lockdep_assert_held(&bus->mdio_map_lock); + + if (WARN_ON_ONCE(!bus->mdio_map_ops)) + return; + bus->mdio_map_ops--; + if (!bus->mdio_map_ops) + wake_up_all(&bus->mdio_map_wait); +} + +/** + * mdiobus_device_change_end - finish changing devices on an MDIO bus + * @bus: MDIO bus previously passed to mdiobus_device_change_begin() + * @removing: value passed to mdiobus_device_change_begin() + */ +void mdiobus_device_change_end(struct mii_bus *bus, bool removing) +{ + mutex_lock(&bus->mdio_map_lock); + if (removing) { + WARN_ON_ONCE(!bus->mdio_map_removing); + bus->mdio_map_removing =3D false; + } + mdiobus_operation_done_locked(bus); + mutex_unlock(&bus->mdio_map_lock); +} +EXPORT_SYMBOL_GPL(mdiobus_device_change_end); + +static void mdiobus_operation_done(struct mii_bus *bus) +{ + mutex_lock(&bus->mdio_map_lock); + mdiobus_operation_done_locked(bus); + mutex_unlock(&bus->mdio_map_lock); +} + +static void mdiobus_unpublish_device(struct mdio_device *mdiodev) +{ + struct mii_bus *bus =3D mdiodev->bus; + + lockdep_assert_held(&bus->mdio_map_lock); + + if (bus->mdio_map[mdiodev->addr] =3D=3D mdiodev) + WRITE_ONCE(bus->mdio_map[mdiodev->addr], NULL); + if (mdiodev->dev.of_node) + of_node_clear_flag(mdiodev->dev.of_node, OF_POPULATED); +} + +int mdiobus_registration_done(struct mdio_device *mdiodev, int err) +{ + struct mii_bus *bus =3D mdiodev->bus; + + mutex_lock(&bus->mdio_map_lock); + if (WARN_ON_ONCE(!(bus->mdio_map_pending & BIT(mdiodev->addr)))) + goto out_unlock; + + if (err) { + mdiobus_unpublish_device(mdiodev); + } else { + WARN_ON_ONCE(bus->mdio_map[mdiodev->addr]); + /* Teardown waits for this operation before consuming the map. */ + smp_store_release(&bus->mdio_map[mdiodev->addr], mdiodev); + } + + bus->mdio_map_pending &=3D ~BIT(mdiodev->addr); + +out_unlock: + mutex_unlock(&bus->mdio_map_lock); + if (err) { + if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) { + mdio_device_reset(mdiodev, 1); + mdio_device_unregister_reset(mdiodev); + } + } + mdiobus_operation_done(bus); + + return err; +} + +int mdiobus_begin_remove(struct mdio_device *mdiodev, bool dynamic) +{ + struct mii_bus *bus =3D mdiodev->bus; + struct phy_device *phydev =3D NULL; + int err =3D 0; + + mutex_lock(&bus->mdio_map_lock); + if (dynamic && bus->state !=3D MDIOBUS_REGISTERED) { + err =3D -ENODEV; + goto out_unlock; + } + if (bus->mdio_map_pending & BIT(mdiodev->addr)) { + err =3D -EBUSY; + goto out_unlock; + } + + if (bus->mdio_map[mdiodev->addr] !=3D mdiodev) { + err =3D -ENODEV; + goto out_unlock; + } + + if (dynamic && mdiodev->flags & MDIO_DEVICE_FLAG_PHY) { + phydev =3D to_phy_device(&mdiodev->dev); + if (phydev->attached) { + err =3D -EBUSY; + goto out_unlock; + } + } + + mdiobus_unpublish_device(mdiodev); + + if (dynamic && mdiodev->flags & MDIO_DEVICE_FLAG_PHY) { + mdio_device_get(mdiodev); + list_add_tail(&phydev->retired_node, + &bus->mdio_map_retired_phys); + } + +out_unlock: + mutex_unlock(&bus->mdio_map_lock); + return err; +} + +void mdiobus_finish_remove(struct mdio_device *mdiodev, bool dynamic) +{ + struct fwnode_handle *fwnode; + + if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) + mdio_device_unregister_reset(mdiodev); + + /* Do not keep an overlay node alive with the retired device. */ + if (dynamic) { + fwnode =3D dev_fwnode(&mdiodev->dev); + device_set_node(&mdiodev->dev, NULL); + fwnode_handle_put(fwnode); + } } =20 /** diff --git a/drivers/net/phy/mscc/mscc_ptp.c b/drivers/net/phy/mscc/mscc_pt= p.c index 4865eac74b0e..546911858e3e 100644 --- a/drivers/net/phy/mscc/mscc_ptp.c +++ b/drivers/net/phy/mscc/mscc_ptp.c @@ -1279,10 +1279,8 @@ static struct vsc8531_private *vsc8584_base_priv(str= uct phy_device *phydev) struct vsc8531_private *vsc8531 =3D phydev->priv; =20 if (vsc8531->ts_base_addr !=3D phydev->mdio.addr) { - struct mdio_device *dev; - - dev =3D phydev->mdio.bus->mdio_map[vsc8531->ts_base_addr]; - phydev =3D container_of(dev, struct phy_device, mdio); + phydev =3D mdiobus_get_phy(phydev->mdio.bus, + vsc8531->ts_base_addr); =20 return phydev->priv; } diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index d65f28dd5773..55a0e06acdf5 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -227,12 +227,14 @@ static void phy_device_release(struct device *dev) kfree(to_phy_device(dev)); } =20 -static void phy_mdio_device_remove(struct mdio_device *mdiodev) +static int __phy_device_remove(struct phy_device *phydev, bool dynamic); + +static int phy_mdio_device_remove(struct mdio_device *mdiodev, bool dynami= c) { struct phy_device *phydev; =20 phydev =3D container_of(mdiodev, struct phy_device, mdio); - phy_device_remove(phydev); + return __phy_device_remove(phydev, dynamic); } =20 static struct phy_driver genphy_driver; @@ -769,6 +771,7 @@ struct phy_device *phy_device_create(struct mii_bus *bu= s, int addr, u32 phy_id, mdiodev->device_free =3D phy_mdio_device_free; mdiodev->device_remove =3D phy_mdio_device_remove; mdiodev->reset_state =3D -1; + INIT_LIST_HEAD(&dev->retired_node); =20 dev->speed =3D SPEED_UNKNOWN; dev->duplex =3D DUPLEX_UNKNOWN; @@ -1121,25 +1124,40 @@ int phy_device_register(struct phy_device *phydev) err =3D phy_scan_fixups(phydev); if (err) { phydev_err(phydev, "failed to initialize\n"); - goto out; + return mdiobus_registration_done(&phydev->mdio, err); } =20 err =3D device_add(&phydev->mdio.dev); - if (err) { + if (err) phydev_err(phydev, "failed to add\n"); - goto out; - } =20 - return 0; + return mdiobus_registration_done(&phydev->mdio, err); +} +EXPORT_SYMBOL(phy_device_register); + +static int __phy_device_remove(struct phy_device *phydev, bool dynamic) +{ + int err; + + err =3D mdiobus_begin_remove(&phydev->mdio, dynamic); + if (dynamic && err =3D=3D -EBUSY) + dev_warn(&phydev->mdio.dev, + "cannot remove a PHY while it is attached or being registered\n"); + if (err) + return err; + + unregister_mii_timestamper(phydev->mii_ts); + pse_control_put(phydev->psec); + + device_del(&phydev->mdio.dev); =20 - out: /* Assert the reset signal */ phy_device_reset(phydev, 1); =20 - mdiobus_unregister_device(&phydev->mdio); - return err; + mdiobus_finish_remove(&phydev->mdio, dynamic); + + return 0; } -EXPORT_SYMBOL(phy_device_register); =20 /** * phy_device_remove - Remove a previously registered phy device from the = MDIO bus @@ -1151,15 +1169,7 @@ EXPORT_SYMBOL(phy_device_register); */ void phy_device_remove(struct phy_device *phydev) { - unregister_mii_timestamper(phydev->mii_ts); - pse_control_put(phydev->psec); - - device_del(&phydev->mdio.dev); - - /* Assert the reset signal */ - phy_device_reset(phydev, 1); - - mdiobus_unregister_device(&phydev->mdio); + __phy_device_remove(phydev, false); } EXPORT_SYMBOL(phy_device_remove); =20 @@ -1734,6 +1744,36 @@ static bool phy_drv_supports_irq(const struct phy_dr= iver *phydrv) return phydrv->config_intr && phydrv->handle_interrupt; } =20 +static int phy_claim(struct phy_device *phydev) +{ + struct mdio_device *mdiodev =3D &phydev->mdio; + struct mii_bus *bus =3D mdiodev->bus; + int err =3D 0; + + mutex_lock(&bus->mdio_map_lock); + if (bus->state !=3D MDIOBUS_REGISTERED || + bus->mdio_map_removing || + bus->mdio_map[mdiodev->addr] !=3D mdiodev || + (bus->mdio_map_pending & BIT(mdiodev->addr))) + err =3D -ENODEV; + else if (phydev->attached) + err =3D -EBUSY; + else + phydev->attached =3D true; + mutex_unlock(&bus->mdio_map_lock); + + return err; +} + +static void phy_release(struct phy_device *phydev) +{ + struct mii_bus *bus =3D phydev->mdio.bus; + + mutex_lock(&bus->mdio_map_lock); + phydev->attached =3D false; + mutex_unlock(&bus->mdio_map_lock); +} + /** * phy_attach_direct - attach a network device to a given PHY device point= er * @dev: network device to attach @@ -1755,6 +1795,7 @@ int phy_attach_direct(struct net_device *dev, struct = phy_device *phydev, struct module *bus_owner =3D phydev->mdio.bus->owner; struct device *d =3D &phydev->mdio.dev; struct module *ndev_owner =3D NULL; + bool claimed =3D false; int err; =20 /* For Ethernet device drivers that register their own MDIO bus, we @@ -1770,6 +1811,13 @@ int phy_attach_direct(struct net_device *dev, struct= phy_device *phydev, } =20 get_device(d); + err =3D phy_claim(phydev); + if (err =3D=3D -EBUSY) + phydev_err(phydev, "PHY already attached\n"); + if (!err) + claimed =3D true; + if (err) + goto error_put_device; =20 /* Assume that if there is no driver, that it doesn't * exist, and we should use the genphy driver. @@ -1798,12 +1846,6 @@ int phy_attach_direct(struct net_device *dev, struct= phy_device *phydev, goto error_module_put; } =20 - if (phydev->attached_dev) { - dev_err(&dev->dev, "PHY already attached\n"); - err =3D -EBUSY; - goto error; - } - phydev->phy_link_change =3D phy_link_change; if (dev) { phydev->attached_dev =3D dev; @@ -1899,6 +1941,8 @@ int phy_attach_direct(struct net_device *dev, struct = phy_device *phydev, phydev->is_genphy_driven =3D 0; d->driver =3D NULL; error_put_device: + if (claimed) + phy_release(phydev); put_device(d); if (ndev_owner !=3D bus_owner) module_put(bus_owner); @@ -1918,7 +1962,6 @@ void phy_detach(struct phy_device *phydev) struct net_device *dev =3D phydev->attached_dev; struct module *ndev_owner =3D NULL; struct module *bus_owner; - struct mii_bus *bus; =20 if (phydev->devlink) { device_link_del(phydev->devlink); @@ -1974,8 +2017,8 @@ void phy_detach(struct phy_device *phydev) phy_device_reset(phydev, 1); =20 /* The PHY and its parent bus may be released by put_device() below. */ - bus =3D phydev->mdio.bus; - bus_owner =3D bus->owner; + bus_owner =3D phydev->mdio.bus->owner; + phy_release(phydev); =20 put_device(&phydev->mdio.dev); if (dev) diff --git a/drivers/net/phy/phylib-internal.h b/drivers/net/phy/phylib-int= ernal.h index 664ed7faa518..7e9161fcdd90 100644 --- a/drivers/net/phy/phylib-internal.h +++ b/drivers/net/phy/phylib-internal.h @@ -25,7 +25,9 @@ int phy_speed_down_core(struct phy_device *phydev); void phy_check_downshift(struct phy_device *phydev); =20 int mdiobus_register_device(struct mdio_device *mdiodev); -int mdiobus_unregister_device(struct mdio_device *mdiodev); +int mdiobus_registration_done(struct mdio_device *mdiodev, int err); +int mdiobus_begin_remove(struct mdio_device *mdiodev, bool dynamic); +void mdiobus_finish_remove(struct mdio_device *mdiodev, bool dynamic); =20 int genphy_c45_read_eee_adv(struct phy_device *phydev, unsigned long *adv); =20 diff --git a/include/linux/mdio.h b/include/linux/mdio.h index a7d9e3ae362a..7ce784af0c66 100644 --- a/include/linux/mdio.h +++ b/include/linux/mdio.h @@ -31,7 +31,7 @@ struct mdio_device { =20 int (*bus_match)(struct device *dev, const struct device_driver *drv); void (*device_free)(struct mdio_device *mdiodev); - void (*device_remove)(struct mdio_device *mdiodev); + int (*device_remove)(struct mdio_device *mdiodev, bool dynamic); =20 /* Bus address of the MDIO device (0-31) */ int addr; @@ -694,6 +694,8 @@ static inline int mdiodev_c45_write(struct mdio_device = *mdiodev, u32 devad, =20 bool mdiobus_is_registered_device(struct mii_bus *bus, int addr); struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr); +int mdiobus_device_change_begin(struct mii_bus *bus, bool removing); +void mdiobus_device_change_end(struct mii_bus *bus, bool removing); =20 /** * mdio_module_driver() - Helper macro for registering mdio drivers diff --git a/include/linux/phy.h b/include/linux/phy.h index 5f8d65868e0f..9a8fe5ea2b74 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -391,7 +392,9 @@ struct mii_bus { /** @state: State of bus structure */ enum { MDIOBUS_ALLOCATED =3D 1, + MDIOBUS_REGISTERING, MDIOBUS_REGISTERED, + MDIOBUS_UNREGISTERING, MDIOBUS_UNREGISTERED, MDIOBUS_RELEASED, } state; @@ -401,6 +404,18 @@ struct mii_bus { =20 /** @mdio_map: list of all MDIO devices on bus */ struct mdio_device *mdio_map[PHY_MAX_ADDR]; + /** @mdio_map_pending: addresses with registration in progress */ + u32 mdio_map_pending; + /** @mdio_map_lock: protects the MDIO device map and bus state */ + struct mutex mdio_map_lock; + /** @mdio_map_wait: wait for active map operations during teardown */ + wait_queue_head_t mdio_map_wait; + /** @mdio_map_ops: active registrations, scans and firmware changes */ + unsigned int mdio_map_ops; + /** @mdio_map_removing: firmware removal blocking map changes and attachm= ent */ + bool mdio_map_removing; + /** @mdio_map_retired_phys: removed PHYs pinned until bus teardown */ + struct list_head mdio_map_retired_phys; =20 /** @phy_mask: PHY addresses to be ignored when probing */ u32 phy_mask; @@ -652,6 +667,9 @@ struct phy_oatc14_sqi_capability { * @n_ports: Number of ports currently attached to the PHY * @max_n_ports: Max number of ports this PHY can expose * @lock: Mutex for serialization access to PHY + * @attached: Whether a network device or standalone user attached the PHY; + * protected by the MDIO bus map lock + * @retired_node: Entry in mii_bus::mdio_map_retired_phys * @state_queue: Work queue for state machine * @link_down_events: Number of times link was lost * @shared: Pointer to private data shared by phys in one package @@ -781,6 +799,8 @@ struct phy_device { struct delayed_work state_queue; =20 struct mutex lock; + bool attached; + struct list_head retired_node; =20 /* This may be modified under the rtnl lock */ bool sfp_bus_attached; --=20 2.53.0 From nobody Mon Aug 24 23:46:15 2026 Received: from mail-oa1-f41.google.com (mail-oa1-f41.google.com [209.85.160.41]) (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 2E4163D34A2 for ; Wed, 5 Aug 2026 17:49:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952171; cv=none; b=Axzp925ouEw7/slKxVSiv/IvlK0/zZoDkFyArqPZr7h9d0Qf3GwS9d97pOhK5KW5VIgkqaAmLoSwKCMI5IFgCc5Ir6NRqtWHu5mvBRB/EAkX+fEfSnOI0FeZNHoiFL5SF8YWtVqMlCpI9VEKZRSqeVk8zjtlSvu/NcCsLmJwlhg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785952171; c=relaxed/simple; bh=44yWujm+x/aZTFO3cmEryslEoFL9xGUIzr6Fnj/EYtk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=f7nLojWiS2+adiSwWXYCjiRWsCb088o28oGrl9klPSMouoYG+Phc6wyI38qXQW5FyZhVjTk+7VDS18tmgRU6rts/pJUHqgh4Ddh1YFZxaKamUivjwihl4HgEaQrRXsLlgLPpc4qFDzdy7FHgMKi9sBBecLJSnChWowiraFpe+TU= 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=GIm1rDVW; arc=none smtp.client-ip=209.85.160.41 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="GIm1rDVW" Received: by mail-oa1-f41.google.com with SMTP id 586e51a60fabf-4472500e25fso510444fac.1 for ; Wed, 05 Aug 2026 10:49:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785952167; x=1786556967; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Td1cIZN7YLfxZHzUpSNhyT7BsW7i4wxaTFJpOHLFZQk=; b=GIm1rDVWEZ5lP6fnNErl4DXxywN24pS92CmaPK0YckGCcTPMbYRuQiFSKOmiScCXzo dVdeE3LvQNAMujR8jHcRlsj5/p0vDjQVD3xrX1F/bRxK6T6AkDFWPUhQTG9gU/5KS2u4 l+pyA4nqsC8P8QXUY5aPPf4f2G0eKyB7qN5B0/dRRdpz6GP9uJUXky26EPzuIhhvfsht jZ2vibv2wmPGBWqLm13YnGs8+HmnLthPqVqEwE/cnpxrrhTG9A92XeCGScJ8KrwBb8go bKmaNDKfTcGneLPVMPbRkW7WRDU8x3hFyE1Yt+n+VJ4fGldP8KS2F+4vbPHqTFVabEAV JuGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785952167; x=1786556967; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=Td1cIZN7YLfxZHzUpSNhyT7BsW7i4wxaTFJpOHLFZQk=; b=W9OyI/CVLeBBWwqqDI4QjdedjxG+xzGkboUY6NQccNPx/qyjPucjwZ18fgqqk2Oa4F eHiKQj5SR96duHgDzMzT6oy7dpozy0fzTZFDG1O+/X3OUplAi6LTOHVBejtAJqs2AtNm 2+kK7xd7YPDRTRagrqHXdCegI6Yym2RLr+wkQUWfsNzER1VHTTZ7aSL7X4Y4a+hWMyha rGOyKU1OeuG+baKd/KFn0Fhqzo0y1yCF+yuuNXjL2e9qe6olcqwIiYjjwCElVySDHYKB q3APLT/v4MvSbxqSZbrXT5LFDg5pquopruFwFDiOESKHZ6hs1Y39EDK/9f6FGgElKLTw woJw== X-Forwarded-Encrypted: i=1; AHgh+RopHwLeZXst4euXE4sftzFLJiNaV+aFSNmkHx+O/49ACyzql6Us154YQvepYJaEocMGDQHG/uuJA80Kolo=@vger.kernel.org X-Gm-Message-State: AOJu0YzGR6+AuiJXCDFWvJ70+sqvgZbdQI7zrOBRacRz45TF5+6Zqf2S I9IYLh1nBHqRkqizrIUfaXHQvqcgRNkhT1DlJeQ77DwlOtafyUbSq/Li X-Gm-Gg: AR+sD10H1RX/A0SAPeLCUlHKVPua3U9DQJSVE+o2nXtq/JLWteQaAoWEuSfMZgeZ22H 9DrttSHB2eqaJI6ZVwzhVpQ/v6BWvfGTGePqfxoSvbht9SXl8w5t30wzzFQsCRofU2oSXE0CUN3 bzhCTDfbvtBr28QLuyazE2cu0EqByDeUB+YAIoH0aYloiydSwJa2Xnw2C5HZWx9GdID6cqVHfsR FsbzZd9fpGgWh4x3PBCSVuccNMVkJn4b5wN8j/9wu1/r4/XVqaUJ8sNR5eLsM7dC9W9D3u6R9TS T50brA307dBI1MJdIhAYypM7BXN1zc/Q4hzQF7tBbNG7vvkGb7kXc9+360EUs4wGMkC+5tfIUHK m5H0IdjBBIdxq1tG1dR9RXn4g0xuGbMm1kJnQPPksS85mNBpXlENtb6z+B6o5QaMFXm24qauaXj ktosGQEaEudELfMr8XbixQXIM1uA3oq79AOgyE7QZqZnRWdLktC+E07bvnGOwU6Hv6tImaZGL3s oKzCAowVEk9NvCfh6CkWKj9QmjUMhloUTdj5OEot0xOKMFTX9tcL0cOJV69tSmJxpQXXMI/Ec2c 0i6l8GQQE9dSjtWedVqyPOOv9FS1XCUl0w+s4bbDUdmN2JU10dMGs7aPlcszHmfmdHct3wk4BeU VieFCS6sUuRfPjzbA X-Received: by 2002:a05:6870:d290:b0:456:1edc:86f8 with SMTP id 586e51a60fabf-4599f117053mr4289358fac.15.1785952166703; Wed, 05 Aug 2026 10:49:26 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-4599dd615ecsm3281704fac.0.2026.08.05.10.49.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 05 Aug 2026 10:49:26 -0700 (PDT) From: James Hilliard Date: Wed, 05 Aug 2026 11:49:15 -0600 Subject: [PATCH net-next v3 4/4] net: mdio: support dynamic OF device changes Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260805-submit-mdio-of-dynamic-v2-v3-4-16f3cc893b0e@gmail.com> References: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> In-Reply-To: <20260805-submit-mdio-of-dynamic-v2-v3-0-16f3cc893b0e@gmail.com> To: Andrew Lunn , Heiner Kallweit , Russell King , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Florian Fainelli , Richard Cochran , Rob Herring , Saravana Kannan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 MDIO buses enumerate firmware children only when the bus is registered. A later DT overlay or status transition is ignored, unlike I2C and SPI. Add an OF reconfiguration notifier. On addition, create fixed-address PHYs and generic MDIO devices, scan free addresses for legacy PHY nodes without reg, and recurse into Ethernet PHY packages. Track populated nodes so static enumeration, address scanning and notifier-driven enumeration cannot create duplicates. Walk every package child and report the first population error without undoing devices which may already have acquired consumers. Only scan nodes which omit reg; reject malformed fixed addresses instead of treating them as legacy addressless PHYs. Serialize static and notifier-driven population while allowing a PHY probe to enable another node on the same bus. Check the node's current state after taking that lock so an older notification cannot override a newer status change. On removal, refuse to unregister attached or in-flight PHYs and find the object by its OF node. This also covers PHYs found by address scanning. Treat all PHYs in a package as one removal scope, so removing one member cannot invalidate state used by an attached peer. Preflight overlay removal while it can still be refused; normal changeset errors are propagated so the caller can restore the previous state. Ignore unrelated nodes and MDIO buses which have already disappeared when the global overlay preflight walks a changeset. Keep overlay preflight outside the global MDIO reconfiguration lock. The OF core invokes PRE_REMOVE while holding its own mutex, and an MDIO device probe running under the reconfiguration lock may apply an OF changeset. Use a per-bus removal transaction to block new registration and attachment while checking the map. Starting that exclusive transaction fails when another map operation is active, so refuse the overlay removal rather than waiting under the OF mutex; the caller can retry after that operation finishes. Hold a core MDIO change transaction across discovery and registration so bus teardown cannot race the MDIO reads used to identify a PHY. Compile the notifier only with CONFIG_OF_DYNAMIC and the overlay preflight only with CONFIG_OF_OVERLAY. Signed-off-by: James Hilliard --- Changes v2 -> v3: - do not block overlays containing unrelated non-MDIO nodes - do not block overlays whose MDIO bus is no longer present - use READ_ONCE() and WRITE_ONCE() for recursive lock-owner accesses - use the common checked MDIO device-removal callback - avoid an OF/MDIO lock inversion during overlay preflight - refuse overlay removal while another MDIO map change is active - propagate removal-transaction conflicts from the OF notifier - do not scan nodes with a malformed reg property - treat a populated node associated with another MDIO bus as busy Changes v1 -> v2: - split from the ACx00 series - move the PHY declaration to function scope for netdev style --- drivers/net/mdio/of_mdio.c | 454 +++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 445 insertions(+), 9 deletions(-) diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c index 051e449bbe7c..9e0b2f29959e 100644 --- a/drivers/net/mdio/of_mdio.c +++ b/drivers/net/mdio/of_mdio.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,8 @@ #include #include #include +#include +#include =20 #define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */ =20 @@ -27,6 +30,52 @@ MODULE_AUTHOR("Grant Likely "= ); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("OpenFirmware MDIO bus (Ethernet PHY) accessors"); =20 +#if IS_ENABLED(CONFIG_OF_DYNAMIC) +/* + * OF changes can nest when probing one MDIO device enables another node on + * the same bus. Serialize independent changes while allowing that nesting. + */ +static DEFINE_MUTEX(of_mdio_reconfig_mutex); +static struct task_struct *of_mdio_reconfig_owner; +static unsigned int of_mdio_reconfig_depth; + +static void of_mdio_reconfig_lock(void) +{ + if (!mutex_trylock(&of_mdio_reconfig_mutex)) { + if (READ_ONCE(of_mdio_reconfig_owner) =3D=3D current) { + of_mdio_reconfig_depth++; + return; + } + mutex_lock(&of_mdio_reconfig_mutex); + } + + WARN_ON_ONCE(READ_ONCE(of_mdio_reconfig_owner)); + WARN_ON_ONCE(of_mdio_reconfig_depth); + WRITE_ONCE(of_mdio_reconfig_owner, current); + of_mdio_reconfig_depth =3D 1; +} + +static void of_mdio_reconfig_unlock(void) +{ + WARN_ON_ONCE(READ_ONCE(of_mdio_reconfig_owner) !=3D current); + WARN_ON_ONCE(!of_mdio_reconfig_depth); + + if (--of_mdio_reconfig_depth) + return; + + WRITE_ONCE(of_mdio_reconfig_owner, NULL); + mutex_unlock(&of_mdio_reconfig_mutex); +} +#else +static inline void of_mdio_reconfig_lock(void) +{ +} + +static inline void of_mdio_reconfig_unlock(void) +{ +} +#endif + /* Extract the clause 22 phy ID from the compatible string of the form * ethernet-phy-idAAAA.BBBB */ static int of_get_phy_id(struct device_node *device, u32 *phy_id) @@ -80,10 +129,20 @@ static int of_mdiobus_register_device(struct mii_bus *= mdio, static int of_mdiobus_register_child(struct mii_bus *mdio, struct device_node *child, u32 addr) { + int rc; + + if (of_node_test_and_set_flag(child, OF_POPULATED)) + return 0; + if (of_mdiobus_child_is_phy(child)) - return of_mdiobus_register_phy(mdio, child, addr); + rc =3D of_mdiobus_register_phy(mdio, child, addr); + else + rc =3D of_mdiobus_register_device(mdio, child, addr); + + if (rc) + of_node_clear_flag(child, OF_POPULATED); =20 - return of_mdiobus_register_device(mdio, child, addr); + return rc; } =20 /* The following is a list of PHY compatible strings which appear in @@ -255,13 +314,21 @@ int __of_mdiobus_register(struct mii_bus *mdio, struc= t device_node *np, if (rc) return rc; =20 + of_mdio_reconfig_lock(); + rc =3D mdiobus_device_change_begin(mdio, false); + if (rc) { + of_mdio_reconfig_unlock(); + mdiobus_unregister(mdio); + return rc; + } + /* Loop over the child nodes and register a phy_device for each phy */ rc =3D __of_mdiobus_parse_phys(mdio, np, &scanphys); if (rc) - goto unregister; + goto out_change; =20 if (!scanphys) - return 0; + goto out_change; =20 /* auto scan for PHYs with empty reg property */ for_each_available_child_of_node(np, child) { @@ -272,19 +339,388 @@ int __of_mdiobus_register(struct mii_bus *mdio, stru= ct device_node *np, =20 rc =3D of_mdiobus_scan_phy(mdio, child); if (rc && rc !=3D -ENODEV) - goto put_unregister; + goto put_child; + rc =3D 0; } =20 - return 0; +out_change: + mdiobus_device_change_end(mdio, false); + of_mdio_reconfig_unlock(); + if (!rc) + return 0; =20 -put_unregister: - of_node_put(child); -unregister: mdiobus_unregister(mdio); return rc; + +put_child: + of_node_put(child); + goto out_change; } EXPORT_SYMBOL(__of_mdiobus_register); =20 +#if IS_ENABLED(CONFIG_OF_DYNAMIC) +static bool of_mdiobus_node_is_available(struct device_node *node) +{ + return !of_node_check_flag(node, OF_DETACHED) && + of_device_is_available(node); +} + +static int of_mdiobus_add_node(struct mii_bus *mdio, + struct device_node *node) +{ + struct device_node *child; + int addr, rc, ret =3D 0; + + if (!of_mdiobus_node_is_available(node)) + return 0; + + if (of_node_name_eq(node, "ethernet-phy-package")) { + if (!of_property_present(node, "reg")) + return 0; + + for_each_available_child_of_node(node, child) { + rc =3D of_mdiobus_add_node(mdio, child); + if (rc && rc !=3D -ENODEV && !ret) + ret =3D rc; + } + + return ret; + } + + addr =3D of_mdio_parse_addr(&mdio->dev, node); + if (addr < 0) { + if (of_property_present(node, "reg")) + return addr; + + rc =3D of_mdiobus_scan_phy(mdio, node); + } else { + rc =3D of_mdiobus_register_child(mdio, node, addr); + } + + if (rc =3D=3D -ENODEV && addr >=3D 0) + dev_err(&mdio->dev, + "MDIO device at address %d is missing.\n", addr); + + return rc; +} + +static bool of_mdiobus_node_is_busy(struct mii_bus *mdio, + struct device_node *node) +{ + struct mdio_device *mdiodev; + struct phy_device *phydev; + bool busy =3D false; + + if (of_node_name_eq(node, "ethernet-phy-package")) { + for_each_child_of_node_scoped(node, child) { + if (of_mdiobus_node_is_busy(mdio, child)) + return true; + } + + return false; + } + + if (!of_node_check_flag(node, OF_POPULATED)) + return false; + + mdiodev =3D of_mdio_find_device(node); + if (!mdiodev) + return true; + if (mdiodev->bus !=3D mdio) { + put_device(&mdiodev->dev); + return true; + } + + mutex_lock(&mdio->mdio_map_lock); + busy =3D mdio->mdio_map[mdiodev->addr] !=3D mdiodev || + (mdio->mdio_map_pending & BIT(mdiodev->addr)); + if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) { + phydev =3D to_phy_device(&mdiodev->dev); + if (phydev->attached) { + busy =3D true; + dev_warn(&mdiodev->dev, + "cannot remove an attached PHY; remove its consumer first\n"); + } + } + mutex_unlock(&mdio->mdio_map_lock); + put_device(&mdiodev->dev); + + return busy; +} + +static struct device_node * +of_mdiobus_get_removal_scope(struct device_node *node) +{ + struct device_node *parent; + + if (of_node_name_eq(node, "ethernet-phy-package")) + return of_node_get(node); + + parent =3D of_get_parent(node); + if (of_node_name_eq(parent, "ethernet-phy-package")) + return parent; + + of_node_put(parent); + return of_node_get(node); +} + +static int of_mdiobus_remove_node(struct mii_bus *mdio, + struct device_node *node) +{ + struct mdio_device *mdiodev; + struct device_node *child; + int ret; + + if (of_node_name_eq(node, "ethernet-phy-package")) { + for_each_child_of_node(node, child) { + ret =3D of_mdiobus_remove_node(mdio, child); + if (ret) { + of_node_put(child); + return ret; + } + } + return 0; + } + + if (!of_node_check_flag(node, OF_POPULATED)) + return 0; + + /* The OF node lookup also covers PHYs found by address scanning. */ + mdiodev =3D of_mdio_find_device(node); + if (!mdiodev) + return -EBUSY; + if (mdiodev->bus !=3D mdio) { + put_device(&mdiodev->dev); + return -ENODEV; + } + + ret =3D mdiodev->device_remove(mdiodev, true); + if (!ret) + mdiodev->device_free(mdiodev); + put_device(&mdiodev->dev); + + return ret =3D=3D -ENODEV ? 0 : ret; +} + +static struct mii_bus *of_mdiobus_find_parent(struct device_node *node) +{ + struct device_node *parent, *bus_node; + struct mii_bus *mdio; + + parent =3D of_get_parent(node); + if (!parent) + return NULL; + + if (of_node_name_eq(parent, "ethernet-phy-package")) { + if (!of_device_is_available(parent) || + !of_property_present(parent, "reg")) { + of_node_put(parent); + return NULL; + } + + bus_node =3D of_get_parent(parent); + of_node_put(parent); + } else { + bus_node =3D parent; + } + + mdio =3D of_mdio_find_bus(bus_node); + of_node_put(bus_node); + + return mdio; +} + +#if IS_ENABLED(CONFIG_OF_OVERLAY) +/* Overlay entry notifier errors cannot stop removal after the tree change= d. */ +static bool of_mdiobus_live_node_is_busy(struct device_node *node) +{ + struct device_node *scope; + struct mii_bus *mdio; + bool busy; + + scope =3D of_mdiobus_get_removal_scope(node); + mdio =3D of_mdiobus_find_parent(scope); + /* Non-MDIO nodes and buses already removed cannot block an overlay. */ + if (!mdio) { + busy =3D false; + goto out_put_scope; + } + + busy =3D true; + if (!mdiobus_device_change_begin(mdio, true)) { + busy =3D of_mdiobus_node_is_busy(mdio, scope); + mdiobus_device_change_end(mdio, true); + } + put_device(&mdio->dev); + +out_put_scope: + of_node_put(scope); + return busy; +} + +static struct device_node * +of_mdiobus_overlay_target_child(struct device_node *target, + struct device_node *overlay_child) +{ + const char *name =3D kbasename(overlay_child->full_name); + struct device_node *child; + + for_each_child_of_node(target, child) { + if (!of_node_cmp(kbasename(child->full_name), name)) + return child; + } + + return NULL; +} + +static bool of_mdiobus_overlay_node_is_busy(struct device_node *overlay, + struct device_node *target, + bool added) +{ + struct device_node *overlay_child, *target_child; + bool busy, child_added; + + if ((added || of_property_present(overlay, "status")) && + of_mdiobus_live_node_is_busy(target)) + return true; + + for_each_child_of_node(overlay, overlay_child) { + target_child =3D of_mdiobus_overlay_target_child(target, + overlay_child); + if (!target_child) + continue; + + child_added =3D of_node_check_flag(target_child, OF_OVERLAY); + busy =3D of_mdiobus_overlay_node_is_busy(overlay_child, + target_child, child_added); + of_node_put(target_child); + if (busy) { + of_node_put(overlay_child); + return true; + } + } + + return false; +} + +static int of_mdiobus_overlay_notify(struct notifier_block *nb, + unsigned long action, void *arg) +{ + struct of_overlay_notify_data *nd =3D arg; + bool busy; + + if (action !=3D OF_OVERLAY_PRE_REMOVE) + return NOTIFY_OK; + + busy =3D of_mdiobus_overlay_node_is_busy(nd->overlay, nd->target, + false); + + return busy ? notifier_from_errno(-EBUSY) : NOTIFY_OK; +} + +static struct notifier_block of_mdio_overlay_notifier =3D { + .notifier_call =3D of_mdiobus_overlay_notify, +}; +#endif + +static int of_mdiobus_notify(struct notifier_block *nb, unsigned long acti= on, + void *arg) +{ + struct of_reconfig_data *rd =3D arg; + struct device_node *scope; + struct mii_bus *mdio; + enum of_reconfig_change change; + bool removing; + int rc, ret =3D NOTIFY_OK; + + of_mdio_reconfig_lock(); + change =3D of_reconfig_get_state_change(action, rd); + switch (change) { + case OF_RECONFIG_CHANGE_ADD: + /* A newer change may have made this notification stale. */ + if (!of_mdiobus_node_is_available(rd->dn)) + goto out_unlock; + removing =3D false; + break; + case OF_RECONFIG_CHANGE_REMOVE: + /* A newer change may have made this notification stale. */ + if (of_mdiobus_node_is_available(rd->dn)) + goto out_unlock; + removing =3D true; + break; + default: + goto out_unlock; + } + + mdio =3D of_mdiobus_find_parent(rd->dn); + if (!mdio) + goto out_unlock; + + rc =3D mdiobus_device_change_begin(mdio, removing); + if (rc) { + if (!removing) + ret =3D notifier_from_errno(-EPROBE_DEFER); + else if (rc !=3D -ENODEV) + ret =3D notifier_from_errno(rc); + goto out_put_mdio; + } + + if (!removing) { + rc =3D of_mdiobus_add_node(mdio, rd->dn); + } else { + /* The node may already be detached from its parent hierarchy. */ + scope =3D of_mdiobus_get_removal_scope(rd->dn); + if (of_mdiobus_node_is_busy(mdio, scope)) + rc =3D -EBUSY; + else + rc =3D of_mdiobus_remove_node(mdio, rd->dn); + of_node_put(scope); + } + + mdiobus_device_change_end(mdio, removing); + if (rc && (removing || rc !=3D -ENODEV)) + ret =3D notifier_from_errno(rc); + +out_put_mdio: + put_device(&mdio->dev); +out_unlock: + of_mdio_reconfig_unlock(); + + return ret; +} + +static struct notifier_block of_mdio_notifier =3D { + .notifier_call =3D of_mdiobus_notify, +}; + +static int __init of_mdio_init(void) +{ + int ret; + + ret =3D of_reconfig_notifier_register(&of_mdio_notifier); + if (ret) + return ret; + +#if IS_ENABLED(CONFIG_OF_OVERLAY) + ret =3D of_overlay_notifier_register(&of_mdio_overlay_notifier); + if (ret) + of_reconfig_notifier_unregister(&of_mdio_notifier); +#endif + + return ret; +} +module_init(of_mdio_init); + +static void __exit of_mdio_exit(void) +{ +#if IS_ENABLED(CONFIG_OF_OVERLAY) + of_overlay_notifier_unregister(&of_mdio_overlay_notifier); +#endif + of_reconfig_notifier_unregister(&of_mdio_notifier); +} +module_exit(of_mdio_exit); +#endif /* CONFIG_OF_DYNAMIC */ + /** * of_mdio_find_device - Given a device tree node, find the mdio_device * @np: pointer to the mdio_device's device tree node --=20 2.53.0