/* Full description of state of transformer. */
 struct xfrm_state
 {
-       /* Note: bydst is re-used during gc */
        struct list_head        all;
-       struct hlist_node       bydst;
+       union {
+               struct list_head        gclist;
+               struct hlist_node       bydst;
+       };
        struct hlist_node       bysrc;
        struct hlist_node       byspi;
 
        walk->count = 0;
 }
 
-static inline void xfrm_state_walk_done(struct xfrm_state_walk *walk)
-{
-       if (walk->state != NULL) {
-               xfrm_state_put(walk->state);
-               walk->state = NULL;
-       }
-}
-
 extern int xfrm_state_walk(struct xfrm_state_walk *walk,
                           int (*func)(struct xfrm_state *, int, void*), void *);
+extern void xfrm_state_walk_done(struct xfrm_state_walk *walk);
 extern struct xfrm_state *xfrm_state_alloc(void);
 extern struct xfrm_state *xfrm_state_find(xfrm_address_t *daddr, xfrm_address_t *saddr, 
                                          struct flowi *fl, struct xfrm_tmpl *tmpl,
 
 static unsigned int xfrm_state_num;
 static unsigned int xfrm_state_genid;
 
+/* Counter indicating ongoing walk, protected by xfrm_state_lock. */
+static unsigned long xfrm_state_walk_ongoing;
+/* Counter indicating walk completion, protected by xfrm_cfg_mutex. */
+static unsigned long xfrm_state_walk_completed;
+
 static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family);
 static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo);
 
 static struct xfrm_state_afinfo *xfrm_state_afinfo[NPROTO];
 
 static struct work_struct xfrm_state_gc_work;
-static HLIST_HEAD(xfrm_state_gc_list);
+static LIST_HEAD(xfrm_state_gc_leftovers);
+static LIST_HEAD(xfrm_state_gc_list);
 static DEFINE_SPINLOCK(xfrm_state_gc_lock);
 
 int __xfrm_state_delete(struct xfrm_state *x);
 
 static void xfrm_state_gc_task(struct work_struct *data)
 {
-       struct xfrm_state *x;
-       struct hlist_node *entry, *tmp;
-       struct hlist_head gc_list;
+       struct xfrm_state *x, *tmp;
+       unsigned long completed;
 
+       mutex_lock(&xfrm_cfg_mutex);
        spin_lock_bh(&xfrm_state_gc_lock);
-       gc_list.first = xfrm_state_gc_list.first;
-       INIT_HLIST_HEAD(&xfrm_state_gc_list);
+       list_splice_tail_init(&xfrm_state_gc_list, &xfrm_state_gc_leftovers);
        spin_unlock_bh(&xfrm_state_gc_lock);
 
-       hlist_for_each_entry_safe(x, entry, tmp, &gc_list, bydst)
+       completed = xfrm_state_walk_completed;
+       mutex_unlock(&xfrm_cfg_mutex);
+
+       list_for_each_entry_safe(x, tmp, &xfrm_state_gc_leftovers, gclist) {
+               if ((long)(x->lastused - completed) > 0)
+                       break;
                xfrm_state_gc_destroy(x);
+       }
 
        wake_up(&km_waitq);
 }
 {
        WARN_ON(x->km.state != XFRM_STATE_DEAD);
 
-       spin_lock_bh(&xfrm_state_lock);
-       list_del(&x->all);
-       spin_unlock_bh(&xfrm_state_lock);
-
        spin_lock_bh(&xfrm_state_gc_lock);
-       hlist_add_head(&x->bydst, &xfrm_state_gc_list);
+       list_add_tail(&x->gclist, &xfrm_state_gc_list);
        spin_unlock_bh(&xfrm_state_gc_lock);
        schedule_work(&xfrm_state_gc_work);
 }
        if (x->km.state != XFRM_STATE_DEAD) {
                x->km.state = XFRM_STATE_DEAD;
                spin_lock(&xfrm_state_lock);
+               x->lastused = xfrm_state_walk_ongoing;
+               list_del_rcu(&x->all);
                hlist_del(&x->bydst);
                hlist_del(&x->bysrc);
                if (x->id.spi)
                        if (err) {
                                xfrm_state_hold(last);
                                walk->state = last;
+                               xfrm_state_walk_ongoing++;
                                goto out;
                        }
                }
                err = func(last, 0, data);
 out:
        spin_unlock_bh(&xfrm_state_lock);
-       if (old != NULL)
+       if (old != NULL) {
                xfrm_state_put(old);
+               xfrm_state_walk_completed++;
+               if (!list_empty(&xfrm_state_gc_leftovers))
+                       schedule_work(&xfrm_state_gc_work);
+       }
        return err;
 }
 EXPORT_SYMBOL(xfrm_state_walk);
 
+void xfrm_state_walk_done(struct xfrm_state_walk *walk)
+{
+       if (walk->state != NULL) {
+               xfrm_state_put(walk->state);
+               walk->state = NULL;
+               xfrm_state_walk_completed++;
+               if (!list_empty(&xfrm_state_gc_leftovers))
+                       schedule_work(&xfrm_state_gc_work);
+       }
+}
+EXPORT_SYMBOL(xfrm_state_walk_done);
+
 
 void xfrm_replay_notify(struct xfrm_state *x, int event)
 {