Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/ethernet/freescale/dpaa2/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 89 kB image not shown  

Quelle  dpaa2-switch.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * DPAA2 Ethernet Switch driver
 *
 * Copyright 2014-2016 Freescale Semiconductor Inc.
 * Copyright 2017-2021 NXP
 *
 */


#include <linux/module.h>

#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/workqueue.h>
#include <linux/iommu.h>
#include <net/pkt_cls.h>

#include <linux/fsl/mc.h>

#include "dpaa2-switch.h"

/* Minimal supported DPSW version */
#define DPSW_MIN_VER_MAJOR  8
#define DPSW_MIN_VER_MINOR  9

#define DEFAULT_VLAN_ID   1

static u16 dpaa2_switch_port_get_fdb_id(struct ethsw_port_priv *port_priv)
{
 return port_priv->fdb->fdb_id;
}

static struct dpaa2_switch_fdb *dpaa2_switch_fdb_get_unused(struct ethsw_core *ethsw)
{
 int i;

 for (i = 0; i < ethsw->sw_attr.num_ifs; i++)
  if (!ethsw->fdbs[i].in_use)
   return ðsw->fdbs[i];
 return NULL;
}

static struct dpaa2_switch_filter_block *
dpaa2_switch_filter_block_get_unused(struct ethsw_core *ethsw)
{
 int i;

 for (i = 0; i < ethsw->sw_attr.num_ifs; i++)
  if (!ethsw->filter_blocks[i].in_use)
   return ðsw->filter_blocks[i];
 return NULL;
}

static u16 dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv,
         struct net_device *bridge_dev)
{
 struct ethsw_port_priv *other_port_priv = NULL;
 struct dpaa2_switch_fdb *fdb;
 struct net_device *other_dev;
 struct list_head *iter;

 /* If we leave a bridge (bridge_dev is NULL), find an unused
 * FDB and use that.
 */

 if (!bridge_dev) {
  fdb = dpaa2_switch_fdb_get_unused(port_priv->ethsw_data);

  /* If there is no unused FDB, we must be the last port that
 * leaves the last bridge, all the others are standalone. We
 * can just keep the FDB that we already have.
 */


  if (!fdb) {
   port_priv->fdb->bridge_dev = NULL;
   return 0;
  }

  port_priv->fdb = fdb;
  port_priv->fdb->in_use = true;
  port_priv->fdb->bridge_dev = NULL;
  return 0;
 }

 /* The below call to netdev_for_each_lower_dev() demands the RTNL lock
 * being held. Assert on it so that it's easier to catch new code
 * paths that reach this point without the RTNL lock.
 */

 ASSERT_RTNL();

 /* If part of a bridge, use the FDB of the first dpaa2 switch interface
 * to be present in that bridge
 */

 netdev_for_each_lower_dev(bridge_dev, other_dev, iter) {
  if (!dpaa2_switch_port_dev_check(other_dev))
   continue;

  if (other_dev == port_priv->netdev)
   continue;

  other_port_priv = netdev_priv(other_dev);
  break;
 }

 /* The current port is about to change its FDB to the one used by the
 * first port that joined the bridge.
 */

 if (other_port_priv) {
  /* The previous FDB is about to become unused, since the
 * interface is no longer standalone.
 */

  port_priv->fdb->in_use = false;
  port_priv->fdb->bridge_dev = NULL;

  /* Get a reference to the new FDB */
  port_priv->fdb = other_port_priv->fdb;
 }

 /* Keep track of the new upper bridge device */
 port_priv->fdb->bridge_dev = bridge_dev;

 return 0;
}

static void dpaa2_switch_fdb_get_flood_cfg(struct ethsw_core *ethsw, u16 fdb_id,
        enum dpsw_flood_type type,
        struct dpsw_egress_flood_cfg *cfg)
{
 int i = 0, j;

 memset(cfg, 0, sizeof(*cfg));

 /* Add all the DPAA2 switch ports found in the same bridging domain to
 * the egress flooding domain
 */

 for (j = 0; j < ethsw->sw_attr.num_ifs; j++) {
  if (!ethsw->ports[j])
   continue;
  if (ethsw->ports[j]->fdb->fdb_id != fdb_id)
   continue;

  if (type == DPSW_BROADCAST && ethsw->ports[j]->bcast_flood)
   cfg->if_id[i++] = ethsw->ports[j]->idx;
  else if (type == DPSW_FLOODING && ethsw->ports[j]->ucast_flood)
   cfg->if_id[i++] = ethsw->ports[j]->idx;
 }

 /* Add the CTRL interface to the egress flooding domain */
 cfg->if_id[i++] = ethsw->sw_attr.num_ifs;

 cfg->fdb_id = fdb_id;
 cfg->flood_type = type;
 cfg->num_ifs = i;
}

static int dpaa2_switch_fdb_set_egress_flood(struct ethsw_core *ethsw, u16 fdb_id)
{
 struct dpsw_egress_flood_cfg flood_cfg;
 int err;

 /* Setup broadcast flooding domain */
 dpaa2_switch_fdb_get_flood_cfg(ethsw, fdb_id, DPSW_BROADCAST, &flood_cfg);
 err = dpsw_set_egress_flood(ethsw->mc_io, 0, ethsw->dpsw_handle,
        &flood_cfg);
 if (err) {
  dev_err(ethsw->dev, "dpsw_set_egress_flood() = %d\n", err);
  return err;
 }

 /* Setup unknown flooding domain */
 dpaa2_switch_fdb_get_flood_cfg(ethsw, fdb_id, DPSW_FLOODING, &flood_cfg);
 err = dpsw_set_egress_flood(ethsw->mc_io, 0, ethsw->dpsw_handle,
        &flood_cfg);
 if (err) {
  dev_err(ethsw->dev, "dpsw_set_egress_flood() = %d\n", err);
  return err;
 }

 return 0;
}

static void *dpaa2_iova_to_virt(struct iommu_domain *domain,
    dma_addr_t iova_addr)
{
 phys_addr_t phys_addr;

 phys_addr = domain ? iommu_iova_to_phys(domain, iova_addr) : iova_addr;

 return phys_to_virt(phys_addr);
}

static int dpaa2_switch_add_vlan(struct ethsw_port_priv *port_priv, u16 vid)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct dpsw_vlan_cfg vcfg = {0};
 int err;

 vcfg.fdb_id = dpaa2_switch_port_get_fdb_id(port_priv);
 err = dpsw_vlan_add(ethsw->mc_io, 0,
       ethsw->dpsw_handle, vid, &vcfg);
 if (err) {
  dev_err(ethsw->dev, "dpsw_vlan_add err %d\n", err);
  return err;
 }
 ethsw->vlans[vid] = ETHSW_VLAN_MEMBER;

 return 0;
}

static bool dpaa2_switch_port_is_up(struct ethsw_port_priv *port_priv)
{
 struct net_device *netdev = port_priv->netdev;
 struct dpsw_link_state state;
 int err;

 err = dpsw_if_get_link_state(port_priv->ethsw_data->mc_io, 0,
         port_priv->ethsw_data->dpsw_handle,
         port_priv->idx, &state);
 if (err) {
  netdev_err(netdev, "dpsw_if_get_link_state() err %d\n", err);
  return true;
 }

 WARN_ONCE(state.up > 1, "Garbage read into link_state");

 return state.up ? true : false;
}

static int dpaa2_switch_port_set_pvid(struct ethsw_port_priv *port_priv, u16 pvid)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct net_device *netdev = port_priv->netdev;
 struct dpsw_tci_cfg tci_cfg = { 0 };
 bool up;
 int err, ret;

 err = dpsw_if_get_tci(ethsw->mc_io, 0, ethsw->dpsw_handle,
         port_priv->idx, &tci_cfg);
 if (err) {
  netdev_err(netdev, "dpsw_if_get_tci err %d\n", err);
  return err;
 }

 tci_cfg.vlan_id = pvid;

 /* Interface needs to be down to change PVID */
 up = dpaa2_switch_port_is_up(port_priv);
 if (up) {
  err = dpsw_if_disable(ethsw->mc_io, 0,
          ethsw->dpsw_handle,
          port_priv->idx);
  if (err) {
   netdev_err(netdev, "dpsw_if_disable err %d\n", err);
   return err;
  }
 }

 err = dpsw_if_set_tci(ethsw->mc_io, 0, ethsw->dpsw_handle,
         port_priv->idx, &tci_cfg);
 if (err) {
  netdev_err(netdev, "dpsw_if_set_tci err %d\n", err);
  goto set_tci_error;
 }

 /* Delete previous PVID info and mark the new one */
 port_priv->vlans[port_priv->pvid] &= ~ETHSW_VLAN_PVID;
 port_priv->vlans[pvid] |= ETHSW_VLAN_PVID;
 port_priv->pvid = pvid;

set_tci_error:
 if (up) {
  ret = dpsw_if_enable(ethsw->mc_io, 0,
         ethsw->dpsw_handle,
         port_priv->idx);
  if (ret) {
   netdev_err(netdev, "dpsw_if_enable err %d\n", ret);
   return ret;
  }
 }

 return err;
}

static int dpaa2_switch_port_add_vlan(struct ethsw_port_priv *port_priv,
          u16 vid, u16 flags)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct net_device *netdev = port_priv->netdev;
 struct dpsw_vlan_if_cfg vcfg = {0};
 int err;

 if (port_priv->vlans[vid]) {
  netdev_err(netdev, "VLAN %d already configured\n", vid);
  return -EEXIST;
 }

 /* If hit, this VLAN rule will lead the packet into the FDB table
 * specified in the vlan configuration below
 */

 vcfg.num_ifs = 1;
 vcfg.if_id[0] = port_priv->idx;
 vcfg.fdb_id = dpaa2_switch_port_get_fdb_id(port_priv);
 vcfg.options |= DPSW_VLAN_ADD_IF_OPT_FDB_ID;
 err = dpsw_vlan_add_if(ethsw->mc_io, 0, ethsw->dpsw_handle, vid, &vcfg);
 if (err) {
  netdev_err(netdev, "dpsw_vlan_add_if err %d\n", err);
  return err;
 }

 port_priv->vlans[vid] = ETHSW_VLAN_MEMBER;

 if (flags & BRIDGE_VLAN_INFO_UNTAGGED) {
  err = dpsw_vlan_add_if_untagged(ethsw->mc_io, 0,
      ethsw->dpsw_handle,
      vid, &vcfg);
  if (err) {
   netdev_err(netdev,
       "dpsw_vlan_add_if_untagged err %d\n", err);
   return err;
  }
  port_priv->vlans[vid] |= ETHSW_VLAN_UNTAGGED;
 }

 if (flags & BRIDGE_VLAN_INFO_PVID) {
  err = dpaa2_switch_port_set_pvid(port_priv, vid);
  if (err)
   return err;
 }

 return 0;
}

