18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * PPC Huge TLB Page Support for Book3E MMU
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2009 David Gibson, IBM Corporation.
68c2ecf20Sopenharmony_ci * Copyright (C) 2011 Becky Bruce, Freescale Semiconductor
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include <linux/mm.h>
108c2ecf20Sopenharmony_ci#include <linux/hugetlb.h>
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <asm/mmu.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#ifdef CONFIG_PPC64
158c2ecf20Sopenharmony_ci#include <asm/paca.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistatic inline int tlb1_next(void)
188c2ecf20Sopenharmony_ci{
198c2ecf20Sopenharmony_ci	struct paca_struct *paca = get_paca();
208c2ecf20Sopenharmony_ci	struct tlb_core_data *tcd;
218c2ecf20Sopenharmony_ci	int this, next;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci	tcd = paca->tcd_ptr;
248c2ecf20Sopenharmony_ci	this = tcd->esel_next;
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci	next = this + 1;
278c2ecf20Sopenharmony_ci	if (next >= tcd->esel_max)
288c2ecf20Sopenharmony_ci		next = tcd->esel_first;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	tcd->esel_next = next;
318c2ecf20Sopenharmony_ci	return this;
328c2ecf20Sopenharmony_ci}
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistatic inline void book3e_tlb_lock(void)
358c2ecf20Sopenharmony_ci{
368c2ecf20Sopenharmony_ci	struct paca_struct *paca = get_paca();
378c2ecf20Sopenharmony_ci	unsigned long tmp;
388c2ecf20Sopenharmony_ci	int token = smp_processor_id() + 1;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	/*
418c2ecf20Sopenharmony_ci	 * Besides being unnecessary in the absence of SMT, this
428c2ecf20Sopenharmony_ci	 * check prevents trying to do lbarx/stbcx. on e5500 which
438c2ecf20Sopenharmony_ci	 * doesn't implement either feature.
448c2ecf20Sopenharmony_ci	 */
458c2ecf20Sopenharmony_ci	if (!cpu_has_feature(CPU_FTR_SMT))
468c2ecf20Sopenharmony_ci		return;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	asm volatile("1: lbarx %0, 0, %1;"
498c2ecf20Sopenharmony_ci		     "cmpwi %0, 0;"
508c2ecf20Sopenharmony_ci		     "bne 2f;"
518c2ecf20Sopenharmony_ci		     "stbcx. %2, 0, %1;"
528c2ecf20Sopenharmony_ci		     "bne 1b;"
538c2ecf20Sopenharmony_ci		     "b 3f;"
548c2ecf20Sopenharmony_ci		     "2: lbzx %0, 0, %1;"
558c2ecf20Sopenharmony_ci		     "cmpwi %0, 0;"
568c2ecf20Sopenharmony_ci		     "bne 2b;"
578c2ecf20Sopenharmony_ci		     "b 1b;"
588c2ecf20Sopenharmony_ci		     "3:"
598c2ecf20Sopenharmony_ci		     : "=&r" (tmp)
608c2ecf20Sopenharmony_ci		     : "r" (&paca->tcd_ptr->lock), "r" (token)
618c2ecf20Sopenharmony_ci		     : "memory");
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic inline void book3e_tlb_unlock(void)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	struct paca_struct *paca = get_paca();
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	if (!cpu_has_feature(CPU_FTR_SMT))
698c2ecf20Sopenharmony_ci		return;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	isync();
728c2ecf20Sopenharmony_ci	paca->tcd_ptr->lock = 0;
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci#else
758c2ecf20Sopenharmony_cistatic inline int tlb1_next(void)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	int index, ncams;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	ncams = mfspr(SPRN_TLB1CFG) & TLBnCFG_N_ENTRY;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	index = this_cpu_read(next_tlbcam_idx);
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/* Just round-robin the entries and wrap when we hit the end */
848c2ecf20Sopenharmony_ci	if (unlikely(index == ncams - 1))
858c2ecf20Sopenharmony_ci		__this_cpu_write(next_tlbcam_idx, tlbcam_index);
868c2ecf20Sopenharmony_ci	else
878c2ecf20Sopenharmony_ci		__this_cpu_inc(next_tlbcam_idx);
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	return index;
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic inline void book3e_tlb_lock(void)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_cistatic inline void book3e_tlb_unlock(void)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci#endif
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic inline int book3e_tlb_exists(unsigned long ea, unsigned long pid)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	int found = 0;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	mtspr(SPRN_MAS6, pid << 16);
1068c2ecf20Sopenharmony_ci	if (mmu_has_feature(MMU_FTR_USE_TLBRSRV)) {
1078c2ecf20Sopenharmony_ci		asm volatile(
1088c2ecf20Sopenharmony_ci			"li	%0,0\n"
1098c2ecf20Sopenharmony_ci			"tlbsx.	0,%1\n"
1108c2ecf20Sopenharmony_ci			"bne	1f\n"
1118c2ecf20Sopenharmony_ci			"li	%0,1\n"
1128c2ecf20Sopenharmony_ci			"1:\n"
1138c2ecf20Sopenharmony_ci			: "=&r"(found) : "r"(ea));
1148c2ecf20Sopenharmony_ci	} else {
1158c2ecf20Sopenharmony_ci		asm volatile(
1168c2ecf20Sopenharmony_ci			"tlbsx	0,%1\n"
1178c2ecf20Sopenharmony_ci			"mfspr	%0,0x271\n"
1188c2ecf20Sopenharmony_ci			"srwi	%0,%0,31\n"
1198c2ecf20Sopenharmony_ci			: "=&r"(found) : "r"(ea));
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	return found;
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic void
1268c2ecf20Sopenharmony_cibook3e_hugetlb_preload(struct vm_area_struct *vma, unsigned long ea, pte_t pte)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	unsigned long mas1, mas2;
1298c2ecf20Sopenharmony_ci	u64 mas7_3;
1308c2ecf20Sopenharmony_ci	unsigned long psize, tsize, shift;
1318c2ecf20Sopenharmony_ci	unsigned long flags;
1328c2ecf20Sopenharmony_ci	struct mm_struct *mm;
1338c2ecf20Sopenharmony_ci	int index;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	if (unlikely(is_kernel_addr(ea)))
1368c2ecf20Sopenharmony_ci		return;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	mm = vma->vm_mm;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	psize = vma_mmu_pagesize(vma);
1418c2ecf20Sopenharmony_ci	shift = __ilog2(psize);
1428c2ecf20Sopenharmony_ci	tsize = shift - 10;
1438c2ecf20Sopenharmony_ci	/*
1448c2ecf20Sopenharmony_ci	 * We can't be interrupted while we're setting up the MAS
1458c2ecf20Sopenharmony_ci	 * regusters or after we've confirmed that no tlb exists.
1468c2ecf20Sopenharmony_ci	 */
1478c2ecf20Sopenharmony_ci	local_irq_save(flags);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	book3e_tlb_lock();
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	if (unlikely(book3e_tlb_exists(ea, mm->context.id))) {
1528c2ecf20Sopenharmony_ci		book3e_tlb_unlock();
1538c2ecf20Sopenharmony_ci		local_irq_restore(flags);
1548c2ecf20Sopenharmony_ci		return;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	/* We have to use the CAM(TLB1) on FSL parts for hugepages */
1588c2ecf20Sopenharmony_ci	index = tlb1_next();
1598c2ecf20Sopenharmony_ci	mtspr(SPRN_MAS0, MAS0_ESEL(index) | MAS0_TLBSEL(1));
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	mas1 = MAS1_VALID | MAS1_TID(mm->context.id) | MAS1_TSIZE(tsize);
1628c2ecf20Sopenharmony_ci	mas2 = ea & ~((1UL << shift) - 1);
1638c2ecf20Sopenharmony_ci	mas2 |= (pte_val(pte) >> PTE_WIMGE_SHIFT) & MAS2_WIMGE_MASK;
1648c2ecf20Sopenharmony_ci	mas7_3 = (u64)pte_pfn(pte) << PAGE_SHIFT;
1658c2ecf20Sopenharmony_ci	mas7_3 |= (pte_val(pte) >> PTE_BAP_SHIFT) & MAS3_BAP_MASK;
1668c2ecf20Sopenharmony_ci	if (!pte_dirty(pte))
1678c2ecf20Sopenharmony_ci		mas7_3 &= ~(MAS3_SW|MAS3_UW);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	mtspr(SPRN_MAS1, mas1);
1708c2ecf20Sopenharmony_ci	mtspr(SPRN_MAS2, mas2);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	if (mmu_has_feature(MMU_FTR_USE_PAIRED_MAS)) {
1738c2ecf20Sopenharmony_ci		mtspr(SPRN_MAS7_MAS3, mas7_3);
1748c2ecf20Sopenharmony_ci	} else {
1758c2ecf20Sopenharmony_ci		if (mmu_has_feature(MMU_FTR_BIG_PHYS))
1768c2ecf20Sopenharmony_ci			mtspr(SPRN_MAS7, upper_32_bits(mas7_3));
1778c2ecf20Sopenharmony_ci		mtspr(SPRN_MAS3, lower_32_bits(mas7_3));
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	asm volatile ("tlbwe");
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	book3e_tlb_unlock();
1838c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci/*
1878c2ecf20Sopenharmony_ci * This is called at the end of handling a user page fault, when the
1888c2ecf20Sopenharmony_ci * fault has been handled by updating a PTE in the linux page tables.
1898c2ecf20Sopenharmony_ci *
1908c2ecf20Sopenharmony_ci * This must always be called with the pte lock held.
1918c2ecf20Sopenharmony_ci */
1928c2ecf20Sopenharmony_civoid update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep)
1938c2ecf20Sopenharmony_ci{
1948c2ecf20Sopenharmony_ci	if (is_vm_hugetlb_page(vma))
1958c2ecf20Sopenharmony_ci		book3e_hugetlb_preload(vma, address, *ptep);
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_civoid flush_hugetlb_page(struct vm_area_struct *vma, unsigned long vmaddr)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct hstate *hstate = hstate_file(vma->vm_file);
2018c2ecf20Sopenharmony_ci	unsigned long tsize = huge_page_shift(hstate) - 10;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	__flush_tlb_page(vma->vm_mm, vmaddr, tsize, 0);
2048c2ecf20Sopenharmony_ci}
205