Skip to content

Commit

Permalink
avr32: Clean up and optimize the TLB operations
Browse files Browse the repository at this point in the history
This and the following patches aim to optimize the code dealing with
page tables and TLB operations. Each patch reduces the time it takes
to gzip a 16 MB file slightly, but I expect things like fork() and
mmap() will improve somewhat more.

This patch deals with the low-level TLB operations:

  * Remove unused _TLBEHI_I define
  * Use gcc builtins instead of inline assembly
  * Remove a few unnecessary pipeline flushes and nops
  * Introduce NR_TLB_ENTRIES define and use it instead of hardcoding it
    to 32 a few places throughout the code.
  * Use sysreg bitops instead of hardcoded shifts and masks
  * Make a few needlessly global functions static

Signed-off-by: Haavard Skinnemoen <haavard.skinnemoen@atmel.com>
  • Loading branch information
Haavard Skinnemoen committed Jul 2, 2008
1 parent d7ff2a4 commit b13d618
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 89 deletions.
175 changes: 87 additions & 88 deletions arch/avr32/mm/tlb.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,39 @@

#include <asm/mmu_context.h>

#define _TLBEHI_I 0x100
/* TODO: Get the correct number from the CONFIG1 system register */
#define NR_TLB_ENTRIES 32

void show_dtlb_entry(unsigned int index)
static void show_dtlb_entry(unsigned int index)
{
unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
u32 tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
unsigned long flags;

local_irq_save(flags);
mmucr_save = sysreg_read(MMUCR);
tlbehi_save = sysreg_read(TLBEHI);
mmucr = mmucr_save & 0x13;
mmucr |= index << 14;
mmucr = SYSREG_BFINS(DRP, index, mmucr_save);
sysreg_write(MMUCR, mmucr);

asm volatile("tlbr" : : : "memory");
__builtin_tlbr();
cpu_sync_pipeline();

tlbehi = sysreg_read(TLBEHI);
tlbelo = sysreg_read(TLBELO);

printk("%2u: %c %c %02x %05x %05x %o %o %c %c %c %c\n",
index,
(tlbehi & 0x200)?'1':'0',
(tlbelo & 0x100)?'1':'0',
(tlbehi & 0xff),
(tlbehi >> 12), (tlbelo >> 12),
(tlbelo >> 4) & 7, (tlbelo >> 2) & 3,
(tlbelo & 0x200)?'1':'0',
(tlbelo & 0x080)?'1':'0',
(tlbelo & 0x001)?'1':'0',
(tlbelo & 0x002)?'1':'0');
SYSREG_BFEXT(TLBEHI_V, tlbehi) ? '1' : '0',
SYSREG_BFEXT(G, tlbelo) ? '1' : '0',
SYSREG_BFEXT(ASID, tlbehi),
SYSREG_BFEXT(VPN, tlbehi) >> 2,
SYSREG_BFEXT(PFN, tlbelo) >> 2,
SYSREG_BFEXT(AP, tlbelo),
SYSREG_BFEXT(SZ, tlbelo),
SYSREG_BFEXT(TLBELO_C, tlbelo) ? 'C' : ' ',
SYSREG_BFEXT(B, tlbelo) ? 'B' : ' ',
SYSREG_BFEXT(W, tlbelo) ? 'W' : ' ',
SYSREG_BFEXT(TLBELO_D, tlbelo) ? 'D' : ' ');

sysreg_write(MMUCR, mmucr_save);
sysreg_write(TLBEHI, tlbehi_save);
Expand All @@ -54,60 +56,48 @@ void dump_dtlb(void)
unsigned int i;

printk("ID V G ASID VPN PFN AP SZ C B W D\n");
for (i = 0; i < 32; i++)
for (i = 0; i < NR_TLB_ENTRIES; i++)
show_dtlb_entry(i);
}

static unsigned long last_mmucr;