static enum dpsw_stp_state br_stp_state_to_dpsw(u8 state)
{
 switch (state) {
 case BR_STATE_DISABLED:
  return DPSW_STP_STATE_DISABLED;
 case BR_STATE_LISTENING:
  return DPSW_STP_STATE_LISTENING;
 case BR_STATE_LEARNING:
  return DPSW_STP_STATE_LEARNING;
 case BR_STATE_FORWARDING:
  return DPSW_STP_STATE_FORWARDING;
 case BR_STATE_BLOCKING:
  return DPSW_STP_STATE_BLOCKING;
 default:
  return DPSW_STP_STATE_DISABLED;
 }
}

static int dpaa2_switch_port_set_stp_state(struct ethsw_port_priv *port_priv, u8 state)
{
 struct dpsw_stp_cfg stp_cfg = {0};
 int err;
 u16 vid;

 if (!netif_running(port_priv->netdev) || state == port_priv->stp_state)
  return 0; /* Nothing to do */

 stp_cfg.state = br_stp_state_to_dpsw(state);
 for (vid = 0; vid <= VLAN_VID_MASK; vid++) {
  if (port_priv->vlans[vid] & ETHSW_VLAN_MEMBER) {
   stp_cfg.vlan_id = vid;
   err = dpsw_if_set_stp(port_priv->ethsw_data->mc_io, 0,
           port_priv->ethsw_data->dpsw_handle,
           port_priv->idx, &stp_cfg);
   if (err) {
    netdev_err(port_priv->netdev,
        "dpsw_if_set_stp err %d\n", err);
    return err;
   }
  }
 }

 port_priv->stp_state = state;

 return 0;
}

static int dpaa2_switch_dellink(struct ethsw_core *ethsw, u16 vid)
{
 struct ethsw_port_priv *ppriv_local = NULL;
 int i, err;

 if (!ethsw->vlans[vid])
  return -ENOENT;

 err = dpsw_vlan_remove(ethsw->mc_io, 0, ethsw->dpsw_handle, vid);
 if (err) {
  dev_err(ethsw->dev, "dpsw_vlan_remove err %d\n", err);
  return err;
 }
 ethsw->vlans[vid] = 0;

 for (i = 0; i < ethsw->sw_attr.num_ifs; i++) {
  ppriv_local = ethsw->ports[i];
  if (ppriv_local)
   ppriv_local->vlans[vid] = 0;
 }

 return 0;
}

static int dpaa2_switch_port_fdb_add_uc(struct ethsw_port_priv *port_priv,
     const unsigned char *addr)
{
 struct dpsw_fdb_unicast_cfg entry = {0};
 u16 fdb_id;
 int err;

 entry.if_egress = port_priv->idx;
 entry.type = DPSW_FDB_ENTRY_STATIC;
 ether_addr_copy(entry.mac_addr, addr);

 fdb_id = dpaa2_switch_port_get_fdb_id(port_priv);
 err = dpsw_fdb_add_unicast(port_priv->ethsw_data->mc_io, 0,
       port_priv->ethsw_data->dpsw_handle,
       fdb_id, &entry);
 if (err)
  netdev_err(port_priv->netdev,
      "dpsw_fdb_add_unicast err %d\n", err);
 return err;
}

static int dpaa2_switch_port_fdb_del_uc(struct ethsw_port_priv *port_priv,
     const unsigned char *addr)
{
 struct dpsw_fdb_unicast_cfg entry = {0};
 u16 fdb_id;
 int err;

 entry.if_egress = port_priv->idx;
 entry.type = DPSW_FDB_ENTRY_STATIC;
 ether_addr_copy(entry.mac_addr, addr);

 fdb_id = dpaa2_switch_port_get_fdb_id(port_priv);
 err = dpsw_fdb_remove_unicast(port_priv->ethsw_data->mc_io, 0,
          port_priv->ethsw_data->dpsw_handle,
          fdb_id, &entry);
 /* Silently discard error for calling multiple times the del command */
 if (err && err != -ENXIO)
  netdev_err(port_priv->netdev,
      "dpsw_fdb_remove_unicast err %d\n", err);
 return err;
}

static int dpaa2_switch_port_fdb_add_mc(struct ethsw_port_priv *port_priv,
     const unsigned char *addr)
{
 struct dpsw_fdb_multicast_cfg entry = {0};
 u16 fdb_id;
 int err;

 ether_addr_copy(entry.mac_addr, addr);
 entry.type = DPSW_FDB_ENTRY_STATIC;
 entry.num_ifs = 1;
 entry.if_id[0] = port_priv->idx;

 fdb_id = dpaa2_switch_port_get_fdb_id(port_priv);
 err = dpsw_fdb_add_multicast(port_priv->ethsw_data->mc_io, 0,
         port_priv->ethsw_data->dpsw_handle,
         fdb_id, &entry);
 /* Silently discard error for calling multiple times the add command */
 if (err && err != -ENXIO)
  netdev_err(port_priv->netdev, "dpsw_fdb_add_multicast err %d\n",
      err);
 return err;
}

static int dpaa2_switch_port_fdb_del_mc(struct ethsw_port_priv *port_priv,
     const unsigned char *addr)
{
 struct dpsw_fdb_multicast_cfg entry = {0};
 u16 fdb_id;
 int err;

 ether_addr_copy(entry.mac_addr, addr);
 entry.type = DPSW_FDB_ENTRY_STATIC;
 entry.num_ifs = 1;
 entry.if_id[0] = port_priv->idx;

 fdb_id = dpaa2_switch_port_get_fdb_id(port_priv);
 err = dpsw_fdb_remove_multicast(port_priv->ethsw_data->mc_io, 0,
     port_priv->ethsw_data->dpsw_handle,
     fdb_id, &entry);
 /* Silently discard error for calling multiple times the del command */
 if (err && err != -ENAVAIL)
  netdev_err(port_priv->netdev,
      "dpsw_fdb_remove_multicast err %d\n", err);
 return err;
}

static void dpaa2_switch_port_get_stats(struct net_device *netdev,
     struct rtnl_link_stats64 *stats)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 u64 tmp;
 int err;

 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
      port_priv->ethsw_data->dpsw_handle,
      port_priv->idx,
      DPSW_CNT_ING_FRAME, &stats->rx_packets);
 if (err)
  goto error;

 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
      port_priv->ethsw_data->dpsw_handle,
      port_priv->idx,
      DPSW_CNT_EGR_FRAME, &stats->tx_packets);
 if (err)
  goto error;

 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
      port_priv->ethsw_data->dpsw_handle,
      port_priv->idx,
      DPSW_CNT_ING_BYTE, &stats->rx_bytes);
 if (err)
  goto error;

 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
      port_priv->ethsw_data->dpsw_handle,
      port_priv->idx,
      DPSW_CNT_EGR_BYTE, &stats->tx_bytes);
 if (err)
  goto error;

 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
      port_priv->ethsw_data->dpsw_handle,
      port_priv->idx,
      DPSW_CNT_ING_FRAME_DISCARD,
      &stats->rx_dropped);
 if (err)
  goto error;

 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
      port_priv->ethsw_data->dpsw_handle,
      port_priv->idx,
      DPSW_CNT_ING_FLTR_FRAME,
      &tmp);
 if (err)
  goto error;
 stats->rx_dropped += tmp;

 err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0,
      port_priv->ethsw_data->dpsw_handle,
      port_priv->idx,
      DPSW_CNT_EGR_FRAME_DISCARD,
      &stats->tx_dropped);
 if (err)
  goto error;

 return;

error:
 netdev_err(netdev, "dpsw_if_get_counter err %d\n", err);
}

static bool dpaa2_switch_port_has_offload_stats(const struct net_device *netdev,
      int attr_id)
{
 return (attr_id == IFLA_OFFLOAD_XSTATS_CPU_HIT);
}

static int dpaa2_switch_port_get_offload_stats(int attr_id,
            const struct net_device *netdev,
            void *sp)
{
 switch (attr_id) {
 case IFLA_OFFLOAD_XSTATS_CPU_HIT:
  dpaa2_switch_port_get_stats((struct net_device *)netdev, sp);
  return 0;
 }

 return -EINVAL;
}

static int dpaa2_switch_port_change_mtu(struct net_device *netdev, int mtu)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 int err;

 err = dpsw_if_set_max_frame_length(port_priv->ethsw_data->mc_io,
        0,
        port_priv->ethsw_data->dpsw_handle,
        port_priv->idx,
        (u16)ETHSW_L2_MAX_FRM(mtu));
 if (err) {
  netdev_err(netdev,
      "dpsw_if_set_max_frame_length() err %d\n", err);
  return err;
 }

 WRITE_ONCE(netdev->mtu, mtu);
 return 0;
}

static int dpaa2_switch_port_link_state_update(struct net_device *netdev)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct dpsw_link_state state;
 int err;

 /* When we manage the MAC/PHY using phylink there is no need
 * to manually update the netif_carrier.
 * We can avoid locking because we are called from the "link changed"
 * IRQ handler, which is the same as the "endpoint changed" IRQ handler
 * (the writer to port_priv->mac), so we cannot race with it.
 */

 if (dpaa2_mac_is_type_phy(port_priv->mac))
  return 0;

 /* Interrupts are received even though no one issued an 'ifconfig up'
 * on the switch interface. Ignore these link state update interrupts
 */

 if (!netif_running(netdev))
  return 0;

 err = dpsw_if_get_link_state(port_priv->ethsw_data->mc_io, 0,
         port_priv->ethsw_data->dpsw_handle,
         port_priv->idx, &state);
 if (err) {
  netdev_err(netdev, "dpsw_if_get_link_state() err %d\n", err);
  return err;
 }

 WARN_ONCE(state.up > 1, "Garbage read into link_state");

 if (state.up != port_priv->link_state) {
  if (state.up) {
   netif_carrier_on(netdev);
   netif_tx_start_all_queues(netdev);
  } else {
   netif_carrier_off(netdev);
   netif_tx_stop_all_queues(netdev);
  }
  port_priv->link_state = state.up;
 }

 return 0;
}

