diff options
author | Heiko Carstens <heiko.carstens@de.ibm.com> | 2012-09-10 10:14:33 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2013-04-23 04:18:02 -0400 |
commit | c5034945ce59abacdd02c5eff29f4f54df197880 (patch) | |
tree | d27a68a9ac388e13e1af097c338a162ca647f7b0 /arch/s390/mm | |
parent | 4e4d035a928340e828f633059b735901584c67a7 (diff) |
s390/mm,gmap: implement gmap_translate()
Implement gmap_translate() function which translates a guest absolute address
to a user space process address without establishing the guest page table
entries.
This is useful for kvm guest address translations where no memory access
is expected to happen soon (e.g. tprot exception handler).
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Reviewed-by: Christian Borntraeger <borntraeger@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'arch/s390/mm')
-rw-r--r-- | arch/s390/mm/pgtable.c | 107 |
1 files changed, 85 insertions, 22 deletions
diff --git a/arch/s390/mm/pgtable.c b/arch/s390/mm/pgtable.c index ae44d2a34313..2accf7113d13 100644 --- a/arch/s390/mm/pgtable.c +++ b/arch/s390/mm/pgtable.c | |||
@@ -379,45 +379,108 @@ out_unmap: | |||
379 | } | 379 | } |
380 | EXPORT_SYMBOL_GPL(gmap_map_segment); | 380 | EXPORT_SYMBOL_GPL(gmap_map_segment); |
381 | 381 | ||
382 | static unsigned long *gmap_table_walk(unsigned long address, struct gmap *gmap) | ||
383 | { | ||
384 | unsigned long *table; | ||
385 | |||
386 | table = gmap->table + ((address >> 53) & 0x7ff); | ||
387 | if (unlikely(*table & _REGION_ENTRY_INV)) | ||
388 | return ERR_PTR(-EFAULT); | ||
389 | table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); | ||
390 | table = table + ((address >> 42) & 0x7ff); | ||
391 | if (unlikely(*table & _REGION_ENTRY_INV)) | ||
392 | return ERR_PTR(-EFAULT); | ||
393 | table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); | ||
394 | table = table + ((address >> 31) & 0x7ff); | ||
395 | if (unlikely(*table & _REGION_ENTRY_INV)) | ||
396 | return ERR_PTR(-EFAULT); | ||
397 | table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); | ||
398 | table = table + ((address >> 20) & 0x7ff); | ||
399 | return table; | ||
400 | } | ||
401 | |||
402 | /** | ||
403 | * __gmap_translate - translate a guest address to a user space address | ||
404 | * @address: guest address | ||
405 | * @gmap: pointer to guest mapping meta data structure | ||
406 | * | ||
407 | * Returns user space address which corresponds to the guest address or | ||
408 | * -EFAULT if no such mapping exists. | ||
409 | * This function does not establish potentially missing page table entries. | ||
410 | * The mmap_sem of the mm that belongs to the address space must be held | ||
411 | * when this function gets called. | ||
412 | */ | ||
413 | unsigned long __gmap_translate(unsigned long address, struct gmap *gmap) | ||
414 | { | ||
415 | unsigned long *segment_ptr, vmaddr, segment; | ||
416 | struct gmap_pgtable *mp; | ||
417 | struct page *page; | ||
418 | |||
419 | current->thread.gmap_addr = address; | ||
420 | segment_ptr = gmap_table_walk(address, gmap); | ||
421 | if (IS_ERR(segment_ptr)) | ||
422 | return PTR_ERR(segment_ptr); | ||
423 | /* Convert the gmap address to an mm address. */ | ||
424 | segment = *segment_ptr; | ||
425 | if (!(segment & _SEGMENT_ENTRY_INV)) { | ||
426 | page = pfn_to_page(segment >> PAGE_SHIFT); | ||
427 | mp = (struct gmap_pgtable *) page->index; | ||
428 | return mp->vmaddr | (address & ~PMD_MASK); | ||
429 | } else if (segment & _SEGMENT_ENTRY_RO) { | ||
430 | vmaddr = segment & _SEGMENT_ENTRY_ORIGIN; | ||
431 | return vmaddr | (address & ~PMD_MASK); | ||
432 | } | ||
433 | return -EFAULT; | ||
434 | } | ||
435 | EXPORT_SYMBOL_GPL(__gmap_translate); | ||
436 | |||
437 | /** | ||
438 | * gmap_translate - translate a guest address to a user space address | ||
439 | * @address: guest address | ||
440 | * @gmap: pointer to guest mapping meta data structure | ||
441 | * | ||
442 | * Returns user space address which corresponds to the guest address or | ||
443 | * -EFAULT if no such mapping exists. | ||
444 | * This function does not establish potentially missing page table entries. | ||
445 | */ | ||
446 | unsigned long gmap_translate(unsigned long address, struct gmap *gmap) | ||
447 | { | ||
448 | unsigned long rc; | ||
449 | |||
450 | down_read(&gmap->mm->mmap_sem); | ||
451 | rc = __gmap_translate(address, gmap); | ||
452 | up_read(&gmap->mm->mmap_sem); | ||
453 | return rc; | ||
454 | } | ||
455 | EXPORT_SYMBOL_GPL(gmap_translate); | ||
456 | |||
382 | /* | 457 | /* |
383 | * this function is assumed to be called with mmap_sem held | 458 | * this function is assumed to be called with mmap_sem held |
384 | */ | 459 | */ |
385 | unsigned long __gmap_fault(unsigned long address, struct gmap *gmap) | 460 | unsigned long __gmap_fault(unsigned long address, struct gmap *gmap) |
386 | { | 461 | { |
387 | unsigned long *table, vmaddr, segment; | 462 | unsigned long *segment_ptr, vmaddr, segment; |
388 | struct mm_struct *mm; | 463 | struct vm_area_struct *vma; |
389 | struct gmap_pgtable *mp; | 464 | struct gmap_pgtable *mp; |
390 | struct gmap_rmap *rmap; | 465 | struct gmap_rmap *rmap; |
391 | struct vm_area_struct *vma; | 466 | struct mm_struct *mm; |
392 | struct page *page; | 467 | struct page *page; |
393 | pgd_t *pgd; | 468 | pgd_t *pgd; |
394 | pud_t *pud; | 469 | pud_t *pud; |
395 | pmd_t *pmd; | 470 | pmd_t *pmd; |
396 | 471 | ||
397 | current->thread.gmap_addr = address; | 472 | current->thread.gmap_addr = address; |
398 | mm = gmap->mm; | 473 | segment_ptr = gmap_table_walk(address, gmap); |
399 | /* Walk the gmap address space page table */ | 474 | if (IS_ERR(segment_ptr)) |
400 | table = gmap->table + ((address >> 53) & 0x7ff); | ||
401 | if (unlikely(*table & _REGION_ENTRY_INV)) | ||
402 | return -EFAULT; | ||
403 | table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); | ||
404 | table = table + ((address >> 42) & 0x7ff); | ||
405 | if (unlikely(*table & _REGION_ENTRY_INV)) | ||
406 | return -EFAULT; | 475 | return -EFAULT; |
407 | table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); | ||
408 | table = table + ((address >> 31) & 0x7ff); | ||
409 | if (unlikely(*table & _REGION_ENTRY_INV)) | ||
410 | return -EFAULT; | ||
411 | table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); | ||
412 | table = table + ((address >> 20) & 0x7ff); | ||
413 | |||
414 | /* Convert the gmap address to an mm address. */ | 476 | /* Convert the gmap address to an mm address. */ |
415 | segment = *table; | 477 | segment = *segment_ptr; |
416 | if (likely(!(segment & _SEGMENT_ENTRY_INV))) { | 478 | if (!(segment & _SEGMENT_ENTRY_INV)) { |
417 | page = pfn_to_page(segment >> PAGE_SHIFT); | 479 | page = pfn_to_page(segment >> PAGE_SHIFT); |
418 | mp = (struct gmap_pgtable *) page->index; | 480 | mp = (struct gmap_pgtable *) page->index; |
419 | return mp->vmaddr | (address & ~PMD_MASK); | 481 | return mp->vmaddr | (address & ~PMD_MASK); |
420 | } else if (segment & _SEGMENT_ENTRY_RO) { | 482 | } else if (segment & _SEGMENT_ENTRY_RO) { |
483 | mm = gmap->mm; | ||
421 | vmaddr = segment & _SEGMENT_ENTRY_ORIGIN; | 484 | vmaddr = segment & _SEGMENT_ENTRY_ORIGIN; |
422 | vma = find_vma(mm, vmaddr); | 485 | vma = find_vma(mm, vmaddr); |
423 | if (!vma || vma->vm_start > vmaddr) | 486 | if (!vma || vma->vm_start > vmaddr) |
@@ -441,12 +504,12 @@ unsigned long __gmap_fault(unsigned long address, struct gmap *gmap) | |||
441 | /* Link gmap segment table entry location to page table. */ | 504 | /* Link gmap segment table entry location to page table. */ |
442 | page = pmd_page(*pmd); | 505 | page = pmd_page(*pmd); |
443 | mp = (struct gmap_pgtable *) page->index; | 506 | mp = (struct gmap_pgtable *) page->index; |
444 | rmap->entry = table; | 507 | rmap->entry = segment_ptr; |
445 | spin_lock(&mm->page_table_lock); | 508 | spin_lock(&mm->page_table_lock); |
446 | list_add(&rmap->list, &mp->mapper); | 509 | list_add(&rmap->list, &mp->mapper); |
447 | spin_unlock(&mm->page_table_lock); | 510 | spin_unlock(&mm->page_table_lock); |
448 | /* Set gmap segment table entry to page table. */ | 511 | /* Set gmap segment table entry to page table. */ |
449 | *table = pmd_val(*pmd) & PAGE_MASK; | 512 | *segment_ptr = pmd_val(*pmd) & PAGE_MASK; |
450 | return vmaddr | (address & ~PMD_MASK); | 513 | return vmaddr | (address & ~PMD_MASK); |
451 | } | 514 | } |
452 | return -EFAULT; | 515 | return -EFAULT; |