Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[pandora-kernel.git] / net / core / dev.c
index 6785b06..3942266 100644 (file)
 #include <linux/in.h>
 #include <linux/jhash.h>
 #include <linux/random.h>
+#include <trace/napi.h>
 
 #include "net-sysfs.h"
 
@@ -1688,6 +1689,14 @@ int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
                                goto gso;
                }
 
+               /*
+                * If device doesnt need skb->dst, release it right now while
+                * its hot in this cpu cache
+                */
+               if ((dev->priv_flags & IFF_XMIT_DST_RELEASE) && skb->dst) {
+                       dst_release(skb->dst);
+                       skb->dst = NULL;
+               }
                rc = ops->ndo_start_xmit(skb, dev);
                /*
                 * TODO: if skb_orphan() was called by
@@ -1737,9 +1746,14 @@ u16 skb_tx_hash(const struct net_device *dev, const struct sk_buff *skb)
 
        if (skb_rx_queue_recorded(skb)) {
                hash = skb_get_rx_queue(skb);
-       } else if (skb->sk && skb->sk->sk_hash) {
+               while (unlikely (hash >= dev->real_num_tx_queues))
+                       hash -= dev->real_num_tx_queues;
+               return hash;
+       }
+
+       if (skb->sk && skb->sk->sk_hash)
                hash = skb->sk->sk_hash;
-       else
+       else
                hash = skb->protocol;
 
        hash = jhash_1word(hash, skb_tx_hashrnd);
@@ -2758,8 +2772,10 @@ static void net_rx_action(struct softirq_action *h)
                 * accidently calling ->poll() when NAPI is not scheduled.
                 */
                work = 0;
-               if (test_bit(NAPI_STATE_SCHED, &n->state))
+               if (test_bit(NAPI_STATE_SCHED, &n->state)) {
                        work = n->poll(n, weight);
+                       trace_napi_poll(n);
+               }
 
                WARN_ON_ONCE(work > weight);
 
@@ -3429,6 +3445,252 @@ void dev_set_rx_mode(struct net_device *dev)
        netif_addr_unlock_bh(dev);
 }
 