static inline void set_replacement_pointer(unsigned shift)
static void update_dtlb(unsigned long address, pte_t pte)
{
unsigned long mmucr, mmucr_save;
u32 tlbehi;
u32 mmucr;

mmucr = mmucr_save = sysreg_read(MMUCR);
/*
* We're not changing the ASID here, so no need to flush the
* pipeline.
*/
tlbehi = sysreg_read(TLBEHI);
tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
tlbehi |= address & MMU_VPN_MASK;
tlbehi |= SYSREG_BIT(TLBEHI_V);
sysreg_write(TLBEHI, tlbehi);

/* Does this mapping already exist? */
__asm__ __volatile__(
" tlbs\n"
" mfsr %0, %1"
: "=r"(mmucr)
: "i"(SYSREG_MMUCR));
__builtin_tlbs();
mmucr = sysreg_read(MMUCR);

if (mmucr & SYSREG_BIT(MMUCR_N)) {
/* Not found -- pick a not-recently-accessed entry */
unsigned long rp;
unsigned long tlbar = sysreg_read(TLBARLO);
unsigned int rp;
u32 tlbar = sysreg_read(TLBARLO);

rp = 32 - fls(tlbar);
if (rp == 32) {
rp = 0;
sysreg_write(TLBARLO, -1L);
}

mmucr &= 0x13;
mmucr |= (rp << shift);

mmucr = SYSREG_BFINS(DRP, rp, mmucr);
sysreg_write(MMUCR, mmucr);
}

last_mmucr = mmucr;
}

static void update_dtlb(unsigned long address, pte_t pte, unsigned long asid)
{
unsigned long vpn;

vpn = (address & MMU_VPN_MASK) | _TLBEHI_VALID | asid;
sysreg_write(TLBEHI, vpn);
cpu_sync_pipeline();

set_replacement_pointer(14);

sysreg_write(TLBELO, pte_val(pte) & _PAGE_FLAGS_HARDWARE_MASK);

/* Let's go */
asm volatile("nop\n\ttlbw" : : : "memory");
cpu_sync_pipeline();
__builtin_tlbw();
}

void update_mmu_cache(struct vm_area_struct *vma,
Expand All @@ -120,39 +110,40 @@ void update_mmu_cache(struct vm_area_struct *vma,
return;

local_irq_save(flags);
update_dtlb(address, pte, get_asid());
update_dtlb(address, pte);
local_irq_restore(flags);
}

void __flush_tlb_page(unsigned long asid, unsigned long page)
static void __flush_tlb_page(unsigned long asid, unsigned long page)
{
unsigned long mmucr, tlbehi;
u32 mmucr, tlbehi;

page |= asid;
sysreg_write(TLBEHI, page);
cpu_sync_pipeline();
asm volatile("tlbs");
/*
* Caller is responsible for masking out non-PFN bits in page
* and changing the current ASID if necessary. This means that
* we don't need to flush the pipeline after writing TLBEHI.
*/
tlbehi = page | asid;
sysreg_write(TLBEHI, tlbehi);

__builtin_tlbs();
mmucr = sysreg_read(MMUCR);

if (!(mmucr & SYSREG_BIT(MMUCR_N))) {
unsigned long tlbarlo;
unsigned long entry;
unsigned int entry;
u32 tlbarlo;

/* Clear the "valid" bit */
tlbehi = sysreg_read(TLBEHI);
tlbehi &= ~_TLBEHI_VALID;
sysreg_write(TLBEHI, tlbehi);
cpu_sync_pipeline();

/* mark the entry as "not accessed" */
entry = (mmucr >> 14) & 0x3f;
entry = SYSREG_BFEXT(DRP, mmucr);
tlbarlo = sysreg_read(TLBARLO);
tlbarlo |= (0x80000000 >> entry);
tlbarlo |= (0x80000000UL >> entry);
sysreg_write(TLBARLO, tlbarlo);

/* update the entry with valid bit clear */
asm volatile("tlbw");
cpu_sync_pipeline();
__builtin_tlbw();
}
}

Expand Down Expand Up @@ -190,17 +181,22 @@ void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,

local_irq_save(flags);
size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;

if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */
mm->context = NO_CONTEXT;
if (mm == current->mm)
activate_context(mm);
} else {
unsigned long asid = mm->context & MMU_CONTEXT_ASID_MASK;
unsigned long saved_asid = MMU_NO_ASID;
unsigned long asid;
unsigned long saved_asid;

asid = mm->context & MMU_CONTEXT_ASID_MASK;
saved_asid = MMU_NO_ASID;

start &= PAGE_MASK;
end += (PAGE_SIZE - 1);
end &= PAGE_MASK;

if (mm != current->mm) {
saved_asid = get_asid();
set_asid(asid);
Expand All @@ -218,33 +214,34 @@ void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
}

/*
* TODO: If this is only called for addresses > TASK_SIZE, we can probably
* skip the ASID stuff and just use the Global bit...
* This function depends on the pages to be flushed having the G
* (global) bit set in their pte. This is true for all
* PAGE_KERNEL(_RO) pages.
*/
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
unsigned long flags;
int size;

local_irq_save(flags);
size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */
flush_tlb_all();
} else {
unsigned long asid = init_mm.context & MMU_CONTEXT_ASID_MASK;
unsigned long saved_asid = get_asid();
unsigned long asid;

local_irq_save(flags);
asid = get_asid();

start &= PAGE_MASK;
end += (PAGE_SIZE - 1);
end &= PAGE_MASK;
set_asid(asid);

while (start < end) {
__flush_tlb_page(asid, start);
start += PAGE_SIZE;
}
set_asid(saved_asid);
local_irq_restore(flags);
}
local_irq_restore(flags);
}

