/*
       * net/dst.c	Protocol independent destination cache.
       *
       * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
       *
       */
      
      #include <asm/system.h>
      #include <asm/bitops.h>
      #include <linux/types.h>
      #include <linux/kernel.h>
      #include <linux/sched.h>
      #include <linux/mm.h>
      #include <linux/string.h>
      #include <linux/errno.h>
      #include <linux/netdevice.h>
      #include <linux/skbuff.h>
      #include <linux/init.h>
      
      #include <net/dst.h>
      
      /* Locking strategy:
       * 1) Garbage collection state of dead destination cache
       *    entries is protected by dst_lock.
       * 2) GC is run only from BH context, and is the only remover
       *    of entries.
       * 3) Entries are added to the garbage list from both BH
       *    and non-BH context, so local BH disabling is needed.
       * 4) All operations modify state, so a spinlock is used.
       */
      static struct dst_entry 	*dst_garbage_list;
      static atomic_t			 dst_total = ATOMIC_INIT(0);
      static spinlock_t		 dst_lock = SPIN_LOCK_UNLOCKED;
      
      static unsigned long dst_gc_timer_expires;
      static unsigned long dst_gc_timer_inc = DST_GC_MAX;
      static void dst_run_gc(unsigned long);
      
      static struct timer_list dst_gc_timer =
      	{ data: DST_GC_MIN, function: dst_run_gc };
      
      
  43  static void dst_run_gc(unsigned long dummy)
      {
      	int    delayed = 0;
      	struct dst_entry * dst, **dstp;
      
  48  	if (!spin_trylock(&dst_lock)) {
      		mod_timer(&dst_gc_timer, jiffies + HZ/10);
  50  		return;
      	}
      
      
      	del_timer(&dst_gc_timer);
      	dstp = &dst_garbage_list;
  56  	while ((dst = *dstp) != NULL) {
  57  		if (atomic_read(&dst->__refcnt)) {
      			dstp = &dst->next;
      			delayed++;
  60  			continue;
      		}
      		*dstp = dst->next;
      		dst_destroy(dst);
      	}
  65  	if (!dst_garbage_list) {
      		dst_gc_timer_inc = DST_GC_MAX;
  67  		goto out;
      	}
  69  	if ((dst_gc_timer_expires += dst_gc_timer_inc) > DST_GC_MAX)
      		dst_gc_timer_expires = DST_GC_MAX;
      	dst_gc_timer_inc += DST_GC_INC;
      	dst_gc_timer.expires = jiffies + dst_gc_timer_expires;
      #if RT_CACHE_DEBUG >= 2
      	printk("dst_total: %d/%d %ld\n",
      	       atomic_read(&dst_total), delayed,  dst_gc_timer_expires);
      #endif
      	add_timer(&dst_gc_timer);
      
      out:
  80  	spin_unlock(&dst_lock);
      }
      
  83  static int dst_discard(struct sk_buff *skb)
      {
      	kfree_skb(skb);
  86  	return 0;
      }
      
  89  static int dst_blackhole(struct sk_buff *skb)
      {
      	kfree_skb(skb);
  92  	return 0;
      }
      
  95  void * dst_alloc(struct dst_ops * ops)
      {
      	struct dst_entry * dst;
      
  99  	if (ops->gc && atomic_read(&ops->entries) > ops->gc_thresh) {
 100  		if (ops->gc())
 101  			return NULL;
      	}
      	dst = kmem_cache_alloc(ops->kmem_cachep, SLAB_ATOMIC);
 104  	if (!dst)
 105  		return NULL;
      	memset(dst, 0, ops->entry_size);
      	dst->ops = ops;
      	dst->lastuse = jiffies;
      	dst->input = dst_discard;
      	dst->output = dst_blackhole;
      	atomic_inc(&dst_total);
      	atomic_inc(&ops->entries);
 113  	return dst;
      }
      
 116  void __dst_free(struct dst_entry * dst)
      {
 118  	spin_lock_bh(&dst_lock);
      
      	/* The first case (dev==NULL) is required, when
      	   protocol module is unloaded.
      	 */
 123  	if (dst->dev == NULL || !(dst->dev->flags&IFF_UP)) {
      		dst->input = dst_discard;
      		dst->output = dst_blackhole;
      	}
      	dst->obsolete = 2;
      	dst->next = dst_garbage_list;
      	dst_garbage_list = dst;
 130  	if (dst_gc_timer_inc > DST_GC_INC) {
      		del_timer(&dst_gc_timer);
      		dst_gc_timer_inc = DST_GC_INC;
      		dst_gc_timer_expires = DST_GC_MIN;
      		dst_gc_timer.expires = jiffies + dst_gc_timer_expires;
      		add_timer(&dst_gc_timer);
      	}
      
 138  	spin_unlock_bh(&dst_lock);
      }
      
 141  void dst_destroy(struct dst_entry * dst)
      {
      	struct neighbour *neigh = dst->neighbour;
      	struct hh_cache *hh = dst->hh;
      
      	dst->hh = NULL;
 147  	if (hh && atomic_dec_and_test(&hh->hh_refcnt))
      		kfree(hh);
      
 150  	if (neigh) {
      		dst->neighbour = NULL;
      		neigh_release(neigh);
      	}
      
      	atomic_dec(&dst->ops->entries);
      
 157  	if (dst->ops->destroy)
      		dst->ops->destroy(dst);
 159  	if (dst->dev)
      		dev_put(dst->dev);
      	atomic_dec(&dst_total);
      	kmem_cache_free(dst->ops->kmem_cachep, dst);
      }
      
 165  static int dst_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
      {
      	struct net_device *dev = ptr;
      	struct dst_entry *dst;
      
 170  	switch (event) {
 171  	case NETDEV_UNREGISTER:
 172  	case NETDEV_DOWN:
 173  		spin_lock_bh(&dst_lock);
 174  		for (dst = dst_garbage_list; dst; dst = dst->next) {
 175  			if (dst->dev == dev) {
      				/* Dirty hack. We did it in 2.2 (in __dst_free),
      				   we have _very_ good reasons not to repeat
      				   this mistake in 2.3, but we have no choice
      				   now. _It_ _is_ _explicit_ _deliberate_
      				   _race_ _condition_.
      				 */
      				if (event!=NETDEV_DOWN &&
      				    !(dev->features & NETIF_F_DYNALLOC) &&
 184  				    dst->output == dst_blackhole) {
      					dst->dev = &loopback_dev;
      					dev_put(dev);
      					dev_hold(&loopback_dev);
      					dst->output = dst_discard;
 189  					if (dst->neighbour && dst->neighbour->dev == dev) {
      						dst->neighbour->dev = &loopback_dev;
      						dev_put(dev);
      						dev_hold(&loopback_dev);
      					}
 194  				} else {
      					dst->input = dst_discard;
      					dst->output = dst_blackhole;
      				}
      			}
      		}
 200  		spin_unlock_bh(&dst_lock);
 201  		break;
      	}
 203  	return NOTIFY_DONE;
      }
      
      struct notifier_block dst_dev_notifier = {
      	dst_dev_event,
      	NULL,
      	0
      };
      
 212  void __init dst_init(void)
      {
      	register_netdevice_notifier(&dst_dev_notifier);
      }