summaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
Diffstat (limited to 'arch')
-rw-r--r--arch/sparc64/mm/tsb.c43
1 files changed, 35 insertions, 8 deletions
diff --git a/arch/sparc64/mm/tsb.c b/arch/sparc64/mm/tsb.c
index 7fbe1e0cd105..3eb8670282fd 100644
--- a/arch/sparc64/mm/tsb.c
+++ b/arch/sparc64/mm/tsb.c
@@ -216,7 +216,8 @@ static void setup_tsb_params(struct mm_struct *mm, unsigned long tsb_bytes)
*
* The TSB can be anywhere from 8K to 1MB in size, in increasing powers
* of two. The TSB must be aligned to it's size, so f.e. a 512K TSB
- * must be 512K aligned.
+ * must be 512K aligned. It also must be physically contiguous, so we
+ * cannot use vmalloc().
*
* The idea here is to grow the TSB when the RSS of the process approaches
* the number of entries that the current TSB can hold at once. Currently,
@@ -228,6 +229,8 @@ void tsb_grow(struct mm_struct *mm, unsigned long rss)
unsigned long size, old_size, flags;
struct page *page;
struct tsb *old_tsb, *new_tsb;
+ unsigned long order, new_rss_limit;
+ gfp_t gfp_flags;
if (max_tsb_size > (PAGE_SIZE << MAX_ORDER))
max_tsb_size = (PAGE_SIZE << MAX_ORDER);
@@ -240,9 +243,37 @@ void tsb_grow(struct mm_struct *mm, unsigned long rss)
break;
}
- page = alloc_pages(GFP_KERNEL, get_order(size));
- if (unlikely(!page))
+ if (size == max_tsb_size)
+ new_rss_limit = ~0UL;
+ else
+ new_rss_limit = ((size / sizeof(struct tsb)) * 3) / 4;
+
+retry_page_alloc:
+ order = get_order(size);
+ gfp_flags = GFP_KERNEL;
+ if (order > 1)
+ gfp_flags = __GFP_NOWARN | __GFP_NORETRY;
+
+ page = alloc_pages(gfp_flags, order);
+ if (unlikely(!page)) {
+ /* Not being able to fork due to a high-order TSB
+ * allocation failure is very bad behavior. Just back
+ * down to a 0-order allocation and force no TSB
+ * growing for this address space.
+ */
+ if (mm->context.tsb == NULL && order > 0) {
+ size = PAGE_SIZE;
+ new_rss_limit = ~0UL;
+ goto retry_page_alloc;
+ }
+
+ /* If we failed on a TSB grow, we are under serious
+ * memory pressure so don't try to grow any more.
+ */
+ if (mm->context.tsb != NULL)
+ mm->context.tsb_rss_limit = ~0UL;
return;
+ }
/* Mark all tags as invalid. */
new_tsb = page_address(page);
@@ -286,11 +317,7 @@ void tsb_grow(struct mm_struct *mm, unsigned long rss)
return;
}
- if (size == max_tsb_size)
- mm->context.tsb_rss_limit = ~0UL;
- else
- mm->context.tsb_rss_limit =
- ((size / sizeof(struct tsb)) * 3) / 4;
+ mm->context.tsb_rss_limit = new_rss_limit;
if (old_tsb) {
extern void copy_tsb(unsigned long old_tsb_base,