blob: d458ec27b74c764c32ef22f537d69304e0914afb [file]
#ifndef _ASM_PGALLOC_H
#define _ASM_PGALLOC_H
/* The usual comment is "Caches aren't brain-dead on the <architecture>".
* Unfortunately, that doesn't apply to PA-RISC. */
#include <asm/processor.h>
#include <asm/fixmap.h>
#include <linux/threads.h>
#include <asm/pgtable.h>
#include <asm/cache.h>
/* Internal use D/I cache flushing routines... */
/* XXX: these functions must not access memory between f[di]ce instructions. */
static inline void __flush_dcache_range(unsigned long start, unsigned long size)
{
#if 0
register unsigned long count = (size / L1_CACHE_BYTES);
register unsigned long loop = cache_info.dc_loop;
register unsigned long i, j;
if (size > 64 * 1024) {
/* Just punt and clear the whole damn thing */
flush_data_cache();
return;
}
for(i = 0; i <= count; i++, start += L1_CACHE_BYTES)
for(j = 0; j < loop; j++)
fdce(start);
#else
flush_data_cache();
#endif
}
static inline void __flush_icache_range(unsigned long start, unsigned long size)
{
#if 0
register unsigned long count = (size / L1_CACHE_BYTES);
register unsigned long loop = cache_info.ic_loop;
register unsigned long i, j;
if (size > 64 * 1024) {
/* Just punt and clear the whole damn thing */
flush_instruction_cache();
return;
}
for(i = 0; i <= count; i++, start += L1_CACHE_BYTES)
for(j = 0; j < loop; j++)
fice(start);
#else
flush_instruction_cache();
#endif
}
static inline void
flush_kernel_dcache_range(unsigned long start, unsigned long size)
{
register unsigned long end = start + size;
register unsigned long i;
start &= ~(L1_CACHE_BYTES - 1);
for (i = start; i < end; i += L1_CACHE_BYTES) {
kernel_fdc(i);
}
asm volatile("sync" : : );
asm volatile("syncdma" : : );
}
extern void __flush_page_to_ram(unsigned long address);
#define flush_cache_all() flush_all_caches()
#define flush_cache_mm(foo) flush_all_caches()
#if 0
/* This is how I think the cache flushing should be done -- mrw */
extern inline void flush_cache_mm(struct mm_struct *mm) {
if (mm == current->mm) {
flush_user_dcache_range(mm->start_data, mm->end_data);
flush_user_icache_range(mm->start_code, mm->end_code);
} else {
flush_other_dcache_range(mm->context, mm->start_data, mm->end_data);
flush_other_icache_range(mm->context, mm->start_code, mm->end_code);
}
}
#endif
#define flush_cache_range(vma, start, end) do { \
__flush_dcache_range(start, (unsigned long)end - (unsigned long)start); \
__flush_icache_range(start, (unsigned long)end - (unsigned long)start); \
} while(0)
#define flush_cache_page(vma, vmaddr) do { \
__flush_dcache_range(vmaddr, PAGE_SIZE); \
__flush_icache_range(vmaddr, PAGE_SIZE); \
} while(0)
#define flush_page_to_ram(page) \
__flush_page_to_ram((unsigned long)page_address(page))
#define flush_icache_range(start, end) \
__flush_icache_range(start, end - start)
#define flush_icache_page(vma, page) \
__flush_icache_range(page_address(page), PAGE_SIZE)
#define flush_dcache_page(page) \
__flush_dcache_range(page_address(page), PAGE_SIZE)
/* TLB flushing routines.... */
extern void flush_data_tlb(void);
extern void flush_instruction_tlb(void);
#define flush_tlb() do { \
flush_data_tlb(); \
flush_instruction_tlb(); \
} while(0);
#define flush_tlb_all() flush_tlb() /* XXX p[id]tlb */
extern __inline__ void flush_tlb_pgtables(struct mm_struct *mm, unsigned long start, unsigned long end)
{
}
static inline void flush_instruction_tlb_range(unsigned long start,
unsigned long size)
{
#if 0
register unsigned long count = (size / PAGE_SIZE);
register unsigned long loop = cache_info.it_loop;
register unsigned long i, j;
for(i = 0; i <= count; i++, start += PAGE_SIZE)
for(j = 0; j < loop; j++)
pitlbe(start);
#else
flush_instruction_tlb();
#endif
}
static inline void flush_data_tlb_range(unsigned long start,
unsigned long size)
{
#if 0
register unsigned long count = (size / PAGE_SIZE);
register unsigned long loop = cache_info.dt_loop;
register unsigned long i, j;
for(i = 0; i <= count; i++, start += PAGE_SIZE)
for(j = 0; j < loop; j++)
pdtlbe(start);
#else
flush_data_tlb();
#endif
}
static inline void __flush_tlb_range(unsigned long space, unsigned long start,
unsigned long size)
{
unsigned long old_sr1;
if(!size)
return;
old_sr1 = mfsp(1);
mtsp(space, 1);
flush_data_tlb_range(start, size);
flush_instruction_tlb_range(start, size);
mtsp(old_sr1, 1);
}
extern void __flush_tlb_space(unsigned long space);
static inline void flush_tlb_mm(struct mm_struct *mm)
{
#if 0
__flush_tlb_space(mm->context);
#else
flush_tlb();
#endif
}
static inline void flush_tlb_page(struct vm_area_struct *vma,
unsigned long addr)
{
__flush_tlb_range(vma->vm_mm->context, addr, PAGE_SIZE);
}
static inline void flush_tlb_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
__flush_tlb_range(vma->vm_mm->context, start, end - start);
}
/*
* NOTE: Many of the below macros use PT_NLEVELS because
* it is convenient that PT_NLEVELS == LOG2(pte size in bytes),
* i.e. we use 3 level page tables when we use 8 byte pte's
* (for 64 bit) and 2 level page tables when we use 4 byte pte's
*/
#ifdef __LP64__
#define PT_NLEVELS 3
#define PT_INITIAL 4 /* Number of initial page tables */
#else
#define PT_NLEVELS 2
#define PT_INITIAL 2 /* Number of initial page tables */
#endif
/* Definitions for 1st level */
#define PGDIR_SHIFT (PAGE_SHIFT + (PT_NLEVELS - 1)*(PAGE_SHIFT - PT_NLEVELS))
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE-1))
#define PTRS_PER_PGD (1UL << (PAGE_SHIFT - PT_NLEVELS))
#define USER_PTRS_PER_PGD (TASK_SIZE/PGDIR_SIZE)
/* Definitions for 2nd level */
#define PMD_SHIFT (PAGE_SHIFT + (PAGE_SHIFT - PT_NLEVELS))
#define PMD_SIZE (1UL << PMD_SHIFT)
#define PMD_MASK (~(PMD_SIZE-1))
#if PT_NLEVELS == 3
#define PTRS_PER_PMD (1UL << (PAGE_SHIFT - PT_NLEVELS))
#else
#define PTRS_PER_PMD 1
#endif
/* Definitions for 3rd level */
#define PTRS_PER_PTE (1UL << (PAGE_SHIFT - PT_NLEVELS))
#define get_pgd_fast get_pgd_slow
#define free_pgd_fast free_pgd_slow
extern __inline__ pgd_t *get_pgd_slow(void)
{
extern unsigned long gateway_pgd_offset;
extern unsigned long gateway_pgd_entry;
pgd_t *ret = (pgd_t *)__get_free_page(GFP_KERNEL);
if (ret) {
memset (ret, 0, PTRS_PER_PGD * sizeof(pgd_t));
/* Install HP-UX and Linux gateway page translations */
pgd_val(*(ret + gateway_pgd_offset)) = gateway_pgd_entry;
}
return ret;
}
extern __inline__ void free_pgd_slow(pgd_t *pgd)
{
free_page((unsigned long)pgd);
}
#if PT_NLEVELS == 3
/* Three Level Page Table Support for pmd's */
extern __inline__ pmd_t *get_pmd_fast(void)
{
return NULL; /* la la */
}
#if 0
extern __inline__ void free_pmd_fast(pmd_t *pmd)
{
}
#else
#define free_pmd_fast free_pmd_slow
#endif
extern __inline__ pmd_t *get_pmd_slow(void)
{
pmd_t *pmd = (pmd_t *) __get_free_page(GFP_KERNEL);
if (pmd)
clear_page(pmd);
return pmd;
}
extern __inline__ void free_pmd_slow(pmd_t *pmd)
{
free_page((unsigned long)pmd);
}
extern void __bad_pgd(pgd_t *pgd);
extern inline pmd_t * pmd_alloc(pgd_t *pgd, unsigned long address)
{
address = (address >> PMD_SHIFT) & (PTRS_PER_PMD - 1);
if (pgd_none(*pgd))
goto getnew;
if (pgd_bad(*pgd))
goto fix;
return (pmd_t *) pgd_page(*pgd) + address;
getnew:
{
pmd_t *page = get_pmd_fast();
if (!page)
page = get_pmd_slow();
if (page) {
if (pgd_none(*pgd)) {
pgd_val(*pgd) = _PAGE_TABLE + __pa((unsigned long)page);
return page + address;
}
else
free_pmd_fast(page);
}
else {
return NULL;
}
}
fix:
__bad_pgd(pgd);
return NULL;
}
#else
/* Two Level Page Table Support for pmd's */
extern inline pmd_t * pmd_alloc(pgd_t * pgd, unsigned long address)
{
return (pmd_t *) pgd;
}
extern inline void free_pmd_fast(pmd_t * pmd)
{
}
#endif
extern __inline__ pte_t *get_pte_fast(void)
{
return NULL; /* la la */
}
#if 0
extern __inline__ void free_pte_fast(pte_t *pte)
{
}
#else
#define free_pte_fast free_pte_slow
#endif
extern pte_t *get_pte_slow(pmd_t *pmd, unsigned long address_preadjusted);
extern __inline__ void free_pte_slow(pte_t *pte)
{
free_page((unsigned long)pte);
}
#define pmd_alloc_kernel pmd_alloc
#define pte_alloc_kernel pte_alloc
#define pte_free(pte) free_pte_fast(pte)
#define pmd_free(pmd) free_pmd_fast(pmd)
#define pgd_free(pgd) free_pgd_fast(pgd)
#define pgd_alloc(mm) get_pgd_fast()
extern void __bad_pmd(pmd_t *pmd);
extern inline pte_t * pte_alloc(pmd_t * pmd, unsigned long address)
{
address = (address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1);
if (pmd_none(*pmd))
goto getnew;
if (pmd_bad(*pmd))
goto fix;
return (pte_t *) pmd_page(*pmd) + address;
getnew:
{
pte_t *page = get_pte_fast();
if (!page)
return get_pte_slow(pmd, address);
pmd_val(*pmd) = _PAGE_TABLE + __pa((unsigned long)page);
return page + address;
}
fix:
__bad_pmd(pmd);
return NULL;
}
extern int do_check_pgt_cache(int, int);
#endif