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

commit 9a0e278416df2dd34063f553448d9db68fe7ecf8
Author: Daniel P. Carvalho <[email protected]>
AuthorDate: Sat Oct 10 12:58:21 2026 -0300

    drivers/net: Add Ethernet bridge support.
    
    Add a transparent, learning Ethernet bridge as a network device of the
    upper half driver interface, like the VLAN device.  A bridge (br0) is
    created with the SIOCBRADDBR ioctl, and upper half devices are added to
    it as ports with SIOCBRADDIF.
    
    The upper half of a bridge port hands every received packet to the
    bridge, which learns the source address and forwards the packet to the
    other ports or to the network stack through the bridge device
    (netdev_lower_input()).  The transmit operation of the bridge device
    forwards the packets of the local host.  The TX quota of the bridge device
    follows the room of its ports, and a completed transmission of a port
    resumes the bridge device.
    
    There is no hook in the IP stack: devices that do not use the upper half
    interface cannot be ports.
    
    The nxstyle errors that were already in netdev_upperhalf.c are fixed.
    
    Assisted-by: Claude Code:claude-sonnet-5-5
    Signed-off-by: Daniel P. Carvalho <[email protected]>
---
 drivers/net/CMakeLists.txt           |    4 +
 drivers/net/Make.defs                |    4 +
 drivers/net/bridge.c                 | 1281 ++++++++++++++++++++++++++++++++++
 drivers/net/netdev_upperhalf.c       |  239 +++++--
 include/nuttx/net/bridge.h           |  126 ++++
 include/nuttx/net/netdev_lowerhalf.h |   55 ++
 net/Kconfig                          |   48 ++
 net/netdev/netdev_ioctl.c            |   10 +
 8 files changed, 1723 insertions(+), 44 deletions(-)

diff --git a/drivers/net/CMakeLists.txt b/drivers/net/CMakeLists.txt
index c8b9fcf1466..fac681c8182 100644
--- a/drivers/net/CMakeLists.txt
+++ b/drivers/net/CMakeLists.txt
@@ -69,6 +69,10 @@ if(CONFIG_NET)
     list(APPEND SRCS vlan.c)
   endif()
 
+  if(CONFIG_NET_BRIDGE)
+    list(APPEND SRCS bridge.c)
+  endif()
+
   if(CONFIG_NET_FTMAC100)
     list(APPEND SRCS ftmac100.c)
   endif()
diff --git a/drivers/net/Make.defs b/drivers/net/Make.defs
index 9dc64e5c641..d92b34aee11 100644
--- a/drivers/net/Make.defs
+++ b/drivers/net/Make.defs
@@ -70,6 +70,10 @@ ifeq ($(CONFIG_NET_VLAN),y)
   CSRCS += vlan.c
 endif
 
+ifeq ($(CONFIG_NET_BRIDGE),y)
+  CSRCS += bridge.c
+endif
+
 ifeq ($(CONFIG_NET_FTMAC100),y)
   CSRCS += ftmac100.c
 endif
