diff options
Diffstat (limited to 'arch/powerpc/mm/hash_utils_64.c')
| -rw-r--r-- | arch/powerpc/mm/hash_utils_64.c | 222 | 
1 files changed, 163 insertions, 59 deletions
diff --git a/arch/powerpc/mm/hash_utils_64.c b/arch/powerpc/mm/hash_utils_64.c index 3a292be2e07..88ac0eeaadd 100644 --- a/arch/powerpc/mm/hash_utils_64.c +++ b/arch/powerpc/mm/hash_utils_64.c @@ -55,6 +55,7 @@  #include <asm/code-patching.h>  #include <asm/fadump.h>  #include <asm/firmware.h> +#include <asm/tm.h>  #ifdef DEBUG  #define DBG(fmt...) udbg_printf(fmt) @@ -125,7 +126,7 @@ static struct mmu_psize_def mmu_psize_defaults_old[] = {  	[MMU_PAGE_4K] = {  		.shift	= 12,  		.sllp	= 0, -		.penc	= 0, +		.penc   = {[MMU_PAGE_4K] = 0, [1 ... MMU_PAGE_COUNT - 1] = -1},  		.avpnm	= 0,  		.tlbiel = 0,  	}, @@ -139,14 +140,15 @@ static struct mmu_psize_def mmu_psize_defaults_gp[] = {  	[MMU_PAGE_4K] = {  		.shift	= 12,  		.sllp	= 0, -		.penc	= 0, +		.penc   = {[MMU_PAGE_4K] = 0, [1 ... MMU_PAGE_COUNT - 1] = -1},  		.avpnm	= 0,  		.tlbiel = 1,  	},  	[MMU_PAGE_16M] = {  		.shift	= 24,  		.sllp	= SLB_VSID_L, -		.penc	= 0, +		.penc   = {[0 ... MMU_PAGE_16M - 1] = -1, [MMU_PAGE_16M] = 0, +			    [MMU_PAGE_16M + 1 ... MMU_PAGE_COUNT - 1] = -1 },  		.avpnm	= 0x1UL,  		.tlbiel = 0,  	}, @@ -194,6 +196,11 @@ int htab_bolt_mapping(unsigned long vstart, unsigned long vend,  		unsigned long vpn  = hpt_vpn(vaddr, vsid, ssize);  		unsigned long tprot = prot; +		/* +		 * If we hit a bad address return error. +		 */ +		if (!vsid) +			return -1;  		/* Make kernel text executable */  		if (overlaps_kernel_text(vaddr, vaddr + step))  			tprot &= ~HPTE_R_N; @@ -203,7 +210,7 @@ int htab_bolt_mapping(unsigned long vstart, unsigned long vend,  		BUG_ON(!ppc_md.hpte_insert);  		ret = ppc_md.hpte_insert(hpteg, vpn, paddr, tprot, -					 HPTE_V_BOLTED, psize, ssize); +					 HPTE_V_BOLTED, psize, psize, ssize);  		if (ret < 0)  			break; @@ -270,6 +277,30 @@ static void __init htab_init_seg_sizes(void)  	of_scan_flat_dt(htab_dt_scan_seg_sizes, NULL);  } +static int __init get_idx_from_shift(unsigned int shift) +{ +	int idx = -1; + +	switch (shift) { +	case 0xc: +		idx = MMU_PAGE_4K; +		break; +	case 0x10: +		idx = MMU_PAGE_64K; +		break; +	case 0x14: +		idx = MMU_PAGE_1M; +		break; +	case 0x18: +		idx = MMU_PAGE_16M; +		break; +	case 0x22: +		idx = MMU_PAGE_16G; +		break; +	} +	return idx; +} +  static int __init htab_dt_scan_page_sizes(unsigned long node,  					  const char *uname, int depth,  					  void *data) @@ -285,64 +316,65 @@ static int __init htab_dt_scan_page_sizes(unsigned long node,  	prop = (u32 *)of_get_flat_dt_prop(node,  					  "ibm,segment-page-sizes", &size);  	if (prop != NULL) { -		DBG("Page sizes from device-tree:\n"); +		pr_info("Page sizes from device-tree:\n");  		size /= 4;  		cur_cpu_spec->mmu_features &= ~(MMU_FTR_16M_PAGE);  		while(size > 0) { -			unsigned int shift = prop[0]; +			unsigned int base_shift = prop[0];  			unsigned int slbenc = prop[1];  			unsigned int lpnum = prop[2]; -			unsigned int lpenc = 0;  			struct mmu_psize_def *def; -			int idx = -1; +			int idx, base_idx;  			size -= 3; prop += 3; -			while(size > 0 && lpnum) { -				if (prop[0] == shift) -					lpenc = prop[1]; -				prop += 2; size -= 2; -				lpnum--; +			base_idx = get_idx_from_shift(base_shift); +			if (base_idx < 0) { +				/* +				 * skip the pte encoding also +				 */ +				prop += lpnum * 2; size -= lpnum * 2; +				continue;  			} -			switch(shift) { -			case 0xc: -				idx = MMU_PAGE_4K; -				break; -			case 0x10: -				idx = MMU_PAGE_64K; -				break; -			case 0x14: -				idx = MMU_PAGE_1M; -				break; -			case 0x18: -				idx = MMU_PAGE_16M; +			def = &mmu_psize_defs[base_idx]; +			if (base_idx == MMU_PAGE_16M)  				cur_cpu_spec->mmu_features |= MMU_FTR_16M_PAGE; -				break; -			case 0x22: -				idx = MMU_PAGE_16G; -				break; -			} -			if (idx < 0) -				continue; -			def = &mmu_psize_defs[idx]; -			def->shift = shift; -			if (shift <= 23) + +			def->shift = base_shift; +			if (base_shift <= 23)  				def->avpnm = 0;  			else -				def->avpnm = (1 << (shift - 23)) - 1; +				def->avpnm = (1 << (base_shift - 23)) - 1;  			def->sllp = slbenc; -			def->penc = lpenc; -			/* We don't know for sure what's up with tlbiel, so +			/* +			 * We don't know for sure what's up with tlbiel, so  			 * for now we only set it for 4K and 64K pages  			 */ -			if (idx == MMU_PAGE_4K || idx == MMU_PAGE_64K) +			if (base_idx == MMU_PAGE_4K || base_idx == MMU_PAGE_64K)  				def->tlbiel = 1;  			else  				def->tlbiel = 0; -			DBG(" %d: shift=%02x, sllp=%04lx, avpnm=%08lx, " -			    "tlbiel=%d, penc=%d\n", -			    idx, shift, def->sllp, def->avpnm, def->tlbiel, -			    def->penc); +			while (size > 0 && lpnum) { +				unsigned int shift = prop[0]; +				int penc  = prop[1]; + +				prop += 2; size -= 2; +				lpnum--; + +				idx = get_idx_from_shift(shift); +				if (idx < 0) +					continue; + +				if (penc == -1) +					pr_err("Invalid penc for base_shift=%d " +					       "shift=%d\n", base_shift, shift); + +				def->penc[idx] = penc; +				pr_info("base_shift=%d: shift=%d, sllp=0x%04lx," +					" avpnm=0x%08lx, tlbiel=%d, penc=%d\n", +					base_shift, shift, def->sllp, +					def->avpnm, def->tlbiel, def->penc[idx]); +			}  		}  		return 1;  	} @@ -391,10 +423,21 @@ static int __init htab_dt_scan_hugepage_blocks(unsigned long node,  }  #endif /* CONFIG_HUGETLB_PAGE */ +static void mmu_psize_set_default_penc(void) +{ +	int bpsize, apsize; +	for (bpsize = 0; bpsize < MMU_PAGE_COUNT; bpsize++) +		for (apsize = 0; apsize < MMU_PAGE_COUNT; apsize++) +			mmu_psize_defs[bpsize].penc[apsize] = -1; +} +  static void __init htab_init_page_sizes(void)  {  	int rc; +	/* se the invalid penc to -1 */ +	mmu_psize_set_default_penc(); +  	/* Default to 4K pages only */  	memcpy(mmu_psize_defs, mmu_psize_defaults_old,  	       sizeof(mmu_psize_defaults_old)); @@ -758,6 +801,8 @@ void __init early_init_mmu(void)  	/* Initialize stab / SLB management */  	if (mmu_has_feature(MMU_FTR_SLB))  		slb_initialize(); +	else +		stab_initialize(get_paca()->stab_real);  }  #ifdef CONFIG_SMP @@ -891,14 +936,14 @@ static inline int subpage_protection(struct mm_struct *mm, unsigned long ea)  void hash_failure_debug(unsigned long ea, unsigned long access,  			unsigned long vsid, unsigned long trap, -			int ssize, int psize, unsigned long pte) +			int ssize, int psize, int lpsize, unsigned long pte)  {  	if (!printk_ratelimit())  		return;  	pr_info("mm: Hashing failure ! EA=0x%lx access=0x%lx current=%s\n",  		ea, access, current->comm); -	pr_info("    trap=0x%lx vsid=0x%lx ssize=%d psize=%d pte=0x%lx\n", -		trap, vsid, ssize, psize, pte); +	pr_info("    trap=0x%lx vsid=0x%lx ssize=%d base psize=%d psize %d pte=0x%lx\n", +		trap, vsid, ssize, psize, lpsize, pte);  }  /* Result code is: @@ -921,11 +966,6 @@ int hash_page(unsigned long ea, unsigned long access, unsigned long trap)  	DBG_LOW("hash_page(ea=%016lx, access=%lx, trap=%lx\n",  		ea, access, trap); -	if ((ea & ~REGION_MASK) >= PGTABLE_RANGE) { -		DBG_LOW(" out of pgtable range !\n"); - 		return 1; -	} -  	/* Get region & vsid */   	switch (REGION_ID(ea)) {  	case USER_REGION_ID: @@ -956,6 +996,11 @@ int hash_page(unsigned long ea, unsigned long access, unsigned long trap)  	}  	DBG_LOW(" mm=%p, mm->pgdir=%p, vsid=%016lx\n", mm, mm->pgd, vsid); +	/* Bad address. */ +	if (!vsid) { +		DBG_LOW("Bad address!\n"); +		return 1; +	}  	/* Get pgdir */  	pgdir = mm->pgd;  	if (pgdir == NULL) @@ -1071,7 +1116,7 @@ int hash_page(unsigned long ea, unsigned long access, unsigned long trap)  	 */  	if (rc == -1)  		hash_failure_debug(ea, access, vsid, trap, ssize, psize, -				   pte_val(*ptep)); +				   psize, pte_val(*ptep));  #ifndef CONFIG_PPC_64K_PAGES  	DBG_LOW(" o-pte: %016lx\n", pte_val(*ptep));  #else @@ -1125,6 +1170,8 @@ void hash_preload(struct mm_struct *mm, unsigned long ea,  	/* Get VSID */  	ssize = user_segment_size(ea);  	vsid = get_vsid(mm->context.id, ea, ssize); +	if (!vsid) +		return;  	/* Hash doesn't like irqs */  	local_irq_save(flags); @@ -1147,7 +1194,9 @@ void hash_preload(struct mm_struct *mm, unsigned long ea,  	 */  	if (rc == -1)  		hash_failure_debug(ea, access, vsid, trap, ssize, -				   mm->context.user_psize, pte_val(*ptep)); +				   mm->context.user_psize, +				   mm->context.user_psize, +				   pte_val(*ptep));  	local_irq_restore(flags);  } @@ -1171,6 +1220,22 @@ void flush_hash_page(unsigned long vpn, real_pte_t pte, int psize, int ssize,  		DBG_LOW(" sub %ld: hash=%lx, hidx=%lx\n", index, slot, hidx);  		ppc_md.hpte_invalidate(slot, vpn, psize, ssize, local);  	} pte_iterate_hashed_end(); + +#ifdef CONFIG_PPC_TRANSACTIONAL_MEM +	/* Transactions are not aborted by tlbiel, only tlbie. +	 * Without, syncing a page back to a block device w/ PIO could pick up +	 * transactional data (bad!) so we force an abort here.  Before the +	 * sync the page will be made read-only, which will flush_hash_page. +	 * BIG ISSUE here: if the kernel uses a page from userspace without +	 * unmapping it first, it may see the speculated version. +	 */ +	if (local && cpu_has_feature(CPU_FTR_TM) && +	    current->thread.regs && +	    MSR_TM_ACTIVE(current->thread.regs->msr)) { +		tm_enable(); +		tm_abort(TM_CAUSE_TLBI); +	} +#endif  }  void flush_hash_range(unsigned long number, int local) @@ -1205,21 +1270,60 @@ void low_hash_fault(struct pt_regs *regs, unsigned long address, int rc)  		bad_page_fault(regs, address, SIGBUS);  } +long hpte_insert_repeating(unsigned long hash, unsigned long vpn, +			   unsigned long pa, unsigned long rflags, +			   unsigned long vflags, int psize, int ssize) +{ +	unsigned long hpte_group; +	long slot; + +repeat: +	hpte_group = ((hash & htab_hash_mask) * +		       HPTES_PER_GROUP) & ~0x7UL; + +	/* Insert into the hash table, primary slot */ +	slot = ppc_md.hpte_insert(hpte_group, vpn, pa, rflags, vflags, +				  psize, psize, ssize); + +	/* Primary is full, try the secondary */ +	if (unlikely(slot == -1)) { +		hpte_group = ((~hash & htab_hash_mask) * +			      HPTES_PER_GROUP) & ~0x7UL; +		slot = ppc_md.hpte_insert(hpte_group, vpn, pa, rflags, +					  vflags | HPTE_V_SECONDARY, +					  psize, psize, ssize); +		if (slot == -1) { +			if (mftb() & 0x1) +				hpte_group = ((hash & htab_hash_mask) * +					      HPTES_PER_GROUP)&~0x7UL; + +			ppc_md.hpte_remove(hpte_group); +			goto repeat; +		} +	} + +	return slot; +} +  #ifdef CONFIG_DEBUG_PAGEALLOC  static void kernel_map_linear_page(unsigned long vaddr, unsigned long lmi)  { -	unsigned long hash, hpteg; +	unsigned long hash;  	unsigned long vsid = get_kernel_vsid(vaddr, mmu_kernel_ssize);  	unsigned long vpn = hpt_vpn(vaddr, vsid, mmu_kernel_ssize);  	unsigned long mode = htab_convert_pte_flags(PAGE_KERNEL); -	int ret; +	long ret;  	hash = hpt_hash(vpn, PAGE_SHIFT, mmu_kernel_ssize); -	hpteg = ((hash & htab_hash_mask) * HPTES_PER_GROUP); -	ret = ppc_md.hpte_insert(hpteg, vpn, __pa(vaddr), -				 mode, HPTE_V_BOLTED, -				 mmu_linear_psize, mmu_kernel_ssize); +	/* Don't create HPTE entries for bad address */ +	if (!vsid) +		return; + +	ret = hpte_insert_repeating(hash, vpn, __pa(vaddr), mode, +				    HPTE_V_BOLTED, +				    mmu_linear_psize, mmu_kernel_ssize); +  	BUG_ON (ret < 0);  	spin_lock(&linear_map_hash_lock);  	BUG_ON(linear_map_hash_slots[lmi] & 0x80);  |