+/* hw addresses list handling functions */
+
+static int __hw_addr_add(struct list_head *list, unsigned char *addr,
+                        int addr_len, unsigned char addr_type)
+{
+       struct netdev_hw_addr *ha;
+       int alloc_size;
+
+       if (addr_len > MAX_ADDR_LEN)
+               return -EINVAL;
+
+       alloc_size = sizeof(*ha);
+       if (alloc_size < L1_CACHE_BYTES)
+               alloc_size = L1_CACHE_BYTES;
+       ha = kmalloc(alloc_size, GFP_ATOMIC);
+       if (!ha)
+               return -ENOMEM;
+       memcpy(ha->addr, addr, addr_len);
+       ha->type = addr_type;
+       list_add_tail_rcu(&ha->list, list);
+       return 0;
+}
+
+static void ha_rcu_free(struct rcu_head *head)
+{
+       struct netdev_hw_addr *ha;
+
+       ha = container_of(head, struct netdev_hw_addr, rcu_head);
+       kfree(ha);
+}
+
+static int __hw_addr_del_ii(struct list_head *list, unsigned char *addr,
+                           int addr_len, unsigned char addr_type,
+                           int ignore_index)
+{
+       struct netdev_hw_addr *ha;
+       int i = 0;
+
+       list_for_each_entry(ha, list, list) {
+               if (i++ != ignore_index &&
+                   !memcmp(ha->addr, addr, addr_len) &&
+                   (ha->type == addr_type || !addr_type)) {
+                       list_del_rcu(&ha->list);
+                       call_rcu(&ha->rcu_head, ha_rcu_free);
+                       return 0;
+               }
+       }
+       return -ENOENT;
+}
+
+static int __hw_addr_add_multiple_ii(struct list_head *to_list,
+                                    struct list_head *from_list,
+                                    int addr_len, unsigned char addr_type,
+                                    int ignore_index)
+{
+       int err;
+       struct netdev_hw_addr *ha, *ha2;
+       unsigned char type;
+
+       list_for_each_entry(ha, from_list, list) {
+               type = addr_type ? addr_type : ha->type;
+               err = __hw_addr_add(to_list, ha->addr, addr_len, type);
+               if (err)
+                       goto unroll;
+       }
+       return 0;
+
+unroll:
+       list_for_each_entry(ha2, from_list, list) {
+               if (ha2 == ha)
+                       break;
+               type = addr_type ? addr_type : ha2->type;
+               __hw_addr_del_ii(to_list, ha2->addr, addr_len, type,
+                                ignore_index);
+       }
+       return err;
+}
+
+static void __hw_addr_del_multiple_ii(struct list_head *to_list,
+                                     struct list_head *from_list,
+                                     int addr_len, unsigned char addr_type,
+                                     int ignore_index)
+{
+       struct netdev_hw_addr *ha;
+       unsigned char type;
+
+       list_for_each_entry(ha, from_list, list) {
+               type = addr_type ? addr_type : ha->type;
+               __hw_addr_del_ii(to_list, ha->addr, addr_len, addr_type,
+                                ignore_index);
+       }
+}
+
+static void __hw_addr_flush(struct list_head *list)
+{
+       struct netdev_hw_addr *ha, *tmp;
+
+       list_for_each_entry_safe(ha, tmp, list, list) {
+               list_del_rcu(&ha->list);
+               call_rcu(&ha->rcu_head, ha_rcu_free);
+       }
+}
+
+/* Device addresses handling functions */
+
+static void dev_addr_flush(struct net_device *dev)
+{
+       /* rtnl_mutex must be held here */
+
+       __hw_addr_flush(&dev->dev_addr_list);
+       dev->dev_addr = NULL;
+}
+
+static int dev_addr_init(struct net_device *dev)
+{
+       unsigned char addr[MAX_ADDR_LEN];
+       struct netdev_hw_addr *ha;
+       int err;
+
+       /* rtnl_mutex must be held here */
+
+       INIT_LIST_HEAD(&dev->dev_addr_list);
+       memset(addr, 0, sizeof(*addr));
+       err = __hw_addr_add(&dev->dev_addr_list, addr, sizeof(*addr),
+                           NETDEV_HW_ADDR_T_LAN);
+       if (!err) {
+               /*
+                * Get the first (previously created) address from the list
+                * and set dev_addr pointer to this location.
+                */
+               ha = list_first_entry(&dev->dev_addr_list,
+                                     struct netdev_hw_addr, list);
+               dev->dev_addr = ha->addr;
+       }
+       return err;
+}
+
+/**
+ *     dev_addr_add    - Add a device address
+ *     @dev: device
+ *     @addr: address to add
+ *     @addr_type: address type
+ *
+ *     Add a device address to the device or increase the reference count if
+ *     it already exists.
+ *
+ *     The caller must hold the rtnl_mutex.
+ */
+int dev_addr_add(struct net_device *dev, unsigned char *addr,
+                unsigned char addr_type)
+{
+       int err;
+
+       ASSERT_RTNL();
+
+       err = __hw_addr_add(&dev->dev_addr_list, addr, dev->addr_len,
+                           addr_type);
+       if (!err)
+               call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
+       return err;
+}
+EXPORT_SYMBOL(dev_addr_add);
+
+/**
+ *     dev_addr_del    - Release a device address.
+ *     @dev: device
+ *     @addr: address to delete
+ *     @addr_type: address type
+ *
+ *     Release reference to a device address and remove it from the device
+ *     if the reference count drops to zero.
+ *
+ *     The caller must hold the rtnl_mutex.
+ */
+int dev_addr_del(struct net_device *dev, unsigned char *addr,
+                unsigned char addr_type)
+{
+       int err;
+
+       ASSERT_RTNL();
+
+       err = __hw_addr_del_ii(&dev->dev_addr_list, addr, dev->addr_len,
+                              addr_type, 0);
+       if (!err)
+               call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
+       return err;
+}
+EXPORT_SYMBOL(dev_addr_del);
+
+/**
+ *     dev_addr_add_multiple   - Add device addresses from another device
+ *     @to_dev: device to which addresses will be added
+ *     @from_dev: device from which addresses will be added
+ *     @addr_type: address type - 0 means type will be used from from_dev
+ *
+ *     Add device addresses of the one device to another.
+ **
+ *     The caller must hold the rtnl_mutex.
+ */
+int dev_addr_add_multiple(struct net_device *to_dev,
+                         struct net_device *from_dev,
+                         unsigned char addr_type)
+{
+       int err;
+
+       ASSERT_RTNL();
+
+       if (from_dev->addr_len != to_dev->addr_len)
+               return -EINVAL;
+       err = __hw_addr_add_multiple_ii(&to_dev->dev_addr_list,
+                                       &from_dev->dev_addr_list,
+                                       to_dev->addr_len, addr_type, 0);
+       if (!err)
+               call_netdevice_notifiers(NETDEV_CHANGEADDR, to_dev);
+       return err;
+}
+EXPORT_SYMBOL(dev_addr_add_multiple);
+
+/**
+ *     dev_addr_del_multiple   - Delete device addresses by another device
+ *     @to_dev: device where the addresses will be deleted
+ *     @from_dev: device by which addresses the addresses will be deleted
+ *     @addr_type: address type - 0 means type will used from from_dev
+ *
+ *     Deletes addresses in to device by the list of addresses in from device.
+ *
+ *     The caller must hold the rtnl_mutex.
+ */
+int dev_addr_del_multiple(struct net_device *to_dev,
+                         struct net_device *from_dev,
+                         unsigned char addr_type)
+{
+       ASSERT_RTNL();
+
+       if (from_dev->addr_len != to_dev->addr_len)
+               return -EINVAL;
+       __hw_addr_del_multiple_ii(&to_dev->dev_addr_list,
+                                 &from_dev->dev_addr_list,
+                                 to_dev->addr_len, addr_type, 0);
+       call_netdevice_notifiers(NETDEV_CHANGEADDR, to_dev);
+       return 0;
+}
+EXPORT_SYMBOL(dev_addr_del_multiple);
+
+/* unicast and multicast addresses handling functions */
+
 int __dev_addr_delete(struct dev_addr_list **list, int *count,
                      void *addr, int alen, int glbl)
 {
@@ -4692,13 +4954,30 @@ void netdev_run_todo(void)
  *     the internal statistics structure is used.
  */
 const struct net_device_stats *dev_get_stats(struct net_device *dev)
- {
+{
        const struct net_device_ops *ops = dev->netdev_ops;
 
        if (ops->ndo_get_stats)
                return ops->ndo_get_stats(dev);
-       else
-               return &dev->stats;
+       else {
+               unsigned long tx_bytes = 0, tx_packets = 0, tx_dropped = 0;
+               struct net_device_stats *stats = &dev->stats;
+               unsigned int i;
+               struct netdev_queue *txq;
+
+               for (i = 0; i < dev->num_tx_queues; i++) {
+                       txq = netdev_get_tx_queue(dev, i);
+                       tx_bytes   += txq->tx_bytes;
+                       tx_packets += txq->tx_packets;
+                       tx_dropped += txq->tx_dropped;
+               }
+               if (tx_bytes || tx_packets || tx_dropped) {
+                       stats->tx_bytes   = tx_bytes;
+                       stats->tx_packets = tx_packets;
+                       stats->tx_dropped = tx_dropped;
+               }
+               return stats;
+       }
 }
 EXPORT_SYMBOL(dev_get_stats);
 
@@ -4756,13 +5035,16 @@ struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
        if (!tx) {
                printk(KERN_ERR "alloc_netdev: Unable to allocate "
                       "tx qdiscs.\n");
-               kfree(p);
-               return NULL;
+               goto free_p;
        }
 
        dev = (struct net_device *)
                (((long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST);
        dev->padded = (char *)dev - (char *)p;
+
+       if (dev_addr_init(dev))
+               goto free_tx;
+
        dev_net_set(dev, &init_net);
 
        dev->_tx = tx;
@@ -4774,9 +5056,17 @@ struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
        netdev_init_queues(dev);
 
        INIT_LIST_HEAD(&dev->napi_list);
+       dev->priv_flags = IFF_XMIT_DST_RELEASE;
        setup(dev);
        strcpy(dev->name, name);
        return dev;
+
+free_tx:
+       kfree(tx);
+
+free_p:
+       kfree(p);
+       return NULL;
 }
 EXPORT_SYMBOL(alloc_netdev_mq);
 
@@ -4796,6 +5086,9 @@ void free_netdev(struct net_device *dev)
 
        kfree(dev->_tx);
 
+       /* Flush device addresses */
+       dev_addr_flush(dev);
+
        list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
                netif_napi_del(p);