// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include #include #include #include "cpunet.h" #define CPUNET_TX_QUEUE_LEN 50 struct cpunet { struct napi_struct napi; struct timer_list retry_rx; struct cpunet_hw *hw; struct net_device *dev; }; static void timer_retry_rx(unsigned long data) { struct cpunet *cpunet = (void *)data; napi_schedule(&cpunet->napi); } static void retry_rx_later(struct cpunet *cpunet) { mod_timer(&cpunet->retry_rx, jiffies + msecs_to_jiffies(100)); } static int cpunet_poll(struct napi_struct *napi, int budget) { struct cpunet *cpunet = container_of(napi, struct cpunet, napi); int i; if (!cpunet_hw_tx_full(cpunet->hw)) netif_wake_queue(cpunet->dev); for (i = 0; i < budget; ++i) { const void *data; unsigned int len; struct sk_buff *skb; /* Do not remove from the queue -> just peek */ if (cpunet_hw_rx(cpunet->hw, &data, &len)) break; skb = napi_alloc_skb(napi, len); /* Out of memory -> block rx queue */ if (unlikely(!skb)) { retry_rx_later(cpunet); break; } /* Revert the alignment (we do not have an ethernet header) */ skb->data -= NET_IP_ALIGN; skb->tail -= NET_IP_ALIGN; /* Copy done -> make room in rx queue */ memcpy(skb_put(skb, len), data, len); cpunet_hw_rx_done(cpunet->hw); /* We have no mac header and therefore and only allow IPv6 */ skb->protocol = ETH_P_IPV6; skb_reset_mac_header(skb); /* we ignore checksums */ skb->ip_summed = CHECKSUM_UNNECESSARY; napi_gro_receive(napi, skb); /* No atomic increment necessary only written here */ cpunet->dev->stats.rx_packets++; cpunet->dev->stats.rx_bytes += len; } if (i < budget) { napi_complete(napi); cpunet_hw_enable_int(cpunet->hw); } return i; } static enum irqreturn cpunet_isr(__maybe_unused int irq, void *data) { struct cpunet *cpunet = data; cpunet_hw_disable_int(cpunet->hw); cpunet_hw_ack_int(cpunet->hw); napi_schedule(&cpunet->napi); return IRQ_HANDLED; } static netdev_tx_t cpunet_xmit(struct sk_buff *skb, struct net_device *dev) { struct cpunet *cpunet = netdev_priv(dev); void *mem; mem = cpunet_hw_tx_alloc(cpunet->hw); if (unlikely(!mem)) return NETDEV_TX_BUSY; skb_orphan(skb); skb_copy_bits(skb, 0, mem, (int)skb->len); skb_tx_timestamp(skb); cpunet_hw_tx(cpunet->hw, skb->len); /* No atomic increment necessary only written here */ dev->stats.tx_packets++; dev->stats.tx_bytes += skb->len; consume_skb(skb); if (cpunet_hw_tx_full(cpunet->hw)) netif_stop_queue(dev); return NETDEV_TX_OK; } static const struct net_device_ops cpunet_ops = { .ndo_start_xmit = &cpunet_xmit, }; static void cpunet_setup(struct net_device *dev) { dev->type = ARPHRD_NONE; dev->hard_header_len = 0; dev->addr_len = 0; dev->mtu = cpunet_hw_buffer_size(); dev->tx_queue_len = CPUNET_TX_QUEUE_LEN; dev->flags = IFF_POINTOPOINT | IFF_NOARP; dev->priv_flags = IFF_TX_SKB_SHARING | IFF_DONT_BRIDGE; dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA; /* kernel should not generate checksums */ dev->features |= NETIF_F_HW_CSUM; dev->netdev_ops = &cpunet_ops; } static int cpunet_probe(struct platform_device *pdev) { int err = -ENOMEM; struct cpunet *cpunet; struct net_device *ndev; ndev = alloc_netdev(sizeof(struct cpunet), "cpunet%d", NET_NAME_UNKNOWN, &cpunet_setup); if (!ndev) goto out; SET_NETDEV_DEV(ndev, &pdev->dev); cpunet = netdev_priv(ndev); memset(cpunet, 0, sizeof(*cpunet)); cpunet->dev = ndev; netif_napi_add(ndev, &cpunet->napi, &cpunet_poll, cpunet_hw_max_num()); setup_pinned_timer(&cpunet->retry_rx, timer_retry_rx, (unsigned long)cpunet); err = register_netdev(ndev); if (err) goto netdev_dealloc; cpunet->hw = cpunet_hw_init(&pdev->dev, &cpunet_isr, cpunet); if (!cpunet->hw) { err = -EFAULT; goto netdev_unregister; } cpunet_hw_enable_int(cpunet->hw); napi_enable(&cpunet->napi); netif_start_queue(ndev); napi_schedule(&cpunet->napi); return 0; netdev_unregister: unregister_netdev(ndev); netdev_dealloc: free_netdev(ndev); out: return err; } static int remove_device(struct device *dev, __maybe_unused void *data) { struct net_device *ndev = to_net_dev(dev); struct cpunet *cpunet = netdev_priv(ndev); netif_tx_disable(ndev); napi_disable(&cpunet->napi); del_timer_sync(&cpunet->retry_rx); cpunet_hw_exit(cpunet->hw); netif_napi_del(&cpunet->napi); unregister_netdev(ndev); free_netdev(ndev); return 0; } static int cpunet_remove(struct platform_device *pdev) { return device_for_each_child(&pdev->dev, NULL, remove_device); } static const struct of_device_id cpunet_match[] = { { .compatible = "avm,cpunet" }, {}, }; MODULE_DEVICE_TABLE(of, cpunet_match); static struct platform_driver cpunet_driver = { .probe = cpunet_probe, .remove = cpunet_remove, .driver = { .name = KBUILD_MODNAME, .of_match_table = cpunet_match, .owner = THIS_MODULE, }, }; module_platform_driver(cpunet_driver); MODULE_AUTHOR("Alexander Theißen "); MODULE_DESCRIPTION("Cross CPU Network Device"); MODULE_LICENSE("GPL v2");