This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 507b577a752 arch/mips/pic32mz: bring the Ethernet interface up without 
a link.
507b577a752 is described below

commit 507b577a7522a00da0dedf383ee8d4ff3ef4392a
Author: Daniel P. Carvalho <[email protected]>
AuthorDate: Sat Oct 24 14:15:40 2026 -0300

    arch/mips/pic32mz: bring the Ethernet interface up without a link.
    
    pic32mz_ifup() waited for the PHY auto-negotiation in a loop that takes
    more than ten seconds to give up, and failed if there was no link
    partner.  An interface with no cable could not be brought up at all, nor
    could anything that depends on it, such as a bridge that has to start
    with the board.
    
    Bound the wait to four seconds and treat the timeout as "no link": the
    interface comes up with the carrier off.  A work item polls the PHY once
    a second.  When the link comes up it reads the negotiated mode,
    configures the MAC for it and turns the carrier on; when the link goes
    down it turns the carrier off.  The PHY interrupt is not used because it
    is not wired on every board.
    
    The code that reads the negotiated speed and duplex moves into
    pic32mz_phygetmode() so that ifup and the poller share it.  The poller
    is only cancelled by ifdown, not waited for, because the carrier
    functions take the device lock that ifdown is called with.
    
    Tested on an EV49N51A bridging a Wi-Fi station to a host: with the
    cable connected at boot the bridge works as before; with no cable at
    boot the bridge comes up, the interface is not running, and plugging
    the cable in turns it on and traffic flows (ping 5/5); with a station
    pinging the host through the bridge, two cable unplug and plug cycles
    were recovered with no intervention.
    
    Assisted-by: Claude Code:claude-sonnet-5-5
    Signed-off-by: Daniel P. Carvalho <[email protected]>
---
 arch/mips/src/pic32mz/pic32mz_ethernet.c | 354 ++++++++++++++++++++++---------
 1 file changed, 248 insertions(+), 106 deletions(-)

diff --git a/arch/mips/src/pic32mz/pic32mz_ethernet.c 
b/arch/mips/src/pic32mz/pic32mz_ethernet.c
index 5d4a7162bd0..ea543c5c8bb 100644
--- a/arch/mips/src/pic32mz/pic32mz_ethernet.c
+++ b/arch/mips/src/pic32mz/pic32mz_ethernet.c
@@ -41,6 +41,7 @@
 #include <nuttx/arch.h>
 #include <nuttx/wdog.h>
 #include <nuttx/wqueue.h>
+#include <nuttx/signal.h>
 #include <nuttx/mutex.h>
 #include <nuttx/net/mii.h>
 #include <nuttx/net/netconfig.h>
@@ -207,6 +208,13 @@
 
 #define PIC32MZ_MIITIMEOUT     (666666)
 
+/* How long ifup waits for the auto-negotiation, and how often the PHY is
+ * polled for link changes.
+ */
+
+#define PIC32MZ_ANEGTIMEOUT    MSEC2TICK(4000)
+#define PIC32MZ_LINKPOLL       MSEC2TICK(1000)
+
 /* Busy flag polls after an MII management command */
 
 #define PIC32MZ_MIIBUSY_POLLS  (1000)
@@ -384,6 +392,7 @@ struct pic32mz_driver_s
 #endif
 
   bool       pd_ifup;           /* true:ifup false:ifdown */
+  bool       pd_linkup;         /* true:link is up, carrier is on */
   uint8_t    pd_mode;           /* Speed/duplex */
 #ifdef PIC32MZ_HAVE_PHY
   uint8_t    pd_phyaddr;        /* PHY device address */