/* Manage all NAPI instances for the control interface.
 *
 * We only have one RX queue and one Tx Conf queue for all
 * switch ports. Therefore, we only need to enable the NAPI instance once, the
 * first time one of the switch ports runs .dev_open().
 */


static void dpaa2_switch_enable_ctrl_if_napi(struct ethsw_core *ethsw)
{
 int i;

 /* Access to the ethsw->napi_users relies on the RTNL lock */
 ASSERT_RTNL();

 /* a new interface is using the NAPI instance */
 ethsw->napi_users++;

 /* if there is already a user of the instance, return */
 if (ethsw->napi_users > 1)
  return;

 for (i = 0; i < DPAA2_SWITCH_RX_NUM_FQS; i++)
  napi_enable(ðsw->fq[i].napi);
}

static void dpaa2_switch_disable_ctrl_if_napi(struct ethsw_core *ethsw)
{
 int i;

 /* Access to the ethsw->napi_users relies on the RTNL lock */
 ASSERT_RTNL();

 /* If we are not the last interface using the NAPI, return */
 ethsw->napi_users--;
 if (ethsw->napi_users)
  return;

 for (i = 0; i < DPAA2_SWITCH_RX_NUM_FQS; i++)
  napi_disable(ðsw->fq[i].napi);
}

static int dpaa2_switch_port_open(struct net_device *netdev)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 int err;

 mutex_lock(&port_priv->mac_lock);

 if (!dpaa2_switch_port_is_type_phy(port_priv)) {
  /* Explicitly set carrier off, otherwise
 * netif_carrier_ok() will return true and cause 'ip link show'
 * to report the LOWER_UP flag, even though the link
 * notification wasn't even received.
 */

  netif_carrier_off(netdev);
 }

 err = dpsw_if_enable(port_priv->ethsw_data->mc_io, 0,
        port_priv->ethsw_data->dpsw_handle,
        port_priv->idx);
 if (err) {
  mutex_unlock(&port_priv->mac_lock);
  netdev_err(netdev, "dpsw_if_enable err %d\n", err);
  return err;
 }

 dpaa2_switch_enable_ctrl_if_napi(ethsw);

 if (dpaa2_switch_port_is_type_phy(port_priv))
  dpaa2_mac_start(port_priv->mac);

 mutex_unlock(&port_priv->mac_lock);

 return 0;
}

static int dpaa2_switch_port_stop(struct net_device *netdev)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 int err;

 mutex_lock(&port_priv->mac_lock);

 if (dpaa2_switch_port_is_type_phy(port_priv)) {
  dpaa2_mac_stop(port_priv->mac);
 } else {
  netif_tx_stop_all_queues(netdev);
  netif_carrier_off(netdev);
 }

 mutex_unlock(&port_priv->mac_lock);

 err = dpsw_if_disable(port_priv->ethsw_data->mc_io, 0,
         port_priv->ethsw_data->dpsw_handle,
         port_priv->idx);
 if (err) {
  netdev_err(netdev, "dpsw_if_disable err %d\n", err);
  return err;
 }

 dpaa2_switch_disable_ctrl_if_napi(ethsw);

 return 0;
}

static int dpaa2_switch_port_parent_id(struct net_device *dev,
           struct netdev_phys_item_id *ppid)
{
 struct ethsw_port_priv *port_priv = netdev_priv(dev);

 ppid->id_len = 1;
 ppid->id[0] = port_priv->ethsw_data->dev_id;

 return 0;
}

static int dpaa2_switch_port_get_phys_name(struct net_device *netdev, char *name,
        size_t len)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 int err;

 err = snprintf(name, len, "p%d", port_priv->idx);
 if (err >= len)
  return -EINVAL;

 return 0;
}

struct ethsw_dump_ctx {
 struct net_device *dev;
 struct sk_buff *skb;
 struct netlink_callback *cb;
 int idx;
};

static int dpaa2_switch_fdb_dump_nl(struct fdb_dump_entry *entry,
        struct ethsw_dump_ctx *dump)
{
 struct ndo_fdb_dump_context *ctx = (void *)dump->cb->ctx;
 int is_dynamic = entry->type & DPSW_FDB_ENTRY_DINAMIC;
 u32 portid = NETLINK_CB(dump->cb->skb).portid;
 u32 seq = dump->cb->nlh->nlmsg_seq;
 struct nlmsghdr *nlh;
 struct ndmsg *ndm;

 if (dump->idx < ctx->fdb_idx)
  goto skip;

 nlh = nlmsg_put(dump->skb, portid, seq, RTM_NEWNEIGH,
   sizeof(*ndm), NLM_F_MULTI);
 if (!nlh)
  return -EMSGSIZE;

 ndm = nlmsg_data(nlh);
 ndm->ndm_family  = AF_BRIDGE;
 ndm->ndm_pad1    = 0;
 ndm->ndm_pad2    = 0;
 ndm->ndm_flags   = NTF_SELF;
 ndm->ndm_type    = 0;
 ndm->ndm_ifindex = dump->dev->ifindex;
 ndm->ndm_state   = is_dynamic ? NUD_REACHABLE : NUD_NOARP;

 if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, entry->mac_addr))
  goto nla_put_failure;

 nlmsg_end(dump->skb, nlh);

skip:
 dump->idx++;
 return 0;

nla_put_failure:
 nlmsg_cancel(dump->skb, nlh);
 return -EMSGSIZE;
}

static int dpaa2_switch_port_fdb_valid_entry(struct fdb_dump_entry *entry,
          struct ethsw_port_priv *port_priv)
{
 int idx = port_priv->idx;
 int valid;

 if (entry->type & DPSW_FDB_ENTRY_TYPE_UNICAST)
  valid = entry->if_info == port_priv->idx;
 else
  valid = entry->if_mask[idx / 8] & BIT(idx % 8);

 return valid;
}

static int dpaa2_switch_fdb_iterate(struct ethsw_port_priv *port_priv,
        dpaa2_switch_fdb_cb_t cb, void *data)
{
 struct net_device *net_dev = port_priv->netdev;
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct device *dev = net_dev->dev.parent;
 struct fdb_dump_entry *fdb_entries;
 struct fdb_dump_entry fdb_entry;
 dma_addr_t fdb_dump_iova;
 u16 num_fdb_entries;
 u32 fdb_dump_size;
 int err = 0, i;
 u8 *dma_mem;
 u16 fdb_id;

 fdb_dump_size = ethsw->sw_attr.max_fdb_entries * sizeof(fdb_entry);
 dma_mem = kzalloc(fdb_dump_size, GFP_KERNEL);
 if (!dma_mem)
  return -ENOMEM;

 fdb_dump_iova = dma_map_single(dev, dma_mem, fdb_dump_size,
           DMA_FROM_DEVICE);
 if (dma_mapping_error(dev, fdb_dump_iova)) {
  netdev_err(net_dev, "dma_map_single() failed\n");
  err = -ENOMEM;
  goto err_map;
 }

 fdb_id = dpaa2_switch_port_get_fdb_id(port_priv);
 err = dpsw_fdb_dump(ethsw->mc_io, 0, ethsw->dpsw_handle, fdb_id,
       fdb_dump_iova, fdb_dump_size, &num_fdb_entries);
 if (err) {
  netdev_err(net_dev, "dpsw_fdb_dump() = %d\n", err);
  goto err_dump;
 }

 dma_unmap_single(dev, fdb_dump_iova, fdb_dump_size, DMA_FROM_DEVICE);

 fdb_entries = (struct fdb_dump_entry *)dma_mem;
 for (i = 0; i < num_fdb_entries; i++) {
  fdb_entry = fdb_entries[i];

  err = cb(port_priv, &fdb_entry, data);
  if (err)
   goto end;
 }

end:
 kfree(dma_mem);

 return 0;

err_dump:
 dma_unmap_single(dev, fdb_dump_iova, fdb_dump_size, DMA_TO_DEVICE);
err_map:
 kfree(dma_mem);
 return err;
}

static int dpaa2_switch_fdb_entry_dump(struct ethsw_port_priv *port_priv,
           struct fdb_dump_entry *fdb_entry,
           void *data)
{
 if (!dpaa2_switch_port_fdb_valid_entry(fdb_entry, port_priv))
  return 0;

 return dpaa2_switch_fdb_dump_nl(fdb_entry, data);
}

static int dpaa2_switch_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
          struct net_device *net_dev,
          struct net_device *filter_dev, int *idx)
{
 struct ethsw_port_priv *port_priv = netdev_priv(net_dev);
 struct ethsw_dump_ctx dump = {
  .dev = net_dev,
  .skb = skb,
  .cb = cb,
  .idx = *idx,
 };
 int err;

 err = dpaa2_switch_fdb_iterate(port_priv, dpaa2_switch_fdb_entry_dump, &dump);
 *idx = dump.idx;

 return err;
}

static int dpaa2_switch_fdb_entry_fast_age(struct ethsw_port_priv *port_priv,
        struct fdb_dump_entry *fdb_entry,
        void *data __always_unused)
{
 if (!dpaa2_switch_port_fdb_valid_entry(fdb_entry, port_priv))
  return 0;

 if (!(fdb_entry->type & DPSW_FDB_ENTRY_TYPE_DYNAMIC))
  return 0;

 if (fdb_entry->type & DPSW_FDB_ENTRY_TYPE_UNICAST)
  dpaa2_switch_port_fdb_del_uc(port_priv, fdb_entry->mac_addr);
 else
  dpaa2_switch_port_fdb_del_mc(port_priv, fdb_entry->mac_addr);

 return 0;
}

static void dpaa2_switch_port_fast_age(struct ethsw_port_priv *port_priv)
{
 dpaa2_switch_fdb_iterate(port_priv,
     dpaa2_switch_fdb_entry_fast_age, NULL);
}

static int dpaa2_switch_port_vlan_add(struct net_device *netdev, __be16 proto,
          u16 vid)
{
 struct switchdev_obj_port_vlan vlan = {
  .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
  .vid = vid,
  .obj.orig_dev = netdev,
  /* This API only allows programming tagged, non-PVID VIDs */
  .flags = 0,
 };

 return dpaa2_switch_port_vlans_add(netdev, &vlan);
}

