diff options
Diffstat (limited to 'arch/m68k/sun3')
-rw-r--r-- | arch/m68k/sun3/Makefile | 7 | ||||
-rw-r--r-- | arch/m68k/sun3/config.c | 188 | ||||
-rw-r--r-- | arch/m68k/sun3/dvma.c | 71 | ||||
-rw-r--r-- | arch/m68k/sun3/idprom.c | 129 | ||||
-rw-r--r-- | arch/m68k/sun3/intersil.c | 76 | ||||
-rw-r--r-- | arch/m68k/sun3/leds.c | 13 | ||||
-rw-r--r-- | arch/m68k/sun3/mmu_emu.c | 427 | ||||
-rw-r--r-- | arch/m68k/sun3/prom/Makefile | 7 | ||||
-rw-r--r-- | arch/m68k/sun3/prom/console.c | 174 | ||||
-rw-r--r-- | arch/m68k/sun3/prom/init.c | 89 | ||||
-rw-r--r-- | arch/m68k/sun3/prom/misc.c | 94 | ||||
-rw-r--r-- | arch/m68k/sun3/prom/printf.c | 61 | ||||
-rw-r--r-- | arch/m68k/sun3/sbus.c | 27 | ||||
-rw-r--r-- | arch/m68k/sun3/sun3_ksyms.c | 13 | ||||
-rw-r--r-- | arch/m68k/sun3/sun3dvma.c | 379 | ||||
-rw-r--r-- | arch/m68k/sun3/sun3ints.c | 265 |
16 files changed, 2020 insertions, 0 deletions
diff --git a/arch/m68k/sun3/Makefile b/arch/m68k/sun3/Makefile new file mode 100644 index 00000000000..4d4f0695d98 --- /dev/null +++ b/arch/m68k/sun3/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Linux arch/m68k/sun3 source directory +# + +obj-y := sun3_ksyms.o sun3ints.o sun3dvma.o sbus.o idprom.o + +obj-$(CONFIG_SUN3) += config.o mmu_emu.o leds.o dvma.o intersil.o diff --git a/arch/m68k/sun3/config.c b/arch/m68k/sun3/config.c new file mode 100644 index 00000000000..77d05bcc322 --- /dev/null +++ b/arch/m68k/sun3/config.c @@ -0,0 +1,188 @@ +/* + * linux/arch/m68k/sun3/config.c + * + * Copyright (C) 1996,1997 Pekka Pietik{inen + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/bootmem.h> + +#include <asm/oplib.h> +#include <asm/setup.h> +#include <asm/contregs.h> +#include <asm/movs.h> +#include <asm/pgtable.h> +#include <asm/sun3-head.h> +#include <asm/sun3mmu.h> +#include <asm/rtc.h> +#include <asm/machdep.h> +#include <asm/intersil.h> +#include <asm/irq.h> +#include <asm/segment.h> +#include <asm/sun3ints.h> + +extern char _text, _end; + +char sun3_reserved_pmeg[SUN3_PMEGS_NUM]; + +extern unsigned long sun3_gettimeoffset(void); +extern int show_sun3_interrupts (struct seq_file *, void *); +extern void sun3_sched_init(irqreturn_t (*handler)(int, void *, struct pt_regs *)); +extern void sun3_get_model (char* model); +extern void idprom_init (void); +extern int sun3_hwclk(int set, struct rtc_time *t); + +volatile char* clock_va; +extern volatile unsigned char* sun3_intreg; +extern unsigned long availmem; +unsigned long num_pages; + +static int sun3_get_hardware_list(char *buffer) +{ + + int len = 0; + + len += sprintf(buffer + len, "PROM Revision:\t%s\n", + romvec->pv_monid); + + return len; + +} + +void __init sun3_init(void) +{ + unsigned char enable_register; + int i; + + m68k_machtype= MACH_SUN3; + m68k_cputype = CPU_68020; + m68k_fputype = FPU_68881; /* mc68881 actually */ + m68k_mmutype = MMU_SUN3; + clock_va = (char *) 0xfe06000; /* dark */ + sun3_intreg = (unsigned char *) 0xfe0a000; /* magic */ + sun3_disable_interrupts(); + + prom_init((void *)LINUX_OPPROM_BEGVM); + + GET_CONTROL_BYTE(AC_SENABLE,enable_register); + enable_register |= 0x50; /* Enable FPU */ + SET_CONTROL_BYTE(AC_SENABLE,enable_register); + GET_CONTROL_BYTE(AC_SENABLE,enable_register); + + /* This code looks suspicious, because it doesn't subtract + memory belonging to the kernel from the available space */ + + + memset(sun3_reserved_pmeg, 0, sizeof(sun3_reserved_pmeg)); + + /* Reserve important PMEGS */ + /* FIXME: These should be probed instead of hardcoded */ + + for (i=0; i<8; i++) /* Kernel PMEGs */ + sun3_reserved_pmeg[i] = 1; + + sun3_reserved_pmeg[247] = 1; /* ROM mapping */ + sun3_reserved_pmeg[248] = 1; /* AMD Ethernet */ + sun3_reserved_pmeg[251] = 1; /* VB area */ + sun3_reserved_pmeg[254] = 1; /* main I/O */ + + sun3_reserved_pmeg[249] = 1; + sun3_reserved_pmeg[252] = 1; + sun3_reserved_pmeg[253] = 1; + set_fs(KERNEL_DS); +} + +/* Without this, Bad Things happen when something calls arch_reset. */ +static void sun3_reboot (void) +{ + prom_reboot ("vmlinux"); +} + +static void sun3_halt (void) +{ + prom_halt (); +} + +/* sun3 bootmem allocation */ + +void __init sun3_bootmem_alloc(unsigned long memory_start, unsigned long memory_end) +{ + unsigned long start_page; + + /* align start/end to page boundaries */ + memory_start = ((memory_start + (PAGE_SIZE-1)) & PAGE_MASK); + memory_end = memory_end & PAGE_MASK; + + start_page = __pa(memory_start) >> PAGE_SHIFT; + num_pages = __pa(memory_end) >> PAGE_SHIFT; + + high_memory = (void *)memory_end; + availmem = memory_start; + + availmem += init_bootmem_node(NODE_DATA(0), start_page, 0, num_pages); + availmem = (availmem + (PAGE_SIZE-1)) & PAGE_MASK; + + free_bootmem(__pa(availmem), memory_end - (availmem)); +} + + +void __init config_sun3(void) +{ + unsigned long memory_start, memory_end; + + printk("ARCH: SUN3\n"); + idprom_init(); + + /* Subtract kernel memory from available memory */ + + mach_sched_init = sun3_sched_init; + mach_init_IRQ = sun3_init_IRQ; + mach_default_handler = &sun3_default_handler; + mach_request_irq = sun3_request_irq; + mach_free_irq = sun3_free_irq; + enable_irq = sun3_enable_irq; + disable_irq = sun3_disable_irq; + mach_process_int = sun3_process_int; + mach_get_irq_list = show_sun3_interrupts; + mach_reset = sun3_reboot; + mach_gettimeoffset = sun3_gettimeoffset; + mach_get_model = sun3_get_model; + mach_hwclk = sun3_hwclk; + mach_halt = sun3_halt; + mach_get_hardware_list = sun3_get_hardware_list; +#if defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +#endif + + memory_start = ((((int)&_end) + 0x2000) & ~0x1fff); +// PROM seems to want the last couple of physical pages. --m + memory_end = *(romvec->pv_sun3mem) + PAGE_OFFSET - 2*PAGE_SIZE; + + m68k_num_memory=1; + m68k_memory[0].size=*(romvec->pv_sun3mem); + + sun3_bootmem_alloc(memory_start, memory_end); +} + +void __init sun3_sched_init(irqreturn_t (*timer_routine)(int, void *, struct pt_regs *)) +{ + sun3_disable_interrupts(); + intersil_clock->cmd_reg=(INTERSIL_RUN|INTERSIL_INT_DISABLE|INTERSIL_24H_MODE); + intersil_clock->int_reg=INTERSIL_HZ_100_MASK; + intersil_clear(); + sun3_enable_irq(5); + intersil_clock->cmd_reg=(INTERSIL_RUN|INTERSIL_INT_ENABLE|INTERSIL_24H_MODE); + sun3_enable_interrupts(); + intersil_clear(); +} + diff --git a/arch/m68k/sun3/dvma.c b/arch/m68k/sun3/dvma.c new file mode 100644 index 00000000000..d2b3093f240 --- /dev/null +++ b/arch/m68k/sun3/dvma.c @@ -0,0 +1,71 @@ +/* + * linux/arch/m68k/sun3/dvma.c + * + * Written by Sam Creasey + * + * Sun3 IOMMU routines used for dvma accesses. + * + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/bootmem.h> +#include <linux/list.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/sun3mmu.h> +#include <asm/dvma.h> + + +static unsigned long ptelist[120]; + +inline unsigned long dvma_page(unsigned long kaddr, unsigned long vaddr) +{ + unsigned long pte; + unsigned long j; + pte_t ptep; + + j = *(volatile unsigned long *)kaddr; + *(volatile unsigned long *)kaddr = j; + + ptep = pfn_pte(virt_to_pfn(kaddr), PAGE_KERNEL); + pte = pte_val(ptep); +// printk("dvma_remap: addr %lx -> %lx pte %08lx len %x\n", +// kaddr, vaddr, pte, len); + if(ptelist[(vaddr & 0xff000) >> PAGE_SHIFT] != pte) { + sun3_put_pte(vaddr, pte); + ptelist[(vaddr & 0xff000) >> PAGE_SHIFT] = pte; + } + + return (vaddr + (kaddr & ~PAGE_MASK)); + +} + +int dvma_map_iommu(unsigned long kaddr, unsigned long baddr, + int len) +{ + + unsigned long end; + unsigned long vaddr; + + vaddr = dvma_btov(baddr); + + end = vaddr + len; + + while(vaddr < end) { + dvma_page(kaddr, vaddr); + kaddr += PAGE_SIZE; + vaddr += PAGE_SIZE; + } + + return 0; + +} + +void sun3_dvma_init(void) +{ + + memset(ptelist, 0, sizeof(ptelist)); + + +} diff --git a/arch/m68k/sun3/idprom.c b/arch/m68k/sun3/idprom.c new file mode 100644 index 00000000000..02c1fee6fe7 --- /dev/null +++ b/arch/m68k/sun3/idprom.c @@ -0,0 +1,129 @@ +/* $Id: idprom.c,v 1.22 1996/11/13 05:09:25 davem Exp $ + * idprom.c: Routines to load the idprom into kernel addresses and + * interpret the data contained within. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Sun3/3x models added by David Monro (davidm@psrg.cs.usyd.edu.au) + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/string.h> + +#include <asm/oplib.h> +#include <asm/idprom.h> +#include <asm/machines.h> /* Fun with Sun released architectures. */ + +struct idprom *idprom; +static struct idprom idprom_buffer; + +/* Here is the master table of Sun machines which use some implementation + * of the Sparc CPU and have a meaningful IDPROM machtype value that we + * know about. See asm-sparc/machines.h for empirical constants. + */ +struct Sun_Machine_Models Sun_Machines[NUM_SUN_MACHINES] = { +/* First, Sun3's */ + { .name = "Sun 3/160 Series", .id_machtype = (SM_SUN3 | SM_3_160) }, + { .name = "Sun 3/50", .id_machtype = (SM_SUN3 | SM_3_50) }, + { .name = "Sun 3/260 Series", .id_machtype = (SM_SUN3 | SM_3_260) }, + { .name = "Sun 3/110 Series", .id_machtype = (SM_SUN3 | SM_3_110) }, + { .name = "Sun 3/60", .id_machtype = (SM_SUN3 | SM_3_60) }, + { .name = "Sun 3/E", .id_machtype = (SM_SUN3 | SM_3_E) }, +/* Now, Sun3x's */ + { .name = "Sun 3/460 Series", .id_machtype = (SM_SUN3X | SM_3_460) }, + { .name = "Sun 3/80", .id_machtype = (SM_SUN3X | SM_3_80) }, +/* Then, Sun4's */ +// { .name = "Sun 4/100 Series", .id_machtype = (SM_SUN4 | SM_4_110) }, +// { .name = "Sun 4/200 Series", .id_machtype = (SM_SUN4 | SM_4_260) }, +// { .name = "Sun 4/300 Series", .id_machtype = (SM_SUN4 | SM_4_330) }, +// { .name = "Sun 4/400 Series", .id_machtype = (SM_SUN4 | SM_4_470) }, +/* And now, Sun4c's */ +// { .name = "Sun4c SparcStation 1", .id_machtype = (SM_SUN4C | SM_4C_SS1) }, +// { .name = "Sun4c SparcStation IPC", .id_machtype = (SM_SUN4C | SM_4C_IPC) }, +// { .name = "Sun4c SparcStation 1+", .id_machtype = (SM_SUN4C | SM_4C_SS1PLUS) }, +// { .name = "Sun4c SparcStation SLC", .id_machtype = (SM_SUN4C | SM_4C_SLC) }, +// { .name = "Sun4c SparcStation 2", .id_machtype = (SM_SUN4C | SM_4C_SS2) }, +// { .name = "Sun4c SparcStation ELC", .id_machtype = (SM_SUN4C | SM_4C_ELC) }, +// { .name = "Sun4c SparcStation IPX", .id_machtype = (SM_SUN4C | SM_4C_IPX) }, +/* Finally, early Sun4m's */ +// { .name = "Sun4m SparcSystem600", .id_machtype = (SM_SUN4M | SM_4M_SS60) }, +// { .name = "Sun4m SparcStation10/20", .id_machtype = (SM_SUN4M | SM_4M_SS50) }, +// { .name = "Sun4m SparcStation5", .id_machtype = (SM_SUN4M | SM_4M_SS40) }, +/* One entry for the OBP arch's which are sun4d, sun4e, and newer sun4m's */ +// { .name = "Sun4M OBP based system", .id_machtype = (SM_SUN4M_OBP | 0x0) } +}; + +static void __init display_system_type(unsigned char machtype) +{ + register int i; + + for (i = 0; i < NUM_SUN_MACHINES; i++) { + if(Sun_Machines[i].id_machtype == machtype) { + if (machtype != (SM_SUN4M_OBP | 0x00)) + printk("TYPE: %s\n", Sun_Machines[i].name); + else { +#if 0 + prom_getproperty(prom_root_node, "banner-name", + sysname, sizeof(sysname)); + printk("TYPE: %s\n", sysname); +#endif + } + return; + } + } + + prom_printf("IDPROM: Bogus id_machtype value, 0x%x\n", machtype); + prom_halt(); +} + +void sun3_get_model(unsigned char* model) +{ + register int i; + + for (i = 0; i < NUM_SUN_MACHINES; i++) { + if(Sun_Machines[i].id_machtype == idprom->id_machtype) { + strcpy(model, Sun_Machines[i].name); + return; + } + } +} + + + +/* Calculate the IDPROM checksum (xor of the data bytes). */ +static unsigned char __init calc_idprom_cksum(struct idprom *idprom) +{ + unsigned char cksum, i, *ptr = (unsigned char *)idprom; + + for (i = cksum = 0; i <= 0x0E; i++) + cksum ^= *ptr++; + + return cksum; +} + +/* Create a local IDPROM copy, verify integrity, and display information. */ +void __init idprom_init(void) +{ + prom_get_idprom((char *) &idprom_buffer, sizeof(idprom_buffer)); + + idprom = &idprom_buffer; + + if (idprom->id_format != 0x01) { + prom_printf("IDPROM: Unknown format type!\n"); + prom_halt(); + } + + if (idprom->id_cksum != calc_idprom_cksum(idprom)) { + prom_printf("IDPROM: Checksum failure (nvram=%x, calc=%x)!\n", + idprom->id_cksum, calc_idprom_cksum(idprom)); + prom_halt(); + } + + display_system_type(idprom->id_machtype); + + printk("Ethernet address: %x:%x:%x:%x:%x:%x\n", + idprom->id_ethaddr[0], idprom->id_ethaddr[1], + idprom->id_ethaddr[2], idprom->id_ethaddr[3], + idprom->id_ethaddr[4], idprom->id_ethaddr[5]); +} diff --git a/arch/m68k/sun3/intersil.c b/arch/m68k/sun3/intersil.c new file mode 100644 index 00000000000..db359d7402a --- /dev/null +++ b/arch/m68k/sun3/intersil.c @@ -0,0 +1,76 @@ +/* + * arch/m68k/sun3/intersil.c + * + * basic routines for accessing the intersil clock within the sun3 machines + * + * started 11/12/1999 Sam Creasey + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include <linux/kernel.h> +#include <linux/rtc.h> + +#include <asm/errno.h> +#include <asm/system.h> +#include <asm/semaphore.h> +#include <asm/rtc.h> +#include <asm/intersil.h> + + +/* bits to set for start/run of the intersil */ +#define STOP_VAL (INTERSIL_STOP | INTERSIL_INT_ENABLE | INTERSIL_24H_MODE) +#define START_VAL (INTERSIL_RUN | INTERSIL_INT_ENABLE | INTERSIL_24H_MODE) + +/* does this need to be implemented? */ +unsigned long sun3_gettimeoffset(void) +{ + return 1; +} + + +/* get/set hwclock */ + +int sun3_hwclk(int set, struct rtc_time *t) +{ + volatile struct intersil_dt *todintersil; + unsigned long flags; + + todintersil = (struct intersil_dt *) &intersil_clock->counter; + + local_irq_save(flags); + + intersil_clock->cmd_reg = STOP_VAL; + + /* set or read the clock */ + if(set) { + todintersil->csec = 0; + todintersil->hour = t->tm_hour; + todintersil->minute = t->tm_min; + todintersil->second = t->tm_sec; + todintersil->month = t->tm_mon; + todintersil->day = t->tm_mday; + todintersil->year = t->tm_year - 68; + todintersil->weekday = t->tm_wday; + } else { + /* read clock */ + t->tm_sec = todintersil->csec; + t->tm_hour = todintersil->hour; + t->tm_min = todintersil->minute; + t->tm_sec = todintersil->second; + t->tm_mon = todintersil->month; + t->tm_mday = todintersil->day; + t->tm_year = todintersil->year + 68; + t->tm_wday = todintersil->weekday; + } + + intersil_clock->cmd_reg = START_VAL; + + local_irq_restore(flags); + + return 0; + +} + diff --git a/arch/m68k/sun3/leds.c b/arch/m68k/sun3/leds.c new file mode 100644 index 00000000000..a3e94846398 --- /dev/null +++ b/arch/m68k/sun3/leds.c @@ -0,0 +1,13 @@ +#include <asm/contregs.h> +#include <asm/sun3mmu.h> +#include <asm/io.h> + +void sun3_leds(unsigned char byte) +{ + unsigned char dfc; + + GET_DFC(dfc); + SET_DFC(FC_CONTROL); + SET_CONTROL_BYTE(AC_LEDS,byte); + SET_DFC(dfc); +} diff --git a/arch/m68k/sun3/mmu_emu.c b/arch/m68k/sun3/mmu_emu.c new file mode 100644 index 00000000000..7a0e3a22068 --- /dev/null +++ b/arch/m68k/sun3/mmu_emu.c @@ -0,0 +1,427 @@ +/* +** Tablewalk MMU emulator +** +** by Toshiyasu Morita +** +** Started 1/16/98 @ 2:22 am +*/ + +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/delay.h> +#include <linux/bootmem.h> +#include <linux/bitops.h> +#include <linux/module.h> + +#include <asm/setup.h> +#include <asm/traps.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/sun3mmu.h> +#include <asm/segment.h> +#include <asm/oplib.h> +#include <asm/mmu_context.h> +#include <asm/dvma.h> + +extern void prom_reboot (char *) __attribute__ ((__noreturn__)); + +#undef DEBUG_MMU_EMU +#define DEBUG_PROM_MAPS + +/* +** Defines +*/ + +#define CONTEXTS_NUM 8 +#define SEGMAPS_PER_CONTEXT_NUM 2048 +#define PAGES_PER_SEGMENT 16 +#define PMEGS_NUM 256 +#define PMEG_MASK 0xFF + +/* +** Globals +*/ + +unsigned long vmalloc_end; +EXPORT_SYMBOL(vmalloc_end); + +unsigned long pmeg_vaddr[PMEGS_NUM]; +unsigned char pmeg_alloc[PMEGS_NUM]; +unsigned char pmeg_ctx[PMEGS_NUM]; + +/* pointers to the mm structs for each task in each + context. 0xffffffff is a marker for kernel context */ +struct mm_struct *ctx_alloc[CONTEXTS_NUM] = { + [0] = (struct mm_struct *)0xffffffff +}; + +/* has this context been mmdrop'd? */ +static unsigned char ctx_avail = CONTEXTS_NUM-1; + +/* array of pages to be marked off for the rom when we do mem_init later */ +/* 256 pages lets the rom take up to 2mb of physical ram.. I really + hope it never wants mote than that. */ +unsigned long rom_pages[256]; + +/* Print a PTE value in symbolic form. For debugging. */ +void print_pte (pte_t pte) +{ +#if 0 + /* Verbose version. */ + unsigned long val = pte_val (pte); + printk (" pte=%lx [addr=%lx", + val, (val & SUN3_PAGE_PGNUM_MASK) << PAGE_SHIFT); + if (val & SUN3_PAGE_VALID) printk (" valid"); + if (val & SUN3_PAGE_WRITEABLE) printk (" write"); + if (val & SUN3_PAGE_SYSTEM) printk (" sys"); + if (val & SUN3_PAGE_NOCACHE) printk (" nocache"); + if (val & SUN3_PAGE_ACCESSED) printk (" accessed"); + if (val & SUN3_PAGE_MODIFIED) printk (" modified"); + switch (val & SUN3_PAGE_TYPE_MASK) { + case SUN3_PAGE_TYPE_MEMORY: printk (" memory"); break; + case SUN3_PAGE_TYPE_IO: printk (" io"); break; + case SUN3_PAGE_TYPE_VME16: printk (" vme16"); break; + case SUN3_PAGE_TYPE_VME32: printk (" vme32"); break; + } + printk ("]\n"); +#else + /* Terse version. More likely to fit on a line. */ + unsigned long val = pte_val (pte); + char flags[7], *type; + + flags[0] = (val & SUN3_PAGE_VALID) ? 'v' : '-'; + flags[1] = (val & SUN3_PAGE_WRITEABLE) ? 'w' : '-'; + flags[2] = (val & SUN3_PAGE_SYSTEM) ? 's' : '-'; + flags[3] = (val & SUN3_PAGE_NOCACHE) ? 'x' : '-'; + flags[4] = (val & SUN3_PAGE_ACCESSED) ? 'a' : '-'; + flags[5] = (val & SUN3_PAGE_MODIFIED) ? 'm' : '-'; + flags[6] = '\0'; + + switch (val & SUN3_PAGE_TYPE_MASK) { + case SUN3_PAGE_TYPE_MEMORY: type = "memory"; break; + case SUN3_PAGE_TYPE_IO: type = "io" ; break; + case SUN3_PAGE_TYPE_VME16: type = "vme16" ; break; + case SUN3_PAGE_TYPE_VME32: type = "vme32" ; break; + default: type = "unknown?"; break; + } + + printk (" pte=%08lx [%07lx %s %s]\n", + val, (val & SUN3_PAGE_PGNUM_MASK) << PAGE_SHIFT, flags, type); +#endif +} + +/* Print the PTE value for a given virtual address. For debugging. */ +void print_pte_vaddr (unsigned long vaddr) +{ + printk (" vaddr=%lx [%02lx]", vaddr, sun3_get_segmap (vaddr)); + print_pte (__pte (sun3_get_pte (vaddr))); +} + +/* + * Initialise the MMU emulator. + */ +void mmu_emu_init(unsigned long bootmem_end) +{ + unsigned long seg, num; + int i,j; + + memset(rom_pages, 0, sizeof(rom_pages)); + memset(pmeg_vaddr, 0, sizeof(pmeg_vaddr)); + memset(pmeg_alloc, 0, sizeof(pmeg_alloc)); + memset(pmeg_ctx, 0, sizeof(pmeg_ctx)); + + /* pmeg align the end of bootmem, adding another pmeg, + * later bootmem allocations will likely need it */ + bootmem_end = (bootmem_end + (2 * SUN3_PMEG_SIZE)) & ~SUN3_PMEG_MASK; + + /* mark all of the pmegs used thus far as reserved */ + for (i=0; i < __pa(bootmem_end) / SUN3_PMEG_SIZE ; ++i) + pmeg_alloc[i] = 2; + + + /* I'm thinking that most of the top pmeg's are going to be + used for something, and we probably shouldn't risk it */ + for(num = 0xf0; num <= 0xff; num++) + pmeg_alloc[num] = 2; + + /* liberate all existing mappings in the rest of kernel space */ + for(seg = bootmem_end; seg < 0x0f800000; seg += SUN3_PMEG_SIZE) { + i = sun3_get_segmap(seg); + + if(!pmeg_alloc[i]) { +#ifdef DEBUG_MMU_EMU + printk("freed: "); + print_pte_vaddr (seg); +#endif + sun3_put_segmap(seg, SUN3_INVALID_PMEG); + } + } + + j = 0; + for (num=0, seg=0x0F800000; seg<0x10000000; seg+=16*PAGE_SIZE) { + if (sun3_get_segmap (seg) != SUN3_INVALID_PMEG) { +#ifdef DEBUG_PROM_MAPS + for(i = 0; i < 16; i++) { + printk ("mapped:"); + print_pte_vaddr (seg + (i*PAGE_SIZE)); + break; + } +#endif + // the lowest mapping here is the end of our + // vmalloc region + if(!vmalloc_end) + vmalloc_end = seg; + + // mark the segmap alloc'd, and reserve any + // of the first 0xbff pages the hardware is + // already using... does any sun3 support > 24mb? + pmeg_alloc[sun3_get_segmap(seg)] = 2; + } + } + + dvma_init(); + + + /* blank everything below the kernel, and we've got the base + mapping to start all the contexts off with... */ + for(seg = 0; seg < PAGE_OFFSET; seg += SUN3_PMEG_SIZE) + sun3_put_segmap(seg, SUN3_INVALID_PMEG); + + set_fs(MAKE_MM_SEG(3)); + for(seg = 0; seg < 0x10000000; seg += SUN3_PMEG_SIZE) { + i = sun3_get_segmap(seg); + for(j = 1; j < CONTEXTS_NUM; j++) + (*(romvec->pv_setctxt))(j, (void *)seg, i); + } + set_fs(KERNEL_DS); + +} + +/* erase the mappings for a dead context. Uses the pg_dir for hints + as the pmeg tables proved somewhat unreliable, and unmapping all of + TASK_SIZE was much slower and no more stable. */ +/* todo: find a better way to keep track of the pmegs used by a + context for when they're cleared */ +void clear_context(unsigned long context) +{ + unsigned char oldctx; + unsigned long i; + + if(context) { + if(!ctx_alloc[context]) + panic("clear_context: context not allocated\n"); + + ctx_alloc[context]->context = SUN3_INVALID_CONTEXT; + ctx_alloc[context] = (struct mm_struct *)0; + ctx_avail++; + } + + oldctx = sun3_get_context(); + + sun3_put_context(context); + + for(i = 0; i < SUN3_INVALID_PMEG; i++) { + if((pmeg_ctx[i] == context) && (pmeg_alloc[i] == 1)) { + sun3_put_segmap(pmeg_vaddr[i], SUN3_INVALID_PMEG); + pmeg_ctx[i] = 0; + pmeg_alloc[i] = 0; + pmeg_vaddr[i] = 0; + } + } + + sun3_put_context(oldctx); +} + +/* gets an empty context. if full, kills the next context listed to + die first */ +/* This context invalidation scheme is, well, totally arbitrary, I'm + sure it could be much more intellegent... but it gets the job done + for now without much overhead in making it's decision. */ +/* todo: come up with optimized scheme for flushing contexts */ +unsigned long get_free_context(struct mm_struct *mm) +{ + unsigned long new = 1; + static unsigned char next_to_die = 1; + + if(!ctx_avail) { + /* kill someone to get our context */ + new = next_to_die; + clear_context(new); + next_to_die = (next_to_die + 1) & 0x7; + if(!next_to_die) + next_to_die++; + } else { + while(new < CONTEXTS_NUM) { + if(ctx_alloc[new]) + new++; + else + break; + } + // check to make sure one was really free... + if(new == CONTEXTS_NUM) + panic("get_free_context: failed to find free context"); + } + + ctx_alloc[new] = mm; + ctx_avail--; + + return new; +} + +/* + * Dynamically select a `spare' PMEG and use it to map virtual `vaddr' in + * `context'. Maintain internal PMEG management structures. This doesn't + * actually map the physical address, but does clear the old mappings. + */ +//todo: better allocation scheme? but is extra complexity worthwhile? +//todo: only clear old entries if necessary? how to tell? + +inline void mmu_emu_map_pmeg (int context, int vaddr) +{ + static unsigned char curr_pmeg = 128; + int i; + + /* Round address to PMEG boundary. */ + vaddr &= ~SUN3_PMEG_MASK; + + /* Find a spare one. */ + while (pmeg_alloc[curr_pmeg] == 2) + ++curr_pmeg; + + +#ifdef DEBUG_MMU_EMU +printk("mmu_emu_map_pmeg: pmeg %x to context %d vaddr %x\n", + curr_pmeg, context, vaddr); +#endif + + /* Invalidate old mapping for the pmeg, if any */ + if (pmeg_alloc[curr_pmeg] == 1) { + sun3_put_context(pmeg_ctx[curr_pmeg]); + sun3_put_segmap (pmeg_vaddr[curr_pmeg], SUN3_INVALID_PMEG); + sun3_put_context(context); + } + + /* Update PMEG management structures. */ + // don't take pmeg's away from the kernel... + if(vaddr >= PAGE_OFFSET) { + /* map kernel pmegs into all contexts */ + unsigned char i; + + for(i = 0; i < CONTEXTS_NUM; i++) { + sun3_put_context(i); + sun3_put_segmap (vaddr, curr_pmeg); + } + sun3_put_context(context); + pmeg_alloc[curr_pmeg] = 2; + pmeg_ctx[curr_pmeg] = 0; + + } + else { + pmeg_alloc[curr_pmeg] = 1; + pmeg_ctx[curr_pmeg] = context; + sun3_put_segmap (vaddr, curr_pmeg); + + } + pmeg_vaddr[curr_pmeg] = vaddr; + + /* Set hardware mapping and clear the old PTE entries. */ + for (i=0; i<SUN3_PMEG_SIZE; i+=SUN3_PTE_SIZE) + sun3_put_pte (vaddr + i, SUN3_PAGE_SYSTEM); + + /* Consider a different one next time. */ + ++curr_pmeg; +} + +/* + * Handle a pagefault at virtual address `vaddr'; check if there should be a + * page there (specifically, whether the software pagetables indicate that + * there is). This is necessary due to the limited size of the second-level + * Sun3 hardware pagetables (256 groups of 16 pages). If there should be a + * mapping present, we select a `spare' PMEG and use it to create a mapping. + * `read_flag' is nonzero for a read fault; zero for a write. Returns nonzero + * if we successfully handled the fault. + */ +//todo: should we bump minor pagefault counter? if so, here or in caller? +//todo: possibly inline this into bus_error030 in <asm/buserror.h> ? + +// kernel_fault is set when a kernel page couldn't be demand mapped, +// and forces another try using the kernel page table. basically a +// hack so that vmalloc would work correctly. + +int mmu_emu_handle_fault (unsigned long vaddr, int read_flag, int kernel_fault) +{ + unsigned long segment, offset; + unsigned char context; + pte_t *pte; + pgd_t * crp; + + if(current->mm == NULL) { + crp = swapper_pg_dir; + context = 0; + } else { + context = current->mm->context; + if(kernel_fault) + crp = swapper_pg_dir; + else + crp = current->mm->pgd; + } + +#ifdef DEBUG_MMU_EMU + printk ("mmu_emu_handle_fault: vaddr=%lx type=%s crp=%p\n", + vaddr, read_flag ? "read" : "write", crp); +#endif + + segment = (vaddr >> SUN3_PMEG_SIZE_BITS) & 0x7FF; + offset = (vaddr >> SUN3_PTE_SIZE_BITS) & 0xF; + +#ifdef DEBUG_MMU_EMU + printk ("mmu_emu_handle_fault: segment=%lx offset=%lx\n", segment, offset); +#endif + + pte = (pte_t *) pgd_val (*(crp + segment)); + +//todo: next line should check for valid pmd properly. + if (!pte) { +// printk ("mmu_emu_handle_fault: invalid pmd\n"); + return 0; + } + + pte = (pte_t *) __va ((unsigned long)(pte + offset)); + + /* Make sure this is a valid page */ + if (!(pte_val (*pte) & SUN3_PAGE_VALID)) + return 0; + + /* Make sure there's a pmeg allocated for the page */ + if (sun3_get_segmap (vaddr&~SUN3_PMEG_MASK) == SUN3_INVALID_PMEG) + mmu_emu_map_pmeg (context, vaddr); + + /* Write the pte value to hardware MMU */ + sun3_put_pte (vaddr&PAGE_MASK, pte_val (*pte)); + + /* Update software copy of the pte value */ +// I'm not sure this is necessary. If this is required, we ought to simply +// copy this out when we reuse the PMEG or at some other convenient time. +// Doing it here is fairly meaningless, anyway, as we only know about the +// first access to a given page. --m + if (!read_flag) { + if (pte_val (*pte) & SUN3_PAGE_WRITEABLE) + pte_val (*pte) |= (SUN3_PAGE_ACCESSED + | SUN3_PAGE_MODIFIED); + else + return 0; /* Write-protect error. */ + } else + pte_val (*pte) |= SUN3_PAGE_ACCESSED; + +#ifdef DEBUG_MMU_EMU + printk ("seg:%d crp:%p ->", get_fs().seg, crp); + print_pte_vaddr (vaddr); + printk ("\n"); +#endif + + return 1; +} diff --git a/arch/m68k/sun3/prom/Makefile b/arch/m68k/sun3/prom/Makefile new file mode 100644 index 00000000000..6e48ae2a717 --- /dev/null +++ b/arch/m68k/sun3/prom/Makefile @@ -0,0 +1,7 @@ +# $Id: Makefile,v 1.5 1995/11/25 00:59:48 davem Exp $ +# Makefile for the Sun Boot PROM interface library under +# Linux. +# + +obj-y := init.o console.o printf.o misc.o +#bootstr.o init.o misc.o segment.o console.o printf.o diff --git a/arch/m68k/sun3/prom/console.c b/arch/m68k/sun3/prom/console.c new file mode 100644 index 00000000000..52c1427863d --- /dev/null +++ b/arch/m68k/sun3/prom/console.c @@ -0,0 +1,174 @@ +/* $Id: console.c,v 1.10 1996/12/18 06:46:54 tridge Exp $ + * console.c: Routines that deal with sending and receiving IO + * to/from the current console device using the PROM. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/system.h> +#include <linux/string.h> + +/* Non blocking get character from console input device, returns -1 + * if no input was taken. This can be used for polling. + */ +int +prom_nbgetchar(void) +{ + int i = -1; + unsigned long flags; + + local_irq_save(flags); + i = (*(romvec->pv_nbgetchar))(); + local_irq_restore(flags); + return i; /* Ugh, we could spin forever on unsupported proms ;( */ +} + +/* Non blocking put character to console device, returns -1 if + * unsuccessful. + */ +int +prom_nbputchar(char c) +{ + unsigned long flags; + int i = -1; + + local_irq_save(flags); + i = (*(romvec->pv_nbputchar))(c); + local_irq_restore(flags); + return i; /* Ugh, we could spin forever on unsupported proms ;( */ +} + +/* Blocking version of get character routine above. */ +char +prom_getchar(void) +{ + int character; + while((character = prom_nbgetchar()) == -1) ; + return (char) character; +} + +/* Blocking version of put character routine above. */ +void +prom_putchar(char c) +{ + while(prom_nbputchar(c) == -1) ; + return; +} + +/* Query for input device type */ +#if 0 +enum prom_input_device +prom_query_input_device() +{ + unsigned long flags; + int st_p; + char propb[64]; + char *p; + + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + default: + switch(*romvec->pv_stdin) { + case PROMDEV_KBD: return PROMDEV_IKBD; + case PROMDEV_TTYA: return PROMDEV_ITTYA; + case PROMDEV_TTYB: return PROMDEV_ITTYB; + default: + return PROMDEV_I_UNK; + }; + case PROM_V3: + case PROM_P1275: + local_irq_save(flags); + st_p = (*romvec->pv_v2devops.v2_inst2pkg)(*romvec->pv_v2bootargs.fd_stdin); + __asm__ __volatile__("ld [%0], %%g6\n\t" : : + "r" (¤t_set[smp_processor_id()]) : + "memory"); + local_irq_restore(flags); + if(prom_node_has_property(st_p, "keyboard")) + return PROMDEV_IKBD; + prom_getproperty(st_p, "device_type", propb, sizeof(propb)); + if(strncmp(propb, "serial", sizeof("serial"))) + return PROMDEV_I_UNK; + prom_getproperty(prom_root_node, "stdin-path", propb, sizeof(propb)); + p = propb; + while(*p) p++; p -= 2; + if(p[0] == ':') { + if(p[1] == 'a') + return PROMDEV_ITTYA; + else if(p[1] == 'b') + return PROMDEV_ITTYB; + } + return PROMDEV_I_UNK; + case PROM_AP1000: + return PROMDEV_I_UNK; + }; +} +#endif + +/* Query for output device type */ + +#if 0 +enum prom_output_device +prom_query_output_device() +{ + unsigned long flags; + int st_p; + char propb[64]; + char *p; + int propl; + + switch(prom_vers) { + case PROM_V0: + switch(*romvec->pv_stdin) { + case PROMDEV_SCREEN: return PROMDEV_OSCREEN; + case PROMDEV_TTYA: return PROMDEV_OTTYA; + case PROMDEV_TTYB: return PROMDEV_OTTYB; + }; + break; + case PROM_V2: + case PROM_V3: + case PROM_P1275: + local_irq_save(flags); + st_p = (*romvec->pv_v2devops.v2_inst2pkg)(*romvec->pv_v2bootargs.fd_stdout); + __asm__ __volatile__("ld [%0], %%g6\n\t" : : + "r" (¤t_set[smp_processor_id()]) : + "memory"); + local_irq_restore(flags); + propl = prom_getproperty(st_p, "device_type", propb, sizeof(propb)); + if (propl >= 0 && propl == sizeof("display") && + strncmp("display", propb, sizeof("display")) == 0) + { + return PROMDEV_OSCREEN; + } + if(prom_vers == PROM_V3) { + if(strncmp("serial", propb, sizeof("serial"))) + return PROMDEV_O_UNK; + prom_getproperty(prom_root_node, "stdout-path", propb, sizeof(propb)); + p = propb; + while(*p) p++; p -= 2; + if(p[0]==':') { + if(p[1] == 'a') + return PROMDEV_OTTYA; + else if(p[1] == 'b') + return PROMDEV_OTTYB; + } + return PROMDEV_O_UNK; + } else { + /* This works on SS-2 (an early OpenFirmware) still. */ + switch(*romvec->pv_stdin) { + case PROMDEV_TTYA: return PROMDEV_OTTYA; + case PROMDEV_TTYB: return PROMDEV_OTTYB; + }; + } + break; + case PROM_AP1000: + return PROMDEV_I_UNK; + }; + return PROMDEV_O_UNK; +} +#endif diff --git a/arch/m68k/sun3/prom/init.c b/arch/m68k/sun3/prom/init.c new file mode 100644 index 00000000000..2e6ae56aec1 --- /dev/null +++ b/arch/m68k/sun3/prom/init.c @@ -0,0 +1,89 @@ +/* $Id: init.c,v 1.9 1996/12/18 06:46:55 tridge Exp $ + * init.c: Initialize internal variables used by the PROM + * library functions. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +struct linux_romvec *romvec; +enum prom_major_version prom_vers; +unsigned int prom_rev, prom_prev; + +/* The root node of the prom device tree. */ +int prom_root_node; + +/* Pointer to the device tree operations structure. */ +struct linux_nodeops *prom_nodeops; + +/* You must call prom_init() before you attempt to use any of the + * routines in the prom library. It returns 0 on success, 1 on + * failure. It gets passed the pointer to the PROM vector. + */ + +extern void prom_meminit(void); +extern void prom_ranges_init(void); + +void __init prom_init(struct linux_romvec *rp) +{ +#ifdef CONFIG_AP1000 + extern struct linux_romvec *ap_prom_init(void); + rp = ap_prom_init(); +#endif + + romvec = rp; +#ifndef CONFIG_SUN3 + switch(romvec->pv_romvers) { + case 0: + prom_vers = PROM_V0; + break; + case 2: + prom_vers = PROM_V2; + break; + case 3: + prom_vers = PROM_V3; + break; + case 4: + prom_vers = PROM_P1275; + prom_printf("PROMLIB: Sun IEEE Prom not supported yet\n"); + prom_halt(); + break; + case 42: /* why not :-) */ + prom_vers = PROM_AP1000; + break; + + default: + prom_printf("PROMLIB: Bad PROM version %d\n", + romvec->pv_romvers); + prom_halt(); + break; + }; + + prom_rev = romvec->pv_plugin_revision; + prom_prev = romvec->pv_printrev; + prom_nodeops = romvec->pv_nodeops; + + prom_root_node = prom_getsibling(0); + if((prom_root_node == 0) || (prom_root_node == -1)) + prom_halt(); + + if((((unsigned long) prom_nodeops) == 0) || + (((unsigned long) prom_nodeops) == -1)) + prom_halt(); + + prom_meminit(); + + prom_ranges_init(); +#endif +// printk("PROMLIB: Sun Boot Prom Version %d Revision %d\n", +// romvec->pv_romvers, prom_rev); + + /* Initialization successful. */ + return; +} diff --git a/arch/m68k/sun3/prom/misc.c b/arch/m68k/sun3/prom/misc.c new file mode 100644 index 00000000000..b88716f2c68 --- /dev/null +++ b/arch/m68k/sun3/prom/misc.c @@ -0,0 +1,94 @@ +/* $Id: misc.c,v 1.15 1997/05/14 20:45:00 davem Exp $ + * misc.c: Miscellaneous prom functions that don't belong + * anywhere else. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <asm/sun3-head.h> +#include <asm/idprom.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/movs.h> + +/* Reset and reboot the machine with the command 'bcommand'. */ +void +prom_reboot(char *bcommand) +{ + unsigned long flags; + local_irq_save(flags); + (*(romvec->pv_reboot))(bcommand); + local_irq_restore(flags); +} + +/* Drop into the prom, with the chance to continue with the 'go' + * prom command. + */ +void +prom_cmdline(void) +{ +} + +/* Drop into the prom, but completely terminate the program. + * No chance of continuing. + */ +void +prom_halt(void) +{ + unsigned long flags; +again: + local_irq_save(flags); + (*(romvec->pv_halt))(); + local_irq_restore(flags); + goto again; /* PROM is out to get me -DaveM */ +} + +typedef void (*sfunc_t)(void); + +/* Get the idprom and stuff it into buffer 'idbuf'. Returns the + * format type. 'num_bytes' is the number of bytes that your idbuf + * has space for. Returns 0xff on error. + */ +unsigned char +prom_get_idprom(char *idbuf, int num_bytes) +{ + int i, oldsfc; + GET_SFC(oldsfc); + SET_SFC(FC_CONTROL); + for(i=0;i<num_bytes; i++) + { + /* There is a problem with the GET_CONTROL_BYTE + macro; defining the extra variable + gets around it. + */ + int c; + GET_CONTROL_BYTE(SUN3_IDPROM_BASE + i, c); + idbuf[i] = c; + } + SET_SFC(oldsfc); + return idbuf[0]; +} + +/* Get the major prom version number. */ +int +prom_version(void) +{ + return romvec->pv_romvers; +} + +/* Get the prom plugin-revision. */ +int +prom_getrev(void) +{ + return prom_rev; +} + +/* Get the prom firmware print revision. */ +int +prom_getprev(void) +{ + return prom_prev; +} diff --git a/arch/m68k/sun3/prom/printf.c b/arch/m68k/sun3/prom/printf.c new file mode 100644 index 00000000000..e6ee1006344 --- /dev/null +++ b/arch/m68k/sun3/prom/printf.c @@ -0,0 +1,61 @@ +/* $Id: printf.c,v 1.5 1996/04/04 16:31:07 tridge Exp $ + * printf.c: Internal prom library printf facility. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +/* This routine is internal to the prom library, no one else should know + * about or use it! It's simple and smelly anyway.... + */ + +#include <linux/config.h> +#include <linux/kernel.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +#ifdef CONFIG_KGDB +extern int kgdb_initialized; +#endif + +static char ppbuf[1024]; + +void +prom_printf(char *fmt, ...) +{ + va_list args; + char ch, *bptr; + int i; + + va_start(args, fmt); + +#ifdef CONFIG_KGDB + ppbuf[0] = 'O'; + i = vsprintf(ppbuf + 1, fmt, args) + 1; +#else + i = vsprintf(ppbuf, fmt, args); +#endif + + bptr = ppbuf; + +#ifdef CONFIG_AP1000 + ap_write(1,bptr,strlen(bptr)); +#else + +#ifdef CONFIG_KGDB + if (kgdb_initialized) { + printk("kgdb_initialized = %d\n", kgdb_initialized); + putpacket(bptr, 1); + } else +#else + while((ch = *(bptr++)) != 0) { + if(ch == '\n') + prom_putchar('\r'); + + prom_putchar(ch); + } +#endif +#endif + va_end(args); + return; +} diff --git a/arch/m68k/sun3/sbus.c b/arch/m68k/sun3/sbus.c new file mode 100644 index 00000000000..babdbfa3cda --- /dev/null +++ b/arch/m68k/sun3/sbus.c @@ -0,0 +1,27 @@ +/* + * SBus helper functions + * + * Sun3 don't have a sbus, but many of the used devices are also + * used on Sparc machines with sbus. To avoid having a lot of + * duplicate code, we provide necessary glue stuff to make using + * of the sbus driver code possible. + * + * (C) 1999 Thomas Bogendoerfer (tsbogend@alpha.franken.de) + */ + +#include <linux/types.h> +#include <linux/compiler.h> +#include <linux/init.h> + +int __init sbus_init(void) +{ + return 0; +} + +void *sparc_alloc_io (u32 address, void *virtual, int len, char *name, + u32 bus_type, int rdonly) +{ + return (void *)address; +} + +subsys_initcall(sbus_init); diff --git a/arch/m68k/sun3/sun3_ksyms.c b/arch/m68k/sun3/sun3_ksyms.c new file mode 100644 index 00000000000..43e5a9af8ab --- /dev/null +++ b/arch/m68k/sun3/sun3_ksyms.c @@ -0,0 +1,13 @@ +#include <linux/module.h> +#include <linux/types.h> +#include <asm/dvma.h> +#include <asm/idprom.h> + +/* + * Add things here when you find the need for it. + */ +EXPORT_SYMBOL(dvma_map_align); +EXPORT_SYMBOL(dvma_unmap); +EXPORT_SYMBOL(dvma_malloc_align); +EXPORT_SYMBOL(dvma_free); +EXPORT_SYMBOL(idprom); diff --git a/arch/m68k/sun3/sun3dvma.c b/arch/m68k/sun3/sun3dvma.c new file mode 100644 index 00000000000..f04a1d25f1a --- /dev/null +++ b/arch/m68k/sun3/sun3dvma.c @@ -0,0 +1,379 @@ +/* + * linux/arch/m68k/mm/sun3dvma.c + * + * Copyright (C) 2000 Sam Creasey + * + * Contains common routines for sun3/sun3x DVMA management. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/list.h> + +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/dvma.h> + +#undef DVMA_DEBUG + +#ifdef CONFIG_SUN3X +extern void dvma_unmap_iommu(unsigned long baddr, int len); +#else +static inline void dvma_unmap_iommu(unsigned long a, int b) +{ +} +#endif + +#ifdef CONFIG_SUN3 +extern void sun3_dvma_init(void); +#endif + +unsigned long iommu_use[IOMMU_TOTAL_ENTRIES]; + +#define dvma_index(baddr) ((baddr - DVMA_START) >> DVMA_PAGE_SHIFT) + +#define dvma_entry_use(baddr) (iommu_use[dvma_index(baddr)]) + +struct hole { + unsigned long start; + unsigned long end; + unsigned long size; + struct list_head list; +}; + +static struct list_head hole_list; +static struct list_head hole_cache; +static struct hole initholes[64]; + +#ifdef DVMA_DEBUG + +static unsigned long dvma_allocs; +static unsigned long dvma_frees; +static unsigned long long dvma_alloc_bytes; +static unsigned long long dvma_free_bytes; + +static void print_use(void) +{ + + int i; + int j = 0; + + printk("dvma entry usage:\n"); + + for(i = 0; i < IOMMU_TOTAL_ENTRIES; i++) { + if(!iommu_use[i]) + continue; + + j++; + + printk("dvma entry: %08lx len %08lx\n", + ( i << DVMA_PAGE_SHIFT) + DVMA_START, + iommu_use[i]); + } + + printk("%d entries in use total\n", j); + + printk("allocation/free calls: %lu/%lu\n", dvma_allocs, dvma_frees); + printk("allocation/free bytes: %Lx/%Lx\n", dvma_alloc_bytes, + dvma_free_bytes); +} + +static void print_holes(struct list_head *holes) +{ + + struct list_head *cur; + struct hole *hole; + + printk("listing dvma holes\n"); + list_for_each(cur, holes) { + hole = list_entry(cur, struct hole, list); + + if((hole->start == 0) && (hole->end == 0) && (hole->size == 0)) + continue; + + printk("hole: start %08lx end %08lx size %08lx\n", hole->start, hole->end, hole->size); + } + + printk("end of hole listing...\n"); + +} +#endif /* DVMA_DEBUG */ + +static inline int refill(void) +{ + + struct hole *hole; + struct hole *prev = NULL; + struct list_head *cur; + int ret = 0; + + list_for_each(cur, &hole_list) { + hole = list_entry(cur, struct hole, list); + + if(!prev) { + prev = hole; + continue; + } + + if(hole->end == prev->start) { + hole->size += prev->size; + hole->end = prev->end; + list_del(&(prev->list)); + list_add(&(prev->list), &hole_cache); + ret++; + } + + } + + return ret; +} + +static inline struct hole *rmcache(void) +{ + struct hole *ret; + + if(list_empty(&hole_cache)) { + if(!refill()) { + printk("out of dvma hole cache!\n"); + BUG(); + } + } + + ret = list_entry(hole_cache.next, struct hole, list); + list_del(&(ret->list)); + + return ret; + +} + +static inline unsigned long get_baddr(int len, unsigned long align) +{ + + struct list_head *cur; + struct hole *hole; + + if(list_empty(&hole_list)) { +#ifdef DVMA_DEBUG + printk("out of dvma holes! (printing hole cache)\n"); + print_holes(&hole_cache); + print_use(); +#endif + BUG(); + } + + list_for_each(cur, &hole_list) { + unsigned long newlen; + + hole = list_entry(cur, struct hole, list); + + if(align > DVMA_PAGE_SIZE) + newlen = len + ((hole->end - len) & (align-1)); + else + newlen = len; + + if(hole->size > newlen) { + hole->end -= newlen; + hole->size -= newlen; + dvma_entry_use(hole->end) = newlen; +#ifdef DVMA_DEBUG + dvma_allocs++; + dvma_alloc_bytes += newlen; +#endif + return hole->end; + } else if(hole->size == newlen) { + list_del(&(hole->list)); + list_add(&(hole->list), &hole_cache); + dvma_entry_use(hole->start) = newlen; +#ifdef DVMA_DEBUG + dvma_allocs++; + dvma_alloc_bytes += newlen; +#endif + return hole->start; + } + + } + + printk("unable to find dvma hole!\n"); + BUG(); + return 0; +} + +static inline int free_baddr(unsigned long baddr) +{ + + unsigned long len; + struct hole *hole; + struct list_head *cur; + unsigned long orig_baddr; + + orig_baddr = baddr; + len = dvma_entry_use(baddr); + dvma_entry_use(baddr) = 0; + baddr &= DVMA_PAGE_MASK; + dvma_unmap_iommu(baddr, len); + +#ifdef DVMA_DEBUG + dvma_frees++; + dvma_free_bytes += len; +#endif + + list_for_each(cur, &hole_list) { + hole = list_entry(cur, struct hole, list); + + if(hole->end == baddr) { + hole->end += len; + hole->size += len; + return 0; + } else if(hole->start == (baddr + len)) { + hole->start = baddr; + hole->size += len; + return 0; + } + + } + + hole = rmcache(); + + hole->start = baddr; + hole->end = baddr + len; + hole->size = len; + +// list_add_tail(&(hole->list), cur); + list_add(&(hole->list), cur); + + return 0; + +} + +void dvma_init(void) +{ + + struct hole *hole; + int i; + + INIT_LIST_HEAD(&hole_list); + INIT_LIST_HEAD(&hole_cache); + + /* prepare the hole cache */ + for(i = 0; i < 64; i++) + list_add(&(initholes[i].list), &hole_cache); + + hole = rmcache(); + hole->start = DVMA_START; + hole->end = DVMA_END; + hole->size = DVMA_SIZE; + + list_add(&(hole->list), &hole_list); + + memset(iommu_use, 0, sizeof(iommu_use)); + + dvma_unmap_iommu(DVMA_START, DVMA_SIZE); + +#ifdef CONFIG_SUN3 + sun3_dvma_init(); +#endif + +} + +inline unsigned long dvma_map_align(unsigned long kaddr, int len, int align) +{ + + unsigned long baddr; + unsigned long off; + + if(!len) + len = 0x800; + + if(!kaddr || !len) { +// printk("error: kaddr %lx len %x\n", kaddr, len); +// *(int *)4 = 0; + return 0; + } + +#ifdef DEBUG + printk("dvma_map request %08lx bytes from %08lx\n", + len, kaddr); +#endif + off = kaddr & ~DVMA_PAGE_MASK; + kaddr &= PAGE_MASK; + len += off; + len = ((len + (DVMA_PAGE_SIZE-1)) & DVMA_PAGE_MASK); + + if(align == 0) + align = DVMA_PAGE_SIZE; + else + align = ((align + (DVMA_PAGE_SIZE-1)) & DVMA_PAGE_MASK); + + baddr = get_baddr(len, align); +// printk("using baddr %lx\n", baddr); + + if(!dvma_map_iommu(kaddr, baddr, len)) + return (baddr + off); + + printk("dvma_map failed kaddr %lx baddr %lx len %x\n", kaddr, baddr, len); + BUG(); + return 0; +} + +void dvma_unmap(void *baddr) +{ + unsigned long addr; + + addr = (unsigned long)baddr; + /* check if this is a vme mapping */ + if(!(addr & 0x00f00000)) + addr |= 0xf00000; + + free_baddr(addr); + + return; + +} + + +void *dvma_malloc_align(unsigned long len, unsigned long align) +{ + unsigned long kaddr; + unsigned long baddr; + unsigned long vaddr; + + if(!len) + return NULL; + +#ifdef DEBUG + printk("dvma_malloc request %lx bytes\n", len); +#endif + len = ((len + (DVMA_PAGE_SIZE-1)) & DVMA_PAGE_MASK); + + if((kaddr = __get_free_pages(GFP_ATOMIC, get_order(len))) == 0) + return NULL; + + if((baddr = (unsigned long)dvma_map_align(kaddr, len, align)) == 0) { + free_pages(kaddr, get_order(len)); + return NULL; + } + + vaddr = dvma_btov(baddr); + + if(dvma_map_cpu(kaddr, vaddr, len) < 0) { + dvma_unmap((void *)baddr); + free_pages(kaddr, get_order(len)); + return NULL; + } + +#ifdef DEBUG + printk("mapped %08lx bytes %08lx kern -> %08lx bus\n", + len, kaddr, baddr); +#endif + + return (void *)vaddr; + +} + +void dvma_free(void *vaddr) +{ + + return; + +} diff --git a/arch/m68k/sun3/sun3ints.c b/arch/m68k/sun3/sun3ints.c new file mode 100644 index 00000000000..e62a033cd49 --- /dev/null +++ b/arch/m68k/sun3/sun3ints.c @@ -0,0 +1,265 @@ + /* + * linux/arch/m68k/sun3/sun3ints.c -- Sun-3(x) Linux interrupt handling code + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/kernel_stat.h> +#include <linux/interrupt.h> +#include <asm/segment.h> +#include <asm/intersil.h> +#include <asm/oplib.h> +#include <asm/sun3ints.h> +#include <linux/seq_file.h> + +extern void sun3_leds (unsigned char); +static irqreturn_t sun3_inthandle(int irq, void *dev_id, struct pt_regs *fp); + +void sun3_disable_interrupts(void) +{ + sun3_disable_irq(0); +} + +void sun3_enable_interrupts(void) +{ + sun3_enable_irq(0); +} + +int led_pattern[8] = { + ~(0x80), ~(0x01), + ~(0x40), ~(0x02), + ~(0x20), ~(0x04), + ~(0x10), ~(0x08) +}; + +volatile unsigned char* sun3_intreg; + +void sun3_insert_irq(irq_node_t **list, irq_node_t *node) +{ +} + +void sun3_delete_irq(irq_node_t **list, void *dev_id) +{ +} + +void sun3_enable_irq(unsigned int irq) +{ + *sun3_intreg |= (1<<irq); +} + +void sun3_disable_irq(unsigned int irq) +{ + *sun3_intreg &= ~(1<<irq); +} + +inline void sun3_do_irq(int irq, struct pt_regs *fp) +{ + kstat_cpu(0).irqs[SYS_IRQS + irq]++; + *sun3_intreg &= ~(1<<irq); + *sun3_intreg |= (1<<irq); +} + +static irqreturn_t sun3_int7(int irq, void *dev_id, struct pt_regs *fp) +{ + sun3_do_irq(irq,fp); + if(!(kstat_cpu(0).irqs[SYS_IRQS + irq] % 2000)) + sun3_leds(led_pattern[(kstat_cpu(0).irqs[SYS_IRQS+irq]%16000) + /2000]); + return IRQ_HANDLED; +} + +static irqreturn_t sun3_int5(int irq, void *dev_id, struct pt_regs *fp) +{ + kstat_cpu(0).irqs[SYS_IRQS + irq]++; +#ifdef CONFIG_SUN3 + intersil_clear(); +#endif + *sun3_intreg &= ~(1<<irq); + *sun3_intreg |= (1<<irq); +#ifdef CONFIG_SUN3 + intersil_clear(); +#endif + do_timer(fp); +#ifndef CONFIG_SMP + update_process_times(user_mode(fp)); +#endif + if(!(kstat_cpu(0).irqs[SYS_IRQS + irq] % 20)) + sun3_leds(led_pattern[(kstat_cpu(0).irqs[SYS_IRQS+irq]%160) + /20]); + return IRQ_HANDLED; +} + +/* handle requested ints, excepting 5 and 7, which always do the same + thing */ +irqreturn_t (*sun3_default_handler[SYS_IRQS])(int, void *, struct pt_regs *) = { + [0] = sun3_inthandle, + [1] = sun3_inthandle, + [2] = sun3_inthandle, + [3] = sun3_inthandle, + [4] = sun3_inthandle, + [5] = sun3_int5, + [6] = sun3_inthandle, + [7] = sun3_int7 +}; + +static const char *dev_names[SYS_IRQS] = { + [5] = "timer", + [7] = "int7 handler" +}; +static void *dev_ids[SYS_IRQS]; +static irqreturn_t (*sun3_inthandler[SYS_IRQS])(int, void *, struct pt_regs *) = { + [5] = sun3_int5, + [7] = sun3_int7 +}; +static irqreturn_t (*sun3_vechandler[SUN3_INT_VECS])(int, void *, struct pt_regs *); +static void *vec_ids[SUN3_INT_VECS]; +static const char *vec_names[SUN3_INT_VECS]; +static int vec_ints[SUN3_INT_VECS]; + + +int show_sun3_interrupts(struct seq_file *p, void *v) +{ + int i; + + for(i = 0; i < (SUN3_INT_VECS-1); i++) { + if(sun3_vechandler[i] != NULL) { + seq_printf(p, "vec %3d: %10u %s\n", i+64, + vec_ints[i], + (vec_names[i]) ? vec_names[i] : + "sun3_vechandler"); + } + } + + return 0; +} + +static irqreturn_t sun3_inthandle(int irq, void *dev_id, struct pt_regs *fp) +{ + if(sun3_inthandler[irq] == NULL) + panic ("bad interrupt %d received (id %p)\n",irq, dev_id); + + kstat_cpu(0).irqs[SYS_IRQS + irq]++; + *sun3_intreg &= ~(1<<irq); + + sun3_inthandler[irq](irq, dev_ids[irq], fp); + return IRQ_HANDLED; +} + +static irqreturn_t sun3_vec255(int irq, void *dev_id, struct pt_regs *fp) +{ +// intersil_clear(); + return IRQ_HANDLED; +} + +void sun3_init_IRQ(void) +{ + int i; + + *sun3_intreg = 1; + + for(i = 0; i < SYS_IRQS; i++) + { + if(dev_names[i]) + cpu_request_irq(i, sun3_default_handler[i], 0, + dev_names[i], NULL); + } + + for(i = 0; i < 192; i++) + sun3_vechandler[i] = NULL; + + sun3_vechandler[191] = sun3_vec255; +} + +int sun3_request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), + unsigned long flags, const char *devname, void *dev_id) +{ + + if(irq < SYS_IRQS) { + if(sun3_inthandler[irq] != NULL) { + printk("sun3_request_irq: request for irq %d -- already taken!\n", irq); + return 1; + } + + sun3_inthandler[irq] = handler; + dev_ids[irq] = dev_id; + dev_names[irq] = devname; + + /* setting devname would be nice */ + cpu_request_irq(irq, sun3_default_handler[irq], 0, devname, + NULL); + + return 0; + } else { + if((irq >= 64) && (irq <= 255)) { + int vec; + + vec = irq - 64; + if(sun3_vechandler[vec] != NULL) { + printk("sun3_request_irq: request for vec %d -- already taken!\n", irq); + return 1; + } + + sun3_vechandler[vec] = handler; + vec_ids[vec] = dev_id; + vec_names[vec] = devname; + vec_ints[vec] = 0; + + return 0; + } + } + + printk("sun3_request_irq: invalid irq %d\n", irq); + return 1; + +} + +void sun3_free_irq(unsigned int irq, void *dev_id) +{ + + if(irq < SYS_IRQS) { + if(sun3_inthandler[irq] == NULL) + panic("sun3_free_int: attempt to free unused irq %d\n", irq); + if(dev_ids[irq] != dev_id) + panic("sun3_free_int: incorrect dev_id for irq %d\n", irq); + + sun3_inthandler[irq] = NULL; + return; + } else if((irq >= 64) && (irq <= 255)) { + int vec; + + vec = irq - 64; + if(sun3_vechandler[vec] == NULL) + panic("sun3_free_int: attempt to free unused vector %d\n", irq); + if(vec_ids[irq] != dev_id) + panic("sun3_free_int: incorrect dev_id for vec %d\n", irq); + + sun3_vechandler[vec] = NULL; + return; + } else { + panic("sun3_free_irq: invalid irq %d\n", irq); + } +} + +irqreturn_t sun3_process_int(int irq, struct pt_regs *regs) +{ + + if((irq >= 64) && (irq <= 255)) { + int vec; + + vec = irq - 64; + if(sun3_vechandler[vec] == NULL) + panic ("bad interrupt vector %d received\n",irq); + + vec_ints[vec]++; + return sun3_vechandler[vec](irq, vec_ids[vec], regs); + } else { + panic("sun3_process_int: unable to handle interrupt vector %d\n", + irq); + } +} |