| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright IBM Corp. 2011 | 
					
						
							|  |  |  |  * Author(s): Jan Glauber <jang@linux.vnet.ibm.com> | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2012-11-02 12:56:43 +01:00
										 |  |  | #include <linux/hugetlb.h>
 | 
					
						
							| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/mm.h>
 | 
					
						
							| 
									
										
										
										
											2011-10-30 15:17:13 +01:00
										 |  |  | #include <asm/cacheflush.h>
 | 
					
						
							| 
									
										
										
										
											2014-09-23 21:29:20 +02:00
										 |  |  | #include <asm/facility.h>
 | 
					
						
							| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | #include <asm/pgtable.h>
 | 
					
						
							| 
									
										
										
										
											2012-11-02 12:56:43 +01:00
										 |  |  | #include <asm/page.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-07 12:12:32 +02:00
										 |  |  | #if PAGE_DEFAULT_KEY
 | 
					
						
							| 
									
										
										
										
											2013-03-14 16:46:05 +01:00
										 |  |  | static inline unsigned long sske_frame(unsigned long addr, unsigned char skey) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	asm volatile(".insn rrf,0xb22b0000,%[skey],%[addr],9,0" | 
					
						
							|  |  |  | 		     : [addr] "+a" (addr) : [skey] "d" (skey)); | 
					
						
							|  |  |  | 	return addr; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-07 12:12:32 +02:00
										 |  |  | void __storage_key_init_range(unsigned long start, unsigned long end) | 
					
						
							| 
									
										
										
										
											2012-11-02 12:56:43 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2013-03-14 16:46:05 +01:00
										 |  |  | 	unsigned long boundary, size; | 
					
						
							| 
									
										
										
										
											2012-11-02 12:56:43 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	while (start < end) { | 
					
						
							|  |  |  | 		if (MACHINE_HAS_EDAT1) { | 
					
						
							|  |  |  | 			/* set storage keys for a 1MB frame */ | 
					
						
							|  |  |  | 			size = 1UL << 20; | 
					
						
							|  |  |  | 			boundary = (start + size) & ~(size - 1); | 
					
						
							|  |  |  | 			if (boundary <= end) { | 
					
						
							|  |  |  | 				do { | 
					
						
							| 
									
										
										
										
											2013-03-14 16:46:05 +01:00
										 |  |  | 					start = sske_frame(start, PAGE_DEFAULT_KEY); | 
					
						
							| 
									
										
										
										
											2012-11-02 12:56:43 +01:00
										 |  |  | 				} while (start < boundary); | 
					
						
							|  |  |  | 				continue; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		page_set_storage_key(start, PAGE_DEFAULT_KEY, 0); | 
					
						
							|  |  |  | 		start += PAGE_SIZE; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2013-10-07 12:12:32 +02:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-01 16:18:46 +02:00
										 |  |  | static pte_t *walk_page_table(unsigned long addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pgd_t *pgdp; | 
					
						
							|  |  |  | 	pud_t *pudp; | 
					
						
							|  |  |  | 	pmd_t *pmdp; | 
					
						
							|  |  |  | 	pte_t *ptep; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pgdp = pgd_offset_k(addr); | 
					
						
							|  |  |  | 	if (pgd_none(*pgdp)) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	pudp = pud_offset(pgdp, addr); | 
					
						
							| 
									
										
										
										
											2012-10-08 09:18:26 +02:00
										 |  |  | 	if (pud_none(*pudp) || pud_large(*pudp)) | 
					
						
							| 
									
										
										
										
											2012-10-01 16:18:46 +02:00
										 |  |  | 		return NULL; | 
					
						
							|  |  |  | 	pmdp = pmd_offset(pudp, addr); | 
					
						
							|  |  |  | 	if (pmd_none(*pmdp) || pmd_large(*pmdp)) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	ptep = pte_offset_kernel(pmdp, addr); | 
					
						
							|  |  |  | 	if (pte_none(*ptep)) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	return ptep; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | static void change_page_attr(unsigned long addr, int numpages, | 
					
						
							|  |  |  | 			     pte_t (*set) (pte_t)) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pte_t *ptep, pte; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < numpages; i++) { | 
					
						
							| 
									
										
										
										
											2012-10-01 16:18:46 +02:00
										 |  |  | 		ptep = walk_page_table(addr); | 
					
						
							|  |  |  | 		if (WARN_ON_ONCE(!ptep)) | 
					
						
							|  |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | 		pte = *ptep; | 
					
						
							|  |  |  | 		pte = set(pte); | 
					
						
							| 
									
										
										
										
											2011-05-23 10:24:40 +02:00
										 |  |  | 		__ptep_ipte(addr, ptep); | 
					
						
							| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | 		*ptep = pte; | 
					
						
							| 
									
										
										
										
											2011-04-20 10:15:32 +02:00
										 |  |  | 		addr += PAGE_SIZE; | 
					
						
							| 
									
										
										
										
											2011-03-15 17:08:22 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int set_memory_ro(unsigned long addr, int numpages) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	change_page_attr(addr, numpages, pte_wrprotect); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int set_memory_rw(unsigned long addr, int numpages) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	change_page_attr(addr, numpages, pte_mkwrite); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* not possible */ | 
					
						
							|  |  |  | int set_memory_nx(unsigned long addr, int numpages) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2011-05-19 16:55:26 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | int set_memory_x(unsigned long addr, int numpages) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2012-11-02 13:28:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_DEBUG_PAGEALLOC
 | 
					
						
							| 
									
										
										
										
											2014-09-23 21:29:20 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | static void ipte_range(pte_t *pte, unsigned long address, int nr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-12 13:08:27 +01:00
										 |  |  | 	if (test_facility(13)) { | 
					
						
							| 
									
										
										
										
											2014-09-23 21:29:20 +02:00
										 |  |  | 		__ptep_ipte_range(address, nr - 1, pte); | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for (i = 0; i < nr; i++) { | 
					
						
							|  |  |  | 		__ptep_ipte(address, pte); | 
					
						
							|  |  |  | 		address += PAGE_SIZE; | 
					
						
							|  |  |  | 		pte++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-12-12 16:55:52 -08:00
										 |  |  | void __kernel_map_pages(struct page *page, int numpages, int enable) | 
					
						
							| 
									
										
										
										
											2012-11-02 13:28:48 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned long address; | 
					
						
							| 
									
										
										
										
											2014-09-23 21:29:20 +02:00
										 |  |  | 	int nr, i, j; | 
					
						
							| 
									
										
										
										
											2012-11-02 13:28:48 +01:00
										 |  |  | 	pgd_t *pgd; | 
					
						
							|  |  |  | 	pud_t *pud; | 
					
						
							|  |  |  | 	pmd_t *pmd; | 
					
						
							|  |  |  | 	pte_t *pte; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-09-23 21:29:20 +02:00
										 |  |  | 	for (i = 0; i < numpages;) { | 
					
						
							| 
									
										
										
										
											2012-11-02 13:28:48 +01:00
										 |  |  | 		address = page_to_phys(page + i); | 
					
						
							|  |  |  | 		pgd = pgd_offset_k(address); | 
					
						
							|  |  |  | 		pud = pud_offset(pgd, address); | 
					
						
							|  |  |  | 		pmd = pmd_offset(pud, address); | 
					
						
							|  |  |  | 		pte = pte_offset_kernel(pmd, address); | 
					
						
							| 
									
										
										
										
											2014-09-23 21:29:20 +02:00
										 |  |  | 		nr = (unsigned long)pte >> ilog2(sizeof(long)); | 
					
						
							|  |  |  | 		nr = PTRS_PER_PTE - (nr & (PTRS_PER_PTE - 1)); | 
					
						
							|  |  |  | 		nr = min(numpages - i, nr); | 
					
						
							|  |  |  | 		if (enable) { | 
					
						
							|  |  |  | 			for (j = 0; j < nr; j++) { | 
					
						
							|  |  |  | 				pte_val(*pte) = __pa(address); | 
					
						
							|  |  |  | 				address += PAGE_SIZE; | 
					
						
							|  |  |  | 				pte++; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ipte_range(pte, address, nr); | 
					
						
							| 
									
										
										
										
											2012-11-02 13:28:48 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2014-09-23 21:29:20 +02:00
										 |  |  | 		i += nr; | 
					
						
							| 
									
										
										
										
											2012-11-02 13:28:48 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_HIBERNATION
 | 
					
						
							|  |  |  | bool kernel_page_present(struct page *page) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long addr; | 
					
						
							|  |  |  | 	int cc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	addr = page_to_phys(page); | 
					
						
							|  |  |  | 	asm volatile( | 
					
						
							|  |  |  | 		"	lra	%1,0(%1)\n" | 
					
						
							|  |  |  | 		"	ipm	%0\n" | 
					
						
							|  |  |  | 		"	srl	%0,28" | 
					
						
							|  |  |  | 		: "=d" (cc), "+a" (addr) : : "cc"); | 
					
						
							|  |  |  | 	return cc == 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | #endif /* CONFIG_HIBERNATION */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif /* CONFIG_DEBUG_PAGEALLOC */
 |