static int dpaa2_switch_port_vlan_kill(struct net_device *netdev, __be16 proto,
           u16 vid)
{
 struct switchdev_obj_port_vlan vlan = {
  .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
  .vid = vid,
  .obj.orig_dev = netdev,
  /* This API only allows programming tagged, non-PVID VIDs */
  .flags = 0,
 };

 return dpaa2_switch_port_vlans_del(netdev, &vlan);
}

static int dpaa2_switch_port_set_mac_addr(struct ethsw_port_priv *port_priv)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct net_device *net_dev = port_priv->netdev;
 struct device *dev = net_dev->dev.parent;
 u8 mac_addr[ETH_ALEN];
 int err;

 if (!(ethsw->features & ETHSW_FEATURE_MAC_ADDR))
  return 0;

 /* Get firmware address, if any */
 err = dpsw_if_get_port_mac_addr(ethsw->mc_io, 0, ethsw->dpsw_handle,
     port_priv->idx, mac_addr);
 if (err) {
  dev_err(dev, "dpsw_if_get_port_mac_addr() failed\n");
  return err;
 }

 /* First check if firmware has any address configured by bootloader */
 if (!is_zero_ether_addr(mac_addr)) {
  eth_hw_addr_set(net_dev, mac_addr);
 } else {
  /* No MAC address configured, fill in net_dev->dev_addr
 * with a random one
 */

  eth_hw_addr_random(net_dev);
  dev_dbg_once(dev, "device(s) have all-zero hwaddr, replaced with random\n");

  /* Override NET_ADDR_RANDOM set by eth_hw_addr_random(); for all
 * practical purposes, this will be our "permanent" mac address,
 * at least until the next reboot. This move will also permit
 * register_netdevice() to properly fill up net_dev->perm_addr.
 */

  net_dev->addr_assign_type = NET_ADDR_PERM;
 }

 return 0;
}

static void dpaa2_switch_free_fd(const struct ethsw_core *ethsw,
     const struct dpaa2_fd *fd)
{
 struct device *dev = ethsw->dev;
 unsigned char *buffer_start;
 struct sk_buff **skbh, *skb;
 dma_addr_t fd_addr;

 fd_addr = dpaa2_fd_get_addr(fd);
 skbh = dpaa2_iova_to_virt(ethsw->iommu_domain, fd_addr);

 skb = *skbh;
 buffer_start = (unsigned char *)skbh;

 dma_unmap_single(dev, fd_addr,
    skb_tail_pointer(skb) - buffer_start,
    DMA_TO_DEVICE);

 /* Move on with skb release */
 dev_kfree_skb(skb);
}

static int dpaa2_switch_build_single_fd(struct ethsw_core *ethsw,
     struct sk_buff *skb,
     struct dpaa2_fd *fd)
{
 struct device *dev = ethsw->dev;
 struct sk_buff **skbh;
 dma_addr_t addr;
 u8 *buff_start;
 void *hwa;

 buff_start = PTR_ALIGN(skb->data - DPAA2_SWITCH_TX_DATA_OFFSET -
          DPAA2_SWITCH_TX_BUF_ALIGN,
          DPAA2_SWITCH_TX_BUF_ALIGN);

 /* Clear FAS to have consistent values for TX confirmation. It is
 * located in the first 8 bytes of the buffer's hardware annotation
 * area
 */

 hwa = buff_start + DPAA2_SWITCH_SWA_SIZE;
 memset(hwa, 0, 8);

 /* Store a backpointer to the skb at the beginning of the buffer
 * (in the private data area) such that we can release it
 * on Tx confirm
 */

 skbh = (struct sk_buff **)buff_start;
 *skbh = skb;

 addr = dma_map_single(dev, buff_start,
         skb_tail_pointer(skb) - buff_start,
         DMA_TO_DEVICE);
 if (unlikely(dma_mapping_error(dev, addr)))
  return -ENOMEM;

 /* Setup the FD fields */
 memset(fd, 0, sizeof(*fd));

 dpaa2_fd_set_addr(fd, addr);
 dpaa2_fd_set_offset(fd, (u16)(skb->data - buff_start));
 dpaa2_fd_set_len(fd, skb->len);
 dpaa2_fd_set_format(fd, dpaa2_fd_single);

 return 0;
}

static netdev_tx_t dpaa2_switch_port_tx(struct sk_buff *skb,
     struct net_device *net_dev)
{
 struct ethsw_port_priv *port_priv = netdev_priv(net_dev);
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 int retries = DPAA2_SWITCH_SWP_BUSY_RETRIES;
 struct dpaa2_fd fd;
 int err;

 if (unlikely(skb_headroom(skb) < DPAA2_SWITCH_NEEDED_HEADROOM)) {
  struct sk_buff *ns;

  ns = skb_realloc_headroom(skb, DPAA2_SWITCH_NEEDED_HEADROOM);
  if (unlikely(!ns)) {
   net_err_ratelimited("%s: Error reallocating skb headroom\n", net_dev->name);
   goto err_free_skb;
  }
  dev_consume_skb_any(skb);
  skb = ns;
 }

 /* We'll be holding a back-reference to the skb until Tx confirmation */
 skb = skb_unshare(skb, GFP_ATOMIC);
 if (unlikely(!skb)) {
  /* skb_unshare() has already freed the skb */
  net_err_ratelimited("%s: Error copying the socket buffer\n", net_dev->name);
  goto err_exit;
 }

 /* At this stage, we do not support non-linear skbs so just try to
 * linearize the skb and if that's not working, just drop the packet.
 */

 err = skb_linearize(skb);
 if (err) {
  net_err_ratelimited("%s: skb_linearize error (%d)!\n", net_dev->name, err);
  goto err_free_skb;
 }

 err = dpaa2_switch_build_single_fd(ethsw, skb, &fd);
 if (unlikely(err)) {
  net_err_ratelimited("%s: ethsw_build_*_fd() %d\n", net_dev->name, err);
  goto err_free_skb;
 }

 do {
  err = dpaa2_io_service_enqueue_qd(NULL,
        port_priv->tx_qdid,
        8, 0, &fd);
  retries--;
 } while (err == -EBUSY && retries);

 if (unlikely(err < 0)) {
  dpaa2_switch_free_fd(ethsw, &fd);
  goto err_exit;
 }

 return NETDEV_TX_OK;

err_free_skb:
 dev_kfree_skb(skb);
err_exit:
 return NETDEV_TX_OK;
}

static int
dpaa2_switch_setup_tc_cls_flower(struct dpaa2_switch_filter_block *filter_block,
     struct flow_cls_offload *f)
{
 switch (f->command) {
 case FLOW_CLS_REPLACE:
  return dpaa2_switch_cls_flower_replace(filter_block, f);
 case FLOW_CLS_DESTROY:
  return dpaa2_switch_cls_flower_destroy(filter_block, f);
 default:
  return -EOPNOTSUPP;
 }
}

static int
dpaa2_switch_setup_tc_cls_matchall(struct dpaa2_switch_filter_block *block,
       struct tc_cls_matchall_offload *f)
{
 switch (f->command) {
 case TC_CLSMATCHALL_REPLACE:
  return dpaa2_switch_cls_matchall_replace(block, f);
 case TC_CLSMATCHALL_DESTROY:
  return dpaa2_switch_cls_matchall_destroy(block, f);
 default:
  return -EOPNOTSUPP;
 }
}

static int dpaa2_switch_port_setup_tc_block_cb_ig(enum tc_setup_type type,
        void *type_data,
        void *cb_priv)
{
 switch (type) {
 case TC_SETUP_CLSFLOWER:
  return dpaa2_switch_setup_tc_cls_flower(cb_priv, type_data);
 case TC_SETUP_CLSMATCHALL:
  return dpaa2_switch_setup_tc_cls_matchall(cb_priv, type_data);
 default:
  return -EOPNOTSUPP;
 }
}

static LIST_HEAD(dpaa2_switch_block_cb_list);

static int
dpaa2_switch_port_acl_tbl_bind(struct ethsw_port_priv *port_priv,
          struct dpaa2_switch_filter_block *block)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct net_device *netdev = port_priv->netdev;
 struct dpsw_acl_if_cfg acl_if_cfg;
 int err;

 if (port_priv->filter_block)
  return -EINVAL;

 acl_if_cfg.if_id[0] = port_priv->idx;
 acl_if_cfg.num_ifs = 1;
 err = dpsw_acl_add_if(ethsw->mc_io, 0, ethsw->dpsw_handle,
         block->acl_id, &acl_if_cfg);
 if (err) {
  netdev_err(netdev, "dpsw_acl_add_if err %d\n", err);
  return err;
 }

 block->ports |= BIT(port_priv->idx);
 port_priv->filter_block = block;

 return 0;
}

static int
dpaa2_switch_port_acl_tbl_unbind(struct ethsw_port_priv *port_priv,
     struct dpaa2_switch_filter_block *block)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct net_device *netdev = port_priv->netdev;
 struct dpsw_acl_if_cfg acl_if_cfg;
 int err;

 if (port_priv->filter_block != block)
  return -EINVAL;

 acl_if_cfg.if_id[0] = port_priv->idx;
 acl_if_cfg.num_ifs = 1;
 err = dpsw_acl_remove_if(ethsw->mc_io, 0, ethsw->dpsw_handle,
     block->acl_id, &acl_if_cfg);
 if (err) {
  netdev_err(netdev, "dpsw_acl_add_if err %d\n", err);
  return err;
 }

 block->ports &= ~BIT(port_priv->idx);
 port_priv->filter_block = NULL;
 return 0;
}

static int dpaa2_switch_port_block_bind(struct ethsw_port_priv *port_priv,
     struct dpaa2_switch_filter_block *block)
{
 struct dpaa2_switch_filter_block *old_block = port_priv->filter_block;
 int err;

 /* Offload all the mirror entries found in the block on this new port
 * joining it.
 */

 err = dpaa2_switch_block_offload_mirror(block, port_priv);
 if (err)
  return err;

 /* If the port is already bound to this ACL table then do nothing. This
 * can happen when this port is the first one to join a tc block
 */

 if (port_priv->filter_block == block)
  return 0;

 err = dpaa2_switch_port_acl_tbl_unbind(port_priv, old_block);
 if (err)
  return err;

