| // SPDX-License-Identifier: GPL-2.0+ |
| /* Microchip Sparx5 Switch driver |
| * |
| * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. |
| */ |
| |
| #include "sparx5_main_regs.h" |
| #include "sparx5_main.h" |
| |
| #define XTR_EOF_0 ntohl((__force __be32)0x80000000u) |
| #define XTR_EOF_1 ntohl((__force __be32)0x80000001u) |
| #define XTR_EOF_2 ntohl((__force __be32)0x80000002u) |
| #define XTR_EOF_3 ntohl((__force __be32)0x80000003u) |
| #define XTR_PRUNED ntohl((__force __be32)0x80000004u) |
| #define XTR_ABORT ntohl((__force __be32)0x80000005u) |
| #define XTR_ESCAPE ntohl((__force __be32)0x80000006u) |
| #define XTR_NOT_READY ntohl((__force __be32)0x80000007u) |
| |
| #define XTR_VALID_BYTES(x) (4 - ((x) & 3)) |
| |
| #define INJ_TIMEOUT_NS 50000 |
| |
| void sparx5_xtr_flush(struct sparx5 *sparx5, u8 grp) |
| { |
| /* Start flush */ |
| spx5_wr(QS_XTR_FLUSH_FLUSH_SET(BIT(grp)), sparx5, QS_XTR_FLUSH); |
| |
| /* Allow to drain */ |
| mdelay(1); |
| |
| /* All Queues normal */ |
| spx5_wr(0, sparx5, QS_XTR_FLUSH); |
| } |
| |
| void sparx5_ifh_parse(u32 *ifh, struct frame_info *info) |
| { |
| u8 *xtr_hdr = (u8 *)ifh; |
| |
| /* FWD is bit 45-72 (28 bits), but we only read the 27 LSB for now */ |
| u32 fwd = |
| ((u32)xtr_hdr[27] << 24) | |
| ((u32)xtr_hdr[28] << 16) | |
| ((u32)xtr_hdr[29] << 8) | |
| ((u32)xtr_hdr[30] << 0); |
| fwd = (fwd >> 5); |
| info->src_port = FIELD_GET(GENMASK(7, 1), fwd); |
| } |
| |
| static void sparx5_xtr_grp(struct sparx5 *sparx5, u8 grp, bool byte_swap) |
| { |
| bool eof_flag = false, pruned_flag = false, abort_flag = false; |
| struct net_device *netdev; |
| struct sparx5_port *port; |
| struct frame_info fi; |
| int i, byte_cnt = 0; |
| struct sk_buff *skb; |
| u32 ifh[IFH_LEN]; |
| u32 *rxbuf; |
| |
| /* Get IFH */ |
| for (i = 0; i < IFH_LEN; i++) |
| ifh[i] = spx5_rd(sparx5, QS_XTR_RD(grp)); |
| |
| /* Decode IFH (whats needed) */ |
| sparx5_ifh_parse(ifh, &fi); |
| |
| /* Map to port netdev */ |
| port = fi.src_port < SPX5_PORTS ? |
| sparx5->ports[fi.src_port] : NULL; |
| if (!port || !port->ndev) { |
| dev_err(sparx5->dev, "Data on inactive port %d\n", fi.src_port); |
| sparx5_xtr_flush(sparx5, grp); |
| return; |
| } |
| |
| /* Have netdev, get skb */ |
| netdev = port->ndev; |
| skb = netdev_alloc_skb(netdev, netdev->mtu + ETH_HLEN); |
| if (!skb) { |
| sparx5_xtr_flush(sparx5, grp); |
| dev_err(sparx5->dev, "No skb allocated\n"); |
| netdev->stats.rx_dropped++; |
| return; |
| } |
| rxbuf = (u32 *)skb->data; |
| |
| /* Now, pull frame data */ |
| while (!eof_flag) { |
| u32 val = spx5_rd(sparx5, QS_XTR_RD(grp)); |
| u32 cmp = val; |
| |
| if (byte_swap) |
| cmp = ntohl((__force __be32)val); |
| |
| switch (cmp) { |
| case XTR_NOT_READY: |
| break; |
| case XTR_ABORT: |
| /* No accompanying data */ |
| abort_flag = true; |
| eof_flag = true; |
| break; |
| case XTR_EOF_0: |
| case XTR_EOF_1: |
| case XTR_EOF_2: |
| case XTR_EOF_3: |
| /* This assumes STATUS_WORD_POS == 1, Status |
| * just after last data |
| */ |
| byte_cnt -= (4 - XTR_VALID_BYTES(val)); |
| eof_flag = true; |
| break; |
| case XTR_PRUNED: |
| /* But get the last 4 bytes as well */ |
| eof_flag = true; |
| pruned_flag = true; |
| fallthrough; |
| case XTR_ESCAPE: |
| *rxbuf = spx5_rd(sparx5, QS_XTR_RD(grp)); |
| byte_cnt += 4; |
| rxbuf++; |
| break; |
| default: |
| *rxbuf = val; |
| byte_cnt += 4; |
| rxbuf++; |
| } |
| } |
| |
| if (abort_flag || pruned_flag || !eof_flag) { |
| netdev_err(netdev, "Discarded frame: abort:%d pruned:%d eof:%d\n", |
| abort_flag, pruned_flag, eof_flag); |
| kfree_skb(skb); |
| netdev->stats.rx_dropped++; |
| return; |
| } |
| |
| /* Everything we see on an interface that is in the HW bridge |
| * has already been forwarded |
| */ |
| if (test_bit(port->portno, sparx5->bridge_mask)) |
| skb->offload_fwd_mark = 1; |
| |
| /* Finish up skb */ |
| skb_put(skb, byte_cnt - ETH_FCS_LEN); |
| eth_skb_pad(skb); |
| skb->protocol = eth_type_trans(skb, netdev); |
| netif_rx(skb); |
| netdev->stats.rx_bytes += skb->len; |
| netdev->stats.rx_packets++; |
| } |
| |
| static int sparx5_inject(struct sparx5 *sparx5, |
| u32 *ifh, |
| struct sk_buff *skb, |
| struct net_device *ndev) |
| { |
| int grp = INJ_QUEUE; |
| u32 val, w, count; |
| u8 *buf; |
| |
| val = spx5_rd(sparx5, QS_INJ_STATUS); |
| if (!(QS_INJ_STATUS_FIFO_RDY_GET(val) & BIT(grp))) { |
| pr_err_ratelimited("Injection: Queue not ready: 0x%lx\n", |
| QS_INJ_STATUS_FIFO_RDY_GET(val)); |
| return -EBUSY; |
| } |
| |
| /* Indicate SOF */ |
| spx5_wr(QS_INJ_CTRL_SOF_SET(1) | |
| QS_INJ_CTRL_GAP_SIZE_SET(1), |
| sparx5, QS_INJ_CTRL(grp)); |
| |
| /* Write the IFH to the chip. */ |
| for (w = 0; w < IFH_LEN; w++) |
| spx5_wr(ifh[w], sparx5, QS_INJ_WR(grp)); |
| |
| /* Write words, round up */ |
| count = DIV_ROUND_UP(skb->len, 4); |
| buf = skb->data; |
| for (w = 0; w < count; w++, buf += 4) { |
| val = get_unaligned((const u32 *)buf); |
| spx5_wr(val, sparx5, QS_INJ_WR(grp)); |
| } |
| |
| /* Add padding */ |
| while (w < (60 / 4)) { |
| spx5_wr(0, sparx5, QS_INJ_WR(grp)); |
| w++; |
| } |
| |
| /* Indicate EOF and valid bytes in last word */ |
| spx5_wr(QS_INJ_CTRL_GAP_SIZE_SET(1) | |
| QS_INJ_CTRL_VLD_BYTES_SET(skb->len < 60 ? 0 : skb->len % 4) | |
| QS_INJ_CTRL_EOF_SET(1), |
| sparx5, QS_INJ_CTRL(grp)); |
| |
| /* Add dummy CRC */ |
| spx5_wr(0, sparx5, QS_INJ_WR(grp)); |
| w++; |
| |
| val = spx5_rd(sparx5, QS_INJ_STATUS); |
| if (QS_INJ_STATUS_WMARK_REACHED_GET(val) & BIT(grp)) { |
| struct sparx5_port *port = netdev_priv(ndev); |
| |
| pr_err_ratelimited("Injection: Watermark reached: 0x%lx\n", |
| QS_INJ_STATUS_WMARK_REACHED_GET(val)); |
| netif_stop_queue(ndev); |
| hrtimer_start(&port->inj_timer, INJ_TIMEOUT_NS, |
| HRTIMER_MODE_REL); |
| } |
| |
| return NETDEV_TX_OK; |
| } |
| |
| int sparx5_port_xmit_impl(struct sk_buff *skb, struct net_device *dev) |
| { |
| struct net_device_stats *stats = &dev->stats; |
| struct sparx5_port *port = netdev_priv(dev); |
| struct sparx5 *sparx5 = port->sparx5; |
| int ret; |
| |
| if (sparx5->fdma_irq > 0) |
| ret = sparx5_fdma_xmit(sparx5, port->ifh, skb); |
| else |
| ret = sparx5_inject(sparx5, port->ifh, skb, dev); |
| |
| if (ret == NETDEV_TX_OK) { |
| stats->tx_bytes += skb->len; |
| stats->tx_packets++; |
| skb_tx_timestamp(skb); |
| dev_kfree_skb_any(skb); |
| } else { |
| stats->tx_dropped++; |
| } |
| return ret; |
| } |
| |
| static enum hrtimer_restart sparx5_injection_timeout(struct hrtimer *tmr) |
| { |
| struct sparx5_port *port = container_of(tmr, struct sparx5_port, |
| inj_timer); |
| int grp = INJ_QUEUE; |
| u32 val; |
| |
| val = spx5_rd(port->sparx5, QS_INJ_STATUS); |
| if (QS_INJ_STATUS_WMARK_REACHED_GET(val) & BIT(grp)) { |
| pr_err_ratelimited("Injection: Reset watermark count\n"); |
| /* Reset Watermark count to restart */ |
| spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR_SET(1), |
| DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR, |
| port->sparx5, |
| DSM_DEV_TX_STOP_WM_CFG(port->portno)); |
| } |
| netif_wake_queue(port->ndev); |
| return HRTIMER_NORESTART; |
| } |
| |
| int sparx5_manual_injection_mode(struct sparx5 *sparx5) |
| { |
| const int byte_swap = 1; |
| int portno; |
| |
| /* Change mode to manual extraction and injection */ |
| spx5_wr(QS_XTR_GRP_CFG_MODE_SET(1) | |
| QS_XTR_GRP_CFG_STATUS_WORD_POS_SET(1) | |
| QS_XTR_GRP_CFG_BYTE_SWAP_SET(byte_swap), |
| sparx5, QS_XTR_GRP_CFG(XTR_QUEUE)); |
| spx5_wr(QS_INJ_GRP_CFG_MODE_SET(1) | |
| QS_INJ_GRP_CFG_BYTE_SWAP_SET(byte_swap), |
| sparx5, QS_INJ_GRP_CFG(INJ_QUEUE)); |
| |
| /* CPU ports capture setup */ |
| for (portno = SPX5_PORT_CPU_0; portno <= SPX5_PORT_CPU_1; portno++) { |
| /* ASM CPU port: No preamble, IFH, enable padding */ |
| spx5_wr(ASM_PORT_CFG_PAD_ENA_SET(1) | |
| ASM_PORT_CFG_NO_PREAMBLE_ENA_SET(1) | |
| ASM_PORT_CFG_INJ_FORMAT_CFG_SET(1), /* 1 = IFH */ |
| sparx5, ASM_PORT_CFG(portno)); |
| |
| /* Reset WM cnt to unclog queued frames */ |
| spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR_SET(1), |
| DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR, |
| sparx5, |
| DSM_DEV_TX_STOP_WM_CFG(portno)); |
| |
| /* Set Disassembler Stop Watermark level */ |
| spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(0), |
| DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM, |
| sparx5, |
| DSM_DEV_TX_STOP_WM_CFG(portno)); |
| |
| /* Enable Disassembler buffer underrun watchdog |
| */ |
| spx5_rmw(DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS_SET(0), |
| DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS, |
| sparx5, |
| DSM_BUF_CFG(portno)); |
| } |
| return 0; |
| } |
| |
| irqreturn_t sparx5_xtr_handler(int irq, void *_sparx5) |
| { |
| struct sparx5 *s5 = _sparx5; |
| int poll = 64; |
| |
| /* Check data in queue */ |
| while (spx5_rd(s5, QS_XTR_DATA_PRESENT) & BIT(XTR_QUEUE) && poll-- > 0) |
| sparx5_xtr_grp(s5, XTR_QUEUE, false); |
| |
| return IRQ_HANDLED; |
| } |
| |
| void sparx5_port_inj_timer_setup(struct sparx5_port *port) |
| { |
| hrtimer_init(&port->inj_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| port->inj_timer.function = sparx5_injection_timeout; |
| } |