18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * KVM guest address space mapping code 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright IBM Corp. 2007, 2016, 2018 68c2ecf20Sopenharmony_ci * Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com> 78c2ecf20Sopenharmony_ci * David Hildenbrand <david@redhat.com> 88c2ecf20Sopenharmony_ci * Janosch Frank <frankja@linux.vnet.ibm.com> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/pagewalk.h> 138c2ecf20Sopenharmony_ci#include <linux/swap.h> 148c2ecf20Sopenharmony_ci#include <linux/smp.h> 158c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/swapops.h> 188c2ecf20Sopenharmony_ci#include <linux/ksm.h> 198c2ecf20Sopenharmony_ci#include <linux/mman.h> 208c2ecf20Sopenharmony_ci#include <linux/pgtable.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include <asm/pgalloc.h> 238c2ecf20Sopenharmony_ci#include <asm/gmap.h> 248c2ecf20Sopenharmony_ci#include <asm/tlb.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define GMAP_SHADOW_FAKE_TABLE 1ULL 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/** 298c2ecf20Sopenharmony_ci * gmap_alloc - allocate and initialize a guest address space 308c2ecf20Sopenharmony_ci * @mm: pointer to the parent mm_struct 318c2ecf20Sopenharmony_ci * @limit: maximum address of the gmap address space 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * Returns a guest address space structure. 348c2ecf20Sopenharmony_ci */ 358c2ecf20Sopenharmony_cistatic struct gmap *gmap_alloc(unsigned long limit) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci struct gmap *gmap; 388c2ecf20Sopenharmony_ci struct page *page; 398c2ecf20Sopenharmony_ci unsigned long *table; 408c2ecf20Sopenharmony_ci unsigned long etype, atype; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci if (limit < _REGION3_SIZE) { 438c2ecf20Sopenharmony_ci limit = _REGION3_SIZE - 1; 448c2ecf20Sopenharmony_ci atype = _ASCE_TYPE_SEGMENT; 458c2ecf20Sopenharmony_ci etype = _SEGMENT_ENTRY_EMPTY; 468c2ecf20Sopenharmony_ci } else if (limit < _REGION2_SIZE) { 478c2ecf20Sopenharmony_ci limit = _REGION2_SIZE - 1; 488c2ecf20Sopenharmony_ci atype = _ASCE_TYPE_REGION3; 498c2ecf20Sopenharmony_ci etype = _REGION3_ENTRY_EMPTY; 508c2ecf20Sopenharmony_ci } else if (limit < _REGION1_SIZE) { 518c2ecf20Sopenharmony_ci limit = _REGION1_SIZE - 1; 528c2ecf20Sopenharmony_ci atype = _ASCE_TYPE_REGION2; 538c2ecf20Sopenharmony_ci etype = _REGION2_ENTRY_EMPTY; 548c2ecf20Sopenharmony_ci } else { 558c2ecf20Sopenharmony_ci limit = -1UL; 568c2ecf20Sopenharmony_ci atype = _ASCE_TYPE_REGION1; 578c2ecf20Sopenharmony_ci etype = _REGION1_ENTRY_EMPTY; 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci gmap = kzalloc(sizeof(struct gmap), GFP_KERNEL); 608c2ecf20Sopenharmony_ci if (!gmap) 618c2ecf20Sopenharmony_ci goto out; 628c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&gmap->crst_list); 638c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&gmap->children); 648c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&gmap->pt_list); 658c2ecf20Sopenharmony_ci INIT_RADIX_TREE(&gmap->guest_to_host, GFP_KERNEL); 668c2ecf20Sopenharmony_ci INIT_RADIX_TREE(&gmap->host_to_guest, GFP_ATOMIC); 678c2ecf20Sopenharmony_ci INIT_RADIX_TREE(&gmap->host_to_rmap, GFP_ATOMIC); 688c2ecf20Sopenharmony_ci spin_lock_init(&gmap->guest_table_lock); 698c2ecf20Sopenharmony_ci spin_lock_init(&gmap->shadow_lock); 708c2ecf20Sopenharmony_ci refcount_set(&gmap->ref_count, 1); 718c2ecf20Sopenharmony_ci page = alloc_pages(GFP_KERNEL, CRST_ALLOC_ORDER); 728c2ecf20Sopenharmony_ci if (!page) 738c2ecf20Sopenharmony_ci goto out_free; 748c2ecf20Sopenharmony_ci page->index = 0; 758c2ecf20Sopenharmony_ci list_add(&page->lru, &gmap->crst_list); 768c2ecf20Sopenharmony_ci table = (unsigned long *) page_to_phys(page); 778c2ecf20Sopenharmony_ci crst_table_init(table, etype); 788c2ecf20Sopenharmony_ci gmap->table = table; 798c2ecf20Sopenharmony_ci gmap->asce = atype | _ASCE_TABLE_LENGTH | 808c2ecf20Sopenharmony_ci _ASCE_USER_BITS | __pa(table); 818c2ecf20Sopenharmony_ci gmap->asce_end = limit; 828c2ecf20Sopenharmony_ci return gmap; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ciout_free: 858c2ecf20Sopenharmony_ci kfree(gmap); 868c2ecf20Sopenharmony_ciout: 878c2ecf20Sopenharmony_ci return NULL; 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci/** 918c2ecf20Sopenharmony_ci * gmap_create - create a guest address space 928c2ecf20Sopenharmony_ci * @mm: pointer to the parent mm_struct 938c2ecf20Sopenharmony_ci * @limit: maximum size of the gmap address space 948c2ecf20Sopenharmony_ci * 958c2ecf20Sopenharmony_ci * Returns a guest address space structure. 968c2ecf20Sopenharmony_ci */ 978c2ecf20Sopenharmony_cistruct gmap *gmap_create(struct mm_struct *mm, unsigned long limit) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci struct gmap *gmap; 1008c2ecf20Sopenharmony_ci unsigned long gmap_asce; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci gmap = gmap_alloc(limit); 1038c2ecf20Sopenharmony_ci if (!gmap) 1048c2ecf20Sopenharmony_ci return NULL; 1058c2ecf20Sopenharmony_ci gmap->mm = mm; 1068c2ecf20Sopenharmony_ci spin_lock(&mm->context.lock); 1078c2ecf20Sopenharmony_ci list_add_rcu(&gmap->list, &mm->context.gmap_list); 1088c2ecf20Sopenharmony_ci if (list_is_singular(&mm->context.gmap_list)) 1098c2ecf20Sopenharmony_ci gmap_asce = gmap->asce; 1108c2ecf20Sopenharmony_ci else 1118c2ecf20Sopenharmony_ci gmap_asce = -1UL; 1128c2ecf20Sopenharmony_ci WRITE_ONCE(mm->context.gmap_asce, gmap_asce); 1138c2ecf20Sopenharmony_ci spin_unlock(&mm->context.lock); 1148c2ecf20Sopenharmony_ci return gmap; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_create); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic void gmap_flush_tlb(struct gmap *gmap) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci if (MACHINE_HAS_IDTE) 1218c2ecf20Sopenharmony_ci __tlb_flush_idte(gmap->asce); 1228c2ecf20Sopenharmony_ci else 1238c2ecf20Sopenharmony_ci __tlb_flush_global(); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic void gmap_radix_tree_free(struct radix_tree_root *root) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci struct radix_tree_iter iter; 1298c2ecf20Sopenharmony_ci unsigned long indices[16]; 1308c2ecf20Sopenharmony_ci unsigned long index; 1318c2ecf20Sopenharmony_ci void __rcu **slot; 1328c2ecf20Sopenharmony_ci int i, nr; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci /* A radix tree is freed by deleting all of its entries */ 1358c2ecf20Sopenharmony_ci index = 0; 1368c2ecf20Sopenharmony_ci do { 1378c2ecf20Sopenharmony_ci nr = 0; 1388c2ecf20Sopenharmony_ci radix_tree_for_each_slot(slot, root, &iter, index) { 1398c2ecf20Sopenharmony_ci indices[nr] = iter.index; 1408c2ecf20Sopenharmony_ci if (++nr == 16) 1418c2ecf20Sopenharmony_ci break; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci for (i = 0; i < nr; i++) { 1448c2ecf20Sopenharmony_ci index = indices[i]; 1458c2ecf20Sopenharmony_ci radix_tree_delete(root, index); 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci } while (nr > 0); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic void gmap_rmap_radix_tree_free(struct radix_tree_root *root) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci struct gmap_rmap *rmap, *rnext, *head; 1538c2ecf20Sopenharmony_ci struct radix_tree_iter iter; 1548c2ecf20Sopenharmony_ci unsigned long indices[16]; 1558c2ecf20Sopenharmony_ci unsigned long index; 1568c2ecf20Sopenharmony_ci void __rcu **slot; 1578c2ecf20Sopenharmony_ci int i, nr; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* A radix tree is freed by deleting all of its entries */ 1608c2ecf20Sopenharmony_ci index = 0; 1618c2ecf20Sopenharmony_ci do { 1628c2ecf20Sopenharmony_ci nr = 0; 1638c2ecf20Sopenharmony_ci radix_tree_for_each_slot(slot, root, &iter, index) { 1648c2ecf20Sopenharmony_ci indices[nr] = iter.index; 1658c2ecf20Sopenharmony_ci if (++nr == 16) 1668c2ecf20Sopenharmony_ci break; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci for (i = 0; i < nr; i++) { 1698c2ecf20Sopenharmony_ci index = indices[i]; 1708c2ecf20Sopenharmony_ci head = radix_tree_delete(root, index); 1718c2ecf20Sopenharmony_ci gmap_for_each_rmap_safe(rmap, rnext, head) 1728c2ecf20Sopenharmony_ci kfree(rmap); 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci } while (nr > 0); 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci/** 1788c2ecf20Sopenharmony_ci * gmap_free - free a guest address space 1798c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 1808c2ecf20Sopenharmony_ci * 1818c2ecf20Sopenharmony_ci * No locks required. There are no references to this gmap anymore. 1828c2ecf20Sopenharmony_ci */ 1838c2ecf20Sopenharmony_cistatic void gmap_free(struct gmap *gmap) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci struct page *page, *next; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci /* Flush tlb of all gmaps (if not already done for shadows) */ 1888c2ecf20Sopenharmony_ci if (!(gmap_is_shadow(gmap) && gmap->removed)) 1898c2ecf20Sopenharmony_ci gmap_flush_tlb(gmap); 1908c2ecf20Sopenharmony_ci /* Free all segment & region tables. */ 1918c2ecf20Sopenharmony_ci list_for_each_entry_safe(page, next, &gmap->crst_list, lru) 1928c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 1938c2ecf20Sopenharmony_ci gmap_radix_tree_free(&gmap->guest_to_host); 1948c2ecf20Sopenharmony_ci gmap_radix_tree_free(&gmap->host_to_guest); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* Free additional data for a shadow gmap */ 1978c2ecf20Sopenharmony_ci if (gmap_is_shadow(gmap)) { 1988c2ecf20Sopenharmony_ci /* Free all page tables. */ 1998c2ecf20Sopenharmony_ci list_for_each_entry_safe(page, next, &gmap->pt_list, lru) 2008c2ecf20Sopenharmony_ci page_table_free_pgste(page); 2018c2ecf20Sopenharmony_ci gmap_rmap_radix_tree_free(&gmap->host_to_rmap); 2028c2ecf20Sopenharmony_ci /* Release reference to the parent */ 2038c2ecf20Sopenharmony_ci gmap_put(gmap->parent); 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci kfree(gmap); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci/** 2108c2ecf20Sopenharmony_ci * gmap_get - increase reference counter for guest address space 2118c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 2128c2ecf20Sopenharmony_ci * 2138c2ecf20Sopenharmony_ci * Returns the gmap pointer 2148c2ecf20Sopenharmony_ci */ 2158c2ecf20Sopenharmony_cistruct gmap *gmap_get(struct gmap *gmap) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci refcount_inc(&gmap->ref_count); 2188c2ecf20Sopenharmony_ci return gmap; 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_get); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci/** 2238c2ecf20Sopenharmony_ci * gmap_put - decrease reference counter for guest address space 2248c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 2258c2ecf20Sopenharmony_ci * 2268c2ecf20Sopenharmony_ci * If the reference counter reaches zero the guest address space is freed. 2278c2ecf20Sopenharmony_ci */ 2288c2ecf20Sopenharmony_civoid gmap_put(struct gmap *gmap) 2298c2ecf20Sopenharmony_ci{ 2308c2ecf20Sopenharmony_ci if (refcount_dec_and_test(&gmap->ref_count)) 2318c2ecf20Sopenharmony_ci gmap_free(gmap); 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_put); 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci/** 2368c2ecf20Sopenharmony_ci * gmap_remove - remove a guest address space but do not free it yet 2378c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_civoid gmap_remove(struct gmap *gmap) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct gmap *sg, *next; 2428c2ecf20Sopenharmony_ci unsigned long gmap_asce; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci /* Remove all shadow gmaps linked to this gmap */ 2458c2ecf20Sopenharmony_ci if (!list_empty(&gmap->children)) { 2468c2ecf20Sopenharmony_ci spin_lock(&gmap->shadow_lock); 2478c2ecf20Sopenharmony_ci list_for_each_entry_safe(sg, next, &gmap->children, list) { 2488c2ecf20Sopenharmony_ci list_del(&sg->list); 2498c2ecf20Sopenharmony_ci gmap_put(sg); 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci spin_unlock(&gmap->shadow_lock); 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci /* Remove gmap from the pre-mm list */ 2548c2ecf20Sopenharmony_ci spin_lock(&gmap->mm->context.lock); 2558c2ecf20Sopenharmony_ci list_del_rcu(&gmap->list); 2568c2ecf20Sopenharmony_ci if (list_empty(&gmap->mm->context.gmap_list)) 2578c2ecf20Sopenharmony_ci gmap_asce = 0; 2588c2ecf20Sopenharmony_ci else if (list_is_singular(&gmap->mm->context.gmap_list)) 2598c2ecf20Sopenharmony_ci gmap_asce = list_first_entry(&gmap->mm->context.gmap_list, 2608c2ecf20Sopenharmony_ci struct gmap, list)->asce; 2618c2ecf20Sopenharmony_ci else 2628c2ecf20Sopenharmony_ci gmap_asce = -1UL; 2638c2ecf20Sopenharmony_ci WRITE_ONCE(gmap->mm->context.gmap_asce, gmap_asce); 2648c2ecf20Sopenharmony_ci spin_unlock(&gmap->mm->context.lock); 2658c2ecf20Sopenharmony_ci synchronize_rcu(); 2668c2ecf20Sopenharmony_ci /* Put reference */ 2678c2ecf20Sopenharmony_ci gmap_put(gmap); 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_remove); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci/** 2728c2ecf20Sopenharmony_ci * gmap_enable - switch primary space to the guest address space 2738c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 2748c2ecf20Sopenharmony_ci */ 2758c2ecf20Sopenharmony_civoid gmap_enable(struct gmap *gmap) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci S390_lowcore.gmap = (unsigned long) gmap; 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_enable); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci/** 2828c2ecf20Sopenharmony_ci * gmap_disable - switch back to the standard primary address space 2838c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 2848c2ecf20Sopenharmony_ci */ 2858c2ecf20Sopenharmony_civoid gmap_disable(struct gmap *gmap) 2868c2ecf20Sopenharmony_ci{ 2878c2ecf20Sopenharmony_ci S390_lowcore.gmap = 0UL; 2888c2ecf20Sopenharmony_ci} 2898c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_disable); 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci/** 2928c2ecf20Sopenharmony_ci * gmap_get_enabled - get a pointer to the currently enabled gmap 2938c2ecf20Sopenharmony_ci * 2948c2ecf20Sopenharmony_ci * Returns a pointer to the currently enabled gmap. 0 if none is enabled. 2958c2ecf20Sopenharmony_ci */ 2968c2ecf20Sopenharmony_cistruct gmap *gmap_get_enabled(void) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci return (struct gmap *) S390_lowcore.gmap; 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_get_enabled); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci/* 3038c2ecf20Sopenharmony_ci * gmap_alloc_table is assumed to be called with mmap_lock held 3048c2ecf20Sopenharmony_ci */ 3058c2ecf20Sopenharmony_cistatic int gmap_alloc_table(struct gmap *gmap, unsigned long *table, 3068c2ecf20Sopenharmony_ci unsigned long init, unsigned long gaddr) 3078c2ecf20Sopenharmony_ci{ 3088c2ecf20Sopenharmony_ci struct page *page; 3098c2ecf20Sopenharmony_ci unsigned long *new; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci /* since we dont free the gmap table until gmap_free we can unlock */ 3128c2ecf20Sopenharmony_ci page = alloc_pages(GFP_KERNEL, CRST_ALLOC_ORDER); 3138c2ecf20Sopenharmony_ci if (!page) 3148c2ecf20Sopenharmony_ci return -ENOMEM; 3158c2ecf20Sopenharmony_ci new = (unsigned long *) page_to_phys(page); 3168c2ecf20Sopenharmony_ci crst_table_init(new, init); 3178c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 3188c2ecf20Sopenharmony_ci if (*table & _REGION_ENTRY_INVALID) { 3198c2ecf20Sopenharmony_ci list_add(&page->lru, &gmap->crst_list); 3208c2ecf20Sopenharmony_ci *table = (unsigned long) new | _REGION_ENTRY_LENGTH | 3218c2ecf20Sopenharmony_ci (*table & _REGION_ENTRY_TYPE_MASK); 3228c2ecf20Sopenharmony_ci page->index = gaddr; 3238c2ecf20Sopenharmony_ci page = NULL; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 3268c2ecf20Sopenharmony_ci if (page) 3278c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 3288c2ecf20Sopenharmony_ci return 0; 3298c2ecf20Sopenharmony_ci} 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci/** 3328c2ecf20Sopenharmony_ci * __gmap_segment_gaddr - find virtual address from segment pointer 3338c2ecf20Sopenharmony_ci * @entry: pointer to a segment table entry in the guest address space 3348c2ecf20Sopenharmony_ci * 3358c2ecf20Sopenharmony_ci * Returns the virtual address in the guest address space for the segment 3368c2ecf20Sopenharmony_ci */ 3378c2ecf20Sopenharmony_cistatic unsigned long __gmap_segment_gaddr(unsigned long *entry) 3388c2ecf20Sopenharmony_ci{ 3398c2ecf20Sopenharmony_ci struct page *page; 3408c2ecf20Sopenharmony_ci unsigned long offset, mask; 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci offset = (unsigned long) entry / sizeof(unsigned long); 3438c2ecf20Sopenharmony_ci offset = (offset & (PTRS_PER_PMD - 1)) * PMD_SIZE; 3448c2ecf20Sopenharmony_ci mask = ~(PTRS_PER_PMD * sizeof(pmd_t) - 1); 3458c2ecf20Sopenharmony_ci page = virt_to_page((void *)((unsigned long) entry & mask)); 3468c2ecf20Sopenharmony_ci return page->index + offset; 3478c2ecf20Sopenharmony_ci} 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci/** 3508c2ecf20Sopenharmony_ci * __gmap_unlink_by_vmaddr - unlink a single segment via a host address 3518c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 3528c2ecf20Sopenharmony_ci * @vmaddr: address in the host process address space 3538c2ecf20Sopenharmony_ci * 3548c2ecf20Sopenharmony_ci * Returns 1 if a TLB flush is required 3558c2ecf20Sopenharmony_ci */ 3568c2ecf20Sopenharmony_cistatic int __gmap_unlink_by_vmaddr(struct gmap *gmap, unsigned long vmaddr) 3578c2ecf20Sopenharmony_ci{ 3588c2ecf20Sopenharmony_ci unsigned long *entry; 3598c2ecf20Sopenharmony_ci int flush = 0; 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 3628c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 3638c2ecf20Sopenharmony_ci entry = radix_tree_delete(&gmap->host_to_guest, vmaddr >> PMD_SHIFT); 3648c2ecf20Sopenharmony_ci if (entry) { 3658c2ecf20Sopenharmony_ci flush = (*entry != _SEGMENT_ENTRY_EMPTY); 3668c2ecf20Sopenharmony_ci *entry = _SEGMENT_ENTRY_EMPTY; 3678c2ecf20Sopenharmony_ci } 3688c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 3698c2ecf20Sopenharmony_ci return flush; 3708c2ecf20Sopenharmony_ci} 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci/** 3738c2ecf20Sopenharmony_ci * __gmap_unmap_by_gaddr - unmap a single segment via a guest address 3748c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 3758c2ecf20Sopenharmony_ci * @gaddr: address in the guest address space 3768c2ecf20Sopenharmony_ci * 3778c2ecf20Sopenharmony_ci * Returns 1 if a TLB flush is required 3788c2ecf20Sopenharmony_ci */ 3798c2ecf20Sopenharmony_cistatic int __gmap_unmap_by_gaddr(struct gmap *gmap, unsigned long gaddr) 3808c2ecf20Sopenharmony_ci{ 3818c2ecf20Sopenharmony_ci unsigned long vmaddr; 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci vmaddr = (unsigned long) radix_tree_delete(&gmap->guest_to_host, 3848c2ecf20Sopenharmony_ci gaddr >> PMD_SHIFT); 3858c2ecf20Sopenharmony_ci return vmaddr ? __gmap_unlink_by_vmaddr(gmap, vmaddr) : 0; 3868c2ecf20Sopenharmony_ci} 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci/** 3898c2ecf20Sopenharmony_ci * gmap_unmap_segment - unmap segment from the guest address space 3908c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 3918c2ecf20Sopenharmony_ci * @to: address in the guest address space 3928c2ecf20Sopenharmony_ci * @len: length of the memory area to unmap 3938c2ecf20Sopenharmony_ci * 3948c2ecf20Sopenharmony_ci * Returns 0 if the unmap succeeded, -EINVAL if not. 3958c2ecf20Sopenharmony_ci */ 3968c2ecf20Sopenharmony_ciint gmap_unmap_segment(struct gmap *gmap, unsigned long to, unsigned long len) 3978c2ecf20Sopenharmony_ci{ 3988c2ecf20Sopenharmony_ci unsigned long off; 3998c2ecf20Sopenharmony_ci int flush; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 4028c2ecf20Sopenharmony_ci if ((to | len) & (PMD_SIZE - 1)) 4038c2ecf20Sopenharmony_ci return -EINVAL; 4048c2ecf20Sopenharmony_ci if (len == 0 || to + len < to) 4058c2ecf20Sopenharmony_ci return -EINVAL; 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci flush = 0; 4088c2ecf20Sopenharmony_ci mmap_write_lock(gmap->mm); 4098c2ecf20Sopenharmony_ci for (off = 0; off < len; off += PMD_SIZE) 4108c2ecf20Sopenharmony_ci flush |= __gmap_unmap_by_gaddr(gmap, to + off); 4118c2ecf20Sopenharmony_ci mmap_write_unlock(gmap->mm); 4128c2ecf20Sopenharmony_ci if (flush) 4138c2ecf20Sopenharmony_ci gmap_flush_tlb(gmap); 4148c2ecf20Sopenharmony_ci return 0; 4158c2ecf20Sopenharmony_ci} 4168c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_unmap_segment); 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci/** 4198c2ecf20Sopenharmony_ci * gmap_map_segment - map a segment to the guest address space 4208c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 4218c2ecf20Sopenharmony_ci * @from: source address in the parent address space 4228c2ecf20Sopenharmony_ci * @to: target address in the guest address space 4238c2ecf20Sopenharmony_ci * @len: length of the memory area to map 4248c2ecf20Sopenharmony_ci * 4258c2ecf20Sopenharmony_ci * Returns 0 if the mmap succeeded, -EINVAL or -ENOMEM if not. 4268c2ecf20Sopenharmony_ci */ 4278c2ecf20Sopenharmony_ciint gmap_map_segment(struct gmap *gmap, unsigned long from, 4288c2ecf20Sopenharmony_ci unsigned long to, unsigned long len) 4298c2ecf20Sopenharmony_ci{ 4308c2ecf20Sopenharmony_ci unsigned long off; 4318c2ecf20Sopenharmony_ci int flush; 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 4348c2ecf20Sopenharmony_ci if ((from | to | len) & (PMD_SIZE - 1)) 4358c2ecf20Sopenharmony_ci return -EINVAL; 4368c2ecf20Sopenharmony_ci if (len == 0 || from + len < from || to + len < to || 4378c2ecf20Sopenharmony_ci from + len - 1 > TASK_SIZE_MAX || to + len - 1 > gmap->asce_end) 4388c2ecf20Sopenharmony_ci return -EINVAL; 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci flush = 0; 4418c2ecf20Sopenharmony_ci mmap_write_lock(gmap->mm); 4428c2ecf20Sopenharmony_ci for (off = 0; off < len; off += PMD_SIZE) { 4438c2ecf20Sopenharmony_ci /* Remove old translation */ 4448c2ecf20Sopenharmony_ci flush |= __gmap_unmap_by_gaddr(gmap, to + off); 4458c2ecf20Sopenharmony_ci /* Store new translation */ 4468c2ecf20Sopenharmony_ci if (radix_tree_insert(&gmap->guest_to_host, 4478c2ecf20Sopenharmony_ci (to + off) >> PMD_SHIFT, 4488c2ecf20Sopenharmony_ci (void *) from + off)) 4498c2ecf20Sopenharmony_ci break; 4508c2ecf20Sopenharmony_ci } 4518c2ecf20Sopenharmony_ci mmap_write_unlock(gmap->mm); 4528c2ecf20Sopenharmony_ci if (flush) 4538c2ecf20Sopenharmony_ci gmap_flush_tlb(gmap); 4548c2ecf20Sopenharmony_ci if (off >= len) 4558c2ecf20Sopenharmony_ci return 0; 4568c2ecf20Sopenharmony_ci gmap_unmap_segment(gmap, to, len); 4578c2ecf20Sopenharmony_ci return -ENOMEM; 4588c2ecf20Sopenharmony_ci} 4598c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_map_segment); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci/** 4628c2ecf20Sopenharmony_ci * __gmap_translate - translate a guest address to a user space address 4638c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 4648c2ecf20Sopenharmony_ci * @gaddr: guest address 4658c2ecf20Sopenharmony_ci * 4668c2ecf20Sopenharmony_ci * Returns user space address which corresponds to the guest address or 4678c2ecf20Sopenharmony_ci * -EFAULT if no such mapping exists. 4688c2ecf20Sopenharmony_ci * This function does not establish potentially missing page table entries. 4698c2ecf20Sopenharmony_ci * The mmap_lock of the mm that belongs to the address space must be held 4708c2ecf20Sopenharmony_ci * when this function gets called. 4718c2ecf20Sopenharmony_ci * 4728c2ecf20Sopenharmony_ci * Note: Can also be called for shadow gmaps. 4738c2ecf20Sopenharmony_ci */ 4748c2ecf20Sopenharmony_ciunsigned long __gmap_translate(struct gmap *gmap, unsigned long gaddr) 4758c2ecf20Sopenharmony_ci{ 4768c2ecf20Sopenharmony_ci unsigned long vmaddr; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci vmaddr = (unsigned long) 4798c2ecf20Sopenharmony_ci radix_tree_lookup(&gmap->guest_to_host, gaddr >> PMD_SHIFT); 4808c2ecf20Sopenharmony_ci /* Note: guest_to_host is empty for a shadow gmap */ 4818c2ecf20Sopenharmony_ci return vmaddr ? (vmaddr | (gaddr & ~PMD_MASK)) : -EFAULT; 4828c2ecf20Sopenharmony_ci} 4838c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(__gmap_translate); 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci/** 4868c2ecf20Sopenharmony_ci * gmap_translate - translate a guest address to a user space address 4878c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 4888c2ecf20Sopenharmony_ci * @gaddr: guest address 4898c2ecf20Sopenharmony_ci * 4908c2ecf20Sopenharmony_ci * Returns user space address which corresponds to the guest address or 4918c2ecf20Sopenharmony_ci * -EFAULT if no such mapping exists. 4928c2ecf20Sopenharmony_ci * This function does not establish potentially missing page table entries. 4938c2ecf20Sopenharmony_ci */ 4948c2ecf20Sopenharmony_ciunsigned long gmap_translate(struct gmap *gmap, unsigned long gaddr) 4958c2ecf20Sopenharmony_ci{ 4968c2ecf20Sopenharmony_ci unsigned long rc; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci mmap_read_lock(gmap->mm); 4998c2ecf20Sopenharmony_ci rc = __gmap_translate(gmap, gaddr); 5008c2ecf20Sopenharmony_ci mmap_read_unlock(gmap->mm); 5018c2ecf20Sopenharmony_ci return rc; 5028c2ecf20Sopenharmony_ci} 5038c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_translate); 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci/** 5068c2ecf20Sopenharmony_ci * gmap_unlink - disconnect a page table from the gmap shadow tables 5078c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 5088c2ecf20Sopenharmony_ci * @table: pointer to the host page table 5098c2ecf20Sopenharmony_ci * @vmaddr: vm address associated with the host page table 5108c2ecf20Sopenharmony_ci */ 5118c2ecf20Sopenharmony_civoid gmap_unlink(struct mm_struct *mm, unsigned long *table, 5128c2ecf20Sopenharmony_ci unsigned long vmaddr) 5138c2ecf20Sopenharmony_ci{ 5148c2ecf20Sopenharmony_ci struct gmap *gmap; 5158c2ecf20Sopenharmony_ci int flush; 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci rcu_read_lock(); 5188c2ecf20Sopenharmony_ci list_for_each_entry_rcu(gmap, &mm->context.gmap_list, list) { 5198c2ecf20Sopenharmony_ci flush = __gmap_unlink_by_vmaddr(gmap, vmaddr); 5208c2ecf20Sopenharmony_ci if (flush) 5218c2ecf20Sopenharmony_ci gmap_flush_tlb(gmap); 5228c2ecf20Sopenharmony_ci } 5238c2ecf20Sopenharmony_ci rcu_read_unlock(); 5248c2ecf20Sopenharmony_ci} 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_cistatic void gmap_pmdp_xchg(struct gmap *gmap, pmd_t *old, pmd_t new, 5278c2ecf20Sopenharmony_ci unsigned long gaddr); 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci/** 5308c2ecf20Sopenharmony_ci * gmap_link - set up shadow page tables to connect a host to a guest address 5318c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 5328c2ecf20Sopenharmony_ci * @gaddr: guest address 5338c2ecf20Sopenharmony_ci * @vmaddr: vm address 5348c2ecf20Sopenharmony_ci * 5358c2ecf20Sopenharmony_ci * Returns 0 on success, -ENOMEM for out of memory conditions, and -EFAULT 5368c2ecf20Sopenharmony_ci * if the vm address is already mapped to a different guest segment. 5378c2ecf20Sopenharmony_ci * The mmap_lock of the mm that belongs to the address space must be held 5388c2ecf20Sopenharmony_ci * when this function gets called. 5398c2ecf20Sopenharmony_ci */ 5408c2ecf20Sopenharmony_ciint __gmap_link(struct gmap *gmap, unsigned long gaddr, unsigned long vmaddr) 5418c2ecf20Sopenharmony_ci{ 5428c2ecf20Sopenharmony_ci struct mm_struct *mm; 5438c2ecf20Sopenharmony_ci unsigned long *table; 5448c2ecf20Sopenharmony_ci spinlock_t *ptl; 5458c2ecf20Sopenharmony_ci pgd_t *pgd; 5468c2ecf20Sopenharmony_ci p4d_t *p4d; 5478c2ecf20Sopenharmony_ci pud_t *pud; 5488c2ecf20Sopenharmony_ci pmd_t *pmd; 5498c2ecf20Sopenharmony_ci u64 unprot; 5508c2ecf20Sopenharmony_ci int rc; 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 5538c2ecf20Sopenharmony_ci /* Create higher level tables in the gmap page table */ 5548c2ecf20Sopenharmony_ci table = gmap->table; 5558c2ecf20Sopenharmony_ci if ((gmap->asce & _ASCE_TYPE_MASK) >= _ASCE_TYPE_REGION1) { 5568c2ecf20Sopenharmony_ci table += (gaddr & _REGION1_INDEX) >> _REGION1_SHIFT; 5578c2ecf20Sopenharmony_ci if ((*table & _REGION_ENTRY_INVALID) && 5588c2ecf20Sopenharmony_ci gmap_alloc_table(gmap, table, _REGION2_ENTRY_EMPTY, 5598c2ecf20Sopenharmony_ci gaddr & _REGION1_MASK)) 5608c2ecf20Sopenharmony_ci return -ENOMEM; 5618c2ecf20Sopenharmony_ci table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); 5628c2ecf20Sopenharmony_ci } 5638c2ecf20Sopenharmony_ci if ((gmap->asce & _ASCE_TYPE_MASK) >= _ASCE_TYPE_REGION2) { 5648c2ecf20Sopenharmony_ci table += (gaddr & _REGION2_INDEX) >> _REGION2_SHIFT; 5658c2ecf20Sopenharmony_ci if ((*table & _REGION_ENTRY_INVALID) && 5668c2ecf20Sopenharmony_ci gmap_alloc_table(gmap, table, _REGION3_ENTRY_EMPTY, 5678c2ecf20Sopenharmony_ci gaddr & _REGION2_MASK)) 5688c2ecf20Sopenharmony_ci return -ENOMEM; 5698c2ecf20Sopenharmony_ci table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); 5708c2ecf20Sopenharmony_ci } 5718c2ecf20Sopenharmony_ci if ((gmap->asce & _ASCE_TYPE_MASK) >= _ASCE_TYPE_REGION3) { 5728c2ecf20Sopenharmony_ci table += (gaddr & _REGION3_INDEX) >> _REGION3_SHIFT; 5738c2ecf20Sopenharmony_ci if ((*table & _REGION_ENTRY_INVALID) && 5748c2ecf20Sopenharmony_ci gmap_alloc_table(gmap, table, _SEGMENT_ENTRY_EMPTY, 5758c2ecf20Sopenharmony_ci gaddr & _REGION3_MASK)) 5768c2ecf20Sopenharmony_ci return -ENOMEM; 5778c2ecf20Sopenharmony_ci table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); 5788c2ecf20Sopenharmony_ci } 5798c2ecf20Sopenharmony_ci table += (gaddr & _SEGMENT_INDEX) >> _SEGMENT_SHIFT; 5808c2ecf20Sopenharmony_ci /* Walk the parent mm page table */ 5818c2ecf20Sopenharmony_ci mm = gmap->mm; 5828c2ecf20Sopenharmony_ci pgd = pgd_offset(mm, vmaddr); 5838c2ecf20Sopenharmony_ci VM_BUG_ON(pgd_none(*pgd)); 5848c2ecf20Sopenharmony_ci p4d = p4d_offset(pgd, vmaddr); 5858c2ecf20Sopenharmony_ci VM_BUG_ON(p4d_none(*p4d)); 5868c2ecf20Sopenharmony_ci pud = pud_offset(p4d, vmaddr); 5878c2ecf20Sopenharmony_ci VM_BUG_ON(pud_none(*pud)); 5888c2ecf20Sopenharmony_ci /* large puds cannot yet be handled */ 5898c2ecf20Sopenharmony_ci if (pud_large(*pud)) 5908c2ecf20Sopenharmony_ci return -EFAULT; 5918c2ecf20Sopenharmony_ci pmd = pmd_offset(pud, vmaddr); 5928c2ecf20Sopenharmony_ci VM_BUG_ON(pmd_none(*pmd)); 5938c2ecf20Sopenharmony_ci /* Are we allowed to use huge pages? */ 5948c2ecf20Sopenharmony_ci if (pmd_large(*pmd) && !gmap->mm->context.allow_gmap_hpage_1m) 5958c2ecf20Sopenharmony_ci return -EFAULT; 5968c2ecf20Sopenharmony_ci /* Link gmap segment table entry location to page table. */ 5978c2ecf20Sopenharmony_ci rc = radix_tree_preload(GFP_KERNEL); 5988c2ecf20Sopenharmony_ci if (rc) 5998c2ecf20Sopenharmony_ci return rc; 6008c2ecf20Sopenharmony_ci ptl = pmd_lock(mm, pmd); 6018c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 6028c2ecf20Sopenharmony_ci if (*table == _SEGMENT_ENTRY_EMPTY) { 6038c2ecf20Sopenharmony_ci rc = radix_tree_insert(&gmap->host_to_guest, 6048c2ecf20Sopenharmony_ci vmaddr >> PMD_SHIFT, table); 6058c2ecf20Sopenharmony_ci if (!rc) { 6068c2ecf20Sopenharmony_ci if (pmd_large(*pmd)) { 6078c2ecf20Sopenharmony_ci *table = (pmd_val(*pmd) & 6088c2ecf20Sopenharmony_ci _SEGMENT_ENTRY_HARDWARE_BITS_LARGE) 6098c2ecf20Sopenharmony_ci | _SEGMENT_ENTRY_GMAP_UC; 6108c2ecf20Sopenharmony_ci } else 6118c2ecf20Sopenharmony_ci *table = pmd_val(*pmd) & 6128c2ecf20Sopenharmony_ci _SEGMENT_ENTRY_HARDWARE_BITS; 6138c2ecf20Sopenharmony_ci } 6148c2ecf20Sopenharmony_ci } else if (*table & _SEGMENT_ENTRY_PROTECT && 6158c2ecf20Sopenharmony_ci !(pmd_val(*pmd) & _SEGMENT_ENTRY_PROTECT)) { 6168c2ecf20Sopenharmony_ci unprot = (u64)*table; 6178c2ecf20Sopenharmony_ci unprot &= ~_SEGMENT_ENTRY_PROTECT; 6188c2ecf20Sopenharmony_ci unprot |= _SEGMENT_ENTRY_GMAP_UC; 6198c2ecf20Sopenharmony_ci gmap_pmdp_xchg(gmap, (pmd_t *)table, __pmd(unprot), gaddr); 6208c2ecf20Sopenharmony_ci } 6218c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 6228c2ecf20Sopenharmony_ci spin_unlock(ptl); 6238c2ecf20Sopenharmony_ci radix_tree_preload_end(); 6248c2ecf20Sopenharmony_ci return rc; 6258c2ecf20Sopenharmony_ci} 6268c2ecf20Sopenharmony_ci 6278c2ecf20Sopenharmony_ci/** 6288c2ecf20Sopenharmony_ci * gmap_fault - resolve a fault on a guest address 6298c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 6308c2ecf20Sopenharmony_ci * @gaddr: guest address 6318c2ecf20Sopenharmony_ci * @fault_flags: flags to pass down to handle_mm_fault() 6328c2ecf20Sopenharmony_ci * 6338c2ecf20Sopenharmony_ci * Returns 0 on success, -ENOMEM for out of memory conditions, and -EFAULT 6348c2ecf20Sopenharmony_ci * if the vm address is already mapped to a different guest segment. 6358c2ecf20Sopenharmony_ci */ 6368c2ecf20Sopenharmony_ciint gmap_fault(struct gmap *gmap, unsigned long gaddr, 6378c2ecf20Sopenharmony_ci unsigned int fault_flags) 6388c2ecf20Sopenharmony_ci{ 6398c2ecf20Sopenharmony_ci unsigned long vmaddr; 6408c2ecf20Sopenharmony_ci int rc; 6418c2ecf20Sopenharmony_ci bool unlocked; 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_ci mmap_read_lock(gmap->mm); 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_ciretry: 6468c2ecf20Sopenharmony_ci unlocked = false; 6478c2ecf20Sopenharmony_ci vmaddr = __gmap_translate(gmap, gaddr); 6488c2ecf20Sopenharmony_ci if (IS_ERR_VALUE(vmaddr)) { 6498c2ecf20Sopenharmony_ci rc = vmaddr; 6508c2ecf20Sopenharmony_ci goto out_up; 6518c2ecf20Sopenharmony_ci } 6528c2ecf20Sopenharmony_ci if (fixup_user_fault(gmap->mm, vmaddr, fault_flags, 6538c2ecf20Sopenharmony_ci &unlocked)) { 6548c2ecf20Sopenharmony_ci rc = -EFAULT; 6558c2ecf20Sopenharmony_ci goto out_up; 6568c2ecf20Sopenharmony_ci } 6578c2ecf20Sopenharmony_ci /* 6588c2ecf20Sopenharmony_ci * In the case that fixup_user_fault unlocked the mmap_lock during 6598c2ecf20Sopenharmony_ci * faultin redo __gmap_translate to not race with a map/unmap_segment. 6608c2ecf20Sopenharmony_ci */ 6618c2ecf20Sopenharmony_ci if (unlocked) 6628c2ecf20Sopenharmony_ci goto retry; 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_ci rc = __gmap_link(gmap, gaddr, vmaddr); 6658c2ecf20Sopenharmony_ciout_up: 6668c2ecf20Sopenharmony_ci mmap_read_unlock(gmap->mm); 6678c2ecf20Sopenharmony_ci return rc; 6688c2ecf20Sopenharmony_ci} 6698c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_fault); 6708c2ecf20Sopenharmony_ci 6718c2ecf20Sopenharmony_ci/* 6728c2ecf20Sopenharmony_ci * this function is assumed to be called with mmap_lock held 6738c2ecf20Sopenharmony_ci */ 6748c2ecf20Sopenharmony_civoid __gmap_zap(struct gmap *gmap, unsigned long gaddr) 6758c2ecf20Sopenharmony_ci{ 6768c2ecf20Sopenharmony_ci unsigned long vmaddr; 6778c2ecf20Sopenharmony_ci spinlock_t *ptl; 6788c2ecf20Sopenharmony_ci pte_t *ptep; 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_ci /* Find the vm address for the guest address */ 6818c2ecf20Sopenharmony_ci vmaddr = (unsigned long) radix_tree_lookup(&gmap->guest_to_host, 6828c2ecf20Sopenharmony_ci gaddr >> PMD_SHIFT); 6838c2ecf20Sopenharmony_ci if (vmaddr) { 6848c2ecf20Sopenharmony_ci vmaddr |= gaddr & ~PMD_MASK; 6858c2ecf20Sopenharmony_ci /* Get pointer to the page table entry */ 6868c2ecf20Sopenharmony_ci ptep = get_locked_pte(gmap->mm, vmaddr, &ptl); 6878c2ecf20Sopenharmony_ci if (likely(ptep)) { 6888c2ecf20Sopenharmony_ci ptep_zap_unused(gmap->mm, vmaddr, ptep, 0); 6898c2ecf20Sopenharmony_ci pte_unmap_unlock(ptep, ptl); 6908c2ecf20Sopenharmony_ci } 6918c2ecf20Sopenharmony_ci } 6928c2ecf20Sopenharmony_ci} 6938c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(__gmap_zap); 6948c2ecf20Sopenharmony_ci 6958c2ecf20Sopenharmony_civoid gmap_discard(struct gmap *gmap, unsigned long from, unsigned long to) 6968c2ecf20Sopenharmony_ci{ 6978c2ecf20Sopenharmony_ci unsigned long gaddr, vmaddr, size; 6988c2ecf20Sopenharmony_ci struct vm_area_struct *vma; 6998c2ecf20Sopenharmony_ci 7008c2ecf20Sopenharmony_ci mmap_read_lock(gmap->mm); 7018c2ecf20Sopenharmony_ci for (gaddr = from; gaddr < to; 7028c2ecf20Sopenharmony_ci gaddr = (gaddr + PMD_SIZE) & PMD_MASK) { 7038c2ecf20Sopenharmony_ci /* Find the vm address for the guest address */ 7048c2ecf20Sopenharmony_ci vmaddr = (unsigned long) 7058c2ecf20Sopenharmony_ci radix_tree_lookup(&gmap->guest_to_host, 7068c2ecf20Sopenharmony_ci gaddr >> PMD_SHIFT); 7078c2ecf20Sopenharmony_ci if (!vmaddr) 7088c2ecf20Sopenharmony_ci continue; 7098c2ecf20Sopenharmony_ci vmaddr |= gaddr & ~PMD_MASK; 7108c2ecf20Sopenharmony_ci /* Find vma in the parent mm */ 7118c2ecf20Sopenharmony_ci vma = find_vma(gmap->mm, vmaddr); 7128c2ecf20Sopenharmony_ci if (!vma) 7138c2ecf20Sopenharmony_ci continue; 7148c2ecf20Sopenharmony_ci /* 7158c2ecf20Sopenharmony_ci * We do not discard pages that are backed by 7168c2ecf20Sopenharmony_ci * hugetlbfs, so we don't have to refault them. 7178c2ecf20Sopenharmony_ci */ 7188c2ecf20Sopenharmony_ci if (is_vm_hugetlb_page(vma)) 7198c2ecf20Sopenharmony_ci continue; 7208c2ecf20Sopenharmony_ci size = min(to - gaddr, PMD_SIZE - (gaddr & ~PMD_MASK)); 7218c2ecf20Sopenharmony_ci zap_page_range(vma, vmaddr, size); 7228c2ecf20Sopenharmony_ci } 7238c2ecf20Sopenharmony_ci mmap_read_unlock(gmap->mm); 7248c2ecf20Sopenharmony_ci} 7258c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_discard); 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_cistatic LIST_HEAD(gmap_notifier_list); 7288c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(gmap_notifier_lock); 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_ci/** 7318c2ecf20Sopenharmony_ci * gmap_register_pte_notifier - register a pte invalidation callback 7328c2ecf20Sopenharmony_ci * @nb: pointer to the gmap notifier block 7338c2ecf20Sopenharmony_ci */ 7348c2ecf20Sopenharmony_civoid gmap_register_pte_notifier(struct gmap_notifier *nb) 7358c2ecf20Sopenharmony_ci{ 7368c2ecf20Sopenharmony_ci spin_lock(&gmap_notifier_lock); 7378c2ecf20Sopenharmony_ci list_add_rcu(&nb->list, &gmap_notifier_list); 7388c2ecf20Sopenharmony_ci spin_unlock(&gmap_notifier_lock); 7398c2ecf20Sopenharmony_ci} 7408c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_register_pte_notifier); 7418c2ecf20Sopenharmony_ci 7428c2ecf20Sopenharmony_ci/** 7438c2ecf20Sopenharmony_ci * gmap_unregister_pte_notifier - remove a pte invalidation callback 7448c2ecf20Sopenharmony_ci * @nb: pointer to the gmap notifier block 7458c2ecf20Sopenharmony_ci */ 7468c2ecf20Sopenharmony_civoid gmap_unregister_pte_notifier(struct gmap_notifier *nb) 7478c2ecf20Sopenharmony_ci{ 7488c2ecf20Sopenharmony_ci spin_lock(&gmap_notifier_lock); 7498c2ecf20Sopenharmony_ci list_del_rcu(&nb->list); 7508c2ecf20Sopenharmony_ci spin_unlock(&gmap_notifier_lock); 7518c2ecf20Sopenharmony_ci synchronize_rcu(); 7528c2ecf20Sopenharmony_ci} 7538c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_unregister_pte_notifier); 7548c2ecf20Sopenharmony_ci 7558c2ecf20Sopenharmony_ci/** 7568c2ecf20Sopenharmony_ci * gmap_call_notifier - call all registered invalidation callbacks 7578c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 7588c2ecf20Sopenharmony_ci * @start: start virtual address in the guest address space 7598c2ecf20Sopenharmony_ci * @end: end virtual address in the guest address space 7608c2ecf20Sopenharmony_ci */ 7618c2ecf20Sopenharmony_cistatic void gmap_call_notifier(struct gmap *gmap, unsigned long start, 7628c2ecf20Sopenharmony_ci unsigned long end) 7638c2ecf20Sopenharmony_ci{ 7648c2ecf20Sopenharmony_ci struct gmap_notifier *nb; 7658c2ecf20Sopenharmony_ci 7668c2ecf20Sopenharmony_ci list_for_each_entry(nb, &gmap_notifier_list, list) 7678c2ecf20Sopenharmony_ci nb->notifier_call(gmap, start, end); 7688c2ecf20Sopenharmony_ci} 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_ci/** 7718c2ecf20Sopenharmony_ci * gmap_table_walk - walk the gmap page tables 7728c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 7738c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 7748c2ecf20Sopenharmony_ci * @level: page table level to stop at 7758c2ecf20Sopenharmony_ci * 7768c2ecf20Sopenharmony_ci * Returns a table entry pointer for the given guest address and @level 7778c2ecf20Sopenharmony_ci * @level=0 : returns a pointer to a page table table entry (or NULL) 7788c2ecf20Sopenharmony_ci * @level=1 : returns a pointer to a segment table entry (or NULL) 7798c2ecf20Sopenharmony_ci * @level=2 : returns a pointer to a region-3 table entry (or NULL) 7808c2ecf20Sopenharmony_ci * @level=3 : returns a pointer to a region-2 table entry (or NULL) 7818c2ecf20Sopenharmony_ci * @level=4 : returns a pointer to a region-1 table entry (or NULL) 7828c2ecf20Sopenharmony_ci * 7838c2ecf20Sopenharmony_ci * Returns NULL if the gmap page tables could not be walked to the 7848c2ecf20Sopenharmony_ci * requested level. 7858c2ecf20Sopenharmony_ci * 7868c2ecf20Sopenharmony_ci * Note: Can also be called for shadow gmaps. 7878c2ecf20Sopenharmony_ci */ 7888c2ecf20Sopenharmony_cistatic inline unsigned long *gmap_table_walk(struct gmap *gmap, 7898c2ecf20Sopenharmony_ci unsigned long gaddr, int level) 7908c2ecf20Sopenharmony_ci{ 7918c2ecf20Sopenharmony_ci const int asce_type = gmap->asce & _ASCE_TYPE_MASK; 7928c2ecf20Sopenharmony_ci unsigned long *table = gmap->table; 7938c2ecf20Sopenharmony_ci 7948c2ecf20Sopenharmony_ci if (gmap_is_shadow(gmap) && gmap->removed) 7958c2ecf20Sopenharmony_ci return NULL; 7968c2ecf20Sopenharmony_ci 7978c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(level > (asce_type >> 2) + 1)) 7988c2ecf20Sopenharmony_ci return NULL; 7998c2ecf20Sopenharmony_ci 8008c2ecf20Sopenharmony_ci if (asce_type != _ASCE_TYPE_REGION1 && 8018c2ecf20Sopenharmony_ci gaddr & (-1UL << (31 + (asce_type >> 2) * 11))) 8028c2ecf20Sopenharmony_ci return NULL; 8038c2ecf20Sopenharmony_ci 8048c2ecf20Sopenharmony_ci switch (asce_type) { 8058c2ecf20Sopenharmony_ci case _ASCE_TYPE_REGION1: 8068c2ecf20Sopenharmony_ci table += (gaddr & _REGION1_INDEX) >> _REGION1_SHIFT; 8078c2ecf20Sopenharmony_ci if (level == 4) 8088c2ecf20Sopenharmony_ci break; 8098c2ecf20Sopenharmony_ci if (*table & _REGION_ENTRY_INVALID) 8108c2ecf20Sopenharmony_ci return NULL; 8118c2ecf20Sopenharmony_ci table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); 8128c2ecf20Sopenharmony_ci fallthrough; 8138c2ecf20Sopenharmony_ci case _ASCE_TYPE_REGION2: 8148c2ecf20Sopenharmony_ci table += (gaddr & _REGION2_INDEX) >> _REGION2_SHIFT; 8158c2ecf20Sopenharmony_ci if (level == 3) 8168c2ecf20Sopenharmony_ci break; 8178c2ecf20Sopenharmony_ci if (*table & _REGION_ENTRY_INVALID) 8188c2ecf20Sopenharmony_ci return NULL; 8198c2ecf20Sopenharmony_ci table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); 8208c2ecf20Sopenharmony_ci fallthrough; 8218c2ecf20Sopenharmony_ci case _ASCE_TYPE_REGION3: 8228c2ecf20Sopenharmony_ci table += (gaddr & _REGION3_INDEX) >> _REGION3_SHIFT; 8238c2ecf20Sopenharmony_ci if (level == 2) 8248c2ecf20Sopenharmony_ci break; 8258c2ecf20Sopenharmony_ci if (*table & _REGION_ENTRY_INVALID) 8268c2ecf20Sopenharmony_ci return NULL; 8278c2ecf20Sopenharmony_ci table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN); 8288c2ecf20Sopenharmony_ci fallthrough; 8298c2ecf20Sopenharmony_ci case _ASCE_TYPE_SEGMENT: 8308c2ecf20Sopenharmony_ci table += (gaddr & _SEGMENT_INDEX) >> _SEGMENT_SHIFT; 8318c2ecf20Sopenharmony_ci if (level == 1) 8328c2ecf20Sopenharmony_ci break; 8338c2ecf20Sopenharmony_ci if (*table & _REGION_ENTRY_INVALID) 8348c2ecf20Sopenharmony_ci return NULL; 8358c2ecf20Sopenharmony_ci table = (unsigned long *)(*table & _SEGMENT_ENTRY_ORIGIN); 8368c2ecf20Sopenharmony_ci table += (gaddr & _PAGE_INDEX) >> _PAGE_SHIFT; 8378c2ecf20Sopenharmony_ci } 8388c2ecf20Sopenharmony_ci return table; 8398c2ecf20Sopenharmony_ci} 8408c2ecf20Sopenharmony_ci 8418c2ecf20Sopenharmony_ci/** 8428c2ecf20Sopenharmony_ci * gmap_pte_op_walk - walk the gmap page table, get the page table lock 8438c2ecf20Sopenharmony_ci * and return the pte pointer 8448c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 8458c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 8468c2ecf20Sopenharmony_ci * @ptl: pointer to the spinlock pointer 8478c2ecf20Sopenharmony_ci * 8488c2ecf20Sopenharmony_ci * Returns a pointer to the locked pte for a guest address, or NULL 8498c2ecf20Sopenharmony_ci */ 8508c2ecf20Sopenharmony_cistatic pte_t *gmap_pte_op_walk(struct gmap *gmap, unsigned long gaddr, 8518c2ecf20Sopenharmony_ci spinlock_t **ptl) 8528c2ecf20Sopenharmony_ci{ 8538c2ecf20Sopenharmony_ci unsigned long *table; 8548c2ecf20Sopenharmony_ci 8558c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 8568c2ecf20Sopenharmony_ci /* Walk the gmap page table, lock and get pte pointer */ 8578c2ecf20Sopenharmony_ci table = gmap_table_walk(gmap, gaddr, 1); /* get segment pointer */ 8588c2ecf20Sopenharmony_ci if (!table || *table & _SEGMENT_ENTRY_INVALID) 8598c2ecf20Sopenharmony_ci return NULL; 8608c2ecf20Sopenharmony_ci return pte_alloc_map_lock(gmap->mm, (pmd_t *) table, gaddr, ptl); 8618c2ecf20Sopenharmony_ci} 8628c2ecf20Sopenharmony_ci 8638c2ecf20Sopenharmony_ci/** 8648c2ecf20Sopenharmony_ci * gmap_pte_op_fixup - force a page in and connect the gmap page table 8658c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 8668c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 8678c2ecf20Sopenharmony_ci * @vmaddr: address in the host process address space 8688c2ecf20Sopenharmony_ci * @prot: indicates access rights: PROT_NONE, PROT_READ or PROT_WRITE 8698c2ecf20Sopenharmony_ci * 8708c2ecf20Sopenharmony_ci * Returns 0 if the caller can retry __gmap_translate (might fail again), 8718c2ecf20Sopenharmony_ci * -ENOMEM if out of memory and -EFAULT if anything goes wrong while fixing 8728c2ecf20Sopenharmony_ci * up or connecting the gmap page table. 8738c2ecf20Sopenharmony_ci */ 8748c2ecf20Sopenharmony_cistatic int gmap_pte_op_fixup(struct gmap *gmap, unsigned long gaddr, 8758c2ecf20Sopenharmony_ci unsigned long vmaddr, int prot) 8768c2ecf20Sopenharmony_ci{ 8778c2ecf20Sopenharmony_ci struct mm_struct *mm = gmap->mm; 8788c2ecf20Sopenharmony_ci unsigned int fault_flags; 8798c2ecf20Sopenharmony_ci bool unlocked = false; 8808c2ecf20Sopenharmony_ci 8818c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 8828c2ecf20Sopenharmony_ci fault_flags = (prot == PROT_WRITE) ? FAULT_FLAG_WRITE : 0; 8838c2ecf20Sopenharmony_ci if (fixup_user_fault(mm, vmaddr, fault_flags, &unlocked)) 8848c2ecf20Sopenharmony_ci return -EFAULT; 8858c2ecf20Sopenharmony_ci if (unlocked) 8868c2ecf20Sopenharmony_ci /* lost mmap_lock, caller has to retry __gmap_translate */ 8878c2ecf20Sopenharmony_ci return 0; 8888c2ecf20Sopenharmony_ci /* Connect the page tables */ 8898c2ecf20Sopenharmony_ci return __gmap_link(gmap, gaddr, vmaddr); 8908c2ecf20Sopenharmony_ci} 8918c2ecf20Sopenharmony_ci 8928c2ecf20Sopenharmony_ci/** 8938c2ecf20Sopenharmony_ci * gmap_pte_op_end - release the page table lock 8948c2ecf20Sopenharmony_ci * @ptl: pointer to the spinlock pointer 8958c2ecf20Sopenharmony_ci */ 8968c2ecf20Sopenharmony_cistatic void gmap_pte_op_end(spinlock_t *ptl) 8978c2ecf20Sopenharmony_ci{ 8988c2ecf20Sopenharmony_ci if (ptl) 8998c2ecf20Sopenharmony_ci spin_unlock(ptl); 9008c2ecf20Sopenharmony_ci} 9018c2ecf20Sopenharmony_ci 9028c2ecf20Sopenharmony_ci/** 9038c2ecf20Sopenharmony_ci * gmap_pmd_op_walk - walk the gmap tables, get the guest table lock 9048c2ecf20Sopenharmony_ci * and return the pmd pointer 9058c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 9068c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 9078c2ecf20Sopenharmony_ci * 9088c2ecf20Sopenharmony_ci * Returns a pointer to the pmd for a guest address, or NULL 9098c2ecf20Sopenharmony_ci */ 9108c2ecf20Sopenharmony_cistatic inline pmd_t *gmap_pmd_op_walk(struct gmap *gmap, unsigned long gaddr) 9118c2ecf20Sopenharmony_ci{ 9128c2ecf20Sopenharmony_ci pmd_t *pmdp; 9138c2ecf20Sopenharmony_ci 9148c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 9158c2ecf20Sopenharmony_ci pmdp = (pmd_t *) gmap_table_walk(gmap, gaddr, 1); 9168c2ecf20Sopenharmony_ci if (!pmdp) 9178c2ecf20Sopenharmony_ci return NULL; 9188c2ecf20Sopenharmony_ci 9198c2ecf20Sopenharmony_ci /* without huge pages, there is no need to take the table lock */ 9208c2ecf20Sopenharmony_ci if (!gmap->mm->context.allow_gmap_hpage_1m) 9218c2ecf20Sopenharmony_ci return pmd_none(*pmdp) ? NULL : pmdp; 9228c2ecf20Sopenharmony_ci 9238c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 9248c2ecf20Sopenharmony_ci if (pmd_none(*pmdp)) { 9258c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 9268c2ecf20Sopenharmony_ci return NULL; 9278c2ecf20Sopenharmony_ci } 9288c2ecf20Sopenharmony_ci 9298c2ecf20Sopenharmony_ci /* 4k page table entries are locked via the pte (pte_alloc_map_lock). */ 9308c2ecf20Sopenharmony_ci if (!pmd_large(*pmdp)) 9318c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 9328c2ecf20Sopenharmony_ci return pmdp; 9338c2ecf20Sopenharmony_ci} 9348c2ecf20Sopenharmony_ci 9358c2ecf20Sopenharmony_ci/** 9368c2ecf20Sopenharmony_ci * gmap_pmd_op_end - release the guest_table_lock if needed 9378c2ecf20Sopenharmony_ci * @gmap: pointer to the guest mapping meta data structure 9388c2ecf20Sopenharmony_ci * @pmdp: pointer to the pmd 9398c2ecf20Sopenharmony_ci */ 9408c2ecf20Sopenharmony_cistatic inline void gmap_pmd_op_end(struct gmap *gmap, pmd_t *pmdp) 9418c2ecf20Sopenharmony_ci{ 9428c2ecf20Sopenharmony_ci if (pmd_large(*pmdp)) 9438c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 9448c2ecf20Sopenharmony_ci} 9458c2ecf20Sopenharmony_ci 9468c2ecf20Sopenharmony_ci/* 9478c2ecf20Sopenharmony_ci * gmap_protect_pmd - remove access rights to memory and set pmd notification bits 9488c2ecf20Sopenharmony_ci * @pmdp: pointer to the pmd to be protected 9498c2ecf20Sopenharmony_ci * @prot: indicates access rights: PROT_NONE, PROT_READ or PROT_WRITE 9508c2ecf20Sopenharmony_ci * @bits: notification bits to set 9518c2ecf20Sopenharmony_ci * 9528c2ecf20Sopenharmony_ci * Returns: 9538c2ecf20Sopenharmony_ci * 0 if successfully protected 9548c2ecf20Sopenharmony_ci * -EAGAIN if a fixup is needed 9558c2ecf20Sopenharmony_ci * -EINVAL if unsupported notifier bits have been specified 9568c2ecf20Sopenharmony_ci * 9578c2ecf20Sopenharmony_ci * Expected to be called with sg->mm->mmap_lock in read and 9588c2ecf20Sopenharmony_ci * guest_table_lock held. 9598c2ecf20Sopenharmony_ci */ 9608c2ecf20Sopenharmony_cistatic int gmap_protect_pmd(struct gmap *gmap, unsigned long gaddr, 9618c2ecf20Sopenharmony_ci pmd_t *pmdp, int prot, unsigned long bits) 9628c2ecf20Sopenharmony_ci{ 9638c2ecf20Sopenharmony_ci int pmd_i = pmd_val(*pmdp) & _SEGMENT_ENTRY_INVALID; 9648c2ecf20Sopenharmony_ci int pmd_p = pmd_val(*pmdp) & _SEGMENT_ENTRY_PROTECT; 9658c2ecf20Sopenharmony_ci pmd_t new = *pmdp; 9668c2ecf20Sopenharmony_ci 9678c2ecf20Sopenharmony_ci /* Fixup needed */ 9688c2ecf20Sopenharmony_ci if ((pmd_i && (prot != PROT_NONE)) || (pmd_p && (prot == PROT_WRITE))) 9698c2ecf20Sopenharmony_ci return -EAGAIN; 9708c2ecf20Sopenharmony_ci 9718c2ecf20Sopenharmony_ci if (prot == PROT_NONE && !pmd_i) { 9728c2ecf20Sopenharmony_ci pmd_val(new) |= _SEGMENT_ENTRY_INVALID; 9738c2ecf20Sopenharmony_ci gmap_pmdp_xchg(gmap, pmdp, new, gaddr); 9748c2ecf20Sopenharmony_ci } 9758c2ecf20Sopenharmony_ci 9768c2ecf20Sopenharmony_ci if (prot == PROT_READ && !pmd_p) { 9778c2ecf20Sopenharmony_ci pmd_val(new) &= ~_SEGMENT_ENTRY_INVALID; 9788c2ecf20Sopenharmony_ci pmd_val(new) |= _SEGMENT_ENTRY_PROTECT; 9798c2ecf20Sopenharmony_ci gmap_pmdp_xchg(gmap, pmdp, new, gaddr); 9808c2ecf20Sopenharmony_ci } 9818c2ecf20Sopenharmony_ci 9828c2ecf20Sopenharmony_ci if (bits & GMAP_NOTIFY_MPROT) 9838c2ecf20Sopenharmony_ci pmd_val(*pmdp) |= _SEGMENT_ENTRY_GMAP_IN; 9848c2ecf20Sopenharmony_ci 9858c2ecf20Sopenharmony_ci /* Shadow GMAP protection needs split PMDs */ 9868c2ecf20Sopenharmony_ci if (bits & GMAP_NOTIFY_SHADOW) 9878c2ecf20Sopenharmony_ci return -EINVAL; 9888c2ecf20Sopenharmony_ci 9898c2ecf20Sopenharmony_ci return 0; 9908c2ecf20Sopenharmony_ci} 9918c2ecf20Sopenharmony_ci 9928c2ecf20Sopenharmony_ci/* 9938c2ecf20Sopenharmony_ci * gmap_protect_pte - remove access rights to memory and set pgste bits 9948c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 9958c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 9968c2ecf20Sopenharmony_ci * @pmdp: pointer to the pmd associated with the pte 9978c2ecf20Sopenharmony_ci * @prot: indicates access rights: PROT_NONE, PROT_READ or PROT_WRITE 9988c2ecf20Sopenharmony_ci * @bits: notification bits to set 9998c2ecf20Sopenharmony_ci * 10008c2ecf20Sopenharmony_ci * Returns 0 if successfully protected, -ENOMEM if out of memory and 10018c2ecf20Sopenharmony_ci * -EAGAIN if a fixup is needed. 10028c2ecf20Sopenharmony_ci * 10038c2ecf20Sopenharmony_ci * Expected to be called with sg->mm->mmap_lock in read 10048c2ecf20Sopenharmony_ci */ 10058c2ecf20Sopenharmony_cistatic int gmap_protect_pte(struct gmap *gmap, unsigned long gaddr, 10068c2ecf20Sopenharmony_ci pmd_t *pmdp, int prot, unsigned long bits) 10078c2ecf20Sopenharmony_ci{ 10088c2ecf20Sopenharmony_ci int rc; 10098c2ecf20Sopenharmony_ci pte_t *ptep; 10108c2ecf20Sopenharmony_ci spinlock_t *ptl = NULL; 10118c2ecf20Sopenharmony_ci unsigned long pbits = 0; 10128c2ecf20Sopenharmony_ci 10138c2ecf20Sopenharmony_ci if (pmd_val(*pmdp) & _SEGMENT_ENTRY_INVALID) 10148c2ecf20Sopenharmony_ci return -EAGAIN; 10158c2ecf20Sopenharmony_ci 10168c2ecf20Sopenharmony_ci ptep = pte_alloc_map_lock(gmap->mm, pmdp, gaddr, &ptl); 10178c2ecf20Sopenharmony_ci if (!ptep) 10188c2ecf20Sopenharmony_ci return -ENOMEM; 10198c2ecf20Sopenharmony_ci 10208c2ecf20Sopenharmony_ci pbits |= (bits & GMAP_NOTIFY_MPROT) ? PGSTE_IN_BIT : 0; 10218c2ecf20Sopenharmony_ci pbits |= (bits & GMAP_NOTIFY_SHADOW) ? PGSTE_VSIE_BIT : 0; 10228c2ecf20Sopenharmony_ci /* Protect and unlock. */ 10238c2ecf20Sopenharmony_ci rc = ptep_force_prot(gmap->mm, gaddr, ptep, prot, pbits); 10248c2ecf20Sopenharmony_ci gmap_pte_op_end(ptl); 10258c2ecf20Sopenharmony_ci return rc; 10268c2ecf20Sopenharmony_ci} 10278c2ecf20Sopenharmony_ci 10288c2ecf20Sopenharmony_ci/* 10298c2ecf20Sopenharmony_ci * gmap_protect_range - remove access rights to memory and set pgste bits 10308c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 10318c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 10328c2ecf20Sopenharmony_ci * @len: size of area 10338c2ecf20Sopenharmony_ci * @prot: indicates access rights: PROT_NONE, PROT_READ or PROT_WRITE 10348c2ecf20Sopenharmony_ci * @bits: pgste notification bits to set 10358c2ecf20Sopenharmony_ci * 10368c2ecf20Sopenharmony_ci * Returns 0 if successfully protected, -ENOMEM if out of memory and 10378c2ecf20Sopenharmony_ci * -EFAULT if gaddr is invalid (or mapping for shadows is missing). 10388c2ecf20Sopenharmony_ci * 10398c2ecf20Sopenharmony_ci * Called with sg->mm->mmap_lock in read. 10408c2ecf20Sopenharmony_ci */ 10418c2ecf20Sopenharmony_cistatic int gmap_protect_range(struct gmap *gmap, unsigned long gaddr, 10428c2ecf20Sopenharmony_ci unsigned long len, int prot, unsigned long bits) 10438c2ecf20Sopenharmony_ci{ 10448c2ecf20Sopenharmony_ci unsigned long vmaddr, dist; 10458c2ecf20Sopenharmony_ci pmd_t *pmdp; 10468c2ecf20Sopenharmony_ci int rc; 10478c2ecf20Sopenharmony_ci 10488c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(gmap)); 10498c2ecf20Sopenharmony_ci while (len) { 10508c2ecf20Sopenharmony_ci rc = -EAGAIN; 10518c2ecf20Sopenharmony_ci pmdp = gmap_pmd_op_walk(gmap, gaddr); 10528c2ecf20Sopenharmony_ci if (pmdp) { 10538c2ecf20Sopenharmony_ci if (!pmd_large(*pmdp)) { 10548c2ecf20Sopenharmony_ci rc = gmap_protect_pte(gmap, gaddr, pmdp, prot, 10558c2ecf20Sopenharmony_ci bits); 10568c2ecf20Sopenharmony_ci if (!rc) { 10578c2ecf20Sopenharmony_ci len -= PAGE_SIZE; 10588c2ecf20Sopenharmony_ci gaddr += PAGE_SIZE; 10598c2ecf20Sopenharmony_ci } 10608c2ecf20Sopenharmony_ci } else { 10618c2ecf20Sopenharmony_ci rc = gmap_protect_pmd(gmap, gaddr, pmdp, prot, 10628c2ecf20Sopenharmony_ci bits); 10638c2ecf20Sopenharmony_ci if (!rc) { 10648c2ecf20Sopenharmony_ci dist = HPAGE_SIZE - (gaddr & ~HPAGE_MASK); 10658c2ecf20Sopenharmony_ci len = len < dist ? 0 : len - dist; 10668c2ecf20Sopenharmony_ci gaddr = (gaddr & HPAGE_MASK) + HPAGE_SIZE; 10678c2ecf20Sopenharmony_ci } 10688c2ecf20Sopenharmony_ci } 10698c2ecf20Sopenharmony_ci gmap_pmd_op_end(gmap, pmdp); 10708c2ecf20Sopenharmony_ci } 10718c2ecf20Sopenharmony_ci if (rc) { 10728c2ecf20Sopenharmony_ci if (rc == -EINVAL) 10738c2ecf20Sopenharmony_ci return rc; 10748c2ecf20Sopenharmony_ci 10758c2ecf20Sopenharmony_ci /* -EAGAIN, fixup of userspace mm and gmap */ 10768c2ecf20Sopenharmony_ci vmaddr = __gmap_translate(gmap, gaddr); 10778c2ecf20Sopenharmony_ci if (IS_ERR_VALUE(vmaddr)) 10788c2ecf20Sopenharmony_ci return vmaddr; 10798c2ecf20Sopenharmony_ci rc = gmap_pte_op_fixup(gmap, gaddr, vmaddr, prot); 10808c2ecf20Sopenharmony_ci if (rc) 10818c2ecf20Sopenharmony_ci return rc; 10828c2ecf20Sopenharmony_ci } 10838c2ecf20Sopenharmony_ci } 10848c2ecf20Sopenharmony_ci return 0; 10858c2ecf20Sopenharmony_ci} 10868c2ecf20Sopenharmony_ci 10878c2ecf20Sopenharmony_ci/** 10888c2ecf20Sopenharmony_ci * gmap_mprotect_notify - change access rights for a range of ptes and 10898c2ecf20Sopenharmony_ci * call the notifier if any pte changes again 10908c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 10918c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 10928c2ecf20Sopenharmony_ci * @len: size of area 10938c2ecf20Sopenharmony_ci * @prot: indicates access rights: PROT_NONE, PROT_READ or PROT_WRITE 10948c2ecf20Sopenharmony_ci * 10958c2ecf20Sopenharmony_ci * Returns 0 if for each page in the given range a gmap mapping exists, 10968c2ecf20Sopenharmony_ci * the new access rights could be set and the notifier could be armed. 10978c2ecf20Sopenharmony_ci * If the gmap mapping is missing for one or more pages -EFAULT is 10988c2ecf20Sopenharmony_ci * returned. If no memory could be allocated -ENOMEM is returned. 10998c2ecf20Sopenharmony_ci * This function establishes missing page table entries. 11008c2ecf20Sopenharmony_ci */ 11018c2ecf20Sopenharmony_ciint gmap_mprotect_notify(struct gmap *gmap, unsigned long gaddr, 11028c2ecf20Sopenharmony_ci unsigned long len, int prot) 11038c2ecf20Sopenharmony_ci{ 11048c2ecf20Sopenharmony_ci int rc; 11058c2ecf20Sopenharmony_ci 11068c2ecf20Sopenharmony_ci if ((gaddr & ~PAGE_MASK) || (len & ~PAGE_MASK) || gmap_is_shadow(gmap)) 11078c2ecf20Sopenharmony_ci return -EINVAL; 11088c2ecf20Sopenharmony_ci if (!MACHINE_HAS_ESOP && prot == PROT_READ) 11098c2ecf20Sopenharmony_ci return -EINVAL; 11108c2ecf20Sopenharmony_ci mmap_read_lock(gmap->mm); 11118c2ecf20Sopenharmony_ci rc = gmap_protect_range(gmap, gaddr, len, prot, GMAP_NOTIFY_MPROT); 11128c2ecf20Sopenharmony_ci mmap_read_unlock(gmap->mm); 11138c2ecf20Sopenharmony_ci return rc; 11148c2ecf20Sopenharmony_ci} 11158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_mprotect_notify); 11168c2ecf20Sopenharmony_ci 11178c2ecf20Sopenharmony_ci/** 11188c2ecf20Sopenharmony_ci * gmap_read_table - get an unsigned long value from a guest page table using 11198c2ecf20Sopenharmony_ci * absolute addressing, without marking the page referenced. 11208c2ecf20Sopenharmony_ci * @gmap: pointer to guest mapping meta data structure 11218c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 11228c2ecf20Sopenharmony_ci * @val: pointer to the unsigned long value to return 11238c2ecf20Sopenharmony_ci * 11248c2ecf20Sopenharmony_ci * Returns 0 if the value was read, -ENOMEM if out of memory and -EFAULT 11258c2ecf20Sopenharmony_ci * if reading using the virtual address failed. -EINVAL if called on a gmap 11268c2ecf20Sopenharmony_ci * shadow. 11278c2ecf20Sopenharmony_ci * 11288c2ecf20Sopenharmony_ci * Called with gmap->mm->mmap_lock in read. 11298c2ecf20Sopenharmony_ci */ 11308c2ecf20Sopenharmony_ciint gmap_read_table(struct gmap *gmap, unsigned long gaddr, unsigned long *val) 11318c2ecf20Sopenharmony_ci{ 11328c2ecf20Sopenharmony_ci unsigned long address, vmaddr; 11338c2ecf20Sopenharmony_ci spinlock_t *ptl; 11348c2ecf20Sopenharmony_ci pte_t *ptep, pte; 11358c2ecf20Sopenharmony_ci int rc; 11368c2ecf20Sopenharmony_ci 11378c2ecf20Sopenharmony_ci if (gmap_is_shadow(gmap)) 11388c2ecf20Sopenharmony_ci return -EINVAL; 11398c2ecf20Sopenharmony_ci 11408c2ecf20Sopenharmony_ci while (1) { 11418c2ecf20Sopenharmony_ci rc = -EAGAIN; 11428c2ecf20Sopenharmony_ci ptep = gmap_pte_op_walk(gmap, gaddr, &ptl); 11438c2ecf20Sopenharmony_ci if (ptep) { 11448c2ecf20Sopenharmony_ci pte = *ptep; 11458c2ecf20Sopenharmony_ci if (pte_present(pte) && (pte_val(pte) & _PAGE_READ)) { 11468c2ecf20Sopenharmony_ci address = pte_val(pte) & PAGE_MASK; 11478c2ecf20Sopenharmony_ci address += gaddr & ~PAGE_MASK; 11488c2ecf20Sopenharmony_ci *val = *(unsigned long *) address; 11498c2ecf20Sopenharmony_ci pte_val(*ptep) |= _PAGE_YOUNG; 11508c2ecf20Sopenharmony_ci /* Do *NOT* clear the _PAGE_INVALID bit! */ 11518c2ecf20Sopenharmony_ci rc = 0; 11528c2ecf20Sopenharmony_ci } 11538c2ecf20Sopenharmony_ci gmap_pte_op_end(ptl); 11548c2ecf20Sopenharmony_ci } 11558c2ecf20Sopenharmony_ci if (!rc) 11568c2ecf20Sopenharmony_ci break; 11578c2ecf20Sopenharmony_ci vmaddr = __gmap_translate(gmap, gaddr); 11588c2ecf20Sopenharmony_ci if (IS_ERR_VALUE(vmaddr)) { 11598c2ecf20Sopenharmony_ci rc = vmaddr; 11608c2ecf20Sopenharmony_ci break; 11618c2ecf20Sopenharmony_ci } 11628c2ecf20Sopenharmony_ci rc = gmap_pte_op_fixup(gmap, gaddr, vmaddr, PROT_READ); 11638c2ecf20Sopenharmony_ci if (rc) 11648c2ecf20Sopenharmony_ci break; 11658c2ecf20Sopenharmony_ci } 11668c2ecf20Sopenharmony_ci return rc; 11678c2ecf20Sopenharmony_ci} 11688c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_read_table); 11698c2ecf20Sopenharmony_ci 11708c2ecf20Sopenharmony_ci/** 11718c2ecf20Sopenharmony_ci * gmap_insert_rmap - add a rmap to the host_to_rmap radix tree 11728c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 11738c2ecf20Sopenharmony_ci * @vmaddr: vm address associated with the rmap 11748c2ecf20Sopenharmony_ci * @rmap: pointer to the rmap structure 11758c2ecf20Sopenharmony_ci * 11768c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 11778c2ecf20Sopenharmony_ci */ 11788c2ecf20Sopenharmony_cistatic inline void gmap_insert_rmap(struct gmap *sg, unsigned long vmaddr, 11798c2ecf20Sopenharmony_ci struct gmap_rmap *rmap) 11808c2ecf20Sopenharmony_ci{ 11818c2ecf20Sopenharmony_ci void __rcu **slot; 11828c2ecf20Sopenharmony_ci 11838c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 11848c2ecf20Sopenharmony_ci slot = radix_tree_lookup_slot(&sg->host_to_rmap, vmaddr >> PAGE_SHIFT); 11858c2ecf20Sopenharmony_ci if (slot) { 11868c2ecf20Sopenharmony_ci rmap->next = radix_tree_deref_slot_protected(slot, 11878c2ecf20Sopenharmony_ci &sg->guest_table_lock); 11888c2ecf20Sopenharmony_ci radix_tree_replace_slot(&sg->host_to_rmap, slot, rmap); 11898c2ecf20Sopenharmony_ci } else { 11908c2ecf20Sopenharmony_ci rmap->next = NULL; 11918c2ecf20Sopenharmony_ci radix_tree_insert(&sg->host_to_rmap, vmaddr >> PAGE_SHIFT, 11928c2ecf20Sopenharmony_ci rmap); 11938c2ecf20Sopenharmony_ci } 11948c2ecf20Sopenharmony_ci} 11958c2ecf20Sopenharmony_ci 11968c2ecf20Sopenharmony_ci/** 11978c2ecf20Sopenharmony_ci * gmap_protect_rmap - restrict access rights to memory (RO) and create an rmap 11988c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 11998c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow gmap 12008c2ecf20Sopenharmony_ci * @paddr: address in the parent guest address space 12018c2ecf20Sopenharmony_ci * @len: length of the memory area to protect 12028c2ecf20Sopenharmony_ci * 12038c2ecf20Sopenharmony_ci * Returns 0 if successfully protected and the rmap was created, -ENOMEM 12048c2ecf20Sopenharmony_ci * if out of memory and -EFAULT if paddr is invalid. 12058c2ecf20Sopenharmony_ci */ 12068c2ecf20Sopenharmony_cistatic int gmap_protect_rmap(struct gmap *sg, unsigned long raddr, 12078c2ecf20Sopenharmony_ci unsigned long paddr, unsigned long len) 12088c2ecf20Sopenharmony_ci{ 12098c2ecf20Sopenharmony_ci struct gmap *parent; 12108c2ecf20Sopenharmony_ci struct gmap_rmap *rmap; 12118c2ecf20Sopenharmony_ci unsigned long vmaddr; 12128c2ecf20Sopenharmony_ci spinlock_t *ptl; 12138c2ecf20Sopenharmony_ci pte_t *ptep; 12148c2ecf20Sopenharmony_ci int rc; 12158c2ecf20Sopenharmony_ci 12168c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 12178c2ecf20Sopenharmony_ci parent = sg->parent; 12188c2ecf20Sopenharmony_ci while (len) { 12198c2ecf20Sopenharmony_ci vmaddr = __gmap_translate(parent, paddr); 12208c2ecf20Sopenharmony_ci if (IS_ERR_VALUE(vmaddr)) 12218c2ecf20Sopenharmony_ci return vmaddr; 12228c2ecf20Sopenharmony_ci rmap = kzalloc(sizeof(*rmap), GFP_KERNEL); 12238c2ecf20Sopenharmony_ci if (!rmap) 12248c2ecf20Sopenharmony_ci return -ENOMEM; 12258c2ecf20Sopenharmony_ci rmap->raddr = raddr; 12268c2ecf20Sopenharmony_ci rc = radix_tree_preload(GFP_KERNEL); 12278c2ecf20Sopenharmony_ci if (rc) { 12288c2ecf20Sopenharmony_ci kfree(rmap); 12298c2ecf20Sopenharmony_ci return rc; 12308c2ecf20Sopenharmony_ci } 12318c2ecf20Sopenharmony_ci rc = -EAGAIN; 12328c2ecf20Sopenharmony_ci ptep = gmap_pte_op_walk(parent, paddr, &ptl); 12338c2ecf20Sopenharmony_ci if (ptep) { 12348c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 12358c2ecf20Sopenharmony_ci rc = ptep_force_prot(parent->mm, paddr, ptep, PROT_READ, 12368c2ecf20Sopenharmony_ci PGSTE_VSIE_BIT); 12378c2ecf20Sopenharmony_ci if (!rc) 12388c2ecf20Sopenharmony_ci gmap_insert_rmap(sg, vmaddr, rmap); 12398c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 12408c2ecf20Sopenharmony_ci gmap_pte_op_end(ptl); 12418c2ecf20Sopenharmony_ci } 12428c2ecf20Sopenharmony_ci radix_tree_preload_end(); 12438c2ecf20Sopenharmony_ci if (rc) { 12448c2ecf20Sopenharmony_ci kfree(rmap); 12458c2ecf20Sopenharmony_ci rc = gmap_pte_op_fixup(parent, paddr, vmaddr, PROT_READ); 12468c2ecf20Sopenharmony_ci if (rc) 12478c2ecf20Sopenharmony_ci return rc; 12488c2ecf20Sopenharmony_ci continue; 12498c2ecf20Sopenharmony_ci } 12508c2ecf20Sopenharmony_ci paddr += PAGE_SIZE; 12518c2ecf20Sopenharmony_ci len -= PAGE_SIZE; 12528c2ecf20Sopenharmony_ci } 12538c2ecf20Sopenharmony_ci return 0; 12548c2ecf20Sopenharmony_ci} 12558c2ecf20Sopenharmony_ci 12568c2ecf20Sopenharmony_ci#define _SHADOW_RMAP_MASK 0x7 12578c2ecf20Sopenharmony_ci#define _SHADOW_RMAP_REGION1 0x5 12588c2ecf20Sopenharmony_ci#define _SHADOW_RMAP_REGION2 0x4 12598c2ecf20Sopenharmony_ci#define _SHADOW_RMAP_REGION3 0x3 12608c2ecf20Sopenharmony_ci#define _SHADOW_RMAP_SEGMENT 0x2 12618c2ecf20Sopenharmony_ci#define _SHADOW_RMAP_PGTABLE 0x1 12628c2ecf20Sopenharmony_ci 12638c2ecf20Sopenharmony_ci/** 12648c2ecf20Sopenharmony_ci * gmap_idte_one - invalidate a single region or segment table entry 12658c2ecf20Sopenharmony_ci * @asce: region or segment table *origin* + table-type bits 12668c2ecf20Sopenharmony_ci * @vaddr: virtual address to identify the table entry to flush 12678c2ecf20Sopenharmony_ci * 12688c2ecf20Sopenharmony_ci * The invalid bit of a single region or segment table entry is set 12698c2ecf20Sopenharmony_ci * and the associated TLB entries depending on the entry are flushed. 12708c2ecf20Sopenharmony_ci * The table-type of the @asce identifies the portion of the @vaddr 12718c2ecf20Sopenharmony_ci * that is used as the invalidation index. 12728c2ecf20Sopenharmony_ci */ 12738c2ecf20Sopenharmony_cistatic inline void gmap_idte_one(unsigned long asce, unsigned long vaddr) 12748c2ecf20Sopenharmony_ci{ 12758c2ecf20Sopenharmony_ci asm volatile( 12768c2ecf20Sopenharmony_ci " .insn rrf,0xb98e0000,%0,%1,0,0" 12778c2ecf20Sopenharmony_ci : : "a" (asce), "a" (vaddr) : "cc", "memory"); 12788c2ecf20Sopenharmony_ci} 12798c2ecf20Sopenharmony_ci 12808c2ecf20Sopenharmony_ci/** 12818c2ecf20Sopenharmony_ci * gmap_unshadow_page - remove a page from a shadow page table 12828c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 12838c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 12848c2ecf20Sopenharmony_ci * 12858c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 12868c2ecf20Sopenharmony_ci */ 12878c2ecf20Sopenharmony_cistatic void gmap_unshadow_page(struct gmap *sg, unsigned long raddr) 12888c2ecf20Sopenharmony_ci{ 12898c2ecf20Sopenharmony_ci unsigned long *table; 12908c2ecf20Sopenharmony_ci 12918c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 12928c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, raddr, 0); /* get page table pointer */ 12938c2ecf20Sopenharmony_ci if (!table || *table & _PAGE_INVALID) 12948c2ecf20Sopenharmony_ci return; 12958c2ecf20Sopenharmony_ci gmap_call_notifier(sg, raddr, raddr + _PAGE_SIZE - 1); 12968c2ecf20Sopenharmony_ci ptep_unshadow_pte(sg->mm, raddr, (pte_t *) table); 12978c2ecf20Sopenharmony_ci} 12988c2ecf20Sopenharmony_ci 12998c2ecf20Sopenharmony_ci/** 13008c2ecf20Sopenharmony_ci * __gmap_unshadow_pgt - remove all entries from a shadow page table 13018c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 13028c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 13038c2ecf20Sopenharmony_ci * @pgt: pointer to the start of a shadow page table 13048c2ecf20Sopenharmony_ci * 13058c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 13068c2ecf20Sopenharmony_ci */ 13078c2ecf20Sopenharmony_cistatic void __gmap_unshadow_pgt(struct gmap *sg, unsigned long raddr, 13088c2ecf20Sopenharmony_ci unsigned long *pgt) 13098c2ecf20Sopenharmony_ci{ 13108c2ecf20Sopenharmony_ci int i; 13118c2ecf20Sopenharmony_ci 13128c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 13138c2ecf20Sopenharmony_ci for (i = 0; i < _PAGE_ENTRIES; i++, raddr += _PAGE_SIZE) 13148c2ecf20Sopenharmony_ci pgt[i] = _PAGE_INVALID; 13158c2ecf20Sopenharmony_ci} 13168c2ecf20Sopenharmony_ci 13178c2ecf20Sopenharmony_ci/** 13188c2ecf20Sopenharmony_ci * gmap_unshadow_pgt - remove a shadow page table from a segment entry 13198c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 13208c2ecf20Sopenharmony_ci * @raddr: address in the shadow guest address space 13218c2ecf20Sopenharmony_ci * 13228c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 13238c2ecf20Sopenharmony_ci */ 13248c2ecf20Sopenharmony_cistatic void gmap_unshadow_pgt(struct gmap *sg, unsigned long raddr) 13258c2ecf20Sopenharmony_ci{ 13268c2ecf20Sopenharmony_ci unsigned long sto, *ste, *pgt; 13278c2ecf20Sopenharmony_ci struct page *page; 13288c2ecf20Sopenharmony_ci 13298c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 13308c2ecf20Sopenharmony_ci ste = gmap_table_walk(sg, raddr, 1); /* get segment pointer */ 13318c2ecf20Sopenharmony_ci if (!ste || !(*ste & _SEGMENT_ENTRY_ORIGIN)) 13328c2ecf20Sopenharmony_ci return; 13338c2ecf20Sopenharmony_ci gmap_call_notifier(sg, raddr, raddr + _SEGMENT_SIZE - 1); 13348c2ecf20Sopenharmony_ci sto = (unsigned long) (ste - ((raddr & _SEGMENT_INDEX) >> _SEGMENT_SHIFT)); 13358c2ecf20Sopenharmony_ci gmap_idte_one(sto | _ASCE_TYPE_SEGMENT, raddr); 13368c2ecf20Sopenharmony_ci pgt = (unsigned long *)(*ste & _SEGMENT_ENTRY_ORIGIN); 13378c2ecf20Sopenharmony_ci *ste = _SEGMENT_ENTRY_EMPTY; 13388c2ecf20Sopenharmony_ci __gmap_unshadow_pgt(sg, raddr, pgt); 13398c2ecf20Sopenharmony_ci /* Free page table */ 13408c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(pgt) >> PAGE_SHIFT); 13418c2ecf20Sopenharmony_ci list_del(&page->lru); 13428c2ecf20Sopenharmony_ci page_table_free_pgste(page); 13438c2ecf20Sopenharmony_ci} 13448c2ecf20Sopenharmony_ci 13458c2ecf20Sopenharmony_ci/** 13468c2ecf20Sopenharmony_ci * __gmap_unshadow_sgt - remove all entries from a shadow segment table 13478c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 13488c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 13498c2ecf20Sopenharmony_ci * @sgt: pointer to the start of a shadow segment table 13508c2ecf20Sopenharmony_ci * 13518c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 13528c2ecf20Sopenharmony_ci */ 13538c2ecf20Sopenharmony_cistatic void __gmap_unshadow_sgt(struct gmap *sg, unsigned long raddr, 13548c2ecf20Sopenharmony_ci unsigned long *sgt) 13558c2ecf20Sopenharmony_ci{ 13568c2ecf20Sopenharmony_ci unsigned long *pgt; 13578c2ecf20Sopenharmony_ci struct page *page; 13588c2ecf20Sopenharmony_ci int i; 13598c2ecf20Sopenharmony_ci 13608c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 13618c2ecf20Sopenharmony_ci for (i = 0; i < _CRST_ENTRIES; i++, raddr += _SEGMENT_SIZE) { 13628c2ecf20Sopenharmony_ci if (!(sgt[i] & _SEGMENT_ENTRY_ORIGIN)) 13638c2ecf20Sopenharmony_ci continue; 13648c2ecf20Sopenharmony_ci pgt = (unsigned long *)(sgt[i] & _REGION_ENTRY_ORIGIN); 13658c2ecf20Sopenharmony_ci sgt[i] = _SEGMENT_ENTRY_EMPTY; 13668c2ecf20Sopenharmony_ci __gmap_unshadow_pgt(sg, raddr, pgt); 13678c2ecf20Sopenharmony_ci /* Free page table */ 13688c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(pgt) >> PAGE_SHIFT); 13698c2ecf20Sopenharmony_ci list_del(&page->lru); 13708c2ecf20Sopenharmony_ci page_table_free_pgste(page); 13718c2ecf20Sopenharmony_ci } 13728c2ecf20Sopenharmony_ci} 13738c2ecf20Sopenharmony_ci 13748c2ecf20Sopenharmony_ci/** 13758c2ecf20Sopenharmony_ci * gmap_unshadow_sgt - remove a shadow segment table from a region-3 entry 13768c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 13778c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 13788c2ecf20Sopenharmony_ci * 13798c2ecf20Sopenharmony_ci * Called with the shadow->guest_table_lock 13808c2ecf20Sopenharmony_ci */ 13818c2ecf20Sopenharmony_cistatic void gmap_unshadow_sgt(struct gmap *sg, unsigned long raddr) 13828c2ecf20Sopenharmony_ci{ 13838c2ecf20Sopenharmony_ci unsigned long r3o, *r3e, *sgt; 13848c2ecf20Sopenharmony_ci struct page *page; 13858c2ecf20Sopenharmony_ci 13868c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 13878c2ecf20Sopenharmony_ci r3e = gmap_table_walk(sg, raddr, 2); /* get region-3 pointer */ 13888c2ecf20Sopenharmony_ci if (!r3e || !(*r3e & _REGION_ENTRY_ORIGIN)) 13898c2ecf20Sopenharmony_ci return; 13908c2ecf20Sopenharmony_ci gmap_call_notifier(sg, raddr, raddr + _REGION3_SIZE - 1); 13918c2ecf20Sopenharmony_ci r3o = (unsigned long) (r3e - ((raddr & _REGION3_INDEX) >> _REGION3_SHIFT)); 13928c2ecf20Sopenharmony_ci gmap_idte_one(r3o | _ASCE_TYPE_REGION3, raddr); 13938c2ecf20Sopenharmony_ci sgt = (unsigned long *)(*r3e & _REGION_ENTRY_ORIGIN); 13948c2ecf20Sopenharmony_ci *r3e = _REGION3_ENTRY_EMPTY; 13958c2ecf20Sopenharmony_ci __gmap_unshadow_sgt(sg, raddr, sgt); 13968c2ecf20Sopenharmony_ci /* Free segment table */ 13978c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(sgt) >> PAGE_SHIFT); 13988c2ecf20Sopenharmony_ci list_del(&page->lru); 13998c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 14008c2ecf20Sopenharmony_ci} 14018c2ecf20Sopenharmony_ci 14028c2ecf20Sopenharmony_ci/** 14038c2ecf20Sopenharmony_ci * __gmap_unshadow_r3t - remove all entries from a shadow region-3 table 14048c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 14058c2ecf20Sopenharmony_ci * @raddr: address in the shadow guest address space 14068c2ecf20Sopenharmony_ci * @r3t: pointer to the start of a shadow region-3 table 14078c2ecf20Sopenharmony_ci * 14088c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 14098c2ecf20Sopenharmony_ci */ 14108c2ecf20Sopenharmony_cistatic void __gmap_unshadow_r3t(struct gmap *sg, unsigned long raddr, 14118c2ecf20Sopenharmony_ci unsigned long *r3t) 14128c2ecf20Sopenharmony_ci{ 14138c2ecf20Sopenharmony_ci unsigned long *sgt; 14148c2ecf20Sopenharmony_ci struct page *page; 14158c2ecf20Sopenharmony_ci int i; 14168c2ecf20Sopenharmony_ci 14178c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 14188c2ecf20Sopenharmony_ci for (i = 0; i < _CRST_ENTRIES; i++, raddr += _REGION3_SIZE) { 14198c2ecf20Sopenharmony_ci if (!(r3t[i] & _REGION_ENTRY_ORIGIN)) 14208c2ecf20Sopenharmony_ci continue; 14218c2ecf20Sopenharmony_ci sgt = (unsigned long *)(r3t[i] & _REGION_ENTRY_ORIGIN); 14228c2ecf20Sopenharmony_ci r3t[i] = _REGION3_ENTRY_EMPTY; 14238c2ecf20Sopenharmony_ci __gmap_unshadow_sgt(sg, raddr, sgt); 14248c2ecf20Sopenharmony_ci /* Free segment table */ 14258c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(sgt) >> PAGE_SHIFT); 14268c2ecf20Sopenharmony_ci list_del(&page->lru); 14278c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 14288c2ecf20Sopenharmony_ci } 14298c2ecf20Sopenharmony_ci} 14308c2ecf20Sopenharmony_ci 14318c2ecf20Sopenharmony_ci/** 14328c2ecf20Sopenharmony_ci * gmap_unshadow_r3t - remove a shadow region-3 table from a region-2 entry 14338c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 14348c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 14358c2ecf20Sopenharmony_ci * 14368c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 14378c2ecf20Sopenharmony_ci */ 14388c2ecf20Sopenharmony_cistatic void gmap_unshadow_r3t(struct gmap *sg, unsigned long raddr) 14398c2ecf20Sopenharmony_ci{ 14408c2ecf20Sopenharmony_ci unsigned long r2o, *r2e, *r3t; 14418c2ecf20Sopenharmony_ci struct page *page; 14428c2ecf20Sopenharmony_ci 14438c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 14448c2ecf20Sopenharmony_ci r2e = gmap_table_walk(sg, raddr, 3); /* get region-2 pointer */ 14458c2ecf20Sopenharmony_ci if (!r2e || !(*r2e & _REGION_ENTRY_ORIGIN)) 14468c2ecf20Sopenharmony_ci return; 14478c2ecf20Sopenharmony_ci gmap_call_notifier(sg, raddr, raddr + _REGION2_SIZE - 1); 14488c2ecf20Sopenharmony_ci r2o = (unsigned long) (r2e - ((raddr & _REGION2_INDEX) >> _REGION2_SHIFT)); 14498c2ecf20Sopenharmony_ci gmap_idte_one(r2o | _ASCE_TYPE_REGION2, raddr); 14508c2ecf20Sopenharmony_ci r3t = (unsigned long *)(*r2e & _REGION_ENTRY_ORIGIN); 14518c2ecf20Sopenharmony_ci *r2e = _REGION2_ENTRY_EMPTY; 14528c2ecf20Sopenharmony_ci __gmap_unshadow_r3t(sg, raddr, r3t); 14538c2ecf20Sopenharmony_ci /* Free region 3 table */ 14548c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(r3t) >> PAGE_SHIFT); 14558c2ecf20Sopenharmony_ci list_del(&page->lru); 14568c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 14578c2ecf20Sopenharmony_ci} 14588c2ecf20Sopenharmony_ci 14598c2ecf20Sopenharmony_ci/** 14608c2ecf20Sopenharmony_ci * __gmap_unshadow_r2t - remove all entries from a shadow region-2 table 14618c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 14628c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 14638c2ecf20Sopenharmony_ci * @r2t: pointer to the start of a shadow region-2 table 14648c2ecf20Sopenharmony_ci * 14658c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 14668c2ecf20Sopenharmony_ci */ 14678c2ecf20Sopenharmony_cistatic void __gmap_unshadow_r2t(struct gmap *sg, unsigned long raddr, 14688c2ecf20Sopenharmony_ci unsigned long *r2t) 14698c2ecf20Sopenharmony_ci{ 14708c2ecf20Sopenharmony_ci unsigned long *r3t; 14718c2ecf20Sopenharmony_ci struct page *page; 14728c2ecf20Sopenharmony_ci int i; 14738c2ecf20Sopenharmony_ci 14748c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 14758c2ecf20Sopenharmony_ci for (i = 0; i < _CRST_ENTRIES; i++, raddr += _REGION2_SIZE) { 14768c2ecf20Sopenharmony_ci if (!(r2t[i] & _REGION_ENTRY_ORIGIN)) 14778c2ecf20Sopenharmony_ci continue; 14788c2ecf20Sopenharmony_ci r3t = (unsigned long *)(r2t[i] & _REGION_ENTRY_ORIGIN); 14798c2ecf20Sopenharmony_ci r2t[i] = _REGION2_ENTRY_EMPTY; 14808c2ecf20Sopenharmony_ci __gmap_unshadow_r3t(sg, raddr, r3t); 14818c2ecf20Sopenharmony_ci /* Free region 3 table */ 14828c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(r3t) >> PAGE_SHIFT); 14838c2ecf20Sopenharmony_ci list_del(&page->lru); 14848c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 14858c2ecf20Sopenharmony_ci } 14868c2ecf20Sopenharmony_ci} 14878c2ecf20Sopenharmony_ci 14888c2ecf20Sopenharmony_ci/** 14898c2ecf20Sopenharmony_ci * gmap_unshadow_r2t - remove a shadow region-2 table from a region-1 entry 14908c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 14918c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 14928c2ecf20Sopenharmony_ci * 14938c2ecf20Sopenharmony_ci * Called with the sg->guest_table_lock 14948c2ecf20Sopenharmony_ci */ 14958c2ecf20Sopenharmony_cistatic void gmap_unshadow_r2t(struct gmap *sg, unsigned long raddr) 14968c2ecf20Sopenharmony_ci{ 14978c2ecf20Sopenharmony_ci unsigned long r1o, *r1e, *r2t; 14988c2ecf20Sopenharmony_ci struct page *page; 14998c2ecf20Sopenharmony_ci 15008c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 15018c2ecf20Sopenharmony_ci r1e = gmap_table_walk(sg, raddr, 4); /* get region-1 pointer */ 15028c2ecf20Sopenharmony_ci if (!r1e || !(*r1e & _REGION_ENTRY_ORIGIN)) 15038c2ecf20Sopenharmony_ci return; 15048c2ecf20Sopenharmony_ci gmap_call_notifier(sg, raddr, raddr + _REGION1_SIZE - 1); 15058c2ecf20Sopenharmony_ci r1o = (unsigned long) (r1e - ((raddr & _REGION1_INDEX) >> _REGION1_SHIFT)); 15068c2ecf20Sopenharmony_ci gmap_idte_one(r1o | _ASCE_TYPE_REGION1, raddr); 15078c2ecf20Sopenharmony_ci r2t = (unsigned long *)(*r1e & _REGION_ENTRY_ORIGIN); 15088c2ecf20Sopenharmony_ci *r1e = _REGION1_ENTRY_EMPTY; 15098c2ecf20Sopenharmony_ci __gmap_unshadow_r2t(sg, raddr, r2t); 15108c2ecf20Sopenharmony_ci /* Free region 2 table */ 15118c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(r2t) >> PAGE_SHIFT); 15128c2ecf20Sopenharmony_ci list_del(&page->lru); 15138c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 15148c2ecf20Sopenharmony_ci} 15158c2ecf20Sopenharmony_ci 15168c2ecf20Sopenharmony_ci/** 15178c2ecf20Sopenharmony_ci * __gmap_unshadow_r1t - remove all entries from a shadow region-1 table 15188c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 15198c2ecf20Sopenharmony_ci * @raddr: rmap address in the shadow guest address space 15208c2ecf20Sopenharmony_ci * @r1t: pointer to the start of a shadow region-1 table 15218c2ecf20Sopenharmony_ci * 15228c2ecf20Sopenharmony_ci * Called with the shadow->guest_table_lock 15238c2ecf20Sopenharmony_ci */ 15248c2ecf20Sopenharmony_cistatic void __gmap_unshadow_r1t(struct gmap *sg, unsigned long raddr, 15258c2ecf20Sopenharmony_ci unsigned long *r1t) 15268c2ecf20Sopenharmony_ci{ 15278c2ecf20Sopenharmony_ci unsigned long asce, *r2t; 15288c2ecf20Sopenharmony_ci struct page *page; 15298c2ecf20Sopenharmony_ci int i; 15308c2ecf20Sopenharmony_ci 15318c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 15328c2ecf20Sopenharmony_ci asce = (unsigned long) r1t | _ASCE_TYPE_REGION1; 15338c2ecf20Sopenharmony_ci for (i = 0; i < _CRST_ENTRIES; i++, raddr += _REGION1_SIZE) { 15348c2ecf20Sopenharmony_ci if (!(r1t[i] & _REGION_ENTRY_ORIGIN)) 15358c2ecf20Sopenharmony_ci continue; 15368c2ecf20Sopenharmony_ci r2t = (unsigned long *)(r1t[i] & _REGION_ENTRY_ORIGIN); 15378c2ecf20Sopenharmony_ci __gmap_unshadow_r2t(sg, raddr, r2t); 15388c2ecf20Sopenharmony_ci /* Clear entry and flush translation r1t -> r2t */ 15398c2ecf20Sopenharmony_ci gmap_idte_one(asce, raddr); 15408c2ecf20Sopenharmony_ci r1t[i] = _REGION1_ENTRY_EMPTY; 15418c2ecf20Sopenharmony_ci /* Free region 2 table */ 15428c2ecf20Sopenharmony_ci page = pfn_to_page(__pa(r2t) >> PAGE_SHIFT); 15438c2ecf20Sopenharmony_ci list_del(&page->lru); 15448c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 15458c2ecf20Sopenharmony_ci } 15468c2ecf20Sopenharmony_ci} 15478c2ecf20Sopenharmony_ci 15488c2ecf20Sopenharmony_ci/** 15498c2ecf20Sopenharmony_ci * gmap_unshadow - remove a shadow page table completely 15508c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 15518c2ecf20Sopenharmony_ci * 15528c2ecf20Sopenharmony_ci * Called with sg->guest_table_lock 15538c2ecf20Sopenharmony_ci */ 15548c2ecf20Sopenharmony_cistatic void gmap_unshadow(struct gmap *sg) 15558c2ecf20Sopenharmony_ci{ 15568c2ecf20Sopenharmony_ci unsigned long *table; 15578c2ecf20Sopenharmony_ci 15588c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 15598c2ecf20Sopenharmony_ci if (sg->removed) 15608c2ecf20Sopenharmony_ci return; 15618c2ecf20Sopenharmony_ci sg->removed = 1; 15628c2ecf20Sopenharmony_ci gmap_call_notifier(sg, 0, -1UL); 15638c2ecf20Sopenharmony_ci gmap_flush_tlb(sg); 15648c2ecf20Sopenharmony_ci table = (unsigned long *)(sg->asce & _ASCE_ORIGIN); 15658c2ecf20Sopenharmony_ci switch (sg->asce & _ASCE_TYPE_MASK) { 15668c2ecf20Sopenharmony_ci case _ASCE_TYPE_REGION1: 15678c2ecf20Sopenharmony_ci __gmap_unshadow_r1t(sg, 0, table); 15688c2ecf20Sopenharmony_ci break; 15698c2ecf20Sopenharmony_ci case _ASCE_TYPE_REGION2: 15708c2ecf20Sopenharmony_ci __gmap_unshadow_r2t(sg, 0, table); 15718c2ecf20Sopenharmony_ci break; 15728c2ecf20Sopenharmony_ci case _ASCE_TYPE_REGION3: 15738c2ecf20Sopenharmony_ci __gmap_unshadow_r3t(sg, 0, table); 15748c2ecf20Sopenharmony_ci break; 15758c2ecf20Sopenharmony_ci case _ASCE_TYPE_SEGMENT: 15768c2ecf20Sopenharmony_ci __gmap_unshadow_sgt(sg, 0, table); 15778c2ecf20Sopenharmony_ci break; 15788c2ecf20Sopenharmony_ci } 15798c2ecf20Sopenharmony_ci} 15808c2ecf20Sopenharmony_ci 15818c2ecf20Sopenharmony_ci/** 15828c2ecf20Sopenharmony_ci * gmap_find_shadow - find a specific asce in the list of shadow tables 15838c2ecf20Sopenharmony_ci * @parent: pointer to the parent gmap 15848c2ecf20Sopenharmony_ci * @asce: ASCE for which the shadow table is created 15858c2ecf20Sopenharmony_ci * @edat_level: edat level to be used for the shadow translation 15868c2ecf20Sopenharmony_ci * 15878c2ecf20Sopenharmony_ci * Returns the pointer to a gmap if a shadow table with the given asce is 15888c2ecf20Sopenharmony_ci * already available, ERR_PTR(-EAGAIN) if another one is just being created, 15898c2ecf20Sopenharmony_ci * otherwise NULL 15908c2ecf20Sopenharmony_ci */ 15918c2ecf20Sopenharmony_cistatic struct gmap *gmap_find_shadow(struct gmap *parent, unsigned long asce, 15928c2ecf20Sopenharmony_ci int edat_level) 15938c2ecf20Sopenharmony_ci{ 15948c2ecf20Sopenharmony_ci struct gmap *sg; 15958c2ecf20Sopenharmony_ci 15968c2ecf20Sopenharmony_ci list_for_each_entry(sg, &parent->children, list) { 15978c2ecf20Sopenharmony_ci if (sg->orig_asce != asce || sg->edat_level != edat_level || 15988c2ecf20Sopenharmony_ci sg->removed) 15998c2ecf20Sopenharmony_ci continue; 16008c2ecf20Sopenharmony_ci if (!sg->initialized) 16018c2ecf20Sopenharmony_ci return ERR_PTR(-EAGAIN); 16028c2ecf20Sopenharmony_ci refcount_inc(&sg->ref_count); 16038c2ecf20Sopenharmony_ci return sg; 16048c2ecf20Sopenharmony_ci } 16058c2ecf20Sopenharmony_ci return NULL; 16068c2ecf20Sopenharmony_ci} 16078c2ecf20Sopenharmony_ci 16088c2ecf20Sopenharmony_ci/** 16098c2ecf20Sopenharmony_ci * gmap_shadow_valid - check if a shadow guest address space matches the 16108c2ecf20Sopenharmony_ci * given properties and is still valid 16118c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 16128c2ecf20Sopenharmony_ci * @asce: ASCE for which the shadow table is requested 16138c2ecf20Sopenharmony_ci * @edat_level: edat level to be used for the shadow translation 16148c2ecf20Sopenharmony_ci * 16158c2ecf20Sopenharmony_ci * Returns 1 if the gmap shadow is still valid and matches the given 16168c2ecf20Sopenharmony_ci * properties, the caller can continue using it. Returns 0 otherwise, the 16178c2ecf20Sopenharmony_ci * caller has to request a new shadow gmap in this case. 16188c2ecf20Sopenharmony_ci * 16198c2ecf20Sopenharmony_ci */ 16208c2ecf20Sopenharmony_ciint gmap_shadow_valid(struct gmap *sg, unsigned long asce, int edat_level) 16218c2ecf20Sopenharmony_ci{ 16228c2ecf20Sopenharmony_ci if (sg->removed) 16238c2ecf20Sopenharmony_ci return 0; 16248c2ecf20Sopenharmony_ci return sg->orig_asce == asce && sg->edat_level == edat_level; 16258c2ecf20Sopenharmony_ci} 16268c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow_valid); 16278c2ecf20Sopenharmony_ci 16288c2ecf20Sopenharmony_ci/** 16298c2ecf20Sopenharmony_ci * gmap_shadow - create/find a shadow guest address space 16308c2ecf20Sopenharmony_ci * @parent: pointer to the parent gmap 16318c2ecf20Sopenharmony_ci * @asce: ASCE for which the shadow table is created 16328c2ecf20Sopenharmony_ci * @edat_level: edat level to be used for the shadow translation 16338c2ecf20Sopenharmony_ci * 16348c2ecf20Sopenharmony_ci * The pages of the top level page table referred by the asce parameter 16358c2ecf20Sopenharmony_ci * will be set to read-only and marked in the PGSTEs of the kvm process. 16368c2ecf20Sopenharmony_ci * The shadow table will be removed automatically on any change to the 16378c2ecf20Sopenharmony_ci * PTE mapping for the source table. 16388c2ecf20Sopenharmony_ci * 16398c2ecf20Sopenharmony_ci * Returns a guest address space structure, ERR_PTR(-ENOMEM) if out of memory, 16408c2ecf20Sopenharmony_ci * ERR_PTR(-EAGAIN) if the caller has to retry and ERR_PTR(-EFAULT) if the 16418c2ecf20Sopenharmony_ci * parent gmap table could not be protected. 16428c2ecf20Sopenharmony_ci */ 16438c2ecf20Sopenharmony_cistruct gmap *gmap_shadow(struct gmap *parent, unsigned long asce, 16448c2ecf20Sopenharmony_ci int edat_level) 16458c2ecf20Sopenharmony_ci{ 16468c2ecf20Sopenharmony_ci struct gmap *sg, *new; 16478c2ecf20Sopenharmony_ci unsigned long limit; 16488c2ecf20Sopenharmony_ci int rc; 16498c2ecf20Sopenharmony_ci 16508c2ecf20Sopenharmony_ci BUG_ON(parent->mm->context.allow_gmap_hpage_1m); 16518c2ecf20Sopenharmony_ci BUG_ON(gmap_is_shadow(parent)); 16528c2ecf20Sopenharmony_ci spin_lock(&parent->shadow_lock); 16538c2ecf20Sopenharmony_ci sg = gmap_find_shadow(parent, asce, edat_level); 16548c2ecf20Sopenharmony_ci spin_unlock(&parent->shadow_lock); 16558c2ecf20Sopenharmony_ci if (sg) 16568c2ecf20Sopenharmony_ci return sg; 16578c2ecf20Sopenharmony_ci /* Create a new shadow gmap */ 16588c2ecf20Sopenharmony_ci limit = -1UL >> (33 - (((asce & _ASCE_TYPE_MASK) >> 2) * 11)); 16598c2ecf20Sopenharmony_ci if (asce & _ASCE_REAL_SPACE) 16608c2ecf20Sopenharmony_ci limit = -1UL; 16618c2ecf20Sopenharmony_ci new = gmap_alloc(limit); 16628c2ecf20Sopenharmony_ci if (!new) 16638c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 16648c2ecf20Sopenharmony_ci new->mm = parent->mm; 16658c2ecf20Sopenharmony_ci new->parent = gmap_get(parent); 16668c2ecf20Sopenharmony_ci new->orig_asce = asce; 16678c2ecf20Sopenharmony_ci new->edat_level = edat_level; 16688c2ecf20Sopenharmony_ci new->initialized = false; 16698c2ecf20Sopenharmony_ci spin_lock(&parent->shadow_lock); 16708c2ecf20Sopenharmony_ci /* Recheck if another CPU created the same shadow */ 16718c2ecf20Sopenharmony_ci sg = gmap_find_shadow(parent, asce, edat_level); 16728c2ecf20Sopenharmony_ci if (sg) { 16738c2ecf20Sopenharmony_ci spin_unlock(&parent->shadow_lock); 16748c2ecf20Sopenharmony_ci gmap_free(new); 16758c2ecf20Sopenharmony_ci return sg; 16768c2ecf20Sopenharmony_ci } 16778c2ecf20Sopenharmony_ci if (asce & _ASCE_REAL_SPACE) { 16788c2ecf20Sopenharmony_ci /* only allow one real-space gmap shadow */ 16798c2ecf20Sopenharmony_ci list_for_each_entry(sg, &parent->children, list) { 16808c2ecf20Sopenharmony_ci if (sg->orig_asce & _ASCE_REAL_SPACE) { 16818c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 16828c2ecf20Sopenharmony_ci gmap_unshadow(sg); 16838c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 16848c2ecf20Sopenharmony_ci list_del(&sg->list); 16858c2ecf20Sopenharmony_ci gmap_put(sg); 16868c2ecf20Sopenharmony_ci break; 16878c2ecf20Sopenharmony_ci } 16888c2ecf20Sopenharmony_ci } 16898c2ecf20Sopenharmony_ci } 16908c2ecf20Sopenharmony_ci refcount_set(&new->ref_count, 2); 16918c2ecf20Sopenharmony_ci list_add(&new->list, &parent->children); 16928c2ecf20Sopenharmony_ci if (asce & _ASCE_REAL_SPACE) { 16938c2ecf20Sopenharmony_ci /* nothing to protect, return right away */ 16948c2ecf20Sopenharmony_ci new->initialized = true; 16958c2ecf20Sopenharmony_ci spin_unlock(&parent->shadow_lock); 16968c2ecf20Sopenharmony_ci return new; 16978c2ecf20Sopenharmony_ci } 16988c2ecf20Sopenharmony_ci spin_unlock(&parent->shadow_lock); 16998c2ecf20Sopenharmony_ci /* protect after insertion, so it will get properly invalidated */ 17008c2ecf20Sopenharmony_ci mmap_read_lock(parent->mm); 17018c2ecf20Sopenharmony_ci rc = gmap_protect_range(parent, asce & _ASCE_ORIGIN, 17028c2ecf20Sopenharmony_ci ((asce & _ASCE_TABLE_LENGTH) + 1) * PAGE_SIZE, 17038c2ecf20Sopenharmony_ci PROT_READ, GMAP_NOTIFY_SHADOW); 17048c2ecf20Sopenharmony_ci mmap_read_unlock(parent->mm); 17058c2ecf20Sopenharmony_ci spin_lock(&parent->shadow_lock); 17068c2ecf20Sopenharmony_ci new->initialized = true; 17078c2ecf20Sopenharmony_ci if (rc) { 17088c2ecf20Sopenharmony_ci list_del(&new->list); 17098c2ecf20Sopenharmony_ci gmap_free(new); 17108c2ecf20Sopenharmony_ci new = ERR_PTR(rc); 17118c2ecf20Sopenharmony_ci } 17128c2ecf20Sopenharmony_ci spin_unlock(&parent->shadow_lock); 17138c2ecf20Sopenharmony_ci return new; 17148c2ecf20Sopenharmony_ci} 17158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow); 17168c2ecf20Sopenharmony_ci 17178c2ecf20Sopenharmony_ci/** 17188c2ecf20Sopenharmony_ci * gmap_shadow_r2t - create an empty shadow region 2 table 17198c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 17208c2ecf20Sopenharmony_ci * @saddr: faulting address in the shadow gmap 17218c2ecf20Sopenharmony_ci * @r2t: parent gmap address of the region 2 table to get shadowed 17228c2ecf20Sopenharmony_ci * @fake: r2t references contiguous guest memory block, not a r2t 17238c2ecf20Sopenharmony_ci * 17248c2ecf20Sopenharmony_ci * The r2t parameter specifies the address of the source table. The 17258c2ecf20Sopenharmony_ci * four pages of the source table are made read-only in the parent gmap 17268c2ecf20Sopenharmony_ci * address space. A write to the source table area @r2t will automatically 17278c2ecf20Sopenharmony_ci * remove the shadow r2 table and all of its decendents. 17288c2ecf20Sopenharmony_ci * 17298c2ecf20Sopenharmony_ci * Returns 0 if successfully shadowed or already shadowed, -EAGAIN if the 17308c2ecf20Sopenharmony_ci * shadow table structure is incomplete, -ENOMEM if out of memory and 17318c2ecf20Sopenharmony_ci * -EFAULT if an address in the parent gmap could not be resolved. 17328c2ecf20Sopenharmony_ci * 17338c2ecf20Sopenharmony_ci * Called with sg->mm->mmap_lock in read. 17348c2ecf20Sopenharmony_ci */ 17358c2ecf20Sopenharmony_ciint gmap_shadow_r2t(struct gmap *sg, unsigned long saddr, unsigned long r2t, 17368c2ecf20Sopenharmony_ci int fake) 17378c2ecf20Sopenharmony_ci{ 17388c2ecf20Sopenharmony_ci unsigned long raddr, origin, offset, len; 17398c2ecf20Sopenharmony_ci unsigned long *s_r2t, *table; 17408c2ecf20Sopenharmony_ci struct page *page; 17418c2ecf20Sopenharmony_ci int rc; 17428c2ecf20Sopenharmony_ci 17438c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 17448c2ecf20Sopenharmony_ci /* Allocate a shadow region second table */ 17458c2ecf20Sopenharmony_ci page = alloc_pages(GFP_KERNEL, CRST_ALLOC_ORDER); 17468c2ecf20Sopenharmony_ci if (!page) 17478c2ecf20Sopenharmony_ci return -ENOMEM; 17488c2ecf20Sopenharmony_ci page->index = r2t & _REGION_ENTRY_ORIGIN; 17498c2ecf20Sopenharmony_ci if (fake) 17508c2ecf20Sopenharmony_ci page->index |= GMAP_SHADOW_FAKE_TABLE; 17518c2ecf20Sopenharmony_ci s_r2t = (unsigned long *) page_to_phys(page); 17528c2ecf20Sopenharmony_ci /* Install shadow region second table */ 17538c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 17548c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 4); /* get region-1 pointer */ 17558c2ecf20Sopenharmony_ci if (!table) { 17568c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 17578c2ecf20Sopenharmony_ci goto out_free; 17588c2ecf20Sopenharmony_ci } 17598c2ecf20Sopenharmony_ci if (!(*table & _REGION_ENTRY_INVALID)) { 17608c2ecf20Sopenharmony_ci rc = 0; /* Already established */ 17618c2ecf20Sopenharmony_ci goto out_free; 17628c2ecf20Sopenharmony_ci } else if (*table & _REGION_ENTRY_ORIGIN) { 17638c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with shadow */ 17648c2ecf20Sopenharmony_ci goto out_free; 17658c2ecf20Sopenharmony_ci } 17668c2ecf20Sopenharmony_ci crst_table_init(s_r2t, _REGION2_ENTRY_EMPTY); 17678c2ecf20Sopenharmony_ci /* mark as invalid as long as the parent table is not protected */ 17688c2ecf20Sopenharmony_ci *table = (unsigned long) s_r2t | _REGION_ENTRY_LENGTH | 17698c2ecf20Sopenharmony_ci _REGION_ENTRY_TYPE_R1 | _REGION_ENTRY_INVALID; 17708c2ecf20Sopenharmony_ci if (sg->edat_level >= 1) 17718c2ecf20Sopenharmony_ci *table |= (r2t & _REGION_ENTRY_PROTECT); 17728c2ecf20Sopenharmony_ci list_add(&page->lru, &sg->crst_list); 17738c2ecf20Sopenharmony_ci if (fake) { 17748c2ecf20Sopenharmony_ci /* nothing to protect for fake tables */ 17758c2ecf20Sopenharmony_ci *table &= ~_REGION_ENTRY_INVALID; 17768c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 17778c2ecf20Sopenharmony_ci return 0; 17788c2ecf20Sopenharmony_ci } 17798c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 17808c2ecf20Sopenharmony_ci /* Make r2t read-only in parent gmap page table */ 17818c2ecf20Sopenharmony_ci raddr = (saddr & _REGION1_MASK) | _SHADOW_RMAP_REGION1; 17828c2ecf20Sopenharmony_ci origin = r2t & _REGION_ENTRY_ORIGIN; 17838c2ecf20Sopenharmony_ci offset = ((r2t & _REGION_ENTRY_OFFSET) >> 6) * PAGE_SIZE; 17848c2ecf20Sopenharmony_ci len = ((r2t & _REGION_ENTRY_LENGTH) + 1) * PAGE_SIZE - offset; 17858c2ecf20Sopenharmony_ci rc = gmap_protect_rmap(sg, raddr, origin + offset, len); 17868c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 17878c2ecf20Sopenharmony_ci if (!rc) { 17888c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 4); 17898c2ecf20Sopenharmony_ci if (!table || (*table & _REGION_ENTRY_ORIGIN) != 17908c2ecf20Sopenharmony_ci (unsigned long) s_r2t) 17918c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 17928c2ecf20Sopenharmony_ci else 17938c2ecf20Sopenharmony_ci *table &= ~_REGION_ENTRY_INVALID; 17948c2ecf20Sopenharmony_ci } else { 17958c2ecf20Sopenharmony_ci gmap_unshadow_r2t(sg, raddr); 17968c2ecf20Sopenharmony_ci } 17978c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 17988c2ecf20Sopenharmony_ci return rc; 17998c2ecf20Sopenharmony_ciout_free: 18008c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 18018c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 18028c2ecf20Sopenharmony_ci return rc; 18038c2ecf20Sopenharmony_ci} 18048c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow_r2t); 18058c2ecf20Sopenharmony_ci 18068c2ecf20Sopenharmony_ci/** 18078c2ecf20Sopenharmony_ci * gmap_shadow_r3t - create a shadow region 3 table 18088c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 18098c2ecf20Sopenharmony_ci * @saddr: faulting address in the shadow gmap 18108c2ecf20Sopenharmony_ci * @r3t: parent gmap address of the region 3 table to get shadowed 18118c2ecf20Sopenharmony_ci * @fake: r3t references contiguous guest memory block, not a r3t 18128c2ecf20Sopenharmony_ci * 18138c2ecf20Sopenharmony_ci * Returns 0 if successfully shadowed or already shadowed, -EAGAIN if the 18148c2ecf20Sopenharmony_ci * shadow table structure is incomplete, -ENOMEM if out of memory and 18158c2ecf20Sopenharmony_ci * -EFAULT if an address in the parent gmap could not be resolved. 18168c2ecf20Sopenharmony_ci * 18178c2ecf20Sopenharmony_ci * Called with sg->mm->mmap_lock in read. 18188c2ecf20Sopenharmony_ci */ 18198c2ecf20Sopenharmony_ciint gmap_shadow_r3t(struct gmap *sg, unsigned long saddr, unsigned long r3t, 18208c2ecf20Sopenharmony_ci int fake) 18218c2ecf20Sopenharmony_ci{ 18228c2ecf20Sopenharmony_ci unsigned long raddr, origin, offset, len; 18238c2ecf20Sopenharmony_ci unsigned long *s_r3t, *table; 18248c2ecf20Sopenharmony_ci struct page *page; 18258c2ecf20Sopenharmony_ci int rc; 18268c2ecf20Sopenharmony_ci 18278c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 18288c2ecf20Sopenharmony_ci /* Allocate a shadow region second table */ 18298c2ecf20Sopenharmony_ci page = alloc_pages(GFP_KERNEL, CRST_ALLOC_ORDER); 18308c2ecf20Sopenharmony_ci if (!page) 18318c2ecf20Sopenharmony_ci return -ENOMEM; 18328c2ecf20Sopenharmony_ci page->index = r3t & _REGION_ENTRY_ORIGIN; 18338c2ecf20Sopenharmony_ci if (fake) 18348c2ecf20Sopenharmony_ci page->index |= GMAP_SHADOW_FAKE_TABLE; 18358c2ecf20Sopenharmony_ci s_r3t = (unsigned long *) page_to_phys(page); 18368c2ecf20Sopenharmony_ci /* Install shadow region second table */ 18378c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 18388c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 3); /* get region-2 pointer */ 18398c2ecf20Sopenharmony_ci if (!table) { 18408c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 18418c2ecf20Sopenharmony_ci goto out_free; 18428c2ecf20Sopenharmony_ci } 18438c2ecf20Sopenharmony_ci if (!(*table & _REGION_ENTRY_INVALID)) { 18448c2ecf20Sopenharmony_ci rc = 0; /* Already established */ 18458c2ecf20Sopenharmony_ci goto out_free; 18468c2ecf20Sopenharmony_ci } else if (*table & _REGION_ENTRY_ORIGIN) { 18478c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with shadow */ 18488c2ecf20Sopenharmony_ci goto out_free; 18498c2ecf20Sopenharmony_ci } 18508c2ecf20Sopenharmony_ci crst_table_init(s_r3t, _REGION3_ENTRY_EMPTY); 18518c2ecf20Sopenharmony_ci /* mark as invalid as long as the parent table is not protected */ 18528c2ecf20Sopenharmony_ci *table = (unsigned long) s_r3t | _REGION_ENTRY_LENGTH | 18538c2ecf20Sopenharmony_ci _REGION_ENTRY_TYPE_R2 | _REGION_ENTRY_INVALID; 18548c2ecf20Sopenharmony_ci if (sg->edat_level >= 1) 18558c2ecf20Sopenharmony_ci *table |= (r3t & _REGION_ENTRY_PROTECT); 18568c2ecf20Sopenharmony_ci list_add(&page->lru, &sg->crst_list); 18578c2ecf20Sopenharmony_ci if (fake) { 18588c2ecf20Sopenharmony_ci /* nothing to protect for fake tables */ 18598c2ecf20Sopenharmony_ci *table &= ~_REGION_ENTRY_INVALID; 18608c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 18618c2ecf20Sopenharmony_ci return 0; 18628c2ecf20Sopenharmony_ci } 18638c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 18648c2ecf20Sopenharmony_ci /* Make r3t read-only in parent gmap page table */ 18658c2ecf20Sopenharmony_ci raddr = (saddr & _REGION2_MASK) | _SHADOW_RMAP_REGION2; 18668c2ecf20Sopenharmony_ci origin = r3t & _REGION_ENTRY_ORIGIN; 18678c2ecf20Sopenharmony_ci offset = ((r3t & _REGION_ENTRY_OFFSET) >> 6) * PAGE_SIZE; 18688c2ecf20Sopenharmony_ci len = ((r3t & _REGION_ENTRY_LENGTH) + 1) * PAGE_SIZE - offset; 18698c2ecf20Sopenharmony_ci rc = gmap_protect_rmap(sg, raddr, origin + offset, len); 18708c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 18718c2ecf20Sopenharmony_ci if (!rc) { 18728c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 3); 18738c2ecf20Sopenharmony_ci if (!table || (*table & _REGION_ENTRY_ORIGIN) != 18748c2ecf20Sopenharmony_ci (unsigned long) s_r3t) 18758c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 18768c2ecf20Sopenharmony_ci else 18778c2ecf20Sopenharmony_ci *table &= ~_REGION_ENTRY_INVALID; 18788c2ecf20Sopenharmony_ci } else { 18798c2ecf20Sopenharmony_ci gmap_unshadow_r3t(sg, raddr); 18808c2ecf20Sopenharmony_ci } 18818c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 18828c2ecf20Sopenharmony_ci return rc; 18838c2ecf20Sopenharmony_ciout_free: 18848c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 18858c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 18868c2ecf20Sopenharmony_ci return rc; 18878c2ecf20Sopenharmony_ci} 18888c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow_r3t); 18898c2ecf20Sopenharmony_ci 18908c2ecf20Sopenharmony_ci/** 18918c2ecf20Sopenharmony_ci * gmap_shadow_sgt - create a shadow segment table 18928c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 18938c2ecf20Sopenharmony_ci * @saddr: faulting address in the shadow gmap 18948c2ecf20Sopenharmony_ci * @sgt: parent gmap address of the segment table to get shadowed 18958c2ecf20Sopenharmony_ci * @fake: sgt references contiguous guest memory block, not a sgt 18968c2ecf20Sopenharmony_ci * 18978c2ecf20Sopenharmony_ci * Returns: 0 if successfully shadowed or already shadowed, -EAGAIN if the 18988c2ecf20Sopenharmony_ci * shadow table structure is incomplete, -ENOMEM if out of memory and 18998c2ecf20Sopenharmony_ci * -EFAULT if an address in the parent gmap could not be resolved. 19008c2ecf20Sopenharmony_ci * 19018c2ecf20Sopenharmony_ci * Called with sg->mm->mmap_lock in read. 19028c2ecf20Sopenharmony_ci */ 19038c2ecf20Sopenharmony_ciint gmap_shadow_sgt(struct gmap *sg, unsigned long saddr, unsigned long sgt, 19048c2ecf20Sopenharmony_ci int fake) 19058c2ecf20Sopenharmony_ci{ 19068c2ecf20Sopenharmony_ci unsigned long raddr, origin, offset, len; 19078c2ecf20Sopenharmony_ci unsigned long *s_sgt, *table; 19088c2ecf20Sopenharmony_ci struct page *page; 19098c2ecf20Sopenharmony_ci int rc; 19108c2ecf20Sopenharmony_ci 19118c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg) || (sgt & _REGION3_ENTRY_LARGE)); 19128c2ecf20Sopenharmony_ci /* Allocate a shadow segment table */ 19138c2ecf20Sopenharmony_ci page = alloc_pages(GFP_KERNEL, CRST_ALLOC_ORDER); 19148c2ecf20Sopenharmony_ci if (!page) 19158c2ecf20Sopenharmony_ci return -ENOMEM; 19168c2ecf20Sopenharmony_ci page->index = sgt & _REGION_ENTRY_ORIGIN; 19178c2ecf20Sopenharmony_ci if (fake) 19188c2ecf20Sopenharmony_ci page->index |= GMAP_SHADOW_FAKE_TABLE; 19198c2ecf20Sopenharmony_ci s_sgt = (unsigned long *) page_to_phys(page); 19208c2ecf20Sopenharmony_ci /* Install shadow region second table */ 19218c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 19228c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 2); /* get region-3 pointer */ 19238c2ecf20Sopenharmony_ci if (!table) { 19248c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 19258c2ecf20Sopenharmony_ci goto out_free; 19268c2ecf20Sopenharmony_ci } 19278c2ecf20Sopenharmony_ci if (!(*table & _REGION_ENTRY_INVALID)) { 19288c2ecf20Sopenharmony_ci rc = 0; /* Already established */ 19298c2ecf20Sopenharmony_ci goto out_free; 19308c2ecf20Sopenharmony_ci } else if (*table & _REGION_ENTRY_ORIGIN) { 19318c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with shadow */ 19328c2ecf20Sopenharmony_ci goto out_free; 19338c2ecf20Sopenharmony_ci } 19348c2ecf20Sopenharmony_ci crst_table_init(s_sgt, _SEGMENT_ENTRY_EMPTY); 19358c2ecf20Sopenharmony_ci /* mark as invalid as long as the parent table is not protected */ 19368c2ecf20Sopenharmony_ci *table = (unsigned long) s_sgt | _REGION_ENTRY_LENGTH | 19378c2ecf20Sopenharmony_ci _REGION_ENTRY_TYPE_R3 | _REGION_ENTRY_INVALID; 19388c2ecf20Sopenharmony_ci if (sg->edat_level >= 1) 19398c2ecf20Sopenharmony_ci *table |= sgt & _REGION_ENTRY_PROTECT; 19408c2ecf20Sopenharmony_ci list_add(&page->lru, &sg->crst_list); 19418c2ecf20Sopenharmony_ci if (fake) { 19428c2ecf20Sopenharmony_ci /* nothing to protect for fake tables */ 19438c2ecf20Sopenharmony_ci *table &= ~_REGION_ENTRY_INVALID; 19448c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 19458c2ecf20Sopenharmony_ci return 0; 19468c2ecf20Sopenharmony_ci } 19478c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 19488c2ecf20Sopenharmony_ci /* Make sgt read-only in parent gmap page table */ 19498c2ecf20Sopenharmony_ci raddr = (saddr & _REGION3_MASK) | _SHADOW_RMAP_REGION3; 19508c2ecf20Sopenharmony_ci origin = sgt & _REGION_ENTRY_ORIGIN; 19518c2ecf20Sopenharmony_ci offset = ((sgt & _REGION_ENTRY_OFFSET) >> 6) * PAGE_SIZE; 19528c2ecf20Sopenharmony_ci len = ((sgt & _REGION_ENTRY_LENGTH) + 1) * PAGE_SIZE - offset; 19538c2ecf20Sopenharmony_ci rc = gmap_protect_rmap(sg, raddr, origin + offset, len); 19548c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 19558c2ecf20Sopenharmony_ci if (!rc) { 19568c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 2); 19578c2ecf20Sopenharmony_ci if (!table || (*table & _REGION_ENTRY_ORIGIN) != 19588c2ecf20Sopenharmony_ci (unsigned long) s_sgt) 19598c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 19608c2ecf20Sopenharmony_ci else 19618c2ecf20Sopenharmony_ci *table &= ~_REGION_ENTRY_INVALID; 19628c2ecf20Sopenharmony_ci } else { 19638c2ecf20Sopenharmony_ci gmap_unshadow_sgt(sg, raddr); 19648c2ecf20Sopenharmony_ci } 19658c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 19668c2ecf20Sopenharmony_ci return rc; 19678c2ecf20Sopenharmony_ciout_free: 19688c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 19698c2ecf20Sopenharmony_ci __free_pages(page, CRST_ALLOC_ORDER); 19708c2ecf20Sopenharmony_ci return rc; 19718c2ecf20Sopenharmony_ci} 19728c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow_sgt); 19738c2ecf20Sopenharmony_ci 19748c2ecf20Sopenharmony_ci/** 19758c2ecf20Sopenharmony_ci * gmap_shadow_lookup_pgtable - find a shadow page table 19768c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 19778c2ecf20Sopenharmony_ci * @saddr: the address in the shadow aguest address space 19788c2ecf20Sopenharmony_ci * @pgt: parent gmap address of the page table to get shadowed 19798c2ecf20Sopenharmony_ci * @dat_protection: if the pgtable is marked as protected by dat 19808c2ecf20Sopenharmony_ci * @fake: pgt references contiguous guest memory block, not a pgtable 19818c2ecf20Sopenharmony_ci * 19828c2ecf20Sopenharmony_ci * Returns 0 if the shadow page table was found and -EAGAIN if the page 19838c2ecf20Sopenharmony_ci * table was not found. 19848c2ecf20Sopenharmony_ci * 19858c2ecf20Sopenharmony_ci * Called with sg->mm->mmap_lock in read. 19868c2ecf20Sopenharmony_ci */ 19878c2ecf20Sopenharmony_ciint gmap_shadow_pgt_lookup(struct gmap *sg, unsigned long saddr, 19888c2ecf20Sopenharmony_ci unsigned long *pgt, int *dat_protection, 19898c2ecf20Sopenharmony_ci int *fake) 19908c2ecf20Sopenharmony_ci{ 19918c2ecf20Sopenharmony_ci unsigned long *table; 19928c2ecf20Sopenharmony_ci struct page *page; 19938c2ecf20Sopenharmony_ci int rc; 19948c2ecf20Sopenharmony_ci 19958c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 19968c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 19978c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 1); /* get segment pointer */ 19988c2ecf20Sopenharmony_ci if (table && !(*table & _SEGMENT_ENTRY_INVALID)) { 19998c2ecf20Sopenharmony_ci /* Shadow page tables are full pages (pte+pgste) */ 20008c2ecf20Sopenharmony_ci page = pfn_to_page(*table >> PAGE_SHIFT); 20018c2ecf20Sopenharmony_ci *pgt = page->index & ~GMAP_SHADOW_FAKE_TABLE; 20028c2ecf20Sopenharmony_ci *dat_protection = !!(*table & _SEGMENT_ENTRY_PROTECT); 20038c2ecf20Sopenharmony_ci *fake = !!(page->index & GMAP_SHADOW_FAKE_TABLE); 20048c2ecf20Sopenharmony_ci rc = 0; 20058c2ecf20Sopenharmony_ci } else { 20068c2ecf20Sopenharmony_ci rc = -EAGAIN; 20078c2ecf20Sopenharmony_ci } 20088c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 20098c2ecf20Sopenharmony_ci return rc; 20108c2ecf20Sopenharmony_ci 20118c2ecf20Sopenharmony_ci} 20128c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow_pgt_lookup); 20138c2ecf20Sopenharmony_ci 20148c2ecf20Sopenharmony_ci/** 20158c2ecf20Sopenharmony_ci * gmap_shadow_pgt - instantiate a shadow page table 20168c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 20178c2ecf20Sopenharmony_ci * @saddr: faulting address in the shadow gmap 20188c2ecf20Sopenharmony_ci * @pgt: parent gmap address of the page table to get shadowed 20198c2ecf20Sopenharmony_ci * @fake: pgt references contiguous guest memory block, not a pgtable 20208c2ecf20Sopenharmony_ci * 20218c2ecf20Sopenharmony_ci * Returns 0 if successfully shadowed or already shadowed, -EAGAIN if the 20228c2ecf20Sopenharmony_ci * shadow table structure is incomplete, -ENOMEM if out of memory, 20238c2ecf20Sopenharmony_ci * -EFAULT if an address in the parent gmap could not be resolved and 20248c2ecf20Sopenharmony_ci * 20258c2ecf20Sopenharmony_ci * Called with gmap->mm->mmap_lock in read 20268c2ecf20Sopenharmony_ci */ 20278c2ecf20Sopenharmony_ciint gmap_shadow_pgt(struct gmap *sg, unsigned long saddr, unsigned long pgt, 20288c2ecf20Sopenharmony_ci int fake) 20298c2ecf20Sopenharmony_ci{ 20308c2ecf20Sopenharmony_ci unsigned long raddr, origin; 20318c2ecf20Sopenharmony_ci unsigned long *s_pgt, *table; 20328c2ecf20Sopenharmony_ci struct page *page; 20338c2ecf20Sopenharmony_ci int rc; 20348c2ecf20Sopenharmony_ci 20358c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg) || (pgt & _SEGMENT_ENTRY_LARGE)); 20368c2ecf20Sopenharmony_ci /* Allocate a shadow page table */ 20378c2ecf20Sopenharmony_ci page = page_table_alloc_pgste(sg->mm); 20388c2ecf20Sopenharmony_ci if (!page) 20398c2ecf20Sopenharmony_ci return -ENOMEM; 20408c2ecf20Sopenharmony_ci page->index = pgt & _SEGMENT_ENTRY_ORIGIN; 20418c2ecf20Sopenharmony_ci if (fake) 20428c2ecf20Sopenharmony_ci page->index |= GMAP_SHADOW_FAKE_TABLE; 20438c2ecf20Sopenharmony_ci s_pgt = (unsigned long *) page_to_phys(page); 20448c2ecf20Sopenharmony_ci /* Install shadow page table */ 20458c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 20468c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 1); /* get segment pointer */ 20478c2ecf20Sopenharmony_ci if (!table) { 20488c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 20498c2ecf20Sopenharmony_ci goto out_free; 20508c2ecf20Sopenharmony_ci } 20518c2ecf20Sopenharmony_ci if (!(*table & _SEGMENT_ENTRY_INVALID)) { 20528c2ecf20Sopenharmony_ci rc = 0; /* Already established */ 20538c2ecf20Sopenharmony_ci goto out_free; 20548c2ecf20Sopenharmony_ci } else if (*table & _SEGMENT_ENTRY_ORIGIN) { 20558c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with shadow */ 20568c2ecf20Sopenharmony_ci goto out_free; 20578c2ecf20Sopenharmony_ci } 20588c2ecf20Sopenharmony_ci /* mark as invalid as long as the parent table is not protected */ 20598c2ecf20Sopenharmony_ci *table = (unsigned long) s_pgt | _SEGMENT_ENTRY | 20608c2ecf20Sopenharmony_ci (pgt & _SEGMENT_ENTRY_PROTECT) | _SEGMENT_ENTRY_INVALID; 20618c2ecf20Sopenharmony_ci list_add(&page->lru, &sg->pt_list); 20628c2ecf20Sopenharmony_ci if (fake) { 20638c2ecf20Sopenharmony_ci /* nothing to protect for fake tables */ 20648c2ecf20Sopenharmony_ci *table &= ~_SEGMENT_ENTRY_INVALID; 20658c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 20668c2ecf20Sopenharmony_ci return 0; 20678c2ecf20Sopenharmony_ci } 20688c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 20698c2ecf20Sopenharmony_ci /* Make pgt read-only in parent gmap page table (not the pgste) */ 20708c2ecf20Sopenharmony_ci raddr = (saddr & _SEGMENT_MASK) | _SHADOW_RMAP_SEGMENT; 20718c2ecf20Sopenharmony_ci origin = pgt & _SEGMENT_ENTRY_ORIGIN & PAGE_MASK; 20728c2ecf20Sopenharmony_ci rc = gmap_protect_rmap(sg, raddr, origin, PAGE_SIZE); 20738c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 20748c2ecf20Sopenharmony_ci if (!rc) { 20758c2ecf20Sopenharmony_ci table = gmap_table_walk(sg, saddr, 1); 20768c2ecf20Sopenharmony_ci if (!table || (*table & _SEGMENT_ENTRY_ORIGIN) != 20778c2ecf20Sopenharmony_ci (unsigned long) s_pgt) 20788c2ecf20Sopenharmony_ci rc = -EAGAIN; /* Race with unshadow */ 20798c2ecf20Sopenharmony_ci else 20808c2ecf20Sopenharmony_ci *table &= ~_SEGMENT_ENTRY_INVALID; 20818c2ecf20Sopenharmony_ci } else { 20828c2ecf20Sopenharmony_ci gmap_unshadow_pgt(sg, raddr); 20838c2ecf20Sopenharmony_ci } 20848c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 20858c2ecf20Sopenharmony_ci return rc; 20868c2ecf20Sopenharmony_ciout_free: 20878c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 20888c2ecf20Sopenharmony_ci page_table_free_pgste(page); 20898c2ecf20Sopenharmony_ci return rc; 20908c2ecf20Sopenharmony_ci 20918c2ecf20Sopenharmony_ci} 20928c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow_pgt); 20938c2ecf20Sopenharmony_ci 20948c2ecf20Sopenharmony_ci/** 20958c2ecf20Sopenharmony_ci * gmap_shadow_page - create a shadow page mapping 20968c2ecf20Sopenharmony_ci * @sg: pointer to the shadow guest address space structure 20978c2ecf20Sopenharmony_ci * @saddr: faulting address in the shadow gmap 20988c2ecf20Sopenharmony_ci * @pte: pte in parent gmap address space to get shadowed 20998c2ecf20Sopenharmony_ci * 21008c2ecf20Sopenharmony_ci * Returns 0 if successfully shadowed or already shadowed, -EAGAIN if the 21018c2ecf20Sopenharmony_ci * shadow table structure is incomplete, -ENOMEM if out of memory and 21028c2ecf20Sopenharmony_ci * -EFAULT if an address in the parent gmap could not be resolved. 21038c2ecf20Sopenharmony_ci * 21048c2ecf20Sopenharmony_ci * Called with sg->mm->mmap_lock in read. 21058c2ecf20Sopenharmony_ci */ 21068c2ecf20Sopenharmony_ciint gmap_shadow_page(struct gmap *sg, unsigned long saddr, pte_t pte) 21078c2ecf20Sopenharmony_ci{ 21088c2ecf20Sopenharmony_ci struct gmap *parent; 21098c2ecf20Sopenharmony_ci struct gmap_rmap *rmap; 21108c2ecf20Sopenharmony_ci unsigned long vmaddr, paddr; 21118c2ecf20Sopenharmony_ci spinlock_t *ptl; 21128c2ecf20Sopenharmony_ci pte_t *sptep, *tptep; 21138c2ecf20Sopenharmony_ci int prot; 21148c2ecf20Sopenharmony_ci int rc; 21158c2ecf20Sopenharmony_ci 21168c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 21178c2ecf20Sopenharmony_ci parent = sg->parent; 21188c2ecf20Sopenharmony_ci prot = (pte_val(pte) & _PAGE_PROTECT) ? PROT_READ : PROT_WRITE; 21198c2ecf20Sopenharmony_ci 21208c2ecf20Sopenharmony_ci rmap = kzalloc(sizeof(*rmap), GFP_KERNEL); 21218c2ecf20Sopenharmony_ci if (!rmap) 21228c2ecf20Sopenharmony_ci return -ENOMEM; 21238c2ecf20Sopenharmony_ci rmap->raddr = (saddr & PAGE_MASK) | _SHADOW_RMAP_PGTABLE; 21248c2ecf20Sopenharmony_ci 21258c2ecf20Sopenharmony_ci while (1) { 21268c2ecf20Sopenharmony_ci paddr = pte_val(pte) & PAGE_MASK; 21278c2ecf20Sopenharmony_ci vmaddr = __gmap_translate(parent, paddr); 21288c2ecf20Sopenharmony_ci if (IS_ERR_VALUE(vmaddr)) { 21298c2ecf20Sopenharmony_ci rc = vmaddr; 21308c2ecf20Sopenharmony_ci break; 21318c2ecf20Sopenharmony_ci } 21328c2ecf20Sopenharmony_ci rc = radix_tree_preload(GFP_KERNEL); 21338c2ecf20Sopenharmony_ci if (rc) 21348c2ecf20Sopenharmony_ci break; 21358c2ecf20Sopenharmony_ci rc = -EAGAIN; 21368c2ecf20Sopenharmony_ci sptep = gmap_pte_op_walk(parent, paddr, &ptl); 21378c2ecf20Sopenharmony_ci if (sptep) { 21388c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 21398c2ecf20Sopenharmony_ci /* Get page table pointer */ 21408c2ecf20Sopenharmony_ci tptep = (pte_t *) gmap_table_walk(sg, saddr, 0); 21418c2ecf20Sopenharmony_ci if (!tptep) { 21428c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 21438c2ecf20Sopenharmony_ci gmap_pte_op_end(ptl); 21448c2ecf20Sopenharmony_ci radix_tree_preload_end(); 21458c2ecf20Sopenharmony_ci break; 21468c2ecf20Sopenharmony_ci } 21478c2ecf20Sopenharmony_ci rc = ptep_shadow_pte(sg->mm, saddr, sptep, tptep, pte); 21488c2ecf20Sopenharmony_ci if (rc > 0) { 21498c2ecf20Sopenharmony_ci /* Success and a new mapping */ 21508c2ecf20Sopenharmony_ci gmap_insert_rmap(sg, vmaddr, rmap); 21518c2ecf20Sopenharmony_ci rmap = NULL; 21528c2ecf20Sopenharmony_ci rc = 0; 21538c2ecf20Sopenharmony_ci } 21548c2ecf20Sopenharmony_ci gmap_pte_op_end(ptl); 21558c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 21568c2ecf20Sopenharmony_ci } 21578c2ecf20Sopenharmony_ci radix_tree_preload_end(); 21588c2ecf20Sopenharmony_ci if (!rc) 21598c2ecf20Sopenharmony_ci break; 21608c2ecf20Sopenharmony_ci rc = gmap_pte_op_fixup(parent, paddr, vmaddr, prot); 21618c2ecf20Sopenharmony_ci if (rc) 21628c2ecf20Sopenharmony_ci break; 21638c2ecf20Sopenharmony_ci } 21648c2ecf20Sopenharmony_ci kfree(rmap); 21658c2ecf20Sopenharmony_ci return rc; 21668c2ecf20Sopenharmony_ci} 21678c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_shadow_page); 21688c2ecf20Sopenharmony_ci 21698c2ecf20Sopenharmony_ci/** 21708c2ecf20Sopenharmony_ci * gmap_shadow_notify - handle notifications for shadow gmap 21718c2ecf20Sopenharmony_ci * 21728c2ecf20Sopenharmony_ci * Called with sg->parent->shadow_lock. 21738c2ecf20Sopenharmony_ci */ 21748c2ecf20Sopenharmony_cistatic void gmap_shadow_notify(struct gmap *sg, unsigned long vmaddr, 21758c2ecf20Sopenharmony_ci unsigned long gaddr) 21768c2ecf20Sopenharmony_ci{ 21778c2ecf20Sopenharmony_ci struct gmap_rmap *rmap, *rnext, *head; 21788c2ecf20Sopenharmony_ci unsigned long start, end, bits, raddr; 21798c2ecf20Sopenharmony_ci 21808c2ecf20Sopenharmony_ci BUG_ON(!gmap_is_shadow(sg)); 21818c2ecf20Sopenharmony_ci 21828c2ecf20Sopenharmony_ci spin_lock(&sg->guest_table_lock); 21838c2ecf20Sopenharmony_ci if (sg->removed) { 21848c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 21858c2ecf20Sopenharmony_ci return; 21868c2ecf20Sopenharmony_ci } 21878c2ecf20Sopenharmony_ci /* Check for top level table */ 21888c2ecf20Sopenharmony_ci start = sg->orig_asce & _ASCE_ORIGIN; 21898c2ecf20Sopenharmony_ci end = start + ((sg->orig_asce & _ASCE_TABLE_LENGTH) + 1) * PAGE_SIZE; 21908c2ecf20Sopenharmony_ci if (!(sg->orig_asce & _ASCE_REAL_SPACE) && gaddr >= start && 21918c2ecf20Sopenharmony_ci gaddr < end) { 21928c2ecf20Sopenharmony_ci /* The complete shadow table has to go */ 21938c2ecf20Sopenharmony_ci gmap_unshadow(sg); 21948c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 21958c2ecf20Sopenharmony_ci list_del(&sg->list); 21968c2ecf20Sopenharmony_ci gmap_put(sg); 21978c2ecf20Sopenharmony_ci return; 21988c2ecf20Sopenharmony_ci } 21998c2ecf20Sopenharmony_ci /* Remove the page table tree from on specific entry */ 22008c2ecf20Sopenharmony_ci head = radix_tree_delete(&sg->host_to_rmap, vmaddr >> PAGE_SHIFT); 22018c2ecf20Sopenharmony_ci gmap_for_each_rmap_safe(rmap, rnext, head) { 22028c2ecf20Sopenharmony_ci bits = rmap->raddr & _SHADOW_RMAP_MASK; 22038c2ecf20Sopenharmony_ci raddr = rmap->raddr ^ bits; 22048c2ecf20Sopenharmony_ci switch (bits) { 22058c2ecf20Sopenharmony_ci case _SHADOW_RMAP_REGION1: 22068c2ecf20Sopenharmony_ci gmap_unshadow_r2t(sg, raddr); 22078c2ecf20Sopenharmony_ci break; 22088c2ecf20Sopenharmony_ci case _SHADOW_RMAP_REGION2: 22098c2ecf20Sopenharmony_ci gmap_unshadow_r3t(sg, raddr); 22108c2ecf20Sopenharmony_ci break; 22118c2ecf20Sopenharmony_ci case _SHADOW_RMAP_REGION3: 22128c2ecf20Sopenharmony_ci gmap_unshadow_sgt(sg, raddr); 22138c2ecf20Sopenharmony_ci break; 22148c2ecf20Sopenharmony_ci case _SHADOW_RMAP_SEGMENT: 22158c2ecf20Sopenharmony_ci gmap_unshadow_pgt(sg, raddr); 22168c2ecf20Sopenharmony_ci break; 22178c2ecf20Sopenharmony_ci case _SHADOW_RMAP_PGTABLE: 22188c2ecf20Sopenharmony_ci gmap_unshadow_page(sg, raddr); 22198c2ecf20Sopenharmony_ci break; 22208c2ecf20Sopenharmony_ci } 22218c2ecf20Sopenharmony_ci kfree(rmap); 22228c2ecf20Sopenharmony_ci } 22238c2ecf20Sopenharmony_ci spin_unlock(&sg->guest_table_lock); 22248c2ecf20Sopenharmony_ci} 22258c2ecf20Sopenharmony_ci 22268c2ecf20Sopenharmony_ci/** 22278c2ecf20Sopenharmony_ci * ptep_notify - call all invalidation callbacks for a specific pte. 22288c2ecf20Sopenharmony_ci * @mm: pointer to the process mm_struct 22298c2ecf20Sopenharmony_ci * @addr: virtual address in the process address space 22308c2ecf20Sopenharmony_ci * @pte: pointer to the page table entry 22318c2ecf20Sopenharmony_ci * @bits: bits from the pgste that caused the notify call 22328c2ecf20Sopenharmony_ci * 22338c2ecf20Sopenharmony_ci * This function is assumed to be called with the page table lock held 22348c2ecf20Sopenharmony_ci * for the pte to notify. 22358c2ecf20Sopenharmony_ci */ 22368c2ecf20Sopenharmony_civoid ptep_notify(struct mm_struct *mm, unsigned long vmaddr, 22378c2ecf20Sopenharmony_ci pte_t *pte, unsigned long bits) 22388c2ecf20Sopenharmony_ci{ 22398c2ecf20Sopenharmony_ci unsigned long offset, gaddr = 0; 22408c2ecf20Sopenharmony_ci unsigned long *table; 22418c2ecf20Sopenharmony_ci struct gmap *gmap, *sg, *next; 22428c2ecf20Sopenharmony_ci 22438c2ecf20Sopenharmony_ci offset = ((unsigned long) pte) & (255 * sizeof(pte_t)); 22448c2ecf20Sopenharmony_ci offset = offset * (PAGE_SIZE / sizeof(pte_t)); 22458c2ecf20Sopenharmony_ci rcu_read_lock(); 22468c2ecf20Sopenharmony_ci list_for_each_entry_rcu(gmap, &mm->context.gmap_list, list) { 22478c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 22488c2ecf20Sopenharmony_ci table = radix_tree_lookup(&gmap->host_to_guest, 22498c2ecf20Sopenharmony_ci vmaddr >> PMD_SHIFT); 22508c2ecf20Sopenharmony_ci if (table) 22518c2ecf20Sopenharmony_ci gaddr = __gmap_segment_gaddr(table) + offset; 22528c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 22538c2ecf20Sopenharmony_ci if (!table) 22548c2ecf20Sopenharmony_ci continue; 22558c2ecf20Sopenharmony_ci 22568c2ecf20Sopenharmony_ci if (!list_empty(&gmap->children) && (bits & PGSTE_VSIE_BIT)) { 22578c2ecf20Sopenharmony_ci spin_lock(&gmap->shadow_lock); 22588c2ecf20Sopenharmony_ci list_for_each_entry_safe(sg, next, 22598c2ecf20Sopenharmony_ci &gmap->children, list) 22608c2ecf20Sopenharmony_ci gmap_shadow_notify(sg, vmaddr, gaddr); 22618c2ecf20Sopenharmony_ci spin_unlock(&gmap->shadow_lock); 22628c2ecf20Sopenharmony_ci } 22638c2ecf20Sopenharmony_ci if (bits & PGSTE_IN_BIT) 22648c2ecf20Sopenharmony_ci gmap_call_notifier(gmap, gaddr, gaddr + PAGE_SIZE - 1); 22658c2ecf20Sopenharmony_ci } 22668c2ecf20Sopenharmony_ci rcu_read_unlock(); 22678c2ecf20Sopenharmony_ci} 22688c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ptep_notify); 22698c2ecf20Sopenharmony_ci 22708c2ecf20Sopenharmony_cistatic void pmdp_notify_gmap(struct gmap *gmap, pmd_t *pmdp, 22718c2ecf20Sopenharmony_ci unsigned long gaddr) 22728c2ecf20Sopenharmony_ci{ 22738c2ecf20Sopenharmony_ci pmd_val(*pmdp) &= ~_SEGMENT_ENTRY_GMAP_IN; 22748c2ecf20Sopenharmony_ci gmap_call_notifier(gmap, gaddr, gaddr + HPAGE_SIZE - 1); 22758c2ecf20Sopenharmony_ci} 22768c2ecf20Sopenharmony_ci 22778c2ecf20Sopenharmony_ci/** 22788c2ecf20Sopenharmony_ci * gmap_pmdp_xchg - exchange a gmap pmd with another 22798c2ecf20Sopenharmony_ci * @gmap: pointer to the guest address space structure 22808c2ecf20Sopenharmony_ci * @pmdp: pointer to the pmd entry 22818c2ecf20Sopenharmony_ci * @new: replacement entry 22828c2ecf20Sopenharmony_ci * @gaddr: the affected guest address 22838c2ecf20Sopenharmony_ci * 22848c2ecf20Sopenharmony_ci * This function is assumed to be called with the guest_table_lock 22858c2ecf20Sopenharmony_ci * held. 22868c2ecf20Sopenharmony_ci */ 22878c2ecf20Sopenharmony_cistatic void gmap_pmdp_xchg(struct gmap *gmap, pmd_t *pmdp, pmd_t new, 22888c2ecf20Sopenharmony_ci unsigned long gaddr) 22898c2ecf20Sopenharmony_ci{ 22908c2ecf20Sopenharmony_ci gaddr &= HPAGE_MASK; 22918c2ecf20Sopenharmony_ci pmdp_notify_gmap(gmap, pmdp, gaddr); 22928c2ecf20Sopenharmony_ci pmd_val(new) &= ~_SEGMENT_ENTRY_GMAP_IN; 22938c2ecf20Sopenharmony_ci if (MACHINE_HAS_TLB_GUEST) 22948c2ecf20Sopenharmony_ci __pmdp_idte(gaddr, (pmd_t *)pmdp, IDTE_GUEST_ASCE, gmap->asce, 22958c2ecf20Sopenharmony_ci IDTE_GLOBAL); 22968c2ecf20Sopenharmony_ci else if (MACHINE_HAS_IDTE) 22978c2ecf20Sopenharmony_ci __pmdp_idte(gaddr, (pmd_t *)pmdp, 0, 0, IDTE_GLOBAL); 22988c2ecf20Sopenharmony_ci else 22998c2ecf20Sopenharmony_ci __pmdp_csp(pmdp); 23008c2ecf20Sopenharmony_ci *pmdp = new; 23018c2ecf20Sopenharmony_ci} 23028c2ecf20Sopenharmony_ci 23038c2ecf20Sopenharmony_cistatic void gmap_pmdp_clear(struct mm_struct *mm, unsigned long vmaddr, 23048c2ecf20Sopenharmony_ci int purge) 23058c2ecf20Sopenharmony_ci{ 23068c2ecf20Sopenharmony_ci pmd_t *pmdp; 23078c2ecf20Sopenharmony_ci struct gmap *gmap; 23088c2ecf20Sopenharmony_ci unsigned long gaddr; 23098c2ecf20Sopenharmony_ci 23108c2ecf20Sopenharmony_ci rcu_read_lock(); 23118c2ecf20Sopenharmony_ci list_for_each_entry_rcu(gmap, &mm->context.gmap_list, list) { 23128c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 23138c2ecf20Sopenharmony_ci pmdp = (pmd_t *)radix_tree_delete(&gmap->host_to_guest, 23148c2ecf20Sopenharmony_ci vmaddr >> PMD_SHIFT); 23158c2ecf20Sopenharmony_ci if (pmdp) { 23168c2ecf20Sopenharmony_ci gaddr = __gmap_segment_gaddr((unsigned long *)pmdp); 23178c2ecf20Sopenharmony_ci pmdp_notify_gmap(gmap, pmdp, gaddr); 23188c2ecf20Sopenharmony_ci WARN_ON(pmd_val(*pmdp) & ~(_SEGMENT_ENTRY_HARDWARE_BITS_LARGE | 23198c2ecf20Sopenharmony_ci _SEGMENT_ENTRY_GMAP_UC)); 23208c2ecf20Sopenharmony_ci if (purge) 23218c2ecf20Sopenharmony_ci __pmdp_csp(pmdp); 23228c2ecf20Sopenharmony_ci pmd_val(*pmdp) = _SEGMENT_ENTRY_EMPTY; 23238c2ecf20Sopenharmony_ci } 23248c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 23258c2ecf20Sopenharmony_ci } 23268c2ecf20Sopenharmony_ci rcu_read_unlock(); 23278c2ecf20Sopenharmony_ci} 23288c2ecf20Sopenharmony_ci 23298c2ecf20Sopenharmony_ci/** 23308c2ecf20Sopenharmony_ci * gmap_pmdp_invalidate - invalidate all affected guest pmd entries without 23318c2ecf20Sopenharmony_ci * flushing 23328c2ecf20Sopenharmony_ci * @mm: pointer to the process mm_struct 23338c2ecf20Sopenharmony_ci * @vmaddr: virtual address in the process address space 23348c2ecf20Sopenharmony_ci */ 23358c2ecf20Sopenharmony_civoid gmap_pmdp_invalidate(struct mm_struct *mm, unsigned long vmaddr) 23368c2ecf20Sopenharmony_ci{ 23378c2ecf20Sopenharmony_ci gmap_pmdp_clear(mm, vmaddr, 0); 23388c2ecf20Sopenharmony_ci} 23398c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_pmdp_invalidate); 23408c2ecf20Sopenharmony_ci 23418c2ecf20Sopenharmony_ci/** 23428c2ecf20Sopenharmony_ci * gmap_pmdp_csp - csp all affected guest pmd entries 23438c2ecf20Sopenharmony_ci * @mm: pointer to the process mm_struct 23448c2ecf20Sopenharmony_ci * @vmaddr: virtual address in the process address space 23458c2ecf20Sopenharmony_ci */ 23468c2ecf20Sopenharmony_civoid gmap_pmdp_csp(struct mm_struct *mm, unsigned long vmaddr) 23478c2ecf20Sopenharmony_ci{ 23488c2ecf20Sopenharmony_ci gmap_pmdp_clear(mm, vmaddr, 1); 23498c2ecf20Sopenharmony_ci} 23508c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_pmdp_csp); 23518c2ecf20Sopenharmony_ci 23528c2ecf20Sopenharmony_ci/** 23538c2ecf20Sopenharmony_ci * gmap_pmdp_idte_local - invalidate and clear a guest pmd entry 23548c2ecf20Sopenharmony_ci * @mm: pointer to the process mm_struct 23558c2ecf20Sopenharmony_ci * @vmaddr: virtual address in the process address space 23568c2ecf20Sopenharmony_ci */ 23578c2ecf20Sopenharmony_civoid gmap_pmdp_idte_local(struct mm_struct *mm, unsigned long vmaddr) 23588c2ecf20Sopenharmony_ci{ 23598c2ecf20Sopenharmony_ci unsigned long *entry, gaddr; 23608c2ecf20Sopenharmony_ci struct gmap *gmap; 23618c2ecf20Sopenharmony_ci pmd_t *pmdp; 23628c2ecf20Sopenharmony_ci 23638c2ecf20Sopenharmony_ci rcu_read_lock(); 23648c2ecf20Sopenharmony_ci list_for_each_entry_rcu(gmap, &mm->context.gmap_list, list) { 23658c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 23668c2ecf20Sopenharmony_ci entry = radix_tree_delete(&gmap->host_to_guest, 23678c2ecf20Sopenharmony_ci vmaddr >> PMD_SHIFT); 23688c2ecf20Sopenharmony_ci if (entry) { 23698c2ecf20Sopenharmony_ci pmdp = (pmd_t *)entry; 23708c2ecf20Sopenharmony_ci gaddr = __gmap_segment_gaddr(entry); 23718c2ecf20Sopenharmony_ci pmdp_notify_gmap(gmap, pmdp, gaddr); 23728c2ecf20Sopenharmony_ci WARN_ON(*entry & ~(_SEGMENT_ENTRY_HARDWARE_BITS_LARGE | 23738c2ecf20Sopenharmony_ci _SEGMENT_ENTRY_GMAP_UC)); 23748c2ecf20Sopenharmony_ci if (MACHINE_HAS_TLB_GUEST) 23758c2ecf20Sopenharmony_ci __pmdp_idte(gaddr, pmdp, IDTE_GUEST_ASCE, 23768c2ecf20Sopenharmony_ci gmap->asce, IDTE_LOCAL); 23778c2ecf20Sopenharmony_ci else if (MACHINE_HAS_IDTE) 23788c2ecf20Sopenharmony_ci __pmdp_idte(gaddr, pmdp, 0, 0, IDTE_LOCAL); 23798c2ecf20Sopenharmony_ci *entry = _SEGMENT_ENTRY_EMPTY; 23808c2ecf20Sopenharmony_ci } 23818c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 23828c2ecf20Sopenharmony_ci } 23838c2ecf20Sopenharmony_ci rcu_read_unlock(); 23848c2ecf20Sopenharmony_ci} 23858c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_pmdp_idte_local); 23868c2ecf20Sopenharmony_ci 23878c2ecf20Sopenharmony_ci/** 23888c2ecf20Sopenharmony_ci * gmap_pmdp_idte_global - invalidate and clear a guest pmd entry 23898c2ecf20Sopenharmony_ci * @mm: pointer to the process mm_struct 23908c2ecf20Sopenharmony_ci * @vmaddr: virtual address in the process address space 23918c2ecf20Sopenharmony_ci */ 23928c2ecf20Sopenharmony_civoid gmap_pmdp_idte_global(struct mm_struct *mm, unsigned long vmaddr) 23938c2ecf20Sopenharmony_ci{ 23948c2ecf20Sopenharmony_ci unsigned long *entry, gaddr; 23958c2ecf20Sopenharmony_ci struct gmap *gmap; 23968c2ecf20Sopenharmony_ci pmd_t *pmdp; 23978c2ecf20Sopenharmony_ci 23988c2ecf20Sopenharmony_ci rcu_read_lock(); 23998c2ecf20Sopenharmony_ci list_for_each_entry_rcu(gmap, &mm->context.gmap_list, list) { 24008c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 24018c2ecf20Sopenharmony_ci entry = radix_tree_delete(&gmap->host_to_guest, 24028c2ecf20Sopenharmony_ci vmaddr >> PMD_SHIFT); 24038c2ecf20Sopenharmony_ci if (entry) { 24048c2ecf20Sopenharmony_ci pmdp = (pmd_t *)entry; 24058c2ecf20Sopenharmony_ci gaddr = __gmap_segment_gaddr(entry); 24068c2ecf20Sopenharmony_ci pmdp_notify_gmap(gmap, pmdp, gaddr); 24078c2ecf20Sopenharmony_ci WARN_ON(*entry & ~(_SEGMENT_ENTRY_HARDWARE_BITS_LARGE | 24088c2ecf20Sopenharmony_ci _SEGMENT_ENTRY_GMAP_UC)); 24098c2ecf20Sopenharmony_ci if (MACHINE_HAS_TLB_GUEST) 24108c2ecf20Sopenharmony_ci __pmdp_idte(gaddr, pmdp, IDTE_GUEST_ASCE, 24118c2ecf20Sopenharmony_ci gmap->asce, IDTE_GLOBAL); 24128c2ecf20Sopenharmony_ci else if (MACHINE_HAS_IDTE) 24138c2ecf20Sopenharmony_ci __pmdp_idte(gaddr, pmdp, 0, 0, IDTE_GLOBAL); 24148c2ecf20Sopenharmony_ci else 24158c2ecf20Sopenharmony_ci __pmdp_csp(pmdp); 24168c2ecf20Sopenharmony_ci *entry = _SEGMENT_ENTRY_EMPTY; 24178c2ecf20Sopenharmony_ci } 24188c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 24198c2ecf20Sopenharmony_ci } 24208c2ecf20Sopenharmony_ci rcu_read_unlock(); 24218c2ecf20Sopenharmony_ci} 24228c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_pmdp_idte_global); 24238c2ecf20Sopenharmony_ci 24248c2ecf20Sopenharmony_ci/** 24258c2ecf20Sopenharmony_ci * gmap_test_and_clear_dirty_pmd - test and reset segment dirty status 24268c2ecf20Sopenharmony_ci * @gmap: pointer to guest address space 24278c2ecf20Sopenharmony_ci * @pmdp: pointer to the pmd to be tested 24288c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 24298c2ecf20Sopenharmony_ci * 24308c2ecf20Sopenharmony_ci * This function is assumed to be called with the guest_table_lock 24318c2ecf20Sopenharmony_ci * held. 24328c2ecf20Sopenharmony_ci */ 24338c2ecf20Sopenharmony_cistatic bool gmap_test_and_clear_dirty_pmd(struct gmap *gmap, pmd_t *pmdp, 24348c2ecf20Sopenharmony_ci unsigned long gaddr) 24358c2ecf20Sopenharmony_ci{ 24368c2ecf20Sopenharmony_ci if (pmd_val(*pmdp) & _SEGMENT_ENTRY_INVALID) 24378c2ecf20Sopenharmony_ci return false; 24388c2ecf20Sopenharmony_ci 24398c2ecf20Sopenharmony_ci /* Already protected memory, which did not change is clean */ 24408c2ecf20Sopenharmony_ci if (pmd_val(*pmdp) & _SEGMENT_ENTRY_PROTECT && 24418c2ecf20Sopenharmony_ci !(pmd_val(*pmdp) & _SEGMENT_ENTRY_GMAP_UC)) 24428c2ecf20Sopenharmony_ci return false; 24438c2ecf20Sopenharmony_ci 24448c2ecf20Sopenharmony_ci /* Clear UC indication and reset protection */ 24458c2ecf20Sopenharmony_ci pmd_val(*pmdp) &= ~_SEGMENT_ENTRY_GMAP_UC; 24468c2ecf20Sopenharmony_ci gmap_protect_pmd(gmap, gaddr, pmdp, PROT_READ, 0); 24478c2ecf20Sopenharmony_ci return true; 24488c2ecf20Sopenharmony_ci} 24498c2ecf20Sopenharmony_ci 24508c2ecf20Sopenharmony_ci/** 24518c2ecf20Sopenharmony_ci * gmap_sync_dirty_log_pmd - set bitmap based on dirty status of segment 24528c2ecf20Sopenharmony_ci * @gmap: pointer to guest address space 24538c2ecf20Sopenharmony_ci * @bitmap: dirty bitmap for this pmd 24548c2ecf20Sopenharmony_ci * @gaddr: virtual address in the guest address space 24558c2ecf20Sopenharmony_ci * @vmaddr: virtual address in the host address space 24568c2ecf20Sopenharmony_ci * 24578c2ecf20Sopenharmony_ci * This function is assumed to be called with the guest_table_lock 24588c2ecf20Sopenharmony_ci * held. 24598c2ecf20Sopenharmony_ci */ 24608c2ecf20Sopenharmony_civoid gmap_sync_dirty_log_pmd(struct gmap *gmap, unsigned long bitmap[4], 24618c2ecf20Sopenharmony_ci unsigned long gaddr, unsigned long vmaddr) 24628c2ecf20Sopenharmony_ci{ 24638c2ecf20Sopenharmony_ci int i; 24648c2ecf20Sopenharmony_ci pmd_t *pmdp; 24658c2ecf20Sopenharmony_ci pte_t *ptep; 24668c2ecf20Sopenharmony_ci spinlock_t *ptl; 24678c2ecf20Sopenharmony_ci 24688c2ecf20Sopenharmony_ci pmdp = gmap_pmd_op_walk(gmap, gaddr); 24698c2ecf20Sopenharmony_ci if (!pmdp) 24708c2ecf20Sopenharmony_ci return; 24718c2ecf20Sopenharmony_ci 24728c2ecf20Sopenharmony_ci if (pmd_large(*pmdp)) { 24738c2ecf20Sopenharmony_ci if (gmap_test_and_clear_dirty_pmd(gmap, pmdp, gaddr)) 24748c2ecf20Sopenharmony_ci bitmap_fill(bitmap, _PAGE_ENTRIES); 24758c2ecf20Sopenharmony_ci } else { 24768c2ecf20Sopenharmony_ci for (i = 0; i < _PAGE_ENTRIES; i++, vmaddr += PAGE_SIZE) { 24778c2ecf20Sopenharmony_ci ptep = pte_alloc_map_lock(gmap->mm, pmdp, vmaddr, &ptl); 24788c2ecf20Sopenharmony_ci if (!ptep) 24798c2ecf20Sopenharmony_ci continue; 24808c2ecf20Sopenharmony_ci if (ptep_test_and_clear_uc(gmap->mm, vmaddr, ptep)) 24818c2ecf20Sopenharmony_ci set_bit(i, bitmap); 24828c2ecf20Sopenharmony_ci spin_unlock(ptl); 24838c2ecf20Sopenharmony_ci } 24848c2ecf20Sopenharmony_ci } 24858c2ecf20Sopenharmony_ci gmap_pmd_op_end(gmap, pmdp); 24868c2ecf20Sopenharmony_ci} 24878c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_sync_dirty_log_pmd); 24888c2ecf20Sopenharmony_ci 24898c2ecf20Sopenharmony_ci#ifdef CONFIG_TRANSPARENT_HUGEPAGE 24908c2ecf20Sopenharmony_cistatic int thp_split_walk_pmd_entry(pmd_t *pmd, unsigned long addr, 24918c2ecf20Sopenharmony_ci unsigned long end, struct mm_walk *walk) 24928c2ecf20Sopenharmony_ci{ 24938c2ecf20Sopenharmony_ci struct vm_area_struct *vma = walk->vma; 24948c2ecf20Sopenharmony_ci 24958c2ecf20Sopenharmony_ci split_huge_pmd(vma, pmd, addr); 24968c2ecf20Sopenharmony_ci return 0; 24978c2ecf20Sopenharmony_ci} 24988c2ecf20Sopenharmony_ci 24998c2ecf20Sopenharmony_cistatic const struct mm_walk_ops thp_split_walk_ops = { 25008c2ecf20Sopenharmony_ci .pmd_entry = thp_split_walk_pmd_entry, 25018c2ecf20Sopenharmony_ci}; 25028c2ecf20Sopenharmony_ci 25038c2ecf20Sopenharmony_cistatic inline void thp_split_mm(struct mm_struct *mm) 25048c2ecf20Sopenharmony_ci{ 25058c2ecf20Sopenharmony_ci struct vm_area_struct *vma; 25068c2ecf20Sopenharmony_ci 25078c2ecf20Sopenharmony_ci for (vma = mm->mmap; vma != NULL; vma = vma->vm_next) { 25088c2ecf20Sopenharmony_ci vma->vm_flags &= ~VM_HUGEPAGE; 25098c2ecf20Sopenharmony_ci vma->vm_flags |= VM_NOHUGEPAGE; 25108c2ecf20Sopenharmony_ci walk_page_vma(vma, &thp_split_walk_ops, NULL); 25118c2ecf20Sopenharmony_ci } 25128c2ecf20Sopenharmony_ci mm->def_flags |= VM_NOHUGEPAGE; 25138c2ecf20Sopenharmony_ci} 25148c2ecf20Sopenharmony_ci#else 25158c2ecf20Sopenharmony_cistatic inline void thp_split_mm(struct mm_struct *mm) 25168c2ecf20Sopenharmony_ci{ 25178c2ecf20Sopenharmony_ci} 25188c2ecf20Sopenharmony_ci#endif /* CONFIG_TRANSPARENT_HUGEPAGE */ 25198c2ecf20Sopenharmony_ci 25208c2ecf20Sopenharmony_ci/* 25218c2ecf20Sopenharmony_ci * Remove all empty zero pages from the mapping for lazy refaulting 25228c2ecf20Sopenharmony_ci * - This must be called after mm->context.has_pgste is set, to avoid 25238c2ecf20Sopenharmony_ci * future creation of zero pages 25248c2ecf20Sopenharmony_ci * - This must be called after THP was enabled 25258c2ecf20Sopenharmony_ci */ 25268c2ecf20Sopenharmony_cistatic int __zap_zero_pages(pmd_t *pmd, unsigned long start, 25278c2ecf20Sopenharmony_ci unsigned long end, struct mm_walk *walk) 25288c2ecf20Sopenharmony_ci{ 25298c2ecf20Sopenharmony_ci unsigned long addr; 25308c2ecf20Sopenharmony_ci 25318c2ecf20Sopenharmony_ci for (addr = start; addr != end; addr += PAGE_SIZE) { 25328c2ecf20Sopenharmony_ci pte_t *ptep; 25338c2ecf20Sopenharmony_ci spinlock_t *ptl; 25348c2ecf20Sopenharmony_ci 25358c2ecf20Sopenharmony_ci ptep = pte_offset_map_lock(walk->mm, pmd, addr, &ptl); 25368c2ecf20Sopenharmony_ci if (is_zero_pfn(pte_pfn(*ptep))) 25378c2ecf20Sopenharmony_ci ptep_xchg_direct(walk->mm, addr, ptep, __pte(_PAGE_INVALID)); 25388c2ecf20Sopenharmony_ci pte_unmap_unlock(ptep, ptl); 25398c2ecf20Sopenharmony_ci } 25408c2ecf20Sopenharmony_ci return 0; 25418c2ecf20Sopenharmony_ci} 25428c2ecf20Sopenharmony_ci 25438c2ecf20Sopenharmony_cistatic const struct mm_walk_ops zap_zero_walk_ops = { 25448c2ecf20Sopenharmony_ci .pmd_entry = __zap_zero_pages, 25458c2ecf20Sopenharmony_ci}; 25468c2ecf20Sopenharmony_ci 25478c2ecf20Sopenharmony_ci/* 25488c2ecf20Sopenharmony_ci * switch on pgstes for its userspace process (for kvm) 25498c2ecf20Sopenharmony_ci */ 25508c2ecf20Sopenharmony_ciint s390_enable_sie(void) 25518c2ecf20Sopenharmony_ci{ 25528c2ecf20Sopenharmony_ci struct mm_struct *mm = current->mm; 25538c2ecf20Sopenharmony_ci 25548c2ecf20Sopenharmony_ci /* Do we have pgstes? if yes, we are done */ 25558c2ecf20Sopenharmony_ci if (mm_has_pgste(mm)) 25568c2ecf20Sopenharmony_ci return 0; 25578c2ecf20Sopenharmony_ci /* Fail if the page tables are 2K */ 25588c2ecf20Sopenharmony_ci if (!mm_alloc_pgste(mm)) 25598c2ecf20Sopenharmony_ci return -EINVAL; 25608c2ecf20Sopenharmony_ci mmap_write_lock(mm); 25618c2ecf20Sopenharmony_ci mm->context.has_pgste = 1; 25628c2ecf20Sopenharmony_ci /* split thp mappings and disable thp for future mappings */ 25638c2ecf20Sopenharmony_ci thp_split_mm(mm); 25648c2ecf20Sopenharmony_ci walk_page_range(mm, 0, TASK_SIZE, &zap_zero_walk_ops, NULL); 25658c2ecf20Sopenharmony_ci mmap_write_unlock(mm); 25668c2ecf20Sopenharmony_ci return 0; 25678c2ecf20Sopenharmony_ci} 25688c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s390_enable_sie); 25698c2ecf20Sopenharmony_ci 25708c2ecf20Sopenharmony_ciint gmap_mark_unmergeable(void) 25718c2ecf20Sopenharmony_ci{ 25728c2ecf20Sopenharmony_ci struct mm_struct *mm = current->mm; 25738c2ecf20Sopenharmony_ci struct vm_area_struct *vma; 25748c2ecf20Sopenharmony_ci int ret; 25758c2ecf20Sopenharmony_ci 25768c2ecf20Sopenharmony_ci for (vma = mm->mmap; vma; vma = vma->vm_next) { 25778c2ecf20Sopenharmony_ci ret = ksm_madvise(vma, vma->vm_start, vma->vm_end, 25788c2ecf20Sopenharmony_ci MADV_UNMERGEABLE, &vma->vm_flags); 25798c2ecf20Sopenharmony_ci if (ret) 25808c2ecf20Sopenharmony_ci return ret; 25818c2ecf20Sopenharmony_ci } 25828c2ecf20Sopenharmony_ci mm->def_flags &= ~VM_MERGEABLE; 25838c2ecf20Sopenharmony_ci return 0; 25848c2ecf20Sopenharmony_ci} 25858c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(gmap_mark_unmergeable); 25868c2ecf20Sopenharmony_ci 25878c2ecf20Sopenharmony_ci/* 25888c2ecf20Sopenharmony_ci * Enable storage key handling from now on and initialize the storage 25898c2ecf20Sopenharmony_ci * keys with the default key. 25908c2ecf20Sopenharmony_ci */ 25918c2ecf20Sopenharmony_cistatic int __s390_enable_skey_pte(pte_t *pte, unsigned long addr, 25928c2ecf20Sopenharmony_ci unsigned long next, struct mm_walk *walk) 25938c2ecf20Sopenharmony_ci{ 25948c2ecf20Sopenharmony_ci /* Clear storage key */ 25958c2ecf20Sopenharmony_ci ptep_zap_key(walk->mm, addr, pte); 25968c2ecf20Sopenharmony_ci return 0; 25978c2ecf20Sopenharmony_ci} 25988c2ecf20Sopenharmony_ci 25998c2ecf20Sopenharmony_ci/* 26008c2ecf20Sopenharmony_ci * Give a chance to schedule after setting a key to 256 pages. 26018c2ecf20Sopenharmony_ci * We only hold the mm lock, which is a rwsem and the kvm srcu. 26028c2ecf20Sopenharmony_ci * Both can sleep. 26038c2ecf20Sopenharmony_ci */ 26048c2ecf20Sopenharmony_cistatic int __s390_enable_skey_pmd(pmd_t *pmd, unsigned long addr, 26058c2ecf20Sopenharmony_ci unsigned long next, struct mm_walk *walk) 26068c2ecf20Sopenharmony_ci{ 26078c2ecf20Sopenharmony_ci cond_resched(); 26088c2ecf20Sopenharmony_ci return 0; 26098c2ecf20Sopenharmony_ci} 26108c2ecf20Sopenharmony_ci 26118c2ecf20Sopenharmony_cistatic int __s390_enable_skey_hugetlb(pte_t *pte, unsigned long addr, 26128c2ecf20Sopenharmony_ci unsigned long hmask, unsigned long next, 26138c2ecf20Sopenharmony_ci struct mm_walk *walk) 26148c2ecf20Sopenharmony_ci{ 26158c2ecf20Sopenharmony_ci pmd_t *pmd = (pmd_t *)pte; 26168c2ecf20Sopenharmony_ci unsigned long start, end; 26178c2ecf20Sopenharmony_ci struct page *page = pmd_page(*pmd); 26188c2ecf20Sopenharmony_ci 26198c2ecf20Sopenharmony_ci /* 26208c2ecf20Sopenharmony_ci * The write check makes sure we do not set a key on shared 26218c2ecf20Sopenharmony_ci * memory. This is needed as the walker does not differentiate 26228c2ecf20Sopenharmony_ci * between actual guest memory and the process executable or 26238c2ecf20Sopenharmony_ci * shared libraries. 26248c2ecf20Sopenharmony_ci */ 26258c2ecf20Sopenharmony_ci if (pmd_val(*pmd) & _SEGMENT_ENTRY_INVALID || 26268c2ecf20Sopenharmony_ci !(pmd_val(*pmd) & _SEGMENT_ENTRY_WRITE)) 26278c2ecf20Sopenharmony_ci return 0; 26288c2ecf20Sopenharmony_ci 26298c2ecf20Sopenharmony_ci start = pmd_val(*pmd) & HPAGE_MASK; 26308c2ecf20Sopenharmony_ci end = start + HPAGE_SIZE - 1; 26318c2ecf20Sopenharmony_ci __storage_key_init_range(start, end); 26328c2ecf20Sopenharmony_ci set_bit(PG_arch_1, &page->flags); 26338c2ecf20Sopenharmony_ci cond_resched(); 26348c2ecf20Sopenharmony_ci return 0; 26358c2ecf20Sopenharmony_ci} 26368c2ecf20Sopenharmony_ci 26378c2ecf20Sopenharmony_cistatic const struct mm_walk_ops enable_skey_walk_ops = { 26388c2ecf20Sopenharmony_ci .hugetlb_entry = __s390_enable_skey_hugetlb, 26398c2ecf20Sopenharmony_ci .pte_entry = __s390_enable_skey_pte, 26408c2ecf20Sopenharmony_ci .pmd_entry = __s390_enable_skey_pmd, 26418c2ecf20Sopenharmony_ci}; 26428c2ecf20Sopenharmony_ci 26438c2ecf20Sopenharmony_ciint s390_enable_skey(void) 26448c2ecf20Sopenharmony_ci{ 26458c2ecf20Sopenharmony_ci struct mm_struct *mm = current->mm; 26468c2ecf20Sopenharmony_ci int rc = 0; 26478c2ecf20Sopenharmony_ci 26488c2ecf20Sopenharmony_ci mmap_write_lock(mm); 26498c2ecf20Sopenharmony_ci if (mm_uses_skeys(mm)) 26508c2ecf20Sopenharmony_ci goto out_up; 26518c2ecf20Sopenharmony_ci 26528c2ecf20Sopenharmony_ci mm->context.uses_skeys = 1; 26538c2ecf20Sopenharmony_ci rc = gmap_mark_unmergeable(); 26548c2ecf20Sopenharmony_ci if (rc) { 26558c2ecf20Sopenharmony_ci mm->context.uses_skeys = 0; 26568c2ecf20Sopenharmony_ci goto out_up; 26578c2ecf20Sopenharmony_ci } 26588c2ecf20Sopenharmony_ci walk_page_range(mm, 0, TASK_SIZE, &enable_skey_walk_ops, NULL); 26598c2ecf20Sopenharmony_ci 26608c2ecf20Sopenharmony_ciout_up: 26618c2ecf20Sopenharmony_ci mmap_write_unlock(mm); 26628c2ecf20Sopenharmony_ci return rc; 26638c2ecf20Sopenharmony_ci} 26648c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s390_enable_skey); 26658c2ecf20Sopenharmony_ci 26668c2ecf20Sopenharmony_ci/* 26678c2ecf20Sopenharmony_ci * Reset CMMA state, make all pages stable again. 26688c2ecf20Sopenharmony_ci */ 26698c2ecf20Sopenharmony_cistatic int __s390_reset_cmma(pte_t *pte, unsigned long addr, 26708c2ecf20Sopenharmony_ci unsigned long next, struct mm_walk *walk) 26718c2ecf20Sopenharmony_ci{ 26728c2ecf20Sopenharmony_ci ptep_zap_unused(walk->mm, addr, pte, 1); 26738c2ecf20Sopenharmony_ci return 0; 26748c2ecf20Sopenharmony_ci} 26758c2ecf20Sopenharmony_ci 26768c2ecf20Sopenharmony_cistatic const struct mm_walk_ops reset_cmma_walk_ops = { 26778c2ecf20Sopenharmony_ci .pte_entry = __s390_reset_cmma, 26788c2ecf20Sopenharmony_ci}; 26798c2ecf20Sopenharmony_ci 26808c2ecf20Sopenharmony_civoid s390_reset_cmma(struct mm_struct *mm) 26818c2ecf20Sopenharmony_ci{ 26828c2ecf20Sopenharmony_ci mmap_write_lock(mm); 26838c2ecf20Sopenharmony_ci walk_page_range(mm, 0, TASK_SIZE, &reset_cmma_walk_ops, NULL); 26848c2ecf20Sopenharmony_ci mmap_write_unlock(mm); 26858c2ecf20Sopenharmony_ci} 26868c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s390_reset_cmma); 26878c2ecf20Sopenharmony_ci 26888c2ecf20Sopenharmony_ci/* 26898c2ecf20Sopenharmony_ci * make inaccessible pages accessible again 26908c2ecf20Sopenharmony_ci */ 26918c2ecf20Sopenharmony_cistatic int __s390_reset_acc(pte_t *ptep, unsigned long addr, 26928c2ecf20Sopenharmony_ci unsigned long next, struct mm_walk *walk) 26938c2ecf20Sopenharmony_ci{ 26948c2ecf20Sopenharmony_ci pte_t pte = READ_ONCE(*ptep); 26958c2ecf20Sopenharmony_ci 26968c2ecf20Sopenharmony_ci if (pte_present(pte)) 26978c2ecf20Sopenharmony_ci WARN_ON_ONCE(uv_destroy_page(pte_val(pte) & PAGE_MASK)); 26988c2ecf20Sopenharmony_ci return 0; 26998c2ecf20Sopenharmony_ci} 27008c2ecf20Sopenharmony_ci 27018c2ecf20Sopenharmony_cistatic const struct mm_walk_ops reset_acc_walk_ops = { 27028c2ecf20Sopenharmony_ci .pte_entry = __s390_reset_acc, 27038c2ecf20Sopenharmony_ci}; 27048c2ecf20Sopenharmony_ci 27058c2ecf20Sopenharmony_ci#include <linux/sched/mm.h> 27068c2ecf20Sopenharmony_civoid s390_reset_acc(struct mm_struct *mm) 27078c2ecf20Sopenharmony_ci{ 27088c2ecf20Sopenharmony_ci if (!mm_is_protected(mm)) 27098c2ecf20Sopenharmony_ci return; 27108c2ecf20Sopenharmony_ci /* 27118c2ecf20Sopenharmony_ci * we might be called during 27128c2ecf20Sopenharmony_ci * reset: we walk the pages and clear 27138c2ecf20Sopenharmony_ci * close of all kvm file descriptors: we walk the pages and clear 27148c2ecf20Sopenharmony_ci * exit of process on fd closure: vma already gone, do nothing 27158c2ecf20Sopenharmony_ci */ 27168c2ecf20Sopenharmony_ci if (!mmget_not_zero(mm)) 27178c2ecf20Sopenharmony_ci return; 27188c2ecf20Sopenharmony_ci mmap_read_lock(mm); 27198c2ecf20Sopenharmony_ci walk_page_range(mm, 0, TASK_SIZE, &reset_acc_walk_ops, NULL); 27208c2ecf20Sopenharmony_ci mmap_read_unlock(mm); 27218c2ecf20Sopenharmony_ci mmput(mm); 27228c2ecf20Sopenharmony_ci} 27238c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s390_reset_acc); 27248c2ecf20Sopenharmony_ci 27258c2ecf20Sopenharmony_ci/** 27268c2ecf20Sopenharmony_ci * s390_unlist_old_asce - Remove the topmost level of page tables from the 27278c2ecf20Sopenharmony_ci * list of page tables of the gmap. 27288c2ecf20Sopenharmony_ci * @gmap: the gmap whose table is to be removed 27298c2ecf20Sopenharmony_ci * 27308c2ecf20Sopenharmony_ci * On s390x, KVM keeps a list of all pages containing the page tables of the 27318c2ecf20Sopenharmony_ci * gmap (the CRST list). This list is used at tear down time to free all 27328c2ecf20Sopenharmony_ci * pages that are now not needed anymore. 27338c2ecf20Sopenharmony_ci * 27348c2ecf20Sopenharmony_ci * This function removes the topmost page of the tree (the one pointed to by 27358c2ecf20Sopenharmony_ci * the ASCE) from the CRST list. 27368c2ecf20Sopenharmony_ci * 27378c2ecf20Sopenharmony_ci * This means that it will not be freed when the VM is torn down, and needs 27388c2ecf20Sopenharmony_ci * to be handled separately by the caller, unless a leak is actually 27398c2ecf20Sopenharmony_ci * intended. Notice that this function will only remove the page from the 27408c2ecf20Sopenharmony_ci * list, the page will still be used as a top level page table (and ASCE). 27418c2ecf20Sopenharmony_ci */ 27428c2ecf20Sopenharmony_civoid s390_unlist_old_asce(struct gmap *gmap) 27438c2ecf20Sopenharmony_ci{ 27448c2ecf20Sopenharmony_ci struct page *old; 27458c2ecf20Sopenharmony_ci 27468c2ecf20Sopenharmony_ci old = virt_to_page(gmap->table); 27478c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 27488c2ecf20Sopenharmony_ci list_del(&old->lru); 27498c2ecf20Sopenharmony_ci /* 27508c2ecf20Sopenharmony_ci * Sometimes the topmost page might need to be "removed" multiple 27518c2ecf20Sopenharmony_ci * times, for example if the VM is rebooted into secure mode several 27528c2ecf20Sopenharmony_ci * times concurrently, or if s390_replace_asce fails after calling 27538c2ecf20Sopenharmony_ci * s390_remove_old_asce and is attempted again later. In that case 27548c2ecf20Sopenharmony_ci * the old asce has been removed from the list, and therefore it 27558c2ecf20Sopenharmony_ci * will not be freed when the VM terminates, but the ASCE is still 27568c2ecf20Sopenharmony_ci * in use and still pointed to. 27578c2ecf20Sopenharmony_ci * A subsequent call to replace_asce will follow the pointer and try 27588c2ecf20Sopenharmony_ci * to remove the same page from the list again. 27598c2ecf20Sopenharmony_ci * Therefore it's necessary that the page of the ASCE has valid 27608c2ecf20Sopenharmony_ci * pointers, so list_del can work (and do nothing) without 27618c2ecf20Sopenharmony_ci * dereferencing stale or invalid pointers. 27628c2ecf20Sopenharmony_ci */ 27638c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&old->lru); 27648c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 27658c2ecf20Sopenharmony_ci} 27668c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s390_unlist_old_asce); 27678c2ecf20Sopenharmony_ci 27688c2ecf20Sopenharmony_ci/** 27698c2ecf20Sopenharmony_ci * s390_replace_asce - Try to replace the current ASCE of a gmap with a copy 27708c2ecf20Sopenharmony_ci * @gmap: the gmap whose ASCE needs to be replaced 27718c2ecf20Sopenharmony_ci * 27728c2ecf20Sopenharmony_ci * If the allocation of the new top level page table fails, the ASCE is not 27738c2ecf20Sopenharmony_ci * replaced. 27748c2ecf20Sopenharmony_ci * In any case, the old ASCE is always removed from the gmap CRST list. 27758c2ecf20Sopenharmony_ci * Therefore the caller has to make sure to save a pointer to it 27768c2ecf20Sopenharmony_ci * beforehand, unless a leak is actually intended. 27778c2ecf20Sopenharmony_ci */ 27788c2ecf20Sopenharmony_ciint s390_replace_asce(struct gmap *gmap) 27798c2ecf20Sopenharmony_ci{ 27808c2ecf20Sopenharmony_ci unsigned long asce; 27818c2ecf20Sopenharmony_ci struct page *page; 27828c2ecf20Sopenharmony_ci void *table; 27838c2ecf20Sopenharmony_ci 27848c2ecf20Sopenharmony_ci s390_unlist_old_asce(gmap); 27858c2ecf20Sopenharmony_ci 27868c2ecf20Sopenharmony_ci page = alloc_pages(GFP_KERNEL_ACCOUNT, CRST_ALLOC_ORDER); 27878c2ecf20Sopenharmony_ci if (!page) 27888c2ecf20Sopenharmony_ci return -ENOMEM; 27898c2ecf20Sopenharmony_ci page->index = 0; 27908c2ecf20Sopenharmony_ci table = page_to_virt(page); 27918c2ecf20Sopenharmony_ci memcpy(table, gmap->table, 1UL << (CRST_ALLOC_ORDER + PAGE_SHIFT)); 27928c2ecf20Sopenharmony_ci 27938c2ecf20Sopenharmony_ci /* 27948c2ecf20Sopenharmony_ci * The caller has to deal with the old ASCE, but here we make sure 27958c2ecf20Sopenharmony_ci * the new one is properly added to the CRST list, so that 27968c2ecf20Sopenharmony_ci * it will be freed when the VM is torn down. 27978c2ecf20Sopenharmony_ci */ 27988c2ecf20Sopenharmony_ci spin_lock(&gmap->guest_table_lock); 27998c2ecf20Sopenharmony_ci list_add(&page->lru, &gmap->crst_list); 28008c2ecf20Sopenharmony_ci spin_unlock(&gmap->guest_table_lock); 28018c2ecf20Sopenharmony_ci 28028c2ecf20Sopenharmony_ci /* Set new table origin while preserving existing ASCE control bits */ 28038c2ecf20Sopenharmony_ci asce = (gmap->asce & ~_ASCE_ORIGIN) | __pa(table); 28048c2ecf20Sopenharmony_ci WRITE_ONCE(gmap->asce, asce); 28058c2ecf20Sopenharmony_ci WRITE_ONCE(gmap->mm->context.gmap_asce, asce); 28068c2ecf20Sopenharmony_ci WRITE_ONCE(gmap->table, table); 28078c2ecf20Sopenharmony_ci 28088c2ecf20Sopenharmony_ci return 0; 28098c2ecf20Sopenharmony_ci} 28108c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(s390_replace_asce); 2811