-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
arm64: TLB maintenance functionality
This patch adds the TLB maintenance functions. There is no distinction made between the I and D TLBs. TLB maintenance operations are automatically broadcast between CPUs in hardware. The inner-shareable operations are always present, even on UP systems. NOTE: Large part of this patch to be dropped once Peter Z's generic mmu_gather patches are merged. Signed-off-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Acked-by: Tony Lindgren <tony@atomide.com> Acked-by: Nicolas Pitre <nico@linaro.org> Acked-by: Olof Johansson <olof@lixom.net> Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com> Acked-by: Arnd Bergmann <arnd@arndb.de>
- Loading branch information
Catalin Marinas
committed
Sep 17, 2012
1 parent
f1a0c4a
commit 58d0ba5
Showing
3 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/* | ||
* Based on arch/arm/include/asm/tlb.h | ||
* | ||
* Copyright (C) 2002 Russell King | ||
* Copyright (C) 2012 ARM Ltd. | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
#ifndef __ASM_TLB_H | ||
#define __ASM_TLB_H | ||
|
||
#include <linux/pagemap.h> | ||
#include <linux/swap.h> | ||
|
||
#include <asm/pgalloc.h> | ||
#include <asm/tlbflush.h> | ||
|
||
#define MMU_GATHER_BUNDLE 8 | ||
|
||
/* | ||
* TLB handling. This allows us to remove pages from the page | ||
* tables, and efficiently handle the TLB issues. | ||
*/ | ||
struct mmu_gather { | ||
struct mm_struct *mm; | ||
unsigned int fullmm; | ||
struct vm_area_struct *vma; | ||
unsigned long range_start; | ||
unsigned long range_end; | ||
unsigned int nr; | ||
unsigned int max; | ||
struct page **pages; | ||
struct page *local[MMU_GATHER_BUNDLE]; | ||
}; | ||
|
||
/* | ||
* This is unnecessarily complex. There's three ways the TLB shootdown | ||
* code is used: | ||
* 1. Unmapping a range of vmas. See zap_page_range(), unmap_region(). | ||
* tlb->fullmm = 0, and tlb_start_vma/tlb_end_vma will be called. | ||
* tlb->vma will be non-NULL. | ||
* 2. Unmapping all vmas. See exit_mmap(). | ||
* tlb->fullmm = 1, and tlb_start_vma/tlb_end_vma will be called. | ||
* tlb->vma will be non-NULL. Additionally, page tables will be freed. | ||
* 3. Unmapping argument pages. See shift_arg_pages(). | ||
* tlb->fullmm = 0, but tlb_start_vma/tlb_end_vma will not be called. | ||
* tlb->vma will be NULL. | ||
*/ | ||
static inline void tlb_flush(struct mmu_gather *tlb) | ||
{ | ||
if (tlb->fullmm || !tlb->vma) | ||
flush_tlb_mm(tlb->mm); | ||
else if (tlb->range_end > 0) { | ||
flush_tlb_range(tlb->vma, tlb->range_start, tlb->range_end); | ||
tlb->range_start = TASK_SIZE; | ||
tlb->range_end = 0; | ||
} | ||
} | ||
|
||
static inline void tlb_add_flush(struct mmu_gather *tlb, unsigned long addr) | ||
{ | ||
if (!tlb->fullmm) { | ||
if (addr < tlb->range_start) | ||
tlb->range_start = addr; | ||
if (addr + PAGE_SIZE > tlb->range_end) | ||
tlb->range_end = addr + PAGE_SIZE; | ||
} | ||
} | ||
|
||
static inline void __tlb_alloc_page(struct mmu_gather *tlb) | ||
{ | ||
unsigned long addr = __get_free_pages(GFP_NOWAIT | __GFP_NOWARN, 0); | ||
|
||
if (addr) { | ||
tlb->pages = (void *)addr; | ||
tlb->max = PAGE_SIZE / sizeof(struct page *); | ||
} | ||
} | ||
|
||
static inline void tlb_flush_mmu(struct mmu_gather *tlb) | ||
{ | ||
tlb_flush(tlb); | ||
free_pages_and_swap_cache(tlb->pages, tlb->nr); | ||
tlb->nr = 0; | ||
if (tlb->pages == tlb->local) | ||
__tlb_alloc_page(tlb); | ||
} | ||
|
||
static inline void | ||
tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned int fullmm) | ||
{ | ||
tlb->mm = mm; | ||
tlb->fullmm = fullmm; | ||
tlb->vma = NULL; | ||
tlb->max = ARRAY_SIZE(tlb->local); | ||
tlb->pages = tlb->local; | ||
tlb->nr = 0; | ||
__tlb_alloc_page(tlb); | ||
} | ||
|
||
static inline void | ||
tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end) | ||
{ | ||
tlb_flush_mmu(tlb); | ||
|
||
/* keep the page table cache within bounds */ | ||
check_pgt_cache(); | ||
|
||
if (tlb->pages != tlb->local) | ||
free_pages((unsigned long)tlb->pages, 0); | ||
} | ||
|
||
/* | ||
* Memorize the range for the TLB flush. | ||
*/ | ||
static inline void | ||
tlb_remove_tlb_entry(struct mmu_gather *tlb, pte_t *ptep, unsigned long addr) | ||
{ | ||
tlb_add_flush(tlb, addr); | ||
} | ||
|
||
/* | ||
* In the case of tlb vma handling, we can optimise these away in the | ||
* case where we're doing a full MM flush. When we're doing a munmap, | ||
* the vmas are adjusted to only cover the region to be torn down. | ||
*/ | ||
static inline void | ||
tlb_start_vma(struct mmu_gather *tlb, struct vm_area_struct *vma) | ||
{ | ||
if (!tlb->fullmm) { | ||
tlb->vma = vma; | ||
tlb->range_start = TASK_SIZE; | ||
tlb->range_end = 0; | ||
} | ||
} | ||
|
||
static inline void | ||
tlb_end_vma(struct mmu_gather *tlb, struct vm_area_struct *vma) | ||
{ | ||
if (!tlb->fullmm) | ||
tlb_flush(tlb); | ||
} | ||
|
||
static inline int __tlb_remove_page(struct mmu_gather *tlb, struct page *page) | ||
{ | ||
tlb->pages[tlb->nr++] = page; | ||
VM_BUG_ON(tlb->nr > tlb->max); | ||
return tlb->max - tlb->nr; | ||
} | ||
|
||
static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page) | ||
{ | ||
if (!__tlb_remove_page(tlb, page)) | ||
tlb_flush_mmu(tlb); | ||
} | ||
|
||
static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte, | ||
unsigned long addr) | ||
{ | ||
pgtable_page_dtor(pte); | ||
tlb_add_flush(tlb, addr); | ||
tlb_remove_page(tlb, pte); | ||
} | ||
|
||
#ifndef CONFIG_ARM64_64K_PAGES | ||
static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmdp, | ||
unsigned long addr) | ||
{ | ||
tlb_add_flush(tlb, addr); | ||
tlb_remove_page(tlb, virt_to_page(pmdp)); | ||
} | ||
#endif | ||
|
||
#define pte_free_tlb(tlb, ptep, addr) __pte_free_tlb(tlb, ptep, addr) | ||
#define pmd_free_tlb(tlb, pmdp, addr) __pmd_free_tlb(tlb, pmdp, addr) | ||
#define pud_free_tlb(tlb, pudp, addr) pud_free((tlb)->mm, pudp) | ||
|
||
#define tlb_migrate_finish(mm) do { } while (0) | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/* | ||
* Based on arch/arm/include/asm/tlbflush.h | ||
* | ||
* Copyright (C) 1999-2003 Russell King | ||
* Copyright (C) 2012 ARM Ltd. | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
#ifndef __ASM_TLBFLUSH_H | ||
#define __ASM_TLBFLUSH_H | ||
|
||
#ifndef __ASSEMBLY__ | ||
|
||
#include <linux/sched.h> | ||
#include <asm/cputype.h> | ||
|
||
extern void __cpu_flush_user_tlb_range(unsigned long, unsigned long, struct vm_area_struct *); | ||
extern void __cpu_flush_kern_tlb_range(unsigned long, unsigned long); | ||
|
||
extern struct cpu_tlb_fns cpu_tlb; | ||
|
||
/* | ||
* TLB Management | ||
* ============== | ||
* | ||
* The arch/arm64/mm/tlb.S files implement these methods. | ||
* | ||
* The TLB specific code is expected to perform whatever tests it needs | ||
* to determine if it should invalidate the TLB for each call. Start | ||
* addresses are inclusive and end addresses are exclusive; it is safe to | ||
* round these addresses down. | ||
* | ||
* flush_tlb_all() | ||
* | ||
* Invalidate the entire TLB. | ||
* | ||
* flush_tlb_mm(mm) | ||
* | ||
* Invalidate all TLB entries in a particular address space. | ||
* - mm - mm_struct describing address space | ||
* | ||
* flush_tlb_range(mm,start,end) | ||
* | ||
* Invalidate a range of TLB entries in the specified address | ||
* space. | ||
* - mm - mm_struct describing address space | ||
* - start - start address (may not be aligned) | ||
* - end - end address (exclusive, may not be aligned) | ||
* | ||
* flush_tlb_page(vaddr,vma) | ||
* | ||
* Invalidate the specified page in the specified address range. | ||
* - vaddr - virtual address (may not be aligned) | ||
* - vma - vma_struct describing address range | ||
* | ||
* flush_kern_tlb_page(kaddr) | ||
* | ||
* Invalidate the TLB entry for the specified page. The address | ||
* will be in the kernels virtual memory space. Current uses | ||
* only require the D-TLB to be invalidated. | ||
* - kaddr - Kernel virtual memory address | ||
*/ | ||
static inline void flush_tlb_all(void) | ||
{ | ||
dsb(); | ||
asm("tlbi vmalle1is"); | ||
dsb(); | ||
isb(); | ||
} | ||
|
||
static inline void flush_tlb_mm(struct mm_struct *mm) | ||
{ | ||
unsigned long asid = (unsigned long)ASID(mm) << 48; | ||
|
||
dsb(); | ||
asm("tlbi aside1is, %0" : : "r" (asid)); | ||
dsb(); | ||
} | ||
|
||
static inline void flush_tlb_page(struct vm_area_struct *vma, | ||
unsigned long uaddr) | ||
{ | ||
unsigned long addr = uaddr >> 12 | | ||
((unsigned long)ASID(vma->vm_mm) << 48); | ||
|
||
dsb(); | ||
asm("tlbi vae1is, %0" : : "r" (addr)); | ||
dsb(); | ||
} | ||
|
||
/* | ||
* Convert calls to our calling convention. | ||
*/ | ||
#define flush_tlb_range(vma,start,end) __cpu_flush_user_tlb_range(start,end,vma) | ||
#define flush_tlb_kernel_range(s,e) __cpu_flush_kern_tlb_range(s,e) | ||
|
||
/* | ||
* On AArch64, the cache coherency is handled via the set_pte_at() function. | ||
*/ | ||
static inline void update_mmu_cache(struct vm_area_struct *vma, | ||
unsigned long addr, pte_t *ptep) | ||
{ | ||
/* | ||
* set_pte() does not have a DSB, so make sure that the page table | ||
* write is visible. | ||
*/ | ||
dsb(); | ||
} | ||
|
||
#endif | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Based on arch/arm/mm/tlb.S | ||
* | ||
* Copyright (C) 1997-2002 Russell King | ||
* Copyright (C) 2012 ARM Ltd. | ||
* Written by Catalin Marinas <catalin.marinas@arm.com> | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
#include <linux/linkage.h> | ||
#include <asm/assembler.h> | ||
#include <asm/asm-offsets.h> | ||
#include <asm/page.h> | ||
#include <asm/tlbflush.h> | ||
#include "proc-macros.S" | ||
|
||
/* | ||
* __cpu_flush_user_tlb_range(start, end, vma) | ||
* | ||
* Invalidate a range of TLB entries in the specified address space. | ||
* | ||
* - start - start address (may not be aligned) | ||
* - end - end address (exclusive, may not be aligned) | ||
* - vma - vma_struct describing address range | ||
*/ | ||
ENTRY(__cpu_flush_user_tlb_range) | ||
vma_vm_mm x3, x2 // get vma->vm_mm | ||
mmid x3, x3 // get vm_mm->context.id | ||
dsb sy | ||
lsr x0, x0, #12 // align address | ||
lsr x1, x1, #12 | ||
bfi x0, x3, #48, #16 // start VA and ASID | ||
bfi x1, x3, #48, #16 // end VA and ASID | ||
1: tlbi vae1is, x0 // TLB invalidate by address and ASID | ||
add x0, x0, #1 | ||
cmp x0, x1 | ||
b.lo 1b | ||
dsb sy | ||
ret | ||
ENDPROC(__cpu_flush_user_tlb_range) | ||
|
||
/* | ||
* __cpu_flush_kern_tlb_range(start,end) | ||
* | ||
* Invalidate a range of kernel TLB entries. | ||
* | ||
* - start - start address (may not be aligned) | ||
* - end - end address (exclusive, may not be aligned) | ||
*/ | ||
ENTRY(__cpu_flush_kern_tlb_range) | ||
dsb sy | ||
lsr x0, x0, #12 // align address | ||
lsr x1, x1, #12 | ||
1: tlbi vaae1is, x0 // TLB invalidate by address | ||
add x0, x0, #1 | ||
cmp x0, x1 | ||
b.lo 1b | ||
dsb sy | ||
isb | ||
ret | ||
ENDPROC(__cpu_flush_kern_tlb_range) |