patch-1.3.71 linux/arch/sparc/mm/sun4c.c
Next file: linux/arch/sparc/mm/sun4c_vac.c
Previous file: linux/arch/sparc/mm/srmmu.c
Back to the patch index
Back to the overall index
- Lines: 2240
- Date:
Mon Mar 4 08:49:58 1996
- Orig file:
v1.3.70/linux/arch/sparc/mm/sun4c.c
- Orig date:
Mon Nov 27 12:48:27 1995
diff -u --recursive --new-file v1.3.70/linux/arch/sparc/mm/sun4c.c linux/arch/sparc/mm/sun4c.c
@@ -1,384 +1,1222 @@
-/* $Id: sun4c.c,v 1.56 1995/11/25 00:59:39 davem Exp $
- * sun4c.c: Sun4C specific mm routines.
+/* sun4c.c: Doing in software what should be done in hardware.
*
- * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
- */
-
-/* The SUN4C has an MMU based upon a Translation Lookaside Buffer scheme
- * where only so many translations can be loaded at once. As Linus said
- * in Boston, this is a broken way of doing things.
+ * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu)
*/
#include <linux/kernel.h>
-#include <linux/sched.h>
+#include <linux/mm.h>
-#include <asm/processor.h>
#include <asm/page.h>
#include <asm/pgtable.h>
-#include <asm/vac-ops.h>
#include <asm/vaddrs.h>
-#include <asm/asi.h>
-#include <asm/system.h>
-#include <asm/contregs.h>
-#include <asm/oplib.h>
#include <asm/idprom.h>
#include <asm/machines.h>
#include <asm/memreg.h>
-#include <asm/kdebug.h>
+#include <asm/processor.h>
-/* Pseg allocation structures. */
-static struct pseg_list s4cpseg_pool[256];
+extern int num_segmaps, num_contexts;
-struct pseg_list s4cpseg_free;
-struct pseg_list s4cpseg_used;
-static struct pseg_list s4cpseg_locked;
-static struct pseg_list s4cpseg_per_context[16];
+/* Flushing the cache. */
+struct sun4c_vac_props sun4c_vacinfo;
+static int ctxflushes, segflushes, pageflushes;
-static unsigned char pseg_count_per_context[16];
+/* Invalidate every sun4c cache line tag. */
+void sun4c_flush_all(void)
+{
+ unsigned long begin, end;
-unsigned int sun4c_pmd_align(unsigned int addr) { return SUN4C_PMD_ALIGN(addr); }
-unsigned int sun4c_pgdir_align(unsigned int addr) { return SUN4C_PGDIR_ALIGN(addr); }
+ if(sun4c_vacinfo.on)
+ panic("SUN4C: AIEEE, trying to invalidate vac while"
+ " it is on.");
-extern int num_segmaps, num_contexts;
+ /* Clear 'valid' bit in all cache line tags */
+ begin = AC_CACHETAGS;
+ end = (AC_CACHETAGS + sun4c_vacinfo.num_bytes);
+ while(begin < end) {
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (begin), "i" (ASI_CONTROL));
+ begin += sun4c_vacinfo.linesize;
+ }
+}
-/* First the functions which the mid-level code uses to directly
- * manipulate the software page tables. Some defines since we are
- * emulating the i386 page directory layout.
+/* Blow the entire current context out of the virtual cache. */
+/* static */ inline void sun4c_flush_context(void)
+{
+ unsigned long vaddr;
+
+ ctxflushes++;
+ if(sun4c_vacinfo.do_hwflushes) {
+ for(vaddr=0; vaddr < sun4c_vacinfo.num_bytes; vaddr+=PAGE_SIZE)
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (vaddr), "i" (ASI_HWFLUSHCONTEXT));
+ } else {
+ int incr = sun4c_vacinfo.linesize;
+ for(vaddr=0; vaddr < sun4c_vacinfo.num_bytes; vaddr+=incr)
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (vaddr), "i" (ASI_FLUSHCTX));
+ }
+}
+
+/* Scrape the segment starting at ADDR from the virtual cache. */
+static inline void sun4c_flush_segment(unsigned long addr)
+{
+ unsigned long end;
+
+ segflushes++;
+ addr &= SUN4C_REAL_PGDIR_MASK;
+ end = (addr + sun4c_vacinfo.num_bytes);
+ if(sun4c_vacinfo.do_hwflushes) {
+ for( ; addr < end; addr += PAGE_SIZE)
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (addr), "i" (ASI_HWFLUSHSEG));
+ } else {
+ int incr = sun4c_vacinfo.linesize;
+ for( ; addr < end; addr += incr)
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (addr), "i" (ASI_FLUSHSEG));
+ }
+}
+
+/* Bolix one page from the virtual cache. */
+static inline void sun4c_flush_page(unsigned long addr)
+{
+ addr &= PAGE_MASK;
+
+ pageflushes++;
+ if(sun4c_vacinfo.do_hwflushes) {
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (addr), "i" (ASI_HWFLUSHPAGE));
+ } else {
+ unsigned long end = addr + PAGE_SIZE;
+ int incr = sun4c_vacinfo.linesize;
+
+ for( ; addr < end; addr += incr)
+ __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : :
+ "r" (addr), "i" (ASI_FLUSHPG));
+ }
+}
+
+/* The sun4c's do have an on chip store buffer. And the way you
+ * clear them out isn't so obvious. The only way I can think of
+ * to accomplish this is to read the current context register,
+ * store the same value there, then do a bunch of nops for the
+ * pipeline to clear itself completely. This is only used for
+ * dealing with memory errors, so it is not that critical.
*/
-#define PGD_PRESENT 0x001
-#define PGD_RW 0x002
-#define PGD_USER 0x004
-#define PGD_ACCESSED 0x020
-#define PGD_DIRTY 0x040
-#define PGD_TABLE (PGD_PRESENT | PGD_RW | PGD_USER | PGD_ACCESSED | PGD_DIRTY)
+void sun4c_complete_all_stores(void)
+{
+ volatile int _unused;
+
+ _unused = sun4c_get_context();
+ sun4c_set_context(_unused);
+ nop(); nop(); nop(); nop();
+ nop(); nop(); nop(); nop();
+ /* Is that enough? */
+}
-unsigned long sun4c_vmalloc_start(void)
+/* Bootup utility functions. */
+static inline void sun4c_init_clean_segmap(unsigned char pseg)
{
- return SUN4C_VMALLOC_START;
+ unsigned long vaddr;
+
+ sun4c_put_segmap(0, pseg);
+ for(vaddr = 0; vaddr < SUN4C_REAL_PGDIR_SIZE; vaddr+=PAGE_SIZE)
+ sun4c_put_pte(vaddr, 0);
+ sun4c_put_segmap(0, invalid_segment);
}
-/* Update the root mmu directory on the sun4c mmu. */
-void sun4c_update_rootmmu_dir(struct task_struct *tsk, pgd_t *pgdir)
+static inline void sun4c_init_clean_mmu(unsigned long kernel_end)
{
- (tsk)->tss.pgd_ptr = (unsigned long) (pgdir);
+ unsigned long vaddr;
+ unsigned char savectx, ctx;
+
+ savectx = sun4c_get_context();
+ kernel_end = SUN4C_REAL_PGDIR_ALIGN(kernel_end);
+ for(ctx = 0; ctx < num_contexts; ctx++) {
+ sun4c_set_context(ctx);
+ for(vaddr = 0; vaddr < 0x20000000; vaddr += SUN4C_REAL_PGDIR_SIZE)
+ sun4c_put_segmap(vaddr, invalid_segment);
+ for(vaddr = 0xe0000000; vaddr < KERNBASE; vaddr += SUN4C_REAL_PGDIR_SIZE)
+ sun4c_put_segmap(vaddr, invalid_segment);
+ for(vaddr = kernel_end; vaddr < KADB_DEBUGGER_BEGVM; vaddr += SUN4C_REAL_PGDIR_SIZE)
+ sun4c_put_segmap(vaddr, invalid_segment);
+ for(vaddr = LINUX_OPPROM_ENDVM; vaddr; vaddr += SUN4C_REAL_PGDIR_SIZE)
+ sun4c_put_segmap(vaddr, invalid_segment);
+ }
+ sun4c_set_context(ctx);
+}
+
+void sun4c_probe_vac(void)
+{
+ int propval;
+
+ sun4c_disable_vac();
+ sun4c_vacinfo.num_bytes = prom_getintdefault(prom_root_node,
+ "vac-size", 65536);
+ sun4c_vacinfo.linesize = prom_getintdefault(prom_root_node,
+ "vac-linesize", 16);
+ sun4c_vacinfo.num_lines =
+ (sun4c_vacinfo.num_bytes / sun4c_vacinfo.linesize);
+ switch(sun4c_vacinfo.linesize) {
+ case 16:
+ sun4c_vacinfo.log2lsize = 4;
+ break;
+ case 32:
+ sun4c_vacinfo.log2lsize = 5;
+ break;
+ default:
+ prom_printf("probe_vac: Didn't expect vac-linesize of %d, halting\n",
+ sun4c_vacinfo.linesize);
+ prom_halt();
+ };
+
+ propval = prom_getintdefault(prom_root_node, "vac_hwflush", -1);
+ sun4c_vacinfo.do_hwflushes = (propval == -1 ?
+ prom_getintdefault(prom_root_node,
+ "vac-hwflush", 0) :
+ propval);
+
+ if(sun4c_vacinfo.num_bytes != 65536) {
+ prom_printf("WEIRD Sun4C VAC cache size, tell davem");
+ prom_halt();
+ }
+
+ sun4c_flush_all();
+ sun4c_enable_vac();
}
-int sun4c_pte_none(pte_t pte) { return !pte_val(pte); }
-int sun4c_pte_present(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_VALID; }
-int sun4c_pte_inuse(pte_t *ptep) { return mem_map[MAP_NR(ptep)].reserved || mem_map[MAP_NR(ptep)].count != 1; }
-void sun4c_pte_clear(pte_t *ptep) { pte_val(*ptep) = 0; }
-void sun4c_pte_reuse(pte_t *ptep)
+static void sun4c_probe_mmu(void)
{
- if(!mem_map[MAP_NR(ptep)].reserved)
- mem_map[MAP_NR(ptep)].count++;
+ num_segmaps = prom_getintdefault(prom_root_node, "mmu-npmg", 128);
+ num_contexts = prom_getintdefault(prom_root_node, "mmu-nctx", 0x8);
}
-int sun4c_pmd_none(pmd_t pmd) { return !pmd_val(pmd); }
-int sun4c_pmd_bad(pmd_t pmd)
+static inline void sun4c_init_ss2_cache_bug(void)
{
- return (pmd_val(pmd) & ~PAGE_MASK) != PGD_TABLE || pmd_val(pmd) > high_memory;
+ extern unsigned long start;
+
+ if(idprom->id_machtype == (SM_SUN4C | SM_4C_SS2)) {
+ /* Whee.. */
+ printk("SS2 cache bug detected, uncaching trap table page\n");
+ sun4c_flush_page((unsigned int) &start);
+ sun4c_put_pte(((unsigned long) &start),
+ (sun4c_get_pte((unsigned long) &start) | _SUN4C_PAGE_NOCACHE));
+ }
}
-int sun4c_pmd_present(pmd_t pmd) { return pmd_val(pmd) & PGD_PRESENT; }
-int sun4c_pmd_inuse(pmd_t *pmdp) { return 0; }
-void sun4c_pmd_clear(pmd_t *pmdp) { pmd_val(*pmdp) = 0; }
-void sun4c_pmd_reuse(pmd_t * pmdp) { }
-
-int sun4c_pgd_none(pgd_t pgd) { return 0; }
-int sun4c_pgd_bad(pgd_t pgd) { return 0; }
-int sun4c_pgd_present(pgd_t pgd) { return 1; }
-int sun4c_pgd_inuse(pgd_t *pgdp) { return mem_map[MAP_NR(pgdp)].reserved; }
-void sun4c_pgd_clear(pgd_t * pgdp) { }
+static inline unsigned long sun4c_init_alloc_dvma_pages(unsigned long start_mem)
+{
+ unsigned long addr, pte;
-/*
- * The following only work if pte_present() is true.
- * Undefined behaviour if not..
+ for(addr = DVMA_VADDR; addr < DVMA_END; addr += PAGE_SIZE) {
+ pte = (start_mem - PAGE_OFFSET) >> PAGE_SHIFT;
+ pte |= (_SUN4C_PAGE_VALID | _SUN4C_PAGE_WRITE | _SUN4C_PAGE_NOCACHE);
+ sun4c_put_pte(addr, pte);
+ start_mem += PAGE_SIZE;
+ }
+ return start_mem;
+}
+
+/* TLB management. */
+struct sun4c_mmu_entry {
+ struct sun4c_mmu_entry *next;
+ struct sun4c_mmu_entry *prev;
+ unsigned long vaddr;
+ unsigned char pseg;
+ unsigned char locked;
+};
+static struct sun4c_mmu_entry mmu_entry_pool[256];
+
+static void sun4c_init_mmu_entry_pool(void)
+{
+ int i;
+
+ for(i=0; i < 256; i++) {
+ mmu_entry_pool[i].pseg = i;
+ mmu_entry_pool[i].next = 0;
+ mmu_entry_pool[i].prev = 0;
+ mmu_entry_pool[i].vaddr = 0;
+ mmu_entry_pool[i].locked = 0;
+ }
+ mmu_entry_pool[invalid_segment].locked = 1;
+}
+
+static inline void fix_permissions(unsigned long vaddr, unsigned long bits_on,
+ unsigned long bits_off)
+{
+ unsigned long start, end;
+
+ end = vaddr + SUN4C_REAL_PGDIR_SIZE;
+ for(start = vaddr; start < end; start += PAGE_SIZE)
+ if(sun4c_get_pte(start) & _SUN4C_PAGE_VALID)
+ sun4c_put_pte(start, (sun4c_get_pte(start) | bits_on) &
+ ~bits_off);
+}
+
+static inline void sun4c_init_map_kernelprom(unsigned long kernel_end)
+{
+ unsigned long vaddr;
+ unsigned char pseg, ctx;
+
+ for(vaddr = KADB_DEBUGGER_BEGVM;
+ vaddr < LINUX_OPPROM_ENDVM;
+ vaddr += SUN4C_REAL_PGDIR_SIZE) {
+ pseg = sun4c_get_segmap(vaddr);
+ if(pseg != invalid_segment) {
+ mmu_entry_pool[pseg].locked = 1;
+ for(ctx = 0; ctx < num_contexts; ctx++)
+ prom_putsegment(ctx, vaddr, pseg);
+ fix_permissions(vaddr, _SUN4C_PAGE_PRIV, 0);
+ }
+ }
+ for(vaddr = KERNBASE; vaddr < kernel_end; vaddr += SUN4C_REAL_PGDIR_SIZE) {
+ pseg = sun4c_get_segmap(vaddr);
+ mmu_entry_pool[pseg].locked = 1;
+ for(ctx = 0; ctx < num_contexts; ctx++)
+ prom_putsegment(ctx, vaddr, pseg);
+ fix_permissions(vaddr, _SUN4C_PAGE_PRIV, _SUN4C_PAGE_NOCACHE);
+ }
+}
+
+static void sun4c_init_lock_area(unsigned long start, unsigned long end)
+{
+ int i, ctx;
+
+ while(start < end) {
+ for(i=0; i < invalid_segment; i++)
+ if(!mmu_entry_pool[i].locked)
+ break;
+ mmu_entry_pool[i].locked = 1;
+ sun4c_init_clean_segmap(i);
+ for(ctx = 0; ctx < num_contexts; ctx++)
+ prom_putsegment(ctx, start, mmu_entry_pool[i].pseg);
+ start += SUN4C_REAL_PGDIR_SIZE;
+ }
+}
+
+struct sun4c_mmu_ring {
+ struct sun4c_mmu_entry ringhd;
+ int num_entries;
+};
+static struct sun4c_mmu_ring sun4c_context_ring[16]; /* used user entries */
+static struct sun4c_mmu_ring sun4c_ufree_ring; /* free user entries */
+static struct sun4c_mmu_ring sun4c_kernel_ring; /* used kernel entries */
+static struct sun4c_mmu_ring sun4c_kfree_ring; /* free kernel entries */
+
+static inline void sun4c_init_rings(void)
+{
+ int i;
+ for(i=0; i<16; i++) {
+ sun4c_context_ring[i].ringhd.next =
+ sun4c_context_ring[i].ringhd.prev =
+ &sun4c_context_ring[i].ringhd;
+ sun4c_context_ring[i].num_entries = 0;
+ }
+ sun4c_ufree_ring.ringhd.next = sun4c_ufree_ring.ringhd.prev =
+ &sun4c_ufree_ring.ringhd;
+ sun4c_kernel_ring.ringhd.next = sun4c_kernel_ring.ringhd.prev =
+ &sun4c_kernel_ring.ringhd;
+ sun4c_kfree_ring.ringhd.next = sun4c_kfree_ring.ringhd.prev =
+ &sun4c_kfree_ring.ringhd;
+ sun4c_ufree_ring.num_entries = sun4c_kernel_ring.num_entries =
+ sun4c_kfree_ring.num_entries = 0;
+}
+
+static inline void add_ring(struct sun4c_mmu_ring *ring, struct sun4c_mmu_entry *entry)
+{
+ struct sun4c_mmu_entry *head = &ring->ringhd;
+
+ entry->prev = head;
+ (entry->next = head->next)->prev = entry;
+ head->next = entry;
+ ring->num_entries++;
+}
+
+static inline void remove_ring(struct sun4c_mmu_ring *ring, struct sun4c_mmu_entry *entry)
+{
+ struct sun4c_mmu_entry *next = entry->next;
+
+ (next->prev = entry->prev)->next = next;
+ ring->num_entries--;
+}
+
+static inline void recycle_ring(struct sun4c_mmu_ring *ring, struct sun4c_mmu_entry *entry)
+{
+ struct sun4c_mmu_entry *head = &ring->ringhd;
+ struct sun4c_mmu_entry *next = entry->next;
+
+ (next->prev = entry->prev)->next = next;
+ entry->prev = head; (entry->next = head->next)->prev = entry;
+ head->next = entry;
+ /* num_entries stays the same */
+}
+
+static inline void free_user_entry(int ctx, struct sun4c_mmu_entry *entry)
+{
+ remove_ring(sun4c_context_ring+ctx, entry);
+ add_ring(&sun4c_ufree_ring, entry);
+}
+
+static inline void assign_user_entry(int ctx, struct sun4c_mmu_entry *entry)
+{
+ remove_ring(&sun4c_ufree_ring, entry);
+ add_ring(sun4c_context_ring+ctx, entry);
+}
+
+static inline void free_kernel_entry(struct sun4c_mmu_entry *entry, struct sun4c_mmu_ring *ring)
+{
+ remove_ring(ring, entry);
+ add_ring(&sun4c_kfree_ring, entry);
+}
+
+static inline void assign_kernel_entry(struct sun4c_mmu_entry *entry, struct sun4c_mmu_ring *ring)
+{
+ remove_ring(ring, entry);
+ add_ring(&sun4c_kernel_ring, entry);
+}
+
+static inline void reassign_kernel_entry(struct sun4c_mmu_entry *entry)
+{
+ recycle_ring(&sun4c_kernel_ring, entry);
+}
+
+static void sun4c_init_fill_kernel_ring(int howmany)
+{
+ int i;
+
+ while(howmany) {
+ for(i=0; i < invalid_segment; i++)
+ if(!mmu_entry_pool[i].locked)
+ break;
+ mmu_entry_pool[i].locked = 1;
+ sun4c_init_clean_segmap(i);
+ add_ring(&sun4c_kfree_ring, &mmu_entry_pool[i]);
+ howmany--;
+ }
+}
+
+static void sun4c_init_fill_user_ring(void)
+{
+ int i;
+
+ for(i=0; i < invalid_segment; i++) {
+ if(mmu_entry_pool[i].locked)
+ continue;
+ sun4c_init_clean_segmap(i);
+ add_ring(&sun4c_ufree_ring, &mmu_entry_pool[i]);
+ }
+}
+
+static inline void sun4c_kernel_unmap(struct sun4c_mmu_entry *kentry)
+{
+ int savectx, ctx;
+
+ savectx = sun4c_get_context();
+ flush_user_windows();
+ sun4c_flush_segment(kentry->vaddr);
+ for(ctx = 0; ctx < num_contexts; ctx++) {
+ sun4c_set_context(ctx);
+ sun4c_put_segmap(kentry->vaddr, invalid_segment);
+ }
+ sun4c_set_context(savectx);
+}
+
+static inline void sun4c_kernel_map(struct sun4c_mmu_entry *kentry)
+{
+ int savectx, ctx;
+
+ savectx = sun4c_get_context();
+ flush_user_windows();
+ for(ctx = 0; ctx < num_contexts; ctx++) {
+ sun4c_set_context(ctx);
+ sun4c_put_segmap(kentry->vaddr, kentry->pseg);
+ }
+ sun4c_set_context(savectx);
+}
+
+static inline void sun4c_user_unmap(struct sun4c_mmu_entry *uentry)
+{
+ sun4c_flush_segment(uentry->vaddr);
+ sun4c_put_segmap(uentry->vaddr, invalid_segment);
+}
+
+static inline void sun4c_user_map(struct sun4c_mmu_entry *uentry)
+{
+ unsigned long start = uentry->vaddr;
+ unsigned long end = start + SUN4C_REAL_PGDIR_SIZE;
+
+ sun4c_put_segmap(uentry->vaddr, uentry->pseg);
+ while(start < end) {
+ sun4c_put_pte(start, 0);
+ start += PAGE_SIZE;
+ }
+}
+
+static inline void sun4c_demap_context(struct sun4c_mmu_ring *crp, unsigned char ctx)
+{
+ struct sun4c_mmu_entry *this_entry, *next_entry;
+ int savectx = sun4c_get_context();
+
+ this_entry = crp->ringhd.next;
+ flush_user_windows();
+ sun4c_set_context(ctx);
+ while(crp->num_entries) {
+ next_entry = this_entry->next;
+ sun4c_user_unmap(this_entry);
+ free_user_entry(ctx, this_entry);
+ this_entry = next_entry;
+ }
+ sun4c_set_context(savectx);
+}
+
+static inline void sun4c_demap_one(struct sun4c_mmu_ring *crp, unsigned char ctx)
+{
+ struct sun4c_mmu_entry *entry = crp->ringhd.next;
+ int savectx = sun4c_get_context();
+
+ flush_user_windows();
+ sun4c_set_context(ctx);
+ sun4c_user_unmap(entry);
+ free_user_entry(ctx, entry);
+ sun4c_set_context(savectx);
+}
+
+/* Using this method to free up mmu entries eliminates a lot of
+ * potention races since we have a kernel that incurs tlb
+ * replacement faults. There may be performance penalties.
+ */
+static inline struct sun4c_mmu_entry *sun4c_user_strategy(void)
+{
+ struct sun4c_mmu_ring *rp = 0;
+ unsigned char mmuhog, i, ctx = 0;
+
+ /* If some are free, return first one. */
+ if(sun4c_ufree_ring.num_entries)
+ return sun4c_ufree_ring.ringhd.next;
+
+ /* Else free one up. */
+ mmuhog = 0;
+ for(i=0; i < num_contexts; i++) {
+ if(sun4c_context_ring[i].num_entries > mmuhog) {
+ rp = &sun4c_context_ring[i];
+ mmuhog = rp->num_entries;
+ ctx = i;
+ }
+ }
+ sun4c_demap_one(rp, ctx);
+ return sun4c_ufree_ring.ringhd.next;
+}
+
+static inline struct sun4c_mmu_entry *sun4c_kernel_strategy(void)
+{
+ struct sun4c_mmu_entry *this_entry;
+
+ /* If some are free, return first one. */
+ if(sun4c_kfree_ring.num_entries)
+ return sun4c_kfree_ring.ringhd.next;
+
+ /* Else free one up. */
+ this_entry = sun4c_kernel_ring.ringhd.prev;
+ sun4c_kernel_unmap(this_entry);
+ free_kernel_entry(this_entry, &sun4c_kernel_ring);
+ return sun4c_kfree_ring.ringhd.next;
+}
+
+static inline void alloc_user_segment(unsigned long address, unsigned char ctx)
+{
+ struct sun4c_mmu_entry *entry;
+
+ address &= SUN4C_REAL_PGDIR_MASK;
+ entry = sun4c_user_strategy();
+ assign_user_entry(ctx, entry);
+ entry->vaddr = address;
+ sun4c_user_map(entry);
+}
+
+static inline void alloc_kernel_segment(unsigned long address)
+{
+ struct sun4c_mmu_entry *entry;
+
+ address &= SUN4C_REAL_PGDIR_MASK;
+ entry = sun4c_kernel_strategy();
+
+ assign_kernel_entry(entry, &sun4c_kfree_ring);
+ entry->vaddr = address;
+ sun4c_kernel_map(entry);
+}
+
+/* XXX Just like kernel tlb replacement we'd like to have a low level
+ * XXX equivalent for user faults which need not go through the mm
+ * XXX subsystem just to load a mmu entry. But this might not be as
+ * XXX feasible since we need to go through the kernel page tables
+ * XXX for this process, which we currently don't lock into the mmu
+ * XXX so we would fault with traps off... must think about this...
+ */
+static void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+ address &= PAGE_MASK;
+ if(sun4c_get_segmap(address) == invalid_segment)
+ alloc_user_segment(address, sun4c_get_context());
+ sun4c_put_pte(address, pte_val(pte));
+ restore_flags(flags);
+}
+
+/* READ THIS: If you put any diagnostic printing code in any of the kernel
+ * fault handling code you will lose badly. This is the most
+ * delicate piece of code in the entire kernel, atomicity of
+ * kernel tlb replacement must be guarenteed. This is why we
+ * have seperate user and kernel allocation rings to alleviate
+ * as many bad interactions as possible.
+ *
+ * XXX Someday make this into a fast in-window trap handler to avoid
+ * XXX any and all races. *High* priority, also for performance.
*/
-int sun4c_pte_read(pte_t pte) { return !(pte_val(pte) & _SUN4C_PAGE_PRIV); }
-int sun4c_pte_write(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_WRITE; }
-int sun4c_pte_exec(pte_t pte) { return !(pte_val(pte) & _SUN4C_PAGE_PRIV); }
-int sun4c_pte_dirty(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_DIRTY; }
-int sun4c_pte_young(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_REF; }
-int sun4c_pte_cow(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_COW; }
-
-pte_t sun4c_pte_wrprotect(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_WRITE; return pte; }
-pte_t sun4c_pte_rdprotect(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_PRIV; return pte; }
-pte_t sun4c_pte_exprotect(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_PRIV; return pte; }
-pte_t sun4c_pte_mkclean(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_DIRTY; return pte; }
-pte_t sun4c_pte_mkold(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_REF; return pte; }
-pte_t sun4c_pte_uncow(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_COW; return pte; }
-pte_t sun4c_pte_mkwrite(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_WRITE; return pte; }
-pte_t sun4c_pte_mkread(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_PRIV; return pte; }
-pte_t sun4c_pte_mkexec(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_PRIV; return pte; }
-pte_t sun4c_pte_mkdirty(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_DIRTY; return pte; }
-pte_t sun4c_pte_mkyoung(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_REF; return pte; }
-pte_t sun4c_pte_mkcow(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_COW; return pte; }
+static void sun4c_quick_kernel_fault(unsigned long address)
+{
+ unsigned long end, flags;
+
+ save_flags(flags); cli();
+ address &= SUN4C_REAL_PGDIR_MASK;
+ end = address + SUN4C_REAL_PGDIR_SIZE;
+ if(sun4c_get_segmap(address) == invalid_segment)
+ alloc_kernel_segment(address);
+
+ if(address < SUN4C_VMALLOC_START) {
+ unsigned long pte;
+ pte = (address - PAGE_OFFSET) >> PAGE_SHIFT;
+ pte |= pgprot_val(SUN4C_PAGE_KERNEL);
+ /* Stupid pte tricks... */
+ while(address < end) {
+ sun4c_put_pte(address, pte++);
+ address += PAGE_SIZE;
+ }
+ } else {
+ pte_t *ptep;
+
+ ptep = (pte_t *) (PAGE_MASK & pgd_val(swapper_pg_dir[address>>SUN4C_PGDIR_SHIFT]));
+ ptep = (ptep + ((address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1)));
+ while(address < end) {
+ sun4c_put_pte(address, pte_val(*ptep++));
+ address += PAGE_SIZE;
+ }
+ }
+ restore_flags(flags);
+}
/*
- * Conversion functions: convert a page and protection to a page entry,
- * and a page entry and page directory to the page they refer to.
+ * 4 page buckets for task struct and kernel stack allocation.
+ *
+ * TASK_STACK_BEGIN
+ * bucket[0]
+ * bucket[1]
+ * [ ... ]
+ * bucket[NR_TASKS-1]
+ * TASK_STACK_BEGIN + (sizeof(struct task_bucket) * NR_TASKS)
+ *
+ * Each slot looks like:
+ *
+ * page 1 -- task struct
+ * page 2 -- unmapped, for stack redzone (maybe use for pgd)
+ * page 3/4 -- kernel stack
*/
-pte_t sun4c_mk_pte(unsigned long page, pgprot_t pgprot)
+
+struct task_bucket {
+ struct task_struct task;
+ char _unused1[PAGE_SIZE - sizeof(struct task_struct)];
+ char _unused2[PAGE_SIZE];
+ char kstack[(PAGE_SIZE<<1)];
+};
+
+struct task_bucket *sun4c_bucket[NR_TASKS];
+
+#define BUCKET_EMPTY ((struct task_bucket *) 0)
+#define BUCKET_SIZE (PAGE_SIZE << 2)
+#define BUCKET_SHIFT 14 /* log2(sizeof(struct task_bucket)) */
+#define BUCKET_NUM(addr) ((((addr) - SUN4C_LOCK_VADDR) >> BUCKET_SHIFT))
+#define BUCKET_ADDR(num) (((num) << BUCKET_SHIFT) + SUN4C_LOCK_VADDR)
+#define BUCKET_PTE(page) \
+ ((((page) - PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(SUN4C_PAGE_KERNEL))
+#define BUCKET_PTE_PAGE(pte) \
+ (PAGE_OFFSET + (((pte) & 0xffff) << PAGE_SHIFT))
+
+static inline void get_task_segment(unsigned long addr)
{
- return __pte(((page - PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(pgprot));
+ struct sun4c_mmu_entry *stolen;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+ addr &= SUN4C_REAL_PGDIR_MASK;
+ stolen = sun4c_user_strategy();
+ remove_ring(&sun4c_ufree_ring, stolen);
+ stolen->vaddr = addr;
+ sun4c_kernel_map(stolen);
+ restore_flags(flags);
}
-pte_t sun4c_pte_modify(pte_t pte, pgprot_t newprot)
+static inline void free_task_segment(unsigned long addr)
{
- return __pte((pte_val(pte) & _SUN4C_PAGE_CHG_MASK) | pgprot_val(newprot));
+ struct sun4c_mmu_entry *entry;
+ unsigned long flags;
+ unsigned char pseg;
+
+ save_flags(flags); cli();
+ addr &= SUN4C_REAL_PGDIR_MASK;
+ pseg = sun4c_get_segmap(addr);
+ entry = &mmu_entry_pool[pseg];
+ sun4c_flush_segment(addr);
+ sun4c_kernel_unmap(entry);
+ add_ring(&sun4c_ufree_ring, entry);
+ restore_flags(flags);
}
-unsigned long sun4c_pte_page(pte_t pte)
+static inline void garbage_collect(int entry)
{
- return (PAGE_OFFSET + ((pte_val(pte) & 0xffff) << (PAGE_SHIFT)));
+ int start, end;
+
+ /* 16 buckets per segment... */
+ entry &= ~15;
+ start = entry;
+ for(end = (start + 16); start < end; start++)
+ if(sun4c_bucket[start] != BUCKET_EMPTY)
+ return;
+ /* Entire segment empty, release it. */
+ free_task_segment(BUCKET_ADDR(entry));
+}
+
+static struct task_struct *sun4c_alloc_task_struct(void)
+{
+ unsigned long addr, page;
+ int entry;
+
+ page = get_free_page(GFP_KERNEL);
+ if(!page)
+ return (struct task_struct *) 0;
+ /* XXX Bahh, linear search too slow, use hash
+ * XXX table in final implementation. Or
+ * XXX keep track of first free when we free
+ * XXX a bucket... anything but this.
+ */
+ for(entry = 0; entry < NR_TASKS; entry++)
+ if(sun4c_bucket[entry] == BUCKET_EMPTY)
+ break;
+ if(entry == NR_TASKS) {
+ free_page(page);
+ return (struct task_struct *) 0;
+ }
+ addr = BUCKET_ADDR(entry);
+ sun4c_bucket[entry] = (struct task_bucket *) addr;
+ if(sun4c_get_segmap(addr) == invalid_segment)
+ get_task_segment(addr);
+ sun4c_put_pte(addr, BUCKET_PTE(page));
+ return (struct task_struct *) addr;
}
-unsigned long sun4c_pmd_page(pmd_t pmd)
+static unsigned long sun4c_alloc_kernel_stack(struct task_struct *tsk)
{
- return (pmd_val(pmd) & PAGE_MASK);
+ unsigned long saddr = (unsigned long) tsk;
+ unsigned long page[2];
+
+ if(!saddr)
+ return 0;
+ page[0] = get_free_page(GFP_KERNEL);
+ if(!page[0])
+ return 0;
+ page[1] = get_free_page(GFP_KERNEL);
+ if(!page[1]) {
+ free_page(page[0]);
+ return 0;
+ }
+ saddr += (PAGE_SIZE << 1);
+ sun4c_put_pte(saddr - PAGE_SIZE, 0);
+ sun4c_put_pte(saddr, BUCKET_PTE(page[0]));
+ sun4c_put_pte(saddr + PAGE_SIZE, BUCKET_PTE(page[1]));
+ return saddr;
}
-/* to find an entry in a page-table-directory */
-pgd_t *sun4c_pgd_offset(struct mm_struct * mm, unsigned long address)
+static void sun4c_free_kernel_stack(unsigned long stack)
{
- return mm->pgd + (address >> SUN4C_PGDIR_SHIFT);
+ unsigned long page[2];
+
+ page[0] = BUCKET_PTE_PAGE(sun4c_get_pte(stack));
+ page[1] = BUCKET_PTE_PAGE(sun4c_get_pte(stack+PAGE_SIZE));
+ sun4c_flush_segment(stack & SUN4C_REAL_PGDIR_MASK);
+ sun4c_put_pte(stack, 0);
+ sun4c_put_pte(stack + PAGE_SIZE, 0);
+ free_page(page[0]);
+ free_page(page[1]);
}
-/* Find an entry in the second-level page table.. */
-pmd_t *sun4c_pmd_offset(pgd_t * dir, unsigned long address)
+static void sun4c_free_task_struct(struct task_struct *tsk)
{
- return (pmd_t *) dir;
+ unsigned long tsaddr = (unsigned long) tsk;
+ unsigned long page = BUCKET_PTE_PAGE(sun4c_get_pte(tsaddr));
+ int entry = BUCKET_NUM(tsaddr);
+
+ sun4c_flush_segment(tsaddr & SUN4C_REAL_PGDIR_MASK);
+ sun4c_put_pte(tsaddr, 0);
+ sun4c_bucket[entry] = BUCKET_EMPTY;
+ free_page(page);
+ garbage_collect(entry);
}
-/* Find an entry in the third-level page table.. */
-pte_t *sun4c_pte_offset(pmd_t * dir, unsigned long address)
+static void sun4c_init_buckets(void)
{
- return (pte_t *) sun4c_pmd_page(*dir) + ((address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1));
+ int entry;
+
+ if(sizeof(struct task_bucket) != (PAGE_SIZE << 2)) {
+ prom_printf("task bucket not 4 pages!\n");
+ prom_halt();
+ }
+ for(entry = 0; entry < NR_TASKS; entry++)
+ sun4c_bucket[entry] = BUCKET_EMPTY;
+}
+
+static unsigned long sun4c_iobuffer_start;
+static unsigned long sun4c_iobuffer_end;
+static unsigned long *sun4c_iobuffer_map;
+static int iobuffer_map_size;
+
+static char *sun4c_lockpage(char *vaddr, unsigned long _unused)
+{
+ unsigned long vpage, voffset, search, pte;
+ unsigned long npage;
+
+ vpage = ((unsigned long) vaddr) & PAGE_MASK;
+ voffset = ((unsigned long) vaddr) & ~PAGE_MASK;
+ pte = ((vpage-PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(SUN4C_PAGE_KERNEL);
+ pte |= _SUN4C_PAGE_NOCACHE;
+ search = find_first_zero_bit(sun4c_iobuffer_map, iobuffer_map_size);
+ set_bit(search, sun4c_iobuffer_map);
+ npage = (search << PAGE_SHIFT) + sun4c_iobuffer_start;
+ sun4c_flush_page(vpage);
+ sun4c_put_pte(npage, pte);
+ return (char *) (npage + voffset);
+}
+
+static void sun4c_unlockpage(char *vaddr, unsigned long _unused)
+{
+ unsigned long vpage, nr;
+
+ vpage = (unsigned long) vaddr;
+ vpage &= PAGE_MASK;
+ nr = (vpage - sun4c_iobuffer_start) >> PAGE_SHIFT;
+ sun4c_put_pte(vpage, 0);
+ clear_bit(nr, sun4c_iobuffer_map);
+}
+
+/* Note the scsi code at init time passes to here buffers
+ * which sit on the kernel stack, those are already locked
+ * by implication and fool the page locking code above
+ * if passed to by mistake.
+ */
+static char *sun4c_get_scsi_buffer(char *bufptr, unsigned long len, struct linux_sbus *sbus)
+{
+ unsigned long page1, page2;
+
+ page1 = ((unsigned long) bufptr) & PAGE_MASK;
+ page2 = (((unsigned long) bufptr) + len - 1) & PAGE_MASK;
+ if(page1 != page2) {
+ printk("Problem, trying to lock multipage scsi buffer.\n");
+ printk("page1<%08lx> page2<%08lx>\n", page1, page2);
+ panic("Scsi buffer too big.");
+ }
+ if(page1 > high_memory)
+ return bufptr; /* already locked */
+ return sun4c_lockpage(bufptr, PAGE_SIZE);
+}
+
+static void sun4c_release_scsi_buffer(char *bufptr, unsigned long len, struct linux_sbus *sbus)
+{
+ unsigned long page = (unsigned long) bufptr;
+
+ if(page < sun4c_iobuffer_start)
+ return; /* On kernel stack or similar, see above */
+ sun4c_unlockpage(bufptr, PAGE_SIZE);
+}
+
+#define TASK_ENTRY_SIZE (3 * PAGE_SIZE)
+#define LONG_ALIGN(x) (((x)+(sizeof(long))-1)&~((sizeof(long))-1))
+
+struct vm_area_struct sun4c_kstack_vma;
+
+static unsigned long sun4c_init_lock_areas(unsigned long start_mem)
+{
+ unsigned long sun4c_taskstack_start;
+ unsigned long sun4c_taskstack_end;
+ int bitmap_size;
+
+ sun4c_init_buckets();
+ sun4c_taskstack_start = SUN4C_LOCK_VADDR;
+ sun4c_taskstack_end = (sun4c_taskstack_start +
+ (TASK_ENTRY_SIZE * NR_TASKS));
+ if(sun4c_taskstack_end >= SUN4C_LOCK_END) {
+ prom_printf("Too many tasks, decrease NR_TASKS please.\n");
+ prom_halt();
+ }
+
+ sun4c_iobuffer_start = SUN4C_REAL_PGDIR_ALIGN(sun4c_taskstack_end);
+ sun4c_iobuffer_end = sun4c_iobuffer_start + SUN4C_REAL_PGDIR_SIZE;
+ bitmap_size = (sun4c_iobuffer_end - sun4c_iobuffer_start) >> PAGE_SHIFT;
+ bitmap_size = (bitmap_size + 7) >> 3;
+ bitmap_size = LONG_ALIGN(bitmap_size);
+ iobuffer_map_size = bitmap_size << 3;
+ sun4c_iobuffer_map = (unsigned long *) start_mem;
+ memset((void *) start_mem, 0, bitmap_size);
+ start_mem += bitmap_size;
+
+ /* Now get us some mmu entries for I/O maps. */
+ sun4c_init_lock_area(sun4c_iobuffer_start, sun4c_iobuffer_end);
+ sun4c_kstack_vma.vm_mm = init_task.mm;
+ sun4c_kstack_vma.vm_start = sun4c_taskstack_start;
+ sun4c_kstack_vma.vm_end = sun4c_taskstack_end;
+ sun4c_kstack_vma.vm_page_prot = PAGE_SHARED;
+ sun4c_kstack_vma.vm_flags = VM_READ | VM_WRITE | VM_EXEC;
+ insert_vm_struct(&init_task, &sun4c_kstack_vma);
+ return start_mem;
+}
+
+static void sun4c_invalidate_all(void)
+{
+ struct sun4c_mmu_entry *this_entry, *next_entry;
+
+ this_entry = sun4c_kernel_ring.ringhd.next;
+ while(sun4c_kernel_ring.num_entries) {
+ next_entry = this_entry->next;
+ sun4c_kernel_unmap(this_entry);
+ free_kernel_entry(this_entry, &sun4c_kernel_ring);
+ this_entry = next_entry;
+ }
+}
+
+static void sun4c_invalidate_mm(struct mm_struct *mm)
+{
+ if(mm->context == NO_CONTEXT)
+ return;
+ sun4c_demap_context(&sun4c_context_ring[mm->context], mm->context);
+}
+
+static void sun4c_invalidate_range(struct mm_struct *mm, unsigned long start, unsigned long end)
+{
+ struct sun4c_mmu_entry *this_entry;
+ unsigned char pseg, savectx;
+
+ if(mm->context == NO_CONTEXT)
+ return;
+ flush_user_windows();
+ savectx = sun4c_get_context();
+ sun4c_set_context(mm->context);
+ start &= SUN4C_REAL_PGDIR_MASK;
+ while(start < end) {
+ pseg = sun4c_get_segmap(start);
+ if(pseg == invalid_segment)
+ goto next_one;
+ this_entry = &mmu_entry_pool[pseg];
+ sun4c_user_unmap(this_entry);
+ free_user_entry(mm->context, this_entry);
+ next_one:
+ start += SUN4C_REAL_PGDIR_SIZE;
+ }
+ sun4c_set_context(savectx);
+}
+
+static void sun4c_invalidate_page(struct vm_area_struct *vma, unsigned long page)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ unsigned char savectx;
+
+ if(mm->context == NO_CONTEXT)
+ return;
+ flush_user_windows();
+ savectx = sun4c_get_context();
+ sun4c_set_context(mm->context);
+ page &= PAGE_MASK;
+ if(sun4c_get_pte(page) & _SUN4C_PAGE_VALID) {
+ sun4c_flush_page(page);
+ sun4c_put_pte(page, 0);
+ }
+ sun4c_set_context(savectx);
+}
+
+/* Sun4c mmu hardware doesn't update the dirty bit in the pte's
+ * for us, so we do it in software.
+ */
+static void sun4c_set_pte(pte_t *ptep, pte_t pte)
+{
+
+ if((pte_val(pte) & (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_DIRTY)) ==
+ _SUN4C_PAGE_WRITE)
+ pte_val(pte) |= _SUN4C_PAGE_DIRTY;
+
+ *ptep = pte;
+}
+
+/* static */ void sun4c_mapioaddr(unsigned long physaddr, unsigned long virt_addr,
+ int bus_type, int rdonly)
+{
+ unsigned long page_entry;
+
+ page_entry = ((physaddr >> PAGE_SHIFT) & 0xffff);
+ page_entry |= (_SUN4C_PAGE_VALID | _SUN4C_PAGE_WRITE |
+ _SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_IO);
+ if(rdonly)
+ page_entry &= (~_SUN4C_PAGE_WRITE);
+ sun4c_flush_page(virt_addr);
+ sun4c_put_pte(virt_addr, page_entry);
+}
+
+static inline void sun4c_alloc_context(struct mm_struct *mm)
+{
+ struct ctx_list *ctxp;
+
+ ctxp = ctx_free.next;
+ if(ctxp != &ctx_free) {
+ remove_from_ctx_list(ctxp);
+ add_to_used_ctxlist(ctxp);
+ mm->context = ctxp->ctx_number;
+ ctxp->ctx_mm = mm;
+ return;
+ }
+ ctxp = ctx_used.next;
+ if(ctxp->ctx_mm == current->mm)
+ ctxp = ctxp->next;
+ if(ctxp == &ctx_used)
+ panic("out of mmu contexts");
+ remove_from_ctx_list(ctxp);
+ add_to_used_ctxlist(ctxp);
+ ctxp->ctx_mm->context = NO_CONTEXT;
+ ctxp->ctx_mm = mm;
+ mm->context = ctxp->ctx_number;
+ sun4c_demap_context(&sun4c_context_ring[ctxp->ctx_number], ctxp->ctx_number);
+}
+
+#if some_day_soon /* We need some tweaking to start using this */
+extern void force_user_fault(unsigned long, int);
+
+void sun4c_switch_heuristic(struct pt_regs *regs)
+{
+ unsigned long sp = regs->u_regs[UREG_FP];
+ unsigned long sp2 = sp + REGWIN_SZ - 0x8;
+
+ force_user_fault(regs->pc, 0);
+ force_user_fault(sp, 0);
+ if((sp&PAGE_MASK) != (sp2&PAGE_MASK))
+ force_user_fault(sp2, 0);
}
+#endif
+
+static void sun4c_switch_to_context(struct task_struct *tsk)
+{
+ /* Kernel threads can execute in any context and so can tasks
+ * sleeping in the middle of exiting. If this task has already
+ * been allocated a piece of the mmu realestate, just jump to
+ * it.
+ */
+ if((tsk->tss.flags & SPARC_FLAG_KTHREAD) ||
+ (tsk->flags & PF_EXITING))
+ return;
+ if(tsk->mm->context == NO_CONTEXT)
+ sun4c_alloc_context(tsk->mm);
+
+ sun4c_set_context(tsk->mm->context);
+}
+
+static void sun4c_flush_hook(void)
+{
+ if(current->tss.flags & SPARC_FLAG_KTHREAD) {
+ sun4c_alloc_context(current->mm);
+ sun4c_set_context(current->mm->context);
+ }
+}
+
+static void sun4c_exit_hook(void)
+{
+ struct ctx_list *ctx_old;
+ struct mm_struct *mm = current->mm;
+
+ if(mm->context != NO_CONTEXT) {
+ sun4c_demap_context(&sun4c_context_ring[mm->context], mm->context);
+ ctx_old = ctx_list_pool + mm->context;
+ remove_from_ctx_list(ctx_old);
+ add_to_free_ctxlist(ctx_old);
+ mm->context = NO_CONTEXT;
+ }
+}
+
+void sun4c_test_wp(void)
+{
+ wp_works_ok = -1;
+
+ /* Let it rip... */
+ sun4c_put_pte((unsigned long) 0x0, (_SUN4C_PAGE_VALID | _SUN4C_PAGE_PRIV));
+ __asm__ __volatile__("st %%g0, [0x0]\n\t": : :"memory");
+ sun4c_put_pte((unsigned long) 0x0, 0x0);
+ if (wp_works_ok < 0)
+ wp_works_ok = 0;
+}
+
+static char s4cinfo[512];
+
+static char *sun4c_mmu_info(void)
+{
+ int used_user_entries, i;
+
+ used_user_entries = 0;
+ for(i=0; i < num_contexts; i++)
+ used_user_entries += sun4c_context_ring[i].num_entries;
+
+ sprintf(s4cinfo, "vacsize: %d bytes\n"
+ "vachwflush\t: %s\n"
+ "vaclinesize\t: %d bytes\n"
+ "mmuctxs\t\t: %d\n"
+ "mmupsegs\t: %d\n"
+ "usedpsegs\t: %d\n"
+ "ufreepsegs\t: %d\n"
+ "context\t\t: %d flushes\n"
+ "segment\t\t: %d flushes\n"
+ "page\t\t: %d flushes\n",
+ sun4c_vacinfo.num_bytes,
+ (sun4c_vacinfo.do_hwflushes ? "yes" : "no"),
+ sun4c_vacinfo.linesize,
+ num_contexts,
+ (invalid_segment + 1),
+ used_user_entries,
+ sun4c_ufree_ring.num_entries,
+ ctxflushes, segflushes, pageflushes);
+
+ return s4cinfo;
+}
+
+/* Nothing below here should touch the mmu hardware nor the mmu_entry
+ * data structures.
+ */
+
+static unsigned int sun4c_pmd_align(unsigned int addr) { return SUN4C_PMD_ALIGN(addr); }
+static unsigned int sun4c_pgdir_align(unsigned int addr) { return SUN4C_PGDIR_ALIGN(addr); }
+
+/* First the functions which the mid-level code uses to directly
+ * manipulate the software page tables. Some defines since we are
+ * emulating the i386 page directory layout.
+ */
+#define PGD_PRESENT 0x001
+#define PGD_RW 0x002
+#define PGD_USER 0x004
+#define PGD_ACCESSED 0x020
+#define PGD_DIRTY 0x040
+#define PGD_TABLE (PGD_PRESENT | PGD_RW | PGD_USER | PGD_ACCESSED | PGD_DIRTY)
-/* Here comes the sun4c mmu-tlb management engine. It is here because
- * some of the mid-level mm support needs to be able to lock down
- * critical areas of kernel memory into the tlb.
- */
-static inline void add_pseg_list(struct pseg_list *head, struct pseg_list *entry)
+static unsigned long sun4c_vmalloc_start(void)
{
- entry->next = head;
- (entry->prev = head->prev)->next = entry;
- head->prev = entry;
+ return SUN4C_VMALLOC_START;
}
-#define add_to_used_pseg_list(entry) add_pseg_list(&s4cpseg_used, entry)
-#define add_to_free_pseg_list(entry) add_pseg_list(&s4cpseg_free, entry)
-#define add_to_locked_pseg_list(entry) add_pseg_list(&s4cpseg_locked, entry)
-static inline void remove_pseg_list(struct pseg_list *entry)
+static int sun4c_pte_none(pte_t pte) { return !pte_val(pte); }
+static int sun4c_pte_present(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_VALID; }
+static int sun4c_pte_inuse(pte_t *ptep) { return mem_map[MAP_NR(ptep)].reserved || mem_map[MAP_NR(ptep)].count != 1; }
+static void sun4c_pte_clear(pte_t *ptep) { pte_val(*ptep) = 0; }
+static void sun4c_pte_reuse(pte_t *ptep)
{
- entry->next->prev = entry->prev;
- entry->prev->next = entry->next;
+ if(!mem_map[MAP_NR(ptep)].reserved)
+ mem_map[MAP_NR(ptep)].count++;
}
-static inline void add_pseg_ctxlist(struct pseg_list *entry, int ctx)
+static int sun4c_pmd_none(pmd_t pmd) { return !pmd_val(pmd); }
+static int sun4c_pmd_bad(pmd_t pmd)
{
- struct pseg_list *head = &s4cpseg_per_context[ctx];
-
- entry->ctx_next = head;
- (entry->ctx_prev = head->ctx_prev)->ctx_next = entry;
- head->ctx_prev = entry;
- pseg_count_per_context[ctx]++;
+ return (pmd_val(pmd) & ~PAGE_MASK) != PGD_TABLE || pmd_val(pmd) > high_memory;
}
-static inline void remove_pseg_ctxlist(struct pseg_list *entry, int ctx)
-{
- entry->ctx_next->ctx_prev = entry->ctx_prev;
- entry->ctx_prev->ctx_next = entry->ctx_next;
- pseg_count_per_context[ctx]--;
-}
+static int sun4c_pmd_present(pmd_t pmd) { return pmd_val(pmd) & PGD_PRESENT; }
+static int sun4c_pmd_inuse(pmd_t *pmdp) { return 0; }
+static void sun4c_pmd_clear(pmd_t *pmdp) { pmd_val(*pmdp) = 0; }
+static void sun4c_pmd_reuse(pmd_t * pmdp) { }
+
+static int sun4c_pgd_none(pgd_t pgd) { return 0; }
+static int sun4c_pgd_bad(pgd_t pgd) { return 0; }
+static int sun4c_pgd_present(pgd_t pgd) { return 1; }
+static int sun4c_pgd_inuse(pgd_t *pgdp) { return mem_map[MAP_NR(pgdp)].reserved; }
+static void sun4c_pgd_clear(pgd_t * pgdp) { }
-static inline void sun4c_init_pseg_lists(void)
-{
- int i;
+/*
+ * The following only work if pte_present() is true.
+ * Undefined behaviour if not..
+ */
+static int sun4c_pte_write(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_WRITE; }
+static int sun4c_pte_dirty(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_DIRTY; }
+static int sun4c_pte_young(pte_t pte) { return pte_val(pte) & _SUN4C_PAGE_REF; }
+
+static pte_t sun4c_pte_wrprotect(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_WRITE; return pte; }
+static pte_t sun4c_pte_mkclean(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_DIRTY; return pte; }
+static pte_t sun4c_pte_mkold(pte_t pte) { pte_val(pte) &= ~_SUN4C_PAGE_REF; return pte; }
+static pte_t sun4c_pte_mkwrite(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_WRITE; return pte; }
+static pte_t sun4c_pte_mkdirty(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_DIRTY; return pte; }
+static pte_t sun4c_pte_mkyoung(pte_t pte) { pte_val(pte) |= _SUN4C_PAGE_REF; return pte; }
- s4cpseg_free.prev = s4cpseg_free.next = &s4cpseg_free;
- s4cpseg_used.prev = s4cpseg_used.next = &s4cpseg_used;
- s4cpseg_locked.prev = s4cpseg_locked.next = &s4cpseg_locked;
- for(i = 0; i < num_contexts; i++) {
- s4cpseg_per_context[i].ctx_prev = s4cpseg_per_context[i].ctx_next =
- &s4cpseg_per_context[i];
- }
- for(i = 0; i <= invalid_segment; i++) {
- s4cpseg_pool[i].vaddr = 0;
- s4cpseg_pool[i].context = 0;
- s4cpseg_pool[i].ref_cnt = 0;
- s4cpseg_pool[i].hardlock = 0;
- s4cpseg_pool[i].pseg = i;
- }
- s4cpseg_pool[invalid_segment].hardlock = 1;
+/*
+ * Conversion functions: convert a page and protection to a page entry,
+ * and a page entry and page directory to the page they refer to.
+ */
+static pte_t sun4c_mk_pte(unsigned long page, pgprot_t pgprot)
+{
+ return __pte(((page - PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(pgprot));
}
-static inline void sun4c_distribute_kernel_mapping(unsigned long address,
- unsigned char pseg)
+static pte_t sun4c_mk_pte_io(unsigned long page, pgprot_t pgprot)
{
- unsigned int flags;
- int ctx, save_ctx;
-
- save_flags(flags); cli();
- save_ctx = get_context();
- flush_user_windows();
- for(ctx = 0; ctx < num_contexts; ctx++) {
- set_context(ctx);
- put_segmap(address, pseg);
- }
- set_context(save_ctx);
- restore_flags(flags);
+ return __pte(((page - PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(pgprot));
}
-static inline void sun4c_delete_kernel_mapping(unsigned long address)
+static pte_t sun4c_pte_modify(pte_t pte, pgprot_t newprot)
{
- unsigned int flags;
- int ctx, save_ctx;
-
- save_flags(flags); cli();
- save_ctx = get_context();
- flush_user_windows();
-
- /* Flush only needed in one context for kernel mappings. */
- sun4c_flush_segment(address);
- for(ctx = 0; ctx < num_contexts; ctx++) {
- set_context(ctx);
- put_segmap(address, invalid_segment);
- }
- set_context(save_ctx);
- restore_flags(flags);
+ return __pte((pte_val(pte) & _SUN4C_PAGE_CHG_MASK) | pgprot_val(newprot));
}
-/* NOTE: You can only lock kernel tlb entries, attempts to lock
- * pages in user vm will bolix the entire system.
- */
-static inline void sun4c_lock_tlb_entry(unsigned long address)
+static unsigned long sun4c_pte_page(pte_t pte)
{
- unsigned long flags;
- unsigned char pseg;
-
- save_flags(flags); cli();
- /* Fault it in. */
- __asm__ __volatile__("ldub [%0], %%g0\n\t" : : "r" (address));
- address &= SUN4C_REAL_PGDIR_MASK;
- pseg = get_segmap(address);
- if(address < KERNBASE)
- panic("locking user address space into tlb!");
- if(pseg == invalid_segment)
- panic("cannot lock kernel tlb entry...");
- if(!s4cpseg_pool[pseg].ref_cnt++ && !s4cpseg_pool[pseg].hardlock) {
- /* Move from used to locked list. */
- remove_pseg_list(&s4cpseg_pool[pseg]);
- add_to_locked_pseg_list(&s4cpseg_pool[pseg]);
- }
- restore_flags(flags);
+ return (PAGE_OFFSET + ((pte_val(pte) & 0xffff) << (PAGE_SHIFT)));
}
-static inline void sun4c_unlock_tlb_entry(unsigned long address)
+static unsigned long sun4c_pmd_page(pmd_t pmd)
{
- unsigned long flags;
- struct pseg_list *psegp;
- unsigned char pseg;
-
- save_flags(flags); cli();
- address &= SUN4C_REAL_PGDIR_MASK;
- pseg = get_segmap(address);
- if(address < KERNBASE)
- panic("unlocking user tlb entry!");
- if(pseg == invalid_segment)
- panic("unlocking non-locked kernel tlb entry...");
- psegp = &s4cpseg_pool[pseg];
- if(!--psegp->ref_cnt && !psegp->hardlock) {
- /* Move from locked list to used list. */
- remove_pseg_list(psegp);
- add_to_used_pseg_list(psegp);
- }
- restore_flags(flags);
+ return (pmd_val(pmd) & PAGE_MASK);
}
-/* Anyone who calls this must turn _all_ interrupts off and flush
- * any necessary user windows beforehand.
- */
-static inline void sun4c_unload_context_from_tlb(unsigned char ctx)
+/* to find an entry in a page-table-directory */
+static pgd_t *sun4c_pgd_offset(struct mm_struct * mm, unsigned long address)
{
- struct pseg_list *psegp, *pnextp;
-
- if(pseg_count_per_context[ctx]) {
- sun4c_flush_context(); /* Most efficient */
- psegp = s4cpseg_per_context[ctx].ctx_next;
- while(psegp != &s4cpseg_per_context[ctx]) {
- pnextp = psegp->ctx_next;
- if(psegp->vaddr >= KERNBASE)
- panic("Unloading kernel from tlb, not good.");
- put_segmap(psegp->vaddr, invalid_segment);
- remove_pseg_ctxlist(psegp, ctx);
- remove_pseg_list(psegp);
- add_to_free_pseg_list(psegp);
- psegp = pnextp;
- }
- if(pseg_count_per_context[ctx])
- panic("pseg_count_per_context inconsistant after "
- "invalidate.");
- }
+ return mm->pgd + (address >> SUN4C_PGDIR_SHIFT);
}
-/* This page must be a page in user vma... again all IRQ's gotta be off. */
-static inline void sun4c_unload_page_from_tlb(unsigned long addr,
- struct task_struct *tsk)
+/* Find an entry in the second-level page table.. */
+static pmd_t *sun4c_pmd_offset(pgd_t * dir, unsigned long address)
{
- unsigned char save_ctx;
-
- if(tsk->tss.context != -1) {
- save_ctx = get_context();
- flush_user_windows();
- set_context(tsk->tss.context);
- sun4c_flush_page(addr);
- put_pte(addr, 0);
- set_context(save_ctx);
- }
+ return (pmd_t *) dir;
}
-/* NOTE: When we have finer grained invalidate()'s (RSN) this
- * whole scheme will be much more efficient and need to
- * be re-written. Also note that this routine only
- * unloads user page translations, this may need to
- * be changed at some point.
- */
-void sun4c_invalidate(void)
+/* Find an entry in the third-level page table.. */
+static pte_t *sun4c_pte_offset(pmd_t * dir, unsigned long address)
{
- int orig_ctx, cur_ctx, flags;
-
- save_flags(flags); cli();
- flush_user_windows();
- orig_ctx = get_context();
- for(cur_ctx = 0; cur_ctx < num_contexts; cur_ctx++) {
- set_context(cur_ctx);
- sun4c_unload_context_from_tlb(cur_ctx);
- }
- set_context(orig_ctx);
- restore_flags(flags);
+ return (pte_t *) sun4c_pmd_page(*dir) + ((address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1));
}
-/* We're only updating software tables on the sun4c. */
-void sun4c_set_pte(pte_t *ptep, pte_t pteval)
+/* Update the root mmu directory. */
+static void sun4c_update_rootmmu_dir(struct task_struct *tsk, pgd_t *pgdir)
{
- *ptep = pteval;
}
-/* Now back to the mid-level interface code:
- *
- * Allocate and free page tables. The xxx_kernel() versions are
+/* Allocate and free page tables. The xxx_kernel() versions are
* used to allocate a kernel page table - this turns on ASN bits
* if any, and marks the page tables reserved.
*/
-void sun4c_pte_free_kernel(pte_t *pte)
+static void sun4c_pte_free_kernel(pte_t *pte)
{
mem_map[MAP_NR(pte)].reserved = 0;
free_page((unsigned long) pte);
}
-pte_t *sun4c_pte_alloc_kernel(pmd_t *pmd, unsigned long address)
+static pte_t *sun4c_pte_alloc_kernel(pmd_t *pmd, unsigned long address)
{
address = (address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1);
if (sun4c_pmd_none(*pmd)) {
@@ -406,22 +1244,22 @@
* allocating and freeing a pmd is trivial: the 1-entry pmd is
* inside the pgd, so has no extra memory associated with it.
*/
-void sun4c_pmd_free_kernel(pmd_t *pmd)
+static void sun4c_pmd_free_kernel(pmd_t *pmd)
{
pmd_val(*pmd) = 0;
}
-pmd_t *sun4c_pmd_alloc_kernel(pgd_t *pgd, unsigned long address)
+static pmd_t *sun4c_pmd_alloc_kernel(pgd_t *pgd, unsigned long address)
{
return (pmd_t *) pgd;
}
-void sun4c_pte_free(pte_t *pte)
+static void sun4c_pte_free(pte_t *pte)
{
free_page((unsigned long) pte);
}
-pte_t *sun4c_pte_alloc(pmd_t * pmd, unsigned long address)
+static pte_t *sun4c_pte_alloc(pmd_t * pmd, unsigned long address)
{
address = (address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1);
if (sun4c_pmd_none(*pmd)) {
@@ -448,606 +1286,70 @@
* allocating and freeing a pmd is trivial: the 1-entry pmd is
* inside the pgd, so has no extra memory associated with it.
*/
-void sun4c_pmd_free(pmd_t * pmd)
+static void sun4c_pmd_free(pmd_t * pmd)
{
pmd_val(*pmd) = 0;
}
-pmd_t *sun4c_pmd_alloc(pgd_t * pgd, unsigned long address)
+static pmd_t *sun4c_pmd_alloc(pgd_t * pgd, unsigned long address)
{
return (pmd_t *) pgd;
}
-void sun4c_pgd_free(pgd_t *pgd)
+static void sun4c_pgd_free(pgd_t *pgd)
{
free_page((unsigned long) pgd);
}
-pgd_t *sun4c_pgd_alloc(void)
-{
- unsigned long new_pgd = get_free_page(GFP_KERNEL);
- return (pgd_t *) new_pgd;
-}
-
-/* Jumping to and fro different contexts, the modifying of the pseg lists
- * must be atomic during the switch, or else...
- */
-void sun4c_switch_to_context(void *new_task)
-{
- struct task_struct *tsk = (struct task_struct *) new_task;
- struct task_struct *old_tsk;
- struct ctx_list *ctxp;
- unsigned long flags;
- int ctx = tsk->tss.context;
-
- /* Swapper can execute in any context, or this task
- * has already been allocated a piece of the mmu real-
- * estate.
- */
- if(tsk->pid == 0 || ctx != -1)
- return;
- ctxp = ctx_free.next;
- if(ctxp != &ctx_free) {
- save_flags(flags); cli();
- ctx = ctxp->ctx_number;
- remove_from_ctx_list(ctxp);
- add_to_used_ctxlist(ctxp);
- tsk->tss.context = ctx;
- ctxp->ctx_task = tsk;
- restore_flags(flags);
- return;
- }
- save_flags(flags); cli();
- ctxp = ctx_used.prev;
- /* Don't steal from current, thank you. */
- if(ctxp->ctx_task == current)
- ctxp = ctxp->prev;
- if(ctxp == &ctx_used)
- panic("out of contexts");
- remove_from_ctx_list(ctxp);
- old_tsk = ctxp->ctx_task;
- old_tsk->tss.context = -1;
- ctxp->ctx_task = tsk;
- tsk->tss.context = ctxp->ctx_number;
- add_to_used_ctxlist(ctxp);
- /* User windows flushed already by switch_to(p) macro. */
- set_context(ctxp->ctx_number);
- sun4c_unload_context_from_tlb(ctxp->ctx_number);
- restore_flags(flags);
-}
-
-/* Low level IO area allocation on the Sun4c MMU. This function is called
- * for each page of IO area you need. Kernel code should not call this
- * routine directly, use sparc_alloc_io() instead.
- */
-void sun4c_mapioaddr(unsigned long physaddr, unsigned long virt_addr,
- int bus_type, int rdonly)
-{
- unsigned long page_entry;
-
- page_entry = ((physaddr >> PAGE_SHIFT) & 0xffff);
- page_entry |= (_SUN4C_PAGE_VALID | _SUN4C_PAGE_WRITE |
- _SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_IO);
- if(rdonly)
- page_entry &= (~_SUN4C_PAGE_WRITE);
- sun4c_flush_page(virt_addr);
- put_pte(virt_addr, page_entry);
-}
-
-/* These routines are used to lock down and unlock data transfer
- * areas in the sun4c tlb. If the pages need to be uncached the
- * caller must do that himself.
- */
-inline char *sun4c_lockarea(char *vaddr, unsigned long size)
-{
- unsigned long flags;
- unsigned long orig_addr = (unsigned long) vaddr;
- unsigned long first_seg = (orig_addr & SUN4C_REAL_PGDIR_MASK);
- unsigned long last_seg = ((orig_addr + size) & SUN4C_REAL_PGDIR_MASK);
-
- save_flags(flags); cli();
- for(; first_seg <= last_seg; first_seg += SUN4C_REAL_PGDIR_SIZE)
- sun4c_lock_tlb_entry(first_seg);
-
- restore_flags(flags);
- return vaddr;
-}
-
-/* Note that when calling unlockarea you pass as 'vaddr' the address that
- * was returned to you by lockarea for this pool above.
- */
-inline void sun4c_unlockarea(char *vaddr, unsigned long size)
-{
- unsigned long flags;
- unsigned long orig_addr = (unsigned long) vaddr;
- unsigned long first_seg = (orig_addr & SUN4C_REAL_PGDIR_MASK);
- unsigned long last_seg = ((orig_addr + size) & SUN4C_REAL_PGDIR_MASK);
-
- save_flags(flags); cli();
- for(; first_seg <= last_seg; first_seg += SUN4C_REAL_PGDIR_SIZE)
- sun4c_unlock_tlb_entry(first_seg);
-
- restore_flags(flags);
-}
-
-/* Getting and Releasing scsi dvma buffers. */
-char *sun4c_get_scsi_buffer(char *bufptr, unsigned long len)
-{
- unsigned long first_page = ((unsigned long) bufptr) & PAGE_MASK;
- unsigned long last_page = (((unsigned long) bufptr) + len) & PAGE_MASK;
-
- /* First lock down the area. */
- bufptr = sun4c_lockarea(bufptr, len);
-
- /* Uncache and flush all the pages. */
- for(; first_page <= last_page; first_page += PAGE_SIZE) {
- sun4c_flush_page(first_page);
- put_pte(first_page, get_pte(first_page) | PTE_NC);
- }
- return bufptr;
-}
-
-void sun4c_release_scsi_buffer(char *bufptr, unsigned long len)
-{
- unsigned long first_page = ((unsigned long) bufptr) & PAGE_MASK;
- unsigned long last_page = (((unsigned long) bufptr) + len) & PAGE_MASK;
-
-
- /* Recache all the pages. */
- for(; first_page <= last_page; first_page += PAGE_SIZE)
- put_pte(first_page, get_pte(first_page) & ~PTE_NC);
-
- sun4c_unlockarea(bufptr, len);
-}
-
-/* Code to fill the sun4c tlb during a fault. Plus fault helper routine. */
-int sun4c_get_fault_info(unsigned long *address, unsigned long *error_code,
- unsigned long from_user)
-{
- unsigned long faddr, fstatus, new_code;
-
- faddr = sun4c_get_synchronous_address();
- *address = faddr;
- if(faddr >= 0x20000000 && faddr < 0xe0000000) {
- printk("SUN4C: Fault in vm hole at %08lx\n", faddr);
- *error_code = from_user;
- return 1;
- }
- fstatus = sun4c_get_synchronous_error();
- if(fstatus & SUN4C_SYNC_BOLIXED)
- panic("SUN4C: Unrecoverable fault type.");
- new_code = 0;
- if(fstatus & SUN4C_SYNC_PROT)
- new_code |= FAULT_CODE_PROT;
- if(fstatus & SUN4C_SYNC_BADWRITE)
- new_code |= FAULT_CODE_WRITE;
- *error_code = (new_code | from_user);
- return 0;
-}
-
-static inline void sun4c_alloc_pseg(unsigned long address)
-{
- struct pseg_list *psegp;
- unsigned char cur_ctx = get_context();
- int kernel_address = (address >= KERNBASE);
- int user_address = !kernel_address;
-
- psegp = s4cpseg_free.next;
- if(psegp != &s4cpseg_free) {
- remove_pseg_list(psegp);
- add_to_used_pseg_list(psegp);
- if(user_address)
- add_pseg_ctxlist(psegp, cur_ctx);
- psegp->vaddr = address;
- psegp->context = cur_ctx;
- /* No cache flush needed */
- if(kernel_address)
- sun4c_distribute_kernel_mapping(address, psegp->pseg);
- else
- put_segmap(address, psegp->pseg);
- return;
- }
- psegp = s4cpseg_used.prev; /* Take last used list entry. */
- if(psegp == &s4cpseg_used)
- panic("Sun4c psegs have disappeared...");
- if(psegp->vaddr >= KERNBASE) {
- sun4c_delete_kernel_mapping(psegp->vaddr);
- } else {
- flush_user_windows();
- set_context(psegp->context);
- sun4c_flush_segment(psegp->vaddr);
- put_segmap(psegp->vaddr, invalid_segment);
- set_context(cur_ctx);
- }
- remove_pseg_list(psegp);
- if(psegp->vaddr < KERNBASE)
- remove_pseg_ctxlist(psegp, psegp->context);
- psegp->vaddr = address;
- psegp->context = cur_ctx;
- if(kernel_address)
- sun4c_distribute_kernel_mapping(address, psegp->pseg);
- else
- put_segmap(address, psegp->pseg);
- add_to_used_pseg_list(psegp);
- if(user_address)
- add_pseg_ctxlist(psegp, cur_ctx);
-}
-
-/*
- * handle_mm_fault() gets here so that we can update our 'view'
- * of a new address translation. A lot of the time, mappings
- * don't change and we are just 'working the tlb cache'.
- */
-void sun4c_update_mmu_cache(struct vm_area_struct * vma,
- unsigned long address, pte_t pte)
-{
- unsigned long flags, segmap, segaddr, clean;
-
- save_flags(flags); cli();
- address &= PAGE_MASK;
- segaddr = address & SUN4C_REAL_PGDIR_MASK;
- segmap = get_segmap(segaddr);
- if(segmap == invalid_segment) {
- sun4c_alloc_pseg(segaddr);
- /* XXX make segmap freeing routines do this. XXX */
- for(clean = segaddr; clean < (segaddr + SUN4C_REAL_PGDIR_SIZE);
- clean += PAGE_SIZE)
- put_pte(clean, 0);
- }
-
- /* If this is a user fault, only load the one pte so that
- * the kernel's ref/mod bits accurately reflect what is
- * in the tlb. handle_pte_fault() causes this to work.
- */
- if(address < TASK_SIZE)
- put_pte(address, pte_val(pte));
- else {
- /* We have a kernel fault here, load entire segment. */
- pgd_t *pgdp;
- pte_t *ptable;
- int pnum = 64;
-
- pgdp = sun4c_pgd_offset(&init_mm, segaddr);
- ptable = sun4c_pte_offset((pmd_t *)pgdp, segaddr);
- while(pnum--) {
- put_pte(segaddr, pte_val(*ptable++));
- segaddr += PAGE_SIZE;
- };
- }
- restore_flags(flags);
-}
-
-/* Paging initialization on the Sun4c. */
-static inline void sun4c_free_all_nonlocked_psegs(void)
-{
- struct pseg_list *plp;
- int i;
-
- for(i=0; i < invalid_segment; i++)
- if(!s4cpseg_pool[i].hardlock)
- add_to_free_pseg_list(&s4cpseg_pool[i]);
- /* Now for every free pseg, make all the ptes invalid. */
- plp = s4cpseg_free.next;
- while(plp != &s4cpseg_free) {
- put_segmap(0x0, plp->pseg);
- for(i=0; i<64; i++)
- put_pte((i * PAGE_SIZE), 0x0);
- plp = plp->next;
- }
- put_segmap(0x0, invalid_segment);
-}
-
-static inline struct pseg_list *sun4c_alloc_pseg_from_free_list(void)
-{
- struct pseg_list *psegp;
-
- psegp = s4cpseg_free.next;
- if(psegp != &s4cpseg_free) {
- remove_pseg_list(psegp);
- return psegp;
- }
- return 0;
-}
-
-static inline void sun4c_init_lock_area(unsigned long start_addr,
- unsigned long end_addr)
-{
- struct pseg_list *psegp;
- unsigned long a;
- int ctx;
-
- for(a = start_addr; a < end_addr; a += SUN4C_REAL_PGDIR_SIZE) {
- psegp = sun4c_alloc_pseg_from_free_list();
- if(!psegp) {
- prom_printf("whoops...");
- prom_halt();
- }
- for(ctx=0;ctx<num_contexts;ctx++)
- prom_putsegment(ctx,a,psegp->pseg);
- add_to_locked_pseg_list(psegp);
- psegp->hardlock = 1;
- }
-}
-
-static inline void sun4c_check_for_ss2_cache_bug(void)
+static pgd_t *sun4c_pgd_alloc(void)
{
- extern unsigned long start;
-
- /* Well we've now got a problem, on the SS2 a cache bug
- * causes line entries to get severely corrupted if the
- * trap table is able to be cached. A sane and simple
- * workaround, at least for now, is to mark the trap
- * table page as uncacheable.
- *
- * XXX Investigate other possible workarounds and see
- * XXX if they help performance enough to warrant using
- * XXX them. -- 8/6/95 davem
- */
- if(idprom->id_machtype == (SM_SUN4C | SM_4C_SS2)) {
- /* Whee.. */
- printk("SS2 cache bug detected, uncaching trap table page\n");
- sun4c_flush_page((unsigned int) &start);
- put_pte(((unsigned long) &start),
- (get_pte((unsigned long) &start) | PTE_NC));
- }
+ return (pgd_t *) get_free_page(GFP_KERNEL);
}
+#define SUN4C_KERNEL_BUCKETS 16
extern unsigned long free_area_init(unsigned long, unsigned long);
-
-/* Whee, this is now *ultra* clean and more managable */
+extern unsigned long sparc_context_init(unsigned long, int);
extern unsigned long end;
-extern void probe_mmu(void);
unsigned long sun4c_paging_init(unsigned long start_mem, unsigned long end_mem)
{
- unsigned long addr, vaddr, kern_begin, kern_end;
- unsigned long prom_begin, prom_end, kadb_begin;
- pgd_t *pgdp;
- pte_t *pg_table;
- int phys_seg, i, ctx;
-
- start_mem = PAGE_ALIGN(start_mem);
+ int i, cnt;
+ unsigned long kernel_end;
- probe_mmu();
+ kernel_end = (unsigned long) &end;
+ kernel_end += (SUN4C_REAL_PGDIR_SIZE * 3);
+ kernel_end = SUN4C_REAL_PGDIR_ALIGN(kernel_end);
+ sun4c_probe_mmu();
invalid_segment = (num_segmaps - 1);
- sun4c_init_pseg_lists();
- for(kern_begin = KERNBASE;
- kern_begin < (unsigned long) &end;
- kern_begin += SUN4C_REAL_PGDIR_SIZE) {
- unsigned char pseg = get_segmap(kern_begin);
-
- s4cpseg_pool[pseg].hardlock=1;
- for(ctx=0; ctx<num_contexts;ctx++)
- prom_putsegment(ctx,kern_begin,pseg);
- }
- for(kern_begin = SUN4C_REAL_PGDIR_ALIGN((unsigned long) &end);
- kern_begin < KADB_DEBUGGER_BEGVM;
- kern_begin += SUN4C_REAL_PGDIR_SIZE)
- for(ctx=0; ctx<num_contexts;ctx++)
- prom_putsegment(ctx, kern_begin, invalid_segment);
- for(prom_begin = KADB_DEBUGGER_BEGVM;
- prom_begin < LINUX_OPPROM_ENDVM;
- prom_begin += SUN4C_REAL_PGDIR_SIZE) {
- unsigned long pseg = get_segmap(prom_begin);
-
- if(pseg != invalid_segment) {
- s4cpseg_pool[pseg].hardlock=1;
- for(ctx=0; ctx<num_contexts; ctx++)
- prom_putsegment(ctx,prom_begin,pseg);
- }
- }
- /* Clean the MMU of excess garbage... */
- for(ctx=0; ctx<num_contexts;ctx++) {
- set_context(ctx);
- for(vaddr = 0; vaddr < 0x20000000;
- vaddr += SUN4C_REAL_PGDIR_SIZE)
- put_segmap(vaddr,invalid_segment);
- for(vaddr = 0xe0000000; vaddr < KERNBASE;
- vaddr += SUN4C_REAL_PGDIR_SIZE)
- put_segmap(vaddr,invalid_segment);
- for(vaddr = LINUX_OPPROM_ENDVM; vaddr != 0;
- vaddr += SUN4C_REAL_PGDIR_SIZE)
- put_segmap(vaddr,invalid_segment);
- }
- set_context(0);
- sun4c_free_all_nonlocked_psegs();
- /* Lock I/O and DVMA areas for the system. */
+ sun4c_init_mmu_entry_pool();
+ sun4c_init_rings();
+ sun4c_init_map_kernelprom(kernel_end);
+ sun4c_init_clean_mmu(kernel_end);
+ sun4c_init_fill_kernel_ring(SUN4C_KERNEL_BUCKETS);
sun4c_init_lock_area(IOBASE_VADDR, IOBASE_END);
sun4c_init_lock_area(DVMA_VADDR, DVMA_END);
- /* Zero out swapper_pg_dir and pg0 */
+ start_mem = sun4c_init_lock_areas(start_mem);
+ sun4c_init_fill_user_ring();
+
+ sun4c_set_context(0);
memset(swapper_pg_dir, 0, PAGE_SIZE);
memset(pg0, 0, PAGE_SIZE);
- /* This makes us Solaris boot-loader 'safe' */
- pgd_val(swapper_pg_dir[KERNBASE>>SUN4C_PGDIR_SHIFT]) =
+ /* Save work later. */
+ pgd_val(swapper_pg_dir[SUN4C_VMALLOC_START>>SUN4C_PGDIR_SHIFT]) =
PGD_TABLE | (unsigned long) pg0;
-
- /* Initialize swapper_pg_dir to map the kernel
- * addresses in high memory. Note that as soon as we get past
- * the 4MB lower mapping and start using dynamic memory from
- * start_mem we can start faulting and this is ok since our
- * pseg free list and the lower 4MB of the kernel is mapped
- * properly in the software page tables.
- */
- pgdp = swapper_pg_dir;
- kern_end = PAGE_ALIGN(end_mem);
- kern_begin = KERNBASE;
- while(kern_begin < kern_end) {
- unsigned long pte, tmp;
-
- /* We only need _one_ mapping, the high address one. */
- pg_table = (pte_t *) (PAGE_MASK & pgd_val(pgdp[KERNBASE>>SUN4C_PGDIR_SHIFT]));
- if(!pg_table) {
- pg_table = (pte_t *) start_mem;
- start_mem += PAGE_SIZE;
- }
- pgd_val(pgdp[KERNBASE>>SUN4C_PGDIR_SHIFT]) =
- PGD_TABLE | (unsigned long) pg_table;
- pgdp++;
- for(tmp = 0; tmp < SUN4C_PTRS_PER_PTE; tmp++, pg_table++) {
- if(kern_begin < kern_end)
- sun4c_set_pte(pg_table,
- mk_pte(kern_begin,
- SUN4C_PAGE_SHARED));
- else
- sun4c_pte_clear(pg_table);
- pte = get_pte(kern_begin);
- if(pte & _SUN4C_PAGE_VALID) {
- pte &= ~(_SUN4C_PAGE_NOCACHE);
- pte |= (_SUN4C_PAGE_PRIV | _SUN4C_PAGE_WRITE |
- _SUN4C_PAGE_REF | _SUN4C_PAGE_DIRTY);
- put_pte(kern_begin, pte);
- }
- kern_begin += PAGE_SIZE;
- }
- }
- sun4c_check_for_ss2_cache_bug();
- /* Fix kadb/prom permissions. */
- kadb_begin = KADB_DEBUGGER_BEGVM;
- prom_end = LINUX_OPPROM_ENDVM;
- for(; kadb_begin < prom_end; kadb_begin += PAGE_SIZE) {
- unsigned long pte = get_pte(kadb_begin);
- if(pte & _SUN4C_PAGE_VALID)
- put_pte(kadb_begin, (pte | _SUN4C_PAGE_PRIV));
- }
- /* Allocate the DVMA pages */
- addr = DVMA_VADDR;
- start_mem = PAGE_ALIGN(start_mem);
- while(addr < DVMA_END) {
- unsigned long dvmapte = start_mem - PAGE_OFFSET;
-
- start_mem += PAGE_SIZE;
- dvmapte = ((dvmapte>>PAGE_SHIFT) & 0xffff);
- dvmapte |= (_SUN4C_PAGE_VALID |
- _SUN4C_PAGE_WRITE |
- _SUN4C_PAGE_NOCACHE);
- put_pte(addr, dvmapte);
- addr += PAGE_SIZE;
- }
- /* Tell the user our allocations */
- for(phys_seg=0, i=0; i<=invalid_segment; i++)
- if(s4cpseg_pool[i].hardlock)
- phys_seg++;
- printk("SUN4C: Hard locked %d boot-up psegs\n", phys_seg);
- /* Init the context pool and lists */
- ctx_list_pool = (struct ctx_list *) start_mem;
- start_mem += (num_contexts * sizeof(struct ctx_list));
- for(ctx = 0; ctx < num_contexts; ctx++) {
- struct ctx_list *clist;
-
- clist = (ctx_list_pool + ctx);
- clist->ctx_number = ctx;
- clist->ctx_task = 0;
- }
- ctx_free.next = ctx_free.prev = &ctx_free;
- ctx_used.next = ctx_used.prev = &ctx_used;
- for(ctx = 0; ctx < num_contexts; ctx++)
- add_to_free_ctxlist(ctx_list_pool + ctx);
+ sun4c_init_ss2_cache_bug();
start_mem = PAGE_ALIGN(start_mem);
+ start_mem = sun4c_init_alloc_dvma_pages(start_mem);
+ start_mem = sparc_context_init(start_mem, num_contexts);
start_mem = free_area_init(start_mem, end_mem);
- start_mem = PAGE_ALIGN(start_mem);
+ cnt = 0;
+ for(i = 0; i < num_segmaps; i++)
+ if(mmu_entry_pool[i].locked)
+ cnt++;
+ printk("SUN4C: %d mmu entries for the kernel\n", cnt);
return start_mem;
}
-/* Test the WP bit on the sun4c. */
-void sun4c_test_wp(void)
-{
- wp_works_ok = -1;
-
- /* Let it rip... */
- put_pte((unsigned long) 0x0, (PTE_V | PTE_P));
- __asm__ __volatile__("st %%g0, [0x0]\n\t": : :"memory");
- put_pte((unsigned long) 0x0, 0x0);
- if (wp_works_ok < 0)
- wp_works_ok = 0;
-}
-
-void sun4c_lock_entire_kernel(unsigned long start_mem)
-{
- unsigned long addr = (unsigned long) &end;
-
- addr = (addr & SUN4C_REAL_PGDIR_MASK);
- start_mem = SUN4C_REAL_PGDIR_ALIGN(start_mem);
- while(addr < start_mem) {
- int pseg;
-
- sun4c_lock_tlb_entry(addr);
- pseg = get_segmap(addr);
- if(!s4cpseg_pool[pseg].hardlock) {
- s4cpseg_pool[pseg].hardlock = 1;
- remove_pseg_list(&s4cpseg_pool[pseg]);
- }
- addr += SUN4C_REAL_PGDIR_SIZE;
- }
-}
-
-static void sun4c_fork_hook(void *vtask, unsigned long kthread_usp)
-{
- struct task_struct *new_task = vtask;
-
- /* These pages must not cause a fault when traps
- * are off (such as in a window spill/fill) so
- * lock them down for the life of the task.
- */
- sun4c_lock_tlb_entry((unsigned long) new_task);
- sun4c_lock_tlb_entry(new_task->kernel_stack_page);
- if(kthread_usp)
- sun4c_lock_tlb_entry(kthread_usp);
-}
-
-static void sun4c_release_hook(void *vtask)
-{
- struct task_struct *old_task = vtask;
- struct ctx_list *ctx_old;
- struct pt_regs *regs;
- unsigned char this_ctx = get_context();
- unsigned long flags;
-
- save_flags(flags); cli();
- if(old_task == &init_task)
- panic("AIEEE releasing swapper");
- if(old_task->tss.context != -1) {
-
- /* Clear from the mmu, all notions of this dead task. */
- flush_user_windows();
- set_context(old_task->tss.context);
- sun4c_unload_context_from_tlb(old_task->tss.context);
- set_context(this_ctx);
-
- ctx_old = ctx_list_pool + old_task->tss.context;
- remove_from_ctx_list(ctx_old);
- add_to_free_ctxlist(ctx_old);
- old_task->tss.context = -1;
- }
- regs = (struct pt_regs *)
- (((old_task->tss.ksp & ~0xfff)) + (0x1000 - TRACEREG_SZ));
- if(regs->u_regs[UREG_FP] > KERNBASE)
- sun4c_unlock_tlb_entry(regs->u_regs[UREG_FP] & PAGE_MASK);
- sun4c_unlock_tlb_entry(old_task->kernel_stack_page);
- sun4c_unlock_tlb_entry((unsigned long) old_task);
- restore_flags(flags);
- /* bye bye... */
-}
-
-static void sun4c_flush_hook(void *vtask)
-{
- struct task_struct *dead_task = vtask;
-
- if(dead_task->tss.context != -1)
- sun4c_flush_context();
-}
-
-static void sun4c_task_cacheflush(void *vtask)
-{
- struct task_struct *flush_task = vtask;
-
- if(flush_task->tss.context != -1)
- sun4c_flush_context();
-}
-
-static void sun4c_exit_hook(void *vtask)
-{
-}
-
/* Load up routines and constants for sun4c mmu */
void ld_mmu_sun4c(void)
{
@@ -1070,10 +1372,14 @@
page_copy = SUN4C_PAGE_COPY;
page_readonly = SUN4C_PAGE_READONLY;
page_kernel = SUN4C_PAGE_KERNEL;
- page_invalid = SUN4C_PAGE_INVALID;
+ pg_iobits = _SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_IO | _SUN4C_PAGE_VALID
+ | _SUN4C_PAGE_WRITE | _SUN4C_PAGE_DIRTY;
/* Functions */
- invalidate = sun4c_invalidate;
+ invalidate_all = sun4c_invalidate_all;
+ invalidate_mm = sun4c_invalidate_mm;
+ invalidate_range = sun4c_invalidate_range;
+ invalidate_page = sun4c_invalidate_page;
set_pte = sun4c_set_pte;
switch_to_context = sun4c_switch_to_context;
pmd_align = sun4c_pmd_align;
@@ -1105,6 +1411,7 @@
pgd_clear = sun4c_pgd_clear;
mk_pte = sun4c_mk_pte;
+ mk_pte_io = sun4c_mk_pte_io;
pte_modify = sun4c_pte_modify;
pgd_offset = sun4c_pgd_offset;
pmd_offset = sun4c_pmd_offset;
@@ -1120,35 +1427,31 @@
pgd_free = sun4c_pgd_free;
pgd_alloc = sun4c_pgd_alloc;
- pte_read = sun4c_pte_read;
pte_write = sun4c_pte_write;
- pte_exec = sun4c_pte_exec;
pte_dirty = sun4c_pte_dirty;
pte_young = sun4c_pte_young;
- pte_cow = sun4c_pte_cow;
pte_wrprotect = sun4c_pte_wrprotect;
- pte_rdprotect = sun4c_pte_rdprotect;
- pte_exprotect = sun4c_pte_exprotect;
pte_mkclean = sun4c_pte_mkclean;
pte_mkold = sun4c_pte_mkold;
- pte_uncow = sun4c_pte_uncow;
pte_mkwrite = sun4c_pte_mkwrite;
- pte_mkread = sun4c_pte_mkread;
- pte_mkexec = sun4c_pte_mkexec;
pte_mkdirty = sun4c_pte_mkdirty;
pte_mkyoung = sun4c_pte_mkyoung;
- pte_mkcow = sun4c_pte_mkcow;
- get_fault_info = sun4c_get_fault_info;
update_mmu_cache = sun4c_update_mmu_cache;
mmu_exit_hook = sun4c_exit_hook;
- mmu_fork_hook = sun4c_fork_hook;
- mmu_release_hook = sun4c_release_hook;
mmu_flush_hook = sun4c_flush_hook;
- mmu_task_cacheflush = sun4c_task_cacheflush;
- mmu_lockarea = sun4c_lockarea;
- mmu_unlockarea = sun4c_unlockarea;
+ mmu_lockarea = sun4c_lockpage;
+ mmu_unlockarea = sun4c_unlockpage;
mmu_get_scsi_buffer = sun4c_get_scsi_buffer;
mmu_release_scsi_buffer = sun4c_release_scsi_buffer;
+
+ /* Task struct and kernel stack allocating/freeing. */
+ alloc_kernel_stack = sun4c_alloc_kernel_stack;
+ alloc_task_struct = sun4c_alloc_task_struct;
+ free_kernel_stack = sun4c_free_kernel_stack;
+ free_task_struct = sun4c_free_task_struct;
+
+ quick_kernel_fault = sun4c_quick_kernel_fault;
+ mmu_info = sun4c_mmu_info;
/* These should _never_ get called with two level tables. */
pgd_set = 0;
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov
with Sam's (original) version of this