diff options
Diffstat (limited to 'drivers/net/ethernet/ti')
21 files changed, 2317 insertions, 76 deletions
diff --git a/drivers/net/ethernet/ti/Kconfig b/drivers/net/ethernet/ti/Kconfig index fe5b2926d8ab..c60b04921c62 100644 --- a/drivers/net/ethernet/ti/Kconfig +++ b/drivers/net/ethernet/ti/Kconfig @@ -192,6 +192,7 @@ config TI_ICSSG_PRUETH depends on NET_SWITCHDEV depends on ARCH_K3 && OF && TI_K3_UDMA_GLUE_LAYER depends on PTP_1588_CLOCK_OPTIONAL + depends on HSR || !HSR help Support dual Gigabit Ethernet ports over the ICSSG PRU Subsystem. This subsystem is available starting with the AM65 platform. diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile index 93c0a4d0e33a..6da50f4b7c2e 100644 --- a/drivers/net/ethernet/ti/Makefile +++ b/drivers/net/ethernet/ti/Makefile @@ -4,7 +4,7 @@ # obj-$(CONFIG_TI_PRUETH) += icssm-prueth.o -icssm-prueth-y := icssm/icssm_prueth.o +icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o obj-$(CONFIG_TI_CPSW) += cpsw-common.o obj-$(CONFIG_TI_DAVINCI_EMAC) += cpsw-common.o diff --git a/drivers/net/ethernet/ti/am65-cpsw-ethtool.c b/drivers/net/ethernet/ti/am65-cpsw-ethtool.c index c57497074ae6..98d60da7cc3b 100644 --- a/drivers/net/ethernet/ti/am65-cpsw-ethtool.c +++ b/drivers/net/ethernet/ti/am65-cpsw-ethtool.c @@ -391,11 +391,8 @@ static int am65_cpsw_ethtool_op_begin(struct net_device *ndev) static void am65_cpsw_ethtool_op_complete(struct net_device *ndev) { struct am65_cpsw_common *common = am65_ndev_to_common(ndev); - int ret; - ret = pm_runtime_put(common->dev); - if (ret < 0 && ret != -EBUSY) - dev_err(common->dev, "ethtool complete failed %d\n", ret); + pm_runtime_put(common->dev); } static void am65_cpsw_get_drvinfo(struct net_device *ndev, diff --git a/drivers/net/ethernet/ti/cpsw_ale.c b/drivers/net/ethernet/ti/cpsw_ale.c index fbe35af615a6..bb969dd435b4 100644 --- a/drivers/net/ethernet/ti/cpsw_ale.c +++ b/drivers/net/ethernet/ti/cpsw_ale.c @@ -23,11 +23,6 @@ #define BITMASK(bits) (BIT(bits) - 1) -#define ALE_VERSION_MAJOR(rev, mask) (((rev) >> 8) & (mask)) -#define ALE_VERSION_MINOR(rev) (rev & 0xff) -#define ALE_VERSION_1R3 0x0103 -#define ALE_VERSION_1R4 0x0104 - /* ALE Registers */ #define ALE_IDVER 0x00 #define ALE_STATUS 0x04 diff --git a/drivers/net/ethernet/ti/cpsw_ethtool.c b/drivers/net/ethernet/ti/cpsw_ethtool.c index bdc4db0d169c..a43f75ee269e 100644 --- a/drivers/net/ethernet/ti/cpsw_ethtool.c +++ b/drivers/net/ethernet/ti/cpsw_ethtool.c @@ -374,11 +374,8 @@ int cpsw_ethtool_op_begin(struct net_device *ndev) void cpsw_ethtool_op_complete(struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); - int ret; - ret = pm_runtime_put(priv->cpsw->dev); - if (ret < 0) - cpsw_err(priv, drv, "ethtool complete failed %d\n", ret); + pm_runtime_put(priv->cpsw->dev); } void cpsw_get_channels(struct net_device *ndev, struct ethtool_channels *ch) diff --git a/drivers/net/ethernet/ti/cpsw_new.c b/drivers/net/ethernet/ti/cpsw_new.c index 21af0a10626a..7f42f58a4b03 100644 --- a/drivers/net/ethernet/ti/cpsw_new.c +++ b/drivers/net/ethernet/ti/cpsw_new.c @@ -1472,7 +1472,7 @@ static void cpsw_unregister_ports(struct cpsw_common *cpsw) for (i = 0; i < cpsw->data.slaves; i++) { ndev = cpsw->slaves[i].ndev; - if (!ndev) + if (!ndev || ndev->reg_state != NETREG_REGISTERED) continue; priv = netdev_priv(ndev); @@ -1494,7 +1494,6 @@ static int cpsw_register_ports(struct cpsw_common *cpsw) if (ret) { dev_err(cpsw->dev, "cpsw: err registering net device%d\n", i); - cpsw->slaves[i].ndev = NULL; break; } } @@ -2003,7 +2002,7 @@ static int cpsw_probe(struct platform_device *pdev) /* setup netdevs */ ret = cpsw_create_ports(cpsw); if (ret) - goto clean_unregister_netdev; + goto clean_cpts; /* Grab RX and TX IRQs. Note that we also have RX_THRESHOLD and * MISC IRQs which are always kept disabled with this driver so @@ -2017,14 +2016,14 @@ static int cpsw_probe(struct platform_device *pdev) 0, dev_name(dev), cpsw); if (ret < 0) { dev_err(dev, "error attaching irq (%d)\n", ret); - goto clean_unregister_netdev; + goto clean_cpts; } 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; + goto clean_cpts; } if (!cpsw->cpts) @@ -2034,7 +2033,7 @@ static int cpsw_probe(struct platform_device *pdev) 0, dev_name(&pdev->dev), cpsw); if (ret < 0) { dev_err(dev, "error attaching misc irq (%d)\n", ret); - goto clean_unregister_netdev; + goto clean_cpts; } /* Enable misc CPTS evnt_pend IRQ */ @@ -2043,7 +2042,7 @@ static int cpsw_probe(struct platform_device *pdev) skip_cpts: ret = cpsw_register_notifiers(cpsw); if (ret) - goto clean_unregister_netdev; + goto clean_cpts; ret = cpsw_register_devlink(cpsw); if (ret) @@ -2065,8 +2064,6 @@ skip_cpts: 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); diff --git a/drivers/net/ethernet/ti/icssg/icssg_common.c b/drivers/net/ethernet/ti/icssg/icssg_common.c index 090aa74d3ce7..0cf9dfe0fa36 100644 --- a/drivers/net/ethernet/ti/icssg/icssg_common.c +++ b/drivers/net/ethernet/ti/icssg/icssg_common.c @@ -1720,7 +1720,6 @@ void prueth_netdev_exit(struct prueth *prueth, netif_napi_del(&emac->napi_rx); pruss_release_mem_region(prueth->pruss, &emac->dram); - destroy_workqueue(emac->cmd_wq); free_netdev(emac->ndev); prueth->emac[mac] = NULL; } diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.c b/drivers/net/ethernet/ti/icssg/icssg_prueth.c index f65041662173..0939994c932f 100644 --- a/drivers/net/ethernet/ti/icssg/icssg_prueth.c +++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.c @@ -1099,7 +1099,7 @@ static void emac_ndo_set_rx_mode(struct net_device *ndev) { struct prueth_emac *emac = netdev_priv(ndev); - queue_work(emac->cmd_wq, &emac->rx_mode_work); + schedule_work(&emac->rx_mode_work); } static netdev_features_t emac_ndo_fix_features(struct net_device *ndev, @@ -1451,11 +1451,6 @@ static int prueth_netdev_init(struct prueth *prueth, emac->port_id = port; emac->xdp_prog = NULL; emac->ndev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS; - emac->cmd_wq = create_singlethread_workqueue("icssg_cmd_wq"); - if (!emac->cmd_wq) { - ret = -ENOMEM; - goto free_ndev; - } INIT_WORK(&emac->rx_mode_work, emac_ndo_set_rx_mode_work); INIT_DELAYED_WORK(&emac->stats_work, icssg_stats_work_handler); @@ -1467,7 +1462,7 @@ static int prueth_netdev_init(struct prueth *prueth, if (ret) { dev_err(prueth->dev, "unable to get DRAM: %d\n", ret); ret = -ENOMEM; - goto free_wq; + goto free_ndev; } emac->tx_ch_num = 1; @@ -1566,8 +1561,6 @@ static int prueth_netdev_init(struct prueth *prueth, free: pruss_release_mem_region(prueth->pruss, &emac->dram); -free_wq: - destroy_workqueue(emac->cmd_wq); free_ndev: emac->ndev = NULL; prueth->emac[mac] = NULL; @@ -2236,6 +2229,7 @@ netdev_unregister: prueth->emac[i]->ndev->phydev = NULL; } unregister_netdev(prueth->registered_netdevs[i]); + disable_work_sync(&prueth->emac[i]->rx_mode_work); } netdev_exit: @@ -2295,6 +2289,7 @@ static void prueth_remove(struct platform_device *pdev) phy_disconnect(prueth->emac[i]->ndev->phydev); prueth->emac[i]->ndev->phydev = NULL; unregister_netdev(prueth->registered_netdevs[i]); + disable_work_sync(&prueth->emac[i]->rx_mode_work); } for (i = 0; i < PRUETH_NUM_MACS; i++) { diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.h b/drivers/net/ethernet/ti/icssg/icssg_prueth.h index 10eadd356650..3d94fa5a7ac1 100644 --- a/drivers/net/ethernet/ti/icssg/icssg_prueth.h +++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.h @@ -236,7 +236,6 @@ struct prueth_emac { /* Mutex to serialize access to firmware command interface */ struct mutex cmd_lock; struct work_struct rx_mode_work; - struct workqueue_struct *cmd_wq; struct pruss_mem_region dram; diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth_sr1.c b/drivers/net/ethernet/ti/icssg/icssg_prueth_sr1.c index 7bb4f0d850cc..b8115ca47082 100644 --- a/drivers/net/ethernet/ti/icssg/icssg_prueth_sr1.c +++ b/drivers/net/ethernet/ti/icssg/icssg_prueth_sr1.c @@ -783,11 +783,6 @@ static int prueth_netdev_init(struct prueth *prueth, emac->prueth = prueth; emac->ndev = ndev; emac->port_id = port; - emac->cmd_wq = create_singlethread_workqueue("icssg_cmd_wq"); - if (!emac->cmd_wq) { - ret = -ENOMEM; - goto free_ndev; - } INIT_DELAYED_WORK(&emac->stats_work, icssg_stats_work_handler); @@ -798,7 +793,7 @@ static int prueth_netdev_init(struct prueth *prueth, if (ret) { dev_err(prueth->dev, "unable to get DRAM: %d\n", ret); ret = -ENOMEM; - goto free_wq; + goto free_ndev; } /* SR1.0 uses a dedicated high priority channel @@ -883,8 +878,6 @@ static int prueth_netdev_init(struct prueth *prueth, free: pruss_release_mem_region(prueth->pruss, &emac->dram); -free_wq: - destroy_workqueue(emac->cmd_wq); free_ndev: emac->ndev = NULL; prueth->emac[mac] = NULL; diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c b/drivers/net/ethernet/ti/icssm/icssm_prueth.c index 293b7af04263..53bbd9290904 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth.c +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.c @@ -29,6 +29,8 @@ #include <net/pkt_cls.h> #include "icssm_prueth.h" +#include "icssm_prueth_switch.h" +#include "icssm_vlan_mcast_filter_mmap.h" #include "../icssg/icssg_mii_rt.h" #include "../icssg/icss_iep.h" @@ -145,7 +147,7 @@ static const struct prueth_queue_info queue_infos[][NUM_QUEUES] = { }, }; -static const struct prueth_queue_desc queue_descs[][NUM_QUEUES] = { +const struct prueth_queue_desc queue_descs[][NUM_QUEUES] = { [PRUETH_PORT_QUEUE_HOST] = { { .rd_ptr = P0_Q1_BD_OFFSET, .wr_ptr = P0_Q1_BD_OFFSET, }, { .rd_ptr = P0_Q2_BD_OFFSET, .wr_ptr = P0_Q2_BD_OFFSET, }, @@ -205,9 +207,9 @@ static void icssm_prueth_hostconfig(struct prueth *prueth) static void icssm_prueth_mii_init(struct prueth *prueth) { + u32 txcfg_reg, txcfg, txcfg2; struct regmap *mii_rt; u32 rxcfg_reg, rxcfg; - u32 txcfg_reg, txcfg; mii_rt = prueth->mii_rt; @@ -235,17 +237,23 @@ static void icssm_prueth_mii_init(struct prueth *prueth) (TX_START_DELAY << PRUSS_MII_RT_TXCFG_TX_START_DELAY_SHIFT) | (TX_CLK_DELAY_100M << PRUSS_MII_RT_TXCFG_TX_CLK_DELAY_SHIFT); + txcfg2 = txcfg; + if (!PRUETH_IS_EMAC(prueth)) + txcfg2 |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL; + /* Configuration of Port 0 Tx */ txcfg_reg = PRUSS_MII_RT_TXCFG0; - regmap_write(mii_rt, txcfg_reg, txcfg); + regmap_write(mii_rt, txcfg_reg, txcfg2); - txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL; + txcfg2 = txcfg; + if (PRUETH_IS_EMAC(prueth)) + txcfg2 |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL; /* Configuration of Port 1 Tx */ txcfg_reg = PRUSS_MII_RT_TXCFG1; - regmap_write(mii_rt, txcfg_reg, txcfg); + regmap_write(mii_rt, txcfg_reg, txcfg2); txcfg_reg = PRUSS_MII_RT_RX_FRMS0; @@ -292,7 +300,10 @@ static void icssm_prueth_hostinit(struct prueth *prueth) icssm_prueth_clearmem(prueth, PRUETH_MEM_DRAM1); /* Initialize host queues in shared RAM */ - icssm_prueth_hostconfig(prueth); + if (!PRUETH_IS_EMAC(prueth)) + icssm_prueth_sw_hostconfig(prueth); + else + icssm_prueth_hostconfig(prueth); /* Configure MII_RT */ icssm_prueth_mii_init(prueth); @@ -499,19 +510,24 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac, struct prueth_queue_desc __iomem *queue_desc; const struct prueth_queue_info *txqueue; struct net_device *ndev = emac->ndev; + struct prueth *prueth = emac->prueth; unsigned int buffer_desc_count; int free_blocks, update_block; bool buffer_wrapped = false; int write_block, read_block; void *src_addr, *dst_addr; int pkt_block_size; + void __iomem *sram; void __iomem *dram; int txport, pktlen; u16 update_wr_ptr; u32 wr_buf_desc; void *ocmc_ram; - dram = emac->prueth->mem[emac->dram].va; + if (!PRUETH_IS_EMAC(prueth)) + dram = prueth->mem[PRUETH_MEM_DRAM1].va; + else + dram = emac->prueth->mem[emac->dram].va; if (eth_skb_pad(skb)) { if (netif_msg_tx_err(emac) && net_ratelimit()) netdev_err(ndev, "packet pad failed\n"); @@ -524,7 +540,10 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac, pktlen = skb->len; /* Get the tx queue */ queue_desc = emac->tx_queue_descs + queue_id; - txqueue = &queue_infos[txport][queue_id]; + if (!PRUETH_IS_EMAC(prueth)) + txqueue = &sw_queue_infos[txport][queue_id]; + else + txqueue = &queue_infos[txport][queue_id]; buffer_desc_count = icssm_get_buff_desc_count(txqueue); @@ -590,7 +609,11 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac, /* update first buffer descriptor */ wr_buf_desc = (pktlen << PRUETH_BD_LENGTH_SHIFT) & PRUETH_BD_LENGTH_MASK; - writel(wr_buf_desc, dram + readw(&queue_desc->wr_ptr)); + sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va; + if (!PRUETH_IS_EMAC(prueth)) + writel(wr_buf_desc, sram + readw(&queue_desc->wr_ptr)); + else + writel(wr_buf_desc, dram + readw(&queue_desc->wr_ptr)); /* update the write pointer in this queue descriptor, the firmware * polls for this change so this will signal the start of transmission @@ -604,7 +627,6 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac, void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor, struct prueth_packet_info *pkt_info) { - pkt_info->shadow = !!(buffer_descriptor & PRUETH_BD_SHADOW_MASK); pkt_info->port = (buffer_descriptor & PRUETH_BD_PORT_MASK) >> PRUETH_BD_PORT_SHIFT; pkt_info->length = (buffer_descriptor & PRUETH_BD_LENGTH_MASK) >> @@ -713,11 +735,19 @@ int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr, src_addr += actual_pkt_len; } + if (PRUETH_IS_SWITCH(emac->prueth)) { + skb->offload_fwd_mark = READ_ONCE(emac->offload_fwd_mark); + if (!pkt_info->lookup_success) + icssm_prueth_sw_learn_fdb(emac, skb->data + ETH_ALEN); + } + skb_put(skb, actual_pkt_len); /* send packet up the stack */ skb->protocol = eth_type_trans(skb, ndev); + local_bh_disable(); netif_receive_skb(skb); + local_bh_enable(); /* update stats */ emac->stats.rx_bytes += actual_pkt_len; @@ -743,6 +773,7 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget) shared_ram = emac->prueth->mem[PRUETH_MEM_SHARED_RAM].va; + /* Start and end queue is made common for EMAC, RSTP */ start_queue = emac->rx_queue_start; end_queue = emac->rx_queue_end; @@ -753,8 +784,10 @@ static int icssm_emac_rx_packets(struct prueth_emac *emac, int budget) /* search host queues for packets */ for (i = start_queue; i <= end_queue; i++) { queue_desc = emac->rx_queue_descs + i; - rxqueue = &queue_infos[PRUETH_PORT_HOST][i]; - + if (PRUETH_IS_SWITCH(emac->prueth)) + rxqueue = &sw_queue_infos[PRUETH_PORT_HOST][i]; + else + rxqueue = &queue_infos[PRUETH_PORT_HOST][i]; overflow_cnt = readb(&queue_desc->overflow_cnt); if (overflow_cnt > 0) { emac->stats.rx_over_errors += overflow_cnt; @@ -879,6 +912,13 @@ static int icssm_emac_request_irqs(struct prueth_emac *emac) return ret; } +/* Function to free memory related to sw */ +static void icssm_prueth_free_memory(struct prueth *prueth) +{ + if (PRUETH_IS_SWITCH(prueth)) + icssm_prueth_sw_free_fdb_table(prueth); +} + static void icssm_ptp_dram_init(struct prueth_emac *emac) { void __iomem *sram = emac->prueth->mem[PRUETH_MEM_SHARED_RAM].va; @@ -941,20 +981,38 @@ static int icssm_emac_ndo_open(struct net_device *ndev) if (!prueth->emac_configured) icssm_prueth_init_ethernet_mode(prueth); - icssm_prueth_emac_config(emac); + /* reset and start PRU firmware */ + if (PRUETH_IS_SWITCH(prueth)) { + ret = icssm_prueth_sw_emac_config(emac); + if (ret) + return ret; + + ret = icssm_prueth_sw_init_fdb_table(prueth); + if (ret) + return ret; + } else { + icssm_prueth_emac_config(emac); + } if (!prueth->emac_configured) { icssm_ptp_dram_init(emac); ret = icss_iep_init(prueth->iep, NULL, NULL, 0); if (ret) { netdev_err(ndev, "Failed to initialize iep: %d\n", ret); - goto iep_exit; + goto free_mem; } } - ret = icssm_emac_set_boot_pru(emac, ndev); - if (ret) - goto iep_exit; + if (!PRUETH_IS_EMAC(prueth)) { + ret = icssm_prueth_sw_boot_prus(prueth, ndev); + if (ret) + goto iep_exit; + } else { + /* boot the PRU */ + ret = icssm_emac_set_boot_pru(emac, ndev); + if (ret) + goto iep_exit; + } ret = icssm_emac_request_irqs(emac); if (ret) @@ -969,19 +1027,25 @@ static int icssm_emac_ndo_open(struct net_device *ndev) icssm_prueth_port_enable(emac, true); prueth->emac_configured |= BIT(emac->port_id); - + if (PRUETH_IS_SWITCH(prueth)) + icssm_prueth_sw_set_stp_state(prueth, emac->port_id, + BR_STATE_LEARNING); if (netif_msg_drv(emac)) dev_notice(&ndev->dev, "started\n"); return 0; rproc_shutdown: - rproc_shutdown(emac->pru); + if (!PRUETH_IS_EMAC(prueth)) + icssm_prueth_sw_shutdown_prus(emac, ndev); + else + rproc_shutdown(emac->pru); iep_exit: if (!prueth->emac_configured) icss_iep_exit(prueth->iep); - +free_mem: + icssm_prueth_free_memory(emac->prueth); return ret; } @@ -1010,17 +1074,83 @@ static int icssm_emac_ndo_stop(struct net_device *ndev) hrtimer_cancel(&emac->tx_hrtimer); /* stop the PRU */ - rproc_shutdown(emac->pru); + if (!PRUETH_IS_EMAC(prueth)) + icssm_prueth_sw_shutdown_prus(emac, ndev); + else + rproc_shutdown(emac->pru); /* free rx interrupts */ free_irq(emac->rx_irq, ndev); + /* free memory related to sw */ + icssm_prueth_free_memory(emac->prueth); + + if (!prueth->emac_configured) + icss_iep_exit(prueth->iep); + if (netif_msg_drv(emac)) dev_notice(&ndev->dev, "stopped\n"); return 0; } +static int icssm_prueth_change_mode(struct prueth *prueth, + enum pruss_ethtype mode) +{ + bool portstatus[PRUETH_NUM_MACS]; + struct prueth_emac *emac; + struct net_device *ndev; + int i, ret; + + for (i = 0; i < PRUETH_NUM_MACS; i++) { + if (!prueth->emac[i]) { + dev_err(prueth->dev, "Unknown MAC port\n"); + return -EINVAL; + } + + emac = prueth->emac[i]; + ndev = emac->ndev; + + portstatus[i] = netif_running(ndev); + if (!portstatus[i]) + continue; + + ret = ndev->netdev_ops->ndo_stop(ndev); + if (ret < 0) { + netdev_err(ndev, "failed to stop: %d", ret); + return ret; + } + } + + if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH) { + prueth->eth_type = mode; + } else { + dev_err(prueth->dev, "unknown mode\n"); + return -EINVAL; + } + + for (i = 0; i < PRUETH_NUM_MACS; i++) { + if (!prueth->emac[i]) { + dev_err(prueth->dev, "Unknown MAC port\n"); + return -EINVAL; + } + + emac = prueth->emac[i]; + ndev = emac->ndev; + + if (!portstatus[i]) + continue; + + ret = ndev->netdev_ops->ndo_open(ndev); + if (ret < 0) { + netdev_err(ndev, "failed to start: %d", ret); + return ret; + } + } + + return 0; +} + /* VLAN-tag PCP to priority queue map for EMAC/Switch/HSR/PRP used by driver * Index is PCP val / 2. * low - pcp 0..3 maps to Q4 for Host @@ -1131,11 +1261,183 @@ static void icssm_emac_ndo_get_stats64(struct net_device *ndev, stats->rx_length_errors = emac->stats.rx_length_errors; } +/* enable/disable MC filter */ +static void icssm_emac_mc_filter_ctrl(struct prueth_emac *emac, bool enable) +{ + struct prueth *prueth = emac->prueth; + void __iomem *mc_filter_ctrl; + void __iomem *ram; + u32 reg; + + ram = prueth->mem[emac->dram].va; + mc_filter_ctrl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET; + + if (enable) + reg = ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_ENABLED; + else + reg = ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_DISABLED; + + writeb(reg, mc_filter_ctrl); +} + +/* reset MC filter bins */ +static void icssm_emac_mc_filter_reset(struct prueth_emac *emac) +{ + struct prueth *prueth = emac->prueth; + void __iomem *mc_filter_tbl; + u32 mc_filter_tbl_base; + void __iomem *ram; + + ram = prueth->mem[emac->dram].va; + mc_filter_tbl_base = ICSS_EMAC_FW_MULTICAST_FILTER_TABLE; + + mc_filter_tbl = ram + mc_filter_tbl_base; + memset_io(mc_filter_tbl, 0, ICSS_EMAC_FW_MULTICAST_TABLE_SIZE_BYTES); +} + +/* set MC filter hashmask */ +static void icssm_emac_mc_filter_hashmask + (struct prueth_emac *emac, + u8 mask[ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES]) +{ + struct prueth *prueth = emac->prueth; + void __iomem *mc_filter_mask; + void __iomem *ram; + + ram = prueth->mem[emac->dram].va; + + mc_filter_mask = ram + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET; + memcpy_toio(mc_filter_mask, mask, + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES); +} + +static void icssm_emac_mc_filter_bin_update(struct prueth_emac *emac, u8 hash, + u8 val) +{ + struct prueth *prueth = emac->prueth; + void __iomem *mc_filter_tbl; + void __iomem *ram; + + ram = prueth->mem[emac->dram].va; + + mc_filter_tbl = ram + ICSS_EMAC_FW_MULTICAST_FILTER_TABLE; + writeb(val, mc_filter_tbl + hash); +} + +void icssm_emac_mc_filter_bin_allow(struct prueth_emac *emac, u8 hash) +{ + icssm_emac_mc_filter_bin_update + (emac, hash, + ICSS_EMAC_FW_MULTICAST_FILTER_HOST_RCV_ALLOWED); +} + +void icssm_emac_mc_filter_bin_disallow(struct prueth_emac *emac, u8 hash) +{ + icssm_emac_mc_filter_bin_update + (emac, hash, + ICSS_EMAC_FW_MULTICAST_FILTER_HOST_RCV_NOT_ALLOWED); +} + +u8 icssm_emac_get_mc_hash(u8 *mac, u8 *mask) +{ + u8 hash; + int j; + + for (j = 0, hash = 0; j < ETH_ALEN; j++) + hash ^= (mac[j] & mask[j]); + + return hash; +} + +/** + * icssm_emac_ndo_set_rx_mode - EMAC set receive mode function + * @ndev: The EMAC network adapter + * + * Called when system wants to set the receive mode of the device. + * + */ +static void icssm_emac_ndo_set_rx_mode(struct net_device *ndev) +{ + struct prueth_emac *emac = netdev_priv(ndev); + bool promisc = ndev->flags & IFF_PROMISC; + struct netdev_hw_addr *ha; + struct prueth *prueth; + unsigned long flags; + void __iomem *sram; + u32 mask, reg; + u8 hash; + + prueth = emac->prueth; + sram = prueth->mem[PRUETH_MEM_SHARED_RAM].va; + reg = readl(sram + EMAC_PROMISCUOUS_MODE_OFFSET); + + /* It is a shared table. So lock the access */ + spin_lock_irqsave(&emac->addr_lock, flags); + + /* Disable and reset multicast filter, allows allmulti */ + icssm_emac_mc_filter_ctrl(emac, false); + icssm_emac_mc_filter_reset(emac); + icssm_emac_mc_filter_hashmask(emac, emac->mc_filter_mask); + + if (PRUETH_IS_EMAC(prueth)) { + switch (emac->port_id) { + case PRUETH_PORT_MII0: + mask = EMAC_P1_PROMISCUOUS_BIT; + break; + case PRUETH_PORT_MII1: + mask = EMAC_P2_PROMISCUOUS_BIT; + break; + default: + netdev_err(ndev, "%s: invalid port\n", __func__); + goto unlock; + } + + if (promisc) { + /* Enable promiscuous mode */ + reg |= mask; + } else { + /* Disable promiscuous mode */ + reg &= ~mask; + } + + writel(reg, sram + EMAC_PROMISCUOUS_MODE_OFFSET); + + if (promisc) + goto unlock; + } + + if (ndev->flags & IFF_ALLMULTI && !PRUETH_IS_SWITCH(prueth)) + goto unlock; + + icssm_emac_mc_filter_ctrl(emac, true); /* all multicast blocked */ + + if (netdev_mc_empty(ndev)) + goto unlock; + + netdev_for_each_mc_addr(ha, ndev) { + hash = icssm_emac_get_mc_hash(ha->addr, emac->mc_filter_mask); + icssm_emac_mc_filter_bin_allow(emac, hash); + } + + /* Add bridge device's MC addresses as well */ + if (prueth->hw_bridge_dev) { + netdev_for_each_mc_addr(ha, prueth->hw_bridge_dev) { + hash = icssm_emac_get_mc_hash(ha->addr, + emac->mc_filter_mask); + icssm_emac_mc_filter_bin_allow(emac, hash); + } + } + +unlock: + spin_unlock_irqrestore(&emac->addr_lock, flags); +} + static const struct net_device_ops emac_netdev_ops = { .ndo_open = icssm_emac_ndo_open, .ndo_stop = icssm_emac_ndo_stop, .ndo_start_xmit = icssm_emac_ndo_start_xmit, .ndo_get_stats64 = icssm_emac_ndo_get_stats64, + .ndo_set_rx_mode = icssm_emac_ndo_set_rx_mode, }; /* get emac_port corresponding to eth_node name */ @@ -1188,6 +1490,7 @@ static enum hrtimer_restart icssm_emac_tx_timer_callback(struct hrtimer *timer) static int icssm_prueth_netdev_init(struct prueth *prueth, struct device_node *eth_node) { + const struct prueth_private_data *fw_data = prueth->fw_data; struct prueth_emac *emac; struct net_device *ndev; enum prueth_port port; @@ -1212,6 +1515,7 @@ static int icssm_prueth_netdev_init(struct prueth *prueth, emac->prueth = prueth; emac->ndev = ndev; emac->port_id = port; + memset(&emac->mc_filter_mask[0], 0xff, ETH_ALEN); /* by default eth_type is EMAC */ switch (port) { @@ -1247,6 +1551,9 @@ static int icssm_prueth_netdev_init(struct prueth *prueth, goto free; } + spin_lock_init(&emac->lock); + spin_lock_init(&emac->addr_lock); + /* get mac address from DT and set private and netdev addr */ ret = of_get_ethdev_address(eth_node, ndev); if (!is_valid_ether_addr(ndev->dev_addr)) { @@ -1274,6 +1581,14 @@ static int icssm_prueth_netdev_init(struct prueth *prueth, phy_remove_link_mode(emac->phydev, ETHTOOL_LINK_MODE_Pause_BIT); phy_remove_link_mode(emac->phydev, ETHTOOL_LINK_MODE_Asym_Pause_BIT); + /* Protocol switching + * Enabling L2 Firmware offloading + */ + if (fw_data->support_switch) { + ndev->features |= NETIF_F_HW_L2FW_DOFFLOAD; + ndev->hw_features |= NETIF_F_HW_L2FW_DOFFLOAD; + } + ndev->dev.of_node = eth_node; ndev->netdev_ops = &emac_netdev_ops; @@ -1310,6 +1625,169 @@ static void icssm_prueth_netdev_exit(struct prueth *prueth, prueth->emac[mac] = NULL; } +bool icssm_prueth_sw_port_dev_check(const struct net_device *ndev) +{ + if (ndev->netdev_ops != &emac_netdev_ops) + return false; + + if (ndev->features & NETIF_F_HW_L2FW_DOFFLOAD) + return true; + + return false; +} + +static int icssm_prueth_port_offload_fwd_mark_update(struct prueth *prueth) +{ + int set_val = 0; + int i, ret = 0; + u8 all_slaves; + + all_slaves = BIT(PRUETH_PORT_MII0) | BIT(PRUETH_PORT_MII1); + + if (prueth->br_members == all_slaves) + set_val = 1; + + dev_dbg(prueth->dev, "set offload_fwd_mark %d, mbrs=0x%x\n", + set_val, prueth->br_members); + + for (i = 0; i < PRUETH_NUM_MACS; i++) { + if (prueth->emac[i]) + WRITE_ONCE(prueth->emac[i]->offload_fwd_mark, set_val); + } + + /* Bridge is created, load switch firmware, + * if not already in that mode + */ + if (set_val && !PRUETH_IS_SWITCH(prueth)) { + ret = icssm_prueth_change_mode(prueth, PRUSS_ETHTYPE_SWITCH); + if (ret < 0) + dev_err(prueth->dev, "Failed to enable Switch mode\n"); + else + dev_info(prueth->dev, + "TI PRU ethernet now in Switch mode\n"); + } + + /* Bridge is deleted, switch to Dual EMAC mode */ + if (!prueth->br_members && !PRUETH_IS_EMAC(prueth)) { + ret = icssm_prueth_change_mode(prueth, PRUSS_ETHTYPE_EMAC); + if (ret < 0) + dev_err(prueth->dev, "Failed to enable Dual EMAC mode\n"); + else + dev_info(prueth->dev, + "TI PRU ethernet now in Dual EMAC mode\n"); + } + + return ret; +} + +static int icssm_prueth_ndev_port_link(struct net_device *ndev, + struct net_device *br_ndev) +{ + struct prueth_emac *emac = netdev_priv(ndev); + struct prueth *prueth = emac->prueth; + unsigned long flags; + int ret = 0; + + dev_dbg(prueth->dev, "%s: br_mbrs=0x%x %s\n", + __func__, prueth->br_members, ndev->name); + + spin_lock_irqsave(&emac->addr_lock, flags); + + if (!prueth->br_members) { + prueth->hw_bridge_dev = br_ndev; + } else { + /* This is adding the port to a second bridge, + * this is unsupported + */ + if (prueth->hw_bridge_dev != br_ndev) { + spin_unlock_irqrestore(&emac->addr_lock, flags); + return -EOPNOTSUPP; + } + } + + prueth->br_members |= BIT(emac->port_id); + + spin_unlock_irqrestore(&emac->addr_lock, flags); + + ret = icssm_prueth_port_offload_fwd_mark_update(prueth); + + return ret; +} + +static int icssm_prueth_ndev_port_unlink(struct net_device *ndev) +{ + struct prueth_emac *emac = netdev_priv(ndev); + struct prueth *prueth = emac->prueth; + unsigned long flags; + int ret = 0; + + dev_dbg(prueth->dev, "emac_sw_ndev_port_unlink\n"); + + spin_lock_irqsave(&emac->addr_lock, flags); + + prueth->br_members &= ~BIT(emac->port_id); + + spin_unlock_irqrestore(&emac->addr_lock, flags); + + ret = icssm_prueth_port_offload_fwd_mark_update(prueth); + + spin_lock_irqsave(&emac->addr_lock, flags); + + if (!prueth->br_members) + prueth->hw_bridge_dev = NULL; + + spin_unlock_irqrestore(&emac->addr_lock, flags); + + return ret; +} + +static int icssm_prueth_ndev_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 (!icssm_prueth_sw_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 = icssm_prueth_ndev_port_link + (ndev, info->upper_dev); + else + ret = icssm_prueth_ndev_port_unlink(ndev); + } + break; + default: + return NOTIFY_DONE; + } + + return notifier_from_errno(ret); +} + +static int icssm_prueth_register_notifiers(struct prueth *prueth) +{ + int ret = 0; + + prueth->prueth_netdevice_nb.notifier_call = icssm_prueth_ndev_event; + ret = register_netdevice_notifier(&prueth->prueth_netdevice_nb); + if (ret) { + dev_err(prueth->dev, + "register netdevice notifier failed ret: %d\n", ret); + return ret; + } + + ret = icssm_prueth_sw_register_notifiers(prueth); + if (ret) + unregister_netdevice_notifier(&prueth->prueth_netdevice_nb); + + return ret; +} + static int icssm_prueth_probe(struct platform_device *pdev) { struct device_node *eth0_node = NULL, *eth1_node = NULL; @@ -1529,6 +2007,12 @@ static int icssm_prueth_probe(struct platform_device *pdev) prueth->emac[PRUETH_MAC1]->ndev; } + ret = icssm_prueth_register_notifiers(prueth); + if (ret) { + dev_err(dev, "can't register switchdev notifiers"); + goto netdev_unregister; + } + dev_info(dev, "TI PRU ethernet driver initialized: %s EMAC mode\n", (!eth0_node || !eth1_node) ? "single" : "dual"); @@ -1589,6 +2073,9 @@ static void icssm_prueth_remove(struct platform_device *pdev) struct device_node *eth_node; int i; + unregister_netdevice_notifier(&prueth->prueth_netdevice_nb); + icssm_prueth_sw_unregister_notifiers(prueth); + for (i = 0; i < PRUETH_NUM_MACS; i++) { if (!prueth->registered_netdevs[i]) continue; @@ -1688,11 +2175,16 @@ static struct prueth_private_data am335x_prueth_pdata = { .fw_pru[PRUSS_PRU0] = { .fw_name[PRUSS_ETHTYPE_EMAC] = "ti-pruss/am335x-pru0-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_SWITCH] = + "ti-pruss/am335x-pru0-prusw-fw.elf", }, .fw_pru[PRUSS_PRU1] = { .fw_name[PRUSS_ETHTYPE_EMAC] = "ti-pruss/am335x-pru1-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_SWITCH] = + "ti-pruss/am335x-pru1-prusw-fw.elf", }, + .support_switch = true, }; /* AM437x SoC-specific firmware data */ @@ -1701,11 +2193,16 @@ static struct prueth_private_data am437x_prueth_pdata = { .fw_pru[PRUSS_PRU0] = { .fw_name[PRUSS_ETHTYPE_EMAC] = "ti-pruss/am437x-pru0-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_SWITCH] = + "ti-pruss/am437x-pru0-prusw-fw.elf", }, .fw_pru[PRUSS_PRU1] = { .fw_name[PRUSS_ETHTYPE_EMAC] = "ti-pruss/am437x-pru1-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_SWITCH] = + "ti-pruss/am437x-pru1-prusw-fw.elf", }, + .support_switch = true, }; /* AM57xx SoC-specific firmware data */ @@ -1714,11 +2211,17 @@ static struct prueth_private_data am57xx_prueth_pdata = { .fw_pru[PRUSS_PRU0] = { .fw_name[PRUSS_ETHTYPE_EMAC] = "ti-pruss/am57xx-pru0-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_SWITCH] = + "ti-pruss/am57xx-pru0-prusw-fw.elf", }, .fw_pru[PRUSS_PRU1] = { .fw_name[PRUSS_ETHTYPE_EMAC] = "ti-pruss/am57xx-pru1-prueth-fw.elf", + .fw_name[PRUSS_ETHTYPE_SWITCH] = + "ti-pruss/am57xx-pru1-prusw-fw.elf", + }, + .support_switch = true, }; static const struct of_device_id prueth_dt_match[] = { diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.h b/drivers/net/ethernet/ti/icssm/icssm_prueth.h index 8e7e0af08144..d5b49b462c24 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_prueth.h +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth.h @@ -15,6 +15,7 @@ #include "icssm_switch.h" #include "icssm_prueth_ptp.h" +#include "icssm_prueth_fdb_tbl.h" /* ICSSM size of redundancy tag */ #define ICSSM_LRE_TAG_SIZE 6 @@ -181,10 +182,12 @@ enum pruss_device { * struct prueth_private_data - PRU Ethernet private data * @driver_data: PRU Ethernet device name * @fw_pru: firmware names to be used for PRUSS ethernet usecases + * @support_switch: boolean to indicate if switch is enabled */ struct prueth_private_data { enum pruss_device driver_data; const struct prueth_firmware fw_pru[PRUSS_NUM_PRUS]; + bool support_switch; }; struct prueth_emac_stats { @@ -221,15 +224,18 @@ struct prueth_emac { const char *phy_id; u32 msg_enable; u8 mac_addr[6]; + unsigned char mc_filter_mask[ETH_ALEN]; /* for multicast filtering */ phy_interface_t phy_if; /* spin lock used to protect * during link configuration */ spinlock_t lock; + spinlock_t addr_lock; /* serialize access to VLAN/MC filter table */ struct hrtimer tx_hrtimer; struct prueth_emac_stats stats; + int offload_fwd_mark; }; struct prueth { @@ -248,15 +254,27 @@ struct prueth { struct prueth_emac *emac[PRUETH_NUM_MACS]; struct net_device *registered_netdevs[PRUETH_NUM_MACS]; + struct net_device *hw_bridge_dev; + struct fdb_tbl *fdb_tbl; + + struct notifier_block prueth_netdevice_nb; + struct notifier_block prueth_switchdev_nb; + struct notifier_block prueth_switchdev_bl_nb; + unsigned int eth_type; size_t ocmc_ram_size; u8 emac_configured; + u8 br_members; }; +extern const struct prueth_queue_desc queue_descs[][NUM_QUEUES]; + void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor, struct prueth_packet_info *pkt_info); int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr, struct prueth_packet_info *pkt_info, const struct prueth_queue_info *rxqueue); - +void icssm_emac_mc_filter_bin_allow(struct prueth_emac *emac, u8 hash); +void icssm_emac_mc_filter_bin_disallow(struct prueth_emac *emac, u8 hash); +u8 icssm_emac_get_mc_hash(u8 *mac, u8 *mask); #endif /* __NET_TI_PRUETH_H */ diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_fdb_tbl.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_fdb_tbl.h new file mode 100644 index 000000000000..9089259d96ea --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_fdb_tbl.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2019-2021 Texas Instruments Incorporated - https://www.ti.com */ +#ifndef __NET_TI_PRUSS_FDB_TBL_H +#define __NET_TI_PRUSS_FDB_TBL_H + +#include <linux/kernel.h> +#include <linux/debugfs.h> +#include "icssm_prueth.h" + +/* 4 bytes */ +struct fdb_index_tbl_entry { + /* Bucket Table index of first Bucket with this MAC address */ + u16 bucket_idx; + u16 bucket_entries; /* Number of entries in this bucket */ +}; + +/* 4 * 256 = 1024 = 0x200 bytes */ +struct fdb_index_array { + struct fdb_index_tbl_entry index_tbl_entry[FDB_INDEX_TBL_MAX_ENTRIES]; +}; + +/* 10 bytes */ +struct fdb_mac_tbl_entry { + u8 mac[ETH_ALEN]; + u16 age; + u8 port; /* 0 based: 0=port1, 1=port2 */ + union { + struct { + u8 is_static:1; + u8 active:1; + }; + u8 flags; + }; +}; + +/* 10 * 256 = 2560 = 0xa00 bytes */ +struct fdb_mac_tbl_array { + struct fdb_mac_tbl_entry mac_tbl_entry[FDB_MAC_TBL_MAX_ENTRIES]; +}; + +/* 1 byte */ +struct fdb_stp_config { + u8 state; /* per-port STP state (defined in FW header) */ +}; + +/* 1 byte */ +struct fdb_flood_config { + u8 host_flood_enable:1; + u8 port1_flood_enable:1; + u8 port2_flood_enable:1; +}; + +/* 2 byte */ +struct fdb_arbitration { + u8 host_lock; + u8 pru_locks; +}; + +struct fdb_tbl { + /* fdb index table */ + struct fdb_index_array __iomem *index_a; + /* fdb MAC table */ + struct fdb_mac_tbl_array __iomem *mac_tbl_a; + /* port 1 stp config */ + struct fdb_stp_config __iomem *port1_stp_cfg; + /* port 2 stp config */ + struct fdb_stp_config __iomem *port2_stp_cfg; + /* per-port flood enable */ + struct fdb_flood_config __iomem *flood_enable_flags; + /* fdb locking mechanism */ + struct fdb_arbitration __iomem *locks; + /* total number of entries in hash table */ + u16 total_entries; +}; + +#endif diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c new file mode 100644 index 000000000000..07c08564386e --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.c @@ -0,0 +1,1065 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Texas Instruments PRUETH Switch Driver + * + * Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com + */ +#include <linux/etherdevice.h> +#include <linux/kernel.h> +#include <linux/remoteproc.h> +#include <net/switchdev.h> +#include "icssm_prueth.h" +#include "icssm_prueth_switch.h" +#include "icssm_prueth_fdb_tbl.h" + +#define FDB_IDX_TBL_ENTRY(n) (&prueth->fdb_tbl->index_a->index_tbl_entry[n]) + +#define FDB_MAC_TBL_ENTRY(n) (&prueth->fdb_tbl->mac_tbl_a->mac_tbl_entry[n]) + +#define FLAG_IS_STATIC BIT(0) +#define FLAG_ACTIVE BIT(1) + +#define FDB_LEARN 1 +#define FDB_PURGE 2 + +struct icssm_prueth_sw_fdb_work { + netdevice_tracker ndev_tracker; + struct work_struct work; + struct prueth_emac *emac; + u8 addr[ETH_ALEN]; + int event; +}; + +const struct prueth_queue_info sw_queue_infos[][NUM_QUEUES] = { + [PRUETH_PORT_QUEUE_HOST] = { + [PRUETH_QUEUE1] = { + P0_Q1_BUFFER_OFFSET, + P0_QUEUE_DESC_OFFSET, + P0_Q1_BD_OFFSET, + P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE2] = { + P0_Q2_BUFFER_OFFSET, + P0_QUEUE_DESC_OFFSET + 8, + P0_Q2_BD_OFFSET, + P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE3] = { + P0_Q3_BUFFER_OFFSET, + P0_QUEUE_DESC_OFFSET + 16, + P0_Q3_BD_OFFSET, + P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE4] = { + P0_Q4_BUFFER_OFFSET, + P0_QUEUE_DESC_OFFSET + 24, + P0_Q4_BD_OFFSET, + P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE), + }, + }, + [PRUETH_PORT_QUEUE_MII0] = { + [PRUETH_QUEUE1] = { + P1_Q1_BUFFER_OFFSET, + P1_Q1_BUFFER_OFFSET + + ((QUEUE_1_SIZE - 1) * ICSS_BLOCK_SIZE), + P1_Q1_BD_OFFSET, + P1_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE2] = { + P1_Q2_BUFFER_OFFSET, + P1_Q2_BUFFER_OFFSET + + ((QUEUE_2_SIZE - 1) * ICSS_BLOCK_SIZE), + P1_Q2_BD_OFFSET, + P1_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE3] = { + P1_Q3_BUFFER_OFFSET, + P1_Q3_BUFFER_OFFSET + + ((QUEUE_3_SIZE - 1) * ICSS_BLOCK_SIZE), + P1_Q3_BD_OFFSET, + P1_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE4] = { + P1_Q4_BUFFER_OFFSET, + P1_Q4_BUFFER_OFFSET + + ((QUEUE_4_SIZE - 1) * ICSS_BLOCK_SIZE), + P1_Q4_BD_OFFSET, + P1_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE), + }, + }, + [PRUETH_PORT_QUEUE_MII1] = { + [PRUETH_QUEUE1] = { + P2_Q1_BUFFER_OFFSET, + P2_Q1_BUFFER_OFFSET + + ((QUEUE_1_SIZE - 1) * ICSS_BLOCK_SIZE), + P2_Q1_BD_OFFSET, + P2_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE2] = { + P2_Q2_BUFFER_OFFSET, + P2_Q2_BUFFER_OFFSET + + ((QUEUE_2_SIZE - 1) * ICSS_BLOCK_SIZE), + P2_Q2_BD_OFFSET, + P2_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE3] = { + P2_Q3_BUFFER_OFFSET, + P2_Q3_BUFFER_OFFSET + + ((QUEUE_3_SIZE - 1) * ICSS_BLOCK_SIZE), + P2_Q3_BD_OFFSET, + P2_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE4] = { + P2_Q4_BUFFER_OFFSET, + P2_Q4_BUFFER_OFFSET + + ((QUEUE_4_SIZE - 1) * ICSS_BLOCK_SIZE), + P2_Q4_BD_OFFSET, + P2_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE), + }, + }, +}; + +static const struct prueth_queue_info rx_queue_infos[][NUM_QUEUES] = { + [PRUETH_PORT_QUEUE_HOST] = { + [PRUETH_QUEUE1] = { + P0_Q1_BUFFER_OFFSET, + HOST_QUEUE_DESC_OFFSET, + P0_Q1_BD_OFFSET, + P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE2] = { + P0_Q2_BUFFER_OFFSET, + HOST_QUEUE_DESC_OFFSET + 8, + P0_Q2_BD_OFFSET, + P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE3] = { + P0_Q3_BUFFER_OFFSET, + HOST_QUEUE_DESC_OFFSET + 16, + P0_Q3_BD_OFFSET, + P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE4] = { + P0_Q4_BUFFER_OFFSET, + HOST_QUEUE_DESC_OFFSET + 24, + P0_Q4_BD_OFFSET, + P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE), + }, + }, + [PRUETH_PORT_QUEUE_MII0] = { + [PRUETH_QUEUE1] = { + P1_Q1_BUFFER_OFFSET, + P1_QUEUE_DESC_OFFSET, + P1_Q1_BD_OFFSET, + P1_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE2] = { + P1_Q2_BUFFER_OFFSET, + P1_QUEUE_DESC_OFFSET + 8, + P1_Q2_BD_OFFSET, + P1_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE3] = { + P1_Q3_BUFFER_OFFSET, + P1_QUEUE_DESC_OFFSET + 16, + P1_Q3_BD_OFFSET, + P1_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE4] = { + P1_Q4_BUFFER_OFFSET, + P1_QUEUE_DESC_OFFSET + 24, + P1_Q4_BD_OFFSET, + P1_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE), + }, + }, + [PRUETH_PORT_QUEUE_MII1] = { + [PRUETH_QUEUE1] = { + P2_Q1_BUFFER_OFFSET, + P2_QUEUE_DESC_OFFSET, + P2_Q1_BD_OFFSET, + P2_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE2] = { + P2_Q2_BUFFER_OFFSET, + P2_QUEUE_DESC_OFFSET + 8, + P2_Q2_BD_OFFSET, + P2_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE3] = { + P2_Q3_BUFFER_OFFSET, + P2_QUEUE_DESC_OFFSET + 16, + P2_Q3_BD_OFFSET, + P2_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE), + }, + [PRUETH_QUEUE4] = { + P2_Q4_BUFFER_OFFSET, + P2_QUEUE_DESC_OFFSET + 24, + P2_Q4_BD_OFFSET, + P2_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE), + }, + }, +}; + +void icssm_prueth_sw_free_fdb_table(struct prueth *prueth) +{ + if (prueth->emac_configured) + return; + + kfree(prueth->fdb_tbl); + prueth->fdb_tbl = NULL; +} + +void icssm_prueth_sw_fdb_tbl_init(struct prueth *prueth) +{ + struct fdb_tbl *t = prueth->fdb_tbl; + void __iomem *sram_base; + u8 val; + + sram_base = prueth->mem[PRUETH_MEM_SHARED_RAM].va; + + t->index_a = sram_base + V2_1_FDB_TBL_OFFSET; + t->mac_tbl_a = sram_base + FDB_MAC_TBL_OFFSET; + t->port1_stp_cfg = sram_base + FDB_PORT1_STP_CFG_OFFSET; + t->port2_stp_cfg = sram_base + FDB_PORT2_STP_CFG_OFFSET; + t->flood_enable_flags = sram_base + FDB_FLOOD_ENABLE_FLAGS_OFFSET; + t->locks = sram_base + FDB_LOCKS_OFFSET; + + val = readb(t->flood_enable_flags); + /* host_flood_enable = 1 */ + val |= BIT(0); + /* port1_flood_enable = 1 */ + val |= BIT(1); + /* port2_flood_enable = 1 */ + val |= BIT(2); + writeb(val, t->flood_enable_flags); + + writeb(0, &t->locks->host_lock); + t->total_entries = 0; +} + +static u8 icssm_pru_lock_done(struct fdb_tbl *fdb_tbl) +{ + return readb(&fdb_tbl->locks->pru_locks); +} + +static int icssm_prueth_sw_fdb_spin_lock(struct fdb_tbl *fdb_tbl) +{ + u8 done; + int ret; + + /* Take the host lock */ + writeb(1, &fdb_tbl->locks->host_lock); + + /* Wait for the PRUs to release their locks */ + ret = read_poll_timeout(icssm_pru_lock_done, done, done == 0, + 1, 10, false, fdb_tbl); + if (ret == -ETIMEDOUT) + writeb(0, &fdb_tbl->locks->host_lock); + + return ret; +} + +static void icssm_prueth_sw_fdb_spin_unlock(struct fdb_tbl *fdb_tbl) +{ + writeb(0, &fdb_tbl->locks->host_lock); +} + +static u8 icssm_prueth_sw_fdb_hash(const u8 *mac) +{ + return (mac[0] ^ mac[1] ^ mac[2] ^ mac[3] ^ mac[4] ^ mac[5]); +} + +static int +icssm_prueth_sw_fdb_search(struct fdb_mac_tbl_array __iomem *mac_tbl, + struct fdb_index_tbl_entry __iomem *bucket_info, + const u8 *mac) +{ + unsigned int bucket_entries, mac_tbl_idx; + u8 tmp_mac[ETH_ALEN]; + int i; + + mac_tbl_idx = readw(&bucket_info->bucket_idx); + bucket_entries = readw(&bucket_info->bucket_entries); + for (i = 0; i < bucket_entries; i++, mac_tbl_idx++) { + memcpy_fromio(tmp_mac, mac_tbl->mac_tbl_entry[mac_tbl_idx].mac, + ETH_ALEN); + if (ether_addr_equal(mac, tmp_mac)) + return mac_tbl_idx; + } + + return -ENODATA; +} + +static int icssm_prueth_sw_fdb_find_open_slot(struct fdb_tbl *fdb_tbl) +{ + unsigned int i; + u8 flags; + + for (i = 0; i < FDB_MAC_TBL_MAX_ENTRIES; i++) { + flags = readb(&fdb_tbl->mac_tbl_a->mac_tbl_entry[i].flags); + if (!(flags & FLAG_ACTIVE)) + break; + } + + return i; +} + +static int +icssm_prueth_sw_find_fdb_insert(struct fdb_tbl *fdb, struct prueth *prueth, + struct fdb_index_tbl_entry __iomem *bkt_info, + const u8 *mac, const u8 port) +{ + struct fdb_mac_tbl_array __iomem *mac_tbl = fdb->mac_tbl_a; + unsigned int bucket_entries, mac_tbl_idx; + struct fdb_mac_tbl_entry __iomem *e; + u8 mac_from_hw[ETH_ALEN]; + s8 cmp; + int i; + + mac_tbl_idx = readw(&bkt_info->bucket_idx); + bucket_entries = readw(&bkt_info->bucket_entries); + + for (i = 0; i < bucket_entries; i++, mac_tbl_idx++) { + e = &mac_tbl->mac_tbl_entry[mac_tbl_idx]; + memcpy_fromio(mac_from_hw, e->mac, ETH_ALEN); + cmp = memcmp(mac, mac_from_hw, ETH_ALEN); + if (cmp < 0) { + return mac_tbl_idx; + } else if (cmp == 0) { + if (readb(&e->port) != port) { + /* MAC is already in FDB, only port is + * different. So just update the port. + * Note: total_entries and bucket_entries + * remain the same. + */ + writeb(port, &e->port); + } + + /* MAC and port are the same, touch the fdb */ + writew(0, &e->age); + return -EEXIST; + } + } + + return mac_tbl_idx; +} + +static int +icssm_prueth_sw_fdb_empty_slot_left(struct fdb_mac_tbl_array __iomem *mac_tbl, + unsigned int mac_tbl_idx) +{ + u8 flags; + int i; + + for (i = mac_tbl_idx - 1; i > -1; i--) { + flags = readb(&mac_tbl->mac_tbl_entry[i].flags); + if (!(flags & FLAG_ACTIVE)) + break; + } + + return i; +} + +static int +icssm_prueth_sw_fdb_empty_slot_right(struct fdb_mac_tbl_array __iomem *mac_tbl, + unsigned int mac_tbl_idx) +{ + u8 flags; + int i; + + for (i = mac_tbl_idx; i < FDB_MAC_TBL_MAX_ENTRIES; i++) { + flags = readb(&mac_tbl->mac_tbl_entry[i].flags); + if (!(flags & FLAG_ACTIVE)) + return i; + } + + return -1; +} + +static void icssm_prueth_sw_fdb_move_range_left(struct prueth *prueth, + u16 left, u16 right) +{ + struct fdb_mac_tbl_entry entry; + u32 sz = 0; + u16 i; + + sz = sizeof(struct fdb_mac_tbl_entry); + for (i = left; i < right; i++) { + memcpy_fromio(&entry, FDB_MAC_TBL_ENTRY(i + 1), sz); + memcpy_toio(FDB_MAC_TBL_ENTRY(i), &entry, sz); + } +} + +static void icssm_prueth_sw_fdb_move_range_right(struct prueth *prueth, + u16 left, u16 right) +{ + struct fdb_mac_tbl_entry entry; + u32 sz = 0; + u16 i; + + sz = sizeof(struct fdb_mac_tbl_entry); + for (i = right; i > left; i--) { + memcpy_fromio(&entry, FDB_MAC_TBL_ENTRY(i - 1), sz); + memcpy_toio(FDB_MAC_TBL_ENTRY(i), &entry, sz); + } +} + +static void icssm_prueth_sw_fdb_update_index_tbl(struct prueth *prueth, + u16 left, u16 right) +{ + unsigned int hash, hash_prev; + u8 mac[ETH_ALEN]; + unsigned int i; + + /* To ensure we don't improperly update the + * bucket index, initialize with an invalid + * hash in case we are in leftmost slot + */ + hash_prev = 0xff; + + if (left > 0) { + memcpy_fromio(mac, FDB_MAC_TBL_ENTRY(left - 1)->mac, ETH_ALEN); + hash_prev = icssm_prueth_sw_fdb_hash(mac); + } + + /* For each moved element, update the bucket index */ + for (i = left; i <= right; i++) { + memcpy_fromio(mac, FDB_MAC_TBL_ENTRY(i)->mac, ETH_ALEN); + hash = icssm_prueth_sw_fdb_hash(mac); + + /* Only need to update buckets once */ + if (hash != hash_prev) + writew(i, &FDB_IDX_TBL_ENTRY(hash)->bucket_idx); + + hash_prev = hash; + } +} + +static struct fdb_mac_tbl_entry __iomem * +icssm_prueth_sw_find_free_mac(struct prueth *prueth, struct fdb_index_tbl_entry + __iomem *bucket_info, u8 suggested_mac_tbl_idx, + bool *update_indexes, const u8 *mac) +{ + s16 empty_slot_idx = 0, left = 0, right = 0; + unsigned int mti = suggested_mac_tbl_idx; + struct fdb_mac_tbl_array __iomem *mt; + struct fdb_tbl *fdb; + u8 flags; + + fdb = prueth->fdb_tbl; + mt = fdb->mac_tbl_a; + + flags = readb(&FDB_MAC_TBL_ENTRY(mti)->flags); + if (!(flags & FLAG_ACTIVE)) { + /* Claim the entry */ + flags |= FLAG_ACTIVE; + writeb(flags, &FDB_MAC_TBL_ENTRY(mti)->flags); + + return FDB_MAC_TBL_ENTRY(mti); + } + + if (fdb->total_entries == FDB_MAC_TBL_MAX_ENTRIES) + return NULL; + + empty_slot_idx = icssm_prueth_sw_fdb_empty_slot_left(mt, mti); + if (empty_slot_idx == -1) { + /* Nothing available on the left. But table isn't full + * so there must be space to the right, + */ + empty_slot_idx = icssm_prueth_sw_fdb_empty_slot_right(mt, mti); + + /* Shift right */ + left = mti; + right = empty_slot_idx; + icssm_prueth_sw_fdb_move_range_right(prueth, left, right); + + /* Claim the entry */ + flags = readb(&FDB_MAC_TBL_ENTRY(mti)->flags); + flags |= FLAG_ACTIVE; + writeb(flags, &FDB_MAC_TBL_ENTRY(mti)->flags); + + memcpy_toio(FDB_MAC_TBL_ENTRY(mti)->mac, mac, ETH_ALEN); + + /* There is a chance we moved something in a + * different bucket, update index table + */ + icssm_prueth_sw_fdb_update_index_tbl(prueth, left, right); + + return FDB_MAC_TBL_ENTRY(mti); + } + + if (empty_slot_idx == mti - 1) { + /* There is space immediately left of the open slot, + * which means the inserted MAC address + * must be the lowest-valued MAC address in bucket. + * Update bucket pointer accordingly. + */ + writew(empty_slot_idx, &bucket_info->bucket_idx); + + /* Claim the entry */ + flags = readb(&FDB_MAC_TBL_ENTRY(empty_slot_idx)->flags); + flags |= FLAG_ACTIVE; + writeb(flags, &FDB_MAC_TBL_ENTRY(empty_slot_idx)->flags); + + return FDB_MAC_TBL_ENTRY(empty_slot_idx); + } + + /* There is empty space to the left, shift MAC table entries left */ + left = empty_slot_idx; + right = mti - 1; + icssm_prueth_sw_fdb_move_range_left(prueth, left, right); + + /* Claim the entry */ + flags = readb(&FDB_MAC_TBL_ENTRY(mti - 1)->flags); + flags |= FLAG_ACTIVE; + writeb(flags, &FDB_MAC_TBL_ENTRY(mti - 1)->flags); + + memcpy_toio(FDB_MAC_TBL_ENTRY(mti - 1)->mac, mac, ETH_ALEN); + + /* There is a chance we moved something in a + * different bucket, update index table + */ + icssm_prueth_sw_fdb_update_index_tbl(prueth, left, right); + + return FDB_MAC_TBL_ENTRY(mti - 1); +} + +static int icssm_prueth_sw_insert_fdb_entry(struct prueth_emac *emac, + const u8 *mac, u8 is_static) +{ + struct fdb_index_tbl_entry __iomem *bucket_info; + struct fdb_mac_tbl_entry __iomem *mac_info; + struct prueth *prueth = emac->prueth; + unsigned int hash_val, mac_tbl_idx; + struct prueth_emac *other_emac; + enum prueth_port other_port_id; + int total_fdb_entries; + struct fdb_tbl *fdb; + u8 flags; + s16 ret; + int err; + u16 val; + + fdb = prueth->fdb_tbl; + other_port_id = (emac->port_id == PRUETH_PORT_MII0) ? + PRUETH_PORT_MII1 : PRUETH_PORT_MII0; + + other_emac = prueth->emac[other_port_id - 1]; + if (!other_emac) + return -EINVAL; + + err = icssm_prueth_sw_fdb_spin_lock(fdb); + if (err) { + dev_err(prueth->dev, "PRU lock timeout %d\n", err); + return err; + } + + if (fdb->total_entries == FDB_MAC_TBL_MAX_ENTRIES) { + icssm_prueth_sw_fdb_spin_unlock(fdb); + return -ENOMEM; + } + + if (ether_addr_equal(mac, emac->mac_addr) || + (ether_addr_equal(mac, other_emac->mac_addr))) { + icssm_prueth_sw_fdb_spin_unlock(fdb); + /* Don't insert fdb of own mac addr */ + return -EINVAL; + } + + /* Get the bucket that the mac belongs to */ + hash_val = icssm_prueth_sw_fdb_hash(mac); + bucket_info = FDB_IDX_TBL_ENTRY(hash_val); + + if (!readw(&bucket_info->bucket_entries)) { + mac_tbl_idx = icssm_prueth_sw_fdb_find_open_slot(fdb); + writew(mac_tbl_idx, &bucket_info->bucket_idx); + } + + ret = icssm_prueth_sw_find_fdb_insert(fdb, prueth, bucket_info, mac, + emac->port_id - 1); + if (ret < 0) { + icssm_prueth_sw_fdb_spin_unlock(fdb); + /* mac is already in fdb table */ + return 0; + } + + mac_tbl_idx = ret; + + mac_info = icssm_prueth_sw_find_free_mac(prueth, bucket_info, + mac_tbl_idx, NULL, + mac); + if (!mac_info) { + /* Should not happen */ + dev_warn(prueth->dev, "OUT of FDB MEM\n"); + icssm_prueth_sw_fdb_spin_unlock(fdb); + return -ENOMEM; + } + + memcpy_toio(mac_info->mac, mac, ETH_ALEN); + writew(0, &mac_info->age); + writeb(emac->port_id - 1, &mac_info->port); + + flags = readb(&mac_info->flags); + if (is_static) + flags |= FLAG_IS_STATIC; + else + flags &= ~FLAG_IS_STATIC; + + /* bit 1 - active */ + flags |= FLAG_ACTIVE; + writeb(flags, &mac_info->flags); + + val = readw(&bucket_info->bucket_entries); + val++; + writew(val, &bucket_info->bucket_entries); + + fdb->total_entries++; + + total_fdb_entries = fdb->total_entries; + + icssm_prueth_sw_fdb_spin_unlock(fdb); + + dev_dbg(prueth->dev, "added fdb: %pM port=%d total_entries=%u\n", + mac, emac->port_id, total_fdb_entries); + + return 0; +} + +static int icssm_prueth_sw_delete_fdb_entry(struct prueth_emac *emac, + const u8 *mac, u8 is_static) +{ + struct fdb_index_tbl_entry __iomem *bucket_info; + struct fdb_mac_tbl_entry __iomem *mac_info; + struct fdb_mac_tbl_array __iomem *mt; + unsigned int hash_val, mac_tbl_idx; + unsigned int idx, entries; + struct prueth *prueth; + int total_fdb_entries; + s16 ret, left, right; + struct fdb_tbl *fdb; + u8 flags; + int err; + u16 val; + + prueth = emac->prueth; + fdb = prueth->fdb_tbl; + mt = fdb->mac_tbl_a; + + err = icssm_prueth_sw_fdb_spin_lock(fdb); + if (err) { + dev_err(prueth->dev, "PRU lock timeout %d\n", err); + return err; + } + + if (fdb->total_entries == 0) { + icssm_prueth_sw_fdb_spin_unlock(fdb); + return 0; + } + + /* Get the bucket that the mac belongs to */ + hash_val = icssm_prueth_sw_fdb_hash(mac); + bucket_info = FDB_IDX_TBL_ENTRY(hash_val); + + ret = icssm_prueth_sw_fdb_search(mt, bucket_info, mac); + if (ret < 0) { + icssm_prueth_sw_fdb_spin_unlock(fdb); + return ret; + } + + mac_tbl_idx = ret; + mac_info = FDB_MAC_TBL_ENTRY(mac_tbl_idx); + + /* Shift all elements in bucket to the left. No need to + * update index table since only shifting within bucket. + */ + left = mac_tbl_idx; + idx = readw(&bucket_info->bucket_idx); + entries = readw(&bucket_info->bucket_entries); + right = idx + entries - 1; + icssm_prueth_sw_fdb_move_range_left(prueth, left, right); + + /* Remove end of bucket from table */ + mac_info = FDB_MAC_TBL_ENTRY(right); + flags = readb(&mac_info->flags); + /* active = 0 */ + flags &= ~FLAG_ACTIVE; + writeb(flags, &mac_info->flags); + val = readw(&bucket_info->bucket_entries); + val--; + writew(val, &bucket_info->bucket_entries); + fdb->total_entries--; + + total_fdb_entries = fdb->total_entries; + + icssm_prueth_sw_fdb_spin_unlock(fdb); + + dev_dbg(prueth->dev, "del fdb: %pM total_entries=%u\n", + mac, total_fdb_entries); + + return 0; +} + +int icssm_prueth_sw_do_purge_fdb(struct prueth_emac *emac) +{ + struct fdb_index_tbl_entry __iomem *bucket_info; + struct prueth *prueth = emac->prueth; + u8 flags, mac[ETH_ALEN]; + unsigned int hash_val; + struct fdb_tbl *fdb; + int ret, i; + u16 val; + + fdb = prueth->fdb_tbl; + + ret = icssm_prueth_sw_fdb_spin_lock(fdb); + if (ret) { + dev_err(prueth->dev, "PRU lock timeout %d\n", ret); + return ret; + } + + if (fdb->total_entries == 0) { + icssm_prueth_sw_fdb_spin_unlock(fdb); + return 0; + } + + for (i = 0; i < FDB_MAC_TBL_MAX_ENTRIES; i++) { + flags = readb(&fdb->mac_tbl_a->mac_tbl_entry[i].flags); + if ((flags & FLAG_ACTIVE) && !(flags & FLAG_IS_STATIC)) { + /* Get the bucket that the mac belongs to */ + memcpy_fromio(mac, FDB_MAC_TBL_ENTRY(i)->mac, + ETH_ALEN); + hash_val = icssm_prueth_sw_fdb_hash(mac); + bucket_info = FDB_IDX_TBL_ENTRY(hash_val); + flags &= ~FLAG_ACTIVE; + writeb(flags, + &fdb->mac_tbl_a->mac_tbl_entry[i].flags); + val = readw(&bucket_info->bucket_entries); + val--; + writew(val, &bucket_info->bucket_entries); + fdb->total_entries--; + } + } + + icssm_prueth_sw_fdb_spin_unlock(fdb); + return 0; +} + +int icssm_prueth_sw_init_fdb_table(struct prueth *prueth) +{ + if (prueth->emac_configured) + return 0; + + prueth->fdb_tbl = kmalloc(sizeof(*prueth->fdb_tbl), GFP_KERNEL); + if (!prueth->fdb_tbl) + return -ENOMEM; + + icssm_prueth_sw_fdb_tbl_init(prueth); + + return 0; +} + +/** + * icssm_prueth_sw_fdb_add - insert fdb entry + * + * @emac: EMAC data structure + * @fdb: fdb info + * + */ +void icssm_prueth_sw_fdb_add(struct prueth_emac *emac, + struct switchdev_notifier_fdb_info *fdb) +{ + icssm_prueth_sw_insert_fdb_entry(emac, fdb->addr, 1); +} + +/** + * icssm_prueth_sw_fdb_del - delete fdb entry + * + * @emac: EMAC data structure + * @fdb: fdb info + * + */ +void icssm_prueth_sw_fdb_del(struct prueth_emac *emac, + struct switchdev_notifier_fdb_info *fdb) +{ + icssm_prueth_sw_delete_fdb_entry(emac, fdb->addr, 1); +} + +static void icssm_prueth_sw_fdb_work(struct work_struct *work) +{ + struct icssm_prueth_sw_fdb_work *fdb_work = + container_of(work, struct icssm_prueth_sw_fdb_work, work); + struct prueth_emac *emac = fdb_work->emac; + + rtnl_lock(); + + /* Interface is not up */ + if (!emac->prueth->fdb_tbl) + goto free; + + switch (fdb_work->event) { + case FDB_LEARN: + icssm_prueth_sw_insert_fdb_entry(emac, fdb_work->addr, 0); + break; + case FDB_PURGE: + icssm_prueth_sw_do_purge_fdb(emac); + break; + default: + break; + } + +free: + rtnl_unlock(); + netdev_put(emac->ndev, &fdb_work->ndev_tracker); + kfree(fdb_work); +} + +int icssm_prueth_sw_learn_fdb(struct prueth_emac *emac, u8 *src_mac) +{ + struct icssm_prueth_sw_fdb_work *fdb_work; + + fdb_work = kzalloc(sizeof(*fdb_work), GFP_ATOMIC); + if (WARN_ON(!fdb_work)) + return -ENOMEM; + + INIT_WORK(&fdb_work->work, icssm_prueth_sw_fdb_work); + + fdb_work->event = FDB_LEARN; + fdb_work->emac = emac; + ether_addr_copy(fdb_work->addr, src_mac); + + netdev_hold(emac->ndev, &fdb_work->ndev_tracker, GFP_ATOMIC); + queue_work(system_long_wq, &fdb_work->work); + return 0; +} + +int icssm_prueth_sw_purge_fdb(struct prueth_emac *emac) +{ + struct icssm_prueth_sw_fdb_work *fdb_work; + + fdb_work = kzalloc(sizeof(*fdb_work), GFP_ATOMIC); + if (WARN_ON(!fdb_work)) + return -ENOMEM; + + INIT_WORK(&fdb_work->work, icssm_prueth_sw_fdb_work); + + fdb_work->event = FDB_PURGE; + fdb_work->emac = emac; + + netdev_hold(emac->ndev, &fdb_work->ndev_tracker, GFP_ATOMIC); + queue_work(system_long_wq, &fdb_work->work); + return 0; +} + +void icssm_prueth_sw_hostconfig(struct prueth *prueth) +{ + void __iomem *dram1_base = prueth->mem[PRUETH_MEM_DRAM1].va; + void __iomem *dram; + + /* queue information table */ + dram = dram1_base + P0_Q1_RX_CONTEXT_OFFSET; + memcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST], + sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST])); + + /* buffer descriptor offset table*/ + dram = dram1_base + QUEUE_DESCRIPTOR_OFFSET_ADDR; + writew(P0_Q1_BD_OFFSET, dram); + writew(P0_Q2_BD_OFFSET, dram + 2); + writew(P0_Q3_BD_OFFSET, dram + 4); + writew(P0_Q4_BD_OFFSET, dram + 6); + + /* buffer offset table */ + dram = dram1_base + QUEUE_OFFSET_ADDR; + writew(P0_Q1_BUFFER_OFFSET, dram); + writew(P0_Q2_BUFFER_OFFSET, dram + 2); + writew(P0_Q3_BUFFER_OFFSET, dram + 4); + writew(P0_Q4_BUFFER_OFFSET, dram + 6); + + /* queue size lookup table */ + dram = dram1_base + QUEUE_SIZE_ADDR; + writew(HOST_QUEUE_1_SIZE, dram); + writew(HOST_QUEUE_1_SIZE, dram + 2); + writew(HOST_QUEUE_1_SIZE, dram + 4); + writew(HOST_QUEUE_1_SIZE, dram + 6); + + /* queue table */ + dram = dram1_base + P0_QUEUE_DESC_OFFSET; + memcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST], + sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST])); +} + +static int icssm_prueth_sw_port_config(struct prueth *prueth, + enum prueth_port port_id) +{ + unsigned int tx_context_ofs_addr, rx_context_ofs, queue_desc_ofs; + void __iomem *dram, *dram_base, *dram_mac; + struct prueth_emac *emac; + void __iomem *dram1_base; + + dram1_base = prueth->mem[PRUETH_MEM_DRAM1].va; + emac = prueth->emac[port_id - 1]; + switch (port_id) { + case PRUETH_PORT_MII0: + tx_context_ofs_addr = TX_CONTEXT_P1_Q1_OFFSET_ADDR; + rx_context_ofs = P1_Q1_RX_CONTEXT_OFFSET; + queue_desc_ofs = P1_QUEUE_DESC_OFFSET; + + /* for switch PORT MII0 mac addr is in DRAM0. */ + dram_mac = prueth->mem[PRUETH_MEM_DRAM0].va; + break; + case PRUETH_PORT_MII1: + tx_context_ofs_addr = TX_CONTEXT_P2_Q1_OFFSET_ADDR; + rx_context_ofs = P2_Q1_RX_CONTEXT_OFFSET; + queue_desc_ofs = P2_QUEUE_DESC_OFFSET; + + /* for switch PORT MII1 mac addr is in DRAM1. */ + dram_mac = prueth->mem[PRUETH_MEM_DRAM1].va; + break; + default: + netdev_err(emac->ndev, "invalid port\n"); + return -EINVAL; + } + + /* setup mac address */ + memcpy_toio(dram_mac + PORT_MAC_ADDR, emac->mac_addr, 6); + + /* Remaining switch port configs are in DRAM1 */ + dram_base = prueth->mem[PRUETH_MEM_DRAM1].va; + + /* queue information table */ + memcpy_toio(dram_base + tx_context_ofs_addr, + sw_queue_infos[port_id], + sizeof(sw_queue_infos[port_id])); + + memcpy_toio(dram_base + rx_context_ofs, + rx_queue_infos[port_id], + sizeof(rx_queue_infos[port_id])); + + /* buffer descriptor offset table*/ + dram = dram_base + QUEUE_DESCRIPTOR_OFFSET_ADDR + + (port_id * NUM_QUEUES * sizeof(u16)); + writew(sw_queue_infos[port_id][PRUETH_QUEUE1].buffer_desc_offset, dram); + writew(sw_queue_infos[port_id][PRUETH_QUEUE2].buffer_desc_offset, + dram + 2); + writew(sw_queue_infos[port_id][PRUETH_QUEUE3].buffer_desc_offset, + dram + 4); + writew(sw_queue_infos[port_id][PRUETH_QUEUE4].buffer_desc_offset, + dram + 6); + + /* buffer offset table */ + dram = dram_base + QUEUE_OFFSET_ADDR + + port_id * NUM_QUEUES * sizeof(u16); + writew(sw_queue_infos[port_id][PRUETH_QUEUE1].buffer_offset, dram); + writew(sw_queue_infos[port_id][PRUETH_QUEUE2].buffer_offset, + dram + 2); + writew(sw_queue_infos[port_id][PRUETH_QUEUE3].buffer_offset, + dram + 4); + writew(sw_queue_infos[port_id][PRUETH_QUEUE4].buffer_offset, + dram + 6); + + /* queue size lookup table */ + dram = dram_base + QUEUE_SIZE_ADDR + + port_id * NUM_QUEUES * sizeof(u16); + writew(QUEUE_1_SIZE, dram); + writew(QUEUE_2_SIZE, dram + 2); + writew(QUEUE_3_SIZE, dram + 4); + writew(QUEUE_4_SIZE, dram + 6); + + /* queue table */ + memcpy_toio(dram_base + queue_desc_ofs, + &queue_descs[port_id][0], + 4 * sizeof(queue_descs[port_id][0])); + + emac->rx_queue_descs = dram1_base + P0_QUEUE_DESC_OFFSET; + emac->tx_queue_descs = dram1_base + + rx_queue_infos[port_id][PRUETH_QUEUE1].queue_desc_offset; + + return 0; +} + +int icssm_prueth_sw_emac_config(struct prueth_emac *emac) +{ + struct prueth *prueth = emac->prueth; + u32 sharedramaddr, ocmcaddr; + int ret; + + /* PRU needs local shared RAM address for C28 */ + sharedramaddr = ICSS_LOCAL_SHARED_RAM; + /* PRU needs real global OCMC address for C30*/ + ocmcaddr = (u32)prueth->mem[PRUETH_MEM_OCMC].pa; + + if (prueth->emac_configured & BIT(emac->port_id)) + return 0; + + ret = icssm_prueth_sw_port_config(prueth, emac->port_id); + if (ret) + return ret; + + if (!prueth->emac_configured) { + /* Set in constant table C28 of PRUn to ICSS Shared memory */ + pru_rproc_set_ctable(prueth->pru0, PRU_C28, sharedramaddr); + pru_rproc_set_ctable(prueth->pru1, PRU_C28, sharedramaddr); + + /* Set in constant table C30 of PRUn to OCMC memory */ + pru_rproc_set_ctable(prueth->pru0, PRU_C30, ocmcaddr); + pru_rproc_set_ctable(prueth->pru1, PRU_C30, ocmcaddr); + } + return 0; +} + +int icssm_prueth_sw_boot_prus(struct prueth *prueth, struct net_device *ndev) +{ + const struct prueth_firmware *pru_firmwares; + const char *fw_name, *fw_name1; + int ret; + + if (prueth->emac_configured) + return 0; + + pru_firmwares = &prueth->fw_data->fw_pru[PRUSS_PRU0]; + fw_name = pru_firmwares->fw_name[prueth->eth_type]; + pru_firmwares = &prueth->fw_data->fw_pru[PRUSS_PRU1]; + fw_name1 = pru_firmwares->fw_name[prueth->eth_type]; + + ret = rproc_set_firmware(prueth->pru0, fw_name); + if (ret) { + netdev_err(ndev, "failed to set PRU0 firmware %s: %d\n", + fw_name, ret); + return ret; + } + ret = rproc_boot(prueth->pru0); + if (ret) { + netdev_err(ndev, "failed to boot PRU0: %d\n", ret); + return ret; + } + + ret = rproc_set_firmware(prueth->pru1, fw_name1); + if (ret) { + netdev_err(ndev, "failed to set PRU1 firmware %s: %d\n", + fw_name1, ret); + goto rproc0_shutdown; + } + ret = rproc_boot(prueth->pru1); + if (ret) { + netdev_err(ndev, "failed to boot PRU1: %d\n", ret); + goto rproc0_shutdown; + } + + return 0; + +rproc0_shutdown: + rproc_shutdown(prueth->pru0); + return ret; +} + +int icssm_prueth_sw_shutdown_prus(struct prueth_emac *emac, + struct net_device *ndev) +{ + struct prueth *prueth = emac->prueth; + + if (prueth->emac_configured) + return 0; + + rproc_shutdown(prueth->pru0); + rproc_shutdown(prueth->pru1); + + return 0; +} diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h new file mode 100644 index 000000000000..e6111bba166e --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_prueth_switch.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com + */ + +#ifndef __NET_TI_PRUETH_SWITCH_H +#define __NET_TI_PRUETH_SWITCH_H + +#include <net/switchdev.h> + +#include "icssm_prueth.h" +#include "icssm_prueth_fdb_tbl.h" +#include "icssm_switchdev.h" + +void icssm_prueth_sw_set_stp_state(struct prueth *prueth, + enum prueth_port port, u8 state); +u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth, + enum prueth_port port); + +extern const struct prueth_queue_info sw_queue_infos[][4]; + +void icssm_prueth_sw_fdb_tbl_init(struct prueth *prueth); +int icssm_prueth_sw_init_fdb_table(struct prueth *prueth); +void icssm_prueth_sw_free_fdb_table(struct prueth *prueth); +int icssm_prueth_sw_do_purge_fdb(struct prueth_emac *emac); +void icssm_prueth_sw_fdb_add(struct prueth_emac *emac, + struct switchdev_notifier_fdb_info *fdb); +void icssm_prueth_sw_fdb_del(struct prueth_emac *emac, + struct switchdev_notifier_fdb_info *fdb); +int icssm_prueth_sw_learn_fdb(struct prueth_emac *emac, u8 *src_mac); +int icssm_prueth_sw_purge_fdb(struct prueth_emac *emac); +void icssm_prueth_sw_hostconfig(struct prueth *prueth); +int icssm_prueth_sw_emac_config(struct prueth_emac *emac); +int icssm_prueth_sw_boot_prus(struct prueth *prueth, struct net_device *ndev); +int icssm_prueth_sw_shutdown_prus(struct prueth_emac *emac, + struct net_device *ndev); + +#endif /* __NET_TI_PRUETH_SWITCH_H */ diff --git a/drivers/net/ethernet/ti/icssm/icssm_switch.h b/drivers/net/ethernet/ti/icssm/icssm_switch.h index 8b494ffdcde7..5ba9ce14da44 100644 --- a/drivers/net/ethernet/ti/icssm/icssm_switch.h +++ b/drivers/net/ethernet/ti/icssm/icssm_switch.h @@ -117,6 +117,15 @@ #define STATISTICS_OFFSET 0x1F00 #define STAT_SIZE 0x98 +/* The following offsets indicate which sections of the memory are used + * for switch internal tasks + */ +#define SWITCH_SPECIFIC_DRAM0_START_SIZE 0x100 +#define SWITCH_SPECIFIC_DRAM0_START_OFFSET 0x1F00 + +#define SWITCH_SPECIFIC_DRAM1_START_SIZE 0x300 +#define SWITCH_SPECIFIC_DRAM1_START_OFFSET 0x1D00 + /* Offset for storing * 1. Storm Prevention Params * 2. PHY Speed Offset @@ -146,6 +155,74 @@ /* 4 bytes ? */ #define STP_INVALID_STATE_OFFSET (STATISTICS_OFFSET + STAT_SIZE + 33) +/* DRAM1 Offsets for Switch */ +/* 4 queue descriptors for port 0 (host receive) */ +#define P0_QUEUE_DESC_OFFSET 0x1E7C +#define P1_QUEUE_DESC_OFFSET 0x1E9C +#define P2_QUEUE_DESC_OFFSET 0x1EBC +/* collision descriptor of port 0 */ +#define P0_COL_QUEUE_DESC_OFFSET 0x1E64 +#define P1_COL_QUEUE_DESC_OFFSET 0x1E6C +#define P2_COL_QUEUE_DESC_OFFSET 0x1E74 +/* Collision Status Register + * P0: bit 0 is pending flag, bit 1..2 indicates which queue, + * P1: bit 8 is pending flag, 9..10 is queue number + * P2: bit 16 is pending flag, 17..18 is queue number, remaining bits are 0. + */ +#define COLLISION_STATUS_ADDR 0x1E60 + +#define INTERFACE_MAC_ADDR 0x1E58 +#define P2_MAC_ADDR 0x1E50 +#define P1_MAC_ADDR 0x1E48 + +#define QUEUE_SIZE_ADDR 0x1E30 +#define QUEUE_OFFSET_ADDR 0x1E18 +#define QUEUE_DESCRIPTOR_OFFSET_ADDR 0x1E00 + +#define COL_RX_CONTEXT_P2_OFFSET_ADDR (COL_RX_CONTEXT_P1_OFFSET_ADDR + 12) +#define COL_RX_CONTEXT_P1_OFFSET_ADDR (COL_RX_CONTEXT_P0_OFFSET_ADDR + 12) +#define COL_RX_CONTEXT_P0_OFFSET_ADDR (P2_Q4_RX_CONTEXT_OFFSET + 8) + +/* Port 2 Rx Context */ +#define P2_Q4_RX_CONTEXT_OFFSET (P2_Q3_RX_CONTEXT_OFFSET + 8) +#define P2_Q3_RX_CONTEXT_OFFSET (P2_Q2_RX_CONTEXT_OFFSET + 8) +#define P2_Q2_RX_CONTEXT_OFFSET (P2_Q1_RX_CONTEXT_OFFSET + 8) +#define P2_Q1_RX_CONTEXT_OFFSET RX_CONTEXT_P2_Q1_OFFSET_ADDR +#define RX_CONTEXT_P2_Q1_OFFSET_ADDR (P1_Q4_RX_CONTEXT_OFFSET + 8) + +/* Port 1 Rx Context */ +#define P1_Q4_RX_CONTEXT_OFFSET (P1_Q3_RX_CONTEXT_OFFSET + 8) +#define P1_Q3_RX_CONTEXT_OFFSET (P1_Q2_RX_CONTEXT_OFFSET + 8) +#define P1_Q2_RX_CONTEXT_OFFSET (P1_Q1_RX_CONTEXT_OFFSET + 8) +#define P1_Q1_RX_CONTEXT_OFFSET (RX_CONTEXT_P1_Q1_OFFSET_ADDR) +#define RX_CONTEXT_P1_Q1_OFFSET_ADDR (P0_Q4_RX_CONTEXT_OFFSET + 8) + +/* Host Port Rx Context */ +#define P0_Q4_RX_CONTEXT_OFFSET (P0_Q3_RX_CONTEXT_OFFSET + 8) +#define P0_Q3_RX_CONTEXT_OFFSET (P0_Q2_RX_CONTEXT_OFFSET + 8) +#define P0_Q2_RX_CONTEXT_OFFSET (P0_Q1_RX_CONTEXT_OFFSET + 8) +#define P0_Q1_RX_CONTEXT_OFFSET RX_CONTEXT_P0_Q1_OFFSET_ADDR +#define RX_CONTEXT_P0_Q1_OFFSET_ADDR (COL_TX_CONTEXT_P2_Q1_OFFSET_ADDR + 8) + +/* Port 2 Tx Collision Context */ +#define COL_TX_CONTEXT_P2_Q1_OFFSET_ADDR (COL_TX_CONTEXT_P1_Q1_OFFSET_ADDR + 8) +/* Port 1 Tx Collision Context */ +#define COL_TX_CONTEXT_P1_Q1_OFFSET_ADDR (P2_Q4_TX_CONTEXT_OFFSET + 8) + +/* Port 2 */ +#define P2_Q4_TX_CONTEXT_OFFSET (P2_Q3_TX_CONTEXT_OFFSET + 8) +#define P2_Q3_TX_CONTEXT_OFFSET (P2_Q2_TX_CONTEXT_OFFSET + 8) +#define P2_Q2_TX_CONTEXT_OFFSET (P2_Q1_TX_CONTEXT_OFFSET + 8) +#define P2_Q1_TX_CONTEXT_OFFSET TX_CONTEXT_P2_Q1_OFFSET_ADDR +#define TX_CONTEXT_P2_Q1_OFFSET_ADDR (P1_Q4_TX_CONTEXT_OFFSET + 8) + +/* Port 1 */ +#define P1_Q4_TX_CONTEXT_OFFSET (P1_Q3_TX_CONTEXT_OFFSET + 8) +#define P1_Q3_TX_CONTEXT_OFFSET (P1_Q2_TX_CONTEXT_OFFSET + 8) +#define P1_Q2_TX_CONTEXT_OFFSET (P1_Q1_TX_CONTEXT_OFFSET + 8) +#define P1_Q1_TX_CONTEXT_OFFSET TX_CONTEXT_P1_Q1_OFFSET_ADDR +#define TX_CONTEXT_P1_Q1_OFFSET_ADDR SWITCH_SPECIFIC_DRAM1_START_OFFSET + /* DRAM Offsets for EMAC * Present on Both DRAM0 and DRAM1 */ @@ -254,4 +331,30 @@ #define P0_COL_BUFFER_OFFSET 0xEE00 #define P0_Q1_BUFFER_OFFSET 0x0000 +#define V2_1_FDB_TBL_LOC PRUETH_MEM_SHARED_RAM +#define V2_1_FDB_TBL_OFFSET 0x2000 + +#define FDB_INDEX_TBL_MAX_ENTRIES 256 +#define FDB_MAC_TBL_MAX_ENTRIES 256 + +#define FDB_INDEX_TBL_OFFSET V2_1_FDB_TBL_OFFSET +#define FDB_INDEX_TBL_SIZE (FDB_INDEX_TBL_MAX_ENTRIES * \ + sizeof(struct fdb_index_tbl_entry)) + +#define FDB_MAC_TBL_OFFSET (FDB_INDEX_TBL_OFFSET + FDB_INDEX_TBL_SIZE) +#define FDB_MAC_TBL_SIZE (FDB_MAC_TBL_MAX_ENTRIES * \ + sizeof(struct fdb_mac_tbl_entry)) + +#define FDB_PORT1_STP_CFG_OFFSET (FDB_MAC_TBL_OFFSET + FDB_MAC_TBL_SIZE) +#define FDB_PORT_STP_CFG_SIZE sizeof(struct fdb_stp_config) +#define FDB_PORT2_STP_CFG_OFFSET (FDB_PORT1_STP_CFG_OFFSET + \ + FDB_PORT_STP_CFG_SIZE) + +#define FDB_FLOOD_ENABLE_FLAGS_OFFSET (FDB_PORT2_STP_CFG_OFFSET + \ + FDB_PORT_STP_CFG_SIZE) +#define FDB_FLOOD_ENABLE_FLAGS_SIZE sizeof(struct fdb_flood_config) + +#define FDB_LOCKS_OFFSET (FDB_FLOOD_ENABLE_FLAGS_OFFSET + \ + FDB_FLOOD_ENABLE_FLAGS_SIZE) + #endif /* __ICSS_SWITCH_H */ diff --git a/drivers/net/ethernet/ti/icssm/icssm_switchdev.c b/drivers/net/ethernet/ti/icssm/icssm_switchdev.c new file mode 100644 index 000000000000..414ec9fc02a0 --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_switchdev.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Texas Instruments ICSSM Ethernet Driver + * + * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/ + * + */ + +#include <linux/etherdevice.h> +#include <linux/kernel.h> +#include <linux/remoteproc.h> +#include <net/switchdev.h> + +#include "icssm_prueth.h" +#include "icssm_prueth_switch.h" +#include "icssm_prueth_fdb_tbl.h" + +/* switchev event work */ +struct icssm_sw_event_work { + netdevice_tracker ndev_tracker; + struct work_struct work; + struct switchdev_notifier_fdb_info fdb_info; + struct prueth_emac *emac; + unsigned long event; +}; + +void icssm_prueth_sw_set_stp_state(struct prueth *prueth, + enum prueth_port port, u8 state) +{ + struct fdb_tbl *t = prueth->fdb_tbl; + + writeb(state, port - 1 ? (void __iomem *)&t->port2_stp_cfg->state : + (void __iomem *)&t->port1_stp_cfg->state); +} + +u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth, enum prueth_port port) +{ + struct fdb_tbl *t = prueth->fdb_tbl; + u8 state; + + state = readb(port - 1 ? (void __iomem *)&t->port2_stp_cfg->state : + (void __iomem *)&t->port1_stp_cfg->state); + return state; +} + +static int icssm_prueth_sw_attr_set(struct net_device *ndev, const void *ctx, + const struct switchdev_attr *attr, + struct netlink_ext_ack *extack) +{ + struct prueth_emac *emac = netdev_priv(ndev); + struct prueth *prueth = emac->prueth; + int err = 0; + u8 o_state; + + /* Interface is not up */ + if (!prueth->fdb_tbl) + return 0; + + switch (attr->id) { + case SWITCHDEV_ATTR_ID_PORT_STP_STATE: + o_state = icssm_prueth_sw_get_stp_state(prueth, emac->port_id); + icssm_prueth_sw_set_stp_state(prueth, emac->port_id, + attr->u.stp_state); + + if (o_state != attr->u.stp_state) + icssm_prueth_sw_purge_fdb(emac); + + dev_dbg(prueth->dev, "attr set: stp state:%u port:%u\n", + attr->u.stp_state, emac->port_id); + break; + default: + err = -EOPNOTSUPP; + break; + } + + return err; +} + +static void icssm_prueth_sw_fdb_offload(struct net_device *ndev, + struct switchdev_notifier_fdb_info *rcv) +{ + struct switchdev_notifier_fdb_info info; + + info.addr = rcv->addr; + info.vid = rcv->vid; + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, ndev, &info.info, + NULL); +} + +/** + * icssm_sw_event_work - insert/delete fdb entry + * + * @work: work structure + * + */ +static void icssm_sw_event_work(struct work_struct *work) +{ + struct icssm_sw_event_work *switchdev_work = + container_of(work, struct icssm_sw_event_work, work); + struct prueth_emac *emac = switchdev_work->emac; + struct switchdev_notifier_fdb_info *fdb; + struct prueth *prueth = emac->prueth; + int port = emac->port_id; + + rtnl_lock(); + + /* Interface is not up */ + if (!emac->prueth->fdb_tbl) + goto free; + + switch (switchdev_work->event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: + fdb = &switchdev_work->fdb_info; + dev_dbg(prueth->dev, + "prueth fdb add: MACID = %pM vid = %u flags = %u -- port %d\n", + fdb->addr, fdb->vid, fdb->added_by_user, port); + + if (!fdb->added_by_user) + break; + + if (fdb->is_local) + break; + + icssm_prueth_sw_fdb_add(emac, fdb); + icssm_prueth_sw_fdb_offload(emac->ndev, fdb); + break; + case SWITCHDEV_FDB_DEL_TO_DEVICE: + fdb = &switchdev_work->fdb_info; + dev_dbg(prueth->dev, + "prueth fdb del: MACID = %pM vid = %u flags = %u -- port %d\n", + fdb->addr, fdb->vid, fdb->added_by_user, port); + + if (fdb->is_local) + break; + + icssm_prueth_sw_fdb_del(emac, fdb); + break; + default: + break; + } + +free: + rtnl_unlock(); + + netdev_put(emac->ndev, &switchdev_work->ndev_tracker); + kfree(switchdev_work->fdb_info.addr); + kfree(switchdev_work); +} + +/* called under rcu_read_lock() */ +static int icssm_prueth_sw_switchdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *ndev = switchdev_notifier_info_to_dev(ptr); + struct switchdev_notifier_fdb_info *fdb_info = ptr; + struct prueth_emac *emac = netdev_priv(ndev); + struct icssm_sw_event_work *switchdev_work; + int err; + + if (!icssm_prueth_sw_port_dev_check(ndev)) + return NOTIFY_DONE; + + if (event == SWITCHDEV_PORT_ATTR_SET) { + err = switchdev_handle_port_attr_set + (ndev, ptr, icssm_prueth_sw_port_dev_check, + icssm_prueth_sw_attr_set); + return notifier_from_errno(err); + } + + switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC); + if (WARN_ON(!switchdev_work)) + return NOTIFY_BAD; + + INIT_WORK(&switchdev_work->work, icssm_sw_event_work); + switchdev_work->emac = emac; + 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); + netdev_hold(ndev, &switchdev_work->ndev_tracker, GFP_ATOMIC); + break; + default: + kfree(switchdev_work); + return NOTIFY_DONE; + } + + queue_work(system_long_wq, &switchdev_work->work); + + return NOTIFY_DONE; + +err_addr_alloc: + kfree(switchdev_work); + return NOTIFY_BAD; +} + +static int icssm_prueth_switchdev_obj_add(struct net_device *ndev, + const void *ctx, + const struct switchdev_obj *obj, + struct netlink_ext_ack *extack) +{ + struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + struct prueth_emac *emac = netdev_priv(ndev); + struct prueth *prueth = emac->prueth; + int ret = 0; + u8 hash; + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_HOST_MDB: + dev_dbg(prueth->dev, "MDB add: %s: vid %u:%pM port: %x\n", + ndev->name, mdb->vid, mdb->addr, emac->port_id); + hash = icssm_emac_get_mc_hash(mdb->addr, emac->mc_filter_mask); + icssm_emac_mc_filter_bin_allow(emac, hash); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static int icssm_prueth_switchdev_obj_del(struct net_device *ndev, + const void *ctx, + const struct switchdev_obj *obj) +{ + struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj); + struct prueth_emac *emac = netdev_priv(ndev); + struct prueth *prueth = emac->prueth; + struct netdev_hw_addr *ha; + u8 hash, tmp_hash; + int ret = 0; + u8 *mask; + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_HOST_MDB: + dev_dbg(prueth->dev, "MDB del: %s: vid %u:%pM port: %x\n", + ndev->name, mdb->vid, mdb->addr, emac->port_id); + if (prueth->hw_bridge_dev) { + mask = emac->mc_filter_mask; + hash = icssm_emac_get_mc_hash(mdb->addr, mask); + netdev_for_each_mc_addr(ha, prueth->hw_bridge_dev) { + tmp_hash = icssm_emac_get_mc_hash(ha->addr, + mask); + /* Another MC address is in the bin. + * Don't disable. + */ + if (tmp_hash == hash) + return 0; + } + icssm_emac_mc_filter_bin_disallow(emac, hash); + } + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +/* switchdev notifiers */ +static int icssm_prueth_sw_blocking_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *ndev = switchdev_notifier_info_to_dev(ptr); + int err; + + switch (event) { + case SWITCHDEV_PORT_OBJ_ADD: + err = switchdev_handle_port_obj_add + (ndev, ptr, icssm_prueth_sw_port_dev_check, + icssm_prueth_switchdev_obj_add); + return notifier_from_errno(err); + + case SWITCHDEV_PORT_OBJ_DEL: + err = switchdev_handle_port_obj_del + (ndev, ptr, icssm_prueth_sw_port_dev_check, + icssm_prueth_switchdev_obj_del); + return notifier_from_errno(err); + + case SWITCHDEV_PORT_ATTR_SET: + err = switchdev_handle_port_attr_set + (ndev, ptr, icssm_prueth_sw_port_dev_check, + icssm_prueth_sw_attr_set); + return notifier_from_errno(err); + + default: + break; + } + + return NOTIFY_DONE; +} + +int icssm_prueth_sw_register_notifiers(struct prueth *prueth) +{ + int ret = 0; + + prueth->prueth_switchdev_nb.notifier_call = + &icssm_prueth_sw_switchdev_event; + ret = register_switchdev_notifier(&prueth->prueth_switchdev_nb); + if (ret) { + dev_err(prueth->dev, + "register switchdev notifier failed ret:%d\n", ret); + return ret; + } + + prueth->prueth_switchdev_bl_nb.notifier_call = + &icssm_prueth_sw_blocking_event; + ret = register_switchdev_blocking_notifier + (&prueth->prueth_switchdev_bl_nb); + if (ret) { + dev_err(prueth->dev, + "register switchdev blocking notifier failed ret:%d\n", + ret); + unregister_switchdev_notifier(&prueth->prueth_switchdev_nb); + } + + return ret; +} + +void icssm_prueth_sw_unregister_notifiers(struct prueth *prueth) +{ + unregister_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb); + unregister_switchdev_notifier(&prueth->prueth_switchdev_nb); +} diff --git a/drivers/net/ethernet/ti/icssm/icssm_switchdev.h b/drivers/net/ethernet/ti/icssm/icssm_switchdev.h new file mode 100644 index 000000000000..b03a98e3472e --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_switchdev.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com + */ + +#ifndef __NET_TI_ICSSM_SWITCHDEV_H +#define __NET_TI_ICSSM_SWITCHDEV_H + +#include "icssm_prueth.h" + +int icssm_prueth_sw_register_notifiers(struct prueth *prueth); +void icssm_prueth_sw_unregister_notifiers(struct prueth *prueth); +bool icssm_prueth_sw_port_dev_check(const struct net_device *ndev); +#endif /* __NET_TI_ICSSM_SWITCHDEV_H */ diff --git a/drivers/net/ethernet/ti/icssm/icssm_vlan_mcast_filter_mmap.h b/drivers/net/ethernet/ti/icssm/icssm_vlan_mcast_filter_mmap.h new file mode 100644 index 000000000000..c177c19a36ef --- /dev/null +++ b/drivers/net/ethernet/ti/icssm/icssm_vlan_mcast_filter_mmap.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Copyright (C) 2015-2021 Texas Instruments Incorporated - https://www.ti.com + * + * This file contains VLAN/Multicast filtering feature memory map + * + */ + +#ifndef ICSS_VLAN_MULTICAST_FILTER_MM_H +#define ICSS_VLAN_MULTICAST_FILTER_MM_H + +/* VLAN/Multicast filter defines & offsets, + * present on both PRU0 and PRU1 DRAM + */ + +/* Feature enable/disable values for multicast filtering */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_DISABLED 0x00 +#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_ENABLED 0x01 + +/* Feature enable/disable values for VLAN filtering */ +#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_DISABLED 0x00 +#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_ENABLED 0x01 + +/* Add/remove multicast mac id for filtering bin */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_HOST_RCV_ALLOWED 0x01 +#define ICSS_EMAC_FW_MULTICAST_FILTER_HOST_RCV_NOT_ALLOWED 0x00 + +/* Default HASH value for the multicast filtering Mask */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_INIT_VAL 0xFF + +/* Size requirements for Multicast filtering feature */ +#define ICSS_EMAC_FW_MULTICAST_TABLE_SIZE_BYTES 256 +#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES 6 +#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_SIZE_BYTES 1 +#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_STATUS_SIZE_BYTES 1 +#define ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_SIZE_BYTES 4 + +/* Size requirements for VLAN filtering feature : 4096 bits = 512 bytes */ +#define ICSS_EMAC_FW_VLAN_FILTER_TABLE_SIZE_BYTES 512 +#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_SIZE_BYTES 1 +#define ICSS_EMAC_FW_VLAN_FILTER_DROP_CNT_SIZE_BYTES 4 + +/* Mask override set status */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_SET 1 +/* Mask override not set status */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_NOT_SET 0 +/* 6 bytes HASH Mask for the MAC */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET 0xF4 +/* 0 -> multicast filtering disabled | 1 -> multicast filtering enabled */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET \ + (ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET + \ + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES) +/* Status indicating if the HASH override is done or not: 0: no, 1: yes */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_OVERRIDE_STATUS \ + (ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET + \ + ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_SIZE_BYTES) +/* Multicast drop statistics */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_OFFSET \ + (ICSS_EMAC_FW_MULTICAST_FILTER_OVERRIDE_STATUS +\ + ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_STATUS_SIZE_BYTES) +/* Multicast table */ +#define ICSS_EMAC_FW_MULTICAST_FILTER_TABLE \ + (ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_OFFSET +\ + ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_SIZE_BYTES) + +/* Multicast filter defines & offsets for LRE + */ +#define ICSS_LRE_FW_MULTICAST_TABLE_SEARCH_OP_CONTROL_BIT 0xE0 +/* one byte field : + * 0 -> multicast filtering disabled + * 1 -> multicast filtering enabled + */ +#define ICSS_LRE_FW_MULTICAST_FILTER_MASK 0xE4 +#define ICSS_LRE_FW_MULTICAST_FILTER_TABLE 0x100 + +/* VLAN table Offsets */ +#define ICSS_EMAC_FW_VLAN_FLTR_TBL_BASE_ADDR 0x200 +#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_BITMAP_OFFSET 0xEF +#define ICSS_EMAC_FW_VLAN_FILTER_DROP_CNT_OFFSET \ + (ICSS_EMAC_FW_VLAN_FILTER_CTRL_BITMAP_OFFSET + \ + ICSS_EMAC_FW_VLAN_FILTER_CTRL_SIZE_BYTES) + +/* VLAN filter Control Bit maps */ +/* one bit field, bit 0: | 0 : VLAN filter disabled (default), + * 1: VLAN filter enabled + */ +#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_ENABLE_BIT 0 +/* one bit field, bit 1: | 0 : untagged host rcv allowed (default), + * 1: untagged host rcv not allowed + */ +#define ICSS_EMAC_FW_VLAN_FILTER_UNTAG_HOST_RCV_ALLOW_CTRL_BIT 1 +/* one bit field, bit 1: | 0 : priotag host rcv allowed (default), + * 1: priotag host rcv not allowed + */ +#define ICSS_EMAC_FW_VLAN_FILTER_PRIOTAG_HOST_RCV_ALLOW_CTRL_BIT 2 +/* one bit field, bit 1: | 0 : skip sv vlan flow + * :1 : take sv vlan flow (not applicable for dual emac ) + */ +#define ICSS_EMAC_FW_VLAN_FILTER_SV_VLAN_FLOW_HOST_RCV_ALLOW_CTRL_BIT 3 + +/* VLAN IDs */ +#define ICSS_EMAC_FW_VLAN_FILTER_PRIOTAG_VID 0 +#define ICSS_EMAC_FW_VLAN_FILTER_VID_MIN 0x0000 +#define ICSS_EMAC_FW_VLAN_FILTER_VID_MAX 0x0FFF + +/* VLAN Filtering Commands */ +#define ICSS_EMAC_FW_VLAN_FILTER_ADD_VLAN_VID_CMD 0x00 +#define ICSS_EMAC_FW_VLAN_FILTER_REMOVE_VLAN_VID_CMD 0x01 + +/* Switch defines for VLAN/MC filtering */ +/* SRAM + * VLAN filter defines & offsets + */ +#define ICSS_LRE_FW_VLAN_FLTR_CTRL_BYTE 0x1FE +/* one bit field | 0 : VLAN filter disabled + * | 1 : VLAN filter enabled + */ +#define ICSS_LRE_FW_VLAN_FLTR_TBL_BASE_ADDR 0x200 + +#endif /* ICSS_MULTICAST_FILTER_MM_H */ diff --git a/drivers/net/ethernet/ti/netcp.h b/drivers/net/ethernet/ti/netcp.h index b9cbd3b4a8a2..9cfddaa807e2 100644 --- a/drivers/net/ethernet/ti/netcp.h +++ b/drivers/net/ethernet/ti/netcp.h @@ -65,14 +65,14 @@ struct netcp_addr { struct netcp_stats { struct u64_stats_sync syncp_rx ____cacheline_aligned_in_smp; - u64 rx_packets; - u64 rx_bytes; + u64_stats_t rx_packets; + u64_stats_t rx_bytes; u32 rx_errors; u32 rx_dropped; struct u64_stats_sync syncp_tx ____cacheline_aligned_in_smp; - u64 tx_packets; - u64 tx_bytes; + u64_stats_t tx_packets; + u64_stats_t tx_bytes; u32 tx_errors; u32 tx_dropped; }; diff --git a/drivers/net/ethernet/ti/netcp_core.c b/drivers/net/ethernet/ti/netcp_core.c index 5ed1c46bbcb1..eb8fc2ed05f4 100644 --- a/drivers/net/ethernet/ti/netcp_core.c +++ b/drivers/net/ethernet/ti/netcp_core.c @@ -759,8 +759,8 @@ static int netcp_process_one_rx_packet(struct netcp_intf *netcp) knav_pool_desc_put(netcp->rx_pool, desc); u64_stats_update_begin(&rx_stats->syncp_rx); - rx_stats->rx_packets++; - rx_stats->rx_bytes += skb->len; + u64_stats_inc(&rx_stats->rx_packets); + u64_stats_add(&rx_stats->rx_bytes, skb->len); u64_stats_update_end(&rx_stats->syncp_rx); /* push skb up the stack */ @@ -1045,8 +1045,8 @@ static int netcp_process_tx_compl_packets(struct netcp_intf *netcp, } u64_stats_update_begin(&tx_stats->syncp_tx); - tx_stats->tx_packets++; - tx_stats->tx_bytes += skb->len; + u64_stats_inc(&tx_stats->tx_packets); + u64_stats_add(&tx_stats->tx_bytes, skb->len); u64_stats_update_end(&tx_stats->syncp_tx); dev_kfree_skb(skb); pkts++; @@ -1973,14 +1973,14 @@ netcp_get_stats(struct net_device *ndev, struct rtnl_link_stats64 *stats) do { start = u64_stats_fetch_begin(&p->syncp_rx); - rxpackets = p->rx_packets; - rxbytes = p->rx_bytes; + rxpackets = u64_stats_read(&p->rx_packets); + rxbytes = u64_stats_read(&p->rx_bytes); } while (u64_stats_fetch_retry(&p->syncp_rx, start)); do { start = u64_stats_fetch_begin(&p->syncp_tx); - txpackets = p->tx_packets; - txbytes = p->tx_bytes; + txpackets = u64_stats_read(&p->tx_packets); + txbytes = u64_stats_read(&p->tx_bytes); } while (u64_stats_fetch_retry(&p->syncp_tx, start)); stats->rx_packets = rxpackets; |
