diff options
Diffstat (limited to 'arch/s390/mm/gup.c')
| -rw-r--r-- | arch/s390/mm/gup.c | 139 | 
1 files changed, 80 insertions, 59 deletions
diff --git a/arch/s390/mm/gup.c b/arch/s390/mm/gup.c index 38e641cdd97..639fce46400 100644 --- a/arch/s390/mm/gup.c +++ b/arch/s390/mm/gup.c @@ -20,18 +20,17 @@  static inline int gup_pte_range(pmd_t *pmdp, pmd_t pmd, unsigned long addr,  		unsigned long end, int write, struct page **pages, int *nr)  { -	unsigned long mask, result; +	unsigned long mask;  	pte_t *ptep, pte;  	struct page *page; -	result = write ? 0 : _PAGE_RO; -	mask = result | _PAGE_INVALID | _PAGE_SPECIAL; +	mask = (write ? _PAGE_PROTECT : 0) | _PAGE_INVALID | _PAGE_SPECIAL;  	ptep = ((pte_t *) pmd_deref(pmd)) + pte_index(addr);  	do {  		pte = *ptep;  		barrier(); -		if ((pte_val(pte) & mask) != result) +		if ((pte_val(pte) & mask) != 0)  			return 0;  		VM_BUG_ON(!pfn_valid(pte_pfn(pte)));  		page = pte_page(pte); @@ -53,11 +52,11 @@ static inline int gup_huge_pmd(pmd_t *pmdp, pmd_t pmd, unsigned long addr,  		unsigned long end, int write, struct page **pages, int *nr)  {  	unsigned long mask, result; -	struct page *head, *page; +	struct page *head, *page, *tail;  	int refs; -	result = write ? 0 : _SEGMENT_ENTRY_RO; -	mask = result | _SEGMENT_ENTRY_INV; +	result = write ? 0 : _SEGMENT_ENTRY_PROTECT; +	mask = result | _SEGMENT_ENTRY_INVALID;  	if ((pmd_val(pmd) & mask) != result)  		return 0;  	VM_BUG_ON(!pfn_valid(pmd_val(pmd) >> PAGE_SHIFT)); @@ -65,6 +64,7 @@ static inline int gup_huge_pmd(pmd_t *pmdp, pmd_t pmd, unsigned long addr,  	refs = 0;  	head = pmd_page(pmd);  	page = head + ((addr & ~PMD_MASK) >> PAGE_SHIFT); +	tail = page;  	do {  		VM_BUG_ON(compound_head(page) != head);  		pages[*nr] = page; @@ -82,6 +82,17 @@ static inline int gup_huge_pmd(pmd_t *pmdp, pmd_t pmd, unsigned long addr,  		*nr -= refs;  		while (refs--)  			put_page(head); +		return 0; +	} + +	/* +	 * Any tail page need their mapcount reference taken before we +	 * return. +	 */ +	while (refs--) { +		if (PageTail(tail)) +			get_huge_page_tail(tail); +		tail++;  	}  	return 1; @@ -104,9 +115,18 @@ static inline int gup_pmd_range(pud_t *pudp, pud_t pud, unsigned long addr,  		pmd = *pmdp;  		barrier();  		next = pmd_addr_end(addr, end); -		if (pmd_none(pmd)) +		/* +		 * The pmd_trans_splitting() check below explains why +		 * pmdp_splitting_flush() has to serialize with +		 * smp_call_function() against our disabled IRQs, to stop +		 * this gup-fast code from running while we set the +		 * splitting bit in the pmd. Returning zero will take +		 * the slow path that will call wait_split_huge_page() +		 * if the pmd is still in splitting state. +		 */ +		if (pmd_none(pmd) || pmd_trans_splitting(pmd))  			return 0; -		if (unlikely(pmd_huge(pmd))) { +		if (unlikely(pmd_large(pmd))) {  			if (!gup_huge_pmd(pmdp, pmd, addr, next,  					  write, pages, nr))  				return 0; @@ -143,28 +163,16 @@ static inline int gup_pud_range(pgd_t *pgdp, pgd_t pgd, unsigned long addr,  	return 1;  } -/** - * get_user_pages_fast() - pin user pages in memory - * @start:	starting user address - * @nr_pages:	number of pages from start to pin - * @write:	whether pages will be written to - * @pages:	array that receives pointers to the pages pinned. - *		Should be at least nr_pages long. - * - * Attempt to pin user pages in memory without taking mm->mmap_sem. - * If not successful, it will fall back to taking the lock and - * calling get_user_pages(). - * - * Returns number of pages pinned. This may be fewer than the number - * requested. If nr_pages is 0 or negative, returns 0. If no pages - * were pinned, returns -errno. +/* + * Like get_user_pages_fast() except its IRQ-safe in that it won't fall + * back to the regular GUP.   */ -int get_user_pages_fast(unsigned long start, int nr_pages, int write, -			struct page **pages) +int __get_user_pages_fast(unsigned long start, int nr_pages, int write, +			  struct page **pages)  {  	struct mm_struct *mm = current->mm;  	unsigned long addr, len, end; -	unsigned long next; +	unsigned long next, flags;  	pgd_t *pgdp, pgd;  	int nr = 0; @@ -172,54 +180,67 @@ int get_user_pages_fast(unsigned long start, int nr_pages, int write,  	addr = start;  	len = (unsigned long) nr_pages << PAGE_SHIFT;  	end = start + len; -	if (end < start) -		goto slow_irqon; - +	if ((end <= start) || (end > TASK_SIZE)) +		return 0;  	/* -	 * local_irq_disable() doesn't prevent pagetable teardown, but does +	 * local_irq_save() doesn't prevent pagetable teardown, but does  	 * prevent the pagetables from being freed on s390.  	 *  	 * So long as we atomically load page table pointers versus teardown,  	 * we can follow the address down to the the page and take a ref on it.  	 */ -	local_irq_disable(); +	local_irq_save(flags);  	pgdp = pgd_offset(mm, addr);  	do {  		pgd = *pgdp;  		barrier();  		next = pgd_addr_end(addr, end);  		if (pgd_none(pgd)) -			goto slow; +			break;  		if (!gup_pud_range(pgdp, pgd, addr, next, write, pages, &nr)) -			goto slow; +			break;  	} while (pgdp++, addr = next, addr != end); -	local_irq_enable(); +	local_irq_restore(flags); -	VM_BUG_ON(nr != (end - start) >> PAGE_SHIFT);  	return nr; +} -	{ -		int ret; -slow: -		local_irq_enable(); -slow_irqon: -		/* Try to get the remaining pages with get_user_pages */ -		start += nr << PAGE_SHIFT; -		pages += nr; - -		down_read(&mm->mmap_sem); -		ret = get_user_pages(current, mm, start, -			(end - start) >> PAGE_SHIFT, write, 0, pages, NULL); -		up_read(&mm->mmap_sem); - -		/* Have to be a bit careful with return values */ -		if (nr > 0) { -			if (ret < 0) -				ret = nr; -			else -				ret += nr; -		} +/** + * get_user_pages_fast() - pin user pages in memory + * @start:	starting user address + * @nr_pages:	number of pages from start to pin + * @write:	whether pages will be written to + * @pages:	array that receives pointers to the pages pinned. + *		Should be at least nr_pages long. + * + * Attempt to pin user pages in memory without taking mm->mmap_sem. + * If not successful, it will fall back to taking the lock and + * calling get_user_pages(). + * + * Returns number of pages pinned. This may be fewer than the number + * requested. If nr_pages is 0 or negative, returns 0. If no pages + * were pinned, returns -errno. + */ +int get_user_pages_fast(unsigned long start, int nr_pages, int write, +			struct page **pages) +{ +	struct mm_struct *mm = current->mm; +	int nr, ret; -		return ret; -	} +	start &= PAGE_MASK; +	nr = __get_user_pages_fast(start, nr_pages, write, pages); +	if (nr == nr_pages) +		return nr; + +	/* Try to get the remaining pages with get_user_pages */ +	start += nr << PAGE_SHIFT; +	pages += nr; +	down_read(&mm->mmap_sem); +	ret = get_user_pages(current, mm, start, +			     nr_pages - nr, write, 0, pages, NULL); +	up_read(&mm->mmap_sem); +	/* Have to be a bit careful with return values */ +	if (nr > 0) +		ret = (ret < 0) ? nr : ret + nr; +	return ret;  }  | 
