123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- // SPDX-License-Identifier: GPL-2.0-only
- /* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
- *
- * RMNET Data virtual network driver
- */
- #include <linux/etherdevice.h>
- #include <linux/if_arp.h>
- #include <net/pkt_sched.h>
- #include "rmnet_config.h"
- #include "rmnet_handlers.h"
- #include "rmnet_private.h"
- #include "rmnet_map.h"
- #include "rmnet_vnd.h"
- /* RX/TX Fixup */
- void rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev)
- {
- struct rmnet_priv *priv = netdev_priv(dev);
- struct rmnet_pcpu_stats *pcpu_ptr;
- pcpu_ptr = this_cpu_ptr(priv->pcpu_stats);
- u64_stats_update_begin(&pcpu_ptr->syncp);
- pcpu_ptr->stats.rx_pkts++;
- pcpu_ptr->stats.rx_bytes += skb->len;
- u64_stats_update_end(&pcpu_ptr->syncp);
- }
- void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev)
- {
- struct rmnet_priv *priv = netdev_priv(dev);
- struct rmnet_pcpu_stats *pcpu_ptr;
- pcpu_ptr = this_cpu_ptr(priv->pcpu_stats);
- u64_stats_update_begin(&pcpu_ptr->syncp);
- pcpu_ptr->stats.tx_pkts++;
- pcpu_ptr->stats.tx_bytes += skb->len;
- u64_stats_update_end(&pcpu_ptr->syncp);
- }
- /* Network Device Operations */
- static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
- struct net_device *dev)
- {
- struct rmnet_priv *priv;
- priv = netdev_priv(dev);
- if (priv->real_dev) {
- rmnet_egress_handler(skb);
- } else {
- this_cpu_inc(priv->pcpu_stats->stats.tx_drops);
- kfree_skb(skb);
- }
- return NETDEV_TX_OK;
- }
- static int rmnet_vnd_change_mtu(struct net_device *rmnet_dev, int new_mtu)
- {
- if (new_mtu < 0 || new_mtu > RMNET_MAX_PACKET_SIZE)
- return -EINVAL;
- rmnet_dev->mtu = new_mtu;
- return 0;
- }
- static int rmnet_vnd_get_iflink(const struct net_device *dev)
- {
- struct rmnet_priv *priv = netdev_priv(dev);
- return priv->real_dev->ifindex;
- }
- static int rmnet_vnd_init(struct net_device *dev)
- {
- struct rmnet_priv *priv = netdev_priv(dev);
- int err;
- priv->pcpu_stats = alloc_percpu(struct rmnet_pcpu_stats);
- if (!priv->pcpu_stats)
- return -ENOMEM;
- err = gro_cells_init(&priv->gro_cells, dev);
- if (err) {
- free_percpu(priv->pcpu_stats);
- return err;
- }
- return 0;
- }
- static void rmnet_vnd_uninit(struct net_device *dev)
- {
- struct rmnet_priv *priv = netdev_priv(dev);
- gro_cells_destroy(&priv->gro_cells);
- free_percpu(priv->pcpu_stats);
- }
- static void rmnet_get_stats64(struct net_device *dev,
- struct rtnl_link_stats64 *s)
- {
- struct rmnet_priv *priv = netdev_priv(dev);
- struct rmnet_vnd_stats total_stats;
- struct rmnet_pcpu_stats *pcpu_ptr;
- unsigned int cpu, start;
- memset(&total_stats, 0, sizeof(struct rmnet_vnd_stats));
- for_each_possible_cpu(cpu) {
- pcpu_ptr = per_cpu_ptr(priv->pcpu_stats, cpu);
- do {
- start = u64_stats_fetch_begin_irq(&pcpu_ptr->syncp);
- total_stats.rx_pkts += pcpu_ptr->stats.rx_pkts;
- total_stats.rx_bytes += pcpu_ptr->stats.rx_bytes;
- total_stats.tx_pkts += pcpu_ptr->stats.tx_pkts;
- total_stats.tx_bytes += pcpu_ptr->stats.tx_bytes;
- } while (u64_stats_fetch_retry_irq(&pcpu_ptr->syncp, start));
- total_stats.tx_drops += pcpu_ptr->stats.tx_drops;
- }
- s->rx_packets = total_stats.rx_pkts;
- s->rx_bytes = total_stats.rx_bytes;
- s->tx_packets = total_stats.tx_pkts;
- s->tx_bytes = total_stats.tx_bytes;
- s->tx_dropped = total_stats.tx_drops;
- }
- static const struct net_device_ops rmnet_vnd_ops = {
- .ndo_start_xmit = rmnet_vnd_start_xmit,
- .ndo_change_mtu = rmnet_vnd_change_mtu,
- .ndo_get_iflink = rmnet_vnd_get_iflink,
- .ndo_add_slave = rmnet_add_bridge,
- .ndo_del_slave = rmnet_del_bridge,
- .ndo_init = rmnet_vnd_init,
- .ndo_uninit = rmnet_vnd_uninit,
- .ndo_get_stats64 = rmnet_get_stats64,
- };
- static const char rmnet_gstrings_stats[][ETH_GSTRING_LEN] = {
- "Checksum ok",
- "Checksum valid bit not set",
- "Checksum validation failed",
- "Checksum error bad buffer",
- "Checksum error bad ip version",
- "Checksum error bad transport",
- "Checksum skipped on ip fragment",
- "Checksum skipped",
- "Checksum computed in software",
- };
- static void rmnet_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
- {
- switch (stringset) {
- case ETH_SS_STATS:
- memcpy(buf, &rmnet_gstrings_stats,
- sizeof(rmnet_gstrings_stats));
- break;
- }
- }
- static int rmnet_get_sset_count(struct net_device *dev, int sset)
- {
- switch (sset) {
- case ETH_SS_STATS:
- return ARRAY_SIZE(rmnet_gstrings_stats);
- default:
- return -EOPNOTSUPP;
- }
- }
- static void rmnet_get_ethtool_stats(struct net_device *dev,
- struct ethtool_stats *stats, u64 *data)
- {
- struct rmnet_priv *priv = netdev_priv(dev);
- struct rmnet_priv_stats *st = &priv->stats;
- if (!data)
- return;
- memcpy(data, st, ARRAY_SIZE(rmnet_gstrings_stats) * sizeof(u64));
- }
- static const struct ethtool_ops rmnet_ethtool_ops = {
- .get_ethtool_stats = rmnet_get_ethtool_stats,
- .get_strings = rmnet_get_strings,
- .get_sset_count = rmnet_get_sset_count,
- };
- /* Called by kernel whenever a new rmnet<n> device is created. Sets MTU,
- * flags, ARP type, needed headroom, etc...
- */
- void rmnet_vnd_setup(struct net_device *rmnet_dev)
- {
- rmnet_dev->netdev_ops = &rmnet_vnd_ops;
- rmnet_dev->mtu = RMNET_DFLT_PACKET_SIZE;
- rmnet_dev->needed_headroom = RMNET_NEEDED_HEADROOM;
- eth_random_addr(rmnet_dev->dev_addr);
- rmnet_dev->tx_queue_len = RMNET_TX_QUEUE_LEN;
- /* Raw IP mode */
- rmnet_dev->header_ops = NULL; /* No header */
- rmnet_dev->type = ARPHRD_RAWIP;
- rmnet_dev->hard_header_len = 0;
- rmnet_dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
- rmnet_dev->needs_free_netdev = true;
- rmnet_dev->ethtool_ops = &rmnet_ethtool_ops;
- /* This perm addr will be used as interface identifier by IPv6 */
- rmnet_dev->addr_assign_type = NET_ADDR_RANDOM;
- eth_random_addr(rmnet_dev->perm_addr);
- }
- /* Exposed API */
- int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
- struct rmnet_port *port,
- struct net_device *real_dev,
- struct rmnet_endpoint *ep)
- {
- struct rmnet_priv *priv = netdev_priv(rmnet_dev);
- int rc;
- if (ep->egress_dev)
- return -EINVAL;
- if (rmnet_get_endpoint(port, id))
- return -EBUSY;
- rmnet_dev->hw_features = NETIF_F_RXCSUM;
- rmnet_dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
- rmnet_dev->hw_features |= NETIF_F_SG;
- priv->real_dev = real_dev;
- rc = register_netdevice(rmnet_dev);
- if (!rc) {
- ep->egress_dev = rmnet_dev;
- ep->mux_id = id;
- port->nr_rmnet_devs++;
- rmnet_dev->rtnl_link_ops = &rmnet_link_ops;
- priv->mux_id = id;
- netdev_dbg(rmnet_dev, "rmnet dev created\n");
- }
- return rc;
- }
- int rmnet_vnd_dellink(u8 id, struct rmnet_port *port,
- struct rmnet_endpoint *ep)
- {
- if (id >= RMNET_MAX_LOGICAL_EP || !ep->egress_dev)
- return -EINVAL;
- ep->egress_dev = NULL;
- port->nr_rmnet_devs--;
- return 0;
- }
- int rmnet_vnd_do_flow_control(struct net_device *rmnet_dev, int enable)
- {
- netdev_dbg(rmnet_dev, "Setting VND TX queue state to %d\n", enable);
- /* Although we expect similar number of enable/disable
- * commands, optimize for the disable. That is more
- * latency sensitive than enable
- */
- if (unlikely(enable))
- netif_wake_queue(rmnet_dev);
- else
- netif_stop_queue(rmnet_dev);
- return 0;
- }
|