xen/gnttab: add deferred freeing logic

Rather than just leaking pages that can't be freed at the point where
access permission for the backend domain gets revoked, put them on a
list and run a timer to (infrequently) retry freeing them. (This can
particularly happen when unloading a frontend driver when devices are
still present, and the backend still has them in non-closed state or
hasn't finished closing them yet.)

Signed-off-by: Jan Beulich <jbeulich@suse.com>
Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
This commit is contained in:
Jan Beulich 2012-04-05 16:10:07 +01:00 committed by Konrad Rzeszutek Wilk
parent 9fe2a70153
commit 569ca5b3f9
1 changed files with 96 additions and 10 deletions

View File

@ -426,10 +426,8 @@ static int gnttab_end_foreign_access_ref_v1(grant_ref_t ref, int readonly)
nflags = *pflags; nflags = *pflags;
do { do {
flags = nflags; flags = nflags;
if (flags & (GTF_reading|GTF_writing)) { if (flags & (GTF_reading|GTF_writing))
printk(KERN_ALERT "WARNING: g.e. still in use!\n");
return 0; return 0;
}
} while ((nflags = sync_cmpxchg(pflags, flags, 0)) != flags); } while ((nflags = sync_cmpxchg(pflags, flags, 0)) != flags);
return 1; return 1;
@ -458,12 +456,103 @@ static int gnttab_end_foreign_access_ref_v2(grant_ref_t ref, int readonly)
return 1; return 1;
} }
int gnttab_end_foreign_access_ref(grant_ref_t ref, int readonly) static inline int _gnttab_end_foreign_access_ref(grant_ref_t ref, int readonly)
{ {
return gnttab_interface->end_foreign_access_ref(ref, readonly); return gnttab_interface->end_foreign_access_ref(ref, readonly);
} }
int gnttab_end_foreign_access_ref(grant_ref_t ref, int readonly)
{
if (_gnttab_end_foreign_access_ref(ref, readonly))
return 1;
pr_warn("WARNING: g.e. %#x still in use!\n", ref);
return 0;
}
EXPORT_SYMBOL_GPL(gnttab_end_foreign_access_ref); EXPORT_SYMBOL_GPL(gnttab_end_foreign_access_ref);
struct deferred_entry {
struct list_head list;
grant_ref_t ref;
bool ro;
uint16_t warn_delay;
struct page *page;
};
static LIST_HEAD(deferred_list);
static void gnttab_handle_deferred(unsigned long);
static DEFINE_TIMER(deferred_timer, gnttab_handle_deferred, 0, 0);
static void gnttab_handle_deferred(unsigned long unused)
{
unsigned int nr = 10;
struct deferred_entry *first = NULL;
unsigned long flags;
spin_lock_irqsave(&gnttab_list_lock, flags);
while (nr--) {
struct deferred_entry *entry
= list_first_entry(&deferred_list,
struct deferred_entry, list);
if (entry == first)
break;
list_del(&entry->list);
spin_unlock_irqrestore(&gnttab_list_lock, flags);
if (_gnttab_end_foreign_access_ref(entry->ref, entry->ro)) {
put_free_entry(entry->ref);
if (entry->page) {
pr_debug("freeing g.e. %#x (pfn %#lx)\n",
entry->ref, page_to_pfn(entry->page));
__free_page(entry->page);
} else
pr_info("freeing g.e. %#x\n", entry->ref);
kfree(entry);
entry = NULL;
} else {
if (!--entry->warn_delay)
pr_info("g.e. %#x still pending\n",
entry->ref);
if (!first)
first = entry;
}
spin_lock_irqsave(&gnttab_list_lock, flags);
if (entry)
list_add_tail(&entry->list, &deferred_list);
else if (list_empty(&deferred_list))
break;
}
if (!list_empty(&deferred_list) && !timer_pending(&deferred_timer)) {
deferred_timer.expires = jiffies + HZ;
add_timer(&deferred_timer);
}
spin_unlock_irqrestore(&gnttab_list_lock, flags);
}
static void gnttab_add_deferred(grant_ref_t ref, bool readonly,
struct page *page)
{
struct deferred_entry *entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
const char *what = KERN_WARNING "leaking";
if (entry) {
unsigned long flags;
entry->ref = ref;
entry->ro = readonly;
entry->page = page;
entry->warn_delay = 60;
spin_lock_irqsave(&gnttab_list_lock, flags);
list_add_tail(&entry->list, &deferred_list);
if (!timer_pending(&deferred_timer)) {
deferred_timer.expires = jiffies + HZ;
add_timer(&deferred_timer);
}
spin_unlock_irqrestore(&gnttab_list_lock, flags);
what = KERN_DEBUG "deferring";
}
printk("%s g.e. %#x (pfn %#lx)\n",
what, ref, page ? page_to_pfn(page) : -1);
}
void gnttab_end_foreign_access(grant_ref_t ref, int readonly, void gnttab_end_foreign_access(grant_ref_t ref, int readonly,
unsigned long page) unsigned long page)
{ {
@ -471,12 +560,9 @@ void gnttab_end_foreign_access(grant_ref_t ref, int readonly,
put_free_entry(ref); put_free_entry(ref);
if (page != 0) if (page != 0)
free_page(page); free_page(page);
} else { } else
/* XXX This needs to be fixed so that the ref and page are gnttab_add_deferred(ref, readonly,
placed on a list to be freed up later. */ page ? virt_to_page(page) : NULL);
printk(KERN_WARNING
"WARNING: leaking g.e. and page still in use!\n");
}
} }
EXPORT_SYMBOL_GPL(gnttab_end_foreign_access); EXPORT_SYMBOL_GPL(gnttab_end_foreign_access);