staticinlinevoid fixup_tlbie_pid(unsignedlong pid)
{ /* * We can use any address for the invalidation, pick one which is * probably unused as an optimisation.
*/ unsignedlong va = ((1UL << 52) - 1);
if (cpu_has_feature(CPU_FTR_P9_TLBIE_ERAT_BUG)) { asmvolatile("ptesync": : :"memory");
__tlbie_pid(0, RIC_FLUSH_TLB);
}
staticinlinevoid fixup_tlbie_lpid(unsignedlong lpid)
{ /* * We can use any address for the invalidation, pick one which is * probably unused as an optimisation.
*/ unsignedlong va = ((1UL << 52) - 1);
if (cpu_has_feature(CPU_FTR_P9_TLBIE_ERAT_BUG)) { asmvolatile("ptesync": : :"memory");
__tlbie_lpid(0, RIC_FLUSH_TLB);
}
/* * We use 128 set in radix mode and 256 set in hpt mode.
*/ staticinlinevoid _tlbiel_pid(unsignedlong pid, unsignedlong ric)
{ int set;
asmvolatile("ptesync": : :"memory");
switch (ric) { case RIC_FLUSH_PWC:
/* For PWC, only one flush is needed */
__tlbiel_pid(pid, 0, RIC_FLUSH_PWC);
ppc_after_tlbiel_barrier(); return; case RIC_FLUSH_TLB:
__tlbiel_pid(pid, 0, RIC_FLUSH_TLB); break; case RIC_FLUSH_ALL: default: /* * Flush the first set of the TLB, and if * we're doing a RIC_FLUSH_ALL, also flush * the entire Page Walk Cache.
*/
__tlbiel_pid(pid, 0, RIC_FLUSH_ALL);
}
if (!cpu_has_feature(CPU_FTR_ARCH_31)) { /* For the remaining sets, just flush the TLB */ for (set = 1; set < POWER9_TLB_SETS_RADIX ; set++)
__tlbiel_pid(pid, set, RIC_FLUSH_TLB);
}
/* * Workaround the fact that the "ric" argument to __tlbie_pid * must be a compile-time constraint to match the "i" constraint * in the asm statement.
*/ switch (ric) { case RIC_FLUSH_TLB:
__tlbie_pid(pid, RIC_FLUSH_TLB);
fixup_tlbie_pid(pid); break; case RIC_FLUSH_PWC:
__tlbie_pid(pid, RIC_FLUSH_PWC); break; case RIC_FLUSH_ALL: default:
__tlbie_pid(pid, RIC_FLUSH_ALL);
fixup_tlbie_pid(pid);
} asmvolatile("eieio; tlbsync; ptesync": : :"memory");
}
on_each_cpu_mask(cpus, do_tlbiel_pid, &t, 1); /* * Always want the CPU translations to be invalidated with tlbiel in * these paths, so while coprocessors must use tlbie, we can not * optimise away the tlbiel component.
*/ if (atomic_read(&mm->context.copros) > 0)
_tlbie_pid(pid, RIC_FLUSH_ALL);
}
/* * Workaround the fact that the "ric" argument to __tlbie_pid * must be a compile-time contraint to match the "i" constraint * in the asm statement.
*/ switch (ric) { case RIC_FLUSH_TLB:
__tlbie_lpid(lpid, RIC_FLUSH_TLB);
fixup_tlbie_lpid(lpid); break; case RIC_FLUSH_PWC:
__tlbie_lpid(lpid, RIC_FLUSH_PWC); break; case RIC_FLUSH_ALL: default:
__tlbie_lpid(lpid, RIC_FLUSH_ALL);
fixup_tlbie_lpid(lpid);
} asmvolatile("eieio; tlbsync; ptesync": : :"memory");
}
static __always_inline void _tlbie_lpid_guest(unsignedlong lpid, unsignedlong ric)
{ /* * Workaround the fact that the "ric" argument to __tlbie_pid * must be a compile-time contraint to match the "i" constraint * in the asm statement.
*/ switch (ric) { case RIC_FLUSH_TLB:
__tlbie_lpid_guest(lpid, RIC_FLUSH_TLB); break; case RIC_FLUSH_PWC:
__tlbie_lpid_guest(lpid, RIC_FLUSH_PWC); break; case RIC_FLUSH_ALL: default:
__tlbie_lpid_guest(lpid, RIC_FLUSH_ALL);
}
fixup_tlbie_lpid(lpid); asmvolatile("eieio; tlbsync; ptesync": : :"memory");
}
/* * Base TLB flushing operations: * * - flush_tlb_mm(mm) flushes the specified mm context TLB's * - flush_tlb_page(vma, vmaddr) flushes one page * - flush_tlb_range(vma, start, end) flushes a range of pages * - flush_tlb_kernel_range(start, end) flushes kernel pages * * - local_* variants of page and mm only apply to the current * processor
*/ void radix__local_flush_tlb_mm(struct mm_struct *mm)
{ unsignedlong pid = mm->context.id;
void radix__local_flush_tlb_page(struct vm_area_struct *vma, unsignedlong vmaddr)
{ #ifdef CONFIG_HUGETLB_PAGE /* need the return fix for nohash.c */ if (is_vm_hugetlb_page(vma)) return radix__local_flush_hugetlb_page(vma, vmaddr); #endif
radix__local_flush_tlb_page_psize(vma->vm_mm, vmaddr, mmu_virtual_psize);
}
EXPORT_SYMBOL(radix__local_flush_tlb_page);
staticbool mm_needs_flush_escalation(struct mm_struct *mm)
{ /* * The P9 nest MMU has issues with the page walk cache caching PTEs * and not flushing them when RIC = 0 for a PID/LPID invalidate. * * This may have been fixed in shipping firmware (by disabling PWC * or preventing it from caching PTEs), but until that is confirmed, * this workaround is required - escalate all RIC=0 IS=1/2/3 flushes * to RIC=2. * * POWER10 (and P9P) does not have this problem.
*/ if (cpu_has_feature(CPU_FTR_ARCH_31)) returnfalse; if (atomic_read(&mm->context.copros) > 0) returntrue; returnfalse;
}
/* * If always_flush is true, then flush even if this CPU can't be removed * from mm_cpumask.
*/ void exit_lazy_flush_tlb(struct mm_struct *mm, bool always_flush)
{ unsignedlong pid = mm->context.id; int cpu = smp_processor_id();
/* * A kthread could have done a mmget_not_zero() after the flushing CPU * checked mm_cpumask, and be in the process of kthread_use_mm when * interrupted here. In that case, current->mm will be set to mm, * because kthread_use_mm() setting ->mm and switching to the mm is * done with interrupts off.
*/ if (current->mm == mm) goto out;
if (current->active_mm == mm) { unsignedlong flags;
WARN_ON_ONCE(current->mm != NULL); /* * It is a kernel thread and is using mm as the lazy tlb, so * switch it to init_mm. This is not always called from IPI * (e.g., flush_type_needed), so must disable irqs.
*/
local_irq_save(flags);
mmgrab_lazy_tlb(&init_mm);
current->active_mm = &init_mm;
switch_mm_irqs_off(mm, &init_mm, current);
mmdrop_lazy_tlb(mm);
local_irq_restore(flags);
}
/* * This IPI may be initiated from any source including those not * running the mm, so there may be a racing IPI that comes after * this one which finds the cpumask already clear. Check and avoid * underflowing the active_cpus count in that case. The race should * not otherwise be a problem, but the TLB must be flushed because * that's what the caller expects.
*/ if (cpumask_test_cpu(cpu, mm_cpumask(mm))) {
dec_mm_active_cpus(mm);
cpumask_clear_cpu(cpu, mm_cpumask(mm));
always_flush = true;
}
out: if (always_flush)
_tlbiel_pid(pid, RIC_FLUSH_ALL);
}
staticvoid exit_flush_lazy_tlbs(struct mm_struct *mm)
{ /* * Would be nice if this was async so it could be run in * parallel with our local flush, but generic code does not * give a good API for it. Could extend the generic code or * make a special powerpc IPI for flushing TLBs. * For now it's not too performance critical.
*/
smp_call_function_many(mm_cpumask(mm), do_exit_flush_lazy_tlb,
(void *)mm, 1);
}
/* * Interval between flushes at which we send out IPIs to check whether the * mm_cpumask can be trimmed for the case where it's not a single-threaded * process flushing its own mm. The intent is to reduce the cost of later * flushes. Don't want this to be so low that it adds noticable cost to TLB * flushing, or so high that it doesn't help reduce global TLBIEs.
*/ staticunsignedlong tlb_mm_cpumask_trim_timer = 1073;
staticenum tlb_flush_type flush_type_needed(struct mm_struct *mm, bool fullmm)
{ int active_cpus = atomic_read(&mm->context.active_cpus); int cpu = smp_processor_id();
if (active_cpus == 0) return FLUSH_TYPE_NONE; if (active_cpus == 1 && cpumask_test_cpu(cpu, mm_cpumask(mm))) { if (current->mm != mm) { /* * Asynchronous flush sources may trim down to nothing * if the process is not running, so occasionally try * to trim.
*/ if (tick_and_test_trim_clock()) {
exit_lazy_flush_tlb(mm, true); return FLUSH_TYPE_NONE;
}
} return FLUSH_TYPE_LOCAL;
}
/* Coprocessors require TLBIE to invalidate nMMU. */ if (atomic_read(&mm->context.copros) > 0) return FLUSH_TYPE_GLOBAL;
/* * In the fullmm case there's no point doing the exit_flush_lazy_tlbs * because the mm is being taken down anyway, and a TLBIE tends to * be faster than an IPI+TLBIEL.
*/ if (fullmm) return FLUSH_TYPE_GLOBAL;
/* * If we are running the only thread of a single-threaded process, * then we should almost always be able to trim off the rest of the * CPU mask (except in the case of use_mm() races), so always try * trimming the mask.
*/ if (atomic_read(&mm->mm_users) <= 1 && current->mm == mm) {
exit_flush_lazy_tlbs(mm); /* * use_mm() race could prevent IPIs from being able to clear * the cpumask here, however those users are established * after our first check (and so after the PTEs are removed), * and the TLB still gets flushed by the IPI, so this CPU * will only require a local flush.
*/ return FLUSH_TYPE_LOCAL;
}
/* * Occasionally try to trim down the cpumask. It's possible this can * bring the mask to zero, which results in no flush.
*/ if (tick_and_test_trim_clock()) {
exit_flush_lazy_tlbs(mm); if (current->mm == mm) return FLUSH_TYPE_LOCAL; if (cpumask_test_cpu(cpu, mm_cpumask(mm)))
exit_lazy_flush_tlb(mm, true); return FLUSH_TYPE_NONE;
}
pid = mm->context.id; if (WARN_ON_ONCE(pid == MMU_NO_CONTEXT)) return;
preempt_disable(); /* * Order loads of mm_cpumask (in flush_type_needed) vs previous * stores to clear ptes before the invalidate. See barrier in * switch_mm_irqs_off
*/
smp_mb();
type = flush_type_needed(mm, false); if (type == FLUSH_TYPE_LOCAL) {
_tlbiel_pid(pid, RIC_FLUSH_TLB);
} elseif (type == FLUSH_TYPE_GLOBAL) { if (!mmu_has_feature(MMU_FTR_GTSE)) { unsignedlong tgt = H_RPTI_TARGET_CMMU;
staticinlinevoid _tlbiel_kernel_broadcast(void)
{
on_each_cpu(do_tlbiel_kernel, NULL, 1); if (tlbie_capable) { /* * Coherent accelerators don't refcount kernel memory mappings, * so have to always issue a tlbie for them. This is quite a * slow path anyway.
*/
_tlbie_pid(0, RIC_FLUSH_ALL);
}
}
/* * If kernel TLBIs ever become local rather than global, then * drivers/misc/ocxl/link.c:ocxl_link_add_pe will need some work, as it * assumes kernel TLBIs are global.
*/ void radix__flush_tlb_kernel_range(unsignedlong start, unsignedlong end)
{ if (!mmu_has_feature(MMU_FTR_GTSE)) { unsignedlong tgt = H_RPTI_TARGET_CMMU | H_RPTI_TARGET_NMMU; unsignedlong type = H_RPTI_TYPE_TLB | H_RPTI_TYPE_PWC |
H_RPTI_TYPE_PRT;
/* * Doesn't appear to be used anywhere. Remove.
*/ #define TLB_FLUSH_ALL -1UL
/* * Number of pages above which we invalidate the entire PID rather than * flush individual pages, for local and global flushes respectively. * * tlbie goes out to the interconnect and individual ops are more costly. * It also does not iterate over sets like the local tlbiel variant when * invalidating a full PID, so it has a far lower threshold to change from * individual page flushes to full-pid flushes.
*/ static u32 tlb_single_page_flush_ceiling __read_mostly = 33; static u32 tlb_local_single_page_flush_ceiling __read_mostly = POWER9_TLB_SETS_RADIX * 2;
pid = mm->context.id; if (WARN_ON_ONCE(pid == MMU_NO_CONTEXT)) return;
WARN_ON_ONCE(end == TLB_FLUSH_ALL);
preempt_disable();
smp_mb(); /* see radix__flush_tlb_mm */
type = flush_type_needed(mm, false); if (type == FLUSH_TYPE_NONE) goto out;
if (type == FLUSH_TYPE_GLOBAL)
flush_pid = nr_pages > tlb_single_page_flush_ceiling; else
flush_pid = nr_pages > tlb_local_single_page_flush_ceiling; /* * full pid flush already does the PWC flush. if it is not full pid * flush check the range is more than PMD and force a pwc flush * mremap() depends on this behaviour.
*/ if (!flush_pid && (end - start) >= PMD_SIZE)
flush_pwc = true;
if (!mmu_has_feature(MMU_FTR_GTSE) && type == FLUSH_TYPE_GLOBAL) { unsignedlong type = H_RPTI_TYPE_TLB; unsignedlong tgt = H_RPTI_TARGET_CMMU; unsignedlong pg_sizes = psize_to_rpti_pgsize(mmu_virtual_psize);
if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
pg_sizes |= psize_to_rpti_pgsize(MMU_PAGE_2M); if (atomic_read(&mm->context.copros) > 0)
tgt |= H_RPTI_TARGET_NMMU; if (flush_pwc)
type |= H_RPTI_TYPE_PWC;
pseries_rpt_invalidate(pid, tgt, type, pg_sizes, start, end);
} elseif (flush_pid) { /* * We are now flushing a range larger than PMD size force a RIC_FLUSH_ALL
*/ if (type == FLUSH_TYPE_LOCAL) {
_tlbiel_pid(pid, RIC_FLUSH_ALL);
} else { if (cputlb_use_tlbie()) {
_tlbie_pid(pid, RIC_FLUSH_ALL);
} else {
_tlbiel_pid_multicast(mm, pid, RIC_FLUSH_ALL);
}
}
} else { bool hflush; unsignedlong hstart, hend;
/* * Flush process scoped translations from LPID (=LPIDR)
*/ void radix__flush_all_lpid_guest(unsignedint lpid)
{
_tlbie_lpid_guest(lpid, RIC_FLUSH_ALL);
}
void radix__tlb_flush(struct mmu_gather *tlb)
{ int psize = 0; struct mm_struct *mm = tlb->mm; int page_size = tlb->page_size; unsignedlong start = tlb->start; unsignedlong end = tlb->end;
/* * if page size is not something we understand, do a full mm flush * * A "fullmm" flush must always do a flush_all_mm (RIC=2) flush * that flushes the process table entry cache upon process teardown. * See the comment for radix in arch_exit_mmap().
*/ if (tlb->fullmm) { if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_SHOOTDOWN)) { /* * Shootdown based lazy tlb mm refcounting means we * have to IPI everyone in the mm_cpumask anyway soon * when the mm goes away, so might as well do it as * part of the final flush now. * * If lazy shootdown was improved to reduce IPIs (e.g., * by batching), then it may end up being better to use * tlbies here instead.
*/
preempt_disable();
smp_mb(); /* see radix__flush_tlb_mm */
exit_flush_lazy_tlbs(mm);
__flush_all_mm(mm, true);
pid = mm->context.id; if (WARN_ON_ONCE(pid == MMU_NO_CONTEXT)) return;
/* 4k page size, just blow the world */ if (PAGE_SIZE == 0x1000) {
radix__flush_all_mm(mm); return;
}
end = addr + HPAGE_PMD_SIZE;
/* Otherwise first do the PWC, then iterate the pages. */
preempt_disable();
smp_mb(); /* see radix__flush_tlb_mm */
type = flush_type_needed(mm, false); if (type == FLUSH_TYPE_LOCAL) {
_tlbiel_va_range(addr, end, pid, PAGE_SIZE, mmu_virtual_psize, true);
} elseif (type == FLUSH_TYPE_GLOBAL) { if (!mmu_has_feature(MMU_FTR_GTSE)) { unsignedlong tgt, type, pg_sizes;
staticinlinevoid fixup_tlbie_pid_lpid(unsignedlong pid, unsignedlong lpid)
{ /* * We can use any address for the invalidation, pick one which is * probably unused as an optimisation.
*/ unsignedlong va = ((1UL << 52) - 1);
/* * Workaround the fact that the "ric" argument to __tlbie_pid * must be a compile-time contraint to match the "i" constraint * in the asm statement.
*/ switch (ric) { case RIC_FLUSH_TLB:
__tlbie_pid_lpid(pid, lpid, RIC_FLUSH_TLB);
fixup_tlbie_pid_lpid(pid, lpid); break; case RIC_FLUSH_PWC:
__tlbie_pid_lpid(pid, lpid, RIC_FLUSH_PWC); break; case RIC_FLUSH_ALL: default:
__tlbie_pid_lpid(pid, lpid, RIC_FLUSH_ALL);
fixup_tlbie_pid_lpid(pid, lpid);
} asmvolatile("eieio; tlbsync; ptesync" : : : "memory");
}
/* * Performs process-scoped invalidations for a given LPID * as part of H_RPT_INVALIDATE hcall.
*/ void do_h_rpt_invalidate_prt(unsignedlong pid, unsignedlong lpid, unsignedlong type, unsignedlong pg_sizes, unsignedlong start, unsignedlong end)
{ unsignedlong psize, nr_pages; struct mmu_psize_def *def; bool flush_pid;
/* * A H_RPTI_TYPE_ALL request implies RIC=3, hence * do a single IS=1 based flush.
*/ if ((type & H_RPTI_TYPE_ALL) == H_RPTI_TYPE_ALL) {
_tlbie_pid_lpid(pid, lpid, RIC_FLUSH_ALL); return;
}
if (type & H_RPTI_TYPE_PWC)
_tlbie_pid_lpid(pid, lpid, RIC_FLUSH_PWC);
/* Full PID flush */ if (start == 0 && end == -1) return _tlbie_pid_lpid(pid, lpid, RIC_FLUSH_TLB);
/* Do range invalidation for all the valid page sizes */ for (psize = 0; psize < MMU_PAGE_COUNT; psize++) {
def = &mmu_psize_defs[psize]; if (!(pg_sizes & def->h_rpt_pgsize)) continue;
/* * If the number of pages spanning the range is above * the ceiling, convert the request into a full PID flush. * And since PID flush takes out all the page sizes, there * is no need to consider remaining page sizes.
*/ if (flush_pid) {
_tlbie_pid_lpid(pid, lpid, RIC_FLUSH_TLB); return;
}
_tlbie_va_range_lpid(start, end, pid, lpid,
(1UL << def->shift), psize, false);
}
}
EXPORT_SYMBOL_GPL(do_h_rpt_invalidate_prt);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.