 /* Mark the previous ACL table as being unused if this was the last
 * port that was using it.
 */

 if (old_block->ports == 0)
  old_block->in_use = false;

 return dpaa2_switch_port_acl_tbl_bind(port_priv, block);
}

static int
dpaa2_switch_port_block_unbind(struct ethsw_port_priv *port_priv,
          struct dpaa2_switch_filter_block *block)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct dpaa2_switch_filter_block *new_block;
 int err;

 /* Unoffload all the mirror entries found in the block from the
 * port leaving it.
 */

 err = dpaa2_switch_block_unoffload_mirror(block, port_priv);
 if (err)
  return err;

 /* We are the last port that leaves a block (an ACL table).
 * We'll continue to use this table.
 */

 if (block->ports == BIT(port_priv->idx))
  return 0;

 err = dpaa2_switch_port_acl_tbl_unbind(port_priv, block);
 if (err)
  return err;

 if (block->ports == 0)
  block->in_use = false;

 new_block = dpaa2_switch_filter_block_get_unused(ethsw);
 new_block->in_use = true;
 return dpaa2_switch_port_acl_tbl_bind(port_priv, new_block);
}

static int dpaa2_switch_setup_tc_block_bind(struct net_device *netdev,
         struct flow_block_offload *f)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct dpaa2_switch_filter_block *filter_block;
 struct flow_block_cb *block_cb;
 bool register_block = false;
 int err;

 block_cb = flow_block_cb_lookup(f->block,
     dpaa2_switch_port_setup_tc_block_cb_ig,
     ethsw);

 if (!block_cb) {
  /* If the filter block is not already known, then this port
 * must be the first to join it. In this case, we can just
 * continue to use our private table
 */

  filter_block = port_priv->filter_block;

  block_cb = flow_block_cb_alloc(dpaa2_switch_port_setup_tc_block_cb_ig,
            ethsw, filter_block, NULL);
  if (IS_ERR(block_cb))
   return PTR_ERR(block_cb);

  register_block = true;
 } else {
  filter_block = flow_block_cb_priv(block_cb);
 }

 flow_block_cb_incref(block_cb);
 err = dpaa2_switch_port_block_bind(port_priv, filter_block);
 if (err)
  goto err_block_bind;

 if (register_block) {
  flow_block_cb_add(block_cb, f);
  list_add_tail(&block_cb->driver_list,
         &dpaa2_switch_block_cb_list);
 }

 return 0;

err_block_bind:
 if (!flow_block_cb_decref(block_cb))
  flow_block_cb_free(block_cb);
 return err;
}

static void dpaa2_switch_setup_tc_block_unbind(struct net_device *netdev,
            struct flow_block_offload *f)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct dpaa2_switch_filter_block *filter_block;
 struct flow_block_cb *block_cb;
 int err;

 block_cb = flow_block_cb_lookup(f->block,
     dpaa2_switch_port_setup_tc_block_cb_ig,
     ethsw);
 if (!block_cb)
  return;

 filter_block = flow_block_cb_priv(block_cb);
 err = dpaa2_switch_port_block_unbind(port_priv, filter_block);
 if (!err && !flow_block_cb_decref(block_cb)) {
  flow_block_cb_remove(block_cb, f);
  list_del(&block_cb->driver_list);
 }
}

static int dpaa2_switch_setup_tc_block(struct net_device *netdev,
           struct flow_block_offload *f)
{
 if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
  return -EOPNOTSUPP;

 f->driver_block_list = &dpaa2_switch_block_cb_list;

 switch (f->command) {
 case FLOW_BLOCK_BIND:
  return dpaa2_switch_setup_tc_block_bind(netdev, f);
 case FLOW_BLOCK_UNBIND:
  dpaa2_switch_setup_tc_block_unbind(netdev, f);
  return 0;
 default:
  return -EOPNOTSUPP;
 }
}

static int dpaa2_switch_port_setup_tc(struct net_device *netdev,
          enum tc_setup_type type,
          void *type_data)
{
 switch (type) {
 case TC_SETUP_BLOCK: {
  return dpaa2_switch_setup_tc_block(netdev, type_data);
 }
 default:
  return -EOPNOTSUPP;
 }

 return 0;
}

static const struct net_device_ops dpaa2_switch_port_ops = {
 .ndo_open  = dpaa2_switch_port_open,
 .ndo_stop  = dpaa2_switch_port_stop,

 .ndo_set_mac_address = eth_mac_addr,
 .ndo_get_stats64 = dpaa2_switch_port_get_stats,
 .ndo_change_mtu  = dpaa2_switch_port_change_mtu,
 .ndo_has_offload_stats = dpaa2_switch_port_has_offload_stats,
 .ndo_get_offload_stats = dpaa2_switch_port_get_offload_stats,
 .ndo_fdb_dump  = dpaa2_switch_port_fdb_dump,
 .ndo_vlan_rx_add_vid = dpaa2_switch_port_vlan_add,
 .ndo_vlan_rx_kill_vid = dpaa2_switch_port_vlan_kill,

 .ndo_start_xmit  = dpaa2_switch_port_tx,
 .ndo_get_port_parent_id = dpaa2_switch_port_parent_id,
 .ndo_get_phys_port_name = dpaa2_switch_port_get_phys_name,
 .ndo_setup_tc  = dpaa2_switch_port_setup_tc,
};

bool dpaa2_switch_port_dev_check(const struct net_device *netdev)
{
 return netdev->netdev_ops == &dpaa2_switch_port_ops;
}

static int dpaa2_switch_port_connect_mac(struct ethsw_port_priv *port_priv)
{
 struct fsl_mc_device *dpsw_port_dev, *dpmac_dev;
 struct dpaa2_mac *mac;
 int err;

 dpsw_port_dev = to_fsl_mc_device(port_priv->netdev->dev.parent);
 dpmac_dev = fsl_mc_get_endpoint(dpsw_port_dev, port_priv->idx);

 if (PTR_ERR(dpmac_dev) == -EPROBE_DEFER)
  return PTR_ERR(dpmac_dev);

 if (IS_ERR(dpmac_dev))
  return 0;

 if (dpmac_dev->dev.type != &fsl_mc_bus_dpmac_type) {
  err = 0;
  goto out_put_device;
 }

 mac = kzalloc(sizeof(*mac), GFP_KERNEL);
 if (!mac) {
  err = -ENOMEM;
  goto out_put_device;
 }

 mac->mc_dev = dpmac_dev;
 mac->mc_io = port_priv->ethsw_data->mc_io;
 mac->net_dev = port_priv->netdev;

 err = dpaa2_mac_open(mac);
 if (err)
  goto err_free_mac;

 if (dpaa2_mac_is_type_phy(mac)) {
  err = dpaa2_mac_connect(mac);
  if (err) {
   netdev_err(port_priv->netdev,
       "Error connecting to the MAC endpoint %pe\n",
       ERR_PTR(err));
   goto err_close_mac;
  }
 }

 mutex_lock(&port_priv->mac_lock);
 port_priv->mac = mac;
 mutex_unlock(&port_priv->mac_lock);

 return 0;

err_close_mac:
 dpaa2_mac_close(mac);
err_free_mac:
 kfree(mac);
out_put_device:
 put_device(&dpmac_dev->dev);
 return err;
}

static void dpaa2_switch_port_disconnect_mac(struct ethsw_port_priv *port_priv)
{
 struct dpaa2_mac *mac;

 mutex_lock(&port_priv->mac_lock);
 mac = port_priv->mac;
 port_priv->mac = NULL;
 mutex_unlock(&port_priv->mac_lock);

 if (!mac)
  return;

 if (dpaa2_mac_is_type_phy(mac))
  dpaa2_mac_disconnect(mac);

 dpaa2_mac_close(mac);
 kfree(mac);
}

static irqreturn_t dpaa2_switch_irq0_handler_thread(int irq_num, void *arg)
{
 struct device *dev = (struct device *)arg;
 struct ethsw_core *ethsw = dev_get_drvdata(dev);
 struct ethsw_port_priv *port_priv;
 int err, if_id;
 bool had_mac;
 u32 status;

 err = dpsw_get_irq_status(ethsw->mc_io, 0, ethsw->dpsw_handle,
      DPSW_IRQ_INDEX_IF, &status);
 if (err) {
  dev_err(dev, "Can't get irq status (err %d)\n", err);
  goto out;
 }

 if_id = (status & 0xFFFF0000) >> 16;
 port_priv = ethsw->ports[if_id];

 if (status & DPSW_IRQ_EVENT_LINK_CHANGED)
  dpaa2_switch_port_link_state_update(port_priv->netdev);

 if (status & DPSW_IRQ_EVENT_ENDPOINT_CHANGED) {
  dpaa2_switch_port_set_mac_addr(port_priv);
  /* We can avoid locking because the "endpoint changed" IRQ
 * handler is the only one who changes priv->mac at runtime,
 * so we are not racing with anyone.
 */

  had_mac = !!port_priv->mac;
  if (had_mac)
   dpaa2_switch_port_disconnect_mac(port_priv);
  else
   dpaa2_switch_port_connect_mac(port_priv);
 }

 err = dpsw_clear_irq_status(ethsw->mc_io, 0, ethsw->dpsw_handle,
        DPSW_IRQ_INDEX_IF, status);
 if (err)
  dev_err(dev, "Can't clear irq status (err %d)\n", err);

out:
 return IRQ_HANDLED;
}