@@ -393,6 +402,7 @@ struct pic32mz_driver_s
   uint32_t   pd_inten;          /* Shadow copy of INTEN register */
   struct wdog_s pd_txtimeout;   /* TX timeout timer */
   struct work_s pd_irqwork;     /* For deferring interrupt work to the work 
queue */
+  struct work_s pd_linkwork;    /* Polls the PHY for link changes */
   mutex_t    pd_lock;           /* Protects the descriptor rings */
 
   /* The packet being sent by each TX descriptor, kept in the descriptor
@@ -540,7 +550,9 @@ static inline int pic32mz_phyautoneg(uint8_t phyaddr);
 #  else
 static int pic32mz_phymode(uint8_t phyaddr, uint8_t mode);
 #  endif
+static int pic32mz_phygetmode(struct pic32mz_driver_s *priv);
 static inline int pic32mz_phyinit(struct pic32mz_driver_s *priv);
+static void pic32mz_linkwork(void *arg);
 #  if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT)
 static int pic32mz_phyintenable(struct pic32mz_driver_s *priv);
 #  endif
@@ -2354,7 +2366,21 @@ static int pic32mz_ifup(struct netdev_lowerhalf_s *dev)
   up_enable_irq(PIC32MZ_IRQ_ETH);
 #endif
 
-  netdev_lower_carrier_on(dev);
+  if (priv->pd_linkup)
+    {
+      netdev_lower_carrier_on(dev);
+    }
+  else
+    {
+      netdev_lower_carrier_off(dev);
+    }
+
+#ifdef PIC32MZ_HAVE_PHY
+  /* Follow the link from now on */
+
+  work_queue(ETHWORK, &priv->pd_linkwork, pic32mz_linkwork, priv,
+             PIC32MZ_LINKPOLL);
+#endif
 
   return OK;
 }
@@ -2389,9 +2415,10 @@ static int pic32mz_ifdown(struct netdev_lowerhalf_s *dev)
   up_disable_irq(PIC32MZ_IRQ_ETH);
 #endif
 
-  /* Cancel the TX timeout timers */
+  /* Cancel the TX timeout timers and stop polling the link */
 
   wd_cancel(&priv->pd_txtimeout);
+  work_cancel(ETHWORK, &priv->pd_linkwork);
 
   /* Reset the device and mark it as down. */
 
@@ -2845,16 +2872,19 @@ static inline int pic32mz_phyreset(uint8_t phyaddr)
 #if defined(PIC32MZ_HAVE_PHY) && defined(CONFIG_PIC32MZ_PHY_AUTONEG)
 static inline int pic32mz_phyautoneg(uint8_t phyaddr)
 {
-  int32_t timeout;
+  clock_t start;
   uint16_t phyreg;
 
   /* Start auto-negotiation */
 
   pic32mz_phywrite(phyaddr, MII_MCR, MII_MCR_ANENABLE | MII_MCR_ANRESTART);
 
-  /* Wait for autonegotiation to complete */
+  /* Wait for autonegotiation to complete.  It never does without a link
+   * partner, so the wait is bounded.
+   */
 
-  for (timeout = PIC32MZ_MIITIMEOUT; timeout > 0; timeout--)
+  start = clock_systime_ticks();
+  do
     {
       /* Check if auto-negotiation has completed */
 
@@ -2865,9 +2895,12 @@ static inline int pic32mz_phyautoneg(uint8_t phyaddr)
 
           return OK;
         }
+
+      nxsig_usleep(10000);
     }
+  while (clock_systime_ticks() - start < PIC32MZ_ANEGTIMEOUT);
 
-  nerr("ERROR: Auto-negotiation failed. MSR: %04x\n", phyreg);
+  nwarn("WARNING: Auto-negotiation did not complete. MSR: %04x\n", phyreg);
   return -ETIMEDOUT;
 }
 #endif
@@ -2951,6 +2984,197 @@ static int pic32mz_phymode(uint8_t phyaddr, uint8_t 
mode)
 }
 #endif
 