void flush_tlb_mm(struct mm_struct *mm)
Expand Down Expand Up @@ -280,7 +277,7 @@ static void *tlb_start(struct seq_file *tlb, loff_t *pos)
{
static unsigned long tlb_index;

if (*pos >= 32)
if (*pos >= NR_TLB_ENTRIES)
return NULL;

tlb_index = 0;
Expand All @@ -291,7 +288,7 @@ static void *tlb_next(struct seq_file *tlb, void *v, loff_t *pos)
{
unsigned long *index = v;

if (*index >= 31)
if (*index >= NR_TLB_ENTRIES - 1)
return NULL;

++*pos;
Expand All @@ -313,16 +310,16 @@ static int tlb_show(struct seq_file *tlb, void *v)
if (*index == 0)
seq_puts(tlb, "ID V G ASID VPN PFN AP SZ C B W D\n");

BUG_ON(*index >= 32);
BUG_ON(*index >= NR_TLB_ENTRIES);

local_irq_save(flags);
mmucr_save = sysreg_read(MMUCR);
tlbehi_save = sysreg_read(TLBEHI);
mmucr = mmucr_save & 0x13;
mmucr |= *index << 14;
mmucr = SYSREG_BFINS(DRP, *index, mmucr_save);
sysreg_write(MMUCR, mmucr);

asm volatile("tlbr" : : : "memory");
/* TLBR might change the ASID */
__builtin_tlbr();
cpu_sync_pipeline();

tlbehi = sysreg_read(TLBEHI);
Expand All @@ -334,16 +331,18 @@ static int tlb_show(struct seq_file *tlb, void *v)
local_irq_restore(flags);

seq_printf(tlb, "%2lu: %c %c %02x %05x %05x %o %o %c %c %c %c\n",
*index,
(tlbehi & 0x200)?'1':'0',
(tlbelo & 0x100)?'1':'0',
(tlbehi & 0xff),
(tlbehi >> 12), (tlbelo >> 12),
(tlbelo >> 4) & 7, (tlbelo >> 2) & 3,
(tlbelo & 0x200)?'1':'0',
(tlbelo & 0x080)?'1':'0',
(tlbelo & 0x001)?'1':'0',
(tlbelo & 0x002)?'1':'0');
*index,
SYSREG_BFEXT(TLBEHI_V, tlbehi) ? '1' : '0',
SYSREG_BFEXT(G, tlbelo) ? '1' : '0',
SYSREG_BFEXT(ASID, tlbehi),
SYSREG_BFEXT(VPN, tlbehi) >> 2,
SYSREG_BFEXT(PFN, tlbelo) >> 2,
SYSREG_BFEXT(AP, tlbelo),
SYSREG_BFEXT(SZ, tlbelo),
SYSREG_BFEXT(TLBELO_C, tlbelo) ? '1' : '0',
SYSREG_BFEXT(B, tlbelo) ? '1' : '0',
SYSREG_BFEXT(W, tlbelo) ? '1' : '0',
SYSREG_BFEXT(TLBELO_D, tlbelo) ? '1' : '0');

return 0;
}
Expand Down
1 change: 0 additions & 1 deletion include/asm-avr32/tlbflush.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ extern void flush_tlb_mm(struct mm_struct *mm);
extern void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
unsigned long end);
extern void flush_tlb_page(struct vm_area_struct *vma, unsigned long page);
extern void __flush_tlb_page(unsigned long asid, unsigned long page);

extern void flush_tlb_kernel_range(unsigned long start, unsigned long end);

Expand Down

0 comments on commit b13d618

Please sign in to comment.