static int dpaa2_switch_setup_irqs(struct fsl_mc_device *sw_dev)
{
 u32 mask = DPSW_IRQ_EVENT_LINK_CHANGED | DPSW_IRQ_EVENT_ENDPOINT_CHANGED;
 struct device *dev = &sw_dev->dev;
 struct ethsw_core *ethsw = dev_get_drvdata(dev);
 struct fsl_mc_device_irq *irq;
 int err;

 err = fsl_mc_allocate_irqs(sw_dev);
 if (err) {
  dev_err(dev, "MC irqs allocation failed\n");
  return err;
 }

 if (WARN_ON(sw_dev->obj_desc.irq_count != DPSW_IRQ_NUM)) {
  err = -EINVAL;
  goto free_irq;
 }

 err = dpsw_set_irq_enable(ethsw->mc_io, 0, ethsw->dpsw_handle,
      DPSW_IRQ_INDEX_IF, 0);
 if (err) {
  dev_err(dev, "dpsw_set_irq_enable err %d\n", err);
  goto free_irq;
 }

 irq = sw_dev->irqs[DPSW_IRQ_INDEX_IF];

 err = devm_request_threaded_irq(dev, irq->virq, NULL,
     dpaa2_switch_irq0_handler_thread,
     IRQF_NO_SUSPEND | IRQF_ONESHOT,
     dev_name(dev), dev);
 if (err) {
  dev_err(dev, "devm_request_threaded_irq(): %d\n", err);
  goto free_irq;
 }

 err = dpsw_set_irq_mask(ethsw->mc_io, 0, ethsw->dpsw_handle,
    DPSW_IRQ_INDEX_IF, mask);
 if (err) {
  dev_err(dev, "dpsw_set_irq_mask(): %d\n", err);
  goto free_devm_irq;
 }

 err = dpsw_set_irq_enable(ethsw->mc_io, 0, ethsw->dpsw_handle,
      DPSW_IRQ_INDEX_IF, 1);
 if (err) {
  dev_err(dev, "dpsw_set_irq_enable(): %d\n", err);
  goto free_devm_irq;
 }

 return 0;

free_devm_irq:
 devm_free_irq(dev, irq->virq, dev);
free_irq:
 fsl_mc_free_irqs(sw_dev);
 return err;
}

static void dpaa2_switch_teardown_irqs(struct fsl_mc_device *sw_dev)
{
 struct device *dev = &sw_dev->dev;
 struct ethsw_core *ethsw = dev_get_drvdata(dev);
 int err;

 err = dpsw_set_irq_enable(ethsw->mc_io, 0, ethsw->dpsw_handle,
      DPSW_IRQ_INDEX_IF, 0);
 if (err)
  dev_err(dev, "dpsw_set_irq_enable err %d\n", err);

 fsl_mc_free_irqs(sw_dev);
}

static int dpaa2_switch_port_set_learning(struct ethsw_port_priv *port_priv, bool enable)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 enum dpsw_learning_mode learn_mode;
 int err;

 if (enable)
  learn_mode = DPSW_LEARNING_MODE_HW;
 else
  learn_mode = DPSW_LEARNING_MODE_DIS;

 err = dpsw_if_set_learning_mode(ethsw->mc_io, 0, ethsw->dpsw_handle,
     port_priv->idx, learn_mode);
 if (err)
  netdev_err(port_priv->netdev, "dpsw_if_set_learning_mode err %d\n", err);

 if (!enable)
  dpaa2_switch_port_fast_age(port_priv);

 return err;
}

static int dpaa2_switch_port_attr_stp_state_set(struct net_device *netdev,
      u8 state)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 int err;

 err = dpaa2_switch_port_set_stp_state(port_priv, state);
 if (err)
  return err;

 switch (state) {
 case BR_STATE_DISABLED:
 case BR_STATE_BLOCKING:
 case BR_STATE_LISTENING:
  err = dpaa2_switch_port_set_learning(port_priv, false);
  break;
 case BR_STATE_LEARNING:
 case BR_STATE_FORWARDING:
  err = dpaa2_switch_port_set_learning(port_priv,
           port_priv->learn_ena);
  break;
 }

 return err;
}

static int dpaa2_switch_port_flood(struct ethsw_port_priv *port_priv,
       struct switchdev_brport_flags flags)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;

 if (flags.mask & BR_BCAST_FLOOD)
  port_priv->bcast_flood = !!(flags.val & BR_BCAST_FLOOD);

 if (flags.mask & BR_FLOOD)
  port_priv->ucast_flood = !!(flags.val & BR_FLOOD);

 return dpaa2_switch_fdb_set_egress_flood(ethsw, port_priv->fdb->fdb_id);
}

static int dpaa2_switch_port_pre_bridge_flags(struct net_device *netdev,
           struct switchdev_brport_flags flags,
           struct netlink_ext_ack *extack)
{
 if (flags.mask & ~(BR_LEARNING | BR_BCAST_FLOOD | BR_FLOOD |
      BR_MCAST_FLOOD))
  return -EINVAL;

 if (flags.mask & (BR_FLOOD | BR_MCAST_FLOOD)) {
  bool multicast = !!(flags.val & BR_MCAST_FLOOD);
  bool unicast = !!(flags.val & BR_FLOOD);

  if (unicast != multicast) {
   NL_SET_ERR_MSG_MOD(extack,
        "Cannot configure multicast flooding independently of unicast");
   return -EINVAL;
  }
 }

 return 0;
}

static int dpaa2_switch_port_bridge_flags(struct net_device *netdev,
       struct switchdev_brport_flags flags,
       struct netlink_ext_ack *extack)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 int err;

 if (flags.mask & BR_LEARNING) {
  bool learn_ena = !!(flags.val & BR_LEARNING);

  err = dpaa2_switch_port_set_learning(port_priv, learn_ena);
  if (err)
   return err;
  port_priv->learn_ena = learn_ena;
 }

 if (flags.mask & (BR_BCAST_FLOOD | BR_FLOOD | BR_MCAST_FLOOD)) {
  err = dpaa2_switch_port_flood(port_priv, flags);
  if (err)
   return err;
 }

 return 0;
}

static int dpaa2_switch_port_attr_set(struct net_device *netdev, const void *ctx,
          const struct switchdev_attr *attr,
          struct netlink_ext_ack *extack)
{
 int err = 0;

 switch (attr->id) {
 case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
  err = dpaa2_switch_port_attr_stp_state_set(netdev,
          attr->u.stp_state);
  break;
 case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
  if (!attr->u.vlan_filtering) {
   NL_SET_ERR_MSG_MOD(extack,
        "The DPAA2 switch does not support VLAN-unaware operation");
   return -EOPNOTSUPP;
  }
  break;
 case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
  err = dpaa2_switch_port_pre_bridge_flags(netdev, attr->u.brport_flags, extack);
  break;
 case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
  err = dpaa2_switch_port_bridge_flags(netdev, attr->u.brport_flags, extack);
  break;
 default:
  err = -EOPNOTSUPP;
  break;
 }

 return err;
}

int dpaa2_switch_port_vlans_add(struct net_device *netdev,
    const struct switchdev_obj_port_vlan *vlan)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct dpsw_attr *attr = ðsw->sw_attr;
 int err = 0;

 /* Make sure that the VLAN is not already configured
 * on the switch port
 */

 if (port_priv->vlans[vlan->vid] & ETHSW_VLAN_MEMBER) {
  netdev_err(netdev, "VLAN %d already configured\n", vlan->vid);
  return -EEXIST;
 }

 /* Check if there is space for a new VLAN */
 err = dpsw_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle,
      ðsw->sw_attr);
 if (err) {
  netdev_err(netdev, "dpsw_get_attributes err %d\n", err);
  return err;
 }
 if (attr->max_vlans - attr->num_vlans < 1)
  return -ENOSPC;

 /* Check if there is space for a new VLAN */
 err = dpsw_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle,
      ðsw->sw_attr);
 if (err) {
  netdev_err(netdev, "dpsw_get_attributes err %d\n", err);
  return err;
 }
 if (attr->max_vlans - attr->num_vlans < 1)
  return -ENOSPC;

 if (!port_priv->ethsw_data->vlans[vlan->vid]) {
  /* this is a new VLAN */
  err = dpaa2_switch_add_vlan(port_priv, vlan->vid);
  if (err)
   return err;

  port_priv->ethsw_data->vlans[vlan->vid] |= ETHSW_VLAN_GLOBAL;
 }

 return dpaa2_switch_port_add_vlan(port_priv, vlan->vid, vlan->flags);
}

static int dpaa2_switch_port_lookup_address(struct net_device *netdev, int is_uc,
         const unsigned char *addr)
{
 struct netdev_hw_addr_list *list = (is_uc) ? &netdev->uc : &netdev->mc;
 struct netdev_hw_addr *ha;

 netif_addr_lock_bh(netdev);
 list_for_each_entry(ha, &list->list, list) {
  if (ether_addr_equal(ha->addr, addr)) {
   netif_addr_unlock_bh(netdev);
   return 1;
  }
 }
 netif_addr_unlock_bh(netdev);
 return 0;
}

static int dpaa2_switch_port_mdb_add(struct net_device *netdev,
         const struct switchdev_obj_port_mdb *mdb)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 int err;

 /* Check if address is already set on this port */
 if (dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr))
  return -EEXIST;

 err = dpaa2_switch_port_fdb_add_mc(port_priv, mdb->addr);
 if (err)
  return err;

 err = dev_mc_add(netdev, mdb->addr);
 if (err) {
  netdev_err(netdev, "dev_mc_add err %d\n", err);
  dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr);
 }

 return err;
}

static int dpaa2_switch_port_obj_add(struct net_device *netdev,
         const struct switchdev_obj *obj)
{
 int err;

 switch (obj->id) {
 case SWITCHDEV_OBJ_ID_PORT_VLAN:
  err = dpaa2_switch_port_vlans_add(netdev,
        SWITCHDEV_OBJ_PORT_VLAN(obj));
  break;
 case SWITCHDEV_OBJ_ID_PORT_MDB:
  err = dpaa2_switch_port_mdb_add(netdev,
      SWITCHDEV_OBJ_PORT_MDB(obj));
  break;
 default:
  err = -EOPNOTSUPP;
  break;
 }

 return err;
}

