diff options
Diffstat (limited to 'mm/memory-failure.c')
| -rw-r--r-- | mm/memory-failure.c | 159 | 
1 files changed, 94 insertions, 65 deletions
diff --git a/mm/memory-failure.c b/mm/memory-failure.c index c53543d89282..95882692e747 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -130,27 +130,15 @@ static int hwpoison_filter_flags(struct page *p)   * can only guarantee that the page either belongs to the memcg tasks, or is   * a freed page.   */ -#ifdef	CONFIG_MEMCG_SWAP +#ifdef CONFIG_MEMCG  u64 hwpoison_filter_memcg;  EXPORT_SYMBOL_GPL(hwpoison_filter_memcg);  static int hwpoison_filter_task(struct page *p)  { -	struct mem_cgroup *mem; -	struct cgroup_subsys_state *css; -	unsigned long ino; -  	if (!hwpoison_filter_memcg)  		return 0; -	mem = try_get_mem_cgroup_from_page(p); -	if (!mem) -		return -EINVAL; - -	css = mem_cgroup_css(mem); -	ino = cgroup_ino(css->cgroup); -	css_put(css); - -	if (ino != hwpoison_filter_memcg) +	if (page_cgroup_ino(p) != hwpoison_filter_memcg)  		return -EINVAL;  	return 0; @@ -909,6 +897,18 @@ int get_hwpoison_page(struct page *page)  	 * directly for tail pages.  	 */  	if (PageTransHuge(head)) { +		/* +		 * Non anonymous thp exists only in allocation/free time. We +		 * can't handle such a case correctly, so let's give it up. +		 * This should be better than triggering BUG_ON when kernel +		 * tries to touch the "partially handled" page. +		 */ +		if (!PageAnon(head)) { +			pr_err("MCE: %#lx: non anonymous thp\n", +				page_to_pfn(page)); +			return 0; +		} +  		if (get_page_unless_zero(head)) {  			if (PageTail(page))  				get_page(page); @@ -922,6 +922,27 @@ int get_hwpoison_page(struct page *page)  }  EXPORT_SYMBOL_GPL(get_hwpoison_page); +/** + * put_hwpoison_page() - Put refcount for memory error handling: + * @page:	raw error page (hit by memory error) + */ +void put_hwpoison_page(struct page *page) +{ +	struct page *head = compound_head(page); + +	if (PageHuge(head)) { +		put_page(head); +		return; +	} + +	if (PageTransHuge(head)) +		if (page != head) +			put_page(head); + +	put_page(page); +} +EXPORT_SYMBOL_GPL(put_hwpoison_page); +  /*   * Do all that is necessary to remove user space mappings. Unmap   * the pages and send SIGBUS to the processes if the data was dirty. @@ -1088,7 +1109,7 @@ int memory_failure(unsigned long pfn, int trapno, int flags)  		nr_pages = 1 << compound_order(hpage);  	else /* normal page or thp */  		nr_pages = 1; -	atomic_long_add(nr_pages, &num_poisoned_pages); +	num_poisoned_pages_add(nr_pages);  	/*  	 * We need/can do nothing about count=0 pages. @@ -1116,7 +1137,7 @@ int memory_failure(unsigned long pfn, int trapno, int flags)  			if (PageHWPoison(hpage)) {  				if ((hwpoison_filter(p) && TestClearPageHWPoison(p))  				    || (p != hpage && TestSetPageHWPoison(hpage))) { -					atomic_long_sub(nr_pages, &num_poisoned_pages); +					num_poisoned_pages_sub(nr_pages);  					unlock_page(hpage);  					return 0;  				} @@ -1134,22 +1155,14 @@ int memory_failure(unsigned long pfn, int trapno, int flags)  	}  	if (!PageHuge(p) && PageTransHuge(hpage)) { -		if (!PageAnon(hpage)) { -			pr_err("MCE: %#lx: non anonymous thp\n", pfn); -			if (TestClearPageHWPoison(p)) -				atomic_long_sub(nr_pages, &num_poisoned_pages); -			put_page(p); -			if (p != hpage) -				put_page(hpage); -			return -EBUSY; -		} -		if (unlikely(split_huge_page(hpage))) { -			pr_err("MCE: %#lx: thp split failed\n", pfn); +		if (!PageAnon(hpage) || unlikely(split_huge_page(hpage))) { +			if (!PageAnon(hpage)) +				pr_err("MCE: %#lx: non anonymous thp\n", pfn); +			else +				pr_err("MCE: %#lx: thp split failed\n", pfn);  			if (TestClearPageHWPoison(p)) -				atomic_long_sub(nr_pages, &num_poisoned_pages); -			put_page(p); -			if (p != hpage) -				put_page(hpage); +				num_poisoned_pages_sub(nr_pages); +			put_hwpoison_page(p);  			return -EBUSY;  		}  		VM_BUG_ON_PAGE(!page_count(p), p); @@ -1208,16 +1221,16 @@ int memory_failure(unsigned long pfn, int trapno, int flags)  	 */  	if (!PageHWPoison(p)) {  		printk(KERN_ERR "MCE %#lx: just unpoisoned\n", pfn); -		atomic_long_sub(nr_pages, &num_poisoned_pages); -		put_page(hpage); -		res = 0; -		goto out; +		num_poisoned_pages_sub(nr_pages); +		unlock_page(hpage); +		put_hwpoison_page(hpage); +		return 0;  	}  	if (hwpoison_filter(p)) {  		if (TestClearPageHWPoison(p)) -			atomic_long_sub(nr_pages, &num_poisoned_pages); +			num_poisoned_pages_sub(nr_pages);  		unlock_page(hpage); -		put_page(hpage); +		put_hwpoison_page(hpage);  		return 0;  	} @@ -1231,7 +1244,7 @@ int memory_failure(unsigned long pfn, int trapno, int flags)  	if (PageHuge(p) && PageTail(p) && TestSetPageHWPoison(hpage)) {  		action_result(pfn, MF_MSG_POISONED_HUGE, MF_IGNORED);  		unlock_page(hpage); -		put_page(hpage); +		put_hwpoison_page(hpage);  		return 0;  	}  	/* @@ -1420,6 +1433,22 @@ int unpoison_memory(unsigned long pfn)  		return 0;  	} +	if (page_count(page) > 1) { +		pr_info("MCE: Someone grabs the hwpoison page %#lx\n", pfn); +		return 0; +	} + +	if (page_mapped(page)) { +		pr_info("MCE: Someone maps the hwpoison page %#lx\n", pfn); +		return 0; +	} + +	if (page_mapping(page)) { +		pr_info("MCE: the hwpoison page has non-NULL mapping %#lx\n", +			pfn); +		return 0; +	} +  	/*  	 * unpoison_memory() can encounter thp only when the thp is being  	 * worked by memory_failure() and the page lock is not held yet. @@ -1444,7 +1473,7 @@ int unpoison_memory(unsigned long pfn)  			return 0;  		}  		if (TestClearPageHWPoison(p)) -			atomic_long_dec(&num_poisoned_pages); +			num_poisoned_pages_dec();  		pr_info("MCE: Software-unpoisoned free page %#lx\n", pfn);  		return 0;  	} @@ -1458,16 +1487,16 @@ int unpoison_memory(unsigned long pfn)  	 */  	if (TestClearPageHWPoison(page)) {  		pr_info("MCE: Software-unpoisoned page %#lx\n", pfn); -		atomic_long_sub(nr_pages, &num_poisoned_pages); +		num_poisoned_pages_sub(nr_pages);  		freeit = 1;  		if (PageHuge(page))  			clear_page_hwpoison_huge_page(page);  	}  	unlock_page(page); -	put_page(page); +	put_hwpoison_page(page);  	if (freeit && !(pfn == my_zero_pfn(0) && page_count(p) == 1)) -		put_page(page); +		put_hwpoison_page(page);  	return 0;  } @@ -1480,7 +1509,7 @@ static struct page *new_page(struct page *p, unsigned long private, int **x)  		return alloc_huge_page_node(page_hstate(compound_head(p)),  						   nid);  	else -		return alloc_pages_exact_node(nid, GFP_HIGHUSER_MOVABLE, 0); +		return __alloc_pages_node(nid, GFP_HIGHUSER_MOVABLE, 0);  }  /* @@ -1527,7 +1556,7 @@ static int get_any_page(struct page *page, unsigned long pfn, int flags)  		/*  		 * Try to free it.  		 */ -		put_page(page); +		put_hwpoison_page(page);  		shake_page(page, 1);  		/* @@ -1535,6 +1564,8 @@ static int get_any_page(struct page *page, unsigned long pfn, int flags)  		 */  		ret = __get_any_page(page, pfn, 0);  		if (!PageLRU(page)) { +			/* Drop page reference which is from __get_any_page() */ +			put_hwpoison_page(page);  			pr_info("soft_offline: %#lx: unknown non LRU page type %lx\n",  				pfn, page->flags);  			return -EIO; @@ -1557,20 +1588,19 @@ static int soft_offline_huge_page(struct page *page, int flags)  	lock_page(hpage);  	if (PageHWPoison(hpage)) {  		unlock_page(hpage); -		put_page(hpage); +		put_hwpoison_page(hpage);  		pr_info("soft offline: %#lx hugepage already poisoned\n", pfn);  		return -EBUSY;  	}  	unlock_page(hpage);  	ret = isolate_huge_page(hpage, &pagelist); -	if (ret) { -		/* -		 * get_any_page() and isolate_huge_page() takes a refcount each, -		 * so need to drop one here. -		 */ -		put_page(hpage); -	} else { +	/* +	 * get_any_page() and isolate_huge_page() takes a refcount each, +	 * so need to drop one here. +	 */ +	put_hwpoison_page(hpage); +	if (!ret) {  		pr_info("soft offline: %#lx hugepage failed to isolate\n", pfn);  		return -EBUSY;  	} @@ -1593,11 +1623,10 @@ static int soft_offline_huge_page(struct page *page, int flags)  		if (PageHuge(page)) {  			set_page_hwpoison_huge_page(hpage);  			dequeue_hwpoisoned_huge_page(hpage); -			atomic_long_add(1 << compound_order(hpage), -					&num_poisoned_pages); +			num_poisoned_pages_add(1 << compound_order(hpage));  		} else {  			SetPageHWPoison(page); -			atomic_long_inc(&num_poisoned_pages); +			num_poisoned_pages_inc();  		}  	}  	return ret; @@ -1618,7 +1647,7 @@ static int __soft_offline_page(struct page *page, int flags)  	wait_on_page_writeback(page);  	if (PageHWPoison(page)) {  		unlock_page(page); -		put_page(page); +		put_hwpoison_page(page);  		pr_info("soft offline: %#lx page already poisoned\n", pfn);  		return -EBUSY;  	} @@ -1633,10 +1662,10 @@ static int __soft_offline_page(struct page *page, int flags)  	 * would need to fix isolation locking first.  	 */  	if (ret == 1) { -		put_page(page); +		put_hwpoison_page(page);  		pr_info("soft_offline: %#lx: invalidated\n", pfn);  		SetPageHWPoison(page); -		atomic_long_inc(&num_poisoned_pages); +		num_poisoned_pages_inc();  		return 0;  	} @@ -1650,7 +1679,7 @@ static int __soft_offline_page(struct page *page, int flags)  	 * Drop page reference which is came from get_any_page()  	 * successful isolate_lru_page() already took another one.  	 */ -	put_page(page); +	put_hwpoison_page(page);  	if (!ret) {  		LIST_HEAD(pagelist);  		inc_zone_page_state(page, NR_ISOLATED_ANON + @@ -1670,9 +1699,6 @@ static int __soft_offline_page(struct page *page, int flags)  				pfn, ret, page->flags);  			if (ret > 0)  				ret = -EIO; -		} else { -			SetPageHWPoison(page); -			atomic_long_inc(&num_poisoned_pages);  		}  	} else {  		pr_info("soft offline: %#lx: isolation failed: %d, page count %d, type %lx\n", @@ -1711,12 +1737,16 @@ int soft_offline_page(struct page *page, int flags)  	if (PageHWPoison(page)) {  		pr_info("soft offline: %#lx page already poisoned\n", pfn); +		if (flags & MF_COUNT_INCREASED) +			put_hwpoison_page(page);  		return -EBUSY;  	}  	if (!PageHuge(page) && PageTransHuge(hpage)) {  		if (PageAnon(hpage) && unlikely(split_huge_page(hpage))) {  			pr_info("soft offline: %#lx: failed to split THP\n",  				pfn); +			if (flags & MF_COUNT_INCREASED) +				put_hwpoison_page(page);  			return -EBUSY;  		}  	} @@ -1734,11 +1764,10 @@ int soft_offline_page(struct page *page, int flags)  		if (PageHuge(page)) {  			set_page_hwpoison_huge_page(hpage);  			if (!dequeue_hwpoisoned_huge_page(hpage)) -				atomic_long_add(1 << compound_order(hpage), -					&num_poisoned_pages); +				num_poisoned_pages_add(1 << compound_order(hpage));  		} else {  			if (!TestSetPageHWPoison(page)) -				atomic_long_inc(&num_poisoned_pages); +				num_poisoned_pages_inc();  		}  	}  	return ret;  |