+/****************************************************************************
+ * Function: pic32mz_phygetmode
+ *
+ * Description:
+ *   Read the speed and duplex mode that the PHY settled on and save it in
+ *   priv->pd_mode.
+ *
+ * Input Parameters:
+ *   priv - Pointer to EMAC device driver structure
+ *
+ * Returned Value:
+ *   OK on success; a negated errno on failure
+ *
+ ****************************************************************************/
+
+#ifdef PIC32MZ_HAVE_PHY
+static int pic32mz_phygetmode(struct pic32mz_driver_s *priv)
+{
+#if defined(CONFIG_ETH0_PHY_KS8721) || defined(CONFIG_ETH0_PHY_DP83848C)
+  uint16_t phyreg;
+#endif
+  uint8_t phyaddr = priv->pd_phyaddr;
+
+#if defined(CONFIG_ETH0_PHY_KS8721)
+  phyreg = pic32mz_phyread(phyaddr, MII_KS8721_10BTCR);
+
+  switch (phyreg & KS8721_10BTCR_MODE_MASK)
+    {
+      case KS8721_10BTCR_MODE_10BTHD:  /* 10BASE-T half duplex */
+        priv->pd_mode = PIC32MZ_10BASET_HD;
+        break;
+      case KS8721_10BTCR_MODE_100BTHD: /* 100BASE-T half duplex */
+        priv->pd_mode = PIC32MZ_100BASET_HD;
+        break;
+      case KS8721_10BTCR_MODE_10BTFD: /* 10BASE-T full duplex */
+        priv->pd_mode = PIC32MZ_10BASET_FD;
+        break;
+      case KS8721_10BTCR_MODE_100BTFD: /* 100BASE-T full duplex */
+        priv->pd_mode = PIC32MZ_100BASET_FD;
+        break;
+      default:
+        nerr("ERROR: Unrecognized mode: %04x\n", phyreg);
+        return -ENODEV;
+    }
+#elif defined(CONFIG_ETH0_PHY_DP83848C)
+  phyreg = pic32mz_phyread(phyaddr, MII_DP83848C_STS);
+
+  /* Configure for full/half duplex mode and speed */
+
+  switch (phyreg & 0x0006)
+    {
+      case 0x0000:
+        priv->pd_mode = PIC32MZ_100BASET_HD;
+        break;
+      case 0x0002:
+        priv->pd_mode = PIC32MZ_10BASET_HD;
+        break;
+      case 0x0004:
+        priv->pd_mode = PIC32MZ_100BASET_FD;
+        break;
+      case 0x0006:
+        priv->pd_mode = PIC32MZ_10BASET_FD;
+        break;
+      default:
+        nerr("ERROR: Unrecognized mode: %04x\n", phyreg);
+        return -ENODEV;
+    }
+#elif defined(CONFIG_ETH0_PHY_LAN8720) || defined(CONFIG_ETH0_PHY_LAN8740) ||\
+      defined(CONFIG_ETH0_PHY_LAN8740A)
+  {
+    uint16_t advertise;
+    uint16_t lpa;
+
+    up_udelay(500);
+    advertise = pic32mz_phyread(phyaddr, MII_ADVERTISE);
+    lpa       = pic32mz_phyread(phyaddr, MII_LPA);
+
+    /* Check for 100BASETX full duplex */
+
+    if ((advertise & MII_ADVERTISE_100BASETXFULL) != 0 &&
+        (lpa & MII_LPA_100BASETXFULL) != 0)
+      {
+        priv->pd_mode = PIC32MZ_100BASET_FD;
+      }
+
+    /* Check for 100BASETX half duplex */
+
+    else if ((advertise & MII_ADVERTISE_100BASETXHALF) != 0 &&
+             (lpa & MII_LPA_100BASETXHALF) != 0)
+      {
+        priv->pd_mode = PIC32MZ_100BASET_HD;
+      }
+
+    /* Check for 10BASETX full duplex */
+
+    else if ((advertise & MII_ADVERTISE_10BASETXFULL) != 0 &&
+             (lpa & MII_LPA_10BASETXFULL) != 0)
+      {
+        priv->pd_mode = PIC32MZ_10BASET_FD;
+      }
+
+    /* Check for 10BASETX half duplex */
+
+    else if ((advertise & MII_ADVERTISE_10BASETXHALF) != 0 &&
+             (lpa & MII_LPA_10BASETXHALF) != 0)
+      {
+        priv->pd_mode = PIC32MZ_10BASET_HD;
+      }
+    else
+      {
+        nerr("ERROR: Unrecognized mode: %04x\n", lpa);
+        return -ENODEV;
+      }
+  }
+#else
+#  warning "PHY Unknown: speed and duplex are bogus"
+#endif
+
+  ninfo("%dBase-T %s duplex\n",
+        (priv->pd_mode & PIC32MZ_SPEED_MASK) ==
+          PIC32MZ_SPEED_100 ? 100 : 10,
+        (priv->pd_mode & PIC32MZ_DUPLEX_MASK) ==
+          PIC32MZ_DUPLEX_FULL ?"full" : "half");
+
+  return OK;
+}
+
+/****************************************************************************
+ * Function: pic32mz_linkwork
+ *
+ * Description:
+ *   Poll the PHY for link changes.  The PHY interrupt is not available on
+ *   every board.  When the link comes up, configure the MAC for the mode
+ *   that was negotiated and turn the carrier on; turn it off when the link
+ *   goes down.
+ *
+ * Input Parameters:
+ *   arg - The argument passed when work_queue() was called.
+ *
+ * Assumptions:
+ *   pic32mz_ifdown() does not wait for this work, which could deadlock on
+ *   the network device lock: it only cancels it and clears pd_ifup.
+ *
+ ****************************************************************************/
+
+static void pic32mz_linkwork(void *arg)
+{
+  struct pic32mz_driver_s *priv = (struct pic32mz_driver_s *)arg;
+  uint8_t phyaddr = priv->pd_phyaddr;
+  uint16_t phyreg;
+  bool up;
+
+  if (!priv->pd_ifup)
+    {
+      return;
+    }
+
+  /* The link status bit latches a link failure: read it twice to get the
+   * current state.
+   */
+
+  phyreg = pic32mz_phyread(phyaddr, MII_MSR);
+  phyreg = pic32mz_phyread(phyaddr, MII_MSR);
+  up     = (phyreg & MII_MSR_LINKSTATUS) != 0;
+
+#ifdef CONFIG_PIC32MZ_PHY_AUTONEG
+  /* The partner capabilities are only valid when the negotiation is done */
+
+  up = up && (phyreg & MII_MSR_ANEGCOMPLETE) != 0;
+#endif
+
+  if (up && !priv->pd_linkup)
+    {
+      if (pic32mz_phygetmode(priv) == OK)
+        {
+          pic32mz_macmode(priv->pd_mode);
+          priv->pd_linkup = true;
+          netdev_lower_carrier_on(&priv->pd_dev);
+        }
+    }
+  else if (!up && priv->pd_linkup)
+    {
+      priv->pd_linkup = false;
+      netdev_lower_carrier_off(&priv->pd_dev);
+    }
+
+  work_queue(ETHWORK, &priv->pd_linkwork, pic32mz_linkwork, priv,
+             PIC32MZ_LINKPOLL);
+}
+#endif
+
 /****************************************************************************
  * Function: pic32mz_phyinit
  *
@@ -3115,7 +3339,18 @@ static inline int pic32mz_phyinit(struct 
pic32mz_driver_s *priv)
   /* Then perform the auto-negotiation */
 
   ret = pic32mz_phyautoneg(phyaddr);