static int dpaa2_switch_port_del_vlan(struct ethsw_port_priv *port_priv, u16 vid)
{
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 struct net_device *netdev = port_priv->netdev;
 struct dpsw_vlan_if_cfg vcfg;
 int i, err;

 if (!port_priv->vlans[vid])
  return -ENOENT;

 if (port_priv->vlans[vid] & ETHSW_VLAN_PVID) {
  /* If we are deleting the PVID of a port, use VLAN 4095 instead
 * as we are sure that neither the bridge nor the 8021q module
 * will use it
 */

  err = dpaa2_switch_port_set_pvid(port_priv, 4095);
  if (err)
   return err;
 }

 vcfg.num_ifs = 1;
 vcfg.if_id[0] = port_priv->idx;
 if (port_priv->vlans[vid] & ETHSW_VLAN_UNTAGGED) {
  err = dpsw_vlan_remove_if_untagged(ethsw->mc_io, 0,
         ethsw->dpsw_handle,
         vid, &vcfg);
  if (err) {
   netdev_err(netdev,
       "dpsw_vlan_remove_if_untagged err %d\n",
       err);
  }
  port_priv->vlans[vid] &= ~ETHSW_VLAN_UNTAGGED;
 }

 if (port_priv->vlans[vid] & ETHSW_VLAN_MEMBER) {
  err = dpsw_vlan_remove_if(ethsw->mc_io, 0, ethsw->dpsw_handle,
       vid, &vcfg);
  if (err) {
   netdev_err(netdev,
       "dpsw_vlan_remove_if err %d\n", err);
   return err;
  }
  port_priv->vlans[vid] &= ~ETHSW_VLAN_MEMBER;

  /* Delete VLAN from switch if it is no longer configured on
 * any port
 */

  for (i = 0; i < ethsw->sw_attr.num_ifs; i++) {
   if (ethsw->ports[i] &&
       ethsw->ports[i]->vlans[vid] & ETHSW_VLAN_MEMBER)
    return 0; /* Found a port member in VID */
  }

  ethsw->vlans[vid] &= ~ETHSW_VLAN_GLOBAL;

  err = dpaa2_switch_dellink(ethsw, vid);
  if (err)
   return err;
 }

 return 0;
}

int dpaa2_switch_port_vlans_del(struct net_device *netdev,
    const struct switchdev_obj_port_vlan *vlan)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);

 if (netif_is_bridge_master(vlan->obj.orig_dev))
  return -EOPNOTSUPP;

 return dpaa2_switch_port_del_vlan(port_priv, vlan->vid);
}

static int dpaa2_switch_port_mdb_del(struct net_device *netdev,
         const struct switchdev_obj_port_mdb *mdb)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 int err;

 if (!dpaa2_switch_port_lookup_address(netdev, 0, mdb->addr))
  return -ENOENT;

 err = dpaa2_switch_port_fdb_del_mc(port_priv, mdb->addr);
 if (err)
  return err;

 err = dev_mc_del(netdev, mdb->addr);
 if (err) {
  netdev_err(netdev, "dev_mc_del err %d\n", err);
  return err;
 }

 return err;
}

static int dpaa2_switch_port_obj_del(struct net_device *netdev,
         const struct switchdev_obj *obj)
{
 int err;

 switch (obj->id) {
 case SWITCHDEV_OBJ_ID_PORT_VLAN:
  err = dpaa2_switch_port_vlans_del(netdev, SWITCHDEV_OBJ_PORT_VLAN(obj));
  break;
 case SWITCHDEV_OBJ_ID_PORT_MDB:
  err = dpaa2_switch_port_mdb_del(netdev, SWITCHDEV_OBJ_PORT_MDB(obj));
  break;
 default:
  err = -EOPNOTSUPP;
  break;
 }
 return err;
}

static int dpaa2_switch_port_attr_set_event(struct net_device *netdev,
         struct switchdev_notifier_port_attr_info *ptr)
{
 int err;

 err = switchdev_handle_port_attr_set(netdev, ptr,
          dpaa2_switch_port_dev_check,
          dpaa2_switch_port_attr_set);
 return notifier_from_errno(err);
}

static int dpaa2_switch_port_bridge_join(struct net_device *netdev,
      struct net_device *upper_dev,
      struct netlink_ext_ack *extack)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct dpaa2_switch_fdb *old_fdb = port_priv->fdb;
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 bool learn_ena;
 int err;

 /* Delete the previously manually installed VLAN 1 */
 err = dpaa2_switch_port_del_vlan(port_priv, 1);
 if (err)
  return err;

 dpaa2_switch_port_set_fdb(port_priv, upper_dev);

 /* Inherit the initial bridge port learning state */
 learn_ena = br_port_flag_is_set(netdev, BR_LEARNING);
 err = dpaa2_switch_port_set_learning(port_priv, learn_ena);
 port_priv->learn_ena = learn_ena;

 /* Setup the egress flood policy (broadcast, unknown unicast) */
 err = dpaa2_switch_fdb_set_egress_flood(ethsw, port_priv->fdb->fdb_id);
 if (err)
  goto err_egress_flood;

 /* Recreate the egress flood domain of the FDB that we just left. */
 err = dpaa2_switch_fdb_set_egress_flood(ethsw, old_fdb->fdb_id);
 if (err)
  goto err_egress_flood;

 err = switchdev_bridge_port_offload(netdev, netdev, NULL,
         NULL, NULL, false, extack);
 if (err)
  goto err_switchdev_offload;

 return 0;

err_switchdev_offload:
err_egress_flood:
 dpaa2_switch_port_set_fdb(port_priv, NULL);
 return err;
}

static int dpaa2_switch_port_clear_rxvlan(struct net_device *vdev, int vid, void *arg)
{
 __be16 vlan_proto = htons(ETH_P_8021Q);

 if (vdev)
  vlan_proto = vlan_dev_vlan_proto(vdev);

 return dpaa2_switch_port_vlan_kill(arg, vlan_proto, vid);
}

static int dpaa2_switch_port_restore_rxvlan(struct net_device *vdev, int vid, void *arg)
{
 __be16 vlan_proto = htons(ETH_P_8021Q);

 if (vdev)
  vlan_proto = vlan_dev_vlan_proto(vdev);

 return dpaa2_switch_port_vlan_add(arg, vlan_proto, vid);
}

static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev)
{
 switchdev_bridge_port_unoffload(netdev, NULL, NULL, NULL);
}

static int dpaa2_switch_port_bridge_leave(struct net_device *netdev)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct dpaa2_switch_fdb *old_fdb = port_priv->fdb;
 struct ethsw_core *ethsw = port_priv->ethsw_data;
 int err;

 /* First of all, fast age any learn FDB addresses on this switch port */
 dpaa2_switch_port_fast_age(port_priv);

 /* Clear all RX VLANs installed through vlan_vid_add() either as VLAN
 * upper devices or otherwise from the FDB table that we are about to
 * leave
 */

 err = vlan_for_each(netdev, dpaa2_switch_port_clear_rxvlan, netdev);
 if (err)
  netdev_err(netdev, "Unable to clear RX VLANs from old FDB table, err (%d)\n", err);

 dpaa2_switch_port_set_fdb(port_priv, NULL);

 /* Restore all RX VLANs into the new FDB table that we just joined */
 err = vlan_for_each(netdev, dpaa2_switch_port_restore_rxvlan, netdev);
 if (err)
  netdev_err(netdev, "Unable to restore RX VLANs to the new FDB, err (%d)\n", err);

 /* Reset the flooding state to denote that this port can send any
 * packet in standalone mode. With this, we are also ensuring that any
 * later bridge join will have the flooding flag on.
 */

 port_priv->bcast_flood = true;
 port_priv->ucast_flood = true;

 /* Setup the egress flood policy (broadcast, unknown unicast).
 * When the port is not under a bridge, only the CTRL interface is part
 * of the flooding domain besides the actual port
 */

 err = dpaa2_switch_fdb_set_egress_flood(ethsw, port_priv->fdb->fdb_id);
 if (err)
  return err;

 /* Recreate the egress flood domain of the FDB that we just left */
 err = dpaa2_switch_fdb_set_egress_flood(ethsw, old_fdb->fdb_id);
 if (err)
  return err;

 /* No HW learning when not under a bridge */
 err = dpaa2_switch_port_set_learning(port_priv, false);
 if (err)
  return err;
 port_priv->learn_ena = false;

 /* Add the VLAN 1 as PVID when not under a bridge. We need this since
 * the dpaa2 switch interfaces are not capable to be VLAN unaware
 */

 return dpaa2_switch_port_add_vlan(port_priv, DEFAULT_VLAN_ID,
       BRIDGE_VLAN_INFO_UNTAGGED | BRIDGE_VLAN_INFO_PVID);
}

static int dpaa2_switch_prevent_bridging_with_8021q_upper(struct net_device *netdev)
{
 struct net_device *upper_dev;
 struct list_head *iter;

 /* RCU read lock not necessary because we have write-side protection
 * (rtnl_mutex), however a non-rcu iterator does not exist.
 */

 netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter)
  if (is_vlan_dev(upper_dev))
   return -EOPNOTSUPP;

 return 0;
}

static int
dpaa2_switch_prechangeupper_sanity_checks(struct net_device *netdev,
       struct net_device *upper_dev,
       struct netlink_ext_ack *extack)
{
 struct ethsw_port_priv *port_priv = netdev_priv(netdev);
 struct ethsw_port_priv *other_port_priv;
 struct net_device *other_dev;
 struct list_head *iter;
 int err;

 if (!br_vlan_enabled(upper_dev)) {
  NL_SET_ERR_MSG_MOD(extack, "Cannot join a VLAN-unaware bridge");
  return -EOPNOTSUPP;
 }

 err = dpaa2_switch_prevent_bridging_with_8021q_upper(netdev);
 if (err) {
  NL_SET_ERR_MSG_MOD(extack,
       "Cannot join a bridge while VLAN uppers are present");
  return 0;
 }

 netdev_for_each_lower_dev(upper_dev, other_dev, iter) {
  if (!dpaa2_switch_port_dev_check(other_dev))
   continue;

  other_port_priv = netdev_priv(other_dev);
  if (other_port_priv->ethsw_data != port_priv->ethsw_data) {
   NL_SET_ERR_MSG_MOD(extack,
        "Interface from a different DPSW is in the bridge already");
   return -EINVAL;
  }
 }

 return 0;
}

static int dpaa2_switch_port_prechangeupper(struct net_device *netdev,
         struct netdev_notifier_changeupper_info *info)
{
 struct netlink_ext_ack *extack;
 struct net_device *upper_dev;
 int err;

 if (!dpaa2_switch_port_dev_check(netdev))
  return 0;

 extack = netdev_notifier_info_to_extack(&info->info);
 upper_dev = info->upper_dev;
 if (netif_is_bridge_master(upper_dev)) {
  err = dpaa2_switch_prechangeupper_sanity_checks(netdev,
        upper_dev,
        extack);
  if (err)
   return err;

  if (!info->linking)
   dpaa2_switch_port_pre_bridge_leave(netdev);
 }

 return 0;
}

static int dpaa2_switch_port_changeupper(struct net_device *netdev,
      struct netdev_notifier_changeupper_info *info)
{
 struct netlink_ext_ack *extack;
 struct net_device *upper_dev;

