From patchwork Mon Aug 3 05:14:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 2900 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10]) by mxe881.netcup.net (Postfix) with ESMTPS id DE23D1C057E for ; Mon, 3 Aug 2026 07:20:15 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.234.253.10) smtp.mailfrom=linux-sunxi+bounces-24917-noreply=patchwork.local@lists.linux.dev smtp.helo=sea.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.234.253.10 as permitted sender) client-ip=172.234.253.10; envelope-from=linux-sunxi+bounces-24917-noreply=patchwork.local@lists.linux.dev; helo=sea.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 66E4130841D2 for ; Mon, 3 Aug 2026 05:15:42 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4832E31E84F; Mon, 3 Aug 2026 05:15:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="XHN0Sy1h" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-ot1-f47.google.com (mail-ot1-f47.google.com [209.85.210.47]) (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 A0F8E38A70A for ; Mon, 3 Aug 2026 05:15:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785734135; cv=none; b=nhb4q8sQU623t+Cu/6lCJRDwFbleeRglSPyJypEgAcXa3u1CZ0MUIGy1UHlnmKf6dSMC7md6IAYFwZrHFcoqsB8+ECjL9sHwjQdMJ+yrburZ0CCGOAl1qhnvqN4k+ZKMC3IW1bBsB4BnneECg8fJdBuzry/3zg+YgTKxtpvwPd8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785734135; c=relaxed/simple; bh=Tw9U9x3x5olXZaLQIjQpOA/JFbVYSe4nj6Dx94hoQbg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AVY+um5DqHWPvY3T8cchvGV2UosgxcbzBMAzFixH3a+2ZYeYDC1JgTYo2XaUF93ls6tDijnddjPVkYxPFij9uzMZNx8dmp7u8y6w0oUrTCfTJLrDJEsl7+LimBDESuP4NyaTFx+7AGGrvfFWZ6CtbzAcnb8EZJYVSU1X4GisdCg= 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=XHN0Sy1h; arc=none smtp.client-ip=209.85.210.47 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 Received: by mail-ot1-f47.google.com with SMTP id 46e09a7af769-7eb64371a2aso1680073a34.0 for ; Sun, 02 Aug 2026 22:15:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785734128; x=1786338928; darn=lists.linux.dev; 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=maRxG9UiuybqBbIKlnDyAqHdPuODzpVicj9v0akXa2o=; b=XHN0Sy1h2lu6xX+D570KZSXV85aQPcVZDLm2LzoNfW2xDGWV3YSqfn0YSQWlMqrQgL SrumggipUGvVQ6NYDtMFbLbKIhkaPPPLlxCI5OVZLcwREtUK0EeJfDAfQUgqRMev7ubE JzCkvdnyN/Kv0tOCifH87omccheqS3+9PVs0nVL2Dgg1OPGCbHnW4TjP6vgKOhBbZlps DmRlyZETjD1cIlLodEs/gK0NAFYs9RAf9xRHaETdDomrTVwjAZZm2l21oZ6pRvnMhz6o RI4R7MVFcthW/FVlLkRzuUlKpp8HIVLSXKueyPWpU8T3lljMbiFU9Lx0S/sVu/jDmXb1 upWg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785734128; x=1786338928; 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=maRxG9UiuybqBbIKlnDyAqHdPuODzpVicj9v0akXa2o=; b=c1FRSiugZOYrJ2Npk14bPngYuI06D2xI4LWxqXBXnDPNf8/9XInFtUyD0E7qSGzJse YsBWieGYxDPUT+FC15VFs0VheZLVk/dlmKFp159ydOoYfSfbayIRh3w7gd/ecDRL6Gjl 9i7McmS984murjQckfeO/5Mm5nusZHwy9uwry7ZIjyiPINWGX0d9achXLgRLFj0Fhqj2 CvEKKpoqpBXiqmq3sF0ObG8GnmtrMnYCDn4Ew/VQTvKTAdqFMOatATXYwSX3QWxai7k6 FlIdwOqRs5leNgVAGCxwWGzm+/EOciwF9KST2gu5LV43j49tkb2x0OfY5EpZ3mRNEQtF v+Jg== X-Forwarded-Encrypted: i=1; AHgh+Rob2WeHLORRiOxYEh69eStKEycV5UiOqGw7RpVCzOhI2uNey1gcniL1ZsHm3O1SMaEkHHvmXmg5dEWKyQ==@lists.linux.dev X-Gm-Message-State: AOJu0YxaXi9XRqDsum79NwzDJtfRfb/0tQzcu+kfyfRtf6HP0ho4ZKD2 iQ6UIj/BUgYx4ZYoJWCn4+1+ljKM//sXkJxxv2M5QfZEjFZNOwrfPD+M X-Gm-Gg: AR+sD12mxycKw1bTkmEXN7iYrf1kVN6FjoVNtZetWomwyVyylZYvUwRnkZTOyvxo5ME iqBIpw/2awTDQ6NuswiK9xYoocdA6TqEQrOHeSyhN9rLBhZH4fjFeOQqI+Jw2ZNI3DBZYV407Hh rL3QNx9N8rOXKD6E0xTLh5hpGt58xxC2+boSn79qBtywsTfniDZmV9d13U7S8i7FBVqGsnDrHB9 PoPru4diEPQgzwP1qziWhLEr95QA8uaw45xf0fDCzEYky7Lk7+J5YP606Lak/PGuKltr/4DfHom irYK0kRJk2ydQwj6aj1DMd/nPHmKTM+ESbjTwqt4+P88Fp1ZhPnoWhN6iQm2U1+1llhYeeJpYPp fj/Te2qiWhEf7IQFRA4Q4w1PxYjQycGN9XAtTA3vUB9fbeMXsE8oabPsU92thWhyLeGJ9uHhCS5 N+vTEiVjocNwWaeVT7CLySRMESYvm9fRPKNWR82fr5c5g9lb93QeoGNnJrkMXw/XaH1PmV/sNXB 3JBe5iaw9JtapMZ6XgwAAIQ9PE9fuViixLeDzqtJsd8v3/SA0pX2xBrISpF68oygS/nDKkIxr/n FbEUEDgGCqTmPNElHnN1n6SSNWgG3dcxJMBtjEq3HbTgk5AO8UVzEoEd44gmh/swZqfByawHWhF KFUoQUQTh7hCxycJ1 X-Received: by 2002:a05:6830:61ce:b0:7dc:3db6:f02 with SMTP id 46e09a7af769-7f196d51885mr15457605a34.9.1785734128311; Sun, 02 Aug 2026 22:15:28 -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 46e09a7af769-7f18f07a5e3sm6787108a34.15.2026.08.02.22.15.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 02 Aug 2026 22:15:27 -0700 (PDT) From: James Hilliard Date: Sun, 02 Aug 2026 23:14:16 -0600 Subject: [PATCH 06/21] net: mdio: support dynamic OF device changes Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260802-submit-acx00-of-dynamic-v1-v1-6-0a53cd9e21cc@gmail.com> References: <20260802-submit-acx00-of-dynamic-v1-v1-0-0a53cd9e21cc@gmail.com> In-Reply-To: <20260802-submit-acx00-of-dynamic-v1-v1-0-0a53cd9e21cc@gmail.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland , Andre Przywara , Richard Genoud , Maxime Ripard , Alexandre Torgue , Giuseppe Cavallaro , Jose Abreu , Maxime Chevallier , Maxime Coquelin , Andrew Lunn , Heiner Kallweit , Russell King , Saravana Kannan , Lee Jones , Heiko Stuebner Cc: netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-kernel@vger.kernel.org, linux-stm32@st-md-mailman.stormreply.com, mfd@lists.linux.dev, linux-rockchip@lists.infradead.org, James Hilliard X-Mailer: b4 0.15.2 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= 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. 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, reject attached or in-flight PHYs and unregister the object found 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. 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 --- drivers/net/mdio/of_mdio.c | 448 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 438 insertions(+), 10 deletions(-) diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c index 051e449bbe7c..a8313b3721f6 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 #define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */ @@ -27,6 +30,52 @@ MODULE_AUTHOR("Grant Likely "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("OpenFirmware MDIO bus (Ethernet PHY) accessors"); +#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 (of_mdio_reconfig_owner == current) { + of_mdio_reconfig_depth++; + return; + } + mutex_lock(&of_mdio_reconfig_mutex); + } + + WARN_ON_ONCE(of_mdio_reconfig_owner); + WARN_ON_ONCE(of_mdio_reconfig_depth); + of_mdio_reconfig_owner = current; + of_mdio_reconfig_depth = 1; +} + +static void of_mdio_reconfig_unlock(void) +{ + WARN_ON_ONCE(of_mdio_reconfig_owner != current); + WARN_ON_ONCE(!of_mdio_reconfig_depth); + + if (--of_mdio_reconfig_depth) + return; + + 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) @@ -44,7 +93,7 @@ int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy, EXPORT_SYMBOL(of_mdiobus_phy_device_register); static int of_mdiobus_register_phy(struct mii_bus *mdio, - struct device_node *child, u32 addr) + struct device_node *child, u32 addr) { return fwnode_mdiobus_register_phy(mdio, of_fwnode_handle(child), addr); } @@ -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 = of_mdiobus_register_phy(mdio, child, addr); + else + rc = of_mdiobus_register_device(mdio, child, addr); - return of_mdiobus_register_device(mdio, child, addr); + if (rc) + of_node_clear_flag(child, OF_POPULATED); + + return rc; } /* The following is a list of PHY compatible strings which appear in @@ -255,13 +314,21 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np, if (rc) return rc; + of_mdio_reconfig_lock(); + rc = 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 = __of_mdiobus_parse_phys(mdio, np, &scanphys); if (rc) - goto unregister; + goto out_change; if (!scanphys) - return 0; + goto out_change; /* auto scan for PHYs with empty reg property */ for_each_available_child_of_node(np, child) { @@ -272,19 +339,380 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np, rc = of_mdiobus_scan_phy(mdio, child); if (rc && rc != -ENODEV) - goto put_unregister; + goto put_child; + rc = 0; } - return 0; +out_change: + mdiobus_device_change_end(mdio, false); + of_mdio_reconfig_unlock(); + if (!rc) + return 0; -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); +#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 = 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 = of_mdiobus_add_node(mdio, child); + if (rc && rc != -ENODEV && !ret) + ret = rc; + } + + return ret; + } + + addr = of_mdio_parse_addr(&mdio->dev, node); + if (addr < 0) + rc = of_mdiobus_scan_phy(mdio, node); + else + rc = of_mdiobus_register_child(mdio, node, addr); + + if (rc == -ENODEV && addr >= 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; + bool busy = 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 = of_mdio_find_device(node); + if (!mdiodev) + return true; + if (mdiodev->bus != mdio) { + put_device(&mdiodev->dev); + return false; + } + + mutex_lock(&mdio->mdio_map_lock); + busy = mdio->mdio_map[mdiodev->addr] != mdiodev || + (mdio->mdio_map_pending & BIT(mdiodev->addr)); + if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) { + struct phy_device *phydev = to_phy_device(&mdiodev->dev); + + if (phydev->attached) { + busy = 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 = 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 = 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 = of_mdio_find_device(node); + if (!mdiodev) + return -EBUSY; + if (mdiodev->bus != mdio) { + put_device(&mdiodev->dev); + return -ENODEV; + } + + ret = mdiodev->device_remove_dynamic(mdiodev); + if (!ret) + mdiodev->device_free(mdiodev); + put_device(&mdiodev->dev); + + return ret == -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 = 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 = of_get_parent(parent); + of_node_put(parent); + } else { + bus_node = parent; + } + + mdio = 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 changed. */ +static bool of_mdiobus_live_node_is_busy(struct device_node *node) +{ + struct device_node *scope; + struct mii_bus *mdio; + bool busy = true; + + scope = of_mdiobus_get_removal_scope(node); + mdio = of_mdiobus_find_parent(scope); + if (!mdio) + goto out_put_scope; + + if (!mdiobus_device_change_begin(mdio, true)) { + busy = 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 = 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 = of_mdiobus_overlay_target_child(target, + overlay_child); + if (!target_child) + continue; + + child_added = of_node_check_flag(target_child, OF_OVERLAY); + busy = 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 = arg; + bool busy; + + if (action != OF_OVERLAY_PRE_REMOVE) + return NOTIFY_OK; + + of_mdio_reconfig_lock(); + busy = of_mdiobus_overlay_node_is_busy(nd->overlay, nd->target, + false); + of_mdio_reconfig_unlock(); + + return busy ? notifier_from_errno(-EBUSY) : NOTIFY_OK; +} + +static struct notifier_block of_mdio_overlay_notifier = { + .notifier_call = of_mdiobus_overlay_notify, +}; +#endif + +static int of_mdiobus_notify(struct notifier_block *nb, unsigned long action, + void *arg) +{ + struct of_reconfig_data *rd = arg; + struct device_node *scope; + struct mii_bus *mdio; + enum of_reconfig_change change; + bool removing; + int rc, ret = NOTIFY_OK; + + of_mdio_reconfig_lock(); + change = 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 = 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 = true; + break; + default: + goto out_unlock; + } + + mdio = of_mdiobus_find_parent(rd->dn); + if (!mdio) + goto out_unlock; + + rc = mdiobus_device_change_begin(mdio, removing); + if (rc) { + if (!removing) + ret = notifier_from_errno(-EPROBE_DEFER); + goto out_put_mdio; + } + + if (!removing) { + rc = of_mdiobus_add_node(mdio, rd->dn); + } else { + /* The node may already be detached from its parent hierarchy. */ + scope = of_mdiobus_get_removal_scope(rd->dn); + if (of_mdiobus_node_is_busy(mdio, scope)) + rc = -EBUSY; + else + rc = of_mdiobus_remove_node(mdio, rd->dn); + of_node_put(scope); + } + + mdiobus_device_change_end(mdio, removing); + if (rc && (removing || rc != -ENODEV)) + ret = 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 = { + .notifier_call = of_mdiobus_notify, +}; + +static int __init of_mdio_init(void) +{ + int ret; + + ret = of_reconfig_notifier_register(&of_mdio_notifier); + if (ret) + return ret; + +#if IS_ENABLED(CONFIG_OF_OVERLAY) + ret = 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