diff --git a/drivers/net/bridge.c b/drivers/net/bridge.c
new file mode 100644
index 00000000000..25bb03e48e0
--- /dev/null
+++ b/drivers/net/bridge.c
@@ -0,0 +1,1281 @@
+/****************************************************************************
+ * drivers/net/bridge.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <net/if.h>
+
+#include <nuttx/clock.h>
+#include <nuttx/debug.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mm/iob.h>
+#include <nuttx/mutex.h>
+#include <nuttx/net/bridge.h>
+#include <nuttx/net/ethernet.h>
+#include <nuttx/net/ioctl.h>
+#include <nuttx/net/netdev_lowerhalf.h>
+#include <nuttx/wqueue.h>
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Locking.  Locks are always taken in this order, never the reverse:
+ *
+ *   g_bridge_lock -> port device lock -> bridge device lock -> br_lock
+ *
+ * g_bridge_lock serializes the configuration (ioctl and the unregistering
+ * of a port).  br_lock protects the filtering database and the port table,
+ * and serializes the transmission to the ports.  Nothing that takes a
+ * device lock is called with br_lock held.
+ */
+
+struct bridge_s;
+
+/* A bridge port */
+
+struct bridge_port_s
+{
+  FAR struct bridge_s *bp_bridge;         /* The bridge of this port */
+  FAR struct netdev_lowerhalf_s *bp_dev;  /* The port device, NULL = unused */
+};
+
+/* Like the filtering database, the used ports are always at the head of the
+ * port table, so the walks of the table stop at the first unused port.
+ */
+
+/* A filtering database entry.  The used entries are always at the head of
+ * the table, so the walks of the table stop at the first free entry.
+ */
+
+struct bridge_fdb_s
+{
+  FAR struct bridge_port_s *fdb_port;     /* Port of the address, NULL = free 
*/
+  clock_t fdb_time;                       /* Last time the address was seen */
+  uint8_t fdb_mac[ETHER_ADDR_LEN];        /* The learned MAC address */
+};
+
+/* A bridge */
+
+struct bridge_s
+{
+  struct netdev_lowerhalf_s br_dev;       /* The bridge device, must be first 
*/
+  FAR struct bridge_s *br_flink;          /* Next bridge in the list */
+  mutex_t br_lock;                        /* FDB, ports and TX lock */
+  struct bridge_port_s br_ports[CONFIG_NET_BRIDGE_MAX_PORTS];
+  struct bridge_fdb_s br_fdb[CONFIG_NET_BRIDGE_FDB_SIZE];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int bridge_ifup(FAR struct netdev_lowerhalf_s *dev);
+static int bridge_ifdown(FAR struct netdev_lowerhalf_s *dev);
+static int bridge_transmit(FAR struct netdev_lowerhalf_s *dev,
+                           FAR netpkt_t *pkt);
+static FAR netpkt_t *bridge_receive(FAR struct netdev_lowerhalf_s *dev);
+#ifdef CONFIG_NET_MCASTGROUP
+static int bridge_addmac(FAR struct netdev_lowerhalf_s *dev,
+                         FAR const uint8_t *mac);
+static int bridge_rmmac(FAR struct netdev_lowerhalf_s *dev,
+                        FAR const uint8_t *mac);
+#endif
+static void bridge_reclaim(FAR struct netdev_lowerhalf_s *dev);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct netdev_ops_s g_bridge_ops =
+{
+  bridge_ifup,     /* ifup */
+  bridge_ifdown,   /* ifdown */
+  bridge_transmit, /* transmit */
+  bridge_receive,  /* receive */
+#ifdef CONFIG_NET_MCASTGROUP
+  bridge_addmac,   /* addmac */
+  bridge_rmmac,    /* rmmac */
+#endif
+#ifdef CONFIG_NETDEV_IOCTL
+  NULL,            /* ioctl */
+#endif
+  bridge_reclaim   /* reclaim */
+};
+
+/* All bridges, protected by g_bridge_lock */
+
+static FAR struct bridge_s *g_bridges;
+static mutex_t g_bridge_lock = NXMUTEX_INITIALIZER;
+
+static const uint8_t g_zero_mac[ETHER_ADDR_LEN];
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: bridge_is_multicast
+ *
+ * Description:
+ *   Return true for group (multicast and broadcast) MAC addresses.
+ *
+ ****************************************************************************/
+
+static inline bool bridge_is_multicast(FAR const uint8_t *mac)
+{
+  return (mac[0] & 0x01) != 0;
+}
+
+/****************************************************************************
+ * Name: bridge_is_linklocal
+ *
+ * Description:
+ *   Return true for the IEEE 802.1D reserved group addresses
+ *   01-80-C2-00-00-00 to 01-80-C2-00-00-0F, which a bridge must not
+ *   forward.
+ *
+ ****************************************************************************/
+
+static inline bool bridge_is_linklocal(FAR const uint8_t *mac)
+{
+  return mac[0] == 0x01 && mac[1] == 0x80 && mac[2] == 0xc2 &&
+         mac[3] == 0x00 && mac[4] == 0x00 && (mac[5] & 0xf0) == 0x00;
+}
+
+/****************************************************************************
+ * Name: bridge_is_local
+ *
+ * Description:
+ *   Return true if a unicast address belongs to the bridge device or to one
+ *   of its ports.
+ *
+ * Assumptions:
+ *   Called with br_lock held.
+ *
+ ****************************************************************************/
+
+static bool bridge_is_local(FAR struct bridge_s *br, FAR const uint8_t *mac)
+{
+  int i;
+
+  if (memcmp(mac, br->br_dev.netdev.d_mac.ether.ether_addr_octet,
+             ETHER_ADDR_LEN) == 0)
+    {
+      return true;
+    }
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct netdev_lowerhalf_s *dev = br->br_ports[i].bp_dev;
+
+      if (dev == NULL)
+        {
+          break;
+        }
+
+      if (memcmp(mac, dev->netdev.d_mac.ether.ether_addr_octet,
+                 ETHER_ADDR_LEN) == 0)
+        {
+          return true;
+        }
+    }
+
+  return false;
+}
+
+/****************************************************************************
+ * Name: bridge_fdb_remove
+ *
+ * Description:
+ *   Release an entry, moving the last used entry into its place to keep the
+ *   used entries at the head of the table.
+ *
+ * Assumptions:
+ *   Called with br_lock held.
+ *
+ ****************************************************************************/
+
+static void bridge_fdb_remove(FAR struct bridge_s *br, int index)
+{
+  int last = index;
+
+  while (last + 1 < CONFIG_NET_BRIDGE_FDB_SIZE &&
+         br->br_fdb[last + 1].fdb_port != NULL)
+    {
+      last++;
+    }
+
+  br->br_fdb[index]         = br->br_fdb[last];
+  br->br_fdb[last].fdb_port = NULL;
+}
+
+/****************************************************************************
+ * Name: bridge_fdb_lookup
+ *
+ * Description:
+ *   Find the port of a learned MAC address.  Expired entries are released
+ *   on the way.
+ *
+ * Assumptions:
+ *   Called with br_lock held.
+ *
+ ****************************************************************************/
+
+static FAR struct bridge_port_s *
+bridge_fdb_lookup(FAR struct bridge_s *br, FAR const uint8_t *mac)
+{
+  clock_t now = clock_systime_ticks();
+  int i = 0;
+
+  while (i < CONFIG_NET_BRIDGE_FDB_SIZE)
+    {
+      FAR struct bridge_fdb_s *fdb = &br->br_fdb[i];
+
+      if (fdb->fdb_port == NULL)
+        {
+          break;
+        }
+
+      if (now - fdb->fdb_time > SEC2TICK(CONFIG_NET_BRIDGE_AGEING_TIME))
+        {
+          /* The last entry took the place of this one, check it too */
+
+          bridge_fdb_remove(br, i);
+          continue;
+        }
+
+      if (memcmp(fdb->fdb_mac, mac, ETHER_ADDR_LEN) == 0)
+        {
+          return fdb->fdb_port;
+        }
+
+      i++;
+    }
+
+  return NULL;
+}
+
+/****************************************************************************
+ * Name: bridge_fdb_learn
+ *
+ * Description:
+ *   Remember that a source MAC address was seen on a port.  When the
+ *   database is full, the least recently seen address is replaced.
+ *
+ * Assumptions:
+ *   Called with br_lock held.
+ *
+ ****************************************************************************/
+
+static void bridge_fdb_learn(FAR struct bridge_s *br,
+                             FAR struct bridge_port_s *port,
+                             FAR const uint8_t *mac)
+{
+  FAR struct bridge_fdb_s *victim = NULL;
+  clock_t now = clock_systime_ticks();
+  int i;
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_FDB_SIZE; i++)
+    {
+      FAR struct bridge_fdb_s *fdb = &br->br_fdb[i];
+
+      /* The first free entry ends the used ones, and is where to add */
+
+      if (fdb->fdb_port == NULL)
+        {
+          victim = fdb;
+          break;
+        }
+
+      if (memcmp(fdb->fdb_mac, mac, ETHER_ADDR_LEN) == 0)
+        {
+          if (fdb->fdb_port != port)
+            {
+              ninfo("%s: %02x:%02x:%02x:%02x:%02x:%02x moved to %s\n",
+                    br->br_dev.netdev.d_ifname, mac[0], mac[1], mac[2],
+                    mac[3], mac[4], mac[5], port->bp_dev->netdev.d_ifname);
+            }
+
+          fdb->fdb_port = port;
+          fdb->fdb_time = now;
+          return;
+        }
+
+      if (victim == NULL || now - fdb->fdb_time > now - victim->fdb_time)
+        {
+          victim = fdb;
+        }
+    }
+
+  DEBUGASSERT(victim != NULL);
+  memcpy(victim->fdb_mac, mac, ETHER_ADDR_LEN);
+  victim->fdb_port = port;
+  victim->fdb_time = now;
+}
+
+/****************************************************************************
+ * Name: bridge_update_quota
+ *
+ * Description:
+ *   The bridge device can send as many packets as the port with the least
+ *   room.  Publish that number as the TX quota of the bridge device, so
+ *   that the network stack stops polling the device when a port is full,
+ *   and resumes when bridge_txdone() is called.
+ *
+ * Assumptions:
+ *   Called with br_lock held.
+ *
+ ****************************************************************************/
+
+static void bridge_update_quota(FAR struct bridge_s *br)
+{
+  int quota = INT_MAX;
+  int i;
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct netdev_lowerhalf_s *dev = br->br_ports[i].bp_dev;
+      int avail;
+
+      if (dev == NULL)
+        {
+          break;
+        }
+
+      avail = atomic_read(&dev->quota_ptr[NETPKT_TX]);
+      if (avail < quota)
+        {
+          quota = avail;
+        }
+    }
+
+  atomic_set(&br->br_dev.quota_ptr[NETPKT_TX], quota == INT_MAX ? 0 : quota);
+}
+
+/****************************************************************************
+ * Name: bridge_clone
+ *
+ * Description:
+ *   Copy a packet, including the Ethernet header, which precedes the data.
+ *
+ ****************************************************************************/
+
+static FAR netpkt_t *bridge_clone(FAR struct bridge_s *br,
+                                  FAR netpkt_t *pkt)
+{
+  FAR struct netdev_lowerhalf_s *dev = &br->br_dev;
+  FAR netpkt_t *clone;
+
+  clone = iob_tryalloc(false);
+  if (clone == NULL)
+    {
+      return NULL;
+    }
+
+  iob_reserve(clone, CONFIG_NET_LL_GUARDSIZE);
+  if (iob_clone_partial(pkt, pkt->io_pktlen, 0, clone, 0, false,
+                        false) < 0)
+    {
+      iob_free_chain(clone);
+      return NULL;
+    }
+
+  memcpy(netpkt_getdata(dev, clone), netpkt_getdata(dev, pkt), ETH_HDRLEN);
+  return clone;
+}
+
+/****************************************************************************
+ * Name: bridge_port_send
+ *
+ * Description:
+ *   Send a packet on a port, or drop it.  Takes ownership of the packet.
+ *   The packet is not counted in the TX quota of the port when it comes
+ *   here, as it was not allocated by the port or by its upper half, so the
+ *   quota is taken like the upper half does for its own packets.
+ *
+ * Assumptions:
+ *   Called with br_lock held.
+ *
+ ****************************************************************************/
+
+static void bridge_port_send(FAR struct bridge_port_s *port,
+                             FAR netpkt_t *pkt)
+{
+  FAR struct netdev_lowerhalf_s *dev = port->bp_dev;
+  int ret;
+
+  if (!IFF_IS_UP(dev->netdev.d_flags) ||
+      netpkt_getdatalen(dev, pkt) > NETDEV_PKTSIZE(&dev->netdev))
+    {
+      goto drop;
+    }
+
+  if (atomic_read(&dev->quota_ptr[NETPKT_TX]) <= 0 &&
+      dev->ops->reclaim != NULL)
+    {
+      dev->ops->reclaim(dev);
+    }
+
+  if (atomic_sub(&dev->quota_ptr[NETPKT_TX], 1) <= 0)
+    {
+      atomic_add(&dev->quota_ptr[NETPKT_TX], 1);
+      goto drop;
+    }
+
+  NETDEV_TXPACKETS(&dev->netdev);
+
+  ret = dev->ops->transmit(dev, pkt);
+  if (ret < 0)
+    {
+      atomic_add(&dev->quota_ptr[NETPKT_TX], 1);
+      goto drop;
+    }
+
+  return;
+
+drop:
+  NETDEV_TXERRORS(&dev->netdev);
+  iob_free_chain(pkt);
+}
+
+/****************************************************************************
+ * Name: bridge_flood
+ *
+ * Description:
+ *   Send copies of a packet on all ports except the ingress port.
+ *
+ * Input Parameters:
+ *   br      - The bridge
+ *   inport  - The ingress port, NULL for a packet of the local host
+ *   pkt     - The packet
+ *   consume - True if the packet itself can be sent on the last port, in
+ *             which case the packet is owned by the bridge afterwards
+ *
+ * Returned Value:
+ *   True if the packet was consumed.  False if the caller still owns it
+ *   (always the case when consume is false, or when no port was found).
+ *
+ * Assumptions:
+ *   Called with br_lock held.
+ *
+ ****************************************************************************/
+
+static bool bridge_flood(FAR struct bridge_s *br,
+                         FAR struct bridge_port_s *inport,
+                         FAR netpkt_t *pkt, bool consume)
+{
+  FAR struct bridge_port_s *last = NULL;
+  int i;
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct bridge_port_s *port = &br->br_ports[i];
+      FAR netpkt_t *clone;
+
+      if (port->bp_dev == NULL)
+        {
+          break;
+        }
+
+      if (port == inport)
+        {
+          continue;
+        }
+
+      /* Send on the previous port, now that we know there is one more */
+
+      if (last != NULL)
+        {
+          clone = bridge_clone(br, pkt);
+          if (clone != NULL)
+            {
+              bridge_port_send(last, clone);
+            }
+          else
+            {
+              NETDEV_TXERRORS(&last->bp_dev->netdev);
+            }
+        }
+
+      last = port;
+    }
+
+  if (last == NULL)
+    {
+      return false;
+    }
+
+  if (consume)
+    {
+      bridge_port_send(last, pkt);
+      return true;
+    }
+
+  pkt = bridge_clone(br, pkt);
+  if (pkt != NULL)
+    {
+      bridge_port_send(last, pkt);
+    }
+  else
+    {
+      NETDEV_TXERRORS(&last->bp_dev->netdev);
+    }
+
+  return false;
+}
+
+/****************************************************************************
+ * Name: bridge_ifup
+ ****************************************************************************/
+
+static int bridge_ifup(FAR struct netdev_lowerhalf_s *dev)
+{
+  netdev_lower_carrier_on(dev);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: bridge_ifdown
+ ****************************************************************************/
+
+static int bridge_ifdown(FAR struct netdev_lowerhalf_s *dev)
+{
+  netdev_lower_carrier_off(dev);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: bridge_transmit
+ *
+ * Description:
+ *   Send a packet of the local host through the bridge.
+ *
+ ****************************************************************************/
+
+static int bridge_transmit(FAR struct netdev_lowerhalf_s *dev,
+                           FAR netpkt_t *pkt)
+{
+  FAR struct bridge_s *br = (FAR struct bridge_s *)dev;
+  FAR const uint8_t *dest = netpkt_getdata(dev, pkt);
+  FAR struct bridge_port_s *outport = NULL;
+
+  nxmutex_lock(&br->br_lock);
+
+  if (!bridge_is_multicast(dest))
+    {
+      outport = bridge_fdb_lookup(br, dest);
+    }
+
+  if (outport != NULL)
+    {
+      bridge_port_send(outport, pkt);
+    }
+  else if (!bridge_flood(br, NULL, pkt, true))
+    {
+      iob_free_chain(pkt);
+    }
+
+  /* The packet is gone, the quota of the bridge now follows the ports */
+
+  bridge_update_quota(br);
+  nxmutex_unlock(&br->br_lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: bridge_receive
+ ****************************************************************************/
+
+static FAR netpkt_t *bridge_receive(FAR struct netdev_lowerhalf_s *dev)
+{
+  /* The bridge device doesn't receive packets, its ports do. */
+
+  return NULL;
+}
+
+/****************************************************************************
+ * Name: bridge_addmac
+ *
+ * Description:
+ *   The ports receive all multicast packets, as they are flooded.  Let
+ *   them know the group too, in case the hardware filters.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_MCASTGROUP
+static int bridge_addmac(FAR struct netdev_lowerhalf_s *dev,
+                         FAR const uint8_t *mac)
+{
+  FAR struct bridge_s *br = (FAR struct bridge_s *)dev;
+  int i;
+
+  nxmutex_lock(&br->br_lock);
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct netdev_lowerhalf_s *port = br->br_ports[i].bp_dev;
+
+      if (port == NULL)
+        {
+          break;
+        }
+
+      if (port->ops->addmac != NULL)
+        {
+          port->ops->addmac(port, mac);
+        }
+    }
+
+  nxmutex_unlock(&br->br_lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: bridge_rmmac
+ ****************************************************************************/
+
+static int bridge_rmmac(FAR struct netdev_lowerhalf_s *dev,
+                        FAR const uint8_t *mac)
+{
+  FAR struct bridge_s *br = (FAR struct bridge_s *)dev;
+  int i;
+
+  nxmutex_lock(&br->br_lock);
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct netdev_lowerhalf_s *port = br->br_ports[i].bp_dev;
+
+      if (port == NULL)
+        {
+          break;
+        }
+
+      if (port->ops->rmmac != NULL)
+        {
+          port->ops->rmmac(port, mac);
+        }
+    }
+
+  nxmutex_unlock(&br->br_lock);
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: bridge_reclaim
+ *
+ * Description:
+ *   The network stack found no room on the bridge device: let the ports
+ *   reclaim their sent packets, then look at the room again.
+ *
+ ****************************************************************************/
+
+static void bridge_reclaim(FAR struct netdev_lowerhalf_s *dev)
+{
+  FAR struct bridge_s *br = (FAR struct bridge_s *)dev;
+  int i;
+
+  nxmutex_lock(&br->br_lock);
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct netdev_lowerhalf_s *port = br->br_ports[i].bp_dev;
+
+      if (port == NULL)
+        {
+          break;
+        }
+
+      if (port->ops->reclaim != NULL)
+        {
+          port->ops->reclaim(port);
+        }
+    }
+
+  bridge_update_quota(br);
+  nxmutex_unlock(&br->br_lock);
+}
+
+/****************************************************************************
+ * Name: bridge_find
+ *
+ * Description:
+ *   Find a bridge by name.
+ *
+ * Assumptions:
+ *   Called with g_bridge_lock held.
+ *
+ ****************************************************************************/
+
+static FAR struct bridge_s *bridge_find(FAR const char *name)
+{
+  FAR struct bridge_s *br;
+
+  for (br = g_bridges; br != NULL; br = br->br_flink)
+    {
+      if (strncmp(br->br_dev.netdev.d_ifname, name, IFNAMSIZ) == 0)
+        {
+          return br;
+        }
+    }
+
+  return NULL;
+}
+
+/****************************************************************************
+ * Name: bridge_update_pktsize
+ *
+ * Description:
+ *   The bridge MTU is the smallest MTU of its ports, or the default
+ *   Ethernet MTU when it has no ports.
+ *
+ * Assumptions:
+ *   Called with g_bridge_lock held.
+ *
+ ****************************************************************************/
+
+static void bridge_update_pktsize(FAR struct bridge_s *br)
+{
+  uint16_t pktsize = UINT16_MAX;
+  int i;
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct netdev_lowerhalf_s *dev = br->br_ports[i].bp_dev;
+
+      if (dev == NULL)
+        {
+          break;
+        }
+
+      if (NETDEV_PKTSIZE(&dev->netdev) < pktsize)
+        {
+          pktsize = NETDEV_PKTSIZE(&dev->netdev);
+        }
+    }
+
+  br->br_dev.netdev.d_pktsize = pktsize != UINT16_MAX ? pktsize :
+                                CONFIG_NET_ETH_PKTSIZE;
+}
+
+/****************************************************************************
+ * Name: bridge_delport
+ *
+ * Description:
+ *   Remove a port from its bridge.
+ *
+ * Assumptions:
+ *   Called with g_bridge_lock held.
+ *
+ ****************************************************************************/
+
+static void bridge_delport(FAR struct bridge_port_s *port)
+{
+  FAR struct bridge_s *br = port->bp_bridge;
+  FAR struct netdev_lowerhalf_s *dev = port->bp_dev;
+  FAR struct netdev_lowerhalf_s *moved = NULL;
+  FAR struct bridge_port_s *last = port;
+  int i;
+
+  /* Stop the RX path of the port first, then drop its state */
+
+  netdev_lower_bridge_set(dev, NULL);
+
+  /* The last used port takes the place of this one, to keep the used ports
+   * at the head of the table.  The ports are only changed with
+   * g_bridge_lock held, so they can be read without br_lock.  The RX path
+   * of the moved port is stopped while its port structure changes, its
+   * frames go to the network stack in the meantime.
+   */
+
+  for (i = port - br->br_ports + 1;
+       i < CONFIG_NET_BRIDGE_MAX_PORTS && br->br_ports[i].bp_dev != NULL;
+       i++)
+    {
+      last = &br->br_ports[i];
+    }
+
+  if (last != port)
+    {
+      moved = last->bp_dev;
+      netdev_lower_bridge_set(moved, NULL);
+    }
+
+  nxmutex_lock(&br->br_lock);
+
+  i = 0;
+  while (i < CONFIG_NET_BRIDGE_FDB_SIZE && br->br_fdb[i].fdb_port != NULL)
+    {
+      if (br->br_fdb[i].fdb_port == port)
+        {
+          /* The last entry took the place of this one, check it too */
+
+          bridge_fdb_remove(br, i);
+        }
+      else
+        {
+          i++;
+        }
+    }
+
+  port->bp_dev = moved;
+
+  if (moved != NULL)
+    {
+      for (i = 0; i < CONFIG_NET_BRIDGE_FDB_SIZE &&
+                  br->br_fdb[i].fdb_port != NULL; i++)
+        {
+          if (br->br_fdb[i].fdb_port == last)
+            {
+              br->br_fdb[i].fdb_port = port;
+            }
+        }
+
+      last->bp_dev = NULL;
+    }
+
+  bridge_update_quota(br);
+  nxmutex_unlock(&br->br_lock);
+
+  if (moved != NULL)
+    {
+      netdev_lower_bridge_set(moved, port);
+    }
+
+  bridge_update_pktsize(br);
+  ninfo("%s: removed port %s\n", br->br_dev.netdev.d_ifname,
+        dev->netdev.d_ifname);
+}
+
+/****************************************************************************
+ * Name: bridge_addbr
+ ****************************************************************************/
+
+static int bridge_addbr(FAR const char *name)
+{
+  FAR struct bridge_s *br;
+  int ret;
+  int i;
+
+  if (name == NULL || name[0] == '\0' || strlen(name) >= IFNAMSIZ ||
+      strchr(name, '%') != NULL)
+    {
+      return -EINVAL;
+    }
+
+  if (netdev_findbyname(name) != NULL)
+    {
+      return -EEXIST;
+    }
+
+  br = kmm_zalloc(sizeof(struct bridge_s));
+  if (br == NULL)
+    {
+      return -ENOMEM;
+    }
+
+  nxmutex_init(&br->br_lock);
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      br->br_ports[i].bp_bridge = br;
+    }
+
+  br->br_dev.ops    = &g_bridge_ops;
+  br->br_dev.rxtype = NETDEV_RX_DIRECT;
+  strlcpy(br->br_dev.netdev.d_ifname, name, IFNAMSIZ);
+
+  ret = netdev_lower_register(&br->br_dev, NET_LL_ETHERNET);
+  if (ret < 0)
+    {
+      nxmutex_destroy(&br->br_lock);
+      kmm_free(br);
+      return ret;
+    }
+
+  br->br_flink = g_bridges;
+  g_bridges    = br;
+
+  ninfo("%s: created\n", name);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: bridge_delbr
+ ****************************************************************************/
+
+static int bridge_delbr(FAR const char *name)
+{
+  FAR struct bridge_s **pprev;
+  FAR struct bridge_s *br;
+  int i;
+
+  if (name == NULL)
+    {
+      return -EINVAL;
+    }
+
+  br = bridge_find(name);
+  if (br == NULL)
+    {
+      return -ENODEV;
+    }
+
+  /* Like Linux, refuse to delete a bridge that is up */
+
+  if (IFF_IS_UP(br->br_dev.netdev.d_flags))
+    {
+      return -EBUSY;
+    }
+
+  for (pprev = &g_bridges; *pprev != br; pprev = &(*pprev)->br_flink);
+  *pprev = br->br_flink;
+
+  for (i = CONFIG_NET_BRIDGE_MAX_PORTS - 1; i >= 0; i--)
+    {
+      if (br->br_ports[i].bp_dev != NULL)
+        {
+          bridge_delport(&br->br_ports[i]);
+        }
+    }
+
+  netdev_lower_unregister(&br->br_dev);
+
+  nxmutex_destroy(&br->br_lock);
+  kmm_free(br);
+
+  ninfo("%s: deleted\n", name);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: bridge_addif
+ ****************************************************************************/
+
+static int bridge_addif(FAR const struct ifreq *req)
+{
+  FAR struct bridge_port_s *port = NULL;
+  FAR struct netdev_lowerhalf_s *dev;
+  FAR struct net_driver_s *netdev;
+  FAR struct bridge_s *br;
+  int i;
+
+  br = bridge_find(req->ifr_name);
+  if (br == NULL)
+    {
+      return -ENODEV;
+    }
+
+  netdev = netdev_findbyindex(req->ifr_ifindex);
+  if (netdev == NULL)
+    {
+      return -ENODEV;
+    }
+
+  /* Only upper half devices can be ports, because their receive path
+   * hands the packets to the bridge.
+   */
+
+  dev = netdev_lower_get(netdev);
+  if (dev == NULL)
+    {
+      return -EOPNOTSUPP;
+    }
+
+  /* Only Ethernet-like devices that are not a bridge can be ports */
+
+  if ((netdev->d_lltype != NET_LL_ETHERNET &&
+       netdev->d_lltype != NET_LL_IEEE80211) ||
+      netdev->d_llhdrlen != ETH_HDRLEN || dev->ops == &g_bridge_ops)
+    {
+      return -EINVAL;
+    }
+
+  nxmutex_lock(&br->br_lock);
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      if (br->br_ports[i].bp_dev == dev)
+        {
+          nxmutex_unlock(&br->br_lock);
+          return -EBUSY;
+        }
+
+      /* The first unused port ends the used ones, and is where to add */
+
+      if (br->br_ports[i].bp_dev == NULL)
+        {
+          port = &br->br_ports[i];
+          break;
+        }
+    }
+
+  if (port == NULL)
+    {
+      nxmutex_unlock(&br->br_lock);
+      return -ENOSPC;
+    }
+
+  /* Like Linux, a bridge without an address takes the one of its first
+   * port.
+   */
+
+  if (memcmp(br->br_dev.netdev.d_mac.ether.ether_addr_octet,
+             g_zero_mac, ETHER_ADDR_LEN) == 0)
+    {
+      memcpy(br->br_dev.netdev.d_mac.ether.ether_addr_octet,
+             netdev->d_mac.ether.ether_addr_octet, ETHER_ADDR_LEN);
+    }
+
+  port->bp_dev = dev;
+  bridge_update_quota(br);
+  nxmutex_unlock(&br->br_lock);
+
+  bridge_update_pktsize(br);
+
+  /* Attach last: from now on the RX path of the port feeds the bridge */
+
+  netdev_lower_bridge_set(dev, port);
+
+  ninfo("%s: added port %s\n", br->br_dev.netdev.d_ifname, netdev->d_ifname);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: bridge_delif
+ ****************************************************************************/
+
+static int bridge_delif(FAR const struct ifreq *req)
+{
+  FAR struct bridge_s *br;
+  int i;
+
+  br = bridge_find(req->ifr_name);
+  if (br == NULL)
+    {
+      return -ENODEV;
+    }
+
+  for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS; i++)
+    {
+      FAR struct netdev_lowerhalf_s *dev = br->br_ports[i].bp_dev;
+
+      if (dev == NULL)
+        {
+          break;
+        }
+
+      if (dev->netdev.d_ifindex == req->ifr_ifindex)
+        {
+          bridge_delport(&br->br_ports[i]);
+          return OK;
+        }
+    }
+
+  return netdev_findbyindex(req->ifr_ifindex) == NULL ? -ENODEV : -EINVAL;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: bridge_ioctl
+ ****************************************************************************/
+
+int bridge_ioctl(int cmd, unsigned long arg)
+{
+  int ret;
+
+  switch (cmd)
+    {
+      case SIOCBRADDBR:
+      case SIOCBRDELBR:
+      case SIOCBRADDIF:
+      case SIOCBRDELIF:
+        break;
+
+      default:
+        return -ENOTTY;
+    }
+
+  if (arg == 0)
+    {
+      return -EINVAL;
+    }
+
+  nxmutex_lock(&g_bridge_lock);
+
+  switch (cmd)
+    {
+      case SIOCBRADDBR:
+        ret = bridge_addbr((FAR const char *)(uintptr_t)arg);
+        break;
+
+      case SIOCBRDELBR:
+        ret = bridge_delbr((FAR const char *)(uintptr_t)arg);
+        break;
+
+      case SIOCBRADDIF:
+        ret = bridge_addif((FAR const struct ifreq *)(uintptr_t)arg);
+        break;
+
+      default:
+        ret = bridge_delif((FAR const struct ifreq *)(uintptr_t)arg);
+        break;
+    }
+
+  nxmutex_unlock(&g_bridge_lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: bridge_input
+ ****************************************************************************/
+
+void bridge_input(FAR struct bridge_port_s *port, FAR netpkt_t *pkt)
+{
+  FAR struct bridge_s *br = port->bp_bridge;
+  FAR struct netdev_lowerhalf_s *dev = &br->br_dev;
+  FAR struct eth_hdr_s *eth;
+  bool local = false;
+
+  if (netpkt_getdatalen(dev, pkt) < ETH_HDRLEN ||
+      !IFF_IS_UP(dev->netdev.d_flags))
+    {
+      NETDEV_RXDROPPED(&port->bp_dev->netdev);
+      iob_free_chain(pkt);
+      return;
+    }
+
+  eth = (FAR struct eth_hdr_s *)netpkt_getdata(dev, pkt);
+
+  nxmutex_lock(&br->br_lock);
+
+  if (!bridge_is_multicast(eth->src))
+    {
+      bridge_fdb_learn(br, port, eth->src);
+    }
+
+  if (bridge_is_multicast(eth->dest))
+    {
+      /* Group addresses go to the local host and, except for the reserved
+       * link-local ones, to all other ports.
+       */
+
+      if (!bridge_is_linklocal(eth->dest))
+        {
+          bridge_flood(br, port, pkt, false);
+        }
+
+      local = true;
+    }
+  else if (bridge_is_local(br, eth->dest))
+    {
+      local = true;
+    }
+  else
+    {
+      FAR struct bridge_port_s *outport = bridge_fdb_lookup(br, eth->dest);
+
+      if (outport == NULL)
+        {
+          /* Unknown destination, flood it */
+
+          if (!bridge_flood(br, port, pkt, true))
+            {
+              iob_free_chain(pkt);
+            }
+        }
+      else if (outport != port)
+        {
+          bridge_port_send(outport, pkt);
+        }
+      else
+        {
+          /* The destination is on the ingress segment */
+
+          iob_free_chain(pkt);
+        }
+    }
+
+  nxmutex_unlock(&br->br_lock);
+
+  /* The local host is served outside of br_lock: the stack may reply
+   * right away, which comes back to bridge_transmit().
+   */
+
+  if (local)
+    {
+      netdev_lower_input(dev, pkt);
+    }
+}
+
+/****************************************************************************
+ * Name: bridge_txdone
+ ****************************************************************************/
+
+void bridge_txdone(FAR struct bridge_port_s *port)
+{
+  netdev_lower_txdone(&port->bp_bridge->br_dev);
+}
+
+/****************************************************************************
+ * Name: bridge_port_detach
+ ****************************************************************************/
+
+void bridge_port_detach(FAR struct netdev_lowerhalf_s *dev)
+{
+  FAR struct bridge_s *br;
+  int i;
+
+  /* The port structure of the device may have moved since the caller looked
+   * at it, so find it again with g_bridge_lock held.
+   */
+
+  nxmutex_lock(&g_bridge_lock);
+
+  for (br = g_bridges; br != NULL; br = br->br_flink)
+    {
+      for (i = 0; i < CONFIG_NET_BRIDGE_MAX_PORTS &&
+                  br->br_ports[i].bp_dev != NULL; i++)
+        {
+          if (br->br_ports[i].bp_dev == dev)
+            {
+              bridge_delport(&br->br_ports[i]);
+              nxmutex_unlock(&g_bridge_lock);
+              return;
+            }
+        }
+    }
+
+  nxmutex_unlock(&g_bridge_lock);
+}
diff --git a/drivers/net/netdev_upperhalf.c b/drivers/net/netdev_upperhalf.c
index 9da33fb8922..7bb5bdaf00d 100644
--- a/drivers/net/netdev_upperhalf.c
+++ b/drivers/net/netdev_upperhalf.c
@@ -35,6 +35,7 @@
 #include <nuttx/kmalloc.h>
 #include <nuttx/kthread.h>
 #include <nuttx/mm/iob.h>
+#include <nuttx/net/bridge.h>
 #include <nuttx/net/can.h>
 #include <nuttx/net/net.h>
 #include <nuttx/net/netdev_lowerhalf.h>
@@ -86,6 +87,12 @@ struct netdev_upperhalf_s
   struct netdev_vlan_entry_s vlan[CONFIG_NET_VLAN_COUNT];
 #endif
 
+  /* The bridge port of this device, NULL if it is not bridged */
+
+#ifdef CONFIG_NET_BRIDGE
+  FAR struct bridge_port_s *bridge;
+#endif
+
   bool txing;
 
   /* Deferring process to work queue or thread */
@@ -688,81 +695,120 @@ static void ip_input(FAR struct net_driver_s *dev)
 #endif
 
 /****************************************************************************
- * Function: netdev_upper_rxpoll_work
+ * Name: netdev_upper_input
  *
  * Description:
- *   Try to receive packets from device and pass packets into IP
- *   stack and send packets which is from IP stack if necessary.
+ *   Handle a received packet, which is in dev->d_iob: drop it when the
+ *   interface is down, feed the packet tap, then give it to the bridge when
+ *   the device is a bridge port, or to the protocol of the link layer
+ *   otherwise.
  *
  * Input Parameters:
- *   upper - Reference to the upper half driver structure
+ *   dev - Reference to the NuttX network driver state structure
  *
  * Assumptions:
- *   Called with the network locked.
+ *   Called with the network device locked.
  *
  ****************************************************************************/
 
-static void netdev_upper_rxpoll_work(FAR struct netdev_upperhalf_s *upper)
+static void netdev_upper_input(FAR struct net_driver_s *dev)
 {
-  FAR struct netdev_lowerhalf_s *lower = upper->lower;
-  FAR struct net_driver_s       *dev   = &lower->netdev;
-  FAR netpkt_t                  *pkt;
-
-  /* Loop while receive() successfully retrieves valid Ethernet frames. */
+#ifdef CONFIG_NET_BRIDGE
+  FAR struct netdev_upperhalf_s *upper = dev->d_private;
+#endif
 
-  netdev_lock(dev);
-  while ((pkt = lower->ops->receive(lower)) != NULL)
+  if (!IFF_IS_UP(dev->d_flags))
     {
-      if (!IFF_IS_UP(dev->d_flags))
-        {
-          /* Interface down, drop frame */
+      /* Interface down, drop frame */
 
-          NETDEV_RXDROPPED(dev);
-          netpkt_free(lower, pkt, NETPKT_RX);
-          nerr("ERROR: Dropped frame due to lower dev not up\n");
-          continue;
-        }
+      NETDEV_RXDROPPED(dev);
+      netdev_iob_release(dev);
+      nerr("ERROR: Dropped frame due to lower dev not up\n");
+      return;
+    }
 
-      netpkt_put(dev, pkt, NETPKT_RX);
-      NETDEV_RXPACKETS(dev);
+  NETDEV_RXPACKETS(dev);
 
 #ifdef CONFIG_NET_PKT
-      /* When packet sockets are enabled, feed the frame into the tap */
+  /* When packet sockets are enabled, feed the frame into the tap */
 
-      pkt_input(dev);
+  pkt_input(dev);
 #endif
 
-      switch (dev->d_lltype)
-        {
+#ifdef CONFIG_NET_BRIDGE
+  /* The packets of a bridge port belong to the bridge */
+
+  if (upper->bridge != NULL)
+    {
+      FAR netpkt_t *pkt = dev->d_iob;
+
+      netdev_iob_clear(dev);
+      bridge_input(upper->bridge, pkt);
+      return;
+    }
+#endif
+
+  switch (dev->d_lltype)
+    {
 #ifdef CONFIG_NET_LOOPBACK
-        case NET_LL_LOOPBACK:
+      case NET_LL_LOOPBACK:
 #endif
 #ifdef CONFIG_NET_ETHERNET
-        case NET_LL_ETHERNET:
+      case NET_LL_ETHERNET:
 #endif
 #ifdef CONFIG_DRIVERS_IEEE80211
-        case NET_LL_IEEE80211:
+      case NET_LL_IEEE80211:
 #endif
 #if defined(CONFIG_NET_LOOPBACK) || defined(CONFIG_NET_ETHERNET) || \
     defined(CONFIG_DRIVERS_IEEE80211)
-          eth_input(dev);
-          break;
+        eth_input(dev);
+        break;
 #endif
 #ifdef CONFIG_NET_MBIM
-        case NET_LL_MBIM:
-          ip_input(dev);
-          break;
+      case NET_LL_MBIM:
+        ip_input(dev);
+        break;
 #endif
 #ifdef CONFIG_NET_CAN
-        case NET_LL_CAN:
-          ninfo("CAN frame");
-          can_input(dev);
-          break;
+      case NET_LL_CAN:
+        ninfo("CAN frame");
+        can_input(dev);
+        break;
 #endif
-        default:
-          nerr("Unknown link type %d\n", dev->d_lltype);
-          break;
-        }
+      default:
+        nerr("Unknown link type %d\n", dev->d_lltype);
+        break;
+    }
+}
+
+/****************************************************************************
+ * Function: netdev_upper_rxpoll_work
+ *
+ * Description:
+ *   Try to receive packets from device and pass packets into IP
+ *   stack and send packets which is from IP stack if necessary.
+ *
+ * Input Parameters:
+ *   upper - Reference to the upper half driver structure
+ *
+ * Assumptions:
+ *   Called with the network locked.
+ *
+ ****************************************************************************/
+
+static void netdev_upper_rxpoll_work(FAR struct netdev_upperhalf_s *upper)
+{
+  FAR struct netdev_lowerhalf_s *lower = upper->lower;
+  FAR struct net_driver_s       *dev   = &lower->netdev;
+  FAR netpkt_t                  *pkt;
+
+  /* Loop while receive() successfully retrieves valid Ethernet frames. */
+
+  netdev_lock(dev);
+  while ((pkt = lower->ops->receive(lower)) != NULL)
+    {
+      netpkt_put(dev, pkt, NETPKT_RX);
+      netdev_upper_input(dev);
     }
 
   netdev_unlock(dev);
@@ -844,6 +890,7 @@ static inline void netdev_upper_queue_work(FAR struct 
net_driver_s *dev)
       case NETDEV_RX_WORK:
         {
           FAR struct work_s *work = upper->work;
+
           if (work_available(work))
             {
               /* Schedule to serialize the poll on the worker thread. */
@@ -1483,6 +1530,13 @@ int netdev_lower_unregister(FAR struct 
netdev_lowerhalf_s *dev)
   netdev_upper_vlan_foreach(upper, vlan_unregister);
 #endif
 
+#ifdef CONFIG_NET_BRIDGE
+  if (upper->bridge != NULL)
+    {
+      bridge_port_detach(dev);
+    }
+#endif
+
   ret = netdev_unregister(&dev->netdev);
   if (ret < 0)
     {
@@ -1534,6 +1588,7 @@ void netdev_lower_carrier_on(FAR struct 
netdev_lowerhalf_s *dev)
 {
 #ifdef CONFIG_NET_VLAN
   FAR struct netdev_upperhalf_s *upper = dev->netdev.d_private;
+
   netdev_upper_vlan_foreach(upper, netdev_lower_carrier_on);
 #endif
 
@@ -1558,6 +1613,7 @@ void netdev_lower_carrier_off(FAR struct 
netdev_lowerhalf_s *dev)
 {
 #ifdef CONFIG_NET_VLAN
   FAR struct netdev_upperhalf_s *upper = dev->netdev.d_private;
+
   netdev_upper_vlan_foreach(upper, netdev_lower_carrier_off);
 #endif
 
@@ -1606,10 +1662,23 @@ void netdev_lower_rxready(FAR struct netdev_lowerhalf_s 
*dev)
 
 void netdev_lower_txdone(FAR struct netdev_lowerhalf_s *dev)
 {
-#ifdef CONFIG_NET_VLAN
+#if defined(CONFIG_NET_VLAN) || defined(CONFIG_NET_BRIDGE)
   FAR struct netdev_upperhalf_s *upper = dev->netdev.d_private;
+#endif
+
+#ifdef CONFIG_NET_VLAN
   netdev_upper_vlan_foreach(upper, netdev_lower_txdone);
 #endif
+
+#ifdef CONFIG_NET_BRIDGE
+  /* The bridge device may wait for room on its ports */
+
+  if (upper->bridge != NULL)
+    {
+      bridge_txdone(upper->bridge);
+    }
+#endif
+
   if (dev->rxtype == NETDEV_RX_DIRECT)
     {
       netdev_upper_txavail_work(dev->netdev.d_private);
@@ -1703,6 +1772,87 @@ int netdev_lower_vlan_del(FAR struct netdev_lowerhalf_s 
*dev, uint16_t vid)
 }
 #endif
 
+/****************************************************************************
+ * Name: netdev_lower_get
+ *
+ * Description:
+ *   Get the lower half of a network device.
+ *
+ * Input Parameters:
+ *   dev - The network device
+ *
+ * Returned Value:
+ *   The lower half device driver structure, or NULL if the device is not
+ *   managed by the upper half network driver (e.g. legacy drivers).
+ *
+ ****************************************************************************/
+
+FAR struct netdev_lowerhalf_s *
+netdev_lower_get(FAR struct net_driver_s *dev)
+{
+  FAR struct netdev_upperhalf_s *upper;
+
+  if (dev == NULL || dev->d_ifup != netdev_upper_ifup)
+    {
+      return NULL;
+    }
+
+  upper = dev->d_private;
+  return upper->lower;
+}
+
+/****************************************************************************
+ * Name: netdev_lower_input
+ *
+ * Description:
+ *   Pass a packet to the network stack as if the device had received it.
+ *   Used by virtual devices (bridge) that get their packets from another
+ *   device.
+ *
+ * Input Parameters:
+ *   dev - The lower half device driver structure
+ *   pkt - The packet, owned by the network stack from now on
+ *
+ ****************************************************************************/
+
+void netdev_lower_input(FAR struct netdev_lowerhalf_s *dev,
+                        FAR netpkt_t *pkt)
+{
+  FAR struct net_driver_s *netdev = &dev->netdev;
+
+  netdev_lock(netdev);
+  netdev_iob_replace_l2(netdev, pkt);
+  netdev_upper_input(netdev);
+  netdev_iob_release(netdev);
+  netdev_unlock(netdev);
+}
+
+/****************************************************************************
+ * Name: netdev_lower_bridge_set
+ *
+ * Description:
+ *   Make the device a port of a bridge, or release it from the bridge.
+ *   The packets received by a bridge port are given to the bridge instead
+ *   of the network stack.
+ *
+ * Input Parameters:
+ *   dev  - The lower half device driver structure
+ *   port - The bridge port, or NULL to release the device
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_BRIDGE
+void netdev_lower_bridge_set(FAR struct netdev_lowerhalf_s *dev,
+                             FAR struct bridge_port_s *port)
+{
+  FAR struct netdev_upperhalf_s *upper = dev->netdev.d_private;
+
+  netdev_lock(&dev->netdev);
+  upper->bridge = port;
+  netdev_unlock(&dev->netdev);
+}
+#endif
+
 /****************************************************************************
  * Name: netpkt_alloc
  *
@@ -1871,6 +2021,7 @@ int netpkt_setdatalen(FAR struct netdev_lowerhalf_s *dev,
 {
   uint8_t llhdrlen = NET_LL_HDRLEN(&dev->netdev);
   int ret = iob_update_pktlen(pkt, len - llhdrlen, false);
+
   return ret >= 0 ? ret + llhdrlen : ret;
 }
 
diff --git a/include/nuttx/net/bridge.h b/include/nuttx/net/bridge.h
new file mode 100644
index 00000000000..9eaed79fef2
--- /dev/null
+++ b/include/nuttx/net/bridge.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+ * include/nuttx/net/bridge.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_NET_BRIDGE_H
+#define __INCLUDE_NUTTX_NET_BRIDGE_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <nuttx/net/netdev_lowerhalf.h>
+
+#ifdef CONFIG_NET_BRIDGE
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+struct bridge_port_s; /* Opaque, defined by the bridge driver */
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: bridge_ioctl
+ *
+ * Description:
+ *   Handle the SIOCBRADDBR, SIOCBRDELBR, SIOCBRADDIF and SIOCBRDELIF
+ *   ioctl commands.
+ *
+ * Input Parameters:
+ *   cmd - The ioctl command
+ *   arg - The argument of the command: the bridge name for SIOCBRADDBR
+ *         and SIOCBRDELBR, a pointer to a struct ifreq for the others
+ *
+ * Returned Value:
+ *   OK on success, -ENOTTY if cmd is not a bridge command, or another
+ *   negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int bridge_ioctl(int cmd, unsigned long arg);
+
+/****************************************************************************
+ * Name: bridge_input
+ *
+ * Description:
+ *   Called by the upper half of a bridge port for every received packet.
+ *   The bridge takes the packet: it forwards it to the other ports and/or
+ *   passes it to the network stack through the bridge device.
+ *
+ * Input Parameters:
+ *   port - The bridge port that received the packet
+ *   pkt  - The packet, owned by the bridge from now on
+ *
+ * Assumptions:
+ *   Called from the RX path of the port, with the port device locked.
+ *
+ ****************************************************************************/
+
+void bridge_input(FAR struct bridge_port_s *port, FAR netpkt_t *pkt);
+
+/****************************************************************************
+ * Name: bridge_txdone
+ *
+ * Description:
+ *   Called by the upper half of a bridge port when the port has completed a
+ *   transmission, so that the bridge device resumes its own transmission.
+ *   The bridge device receives and polls in the context of the caller
+ *   (NETDEV_RX_DIRECT), so the caller must not be an interrupt handler.
+ *
+ ****************************************************************************/
+
+void bridge_txdone(FAR struct bridge_port_s *port);
+
+/****************************************************************************
+ * Name: bridge_port_detach
+ *
+ * Description:
+ *   Called by the upper half when a port device is unregistered, to remove
+ *   it from its bridge.
+ *
+ * Input Parameters:
+ *   dev - The lower half device driver structure of the port
+ *
+ ****************************************************************************/
+
+void bridge_port_detach(FAR struct netdev_lowerhalf_s *dev);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONFIG_NET_BRIDGE */
+#endif /* __INCLUDE_NUTTX_NET_BRIDGE_H */
diff --git a/include/nuttx/net/netdev_lowerhalf.h 
b/include/nuttx/net/netdev_lowerhalf.h
index 21a52207900..f4ebbd1fa8a 100644
--- a/include/nuttx/net/netdev_lowerhalf.h
+++ b/include/nuttx/net/netdev_lowerhalf.h
@@ -368,6 +368,61 @@ int netdev_lower_vlan_add(FAR struct netdev_lowerhalf_s 
*dev, uint16_t vid,
 int netdev_lower_vlan_del(FAR struct netdev_lowerhalf_s *dev, uint16_t vid);
 #endif
 
+/****************************************************************************
+ * Name: netdev_lower_get
+ *
+ * Description:
+ *   Get the lower half of a network device.
+ *
+ * Input Parameters:
+ *   dev - The network device
+ *
+ * Returned Value:
+ *   The lower half device driver structure, or NULL if the device is not
+ *   managed by the upper half network driver (e.g. legacy drivers).
+ *
+ ****************************************************************************/
+
+FAR struct netdev_lowerhalf_s *
+netdev_lower_get(FAR struct net_driver_s *dev);
+
+/****************************************************************************
+ * Name: netdev_lower_input
+ *
+ * Description:
+ *   Pass a packet to the network stack as if the device had received it.
+ *   Used by virtual devices (bridge) that get their packets from another
+ *   device.
+ *
+ * Input Parameters:
+ *   dev - The lower half device driver structure
+ *   pkt - The packet, owned by the network stack from now on
+ *
+ ****************************************************************************/
+
+void netdev_lower_input(FAR struct netdev_lowerhalf_s *dev,
+                        FAR netpkt_t *pkt);
+
+/****************************************************************************
+ * Name: netdev_lower_bridge_set
+ *
+ * Description:
+ *   Make the device a port of a bridge, or release it from the bridge.
+ *   The packets received by a bridge port are given to the bridge instead
+ *   of the network stack.
+ *
+ * Input Parameters:
+ *   dev  - The lower half device driver structure
+ *   port - The bridge port, or NULL to release the device
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_BRIDGE
+struct bridge_port_s;
+void netdev_lower_bridge_set(FAR struct netdev_lowerhalf_s *dev,
+                             FAR struct bridge_port_s *port);
+#endif
+
 /****************************************************************************
  * Name: netpkt_alloc
  *
diff --git a/net/Kconfig b/net/Kconfig
index 7ca3c467637..8cae8909f42 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -312,6 +312,54 @@ config NET_VLAN_COUNT
 
 endif # NET_VLAN
 
+menuconfig NET_BRIDGE
+       bool "Ethernet bridge support"
+       default n
+       depends on NET_ETHERNET
+       select NETDEV_IFINDEX
+       ---help---
+               Enable a transparent, learning Ethernet bridge (IEEE 802.1D 
without
+               spanning tree).  A bridge is a virtual network device (e.g. br0)
+               created with the SIOCBRADDBR ioctl.  Ethernet (or IEEE 802.11)
+               devices are added to it as ports with SIOCBRADDIF.  Frames 
received
+               on a port are forwarded to the other ports based on a filtering
+               database of learned MAC addresses, and frames for the local 
host are
+               passed to the network stack through the bridge device, which 
holds
+               the IP configuration.
+
+               Only network drivers that use the upper-half interface
+               (netdev_lowerhalf_s) can be bridge ports.
+
+               There is no spanning tree protocol: the network must not contain
+               loops through the bridge.
+
+               Port devices must accept frames for foreign destination MAC
+               addresses (promiscuous mode, see NET_PROMISCUOUS) to forward 
unicast
+               traffic.
+
+if NET_BRIDGE
+
+config NET_BRIDGE_MAX_PORTS
+       int "Maximum number of ports per bridge"
+       default 4
+       range 2 32
+
+config NET_BRIDGE_FDB_SIZE
+       int "Filtering database size"
+       default 32
+       ---help---
+               Number of MAC addresses that each bridge can learn.  When the 
table
+               is full, the oldest entry is replaced.
+
+config NET_BRIDGE_AGEING_TIME
+       int "Filtering database ageing time (seconds)"
+       default 300
+       ---help---
+               A learned MAC address is forgotten after this many seconds 
without
+               receiving a frame from it.
+
+endif # NET_BRIDGE
+
 config NETDEV_LATEINIT
        bool "Late driver initialization"
        default n
diff --git a/net/netdev/netdev_ioctl.c b/net/netdev/netdev_ioctl.c
index 1b846e30e61..6d752ad1465 100644
--- a/net/netdev/netdev_ioctl.c
+++ b/net/netdev/netdev_ioctl.c
@@ -46,6 +46,7 @@
 #include <nuttx/ethtool.h>
 #include <nuttx/net/netdev.h>
 #include <nuttx/net/radiodev.h>
+#include <nuttx/net/bridge.h>
 #include <nuttx/net/vlan.h>
 
 #ifdef CONFIG_NET_6LOWPAN
@@ -2022,6 +2023,15 @@ int psock_vioctl(FAR struct socket *psock, int cmd, 
va_list ap)
     }
 #endif
 
+#ifdef CONFIG_NET_BRIDGE
+  /* Check for a bridge command */
+
+  if (ret == -ENOTTY)
+    {
+      ret = bridge_ioctl(cmd, arg);
+    }
+#endif
+
 #ifdef HAVE_IEEE802154_IOCTL
   /* Check for a IEEE802.15.4 network device IOCTL command */
 

Reply via email to