 if (!dpaa2_switch_port_dev_check(netdev))
  return 0;

 extack = netdev_notifier_info_to_extack(&info->info);

 upper_dev = info->upper_dev;
 if (netif_is_bridge_master(upper_dev)) {
  if (info->linking)
   return dpaa2_switch_port_bridge_join(netdev,
            upper_dev,
            extack);
  else
   return dpaa2_switch_port_bridge_leave(netdev);
 }

 return 0;
}

static int dpaa2_switch_port_netdevice_event(struct notifier_block *nb,
          unsigned long event, void *ptr)
{
 struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
 int err = 0;

 switch (event) {
 case NETDEV_PRECHANGEUPPER:
  err = dpaa2_switch_port_prechangeupper(netdev, ptr);
  if (err)
   return notifier_from_errno(err);

  break;
 case NETDEV_CHANGEUPPER:
  err = dpaa2_switch_port_changeupper(netdev, ptr);
  if (err)
   return notifier_from_errno(err);

  break;
 }

 return NOTIFY_DONE;
}

struct ethsw_switchdev_event_work {
 struct work_struct work;
 struct switchdev_notifier_fdb_info fdb_info;
 struct net_device *dev;
 unsigned long event;
};

static void dpaa2_switch_event_work(struct work_struct *work)
{
 struct ethsw_switchdev_event_work *switchdev_work =
  container_of(work, struct ethsw_switchdev_event_work, work);
 struct net_device *dev = switchdev_work->dev;
 struct switchdev_notifier_fdb_info *fdb_info;
 int err;

 rtnl_lock();
 fdb_info = &switchdev_work->fdb_info;

 switch (switchdev_work->event) {
 case SWITCHDEV_FDB_ADD_TO_DEVICE:
  if (!fdb_info->added_by_user || fdb_info->is_local)
   break;
  if (is_unicast_ether_addr(fdb_info->addr))
   err = dpaa2_switch_port_fdb_add_uc(netdev_priv(dev),
          fdb_info->addr);
  else
   err = dpaa2_switch_port_fdb_add_mc(netdev_priv(dev),
          fdb_info->addr);
  if (err)
   break;
  fdb_info->offloaded = true;
  call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev,
      &fdb_info->info, NULL);
  break;
 case SWITCHDEV_FDB_DEL_TO_DEVICE:
  if (!fdb_info->added_by_user || fdb_info->is_local)
   break;
  if (is_unicast_ether_addr(fdb_info->addr))
   dpaa2_switch_port_fdb_del_uc(netdev_priv(dev), fdb_info->addr);
  else
   dpaa2_switch_port_fdb_del_mc(netdev_priv(dev), fdb_info->addr);
  break;
 }

 rtnl_unlock();
 kfree(switchdev_work->fdb_info.addr);
 kfree(switchdev_work);
 dev_put(dev);
}

/* Called under rcu_read_lock() */
static int dpaa2_switch_port_event(struct notifier_block *nb,
       unsigned long event, void *ptr)
{
 struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
 struct ethsw_port_priv *port_priv = netdev_priv(dev);
 struct ethsw_switchdev_event_work *switchdev_work;
 struct switchdev_notifier_fdb_info *fdb_info = ptr;
 struct ethsw_core *ethsw = port_priv->ethsw_data;

 if (event == SWITCHDEV_PORT_ATTR_SET)
  return dpaa2_switch_port_attr_set_event(dev, ptr);

 if (!dpaa2_switch_port_dev_check(dev))
  return NOTIFY_DONE;

 switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
 if (!switchdev_work)
  return NOTIFY_BAD;

 INIT_WORK(&switchdev_work->work, dpaa2_switch_event_work);
 switchdev_work->dev = dev;
 switchdev_work->event = event;

 switch (event) {
 case SWITCHDEV_FDB_ADD_TO_DEVICE:
 case SWITCHDEV_FDB_DEL_TO_DEVICE:
  memcpy(&switchdev_work->fdb_info, ptr,
         sizeof(switchdev_work->fdb_info));
  switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
  if (!switchdev_work->fdb_info.addr)
   goto err_addr_alloc;

  ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
    fdb_info->addr);

  /* Take a reference on the device to avoid being freed. */
  dev_hold(dev);
  break;
 default:
  kfree(switchdev_work);
  return NOTIFY_DONE;
 }

 queue_work(ethsw->workqueue, &switchdev_work->work);

 return NOTIFY_DONE;

err_addr_alloc:
 kfree(switchdev_work);
 return NOTIFY_BAD;
}

static int dpaa2_switch_port_obj_event(unsigned long event,
           struct net_device *netdev,
           struct switchdev_notifier_port_obj_info *port_obj_info)
{
 int err = -EOPNOTSUPP;

 if (!dpaa2_switch_port_dev_check(netdev))
  return NOTIFY_DONE;

 switch (event) {
 case SWITCHDEV_PORT_OBJ_ADD:
  err = dpaa2_switch_port_obj_add(netdev, port_obj_info->obj);
  break;
 case SWITCHDEV_PORT_OBJ_DEL:
  err = dpaa2_switch_port_obj_del(netdev, port_obj_info->obj);
  break;
 }

 port_obj_info->handled = true;
 return notifier_from_errno(err);
}

static int dpaa2_switch_port_blocking_event(struct notifier_block *nb,
         unsigned long event, void *ptr)
{
 struct net_device *dev = switchdev_notifier_info_to_dev(ptr);

 switch (event) {
 case SWITCHDEV_PORT_OBJ_ADD:
 case SWITCHDEV_PORT_OBJ_DEL:
  return dpaa2_switch_port_obj_event(event, dev, ptr);
 case SWITCHDEV_PORT_ATTR_SET:
  return dpaa2_switch_port_attr_set_event(dev, ptr);
 }

 return NOTIFY_DONE;
}

/* Build a linear skb based on a single-buffer frame descriptor */
static struct sk_buff *dpaa2_switch_build_linear_skb(struct ethsw_core *ethsw,
           const struct dpaa2_fd *fd)
{
 u16 fd_offset = dpaa2_fd_get_offset(fd);
 dma_addr_t addr = dpaa2_fd_get_addr(fd);
 u32 fd_length = dpaa2_fd_get_len(fd);
 struct device *dev = ethsw->dev;
 struct sk_buff *skb = NULL;
 void *fd_vaddr;

 fd_vaddr = dpaa2_iova_to_virt(ethsw->iommu_domain, addr);
 dma_unmap_page(dev, addr, DPAA2_SWITCH_RX_BUF_SIZE,
         DMA_FROM_DEVICE);

 skb = build_skb(fd_vaddr, DPAA2_SWITCH_RX_BUF_SIZE +
   SKB_DATA_ALIGN(sizeof(struct skb_shared_info)));
 if (unlikely(!skb)) {
  dev_err(dev, "build_skb() failed\n");
  return NULL;
 }

 skb_reserve(skb, fd_offset);
 skb_put(skb, fd_length);

 ethsw->buf_count--;

 return skb;
}

static void dpaa2_switch_tx_conf(struct dpaa2_switch_fq *fq,
     const struct dpaa2_fd *fd)
{
 dpaa2_switch_free_fd(fq->ethsw, fd);
}

static void dpaa2_switch_rx(struct dpaa2_switch_fq *fq,
       const struct dpaa2_fd *fd)
{
 struct ethsw_core *ethsw = fq->ethsw;
 struct ethsw_port_priv *port_priv;
 struct net_device *netdev;
 struct vlan_ethhdr *hdr;
 struct sk_buff *skb;
 u16 vlan_tci, vid;
 int if_id, err;

 /* get switch ingress interface ID */
 if_id = upper_32_bits(dpaa2_fd_get_flc(fd)) & 0x0000FFFF;

 if (if_id >= ethsw->sw_attr.num_ifs) {
  dev_err(ethsw->dev, "Frame received from unknown interface!\n");
  goto err_free_fd;
 }
 port_priv = ethsw->ports[if_id];
 netdev = port_priv->netdev;

 /* build the SKB based on the FD received */
 if (dpaa2_fd_get_format(fd) != dpaa2_fd_single) {
  if (net_ratelimit()) {
   netdev_err(netdev, "Received invalid frame format\n");
   goto err_free_fd;
  }
 }

 skb = dpaa2_switch_build_linear_skb(ethsw, fd);
 if (unlikely(!skb))
  goto err_free_fd;

 skb_reset_mac_header(skb);

 /* Remove the VLAN header if the packet that we just received has a vid
 * equal to the port PVIDs. Since the dpaa2-switch can operate only in
 * VLAN-aware mode and no alterations are made on the packet when it's
 * redirected/mirrored to the control interface, we are sure that there
 * will always be a VLAN header present.
 */

 hdr = vlan_eth_hdr(skb);
 vid = ntohs(hdr->h_vlan_TCI) & VLAN_VID_MASK;
 if (vid == port_priv->pvid) {
  err = __skb_vlan_pop(skb, &vlan_tci);
  if (err) {
   dev_info(ethsw->dev, "__skb_vlan_pop() returned %d", err);
   goto err_free_fd;
  }
 }

 skb->dev = netdev;
 skb->protocol = eth_type_trans(skb, skb->dev);

 /* Setup the offload_fwd_mark only if the port is under a bridge */
 skb->offload_fwd_mark = !!(port_priv->fdb->bridge_dev);

 netif_receive_skb(skb);

 return;

err_free_fd:
 dpaa2_switch_free_fd(ethsw, fd);
}

static void dpaa2_switch_detect_features(struct ethsw_core *ethsw)
{
 ethsw->features = 0;

 if (ethsw->major > 8 || (ethsw->major == 8 && ethsw->minor >= 6))
  ethsw->features |= ETHSW_FEATURE_MAC_ADDR;
}

static int dpaa2_switch_setup_fqs(struct ethsw_core *ethsw)
{
 struct dpsw_ctrl_if_attr ctrl_if_attr;
 struct device *dev = ethsw->dev;
 int i = 0;
 int err;

 err = dpsw_ctrl_if_get_attributes(ethsw->mc_io, 0, ethsw->dpsw_handle,
       &ctrl_if_attr);
 if (err) {
  dev_err(dev, "dpsw_ctrl_if_get_attributes() = %d\n", err);
  return err;
 }

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=92 G=94

¤ Dauer der Verarbeitung: 0.26 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.