Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Ilias Apalodimas | 9571 | 99.95% | 2 | 66.67% |
Jakub Kiciński | 5 | 0.05% | 1 | 33.33% |
Total | 9576 | 3 |
// SPDX-License-Identifier: GPL-2.0 /* * Texas Instruments Ethernet Switch Driver * * Copyright (C) 2019 Texas Instruments */ #include <linux/io.h> #include <linux/clk.h> #include <linux/timer.h> #include <linux/module.h> #include <linux/irqreturn.h> #include <linux/interrupt.h> #include <linux/if_ether.h> #include <linux/etherdevice.h> #include <linux/net_tstamp.h> #include <linux/phy.h> #include <linux/phy/phy.h> #include <linux/delay.h> #include <linux/pm_runtime.h> #include <linux/gpio/consumer.h> #include <linux/of.h> #include <linux/of_mdio.h> #include <linux/of_net.h> #include <linux/of_device.h> #include <linux/if_vlan.h> #include <linux/kmemleak.h> #include <linux/sys_soc.h> #include <net/page_pool.h> #include <net/pkt_cls.h> #include <net/devlink.h> #include "cpsw.h" #include "cpsw_ale.h" #include "cpsw_priv.h" #include "cpsw_sl.h" #include "cpsw_switchdev.h" #include "cpts.h" #include "davinci_cpdma.h" #include <net/pkt_sched.h> static int debug_level; static int ale_ageout = CPSW_ALE_AGEOUT_DEFAULT; static int rx_packet_max = CPSW_MAX_PACKET_SIZE; static int descs_pool_size = CPSW_CPDMA_DESCS_POOL_SIZE_DEFAULT; struct cpsw_devlink { struct cpsw_common *cpsw; }; enum cpsw_devlink_param_id { CPSW_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, CPSW_DL_PARAM_SWITCH_MODE, CPSW_DL_PARAM_ALE_BYPASS, }; /* struct cpsw_common is not needed, kept here for compatibility * reasons witrh the old driver */ static int cpsw_slave_index_priv(struct cpsw_common *cpsw, struct cpsw_priv *priv) { if (priv->emac_port == HOST_PORT_NUM) return -1; return priv->emac_port - 1; } static bool cpsw_is_switch_en(struct cpsw_common *cpsw) { return !cpsw->data.dual_emac; } static void cpsw_set_promiscious(struct net_device *ndev, bool enable) { struct cpsw_common *cpsw = ndev_to_cpsw(ndev); bool enable_uni = false; int i; if (cpsw_is_switch_en(cpsw)) return; /* Enabling promiscuous mode for one interface will be * common for both the interface as the interface shares * the same hardware resource. */ for (i = 0; i < cpsw->data.slaves; i++) if (cpsw->slaves[i].ndev && (cpsw->slaves[i].ndev->flags & IFF_PROMISC)) enable_uni = true; if (!enable && enable_uni) { enable = enable_uni; dev_dbg(cpsw->dev, "promiscuity not disabled as the other interface is still in promiscuity mode\n"); } if (enable) { /* Enable unknown unicast, reg/unreg mcast */ cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, 1); dev_dbg(cpsw->dev, "promiscuity enabled\n"); } else { /* Disable unknown unicast */ cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, 0); dev_dbg(cpsw->dev, "promiscuity disabled\n"); } } /** * cpsw_set_mc - adds multicast entry to the table if it's not added or deletes * if it's not deleted * @ndev: device to sync * @addr: address to be added or deleted * @vid: vlan id, if vid < 0 set/unset address for real device * @add: add address if the flag is set or remove otherwise */ static int cpsw_set_mc(struct net_device *ndev, const u8 *addr, int vid, int add) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; int mask, flags, ret, slave_no; slave_no = cpsw_slave_index(cpsw, priv); if (vid < 0) vid = cpsw->slaves[slave_no].port_vlan; mask = ALE_PORT_HOST; flags = vid ? ALE_VLAN : 0; if (add) ret = cpsw_ale_add_mcast(cpsw->ale, addr, mask, flags, vid, 0); else ret = cpsw_ale_del_mcast(cpsw->ale, addr, 0, flags, vid); return ret; } static int cpsw_update_vlan_mc(struct net_device *vdev, int vid, void *ctx) { struct addr_sync_ctx *sync_ctx = ctx; struct netdev_hw_addr *ha; int found = 0, ret = 0; if (!vdev || !(vdev->flags & IFF_UP)) return 0; /* vlan address is relevant if its sync_cnt != 0 */ netdev_for_each_mc_addr(ha, vdev) { if (ether_addr_equal(ha->addr, sync_ctx->addr)) { found = ha->sync_cnt; break; } } if (found) sync_ctx->consumed++; if (sync_ctx->flush) { if (!found) cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0); return 0; } if (found) ret = cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 1); return ret; } static int cpsw_add_mc_addr(struct net_device *ndev, const u8 *addr, int num) { struct addr_sync_ctx sync_ctx; int ret; sync_ctx.consumed = 0; sync_ctx.addr = addr; sync_ctx.ndev = ndev; sync_ctx.flush = 0; ret = vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx); if (sync_ctx.consumed < num && !ret) ret = cpsw_set_mc(ndev, addr, -1, 1); return ret; } static int cpsw_del_mc_addr(struct net_device *ndev, const u8 *addr, int num) { struct addr_sync_ctx sync_ctx; sync_ctx.consumed = 0; sync_ctx.addr = addr; sync_ctx.ndev = ndev; sync_ctx.flush = 1; vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx); if (sync_ctx.consumed == num) cpsw_set_mc(ndev, addr, -1, 0); return 0; } static int cpsw_purge_vlan_mc(struct net_device *vdev, int vid, void *ctx) { struct addr_sync_ctx *sync_ctx = ctx; struct netdev_hw_addr *ha; int found = 0; if (!vdev || !(vdev->flags & IFF_UP)) return 0; /* vlan address is relevant if its sync_cnt != 0 */ netdev_for_each_mc_addr(ha, vdev) { if (ether_addr_equal(ha->addr, sync_ctx->addr)) { found = ha->sync_cnt; break; } } if (!found) return 0; sync_ctx->consumed++; cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0); return 0; } static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num) { struct addr_sync_ctx sync_ctx; sync_ctx.addr = addr; sync_ctx.ndev = ndev; sync_ctx.consumed = 0; vlan_for_each(ndev, cpsw_purge_vlan_mc, &sync_ctx); if (sync_ctx.consumed < num) cpsw_set_mc(ndev, addr, -1, 0); return 0; } static void cpsw_ndo_set_rx_mode(struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; if (ndev->flags & IFF_PROMISC) { /* Enable promiscuous mode */ cpsw_set_promiscious(ndev, true); cpsw_ale_set_allmulti(cpsw->ale, IFF_ALLMULTI, priv->emac_port); return; } /* Disable promiscuous mode */ cpsw_set_promiscious(ndev, false); /* Restore allmulti on vlans if necessary */ cpsw_ale_set_allmulti(cpsw->ale, ndev->flags & IFF_ALLMULTI, priv->emac_port); /* add/remove mcast address either for real netdev or for vlan */ __hw_addr_ref_sync_dev(&ndev->mc, ndev, cpsw_add_mc_addr, cpsw_del_mc_addr); } static unsigned int cpsw_rxbuf_total_len(unsigned int len) { len += CPSW_HEADROOM; len += SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); return SKB_DATA_ALIGN(len); } static void cpsw_rx_handler(void *token, int len, int status) { struct page *new_page, *page = token; void *pa = page_address(page); int headroom = CPSW_HEADROOM; struct cpsw_meta_xdp *xmeta; struct cpsw_common *cpsw; struct net_device *ndev; int port, ch, pkt_size; struct cpsw_priv *priv; struct page_pool *pool; struct sk_buff *skb; struct xdp_buff xdp; int ret = 0; dma_addr_t dma; xmeta = pa + CPSW_XMETA_OFFSET; cpsw = ndev_to_cpsw(xmeta->ndev); ndev = xmeta->ndev; pkt_size = cpsw->rx_packet_max; ch = xmeta->ch; if (status >= 0) { port = CPDMA_RX_SOURCE_PORT(status); if (port) ndev = cpsw->slaves[--port].ndev; } priv = netdev_priv(ndev); pool = cpsw->page_pool[ch]; if (unlikely(status < 0) || unlikely(!netif_running(ndev))) { /* In dual emac mode check for all interfaces */ if (cpsw->usage_count && status >= 0) { /* The packet received is for the interface which * is already down and the other interface is up * and running, instead of freeing which results * in reducing of the number of rx descriptor in * DMA engine, requeue page back to cpdma. */ new_page = page; goto requeue; } /* the interface is going down, pages are purged */ page_pool_recycle_direct(pool, page); return; } new_page = page_pool_dev_alloc_pages(pool); if (unlikely(!new_page)) { new_page = page; ndev->stats.rx_dropped++; goto requeue; } if (priv->xdp_prog) { if (status & CPDMA_RX_VLAN_ENCAP) { xdp.data = pa + CPSW_HEADROOM + CPSW_RX_VLAN_ENCAP_HDR_SIZE; xdp.data_end = xdp.data + len - CPSW_RX_VLAN_ENCAP_HDR_SIZE; } else { xdp.data = pa + CPSW_HEADROOM; xdp.data_end = xdp.data + len; } xdp_set_data_meta_invalid(&xdp); xdp.data_hard_start = pa; xdp.rxq = &priv->xdp_rxq[ch]; ret = cpsw_run_xdp(priv, ch, &xdp, page, priv->emac_port); if (ret != CPSW_XDP_PASS) goto requeue; /* XDP prog might have changed packet data and boundaries */ len = xdp.data_end - xdp.data; headroom = xdp.data - xdp.data_hard_start; /* XDP prog can modify vlan tag, so can't use encap header */ status &= ~CPDMA_RX_VLAN_ENCAP; } /* pass skb to netstack if no XDP prog or returned XDP_PASS */ skb = build_skb(pa, cpsw_rxbuf_total_len(pkt_size)); if (!skb) { ndev->stats.rx_dropped++; page_pool_recycle_direct(pool, page); goto requeue; } skb->offload_fwd_mark = priv->offload_fwd_mark; skb_reserve(skb, headroom); skb_put(skb, len); skb->dev = ndev; if (status & CPDMA_RX_VLAN_ENCAP) cpsw_rx_vlan_encap(skb); if (priv->rx_ts_enabled) cpts_rx_timestamp(cpsw->cpts, skb); skb->protocol = eth_type_trans(skb, ndev); /* unmap page as no netstack skb page recycling */ page_pool_release_page(pool, page); netif_receive_skb(skb); ndev->stats.rx_bytes += len; ndev->stats.rx_packets++; requeue: xmeta = page_address(new_page) + CPSW_XMETA_OFFSET; xmeta->ndev = ndev; xmeta->ch = ch; dma = page_pool_get_dma_addr(new_page) + CPSW_HEADROOM; ret = cpdma_chan_submit_mapped(cpsw->rxv[ch].ch, new_page, dma, pkt_size, 0); if (ret < 0) { WARN_ON(ret == -ENOMEM); page_pool_recycle_direct(pool, new_page); } } static int cpsw_add_vlan_ale_entry(struct cpsw_priv *priv, unsigned short vid) { struct cpsw_common *cpsw = priv->cpsw; int unreg_mcast_mask = 0; int mcast_mask; u32 port_mask; int ret; port_mask = (1 << priv->emac_port) | ALE_PORT_HOST; mcast_mask = ALE_PORT_HOST; if (priv->ndev->flags & IFF_ALLMULTI) unreg_mcast_mask = mcast_mask; ret = cpsw_ale_add_vlan(cpsw->ale, vid, port_mask, 0, port_mask, unreg_mcast_mask); if (ret != 0) return ret; ret = cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, ALE_VLAN, vid); if (ret != 0) goto clean_vid; ret = cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast, mcast_mask, ALE_VLAN, vid, 0); if (ret != 0) goto clean_vlan_ucast; return 0; clean_vlan_ucast: cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, ALE_VLAN, vid); clean_vid: cpsw_ale_del_vlan(cpsw->ale, vid, 0); return ret; } static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; int ret, i; if (cpsw_is_switch_en(cpsw)) { dev_dbg(cpsw->dev, ".ndo_vlan_rx_add_vid called in switch mode\n"); return 0; } if (vid == cpsw->data.default_vlan) return 0; ret = pm_runtime_get_sync(cpsw->dev); if (ret < 0) { pm_runtime_put_noidle(cpsw->dev); return ret; } /* In dual EMAC, reserved VLAN id should not be used for * creating VLAN interfaces as this can break the dual * EMAC port separation */ for (i = 0; i < cpsw->data.slaves; i++) { if (cpsw->slaves[i].ndev && vid == cpsw->slaves[i].port_vlan) { ret = -EINVAL; goto err; } } dev_dbg(priv->dev, "Adding vlanid %d to vlan filter\n", vid); ret = cpsw_add_vlan_ale_entry(priv, vid); err: pm_runtime_put(cpsw->dev); return ret; } static int cpsw_restore_vlans(struct net_device *vdev, int vid, void *arg) { struct cpsw_priv *priv = arg; if (!vdev || !vid) return 0; cpsw_ndo_vlan_rx_add_vid(priv->ndev, 0, vid); return 0; } /* restore resources after port reset */ static void cpsw_restore(struct cpsw_priv *priv) { struct cpsw_common *cpsw = priv->cpsw; /* restore vlan configurations */ vlan_for_each(priv->ndev, cpsw_restore_vlans, priv); /* restore MQPRIO offload */ cpsw_mqprio_resume(&cpsw->slaves[priv->emac_port - 1], priv); /* restore CBS offload */ cpsw_cbs_resume(&cpsw->slaves[priv->emac_port - 1], priv); } static void cpsw_init_stp_ale_entry(struct cpsw_common *cpsw) { char stpa[] = {0x01, 0x80, 0xc2, 0x0, 0x0, 0x0}; cpsw_ale_add_mcast(cpsw->ale, stpa, ALE_PORT_HOST, ALE_SUPER, 0, ALE_MCAST_BLOCK_LEARN_FWD); } static void cpsw_init_host_port_switch(struct cpsw_common *cpsw) { int vlan = cpsw->data.default_vlan; writel(CPSW_FIFO_NORMAL_MODE, &cpsw->host_port_regs->tx_in_ctl); writel(vlan, &cpsw->host_port_regs->port_vlan); cpsw_ale_add_vlan(cpsw->ale, vlan, ALE_ALL_PORTS, ALE_ALL_PORTS, ALE_ALL_PORTS, ALE_PORT_1 | ALE_PORT_2); cpsw_init_stp_ale_entry(cpsw); cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, 1); dev_dbg(cpsw->dev, "Set P0_UNI_FLOOD\n"); cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_PORT_NOLEARN, 0); } static void cpsw_init_host_port_dual_mac(struct cpsw_common *cpsw) { int vlan = cpsw->data.default_vlan; writel(CPSW_FIFO_DUAL_MAC_MODE, &cpsw->host_port_regs->tx_in_ctl); cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, 0); dev_dbg(cpsw->dev, "unset P0_UNI_FLOOD\n"); writel(vlan, &cpsw->host_port_regs->port_vlan); cpsw_ale_add_vlan(cpsw->ale, vlan, ALE_ALL_PORTS, ALE_ALL_PORTS, 0, 0); /* learning make no sense in dual_mac mode */ cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_PORT_NOLEARN, 1); } static void cpsw_init_host_port(struct cpsw_priv *priv) { struct cpsw_common *cpsw = priv->cpsw; u32 control_reg; /* soft reset the controller and initialize ale */ soft_reset("cpsw", &cpsw->regs->soft_reset); cpsw_ale_start(cpsw->ale); /* switch to vlan unaware mode */ cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_VLAN_AWARE, CPSW_ALE_VLAN_AWARE); control_reg = readl(&cpsw->regs->control); control_reg |= CPSW_VLAN_AWARE | CPSW_RX_VLAN_ENCAP; writel(control_reg, &cpsw->regs->control); /* setup host port priority mapping */ writel_relaxed(CPDMA_TX_PRIORITY_MAP, &cpsw->host_port_regs->cpdma_tx_pri_map); writel_relaxed(0, &cpsw->host_port_regs->cpdma_rx_chan_map); /* disable priority elevation */ writel_relaxed(0, &cpsw->regs->ptype); /* enable statistics collection only on all ports */ writel_relaxed(0x7, &cpsw->regs->stat_port_en); /* Enable internal fifo flow control */ writel(0x7, &cpsw->regs->flow_control); if (cpsw_is_switch_en(cpsw)) cpsw_init_host_port_switch(cpsw); else cpsw_init_host_port_dual_mac(cpsw); cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_PORT_STATE, ALE_PORT_STATE_FORWARD); } static void cpsw_port_add_dual_emac_def_ale_entries(struct cpsw_priv *priv, struct cpsw_slave *slave) { u32 port_mask = 1 << priv->emac_port | ALE_PORT_HOST; struct cpsw_common *cpsw = priv->cpsw; u32 reg; reg = (cpsw->version == CPSW_VERSION_1) ? CPSW1_PORT_VLAN : CPSW2_PORT_VLAN; slave_write(slave, slave->port_vlan, reg); cpsw_ale_add_vlan(cpsw->ale, slave->port_vlan, port_mask, port_mask, port_mask, 0); cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast, ALE_PORT_HOST, ALE_VLAN, slave->port_vlan, ALE_MCAST_FWD); cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, ALE_VLAN | ALE_SECURE, slave->port_vlan); cpsw_ale_control_set(cpsw->ale, priv->emac_port, ALE_PORT_DROP_UNKNOWN_VLAN, 1); /* learning make no sense in dual_mac mode */ cpsw_ale_control_set(cpsw->ale, priv->emac_port, ALE_PORT_NOLEARN, 1); } static void cpsw_port_add_switch_def_ale_entries(struct cpsw_priv *priv, struct cpsw_slave *slave) { u32 port_mask = 1 << priv->emac_port | ALE_PORT_HOST; struct cpsw_common *cpsw = priv->cpsw; u32 reg; cpsw_ale_control_set(cpsw->ale, priv->emac_port, ALE_PORT_DROP_UNKNOWN_VLAN, 0); cpsw_ale_control_set(cpsw->ale, priv->emac_port, ALE_PORT_NOLEARN, 0); /* disabling SA_UPDATE required to make stp work, without this setting * Host MAC addresses will jump between ports. * As per TRM MAC address can be defined as unicast supervisory (super) * by setting both (ALE_BLOCKED | ALE_SECURE) which should prevent * SA_UPDATE, but HW seems works incorrectly and setting ALE_SECURE * causes STP packets to be dropped due to ingress filter * if (source address found) and (secure) and * (receive port number != port_number)) * then discard the packet */ cpsw_ale_control_set(cpsw->ale, priv->emac_port, ALE_PORT_NO_SA_UPDATE, 1); cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast, port_mask, ALE_VLAN, slave->port_vlan, ALE_MCAST_FWD_2); cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, ALE_VLAN, slave->port_vlan); reg = (cpsw->version == CPSW_VERSION_1) ? CPSW1_PORT_VLAN : CPSW2_PORT_VLAN; slave_write(slave, slave->port_vlan, reg); } static void cpsw_adjust_link(struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; struct cpsw_slave *slave; struct phy_device *phy; u32 mac_control = 0; slave = &cpsw->slaves[priv->emac_port - 1]; phy = slave->phy; if (!phy) return; if (phy->link) { mac_control = CPSW_SL_CTL_GMII_EN; if (phy->speed == 1000) mac_control |= CPSW_SL_CTL_GIG; if (phy->duplex) mac_control |= CPSW_SL_CTL_FULLDUPLEX; /* set speed_in input in case RMII mode is used in 100Mbps */ if (phy->speed == 100) mac_control |= CPSW_SL_CTL_IFCTL_A; /* in band mode only works in 10Mbps RGMII mode */ else if ((phy->speed == 10) && phy_interface_is_rgmii(phy)) mac_control |= CPSW_SL_CTL_EXT_EN; /* In Band mode */ if (priv->rx_pause) mac_control |= CPSW_SL_CTL_RX_FLOW_EN; if (priv->tx_pause) mac_control |= CPSW_SL_CTL_TX_FLOW_EN; if (mac_control != slave->mac_control) cpsw_sl_ctl_set(slave->mac_sl, mac_control); /* enable forwarding */ cpsw_ale_control_set(cpsw->ale, priv->emac_port, ALE_PORT_STATE, ALE_PORT_STATE_FORWARD); netif_tx_wake_all_queues(ndev); if (priv->shp_cfg_speed && priv->shp_cfg_speed != slave->phy->speed && !cpsw_shp_is_off(priv)) dev_warn(priv->dev, "Speed was changed, CBS shaper speeds are changed!"); } else { netif_tx_stop_all_queues(ndev); mac_control = 0; /* disable forwarding */ cpsw_ale_control_set(cpsw->ale, priv->emac_port, ALE_PORT_STATE, ALE_PORT_STATE_DISABLE); cpsw_sl_wait_for_idle(slave->mac_sl, 100); cpsw_sl_ctl_reset(slave->mac_sl); } if (mac_control != slave->mac_control) phy_print_status(phy); slave->mac_control = mac_control; if (phy->link && cpsw_need_resplit(cpsw)) cpsw_split_res(cpsw); } static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv) { struct cpsw_common *cpsw = priv->cpsw; struct phy_device *phy; cpsw_sl_reset(slave->mac_sl, 100); cpsw_sl_ctl_reset(slave->mac_sl); /* setup priority mapping */ cpsw_sl_reg_write(slave->mac_sl, CPSW_SL_RX_PRI_MAP, RX_PRIORITY_MAPPING); switch (cpsw->version) { case CPSW_VERSION_1: slave_write(slave, TX_PRIORITY_MAPPING, CPSW1_TX_PRI_MAP); /* Increase RX FIFO size to 5 for supporting fullduplex * flow control mode */ slave_write(slave, (CPSW_MAX_BLKS_TX << CPSW_MAX_BLKS_TX_SHIFT) | CPSW_MAX_BLKS_RX, CPSW1_MAX_BLKS); break; case CPSW_VERSION_2: case CPSW_VERSION_3: case CPSW_VERSION_4: slave_write(slave, TX_PRIORITY_MAPPING, CPSW2_TX_PRI_MAP); /* Increase RX FIFO size to 5 for supporting fullduplex * flow control mode */ slave_write(slave, (CPSW_MAX_BLKS_TX << CPSW_MAX_BLKS_TX_SHIFT) | CPSW_MAX_BLKS_RX, CPSW2_MAX_BLKS); break; } /* setup max packet size, and mac address */ cpsw_sl_reg_write(slave->mac_sl, CPSW_SL_RX_MAXLEN, cpsw->rx_packet_max); cpsw_set_slave_mac(slave, priv); slave->mac_control = 0; /* no link yet */ if (cpsw_is_switch_en(cpsw)) cpsw_port_add_switch_def_ale_entries(priv, slave); else cpsw_port_add_dual_emac_def_ale_entries(priv, slave); if (!slave->data->phy_node) dev_err(priv->dev, "no phy found on slave %d\n", slave->slave_num); phy = of_phy_connect(priv->ndev, slave->data->phy_node, &cpsw_adjust_link, 0, slave->data->phy_if); if (!phy) { dev_err(priv->dev, "phy \"%pOF\" not found on slave %d\n", slave->data->phy_node, slave->slave_num); return; } slave->phy = phy; phy_attached_info(slave->phy); phy_start(slave->phy); /* Configure GMII_SEL register */ phy_set_mode_ext(slave->data->ifphy, PHY_MODE_ETHERNET, slave->data->phy_if); } static int cpsw_ndo_stop(struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; struct cpsw_slave *slave; cpsw_info(priv, ifdown, "shutting down ndev\n"); slave = &cpsw->slaves[priv->emac_port - 1]; if (slave->phy) phy_stop(slave->phy); netif_tx_stop_all_queues(priv->ndev); if (slave->phy) { phy_disconnect(slave->phy); slave->phy = NULL; } __hw_addr_ref_unsync_dev(&ndev->mc, ndev, cpsw_purge_all_mc); if (cpsw->usage_count <= 1) { napi_disable(&cpsw->napi_rx); napi_disable(&cpsw->napi_tx); cpts_unregister(cpsw->cpts); cpsw_intr_disable(cpsw); cpdma_ctlr_stop(cpsw->dma); cpsw_ale_stop(cpsw->ale); cpsw_destroy_xdp_rxqs(cpsw); } if (cpsw_need_resplit(cpsw)) cpsw_split_res(cpsw); cpsw->usage_count--; pm_runtime_put_sync(cpsw->dev); return 0; } static int cpsw_ndo_open(struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; int ret; dev_info(priv->dev, "starting ndev. mode: %s\n", cpsw_is_switch_en(cpsw) ? "switch" : "dual_mac"); ret = pm_runtime_get_sync(cpsw->dev); if (ret < 0) { pm_runtime_put_noidle(cpsw->dev); return ret; } /* Notify the stack of the actual queue counts. */ ret = netif_set_real_num_tx_queues(ndev, cpsw->tx_ch_num); if (ret) { dev_err(priv->dev, "cannot set real number of tx queues\n"); goto pm_cleanup; } ret = netif_set_real_num_rx_queues(ndev, cpsw->rx_ch_num); if (ret) { dev_err(priv->dev, "cannot set real number of rx queues\n"); goto pm_cleanup; } /* Initialize host and slave ports */ if (!cpsw->usage_count) cpsw_init_host_port(priv); cpsw_slave_open(&cpsw->slaves[priv->emac_port - 1], priv); /* initialize shared resources for every ndev */ if (!cpsw->usage_count) { /* create rxqs for both infs in dual mac as they use same pool * and must be destroyed together when no users. */ ret = cpsw_create_xdp_rxqs(cpsw); if (ret < 0) goto err_cleanup; ret = cpsw_fill_rx_channels(priv); if (ret < 0) goto err_cleanup; if (cpts_register(cpsw->cpts)) dev_err(priv->dev, "error registering cpts device\n"); napi_enable(&cpsw->napi_rx); napi_enable(&cpsw->napi_tx); if (cpsw->tx_irq_disabled) { cpsw->tx_irq_disabled = false; enable_irq(cpsw->irqs_table[1]); } if (cpsw->rx_irq_disabled) { cpsw->rx_irq_disabled = false; enable_irq(cpsw->irqs_table[0]); } } cpsw_restore(priv); /* Enable Interrupt pacing if configured */ if (cpsw->coal_intvl != 0) { struct ethtool_coalesce coal; coal.rx_coalesce_usecs = cpsw->coal_intvl; cpsw_set_coalesce(ndev, &coal); } cpdma_ctlr_start(cpsw->dma); cpsw_intr_enable(cpsw); cpsw->usage_count++; return 0; err_cleanup: cpsw_ndo_stop(ndev); pm_cleanup: pm_runtime_put_sync(cpsw->dev); return ret; } static netdev_tx_t cpsw_ndo_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; struct cpts *cpts = cpsw->cpts; struct netdev_queue *txq; struct cpdma_chan *txch; int ret, q_idx; if (skb_padto(skb, CPSW_MIN_PACKET_SIZE)) { cpsw_err(priv, tx_err, "packet pad failed\n"); ndev->stats.tx_dropped++; return NET_XMIT_DROP; } if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP && priv->tx_ts_enabled && cpts_can_timestamp(cpts, skb)) skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; q_idx = skb_get_queue_mapping(skb); if (q_idx >= cpsw->tx_ch_num) q_idx = q_idx % cpsw->tx_ch_num; txch = cpsw->txv[q_idx].ch; txq = netdev_get_tx_queue(ndev, q_idx); skb_tx_timestamp(skb); ret = cpdma_chan_submit(txch, skb, skb->data, skb->len, priv->emac_port); if (unlikely(ret != 0)) { cpsw_err(priv, tx_err, "desc submit failed\n"); goto fail; } /* If there is no more tx desc left free then we need to * tell the kernel to stop sending us tx frames. */ if (unlikely(!cpdma_check_free_tx_desc(txch))) { netif_tx_stop_queue(txq); /* Barrier, so that stop_queue visible to other cpus */ smp_mb__after_atomic(); if (cpdma_check_free_tx_desc(txch)) netif_tx_wake_queue(txq); } return NETDEV_TX_OK; fail: ndev->stats.tx_dropped++; netif_tx_stop_queue(txq); /* Barrier, so that stop_queue visible to other cpus */ smp_mb__after_atomic(); if (cpdma_check_free_tx_desc(txch)) netif_tx_wake_queue(txq); return NETDEV_TX_BUSY; } static int cpsw_ndo_set_mac_address(struct net_device *ndev, void *p) { struct sockaddr *addr = (struct sockaddr *)p; struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; int ret, slave_no; int flags = 0; u16 vid = 0; slave_no = cpsw_slave_index(cpsw, priv); if (!is_valid_ether_addr(addr->sa_data)) return -EADDRNOTAVAIL; ret = pm_runtime_get_sync(cpsw->dev); if (ret < 0) { pm_runtime_put_noidle(cpsw->dev); return ret; } vid = cpsw->slaves[slave_no].port_vlan; flags = ALE_VLAN | ALE_SECURE; cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, flags, vid); cpsw_ale_add_ucast(cpsw->ale, addr->sa_data, HOST_PORT_NUM, flags, vid); ether_addr_copy(priv->mac_addr, addr->sa_data); ether_addr_copy(ndev->dev_addr, priv->mac_addr); cpsw_set_slave_mac(&cpsw->slaves[slave_no], priv); pm_runtime_put(cpsw->dev); return 0; } static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; int ret; int i; if (cpsw_is_switch_en(cpsw)) { dev_dbg(cpsw->dev, "ndo del vlan is called in switch mode\n"); return 0; } if (vid == cpsw->data.default_vlan) return 0; ret = pm_runtime_get_sync(cpsw->dev); if (ret < 0) { pm_runtime_put_noidle(cpsw->dev); return ret; } for (i = 0; i < cpsw->data.slaves; i++) { if (cpsw->slaves[i].ndev && vid == cpsw->slaves[i].port_vlan) goto err; } dev_dbg(priv->dev, "removing vlanid %d from vlan filter\n", vid); cpsw_ale_del_vlan(cpsw->ale, vid, 0); cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM, ALE_VLAN, vid); cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast, 0, ALE_VLAN, vid); cpsw_ale_flush_multicast(cpsw->ale, 0, vid); err: pm_runtime_put(cpsw->dev); return ret; } static int cpsw_ndo_get_phys_port_name(struct net_device *ndev, char *name, size_t len) { struct cpsw_priv *priv = netdev_priv(ndev); int err; err = snprintf(name, len, "p%d", priv->emac_port); if (err >= len) return -EINVAL; return 0; } #ifdef CONFIG_NET_POLL_CONTROLLER static void cpsw_ndo_poll_controller(struct net_device *ndev) { struct cpsw_common *cpsw = ndev_to_cpsw(ndev); cpsw_intr_disable(cpsw); cpsw_rx_interrupt(cpsw->irqs_table[0], cpsw); cpsw_tx_interrupt(cpsw->irqs_table[1], cpsw); cpsw_intr_enable(cpsw); } #endif static int cpsw_ndo_xdp_xmit(struct net_device *ndev, int n, struct xdp_frame **frames, u32 flags) { struct cpsw_priv *priv = netdev_priv(ndev); struct xdp_frame *xdpf; int i, drops = 0; if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK)) return -EINVAL; for (i = 0; i < n; i++) { xdpf = frames[i]; if (xdpf->len < CPSW_MIN_PACKET_SIZE) { xdp_return_frame_rx_napi(xdpf); drops++; continue; } if (cpsw_xdp_tx_frame(priv, xdpf, NULL, priv->emac_port)) drops++; } return n - drops; } static int cpsw_get_port_parent_id(struct net_device *ndev, struct netdev_phys_item_id *ppid) { struct cpsw_common *cpsw = ndev_to_cpsw(ndev); ppid->id_len = sizeof(cpsw->base_mac); memcpy(&ppid->id, &cpsw->base_mac, ppid->id_len); return 0; } static const struct net_device_ops cpsw_netdev_ops = { .ndo_open = cpsw_ndo_open, .ndo_stop = cpsw_ndo_stop, .ndo_start_xmit = cpsw_ndo_start_xmit, .ndo_set_mac_address = cpsw_ndo_set_mac_address, .ndo_do_ioctl = cpsw_ndo_ioctl, .ndo_validate_addr = eth_validate_addr, .ndo_tx_timeout = cpsw_ndo_tx_timeout, .ndo_set_rx_mode = cpsw_ndo_set_rx_mode, .ndo_set_tx_maxrate = cpsw_ndo_set_tx_maxrate, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = cpsw_ndo_poll_controller, #endif .ndo_vlan_rx_add_vid = cpsw_ndo_vlan_rx_add_vid, .ndo_vlan_rx_kill_vid = cpsw_ndo_vlan_rx_kill_vid, .ndo_setup_tc = cpsw_ndo_setup_tc, .ndo_get_phys_port_name = cpsw_ndo_get_phys_port_name, .ndo_bpf = cpsw_ndo_bpf, .ndo_xdp_xmit = cpsw_ndo_xdp_xmit, .ndo_get_port_parent_id = cpsw_get_port_parent_id, }; static void cpsw_get_drvinfo(struct net_device *ndev, struct ethtool_drvinfo *info) { struct cpsw_common *cpsw = ndev_to_cpsw(ndev); struct platform_device *pdev; pdev = to_platform_device(cpsw->dev); strlcpy(info->driver, "cpsw-switch", sizeof(info->driver)); strlcpy(info->version, "2.0", sizeof(info->version)); strlcpy(info->bus_info, pdev->name, sizeof(info->bus_info)); } static int cpsw_set_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *pause) { struct cpsw_common *cpsw = ndev_to_cpsw(ndev); struct cpsw_priv *priv = netdev_priv(ndev); int slave_no; slave_no = cpsw_slave_index(cpsw, priv); if (!cpsw->slaves[slave_no].phy) return -EINVAL; if (!phy_validate_pause(cpsw->slaves[slave_no].phy, pause)) return -EINVAL; priv->rx_pause = pause->rx_pause ? true : false; priv->tx_pause = pause->tx_pause ? true : false; phy_set_asym_pause(cpsw->slaves[slave_no].phy, priv->rx_pause, priv->tx_pause); return 0; } static int cpsw_set_channels(struct net_device *ndev, struct ethtool_channels *chs) { return cpsw_set_channels_common(ndev, chs, cpsw_rx_handler); } static const struct ethtool_ops cpsw_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS, .get_drvinfo = cpsw_get_drvinfo, .get_msglevel = cpsw_get_msglevel, .set_msglevel = cpsw_set_msglevel, .get_link = ethtool_op_get_link, .get_ts_info = cpsw_get_ts_info, .get_coalesce = cpsw_get_coalesce, .set_coalesce = cpsw_set_coalesce, .get_sset_count = cpsw_get_sset_count, .get_strings = cpsw_get_strings, .get_ethtool_stats = cpsw_get_ethtool_stats, .get_pauseparam = cpsw_get_pauseparam, .set_pauseparam = cpsw_set_pauseparam, .get_wol = cpsw_get_wol, .set_wol = cpsw_set_wol, .get_regs_len = cpsw_get_regs_len, .get_regs = cpsw_get_regs, .begin = cpsw_ethtool_op_begin, .complete = cpsw_ethtool_op_complete, .get_channels = cpsw_get_channels, .set_channels = cpsw_set_channels, .get_link_ksettings = cpsw_get_link_ksettings, .set_link_ksettings = cpsw_set_link_ksettings, .get_eee = cpsw_get_eee, .set_eee = cpsw_set_eee, .nway_reset = cpsw_nway_reset, .get_ringparam = cpsw_get_ringparam, .set_ringparam = cpsw_set_ringparam, }; static int cpsw_probe_dt(struct cpsw_common *cpsw) { struct device_node *node = cpsw->dev->of_node, *tmp_node, *port_np; struct cpsw_platform_data *data = &cpsw->data; struct device *dev = cpsw->dev; int ret; u32 prop; if (!node) return -EINVAL; tmp_node = of_get_child_by_name(node, "ethernet-ports"); if (!tmp_node) return -ENOENT; data->slaves = of_get_child_count(tmp_node); if (data->slaves != CPSW_SLAVE_PORTS_NUM) { of_node_put(tmp_node); return -ENOENT; } data->active_slave = 0; data->channels = CPSW_MAX_QUEUES; data->ale_entries = CPSW_ALE_NUM_ENTRIES; data->dual_emac = 1; data->bd_ram_size = CPSW_BD_RAM_SIZE; data->mac_control = 0; data->slave_data = devm_kcalloc(dev, CPSW_SLAVE_PORTS_NUM, sizeof(struct cpsw_slave_data), GFP_KERNEL); if (!data->slave_data) return -ENOMEM; /* Populate all the child nodes here... */ ret = devm_of_platform_populate(dev); /* We do not want to force this, as in some cases may not have child */ if (ret) dev_warn(dev, "Doesn't have any child node\n"); for_each_child_of_node(tmp_node, port_np) { struct cpsw_slave_data *slave_data; const void *mac_addr; u32 port_id; ret = of_property_read_u32(port_np, "reg", &port_id); if (ret < 0) { dev_err(dev, "%pOF error reading port_id %d\n", port_np, ret); goto err_node_put; } if (!port_id || port_id > CPSW_SLAVE_PORTS_NUM) { dev_err(dev, "%pOF has invalid port_id %u\n", port_np, port_id); ret = -EINVAL; goto err_node_put; } slave_data = &data->slave_data[port_id - 1]; slave_data->disabled = !of_device_is_available(port_np); if (slave_data->disabled) continue; slave_data->slave_node = port_np; slave_data->ifphy = devm_of_phy_get(dev, port_np, NULL); if (IS_ERR(slave_data->ifphy)) { ret = PTR_ERR(slave_data->ifphy); dev_err(dev, "%pOF: Error retrieving port phy: %d\n", port_np, ret); goto err_node_put; } if (of_phy_is_fixed_link(port_np)) { ret = of_phy_register_fixed_link(port_np); if (ret) { if (ret != -EPROBE_DEFER) dev_err(dev, "%pOF failed to register fixed-link phy: %d\n", port_np, ret); goto err_node_put; } slave_data->phy_node = of_node_get(port_np); } else { slave_data->phy_node = of_parse_phandle(port_np, "phy-handle", 0); } if (!slave_data->phy_node) { dev_err(dev, "%pOF no phy found\n", port_np); ret = -ENODEV; goto err_node_put; } ret = of_get_phy_mode(port_np, &slave_data->phy_if); if (ret) { dev_err(dev, "%pOF read phy-mode err %d\n", port_np, ret); goto err_node_put; } mac_addr = of_get_mac_address(port_np); if (!IS_ERR(mac_addr)) { ether_addr_copy(slave_data->mac_addr, mac_addr); } else { ret = ti_cm_get_macid(dev, port_id - 1, slave_data->mac_addr); if (ret) goto err_node_put; } if (of_property_read_u32(port_np, "ti,dual-emac-pvid", &prop)) { dev_err(dev, "%pOF Missing dual_emac_res_vlan in DT.\n", port_np); slave_data->dual_emac_res_vlan = port_id; dev_err(dev, "%pOF Using %d as Reserved VLAN\n", port_np, slave_data->dual_emac_res_vlan); } else { slave_data->dual_emac_res_vlan = prop; } } of_node_put(tmp_node); return 0; err_node_put: of_node_put(port_np); return ret; } static void cpsw_remove_dt(struct cpsw_common *cpsw) { struct cpsw_platform_data *data = &cpsw->data; int i = 0; for (i = 0; i < cpsw->data.slaves; i++) { struct cpsw_slave_data *slave_data = &data->slave_data[i]; struct device_node *port_np = slave_data->phy_node; if (port_np) { if (of_phy_is_fixed_link(port_np)) of_phy_deregister_fixed_link(port_np); of_node_put(port_np); } } } static int cpsw_create_ports(struct cpsw_common *cpsw) { struct cpsw_platform_data *data = &cpsw->data; struct net_device *ndev, *napi_ndev = NULL; struct device *dev = cpsw->dev; struct cpsw_priv *priv; int ret = 0, i = 0; for (i = 0; i < cpsw->data.slaves; i++) { struct cpsw_slave_data *slave_data = &data->slave_data[i]; if (slave_data->disabled) continue; ndev = devm_alloc_etherdev_mqs(dev, sizeof(struct cpsw_priv), CPSW_MAX_QUEUES, CPSW_MAX_QUEUES); if (!ndev) { dev_err(dev, "error allocating net_device\n"); return -ENOMEM; } priv = netdev_priv(ndev); priv->cpsw = cpsw; priv->ndev = ndev; priv->dev = dev; priv->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG); priv->emac_port = i + 1; if (is_valid_ether_addr(slave_data->mac_addr)) { ether_addr_copy(priv->mac_addr, slave_data->mac_addr); dev_info(cpsw->dev, "Detected MACID = %pM\n", priv->mac_addr); } else { eth_random_addr(slave_data->mac_addr); dev_info(cpsw->dev, "Random MACID = %pM\n", priv->mac_addr); } ether_addr_copy(ndev->dev_addr, slave_data->mac_addr); ether_addr_copy(priv->mac_addr, slave_data->mac_addr); cpsw->slaves[i].ndev = ndev; ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_NETNS_LOCAL; ndev->netdev_ops = &cpsw_netdev_ops; ndev->ethtool_ops = &cpsw_ethtool_ops; SET_NETDEV_DEV(ndev, dev); if (!napi_ndev) { /* CPSW Host port CPDMA interface is shared between * ports and there is only one TX and one RX IRQs * available for all possible TX and RX channels * accordingly. */ netif_napi_add(ndev, &cpsw->napi_rx, cpsw->quirk_irq ? cpsw_rx_poll : cpsw_rx_mq_poll, CPSW_POLL_WEIGHT); netif_tx_napi_add(ndev, &cpsw->napi_tx, cpsw->quirk_irq ? cpsw_tx_poll : cpsw_tx_mq_poll, CPSW_POLL_WEIGHT); } napi_ndev = ndev; } return ret; } static void cpsw_unregister_ports(struct cpsw_common *cpsw) { int i = 0; for (i = 0; i < cpsw->data.slaves; i++) { if (!cpsw->slaves[i].ndev) continue; unregister_netdev(cpsw->slaves[i].ndev); } } static int cpsw_register_ports(struct cpsw_common *cpsw) { int ret = 0, i = 0; for (i = 0; i < cpsw->data.slaves; i++) { if (!cpsw->slaves[i].ndev) continue; /* register the network device */ ret = register_netdev(cpsw->slaves[i].ndev); if (ret) { dev_err(cpsw->dev, "cpsw: err registering net device%d\n", i); cpsw->slaves[i].ndev = NULL; break; } } if (ret) cpsw_unregister_ports(cpsw); return ret; } bool cpsw_port_dev_check(const struct net_device *ndev) { if (ndev->netdev_ops == &cpsw_netdev_ops) { struct cpsw_common *cpsw = ndev_to_cpsw(ndev); return !cpsw->data.dual_emac; } return false; } static void cpsw_port_offload_fwd_mark_update(struct cpsw_common *cpsw) { int set_val = 0; int i; if (!cpsw->ale_bypass && (cpsw->br_members == (ALE_PORT_1 | ALE_PORT_2))) set_val = 1; dev_dbg(cpsw->dev, "set offload_fwd_mark %d\n", set_val); for (i = 0; i < cpsw->data.slaves; i++) { struct net_device *sl_ndev = cpsw->slaves[i].ndev; struct cpsw_priv *priv = netdev_priv(sl_ndev); priv->offload_fwd_mark = set_val; } } static int cpsw_netdevice_port_link(struct net_device *ndev, struct net_device *br_ndev) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; if (!cpsw->br_members) { cpsw->hw_bridge_dev = br_ndev; } else { /* This is adding the port to a second bridge, this is * unsupported */ if (cpsw->hw_bridge_dev != br_ndev) return -EOPNOTSUPP; } cpsw->br_members |= BIT(priv->emac_port); cpsw_port_offload_fwd_mark_update(cpsw); return NOTIFY_DONE; } static void cpsw_netdevice_port_unlink(struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); struct cpsw_common *cpsw = priv->cpsw; cpsw->br_members &= ~BIT(priv->emac_port); cpsw_port_offload_fwd_mark_update(cpsw); if (!cpsw->br_members) cpsw->hw_bridge_dev = NULL; } /* netdev notifier */ static int cpsw_netdevice_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct net_device *ndev = netdev_notifier_info_to_dev(ptr); struct netdev_notifier_changeupper_info *info; int ret = NOTIFY_DONE; if (!cpsw_port_dev_check(ndev)) return NOTIFY_DONE; switch (event) { case NETDEV_CHANGEUPPER: info = ptr; if (netif_is_bridge_master(info->upper_dev)) { if (info->linking) ret = cpsw_netdevice_port_link(ndev, info->upper_dev); else cpsw_netdevice_port_unlink(ndev); } break; default: return NOTIFY_DONE; } return notifier_from_errno(ret); } static struct notifier_block cpsw_netdevice_nb __read_mostly = { .notifier_call = cpsw_netdevice_event, }; static int cpsw_register_notifiers(struct cpsw_common *cpsw) { int ret = 0; ret = register_netdevice_notifier(&cpsw_netdevice_nb); if (ret) { dev_err(cpsw->dev, "can't register netdevice notifier\n"); return ret; } ret = cpsw_switchdev_register_notifiers(cpsw); if (ret) unregister_netdevice_notifier(&cpsw_netdevice_nb); return ret; } static void cpsw_unregister_notifiers(struct cpsw_common *cpsw) { cpsw_switchdev_unregister_notifiers(cpsw); unregister_netdevice_notifier(&cpsw_netdevice_nb); } static const struct devlink_ops cpsw_devlink_ops = { }; static int cpsw_dl_switch_mode_get(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx) { struct cpsw_devlink *dl_priv = devlink_priv(dl); struct cpsw_common *cpsw = dl_priv->cpsw; dev_dbg(cpsw->dev, "%s id:%u\n", __func__, id); if (id != CPSW_DL_PARAM_SWITCH_MODE) return -EOPNOTSUPP; ctx->val.vbool = !cpsw->data.dual_emac; return 0; } static int cpsw_dl_switch_mode_set(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx) { struct cpsw_devlink *dl_priv = devlink_priv(dl); struct cpsw_common *cpsw = dl_priv->cpsw; int vlan = cpsw->data.default_vlan; bool switch_en = ctx->val.vbool; bool if_running = false; int i; dev_dbg(cpsw->dev, "%s id:%u\n", __func__, id); if (id != CPSW_DL_PARAM_SWITCH_MODE) return -EOPNOTSUPP; if (switch_en == !cpsw->data.dual_emac) return 0; if (!switch_en && cpsw->br_members) { dev_err(cpsw->dev, "Remove ports from BR before disabling switch mode\n"); return -EINVAL; } rtnl_lock(); for (i = 0; i < cpsw->data.slaves; i++) { struct cpsw_slave *slave = &cpsw->slaves[i]; struct net_device *sl_ndev = slave->ndev; if (!sl_ndev || !netif_running(sl_ndev)) continue; if_running = true; } if (!if_running) { /* all ndevs are down */ cpsw->data.dual_emac = !switch_en; for (i = 0; i < cpsw->data.slaves; i++) { struct cpsw_slave *slave = &cpsw->slaves[i]; struct net_device *sl_ndev = slave->ndev; struct cpsw_priv *priv; if (!sl_ndev) continue; priv = netdev_priv(sl_ndev); if (switch_en) vlan = cpsw->data.default_vlan; else vlan = slave->data->dual_emac_res_vlan; slave->port_vlan = vlan; } goto exit; } if (switch_en) { dev_info(cpsw->dev, "Enable switch mode\n"); /* enable bypass - no forwarding; all traffic goes to Host */ cpsw_ale_control_set(cpsw->ale, 0, ALE_BYPASS, 1); /* clean up ALE table */ cpsw_ale_control_set(cpsw->ale, 0, ALE_CLEAR, 1); cpsw_ale_control_get(cpsw->ale, 0, ALE_AGEOUT); cpsw_init_host_port_switch(cpsw); for (i = 0; i < cpsw->data.slaves; i++) { struct cpsw_slave *slave = &cpsw->slaves[i]; struct net_device *sl_ndev = slave->ndev; struct cpsw_priv *priv; if (!sl_ndev) continue; priv = netdev_priv(sl_ndev); slave->port_vlan = vlan; if (netif_running(sl_ndev)) cpsw_port_add_switch_def_ale_entries(priv, slave); } cpsw_ale_control_set(cpsw->ale, 0, ALE_BYPASS, 0); cpsw->data.dual_emac = false; } else { dev_info(cpsw->dev, "Disable switch mode\n"); /* enable bypass - no forwarding; all traffic goes to Host */ cpsw_ale_control_set(cpsw->ale, 0, ALE_BYPASS, 1); cpsw_ale_control_set(cpsw->ale, 0, ALE_CLEAR, 1); cpsw_ale_control_get(cpsw->ale, 0, ALE_AGEOUT); cpsw_init_host_port_dual_mac(cpsw); for (i = 0; i < cpsw->data.slaves; i++) { struct cpsw_slave *slave = &cpsw->slaves[i]; struct net_device *sl_ndev = slave->ndev; struct cpsw_priv *priv; if (!sl_ndev) continue; priv = netdev_priv(slave->ndev); slave->port_vlan = slave->data->dual_emac_res_vlan; cpsw_port_add_dual_emac_def_ale_entries(priv, slave); } cpsw_ale_control_set(cpsw->ale, 0, ALE_BYPASS, 0); cpsw->data.dual_emac = true; } exit: rtnl_unlock(); return 0; } static int cpsw_dl_ale_ctrl_get(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx) { struct cpsw_devlink *dl_priv = devlink_priv(dl); struct cpsw_common *cpsw = dl_priv->cpsw; dev_dbg(cpsw->dev, "%s id:%u\n", __func__, id); switch (id) { case CPSW_DL_PARAM_ALE_BYPASS: ctx->val.vbool = cpsw_ale_control_get(cpsw->ale, 0, ALE_BYPASS); break; default: return -EOPNOTSUPP; } return 0; } static int cpsw_dl_ale_ctrl_set(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx) { struct cpsw_devlink *dl_priv = devlink_priv(dl); struct cpsw_common *cpsw = dl_priv->cpsw; int ret = -EOPNOTSUPP; dev_dbg(cpsw->dev, "%s id:%u\n", __func__, id); switch (id) { case CPSW_DL_PARAM_ALE_BYPASS: ret = cpsw_ale_control_set(cpsw->ale, 0, ALE_BYPASS, ctx->val.vbool); if (!ret) { cpsw->ale_bypass = ctx->val.vbool; cpsw_port_offload_fwd_mark_update(cpsw); } break; default: return -EOPNOTSUPP; } return 0; } static const struct devlink_param cpsw_devlink_params[] = { DEVLINK_PARAM_DRIVER(CPSW_DL_PARAM_SWITCH_MODE, "switch_mode", DEVLINK_PARAM_TYPE_BOOL, BIT(DEVLINK_PARAM_CMODE_RUNTIME), cpsw_dl_switch_mode_get, cpsw_dl_switch_mode_set, NULL), DEVLINK_PARAM_DRIVER(CPSW_DL_PARAM_ALE_BYPASS, "ale_bypass", DEVLINK_PARAM_TYPE_BOOL, BIT(DEVLINK_PARAM_CMODE_RUNTIME), cpsw_dl_ale_ctrl_get, cpsw_dl_ale_ctrl_set, NULL), }; static int cpsw_register_devlink(struct cpsw_common *cpsw) { struct device *dev = cpsw->dev; struct cpsw_devlink *dl_priv; int ret = 0; cpsw->devlink = devlink_alloc(&cpsw_devlink_ops, sizeof(*dl_priv)); if (!cpsw->devlink) return -ENOMEM; dl_priv = devlink_priv(cpsw->devlink); dl_priv->cpsw = cpsw; ret = devlink_register(cpsw->devlink, dev); if (ret) { dev_err(dev, "DL reg fail ret:%d\n", ret); goto dl_free; } ret = devlink_params_register(cpsw->devlink, cpsw_devlink_params, ARRAY_SIZE(cpsw_devlink_params)); if (ret) { dev_err(dev, "DL params reg fail ret:%d\n", ret); goto dl_unreg; } devlink_params_publish(cpsw->devlink); return ret; dl_unreg: devlink_unregister(cpsw->devlink); dl_free: devlink_free(cpsw->devlink); return ret; } static void cpsw_unregister_devlink(struct cpsw_common *cpsw) { devlink_params_unpublish(cpsw->devlink); devlink_params_unregister(cpsw->devlink, cpsw_devlink_params, ARRAY_SIZE(cpsw_devlink_params)); devlink_unregister(cpsw->devlink); devlink_free(cpsw->devlink); } static const struct of_device_id cpsw_of_mtable[] = { { .compatible = "ti,cpsw-switch"}, { .compatible = "ti,am335x-cpsw-switch"}, { .compatible = "ti,am4372-cpsw-switch"}, { .compatible = "ti,dra7-cpsw-switch"}, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, cpsw_of_mtable); static const struct soc_device_attribute cpsw_soc_devices[] = { { .family = "AM33xx", .revision = "ES1.0"}, { /* sentinel */ } }; static int cpsw_probe(struct platform_device *pdev) { const struct soc_device_attribute *soc; struct device *dev = &pdev->dev; struct cpsw_common *cpsw; struct resource *ss_res; struct gpio_descs *mode; void __iomem *ss_regs; int ret = 0, ch; struct clk *clk; int irq; cpsw = devm_kzalloc(dev, sizeof(struct cpsw_common), GFP_KERNEL); if (!cpsw) return -ENOMEM; cpsw_slave_index = cpsw_slave_index_priv; cpsw->dev = dev; cpsw->slaves = devm_kcalloc(dev, CPSW_SLAVE_PORTS_NUM, sizeof(struct cpsw_slave), GFP_KERNEL); if (!cpsw->slaves) return -ENOMEM; mode = devm_gpiod_get_array_optional(dev, "mode", GPIOD_OUT_LOW); if (IS_ERR(mode)) { ret = PTR_ERR(mode); dev_err(dev, "gpio request failed, ret %d\n", ret); return ret; } clk = devm_clk_get(dev, "fck"); if (IS_ERR(clk)) { ret = PTR_ERR(clk); dev_err(dev, "fck is not found %d\n", ret); return ret; } cpsw->bus_freq_mhz = clk_get_rate(clk) / 1000000; ss_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ss_regs = devm_ioremap_resource(dev, ss_res); if (IS_ERR(ss_regs)) { ret = PTR_ERR(ss_regs); return ret; } cpsw->regs = ss_regs; irq = platform_get_irq_byname(pdev, "rx"); if (irq < 0) return irq; cpsw->irqs_table[0] = irq; irq = platform_get_irq_byname(pdev, "tx"); if (irq < 0) return irq; cpsw->irqs_table[1] = irq; platform_set_drvdata(pdev, cpsw); /* This may be required here for child devices. */ pm_runtime_enable(dev); /* Need to enable clocks with runtime PM api to access module * registers */ ret = pm_runtime_get_sync(dev); if (ret < 0) { pm_runtime_put_noidle(dev); pm_runtime_disable(dev); return ret; } ret = cpsw_probe_dt(cpsw); if (ret) goto clean_dt_ret; soc = soc_device_match(cpsw_soc_devices); if (soc) cpsw->quirk_irq = 1; cpsw->rx_packet_max = rx_packet_max; cpsw->descs_pool_size = descs_pool_size; eth_random_addr(cpsw->base_mac); ret = cpsw_init_common(cpsw, ss_regs, ale_ageout, (u32 __force)ss_res->start + CPSW2_BD_OFFSET, descs_pool_size); if (ret) goto clean_dt_ret; cpsw->wr_regs = cpsw->version == CPSW_VERSION_1 ? ss_regs + CPSW1_WR_OFFSET : ss_regs + CPSW2_WR_OFFSET; ch = cpsw->quirk_irq ? 0 : 7; cpsw->txv[0].ch = cpdma_chan_create(cpsw->dma, ch, cpsw_tx_handler, 0); if (IS_ERR(cpsw->txv[0].ch)) { dev_err(dev, "error initializing tx dma channel\n"); ret = PTR_ERR(cpsw->txv[0].ch); goto clean_cpts; } cpsw->rxv[0].ch = cpdma_chan_create(cpsw->dma, 0, cpsw_rx_handler, 1); if (IS_ERR(cpsw->rxv[0].ch)) { dev_err(dev, "error initializing rx dma channel\n"); ret = PTR_ERR(cpsw->rxv[0].ch); goto clean_cpts; } cpsw_split_res(cpsw); /* setup netdevs */ ret = cpsw_create_ports(cpsw); if (ret) goto clean_unregister_netdev; /* Grab RX and TX IRQs. Note that we also have RX_THRESHOLD and * MISC IRQs which are always kept disabled with this driver so * we will not request them. * * If anyone wants to implement support for those, make sure to * first request and append them to irqs_table array. */ ret = devm_request_irq(dev, cpsw->irqs_table[0], cpsw_rx_interrupt, 0, dev_name(dev), cpsw); if (ret < 0) { dev_err(dev, "error attaching irq (%d)\n", ret); goto clean_unregister_netdev; } ret = devm_request_irq(dev, cpsw->irqs_table[1], cpsw_tx_interrupt, 0, dev_name(dev), cpsw); if (ret < 0) { dev_err(dev, "error attaching irq (%d)\n", ret); goto clean_unregister_netdev; } ret = cpsw_register_notifiers(cpsw); if (ret) goto clean_unregister_netdev; ret = cpsw_register_devlink(cpsw); if (ret) goto clean_unregister_notifiers; ret = cpsw_register_ports(cpsw); if (ret) goto clean_unregister_notifiers; dev_notice(dev, "initialized (regs %pa, pool size %d) hw_ver:%08X %d.%d (%d)\n", &ss_res->start, descs_pool_size, cpsw->version, CPSW_MAJOR_VERSION(cpsw->version), CPSW_MINOR_VERSION(cpsw->version), CPSW_RTL_VERSION(cpsw->version)); pm_runtime_put(dev); return 0; clean_unregister_notifiers: cpsw_unregister_notifiers(cpsw); clean_unregister_netdev: cpsw_unregister_ports(cpsw); clean_cpts: cpts_release(cpsw->cpts); cpdma_ctlr_destroy(cpsw->dma); clean_dt_ret: cpsw_remove_dt(cpsw); pm_runtime_put_sync(dev); pm_runtime_disable(dev); return ret; } static int cpsw_remove(struct platform_device *pdev) { struct cpsw_common *cpsw = platform_get_drvdata(pdev); int ret; ret = pm_runtime_get_sync(&pdev->dev); if (ret < 0) { pm_runtime_put_noidle(&pdev->dev); return ret; } cpsw_unregister_notifiers(cpsw); cpsw_unregister_devlink(cpsw); cpsw_unregister_ports(cpsw); cpts_release(cpsw->cpts); cpdma_ctlr_destroy(cpsw->dma); cpsw_remove_dt(cpsw); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return 0; } static struct platform_driver cpsw_driver = { .driver = { .name = "cpsw-switch", .of_match_table = cpsw_of_mtable, }, .probe = cpsw_probe, .remove = cpsw_remove, }; module_platform_driver(cpsw_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("TI CPSW switchdev Ethernet driver");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1