-  if (ret < 0)
+  if (ret == -ETIMEDOUT)
+    {
+      /* No link partner, such as an unplugged cable: not an error.  The
+       * interface comes up without a carrier and pic32mz_linkwork()
+       * configures the MAC when the link comes up.
+       */
+
+      priv->pd_mode   = PIC32MZ_MODE_DEFLT;
+      priv->pd_linkup = false;
+      return OK;
+    }
+  else if (ret < 0)
     {
       return ret;
     }
@@ -3140,106 +3375,11 @@ static inline int pic32mz_phyinit(struct 
pic32mz_driver_s *priv)
 
   /* Check configuration */
 
-#if defined(CONFIG_ETH0_PHY_KS8721)
-  phyreg = pic32mz_phyread(phyaddr, MII_KS8721_10BTCR);
-
-  switch (phyreg & KS8721_10BTCR_MODE_MASK)
-    {
-      case KS8721_10BTCR_MODE_10BTHD:  /* 10BASE-T half duplex */
-        priv->pd_mode = PIC32MZ_10BASET_HD;
-        break;
-      case KS8721_10BTCR_MODE_100BTHD: /* 100BASE-T half duplex */
-        priv->pd_mode = PIC32MZ_100BASET_HD;
-        break;
-      case KS8721_10BTCR_MODE_10BTFD: /* 10BASE-T full duplex */
-        priv->pd_mode = PIC32MZ_10BASET_FD;
-        break;
-      case KS8721_10BTCR_MODE_100BTFD: /* 100BASE-T full duplex */
-        priv->pd_mode = PIC32MZ_100BASET_FD;
-        break;
-      default:
-        nerr("ERROR: Unrecognized mode: %04x\n", phyreg);
-        return -ENODEV;
-    }
-#elif defined(CONFIG_ETH0_PHY_DP83848C)
-  phyreg = pic32mz_phyread(phyaddr, MII_DP83848C_STS);
-
-  /* Configure for full/half duplex mode and speed */
-
-  switch (phyreg & 0x0006)
+  ret = pic32mz_phygetmode(priv);
+  if (ret < 0)
     {
-      case 0x0000:
-        priv->pd_mode = PIC32MZ_100BASET_HD;
-        break;
-      case 0x0002:
-        priv->pd_mode = PIC32MZ_10BASET_HD;
-        break;
-      case 0x0004:
-        priv->pd_mode = PIC32MZ_100BASET_FD;
-        break;
-      case 0x0006:
-        priv->pd_mode = PIC32MZ_10BASET_FD;
-        break;
-      default:
-        nerr("ERROR: Unrecognized mode: %04x\n", phyreg);
-        return -ENODEV;
+      return ret;
     }
