diff options
Diffstat (limited to 'drivers/net/dummy.c')
| -rw-r--r-- | drivers/net/dummy.c | 188 |
1 files changed, 124 insertions, 64 deletions
diff --git a/drivers/net/dummy.c b/drivers/net/dummy.c index dd8c15ac5c7..0932ffbf381 100644 --- a/drivers/net/dummy.c +++ b/drivers/net/dummy.c @@ -11,7 +11,7 @@ One solution is to set up a dummy link using PPP/SLIP/PLIP, but this seems (to me) too much overhead for too little gain. This driver provides a small alternative. Thus you can do - + [when not running slip] ifconfig dummy slip.addr.ess.here up [to go to slip] @@ -28,125 +28,185 @@ Alan Cox, 30th May 1994 */ -#include <linux/config.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/init.h> #include <linux/moduleparam.h> +#include <linux/rtnetlink.h> +#include <net/rtnetlink.h> +#include <linux/u64_stats_sync.h> static int numdummies = 1; -static int dummy_xmit(struct sk_buff *skb, struct net_device *dev); -static struct net_device_stats *dummy_get_stats(struct net_device *dev); +/* fake multicast ability */ +static void set_multicast_list(struct net_device *dev) +{ +} + +struct pcpu_dstats { + u64 tx_packets; + u64 tx_bytes; + struct u64_stats_sync syncp; +}; + +static struct rtnl_link_stats64 *dummy_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *stats) +{ + int i; + + for_each_possible_cpu(i) { + const struct pcpu_dstats *dstats; + u64 tbytes, tpackets; + unsigned int start; + + dstats = per_cpu_ptr(dev->dstats, i); + do { + start = u64_stats_fetch_begin_irq(&dstats->syncp); + tbytes = dstats->tx_bytes; + tpackets = dstats->tx_packets; + } while (u64_stats_fetch_retry_irq(&dstats->syncp, start)); + stats->tx_bytes += tbytes; + stats->tx_packets += tpackets; + } + return stats; +} + +static netdev_tx_t dummy_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct pcpu_dstats *dstats = this_cpu_ptr(dev->dstats); + + u64_stats_update_begin(&dstats->syncp); + dstats->tx_packets++; + dstats->tx_bytes += skb->len; + u64_stats_update_end(&dstats->syncp); + + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} -static int dummy_set_address(struct net_device *dev, void *p) +static int dummy_dev_init(struct net_device *dev) { - struct sockaddr *sa = p; + dev->dstats = netdev_alloc_pcpu_stats(struct pcpu_dstats); + if (!dev->dstats) + return -ENOMEM; - if (!is_valid_ether_addr(sa->sa_data)) - return -EADDRNOTAVAIL; - - memcpy(dev->dev_addr, sa->sa_data, ETH_ALEN); return 0; } -/* fake multicast ability */ -static void set_multicast_list(struct net_device *dev) +static void dummy_dev_uninit(struct net_device *dev) { + free_percpu(dev->dstats); } -static void __init dummy_setup(struct net_device *dev) +static int dummy_change_carrier(struct net_device *dev, bool new_carrier) { + if (new_carrier) + netif_carrier_on(dev); + else + netif_carrier_off(dev); + return 0; +} + +static const struct net_device_ops dummy_netdev_ops = { + .ndo_init = dummy_dev_init, + .ndo_uninit = dummy_dev_uninit, + .ndo_start_xmit = dummy_xmit, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_rx_mode = set_multicast_list, + .ndo_set_mac_address = eth_mac_addr, + .ndo_get_stats64 = dummy_get_stats64, + .ndo_change_carrier = dummy_change_carrier, +}; + +static void dummy_setup(struct net_device *dev) +{ + ether_setup(dev); + /* Initialize the device structure. */ - dev->get_stats = dummy_get_stats; - dev->hard_start_xmit = dummy_xmit; - dev->set_multicast_list = set_multicast_list; - dev->set_mac_address = dummy_set_address; + dev->netdev_ops = &dummy_netdev_ops; + dev->destructor = free_netdev; /* Fill in device structure with ethernet-generic values. */ - ether_setup(dev); dev->tx_queue_len = 0; - dev->change_mtu = NULL; dev->flags |= IFF_NOARP; dev->flags &= ~IFF_MULTICAST; - SET_MODULE_OWNER(dev); - random_ether_addr(dev->dev_addr); + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + dev->features |= NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_TSO; + dev->features |= NETIF_F_HW_CSUM | NETIF_F_HIGHDMA | NETIF_F_LLTX; + eth_hw_addr_random(dev); } -static int dummy_xmit(struct sk_buff *skb, struct net_device *dev) +static int dummy_validate(struct nlattr *tb[], struct nlattr *data[]) { - struct net_device_stats *stats = netdev_priv(dev); - - stats->tx_packets++; - stats->tx_bytes+=skb->len; - - dev_kfree_skb(skb); + if (tb[IFLA_ADDRESS]) { + if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) + return -EINVAL; + if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) + return -EADDRNOTAVAIL; + } return 0; } -static struct net_device_stats *dummy_get_stats(struct net_device *dev) -{ - return netdev_priv(dev); -} - -static struct net_device **dummies; +static struct rtnl_link_ops dummy_link_ops __read_mostly = { + .kind = "dummy", + .setup = dummy_setup, + .validate = dummy_validate, +}; /* Number of dummy devices to be set up by this module. */ module_param(numdummies, int, 0); MODULE_PARM_DESC(numdummies, "Number of dummy pseudo devices"); -static int __init dummy_init_one(int index) +static int __init dummy_init_one(void) { struct net_device *dev_dummy; int err; - dev_dummy = alloc_netdev(sizeof(struct net_device_stats), - "dummy%d", dummy_setup); - + dev_dummy = alloc_netdev(0, "dummy%d", dummy_setup); if (!dev_dummy) return -ENOMEM; - if ((err = register_netdev(dev_dummy))) { - free_netdev(dev_dummy); - dev_dummy = NULL; - } else { - dummies[index] = dev_dummy; - } + dev_dummy->rtnl_link_ops = &dummy_link_ops; + err = register_netdevice(dev_dummy); + if (err < 0) + goto err; + return 0; +err: + free_netdev(dev_dummy); return err; } -static void dummy_free_one(int index) -{ - unregister_netdev(dummies[index]); - free_netdev(dummies[index]); -} - static int __init dummy_init_module(void) -{ +{ int i, err = 0; - dummies = kmalloc(numdummies * sizeof(void *), GFP_KERNEL); - if (!dummies) - return -ENOMEM; - for (i = 0; i < numdummies && !err; i++) - err = dummy_init_one(i); - if (err) { - while (--i >= 0) - dummy_free_one(i); + + rtnl_lock(); + err = __rtnl_link_register(&dummy_link_ops); + if (err < 0) + goto out; + + for (i = 0; i < numdummies && !err; i++) { + err = dummy_init_one(); + cond_resched(); } + if (err < 0) + __rtnl_link_unregister(&dummy_link_ops); + +out: + rtnl_unlock(); + return err; -} +} static void __exit dummy_cleanup_module(void) { - int i; - for (i = 0; i < numdummies; i++) - dummy_free_one(i); - kfree(dummies); + rtnl_link_unregister(&dummy_link_ops); } module_init(dummy_init_module); module_exit(dummy_cleanup_module); MODULE_LICENSE("GPL"); +MODULE_ALIAS_RTNL_LINK("dummy"); |
