diff options
Diffstat (limited to 'arch/s390/mm/pgtable.c')
| -rw-r--r-- | arch/s390/mm/pgtable.c | 233 | 
1 files changed, 159 insertions, 74 deletions
diff --git a/arch/s390/mm/pgtable.c b/arch/s390/mm/pgtable.c index ae44d2a3431..bd954e96f51 100644 --- a/arch/s390/mm/pgtable.c +++ b/arch/s390/mm/pgtable.c @@ -379,75 +379,183 @@ out_unmap:  }  EXPORT_SYMBOL_GPL(gmap_map_segment); -/* - * this function is assumed to be called with mmap_sem held - */ -unsigned long __gmap_fault(unsigned long address, struct gmap *gmap) +static unsigned long *gmap_table_walk(unsigned long address, struct gmap *gmap)  { -	unsigned long *table, vmaddr, segment; -	struct mm_struct *mm; -	struct gmap_pgtable *mp; -	struct gmap_rmap *rmap; -	struct vm_area_struct *vma; -	struct page *page; -	pgd_t *pgd; -	pud_t *pud; -	pmd_t *pmd; +	unsigned long *table; -	current->thread.gmap_addr = address; -	mm = gmap->mm; -	/* Walk the gmap address space page table */  	table = gmap->table + ((address >> 53) & 0x7ff);  	if (unlikely(*table & _REGION_ENTRY_INV)) -		return -EFAULT; +		return ERR_PTR(-EFAULT);  	table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);  	table = table + ((address >> 42) & 0x7ff);  	if (unlikely(*table & _REGION_ENTRY_INV)) -		return -EFAULT; +		return ERR_PTR(-EFAULT);  	table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);  	table = table + ((address >> 31) & 0x7ff);  	if (unlikely(*table & _REGION_ENTRY_INV)) -		return -EFAULT; +		return ERR_PTR(-EFAULT);  	table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);  	table = table + ((address >> 20) & 0x7ff); +	return table; +} + +/** + * __gmap_translate - translate a guest address to a user space address + * @address: guest address + * @gmap: pointer to guest mapping meta data structure + * + * Returns user space address which corresponds to the guest address or + * -EFAULT if no such mapping exists. + * This function does not establish potentially missing page table entries. + * The mmap_sem of the mm that belongs to the address space must be held + * when this function gets called. + */ +unsigned long __gmap_translate(unsigned long address, struct gmap *gmap) +{ +	unsigned long *segment_ptr, vmaddr, segment; +	struct gmap_pgtable *mp; +	struct page *page; +	current->thread.gmap_addr = address; +	segment_ptr = gmap_table_walk(address, gmap); +	if (IS_ERR(segment_ptr)) +		return PTR_ERR(segment_ptr);  	/* Convert the gmap address to an mm address. */ -	segment = *table; -	if (likely(!(segment & _SEGMENT_ENTRY_INV))) { +	segment = *segment_ptr; +	if (!(segment & _SEGMENT_ENTRY_INV)) {  		page = pfn_to_page(segment >> PAGE_SHIFT);  		mp = (struct gmap_pgtable *) page->index;  		return mp->vmaddr | (address & ~PMD_MASK);  	} else if (segment & _SEGMENT_ENTRY_RO) {  		vmaddr = segment & _SEGMENT_ENTRY_ORIGIN; -		vma = find_vma(mm, vmaddr); -		if (!vma || vma->vm_start > vmaddr) -			return -EFAULT; +		return vmaddr | (address & ~PMD_MASK); +	} +	return -EFAULT; +} +EXPORT_SYMBOL_GPL(__gmap_translate); -		/* Walk the parent mm page table */ -		pgd = pgd_offset(mm, vmaddr); -		pud = pud_alloc(mm, pgd, vmaddr); -		if (!pud) -			return -ENOMEM; -		pmd = pmd_alloc(mm, pud, vmaddr); -		if (!pmd) -			return -ENOMEM; -		if (!pmd_present(*pmd) && -		    __pte_alloc(mm, vma, pmd, vmaddr)) -			return -ENOMEM; -		/* pmd now points to a valid segment table entry. */ -		rmap = kmalloc(sizeof(*rmap), GFP_KERNEL|__GFP_REPEAT); -		if (!rmap) -			return -ENOMEM; -		/* Link gmap segment table entry location to page table. */ -		page = pmd_page(*pmd); -		mp = (struct gmap_pgtable *) page->index; -		rmap->entry = table; -		spin_lock(&mm->page_table_lock); +/** + * gmap_translate - translate a guest address to a user space address + * @address: guest address + * @gmap: pointer to guest mapping meta data structure + * + * Returns user space address which corresponds to the guest address or + * -EFAULT if no such mapping exists. + * This function does not establish potentially missing page table entries. + */ +unsigned long gmap_translate(unsigned long address, struct gmap *gmap) +{ +	unsigned long rc; + +	down_read(&gmap->mm->mmap_sem); +	rc = __gmap_translate(address, gmap); +	up_read(&gmap->mm->mmap_sem); +	return rc; +} +EXPORT_SYMBOL_GPL(gmap_translate); + +static int gmap_connect_pgtable(unsigned long segment, +				unsigned long *segment_ptr, +				struct gmap *gmap) +{ +	unsigned long vmaddr; +	struct vm_area_struct *vma; +	struct gmap_pgtable *mp; +	struct gmap_rmap *rmap; +	struct mm_struct *mm; +	struct page *page; +	pgd_t *pgd; +	pud_t *pud; +	pmd_t *pmd; + +	mm = gmap->mm; +	vmaddr = segment & _SEGMENT_ENTRY_ORIGIN; +	vma = find_vma(mm, vmaddr); +	if (!vma || vma->vm_start > vmaddr) +		return -EFAULT; +	/* Walk the parent mm page table */ +	pgd = pgd_offset(mm, vmaddr); +	pud = pud_alloc(mm, pgd, vmaddr); +	if (!pud) +		return -ENOMEM; +	pmd = pmd_alloc(mm, pud, vmaddr); +	if (!pmd) +		return -ENOMEM; +	if (!pmd_present(*pmd) && +	    __pte_alloc(mm, vma, pmd, vmaddr)) +		return -ENOMEM; +	/* pmd now points to a valid segment table entry. */ +	rmap = kmalloc(sizeof(*rmap), GFP_KERNEL|__GFP_REPEAT); +	if (!rmap) +		return -ENOMEM; +	/* Link gmap segment table entry location to page table. */ +	page = pmd_page(*pmd); +	mp = (struct gmap_pgtable *) page->index; +	rmap->entry = segment_ptr; +	spin_lock(&mm->page_table_lock); +	if (*segment_ptr == segment) {  		list_add(&rmap->list, &mp->mapper); -		spin_unlock(&mm->page_table_lock);  		/* Set gmap segment table entry to page table. */ -		*table = pmd_val(*pmd) & PAGE_MASK; -		return vmaddr | (address & ~PMD_MASK); +		*segment_ptr = pmd_val(*pmd) & PAGE_MASK; +		rmap = NULL; +	} +	spin_unlock(&mm->page_table_lock); +	kfree(rmap); +	return 0; +} + +static void gmap_disconnect_pgtable(struct mm_struct *mm, unsigned long *table) +{ +	struct gmap_rmap *rmap, *next; +	struct gmap_pgtable *mp; +	struct page *page; +	int flush; + +	flush = 0; +	spin_lock(&mm->page_table_lock); +	page = pfn_to_page(__pa(table) >> PAGE_SHIFT); +	mp = (struct gmap_pgtable *) page->index; +	list_for_each_entry_safe(rmap, next, &mp->mapper, list) { +		*rmap->entry = +			_SEGMENT_ENTRY_INV | _SEGMENT_ENTRY_RO | mp->vmaddr; +		list_del(&rmap->list); +		kfree(rmap); +		flush = 1; +	} +	spin_unlock(&mm->page_table_lock); +	if (flush) +		__tlb_flush_global(); +} + +/* + * this function is assumed to be called with mmap_sem held + */ +unsigned long __gmap_fault(unsigned long address, struct gmap *gmap) +{ +	unsigned long *segment_ptr, segment; +	struct gmap_pgtable *mp; +	struct page *page; +	int rc; + +	current->thread.gmap_addr = address; +	segment_ptr = gmap_table_walk(address, gmap); +	if (IS_ERR(segment_ptr)) +		return -EFAULT; +	/* Convert the gmap address to an mm address. */ +	while (1) { +		segment = *segment_ptr; +		if (!(segment & _SEGMENT_ENTRY_INV)) { +			/* Page table is present */ +			page = pfn_to_page(segment >> PAGE_SHIFT); +			mp = (struct gmap_pgtable *) page->index; +			return mp->vmaddr | (address & ~PMD_MASK); +		} +		if (!(segment & _SEGMENT_ENTRY_RO)) +			/* Nothing mapped in the gmap address space. */ +			break; +		rc = gmap_connect_pgtable(segment, segment_ptr, gmap); +		if (rc) +			return rc;  	}  	return -EFAULT;  } @@ -511,29 +619,6 @@ void gmap_discard(unsigned long from, unsigned long to, struct gmap *gmap)  }  EXPORT_SYMBOL_GPL(gmap_discard); -void gmap_unmap_notifier(struct mm_struct *mm, unsigned long *table) -{ -	struct gmap_rmap *rmap, *next; -	struct gmap_pgtable *mp; -	struct page *page; -	int flush; - -	flush = 0; -	spin_lock(&mm->page_table_lock); -	page = pfn_to_page(__pa(table) >> PAGE_SHIFT); -	mp = (struct gmap_pgtable *) page->index; -	list_for_each_entry_safe(rmap, next, &mp->mapper, list) { -		*rmap->entry = -			_SEGMENT_ENTRY_INV | _SEGMENT_ENTRY_RO | mp->vmaddr; -		list_del(&rmap->list); -		kfree(rmap); -		flush = 1; -	} -	spin_unlock(&mm->page_table_lock); -	if (flush) -		__tlb_flush_global(); -} -  static inline unsigned long *page_table_alloc_pgste(struct mm_struct *mm,  						    unsigned long vmaddr)  { @@ -586,8 +671,8 @@ static inline void page_table_free_pgste(unsigned long *table)  {  } -static inline void gmap_unmap_notifier(struct mm_struct *mm, -					  unsigned long *table) +static inline void gmap_disconnect_pgtable(struct mm_struct *mm, +					   unsigned long *table)  {  } @@ -653,7 +738,7 @@ void page_table_free(struct mm_struct *mm, unsigned long *table)  	unsigned int bit, mask;  	if (mm_has_pgste(mm)) { -		gmap_unmap_notifier(mm, table); +		gmap_disconnect_pgtable(mm, table);  		return page_table_free_pgste(table);  	}  	/* Free 1K/2K page table fragment of a 4K page */ @@ -696,7 +781,7 @@ void page_table_free_rcu(struct mmu_gather *tlb, unsigned long *table)  	mm = tlb->mm;  	if (mm_has_pgste(mm)) { -		gmap_unmap_notifier(mm, table); +		gmap_disconnect_pgtable(mm, table);  		table = (unsigned long *) (__pa(table) | FRAG_MASK);  		tlb_remove_table(tlb, table);  		return;  |