-#elif defined(CONFIG_ETH0_PHY_LAN8720) || defined(CONFIG_ETH0_PHY_LAN8740) ||\
-      defined(CONFIG_ETH0_PHY_LAN8740A)
-  {
-    uint16_t advertise;
-    uint16_t lpa;
-
-    up_udelay(500);
-    advertise = pic32mz_phyread(phyaddr, MII_ADVERTISE);
-    lpa       = pic32mz_phyread(phyaddr, MII_LPA);
-
-    /* Check for 100BASETX full duplex */
-
-    if ((advertise & MII_ADVERTISE_100BASETXFULL) != 0 &&
-        (lpa & MII_LPA_100BASETXFULL) != 0)
-      {
-        priv->pd_mode = PIC32MZ_100BASET_FD;
-      }
-
-    /* Check for 100BASETX half duplex */
-
-    else if ((advertise & MII_ADVERTISE_100BASETXHALF) != 0 &&
-             (lpa & MII_LPA_100BASETXHALF) != 0)
-      {
-        priv->pd_mode = PIC32MZ_100BASET_HD;
-      }
-
-    /* Check for 10BASETX full duplex */
-
-    else if ((advertise & MII_ADVERTISE_10BASETXFULL) != 0 &&
-             (lpa & MII_LPA_10BASETXFULL) != 0)
-      {
-        priv->pd_mode = PIC32MZ_10BASET_FD;
-      }
-
-    /* Check for 10BASETX half duplex */
-
-    else if ((advertise & MII_ADVERTISE_10BASETXHALF) != 0 &&
-             (lpa & MII_LPA_10BASETXHALF) != 0)
-      {
-        priv->pd_mode = PIC32MZ_10BASET_HD;
-      }
-    else
-      {
-        nerr("ERROR: Unrecognized mode: %04x\n", phyreg);
-        return -ENODEV;
-      }
-  }
-#else
-#  warning "PHY Unknown: speed and duplex are bogus"
-#endif
-
-  ninfo("%dBase-T %s duplex\n",
-        (priv->pd_mode & PIC32MZ_SPEED_MASK) ==
-          PIC32MZ_SPEED_100 ? 100 : 10,
-        (priv->pd_mode & PIC32MZ_DUPLEX_MASK) ==
-          PIC32MZ_DUPLEX_FULL ?"full" : "half");
 
 #ifndef CONFIG_PIC32MZ_PHY_AUTONEG
   /* Set the fixed speed/duplex mode (again).  With auto-negotiation, the
@@ -3251,13 +3391,15 @@ static inline int pic32mz_phyinit(struct 
pic32mz_driver_s *priv)
   ret = pic32mz_phymode(phyaddr, priv->pd_mode);
 #endif
 
+  priv->pd_linkup = true;
   pic32mz_showmii(phyaddr, "After final configuration");
   return ret;
 }
 #else
 static inline int pic32mz_phyinit(struct pic32mz_driver_s *priv)
 {
-  priv->pd_mode = PIC32MZ_MODE_DEFLT;
+  priv->pd_mode   = PIC32MZ_MODE_DEFLT;
+  priv->pd_linkup = true;
   return OK;
 }
 #endif

Reply via email to