diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 00:20:36 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 00:20:36 +0200 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/sparc | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/sparc')
127 files changed, 40258 insertions, 0 deletions
diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig new file mode 100644 index 000000000000..237f922520fd --- /dev/null +++ b/arch/sparc/Kconfig @@ -0,0 +1,393 @@ +# $Id: config.in,v 1.113 2002/01/24 22:14:44 davem Exp $ +# For a description of the syntax of this configuration file, +# see Documentation/kbuild/kconfig-language.txt. +# + +mainmenu "Linux/SPARC Kernel Configuration" + +config MMU + bool + default y + +config UID16 + bool + default y + +config HIGHMEM + bool + default y + +config GENERIC_ISA_DMA + bool + default y + +source "init/Kconfig" + +menu "General machine setup" + +config VT + bool + select INPUT + default y + ---help--- + If you say Y here, you will get support for terminal devices with + display and keyboard devices. These are called "virtual" because you + can run several virtual terminals (also called virtual consoles) on + one physical terminal. This is rather useful, for example one + virtual terminal can collect system messages and warnings, another + one can be used for a text-mode user session, and a third could run + an X session, all in parallel. Switching between virtual terminals + is done with certain key combinations, usually Alt-<function key>. + + The setterm command ("man setterm") can be used to change the + properties (such as colors or beeping) of a virtual terminal. The + man page console_codes(4) ("man console_codes") contains the special + character sequences that can be used to change those properties + directly. The fonts used on virtual terminals can be changed with + the setfont ("man setfont") command and the key bindings are defined + with the loadkeys ("man loadkeys") command. + + You need at least one virtual terminal device in order to make use + of your keyboard and monitor. Therefore, only people configuring an + embedded system would want to say N here in order to save some + memory; the only way to log into such a system is then via a serial + or network connection. + + If unsure, say Y, or else you won't be able to do much with your new + shiny Linux system :-) + +config VT_CONSOLE + bool + default y + ---help--- + The system console is the device which receives all kernel messages + and warnings and which allows logins in single user mode. If you + answer Y here, a virtual terminal (the device used to interact with + a physical terminal) can be used as system console. This is the most + common mode of operations, so you should say Y here unless you want + the kernel messages be output only to a serial port (in which case + you should say Y to "Console on serial port", below). + + If you do say Y here, by default the currently visible virtual + terminal (/dev/tty0) will be used as system console. You can change + that with a kernel command line option such as "console=tty3" which + would use the third virtual terminal as system console. (Try "man + bootparam" or see the documentation of your boot loader (lilo or + loadlin) about how to pass options to the kernel at boot time.) + + If unsure, say Y. + +config HW_CONSOLE + bool + default y + +config SMP + bool "Symmetric multi-processing support (does not work on sun4/sun4c)" + depends on BROKEN + ---help--- + This enables support for systems with more than one CPU. If you have + a system with only one CPU, say N. If you have a system with more + than one CPU, say Y. + + If you say N here, the kernel will run on single and multiprocessor + machines, but will use only one CPU of a multiprocessor machine. If + you say Y here, the kernel will run on many, but not all, + singleprocessor machines. On a singleprocessor machine, the kernel + will run faster if you say N here. + + People using multiprocessor machines who say Y here should also say + Y to "Enhanced Real Time Clock Support", below. The "Advanced Power + Management" code will be disabled if you say Y here. + + See also the <file:Documentation/smp.txt>, + <file:Documentation/nmi_watchdog.txt> and the SMP-HOWTO available at + <http://www.tldp.org/docs.html#howto>. + + If you don't know what to do here, say N. + +config NR_CPUS + int "Maximum number of CPUs (2-32)" + range 2 32 + depends on SMP + default "32" + +# Identify this as a Sparc32 build +config SPARC32 + bool + default y + help + SPARC is a family of RISC microprocessors designed and marketed by + Sun Microsystems, incorporated. They are very widely found in Sun + workstations and clones. This port covers the original 32-bit SPARC; + it is old and stable and usually considered one of the "big three" + along with the Intel and Alpha ports. The UltraLinux project + maintains both the SPARC32 and SPARC64 ports; its web page is + available at <http://www.ultralinux.org/>. + +# Global things across all Sun machines. +config ISA + bool + help + ISA is found on Espresso only and is not supported currently. + Say N + +config EISA + bool + help + EISA is not supported. + Say N + +config MCA + bool + help + MCA is not supported. + Say N + +config PCMCIA + tristate + ---help--- + Say Y here if you want to attach PCMCIA- or PC-cards to your Linux + computer. These are credit-card size devices such as network cards, + modems or hard drives often used with laptops computers. There are + actually two varieties of these cards: the older 16 bit PCMCIA cards + and the newer 32 bit CardBus cards. If you want to use CardBus + cards, you need to say Y here and also to "CardBus support" below. + + To use your PC-cards, you will need supporting software from David + Hinds' pcmcia-cs package (see the file <file:Documentation/Changes> + for location). Please also read the PCMCIA-HOWTO, available from + <http://www.tldp.org/docs.html#howto>. + + To compile this driver as modules, choose M here: the + modules will be called pcmcia_core and ds. + +config SBUS + bool + default y + +config SBUSCHAR + bool + default y + +config SERIAL_CONSOLE + bool + default y + ---help--- + If you say Y here, it will be possible to use a serial port as the + system console (the system console is the device which receives all + kernel messages and warnings and which allows logins in single user + mode). This could be useful if some terminal or printer is connected + to that serial port. + + Even if you say Y here, the currently visible virtual console + (/dev/tty0) will still be used as the system console by default, but + you can alter that using a kernel command line option such as + "console=ttyS1". (Try "man bootparam" or see the documentation of + your boot loader (silo) about how to pass options to the kernel at + boot time.) + + If you don't have a graphics card installed and you say Y here, the + kernel will automatically use the first serial line, /dev/ttyS0, as + system console. + + If unsure, say N. + +config SUN_AUXIO + bool + default y + +config SUN_IO + bool + default y + +config RWSEM_GENERIC_SPINLOCK + bool + default y + +config RWSEM_XCHGADD_ALGORITHM + bool + +config GENERIC_CALIBRATE_DELAY + bool + default y + +config SUN_PM + bool + default y + help + Enable power management and CPU standby features on supported + SPARC platforms. + +config SUN4 + bool "Support for SUN4 machines (disables SUN4[CDM] support)" + depends on !SMP + default n + help + Say Y here if, and only if, your machine is a sun4. Note that + a kernel compiled with this option will run only on sun4. + (And the current version will probably work only on sun4/330.) + +if !SUN4 + +config PCI + bool "Support for PCI and PS/2 keyboard/mouse" + help + CONFIG_PCI is needed for all JavaStation's (including MrCoffee), + CP-1200, JavaEngine-1, Corona, Red October, and Serengeti SGSC. + All of these platforms are extremely obscure, so say N if unsure. + +source "drivers/pci/Kconfig" + +endif + +config SUN_OPENPROMFS + tristate "Openprom tree appears in /proc/openprom" + help + If you say Y, the OpenPROM device tree will be available as a + virtual file system, which you can mount to /proc/openprom by "mount + -t openpromfs none /proc/openprom". + + To compile the /proc/openprom support as a module, choose M here: the + module will be called openpromfs. + + Only choose N if you know in advance that you will not need to modify + OpenPROM settings on the running system. + +source "fs/Kconfig.binfmt" + +config SUNOS_EMUL + bool "SunOS binary emulation" + help + This allows you to run most SunOS binaries. If you want to do this, + say Y here and place appropriate files in /usr/gnemul/sunos. See + <http://www.ultralinux.org/faq.html> for more information. If you + want to run SunOS binaries on an Ultra you must also say Y to + "Kernel support for 32-bit a.out binaries" above. + +source "drivers/parport/Kconfig" + +config PRINTER + tristate "Parallel printer support" + depends on PARPORT + ---help--- + If you intend to attach a printer to the parallel port of your Linux + box (as opposed to using a serial printer; if the connector at the + printer has 9 or 25 holes ["female"], then it's serial), say Y. + Also read the Printing-HOWTO, available from + <http://www.tldp.org/docs.html#howto>. + + It is possible to share one parallel port among several devices + (e.g. printer and ZIP drive) and it is safe to compile the + corresponding drivers into the kernel. If you want to compile this + driver as a module however, choose M here and read + <file:Documentation/parport.txt>. The module will be called lp. + + If you have several parallel ports, you can specify which ports to + use with the "lp" kernel command line option. (Try "man bootparam" + or see the documentation of your boot loader (silo) about how to pass + options to the kernel at boot time.) The syntax of the "lp" command + line option can be found in <file:drivers/char/lp.c>. + + If you have more than 8 printers, you need to increase the LP_NO + macro in lp.c and the PARPORT_MAX macro in parport.h. + +endmenu + +source "drivers/base/Kconfig" + +source "drivers/video/Kconfig" + +source "drivers/mtd/Kconfig" + +source "drivers/serial/Kconfig" + +if !SUN4 +source "drivers/sbus/char/Kconfig" +endif + +source "drivers/block/Kconfig" + +# Don't frighten a common SBus user +if PCI + +source "drivers/ide/Kconfig" + +endif + +source "drivers/isdn/Kconfig" + +source "drivers/scsi/Kconfig" + +source "drivers/fc4/Kconfig" + +source "drivers/md/Kconfig" + +source "net/Kconfig" + +# This one must be before the filesystem configs. -DaveM + +menu "Unix98 PTY support" + +config UNIX98_PTYS + bool "Unix98 PTY support" + ---help--- + A pseudo terminal (PTY) is a software device consisting of two + halves: a master and a slave. The slave device behaves identical to + a physical terminal; the master device is used by a process to + read data from and write data to the slave, thereby emulating a + terminal. Typical programs for the master side are telnet servers + and xterms. + + Linux has traditionally used the BSD-like names /dev/ptyxx for + masters and /dev/ttyxx for slaves of pseudo terminals. This scheme + has a number of problems. The GNU C library glibc 2.1 and later, + however, supports the Unix98 naming standard: in order to acquire a + pseudo terminal, a process opens /dev/ptmx; the number of the pseudo + terminal is then made available to the process and the pseudo + terminal slave can be accessed as /dev/pts/<number>. What was + traditionally /dev/ttyp2 will then be /dev/pts/2, for example. + + The entries in /dev/pts/ are created on the fly by a virtual + file system; therefore, if you say Y here you should say Y to + "/dev/pts file system for Unix98 PTYs" as well. + + If you want to say Y here, you need to have the C library glibc 2.1 + or later (equal to libc-6.1, check with "ls -l /lib/libc.so.*"). + Read the instructions in <file:Documentation/Changes> pertaining to + pseudo terminals. It's safe to say N. + +config UNIX98_PTY_COUNT + int "Maximum number of Unix98 PTYs in use (0-2048)" + depends on UNIX98_PTYS + default "256" + help + The maximum number of Unix98 PTYs that can be used at any one time. + The default is 256, and should be enough for desktop systems. Server + machines which support incoming telnet/rlogin/ssh connections and/or + serve several X terminals may want to increase this: every incoming + connection and every xterm uses up one PTY. + + When not in use, each additional set of 256 PTYs occupy + approximately 8 KB of kernel memory on 32-bit architectures. + +endmenu + +source "drivers/input/Kconfig" + +source "fs/Kconfig" + +source "sound/Kconfig" + +source "drivers/usb/Kconfig" + +source "drivers/infiniband/Kconfig" + +source "drivers/char/watchdog/Kconfig" + +source "arch/sparc/Kconfig.debug" + +source "security/Kconfig" + +source "crypto/Kconfig" + +source "lib/Kconfig" diff --git a/arch/sparc/Kconfig.debug b/arch/sparc/Kconfig.debug new file mode 100644 index 000000000000..120f6b529348 --- /dev/null +++ b/arch/sparc/Kconfig.debug @@ -0,0 +1,14 @@ +menu "Kernel hacking" + +source "lib/Kconfig.debug" + +config DEBUG_STACK_USAGE + bool "Enable stack utilization instrumentation" + depends on DEBUG_KERNEL + help + Enables the display of the minimum amount of free stack which each + task has ever had available in the sysrq-T and sysrq-P debug output. + + This option will slow down process creation somewhat. + +endmenu diff --git a/arch/sparc/Makefile b/arch/sparc/Makefile new file mode 100644 index 000000000000..7b3bbaf083a6 --- /dev/null +++ b/arch/sparc/Makefile @@ -0,0 +1,78 @@ +# +# sparc/Makefile +# +# Makefile for the architecture dependent flags and dependencies on the +# Sparc. +# +# Copyright (C) 1994 David S. Miller (davem@caip.rutgers.edu) +# + +# +# Uncomment the first CFLAGS if you are doing kgdb source level +# debugging of the kernel to get the proper debugging information. + +AS := $(AS) -32 +LDFLAGS := -m elf32_sparc +CHECKFLAGS += -D__sparc__ + +#CFLAGS := $(CFLAGS) -g -pipe -fcall-used-g5 -fcall-used-g7 +CFLAGS := $(CFLAGS) -m32 -pipe -mno-fpu -fcall-used-g5 -fcall-used-g7 +AFLAGS := $(AFLAGS) -m32 + +#LDFLAGS_vmlinux = -N -Ttext 0xf0004000 +# Since 2.5.40, the first stage is left not btfix-ed. +# Actual linking is done with "make image". +LDFLAGS_vmlinux = -r + +head-y := arch/sparc/kernel/head.o arch/sparc/kernel/init_task.o +HEAD_Y := $(head-y) + +core-y += arch/sparc/kernel/ arch/sparc/mm/ arch/sparc/math-emu/ +libs-y += arch/sparc/prom/ arch/sparc/lib/ + +# Export what is needed by arch/sparc/boot/Makefile +# Renaming is done to avoid confusing pattern matching rules in 2.5.45 (multy-) +INIT_Y := $(patsubst %/, %/built-in.o, $(init-y)) +CORE_Y := $(core-y) +CORE_Y += kernel/ mm/ fs/ ipc/ security/ crypto/ +CORE_Y := $(patsubst %/, %/built-in.o, $(CORE_Y)) +DRIVERS_Y := $(patsubst %/, %/built-in.o, $(drivers-y)) +NET_Y := $(patsubst %/, %/built-in.o, $(net-y)) +LIBS_Y1 := $(patsubst %/, %/lib.a, $(libs-y)) +LIBS_Y2 := $(patsubst %/, %/built-in.o, $(libs-y)) +LIBS_Y := $(LIBS_Y1) $(LIBS_Y2) + +ifdef CONFIG_KALLSYMS +kallsyms.o := .tmp_kallsyms2.o +endif + +export INIT_Y CORE_Y DRIVERS_Y NET_Y LIBS_Y HEAD_Y kallsyms.o + +# Default target +all: image + +boot := arch/sparc/boot + +image tftpboot.img: vmlinux + $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ + +archclean: + $(Q)$(MAKE) $(clean)=$(boot) + +prepare: include/asm-$(ARCH)/asm_offsets.h + +arch/$(ARCH)/kernel/asm-offsets.s: include/asm include/linux/version.h \ + include/config/MARKER + +include/asm-$(ARCH)/asm_offsets.h: arch/$(ARCH)/kernel/asm-offsets.s + $(call filechk,gen-asm-offsets) + +CLEAN_FILES += include/asm-$(ARCH)/asm_offsets.h \ + arch/$(ARCH)/kernel/asm-offsets.s \ + arch/$(ARCH)/boot/System.map + +# Don't use tabs in echo arguments. +define archhelp + echo '* image - kernel image ($(boot)/image)' + echo ' tftpboot.img - image prepared for tftp' +endef diff --git a/arch/sparc/boot/Makefile b/arch/sparc/boot/Makefile new file mode 100644 index 000000000000..b365084316ac --- /dev/null +++ b/arch/sparc/boot/Makefile @@ -0,0 +1,58 @@ +# $Id: Makefile,v 1.10 2000/02/23 08:17:46 jj Exp $ +# Makefile for the Sparc boot stuff. +# +# Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) +# Copyright (C) 1997,1998 Jakub Jelinek (jj@ultra.linux.cz) + +ROOT_IMG := /usr/src/root.img +ELFTOAOUT := elftoaout + +hostprogs-y := piggyback btfixupprep +targets := tftpboot.img btfix.o btfix.S image + +quiet_cmd_elftoaout = ELFTOAOUT $@ + cmd_elftoaout = $(ELFTOAOUT) $(obj)/image -o $@ +quiet_cmd_piggy = PIGGY $@ + cmd_piggy = $(obj)/piggyback $@ $(obj)/System.map $(ROOT_IMG) +quiet_cmd_btfix = BTFIX $@ + cmd_btfix = $(OBJDUMP) -x vmlinux | $(obj)/btfixupprep > $@ +quiet_cmd_sysmap = SYSMAP $(obj)/System.map + cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap +quiet_cmd_image = LD $@ + cmd_image = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_$(@F)) -o $@ + +define rule_image + $(if $($(quiet)cmd_image), \ + echo ' $($(quiet)cmd_image)' &&) \ + $(cmd_image); \ + $(if $($(quiet)cmd_sysmap), \ + echo ' $($(quiet)cmd_sysmap)' &&) \ + $(cmd_sysmap) $@ $(obj)/System.map; \ + if [ $$? -ne 0 ]; then \ + rm -f $@; \ + /bin/false; \ + fi; \ + echo 'cmd_$@ := $(cmd_image)' > $(@D)/.$(@F).cmd +endef + +BTOBJS := $(HEAD_Y) $(INIT_Y) +BTLIBS := $(CORE_Y) $(LIBS_Y) $(DRIVERS_Y) $(NET_Y) +LDFLAGS_image := -T arch/sparc/kernel/vmlinux.lds $(BTOBJS) \ + --start-group $(BTLIBS) --end-group \ + $(kallsyms.o) $(obj)/btfix.o + +# Link the final image including btfixup'ed symbols. +# This is a replacement for the link done in the top-level Makefile. +# Note: No dependency on the prerequisite files since that would require +# make to try check if they are updated - and due to changes +# in gcc options (path for example) this would result in +# these files being recompiled for each build. +$(obj)/image: $(obj)/btfix.o FORCE + $(call if_changed_rule,image) + +$(obj)/tftpboot.img: $(obj)/piggyback $(obj)/System.map $(obj)/image FORCE + $(call if_changed,elftoaout) + $(call if_changed,piggy) + +$(obj)/btfix.S: $(obj)/btfixupprep vmlinux FORCE + $(call if_changed,btfix) diff --git a/arch/sparc/boot/btfixupprep.c b/arch/sparc/boot/btfixupprep.c new file mode 100644 index 000000000000..dc7b0546e3bb --- /dev/null +++ b/arch/sparc/boot/btfixupprep.c @@ -0,0 +1,386 @@ +/* $Id: btfixupprep.c,v 1.6 2001/08/22 15:27:47 davem Exp $ + Simple utility to prepare vmlinux image for sparc. + Resolves all BTFIXUP uses and settings and creates + a special .s object to link to the image. + + Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <malloc.h> + +#define MAXSYMS 1024 + +static char *symtab = "SYMBOL TABLE:"; +static char *relrec = "RELOCATION RECORDS FOR ["; +static int rellen; +static int symlen; +int mode; + +struct _btfixup; + +typedef struct _btfixuprel { + char *sect; + unsigned long offset; + struct _btfixup *f; + int frel; + struct _btfixuprel *next; +} btfixuprel; + +typedef struct _btfixup { + int type; + int setinitval; + unsigned int initval; + char *initvalstr; + char *name; + btfixuprel *rel; +} btfixup; + +btfixup array[MAXSYMS]; +int last = 0; +char buffer[1024]; +unsigned long lastfoffset = -1; +unsigned long lastfrelno; +btfixup *lastf; + +void fatal(void) __attribute__((noreturn)); +void fatal(void) +{ + fprintf(stderr, "Malformed output from objdump\n%s\n", buffer); + exit(1); +} + +btfixup *find(int type, char *name) +{ + int i; + for (i = 0; i < last; i++) { + if (array[i].type == type && !strcmp(array[i].name, name)) + return array + i; + } + array[last].type = type; + array[last].name = strdup(name); + array[last].setinitval = 0; + if (!array[last].name) fatal(); + array[last].rel = NULL; + last++; + if (last >= MAXSYMS) { + fprintf(stderr, "Ugh. Something strange. More than %d different BTFIXUP symbols\n", MAXSYMS); + exit(1); + } + return array + last - 1; +} + +void set_mode (char *buffer) +{ + for (mode = 0;; mode++) + if (buffer[mode] < '0' || buffer[mode] > '9') + break; + if (mode != 8 && mode != 16) + fatal(); +} + + +int main(int argc,char **argv) +{ + char *p, *q; + char *sect; + int i, j, k; + unsigned int initval; + int shift; + btfixup *f; + btfixuprel *r, **rr; + unsigned long offset; + char *initvalstr; + + symlen = strlen(symtab); + while (fgets (buffer, 1024, stdin) != NULL) + if (!strncmp (buffer, symtab, symlen)) + goto main0; + fatal(); +main0: + rellen = strlen(relrec); + while (fgets (buffer, 1024, stdin) != NULL) + if (!strncmp (buffer, relrec, rellen)) + goto main1; + fatal(); +main1: + sect = malloc(strlen (buffer + rellen) + 1); + if (!sect) fatal(); + strcpy (sect, buffer + rellen); + p = strchr (sect, ']'); + if (!p) fatal(); + *p = 0; + if (fgets (buffer, 1024, stdin) == NULL) + fatal(); + while (fgets (buffer, 1024, stdin) != NULL) { + int nbase; + if (!strncmp (buffer, relrec, rellen)) + goto main1; + if (mode == 0) + set_mode (buffer); + p = strchr (buffer, '\n'); + if (p) *p = 0; + if (strlen (buffer) < 22+mode) + continue; + if (strncmp (buffer + mode, " R_SPARC_", 9)) + continue; + nbase = 27 - 8 + mode; + if (buffer[nbase] != '_' || buffer[nbase+1] != '_' || buffer[nbase+2] != '_') + continue; + switch (buffer[nbase+3]) { + case 'f': /* CALL */ + case 'b': /* BLACKBOX */ + case 's': /* SIMM13 */ + case 'a': /* HALF */ + case 'h': /* SETHI */ + case 'i': /* INT */ + break; + default: + continue; + } + p = strchr (buffer + nbase+5, '+'); + if (p) *p = 0; + shift = nbase + 5; + if (buffer[nbase+4] == 's' && buffer[nbase+5] == '_') { + shift = nbase + 6; + if (strcmp (sect, ".init.text")) { + fprintf(stderr, + "Wrong use of '%s' BTFIXUPSET in '%s' section.\n" + "BTFIXUPSET_CALL can be used only in" + " __init sections\n", + buffer + shift, sect); + exit(1); + } + } else if (buffer[nbase+4] != '_') + continue; + if (!strcmp (sect, ".text.exit")) + continue; + if (strcmp (sect, ".text") && + strcmp (sect, ".init.text") && + strcmp (sect, ".fixup") && + (strcmp (sect, "__ksymtab") || buffer[nbase+3] != 'f')) { + if (buffer[nbase+3] == 'f') + fprintf(stderr, + "Wrong use of '%s' in '%s' section.\n" + " It can be used only in .text, .init.text," + " .fixup and __ksymtab\n", + buffer + shift, sect); + else + fprintf(stderr, + "Wrong use of '%s' in '%s' section.\n" + " It can be only used in .text, .init.text," + " and .fixup\n", buffer + shift, sect); + exit(1); + } + p = strstr (buffer + shift, "__btset_"); + if (p && buffer[nbase+4] == 's') { + fprintf(stderr, "__btset_ in BTFIXUP name can only be used when defining the variable, not for setting\n%s\n", buffer); + exit(1); + } + initval = 0; + initvalstr = NULL; + if (p) { + if (p[8] != '0' || p[9] != 'x') { + fprintf(stderr, "Pre-initialized values can be only initialized with hexadecimal constants starting 0x\n%s\n", buffer); + exit(1); + } + initval = strtoul(p + 10, &q, 16); + if (*q || !initval) { + fprintf(stderr, "Pre-initialized values can be only in the form name__btset_0xXXXXXXXX where X are hex digits.\nThey cannot be name__btset_0x00000000 though. Use BTFIXUPDEF_XX instead of BTFIXUPDEF_XX_INIT then.\n%s\n", buffer); + exit(1); + } + initvalstr = p + 10; + *p = 0; + } + f = find(buffer[nbase+3], buffer + shift); + if (buffer[nbase+4] == 's') + continue; + switch (buffer[nbase+3]) { + case 'f': + if (initval) { + fprintf(stderr, "Cannot use pre-initalized fixups for calls\n%s\n", buffer); + exit(1); + } + if (!strcmp (sect, "__ksymtab")) { + if (strncmp (buffer + mode+9, "32 ", 10)) { + fprintf(stderr, "BTFIXUP_CALL in EXPORT_SYMBOL results in relocation other than R_SPARC_32\n\%s\n", buffer); + exit(1); + } + } else if (strncmp (buffer + mode+9, "WDISP30 ", 10) && + strncmp (buffer + mode+9, "HI22 ", 10) && + strncmp (buffer + mode+9, "LO10 ", 10)) { + fprintf(stderr, "BTFIXUP_CALL results in relocation other than R_SPARC_WDISP30, R_SPARC_HI22 or R_SPARC_LO10\n%s\n", buffer); + exit(1); + } + break; + case 'b': + if (initval) { + fprintf(stderr, "Cannot use pre-initialized fixups for blackboxes\n%s\n", buffer); + exit(1); + } + if (strncmp (buffer + mode+9, "HI22 ", 10)) { + fprintf(stderr, "BTFIXUP_BLACKBOX results in relocation other than R_SPARC_HI22\n%s\n", buffer); + exit(1); + } + break; + case 's': + if (initval + 0x1000 >= 0x2000) { + fprintf(stderr, "Wrong initializer for SIMM13. Has to be from $fffff000 to $00000fff\n%s\n", buffer); + exit(1); + } + if (strncmp (buffer + mode+9, "13 ", 10)) { + fprintf(stderr, "BTFIXUP_SIMM13 results in relocation other than R_SPARC_13\n%s\n", buffer); + exit(1); + } + break; + case 'a': + if (initval + 0x1000 >= 0x2000 && (initval & 0x3ff)) { + fprintf(stderr, "Wrong initializer for HALF.\n%s\n", buffer); + exit(1); + } + if (strncmp (buffer + mode+9, "13 ", 10)) { + fprintf(stderr, "BTFIXUP_HALF results in relocation other than R_SPARC_13\n%s\n", buffer); + exit(1); + } + break; + case 'h': + if (initval & 0x3ff) { + fprintf(stderr, "Wrong initializer for SETHI. Cannot have set low 10 bits\n%s\n", buffer); + exit(1); + } + if (strncmp (buffer + mode+9, "HI22 ", 10)) { + fprintf(stderr, "BTFIXUP_SETHI results in relocation other than R_SPARC_HI22\n%s\n", buffer); + exit(1); + } + break; + case 'i': + if (initval) { + fprintf(stderr, "Cannot use pre-initalized fixups for INT\n%s\n", buffer); + exit(1); + } + if (strncmp (buffer + mode+9, "HI22 ", 10) && strncmp (buffer + mode+9, "LO10 ", 10)) { + fprintf(stderr, "BTFIXUP_INT results in relocation other than R_SPARC_HI22 and R_SPARC_LO10\n%s\n", buffer); + exit(1); + } + break; + } + if (!f->setinitval) { + f->initval = initval; + if (initvalstr) { + f->initvalstr = strdup(initvalstr); + if (!f->initvalstr) fatal(); + } + f->setinitval = 1; + } else if (f->initval != initval) { + fprintf(stderr, "Btfixup %s previously used with initializer %s which doesn't match with current initializer\n%s\n", + f->name, f->initvalstr ? : "0x00000000", buffer); + exit(1); + } else if (initval && strcmp(f->initvalstr, initvalstr)) { + fprintf(stderr, "Btfixup %s previously used with initializer %s which doesn't match with current initializer.\n" + "Initializers have to match literally as well.\n%s\n", + f->name, f->initvalstr, buffer); + exit(1); + } + offset = strtoul(buffer, &q, 16); + if (q != buffer + mode || (!offset && (mode == 8 ? strncmp (buffer, "00000000 ", 9) : strncmp (buffer, "0000000000000000 ", 17)))) { + fprintf(stderr, "Malformed relocation address in\n%s\n", buffer); + exit(1); + } + for (k = 0, r = f->rel, rr = &f->rel; r; rr = &r->next, r = r->next, k++) + if (r->offset == offset && !strcmp(r->sect, sect)) { + fprintf(stderr, "Ugh. One address has two relocation records\n"); + exit(1); + } + *rr = malloc(sizeof(btfixuprel)); + if (!*rr) fatal(); + (*rr)->offset = offset; + (*rr)->f = NULL; + if (buffer[nbase+3] == 'f') { + lastf = f; + lastfoffset = offset; + lastfrelno = k; + } else if (lastfoffset + 4 == offset) { + (*rr)->f = lastf; + (*rr)->frel = lastfrelno; + } + (*rr)->sect = sect; + (*rr)->next = NULL; + } + printf("! Generated by btfixupprep. Do not edit.\n\n"); + printf("\t.section\t\".data.init\",#alloc,#write\n\t.align\t4\n\n"); + printf("\t.global\t___btfixup_start\n___btfixup_start:\n\n"); + for (i = 0; i < last; i++) { + f = array + i; + printf("\t.global\t___%cs_%s\n", f->type, f->name); + if (f->type == 'f') + printf("___%cs_%s:\n\t.word 0x%08x,0,0,", f->type, f->name, f->type << 24); + else + printf("___%cs_%s:\n\t.word 0x%08x,0,", f->type, f->name, f->type << 24); + for (j = 0, r = f->rel; r != NULL; j++, r = r->next); + if (j) + printf("%d\n\t.word\t", j * 2); + else + printf("0\n"); + for (r = f->rel, j--; r != NULL; j--, r = r->next) { + if (!strcmp (r->sect, ".text")) + printf ("_stext+0x%08lx", r->offset); + else if (!strcmp (r->sect, ".init.text")) + printf ("__init_begin+0x%08lx", r->offset); + else if (!strcmp (r->sect, "__ksymtab")) + printf ("__start___ksymtab+0x%08lx", r->offset); + else if (!strcmp (r->sect, ".fixup")) + printf ("__start___fixup+0x%08lx", r->offset); + else + fatal(); + if (f->type == 'f' || !r->f) + printf (",0"); + else + printf (",___fs_%s+0x%08x", r->f->name, (4 + r->frel*2)*4 + 4); + if (j) printf (","); + else printf ("\n"); + } + printf("\n"); + } + printf("\n\t.global\t___btfixup_end\n___btfixup_end:\n"); + printf("\n\n! Define undefined references\n\n"); + for (i = 0; i < last; i++) { + f = array + i; + if (f->type == 'f') { + printf("\t.global\t___f_%s\n", f->name); + printf("___f_%s:\n", f->name); + } + } + printf("\tretl\n\t nop\n\n"); + for (i = 0; i < last; i++) { + f = array + i; + if (f->type != 'f') { + if (!f->initval) { + printf("\t.global\t___%c_%s\n", f->type, f->name); + printf("___%c_%s = 0\n", f->type, f->name); + } else { + printf("\t.global\t___%c_%s__btset_0x%s\n", f->type, f->name, f->initvalstr); + printf("___%c_%s__btset_0x%s = 0x%08x\n", f->type, f->name, f->initvalstr, f->initval); + } + } + } + printf("\n\n"); + exit(0); +} diff --git a/arch/sparc/boot/piggyback.c b/arch/sparc/boot/piggyback.c new file mode 100644 index 000000000000..6962cc68ed5b --- /dev/null +++ b/arch/sparc/boot/piggyback.c @@ -0,0 +1,137 @@ +/* $Id: piggyback.c,v 1.4 2000/12/05 00:48:57 anton Exp $ + Simple utility to make a single-image install kernel with initial ramdisk + for Sparc tftpbooting without need to set up nfs. + + Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + Pete Zaitcev <zaitcev@yahoo.com> endian fixes for cross-compiles, 2000. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <dirent.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +/* + * Note: run this on an a.out kernel (use elftoaout for it), + * as PROM looks for a.out image only. + */ + +unsigned short ld2(char *p) +{ + return (p[0] << 8) | p[1]; +} + +unsigned int ld4(char *p) +{ + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} + +void st4(char *p, unsigned int x) +{ + p[0] = x >> 24; + p[1] = x >> 16; + p[2] = x >> 8; + p[3] = x; +} + +void usage(void) +{ + /* fs_img.gz is an image of initial ramdisk. */ + fprintf(stderr, "Usage: piggyback vmlinux.aout System.map fs_img.gz\n"); + fprintf(stderr, "\tKernel image will be modified in place.\n"); + exit(1); +} + +void die(char *str) +{ + perror (str); + exit(1); +} + +int main(int argc,char **argv) +{ + static char aout_magic[] = { 0x01, 0x03, 0x01, 0x07 }; + unsigned char buffer[1024], *q, *r; + unsigned int i, j, k, start, end, offset; + FILE *map; + struct stat s; + int image, tail; + + if (argc != 4) usage(); + start = end = 0; + if (stat (argv[3], &s) < 0) die (argv[3]); + map = fopen (argv[2], "r"); + if (!map) die(argv[2]); + while (fgets (buffer, 1024, map)) { + if (!strcmp (buffer + 8, " T start\n") || !strcmp (buffer + 16, " T start\n")) + start = strtoul (buffer, NULL, 16); + else if (!strcmp (buffer + 8, " A end\n") || !strcmp (buffer + 16, " A end\n")) + end = strtoul (buffer, NULL, 16); + } + fclose (map); + if (!start || !end) { + fprintf (stderr, "Could not determine start and end from System.map\n"); + exit(1); + } + if ((image = open(argv[1],O_RDWR)) < 0) die(argv[1]); + if (read(image,buffer,512) != 512) die(argv[1]); + if (memcmp (buffer, "\177ELF", 4) == 0) { + q = buffer + ld4(buffer + 28); + i = ld4(q + 4) + ld4(buffer + 24) - ld4(q + 8); + if (lseek(image,i,0) < 0) die("lseek"); + if (read(image,buffer,512) != 512) die(argv[1]); + j = 0; + } else if (memcmp(buffer, aout_magic, 4) == 0) { + i = j = 32; + } else { + fprintf (stderr, "Not ELF nor a.out. Don't blame me.\n"); + exit(1); + } + k = i; + i += (ld2(buffer + j + 2)<<2) - 512; + if (lseek(image,i,0) < 0) die("lseek"); + if (read(image,buffer,1024) != 1024) die(argv[1]); + for (q = buffer, r = q + 512; q < r; q += 4) { + if (*q == 'H' && q[1] == 'd' && q[2] == 'r' && q[3] == 'S') + break; + } + if (q == r) { + fprintf (stderr, "Couldn't find headers signature in the kernel.\n"); + exit(1); + } + offset = i + (q - buffer) + 10; + if (lseek(image, offset, 0) < 0) die ("lseek"); + + st4(buffer, 0); + st4(buffer + 4, 0x01000000); + st4(buffer + 8, (end + 32 + 4095) & ~4095); + st4(buffer + 12, s.st_size); + + if (write(image,buffer+2,14) != 14) die (argv[1]); + if (lseek(image, k - start + ((end + 32 + 4095) & ~4095), 0) < 0) die ("lseek"); + if ((tail = open(argv[3],O_RDONLY)) < 0) die(argv[3]); + while ((i = read (tail,buffer,1024)) > 0) + if (write(image,buffer,i) != i) die (argv[1]); + if (close(image) < 0) die("close"); + if (close(tail) < 0) die("close"); + return 0; +} diff --git a/arch/sparc/defconfig b/arch/sparc/defconfig new file mode 100644 index 000000000000..a69856263009 --- /dev/null +++ b/arch/sparc/defconfig @@ -0,0 +1,644 @@ +# +# Automatically generated make config: don't edit +# +CONFIG_MMU=y +CONFIG_UID16=y +CONFIG_HIGHMEM=y +CONFIG_GENERIC_ISA_DMA=y + +# +# Code maturity level options +# +CONFIG_EXPERIMENTAL=y +CONFIG_CLEAN_COMPILE=y +CONFIG_STANDALONE=y +CONFIG_BROKEN_ON_SMP=y + +# +# General setup +# +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +# CONFIG_BSD_PROCESS_ACCT is not set +CONFIG_SYSCTL=y +# CONFIG_AUDIT is not set +CONFIG_LOG_BUF_SHIFT=14 +# CONFIG_HOTPLUG is not set +# CONFIG_IKCONFIG is not set +# CONFIG_EMBEDDED is not set +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_ALL is not set +CONFIG_FUTEX=y +CONFIG_EPOLL=y +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set + +# +# Loadable module support +# +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set +CONFIG_OBSOLETE_MODPARM=y +# CONFIG_MODVERSIONS is not set +CONFIG_KMOD=y + +# +# General setup +# +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_SMP is not set +CONFIG_SPARC32=y +CONFIG_SBUS=y +CONFIG_SBUSCHAR=y +CONFIG_SERIAL_CONSOLE=y +CONFIG_SUN_AUXIO=y +CONFIG_SUN_IO=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +CONFIG_SUN_PM=y +# CONFIG_SUN4 is not set +CONFIG_PCI=y +# CONFIG_PCI_LEGACY_PROC is not set +# CONFIG_PCI_NAMES is not set +CONFIG_SUN_OPENPROMFS=m +CONFIG_BINFMT_ELF=y +CONFIG_BINFMT_AOUT=y +CONFIG_BINFMT_MISC=m +CONFIG_SUNOS_EMUL=y + +# +# Parallel port support +# +# CONFIG_PARPORT is not set + +# +# Generic Driver Options +# +# CONFIG_DEBUG_DRIVER is not set + +# +# Graphics support +# +# CONFIG_FB is not set + +# +# Console display driver support +# +# CONFIG_MDA_CONSOLE is not set +# CONFIG_PROM_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y + +# +# Memory Technology Devices (MTD) +# +# CONFIG_MTD is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +CONFIG_SERIAL_SUNCORE=y +CONFIG_SERIAL_SUNZILOG=y +CONFIG_SERIAL_SUNZILOG_CONSOLE=y +CONFIG_SERIAL_SUNSU=y +CONFIG_SERIAL_SUNSU_CONSOLE=y +# CONFIG_SERIAL_SUNSAB is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y + +# +# Misc Linux/SPARC drivers +# +CONFIG_SUN_OPENPROMIO=m +CONFIG_SUN_MOSTEK_RTC=m +# CONFIG_SUN_BPP is not set +# CONFIG_SUN_VIDEOPIX is not set +# CONFIG_SUN_AURORA is not set +# CONFIG_TADPOLE_TS102_UCTRL is not set +# CONFIG_SUN_JSFLASH is not set +CONFIG_APM_RTC_IS_GMT=y +CONFIG_RTC=m + +# +# Block devices +# +# CONFIG_BLK_DEV_FD is not set +# CONFIG_BLK_CPQ_DA is not set +# CONFIG_BLK_CPQ_CISS_DA is not set +# CONFIG_BLK_DEV_DAC960 is not set +# CONFIG_BLK_DEV_UMEM is not set +CONFIG_BLK_DEV_LOOP=m +CONFIG_BLK_DEV_CRYPTOLOOP=m +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_CARMEL is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=4096 +CONFIG_BLK_DEV_INITRD=y + +# +# ATA/ATAPI/MFM/RLL support +# +# CONFIG_IDE is not set + +# +# ISDN subsystem +# +# CONFIG_ISDN is not set + +# +# SCSI device support +# +CONFIG_SCSI=y +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +CONFIG_BLK_DEV_SR=m +# CONFIG_BLK_DEV_SR_VENDOR is not set +CONFIG_CHR_DEV_SG=m + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set + +# +# SCSI Transport Attributes +# +CONFIG_SCSI_SPI_ATTRS=m +# CONFIG_SCSI_FC_ATTRS is not set + +# +# SCSI low-level drivers +# +# CONFIG_BLK_DEV_3W_XXXX_RAID is not set +# CONFIG_SCSI_ACARD is not set +# CONFIG_SCSI_AACRAID is not set +# CONFIG_SCSI_AIC7XXX is not set +# CONFIG_SCSI_AIC7XXX_OLD is not set +# CONFIG_SCSI_AIC79XX is not set +# CONFIG_SCSI_DPT_I2O is not set +# CONFIG_SCSI_ADVANSYS is not set +# CONFIG_SCSI_MEGARAID is not set +# CONFIG_SCSI_SATA is not set +# CONFIG_SCSI_BUSLOGIC is not set +# CONFIG_SCSI_DMX3191D is not set +# CONFIG_SCSI_EATA is not set +# CONFIG_SCSI_EATA_PIO is not set +# CONFIG_SCSI_FUTURE_DOMAIN is not set +# CONFIG_SCSI_GDTH is not set +# CONFIG_SCSI_IPS is not set +# CONFIG_SCSI_INIA100 is not set +# CONFIG_SCSI_SYM53C8XX_2 is not set +# CONFIG_SCSI_IPR is not set +# CONFIG_SCSI_QLOGIC_ISP is not set +# CONFIG_SCSI_QLOGIC_FC is not set +# CONFIG_SCSI_QLOGIC_1280 is not set +CONFIG_SCSI_QLOGICPTI=m +CONFIG_SCSI_QLA2XXX=y +# CONFIG_SCSI_QLA21XX is not set +# CONFIG_SCSI_QLA22XX is not set +# CONFIG_SCSI_QLA2300 is not set +# CONFIG_SCSI_QLA2322 is not set +# CONFIG_SCSI_QLA6312 is not set +# CONFIG_SCSI_QLA6322 is not set +# CONFIG_SCSI_DC395x is not set +# CONFIG_SCSI_DC390T is not set +# CONFIG_SCSI_NSP32 is not set +# CONFIG_SCSI_DEBUG is not set +CONFIG_SCSI_SUNESP=y + +# +# Fibre Channel support +# +# CONFIG_FC4 is not set + +# +# Multi-device support (RAID and LVM) +# +# CONFIG_MD is not set + +# +# Networking support +# +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +# CONFIG_PACKET_MMAP is not set +CONFIG_NETLINK_DEV=y +CONFIG_UNIX=y +CONFIG_NET_KEY=m +CONFIG_INET=y +# CONFIG_IP_MULTICAST is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_IP_PNP_BOOTP is not set +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_IPV6=m +CONFIG_IPV6_PRIVACY=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_TUNNEL=m +# CONFIG_NETFILTER is not set +CONFIG_XFRM=y +CONFIG_XFRM_USER=m + +# +# SCTP Configuration (EXPERIMENTAL) +# +CONFIG_IP_SCTP=m +# CONFIG_SCTP_DBG_MSG is not set +CONFIG_SCTP_DBG_OBJCNT=y +# CONFIG_SCTP_HMAC_NONE is not set +# CONFIG_SCTP_HMAC_SHA1 is not set +CONFIG_SCTP_HMAC_MD5=y +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_NET_DIVERT is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_HW_FLOWCONTROL is not set + +# +# QoS and/or fair queueing +# +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +CONFIG_NET_PKTGEN=m +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_HAMRADIO is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +CONFIG_NETDEVICES=y +CONFIG_DUMMY=m +# CONFIG_BONDING is not set +# CONFIG_EQUALIZER is not set +CONFIG_TUN=m +# CONFIG_ETHERTAP is not set + +# +# ARCnet devices +# +# CONFIG_ARCNET is not set + +# +# Ethernet (10 or 100Mbit) +# +CONFIG_NET_ETHERNET=y +CONFIG_MII=m +CONFIG_SUNLANCE=y +CONFIG_HAPPYMEAL=m +CONFIG_SUNBMAC=m +CONFIG_SUNQE=m +# CONFIG_SUNGEM is not set +# CONFIG_NET_VENDOR_3COM is not set + +# +# Tulip family network device support +# +# CONFIG_NET_TULIP is not set +# CONFIG_HP100 is not set +# CONFIG_NET_PCI is not set + +# +# Ethernet (1000 Mbit) +# +# CONFIG_ACENIC is not set +# CONFIG_DL2K is not set +# CONFIG_E1000 is not set +# CONFIG_MYRI_SBUS is not set +# CONFIG_NS83820 is not set +# CONFIG_HAMACHI is not set +# CONFIG_YELLOWFIN is not set +# CONFIG_R8169 is not set +# CONFIG_SK98LIN is not set +# CONFIG_TIGON3 is not set + +# +# Ethernet (10000 Mbit) +# +# CONFIG_IXGB is not set +# CONFIG_S2IO is not set + +# +# Token Ring devices +# +# CONFIG_TR is not set + +# +# Wireless LAN (non-hamradio) +# +# CONFIG_NET_RADIO is not set + +# +# Wan interfaces +# +# CONFIG_WAN is not set +# CONFIG_FDDI is not set +# CONFIG_HIPPI is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NET_FC is not set +# CONFIG_SHAPER is not set +# CONFIG_NETCONSOLE is not set + +# +# Unix98 PTY support +# +CONFIG_UNIX98_PTYS=y +CONFIG_UNIX98_PTY_COUNT=256 + +# +# Input device support +# +CONFIG_INPUT=y + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_PSAUX=y +CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 +CONFIG_INPUT_JOYDEV=m +# CONFIG_INPUT_TSDEV is not set +CONFIG_INPUT_EVDEV=m +CONFIG_INPUT_EVBUG=m + +# +# Input I/O drivers +# +# CONFIG_GAMEPORT is not set +CONFIG_SOUND_GAMEPORT=y +CONFIG_SERIO=m +# CONFIG_SERIO_I8042 is not set +CONFIG_SERIO_SERPORT=m +# CONFIG_SERIO_CT82C710 is not set +# CONFIG_SERIO_PCIPS2 is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +CONFIG_KEYBOARD_ATKBD=m +CONFIG_KEYBOARD_SUNKBD=m +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=m +CONFIG_MOUSE_SERIAL=m +# CONFIG_MOUSE_VSXXXAA is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TOUCHSCREEN is not set +# CONFIG_INPUT_MISC is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT2_FS_POSIX_ACL=y +CONFIG_EXT2_FS_SECURITY=y +# CONFIG_EXT3_FS is not set +# CONFIG_JBD is not set +CONFIG_FS_MBCACHE=y +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +CONFIG_FS_POSIX_ACL=y +CONFIG_XFS_FS=m +CONFIG_XFS_RT=y +CONFIG_XFS_QUOTA=y +CONFIG_XFS_SECURITY=y +CONFIG_XFS_POSIX_ACL=y +# CONFIG_MINIX_FS is not set +CONFIG_ROMFS_FS=m +# CONFIG_QUOTA is not set +CONFIG_QUOTACTL=y +CONFIG_AUTOFS_FS=m +CONFIG_AUTOFS4_FS=m + +# +# CD-ROM/DVD Filesystems +# +CONFIG_ISO9660_FS=m +# CONFIG_JOLIET is not set +# CONFIG_ZISOFS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +# CONFIG_FAT_FS is not set +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_KCORE=y +CONFIG_SYSFS=y +# CONFIG_DEVFS_FS is not set +CONFIG_DEVPTS_FS_XATTR=y +# CONFIG_DEVPTS_FS_SECURITY is not set +# CONFIG_TMPFS is not set +# CONFIG_HUGETLB_PAGE is not set +CONFIG_RAMFS=y + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +CONFIG_BEFS_FS=m +# CONFIG_BEFS_DEBUG is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +# CONFIG_CRAMFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set + +# +# Network File Systems +# +CONFIG_NFS_FS=y +# CONFIG_NFS_V3 is not set +# CONFIG_NFS_V4 is not set +# CONFIG_NFS_DIRECTIO is not set +# CONFIG_NFSD is not set +CONFIG_ROOT_NFS=y +CONFIG_LOCKD=y +# CONFIG_EXPORTFS is not set +CONFIG_SUNRPC=y +CONFIG_SUNRPC_GSS=m +CONFIG_RPCSEC_GSS_KRB5=m +# CONFIG_SMB_FS is not set +CONFIG_CIFS=m +# CONFIG_CIFS_STATS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +CONFIG_AFS_FS=m +CONFIG_RXRPC=m + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_SUN_PARTITION=y + +# +# Native Language Support +# +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="iso8859-1" +# CONFIG_NLS_CODEPAGE_437 is not set +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ISO8859_1 is not set +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +# CONFIG_NLS_UTF8 is not set + +# +# Sound +# +# CONFIG_SOUND is not set + +# +# USB support +# +# CONFIG_USB is not set + +# +# USB Gadget Support +# +# CONFIG_USB_GADGET is not set + +# +# Watchdog Cards +# +# CONFIG_WATCHDOG is not set + +# +# Kernel hacking +# +CONFIG_DEBUG_KERNEL=y +# CONFIG_DEBUG_STACK_USAGE is not set +# CONFIG_DEBUG_SLAB is not set +CONFIG_MAGIC_SYSRQ=y +# CONFIG_DEBUG_SPINLOCK is not set +# CONFIG_DEBUG_HIGHMEM is not set +# CONFIG_DEBUG_SPINLOCK_SLEEP is not set +# CONFIG_DEBUG_BUGVERBOSE is not set + +# +# Security options +# +# CONFIG_SECURITY is not set + +# +# Cryptographic options +# +CONFIG_CRYPTO=y +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_NULL=m +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA256=m +CONFIG_CRYPTO_SHA512=m +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_BLOWFISH=m +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_SERPENT=m +CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_CAST6=m +CONFIG_CRYPTO_ARC4=m +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_MICHAEL_MIC=m +CONFIG_CRYPTO_CRC32C=m +# CONFIG_CRYPTO_TEST is not set + +# +# Library routines +# +CONFIG_CRC32=y +CONFIG_LIBCRC32C=m +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile new file mode 100644 index 000000000000..3d22ba2af01c --- /dev/null +++ b/arch/sparc/kernel/Makefile @@ -0,0 +1,27 @@ +# $Id: Makefile,v 1.62 2000/12/15 00:41:17 davem Exp $ +# Makefile for the linux kernel. +# + +extra-y := head.o init_task.o vmlinux.lds + +EXTRA_AFLAGS := -ansi + +IRQ_OBJS := irq.o sun4m_irq.o sun4c_irq.o sun4d_irq.o +obj-y := entry.o wof.o wuf.o etrap.o rtrap.o traps.o $(IRQ_OBJS) \ + process.o signal.o ioport.o setup.o idprom.o \ + sys_sparc.o sunos_asm.o systbls.o \ + time.o windows.o cpu.o devices.o sclow.o \ + tadpole.o tick14.o ptrace.o sys_solaris.o \ + unaligned.o muldiv.o semaphore.o + +obj-$(CONFIG_PCI) += pcic.o +obj-$(CONFIG_SUN4) += sun4setup.o +obj-$(CONFIG_SMP) += trampoline.o smp.o sun4m_smp.o sun4d_smp.o +obj-$(CONFIG_SUN_AUXIO) += auxio.o +obj-$(CONFIG_PCI) += ebus.o +obj-$(CONFIG_SUN_PM) += apc.o pmc.o +obj-$(CONFIG_MODULES) += module.o sparc_ksyms.o + +ifdef CONFIG_SUNOS_EMUL +obj-y += sys_sunos.o sunos_ioctl.o +endif diff --git a/arch/sparc/kernel/apc.c b/arch/sparc/kernel/apc.c new file mode 100644 index 000000000000..406dd94afb45 --- /dev/null +++ b/arch/sparc/kernel/apc.c @@ -0,0 +1,186 @@ +/* apc - Driver implementation for power management functions + * of Aurora Personality Chip (APC) on SPARCstation-4/5 and + * derivatives. + * + * Copyright (c) 2002 Eric Brower (ebrower@usa.net) + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/pm.h> + +#include <asm/io.h> +#include <asm/sbus.h> +#include <asm/oplib.h> +#include <asm/uaccess.h> +#include <asm/auxio.h> +#include <asm/apc.h> + +/* Debugging + * + * #define APC_DEBUG_LED + */ + +#define APC_MINOR MISC_DYNAMIC_MINOR +#define APC_OBPNAME "power-management" +#define APC_DEVNAME "apc" + +volatile static u8 __iomem *regs; +static int apc_regsize; +static int apc_no_idle __initdata = 0; + +#define apc_readb(offs) (sbus_readb(regs+offs)) +#define apc_writeb(val, offs) (sbus_writeb(val, regs+offs)) + +/* Specify "apc=noidle" on the kernel command line to + * disable APC CPU standby support. Certain prototype + * systems (SPARCstation-Fox) do not play well with APC + * CPU idle, so disable this if your system has APC and + * crashes randomly. + */ +static int __init apc_setup(char *str) +{ + if(!strncmp(str, "noidle", strlen("noidle"))) { + apc_no_idle = 1; + return 1; + } + return 0; +} +__setup("apc=", apc_setup); + +/* + * CPU idle callback function + * See .../arch/sparc/kernel/process.c + */ +void apc_swift_idle(void) +{ +#ifdef APC_DEBUG_LED + set_auxio(0x00, AUXIO_LED); +#endif + + apc_writeb(apc_readb(APC_IDLE_REG) | APC_IDLE_ON, APC_IDLE_REG); + +#ifdef APC_DEBUG_LED + set_auxio(AUXIO_LED, 0x00); +#endif +} + +static inline void apc_free(void) +{ + sbus_iounmap(regs, apc_regsize); +} + +static int apc_open(struct inode *inode, struct file *f) +{ + return 0; +} + +static int apc_release(struct inode *inode, struct file *f) +{ + return 0; +} + +static int apc_ioctl(struct inode *inode, struct file *f, + unsigned int cmd, unsigned long __arg) +{ + __u8 inarg, __user *arg; + + arg = (__u8 __user *) __arg; + switch (cmd) { + case APCIOCGFANCTL: + if (put_user(apc_readb(APC_FANCTL_REG) & APC_REGMASK, arg)) + return -EFAULT; + break; + + case APCIOCGCPWR: + if (put_user(apc_readb(APC_CPOWER_REG) & APC_REGMASK, arg)) + return -EFAULT; + break; + + case APCIOCGBPORT: + if (put_user(apc_readb(APC_BPORT_REG) & APC_BPMASK, arg)) + return -EFAULT; + break; + + case APCIOCSFANCTL: + if (get_user(inarg, arg)) + return -EFAULT; + apc_writeb(inarg & APC_REGMASK, APC_FANCTL_REG); + break; + case APCIOCSCPWR: + if (get_user(inarg, arg)) + return -EFAULT; + apc_writeb(inarg & APC_REGMASK, APC_CPOWER_REG); + break; + case APCIOCSBPORT: + if (get_user(inarg, arg)) + return -EFAULT; + apc_writeb(inarg & APC_BPMASK, APC_BPORT_REG); + break; + default: + return -EINVAL; + }; + + return 0; +} + +static struct file_operations apc_fops = { + .ioctl = apc_ioctl, + .open = apc_open, + .release = apc_release, +}; + +static struct miscdevice apc_miscdev = { APC_MINOR, APC_DEVNAME, &apc_fops }; + +static int __init apc_probe(void) +{ + struct sbus_bus *sbus = NULL; + struct sbus_dev *sdev = NULL; + int iTmp = 0; + + for_each_sbus(sbus) { + for_each_sbusdev(sdev, sbus) { + if (!strcmp(sdev->prom_name, APC_OBPNAME)) { + goto sbus_done; + } + } + } + +sbus_done: + if (!sdev) { + return -ENODEV; + } + + apc_regsize = sdev->reg_addrs[0].reg_size; + regs = sbus_ioremap(&sdev->resource[0], 0, + apc_regsize, APC_OBPNAME); + if(!regs) { + printk(KERN_ERR "%s: unable to map registers\n", APC_DEVNAME); + return -ENODEV; + } + + iTmp = misc_register(&apc_miscdev); + if (iTmp != 0) { + printk(KERN_ERR "%s: unable to register device\n", APC_DEVNAME); + apc_free(); + return -ENODEV; + } + + /* Assign power management IDLE handler */ + if(!apc_no_idle) + pm_idle = apc_swift_idle; + + printk(KERN_INFO "%s: power management initialized%s\n", + APC_DEVNAME, apc_no_idle ? " (CPU idle disabled)" : ""); + return 0; +} + +/* This driver is not critical to the boot process + * and is easiest to ioremap when SBus is already + * initialized, so we install ourselves thusly: + */ +__initcall(apc_probe); + diff --git a/arch/sparc/kernel/asm-offsets.c b/arch/sparc/kernel/asm-offsets.c new file mode 100644 index 000000000000..1f55231f07de --- /dev/null +++ b/arch/sparc/kernel/asm-offsets.c @@ -0,0 +1,45 @@ +/* + * This program is used to generate definitions needed by + * assembly language modules. + * + * We use the technique used in the OSF Mach kernel code: + * generate asm statements containing #defines, + * compile this file to assembler, and then extract the + * #defines from the assembly-language output. + * + * On sparc, thread_info data is static and TI_XXX offsets are computed by hand. + */ + +#include <linux/config.h> +#include <linux/sched.h> +// #include <linux/mm.h> + +#define DEFINE(sym, val) \ + asm volatile("\n->" #sym " %0 " #val : : "i" (val)) + +#define BLANK() asm volatile("\n->" : : ) + +int foo(void) +{ + DEFINE(AOFF_task_thread, offsetof(struct task_struct, thread)); + BLANK(); + /* XXX This is the stuff for sclow.S, kill it. */ + DEFINE(AOFF_task_pid, offsetof(struct task_struct, pid)); + DEFINE(AOFF_task_uid, offsetof(struct task_struct, uid)); + DEFINE(AOFF_task_gid, offsetof(struct task_struct, gid)); + DEFINE(AOFF_task_euid, offsetof(struct task_struct, euid)); + DEFINE(AOFF_task_egid, offsetof(struct task_struct, egid)); + /* DEFINE(THREAD_INFO, offsetof(struct task_struct, thread_info)); */ + DEFINE(ASIZ_task_uid, sizeof(current->uid)); + DEFINE(ASIZ_task_gid, sizeof(current->gid)); + DEFINE(ASIZ_task_euid, sizeof(current->euid)); + DEFINE(ASIZ_task_egid, sizeof(current->egid)); + BLANK(); + DEFINE(AOFF_thread_fork_kpsr, + offsetof(struct thread_struct, fork_kpsr)); + BLANK(); + DEFINE(AOFF_mm_context, offsetof(struct mm_struct, context)); + + /* DEFINE(NUM_USER_SEGMENTS, TASK_SIZE>>28); */ + return 0; +} diff --git a/arch/sparc/kernel/auxio.c b/arch/sparc/kernel/auxio.c new file mode 100644 index 000000000000..d3b3648362c0 --- /dev/null +++ b/arch/sparc/kernel/auxio.c @@ -0,0 +1,138 @@ +/* auxio.c: Probing for the Sparc AUXIO register at boot time. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/stddef.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/spinlock.h> +#include <asm/oplib.h> +#include <asm/io.h> +#include <asm/auxio.h> +#include <asm/string.h> /* memset(), Linux has no bzero() */ + +/* Probe and map in the Auxiliary I/O register */ + +/* auxio_register is not static because it is referenced + * in entry.S::floppy_tdone + */ +void __iomem *auxio_register = NULL; +static DEFINE_SPINLOCK(auxio_lock); + +void __init auxio_probe(void) +{ + int node, auxio_nd; + struct linux_prom_registers auxregs[1]; + struct resource r; + + switch (sparc_cpu_model) { + case sun4d: + case sun4: + return; + default: + break; + } + node = prom_getchild(prom_root_node); + auxio_nd = prom_searchsiblings(node, "auxiliary-io"); + if(!auxio_nd) { + node = prom_searchsiblings(node, "obio"); + node = prom_getchild(node); + auxio_nd = prom_searchsiblings(node, "auxio"); + if(!auxio_nd) { +#ifdef CONFIG_PCI + /* There may be auxio on Ebus */ + return; +#else + if(prom_searchsiblings(node, "leds")) { + /* VME chassis sun4m machine, no auxio exists. */ + return; + } + prom_printf("Cannot find auxio node, cannot continue...\n"); + prom_halt(); +#endif + } + } + if(prom_getproperty(auxio_nd, "reg", (char *) auxregs, sizeof(auxregs)) <= 0) + return; + prom_apply_obio_ranges(auxregs, 0x1); + /* Map the register both read and write */ + r.flags = auxregs[0].which_io & 0xF; + r.start = auxregs[0].phys_addr; + r.end = auxregs[0].phys_addr + auxregs[0].reg_size - 1; + auxio_register = sbus_ioremap(&r, 0, auxregs[0].reg_size, "auxio"); + /* Fix the address on sun4m and sun4c. */ + if((((unsigned long) auxregs[0].phys_addr) & 3) == 3 || + sparc_cpu_model == sun4c) + auxio_register += (3 - ((unsigned long)auxio_register & 3)); + + set_auxio(AUXIO_LED, 0); +} + +unsigned char get_auxio(void) +{ + if(auxio_register) + return sbus_readb(auxio_register); + return 0; +} + +void set_auxio(unsigned char bits_on, unsigned char bits_off) +{ + unsigned char regval; + unsigned long flags; + spin_lock_irqsave(&auxio_lock, flags); + switch(sparc_cpu_model) { + case sun4c: + regval = sbus_readb(auxio_register); + sbus_writeb(((regval | bits_on) & ~bits_off) | AUXIO_ORMEIN, + auxio_register); + break; + case sun4m: + if(!auxio_register) + break; /* VME chassic sun4m, no auxio. */ + regval = sbus_readb(auxio_register); + sbus_writeb(((regval | bits_on) & ~bits_off) | AUXIO_ORMEIN4M, + auxio_register); + break; + case sun4d: + break; + default: + panic("Can't set AUXIO register on this machine."); + }; + spin_unlock_irqrestore(&auxio_lock, flags); +} + + +/* sun4m power control register (AUXIO2) */ + +volatile unsigned char * auxio_power_register = NULL; + +void __init auxio_power_probe(void) +{ + struct linux_prom_registers regs; + int node; + struct resource r; + + /* Attempt to find the sun4m power control node. */ + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(node, "obio"); + node = prom_getchild(node); + node = prom_searchsiblings(node, "power"); + if (node == 0 || node == -1) + return; + + /* Map the power control register. */ + if (prom_getproperty(node, "reg", (char *)®s, sizeof(regs)) <= 0) + return; + prom_apply_obio_ranges(®s, 1); + memset(&r, 0, sizeof(r)); + r.flags = regs.which_io & 0xF; + r.start = regs.phys_addr; + r.end = regs.phys_addr + regs.reg_size - 1; + auxio_power_register = (unsigned char *) sbus_ioremap(&r, 0, + regs.reg_size, "auxpower"); + + /* Display a quick message on the console. */ + if (auxio_power_register) + printk(KERN_INFO "Power off control detected.\n"); +} diff --git a/arch/sparc/kernel/cpu.c b/arch/sparc/kernel/cpu.c new file mode 100644 index 000000000000..6a4ebc62193e --- /dev/null +++ b/arch/sparc/kernel/cpu.c @@ -0,0 +1,168 @@ +/* cpu.c: Dinky routines to look for the kind of Sparc cpu + * we are on. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/threads.h> +#include <asm/oplib.h> +#include <asm/page.h> +#include <asm/head.h> +#include <asm/psr.h> +#include <asm/mbus.h> +#include <asm/cpudata.h> + +DEFINE_PER_CPU(cpuinfo_sparc, __cpu_data) = { 0 }; + +struct cpu_iu_info { + int psr_impl; + int psr_vers; + char* cpu_name; /* should be enough I hope... */ +}; + +struct cpu_fp_info { + int psr_impl; + int fp_vers; + char* fp_name; +}; + +/* In order to get the fpu type correct, you need to take the IDPROM's + * machine type value into consideration too. I will fix this. + */ +struct cpu_fp_info linux_sparc_fpu[] = { + { 0, 0, "Fujitsu MB86910 or Weitek WTL1164/5"}, + { 0, 1, "Fujitsu MB86911 or Weitek WTL1164/5 or LSI L64831"}, + { 0, 2, "LSI Logic L64802 or Texas Instruments ACT8847"}, + /* SparcStation SLC, SparcStation1 */ + { 0, 3, "Weitek WTL3170/2"}, + /* SPARCstation-5 */ + { 0, 4, "Lsi Logic/Meiko L64804 or compatible"}, + { 0, 5, "reserved"}, + { 0, 6, "reserved"}, + { 0, 7, "No FPU"}, + { 1, 0, "ROSS HyperSparc combined IU/FPU"}, + { 1, 1, "Lsi Logic L64814"}, + { 1, 2, "Texas Instruments TMS390-C602A"}, + { 1, 3, "Cypress CY7C602 FPU"}, + { 1, 4, "reserved"}, + { 1, 5, "reserved"}, + { 1, 6, "reserved"}, + { 1, 7, "No FPU"}, + { 2, 0, "BIT B5010 or B5110/20 or B5210"}, + { 2, 1, "reserved"}, + { 2, 2, "reserved"}, + { 2, 3, "reserved"}, + { 2, 4, "reserved"}, + { 2, 5, "reserved"}, + { 2, 6, "reserved"}, + { 2, 7, "No FPU"}, + /* SuperSparc 50 module */ + { 4, 0, "SuperSparc on-chip FPU"}, + /* SparcClassic */ + { 4, 4, "TI MicroSparc on chip FPU"}, + { 5, 0, "Matsushita MN10501"}, + { 5, 1, "reserved"}, + { 5, 2, "reserved"}, + { 5, 3, "reserved"}, + { 5, 4, "reserved"}, + { 5, 5, "reserved"}, + { 5, 6, "reserved"}, + { 5, 7, "No FPU"}, + { 9, 3, "Fujitsu or Weitek on-chip FPU"}, +}; + +#define NSPARCFPU (sizeof(linux_sparc_fpu)/sizeof(struct cpu_fp_info)) + +struct cpu_iu_info linux_sparc_chips[] = { + /* Sun4/100, 4/200, SLC */ + { 0, 0, "Fujitsu MB86900/1A or LSI L64831 SparcKIT-40"}, + /* borned STP1012PGA */ + { 0, 4, "Fujitsu MB86904"}, + { 0, 5, "Fujitsu TurboSparc MB86907"}, + /* SparcStation2, SparcServer 490 & 690 */ + { 1, 0, "LSI Logic Corporation - L64811"}, + /* SparcStation2 */ + { 1, 1, "Cypress/ROSS CY7C601"}, + /* Embedded controller */ + { 1, 3, "Cypress/ROSS CY7C611"}, + /* Ross Technologies HyperSparc */ + { 1, 0xf, "ROSS HyperSparc RT620"}, + { 1, 0xe, "ROSS HyperSparc RT625 or RT626"}, + /* ECL Implementation, CRAY S-MP Supercomputer... AIEEE! */ + /* Someone please write the code to support this beast! ;) */ + { 2, 0, "Bipolar Integrated Technology - B5010"}, + { 3, 0, "LSI Logic Corporation - unknown-type"}, + { 4, 0, "Texas Instruments, Inc. - SuperSparc-(II)"}, + /* SparcClassic -- borned STP1010TAB-50*/ + { 4, 1, "Texas Instruments, Inc. - MicroSparc"}, + { 4, 2, "Texas Instruments, Inc. - MicroSparc II"}, + { 4, 3, "Texas Instruments, Inc. - SuperSparc 51"}, + { 4, 4, "Texas Instruments, Inc. - SuperSparc 61"}, + { 4, 5, "Texas Instruments, Inc. - unknown"}, + { 5, 0, "Matsushita - MN10501"}, + { 6, 0, "Philips Corporation - unknown"}, + { 7, 0, "Harvest VLSI Design Center, Inc. - unknown"}, + /* Gallium arsenide 200MHz, BOOOOGOOOOMIPS!!! */ + { 8, 0, "Systems and Processes Engineering Corporation (SPEC)"}, + { 9, 0, "Fujitsu or Weitek Power-UP"}, + { 9, 1, "Fujitsu or Weitek Power-UP"}, + { 9, 2, "Fujitsu or Weitek Power-UP"}, + { 9, 3, "Fujitsu or Weitek Power-UP"}, + { 0xa, 0, "UNKNOWN CPU-VENDOR/TYPE"}, + { 0xb, 0, "UNKNOWN CPU-VENDOR/TYPE"}, + { 0xc, 0, "UNKNOWN CPU-VENDOR/TYPE"}, + { 0xd, 0, "UNKNOWN CPU-VENDOR/TYPE"}, + { 0xe, 0, "UNKNOWN CPU-VENDOR/TYPE"}, + { 0xf, 0, "UNKNOWN CPU-VENDOR/TYPE"}, +}; + +#define NSPARCCHIPS (sizeof(linux_sparc_chips)/sizeof(struct cpu_iu_info)) + +char *sparc_cpu_type; +char *sparc_fpu_type; + +unsigned int fsr_storage; + +void __init cpu_probe(void) +{ + int psr_impl, psr_vers, fpu_vers; + int i, psr; + + psr_impl = ((get_psr()>>28)&0xf); + psr_vers = ((get_psr()>>24)&0xf); + + psr = get_psr(); + put_psr(psr | PSR_EF); + fpu_vers = ((get_fsr()>>17)&0x7); + put_psr(psr); + + for(i = 0; i<NSPARCCHIPS; i++) { + if(linux_sparc_chips[i].psr_impl == psr_impl) + if(linux_sparc_chips[i].psr_vers == psr_vers) { + sparc_cpu_type = linux_sparc_chips[i].cpu_name; + break; + } + } + + if(i==NSPARCCHIPS) + printk("DEBUG: psr.impl = 0x%x psr.vers = 0x%x\n", psr_impl, + psr_vers); + + for(i = 0; i<NSPARCFPU; i++) { + if(linux_sparc_fpu[i].psr_impl == psr_impl) + if(linux_sparc_fpu[i].fp_vers == fpu_vers) { + sparc_fpu_type = linux_sparc_fpu[i].fp_name; + break; + } + } + + if(i == NSPARCFPU) { + printk("DEBUG: psr.impl = 0x%x fsr.vers = 0x%x\n", psr_impl, + fpu_vers); + sparc_fpu_type = linux_sparc_fpu[31].fp_name; + } +} diff --git a/arch/sparc/kernel/devices.c b/arch/sparc/kernel/devices.c new file mode 100644 index 000000000000..fcb0c049c3fe --- /dev/null +++ b/arch/sparc/kernel/devices.c @@ -0,0 +1,160 @@ +/* devices.c: Initial scan of the prom device tree for important + * Sparc device nodes which we need to find. + * + * This is based on the sparc64 version, but sun4m doesn't always use + * the hardware MIDs, so be careful. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/threads.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/errno.h> + +#include <asm/page.h> +#include <asm/oplib.h> +#include <asm/smp.h> +#include <asm/system.h> +#include <asm/cpudata.h> + +extern void cpu_probe(void); +extern void clock_stop_probe(void); /* tadpole.c */ +extern void sun4c_probe_memerr_reg(void); + +static char *cpu_mid_prop(void) +{ + if (sparc_cpu_model == sun4d) + return "cpu-id"; + return "mid"; +} + +static int check_cpu_node(int nd, int *cur_inst, + int (*compare)(int, int, void *), void *compare_arg, + int *prom_node, int *mid) +{ + char node_str[128]; + + prom_getstring(nd, "device_type", node_str, sizeof(node_str)); + if (strcmp(node_str, "cpu")) + return -ENODEV; + + if (!compare(nd, *cur_inst, compare_arg)) { + if (prom_node) + *prom_node = nd; + if (mid) { + *mid = prom_getintdefault(nd, cpu_mid_prop(), 0); + if (sparc_cpu_model == sun4m) + *mid &= 3; + } + return 0; + } + + (*cur_inst)++; + + return -ENODEV; +} + +static int __cpu_find_by(int (*compare)(int, int, void *), void *compare_arg, + int *prom_node, int *mid) +{ + int nd, cur_inst, err; + + nd = prom_root_node; + cur_inst = 0; + + err = check_cpu_node(nd, &cur_inst, compare, compare_arg, + prom_node, mid); + if (!err) + return 0; + + nd = prom_getchild(nd); + while ((nd = prom_getsibling(nd)) != 0) { + err = check_cpu_node(nd, &cur_inst, compare, compare_arg, + prom_node, mid); + if (!err) + return 0; + } + + return -ENODEV; +} + +static int cpu_instance_compare(int nd, int instance, void *_arg) +{ + int desired_instance = (int) _arg; + + if (instance == desired_instance) + return 0; + return -ENODEV; +} + +int cpu_find_by_instance(int instance, int *prom_node, int *mid) +{ + return __cpu_find_by(cpu_instance_compare, (void *)instance, + prom_node, mid); +} + +static int cpu_mid_compare(int nd, int instance, void *_arg) +{ + int desired_mid = (int) _arg; + int this_mid; + + this_mid = prom_getintdefault(nd, cpu_mid_prop(), 0); + if (this_mid == desired_mid + || (sparc_cpu_model == sun4m && (this_mid & 3) == desired_mid)) + return 0; + return -ENODEV; +} + +int cpu_find_by_mid(int mid, int *prom_node) +{ + return __cpu_find_by(cpu_mid_compare, (void *)mid, + prom_node, NULL); +} + +/* sun4m uses truncated mids since we base the cpuid on the ttable/irqset + * address (0-3). This gives us the true hardware mid, which might have + * some other bits set. On 4d hardware and software mids are the same. + */ +int cpu_get_hwmid(int prom_node) +{ + return prom_getintdefault(prom_node, cpu_mid_prop(), -ENODEV); +} + +void __init device_scan(void) +{ + prom_printf("Booting Linux...\n"); + +#ifndef CONFIG_SMP + { + int err, cpu_node; + err = cpu_find_by_instance(0, &cpu_node, NULL); + if (err) { + /* Probably a sun4e, Sun is trying to trick us ;-) */ + prom_printf("No cpu nodes, cannot continue\n"); + prom_halt(); + } + cpu_data(0).clock_tick = prom_getintdefault(cpu_node, + "clock-frequency", + 0); + } +#endif /* !CONFIG_SMP */ + + cpu_probe(); +#ifdef CONFIG_SUN_AUXIO + { + extern void auxio_probe(void); + extern void auxio_power_probe(void); + auxio_probe(); + auxio_power_probe(); + } +#endif + clock_stop_probe(); + + if (ARCH_SUN4C_SUN4) + sun4c_probe_memerr_reg(); + + return; +} diff --git a/arch/sparc/kernel/ebus.c b/arch/sparc/kernel/ebus.c new file mode 100644 index 000000000000..1754192c69d0 --- /dev/null +++ b/arch/sparc/kernel/ebus.c @@ -0,0 +1,361 @@ +/* $Id: ebus.c,v 1.20 2002/01/05 01:13:43 davem Exp $ + * ebus.c: PCI to EBus bridge device. + * + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + * + * Adopted for sparc by V. Roganov and G. Raiko. + * Fixes for different platforms by Pete Zaitcev. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <asm/system.h> +#include <asm/page.h> +#include <asm/pbm.h> +#include <asm/ebus.h> +#include <asm/io.h> +#include <asm/oplib.h> +#include <asm/bpp.h> + +struct linux_ebus *ebus_chain = 0; + +/* We are together with pcic.c under CONFIG_PCI. */ +extern unsigned int pcic_pin_to_irq(unsigned int, char *name); + +/* + * IRQ Blacklist + * Here we list PROMs and systems that are known to supply crap as IRQ numbers. + */ +struct ebus_device_irq { + char *name; + unsigned int pin; +}; + +struct ebus_system_entry { + char *esname; + struct ebus_device_irq *ipt; +}; + +static struct ebus_device_irq je1_1[] = { + { "8042", 3 }, + { "SUNW,CS4231", 0 }, + { "parallel", 0 }, + { "se", 2 }, + { 0, 0 } +}; + +/* + * Gleb's JE1 supplied reasonable pin numbers, but mine did not (OBP 2.32). + * Blacklist the sucker... Note that Gleb's system will work. + */ +static struct ebus_system_entry ebus_blacklist[] = { + { "SUNW,JavaEngine1", je1_1 }, + { 0, 0 } +}; + +static struct ebus_device_irq *ebus_blackp = NULL; + +/* + */ +static inline unsigned long ebus_alloc(size_t size) +{ + return (unsigned long)kmalloc(size, GFP_ATOMIC); +} + +/* + */ +int __init ebus_blacklist_irq(char *name) +{ + struct ebus_device_irq *dp; + + if ((dp = ebus_blackp) != NULL) { + for (; dp->name != NULL; dp++) { + if (strcmp(name, dp->name) == 0) { + return pcic_pin_to_irq(dp->pin, name); + } + } + } + return 0; +} + +void __init fill_ebus_child(int node, struct linux_prom_registers *preg, + struct linux_ebus_child *dev) +{ + int regs[PROMREG_MAX]; + int irqs[PROMREG_MAX]; + char lbuf[128]; + int i, len; + + dev->prom_node = node; + prom_getstring(node, "name", lbuf, sizeof(lbuf)); + strcpy(dev->prom_name, lbuf); + + len = prom_getproperty(node, "reg", (void *)regs, sizeof(regs)); + if (len == -1) len = 0; + dev->num_addrs = len / sizeof(regs[0]); + + for (i = 0; i < dev->num_addrs; i++) { + if (regs[i] >= dev->parent->num_addrs) { + prom_printf("UGH: property for %s was %d, need < %d\n", + dev->prom_name, len, dev->parent->num_addrs); + panic(__FUNCTION__); + } + dev->resource[i].start = dev->parent->resource[regs[i]].start; /* XXX resource */ + } + + for (i = 0; i < PROMINTR_MAX; i++) + dev->irqs[i] = PCI_IRQ_NONE; + + if ((dev->irqs[0] = ebus_blacklist_irq(dev->prom_name)) != 0) { + dev->num_irqs = 1; + } else if ((len = prom_getproperty(node, "interrupts", + (char *)&irqs, sizeof(irqs))) == -1 || len == 0) { + dev->num_irqs = 0; + dev->irqs[0] = 0; + if (dev->parent->num_irqs != 0) { + dev->num_irqs = 1; + dev->irqs[0] = dev->parent->irqs[0]; +/* P3 */ /* printk("EBUS: dev %s irq %d from parent\n", dev->prom_name, dev->irqs[0]); */ + } + } else { + dev->num_irqs = len / sizeof(irqs[0]); + if (irqs[0] == 0 || irqs[0] >= 8) { + /* + * XXX Zero is a valid pin number... + * This works as long as Ebus is not wired to INTA#. + */ + printk("EBUS: %s got bad irq %d from PROM\n", + dev->prom_name, irqs[0]); + dev->num_irqs = 0; + dev->irqs[0] = 0; + } else { + dev->irqs[0] = pcic_pin_to_irq(irqs[0], dev->prom_name); + } + } +} + +void __init fill_ebus_device(int node, struct linux_ebus_device *dev) +{ + struct linux_prom_registers regs[PROMREG_MAX]; + struct linux_ebus_child *child; + int irqs[PROMINTR_MAX]; + char lbuf[128]; + int i, n, len; + unsigned long baseaddr; + + dev->prom_node = node; + prom_getstring(node, "name", lbuf, sizeof(lbuf)); + strcpy(dev->prom_name, lbuf); + + len = prom_getproperty(node, "reg", (void *)regs, sizeof(regs)); + if (len % sizeof(struct linux_prom_registers)) { + prom_printf("UGH: proplen for %s was %d, need multiple of %d\n", + dev->prom_name, len, + (int)sizeof(struct linux_prom_registers)); + panic(__FUNCTION__); + } + dev->num_addrs = len / sizeof(struct linux_prom_registers); + + for (i = 0; i < dev->num_addrs; i++) { + /* + * XXX Collect JE-1 PROM + * + * Example - JS-E with 3.11: + * /ebus + * regs + * 0x00000000, 0x0, 0x00000000, 0x0, 0x00000000, + * 0x82000010, 0x0, 0xf0000000, 0x0, 0x01000000, + * 0x82000014, 0x0, 0x38800000, 0x0, 0x00800000, + * ranges + * 0x00, 0x00000000, 0x02000010, 0x0, 0x0, 0x01000000, + * 0x01, 0x01000000, 0x02000014, 0x0, 0x0, 0x00800000, + * /ebus/8042 + * regs + * 0x00000001, 0x00300060, 0x00000008, + * 0x00000001, 0x00300060, 0x00000008, + */ + n = regs[i].which_io; + if (n >= 4) { + /* XXX This is copied from old JE-1 by Gleb. */ + n = (regs[i].which_io - 0x10) >> 2; + } else { + ; + } + +/* + * XXX Now as we have regions, why don't we make an on-demand allocation... + */ + dev->resource[i].start = 0; + if ((baseaddr = dev->bus->self->resource[n].start + + regs[i].phys_addr) != 0) { + /* dev->resource[i].name = dev->prom_name; */ + if ((baseaddr = (unsigned long) ioremap(baseaddr, + regs[i].reg_size)) == 0) { + panic("ebus: unable to remap dev %s", + dev->prom_name); + } + } + dev->resource[i].start = baseaddr; /* XXX Unaligned */ + } + + for (i = 0; i < PROMINTR_MAX; i++) + dev->irqs[i] = PCI_IRQ_NONE; + + if ((dev->irqs[0] = ebus_blacklist_irq(dev->prom_name)) != 0) { + dev->num_irqs = 1; + } else if ((len = prom_getproperty(node, "interrupts", + (char *)&irqs, sizeof(irqs))) == -1 || len == 0) { + dev->num_irqs = 0; + if ((dev->irqs[0] = dev->bus->self->irq) != 0) { + dev->num_irqs = 1; +/* P3 */ /* printk("EBUS: child %s irq %d from parent\n", dev->prom_name, dev->irqs[0]); */ + } + } else { + dev->num_irqs = 1; /* dev->num_irqs = len / sizeof(irqs[0]); */ + if (irqs[0] == 0 || irqs[0] >= 8) { + /* See above for the parent. XXX */ + printk("EBUS: %s got bad irq %d from PROM\n", + dev->prom_name, irqs[0]); + dev->num_irqs = 0; + dev->irqs[0] = 0; + } else { + dev->irqs[0] = pcic_pin_to_irq(irqs[0], dev->prom_name); + } + } + + if ((node = prom_getchild(node))) { + dev->children = (struct linux_ebus_child *) + ebus_alloc(sizeof(struct linux_ebus_child)); + + child = dev->children; + child->next = 0; + child->parent = dev; + child->bus = dev->bus; + fill_ebus_child(node, ®s[0], child); + + while ((node = prom_getsibling(node)) != 0) { + child->next = (struct linux_ebus_child *) + ebus_alloc(sizeof(struct linux_ebus_child)); + + child = child->next; + child->next = 0; + child->parent = dev; + child->bus = dev->bus; + fill_ebus_child(node, ®s[0], child); + } + } +} + +void __init ebus_init(void) +{ + struct linux_prom_pci_registers regs[PROMREG_MAX]; + struct linux_pbm_info *pbm; + struct linux_ebus_device *dev; + struct linux_ebus *ebus; + struct ebus_system_entry *sp; + struct pci_dev *pdev; + struct pcidev_cookie *cookie; + char lbuf[128]; + unsigned long addr, *base; + unsigned short pci_command; + int nd, len, ebusnd; + int reg, nreg; + int num_ebus = 0; + + prom_getstring(prom_root_node, "name", lbuf, sizeof(lbuf)); + for (sp = ebus_blacklist; sp->esname != NULL; sp++) { + if (strcmp(lbuf, sp->esname) == 0) { + ebus_blackp = sp->ipt; + break; + } + } + + pdev = pci_get_device(PCI_VENDOR_ID_SUN, PCI_DEVICE_ID_SUN_EBUS, 0); + if (!pdev) { + return; + } + cookie = pdev->sysdata; + ebusnd = cookie->prom_node; + + ebus_chain = ebus = (struct linux_ebus *) + ebus_alloc(sizeof(struct linux_ebus)); + ebus->next = 0; + + while (ebusnd) { + + prom_getstring(ebusnd, "name", lbuf, sizeof(lbuf)); + ebus->prom_node = ebusnd; + strcpy(ebus->prom_name, lbuf); + ebus->self = pdev; + ebus->parent = pbm = cookie->pbm; + + /* Enable BUS Master. */ + pci_read_config_word(pdev, PCI_COMMAND, &pci_command); + pci_command |= PCI_COMMAND_MASTER; + pci_write_config_word(pdev, PCI_COMMAND, pci_command); + + len = prom_getproperty(ebusnd, "reg", (void *)regs, + sizeof(regs)); + if (len == 0 || len == -1) { + prom_printf("%s: can't find reg property\n", + __FUNCTION__); + prom_halt(); + } + nreg = len / sizeof(struct linux_prom_pci_registers); + + base = &ebus->self->resource[0].start; + for (reg = 0; reg < nreg; reg++) { + if (!(regs[reg].which_io & 0x03000000)) + continue; + + addr = regs[reg].phys_lo; + *base++ = addr; + } + + nd = prom_getchild(ebusnd); + if (!nd) + goto next_ebus; + + ebus->devices = (struct linux_ebus_device *) + ebus_alloc(sizeof(struct linux_ebus_device)); + + dev = ebus->devices; + dev->next = 0; + dev->children = 0; + dev->bus = ebus; + fill_ebus_device(nd, dev); + + while ((nd = prom_getsibling(nd)) != 0) { + dev->next = (struct linux_ebus_device *) + ebus_alloc(sizeof(struct linux_ebus_device)); + + dev = dev->next; + dev->next = 0; + dev->children = 0; + dev->bus = ebus; + fill_ebus_device(nd, dev); + } + + next_ebus: + pdev = pci_get_device(PCI_VENDOR_ID_SUN, + PCI_DEVICE_ID_SUN_EBUS, pdev); + if (!pdev) + break; + + cookie = pdev->sysdata; + ebusnd = cookie->prom_node; + + ebus->next = (struct linux_ebus *) + ebus_alloc(sizeof(struct linux_ebus)); + ebus = ebus->next; + ebus->next = 0; + ++num_ebus; + } + if (pdev) + pci_dev_put(pdev); +} diff --git a/arch/sparc/kernel/entry.S b/arch/sparc/kernel/entry.S new file mode 100644 index 000000000000..b448166f5da9 --- /dev/null +++ b/arch/sparc/kernel/entry.S @@ -0,0 +1,1956 @@ +/* $Id: entry.S,v 1.170 2001/11/13 00:57:05 davem Exp $ + * arch/sparc/kernel/entry.S: Sparc trap low-level entry points. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1996-1999 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1997 Anton Blanchard (anton@progsoc.uts.edu.au) + */ + +#include <linux/config.h> +#include <linux/errno.h> + +#include <asm/head.h> +#include <asm/asi.h> +#include <asm/smp.h> +#include <asm/kgdb.h> +#include <asm/contregs.h> +#include <asm/ptrace.h> +#include <asm/asm_offsets.h> +#include <asm/psr.h> +#include <asm/vaddrs.h> +#include <asm/memreg.h> +#include <asm/page.h> +#ifdef CONFIG_SUN4 +#include <asm/pgtsun4.h> +#else +#include <asm/pgtsun4c.h> +#endif +#include <asm/winmacro.h> +#include <asm/signal.h> +#include <asm/obio.h> +#include <asm/mxcc.h> +#include <asm/thread_info.h> +#include <asm/param.h> + +#include <asm/asmmacro.h> + +#define curptr g6 + +#define NR_SYSCALLS 284 /* Each OS is different... */ + +/* These are just handy. */ +#define _SV save %sp, -STACKFRAME_SZ, %sp +#define _RS restore + +#define FLUSH_ALL_KERNEL_WINDOWS \ + _SV; _SV; _SV; _SV; _SV; _SV; _SV; \ + _RS; _RS; _RS; _RS; _RS; _RS; _RS; + +/* First, KGDB low level things. This is a rewrite + * of the routines found in the sparc-stub.c asm() statement + * from the gdb distribution. This is also dual-purpose + * as a software trap for userlevel programs. + */ + .data + .align 4 + +in_trap_handler: + .word 0 + + .text + .align 4 + +#if 0 /* kgdb is dropped from 2.5.33 */ +! This function is called when any SPARC trap (except window overflow or +! underflow) occurs. It makes sure that the invalid register window is still +! available before jumping into C code. It will also restore the world if you +! return from handle_exception. + + .globl trap_low +trap_low: + rd %wim, %l3 + SAVE_ALL + + sethi %hi(in_trap_handler), %l4 + ld [%lo(in_trap_handler) + %l4], %l5 + inc %l5 + st %l5, [%lo(in_trap_handler) + %l4] + + /* Make sure kgdb sees the same state we just saved. */ + LOAD_PT_GLOBALS(sp) + LOAD_PT_INS(sp) + ld [%sp + STACKFRAME_SZ + PT_Y], %l4 + ld [%sp + STACKFRAME_SZ + PT_WIM], %l3 + ld [%sp + STACKFRAME_SZ + PT_PSR], %l0 + ld [%sp + STACKFRAME_SZ + PT_PC], %l1 + ld [%sp + STACKFRAME_SZ + PT_NPC], %l2 + rd %tbr, %l5 /* Never changes... */ + + /* Make kgdb exception frame. */ + sub %sp,(16+1+6+1+72)*4,%sp ! Make room for input & locals + ! + hidden arg + arg spill + ! + doubleword alignment + ! + registers[72] local var + SAVE_KGDB_GLOBALS(sp) + SAVE_KGDB_INS(sp) + SAVE_KGDB_SREGS(sp, l4, l0, l3, l5, l1, l2) + + /* We are increasing PIL, so two writes. */ + or %l0, PSR_PIL, %l0 + wr %l0, 0, %psr + WRITE_PAUSE + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + call handle_exception + add %sp, STACKFRAME_SZ, %o0 ! Pass address of registers + + /* Load new kgdb register set. */ + LOAD_KGDB_GLOBALS(sp) + LOAD_KGDB_INS(sp) + LOAD_KGDB_SREGS(sp, l4, l0, l3, l5, l1, l2) + wr %l4, 0x0, %y + + sethi %hi(in_trap_handler), %l4 + ld [%lo(in_trap_handler) + %l4], %l5 + dec %l5 + st %l5, [%lo(in_trap_handler) + %l4] + + add %sp,(16+1+6+1+72)*4,%sp ! Undo the kgdb trap frame. + + /* Now take what kgdb did and place it into the pt_regs + * frame which SparcLinux RESTORE_ALL understands., + */ + STORE_PT_INS(sp) + STORE_PT_GLOBALS(sp) + STORE_PT_YREG(sp, g2) + STORE_PT_PRIV(sp, l0, l1, l2) + + RESTORE_ALL +#endif + +#ifdef CONFIG_BLK_DEV_FD + .text + .align 4 + .globl floppy_hardint +floppy_hardint: + /* + * This code cannot touch registers %l0 %l1 and %l2 + * because SAVE_ALL depends on their values. It depends + * on %l3 also, but we regenerate it before a call. + * Other registers are: + * %l3 -- base address of fdc registers + * %l4 -- pdma_vaddr + * %l5 -- scratch for ld/st address + * %l6 -- pdma_size + * %l7 -- scratch [floppy byte, ld/st address, aux. data] + */ + + /* Do we have work to do? */ + sethi %hi(doing_pdma), %l7 + ld [%l7 + %lo(doing_pdma)], %l7 + cmp %l7, 0 + be floppy_dosoftint + nop + + /* Load fdc register base */ + sethi %hi(fdc_status), %l3 + ld [%l3 + %lo(fdc_status)], %l3 + + /* Setup register addresses */ + sethi %hi(pdma_vaddr), %l5 ! transfer buffer + ld [%l5 + %lo(pdma_vaddr)], %l4 + sethi %hi(pdma_size), %l5 ! bytes to go + ld [%l5 + %lo(pdma_size)], %l6 +next_byte: + ldub [%l3], %l7 + + andcc %l7, 0x80, %g0 ! Does fifo still have data + bz floppy_fifo_emptied ! fifo has been emptied... + andcc %l7, 0x20, %g0 ! in non-dma mode still? + bz floppy_overrun ! nope, overrun + andcc %l7, 0x40, %g0 ! 0=write 1=read + bz floppy_write + sub %l6, 0x1, %l6 + + /* Ok, actually read this byte */ + ldub [%l3 + 1], %l7 + orcc %g0, %l6, %g0 + stb %l7, [%l4] + bne next_byte + add %l4, 0x1, %l4 + + b floppy_tdone + nop + +floppy_write: + /* Ok, actually write this byte */ + ldub [%l4], %l7 + orcc %g0, %l6, %g0 + stb %l7, [%l3 + 1] + bne next_byte + add %l4, 0x1, %l4 + + /* fall through... */ +floppy_tdone: + sethi %hi(pdma_vaddr), %l5 + st %l4, [%l5 + %lo(pdma_vaddr)] + sethi %hi(pdma_size), %l5 + st %l6, [%l5 + %lo(pdma_size)] + /* Flip terminal count pin */ + set auxio_register, %l7 + ld [%l7], %l7 + + set sparc_cpu_model, %l5 + ld [%l5], %l5 + subcc %l5, 1, %g0 /* enum { sun4c = 1 }; */ + be 1f + ldub [%l7], %l5 + + or %l5, 0xc2, %l5 + stb %l5, [%l7] + andn %l5, 0x02, %l5 + b 2f + nop + +1: + or %l5, 0xf4, %l5 + stb %l5, [%l7] + andn %l5, 0x04, %l5 + +2: + /* Kill some time so the bits set */ + WRITE_PAUSE + WRITE_PAUSE + + stb %l5, [%l7] + + /* Prevent recursion */ + sethi %hi(doing_pdma), %l7 + b floppy_dosoftint + st %g0, [%l7 + %lo(doing_pdma)] + + /* We emptied the FIFO, but we haven't read everything + * as of yet. Store the current transfer address and + * bytes left to read so we can continue when the next + * fast IRQ comes in. + */ +floppy_fifo_emptied: + sethi %hi(pdma_vaddr), %l5 + st %l4, [%l5 + %lo(pdma_vaddr)] + sethi %hi(pdma_size), %l7 + st %l6, [%l7 + %lo(pdma_size)] + + /* Restore condition codes */ + wr %l0, 0x0, %psr + WRITE_PAUSE + + jmp %l1 + rett %l2 + +floppy_overrun: + sethi %hi(pdma_vaddr), %l5 + st %l4, [%l5 + %lo(pdma_vaddr)] + sethi %hi(pdma_size), %l5 + st %l6, [%l5 + %lo(pdma_size)] + /* Prevent recursion */ + sethi %hi(doing_pdma), %l7 + st %g0, [%l7 + %lo(doing_pdma)] + + /* fall through... */ +floppy_dosoftint: + rd %wim, %l3 + SAVE_ALL + + /* Set all IRQs off. */ + or %l0, PSR_PIL, %l4 + wr %l4, 0x0, %psr + WRITE_PAUSE + wr %l4, PSR_ET, %psr + WRITE_PAUSE + + mov 11, %o0 ! floppy irq level (unused anyway) + mov %g0, %o1 ! devid is not used in fast interrupts + call sparc_floppy_irq + add %sp, STACKFRAME_SZ, %o2 ! struct pt_regs *regs + + RESTORE_ALL + +#endif /* (CONFIG_BLK_DEV_FD) */ + + /* Bad trap handler */ + .globl bad_trap_handler +bad_trap_handler: + SAVE_ALL + + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 ! pt_regs + call do_hw_interrupt + mov %l7, %o1 ! trap number + + RESTORE_ALL + +/* For now all IRQ's not registered get sent here. handler_irq() will + * see if a routine is registered to handle this interrupt and if not + * it will say so on the console. + */ + + .align 4 + .globl real_irq_entry, patch_handler_irq +real_irq_entry: + SAVE_ALL + +#ifdef CONFIG_SMP + .globl patchme_maybe_smp_msg + + cmp %l7, 12 +patchme_maybe_smp_msg: + bgu maybe_smp4m_msg + nop +#endif + +real_irq_continue: + or %l0, PSR_PIL, %g2 + wr %g2, 0x0, %psr + WRITE_PAUSE + wr %g2, PSR_ET, %psr + WRITE_PAUSE + mov %l7, %o0 ! irq level +patch_handler_irq: + call handler_irq + add %sp, STACKFRAME_SZ, %o1 ! pt_regs ptr + or %l0, PSR_PIL, %g2 ! restore PIL after handler_irq + wr %g2, PSR_ET, %psr ! keep ET up + WRITE_PAUSE + + RESTORE_ALL + +#ifdef CONFIG_SMP + /* SMP per-cpu ticker interrupts are handled specially. */ +smp4m_ticker: + bne real_irq_continue+4 + or %l0, PSR_PIL, %g2 + wr %g2, 0x0, %psr + WRITE_PAUSE + wr %g2, PSR_ET, %psr + WRITE_PAUSE + call smp4m_percpu_timer_interrupt + add %sp, STACKFRAME_SZ, %o0 + wr %l0, PSR_ET, %psr + WRITE_PAUSE + RESTORE_ALL + + /* Here is where we check for possible SMP IPI passed to us + * on some level other than 15 which is the NMI and only used + * for cross calls. That has a separate entry point below. + */ +maybe_smp4m_msg: + GET_PROCESSOR4M_ID(o3) + set sun4m_interrupts, %l5 + ld [%l5], %o5 + sethi %hi(0x40000000), %o2 + sll %o3, 12, %o3 + ld [%o5 + %o3], %o1 + andcc %o1, %o2, %g0 + be,a smp4m_ticker + cmp %l7, 14 + st %o2, [%o5 + 0x4] + WRITE_PAUSE + ld [%o5], %g0 + WRITE_PAUSE + or %l0, PSR_PIL, %l4 + wr %l4, 0x0, %psr + WRITE_PAUSE + wr %l4, PSR_ET, %psr + WRITE_PAUSE + call smp_reschedule_irq + nop + + RESTORE_ALL + + .align 4 + .globl linux_trap_ipi15_sun4m +linux_trap_ipi15_sun4m: + SAVE_ALL + sethi %hi(0x80000000), %o2 + GET_PROCESSOR4M_ID(o0) + set sun4m_interrupts, %l5 + ld [%l5], %o5 + sll %o0, 12, %o0 + add %o5, %o0, %o5 + ld [%o5], %o3 + andcc %o3, %o2, %g0 + be 1f ! Must be an NMI async memory error + st %o2, [%o5 + 4] + WRITE_PAUSE + ld [%o5], %g0 + WRITE_PAUSE + or %l0, PSR_PIL, %l4 + wr %l4, 0x0, %psr + WRITE_PAUSE + wr %l4, PSR_ET, %psr + WRITE_PAUSE + call smp4m_cross_call_irq + nop + b ret_trap_lockless_ipi + clr %l6 +1: + /* NMI async memory error handling. */ + sethi %hi(0x80000000), %l4 + sethi %hi(0x4000), %o3 + sub %o5, %o0, %o5 + add %o5, %o3, %l5 + st %l4, [%l5 + 0xc] + WRITE_PAUSE + ld [%l5], %g0 + WRITE_PAUSE + or %l0, PSR_PIL, %l4 + wr %l4, 0x0, %psr + WRITE_PAUSE + wr %l4, PSR_ET, %psr + WRITE_PAUSE + call sun4m_nmi + nop + st %l4, [%l5 + 0x8] + WRITE_PAUSE + ld [%l5], %g0 + WRITE_PAUSE + RESTORE_ALL + + .globl smp4d_ticker + /* SMP per-cpu ticker interrupts are handled specially. */ +smp4d_ticker: + SAVE_ALL + or %l0, PSR_PIL, %g2 + sethi %hi(CC_ICLR), %o0 + sethi %hi(1 << 14), %o1 + or %o0, %lo(CC_ICLR), %o0 + stha %o1, [%o0] ASI_M_MXCC /* Clear PIL 14 in MXCC's ICLR */ + wr %g2, 0x0, %psr + WRITE_PAUSE + wr %g2, PSR_ET, %psr + WRITE_PAUSE + call smp4d_percpu_timer_interrupt + add %sp, STACKFRAME_SZ, %o0 + wr %l0, PSR_ET, %psr + WRITE_PAUSE + RESTORE_ALL + + .align 4 + .globl linux_trap_ipi15_sun4d +linux_trap_ipi15_sun4d: + SAVE_ALL + sethi %hi(CC_BASE), %o4 + sethi %hi(MXCC_ERR_ME|MXCC_ERR_PEW|MXCC_ERR_ASE|MXCC_ERR_PEE), %o2 + or %o4, (CC_EREG - CC_BASE), %o0 + ldda [%o0] ASI_M_MXCC, %o0 + andcc %o0, %o2, %g0 + bne 1f + sethi %hi(BB_STAT2), %o2 + lduba [%o2] ASI_M_CTL, %o2 + andcc %o2, BB_STAT2_MASK, %g0 + bne 2f + or %o4, (CC_ICLR - CC_BASE), %o0 + sethi %hi(1 << 15), %o1 + stha %o1, [%o0] ASI_M_MXCC /* Clear PIL 15 in MXCC's ICLR */ + or %l0, PSR_PIL, %l4 + wr %l4, 0x0, %psr + WRITE_PAUSE + wr %l4, PSR_ET, %psr + WRITE_PAUSE + call smp4d_cross_call_irq + nop + b ret_trap_lockless_ipi + clr %l6 + +1: /* MXCC error */ +2: /* BB error */ + /* Disable PIL 15 */ + set CC_IMSK, %l4 + lduha [%l4] ASI_M_MXCC, %l5 + sethi %hi(1 << 15), %l7 + or %l5, %l7, %l5 + stha %l5, [%l4] ASI_M_MXCC + /* FIXME */ +1: b,a 1b + +#endif /* CONFIG_SMP */ + + /* This routine handles illegal instructions and privileged + * instruction attempts from user code. + */ + .align 4 + .globl bad_instruction +bad_instruction: + sethi %hi(0xc1f80000), %l4 + ld [%l1], %l5 + sethi %hi(0x81d80000), %l7 + and %l5, %l4, %l5 + cmp %l5, %l7 + be 1f + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call do_illegal_instruction + mov %l0, %o3 + + RESTORE_ALL + +1: /* unimplemented flush - just skip */ + jmpl %l2, %g0 + rett %l2 + 4 + + .align 4 + .globl priv_instruction +priv_instruction: + SAVE_ALL + + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call do_priv_instruction + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles unaligned data accesses. */ + .align 4 + .globl mna_handler +mna_handler: + andcc %l0, PSR_PS, %g0 + be mna_fromuser + nop + + SAVE_ALL + + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + ld [%l1], %o1 + call kernel_unaligned_trap + add %sp, STACKFRAME_SZ, %o0 + + RESTORE_ALL + +mna_fromuser: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + ld [%l1], %o1 + call user_unaligned_trap + add %sp, STACKFRAME_SZ, %o0 + + RESTORE_ALL + + /* This routine handles floating point disabled traps. */ + .align 4 + .globl fpd_trap_handler +fpd_trap_handler: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call do_fpd_trap + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles Floating Point Exceptions. */ + .align 4 + .globl fpe_trap_handler +fpe_trap_handler: + set fpsave_magic, %l5 + cmp %l1, %l5 + be 1f + sethi %hi(fpsave), %l5 + or %l5, %lo(fpsave), %l5 + cmp %l1, %l5 + bne 2f + sethi %hi(fpsave_catch2), %l5 + or %l5, %lo(fpsave_catch2), %l5 + wr %l0, 0x0, %psr + WRITE_PAUSE + jmp %l5 + rett %l5 + 4 +1: + sethi %hi(fpsave_catch), %l5 + or %l5, %lo(fpsave_catch), %l5 + wr %l0, 0x0, %psr + WRITE_PAUSE + jmp %l5 + rett %l5 + 4 + +2: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call do_fpe_trap + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles Tag Overflow Exceptions. */ + .align 4 + .globl do_tag_overflow +do_tag_overflow: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call handle_tag_overflow + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles Watchpoint Exceptions. */ + .align 4 + .globl do_watchpoint +do_watchpoint: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call handle_watchpoint + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles Register Access Exceptions. */ + .align 4 + .globl do_reg_access +do_reg_access: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call handle_reg_access + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles Co-Processor Disabled Exceptions. */ + .align 4 + .globl do_cp_disabled +do_cp_disabled: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call handle_cp_disabled + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles Co-Processor Exceptions. */ + .align 4 + .globl do_cp_exception +do_cp_exception: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call handle_cp_exception + mov %l0, %o3 + + RESTORE_ALL + + /* This routine handles Hardware Divide By Zero Exceptions. */ + .align 4 + .globl do_hw_divzero +do_hw_divzero: + SAVE_ALL + + wr %l0, PSR_ET, %psr ! re-enable traps + WRITE_PAUSE + + add %sp, STACKFRAME_SZ, %o0 + mov %l1, %o1 + mov %l2, %o2 + call handle_hw_divzero + mov %l0, %o3 + + RESTORE_ALL + + .align 4 + .globl do_flush_windows +do_flush_windows: + SAVE_ALL + + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + andcc %l0, PSR_PS, %g0 + bne dfw_kernel + nop + + call flush_user_windows + nop + + /* Advance over the trap instruction. */ + ld [%sp + STACKFRAME_SZ + PT_NPC], %l1 + add %l1, 0x4, %l2 + st %l1, [%sp + STACKFRAME_SZ + PT_PC] + st %l2, [%sp + STACKFRAME_SZ + PT_NPC] + + RESTORE_ALL + + .globl flush_patch_one + + /* We get these for debugging routines using __builtin_return_address() */ +dfw_kernel: +flush_patch_one: + FLUSH_ALL_KERNEL_WINDOWS + + /* Advance over the trap instruction. */ + ld [%sp + STACKFRAME_SZ + PT_NPC], %l1 + add %l1, 0x4, %l2 + st %l1, [%sp + STACKFRAME_SZ + PT_PC] + st %l2, [%sp + STACKFRAME_SZ + PT_NPC] + + RESTORE_ALL + + /* The getcc software trap. The user wants the condition codes from + * the %psr in register %g1. + */ + + .align 4 + .globl getcc_trap_handler +getcc_trap_handler: + srl %l0, 20, %g1 ! give user + and %g1, 0xf, %g1 ! only ICC bits in %psr + jmp %l2 ! advance over trap instruction + rett %l2 + 0x4 ! like this... + + /* The setcc software trap. The user has condition codes in %g1 + * that it would like placed in the %psr. Be careful not to flip + * any unintentional bits! + */ + + .align 4 + .globl setcc_trap_handler +setcc_trap_handler: + sll %g1, 0x14, %l4 + set PSR_ICC, %l5 + andn %l0, %l5, %l0 ! clear ICC bits in %psr + and %l4, %l5, %l4 ! clear non-ICC bits in user value + or %l4, %l0, %l4 ! or them in... mix mix mix + + wr %l4, 0x0, %psr ! set new %psr + WRITE_PAUSE ! TI scumbags... + + jmp %l2 ! advance over trap instruction + rett %l2 + 0x4 ! like this... + + .align 4 + .globl linux_trap_nmi_sun4c +linux_trap_nmi_sun4c: + SAVE_ALL + + /* Ugh, we need to clear the IRQ line. This is now + * a very sun4c specific trap handler... + */ + sethi %hi(interrupt_enable), %l5 + ld [%l5 + %lo(interrupt_enable)], %l5 + ldub [%l5], %l6 + andn %l6, INTS_ENAB, %l6 + stb %l6, [%l5] + + /* Now it is safe to re-enable traps without recursion. */ + or %l0, PSR_PIL, %l0 + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + /* Now call the c-code with the pt_regs frame ptr and the + * memory error registers as arguments. The ordering chosen + * here is due to unlatching semantics. + */ + sethi %hi(AC_SYNC_ERR), %o0 + add %o0, 0x4, %o0 + lda [%o0] ASI_CONTROL, %o2 ! sync vaddr + sub %o0, 0x4, %o0 + lda [%o0] ASI_CONTROL, %o1 ! sync error + add %o0, 0xc, %o0 + lda [%o0] ASI_CONTROL, %o4 ! async vaddr + sub %o0, 0x4, %o0 + lda [%o0] ASI_CONTROL, %o3 ! async error + call sparc_lvl15_nmi + add %sp, STACKFRAME_SZ, %o0 + + RESTORE_ALL + + .align 4 + .globl invalid_segment_patch1_ff + .globl invalid_segment_patch2_ff +invalid_segment_patch1_ff: cmp %l4, 0xff +invalid_segment_patch2_ff: mov 0xff, %l3 + + .align 4 + .globl invalid_segment_patch1_1ff + .globl invalid_segment_patch2_1ff +invalid_segment_patch1_1ff: cmp %l4, 0x1ff +invalid_segment_patch2_1ff: mov 0x1ff, %l3 + + .align 4 + .globl num_context_patch1_16, num_context_patch2_16 +num_context_patch1_16: mov 0x10, %l7 +num_context_patch2_16: mov 0x10, %l7 + + .align 4 + .globl vac_linesize_patch_32 +vac_linesize_patch_32: subcc %l7, 32, %l7 + + .align 4 + .globl vac_hwflush_patch1_on, vac_hwflush_patch2_on + +/* + * Ugly, but we cant use hardware flushing on the sun4 and we'd require + * two instructions (Anton) + */ +#ifdef CONFIG_SUN4 +vac_hwflush_patch1_on: nop +#else +vac_hwflush_patch1_on: addcc %l7, -PAGE_SIZE, %l7 +#endif + +vac_hwflush_patch2_on: sta %g0, [%l3 + %l7] ASI_HWFLUSHSEG + + .globl invalid_segment_patch1, invalid_segment_patch2 + .globl num_context_patch1 + .globl vac_linesize_patch, vac_hwflush_patch1 + .globl vac_hwflush_patch2 + + .align 4 + .globl sun4c_fault + +! %l0 = %psr +! %l1 = %pc +! %l2 = %npc +! %l3 = %wim +! %l7 = 1 for textfault +! We want error in %l5, vaddr in %l6 +sun4c_fault: +#ifdef CONFIG_SUN4 + sethi %hi(sun4c_memerr_reg), %l4 + ld [%l4+%lo(sun4c_memerr_reg)], %l4 ! memerr ctrl reg addr + ld [%l4], %l6 ! memerr ctrl reg + ld [%l4 + 4], %l5 ! memerr vaddr reg + andcc %l6, 0x80, %g0 ! check for error type + st %g0, [%l4 + 4] ! clear the error + be 0f ! normal error + sethi %hi(AC_BUS_ERROR), %l4 ! bus err reg addr + + call prom_halt ! something weird happened + ! what exactly did happen? + ! what should we do here? + +0: or %l4, %lo(AC_BUS_ERROR), %l4 ! bus err reg addr + lduba [%l4] ASI_CONTROL, %l6 ! bus err reg + + cmp %l7, 1 ! text fault? + be 1f ! yes + nop + + ld [%l1], %l4 ! load instruction that caused fault + srl %l4, 21, %l4 + andcc %l4, 1, %g0 ! store instruction? + + be 1f ! no + sethi %hi(SUN4C_SYNC_BADWRITE), %l4 ! yep + ! %lo(SUN4C_SYNC_BADWRITE) = 0 + or %l4, %l6, %l6 ! set write bit to emulate sun4c +1: +#else + sethi %hi(AC_SYNC_ERR), %l4 + add %l4, 0x4, %l6 ! AC_SYNC_VA in %l6 + lda [%l6] ASI_CONTROL, %l5 ! Address + lda [%l4] ASI_CONTROL, %l6 ! Error, retained for a bit +#endif + + andn %l5, 0xfff, %l5 ! Encode all info into l7 + srl %l6, 14, %l4 + + and %l4, 2, %l4 + or %l5, %l4, %l4 + + or %l4, %l7, %l7 ! l7 = [addr,write,txtfault] + + andcc %l0, PSR_PS, %g0 + be sun4c_fault_fromuser + andcc %l7, 1, %g0 ! Text fault? + + be 1f + sethi %hi(KERNBASE), %l4 + + mov %l1, %l5 ! PC + +1: + cmp %l5, %l4 + blu sun4c_fault_fromuser + sethi %hi(~((1 << SUN4C_REAL_PGDIR_SHIFT) - 1)), %l4 + + /* If the kernel references a bum kernel pointer, or a pte which + * points to a non existant page in ram, we will run this code + * _forever_ and lock up the machine!!!!! So we must check for + * this condition, the AC_SYNC_ERR bits are what we must examine. + * Also a parity error would make this happen as well. So we just + * check that we are in fact servicing a tlb miss and not some + * other type of fault for the kernel. + */ + andcc %l6, 0x80, %g0 + be sun4c_fault_fromuser + and %l5, %l4, %l5 + + /* Test for NULL pte_t * in vmalloc area. */ + sethi %hi(VMALLOC_START), %l4 + cmp %l5, %l4 + blu,a invalid_segment_patch1 + lduXa [%l5] ASI_SEGMAP, %l4 + + sethi %hi(swapper_pg_dir), %l4 + srl %l5, SUN4C_PGDIR_SHIFT, %l6 + or %l4, %lo(swapper_pg_dir), %l4 + sll %l6, 2, %l6 + ld [%l4 + %l6], %l4 +#ifdef CONFIG_SUN4 + sethi %hi(PAGE_MASK), %l6 + andcc %l4, %l6, %g0 +#else + andcc %l4, PAGE_MASK, %g0 +#endif + be sun4c_fault_fromuser + lduXa [%l5] ASI_SEGMAP, %l4 + +invalid_segment_patch1: + cmp %l4, 0x7f + bne 1f + sethi %hi(sun4c_kfree_ring), %l4 + or %l4, %lo(sun4c_kfree_ring), %l4 + ld [%l4 + 0x18], %l3 + deccc %l3 ! do we have a free entry? + bcs,a 2f ! no, unmap one. + sethi %hi(sun4c_kernel_ring), %l4 + + st %l3, [%l4 + 0x18] ! sun4c_kfree_ring.num_entries-- + + ld [%l4 + 0x00], %l6 ! entry = sun4c_kfree_ring.ringhd.next + st %l5, [%l6 + 0x08] ! entry->vaddr = address + + ld [%l6 + 0x00], %l3 ! next = entry->next + ld [%l6 + 0x04], %l7 ! entry->prev + + st %l7, [%l3 + 0x04] ! next->prev = entry->prev + st %l3, [%l7 + 0x00] ! entry->prev->next = next + + sethi %hi(sun4c_kernel_ring), %l4 + or %l4, %lo(sun4c_kernel_ring), %l4 + ! head = &sun4c_kernel_ring.ringhd + + ld [%l4 + 0x00], %l7 ! head->next + + st %l4, [%l6 + 0x04] ! entry->prev = head + st %l7, [%l6 + 0x00] ! entry->next = head->next + st %l6, [%l7 + 0x04] ! head->next->prev = entry + + st %l6, [%l4 + 0x00] ! head->next = entry + + ld [%l4 + 0x18], %l3 + inc %l3 ! sun4c_kernel_ring.num_entries++ + st %l3, [%l4 + 0x18] + b 4f + ld [%l6 + 0x08], %l5 + +2: + or %l4, %lo(sun4c_kernel_ring), %l4 + ! head = &sun4c_kernel_ring.ringhd + + ld [%l4 + 0x04], %l6 ! entry = head->prev + + ld [%l6 + 0x08], %l3 ! tmp = entry->vaddr + + ! Flush segment from the cache. +#ifdef CONFIG_SUN4 + sethi %hi((128 * 1024)), %l7 +#else + sethi %hi((64 * 1024)), %l7 +#endif +9: +vac_hwflush_patch1: +vac_linesize_patch: + subcc %l7, 16, %l7 + bne 9b +vac_hwflush_patch2: + sta %g0, [%l3 + %l7] ASI_FLUSHSEG + + st %l5, [%l6 + 0x08] ! entry->vaddr = address + + ld [%l6 + 0x00], %l5 ! next = entry->next + ld [%l6 + 0x04], %l7 ! entry->prev + + st %l7, [%l5 + 0x04] ! next->prev = entry->prev + st %l5, [%l7 + 0x00] ! entry->prev->next = next + st %l4, [%l6 + 0x04] ! entry->prev = head + + ld [%l4 + 0x00], %l7 ! head->next + + st %l7, [%l6 + 0x00] ! entry->next = head->next + st %l6, [%l7 + 0x04] ! head->next->prev = entry + st %l6, [%l4 + 0x00] ! head->next = entry + + mov %l3, %l5 ! address = tmp + +4: +num_context_patch1: + mov 0x08, %l7 + + ld [%l6 + 0x08], %l4 + ldub [%l6 + 0x0c], %l3 + or %l4, %l3, %l4 ! encode new vaddr/pseg into l4 + + sethi %hi(AC_CONTEXT), %l3 + lduba [%l3] ASI_CONTROL, %l6 + + /* Invalidate old mapping, instantiate new mapping, + * for each context. Registers l6/l7 are live across + * this loop. + */ +3: deccc %l7 + sethi %hi(AC_CONTEXT), %l3 + stba %l7, [%l3] ASI_CONTROL +invalid_segment_patch2: + mov 0x7f, %l3 + stXa %l3, [%l5] ASI_SEGMAP + andn %l4, 0x1ff, %l3 + bne 3b + stXa %l4, [%l3] ASI_SEGMAP + + sethi %hi(AC_CONTEXT), %l3 + stba %l6, [%l3] ASI_CONTROL + + andn %l4, 0x1ff, %l5 + +1: + sethi %hi(VMALLOC_START), %l4 + cmp %l5, %l4 + + bgeu 1f + mov 1 << (SUN4C_REAL_PGDIR_SHIFT - PAGE_SHIFT), %l7 + + sethi %hi(KERNBASE), %l6 + + sub %l5, %l6, %l4 + srl %l4, PAGE_SHIFT, %l4 + sethi %hi((SUN4C_PAGE_KERNEL & 0xf4000000)), %l3 + or %l3, %l4, %l3 + + sethi %hi(PAGE_SIZE), %l4 + +2: + sta %l3, [%l5] ASI_PTE + deccc %l7 + inc %l3 + bne 2b + add %l5, %l4, %l5 + + b 7f + sethi %hi(sun4c_kernel_faults), %l4 + +1: + srl %l5, SUN4C_PGDIR_SHIFT, %l3 + sethi %hi(swapper_pg_dir), %l4 + or %l4, %lo(swapper_pg_dir), %l4 + sll %l3, 2, %l3 + ld [%l4 + %l3], %l4 +#ifndef CONFIG_SUN4 + and %l4, PAGE_MASK, %l4 +#else + sethi %hi(PAGE_MASK), %l6 + and %l4, %l6, %l4 +#endif + + srl %l5, (PAGE_SHIFT - 2), %l6 + and %l6, ((SUN4C_PTRS_PER_PTE - 1) << 2), %l6 + add %l6, %l4, %l6 + + sethi %hi(PAGE_SIZE), %l4 + +2: + ld [%l6], %l3 + deccc %l7 + sta %l3, [%l5] ASI_PTE + add %l6, 0x4, %l6 + bne 2b + add %l5, %l4, %l5 + + sethi %hi(sun4c_kernel_faults), %l4 +7: + ld [%l4 + %lo(sun4c_kernel_faults)], %l3 + inc %l3 + st %l3, [%l4 + %lo(sun4c_kernel_faults)] + + /* Restore condition codes */ + wr %l0, 0x0, %psr + WRITE_PAUSE + jmp %l1 + rett %l2 + +sun4c_fault_fromuser: + SAVE_ALL + nop + + mov %l7, %o1 ! Decode the info from %l7 + mov %l7, %o2 + and %o1, 1, %o1 ! arg2 = text_faultp + mov %l7, %o3 + and %o2, 2, %o2 ! arg3 = writep + andn %o3, 0xfff, %o3 ! arg4 = faulting address + + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + call do_sun4c_fault + add %sp, STACKFRAME_SZ, %o0 ! arg1 = pt_regs ptr + + RESTORE_ALL + + .align 4 + .globl srmmu_fault +srmmu_fault: + mov 0x400, %l5 + mov 0x300, %l4 + + lda [%l5] ASI_M_MMUREGS, %l6 ! read sfar first + lda [%l4] ASI_M_MMUREGS, %l5 ! read sfsr last + + andn %l6, 0xfff, %l6 + srl %l5, 6, %l5 ! and encode all info into l7 + + and %l5, 2, %l5 + or %l5, %l6, %l6 + + or %l6, %l7, %l7 ! l7 = [addr,write,txtfault] + + SAVE_ALL + + mov %l7, %o1 + mov %l7, %o2 + and %o1, 1, %o1 ! arg2 = text_faultp + mov %l7, %o3 + and %o2, 2, %o2 ! arg3 = writep + andn %o3, 0xfff, %o3 ! arg4 = faulting address + + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + call do_sparc_fault + add %sp, STACKFRAME_SZ, %o0 ! arg1 = pt_regs ptr + + RESTORE_ALL + +#ifdef CONFIG_SUNOS_EMUL + /* SunOS uses syscall zero as the 'indirect syscall' it looks + * like indir_syscall(scall_num, arg0, arg1, arg2...); etc. + * This is complete brain damage. + */ + .globl sunos_indir +sunos_indir: + mov %o7, %l4 + cmp %o0, NR_SYSCALLS + blu,a 1f + sll %o0, 0x2, %o0 + + sethi %hi(sunos_nosys), %l6 + b 2f + or %l6, %lo(sunos_nosys), %l6 + +1: + set sunos_sys_table, %l7 + ld [%l7 + %o0], %l6 + +2: + mov %o1, %o0 + mov %o2, %o1 + mov %o3, %o2 + mov %o4, %o3 + mov %o5, %o4 + call %l6 + mov %l4, %o7 +#endif + + .align 4 + .globl sys_nis_syscall +sys_nis_syscall: + mov %o7, %l5 + add %sp, STACKFRAME_SZ, %o0 ! pt_regs *regs arg + call c_sys_nis_syscall + mov %l5, %o7 + + .align 4 + .globl sys_ptrace +sys_ptrace: + call do_ptrace + add %sp, STACKFRAME_SZ, %o0 + + ld [%curptr + TI_FLAGS], %l5 + andcc %l5, _TIF_SYSCALL_TRACE, %g0 + be 1f + nop + + call syscall_trace + nop + +1: + RESTORE_ALL + + .align 4 + .globl sys_execve +sys_execve: + mov %o7, %l5 + add %sp, STACKFRAME_SZ, %o0 ! pt_regs *regs arg + call sparc_execve + mov %l5, %o7 + + .align 4 + .globl sys_pipe +sys_pipe: + mov %o7, %l5 + add %sp, STACKFRAME_SZ, %o0 ! pt_regs *regs arg + call sparc_pipe + mov %l5, %o7 + + .align 4 + .globl sys_sigaltstack +sys_sigaltstack: + mov %o7, %l5 + mov %fp, %o2 + call do_sigaltstack + mov %l5, %o7 + + .align 4 + .globl sys_sigstack +sys_sigstack: + mov %o7, %l5 + mov %fp, %o2 + call do_sys_sigstack + mov %l5, %o7 + + .align 4 + .globl sys_sigpause +sys_sigpause: + /* Note: %o0 already has correct value... */ + call do_sigpause + add %sp, STACKFRAME_SZ, %o1 + + ld [%curptr + TI_FLAGS], %l5 + andcc %l5, _TIF_SYSCALL_TRACE, %g0 + be 1f + nop + + call syscall_trace + nop + +1: + /* We are returning to a signal handler. */ + RESTORE_ALL + + .align 4 + .globl sys_sigsuspend +sys_sigsuspend: + call do_sigsuspend + add %sp, STACKFRAME_SZ, %o0 + + ld [%curptr + TI_FLAGS], %l5 + andcc %l5, _TIF_SYSCALL_TRACE, %g0 + be 1f + nop + + call syscall_trace + nop + +1: + /* We are returning to a signal handler. */ + RESTORE_ALL + + .align 4 + .globl sys_rt_sigsuspend +sys_rt_sigsuspend: + /* Note: %o0, %o1 already have correct value... */ + call do_rt_sigsuspend + add %sp, STACKFRAME_SZ, %o2 + + ld [%curptr + TI_FLAGS], %l5 + andcc %l5, _TIF_SYSCALL_TRACE, %g0 + be 1f + nop + + call syscall_trace + nop + +1: + /* We are returning to a signal handler. */ + RESTORE_ALL + + .align 4 + .globl sys_sigreturn +sys_sigreturn: + call do_sigreturn + add %sp, STACKFRAME_SZ, %o0 + + ld [%curptr + TI_FLAGS], %l5 + andcc %l5, _TIF_SYSCALL_TRACE, %g0 + be 1f + nop + + call syscall_trace + nop + +1: + /* We don't want to muck with user registers like a + * normal syscall, just return. + */ + RESTORE_ALL + + .align 4 + .globl sys_rt_sigreturn +sys_rt_sigreturn: + call do_rt_sigreturn + add %sp, STACKFRAME_SZ, %o0 + + ld [%curptr + TI_FLAGS], %l5 + andcc %l5, _TIF_SYSCALL_TRACE, %g0 + be 1f + nop + + call syscall_trace + nop + +1: + /* We are returning to a signal handler. */ + RESTORE_ALL + + /* Now that we have a real sys_clone, sys_fork() is + * implemented in terms of it. Our _real_ implementation + * of SunOS vfork() will use sys_vfork(). + * + * XXX These three should be consolidated into mostly shared + * XXX code just like on sparc64... -DaveM + */ + .align 4 + .globl sys_fork, flush_patch_two +sys_fork: + mov %o7, %l5 +flush_patch_two: + FLUSH_ALL_KERNEL_WINDOWS; + ld [%curptr + TI_TASK], %o4 + rd %psr, %g4 + WRITE_PAUSE + mov SIGCHLD, %o0 ! arg0: clone flags + rd %wim, %g5 + WRITE_PAUSE + mov %fp, %o1 ! arg1: usp + std %g4, [%o4 + AOFF_task_thread + AOFF_thread_fork_kpsr] + add %sp, STACKFRAME_SZ, %o2 ! arg2: pt_regs ptr + mov 0, %o3 + call sparc_do_fork + mov %l5, %o7 + + /* Whee, kernel threads! */ + .globl sys_clone, flush_patch_three +sys_clone: + mov %o7, %l5 +flush_patch_three: + FLUSH_ALL_KERNEL_WINDOWS; + ld [%curptr + TI_TASK], %o4 + rd %psr, %g4 + WRITE_PAUSE + + /* arg0,1: flags,usp -- loaded already */ + cmp %o1, 0x0 ! Is new_usp NULL? + rd %wim, %g5 + WRITE_PAUSE + be,a 1f + mov %fp, %o1 ! yes, use callers usp + andn %o1, 7, %o1 ! no, align to 8 bytes +1: + std %g4, [%o4 + AOFF_task_thread + AOFF_thread_fork_kpsr] + add %sp, STACKFRAME_SZ, %o2 ! arg2: pt_regs ptr + mov 0, %o3 + call sparc_do_fork + mov %l5, %o7 + + /* Whee, real vfork! */ + .globl sys_vfork, flush_patch_four +sys_vfork: +flush_patch_four: + FLUSH_ALL_KERNEL_WINDOWS; + ld [%curptr + TI_TASK], %o4 + rd %psr, %g4 + WRITE_PAUSE + rd %wim, %g5 + WRITE_PAUSE + std %g4, [%o4 + AOFF_task_thread + AOFF_thread_fork_kpsr] + sethi %hi(0x4000 | 0x0100 | SIGCHLD), %o0 + mov %fp, %o1 + or %o0, %lo(0x4000 | 0x0100 | SIGCHLD), %o0 + sethi %hi(sparc_do_fork), %l1 + mov 0, %o3 + jmpl %l1 + %lo(sparc_do_fork), %g0 + add %sp, STACKFRAME_SZ, %o2 + + .align 4 +linux_sparc_ni_syscall: + sethi %hi(sys_ni_syscall), %l7 + b syscall_is_too_hard + or %l7, %lo(sys_ni_syscall), %l7 + +linux_fast_syscall: + andn %l7, 3, %l7 + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + jmpl %l7 + %g0, %g0 + mov %i3, %o3 + +linux_syscall_trace: + call syscall_trace + nop + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + mov %i3, %o3 + b 2f + mov %i4, %o4 + + .globl ret_from_fork +ret_from_fork: + call schedule_tail + mov %g3, %o0 + b ret_sys_call + ld [%sp + STACKFRAME_SZ + PT_I0], %o0 + + /* Linux native and SunOS system calls enter here... */ + .align 4 + .globl linux_sparc_syscall +linux_sparc_syscall: + /* Direct access to user regs, must faster. */ + cmp %g1, NR_SYSCALLS + bgeu linux_sparc_ni_syscall + sll %g1, 2, %l4 + ld [%l7 + %l4], %l7 + andcc %l7, 1, %g0 + bne linux_fast_syscall + /* Just do first insn from SAVE_ALL in the delay slot */ + + .globl syscall_is_too_hard +syscall_is_too_hard: + SAVE_ALL_HEAD + rd %wim, %l3 + + wr %l0, PSR_ET, %psr + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + + ld [%curptr + TI_FLAGS], %l5 + mov %i3, %o3 + andcc %l5, _TIF_SYSCALL_TRACE, %g0 + mov %i4, %o4 + bne linux_syscall_trace + mov %i0, %l5 +2: + call %l7 + mov %i5, %o5 + + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + + .globl ret_sys_call +ret_sys_call: + ld [%curptr + TI_FLAGS], %l6 + cmp %o0, -ERESTART_RESTARTBLOCK + ld [%sp + STACKFRAME_SZ + PT_PSR], %g3 + set PSR_C, %g2 + bgeu 1f + andcc %l6, _TIF_SYSCALL_TRACE, %g0 + + /* System call success, clear Carry condition code. */ + andn %g3, %g2, %g3 + clr %l6 + st %g3, [%sp + STACKFRAME_SZ + PT_PSR] + bne linux_syscall_trace2 + ld [%sp + STACKFRAME_SZ + PT_NPC], %l1 /* pc = npc */ + add %l1, 0x4, %l2 /* npc = npc+4 */ + st %l1, [%sp + STACKFRAME_SZ + PT_PC] + b ret_trap_entry + st %l2, [%sp + STACKFRAME_SZ + PT_NPC] +1: + /* System call failure, set Carry condition code. + * Also, get abs(errno) to return to the process. + */ + sub %g0, %o0, %o0 + or %g3, %g2, %g3 + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + mov 1, %l6 + st %g3, [%sp + STACKFRAME_SZ + PT_PSR] + bne linux_syscall_trace2 + ld [%sp + STACKFRAME_SZ + PT_NPC], %l1 /* pc = npc */ + add %l1, 0x4, %l2 /* npc = npc+4 */ + st %l1, [%sp + STACKFRAME_SZ + PT_PC] + b ret_trap_entry + st %l2, [%sp + STACKFRAME_SZ + PT_NPC] + +linux_syscall_trace2: + call syscall_trace + add %l1, 0x4, %l2 /* npc = npc+4 */ + st %l1, [%sp + STACKFRAME_SZ + PT_PC] + b ret_trap_entry + st %l2, [%sp + STACKFRAME_SZ + PT_NPC] + + + /* + * Solaris system calls and indirect system calls enter here. + * + * I have named the solaris indirect syscalls like that because + * it seems like Solaris has some fast path syscalls that can + * be handled as indirect system calls. - mig + */ + +linux_syscall_for_solaris: + sethi %hi(sys_call_table), %l7 + b linux_sparc_syscall + or %l7, %lo(sys_call_table), %l7 + + .align 4 + .globl solaris_syscall +solaris_syscall: + cmp %g1,59 + be linux_syscall_for_solaris + cmp %g1,2 + be linux_syscall_for_solaris + cmp %g1,42 + be linux_syscall_for_solaris + cmp %g1,119 + be,a linux_syscall_for_solaris + mov 2, %g1 +1: + SAVE_ALL_HEAD + rd %wim, %l3 + + wr %l0, PSR_ET, %psr + nop + nop + mov %i0, %l5 + + call do_solaris_syscall + add %sp, STACKFRAME_SZ, %o0 + + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + set PSR_C, %g2 + cmp %o0, -ERESTART_RESTARTBLOCK + bgeu 1f + ld [%sp + STACKFRAME_SZ + PT_PSR], %g3 + + /* System call success, clear Carry condition code. */ + andn %g3, %g2, %g3 + clr %l6 + b 2f + st %g3, [%sp + STACKFRAME_SZ + PT_PSR] + +1: + /* System call failure, set Carry condition code. + * Also, get abs(errno) to return to the process. + */ + sub %g0, %o0, %o0 + mov 1, %l6 + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + or %g3, %g2, %g3 + st %g3, [%sp + STACKFRAME_SZ + PT_PSR] + + /* Advance the pc and npc over the trap instruction. + * If the npc is unaligned (has a 1 in the lower byte), it means + * the kernel does not want us to play magic (ie, skipping over + * traps). Mainly when the Solaris code wants to set some PC and + * nPC (setcontext). + */ +2: + ld [%sp + STACKFRAME_SZ + PT_NPC], %l1 /* pc = npc */ + andcc %l1, 1, %g0 + bne 1f + add %l1, 0x4, %l2 /* npc = npc+4 */ + st %l1, [%sp + STACKFRAME_SZ + PT_PC] + b ret_trap_entry + st %l2, [%sp + STACKFRAME_SZ + PT_NPC] + + /* kernel knows what it is doing, fixup npc and continue */ +1: + sub %l1, 1, %l1 + b ret_trap_entry + st %l1, [%sp + STACKFRAME_SZ + PT_NPC] + +#ifndef CONFIG_SUNOS_EMUL + .align 4 + .globl sunos_syscall +sunos_syscall: + SAVE_ALL_HEAD + rd %wim, %l3 + wr %l0, PSR_ET, %psr + nop + nop + mov %i0, %l5 + call do_sunos_syscall + add %sp, STACKFRAME_SZ, %o0 +#endif + + /* {net, open}bsd system calls enter here... */ + .align 4 + .globl bsd_syscall +bsd_syscall: + /* Direct access to user regs, must faster. */ + cmp %g1, NR_SYSCALLS + blu,a 1f + sll %g1, 2, %l4 + + set sys_ni_syscall, %l7 + b bsd_is_too_hard + nop + +1: + ld [%l7 + %l4], %l7 + + .globl bsd_is_too_hard +bsd_is_too_hard: + rd %wim, %l3 + SAVE_ALL + + wr %l0, PSR_ET, %psr + WRITE_PAUSE + +2: + mov %i0, %o0 + mov %i1, %o1 + mov %i2, %o2 + mov %i0, %l5 + mov %i3, %o3 + mov %i4, %o4 + call %l7 + mov %i5, %o5 + + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + set PSR_C, %g2 + cmp %o0, -ERESTART_RESTARTBLOCK + bgeu 1f + ld [%sp + STACKFRAME_SZ + PT_PSR], %g3 + + /* System call success, clear Carry condition code. */ + andn %g3, %g2, %g3 + clr %l6 + b 2f + st %g3, [%sp + STACKFRAME_SZ + PT_PSR] + +1: + /* System call failure, set Carry condition code. + * Also, get abs(errno) to return to the process. + */ + sub %g0, %o0, %o0 +#if 0 /* XXX todo XXX */ + sethi %hi(bsd_xlatb_rorl), %o3 + or %o3, %lo(bsd_xlatb_rorl), %o3 + sll %o0, 2, %o0 + ld [%o3 + %o0], %o0 +#endif + mov 1, %l6 + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + or %g3, %g2, %g3 + st %g3, [%sp + STACKFRAME_SZ + PT_PSR] + + /* Advance the pc and npc over the trap instruction. */ +2: + ld [%sp + STACKFRAME_SZ + PT_NPC], %l1 /* pc = npc */ + add %l1, 0x4, %l2 /* npc = npc+4 */ + st %l1, [%sp + STACKFRAME_SZ + PT_PC] + b ret_trap_entry + st %l2, [%sp + STACKFRAME_SZ + PT_NPC] + +/* Saving and restoring the FPU state is best done from lowlevel code. + * + * void fpsave(unsigned long *fpregs, unsigned long *fsr, + * void *fpqueue, unsigned long *fpqdepth) + */ + + .globl fpsave +fpsave: + st %fsr, [%o1] ! this can trap on us if fpu is in bogon state + ld [%o1], %g1 + set 0x2000, %g4 + andcc %g1, %g4, %g0 + be 2f + mov 0, %g2 + + /* We have an fpqueue to save. */ +1: + std %fq, [%o2] +fpsave_magic: + st %fsr, [%o1] + ld [%o1], %g3 + andcc %g3, %g4, %g0 + add %g2, 1, %g2 + bne 1b + add %o2, 8, %o2 + +2: + st %g2, [%o3] + + std %f0, [%o0 + 0x00] + std %f2, [%o0 + 0x08] + std %f4, [%o0 + 0x10] + std %f6, [%o0 + 0x18] + std %f8, [%o0 + 0x20] + std %f10, [%o0 + 0x28] + std %f12, [%o0 + 0x30] + std %f14, [%o0 + 0x38] + std %f16, [%o0 + 0x40] + std %f18, [%o0 + 0x48] + std %f20, [%o0 + 0x50] + std %f22, [%o0 + 0x58] + std %f24, [%o0 + 0x60] + std %f26, [%o0 + 0x68] + std %f28, [%o0 + 0x70] + retl + std %f30, [%o0 + 0x78] + + /* Thanks for Theo Deraadt and the authors of the Sprite/netbsd/openbsd + * code for pointing out this possible deadlock, while we save state + * above we could trap on the fsr store so our low level fpu trap + * code has to know how to deal with this. + */ +fpsave_catch: + b fpsave_magic + 4 + st %fsr, [%o1] + +fpsave_catch2: + b fpsave + 4 + st %fsr, [%o1] + + /* void fpload(unsigned long *fpregs, unsigned long *fsr); */ + + .globl fpload +fpload: + ldd [%o0 + 0x00], %f0 + ldd [%o0 + 0x08], %f2 + ldd [%o0 + 0x10], %f4 + ldd [%o0 + 0x18], %f6 + ldd [%o0 + 0x20], %f8 + ldd [%o0 + 0x28], %f10 + ldd [%o0 + 0x30], %f12 + ldd [%o0 + 0x38], %f14 + ldd [%o0 + 0x40], %f16 + ldd [%o0 + 0x48], %f18 + ldd [%o0 + 0x50], %f20 + ldd [%o0 + 0x58], %f22 + ldd [%o0 + 0x60], %f24 + ldd [%o0 + 0x68], %f26 + ldd [%o0 + 0x70], %f28 + ldd [%o0 + 0x78], %f30 + ld [%o1], %fsr + retl + nop + + /* __ndelay and __udelay take two arguments: + * 0 - nsecs or usecs to delay + * 1 - per_cpu udelay_val (loops per jiffy) + * + * Note that ndelay gives HZ times higher resolution but has a 10ms + * limit. udelay can handle up to 1s. + */ + .globl __ndelay +__ndelay: + save %sp, -STACKFRAME_SZ, %sp + mov %i0, %o0 + call .umul + mov 0x1ad, %o1 ! 2**32 / (1 000 000 000 / HZ) + call .umul + mov %i1, %o1 ! udelay_val + ba delay_continue + mov %o1, %o0 ! >>32 later for better resolution + + .globl __udelay +__udelay: + save %sp, -STACKFRAME_SZ, %sp + mov %i0, %o0 + sethi %hi(0x10c6), %o1 + call .umul + or %o1, %lo(0x10c6), %o1 ! 2**32 / 1 000 000 + call .umul + mov %i1, %o1 ! udelay_val + call .umul + mov HZ, %o0 ! >>32 earlier for wider range + +delay_continue: + cmp %o0, 0x0 +1: + bne 1b + subcc %o0, 1, %o0 + + ret + restore + + /* Handle a software breakpoint */ + /* We have to inform parent that child has stopped */ + .align 4 + .globl breakpoint_trap +breakpoint_trap: + rd %wim,%l3 + SAVE_ALL + wr %l0, PSR_ET, %psr + WRITE_PAUSE + + st %i0, [%sp + STACKFRAME_SZ + PT_G0] ! for restarting syscalls + call sparc_breakpoint + add %sp, STACKFRAME_SZ, %o0 + + RESTORE_ALL + + .align 4 + .globl __handle_exception, flush_patch_exception +__handle_exception: +flush_patch_exception: + FLUSH_ALL_KERNEL_WINDOWS; + ldd [%o0], %o6 + jmpl %o7 + 0xc, %g0 ! see asm-sparc/processor.h + mov 1, %g1 ! signal EFAULT condition + + .align 4 + .globl kill_user_windows, kuw_patch1_7win + .globl kuw_patch1 +kuw_patch1_7win: sll %o3, 6, %o3 + + /* No matter how much overhead this routine has in the worst + * case scenerio, it is several times better than taking the + * traps with the old method of just doing flush_user_windows(). + */ +kill_user_windows: + ld [%g6 + TI_UWINMASK], %o0 ! get current umask + orcc %g0, %o0, %g0 ! if no bits set, we are done + be 3f ! nothing to do + rd %psr, %o5 ! must clear interrupts + or %o5, PSR_PIL, %o4 ! or else that could change + wr %o4, 0x0, %psr ! the uwinmask state + WRITE_PAUSE ! burn them cycles +1: + ld [%g6 + TI_UWINMASK], %o0 ! get consistent state + orcc %g0, %o0, %g0 ! did an interrupt come in? + be 4f ! yep, we are done + rd %wim, %o3 ! get current wim + srl %o3, 1, %o4 ! simulate a save +kuw_patch1: + sll %o3, 7, %o3 ! compute next wim + or %o4, %o3, %o3 ! result + andncc %o0, %o3, %o0 ! clean this bit in umask + bne kuw_patch1 ! not done yet + srl %o3, 1, %o4 ! begin another save simulation + wr %o3, 0x0, %wim ! set the new wim + st %g0, [%g6 + TI_UWINMASK] ! clear uwinmask +4: + wr %o5, 0x0, %psr ! re-enable interrupts + WRITE_PAUSE ! burn baby burn +3: + retl ! return + st %g0, [%g6 + TI_W_SAVED] ! no windows saved + + .align 4 + .globl restore_current +restore_current: + LOAD_CURRENT(g6, o0) + retl + nop + +#ifdef CONFIG_PCI +#include <asm/pcic.h> + + .align 4 + .globl linux_trap_ipi15_pcic +linux_trap_ipi15_pcic: + rd %wim, %l3 + SAVE_ALL + + /* + * First deactivate NMI + * or we cannot drop ET, cannot get window spill traps. + * The busy loop is necessary because the PIO error + * sometimes does not go away quickly and we trap again. + */ + sethi %hi(pcic_regs), %o1 + ld [%o1 + %lo(pcic_regs)], %o2 + + ! Get pending status for printouts later. + ld [%o2 + PCI_SYS_INT_PENDING], %o0 + + mov PCI_SYS_INT_PENDING_CLEAR_ALL, %o1 + stb %o1, [%o2 + PCI_SYS_INT_PENDING_CLEAR] +1: + ld [%o2 + PCI_SYS_INT_PENDING], %o1 + andcc %o1, ((PCI_SYS_INT_PENDING_PIO|PCI_SYS_INT_PENDING_PCI)>>24), %g0 + bne 1b + nop + + or %l0, PSR_PIL, %l4 + wr %l4, 0x0, %psr + WRITE_PAUSE + wr %l4, PSR_ET, %psr + WRITE_PAUSE + + call pcic_nmi + add %sp, STACKFRAME_SZ, %o1 ! struct pt_regs *regs + RESTORE_ALL + + .globl pcic_nmi_trap_patch +pcic_nmi_trap_patch: + sethi %hi(linux_trap_ipi15_pcic), %l3 + jmpl %l3 + %lo(linux_trap_ipi15_pcic), %g0 + rd %psr, %l0 + .word 0 + +#endif /* CONFIG_PCI */ + +/* End of entry.S */ diff --git a/arch/sparc/kernel/errtbls.c b/arch/sparc/kernel/errtbls.c new file mode 100644 index 000000000000..bb36f6eadfee --- /dev/null +++ b/arch/sparc/kernel/errtbls.c @@ -0,0 +1,276 @@ +/* $Id: errtbls.c,v 1.2 1995/11/25 00:57:55 davem Exp $ + * errtbls.c: Error number conversion tables between various syscall + * OS semantics. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * + * Based upon preliminary work which is: + * + * Copyright (C) 1995 Adrian M. Rodriguez (adrian@remus.rutgers.edu) + */ + +#include <asm/bsderrno.h> /* NetBSD (bsd4.4) errnos */ +#include <asm/solerrno.h> /* Solaris errnos */ + +/* Here are tables which convert between Linux/SunOS error number + * values to the equivalent in other OSs. Note that since the Linux + * ones have been set up to match exactly those of SunOS, no + * translation table is needed for that OS. + */ + +int solaris_errno[] = { + 0, + SOL_EPERM, + SOL_ENOENT, + SOL_ESRCH, + SOL_EINTR, + SOL_EIO, + SOL_ENXIO, + SOL_E2BIG, + SOL_ENOEXEC, + SOL_EBADF, + SOL_ECHILD, + SOL_EAGAIN, + SOL_ENOMEM, + SOL_EACCES, + SOL_EFAULT, + SOL_NOTBLK, + SOL_EBUSY, + SOL_EEXIST, + SOL_EXDEV, + SOL_ENODEV, + SOL_ENOTDIR, + SOL_EISDIR, + SOL_EINVAL, + SOL_ENFILE, + SOL_EMFILE, + SOL_ENOTTY, + SOL_ETXTBSY, + SOL_EFBIG, + SOL_ENOSPC, + SOL_ESPIPE, + SOL_EROFS, + SOL_EMLINK, + SOL_EPIPE, + SOL_EDOM, + SOL_ERANGE, + SOL_EWOULDBLOCK, + SOL_EINPROGRESS, + SOL_EALREADY, + SOL_ENOTSOCK, + SOL_EDESTADDRREQ, + SOL_EMSGSIZE, + SOL_EPROTOTYPE, + SOL_ENOPROTOOPT, + SOL_EPROTONOSUPPORT, + SOL_ESOCKTNOSUPPORT, + SOL_EOPNOTSUPP, + SOL_EPFNOSUPPORT, + SOL_EAFNOSUPPORT, + SOL_EADDRINUSE, + SOL_EADDRNOTAVAIL, + SOL_ENETDOWN, + SOL_ENETUNREACH, + SOL_ENETRESET, + SOL_ECONNABORTED, + SOL_ECONNRESET, + SOL_ENOBUFS, + SOL_EISCONN, + SOL_ENOTONN, + SOL_ESHUTDOWN, + SOL_ETOOMANYREFS, + SOL_ETIMEDOUT, + SOL_ECONNREFUSED, + SOL_ELOOP, + SOL_ENAMETOOLONG, + SOL_EHOSTDOWN, + SOL_EHOSTUNREACH, + SOL_ENOTEMPTY, + SOL_EPROCLIM, + SOL_EUSERS, + SOL_EDQUOT, + SOL_ESTALE, + SOL_EREMOTE, + SOL_ENOSTR, + SOL_ETIME, + SOL_ENOSR, + SOL_ENOMSG, + SOL_EBADMSG, + SOL_IDRM, + SOL_EDEADLK, + SOL_ENOLCK, + SOL_ENONET, + SOL_ERREMOTE, + SOL_ENOLINK, + SOL_EADV, + SOL_ESRMNT, + SOL_ECOMM, + SOL_EPROTO, + SOL_EMULTIHOP, + SOL_EINVAL, /* EDOTDOT XXX??? */ + SOL_REMCHG, + SOL_NOSYS, + SOL_STRPIPE, + SOL_EOVERFLOW, + SOL_EBADFD, + SOL_ECHRNG, + SOL_EL2NSYNC, + SOL_EL3HLT, + SOL_EL3RST, + SOL_NRNG, + SOL_EUNATCH, + SOL_ENOCSI, + SOL_EL2HLT, + SOL_EBADE, + SOL_EBADR, + SOL_EXFULL, + SOL_ENOANO, + SOL_EBADRQC, + SOL_EBADSLT, + SOL_EDEADLOCK, + SOL_EBFONT, + SOL_ELIBEXEC, + SOL_ENODATA, + SOL_ELIBBAD, + SOL_ENOPKG, + SOL_ELIBACC, + SOL_ENOTUNIQ, + SOL_ERESTART, + SOL_EUCLEAN, + SOL_ENOTNAM, + SOL_ENAVAIL, + SOL_EISNAM, + SOL_EREMOTEIO, + SOL_EILSEQ, + SOL_ELIBMAX, + SOL_ELIBSCN, +}; + +int netbsd_errno[] = { + 0, + BSD_EPERM, + BSD_ENOENT, + BSD_ESRCH, + BSD_EINTR, + BSD_EIO, + BSD_ENXIO, + BSD_E2BIG, + BSD_ENOEXEC, + BSD_EBADF, + BSD_ECHILD, + BSD_EAGAIN, + BSD_ENOMEM, + BSD_EACCES, + BSD_EFAULT, + BSD_NOTBLK, + BSD_EBUSY, + BSD_EEXIST, + BSD_EXDEV, + BSD_ENODEV, + BSD_ENOTDIR, + BSD_EISDIR, + BSD_EINVAL, + BSD_ENFILE, + BSD_EMFILE, + BSD_ENOTTY, + BSD_ETXTBSY, + BSD_EFBIG, + BSD_ENOSPC, + BSD_ESPIPE, + BSD_EROFS, + BSD_EMLINK, + BSD_EPIPE, + BSD_EDOM, + BSD_ERANGE, + BSD_EWOULDBLOCK, + BSD_EINPROGRESS, + BSD_EALREADY, + BSD_ENOTSOCK, + BSD_EDESTADDRREQ, + BSD_EMSGSIZE, + BSD_EPROTOTYPE, + BSD_ENOPROTOOPT, + BSD_EPROTONOSUPPORT, + BSD_ESOCKTNOSUPPORT, + BSD_EOPNOTSUPP, + BSD_EPFNOSUPPORT, + BSD_EAFNOSUPPORT, + BSD_EADDRINUSE, + BSD_EADDRNOTAVAIL, + BSD_ENETDOWN, + BSD_ENETUNREACH, + BSD_ENETRESET, + BSD_ECONNABORTED, + BSD_ECONNRESET, + BSD_ENOBUFS, + BSD_EISCONN, + BSD_ENOTONN, + BSD_ESHUTDOWN, + BSD_ETOOMANYREFS, + BSD_ETIMEDOUT, + BSD_ECONNREFUSED, + BSD_ELOOP, + BSD_ENAMETOOLONG, + BSD_EHOSTDOWN, + BSD_EHOSTUNREACH, + BSD_ENOTEMPTY, + BSD_EPROCLIM, + BSD_EUSERS, + BSD_EDQUOT, + BSD_ESTALE, + BSD_EREMOTE, + BSD_ENOSTR, + BSD_ETIME, + BSD_ENOSR, + BSD_ENOMSG, + BSD_EBADMSG, + BSD_IDRM, + BSD_EDEADLK, + BSD_ENOLCK, + BSD_ENONET, + BSD_ERREMOTE, + BSD_ENOLINK, + BSD_EADV, + BSD_ESRMNT, + BSD_ECOMM, + BSD_EPROTO, + BSD_EMULTIHOP, + BSD_EINVAL, /* EDOTDOT XXX??? */ + BSD_REMCHG, + BSD_NOSYS, + BSD_STRPIPE, + BSD_EOVERFLOW, + BSD_EBADFD, + BSD_ECHRNG, + BSD_EL2NSYNC, + BSD_EL3HLT, + BSD_EL3RST, + BSD_NRNG, + BSD_EUNATCH, + BSD_ENOCSI, + BSD_EL2HLT, + BSD_EBADE, + BSD_EBADR, + BSD_EXFULL, + BSD_ENOANO, + BSD_EBADRQC, + BSD_EBADSLT, + BSD_EDEADLOCK, + BSD_EBFONT, + BSD_ELIBEXEC, + BSD_ENODATA, + BSD_ELIBBAD, + BSD_ENOPKG, + BSD_ELIBACC, + BSD_ENOTUNIQ, + BSD_ERESTART, + BSD_EUCLEAN, + BSD_ENOTNAM, + BSD_ENAVAIL, + BSD_EISNAM, + BSD_EREMOTEIO, + BSD_EILSEQ, + BSD_ELIBMAX, + BSD_ELIBSCN, +}; + diff --git a/arch/sparc/kernel/etrap.S b/arch/sparc/kernel/etrap.S new file mode 100644 index 000000000000..a8b35bed12a2 --- /dev/null +++ b/arch/sparc/kernel/etrap.S @@ -0,0 +1,321 @@ +/* $Id: etrap.S,v 1.31 2000/01/08 16:38:18 anton Exp $ + * etrap.S: Sparc trap window preparation for entry into the + * Linux kernel. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/head.h> +#include <asm/asi.h> +#include <asm/contregs.h> +#include <asm/page.h> +#include <asm/psr.h> +#include <asm/ptrace.h> +#include <asm/winmacro.h> +#include <asm/asmmacro.h> +#include <asm/thread_info.h> + +/* Registers to not touch at all. */ +#define t_psr l0 /* Set by caller */ +#define t_pc l1 /* Set by caller */ +#define t_npc l2 /* Set by caller */ +#define t_wim l3 /* Set by caller */ +#define t_twinmask l4 /* Set at beginning of this entry routine. */ +#define t_kstack l5 /* Set right before pt_regs frame is built */ +#define t_retpc l6 /* If you change this, change winmacro.h header file */ +#define t_systable l7 /* Never touch this, could be the syscall table ptr. */ +#define curptr g6 /* Set after pt_regs frame is built */ + + .text + .align 4 + + /* SEVEN WINDOW PATCH INSTRUCTIONS */ + .globl tsetup_7win_patch1, tsetup_7win_patch2 + .globl tsetup_7win_patch3, tsetup_7win_patch4 + .globl tsetup_7win_patch5, tsetup_7win_patch6 +tsetup_7win_patch1: sll %t_wim, 0x6, %t_wim +tsetup_7win_patch2: and %g2, 0x7f, %g2 +tsetup_7win_patch3: and %g2, 0x7f, %g2 +tsetup_7win_patch4: and %g1, 0x7f, %g1 +tsetup_7win_patch5: sll %t_wim, 0x6, %t_wim +tsetup_7win_patch6: and %g2, 0x7f, %g2 + /* END OF PATCH INSTRUCTIONS */ + + /* At trap time, interrupts and all generic traps do the + * following: + * + * rd %psr, %l0 + * b some_handler + * rd %wim, %l3 + * nop + * + * Then 'some_handler' if it needs a trap frame (ie. it has + * to call c-code and the trap cannot be handled in-window) + * then it does the SAVE_ALL macro in entry.S which does + * + * sethi %hi(trap_setup), %l4 + * jmpl %l4 + %lo(trap_setup), %l6 + * nop + */ + + /* 2 3 4 window number + * ----- + * O T S mnemonic + * + * O == Current window before trap + * T == Window entered when trap occurred + * S == Window we will need to save if (1<<T) == %wim + * + * Before execution gets here, it must be guaranteed that + * %l0 contains trap time %psr, %l1 and %l2 contain the + * trap pc and npc, and %l3 contains the trap time %wim. + */ + + .globl trap_setup, tsetup_patch1, tsetup_patch2 + .globl tsetup_patch3, tsetup_patch4 + .globl tsetup_patch5, tsetup_patch6 +trap_setup: + /* Calculate mask of trap window. See if from user + * or kernel and branch conditionally. + */ + mov 1, %t_twinmask + andcc %t_psr, PSR_PS, %g0 ! fromsupv_p = (psr & PSR_PS) + be trap_setup_from_user ! nope, from user mode + sll %t_twinmask, %t_psr, %t_twinmask ! t_twinmask = (1 << psr) + + /* From kernel, allocate more kernel stack and + * build a pt_regs trap frame. + */ + sub %fp, (STACKFRAME_SZ + TRACEREG_SZ), %t_kstack + STORE_PT_ALL(t_kstack, t_psr, t_pc, t_npc, g2) + + /* See if we are in the trap window. */ + andcc %t_twinmask, %t_wim, %g0 + bne trap_setup_kernel_spill ! in trap window, clean up + nop + + /* Trap from kernel with a window available. + * Just do it... + */ + jmpl %t_retpc + 0x8, %g0 ! return to caller + mov %t_kstack, %sp ! jump onto new stack + +trap_setup_kernel_spill: + ld [%curptr + TI_UWINMASK], %g1 + orcc %g0, %g1, %g0 + bne trap_setup_user_spill ! there are some user windows, yuck + /* Spill from kernel, but only kernel windows, adjust + * %wim and go. + */ + srl %t_wim, 0x1, %g2 ! begin computation of new %wim +tsetup_patch1: + sll %t_wim, 0x7, %t_wim ! patched on 7 window Sparcs + or %t_wim, %g2, %g2 +tsetup_patch2: + and %g2, 0xff, %g2 ! patched on 7 window Sparcs + + save %g0, %g0, %g0 + + /* Set new %wim value */ + wr %g2, 0x0, %wim + + /* Save the kernel window onto the corresponding stack. */ + STORE_WINDOW(sp) + + restore %g0, %g0, %g0 + + jmpl %t_retpc + 0x8, %g0 ! return to caller + mov %t_kstack, %sp ! and onto new kernel stack + +#define STACK_OFFSET (THREAD_SIZE - TRACEREG_SZ - STACKFRAME_SZ) + +trap_setup_from_user: + /* We can't use %curptr yet. */ + LOAD_CURRENT(t_kstack, t_twinmask) + + sethi %hi(STACK_OFFSET), %t_twinmask + or %t_twinmask, %lo(STACK_OFFSET), %t_twinmask + add %t_kstack, %t_twinmask, %t_kstack + + mov 1, %t_twinmask + sll %t_twinmask, %t_psr, %t_twinmask ! t_twinmask = (1 << psr) + + /* Build pt_regs frame. */ + STORE_PT_ALL(t_kstack, t_psr, t_pc, t_npc, g2) + +#if 0 + /* If we're sure every task_struct is THREAD_SIZE aligned, + we can speed this up. */ + sethi %hi(STACK_OFFSET), %curptr + or %curptr, %lo(STACK_OFFSET), %curptr + sub %t_kstack, %curptr, %curptr +#else + sethi %hi(~(THREAD_SIZE - 1)), %curptr + and %t_kstack, %curptr, %curptr +#endif + + /* Clear current_thread_info->w_saved */ + st %g0, [%curptr + TI_W_SAVED] + + /* See if we are in the trap window. */ + andcc %t_twinmask, %t_wim, %g0 + bne trap_setup_user_spill ! yep we are + orn %g0, %t_twinmask, %g1 ! negate trap win mask into %g1 + + /* Trap from user, but not into the invalid window. + * Calculate new umask. The way this works is, + * any window from the %wim at trap time until + * the window right before the one we are in now, + * is a user window. A diagram: + * + * 7 6 5 4 3 2 1 0 window number + * --------------- + * I L T mnemonic + * + * Window 'I' is the invalid window in our example, + * window 'L' is the window the user was in when + * the trap occurred, window T is the trap window + * we are in now. So therefore, windows 5, 4 and + * 3 are user windows. The following sequence + * computes the user winmask to represent this. + */ + subcc %t_wim, %t_twinmask, %g2 + bneg,a 1f + sub %g2, 0x1, %g2 +1: + andn %g2, %t_twinmask, %g2 +tsetup_patch3: + and %g2, 0xff, %g2 ! patched on 7win Sparcs + st %g2, [%curptr + TI_UWINMASK] ! store new umask + + jmpl %t_retpc + 0x8, %g0 ! return to caller + mov %t_kstack, %sp ! and onto kernel stack + +trap_setup_user_spill: + /* A spill occurred from either kernel or user mode + * and there exist some user windows to deal with. + * A mask of the currently valid user windows + * is in %g1 upon entry to here. + */ + +tsetup_patch4: + and %g1, 0xff, %g1 ! patched on 7win Sparcs, mask + srl %t_wim, 0x1, %g2 ! compute new %wim +tsetup_patch5: + sll %t_wim, 0x7, %t_wim ! patched on 7win Sparcs + or %t_wim, %g2, %g2 ! %g2 is new %wim +tsetup_patch6: + and %g2, 0xff, %g2 ! patched on 7win Sparcs + andn %g1, %g2, %g1 ! clear this bit in %g1 + st %g1, [%curptr + TI_UWINMASK] + + save %g0, %g0, %g0 + + wr %g2, 0x0, %wim + + /* Call MMU-architecture dependent stack checking + * routine. + */ + .globl tsetup_mmu_patchme +tsetup_mmu_patchme: + b tsetup_sun4c_stackchk + andcc %sp, 0x7, %g0 + + /* Architecture specific stack checking routines. When either + * of these routines are called, the globals are free to use + * as they have been safely stashed on the new kernel stack + * pointer. Thus the definition below for simplicity. + */ +#define glob_tmp g1 + + .globl tsetup_sun4c_stackchk +tsetup_sun4c_stackchk: + /* Done by caller: andcc %sp, 0x7, %g0 */ + bne trap_setup_user_stack_is_bolixed + sra %sp, 29, %glob_tmp + + add %glob_tmp, 0x1, %glob_tmp + andncc %glob_tmp, 0x1, %g0 + bne trap_setup_user_stack_is_bolixed + and %sp, 0xfff, %glob_tmp ! delay slot + + /* See if our dump area will be on more than one + * page. + */ + add %glob_tmp, 0x38, %glob_tmp + andncc %glob_tmp, 0xff8, %g0 + be tsetup_sun4c_onepage ! only one page to check + lda [%sp] ASI_PTE, %glob_tmp ! have to check first page anyways + +tsetup_sun4c_twopages: + /* Is first page ok permission wise? */ + srl %glob_tmp, 29, %glob_tmp + cmp %glob_tmp, 0x6 + bne trap_setup_user_stack_is_bolixed + add %sp, 0x38, %glob_tmp /* Is second page in vma hole? */ + + sra %glob_tmp, 29, %glob_tmp + add %glob_tmp, 0x1, %glob_tmp + andncc %glob_tmp, 0x1, %g0 + bne trap_setup_user_stack_is_bolixed + add %sp, 0x38, %glob_tmp + + lda [%glob_tmp] ASI_PTE, %glob_tmp + +tsetup_sun4c_onepage: + srl %glob_tmp, 29, %glob_tmp + cmp %glob_tmp, 0x6 ! can user write to it? + bne trap_setup_user_stack_is_bolixed ! failure + nop + + STORE_WINDOW(sp) + + restore %g0, %g0, %g0 + + jmpl %t_retpc + 0x8, %g0 + mov %t_kstack, %sp + + .globl tsetup_srmmu_stackchk +tsetup_srmmu_stackchk: + /* Check results of callers andcc %sp, 0x7, %g0 */ + bne trap_setup_user_stack_is_bolixed + sethi %hi(PAGE_OFFSET), %glob_tmp + + cmp %glob_tmp, %sp + bleu,a 1f + lda [%g0] ASI_M_MMUREGS, %glob_tmp ! read MMU control + +trap_setup_user_stack_is_bolixed: + /* From user/kernel into invalid window w/bad user + * stack. Save bad user stack, and return to caller. + */ + SAVE_BOLIXED_USER_STACK(curptr, g3) + restore %g0, %g0, %g0 + + jmpl %t_retpc + 0x8, %g0 + mov %t_kstack, %sp + +1: + /* Clear the fault status and turn on the no_fault bit. */ + or %glob_tmp, 0x2, %glob_tmp ! or in no_fault bit + sta %glob_tmp, [%g0] ASI_M_MMUREGS ! set it + + /* Dump the registers and cross fingers. */ + STORE_WINDOW(sp) + + /* Clear the no_fault bit and check the status. */ + andn %glob_tmp, 0x2, %glob_tmp + sta %glob_tmp, [%g0] ASI_M_MMUREGS + mov AC_M_SFAR, %glob_tmp + lda [%glob_tmp] ASI_M_MMUREGS, %g0 + mov AC_M_SFSR, %glob_tmp + lda [%glob_tmp] ASI_M_MMUREGS, %glob_tmp ! save away status of winstore + andcc %glob_tmp, 0x2, %g0 ! did we fault? + bne trap_setup_user_stack_is_bolixed ! failure + nop + + restore %g0, %g0, %g0 + + jmpl %t_retpc + 0x8, %g0 + mov %t_kstack, %sp + diff --git a/arch/sparc/kernel/head.S b/arch/sparc/kernel/head.S new file mode 100644 index 000000000000..42d3de59d19b --- /dev/null +++ b/arch/sparc/kernel/head.S @@ -0,0 +1,1326 @@ +/* $Id: head.S,v 1.105 2001/08/12 09:08:56 davem Exp $ + * head.S: The initial boot code for the Sparc port of Linux. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995,1999 Pete Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1997 Michael A. Griffith (grif@acm.org) + * + * CompactPCI platform by Eric Brower, 1999. + */ + +#include <linux/version.h> +#include <linux/config.h> +#include <linux/init.h> + +#include <asm/head.h> +#include <asm/asi.h> +#include <asm/contregs.h> +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/page.h> +#include <asm/kdebug.h> +#include <asm/winmacro.h> +#include <asm/thread_info.h> /* TI_UWINMASK */ +#include <asm/errno.h> +#include <asm/pgtsrmmu.h> /* SRMMU_PGDIR_SHIFT */ + + .data +/* + * The following are used with the prom_vector node-ops to figure out + * the cpu-type + */ + + .align 4 + .globl cputyp +cputyp: + .word 1 + + .align 4 + .globl cputypval +cputypval: + .asciz "sun4c" + .ascii " " + +cputypvalend: +cputypvallen = cputypvar - cputypval + + .align 4 +/* + * Sun people can't spell worth damn. "compatability" indeed. + * At least we *know* we can't spell, and use a spell-checker. + */ + +/* Uh, actually Linus it is I who cannot spell. Too much murky + * Sparc assembly will do this to ya. + */ +cputypvar: + .asciz "compatability" + +/* Tested on SS-5, SS-10. Probably someone at Sun applied a spell-checker. */ + .align 4 +cputypvar_sun4m: + .asciz "compatible" + + .align 4 + +#ifndef CONFIG_SUN4 +sun4_notsup: + .asciz "Sparc-Linux sun4 needs a specially compiled kernel, turn CONFIG_SUN4 on.\n\n" + .align 4 +#else +sun4cdm_notsup: + .asciz "Kernel compiled with CONFIG_SUN4 cannot run on SUN4C/SUN4M/SUN4D\nTurn CONFIG_SUN4 off.\n\n" + .align 4 +#endif + +sun4e_notsup: + .asciz "Sparc-Linux sun4e support does not exist\n\n" + .align 4 + +#ifndef CONFIG_SUNOS_EMUL +#undef SUNOS_SYSCALL_TRAP +#define SUNOS_SYSCALL_TRAP SUNOS_NO_SYSCALL_TRAP +#endif + + /* The Sparc trap table, bootloader gives us control at _start. */ + .text + .globl start, _stext, _start, __stext + .globl trapbase +_start: /* danger danger */ +__stext: +_stext: +start: +trapbase: +#ifdef CONFIG_SMP +trapbase_cpu0: +#endif +/* We get control passed to us here at t_zero. */ +t_zero: b gokernel; nop; nop; nop; +t_tflt: SPARC_TFAULT /* Inst. Access Exception */ +t_bins: TRAP_ENTRY(0x2, bad_instruction) /* Illegal Instruction */ +t_pins: TRAP_ENTRY(0x3, priv_instruction) /* Privileged Instruction */ +t_fpd: TRAP_ENTRY(0x4, fpd_trap_handler) /* Floating Point Disabled */ +t_wovf: WINDOW_SPILL /* Window Overflow */ +t_wunf: WINDOW_FILL /* Window Underflow */ +t_mna: TRAP_ENTRY(0x7, mna_handler) /* Memory Address Not Aligned */ +t_fpe: TRAP_ENTRY(0x8, fpe_trap_handler) /* Floating Point Exception */ +t_dflt: SPARC_DFAULT /* Data Miss Exception */ +t_tio: TRAP_ENTRY(0xa, do_tag_overflow) /* Tagged Instruction Ovrflw */ +t_wpt: TRAP_ENTRY(0xb, do_watchpoint) /* Watchpoint Detected */ +t_badc: BAD_TRAP(0xc) BAD_TRAP(0xd) BAD_TRAP(0xe) BAD_TRAP(0xf) BAD_TRAP(0x10) +t_irq1: TRAP_ENTRY_INTERRUPT(1) /* IRQ Software/SBUS Level 1 */ +t_irq2: TRAP_ENTRY_INTERRUPT(2) /* IRQ SBUS Level 2 */ +t_irq3: TRAP_ENTRY_INTERRUPT(3) /* IRQ SCSI/DMA/SBUS Level 3 */ +t_irq4: TRAP_ENTRY_INTERRUPT(4) /* IRQ Software Level 4 */ +t_irq5: TRAP_ENTRY_INTERRUPT(5) /* IRQ SBUS/Ethernet Level 5 */ +t_irq6: TRAP_ENTRY_INTERRUPT(6) /* IRQ Software Level 6 */ +t_irq7: TRAP_ENTRY_INTERRUPT(7) /* IRQ Video/SBUS Level 5 */ +t_irq8: TRAP_ENTRY_INTERRUPT(8) /* IRQ SBUS Level 6 */ +t_irq9: TRAP_ENTRY_INTERRUPT(9) /* IRQ SBUS Level 7 */ +t_irq10:TRAP_ENTRY_INTERRUPT(10) /* IRQ Timer #1 (one we use) */ +t_irq11:TRAP_ENTRY_INTERRUPT(11) /* IRQ Floppy Intr. */ +t_irq12:TRAP_ENTRY_INTERRUPT(12) /* IRQ Zilog serial chip */ +t_irq13:TRAP_ENTRY_INTERRUPT(13) /* IRQ Audio Intr. */ +t_irq14:TRAP_ENTRY_INTERRUPT(14) /* IRQ Timer #2 */ + .globl t_nmi +#ifndef CONFIG_SMP +t_nmi: NMI_TRAP /* Level 15 (NMI) */ +#else +t_nmi: TRAP_ENTRY(0x1f, linux_trap_ipi15_sun4m) +#endif +t_racc: TRAP_ENTRY(0x20, do_reg_access) /* General Register Access Error */ +t_iacce:BAD_TRAP(0x21) /* Instr Access Error */ +t_bad22:BAD_TRAP(0x22) BAD_TRAP(0x23) +t_cpdis:TRAP_ENTRY(0x24, do_cp_disabled) /* Co-Processor Disabled */ +t_uflsh:SKIP_TRAP(0x25, unimp_flush) /* Unimplemented FLUSH inst. */ +t_bad26:BAD_TRAP(0x26) BAD_TRAP(0x27) +t_cpexc:TRAP_ENTRY(0x28, do_cp_exception) /* Co-Processor Exception */ +t_dacce:SPARC_DFAULT /* Data Access Error */ +t_hwdz: TRAP_ENTRY(0x2a, do_hw_divzero) /* Division by zero, you lose... */ +t_dserr:BAD_TRAP(0x2b) /* Data Store Error */ +t_daccm:BAD_TRAP(0x2c) /* Data Access MMU-Miss */ +t_bad2d:BAD_TRAP(0x2d) BAD_TRAP(0x2e) BAD_TRAP(0x2f) BAD_TRAP(0x30) BAD_TRAP(0x31) +t_bad32:BAD_TRAP(0x32) BAD_TRAP(0x33) BAD_TRAP(0x34) BAD_TRAP(0x35) BAD_TRAP(0x36) +t_bad37:BAD_TRAP(0x37) BAD_TRAP(0x38) BAD_TRAP(0x39) BAD_TRAP(0x3a) BAD_TRAP(0x3b) +t_iaccm:BAD_TRAP(0x3c) /* Instr Access MMU-Miss */ +t_bad3d:BAD_TRAP(0x3d) BAD_TRAP(0x3e) BAD_TRAP(0x3f) BAD_TRAP(0x40) BAD_TRAP(0x41) +t_bad42:BAD_TRAP(0x42) BAD_TRAP(0x43) BAD_TRAP(0x44) BAD_TRAP(0x45) BAD_TRAP(0x46) +t_bad47:BAD_TRAP(0x47) BAD_TRAP(0x48) BAD_TRAP(0x49) BAD_TRAP(0x4a) BAD_TRAP(0x4b) +t_bad4c:BAD_TRAP(0x4c) BAD_TRAP(0x4d) BAD_TRAP(0x4e) BAD_TRAP(0x4f) BAD_TRAP(0x50) +t_bad51:BAD_TRAP(0x51) BAD_TRAP(0x52) BAD_TRAP(0x53) BAD_TRAP(0x54) BAD_TRAP(0x55) +t_bad56:BAD_TRAP(0x56) BAD_TRAP(0x57) BAD_TRAP(0x58) BAD_TRAP(0x59) BAD_TRAP(0x5a) +t_bad5b:BAD_TRAP(0x5b) BAD_TRAP(0x5c) BAD_TRAP(0x5d) BAD_TRAP(0x5e) BAD_TRAP(0x5f) +t_bad60:BAD_TRAP(0x60) BAD_TRAP(0x61) BAD_TRAP(0x62) BAD_TRAP(0x63) BAD_TRAP(0x64) +t_bad65:BAD_TRAP(0x65) BAD_TRAP(0x66) BAD_TRAP(0x67) BAD_TRAP(0x68) BAD_TRAP(0x69) +t_bad6a:BAD_TRAP(0x6a) BAD_TRAP(0x6b) BAD_TRAP(0x6c) BAD_TRAP(0x6d) BAD_TRAP(0x6e) +t_bad6f:BAD_TRAP(0x6f) BAD_TRAP(0x70) BAD_TRAP(0x71) BAD_TRAP(0x72) BAD_TRAP(0x73) +t_bad74:BAD_TRAP(0x74) BAD_TRAP(0x75) BAD_TRAP(0x76) BAD_TRAP(0x77) BAD_TRAP(0x78) +t_bad79:BAD_TRAP(0x79) BAD_TRAP(0x7a) BAD_TRAP(0x7b) BAD_TRAP(0x7c) BAD_TRAP(0x7d) +t_bad7e:BAD_TRAP(0x7e) BAD_TRAP(0x7f) +t_sunos:SUNOS_SYSCALL_TRAP /* SunOS System Call */ +t_sbkpt:BREAKPOINT_TRAP /* Software Breakpoint/KGDB */ +t_divz: TRAP_ENTRY(0x82, do_hw_divzero) /* Divide by zero trap */ +t_flwin:TRAP_ENTRY(0x83, do_flush_windows) /* Flush Windows Trap */ +t_clwin:BAD_TRAP(0x84) /* Clean Windows Trap */ +t_rchk: BAD_TRAP(0x85) /* Range Check */ +t_funal:BAD_TRAP(0x86) /* Fix Unaligned Access Trap */ +t_iovf: BAD_TRAP(0x87) /* Integer Overflow Trap */ +t_slowl:SOLARIS_SYSCALL_TRAP /* Slowaris System Call */ +t_netbs:NETBSD_SYSCALL_TRAP /* Net-B.S. System Call */ +t_bad8a:BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c) BAD_TRAP(0x8d) BAD_TRAP(0x8e) +t_bad8f:BAD_TRAP(0x8f) +t_linux:LINUX_SYSCALL_TRAP /* Linux System Call */ +t_bad91:BAD_TRAP(0x91) BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94) BAD_TRAP(0x95) +t_bad96:BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99) BAD_TRAP(0x9a) +t_bad9b:BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e) BAD_TRAP(0x9f) +t_getcc:GETCC_TRAP /* Get Condition Codes */ +t_setcc:SETCC_TRAP /* Set Condition Codes */ +t_getpsr:GETPSR_TRAP /* Get PSR Register */ +t_bada3:BAD_TRAP(0xa3) BAD_TRAP(0xa4) BAD_TRAP(0xa5) BAD_TRAP(0xa6) +t_slowi:INDIRECT_SOLARIS_SYSCALL(156) +t_bada8:BAD_TRAP(0xa8) BAD_TRAP(0xa9) BAD_TRAP(0xaa) BAD_TRAP(0xab) +t_badac:BAD_TRAP(0xac) BAD_TRAP(0xad) BAD_TRAP(0xae) BAD_TRAP(0xaf) BAD_TRAP(0xb0) +t_badb1:BAD_TRAP(0xb1) BAD_TRAP(0xb2) BAD_TRAP(0xb3) BAD_TRAP(0xb4) BAD_TRAP(0xb5) +t_badb6:BAD_TRAP(0xb6) BAD_TRAP(0xb7) BAD_TRAP(0xb8) BAD_TRAP(0xb9) BAD_TRAP(0xba) +t_badbb:BAD_TRAP(0xbb) BAD_TRAP(0xbc) BAD_TRAP(0xbd) BAD_TRAP(0xbe) BAD_TRAP(0xbf) +t_badc0:BAD_TRAP(0xc0) BAD_TRAP(0xc1) BAD_TRAP(0xc2) BAD_TRAP(0xc3) BAD_TRAP(0xc4) +t_badc5:BAD_TRAP(0xc5) BAD_TRAP(0xc6) BAD_TRAP(0xc7) BAD_TRAP(0xc8) BAD_TRAP(0xc9) +t_badca:BAD_TRAP(0xca) BAD_TRAP(0xcb) BAD_TRAP(0xcc) BAD_TRAP(0xcd) BAD_TRAP(0xce) +t_badcf:BAD_TRAP(0xcf) BAD_TRAP(0xd0) BAD_TRAP(0xd1) BAD_TRAP(0xd2) BAD_TRAP(0xd3) +t_badd4:BAD_TRAP(0xd4) BAD_TRAP(0xd5) BAD_TRAP(0xd6) BAD_TRAP(0xd7) BAD_TRAP(0xd8) +t_badd9:BAD_TRAP(0xd9) BAD_TRAP(0xda) BAD_TRAP(0xdb) BAD_TRAP(0xdc) BAD_TRAP(0xdd) +t_badde:BAD_TRAP(0xde) BAD_TRAP(0xdf) BAD_TRAP(0xe0) BAD_TRAP(0xe1) BAD_TRAP(0xe2) +t_bade3:BAD_TRAP(0xe3) BAD_TRAP(0xe4) BAD_TRAP(0xe5) BAD_TRAP(0xe6) BAD_TRAP(0xe7) +t_bade8:BAD_TRAP(0xe8) BAD_TRAP(0xe9) BAD_TRAP(0xea) BAD_TRAP(0xeb) BAD_TRAP(0xec) +t_baded:BAD_TRAP(0xed) BAD_TRAP(0xee) BAD_TRAP(0xef) BAD_TRAP(0xf0) BAD_TRAP(0xf1) +t_badf2:BAD_TRAP(0xf2) BAD_TRAP(0xf3) BAD_TRAP(0xf4) BAD_TRAP(0xf5) BAD_TRAP(0xf6) +t_badf7:BAD_TRAP(0xf7) BAD_TRAP(0xf8) BAD_TRAP(0xf9) BAD_TRAP(0xfa) BAD_TRAP(0xfb) +t_badfc:BAD_TRAP(0xfc) BAD_TRAP(0xfd) +dbtrap: BAD_TRAP(0xfe) /* Debugger/PROM breakpoint #1 */ +dbtrap2:BAD_TRAP(0xff) /* Debugger/PROM breakpoint #2 */ + + .globl end_traptable +end_traptable: + +#ifdef CONFIG_SMP + /* Trap tables for the other cpus. */ + .globl trapbase_cpu1, trapbase_cpu2, trapbase_cpu3 +trapbase_cpu1: + BAD_TRAP(0x0) SRMMU_TFAULT TRAP_ENTRY(0x2, bad_instruction) + TRAP_ENTRY(0x3, priv_instruction) TRAP_ENTRY(0x4, fpd_trap_handler) + WINDOW_SPILL WINDOW_FILL TRAP_ENTRY(0x7, mna_handler) + TRAP_ENTRY(0x8, fpe_trap_handler) SRMMU_DFAULT + TRAP_ENTRY(0xa, do_tag_overflow) TRAP_ENTRY(0xb, do_watchpoint) + BAD_TRAP(0xc) BAD_TRAP(0xd) BAD_TRAP(0xe) BAD_TRAP(0xf) BAD_TRAP(0x10) + TRAP_ENTRY_INTERRUPT(1) TRAP_ENTRY_INTERRUPT(2) + TRAP_ENTRY_INTERRUPT(3) TRAP_ENTRY_INTERRUPT(4) + TRAP_ENTRY_INTERRUPT(5) TRAP_ENTRY_INTERRUPT(6) + TRAP_ENTRY_INTERRUPT(7) TRAP_ENTRY_INTERRUPT(8) + TRAP_ENTRY_INTERRUPT(9) TRAP_ENTRY_INTERRUPT(10) + TRAP_ENTRY_INTERRUPT(11) TRAP_ENTRY_INTERRUPT(12) + TRAP_ENTRY_INTERRUPT(13) TRAP_ENTRY_INTERRUPT(14) + TRAP_ENTRY(0x1f, linux_trap_ipi15_sun4m) + TRAP_ENTRY(0x20, do_reg_access) BAD_TRAP(0x21) BAD_TRAP(0x22) + BAD_TRAP(0x23) TRAP_ENTRY(0x24, do_cp_disabled) SKIP_TRAP(0x25, unimp_flush) + BAD_TRAP(0x26) BAD_TRAP(0x27) TRAP_ENTRY(0x28, do_cp_exception) + SRMMU_DFAULT TRAP_ENTRY(0x2a, do_hw_divzero) BAD_TRAP(0x2b) BAD_TRAP(0x2c) + BAD_TRAP(0x2d) BAD_TRAP(0x2e) BAD_TRAP(0x2f) BAD_TRAP(0x30) BAD_TRAP(0x31) + BAD_TRAP(0x32) BAD_TRAP(0x33) BAD_TRAP(0x34) BAD_TRAP(0x35) BAD_TRAP(0x36) + BAD_TRAP(0x37) BAD_TRAP(0x38) BAD_TRAP(0x39) BAD_TRAP(0x3a) BAD_TRAP(0x3b) + BAD_TRAP(0x3c) BAD_TRAP(0x3d) BAD_TRAP(0x3e) BAD_TRAP(0x3f) BAD_TRAP(0x40) + BAD_TRAP(0x41) BAD_TRAP(0x42) BAD_TRAP(0x43) BAD_TRAP(0x44) BAD_TRAP(0x45) + BAD_TRAP(0x46) BAD_TRAP(0x47) BAD_TRAP(0x48) BAD_TRAP(0x49) BAD_TRAP(0x4a) + BAD_TRAP(0x4b) BAD_TRAP(0x4c) BAD_TRAP(0x4d) BAD_TRAP(0x4e) BAD_TRAP(0x4f) + BAD_TRAP(0x50) + BAD_TRAP(0x51) BAD_TRAP(0x52) BAD_TRAP(0x53) BAD_TRAP(0x54) BAD_TRAP(0x55) + BAD_TRAP(0x56) BAD_TRAP(0x57) BAD_TRAP(0x58) BAD_TRAP(0x59) BAD_TRAP(0x5a) + BAD_TRAP(0x5b) BAD_TRAP(0x5c) BAD_TRAP(0x5d) BAD_TRAP(0x5e) BAD_TRAP(0x5f) + BAD_TRAP(0x60) BAD_TRAP(0x61) BAD_TRAP(0x62) BAD_TRAP(0x63) BAD_TRAP(0x64) + BAD_TRAP(0x65) BAD_TRAP(0x66) BAD_TRAP(0x67) BAD_TRAP(0x68) BAD_TRAP(0x69) + BAD_TRAP(0x6a) BAD_TRAP(0x6b) BAD_TRAP(0x6c) BAD_TRAP(0x6d) BAD_TRAP(0x6e) + BAD_TRAP(0x6f) BAD_TRAP(0x70) BAD_TRAP(0x71) BAD_TRAP(0x72) BAD_TRAP(0x73) + BAD_TRAP(0x74) BAD_TRAP(0x75) BAD_TRAP(0x76) BAD_TRAP(0x77) BAD_TRAP(0x78) + BAD_TRAP(0x79) BAD_TRAP(0x7a) BAD_TRAP(0x7b) BAD_TRAP(0x7c) BAD_TRAP(0x7d) + BAD_TRAP(0x7e) BAD_TRAP(0x7f) + SUNOS_SYSCALL_TRAP + BREAKPOINT_TRAP + TRAP_ENTRY(0x82, do_hw_divzero) + TRAP_ENTRY(0x83, do_flush_windows) BAD_TRAP(0x84) BAD_TRAP(0x85) + BAD_TRAP(0x86) BAD_TRAP(0x87) SOLARIS_SYSCALL_TRAP + NETBSD_SYSCALL_TRAP BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c) + BAD_TRAP(0x8d) BAD_TRAP(0x8e) BAD_TRAP(0x8f) + LINUX_SYSCALL_TRAP BAD_TRAP(0x91) BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94) + BAD_TRAP(0x95) BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99) + BAD_TRAP(0x9a) BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e) + BAD_TRAP(0x9f) GETCC_TRAP SETCC_TRAP GETPSR_TRAP + BAD_TRAP(0xa3) BAD_TRAP(0xa4) BAD_TRAP(0xa5) BAD_TRAP(0xa6) + INDIRECT_SOLARIS_SYSCALL(156) BAD_TRAP(0xa8) BAD_TRAP(0xa9) BAD_TRAP(0xaa) BAD_TRAP(0xab) + BAD_TRAP(0xac) BAD_TRAP(0xad) BAD_TRAP(0xae) BAD_TRAP(0xaf) BAD_TRAP(0xb0) + BAD_TRAP(0xb1) BAD_TRAP(0xb2) BAD_TRAP(0xb3) BAD_TRAP(0xb4) BAD_TRAP(0xb5) + BAD_TRAP(0xb6) BAD_TRAP(0xb7) BAD_TRAP(0xb8) BAD_TRAP(0xb9) BAD_TRAP(0xba) + BAD_TRAP(0xbb) BAD_TRAP(0xbc) BAD_TRAP(0xbd) BAD_TRAP(0xbe) BAD_TRAP(0xbf) + BAD_TRAP(0xc0) BAD_TRAP(0xc1) BAD_TRAP(0xc2) BAD_TRAP(0xc3) BAD_TRAP(0xc4) + BAD_TRAP(0xc5) BAD_TRAP(0xc6) BAD_TRAP(0xc7) BAD_TRAP(0xc8) BAD_TRAP(0xc9) + BAD_TRAP(0xca) BAD_TRAP(0xcb) BAD_TRAP(0xcc) BAD_TRAP(0xcd) BAD_TRAP(0xce) + BAD_TRAP(0xcf) BAD_TRAP(0xd0) BAD_TRAP(0xd1) BAD_TRAP(0xd2) BAD_TRAP(0xd3) + BAD_TRAP(0xd4) BAD_TRAP(0xd5) BAD_TRAP(0xd6) BAD_TRAP(0xd7) BAD_TRAP(0xd8) + BAD_TRAP(0xd9) BAD_TRAP(0xda) BAD_TRAP(0xdb) BAD_TRAP(0xdc) BAD_TRAP(0xdd) + BAD_TRAP(0xde) BAD_TRAP(0xdf) BAD_TRAP(0xe0) BAD_TRAP(0xe1) BAD_TRAP(0xe2) + BAD_TRAP(0xe3) BAD_TRAP(0xe4) BAD_TRAP(0xe5) BAD_TRAP(0xe6) BAD_TRAP(0xe7) + BAD_TRAP(0xe8) BAD_TRAP(0xe9) BAD_TRAP(0xea) BAD_TRAP(0xeb) BAD_TRAP(0xec) + BAD_TRAP(0xed) BAD_TRAP(0xee) BAD_TRAP(0xef) BAD_TRAP(0xf0) BAD_TRAP(0xf1) + BAD_TRAP(0xf2) BAD_TRAP(0xf3) BAD_TRAP(0xf4) BAD_TRAP(0xf5) BAD_TRAP(0xf6) + BAD_TRAP(0xf7) BAD_TRAP(0xf8) BAD_TRAP(0xf9) BAD_TRAP(0xfa) BAD_TRAP(0xfb) + BAD_TRAP(0xfc) BAD_TRAP(0xfd) BAD_TRAP(0xfe) BAD_TRAP(0xff) + +trapbase_cpu2: + BAD_TRAP(0x0) SRMMU_TFAULT TRAP_ENTRY(0x2, bad_instruction) + TRAP_ENTRY(0x3, priv_instruction) TRAP_ENTRY(0x4, fpd_trap_handler) + WINDOW_SPILL WINDOW_FILL TRAP_ENTRY(0x7, mna_handler) + TRAP_ENTRY(0x8, fpe_trap_handler) SRMMU_DFAULT + TRAP_ENTRY(0xa, do_tag_overflow) TRAP_ENTRY(0xb, do_watchpoint) + BAD_TRAP(0xc) BAD_TRAP(0xd) BAD_TRAP(0xe) BAD_TRAP(0xf) BAD_TRAP(0x10) + TRAP_ENTRY_INTERRUPT(1) TRAP_ENTRY_INTERRUPT(2) + TRAP_ENTRY_INTERRUPT(3) TRAP_ENTRY_INTERRUPT(4) + TRAP_ENTRY_INTERRUPT(5) TRAP_ENTRY_INTERRUPT(6) + TRAP_ENTRY_INTERRUPT(7) TRAP_ENTRY_INTERRUPT(8) + TRAP_ENTRY_INTERRUPT(9) TRAP_ENTRY_INTERRUPT(10) + TRAP_ENTRY_INTERRUPT(11) TRAP_ENTRY_INTERRUPT(12) + TRAP_ENTRY_INTERRUPT(13) TRAP_ENTRY_INTERRUPT(14) + TRAP_ENTRY(0x1f, linux_trap_ipi15_sun4m) + TRAP_ENTRY(0x20, do_reg_access) BAD_TRAP(0x21) BAD_TRAP(0x22) + BAD_TRAP(0x23) TRAP_ENTRY(0x24, do_cp_disabled) SKIP_TRAP(0x25, unimp_flush) + BAD_TRAP(0x26) BAD_TRAP(0x27) TRAP_ENTRY(0x28, do_cp_exception) + SRMMU_DFAULT TRAP_ENTRY(0x2a, do_hw_divzero) BAD_TRAP(0x2b) BAD_TRAP(0x2c) + BAD_TRAP(0x2d) BAD_TRAP(0x2e) BAD_TRAP(0x2f) BAD_TRAP(0x30) BAD_TRAP(0x31) + BAD_TRAP(0x32) BAD_TRAP(0x33) BAD_TRAP(0x34) BAD_TRAP(0x35) BAD_TRAP(0x36) + BAD_TRAP(0x37) BAD_TRAP(0x38) BAD_TRAP(0x39) BAD_TRAP(0x3a) BAD_TRAP(0x3b) + BAD_TRAP(0x3c) BAD_TRAP(0x3d) BAD_TRAP(0x3e) BAD_TRAP(0x3f) BAD_TRAP(0x40) + BAD_TRAP(0x41) BAD_TRAP(0x42) BAD_TRAP(0x43) BAD_TRAP(0x44) BAD_TRAP(0x45) + BAD_TRAP(0x46) BAD_TRAP(0x47) BAD_TRAP(0x48) BAD_TRAP(0x49) BAD_TRAP(0x4a) + BAD_TRAP(0x4b) BAD_TRAP(0x4c) BAD_TRAP(0x4d) BAD_TRAP(0x4e) BAD_TRAP(0x4f) + BAD_TRAP(0x50) + BAD_TRAP(0x51) BAD_TRAP(0x52) BAD_TRAP(0x53) BAD_TRAP(0x54) BAD_TRAP(0x55) + BAD_TRAP(0x56) BAD_TRAP(0x57) BAD_TRAP(0x58) BAD_TRAP(0x59) BAD_TRAP(0x5a) + BAD_TRAP(0x5b) BAD_TRAP(0x5c) BAD_TRAP(0x5d) BAD_TRAP(0x5e) BAD_TRAP(0x5f) + BAD_TRAP(0x60) BAD_TRAP(0x61) BAD_TRAP(0x62) BAD_TRAP(0x63) BAD_TRAP(0x64) + BAD_TRAP(0x65) BAD_TRAP(0x66) BAD_TRAP(0x67) BAD_TRAP(0x68) BAD_TRAP(0x69) + BAD_TRAP(0x6a) BAD_TRAP(0x6b) BAD_TRAP(0x6c) BAD_TRAP(0x6d) BAD_TRAP(0x6e) + BAD_TRAP(0x6f) BAD_TRAP(0x70) BAD_TRAP(0x71) BAD_TRAP(0x72) BAD_TRAP(0x73) + BAD_TRAP(0x74) BAD_TRAP(0x75) BAD_TRAP(0x76) BAD_TRAP(0x77) BAD_TRAP(0x78) + BAD_TRAP(0x79) BAD_TRAP(0x7a) BAD_TRAP(0x7b) BAD_TRAP(0x7c) BAD_TRAP(0x7d) + BAD_TRAP(0x7e) BAD_TRAP(0x7f) + SUNOS_SYSCALL_TRAP + BREAKPOINT_TRAP + TRAP_ENTRY(0x82, do_hw_divzero) + TRAP_ENTRY(0x83, do_flush_windows) BAD_TRAP(0x84) BAD_TRAP(0x85) + BAD_TRAP(0x86) BAD_TRAP(0x87) SOLARIS_SYSCALL_TRAP + NETBSD_SYSCALL_TRAP BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c) + BAD_TRAP(0x8d) BAD_TRAP(0x8e) BAD_TRAP(0x8f) + LINUX_SYSCALL_TRAP BAD_TRAP(0x91) BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94) + BAD_TRAP(0x95) BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99) + BAD_TRAP(0x9a) BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e) + BAD_TRAP(0x9f) GETCC_TRAP SETCC_TRAP GETPSR_TRAP + BAD_TRAP(0xa3) BAD_TRAP(0xa4) BAD_TRAP(0xa5) BAD_TRAP(0xa6) + INDIRECT_SOLARIS_SYSCALL(156) BAD_TRAP(0xa8) BAD_TRAP(0xa9) BAD_TRAP(0xaa) BAD_TRAP(0xab) + BAD_TRAP(0xac) BAD_TRAP(0xad) BAD_TRAP(0xae) BAD_TRAP(0xaf) BAD_TRAP(0xb0) + BAD_TRAP(0xb1) BAD_TRAP(0xb2) BAD_TRAP(0xb3) BAD_TRAP(0xb4) BAD_TRAP(0xb5) + BAD_TRAP(0xb6) BAD_TRAP(0xb7) BAD_TRAP(0xb8) BAD_TRAP(0xb9) BAD_TRAP(0xba) + BAD_TRAP(0xbb) BAD_TRAP(0xbc) BAD_TRAP(0xbd) BAD_TRAP(0xbe) BAD_TRAP(0xbf) + BAD_TRAP(0xc0) BAD_TRAP(0xc1) BAD_TRAP(0xc2) BAD_TRAP(0xc3) BAD_TRAP(0xc4) + BAD_TRAP(0xc5) BAD_TRAP(0xc6) BAD_TRAP(0xc7) BAD_TRAP(0xc8) BAD_TRAP(0xc9) + BAD_TRAP(0xca) BAD_TRAP(0xcb) BAD_TRAP(0xcc) BAD_TRAP(0xcd) BAD_TRAP(0xce) + BAD_TRAP(0xcf) BAD_TRAP(0xd0) BAD_TRAP(0xd1) BAD_TRAP(0xd2) BAD_TRAP(0xd3) + BAD_TRAP(0xd4) BAD_TRAP(0xd5) BAD_TRAP(0xd6) BAD_TRAP(0xd7) BAD_TRAP(0xd8) + BAD_TRAP(0xd9) BAD_TRAP(0xda) BAD_TRAP(0xdb) BAD_TRAP(0xdc) BAD_TRAP(0xdd) + BAD_TRAP(0xde) BAD_TRAP(0xdf) BAD_TRAP(0xe0) BAD_TRAP(0xe1) BAD_TRAP(0xe2) + BAD_TRAP(0xe3) BAD_TRAP(0xe4) BAD_TRAP(0xe5) BAD_TRAP(0xe6) BAD_TRAP(0xe7) + BAD_TRAP(0xe8) BAD_TRAP(0xe9) BAD_TRAP(0xea) BAD_TRAP(0xeb) BAD_TRAP(0xec) + BAD_TRAP(0xed) BAD_TRAP(0xee) BAD_TRAP(0xef) BAD_TRAP(0xf0) BAD_TRAP(0xf1) + BAD_TRAP(0xf2) BAD_TRAP(0xf3) BAD_TRAP(0xf4) BAD_TRAP(0xf5) BAD_TRAP(0xf6) + BAD_TRAP(0xf7) BAD_TRAP(0xf8) BAD_TRAP(0xf9) BAD_TRAP(0xfa) BAD_TRAP(0xfb) + BAD_TRAP(0xfc) BAD_TRAP(0xfd) BAD_TRAP(0xfe) BAD_TRAP(0xff) + +trapbase_cpu3: + BAD_TRAP(0x0) SRMMU_TFAULT TRAP_ENTRY(0x2, bad_instruction) + TRAP_ENTRY(0x3, priv_instruction) TRAP_ENTRY(0x4, fpd_trap_handler) + WINDOW_SPILL WINDOW_FILL TRAP_ENTRY(0x7, mna_handler) + TRAP_ENTRY(0x8, fpe_trap_handler) SRMMU_DFAULT + TRAP_ENTRY(0xa, do_tag_overflow) TRAP_ENTRY(0xb, do_watchpoint) + BAD_TRAP(0xc) BAD_TRAP(0xd) BAD_TRAP(0xe) BAD_TRAP(0xf) BAD_TRAP(0x10) + TRAP_ENTRY_INTERRUPT(1) TRAP_ENTRY_INTERRUPT(2) + TRAP_ENTRY_INTERRUPT(3) TRAP_ENTRY_INTERRUPT(4) + TRAP_ENTRY_INTERRUPT(5) TRAP_ENTRY_INTERRUPT(6) + TRAP_ENTRY_INTERRUPT(7) TRAP_ENTRY_INTERRUPT(8) + TRAP_ENTRY_INTERRUPT(9) TRAP_ENTRY_INTERRUPT(10) + TRAP_ENTRY_INTERRUPT(11) TRAP_ENTRY_INTERRUPT(12) + TRAP_ENTRY_INTERRUPT(13) TRAP_ENTRY_INTERRUPT(14) + TRAP_ENTRY(0x1f, linux_trap_ipi15_sun4m) + TRAP_ENTRY(0x20, do_reg_access) BAD_TRAP(0x21) BAD_TRAP(0x22) + BAD_TRAP(0x23) TRAP_ENTRY(0x24, do_cp_disabled) SKIP_TRAP(0x25, unimp_flush) + BAD_TRAP(0x26) BAD_TRAP(0x27) TRAP_ENTRY(0x28, do_cp_exception) + SRMMU_DFAULT TRAP_ENTRY(0x2a, do_hw_divzero) BAD_TRAP(0x2b) BAD_TRAP(0x2c) + BAD_TRAP(0x2d) BAD_TRAP(0x2e) BAD_TRAP(0x2f) BAD_TRAP(0x30) BAD_TRAP(0x31) + BAD_TRAP(0x32) BAD_TRAP(0x33) BAD_TRAP(0x34) BAD_TRAP(0x35) BAD_TRAP(0x36) + BAD_TRAP(0x37) BAD_TRAP(0x38) BAD_TRAP(0x39) BAD_TRAP(0x3a) BAD_TRAP(0x3b) + BAD_TRAP(0x3c) BAD_TRAP(0x3d) BAD_TRAP(0x3e) BAD_TRAP(0x3f) BAD_TRAP(0x40) + BAD_TRAP(0x41) BAD_TRAP(0x42) BAD_TRAP(0x43) BAD_TRAP(0x44) BAD_TRAP(0x45) + BAD_TRAP(0x46) BAD_TRAP(0x47) BAD_TRAP(0x48) BAD_TRAP(0x49) BAD_TRAP(0x4a) + BAD_TRAP(0x4b) BAD_TRAP(0x4c) BAD_TRAP(0x4d) BAD_TRAP(0x4e) BAD_TRAP(0x4f) + BAD_TRAP(0x50) + BAD_TRAP(0x51) BAD_TRAP(0x52) BAD_TRAP(0x53) BAD_TRAP(0x54) BAD_TRAP(0x55) + BAD_TRAP(0x56) BAD_TRAP(0x57) BAD_TRAP(0x58) BAD_TRAP(0x59) BAD_TRAP(0x5a) + BAD_TRAP(0x5b) BAD_TRAP(0x5c) BAD_TRAP(0x5d) BAD_TRAP(0x5e) BAD_TRAP(0x5f) + BAD_TRAP(0x60) BAD_TRAP(0x61) BAD_TRAP(0x62) BAD_TRAP(0x63) BAD_TRAP(0x64) + BAD_TRAP(0x65) BAD_TRAP(0x66) BAD_TRAP(0x67) BAD_TRAP(0x68) BAD_TRAP(0x69) + BAD_TRAP(0x6a) BAD_TRAP(0x6b) BAD_TRAP(0x6c) BAD_TRAP(0x6d) BAD_TRAP(0x6e) + BAD_TRAP(0x6f) BAD_TRAP(0x70) BAD_TRAP(0x71) BAD_TRAP(0x72) BAD_TRAP(0x73) + BAD_TRAP(0x74) BAD_TRAP(0x75) BAD_TRAP(0x76) BAD_TRAP(0x77) BAD_TRAP(0x78) + BAD_TRAP(0x79) BAD_TRAP(0x7a) BAD_TRAP(0x7b) BAD_TRAP(0x7c) BAD_TRAP(0x7d) + BAD_TRAP(0x7e) BAD_TRAP(0x7f) + SUNOS_SYSCALL_TRAP + BREAKPOINT_TRAP + TRAP_ENTRY(0x82, do_hw_divzero) + TRAP_ENTRY(0x83, do_flush_windows) BAD_TRAP(0x84) BAD_TRAP(0x85) + BAD_TRAP(0x86) BAD_TRAP(0x87) SOLARIS_SYSCALL_TRAP + NETBSD_SYSCALL_TRAP BAD_TRAP(0x8a) BAD_TRAP(0x8b) BAD_TRAP(0x8c) + BAD_TRAP(0x8d) BAD_TRAP(0x8e) BAD_TRAP(0x8f) + LINUX_SYSCALL_TRAP BAD_TRAP(0x91) BAD_TRAP(0x92) BAD_TRAP(0x93) BAD_TRAP(0x94) + BAD_TRAP(0x95) BAD_TRAP(0x96) BAD_TRAP(0x97) BAD_TRAP(0x98) BAD_TRAP(0x99) + BAD_TRAP(0x9a) BAD_TRAP(0x9b) BAD_TRAP(0x9c) BAD_TRAP(0x9d) BAD_TRAP(0x9e) + BAD_TRAP(0x9f) GETCC_TRAP SETCC_TRAP GETPSR_TRAP + BAD_TRAP(0xa3) BAD_TRAP(0xa4) BAD_TRAP(0xa5) BAD_TRAP(0xa6) + INDIRECT_SOLARIS_SYSCALL(156) BAD_TRAP(0xa8) BAD_TRAP(0xa9) BAD_TRAP(0xaa) BAD_TRAP(0xab) + BAD_TRAP(0xac) BAD_TRAP(0xad) BAD_TRAP(0xae) BAD_TRAP(0xaf) BAD_TRAP(0xb0) + BAD_TRAP(0xb1) BAD_TRAP(0xb2) BAD_TRAP(0xb3) BAD_TRAP(0xb4) BAD_TRAP(0xb5) + BAD_TRAP(0xb6) BAD_TRAP(0xb7) BAD_TRAP(0xb8) BAD_TRAP(0xb9) BAD_TRAP(0xba) + BAD_TRAP(0xbb) BAD_TRAP(0xbc) BAD_TRAP(0xbd) BAD_TRAP(0xbe) BAD_TRAP(0xbf) + BAD_TRAP(0xc0) BAD_TRAP(0xc1) BAD_TRAP(0xc2) BAD_TRAP(0xc3) BAD_TRAP(0xc4) + BAD_TRAP(0xc5) BAD_TRAP(0xc6) BAD_TRAP(0xc7) BAD_TRAP(0xc8) BAD_TRAP(0xc9) + BAD_TRAP(0xca) BAD_TRAP(0xcb) BAD_TRAP(0xcc) BAD_TRAP(0xcd) BAD_TRAP(0xce) + BAD_TRAP(0xcf) BAD_TRAP(0xd0) BAD_TRAP(0xd1) BAD_TRAP(0xd2) BAD_TRAP(0xd3) + BAD_TRAP(0xd4) BAD_TRAP(0xd5) BAD_TRAP(0xd6) BAD_TRAP(0xd7) BAD_TRAP(0xd8) + BAD_TRAP(0xd9) BAD_TRAP(0xda) BAD_TRAP(0xdb) BAD_TRAP(0xdc) BAD_TRAP(0xdd) + BAD_TRAP(0xde) BAD_TRAP(0xdf) BAD_TRAP(0xe0) BAD_TRAP(0xe1) BAD_TRAP(0xe2) + BAD_TRAP(0xe3) BAD_TRAP(0xe4) BAD_TRAP(0xe5) BAD_TRAP(0xe6) BAD_TRAP(0xe7) + BAD_TRAP(0xe8) BAD_TRAP(0xe9) BAD_TRAP(0xea) BAD_TRAP(0xeb) BAD_TRAP(0xec) + BAD_TRAP(0xed) BAD_TRAP(0xee) BAD_TRAP(0xef) BAD_TRAP(0xf0) BAD_TRAP(0xf1) + BAD_TRAP(0xf2) BAD_TRAP(0xf3) BAD_TRAP(0xf4) BAD_TRAP(0xf5) BAD_TRAP(0xf6) + BAD_TRAP(0xf7) BAD_TRAP(0xf8) BAD_TRAP(0xf9) BAD_TRAP(0xfa) BAD_TRAP(0xfb) + BAD_TRAP(0xfc) BAD_TRAP(0xfd) BAD_TRAP(0xfe) BAD_TRAP(0xff) + +#endif + .align PAGE_SIZE + +/* This was the only reasonable way I could think of to properly align + * these page-table data structures. + */ + .globl pg0, pg1, pg2, pg3 + .globl empty_bad_page + .globl empty_bad_page_table + .globl empty_zero_page + .globl swapper_pg_dir +swapper_pg_dir: .skip PAGE_SIZE +pg0: .skip PAGE_SIZE +pg1: .skip PAGE_SIZE +pg2: .skip PAGE_SIZE +pg3: .skip PAGE_SIZE +empty_bad_page: .skip PAGE_SIZE +empty_bad_page_table: .skip PAGE_SIZE +empty_zero_page: .skip PAGE_SIZE + + .global root_flags + .global ram_flags + .global root_dev + .global sparc_ramdisk_image + .global sparc_ramdisk_size + +/* This stuff has to be in sync with SILO and other potential boot loaders + * Fields should be kept upward compatible and whenever any change is made, + * HdrS version should be incremented. + */ + .ascii "HdrS" + .word LINUX_VERSION_CODE + .half 0x0203 /* HdrS version */ +root_flags: + .half 1 +root_dev: + .half 0 +ram_flags: + .half 0 +sparc_ramdisk_image: + .word 0 +sparc_ramdisk_size: + .word 0 + .word reboot_command + .word 0, 0, 0 + .word _end + +/* Cool, here we go. Pick up the romvec pointer in %o0 and stash it in + * %g7 and at prom_vector_p. And also quickly check whether we are on + * a v0, v2, or v3 prom. + */ +gokernel: + /* Ok, it's nice to know, as early as possible, if we + * are already mapped where we expect to be in virtual + * memory. The Solaris /boot elf format bootloader + * will peek into our elf header and load us where + * we want to be, otherwise we have to re-map. + * + * Some boot loaders don't place the jmp'rs address + * in %o7, so we do a pc-relative call to a local + * label, then see what %o7 has. + */ + + mov %o7, %g4 ! Save %o7 + + /* Jump to it, and pray... */ +current_pc: + call 1f + nop + +1: + mov %o7, %g3 + + tst %o0 + be no_sun4u_here + mov %g4, %o7 /* Previous %o7. */ + + mov %o0, %l0 ! stash away romvec + mov %o0, %g7 ! put it here too + mov %o1, %l1 ! stash away debug_vec too + + /* Ok, let's check out our run time program counter. */ + set current_pc, %g5 + cmp %g3, %g5 + be already_mapped + nop + + /* %l6 will hold the offset we have to subtract + * from absolute symbols in order to access areas + * in our own image. If already mapped this is + * just plain zero, else it is KERNBASE. + */ + set KERNBASE, %l6 + b copy_prom_lvl14 + nop + +already_mapped: + mov 0, %l6 + + /* Copy over the Prom's level 14 clock handler. */ +copy_prom_lvl14: +#if 1 + /* DJHR + * preserve our linked/calculated instructions + */ + set lvl14_save, %g1 + set t_irq14, %g3 + sub %g1, %l6, %g1 ! translate to physical + sub %g3, %l6, %g3 ! translate to physical + ldd [%g3], %g4 + std %g4, [%g1] + ldd [%g3+8], %g4 + std %g4, [%g1+8] +#endif + rd %tbr, %g1 + andn %g1, 0xfff, %g1 ! proms trap table base + or %g0, (0x1e<<4), %g2 ! offset to lvl14 intr + or %g1, %g2, %g2 + set t_irq14, %g3 + sub %g3, %l6, %g3 + ldd [%g2], %g4 + std %g4, [%g3] + ldd [%g2 + 0x8], %g4 + std %g4, [%g3 + 0x8] ! Copy proms handler + +/* Must determine whether we are on a sun4c MMU, SRMMU, or SUN4/400 MUTANT + * MMU so we can remap ourselves properly. DON'T TOUCH %l0 thru %l5 in these + * remapping routines, we need their values afterwards! + */ + /* Now check whether we are already mapped, if we + * are we can skip all this garbage coming up. + */ +copy_prom_done: + cmp %l6, 0 + be go_to_highmem ! this will be a nop then + nop + + set LOAD_ADDR, %g6 + cmp %g7, %g6 + bne remap_not_a_sun4 ! This is not a Sun4 + nop + + or %g0, 0x1, %g1 + lduba [%g1] ASI_CONTROL, %g1 ! Only safe to try on Sun4. + subcc %g1, 0x24, %g0 ! Is this a mutant Sun4/400??? + be sun4_mutant_remap ! Ugh, it is... + nop + + b sun4_normal_remap ! regular sun4, 2 level mmu + nop + +remap_not_a_sun4: + lda [%g0] ASI_M_MMUREGS, %g1 ! same as ASI_PTE on sun4c + and %g1, 0x1, %g1 ! Test SRMMU Enable bit ;-) + cmp %g1, 0x0 + be sun4c_remap ! A sun4c MMU or normal Sun4 + nop +srmmu_remap: + /* First, check for a viking (TI) module. */ + set 0x40000000, %g2 + rd %psr, %g3 + and %g2, %g3, %g3 + subcc %g3, 0x0, %g0 + bz srmmu_nviking + nop + + /* Figure out what kind of viking we are on. + * We need to know if we have to play with the + * AC bit and disable traps or not. + */ + + /* I've only seen MicroSparc's on SparcClassics with this + * bit set. + */ + set 0x800, %g2 + lda [%g0] ASI_M_MMUREGS, %g3 ! peek in the control reg + and %g2, %g3, %g3 + subcc %g3, 0x0, %g0 + bnz srmmu_nviking ! is in mbus mode + nop + + rd %psr, %g3 ! DO NOT TOUCH %g3 + andn %g3, PSR_ET, %g2 + wr %g2, 0x0, %psr + WRITE_PAUSE + + /* Get context table pointer, then convert to + * a physical address, which is 36 bits. + */ + set AC_M_CTPR, %g4 + lda [%g4] ASI_M_MMUREGS, %g4 + sll %g4, 0x4, %g4 ! We use this below + ! DO NOT TOUCH %g4 + + /* Set the AC bit in the Viking's MMU control reg. */ + lda [%g0] ASI_M_MMUREGS, %g5 ! DO NOT TOUCH %g5 + set 0x8000, %g6 ! AC bit mask + or %g5, %g6, %g6 ! Or it in... + sta %g6, [%g0] ASI_M_MMUREGS ! Close your eyes... + + /* Grrr, why does it seem like every other load/store + * on the sun4m is in some ASI space... + * Fine with me, let's get the pointer to the level 1 + * page table directory and fetch its entry. + */ + lda [%g4] ASI_M_BYPASS, %o1 ! This is a level 1 ptr + srl %o1, 0x4, %o1 ! Clear low 4 bits + sll %o1, 0x8, %o1 ! Make physical + + /* Ok, pull in the PTD. */ + lda [%o1] ASI_M_BYPASS, %o2 ! This is the 0x0 16MB pgd + + /* Calculate to KERNBASE entry. */ + add %o1, KERNBASE >> (SRMMU_PGDIR_SHIFT - 2), %o3 + + /* Poke the entry into the calculated address. */ + sta %o2, [%o3] ASI_M_BYPASS + + /* I don't get it Sun, if you engineered all these + * boot loaders and the PROM (thank you for the debugging + * features btw) why did you not have them load kernel + * images up in high address space, since this is necessary + * for ABI compliance anyways? Does this low-mapping provide + * enhanced interoperability? + * + * "The PROM is the computer." + */ + + /* Ok, restore the MMU control register we saved in %g5 */ + sta %g5, [%g0] ASI_M_MMUREGS ! POW... ouch + + /* Turn traps back on. We saved it in %g3 earlier. */ + wr %g3, 0x0, %psr ! tick tock, tick tock + + /* Now we burn precious CPU cycles due to bad engineering. */ + WRITE_PAUSE + + /* Wow, all that just to move a 32-bit value from one + * place to another... Jump to high memory. + */ + b go_to_highmem + nop + + /* This works on viking's in Mbus mode and all + * other MBUS modules. It is virtually the same as + * the above madness sans turning traps off and flipping + * the AC bit. + */ +srmmu_nviking: + set AC_M_CTPR, %g1 + lda [%g1] ASI_M_MMUREGS, %g1 ! get ctx table ptr + sll %g1, 0x4, %g1 ! make physical addr + lda [%g1] ASI_M_BYPASS, %g1 ! ptr to level 1 pg_table + srl %g1, 0x4, %g1 + sll %g1, 0x8, %g1 ! make phys addr for l1 tbl + + lda [%g1] ASI_M_BYPASS, %g2 ! get level1 entry for 0x0 + add %g1, KERNBASE >> (SRMMU_PGDIR_SHIFT - 2), %g3 + sta %g2, [%g3] ASI_M_BYPASS ! place at KERNBASE entry + b go_to_highmem + nop ! wheee.... + + /* This remaps the kernel on Sun4/4xx machines + * that have the Sun Mutant Three Level MMU. + * It's like a platypus, Sun didn't have the + * SRMMU in conception so they kludged the three + * level logic in the regular Sun4 MMU probably. + * + * Basically, you take each entry in the top level + * directory that maps the low 3MB starting at + * address zero and put the mapping in the KERNBASE + * slots. These top level pgd's are called regmaps. + */ +sun4_mutant_remap: + or %g0, %g0, %g3 ! source base + sethi %hi(KERNBASE), %g4 ! destination base + or %g4, %lo(KERNBASE), %g4 + sethi %hi(0x300000), %g5 + or %g5, %lo(0x300000), %g5 ! upper bound 3MB + or %g0, 0x1, %l6 + sll %l6, 24, %l6 ! Regmap mapping size + add %g3, 0x2, %g3 ! Base magic + add %g4, 0x2, %g4 ! Base magic + + /* Main remapping loop on Sun4-Mutant-MMU. + * "I am not an animal..." -Famous Mutant Person + */ +sun4_mutant_loop: + lduha [%g3] ASI_REGMAP, %g2 ! Get lower entry + stha %g2, [%g4] ASI_REGMAP ! Store in high entry + add %g4, %l6, %g4 ! Move up high memory ptr + subcc %g3, %g5, %g0 ! Reached our limit? + blu sun4_mutant_loop ! Nope, loop again + add %g3, %l6, %g3 ! delay, Move up low ptr + b go_to_highmem ! Jump to high memory. + nop + + /* The following is for non-4/4xx sun4 MMU's. */ +sun4_normal_remap: + mov 0, %g3 ! source base + set KERNBASE, %g4 ! destination base + set 0x300000, %g5 ! upper bound 3MB + mov 1, %l6 + sll %l6, 18, %l6 ! sun4 mmu segmap size +sun4_normal_loop: + lduha [%g3] ASI_SEGMAP, %g6 ! load phys_seg + stha %g6, [%g4] ASI_SEGMAP ! stort new virt mapping + add %g3, %l6, %g3 ! increment source pointer + subcc %g3, %g5, %g0 ! reached limit? + blu sun4_normal_loop ! nope, loop again + add %g4, %l6, %g4 ! delay, increment dest ptr + b go_to_highmem + nop + + /* The following works for Sun4c MMU's */ +sun4c_remap: + mov 0, %g3 ! source base + set KERNBASE, %g4 ! destination base + set 0x300000, %g5 ! upper bound 3MB + mov 1, %l6 + sll %l6, 18, %l6 ! sun4c mmu segmap size +sun4c_remap_loop: + lda [%g3] ASI_SEGMAP, %g6 ! load phys_seg + sta %g6, [%g4] ASI_SEGMAP ! store new virt mapping + add %g3, %l6, %g3 ! Increment source ptr + subcc %g3, %g5, %g0 ! Reached limit? + bl sun4c_remap_loop ! Nope, loop again + add %g4, %l6, %g4 ! delay, Increment dest ptr + +/* Now do a non-relative jump so that PC is in high-memory */ +go_to_highmem: + set execute_in_high_mem, %g1 + jmpl %g1, %g0 + nop + +/* The code above should be at beginning and we have to take care about + * short jumps, as branching to .text.init section from .text is usually + * impossible */ + __INIT +/* Acquire boot time privileged register values, this will help debugging. + * I figure out and store nwindows and nwindowsm1 later on. + */ +execute_in_high_mem: + mov %l0, %o0 ! put back romvec + mov %l1, %o1 ! and debug_vec + + sethi %hi(prom_vector_p), %g1 + st %o0, [%g1 + %lo(prom_vector_p)] + + sethi %hi(linux_dbvec), %g1 + st %o1, [%g1 + %lo(linux_dbvec)] + + ld [%o0 + 0x4], %o3 + and %o3, 0x3, %o5 ! get the version + + cmp %o3, 0x2 ! a v2 prom? + be found_version + nop + + /* paul@sfe.com.au */ + cmp %o3, 0x3 ! a v3 prom? + be found_version + nop + +/* Old sun4's pass our load address into %o0 instead of the prom + * pointer. On sun4's you have to hard code the romvec pointer into + * your code. Sun probably still does that because they don't even + * trust their own "OpenBoot" specifications. + */ + set LOAD_ADDR, %g6 + cmp %o0, %g6 ! an old sun4? + be sun4_init + nop + +found_version: +#ifdef CONFIG_SUN4 +/* For people who try sun4 kernels, even if Configure.help advises them. */ + ld [%g7 + 0x68], %o1 + set sun4cdm_notsup, %o0 + call %o1 + nop + b halt_me + nop +#endif +/* Get the machine type via the mysterious romvec node operations. */ + + add %g7, 0x1c, %l1 + ld [%l1], %l0 + ld [%l0], %l0 + call %l0 + or %g0, %g0, %o0 ! next_node(0) = first_node + or %o0, %g0, %g6 + + sethi %hi(cputypvar), %o1 ! First node has cpu-arch + or %o1, %lo(cputypvar), %o1 + sethi %hi(cputypval), %o2 ! information, the string + or %o2, %lo(cputypval), %o2 + ld [%l1], %l0 ! 'compatibility' tells + ld [%l0 + 0xc], %l0 ! that we want 'sun4x' where + call %l0 ! x is one of '', 'c', 'm', + nop ! 'd' or 'e'. %o2 holds pointer + ! to a buf where above string + ! will get stored by the prom. + + subcc %o0, %g0, %g0 + bpos got_prop ! Got the property + nop + + or %g6, %g0, %o0 + sethi %hi(cputypvar_sun4m), %o1 + or %o1, %lo(cputypvar_sun4m), %o1 + sethi %hi(cputypval), %o2 + or %o2, %lo(cputypval), %o2 + ld [%l1], %l0 + ld [%l0 + 0xc], %l0 + call %l0 + nop + +got_prop: + set cputypval, %o2 + ldub [%o2 + 0x4], %l1 + + cmp %l1, ' ' + be 1f + cmp %l1, 'c' + be 1f + cmp %l1, 'm' + be 1f + cmp %l1, 's' + be 1f + cmp %l1, 'd' + be 1f + cmp %l1, 'e' + be no_sun4e_here ! Could be a sun4e. + nop + b no_sun4u_here ! AIEEE, a V9 sun4u... Get our BIG BROTHER kernel :)) + nop + +1: set cputypval, %l1 + ldub [%l1 + 0x4], %l1 + cmp %l1, 'm' ! Test for sun4d, sun4e ? + be sun4m_init + cmp %l1, 's' ! Treat sun4s as sun4m + be sun4m_init + cmp %l1, 'd' ! Let us see how the beast will die + be sun4d_init + nop + + /* Jump into mmu context zero. */ + set AC_CONTEXT, %g1 + stba %g0, [%g1] ASI_CONTROL + + b sun4c_continue_boot + nop + +/* CPUID in bootbus can be found at PA 0xff0140000 */ +#define SUN4D_BOOTBUS_CPUID 0xf0140000 + +sun4d_init: + /* Need to patch call to handler_irq */ + set patch_handler_irq, %g4 + set sun4d_handler_irq, %g5 + sethi %hi(0x40000000), %g3 ! call + sub %g5, %g4, %g5 + srl %g5, 2, %g5 + or %g5, %g3, %g5 + st %g5, [%g4] + +#ifdef CONFIG_SMP + /* Get our CPU id out of bootbus */ + set SUN4D_BOOTBUS_CPUID, %g3 + lduba [%g3] ASI_M_CTL, %g3 + and %g3, 0xf8, %g3 + srl %g3, 3, %g4 + sta %g4, [%g0] ASI_M_VIKING_TMP1 + sethi %hi(boot_cpu_id), %g5 + stb %g4, [%g5 + %lo(boot_cpu_id)] + sll %g4, 2, %g4 + sethi %hi(boot_cpu_id4), %g5 + stb %g4, [%g5 + %lo(boot_cpu_id4)] +#endif + + /* Fall through to sun4m_init */ + +sun4m_init: + /* XXX Fucking Cypress... */ + lda [%g0] ASI_M_MMUREGS, %g5 + srl %g5, 28, %g4 + + cmp %g4, 1 + bne 1f + srl %g5, 24, %g4 + + and %g4, 0xf, %g4 + cmp %g4, 7 /* This would be a HyperSparc. */ + + bne 2f + nop + +1: + +#define PATCH_IT(dst, src) \ + set (dst), %g5; \ + set (src), %g4; \ + ld [%g4], %g3; \ + st %g3, [%g5]; \ + ld [%g4+0x4], %g3; \ + st %g3, [%g5+0x4]; + + /* Signed multiply. */ + PATCH_IT(.mul, .mul_patch) + PATCH_IT(.mul+0x08, .mul_patch+0x08) + + /* Signed remainder. */ + PATCH_IT(.rem, .rem_patch) + PATCH_IT(.rem+0x08, .rem_patch+0x08) + PATCH_IT(.rem+0x10, .rem_patch+0x10) + PATCH_IT(.rem+0x18, .rem_patch+0x18) + PATCH_IT(.rem+0x20, .rem_patch+0x20) + PATCH_IT(.rem+0x28, .rem_patch+0x28) + + /* Signed division. */ + PATCH_IT(.div, .div_patch) + PATCH_IT(.div+0x08, .div_patch+0x08) + PATCH_IT(.div+0x10, .div_patch+0x10) + PATCH_IT(.div+0x18, .div_patch+0x18) + PATCH_IT(.div+0x20, .div_patch+0x20) + + /* Unsigned multiply. */ + PATCH_IT(.umul, .umul_patch) + PATCH_IT(.umul+0x08, .umul_patch+0x08) + + /* Unsigned remainder. */ + PATCH_IT(.urem, .urem_patch) + PATCH_IT(.urem+0x08, .urem_patch+0x08) + PATCH_IT(.urem+0x10, .urem_patch+0x10) + PATCH_IT(.urem+0x18, .urem_patch+0x18) + + /* Unsigned division. */ + PATCH_IT(.udiv, .udiv_patch) + PATCH_IT(.udiv+0x08, .udiv_patch+0x08) + PATCH_IT(.udiv+0x10, .udiv_patch+0x10) + +#undef PATCH_IT + +/* Ok, the PROM could have done funny things and apple cider could still + * be sitting in the fault status/address registers. Read them all to + * clear them so we don't get magic faults later on. + */ +/* This sucks, apparently this makes Vikings call prom panic, will fix later */ +2: + rd %psr, %o1 + srl %o1, 28, %o1 ! Get a type of the CPU + + subcc %o1, 4, %g0 ! TI: Viking or MicroSPARC + be sun4c_continue_boot + nop + + set AC_M_SFSR, %o0 + lda [%o0] ASI_M_MMUREGS, %g0 + set AC_M_SFAR, %o0 + lda [%o0] ASI_M_MMUREGS, %g0 + + /* Fujitsu MicroSPARC-II has no asynchronous flavors of FARs */ + subcc %o1, 0, %g0 + be sun4c_continue_boot + nop + + set AC_M_AFSR, %o0 + lda [%o0] ASI_M_MMUREGS, %g0 + set AC_M_AFAR, %o0 + lda [%o0] ASI_M_MMUREGS, %g0 + nop + + +sun4c_continue_boot: + + +/* Aieee, now set PC and nPC, enable traps, give ourselves a stack and it's + * show-time! + */ + + sethi %hi(cputyp), %o0 + st %g4, [%o0 + %lo(cputyp)] + + /* Turn on Supervisor, EnableFloating, and all the PIL bits. + * Also puts us in register window zero with traps off. + */ + set (PSR_PS | PSR_S | PSR_PIL | PSR_EF), %g2 + wr %g2, 0x0, %psr + WRITE_PAUSE + + /* I want a kernel stack NOW! */ + set init_thread_union, %g1 + set (THREAD_SIZE - STACKFRAME_SZ), %g2 + add %g1, %g2, %sp + mov 0, %fp /* And for good luck */ + + /* Zero out our BSS section. */ + set __bss_start , %o0 ! First address of BSS + set end , %o1 ! Last address of BSS + add %o0, 0x1, %o0 +1: + stb %g0, [%o0] + subcc %o0, %o1, %g0 + bl 1b + add %o0, 0x1, %o0 + + /* Initialize the uwinmask value for init task just in case. + * But first make current_set[boot_cpu_id] point to something useful. + */ + set init_thread_union, %g6 + set current_set, %g2 +#ifdef CONFIG_SMP + sethi %hi(boot_cpu_id4), %g3 + ldub [%g3 + %lo(boot_cpu_id4)], %g3 + st %g6, [%g2] + add %g2, %g3, %g2 +#endif + st %g6, [%g2] + + st %g0, [%g6 + TI_UWINMASK] + +/* Compute NWINDOWS and stash it away. Now uses %wim trick explained + * in the V8 manual. Ok, this method seems to work, Sparc is cool... + * No, it doesn't work, have to play the save/readCWP/restore trick. + */ + + wr %g0, 0x0, %wim ! so we do not get a trap + WRITE_PAUSE + + save + + rd %psr, %g3 + + restore + + and %g3, 0x1f, %g3 + add %g3, 0x1, %g3 + + mov 2, %g1 + wr %g1, 0x0, %wim ! make window 1 invalid + WRITE_PAUSE + + cmp %g3, 0x7 + bne 2f + nop + + /* Adjust our window handling routines to + * do things correctly on 7 window Sparcs. + */ + +#define PATCH_INSN(src, dest) \ + set src, %g5; \ + set dest, %g2; \ + ld [%g5], %g4; \ + st %g4, [%g2]; + + /* Patch for window spills... */ + PATCH_INSN(spnwin_patch1_7win, spnwin_patch1) + PATCH_INSN(spnwin_patch2_7win, spnwin_patch2) + PATCH_INSN(spnwin_patch3_7win, spnwin_patch3) + + /* Patch for window fills... */ + PATCH_INSN(fnwin_patch1_7win, fnwin_patch1) + PATCH_INSN(fnwin_patch2_7win, fnwin_patch2) + + /* Patch for trap entry setup... */ + PATCH_INSN(tsetup_7win_patch1, tsetup_patch1) + PATCH_INSN(tsetup_7win_patch2, tsetup_patch2) + PATCH_INSN(tsetup_7win_patch3, tsetup_patch3) + PATCH_INSN(tsetup_7win_patch4, tsetup_patch4) + PATCH_INSN(tsetup_7win_patch5, tsetup_patch5) + PATCH_INSN(tsetup_7win_patch6, tsetup_patch6) + + /* Patch for returning from traps... */ + PATCH_INSN(rtrap_7win_patch1, rtrap_patch1) + PATCH_INSN(rtrap_7win_patch2, rtrap_patch2) + PATCH_INSN(rtrap_7win_patch3, rtrap_patch3) + PATCH_INSN(rtrap_7win_patch4, rtrap_patch4) + PATCH_INSN(rtrap_7win_patch5, rtrap_patch5) + + /* Patch for killing user windows from the register file. */ + PATCH_INSN(kuw_patch1_7win, kuw_patch1) + + /* Now patch the kernel window flush sequences. + * This saves 2 traps on every switch and fork. + */ + set 0x01000000, %g4 + set flush_patch_one, %g5 + st %g4, [%g5 + 0x18] + st %g4, [%g5 + 0x1c] + set flush_patch_two, %g5 + st %g4, [%g5 + 0x18] + st %g4, [%g5 + 0x1c] + set flush_patch_three, %g5 + st %g4, [%g5 + 0x18] + st %g4, [%g5 + 0x1c] + set flush_patch_four, %g5 + st %g4, [%g5 + 0x18] + st %g4, [%g5 + 0x1c] + set flush_patch_exception, %g5 + st %g4, [%g5 + 0x18] + st %g4, [%g5 + 0x1c] + set flush_patch_switch, %g5 + st %g4, [%g5 + 0x18] + st %g4, [%g5 + 0x1c] + +2: + sethi %hi(nwindows), %g4 + st %g3, [%g4 + %lo(nwindows)] ! store final value + sub %g3, 0x1, %g3 + sethi %hi(nwindowsm1), %g4 + st %g3, [%g4 + %lo(nwindowsm1)] + + /* Here we go, start using Linux's trap table... */ + set trapbase, %g3 + wr %g3, 0x0, %tbr + WRITE_PAUSE + + /* Finally, turn on traps so that we can call c-code. */ + rd %psr, %g3 + wr %g3, 0x0, %psr + WRITE_PAUSE + + wr %g3, PSR_ET, %psr + WRITE_PAUSE + + /* First we call prom_init() to set up PROMLIB, then + * off to start_kernel(). + */ + + sethi %hi(prom_vector_p), %g5 + ld [%g5 + %lo(prom_vector_p)], %o0 + call prom_init + nop + + call start_kernel + nop + + /* We should not get here. */ + call halt_me + nop + +sun4_init: +#ifdef CONFIG_SUN4 +/* There, happy now Adrian? */ + set cputypval, %o2 ! Let everyone know we + set ' ', %o0 ! are a "sun4 " architecture + stb %o0, [%o2 + 0x4] + + b got_prop + nop +#else + sethi %hi(SUN4_PROM_VECTOR+0x84), %o1 + ld [%o1 + %lo(SUN4_PROM_VECTOR+0x84)], %o1 + set sun4_notsup, %o0 + call %o1 /* printf */ + nop + sethi %hi(SUN4_PROM_VECTOR+0xc4), %o1 + ld [%o1 + %lo(SUN4_PROM_VECTOR+0xc4)], %o1 + call %o1 /* exittomon */ + nop +1: ba 1b ! Cannot exit into KMON + nop +#endif +no_sun4e_here: + ld [%g7 + 0x68], %o1 + set sun4e_notsup, %o0 + call %o1 + nop + b halt_me + nop + + __INITDATA + +sun4u_1: + .asciz "finddevice" + .align 4 +sun4u_2: + .asciz "/chosen" + .align 4 +sun4u_3: + .asciz "getprop" + .align 4 +sun4u_4: + .asciz "stdout" + .align 4 +sun4u_5: + .asciz "write" + .align 4 +sun4u_6: + .asciz "\n\rOn sun4u you have to use UltraLinux (64bit) kernel\n\rand not a 32bit sun4[cdem] version\n\r\n\r" +sun4u_6e: + .align 4 +sun4u_7: + .asciz "exit" + .align 8 +sun4u_a1: + .word 0, sun4u_1, 0, 1, 0, 1, 0, sun4u_2, 0 +sun4u_r1: + .word 0 +sun4u_a2: + .word 0, sun4u_3, 0, 4, 0, 1, 0 +sun4u_i2: + .word 0, 0, sun4u_4, 0, sun4u_1, 0, 8, 0 +sun4u_r2: + .word 0 +sun4u_a3: + .word 0, sun4u_5, 0, 3, 0, 1, 0 +sun4u_i3: + .word 0, 0, sun4u_6, 0, sun4u_6e - sun4u_6 - 1, 0 +sun4u_r3: + .word 0 +sun4u_a4: + .word 0, sun4u_7, 0, 0, 0, 0 +sun4u_r4: + + __INIT +no_sun4u_here: + set sun4u_a1, %o0 + set current_pc, %l2 + cmp %l2, %g3 + be 1f + mov %o4, %l0 + sub %g3, %l2, %l6 + add %o0, %l6, %o0 + mov %o0, %l4 + mov sun4u_r4 - sun4u_a1, %l3 + ld [%l4], %l5 +2: + add %l4, 4, %l4 + cmp %l5, %l2 + add %l5, %l6, %l5 + bgeu,a 3f + st %l5, [%l4 - 4] +3: + subcc %l3, 4, %l3 + bne 2b + ld [%l4], %l5 +1: + call %l0 + mov %o0, %l1 + + ld [%l1 + (sun4u_r1 - sun4u_a1)], %o1 + add %l1, (sun4u_a2 - sun4u_a1), %o0 + call %l0 + st %o1, [%o0 + (sun4u_i2 - sun4u_a2)] + + ld [%l1 + (sun4u_1 - sun4u_a1)], %o1 + add %l1, (sun4u_a3 - sun4u_a1), %o0 + call %l0 + st %o1, [%o0 + (sun4u_i3 - sun4u_a3)] + + call %l0 + add %l1, (sun4u_a4 - sun4u_a1), %o0 + + /* Not reached */ +halt_me: + ld [%g7 + 0x74], %o0 + call %o0 ! Get us out of here... + nop ! Apparently Solaris is better. + +/* Ok, now we continue in the .data/.text sections */ + + .data + .align 4 + +/* + * Fill up the prom vector, note in particular the kind first element, + * no joke. I don't need all of them in here as the entire prom vector + * gets initialized in c-code so all routines can use it. + */ + + .globl prom_vector_p +prom_vector_p: + .word 0 + +/* We calculate the following at boot time, window fills/spills and trap entry + * code uses these to keep track of the register windows. + */ + + .align 4 + .globl nwindows + .globl nwindowsm1 +nwindows: + .word 8 +nwindowsm1: + .word 7 + +/* Boot time debugger vector value. We need this later on. */ + + .align 4 + .globl linux_dbvec +linux_dbvec: + .word 0 + .word 0 + + .align 8 + + .globl lvl14_save +lvl14_save: + .word 0 + .word 0 + .word 0 + .word 0 + .word t_irq14 + + .section ".fixup",#alloc,#execinstr + .globl __ret_efault +__ret_efault: + ret + restore %g0, -EFAULT, %o0 diff --git a/arch/sparc/kernel/idprom.c b/arch/sparc/kernel/idprom.c new file mode 100644 index 000000000000..2e1b0f6e99d4 --- /dev/null +++ b/arch/sparc/kernel/idprom.c @@ -0,0 +1,108 @@ +/* $Id: idprom.c,v 1.24 1999/08/31 06:54:20 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) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> + +#include <asm/oplib.h> +#include <asm/idprom.h> +#include <asm/machines.h> /* Fun with Sun released architectures. */ +#ifdef CONFIG_SUN4 +#include <asm/sun4paddr.h> +extern void sun4setup(void); +#endif + +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, Sun4's */ +{ "Sun 4/100 Series", (SM_SUN4 | SM_4_110) }, +{ "Sun 4/200 Series", (SM_SUN4 | SM_4_260) }, +{ "Sun 4/300 Series", (SM_SUN4 | SM_4_330) }, +{ "Sun 4/400 Series", (SM_SUN4 | SM_4_470) }, +/* Now, Sun4c's */ +{ "Sun4c SparcStation 1", (SM_SUN4C | SM_4C_SS1) }, +{ "Sun4c SparcStation IPC", (SM_SUN4C | SM_4C_IPC) }, +{ "Sun4c SparcStation 1+", (SM_SUN4C | SM_4C_SS1PLUS) }, +{ "Sun4c SparcStation SLC", (SM_SUN4C | SM_4C_SLC) }, +{ "Sun4c SparcStation 2", (SM_SUN4C | SM_4C_SS2) }, +{ "Sun4c SparcStation ELC", (SM_SUN4C | SM_4C_ELC) }, +{ "Sun4c SparcStation IPX", (SM_SUN4C | SM_4C_IPX) }, +/* Finally, early Sun4m's */ +{ "Sun4m SparcSystem600", (SM_SUN4M | SM_4M_SS60) }, +{ "Sun4m SparcStation10/20", (SM_SUN4M | SM_4M_SS50) }, +{ "Sun4m SparcStation5", (SM_SUN4M | SM_4M_SS40) }, +/* One entry for the OBP arch's which are sun4d, sun4e, and newer sun4m's */ +{ "Sun4M OBP based system", (SM_SUN4M_OBP | 0x0) } }; + +static void __init display_system_type(unsigned char machtype) +{ + char sysname[128]; + register int i; + + for (i = 0; i < NUM_SUN_MACHINES; i++) { + if(Sun_Machines[i].id_machtype == machtype) { + if (machtype != (SM_SUN4M_OBP | 0x00) || + prom_getproperty(prom_root_node, "banner-name", + sysname, sizeof(sysname)) <= 0) + printk("TYPE: %s\n", Sun_Machines[i].name); + else + printk("TYPE: %s\n", sysname); + return; + } + } + + prom_printf("IDPROM: Bogus id_machtype value, 0x%x\n", machtype); + prom_halt(); +} + +/* 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]); +#ifdef CONFIG_SUN4 + sun4setup(); +#endif +} diff --git a/arch/sparc/kernel/init_task.c b/arch/sparc/kernel/init_task.c new file mode 100644 index 000000000000..fc31de66b1c2 --- /dev/null +++ b/arch/sparc/kernel/init_task.c @@ -0,0 +1,28 @@ +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/init_task.h> +#include <linux/mqueue.h> + +#include <asm/pgtable.h> +#include <asm/uaccess.h> + +static struct fs_struct init_fs = INIT_FS; +static struct files_struct init_files = INIT_FILES; +static struct signal_struct init_signals = INIT_SIGNALS(init_signals); +static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand); +struct mm_struct init_mm = INIT_MM(init_mm); +struct task_struct init_task = INIT_TASK(init_task); + +EXPORT_SYMBOL(init_mm); +EXPORT_SYMBOL(init_task); + +/* .text section in head.S is aligned at 8k boundary and this gets linked + * right after that so that the init_thread_union is aligned properly as well. + * If this is not aligned on a 8k boundry, then you should change code + * in etrap.S which assumes it. + */ +union thread_union init_thread_union + __attribute__((section (".text\"\n\t#"))) + __attribute__((aligned (THREAD_SIZE))) + = { INIT_THREAD_INFO(init_task) }; diff --git a/arch/sparc/kernel/ioport.c b/arch/sparc/kernel/ioport.c new file mode 100644 index 000000000000..d0f2bd227c4c --- /dev/null +++ b/arch/sparc/kernel/ioport.c @@ -0,0 +1,731 @@ +/* $Id: ioport.c,v 1.45 2001/10/30 04:54:21 davem Exp $ + * ioport.c: Simple io mapping allocator. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) + * + * 1996: sparc_free_io, 1999: ioremap()/iounmap() by Pete Zaitcev. + * + * 2000/01/29 + * <rth> zait: as long as pci_alloc_consistent produces something addressable, + * things are ok. + * <zaitcev> rth: no, it is relevant, because get_free_pages returns you a + * pointer into the big page mapping + * <rth> zait: so what? + * <rth> zait: remap_it_my_way(virt_to_phys(get_free_page())) + * <zaitcev> Hmm + * <zaitcev> Suppose I did this remap_it_my_way(virt_to_phys(get_free_page())). + * So far so good. + * <zaitcev> Now, driver calls pci_free_consistent(with result of + * remap_it_my_way()). + * <zaitcev> How do you find the address to pass to free_pages()? + * <rth> zait: walk the page tables? It's only two or three level after all. + * <rth> zait: you have to walk them anyway to remove the mapping. + * <zaitcev> Hmm + * <zaitcev> Sounds reasonable + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/pci.h> /* struct pci_dev */ +#include <linux/proc_fs.h> + +#include <asm/io.h> +#include <asm/vaddrs.h> +#include <asm/oplib.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/dma.h> + +#define mmu_inval_dma_area(p, l) /* Anton pulled it out for 2.4.0-xx */ + +struct resource *_sparc_find_resource(struct resource *r, unsigned long); + +static void __iomem *_sparc_ioremap(struct resource *res, u32 bus, u32 pa, int sz); +static void __iomem *_sparc_alloc_io(unsigned int busno, unsigned long phys, + unsigned long size, char *name); +static void _sparc_free_io(struct resource *res); + +/* This points to the next to use virtual memory for DVMA mappings */ +static struct resource _sparc_dvma = { + .name = "sparc_dvma", .start = DVMA_VADDR, .end = DVMA_END - 1 +}; +/* This points to the start of I/O mappings, cluable from outside. */ +/*ext*/ struct resource sparc_iomap = { + .name = "sparc_iomap", .start = IOBASE_VADDR, .end = IOBASE_END - 1 +}; + +/* + * Our mini-allocator... + * Boy this is gross! We need it because we must map I/O for + * timers and interrupt controller before the kmalloc is available. + */ + +#define XNMLN 15 +#define XNRES 10 /* SS-10 uses 8 */ + +struct xresource { + struct resource xres; /* Must be first */ + int xflag; /* 1 == used */ + char xname[XNMLN+1]; +}; + +static struct xresource xresv[XNRES]; + +static struct xresource *xres_alloc(void) { + struct xresource *xrp; + int n; + + xrp = xresv; + for (n = 0; n < XNRES; n++) { + if (xrp->xflag == 0) { + xrp->xflag = 1; + return xrp; + } + xrp++; + } + return NULL; +} + +static void xres_free(struct xresource *xrp) { + xrp->xflag = 0; +} + +/* + * These are typically used in PCI drivers + * which are trying to be cross-platform. + * + * Bus type is always zero on IIep. + */ +void __iomem *ioremap(unsigned long offset, unsigned long size) +{ + char name[14]; + + sprintf(name, "phys_%08x", (u32)offset); + return _sparc_alloc_io(0, offset, size, name); +} + +/* + * Comlimentary to ioremap(). + */ +void iounmap(volatile void __iomem *virtual) +{ + unsigned long vaddr = (unsigned long) virtual & PAGE_MASK; + struct resource *res; + + if ((res = _sparc_find_resource(&sparc_iomap, vaddr)) == NULL) { + printk("free_io/iounmap: cannot free %lx\n", vaddr); + return; + } + _sparc_free_io(res); + + if ((char *)res >= (char*)xresv && (char *)res < (char *)&xresv[XNRES]) { + xres_free((struct xresource *)res); + } else { + kfree(res); + } +} + +/* + */ +void __iomem *sbus_ioremap(struct resource *phyres, unsigned long offset, + unsigned long size, char *name) +{ + return _sparc_alloc_io(phyres->flags & 0xF, + phyres->start + offset, size, name); +} + +/* + */ +void sbus_iounmap(volatile void __iomem *addr, unsigned long size) +{ + iounmap(addr); +} + +/* + * Meat of mapping + */ +static void __iomem *_sparc_alloc_io(unsigned int busno, unsigned long phys, + unsigned long size, char *name) +{ + static int printed_full; + struct xresource *xres; + struct resource *res; + char *tack; + int tlen; + void __iomem *va; /* P3 diag */ + + if (name == NULL) name = "???"; + + if ((xres = xres_alloc()) != 0) { + tack = xres->xname; + res = &xres->xres; + } else { + if (!printed_full) { + printk("ioremap: done with statics, switching to malloc\n"); + printed_full = 1; + } + tlen = strlen(name); + tack = kmalloc(sizeof (struct resource) + tlen + 1, GFP_KERNEL); + if (tack == NULL) return NULL; + memset(tack, 0, sizeof(struct resource)); + res = (struct resource *) tack; + tack += sizeof (struct resource); + } + + strlcpy(tack, name, XNMLN+1); + res->name = tack; + + va = _sparc_ioremap(res, busno, phys, size); + /* printk("ioremap(0x%x:%08lx[0x%lx])=%p\n", busno, phys, size, va); */ /* P3 diag */ + return va; +} + +/* + */ +static void __iomem * +_sparc_ioremap(struct resource *res, u32 bus, u32 pa, int sz) +{ + unsigned long offset = ((unsigned long) pa) & (~PAGE_MASK); + + if (allocate_resource(&sparc_iomap, res, + (offset + sz + PAGE_SIZE-1) & PAGE_MASK, + sparc_iomap.start, sparc_iomap.end, PAGE_SIZE, NULL, NULL) != 0) { + /* Usually we cannot see printks in this case. */ + prom_printf("alloc_io_res(%s): cannot occupy\n", + (res->name != NULL)? res->name: "???"); + prom_halt(); + } + + pa &= PAGE_MASK; + sparc_mapiorange(bus, pa, res->start, res->end - res->start + 1); + + return (void __iomem *) (res->start + offset); +} + +/* + * Comlimentary to _sparc_ioremap(). + */ +static void _sparc_free_io(struct resource *res) +{ + unsigned long plen; + + plen = res->end - res->start + 1; + if ((plen & (PAGE_SIZE-1)) != 0) BUG(); + sparc_unmapiorange(res->start, plen); + release_resource(res); +} + +#ifdef CONFIG_SBUS + +void sbus_set_sbus64(struct sbus_dev *sdev, int x) { + printk("sbus_set_sbus64: unsupported\n"); +} + +/* + * Allocate a chunk of memory suitable for DMA. + * Typically devices use them for control blocks. + * CPU may access them without any explicit flushing. + * + * XXX Some clever people know that sdev is not used and supply NULL. Watch. + */ +void *sbus_alloc_consistent(struct sbus_dev *sdev, long len, u32 *dma_addrp) +{ + unsigned long len_total = (len + PAGE_SIZE-1) & PAGE_MASK; + unsigned long va; + struct resource *res; + int order; + + /* XXX why are some lenghts signed, others unsigned? */ + if (len <= 0) { + return NULL; + } + /* XXX So what is maxphys for us and how do drivers know it? */ + if (len > 256*1024) { /* __get_free_pages() limit */ + return NULL; + } + + order = get_order(len_total); + if ((va = __get_free_pages(GFP_KERNEL, order)) == 0) + goto err_nopages; + + if ((res = kmalloc(sizeof(struct resource), GFP_KERNEL)) == NULL) + goto err_nomem; + memset((char*)res, 0, sizeof(struct resource)); + + if (allocate_resource(&_sparc_dvma, res, len_total, + _sparc_dvma.start, _sparc_dvma.end, PAGE_SIZE, NULL, NULL) != 0) { + printk("sbus_alloc_consistent: cannot occupy 0x%lx", len_total); + goto err_nova; + } + mmu_inval_dma_area(va, len_total); + // XXX The mmu_map_dma_area does this for us below, see comments. + // sparc_mapiorange(0, virt_to_phys(va), res->start, len_total); + /* + * XXX That's where sdev would be used. Currently we load + * all iommu tables with the same translations. + */ + if (mmu_map_dma_area(dma_addrp, va, res->start, len_total) != 0) + goto err_noiommu; + + return (void *)res->start; + +err_noiommu: + release_resource(res); +err_nova: + free_pages(va, order); +err_nomem: + kfree(res); +err_nopages: + return NULL; +} + +void sbus_free_consistent(struct sbus_dev *sdev, long n, void *p, u32 ba) +{ + struct resource *res; + struct page *pgv; + + if ((res = _sparc_find_resource(&_sparc_dvma, + (unsigned long)p)) == NULL) { + printk("sbus_free_consistent: cannot free %p\n", p); + return; + } + + if (((unsigned long)p & (PAGE_SIZE-1)) != 0) { + printk("sbus_free_consistent: unaligned va %p\n", p); + return; + } + + n = (n + PAGE_SIZE-1) & PAGE_MASK; + if ((res->end-res->start)+1 != n) { + printk("sbus_free_consistent: region 0x%lx asked 0x%lx\n", + (long)((res->end-res->start)+1), n); + return; + } + + release_resource(res); + kfree(res); + + /* mmu_inval_dma_area(va, n); */ /* it's consistent, isn't it */ + pgv = mmu_translate_dvma(ba); + mmu_unmap_dma_area(ba, n); + + __free_pages(pgv, get_order(n)); +} + +/* + * Map a chunk of memory so that devices can see it. + * CPU view of this memory may be inconsistent with + * a device view and explicit flushing is necessary. + */ +dma_addr_t sbus_map_single(struct sbus_dev *sdev, void *va, size_t len, int direction) +{ + /* XXX why are some lenghts signed, others unsigned? */ + if (len <= 0) { + return 0; + } + /* XXX So what is maxphys for us and how do drivers know it? */ + if (len > 256*1024) { /* __get_free_pages() limit */ + return 0; + } + return mmu_get_scsi_one(va, len, sdev->bus); +} + +void sbus_unmap_single(struct sbus_dev *sdev, dma_addr_t ba, size_t n, int direction) +{ + mmu_release_scsi_one(ba, n, sdev->bus); +} + +int sbus_map_sg(struct sbus_dev *sdev, struct scatterlist *sg, int n, int direction) +{ + mmu_get_scsi_sgl(sg, n, sdev->bus); + + /* + * XXX sparc64 can return a partial length here. sun4c should do this + * but it currently panics if it can't fulfill the request - Anton + */ + return n; +} + +void sbus_unmap_sg(struct sbus_dev *sdev, struct scatterlist *sg, int n, int direction) +{ + mmu_release_scsi_sgl(sg, n, sdev->bus); +} + +/* + */ +void sbus_dma_sync_single_for_cpu(struct sbus_dev *sdev, dma_addr_t ba, size_t size, int direction) +{ +#if 0 + unsigned long va; + struct resource *res; + + /* We do not need the resource, just print a message if invalid. */ + res = _sparc_find_resource(&_sparc_dvma, ba); + if (res == NULL) + panic("sbus_dma_sync_single: 0x%x\n", ba); + + va = page_address(mmu_translate_dvma(ba)); /* XXX higmem */ + /* + * XXX This bogosity will be fixed with the iommu rewrite coming soon + * to a kernel near you. - Anton + */ + /* mmu_inval_dma_area(va, (size + PAGE_SIZE-1) & PAGE_MASK); */ +#endif +} + +void sbus_dma_sync_single_for_device(struct sbus_dev *sdev, dma_addr_t ba, size_t size, int direction) +{ +#if 0 + unsigned long va; + struct resource *res; + + /* We do not need the resource, just print a message if invalid. */ + res = _sparc_find_resource(&_sparc_dvma, ba); + if (res == NULL) + panic("sbus_dma_sync_single: 0x%x\n", ba); + + va = page_address(mmu_translate_dvma(ba)); /* XXX higmem */ + /* + * XXX This bogosity will be fixed with the iommu rewrite coming soon + * to a kernel near you. - Anton + */ + /* mmu_inval_dma_area(va, (size + PAGE_SIZE-1) & PAGE_MASK); */ +#endif +} + +void sbus_dma_sync_sg_for_cpu(struct sbus_dev *sdev, struct scatterlist *sg, int n, int direction) +{ + printk("sbus_dma_sync_sg_for_cpu: not implemented yet\n"); +} + +void sbus_dma_sync_sg_for_device(struct sbus_dev *sdev, struct scatterlist *sg, int n, int direction) +{ + printk("sbus_dma_sync_sg_for_device: not implemented yet\n"); +} +#endif /* CONFIG_SBUS */ + +#ifdef CONFIG_PCI + +/* Allocate and map kernel buffer using consistent mode DMA for a device. + * hwdev should be valid struct pci_dev pointer for PCI devices. + */ +void *pci_alloc_consistent(struct pci_dev *pdev, size_t len, dma_addr_t *pba) +{ + unsigned long len_total = (len + PAGE_SIZE-1) & PAGE_MASK; + unsigned long va; + struct resource *res; + int order; + + if (len == 0) { + return NULL; + } + if (len > 256*1024) { /* __get_free_pages() limit */ + return NULL; + } + + order = get_order(len_total); + va = __get_free_pages(GFP_KERNEL, order); + if (va == 0) { + printk("pci_alloc_consistent: no %ld pages\n", len_total>>PAGE_SHIFT); + return NULL; + } + + if ((res = kmalloc(sizeof(struct resource), GFP_KERNEL)) == NULL) { + free_pages(va, order); + printk("pci_alloc_consistent: no core\n"); + return NULL; + } + memset((char*)res, 0, sizeof(struct resource)); + + if (allocate_resource(&_sparc_dvma, res, len_total, + _sparc_dvma.start, _sparc_dvma.end, PAGE_SIZE, NULL, NULL) != 0) { + printk("pci_alloc_consistent: cannot occupy 0x%lx", len_total); + free_pages(va, order); + kfree(res); + return NULL; + } + mmu_inval_dma_area(va, len_total); +#if 0 +/* P3 */ printk("pci_alloc_consistent: kva %lx uncva %lx phys %lx size %lx\n", + (long)va, (long)res->start, (long)virt_to_phys(va), len_total); +#endif + sparc_mapiorange(0, virt_to_phys(va), res->start, len_total); + + *pba = virt_to_phys(va); /* equals virt_to_bus (R.I.P.) for us. */ + return (void *) res->start; +} + +/* Free and unmap a consistent DMA buffer. + * cpu_addr is what was returned from pci_alloc_consistent, + * size must be the same as what as passed into pci_alloc_consistent, + * and likewise dma_addr must be the same as what *dma_addrp was set to. + * + * References to the memory and mappings assosciated with cpu_addr/dma_addr + * past this call are illegal. + */ +void pci_free_consistent(struct pci_dev *pdev, size_t n, void *p, dma_addr_t ba) +{ + struct resource *res; + unsigned long pgp; + + if ((res = _sparc_find_resource(&_sparc_dvma, + (unsigned long)p)) == NULL) { + printk("pci_free_consistent: cannot free %p\n", p); + return; + } + + if (((unsigned long)p & (PAGE_SIZE-1)) != 0) { + printk("pci_free_consistent: unaligned va %p\n", p); + return; + } + + n = (n + PAGE_SIZE-1) & PAGE_MASK; + if ((res->end-res->start)+1 != n) { + printk("pci_free_consistent: region 0x%lx asked 0x%lx\n", + (long)((res->end-res->start)+1), (long)n); + return; + } + + pgp = (unsigned long) phys_to_virt(ba); /* bus_to_virt actually */ + mmu_inval_dma_area(pgp, n); + sparc_unmapiorange((unsigned long)p, n); + + release_resource(res); + kfree(res); + + free_pages(pgp, get_order(n)); +} + +/* Map a single buffer of the indicated size for DMA in streaming mode. + * The 32-bit bus address to use is returned. + * + * Once the device is given the dma address, the device owns this memory + * until either pci_unmap_single or pci_dma_sync_single_* is performed. + */ +dma_addr_t pci_map_single(struct pci_dev *hwdev, void *ptr, size_t size, + int direction) +{ + if (direction == PCI_DMA_NONE) + BUG(); + /* IIep is write-through, not flushing. */ + return virt_to_phys(ptr); +} + +/* Unmap a single streaming mode DMA translation. The dma_addr and size + * must match what was provided for in a previous pci_map_single call. All + * other usages are undefined. + * + * After this call, reads by the cpu to the buffer are guaranteed to see + * whatever the device wrote there. + */ +void pci_unmap_single(struct pci_dev *hwdev, dma_addr_t ba, size_t size, + int direction) +{ + if (direction == PCI_DMA_NONE) + BUG(); + if (direction != PCI_DMA_TODEVICE) { + mmu_inval_dma_area((unsigned long)phys_to_virt(ba), + (size + PAGE_SIZE-1) & PAGE_MASK); + } +} + +/* + * Same as pci_map_single, but with pages. + */ +dma_addr_t pci_map_page(struct pci_dev *hwdev, struct page *page, + unsigned long offset, size_t size, int direction) +{ + if (direction == PCI_DMA_NONE) + BUG(); + /* IIep is write-through, not flushing. */ + return page_to_phys(page) + offset; +} + +void pci_unmap_page(struct pci_dev *hwdev, + dma_addr_t dma_address, size_t size, int direction) +{ + if (direction == PCI_DMA_NONE) + BUG(); + /* mmu_inval_dma_area XXX */ +} + +/* Map a set of buffers described by scatterlist in streaming + * mode for DMA. This is the scather-gather version of the + * above pci_map_single interface. Here the scatter gather list + * elements are each tagged with the appropriate dma address + * and length. They are obtained via sg_dma_{address,length}(SG). + * + * NOTE: An implementation may be able to use a smaller number of + * DMA address/length pairs than there are SG table elements. + * (for example via virtual mapping capabilities) + * The routine returns the number of addr/length pairs actually + * used, at most nents. + * + * Device ownership issues as mentioned above for pci_map_single are + * the same here. + */ +int pci_map_sg(struct pci_dev *hwdev, struct scatterlist *sg, int nents, + int direction) +{ + int n; + + if (direction == PCI_DMA_NONE) + BUG(); + /* IIep is write-through, not flushing. */ + for (n = 0; n < nents; n++) { + if (page_address(sg->page) == NULL) BUG(); + sg->dvma_address = virt_to_phys(page_address(sg->page)); + sg->dvma_length = sg->length; + sg++; + } + return nents; +} + +/* Unmap a set of streaming mode DMA translations. + * Again, cpu read rules concerning calls here are the same as for + * pci_unmap_single() above. + */ +void pci_unmap_sg(struct pci_dev *hwdev, struct scatterlist *sg, int nents, + int direction) +{ + int n; + + if (direction == PCI_DMA_NONE) + BUG(); + if (direction != PCI_DMA_TODEVICE) { + for (n = 0; n < nents; n++) { + if (page_address(sg->page) == NULL) BUG(); + mmu_inval_dma_area( + (unsigned long) page_address(sg->page), + (sg->length + PAGE_SIZE-1) & PAGE_MASK); + sg++; + } + } +} + +/* Make physical memory consistent for a single + * streaming mode DMA translation before or after a transfer. + * + * If you perform a pci_map_single() but wish to interrogate the + * buffer using the cpu, yet do not wish to teardown the PCI dma + * mapping, you must call this function before doing so. At the + * next point you give the PCI dma address back to the card, you + * must first perform a pci_dma_sync_for_device, and then the + * device again owns the buffer. + */ +void pci_dma_sync_single_for_cpu(struct pci_dev *hwdev, dma_addr_t ba, size_t size, int direction) +{ + if (direction == PCI_DMA_NONE) + BUG(); + if (direction != PCI_DMA_TODEVICE) { + mmu_inval_dma_area((unsigned long)phys_to_virt(ba), + (size + PAGE_SIZE-1) & PAGE_MASK); + } +} + +void pci_dma_sync_single_for_device(struct pci_dev *hwdev, dma_addr_t ba, size_t size, int direction) +{ + if (direction == PCI_DMA_NONE) + BUG(); + if (direction != PCI_DMA_TODEVICE) { + mmu_inval_dma_area((unsigned long)phys_to_virt(ba), + (size + PAGE_SIZE-1) & PAGE_MASK); + } +} + +/* Make physical memory consistent for a set of streaming + * mode DMA translations after a transfer. + * + * The same as pci_dma_sync_single_* but for a scatter-gather list, + * same rules and usage. + */ +void pci_dma_sync_sg_for_cpu(struct pci_dev *hwdev, struct scatterlist *sg, int nents, int direction) +{ + int n; + + if (direction == PCI_DMA_NONE) + BUG(); + if (direction != PCI_DMA_TODEVICE) { + for (n = 0; n < nents; n++) { + if (page_address(sg->page) == NULL) BUG(); + mmu_inval_dma_area( + (unsigned long) page_address(sg->page), + (sg->length + PAGE_SIZE-1) & PAGE_MASK); + sg++; + } + } +} + +void pci_dma_sync_sg_for_device(struct pci_dev *hwdev, struct scatterlist *sg, int nents, int direction) +{ + int n; + + if (direction == PCI_DMA_NONE) + BUG(); + if (direction != PCI_DMA_TODEVICE) { + for (n = 0; n < nents; n++) { + if (page_address(sg->page) == NULL) BUG(); + mmu_inval_dma_area( + (unsigned long) page_address(sg->page), + (sg->length + PAGE_SIZE-1) & PAGE_MASK); + sg++; + } + } +} +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_PROC_FS + +static int +_sparc_io_get_info(char *buf, char **start, off_t fpos, int length, int *eof, + void *data) +{ + char *p = buf, *e = buf + length; + struct resource *r; + const char *nm; + + for (r = ((struct resource *)data)->child; r != NULL; r = r->sibling) { + if (p + 32 >= e) /* Better than nothing */ + break; + if ((nm = r->name) == 0) nm = "???"; + p += sprintf(p, "%08lx-%08lx: %s\n", r->start, r->end, nm); + } + + return p-buf; +} + +#endif /* CONFIG_PROC_FS */ + +/* + * This is a version of find_resource and it belongs to kernel/resource.c. + * Until we have agreement with Linus and Martin, it lingers here. + * + * XXX Too slow. Can have 8192 DVMA pages on sun4m in the worst case. + * This probably warrants some sort of hashing. + */ +struct resource * +_sparc_find_resource(struct resource *root, unsigned long hit) +{ + struct resource *tmp; + + for (tmp = root->child; tmp != 0; tmp = tmp->sibling) { + if (tmp->start <= hit && tmp->end >= hit) + return tmp; + } + return NULL; +} + +void register_proc_sparc_ioport(void) +{ +#ifdef CONFIG_PROC_FS + create_proc_read_entry("io_map",0,NULL,_sparc_io_get_info,&sparc_iomap); + create_proc_read_entry("dvma_map",0,NULL,_sparc_io_get_info,&_sparc_dvma); +#endif +} diff --git a/arch/sparc/kernel/irq.c b/arch/sparc/kernel/irq.c new file mode 100644 index 000000000000..410b9a72aba9 --- /dev/null +++ b/arch/sparc/kernel/irq.c @@ -0,0 +1,614 @@ +/* $Id: irq.c,v 1.114 2001/12/11 04:55:51 davem Exp $ + * arch/sparc/kernel/irq.c: Interrupt request handling routines. On the + * Sparc the IRQ's are basically 'cast in stone' + * and you are supposed to probe the prom's device + * node trees to find out who's got which IRQ. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1995,2002 Pete A. Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Dave Redman (djhr@tadpole.co.uk) + * Copyright (C) 1998-2000 Anton Blanchard (anton@samba.org) + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/linkage.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/delay.h> +#include <linux/threads.h> +#include <linux/spinlock.h> +#include <linux/seq_file.h> + +#include <asm/ptrace.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/psr.h> +#include <asm/smp.h> +#include <asm/vaddrs.h> +#include <asm/timer.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/traps.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/pcic.h> +#include <asm/cacheflush.h> + +#ifdef CONFIG_SMP +#define SMP_NOP2 "nop; nop;\n\t" +#define SMP_NOP3 "nop; nop; nop;\n\t" +#else +#define SMP_NOP2 +#define SMP_NOP3 +#endif /* SMP */ +unsigned long __local_irq_save(void) +{ + unsigned long retval; + unsigned long tmp; + + __asm__ __volatile__( + "rd %%psr, %0\n\t" + SMP_NOP3 /* Sun4m + Cypress + SMP bug */ + "or %0, %2, %1\n\t" + "wr %1, 0, %%psr\n\t" + "nop; nop; nop\n" + : "=&r" (retval), "=r" (tmp) + : "i" (PSR_PIL) + : "memory"); + + return retval; +} + +void local_irq_enable(void) +{ + unsigned long tmp; + + __asm__ __volatile__( + "rd %%psr, %0\n\t" + SMP_NOP3 /* Sun4m + Cypress + SMP bug */ + "andn %0, %1, %0\n\t" + "wr %0, 0, %%psr\n\t" + "nop; nop; nop\n" + : "=&r" (tmp) + : "i" (PSR_PIL) + : "memory"); +} + +void local_irq_restore(unsigned long old_psr) +{ + unsigned long tmp; + + __asm__ __volatile__( + "rd %%psr, %0\n\t" + "and %2, %1, %2\n\t" + SMP_NOP2 /* Sun4m + Cypress + SMP bug */ + "andn %0, %1, %0\n\t" + "wr %0, %2, %%psr\n\t" + "nop; nop; nop\n" + : "=&r" (tmp) + : "i" (PSR_PIL), "r" (old_psr) + : "memory"); +} + +EXPORT_SYMBOL(__local_irq_save); +EXPORT_SYMBOL(local_irq_enable); +EXPORT_SYMBOL(local_irq_restore); + +/* + * Dave Redman (djhr@tadpole.co.uk) + * + * IRQ numbers.. These are no longer restricted to 15.. + * + * this is done to enable SBUS cards and onboard IO to be masked + * correctly. using the interrupt level isn't good enough. + * + * For example: + * A device interrupting at sbus level6 and the Floppy both come in + * at IRQ11, but enabling and disabling them requires writing to + * different bits in the SLAVIO/SEC. + * + * As a result of these changes sun4m machines could now support + * directed CPU interrupts using the existing enable/disable irq code + * with tweaks. + * + */ + +static void irq_panic(void) +{ + extern char *cputypval; + prom_printf("machine: %s doesn't have irq handlers defined!\n",cputypval); + prom_halt(); +} + +void (*sparc_init_timers)(irqreturn_t (*)(int, void *,struct pt_regs *)) = + (void (*)(irqreturn_t (*)(int, void *,struct pt_regs *))) irq_panic; + +/* + * Dave Redman (djhr@tadpole.co.uk) + * + * There used to be extern calls and hard coded values here.. very sucky! + * instead, because some of the devices attach very early, I do something + * equally sucky but at least we'll never try to free statically allocated + * space or call kmalloc before kmalloc_init :(. + * + * In fact it's the timer10 that attaches first.. then timer14 + * then kmalloc_init is called.. then the tty interrupts attach. + * hmmm.... + * + */ +#define MAX_STATIC_ALLOC 4 +struct irqaction static_irqaction[MAX_STATIC_ALLOC]; +int static_irq_count; + +struct irqaction *irq_action[NR_IRQS] = { + [0 ... (NR_IRQS-1)] = NULL +}; + +/* Used to protect the IRQ action lists */ +DEFINE_SPINLOCK(irq_action_lock); + +int show_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v; + struct irqaction * action; + unsigned long flags; +#ifdef CONFIG_SMP + int j; +#endif + + if (sparc_cpu_model == sun4d) { + extern int show_sun4d_interrupts(struct seq_file *, void *); + + return show_sun4d_interrupts(p, v); + } + spin_lock_irqsave(&irq_action_lock, flags); + if (i < NR_IRQS) { + action = *(i + irq_action); + if (!action) + goto out_unlock; + seq_printf(p, "%3d: ", i); +#ifndef CONFIG_SMP + seq_printf(p, "%10u ", kstat_irqs(i)); +#else + for (j = 0; j < NR_CPUS; j++) { + if (cpu_online(j)) + seq_printf(p, "%10u ", + kstat_cpu(cpu_logical_map(j)).irqs[i]); + } +#endif + seq_printf(p, " %c %s", + (action->flags & SA_INTERRUPT) ? '+' : ' ', + action->name); + for (action=action->next; action; action = action->next) { + seq_printf(p, ",%s %s", + (action->flags & SA_INTERRUPT) ? " +" : "", + action->name); + } + seq_putc(p, '\n'); + } +out_unlock: + spin_unlock_irqrestore(&irq_action_lock, flags); + return 0; +} + +void free_irq(unsigned int irq, void *dev_id) +{ + struct irqaction * action; + struct irqaction * tmp = NULL; + unsigned long flags; + unsigned int cpu_irq; + + if (sparc_cpu_model == sun4d) { + extern void sun4d_free_irq(unsigned int, void *); + + sun4d_free_irq(irq, dev_id); + return; + } + cpu_irq = irq & (NR_IRQS - 1); + if (cpu_irq > 14) { /* 14 irq levels on the sparc */ + printk("Trying to free bogus IRQ %d\n", irq); + return; + } + + spin_lock_irqsave(&irq_action_lock, flags); + + action = *(cpu_irq + irq_action); + + if (!action->handler) { + printk("Trying to free free IRQ%d\n",irq); + goto out_unlock; + } + if (dev_id) { + for (; action; action = action->next) { + if (action->dev_id == dev_id) + break; + tmp = action; + } + if (!action) { + printk("Trying to free free shared IRQ%d\n",irq); + goto out_unlock; + } + } else if (action->flags & SA_SHIRQ) { + printk("Trying to free shared IRQ%d with NULL device ID\n", irq); + goto out_unlock; + } + if (action->flags & SA_STATIC_ALLOC) + { + /* This interrupt is marked as specially allocated + * so it is a bad idea to free it. + */ + printk("Attempt to free statically allocated IRQ%d (%s)\n", + irq, action->name); + goto out_unlock; + } + + if (action && tmp) + tmp->next = action->next; + else + *(cpu_irq + irq_action) = action->next; + + spin_unlock_irqrestore(&irq_action_lock, flags); + + synchronize_irq(irq); + + spin_lock_irqsave(&irq_action_lock, flags); + + kfree(action); + + if (!(*(cpu_irq + irq_action))) + disable_irq(irq); + +out_unlock: + spin_unlock_irqrestore(&irq_action_lock, flags); +} + +EXPORT_SYMBOL(free_irq); + +/* + * This is called when we want to synchronize with + * interrupts. We may for example tell a device to + * stop sending interrupts: but to make sure there + * are no interrupts that are executing on another + * CPU we need to call this function. + */ +#ifdef CONFIG_SMP +void synchronize_irq(unsigned int irq) +{ + printk("synchronize_irq says: implement me!\n"); + BUG(); +} +#endif /* SMP */ + +void unexpected_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + int i; + struct irqaction * action; + unsigned int cpu_irq; + + cpu_irq = irq & (NR_IRQS - 1); + action = *(cpu_irq + irq_action); + + printk("IO device interrupt, irq = %d\n", irq); + printk("PC = %08lx NPC = %08lx FP=%08lx\n", regs->pc, + regs->npc, regs->u_regs[14]); + if (action) { + printk("Expecting: "); + for (i = 0; i < 16; i++) + if (action->handler) + printk("[%s:%d:0x%x] ", action->name, + (int) i, (unsigned int) action->handler); + } + printk("AIEEE\n"); + panic("bogus interrupt received"); +} + +void handler_irq(int irq, struct pt_regs * regs) +{ + struct irqaction * action; + int cpu = smp_processor_id(); +#ifdef CONFIG_SMP + extern void smp4m_irq_rotate(int cpu); +#endif + + irq_enter(); + disable_pil_irq(irq); +#ifdef CONFIG_SMP + /* Only rotate on lower priority IRQ's (scsi, ethernet, etc.). */ + if(irq < 10) + smp4m_irq_rotate(cpu); +#endif + action = *(irq + irq_action); + kstat_cpu(cpu).irqs[irq]++; + do { + if (!action || !action->handler) + unexpected_irq(irq, NULL, regs); + action->handler(irq, action->dev_id, regs); + action = action->next; + } while (action); + enable_pil_irq(irq); + irq_exit(); +} + +#ifdef CONFIG_BLK_DEV_FD +extern void floppy_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +void sparc_floppy_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + disable_pil_irq(irq); + irq_enter(); + kstat_cpu(cpu).irqs[irq]++; + floppy_interrupt(irq, dev_id, regs); + irq_exit(); + enable_pil_irq(irq); + // XXX Eek, it's totally changed with preempt_count() and such + // if (softirq_pending(cpu)) + // do_softirq(); +} +#endif + +/* Fast IRQ's on the Sparc can only have one routine attached to them, + * thus no sharing possible. + */ +int request_fast_irq(unsigned int irq, + irqreturn_t (*handler)(int, void *, struct pt_regs *), + unsigned long irqflags, const char *devname) +{ + struct irqaction *action; + unsigned long flags; + unsigned int cpu_irq; + int ret; +#ifdef CONFIG_SMP + struct tt_entry *trap_table; + extern struct tt_entry trapbase_cpu1, trapbase_cpu2, trapbase_cpu3; +#endif + + cpu_irq = irq & (NR_IRQS - 1); + if(cpu_irq > 14) { + ret = -EINVAL; + goto out; + } + if(!handler) { + ret = -EINVAL; + goto out; + } + + spin_lock_irqsave(&irq_action_lock, flags); + + action = *(cpu_irq + irq_action); + if(action) { + if(action->flags & SA_SHIRQ) + panic("Trying to register fast irq when already shared.\n"); + if(irqflags & SA_SHIRQ) + panic("Trying to register fast irq as shared.\n"); + + /* Anyway, someone already owns it so cannot be made fast. */ + printk("request_fast_irq: Trying to register yet already owned.\n"); + ret = -EBUSY; + goto out_unlock; + } + + /* If this is flagged as statically allocated then we use our + * private struct which is never freed. + */ + if (irqflags & SA_STATIC_ALLOC) { + if (static_irq_count < MAX_STATIC_ALLOC) + action = &static_irqaction[static_irq_count++]; + else + printk("Fast IRQ%d (%s) SA_STATIC_ALLOC failed using kmalloc\n", + irq, devname); + } + + if (action == NULL) + action = (struct irqaction *)kmalloc(sizeof(struct irqaction), + GFP_ATOMIC); + + if (!action) { + ret = -ENOMEM; + goto out_unlock; + } + + /* Dork with trap table if we get this far. */ +#define INSTANTIATE(table) \ + table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_one = SPARC_RD_PSR_L0; \ + table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_two = \ + SPARC_BRANCH((unsigned long) handler, \ + (unsigned long) &table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_two);\ + table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_three = SPARC_RD_WIM_L3; \ + table[SP_TRAP_IRQ1+(cpu_irq-1)].inst_four = SPARC_NOP; + + INSTANTIATE(sparc_ttable) +#ifdef CONFIG_SMP + trap_table = &trapbase_cpu1; INSTANTIATE(trap_table) + trap_table = &trapbase_cpu2; INSTANTIATE(trap_table) + trap_table = &trapbase_cpu3; INSTANTIATE(trap_table) +#endif +#undef INSTANTIATE + /* + * XXX Correct thing whould be to flush only I- and D-cache lines + * which contain the handler in question. But as of time of the + * writing we have no CPU-neutral interface to fine-grained flushes. + */ + flush_cache_all(); + + action->handler = handler; + action->flags = irqflags; + cpus_clear(action->mask); + action->name = devname; + action->dev_id = NULL; + action->next = NULL; + + *(cpu_irq + irq_action) = action; + + enable_irq(irq); + + ret = 0; +out_unlock: + spin_unlock_irqrestore(&irq_action_lock, flags); +out: + return ret; +} + +int request_irq(unsigned int irq, + irqreturn_t (*handler)(int, void *, struct pt_regs *), + unsigned long irqflags, const char * devname, void *dev_id) +{ + struct irqaction * action, *tmp = NULL; + unsigned long flags; + unsigned int cpu_irq; + int ret; + + if (sparc_cpu_model == sun4d) { + extern int sun4d_request_irq(unsigned int, + irqreturn_t (*)(int, void *, struct pt_regs *), + unsigned long, const char *, void *); + return sun4d_request_irq(irq, handler, irqflags, devname, dev_id); + } + cpu_irq = irq & (NR_IRQS - 1); + if(cpu_irq > 14) { + ret = -EINVAL; + goto out; + } + if (!handler) { + ret = -EINVAL; + goto out; + } + + spin_lock_irqsave(&irq_action_lock, flags); + + action = *(cpu_irq + irq_action); + if (action) { + if ((action->flags & SA_SHIRQ) && (irqflags & SA_SHIRQ)) { + for (tmp = action; tmp->next; tmp = tmp->next); + } else { + ret = -EBUSY; + goto out_unlock; + } + if ((action->flags & SA_INTERRUPT) ^ (irqflags & SA_INTERRUPT)) { + printk("Attempt to mix fast and slow interrupts on IRQ%d denied\n", irq); + ret = -EBUSY; + goto out_unlock; + } + action = NULL; /* Or else! */ + } + + /* If this is flagged as statically allocated then we use our + * private struct which is never freed. + */ + if (irqflags & SA_STATIC_ALLOC) { + if (static_irq_count < MAX_STATIC_ALLOC) + action = &static_irqaction[static_irq_count++]; + else + printk("Request for IRQ%d (%s) SA_STATIC_ALLOC failed using kmalloc\n", irq, devname); + } + + if (action == NULL) + action = (struct irqaction *)kmalloc(sizeof(struct irqaction), + GFP_ATOMIC); + + if (!action) { + ret = -ENOMEM; + goto out_unlock; + } + + action->handler = handler; + action->flags = irqflags; + cpus_clear(action->mask); + action->name = devname; + action->next = NULL; + action->dev_id = dev_id; + + if (tmp) + tmp->next = action; + else + *(cpu_irq + irq_action) = action; + + enable_irq(irq); + + ret = 0; +out_unlock: + spin_unlock_irqrestore(&irq_action_lock, flags); +out: + return ret; +} + +EXPORT_SYMBOL(request_irq); + +/* We really don't need these at all on the Sparc. We only have + * stubs here because they are exported to modules. + */ +unsigned long probe_irq_on(void) +{ + return 0; +} + +EXPORT_SYMBOL(probe_irq_on); + +int probe_irq_off(unsigned long mask) +{ + return 0; +} + +EXPORT_SYMBOL(probe_irq_off); + +/* djhr + * This could probably be made indirect too and assigned in the CPU + * bits of the code. That would be much nicer I think and would also + * fit in with the idea of being able to tune your kernel for your machine + * by removing unrequired machine and device support. + * + */ + +void __init init_IRQ(void) +{ + extern void sun4c_init_IRQ( void ); + extern void sun4m_init_IRQ( void ); + extern void sun4d_init_IRQ( void ); + + switch(sparc_cpu_model) { + case sun4c: + case sun4: + sun4c_init_IRQ(); + break; + + case sun4m: +#ifdef CONFIG_PCI + pcic_probe(); + if (pcic_present()) { + sun4m_pci_init_IRQ(); + break; + } +#endif + sun4m_init_IRQ(); + break; + + case sun4d: + sun4d_init_IRQ(); + break; + + default: + prom_printf("Cannot initialize IRQ's on this Sun machine..."); + break; + } + btfixup(); +} + +void init_irq_proc(void) +{ + /* For now, nothing... */ +} diff --git a/arch/sparc/kernel/module.c b/arch/sparc/kernel/module.c new file mode 100644 index 000000000000..7931d6f92819 --- /dev/null +++ b/arch/sparc/kernel/module.c @@ -0,0 +1,159 @@ +/* Kernel module help for sparc32. + * + * Copyright (C) 2001 Rusty Russell. + * Copyright (C) 2002 David S. Miller. + */ + +#include <linux/moduleloader.h> +#include <linux/kernel.h> +#include <linux/elf.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/string.h> + +void *module_alloc(unsigned long size) +{ + void *ret; + + /* We handle the zero case fine, unlike vmalloc */ + if (size == 0) + return NULL; + + ret = vmalloc(size); + if (!ret) + ret = ERR_PTR(-ENOMEM); + else + memset(ret, 0, size); + + return ret; +} + +/* Free memory returned from module_core_alloc/module_init_alloc */ +void module_free(struct module *mod, void *module_region) +{ + vfree(module_region); + /* FIXME: If module_region == mod->init_region, trim exception + table entries. */ +} + +/* Make generic code ignore STT_REGISTER dummy undefined symbols, + * and replace references to .func with func as in ppc64's dedotify. + */ +int module_frob_arch_sections(Elf_Ehdr *hdr, + Elf_Shdr *sechdrs, + char *secstrings, + struct module *mod) +{ + unsigned int symidx; + Elf32_Sym *sym; + char *strtab; + int i; + + for (symidx = 0; sechdrs[symidx].sh_type != SHT_SYMTAB; symidx++) { + if (symidx == hdr->e_shnum-1) { + printk("%s: no symtab found.\n", mod->name); + return -ENOEXEC; + } + } + sym = (Elf32_Sym *)sechdrs[symidx].sh_addr; + strtab = (char *)sechdrs[sechdrs[symidx].sh_link].sh_addr; + + for (i = 1; i < sechdrs[symidx].sh_size / sizeof(Elf_Sym); i++) { + if (sym[i].st_shndx == SHN_UNDEF) { + if (ELF32_ST_TYPE(sym[i].st_info) == STT_REGISTER) + sym[i].st_shndx = SHN_ABS; + else { + char *name = strtab + sym[i].st_name; + if (name[0] == '.') + memmove(name, name+1, strlen(name)); + } + } + } + return 0; +} + +int apply_relocate(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + printk(KERN_ERR "module %s: non-ADD RELOCATION unsupported\n", + me->name); + return -ENOEXEC; +} + +int apply_relocate_add(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + unsigned int i; + Elf32_Rela *rel = (void *)sechdrs[relsec].sh_addr; + Elf32_Sym *sym; + u8 *location; + u32 *loc32; + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + Elf32_Addr v; + + /* This is where to make the change */ + location = (u8 *)sechdrs[sechdrs[relsec].sh_info].sh_addr + + rel[i].r_offset; + loc32 = (u32 *) location; + /* This is the symbol it is referring to. Note that all + undefined symbols have been resolved. */ + sym = (Elf32_Sym *)sechdrs[symindex].sh_addr + + ELF32_R_SYM(rel[i].r_info); + v = sym->st_value + rel[i].r_addend; + + switch (ELF32_R_TYPE(rel[i].r_info)) { + case R_SPARC_32: + location[0] = v >> 24; + location[1] = v >> 16; + location[2] = v >> 8; + location[3] = v >> 0; + break; + + case R_SPARC_WDISP30: + v -= (Elf32_Addr) location; + *loc32 = (*loc32 & ~0x3fffffff) | + ((v >> 2) & 0x3fffffff); + break; + + case R_SPARC_WDISP22: + v -= (Elf32_Addr) location; + *loc32 = (*loc32 & ~0x3fffff) | + ((v >> 2) & 0x3fffff); + break; + + case R_SPARC_LO10: + *loc32 = (*loc32 & ~0x3ff) | (v & 0x3ff); + break; + + case R_SPARC_HI22: + *loc32 = (*loc32 & ~0x3fffff) | + ((v >> 10) & 0x3fffff); + break; + + default: + printk(KERN_ERR "module %s: Unknown relocation: %x\n", + me->name, + (int) (ELF32_R_TYPE(rel[i].r_info) & 0xff)); + return -ENOEXEC; + }; + } + return 0; +} + +int module_finalize(const Elf_Ehdr *hdr, + const Elf_Shdr *sechdrs, + struct module *me) +{ + return 0; +} + +void module_arch_cleanup(struct module *mod) +{ +} diff --git a/arch/sparc/kernel/muldiv.c b/arch/sparc/kernel/muldiv.c new file mode 100644 index 000000000000..37b9a4942232 --- /dev/null +++ b/arch/sparc/kernel/muldiv.c @@ -0,0 +1,240 @@ +/* $Id: muldiv.c,v 1.5 1997/12/15 20:07:20 ecd Exp $ + * muldiv.c: Hardware multiply/division illegal instruction trap + * for sun4c/sun4 (which do not have those instructions) + * + * Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * + * 2004-12-25 Krzysztof Helt (krzysztof.h1@wp.pl) + * - fixed registers constrains in inline assembly declarations + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <asm/ptrace.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +/* #define DEBUG_MULDIV */ + +static inline int has_imm13(int insn) +{ + return (insn & 0x2000); +} + +static inline int is_foocc(int insn) +{ + return (insn & 0x800000); +} + +static inline int sign_extend_imm13(int imm) +{ + return imm << 19 >> 19; +} + +static inline void advance(struct pt_regs *regs) +{ + regs->pc = regs->npc; + regs->npc += 4; +} + +static inline void maybe_flush_windows(unsigned int rs1, unsigned int rs2, + unsigned int rd) +{ + if(rs2 >= 16 || rs1 >= 16 || rd >= 16) { + /* Wheee... */ + __asm__ __volatile__("save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "restore; restore; restore; restore;\n\t" + "restore; restore; restore;\n\t"); + } +} + +#define fetch_reg(reg, regs) ({ \ + struct reg_window __user *win; \ + register unsigned long ret; \ + \ + if (!(reg)) ret = 0; \ + else if ((reg) < 16) { \ + ret = regs->u_regs[(reg)]; \ + } else { \ + /* Ho hum, the slightly complicated case. */ \ + win = (struct reg_window __user *)regs->u_regs[UREG_FP];\ + if (get_user (ret, &win->locals[(reg) - 16])) return -1;\ + } \ + ret; \ +}) + +static inline int +store_reg(unsigned int result, unsigned int reg, struct pt_regs *regs) +{ + struct reg_window __user *win; + + if (!reg) + return 0; + if (reg < 16) { + regs->u_regs[reg] = result; + return 0; + } else { + /* need to use put_user() in this case: */ + win = (struct reg_window __user *) regs->u_regs[UREG_FP]; + return (put_user(result, &win->locals[reg - 16])); + } +} + +extern void handle_hw_divzero (struct pt_regs *regs, unsigned long pc, + unsigned long npc, unsigned long psr); + +/* Should return 0 if mul/div emulation succeeded and SIGILL should + * not be issued. + */ +int do_user_muldiv(struct pt_regs *regs, unsigned long pc) +{ + unsigned int insn; + int inst; + unsigned int rs1, rs2, rdv; + + if (!pc) + return -1; /* This happens to often, I think */ + if (get_user (insn, (unsigned int __user *)pc)) + return -1; + if ((insn & 0xc1400000) != 0x80400000) + return -1; + inst = ((insn >> 19) & 0xf); + if ((inst & 0xe) != 10 && (inst & 0xe) != 14) + return -1; + + /* Now we know we have to do something with umul, smul, udiv or sdiv */ + rs1 = (insn >> 14) & 0x1f; + rs2 = insn & 0x1f; + rdv = (insn >> 25) & 0x1f; + if (has_imm13(insn)) { + maybe_flush_windows(rs1, 0, rdv); + rs2 = sign_extend_imm13(insn); + } else { + maybe_flush_windows(rs1, rs2, rdv); + rs2 = fetch_reg(rs2, regs); + } + rs1 = fetch_reg(rs1, regs); + switch (inst) { + case 10: /* umul */ +#ifdef DEBUG_MULDIV + printk ("unsigned muldiv: 0x%x * 0x%x = ", rs1, rs2); +#endif + __asm__ __volatile__ ("\n\t" + "mov %0, %%o0\n\t" + "call .umul\n\t" + " mov %1, %%o1\n\t" + "mov %%o0, %0\n\t" + "mov %%o1, %1\n\t" + : "=r" (rs1), "=r" (rs2) + : "0" (rs1), "1" (rs2) + : "o0", "o1", "o2", "o3", "o4", "o5", "o7", "cc"); +#ifdef DEBUG_MULDIV + printk ("0x%x%08x\n", rs2, rs1); +#endif + if (store_reg(rs1, rdv, regs)) + return -1; + regs->y = rs2; + break; + case 11: /* smul */ +#ifdef DEBUG_MULDIV + printk ("signed muldiv: 0x%x * 0x%x = ", rs1, rs2); +#endif + __asm__ __volatile__ ("\n\t" + "mov %0, %%o0\n\t" + "call .mul\n\t" + " mov %1, %%o1\n\t" + "mov %%o0, %0\n\t" + "mov %%o1, %1\n\t" + : "=r" (rs1), "=r" (rs2) + : "0" (rs1), "1" (rs2) + : "o0", "o1", "o2", "o3", "o4", "o5", "o7", "cc"); +#ifdef DEBUG_MULDIV + printk ("0x%x%08x\n", rs2, rs1); +#endif + if (store_reg(rs1, rdv, regs)) + return -1; + regs->y = rs2; + break; + case 14: /* udiv */ +#ifdef DEBUG_MULDIV + printk ("unsigned muldiv: 0x%x%08x / 0x%x = ", regs->y, rs1, rs2); +#endif + if (!rs2) { +#ifdef DEBUG_MULDIV + printk ("DIVISION BY ZERO\n"); +#endif + handle_hw_divzero (regs, pc, regs->npc, regs->psr); + return 0; + } + __asm__ __volatile__ ("\n\t" + "mov %2, %%o0\n\t" + "mov %0, %%o1\n\t" + "mov %%g0, %%o2\n\t" + "call __udivdi3\n\t" + " mov %1, %%o3\n\t" + "mov %%o1, %0\n\t" + "mov %%o0, %1\n\t" + : "=r" (rs1), "=r" (rs2) + : "r" (regs->y), "0" (rs1), "1" (rs2) + : "o0", "o1", "o2", "o3", "o4", "o5", "o7", + "g1", "g2", "g3", "cc"); +#ifdef DEBUG_MULDIV + printk ("0x%x\n", rs1); +#endif + if (store_reg(rs1, rdv, regs)) + return -1; + break; + case 15: /* sdiv */ +#ifdef DEBUG_MULDIV + printk ("signed muldiv: 0x%x%08x / 0x%x = ", regs->y, rs1, rs2); +#endif + if (!rs2) { +#ifdef DEBUG_MULDIV + printk ("DIVISION BY ZERO\n"); +#endif + handle_hw_divzero (regs, pc, regs->npc, regs->psr); + return 0; + } + __asm__ __volatile__ ("\n\t" + "mov %2, %%o0\n\t" + "mov %0, %%o1\n\t" + "mov %%g0, %%o2\n\t" + "call __divdi3\n\t" + " mov %1, %%o3\n\t" + "mov %%o1, %0\n\t" + "mov %%o0, %1\n\t" + : "=r" (rs1), "=r" (rs2) + : "r" (regs->y), "0" (rs1), "1" (rs2) + : "o0", "o1", "o2", "o3", "o4", "o5", "o7", + "g1", "g2", "g3", "cc"); +#ifdef DEBUG_MULDIV + printk ("0x%x\n", rs1); +#endif + if (store_reg(rs1, rdv, regs)) + return -1; + break; + } + if (is_foocc (insn)) { + regs->psr &= ~PSR_ICC; + if ((inst & 0xe) == 14) { + /* ?div */ + if (rs2) regs->psr |= PSR_V; + } + if (!rs1) regs->psr |= PSR_Z; + if (((int)rs1) < 0) regs->psr |= PSR_N; +#ifdef DEBUG_MULDIV + printk ("psr muldiv: %08x\n", regs->psr); +#endif + } + advance(regs); + return 0; +} diff --git a/arch/sparc/kernel/pcic.c b/arch/sparc/kernel/pcic.c new file mode 100644 index 000000000000..597d3ff6ad68 --- /dev/null +++ b/arch/sparc/kernel/pcic.c @@ -0,0 +1,1041 @@ +/* + * pcic.c: MicroSPARC-IIep PCI controller support + * + * Copyright (C) 1998 V. Roganov and G. Raiko + * + * Code is derived from Ultra/PCI PSYCHO controller support, see that + * for author info. + * + * Support for diverse IIep based platforms by Pete Zaitcev. + * CP-1200 by Eric Brower. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/jiffies.h> + +#include <asm/ebus.h> +#include <asm/sbus.h> /* for sanity check... */ +#include <asm/swift.h> /* for cache flushing. */ +#include <asm/io.h> + +#include <linux/ctype.h> +#include <linux/pci.h> +#include <linux/time.h> +#include <linux/timex.h> +#include <linux/interrupt.h> + +#include <asm/irq.h> +#include <asm/oplib.h> +#include <asm/pcic.h> +#include <asm/timer.h> +#include <asm/uaccess.h> + + +unsigned int pcic_pin_to_irq(unsigned int pin, char *name); + +/* + * I studied different documents and many live PROMs both from 2.30 + * family and 3.xx versions. I came to the amazing conclusion: there is + * absolutely no way to route interrupts in IIep systems relying on + * information which PROM presents. We must hardcode interrupt routing + * schematics. And this actually sucks. -- zaitcev 1999/05/12 + * + * To find irq for a device we determine which routing map + * is in effect or, in other words, on which machine we are running. + * We use PROM name for this although other techniques may be used + * in special cases (Gleb reports a PROMless IIep based system). + * Once we know the map we take device configuration address and + * find PCIC pin number where INT line goes. Then we may either program + * preferred irq into the PCIC or supply the preexisting irq to the device. + */ +struct pcic_ca2irq { + unsigned char busno; /* PCI bus number */ + unsigned char devfn; /* Configuration address */ + unsigned char pin; /* PCIC external interrupt pin */ + unsigned char irq; /* Preferred IRQ (mappable in PCIC) */ + unsigned int force; /* Enforce preferred IRQ */ +}; + +struct pcic_sn2list { + char *sysname; + struct pcic_ca2irq *intmap; + int mapdim; +}; + +/* + * JavaEngine-1 apparently has different versions. + * + * According to communications with Sun folks, for P2 build 501-4628-03: + * pin 0 - parallel, audio; + * pin 1 - Ethernet; + * pin 2 - su; + * pin 3 - PS/2 kbd and mouse. + * + * OEM manual (805-1486): + * pin 0: Ethernet + * pin 1: All EBus + * pin 2: IGA (unused) + * pin 3: Not connected + * OEM manual says that 501-4628 & 501-4811 are the same thing, + * only the latter has NAND flash in place. + * + * So far unofficial Sun wins over the OEM manual. Poor OEMs... + */ +static struct pcic_ca2irq pcic_i_je1a[] = { /* 501-4811-03 */ + { 0, 0x00, 2, 12, 0 }, /* EBus: hogs all */ + { 0, 0x01, 1, 6, 1 }, /* Happy Meal */ + { 0, 0x80, 0, 7, 0 }, /* IGA (unused) */ +}; + +/* XXX JS-E entry is incomplete - PCI Slot 2 address (pin 7)? */ +static struct pcic_ca2irq pcic_i_jse[] = { + { 0, 0x00, 0, 13, 0 }, /* Ebus - serial and keyboard */ + { 0, 0x01, 1, 6, 0 }, /* hme */ + { 0, 0x08, 2, 9, 0 }, /* VGA - we hope not used :) */ + { 0, 0x10, 6, 8, 0 }, /* PCI INTA# in Slot 1 */ + { 0, 0x18, 7, 12, 0 }, /* PCI INTA# in Slot 2, shared w. RTC */ + { 0, 0x38, 4, 9, 0 }, /* All ISA devices. Read 8259. */ + { 0, 0x80, 5, 11, 0 }, /* EIDE */ + /* {0,0x88, 0,0,0} - unknown device... PMU? Probably no interrupt. */ + { 0, 0xA0, 4, 9, 0 }, /* USB */ + /* + * Some pins belong to non-PCI devices, we hardcode them in drivers. + * sun4m timers - irq 10, 14 + * PC style RTC - pin 7, irq 4 ? + * Smart card, Parallel - pin 4 shared with USB, ISA + * audio - pin 3, irq 5 ? + */ +}; + +/* SPARCengine-6 was the original release name of CP1200. + * The documentation differs between the two versions + */ +static struct pcic_ca2irq pcic_i_se6[] = { + { 0, 0x08, 0, 2, 0 }, /* SCSI */ + { 0, 0x01, 1, 6, 0 }, /* HME */ + { 0, 0x00, 3, 13, 0 }, /* EBus */ +}; + +/* + * Krups (courtesy of Varol Kaptan) + * No documentation available, but it was easy to guess + * because it was very similar to Espresso. + * + * pin 0 - kbd, mouse, serial; + * pin 1 - Ethernet; + * pin 2 - igs (we do not use it); + * pin 3 - audio; + * pin 4,5,6 - unused; + * pin 7 - RTC (from P2 onwards as David B. says). + */ +static struct pcic_ca2irq pcic_i_jk[] = { + { 0, 0x00, 0, 13, 0 }, /* Ebus - serial and keyboard */ + { 0, 0x01, 1, 6, 0 }, /* hme */ +}; + +/* + * Several entries in this list may point to the same routing map + * as several PROMs may be installed on the same physical board. + */ +#define SN2L_INIT(name, map) \ + { name, map, sizeof(map)/sizeof(struct pcic_ca2irq) } + +static struct pcic_sn2list pcic_known_sysnames[] = { + SN2L_INIT("SUNW,JavaEngine1", pcic_i_je1a), /* JE1, PROM 2.32 */ + SN2L_INIT("SUNW,JS-E", pcic_i_jse), /* PROLL JavaStation-E */ + SN2L_INIT("SUNW,SPARCengine-6", pcic_i_se6), /* SPARCengine-6/CP-1200 */ + SN2L_INIT("SUNW,JS-NC", pcic_i_jk), /* PROLL JavaStation-NC */ + SN2L_INIT("SUNW,JSIIep", pcic_i_jk), /* OBP JavaStation-NC */ + { NULL, NULL, 0 } +}; + +/* + * Only one PCIC per IIep, + * and since we have no SMP IIep, only one per system. + */ +static int pcic0_up; +static struct linux_pcic pcic0; + +void * __iomem pcic_regs; +volatile int pcic_speculative; +volatile int pcic_trapped; + +static void pci_do_gettimeofday(struct timeval *tv); +static int pci_do_settimeofday(struct timespec *tv); + +#define CONFIG_CMD(bus, device_fn, where) (0x80000000 | (((unsigned int)bus) << 16) | (((unsigned int)device_fn) << 8) | (where & ~3)) + +static int pcic_read_config_dword(unsigned int busno, unsigned int devfn, + int where, u32 *value) +{ + struct linux_pcic *pcic; + unsigned long flags; + + pcic = &pcic0; + + local_irq_save(flags); +#if 0 /* does not fail here */ + pcic_speculative = 1; + pcic_trapped = 0; +#endif + writel(CONFIG_CMD(busno, devfn, where), pcic->pcic_config_space_addr); +#if 0 /* does not fail here */ + nop(); + if (pcic_trapped) { + local_irq_restore(flags); + *value = ~0; + return 0; + } +#endif + pcic_speculative = 2; + pcic_trapped = 0; + *value = readl(pcic->pcic_config_space_data + (where&4)); + nop(); + if (pcic_trapped) { + pcic_speculative = 0; + local_irq_restore(flags); + *value = ~0; + return 0; + } + pcic_speculative = 0; + local_irq_restore(flags); + return 0; +} + +static int pcic_read_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + unsigned int v; + + if (bus->number != 0) return -EINVAL; + switch (size) { + case 1: + pcic_read_config_dword(bus->number, devfn, where&~3, &v); + *val = 0xff & (v >> (8*(where & 3))); + return 0; + case 2: + if (where&1) return -EINVAL; + pcic_read_config_dword(bus->number, devfn, where&~3, &v); + *val = 0xffff & (v >> (8*(where & 3))); + return 0; + case 4: + if (where&3) return -EINVAL; + pcic_read_config_dword(bus->number, devfn, where&~3, val); + return 0; + } + return -EINVAL; +} + +static int pcic_write_config_dword(unsigned int busno, unsigned int devfn, + int where, u32 value) +{ + struct linux_pcic *pcic; + unsigned long flags; + + pcic = &pcic0; + + local_irq_save(flags); + writel(CONFIG_CMD(busno, devfn, where), pcic->pcic_config_space_addr); + writel(value, pcic->pcic_config_space_data + (where&4)); + local_irq_restore(flags); + return 0; +} + +static int pcic_write_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + unsigned int v; + + if (bus->number != 0) return -EINVAL; + switch (size) { + case 1: + pcic_read_config_dword(bus->number, devfn, where&~3, &v); + v = (v & ~(0xff << (8*(where&3)))) | + ((0xff&val) << (8*(where&3))); + return pcic_write_config_dword(bus->number, devfn, where&~3, v); + case 2: + if (where&1) return -EINVAL; + pcic_read_config_dword(bus->number, devfn, where&~3, &v); + v = (v & ~(0xffff << (8*(where&3)))) | + ((0xffff&val) << (8*(where&3))); + return pcic_write_config_dword(bus->number, devfn, where&~3, v); + case 4: + if (where&3) return -EINVAL; + return pcic_write_config_dword(bus->number, devfn, where, val); + } + return -EINVAL; +} + +static struct pci_ops pcic_ops = { + .read = pcic_read_config, + .write = pcic_write_config, +}; + +/* + * On sparc64 pcibios_init() calls pci_controller_probe(). + * We want PCIC probed little ahead so that interrupt controller + * would be operational. + */ +int __init pcic_probe(void) +{ + struct linux_pcic *pcic; + struct linux_prom_registers regs[PROMREG_MAX]; + struct linux_pbm_info* pbm; + char namebuf[64]; + int node; + int err; + + if (pcic0_up) { + prom_printf("PCIC: called twice!\n"); + prom_halt(); + } + pcic = &pcic0; + + node = prom_getchild (prom_root_node); + node = prom_searchsiblings (node, "pci"); + if (node == 0) + return -ENODEV; + /* + * Map in PCIC register set, config space, and IO base + */ + err = prom_getproperty(node, "reg", (char*)regs, sizeof(regs)); + if (err == 0 || err == -1) { + prom_printf("PCIC: Error, cannot get PCIC registers " + "from PROM.\n"); + prom_halt(); + } + + pcic0_up = 1; + + pcic->pcic_res_regs.name = "pcic_registers"; + pcic->pcic_regs = ioremap(regs[0].phys_addr, regs[0].reg_size); + if (!pcic->pcic_regs) { + prom_printf("PCIC: Error, cannot map PCIC registers.\n"); + prom_halt(); + } + + pcic->pcic_res_io.name = "pcic_io"; + if ((pcic->pcic_io = (unsigned long) + ioremap(regs[1].phys_addr, 0x10000)) == 0) { + prom_printf("PCIC: Error, cannot map PCIC IO Base.\n"); + prom_halt(); + } + + pcic->pcic_res_cfg_addr.name = "pcic_cfg_addr"; + if ((pcic->pcic_config_space_addr = + ioremap(regs[2].phys_addr, regs[2].reg_size * 2)) == 0) { + prom_printf("PCIC: Error, cannot map" + "PCI Configuration Space Address.\n"); + prom_halt(); + } + + /* + * Docs say three least significant bits in address and data + * must be the same. Thus, we need adjust size of data. + */ + pcic->pcic_res_cfg_data.name = "pcic_cfg_data"; + if ((pcic->pcic_config_space_data = + ioremap(regs[3].phys_addr, regs[3].reg_size * 2)) == 0) { + prom_printf("PCIC: Error, cannot map" + "PCI Configuration Space Data.\n"); + prom_halt(); + } + + pbm = &pcic->pbm; + pbm->prom_node = node; + prom_getstring(node, "name", namebuf, 63); namebuf[63] = 0; + strcpy(pbm->prom_name, namebuf); + + { + extern volatile int t_nmi[1]; + extern int pcic_nmi_trap_patch[1]; + + t_nmi[0] = pcic_nmi_trap_patch[0]; + t_nmi[1] = pcic_nmi_trap_patch[1]; + t_nmi[2] = pcic_nmi_trap_patch[2]; + t_nmi[3] = pcic_nmi_trap_patch[3]; + swift_flush_dcache(); + pcic_regs = pcic->pcic_regs; + } + + prom_getstring(prom_root_node, "name", namebuf, 63); namebuf[63] = 0; + { + struct pcic_sn2list *p; + + for (p = pcic_known_sysnames; p->sysname != NULL; p++) { + if (strcmp(namebuf, p->sysname) == 0) + break; + } + pcic->pcic_imap = p->intmap; + pcic->pcic_imdim = p->mapdim; + } + if (pcic->pcic_imap == NULL) { + /* + * We do not panic here for the sake of embedded systems. + */ + printk("PCIC: System %s is unknown, cannot route interrupts\n", + namebuf); + } + + return 0; +} + +static void __init pcic_pbm_scan_bus(struct linux_pcic *pcic) +{ + struct linux_pbm_info *pbm = &pcic->pbm; + + pbm->pci_bus = pci_scan_bus(pbm->pci_first_busno, &pcic_ops, pbm); +#if 0 /* deadwood transplanted from sparc64 */ + pci_fill_in_pbm_cookies(pbm->pci_bus, pbm, pbm->prom_node); + pci_record_assignments(pbm, pbm->pci_bus); + pci_assign_unassigned(pbm, pbm->pci_bus); + pci_fixup_irq(pbm, pbm->pci_bus); +#endif +} + +/* + * Main entry point from the PCI subsystem. + */ +static int __init pcic_init(void) +{ + struct linux_pcic *pcic; + + /* + * PCIC should be initialized at start of the timer. + * So, here we report the presence of PCIC and do some magic passes. + */ + if(!pcic0_up) + return 0; + pcic = &pcic0; + + /* + * Switch off IOTLB translation. + */ + writeb(PCI_DVMA_CONTROL_IOTLB_DISABLE, + pcic->pcic_regs+PCI_DVMA_CONTROL); + + /* + * Increase mapped size for PCI memory space (DMA access). + * Should be done in that order (size first, address second). + * Why we couldn't set up 4GB and forget about it? XXX + */ + writel(0xF0000000UL, pcic->pcic_regs+PCI_SIZE_0); + writel(0+PCI_BASE_ADDRESS_SPACE_MEMORY, + pcic->pcic_regs+PCI_BASE_ADDRESS_0); + + pcic_pbm_scan_bus(pcic); + + ebus_init(); + return 0; +} + +int pcic_present(void) +{ + return pcic0_up; +} + +static int __init pdev_to_pnode(struct linux_pbm_info *pbm, + struct pci_dev *pdev) +{ + struct linux_prom_pci_registers regs[PROMREG_MAX]; + int err; + int node = prom_getchild(pbm->prom_node); + + while(node) { + err = prom_getproperty(node, "reg", + (char *)®s[0], sizeof(regs)); + if(err != 0 && err != -1) { + unsigned long devfn = (regs[0].which_io >> 8) & 0xff; + if(devfn == pdev->devfn) + return node; + } + node = prom_getsibling(node); + } + return 0; +} + +static inline struct pcidev_cookie *pci_devcookie_alloc(void) +{ + return kmalloc(sizeof(struct pcidev_cookie), GFP_ATOMIC); +} + +static void pcic_map_pci_device(struct linux_pcic *pcic, + struct pci_dev *dev, int node) +{ + char namebuf[64]; + unsigned long address; + unsigned long flags; + int j; + + if (node == 0 || node == -1) { + strcpy(namebuf, "???"); + } else { + prom_getstring(node, "name", namebuf, 63); namebuf[63] = 0; + } + + for (j = 0; j < 6; j++) { + address = dev->resource[j].start; + if (address == 0) break; /* are sequential */ + flags = dev->resource[j].flags; + if ((flags & IORESOURCE_IO) != 0) { + if (address < 0x10000) { + /* + * A device responds to I/O cycles on PCI. + * We generate these cycles with memory + * access into the fixed map (phys 0x30000000). + * + * Since a device driver does not want to + * do ioremap() before accessing PC-style I/O, + * we supply virtual, ready to access address. + * + * Ebus devices do not come here even if + * CheerIO makes a similar conversion. + * See ebus.c for details. + * + * Note that check_region()/request_region() + * work for these devices. + * + * XXX Neat trick, but it's a *bad* idea + * to shit into regions like that. + * What if we want to allocate one more + * PCI base address... + */ + dev->resource[j].start = + pcic->pcic_io + address; + dev->resource[j].end = 1; /* XXX */ + dev->resource[j].flags = + (flags & ~IORESOURCE_IO) | IORESOURCE_MEM; + } else { + /* + * OOPS... PCI Spec allows this. Sun does + * not have any devices getting above 64K + * so it must be user with a weird I/O + * board in a PCI slot. We must remap it + * under 64K but it is not done yet. XXX + */ + printk("PCIC: Skipping I/O space at 0x%lx," + "this will Oops if a driver attaches;" + "device '%s' at %02x:%02x)\n", address, + namebuf, dev->bus->number, dev->devfn); + } + } + } +} + +static void +pcic_fill_irq(struct linux_pcic *pcic, struct pci_dev *dev, int node) +{ + struct pcic_ca2irq *p; + int i, ivec; + char namebuf[64]; + + if (node == 0 || node == -1) { + strcpy(namebuf, "???"); + } else { + prom_getstring(node, "name", namebuf, sizeof(namebuf)); + } + + if ((p = pcic->pcic_imap) == 0) { + dev->irq = 0; + return; + } + for (i = 0; i < pcic->pcic_imdim; i++) { + if (p->busno == dev->bus->number && p->devfn == dev->devfn) + break; + p++; + } + if (i >= pcic->pcic_imdim) { + printk("PCIC: device %s devfn %02x:%02x not found in %d\n", + namebuf, dev->bus->number, dev->devfn, pcic->pcic_imdim); + dev->irq = 0; + return; + } + + i = p->pin; + if (i >= 0 && i < 4) { + ivec = readw(pcic->pcic_regs+PCI_INT_SELECT_LO); + dev->irq = ivec >> (i << 2) & 0xF; + } else if (i >= 4 && i < 8) { + ivec = readw(pcic->pcic_regs+PCI_INT_SELECT_HI); + dev->irq = ivec >> ((i-4) << 2) & 0xF; + } else { /* Corrupted map */ + printk("PCIC: BAD PIN %d\n", i); for (;;) {} + } +/* P3 */ /* printk("PCIC: device %s pin %d ivec 0x%x irq %x\n", namebuf, i, ivec, dev->irq); */ + + /* + * dev->irq=0 means PROM did not bother to program the upper + * half of PCIC. This happens on JS-E with PROM 3.11, for instance. + */ + if (dev->irq == 0 || p->force) { + if (p->irq == 0 || p->irq >= 15) { /* Corrupted map */ + printk("PCIC: BAD IRQ %d\n", p->irq); for (;;) {} + } + printk("PCIC: setting irq %d at pin %d for device %02x:%02x\n", + p->irq, p->pin, dev->bus->number, dev->devfn); + dev->irq = p->irq; + + i = p->pin; + if (i >= 4) { + ivec = readw(pcic->pcic_regs+PCI_INT_SELECT_HI); + ivec &= ~(0xF << ((i - 4) << 2)); + ivec |= p->irq << ((i - 4) << 2); + writew(ivec, pcic->pcic_regs+PCI_INT_SELECT_HI); + } else { + ivec = readw(pcic->pcic_regs+PCI_INT_SELECT_LO); + ivec &= ~(0xF << (i << 2)); + ivec |= p->irq << (i << 2); + writew(ivec, pcic->pcic_regs+PCI_INT_SELECT_LO); + } + } + + return; +} + +/* + * Normally called from {do_}pci_scan_bus... + */ +void __init pcibios_fixup_bus(struct pci_bus *bus) +{ + struct pci_dev *dev; + int i, has_io, has_mem; + unsigned int cmd; + struct linux_pcic *pcic; + /* struct linux_pbm_info* pbm = &pcic->pbm; */ + int node; + struct pcidev_cookie *pcp; + + if (!pcic0_up) { + printk("pcibios_fixup_bus: no PCIC\n"); + return; + } + pcic = &pcic0; + + /* + * Next crud is an equivalent of pbm = pcic_bus_to_pbm(bus); + */ + if (bus->number != 0) { + printk("pcibios_fixup_bus: nonzero bus 0x%x\n", bus->number); + return; + } + + list_for_each_entry(dev, &bus->devices, bus_list) { + + /* + * Comment from i386 branch: + * There are buggy BIOSes that forget to enable I/O and memory + * access to PCI devices. We try to fix this, but we need to + * be sure that the BIOS didn't forget to assign an address + * to the device. [mj] + * OBP is a case of such BIOS :-) + */ + has_io = has_mem = 0; + for(i=0; i<6; i++) { + unsigned long f = dev->resource[i].flags; + if (f & IORESOURCE_IO) { + has_io = 1; + } else if (f & IORESOURCE_MEM) + has_mem = 1; + } + pcic_read_config(dev->bus, dev->devfn, PCI_COMMAND, 2, &cmd); + if (has_io && !(cmd & PCI_COMMAND_IO)) { + printk("PCIC: Enabling I/O for device %02x:%02x\n", + dev->bus->number, dev->devfn); + cmd |= PCI_COMMAND_IO; + pcic_write_config(dev->bus, dev->devfn, + PCI_COMMAND, 2, cmd); + } + if (has_mem && !(cmd & PCI_COMMAND_MEMORY)) { + printk("PCIC: Enabling memory for device %02x:%02x\n", + dev->bus->number, dev->devfn); + cmd |= PCI_COMMAND_MEMORY; + pcic_write_config(dev->bus, dev->devfn, + PCI_COMMAND, 2, cmd); + } + + node = pdev_to_pnode(&pcic->pbm, dev); + if(node == 0) + node = -1; + + /* cookies */ + pcp = pci_devcookie_alloc(); + pcp->pbm = &pcic->pbm; + pcp->prom_node = node; + dev->sysdata = pcp; + + /* fixing I/O to look like memory */ + if ((dev->class>>16) != PCI_BASE_CLASS_BRIDGE) + pcic_map_pci_device(pcic, dev, node); + + pcic_fill_irq(pcic, dev, node); + } +} + +/* + * pcic_pin_to_irq() is exported to ebus.c. + */ +unsigned int +pcic_pin_to_irq(unsigned int pin, char *name) +{ + struct linux_pcic *pcic = &pcic0; + unsigned int irq; + unsigned int ivec; + + if (pin < 4) { + ivec = readw(pcic->pcic_regs+PCI_INT_SELECT_LO); + irq = ivec >> (pin << 2) & 0xF; + } else if (pin < 8) { + ivec = readw(pcic->pcic_regs+PCI_INT_SELECT_HI); + irq = ivec >> ((pin-4) << 2) & 0xF; + } else { /* Corrupted map */ + printk("PCIC: BAD PIN %d FOR %s\n", pin, name); + for (;;) {} /* XXX Cannot panic properly in case of PROLL */ + } +/* P3 */ /* printk("PCIC: dev %s pin %d ivec 0x%x irq %x\n", name, pin, ivec, irq); */ + return irq; +} + +/* Makes compiler happy */ +static volatile int pcic_timer_dummy; + +static void pcic_clear_clock_irq(void) +{ + pcic_timer_dummy = readl(pcic0.pcic_regs+PCI_SYS_LIMIT); +} + +static irqreturn_t pcic_timer_handler (int irq, void *h, struct pt_regs *regs) +{ + write_seqlock(&xtime_lock); /* Dummy, to show that we remember */ + pcic_clear_clock_irq(); + do_timer(regs); +#ifndef CONFIG_SMP + update_process_times(user_mode(regs)); +#endif + write_sequnlock(&xtime_lock); + return IRQ_HANDLED; +} + +#define USECS_PER_JIFFY 10000 /* We have 100HZ "standard" timer for sparc */ +#define TICK_TIMER_LIMIT ((100*1000000/4)/100) + +void __init pci_time_init(void) +{ + struct linux_pcic *pcic = &pcic0; + unsigned long v; + int timer_irq, irq; + + /* A hack until do_gettimeofday prototype is moved to arch specific headers + and btfixupped. Patch do_gettimeofday with ba pci_do_gettimeofday; nop */ + ((unsigned int *)do_gettimeofday)[0] = + 0x10800000 | ((((unsigned long)pci_do_gettimeofday - + (unsigned long)do_gettimeofday) >> 2) & 0x003fffff); + ((unsigned int *)do_gettimeofday)[1] = 0x01000000; + BTFIXUPSET_CALL(bus_do_settimeofday, pci_do_settimeofday, BTFIXUPCALL_NORM); + btfixup(); + + writel (TICK_TIMER_LIMIT, pcic->pcic_regs+PCI_SYS_LIMIT); + /* PROM should set appropriate irq */ + v = readb(pcic->pcic_regs+PCI_COUNTER_IRQ); + timer_irq = PCI_COUNTER_IRQ_SYS(v); + writel (PCI_COUNTER_IRQ_SET(timer_irq, 0), + pcic->pcic_regs+PCI_COUNTER_IRQ); + irq = request_irq(timer_irq, pcic_timer_handler, + (SA_INTERRUPT | SA_STATIC_ALLOC), "timer", NULL); + if (irq) { + prom_printf("time_init: unable to attach IRQ%d\n", timer_irq); + prom_halt(); + } + local_irq_enable(); +} + +static __inline__ unsigned long do_gettimeoffset(void) +{ + /* + * We devide all to 100 + * to have microsecond resolution and to avoid overflow + */ + unsigned long count = + readl(pcic0.pcic_regs+PCI_SYS_COUNTER) & ~PCI_SYS_COUNTER_OVERFLOW; + count = ((count/100)*USECS_PER_JIFFY) / (TICK_TIMER_LIMIT/100); + return count; +} + +extern unsigned long wall_jiffies; + +static void pci_do_gettimeofday(struct timeval *tv) +{ + unsigned long flags; + unsigned long seq; + unsigned long usec, sec; + unsigned long max_ntp_tick = tick_usec - tickadj; + + do { + unsigned long lost; + + seq = read_seqbegin_irqsave(&xtime_lock, flags); + usec = do_gettimeoffset(); + lost = jiffies - wall_jiffies; + + /* + * If time_adjust is negative then NTP is slowing the clock + * so make sure not to go into next possible interval. + * Better to lose some accuracy than have time go backwards.. + */ + if (unlikely(time_adjust < 0)) { + usec = min(usec, max_ntp_tick); + + if (lost) + usec += lost * max_ntp_tick; + } + else if (unlikely(lost)) + usec += lost * tick_usec; + + sec = xtime.tv_sec; + usec += (xtime.tv_nsec / 1000); + } while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); + + while (usec >= 1000000) { + usec -= 1000000; + sec++; + } + + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +static int pci_do_settimeofday(struct timespec *tv) +{ + if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + /* + * This is revolting. We need to set "xtime" correctly. However, the + * value in this location is the value at the most recent update of + * wall time. Discover what correction gettimeofday() would have + * made, and then undo it! + */ + tv->tv_nsec -= 1000 * (do_gettimeoffset() + + (jiffies - wall_jiffies) * (USEC_PER_SEC / HZ)); + while (tv->tv_nsec < 0) { + tv->tv_nsec += NSEC_PER_SEC; + tv->tv_sec--; + } + + wall_to_monotonic.tv_sec += xtime.tv_sec - tv->tv_sec; + wall_to_monotonic.tv_nsec += xtime.tv_nsec - tv->tv_nsec; + + if (wall_to_monotonic.tv_nsec > NSEC_PER_SEC) { + wall_to_monotonic.tv_nsec -= NSEC_PER_SEC; + wall_to_monotonic.tv_sec++; + } + if (wall_to_monotonic.tv_nsec < 0) { + wall_to_monotonic.tv_nsec += NSEC_PER_SEC; + wall_to_monotonic.tv_sec--; + } + + xtime.tv_sec = tv->tv_sec; + xtime.tv_nsec = tv->tv_nsec; + time_adjust = 0; /* stop active adjtime() */ + time_status |= STA_UNSYNC; + time_maxerror = NTP_PHASE_LIMIT; + time_esterror = NTP_PHASE_LIMIT; + return 0; +} + +#if 0 +static void watchdog_reset() { + writeb(0, pcic->pcic_regs+PCI_SYS_STATUS); +} +#endif + +/* + * Other archs parse arguments here. + */ +char * __init pcibios_setup(char *str) +{ + return str; +} + +void pcibios_align_resource(void *data, struct resource *res, + unsigned long size, unsigned long align) +{ +} + +int pcibios_enable_device(struct pci_dev *pdev, int mask) +{ + return 0; +} + +/* + * NMI + */ +void pcic_nmi(unsigned int pend, struct pt_regs *regs) +{ + + pend = flip_dword(pend); + + if (!pcic_speculative || (pend & PCI_SYS_INT_PENDING_PIO) == 0) { + /* + * XXX On CP-1200 PCI #SERR may happen, we do not know + * what to do about it yet. + */ + printk("Aiee, NMI pend 0x%x pc 0x%x spec %d, hanging\n", + pend, (int)regs->pc, pcic_speculative); + for (;;) { } + } + pcic_speculative = 0; + pcic_trapped = 1; + regs->pc = regs->npc; + regs->npc += 4; +} + +static inline unsigned long get_irqmask(int irq_nr) +{ + return 1 << irq_nr; +} + +static inline char *pcic_irq_itoa(unsigned int irq) +{ + static char buff[16]; + sprintf(buff, "%d", irq); + return buff; +} + +static void pcic_disable_irq(unsigned int irq_nr) +{ + unsigned long mask, flags; + + mask = get_irqmask(irq_nr); + local_irq_save(flags); + writel(mask, pcic0.pcic_regs+PCI_SYS_INT_TARGET_MASK_SET); + local_irq_restore(flags); +} + +static void pcic_enable_irq(unsigned int irq_nr) +{ + unsigned long mask, flags; + + mask = get_irqmask(irq_nr); + local_irq_save(flags); + writel(mask, pcic0.pcic_regs+PCI_SYS_INT_TARGET_MASK_CLEAR); + local_irq_restore(flags); +} + +static void pcic_clear_profile_irq(int cpu) +{ + printk("PCIC: unimplemented code: FILE=%s LINE=%d", __FILE__, __LINE__); +} + +static void pcic_load_profile_irq(int cpu, unsigned int limit) +{ + printk("PCIC: unimplemented code: FILE=%s LINE=%d", __FILE__, __LINE__); +} + +/* We assume the caller has disabled local interrupts when these are called, + * or else very bizarre behavior will result. + */ +static void pcic_disable_pil_irq(unsigned int pil) +{ + writel(get_irqmask(pil), pcic0.pcic_regs+PCI_SYS_INT_TARGET_MASK_SET); +} + +static void pcic_enable_pil_irq(unsigned int pil) +{ + writel(get_irqmask(pil), pcic0.pcic_regs+PCI_SYS_INT_TARGET_MASK_CLEAR); +} + +void __init sun4m_pci_init_IRQ(void) +{ + BTFIXUPSET_CALL(enable_irq, pcic_enable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(disable_irq, pcic_disable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(enable_pil_irq, pcic_enable_pil_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(disable_pil_irq, pcic_disable_pil_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_clock_irq, pcic_clear_clock_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_profile_irq, pcic_clear_profile_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(load_profile_irq, pcic_load_profile_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__irq_itoa, pcic_irq_itoa, BTFIXUPCALL_NORM); +} + +int pcibios_assign_resource(struct pci_dev *pdev, int resource) +{ + return -ENXIO; +} + +/* + * This probably belongs here rather than ioport.c because + * we do not want this crud linked into SBus kernels. + * Also, think for a moment about likes of floppy.c that + * include architecture specific parts. They may want to redefine ins/outs. + * + * We do not use horroble macroses here because we want to + * advance pointer by sizeof(size). + */ +void outsb(unsigned long addr, const void *src, unsigned long count) +{ + while (count) { + count -= 1; + outb(*(const char *)src, addr); + src += 1; + /* addr += 1; */ + } +} + +void outsw(unsigned long addr, const void *src, unsigned long count) +{ + while (count) { + count -= 2; + outw(*(const short *)src, addr); + src += 2; + /* addr += 2; */ + } +} + +void outsl(unsigned long addr, const void *src, unsigned long count) +{ + while (count) { + count -= 4; + outl(*(const long *)src, addr); + src += 4; + /* addr += 4; */ + } +} + +void insb(unsigned long addr, void *dst, unsigned long count) +{ + while (count) { + count -= 1; + *(unsigned char *)dst = inb(addr); + dst += 1; + /* addr += 1; */ + } +} + +void insw(unsigned long addr, void *dst, unsigned long count) +{ + while (count) { + count -= 2; + *(unsigned short *)dst = inw(addr); + dst += 2; + /* addr += 2; */ + } +} + +void insl(unsigned long addr, void *dst, unsigned long count) +{ + while (count) { + count -= 4; + /* + * XXX I am sure we are in for an unaligned trap here. + */ + *(unsigned long *)dst = inl(addr); + dst += 4; + /* addr += 4; */ + } +} + +subsys_initcall(pcic_init); diff --git a/arch/sparc/kernel/pmc.c b/arch/sparc/kernel/pmc.c new file mode 100644 index 000000000000..7eca8871ff47 --- /dev/null +++ b/arch/sparc/kernel/pmc.c @@ -0,0 +1,99 @@ +/* pmc - Driver implementation for power management functions + * of Power Management Controller (PMC) on SPARCstation-Voyager. + * + * Copyright (c) 2002 Eric Brower (ebrower@usa.net) + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/pm.h> + +#include <asm/io.h> +#include <asm/sbus.h> +#include <asm/oplib.h> +#include <asm/uaccess.h> +#include <asm/auxio.h> + +/* Debug + * + * #define PMC_DEBUG_LED + * #define PMC_NO_IDLE + */ + +#define PMC_MINOR MISC_DYNAMIC_MINOR +#define PMC_OBPNAME "SUNW,pmc" +#define PMC_DEVNAME "pmc" + +#define PMC_IDLE_REG 0x00 +#define PMC_IDLE_ON 0x01 + +volatile static u8 __iomem *regs; +static int pmc_regsize; + +#define pmc_readb(offs) (sbus_readb(regs+offs)) +#define pmc_writeb(val, offs) (sbus_writeb(val, regs+offs)) + +/* + * CPU idle callback function + * See .../arch/sparc/kernel/process.c + */ +void pmc_swift_idle(void) +{ +#ifdef PMC_DEBUG_LED + set_auxio(0x00, AUXIO_LED); +#endif + + pmc_writeb(pmc_readb(PMC_IDLE_REG) | PMC_IDLE_ON, PMC_IDLE_REG); + +#ifdef PMC_DEBUG_LED + set_auxio(AUXIO_LED, 0x00); +#endif +} + +static inline void pmc_free(void) +{ + sbus_iounmap(regs, pmc_regsize); +} + +static int __init pmc_probe(void) +{ + struct sbus_bus *sbus = NULL; + struct sbus_dev *sdev = NULL; + for_each_sbus(sbus) { + for_each_sbusdev(sdev, sbus) { + if (!strcmp(sdev->prom_name, PMC_OBPNAME)) { + goto sbus_done; + } + } + } + +sbus_done: + if (!sdev) { + return -ENODEV; + } + + pmc_regsize = sdev->reg_addrs[0].reg_size; + regs = sbus_ioremap(&sdev->resource[0], 0, + pmc_regsize, PMC_OBPNAME); + if (!regs) { + printk(KERN_ERR "%s: unable to map registers\n", PMC_DEVNAME); + return -ENODEV; + } + +#ifndef PMC_NO_IDLE + /* Assign power management IDLE handler */ + pm_idle = pmc_swift_idle; +#endif + + printk(KERN_INFO "%s: power management initialized\n", PMC_DEVNAME); + return 0; +} + +/* This driver is not critical to the boot process + * and is easiest to ioremap when SBus is already + * initialized, so we install ourselves thusly: + */ +__initcall(pmc_probe); diff --git a/arch/sparc/kernel/process.c b/arch/sparc/kernel/process.c new file mode 100644 index 000000000000..143fe2f3c1c4 --- /dev/null +++ b/arch/sparc/kernel/process.c @@ -0,0 +1,746 @@ +/* $Id: process.c,v 1.161 2002/01/23 11:27:32 davem Exp $ + * linux/arch/sparc/kernel/process.c + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + */ + +/* + * This file handles the architecture-dependent parts of process handling.. + */ + +#include <stdarg.h> + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/kallsyms.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/config.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/reboot.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/init.h> + +#include <asm/auxio.h> +#include <asm/oplib.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/delay.h> +#include <asm/processor.h> +#include <asm/psr.h> +#include <asm/elf.h> +#include <asm/unistd.h> + +/* + * Power management idle function + * Set in pm platform drivers (apc.c and pmc.c) + */ +void (*pm_idle)(void); + +/* + * Power-off handler instantiation for pm.h compliance + * This is done via auxio, but could be used as a fallback + * handler when auxio is not present-- unused for now... + */ +void (*pm_power_off)(void); + +/* + * sysctl - toggle power-off restriction for serial console + * systems in machine_power_off() + */ +int scons_pwroff = 1; + +extern void fpsave(unsigned long *, unsigned long *, void *, unsigned long *); + +struct task_struct *last_task_used_math = NULL; +struct thread_info *current_set[NR_CPUS]; + +/* + * default_idle is new in 2.5. XXX Review, currently stolen from sparc64. + */ +void default_idle(void) +{ +} + +#ifndef CONFIG_SMP + +#define SUN4C_FAULT_HIGH 100 + +/* + * the idle loop on a Sparc... ;) + */ +void cpu_idle(void) +{ + if (current->pid != 0) + goto out; + + /* endless idle loop with no priority at all */ + for (;;) { + if (ARCH_SUN4C_SUN4) { + static int count = HZ; + static unsigned long last_jiffies; + static unsigned long last_faults; + static unsigned long fps; + unsigned long now; + unsigned long faults; + unsigned long flags; + + extern unsigned long sun4c_kernel_faults; + extern void sun4c_grow_kernel_ring(void); + + local_irq_save(flags); + now = jiffies; + count -= (now - last_jiffies); + last_jiffies = now; + if (count < 0) { + count += HZ; + faults = sun4c_kernel_faults; + fps = (fps + (faults - last_faults)) >> 1; + last_faults = faults; +#if 0 + printk("kernel faults / second = %ld\n", fps); +#endif + if (fps >= SUN4C_FAULT_HIGH) { + sun4c_grow_kernel_ring(); + } + } + local_irq_restore(flags); + } + + while((!need_resched()) && pm_idle) { + (*pm_idle)(); + } + + schedule(); + check_pgt_cache(); + } +out: + return; +} + +#else + +/* This is being executed in task 0 'user space'. */ +void cpu_idle(void) +{ + /* endless idle loop with no priority at all */ + while(1) { + if(need_resched()) { + schedule(); + check_pgt_cache(); + } + barrier(); /* or else gcc optimizes... */ + } +} + +#endif + +extern char reboot_command []; + +extern void (*prom_palette)(int); + +/* XXX cli/sti -> local_irq_xxx here, check this works once SMP is fixed. */ +void machine_halt(void) +{ + local_irq_enable(); + mdelay(8); + local_irq_disable(); + if (!serial_console && prom_palette) + prom_palette (1); + prom_halt(); + panic("Halt failed!"); +} + +EXPORT_SYMBOL(machine_halt); + +void machine_restart(char * cmd) +{ + char *p; + + local_irq_enable(); + mdelay(8); + local_irq_disable(); + + p = strchr (reboot_command, '\n'); + if (p) *p = 0; + if (!serial_console && prom_palette) + prom_palette (1); + if (cmd) + prom_reboot(cmd); + if (*reboot_command) + prom_reboot(reboot_command); + prom_feval ("reset"); + panic("Reboot failed!"); +} + +EXPORT_SYMBOL(machine_restart); + +void machine_power_off(void) +{ +#ifdef CONFIG_SUN_AUXIO + if (auxio_power_register && (!serial_console || scons_pwroff)) + *auxio_power_register |= AUXIO_POWER_OFF; +#endif + machine_halt(); +} + +EXPORT_SYMBOL(machine_power_off); + +static DEFINE_SPINLOCK(sparc_backtrace_lock); + +void __show_backtrace(unsigned long fp) +{ + struct reg_window *rw; + unsigned long flags; + int cpu = smp_processor_id(); + + spin_lock_irqsave(&sparc_backtrace_lock, flags); + + rw = (struct reg_window *)fp; + while(rw && (((unsigned long) rw) >= PAGE_OFFSET) && + !(((unsigned long) rw) & 0x7)) { + printk("CPU[%d]: ARGS[%08lx,%08lx,%08lx,%08lx,%08lx,%08lx] " + "FP[%08lx] CALLER[%08lx]: ", cpu, + rw->ins[0], rw->ins[1], rw->ins[2], rw->ins[3], + rw->ins[4], rw->ins[5], + rw->ins[6], + rw->ins[7]); + print_symbol("%s\n", rw->ins[7]); + rw = (struct reg_window *) rw->ins[6]; + } + spin_unlock_irqrestore(&sparc_backtrace_lock, flags); +} + +#define __SAVE __asm__ __volatile__("save %sp, -0x40, %sp\n\t") +#define __RESTORE __asm__ __volatile__("restore %g0, %g0, %g0\n\t") +#define __GET_FP(fp) __asm__ __volatile__("mov %%i6, %0" : "=r" (fp)) + +void show_backtrace(void) +{ + unsigned long fp; + + __SAVE; __SAVE; __SAVE; __SAVE; + __SAVE; __SAVE; __SAVE; __SAVE; + __RESTORE; __RESTORE; __RESTORE; __RESTORE; + __RESTORE; __RESTORE; __RESTORE; __RESTORE; + + __GET_FP(fp); + + __show_backtrace(fp); +} + +#ifdef CONFIG_SMP +void smp_show_backtrace_all_cpus(void) +{ + xc0((smpfunc_t) show_backtrace); + show_backtrace(); +} +#endif + +#if 0 +void show_stackframe(struct sparc_stackf *sf) +{ + unsigned long size; + unsigned long *stk; + int i; + + printk("l0: %08lx l1: %08lx l2: %08lx l3: %08lx " + "l4: %08lx l5: %08lx l6: %08lx l7: %08lx\n", + sf->locals[0], sf->locals[1], sf->locals[2], sf->locals[3], + sf->locals[4], sf->locals[5], sf->locals[6], sf->locals[7]); + printk("i0: %08lx i1: %08lx i2: %08lx i3: %08lx " + "i4: %08lx i5: %08lx fp: %08lx i7: %08lx\n", + sf->ins[0], sf->ins[1], sf->ins[2], sf->ins[3], + sf->ins[4], sf->ins[5], (unsigned long)sf->fp, sf->callers_pc); + printk("sp: %08lx x0: %08lx x1: %08lx x2: %08lx " + "x3: %08lx x4: %08lx x5: %08lx xx: %08lx\n", + (unsigned long)sf->structptr, sf->xargs[0], sf->xargs[1], + sf->xargs[2], sf->xargs[3], sf->xargs[4], sf->xargs[5], + sf->xxargs[0]); + size = ((unsigned long)sf->fp) - ((unsigned long)sf); + size -= STACKFRAME_SZ; + stk = (unsigned long *)((unsigned long)sf + STACKFRAME_SZ); + i = 0; + do { + printk("s%d: %08lx\n", i++, *stk++); + } while ((size -= sizeof(unsigned long))); +} +#endif + +void show_regs(struct pt_regs *r) +{ + struct reg_window *rw = (struct reg_window *) r->u_regs[14]; + + printk("PSR: %08lx PC: %08lx NPC: %08lx Y: %08lx %s\n", + r->psr, r->pc, r->npc, r->y, print_tainted()); + print_symbol("PC: <%s>\n", r->pc); + printk("%%G: %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx\n", + r->u_regs[0], r->u_regs[1], r->u_regs[2], r->u_regs[3], + r->u_regs[4], r->u_regs[5], r->u_regs[6], r->u_regs[7]); + printk("%%O: %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx\n", + r->u_regs[8], r->u_regs[9], r->u_regs[10], r->u_regs[11], + r->u_regs[12], r->u_regs[13], r->u_regs[14], r->u_regs[15]); + print_symbol("RPC: <%s>\n", r->u_regs[15]); + + printk("%%L: %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx\n", + rw->locals[0], rw->locals[1], rw->locals[2], rw->locals[3], + rw->locals[4], rw->locals[5], rw->locals[6], rw->locals[7]); + printk("%%I: %08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx\n", + rw->ins[0], rw->ins[1], rw->ins[2], rw->ins[3], + rw->ins[4], rw->ins[5], rw->ins[6], rw->ins[7]); +} + +/* + * The show_stack is an external API which we do not use ourselves. + * The oops is printed in die_if_kernel. + */ +void show_stack(struct task_struct *tsk, unsigned long *_ksp) +{ + unsigned long pc, fp; + unsigned long task_base; + struct reg_window *rw; + int count = 0; + + if (tsk != NULL) + task_base = (unsigned long) tsk->thread_info; + else + task_base = (unsigned long) current_thread_info(); + + fp = (unsigned long) _ksp; + do { + /* Bogus frame pointer? */ + if (fp < (task_base + sizeof(struct thread_info)) || + fp >= (task_base + (PAGE_SIZE << 1))) + break; + rw = (struct reg_window *) fp; + pc = rw->ins[7]; + printk("[%08lx : ", pc); + print_symbol("%s ] ", pc); + fp = rw->ins[6]; + } while (++count < 16); + printk("\n"); +} + +/* + * Note: sparc64 has a pretty intricated thread_saved_pc, check it out. + */ +unsigned long thread_saved_pc(struct task_struct *tsk) +{ + return tsk->thread_info->kpc; +} + +/* + * Free current thread data structures etc.. + */ +void exit_thread(void) +{ +#ifndef CONFIG_SMP + if(last_task_used_math == current) { +#else + if(current_thread_info()->flags & _TIF_USEDFPU) { +#endif + /* Keep process from leaving FPU in a bogon state. */ + put_psr(get_psr() | PSR_EF); + fpsave(¤t->thread.float_regs[0], ¤t->thread.fsr, + ¤t->thread.fpqueue[0], ¤t->thread.fpqdepth); +#ifndef CONFIG_SMP + last_task_used_math = NULL; +#else + current_thread_info()->flags &= ~_TIF_USEDFPU; +#endif + } +} + +void flush_thread(void) +{ + current_thread_info()->w_saved = 0; + + /* No new signal delivery by default */ + current->thread.new_signal = 0; +#ifndef CONFIG_SMP + if(last_task_used_math == current) { +#else + if(current_thread_info()->flags & _TIF_USEDFPU) { +#endif + /* Clean the fpu. */ + put_psr(get_psr() | PSR_EF); + fpsave(¤t->thread.float_regs[0], ¤t->thread.fsr, + ¤t->thread.fpqueue[0], ¤t->thread.fpqdepth); +#ifndef CONFIG_SMP + last_task_used_math = NULL; +#else + current_thread_info()->flags &= ~_TIF_USEDFPU; +#endif + } + + /* Now, this task is no longer a kernel thread. */ + current->thread.current_ds = USER_DS; + if (current->thread.flags & SPARC_FLAG_KTHREAD) { + current->thread.flags &= ~SPARC_FLAG_KTHREAD; + + /* We must fixup kregs as well. */ + /* XXX This was not fixed for ti for a while, worked. Unused? */ + current->thread.kregs = (struct pt_regs *) + ((char *)current->thread_info + (THREAD_SIZE - TRACEREG_SZ)); + } +} + +static __inline__ struct sparc_stackf __user * +clone_stackframe(struct sparc_stackf __user *dst, + struct sparc_stackf __user *src) +{ + unsigned long size, fp; + struct sparc_stackf *tmp; + struct sparc_stackf __user *sp; + + if (get_user(tmp, &src->fp)) + return NULL; + + fp = (unsigned long) tmp; + size = (fp - ((unsigned long) src)); + fp = (unsigned long) dst; + sp = (struct sparc_stackf __user *)(fp - size); + + /* do_fork() grabs the parent semaphore, we must release it + * temporarily so we can build the child clone stack frame + * without deadlocking. + */ + if (__copy_user(sp, src, size)) + sp = NULL; + else if (put_user(fp, &sp->fp)) + sp = NULL; + + return sp; +} + +asmlinkage int sparc_do_fork(unsigned long clone_flags, + unsigned long stack_start, + struct pt_regs *regs, + unsigned long stack_size) +{ + unsigned long parent_tid_ptr, child_tid_ptr; + + parent_tid_ptr = regs->u_regs[UREG_I2]; + child_tid_ptr = regs->u_regs[UREG_I4]; + + return do_fork(clone_flags, stack_start, + regs, stack_size, + (int __user *) parent_tid_ptr, + (int __user *) child_tid_ptr); +} + +/* Copy a Sparc thread. The fork() return value conventions + * under SunOS are nothing short of bletcherous: + * Parent --> %o0 == childs pid, %o1 == 0 + * Child --> %o0 == parents pid, %o1 == 1 + * + * NOTE: We have a separate fork kpsr/kwim because + * the parent could change these values between + * sys_fork invocation and when we reach here + * if the parent should sleep while trying to + * allocate the task_struct and kernel stack in + * do_fork(). + * XXX See comment above sys_vfork in sparc64. todo. + */ +extern void ret_from_fork(void); + +int copy_thread(int nr, unsigned long clone_flags, unsigned long sp, + unsigned long unused, + struct task_struct *p, struct pt_regs *regs) +{ + struct thread_info *ti = p->thread_info; + struct pt_regs *childregs; + char *new_stack; + +#ifndef CONFIG_SMP + if(last_task_used_math == current) { +#else + if(current_thread_info()->flags & _TIF_USEDFPU) { +#endif + put_psr(get_psr() | PSR_EF); + fpsave(&p->thread.float_regs[0], &p->thread.fsr, + &p->thread.fpqueue[0], &p->thread.fpqdepth); +#ifdef CONFIG_SMP + current_thread_info()->flags &= ~_TIF_USEDFPU; +#endif + } + + /* + * p->thread_info new_stack childregs + * ! ! ! {if(PSR_PS) } + * V V (stk.fr.) V (pt_regs) { (stk.fr.) } + * +----- - - - - - ------+===========+============={+==========}+ + */ + new_stack = (char*)ti + THREAD_SIZE; + if (regs->psr & PSR_PS) + new_stack -= STACKFRAME_SZ; + new_stack -= STACKFRAME_SZ + TRACEREG_SZ; + memcpy(new_stack, (char *)regs - STACKFRAME_SZ, STACKFRAME_SZ + TRACEREG_SZ); + childregs = (struct pt_regs *) (new_stack + STACKFRAME_SZ); + + /* + * A new process must start with interrupts closed in 2.5, + * because this is how Mingo's scheduler works (see schedule_tail + * and finish_arch_switch). If we do not do it, a timer interrupt hits + * before we unlock, attempts to re-take the rq->lock, and then we die. + * Thus, kpsr|=PSR_PIL. + */ + ti->ksp = (unsigned long) new_stack; + ti->kpc = (((unsigned long) ret_from_fork) - 0x8); + ti->kpsr = current->thread.fork_kpsr | PSR_PIL; + ti->kwim = current->thread.fork_kwim; + + if(regs->psr & PSR_PS) { + extern struct pt_regs fake_swapper_regs; + + p->thread.kregs = &fake_swapper_regs; + new_stack += STACKFRAME_SZ + TRACEREG_SZ; + childregs->u_regs[UREG_FP] = (unsigned long) new_stack; + p->thread.flags |= SPARC_FLAG_KTHREAD; + p->thread.current_ds = KERNEL_DS; + memcpy(new_stack, (void *)regs->u_regs[UREG_FP], STACKFRAME_SZ); + childregs->u_regs[UREG_G6] = (unsigned long) ti; + } else { + p->thread.kregs = childregs; + childregs->u_regs[UREG_FP] = sp; + p->thread.flags &= ~SPARC_FLAG_KTHREAD; + p->thread.current_ds = USER_DS; + + if (sp != regs->u_regs[UREG_FP]) { + struct sparc_stackf __user *childstack; + struct sparc_stackf __user *parentstack; + + /* + * This is a clone() call with supplied user stack. + * Set some valid stack frames to give to the child. + */ + childstack = (struct sparc_stackf __user *) + (sp & ~0x7UL); + parentstack = (struct sparc_stackf __user *) + regs->u_regs[UREG_FP]; + +#if 0 + printk("clone: parent stack:\n"); + show_stackframe(parentstack); +#endif + + childstack = clone_stackframe(childstack, parentstack); + if (!childstack) + return -EFAULT; + +#if 0 + printk("clone: child stack:\n"); + show_stackframe(childstack); +#endif + + childregs->u_regs[UREG_FP] = (unsigned long)childstack; + } + } + +#ifdef CONFIG_SMP + /* FPU must be disabled on SMP. */ + childregs->psr &= ~PSR_EF; +#endif + + /* Set the return value for the child. */ + childregs->u_regs[UREG_I0] = current->pid; + childregs->u_regs[UREG_I1] = 1; + + /* Set the return value for the parent. */ + regs->u_regs[UREG_I1] = 0; + + if (clone_flags & CLONE_SETTLS) + childregs->u_regs[UREG_G7] = regs->u_regs[UREG_I3]; + + return 0; +} + +/* + * fill in the user structure for a core dump.. + */ +void dump_thread(struct pt_regs * regs, struct user * dump) +{ + unsigned long first_stack_page; + + dump->magic = SUNOS_CORE_MAGIC; + dump->len = sizeof(struct user); + dump->regs.psr = regs->psr; + dump->regs.pc = regs->pc; + dump->regs.npc = regs->npc; + dump->regs.y = regs->y; + /* fuck me plenty */ + memcpy(&dump->regs.regs[0], ®s->u_regs[1], (sizeof(unsigned long) * 15)); + dump->uexec = current->thread.core_exec; + dump->u_tsize = (((unsigned long) current->mm->end_code) - + ((unsigned long) current->mm->start_code)) & ~(PAGE_SIZE - 1); + dump->u_dsize = ((unsigned long) (current->mm->brk + (PAGE_SIZE-1))); + dump->u_dsize -= dump->u_tsize; + dump->u_dsize &= ~(PAGE_SIZE - 1); + first_stack_page = (regs->u_regs[UREG_FP] & ~(PAGE_SIZE - 1)); + dump->u_ssize = (TASK_SIZE - first_stack_page) & ~(PAGE_SIZE - 1); + memcpy(&dump->fpu.fpstatus.fregs.regs[0], ¤t->thread.float_regs[0], (sizeof(unsigned long) * 32)); + dump->fpu.fpstatus.fsr = current->thread.fsr; + dump->fpu.fpstatus.flags = dump->fpu.fpstatus.extra = 0; + dump->fpu.fpstatus.fpq_count = current->thread.fpqdepth; + memcpy(&dump->fpu.fpstatus.fpq[0], ¤t->thread.fpqueue[0], + ((sizeof(unsigned long) * 2) * 16)); + dump->sigcode = 0; +} + +/* + * fill in the fpu structure for a core dump. + */ +int dump_fpu (struct pt_regs * regs, elf_fpregset_t * fpregs) +{ + if (used_math()) { + memset(fpregs, 0, sizeof(*fpregs)); + fpregs->pr_q_entrysize = 8; + return 1; + } +#ifdef CONFIG_SMP + if (current_thread_info()->flags & _TIF_USEDFPU) { + put_psr(get_psr() | PSR_EF); + fpsave(¤t->thread.float_regs[0], ¤t->thread.fsr, + ¤t->thread.fpqueue[0], ¤t->thread.fpqdepth); + if (regs != NULL) { + regs->psr &= ~(PSR_EF); + current_thread_info()->flags &= ~(_TIF_USEDFPU); + } + } +#else + if (current == last_task_used_math) { + put_psr(get_psr() | PSR_EF); + fpsave(¤t->thread.float_regs[0], ¤t->thread.fsr, + ¤t->thread.fpqueue[0], ¤t->thread.fpqdepth); + if (regs != NULL) { + regs->psr &= ~(PSR_EF); + last_task_used_math = NULL; + } + } +#endif + memcpy(&fpregs->pr_fr.pr_regs[0], + ¤t->thread.float_regs[0], + (sizeof(unsigned long) * 32)); + fpregs->pr_fsr = current->thread.fsr; + fpregs->pr_qcnt = current->thread.fpqdepth; + fpregs->pr_q_entrysize = 8; + fpregs->pr_en = 1; + if(fpregs->pr_qcnt != 0) { + memcpy(&fpregs->pr_q[0], + ¤t->thread.fpqueue[0], + sizeof(struct fpq) * fpregs->pr_qcnt); + } + /* Zero out the rest. */ + memset(&fpregs->pr_q[fpregs->pr_qcnt], 0, + sizeof(struct fpq) * (32 - fpregs->pr_qcnt)); + return 1; +} + +/* + * sparc_execve() executes a new program after the asm stub has set + * things up for us. This should basically do what I want it to. + */ +asmlinkage int sparc_execve(struct pt_regs *regs) +{ + int error, base = 0; + char *filename; + + /* Check for indirect call. */ + if(regs->u_regs[UREG_G1] == 0) + base = 1; + + filename = getname((char __user *)regs->u_regs[base + UREG_I0]); + error = PTR_ERR(filename); + if(IS_ERR(filename)) + goto out; + error = do_execve(filename, + (char __user * __user *)regs->u_regs[base + UREG_I1], + (char __user * __user *)regs->u_regs[base + UREG_I2], + regs); + putname(filename); + if (error == 0) { + task_lock(current); + current->ptrace &= ~PT_DTRACE; + task_unlock(current); + } +out: + return error; +} + +/* + * This is the mechanism for creating a new kernel thread. + * + * NOTE! Only a kernel-only process(ie the swapper or direct descendants + * who haven't done an "execve()") should use this: it will work within + * a system call from a "real" process, but the process memory space will + * not be free'd until both the parent and the child have exited. + */ +pid_t kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) +{ + long retval; + + __asm__ __volatile__("mov %4, %%g2\n\t" /* Set aside fn ptr... */ + "mov %5, %%g3\n\t" /* and arg. */ + "mov %1, %%g1\n\t" + "mov %2, %%o0\n\t" /* Clone flags. */ + "mov 0, %%o1\n\t" /* usp arg == 0 */ + "t 0x10\n\t" /* Linux/Sparc clone(). */ + "cmp %%o1, 0\n\t" + "be 1f\n\t" /* The parent, just return. */ + " nop\n\t" /* Delay slot. */ + "jmpl %%g2, %%o7\n\t" /* Call the function. */ + " mov %%g3, %%o0\n\t" /* Get back the arg in delay. */ + "mov %3, %%g1\n\t" + "t 0x10\n\t" /* Linux/Sparc exit(). */ + /* Notreached by child. */ + "1: mov %%o0, %0\n\t" : + "=r" (retval) : + "i" (__NR_clone), "r" (flags | CLONE_VM | CLONE_UNTRACED), + "i" (__NR_exit), "r" (fn), "r" (arg) : + "g1", "g2", "g3", "o0", "o1", "memory", "cc"); + return retval; +} + +unsigned long get_wchan(struct task_struct *task) +{ + unsigned long pc, fp, bias = 0; + unsigned long task_base = (unsigned long) task; + unsigned long ret = 0; + struct reg_window *rw; + int count = 0; + + if (!task || task == current || + task->state == TASK_RUNNING) + goto out; + + fp = task->thread_info->ksp + bias; + do { + /* Bogus frame pointer? */ + if (fp < (task_base + sizeof(struct thread_info)) || + fp >= (task_base + (2 * PAGE_SIZE))) + break; + rw = (struct reg_window *) fp; + pc = rw->ins[7]; + if (!in_sched_functions(pc)) { + ret = pc; + goto out; + } + fp = rw->ins[6] + bias; + } while (++count < 16); + +out: + return ret; +} + diff --git a/arch/sparc/kernel/ptrace.c b/arch/sparc/kernel/ptrace.c new file mode 100644 index 000000000000..fc4ad69357b8 --- /dev/null +++ b/arch/sparc/kernel/ptrace.c @@ -0,0 +1,632 @@ +/* ptrace.c: Sparc process tracing support. + * + * Copyright (C) 1996 David S. Miller (davem@caipfs.rutgers.edu) + * + * Based upon code written by Ross Biro, Linus Torvalds, Bob Manson, + * and David Mosberger. + * + * Added Linux support -miguel (weird, eh?, the orignal code was meant + * to emulate SunOS). + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/user.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/security.h> + +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +#define MAGIC_CONSTANT 0x80000000 + + +/* Returning from ptrace is a bit tricky because the syscall return + * low level code assumes any value returned which is negative and + * is a valid errno will mean setting the condition codes to indicate + * an error return. This doesn't work, so we have this hook. + */ +static inline void pt_error_return(struct pt_regs *regs, unsigned long error) +{ + regs->u_regs[UREG_I0] = error; + regs->psr |= PSR_C; + regs->pc = regs->npc; + regs->npc += 4; +} + +static inline void pt_succ_return(struct pt_regs *regs, unsigned long value) +{ + regs->u_regs[UREG_I0] = value; + regs->psr &= ~PSR_C; + regs->pc = regs->npc; + regs->npc += 4; +} + +static void +pt_succ_return_linux(struct pt_regs *regs, unsigned long value, long __user *addr) +{ + if (put_user(value, addr)) { + pt_error_return(regs, EFAULT); + return; + } + regs->u_regs[UREG_I0] = 0; + regs->psr &= ~PSR_C; + regs->pc = regs->npc; + regs->npc += 4; +} + +static void +pt_os_succ_return (struct pt_regs *regs, unsigned long val, long __user *addr) +{ + if (current->personality == PER_SUNOS) + pt_succ_return (regs, val); + else + pt_succ_return_linux (regs, val, addr); +} + +/* Fuck me gently with a chainsaw... */ +static inline void read_sunos_user(struct pt_regs *regs, unsigned long offset, + struct task_struct *tsk, long __user *addr) +{ + struct pt_regs *cregs = tsk->thread.kregs; + struct thread_info *t = tsk->thread_info; + int v; + + if(offset >= 1024) + offset -= 1024; /* whee... */ + if(offset & ((sizeof(unsigned long) - 1))) { + pt_error_return(regs, EIO); + return; + } + if(offset >= 16 && offset < 784) { + offset -= 16; offset >>= 2; + pt_os_succ_return(regs, *(((unsigned long *)(&t->reg_window[0]))+offset), addr); + return; + } + if(offset >= 784 && offset < 832) { + offset -= 784; offset >>= 2; + pt_os_succ_return(regs, *(((unsigned long *)(&t->rwbuf_stkptrs[0]))+offset), addr); + return; + } + switch(offset) { + case 0: + v = t->ksp; + break; + case 4: + v = t->kpc; + break; + case 8: + v = t->kpsr; + break; + case 12: + v = t->uwinmask; + break; + case 832: + v = t->w_saved; + break; + case 896: + v = cregs->u_regs[UREG_I0]; + break; + case 900: + v = cregs->u_regs[UREG_I1]; + break; + case 904: + v = cregs->u_regs[UREG_I2]; + break; + case 908: + v = cregs->u_regs[UREG_I3]; + break; + case 912: + v = cregs->u_regs[UREG_I4]; + break; + case 916: + v = cregs->u_regs[UREG_I5]; + break; + case 920: + v = cregs->u_regs[UREG_I6]; + break; + case 924: + if(tsk->thread.flags & MAGIC_CONSTANT) + v = cregs->u_regs[UREG_G1]; + else + v = 0; + break; + case 940: + v = cregs->u_regs[UREG_I0]; + break; + case 944: + v = cregs->u_regs[UREG_I1]; + break; + + case 948: + /* Isn't binary compatibility _fun_??? */ + if(cregs->psr & PSR_C) + v = cregs->u_regs[UREG_I0] << 24; + else + v = 0; + break; + + /* Rest of them are completely unsupported. */ + default: + printk("%s [%d]: Wants to read user offset %ld\n", + current->comm, current->pid, offset); + pt_error_return(regs, EIO); + return; + } + if (current->personality == PER_SUNOS) + pt_succ_return (regs, v); + else + pt_succ_return_linux (regs, v, addr); + return; +} + +static inline void write_sunos_user(struct pt_regs *regs, unsigned long offset, + struct task_struct *tsk) +{ + struct pt_regs *cregs = tsk->thread.kregs; + struct thread_info *t = tsk->thread_info; + unsigned long value = regs->u_regs[UREG_I3]; + + if(offset >= 1024) + offset -= 1024; /* whee... */ + if(offset & ((sizeof(unsigned long) - 1))) + goto failure; + if(offset >= 16 && offset < 784) { + offset -= 16; offset >>= 2; + *(((unsigned long *)(&t->reg_window[0]))+offset) = value; + goto success; + } + if(offset >= 784 && offset < 832) { + offset -= 784; offset >>= 2; + *(((unsigned long *)(&t->rwbuf_stkptrs[0]))+offset) = value; + goto success; + } + switch(offset) { + case 896: + cregs->u_regs[UREG_I0] = value; + break; + case 900: + cregs->u_regs[UREG_I1] = value; + break; + case 904: + cregs->u_regs[UREG_I2] = value; + break; + case 908: + cregs->u_regs[UREG_I3] = value; + break; + case 912: + cregs->u_regs[UREG_I4] = value; + break; + case 916: + cregs->u_regs[UREG_I5] = value; + break; + case 920: + cregs->u_regs[UREG_I6] = value; + break; + case 924: + cregs->u_regs[UREG_I7] = value; + break; + case 940: + cregs->u_regs[UREG_I0] = value; + break; + case 944: + cregs->u_regs[UREG_I1] = value; + break; + + /* Rest of them are completely unsupported or "no-touch". */ + default: + printk("%s [%d]: Wants to write user offset %ld\n", + current->comm, current->pid, offset); + goto failure; + } +success: + pt_succ_return(regs, 0); + return; +failure: + pt_error_return(regs, EIO); + return; +} + +/* #define ALLOW_INIT_TRACING */ +/* #define DEBUG_PTRACE */ + +#ifdef DEBUG_PTRACE +char *pt_rq [] = { + /* 0 */ "TRACEME", "PEEKTEXT", "PEEKDATA", "PEEKUSR", + /* 4 */ "POKETEXT", "POKEDATA", "POKEUSR", "CONT", + /* 8 */ "KILL", "SINGLESTEP", "SUNATTACH", "SUNDETACH", + /* 12 */ "GETREGS", "SETREGS", "GETFPREGS", "SETFPREGS", + /* 16 */ "READDATA", "WRITEDATA", "READTEXT", "WRITETEXT", + /* 20 */ "GETFPAREGS", "SETFPAREGS", "unknown", "unknown", + /* 24 */ "SYSCALL", "" +}; +#endif + +/* + * Called by kernel/ptrace.c when detaching.. + * + * Make sure single step bits etc are not set. + */ +void ptrace_disable(struct task_struct *child) +{ + /* nothing to do */ +} + +asmlinkage void do_ptrace(struct pt_regs *regs) +{ + unsigned long request = regs->u_regs[UREG_I0]; + unsigned long pid = regs->u_regs[UREG_I1]; + unsigned long addr = regs->u_regs[UREG_I2]; + unsigned long data = regs->u_regs[UREG_I3]; + unsigned long addr2 = regs->u_regs[UREG_I4]; + struct task_struct *child; + int ret; + + lock_kernel(); +#ifdef DEBUG_PTRACE + { + char *s; + + if ((request >= 0) && (request <= 24)) + s = pt_rq [request]; + else + s = "unknown"; + + if (request == PTRACE_POKEDATA && data == 0x91d02001){ + printk ("do_ptrace: breakpoint pid=%d, addr=%08lx addr2=%08lx\n", + pid, addr, addr2); + } else + printk("do_ptrace: rq=%s(%d) pid=%d addr=%08lx data=%08lx addr2=%08lx\n", + s, (int) request, (int) pid, addr, data, addr2); + } +#endif + if (request == PTRACE_TRACEME) { + int my_ret; + + /* are we already being traced? */ + if (current->ptrace & PT_PTRACED) { + pt_error_return(regs, EPERM); + goto out; + } + my_ret = security_ptrace(current->parent, current); + if (my_ret) { + pt_error_return(regs, -my_ret); + goto out; + } + + /* set the ptrace bit in the process flags. */ + current->ptrace |= PT_PTRACED; + pt_succ_return(regs, 0); + goto out; + } +#ifndef ALLOW_INIT_TRACING + if (pid == 1) { + /* Can't dork with init. */ + pt_error_return(regs, EPERM); + goto out; + } +#endif + read_lock(&tasklist_lock); + child = find_task_by_pid(pid); + if (child) + get_task_struct(child); + read_unlock(&tasklist_lock); + + if (!child) { + pt_error_return(regs, ESRCH); + goto out; + } + + if ((current->personality == PER_SUNOS && request == PTRACE_SUNATTACH) + || (current->personality != PER_SUNOS && request == PTRACE_ATTACH)) { + if (ptrace_attach(child)) { + pt_error_return(regs, EPERM); + goto out_tsk; + } + pt_succ_return(regs, 0); + goto out_tsk; + } + + ret = ptrace_check_attach(child, request == PTRACE_KILL); + if (ret < 0) { + pt_error_return(regs, -ret); + goto out_tsk; + } + + switch(request) { + case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKDATA: { + unsigned long tmp; + + if (access_process_vm(child, addr, + &tmp, sizeof(tmp), 0) == sizeof(tmp)) + pt_os_succ_return(regs, tmp, (long __user *)data); + else + pt_error_return(regs, EIO); + goto out_tsk; + } + + case PTRACE_PEEKUSR: + read_sunos_user(regs, addr, child, (long __user *) data); + goto out_tsk; + + case PTRACE_POKEUSR: + write_sunos_user(regs, addr, child); + goto out_tsk; + + case PTRACE_POKETEXT: /* write the word at location addr. */ + case PTRACE_POKEDATA: { + if (access_process_vm(child, addr, + &data, sizeof(data), 1) == sizeof(data)) + pt_succ_return(regs, 0); + else + pt_error_return(regs, EIO); + goto out_tsk; + } + + case PTRACE_GETREGS: { + struct pt_regs __user *pregs = (struct pt_regs __user *) addr; + struct pt_regs *cregs = child->thread.kregs; + int rval; + + if (!access_ok(VERIFY_WRITE, pregs, sizeof(struct pt_regs))) { + rval = -EFAULT; + pt_error_return(regs, -rval); + goto out_tsk; + } + __put_user(cregs->psr, (&pregs->psr)); + __put_user(cregs->pc, (&pregs->pc)); + __put_user(cregs->npc, (&pregs->npc)); + __put_user(cregs->y, (&pregs->y)); + for(rval = 1; rval < 16; rval++) + __put_user(cregs->u_regs[rval], (&pregs->u_regs[rval - 1])); + pt_succ_return(regs, 0); +#ifdef DEBUG_PTRACE + printk ("PC=%x nPC=%x o7=%x\n", cregs->pc, cregs->npc, cregs->u_regs [15]); +#endif + goto out_tsk; + } + + case PTRACE_SETREGS: { + struct pt_regs __user *pregs = (struct pt_regs __user *) addr; + struct pt_regs *cregs = child->thread.kregs; + unsigned long psr, pc, npc, y; + int i; + + /* Must be careful, tracing process can only set certain + * bits in the psr. + */ + if (!access_ok(VERIFY_READ, pregs, sizeof(struct pt_regs))) { + pt_error_return(regs, EFAULT); + goto out_tsk; + } + __get_user(psr, (&pregs->psr)); + __get_user(pc, (&pregs->pc)); + __get_user(npc, (&pregs->npc)); + __get_user(y, (&pregs->y)); + psr &= PSR_ICC; + cregs->psr &= ~PSR_ICC; + cregs->psr |= psr; + if (!((pc | npc) & 3)) { + cregs->pc = pc; + cregs->npc =npc; + } + cregs->y = y; + for(i = 1; i < 16; i++) + __get_user(cregs->u_regs[i], (&pregs->u_regs[i-1])); + pt_succ_return(regs, 0); + goto out_tsk; + } + + case PTRACE_GETFPREGS: { + struct fps { + unsigned long regs[32]; + unsigned long fsr; + unsigned long flags; + unsigned long extra; + unsigned long fpqd; + struct fq { + unsigned long *insnaddr; + unsigned long insn; + } fpq[16]; + }; + struct fps __user *fps = (struct fps __user *) addr; + int i; + + if (!access_ok(VERIFY_WRITE, fps, sizeof(struct fps))) { + i = -EFAULT; + pt_error_return(regs, -i); + goto out_tsk; + } + for(i = 0; i < 32; i++) + __put_user(child->thread.float_regs[i], (&fps->regs[i])); + __put_user(child->thread.fsr, (&fps->fsr)); + __put_user(child->thread.fpqdepth, (&fps->fpqd)); + __put_user(0, (&fps->flags)); + __put_user(0, (&fps->extra)); + for(i = 0; i < 16; i++) { + __put_user(child->thread.fpqueue[i].insn_addr, + (&fps->fpq[i].insnaddr)); + __put_user(child->thread.fpqueue[i].insn, (&fps->fpq[i].insn)); + } + pt_succ_return(regs, 0); + goto out_tsk; + } + + case PTRACE_SETFPREGS: { + struct fps { + unsigned long regs[32]; + unsigned long fsr; + unsigned long flags; + unsigned long extra; + unsigned long fpqd; + struct fq { + unsigned long *insnaddr; + unsigned long insn; + } fpq[16]; + }; + struct fps __user *fps = (struct fps __user *) addr; + int i; + + if (!access_ok(VERIFY_READ, fps, sizeof(struct fps))) { + i = -EFAULT; + pt_error_return(regs, -i); + goto out_tsk; + } + copy_from_user(&child->thread.float_regs[0], &fps->regs[0], (32 * sizeof(unsigned long))); + __get_user(child->thread.fsr, (&fps->fsr)); + __get_user(child->thread.fpqdepth, (&fps->fpqd)); + for(i = 0; i < 16; i++) { + __get_user(child->thread.fpqueue[i].insn_addr, + (&fps->fpq[i].insnaddr)); + __get_user(child->thread.fpqueue[i].insn, (&fps->fpq[i].insn)); + } + pt_succ_return(regs, 0); + goto out_tsk; + } + + case PTRACE_READTEXT: + case PTRACE_READDATA: { + int res = ptrace_readdata(child, addr, + (void __user *) addr2, data); + + if (res == data) { + pt_succ_return(regs, 0); + goto out_tsk; + } + /* Partial read is an IO failure */ + if (res >= 0) + res = -EIO; + pt_error_return(regs, -res); + goto out_tsk; + } + + case PTRACE_WRITETEXT: + case PTRACE_WRITEDATA: { + int res = ptrace_writedata(child, (void __user *) addr2, + addr, data); + + if (res == data) { + pt_succ_return(regs, 0); + goto out_tsk; + } + /* Partial write is an IO failure */ + if (res >= 0) + res = -EIO; + pt_error_return(regs, -res); + goto out_tsk; + } + + case PTRACE_SYSCALL: /* continue and stop at (return from) syscall */ + addr = 1; + + case PTRACE_CONT: { /* restart after signal. */ + if (data > _NSIG) { + pt_error_return(regs, EIO); + goto out_tsk; + } + if (addr != 1) { + if (addr & 3) { + pt_error_return(regs, EINVAL); + goto out_tsk; + } +#ifdef DEBUG_PTRACE + printk ("Original: %08lx %08lx\n", child->thread.kregs->pc, child->thread.kregs->npc); + printk ("Continuing with %08lx %08lx\n", addr, addr+4); +#endif + child->thread.kregs->pc = addr; + child->thread.kregs->npc = addr + 4; + } + + if (request == PTRACE_SYSCALL) + set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + else + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + + child->exit_code = data; +#ifdef DEBUG_PTRACE + printk("CONT: %s [%d]: set exit_code = %x %lx %lx\n", + child->comm, child->pid, child->exit_code, + child->thread.kregs->pc, + child->thread.kregs->npc); +#endif + wake_up_process(child); + pt_succ_return(regs, 0); + goto out_tsk; + } + +/* + * make the child exit. Best I can do is send it a sigkill. + * perhaps it should be put in the status that it wants to + * exit. + */ + case PTRACE_KILL: { + if (child->exit_state == EXIT_ZOMBIE) { /* already dead */ + pt_succ_return(regs, 0); + goto out_tsk; + } + wake_up_process(child); + child->exit_code = SIGKILL; + pt_succ_return(regs, 0); + goto out_tsk; + } + + case PTRACE_SUNDETACH: { /* detach a process that was attached. */ + int err = ptrace_detach(child, data); + if (err) { + pt_error_return(regs, EIO); + goto out_tsk; + } + pt_succ_return(regs, 0); + goto out_tsk; + } + + /* PTRACE_DUMPCORE unsupported... */ + + default: { + int err = ptrace_request(child, request, addr, data); + if (err) + pt_error_return(regs, -err); + else + pt_succ_return(regs, 0); + goto out_tsk; + } + } +out_tsk: + if (child) + put_task_struct(child); +out: + unlock_kernel(); +} + +asmlinkage void syscall_trace(void) +{ +#ifdef DEBUG_PTRACE + printk("%s [%d]: syscall_trace\n", current->comm, current->pid); +#endif + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + if (!(current->ptrace & PT_PTRACED)) + return; + current->thread.flags ^= MAGIC_CONSTANT; + ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) + ? 0x80 : 0)); + /* + * this isn't the same as continuing with a signal, but it will do + * for normal use. strace only continues with a signal if the + * stopping signal is not SIGTRAP. -brl + */ +#ifdef DEBUG_PTRACE + printk("%s [%d]: syscall_trace exit= %x\n", current->comm, + current->pid, current->exit_code); +#endif + if (current->exit_code) { + send_sig (current->exit_code, current, 1); + current->exit_code = 0; + } +} diff --git a/arch/sparc/kernel/rtrap.S b/arch/sparc/kernel/rtrap.S new file mode 100644 index 000000000000..f7460d897e79 --- /dev/null +++ b/arch/sparc/kernel/rtrap.S @@ -0,0 +1,319 @@ +/* $Id: rtrap.S,v 1.58 2002/01/31 03:30:05 davem Exp $ + * rtrap.S: Return from Sparc trap low-level code. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/page.h> +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/asi.h> +#include <asm/smp.h> +#include <asm/contregs.h> +#include <asm/winmacro.h> +#include <asm/asmmacro.h> +#include <asm/thread_info.h> + +#define t_psr l0 +#define t_pc l1 +#define t_npc l2 +#define t_wim l3 +#define twin_tmp1 l4 +#define glob_tmp g4 +#define curptr g6 + + /* 7 WINDOW SPARC PATCH INSTRUCTIONS */ + .globl rtrap_7win_patch1, rtrap_7win_patch2, rtrap_7win_patch3 + .globl rtrap_7win_patch4, rtrap_7win_patch5 +rtrap_7win_patch1: srl %t_wim, 0x6, %glob_tmp +rtrap_7win_patch2: and %glob_tmp, 0x7f, %glob_tmp +rtrap_7win_patch3: srl %g1, 7, %g2 +rtrap_7win_patch4: srl %g2, 6, %g2 +rtrap_7win_patch5: and %g1, 0x7f, %g1 + /* END OF PATCH INSTRUCTIONS */ + + /* We need to check for a few things which are: + * 1) The need to call schedule() because this + * processes quantum is up. + * 2) Pending signals for this process, if any + * exist we need to call do_signal() to do + * the needy. + * + * Else we just check if the rett would land us + * in an invalid window, if so we need to grab + * it off the user/kernel stack first. + */ + + .globl ret_trap_entry, rtrap_patch1, rtrap_patch2 + .globl rtrap_patch3, rtrap_patch4, rtrap_patch5 + .globl ret_trap_lockless_ipi +ret_trap_entry: +ret_trap_lockless_ipi: + andcc %t_psr, PSR_PS, %g0 + be 1f + nop + + wr %t_psr, 0x0, %psr + b ret_trap_kernel + nop + +1: + ld [%curptr + TI_FLAGS], %g2 + andcc %g2, (_TIF_NEED_RESCHED), %g0 + be signal_p + nop + + call schedule + nop + + ld [%curptr + TI_FLAGS], %g2 +signal_p: + andcc %g2, (_TIF_NOTIFY_RESUME|_TIF_SIGPENDING), %g0 + bz,a ret_trap_continue + ld [%sp + STACKFRAME_SZ + PT_PSR], %t_psr + + clr %o0 + mov %l5, %o2 + mov %l6, %o3 + call do_signal + add %sp, STACKFRAME_SZ, %o1 ! pt_regs ptr + + /* Fall through. */ + ld [%sp + STACKFRAME_SZ + PT_PSR], %t_psr + clr %l6 +ret_trap_continue: + wr %t_psr, 0x0, %psr + WRITE_PAUSE + + ld [%curptr + TI_W_SAVED], %twin_tmp1 + orcc %g0, %twin_tmp1, %g0 + be ret_trap_nobufwins + nop + + wr %t_psr, PSR_ET, %psr + WRITE_PAUSE + + mov 1, %o1 + call try_to_clear_window_buffer + add %sp, STACKFRAME_SZ, %o0 + + b signal_p + ld [%curptr + TI_FLAGS], %g2 + +ret_trap_nobufwins: + /* Load up the user's out registers so we can pull + * a window from the stack, if necessary. + */ + LOAD_PT_INS(sp) + + /* If there are already live user windows in the + * set we can return from trap safely. + */ + ld [%curptr + TI_UWINMASK], %twin_tmp1 + orcc %g0, %twin_tmp1, %g0 + bne ret_trap_userwins_ok + nop + + /* Calculate new %wim, we have to pull a register + * window from the users stack. + */ +ret_trap_pull_one_window: + rd %wim, %t_wim + sll %t_wim, 0x1, %twin_tmp1 +rtrap_patch1: srl %t_wim, 0x7, %glob_tmp + or %glob_tmp, %twin_tmp1, %glob_tmp +rtrap_patch2: and %glob_tmp, 0xff, %glob_tmp + + wr %glob_tmp, 0x0, %wim + + /* Here comes the architecture specific + * branch to the user stack checking routine + * for return from traps. + */ + .globl rtrap_mmu_patchme +rtrap_mmu_patchme: b sun4c_rett_stackchk + andcc %fp, 0x7, %g0 + +ret_trap_userwins_ok: + LOAD_PT_PRIV(sp, t_psr, t_pc, t_npc) + or %t_pc, %t_npc, %g2 + andcc %g2, 0x3, %g0 + be 1f + nop + + b ret_trap_unaligned_pc + add %sp, STACKFRAME_SZ, %o0 + +1: + LOAD_PT_YREG(sp, g1) + LOAD_PT_GLOBALS(sp) + + wr %t_psr, 0x0, %psr + WRITE_PAUSE + + jmp %t_pc + rett %t_npc + +ret_trap_unaligned_pc: + ld [%sp + STACKFRAME_SZ + PT_PC], %o1 + ld [%sp + STACKFRAME_SZ + PT_NPC], %o2 + ld [%sp + STACKFRAME_SZ + PT_PSR], %o3 + + wr %t_wim, 0x0, %wim ! or else... + + wr %t_psr, PSR_ET, %psr + WRITE_PAUSE + + call do_memaccess_unaligned + nop + + b signal_p + ld [%curptr + TI_FLAGS], %g2 + +ret_trap_kernel: + /* Will the rett land us in the invalid window? */ + mov 2, %g1 + sll %g1, %t_psr, %g1 +rtrap_patch3: srl %g1, 8, %g2 + or %g1, %g2, %g1 + rd %wim, %g2 + andcc %g2, %g1, %g0 + be 1f ! Nope, just return from the trap + sll %g2, 0x1, %g1 + + /* We have to grab a window before returning. */ +rtrap_patch4: srl %g2, 7, %g2 + or %g1, %g2, %g1 +rtrap_patch5: and %g1, 0xff, %g1 + + wr %g1, 0x0, %wim + + /* Grrr, make sure we load from the right %sp... */ + LOAD_PT_ALL(sp, t_psr, t_pc, t_npc, g1) + + restore %g0, %g0, %g0 + LOAD_WINDOW(sp) + b 2f + save %g0, %g0, %g0 + + /* Reload the entire frame in case this is from a + * kernel system call or whatever... + */ +1: + LOAD_PT_ALL(sp, t_psr, t_pc, t_npc, g1) +2: + wr %t_psr, 0x0, %psr + WRITE_PAUSE + + jmp %t_pc + rett %t_npc + +ret_trap_user_stack_is_bolixed: + wr %t_wim, 0x0, %wim + + wr %t_psr, PSR_ET, %psr + WRITE_PAUSE + + call window_ret_fault + add %sp, STACKFRAME_SZ, %o0 + + b signal_p + ld [%curptr + TI_FLAGS], %g2 + + + .globl sun4c_rett_stackchk +sun4c_rett_stackchk: + be 1f + and %fp, 0xfff, %g1 ! delay slot + + b ret_trap_user_stack_is_bolixed + 0x4 + wr %t_wim, 0x0, %wim + + /* See if we have to check the sanity of one page or two */ +1: + add %g1, 0x38, %g1 + sra %fp, 29, %g2 + add %g2, 0x1, %g2 + andncc %g2, 0x1, %g0 + be 1f + andncc %g1, 0xff8, %g0 + + /* %sp is in vma hole, yuck */ + b ret_trap_user_stack_is_bolixed + 0x4 + wr %t_wim, 0x0, %wim + +1: + be sun4c_rett_onepage /* Only one page to check */ + lda [%fp] ASI_PTE, %g2 + +sun4c_rett_twopages: + add %fp, 0x38, %g1 + sra %g1, 29, %g2 + add %g2, 0x1, %g2 + andncc %g2, 0x1, %g0 + be 1f + lda [%g1] ASI_PTE, %g2 + + /* Second page is in vma hole */ + b ret_trap_user_stack_is_bolixed + 0x4 + wr %t_wim, 0x0, %wim + +1: + srl %g2, 29, %g2 + andcc %g2, 0x4, %g0 + bne sun4c_rett_onepage + lda [%fp] ASI_PTE, %g2 + + /* Second page has bad perms */ + b ret_trap_user_stack_is_bolixed + 0x4 + wr %t_wim, 0x0, %wim + +sun4c_rett_onepage: + srl %g2, 29, %g2 + andcc %g2, 0x4, %g0 + bne,a 1f + restore %g0, %g0, %g0 + + /* A page had bad page permissions, losing... */ + b ret_trap_user_stack_is_bolixed + 0x4 + wr %t_wim, 0x0, %wim + + /* Whee, things are ok, load the window and continue. */ +1: + LOAD_WINDOW(sp) + + b ret_trap_userwins_ok + save %g0, %g0, %g0 + + .globl srmmu_rett_stackchk +srmmu_rett_stackchk: + bne ret_trap_user_stack_is_bolixed + sethi %hi(PAGE_OFFSET), %g1 + cmp %g1, %fp + bleu ret_trap_user_stack_is_bolixed + mov AC_M_SFSR, %g1 + lda [%g1] ASI_M_MMUREGS, %g0 + + lda [%g0] ASI_M_MMUREGS, %g1 + or %g1, 0x2, %g1 + sta %g1, [%g0] ASI_M_MMUREGS + + restore %g0, %g0, %g0 + + LOAD_WINDOW(sp) + + save %g0, %g0, %g0 + + andn %g1, 0x2, %g1 + sta %g1, [%g0] ASI_M_MMUREGS + + mov AC_M_SFAR, %g2 + lda [%g2] ASI_M_MMUREGS, %g2 + + mov AC_M_SFSR, %g1 + lda [%g1] ASI_M_MMUREGS, %g1 + andcc %g1, 0x2, %g0 + be ret_trap_userwins_ok + nop + + b,a ret_trap_user_stack_is_bolixed diff --git a/arch/sparc/kernel/sclow.S b/arch/sparc/kernel/sclow.S new file mode 100644 index 000000000000..3a867fc19927 --- /dev/null +++ b/arch/sparc/kernel/sclow.S @@ -0,0 +1,86 @@ +/* sclow.S: Low level special syscall handling. + * Basically these are cases where we can completely + * handle the system call without saving any state + * because we know that the process will not sleep. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/ptrace.h> +#include <asm/asm_offsets.h> +#include <asm/errno.h> +#include <asm/winmacro.h> +#include <asm/thread_info.h> +#include <asm/psr.h> +#include <asm/page.h> + +#define CC_AND_RETT \ + set PSR_C, %l4; \ + andn %l0, %l4, %l4; \ + wr %l4, 0x0, %psr; \ + nop; nop; nop; \ + jmp %l2; \ + rett %l2 + 4; + +#define SC_AND_RETT \ + set PSR_C, %l4; \ + or %l0, %l4, %l4; \ + wr %l4, 0x0, %psr; \ + nop; nop; nop; \ + jmp %l2; \ + rett %l2 + 4; + +#define LABEL(func) func##_low + + .globl LABEL(sunosnop) +LABEL(sunosnop): + CC_AND_RETT + +#if (ASIZ_task_uid == 2 && ASIZ_task_euid == 2) + .globl LABEL(sunosgetuid) +LABEL(sunosgetuid): + LOAD_CURRENT(l4, l5) + ld [%l4 + TI_TASK], %l4 + lduh [%l4 + AOFF_task_uid], %i0 + lduh [%l4 + AOFF_task_euid], %i1 + CC_AND_RETT +#endif + +#if (ASIZ_task_gid == 2 && ASIZ_task_egid == 2) + .globl LABEL(sunosgetgid) +LABEL(sunosgetgid): + LOAD_CURRENT(l4, l5) + ld [%l4 + TI_TASK], %l4 + lduh [%l4 + AOFF_task_gid], %i0 + lduh [%l4 + AOFF_task_egid], %i1 + CC_AND_RETT +#endif + + .globl LABEL(sunosmctl) +LABEL(sunosmctl): + mov 0, %i0 + CC_AND_RETT + + .globl LABEL(sunosgdtsize) +LABEL(sunosgdtsize): + mov 256, %i0 + CC_AND_RETT + + .globl LABEL(getpagesize) +LABEL(getpagesize): + set PAGE_SIZE, %i0 + CC_AND_RETT + + /* XXX sys_nice() XXX */ + /* XXX sys_setpriority() XXX */ + /* XXX sys_getpriority() XXX */ + /* XXX sys_setregid() XXX */ + /* XXX sys_setgid() XXX */ + /* XXX sys_setreuid() XXX */ + /* XXX sys_setuid() XXX */ + /* XXX sys_setfsuid() XXX */ + /* XXX sys_setfsgid() XXX */ + /* XXX sys_setpgid() XXX */ + /* XXX sys_getpgid() XXX */ + /* XXX sys_setsid() XXX */ + /* XXX sys_getsid() XXX */ diff --git a/arch/sparc/kernel/semaphore.c b/arch/sparc/kernel/semaphore.c new file mode 100644 index 000000000000..0c37c1a7cd7e --- /dev/null +++ b/arch/sparc/kernel/semaphore.c @@ -0,0 +1,155 @@ +/* $Id: semaphore.c,v 1.7 2001/04/18 21:06:05 davem Exp $ */ + +/* sparc32 semaphore implementation, based on i386 version */ + +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/init.h> + +#include <asm/semaphore.h> + +/* + * Semaphores are implemented using a two-way counter: + * The "count" variable is decremented for each process + * that tries to acquire the semaphore, while the "sleeping" + * variable is a count of such acquires. + * + * Notably, the inline "up()" and "down()" functions can + * efficiently test if they need to do any extra work (up + * needs to do something only if count was negative before + * the increment operation. + * + * "sleeping" and the contention routine ordering is + * protected by the semaphore spinlock. + * + * Note that these functions are only called when there is + * contention on the lock, and as such all this is the + * "non-critical" part of the whole semaphore business. The + * critical part is the inline stuff in <asm/semaphore.h> + * where we want to avoid any extra jumps and calls. + */ + +/* + * Logic: + * - only on a boundary condition do we need to care. When we go + * from a negative count to a non-negative, we wake people up. + * - when we go from a non-negative count to a negative do we + * (a) synchronize with the "sleeper" count and (b) make sure + * that we're on the wakeup list before we synchronize so that + * we cannot lose wakeup events. + */ + +void __up(struct semaphore *sem) +{ + wake_up(&sem->wait); +} + +static DEFINE_SPINLOCK(semaphore_lock); + +void __sched __down(struct semaphore * sem) +{ + struct task_struct *tsk = current; + DECLARE_WAITQUEUE(wait, tsk); + tsk->state = TASK_UNINTERRUPTIBLE; + add_wait_queue_exclusive(&sem->wait, &wait); + + spin_lock_irq(&semaphore_lock); + sem->sleepers++; + for (;;) { + int sleepers = sem->sleepers; + + /* + * Add "everybody else" into it. They aren't + * playing, because we own the spinlock. + */ + if (!atomic24_add_negative(sleepers - 1, &sem->count)) { + sem->sleepers = 0; + break; + } + sem->sleepers = 1; /* us - see -1 above */ + spin_unlock_irq(&semaphore_lock); + + schedule(); + tsk->state = TASK_UNINTERRUPTIBLE; + spin_lock_irq(&semaphore_lock); + } + spin_unlock_irq(&semaphore_lock); + remove_wait_queue(&sem->wait, &wait); + tsk->state = TASK_RUNNING; + wake_up(&sem->wait); +} + +int __sched __down_interruptible(struct semaphore * sem) +{ + int retval = 0; + struct task_struct *tsk = current; + DECLARE_WAITQUEUE(wait, tsk); + tsk->state = TASK_INTERRUPTIBLE; + add_wait_queue_exclusive(&sem->wait, &wait); + + spin_lock_irq(&semaphore_lock); + sem->sleepers ++; + for (;;) { + int sleepers = sem->sleepers; + + /* + * With signals pending, this turns into + * the trylock failure case - we won't be + * sleeping, and we* can't get the lock as + * it has contention. Just correct the count + * and exit. + */ + if (signal_pending(current)) { + retval = -EINTR; + sem->sleepers = 0; + atomic24_add(sleepers, &sem->count); + break; + } + + /* + * Add "everybody else" into it. They aren't + * playing, because we own the spinlock. The + * "-1" is because we're still hoping to get + * the lock. + */ + if (!atomic24_add_negative(sleepers - 1, &sem->count)) { + sem->sleepers = 0; + break; + } + sem->sleepers = 1; /* us - see -1 above */ + spin_unlock_irq(&semaphore_lock); + + schedule(); + tsk->state = TASK_INTERRUPTIBLE; + spin_lock_irq(&semaphore_lock); + } + spin_unlock_irq(&semaphore_lock); + tsk->state = TASK_RUNNING; + remove_wait_queue(&sem->wait, &wait); + wake_up(&sem->wait); + return retval; +} + +/* + * Trylock failed - make sure we correct for + * having decremented the count. + */ +int __down_trylock(struct semaphore * sem) +{ + int sleepers; + unsigned long flags; + + spin_lock_irqsave(&semaphore_lock, flags); + sleepers = sem->sleepers + 1; + sem->sleepers = 0; + + /* + * Add "everybody else" and us into it. They aren't + * playing, because we own the spinlock. + */ + if (!atomic24_add_negative(sleepers, &sem->count)) + wake_up(&sem->wait); + + spin_unlock_irqrestore(&semaphore_lock, flags); + return 1; +} diff --git a/arch/sparc/kernel/setup.c b/arch/sparc/kernel/setup.c new file mode 100644 index 000000000000..55352ed85e8a --- /dev/null +++ b/arch/sparc/kernel/setup.c @@ -0,0 +1,476 @@ +/* $Id: setup.c,v 1.126 2001/11/13 00:49:27 davem Exp $ + * linux/arch/sparc/kernel/setup.c + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 2000 Anton Blanchard (anton@samba.org) + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/initrd.h> +#include <asm/smp.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/tty.h> +#include <linux/delay.h> +#include <linux/config.h> +#include <linux/fs.h> +#include <linux/seq_file.h> +#include <linux/syscalls.h> +#include <linux/kdev_t.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/console.h> +#include <linux/spinlock.h> +#include <linux/root_dev.h> + +#include <asm/segment.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/processor.h> +#include <asm/oplib.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/traps.h> +#include <asm/vaddrs.h> +#include <asm/kdebug.h> +#include <asm/mbus.h> +#include <asm/idprom.h> +#include <asm/machines.h> +#include <asm/cpudata.h> +#include <asm/setup.h> + +struct screen_info screen_info = { + 0, 0, /* orig-x, orig-y */ + 0, /* unused */ + 0, /* orig-video-page */ + 0, /* orig-video-mode */ + 128, /* orig-video-cols */ + 0,0,0, /* ega_ax, ega_bx, ega_cx */ + 54, /* orig-video-lines */ + 0, /* orig-video-isVGA */ + 16 /* orig-video-points */ +}; + +/* Typing sync at the prom prompt calls the function pointed to by + * romvec->pv_synchook which I set to the following function. + * This should sync all filesystems and return, for now it just + * prints out pretty messages and returns. + */ + +extern unsigned long trapbase; +void (*prom_palette)(int); + +/* Pretty sick eh? */ +void prom_sync_me(void) +{ + unsigned long prom_tbr, flags; + + /* XXX Badly broken. FIX! - Anton */ + local_irq_save(flags); + __asm__ __volatile__("rd %%tbr, %0\n\t" : "=r" (prom_tbr)); + __asm__ __volatile__("wr %0, 0x0, %%tbr\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" : : "r" (&trapbase)); + + if (prom_palette) + prom_palette(1); + prom_printf("PROM SYNC COMMAND...\n"); + show_free_areas(); + if(current->pid != 0) { + local_irq_enable(); + sys_sync(); + local_irq_disable(); + } + prom_printf("Returning to prom\n"); + + __asm__ __volatile__("wr %0, 0x0, %%tbr\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" : : "r" (prom_tbr)); + local_irq_restore(flags); + + return; +} + +unsigned int boot_flags __initdata = 0; +#define BOOTME_DEBUG 0x1 +#define BOOTME_SINGLE 0x2 + +/* Exported for mm/init.c:paging_init. */ +unsigned long cmdline_memory_size __initdata = 0; + +static void +prom_console_write(struct console *con, const char *s, unsigned n) +{ + prom_write(s, n); +} + +static struct console prom_debug_console = { + .name = "debug", + .write = prom_console_write, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +int obp_system_intr(void) +{ + if (boot_flags & BOOTME_DEBUG) { + printk("OBP: system interrupted\n"); + prom_halt(); + return 1; + } + return 0; +} + +/* + * Process kernel command line switches that are specific to the + * SPARC or that require special low-level processing. + */ +static void __init process_switch(char c) +{ + switch (c) { + case 'd': + boot_flags |= BOOTME_DEBUG; + break; + case 's': + boot_flags |= BOOTME_SINGLE; + break; + case 'h': + prom_printf("boot_flags_init: Halt!\n"); + prom_halt(); + break; + case 'p': + /* Use PROM debug console. */ + register_console(&prom_debug_console); + break; + default: + printk("Unknown boot switch (-%c)\n", c); + break; + } +} + +static void __init process_console(char *commands) +{ + serial_console = 0; + commands += 8; + /* Linux-style serial */ + if (!strncmp(commands, "ttyS", 4)) + serial_console = simple_strtoul(commands + 4, NULL, 10) + 1; + else if (!strncmp(commands, "tty", 3)) { + char c = *(commands + 3); + /* Solaris-style serial */ + if (c == 'a' || c == 'b') + serial_console = c - 'a' + 1; + /* else Linux-style fbcon, not serial */ + } +#if defined(CONFIG_PROM_CONSOLE) + if (!strncmp(commands, "prom", 4)) { + char *p; + + for (p = commands - 8; *p && *p != ' '; p++) + *p = ' '; + conswitchp = &prom_con; + } +#endif +} + +static void __init boot_flags_init(char *commands) +{ + while (*commands) { + /* Move to the start of the next "argument". */ + while (*commands && *commands == ' ') + commands++; + + /* Process any command switches, otherwise skip it. */ + if (*commands == '\0') + break; + if (*commands == '-') { + commands++; + while (*commands && *commands != ' ') + process_switch(*commands++); + continue; + } + if (!strncmp(commands, "console=", 8)) { + process_console(commands); + } else if (!strncmp(commands, "mem=", 4)) { + /* + * "mem=XXX[kKmM] overrides the PROM-reported + * memory size. + */ + cmdline_memory_size = simple_strtoul(commands + 4, + &commands, 0); + if (*commands == 'K' || *commands == 'k') { + cmdline_memory_size <<= 10; + commands++; + } else if (*commands=='M' || *commands=='m') { + cmdline_memory_size <<= 20; + commands++; + } + } + while (*commands && *commands != ' ') + commands++; + } +} + +/* This routine will in the future do all the nasty prom stuff + * to probe for the mmu type and its parameters, etc. This will + * also be where SMP things happen plus the Sparc specific memory + * physical memory probe as on the alpha. + */ + +extern int prom_probe_memory(void); +extern void sun4c_probe_vac(void); +extern char cputypval; +extern unsigned long start, end; +extern void panic_setup(char *, int *); + +extern unsigned short root_flags; +extern unsigned short root_dev; +extern unsigned short ram_flags; +#define RAMDISK_IMAGE_START_MASK 0x07FF +#define RAMDISK_PROMPT_FLAG 0x8000 +#define RAMDISK_LOAD_FLAG 0x4000 + +extern int root_mountflags; + +char reboot_command[COMMAND_LINE_SIZE]; +enum sparc_cpu sparc_cpu_model; + +struct tt_entry *sparc_ttable; + +struct pt_regs fake_swapper_regs; + +extern void paging_init(void); + +void __init setup_arch(char **cmdline_p) +{ + int i; + unsigned long highest_paddr; + + sparc_ttable = (struct tt_entry *) &start; + + /* Initialize PROM console and command line. */ + *cmdline_p = prom_getbootargs(); + strcpy(saved_command_line, *cmdline_p); + + /* Set sparc_cpu_model */ + sparc_cpu_model = sun_unknown; + if(!strcmp(&cputypval,"sun4 ")) { sparc_cpu_model=sun4; } + if(!strcmp(&cputypval,"sun4c")) { sparc_cpu_model=sun4c; } + if(!strcmp(&cputypval,"sun4m")) { sparc_cpu_model=sun4m; } + if(!strcmp(&cputypval,"sun4s")) { sparc_cpu_model=sun4m; } /* CP-1200 with PROM 2.30 -E */ + if(!strcmp(&cputypval,"sun4d")) { sparc_cpu_model=sun4d; } + if(!strcmp(&cputypval,"sun4e")) { sparc_cpu_model=sun4e; } + if(!strcmp(&cputypval,"sun4u")) { sparc_cpu_model=sun4u; } + +#ifdef CONFIG_SUN4 + if (sparc_cpu_model != sun4) { + prom_printf("This kernel is for Sun4 architecture only.\n"); + prom_halt(); + } +#endif + printk("ARCH: "); + switch(sparc_cpu_model) { + case sun4: + printk("SUN4\n"); + break; + case sun4c: + printk("SUN4C\n"); + break; + case sun4m: + printk("SUN4M\n"); + break; + case sun4d: + printk("SUN4D\n"); + break; + case sun4e: + printk("SUN4E\n"); + break; + case sun4u: + printk("SUN4U\n"); + break; + default: + printk("UNKNOWN!\n"); + break; + }; + +#ifdef CONFIG_DUMMY_CONSOLE + conswitchp = &dummy_con; +#elif defined(CONFIG_PROM_CONSOLE) + conswitchp = &prom_con; +#endif + boot_flags_init(*cmdline_p); + + idprom_init(); + if (ARCH_SUN4C_SUN4) + sun4c_probe_vac(); + load_mmu(); + (void) prom_probe_memory(); + + phys_base = 0xffffffffUL; + highest_paddr = 0UL; + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + unsigned long top; + + if (sp_banks[i].base_addr < phys_base) + phys_base = sp_banks[i].base_addr; + top = sp_banks[i].base_addr + + sp_banks[i].num_bytes; + if (highest_paddr < top) + highest_paddr = top; + } + pfn_base = phys_base >> PAGE_SHIFT; + + if (!root_flags) + root_mountflags &= ~MS_RDONLY; + ROOT_DEV = old_decode_dev(root_dev); +#ifdef CONFIG_BLK_DEV_INITRD + rd_image_start = ram_flags & RAMDISK_IMAGE_START_MASK; + rd_prompt = ((ram_flags & RAMDISK_PROMPT_FLAG) != 0); + rd_doload = ((ram_flags & RAMDISK_LOAD_FLAG) != 0); +#endif + + prom_setsync(prom_sync_me); + + if((boot_flags&BOOTME_DEBUG) && (linux_dbvec!=0) && + ((*(short *)linux_dbvec) != -1)) { + printk("Booted under KADB. Syncing trap table.\n"); + (*(linux_dbvec->teach_debugger))(); + } + + init_mm.context = (unsigned long) NO_CONTEXT; + init_task.thread.kregs = &fake_swapper_regs; + + paging_init(); +} + +static int __init set_preferred_console(void) +{ + int idev, odev; + + /* The user has requested a console so this is already set up. */ + if (serial_console >= 0) + return -EBUSY; + + idev = prom_query_input_device(); + odev = prom_query_output_device(); + if (idev == PROMDEV_IKBD && odev == PROMDEV_OSCREEN) { + serial_console = 0; + } else if (idev == PROMDEV_ITTYA && odev == PROMDEV_OTTYA) { + serial_console = 1; + } else if (idev == PROMDEV_ITTYB && odev == PROMDEV_OTTYB) { + serial_console = 2; + } else if (idev == PROMDEV_I_UNK && odev == PROMDEV_OTTYA) { + prom_printf("MrCoffee ttya\n"); + serial_console = 1; + } else if (idev == PROMDEV_I_UNK && odev == PROMDEV_OSCREEN) { + serial_console = 0; + prom_printf("MrCoffee keyboard\n"); + } else { + prom_printf("Confusing console (idev %d, odev %d)\n", + idev, odev); + serial_console = 1; + } + + if (serial_console) + return add_preferred_console("ttyS", serial_console - 1, NULL); + + return -ENODEV; +} +console_initcall(set_preferred_console); + +extern char *sparc_cpu_type; +extern char *sparc_fpu_type; + +static int show_cpuinfo(struct seq_file *m, void *__unused) +{ + seq_printf(m, + "cpu\t\t: %s\n" + "fpu\t\t: %s\n" + "promlib\t\t: Version %d Revision %d\n" + "prom\t\t: %d.%d\n" + "type\t\t: %s\n" + "ncpus probed\t: %d\n" + "ncpus active\t: %d\n" +#ifndef CONFIG_SMP + "CPU0Bogo\t: %lu.%02lu\n" + "CPU0ClkTck\t: %ld\n" +#endif + , + sparc_cpu_type ? sparc_cpu_type : "undetermined", + sparc_fpu_type ? sparc_fpu_type : "undetermined", + romvec->pv_romvers, + prom_rev, + romvec->pv_printrev >> 16, + romvec->pv_printrev & 0xffff, + &cputypval, + num_possible_cpus(), + num_online_cpus() +#ifndef CONFIG_SMP + , cpu_data(0).udelay_val/(500000/HZ), + (cpu_data(0).udelay_val/(5000/HZ)) % 100, + cpu_data(0).clock_tick +#endif + ); + +#ifdef CONFIG_SMP + smp_bogo(m); +#endif + mmu_info(m); +#ifdef CONFIG_SMP + smp_info(m); +#endif + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + /* The pointer we are returning is arbitrary, + * it just has to be non-NULL and not IS_ERR + * in the success case. + */ + return *pos == 0 ? &c_start : NULL; +} + +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return c_start(m, pos); +} + +static void c_stop(struct seq_file *m, void *v) +{ +} + +struct seq_operations cpuinfo_op = { + .start =c_start, + .next = c_next, + .stop = c_stop, + .show = show_cpuinfo, +}; + +extern int stop_a_enabled; + +void sun_do_break(void) +{ + if (!stop_a_enabled) + return; + + printk("\n"); + flush_user_windows(); + + prom_cmdline(); +} + +int serial_console = -1; +int stop_a_enabled = 1; diff --git a/arch/sparc/kernel/signal.c b/arch/sparc/kernel/signal.c new file mode 100644 index 000000000000..011ff35057a5 --- /dev/null +++ b/arch/sparc/kernel/signal.c @@ -0,0 +1,1181 @@ +/* $Id: signal.c,v 1.110 2002/02/08 03:57:14 davem Exp $ + * linux/arch/sparc/kernel/signal.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/ptrace.h> +#include <linux/unistd.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/binfmts.h> /* do_coredum */ +#include <linux/bitops.h> + +#include <asm/uaccess.h> +#include <asm/ptrace.h> +#include <asm/svr4.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/cacheflush.h> /* flush_sig_insns */ + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +extern void fpsave(unsigned long *fpregs, unsigned long *fsr, + void *fpqueue, unsigned long *fpqdepth); +extern void fpload(unsigned long *fpregs, unsigned long *fsr); + +asmlinkage int do_signal(sigset_t *oldset, struct pt_regs * regs, + unsigned long orig_o0, int restart_syscall); + +/* Signal frames: the original one (compatible with SunOS): + * + * Set up a signal frame... Make the stack look the way SunOS + * expects it to look which is basically: + * + * ---------------------------------- <-- %sp at signal time + * Struct sigcontext + * Signal address + * Ptr to sigcontext area above + * Signal code + * The signal number itself + * One register window + * ---------------------------------- <-- New %sp + */ +struct signal_sframe { + struct reg_window sig_window; + int sig_num; + int sig_code; + struct sigcontext __user *sig_scptr; + int sig_address; + struct sigcontext sig_context; + unsigned int extramask[_NSIG_WORDS - 1]; +}; + +/* + * And the new one, intended to be used for Linux applications only + * (we have enough in there to work with clone). + * All the interesting bits are in the info field. + */ + +struct new_signal_frame { + struct sparc_stackf ss; + __siginfo_t info; + __siginfo_fpu_t __user *fpu_save; + unsigned long insns[2] __attribute__ ((aligned (8))); + unsigned int extramask[_NSIG_WORDS - 1]; + unsigned int extra_size; /* Should be 0 */ + __siginfo_fpu_t fpu_state; +}; + +struct rt_signal_frame { + struct sparc_stackf ss; + siginfo_t info; + struct pt_regs regs; + sigset_t mask; + __siginfo_fpu_t __user *fpu_save; + unsigned int insns[2]; + stack_t stack; + unsigned int extra_size; /* Should be 0 */ + __siginfo_fpu_t fpu_state; +}; + +/* Align macros */ +#define SF_ALIGNEDSZ (((sizeof(struct signal_sframe) + 7) & (~7))) +#define NF_ALIGNEDSZ (((sizeof(struct new_signal_frame) + 7) & (~7))) +#define RT_ALIGNEDSZ (((sizeof(struct rt_signal_frame) + 7) & (~7))) + +/* + * atomically swap in the new signal mask, and wait for a signal. + * This is really tricky on the Sparc, watch out... + */ +asmlinkage void _sigpause_common(old_sigset_t set, struct pt_regs *regs) +{ + sigset_t saveset; + + set &= _BLOCKABLE; + spin_lock_irq(¤t->sighand->siglock); + saveset = current->blocked; + siginitset(¤t->blocked, set); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + regs->pc = regs->npc; + regs->npc += 4; + + /* Condition codes and return value where set here for sigpause, + * and so got used by setup_frame, which again causes sigreturn() + * to return -EINTR. + */ + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + /* + * Return -EINTR and set condition code here, + * so the interrupted system call actually returns + * these. + */ + regs->psr |= PSR_C; + regs->u_regs[UREG_I0] = EINTR; + if (do_signal(&saveset, regs, 0, 0)) + return; + } +} + +asmlinkage void do_sigpause(unsigned int set, struct pt_regs *regs) +{ + _sigpause_common(set, regs); +} + +asmlinkage void do_sigsuspend (struct pt_regs *regs) +{ + _sigpause_common(regs->u_regs[UREG_I0], regs); +} + +asmlinkage void do_rt_sigsuspend(sigset_t __user *uset, size_t sigsetsize, + struct pt_regs *regs) +{ + sigset_t oldset, set; + + /* XXX: Don't preclude handling different sized sigset_t's. */ + if (sigsetsize != sizeof(sigset_t)) { + regs->psr |= PSR_C; + regs->u_regs[UREG_I0] = EINVAL; + return; + } + + if (copy_from_user(&set, uset, sizeof(set))) { + regs->psr |= PSR_C; + regs->u_regs[UREG_I0] = EFAULT; + return; + } + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + oldset = current->blocked; + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + regs->pc = regs->npc; + regs->npc += 4; + + /* Condition codes and return value where set here for sigpause, + * and so got used by setup_frame, which again causes sigreturn() + * to return -EINTR. + */ + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + /* + * Return -EINTR and set condition code here, + * so the interrupted system call actually returns + * these. + */ + regs->psr |= PSR_C; + regs->u_regs[UREG_I0] = EINTR; + if (do_signal(&oldset, regs, 0, 0)) + return; + } +} + +static inline int +restore_fpu_state(struct pt_regs *regs, __siginfo_fpu_t __user *fpu) +{ + int err; +#ifdef CONFIG_SMP + if (test_tsk_thread_flag(current, TIF_USEDFPU)) + regs->psr &= ~PSR_EF; +#else + if (current == last_task_used_math) { + last_task_used_math = NULL; + regs->psr &= ~PSR_EF; + } +#endif + set_used_math(); + clear_tsk_thread_flag(current, TIF_USEDFPU); + + if (!access_ok(VERIFY_READ, fpu, sizeof(*fpu))) + return -EFAULT; + + err = __copy_from_user(¤t->thread.float_regs[0], &fpu->si_float_regs[0], + (sizeof(unsigned long) * 32)); + err |= __get_user(current->thread.fsr, &fpu->si_fsr); + err |= __get_user(current->thread.fpqdepth, &fpu->si_fpqdepth); + if (current->thread.fpqdepth != 0) + err |= __copy_from_user(¤t->thread.fpqueue[0], + &fpu->si_fpqueue[0], + ((sizeof(unsigned long) + + (sizeof(unsigned long *)))*16)); + return err; +} + +static inline void do_new_sigreturn (struct pt_regs *regs) +{ + struct new_signal_frame __user *sf; + unsigned long up_psr, pc, npc; + sigset_t set; + __siginfo_fpu_t __user *fpu_save; + int err; + + sf = (struct new_signal_frame __user *) regs->u_regs[UREG_FP]; + + /* 1. Make sure we are not getting garbage from the user */ + if (!access_ok(VERIFY_READ, sf, sizeof(*sf))) + goto segv_and_exit; + + if (((unsigned long) sf) & 3) + goto segv_and_exit; + + err = __get_user(pc, &sf->info.si_regs.pc); + err |= __get_user(npc, &sf->info.si_regs.npc); + + if ((pc | npc) & 3) + goto segv_and_exit; + + /* 2. Restore the state */ + up_psr = regs->psr; + err |= __copy_from_user(regs, &sf->info.si_regs, sizeof(struct pt_regs)); + + /* User can only change condition codes and FPU enabling in %psr. */ + regs->psr = (up_psr & ~(PSR_ICC | PSR_EF)) + | (regs->psr & (PSR_ICC | PSR_EF)); + + err |= __get_user(fpu_save, &sf->fpu_save); + + if (fpu_save) + err |= restore_fpu_state(regs, fpu_save); + + /* This is pretty much atomic, no amount locking would prevent + * the races which exist anyways. + */ + err |= __get_user(set.sig[0], &sf->info.si_mask); + err |= __copy_from_user(&set.sig[1], &sf->extramask, + (_NSIG_WORDS-1) * sizeof(unsigned int)); + + if (err) + goto segv_and_exit; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + return; + +segv_and_exit: + force_sig(SIGSEGV, current); +} + +asmlinkage void do_sigreturn(struct pt_regs *regs) +{ + struct sigcontext __user *scptr; + unsigned long pc, npc, psr; + sigset_t set; + int err; + + /* Always make any pending restarted system calls return -EINTR */ + current_thread_info()->restart_block.fn = do_no_restart_syscall; + + synchronize_user_stack(); + + if (current->thread.new_signal) { + do_new_sigreturn(regs); + return; + } + + scptr = (struct sigcontext __user *) regs->u_regs[UREG_I0]; + + /* Check sanity of the user arg. */ + if (!access_ok(VERIFY_READ, scptr, sizeof(struct sigcontext)) || + (((unsigned long) scptr) & 3)) + goto segv_and_exit; + + err = __get_user(pc, &scptr->sigc_pc); + err |= __get_user(npc, &scptr->sigc_npc); + + if ((pc | npc) & 3) + goto segv_and_exit; + + /* This is pretty much atomic, no amount locking would prevent + * the races which exist anyways. + */ + err |= __get_user(set.sig[0], &scptr->sigc_mask); + /* Note that scptr + 1 points to extramask */ + err |= __copy_from_user(&set.sig[1], scptr + 1, + (_NSIG_WORDS - 1) * sizeof(unsigned int)); + + if (err) + goto segv_and_exit; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + regs->pc = pc; + regs->npc = npc; + + err = __get_user(regs->u_regs[UREG_FP], &scptr->sigc_sp); + err |= __get_user(regs->u_regs[UREG_I0], &scptr->sigc_o0); + err |= __get_user(regs->u_regs[UREG_G1], &scptr->sigc_g1); + + /* User can only change condition codes in %psr. */ + err |= __get_user(psr, &scptr->sigc_psr); + if (err) + goto segv_and_exit; + + regs->psr &= ~(PSR_ICC); + regs->psr |= (psr & PSR_ICC); + return; + +segv_and_exit: + force_sig(SIGSEGV, current); +} + +asmlinkage void do_rt_sigreturn(struct pt_regs *regs) +{ + struct rt_signal_frame __user *sf; + unsigned int psr, pc, npc; + __siginfo_fpu_t __user *fpu_save; + mm_segment_t old_fs; + sigset_t set; + stack_t st; + int err; + + synchronize_user_stack(); + sf = (struct rt_signal_frame __user *) regs->u_regs[UREG_FP]; + if (!access_ok(VERIFY_READ, sf, sizeof(*sf)) || + (((unsigned long) sf) & 0x03)) + goto segv; + + err = __get_user(pc, &sf->regs.pc); + err |= __get_user(npc, &sf->regs.npc); + err |= ((pc | npc) & 0x03); + + err |= __get_user(regs->y, &sf->regs.y); + err |= __get_user(psr, &sf->regs.psr); + + err |= __copy_from_user(®s->u_regs[UREG_G1], + &sf->regs.u_regs[UREG_G1], 15 * sizeof(u32)); + + regs->psr = (regs->psr & ~PSR_ICC) | (psr & PSR_ICC); + + err |= __get_user(fpu_save, &sf->fpu_save); + + if (fpu_save) + err |= restore_fpu_state(regs, fpu_save); + err |= __copy_from_user(&set, &sf->mask, sizeof(sigset_t)); + + err |= __copy_from_user(&st, &sf->stack, sizeof(stack_t)); + + if (err) + goto segv; + + regs->pc = pc; + regs->npc = npc; + + /* It is more difficult to avoid calling this function than to + * call it and ignore errors. + */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + do_sigaltstack((const stack_t __user *) &st, NULL, (unsigned long)sf); + set_fs(old_fs); + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + return; +segv: + force_sig(SIGSEGV, current); +} + +/* Checks if the fp is valid */ +static inline int invalid_frame_pointer(void __user *fp, int fplen) +{ + if ((((unsigned long) fp) & 7) || + !__access_ok((unsigned long)fp, fplen) || + ((sparc_cpu_model == sun4 || sparc_cpu_model == sun4c) && + ((unsigned long) fp < 0xe0000000 && (unsigned long) fp >= 0x20000000))) + return 1; + + return 0; +} + +static inline void __user *get_sigframe(struct sigaction *sa, struct pt_regs *regs, unsigned long framesize) +{ + unsigned long sp; + + sp = regs->u_regs[UREG_FP]; + + /* This is the X/Open sanctioned signal stack switching. */ + if (sa->sa_flags & SA_ONSTACK) { + if (!on_sig_stack(sp) && !((current->sas_ss_sp + current->sas_ss_size) & 7)) + sp = current->sas_ss_sp + current->sas_ss_size; + } + return (void __user *)(sp - framesize); +} + +static inline void +setup_frame(struct sigaction *sa, struct pt_regs *regs, int signr, sigset_t *oldset, siginfo_t *info) +{ + struct signal_sframe __user *sframep; + struct sigcontext __user *sc; + int window = 0, err; + unsigned long pc = regs->pc; + unsigned long npc = regs->npc; + struct thread_info *tp = current_thread_info(); + void __user *sig_address; + int sig_code; + + synchronize_user_stack(); + sframep = (struct signal_sframe __user *) + get_sigframe(sa, regs, SF_ALIGNEDSZ); + if (invalid_frame_pointer(sframep, sizeof(*sframep))){ + /* Don't change signal code and address, so that + * post mortem debuggers can have a look. + */ + goto sigill_and_return; + } + + sc = &sframep->sig_context; + + /* We've already made sure frame pointer isn't in kernel space... */ + err = __put_user((sas_ss_flags(regs->u_regs[UREG_FP]) == SS_ONSTACK), + &sc->sigc_onstack); + err |= __put_user(oldset->sig[0], &sc->sigc_mask); + err |= __copy_to_user(sframep->extramask, &oldset->sig[1], + (_NSIG_WORDS - 1) * sizeof(unsigned int)); + err |= __put_user(regs->u_regs[UREG_FP], &sc->sigc_sp); + err |= __put_user(pc, &sc->sigc_pc); + err |= __put_user(npc, &sc->sigc_npc); + err |= __put_user(regs->psr, &sc->sigc_psr); + err |= __put_user(regs->u_regs[UREG_G1], &sc->sigc_g1); + err |= __put_user(regs->u_regs[UREG_I0], &sc->sigc_o0); + err |= __put_user(tp->w_saved, &sc->sigc_oswins); + if (tp->w_saved) + for (window = 0; window < tp->w_saved; window++) { + put_user((char *)tp->rwbuf_stkptrs[window], + &sc->sigc_spbuf[window]); + err |= __copy_to_user(&sc->sigc_wbuf[window], + &tp->reg_window[window], + sizeof(struct reg_window)); + } + else + err |= __copy_to_user(sframep, (char *) regs->u_regs[UREG_FP], + sizeof(struct reg_window)); + + tp->w_saved = 0; /* So process is allowed to execute. */ + + err |= __put_user(signr, &sframep->sig_num); + sig_address = NULL; + sig_code = 0; + if (SI_FROMKERNEL (info) && (info->si_code & __SI_MASK) == __SI_FAULT) { + sig_address = info->si_addr; + switch (signr) { + case SIGSEGV: + switch (info->si_code) { + case SEGV_MAPERR: sig_code = SUBSIG_NOMAPPING; break; + default: sig_code = SUBSIG_PROTECTION; break; + } + break; + case SIGILL: + switch (info->si_code) { + case ILL_ILLOPC: sig_code = SUBSIG_ILLINST; break; + case ILL_PRVOPC: sig_code = SUBSIG_PRIVINST; break; + case ILL_ILLTRP: sig_code = SUBSIG_BADTRAP(info->si_trapno); break; + default: sig_code = SUBSIG_STACK; break; + } + break; + case SIGFPE: + switch (info->si_code) { + case FPE_INTDIV: sig_code = SUBSIG_IDIVZERO; break; + case FPE_INTOVF: sig_code = SUBSIG_FPINTOVFL; break; + case FPE_FLTDIV: sig_code = SUBSIG_FPDIVZERO; break; + case FPE_FLTOVF: sig_code = SUBSIG_FPOVFLOW; break; + case FPE_FLTUND: sig_code = SUBSIG_FPUNFLOW; break; + case FPE_FLTRES: sig_code = SUBSIG_FPINEXACT; break; + case FPE_FLTINV: sig_code = SUBSIG_FPOPERROR; break; + default: sig_code = SUBSIG_FPERROR; break; + } + break; + case SIGBUS: + switch (info->si_code) { + case BUS_ADRALN: sig_code = SUBSIG_ALIGNMENT; break; + case BUS_ADRERR: sig_code = SUBSIG_MISCERROR; break; + default: sig_code = SUBSIG_BUSTIMEOUT; break; + } + break; + case SIGEMT: + switch (info->si_code) { + case EMT_TAGOVF: sig_code = SUBSIG_TAG; break; + } + break; + case SIGSYS: + if (info->si_code == (__SI_FAULT|0x100)) { + /* See sys_sunos.c */ + sig_code = info->si_trapno; + break; + } + default: + sig_address = NULL; + } + } + err |= __put_user((unsigned long)sig_address, &sframep->sig_address); + err |= __put_user(sig_code, &sframep->sig_code); + err |= __put_user(sc, &sframep->sig_scptr); + if (err) + goto sigsegv; + + regs->u_regs[UREG_FP] = (unsigned long) sframep; + regs->pc = (unsigned long) sa->sa_handler; + regs->npc = (regs->pc + 4); + return; + +sigill_and_return: + do_exit(SIGILL); +sigsegv: + force_sigsegv(signr, current); +} + + +static inline int +save_fpu_state(struct pt_regs *regs, __siginfo_fpu_t __user *fpu) +{ + int err = 0; +#ifdef CONFIG_SMP + if (test_tsk_thread_flag(current, TIF_USEDFPU)) { + put_psr(get_psr() | PSR_EF); + fpsave(¤t->thread.float_regs[0], ¤t->thread.fsr, + ¤t->thread.fpqueue[0], ¤t->thread.fpqdepth); + regs->psr &= ~(PSR_EF); + clear_tsk_thread_flag(current, TIF_USEDFPU); + } +#else + if (current == last_task_used_math) { + put_psr(get_psr() | PSR_EF); + fpsave(¤t->thread.float_regs[0], ¤t->thread.fsr, + ¤t->thread.fpqueue[0], ¤t->thread.fpqdepth); + last_task_used_math = NULL; + regs->psr &= ~(PSR_EF); + } +#endif + err |= __copy_to_user(&fpu->si_float_regs[0], + ¤t->thread.float_regs[0], + (sizeof(unsigned long) * 32)); + err |= __put_user(current->thread.fsr, &fpu->si_fsr); + err |= __put_user(current->thread.fpqdepth, &fpu->si_fpqdepth); + if (current->thread.fpqdepth != 0) + err |= __copy_to_user(&fpu->si_fpqueue[0], + ¤t->thread.fpqueue[0], + ((sizeof(unsigned long) + + (sizeof(unsigned long *)))*16)); + clear_used_math(); + return err; +} + +static inline void +new_setup_frame(struct k_sigaction *ka, struct pt_regs *regs, + int signo, sigset_t *oldset) +{ + struct new_signal_frame __user *sf; + int sigframe_size, err; + + /* 1. Make sure everything is clean */ + synchronize_user_stack(); + + sigframe_size = NF_ALIGNEDSZ; + if (!used_math()) + sigframe_size -= sizeof(__siginfo_fpu_t); + + sf = (struct new_signal_frame __user *) + get_sigframe(&ka->sa, regs, sigframe_size); + + if (invalid_frame_pointer(sf, sigframe_size)) + goto sigill_and_return; + + if (current_thread_info()->w_saved != 0) + goto sigill_and_return; + + /* 2. Save the current process state */ + err = __copy_to_user(&sf->info.si_regs, regs, sizeof(struct pt_regs)); + + err |= __put_user(0, &sf->extra_size); + + if (used_math()) { + err |= save_fpu_state(regs, &sf->fpu_state); + err |= __put_user(&sf->fpu_state, &sf->fpu_save); + } else { + err |= __put_user(0, &sf->fpu_save); + } + + err |= __put_user(oldset->sig[0], &sf->info.si_mask); + err |= __copy_to_user(sf->extramask, &oldset->sig[1], + (_NSIG_WORDS - 1) * sizeof(unsigned int)); + err |= __copy_to_user(sf, (char *) regs->u_regs[UREG_FP], + sizeof(struct reg_window)); + if (err) + goto sigsegv; + + /* 3. signal handler back-trampoline and parameters */ + regs->u_regs[UREG_FP] = (unsigned long) sf; + regs->u_regs[UREG_I0] = signo; + regs->u_regs[UREG_I1] = (unsigned long) &sf->info; + regs->u_regs[UREG_I2] = (unsigned long) &sf->info; + + /* 4. signal handler */ + regs->pc = (unsigned long) ka->sa.sa_handler; + regs->npc = (regs->pc + 4); + + /* 5. return to kernel instructions */ + if (ka->ka_restorer) + regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer; + else { + regs->u_regs[UREG_I7] = (unsigned long)(&(sf->insns[0]) - 2); + + /* mov __NR_sigreturn, %g1 */ + err |= __put_user(0x821020d8, &sf->insns[0]); + + /* t 0x10 */ + err |= __put_user(0x91d02010, &sf->insns[1]); + if (err) + goto sigsegv; + + /* Flush instruction space. */ + flush_sig_insns(current->mm, (unsigned long) &(sf->insns[0])); + } + return; + +sigill_and_return: + do_exit(SIGILL); +sigsegv: + force_sigsegv(signo, current); +} + +static inline void +new_setup_rt_frame(struct k_sigaction *ka, struct pt_regs *regs, + int signo, sigset_t *oldset, siginfo_t *info) +{ + struct rt_signal_frame __user *sf; + int sigframe_size; + unsigned int psr; + int err; + + synchronize_user_stack(); + sigframe_size = RT_ALIGNEDSZ; + if (!used_math()) + sigframe_size -= sizeof(__siginfo_fpu_t); + sf = (struct rt_signal_frame __user *) + get_sigframe(&ka->sa, regs, sigframe_size); + if (invalid_frame_pointer(sf, sigframe_size)) + goto sigill; + if (current_thread_info()->w_saved != 0) + goto sigill; + + err = __put_user(regs->pc, &sf->regs.pc); + err |= __put_user(regs->npc, &sf->regs.npc); + err |= __put_user(regs->y, &sf->regs.y); + psr = regs->psr; + if (used_math()) + psr |= PSR_EF; + err |= __put_user(psr, &sf->regs.psr); + err |= __copy_to_user(&sf->regs.u_regs, regs->u_regs, sizeof(regs->u_regs)); + err |= __put_user(0, &sf->extra_size); + + if (psr & PSR_EF) { + err |= save_fpu_state(regs, &sf->fpu_state); + err |= __put_user(&sf->fpu_state, &sf->fpu_save); + } else { + err |= __put_user(0, &sf->fpu_save); + } + err |= __copy_to_user(&sf->mask, &oldset->sig[0], sizeof(sigset_t)); + + /* Setup sigaltstack */ + err |= __put_user(current->sas_ss_sp, &sf->stack.ss_sp); + err |= __put_user(sas_ss_flags(regs->u_regs[UREG_FP]), &sf->stack.ss_flags); + err |= __put_user(current->sas_ss_size, &sf->stack.ss_size); + + err |= __copy_to_user(sf, (char *) regs->u_regs[UREG_FP], + sizeof(struct reg_window)); + + err |= copy_siginfo_to_user(&sf->info, info); + + if (err) + goto sigsegv; + + regs->u_regs[UREG_FP] = (unsigned long) sf; + regs->u_regs[UREG_I0] = signo; + regs->u_regs[UREG_I1] = (unsigned long) &sf->info; + regs->u_regs[UREG_I2] = (unsigned long) &sf->regs; + + regs->pc = (unsigned long) ka->sa.sa_handler; + regs->npc = (regs->pc + 4); + + if (ka->ka_restorer) + regs->u_regs[UREG_I7] = (unsigned long)ka->ka_restorer; + else { + regs->u_regs[UREG_I7] = (unsigned long)(&(sf->insns[0]) - 2); + + /* mov __NR_sigreturn, %g1 */ + err |= __put_user(0x821020d8, &sf->insns[0]); + + /* t 0x10 */ + err |= __put_user(0x91d02010, &sf->insns[1]); + if (err) + goto sigsegv; + + /* Flush instruction space. */ + flush_sig_insns(current->mm, (unsigned long) &(sf->insns[0])); + } + return; + +sigill: + do_exit(SIGILL); +sigsegv: + force_sigsegv(signo, current); +} + +/* Setup a Solaris stack frame */ +static inline void +setup_svr4_frame(struct sigaction *sa, unsigned long pc, unsigned long npc, + struct pt_regs *regs, int signr, sigset_t *oldset) +{ + svr4_signal_frame_t __user *sfp; + svr4_gregset_t __user *gr; + svr4_siginfo_t __user *si; + svr4_mcontext_t __user *mc; + svr4_gwindows_t __user *gw; + svr4_ucontext_t __user *uc; + svr4_sigset_t setv; + struct thread_info *tp = current_thread_info(); + int window = 0, err; + + synchronize_user_stack(); + sfp = (svr4_signal_frame_t __user *) + get_sigframe(sa, regs, SVR4_SF_ALIGNED + sizeof(struct reg_window)); + + if (invalid_frame_pointer(sfp, sizeof(*sfp))) + goto sigill_and_return; + + /* Start with a clean frame pointer and fill it */ + err = __clear_user(sfp, sizeof(*sfp)); + + /* Setup convenience variables */ + si = &sfp->si; + uc = &sfp->uc; + gw = &sfp->gw; + mc = &uc->mcontext; + gr = &mc->greg; + + /* FIXME: where am I supposed to put this? + * sc->sigc_onstack = old_status; + * anyways, it does not look like it is used for anything at all. + */ + setv.sigbits[0] = oldset->sig[0]; + setv.sigbits[1] = oldset->sig[1]; + if (_NSIG_WORDS >= 4) { + setv.sigbits[2] = oldset->sig[2]; + setv.sigbits[3] = oldset->sig[3]; + err |= __copy_to_user(&uc->sigmask, &setv, sizeof(svr4_sigset_t)); + } else + err |= __copy_to_user(&uc->sigmask, &setv, + 2 * sizeof(unsigned int)); + + /* Store registers */ + err |= __put_user(regs->pc, &((*gr)[SVR4_PC])); + err |= __put_user(regs->npc, &((*gr)[SVR4_NPC])); + err |= __put_user(regs->psr, &((*gr)[SVR4_PSR])); + err |= __put_user(regs->y, &((*gr)[SVR4_Y])); + + /* Copy g[1..7] and o[0..7] registers */ + err |= __copy_to_user(&(*gr)[SVR4_G1], ®s->u_regs[UREG_G1], + sizeof(long) * 7); + err |= __copy_to_user(&(*gr)[SVR4_O0], ®s->u_regs[UREG_I0], + sizeof(long) * 8); + + /* Setup sigaltstack */ + err |= __put_user(current->sas_ss_sp, &uc->stack.sp); + err |= __put_user(sas_ss_flags(regs->u_regs[UREG_FP]), &uc->stack.flags); + err |= __put_user(current->sas_ss_size, &uc->stack.size); + + /* Save the currently window file: */ + + /* 1. Link sfp->uc->gwins to our windows */ + err |= __put_user(gw, &mc->gwin); + + /* 2. Number of windows to restore at setcontext(): */ + err |= __put_user(tp->w_saved, &gw->count); + + /* 3. Save each valid window + * Currently, it makes a copy of the windows from the kernel copy. + * David's code for SunOS, makes the copy but keeps the pointer to + * the kernel. My version makes the pointer point to a userland + * copy of those. Mhm, I wonder if I shouldn't just ignore those + * on setcontext and use those that are on the kernel, the signal + * handler should not be modyfing those, mhm. + * + * These windows are just used in case synchronize_user_stack failed + * to flush the user windows. + */ + for (window = 0; window < tp->w_saved; window++) { + err |= __put_user((int __user *) &(gw->win[window]), &gw->winptr[window]); + err |= __copy_to_user(&gw->win[window], + &tp->reg_window[window], + sizeof(svr4_rwindow_t)); + err |= __put_user(0, gw->winptr[window]); + } + + /* 4. We just pay attention to the gw->count field on setcontext */ + tp->w_saved = 0; /* So process is allowed to execute. */ + + /* Setup the signal information. Solaris expects a bunch of + * information to be passed to the signal handler, we don't provide + * that much currently, should use siginfo. + */ + err |= __put_user(signr, &si->siginfo.signo); + err |= __put_user(SVR4_SINOINFO, &si->siginfo.code); + if (err) + goto sigsegv; + + regs->u_regs[UREG_FP] = (unsigned long) sfp; + regs->pc = (unsigned long) sa->sa_handler; + regs->npc = (regs->pc + 4); + + /* Arguments passed to signal handler */ + if (regs->u_regs[14]){ + struct reg_window __user *rw = (struct reg_window __user *) + regs->u_regs[14]; + + err |= __put_user(signr, &rw->ins[0]); + err |= __put_user(si, &rw->ins[1]); + err |= __put_user(uc, &rw->ins[2]); + err |= __put_user(sfp, &rw->ins[6]); /* frame pointer */ + if (err) + goto sigsegv; + + regs->u_regs[UREG_I0] = signr; + regs->u_regs[UREG_I1] = (unsigned long) si; + regs->u_regs[UREG_I2] = (unsigned long) uc; + } + return; + +sigill_and_return: + do_exit(SIGILL); +sigsegv: + force_sigsegv(signr, current); +} + +asmlinkage int svr4_getcontext(svr4_ucontext_t __user *uc, struct pt_regs *regs) +{ + svr4_gregset_t __user *gr; + svr4_mcontext_t __user *mc; + svr4_sigset_t setv; + int err = 0; + + synchronize_user_stack(); + + if (current_thread_info()->w_saved) + return -EFAULT; + + err = clear_user(uc, sizeof(*uc)); + if (err) + return -EFAULT; + + /* Setup convenience variables */ + mc = &uc->mcontext; + gr = &mc->greg; + + setv.sigbits[0] = current->blocked.sig[0]; + setv.sigbits[1] = current->blocked.sig[1]; + if (_NSIG_WORDS >= 4) { + setv.sigbits[2] = current->blocked.sig[2]; + setv.sigbits[3] = current->blocked.sig[3]; + err |= __copy_to_user(&uc->sigmask, &setv, sizeof(svr4_sigset_t)); + } else + err |= __copy_to_user(&uc->sigmask, &setv, + 2 * sizeof(unsigned int)); + + /* Store registers */ + err |= __put_user(regs->pc, &uc->mcontext.greg[SVR4_PC]); + err |= __put_user(regs->npc, &uc->mcontext.greg[SVR4_NPC]); + err |= __put_user(regs->psr, &uc->mcontext.greg[SVR4_PSR]); + err |= __put_user(regs->y, &uc->mcontext.greg[SVR4_Y]); + + /* Copy g[1..7] and o[0..7] registers */ + err |= __copy_to_user(&(*gr)[SVR4_G1], ®s->u_regs[UREG_G1], + sizeof(uint) * 7); + err |= __copy_to_user(&(*gr)[SVR4_O0], ®s->u_regs[UREG_I0], + sizeof(uint) * 8); + + /* Setup sigaltstack */ + err |= __put_user(current->sas_ss_sp, &uc->stack.sp); + err |= __put_user(sas_ss_flags(regs->u_regs[UREG_FP]), &uc->stack.flags); + err |= __put_user(current->sas_ss_size, &uc->stack.size); + + /* The register file is not saved + * we have already stuffed all of it with sync_user_stack + */ + return (err ? -EFAULT : 0); +} + +/* Set the context for a svr4 application, this is Solaris way to sigreturn */ +asmlinkage int svr4_setcontext(svr4_ucontext_t __user *c, struct pt_regs *regs) +{ + svr4_gregset_t __user *gr; + unsigned long pc, npc, psr; + mm_segment_t old_fs; + sigset_t set; + svr4_sigset_t setv; + int err; + stack_t st; + + /* Fixme: restore windows, or is this already taken care of in + * svr4_setup_frame when sync_user_windows is done? + */ + flush_user_windows(); + + if (current_thread_info()->w_saved) + goto sigsegv_and_return; + + if (((unsigned long) c) & 3) + goto sigsegv_and_return; + + if (!__access_ok((unsigned long)c, sizeof(*c))) + goto sigsegv_and_return; + + /* Check for valid PC and nPC */ + gr = &c->mcontext.greg; + err = __get_user(pc, &((*gr)[SVR4_PC])); + err |= __get_user(npc, &((*gr)[SVR4_NPC])); + + if ((pc | npc) & 3) + goto sigsegv_and_return; + + /* Retrieve information from passed ucontext */ + /* note that nPC is ored a 1, this is used to inform entry.S */ + /* that we don't want it to mess with our PC and nPC */ + + /* This is pretty much atomic, no amount locking would prevent + * the races which exist anyways. + */ + err |= __copy_from_user(&setv, &c->sigmask, sizeof(svr4_sigset_t)); + + err |= __get_user(st.ss_sp, &c->stack.sp); + err |= __get_user(st.ss_flags, &c->stack.flags); + err |= __get_user(st.ss_size, &c->stack.size); + + if (err) + goto sigsegv_and_return; + + /* It is more difficult to avoid calling this function than to + call it and ignore errors. */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + do_sigaltstack((const stack_t __user *) &st, NULL, + regs->u_regs[UREG_I6]); + set_fs(old_fs); + + set.sig[0] = setv.sigbits[0]; + set.sig[1] = setv.sigbits[1]; + if (_NSIG_WORDS >= 4) { + set.sig[2] = setv.sigbits[2]; + set.sig[3] = setv.sigbits[3]; + } + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + regs->pc = pc; + regs->npc = npc | 1; + err |= __get_user(regs->y, &((*gr)[SVR4_Y])); + err |= __get_user(psr, &((*gr)[SVR4_PSR])); + regs->psr &= ~(PSR_ICC); + regs->psr |= (psr & PSR_ICC); + + /* Restore g[1..7] and o[0..7] registers */ + err |= __copy_from_user(®s->u_regs[UREG_G1], &(*gr)[SVR4_G1], + sizeof(long) * 7); + err |= __copy_from_user(®s->u_regs[UREG_I0], &(*gr)[SVR4_O0], + sizeof(long) * 8); + return (err ? -EFAULT : 0); + +sigsegv_and_return: + force_sig(SIGSEGV, current); + return -EFAULT; +} + +static inline void +handle_signal(unsigned long signr, struct k_sigaction *ka, + siginfo_t *info, sigset_t *oldset, struct pt_regs *regs, + int svr4_signal) +{ + if (svr4_signal) + setup_svr4_frame(&ka->sa, regs->pc, regs->npc, regs, signr, oldset); + else { + if (ka->sa.sa_flags & SA_SIGINFO) + new_setup_rt_frame(ka, regs, signr, oldset, info); + else if (current->thread.new_signal) + new_setup_frame(ka, regs, signr, oldset); + else + setup_frame(&ka->sa, regs, signr, oldset, info); + } + if (!(ka->sa.sa_flags & SA_NOMASK)) { + spin_lock_irq(¤t->sighand->siglock); + sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask); + sigaddset(¤t->blocked, signr); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } +} + +static inline void syscall_restart(unsigned long orig_i0, struct pt_regs *regs, + struct sigaction *sa) +{ + switch(regs->u_regs[UREG_I0]) { + case ERESTART_RESTARTBLOCK: + case ERESTARTNOHAND: + no_system_call_restart: + regs->u_regs[UREG_I0] = EINTR; + regs->psr |= PSR_C; + break; + case ERESTARTSYS: + if (!(sa->sa_flags & SA_RESTART)) + goto no_system_call_restart; + /* fallthrough */ + case ERESTARTNOINTR: + regs->u_regs[UREG_I0] = orig_i0; + regs->pc -= 4; + regs->npc -= 4; + } +} + +/* Note that 'init' is a special process: it doesn't get signals it doesn't + * want to handle. Thus you cannot kill init even with a SIGKILL even by + * mistake. + */ +asmlinkage int do_signal(sigset_t *oldset, struct pt_regs * regs, + unsigned long orig_i0, int restart_syscall) +{ + siginfo_t info; + struct sparc_deliver_cookie cookie; + struct k_sigaction ka; + int signr; + + /* + * XXX Disable svr4 signal handling until solaris emulation works. + * It is buggy - Anton + */ +#define SVR4_SIGNAL_BROKEN 1 +#ifdef SVR4_SIGNAL_BROKEN + int svr4_signal = 0; +#else + int svr4_signal = current->personality == PER_SVR4; +#endif + + cookie.restart_syscall = restart_syscall; + cookie.orig_i0 = orig_i0; + + if (!oldset) + oldset = ¤t->blocked; + + signr = get_signal_to_deliver(&info, &ka, regs, &cookie); + if (signr > 0) { + if (cookie.restart_syscall) + syscall_restart(cookie.orig_i0, regs, &ka.sa); + handle_signal(signr, &ka, &info, oldset, + regs, svr4_signal); + return 1; + } + if (cookie.restart_syscall && + (regs->u_regs[UREG_I0] == ERESTARTNOHAND || + regs->u_regs[UREG_I0] == ERESTARTSYS || + regs->u_regs[UREG_I0] == ERESTARTNOINTR)) { + /* replay the system call when we are done */ + regs->u_regs[UREG_I0] = cookie.orig_i0; + regs->pc -= 4; + regs->npc -= 4; + } + if (cookie.restart_syscall && + regs->u_regs[UREG_I0] == ERESTART_RESTARTBLOCK) { + regs->u_regs[UREG_G1] = __NR_restart_syscall; + regs->pc -= 4; + regs->npc -= 4; + } + return 0; +} + +asmlinkage int +do_sys_sigstack(struct sigstack __user *ssptr, struct sigstack __user *ossptr, + unsigned long sp) +{ + int ret = -EFAULT; + + /* First see if old state is wanted. */ + if (ossptr) { + if (put_user(current->sas_ss_sp + current->sas_ss_size, + &ossptr->the_stack) || + __put_user(on_sig_stack(sp), &ossptr->cur_status)) + goto out; + } + + /* Now see if we want to update the new state. */ + if (ssptr) { + char *ss_sp; + + if (get_user(ss_sp, &ssptr->the_stack)) + goto out; + /* If the current stack was set with sigaltstack, don't + swap stacks while we are on it. */ + ret = -EPERM; + if (current->sas_ss_sp && on_sig_stack(sp)) + goto out; + + /* Since we don't know the extent of the stack, and we don't + track onstack-ness, but rather calculate it, we must + presume a size. Ho hum this interface is lossy. */ + current->sas_ss_sp = (unsigned long)ss_sp - SIGSTKSZ; + current->sas_ss_size = SIGSTKSZ; + } + ret = 0; +out: + return ret; +} + +void ptrace_signal_deliver(struct pt_regs *regs, void *cookie) +{ + struct sparc_deliver_cookie *cp = cookie; + + if (cp->restart_syscall && + (regs->u_regs[UREG_I0] == ERESTARTNOHAND || + regs->u_regs[UREG_I0] == ERESTARTSYS || + regs->u_regs[UREG_I0] == ERESTARTNOINTR)) { + /* replay the system call when we are done */ + regs->u_regs[UREG_I0] = cp->orig_i0; + regs->pc -= 4; + regs->npc -= 4; + cp->restart_syscall = 0; + } + + if (cp->restart_syscall && + regs->u_regs[UREG_I0] == ERESTART_RESTARTBLOCK) { + regs->u_regs[UREG_G1] = __NR_restart_syscall; + regs->pc -= 4; + regs->npc -= 4; + cp->restart_syscall = 0; + } +} diff --git a/arch/sparc/kernel/smp.c b/arch/sparc/kernel/smp.c new file mode 100644 index 000000000000..c6e721d8f477 --- /dev/null +++ b/arch/sparc/kernel/smp.c @@ -0,0 +1,295 @@ +/* smp.c: Sparc SMP support. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 2004 Keith M Wesolowski (wesolows@foobazco.org) + */ + +#include <asm/head.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/threads.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/seq_file.h> +#include <linux/cache.h> +#include <linux/delay.h> + +#include <asm/ptrace.h> +#include <asm/atomic.h> + +#include <asm/irq.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/oplib.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/cpudata.h> + +volatile int smp_processors_ready = 0; +int smp_num_cpus = 1; +volatile unsigned long cpu_callin_map[NR_CPUS] __initdata = {0,}; +unsigned char boot_cpu_id = 0; +unsigned char boot_cpu_id4 = 0; /* boot_cpu_id << 2 */ +int smp_activated = 0; +volatile int __cpu_number_map[NR_CPUS]; +volatile int __cpu_logical_map[NR_CPUS]; + +cpumask_t cpu_online_map = CPU_MASK_NONE; +cpumask_t phys_cpu_present_map = CPU_MASK_NONE; + +/* The only guaranteed locking primitive available on all Sparc + * processors is 'ldstub [%reg + immediate], %dest_reg' which atomically + * places the current byte at the effective address into dest_reg and + * places 0xff there afterwards. Pretty lame locking primitive + * compared to the Alpha and the Intel no? Most Sparcs have 'swap' + * instruction which is much better... + */ + +/* Used to make bitops atomic */ +unsigned char bitops_spinlock = 0; + +volatile unsigned long ipi_count; + +volatile int smp_process_available=0; +volatile int smp_commenced = 0; + +void __init smp_store_cpu_info(int id) +{ + int cpu_node; + + cpu_data(id).udelay_val = loops_per_jiffy; + + cpu_find_by_mid(id, &cpu_node); + cpu_data(id).clock_tick = prom_getintdefault(cpu_node, + "clock-frequency", 0); + cpu_data(id).prom_node = cpu_node; + cpu_data(id).mid = cpu_get_hwmid(cpu_node); + if (cpu_data(id).mid < 0) + panic("No MID found for CPU%d at node 0x%08d", id, cpu_node); +} + +void __init smp_cpus_done(unsigned int max_cpus) +{ +} + +void cpu_panic(void) +{ + printk("CPU[%d]: Returns from cpu_idle!\n", smp_processor_id()); + panic("SMP bolixed\n"); +} + +struct linux_prom_registers smp_penguin_ctable __initdata = { 0 }; + +void __init smp_boot_cpus(void) +{ + extern void smp4m_boot_cpus(void); + extern void smp4d_boot_cpus(void); + + if (sparc_cpu_model == sun4m) + smp4m_boot_cpus(); + else + smp4d_boot_cpus(); +} + +void smp_send_reschedule(int cpu) +{ + /* See sparc64 */ +} + +void smp_send_stop(void) +{ +} + +void smp_flush_cache_all(void) +{ + xc0((smpfunc_t) BTFIXUP_CALL(local_flush_cache_all)); + local_flush_cache_all(); +} + +void smp_flush_tlb_all(void) +{ + xc0((smpfunc_t) BTFIXUP_CALL(local_flush_tlb_all)); + local_flush_tlb_all(); +} + +void smp_flush_cache_mm(struct mm_struct *mm) +{ + if(mm->context != NO_CONTEXT) { + cpumask_t cpu_mask = mm->cpu_vm_mask; + cpu_clear(smp_processor_id(), cpu_mask); + if (!cpus_empty(cpu_mask)) + xc1((smpfunc_t) BTFIXUP_CALL(local_flush_cache_mm), (unsigned long) mm); + local_flush_cache_mm(mm); + } +} + +void smp_flush_tlb_mm(struct mm_struct *mm) +{ + if(mm->context != NO_CONTEXT) { + cpumask_t cpu_mask = mm->cpu_vm_mask; + cpu_clear(smp_processor_id(), cpu_mask); + if (!cpus_empty(cpu_mask)) { + xc1((smpfunc_t) BTFIXUP_CALL(local_flush_tlb_mm), (unsigned long) mm); + if(atomic_read(&mm->mm_users) == 1 && current->active_mm == mm) + mm->cpu_vm_mask = cpumask_of_cpu(smp_processor_id()); + } + local_flush_tlb_mm(mm); + } +} + +void smp_flush_cache_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + + if (mm->context != NO_CONTEXT) { + cpumask_t cpu_mask = mm->cpu_vm_mask; + cpu_clear(smp_processor_id(), cpu_mask); + if (!cpus_empty(cpu_mask)) + xc3((smpfunc_t) BTFIXUP_CALL(local_flush_cache_range), (unsigned long) vma, start, end); + local_flush_cache_range(vma, start, end); + } +} + +void smp_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + + if (mm->context != NO_CONTEXT) { + cpumask_t cpu_mask = mm->cpu_vm_mask; + cpu_clear(smp_processor_id(), cpu_mask); + if (!cpus_empty(cpu_mask)) + xc3((smpfunc_t) BTFIXUP_CALL(local_flush_tlb_range), (unsigned long) vma, start, end); + local_flush_tlb_range(vma, start, end); + } +} + +void smp_flush_cache_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + + if(mm->context != NO_CONTEXT) { + cpumask_t cpu_mask = mm->cpu_vm_mask; + cpu_clear(smp_processor_id(), cpu_mask); + if (!cpus_empty(cpu_mask)) + xc2((smpfunc_t) BTFIXUP_CALL(local_flush_cache_page), (unsigned long) vma, page); + local_flush_cache_page(vma, page); + } +} + +void smp_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + + if(mm->context != NO_CONTEXT) { + cpumask_t cpu_mask = mm->cpu_vm_mask; + cpu_clear(smp_processor_id(), cpu_mask); + if (!cpus_empty(cpu_mask)) + xc2((smpfunc_t) BTFIXUP_CALL(local_flush_tlb_page), (unsigned long) vma, page); + local_flush_tlb_page(vma, page); + } +} + +void smp_reschedule_irq(void) +{ + set_need_resched(); +} + +void smp_flush_page_to_ram(unsigned long page) +{ + /* Current theory is that those who call this are the one's + * who have just dirtied their cache with the pages contents + * in kernel space, therefore we only run this on local cpu. + * + * XXX This experiment failed, research further... -DaveM + */ +#if 1 + xc1((smpfunc_t) BTFIXUP_CALL(local_flush_page_to_ram), page); +#endif + local_flush_page_to_ram(page); +} + +void smp_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr) +{ + cpumask_t cpu_mask = mm->cpu_vm_mask; + cpu_clear(smp_processor_id(), cpu_mask); + if (!cpus_empty(cpu_mask)) + xc2((smpfunc_t) BTFIXUP_CALL(local_flush_sig_insns), (unsigned long) mm, insn_addr); + local_flush_sig_insns(mm, insn_addr); +} + +extern unsigned int lvl14_resolution; + +/* /proc/profile writes can call this, don't __init it please. */ +static DEFINE_SPINLOCK(prof_setup_lock); + +int setup_profiling_timer(unsigned int multiplier) +{ + int i; + unsigned long flags; + + /* Prevent level14 ticker IRQ flooding. */ + if((!multiplier) || (lvl14_resolution / multiplier) < 500) + return -EINVAL; + + spin_lock_irqsave(&prof_setup_lock, flags); + for(i = 0; i < NR_CPUS; i++) { + if (cpu_possible(i)) + load_profile_irq(i, lvl14_resolution / multiplier); + prof_multiplier(i) = multiplier; + } + spin_unlock_irqrestore(&prof_setup_lock, flags); + + return 0; +} + +void __init smp_prepare_cpus(unsigned int maxcpus) +{ +} + +void __devinit smp_prepare_boot_cpu(void) +{ + current_thread_info()->cpu = hard_smp_processor_id(); + cpu_set(smp_processor_id(), cpu_online_map); + cpu_set(smp_processor_id(), phys_cpu_present_map); +} + +int __devinit __cpu_up(unsigned int cpu) +{ + panic("smp doesn't work\n"); +} + +void smp_bogo(struct seq_file *m) +{ + int i; + + for (i = 0; i < NR_CPUS; i++) { + if (cpu_online(i)) + seq_printf(m, + "Cpu%dBogo\t: %lu.%02lu\n", + i, + cpu_data(i).udelay_val/(500000/HZ), + (cpu_data(i).udelay_val/(5000/HZ))%100); + } +} + +void smp_info(struct seq_file *m) +{ + int i; + + seq_printf(m, "State:\n"); + for (i = 0; i < NR_CPUS; i++) { + if (cpu_online(i)) + seq_printf(m, "CPU%d\t\t: online\n", i); + } +} diff --git a/arch/sparc/kernel/sparc-stub.c b/arch/sparc/kernel/sparc-stub.c new file mode 100644 index 000000000000..e84f815e6903 --- /dev/null +++ b/arch/sparc/kernel/sparc-stub.c @@ -0,0 +1,724 @@ +/* $Id: sparc-stub.c,v 1.28 2001/10/30 04:54:21 davem Exp $ + * sparc-stub.c: KGDB support for the Linux kernel. + * + * Modifications to run under Linux + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * + * This file originally came from the gdb sources, and the + * copyright notices have been retained below. + */ + +/**************************************************************************** + + THIS SOFTWARE IS NOT COPYRIGHTED + + HP offers the following for use in the public domain. HP makes no + warranty with regard to the software or its performance and the + user accepts the software "AS IS" with all faults. + + HP DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD + TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +****************************************************************************/ + +/**************************************************************************** + * Header: remcom.c,v 1.34 91/03/09 12:29:49 glenne Exp $ + * + * Module name: remcom.c $ + * Revision: 1.34 $ + * Date: 91/03/09 12:29:49 $ + * Contributor: Lake Stevens Instrument Division$ + * + * Description: low level support for gdb debugger. $ + * + * Considerations: only works on target hardware $ + * + * Written by: Glenn Engel $ + * ModuleState: Experimental $ + * + * NOTES: See Below $ + * + * Modified for SPARC by Stu Grossman, Cygnus Support. + * + * This code has been extensively tested on the Fujitsu SPARClite demo board. + * + * To enable debugger support, two things need to happen. One, a + * call to set_debug_traps() is necessary in order to allow any breakpoints + * or error conditions to be properly intercepted and reported to gdb. + * Two, a breakpoint needs to be generated to begin communication. This + * is most easily accomplished by a call to breakpoint(). Breakpoint() + * simulates a breakpoint by executing a trap #1. + * + ************* + * + * The following gdb commands are supported: + * + * command function Return value + * + * g return the value of the CPU registers hex data or ENN + * G set the value of the CPU registers OK or ENN + * + * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN + * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN + * + * c Resume at current address SNN ( signal NN) + * cAA..AA Continue at address AA..AA SNN + * + * s Step one instruction SNN + * sAA..AA Step one instruction from AA..AA SNN + * + * k kill + * + * ? What was the last sigval ? SNN (signal NN) + * + * bBB..BB Set baud rate to BB..BB OK or BNN, then sets + * baud rate + * + * All commands and responses are sent with a packet which includes a + * checksum. A packet consists of + * + * $<packet info>#<checksum>. + * + * where + * <packet info> :: <characters representing the command or response> + * <checksum> :: < two hex digits computed as modulo 256 sum of <packetinfo>> + * + * When a packet is received, it is first acknowledged with either '+' or '-'. + * '+' indicates a successful transfer. '-' indicates a failed transfer. + * + * Example: + * + * Host: Reply: + * $m0,10#2a +$00010203040506070809101112131415#42 + * + ****************************************************************************/ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> + +#include <asm/system.h> +#include <asm/signal.h> +#include <asm/oplib.h> +#include <asm/head.h> +#include <asm/traps.h> +#include <asm/vac-ops.h> +#include <asm/kgdb.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/cacheflush.h> + +/* + * + * external low-level support routines + */ + +extern void putDebugChar(char); /* write a single character */ +extern char getDebugChar(void); /* read and return a single char */ + +/* + * BUFMAX defines the maximum number of characters in inbound/outbound buffers + * at least NUMREGBYTES*2 are needed for register packets + */ +#define BUFMAX 2048 + +static int initialized; /* !0 means we've been initialized */ + +static const char hexchars[]="0123456789abcdef"; + +#define NUMREGS 72 + +/* Number of bytes of registers. */ +#define NUMREGBYTES (NUMREGS * 4) +enum regnames {G0, G1, G2, G3, G4, G5, G6, G7, + O0, O1, O2, O3, O4, O5, SP, O7, + L0, L1, L2, L3, L4, L5, L6, L7, + I0, I1, I2, I3, I4, I5, FP, I7, + + F0, F1, F2, F3, F4, F5, F6, F7, + F8, F9, F10, F11, F12, F13, F14, F15, + F16, F17, F18, F19, F20, F21, F22, F23, + F24, F25, F26, F27, F28, F29, F30, F31, + Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR }; + + +extern void trap_low(void); /* In arch/sparc/kernel/entry.S */ + +unsigned long get_sun4cpte(unsigned long addr) +{ + unsigned long entry; + + __asm__ __volatile__("\n\tlda [%1] %2, %0\n\t" : + "=r" (entry) : + "r" (addr), "i" (ASI_PTE)); + return entry; +} + +unsigned long get_sun4csegmap(unsigned long addr) +{ + unsigned long entry; + + __asm__ __volatile__("\n\tlduba [%1] %2, %0\n\t" : + "=r" (entry) : + "r" (addr), "i" (ASI_SEGMAP)); + return entry; +} + +#if 0 +/* Have to sort this out. This cannot be done after initialization. */ +static void flush_cache_all_nop(void) {} +#endif + +/* Place where we save old trap entries for restoration */ +struct tt_entry kgdb_savettable[256]; +typedef void (*trapfunc_t)(void); + +/* Helper routine for manipulation of kgdb_savettable */ +static inline void copy_ttentry(struct tt_entry *src, struct tt_entry *dest) +{ + dest->inst_one = src->inst_one; + dest->inst_two = src->inst_two; + dest->inst_three = src->inst_three; + dest->inst_four = src->inst_four; +} + +/* Initialize the kgdb_savettable so that debugging can commence */ +static void eh_init(void) +{ + int i; + + for(i=0; i < 256; i++) + copy_ttentry(&sparc_ttable[i], &kgdb_savettable[i]); +} + +/* Install an exception handler for kgdb */ +static void exceptionHandler(int tnum, trapfunc_t trap_entry) +{ + unsigned long te_addr = (unsigned long) trap_entry; + + /* Make new vector */ + sparc_ttable[tnum].inst_one = + SPARC_BRANCH((unsigned long) te_addr, + (unsigned long) &sparc_ttable[tnum].inst_one); + sparc_ttable[tnum].inst_two = SPARC_RD_PSR_L0; + sparc_ttable[tnum].inst_three = SPARC_NOP; + sparc_ttable[tnum].inst_four = SPARC_NOP; +} + +/* Convert ch from a hex digit to an int */ +static int +hex(unsigned char ch) +{ + if (ch >= 'a' && ch <= 'f') + return ch-'a'+10; + if (ch >= '0' && ch <= '9') + return ch-'0'; + if (ch >= 'A' && ch <= 'F') + return ch-'A'+10; + return -1; +} + +/* scan for the sequence $<data>#<checksum> */ +static void +getpacket(char *buffer) +{ + unsigned char checksum; + unsigned char xmitcsum; + int i; + int count; + unsigned char ch; + + do { + /* wait around for the start character, ignore all other characters */ + while ((ch = (getDebugChar() & 0x7f)) != '$') ; + + checksum = 0; + xmitcsum = -1; + + count = 0; + + /* now, read until a # or end of buffer is found */ + while (count < BUFMAX) { + ch = getDebugChar() & 0x7f; + if (ch == '#') + break; + checksum = checksum + ch; + buffer[count] = ch; + count = count + 1; + } + + if (count >= BUFMAX) + continue; + + buffer[count] = 0; + + if (ch == '#') { + xmitcsum = hex(getDebugChar() & 0x7f) << 4; + xmitcsum |= hex(getDebugChar() & 0x7f); + if (checksum != xmitcsum) + putDebugChar('-'); /* failed checksum */ + else { + putDebugChar('+'); /* successful transfer */ + /* if a sequence char is present, reply the ID */ + if (buffer[2] == ':') { + putDebugChar(buffer[0]); + putDebugChar(buffer[1]); + /* remove sequence chars from buffer */ + count = strlen(buffer); + for (i=3; i <= count; i++) + buffer[i-3] = buffer[i]; + } + } + } + } while (checksum != xmitcsum); +} + +/* send the packet in buffer. */ + +static void +putpacket(unsigned char *buffer) +{ + unsigned char checksum; + int count; + unsigned char ch, recv; + + /* $<packet info>#<checksum>. */ + do { + putDebugChar('$'); + checksum = 0; + count = 0; + + while ((ch = buffer[count])) { + putDebugChar(ch); + checksum += ch; + count += 1; + } + + putDebugChar('#'); + putDebugChar(hexchars[checksum >> 4]); + putDebugChar(hexchars[checksum & 0xf]); + recv = getDebugChar(); + } while ((recv & 0x7f) != '+'); +} + +static char remcomInBuffer[BUFMAX]; +static char remcomOutBuffer[BUFMAX]; + +/* Convert the memory pointed to by mem into hex, placing result in buf. + * Return a pointer to the last char put in buf (null), in case of mem fault, + * return 0. + */ + +static unsigned char * +mem2hex(char *mem, char *buf, int count) +{ + unsigned char ch; + + while (count-- > 0) { + /* This assembler code is basically: ch = *mem++; + * except that we use the SPARC/Linux exception table + * mechanism (see how "fixup" works in kernel_mna_trap_fault) + * to arrange for a "return 0" upon a memory fault + */ + __asm__( + "\n1:\n\t" + "ldub [%0], %1\n\t" + "inc %0\n\t" + ".section .fixup,#alloc,#execinstr\n\t" + ".align 4\n" + "2:\n\t" + "retl\n\t" + " mov 0, %%o0\n\t" + ".section __ex_table, #alloc\n\t" + ".align 4\n\t" + ".word 1b, 2b\n\t" + ".text\n" + : "=r" (mem), "=r" (ch) : "0" (mem)); + *buf++ = hexchars[ch >> 4]; + *buf++ = hexchars[ch & 0xf]; + } + + *buf = 0; + return buf; +} + +/* convert the hex array pointed to by buf into binary to be placed in mem + * return a pointer to the character AFTER the last byte written. +*/ +static char * +hex2mem(char *buf, char *mem, int count) +{ + int i; + unsigned char ch; + + for (i=0; i<count; i++) { + + ch = hex(*buf++) << 4; + ch |= hex(*buf++); + /* Assembler code is *mem++ = ch; with return 0 on fault */ + __asm__( + "\n1:\n\t" + "stb %1, [%0]\n\t" + "inc %0\n\t" + ".section .fixup,#alloc,#execinstr\n\t" + ".align 4\n" + "2:\n\t" + "retl\n\t" + " mov 0, %%o0\n\t" + ".section __ex_table, #alloc\n\t" + ".align 4\n\t" + ".word 1b, 2b\n\t" + ".text\n" + : "=r" (mem) : "r" (ch) , "0" (mem)); + } + return mem; +} + +/* This table contains the mapping between SPARC hardware trap types, and + signals, which are primarily what GDB understands. It also indicates + which hardware traps we need to commandeer when initializing the stub. */ + +static struct hard_trap_info +{ + unsigned char tt; /* Trap type code for SPARC */ + unsigned char signo; /* Signal that we map this trap into */ +} hard_trap_info[] = { + {SP_TRAP_SBPT, SIGTRAP}, /* ta 1 - Linux/KGDB software breakpoint */ + {0, 0} /* Must be last */ +}; + +/* Set up exception handlers for tracing and breakpoints */ + +void +set_debug_traps(void) +{ + struct hard_trap_info *ht; + unsigned long flags; + + local_irq_save(flags); +#if 0 +/* Have to sort this out. This cannot be done after initialization. */ + BTFIXUPSET_CALL(flush_cache_all, flush_cache_all_nop, BTFIXUPCALL_NOP); +#endif + + /* Initialize our copy of the Linux Sparc trap table */ + eh_init(); + + for (ht = hard_trap_info; ht->tt && ht->signo; ht++) { + /* Only if it doesn't destroy our fault handlers */ + if((ht->tt != SP_TRAP_TFLT) && + (ht->tt != SP_TRAP_DFLT)) + exceptionHandler(ht->tt, trap_low); + } + + /* In case GDB is started before us, ack any packets (presumably + * "$?#xx") sitting there. + * + * I've found this code causes more problems than it solves, + * so that's why it's commented out. GDB seems to work fine + * now starting either before or after the kernel -bwb + */ +#if 0 + while((c = getDebugChar()) != '$'); + while((c = getDebugChar()) != '#'); + c = getDebugChar(); /* eat first csum byte */ + c = getDebugChar(); /* eat second csum byte */ + putDebugChar('+'); /* ack it */ +#endif + + initialized = 1; /* connect! */ + local_irq_restore(flags); +} + +/* Convert the SPARC hardware trap type code to a unix signal number. */ + +static int +computeSignal(int tt) +{ + struct hard_trap_info *ht; + + for (ht = hard_trap_info; ht->tt && ht->signo; ht++) + if (ht->tt == tt) + return ht->signo; + + return SIGHUP; /* default for things we don't know about */ +} + +/* + * While we find nice hex chars, build an int. + * Return number of chars processed. + */ + +static int +hexToInt(char **ptr, int *intValue) +{ + int numChars = 0; + int hexValue; + + *intValue = 0; + + while (**ptr) { + hexValue = hex(**ptr); + if (hexValue < 0) + break; + + *intValue = (*intValue << 4) | hexValue; + numChars ++; + + (*ptr)++; + } + + return (numChars); +} + +/* + * This function does all command processing for interfacing to gdb. It + * returns 1 if you should skip the instruction at the trap address, 0 + * otherwise. + */ + +extern void breakinst(void); + +void +handle_exception (unsigned long *registers) +{ + int tt; /* Trap type */ + int sigval; + int addr; + int length; + char *ptr; + unsigned long *sp; + + /* First, we must force all of the windows to be spilled out */ + + asm("save %sp, -64, %sp\n\t" + "save %sp, -64, %sp\n\t" + "save %sp, -64, %sp\n\t" + "save %sp, -64, %sp\n\t" + "save %sp, -64, %sp\n\t" + "save %sp, -64, %sp\n\t" + "save %sp, -64, %sp\n\t" + "save %sp, -64, %sp\n\t" + "restore\n\t" + "restore\n\t" + "restore\n\t" + "restore\n\t" + "restore\n\t" + "restore\n\t" + "restore\n\t" + "restore\n\t"); + + lock_kernel(); + if (registers[PC] == (unsigned long)breakinst) { + /* Skip over breakpoint trap insn */ + registers[PC] = registers[NPC]; + registers[NPC] += 4; + } + + sp = (unsigned long *)registers[SP]; + + tt = (registers[TBR] >> 4) & 0xff; + + /* reply to host that an exception has occurred */ + sigval = computeSignal(tt); + ptr = remcomOutBuffer; + + *ptr++ = 'T'; + *ptr++ = hexchars[sigval >> 4]; + *ptr++ = hexchars[sigval & 0xf]; + + *ptr++ = hexchars[PC >> 4]; + *ptr++ = hexchars[PC & 0xf]; + *ptr++ = ':'; + ptr = mem2hex((char *)®isters[PC], ptr, 4); + *ptr++ = ';'; + + *ptr++ = hexchars[FP >> 4]; + *ptr++ = hexchars[FP & 0xf]; + *ptr++ = ':'; + ptr = mem2hex((char *) (sp + 8 + 6), ptr, 4); /* FP */ + *ptr++ = ';'; + + *ptr++ = hexchars[SP >> 4]; + *ptr++ = hexchars[SP & 0xf]; + *ptr++ = ':'; + ptr = mem2hex((char *)&sp, ptr, 4); + *ptr++ = ';'; + + *ptr++ = hexchars[NPC >> 4]; + *ptr++ = hexchars[NPC & 0xf]; + *ptr++ = ':'; + ptr = mem2hex((char *)®isters[NPC], ptr, 4); + *ptr++ = ';'; + + *ptr++ = hexchars[O7 >> 4]; + *ptr++ = hexchars[O7 & 0xf]; + *ptr++ = ':'; + ptr = mem2hex((char *)®isters[O7], ptr, 4); + *ptr++ = ';'; + + *ptr++ = 0; + + putpacket(remcomOutBuffer); + + /* XXX We may want to add some features dealing with poking the + * XXX page tables, the real ones on the srmmu, and what is currently + * XXX loaded in the sun4/sun4c tlb at this point in time. But this + * XXX also required hacking to the gdb sources directly... + */ + + while (1) { + remcomOutBuffer[0] = 0; + + getpacket(remcomInBuffer); + switch (remcomInBuffer[0]) { + case '?': + remcomOutBuffer[0] = 'S'; + remcomOutBuffer[1] = hexchars[sigval >> 4]; + remcomOutBuffer[2] = hexchars[sigval & 0xf]; + remcomOutBuffer[3] = 0; + break; + + case 'd': + /* toggle debug flag */ + break; + + case 'g': /* return the value of the CPU registers */ + { + ptr = remcomOutBuffer; + /* G & O regs */ + ptr = mem2hex((char *)registers, ptr, 16 * 4); + /* L & I regs */ + ptr = mem2hex((char *) (sp + 0), ptr, 16 * 4); + /* Floating point */ + memset(ptr, '0', 32 * 8); + /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ + mem2hex((char *)®isters[Y], (ptr + 32 * 4 * 2), (8 * 4)); + } + break; + + case 'G': /* set the value of the CPU registers - return OK */ + { + unsigned long *newsp, psr; + + psr = registers[PSR]; + + ptr = &remcomInBuffer[1]; + /* G & O regs */ + hex2mem(ptr, (char *)registers, 16 * 4); + /* L & I regs */ + hex2mem(ptr + 16 * 4 * 2, (char *) (sp + 0), 16 * 4); + /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ + hex2mem(ptr + 64 * 4 * 2, (char *)®isters[Y], 8 * 4); + + /* See if the stack pointer has moved. If so, + * then copy the saved locals and ins to the + * new location. This keeps the window + * overflow and underflow routines happy. + */ + + newsp = (unsigned long *)registers[SP]; + if (sp != newsp) + sp = memcpy(newsp, sp, 16 * 4); + + /* Don't allow CWP to be modified. */ + + if (psr != registers[PSR]) + registers[PSR] = (psr & 0x1f) | (registers[PSR] & ~0x1f); + + strcpy(remcomOutBuffer,"OK"); + } + break; + + case 'm': /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ + /* Try to read %x,%x. */ + + ptr = &remcomInBuffer[1]; + + if (hexToInt(&ptr, &addr) + && *ptr++ == ',' + && hexToInt(&ptr, &length)) { + if (mem2hex((char *)addr, remcomOutBuffer, length)) + break; + + strcpy (remcomOutBuffer, "E03"); + } else { + strcpy(remcomOutBuffer,"E01"); + } + break; + + case 'M': /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */ + /* Try to read '%x,%x:'. */ + + ptr = &remcomInBuffer[1]; + + if (hexToInt(&ptr, &addr) + && *ptr++ == ',' + && hexToInt(&ptr, &length) + && *ptr++ == ':') { + if (hex2mem(ptr, (char *)addr, length)) { + strcpy(remcomOutBuffer, "OK"); + } else { + strcpy(remcomOutBuffer, "E03"); + } + } else { + strcpy(remcomOutBuffer, "E02"); + } + break; + + case 'c': /* cAA..AA Continue at address AA..AA(optional) */ + /* try to read optional parameter, pc unchanged if no parm */ + + ptr = &remcomInBuffer[1]; + if (hexToInt(&ptr, &addr)) { + registers[PC] = addr; + registers[NPC] = addr + 4; + } + +/* Need to flush the instruction cache here, as we may have deposited a + * breakpoint, and the icache probably has no way of knowing that a data ref to + * some location may have changed something that is in the instruction cache. + */ + flush_cache_all(); + unlock_kernel(); + return; + + /* kill the program */ + case 'k' : /* do nothing */ + break; + case 'r': /* Reset */ + asm ("call 0\n\t" + "nop\n\t"); + break; + } /* switch */ + + /* reply to the request */ + putpacket(remcomOutBuffer); + } /* while(1) */ +} + +/* This function will generate a breakpoint exception. It is used at the + beginning of a program to sync up with a debugger and can be used + otherwise as a quick means to stop program execution and "break" into + the debugger. */ + +void +breakpoint(void) +{ + if (!initialized) + return; + + /* Again, watch those c-prefixes for ELF kernels */ +#if defined(__svr4__) || defined(__ELF__) + asm(".globl breakinst\n" + "breakinst:\n\t" + "ta 1\n"); +#else + asm(".globl _breakinst\n" + "_breakinst:\n\t" + "ta 1\n"); +#endif +} diff --git a/arch/sparc/kernel/sparc_ksyms.c b/arch/sparc/kernel/sparc_ksyms.c new file mode 100644 index 000000000000..f91b0e8d0dc8 --- /dev/null +++ b/arch/sparc/kernel/sparc_ksyms.c @@ -0,0 +1,334 @@ +/* $Id: sparc_ksyms.c,v 1.107 2001/07/17 16:17:33 anton Exp $ + * arch/sparc/kernel/ksyms.c: Sparc specific ksyms support. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + */ + +/* Tell string.h we don't want memcpy etc. as cpp defines */ +#define EXPORT_SYMTAB_STROPS +#define PROMLIB_INTERNAL + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/in6.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#ifdef CONFIG_PCI +#include <linux/pci.h> +#endif +#include <linux/pm.h> +#ifdef CONFIG_HIGHMEM +#include <linux/highmem.h> +#endif + +#include <asm/oplib.h> +#include <asm/delay.h> +#include <asm/system.h> +#include <asm/auxio.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/idprom.h> +#include <asm/svr4.h> +#include <asm/head.h> +#include <asm/smp.h> +#include <asm/mostek.h> +#include <asm/ptrace.h> +#include <asm/user.h> +#include <asm/uaccess.h> +#include <asm/checksum.h> +#ifdef CONFIG_SBUS +#include <asm/sbus.h> +#include <asm/dma.h> +#endif +#ifdef CONFIG_PCI +#include <asm/ebus.h> +#endif +#include <asm/a.out.h> +#include <asm/io-unit.h> +#include <asm/bug.h> + +extern spinlock_t rtc_lock; + +struct poll { + int fd; + short events; + short revents; +}; + +extern int svr4_getcontext (svr4_ucontext_t *, struct pt_regs *); +extern int svr4_setcontext (svr4_ucontext_t *, struct pt_regs *); +void _sigpause_common (unsigned int set, struct pt_regs *); +extern void (*__copy_1page)(void *, const void *); +extern void __memmove(void *, const void *, __kernel_size_t); +extern void (*bzero_1page)(void *); +extern void *__bzero(void *, size_t); +extern void *__memscan_zero(void *, size_t); +extern void *__memscan_generic(void *, int, size_t); +extern int __memcmp(const void *, const void *, __kernel_size_t); +extern int __strncmp(const char *, const char *, __kernel_size_t); + +extern int __ashrdi3(int, int); +extern int __ashldi3(int, int); +extern int __lshrdi3(int, int); +extern int __muldi3(int, int); +extern int __divdi3(int, int); + +extern void dump_thread(struct pt_regs *, struct user *); + +/* Private functions with odd calling conventions. */ +extern void ___atomic24_add(void); +extern void ___atomic24_sub(void); +extern void ___set_bit(void); +extern void ___clear_bit(void); +extern void ___change_bit(void); + +/* Alias functions whose names begin with "." and export the aliases. + * The module references will be fixed up by module_frob_arch_sections. + */ +#define DOT_ALIAS2(__ret, __x, __arg1, __arg2) \ + extern __ret __x(__arg1, __arg2) \ + __attribute__((weak, alias("." # __x))); + +DOT_ALIAS2(int, div, int, int) +DOT_ALIAS2(int, mul, int, int) +DOT_ALIAS2(int, rem, int, int) +DOT_ALIAS2(unsigned, udiv, unsigned, unsigned) +DOT_ALIAS2(unsigned, umul, unsigned, unsigned) +DOT_ALIAS2(unsigned, urem, unsigned, unsigned) + +#undef DOT_ALIAS2 + +/* used by various drivers */ +EXPORT_SYMBOL(sparc_cpu_model); +EXPORT_SYMBOL(kernel_thread); +#ifdef CONFIG_DEBUG_SPINLOCK +#ifdef CONFIG_SMP +EXPORT_SYMBOL(_do_spin_lock); +EXPORT_SYMBOL(_do_spin_unlock); +EXPORT_SYMBOL(_spin_trylock); +EXPORT_SYMBOL(_do_read_lock); +EXPORT_SYMBOL(_do_read_unlock); +EXPORT_SYMBOL(_do_write_lock); +EXPORT_SYMBOL(_do_write_unlock); +#endif +#else +// XXX find what uses (or used) these. +// EXPORT_SYMBOL_PRIVATE(_rw_read_enter); +// EXPORT_SYMBOL_PRIVATE(_rw_read_exit); +// EXPORT_SYMBOL_PRIVATE(_rw_write_enter); +#endif +/* semaphores */ +EXPORT_SYMBOL(__up); +EXPORT_SYMBOL(__down); +EXPORT_SYMBOL(__down_trylock); +EXPORT_SYMBOL(__down_interruptible); + +EXPORT_SYMBOL(sparc_valid_addr_bitmap); +EXPORT_SYMBOL(phys_base); +EXPORT_SYMBOL(pfn_base); + +/* Atomic operations. */ +EXPORT_SYMBOL(___atomic24_add); +EXPORT_SYMBOL(___atomic24_sub); + +/* Bit operations. */ +EXPORT_SYMBOL(___set_bit); +EXPORT_SYMBOL(___clear_bit); +EXPORT_SYMBOL(___change_bit); + +#ifdef CONFIG_SMP +/* IRQ implementation. */ +EXPORT_SYMBOL(synchronize_irq); + +/* Misc SMP information */ +EXPORT_SYMBOL(__cpu_number_map); +EXPORT_SYMBOL(__cpu_logical_map); +#endif + +EXPORT_SYMBOL(__udelay); +EXPORT_SYMBOL(__ndelay); +EXPORT_SYMBOL(rtc_lock); +EXPORT_SYMBOL(mostek_lock); +EXPORT_SYMBOL(mstk48t02_regs); +#ifdef CONFIG_SUN_AUXIO +EXPORT_SYMBOL(set_auxio); +EXPORT_SYMBOL(get_auxio); +#endif +EXPORT_SYMBOL(request_fast_irq); +EXPORT_SYMBOL(io_remap_page_range); +EXPORT_SYMBOL(io_remap_pfn_range); + /* P3: iounit_xxx may be needed, sun4d users */ +/* EXPORT_SYMBOL(iounit_map_dma_init); */ +/* EXPORT_SYMBOL(iounit_map_dma_page); */ + +#ifndef CONFIG_SMP +EXPORT_SYMBOL(BTFIXUP_CALL(___xchg32)); +#else +EXPORT_SYMBOL(BTFIXUP_CALL(__hard_smp_processor_id)); +#endif +EXPORT_SYMBOL(BTFIXUP_CALL(enable_irq)); +EXPORT_SYMBOL(BTFIXUP_CALL(disable_irq)); +EXPORT_SYMBOL(BTFIXUP_CALL(__irq_itoa)); +EXPORT_SYMBOL(BTFIXUP_CALL(mmu_unlockarea)); +EXPORT_SYMBOL(BTFIXUP_CALL(mmu_lockarea)); +EXPORT_SYMBOL(BTFIXUP_CALL(mmu_get_scsi_sgl)); +EXPORT_SYMBOL(BTFIXUP_CALL(mmu_get_scsi_one)); +EXPORT_SYMBOL(BTFIXUP_CALL(mmu_release_scsi_sgl)); +EXPORT_SYMBOL(BTFIXUP_CALL(mmu_release_scsi_one)); + +#ifdef CONFIG_SBUS +EXPORT_SYMBOL(sbus_root); +EXPORT_SYMBOL(dma_chain); +EXPORT_SYMBOL(sbus_set_sbus64); +EXPORT_SYMBOL(sbus_alloc_consistent); +EXPORT_SYMBOL(sbus_free_consistent); +EXPORT_SYMBOL(sbus_map_single); +EXPORT_SYMBOL(sbus_unmap_single); +EXPORT_SYMBOL(sbus_map_sg); +EXPORT_SYMBOL(sbus_unmap_sg); +EXPORT_SYMBOL(sbus_dma_sync_single_for_cpu); +EXPORT_SYMBOL(sbus_dma_sync_single_for_device); +EXPORT_SYMBOL(sbus_dma_sync_sg_for_cpu); +EXPORT_SYMBOL(sbus_dma_sync_sg_for_device); +EXPORT_SYMBOL(sbus_iounmap); +EXPORT_SYMBOL(sbus_ioremap); +#endif +#ifdef CONFIG_PCI +EXPORT_SYMBOL(ebus_chain); +EXPORT_SYMBOL(insb); +EXPORT_SYMBOL(outsb); +EXPORT_SYMBOL(insw); +EXPORT_SYMBOL(outsw); +EXPORT_SYMBOL(insl); +EXPORT_SYMBOL(outsl); +EXPORT_SYMBOL(pci_alloc_consistent); +EXPORT_SYMBOL(pci_free_consistent); +EXPORT_SYMBOL(pci_map_single); +EXPORT_SYMBOL(pci_unmap_single); +EXPORT_SYMBOL(pci_dma_sync_single_for_cpu); +EXPORT_SYMBOL(pci_dma_sync_single_for_device); +EXPORT_SYMBOL(pci_dma_sync_sg_for_cpu); +EXPORT_SYMBOL(pci_dma_sync_sg_for_device); +EXPORT_SYMBOL(pci_map_sg); +EXPORT_SYMBOL(pci_unmap_sg); +EXPORT_SYMBOL(pci_map_page); +EXPORT_SYMBOL(pci_unmap_page); +/* Actually, ioremap/iounmap are not PCI specific. But it is ok for drivers. */ +EXPORT_SYMBOL(ioremap); +EXPORT_SYMBOL(iounmap); +#endif + +/* in arch/sparc/mm/highmem.c */ +#ifdef CONFIG_HIGHMEM +EXPORT_SYMBOL(kmap_atomic); +EXPORT_SYMBOL(kunmap_atomic); +#endif + +/* Solaris/SunOS binary compatibility */ +EXPORT_SYMBOL(svr4_setcontext); +EXPORT_SYMBOL(svr4_getcontext); +EXPORT_SYMBOL(_sigpause_common); + +EXPORT_SYMBOL(dump_thread); + +/* prom symbols */ +EXPORT_SYMBOL(idprom); +EXPORT_SYMBOL(prom_root_node); +EXPORT_SYMBOL(prom_getchild); +EXPORT_SYMBOL(prom_getsibling); +EXPORT_SYMBOL(prom_searchsiblings); +EXPORT_SYMBOL(prom_firstprop); +EXPORT_SYMBOL(prom_nextprop); +EXPORT_SYMBOL(prom_getproplen); +EXPORT_SYMBOL(prom_getproperty); +EXPORT_SYMBOL(prom_node_has_property); +EXPORT_SYMBOL(prom_setprop); +EXPORT_SYMBOL(saved_command_line); +EXPORT_SYMBOL(prom_apply_obio_ranges); +EXPORT_SYMBOL(prom_getname); +EXPORT_SYMBOL(prom_feval); +EXPORT_SYMBOL(prom_getbool); +EXPORT_SYMBOL(prom_getstring); +EXPORT_SYMBOL(prom_getint); +EXPORT_SYMBOL(prom_getintdefault); +EXPORT_SYMBOL(prom_finddevice); +EXPORT_SYMBOL(romvec); +EXPORT_SYMBOL(__prom_getchild); +EXPORT_SYMBOL(__prom_getsibling); + +/* sparc library symbols */ +EXPORT_SYMBOL(memchr); +EXPORT_SYMBOL(memscan); +EXPORT_SYMBOL(strlen); +EXPORT_SYMBOL(strnlen); +EXPORT_SYMBOL(strcpy); +EXPORT_SYMBOL(strncpy); +EXPORT_SYMBOL(strcat); +EXPORT_SYMBOL(strncat); +EXPORT_SYMBOL(strcmp); +EXPORT_SYMBOL(strncmp); +EXPORT_SYMBOL(strchr); +EXPORT_SYMBOL(strrchr); +EXPORT_SYMBOL(strpbrk); +EXPORT_SYMBOL(strstr); +EXPORT_SYMBOL(page_kernel); + +/* Special internal versions of library functions. */ +EXPORT_SYMBOL(__copy_1page); +EXPORT_SYMBOL(__memcpy); +EXPORT_SYMBOL(__memset); +EXPORT_SYMBOL(bzero_1page); +EXPORT_SYMBOL(__bzero); +EXPORT_SYMBOL(__memscan_zero); +EXPORT_SYMBOL(__memscan_generic); +EXPORT_SYMBOL(__memcmp); +EXPORT_SYMBOL(__strncmp); +EXPORT_SYMBOL(__memmove); + +/* Moving data to/from userspace. */ +EXPORT_SYMBOL(__copy_user); +EXPORT_SYMBOL(__strncpy_from_user); + +/* Networking helper routines. */ +EXPORT_SYMBOL(__csum_partial_copy_sparc_generic); +EXPORT_SYMBOL(csum_partial); + +/* Cache flushing. */ +EXPORT_SYMBOL(sparc_flush_page_to_ram); + +/* For when serial stuff is built as modules. */ +EXPORT_SYMBOL(sun_do_break); + +EXPORT_SYMBOL(__ret_efault); + +EXPORT_SYMBOL(memcmp); +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memset); +EXPORT_SYMBOL(memmove); +EXPORT_SYMBOL(__ashrdi3); +EXPORT_SYMBOL(__ashldi3); +EXPORT_SYMBOL(__lshrdi3); +EXPORT_SYMBOL(__muldi3); +EXPORT_SYMBOL(__divdi3); + +EXPORT_SYMBOL(rem); +EXPORT_SYMBOL(urem); +EXPORT_SYMBOL(mul); +EXPORT_SYMBOL(umul); +EXPORT_SYMBOL(div); +EXPORT_SYMBOL(udiv); + +#ifdef CONFIG_DEBUG_BUGVERBOSE +EXPORT_SYMBOL(do_BUG); +#endif + +/* Sun Power Management Idle Handler */ +EXPORT_SYMBOL(pm_idle); diff --git a/arch/sparc/kernel/sun4c_irq.c b/arch/sparc/kernel/sun4c_irq.c new file mode 100644 index 000000000000..3d6a99073c42 --- /dev/null +++ b/arch/sparc/kernel/sun4c_irq.c @@ -0,0 +1,250 @@ +/* sun4c_irq.c + * arch/sparc/kernel/sun4c_irq.c: + * + * djhr: Hacked out of irq.c into a CPU dependent version. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1995 Pete A. Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Dave Redman (djhr@tadpole.co.uk) + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/linkage.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/init.h> + +#include <asm/ptrace.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/psr.h> +#include <asm/vaddrs.h> +#include <asm/timer.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/traps.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/sun4paddr.h> +#include <asm/idprom.h> +#include <asm/machines.h> +#include <asm/sbus.h> + +#if 0 +static struct resource sun4c_timer_eb = { "sun4c_timer" }; +static struct resource sun4c_intr_eb = { "sun4c_intr" }; +#endif + +/* Pointer to the interrupt enable byte + * + * Dave Redman (djhr@tadpole.co.uk) + * What you may not be aware of is that entry.S requires this variable. + * + * --- linux_trap_nmi_sun4c -- + * + * so don't go making it static, like I tried. sigh. + */ +unsigned char *interrupt_enable = NULL; + +static int sun4c_pil_map[] = { 0, 1, 2, 3, 5, 7, 8, 9 }; + +unsigned int sun4c_sbint_to_irq(struct sbus_dev *sdev, unsigned int sbint) +{ + if (sbint >= sizeof(sun4c_pil_map)) { + printk(KERN_ERR "%s: bogus SBINT %d\n", sdev->prom_name, sbint); + BUG(); + } + return sun4c_pil_map[sbint]; +} + +static void sun4c_disable_irq(unsigned int irq_nr) +{ + unsigned long flags; + unsigned char current_mask, new_mask; + + local_irq_save(flags); + irq_nr &= (NR_IRQS - 1); + current_mask = *interrupt_enable; + switch(irq_nr) { + case 1: + new_mask = ((current_mask) & (~(SUN4C_INT_E1))); + break; + case 8: + new_mask = ((current_mask) & (~(SUN4C_INT_E8))); + break; + case 10: + new_mask = ((current_mask) & (~(SUN4C_INT_E10))); + break; + case 14: + new_mask = ((current_mask) & (~(SUN4C_INT_E14))); + break; + default: + local_irq_restore(flags); + return; + } + *interrupt_enable = new_mask; + local_irq_restore(flags); +} + +static void sun4c_enable_irq(unsigned int irq_nr) +{ + unsigned long flags; + unsigned char current_mask, new_mask; + + local_irq_save(flags); + irq_nr &= (NR_IRQS - 1); + current_mask = *interrupt_enable; + switch(irq_nr) { + case 1: + new_mask = ((current_mask) | SUN4C_INT_E1); + break; + case 8: + new_mask = ((current_mask) | SUN4C_INT_E8); + break; + case 10: + new_mask = ((current_mask) | SUN4C_INT_E10); + break; + case 14: + new_mask = ((current_mask) | SUN4C_INT_E14); + break; + default: + local_irq_restore(flags); + return; + } + *interrupt_enable = new_mask; + local_irq_restore(flags); +} + +#define TIMER_IRQ 10 /* Also at level 14, but we ignore that one. */ +#define PROFILE_IRQ 14 /* Level14 ticker.. used by OBP for polling */ + +volatile struct sun4c_timer_info *sun4c_timers; + +#ifdef CONFIG_SUN4 +/* This is an ugly hack to work around the + current timer code, and make it work with + the sun4/260 intersil + */ +volatile struct sun4c_timer_info sun4_timer; +#endif + +static void sun4c_clear_clock_irq(void) +{ + volatile unsigned int clear_intr; +#ifdef CONFIG_SUN4 + if (idprom->id_machtype == (SM_SUN4 | SM_4_260)) + clear_intr = sun4_timer.timer_limit10; + else +#endif + clear_intr = sun4c_timers->timer_limit10; +} + +static void sun4c_clear_profile_irq(int cpu) +{ + /* Errm.. not sure how to do this.. */ +} + +static void sun4c_load_profile_irq(int cpu, unsigned int limit) +{ + /* Errm.. not sure how to do this.. */ +} + +static void __init sun4c_init_timers(irqreturn_t (*counter_fn)(int, void *, struct pt_regs *)) +{ + int irq; + + /* Map the Timer chip, this is implemented in hardware inside + * the cache chip on the sun4c. + */ +#ifdef CONFIG_SUN4 + if (idprom->id_machtype == (SM_SUN4 | SM_4_260)) + sun4c_timers = &sun4_timer; + else +#endif + sun4c_timers = ioremap(SUN_TIMER_PHYSADDR, + sizeof(struct sun4c_timer_info)); + + /* Have the level 10 timer tick at 100HZ. We don't touch the + * level 14 timer limit since we are letting the prom handle + * them until we have a real console driver so L1-A works. + */ + sun4c_timers->timer_limit10 = (((1000000/HZ) + 1) << 10); + master_l10_counter = &sun4c_timers->cur_count10; + master_l10_limit = &sun4c_timers->timer_limit10; + + irq = request_irq(TIMER_IRQ, + counter_fn, + (SA_INTERRUPT | SA_STATIC_ALLOC), + "timer", NULL); + if (irq) { + prom_printf("time_init: unable to attach IRQ%d\n",TIMER_IRQ); + prom_halt(); + } + +#if 0 + /* This does not work on 4/330 */ + sun4c_enable_irq(10); +#endif + claim_ticker14(NULL, PROFILE_IRQ, 0); +} + +#ifdef CONFIG_SMP +static void sun4c_nop(void) {} +#endif + +extern char *sun4m_irq_itoa(unsigned int irq); + +void __init sun4c_init_IRQ(void) +{ + struct linux_prom_registers int_regs[2]; + int ie_node; + + if (ARCH_SUN4) { + interrupt_enable = (char *) + ioremap(sun4_ie_physaddr, PAGE_SIZE); + } else { + struct resource phyres; + + ie_node = prom_searchsiblings (prom_getchild(prom_root_node), + "interrupt-enable"); + if(ie_node == 0) + panic("Cannot find /interrupt-enable node"); + + /* Depending on the "address" property is bad news... */ + interrupt_enable = NULL; + if (prom_getproperty(ie_node, "reg", (char *) int_regs, + sizeof(int_regs)) != -1) { + memset(&phyres, 0, sizeof(struct resource)); + phyres.flags = int_regs[0].which_io; + phyres.start = int_regs[0].phys_addr; + interrupt_enable = (char *) sbus_ioremap(&phyres, 0, + int_regs[0].reg_size, "sun4c_intr"); + } + } + if (!interrupt_enable) + panic("Cannot map interrupt_enable"); + + BTFIXUPSET_CALL(sbint_to_irq, sun4c_sbint_to_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(enable_irq, sun4c_enable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(disable_irq, sun4c_disable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(enable_pil_irq, sun4c_enable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(disable_pil_irq, sun4c_disable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_clock_irq, sun4c_clear_clock_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_profile_irq, sun4c_clear_profile_irq, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(load_profile_irq, sun4c_load_profile_irq, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(__irq_itoa, sun4m_irq_itoa, BTFIXUPCALL_NORM); + sparc_init_timers = sun4c_init_timers; +#ifdef CONFIG_SMP + BTFIXUPSET_CALL(set_cpu_int, sun4c_nop, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(clear_cpu_int, sun4c_nop, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(set_irq_udt, sun4c_nop, BTFIXUPCALL_NOP); +#endif + *interrupt_enable = (SUN4C_INT_ENABLE); + /* Cannot enable interrupts until OBP ticker is disabled. */ +} diff --git a/arch/sparc/kernel/sun4d_irq.c b/arch/sparc/kernel/sun4d_irq.c new file mode 100644 index 000000000000..52621348a56c --- /dev/null +++ b/arch/sparc/kernel/sun4d_irq.c @@ -0,0 +1,594 @@ +/* $Id: sun4d_irq.c,v 1.29 2001/12/11 04:55:51 davem Exp $ + * arch/sparc/kernel/sun4d_irq.c: + * SS1000/SC2000 interrupt handling. + * + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Heavily based on arch/sparc/kernel/irq.c. + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/linkage.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/spinlock.h> +#include <linux/seq_file.h> + +#include <asm/ptrace.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/psr.h> +#include <asm/smp.h> +#include <asm/vaddrs.h> +#include <asm/timer.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/traps.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/sbus.h> +#include <asm/sbi.h> +#include <asm/cacheflush.h> + +/* If you trust current SCSI layer to handle different SCSI IRQs, enable this. I don't trust it... -jj */ +/* #define DISTRIBUTE_IRQS */ + +struct sun4d_timer_regs *sun4d_timers; +#define TIMER_IRQ 10 + +#define MAX_STATIC_ALLOC 4 +extern struct irqaction static_irqaction[MAX_STATIC_ALLOC]; +extern int static_irq_count; +unsigned char cpu_leds[32]; +#ifdef CONFIG_SMP +unsigned char sbus_tid[32]; +#endif + +extern struct irqaction *irq_action[]; +extern spinlock_t irq_action_lock; + +struct sbus_action { + struct irqaction *action; + /* For SMP this needs to be extended */ +} *sbus_actions; + +static int pil_to_sbus[] = { + 0, 0, 1, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 0, +}; + +static int sbus_to_pil[] = { + 0, 2, 3, 5, 7, 9, 11, 13, +}; + +static int nsbi; +#ifdef CONFIG_SMP +DEFINE_SPINLOCK(sun4d_imsk_lock); +#endif + +int show_sun4d_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v, j = 0, k = 0, sbusl; + struct irqaction * action; + unsigned long flags; +#ifdef CONFIG_SMP + int x; +#endif + + spin_lock_irqsave(&irq_action_lock, flags); + if (i < NR_IRQS) { + sbusl = pil_to_sbus[i]; + if (!sbusl) { + action = *(i + irq_action); + if (!action) + goto out_unlock; + } else { + for (j = 0; j < nsbi; j++) { + for (k = 0; k < 4; k++) + if ((action = sbus_actions [(j << 5) + (sbusl << 2) + k].action)) + goto found_it; + } + goto out_unlock; + } +found_it: seq_printf(p, "%3d: ", i); +#ifndef CONFIG_SMP + seq_printf(p, "%10u ", kstat_irqs(i)); +#else + for (x = 0; x < NR_CPUS; x++) { + if (cpu_online(x)) + seq_printf(p, "%10u ", + kstat_cpu(cpu_logical_map(x)).irqs[i]); + } +#endif + seq_printf(p, "%c %s", + (action->flags & SA_INTERRUPT) ? '+' : ' ', + action->name); + action = action->next; + for (;;) { + for (; action; action = action->next) { + seq_printf(p, ",%s %s", + (action->flags & SA_INTERRUPT) ? " +" : "", + action->name); + } + if (!sbusl) break; + k++; + if (k < 4) + action = sbus_actions [(j << 5) + (sbusl << 2) + k].action; + else { + j++; + if (j == nsbi) break; + k = 0; + action = sbus_actions [(j << 5) + (sbusl << 2)].action; + } + } + seq_putc(p, '\n'); + } +out_unlock: + spin_unlock_irqrestore(&irq_action_lock, flags); + return 0; +} + +void sun4d_free_irq(unsigned int irq, void *dev_id) +{ + struct irqaction *action, **actionp; + struct irqaction *tmp = NULL; + unsigned long flags; + + spin_lock_irqsave(&irq_action_lock, flags); + if (irq < 15) + actionp = irq + irq_action; + else + actionp = &(sbus_actions[irq - (1 << 5)].action); + action = *actionp; + if (!action) { + printk("Trying to free free IRQ%d\n",irq); + goto out_unlock; + } + if (dev_id) { + for (; action; action = action->next) { + if (action->dev_id == dev_id) + break; + tmp = action; + } + if (!action) { + printk("Trying to free free shared IRQ%d\n",irq); + goto out_unlock; + } + } else if (action->flags & SA_SHIRQ) { + printk("Trying to free shared IRQ%d with NULL device ID\n", irq); + goto out_unlock; + } + if (action->flags & SA_STATIC_ALLOC) + { + /* This interrupt is marked as specially allocated + * so it is a bad idea to free it. + */ + printk("Attempt to free statically allocated IRQ%d (%s)\n", + irq, action->name); + goto out_unlock; + } + + if (action && tmp) + tmp->next = action->next; + else + *actionp = action->next; + + spin_unlock_irqrestore(&irq_action_lock, flags); + + synchronize_irq(irq); + + spin_lock_irqsave(&irq_action_lock, flags); + + kfree(action); + + if (!(*actionp)) + disable_irq(irq); + +out_unlock: + spin_unlock_irqrestore(&irq_action_lock, flags); +} + +extern void unexpected_irq(int, void *, struct pt_regs *); + +void sun4d_handler_irq(int irq, struct pt_regs * regs) +{ + struct irqaction * action; + int cpu = smp_processor_id(); + /* SBUS IRQ level (1 - 7) */ + int sbusl = pil_to_sbus[irq]; + + /* FIXME: Is this necessary?? */ + cc_get_ipen(); + + cc_set_iclr(1 << irq); + + irq_enter(); + kstat_cpu(cpu).irqs[irq]++; + if (!sbusl) { + action = *(irq + irq_action); + if (!action) + unexpected_irq(irq, NULL, regs); + do { + action->handler(irq, action->dev_id, regs); + action = action->next; + } while (action); + } else { + int bus_mask = bw_get_intr_mask(sbusl) & 0x3ffff; + int sbino; + struct sbus_action *actionp; + unsigned mask, slot; + int sbil = (sbusl << 2); + + bw_clear_intr_mask(sbusl, bus_mask); + + /* Loop for each pending SBI */ + for (sbino = 0; bus_mask; sbino++, bus_mask >>= 1) + if (bus_mask & 1) { + mask = acquire_sbi(SBI2DEVID(sbino), 0xf << sbil); + mask &= (0xf << sbil); + actionp = sbus_actions + (sbino << 5) + (sbil); + /* Loop for each pending SBI slot */ + for (slot = (1 << sbil); mask; slot <<= 1, actionp++) + if (mask & slot) { + mask &= ~slot; + action = actionp->action; + + if (!action) + unexpected_irq(irq, NULL, regs); + do { + action->handler(irq, action->dev_id, regs); + action = action->next; + } while (action); + release_sbi(SBI2DEVID(sbino), slot); + } + } + } + irq_exit(); +} + +unsigned int sun4d_build_irq(struct sbus_dev *sdev, int irq) +{ + int sbusl = pil_to_sbus[irq]; + + if (sbusl) + return ((sdev->bus->board + 1) << 5) + (sbusl << 2) + sdev->slot; + else + return irq; +} + +unsigned int sun4d_sbint_to_irq(struct sbus_dev *sdev, unsigned int sbint) +{ + if (sbint >= sizeof(sbus_to_pil)) { + printk(KERN_ERR "%s: bogus SBINT %d\n", sdev->prom_name, sbint); + BUG(); + } + return sun4d_build_irq(sdev, sbus_to_pil[sbint]); +} + +int sun4d_request_irq(unsigned int irq, + irqreturn_t (*handler)(int, void *, struct pt_regs *), + unsigned long irqflags, const char * devname, void *dev_id) +{ + struct irqaction *action, *tmp = NULL, **actionp; + unsigned long flags; + int ret; + + if(irq > 14 && irq < (1 << 5)) { + ret = -EINVAL; + goto out; + } + + if (!handler) { + ret = -EINVAL; + goto out; + } + + spin_lock_irqsave(&irq_action_lock, flags); + + if (irq >= (1 << 5)) + actionp = &(sbus_actions[irq - (1 << 5)].action); + else + actionp = irq + irq_action; + action = *actionp; + + if (action) { + if ((action->flags & SA_SHIRQ) && (irqflags & SA_SHIRQ)) { + for (tmp = action; tmp->next; tmp = tmp->next); + } else { + ret = -EBUSY; + goto out_unlock; + } + if ((action->flags & SA_INTERRUPT) ^ (irqflags & SA_INTERRUPT)) { + printk("Attempt to mix fast and slow interrupts on IRQ%d denied\n", irq); + ret = -EBUSY; + goto out_unlock; + } + action = NULL; /* Or else! */ + } + + /* If this is flagged as statically allocated then we use our + * private struct which is never freed. + */ + if (irqflags & SA_STATIC_ALLOC) { + if (static_irq_count < MAX_STATIC_ALLOC) + action = &static_irqaction[static_irq_count++]; + else + printk("Request for IRQ%d (%s) SA_STATIC_ALLOC failed using kmalloc\n", irq, devname); + } + + if (action == NULL) + action = (struct irqaction *)kmalloc(sizeof(struct irqaction), + GFP_ATOMIC); + + if (!action) { + ret = -ENOMEM; + goto out_unlock; + } + + action->handler = handler; + action->flags = irqflags; + cpus_clear(action->mask); + action->name = devname; + action->next = NULL; + action->dev_id = dev_id; + + if (tmp) + tmp->next = action; + else + *actionp = action; + + enable_irq(irq); + + ret = 0; +out_unlock: + spin_unlock_irqrestore(&irq_action_lock, flags); +out: + return ret; +} + +static void sun4d_disable_irq(unsigned int irq) +{ +#ifdef CONFIG_SMP + int tid = sbus_tid[(irq >> 5) - 1]; + unsigned long flags; +#endif + + if (irq < NR_IRQS) return; +#ifdef CONFIG_SMP + spin_lock_irqsave(&sun4d_imsk_lock, flags); + cc_set_imsk_other(tid, cc_get_imsk_other(tid) | (1 << sbus_to_pil[(irq >> 2) & 7])); + spin_unlock_irqrestore(&sun4d_imsk_lock, flags); +#else + cc_set_imsk(cc_get_imsk() | (1 << sbus_to_pil[(irq >> 2) & 7])); +#endif +} + +static void sun4d_enable_irq(unsigned int irq) +{ +#ifdef CONFIG_SMP + int tid = sbus_tid[(irq >> 5) - 1]; + unsigned long flags; +#endif + + if (irq < NR_IRQS) return; +#ifdef CONFIG_SMP + spin_lock_irqsave(&sun4d_imsk_lock, flags); + cc_set_imsk_other(tid, cc_get_imsk_other(tid) & ~(1 << sbus_to_pil[(irq >> 2) & 7])); + spin_unlock_irqrestore(&sun4d_imsk_lock, flags); +#else + cc_set_imsk(cc_get_imsk() & ~(1 << sbus_to_pil[(irq >> 2) & 7])); +#endif +} + +#ifdef CONFIG_SMP +static void sun4d_set_cpu_int(int cpu, int level) +{ + sun4d_send_ipi(cpu, level); +} + +static void sun4d_clear_ipi(int cpu, int level) +{ +} + +static void sun4d_set_udt(int cpu) +{ +} + +/* Setup IRQ distribution scheme. */ +void __init sun4d_distribute_irqs(void) +{ +#ifdef DISTRIBUTE_IRQS + struct sbus_bus *sbus; + unsigned long sbus_serving_map; + + sbus_serving_map = cpu_present_map; + for_each_sbus(sbus) { + if ((sbus->board * 2) == boot_cpu_id && (cpu_present_map & (1 << (sbus->board * 2 + 1)))) + sbus_tid[sbus->board] = (sbus->board * 2 + 1); + else if (cpu_present_map & (1 << (sbus->board * 2))) + sbus_tid[sbus->board] = (sbus->board * 2); + else if (cpu_present_map & (1 << (sbus->board * 2 + 1))) + sbus_tid[sbus->board] = (sbus->board * 2 + 1); + else + sbus_tid[sbus->board] = 0xff; + if (sbus_tid[sbus->board] != 0xff) + sbus_serving_map &= ~(1 << sbus_tid[sbus->board]); + } + for_each_sbus(sbus) + if (sbus_tid[sbus->board] == 0xff) { + int i = 31; + + if (!sbus_serving_map) + sbus_serving_map = cpu_present_map; + while (!(sbus_serving_map & (1 << i))) + i--; + sbus_tid[sbus->board] = i; + sbus_serving_map &= ~(1 << i); + } + for_each_sbus(sbus) { + printk("sbus%d IRQs directed to CPU%d\n", sbus->board, sbus_tid[sbus->board]); + set_sbi_tid(sbus->devid, sbus_tid[sbus->board] << 3); + } +#else + struct sbus_bus *sbus; + int cpuid = cpu_logical_map(1); + + if (cpuid == -1) + cpuid = cpu_logical_map(0); + for_each_sbus(sbus) { + sbus_tid[sbus->board] = cpuid; + set_sbi_tid(sbus->devid, cpuid << 3); + } + printk("All sbus IRQs directed to CPU%d\n", cpuid); +#endif +} +#endif + +static void sun4d_clear_clock_irq(void) +{ + volatile unsigned int clear_intr; + clear_intr = sun4d_timers->l10_timer_limit; +} + +static void sun4d_clear_profile_irq(int cpu) +{ + bw_get_prof_limit(cpu); +} + +static void sun4d_load_profile_irq(int cpu, unsigned int limit) +{ + bw_set_prof_limit(cpu, limit); +} + +static void __init sun4d_init_timers(irqreturn_t (*counter_fn)(int, void *, struct pt_regs *)) +{ + int irq; + int cpu; + struct resource r; + int mid; + + /* Map the User Timer registers. */ + memset(&r, 0, sizeof(r)); +#ifdef CONFIG_SMP + r.start = CSR_BASE(boot_cpu_id)+BW_TIMER_LIMIT; +#else + r.start = CSR_BASE(0)+BW_TIMER_LIMIT; +#endif + r.flags = 0xf; + sun4d_timers = (struct sun4d_timer_regs *) sbus_ioremap(&r, 0, + PAGE_SIZE, "user timer"); + + sun4d_timers->l10_timer_limit = (((1000000/HZ) + 1) << 10); + master_l10_counter = &sun4d_timers->l10_cur_count; + master_l10_limit = &sun4d_timers->l10_timer_limit; + + irq = request_irq(TIMER_IRQ, + counter_fn, + (SA_INTERRUPT | SA_STATIC_ALLOC), + "timer", NULL); + if (irq) { + prom_printf("time_init: unable to attach IRQ%d\n",TIMER_IRQ); + prom_halt(); + } + + /* Enable user timer free run for CPU 0 in BW */ + /* bw_set_ctrl(0, bw_get_ctrl(0) | BW_CTRL_USER_TIMER); */ + + cpu = 0; + while (!cpu_find_by_instance(cpu, NULL, &mid)) { + sun4d_load_profile_irq(mid >> 3, 0); + cpu++; + } + +#ifdef CONFIG_SMP + { + unsigned long flags; + extern unsigned long lvl14_save[4]; + struct tt_entry *trap_table = &sparc_ttable[SP_TRAP_IRQ1 + (14 - 1)]; + extern unsigned int real_irq_entry[], smp4d_ticker[]; + extern unsigned int patchme_maybe_smp_msg[]; + + /* Adjust so that we jump directly to smp4d_ticker */ + lvl14_save[2] += smp4d_ticker - real_irq_entry; + + /* For SMP we use the level 14 ticker, however the bootup code + * has copied the firmwares level 14 vector into boot cpu's + * trap table, we must fix this now or we get squashed. + */ + local_irq_save(flags); + patchme_maybe_smp_msg[0] = 0x01000000; /* NOP out the branch */ + trap_table->inst_one = lvl14_save[0]; + trap_table->inst_two = lvl14_save[1]; + trap_table->inst_three = lvl14_save[2]; + trap_table->inst_four = lvl14_save[3]; + local_flush_cache_all(); + local_irq_restore(flags); + } +#endif +} + +void __init sun4d_init_sbi_irq(void) +{ + struct sbus_bus *sbus; + unsigned mask; + + nsbi = 0; + for_each_sbus(sbus) + nsbi++; + sbus_actions = (struct sbus_action *)kmalloc (nsbi * 8 * 4 * sizeof(struct sbus_action), GFP_ATOMIC); + memset (sbus_actions, 0, (nsbi * 8 * 4 * sizeof(struct sbus_action))); + for_each_sbus(sbus) { +#ifdef CONFIG_SMP + extern unsigned char boot_cpu_id; + + set_sbi_tid(sbus->devid, boot_cpu_id << 3); + sbus_tid[sbus->board] = boot_cpu_id; +#endif + /* Get rid of pending irqs from PROM */ + mask = acquire_sbi(sbus->devid, 0xffffffff); + if (mask) { + printk ("Clearing pending IRQs %08x on SBI %d\n", mask, sbus->board); + release_sbi(sbus->devid, mask); + } + } +} + +static char *sun4d_irq_itoa(unsigned int irq) +{ + static char buff[16]; + + if (irq < (1 << 5)) + sprintf(buff, "%d", irq); + else + sprintf(buff, "%d,%x", sbus_to_pil[(irq >> 2) & 7], irq); + return buff; +} + +void __init sun4d_init_IRQ(void) +{ + local_irq_disable(); + + BTFIXUPSET_CALL(sbint_to_irq, sun4d_sbint_to_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(enable_irq, sun4d_enable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(disable_irq, sun4d_disable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_clock_irq, sun4d_clear_clock_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_profile_irq, sun4d_clear_profile_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(load_profile_irq, sun4d_load_profile_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__irq_itoa, sun4d_irq_itoa, BTFIXUPCALL_NORM); + sparc_init_timers = sun4d_init_timers; +#ifdef CONFIG_SMP + BTFIXUPSET_CALL(set_cpu_int, sun4d_set_cpu_int, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_cpu_int, sun4d_clear_ipi, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(set_irq_udt, sun4d_set_udt, BTFIXUPCALL_NOP); +#endif + /* Cannot enable interrupts until OBP ticker is disabled. */ +} diff --git a/arch/sparc/kernel/sun4d_smp.c b/arch/sparc/kernel/sun4d_smp.c new file mode 100644 index 000000000000..cc1fc898495c --- /dev/null +++ b/arch/sparc/kernel/sun4d_smp.c @@ -0,0 +1,486 @@ +/* sun4d_smp.c: Sparc SS1000/SC2000 SMP support. + * + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * + * Based on sun4m's smp.c, which is: + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/head.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/threads.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/profile.h> + +#include <asm/ptrace.h> +#include <asm/atomic.h> + +#include <asm/delay.h> +#include <asm/irq.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/oplib.h> +#include <asm/sbus.h> +#include <asm/sbi.h> +#include <asm/tlbflush.h> +#include <asm/cacheflush.h> +#include <asm/cpudata.h> + +#define IRQ_CROSS_CALL 15 + +extern ctxd_t *srmmu_ctx_table_phys; + +extern void calibrate_delay(void); + +extern volatile int smp_processors_ready; +extern int smp_num_cpus; +static int smp_highest_cpu; +extern volatile unsigned long cpu_callin_map[NR_CPUS]; +extern struct cpuinfo_sparc cpu_data[NR_CPUS]; +extern unsigned char boot_cpu_id; +extern int smp_activated; +extern volatile int __cpu_number_map[NR_CPUS]; +extern volatile int __cpu_logical_map[NR_CPUS]; +extern volatile unsigned long ipi_count; +extern volatile int smp_process_available; +extern volatile int smp_commenced; +extern int __smp4d_processor_id(void); + +/* #define SMP_DEBUG */ + +#ifdef SMP_DEBUG +#define SMP_PRINTK(x) printk x +#else +#define SMP_PRINTK(x) +#endif + +static inline unsigned long swap(volatile unsigned long *ptr, unsigned long val) +{ + __asm__ __volatile__("swap [%1], %0\n\t" : + "=&r" (val), "=&r" (ptr) : + "0" (val), "1" (ptr)); + return val; +} + +static void smp_setup_percpu_timer(void); +extern void cpu_probe(void); +extern void sun4d_distribute_irqs(void); + +void __init smp4d_callin(void) +{ + int cpuid = hard_smp4d_processor_id(); + extern spinlock_t sun4d_imsk_lock; + unsigned long flags; + + /* Show we are alive */ + cpu_leds[cpuid] = 0x6; + show_leds(cpuid); + + /* Enable level15 interrupt, disable level14 interrupt for now */ + cc_set_imsk((cc_get_imsk() & ~0x8000) | 0x4000); + + local_flush_cache_all(); + local_flush_tlb_all(); + + /* + * Unblock the master CPU _only_ when the scheduler state + * of all secondary CPUs will be up-to-date, so after + * the SMP initialization the master will be just allowed + * to call the scheduler code. + */ + /* Get our local ticker going. */ + smp_setup_percpu_timer(); + + calibrate_delay(); + smp_store_cpu_info(cpuid); + local_flush_cache_all(); + local_flush_tlb_all(); + + /* Allow master to continue. */ + swap((unsigned long *)&cpu_callin_map[cpuid], 1); + local_flush_cache_all(); + local_flush_tlb_all(); + + cpu_probe(); + + while((unsigned long)current_set[cpuid] < PAGE_OFFSET) + barrier(); + + while(current_set[cpuid]->cpu != cpuid) + barrier(); + + /* Fix idle thread fields. */ + __asm__ __volatile__("ld [%0], %%g6\n\t" + : : "r" (¤t_set[cpuid]) + : "memory" /* paranoid */); + + cpu_leds[cpuid] = 0x9; + show_leds(cpuid); + + /* Attach to the address space of init_task. */ + atomic_inc(&init_mm.mm_count); + current->active_mm = &init_mm; + + local_flush_cache_all(); + local_flush_tlb_all(); + + local_irq_enable(); /* We don't allow PIL 14 yet */ + + while(!smp_commenced) + barrier(); + + spin_lock_irqsave(&sun4d_imsk_lock, flags); + cc_set_imsk(cc_get_imsk() & ~0x4000); /* Allow PIL 14 as well */ + spin_unlock_irqrestore(&sun4d_imsk_lock, flags); +} + +extern void init_IRQ(void); +extern void cpu_panic(void); + +/* + * Cycle through the processors asking the PROM to start each one. + */ + +extern struct linux_prom_registers smp_penguin_ctable; +extern unsigned long trapbase_cpu1[]; +extern unsigned long trapbase_cpu2[]; +extern unsigned long trapbase_cpu3[]; + +void __init smp4d_boot_cpus(void) +{ + int cpucount = 0; + int i, mid; + + printk("Entering SMP Mode...\n"); + + if (boot_cpu_id) + current_set[0] = NULL; + + local_irq_enable(); + cpus_clear(cpu_present_map); + + /* XXX This whole thing has to go. See sparc64. */ + for (i = 0; !cpu_find_by_instance(i, NULL, &mid); i++) + cpu_set(mid, cpu_present_map); + SMP_PRINTK(("cpu_present_map %08lx\n", cpus_addr(cpu_present_map)[0])); + for(i=0; i < NR_CPUS; i++) + __cpu_number_map[i] = -1; + for(i=0; i < NR_CPUS; i++) + __cpu_logical_map[i] = -1; + __cpu_number_map[boot_cpu_id] = 0; + __cpu_logical_map[0] = boot_cpu_id; + current_thread_info()->cpu = boot_cpu_id; + smp_store_cpu_info(boot_cpu_id); + smp_setup_percpu_timer(); + local_flush_cache_all(); + if (cpu_find_by_instance(1, NULL, NULL)) + return; /* Not an MP box. */ + SMP_PRINTK(("Iterating over CPUs\n")); + for(i = 0; i < NR_CPUS; i++) { + if(i == boot_cpu_id) + continue; + + if (cpu_isset(i, cpu_present_map)) { + extern unsigned long sun4d_cpu_startup; + unsigned long *entry = &sun4d_cpu_startup; + struct task_struct *p; + int timeout; + int no; + + /* Cook up an idler for this guy. */ + p = fork_idle(i); + cpucount++; + current_set[i] = p->thread_info; + for (no = 0; !cpu_find_by_instance(no, NULL, &mid) + && mid != i; no++) ; + + /* + * Initialize the contexts table + * Since the call to prom_startcpu() trashes the structure, + * we need to re-initialize it for each cpu + */ + smp_penguin_ctable.which_io = 0; + smp_penguin_ctable.phys_addr = (unsigned int) srmmu_ctx_table_phys; + smp_penguin_ctable.reg_size = 0; + + /* whirrr, whirrr, whirrrrrrrrr... */ + SMP_PRINTK(("Starting CPU %d at %p task %d node %08x\n", i, entry, cpucount, cpu_data(no).prom_node)); + local_flush_cache_all(); + prom_startcpu(cpu_data(no).prom_node, + &smp_penguin_ctable, 0, (char *)entry); + + SMP_PRINTK(("prom_startcpu returned :)\n")); + + /* wheee... it's going... */ + for(timeout = 0; timeout < 10000; timeout++) { + if(cpu_callin_map[i]) + break; + udelay(200); + } + + if(cpu_callin_map[i]) { + /* Another "Red Snapper". */ + __cpu_number_map[i] = cpucount; + __cpu_logical_map[cpucount] = i; + } else { + cpucount--; + printk("Processor %d is stuck.\n", i); + } + } + if(!(cpu_callin_map[i])) { + cpu_clear(i, cpu_present_map); + __cpu_number_map[i] = -1; + } + } + local_flush_cache_all(); + if(cpucount == 0) { + printk("Error: only one Processor found.\n"); + cpu_present_map = cpumask_of_cpu(hard_smp4d_processor_id()); + } else { + unsigned long bogosum = 0; + + for(i = 0; i < NR_CPUS; i++) { + if (cpu_isset(i, cpu_present_map)) { + bogosum += cpu_data(i).udelay_val; + smp_highest_cpu = i; + } + } + SMP_PRINTK(("Total of %d Processors activated (%lu.%02lu BogoMIPS).\n", cpucount + 1, bogosum/(500000/HZ), (bogosum/(5000/HZ))%100)); + printk("Total of %d Processors activated (%lu.%02lu BogoMIPS).\n", + cpucount + 1, + bogosum/(500000/HZ), + (bogosum/(5000/HZ))%100); + smp_activated = 1; + smp_num_cpus = cpucount + 1; + } + + /* Free unneeded trap tables */ + ClearPageReserved(virt_to_page(trapbase_cpu1)); + set_page_count(virt_to_page(trapbase_cpu1), 1); + free_page((unsigned long)trapbase_cpu1); + totalram_pages++; + num_physpages++; + + ClearPageReserved(virt_to_page(trapbase_cpu2)); + set_page_count(virt_to_page(trapbase_cpu2), 1); + free_page((unsigned long)trapbase_cpu2); + totalram_pages++; + num_physpages++; + + ClearPageReserved(virt_to_page(trapbase_cpu3)); + set_page_count(virt_to_page(trapbase_cpu3), 1); + free_page((unsigned long)trapbase_cpu3); + totalram_pages++; + num_physpages++; + + /* Ok, they are spinning and ready to go. */ + smp_processors_ready = 1; + sun4d_distribute_irqs(); +} + +static struct smp_funcall { + smpfunc_t func; + unsigned long arg1; + unsigned long arg2; + unsigned long arg3; + unsigned long arg4; + unsigned long arg5; + unsigned char processors_in[NR_CPUS]; /* Set when ipi entered. */ + unsigned char processors_out[NR_CPUS]; /* Set when ipi exited. */ +} ccall_info __attribute__((aligned(8))); + +static DEFINE_SPINLOCK(cross_call_lock); + +/* Cross calls must be serialized, at least currently. */ +void smp4d_cross_call(smpfunc_t func, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, unsigned long arg5) +{ + if(smp_processors_ready) { + register int high = smp_highest_cpu; + unsigned long flags; + + spin_lock_irqsave(&cross_call_lock, flags); + + { + /* If you make changes here, make sure gcc generates proper code... */ + register smpfunc_t f asm("i0") = func; + register unsigned long a1 asm("i1") = arg1; + register unsigned long a2 asm("i2") = arg2; + register unsigned long a3 asm("i3") = arg3; + register unsigned long a4 asm("i4") = arg4; + register unsigned long a5 asm("i5") = arg5; + + __asm__ __volatile__( + "std %0, [%6]\n\t" + "std %2, [%6 + 8]\n\t" + "std %4, [%6 + 16]\n\t" : : + "r"(f), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5), + "r" (&ccall_info.func)); + } + + /* Init receive/complete mapping, plus fire the IPI's off. */ + { + cpumask_t mask; + register int i; + + mask = cpumask_of_cpu(hard_smp4d_processor_id()); + cpus_andnot(mask, cpu_present_map, mask); + for(i = 0; i <= high; i++) { + if (cpu_isset(i, mask)) { + ccall_info.processors_in[i] = 0; + ccall_info.processors_out[i] = 0; + sun4d_send_ipi(i, IRQ_CROSS_CALL); + } + } + } + + { + register int i; + + i = 0; + do { + while(!ccall_info.processors_in[i]) + barrier(); + } while(++i <= high); + + i = 0; + do { + while(!ccall_info.processors_out[i]) + barrier(); + } while(++i <= high); + } + + spin_unlock_irqrestore(&cross_call_lock, flags); + } +} + +/* Running cross calls. */ +void smp4d_cross_call_irq(void) +{ + int i = hard_smp4d_processor_id(); + + ccall_info.processors_in[i] = 1; + ccall_info.func(ccall_info.arg1, ccall_info.arg2, ccall_info.arg3, + ccall_info.arg4, ccall_info.arg5); + ccall_info.processors_out[i] = 1; +} + +static int smp4d_stop_cpu_sender; + +static void smp4d_stop_cpu(void) +{ + int me = hard_smp4d_processor_id(); + + if (me != smp4d_stop_cpu_sender) + while(1) barrier(); +} + +/* Cross calls, in order to work efficiently and atomically do all + * the message passing work themselves, only stopcpu and reschedule + * messages come through here. + */ +void smp4d_message_pass(int target, int msg, unsigned long data, int wait) +{ + int me = hard_smp4d_processor_id(); + + SMP_PRINTK(("smp4d_message_pass %d %d %08lx %d\n", target, msg, data, wait)); + if (msg == MSG_STOP_CPU && target == MSG_ALL_BUT_SELF) { + unsigned long flags; + static DEFINE_SPINLOCK(stop_cpu_lock); + spin_lock_irqsave(&stop_cpu_lock, flags); + smp4d_stop_cpu_sender = me; + smp4d_cross_call((smpfunc_t)smp4d_stop_cpu, 0, 0, 0, 0, 0); + spin_unlock_irqrestore(&stop_cpu_lock, flags); + } + printk("Yeeee, trying to send SMP msg(%d) to %d on cpu %d\n", msg, target, me); + panic("Bogon SMP message pass."); +} + +void smp4d_percpu_timer_interrupt(struct pt_regs *regs) +{ + int cpu = hard_smp4d_processor_id(); + static int cpu_tick[NR_CPUS]; + static char led_mask[] = { 0xe, 0xd, 0xb, 0x7, 0xb, 0xd }; + + bw_get_prof_limit(cpu); + bw_clear_intr_mask(0, 1); /* INTR_TABLE[0] & 1 is Profile IRQ */ + + cpu_tick[cpu]++; + if (!(cpu_tick[cpu] & 15)) { + if (cpu_tick[cpu] == 0x60) + cpu_tick[cpu] = 0; + cpu_leds[cpu] = led_mask[cpu_tick[cpu] >> 4]; + show_leds(cpu); + } + + profile_tick(CPU_PROFILING, regs); + + if(!--prof_counter(cpu)) { + int user = user_mode(regs); + + irq_enter(); + update_process_times(user); + irq_exit(); + + prof_counter(cpu) = prof_multiplier(cpu); + } +} + +extern unsigned int lvl14_resolution; + +static void __init smp_setup_percpu_timer(void) +{ + int cpu = hard_smp4d_processor_id(); + + prof_counter(cpu) = prof_multiplier(cpu) = 1; + load_profile_irq(cpu, lvl14_resolution); +} + +void __init smp4d_blackbox_id(unsigned *addr) +{ + int rd = *addr & 0x3e000000; + + addr[0] = 0xc0800800 | rd; /* lda [%g0] ASI_M_VIKING_TMP1, reg */ + addr[1] = 0x01000000; /* nop */ + addr[2] = 0x01000000; /* nop */ +} + +void __init smp4d_blackbox_current(unsigned *addr) +{ + int rd = *addr & 0x3e000000; + + addr[0] = 0xc0800800 | rd; /* lda [%g0] ASI_M_VIKING_TMP1, reg */ + addr[2] = 0x81282002 | rd | (rd >> 11); /* sll reg, 2, reg */ + addr[4] = 0x01000000; /* nop */ +} + +void __init sun4d_init_smp(void) +{ + int i; + extern unsigned int t_nmi[], linux_trap_ipi15_sun4d[], linux_trap_ipi15_sun4m[]; + + /* Patch ipi15 trap table */ + t_nmi[1] = t_nmi[1] + (linux_trap_ipi15_sun4d - linux_trap_ipi15_sun4m); + + /* And set btfixup... */ + BTFIXUPSET_BLACKBOX(hard_smp_processor_id, smp4d_blackbox_id); + BTFIXUPSET_BLACKBOX(load_current, smp4d_blackbox_current); + BTFIXUPSET_CALL(smp_cross_call, smp4d_cross_call, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(smp_message_pass, smp4d_message_pass, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__hard_smp_processor_id, __smp4d_processor_id, BTFIXUPCALL_NORM); + + for (i = 0; i < NR_CPUS; i++) { + ccall_info.processors_in[i] = 1; + ccall_info.processors_out[i] = 1; + } +} diff --git a/arch/sparc/kernel/sun4m_irq.c b/arch/sparc/kernel/sun4m_irq.c new file mode 100644 index 000000000000..39d712c3c809 --- /dev/null +++ b/arch/sparc/kernel/sun4m_irq.c @@ -0,0 +1,399 @@ +/* sun4m_irq.c + * arch/sparc/kernel/sun4m_irq.c: + * + * djhr: Hacked out of irq.c into a CPU dependent version. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1995 Pete A. Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Dave Redman (djhr@tadpole.co.uk) + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/linkage.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/smp.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/ioport.h> + +#include <asm/ptrace.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/psr.h> +#include <asm/vaddrs.h> +#include <asm/timer.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/traps.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/smp.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/sbus.h> +#include <asm/cacheflush.h> + +static unsigned long dummy; + +struct sun4m_intregs *sun4m_interrupts; +unsigned long *irq_rcvreg = &dummy; + +/* These tables only apply for interrupts greater than 15.. + * + * any intr value below 0x10 is considered to be a soft-int + * this may be useful or it may not.. but that's how I've done it. + * and it won't clash with what OBP is telling us about devices. + * + * take an encoded intr value and lookup if it's valid + * then get the mask bits that match from irq_mask + * + * P3: Translation from irq 0x0d to mask 0x2000 is for MrCoffee. + */ +static unsigned char irq_xlate[32] = { + /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */ + 0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 5, 6, 14, 0, 7, + 0, 0, 8, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 0 +}; + +static unsigned long irq_mask[] = { + 0, /* illegal index */ + SUN4M_INT_SCSI, /* 1 irq 4 */ + SUN4M_INT_ETHERNET, /* 2 irq 6 */ + SUN4M_INT_VIDEO, /* 3 irq 8 */ + SUN4M_INT_REALTIME, /* 4 irq 10 */ + SUN4M_INT_FLOPPY, /* 5 irq 11 */ + (SUN4M_INT_SERIAL | SUN4M_INT_KBDMS), /* 6 irq 12 */ + SUN4M_INT_MODULE_ERR, /* 7 irq 15 */ + SUN4M_INT_SBUS(0), /* 8 irq 2 */ + SUN4M_INT_SBUS(1), /* 9 irq 3 */ + SUN4M_INT_SBUS(2), /* 10 irq 5 */ + SUN4M_INT_SBUS(3), /* 11 irq 7 */ + SUN4M_INT_SBUS(4), /* 12 irq 9 */ + SUN4M_INT_SBUS(5), /* 13 irq 11 */ + SUN4M_INT_SBUS(6) /* 14 irq 13 */ +}; + +static int sun4m_pil_map[] = { 0, 2, 3, 5, 7, 9, 11, 13 }; + +unsigned int sun4m_sbint_to_irq(struct sbus_dev *sdev, unsigned int sbint) +{ + if (sbint >= sizeof(sun4m_pil_map)) { + printk(KERN_ERR "%s: bogus SBINT %d\n", sdev->prom_name, sbint); + BUG(); + } + return sun4m_pil_map[sbint] | 0x30; +} + +inline unsigned long sun4m_get_irqmask(unsigned int irq) +{ + unsigned long mask; + + if (irq > 0x20) { + /* OBIO/SBUS interrupts */ + irq &= 0x1f; + mask = irq_mask[irq_xlate[irq]]; + if (!mask) + printk("sun4m_get_irqmask: IRQ%d has no valid mask!\n",irq); + } else { + /* Soft Interrupts will come here. + * Currently there is no way to trigger them but I'm sure + * something could be cooked up. + */ + irq &= 0xf; + mask = SUN4M_SOFT_INT(irq); + } + return mask; +} + +static void sun4m_disable_irq(unsigned int irq_nr) +{ + unsigned long mask, flags; + int cpu = smp_processor_id(); + + mask = sun4m_get_irqmask(irq_nr); + local_irq_save(flags); + if (irq_nr > 15) + sun4m_interrupts->set = mask; + else + sun4m_interrupts->cpu_intregs[cpu].set = mask; + local_irq_restore(flags); +} + +static void sun4m_enable_irq(unsigned int irq_nr) +{ + unsigned long mask, flags; + int cpu = smp_processor_id(); + + /* Dreadful floppy hack. When we use 0x2b instead of + * 0x0b the system blows (it starts to whistle!). + * So we continue to use 0x0b. Fixme ASAP. --P3 + */ + if (irq_nr != 0x0b) { + mask = sun4m_get_irqmask(irq_nr); + local_irq_save(flags); + if (irq_nr > 15) + sun4m_interrupts->clear = mask; + else + sun4m_interrupts->cpu_intregs[cpu].clear = mask; + local_irq_restore(flags); + } else { + local_irq_save(flags); + sun4m_interrupts->clear = SUN4M_INT_FLOPPY; + local_irq_restore(flags); + } +} + +static unsigned long cpu_pil_to_imask[16] = { +/*0*/ 0x00000000, +/*1*/ 0x00000000, +/*2*/ SUN4M_INT_SBUS(0) | SUN4M_INT_VME(0), +/*3*/ SUN4M_INT_SBUS(1) | SUN4M_INT_VME(1), +/*4*/ SUN4M_INT_SCSI, +/*5*/ SUN4M_INT_SBUS(2) | SUN4M_INT_VME(2), +/*6*/ SUN4M_INT_ETHERNET, +/*7*/ SUN4M_INT_SBUS(3) | SUN4M_INT_VME(3), +/*8*/ SUN4M_INT_VIDEO, +/*9*/ SUN4M_INT_SBUS(4) | SUN4M_INT_VME(4) | SUN4M_INT_MODULE_ERR, +/*10*/ SUN4M_INT_REALTIME, +/*11*/ SUN4M_INT_SBUS(5) | SUN4M_INT_VME(5) | SUN4M_INT_FLOPPY, +/*12*/ SUN4M_INT_SERIAL | SUN4M_INT_KBDMS, +/*13*/ SUN4M_INT_AUDIO, +/*14*/ SUN4M_INT_E14, +/*15*/ 0x00000000 +}; + +/* We assume the caller has disabled local interrupts when these are called, + * or else very bizarre behavior will result. + */ +static void sun4m_disable_pil_irq(unsigned int pil) +{ + sun4m_interrupts->set = cpu_pil_to_imask[pil]; +} + +static void sun4m_enable_pil_irq(unsigned int pil) +{ + sun4m_interrupts->clear = cpu_pil_to_imask[pil]; +} + +#ifdef CONFIG_SMP +static void sun4m_send_ipi(int cpu, int level) +{ + unsigned long mask; + + mask = sun4m_get_irqmask(level); + sun4m_interrupts->cpu_intregs[cpu].set = mask; +} + +static void sun4m_clear_ipi(int cpu, int level) +{ + unsigned long mask; + + mask = sun4m_get_irqmask(level); + sun4m_interrupts->cpu_intregs[cpu].clear = mask; +} + +static void sun4m_set_udt(int cpu) +{ + sun4m_interrupts->undirected_target = cpu; +} +#endif + +#define OBIO_INTR 0x20 +#define TIMER_IRQ (OBIO_INTR | 10) +#define PROFILE_IRQ (OBIO_INTR | 14) + +struct sun4m_timer_regs *sun4m_timers; +unsigned int lvl14_resolution = (((1000000/HZ) + 1) << 10); + +static void sun4m_clear_clock_irq(void) +{ + volatile unsigned int clear_intr; + clear_intr = sun4m_timers->l10_timer_limit; +} + +static void sun4m_clear_profile_irq(int cpu) +{ + volatile unsigned int clear; + + clear = sun4m_timers->cpu_timers[cpu].l14_timer_limit; +} + +static void sun4m_load_profile_irq(int cpu, unsigned int limit) +{ + sun4m_timers->cpu_timers[cpu].l14_timer_limit = limit; +} + +char *sun4m_irq_itoa(unsigned int irq) +{ + static char buff[16]; + sprintf(buff, "%d", irq); + return buff; +} + +static void __init sun4m_init_timers(irqreturn_t (*counter_fn)(int, void *, struct pt_regs *)) +{ + int reg_count, irq, cpu; + struct linux_prom_registers cnt_regs[PROMREG_MAX]; + int obio_node, cnt_node; + struct resource r; + + cnt_node = 0; + if((obio_node = + prom_searchsiblings (prom_getchild(prom_root_node), "obio")) == 0 || + (obio_node = prom_getchild (obio_node)) == 0 || + (cnt_node = prom_searchsiblings (obio_node, "counter")) == 0) { + prom_printf("Cannot find /obio/counter node\n"); + prom_halt(); + } + reg_count = prom_getproperty(cnt_node, "reg", + (void *) cnt_regs, sizeof(cnt_regs)); + reg_count = (reg_count/sizeof(struct linux_prom_registers)); + + /* Apply the obio ranges to the timer registers. */ + prom_apply_obio_ranges(cnt_regs, reg_count); + + cnt_regs[4].phys_addr = cnt_regs[reg_count-1].phys_addr; + cnt_regs[4].reg_size = cnt_regs[reg_count-1].reg_size; + cnt_regs[4].which_io = cnt_regs[reg_count-1].which_io; + for(obio_node = 1; obio_node < 4; obio_node++) { + cnt_regs[obio_node].phys_addr = + cnt_regs[obio_node-1].phys_addr + PAGE_SIZE; + cnt_regs[obio_node].reg_size = cnt_regs[obio_node-1].reg_size; + cnt_regs[obio_node].which_io = cnt_regs[obio_node-1].which_io; + } + + memset((char*)&r, 0, sizeof(struct resource)); + /* Map the per-cpu Counter registers. */ + r.flags = cnt_regs[0].which_io; + r.start = cnt_regs[0].phys_addr; + sun4m_timers = (struct sun4m_timer_regs *) sbus_ioremap(&r, 0, + PAGE_SIZE*SUN4M_NCPUS, "sun4m_cpu_cnt"); + /* Map the system Counter register. */ + /* XXX Here we expect consequent calls to yeld adjusent maps. */ + r.flags = cnt_regs[4].which_io; + r.start = cnt_regs[4].phys_addr; + sbus_ioremap(&r, 0, cnt_regs[4].reg_size, "sun4m_sys_cnt"); + + sun4m_timers->l10_timer_limit = (((1000000/HZ) + 1) << 10); + master_l10_counter = &sun4m_timers->l10_cur_count; + master_l10_limit = &sun4m_timers->l10_timer_limit; + + irq = request_irq(TIMER_IRQ, + counter_fn, + (SA_INTERRUPT | SA_STATIC_ALLOC), + "timer", NULL); + if (irq) { + prom_printf("time_init: unable to attach IRQ%d\n",TIMER_IRQ); + prom_halt(); + } + + if (!cpu_find_by_instance(1, NULL, NULL)) { + for(cpu = 0; cpu < 4; cpu++) + sun4m_timers->cpu_timers[cpu].l14_timer_limit = 0; + sun4m_interrupts->set = SUN4M_INT_E14; + } else { + sun4m_timers->cpu_timers[0].l14_timer_limit = 0; + } +#ifdef CONFIG_SMP + { + unsigned long flags; + extern unsigned long lvl14_save[4]; + struct tt_entry *trap_table = &sparc_ttable[SP_TRAP_IRQ1 + (14 - 1)]; + + /* For SMP we use the level 14 ticker, however the bootup code + * has copied the firmwares level 14 vector into boot cpu's + * trap table, we must fix this now or we get squashed. + */ + local_irq_save(flags); + trap_table->inst_one = lvl14_save[0]; + trap_table->inst_two = lvl14_save[1]; + trap_table->inst_three = lvl14_save[2]; + trap_table->inst_four = lvl14_save[3]; + local_flush_cache_all(); + local_irq_restore(flags); + } +#endif +} + +void __init sun4m_init_IRQ(void) +{ + int ie_node,i; + struct linux_prom_registers int_regs[PROMREG_MAX]; + int num_regs; + struct resource r; + int mid; + + local_irq_disable(); + if((ie_node = prom_searchsiblings(prom_getchild(prom_root_node), "obio")) == 0 || + (ie_node = prom_getchild (ie_node)) == 0 || + (ie_node = prom_searchsiblings (ie_node, "interrupt")) == 0) { + prom_printf("Cannot find /obio/interrupt node\n"); + prom_halt(); + } + num_regs = prom_getproperty(ie_node, "reg", (char *) int_regs, + sizeof(int_regs)); + num_regs = (num_regs/sizeof(struct linux_prom_registers)); + + /* Apply the obio ranges to these registers. */ + prom_apply_obio_ranges(int_regs, num_regs); + + int_regs[4].phys_addr = int_regs[num_regs-1].phys_addr; + int_regs[4].reg_size = int_regs[num_regs-1].reg_size; + int_regs[4].which_io = int_regs[num_regs-1].which_io; + for(ie_node = 1; ie_node < 4; ie_node++) { + int_regs[ie_node].phys_addr = int_regs[ie_node-1].phys_addr + PAGE_SIZE; + int_regs[ie_node].reg_size = int_regs[ie_node-1].reg_size; + int_regs[ie_node].which_io = int_regs[ie_node-1].which_io; + } + + memset((char *)&r, 0, sizeof(struct resource)); + /* Map the interrupt registers for all possible cpus. */ + r.flags = int_regs[0].which_io; + r.start = int_regs[0].phys_addr; + sun4m_interrupts = (struct sun4m_intregs *) sbus_ioremap(&r, 0, + PAGE_SIZE*SUN4M_NCPUS, "interrupts_percpu"); + + /* Map the system interrupt control registers. */ + r.flags = int_regs[4].which_io; + r.start = int_regs[4].phys_addr; + sbus_ioremap(&r, 0, int_regs[4].reg_size, "interrupts_system"); + + sun4m_interrupts->set = ~SUN4M_INT_MASKALL; + for (i = 0; !cpu_find_by_instance(i, NULL, &mid); i++) + sun4m_interrupts->cpu_intregs[mid].clear = ~0x17fff; + + if (!cpu_find_by_instance(1, NULL, NULL)) { + /* system wide interrupts go to cpu 0, this should always + * be safe because it is guaranteed to be fitted or OBP doesn't + * come up + * + * Not sure, but writing here on SLAVIO systems may puke + * so I don't do it unless there is more than 1 cpu. + */ + irq_rcvreg = (unsigned long *) + &sun4m_interrupts->undirected_target; + sun4m_interrupts->undirected_target = 0; + } + BTFIXUPSET_CALL(sbint_to_irq, sun4m_sbint_to_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(enable_irq, sun4m_enable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(disable_irq, sun4m_disable_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(enable_pil_irq, sun4m_enable_pil_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(disable_pil_irq, sun4m_disable_pil_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_clock_irq, sun4m_clear_clock_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_profile_irq, sun4m_clear_profile_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(load_profile_irq, sun4m_load_profile_irq, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__irq_itoa, sun4m_irq_itoa, BTFIXUPCALL_NORM); + sparc_init_timers = sun4m_init_timers; +#ifdef CONFIG_SMP + BTFIXUPSET_CALL(set_cpu_int, sun4m_send_ipi, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(clear_cpu_int, sun4m_clear_ipi, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(set_irq_udt, sun4m_set_udt, BTFIXUPCALL_NORM); +#endif + /* Cannot enable interrupts until OBP ticker is disabled. */ +} diff --git a/arch/sparc/kernel/sun4m_smp.c b/arch/sparc/kernel/sun4m_smp.c new file mode 100644 index 000000000000..f113422a3727 --- /dev/null +++ b/arch/sparc/kernel/sun4m_smp.c @@ -0,0 +1,451 @@ +/* sun4m_smp.c: Sparc SUN4M SMP support. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/head.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/threads.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/profile.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +#include <asm/ptrace.h> +#include <asm/atomic.h> + +#include <asm/delay.h> +#include <asm/irq.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/oplib.h> +#include <asm/cpudata.h> + +#define IRQ_RESCHEDULE 13 +#define IRQ_STOP_CPU 14 +#define IRQ_CROSS_CALL 15 + +extern ctxd_t *srmmu_ctx_table_phys; + +extern void calibrate_delay(void); + +extern volatile int smp_processors_ready; +extern int smp_num_cpus; +extern volatile unsigned long cpu_callin_map[NR_CPUS]; +extern unsigned char boot_cpu_id; +extern int smp_activated; +extern volatile int __cpu_number_map[NR_CPUS]; +extern volatile int __cpu_logical_map[NR_CPUS]; +extern volatile unsigned long ipi_count; +extern volatile int smp_process_available; +extern volatile int smp_commenced; +extern int __smp4m_processor_id(void); + +/*#define SMP_DEBUG*/ + +#ifdef SMP_DEBUG +#define SMP_PRINTK(x) printk x +#else +#define SMP_PRINTK(x) +#endif + +static inline unsigned long swap(volatile unsigned long *ptr, unsigned long val) +{ + __asm__ __volatile__("swap [%1], %0\n\t" : + "=&r" (val), "=&r" (ptr) : + "0" (val), "1" (ptr)); + return val; +} + +static void smp_setup_percpu_timer(void); +extern void cpu_probe(void); + +void __init smp4m_callin(void) +{ + int cpuid = hard_smp_processor_id(); + + local_flush_cache_all(); + local_flush_tlb_all(); + + set_irq_udt(boot_cpu_id); + + /* Get our local ticker going. */ + smp_setup_percpu_timer(); + + calibrate_delay(); + smp_store_cpu_info(cpuid); + + local_flush_cache_all(); + local_flush_tlb_all(); + + /* + * Unblock the master CPU _only_ when the scheduler state + * of all secondary CPUs will be up-to-date, so after + * the SMP initialization the master will be just allowed + * to call the scheduler code. + */ + /* Allow master to continue. */ + swap((unsigned long *)&cpu_callin_map[cpuid], 1); + + local_flush_cache_all(); + local_flush_tlb_all(); + + cpu_probe(); + + /* Fix idle thread fields. */ + __asm__ __volatile__("ld [%0], %%g6\n\t" + : : "r" (¤t_set[cpuid]) + : "memory" /* paranoid */); + + /* Attach to the address space of init_task. */ + atomic_inc(&init_mm.mm_count); + current->active_mm = &init_mm; + + while(!smp_commenced) + barrier(); + + local_flush_cache_all(); + local_flush_tlb_all(); + + local_irq_enable(); +} + +extern void init_IRQ(void); +extern void cpu_panic(void); + +/* + * Cycle through the processors asking the PROM to start each one. + */ + +extern struct linux_prom_registers smp_penguin_ctable; +extern unsigned long trapbase_cpu1[]; +extern unsigned long trapbase_cpu2[]; +extern unsigned long trapbase_cpu3[]; + +void __init smp4m_boot_cpus(void) +{ + int cpucount = 0; + int i, mid; + + printk("Entering SMP Mode...\n"); + + local_irq_enable(); + cpus_clear(cpu_present_map); + + for (i = 0; !cpu_find_by_instance(i, NULL, &mid); i++) + cpu_set(mid, cpu_present_map); + + for(i=0; i < NR_CPUS; i++) { + __cpu_number_map[i] = -1; + __cpu_logical_map[i] = -1; + } + + __cpu_number_map[boot_cpu_id] = 0; + __cpu_logical_map[0] = boot_cpu_id; + current_thread_info()->cpu = boot_cpu_id; + + smp_store_cpu_info(boot_cpu_id); + set_irq_udt(boot_cpu_id); + smp_setup_percpu_timer(); + local_flush_cache_all(); + if(cpu_find_by_instance(1, NULL, NULL)) + return; /* Not an MP box. */ + for(i = 0; i < NR_CPUS; i++) { + if(i == boot_cpu_id) + continue; + + if (cpu_isset(i, cpu_present_map)) { + extern unsigned long sun4m_cpu_startup; + unsigned long *entry = &sun4m_cpu_startup; + struct task_struct *p; + int timeout; + + /* Cook up an idler for this guy. */ + p = fork_idle(i); + cpucount++; + current_set[i] = p->thread_info; + /* See trampoline.S for details... */ + entry += ((i-1) * 3); + + /* + * Initialize the contexts table + * Since the call to prom_startcpu() trashes the structure, + * we need to re-initialize it for each cpu + */ + smp_penguin_ctable.which_io = 0; + smp_penguin_ctable.phys_addr = (unsigned int) srmmu_ctx_table_phys; + smp_penguin_ctable.reg_size = 0; + + /* whirrr, whirrr, whirrrrrrrrr... */ + printk("Starting CPU %d at %p\n", i, entry); + local_flush_cache_all(); + prom_startcpu(cpu_data(i).prom_node, + &smp_penguin_ctable, 0, (char *)entry); + + /* wheee... it's going... */ + for(timeout = 0; timeout < 10000; timeout++) { + if(cpu_callin_map[i]) + break; + udelay(200); + } + if(cpu_callin_map[i]) { + /* Another "Red Snapper". */ + __cpu_number_map[i] = i; + __cpu_logical_map[i] = i; + } else { + cpucount--; + printk("Processor %d is stuck.\n", i); + } + } + if(!(cpu_callin_map[i])) { + cpu_clear(i, cpu_present_map); + __cpu_number_map[i] = -1; + } + } + local_flush_cache_all(); + if(cpucount == 0) { + printk("Error: only one Processor found.\n"); + cpu_present_map = cpumask_of_cpu(smp_processor_id()); + } else { + unsigned long bogosum = 0; + for(i = 0; i < NR_CPUS; i++) { + if (cpu_isset(i, cpu_present_map)) + bogosum += cpu_data(i).udelay_val; + } + printk("Total of %d Processors activated (%lu.%02lu BogoMIPS).\n", + cpucount + 1, + bogosum/(500000/HZ), + (bogosum/(5000/HZ))%100); + smp_activated = 1; + smp_num_cpus = cpucount + 1; + } + + /* Free unneeded trap tables */ + if (!cpu_isset(i, cpu_present_map)) { + ClearPageReserved(virt_to_page(trapbase_cpu1)); + set_page_count(virt_to_page(trapbase_cpu1), 1); + free_page((unsigned long)trapbase_cpu1); + totalram_pages++; + num_physpages++; + } + if (!cpu_isset(2, cpu_present_map)) { + ClearPageReserved(virt_to_page(trapbase_cpu2)); + set_page_count(virt_to_page(trapbase_cpu2), 1); + free_page((unsigned long)trapbase_cpu2); + totalram_pages++; + num_physpages++; + } + if (!cpu_isset(3, cpu_present_map)) { + ClearPageReserved(virt_to_page(trapbase_cpu3)); + set_page_count(virt_to_page(trapbase_cpu3), 1); + free_page((unsigned long)trapbase_cpu3); + totalram_pages++; + num_physpages++; + } + + /* Ok, they are spinning and ready to go. */ + smp_processors_ready = 1; +} + +/* At each hardware IRQ, we get this called to forward IRQ reception + * to the next processor. The caller must disable the IRQ level being + * serviced globally so that there are no double interrupts received. + * + * XXX See sparc64 irq.c. + */ +void smp4m_irq_rotate(int cpu) +{ +} + +/* Cross calls, in order to work efficiently and atomically do all + * the message passing work themselves, only stopcpu and reschedule + * messages come through here. + */ +void smp4m_message_pass(int target, int msg, unsigned long data, int wait) +{ + static unsigned long smp_cpu_in_msg[NR_CPUS]; + cpumask_t mask; + int me = smp_processor_id(); + int irq, i; + + if(msg == MSG_RESCHEDULE) { + irq = IRQ_RESCHEDULE; + + if(smp_cpu_in_msg[me]) + return; + } else if(msg == MSG_STOP_CPU) { + irq = IRQ_STOP_CPU; + } else { + goto barf; + } + + smp_cpu_in_msg[me]++; + if(target == MSG_ALL_BUT_SELF || target == MSG_ALL) { + mask = cpu_present_map; + if(target == MSG_ALL_BUT_SELF) + cpu_clear(me, mask); + for(i = 0; i < 4; i++) { + if (cpu_isset(i, mask)) + set_cpu_int(i, irq); + } + } else { + set_cpu_int(target, irq); + } + smp_cpu_in_msg[me]--; + + return; +barf: + printk("Yeeee, trying to send SMP msg(%d) on cpu %d\n", msg, me); + panic("Bogon SMP message pass."); +} + +static struct smp_funcall { + smpfunc_t func; + unsigned long arg1; + unsigned long arg2; + unsigned long arg3; + unsigned long arg4; + unsigned long arg5; + unsigned long processors_in[NR_CPUS]; /* Set when ipi entered. */ + unsigned long processors_out[NR_CPUS]; /* Set when ipi exited. */ +} ccall_info; + +static DEFINE_SPINLOCK(cross_call_lock); + +/* Cross calls must be serialized, at least currently. */ +void smp4m_cross_call(smpfunc_t func, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, unsigned long arg5) +{ + if(smp_processors_ready) { + register int ncpus = smp_num_cpus; + unsigned long flags; + + spin_lock_irqsave(&cross_call_lock, flags); + + /* Init function glue. */ + ccall_info.func = func; + ccall_info.arg1 = arg1; + ccall_info.arg2 = arg2; + ccall_info.arg3 = arg3; + ccall_info.arg4 = arg4; + ccall_info.arg5 = arg5; + + /* Init receive/complete mapping, plus fire the IPI's off. */ + { + cpumask_t mask = cpu_present_map; + register int i; + + cpu_clear(smp_processor_id(), mask); + for(i = 0; i < ncpus; i++) { + if (cpu_isset(i, mask)) { + ccall_info.processors_in[i] = 0; + ccall_info.processors_out[i] = 0; + set_cpu_int(i, IRQ_CROSS_CALL); + } else { + ccall_info.processors_in[i] = 1; + ccall_info.processors_out[i] = 1; + } + } + } + + { + register int i; + + i = 0; + do { + while(!ccall_info.processors_in[i]) + barrier(); + } while(++i < ncpus); + + i = 0; + do { + while(!ccall_info.processors_out[i]) + barrier(); + } while(++i < ncpus); + } + + spin_unlock_irqrestore(&cross_call_lock, flags); + } +} + +/* Running cross calls. */ +void smp4m_cross_call_irq(void) +{ + int i = smp_processor_id(); + + ccall_info.processors_in[i] = 1; + ccall_info.func(ccall_info.arg1, ccall_info.arg2, ccall_info.arg3, + ccall_info.arg4, ccall_info.arg5); + ccall_info.processors_out[i] = 1; +} + +void smp4m_percpu_timer_interrupt(struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + clear_profile_irq(cpu); + + profile_tick(CPU_PROFILING, regs); + + if(!--prof_counter(cpu)) { + int user = user_mode(regs); + + irq_enter(); + update_process_times(user); + irq_exit(); + + prof_counter(cpu) = prof_multiplier(cpu); + } +} + +extern unsigned int lvl14_resolution; + +static void __init smp_setup_percpu_timer(void) +{ + int cpu = smp_processor_id(); + + prof_counter(cpu) = prof_multiplier(cpu) = 1; + load_profile_irq(cpu, lvl14_resolution); + + if(cpu == boot_cpu_id) + enable_pil_irq(14); +} + +void __init smp4m_blackbox_id(unsigned *addr) +{ + int rd = *addr & 0x3e000000; + int rs1 = rd >> 11; + + addr[0] = 0x81580000 | rd; /* rd %tbr, reg */ + addr[1] = 0x8130200c | rd | rs1; /* srl reg, 0xc, reg */ + addr[2] = 0x80082003 | rd | rs1; /* and reg, 3, reg */ +} + +void __init smp4m_blackbox_current(unsigned *addr) +{ + int rd = *addr & 0x3e000000; + int rs1 = rd >> 11; + + addr[0] = 0x81580000 | rd; /* rd %tbr, reg */ + addr[2] = 0x8130200a | rd | rs1; /* srl reg, 0xa, reg */ + addr[4] = 0x8008200c | rd | rs1; /* and reg, 3, reg */ +} + +void __init sun4m_init_smp(void) +{ + BTFIXUPSET_BLACKBOX(hard_smp_processor_id, smp4m_blackbox_id); + BTFIXUPSET_BLACKBOX(load_current, smp4m_blackbox_current); + BTFIXUPSET_CALL(smp_cross_call, smp4m_cross_call, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(smp_message_pass, smp4m_message_pass, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__hard_smp_processor_id, __smp4m_processor_id, BTFIXUPCALL_NORM); +} diff --git a/arch/sparc/kernel/sun4setup.c b/arch/sparc/kernel/sun4setup.c new file mode 100644 index 000000000000..229a52f55f16 --- /dev/null +++ b/arch/sparc/kernel/sun4setup.c @@ -0,0 +1,75 @@ +/* sun4setup.c: Setup the hardware address of various items in the sun4 + * architecture. Called from idprom_init + * + * Copyright (C) 1998 Chris G. Davis (cdavis@cois.on.ca) + */ + +#include <asm/page.h> +#include <asm/oplib.h> +#include <asm/idprom.h> +#include <asm/sun4paddr.h> +#include <asm/machines.h> + +int sun4_memreg_physaddr; +int sun4_ie_physaddr; +int sun4_clock_physaddr; +int sun4_timer_physaddr; +int sun4_eth_physaddr; +int sun4_si_physaddr; +int sun4_bwtwo_physaddr; +int sun4_zs0_physaddr; +int sun4_zs1_physaddr; +int sun4_dma_physaddr; +int sun4_esp_physaddr; +int sun4_ie_physaddr; + +void __init sun4setup(void) +{ + printk("Sun4 Hardware Setup v1.0 18/May/98 Chris Davis (cdavis@cois.on.ca). "); + /* + setup standard sun4 info + */ + sun4_ie_physaddr=SUN4_IE_PHYSADDR; + + /* + setup model specific info + */ + switch(idprom->id_machtype) { + case (SM_SUN4 | SM_4_260 ): + printk("Setup for a SUN4/260\n"); + sun4_memreg_physaddr=SUN4_200_MEMREG_PHYSADDR; + sun4_clock_physaddr=SUN4_200_CLOCK_PHYSADDR; + sun4_timer_physaddr=SUN4_UNUSED_PHYSADDR; + sun4_eth_physaddr=SUN4_200_ETH_PHYSADDR; + sun4_si_physaddr=SUN4_200_SI_PHYSADDR; + sun4_bwtwo_physaddr=SUN4_200_BWTWO_PHYSADDR; + sun4_dma_physaddr=SUN4_UNUSED_PHYSADDR; + sun4_esp_physaddr=SUN4_UNUSED_PHYSADDR; + break; + case (SM_SUN4 | SM_4_330 ): + printk("Setup for a SUN4/330\n"); + sun4_memreg_physaddr=SUN4_300_MEMREG_PHYSADDR; + sun4_clock_physaddr=SUN4_300_CLOCK_PHYSADDR; + sun4_timer_physaddr=SUN4_300_TIMER_PHYSADDR; + sun4_eth_physaddr=SUN4_300_ETH_PHYSADDR; + sun4_si_physaddr=SUN4_UNUSED_PHYSADDR; + sun4_bwtwo_physaddr=SUN4_300_BWTWO_PHYSADDR; + sun4_dma_physaddr=SUN4_300_DMA_PHYSADDR; + sun4_esp_physaddr=SUN4_300_ESP_PHYSADDR; + break; + case (SM_SUN4 | SM_4_470 ): + printk("Setup for a SUN4/470\n"); + sun4_memreg_physaddr=SUN4_400_MEMREG_PHYSADDR; + sun4_clock_physaddr=SUN4_400_CLOCK_PHYSADDR; + sun4_timer_physaddr=SUN4_400_TIMER_PHYSADDR; + sun4_eth_physaddr=SUN4_400_ETH_PHYSADDR; + sun4_si_physaddr=SUN4_UNUSED_PHYSADDR; + sun4_bwtwo_physaddr=SUN4_400_BWTWO_PHYSADDR; + sun4_dma_physaddr=SUN4_400_DMA_PHYSADDR; + sun4_esp_physaddr=SUN4_400_ESP_PHYSADDR; + break; + default: + ; + } +} + diff --git a/arch/sparc/kernel/sunos_asm.S b/arch/sparc/kernel/sunos_asm.S new file mode 100644 index 000000000000..07fe86014fb5 --- /dev/null +++ b/arch/sparc/kernel/sunos_asm.S @@ -0,0 +1,67 @@ +/* $Id: sunos_asm.S,v 1.15 2000/01/11 17:33:21 jj Exp $ + * sunos_asm.S: SunOS system calls which must have a low-level + * entry point to operate correctly. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * + * Based upon preliminary work which is: + * + * Copyright (C) 1995 Adrian M. Rodriguez (adrian@remus.rutgers.edu) + */ + +#include <asm/ptrace.h> + + .text + .align 4 + + /* When calling ret_sys_call, %o0 should contain the same + * value as in [%sp + STACKFRAME_SZ + PT_I0] */ + + /* SunOS getpid() returns pid in %o0 and ppid in %o1 */ + .globl sunos_getpid +sunos_getpid: + call sys_getppid + nop + + call sys_getpid + st %o0, [%sp + STACKFRAME_SZ + PT_I1] + + b ret_sys_call + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + + /* SunOS getuid() returns uid in %o0 and euid in %o1 */ + .globl sunos_getuid +sunos_getuid: + call sys_geteuid16 + nop + + call sys_getuid16 + st %o0, [%sp + STACKFRAME_SZ + PT_I1] + + b ret_sys_call + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + + /* SunOS getgid() returns gid in %o0 and egid in %o1 */ + .globl sunos_getgid +sunos_getgid: + call sys_getegid16 + nop + + call sys_getgid16 + st %o0, [%sp + STACKFRAME_SZ + PT_I1] + + b ret_sys_call + st %o0, [%sp + STACKFRAME_SZ + PT_I0] + + /* SunOS's execv() call only specifies the argv argument, the + * environment settings are the same as the calling processes. + */ + .globl sunos_execv +sunos_execv: + st %g0, [%sp + STACKFRAME_SZ + PT_I2] + + call sparc_execve + add %sp, STACKFRAME_SZ, %o0 + + b ret_sys_call + ld [%sp + STACKFRAME_SZ + PT_I0], %o0 diff --git a/arch/sparc/kernel/sunos_ioctl.c b/arch/sparc/kernel/sunos_ioctl.c new file mode 100644 index 000000000000..df1c0b31a930 --- /dev/null +++ b/arch/sparc/kernel/sunos_ioctl.c @@ -0,0 +1,231 @@ +/* $Id: sunos_ioctl.c,v 1.34 2000/09/03 14:10:56 anton Exp $ + * sunos_ioctl.c: The Linux Operating system: SunOS ioctl compatibility. + * + * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/uaccess.h> + +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/termios.h> +#include <linux/ioctl.h> +#include <linux/route.h> +#include <linux/sockios.h> +#include <linux/if.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/syscalls.h> +#include <linux/file.h> +#include <asm/kbio.h> + +#if 0 +extern char sunkbd_type; +extern char sunkbd_layout; +#endif + +/* NR_OPEN is now larger and dynamic in recent kernels. */ +#define SUNOS_NR_OPEN 256 + +asmlinkage int sunos_ioctl (int fd, unsigned long cmd, unsigned long arg) +{ + int ret = -EBADF; + + if (fd >= SUNOS_NR_OPEN || !fcheck(fd)) + goto out; + + /* First handle an easy compat. case for tty ldisc. */ + if (cmd == TIOCSETD) { + int __user *p; + int ntty = N_TTY, tmp; + mm_segment_t oldfs; + + p = (int __user *) arg; + ret = -EFAULT; + if (get_user(tmp, p)) + goto out; + if (tmp == 2) { + oldfs = get_fs(); + set_fs(KERNEL_DS); + ret = sys_ioctl(fd, cmd, (unsigned long) &ntty); + set_fs(oldfs); + ret = (ret == -EINVAL ? -EOPNOTSUPP : ret); + goto out; + } + } + + /* Binary compatibility is good American knowhow fuckin' up. */ + if (cmd == TIOCNOTTY) { + ret = sys_setsid(); + goto out; + } + + /* SunOS networking ioctls. */ + switch (cmd) { + case _IOW('r', 10, struct rtentry): + ret = sys_ioctl(fd, SIOCADDRT, arg); + goto out; + case _IOW('r', 11, struct rtentry): + ret = sys_ioctl(fd, SIOCDELRT, arg); + goto out; + case _IOW('i', 12, struct ifreq): + ret = sys_ioctl(fd, SIOCSIFADDR, arg); + goto out; + case _IOWR('i', 13, struct ifreq): + ret = sys_ioctl(fd, SIOCGIFADDR, arg); + goto out; + case _IOW('i', 14, struct ifreq): + ret = sys_ioctl(fd, SIOCSIFDSTADDR, arg); + goto out; + case _IOWR('i', 15, struct ifreq): + ret = sys_ioctl(fd, SIOCGIFDSTADDR, arg); + goto out; + case _IOW('i', 16, struct ifreq): + ret = sys_ioctl(fd, SIOCSIFFLAGS, arg); + goto out; + case _IOWR('i', 17, struct ifreq): + ret = sys_ioctl(fd, SIOCGIFFLAGS, arg); + goto out; + case _IOW('i', 18, struct ifreq): + ret = sys_ioctl(fd, SIOCSIFMEM, arg); + goto out; + case _IOWR('i', 19, struct ifreq): + ret = sys_ioctl(fd, SIOCGIFMEM, arg); + goto out; + case _IOWR('i', 20, struct ifconf): + ret = sys_ioctl(fd, SIOCGIFCONF, arg); + goto out; + case _IOW('i', 21, struct ifreq): /* SIOCSIFMTU */ + ret = sys_ioctl(fd, SIOCSIFMTU, arg); + goto out; + case _IOWR('i', 22, struct ifreq): /* SIOCGIFMTU */ + ret = sys_ioctl(fd, SIOCGIFMTU, arg); + goto out; + + case _IOWR('i', 23, struct ifreq): + ret = sys_ioctl(fd, SIOCGIFBRDADDR, arg); + goto out; + case _IOW('i', 24, struct ifreq): + ret = sys_ioctl(fd, SIOCSIFBRDADDR, arg); + goto out; + case _IOWR('i', 25, struct ifreq): + ret = sys_ioctl(fd, SIOCGIFNETMASK, arg); + goto out; + case _IOW('i', 26, struct ifreq): + ret = sys_ioctl(fd, SIOCSIFNETMASK, arg); + goto out; + case _IOWR('i', 27, struct ifreq): + ret = sys_ioctl(fd, SIOCGIFMETRIC, arg); + goto out; + case _IOW('i', 28, struct ifreq): + ret = sys_ioctl(fd, SIOCSIFMETRIC, arg); + goto out; + + case _IOW('i', 30, struct arpreq): + ret = sys_ioctl(fd, SIOCSARP, arg); + goto out; + case _IOWR('i', 31, struct arpreq): + ret = sys_ioctl(fd, SIOCGARP, arg); + goto out; + case _IOW('i', 32, struct arpreq): + ret = sys_ioctl(fd, SIOCDARP, arg); + goto out; + + case _IOW('i', 40, struct ifreq): /* SIOCUPPER */ + case _IOW('i', 41, struct ifreq): /* SIOCLOWER */ + case _IOW('i', 44, struct ifreq): /* SIOCSETSYNC */ + case _IOW('i', 45, struct ifreq): /* SIOCGETSYNC */ + case _IOW('i', 46, struct ifreq): /* SIOCSSDSTATS */ + case _IOW('i', 47, struct ifreq): /* SIOCSSESTATS */ + case _IOW('i', 48, struct ifreq): /* SIOCSPROMISC */ + ret = -EOPNOTSUPP; + goto out; + + case _IOW('i', 49, struct ifreq): + ret = sys_ioctl(fd, SIOCADDMULTI, arg); + goto out; + case _IOW('i', 50, struct ifreq): + ret = sys_ioctl(fd, SIOCDELMULTI, arg); + goto out; + + /* FDDI interface ioctls, unsupported. */ + + case _IOW('i', 51, struct ifreq): /* SIOCFDRESET */ + case _IOW('i', 52, struct ifreq): /* SIOCFDSLEEP */ + case _IOW('i', 53, struct ifreq): /* SIOCSTRTFMWAR */ + case _IOW('i', 54, struct ifreq): /* SIOCLDNSTRTFW */ + case _IOW('i', 55, struct ifreq): /* SIOCGETFDSTAT */ + case _IOW('i', 56, struct ifreq): /* SIOCFDNMIINT */ + case _IOW('i', 57, struct ifreq): /* SIOCFDEXUSER */ + case _IOW('i', 58, struct ifreq): /* SIOCFDGNETMAP */ + case _IOW('i', 59, struct ifreq): /* SIOCFDGIOCTL */ + printk("FDDI ioctl, returning EOPNOTSUPP\n"); + ret = -EOPNOTSUPP; + goto out; + + case _IOW('t', 125, int): + /* More stupid tty sunos ioctls, just + * say it worked. + */ + ret = 0; + goto out; + /* Non posix grp */ + case _IOW('t', 118, int): { + int oldval, newval, __user *ptr; + + cmd = TIOCSPGRP; + ptr = (int __user *) arg; + ret = -EFAULT; + if (get_user(oldval, ptr)) + goto out; + ret = sys_ioctl(fd, cmd, arg); + __get_user(newval, ptr); + if (newval == -1) { + __put_user(oldval, ptr); + ret = -EIO; + } + if (ret == -ENOTTY) + ret = -EIO; + goto out; + } + + case _IOR('t', 119, int): { + int oldval, newval, __user *ptr; + + cmd = TIOCGPGRP; + ptr = (int __user *) arg; + ret = -EFAULT; + if (get_user(oldval, ptr)) + goto out; + ret = sys_ioctl(fd, cmd, arg); + __get_user(newval, ptr); + if (newval == -1) { + __put_user(oldval, ptr); + ret = -EIO; + } + if (ret == -ENOTTY) + ret = -EIO; + goto out; + } + } + +#if 0 + if ((cmd & 0xff00) == ('k' << 8)) { + printk ("[[KBIO: %8.8x\n", (unsigned int) cmd); + } +#endif + + ret = sys_ioctl(fd, cmd, arg); + /* so stupid... */ + ret = (ret == -EINVAL ? -EOPNOTSUPP : ret); +out: + return ret; +} + + diff --git a/arch/sparc/kernel/sys_solaris.c b/arch/sparc/kernel/sys_solaris.c new file mode 100644 index 000000000000..fb7578554c78 --- /dev/null +++ b/arch/sparc/kernel/sys_solaris.c @@ -0,0 +1,37 @@ +/* + * linux/arch/sparc/sys_solaris.c + * + * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/personality.h> +#include <linux/ptrace.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/module.h> + +asmlinkage int +do_solaris_syscall (struct pt_regs *regs) +{ + static int cnt = 0; + if (++cnt < 10) printk ("No solaris handler\n"); + force_sig(SIGSEGV, current); + return 0; +} + +#ifndef CONFIG_SUNOS_EMUL +asmlinkage int +do_sunos_syscall (struct pt_regs *regs) +{ + static int cnt = 0; + if (++cnt < 10) printk ("SunOS binary emulation not compiled in\n"); + force_sig (SIGSEGV, current); + return 0; +} +#endif diff --git a/arch/sparc/kernel/sys_sparc.c b/arch/sparc/kernel/sys_sparc.c new file mode 100644 index 000000000000..0cdfc9d294b4 --- /dev/null +++ b/arch/sparc/kernel/sys_sparc.c @@ -0,0 +1,485 @@ +/* $Id: sys_sparc.c,v 1.70 2001/04/14 01:12:02 davem Exp $ + * linux/arch/sparc/kernel/sys_sparc.c + * + * This file contains various random system calls that + * have a non-standard calling sequence on the Linux/sparc + * platform. + */ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/sem.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/stat.h> +#include <linux/syscalls.h> +#include <linux/mman.h> +#include <linux/utsname.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> + +#include <asm/uaccess.h> +#include <asm/ipc.h> + +/* #define DEBUG_UNIMP_SYSCALL */ + +/* XXX Make this per-binary type, this way we can detect the type of + * XXX a binary. Every Sparc executable calls this very early on. + */ +asmlinkage unsigned long sys_getpagesize(void) +{ + return PAGE_SIZE; /* Possibly older binaries want 8192 on sun4's? */ +} + +#define COLOUR_ALIGN(addr) (((addr)+SHMLBA-1)&~(SHMLBA-1)) + +unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) +{ + struct vm_area_struct * vmm; + + if (flags & MAP_FIXED) { + /* We do not accept a shared mapping if it would violate + * cache aliasing constraints. + */ + if ((flags & MAP_SHARED) && (addr & (SHMLBA - 1))) + return -EINVAL; + return addr; + } + + /* See asm-sparc/uaccess.h */ + if (len > TASK_SIZE - PAGE_SIZE) + return -ENOMEM; + if (ARCH_SUN4C_SUN4 && len > 0x20000000) + return -ENOMEM; + if (!addr) + addr = TASK_UNMAPPED_BASE; + + if (flags & MAP_SHARED) + addr = COLOUR_ALIGN(addr); + else + addr = PAGE_ALIGN(addr); + + for (vmm = find_vma(current->mm, addr); ; vmm = vmm->vm_next) { + /* At this point: (!vmm || addr < vmm->vm_end). */ + if (ARCH_SUN4C_SUN4 && addr < 0xe0000000 && 0x20000000 - len < addr) { + addr = PAGE_OFFSET; + vmm = find_vma(current->mm, PAGE_OFFSET); + } + if (TASK_SIZE - PAGE_SIZE - len < addr) + return -ENOMEM; + if (!vmm || addr + len <= vmm->vm_start) + return addr; + addr = vmm->vm_end; + if (flags & MAP_SHARED) + addr = COLOUR_ALIGN(addr); + } +} + +asmlinkage unsigned long sparc_brk(unsigned long brk) +{ + if(ARCH_SUN4C_SUN4) { + if ((brk & 0xe0000000) != (current->mm->brk & 0xe0000000)) + return current->mm->brk; + } + return sys_brk(brk); +} + +/* + * sys_pipe() is the normal C calling standard for creating + * a pipe. It's not the way unix traditionally does this, though. + */ +asmlinkage int sparc_pipe(struct pt_regs *regs) +{ + int fd[2]; + int error; + + error = do_pipe(fd); + if (error) + goto out; + regs->u_regs[UREG_I1] = fd[1]; + error = fd[0]; +out: + return error; +} + +/* + * sys_ipc() is the de-multiplexer for the SysV IPC calls.. + * + * This is really horribly ugly. + */ + +asmlinkage int sys_ipc (uint call, int first, int second, int third, void __user *ptr, long fifth) +{ + int version, err; + + version = call >> 16; /* hack for backward compatibility */ + call &= 0xffff; + + if (call <= SEMCTL) + switch (call) { + case SEMOP: + err = sys_semtimedop (first, (struct sembuf __user *)ptr, second, NULL); + goto out; + case SEMTIMEDOP: + err = sys_semtimedop (first, (struct sembuf __user *)ptr, second, (const struct timespec __user *) fifth); + goto out; + case SEMGET: + err = sys_semget (first, second, third); + goto out; + case SEMCTL: { + union semun fourth; + err = -EINVAL; + if (!ptr) + goto out; + err = -EFAULT; + if (get_user(fourth.__pad, + (void __user * __user *)ptr)) + goto out; + err = sys_semctl (first, second, third, fourth); + goto out; + } + default: + err = -ENOSYS; + goto out; + } + if (call <= MSGCTL) + switch (call) { + case MSGSND: + err = sys_msgsnd (first, (struct msgbuf __user *) ptr, + second, third); + goto out; + case MSGRCV: + switch (version) { + case 0: { + struct ipc_kludge tmp; + err = -EINVAL; + if (!ptr) + goto out; + err = -EFAULT; + if (copy_from_user(&tmp, (struct ipc_kludge __user *) ptr, sizeof (tmp))) + goto out; + err = sys_msgrcv (first, tmp.msgp, second, tmp.msgtyp, third); + goto out; + } + case 1: default: + err = sys_msgrcv (first, + (struct msgbuf __user *) ptr, + second, fifth, third); + goto out; + } + case MSGGET: + err = sys_msgget ((key_t) first, second); + goto out; + case MSGCTL: + err = sys_msgctl (first, second, (struct msqid_ds __user *) ptr); + goto out; + default: + err = -ENOSYS; + goto out; + } + if (call <= SHMCTL) + switch (call) { + case SHMAT: + switch (version) { + case 0: default: { + ulong raddr; + err = do_shmat (first, (char __user *) ptr, second, &raddr); + if (err) + goto out; + err = -EFAULT; + if (put_user (raddr, (ulong __user *) third)) + goto out; + err = 0; + goto out; + } + case 1: /* iBCS2 emulator entry point */ + err = -EINVAL; + goto out; + } + case SHMDT: + err = sys_shmdt ((char __user *)ptr); + goto out; + case SHMGET: + err = sys_shmget (first, second, third); + goto out; + case SHMCTL: + err = sys_shmctl (first, second, (struct shmid_ds __user *) ptr); + goto out; + default: + err = -ENOSYS; + goto out; + } + else + err = -ENOSYS; +out: + return err; +} + +/* Linux version of mmap */ +static unsigned long do_mmap2(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, unsigned long fd, + unsigned long pgoff) +{ + struct file * file = NULL; + unsigned long retval = -EBADF; + + if (!(flags & MAP_ANONYMOUS)) { + file = fget(fd); + if (!file) + goto out; + } + + retval = -EINVAL; + len = PAGE_ALIGN(len); + if (ARCH_SUN4C_SUN4 && + (len > 0x20000000 || + ((flags & MAP_FIXED) && + addr < 0xe0000000 && addr + len > 0x20000000))) + goto out_putf; + + /* See asm-sparc/uaccess.h */ + if (len > TASK_SIZE - PAGE_SIZE || addr + len > TASK_SIZE - PAGE_SIZE) + goto out_putf; + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + + down_write(¤t->mm->mmap_sem); + retval = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); + up_write(¤t->mm->mmap_sem); + +out_putf: + if (file) + fput(file); +out: + return retval; +} + +asmlinkage unsigned long sys_mmap2(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, unsigned long fd, + unsigned long pgoff) +{ + /* Make sure the shift for mmap2 is constant (12), no matter what PAGE_SIZE + we have. */ + return do_mmap2(addr, len, prot, flags, fd, pgoff >> (PAGE_SHIFT - 12)); +} + +asmlinkage unsigned long sys_mmap(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, unsigned long fd, + unsigned long off) +{ + return do_mmap2(addr, len, prot, flags, fd, off >> PAGE_SHIFT); +} + +long sparc_remap_file_pages(unsigned long start, unsigned long size, + unsigned long prot, unsigned long pgoff, + unsigned long flags) +{ + /* This works on an existing mmap so we don't need to validate + * the range as that was done at the original mmap call. + */ + return sys_remap_file_pages(start, size, prot, + (pgoff >> (PAGE_SHIFT - 12)), flags); +} + +extern unsigned long do_mremap(unsigned long addr, + unsigned long old_len, unsigned long new_len, + unsigned long flags, unsigned long new_addr); + +asmlinkage unsigned long sparc_mremap(unsigned long addr, + unsigned long old_len, unsigned long new_len, + unsigned long flags, unsigned long new_addr) +{ + struct vm_area_struct *vma; + unsigned long ret = -EINVAL; + if (ARCH_SUN4C_SUN4) { + if (old_len > 0x20000000 || new_len > 0x20000000) + goto out; + if (addr < 0xe0000000 && addr + old_len > 0x20000000) + goto out; + } + if (old_len > TASK_SIZE - PAGE_SIZE || + new_len > TASK_SIZE - PAGE_SIZE) + goto out; + down_write(¤t->mm->mmap_sem); + if (flags & MREMAP_FIXED) { + if (ARCH_SUN4C_SUN4 && + new_addr < 0xe0000000 && + new_addr + new_len > 0x20000000) + goto out_sem; + if (new_addr + new_len > TASK_SIZE - PAGE_SIZE) + goto out_sem; + } else if ((ARCH_SUN4C_SUN4 && addr < 0xe0000000 && + addr + new_len > 0x20000000) || + addr + new_len > TASK_SIZE - PAGE_SIZE) { + unsigned long map_flags = 0; + struct file *file = NULL; + + ret = -ENOMEM; + if (!(flags & MREMAP_MAYMOVE)) + goto out_sem; + + vma = find_vma(current->mm, addr); + if (vma) { + if (vma->vm_flags & VM_SHARED) + map_flags |= MAP_SHARED; + file = vma->vm_file; + } + + new_addr = get_unmapped_area(file, addr, new_len, + vma ? vma->vm_pgoff : 0, + map_flags); + ret = new_addr; + if (new_addr & ~PAGE_MASK) + goto out_sem; + flags |= MREMAP_FIXED; + } + ret = do_mremap(addr, old_len, new_len, flags, new_addr); +out_sem: + up_write(¤t->mm->mmap_sem); +out: + return ret; +} + +/* we come to here via sys_nis_syscall so it can setup the regs argument */ +asmlinkage unsigned long +c_sys_nis_syscall (struct pt_regs *regs) +{ + static int count = 0; + + if (count++ > 5) + return -ENOSYS; + printk ("%s[%d]: Unimplemented SPARC system call %d\n", + current->comm, current->pid, (int)regs->u_regs[1]); +#ifdef DEBUG_UNIMP_SYSCALL + show_regs (regs); +#endif + return -ENOSYS; +} + +/* #define DEBUG_SPARC_BREAKPOINT */ + +asmlinkage void +sparc_breakpoint (struct pt_regs *regs) +{ + siginfo_t info; + + lock_kernel(); +#ifdef DEBUG_SPARC_BREAKPOINT + printk ("TRAP: Entering kernel PC=%x, nPC=%x\n", regs->pc, regs->npc); +#endif + info.si_signo = SIGTRAP; + info.si_errno = 0; + info.si_code = TRAP_BRKPT; + info.si_addr = (void __user *)regs->pc; + info.si_trapno = 0; + force_sig_info(SIGTRAP, &info, current); + +#ifdef DEBUG_SPARC_BREAKPOINT + printk ("TRAP: Returning to space: PC=%x nPC=%x\n", regs->pc, regs->npc); +#endif + unlock_kernel(); +} + +asmlinkage int +sparc_sigaction (int sig, const struct old_sigaction __user *act, + struct old_sigaction __user *oact) +{ + struct k_sigaction new_ka, old_ka; + int ret; + + if (sig < 0) { + current->thread.new_signal = 1; + sig = -sig; + } + + if (act) { + unsigned long mask; + + if (!access_ok(VERIFY_READ, act, sizeof(*act)) || + __get_user(new_ka.sa.sa_handler, &act->sa_handler) || + __get_user(new_ka.sa.sa_restorer, &act->sa_restorer)) + return -EFAULT; + __get_user(new_ka.sa.sa_flags, &act->sa_flags); + __get_user(mask, &act->sa_mask); + siginitset(&new_ka.sa.sa_mask, mask); + new_ka.ka_restorer = NULL; + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + /* In the clone() case we could copy half consistent + * state to the user, however this could sleep and + * deadlock us if we held the signal lock on SMP. So for + * now I take the easy way out and do no locking. + */ + if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || + __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer)) + return -EFAULT; + __put_user(old_ka.sa.sa_flags, &oact->sa_flags); + __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return ret; +} + +asmlinkage long +sys_rt_sigaction(int sig, + const struct sigaction __user *act, + struct sigaction __user *oact, + void __user *restorer, + size_t sigsetsize) +{ + struct k_sigaction new_ka, old_ka; + int ret; + + /* XXX: Don't preclude handling different sized sigset_t's. */ + if (sigsetsize != sizeof(sigset_t)) + return -EINVAL; + + /* All tasks which use RT signals (effectively) use + * new style signals. + */ + current->thread.new_signal = 1; + + if (act) { + new_ka.ka_restorer = restorer; + if (copy_from_user(&new_ka.sa, act, sizeof(*act))) + return -EFAULT; + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + if (copy_to_user(oact, &old_ka.sa, sizeof(*oact))) + return -EFAULT; + } + + return ret; +} + +asmlinkage int sys_getdomainname(char __user *name, int len) +{ + int nlen; + int err = -EFAULT; + + down_read(&uts_sem); + + nlen = strlen(system_utsname.domainname) + 1; + + if (nlen < len) + len = nlen; + if (len > __NEW_UTS_LEN) + goto done; + if (copy_to_user(name, system_utsname.domainname, len)) + goto done; + err = 0; +done: + up_read(&uts_sem); + return err; +} diff --git a/arch/sparc/kernel/sys_sunos.c b/arch/sparc/kernel/sys_sunos.c new file mode 100644 index 000000000000..81c894acd0db --- /dev/null +++ b/arch/sparc/kernel/sys_sunos.c @@ -0,0 +1,1194 @@ +/* $Id: sys_sunos.c,v 1.137 2002/02/08 03:57:14 davem Exp $ + * sys_sunos.c: SunOS specific syscall compatibility support. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx) + * + * Based upon preliminary work which is: + * + * Copyright (C) 1995 Adrian M. Rodriguez (adrian@remus.rutgers.edu) + * + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/resource.h> +#include <linux/ipc.h> +#include <linux/shm.h> +#include <linux/msg.h> +#include <linux/sem.h> +#include <linux/signal.h> +#include <linux/uio.h> +#include <linux/utsname.h> +#include <linux/major.h> +#include <linux/stat.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/errno.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/syscalls.h> + +#include <net/sock.h> + +#include <asm/uaccess.h> +#ifndef KERNEL_DS +#include <linux/segment.h> +#endif + +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/pconf.h> +#include <asm/idprom.h> /* for gethostid() */ +#include <asm/unistd.h> +#include <asm/system.h> + +/* For the nfs mount emulation */ +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/nfs.h> +#include <linux/nfs2.h> +#include <linux/nfs_mount.h> + +/* for sunos_select */ +#include <linux/time.h> +#include <linux/personality.h> + +/* NR_OPEN is now larger and dynamic in recent kernels. */ +#define SUNOS_NR_OPEN 256 + +/* We use the SunOS mmap() semantics. */ +asmlinkage unsigned long sunos_mmap(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long off) +{ + struct file * file = NULL; + unsigned long retval, ret_type; + + if (flags & MAP_NORESERVE) { + static int cnt; + if (cnt++ < 10) + printk("%s: unimplemented SunOS MAP_NORESERVE mmap() flag\n", + current->comm); + flags &= ~MAP_NORESERVE; + } + retval = -EBADF; + if (!(flags & MAP_ANONYMOUS)) { + if (fd >= SUNOS_NR_OPEN) + goto out; + file = fget(fd); + if (!file) + goto out; + } + + retval = -EINVAL; + /* If this is ld.so or a shared library doing an mmap + * of /dev/zero, transform it into an anonymous mapping. + * SunOS is so stupid some times... hmph! + */ + if (file) { + if (imajor(file->f_dentry->d_inode) == MEM_MAJOR && + iminor(file->f_dentry->d_inode) == 5) { + flags |= MAP_ANONYMOUS; + fput(file); + file = NULL; + } + } + ret_type = flags & _MAP_NEW; + flags &= ~_MAP_NEW; + + if (!(flags & MAP_FIXED)) + addr = 0; + else { + if (ARCH_SUN4C_SUN4 && + (len > 0x20000000 || + ((flags & MAP_FIXED) && + addr < 0xe0000000 && addr + len > 0x20000000))) + goto out_putf; + + /* See asm-sparc/uaccess.h */ + if (len > TASK_SIZE - PAGE_SIZE || + addr + len > TASK_SIZE - PAGE_SIZE) + goto out_putf; + } + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + down_write(¤t->mm->mmap_sem); + retval = do_mmap(file, addr, len, prot, flags, off); + up_write(¤t->mm->mmap_sem); + if (!ret_type) + retval = ((retval < PAGE_OFFSET) ? 0 : retval); + +out_putf: + if (file) + fput(file); +out: + return retval; +} + +/* lmbench calls this, just say "yeah, ok" */ +asmlinkage int sunos_mctl(unsigned long addr, unsigned long len, int function, char *arg) +{ + return 0; +} + +/* SunOS is completely broken... it returns 0 on success, otherwise + * ENOMEM. For sys_sbrk() it wants the old brk value as a return + * on success and ENOMEM as before on failure. + */ +asmlinkage int sunos_brk(unsigned long brk) +{ + int freepages, retval = -ENOMEM; + unsigned long rlim; + unsigned long newbrk, oldbrk; + + down_write(¤t->mm->mmap_sem); + if (ARCH_SUN4C_SUN4) { + if (brk >= 0x20000000 && brk < 0xe0000000) { + goto out; + } + } + + if (brk < current->mm->end_code) + goto out; + + newbrk = PAGE_ALIGN(brk); + oldbrk = PAGE_ALIGN(current->mm->brk); + retval = 0; + if (oldbrk == newbrk) { + current->mm->brk = brk; + goto out; + } + + /* + * Always allow shrinking brk + */ + if (brk <= current->mm->brk) { + current->mm->brk = brk; + do_munmap(current->mm, newbrk, oldbrk-newbrk); + goto out; + } + /* + * Check against rlimit and stack.. + */ + retval = -ENOMEM; + rlim = current->signal->rlim[RLIMIT_DATA].rlim_cur; + if (rlim >= RLIM_INFINITY) + rlim = ~0; + if (brk - current->mm->end_code > rlim) + goto out; + + /* + * Check against existing mmap mappings. + */ + if (find_vma_intersection(current->mm, oldbrk, newbrk+PAGE_SIZE)) + goto out; + + /* + * stupid algorithm to decide if we have enough memory: while + * simple, it hopefully works in most obvious cases.. Easy to + * fool it, but this should catch most mistakes. + */ + freepages = get_page_cache_size(); + freepages >>= 1; + freepages += nr_free_pages(); + freepages += nr_swap_pages; + freepages -= num_physpages >> 4; + freepages -= (newbrk-oldbrk) >> PAGE_SHIFT; + if (freepages < 0) + goto out; + /* + * Ok, we have probably got enough memory - let it rip. + */ + current->mm->brk = brk; + do_brk(oldbrk, newbrk-oldbrk); + retval = 0; +out: + up_write(¤t->mm->mmap_sem); + return retval; +} + +asmlinkage unsigned long sunos_sbrk(int increment) +{ + int error; + unsigned long oldbrk; + + /* This should do it hopefully... */ + lock_kernel(); + oldbrk = current->mm->brk; + error = sunos_brk(((int) current->mm->brk) + increment); + if (!error) + error = oldbrk; + unlock_kernel(); + return error; +} + +/* XXX Completely undocumented, and completely magic... + * XXX I believe it is to increase the size of the stack by + * XXX argument 'increment' and return the new end of stack + * XXX area. Wheee... + */ +asmlinkage unsigned long sunos_sstk(int increment) +{ + lock_kernel(); + printk("%s: Call to sunos_sstk(increment<%d>) is unsupported\n", + current->comm, increment); + unlock_kernel(); + return -1; +} + +/* Give hints to the kernel as to what paging strategy to use... + * Completely bogus, don't remind me. + */ +#define VA_NORMAL 0 /* Normal vm usage expected */ +#define VA_ABNORMAL 1 /* Abnormal/random vm usage probable */ +#define VA_SEQUENTIAL 2 /* Accesses will be of a sequential nature */ +#define VA_INVALIDATE 3 /* Page table entries should be flushed ??? */ +static char *vstrings[] = { + "VA_NORMAL", + "VA_ABNORMAL", + "VA_SEQUENTIAL", + "VA_INVALIDATE", +}; + +asmlinkage void sunos_vadvise(unsigned long strategy) +{ + /* I wanna see who uses this... */ + lock_kernel(); + printk("%s: Advises us to use %s paging strategy\n", + current->comm, + strategy <= 3 ? vstrings[strategy] : "BOGUS"); + unlock_kernel(); +} + +/* This just wants the soft limit (ie. rlim_cur element) of the RLIMIT_NOFILE + * resource limit and is for backwards compatibility with older sunos + * revs. + */ +asmlinkage long sunos_getdtablesize(void) +{ + return SUNOS_NR_OPEN; +} + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +asmlinkage unsigned long sunos_sigblock(unsigned long blk_mask) +{ + unsigned long old; + + spin_lock_irq(¤t->sighand->siglock); + old = current->blocked.sig[0]; + current->blocked.sig[0] |= (blk_mask & _BLOCKABLE); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + return old; +} + +asmlinkage unsigned long sunos_sigsetmask(unsigned long newmask) +{ + unsigned long retval; + + spin_lock_irq(¤t->sighand->siglock); + retval = current->blocked.sig[0]; + current->blocked.sig[0] = (newmask & _BLOCKABLE); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + return retval; +} + +/* SunOS getdents is very similar to the newer Linux (iBCS2 compliant) */ +/* getdents system call, the format of the structure just has a different */ +/* layout (d_off+d_ino instead of d_ino+d_off) */ +struct sunos_dirent { + long d_off; + unsigned long d_ino; + unsigned short d_reclen; + unsigned short d_namlen; + char d_name[1]; +}; + +struct sunos_dirent_callback { + struct sunos_dirent __user *curr; + struct sunos_dirent __user *previous; + int count; + int error; +}; + +#define NAME_OFFSET(de) ((int) ((de)->d_name - (char __user *) (de))) +#define ROUND_UP(x) (((x)+sizeof(long)-1) & ~(sizeof(long)-1)) + +static int sunos_filldir(void * __buf, const char * name, int namlen, + loff_t offset, ino_t ino, unsigned int d_type) +{ + struct sunos_dirent __user *dirent; + struct sunos_dirent_callback * buf = __buf; + int reclen = ROUND_UP(NAME_OFFSET(dirent) + namlen + 1); + + buf->error = -EINVAL; /* only used if we fail.. */ + if (reclen > buf->count) + return -EINVAL; + dirent = buf->previous; + if (dirent) + put_user(offset, &dirent->d_off); + dirent = buf->curr; + buf->previous = dirent; + put_user(ino, &dirent->d_ino); + put_user(namlen, &dirent->d_namlen); + put_user(reclen, &dirent->d_reclen); + copy_to_user(dirent->d_name, name, namlen); + put_user(0, dirent->d_name + namlen); + dirent = (void __user *) dirent + reclen; + buf->curr = dirent; + buf->count -= reclen; + return 0; +} + +asmlinkage int sunos_getdents(unsigned int fd, void __user *dirent, int cnt) +{ + struct file * file; + struct sunos_dirent __user *lastdirent; + struct sunos_dirent_callback buf; + int error = -EBADF; + + if (fd >= SUNOS_NR_OPEN) + goto out; + + file = fget(fd); + if (!file) + goto out; + + error = -EINVAL; + if (cnt < (sizeof(struct sunos_dirent) + 255)) + goto out_putf; + + buf.curr = (struct sunos_dirent __user *) dirent; + buf.previous = NULL; + buf.count = cnt; + buf.error = 0; + + error = vfs_readdir(file, sunos_filldir, &buf); + if (error < 0) + goto out_putf; + + lastdirent = buf.previous; + error = buf.error; + if (lastdirent) { + put_user(file->f_pos, &lastdirent->d_off); + error = cnt - buf.count; + } + +out_putf: + fput(file); +out: + return error; +} + +/* Old sunos getdirentries, severely broken compatibility stuff here. */ +struct sunos_direntry { + unsigned long d_ino; + unsigned short d_reclen; + unsigned short d_namlen; + char d_name[1]; +}; + +struct sunos_direntry_callback { + struct sunos_direntry __user *curr; + struct sunos_direntry __user *previous; + int count; + int error; +}; + +static int sunos_filldirentry(void * __buf, const char * name, int namlen, + loff_t offset, ino_t ino, unsigned int d_type) +{ + struct sunos_direntry __user *dirent; + struct sunos_direntry_callback *buf = __buf; + int reclen = ROUND_UP(NAME_OFFSET(dirent) + namlen + 1); + + buf->error = -EINVAL; /* only used if we fail.. */ + if (reclen > buf->count) + return -EINVAL; + dirent = buf->previous; + dirent = buf->curr; + buf->previous = dirent; + put_user(ino, &dirent->d_ino); + put_user(namlen, &dirent->d_namlen); + put_user(reclen, &dirent->d_reclen); + copy_to_user(dirent->d_name, name, namlen); + put_user(0, dirent->d_name + namlen); + dirent = (void __user *) dirent + reclen; + buf->curr = dirent; + buf->count -= reclen; + return 0; +} + +asmlinkage int sunos_getdirentries(unsigned int fd, void __user *dirent, + int cnt, unsigned int __user *basep) +{ + struct file * file; + struct sunos_direntry __user *lastdirent; + struct sunos_direntry_callback buf; + int error = -EBADF; + + if (fd >= SUNOS_NR_OPEN) + goto out; + + file = fget(fd); + if (!file) + goto out; + + error = -EINVAL; + if (cnt < (sizeof(struct sunos_direntry) + 255)) + goto out_putf; + + buf.curr = (struct sunos_direntry __user *) dirent; + buf.previous = NULL; + buf.count = cnt; + buf.error = 0; + + error = vfs_readdir(file, sunos_filldirentry, &buf); + if (error < 0) + goto out_putf; + + lastdirent = buf.previous; + error = buf.error; + if (lastdirent) { + put_user(file->f_pos, basep); + error = cnt - buf.count; + } + +out_putf: + fput(file); +out: + return error; +} + +struct sunos_utsname { + char sname[9]; + char nname[9]; + char nnext[56]; + char rel[9]; + char ver[9]; + char mach[9]; +}; + +asmlinkage int sunos_uname(struct sunos_utsname __user *name) +{ + int ret; + down_read(&uts_sem); + ret = copy_to_user(&name->sname[0], &system_utsname.sysname[0], sizeof(name->sname) - 1); + if (!ret) { + ret |= __copy_to_user(&name->nname[0], &system_utsname.nodename[0], sizeof(name->nname) - 1); + ret |= __put_user('\0', &name->nname[8]); + ret |= __copy_to_user(&name->rel[0], &system_utsname.release[0], sizeof(name->rel) - 1); + ret |= __copy_to_user(&name->ver[0], &system_utsname.version[0], sizeof(name->ver) - 1); + ret |= __copy_to_user(&name->mach[0], &system_utsname.machine[0], sizeof(name->mach) - 1); + } + up_read(&uts_sem); + return ret ? -EFAULT : 0; +} + +asmlinkage int sunos_nosys(void) +{ + struct pt_regs *regs; + siginfo_t info; + static int cnt; + + lock_kernel(); + regs = current->thread.kregs; + info.si_signo = SIGSYS; + info.si_errno = 0; + info.si_code = __SI_FAULT|0x100; + info.si_addr = (void __user *)regs->pc; + info.si_trapno = regs->u_regs[UREG_G1]; + send_sig_info(SIGSYS, &info, current); + if (cnt++ < 4) { + printk("Process makes ni_syscall number %d, register dump:\n", + (int) regs->u_regs[UREG_G1]); + show_regs(regs); + } + unlock_kernel(); + return -ENOSYS; +} + +/* This is not a real and complete implementation yet, just to keep + * the easy SunOS binaries happy. + */ +asmlinkage int sunos_fpathconf(int fd, int name) +{ + int ret; + + switch(name) { + case _PCONF_LINK: + ret = LINK_MAX; + break; + case _PCONF_CANON: + ret = MAX_CANON; + break; + case _PCONF_INPUT: + ret = MAX_INPUT; + break; + case _PCONF_NAME: + ret = NAME_MAX; + break; + case _PCONF_PATH: + ret = PATH_MAX; + break; + case _PCONF_PIPE: + ret = PIPE_BUF; + break; + case _PCONF_CHRESTRICT: /* XXX Investigate XXX */ + ret = 1; + break; + case _PCONF_NOTRUNC: /* XXX Investigate XXX */ + case _PCONF_VDISABLE: + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +asmlinkage int sunos_pathconf(char __user *path, int name) +{ + int ret; + + ret = sunos_fpathconf(0, name); /* XXX cheese XXX */ + return ret; +} + +/* SunOS mount system call emulation */ + +asmlinkage int sunos_select(int width, fd_set __user *inp, fd_set __user *outp, + fd_set __user *exp, struct timeval __user *tvp) +{ + int ret; + + /* SunOS binaries expect that select won't change the tvp contents */ + ret = sys_select (width, inp, outp, exp, tvp); + if (ret == -EINTR && tvp) { + time_t sec, usec; + + __get_user(sec, &tvp->tv_sec); + __get_user(usec, &tvp->tv_usec); + + if (sec == 0 && usec == 0) + ret = 0; + } + return ret; +} + +asmlinkage void sunos_nop(void) +{ + return; +} + +/* SunOS mount/umount. */ +#define SMNT_RDONLY 1 +#define SMNT_NOSUID 2 +#define SMNT_NEWTYPE 4 +#define SMNT_GRPID 8 +#define SMNT_REMOUNT 16 +#define SMNT_NOSUB 32 +#define SMNT_MULTI 64 +#define SMNT_SYS5 128 + +struct sunos_fh_t { + char fh_data [NFS_FHSIZE]; +}; + +struct sunos_nfs_mount_args { + struct sockaddr_in __user *addr; /* file server address */ + struct nfs_fh __user *fh; /* File handle to be mounted */ + int flags; /* flags */ + int wsize; /* write size in bytes */ + int rsize; /* read size in bytes */ + int timeo; /* initial timeout in .1 secs */ + int retrans; /* times to retry send */ + char __user *hostname; /* server's hostname */ + int acregmin; /* attr cache file min secs */ + int acregmax; /* attr cache file max secs */ + int acdirmin; /* attr cache dir min secs */ + int acdirmax; /* attr cache dir max secs */ + char __user *netname; /* server's netname */ +}; + + +/* Bind the socket on a local reserved port and connect it to the + * remote server. This on Linux/i386 is done by the mount program, + * not by the kernel. + */ +static int +sunos_nfs_get_server_fd (int fd, struct sockaddr_in *addr) +{ + struct sockaddr_in local; + struct sockaddr_in server; + int try_port; + struct socket *socket; + struct inode *inode; + struct file *file; + int ret, result = 0; + + file = fget(fd); + if (!file) + goto out; + + inode = file->f_dentry->d_inode; + + socket = SOCKET_I(inode); + local.sin_family = AF_INET; + local.sin_addr.s_addr = INADDR_ANY; + + /* IPPORT_RESERVED = 1024, can't find the definition in the kernel */ + try_port = 1024; + do { + local.sin_port = htons (--try_port); + ret = socket->ops->bind(socket, (struct sockaddr*)&local, + sizeof(local)); + } while (ret && try_port > (1024 / 2)); + + if (ret) + goto out_putf; + + server.sin_family = AF_INET; + server.sin_addr = addr->sin_addr; + server.sin_port = NFS_PORT; + + /* Call sys_connect */ + ret = socket->ops->connect (socket, (struct sockaddr *) &server, + sizeof (server), file->f_flags); + if (ret >= 0) + result = 1; + +out_putf: + fput(file); +out: + return result; +} + +static int get_default (int value, int def_value) +{ + if (value) + return value; + else + return def_value; +} + +static int sunos_nfs_mount(char *dir_name, int linux_flags, void __user *data) +{ + int server_fd, err; + char *the_name, *mount_page; + struct nfs_mount_data linux_nfs_mount; + struct sunos_nfs_mount_args sunos_mount; + + /* Ok, here comes the fun part: Linux's nfs mount needs a + * socket connection to the server, but SunOS mount does not + * require this, so we use the information on the destination + * address to create a socket and bind it to a reserved + * port on this system + */ + if (copy_from_user(&sunos_mount, data, sizeof(sunos_mount))) + return -EFAULT; + + server_fd = sys_socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (server_fd < 0) + return -ENXIO; + + if (copy_from_user(&linux_nfs_mount.addr,sunos_mount.addr, + sizeof(*sunos_mount.addr)) || + copy_from_user(&linux_nfs_mount.root,sunos_mount.fh, + sizeof(*sunos_mount.fh))) { + sys_close (server_fd); + return -EFAULT; + } + + if (!sunos_nfs_get_server_fd (server_fd, &linux_nfs_mount.addr)){ + sys_close (server_fd); + return -ENXIO; + } + + /* Now, bind it to a locally reserved port */ + linux_nfs_mount.version = NFS_MOUNT_VERSION; + linux_nfs_mount.flags = sunos_mount.flags; + linux_nfs_mount.fd = server_fd; + + linux_nfs_mount.rsize = get_default (sunos_mount.rsize, 8192); + linux_nfs_mount.wsize = get_default (sunos_mount.wsize, 8192); + linux_nfs_mount.timeo = get_default (sunos_mount.timeo, 10); + linux_nfs_mount.retrans = sunos_mount.retrans; + + linux_nfs_mount.acregmin = sunos_mount.acregmin; + linux_nfs_mount.acregmax = sunos_mount.acregmax; + linux_nfs_mount.acdirmin = sunos_mount.acdirmin; + linux_nfs_mount.acdirmax = sunos_mount.acdirmax; + + the_name = getname(sunos_mount.hostname); + if (IS_ERR(the_name)) + return PTR_ERR(the_name); + + strlcpy(linux_nfs_mount.hostname, the_name, + sizeof(linux_nfs_mount.hostname)); + putname (the_name); + + mount_page = (char *) get_zeroed_page(GFP_KERNEL); + if (!mount_page) + return -ENOMEM; + + memcpy(mount_page, &linux_nfs_mount, sizeof(linux_nfs_mount)); + + err = do_mount("", dir_name, "nfs", linux_flags, mount_page); + + free_page((unsigned long) mount_page); + return err; +} + +asmlinkage int +sunos_mount(char __user *type, char __user *dir, int flags, void __user *data) +{ + int linux_flags = 0; + int ret = -EINVAL; + char *dev_fname = NULL; + char *dir_page, *type_page; + + if (!capable (CAP_SYS_ADMIN)) + return -EPERM; + + lock_kernel(); + /* We don't handle the integer fs type */ + if ((flags & SMNT_NEWTYPE) == 0) + goto out; + + /* Do not allow for those flags we don't support */ + if (flags & (SMNT_GRPID|SMNT_NOSUB|SMNT_MULTI|SMNT_SYS5)) + goto out; + + if (flags & SMNT_REMOUNT) + linux_flags |= MS_REMOUNT; + if (flags & SMNT_RDONLY) + linux_flags |= MS_RDONLY; + if (flags & SMNT_NOSUID) + linux_flags |= MS_NOSUID; + + dir_page = getname(dir); + ret = PTR_ERR(dir_page); + if (IS_ERR(dir_page)) + goto out; + + type_page = getname(type); + ret = PTR_ERR(type_page); + if (IS_ERR(type_page)) + goto out1; + + if (strcmp(type_page, "ext2") == 0) { + dev_fname = getname(data); + } else if (strcmp(type_page, "iso9660") == 0) { + dev_fname = getname(data); + } else if (strcmp(type_page, "minix") == 0) { + dev_fname = getname(data); + } else if (strcmp(type_page, "nfs") == 0) { + ret = sunos_nfs_mount (dir_page, flags, data); + goto out2; + } else if (strcmp(type_page, "ufs") == 0) { + printk("Warning: UFS filesystem mounts unsupported.\n"); + ret = -ENODEV; + goto out2; + } else if (strcmp(type_page, "proc")) { + ret = -ENODEV; + goto out2; + } + ret = PTR_ERR(dev_fname); + if (IS_ERR(dev_fname)) + goto out2; + ret = do_mount(dev_fname, dir_page, type_page, linux_flags, NULL); + if (dev_fname) + putname(dev_fname); +out2: + putname(type_page); +out1: + putname(dir_page); +out: + unlock_kernel(); + return ret; +} + + +asmlinkage int sunos_setpgrp(pid_t pid, pid_t pgid) +{ + int ret; + + /* So stupid... */ + if ((!pid || pid == current->pid) && + !pgid) { + sys_setsid(); + ret = 0; + } else { + ret = sys_setpgid(pid, pgid); + } + return ret; +} + +/* So stupid... */ +asmlinkage int sunos_wait4(pid_t pid, unsigned int __user *stat_addr, + int options, struct rusage __user*ru) +{ + int ret; + + ret = sys_wait4((pid ? pid : -1), stat_addr, options, ru); + return ret; +} + +extern int kill_pg(int, int, int); +asmlinkage int sunos_killpg(int pgrp, int sig) +{ + int ret; + + lock_kernel(); + ret = kill_pg(pgrp, sig, 0); + unlock_kernel(); + return ret; +} + +asmlinkage int sunos_audit(void) +{ + lock_kernel(); + printk ("sys_audit\n"); + unlock_kernel(); + return -1; +} + +asmlinkage unsigned long sunos_gethostid(void) +{ + unsigned long ret; + + lock_kernel(); + ret = ((unsigned long)idprom->id_machtype << 24) | + (unsigned long)idprom->id_sernum; + unlock_kernel(); + return ret; +} + +/* sysconf options, for SunOS compatibility */ +#define _SC_ARG_MAX 1 +#define _SC_CHILD_MAX 2 +#define _SC_CLK_TCK 3 +#define _SC_NGROUPS_MAX 4 +#define _SC_OPEN_MAX 5 +#define _SC_JOB_CONTROL 6 +#define _SC_SAVED_IDS 7 +#define _SC_VERSION 8 + +asmlinkage long sunos_sysconf (int name) +{ + long ret; + + switch (name){ + case _SC_ARG_MAX: + ret = ARG_MAX; + break; + case _SC_CHILD_MAX: + ret = CHILD_MAX; + break; + case _SC_CLK_TCK: + ret = HZ; + break; + case _SC_NGROUPS_MAX: + ret = NGROUPS_MAX; + break; + case _SC_OPEN_MAX: + ret = OPEN_MAX; + break; + case _SC_JOB_CONTROL: + ret = 1; /* yes, we do support job control */ + break; + case _SC_SAVED_IDS: + ret = 1; /* yes, we do support saved uids */ + break; + case _SC_VERSION: + /* mhm, POSIX_VERSION is in /usr/include/unistd.h + * should it go on /usr/include/linux? + */ + ret = 199009L; + break; + default: + ret = -1; + break; + }; + return ret; +} + +asmlinkage int sunos_semsys(int op, unsigned long arg1, unsigned long arg2, + unsigned long arg3, void *ptr) +{ + union semun arg4; + int ret; + + switch (op) { + case 0: + /* Most arguments match on a 1:1 basis but cmd doesn't */ + switch(arg3) { + case 4: + arg3=GETPID; break; + case 5: + arg3=GETVAL; break; + case 6: + arg3=GETALL; break; + case 3: + arg3=GETNCNT; break; + case 7: + arg3=GETZCNT; break; + case 8: + arg3=SETVAL; break; + case 9: + arg3=SETALL; break; + } + /* sys_semctl(): */ + /* value to modify semaphore to */ + arg4.__pad = (void __user *) ptr; + ret = sys_semctl((int)arg1, (int)arg2, (int)arg3, arg4 ); + break; + case 1: + /* sys_semget(): */ + ret = sys_semget((key_t)arg1, (int)arg2, (int)arg3); + break; + case 2: + /* sys_semop(): */ + ret = sys_semop((int)arg1, (struct sembuf __user *)arg2, (unsigned)arg3); + break; + default: + ret = -EINVAL; + break; + }; + return ret; +} + +asmlinkage int sunos_msgsys(int op, unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4) +{ + struct sparc_stackf *sp; + unsigned long arg5; + int rval; + + switch(op) { + case 0: + rval = sys_msgget((key_t)arg1, (int)arg2); + break; + case 1: + rval = sys_msgctl((int)arg1, (int)arg2, + (struct msqid_ds __user *)arg3); + break; + case 2: + lock_kernel(); + sp = (struct sparc_stackf *)current->thread.kregs->u_regs[UREG_FP]; + arg5 = sp->xxargs[0]; + unlock_kernel(); + rval = sys_msgrcv((int)arg1, (struct msgbuf __user *)arg2, + (size_t)arg3, (long)arg4, (int)arg5); + break; + case 3: + rval = sys_msgsnd((int)arg1, (struct msgbuf __user *)arg2, + (size_t)arg3, (int)arg4); + break; + default: + rval = -EINVAL; + break; + } + return rval; +} + +asmlinkage int sunos_shmsys(int op, unsigned long arg1, unsigned long arg2, + unsigned long arg3) +{ + unsigned long raddr; + int rval; + + switch(op) { + case 0: + /* do_shmat(): attach a shared memory area */ + rval = do_shmat((int)arg1,(char __user *)arg2,(int)arg3,&raddr); + if (!rval) + rval = (int) raddr; + break; + case 1: + /* sys_shmctl(): modify shared memory area attr. */ + rval = sys_shmctl((int)arg1,(int)arg2,(struct shmid_ds __user *)arg3); + break; + case 2: + /* sys_shmdt(): detach a shared memory area */ + rval = sys_shmdt((char __user *)arg1); + break; + case 3: + /* sys_shmget(): get a shared memory area */ + rval = sys_shmget((key_t)arg1,(int)arg2,(int)arg3); + break; + default: + rval = -EINVAL; + break; + }; + return rval; +} + +#define SUNOS_EWOULDBLOCK 35 + +/* see the sunos man page read(2v) for an explanation + of this garbage. We use O_NDELAY to mark + file descriptors that have been set non-blocking + using 4.2BSD style calls. (tridge) */ + +static inline int check_nonblock(int ret, int fd) +{ + if (ret == -EAGAIN) { + struct file * file = fget(fd); + if (file) { + if (file->f_flags & O_NDELAY) + ret = -SUNOS_EWOULDBLOCK; + fput(file); + } + } + return ret; +} + +asmlinkage int sunos_read(unsigned int fd, char __user *buf, int count) +{ + int ret; + + ret = check_nonblock(sys_read(fd,buf,count),fd); + return ret; +} + +asmlinkage int sunos_readv(unsigned long fd, const struct iovec __user *vector, + long count) +{ + int ret; + + ret = check_nonblock(sys_readv(fd,vector,count),fd); + return ret; +} + +asmlinkage int sunos_write(unsigned int fd, char __user *buf, int count) +{ + int ret; + + ret = check_nonblock(sys_write(fd,buf,count),fd); + return ret; +} + +asmlinkage int sunos_writev(unsigned long fd, + const struct iovec __user *vector, long count) +{ + int ret; + + ret = check_nonblock(sys_writev(fd,vector,count),fd); + return ret; +} + +asmlinkage int sunos_recv(int fd, void __user *ubuf, int size, unsigned flags) +{ + int ret; + + ret = check_nonblock(sys_recv(fd,ubuf,size,flags),fd); + return ret; +} + +asmlinkage int sunos_send(int fd, void __user *buff, int len, unsigned flags) +{ + int ret; + + ret = check_nonblock(sys_send(fd,buff,len,flags),fd); + return ret; +} + +asmlinkage int sunos_accept(int fd, struct sockaddr __user *sa, + int __user *addrlen) +{ + int ret; + + while (1) { + ret = check_nonblock(sys_accept(fd,sa,addrlen),fd); + if (ret != -ENETUNREACH && ret != -EHOSTUNREACH) + break; + } + + return ret; +} + +#define SUNOS_SV_INTERRUPT 2 + +asmlinkage int +sunos_sigaction(int sig, const struct old_sigaction __user *act, + struct old_sigaction __user *oact) +{ + struct k_sigaction new_ka, old_ka; + int ret; + + if (act) { + old_sigset_t mask; + + if (!access_ok(VERIFY_READ, act, sizeof(*act)) || + __get_user(new_ka.sa.sa_handler, &act->sa_handler) || + __get_user(new_ka.sa.sa_flags, &act->sa_flags)) + return -EFAULT; + __get_user(mask, &act->sa_mask); + new_ka.sa.sa_restorer = NULL; + new_ka.ka_restorer = NULL; + siginitset(&new_ka.sa.sa_mask, mask); + new_ka.sa.sa_flags ^= SUNOS_SV_INTERRUPT; + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + /* In the clone() case we could copy half consistent + * state to the user, however this could sleep and + * deadlock us if we held the signal lock on SMP. So for + * now I take the easy way out and do no locking. + * But then again we don't support SunOS lwp's anyways ;-) + */ + old_ka.sa.sa_flags ^= SUNOS_SV_INTERRUPT; + if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || + __put_user(old_ka.sa.sa_flags, &oact->sa_flags)) + return -EFAULT; + __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return ret; +} + + +asmlinkage int sunos_setsockopt(int fd, int level, int optname, + char __user *optval, int optlen) +{ + int tr_opt = optname; + int ret; + + if (level == SOL_IP) { + /* Multicast socketopts (ttl, membership) */ + if (tr_opt >=2 && tr_opt <= 6) + tr_opt += 30; + } + ret = sys_setsockopt(fd, level, tr_opt, optval, optlen); + return ret; +} + +asmlinkage int sunos_getsockopt(int fd, int level, int optname, + char __user *optval, int __user *optlen) +{ + int tr_opt = optname; + int ret; + + if (level == SOL_IP) { + /* Multicast socketopts (ttl, membership) */ + if (tr_opt >=2 && tr_opt <= 6) + tr_opt += 30; + } + ret = sys_getsockopt(fd, level, tr_opt, optval, optlen); + return ret; +} diff --git a/arch/sparc/kernel/systbls.S b/arch/sparc/kernel/systbls.S new file mode 100644 index 000000000000..928ffeb0fabb --- /dev/null +++ b/arch/sparc/kernel/systbls.S @@ -0,0 +1,186 @@ +/* $Id: systbls.S,v 1.103 2002/02/08 03:57:14 davem Exp $ + * systbls.S: System call entry point tables for OS compatibility. + * The native Linux system call table lives here also. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * + * Based upon preliminary work which is: + * + * Copyright (C) 1995 Adrian M. Rodriguez (adrian@remus.rutgers.edu) + */ + +#include <linux/config.h> + + .data + .align 4 + + /* First, the Linux native syscall table. */ + + .globl sys_call_table +sys_call_table: +/*0*/ .long sys_restart_syscall, sys_exit, sys_fork, sys_read, sys_write +/*5*/ .long sys_open, sys_close, sys_wait4, sys_creat, sys_link +/*10*/ .long sys_unlink, sunos_execv, sys_chdir, sys_chown16, sys_mknod +/*15*/ .long sys_chmod, sys_lchown16, sparc_brk, sys_nis_syscall, sys_lseek +/*20*/ .long sys_getpid, sys_capget, sys_capset, sys_setuid16, sys_getuid16 +/*25*/ .long sys_time, sys_ptrace, sys_alarm, sys_sigaltstack, sys_pause +/*30*/ .long sys_utime, sys_lchown, sys_fchown, sys_access, sys_nice +/*35*/ .long sys_chown, sys_sync, sys_kill, sys_newstat, sys_sendfile +/*40*/ .long sys_newlstat, sys_dup, sys_pipe, sys_times, sys_getuid +/*45*/ .long sys_umount, sys_setgid16, sys_getgid16, sys_signal, sys_geteuid16 +/*50*/ .long sys_getegid16, sys_acct, sys_nis_syscall, sys_getgid, sys_ioctl +/*55*/ .long sys_reboot, sys_mmap2, sys_symlink, sys_readlink, sys_execve +/*60*/ .long sys_umask, sys_chroot, sys_newfstat, sys_fstat64, sys_getpagesize +/*65*/ .long sys_msync, sys_vfork, sys_pread64, sys_pwrite64, sys_geteuid +/*70*/ .long sys_getegid, sys_mmap, sys_setreuid, sys_munmap, sys_mprotect +/*75*/ .long sys_madvise, sys_vhangup, sys_truncate64, sys_mincore, sys_getgroups16 +/*80*/ .long sys_setgroups16, sys_getpgrp, sys_setgroups, sys_setitimer, sys_ftruncate64 +/*85*/ .long sys_swapon, sys_getitimer, sys_setuid, sys_sethostname, sys_setgid +/*90*/ .long sys_dup2, sys_setfsuid, sys_fcntl, sys_select, sys_setfsgid +/*95*/ .long sys_fsync, sys_setpriority, sys_nis_syscall, sys_nis_syscall, sys_nis_syscall +/*100*/ .long sys_getpriority, sys_rt_sigreturn, sys_rt_sigaction, sys_rt_sigprocmask, sys_rt_sigpending +/*105*/ .long sys_rt_sigtimedwait, sys_rt_sigqueueinfo, sys_rt_sigsuspend, sys_setresuid, sys_getresuid +/*110*/ .long sys_setresgid, sys_getresgid, sys_setregid, sys_nis_syscall, sys_nis_syscall +/*115*/ .long sys_getgroups, sys_gettimeofday, sys_getrusage, sys_nis_syscall, sys_getcwd +/*120*/ .long sys_readv, sys_writev, sys_settimeofday, sys_fchown16, sys_fchmod +/*125*/ .long sys_nis_syscall, sys_setreuid16, sys_setregid16, sys_rename, sys_truncate +/*130*/ .long sys_ftruncate, sys_flock, sys_lstat64, sys_nis_syscall, sys_nis_syscall +/*135*/ .long sys_nis_syscall, sys_mkdir, sys_rmdir, sys_utimes, sys_stat64 +/*140*/ .long sys_sendfile64, sys_nis_syscall, sys_futex, sys_gettid, sys_getrlimit +/*145*/ .long sys_setrlimit, sys_pivot_root, sys_prctl, sys_pciconfig_read, sys_pciconfig_write +/*150*/ .long sys_nis_syscall, sys_nis_syscall, sys_nis_syscall, sys_poll, sys_getdents64 +/*155*/ .long sys_fcntl64, sys_ni_syscall, sys_statfs, sys_fstatfs, sys_oldumount +/*160*/ .long sys_sched_setaffinity, sys_sched_getaffinity, sys_getdomainname, sys_setdomainname, sys_nis_syscall +/*165*/ .long sys_quotactl, sys_set_tid_address, sys_mount, sys_ustat, sys_setxattr +/*170*/ .long sys_lsetxattr, sys_fsetxattr, sys_getxattr, sys_lgetxattr, sys_getdents +/*175*/ .long sys_setsid, sys_fchdir, sys_fgetxattr, sys_listxattr, sys_llistxattr +/*180*/ .long sys_flistxattr, sys_removexattr, sys_lremovexattr, sys_sigpending, sys_ni_syscall +/*185*/ .long sys_setpgid, sys_fremovexattr, sys_tkill, sys_exit_group, sys_newuname +/*190*/ .long sys_init_module, sys_personality, sparc_remap_file_pages, sys_epoll_create, sys_epoll_ctl +/*195*/ .long sys_epoll_wait, sys_nis_syscall, sys_getppid, sparc_sigaction, sys_sgetmask +/*200*/ .long sys_ssetmask, sys_sigsuspend, sys_newlstat, sys_uselib, old_readdir +/*205*/ .long sys_readahead, sys_socketcall, sys_syslog, sys_lookup_dcookie, sys_fadvise64 +/*210*/ .long sys_fadvise64_64, sys_tgkill, sys_waitpid, sys_swapoff, sys_sysinfo +/*215*/ .long sys_ipc, sys_sigreturn, sys_clone, sys_nis_syscall, sys_adjtimex +/*220*/ .long sys_sigprocmask, sys_ni_syscall, sys_delete_module, sys_ni_syscall, sys_getpgid +/*225*/ .long sys_bdflush, sys_sysfs, sys_nis_syscall, sys_setfsuid16, sys_setfsgid16 +/*230*/ .long sys_select, sys_time, sys_nis_syscall, sys_stime, sys_statfs64 + /* "We are the Knights of the Forest of Ni!!" */ +/*235*/ .long sys_fstatfs64, sys_llseek, sys_mlock, sys_munlock, sys_mlockall +/*240*/ .long sys_munlockall, sys_sched_setparam, sys_sched_getparam, sys_sched_setscheduler, sys_sched_getscheduler +/*245*/ .long sys_sched_yield, sys_sched_get_priority_max, sys_sched_get_priority_min, sys_sched_rr_get_interval, sys_nanosleep +/*250*/ .long sparc_mremap, sys_sysctl, sys_getsid, sys_fdatasync, sys_nfsservctl +/*255*/ .long sys_nis_syscall, sys_clock_settime, sys_clock_gettime, sys_clock_getres, sys_clock_nanosleep +/*260*/ .long sys_sched_getaffinity, sys_sched_setaffinity, sys_timer_settime, sys_timer_gettime, sys_timer_getoverrun +/*265*/ .long sys_timer_delete, sys_timer_create, sys_nis_syscall, sys_io_setup, sys_io_destroy +/*270*/ .long sys_io_submit, sys_io_cancel, sys_io_getevents, sys_mq_open, sys_mq_unlink +/*275*/ .long sys_mq_timedsend, sys_mq_timedreceive, sys_mq_notify, sys_mq_getsetattr, sys_waitid +/*280*/ .long sys_ni_syscall, sys_add_key, sys_request_key, sys_keyctl + +#ifdef CONFIG_SUNOS_EMUL + /* Now the SunOS syscall table. */ + + .align 4 + .globl sunos_sys_table +sunos_sys_table: +/*0*/ .long sunos_indir, sys_exit, sys_fork + .long sunos_read, sunos_write, sys_open + .long sys_close, sunos_wait4, sys_creat + .long sys_link, sys_unlink, sunos_execv + .long sys_chdir, sunos_nosys, sys_mknod + .long sys_chmod, sys_lchown16, sunos_brk + .long sunos_nosys, sys_lseek, sunos_getpid + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_getuid, sunos_nosys, sys_ptrace + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sys_access, sunos_nosys, sunos_nosys + .long sys_sync, sys_kill, sys_newstat + .long sunos_nosys, sys_newlstat, sys_dup + .long sys_pipe, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_getgid + .long sunos_nosys, sunos_nosys +/*50*/ .long sunos_nosys, sys_acct, sunos_nosys + .long sunos_mctl, sunos_ioctl, sys_reboot + .long sunos_nosys, sys_symlink, sys_readlink + .long sys_execve, sys_umask, sys_chroot + .long sys_newfstat, sunos_nosys, sys_getpagesize + .long sys_msync, sys_vfork, sunos_nosys + .long sunos_nosys, sunos_sbrk, sunos_sstk + .long sunos_mmap, sunos_vadvise, sys_munmap + .long sys_mprotect, sys_madvise, sys_vhangup + .long sunos_nosys, sys_mincore, sys_getgroups16 + .long sys_setgroups16, sys_getpgrp, sunos_setpgrp + .long sys_setitimer, sunos_nosys, sys_swapon + .long sys_getitimer, sys_gethostname, sys_sethostname + .long sunos_getdtablesize, sys_dup2, sunos_nop + .long sys_fcntl, sunos_select, sunos_nop + .long sys_fsync, sys_setpriority, sys_socket + .long sys_connect, sunos_accept +/*100*/ .long sys_getpriority, sunos_send, sunos_recv + .long sunos_nosys, sys_bind, sunos_setsockopt + .long sys_listen, sunos_nosys, sunos_sigaction + .long sunos_sigblock, sunos_sigsetmask, sys_sigpause + .long sys_sigstack, sys_recvmsg, sys_sendmsg + .long sunos_nosys, sys_gettimeofday, sys_getrusage + .long sunos_getsockopt, sunos_nosys, sunos_readv + .long sunos_writev, sys_settimeofday, sys_fchown16 + .long sys_fchmod, sys_recvfrom, sys_setreuid16 + .long sys_setregid16, sys_rename, sys_truncate + .long sys_ftruncate, sys_flock, sunos_nosys + .long sys_sendto, sys_shutdown, sys_socketpair + .long sys_mkdir, sys_rmdir, sys_utimes + .long sys_sigreturn, sunos_nosys, sys_getpeername + .long sunos_gethostid, sunos_nosys, sys_getrlimit + .long sys_setrlimit, sunos_killpg, sunos_nosys + .long sunos_nosys, sunos_nosys +/*150*/ .long sys_getsockname, sunos_nosys, sunos_nosys + .long sys_poll, sunos_nosys, sunos_nosys + .long sunos_getdirentries, sys_statfs, sys_fstatfs + .long sys_oldumount, sunos_nosys, sunos_nosys + .long sys_getdomainname, sys_setdomainname + .long sunos_nosys, sys_quotactl, sunos_nosys + .long sunos_mount, sys_ustat, sunos_semsys + .long sunos_msgsys, sunos_shmsys, sunos_audit + .long sunos_nosys, sunos_getdents, sys_setsid + .long sys_fchdir, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sys_sigpending, sunos_nosys + .long sys_setpgid, sunos_pathconf, sunos_fpathconf + .long sunos_sysconf, sunos_uname, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys +/*200*/ .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys +/*250*/ .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys +/*260*/ .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys +/*270*/ .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys +/*280*/ .long sunos_nosys, sunos_nosys, sunos_nosys + .long sunos_nosys + +#endif diff --git a/arch/sparc/kernel/tadpole.c b/arch/sparc/kernel/tadpole.c new file mode 100644 index 000000000000..f476a5f4af6a --- /dev/null +++ b/arch/sparc/kernel/tadpole.c @@ -0,0 +1,126 @@ +/* tadpole.c: Probing for the tadpole clock stopping h/w at boot time. + * + * Copyright (C) 1996 David Redman (djhr@tadpole.co.uk) + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/init.h> + +#include <asm/asi.h> +#include <asm/oplib.h> +#include <asm/io.h> + +#define MACIO_SCSI_CSR_ADDR 0x78400000 +#define MACIO_EN_DMA 0x00000200 +#define CLOCK_INIT_DONE 1 + +static int clk_state; +static volatile unsigned char *clk_ctrl; +void (*cpu_pwr_save)(void); + +static inline unsigned int ldphys(unsigned int addr) +{ + unsigned long data; + + __asm__ __volatile__("\n\tlda [%1] %2, %0\n\t" : + "=r" (data) : + "r" (addr), "i" (ASI_M_BYPASS)); + return data; +} + +static void clk_init(void) +{ + __asm__ __volatile__("mov 0x6c, %%g1\n\t" + "mov 0x4c, %%g2\n\t" + "mov 0xdf, %%g3\n\t" + "stb %%g1, [%0+3]\n\t" + "stb %%g2, [%0+3]\n\t" + "stb %%g3, [%0+3]\n\t" : : + "r" (clk_ctrl) : + "g1", "g2", "g3"); +} + +static void clk_slow(void) +{ + __asm__ __volatile__("mov 0xcc, %%g2\n\t" + "mov 0x4c, %%g3\n\t" + "mov 0xcf, %%g4\n\t" + "mov 0xdf, %%g5\n\t" + "stb %%g2, [%0+3]\n\t" + "stb %%g3, [%0+3]\n\t" + "stb %%g4, [%0+3]\n\t" + "stb %%g5, [%0+3]\n\t" : : + "r" (clk_ctrl) : + "g2", "g3", "g4", "g5"); +} + +/* + * Tadpole is guaranteed to be UP, using local_irq_save. + */ +static void tsu_clockstop(void) +{ + unsigned int mcsr; + unsigned long flags; + + if (!clk_ctrl) + return; + if (!(clk_state & CLOCK_INIT_DONE)) { + local_irq_save(flags); + clk_init(); + clk_state |= CLOCK_INIT_DONE; /* all done */ + local_irq_restore(flags); + return; + } + if (!(clk_ctrl[2] & 1)) + return; /* no speed up yet */ + + local_irq_save(flags); + + /* if SCSI DMA in progress, don't slow clock */ + mcsr = ldphys(MACIO_SCSI_CSR_ADDR); + if ((mcsr&MACIO_EN_DMA) != 0) { + local_irq_restore(flags); + return; + } + /* TODO... the minimum clock setting ought to increase the + * memory refresh interval.. + */ + clk_slow(); + local_irq_restore(flags); +} + +static void swift_clockstop(void) +{ + if (!clk_ctrl) + return; + clk_ctrl[0] = 0; +} + +void __init clock_stop_probe(void) +{ + unsigned int node, clk_nd; + char name[20]; + + prom_getstring(prom_root_node, "name", name, sizeof(name)); + if (strncmp(name, "Tadpole", 7)) + return; + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(node, "obio"); + node = prom_getchild(node); + clk_nd = prom_searchsiblings(node, "clk-ctrl"); + if (!clk_nd) + return; + printk("Clock Stopping h/w detected... "); + clk_ctrl = (char *) prom_getint(clk_nd, "address"); + clk_state = 0; + if (name[10] == '\0') { + cpu_pwr_save = tsu_clockstop; + printk("enabled (S3)\n"); + } else if ((name[10] == 'X') || (name[10] == 'G')) { + cpu_pwr_save = swift_clockstop; + printk("enabled (%s)\n",name+7); + } else + printk("disabled %s\n",name+7); +} diff --git a/arch/sparc/kernel/tick14.c b/arch/sparc/kernel/tick14.c new file mode 100644 index 000000000000..fd8005a3e6bd --- /dev/null +++ b/arch/sparc/kernel/tick14.c @@ -0,0 +1,85 @@ +/* tick14.c + * linux/arch/sparc/kernel/tick14.c + * + * Copyright (C) 1996 David Redman (djhr@tadpole.co.uk) + * + * This file handles the Sparc specific level14 ticker + * This is really useful for profiling OBP uses it for keyboard + * aborts and other stuff. + * + * + */ +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/timex.h> +#include <linux/interrupt.h> + +#include <asm/oplib.h> +#include <asm/segment.h> +#include <asm/timer.h> +#include <asm/mostek.h> +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/io.h> + +extern unsigned long lvl14_save[5]; +static unsigned long *linux_lvl14 = NULL; +static unsigned long obp_lvl14[4]; + +/* + * Call with timer IRQ closed. + * First time we do it with disable_irq, later prom code uses spin_lock_irq(). + */ +void install_linux_ticker(void) +{ + + if (!linux_lvl14) + return; + linux_lvl14[0] = lvl14_save[0]; + linux_lvl14[1] = lvl14_save[1]; + linux_lvl14[2] = lvl14_save[2]; + linux_lvl14[3] = lvl14_save[3]; +} + +void install_obp_ticker(void) +{ + + if (!linux_lvl14) + return; + linux_lvl14[0] = obp_lvl14[0]; + linux_lvl14[1] = obp_lvl14[1]; + linux_lvl14[2] = obp_lvl14[2]; + linux_lvl14[3] = obp_lvl14[3]; +} + +void claim_ticker14(irqreturn_t (*handler)(int, void *, struct pt_regs *), + int irq_nr, unsigned int timeout ) +{ + int cpu = smp_processor_id(); + + /* first we copy the obp handler instructions + */ + disable_irq(irq_nr); + if (!handler) + return; + + linux_lvl14 = (unsigned long *)lvl14_save[4]; + obp_lvl14[0] = linux_lvl14[0]; + obp_lvl14[1] = linux_lvl14[1]; + obp_lvl14[2] = linux_lvl14[2]; + obp_lvl14[3] = linux_lvl14[3]; + + if (!request_irq(irq_nr, + handler, + (SA_INTERRUPT | SA_STATIC_ALLOC), + "counter14", + NULL)) { + install_linux_ticker(); + load_profile_irq(cpu, timeout); + enable_irq(irq_nr); + } +} diff --git a/arch/sparc/kernel/time.c b/arch/sparc/kernel/time.c new file mode 100644 index 000000000000..6486cbf2efe9 --- /dev/null +++ b/arch/sparc/kernel/time.c @@ -0,0 +1,641 @@ +/* $Id: time.c,v 1.60 2002/01/23 14:33:55 davem Exp $ + * linux/arch/sparc/kernel/time.c + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Thomas K. Dyas (tdyas@eden.rutgers.edu) + * + * Chris Davis (cdavis@cois.on.ca) 03/27/1998 + * Added support for the intersil on the sun4/4200 + * + * Gleb Raiko (rajko@mech.math.msu.su) 08/18/1998 + * Support for MicroSPARC-IIep, PCI CPU. + * + * This file handles the Sparc specific time handling details. + * + * 1997-09-10 Updated NTP code according to technical memorandum Jan '96 + * "A Kernel Model for Precision Timekeeping" by Dave Mills + */ +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/timex.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/profile.h> + +#include <asm/oplib.h> +#include <asm/segment.h> +#include <asm/timer.h> +#include <asm/mostek.h> +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/idprom.h> +#include <asm/machines.h> +#include <asm/sun4paddr.h> +#include <asm/page.h> +#include <asm/pcic.h> + +extern unsigned long wall_jiffies; + +u64 jiffies_64 = INITIAL_JIFFIES; + +EXPORT_SYMBOL(jiffies_64); + +DEFINE_SPINLOCK(rtc_lock); +enum sparc_clock_type sp_clock_typ; +DEFINE_SPINLOCK(mostek_lock); +void __iomem *mstk48t02_regs = NULL; +static struct mostek48t08 *mstk48t08_regs = NULL; +static int set_rtc_mmss(unsigned long); +static int sbus_do_settimeofday(struct timespec *tv); + +#ifdef CONFIG_SUN4 +struct intersil *intersil_clock; +#define intersil_cmd(intersil_reg, intsil_cmd) intersil_reg->int_cmd_reg = \ + (intsil_cmd) + +#define intersil_intr(intersil_reg, intsil_cmd) intersil_reg->int_intr_reg = \ + (intsil_cmd) + +#define intersil_start(intersil_reg) intersil_cmd(intersil_reg, \ + ( INTERSIL_START | INTERSIL_32K | INTERSIL_NORMAL | INTERSIL_24H |\ + INTERSIL_INTR_ENABLE)) + +#define intersil_stop(intersil_reg) intersil_cmd(intersil_reg, \ + ( INTERSIL_STOP | INTERSIL_32K | INTERSIL_NORMAL | INTERSIL_24H |\ + INTERSIL_INTR_ENABLE)) + +#define intersil_read_intr(intersil_reg, towhere) towhere = \ + intersil_reg->int_intr_reg + +#endif + +unsigned long profile_pc(struct pt_regs *regs) +{ + extern char __copy_user_begin[], __copy_user_end[]; + extern char __atomic_begin[], __atomic_end[]; + extern char __bzero_begin[], __bzero_end[]; + extern char __bitops_begin[], __bitops_end[]; + + unsigned long pc = regs->pc; + + if (in_lock_functions(pc) || + (pc >= (unsigned long) __copy_user_begin && + pc < (unsigned long) __copy_user_end) || + (pc >= (unsigned long) __atomic_begin && + pc < (unsigned long) __atomic_end) || + (pc >= (unsigned long) __bzero_begin && + pc < (unsigned long) __bzero_end) || + (pc >= (unsigned long) __bitops_begin && + pc < (unsigned long) __bitops_end)) + pc = regs->u_regs[UREG_RETPC]; + return pc; +} + +__volatile__ unsigned int *master_l10_counter; +__volatile__ unsigned int *master_l10_limit; + +/* + * timer_interrupt() needs to keep up the real-time clock, + * as well as call the "do_timer()" routine every clocktick + */ + +#define TICK_SIZE (tick_nsec / 1000) + +irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + /* last time the cmos clock got updated */ + static long last_rtc_update; + +#ifndef CONFIG_SMP + profile_tick(CPU_PROFILING, regs); +#endif + + /* Protect counter clear so that do_gettimeoffset works */ + write_seqlock(&xtime_lock); +#ifdef CONFIG_SUN4 + if((idprom->id_machtype == (SM_SUN4 | SM_4_260)) || + (idprom->id_machtype == (SM_SUN4 | SM_4_110))) { + int temp; + intersil_read_intr(intersil_clock, temp); + /* re-enable the irq */ + enable_pil_irq(10); + } +#endif + clear_clock_irq(); + + do_timer(regs); +#ifndef CONFIG_SMP + update_process_times(user_mode(regs)); +#endif + + + /* Determine when to update the Mostek clock. */ + if ((time_status & STA_UNSYNC) == 0 && + xtime.tv_sec > last_rtc_update + 660 && + (xtime.tv_nsec / 1000) >= 500000 - ((unsigned) TICK_SIZE) / 2 && + (xtime.tv_nsec / 1000) <= 500000 + ((unsigned) TICK_SIZE) / 2) { + if (set_rtc_mmss(xtime.tv_sec) == 0) + last_rtc_update = xtime.tv_sec; + else + last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */ + } + write_sequnlock(&xtime_lock); + + return IRQ_HANDLED; +} + +/* Kick start a stopped clock (procedure from the Sun NVRAM/hostid FAQ). */ +static void __init kick_start_clock(void) +{ + struct mostek48t02 *regs = (struct mostek48t02 *)mstk48t02_regs; + unsigned char sec; + int i, count; + + prom_printf("CLOCK: Clock was stopped. Kick start "); + + spin_lock_irq(&mostek_lock); + + /* Turn on the kick start bit to start the oscillator. */ + regs->creg |= MSTK_CREG_WRITE; + regs->sec &= ~MSTK_STOP; + regs->hour |= MSTK_KICK_START; + regs->creg &= ~MSTK_CREG_WRITE; + + spin_unlock_irq(&mostek_lock); + + /* Delay to allow the clock oscillator to start. */ + sec = MSTK_REG_SEC(regs); + for (i = 0; i < 3; i++) { + while (sec == MSTK_REG_SEC(regs)) + for (count = 0; count < 100000; count++) + /* nothing */ ; + prom_printf("."); + sec = regs->sec; + } + prom_printf("\n"); + + spin_lock_irq(&mostek_lock); + + /* Turn off kick start and set a "valid" time and date. */ + regs->creg |= MSTK_CREG_WRITE; + regs->hour &= ~MSTK_KICK_START; + MSTK_SET_REG_SEC(regs,0); + MSTK_SET_REG_MIN(regs,0); + MSTK_SET_REG_HOUR(regs,0); + MSTK_SET_REG_DOW(regs,5); + MSTK_SET_REG_DOM(regs,1); + MSTK_SET_REG_MONTH(regs,8); + MSTK_SET_REG_YEAR(regs,1996 - MSTK_YEAR_ZERO); + regs->creg &= ~MSTK_CREG_WRITE; + + spin_unlock_irq(&mostek_lock); + + /* Ensure the kick start bit is off. If it isn't, turn it off. */ + while (regs->hour & MSTK_KICK_START) { + prom_printf("CLOCK: Kick start still on!\n"); + + spin_lock_irq(&mostek_lock); + regs->creg |= MSTK_CREG_WRITE; + regs->hour &= ~MSTK_KICK_START; + regs->creg &= ~MSTK_CREG_WRITE; + spin_unlock_irq(&mostek_lock); + } + + prom_printf("CLOCK: Kick start procedure successful.\n"); +} + +/* Return nonzero if the clock chip battery is low. */ +static __inline__ int has_low_battery(void) +{ + struct mostek48t02 *regs = (struct mostek48t02 *)mstk48t02_regs; + unsigned char data1, data2; + + spin_lock_irq(&mostek_lock); + data1 = regs->eeprom[0]; /* Read some data. */ + regs->eeprom[0] = ~data1; /* Write back the complement. */ + data2 = regs->eeprom[0]; /* Read back the complement. */ + regs->eeprom[0] = data1; /* Restore the original value. */ + spin_unlock_irq(&mostek_lock); + + return (data1 == data2); /* Was the write blocked? */ +} + +/* Probe for the real time clock chip on Sun4 */ +static __inline__ void sun4_clock_probe(void) +{ +#ifdef CONFIG_SUN4 + int temp; + struct resource r; + + memset(&r, 0, sizeof(r)); + if( idprom->id_machtype == (SM_SUN4 | SM_4_330) ) { + sp_clock_typ = MSTK48T02; + r.start = sun4_clock_physaddr; + mstk48t02_regs = sbus_ioremap(&r, 0, + sizeof(struct mostek48t02), NULL); + mstk48t08_regs = NULL; /* To catch weirdness */ + intersil_clock = NULL; /* just in case */ + + /* Kick start the clock if it is completely stopped. */ + if (mostek_read(mstk48t02_regs + MOSTEK_SEC) & MSTK_STOP) + kick_start_clock(); + } else if( idprom->id_machtype == (SM_SUN4 | SM_4_260)) { + /* intersil setup code */ + printk("Clock: INTERSIL at %8x ",sun4_clock_physaddr); + sp_clock_typ = INTERSIL; + r.start = sun4_clock_physaddr; + intersil_clock = (struct intersil *) + sbus_ioremap(&r, 0, sizeof(*intersil_clock), "intersil"); + mstk48t02_regs = 0; /* just be sure */ + mstk48t08_regs = NULL; /* ditto */ + /* initialise the clock */ + + intersil_intr(intersil_clock,INTERSIL_INT_100HZ); + + intersil_start(intersil_clock); + + intersil_read_intr(intersil_clock, temp); + while (!(temp & 0x80)) + intersil_read_intr(intersil_clock, temp); + + intersil_read_intr(intersil_clock, temp); + while (!(temp & 0x80)) + intersil_read_intr(intersil_clock, temp); + + intersil_stop(intersil_clock); + + } +#endif +} + +/* Probe for the mostek real time clock chip. */ +static __inline__ void clock_probe(void) +{ + struct linux_prom_registers clk_reg[2]; + char model[128]; + register int node, cpuunit, bootbus; + struct resource r; + + cpuunit = bootbus = 0; + memset(&r, 0, sizeof(r)); + + /* Determine the correct starting PROM node for the probe. */ + node = prom_getchild(prom_root_node); + switch (sparc_cpu_model) { + case sun4c: + break; + case sun4m: + node = prom_getchild(prom_searchsiblings(node, "obio")); + break; + case sun4d: + node = prom_getchild(bootbus = prom_searchsiblings(prom_getchild(cpuunit = prom_searchsiblings(node, "cpu-unit")), "bootbus")); + break; + default: + prom_printf("CLOCK: Unsupported architecture!\n"); + prom_halt(); + } + + /* Find the PROM node describing the real time clock. */ + sp_clock_typ = MSTK_INVALID; + node = prom_searchsiblings(node,"eeprom"); + if (!node) { + prom_printf("CLOCK: No clock found!\n"); + prom_halt(); + } + + /* Get the model name and setup everything up. */ + model[0] = '\0'; + prom_getstring(node, "model", model, sizeof(model)); + if (strcmp(model, "mk48t02") == 0) { + sp_clock_typ = MSTK48T02; + if (prom_getproperty(node, "reg", (char *) clk_reg, sizeof(clk_reg)) == -1) { + prom_printf("clock_probe: FAILED!\n"); + prom_halt(); + } + if (sparc_cpu_model == sun4d) + prom_apply_generic_ranges (bootbus, cpuunit, clk_reg, 1); + else + prom_apply_obio_ranges(clk_reg, 1); + /* Map the clock register io area read-only */ + r.flags = clk_reg[0].which_io; + r.start = clk_reg[0].phys_addr; + mstk48t02_regs = sbus_ioremap(&r, 0, + sizeof(struct mostek48t02), "mk48t02"); + mstk48t08_regs = NULL; /* To catch weirdness */ + } else if (strcmp(model, "mk48t08") == 0) { + sp_clock_typ = MSTK48T08; + if(prom_getproperty(node, "reg", (char *) clk_reg, + sizeof(clk_reg)) == -1) { + prom_printf("clock_probe: FAILED!\n"); + prom_halt(); + } + if (sparc_cpu_model == sun4d) + prom_apply_generic_ranges (bootbus, cpuunit, clk_reg, 1); + else + prom_apply_obio_ranges(clk_reg, 1); + /* Map the clock register io area read-only */ + /* XXX r/o attribute is somewhere in r.flags */ + r.flags = clk_reg[0].which_io; + r.start = clk_reg[0].phys_addr; + mstk48t08_regs = (struct mostek48t08 *) sbus_ioremap(&r, 0, + sizeof(struct mostek48t08), "mk48t08"); + + mstk48t02_regs = &mstk48t08_regs->regs; + } else { + prom_printf("CLOCK: Unknown model name '%s'\n",model); + prom_halt(); + } + + /* Report a low battery voltage condition. */ + if (has_low_battery()) + printk(KERN_CRIT "NVRAM: Low battery voltage!\n"); + + /* Kick start the clock if it is completely stopped. */ + if (mostek_read(mstk48t02_regs + MOSTEK_SEC) & MSTK_STOP) + kick_start_clock(); +} + +void __init sbus_time_init(void) +{ + unsigned int year, mon, day, hour, min, sec; + struct mostek48t02 *mregs; + +#ifdef CONFIG_SUN4 + int temp; + struct intersil *iregs; +#endif + + BTFIXUPSET_CALL(bus_do_settimeofday, sbus_do_settimeofday, BTFIXUPCALL_NORM); + btfixup(); + + if (ARCH_SUN4) + sun4_clock_probe(); + else + clock_probe(); + + sparc_init_timers(timer_interrupt); + +#ifdef CONFIG_SUN4 + if(idprom->id_machtype == (SM_SUN4 | SM_4_330)) { +#endif + mregs = (struct mostek48t02 *)mstk48t02_regs; + if(!mregs) { + prom_printf("Something wrong, clock regs not mapped yet.\n"); + prom_halt(); + } + spin_lock_irq(&mostek_lock); + mregs->creg |= MSTK_CREG_READ; + sec = MSTK_REG_SEC(mregs); + min = MSTK_REG_MIN(mregs); + hour = MSTK_REG_HOUR(mregs); + day = MSTK_REG_DOM(mregs); + mon = MSTK_REG_MONTH(mregs); + year = MSTK_CVT_YEAR( MSTK_REG_YEAR(mregs) ); + xtime.tv_sec = mktime(year, mon, day, hour, min, sec); + xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ); + set_normalized_timespec(&wall_to_monotonic, + -xtime.tv_sec, -xtime.tv_nsec); + mregs->creg &= ~MSTK_CREG_READ; + spin_unlock_irq(&mostek_lock); +#ifdef CONFIG_SUN4 + } else if(idprom->id_machtype == (SM_SUN4 | SM_4_260) ) { + /* initialise the intersil on sun4 */ + + iregs=intersil_clock; + if(!iregs) { + prom_printf("Something wrong, clock regs not mapped yet.\n"); + prom_halt(); + } + + intersil_intr(intersil_clock,INTERSIL_INT_100HZ); + disable_pil_irq(10); + intersil_stop(iregs); + intersil_read_intr(intersil_clock, temp); + + temp = iregs->clk.int_csec; + + sec = iregs->clk.int_sec; + min = iregs->clk.int_min; + hour = iregs->clk.int_hour; + day = iregs->clk.int_day; + mon = iregs->clk.int_month; + year = MSTK_CVT_YEAR(iregs->clk.int_year); + + enable_pil_irq(10); + intersil_start(iregs); + + xtime.tv_sec = mktime(year, mon, day, hour, min, sec); + xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ); + set_normalized_timespec(&wall_to_monotonic, + -xtime.tv_sec, -xtime.tv_nsec); + printk("%u/%u/%u %u:%u:%u\n",day,mon,year,hour,min,sec); + } +#endif + + /* Now that OBP ticker has been silenced, it is safe to enable IRQ. */ + local_irq_enable(); +} + +void __init time_init(void) +{ +#ifdef CONFIG_PCI + extern void pci_time_init(void); + if (pcic_present()) { + pci_time_init(); + return; + } +#endif + sbus_time_init(); +} + +extern __inline__ unsigned long do_gettimeoffset(void) +{ + return (*master_l10_counter >> 10) & 0x1fffff; +} + +/* + * Returns nanoseconds + * XXX This is a suboptimal implementation. + */ +unsigned long long sched_clock(void) +{ + return (unsigned long long)jiffies * (1000000000 / HZ); +} + +/* Ok, my cute asm atomicity trick doesn't work anymore. + * There are just too many variables that need to be protected + * now (both members of xtime, wall_jiffies, et al.) + */ +void do_gettimeofday(struct timeval *tv) +{ + unsigned long flags; + unsigned long seq; + unsigned long usec, sec; + unsigned long max_ntp_tick = tick_usec - tickadj; + + do { + unsigned long lost; + + seq = read_seqbegin_irqsave(&xtime_lock, flags); + usec = do_gettimeoffset(); + lost = jiffies - wall_jiffies; + + /* + * If time_adjust is negative then NTP is slowing the clock + * so make sure not to go into next possible interval. + * Better to lose some accuracy than have time go backwards.. + */ + if (unlikely(time_adjust < 0)) { + usec = min(usec, max_ntp_tick); + + if (lost) + usec += lost * max_ntp_tick; + } + else if (unlikely(lost)) + usec += lost * tick_usec; + + sec = xtime.tv_sec; + usec += (xtime.tv_nsec / 1000); + } while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); + + while (usec >= 1000000) { + usec -= 1000000; + sec++; + } + + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +EXPORT_SYMBOL(do_gettimeofday); + +int do_settimeofday(struct timespec *tv) +{ + int ret; + + write_seqlock_irq(&xtime_lock); + ret = bus_do_settimeofday(tv); + write_sequnlock_irq(&xtime_lock); + clock_was_set(); + return ret; +} + +EXPORT_SYMBOL(do_settimeofday); + +static int sbus_do_settimeofday(struct timespec *tv) +{ + time_t wtm_sec, sec = tv->tv_sec; + long wtm_nsec, nsec = tv->tv_nsec; + + if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + /* + * This is revolting. We need to set "xtime" correctly. However, the + * value in this location is the value at the most recent update of + * wall time. Discover what correction gettimeofday() would have + * made, and then undo it! + */ + nsec -= 1000 * (do_gettimeoffset() + + (jiffies - wall_jiffies) * (USEC_PER_SEC / HZ)); + + wtm_sec = wall_to_monotonic.tv_sec + (xtime.tv_sec - sec); + wtm_nsec = wall_to_monotonic.tv_nsec + (xtime.tv_nsec - nsec); + + set_normalized_timespec(&xtime, sec, nsec); + set_normalized_timespec(&wall_to_monotonic, wtm_sec, wtm_nsec); + + time_adjust = 0; /* stop active adjtime() */ + time_status |= STA_UNSYNC; + time_maxerror = NTP_PHASE_LIMIT; + time_esterror = NTP_PHASE_LIMIT; + return 0; +} + +/* + * BUG: This routine does not handle hour overflow properly; it just + * sets the minutes. Usually you won't notice until after reboot! + */ +static int set_rtc_mmss(unsigned long nowtime) +{ + int real_seconds, real_minutes, mostek_minutes; + struct mostek48t02 *regs = (struct mostek48t02 *)mstk48t02_regs; + unsigned long flags; +#ifdef CONFIG_SUN4 + struct intersil *iregs = intersil_clock; + int temp; +#endif + + /* Not having a register set can lead to trouble. */ + if (!regs) { +#ifdef CONFIG_SUN4 + if(!iregs) + return -1; + else { + temp = iregs->clk.int_csec; + + mostek_minutes = iregs->clk.int_min; + + real_seconds = nowtime % 60; + real_minutes = nowtime / 60; + if (((abs(real_minutes - mostek_minutes) + 15)/30) & 1) + real_minutes += 30; /* correct for half hour time zone */ + real_minutes %= 60; + + if (abs(real_minutes - mostek_minutes) < 30) { + intersil_stop(iregs); + iregs->clk.int_sec=real_seconds; + iregs->clk.int_min=real_minutes; + intersil_start(iregs); + } else { + printk(KERN_WARNING + "set_rtc_mmss: can't update from %d to %d\n", + mostek_minutes, real_minutes); + return -1; + } + + return 0; + } +#endif + } + + spin_lock_irqsave(&mostek_lock, flags); + /* Read the current RTC minutes. */ + regs->creg |= MSTK_CREG_READ; + mostek_minutes = MSTK_REG_MIN(regs); + regs->creg &= ~MSTK_CREG_READ; + + /* + * since we're only adjusting minutes and seconds, + * don't interfere with hour overflow. This avoids + * messing with unknown time zones but requires your + * RTC not to be off by more than 15 minutes + */ + real_seconds = nowtime % 60; + real_minutes = nowtime / 60; + if (((abs(real_minutes - mostek_minutes) + 15)/30) & 1) + real_minutes += 30; /* correct for half hour time zone */ + real_minutes %= 60; + + if (abs(real_minutes - mostek_minutes) < 30) { + regs->creg |= MSTK_CREG_WRITE; + MSTK_SET_REG_SEC(regs,real_seconds); + MSTK_SET_REG_MIN(regs,real_minutes); + regs->creg &= ~MSTK_CREG_WRITE; + spin_unlock_irqrestore(&mostek_lock, flags); + return 0; + } else { + spin_unlock_irqrestore(&mostek_lock, flags); + return -1; + } +} diff --git a/arch/sparc/kernel/trampoline.S b/arch/sparc/kernel/trampoline.S new file mode 100644 index 000000000000..2dcdaa1fd8cd --- /dev/null +++ b/arch/sparc/kernel/trampoline.S @@ -0,0 +1,162 @@ +/* $Id: trampoline.S,v 1.14 2002/01/11 08:45:38 davem Exp $ + * trampoline.S: SMP cpu boot-up trampoline code. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/init.h> +#include <asm/head.h> +#include <asm/psr.h> +#include <asm/page.h> +#include <asm/asi.h> +#include <asm/ptrace.h> +#include <asm/vaddrs.h> +#include <asm/contregs.h> +#include <asm/thread_info.h> + + .globl sun4m_cpu_startup, __smp4m_processor_id + .globl sun4d_cpu_startup, __smp4d_processor_id + + __INIT + .align 4 + +/* When we start up a cpu for the first time it enters this routine. + * This initializes the chip from whatever state the prom left it + * in and sets PIL in %psr to 15, no irqs. + */ + +sun4m_cpu_startup: +cpu1_startup: + sethi %hi(trapbase_cpu1), %g3 + b 1f + or %g3, %lo(trapbase_cpu1), %g3 + +cpu2_startup: + sethi %hi(trapbase_cpu2), %g3 + b 1f + or %g3, %lo(trapbase_cpu2), %g3 + +cpu3_startup: + sethi %hi(trapbase_cpu3), %g3 + b 1f + or %g3, %lo(trapbase_cpu3), %g3 + +1: + /* Set up a sane %psr -- PIL<0xf> S<0x1> PS<0x1> CWP<0x0> */ + set (PSR_PIL | PSR_S | PSR_PS), %g1 + wr %g1, 0x0, %psr ! traps off though + WRITE_PAUSE + + /* Our %wim is one behind CWP */ + mov 2, %g1 + wr %g1, 0x0, %wim + WRITE_PAUSE + + /* This identifies "this cpu". */ + wr %g3, 0x0, %tbr + WRITE_PAUSE + + /* Give ourselves a stack and curptr. */ + set current_set, %g5 + srl %g3, 10, %g4 + and %g4, 0xc, %g4 + ld [%g5 + %g4], %g6 + + sethi %hi(THREAD_SIZE - STACKFRAME_SZ), %sp + or %sp, %lo(THREAD_SIZE - STACKFRAME_SZ), %sp + add %g6, %sp, %sp + + /* Turn on traps (PSR_ET). */ + rd %psr, %g1 + wr %g1, PSR_ET, %psr ! traps on + WRITE_PAUSE + + /* Init our caches, etc. */ + set poke_srmmu, %g5 + ld [%g5], %g5 + call %g5 + nop + + /* Start this processor. */ + call smp4m_callin + nop + + b,a smp_do_cpu_idle + + .text + .align 4 + +smp_do_cpu_idle: + call cpu_idle + mov 0, %o0 + + call cpu_panic + nop + +__smp4m_processor_id: + rd %tbr, %g2 + srl %g2, 12, %g2 + and %g2, 3, %g2 + retl + mov %g1, %o7 + +__smp4d_processor_id: + lda [%g0] ASI_M_VIKING_TMP1, %g2 + retl + mov %g1, %o7 + +/* CPUID in bootbus can be found at PA 0xff0140000 */ +#define SUN4D_BOOTBUS_CPUID 0xf0140000 + + __INIT + .align 4 + +sun4d_cpu_startup: + /* Set up a sane %psr -- PIL<0xf> S<0x1> PS<0x1> CWP<0x0> */ + set (PSR_PIL | PSR_S | PSR_PS), %g1 + wr %g1, 0x0, %psr ! traps off though + WRITE_PAUSE + + /* Our %wim is one behind CWP */ + mov 2, %g1 + wr %g1, 0x0, %wim + WRITE_PAUSE + + /* Set tbr - we use just one trap table. */ + set trapbase, %g1 + wr %g1, 0x0, %tbr + WRITE_PAUSE + + /* Get our CPU id out of bootbus */ + set SUN4D_BOOTBUS_CPUID, %g3 + lduba [%g3] ASI_M_CTL, %g3 + and %g3, 0xf8, %g3 + srl %g3, 3, %g1 + sta %g1, [%g0] ASI_M_VIKING_TMP1 + + /* Give ourselves a stack and curptr. */ + set current_set, %g5 + srl %g3, 1, %g4 + ld [%g5 + %g4], %g6 + + sethi %hi(THREAD_SIZE - STACKFRAME_SZ), %sp + or %sp, %lo(THREAD_SIZE - STACKFRAME_SZ), %sp + add %g6, %sp, %sp + + /* Turn on traps (PSR_ET). */ + rd %psr, %g1 + wr %g1, PSR_ET, %psr ! traps on + WRITE_PAUSE + + /* Init our caches, etc. */ + set poke_srmmu, %g5 + ld [%g5], %g5 + call %g5 + nop + + /* Start this processor. */ + call smp4d_callin + nop + + b,a smp_do_cpu_idle diff --git a/arch/sparc/kernel/traps.c b/arch/sparc/kernel/traps.c new file mode 100644 index 000000000000..3f451ae66482 --- /dev/null +++ b/arch/sparc/kernel/traps.c @@ -0,0 +1,515 @@ +/* $Id: traps.c,v 1.64 2000/09/03 15:00:49 anton Exp $ + * arch/sparc/kernel/traps.c + * + * Copyright 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright 2000 Jakub Jelinek (jakub@redhat.com) + */ + +/* + * I hate traps on the sparc, grrr... + */ + +#include <linux/config.h> +#include <linux/sched.h> /* for jiffies */ +#include <linux/kernel.h> +#include <linux/kallsyms.h> +#include <linux/signal.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> + +#include <asm/delay.h> +#include <asm/system.h> +#include <asm/ptrace.h> +#include <asm/oplib.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/kdebug.h> +#include <asm/unistd.h> +#include <asm/traps.h> + +/* #define TRAP_DEBUG */ + +struct trap_trace_entry { + unsigned long pc; + unsigned long type; +}; + +int trap_curbuf = 0; +struct trap_trace_entry trapbuf[1024]; + +void syscall_trace_entry(struct pt_regs *regs) +{ + printk("%s[%d]: ", current->comm, current->pid); + printk("scall<%d> (could be %d)\n", (int) regs->u_regs[UREG_G1], + (int) regs->u_regs[UREG_I0]); +} + +void syscall_trace_exit(struct pt_regs *regs) +{ +} + +void sun4m_nmi(struct pt_regs *regs) +{ + unsigned long afsr, afar; + + printk("Aieee: sun4m NMI received!\n"); + /* XXX HyperSparc hack XXX */ + __asm__ __volatile__("mov 0x500, %%g1\n\t" + "lda [%%g1] 0x4, %0\n\t" + "mov 0x600, %%g1\n\t" + "lda [%%g1] 0x4, %1\n\t" : + "=r" (afsr), "=r" (afar)); + printk("afsr=%08lx afar=%08lx\n", afsr, afar); + printk("you lose buddy boy...\n"); + show_regs(regs); + prom_halt(); +} + +void sun4d_nmi(struct pt_regs *regs) +{ + printk("Aieee: sun4d NMI received!\n"); + printk("you lose buddy boy...\n"); + show_regs(regs); + prom_halt(); +} + +void instruction_dump (unsigned long *pc) +{ + int i; + + if((((unsigned long) pc) & 3)) + return; + + for(i = -3; i < 6; i++) + printk("%c%08lx%c",i?' ':'<',pc[i],i?' ':'>'); + printk("\n"); +} + +#define __SAVE __asm__ __volatile__("save %sp, -0x40, %sp\n\t") +#define __RESTORE __asm__ __volatile__("restore %g0, %g0, %g0\n\t") + +void die_if_kernel(char *str, struct pt_regs *regs) +{ + static int die_counter; + int count = 0; + + /* Amuse the user. */ + printk( +" \\|/ ____ \\|/\n" +" \"@'/ ,. \\`@\"\n" +" /_| \\__/ |_\\\n" +" \\__U_/\n"); + + printk("%s(%d): %s [#%d]\n", current->comm, current->pid, str, ++die_counter); + show_regs(regs); + + __SAVE; __SAVE; __SAVE; __SAVE; + __SAVE; __SAVE; __SAVE; __SAVE; + __RESTORE; __RESTORE; __RESTORE; __RESTORE; + __RESTORE; __RESTORE; __RESTORE; __RESTORE; + + { + struct reg_window *rw = (struct reg_window *)regs->u_regs[UREG_FP]; + + /* Stop the back trace when we hit userland or we + * find some badly aligned kernel stack. Set an upper + * bound in case our stack is trashed and we loop. + */ + while(rw && + count++ < 30 && + (((unsigned long) rw) >= PAGE_OFFSET) && + !(((unsigned long) rw) & 0x7)) { + printk("Caller[%08lx]", rw->ins[7]); + print_symbol(": %s\n", rw->ins[7]); + rw = (struct reg_window *)rw->ins[6]; + } + } + printk("Instruction DUMP:"); + instruction_dump ((unsigned long *) regs->pc); + if(regs->psr & PSR_PS) + do_exit(SIGKILL); + do_exit(SIGSEGV); +} + +void do_hw_interrupt(struct pt_regs *regs, unsigned long type) +{ + siginfo_t info; + + if(type < 0x80) { + /* Sun OS's puke from bad traps, Linux survives! */ + printk("Unimplemented Sparc TRAP, type = %02lx\n", type); + die_if_kernel("Whee... Hello Mr. Penguin", regs); + } + + if(regs->psr & PSR_PS) + die_if_kernel("Kernel bad trap", regs); + + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLTRP; + info.si_addr = (void __user *)regs->pc; + info.si_trapno = type - 0x80; + force_sig_info(SIGILL, &info, current); +} + +void do_illegal_instruction(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + extern int do_user_muldiv (struct pt_regs *, unsigned long); + siginfo_t info; + + if(psr & PSR_PS) + die_if_kernel("Kernel illegal instruction", regs); +#ifdef TRAP_DEBUG + printk("Ill instr. at pc=%08lx instruction is %08lx\n", + regs->pc, *(unsigned long *)regs->pc); +#endif + if (!do_user_muldiv (regs, pc)) + return; + + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLOPC; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + send_sig_info(SIGILL, &info, current); +} + +void do_priv_instruction(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + siginfo_t info; + + if(psr & PSR_PS) + die_if_kernel("Penguin instruction from Penguin mode??!?!", regs); + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_PRVOPC; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + send_sig_info(SIGILL, &info, current); +} + +/* XXX User may want to be allowed to do this. XXX */ + +void do_memaccess_unaligned(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + siginfo_t info; + + if(regs->psr & PSR_PS) { + printk("KERNEL MNA at pc %08lx npc %08lx called by %08lx\n", pc, npc, + regs->u_regs[UREG_RETPC]); + die_if_kernel("BOGUS", regs); + /* die_if_kernel("Kernel MNA access", regs); */ + } +#if 0 + show_regs (regs); + instruction_dump ((unsigned long *) regs->pc); + printk ("do_MNA!\n"); +#endif + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRALN; + info.si_addr = /* FIXME: Should dig out mna address */ (void *)0; + info.si_trapno = 0; + send_sig_info(SIGBUS, &info, current); +} + +extern void fpsave(unsigned long *fpregs, unsigned long *fsr, + void *fpqueue, unsigned long *fpqdepth); +extern void fpload(unsigned long *fpregs, unsigned long *fsr); + +static unsigned long init_fsr = 0x0UL; +static unsigned long init_fregs[32] __attribute__ ((aligned (8))) = + { ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, + ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, + ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, + ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL }; + +void do_fpd_trap(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + /* Sanity check... */ + if(psr & PSR_PS) + die_if_kernel("Kernel gets FloatingPenguinUnit disabled trap", regs); + + put_psr(get_psr() | PSR_EF); /* Allow FPU ops. */ + regs->psr |= PSR_EF; +#ifndef CONFIG_SMP + if(last_task_used_math == current) + return; + if(last_task_used_math) { + /* Other processes fpu state, save away */ + struct task_struct *fptask = last_task_used_math; + fpsave(&fptask->thread.float_regs[0], &fptask->thread.fsr, + &fptask->thread.fpqueue[0], &fptask->thread.fpqdepth); + } + last_task_used_math = current; + if(used_math()) { + fpload(¤t->thread.float_regs[0], ¤t->thread.fsr); + } else { + /* Set initial sane state. */ + fpload(&init_fregs[0], &init_fsr); + set_used_math(); + } +#else + if(!used_math()) { + fpload(&init_fregs[0], &init_fsr); + set_used_math(); + } else { + fpload(¤t->thread.float_regs[0], ¤t->thread.fsr); + } + current_thread_info()->flags |= _TIF_USEDFPU; +#endif +} + +static unsigned long fake_regs[32] __attribute__ ((aligned (8))); +static unsigned long fake_fsr; +static unsigned long fake_queue[32] __attribute__ ((aligned (8))); +static unsigned long fake_depth; + +extern int do_mathemu(struct pt_regs *, struct task_struct *); + +void do_fpe_trap(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + static int calls; + siginfo_t info; + unsigned long fsr; + int ret = 0; +#ifndef CONFIG_SMP + struct task_struct *fpt = last_task_used_math; +#else + struct task_struct *fpt = current; +#endif + put_psr(get_psr() | PSR_EF); + /* If nobody owns the fpu right now, just clear the + * error into our fake static buffer and hope it don't + * happen again. Thank you crashme... + */ +#ifndef CONFIG_SMP + if(!fpt) { +#else + if(!(fpt->thread_info->flags & _TIF_USEDFPU)) { +#endif + fpsave(&fake_regs[0], &fake_fsr, &fake_queue[0], &fake_depth); + regs->psr &= ~PSR_EF; + return; + } + fpsave(&fpt->thread.float_regs[0], &fpt->thread.fsr, + &fpt->thread.fpqueue[0], &fpt->thread.fpqdepth); +#ifdef DEBUG_FPU + printk("Hmm, FP exception, fsr was %016lx\n", fpt->thread.fsr); +#endif + + switch ((fpt->thread.fsr & 0x1c000)) { + /* switch on the contents of the ftt [floating point trap type] field */ +#ifdef DEBUG_FPU + case (1 << 14): + printk("IEEE_754_exception\n"); + break; +#endif + case (2 << 14): /* unfinished_FPop (underflow & co) */ + case (3 << 14): /* unimplemented_FPop (quad stuff, maybe sqrt) */ + ret = do_mathemu(regs, fpt); + break; +#ifdef DEBUG_FPU + case (4 << 14): + printk("sequence_error (OS bug...)\n"); + break; + case (5 << 14): + printk("hardware_error (uhoh!)\n"); + break; + case (6 << 14): + printk("invalid_fp_register (user error)\n"); + break; +#endif /* DEBUG_FPU */ + } + /* If we successfully emulated the FPop, we pretend the trap never happened :-> */ + if (ret) { + fpload(¤t->thread.float_regs[0], ¤t->thread.fsr); + return; + } + /* nope, better SIGFPE the offending process... */ + +#ifdef CONFIG_SMP + fpt->thread_info->flags &= ~_TIF_USEDFPU; +#endif + if(psr & PSR_PS) { + /* The first fsr store/load we tried trapped, + * the second one will not (we hope). + */ + printk("WARNING: FPU exception from kernel mode. at pc=%08lx\n", + regs->pc); + regs->pc = regs->npc; + regs->npc += 4; + calls++; + if(calls > 2) + die_if_kernel("Too many Penguin-FPU traps from kernel mode", + regs); + return; + } + + fsr = fpt->thread.fsr; + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + info.si_code = __SI_FAULT; + if ((fsr & 0x1c000) == (1 << 14)) { + if (fsr & 0x10) + info.si_code = FPE_FLTINV; + else if (fsr & 0x08) + info.si_code = FPE_FLTOVF; + else if (fsr & 0x04) + info.si_code = FPE_FLTUND; + else if (fsr & 0x02) + info.si_code = FPE_FLTDIV; + else if (fsr & 0x01) + info.si_code = FPE_FLTRES; + } + send_sig_info(SIGFPE, &info, fpt); +#ifndef CONFIG_SMP + last_task_used_math = NULL; +#endif + regs->psr &= ~PSR_EF; + if(calls > 0) + calls=0; +} + +void handle_tag_overflow(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + siginfo_t info; + + if(psr & PSR_PS) + die_if_kernel("Penguin overflow trap from kernel mode", regs); + info.si_signo = SIGEMT; + info.si_errno = 0; + info.si_code = EMT_TAGOVF; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + send_sig_info(SIGEMT, &info, current); +} + +void handle_watchpoint(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ +#ifdef TRAP_DEBUG + printk("Watchpoint detected at PC %08lx NPC %08lx PSR %08lx\n", + pc, npc, psr); +#endif + if(psr & PSR_PS) + panic("Tell me what a watchpoint trap is, and I'll then deal " + "with such a beast..."); +} + +void handle_reg_access(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + siginfo_t info; + +#ifdef TRAP_DEBUG + printk("Register Access Exception at PC %08lx NPC %08lx PSR %08lx\n", + pc, npc, psr); +#endif + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_OBJERR; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + force_sig_info(SIGBUS, &info, current); +} + +void handle_cp_disabled(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + siginfo_t info; + + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_COPROC; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + send_sig_info(SIGILL, &info, current); +} + +void handle_cp_exception(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + siginfo_t info; + +#ifdef TRAP_DEBUG + printk("Co-Processor Exception at PC %08lx NPC %08lx PSR %08lx\n", + pc, npc, psr); +#endif + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_COPROC; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + send_sig_info(SIGILL, &info, current); +} + +void handle_hw_divzero(struct pt_regs *regs, unsigned long pc, unsigned long npc, + unsigned long psr) +{ + siginfo_t info; + + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_code = FPE_INTDIV; + info.si_addr = (void __user *)pc; + info.si_trapno = 0; + send_sig_info(SIGFPE, &info, current); +} + +#ifdef CONFIG_DEBUG_BUGVERBOSE +void do_BUG(const char *file, int line) +{ + // bust_spinlocks(1); XXX Not in our original BUG() + printk("kernel BUG at %s:%d!\n", file, line); +} +#endif + +/* Since we have our mappings set up, on multiprocessors we can spin them + * up here so that timer interrupts work during initialization. + */ + +extern void sparc_cpu_startup(void); + +int linux_smp_still_initting; +unsigned int thiscpus_tbr; +int thiscpus_mid; + +void trap_init(void) +{ + extern void thread_info_offsets_are_bolixed_pete(void); + + /* Force linker to barf if mismatched */ + if (TI_UWINMASK != offsetof(struct thread_info, uwinmask) || + TI_TASK != offsetof(struct thread_info, task) || + TI_EXECDOMAIN != offsetof(struct thread_info, exec_domain) || + TI_FLAGS != offsetof(struct thread_info, flags) || + TI_CPU != offsetof(struct thread_info, cpu) || + TI_PREEMPT != offsetof(struct thread_info, preempt_count) || + TI_SOFTIRQ != offsetof(struct thread_info, softirq_count) || + TI_HARDIRQ != offsetof(struct thread_info, hardirq_count) || + TI_KSP != offsetof(struct thread_info, ksp) || + TI_KPC != offsetof(struct thread_info, kpc) || + TI_KPSR != offsetof(struct thread_info, kpsr) || + TI_KWIM != offsetof(struct thread_info, kwim) || + TI_REG_WINDOW != offsetof(struct thread_info, reg_window) || + TI_RWIN_SPTRS != offsetof(struct thread_info, rwbuf_stkptrs) || + TI_W_SAVED != offsetof(struct thread_info, w_saved)) + thread_info_offsets_are_bolixed_pete(); + + /* Attach to the address space of init_task. */ + atomic_inc(&init_mm.mm_count); + current->active_mm = &init_mm; + + /* NOTE: Other cpus have this done as they are started + * up on SMP. + */ +} diff --git a/arch/sparc/kernel/unaligned.c b/arch/sparc/kernel/unaligned.c new file mode 100644 index 000000000000..a6330fbc9dd9 --- /dev/null +++ b/arch/sparc/kernel/unaligned.c @@ -0,0 +1,548 @@ +/* $Id: unaligned.c,v 1.23 2001/12/21 00:54:31 davem Exp $ + * unaligned.c: Unaligned load/store trap handling with special + * cases for the kernel to do them more quickly. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <asm/ptrace.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> + +/* #define DEBUG_MNA */ + +enum direction { + load, /* ld, ldd, ldh, ldsh */ + store, /* st, std, sth, stsh */ + both, /* Swap, ldstub, etc. */ + fpload, + fpstore, + invalid, +}; + +#ifdef DEBUG_MNA +static char *dirstrings[] = { + "load", "store", "both", "fpload", "fpstore", "invalid" +}; +#endif + +static inline enum direction decode_direction(unsigned int insn) +{ + unsigned long tmp = (insn >> 21) & 1; + + if(!tmp) + return load; + else { + if(((insn>>19)&0x3f) == 15) + return both; + else + return store; + } +} + +/* 8 = double-word, 4 = word, 2 = half-word */ +static inline int decode_access_size(unsigned int insn) +{ + insn = (insn >> 19) & 3; + + if(!insn) + return 4; + else if(insn == 3) + return 8; + else if(insn == 2) + return 2; + else { + printk("Impossible unaligned trap. insn=%08x\n", insn); + die_if_kernel("Byte sized unaligned access?!?!", current->thread.kregs); + return 4; /* just to keep gcc happy. */ + } +} + +/* 0x400000 = signed, 0 = unsigned */ +static inline int decode_signedness(unsigned int insn) +{ + return (insn & 0x400000); +} + +static inline void maybe_flush_windows(unsigned int rs1, unsigned int rs2, + unsigned int rd) +{ + if(rs2 >= 16 || rs1 >= 16 || rd >= 16) { + /* Wheee... */ + __asm__ __volatile__("save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "save %sp, -0x40, %sp\n\t" + "restore; restore; restore; restore;\n\t" + "restore; restore; restore;\n\t"); + } +} + +static inline int sign_extend_imm13(int imm) +{ + return imm << 19 >> 19; +} + +static inline unsigned long fetch_reg(unsigned int reg, struct pt_regs *regs) +{ + struct reg_window *win; + + if(reg < 16) + return (!reg ? 0 : regs->u_regs[reg]); + + /* Ho hum, the slightly complicated case. */ + win = (struct reg_window *) regs->u_regs[UREG_FP]; + return win->locals[reg - 16]; /* yes, I know what this does... */ +} + +static inline unsigned long safe_fetch_reg(unsigned int reg, struct pt_regs *regs) +{ + struct reg_window __user *win; + unsigned long ret; + + if (reg < 16) + return (!reg ? 0 : regs->u_regs[reg]); + + /* Ho hum, the slightly complicated case. */ + win = (struct reg_window __user *) regs->u_regs[UREG_FP]; + + if ((unsigned long)win & 3) + return -1; + + if (get_user(ret, &win->locals[reg - 16])) + return -1; + + return ret; +} + +static inline unsigned long *fetch_reg_addr(unsigned int reg, struct pt_regs *regs) +{ + struct reg_window *win; + + if(reg < 16) + return ®s->u_regs[reg]; + win = (struct reg_window *) regs->u_regs[UREG_FP]; + return &win->locals[reg - 16]; +} + +static unsigned long compute_effective_address(struct pt_regs *regs, + unsigned int insn) +{ + unsigned int rs1 = (insn >> 14) & 0x1f; + unsigned int rs2 = insn & 0x1f; + unsigned int rd = (insn >> 25) & 0x1f; + + if(insn & 0x2000) { + maybe_flush_windows(rs1, 0, rd); + return (fetch_reg(rs1, regs) + sign_extend_imm13(insn)); + } else { + maybe_flush_windows(rs1, rs2, rd); + return (fetch_reg(rs1, regs) + fetch_reg(rs2, regs)); + } +} + +unsigned long safe_compute_effective_address(struct pt_regs *regs, + unsigned int insn) +{ + unsigned int rs1 = (insn >> 14) & 0x1f; + unsigned int rs2 = insn & 0x1f; + unsigned int rd = (insn >> 25) & 0x1f; + + if(insn & 0x2000) { + maybe_flush_windows(rs1, 0, rd); + return (safe_fetch_reg(rs1, regs) + sign_extend_imm13(insn)); + } else { + maybe_flush_windows(rs1, rs2, rd); + return (safe_fetch_reg(rs1, regs) + safe_fetch_reg(rs2, regs)); + } +} + +/* This is just to make gcc think panic does return... */ +static void unaligned_panic(char *str) +{ + panic(str); +} + +#define do_integer_load(dest_reg, size, saddr, is_signed, errh) ({ \ +__asm__ __volatile__ ( \ + "cmp %1, 8\n\t" \ + "be 9f\n\t" \ + " cmp %1, 4\n\t" \ + "be 6f\n" \ +"4:\t" " ldub [%2], %%l1\n" \ +"5:\t" "ldub [%2 + 1], %%l2\n\t" \ + "sll %%l1, 8, %%l1\n\t" \ + "tst %3\n\t" \ + "be 3f\n\t" \ + " add %%l1, %%l2, %%l1\n\t" \ + "sll %%l1, 16, %%l1\n\t" \ + "sra %%l1, 16, %%l1\n" \ +"3:\t" "b 0f\n\t" \ + " st %%l1, [%0]\n" \ +"6:\t" "ldub [%2 + 1], %%l2\n\t" \ + "sll %%l1, 24, %%l1\n" \ +"7:\t" "ldub [%2 + 2], %%g7\n\t" \ + "sll %%l2, 16, %%l2\n" \ +"8:\t" "ldub [%2 + 3], %%g1\n\t" \ + "sll %%g7, 8, %%g7\n\t" \ + "or %%l1, %%l2, %%l1\n\t" \ + "or %%g7, %%g1, %%g7\n\t" \ + "or %%l1, %%g7, %%l1\n\t" \ + "b 0f\n\t" \ + " st %%l1, [%0]\n" \ +"9:\t" "ldub [%2], %%l1\n" \ +"10:\t" "ldub [%2 + 1], %%l2\n\t" \ + "sll %%l1, 24, %%l1\n" \ +"11:\t" "ldub [%2 + 2], %%g7\n\t" \ + "sll %%l2, 16, %%l2\n" \ +"12:\t" "ldub [%2 + 3], %%g1\n\t" \ + "sll %%g7, 8, %%g7\n\t" \ + "or %%l1, %%l2, %%l1\n\t" \ + "or %%g7, %%g1, %%g7\n\t" \ + "or %%l1, %%g7, %%g7\n" \ +"13:\t" "ldub [%2 + 4], %%l1\n\t" \ + "st %%g7, [%0]\n" \ +"14:\t" "ldub [%2 + 5], %%l2\n\t" \ + "sll %%l1, 24, %%l1\n" \ +"15:\t" "ldub [%2 + 6], %%g7\n\t" \ + "sll %%l2, 16, %%l2\n" \ +"16:\t" "ldub [%2 + 7], %%g1\n\t" \ + "sll %%g7, 8, %%g7\n\t" \ + "or %%l1, %%l2, %%l1\n\t" \ + "or %%g7, %%g1, %%g7\n\t" \ + "or %%l1, %%g7, %%g7\n\t" \ + "st %%g7, [%0 + 4]\n" \ +"0:\n\n\t" \ + ".section __ex_table,#alloc\n\t" \ + ".word 4b, " #errh "\n\t" \ + ".word 5b, " #errh "\n\t" \ + ".word 6b, " #errh "\n\t" \ + ".word 7b, " #errh "\n\t" \ + ".word 8b, " #errh "\n\t" \ + ".word 9b, " #errh "\n\t" \ + ".word 10b, " #errh "\n\t" \ + ".word 11b, " #errh "\n\t" \ + ".word 12b, " #errh "\n\t" \ + ".word 13b, " #errh "\n\t" \ + ".word 14b, " #errh "\n\t" \ + ".word 15b, " #errh "\n\t" \ + ".word 16b, " #errh "\n\n\t" \ + ".previous\n\t" \ + : : "r" (dest_reg), "r" (size), "r" (saddr), "r" (is_signed) \ + : "l1", "l2", "g7", "g1", "cc"); \ +}) + +#define store_common(dst_addr, size, src_val, errh) ({ \ +__asm__ __volatile__ ( \ + "ld [%2], %%l1\n" \ + "cmp %1, 2\n\t" \ + "be 2f\n\t" \ + " cmp %1, 4\n\t" \ + "be 1f\n\t" \ + " srl %%l1, 24, %%l2\n\t" \ + "srl %%l1, 16, %%g7\n" \ +"4:\t" "stb %%l2, [%0]\n\t" \ + "srl %%l1, 8, %%l2\n" \ +"5:\t" "stb %%g7, [%0 + 1]\n\t" \ + "ld [%2 + 4], %%g7\n" \ +"6:\t" "stb %%l2, [%0 + 2]\n\t" \ + "srl %%g7, 24, %%l2\n" \ +"7:\t" "stb %%l1, [%0 + 3]\n\t" \ + "srl %%g7, 16, %%l1\n" \ +"8:\t" "stb %%l2, [%0 + 4]\n\t" \ + "srl %%g7, 8, %%l2\n" \ +"9:\t" "stb %%l1, [%0 + 5]\n" \ +"10:\t" "stb %%l2, [%0 + 6]\n\t" \ + "b 0f\n" \ +"11:\t" " stb %%g7, [%0 + 7]\n" \ +"1:\t" "srl %%l1, 16, %%g7\n" \ +"12:\t" "stb %%l2, [%0]\n\t" \ + "srl %%l1, 8, %%l2\n" \ +"13:\t" "stb %%g7, [%0 + 1]\n" \ +"14:\t" "stb %%l2, [%0 + 2]\n\t" \ + "b 0f\n" \ +"15:\t" " stb %%l1, [%0 + 3]\n" \ +"2:\t" "srl %%l1, 8, %%l2\n" \ +"16:\t" "stb %%l2, [%0]\n" \ +"17:\t" "stb %%l1, [%0 + 1]\n" \ +"0:\n\n\t" \ + ".section __ex_table,#alloc\n\t" \ + ".word 4b, " #errh "\n\t" \ + ".word 5b, " #errh "\n\t" \ + ".word 6b, " #errh "\n\t" \ + ".word 7b, " #errh "\n\t" \ + ".word 8b, " #errh "\n\t" \ + ".word 9b, " #errh "\n\t" \ + ".word 10b, " #errh "\n\t" \ + ".word 11b, " #errh "\n\t" \ + ".word 12b, " #errh "\n\t" \ + ".word 13b, " #errh "\n\t" \ + ".word 14b, " #errh "\n\t" \ + ".word 15b, " #errh "\n\t" \ + ".word 16b, " #errh "\n\t" \ + ".word 17b, " #errh "\n\n\t" \ + ".previous\n\t" \ + : : "r" (dst_addr), "r" (size), "r" (src_val) \ + : "l1", "l2", "g7", "g1", "cc"); \ +}) + +#define do_integer_store(reg_num, size, dst_addr, regs, errh) ({ \ + unsigned long *src_val; \ + static unsigned long zero[2] = { 0, }; \ + \ + if (reg_num) src_val = fetch_reg_addr(reg_num, regs); \ + else { \ + src_val = &zero[0]; \ + if (size == 8) \ + zero[1] = fetch_reg(1, regs); \ + } \ + store_common(dst_addr, size, src_val, errh); \ +}) + +extern void smp_capture(void); +extern void smp_release(void); + +#define do_atomic(srcdest_reg, mem, errh) ({ \ + unsigned long flags, tmp; \ + \ + smp_capture(); \ + local_irq_save(flags); \ + tmp = *srcdest_reg; \ + do_integer_load(srcdest_reg, 4, mem, 0, errh); \ + store_common(mem, 4, &tmp, errh); \ + local_irq_restore(flags); \ + smp_release(); \ +}) + +static inline void advance(struct pt_regs *regs) +{ + regs->pc = regs->npc; + regs->npc += 4; +} + +static inline int floating_point_load_or_store_p(unsigned int insn) +{ + return (insn >> 24) & 1; +} + +static inline int ok_for_kernel(unsigned int insn) +{ + return !floating_point_load_or_store_p(insn); +} + +void kernel_mna_trap_fault(struct pt_regs *regs, unsigned int insn) __asm__ ("kernel_mna_trap_fault"); + +void kernel_mna_trap_fault(struct pt_regs *regs, unsigned int insn) +{ + unsigned long g2 = regs->u_regs [UREG_G2]; + unsigned long fixup = search_extables_range(regs->pc, &g2); + + if (!fixup) { + unsigned long address = compute_effective_address(regs, insn); + if(address < PAGE_SIZE) { + printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference in mna handler"); + } else + printk(KERN_ALERT "Unable to handle kernel paging request in mna handler"); + printk(KERN_ALERT " at virtual address %08lx\n",address); + printk(KERN_ALERT "current->{mm,active_mm}->context = %08lx\n", + (current->mm ? current->mm->context : + current->active_mm->context)); + printk(KERN_ALERT "current->{mm,active_mm}->pgd = %08lx\n", + (current->mm ? (unsigned long) current->mm->pgd : + (unsigned long) current->active_mm->pgd)); + die_if_kernel("Oops", regs); + /* Not reached */ + } + regs->pc = fixup; + regs->npc = regs->pc + 4; + regs->u_regs [UREG_G2] = g2; +} + +asmlinkage void kernel_unaligned_trap(struct pt_regs *regs, unsigned int insn) +{ + enum direction dir = decode_direction(insn); + int size = decode_access_size(insn); + + if(!ok_for_kernel(insn) || dir == both) { + printk("Unsupported unaligned load/store trap for kernel at <%08lx>.\n", + regs->pc); + unaligned_panic("Wheee. Kernel does fpu/atomic unaligned load/store."); + + __asm__ __volatile__ ("\n" +"kernel_unaligned_trap_fault:\n\t" + "mov %0, %%o0\n\t" + "call kernel_mna_trap_fault\n\t" + " mov %1, %%o1\n\t" + : + : "r" (regs), "r" (insn) + : "o0", "o1", "o2", "o3", "o4", "o5", "o7", + "g1", "g2", "g3", "g4", "g5", "g7", "cc"); + } else { + unsigned long addr = compute_effective_address(regs, insn); + +#ifdef DEBUG_MNA + printk("KMNA: pc=%08lx [dir=%s addr=%08lx size=%d] retpc[%08lx]\n", + regs->pc, dirstrings[dir], addr, size, regs->u_regs[UREG_RETPC]); +#endif + switch(dir) { + case load: + do_integer_load(fetch_reg_addr(((insn>>25)&0x1f), regs), + size, (unsigned long *) addr, + decode_signedness(insn), + kernel_unaligned_trap_fault); + break; + + case store: + do_integer_store(((insn>>25)&0x1f), size, + (unsigned long *) addr, regs, + kernel_unaligned_trap_fault); + break; +#if 0 /* unsupported */ + case both: + do_atomic(fetch_reg_addr(((insn>>25)&0x1f), regs), + (unsigned long *) addr, + kernel_unaligned_trap_fault); + break; +#endif + default: + panic("Impossible kernel unaligned trap."); + /* Not reached... */ + } + advance(regs); + } +} + +static inline int ok_for_user(struct pt_regs *regs, unsigned int insn, + enum direction dir) +{ + unsigned int reg; + int check = (dir == load) ? VERIFY_READ : VERIFY_WRITE; + int size = ((insn >> 19) & 3) == 3 ? 8 : 4; + + if ((regs->pc | regs->npc) & 3) + return 0; + + /* Must access_ok() in all the necessary places. */ +#define WINREG_ADDR(regnum) \ + ((void __user *)(((unsigned long *)regs->u_regs[UREG_FP])+(regnum))) + + reg = (insn >> 25) & 0x1f; + if (reg >= 16) { + if (!access_ok(check, WINREG_ADDR(reg - 16), size)) + return -EFAULT; + } + reg = (insn >> 14) & 0x1f; + if (reg >= 16) { + if (!access_ok(check, WINREG_ADDR(reg - 16), size)) + return -EFAULT; + } + if (!(insn & 0x2000)) { + reg = (insn & 0x1f); + if (reg >= 16) { + if (!access_ok(check, WINREG_ADDR(reg - 16), size)) + return -EFAULT; + } + } +#undef WINREG_ADDR + return 0; +} + +void user_mna_trap_fault(struct pt_regs *regs, unsigned int insn) __asm__ ("user_mna_trap_fault"); + +void user_mna_trap_fault(struct pt_regs *regs, unsigned int insn) +{ + siginfo_t info; + + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRALN; + info.si_addr = (void __user *)safe_compute_effective_address(regs, insn); + info.si_trapno = 0; + send_sig_info(SIGBUS, &info, current); +} + +asmlinkage void user_unaligned_trap(struct pt_regs *regs, unsigned int insn) +{ + enum direction dir; + + lock_kernel(); + if(!(current->thread.flags & SPARC_FLAG_UNALIGNED) || + (((insn >> 30) & 3) != 3)) + goto kill_user; + dir = decode_direction(insn); + if(!ok_for_user(regs, insn, dir)) { + goto kill_user; + } else { + int size = decode_access_size(insn); + unsigned long addr; + + if(floating_point_load_or_store_p(insn)) { + printk("User FPU load/store unaligned unsupported.\n"); + goto kill_user; + } + + addr = compute_effective_address(regs, insn); + switch(dir) { + case load: + do_integer_load(fetch_reg_addr(((insn>>25)&0x1f), regs), + size, (unsigned long *) addr, + decode_signedness(insn), + user_unaligned_trap_fault); + break; + + case store: + do_integer_store(((insn>>25)&0x1f), size, + (unsigned long *) addr, regs, + user_unaligned_trap_fault); + break; + + case both: +#if 0 /* unsupported */ + do_atomic(fetch_reg_addr(((insn>>25)&0x1f), regs), + (unsigned long *) addr, + user_unaligned_trap_fault); +#else + /* + * This was supported in 2.4. However, we question + * the value of SWAP instruction across word boundaries. + */ + printk("Unaligned SWAP unsupported.\n"); + goto kill_user; +#endif + break; + + default: + unaligned_panic("Impossible user unaligned trap."); + + __asm__ __volatile__ ("\n" +"user_unaligned_trap_fault:\n\t" + "mov %0, %%o0\n\t" + "call user_mna_trap_fault\n\t" + " mov %1, %%o1\n\t" + : + : "r" (regs), "r" (insn) + : "o0", "o1", "o2", "o3", "o4", "o5", "o7", + "g1", "g2", "g3", "g4", "g5", "g7", "cc"); + goto out; + } + advance(regs); + goto out; + } + +kill_user: + user_mna_trap_fault(regs, insn); +out: + unlock_kernel(); +} diff --git a/arch/sparc/kernel/vmlinux.lds.S b/arch/sparc/kernel/vmlinux.lds.S new file mode 100644 index 000000000000..38938d2e63aa --- /dev/null +++ b/arch/sparc/kernel/vmlinux.lds.S @@ -0,0 +1,103 @@ +/* ld script to make SparcLinux kernel */ + +#include <asm-generic/vmlinux.lds.h> + +OUTPUT_FORMAT("elf32-sparc", "elf32-sparc", "elf32-sparc") +OUTPUT_ARCH(sparc) +ENTRY(_start) +jiffies = jiffies_64 + 4; +SECTIONS +{ + . = 0x10000 + SIZEOF_HEADERS; + .text 0xf0004000 : + { + *(.text) + SCHED_TEXT + LOCK_TEXT + *(.gnu.warning) + } =0 + _etext = .; + PROVIDE (etext = .); + RODATA + .data : + { + *(.data) + CONSTRUCTORS + } + .data1 : { *(.data1) } + _edata = .; + PROVIDE (edata = .); + __start___fixup = .; + .fixup : { *(.fixup) } + __stop___fixup = .; + __start___ex_table = .; + __ex_table : { *(__ex_table) } + __stop___ex_table = .; + + . = ALIGN(4096); + __init_begin = .; + .init.text : { + _sinittext = .; + *(.init.text) + _einittext = .; + } + __init_text_end = .; + .init.data : { *(.init.data) } + . = ALIGN(16); + __setup_start = .; + .init.setup : { *(.init.setup) } + __setup_end = .; + __initcall_start = .; + .initcall.init : { + *(.initcall1.init) + *(.initcall2.init) + *(.initcall3.init) + *(.initcall4.init) + *(.initcall5.init) + *(.initcall6.init) + *(.initcall7.init) + } + __initcall_end = .; + __con_initcall_start = .; + .con_initcall.init : { *(.con_initcall.init) } + __con_initcall_end = .; + SECURITY_INIT + . = ALIGN(4096); + __initramfs_start = .; + .init.ramfs : { *(.init.ramfs) } + __initramfs_end = .; + . = ALIGN(32); + __per_cpu_start = .; + .data.percpu : { *(.data.percpu) } + __per_cpu_end = .; + . = ALIGN(4096); + __init_end = .; + . = ALIGN(32); + .data.cacheline_aligned : { *(.data.cacheline_aligned) } + + __bss_start = .; + .sbss : { *(.sbss) *(.scommon) } + .bss : + { + *(.dynbss) + *(.bss) + *(COMMON) + } + _end = . ; + PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .debug 0 : { *(.debug) } + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + .debug_sfnames 0 : { *(.debug_sfnames) } + .line 0 : { *(.line) } + /DISCARD/ : { *(.exit.text) *(.exit.data) *(.exitcall.exit) } +} diff --git a/arch/sparc/kernel/windows.c b/arch/sparc/kernel/windows.c new file mode 100644 index 000000000000..9cc93eaa4abf --- /dev/null +++ b/arch/sparc/kernel/windows.c @@ -0,0 +1,127 @@ +/* windows.c: Routines to deal with register window management + * at the C-code level. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> + +#include <asm/uaccess.h> + +/* Do save's until all user register windows are out of the cpu. */ +void flush_user_windows(void) +{ + register int ctr asm("g5"); + + ctr = 0; + __asm__ __volatile__( + "\n1:\n\t" + "ld [%%g6 + %2], %%g4\n\t" + "orcc %%g0, %%g4, %%g0\n\t" + "add %0, 1, %0\n\t" + "bne 1b\n\t" + " save %%sp, -64, %%sp\n" + "2:\n\t" + "subcc %0, 1, %0\n\t" + "bne 2b\n\t" + " restore %%g0, %%g0, %%g0\n" + : "=&r" (ctr) + : "0" (ctr), + "i" ((const unsigned long)TI_UWINMASK) + : "g4", "cc"); +} + +static inline void shift_window_buffer(int first_win, int last_win, struct thread_info *tp) +{ + int i; + + for(i = first_win; i < last_win; i++) { + tp->rwbuf_stkptrs[i] = tp->rwbuf_stkptrs[i+1]; + memcpy(&tp->reg_window[i], &tp->reg_window[i+1], sizeof(struct reg_window)); + } +} + +/* Place as many of the user's current register windows + * on the stack that we can. Even if the %sp is unaligned + * we still copy the window there, the only case that we don't + * succeed is if the %sp points to a bum mapping altogether. + * setup_frame() and do_sigreturn() use this before shifting + * the user stack around. Future instruction and hardware + * bug workaround routines will need this functionality as + * well. + */ +void synchronize_user_stack(void) +{ + struct thread_info *tp = current_thread_info(); + int window; + + flush_user_windows(); + if(!tp->w_saved) + return; + + /* Ok, there is some dirty work to do. */ + for(window = tp->w_saved - 1; window >= 0; window--) { + unsigned long sp = tp->rwbuf_stkptrs[window]; + + /* Ok, let it rip. */ + if (copy_to_user((char __user *) sp, &tp->reg_window[window], + sizeof(struct reg_window))) + continue; + + shift_window_buffer(window, tp->w_saved - 1, tp); + tp->w_saved--; + } +} + +#if 0 +/* An optimization. */ +static inline void copy_aligned_window(void *dest, const void *src) +{ + __asm__ __volatile__("ldd [%1], %%g2\n\t" + "ldd [%1 + 0x8], %%g4\n\t" + "std %%g2, [%0]\n\t" + "std %%g4, [%0 + 0x8]\n\t" + "ldd [%1 + 0x10], %%g2\n\t" + "ldd [%1 + 0x18], %%g4\n\t" + "std %%g2, [%0 + 0x10]\n\t" + "std %%g4, [%0 + 0x18]\n\t" + "ldd [%1 + 0x20], %%g2\n\t" + "ldd [%1 + 0x28], %%g4\n\t" + "std %%g2, [%0 + 0x20]\n\t" + "std %%g4, [%0 + 0x28]\n\t" + "ldd [%1 + 0x30], %%g2\n\t" + "ldd [%1 + 0x38], %%g4\n\t" + "std %%g2, [%0 + 0x30]\n\t" + "std %%g4, [%0 + 0x38]\n\t" : : + "r" (dest), "r" (src) : + "g2", "g3", "g4", "g5"); +} +#endif + +/* Try to push the windows in a threads window buffer to the + * user stack. Unaligned %sp's are not allowed here. + */ + +void try_to_clear_window_buffer(struct pt_regs *regs, int who) +{ + struct thread_info *tp = current_thread_info(); + int window; + + lock_kernel(); + flush_user_windows(); + for(window = 0; window < tp->w_saved; window++) { + unsigned long sp = tp->rwbuf_stkptrs[window]; + + if ((sp & 7) || + copy_to_user((char __user *) sp, &tp->reg_window[window], + sizeof(struct reg_window))) + do_exit(SIGILL); + } + tp->w_saved = 0; + unlock_kernel(); +} diff --git a/arch/sparc/kernel/wof.S b/arch/sparc/kernel/wof.S new file mode 100644 index 000000000000..083b1215d515 --- /dev/null +++ b/arch/sparc/kernel/wof.S @@ -0,0 +1,428 @@ +/* $Id: wof.S,v 1.40 2000/01/08 16:38:18 anton Exp $ + * wof.S: Sparc window overflow handler. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/contregs.h> +#include <asm/page.h> +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/smp.h> +#include <asm/asi.h> +#include <asm/winmacro.h> +#include <asm/asmmacro.h> +#include <asm/thread_info.h> + +/* WARNING: This routine is hairy and _very_ complicated, but it + * must be as fast as possible as it handles the allocation + * of register windows to the user and kernel. If you touch + * this code be _very_ careful as many other pieces of the + * kernel depend upon how this code behaves. You have been + * duly warned... + */ + +/* We define macro's for registers which have a fixed + * meaning throughout this entire routine. The 'T' in + * the comments mean that the register can only be + * accessed when in the 'trap' window, 'G' means + * accessible in any window. Do not change these registers + * after they have been set, until you are ready to return + * from the trap. + */ +#define t_psr l0 /* %psr at trap time T */ +#define t_pc l1 /* PC for trap return T */ +#define t_npc l2 /* NPC for trap return T */ +#define t_wim l3 /* %wim at trap time T */ +#define saved_g5 l5 /* Global save register T */ +#define saved_g6 l6 /* Global save register T */ +#define curptr g6 /* Gets set to 'current' then stays G */ + +/* Now registers whose values can change within the handler. */ +#define twin_tmp l4 /* Temp reg, only usable in trap window T */ +#define glob_tmp g5 /* Global temporary reg, usable anywhere G */ + + .text + .align 4 + /* BEGINNING OF PATCH INSTRUCTIONS */ + /* On a 7-window Sparc the boot code patches spnwin_* + * instructions with the following ones. + */ + .globl spnwin_patch1_7win, spnwin_patch2_7win, spnwin_patch3_7win +spnwin_patch1_7win: sll %t_wim, 6, %glob_tmp +spnwin_patch2_7win: and %glob_tmp, 0x7f, %glob_tmp +spnwin_patch3_7win: and %twin_tmp, 0x7f, %twin_tmp + /* END OF PATCH INSTRUCTIONS */ + + /* The trap entry point has done the following: + * + * rd %psr, %l0 + * rd %wim, %l3 + * b spill_window_entry + * andcc %l0, PSR_PS, %g0 + */ + + /* Datum current_thread_info->uwinmask contains at all times a bitmask + * where if any user windows are active, at least one bit will + * be set in to mask. If no user windows are active, the bitmask + * will be all zeroes. + */ + .globl spill_window_entry + .globl spnwin_patch1, spnwin_patch2, spnwin_patch3 +spill_window_entry: + /* LOCATION: Trap Window */ + + mov %g5, %saved_g5 ! save away global temp register + mov %g6, %saved_g6 ! save away 'current' ptr register + + /* Compute what the new %wim will be if we save the + * window properly in this trap handler. + * + * newwim = ((%wim>>1) | (%wim<<(nwindows - 1))); + */ + srl %t_wim, 0x1, %twin_tmp +spnwin_patch1: sll %t_wim, 7, %glob_tmp + or %glob_tmp, %twin_tmp, %glob_tmp +spnwin_patch2: and %glob_tmp, 0xff, %glob_tmp + + /* The trap entry point has set the condition codes + * up for us to see if this is from user or kernel. + * Get the load of 'curptr' out of the way. + */ + LOAD_CURRENT(curptr, twin_tmp) + + andcc %t_psr, PSR_PS, %g0 + be,a spwin_fromuser ! all user wins, branch + save %g0, %g0, %g0 ! Go where saving will occur + + /* See if any user windows are active in the set. */ + ld [%curptr + TI_UWINMASK], %twin_tmp ! grab win mask + orcc %g0, %twin_tmp, %g0 ! check for set bits + bne spwin_exist_uwins ! yep, there are some + andn %twin_tmp, %glob_tmp, %twin_tmp ! compute new uwinmask + + /* Save into the window which must be saved and do it. + * Basically if we are here, this means that we trapped + * from kernel mode with only kernel windows in the register + * file. + */ + save %g0, %g0, %g0 ! save into the window to stash away + wr %glob_tmp, 0x0, %wim ! set new %wim, this is safe now + +spwin_no_userwins_from_kernel: + /* LOCATION: Window to be saved */ + + STORE_WINDOW(sp) ! stash the window + restore %g0, %g0, %g0 ! go back into trap window + + /* LOCATION: Trap window */ + mov %saved_g5, %g5 ! restore %glob_tmp + mov %saved_g6, %g6 ! restore %curptr + wr %t_psr, 0x0, %psr ! restore condition codes in %psr + WRITE_PAUSE ! waste some time + jmp %t_pc ! Return from trap + rett %t_npc ! we are done + +spwin_exist_uwins: + /* LOCATION: Trap window */ + + /* Wow, user windows have to be dealt with, this is dirty + * and messy as all hell. And difficult to follow if you + * are approaching the infamous register window trap handling + * problem for the first time. DON'T LOOK! + * + * Note that how the execution path works out, the new %wim + * will be left for us in the global temporary register, + * %glob_tmp. We cannot set the new %wim first because we + * need to save into the appropriate window without inducing + * a trap (traps are off, we'd get a watchdog wheee)... + * But first, store the new user window mask calculated + * above. + */ + st %twin_tmp, [%curptr + TI_UWINMASK] + save %g0, %g0, %g0 ! Go to where the saving will occur + +spwin_fromuser: + /* LOCATION: Window to be saved */ + wr %glob_tmp, 0x0, %wim ! Now it is safe to set new %wim + + /* LOCATION: Window to be saved */ + + /* This instruction branches to a routine which will check + * to validity of the users stack pointer by whatever means + * are necessary. This means that this is architecture + * specific and thus this branch instruction will need to + * be patched at boot time once the machine type is known. + * This routine _shall not_ touch %curptr under any + * circumstances whatsoever! It will branch back to the + * label 'spwin_good_ustack' if the stack is ok but still + * needs to be dumped (SRMMU for instance will not need to + * do this) or 'spwin_finish_up' if the stack is ok and the + * registers have already been saved. If the stack is found + * to be bogus for some reason the routine shall branch to + * the label 'spwin_user_stack_is_bolixed' which will take + * care of things at that point. + */ + .globl spwin_mmu_patchme +spwin_mmu_patchme: b spwin_sun4c_stackchk + andcc %sp, 0x7, %g0 + +spwin_good_ustack: + /* LOCATION: Window to be saved */ + + /* The users stack is ok and we can safely save it at + * %sp. + */ + STORE_WINDOW(sp) + +spwin_finish_up: + restore %g0, %g0, %g0 /* Back to trap window. */ + + /* LOCATION: Trap window */ + + /* We have spilled successfully, and we have properly stored + * the appropriate window onto the stack. + */ + + /* Restore saved globals */ + mov %saved_g5, %g5 + mov %saved_g6, %g6 + + wr %t_psr, 0x0, %psr + WRITE_PAUSE + jmp %t_pc + rett %t_npc + +spwin_user_stack_is_bolixed: + /* LOCATION: Window to be saved */ + + /* Wheee, user has trashed his/her stack. We have to decide + * how to proceed based upon whether we came from kernel mode + * or not. If we came from kernel mode, toss the window into + * a special buffer and proceed, the kernel _needs_ a window + * and we could be in an interrupt handler so timing is crucial. + * If we came from user land we build a full stack frame and call + * c-code to gun down the process. + */ + rd %psr, %glob_tmp + andcc %glob_tmp, PSR_PS, %g0 + bne spwin_bad_ustack_from_kernel + nop + + /* Oh well, throw this one window into the per-task window + * buffer, the first one. + */ + st %sp, [%curptr + TI_RWIN_SPTRS] + STORE_WINDOW(curptr + TI_REG_WINDOW) + restore %g0, %g0, %g0 + + /* LOCATION: Trap Window */ + + /* Back in the trap window, update winbuffer save count. */ + mov 1, %twin_tmp + st %twin_tmp, [%curptr + TI_W_SAVED] + + /* Compute new user window mask. What we are basically + * doing is taking two windows, the invalid one at trap + * time and the one we attempted to throw onto the users + * stack, and saying that everything else is an ok user + * window. umask = ((~(%t_wim | %wim)) & valid_wim_bits) + */ + rd %wim, %twin_tmp + or %twin_tmp, %t_wim, %twin_tmp + not %twin_tmp +spnwin_patch3: and %twin_tmp, 0xff, %twin_tmp ! patched on 7win Sparcs + st %twin_tmp, [%curptr + TI_UWINMASK] + +#define STACK_OFFSET (THREAD_SIZE - TRACEREG_SZ - STACKFRAME_SZ) + + sethi %hi(STACK_OFFSET), %sp + or %sp, %lo(STACK_OFFSET), %sp + add %curptr, %sp, %sp + + /* Restore the saved globals and build a pt_regs frame. */ + mov %saved_g5, %g5 + mov %saved_g6, %g6 + STORE_PT_ALL(sp, t_psr, t_pc, t_npc, g1) + + sethi %hi(STACK_OFFSET), %g6 + or %g6, %lo(STACK_OFFSET), %g6 + sub %sp, %g6, %g6 ! curptr + + /* Turn on traps and call c-code to deal with it. */ + wr %t_psr, PSR_ET, %psr + nop + call window_overflow_fault + nop + + /* Return from trap if C-code actually fixes things, if it + * doesn't then we never get this far as the process will + * be given the look of death from Commander Peanut. + */ + b ret_trap_entry + clr %l6 + +spwin_bad_ustack_from_kernel: + /* LOCATION: Window to be saved */ + + /* The kernel provoked a spill window trap, but the window we + * need to save is a user one and the process has trashed its + * stack pointer. We need to be quick, so we throw it into + * a per-process window buffer until we can properly handle + * this later on. + */ + SAVE_BOLIXED_USER_STACK(curptr, glob_tmp) + restore %g0, %g0, %g0 + + /* LOCATION: Trap window */ + + /* Restore globals, condition codes in the %psr and + * return from trap. Note, restoring %g6 when returning + * to kernel mode is not necessarily these days. ;-) + */ + mov %saved_g5, %g5 + mov %saved_g6, %g6 + + wr %t_psr, 0x0, %psr + WRITE_PAUSE + + jmp %t_pc + rett %t_npc + +/* Undefine the register macros which would only cause trouble + * if used below. This helps find 'stupid' coding errors that + * produce 'odd' behavior. The routines below are allowed to + * make usage of glob_tmp and t_psr so we leave them defined. + */ +#undef twin_tmp +#undef curptr +#undef t_pc +#undef t_npc +#undef t_wim +#undef saved_g5 +#undef saved_g6 + +/* Now come the per-architecture window overflow stack checking routines. + * As noted above %curptr cannot be touched by this routine at all. + */ + + .globl spwin_sun4c_stackchk +spwin_sun4c_stackchk: + /* LOCATION: Window to be saved on the stack */ + + /* See if the stack is in the address space hole but first, + * check results of callers andcc %sp, 0x7, %g0 + */ + be 1f + sra %sp, 29, %glob_tmp + + rd %psr, %glob_tmp + b spwin_user_stack_is_bolixed + 0x4 + nop + +1: + add %glob_tmp, 0x1, %glob_tmp + andncc %glob_tmp, 0x1, %g0 + be 1f + and %sp, 0xfff, %glob_tmp ! delay slot + + rd %psr, %glob_tmp + b spwin_user_stack_is_bolixed + 0x4 + nop + + /* See if our dump area will be on more than one + * page. + */ +1: + add %glob_tmp, 0x38, %glob_tmp + andncc %glob_tmp, 0xff8, %g0 + be spwin_sun4c_onepage ! only one page to check + lda [%sp] ASI_PTE, %glob_tmp ! have to check first page anyways + +spwin_sun4c_twopages: + /* Is first page ok permission wise? */ + srl %glob_tmp, 29, %glob_tmp + cmp %glob_tmp, 0x6 + be 1f + add %sp, 0x38, %glob_tmp /* Is second page in vma hole? */ + + rd %psr, %glob_tmp + b spwin_user_stack_is_bolixed + 0x4 + nop + +1: + sra %glob_tmp, 29, %glob_tmp + add %glob_tmp, 0x1, %glob_tmp + andncc %glob_tmp, 0x1, %g0 + be 1f + add %sp, 0x38, %glob_tmp + + rd %psr, %glob_tmp + b spwin_user_stack_is_bolixed + 0x4 + nop + +1: + lda [%glob_tmp] ASI_PTE, %glob_tmp + +spwin_sun4c_onepage: + srl %glob_tmp, 29, %glob_tmp + cmp %glob_tmp, 0x6 ! can user write to it? + be spwin_good_ustack ! success + nop + + rd %psr, %glob_tmp + b spwin_user_stack_is_bolixed + 0x4 + nop + + /* This is a generic SRMMU routine. As far as I know this + * works for all current v8/srmmu implementations, we'll + * see... + */ + .globl spwin_srmmu_stackchk +spwin_srmmu_stackchk: + /* LOCATION: Window to be saved on the stack */ + + /* Because of SMP concerns and speed we play a trick. + * We disable fault traps in the MMU control register, + * Execute the stores, then check the fault registers + * to see what happens. I can hear Linus now + * "disgusting... broken hardware...". + * + * But first, check to see if the users stack has ended + * up in kernel vma, then we would succeed for the 'wrong' + * reason... ;( Note that the 'sethi' below assumes the + * kernel is page aligned, which should always be the case. + */ + /* Check results of callers andcc %sp, 0x7, %g0 */ + bne spwin_user_stack_is_bolixed + sethi %hi(PAGE_OFFSET), %glob_tmp + cmp %glob_tmp, %sp + bleu spwin_user_stack_is_bolixed + mov AC_M_SFSR, %glob_tmp + + /* Clear the fault status and turn on the no_fault bit. */ + lda [%glob_tmp] ASI_M_MMUREGS, %g0 ! eat SFSR + + lda [%g0] ASI_M_MMUREGS, %glob_tmp ! read MMU control + or %glob_tmp, 0x2, %glob_tmp ! or in no_fault bit + sta %glob_tmp, [%g0] ASI_M_MMUREGS ! set it + + /* Dump the registers and cross fingers. */ + STORE_WINDOW(sp) + + /* Clear the no_fault bit and check the status. */ + andn %glob_tmp, 0x2, %glob_tmp + sta %glob_tmp, [%g0] ASI_M_MMUREGS + + mov AC_M_SFAR, %glob_tmp + lda [%glob_tmp] ASI_M_MMUREGS, %g0 + + mov AC_M_SFSR, %glob_tmp + lda [%glob_tmp] ASI_M_MMUREGS, %glob_tmp + andcc %glob_tmp, 0x2, %g0 ! did we fault? + be,a spwin_finish_up + 0x4 ! cool beans, success + restore %g0, %g0, %g0 + + rd %psr, %glob_tmp + b spwin_user_stack_is_bolixed + 0x4 ! we faulted, ugh + nop diff --git a/arch/sparc/kernel/wuf.S b/arch/sparc/kernel/wuf.S new file mode 100644 index 000000000000..d1a266bf103a --- /dev/null +++ b/arch/sparc/kernel/wuf.S @@ -0,0 +1,360 @@ +/* $Id: wuf.S,v 1.39 2000/01/08 16:38:18 anton Exp $ + * wuf.S: Window underflow trap handler for the Sparc. + * + * Copyright (C) 1995 David S. Miller + */ + +#include <asm/contregs.h> +#include <asm/page.h> +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/smp.h> +#include <asm/asi.h> +#include <asm/winmacro.h> +#include <asm/asmmacro.h> +#include <asm/thread_info.h> + +/* Just like the overflow handler we define macros for registers + * with fixed meanings in this routine. + */ +#define t_psr l0 +#define t_pc l1 +#define t_npc l2 +#define t_wim l3 +/* Don't touch the above registers or else you die horribly... */ + +/* Now macros for the available scratch registers in this routine. */ +#define twin_tmp1 l4 +#define twin_tmp2 l5 + +#define curptr g6 + + .text + .align 4 + + /* The trap entry point has executed the following: + * + * rd %psr, %l0 + * rd %wim, %l3 + * b fill_window_entry + * andcc %l0, PSR_PS, %g0 + */ + + /* Datum current_thread_info->uwinmask contains at all times a bitmask + * where if any user windows are active, at least one bit will + * be set in to mask. If no user windows are active, the bitmask + * will be all zeroes. + */ + + /* To get an idea of what has just happened to cause this + * trap take a look at this diagram: + * + * 1 2 3 4 <-- Window number + * ---------- + * T O W I <-- Symbolic name + * + * O == the window that execution was in when + * the restore was attempted + * + * T == the trap itself has save'd us into this + * window + * + * W == this window is the one which is now invalid + * and must be made valid plus loaded from the + * stack + * + * I == this window will be the invalid one when we + * are done and return from trap if successful + */ + + /* BEGINNING OF PATCH INSTRUCTIONS */ + + /* On 7-window Sparc the boot code patches fnwin_patch1 + * with the following instruction. + */ + .globl fnwin_patch1_7win, fnwin_patch2_7win +fnwin_patch1_7win: srl %t_wim, 6, %twin_tmp2 +fnwin_patch2_7win: and %twin_tmp1, 0x7f, %twin_tmp1 + /* END OF PATCH INSTRUCTIONS */ + + .globl fill_window_entry, fnwin_patch1, fnwin_patch2 +fill_window_entry: + /* LOCATION: Window 'T' */ + + /* Compute what the new %wim is going to be if we retrieve + * the proper window off of the stack. + */ + sll %t_wim, 1, %twin_tmp1 +fnwin_patch1: srl %t_wim, 7, %twin_tmp2 + or %twin_tmp1, %twin_tmp2, %twin_tmp1 +fnwin_patch2: and %twin_tmp1, 0xff, %twin_tmp1 + + wr %twin_tmp1, 0x0, %wim /* Make window 'I' invalid */ + + andcc %t_psr, PSR_PS, %g0 + be fwin_from_user + restore %g0, %g0, %g0 /* Restore to window 'O' */ + + /* Trapped from kernel, we trust that the kernel does not + * 'over restore' sorta speak and just grab the window + * from the stack and return. Easy enough. + */ +fwin_from_kernel: + /* LOCATION: Window 'O' */ + + restore %g0, %g0, %g0 + + /* LOCATION: Window 'W' */ + + LOAD_WINDOW(sp) /* Load it up */ + + /* Spin the wheel... */ + save %g0, %g0, %g0 + save %g0, %g0, %g0 + /* I'd like to buy a vowel please... */ + + /* LOCATION: Window 'T' */ + + /* Now preserve the condition codes in %psr, pause, and + * return from trap. This is the simplest case of all. + */ + wr %t_psr, 0x0, %psr + WRITE_PAUSE + + jmp %t_pc + rett %t_npc + +fwin_from_user: + /* LOCATION: Window 'O' */ + + restore %g0, %g0, %g0 /* Restore to window 'W' */ + + /* LOCATION: Window 'W' */ + + /* Branch to the architecture specific stack validation + * routine. They can be found below... + */ + .globl fwin_mmu_patchme +fwin_mmu_patchme: b sun4c_fwin_stackchk + andcc %sp, 0x7, %g0 + +#define STACK_OFFSET (THREAD_SIZE - TRACEREG_SZ - STACKFRAME_SZ) + +fwin_user_stack_is_bolixed: + /* LOCATION: Window 'W' */ + + /* Place a pt_regs frame on the kernel stack, save back + * to the trap window and call c-code to deal with this. + */ + LOAD_CURRENT(l4, l5) + + sethi %hi(STACK_OFFSET), %l5 + or %l5, %lo(STACK_OFFSET), %l5 + add %l4, %l5, %l5 + + /* Store globals into pt_regs frame. */ + STORE_PT_GLOBALS(l5) + STORE_PT_YREG(l5, g3) + + /* Save current in a global while we change windows. */ + mov %l4, %curptr + + save %g0, %g0, %g0 + + /* LOCATION: Window 'O' */ + + rd %psr, %g3 /* Read %psr in live user window */ + mov %fp, %g4 /* Save bogus frame pointer. */ + + save %g0, %g0, %g0 + + /* LOCATION: Window 'T' */ + + sethi %hi(STACK_OFFSET), %l5 + or %l5, %lo(STACK_OFFSET), %l5 + add %curptr, %l5, %sp + + /* Build rest of pt_regs. */ + STORE_PT_INS(sp) + STORE_PT_PRIV(sp, t_psr, t_pc, t_npc) + + /* re-set trap time %wim value */ + wr %t_wim, 0x0, %wim + + /* Fix users window mask and buffer save count. */ + mov 0x1, %g5 + sll %g5, %g3, %g5 + st %g5, [%curptr + TI_UWINMASK] ! one live user window still + st %g0, [%curptr + TI_W_SAVED] ! no windows in the buffer + + wr %t_psr, PSR_ET, %psr ! enable traps + nop + call window_underflow_fault + mov %g4, %o0 + + b ret_trap_entry + clr %l6 + +fwin_user_stack_is_ok: + /* LOCATION: Window 'W' */ + + /* The users stack area is kosher and mapped, load the + * window and fall through to the finish up routine. + */ + LOAD_WINDOW(sp) + + /* Round and round she goes... */ + save %g0, %g0, %g0 /* Save to window 'O' */ + save %g0, %g0, %g0 /* Save to window 'T' */ + /* Where she'll trap nobody knows... */ + + /* LOCATION: Window 'T' */ + +fwin_user_finish_up: + /* LOCATION: Window 'T' */ + + wr %t_psr, 0x0, %psr + WRITE_PAUSE + + jmp %t_pc + rett %t_npc + + /* Here come the architecture specific checks for stack. + * mappings. Note that unlike the window overflow handler + * we only need to check whether the user can read from + * the appropriate addresses. Also note that we are in + * an invalid window which will be loaded, and this means + * that until we actually load the window up we are free + * to use any of the local registers contained within. + * + * On success these routine branch to fwin_user_stack_is_ok + * if the area at %sp is user readable and the window still + * needs to be loaded, else fwin_user_finish_up if the + * routine has done the loading itself. On failure (bogus + * user stack) the routine shall branch to the label called + * fwin_user_stack_is_bolixed. + * + * Contrary to the arch-specific window overflow stack + * check routines in wof.S, these routines are free to use + * any of the local registers they want to as this window + * does not belong to anyone at this point, however the + * outs and ins are still verboten as they are part of + * 'someone elses' window possibly. + */ + + .align 4 + .globl sun4c_fwin_stackchk +sun4c_fwin_stackchk: + /* LOCATION: Window 'W' */ + + /* Caller did 'andcc %sp, 0x7, %g0' */ + be 1f + and %sp, 0xfff, %l0 ! delay slot + + b,a fwin_user_stack_is_bolixed + + /* See if we have to check the sanity of one page or two */ +1: + add %l0, 0x38, %l0 + sra %sp, 29, %l5 + add %l5, 0x1, %l5 + andncc %l5, 0x1, %g0 + be 1f + andncc %l0, 0xff8, %g0 + + b,a fwin_user_stack_is_bolixed /* %sp is in vma hole, yuck */ + +1: + be sun4c_fwin_onepage /* Only one page to check */ + lda [%sp] ASI_PTE, %l1 +sun4c_fwin_twopages: + add %sp, 0x38, %l0 + sra %l0, 29, %l5 + add %l5, 0x1, %l5 + andncc %l5, 0x1, %g0 + be 1f + lda [%l0] ASI_PTE, %l1 + + b,a fwin_user_stack_is_bolixed /* Second page in vma hole */ + +1: + srl %l1, 29, %l1 + andcc %l1, 0x4, %g0 + bne sun4c_fwin_onepage + lda [%sp] ASI_PTE, %l1 + + b,a fwin_user_stack_is_bolixed /* Second page has bad perms */ + +sun4c_fwin_onepage: + srl %l1, 29, %l1 + andcc %l1, 0x4, %g0 + bne fwin_user_stack_is_ok + nop + + /* A page had bad page permissions, losing... */ + b,a fwin_user_stack_is_bolixed + + .globl srmmu_fwin_stackchk +srmmu_fwin_stackchk: + /* LOCATION: Window 'W' */ + + /* Caller did 'andcc %sp, 0x7, %g0' */ + bne fwin_user_stack_is_bolixed + sethi %hi(PAGE_OFFSET), %l5 + + /* Check if the users stack is in kernel vma, then our + * trial and error technique below would succeed for + * the 'wrong' reason. + */ + mov AC_M_SFSR, %l4 + cmp %l5, %sp + bleu fwin_user_stack_is_bolixed + lda [%l4] ASI_M_MMUREGS, %g0 ! clear fault status + + /* The technique is, turn off faults on this processor, + * just let the load rip, then check the sfsr to see if + * a fault did occur. Then we turn on fault traps again + * and branch conditionally based upon what happened. + */ + lda [%g0] ASI_M_MMUREGS, %l5 ! read mmu-ctrl reg + or %l5, 0x2, %l5 ! turn on no-fault bit + sta %l5, [%g0] ASI_M_MMUREGS ! store it + + /* Cross fingers and go for it. */ + LOAD_WINDOW(sp) + + /* A penny 'saved'... */ + save %g0, %g0, %g0 + save %g0, %g0, %g0 + /* Is a BADTRAP earned... */ + + /* LOCATION: Window 'T' */ + + lda [%g0] ASI_M_MMUREGS, %twin_tmp1 ! load mmu-ctrl again + andn %twin_tmp1, 0x2, %twin_tmp1 ! clear no-fault bit + sta %twin_tmp1, [%g0] ASI_M_MMUREGS ! store it + + mov AC_M_SFAR, %twin_tmp2 + lda [%twin_tmp2] ASI_M_MMUREGS, %g0 ! read fault address + + mov AC_M_SFSR, %twin_tmp2 + lda [%twin_tmp2] ASI_M_MMUREGS, %twin_tmp2 ! read fault status + andcc %twin_tmp2, 0x2, %g0 ! did fault occur? + + bne 1f ! yep, cleanup + nop + + wr %t_psr, 0x0, %psr + nop + b fwin_user_finish_up + 0x4 + nop + + /* Did I ever tell you about my window lobotomy? + * anyways... fwin_user_stack_is_bolixed expects + * to be in window 'W' so make it happy or else + * we watchdog badly. + */ +1: + restore %g0, %g0, %g0 + b fwin_user_stack_is_bolixed ! oh well + restore %g0, %g0, %g0 diff --git a/arch/sparc/lib/COPYING.LIB b/arch/sparc/lib/COPYING.LIB new file mode 100644 index 000000000000..eb685a5ec981 --- /dev/null +++ b/arch/sparc/lib/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/arch/sparc/lib/Makefile b/arch/sparc/lib/Makefile new file mode 100644 index 000000000000..2296ff9dc47a --- /dev/null +++ b/arch/sparc/lib/Makefile @@ -0,0 +1,13 @@ +# $Id: Makefile,v 1.35 2000/12/15 00:41:18 davem Exp $ +# Makefile for Sparc library files.. +# + +EXTRA_AFLAGS := -ansi -DST_DIV0=0x02 + +lib-y := mul.o rem.o sdiv.o udiv.o umul.o urem.o ashrdi3.o memcpy.o memset.o \ + strlen.o checksum.o blockops.o memscan.o memcmp.o strncmp.o \ + strncpy_from_user.o divdi3.o udivdi3.o strlen_user.o \ + copy_user.o locks.o atomic.o atomic32.o bitops.o \ + lshrdi3.o ashldi3.o rwsem.o muldi3.o bitext.o + +lib-$(CONFIG_DEBUG_SPINLOCK) += debuglocks.o diff --git a/arch/sparc/lib/ashldi3.S b/arch/sparc/lib/ashldi3.S new file mode 100644 index 000000000000..52418a0cb3dd --- /dev/null +++ b/arch/sparc/lib/ashldi3.S @@ -0,0 +1,34 @@ +/* $Id: ashldi3.S,v 1.2 1999/11/19 04:11:46 davem Exp $ + * ashldi3.S: GCC emits these for certain drivers playing + * with long longs. + * + * Copyright (C) 1999 David S. Miller (davem@redhat.com) + */ + + .text + .align 4 + .globl __ashldi3 +__ashldi3: + cmp %o2, 0 + be 9f + mov 0x20, %g2 + + sub %g2, %o2, %g2 + cmp %g2, 0 + bg 7f + sll %o0, %o2, %g3 + + neg %g2 + clr %o5 + b 8f + sll %o1, %g2, %o4 +7: + srl %o1, %g2, %g2 + sll %o1, %o2, %o5 + or %g3, %g2, %o4 +8: + mov %o4, %o0 + mov %o5, %o1 +9: + retl + nop diff --git a/arch/sparc/lib/ashrdi3.S b/arch/sparc/lib/ashrdi3.S new file mode 100644 index 000000000000..2848237598a4 --- /dev/null +++ b/arch/sparc/lib/ashrdi3.S @@ -0,0 +1,36 @@ +/* $Id: ashrdi3.S,v 1.4 1999/11/19 04:11:49 davem Exp $ + * ashrdi3.S: The filesystem code creates all kinds of references to + * this little routine on the sparc with gcc. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + + .text + .align 4 + .globl __ashrdi3 +__ashrdi3: + tst %o2 + be 3f + or %g0, 32, %g2 + + sub %g2, %o2, %g2 + + tst %g2 + bg 1f + sra %o0, %o2, %o4 + + sra %o0, 31, %o4 + sub %g0, %g2, %g2 + ba 2f + sra %o0, %g2, %o5 + +1: + sll %o0, %g2, %g3 + srl %o1, %o2, %g2 + or %g2, %g3, %o5 +2: + or %g0, %o4, %o0 + or %g0, %o5, %o1 +3: + jmpl %o7 + 8, %g0 + nop diff --git a/arch/sparc/lib/atomic.S b/arch/sparc/lib/atomic.S new file mode 100644 index 000000000000..f48ad0c4dadb --- /dev/null +++ b/arch/sparc/lib/atomic.S @@ -0,0 +1,100 @@ +/* atomic.S: Move this stuff here for better ICACHE hit rates. + * + * Copyright (C) 1996 David S. Miller (davem@caipfs.rutgers.edu) + */ + +#include <linux/config.h> +#include <asm/ptrace.h> +#include <asm/psr.h> + + .text + .align 4 + + .globl __atomic_begin +__atomic_begin: + +#ifndef CONFIG_SMP + .globl ___xchg32_sun4c +___xchg32_sun4c: + rd %psr, %g3 + andcc %g3, PSR_PIL, %g0 + bne 1f + nop + wr %g3, PSR_PIL, %psr + nop; nop; nop +1: + andcc %g3, PSR_PIL, %g0 + ld [%g1], %g7 + bne 1f + st %g2, [%g1] + wr %g3, 0x0, %psr + nop; nop; nop +1: + mov %g7, %g2 + jmpl %o7 + 8, %g0 + mov %g4, %o7 + + .globl ___xchg32_sun4md +___xchg32_sun4md: + swap [%g1], %g2 + jmpl %o7 + 8, %g0 + mov %g4, %o7 +#endif + + /* Read asm-sparc/atomic.h carefully to understand how this works for SMP. + * Really, some things here for SMP are overly clever, go read the header. + */ + .globl ___atomic24_add +___atomic24_add: + rd %psr, %g3 ! Keep the code small, old way was stupid + nop; nop; nop; ! Let the bits set + or %g3, PSR_PIL, %g7 ! Disable interrupts + wr %g7, 0x0, %psr ! Set %psr + nop; nop; nop; ! Let the bits set +#ifdef CONFIG_SMP +1: ldstub [%g1 + 3], %g7 ! Spin on the byte lock for SMP. + orcc %g7, 0x0, %g0 ! Did we get it? + bne 1b ! Nope... + ld [%g1], %g7 ! Load locked atomic24_t + sra %g7, 8, %g7 ! Get signed 24-bit integer + add %g7, %g2, %g2 ! Add in argument + sll %g2, 8, %g7 ! Transpose back to atomic24_t + st %g7, [%g1] ! Clever: This releases the lock as well. +#else + ld [%g1], %g7 ! Load locked atomic24_t + add %g7, %g2, %g2 ! Add in argument + st %g2, [%g1] ! Store it back +#endif + wr %g3, 0x0, %psr ! Restore original PSR_PIL + nop; nop; nop; ! Let the bits set + jmpl %o7, %g0 ! NOTE: not + 8, see callers in atomic.h + mov %g4, %o7 ! Restore %o7 + + .globl ___atomic24_sub +___atomic24_sub: + rd %psr, %g3 ! Keep the code small, old way was stupid + nop; nop; nop; ! Let the bits set + or %g3, PSR_PIL, %g7 ! Disable interrupts + wr %g7, 0x0, %psr ! Set %psr + nop; nop; nop; ! Let the bits set +#ifdef CONFIG_SMP +1: ldstub [%g1 + 3], %g7 ! Spin on the byte lock for SMP. + orcc %g7, 0x0, %g0 ! Did we get it? + bne 1b ! Nope... + ld [%g1], %g7 ! Load locked atomic24_t + sra %g7, 8, %g7 ! Get signed 24-bit integer + sub %g7, %g2, %g2 ! Subtract argument + sll %g2, 8, %g7 ! Transpose back to atomic24_t + st %g7, [%g1] ! Clever: This releases the lock as well +#else + ld [%g1], %g7 ! Load locked atomic24_t + sub %g7, %g2, %g2 ! Subtract argument + st %g2, [%g1] ! Store it back +#endif + wr %g3, 0x0, %psr ! Restore original PSR_PIL + nop; nop; nop; ! Let the bits set + jmpl %o7, %g0 ! NOTE: not + 8, see callers in atomic.h + mov %g4, %o7 ! Restore %o7 + + .globl __atomic_end +__atomic_end: diff --git a/arch/sparc/lib/atomic32.c b/arch/sparc/lib/atomic32.c new file mode 100644 index 000000000000..19724c5800a7 --- /dev/null +++ b/arch/sparc/lib/atomic32.c @@ -0,0 +1,53 @@ +/* + * atomic32.c: 32-bit atomic_t implementation + * + * Copyright (C) 2004 Keith M Wesolowski + * + * Based on asm-parisc/atomic.h Copyright (C) 2000 Philipp Rumpf + */ + +#include <asm/atomic.h> +#include <linux/spinlock.h> +#include <linux/module.h> + +#ifdef CONFIG_SMP +#define ATOMIC_HASH_SIZE 4 +#define ATOMIC_HASH(a) (&__atomic_hash[(((unsigned long)a)>>8) & (ATOMIC_HASH_SIZE-1)]) + +spinlock_t __atomic_hash[ATOMIC_HASH_SIZE] = { + [0 ... (ATOMIC_HASH_SIZE-1)] = SPIN_LOCK_UNLOCKED +}; + +#else /* SMP */ + +static spinlock_t dummy = SPIN_LOCK_UNLOCKED; +#define ATOMIC_HASH_SIZE 1 +#define ATOMIC_HASH(a) (&dummy) + +#endif /* SMP */ + +int __atomic_add_return(int i, atomic_t *v) +{ + int ret; + unsigned long flags; + spin_lock_irqsave(ATOMIC_HASH(v), flags); + + ret = (v->counter += i); + + spin_unlock_irqrestore(ATOMIC_HASH(v), flags); + return ret; +} + +void atomic_set(atomic_t *v, int i) +{ + unsigned long flags; + spin_lock_irqsave(ATOMIC_HASH(v), flags); + + v->counter = i; + + spin_unlock_irqrestore(ATOMIC_HASH(v), flags); +} + +EXPORT_SYMBOL(__atomic_add_return); +EXPORT_SYMBOL(atomic_set); + diff --git a/arch/sparc/lib/bitext.c b/arch/sparc/lib/bitext.c new file mode 100644 index 000000000000..94b05e8c906c --- /dev/null +++ b/arch/sparc/lib/bitext.c @@ -0,0 +1,132 @@ +/* + * bitext.c: kernel little helper (of bit shuffling variety). + * + * Copyright (C) 2002 Pete Zaitcev <zaitcev@yahoo.com> + * + * The algorithm to search a zero bit string is geared towards its application. + * We expect a couple of fixed sizes of requests, so a rotating counter, reset + * by align size, should provide fast enough search while maintaining low + * fragmentation. + */ + +#include <linux/smp_lock.h> +#include <linux/bitops.h> + +#include <asm/bitext.h> + +/** + * bit_map_string_get - find and set a bit string in bit map. + * @t: the bit map. + * @len: requested string length + * @align: requested alignment + * + * Returns offset in the map or -1 if out of space. + * + * Not safe to call from an interrupt (uses spin_lock). + */ +int bit_map_string_get(struct bit_map *t, int len, int align) +{ + int offset, count; /* siamese twins */ + int off_new; + int align1; + int i, color; + + if (t->num_colors) { + /* align is overloaded to be the page color */ + color = align; + align = t->num_colors; + } else { + color = 0; + if (align == 0) + align = 1; + } + align1 = align - 1; + if ((align & align1) != 0) + BUG(); + if (align < 0 || align >= t->size) + BUG(); + if (len <= 0 || len > t->size) + BUG(); + color &= align1; + + spin_lock(&t->lock); + if (len < t->last_size) + offset = t->first_free; + else + offset = t->last_off & ~align1; + count = 0; + for (;;) { + off_new = find_next_zero_bit(t->map, t->size, offset); + off_new = ((off_new + align1) & ~align1) + color; + count += off_new - offset; + offset = off_new; + if (offset >= t->size) + offset = 0; + if (count + len > t->size) { + spin_unlock(&t->lock); +/* P3 */ printk(KERN_ERR + "bitmap out: size %d used %d off %d len %d align %d count %d\n", + t->size, t->used, offset, len, align, count); + return -1; + } + + if (offset + len > t->size) { + count += t->size - offset; + offset = 0; + continue; + } + + i = 0; + while (test_bit(offset + i, t->map) == 0) { + i++; + if (i == len) { + for (i = 0; i < len; i++) + __set_bit(offset + i, t->map); + if (offset == t->first_free) + t->first_free = find_next_zero_bit + (t->map, t->size, + t->first_free + len); + if ((t->last_off = offset + len) >= t->size) + t->last_off = 0; + t->used += len; + t->last_size = len; + spin_unlock(&t->lock); + return offset; + } + } + count += i + 1; + if ((offset += i + 1) >= t->size) + offset = 0; + } +} + +void bit_map_clear(struct bit_map *t, int offset, int len) +{ + int i; + + if (t->used < len) + BUG(); /* Much too late to do any good, but alas... */ + spin_lock(&t->lock); + for (i = 0; i < len; i++) { + if (test_bit(offset + i, t->map) == 0) + BUG(); + __clear_bit(offset + i, t->map); + } + if (offset < t->first_free) + t->first_free = offset; + t->used -= len; + spin_unlock(&t->lock); +} + +void bit_map_init(struct bit_map *t, unsigned long *map, int size) +{ + + if ((size & 07) != 0) + BUG(); + memset(map, 0, size>>3); + + memset(t, 0, sizeof *t); + spin_lock_init(&t->lock); + t->map = map; + t->size = size; +} diff --git a/arch/sparc/lib/bitops.S b/arch/sparc/lib/bitops.S new file mode 100644 index 000000000000..3e9399769075 --- /dev/null +++ b/arch/sparc/lib/bitops.S @@ -0,0 +1,110 @@ +/* bitops.S: Low level assembler bit operations. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/config.h> +#include <asm/ptrace.h> +#include <asm/psr.h> + + .text + .align 4 + + .globl __bitops_begin +__bitops_begin: + + /* Take bits in %g2 and set them in word at %g1, + * return whether bits were set in original value + * in %g2. %g4 holds value to restore into %o7 + * in delay slot of jmpl return, %g3 + %g5 + %g7 can be + * used as temporaries and thus is considered clobbered + * by all callers. + */ + .globl ___set_bit +___set_bit: + rd %psr, %g3 + nop; nop; nop; + or %g3, PSR_PIL, %g5 + wr %g5, 0x0, %psr + nop; nop; nop +#ifdef CONFIG_SMP + set bitops_spinlock, %g5 +2: ldstub [%g5], %g7 ! Spin on the byte lock for SMP. + orcc %g7, 0x0, %g0 ! Did we get it? + bne 2b ! Nope... +#endif + ld [%g1], %g7 + or %g7, %g2, %g5 + and %g7, %g2, %g2 +#ifdef CONFIG_SMP + st %g5, [%g1] + set bitops_spinlock, %g5 + stb %g0, [%g5] +#else + st %g5, [%g1] +#endif + wr %g3, 0x0, %psr + nop; nop; nop + jmpl %o7, %g0 + mov %g4, %o7 + + /* Same as above, but clears the bits from %g2 instead. */ + .globl ___clear_bit +___clear_bit: + rd %psr, %g3 + nop; nop; nop + or %g3, PSR_PIL, %g5 + wr %g5, 0x0, %psr + nop; nop; nop +#ifdef CONFIG_SMP + set bitops_spinlock, %g5 +2: ldstub [%g5], %g7 ! Spin on the byte lock for SMP. + orcc %g7, 0x0, %g0 ! Did we get it? + bne 2b ! Nope... +#endif + ld [%g1], %g7 + andn %g7, %g2, %g5 + and %g7, %g2, %g2 +#ifdef CONFIG_SMP + st %g5, [%g1] + set bitops_spinlock, %g5 + stb %g0, [%g5] +#else + st %g5, [%g1] +#endif + wr %g3, 0x0, %psr + nop; nop; nop + jmpl %o7, %g0 + mov %g4, %o7 + + /* Same thing again, but this time toggles the bits from %g2. */ + .globl ___change_bit +___change_bit: + rd %psr, %g3 + nop; nop; nop + or %g3, PSR_PIL, %g5 + wr %g5, 0x0, %psr + nop; nop; nop +#ifdef CONFIG_SMP + set bitops_spinlock, %g5 +2: ldstub [%g5], %g7 ! Spin on the byte lock for SMP. + orcc %g7, 0x0, %g0 ! Did we get it? + bne 2b ! Nope... +#endif + ld [%g1], %g7 + xor %g7, %g2, %g5 + and %g7, %g2, %g2 +#ifdef CONFIG_SMP + st %g5, [%g1] + set bitops_spinlock, %g5 + stb %g0, [%g5] +#else + st %g5, [%g1] +#endif + wr %g3, 0x0, %psr + nop; nop; nop + jmpl %o7, %g0 + mov %g4, %o7 + + .globl __bitops_end +__bitops_end: diff --git a/arch/sparc/lib/blockops.S b/arch/sparc/lib/blockops.S new file mode 100644 index 000000000000..a7c7ffaa4a94 --- /dev/null +++ b/arch/sparc/lib/blockops.S @@ -0,0 +1,89 @@ +/* $Id: blockops.S,v 1.8 1998/01/30 10:58:44 jj Exp $ + * blockops.S: Common block zero optimized routines. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/page.h> + + /* Zero out 64 bytes of memory at (buf + offset). + * Assumes %g1 contains zero. + */ +#define BLAST_BLOCK(buf, offset) \ + std %g0, [buf + offset + 0x38]; \ + std %g0, [buf + offset + 0x30]; \ + std %g0, [buf + offset + 0x28]; \ + std %g0, [buf + offset + 0x20]; \ + std %g0, [buf + offset + 0x18]; \ + std %g0, [buf + offset + 0x10]; \ + std %g0, [buf + offset + 0x08]; \ + std %g0, [buf + offset + 0x00]; + + /* Copy 32 bytes of memory at (src + offset) to + * (dst + offset). + */ +#define MIRROR_BLOCK(dst, src, offset, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [src + offset + 0x18], t0; \ + ldd [src + offset + 0x10], t2; \ + ldd [src + offset + 0x08], t4; \ + ldd [src + offset + 0x00], t6; \ + std t0, [dst + offset + 0x18]; \ + std t2, [dst + offset + 0x10]; \ + std t4, [dst + offset + 0x08]; \ + std t6, [dst + offset + 0x00]; + + /* Profiling evidence indicates that memset() is + * commonly called for blocks of size PAGE_SIZE, + * and (2 * PAGE_SIZE) (for kernel stacks) + * and with a second arg of zero. We assume in + * all of these cases that the buffer is aligned + * on at least an 8 byte boundary. + * + * Therefore we special case them to make them + * as fast as possible. + */ + + .text + .align 4 + .globl bzero_1page, __copy_1page + +bzero_1page: +/* NOTE: If you change the number of insns of this routine, please check + * arch/sparc/mm/hypersparc.S */ + /* %o0 = buf */ + or %g0, %g0, %g1 + or %o0, %g0, %o1 + or %g0, (PAGE_SIZE >> 8), %g2 +1: + BLAST_BLOCK(%o0, 0x00) + BLAST_BLOCK(%o0, 0x40) + BLAST_BLOCK(%o0, 0x80) + BLAST_BLOCK(%o0, 0xc0) + subcc %g2, 1, %g2 + bne 1b + add %o0, 0x100, %o0 + + retl + nop + +__copy_1page: +/* NOTE: If you change the number of insns of this routine, please check + * arch/sparc/mm/hypersparc.S */ + /* %o0 = dst, %o1 = src */ + or %g0, (PAGE_SIZE >> 8), %g1 +1: + MIRROR_BLOCK(%o0, %o1, 0x00, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + MIRROR_BLOCK(%o0, %o1, 0x20, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + MIRROR_BLOCK(%o0, %o1, 0x40, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + MIRROR_BLOCK(%o0, %o1, 0x60, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + MIRROR_BLOCK(%o0, %o1, 0x80, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + MIRROR_BLOCK(%o0, %o1, 0xa0, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + MIRROR_BLOCK(%o0, %o1, 0xc0, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + MIRROR_BLOCK(%o0, %o1, 0xe0, %o2, %o3, %o4, %o5, %g2, %g3, %g4, %g5) + subcc %g1, 1, %g1 + add %o0, 0x100, %o0 + bne 1b + add %o1, 0x100, %o1 + + retl + nop diff --git a/arch/sparc/lib/checksum.S b/arch/sparc/lib/checksum.S new file mode 100644 index 000000000000..77f228533d47 --- /dev/null +++ b/arch/sparc/lib/checksum.S @@ -0,0 +1,583 @@ +/* checksum.S: Sparc optimized checksum code. + * + * Copyright(C) 1995 Linus Torvalds + * Copyright(C) 1995 Miguel de Icaza + * Copyright(C) 1996 David S. Miller + * Copyright(C) 1997 Jakub Jelinek + * + * derived from: + * Linux/Alpha checksum c-code + * Linux/ix86 inline checksum assembly + * RFC1071 Computing the Internet Checksum (esp. Jacobsons m68k code) + * David Mosberger-Tang for optimized reference c-code + * BSD4.4 portable checksum routine + */ + +#include <asm/errno.h> + +#define CSUM_BIGCHUNK(buf, offset, sum, t0, t1, t2, t3, t4, t5) \ + ldd [buf + offset + 0x00], t0; \ + ldd [buf + offset + 0x08], t2; \ + addxcc t0, sum, sum; \ + addxcc t1, sum, sum; \ + ldd [buf + offset + 0x10], t4; \ + addxcc t2, sum, sum; \ + addxcc t3, sum, sum; \ + ldd [buf + offset + 0x18], t0; \ + addxcc t4, sum, sum; \ + addxcc t5, sum, sum; \ + addxcc t0, sum, sum; \ + addxcc t1, sum, sum; + +#define CSUM_LASTCHUNK(buf, offset, sum, t0, t1, t2, t3) \ + ldd [buf - offset - 0x08], t0; \ + ldd [buf - offset - 0x00], t2; \ + addxcc t0, sum, sum; \ + addxcc t1, sum, sum; \ + addxcc t2, sum, sum; \ + addxcc t3, sum, sum; + + /* Do end cruft out of band to get better cache patterns. */ +csum_partial_end_cruft: + be 1f ! caller asks %o1 & 0x8 + andcc %o1, 4, %g0 ! nope, check for word remaining + ldd [%o0], %g2 ! load two + addcc %g2, %o2, %o2 ! add first word to sum + addxcc %g3, %o2, %o2 ! add second word as well + add %o0, 8, %o0 ! advance buf ptr + addx %g0, %o2, %o2 ! add in final carry + andcc %o1, 4, %g0 ! check again for word remaining +1: be 1f ! nope, skip this code + andcc %o1, 3, %o1 ! check for trailing bytes + ld [%o0], %g2 ! load it + addcc %g2, %o2, %o2 ! add to sum + add %o0, 4, %o0 ! advance buf ptr + addx %g0, %o2, %o2 ! add in final carry + andcc %o1, 3, %g0 ! check again for trailing bytes +1: be 1f ! no trailing bytes, return + addcc %o1, -1, %g0 ! only one byte remains? + bne 2f ! at least two bytes more + subcc %o1, 2, %o1 ! only two bytes more? + b 4f ! only one byte remains + or %g0, %g0, %o4 ! clear fake hword value +2: lduh [%o0], %o4 ! get hword + be 6f ! jmp if only hword remains + add %o0, 2, %o0 ! advance buf ptr either way + sll %o4, 16, %o4 ! create upper hword +4: ldub [%o0], %o5 ! get final byte + sll %o5, 8, %o5 ! put into place + or %o5, %o4, %o4 ! coalese with hword (if any) +6: addcc %o4, %o2, %o2 ! add to sum +1: retl ! get outta here + addx %g0, %o2, %o0 ! add final carry into retval + + /* Also do alignment out of band to get better cache patterns. */ +csum_partial_fix_alignment: + cmp %o1, 6 + bl cpte - 0x4 + andcc %o0, 0x2, %g0 + be 1f + andcc %o0, 0x4, %g0 + lduh [%o0 + 0x00], %g2 + sub %o1, 2, %o1 + add %o0, 2, %o0 + sll %g2, 16, %g2 + addcc %g2, %o2, %o2 + srl %o2, 16, %g3 + addx %g0, %g3, %g2 + sll %o2, 16, %o2 + sll %g2, 16, %g3 + srl %o2, 16, %o2 + andcc %o0, 0x4, %g0 + or %g3, %o2, %o2 +1: be cpa + andcc %o1, 0xffffff80, %o3 + ld [%o0 + 0x00], %g2 + sub %o1, 4, %o1 + addcc %g2, %o2, %o2 + add %o0, 4, %o0 + addx %g0, %o2, %o2 + b cpa + andcc %o1, 0xffffff80, %o3 + + /* The common case is to get called with a nicely aligned + * buffer of size 0x20. Follow the code path for that case. + */ + .globl csum_partial +csum_partial: /* %o0=buf, %o1=len, %o2=sum */ + andcc %o0, 0x7, %g0 ! alignment problems? + bne csum_partial_fix_alignment ! yep, handle it + sethi %hi(cpte - 8), %g7 ! prepare table jmp ptr + andcc %o1, 0xffffff80, %o3 ! num loop iterations +cpa: be 3f ! none to do + andcc %o1, 0x70, %g1 ! clears carry flag too +5: CSUM_BIGCHUNK(%o0, 0x00, %o2, %o4, %o5, %g2, %g3, %g4, %g5) + CSUM_BIGCHUNK(%o0, 0x20, %o2, %o4, %o5, %g2, %g3, %g4, %g5) + CSUM_BIGCHUNK(%o0, 0x40, %o2, %o4, %o5, %g2, %g3, %g4, %g5) + CSUM_BIGCHUNK(%o0, 0x60, %o2, %o4, %o5, %g2, %g3, %g4, %g5) + addx %g0, %o2, %o2 ! sink in final carry + subcc %o3, 128, %o3 ! detract from loop iters + bne 5b ! more to do + add %o0, 128, %o0 ! advance buf ptr + andcc %o1, 0x70, %g1 ! clears carry flag too +3: be cpte ! nope + andcc %o1, 0xf, %g0 ! anything left at all? + srl %g1, 1, %o4 ! compute offset + sub %g7, %g1, %g7 ! adjust jmp ptr + sub %g7, %o4, %g7 ! final jmp ptr adjust + jmp %g7 + %lo(cpte - 8) ! enter the table + add %o0, %g1, %o0 ! advance buf ptr +cptbl: CSUM_LASTCHUNK(%o0, 0x68, %o2, %g2, %g3, %g4, %g5) + CSUM_LASTCHUNK(%o0, 0x58, %o2, %g2, %g3, %g4, %g5) + CSUM_LASTCHUNK(%o0, 0x48, %o2, %g2, %g3, %g4, %g5) + CSUM_LASTCHUNK(%o0, 0x38, %o2, %g2, %g3, %g4, %g5) + CSUM_LASTCHUNK(%o0, 0x28, %o2, %g2, %g3, %g4, %g5) + CSUM_LASTCHUNK(%o0, 0x18, %o2, %g2, %g3, %g4, %g5) + CSUM_LASTCHUNK(%o0, 0x08, %o2, %g2, %g3, %g4, %g5) + addx %g0, %o2, %o2 ! fetch final carry + andcc %o1, 0xf, %g0 ! anything left at all? +cpte: bne csum_partial_end_cruft ! yep, handle it + andcc %o1, 8, %g0 ! check how much +cpout: retl ! get outta here + mov %o2, %o0 ! return computed csum + + .globl __csum_partial_copy_start, __csum_partial_copy_end +__csum_partial_copy_start: + +/* Work around cpp -rob */ +#define ALLOC #alloc +#define EXECINSTR #execinstr +#define EX(x,y,a,b) \ +98: x,y; \ + .section .fixup,ALLOC,EXECINSTR; \ + .align 4; \ +99: ba 30f; \ + a, b, %o3; \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word 98b, 99b; \ + .text; \ + .align 4 + +#define EX2(x,y) \ +98: x,y; \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word 98b, 30f; \ + .text; \ + .align 4 + +#define EX3(x,y) \ +98: x,y; \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word 98b, 96f; \ + .text; \ + .align 4 + +#define EXT(start,end,handler) \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word start, 0, end, handler; \ + .text; \ + .align 4 + + /* This aligned version executes typically in 8.5 superscalar cycles, this + * is the best I can do. I say 8.5 because the final add will pair with + * the next ldd in the main unrolled loop. Thus the pipe is always full. + * If you change these macros (including order of instructions), + * please check the fixup code below as well. + */ +#define CSUMCOPY_BIGCHUNK_ALIGNED(src, dst, sum, off, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [src + off + 0x00], t0; \ + ldd [src + off + 0x08], t2; \ + addxcc t0, sum, sum; \ + ldd [src + off + 0x10], t4; \ + addxcc t1, sum, sum; \ + ldd [src + off + 0x18], t6; \ + addxcc t2, sum, sum; \ + std t0, [dst + off + 0x00]; \ + addxcc t3, sum, sum; \ + std t2, [dst + off + 0x08]; \ + addxcc t4, sum, sum; \ + std t4, [dst + off + 0x10]; \ + addxcc t5, sum, sum; \ + std t6, [dst + off + 0x18]; \ + addxcc t6, sum, sum; \ + addxcc t7, sum, sum; + + /* 12 superscalar cycles seems to be the limit for this case, + * because of this we thus do all the ldd's together to get + * Viking MXCC into streaming mode. Ho hum... + */ +#define CSUMCOPY_BIGCHUNK(src, dst, sum, off, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [src + off + 0x00], t0; \ + ldd [src + off + 0x08], t2; \ + ldd [src + off + 0x10], t4; \ + ldd [src + off + 0x18], t6; \ + st t0, [dst + off + 0x00]; \ + addxcc t0, sum, sum; \ + st t1, [dst + off + 0x04]; \ + addxcc t1, sum, sum; \ + st t2, [dst + off + 0x08]; \ + addxcc t2, sum, sum; \ + st t3, [dst + off + 0x0c]; \ + addxcc t3, sum, sum; \ + st t4, [dst + off + 0x10]; \ + addxcc t4, sum, sum; \ + st t5, [dst + off + 0x14]; \ + addxcc t5, sum, sum; \ + st t6, [dst + off + 0x18]; \ + addxcc t6, sum, sum; \ + st t7, [dst + off + 0x1c]; \ + addxcc t7, sum, sum; + + /* Yuck, 6 superscalar cycles... */ +#define CSUMCOPY_LASTCHUNK(src, dst, sum, off, t0, t1, t2, t3) \ + ldd [src - off - 0x08], t0; \ + ldd [src - off - 0x00], t2; \ + addxcc t0, sum, sum; \ + st t0, [dst - off - 0x08]; \ + addxcc t1, sum, sum; \ + st t1, [dst - off - 0x04]; \ + addxcc t2, sum, sum; \ + st t2, [dst - off - 0x00]; \ + addxcc t3, sum, sum; \ + st t3, [dst - off + 0x04]; + + /* Handle the end cruft code out of band for better cache patterns. */ +cc_end_cruft: + be 1f + andcc %o3, 4, %g0 + EX(ldd [%o0 + 0x00], %g2, and %o3, 0xf) + add %o1, 8, %o1 + addcc %g2, %g7, %g7 + add %o0, 8, %o0 + addxcc %g3, %g7, %g7 + EX2(st %g2, [%o1 - 0x08]) + addx %g0, %g7, %g7 + andcc %o3, 4, %g0 + EX2(st %g3, [%o1 - 0x04]) +1: be 1f + andcc %o3, 3, %o3 + EX(ld [%o0 + 0x00], %g2, add %o3, 4) + add %o1, 4, %o1 + addcc %g2, %g7, %g7 + EX2(st %g2, [%o1 - 0x04]) + addx %g0, %g7, %g7 + andcc %o3, 3, %g0 + add %o0, 4, %o0 +1: be 1f + addcc %o3, -1, %g0 + bne 2f + subcc %o3, 2, %o3 + b 4f + or %g0, %g0, %o4 +2: EX(lduh [%o0 + 0x00], %o4, add %o3, 2) + add %o0, 2, %o0 + EX2(sth %o4, [%o1 + 0x00]) + be 6f + add %o1, 2, %o1 + sll %o4, 16, %o4 +4: EX(ldub [%o0 + 0x00], %o5, add %g0, 1) + EX2(stb %o5, [%o1 + 0x00]) + sll %o5, 8, %o5 + or %o5, %o4, %o4 +6: addcc %o4, %g7, %g7 +1: retl + addx %g0, %g7, %o0 + + /* Also, handle the alignment code out of band. */ +cc_dword_align: + cmp %g1, 6 + bl,a ccte + andcc %g1, 0xf, %o3 + andcc %o0, 0x1, %g0 + bne ccslow + andcc %o0, 0x2, %g0 + be 1f + andcc %o0, 0x4, %g0 + EX(lduh [%o0 + 0x00], %g4, add %g1, 0) + sub %g1, 2, %g1 + EX2(sth %g4, [%o1 + 0x00]) + add %o0, 2, %o0 + sll %g4, 16, %g4 + addcc %g4, %g7, %g7 + add %o1, 2, %o1 + srl %g7, 16, %g3 + addx %g0, %g3, %g4 + sll %g7, 16, %g7 + sll %g4, 16, %g3 + srl %g7, 16, %g7 + andcc %o0, 0x4, %g0 + or %g3, %g7, %g7 +1: be 3f + andcc %g1, 0xffffff80, %g0 + EX(ld [%o0 + 0x00], %g4, add %g1, 0) + sub %g1, 4, %g1 + EX2(st %g4, [%o1 + 0x00]) + add %o0, 4, %o0 + addcc %g4, %g7, %g7 + add %o1, 4, %o1 + addx %g0, %g7, %g7 + b 3f + andcc %g1, 0xffffff80, %g0 + + /* Sun, you just can't beat me, you just can't. Stop trying, + * give up. I'm serious, I am going to kick the living shit + * out of you, game over, lights out. + */ + .align 8 + .globl __csum_partial_copy_sparc_generic +__csum_partial_copy_sparc_generic: + /* %o0=src, %o1=dest, %g1=len, %g7=sum */ + xor %o0, %o1, %o4 ! get changing bits + andcc %o4, 3, %g0 ! check for mismatched alignment + bne ccslow ! better this than unaligned/fixups + andcc %o0, 7, %g0 ! need to align things? + bne cc_dword_align ! yes, we check for short lengths there + andcc %g1, 0xffffff80, %g0 ! can we use unrolled loop? +3: be 3f ! nope, less than one loop remains + andcc %o1, 4, %g0 ! dest aligned on 4 or 8 byte boundary? + be ccdbl + 4 ! 8 byte aligned, kick ass +5: CSUMCOPY_BIGCHUNK(%o0,%o1,%g7,0x00,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) + CSUMCOPY_BIGCHUNK(%o0,%o1,%g7,0x20,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) + CSUMCOPY_BIGCHUNK(%o0,%o1,%g7,0x40,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) + CSUMCOPY_BIGCHUNK(%o0,%o1,%g7,0x60,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) +10: EXT(5b, 10b, 20f) ! note for exception handling + sub %g1, 128, %g1 ! detract from length + addx %g0, %g7, %g7 ! add in last carry bit + andcc %g1, 0xffffff80, %g0 ! more to csum? + add %o0, 128, %o0 ! advance src ptr + bne 5b ! we did not go negative, continue looping + add %o1, 128, %o1 ! advance dest ptr +3: andcc %g1, 0x70, %o2 ! can use table? +ccmerge:be ccte ! nope, go and check for end cruft + andcc %g1, 0xf, %o3 ! get low bits of length (clears carry btw) + srl %o2, 1, %o4 ! begin negative offset computation + sethi %hi(12f), %o5 ! set up table ptr end + add %o0, %o2, %o0 ! advance src ptr + sub %o5, %o4, %o5 ! continue table calculation + sll %o2, 1, %g2 ! constant multiplies are fun... + sub %o5, %g2, %o5 ! some more adjustments + jmp %o5 + %lo(12f) ! jump into it, duff style, wheee... + add %o1, %o2, %o1 ! advance dest ptr (carry is clear btw) +cctbl: CSUMCOPY_LASTCHUNK(%o0,%o1,%g7,0x68,%g2,%g3,%g4,%g5) + CSUMCOPY_LASTCHUNK(%o0,%o1,%g7,0x58,%g2,%g3,%g4,%g5) + CSUMCOPY_LASTCHUNK(%o0,%o1,%g7,0x48,%g2,%g3,%g4,%g5) + CSUMCOPY_LASTCHUNK(%o0,%o1,%g7,0x38,%g2,%g3,%g4,%g5) + CSUMCOPY_LASTCHUNK(%o0,%o1,%g7,0x28,%g2,%g3,%g4,%g5) + CSUMCOPY_LASTCHUNK(%o0,%o1,%g7,0x18,%g2,%g3,%g4,%g5) + CSUMCOPY_LASTCHUNK(%o0,%o1,%g7,0x08,%g2,%g3,%g4,%g5) +12: EXT(cctbl, 12b, 22f) ! note for exception table handling + addx %g0, %g7, %g7 + andcc %o3, 0xf, %g0 ! check for low bits set +ccte: bne cc_end_cruft ! something left, handle it out of band + andcc %o3, 8, %g0 ! begin checks for that code + retl ! return + mov %g7, %o0 ! give em the computed checksum +ccdbl: CSUMCOPY_BIGCHUNK_ALIGNED(%o0,%o1,%g7,0x00,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) + CSUMCOPY_BIGCHUNK_ALIGNED(%o0,%o1,%g7,0x20,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) + CSUMCOPY_BIGCHUNK_ALIGNED(%o0,%o1,%g7,0x40,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) + CSUMCOPY_BIGCHUNK_ALIGNED(%o0,%o1,%g7,0x60,%o4,%o5,%g2,%g3,%g4,%g5,%o2,%o3) +11: EXT(ccdbl, 11b, 21f) ! note for exception table handling + sub %g1, 128, %g1 ! detract from length + addx %g0, %g7, %g7 ! add in last carry bit + andcc %g1, 0xffffff80, %g0 ! more to csum? + add %o0, 128, %o0 ! advance src ptr + bne ccdbl ! we did not go negative, continue looping + add %o1, 128, %o1 ! advance dest ptr + b ccmerge ! finish it off, above + andcc %g1, 0x70, %o2 ! can use table? (clears carry btw) + +ccslow: cmp %g1, 0 + mov 0, %g5 + bleu 4f + andcc %o0, 1, %o5 + be,a 1f + srl %g1, 1, %g4 + sub %g1, 1, %g1 + EX(ldub [%o0], %g5, add %g1, 1) + add %o0, 1, %o0 + EX2(stb %g5, [%o1]) + srl %g1, 1, %g4 + add %o1, 1, %o1 +1: cmp %g4, 0 + be,a 3f + andcc %g1, 1, %g0 + andcc %o0, 2, %g0 + be,a 1f + srl %g4, 1, %g4 + EX(lduh [%o0], %o4, add %g1, 0) + sub %g1, 2, %g1 + srl %o4, 8, %g2 + sub %g4, 1, %g4 + EX2(stb %g2, [%o1]) + add %o4, %g5, %g5 + EX2(stb %o4, [%o1 + 1]) + add %o0, 2, %o0 + srl %g4, 1, %g4 + add %o1, 2, %o1 +1: cmp %g4, 0 + be,a 2f + andcc %g1, 2, %g0 + EX3(ld [%o0], %o4) +5: srl %o4, 24, %g2 + srl %o4, 16, %g3 + EX2(stb %g2, [%o1]) + srl %o4, 8, %g2 + EX2(stb %g3, [%o1 + 1]) + add %o0, 4, %o0 + EX2(stb %g2, [%o1 + 2]) + addcc %o4, %g5, %g5 + EX2(stb %o4, [%o1 + 3]) + addx %g5, %g0, %g5 ! I am now to lazy to optimize this (question it + add %o1, 4, %o1 ! is worthy). Maybe some day - with the sll/srl + subcc %g4, 1, %g4 ! tricks + bne,a 5b + EX3(ld [%o0], %o4) + sll %g5, 16, %g2 + srl %g5, 16, %g5 + srl %g2, 16, %g2 + andcc %g1, 2, %g0 + add %g2, %g5, %g5 +2: be,a 3f + andcc %g1, 1, %g0 + EX(lduh [%o0], %o4, and %g1, 3) + andcc %g1, 1, %g0 + srl %o4, 8, %g2 + add %o0, 2, %o0 + EX2(stb %g2, [%o1]) + add %g5, %o4, %g5 + EX2(stb %o4, [%o1 + 1]) + add %o1, 2, %o1 +3: be,a 1f + sll %g5, 16, %o4 + EX(ldub [%o0], %g2, add %g0, 1) + sll %g2, 8, %o4 + EX2(stb %g2, [%o1]) + add %g5, %o4, %g5 + sll %g5, 16, %o4 +1: addcc %o4, %g5, %g5 + srl %g5, 16, %o4 + addx %g0, %o4, %g5 + orcc %o5, %g0, %g0 + be 4f + srl %g5, 8, %o4 + and %g5, 0xff, %g2 + and %o4, 0xff, %o4 + sll %g2, 8, %g2 + or %g2, %o4, %g5 +4: addcc %g7, %g5, %g7 + retl + addx %g0, %g7, %o0 +__csum_partial_copy_end: + +/* We do these strange calculations for the csum_*_from_user case only, ie. + * we only bother with faults on loads... */ + +/* o2 = ((g2%20)&3)*8 + * o3 = g1 - (g2/20)*32 - o2 */ +20: + cmp %g2, 20 + blu,a 1f + and %g2, 3, %o2 + sub %g1, 32, %g1 + b 20b + sub %g2, 20, %g2 +1: + sll %o2, 3, %o2 + b 31f + sub %g1, %o2, %o3 + +/* o2 = (!(g2 & 15) ? 0 : (((g2 & 15) + 1) & ~1)*8) + * o3 = g1 - (g2/16)*32 - o2 */ +21: + andcc %g2, 15, %o3 + srl %g2, 4, %g2 + be,a 1f + clr %o2 + add %o3, 1, %o3 + and %o3, 14, %o3 + sll %o3, 3, %o2 +1: + sll %g2, 5, %g2 + sub %g1, %g2, %o3 + b 31f + sub %o3, %o2, %o3 + +/* o0 += (g2/10)*16 - 0x70 + * 01 += (g2/10)*16 - 0x70 + * o2 = (g2 % 10) ? 8 : 0 + * o3 += 0x70 - (g2/10)*16 - o2 */ +22: + cmp %g2, 10 + blu,a 1f + sub %o0, 0x70, %o0 + add %o0, 16, %o0 + add %o1, 16, %o1 + sub %o3, 16, %o3 + b 22b + sub %g2, 10, %g2 +1: + sub %o1, 0x70, %o1 + add %o3, 0x70, %o3 + clr %o2 + tst %g2 + bne,a 1f + mov 8, %o2 +1: + b 31f + sub %o3, %o2, %o3 +96: + and %g1, 3, %g1 + sll %g4, 2, %g4 + add %g1, %g4, %o3 +30: +/* %o1 is dst + * %o3 is # bytes to zero out + * %o4 is faulting address + * %o5 is %pc where fault occurred */ + clr %o2 +31: +/* %o0 is src + * %o1 is dst + * %o2 is # of bytes to copy from src to dst + * %o3 is # bytes to zero out + * %o4 is faulting address + * %o5 is %pc where fault occurred */ + save %sp, -104, %sp + mov %i5, %o0 + mov %i7, %o1 + mov %i4, %o2 + call lookup_fault + mov %g7, %i4 + cmp %o0, 2 + bne 1f + add %g0, -EFAULT, %i5 + tst %i2 + be 2f + mov %i0, %o1 + mov %i1, %o0 +5: + call __memcpy + mov %i2, %o2 + tst %o0 + bne,a 2f + add %i3, %i2, %i3 + add %i1, %i2, %i1 +2: + mov %i1, %o0 +6: + call __bzero + mov %i3, %o1 +1: + ld [%sp + 168], %o2 ! struct_ptr of parent + st %i5, [%o2] + ret + restore + + .section __ex_table,#alloc + .align 4 + .word 5b,2 + .word 6b,2 diff --git a/arch/sparc/lib/copy_user.S b/arch/sparc/lib/copy_user.S new file mode 100644 index 000000000000..577505b692ae --- /dev/null +++ b/arch/sparc/lib/copy_user.S @@ -0,0 +1,492 @@ +/* copy_user.S: Sparc optimized copy_from_user and copy_to_user code. + * + * Copyright(C) 1995 Linus Torvalds + * Copyright(C) 1996 David S. Miller + * Copyright(C) 1996 Eddie C. Dost + * Copyright(C) 1996,1998 Jakub Jelinek + * + * derived from: + * e-mail between David and Eddie. + * + * Returns 0 if successful, otherwise count of bytes not copied yet + */ + +#include <asm/ptrace.h> +#include <asm/asmmacro.h> +#include <asm/page.h> + +/* Work around cpp -rob */ +#define ALLOC #alloc +#define EXECINSTR #execinstr +#define EX(x,y,a,b) \ +98: x,y; \ + .section .fixup,ALLOC,EXECINSTR; \ + .align 4; \ +99: ba fixupretl; \ + a, b, %g3; \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word 98b, 99b; \ + .text; \ + .align 4 + +#define EX2(x,y,c,d,e,a,b) \ +98: x,y; \ + .section .fixup,ALLOC,EXECINSTR; \ + .align 4; \ +99: c, d, e; \ + ba fixupretl; \ + a, b, %g3; \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word 98b, 99b; \ + .text; \ + .align 4 + +#define EXO2(x,y) \ +98: x, y; \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word 98b, 97f; \ + .text; \ + .align 4 + +#define EXT(start,end,handler) \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word start, 0, end, handler; \ + .text; \ + .align 4 + +/* Please do not change following macros unless you change logic used + * in .fixup at the end of this file as well + */ + +/* Both these macros have to start with exactly the same insn */ +#define MOVE_BIGCHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [%src + (offset) + 0x00], %t0; \ + ldd [%src + (offset) + 0x08], %t2; \ + ldd [%src + (offset) + 0x10], %t4; \ + ldd [%src + (offset) + 0x18], %t6; \ + st %t0, [%dst + (offset) + 0x00]; \ + st %t1, [%dst + (offset) + 0x04]; \ + st %t2, [%dst + (offset) + 0x08]; \ + st %t3, [%dst + (offset) + 0x0c]; \ + st %t4, [%dst + (offset) + 0x10]; \ + st %t5, [%dst + (offset) + 0x14]; \ + st %t6, [%dst + (offset) + 0x18]; \ + st %t7, [%dst + (offset) + 0x1c]; + +#define MOVE_BIGALIGNCHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [%src + (offset) + 0x00], %t0; \ + ldd [%src + (offset) + 0x08], %t2; \ + ldd [%src + (offset) + 0x10], %t4; \ + ldd [%src + (offset) + 0x18], %t6; \ + std %t0, [%dst + (offset) + 0x00]; \ + std %t2, [%dst + (offset) + 0x08]; \ + std %t4, [%dst + (offset) + 0x10]; \ + std %t6, [%dst + (offset) + 0x18]; + +#define MOVE_LASTCHUNK(src, dst, offset, t0, t1, t2, t3) \ + ldd [%src - (offset) - 0x10], %t0; \ + ldd [%src - (offset) - 0x08], %t2; \ + st %t0, [%dst - (offset) - 0x10]; \ + st %t1, [%dst - (offset) - 0x0c]; \ + st %t2, [%dst - (offset) - 0x08]; \ + st %t3, [%dst - (offset) - 0x04]; + +#define MOVE_HALFCHUNK(src, dst, offset, t0, t1, t2, t3) \ + lduh [%src + (offset) + 0x00], %t0; \ + lduh [%src + (offset) + 0x02], %t1; \ + lduh [%src + (offset) + 0x04], %t2; \ + lduh [%src + (offset) + 0x06], %t3; \ + sth %t0, [%dst + (offset) + 0x00]; \ + sth %t1, [%dst + (offset) + 0x02]; \ + sth %t2, [%dst + (offset) + 0x04]; \ + sth %t3, [%dst + (offset) + 0x06]; + +#define MOVE_SHORTCHUNK(src, dst, offset, t0, t1) \ + ldub [%src - (offset) - 0x02], %t0; \ + ldub [%src - (offset) - 0x01], %t1; \ + stb %t0, [%dst - (offset) - 0x02]; \ + stb %t1, [%dst - (offset) - 0x01]; + + .text + .align 4 + + .globl __copy_user_begin +__copy_user_begin: + + .globl __copy_user +dword_align: + andcc %o1, 1, %g0 + be 4f + andcc %o1, 2, %g0 + + EXO2(ldub [%o1], %g2) + add %o1, 1, %o1 + EXO2(stb %g2, [%o0]) + sub %o2, 1, %o2 + bne 3f + add %o0, 1, %o0 + + EXO2(lduh [%o1], %g2) + add %o1, 2, %o1 + EXO2(sth %g2, [%o0]) + sub %o2, 2, %o2 + b 3f + add %o0, 2, %o0 +4: + EXO2(lduh [%o1], %g2) + add %o1, 2, %o1 + EXO2(sth %g2, [%o0]) + sub %o2, 2, %o2 + b 3f + add %o0, 2, %o0 + +__copy_user: /* %o0=dst %o1=src %o2=len */ + xor %o0, %o1, %o4 +1: + andcc %o4, 3, %o5 +2: + bne cannot_optimize + cmp %o2, 15 + + bleu short_aligned_end + andcc %o1, 3, %g0 + + bne dword_align +3: + andcc %o1, 4, %g0 + + be 2f + mov %o2, %g1 + + EXO2(ld [%o1], %o4) + sub %g1, 4, %g1 + EXO2(st %o4, [%o0]) + add %o1, 4, %o1 + add %o0, 4, %o0 +2: + andcc %g1, 0xffffff80, %g7 + be 3f + andcc %o0, 4, %g0 + + be ldd_std + 4 +5: + MOVE_BIGCHUNK(o1, o0, 0x00, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGCHUNK(o1, o0, 0x20, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGCHUNK(o1, o0, 0x40, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGCHUNK(o1, o0, 0x60, o2, o3, o4, o5, g2, g3, g4, g5) +80: + EXT(5b, 80b, 50f) + subcc %g7, 128, %g7 + add %o1, 128, %o1 + bne 5b + add %o0, 128, %o0 +3: + andcc %g1, 0x70, %g7 + be copy_user_table_end + andcc %g1, 8, %g0 + + sethi %hi(copy_user_table_end), %o5 + srl %g7, 1, %o4 + add %g7, %o4, %o4 + add %o1, %g7, %o1 + sub %o5, %o4, %o5 + jmpl %o5 + %lo(copy_user_table_end), %g0 + add %o0, %g7, %o0 + +copy_user_table: + MOVE_LASTCHUNK(o1, o0, 0x60, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x50, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x40, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x30, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x20, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x10, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x00, g2, g3, g4, g5) +copy_user_table_end: + EXT(copy_user_table, copy_user_table_end, 51f) + be copy_user_last7 + andcc %g1, 4, %g0 + + EX(ldd [%o1], %g2, and %g1, 0xf) + add %o0, 8, %o0 + add %o1, 8, %o1 + EX(st %g2, [%o0 - 0x08], and %g1, 0xf) + EX2(st %g3, [%o0 - 0x04], and %g1, 0xf, %g1, sub %g1, 4) +copy_user_last7: + be 1f + andcc %g1, 2, %g0 + + EX(ld [%o1], %g2, and %g1, 7) + add %o1, 4, %o1 + EX(st %g2, [%o0], and %g1, 7) + add %o0, 4, %o0 +1: + be 1f + andcc %g1, 1, %g0 + + EX(lduh [%o1], %g2, and %g1, 3) + add %o1, 2, %o1 + EX(sth %g2, [%o0], and %g1, 3) + add %o0, 2, %o0 +1: + be 1f + nop + + EX(ldub [%o1], %g2, add %g0, 1) + EX(stb %g2, [%o0], add %g0, 1) +1: + retl + clr %o0 + +ldd_std: + MOVE_BIGALIGNCHUNK(o1, o0, 0x00, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGALIGNCHUNK(o1, o0, 0x20, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGALIGNCHUNK(o1, o0, 0x40, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGALIGNCHUNK(o1, o0, 0x60, o2, o3, o4, o5, g2, g3, g4, g5) +81: + EXT(ldd_std, 81b, 52f) + subcc %g7, 128, %g7 + add %o1, 128, %o1 + bne ldd_std + add %o0, 128, %o0 + + andcc %g1, 0x70, %g7 + be copy_user_table_end + andcc %g1, 8, %g0 + + sethi %hi(copy_user_table_end), %o5 + srl %g7, 1, %o4 + add %g7, %o4, %o4 + add %o1, %g7, %o1 + sub %o5, %o4, %o5 + jmpl %o5 + %lo(copy_user_table_end), %g0 + add %o0, %g7, %o0 + +cannot_optimize: + bleu short_end + cmp %o5, 2 + + bne byte_chunk + and %o2, 0xfffffff0, %o3 + + andcc %o1, 1, %g0 + be 10f + nop + + EXO2(ldub [%o1], %g2) + add %o1, 1, %o1 + EXO2(stb %g2, [%o0]) + sub %o2, 1, %o2 + andcc %o2, 0xfffffff0, %o3 + be short_end + add %o0, 1, %o0 +10: + MOVE_HALFCHUNK(o1, o0, 0x00, g2, g3, g4, g5) + MOVE_HALFCHUNK(o1, o0, 0x08, g2, g3, g4, g5) +82: + EXT(10b, 82b, 53f) + subcc %o3, 0x10, %o3 + add %o1, 0x10, %o1 + bne 10b + add %o0, 0x10, %o0 + b 2f + and %o2, 0xe, %o3 + +byte_chunk: + MOVE_SHORTCHUNK(o1, o0, -0x02, g2, g3) + MOVE_SHORTCHUNK(o1, o0, -0x04, g2, g3) + MOVE_SHORTCHUNK(o1, o0, -0x06, g2, g3) + MOVE_SHORTCHUNK(o1, o0, -0x08, g2, g3) + MOVE_SHORTCHUNK(o1, o0, -0x0a, g2, g3) + MOVE_SHORTCHUNK(o1, o0, -0x0c, g2, g3) + MOVE_SHORTCHUNK(o1, o0, -0x0e, g2, g3) + MOVE_SHORTCHUNK(o1, o0, -0x10, g2, g3) +83: + EXT(byte_chunk, 83b, 54f) + subcc %o3, 0x10, %o3 + add %o1, 0x10, %o1 + bne byte_chunk + add %o0, 0x10, %o0 + +short_end: + and %o2, 0xe, %o3 +2: + sethi %hi(short_table_end), %o5 + sll %o3, 3, %o4 + add %o0, %o3, %o0 + sub %o5, %o4, %o5 + add %o1, %o3, %o1 + jmpl %o5 + %lo(short_table_end), %g0 + andcc %o2, 1, %g0 +84: + MOVE_SHORTCHUNK(o1, o0, 0x0c, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x0a, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x08, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x06, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x04, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x02, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x00, g2, g3) +short_table_end: + EXT(84b, short_table_end, 55f) + be 1f + nop + EX(ldub [%o1], %g2, add %g0, 1) + EX(stb %g2, [%o0], add %g0, 1) +1: + retl + clr %o0 + +short_aligned_end: + bne short_end + andcc %o2, 8, %g0 + + be 1f + andcc %o2, 4, %g0 + + EXO2(ld [%o1 + 0x00], %g2) + EXO2(ld [%o1 + 0x04], %g3) + add %o1, 8, %o1 + EXO2(st %g2, [%o0 + 0x00]) + EX(st %g3, [%o0 + 0x04], sub %o2, 4) + add %o0, 8, %o0 +1: + b copy_user_last7 + mov %o2, %g1 + + .section .fixup,#alloc,#execinstr + .align 4 +97: + mov %o2, %g3 +fixupretl: + sethi %hi(PAGE_OFFSET), %g1 + cmp %o0, %g1 + blu 1f + cmp %o1, %g1 + bgeu 1f + nop + save %sp, -64, %sp + mov %i0, %o0 + call __bzero + mov %g3, %o1 + restore +1: retl + mov %g3, %o0 + +/* exception routine sets %g2 to (broken_insn - first_insn)>>2 */ +50: +/* This magic counts how many bytes are left when crash in MOVE_BIGCHUNK + * happens. This is derived from the amount ldd reads, st stores, etc. + * x = g2 % 12; + * g3 = g1 + g7 - ((g2 / 12) * 32 + (x < 4) ? 0 : (x - 4) * 4); + * o0 += (g2 / 12) * 32; + */ + cmp %g2, 12 + add %o0, %g7, %o0 + bcs 1f + cmp %g2, 24 + bcs 2f + cmp %g2, 36 + bcs 3f + nop + sub %g2, 12, %g2 + sub %g7, 32, %g7 +3: sub %g2, 12, %g2 + sub %g7, 32, %g7 +2: sub %g2, 12, %g2 + sub %g7, 32, %g7 +1: cmp %g2, 4 + bcs,a 60f + clr %g2 + sub %g2, 4, %g2 + sll %g2, 2, %g2 +60: and %g1, 0x7f, %g3 + sub %o0, %g7, %o0 + add %g3, %g7, %g3 + ba fixupretl + sub %g3, %g2, %g3 +51: +/* i = 41 - g2; j = i % 6; + * g3 = (g1 & 15) + (i / 6) * 16 + (j < 4) ? (j + 1) * 4 : 16; + * o0 -= (i / 6) * 16 + 16; + */ + neg %g2 + and %g1, 0xf, %g1 + add %g2, 41, %g2 + add %o0, %g1, %o0 +1: cmp %g2, 6 + bcs,a 2f + cmp %g2, 4 + add %g1, 16, %g1 + b 1b + sub %g2, 6, %g2 +2: bcc,a 2f + mov 16, %g2 + inc %g2 + sll %g2, 2, %g2 +2: add %g1, %g2, %g3 + ba fixupretl + sub %o0, %g3, %o0 +52: +/* g3 = g1 + g7 - (g2 / 8) * 32 + (g2 & 4) ? (g2 & 3) * 8 : 0; + o0 += (g2 / 8) * 32 */ + andn %g2, 7, %g4 + add %o0, %g7, %o0 + andcc %g2, 4, %g0 + and %g2, 3, %g2 + sll %g4, 2, %g4 + sll %g2, 3, %g2 + bne 60b + sub %g7, %g4, %g7 + ba 60b + clr %g2 +53: +/* g3 = o3 + (o2 & 15) - (g2 & 8) - (g2 & 4) ? (g2 & 3) * 2 : 0; + o0 += (g2 & 8) */ + and %g2, 3, %g4 + andcc %g2, 4, %g0 + and %g2, 8, %g2 + sll %g4, 1, %g4 + be 1f + add %o0, %g2, %o0 + add %g2, %g4, %g2 +1: and %o2, 0xf, %g3 + add %g3, %o3, %g3 + ba fixupretl + sub %g3, %g2, %g3 +54: +/* g3 = o3 + (o2 & 15) - (g2 / 4) * 2 - (g2 & 2) ? (g2 & 1) : 0; + o0 += (g2 / 4) * 2 */ + srl %g2, 2, %o4 + and %g2, 1, %o5 + srl %g2, 1, %g2 + add %o4, %o4, %o4 + and %o5, %g2, %o5 + and %o2, 0xf, %o2 + add %o0, %o4, %o0 + sub %o3, %o5, %o3 + sub %o2, %o4, %o2 + ba fixupretl + add %o2, %o3, %g3 +55: +/* i = 27 - g2; + g3 = (o2 & 1) + i / 4 * 2 + !(i & 3); + o0 -= i / 4 * 2 + 1 */ + neg %g2 + and %o2, 1, %o2 + add %g2, 27, %g2 + srl %g2, 2, %o5 + andcc %g2, 3, %g0 + mov 1, %g2 + add %o5, %o5, %o5 + be,a 1f + clr %g2 +1: add %g2, %o5, %g3 + sub %o0, %g3, %o0 + ba fixupretl + add %g3, %o2, %g3 + + .globl __copy_user_end +__copy_user_end: diff --git a/arch/sparc/lib/debuglocks.c b/arch/sparc/lib/debuglocks.c new file mode 100644 index 000000000000..fb182352782c --- /dev/null +++ b/arch/sparc/lib/debuglocks.c @@ -0,0 +1,202 @@ +/* $Id: debuglocks.c,v 1.11 2001/09/20 00:35:31 davem Exp $ + * debuglocks.c: Debugging versions of SMP locking primitives. + * + * Copyright (C) 1997 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998-99 Anton Blanchard (anton@progsoc.uts.edu.au) + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/threads.h> /* For NR_CPUS */ +#include <linux/spinlock.h> +#include <asm/psr.h> +#include <asm/system.h> + +#ifdef CONFIG_SMP + +/* Some notes on how these debugging routines work. When a lock is acquired + * an extra debugging member lock->owner_pc is set to the caller of the lock + * acquisition routine. Right before releasing a lock, the debugging program + * counter is cleared to zero. + * + * Furthermore, since PC's are 4 byte aligned on Sparc, we stuff the CPU + * number of the owner in the lowest two bits. + */ + +#define STORE_CALLER(A) __asm__ __volatile__("mov %%i7, %0" : "=r" (A)); + +static inline void show(char *str, spinlock_t *lock, unsigned long caller) +{ + int cpu = smp_processor_id(); + + printk("%s(%p) CPU#%d stuck at %08lx, owner PC(%08lx):CPU(%lx)\n",str, + lock, cpu, caller, lock->owner_pc & ~3, lock->owner_pc & 3); +} + +static inline void show_read(char *str, rwlock_t *lock, unsigned long caller) +{ + int cpu = smp_processor_id(); + + printk("%s(%p) CPU#%d stuck at %08lx, owner PC(%08lx):CPU(%lx)\n", str, + lock, cpu, caller, lock->owner_pc & ~3, lock->owner_pc & 3); +} + +static inline void show_write(char *str, rwlock_t *lock, unsigned long caller) +{ + int cpu = smp_processor_id(); + int i; + + printk("%s(%p) CPU#%d stuck at %08lx, owner PC(%08lx):CPU(%lx)", str, + lock, cpu, caller, lock->owner_pc & ~3, lock->owner_pc & 3); + + for(i = 0; i < NR_CPUS; i++) + printk(" reader[%d]=%08lx", i, lock->reader_pc[i]); + + printk("\n"); +} + +#undef INIT_STUCK +#define INIT_STUCK 100000000 + +void _do_spin_lock(spinlock_t *lock, char *str) +{ + unsigned long caller; + unsigned long val; + int cpu = smp_processor_id(); + int stuck = INIT_STUCK; + + STORE_CALLER(caller); + +again: + __asm__ __volatile__("ldstub [%1], %0" : "=r" (val) : "r" (&(lock->lock))); + if(val) { + while(lock->lock) { + if (!--stuck) { + show(str, lock, caller); + stuck = INIT_STUCK; + } + barrier(); + } + goto again; + } + lock->owner_pc = (cpu & 3) | (caller & ~3); +} + +int _spin_trylock(spinlock_t *lock) +{ + unsigned long val; + unsigned long caller; + int cpu = smp_processor_id(); + + STORE_CALLER(caller); + + __asm__ __volatile__("ldstub [%1], %0" : "=r" (val) : "r" (&(lock->lock))); + if(!val) { + /* We got it, record our identity for debugging. */ + lock->owner_pc = (cpu & 3) | (caller & ~3); + } + return val == 0; +} + +void _do_spin_unlock(spinlock_t *lock) +{ + lock->owner_pc = 0; + barrier(); + lock->lock = 0; +} + +void _do_read_lock(rwlock_t *rw, char *str) +{ + unsigned long caller; + unsigned long val; + int cpu = smp_processor_id(); + int stuck = INIT_STUCK; + + STORE_CALLER(caller); + +wlock_again: + __asm__ __volatile__("ldstub [%1 + 3], %0" : "=r" (val) : "r" (&(rw->lock))); + if(val) { + while(rw->lock & 0xff) { + if (!--stuck) { + show_read(str, rw, caller); + stuck = INIT_STUCK; + } + barrier(); + } + goto wlock_again; + } + + rw->reader_pc[cpu] = caller; + barrier(); + rw->lock++; +} + +void _do_read_unlock(rwlock_t *rw, char *str) +{ + unsigned long caller; + unsigned long val; + int cpu = smp_processor_id(); + int stuck = INIT_STUCK; + + STORE_CALLER(caller); + +wlock_again: + __asm__ __volatile__("ldstub [%1 + 3], %0" : "=r" (val) : "r" (&(rw->lock))); + if(val) { + while(rw->lock & 0xff) { + if (!--stuck) { + show_read(str, rw, caller); + stuck = INIT_STUCK; + } + barrier(); + } + goto wlock_again; + } + + rw->reader_pc[cpu] = 0; + barrier(); + rw->lock -= 0x1ff; +} + +void _do_write_lock(rwlock_t *rw, char *str) +{ + unsigned long caller; + unsigned long val; + int cpu = smp_processor_id(); + int stuck = INIT_STUCK; + + STORE_CALLER(caller); + +wlock_again: + __asm__ __volatile__("ldstub [%1 + 3], %0" : "=r" (val) : "r" (&(rw->lock))); + if(val) { +wlock_wait: + while(rw->lock) { + if (!--stuck) { + show_write(str, rw, caller); + stuck = INIT_STUCK; + } + barrier(); + } + goto wlock_again; + } + + if (rw->lock & ~0xff) { + *(((unsigned char *)&rw->lock)+3) = 0; + barrier(); + goto wlock_wait; + } + + barrier(); + rw->owner_pc = (cpu & 3) | (caller & ~3); +} + +void _do_write_unlock(rwlock_t *rw) +{ + rw->owner_pc = 0; + barrier(); + rw->lock = 0; +} + +#endif /* SMP */ diff --git a/arch/sparc/lib/divdi3.S b/arch/sparc/lib/divdi3.S new file mode 100644 index 000000000000..681b3683da9e --- /dev/null +++ b/arch/sparc/lib/divdi3.S @@ -0,0 +1,295 @@ +/* Copyright (C) 1989, 1992, 1993, 1994, 1995 Free Software Foundation, Inc. + +This file is part of GNU CC. + +GNU CC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU CC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU CC; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + + .data + .align 8 + .globl __clz_tab +__clz_tab: + .byte 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 + .byte 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6 + .byte 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 + .byte 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 + .byte 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + .byte 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + .byte 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + .byte 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + .size __clz_tab,256 + .global .udiv + + .text + .align 4 + .globl __divdi3 +__divdi3: + save %sp,-104,%sp + cmp %i0,0 + bge .LL40 + mov 0,%l4 + mov -1,%l4 + sub %g0,%i1,%o0 + mov %o0,%o5 + subcc %g0,%o0,%g0 + sub %g0,%i0,%o0 + subx %o0,0,%o4 + mov %o4,%i0 + mov %o5,%i1 +.LL40: + cmp %i2,0 + bge .LL84 + mov %i3,%o4 + xnor %g0,%l4,%l4 + sub %g0,%i3,%o0 + mov %o0,%o3 + subcc %g0,%o0,%g0 + sub %g0,%i2,%o0 + subx %o0,0,%o2 + mov %o2,%i2 + mov %o3,%i3 + mov %i3,%o4 +.LL84: + cmp %i2,0 + bne .LL45 + mov %i1,%i3 + cmp %o4,%i0 + bleu .LL46 + mov %i3,%o1 + mov 32,%g1 + subcc %i0,%o4,%g0 +1: bcs 5f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + sub %i0,%o4,%i0 ! this kills msb of n + addx %i0,%i0,%i0 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %i0,%o4,%g0 + bcs 3f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + b 3f + sub %i0,%o4,%i0 ! this kills msb of n +4: sub %i0,%o4,%i0 +5: addxcc %i0,%i0,%i0 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o1,%o1,%o1 ! shift n1n0 and a 0-bit in lsb + sub %i0,%o4,%i0 +3: xnor %o1,0,%o1 + b .LL50 + mov 0,%o2 +.LL46: + cmp %o4,0 + bne .LL85 + mov %i0,%o2 + mov 1,%o0 + call .udiv,0 + mov 0,%o1 + mov %o0,%o4 + mov %i0,%o2 +.LL85: + mov 0,%g3 + mov 32,%g1 + subcc %g3,%o4,%g0 +1: bcs 5f + addxcc %o2,%o2,%o2 ! shift n1n0 and a q-bit in lsb + sub %g3,%o4,%g3 ! this kills msb of n + addx %g3,%g3,%g3 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %g3,%o4,%g0 + bcs 3f + addxcc %o2,%o2,%o2 ! shift n1n0 and a q-bit in lsb + b 3f + sub %g3,%o4,%g3 ! this kills msb of n +4: sub %g3,%o4,%g3 +5: addxcc %g3,%g3,%g3 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o2,%o2,%o2 ! shift n1n0 and a 0-bit in lsb + sub %g3,%o4,%g3 +3: xnor %o2,0,%o2 + mov %g3,%i0 + mov %i3,%o1 + mov 32,%g1 + subcc %i0,%o4,%g0 +1: bcs 5f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + sub %i0,%o4,%i0 ! this kills msb of n + addx %i0,%i0,%i0 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %i0,%o4,%g0 + bcs 3f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + b 3f + sub %i0,%o4,%i0 ! this kills msb of n +4: sub %i0,%o4,%i0 +5: addxcc %i0,%i0,%i0 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o1,%o1,%o1 ! shift n1n0 and a 0-bit in lsb + sub %i0,%o4,%i0 +3: xnor %o1,0,%o1 + b .LL86 + mov %o1,%l1 +.LL45: + cmp %i2,%i0 + bleu .LL51 + sethi %hi(65535),%o0 + b .LL78 + mov 0,%o1 +.LL51: + or %o0,%lo(65535),%o0 + cmp %i2,%o0 + bgu .LL58 + mov %i2,%o1 + cmp %i2,256 + addx %g0,-1,%o0 + b .LL64 + and %o0,8,%o2 +.LL58: + sethi %hi(16777215),%o0 + or %o0,%lo(16777215),%o0 + cmp %i2,%o0 + bgu .LL64 + mov 24,%o2 + mov 16,%o2 +.LL64: + srl %o1,%o2,%o0 + sethi %hi(__clz_tab),%o1 + or %o1,%lo(__clz_tab),%o1 + ldub [%o0+%o1],%o0 + add %o0,%o2,%o0 + mov 32,%o1 + subcc %o1,%o0,%o3 + bne,a .LL72 + sub %o1,%o3,%o1 + cmp %i0,%i2 + bgu .LL74 + cmp %i3,%o4 + blu .LL78 + mov 0,%o1 +.LL74: + b .LL78 + mov 1,%o1 +.LL72: + sll %i2,%o3,%o2 + srl %o4,%o1,%o0 + or %o2,%o0,%i2 + sll %o4,%o3,%o4 + srl %i0,%o1,%o2 + sll %i0,%o3,%o0 + srl %i3,%o1,%o1 + or %o0,%o1,%i0 + sll %i3,%o3,%i3 + mov %i0,%o1 + mov 32,%g1 + subcc %o2,%i2,%g0 +1: bcs 5f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + sub %o2,%i2,%o2 ! this kills msb of n + addx %o2,%o2,%o2 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %o2,%i2,%g0 + bcs 3f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + b 3f + sub %o2,%i2,%o2 ! this kills msb of n +4: sub %o2,%i2,%o2 +5: addxcc %o2,%o2,%o2 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o1,%o1,%o1 ! shift n1n0 and a 0-bit in lsb + sub %o2,%i2,%o2 +3: xnor %o1,0,%o1 + mov %o2,%i0 + wr %g0,%o1,%y ! SPARC has 0-3 delay insn after a wr + sra %o4,31,%g2 ! Do not move this insn + and %o1,%g2,%g2 ! Do not move this insn + andcc %g0,0,%g1 ! Do not move this insn + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,%o4,%g1 + mulscc %g1,0,%g1 + add %g1,%g2,%o0 + rd %y,%o2 + cmp %o0,%i0 + bgu,a .LL78 + add %o1,-1,%o1 + bne,a .LL50 + mov 0,%o2 + cmp %o2,%i3 + bleu .LL50 + mov 0,%o2 + add %o1,-1,%o1 +.LL78: + mov 0,%o2 +.LL50: + mov %o1,%l1 +.LL86: + mov %o2,%l0 + mov %l0,%i0 + mov %l1,%i1 + cmp %l4,0 + be .LL81 + sub %g0,%i1,%o0 + mov %o0,%l3 + subcc %g0,%o0,%g0 + sub %g0,%i0,%o0 + subx %o0,0,%l2 + mov %l2,%i0 + mov %l3,%i1 +.LL81: + ret + restore diff --git a/arch/sparc/lib/locks.S b/arch/sparc/lib/locks.S new file mode 100644 index 000000000000..95fa48424967 --- /dev/null +++ b/arch/sparc/lib/locks.S @@ -0,0 +1,72 @@ +/* $Id: locks.S,v 1.16 2000/02/26 11:02:47 anton Exp $ + * locks.S: SMP low-level lock primitives on Sparc. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Anton Blanchard (anton@progsoc.uts.edu.au) + * Copyright (C) 1998 Jakub Jelinek (jj@ultra.linux.cz) + */ + +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/smp.h> +#include <asm/spinlock.h> + + .text + .align 4 + + /* Read/writer locks, as usual this is overly clever to make it + * as fast as possible. + */ + + /* caches... */ +___rw_read_enter_spin_on_wlock: + orcc %g2, 0x0, %g0 + be,a ___rw_read_enter + ldstub [%g1 + 3], %g2 + b ___rw_read_enter_spin_on_wlock + ldub [%g1 + 3], %g2 +___rw_read_exit_spin_on_wlock: + orcc %g2, 0x0, %g0 + be,a ___rw_read_exit + ldstub [%g1 + 3], %g2 + b ___rw_read_exit_spin_on_wlock + ldub [%g1 + 3], %g2 +___rw_write_enter_spin_on_wlock: + orcc %g2, 0x0, %g0 + be,a ___rw_write_enter + ldstub [%g1 + 3], %g2 + b ___rw_write_enter_spin_on_wlock + ld [%g1], %g2 + + .globl ___rw_read_enter +___rw_read_enter: + orcc %g2, 0x0, %g0 + bne,a ___rw_read_enter_spin_on_wlock + ldub [%g1 + 3], %g2 + ld [%g1], %g2 + add %g2, 1, %g2 + st %g2, [%g1] + retl + mov %g4, %o7 + + .globl ___rw_read_exit +___rw_read_exit: + orcc %g2, 0x0, %g0 + bne,a ___rw_read_exit_spin_on_wlock + ldub [%g1 + 3], %g2 + ld [%g1], %g2 + sub %g2, 0x1ff, %g2 + st %g2, [%g1] + retl + mov %g4, %o7 + + .globl ___rw_write_enter +___rw_write_enter: + orcc %g2, 0x0, %g0 + bne ___rw_write_enter_spin_on_wlock + ld [%g1], %g2 + andncc %g2, 0xff, %g0 + bne,a ___rw_write_enter_spin_on_wlock + stb %g0, [%g1 + 3] + retl + mov %g4, %o7 diff --git a/arch/sparc/lib/lshrdi3.S b/arch/sparc/lib/lshrdi3.S new file mode 100644 index 000000000000..35abf5b2bd15 --- /dev/null +++ b/arch/sparc/lib/lshrdi3.S @@ -0,0 +1,27 @@ +/* $Id: lshrdi3.S,v 1.1 1999/03/21 06:37:45 davem Exp $ */ + + .globl __lshrdi3 +__lshrdi3: + cmp %o2, 0 + be 3f + mov 0x20, %g2 + + sub %g2, %o2, %g2 + cmp %g2, 0 + bg 1f + srl %o0, %o2, %o4 + + clr %o4 + neg %g2 + b 2f + srl %o0, %g2, %o5 +1: + sll %o0, %g2, %g3 + srl %o1, %o2, %g2 + or %g2, %g3, %o5 +2: + mov %o4, %o0 + mov %o5, %o1 +3: + retl + nop diff --git a/arch/sparc/lib/memcmp.S b/arch/sparc/lib/memcmp.S new file mode 100644 index 000000000000..cb4bdb0cc2af --- /dev/null +++ b/arch/sparc/lib/memcmp.S @@ -0,0 +1,312 @@ + .text + .align 4 + .global __memcmp, memcmp +__memcmp: +memcmp: +#if 1 + cmp %o2, 0 + ble L3 + mov 0, %g3 +L5: + ldub [%o0], %g2 + ldub [%o1], %g3 + sub %g2, %g3, %g2 + mov %g2, %g3 + sll %g2, 24, %g2 + + cmp %g2, 0 + bne L3 + add %o0, 1, %o0 + + add %o2, -1, %o2 + + cmp %o2, 0 + bg L5 + add %o1, 1, %o1 +L3: + sll %g3, 24, %o0 + sra %o0, 24, %o0 + + retl + nop +#else + save %sp, -104, %sp + mov %i2, %o4 + mov %i0, %o0 + + cmp %o4, 15 + ble L72 + mov %i1, %i2 + + andcc %i2, 3, %g0 + be L161 + andcc %o0, 3, %g2 +L75: + ldub [%o0], %g3 + ldub [%i2], %g2 + add %o0,1, %o0 + + subcc %g3, %g2, %i0 + bne L156 + add %i2, 1, %i2 + + andcc %i2, 3, %g0 + bne L75 + add %o4, -1, %o4 + + andcc %o0, 3, %g2 +L161: + bne,a L78 + mov %i2, %i1 + + mov %o0, %i5 + mov %i2, %i3 + srl %o4, 2, %i4 + + cmp %i4, 0 + bge L93 + mov %i4, %g2 + + add %i4, 3, %g2 +L93: + sra %g2, 2, %g2 + sll %g2, 2, %g2 + sub %i4, %g2, %g2 + + cmp %g2, 1 + be,a L88 + add %o0, 4, %i5 + + bg L94 + cmp %g2, 2 + + cmp %g2, 0 + be,a L86 + ld [%o0], %g3 + + b L162 + ld [%i5], %g3 +L94: + be L81 + cmp %g2, 3 + + be,a L83 + add %o0, -4, %i5 + + b L162 + ld [%i5], %g3 +L81: + add %o0, -8, %i5 + ld [%o0], %g3 + add %i2, -8, %i3 + ld [%i2], %g2 + + b L82 + add %i4, 2, %i4 +L83: + ld [%o0], %g4 + add %i2, -4, %i3 + ld [%i2], %g1 + + b L84 + add %i4, 1, %i4 +L86: + b L87 + ld [%i2], %g2 +L88: + add %i2, 4, %i3 + ld [%o0], %g4 + add %i4, -1, %i4 + ld [%i2], %g1 +L95: + ld [%i5], %g3 +L162: + cmp %g4, %g1 + be L87 + ld [%i3], %g2 + + cmp %g4, %g1 +L163: + bleu L114 + mov -1, %i0 + + b L114 + mov 1, %i0 +L87: + ld [%i5 + 4], %g4 + cmp %g3, %g2 + bne L163 + ld [%i3 + 4], %g1 +L84: + ld [%i5 + 8], %g3 + + cmp %g4, %g1 + bne L163 + ld [%i3 + 8], %g2 +L82: + ld [%i5 + 12], %g4 + cmp %g3, %g2 + bne L163 + ld [%i3 + 12], %g1 + + add %i5, 16, %i5 + + addcc %i4, -4, %i4 + bne L95 + add %i3, 16, %i3 + + cmp %g4, %g1 + bne L163 + nop + + b L114 + mov 0, %i0 +L78: + srl %o4, 2, %i0 + and %o0, -4, %i3 + orcc %i0, %g0, %g3 + sll %g2, 3, %o7 + mov 32, %g2 + + bge L129 + sub %g2, %o7, %o1 + + add %i0, 3, %g3 +L129: + sra %g3, 2, %g2 + sll %g2, 2, %g2 + sub %i0, %g2, %g2 + + cmp %g2, 1 + be,a L124 + ld [%i3], %o3 + + bg L130 + cmp %g2, 2 + + cmp %g2, 0 + be,a L122 + ld [%i3], %o2 + + b L164 + sll %o3, %o7, %g3 +L130: + be L117 + cmp %g2, 3 + + be,a L119 + ld [%i3], %g1 + + b L164 + sll %o3, %o7, %g3 +L117: + ld [%i3], %g4 + add %i2, -8, %i1 + ld [%i3 + 4], %o3 + add %i0, 2, %i0 + ld [%i2], %i4 + + b L118 + add %i3, -4, %i3 +L119: + ld [%i3 + 4], %g4 + add %i2, -4, %i1 + ld [%i2], %i5 + + b L120 + add %i0, 1, %i0 +L122: + ld [%i3 + 4], %g1 + ld [%i2], %i4 + + b L123 + add %i3, 4, %i3 +L124: + add %i2, 4, %i1 + ld [%i3 + 4], %o2 + add %i0, -1, %i0 + ld [%i2], %i5 + add %i3, 8, %i3 +L131: + sll %o3, %o7, %g3 +L164: + srl %o2, %o1, %g2 + ld [%i3], %g1 + or %g3, %g2, %g3 + + cmp %g3, %i5 + bne L163 + ld [%i1], %i4 +L123: + sll %o2, %o7, %g3 + srl %g1, %o1, %g2 + ld [%i3 + 4], %g4 + or %g3, %g2, %g3 + + cmp %g3, %i4 + bne L163 + ld [%i1 + 4], %i5 +L120: + sll %g1, %o7, %g3 + srl %g4, %o1, %g2 + ld [%i3 + 8], %o3 + or %g3, %g2, %g3 + + cmp %g3, %i5 + bne L163 + ld [%i1 + 8], %i4 +L118: + sll %g4, %o7, %g3 + srl %o3, %o1, %g2 + ld [%i3 + 12], %o2 + or %g3, %g2, %g3 + + cmp %g3, %i4 + bne L163 + ld [%i1 + 12], %i5 + + add %i3, 16, %i3 + addcc %i0, -4, %i0 + bne L131 + add %i1, 16, %i1 + + sll %o3, %o7, %g3 + srl %o2, %o1, %g2 + or %g3, %g2, %g3 + + cmp %g3, %i5 + be,a L114 + mov 0, %i0 + + b,a L163 +L114: + cmp %i0, 0 + bne L156 + and %o4, -4, %g2 + + add %o0, %g2, %o0 + add %i2, %g2, %i2 + and %o4, 3, %o4 +L72: + cmp %o4, 0 + be L156 + mov 0, %i0 + + ldub [%o0], %g3 +L165: + ldub [%i2], %g2 + add %o0, 1, %o0 + + subcc %g3, %g2, %i0 + bne L156 + add %i2, 1, %i2 + + addcc %o4, -1, %o4 + bne,a L165 + ldub [%o0], %g3 + + mov 0, %i0 +L156: + ret + restore +#endif diff --git a/arch/sparc/lib/memcpy.S b/arch/sparc/lib/memcpy.S new file mode 100644 index 000000000000..ce10bc869af9 --- /dev/null +++ b/arch/sparc/lib/memcpy.S @@ -0,0 +1,1150 @@ +/* memcpy.S: Sparc optimized memcpy and memmove code + * Hand optimized from GNU libc's memcpy and memmove + * Copyright (C) 1991,1996 Free Software Foundation + * Copyright (C) 1995 Linus Torvalds (Linus.Torvalds@helsinki.fi) + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#ifdef __KERNEL__ + +#define FUNC(x) \ + .globl x; \ + .type x,@function; \ + .align 4; \ +x: + +#undef FASTER_REVERSE +#undef FASTER_NONALIGNED +#define FASTER_ALIGNED + +/* In kernel these functions don't return a value. + * One should use macros in asm/string.h for that purpose. + * We return 0, so that bugs are more apparent. + */ +#define SETUP_RETL +#define RETL_INSN clr %o0 + +#else + +/* libc */ + +#include "DEFS.h" + +#define FASTER_REVERSE +#define FASTER_NONALIGNED +#define FASTER_ALIGNED + +#define SETUP_RETL mov %o0, %g6 +#define RETL_INSN mov %g6, %o0 + +#endif + +/* Both these macros have to start with exactly the same insn */ +#define MOVE_BIGCHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [%src + (offset) + 0x00], %t0; \ + ldd [%src + (offset) + 0x08], %t2; \ + ldd [%src + (offset) + 0x10], %t4; \ + ldd [%src + (offset) + 0x18], %t6; \ + st %t0, [%dst + (offset) + 0x00]; \ + st %t1, [%dst + (offset) + 0x04]; \ + st %t2, [%dst + (offset) + 0x08]; \ + st %t3, [%dst + (offset) + 0x0c]; \ + st %t4, [%dst + (offset) + 0x10]; \ + st %t5, [%dst + (offset) + 0x14]; \ + st %t6, [%dst + (offset) + 0x18]; \ + st %t7, [%dst + (offset) + 0x1c]; + +#define MOVE_BIGALIGNCHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [%src + (offset) + 0x00], %t0; \ + ldd [%src + (offset) + 0x08], %t2; \ + ldd [%src + (offset) + 0x10], %t4; \ + ldd [%src + (offset) + 0x18], %t6; \ + std %t0, [%dst + (offset) + 0x00]; \ + std %t2, [%dst + (offset) + 0x08]; \ + std %t4, [%dst + (offset) + 0x10]; \ + std %t6, [%dst + (offset) + 0x18]; + +#define MOVE_LASTCHUNK(src, dst, offset, t0, t1, t2, t3) \ + ldd [%src - (offset) - 0x10], %t0; \ + ldd [%src - (offset) - 0x08], %t2; \ + st %t0, [%dst - (offset) - 0x10]; \ + st %t1, [%dst - (offset) - 0x0c]; \ + st %t2, [%dst - (offset) - 0x08]; \ + st %t3, [%dst - (offset) - 0x04]; + +#define MOVE_LASTALIGNCHUNK(src, dst, offset, t0, t1, t2, t3) \ + ldd [%src - (offset) - 0x10], %t0; \ + ldd [%src - (offset) - 0x08], %t2; \ + std %t0, [%dst - (offset) - 0x10]; \ + std %t2, [%dst - (offset) - 0x08]; + +#define MOVE_SHORTCHUNK(src, dst, offset, t0, t1) \ + ldub [%src - (offset) - 0x02], %t0; \ + ldub [%src - (offset) - 0x01], %t1; \ + stb %t0, [%dst - (offset) - 0x02]; \ + stb %t1, [%dst - (offset) - 0x01]; + +/* Both these macros have to start with exactly the same insn */ +#define RMOVE_BIGCHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [%src - (offset) - 0x20], %t0; \ + ldd [%src - (offset) - 0x18], %t2; \ + ldd [%src - (offset) - 0x10], %t4; \ + ldd [%src - (offset) - 0x08], %t6; \ + st %t0, [%dst - (offset) - 0x20]; \ + st %t1, [%dst - (offset) - 0x1c]; \ + st %t2, [%dst - (offset) - 0x18]; \ + st %t3, [%dst - (offset) - 0x14]; \ + st %t4, [%dst - (offset) - 0x10]; \ + st %t5, [%dst - (offset) - 0x0c]; \ + st %t6, [%dst - (offset) - 0x08]; \ + st %t7, [%dst - (offset) - 0x04]; + +#define RMOVE_BIGALIGNCHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, t7) \ + ldd [%src - (offset) - 0x20], %t0; \ + ldd [%src - (offset) - 0x18], %t2; \ + ldd [%src - (offset) - 0x10], %t4; \ + ldd [%src - (offset) - 0x08], %t6; \ + std %t0, [%dst - (offset) - 0x20]; \ + std %t2, [%dst - (offset) - 0x18]; \ + std %t4, [%dst - (offset) - 0x10]; \ + std %t6, [%dst - (offset) - 0x08]; + +#define RMOVE_LASTCHUNK(src, dst, offset, t0, t1, t2, t3) \ + ldd [%src + (offset) + 0x00], %t0; \ + ldd [%src + (offset) + 0x08], %t2; \ + st %t0, [%dst + (offset) + 0x00]; \ + st %t1, [%dst + (offset) + 0x04]; \ + st %t2, [%dst + (offset) + 0x08]; \ + st %t3, [%dst + (offset) + 0x0c]; + +#define RMOVE_SHORTCHUNK(src, dst, offset, t0, t1) \ + ldub [%src + (offset) + 0x00], %t0; \ + ldub [%src + (offset) + 0x01], %t1; \ + stb %t0, [%dst + (offset) + 0x00]; \ + stb %t1, [%dst + (offset) + 0x01]; + +#define SMOVE_CHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, prev, shil, shir, offset2) \ + ldd [%src + (offset) + 0x00], %t0; \ + ldd [%src + (offset) + 0x08], %t2; \ + srl %t0, shir, %t5; \ + srl %t1, shir, %t6; \ + sll %t0, shil, %t0; \ + or %t5, %prev, %t5; \ + sll %t1, shil, %prev; \ + or %t6, %t0, %t0; \ + srl %t2, shir, %t1; \ + srl %t3, shir, %t6; \ + sll %t2, shil, %t2; \ + or %t1, %prev, %t1; \ + std %t4, [%dst + (offset) + (offset2) - 0x04]; \ + std %t0, [%dst + (offset) + (offset2) + 0x04]; \ + sll %t3, shil, %prev; \ + or %t6, %t2, %t4; + +#define SMOVE_ALIGNCHUNK(src, dst, offset, t0, t1, t2, t3, t4, t5, t6, prev, shil, shir, offset2) \ + ldd [%src + (offset) + 0x00], %t0; \ + ldd [%src + (offset) + 0x08], %t2; \ + srl %t0, shir, %t4; \ + srl %t1, shir, %t5; \ + sll %t0, shil, %t6; \ + or %t4, %prev, %t0; \ + sll %t1, shil, %prev; \ + or %t5, %t6, %t1; \ + srl %t2, shir, %t4; \ + srl %t3, shir, %t5; \ + sll %t2, shil, %t6; \ + or %t4, %prev, %t2; \ + sll %t3, shil, %prev; \ + or %t5, %t6, %t3; \ + std %t0, [%dst + (offset) + (offset2) + 0x00]; \ + std %t2, [%dst + (offset) + (offset2) + 0x08]; + + .text + .align 4 + +#ifdef FASTER_REVERSE + +70: /* rdword_align */ + + andcc %o1, 1, %g0 + be 4f + andcc %o1, 2, %g0 + + ldub [%o1 - 1], %g2 + sub %o1, 1, %o1 + stb %g2, [%o0 - 1] + sub %o2, 1, %o2 + be 3f + sub %o0, 1, %o0 +4: + lduh [%o1 - 2], %g2 + sub %o1, 2, %o1 + sth %g2, [%o0 - 2] + sub %o2, 2, %o2 + b 3f + sub %o0, 2, %o0 + +#endif /* FASTER_REVERSE */ + +0: + retl + nop ! Only bcopy returns here and it retuns void... + +#ifdef __KERNEL__ +FUNC(amemmove) +FUNC(__memmove) +#endif +FUNC(memmove) + cmp %o0, %o1 + SETUP_RETL + bleu 9f + sub %o0, %o1, %o4 + + add %o1, %o2, %o3 + cmp %o3, %o0 + bleu 0f + andcc %o4, 3, %o5 + +#ifndef FASTER_REVERSE + + add %o1, %o2, %o1 + add %o0, %o2, %o0 + sub %o1, 1, %o1 + sub %o0, 1, %o0 + +1: /* reverse_bytes */ + + ldub [%o1], %o4 + subcc %o2, 1, %o2 + stb %o4, [%o0] + sub %o1, 1, %o1 + bne 1b + sub %o0, 1, %o0 + + retl + RETL_INSN + +#else /* FASTER_REVERSE */ + + add %o1, %o2, %o1 + add %o0, %o2, %o0 + bne 77f + cmp %o2, 15 + bleu 91f + andcc %o1, 3, %g0 + bne 70b +3: + andcc %o1, 4, %g0 + + be 2f + mov %o2, %g1 + + ld [%o1 - 4], %o4 + sub %g1, 4, %g1 + st %o4, [%o0 - 4] + sub %o1, 4, %o1 + sub %o0, 4, %o0 +2: + andcc %g1, 0xffffff80, %g7 + be 3f + andcc %o0, 4, %g0 + + be 74f + 4 +5: + RMOVE_BIGCHUNK(o1, o0, 0x00, o2, o3, o4, o5, g2, g3, g4, g5) + RMOVE_BIGCHUNK(o1, o0, 0x20, o2, o3, o4, o5, g2, g3, g4, g5) + RMOVE_BIGCHUNK(o1, o0, 0x40, o2, o3, o4, o5, g2, g3, g4, g5) + RMOVE_BIGCHUNK(o1, o0, 0x60, o2, o3, o4, o5, g2, g3, g4, g5) + subcc %g7, 128, %g7 + sub %o1, 128, %o1 + bne 5b + sub %o0, 128, %o0 +3: + andcc %g1, 0x70, %g7 + be 72f + andcc %g1, 8, %g0 + + sethi %hi(72f), %o5 + srl %g7, 1, %o4 + add %g7, %o4, %o4 + sub %o1, %g7, %o1 + sub %o5, %o4, %o5 + jmpl %o5 + %lo(72f), %g0 + sub %o0, %g7, %o0 + +71: /* rmemcpy_table */ + RMOVE_LASTCHUNK(o1, o0, 0x60, g2, g3, g4, g5) + RMOVE_LASTCHUNK(o1, o0, 0x50, g2, g3, g4, g5) + RMOVE_LASTCHUNK(o1, o0, 0x40, g2, g3, g4, g5) + RMOVE_LASTCHUNK(o1, o0, 0x30, g2, g3, g4, g5) + RMOVE_LASTCHUNK(o1, o0, 0x20, g2, g3, g4, g5) + RMOVE_LASTCHUNK(o1, o0, 0x10, g2, g3, g4, g5) + RMOVE_LASTCHUNK(o1, o0, 0x00, g2, g3, g4, g5) + +72: /* rmemcpy_table_end */ + + be 73f + andcc %g1, 4, %g0 + + ldd [%o1 - 0x08], %g2 + sub %o0, 8, %o0 + sub %o1, 8, %o1 + st %g2, [%o0] + st %g3, [%o0 + 0x04] + +73: /* rmemcpy_last7 */ + + be 1f + andcc %g1, 2, %g0 + + ld [%o1 - 4], %g2 + sub %o1, 4, %o1 + st %g2, [%o0 - 4] + sub %o0, 4, %o0 +1: + be 1f + andcc %g1, 1, %g0 + + lduh [%o1 - 2], %g2 + sub %o1, 2, %o1 + sth %g2, [%o0 - 2] + sub %o0, 2, %o0 +1: + be 1f + nop + + ldub [%o1 - 1], %g2 + stb %g2, [%o0 - 1] +1: + retl + RETL_INSN + +74: /* rldd_std */ + RMOVE_BIGALIGNCHUNK(o1, o0, 0x00, o2, o3, o4, o5, g2, g3, g4, g5) + RMOVE_BIGALIGNCHUNK(o1, o0, 0x20, o2, o3, o4, o5, g2, g3, g4, g5) + RMOVE_BIGALIGNCHUNK(o1, o0, 0x40, o2, o3, o4, o5, g2, g3, g4, g5) + RMOVE_BIGALIGNCHUNK(o1, o0, 0x60, o2, o3, o4, o5, g2, g3, g4, g5) + subcc %g7, 128, %g7 + sub %o1, 128, %o1 + bne 74b + sub %o0, 128, %o0 + + andcc %g1, 0x70, %g7 + be 72b + andcc %g1, 8, %g0 + + sethi %hi(72b), %o5 + srl %g7, 1, %o4 + add %g7, %o4, %o4 + sub %o1, %g7, %o1 + sub %o5, %o4, %o5 + jmpl %o5 + %lo(72b), %g0 + sub %o0, %g7, %o0 + +75: /* rshort_end */ + + and %o2, 0xe, %o3 +2: + sethi %hi(76f), %o5 + sll %o3, 3, %o4 + sub %o0, %o3, %o0 + sub %o5, %o4, %o5 + sub %o1, %o3, %o1 + jmpl %o5 + %lo(76f), %g0 + andcc %o2, 1, %g0 + + RMOVE_SHORTCHUNK(o1, o0, 0x0c, g2, g3) + RMOVE_SHORTCHUNK(o1, o0, 0x0a, g2, g3) + RMOVE_SHORTCHUNK(o1, o0, 0x08, g2, g3) + RMOVE_SHORTCHUNK(o1, o0, 0x06, g2, g3) + RMOVE_SHORTCHUNK(o1, o0, 0x04, g2, g3) + RMOVE_SHORTCHUNK(o1, o0, 0x02, g2, g3) + RMOVE_SHORTCHUNK(o1, o0, 0x00, g2, g3) + +76: /* rshort_table_end */ + + be 1f + nop + ldub [%o1 - 1], %g2 + stb %g2, [%o0 - 1] +1: + retl + RETL_INSN + +91: /* rshort_aligned_end */ + + bne 75b + andcc %o2, 8, %g0 + + be 1f + andcc %o2, 4, %g0 + + ld [%o1 - 0x08], %g2 + ld [%o1 - 0x04], %g3 + sub %o1, 8, %o1 + st %g2, [%o0 - 0x08] + st %g3, [%o0 - 0x04] + sub %o0, 8, %o0 +1: + b 73b + mov %o2, %g1 + +77: /* rnon_aligned */ + cmp %o2, 15 + bleu 75b + andcc %o0, 3, %g0 + be 64f + andcc %o0, 1, %g0 + be 63f + andcc %o0, 2, %g0 + ldub [%o1 - 1], %g5 + sub %o1, 1, %o1 + stb %g5, [%o0 - 1] + sub %o0, 1, %o0 + be 64f + sub %o2, 1, %o2 +63: + ldub [%o1 - 1], %g5 + sub %o1, 2, %o1 + stb %g5, [%o0 - 1] + sub %o0, 2, %o0 + ldub [%o1], %g5 + sub %o2, 2, %o2 + stb %g5, [%o0] +64: + and %o1, 3, %g2 + and %o1, -4, %o1 + and %o2, 0xc, %g3 + add %o1, 4, %o1 + cmp %g3, 4 + sll %g2, 3, %g4 + mov 32, %g2 + be 4f + sub %g2, %g4, %g7 + + blu 3f + cmp %g3, 8 + + be 2f + srl %o2, 2, %g3 + + ld [%o1 - 4], %o3 + add %o0, -8, %o0 + ld [%o1 - 8], %o4 + add %o1, -16, %o1 + b 7f + add %g3, 1, %g3 +2: + ld [%o1 - 4], %o4 + add %o0, -4, %o0 + ld [%o1 - 8], %g1 + add %o1, -12, %o1 + b 8f + add %g3, 2, %g3 +3: + ld [%o1 - 4], %o5 + add %o0, -12, %o0 + ld [%o1 - 8], %o3 + add %o1, -20, %o1 + b 6f + srl %o2, 2, %g3 +4: + ld [%o1 - 4], %g1 + srl %o2, 2, %g3 + ld [%o1 - 8], %o5 + add %o1, -24, %o1 + add %o0, -16, %o0 + add %g3, -1, %g3 + + ld [%o1 + 12], %o3 +5: + sll %o5, %g4, %g2 + srl %g1, %g7, %g5 + or %g2, %g5, %g2 + st %g2, [%o0 + 12] +6: + ld [%o1 + 8], %o4 + sll %o3, %g4, %g2 + srl %o5, %g7, %g5 + or %g2, %g5, %g2 + st %g2, [%o0 + 8] +7: + ld [%o1 + 4], %g1 + sll %o4, %g4, %g2 + srl %o3, %g7, %g5 + or %g2, %g5, %g2 + st %g2, [%o0 + 4] +8: + ld [%o1], %o5 + sll %g1, %g4, %g2 + srl %o4, %g7, %g5 + addcc %g3, -4, %g3 + or %g2, %g5, %g2 + add %o1, -16, %o1 + st %g2, [%o0] + add %o0, -16, %o0 + bne,a 5b + ld [%o1 + 12], %o3 + sll %o5, %g4, %g2 + srl %g1, %g7, %g5 + srl %g4, 3, %g3 + or %g2, %g5, %g2 + add %o1, %g3, %o1 + andcc %o2, 2, %g0 + st %g2, [%o0 + 12] + be 1f + andcc %o2, 1, %g0 + + ldub [%o1 + 15], %g5 + add %o1, -2, %o1 + stb %g5, [%o0 + 11] + add %o0, -2, %o0 + ldub [%o1 + 16], %g5 + stb %g5, [%o0 + 12] +1: + be 1f + nop + ldub [%o1 + 15], %g5 + stb %g5, [%o0 + 11] +1: + retl + RETL_INSN + +#endif /* FASTER_REVERSE */ + +/* NOTE: This code is executed just for the cases, + where %src (=%o1) & 3 is != 0. + We need to align it to 4. So, for (%src & 3) + 1 we need to do ldub,lduh + 2 lduh + 3 just ldub + so even if it looks weird, the branches + are correct here. -jj + */ +78: /* dword_align */ + + andcc %o1, 1, %g0 + be 4f + andcc %o1, 2, %g0 + + ldub [%o1], %g2 + add %o1, 1, %o1 + stb %g2, [%o0] + sub %o2, 1, %o2 + bne 3f + add %o0, 1, %o0 +4: + lduh [%o1], %g2 + add %o1, 2, %o1 + sth %g2, [%o0] + sub %o2, 2, %o2 + b 3f + add %o0, 2, %o0 + +#ifdef __KERNEL__ +FUNC(__memcpy) +#endif +FUNC(memcpy) /* %o0=dst %o1=src %o2=len */ + + sub %o0, %o1, %o4 + SETUP_RETL +9: + andcc %o4, 3, %o5 +0: + bne 86f + cmp %o2, 15 + + bleu 90f + andcc %o1, 3, %g0 + + bne 78b +3: + andcc %o1, 4, %g0 + + be 2f + mov %o2, %g1 + + ld [%o1], %o4 + sub %g1, 4, %g1 + st %o4, [%o0] + add %o1, 4, %o1 + add %o0, 4, %o0 +2: + andcc %g1, 0xffffff80, %g7 + be 3f + andcc %o0, 4, %g0 + + be 82f + 4 +5: + MOVE_BIGCHUNK(o1, o0, 0x00, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGCHUNK(o1, o0, 0x20, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGCHUNK(o1, o0, 0x40, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGCHUNK(o1, o0, 0x60, o2, o3, o4, o5, g2, g3, g4, g5) + subcc %g7, 128, %g7 + add %o1, 128, %o1 + bne 5b + add %o0, 128, %o0 +3: + andcc %g1, 0x70, %g7 + be 80f + andcc %g1, 8, %g0 + + sethi %hi(80f), %o5 + srl %g7, 1, %o4 + add %g7, %o4, %o4 + add %o1, %g7, %o1 + sub %o5, %o4, %o5 + jmpl %o5 + %lo(80f), %g0 + add %o0, %g7, %o0 + +79: /* memcpy_table */ + + MOVE_LASTCHUNK(o1, o0, 0x60, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x50, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x40, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x30, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x20, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x10, g2, g3, g4, g5) + MOVE_LASTCHUNK(o1, o0, 0x00, g2, g3, g4, g5) + +80: /* memcpy_table_end */ + be 81f + andcc %g1, 4, %g0 + + ldd [%o1], %g2 + add %o0, 8, %o0 + st %g2, [%o0 - 0x08] + add %o1, 8, %o1 + st %g3, [%o0 - 0x04] + +81: /* memcpy_last7 */ + + be 1f + andcc %g1, 2, %g0 + + ld [%o1], %g2 + add %o1, 4, %o1 + st %g2, [%o0] + add %o0, 4, %o0 +1: + be 1f + andcc %g1, 1, %g0 + + lduh [%o1], %g2 + add %o1, 2, %o1 + sth %g2, [%o0] + add %o0, 2, %o0 +1: + be 1f + nop + + ldub [%o1], %g2 + stb %g2, [%o0] +1: + retl + RETL_INSN + +82: /* ldd_std */ + MOVE_BIGALIGNCHUNK(o1, o0, 0x00, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGALIGNCHUNK(o1, o0, 0x20, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGALIGNCHUNK(o1, o0, 0x40, o2, o3, o4, o5, g2, g3, g4, g5) + MOVE_BIGALIGNCHUNK(o1, o0, 0x60, o2, o3, o4, o5, g2, g3, g4, g5) + subcc %g7, 128, %g7 + add %o1, 128, %o1 + bne 82b + add %o0, 128, %o0 + +#ifndef FASTER_ALIGNED + + andcc %g1, 0x70, %g7 + be 80b + andcc %g1, 8, %g0 + + sethi %hi(80b), %o5 + srl %g7, 1, %o4 + add %g7, %o4, %o4 + add %o1, %g7, %o1 + sub %o5, %o4, %o5 + jmpl %o5 + %lo(80b), %g0 + add %o0, %g7, %o0 + +#else /* FASTER_ALIGNED */ + + andcc %g1, 0x70, %g7 + be 84f + andcc %g1, 8, %g0 + + sethi %hi(84f), %o5 + add %o1, %g7, %o1 + sub %o5, %g7, %o5 + jmpl %o5 + %lo(84f), %g0 + add %o0, %g7, %o0 + +83: /* amemcpy_table */ + + MOVE_LASTALIGNCHUNK(o1, o0, 0x60, g2, g3, g4, g5) + MOVE_LASTALIGNCHUNK(o1, o0, 0x50, g2, g3, g4, g5) + MOVE_LASTALIGNCHUNK(o1, o0, 0x40, g2, g3, g4, g5) + MOVE_LASTALIGNCHUNK(o1, o0, 0x30, g2, g3, g4, g5) + MOVE_LASTALIGNCHUNK(o1, o0, 0x20, g2, g3, g4, g5) + MOVE_LASTALIGNCHUNK(o1, o0, 0x10, g2, g3, g4, g5) + MOVE_LASTALIGNCHUNK(o1, o0, 0x00, g2, g3, g4, g5) + +84: /* amemcpy_table_end */ + be 85f + andcc %g1, 4, %g0 + + ldd [%o1], %g2 + add %o0, 8, %o0 + std %g2, [%o0 - 0x08] + add %o1, 8, %o1 +85: /* amemcpy_last7 */ + be 1f + andcc %g1, 2, %g0 + + ld [%o1], %g2 + add %o1, 4, %o1 + st %g2, [%o0] + add %o0, 4, %o0 +1: + be 1f + andcc %g1, 1, %g0 + + lduh [%o1], %g2 + add %o1, 2, %o1 + sth %g2, [%o0] + add %o0, 2, %o0 +1: + be 1f + nop + + ldub [%o1], %g2 + stb %g2, [%o0] +1: + retl + RETL_INSN + +#endif /* FASTER_ALIGNED */ + +86: /* non_aligned */ + cmp %o2, 6 + bleu 88f + +#ifdef FASTER_NONALIGNED + + cmp %o2, 256 + bcc 87f + +#endif /* FASTER_NONALIGNED */ + + andcc %o0, 3, %g0 + be 61f + andcc %o0, 1, %g0 + be 60f + andcc %o0, 2, %g0 + + ldub [%o1], %g5 + add %o1, 1, %o1 + stb %g5, [%o0] + sub %o2, 1, %o2 + bne 61f + add %o0, 1, %o0 +60: + ldub [%o1], %g3 + add %o1, 2, %o1 + stb %g3, [%o0] + sub %o2, 2, %o2 + ldub [%o1 - 1], %g3 + add %o0, 2, %o0 + stb %g3, [%o0 - 1] +61: + and %o1, 3, %g2 + and %o2, 0xc, %g3 + and %o1, -4, %o1 + cmp %g3, 4 + sll %g2, 3, %g4 + mov 32, %g2 + be 4f + sub %g2, %g4, %g7 + + blu 3f + cmp %g3, 0x8 + + be 2f + srl %o2, 2, %g3 + + ld [%o1], %o3 + add %o0, -8, %o0 + ld [%o1 + 4], %o4 + b 8f + add %g3, 1, %g3 +2: + ld [%o1], %o4 + add %o0, -12, %o0 + ld [%o1 + 4], %o5 + add %g3, 2, %g3 + b 9f + add %o1, -4, %o1 +3: + ld [%o1], %g1 + add %o0, -4, %o0 + ld [%o1 + 4], %o3 + srl %o2, 2, %g3 + b 7f + add %o1, 4, %o1 +4: + ld [%o1], %o5 + cmp %o2, 7 + ld [%o1 + 4], %g1 + srl %o2, 2, %g3 + bleu 10f + add %o1, 8, %o1 + + ld [%o1], %o3 + add %g3, -1, %g3 +5: + sll %o5, %g4, %g2 + srl %g1, %g7, %g5 + or %g2, %g5, %g2 + st %g2, [%o0] +7: + ld [%o1 + 4], %o4 + sll %g1, %g4, %g2 + srl %o3, %g7, %g5 + or %g2, %g5, %g2 + st %g2, [%o0 + 4] +8: + ld [%o1 + 8], %o5 + sll %o3, %g4, %g2 + srl %o4, %g7, %g5 + or %g2, %g5, %g2 + st %g2, [%o0 + 8] +9: + ld [%o1 + 12], %g1 + sll %o4, %g4, %g2 + srl %o5, %g7, %g5 + addcc %g3, -4, %g3 + or %g2, %g5, %g2 + add %o1, 16, %o1 + st %g2, [%o0 + 12] + add %o0, 16, %o0 + bne,a 5b + ld [%o1], %o3 +10: + sll %o5, %g4, %g2 + srl %g1, %g7, %g5 + srl %g7, 3, %g3 + or %g2, %g5, %g2 + sub %o1, %g3, %o1 + andcc %o2, 2, %g0 + st %g2, [%o0] + be 1f + andcc %o2, 1, %g0 + + ldub [%o1], %g2 + add %o1, 2, %o1 + stb %g2, [%o0 + 4] + add %o0, 2, %o0 + ldub [%o1 - 1], %g2 + stb %g2, [%o0 + 3] +1: + be 1f + nop + ldub [%o1], %g2 + stb %g2, [%o0 + 4] +1: + retl + RETL_INSN + +#ifdef FASTER_NONALIGNED + +87: /* faster_nonaligned */ + + andcc %o1, 3, %g0 + be 3f + andcc %o1, 1, %g0 + + be 4f + andcc %o1, 2, %g0 + + ldub [%o1], %g2 + add %o1, 1, %o1 + stb %g2, [%o0] + sub %o2, 1, %o2 + bne 3f + add %o0, 1, %o0 +4: + lduh [%o1], %g2 + add %o1, 2, %o1 + srl %g2, 8, %g3 + sub %o2, 2, %o2 + stb %g3, [%o0] + add %o0, 2, %o0 + stb %g2, [%o0 - 1] +3: + andcc %o1, 4, %g0 + + bne 2f + cmp %o5, 1 + + ld [%o1], %o4 + srl %o4, 24, %g2 + stb %g2, [%o0] + srl %o4, 16, %g3 + stb %g3, [%o0 + 1] + srl %o4, 8, %g2 + stb %g2, [%o0 + 2] + sub %o2, 4, %o2 + stb %o4, [%o0 + 3] + add %o1, 4, %o1 + add %o0, 4, %o0 +2: + be 33f + cmp %o5, 2 + be 32f + sub %o2, 4, %o2 +31: + ld [%o1], %g2 + add %o1, 4, %o1 + srl %g2, 24, %g3 + and %o0, 7, %g5 + stb %g3, [%o0] + cmp %g5, 7 + sll %g2, 8, %g1 + add %o0, 4, %o0 + be 41f + and %o2, 0xffffffc0, %o3 + ld [%o0 - 7], %o4 +4: + SMOVE_CHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + SMOVE_CHUNK(o1, o0, 0x10, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + SMOVE_CHUNK(o1, o0, 0x20, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + SMOVE_CHUNK(o1, o0, 0x30, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + subcc %o3, 64, %o3 + add %o1, 64, %o1 + bne 4b + add %o0, 64, %o0 + + andcc %o2, 0x30, %o3 + be,a 1f + srl %g1, 16, %g2 +4: + SMOVE_CHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + subcc %o3, 16, %o3 + add %o1, 16, %o1 + bne 4b + add %o0, 16, %o0 + + srl %g1, 16, %g2 +1: + st %o4, [%o0 - 7] + sth %g2, [%o0 - 3] + srl %g1, 8, %g4 + b 88f + stb %g4, [%o0 - 1] +32: + ld [%o1], %g2 + add %o1, 4, %o1 + srl %g2, 16, %g3 + and %o0, 7, %g5 + sth %g3, [%o0] + cmp %g5, 6 + sll %g2, 16, %g1 + add %o0, 4, %o0 + be 42f + and %o2, 0xffffffc0, %o3 + ld [%o0 - 6], %o4 +4: + SMOVE_CHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + SMOVE_CHUNK(o1, o0, 0x10, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + SMOVE_CHUNK(o1, o0, 0x20, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + SMOVE_CHUNK(o1, o0, 0x30, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + subcc %o3, 64, %o3 + add %o1, 64, %o1 + bne 4b + add %o0, 64, %o0 + + andcc %o2, 0x30, %o3 + be,a 1f + srl %g1, 16, %g2 +4: + SMOVE_CHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + subcc %o3, 16, %o3 + add %o1, 16, %o1 + bne 4b + add %o0, 16, %o0 + + srl %g1, 16, %g2 +1: + st %o4, [%o0 - 6] + b 88f + sth %g2, [%o0 - 2] +33: + ld [%o1], %g2 + sub %o2, 4, %o2 + srl %g2, 24, %g3 + and %o0, 7, %g5 + stb %g3, [%o0] + cmp %g5, 5 + srl %g2, 8, %g4 + sll %g2, 24, %g1 + sth %g4, [%o0 + 1] + add %o1, 4, %o1 + be 43f + and %o2, 0xffffffc0, %o3 + + ld [%o0 - 1], %o4 + add %o0, 4, %o0 +4: + SMOVE_CHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, -1) + SMOVE_CHUNK(o1, o0, 0x10, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, -1) + SMOVE_CHUNK(o1, o0, 0x20, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, -1) + SMOVE_CHUNK(o1, o0, 0x30, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, -1) + subcc %o3, 64, %o3 + add %o1, 64, %o1 + bne 4b + add %o0, 64, %o0 + + andcc %o2, 0x30, %o3 + be,a 1f + srl %g1, 24, %g2 +4: + SMOVE_CHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, -1) + subcc %o3, 16, %o3 + add %o1, 16, %o1 + bne 4b + add %o0, 16, %o0 + + srl %g1, 24, %g2 +1: + st %o4, [%o0 - 5] + b 88f + stb %g2, [%o0 - 1] +41: + SMOVE_ALIGNCHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + SMOVE_ALIGNCHUNK(o1, o0, 0x10, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + SMOVE_ALIGNCHUNK(o1, o0, 0x20, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + SMOVE_ALIGNCHUNK(o1, o0, 0x30, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + subcc %o3, 64, %o3 + add %o1, 64, %o1 + bne 41b + add %o0, 64, %o0 + + andcc %o2, 0x30, %o3 + be,a 1f + srl %g1, 16, %g2 +4: + SMOVE_ALIGNCHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 8, 24, -3) + subcc %o3, 16, %o3 + add %o1, 16, %o1 + bne 4b + add %o0, 16, %o0 + + srl %g1, 16, %g2 +1: + sth %g2, [%o0 - 3] + srl %g1, 8, %g4 + b 88f + stb %g4, [%o0 - 1] +43: + SMOVE_ALIGNCHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, 3) + SMOVE_ALIGNCHUNK(o1, o0, 0x10, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, 3) + SMOVE_ALIGNCHUNK(o1, o0, 0x20, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, 3) + SMOVE_ALIGNCHUNK(o1, o0, 0x30, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, 3) + subcc %o3, 64, %o3 + add %o1, 64, %o1 + bne 43b + add %o0, 64, %o0 + + andcc %o2, 0x30, %o3 + be,a 1f + srl %g1, 24, %g2 +4: + SMOVE_ALIGNCHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 24, 8, 3) + subcc %o3, 16, %o3 + add %o1, 16, %o1 + bne 4b + add %o0, 16, %o0 + + srl %g1, 24, %g2 +1: + stb %g2, [%o0 + 3] + b 88f + add %o0, 4, %o0 +42: + SMOVE_ALIGNCHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + SMOVE_ALIGNCHUNK(o1, o0, 0x10, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + SMOVE_ALIGNCHUNK(o1, o0, 0x20, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + SMOVE_ALIGNCHUNK(o1, o0, 0x30, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + subcc %o3, 64, %o3 + add %o1, 64, %o1 + bne 42b + add %o0, 64, %o0 + + andcc %o2, 0x30, %o3 + be,a 1f + srl %g1, 16, %g2 +4: + SMOVE_ALIGNCHUNK(o1, o0, 0x00, g2, g3, g4, g5, o4, o5, g7, g1, 16, 16, -2) + subcc %o3, 16, %o3 + add %o1, 16, %o1 + bne 4b + add %o0, 16, %o0 + + srl %g1, 16, %g2 +1: + sth %g2, [%o0 - 2] + + /* Fall through */ + +#endif /* FASTER_NONALIGNED */ + +88: /* short_end */ + + and %o2, 0xe, %o3 +20: + sethi %hi(89f), %o5 + sll %o3, 3, %o4 + add %o0, %o3, %o0 + sub %o5, %o4, %o5 + add %o1, %o3, %o1 + jmpl %o5 + %lo(89f), %g0 + andcc %o2, 1, %g0 + + MOVE_SHORTCHUNK(o1, o0, 0x0c, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x0a, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x08, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x06, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x04, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x02, g2, g3) + MOVE_SHORTCHUNK(o1, o0, 0x00, g2, g3) + +89: /* short_table_end */ + + be 1f + nop + + ldub [%o1], %g2 + stb %g2, [%o0] +1: + retl + RETL_INSN + +90: /* short_aligned_end */ + bne 88b + andcc %o2, 8, %g0 + + be 1f + andcc %o2, 4, %g0 + + ld [%o1 + 0x00], %g2 + ld [%o1 + 0x04], %g3 + add %o1, 8, %o1 + st %g2, [%o0 + 0x00] + st %g3, [%o0 + 0x04] + add %o0, 8, %o0 +1: + b 81b + mov %o2, %g1 diff --git a/arch/sparc/lib/memscan.S b/arch/sparc/lib/memscan.S new file mode 100644 index 000000000000..28e78ff090ac --- /dev/null +++ b/arch/sparc/lib/memscan.S @@ -0,0 +1,133 @@ +/* $Id: memscan.S,v 1.4 1996/09/08 02:01:20 davem Exp $ + * memscan.S: Optimized memscan for the Sparc. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +/* In essence, this is just a fancy strlen. */ + +#define LO_MAGIC 0x01010101 +#define HI_MAGIC 0x80808080 + + .text + .align 4 + .globl __memscan_zero, __memscan_generic + .globl memscan +__memscan_zero: + /* %o0 = addr, %o1 = size */ + cmp %o1, 0 + bne,a 1f + andcc %o0, 3, %g0 + + retl + nop + +1: + be mzero_scan_word + sethi %hi(HI_MAGIC), %g2 + + ldsb [%o0], %g3 +mzero_still_not_word_aligned: + cmp %g3, 0 + bne 1f + add %o0, 1, %o0 + + retl + sub %o0, 1, %o0 + +1: + subcc %o1, 1, %o1 + bne,a 1f + andcc %o0, 3, %g0 + + retl + nop + +1: + bne,a mzero_still_not_word_aligned + ldsb [%o0], %g3 + + sethi %hi(HI_MAGIC), %g2 +mzero_scan_word: + or %g2, %lo(HI_MAGIC), %o3 + sethi %hi(LO_MAGIC), %g3 + or %g3, %lo(LO_MAGIC), %o2 +mzero_next_word: + ld [%o0], %g2 +mzero_next_word_preloaded: + sub %g2, %o2, %g2 +mzero_next_word_preloaded_next: + andcc %g2, %o3, %g0 + bne mzero_byte_zero + add %o0, 4, %o0 + +mzero_check_out_of_fuel: + subcc %o1, 4, %o1 + bg,a 1f + ld [%o0], %g2 + + retl + nop + +1: + b mzero_next_word_preloaded_next + sub %g2, %o2, %g2 + + /* Check every byte. */ +mzero_byte_zero: + ldsb [%o0 - 4], %g2 + cmp %g2, 0 + bne mzero_byte_one + sub %o0, 4, %g3 + + retl + mov %g3, %o0 + +mzero_byte_one: + ldsb [%o0 - 3], %g2 + cmp %g2, 0 + bne,a mzero_byte_two_and_three + ldsb [%o0 - 2], %g2 + + retl + sub %o0, 3, %o0 + +mzero_byte_two_and_three: + cmp %g2, 0 + bne,a 1f + ldsb [%o0 - 1], %g2 + + retl + sub %o0, 2, %o0 + +1: + cmp %g2, 0 + bne,a mzero_next_word_preloaded + ld [%o0], %g2 + + retl + sub %o0, 1, %o0 + +mzero_found_it: + retl + sub %o0, 2, %o0 + +memscan: +__memscan_generic: + /* %o0 = addr, %o1 = c, %o2 = size */ + cmp %o2, 0 + bne,a 0f + ldub [%o0], %g2 + + b,a 2f +1: + ldub [%o0], %g2 +0: + cmp %g2, %o1 + be 2f + addcc %o2, -1, %o2 + bne 1b + add %o0, 1, %o0 +2: + retl + nop diff --git a/arch/sparc/lib/memset.S b/arch/sparc/lib/memset.S new file mode 100644 index 000000000000..a65eba41097c --- /dev/null +++ b/arch/sparc/lib/memset.S @@ -0,0 +1,203 @@ +/* linux/arch/sparc/lib/memset.S: Sparc optimized memset, bzero and clear_user code + * Copyright (C) 1991,1996 Free Software Foundation + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * + * Returns 0, if ok, and number of bytes not yet set if exception + * occurs and we were called as clear_user. + */ + +#include <asm/ptrace.h> + +/* Work around cpp -rob */ +#define ALLOC #alloc +#define EXECINSTR #execinstr +#define EX(x,y,a,b) \ +98: x,y; \ + .section .fixup,ALLOC,EXECINSTR; \ + .align 4; \ +99: ba 30f; \ + a, b, %o0; \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word 98b, 99b; \ + .text; \ + .align 4 + +#define EXT(start,end,handler) \ + .section __ex_table,ALLOC; \ + .align 4; \ + .word start, 0, end, handler; \ + .text; \ + .align 4 + +/* Please don't change these macros, unless you change the logic + * in the .fixup section below as well. + * Store 64 bytes at (BASE + OFFSET) using value SOURCE. */ +#define ZERO_BIG_BLOCK(base, offset, source) \ + std source, [base + offset + 0x00]; \ + std source, [base + offset + 0x08]; \ + std source, [base + offset + 0x10]; \ + std source, [base + offset + 0x18]; \ + std source, [base + offset + 0x20]; \ + std source, [base + offset + 0x28]; \ + std source, [base + offset + 0x30]; \ + std source, [base + offset + 0x38]; + +#define ZERO_LAST_BLOCKS(base, offset, source) \ + std source, [base - offset - 0x38]; \ + std source, [base - offset - 0x30]; \ + std source, [base - offset - 0x28]; \ + std source, [base - offset - 0x20]; \ + std source, [base - offset - 0x18]; \ + std source, [base - offset - 0x10]; \ + std source, [base - offset - 0x08]; \ + std source, [base - offset - 0x00]; + + .text + .align 4 + + .globl __bzero_begin +__bzero_begin: + + .globl __bzero, __memset, + .globl memset + .globl __memset_start, __memset_end +__memset_start: +__memset: +memset: + and %o1, 0xff, %g3 + sll %g3, 8, %g2 + or %g3, %g2, %g3 + sll %g3, 16, %g2 + or %g3, %g2, %g3 + b 1f + mov %o2, %o1 +3: + cmp %o2, 3 + be 2f + EX(stb %g3, [%o0], sub %o1, 0) + + cmp %o2, 2 + be 2f + EX(stb %g3, [%o0 + 0x01], sub %o1, 1) + + EX(stb %g3, [%o0 + 0x02], sub %o1, 2) +2: + sub %o2, 4, %o2 + add %o1, %o2, %o1 + b 4f + sub %o0, %o2, %o0 + +__bzero: + mov %g0, %g3 +1: + cmp %o1, 7 + bleu 7f + andcc %o0, 3, %o2 + + bne 3b +4: + andcc %o0, 4, %g0 + + be 2f + mov %g3, %g2 + + EX(st %g3, [%o0], sub %o1, 0) + sub %o1, 4, %o1 + add %o0, 4, %o0 +2: + andcc %o1, 0xffffff80, %o3 ! Now everything is 8 aligned and o1 is len to run + be 9f + andcc %o1, 0x78, %o2 +10: + ZERO_BIG_BLOCK(%o0, 0x00, %g2) + subcc %o3, 128, %o3 + ZERO_BIG_BLOCK(%o0, 0x40, %g2) +11: + EXT(10b, 11b, 20f) + bne 10b + add %o0, 128, %o0 + + orcc %o2, %g0, %g0 +9: + be 13f + andcc %o1, 7, %o1 + + srl %o2, 1, %o3 + set 13f, %o4 + sub %o4, %o3, %o4 + jmp %o4 + add %o0, %o2, %o0 + +12: + ZERO_LAST_BLOCKS(%o0, 0x48, %g2) + ZERO_LAST_BLOCKS(%o0, 0x08, %g2) +13: + be 8f + andcc %o1, 4, %g0 + + be 1f + andcc %o1, 2, %g0 + + EX(st %g3, [%o0], and %o1, 7) + add %o0, 4, %o0 +1: + be 1f + andcc %o1, 1, %g0 + + EX(sth %g3, [%o0], and %o1, 3) + add %o0, 2, %o0 +1: + bne,a 8f + EX(stb %g3, [%o0], and %o1, 1) +8: + retl + clr %o0 +7: + be 13b + orcc %o1, 0, %g0 + + be 0f +8: + add %o0, 1, %o0 + subcc %o1, 1, %o1 + bne,a 8b + EX(stb %g3, [%o0 - 1], add %o1, 1) +0: + retl + clr %o0 +__memset_end: + + .section .fixup,#alloc,#execinstr + .align 4 +20: + cmp %g2, 8 + bleu 1f + and %o1, 0x7f, %o1 + sub %g2, 9, %g2 + add %o3, 64, %o3 +1: + sll %g2, 3, %g2 + add %o3, %o1, %o0 + b 30f + sub %o0, %g2, %o0 +21: + mov 8, %o0 + and %o1, 7, %o1 + sub %o0, %g2, %o0 + sll %o0, 3, %o0 + b 30f + add %o0, %o1, %o0 +30: +/* %o4 is faulting address, %o5 is %pc where fault occurred */ + save %sp, -104, %sp + mov %i5, %o0 + mov %i7, %o1 + call lookup_fault + mov %i4, %o2 + ret + restore + + .globl __bzero_end +__bzero_end: diff --git a/arch/sparc/lib/mul.S b/arch/sparc/lib/mul.S new file mode 100644 index 000000000000..83dffbc2f62f --- /dev/null +++ b/arch/sparc/lib/mul.S @@ -0,0 +1,135 @@ +/* $Id: mul.S,v 1.4 1996/09/30 02:22:32 davem Exp $ + * mul.S: This routine was taken from glibc-1.09 and is covered + * by the GNU Library General Public License Version 2. + */ + +/* + * Signed multiply, from Appendix E of the Sparc Version 8 + * Architecture Manual. + */ + +/* + * Returns %o0 * %o1 in %o1%o0 (i.e., %o1 holds the upper 32 bits of + * the 64-bit product). + * + * This code optimizes short (less than 13-bit) multiplies. + */ + + .globl .mul +.mul: + mov %o0, %y ! multiplier -> Y + andncc %o0, 0xfff, %g0 ! test bits 12..31 + be Lmul_shortway ! if zero, can do it the short way + andcc %g0, %g0, %o4 ! zero the partial product and clear N and V + + /* + * Long multiply. 32 steps, followed by a final shift step. + */ + mulscc %o4, %o1, %o4 ! 1 + mulscc %o4, %o1, %o4 ! 2 + mulscc %o4, %o1, %o4 ! 3 + mulscc %o4, %o1, %o4 ! 4 + mulscc %o4, %o1, %o4 ! 5 + mulscc %o4, %o1, %o4 ! 6 + mulscc %o4, %o1, %o4 ! 7 + mulscc %o4, %o1, %o4 ! 8 + mulscc %o4, %o1, %o4 ! 9 + mulscc %o4, %o1, %o4 ! 10 + mulscc %o4, %o1, %o4 ! 11 + mulscc %o4, %o1, %o4 ! 12 + mulscc %o4, %o1, %o4 ! 13 + mulscc %o4, %o1, %o4 ! 14 + mulscc %o4, %o1, %o4 ! 15 + mulscc %o4, %o1, %o4 ! 16 + mulscc %o4, %o1, %o4 ! 17 + mulscc %o4, %o1, %o4 ! 18 + mulscc %o4, %o1, %o4 ! 19 + mulscc %o4, %o1, %o4 ! 20 + mulscc %o4, %o1, %o4 ! 21 + mulscc %o4, %o1, %o4 ! 22 + mulscc %o4, %o1, %o4 ! 23 + mulscc %o4, %o1, %o4 ! 24 + mulscc %o4, %o1, %o4 ! 25 + mulscc %o4, %o1, %o4 ! 26 + mulscc %o4, %o1, %o4 ! 27 + mulscc %o4, %o1, %o4 ! 28 + mulscc %o4, %o1, %o4 ! 29 + mulscc %o4, %o1, %o4 ! 30 + mulscc %o4, %o1, %o4 ! 31 + mulscc %o4, %o1, %o4 ! 32 + mulscc %o4, %g0, %o4 ! final shift + + ! If %o0 was negative, the result is + ! (%o0 * %o1) + (%o1 << 32)) + ! We fix that here. + +#if 0 + tst %o0 + bge 1f + rd %y, %o0 + + ! %o0 was indeed negative; fix upper 32 bits of result by subtracting + ! %o1 (i.e., return %o4 - %o1 in %o1). + retl + sub %o4, %o1, %o1 + +1: + retl + mov %o4, %o1 +#else + /* Faster code adapted from tege@sics.se's code for umul.S. */ + sra %o0, 31, %o2 ! make mask from sign bit + and %o1, %o2, %o2 ! %o2 = 0 or %o1, depending on sign of %o0 + rd %y, %o0 ! get lower half of product + retl + sub %o4, %o2, %o1 ! subtract compensation + ! and put upper half in place +#endif + +Lmul_shortway: + /* + * Short multiply. 12 steps, followed by a final shift step. + * The resulting bits are off by 12 and (32-12) = 20 bit positions, + * but there is no problem with %o0 being negative (unlike above). + */ + mulscc %o4, %o1, %o4 ! 1 + mulscc %o4, %o1, %o4 ! 2 + mulscc %o4, %o1, %o4 ! 3 + mulscc %o4, %o1, %o4 ! 4 + mulscc %o4, %o1, %o4 ! 5 + mulscc %o4, %o1, %o4 ! 6 + mulscc %o4, %o1, %o4 ! 7 + mulscc %o4, %o1, %o4 ! 8 + mulscc %o4, %o1, %o4 ! 9 + mulscc %o4, %o1, %o4 ! 10 + mulscc %o4, %o1, %o4 ! 11 + mulscc %o4, %o1, %o4 ! 12 + mulscc %o4, %g0, %o4 ! final shift + + /* + * %o4 has 20 of the bits that should be in the low part of the + * result; %y has the bottom 12 (as %y's top 12). That is: + * + * %o4 %y + * +----------------+----------------+ + * | -12- | -20- | -12- | -20- | + * +------(---------+------)---------+ + * --hi-- ----low-part---- + * + * The upper 12 bits of %o4 should be sign-extended to form the + * high part of the product (i.e., highpart = %o4 >> 20). + */ + + rd %y, %o5 + sll %o4, 12, %o0 ! shift middle bits left 12 + srl %o5, 20, %o5 ! shift low bits right 20, zero fill at left + or %o5, %o0, %o0 ! construct low part of result + retl + sra %o4, 20, %o1 ! ... and extract high part of result + + .globl .mul_patch +.mul_patch: + smul %o0, %o1, %o0 + retl + rd %y, %o1 + nop diff --git a/arch/sparc/lib/muldi3.S b/arch/sparc/lib/muldi3.S new file mode 100644 index 000000000000..7f17872d0603 --- /dev/null +++ b/arch/sparc/lib/muldi3.S @@ -0,0 +1,76 @@ +/* Copyright (C) 1989, 1992, 1993, 1994, 1995 Free Software Foundation, Inc. + +This file is part of GNU CC. + +GNU CC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU CC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU CC; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + + .text + .align 4 + .globl __muldi3 +__muldi3: + save %sp, -104, %sp + wr %g0, %i1, %y + sra %i3, 0x1f, %g2 + and %i1, %g2, %g2 + andcc %g0, 0, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, %i3, %g1 + mulscc %g1, 0, %g1 + add %g1, %g2, %l2 + rd %y, %o1 + mov %o1, %l3 + mov %i1, %o0 + call .umul + mov %i2, %o1 + mov %o0, %l0 + mov %i0, %o0 + call .umul + mov %i3, %o1 + add %l0, %o0, %l0 + mov %l2, %i0 + add %l2, %l0, %i0 + ret + restore %g0, %l3, %o1 diff --git a/arch/sparc/lib/rem.S b/arch/sparc/lib/rem.S new file mode 100644 index 000000000000..44508148d055 --- /dev/null +++ b/arch/sparc/lib/rem.S @@ -0,0 +1,382 @@ +/* $Id: rem.S,v 1.7 1996/09/30 02:22:34 davem Exp $ + * rem.S: This routine was taken from glibc-1.09 and is covered + * by the GNU Library General Public License Version 2. + */ + + +/* This file is generated from divrem.m4; DO NOT EDIT! */ +/* + * Division and remainder, from Appendix E of the Sparc Version 8 + * Architecture Manual, with fixes from Gordon Irlam. + */ + +/* + * Input: dividend and divisor in %o0 and %o1 respectively. + * + * m4 parameters: + * .rem name of function to generate + * rem rem=div => %o0 / %o1; rem=rem => %o0 % %o1 + * true true=true => signed; true=false => unsigned + * + * Algorithm parameters: + * N how many bits per iteration we try to get (4) + * WORDSIZE total number of bits (32) + * + * Derived constants: + * TOPBITS number of bits in the top decade of a number + * + * Important variables: + * Q the partial quotient under development (initially 0) + * R the remainder so far, initially the dividend + * ITER number of main division loop iterations required; + * equal to ceil(log2(quotient) / N). Note that this + * is the log base (2^N) of the quotient. + * V the current comparand, initially divisor*2^(ITER*N-1) + * + * Cost: + * Current estimate for non-large dividend is + * ceil(log2(quotient) / N) * (10 + 7N/2) + C + * A large dividend is one greater than 2^(31-TOPBITS) and takes a + * different path, as the upper bits of the quotient must be developed + * one bit at a time. + */ + + + .globl .rem +.rem: + ! compute sign of result; if neither is negative, no problem + orcc %o1, %o0, %g0 ! either negative? + bge 2f ! no, go do the divide + mov %o0, %g2 ! compute sign in any case + + tst %o1 + bge 1f + tst %o0 + ! %o1 is definitely negative; %o0 might also be negative + bge 2f ! if %o0 not negative... + sub %g0, %o1, %o1 ! in any case, make %o1 nonneg +1: ! %o0 is negative, %o1 is nonnegative + sub %g0, %o0, %o0 ! make %o0 nonnegative +2: + + ! Ready to divide. Compute size of quotient; scale comparand. + orcc %o1, %g0, %o5 + bne 1f + mov %o0, %o3 + + ! Divide by zero trap. If it returns, return 0 (about as + ! wrong as possible, but that is what SunOS does...). + ta ST_DIV0 + retl + clr %o0 + +1: + cmp %o3, %o5 ! if %o1 exceeds %o0, done + blu Lgot_result ! (and algorithm fails otherwise) + clr %o2 + + sethi %hi(1 << (32 - 4 - 1)), %g1 + + cmp %o3, %g1 + blu Lnot_really_big + clr %o4 + + ! Here the dividend is >= 2**(31-N) or so. We must be careful here, + ! as our usual N-at-a-shot divide step will cause overflow and havoc. + ! The number of bits in the result here is N*ITER+SC, where SC <= N. + ! Compute ITER in an unorthodox manner: know we need to shift V into + ! the top decade: so do not even bother to compare to R. + 1: + cmp %o5, %g1 + bgeu 3f + mov 1, %g7 + + sll %o5, 4, %o5 + + b 1b + add %o4, 1, %o4 + + ! Now compute %g7. + 2: + addcc %o5, %o5, %o5 + + bcc Lnot_too_big + add %g7, 1, %g7 + + ! We get here if the %o1 overflowed while shifting. + ! This means that %o3 has the high-order bit set. + ! Restore %o5 and subtract from %o3. + sll %g1, 4, %g1 ! high order bit + srl %o5, 1, %o5 ! rest of %o5 + add %o5, %g1, %o5 + + b Ldo_single_div + sub %g7, 1, %g7 + + Lnot_too_big: + 3: + cmp %o5, %o3 + blu 2b + nop + + be Ldo_single_div + nop + /* NB: these are commented out in the V8-Sparc manual as well */ + /* (I do not understand this) */ + ! %o5 > %o3: went too far: back up 1 step + ! srl %o5, 1, %o5 + ! dec %g7 + ! do single-bit divide steps + ! + ! We have to be careful here. We know that %o3 >= %o5, so we can do the + ! first divide step without thinking. BUT, the others are conditional, + ! and are only done if %o3 >= 0. Because both %o3 and %o5 may have the high- + ! order bit set in the first step, just falling into the regular + ! division loop will mess up the first time around. + ! So we unroll slightly... + Ldo_single_div: + subcc %g7, 1, %g7 + bl Lend_regular_divide + nop + + sub %o3, %o5, %o3 + mov 1, %o2 + + b Lend_single_divloop + nop + Lsingle_divloop: + sll %o2, 1, %o2 + + bl 1f + srl %o5, 1, %o5 + ! %o3 >= 0 + sub %o3, %o5, %o3 + + b 2f + add %o2, 1, %o2 + 1: ! %o3 < 0 + add %o3, %o5, %o3 + sub %o2, 1, %o2 + 2: + Lend_single_divloop: + subcc %g7, 1, %g7 + bge Lsingle_divloop + tst %o3 + + b,a Lend_regular_divide + +Lnot_really_big: +1: + sll %o5, 4, %o5 + cmp %o5, %o3 + bleu 1b + addcc %o4, 1, %o4 + be Lgot_result + sub %o4, 1, %o4 + + tst %o3 ! set up for initial iteration +Ldivloop: + sll %o2, 4, %o2 + ! depth 1, accumulated bits 0 + bl L.1.16 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 2, accumulated bits 1 + bl L.2.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits 3 + bl L.3.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 7 + bl L.4.23 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + + b 9f + add %o2, (7*2+1), %o2 + +L.4.23: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (7*2-1), %o2 + +L.3.19: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 5 + bl L.4.21 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (5*2+1), %o2 + +L.4.21: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (5*2-1), %o2 + +L.2.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits 1 + bl L.3.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 3 + bl L.4.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (3*2+1), %o2 + +L.4.19: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (3*2-1), %o2 + +L.3.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 1 + bl L.4.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (1*2+1), %o2 + +L.4.17: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (1*2-1), %o2 + +L.1.16: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 2, accumulated bits -1 + bl L.2.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits -1 + bl L.3.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -1 + bl L.4.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2+1), %o2 + +L.4.15: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2-1), %o2 + +L.3.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -3 + bl L.4.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2+1), %o2 + +L.4.13: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2-1), %o2 + +L.2.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits -3 + bl L.3.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -5 + bl L.4.11 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2+1), %o2 + +L.4.11: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2-1), %o2 + + +L.3.13: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -7 + bl L.4.9 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2+1), %o2 + +L.4.9: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2-1), %o2 + + 9: +Lend_regular_divide: + subcc %o4, 1, %o4 + bge Ldivloop + tst %o3 + + bl,a Lgot_result + ! non-restoring fixup here (one instruction only!) + add %o3, %o1, %o3 + +Lgot_result: + ! check to see if answer should be < 0 + tst %g2 + bl,a 1f + sub %g0, %o3, %o3 +1: + retl + mov %o3, %o0 + + .globl .rem_patch +.rem_patch: + sra %o0, 0x1f, %o4 + wr %o4, 0x0, %y + nop + nop + nop + sdivcc %o0, %o1, %o2 + bvs,a 1f + xnor %o2, %g0, %o2 +1: smul %o2, %o1, %o2 + retl + sub %o0, %o2, %o0 + nop diff --git a/arch/sparc/lib/rwsem.S b/arch/sparc/lib/rwsem.S new file mode 100644 index 000000000000..e7578dc600b8 --- /dev/null +++ b/arch/sparc/lib/rwsem.S @@ -0,0 +1,205 @@ +/* $Id: rwsem.S,v 1.5 2000/05/09 17:40:13 davem Exp $ + * Assembly part of rw semaphores. + * + * Copyright (C) 1999 Jakub Jelinek (jakub@redhat.com) + */ + +#include <linux/config.h> +#include <asm/ptrace.h> +#include <asm/psr.h> + + .section .sched.text + .align 4 + + .globl ___down_read +___down_read: + rd %psr, %g3 + nop + nop + nop + or %g3, PSR_PIL, %g7 + wr %g7, 0, %psr + nop + nop + nop +#ifdef CONFIG_SMP +1: ldstub [%g1 + 4], %g7 + tst %g7 + bne 1b + ld [%g1], %g7 + sub %g7, 1, %g7 + st %g7, [%g1] + stb %g0, [%g1 + 4] +#else + ld [%g1], %g7 + sub %g7, 1, %g7 + st %g7, [%g1] +#endif + wr %g3, 0, %psr + add %g7, 1, %g7 + nop + nop + subcc %g7, 1, %g7 + bneg 3f + nop +2: jmpl %o7, %g0 + mov %g4, %o7 +3: save %sp, -64, %sp + mov %g1, %l1 + mov %g4, %l4 + bcs 4f + mov %g5, %l5 + call down_read_failed + mov %l1, %o0 + mov %l1, %g1 + mov %l4, %g4 + ba ___down_read + restore %l5, %g0, %g5 +4: call down_read_failed_biased + mov %l1, %o0 + mov %l1, %g1 + mov %l4, %g4 + ba 2b + restore %l5, %g0, %g5 + + .globl ___down_write +___down_write: + rd %psr, %g3 + nop + nop + nop + or %g3, PSR_PIL, %g7 + wr %g7, 0, %psr + sethi %hi(0x01000000), %g2 + nop + nop +#ifdef CONFIG_SMP +1: ldstub [%g1 + 4], %g7 + tst %g7 + bne 1b + ld [%g1], %g7 + sub %g7, %g2, %g7 + st %g7, [%g1] + stb %g0, [%g1 + 4] +#else + ld [%g1], %g7 + sub %g7, %g2, %g7 + st %g7, [%g1] +#endif + wr %g3, 0, %psr + add %g7, %g2, %g7 + nop + nop + subcc %g7, %g2, %g7 + bne 3f + nop +2: jmpl %o7, %g0 + mov %g4, %o7 +3: save %sp, -64, %sp + mov %g1, %l1 + mov %g4, %l4 + bcs 4f + mov %g5, %l5 + call down_write_failed + mov %l1, %o0 + mov %l1, %g1 + mov %l4, %g4 + ba ___down_write + restore %l5, %g0, %g5 +4: call down_write_failed_biased + mov %l1, %o0 + mov %l1, %g1 + mov %l4, %g4 + ba 2b + restore %l5, %g0, %g5 + + .text + .globl ___up_read +___up_read: + rd %psr, %g3 + nop + nop + nop + or %g3, PSR_PIL, %g7 + wr %g7, 0, %psr + nop + nop + nop +#ifdef CONFIG_SMP +1: ldstub [%g1 + 4], %g7 + tst %g7 + bne 1b + ld [%g1], %g7 + add %g7, 1, %g7 + st %g7, [%g1] + stb %g0, [%g1 + 4] +#else + ld [%g1], %g7 + add %g7, 1, %g7 + st %g7, [%g1] +#endif + wr %g3, 0, %psr + nop + nop + nop + cmp %g7, 0 + be 3f + nop +2: jmpl %o7, %g0 + mov %g4, %o7 +3: save %sp, -64, %sp + mov %g1, %l1 + mov %g4, %l4 + mov %g5, %l5 + clr %o1 + call __rwsem_wake + mov %l1, %o0 + mov %l1, %g1 + mov %l4, %g4 + ba 2b + restore %l5, %g0, %g5 + + .globl ___up_write +___up_write: + rd %psr, %g3 + nop + nop + nop + or %g3, PSR_PIL, %g7 + wr %g7, 0, %psr + sethi %hi(0x01000000), %g2 + nop + nop +#ifdef CONFIG_SMP +1: ldstub [%g1 + 4], %g7 + tst %g7 + bne 1b + ld [%g1], %g7 + add %g7, %g2, %g7 + st %g7, [%g1] + stb %g0, [%g1 + 4] +#else + ld [%g1], %g7 + add %g7, %g2, %g7 + st %g7, [%g1] +#endif + wr %g3, 0, %psr + sub %g7, %g2, %g7 + nop + nop + addcc %g7, %g2, %g7 + bcs 3f + nop +2: jmpl %o7, %g0 + mov %g4, %o7 +3: save %sp, -64, %sp + mov %g1, %l1 + mov %g4, %l4 + mov %g5, %l5 + mov %g7, %o1 + call __rwsem_wake + mov %l1, %o0 + mov %l1, %g1 + mov %l4, %g4 + ba 2b + restore %l5, %g0, %g5 diff --git a/arch/sparc/lib/sdiv.S b/arch/sparc/lib/sdiv.S new file mode 100644 index 000000000000..e0ad80b6f63d --- /dev/null +++ b/arch/sparc/lib/sdiv.S @@ -0,0 +1,379 @@ +/* $Id: sdiv.S,v 1.6 1996/10/02 17:37:00 davem Exp $ + * sdiv.S: This routine was taken from glibc-1.09 and is covered + * by the GNU Library General Public License Version 2. + */ + + +/* This file is generated from divrem.m4; DO NOT EDIT! */ +/* + * Division and remainder, from Appendix E of the Sparc Version 8 + * Architecture Manual, with fixes from Gordon Irlam. + */ + +/* + * Input: dividend and divisor in %o0 and %o1 respectively. + * + * m4 parameters: + * .div name of function to generate + * div div=div => %o0 / %o1; div=rem => %o0 % %o1 + * true true=true => signed; true=false => unsigned + * + * Algorithm parameters: + * N how many bits per iteration we try to get (4) + * WORDSIZE total number of bits (32) + * + * Derived constants: + * TOPBITS number of bits in the top decade of a number + * + * Important variables: + * Q the partial quotient under development (initially 0) + * R the remainder so far, initially the dividend + * ITER number of main division loop iterations required; + * equal to ceil(log2(quotient) / N). Note that this + * is the log base (2^N) of the quotient. + * V the current comparand, initially divisor*2^(ITER*N-1) + * + * Cost: + * Current estimate for non-large dividend is + * ceil(log2(quotient) / N) * (10 + 7N/2) + C + * A large dividend is one greater than 2^(31-TOPBITS) and takes a + * different path, as the upper bits of the quotient must be developed + * one bit at a time. + */ + + + .globl .div +.div: + ! compute sign of result; if neither is negative, no problem + orcc %o1, %o0, %g0 ! either negative? + bge 2f ! no, go do the divide + xor %o1, %o0, %g2 ! compute sign in any case + + tst %o1 + bge 1f + tst %o0 + ! %o1 is definitely negative; %o0 might also be negative + bge 2f ! if %o0 not negative... + sub %g0, %o1, %o1 ! in any case, make %o1 nonneg +1: ! %o0 is negative, %o1 is nonnegative + sub %g0, %o0, %o0 ! make %o0 nonnegative +2: + + ! Ready to divide. Compute size of quotient; scale comparand. + orcc %o1, %g0, %o5 + bne 1f + mov %o0, %o3 + + ! Divide by zero trap. If it returns, return 0 (about as + ! wrong as possible, but that is what SunOS does...). + ta ST_DIV0 + retl + clr %o0 + +1: + cmp %o3, %o5 ! if %o1 exceeds %o0, done + blu Lgot_result ! (and algorithm fails otherwise) + clr %o2 + + sethi %hi(1 << (32 - 4 - 1)), %g1 + + cmp %o3, %g1 + blu Lnot_really_big + clr %o4 + + ! Here the dividend is >= 2**(31-N) or so. We must be careful here, + ! as our usual N-at-a-shot divide step will cause overflow and havoc. + ! The number of bits in the result here is N*ITER+SC, where SC <= N. + ! Compute ITER in an unorthodox manner: know we need to shift V into + ! the top decade: so do not even bother to compare to R. + 1: + cmp %o5, %g1 + bgeu 3f + mov 1, %g7 + + sll %o5, 4, %o5 + + b 1b + add %o4, 1, %o4 + + ! Now compute %g7. + 2: + addcc %o5, %o5, %o5 + bcc Lnot_too_big + add %g7, 1, %g7 + + ! We get here if the %o1 overflowed while shifting. + ! This means that %o3 has the high-order bit set. + ! Restore %o5 and subtract from %o3. + sll %g1, 4, %g1 ! high order bit + srl %o5, 1, %o5 ! rest of %o5 + add %o5, %g1, %o5 + + b Ldo_single_div + sub %g7, 1, %g7 + + Lnot_too_big: + 3: + cmp %o5, %o3 + blu 2b + nop + + be Ldo_single_div + nop + /* NB: these are commented out in the V8-Sparc manual as well */ + /* (I do not understand this) */ + ! %o5 > %o3: went too far: back up 1 step + ! srl %o5, 1, %o5 + ! dec %g7 + ! do single-bit divide steps + ! + ! We have to be careful here. We know that %o3 >= %o5, so we can do the + ! first divide step without thinking. BUT, the others are conditional, + ! and are only done if %o3 >= 0. Because both %o3 and %o5 may have the high- + ! order bit set in the first step, just falling into the regular + ! division loop will mess up the first time around. + ! So we unroll slightly... + Ldo_single_div: + subcc %g7, 1, %g7 + bl Lend_regular_divide + nop + + sub %o3, %o5, %o3 + mov 1, %o2 + + b Lend_single_divloop + nop + Lsingle_divloop: + sll %o2, 1, %o2 + + bl 1f + srl %o5, 1, %o5 + ! %o3 >= 0 + sub %o3, %o5, %o3 + + b 2f + add %o2, 1, %o2 + 1: ! %o3 < 0 + add %o3, %o5, %o3 + sub %o2, 1, %o2 + 2: + Lend_single_divloop: + subcc %g7, 1, %g7 + bge Lsingle_divloop + tst %o3 + + b,a Lend_regular_divide + +Lnot_really_big: +1: + sll %o5, 4, %o5 + cmp %o5, %o3 + bleu 1b + addcc %o4, 1, %o4 + + be Lgot_result + sub %o4, 1, %o4 + + tst %o3 ! set up for initial iteration +Ldivloop: + sll %o2, 4, %o2 + ! depth 1, accumulated bits 0 + bl L.1.16 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 2, accumulated bits 1 + bl L.2.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits 3 + bl L.3.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 7 + bl L.4.23 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (7*2+1), %o2 + +L.4.23: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (7*2-1), %o2 + +L.3.19: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 5 + bl L.4.21 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (5*2+1), %o2 + +L.4.21: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (5*2-1), %o2 + +L.2.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits 1 + bl L.3.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 3 + bl L.4.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (3*2+1), %o2 + +L.4.19: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (3*2-1), %o2 + + +L.3.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 1 + bl L.4.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (1*2+1), %o2 + +L.4.17: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (1*2-1), %o2 + +L.1.16: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 2, accumulated bits -1 + bl L.2.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits -1 + bl L.3.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -1 + bl L.4.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2+1), %o2 + +L.4.15: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2-1), %o2 + +L.3.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -3 + bl L.4.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2+1), %o2 + +L.4.13: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2-1), %o2 + +L.2.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits -3 + bl L.3.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -5 + bl L.4.11 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2+1), %o2 + +L.4.11: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2-1), %o2 + +L.3.13: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -7 + bl L.4.9 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2+1), %o2 + +L.4.9: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2-1), %o2 + + 9: +Lend_regular_divide: + subcc %o4, 1, %o4 + bge Ldivloop + tst %o3 + + bl,a Lgot_result + ! non-restoring fixup here (one instruction only!) + sub %o2, 1, %o2 + +Lgot_result: + ! check to see if answer should be < 0 + tst %g2 + bl,a 1f + sub %g0, %o2, %o2 +1: + retl + mov %o2, %o0 + + .globl .div_patch +.div_patch: + sra %o0, 0x1f, %o2 + wr %o2, 0x0, %y + nop + nop + nop + sdivcc %o0, %o1, %o0 + bvs,a 1f + xnor %o0, %g0, %o0 +1: retl + nop diff --git a/arch/sparc/lib/strlen.S b/arch/sparc/lib/strlen.S new file mode 100644 index 000000000000..ed9a763368cd --- /dev/null +++ b/arch/sparc/lib/strlen.S @@ -0,0 +1,81 @@ +/* strlen.S: Sparc optimized strlen code + * Hand optimized from GNU libc's strlen + * Copyright (C) 1991,1996 Free Software Foundation + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#define LO_MAGIC 0x01010101 +#define HI_MAGIC 0x80808080 + +0: + ldub [%o0], %o5 + cmp %o5, 0 + be 1f + add %o0, 1, %o0 + andcc %o0, 3, %g0 + be 4f + or %o4, %lo(HI_MAGIC), %o3 + ldub [%o0], %o5 + cmp %o5, 0 + be 2f + add %o0, 1, %o0 + andcc %o0, 3, %g0 + be 5f + sethi %hi(LO_MAGIC), %o4 + ldub [%o0], %o5 + cmp %o5, 0 + be 3f + add %o0, 1, %o0 + b 8f + or %o4, %lo(LO_MAGIC), %o2 +1: + retl + mov 0, %o0 +2: + retl + mov 1, %o0 +3: + retl + mov 2, %o0 + + .align 4 + .global strlen +strlen: + mov %o0, %o1 + andcc %o0, 3, %g0 + bne 0b + sethi %hi(HI_MAGIC), %o4 + or %o4, %lo(HI_MAGIC), %o3 +4: + sethi %hi(LO_MAGIC), %o4 +5: + or %o4, %lo(LO_MAGIC), %o2 +8: + ld [%o0], %o5 +2: + sub %o5, %o2, %o4 + andcc %o4, %o3, %g0 + be 8b + add %o0, 4, %o0 + + /* Check every byte. */ + srl %o5, 24, %g5 + andcc %g5, 0xff, %g0 + be 1f + add %o0, -4, %o4 + srl %o5, 16, %g5 + andcc %g5, 0xff, %g0 + be 1f + add %o4, 1, %o4 + srl %o5, 8, %g5 + andcc %g5, 0xff, %g0 + be 1f + add %o4, 1, %o4 + andcc %o5, 0xff, %g0 + bne,a 2b + ld [%o0], %o5 + add %o4, 1, %o4 +1: + retl + sub %o4, %o1, %o0 diff --git a/arch/sparc/lib/strlen_user.S b/arch/sparc/lib/strlen_user.S new file mode 100644 index 000000000000..8c8a371df3c9 --- /dev/null +++ b/arch/sparc/lib/strlen_user.S @@ -0,0 +1,109 @@ +/* strlen_user.S: Sparc optimized strlen_user code + * + * Return length of string in userspace including terminating 0 + * or 0 for error + * + * Copyright (C) 1991,1996 Free Software Foundation + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#define LO_MAGIC 0x01010101 +#define HI_MAGIC 0x80808080 + +10: + ldub [%o0], %o5 + cmp %o5, 0 + be 1f + add %o0, 1, %o0 + andcc %o0, 3, %g0 + be 4f + or %o4, %lo(HI_MAGIC), %o3 +11: + ldub [%o0], %o5 + cmp %o5, 0 + be 2f + add %o0, 1, %o0 + andcc %o0, 3, %g0 + be 5f + sethi %hi(LO_MAGIC), %o4 +12: + ldub [%o0], %o5 + cmp %o5, 0 + be 3f + add %o0, 1, %o0 + b 13f + or %o4, %lo(LO_MAGIC), %o2 +1: + retl + mov 1, %o0 +2: + retl + mov 2, %o0 +3: + retl + mov 3, %o0 + + .align 4 + .global __strlen_user, __strnlen_user +__strlen_user: + sethi %hi(32768), %o1 +__strnlen_user: + mov %o1, %g1 + mov %o0, %o1 + andcc %o0, 3, %g0 + bne 10b + sethi %hi(HI_MAGIC), %o4 + or %o4, %lo(HI_MAGIC), %o3 +4: + sethi %hi(LO_MAGIC), %o4 +5: + or %o4, %lo(LO_MAGIC), %o2 +13: + ld [%o0], %o5 +2: + sub %o5, %o2, %o4 + andcc %o4, %o3, %g0 + bne 82f + add %o0, 4, %o0 + sub %o0, %o1, %g2 +81: cmp %g2, %g1 + blu 13b + mov %o0, %o4 + ba,a 1f + + /* Check every byte. */ +82: srl %o5, 24, %g5 + andcc %g5, 0xff, %g0 + be 1f + add %o0, -3, %o4 + srl %o5, 16, %g5 + andcc %g5, 0xff, %g0 + be 1f + add %o4, 1, %o4 + srl %o5, 8, %g5 + andcc %g5, 0xff, %g0 + be 1f + add %o4, 1, %o4 + andcc %o5, 0xff, %g0 + bne 81b + sub %o0, %o1, %g2 + + add %o4, 1, %o4 +1: + retl + sub %o4, %o1, %o0 + + .section .fixup,#alloc,#execinstr + .align 4 +9: + retl + clr %o0 + + .section __ex_table,#alloc + .align 4 + + .word 10b, 9b + .word 11b, 9b + .word 12b, 9b + .word 13b, 9b diff --git a/arch/sparc/lib/strncmp.S b/arch/sparc/lib/strncmp.S new file mode 100644 index 000000000000..615626805d4b --- /dev/null +++ b/arch/sparc/lib/strncmp.S @@ -0,0 +1,118 @@ +/* $Id: strncmp.S,v 1.2 1996/09/09 02:47:20 davem Exp $ + * strncmp.S: Hand optimized Sparc assembly of GCC output from GNU libc + * generic strncmp routine. + */ + + .text + .align 4 + .global __strncmp, strncmp +__strncmp: +strncmp: + mov %o0, %g3 + mov 0, %o3 + + cmp %o2, 3 + ble 7f + mov 0, %g2 + + sra %o2, 2, %o4 + ldub [%g3], %o3 + +0: + ldub [%o1], %g2 + add %g3, 1, %g3 + and %o3, 0xff, %o0 + + cmp %o0, 0 + be 8f + add %o1, 1, %o1 + + cmp %o0, %g2 + be,a 1f + ldub [%g3], %o3 + + retl + sub %o0, %g2, %o0 + +1: + ldub [%o1], %g2 + add %g3,1, %g3 + and %o3, 0xff, %o0 + + cmp %o0, 0 + be 8f + add %o1, 1, %o1 + + cmp %o0, %g2 + be,a 1f + ldub [%g3], %o3 + + retl + sub %o0, %g2, %o0 + +1: + ldub [%o1], %g2 + add %g3, 1, %g3 + and %o3, 0xff, %o0 + + cmp %o0, 0 + be 8f + add %o1, 1, %o1 + + cmp %o0, %g2 + be,a 1f + ldub [%g3], %o3 + + retl + sub %o0, %g2, %o0 + +1: + ldub [%o1], %g2 + add %g3, 1, %g3 + and %o3, 0xff, %o0 + + cmp %o0, 0 + be 8f + add %o1, 1, %o1 + + cmp %o0, %g2 + be 1f + add %o4, -1, %o4 + + retl + sub %o0, %g2, %o0 + +1: + + cmp %o4, 0 + bg,a 0b + ldub [%g3], %o3 + + b 7f + and %o2, 3, %o2 + +9: + ldub [%o1], %g2 + add %g3, 1, %g3 + and %o3, 0xff, %o0 + + cmp %o0, 0 + be 8f + add %o1, 1, %o1 + + cmp %o0, %g2 + be 7f + add %o2, -1, %o2 + +8: + retl + sub %o0, %g2, %o0 + +7: + cmp %o2, 0 + bg,a 9b + ldub [%g3], %o3 + + and %g2, 0xff, %o0 + retl + sub %o3, %o0, %o0 diff --git a/arch/sparc/lib/strncpy_from_user.S b/arch/sparc/lib/strncpy_from_user.S new file mode 100644 index 000000000000..d77198976a66 --- /dev/null +++ b/arch/sparc/lib/strncpy_from_user.S @@ -0,0 +1,47 @@ +/* strncpy_from_user.S: Sparc strncpy from userspace. + * + * Copyright(C) 1996 David S. Miller + */ + +#include <asm/ptrace.h> +#include <asm/errno.h> + + .text + .align 4 + + /* Must return: + * + * -EFAULT for an exception + * count if we hit the buffer limit + * bytes copied if we hit a null byte + */ + + .globl __strncpy_from_user +__strncpy_from_user: + /* %o0=dest, %o1=src, %o2=count */ + mov %o2, %o3 +1: + subcc %o2, 1, %o2 + bneg 2f + nop +10: + ldub [%o1], %o4 + add %o0, 1, %o0 + cmp %o4, 0 + add %o1, 1, %o1 + bne 1b + stb %o4, [%o0 - 1] +2: + add %o2, 1, %o0 + retl + sub %o3, %o0, %o0 + + .section .fixup,#alloc,#execinstr + .align 4 +4: + retl + mov -EFAULT, %o0 + + .section __ex_table,#alloc + .align 4 + .word 10b, 4b diff --git a/arch/sparc/lib/udiv.S b/arch/sparc/lib/udiv.S new file mode 100644 index 000000000000..2abfc6b0f3e9 --- /dev/null +++ b/arch/sparc/lib/udiv.S @@ -0,0 +1,355 @@ +/* $Id: udiv.S,v 1.4 1996/09/30 02:22:38 davem Exp $ + * udiv.S: This routine was taken from glibc-1.09 and is covered + * by the GNU Library General Public License Version 2. + */ + + +/* This file is generated from divrem.m4; DO NOT EDIT! */ +/* + * Division and remainder, from Appendix E of the Sparc Version 8 + * Architecture Manual, with fixes from Gordon Irlam. + */ + +/* + * Input: dividend and divisor in %o0 and %o1 respectively. + * + * m4 parameters: + * .udiv name of function to generate + * div div=div => %o0 / %o1; div=rem => %o0 % %o1 + * false false=true => signed; false=false => unsigned + * + * Algorithm parameters: + * N how many bits per iteration we try to get (4) + * WORDSIZE total number of bits (32) + * + * Derived constants: + * TOPBITS number of bits in the top decade of a number + * + * Important variables: + * Q the partial quotient under development (initially 0) + * R the remainder so far, initially the dividend + * ITER number of main division loop iterations required; + * equal to ceil(log2(quotient) / N). Note that this + * is the log base (2^N) of the quotient. + * V the current comparand, initially divisor*2^(ITER*N-1) + * + * Cost: + * Current estimate for non-large dividend is + * ceil(log2(quotient) / N) * (10 + 7N/2) + C + * A large dividend is one greater than 2^(31-TOPBITS) and takes a + * different path, as the upper bits of the quotient must be developed + * one bit at a time. + */ + + + .globl .udiv +.udiv: + + ! Ready to divide. Compute size of quotient; scale comparand. + orcc %o1, %g0, %o5 + bne 1f + mov %o0, %o3 + + ! Divide by zero trap. If it returns, return 0 (about as + ! wrong as possible, but that is what SunOS does...). + ta ST_DIV0 + retl + clr %o0 + +1: + cmp %o3, %o5 ! if %o1 exceeds %o0, done + blu Lgot_result ! (and algorithm fails otherwise) + clr %o2 + + sethi %hi(1 << (32 - 4 - 1)), %g1 + + cmp %o3, %g1 + blu Lnot_really_big + clr %o4 + + ! Here the dividend is >= 2**(31-N) or so. We must be careful here, + ! as our usual N-at-a-shot divide step will cause overflow and havoc. + ! The number of bits in the result here is N*ITER+SC, where SC <= N. + ! Compute ITER in an unorthodox manner: know we need to shift V into + ! the top decade: so do not even bother to compare to R. + 1: + cmp %o5, %g1 + bgeu 3f + mov 1, %g7 + + sll %o5, 4, %o5 + + b 1b + add %o4, 1, %o4 + + ! Now compute %g7. + 2: + addcc %o5, %o5, %o5 + bcc Lnot_too_big + add %g7, 1, %g7 + + ! We get here if the %o1 overflowed while shifting. + ! This means that %o3 has the high-order bit set. + ! Restore %o5 and subtract from %o3. + sll %g1, 4, %g1 ! high order bit + srl %o5, 1, %o5 ! rest of %o5 + add %o5, %g1, %o5 + + b Ldo_single_div + sub %g7, 1, %g7 + + Lnot_too_big: + 3: + cmp %o5, %o3 + blu 2b + nop + + be Ldo_single_div + nop + /* NB: these are commented out in the V8-Sparc manual as well */ + /* (I do not understand this) */ + ! %o5 > %o3: went too far: back up 1 step + ! srl %o5, 1, %o5 + ! dec %g7 + ! do single-bit divide steps + ! + ! We have to be careful here. We know that %o3 >= %o5, so we can do the + ! first divide step without thinking. BUT, the others are conditional, + ! and are only done if %o3 >= 0. Because both %o3 and %o5 may have the high- + ! order bit set in the first step, just falling into the regular + ! division loop will mess up the first time around. + ! So we unroll slightly... + Ldo_single_div: + subcc %g7, 1, %g7 + bl Lend_regular_divide + nop + + sub %o3, %o5, %o3 + mov 1, %o2 + + b Lend_single_divloop + nop + Lsingle_divloop: + sll %o2, 1, %o2 + bl 1f + srl %o5, 1, %o5 + ! %o3 >= 0 + sub %o3, %o5, %o3 + b 2f + add %o2, 1, %o2 + 1: ! %o3 < 0 + add %o3, %o5, %o3 + sub %o2, 1, %o2 + 2: + Lend_single_divloop: + subcc %g7, 1, %g7 + bge Lsingle_divloop + tst %o3 + + b,a Lend_regular_divide + +Lnot_really_big: +1: + sll %o5, 4, %o5 + + cmp %o5, %o3 + bleu 1b + addcc %o4, 1, %o4 + + be Lgot_result + sub %o4, 1, %o4 + + tst %o3 ! set up for initial iteration +Ldivloop: + sll %o2, 4, %o2 + ! depth 1, accumulated bits 0 + bl L.1.16 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 2, accumulated bits 1 + bl L.2.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits 3 + bl L.3.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 7 + bl L.4.23 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (7*2+1), %o2 + +L.4.23: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (7*2-1), %o2 + +L.3.19: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 5 + bl L.4.21 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (5*2+1), %o2 + +L.4.21: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (5*2-1), %o2 + +L.2.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits 1 + bl L.3.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 3 + bl L.4.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (3*2+1), %o2 + +L.4.19: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (3*2-1), %o2 + +L.3.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 1 + bl L.4.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (1*2+1), %o2 + +L.4.17: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (1*2-1), %o2 + +L.1.16: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 2, accumulated bits -1 + bl L.2.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits -1 + bl L.3.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -1 + bl L.4.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2+1), %o2 + +L.4.15: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2-1), %o2 + +L.3.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -3 + bl L.4.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2+1), %o2 + +L.4.13: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2-1), %o2 + +L.2.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits -3 + bl L.3.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -5 + bl L.4.11 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2+1), %o2 + +L.4.11: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2-1), %o2 + +L.3.13: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -7 + bl L.4.9 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2+1), %o2 + +L.4.9: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2-1), %o2 + + 9: +Lend_regular_divide: + subcc %o4, 1, %o4 + bge Ldivloop + tst %o3 + + bl,a Lgot_result + ! non-restoring fixup here (one instruction only!) + sub %o2, 1, %o2 + +Lgot_result: + + retl + mov %o2, %o0 + + .globl .udiv_patch +.udiv_patch: + wr %g0, 0x0, %y + nop + nop + retl + udiv %o0, %o1, %o0 + nop diff --git a/arch/sparc/lib/udivdi3.S b/arch/sparc/lib/udivdi3.S new file mode 100644 index 000000000000..b430f1f0ef62 --- /dev/null +++ b/arch/sparc/lib/udivdi3.S @@ -0,0 +1,258 @@ +/* Copyright (C) 1989, 1992, 1993, 1994, 1995 Free Software Foundation, Inc. + +This file is part of GNU CC. + +GNU CC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU CC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU CC; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + + .text + .align 4 + .globl __udivdi3 +__udivdi3: + save %sp,-104,%sp + mov %i3,%o3 + cmp %i2,0 + bne .LL40 + mov %i1,%i3 + cmp %o3,%i0 + bleu .LL41 + mov %i3,%o1 + ! Inlined udiv_qrnnd + mov 32,%g1 + subcc %i0,%o3,%g0 +1: bcs 5f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + sub %i0,%o3,%i0 ! this kills msb of n + addx %i0,%i0,%i0 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %i0,%o3,%g0 + bcs 3f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + b 3f + sub %i0,%o3,%i0 ! this kills msb of n +4: sub %i0,%o3,%i0 +5: addxcc %i0,%i0,%i0 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o1,%o1,%o1 ! shift n1n0 and a 0-bit in lsb + sub %i0,%o3,%i0 +3: xnor %o1,0,%o1 + ! End of inline udiv_qrnnd + b .LL45 + mov 0,%o2 +.LL41: + cmp %o3,0 + bne .LL77 + mov %i0,%o2 + mov 1,%o0 + call .udiv,0 + mov 0,%o1 + mov %o0,%o3 + mov %i0,%o2 +.LL77: + mov 0,%o4 + ! Inlined udiv_qrnnd + mov 32,%g1 + subcc %o4,%o3,%g0 +1: bcs 5f + addxcc %o2,%o2,%o2 ! shift n1n0 and a q-bit in lsb + sub %o4,%o3,%o4 ! this kills msb of n + addx %o4,%o4,%o4 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %o4,%o3,%g0 + bcs 3f + addxcc %o2,%o2,%o2 ! shift n1n0 and a q-bit in lsb + b 3f + sub %o4,%o3,%o4 ! this kills msb of n +4: sub %o4,%o3,%o4 +5: addxcc %o4,%o4,%o4 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o2,%o2,%o2 ! shift n1n0 and a 0-bit in lsb + sub %o4,%o3,%o4 +3: xnor %o2,0,%o2 + ! End of inline udiv_qrnnd + mov %o4,%i0 + mov %i3,%o1 + ! Inlined udiv_qrnnd + mov 32,%g1 + subcc %i0,%o3,%g0 +1: bcs 5f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + sub %i0,%o3,%i0 ! this kills msb of n + addx %i0,%i0,%i0 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %i0,%o3,%g0 + bcs 3f + addxcc %o1,%o1,%o1 ! shift n1n0 and a q-bit in lsb + b 3f + sub %i0,%o3,%i0 ! this kills msb of n +4: sub %i0,%o3,%i0 +5: addxcc %i0,%i0,%i0 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o1,%o1,%o1 ! shift n1n0 and a 0-bit in lsb + sub %i0,%o3,%i0 +3: xnor %o1,0,%o1 + ! End of inline udiv_qrnnd + b .LL78 + mov %o1,%l1 +.LL40: + cmp %i2,%i0 + bleu .LL46 + sethi %hi(65535),%o0 + b .LL73 + mov 0,%o1 +.LL46: + or %o0,%lo(65535),%o0 + cmp %i2,%o0 + bgu .LL53 + mov %i2,%o1 + cmp %i2,256 + addx %g0,-1,%o0 + b .LL59 + and %o0,8,%o2 +.LL53: + sethi %hi(16777215),%o0 + or %o0,%lo(16777215),%o0 + cmp %o1,%o0 + bgu .LL59 + mov 24,%o2 + mov 16,%o2 +.LL59: + srl %o1,%o2,%o1 + sethi %hi(__clz_tab),%o0 + or %o0,%lo(__clz_tab),%o0 + ldub [%o1+%o0],%o0 + add %o0,%o2,%o0 + mov 32,%o1 + subcc %o1,%o0,%o2 + bne,a .LL67 + mov 32,%o0 + cmp %i0,%i2 + bgu .LL69 + cmp %i3,%o3 + blu .LL73 + mov 0,%o1 +.LL69: + b .LL73 + mov 1,%o1 +.LL67: + sub %o0,%o2,%o0 + sll %i2,%o2,%i2 + srl %o3,%o0,%o1 + or %i2,%o1,%i2 + sll %o3,%o2,%o3 + srl %i0,%o0,%o1 + sll %i0,%o2,%i0 + srl %i3,%o0,%o0 + or %i0,%o0,%i0 + sll %i3,%o2,%i3 + mov %i0,%o5 + mov %o1,%o4 + ! Inlined udiv_qrnnd + mov 32,%g1 + subcc %o4,%i2,%g0 +1: bcs 5f + addxcc %o5,%o5,%o5 ! shift n1n0 and a q-bit in lsb + sub %o4,%i2,%o4 ! this kills msb of n + addx %o4,%o4,%o4 ! so this cannot give carry + subcc %g1,1,%g1 +2: bne 1b + subcc %o4,%i2,%g0 + bcs 3f + addxcc %o5,%o5,%o5 ! shift n1n0 and a q-bit in lsb + b 3f + sub %o4,%i2,%o4 ! this kills msb of n +4: sub %o4,%i2,%o4 +5: addxcc %o4,%o4,%o4 + bcc 2b + subcc %g1,1,%g1 +! Got carry from n. Subtract next step to cancel this carry. + bne 4b + addcc %o5,%o5,%o5 ! shift n1n0 and a 0-bit in lsb + sub %o4,%i2,%o4 +3: xnor %o5,0,%o5 + ! End of inline udiv_qrnnd + mov %o4,%i0 + mov %o5,%o1 + ! Inlined umul_ppmm + wr %g0,%o1,%y ! SPARC has 0-3 delay insn after a wr + sra %o3,31,%g2 ! Do not move this insn + and %o1,%g2,%g2 ! Do not move this insn + andcc %g0,0,%g1 ! Do not move this insn + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,%o3,%g1 + mulscc %g1,0,%g1 + add %g1,%g2,%o0 + rd %y,%o2 + cmp %o0,%i0 + bgu,a .LL73 + add %o1,-1,%o1 + bne,a .LL45 + mov 0,%o2 + cmp %o2,%i3 + bleu .LL45 + mov 0,%o2 + add %o1,-1,%o1 +.LL73: + mov 0,%o2 +.LL45: + mov %o1,%l1 +.LL78: + mov %o2,%l0 + mov %l0,%i0 + mov %l1,%i1 + ret + restore diff --git a/arch/sparc/lib/umul.S b/arch/sparc/lib/umul.S new file mode 100644 index 000000000000..a784720a8a22 --- /dev/null +++ b/arch/sparc/lib/umul.S @@ -0,0 +1,169 @@ +/* $Id: umul.S,v 1.4 1996/09/30 02:22:39 davem Exp $ + * umul.S: This routine was taken from glibc-1.09 and is covered + * by the GNU Library General Public License Version 2. + */ + + +/* + * Unsigned multiply. Returns %o0 * %o1 in %o1%o0 (i.e., %o1 holds the + * upper 32 bits of the 64-bit product). + * + * This code optimizes short (less than 13-bit) multiplies. Short + * multiplies require 25 instruction cycles, and long ones require + * 45 instruction cycles. + * + * On return, overflow has occurred (%o1 is not zero) if and only if + * the Z condition code is clear, allowing, e.g., the following: + * + * call .umul + * nop + * bnz overflow (or tnz) + */ + + .globl .umul +.umul: + or %o0, %o1, %o4 + mov %o0, %y ! multiplier -> Y + + andncc %o4, 0xfff, %g0 ! test bits 12..31 of *both* args + be Lmul_shortway ! if zero, can do it the short way + andcc %g0, %g0, %o4 ! zero the partial product and clear N and V + + /* + * Long multiply. 32 steps, followed by a final shift step. + */ + mulscc %o4, %o1, %o4 ! 1 + mulscc %o4, %o1, %o4 ! 2 + mulscc %o4, %o1, %o4 ! 3 + mulscc %o4, %o1, %o4 ! 4 + mulscc %o4, %o1, %o4 ! 5 + mulscc %o4, %o1, %o4 ! 6 + mulscc %o4, %o1, %o4 ! 7 + mulscc %o4, %o1, %o4 ! 8 + mulscc %o4, %o1, %o4 ! 9 + mulscc %o4, %o1, %o4 ! 10 + mulscc %o4, %o1, %o4 ! 11 + mulscc %o4, %o1, %o4 ! 12 + mulscc %o4, %o1, %o4 ! 13 + mulscc %o4, %o1, %o4 ! 14 + mulscc %o4, %o1, %o4 ! 15 + mulscc %o4, %o1, %o4 ! 16 + mulscc %o4, %o1, %o4 ! 17 + mulscc %o4, %o1, %o4 ! 18 + mulscc %o4, %o1, %o4 ! 19 + mulscc %o4, %o1, %o4 ! 20 + mulscc %o4, %o1, %o4 ! 21 + mulscc %o4, %o1, %o4 ! 22 + mulscc %o4, %o1, %o4 ! 23 + mulscc %o4, %o1, %o4 ! 24 + mulscc %o4, %o1, %o4 ! 25 + mulscc %o4, %o1, %o4 ! 26 + mulscc %o4, %o1, %o4 ! 27 + mulscc %o4, %o1, %o4 ! 28 + mulscc %o4, %o1, %o4 ! 29 + mulscc %o4, %o1, %o4 ! 30 + mulscc %o4, %o1, %o4 ! 31 + mulscc %o4, %o1, %o4 ! 32 + mulscc %o4, %g0, %o4 ! final shift + + + /* + * Normally, with the shift-and-add approach, if both numbers are + * positive you get the correct result. With 32-bit two's-complement + * numbers, -x is represented as + * + * x 32 + * ( 2 - ------ ) mod 2 * 2 + * 32 + * 2 + * + * (the `mod 2' subtracts 1 from 1.bbbb). To avoid lots of 2^32s, + * we can treat this as if the radix point were just to the left + * of the sign bit (multiply by 2^32), and get + * + * -x = (2 - x) mod 2 + * + * Then, ignoring the `mod 2's for convenience: + * + * x * y = xy + * -x * y = 2y - xy + * x * -y = 2x - xy + * -x * -y = 4 - 2x - 2y + xy + * + * For signed multiplies, we subtract (x << 32) from the partial + * product to fix this problem for negative multipliers (see mul.s). + * Because of the way the shift into the partial product is calculated + * (N xor V), this term is automatically removed for the multiplicand, + * so we don't have to adjust. + * + * But for unsigned multiplies, the high order bit wasn't a sign bit, + * and the correction is wrong. So for unsigned multiplies where the + * high order bit is one, we end up with xy - (y << 32). To fix it + * we add y << 32. + */ +#if 0 + tst %o1 + bl,a 1f ! if %o1 < 0 (high order bit = 1), + add %o4, %o0, %o4 ! %o4 += %o0 (add y to upper half) + +1: + rd %y, %o0 ! get lower half of product + retl + addcc %o4, %g0, %o1 ! put upper half in place and set Z for %o1==0 +#else + /* Faster code from tege@sics.se. */ + sra %o1, 31, %o2 ! make mask from sign bit + and %o0, %o2, %o2 ! %o2 = 0 or %o0, depending on sign of %o1 + rd %y, %o0 ! get lower half of product + retl + addcc %o4, %o2, %o1 ! add compensation and put upper half in place +#endif + +Lmul_shortway: + /* + * Short multiply. 12 steps, followed by a final shift step. + * The resulting bits are off by 12 and (32-12) = 20 bit positions, + * but there is no problem with %o0 being negative (unlike above), + * and overflow is impossible (the answer is at most 24 bits long). + */ + mulscc %o4, %o1, %o4 ! 1 + mulscc %o4, %o1, %o4 ! 2 + mulscc %o4, %o1, %o4 ! 3 + mulscc %o4, %o1, %o4 ! 4 + mulscc %o4, %o1, %o4 ! 5 + mulscc %o4, %o1, %o4 ! 6 + mulscc %o4, %o1, %o4 ! 7 + mulscc %o4, %o1, %o4 ! 8 + mulscc %o4, %o1, %o4 ! 9 + mulscc %o4, %o1, %o4 ! 10 + mulscc %o4, %o1, %o4 ! 11 + mulscc %o4, %o1, %o4 ! 12 + mulscc %o4, %g0, %o4 ! final shift + + /* + * %o4 has 20 of the bits that should be in the result; %y has + * the bottom 12 (as %y's top 12). That is: + * + * %o4 %y + * +----------------+----------------+ + * | -12- | -20- | -12- | -20- | + * +------(---------+------)---------+ + * -----result----- + * + * The 12 bits of %o4 left of the `result' area are all zero; + * in fact, all top 20 bits of %o4 are zero. + */ + + rd %y, %o5 + sll %o4, 12, %o0 ! shift middle bits left 12 + srl %o5, 20, %o5 ! shift low bits right 20 + or %o5, %o0, %o0 + retl + addcc %g0, %g0, %o1 ! %o1 = zero, and set Z + + .globl .umul_patch +.umul_patch: + umul %o0, %o1, %o0 + retl + rd %y, %o1 + nop diff --git a/arch/sparc/lib/urem.S b/arch/sparc/lib/urem.S new file mode 100644 index 000000000000..ec7f0c502c56 --- /dev/null +++ b/arch/sparc/lib/urem.S @@ -0,0 +1,355 @@ +/* $Id: urem.S,v 1.4 1996/09/30 02:22:42 davem Exp $ + * urem.S: This routine was taken from glibc-1.09 and is covered + * by the GNU Library General Public License Version 2. + */ + +/* This file is generated from divrem.m4; DO NOT EDIT! */ +/* + * Division and remainder, from Appendix E of the Sparc Version 8 + * Architecture Manual, with fixes from Gordon Irlam. + */ + +/* + * Input: dividend and divisor in %o0 and %o1 respectively. + * + * m4 parameters: + * .urem name of function to generate + * rem rem=div => %o0 / %o1; rem=rem => %o0 % %o1 + * false false=true => signed; false=false => unsigned + * + * Algorithm parameters: + * N how many bits per iteration we try to get (4) + * WORDSIZE total number of bits (32) + * + * Derived constants: + * TOPBITS number of bits in the top decade of a number + * + * Important variables: + * Q the partial quotient under development (initially 0) + * R the remainder so far, initially the dividend + * ITER number of main division loop iterations required; + * equal to ceil(log2(quotient) / N). Note that this + * is the log base (2^N) of the quotient. + * V the current comparand, initially divisor*2^(ITER*N-1) + * + * Cost: + * Current estimate for non-large dividend is + * ceil(log2(quotient) / N) * (10 + 7N/2) + C + * A large dividend is one greater than 2^(31-TOPBITS) and takes a + * different path, as the upper bits of the quotient must be developed + * one bit at a time. + */ + + .globl .urem +.urem: + + ! Ready to divide. Compute size of quotient; scale comparand. + orcc %o1, %g0, %o5 + bne 1f + mov %o0, %o3 + + ! Divide by zero trap. If it returns, return 0 (about as + ! wrong as possible, but that is what SunOS does...). + ta ST_DIV0 + retl + clr %o0 + +1: + cmp %o3, %o5 ! if %o1 exceeds %o0, done + blu Lgot_result ! (and algorithm fails otherwise) + clr %o2 + + sethi %hi(1 << (32 - 4 - 1)), %g1 + + cmp %o3, %g1 + blu Lnot_really_big + clr %o4 + + ! Here the dividend is >= 2**(31-N) or so. We must be careful here, + ! as our usual N-at-a-shot divide step will cause overflow and havoc. + ! The number of bits in the result here is N*ITER+SC, where SC <= N. + ! Compute ITER in an unorthodox manner: know we need to shift V into + ! the top decade: so do not even bother to compare to R. + 1: + cmp %o5, %g1 + bgeu 3f + mov 1, %g7 + + sll %o5, 4, %o5 + + b 1b + add %o4, 1, %o4 + + ! Now compute %g7. + 2: + addcc %o5, %o5, %o5 + bcc Lnot_too_big + add %g7, 1, %g7 + + ! We get here if the %o1 overflowed while shifting. + ! This means that %o3 has the high-order bit set. + ! Restore %o5 and subtract from %o3. + sll %g1, 4, %g1 ! high order bit + srl %o5, 1, %o5 ! rest of %o5 + add %o5, %g1, %o5 + + b Ldo_single_div + sub %g7, 1, %g7 + + Lnot_too_big: + 3: + cmp %o5, %o3 + blu 2b + nop + + be Ldo_single_div + nop + /* NB: these are commented out in the V8-Sparc manual as well */ + /* (I do not understand this) */ + ! %o5 > %o3: went too far: back up 1 step + ! srl %o5, 1, %o5 + ! dec %g7 + ! do single-bit divide steps + ! + ! We have to be careful here. We know that %o3 >= %o5, so we can do the + ! first divide step without thinking. BUT, the others are conditional, + ! and are only done if %o3 >= 0. Because both %o3 and %o5 may have the high- + ! order bit set in the first step, just falling into the regular + ! division loop will mess up the first time around. + ! So we unroll slightly... + Ldo_single_div: + subcc %g7, 1, %g7 + bl Lend_regular_divide + nop + + sub %o3, %o5, %o3 + mov 1, %o2 + + b Lend_single_divloop + nop + Lsingle_divloop: + sll %o2, 1, %o2 + bl 1f + srl %o5, 1, %o5 + ! %o3 >= 0 + sub %o3, %o5, %o3 + b 2f + add %o2, 1, %o2 + 1: ! %o3 < 0 + add %o3, %o5, %o3 + sub %o2, 1, %o2 + 2: + Lend_single_divloop: + subcc %g7, 1, %g7 + bge Lsingle_divloop + tst %o3 + + b,a Lend_regular_divide + +Lnot_really_big: +1: + sll %o5, 4, %o5 + + cmp %o5, %o3 + bleu 1b + addcc %o4, 1, %o4 + + be Lgot_result + sub %o4, 1, %o4 + + tst %o3 ! set up for initial iteration +Ldivloop: + sll %o2, 4, %o2 + ! depth 1, accumulated bits 0 + bl L.1.16 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 2, accumulated bits 1 + bl L.2.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits 3 + bl L.3.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 7 + bl L.4.23 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (7*2+1), %o2 + +L.4.23: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (7*2-1), %o2 + +L.3.19: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 5 + bl L.4.21 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (5*2+1), %o2 + +L.4.21: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (5*2-1), %o2 + +L.2.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits 1 + bl L.3.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits 3 + bl L.4.19 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (3*2+1), %o2 + +L.4.19: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (3*2-1), %o2 + +L.3.17: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits 1 + bl L.4.17 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (1*2+1), %o2 + +L.4.17: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (1*2-1), %o2 + +L.1.16: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 2, accumulated bits -1 + bl L.2.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 3, accumulated bits -1 + bl L.3.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -1 + bl L.4.15 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2+1), %o2 + +L.4.15: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-1*2-1), %o2 + +L.3.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -3 + bl L.4.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2+1), %o2 + +L.4.13: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-3*2-1), %o2 + +L.2.15: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 3, accumulated bits -3 + bl L.3.13 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + ! depth 4, accumulated bits -5 + bl L.4.11 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2+1), %o2 + +L.4.11: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-5*2-1), %o2 + +L.3.13: + ! remainder is negative + addcc %o3,%o5,%o3 + ! depth 4, accumulated bits -7 + bl L.4.9 + srl %o5,1,%o5 + ! remainder is positive + subcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2+1), %o2 + +L.4.9: + ! remainder is negative + addcc %o3,%o5,%o3 + b 9f + add %o2, (-7*2-1), %o2 + + 9: +Lend_regular_divide: + subcc %o4, 1, %o4 + bge Ldivloop + tst %o3 + + bl,a Lgot_result + ! non-restoring fixup here (one instruction only!) + add %o3, %o1, %o3 + +Lgot_result: + + retl + mov %o3, %o0 + + .globl .urem_patch +.urem_patch: + wr %g0, 0x0, %y + nop + nop + nop + udiv %o0, %o1, %o2 + umul %o2, %o1, %o2 + retl + sub %o0, %o2, %o0 diff --git a/arch/sparc/math-emu/Makefile b/arch/sparc/math-emu/Makefile new file mode 100644 index 000000000000..f84a9a6162be --- /dev/null +++ b/arch/sparc/math-emu/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the FPU instruction emulation. +# + +obj-y := math.o + +EXTRA_AFLAGS := -ansi +EXTRA_CFLAGS = -I. -I$(TOPDIR)/include/math-emu -w diff --git a/arch/sparc/math-emu/ashldi3.S b/arch/sparc/math-emu/ashldi3.S new file mode 100644 index 000000000000..eab1d097296a --- /dev/null +++ b/arch/sparc/math-emu/ashldi3.S @@ -0,0 +1,36 @@ +/* $Id: ashldi3.S,v 1.1 1998/04/06 16:09:28 jj Exp $ + * ashldi3.S: Math-emu code creates all kinds of references to + * this little routine on the sparc with gcc. + * + * Copyright (C) 1998 Jakub Jelinek(jj@ultra.linux.cz) + */ + +#include <asm/cprefix.h> + + .globl C_LABEL(__ashldi3) +C_LABEL(__ashldi3): + tst %o2 + be 3f + mov 32, %g2 + + sub %g2, %o2, %g2 + + tst %g2 + bg 1f + srl %o1, %g2, %g3 + + clr %o5 + neg %g2 + ba 2f + sll %o1, %g2, %o4 + +1: + sll %o1, %o2, %o5 + srl %o0, %o2, %g2 + or %g2, %g3, %o4 +2: + mov %o4, %o0 + mov %o5, %o1 +3: + jmpl %o7 + 8, %g0 + nop diff --git a/arch/sparc/math-emu/math.c b/arch/sparc/math-emu/math.c new file mode 100644 index 000000000000..be2c80932e26 --- /dev/null +++ b/arch/sparc/math-emu/math.c @@ -0,0 +1,521 @@ +/* + * arch/sparc/math-emu/math.c + * + * Copyright (C) 1998 Peter Maydell (pmaydell@chiark.greenend.org.uk) + * Copyright (C) 1997, 1999 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1999 David S. Miller (davem@redhat.com) + * + * This is a good place to start if you're trying to understand the + * emulation code, because it's pretty simple. What we do is + * essentially analyse the instruction to work out what the operation + * is and which registers are involved. We then execute the appropriate + * FXXXX function. [The floating point queue introduces a minor wrinkle; + * see below...] + * The fxxxxx.c files each emulate a single insn. They look relatively + * simple because the complexity is hidden away in an unholy tangle + * of preprocessor macros. + * + * The first layer of macros is single.h, double.h, quad.h. Generally + * these files define macros for working with floating point numbers + * of the three IEEE formats. FP_ADD_D(R,A,B) is for adding doubles, + * for instance. These macros are usually defined as calls to more + * generic macros (in this case _FP_ADD(D,2,R,X,Y) where the number + * of machine words required to store the given IEEE format is passed + * as a parameter. [double.h and co check the number of bits in a word + * and define FP_ADD_D & co appropriately]. + * The generic macros are defined in op-common.h. This is where all + * the grotty stuff like handling NaNs is coded. To handle the possible + * word sizes macros in op-common.h use macros like _FP_FRAC_SLL_##wc() + * where wc is the 'number of machine words' parameter (here 2). + * These are defined in the third layer of macros: op-1.h, op-2.h + * and op-4.h. These handle operations on floating point numbers composed + * of 1,2 and 4 machine words respectively. [For example, on sparc64 + * doubles are one machine word so macros in double.h eventually use + * constructs in op-1.h, but on sparc32 they use op-2.h definitions.] + * soft-fp.h is on the same level as op-common.h, and defines some + * macros which are independent of both word size and FP format. + * Finally, sfp-machine.h is the machine dependent part of the + * code: it defines the word size and what type a word is. It also + * defines how _FP_MUL_MEAT_t() maps to _FP_MUL_MEAT_n_* : op-n.h + * provide several possible flavours of multiply algorithm, most + * of which require that you supply some form of asm or C primitive to + * do the actual multiply. (such asm primitives should be defined + * in sfp-machine.h too). udivmodti4.c is the same sort of thing. + * + * There may be some errors here because I'm working from a + * SPARC architecture manual V9, and what I really want is V8... + * Also, the insns which can generate exceptions seem to be a + * greater subset of the FPops than for V9 (for example, FCMPED + * has to be emulated on V8). So I think I'm going to have + * to emulate them all just to be on the safe side... + * + * Emulation routines originate from soft-fp package, which is + * part of glibc and has appropriate copyrights in it (allegedly). + * + * NB: on sparc int == long == 4 bytes, long long == 8 bytes. + * Most bits of the kernel seem to go for long rather than int, + * so we follow that practice... + */ + +/* TODO: + * fpsave() saves the FP queue but fpload() doesn't reload it. + * Therefore when we context switch or change FPU ownership + * we have to check to see if the queue had anything in it and + * emulate it if it did. This is going to be a pain. + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <asm/uaccess.h> + +#include "sfp-util.h" +#include <math-emu/soft-fp.h> +#include <math-emu/single.h> +#include <math-emu/double.h> +#include <math-emu/quad.h> + +#define FLOATFUNC(x) extern int x(void *,void *,void *) + +/* The Vn labels indicate what version of the SPARC architecture gas thinks + * each insn is. This is from the binutils source :-> + */ +/* quadword instructions */ +#define FSQRTQ 0x02b /* v8 */ +#define FADDQ 0x043 /* v8 */ +#define FSUBQ 0x047 /* v8 */ +#define FMULQ 0x04b /* v8 */ +#define FDIVQ 0x04f /* v8 */ +#define FDMULQ 0x06e /* v8 */ +#define FQTOS 0x0c7 /* v8 */ +#define FQTOD 0x0cb /* v8 */ +#define FITOQ 0x0cc /* v8 */ +#define FSTOQ 0x0cd /* v8 */ +#define FDTOQ 0x0ce /* v8 */ +#define FQTOI 0x0d3 /* v8 */ +#define FCMPQ 0x053 /* v8 */ +#define FCMPEQ 0x057 /* v8 */ +/* single/double instructions (subnormal): should all work */ +#define FSQRTS 0x029 /* v7 */ +#define FSQRTD 0x02a /* v7 */ +#define FADDS 0x041 /* v6 */ +#define FADDD 0x042 /* v6 */ +#define FSUBS 0x045 /* v6 */ +#define FSUBD 0x046 /* v6 */ +#define FMULS 0x049 /* v6 */ +#define FMULD 0x04a /* v6 */ +#define FDIVS 0x04d /* v6 */ +#define FDIVD 0x04e /* v6 */ +#define FSMULD 0x069 /* v6 */ +#define FDTOS 0x0c6 /* v6 */ +#define FSTOD 0x0c9 /* v6 */ +#define FSTOI 0x0d1 /* v6 */ +#define FDTOI 0x0d2 /* v6 */ +#define FABSS 0x009 /* v6 */ +#define FCMPS 0x051 /* v6 */ +#define FCMPES 0x055 /* v6 */ +#define FCMPD 0x052 /* v6 */ +#define FCMPED 0x056 /* v6 */ +#define FMOVS 0x001 /* v6 */ +#define FNEGS 0x005 /* v6 */ +#define FITOS 0x0c4 /* v6 */ +#define FITOD 0x0c8 /* v6 */ + +#define FSR_TEM_SHIFT 23UL +#define FSR_TEM_MASK (0x1fUL << FSR_TEM_SHIFT) +#define FSR_AEXC_SHIFT 5UL +#define FSR_AEXC_MASK (0x1fUL << FSR_AEXC_SHIFT) +#define FSR_CEXC_SHIFT 0UL +#define FSR_CEXC_MASK (0x1fUL << FSR_CEXC_SHIFT) + +static int do_one_mathemu(u32 insn, unsigned long *fsr, unsigned long *fregs); + +/* Unlike the Sparc64 version (which has a struct fpustate), we + * pass the taskstruct corresponding to the task which currently owns the + * FPU. This is partly because we don't have the fpustate struct and + * partly because the task owning the FPU isn't always current (as is + * the case for the Sparc64 port). This is probably SMP-related... + * This function returns 1 if all queued insns were emulated successfully. + * The test for unimplemented FPop in kernel mode has been moved into + * kernel/traps.c for simplicity. + */ +int do_mathemu(struct pt_regs *regs, struct task_struct *fpt) +{ + /* regs->pc isn't necessarily the PC at which the offending insn is sitting. + * The FPU maintains a queue of FPops which cause traps. + * When it hits an instruction that requires that the trapped op succeeded + * (usually because it reads a reg. that the trapped op wrote) then it + * causes this exception. We need to emulate all the insns on the queue + * and then allow the op to proceed. + * This code should also handle the case where the trap was precise, + * in which case the queue length is zero and regs->pc points at the + * single FPop to be emulated. (this case is untested, though :->) + * You'll need this case if you want to be able to emulate all FPops + * because the FPU either doesn't exist or has been software-disabled. + * [The UltraSPARC makes FP a precise trap; this isn't as stupid as it + * might sound because the Ultra does funky things with a superscalar + * architecture.] + */ + + /* You wouldn't believe how often I typed 'ftp' when I meant 'fpt' :-> */ + + int i; + int retcode = 0; /* assume all succeed */ + unsigned long insn; + +#ifdef DEBUG_MATHEMU + printk("In do_mathemu()... pc is %08lx\n", regs->pc); + printk("fpqdepth is %ld\n", fpt->thread.fpqdepth); + for (i = 0; i < fpt->thread.fpqdepth; i++) + printk("%d: %08lx at %08lx\n", i, fpt->thread.fpqueue[i].insn, + (unsigned long)fpt->thread.fpqueue[i].insn_addr); +#endif + + if (fpt->thread.fpqdepth == 0) { /* no queue, guilty insn is at regs->pc */ +#ifdef DEBUG_MATHEMU + printk("precise trap at %08lx\n", regs->pc); +#endif + if (!get_user(insn, (u32 __user *) regs->pc)) { + retcode = do_one_mathemu(insn, &fpt->thread.fsr, fpt->thread.float_regs); + if (retcode) { + /* in this case we need to fix up PC & nPC */ + regs->pc = regs->npc; + regs->npc += 4; + } + } + return retcode; + } + + /* Normal case: need to empty the queue... */ + for (i = 0; i < fpt->thread.fpqdepth; i++) { + retcode = do_one_mathemu(fpt->thread.fpqueue[i].insn, &(fpt->thread.fsr), fpt->thread.float_regs); + if (!retcode) /* insn failed, no point doing any more */ + break; + } + /* Now empty the queue and clear the queue_not_empty flag */ + if (retcode) + fpt->thread.fsr &= ~(0x3000 | FSR_CEXC_MASK); + else + fpt->thread.fsr &= ~0x3000; + fpt->thread.fpqdepth = 0; + + return retcode; +} + +/* All routines returning an exception to raise should detect + * such exceptions _before_ rounding to be consistent with + * the behavior of the hardware in the implemented cases + * (and thus with the recommendations in the V9 architecture + * manual). + * + * We return 0 if a SIGFPE should be sent, 1 otherwise. + */ +static inline int record_exception(unsigned long *pfsr, int eflag) +{ + unsigned long fsr = *pfsr; + int would_trap; + + /* Determine if this exception would have generated a trap. */ + would_trap = (fsr & ((long)eflag << FSR_TEM_SHIFT)) != 0UL; + + /* If trapping, we only want to signal one bit. */ + if (would_trap != 0) { + eflag &= ((fsr & FSR_TEM_MASK) >> FSR_TEM_SHIFT); + if ((eflag & (eflag - 1)) != 0) { + if (eflag & FP_EX_INVALID) + eflag = FP_EX_INVALID; + else if (eflag & FP_EX_OVERFLOW) + eflag = FP_EX_OVERFLOW; + else if (eflag & FP_EX_UNDERFLOW) + eflag = FP_EX_UNDERFLOW; + else if (eflag & FP_EX_DIVZERO) + eflag = FP_EX_DIVZERO; + else if (eflag & FP_EX_INEXACT) + eflag = FP_EX_INEXACT; + } + } + + /* Set CEXC, here is the rule: + * + * In general all FPU ops will set one and only one + * bit in the CEXC field, this is always the case + * when the IEEE exception trap is enabled in TEM. + */ + fsr &= ~(FSR_CEXC_MASK); + fsr |= ((long)eflag << FSR_CEXC_SHIFT); + + /* Set the AEXC field, rule is: + * + * If a trap would not be generated, the + * CEXC just generated is OR'd into the + * existing value of AEXC. + */ + if (would_trap == 0) + fsr |= ((long)eflag << FSR_AEXC_SHIFT); + + /* If trapping, indicate fault trap type IEEE. */ + if (would_trap != 0) + fsr |= (1UL << 14); + + *pfsr = fsr; + + return (would_trap ? 0 : 1); +} + +typedef union { + u32 s; + u64 d; + u64 q[2]; +} *argp; + +static int do_one_mathemu(u32 insn, unsigned long *pfsr, unsigned long *fregs) +{ + /* Emulate the given insn, updating fsr and fregs appropriately. */ + int type = 0; + /* r is rd, b is rs2 and a is rs1. The *u arg tells + whether the argument should be packed/unpacked (0 - do not unpack/pack, 1 - unpack/pack) + non-u args tells the size of the argument (0 - no argument, 1 - single, 2 - double, 3 - quad */ +#define TYPE(dummy, r, ru, b, bu, a, au) type = (au << 2) | (a << 0) | (bu << 5) | (b << 3) | (ru << 8) | (r << 6) + int freg; + argp rs1 = NULL, rs2 = NULL, rd = NULL; + FP_DECL_EX; + FP_DECL_S(SA); FP_DECL_S(SB); FP_DECL_S(SR); + FP_DECL_D(DA); FP_DECL_D(DB); FP_DECL_D(DR); + FP_DECL_Q(QA); FP_DECL_Q(QB); FP_DECL_Q(QR); + int IR; + long fsr; + +#ifdef DEBUG_MATHEMU + printk("In do_mathemu(), emulating %08lx\n", insn); +#endif + + if ((insn & 0xc1f80000) == 0x81a00000) /* FPOP1 */ { + switch ((insn >> 5) & 0x1ff) { + case FSQRTQ: TYPE(3,3,1,3,1,0,0); break; + case FADDQ: + case FSUBQ: + case FMULQ: + case FDIVQ: TYPE(3,3,1,3,1,3,1); break; + case FDMULQ: TYPE(3,3,1,2,1,2,1); break; + case FQTOS: TYPE(3,1,1,3,1,0,0); break; + case FQTOD: TYPE(3,2,1,3,1,0,0); break; + case FITOQ: TYPE(3,3,1,1,0,0,0); break; + case FSTOQ: TYPE(3,3,1,1,1,0,0); break; + case FDTOQ: TYPE(3,3,1,2,1,0,0); break; + case FQTOI: TYPE(3,1,0,3,1,0,0); break; + case FSQRTS: TYPE(2,1,1,1,1,0,0); break; + case FSQRTD: TYPE(2,2,1,2,1,0,0); break; + case FADDD: + case FSUBD: + case FMULD: + case FDIVD: TYPE(2,2,1,2,1,2,1); break; + case FADDS: + case FSUBS: + case FMULS: + case FDIVS: TYPE(2,1,1,1,1,1,1); break; + case FSMULD: TYPE(2,2,1,1,1,1,1); break; + case FDTOS: TYPE(2,1,1,2,1,0,0); break; + case FSTOD: TYPE(2,2,1,1,1,0,0); break; + case FSTOI: TYPE(2,1,0,1,1,0,0); break; + case FDTOI: TYPE(2,1,0,2,1,0,0); break; + case FITOS: TYPE(2,1,1,1,0,0,0); break; + case FITOD: TYPE(2,2,1,1,0,0,0); break; + case FMOVS: + case FABSS: + case FNEGS: TYPE(2,1,0,1,0,0,0); break; + default: +#ifdef DEBUG_MATHEMU + printk("unknown FPop1: %03lx\n",(insn>>5)&0x1ff); +#endif + break; + } + } else if ((insn & 0xc1f80000) == 0x81a80000) /* FPOP2 */ { + switch ((insn >> 5) & 0x1ff) { + case FCMPS: TYPE(3,0,0,1,1,1,1); break; + case FCMPES: TYPE(3,0,0,1,1,1,1); break; + case FCMPD: TYPE(3,0,0,2,1,2,1); break; + case FCMPED: TYPE(3,0,0,2,1,2,1); break; + case FCMPQ: TYPE(3,0,0,3,1,3,1); break; + case FCMPEQ: TYPE(3,0,0,3,1,3,1); break; + default: +#ifdef DEBUG_MATHEMU + printk("unknown FPop2: %03lx\n",(insn>>5)&0x1ff); +#endif + break; + } + } + + if (!type) { /* oops, didn't recognise that FPop */ +#ifdef DEBUG_MATHEMU + printk("attempt to emulate unrecognised FPop!\n"); +#endif + return 0; + } + + /* Decode the registers to be used */ + freg = (*pfsr >> 14) & 0xf; + + *pfsr &= ~0x1c000; /* clear the traptype bits */ + + freg = ((insn >> 14) & 0x1f); + switch (type & 0x3) { /* is rs1 single, double or quad? */ + case 3: + if (freg & 3) { /* quadwords must have bits 4&5 of the */ + /* encoded reg. number set to zero. */ + *pfsr |= (6 << 14); + return 0; /* simulate invalid_fp_register exception */ + } + /* fall through */ + case 2: + if (freg & 1) { /* doublewords must have bit 5 zeroed */ + *pfsr |= (6 << 14); + return 0; + } + } + rs1 = (argp)&fregs[freg]; + switch (type & 0x7) { + case 7: FP_UNPACK_QP (QA, rs1); break; + case 6: FP_UNPACK_DP (DA, rs1); break; + case 5: FP_UNPACK_SP (SA, rs1); break; + } + freg = (insn & 0x1f); + switch ((type >> 3) & 0x3) { /* same again for rs2 */ + case 3: + if (freg & 3) { /* quadwords must have bits 4&5 of the */ + /* encoded reg. number set to zero. */ + *pfsr |= (6 << 14); + return 0; /* simulate invalid_fp_register exception */ + } + /* fall through */ + case 2: + if (freg & 1) { /* doublewords must have bit 5 zeroed */ + *pfsr |= (6 << 14); + return 0; + } + } + rs2 = (argp)&fregs[freg]; + switch ((type >> 3) & 0x7) { + case 7: FP_UNPACK_QP (QB, rs2); break; + case 6: FP_UNPACK_DP (DB, rs2); break; + case 5: FP_UNPACK_SP (SB, rs2); break; + } + freg = ((insn >> 25) & 0x1f); + switch ((type >> 6) & 0x3) { /* and finally rd. This one's a bit different */ + case 0: /* dest is fcc. (this must be FCMPQ or FCMPEQ) */ + if (freg) { /* V8 has only one set of condition codes, so */ + /* anything but 0 in the rd field is an error */ + *pfsr |= (6 << 14); /* (should probably flag as invalid opcode */ + return 0; /* but SIGFPE will do :-> ) */ + } + break; + case 3: + if (freg & 3) { /* quadwords must have bits 4&5 of the */ + /* encoded reg. number set to zero. */ + *pfsr |= (6 << 14); + return 0; /* simulate invalid_fp_register exception */ + } + /* fall through */ + case 2: + if (freg & 1) { /* doublewords must have bit 5 zeroed */ + *pfsr |= (6 << 14); + return 0; + } + /* fall through */ + case 1: + rd = (void *)&fregs[freg]; + break; + } +#ifdef DEBUG_MATHEMU + printk("executing insn...\n"); +#endif + /* do the Right Thing */ + switch ((insn >> 5) & 0x1ff) { + /* + */ + case FADDS: FP_ADD_S (SR, SA, SB); break; + case FADDD: FP_ADD_D (DR, DA, DB); break; + case FADDQ: FP_ADD_Q (QR, QA, QB); break; + /* - */ + case FSUBS: FP_SUB_S (SR, SA, SB); break; + case FSUBD: FP_SUB_D (DR, DA, DB); break; + case FSUBQ: FP_SUB_Q (QR, QA, QB); break; + /* * */ + case FMULS: FP_MUL_S (SR, SA, SB); break; + case FSMULD: FP_CONV (D, S, 2, 1, DA, SA); + FP_CONV (D, S, 2, 1, DB, SB); + case FMULD: FP_MUL_D (DR, DA, DB); break; + case FDMULQ: FP_CONV (Q, D, 4, 2, QA, DA); + FP_CONV (Q, D, 4, 2, QB, DB); + case FMULQ: FP_MUL_Q (QR, QA, QB); break; + /* / */ + case FDIVS: FP_DIV_S (SR, SA, SB); break; + case FDIVD: FP_DIV_D (DR, DA, DB); break; + case FDIVQ: FP_DIV_Q (QR, QA, QB); break; + /* sqrt */ + case FSQRTS: FP_SQRT_S (SR, SB); break; + case FSQRTD: FP_SQRT_D (DR, DB); break; + case FSQRTQ: FP_SQRT_Q (QR, QB); break; + /* mov */ + case FMOVS: rd->s = rs2->s; break; + case FABSS: rd->s = rs2->s & 0x7fffffff; break; + case FNEGS: rd->s = rs2->s ^ 0x80000000; break; + /* float to int */ + case FSTOI: FP_TO_INT_S (IR, SB, 32, 1); break; + case FDTOI: FP_TO_INT_D (IR, DB, 32, 1); break; + case FQTOI: FP_TO_INT_Q (IR, QB, 32, 1); break; + /* int to float */ + case FITOS: IR = rs2->s; FP_FROM_INT_S (SR, IR, 32, int); break; + case FITOD: IR = rs2->s; FP_FROM_INT_D (DR, IR, 32, int); break; + case FITOQ: IR = rs2->s; FP_FROM_INT_Q (QR, IR, 32, int); break; + /* float to float */ + case FSTOD: FP_CONV (D, S, 2, 1, DR, SB); break; + case FSTOQ: FP_CONV (Q, S, 4, 1, QR, SB); break; + case FDTOQ: FP_CONV (Q, D, 4, 2, QR, DB); break; + case FDTOS: FP_CONV (S, D, 1, 2, SR, DB); break; + case FQTOS: FP_CONV (S, Q, 1, 4, SR, QB); break; + case FQTOD: FP_CONV (D, Q, 2, 4, DR, QB); break; + /* comparison */ + case FCMPS: + case FCMPES: + FP_CMP_S(IR, SB, SA, 3); + if (IR == 3 && + (((insn >> 5) & 0x1ff) == FCMPES || + FP_ISSIGNAN_S(SA) || + FP_ISSIGNAN_S(SB))) + FP_SET_EXCEPTION (FP_EX_INVALID); + break; + case FCMPD: + case FCMPED: + FP_CMP_D(IR, DB, DA, 3); + if (IR == 3 && + (((insn >> 5) & 0x1ff) == FCMPED || + FP_ISSIGNAN_D(DA) || + FP_ISSIGNAN_D(DB))) + FP_SET_EXCEPTION (FP_EX_INVALID); + break; + case FCMPQ: + case FCMPEQ: + FP_CMP_Q(IR, QB, QA, 3); + if (IR == 3 && + (((insn >> 5) & 0x1ff) == FCMPEQ || + FP_ISSIGNAN_Q(QA) || + FP_ISSIGNAN_Q(QB))) + FP_SET_EXCEPTION (FP_EX_INVALID); + } + if (!FP_INHIBIT_RESULTS) { + switch ((type >> 6) & 0x7) { + case 0: fsr = *pfsr; + if (IR == -1) IR = 2; + /* fcc is always fcc0 */ + fsr &= ~0xc00; fsr |= (IR << 10); break; + *pfsr = fsr; + break; + case 1: rd->s = IR; break; + case 5: FP_PACK_SP (rd, SR); break; + case 6: FP_PACK_DP (rd, DR); break; + case 7: FP_PACK_QP (rd, QR); break; + } + } + if (_fex == 0) + return 1; /* success! */ + return record_exception(pfsr, _fex); +} diff --git a/arch/sparc/math-emu/sfp-util.h b/arch/sparc/math-emu/sfp-util.h new file mode 100644 index 000000000000..d1b2aff3c259 --- /dev/null +++ b/arch/sparc/math-emu/sfp-util.h @@ -0,0 +1,115 @@ +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <asm/byteorder.h> + +#define add_ssaaaa(sh, sl, ah, al, bh, bl) \ + __asm__ ("addcc %r4,%5,%1\n\t" \ + "addx %r2,%3,%0\n" \ + : "=r" ((USItype)(sh)), \ + "=&r" ((USItype)(sl)) \ + : "%rJ" ((USItype)(ah)), \ + "rI" ((USItype)(bh)), \ + "%rJ" ((USItype)(al)), \ + "rI" ((USItype)(bl)) \ + : "cc") +#define sub_ddmmss(sh, sl, ah, al, bh, bl) \ + __asm__ ("subcc %r4,%5,%1\n\t" \ + "subx %r2,%3,%0\n" \ + : "=r" ((USItype)(sh)), \ + "=&r" ((USItype)(sl)) \ + : "rJ" ((USItype)(ah)), \ + "rI" ((USItype)(bh)), \ + "rJ" ((USItype)(al)), \ + "rI" ((USItype)(bl)) \ + : "cc") + +#define umul_ppmm(w1, w0, u, v) \ + __asm__ ("! Inlined umul_ppmm\n\t" \ + "wr %%g0,%2,%%y ! SPARC has 0-3 delay insn after a wr\n\t" \ + "sra %3,31,%%g2 ! Don't move this insn\n\t" \ + "and %2,%%g2,%%g2 ! Don't move this insn\n\t" \ + "andcc %%g0,0,%%g1 ! Don't move this insn\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,%3,%%g1\n\t" \ + "mulscc %%g1,0,%%g1\n\t" \ + "add %%g1,%%g2,%0\n\t" \ + "rd %%y,%1\n" \ + : "=r" ((USItype)(w1)), \ + "=r" ((USItype)(w0)) \ + : "%rI" ((USItype)(u)), \ + "r" ((USItype)(v)) \ + : "%g1", "%g2", "cc") + +/* It's quite necessary to add this much assembler for the sparc. + The default udiv_qrnnd (in C) is more than 10 times slower! */ +#define udiv_qrnnd(q, r, n1, n0, d) \ + __asm__ ("! Inlined udiv_qrnnd\n\t" \ + "mov 32,%%g1\n\t" \ + "subcc %1,%2,%%g0\n\t" \ + "1: bcs 5f\n\t" \ + "addxcc %0,%0,%0 ! shift n1n0 and a q-bit in lsb\n\t" \ + "sub %1,%2,%1 ! this kills msb of n\n\t" \ + "addx %1,%1,%1 ! so this can't give carry\n\t" \ + "subcc %%g1,1,%%g1\n\t" \ + "2: bne 1b\n\t" \ + "subcc %1,%2,%%g0\n\t" \ + "bcs 3f\n\t" \ + "addxcc %0,%0,%0 ! shift n1n0 and a q-bit in lsb\n\t" \ + "b 3f\n\t" \ + "sub %1,%2,%1 ! this kills msb of n\n\t" \ + "4: sub %1,%2,%1\n\t" \ + "5: addxcc %1,%1,%1\n\t" \ + "bcc 2b\n\t" \ + "subcc %%g1,1,%%g1\n\t" \ + "! Got carry from n. Subtract next step to cancel this carry.\n\t" \ + "bne 4b\n\t" \ + "addcc %0,%0,%0 ! shift n1n0 and a 0-bit in lsb\n\t" \ + "sub %1,%2,%1\n\t" \ + "3: xnor %0,0,%0\n\t" \ + "! End of inline udiv_qrnnd\n" \ + : "=&r" ((USItype)(q)), \ + "=&r" ((USItype)(r)) \ + : "r" ((USItype)(d)), \ + "1" ((USItype)(n1)), \ + "0" ((USItype)(n0)) : "%g1", "cc") +#define UDIV_NEEDS_NORMALIZATION 0 + +#define abort() \ + return 0 + +#ifdef __BIG_ENDIAN +#define __BYTE_ORDER __BIG_ENDIAN +#else +#define __BYTE_ORDER __LITTLE_ENDIAN +#endif diff --git a/arch/sparc/mm/Makefile b/arch/sparc/mm/Makefile new file mode 100644 index 000000000000..16eeba4b991a --- /dev/null +++ b/arch/sparc/mm/Makefile @@ -0,0 +1,23 @@ +# $Id: Makefile,v 1.38 2000/12/15 00:41:22 davem Exp $ +# Makefile for the linux Sparc-specific parts of the memory manager. +# + +EXTRA_AFLAGS := -ansi + +obj-y := fault.o init.o loadmmu.o generic.o extable.o btfixup.o + +ifeq ($(CONFIG_SUN4),y) +obj-y += nosrmmu.o +else +obj-y += srmmu.o iommu.o io-unit.o hypersparc.o viking.o tsunami.o swift.o +endif + +ifdef CONFIG_HIGHMEM +obj-y += highmem.o +endif + +ifdef CONFIG_SMP +obj-y += nosun4c.o +else +obj-y += sun4c.o +endif diff --git a/arch/sparc/mm/btfixup.c b/arch/sparc/mm/btfixup.c new file mode 100644 index 000000000000..f147a44c9780 --- /dev/null +++ b/arch/sparc/mm/btfixup.c @@ -0,0 +1,336 @@ +/* $Id: btfixup.c,v 1.10 2000/05/09 17:40:13 davem Exp $ + * btfixup.c: Boot time code fixup and relocator, so that + * we can get rid of most indirect calls to achieve single + * image sun4c and srmmu kernel. + * + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <asm/btfixup.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/oplib.h> +#include <asm/system.h> +#include <asm/cacheflush.h> + +#define BTFIXUP_OPTIMIZE_NOP +#define BTFIXUP_OPTIMIZE_OTHER + +extern char *srmmu_name; +static char version[] __initdata = "Boot time fixup v1.6. 4/Mar/98 Jakub Jelinek (jj@ultra.linux.cz). Patching kernel for "; +#ifdef CONFIG_SUN4 +static char str_sun4c[] __initdata = "sun4\n"; +#else +static char str_sun4c[] __initdata = "sun4c\n"; +#endif +static char str_srmmu[] __initdata = "srmmu[%s]/"; +static char str_iommu[] __initdata = "iommu\n"; +static char str_iounit[] __initdata = "io-unit\n"; + +static int visited __initdata = 0; +extern unsigned int ___btfixup_start[], ___btfixup_end[], __init_begin[], __init_end[], __init_text_end[]; +extern unsigned int _stext[], _end[], __start___ksymtab[], __stop___ksymtab[]; +static char wrong_f[] __initdata = "Trying to set f fixup %p to invalid function %08x\n"; +static char wrong_b[] __initdata = "Trying to set b fixup %p to invalid function %08x\n"; +static char wrong_s[] __initdata = "Trying to set s fixup %p to invalid value %08x\n"; +static char wrong_h[] __initdata = "Trying to set h fixup %p to invalid value %08x\n"; +static char wrong_a[] __initdata = "Trying to set a fixup %p to invalid value %08x\n"; +static char wrong[] __initdata = "Wrong address for %c fixup %p\n"; +static char insn_f[] __initdata = "Fixup f %p refers to weird instructions at %p[%08x,%08x]\n"; +static char insn_b[] __initdata = "Fixup b %p doesn't refer to a SETHI at %p[%08x]\n"; +static char insn_s[] __initdata = "Fixup s %p doesn't refer to an OR at %p[%08x]\n"; +static char insn_h[] __initdata = "Fixup h %p doesn't refer to a SETHI at %p[%08x]\n"; +static char insn_a[] __initdata = "Fixup a %p doesn't refer to a SETHI nor OR at %p[%08x]\n"; +static char insn_i[] __initdata = "Fixup i %p doesn't refer to a valid instruction at %p[%08x]\n"; +static char fca_und[] __initdata = "flush_cache_all undefined in btfixup()\n"; +static char wrong_setaddr[] __initdata = "Garbled CALL/INT patch at %p[%08x,%08x,%08x]=%08x\n"; + +#ifdef BTFIXUP_OPTIMIZE_OTHER +static void __init set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value) +{ + if (!fmangled) + *addr = value; + else { + unsigned int *q = (unsigned int *)q1; + if (*addr == 0x01000000) { + /* Noped */ + *q = value; + } else if (addr[-1] == *q) { + /* Moved */ + addr[-1] = value; + *q = value; + } else { + prom_printf(wrong_setaddr, addr-1, addr[-1], *addr, *q, value); + prom_halt(); + } + } +} +#else +static __inline__ void set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value) +{ + *addr = value; +} +#endif + +void __init btfixup(void) +{ + unsigned int *p, *q; + int type, count; + unsigned insn; + unsigned *addr; + int fmangled = 0; + void (*flush_cacheall)(void); + + if (!visited) { + visited++; + printk(version); + if (ARCH_SUN4C_SUN4) + printk(str_sun4c); + else { + printk(str_srmmu, srmmu_name); + if (sparc_cpu_model == sun4d) + printk(str_iounit); + else + printk(str_iommu); + } + } + for (p = ___btfixup_start; p < ___btfixup_end; ) { + count = p[2]; + q = p + 3; + switch (type = *(unsigned char *)p) { + case 'f': + count = p[3]; + q = p + 4; + if (((p[0] & 1) || p[1]) + && ((p[1] & 3) || (unsigned *)(p[1]) < _stext || (unsigned *)(p[1]) >= _end)) { + prom_printf(wrong_f, p, p[1]); + prom_halt(); + } + break; + case 'b': + if (p[1] < (unsigned long)__init_begin || p[1] >= (unsigned long)__init_text_end || (p[1] & 3)) { + prom_printf(wrong_b, p, p[1]); + prom_halt(); + } + break; + case 's': + if (p[1] + 0x1000 >= 0x2000) { + prom_printf(wrong_s, p, p[1]); + prom_halt(); + } + break; + case 'h': + if (p[1] & 0x3ff) { + prom_printf(wrong_h, p, p[1]); + prom_halt(); + } + break; + case 'a': + if (p[1] + 0x1000 >= 0x2000 && (p[1] & 0x3ff)) { + prom_printf(wrong_a, p, p[1]); + prom_halt(); + } + break; + } + if (p[0] & 1) { + p[0] &= ~1; + while (count) { + fmangled = 0; + addr = (unsigned *)*q; + if (addr < _stext || addr >= _end) { + prom_printf(wrong, type, p); + prom_halt(); + } + insn = *addr; +#ifdef BTFIXUP_OPTIMIZE_OTHER + if (type != 'f' && q[1]) { + insn = *(unsigned int *)q[1]; + if (!insn || insn == 1) + insn = *addr; + else + fmangled = 1; + } +#endif + switch (type) { + case 'f': /* CALL */ + if (addr >= __start___ksymtab && addr < __stop___ksymtab) { + *addr = p[1]; + break; + } else if (!q[1]) { + if ((insn & 0xc1c00000) == 0x01000000) { /* SETHI */ + *addr = (insn & 0xffc00000) | (p[1] >> 10); break; + } else if ((insn & 0xc1f82000) == 0x80102000) { /* OR X, %LO(i), Y */ + *addr = (insn & 0xffffe000) | (p[1] & 0x3ff); break; + } else if ((insn & 0xc0000000) != 0x40000000) { /* !CALL */ + bad_f: + prom_printf(insn_f, p, addr, insn, addr[1]); + prom_halt(); + } + } else if (q[1] != 1) + addr[1] = q[1]; + if (p[2] == BTFIXUPCALL_NORM) { + norm_f: + *addr = 0x40000000 | ((p[1] - (unsigned)addr) >> 2); + q[1] = 0; + break; + } +#ifndef BTFIXUP_OPTIMIZE_NOP + goto norm_f; +#else + if (!(addr[1] & 0x80000000)) { + if ((addr[1] & 0xc1c00000) != 0x01000000) /* !SETHI */ + goto bad_f; /* CALL, Bicc, FBfcc, CBccc are weird in delay slot, aren't they? */ + } else { + if ((addr[1] & 0x01800000) == 0x01800000) { + if ((addr[1] & 0x01f80000) == 0x01e80000) { + /* RESTORE */ + goto norm_f; /* It is dangerous to patch that */ + } + goto bad_f; + } + if ((addr[1] & 0xffffe003) == 0x9e03e000) { + /* ADD %O7, XX, %o7 */ + int displac = (addr[1] << 19); + + displac = (displac >> 21) + 2; + *addr = (0x10800000) + (displac & 0x3fffff); + q[1] = addr[1]; + addr[1] = p[2]; + break; + } + if ((addr[1] & 0x201f) == 0x200f || (addr[1] & 0x7c000) == 0x3c000) + goto norm_f; /* Someone is playing bad tricks with us: rs1 or rs2 is o7 */ + if ((addr[1] & 0x3e000000) == 0x1e000000) + goto norm_f; /* rd is %o7. We'd better take care. */ + } + if (p[2] == BTFIXUPCALL_NOP) { + *addr = 0x01000000; + q[1] = 1; + break; + } +#ifndef BTFIXUP_OPTIMIZE_OTHER + goto norm_f; +#else + if (addr[1] == 0x01000000) { /* NOP in the delay slot */ + q[1] = addr[1]; + *addr = p[2]; + break; + } + if ((addr[1] & 0xc0000000) != 0xc0000000) { + /* Not a memory operation */ + if ((addr[1] & 0x30000000) == 0x10000000) { + /* Ok, non-memory op with rd %oX */ + if ((addr[1] & 0x3e000000) == 0x1c000000) + goto bad_f; /* Aiee. Someone is playing strange %sp tricks */ + if ((addr[1] & 0x3e000000) > 0x12000000 || + ((addr[1] & 0x3e000000) == 0x12000000 && + p[2] != BTFIXUPCALL_STO1O0 && p[2] != BTFIXUPCALL_SWAPO0O1) || + ((p[2] & 0xffffe000) == BTFIXUPCALL_RETINT(0))) { + /* Nobody uses the result. We can nop it out. */ + *addr = p[2]; + q[1] = addr[1]; + addr[1] = 0x01000000; + break; + } + if ((addr[1] & 0xf1ffffe0) == 0x90100000) { + /* MOV %reg, %Ox */ + if ((addr[1] & 0x3e000000) == 0x10000000 && + (p[2] & 0x7c000) == 0x20000) { + /* Ok, it is call xx; mov reg, %o0 and call optimizes + to doing something on %o0. Patch the patch. */ + *addr = (p[2] & ~0x7c000) | ((addr[1] & 0x1f) << 14); + q[1] = addr[1]; + addr[1] = 0x01000000; + break; + } + if ((addr[1] & 0x3e000000) == 0x12000000 && + p[2] == BTFIXUPCALL_STO1O0) { + *addr = (p[2] & ~0x3e000000) | ((addr[1] & 0x1f) << 25); + q[1] = addr[1]; + addr[1] = 0x01000000; + break; + } + } + } + } + *addr = addr[1]; + q[1] = addr[1]; + addr[1] = p[2]; + break; +#endif /* BTFIXUP_OPTIMIZE_OTHER */ +#endif /* BTFIXUP_OPTIMIZE_NOP */ + case 'b': /* BLACKBOX */ + /* Has to be sethi i, xx */ + if ((insn & 0xc1c00000) != 0x01000000) { + prom_printf(insn_b, p, addr, insn); + prom_halt(); + } else { + void (*do_fixup)(unsigned *); + + do_fixup = (void (*)(unsigned *))p[1]; + do_fixup(addr); + } + break; + case 's': /* SIMM13 */ + /* Has to be or %g0, i, xx */ + if ((insn & 0xc1ffe000) != 0x80102000) { + prom_printf(insn_s, p, addr, insn); + prom_halt(); + } + set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x1fff)); + break; + case 'h': /* SETHI */ + /* Has to be sethi i, xx */ + if ((insn & 0xc1c00000) != 0x01000000) { + prom_printf(insn_h, p, addr, insn); + prom_halt(); + } + set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10)); + break; + case 'a': /* HALF */ + /* Has to be sethi i, xx or or %g0, i, xx */ + if ((insn & 0xc1c00000) != 0x01000000 && + (insn & 0xc1ffe000) != 0x80102000) { + prom_printf(insn_a, p, addr, insn); + prom_halt(); + } + if (p[1] & 0x3ff) + set_addr(addr, q[1], fmangled, + (insn & 0x3e000000) | 0x80102000 | (p[1] & 0x1fff)); + else + set_addr(addr, q[1], fmangled, + (insn & 0x3e000000) | 0x01000000 | (p[1] >> 10)); + break; + case 'i': /* INT */ + if ((insn & 0xc1c00000) == 0x01000000) /* %HI */ + set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10)); + else if ((insn & 0x80002000) == 0x80002000 && + (insn & 0x01800000) != 0x01800000) /* %LO */ + set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x3ff)); + else { + prom_printf(insn_i, p, addr, insn); + prom_halt(); + } + break; + } + count -= 2; + q += 2; + } + } else + p = q + count; + } +#ifdef CONFIG_SMP + flush_cacheall = (void (*)(void))BTFIXUPVAL_CALL(local_flush_cache_all); +#else + flush_cacheall = (void (*)(void))BTFIXUPVAL_CALL(flush_cache_all); +#endif + if (!flush_cacheall) { + prom_printf(fca_und); + prom_halt(); + } + (*flush_cacheall)(); +} diff --git a/arch/sparc/mm/extable.c b/arch/sparc/mm/extable.c new file mode 100644 index 000000000000..c9845c71f426 --- /dev/null +++ b/arch/sparc/mm/extable.c @@ -0,0 +1,77 @@ +/* + * linux/arch/sparc/mm/extable.c + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/uaccess.h> + +void sort_extable(struct exception_table_entry *start, + struct exception_table_entry *finish) +{ +} + +/* Caller knows they are in a range if ret->fixup == 0 */ +const struct exception_table_entry * +search_extable(const struct exception_table_entry *start, + const struct exception_table_entry *last, + unsigned long value) +{ + const struct exception_table_entry *walk; + + /* Single insn entries are encoded as: + * word 1: insn address + * word 2: fixup code address + * + * Range entries are encoded as: + * word 1: first insn address + * word 2: 0 + * word 3: last insn address + 4 bytes + * word 4: fixup code address + * + * See asm/uaccess.h for more details. + */ + + /* 1. Try to find an exact match. */ + for (walk = start; walk <= last; walk++) { + if (walk->fixup == 0) { + /* A range entry, skip both parts. */ + walk++; + continue; + } + + if (walk->insn == value) + return walk; + } + + /* 2. Try to find a range match. */ + for (walk = start; walk <= (last - 1); walk++) { + if (walk->fixup) + continue; + + if (walk[0].insn <= value && walk[1].insn > value) + return walk; + + walk++; + } + + return NULL; +} + +/* Special extable search, which handles ranges. Returns fixup */ +unsigned long search_extables_range(unsigned long addr, unsigned long *g2) +{ + const struct exception_table_entry *entry; + + entry = search_exception_tables(addr); + if (!entry) + return 0; + + /* Inside range? Fix g2 and return correct fixup */ + if (!entry->fixup) { + *g2 = (addr - entry->insn) / 4; + return (entry + 1)->fixup; + } + + return entry->fixup; +} diff --git a/arch/sparc/mm/fault.c b/arch/sparc/mm/fault.c new file mode 100644 index 000000000000..37f4107bae66 --- /dev/null +++ b/arch/sparc/mm/fault.c @@ -0,0 +1,596 @@ +/* $Id: fault.c,v 1.122 2001/11/17 07:19:26 davem Exp $ + * fault.c: Page fault handlers for the Sparc. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <asm/head.h> + +#include <linux/string.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/threads.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/module.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/memreg.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/smp.h> +#include <asm/traps.h> +#include <asm/kdebug.h> +#include <asm/uaccess.h> + +#define ELEMENTS(arr) (sizeof (arr)/sizeof (arr[0])) + +extern int prom_node_root; + +/* At boot time we determine these two values necessary for setting + * up the segment maps and page table entries (pte's). + */ + +int num_segmaps, num_contexts; +int invalid_segment; + +/* various Virtual Address Cache parameters we find at boot time... */ + +int vac_size, vac_linesize, vac_do_hw_vac_flushes; +int vac_entries_per_context, vac_entries_per_segment; +int vac_entries_per_page; + +/* Nice, simple, prom library does all the sweating for us. ;) */ +int prom_probe_memory (void) +{ + register struct linux_mlist_v0 *mlist; + register unsigned long bytes, base_paddr, tally; + register int i; + + i = 0; + mlist= *prom_meminfo()->v0_available; + bytes = tally = mlist->num_bytes; + base_paddr = (unsigned long) mlist->start_adr; + + sp_banks[0].base_addr = base_paddr; + sp_banks[0].num_bytes = bytes; + + while (mlist->theres_more != (void *) 0){ + i++; + mlist = mlist->theres_more; + bytes = mlist->num_bytes; + tally += bytes; + if (i > SPARC_PHYS_BANKS-1) { + printk ("The machine has more banks than " + "this kernel can support\n" + "Increase the SPARC_PHYS_BANKS " + "setting (currently %d)\n", + SPARC_PHYS_BANKS); + i = SPARC_PHYS_BANKS-1; + break; + } + + sp_banks[i].base_addr = (unsigned long) mlist->start_adr; + sp_banks[i].num_bytes = mlist->num_bytes; + } + + i++; + sp_banks[i].base_addr = 0xdeadbeef; + sp_banks[i].num_bytes = 0; + + /* Now mask all bank sizes on a page boundary, it is all we can + * use anyways. + */ + for(i=0; sp_banks[i].num_bytes != 0; i++) + sp_banks[i].num_bytes &= PAGE_MASK; + + return tally; +} + +/* Traverse the memory lists in the prom to see how much physical we + * have. + */ +unsigned long +probe_memory(void) +{ + int total; + + total = prom_probe_memory(); + + /* Oh man, much nicer, keep the dirt in promlib. */ + return total; +} + +extern void sun4c_complete_all_stores(void); + +/* Whee, a level 15 NMI interrupt memory error. Let's have fun... */ +asmlinkage void sparc_lvl15_nmi(struct pt_regs *regs, unsigned long serr, + unsigned long svaddr, unsigned long aerr, + unsigned long avaddr) +{ + sun4c_complete_all_stores(); + printk("FAULT: NMI received\n"); + printk("SREGS: Synchronous Error %08lx\n", serr); + printk(" Synchronous Vaddr %08lx\n", svaddr); + printk(" Asynchronous Error %08lx\n", aerr); + printk(" Asynchronous Vaddr %08lx\n", avaddr); + if (sun4c_memerr_reg) + printk(" Memory Parity Error %08lx\n", *sun4c_memerr_reg); + printk("REGISTER DUMP:\n"); + show_regs(regs); + prom_halt(); +} + +static void unhandled_fault(unsigned long, struct task_struct *, + struct pt_regs *) __attribute__ ((noreturn)); + +static void unhandled_fault(unsigned long address, struct task_struct *tsk, + struct pt_regs *regs) +{ + if((unsigned long) address < PAGE_SIZE) { + printk(KERN_ALERT + "Unable to handle kernel NULL pointer dereference\n"); + } else { + printk(KERN_ALERT "Unable to handle kernel paging request " + "at virtual address %08lx\n", address); + } + printk(KERN_ALERT "tsk->{mm,active_mm}->context = %08lx\n", + (tsk->mm ? tsk->mm->context : tsk->active_mm->context)); + printk(KERN_ALERT "tsk->{mm,active_mm}->pgd = %08lx\n", + (tsk->mm ? (unsigned long) tsk->mm->pgd : + (unsigned long) tsk->active_mm->pgd)); + die_if_kernel("Oops", regs); +} + +asmlinkage int lookup_fault(unsigned long pc, unsigned long ret_pc, + unsigned long address) +{ + struct pt_regs regs; + unsigned long g2; + unsigned int insn; + int i; + + i = search_extables_range(ret_pc, &g2); + switch (i) { + case 3: + /* load & store will be handled by fixup */ + return 3; + + case 1: + /* store will be handled by fixup, load will bump out */ + /* for _to_ macros */ + insn = *((unsigned int *) pc); + if ((insn >> 21) & 1) + return 1; + break; + + case 2: + /* load will be handled by fixup, store will bump out */ + /* for _from_ macros */ + insn = *((unsigned int *) pc); + if (!((insn >> 21) & 1) || ((insn>>19)&0x3f) == 15) + return 2; + break; + + default: + break; + }; + + memset(®s, 0, sizeof (regs)); + regs.pc = pc; + regs.npc = pc + 4; + __asm__ __volatile__( + "rd %%psr, %0\n\t" + "nop\n\t" + "nop\n\t" + "nop\n" : "=r" (regs.psr)); + unhandled_fault(address, current, ®s); + + /* Not reached */ + return 0; +} + +extern unsigned long safe_compute_effective_address(struct pt_regs *, + unsigned int); + +static unsigned long compute_si_addr(struct pt_regs *regs, int text_fault) +{ + unsigned int insn; + + if (text_fault) + return regs->pc; + + if (regs->psr & PSR_PS) { + insn = *(unsigned int *) regs->pc; + } else { + __get_user(insn, (unsigned int *) regs->pc); + } + + return safe_compute_effective_address(regs, insn); +} + +asmlinkage void do_sparc_fault(struct pt_regs *regs, int text_fault, int write, + unsigned long address) +{ + struct vm_area_struct *vma; + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + unsigned int fixup; + unsigned long g2; + siginfo_t info; + int from_user = !(regs->psr & PSR_PS); + + if(text_fault) + address = regs->pc; + + /* + * We fault-in kernel-space virtual memory on-demand. The + * 'reference' page table is init_mm.pgd. + * + * NOTE! We MUST NOT take any locks for this case. We may + * be in an interrupt or a critical region, and should + * only copy the information from the master page table, + * nothing more. + */ + if (!ARCH_SUN4C_SUN4 && address >= TASK_SIZE) + goto vmalloc_fault; + + info.si_code = SEGV_MAPERR; + + /* + * If we're in an interrupt or have no user + * context, we must not take the fault.. + */ + if (in_atomic() || !mm) + goto no_context; + + down_read(&mm->mmap_sem); + + /* + * The kernel referencing a bad kernel pointer can lock up + * a sun4c machine completely, so we must attempt recovery. + */ + if(!from_user && address >= PAGE_OFFSET) + goto bad_area; + + vma = find_vma(mm, address); + if(!vma) + goto bad_area; + if(vma->vm_start <= address) + goto good_area; + if(!(vma->vm_flags & VM_GROWSDOWN)) + goto bad_area; + if(expand_stack(vma, address)) + goto bad_area; + /* + * Ok, we have a good vm_area for this memory access, so + * we can handle it.. + */ +good_area: + info.si_code = SEGV_ACCERR; + if(write) { + if(!(vma->vm_flags & VM_WRITE)) + goto bad_area; + } else { + /* Allow reads even for write-only mappings */ + if(!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto bad_area; + } + + /* + * If for any reason at all we couldn't handle the fault, + * make sure we exit gracefully rather than endlessly redo + * the fault. + */ + switch (handle_mm_fault(mm, vma, address, write)) { + case VM_FAULT_SIGBUS: + goto do_sigbus; + case VM_FAULT_OOM: + goto out_of_memory; + case VM_FAULT_MAJOR: + current->maj_flt++; + break; + case VM_FAULT_MINOR: + default: + current->min_flt++; + break; + } + up_read(&mm->mmap_sem); + return; + + /* + * Something tried to access memory that isn't in our memory map.. + * Fix it, but check if it's kernel or user first.. + */ +bad_area: + up_read(&mm->mmap_sem); + +bad_area_nosemaphore: + /* User mode accesses just cause a SIGSEGV */ + if(from_user) { +#if 0 + printk("Fault whee %s [%d]: segfaults at %08lx pc=%08lx\n", + tsk->comm, tsk->pid, address, regs->pc); +#endif + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* info.si_code set above to make clear whether + this was a SEGV_MAPERR or SEGV_ACCERR fault. */ + info.si_addr = (void __user *)compute_si_addr(regs, text_fault); + info.si_trapno = 0; + force_sig_info (SIGSEGV, &info, tsk); + return; + } + + /* Is this in ex_table? */ +no_context: + g2 = regs->u_regs[UREG_G2]; + if (!from_user && (fixup = search_extables_range(regs->pc, &g2))) { + if (fixup > 10) { /* Values below are reserved for other things */ + extern const unsigned __memset_start[]; + extern const unsigned __memset_end[]; + extern const unsigned __csum_partial_copy_start[]; + extern const unsigned __csum_partial_copy_end[]; + +#ifdef DEBUG_EXCEPTIONS + printk("Exception: PC<%08lx> faddr<%08lx>\n", regs->pc, address); + printk("EX_TABLE: insn<%08lx> fixup<%08x> g2<%08lx>\n", + regs->pc, fixup, g2); +#endif + if ((regs->pc >= (unsigned long)__memset_start && + regs->pc < (unsigned long)__memset_end) || + (regs->pc >= (unsigned long)__csum_partial_copy_start && + regs->pc < (unsigned long)__csum_partial_copy_end)) { + regs->u_regs[UREG_I4] = address; + regs->u_regs[UREG_I5] = regs->pc; + } + regs->u_regs[UREG_G2] = g2; + regs->pc = fixup; + regs->npc = regs->pc + 4; + return; + } + } + + unhandled_fault (address, tsk, regs); + do_exit(SIGKILL); + +/* + * We ran out of memory, or some other thing happened to us that made + * us unable to handle the page fault gracefully. + */ +out_of_memory: + up_read(&mm->mmap_sem); + printk("VM: killing process %s\n", tsk->comm); + if (from_user) + do_exit(SIGKILL); + goto no_context; + +do_sigbus: + up_read(&mm->mmap_sem); + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRERR; + info.si_addr = (void __user *) compute_si_addr(regs, text_fault); + info.si_trapno = 0; + force_sig_info (SIGBUS, &info, tsk); + if (!from_user) + goto no_context; + +vmalloc_fault: + { + /* + * Synchronize this task's top level page-table + * with the 'reference' page table. + */ + int offset = pgd_index(address); + pgd_t *pgd, *pgd_k; + pmd_t *pmd, *pmd_k; + + pgd = tsk->active_mm->pgd + offset; + pgd_k = init_mm.pgd + offset; + + if (!pgd_present(*pgd)) { + if (!pgd_present(*pgd_k)) + goto bad_area_nosemaphore; + pgd_val(*pgd) = pgd_val(*pgd_k); + return; + } + + pmd = pmd_offset(pgd, address); + pmd_k = pmd_offset(pgd_k, address); + + if (pmd_present(*pmd) || !pmd_present(*pmd_k)) + goto bad_area_nosemaphore; + *pmd = *pmd_k; + return; + } +} + +asmlinkage void do_sun4c_fault(struct pt_regs *regs, int text_fault, int write, + unsigned long address) +{ + extern void sun4c_update_mmu_cache(struct vm_area_struct *, + unsigned long,pte_t); + extern pte_t *sun4c_pte_offset_kernel(pmd_t *,unsigned long); + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + pgd_t *pgdp; + pte_t *ptep; + + if (text_fault) { + address = regs->pc; + } else if (!write && + !(regs->psr & PSR_PS)) { + unsigned int insn, __user *ip; + + ip = (unsigned int __user *)regs->pc; + if (!get_user(insn, ip)) { + if ((insn & 0xc1680000) == 0xc0680000) + write = 1; + } + } + + if (!mm) { + /* We are oopsing. */ + do_sparc_fault(regs, text_fault, write, address); + BUG(); /* P3 Oops already, you bitch */ + } + + pgdp = pgd_offset(mm, address); + ptep = sun4c_pte_offset_kernel((pmd_t *) pgdp, address); + + if (pgd_val(*pgdp)) { + if (write) { + if ((pte_val(*ptep) & (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_PRESENT)) + == (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_PRESENT)) { + unsigned long flags; + + *ptep = __pte(pte_val(*ptep) | _SUN4C_PAGE_ACCESSED | + _SUN4C_PAGE_MODIFIED | + _SUN4C_PAGE_VALID | + _SUN4C_PAGE_DIRTY); + + local_irq_save(flags); + if (sun4c_get_segmap(address) != invalid_segment) { + sun4c_put_pte(address, pte_val(*ptep)); + local_irq_restore(flags); + return; + } + local_irq_restore(flags); + } + } else { + if ((pte_val(*ptep) & (_SUN4C_PAGE_READ|_SUN4C_PAGE_PRESENT)) + == (_SUN4C_PAGE_READ|_SUN4C_PAGE_PRESENT)) { + unsigned long flags; + + *ptep = __pte(pte_val(*ptep) | _SUN4C_PAGE_ACCESSED | + _SUN4C_PAGE_VALID); + + local_irq_save(flags); + if (sun4c_get_segmap(address) != invalid_segment) { + sun4c_put_pte(address, pte_val(*ptep)); + local_irq_restore(flags); + return; + } + local_irq_restore(flags); + } + } + } + + /* This conditional is 'interesting'. */ + if (pgd_val(*pgdp) && !(write && !(pte_val(*ptep) & _SUN4C_PAGE_WRITE)) + && (pte_val(*ptep) & _SUN4C_PAGE_VALID)) + /* Note: It is safe to not grab the MMAP semaphore here because + * we know that update_mmu_cache() will not sleep for + * any reason (at least not in the current implementation) + * and therefore there is no danger of another thread getting + * on the CPU and doing a shrink_mmap() on this vma. + */ + sun4c_update_mmu_cache (find_vma(current->mm, address), address, + *ptep); + else + do_sparc_fault(regs, text_fault, write, address); +} + +/* This always deals with user addresses. */ +inline void force_user_fault(unsigned long address, int write) +{ + struct vm_area_struct *vma; + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + siginfo_t info; + + info.si_code = SEGV_MAPERR; + +#if 0 + printk("wf<pid=%d,wr=%d,addr=%08lx>\n", + tsk->pid, write, address); +#endif + down_read(&mm->mmap_sem); + vma = find_vma(mm, address); + if(!vma) + goto bad_area; + if(vma->vm_start <= address) + goto good_area; + if(!(vma->vm_flags & VM_GROWSDOWN)) + goto bad_area; + if(expand_stack(vma, address)) + goto bad_area; +good_area: + info.si_code = SEGV_ACCERR; + if(write) { + if(!(vma->vm_flags & VM_WRITE)) + goto bad_area; + } else { + if(!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto bad_area; + } + switch (handle_mm_fault(mm, vma, address, write)) { + case VM_FAULT_SIGBUS: + case VM_FAULT_OOM: + goto do_sigbus; + } + up_read(&mm->mmap_sem); + return; +bad_area: + up_read(&mm->mmap_sem); +#if 0 + printk("Window whee %s [%d]: segfaults at %08lx\n", + tsk->comm, tsk->pid, address); +#endif + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* info.si_code set above to make clear whether + this was a SEGV_MAPERR or SEGV_ACCERR fault. */ + info.si_addr = (void __user *) address; + info.si_trapno = 0; + force_sig_info (SIGSEGV, &info, tsk); + return; + +do_sigbus: + up_read(&mm->mmap_sem); + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRERR; + info.si_addr = (void __user *) address; + info.si_trapno = 0; + force_sig_info (SIGBUS, &info, tsk); +} + +void window_overflow_fault(void) +{ + unsigned long sp; + + sp = current_thread_info()->rwbuf_stkptrs[0]; + if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) + force_user_fault(sp + 0x38, 1); + force_user_fault(sp, 1); +} + +void window_underflow_fault(unsigned long sp) +{ + if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) + force_user_fault(sp + 0x38, 0); + force_user_fault(sp, 0); +} + +void window_ret_fault(struct pt_regs *regs) +{ + unsigned long sp; + + sp = regs->u_regs[UREG_FP]; + if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) + force_user_fault(sp + 0x38, 0); + force_user_fault(sp, 0); +} diff --git a/arch/sparc/mm/generic.c b/arch/sparc/mm/generic.c new file mode 100644 index 000000000000..db27eee3bda1 --- /dev/null +++ b/arch/sparc/mm/generic.c @@ -0,0 +1,154 @@ +/* $Id: generic.c,v 1.14 2001/12/21 04:56:15 davem Exp $ + * generic.c: Generic Sparc mm routines that are not dependent upon + * MMU type but are Sparc specific. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/pagemap.h> + +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +static inline void forget_pte(pte_t page) +{ +#if 0 /* old 2.4 code */ + if (pte_none(page)) + return; + if (pte_present(page)) { + unsigned long pfn = pte_pfn(page); + struct page *ptpage; + if (!pfn_valid(pfn)) + return; + ptpage = pfn_to_page(pfn); + if (PageReserved(ptpage)) + return; + page_cache_release(ptpage); + return; + } + swap_free(pte_to_swp_entry(page)); +#else + if (!pte_none(page)) { + printk("forget_pte: old mapping existed!\n"); + BUG(); + } +#endif +} + +/* Remap IO memory, the same way as remap_pfn_range(), but use + * the obio memory space. + * + * They use a pgprot that sets PAGE_IO and does not check the + * mem_map table as this is independent of normal memory. + */ +static inline void io_remap_pte_range(struct mm_struct *mm, pte_t * pte, unsigned long address, unsigned long size, + unsigned long offset, pgprot_t prot, int space) +{ + unsigned long end; + + address &= ~PMD_MASK; + end = address + size; + if (end > PMD_SIZE) + end = PMD_SIZE; + do { + pte_t oldpage = *pte; + pte_clear(mm, address, pte); + set_pte(pte, mk_pte_io(offset, prot, space)); + forget_pte(oldpage); + address += PAGE_SIZE; + offset += PAGE_SIZE; + pte++; + } while (address < end); +} + +static inline int io_remap_pmd_range(struct mm_struct *mm, pmd_t * pmd, unsigned long address, unsigned long size, + unsigned long offset, pgprot_t prot, int space) +{ + unsigned long end; + + address &= ~PGDIR_MASK; + end = address + size; + if (end > PGDIR_SIZE) + end = PGDIR_SIZE; + offset -= address; + do { + pte_t * pte = pte_alloc_map(mm, pmd, address); + if (!pte) + return -ENOMEM; + io_remap_pte_range(mm, pte, address, end - address, address + offset, prot, space); + address = (address + PMD_SIZE) & PMD_MASK; + pmd++; + } while (address < end); + return 0; +} + +int io_remap_page_range(struct vm_area_struct *vma, unsigned long from, unsigned long offset, unsigned long size, pgprot_t prot, int space) +{ + int error = 0; + pgd_t * dir; + unsigned long beg = from; + unsigned long end = from + size; + struct mm_struct *mm = vma->vm_mm; + + prot = __pgprot(pg_iobits); + offset -= from; + dir = pgd_offset(mm, from); + flush_cache_range(vma, beg, end); + + spin_lock(&mm->page_table_lock); + while (from < end) { + pmd_t *pmd = pmd_alloc(current->mm, dir, from); + error = -ENOMEM; + if (!pmd) + break; + error = io_remap_pmd_range(mm, pmd, from, end - from, offset + from, prot, space); + if (error) + break; + from = (from + PGDIR_SIZE) & PGDIR_MASK; + dir++; + } + spin_unlock(&mm->page_table_lock); + + flush_tlb_range(vma, beg, end); + return error; +} + +int io_remap_pfn_range(struct vm_area_struct *vma, unsigned long from, + unsigned long pfn, unsigned long size, pgprot_t prot) +{ + int error = 0; + pgd_t * dir; + unsigned long beg = from; + unsigned long end = from + size; + struct mm_struct *mm = vma->vm_mm; + int space = GET_IOSPACE(pfn); + unsigned long offset = GET_PFN(pfn) << PAGE_SHIFT; + + prot = __pgprot(pg_iobits); + offset -= from; + dir = pgd_offset(mm, from); + flush_cache_range(vma, beg, end); + + spin_lock(&mm->page_table_lock); + while (from < end) { + pmd_t *pmd = pmd_alloc(current->mm, dir, from); + error = -ENOMEM; + if (!pmd) + break; + error = io_remap_pmd_range(mm, pmd, from, end - from, offset + from, prot, space); + if (error) + break; + from = (from + PGDIR_SIZE) & PGDIR_MASK; + dir++; + } + spin_unlock(&mm->page_table_lock); + + flush_tlb_range(vma, beg, end); + return error; +} diff --git a/arch/sparc/mm/highmem.c b/arch/sparc/mm/highmem.c new file mode 100644 index 000000000000..4d8ed9c65182 --- /dev/null +++ b/arch/sparc/mm/highmem.c @@ -0,0 +1,120 @@ +/* + * highmem.c: virtual kernel memory mappings for high memory + * + * Provides kernel-static versions of atomic kmap functions originally + * found as inlines in include/asm-sparc/highmem.h. These became + * needed as kmap_atomic() and kunmap_atomic() started getting + * called from within modules. + * -- Tomas Szepe <szepe@pinerecords.com>, September 2002 + * + * But kmap_atomic() and kunmap_atomic() cannot be inlined in + * modules because they are loaded with btfixup-ped functions. + */ + +/* + * The use of kmap_atomic/kunmap_atomic is discouraged - kmap/kunmap + * gives a more generic (and caching) interface. But kmap_atomic can + * be used in IRQ contexts, so in some (very limited) cases we need it. + * + * XXX This is an old text. Actually, it's good to use atomic kmaps, + * provided you remember that they are atomic and not try to sleep + * with a kmap taken, much like a spinlock. Non-atomic kmaps are + * shared by CPUs, and so precious, and establishing them requires IPI. + * Atomic kmaps are lightweight and we may have NCPUS more of them. + */ +#include <linux/mm.h> +#include <linux/highmem.h> +#include <asm/pgalloc.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/fixmap.h> + +void *kmap_atomic(struct page *page, enum km_type type) +{ + unsigned long idx; + unsigned long vaddr; + + /* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */ + inc_preempt_count(); + if (!PageHighMem(page)) + return page_address(page); + + idx = type + KM_TYPE_NR*smp_processor_id(); + vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); + +/* XXX Fix - Anton */ +#if 0 + __flush_cache_one(vaddr); +#else + flush_cache_all(); +#endif + +#ifdef CONFIG_DEBUG_HIGHMEM + BUG_ON(!pte_none(*(kmap_pte-idx))); +#endif + set_pte(kmap_pte-idx, mk_pte(page, kmap_prot)); +/* XXX Fix - Anton */ +#if 0 + __flush_tlb_one(vaddr); +#else + flush_tlb_all(); +#endif + + return (void*) vaddr; +} + +void kunmap_atomic(void *kvaddr, enum km_type type) +{ +#ifdef CONFIG_DEBUG_HIGHMEM + unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK; + unsigned long idx = type + KM_TYPE_NR*smp_processor_id(); + + if (vaddr < FIXADDR_START) { // FIXME + dec_preempt_count(); + preempt_check_resched(); + return; + } + + BUG_ON(vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx)); + +/* XXX Fix - Anton */ +#if 0 + __flush_cache_one(vaddr); +#else + flush_cache_all(); +#endif + + /* + * force other mappings to Oops if they'll try to access + * this pte without first remap it + */ + pte_clear(&init_mm, vaddr, kmap_pte-idx); +/* XXX Fix - Anton */ +#if 0 + __flush_tlb_one(vaddr); +#else + flush_tlb_all(); +#endif +#endif + + dec_preempt_count(); + preempt_check_resched(); +} + +/* We may be fed a pagetable here by ptep_to_xxx and others. */ +struct page *kmap_atomic_to_page(void *ptr) +{ + unsigned long idx, vaddr = (unsigned long)ptr; + pte_t *pte; + + if (vaddr < SRMMU_NOCACHE_VADDR) + return virt_to_page(ptr); + if (vaddr < PKMAP_BASE) + return pfn_to_page(__nocache_pa(vaddr) >> PAGE_SHIFT); + BUG_ON(vaddr < FIXADDR_START); + BUG_ON(vaddr > FIXADDR_TOP); + + idx = virt_to_fix(vaddr); + pte = kmap_pte - (idx - FIX_KMAP_BEGIN); + return pte_page(*pte); +} diff --git a/arch/sparc/mm/hypersparc.S b/arch/sparc/mm/hypersparc.S new file mode 100644 index 000000000000..54b8e764b042 --- /dev/null +++ b/arch/sparc/mm/hypersparc.S @@ -0,0 +1,413 @@ +/* $Id: hypersparc.S,v 1.18 2001/12/21 04:56:15 davem Exp $ + * hypersparc.S: High speed Hypersparc mmu/cache operations. + * + * Copyright (C) 1997 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/asm_offsets.h> +#include <asm/asi.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> +#include <linux/config.h> +#include <linux/init.h> + + .text + .align 4 + + .globl hypersparc_flush_cache_all, hypersparc_flush_cache_mm + .globl hypersparc_flush_cache_range, hypersparc_flush_cache_page + .globl hypersparc_flush_page_to_ram + .globl hypersparc_flush_page_for_dma, hypersparc_flush_sig_insns + .globl hypersparc_flush_tlb_all, hypersparc_flush_tlb_mm + .globl hypersparc_flush_tlb_range, hypersparc_flush_tlb_page + +hypersparc_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) + sethi %hi(vac_cache_size), %g4 + ld [%g4 + %lo(vac_cache_size)], %g5 + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %g2 +1: + subcc %g5, %g2, %g5 ! hyper_flush_unconditional_combined + bne 1b + sta %g0, [%g5] ASI_M_FLUSH_CTX + retl + sta %g0, [%g0] ASI_M_FLUSH_IWHOLE ! hyper_flush_whole_icache + + /* We expand the window flush to get maximum performance. */ +hypersparc_flush_cache_mm: +#ifndef CONFIG_SMP + ld [%o0 + AOFF_mm_context], %g1 + cmp %g1, -1 + be hypersparc_flush_cache_mm_out +#endif + WINDOW_FLUSH(%g4, %g5) + + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o1 + sethi %hi(vac_cache_size), %g2 + ld [%g2 + %lo(vac_cache_size)], %o0 + add %o1, %o1, %g1 + add %o1, %g1, %g2 + add %o1, %g2, %g3 + add %o1, %g3, %g4 + add %o1, %g4, %g5 + add %o1, %g5, %o4 + add %o1, %o4, %o5 + + /* BLAMMO! */ +1: + subcc %o0, %o5, %o0 ! hyper_flush_cache_user + sta %g0, [%o0 + %g0] ASI_M_FLUSH_USER + sta %g0, [%o0 + %o1] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g1] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g2] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g3] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g4] ASI_M_FLUSH_USER + sta %g0, [%o0 + %g5] ASI_M_FLUSH_USER + bne 1b + sta %g0, [%o0 + %o4] ASI_M_FLUSH_USER +hypersparc_flush_cache_mm_out: + retl + nop + + /* The things we do for performance... */ +hypersparc_flush_cache_range: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +#ifndef CONFIG_SMP + ld [%o0 + AOFF_mm_context], %g1 + cmp %g1, -1 + be hypersparc_flush_cache_range_out +#endif + WINDOW_FLUSH(%g4, %g5) + + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o4 + sethi %hi(vac_cache_size), %g2 + ld [%g2 + %lo(vac_cache_size)], %o3 + + /* Here comes the fun part... */ + add %o2, (PAGE_SIZE - 1), %o2 + andn %o1, (PAGE_SIZE - 1), %o1 + add %o4, %o4, %o5 + andn %o2, (PAGE_SIZE - 1), %o2 + add %o4, %o5, %g1 + sub %o2, %o1, %g4 + add %o4, %g1, %g2 + sll %o3, 2, %g5 + add %o4, %g2, %g3 + cmp %g4, %g5 + add %o4, %g3, %g4 + blu 0f + add %o4, %g4, %g5 + add %o4, %g5, %g7 + + /* Flush entire user space, believe it or not this is quicker + * than page at a time flushings for range > (cache_size<<2). + */ +1: + subcc %o3, %g7, %o3 + sta %g0, [%o3 + %g0] ASI_M_FLUSH_USER + sta %g0, [%o3 + %o4] ASI_M_FLUSH_USER + sta %g0, [%o3 + %o5] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g1] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g2] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g3] ASI_M_FLUSH_USER + sta %g0, [%o3 + %g4] ASI_M_FLUSH_USER + bne 1b + sta %g0, [%o3 + %g5] ASI_M_FLUSH_USER + retl + nop + + /* Below our threshold, flush one page at a time. */ +0: + ld [%o0 + AOFF_mm_context], %o0 + mov SRMMU_CTX_REG, %g7 + lda [%g7] ASI_M_MMUREGS, %o3 + sta %o0, [%g7] ASI_M_MMUREGS + add %o2, -PAGE_SIZE, %o0 +1: + or %o0, 0x400, %g7 + lda [%g7] ASI_M_FLUSH_PROBE, %g7 + orcc %g7, 0, %g0 + be,a 3f + mov %o0, %o2 + add %o4, %g5, %g7 +2: + sub %o2, %g7, %o2 + sta %g0, [%o2 + %g0] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %g1] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %g2] ASI_M_FLUSH_PAGE + sta %g0, [%o2 + %g3] ASI_M_FLUSH_PAGE + andcc %o2, 0xffc, %g0 + sta %g0, [%o2 + %g4] ASI_M_FLUSH_PAGE + bne 2b + sta %g0, [%o2 + %g5] ASI_M_FLUSH_PAGE +3: + cmp %o2, %o1 + bne 1b + add %o2, -PAGE_SIZE, %o0 + mov SRMMU_FAULT_STATUS, %g5 + lda [%g5] ASI_M_MMUREGS, %g0 + mov SRMMU_CTX_REG, %g7 + sta %o3, [%g7] ASI_M_MMUREGS +hypersparc_flush_cache_range_out: + retl + nop + + /* HyperSparc requires a valid mapping where we are about to flush + * in order to check for a physical tag match during the flush. + */ + /* Verified, my ass... */ +hypersparc_flush_cache_page: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ + ld [%o0 + AOFF_mm_context], %g2 +#ifndef CONFIG_SMP + cmp %g2, -1 + be hypersparc_flush_cache_page_out +#endif + WINDOW_FLUSH(%g4, %g5) + + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o4 + mov SRMMU_CTX_REG, %o3 + andn %o1, (PAGE_SIZE - 1), %o1 + lda [%o3] ASI_M_MMUREGS, %o2 + sta %g2, [%o3] ASI_M_MMUREGS + or %o1, 0x400, %o5 + lda [%o5] ASI_M_FLUSH_PROBE, %g1 + orcc %g0, %g1, %g0 + be 2f + add %o4, %o4, %o5 + sub %o1, -PAGE_SIZE, %o1 + add %o4, %o5, %g1 + add %o4, %g1, %g2 + add %o4, %g2, %g3 + add %o4, %g3, %g4 + add %o4, %g4, %g5 + add %o4, %g5, %g7 + + /* BLAMMO! */ +1: + sub %o1, %g7, %o1 + sta %g0, [%o1 + %g0] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g1] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g2] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g3] ASI_M_FLUSH_PAGE + andcc %o1, 0xffc, %g0 + sta %g0, [%o1 + %g4] ASI_M_FLUSH_PAGE + bne 1b + sta %g0, [%o1 + %g5] ASI_M_FLUSH_PAGE +2: + mov SRMMU_FAULT_STATUS, %g7 + mov SRMMU_CTX_REG, %g4 + lda [%g7] ASI_M_MMUREGS, %g0 + sta %o2, [%g4] ASI_M_MMUREGS +hypersparc_flush_cache_page_out: + retl + nop + +hypersparc_flush_sig_insns: + flush %o1 + retl + flush %o1 + 4 + + /* HyperSparc is copy-back. */ +hypersparc_flush_page_to_ram: + sethi %hi(vac_line_size), %g1 + ld [%g1 + %lo(vac_line_size)], %o4 + andn %o0, (PAGE_SIZE - 1), %o0 + add %o4, %o4, %o5 + or %o0, 0x400, %g7 + lda [%g7] ASI_M_FLUSH_PROBE, %g5 + add %o4, %o5, %g1 + orcc %g5, 0, %g0 + be 2f + add %o4, %g1, %g2 + add %o4, %g2, %g3 + sub %o0, -PAGE_SIZE, %o0 + add %o4, %g3, %g4 + add %o4, %g4, %g5 + add %o4, %g5, %g7 + + /* BLAMMO! */ +1: + sub %o0, %g7, %o0 + sta %g0, [%o0 + %g0] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %g1] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %g2] ASI_M_FLUSH_PAGE + sta %g0, [%o0 + %g3] ASI_M_FLUSH_PAGE + andcc %o0, 0xffc, %g0 + sta %g0, [%o0 + %g4] ASI_M_FLUSH_PAGE + bne 1b + sta %g0, [%o0 + %g5] ASI_M_FLUSH_PAGE +2: + mov SRMMU_FAULT_STATUS, %g1 + retl + lda [%g1] ASI_M_MMUREGS, %g0 + + /* HyperSparc is IO cache coherent. */ +hypersparc_flush_page_for_dma: + retl + nop + + /* It was noted that at boot time a TLB flush all in a delay slot + * can deliver an illegal instruction to the processor if the timing + * is just right... + */ +hypersparc_flush_tlb_all: + mov 0x400, %g1 + sta %g0, [%g1] ASI_M_FLUSH_PROBE + retl + nop + +hypersparc_flush_tlb_mm: + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o1 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o1, -1 + be hypersparc_flush_tlb_mm_out +#endif + mov 0x300, %g2 + sta %o1, [%g1] ASI_M_MMUREGS + sta %g0, [%g2] ASI_M_FLUSH_PROBE +hypersparc_flush_tlb_mm_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + +hypersparc_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o3, -1 + be hypersparc_flush_tlb_range_out +#endif + sethi %hi(~((1 << SRMMU_PGDIR_SHIFT) - 1)), %o4 + sta %o3, [%g1] ASI_M_MMUREGS + and %o1, %o4, %o1 + add %o1, 0x200, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +1: + sub %o1, %o4, %o1 + cmp %o1, %o2 + blu,a 1b + sta %g0, [%o1] ASI_M_FLUSH_PROBE +hypersparc_flush_tlb_range_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + +hypersparc_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + andn %o1, (PAGE_SIZE - 1), %o1 +#ifndef CONFIG_SMP + cmp %o3, -1 + be hypersparc_flush_tlb_page_out +#endif + lda [%g1] ASI_M_MMUREGS, %g5 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE +hypersparc_flush_tlb_page_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + + __INIT + + /* High speed page clear/copy. */ +hypersparc_bzero_1page: +/* NOTE: This routine has to be shorter than 40insns --jj */ + clr %g1 + mov 32, %g2 + mov 64, %g3 + mov 96, %g4 + mov 128, %g5 + mov 160, %g7 + mov 192, %o2 + mov 224, %o3 + mov 16, %o1 +1: + stda %g0, [%o0 + %g0] ASI_M_BFILL + stda %g0, [%o0 + %g2] ASI_M_BFILL + stda %g0, [%o0 + %g3] ASI_M_BFILL + stda %g0, [%o0 + %g4] ASI_M_BFILL + stda %g0, [%o0 + %g5] ASI_M_BFILL + stda %g0, [%o0 + %g7] ASI_M_BFILL + stda %g0, [%o0 + %o2] ASI_M_BFILL + stda %g0, [%o0 + %o3] ASI_M_BFILL + subcc %o1, 1, %o1 + bne 1b + add %o0, 256, %o0 + + retl + nop + +hypersparc_copy_1page: +/* NOTE: This routine has to be shorter than 70insns --jj */ + sub %o1, %o0, %o2 ! difference + mov 16, %g1 +1: + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + add %o0, 32, %o0 + sta %o0, [%o0 + %o2] ASI_M_BCOPY + subcc %g1, 1, %g1 + bne 1b + add %o0, 32, %o0 + + retl + nop + + .globl hypersparc_setup_blockops +hypersparc_setup_blockops: + sethi %hi(bzero_1page), %o0 + or %o0, %lo(bzero_1page), %o0 + sethi %hi(hypersparc_bzero_1page), %o1 + or %o1, %lo(hypersparc_bzero_1page), %o1 + sethi %hi(hypersparc_copy_1page), %o2 + or %o2, %lo(hypersparc_copy_1page), %o2 + ld [%o1], %o4 +1: + add %o1, 4, %o1 + st %o4, [%o0] + add %o0, 4, %o0 + cmp %o1, %o2 + bne 1b + ld [%o1], %o4 + sethi %hi(__copy_1page), %o0 + or %o0, %lo(__copy_1page), %o0 + sethi %hi(hypersparc_setup_blockops), %o2 + or %o2, %lo(hypersparc_setup_blockops), %o2 + ld [%o1], %o4 +1: + add %o1, 4, %o1 + st %o4, [%o0] + add %o0, 4, %o0 + cmp %o1, %o2 + bne 1b + ld [%o1], %o4 + sta %g0, [%g0] ASI_M_FLUSH_IWHOLE + retl + nop diff --git a/arch/sparc/mm/init.c b/arch/sparc/mm/init.c new file mode 100644 index 000000000000..a2dea69b2f07 --- /dev/null +++ b/arch/sparc/mm/init.c @@ -0,0 +1,515 @@ +/* $Id: init.c,v 1.103 2001/11/19 19:03:08 davem Exp $ + * linux/arch/sparc/mm/init.c + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 2000 Anton Blanchard (anton@samba.org) + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/initrd.h> +#include <linux/init.h> +#include <linux/highmem.h> +#include <linux/bootmem.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/vac-ops.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/vaddrs.h> +#include <asm/pgalloc.h> /* bug in asm-generic/tlb.h: check_pgt_cache */ +#include <asm/tlb.h> + +DEFINE_PER_CPU(struct mmu_gather, mmu_gathers); + +unsigned long *sparc_valid_addr_bitmap; + +unsigned long phys_base; +unsigned long pfn_base; + +unsigned long page_kernel; + +struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS+1]; +unsigned long sparc_unmapped_base; + +struct pgtable_cache_struct pgt_quicklists; + +/* References to section boundaries */ +extern char __init_begin, __init_end, _start, _end, etext , edata; + +/* Initial ramdisk setup */ +extern unsigned int sparc_ramdisk_image; +extern unsigned int sparc_ramdisk_size; + +unsigned long highstart_pfn, highend_pfn; + +pte_t *kmap_pte; +pgprot_t kmap_prot; + +#define kmap_get_fixmap_pte(vaddr) \ + pte_offset_kernel(pmd_offset(pgd_offset_k(vaddr), (vaddr)), (vaddr)) + +void __init kmap_init(void) +{ + /* cache the first kmap pte */ + kmap_pte = kmap_get_fixmap_pte(__fix_to_virt(FIX_KMAP_BEGIN)); + kmap_prot = __pgprot(SRMMU_ET_PTE | SRMMU_PRIV | SRMMU_CACHE); +} + +void show_mem(void) +{ + printk("Mem-info:\n"); + show_free_areas(); + printk("Free swap: %6ldkB\n", + nr_swap_pages << (PAGE_SHIFT-10)); + printk("%ld pages of RAM\n", totalram_pages); + printk("%d free pages\n", nr_free_pages()); +#if 0 /* undefined pgtable_cache_size, pgd_cache_size */ + printk("%ld pages in page table cache\n",pgtable_cache_size); +#ifndef CONFIG_SMP + if (sparc_cpu_model == sun4m || sparc_cpu_model == sun4d) + printk("%ld entries in page dir cache\n",pgd_cache_size); +#endif +#endif +} + +void __init sparc_context_init(int numctx) +{ + int ctx; + + ctx_list_pool = __alloc_bootmem(numctx * sizeof(struct ctx_list), SMP_CACHE_BYTES, 0UL); + + for(ctx = 0; ctx < numctx; ctx++) { + struct ctx_list *clist; + + clist = (ctx_list_pool + ctx); + clist->ctx_number = ctx; + clist->ctx_mm = NULL; + } + ctx_free.next = ctx_free.prev = &ctx_free; + ctx_used.next = ctx_used.prev = &ctx_used; + for(ctx = 0; ctx < numctx; ctx++) + add_to_free_ctxlist(ctx_list_pool + ctx); +} + +extern unsigned long cmdline_memory_size; +unsigned long last_valid_pfn; + +unsigned long calc_highpages(void) +{ + int i; + int nr = 0; + + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + unsigned long start_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + unsigned long end_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + + if (end_pfn <= max_low_pfn) + continue; + + if (start_pfn < max_low_pfn) + start_pfn = max_low_pfn; + + nr += end_pfn - start_pfn; + } + + return nr; +} + +unsigned long calc_max_low_pfn(void) +{ + int i; + unsigned long tmp = pfn_base + (SRMMU_MAXMEM >> PAGE_SHIFT); + unsigned long curr_pfn, last_pfn; + + last_pfn = (sp_banks[0].base_addr + sp_banks[0].num_bytes) >> PAGE_SHIFT; + for (i = 1; sp_banks[i].num_bytes != 0; i++) { + curr_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + + if (curr_pfn >= tmp) { + if (last_pfn < tmp) + tmp = last_pfn; + break; + } + + last_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + } + + return tmp; +} + +unsigned long __init bootmem_init(unsigned long *pages_avail) +{ + unsigned long bootmap_size, start_pfn; + unsigned long end_of_phys_memory = 0UL; + unsigned long bootmap_pfn, bytes_avail, size; + int i; + + bytes_avail = 0UL; + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + end_of_phys_memory = sp_banks[i].base_addr + + sp_banks[i].num_bytes; + bytes_avail += sp_banks[i].num_bytes; + if (cmdline_memory_size) { + if (bytes_avail > cmdline_memory_size) { + unsigned long slack = bytes_avail - cmdline_memory_size; + + bytes_avail -= slack; + end_of_phys_memory -= slack; + + sp_banks[i].num_bytes -= slack; + if (sp_banks[i].num_bytes == 0) { + sp_banks[i].base_addr = 0xdeadbeef; + } else { + sp_banks[i+1].num_bytes = 0; + sp_banks[i+1].base_addr = 0xdeadbeef; + } + break; + } + } + } + + /* Start with page aligned address of last symbol in kernel + * image. + */ + start_pfn = (unsigned long)__pa(PAGE_ALIGN((unsigned long) &_end)); + + /* Now shift down to get the real physical page frame number. */ + start_pfn >>= PAGE_SHIFT; + + bootmap_pfn = start_pfn; + + max_pfn = end_of_phys_memory >> PAGE_SHIFT; + + max_low_pfn = max_pfn; + highstart_pfn = highend_pfn = max_pfn; + + if (max_low_pfn > pfn_base + (SRMMU_MAXMEM >> PAGE_SHIFT)) { + highstart_pfn = pfn_base + (SRMMU_MAXMEM >> PAGE_SHIFT); + max_low_pfn = calc_max_low_pfn(); + printk(KERN_NOTICE "%ldMB HIGHMEM available.\n", + calc_highpages() >> (20 - PAGE_SHIFT)); + } + +#ifdef CONFIG_BLK_DEV_INITRD + /* Now have to check initial ramdisk, so that bootmap does not overwrite it */ + if (sparc_ramdisk_image) { + if (sparc_ramdisk_image >= (unsigned long)&_end - 2 * PAGE_SIZE) + sparc_ramdisk_image -= KERNBASE; + initrd_start = sparc_ramdisk_image + phys_base; + initrd_end = initrd_start + sparc_ramdisk_size; + if (initrd_end > end_of_phys_memory) { + printk(KERN_CRIT "initrd extends beyond end of memory " + "(0x%016lx > 0x%016lx)\ndisabling initrd\n", + initrd_end, end_of_phys_memory); + initrd_start = 0; + } + if (initrd_start) { + if (initrd_start >= (start_pfn << PAGE_SHIFT) && + initrd_start < (start_pfn << PAGE_SHIFT) + 2 * PAGE_SIZE) + bootmap_pfn = PAGE_ALIGN (initrd_end) >> PAGE_SHIFT; + } + } +#endif + /* Initialize the boot-time allocator. */ + bootmap_size = init_bootmem_node(NODE_DATA(0), bootmap_pfn, pfn_base, + max_low_pfn); + + /* Now register the available physical memory with the + * allocator. + */ + *pages_avail = 0; + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + unsigned long curr_pfn, last_pfn; + + curr_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + if (curr_pfn >= max_low_pfn) + break; + + last_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + if (last_pfn > max_low_pfn) + last_pfn = max_low_pfn; + + /* + * .. finally, did all the rounding and playing + * around just make the area go away? + */ + if (last_pfn <= curr_pfn) + continue; + + size = (last_pfn - curr_pfn) << PAGE_SHIFT; + *pages_avail += last_pfn - curr_pfn; + + free_bootmem(sp_banks[i].base_addr, size); + } + +#ifdef CONFIG_BLK_DEV_INITRD + if (initrd_start) { + /* Reserve the initrd image area. */ + size = initrd_end - initrd_start; + reserve_bootmem(initrd_start, size); + *pages_avail -= PAGE_ALIGN(size) >> PAGE_SHIFT; + + initrd_start = (initrd_start - phys_base) + PAGE_OFFSET; + initrd_end = (initrd_end - phys_base) + PAGE_OFFSET; + } +#endif + /* Reserve the kernel text/data/bss. */ + size = (start_pfn << PAGE_SHIFT) - phys_base; + reserve_bootmem(phys_base, size); + *pages_avail -= PAGE_ALIGN(size) >> PAGE_SHIFT; + + /* Reserve the bootmem map. We do not account for it + * in pages_avail because we will release that memory + * in free_all_bootmem. + */ + size = bootmap_size; + reserve_bootmem((bootmap_pfn << PAGE_SHIFT), size); + *pages_avail -= PAGE_ALIGN(size) >> PAGE_SHIFT; + + return max_pfn; +} + +/* + * check_pgt_cache + * + * This is called at the end of unmapping of VMA (zap_page_range), + * to rescan the page cache for architecture specific things, + * presumably something like sun4/sun4c PMEGs. Most architectures + * define check_pgt_cache empty. + * + * We simply copy the 2.4 implementation for now. + */ +int pgt_cache_water[2] = { 25, 50 }; + +void check_pgt_cache(void) +{ + do_check_pgt_cache(pgt_cache_water[0], pgt_cache_water[1]); +} + +/* + * paging_init() sets up the page tables: We call the MMU specific + * init routine based upon the Sun model type on the Sparc. + * + */ +extern void sun4c_paging_init(void); +extern void srmmu_paging_init(void); +extern void device_scan(void); + +void __init paging_init(void) +{ + switch(sparc_cpu_model) { + case sun4c: + case sun4e: + case sun4: + sun4c_paging_init(); + sparc_unmapped_base = 0xe0000000; + BTFIXUPSET_SETHI(sparc_unmapped_base, 0xe0000000); + break; + case sun4m: + case sun4d: + srmmu_paging_init(); + sparc_unmapped_base = 0x50000000; + BTFIXUPSET_SETHI(sparc_unmapped_base, 0x50000000); + break; + default: + prom_printf("paging_init: Cannot init paging on this Sparc\n"); + prom_printf("paging_init: sparc_cpu_model = %d\n", sparc_cpu_model); + prom_printf("paging_init: Halting...\n"); + prom_halt(); + }; + + /* Initialize the protection map with non-constant, MMU dependent values. */ + protection_map[0] = PAGE_NONE; + protection_map[1] = PAGE_READONLY; + protection_map[2] = PAGE_COPY; + protection_map[3] = PAGE_COPY; + protection_map[4] = PAGE_READONLY; + protection_map[5] = PAGE_READONLY; + protection_map[6] = PAGE_COPY; + protection_map[7] = PAGE_COPY; + protection_map[8] = PAGE_NONE; + protection_map[9] = PAGE_READONLY; + protection_map[10] = PAGE_SHARED; + protection_map[11] = PAGE_SHARED; + protection_map[12] = PAGE_READONLY; + protection_map[13] = PAGE_READONLY; + protection_map[14] = PAGE_SHARED; + protection_map[15] = PAGE_SHARED; + btfixup(); + device_scan(); +} + +struct cache_palias *sparc_aliases; + +static void __init taint_real_pages(void) +{ + int i; + + for (i = 0; sp_banks[i].num_bytes; i++) { + unsigned long start, end; + + start = sp_banks[i].base_addr; + end = start + sp_banks[i].num_bytes; + + while (start < end) { + set_bit(start >> 20, sparc_valid_addr_bitmap); + start += PAGE_SIZE; + } + } +} + +void map_high_region(unsigned long start_pfn, unsigned long end_pfn) +{ + unsigned long tmp; + +#ifdef CONFIG_DEBUG_HIGHMEM + printk("mapping high region %08lx - %08lx\n", start_pfn, end_pfn); +#endif + + for (tmp = start_pfn; tmp < end_pfn; tmp++) { + struct page *page = pfn_to_page(tmp); + + ClearPageReserved(page); + set_bit(PG_highmem, &page->flags); + set_page_count(page, 1); + __free_page(page); + totalhigh_pages++; + } +} + +void __init mem_init(void) +{ + int codepages = 0; + int datapages = 0; + int initpages = 0; + int reservedpages = 0; + int i; + + if (PKMAP_BASE+LAST_PKMAP*PAGE_SIZE >= FIXADDR_START) { + prom_printf("BUG: fixmap and pkmap areas overlap\n"); + prom_printf("pkbase: 0x%lx pkend: 0x%lx fixstart 0x%lx\n", + PKMAP_BASE, + (unsigned long)PKMAP_BASE+LAST_PKMAP*PAGE_SIZE, + FIXADDR_START); + prom_printf("Please mail sparclinux@vger.kernel.org.\n"); + prom_halt(); + } + + + /* Saves us work later. */ + memset((void *)&empty_zero_page, 0, PAGE_SIZE); + + i = last_valid_pfn >> ((20 - PAGE_SHIFT) + 5); + i += 1; + sparc_valid_addr_bitmap = (unsigned long *) + __alloc_bootmem(i << 2, SMP_CACHE_BYTES, 0UL); + + if (sparc_valid_addr_bitmap == NULL) { + prom_printf("mem_init: Cannot alloc valid_addr_bitmap.\n"); + prom_halt(); + } + memset(sparc_valid_addr_bitmap, 0, i << 2); + + taint_real_pages(); + + max_mapnr = last_valid_pfn - pfn_base; + high_memory = __va(max_low_pfn << PAGE_SHIFT); + + totalram_pages = free_all_bootmem(); + + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + unsigned long start_pfn = sp_banks[i].base_addr >> PAGE_SHIFT; + unsigned long end_pfn = (sp_banks[i].base_addr + sp_banks[i].num_bytes) >> PAGE_SHIFT; + + num_physpages += sp_banks[i].num_bytes >> PAGE_SHIFT; + + if (end_pfn <= highstart_pfn) + continue; + + if (start_pfn < highstart_pfn) + start_pfn = highstart_pfn; + + map_high_region(start_pfn, end_pfn); + } + + totalram_pages += totalhigh_pages; + + codepages = (((unsigned long) &etext) - ((unsigned long)&_start)); + codepages = PAGE_ALIGN(codepages) >> PAGE_SHIFT; + datapages = (((unsigned long) &edata) - ((unsigned long)&etext)); + datapages = PAGE_ALIGN(datapages) >> PAGE_SHIFT; + initpages = (((unsigned long) &__init_end) - ((unsigned long) &__init_begin)); + initpages = PAGE_ALIGN(initpages) >> PAGE_SHIFT; + + /* Ignore memory holes for the purpose of counting reserved pages */ + for (i=0; i < max_low_pfn; i++) + if (test_bit(i >> (20 - PAGE_SHIFT), sparc_valid_addr_bitmap) + && PageReserved(pfn_to_page(i))) + reservedpages++; + + printk(KERN_INFO "Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data, %dk init, %ldk highmem)\n", + (unsigned long) nr_free_pages() << (PAGE_SHIFT-10), + num_physpages << (PAGE_SHIFT - 10), + codepages << (PAGE_SHIFT-10), + reservedpages << (PAGE_SHIFT - 10), + datapages << (PAGE_SHIFT-10), + initpages << (PAGE_SHIFT-10), + totalhigh_pages << (PAGE_SHIFT-10)); +} + +void free_initmem (void) +{ + unsigned long addr; + + addr = (unsigned long)(&__init_begin); + for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) { + struct page *p; + + p = virt_to_page(addr); + + ClearPageReserved(p); + set_page_count(p, 1); + __free_page(p); + totalram_pages++; + num_physpages++; + } + printk (KERN_INFO "Freeing unused kernel memory: %dk freed\n", (&__init_end - &__init_begin) >> 10); +} + +#ifdef CONFIG_BLK_DEV_INITRD +void free_initrd_mem(unsigned long start, unsigned long end) +{ + if (start < end) + printk (KERN_INFO "Freeing initrd memory: %ldk freed\n", (end - start) >> 10); + for (; start < end; start += PAGE_SIZE) { + struct page *p = virt_to_page(start); + + ClearPageReserved(p); + set_page_count(p, 1); + __free_page(p); + num_physpages++; + } +} +#endif + +void sparc_flush_page_to_ram(struct page *page) +{ + unsigned long vaddr = (unsigned long)page_address(page); + + if (vaddr) + __flush_page_to_ram(vaddr); +} diff --git a/arch/sparc/mm/io-unit.c b/arch/sparc/mm/io-unit.c new file mode 100644 index 000000000000..eefffa1dc5de --- /dev/null +++ b/arch/sparc/mm/io-unit.c @@ -0,0 +1,318 @@ +/* $Id: io-unit.c,v 1.24 2001/12/17 07:05:09 davem Exp $ + * io-unit.c: IO-UNIT specific routines for memory management. + * + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/highmem.h> /* pte_offset_map => kmap_atomic */ +#include <linux/bitops.h> + +#include <asm/scatterlist.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/sbus.h> +#include <asm/io.h> +#include <asm/io-unit.h> +#include <asm/mxcc.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/dma.h> + +/* #define IOUNIT_DEBUG */ +#ifdef IOUNIT_DEBUG +#define IOD(x) printk(x) +#else +#define IOD(x) do { } while (0) +#endif + +#define IOPERM (IOUPTE_CACHE | IOUPTE_WRITE | IOUPTE_VALID) +#define MKIOPTE(phys) __iopte((((phys)>>4) & IOUPTE_PAGE) | IOPERM) + +void __init +iounit_init(int sbi_node, int io_node, struct sbus_bus *sbus) +{ + iopte_t *xpt, *xptend; + struct iounit_struct *iounit; + struct linux_prom_registers iommu_promregs[PROMREG_MAX]; + struct resource r; + + iounit = kmalloc(sizeof(struct iounit_struct), GFP_ATOMIC); + + memset(iounit, 0, sizeof(*iounit)); + iounit->limit[0] = IOUNIT_BMAP1_START; + iounit->limit[1] = IOUNIT_BMAP2_START; + iounit->limit[2] = IOUNIT_BMAPM_START; + iounit->limit[3] = IOUNIT_BMAPM_END; + iounit->rotor[1] = IOUNIT_BMAP2_START; + iounit->rotor[2] = IOUNIT_BMAPM_START; + + xpt = NULL; + if(prom_getproperty(sbi_node, "reg", (void *) iommu_promregs, + sizeof(iommu_promregs)) != -1) { + prom_apply_generic_ranges(io_node, 0, iommu_promregs, 3); + memset(&r, 0, sizeof(r)); + r.flags = iommu_promregs[2].which_io; + r.start = iommu_promregs[2].phys_addr; + xpt = (iopte_t *) sbus_ioremap(&r, 0, PAGE_SIZE * 16, "XPT"); + } + if(!xpt) panic("Cannot map External Page Table."); + + sbus->iommu = (struct iommu_struct *)iounit; + iounit->page_table = xpt; + + for (xptend = iounit->page_table + (16 * PAGE_SIZE) / sizeof(iopte_t); + xpt < xptend;) + iopte_val(*xpt++) = 0; +} + +/* One has to hold iounit->lock to call this */ +static unsigned long iounit_get_area(struct iounit_struct *iounit, unsigned long vaddr, int size) +{ + int i, j, k, npages; + unsigned long rotor, scan, limit; + iopte_t iopte; + + npages = ((vaddr & ~PAGE_MASK) + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + + /* A tiny bit of magic ingredience :) */ + switch (npages) { + case 1: i = 0x0231; break; + case 2: i = 0x0132; break; + default: i = 0x0213; break; + } + + IOD(("iounit_get_area(%08lx,%d[%d])=", vaddr, size, npages)); + +next: j = (i & 15); + rotor = iounit->rotor[j - 1]; + limit = iounit->limit[j]; + scan = rotor; +nexti: scan = find_next_zero_bit(iounit->bmap, limit, scan); + if (scan + npages > limit) { + if (limit != rotor) { + limit = rotor; + scan = iounit->limit[j - 1]; + goto nexti; + } + i >>= 4; + if (!(i & 15)) + panic("iounit_get_area: Couldn't find free iopte slots for (%08lx,%d)\n", vaddr, size); + goto next; + } + for (k = 1, scan++; k < npages; k++) + if (test_bit(scan++, iounit->bmap)) + goto nexti; + iounit->rotor[j - 1] = (scan < limit) ? scan : iounit->limit[j - 1]; + scan -= npages; + iopte = MKIOPTE(__pa(vaddr & PAGE_MASK)); + vaddr = IOUNIT_DMA_BASE + (scan << PAGE_SHIFT) + (vaddr & ~PAGE_MASK); + for (k = 0; k < npages; k++, iopte = __iopte(iopte_val(iopte) + 0x100), scan++) { + set_bit(scan, iounit->bmap); + iounit->page_table[scan] = iopte; + } + IOD(("%08lx\n", vaddr)); + return vaddr; +} + +static __u32 iounit_get_scsi_one(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long ret, flags; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + spin_lock_irqsave(&iounit->lock, flags); + ret = iounit_get_area(iounit, (unsigned long)vaddr, len); + spin_unlock_irqrestore(&iounit->lock, flags); + return ret; +} + +static void iounit_get_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + unsigned long flags; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + /* FIXME: Cache some resolved pages - often several sg entries are to the same page */ + spin_lock_irqsave(&iounit->lock, flags); + while (sz != 0) { + --sz; + sg[sz].dvma_address = iounit_get_area(iounit, (unsigned long)page_address(sg[sz].page) + sg[sz].offset, sg[sz].length); + sg[sz].dvma_length = sg[sz].length; + } + spin_unlock_irqrestore(&iounit->lock, flags); +} + +static void iounit_release_scsi_one(__u32 vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long flags; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + spin_lock_irqsave(&iounit->lock, flags); + len = ((vaddr & ~PAGE_MASK) + len + (PAGE_SIZE-1)) >> PAGE_SHIFT; + vaddr = (vaddr - IOUNIT_DMA_BASE) >> PAGE_SHIFT; + IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); + for (len += vaddr; vaddr < len; vaddr++) + clear_bit(vaddr, iounit->bmap); + spin_unlock_irqrestore(&iounit->lock, flags); +} + +static void iounit_release_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + unsigned long flags; + unsigned long vaddr, len; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + spin_lock_irqsave(&iounit->lock, flags); + while (sz != 0) { + --sz; + len = ((sg[sz].dvma_address & ~PAGE_MASK) + sg[sz].length + (PAGE_SIZE-1)) >> PAGE_SHIFT; + vaddr = (sg[sz].dvma_address - IOUNIT_DMA_BASE) >> PAGE_SHIFT; + IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); + for (len += vaddr; vaddr < len; vaddr++) + clear_bit(vaddr, iounit->bmap); + } + spin_unlock_irqrestore(&iounit->lock, flags); +} + +#ifdef CONFIG_SBUS +static int iounit_map_dma_area(dma_addr_t *pba, unsigned long va, __u32 addr, int len) +{ + unsigned long page, end; + pgprot_t dvma_prot; + iopte_t *iopte; + struct sbus_bus *sbus; + + *pba = addr; + + dvma_prot = __pgprot(SRMMU_CACHE | SRMMU_ET_PTE | SRMMU_PRIV); + end = PAGE_ALIGN((addr + len)); + while(addr < end) { + page = va; + { + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + long i; + + pgdp = pgd_offset(&init_mm, addr); + pmdp = pmd_offset(pgdp, addr); + ptep = pte_offset_map(pmdp, addr); + + set_pte(ptep, mk_pte(virt_to_page(page), dvma_prot)); + + i = ((addr - IOUNIT_DMA_BASE) >> PAGE_SHIFT); + + for_each_sbus(sbus) { + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + iopte = (iopte_t *)(iounit->page_table + i); + *iopte = MKIOPTE(__pa(page)); + } + } + addr += PAGE_SIZE; + va += PAGE_SIZE; + } + flush_cache_all(); + flush_tlb_all(); + + return 0; +} + +static void iounit_unmap_dma_area(unsigned long addr, int len) +{ + /* XXX Somebody please fill this in */ +} + +/* XXX We do not pass sbus device here, bad. */ +static struct page *iounit_translate_dvma(unsigned long addr) +{ + struct sbus_bus *sbus = sbus_root; /* They are all the same */ + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + int i; + iopte_t *iopte; + + i = ((addr - IOUNIT_DMA_BASE) >> PAGE_SHIFT); + iopte = (iopte_t *)(iounit->page_table + i); + return pfn_to_page(iopte_val(*iopte) >> (PAGE_SHIFT-4)); /* XXX sun4d guru, help */ +} +#endif + +static char *iounit_lockarea(char *vaddr, unsigned long len) +{ +/* FIXME: Write this */ + return vaddr; +} + +static void iounit_unlockarea(char *vaddr, unsigned long len) +{ +/* FIXME: Write this */ +} + +void __init ld_mmu_iounit(void) +{ + BTFIXUPSET_CALL(mmu_lockarea, iounit_lockarea, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(mmu_unlockarea, iounit_unlockarea, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(mmu_get_scsi_one, iounit_get_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iounit_get_scsi_sgl, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_one, iounit_release_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_sgl, iounit_release_scsi_sgl, BTFIXUPCALL_NORM); + +#ifdef CONFIG_SBUS + BTFIXUPSET_CALL(mmu_map_dma_area, iounit_map_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unmap_dma_area, iounit_unmap_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_translate_dvma, iounit_translate_dvma, BTFIXUPCALL_NORM); +#endif +} + +__u32 iounit_map_dma_init(struct sbus_bus *sbus, int size) +{ + int i, j, k, npages; + unsigned long rotor, scan, limit; + unsigned long flags; + __u32 ret; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + npages = (size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + i = 0x0213; + spin_lock_irqsave(&iounit->lock, flags); +next: j = (i & 15); + rotor = iounit->rotor[j - 1]; + limit = iounit->limit[j]; + scan = rotor; +nexti: scan = find_next_zero_bit(iounit->bmap, limit, scan); + if (scan + npages > limit) { + if (limit != rotor) { + limit = rotor; + scan = iounit->limit[j - 1]; + goto nexti; + } + i >>= 4; + if (!(i & 15)) + panic("iounit_map_dma_init: Couldn't find free iopte slots for %d bytes\n", size); + goto next; + } + for (k = 1, scan++; k < npages; k++) + if (test_bit(scan++, iounit->bmap)) + goto nexti; + iounit->rotor[j - 1] = (scan < limit) ? scan : iounit->limit[j - 1]; + scan -= npages; + ret = IOUNIT_DMA_BASE + (scan << PAGE_SHIFT); + for (k = 0; k < npages; k++, scan++) + set_bit(scan, iounit->bmap); + spin_unlock_irqrestore(&iounit->lock, flags); + return ret; +} + +__u32 iounit_map_dma_page(__u32 vaddr, void *addr, struct sbus_bus *sbus) +{ + int scan = (vaddr - IOUNIT_DMA_BASE) >> PAGE_SHIFT; + struct iounit_struct *iounit = (struct iounit_struct *)sbus->iommu; + + iounit->page_table[scan] = MKIOPTE(__pa(((unsigned long)addr) & PAGE_MASK)); + return vaddr + (((unsigned long)addr) & ~PAGE_MASK); +} diff --git a/arch/sparc/mm/iommu.c b/arch/sparc/mm/iommu.c new file mode 100644 index 000000000000..489bf68d5f05 --- /dev/null +++ b/arch/sparc/mm/iommu.c @@ -0,0 +1,475 @@ +/* + * iommu.c: IOMMU specific routines for memory management. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995,2002 Pete Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/highmem.h> /* pte_offset_map => kmap_atomic */ + +#include <asm/scatterlist.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/sbus.h> +#include <asm/io.h> +#include <asm/mxcc.h> +#include <asm/mbus.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/bitext.h> +#include <asm/iommu.h> +#include <asm/dma.h> + +/* + * This can be sized dynamically, but we will do this + * only when we have a guidance about actual I/O pressures. + */ +#define IOMMU_RNGE IOMMU_RNGE_256MB +#define IOMMU_START 0xF0000000 +#define IOMMU_WINSIZE (256*1024*1024U) +#define IOMMU_NPTES (IOMMU_WINSIZE/PAGE_SIZE) /* 64K PTEs, 265KB */ +#define IOMMU_ORDER 6 /* 4096 * (1<<6) */ + +/* srmmu.c */ +extern int viking_mxcc_present; +BTFIXUPDEF_CALL(void, flush_page_for_dma, unsigned long) +#define flush_page_for_dma(page) BTFIXUP_CALL(flush_page_for_dma)(page) +extern int flush_page_for_dma_global; +static int viking_flush; +/* viking.S */ +extern void viking_flush_page(unsigned long page); +extern void viking_mxcc_flush_page(unsigned long page); + +/* + * Values precomputed according to CPU type. + */ +static unsigned int ioperm_noc; /* Consistent mapping iopte flags */ +static pgprot_t dvma_prot; /* Consistent mapping pte flags */ + +#define IOPERM (IOPTE_CACHE | IOPTE_WRITE | IOPTE_VALID) +#define MKIOPTE(pfn, perm) (((((pfn)<<8) & IOPTE_PAGE) | (perm)) & ~IOPTE_WAZ) + +void __init +iommu_init(int iommund, struct sbus_bus *sbus) +{ + unsigned int impl, vers; + unsigned long tmp; + struct iommu_struct *iommu; + struct linux_prom_registers iommu_promregs[PROMREG_MAX]; + struct resource r; + unsigned long *bitmap; + + iommu = kmalloc(sizeof(struct iommu_struct), GFP_ATOMIC); + if (!iommu) { + prom_printf("Unable to allocate iommu structure\n"); + prom_halt(); + } + iommu->regs = NULL; + if (prom_getproperty(iommund, "reg", (void *) iommu_promregs, + sizeof(iommu_promregs)) != -1) { + memset(&r, 0, sizeof(r)); + r.flags = iommu_promregs[0].which_io; + r.start = iommu_promregs[0].phys_addr; + iommu->regs = (struct iommu_regs *) + sbus_ioremap(&r, 0, PAGE_SIZE * 3, "iommu_regs"); + } + if (!iommu->regs) { + prom_printf("Cannot map IOMMU registers\n"); + prom_halt(); + } + impl = (iommu->regs->control & IOMMU_CTRL_IMPL) >> 28; + vers = (iommu->regs->control & IOMMU_CTRL_VERS) >> 24; + tmp = iommu->regs->control; + tmp &= ~(IOMMU_CTRL_RNGE); + tmp |= (IOMMU_RNGE_256MB | IOMMU_CTRL_ENAB); + iommu->regs->control = tmp; + iommu_invalidate(iommu->regs); + iommu->start = IOMMU_START; + iommu->end = 0xffffffff; + + /* Allocate IOMMU page table */ + /* Stupid alignment constraints give me a headache. + We need 256K or 512K or 1M or 2M area aligned to + its size and current gfp will fortunately give + it to us. */ + tmp = __get_free_pages(GFP_KERNEL, IOMMU_ORDER); + if (!tmp) { + prom_printf("Unable to allocate iommu table [0x%08x]\n", + IOMMU_NPTES*sizeof(iopte_t)); + prom_halt(); + } + iommu->page_table = (iopte_t *)tmp; + + /* Initialize new table. */ + memset(iommu->page_table, 0, IOMMU_NPTES*sizeof(iopte_t)); + flush_cache_all(); + flush_tlb_all(); + iommu->regs->base = __pa((unsigned long) iommu->page_table) >> 4; + iommu_invalidate(iommu->regs); + + bitmap = kmalloc(IOMMU_NPTES>>3, GFP_KERNEL); + if (!bitmap) { + prom_printf("Unable to allocate iommu bitmap [%d]\n", + (int)(IOMMU_NPTES>>3)); + prom_halt(); + } + bit_map_init(&iommu->usemap, bitmap, IOMMU_NPTES); + /* To be coherent on HyperSparc, the page color of DVMA + * and physical addresses must match. + */ + if (srmmu_modtype == HyperSparc) + iommu->usemap.num_colors = vac_cache_size >> PAGE_SHIFT; + else + iommu->usemap.num_colors = 1; + + printk("IOMMU: impl %d vers %d table 0x%p[%d B] map [%d b]\n", + impl, vers, iommu->page_table, + (int)(IOMMU_NPTES*sizeof(iopte_t)), (int)IOMMU_NPTES); + + sbus->iommu = iommu; +} + +/* This begs to be btfixup-ed by srmmu. */ +/* Flush the iotlb entries to ram. */ +/* This could be better if we didn't have to flush whole pages. */ +static void iommu_flush_iotlb(iopte_t *iopte, unsigned int niopte) +{ + unsigned long start; + unsigned long end; + + start = (unsigned long)iopte & PAGE_MASK; + end = PAGE_ALIGN(start + niopte*sizeof(iopte_t)); + if (viking_mxcc_present) { + while(start < end) { + viking_mxcc_flush_page(start); + start += PAGE_SIZE; + } + } else if (viking_flush) { + while(start < end) { + viking_flush_page(start); + start += PAGE_SIZE; + } + } else { + while(start < end) { + __flush_page_to_ram(start); + start += PAGE_SIZE; + } + } +} + +static u32 iommu_get_one(struct page *page, int npages, struct sbus_bus *sbus) +{ + struct iommu_struct *iommu = sbus->iommu; + int ioptex; + iopte_t *iopte, *iopte0; + unsigned int busa, busa0; + int i; + + /* page color = pfn of page */ + ioptex = bit_map_string_get(&iommu->usemap, npages, page_to_pfn(page)); + if (ioptex < 0) + panic("iommu out"); + busa0 = iommu->start + (ioptex << PAGE_SHIFT); + iopte0 = &iommu->page_table[ioptex]; + + busa = busa0; + iopte = iopte0; + for (i = 0; i < npages; i++) { + iopte_val(*iopte) = MKIOPTE(page_to_pfn(page), IOPERM); + iommu_invalidate_page(iommu->regs, busa); + busa += PAGE_SIZE; + iopte++; + page++; + } + + iommu_flush_iotlb(iopte0, npages); + + return busa0; +} + +static u32 iommu_get_scsi_one(char *vaddr, unsigned int len, + struct sbus_bus *sbus) +{ + unsigned long off; + int npages; + struct page *page; + u32 busa; + + off = (unsigned long)vaddr & ~PAGE_MASK; + npages = (off + len + PAGE_SIZE-1) >> PAGE_SHIFT; + page = virt_to_page((unsigned long)vaddr & PAGE_MASK); + busa = iommu_get_one(page, npages, sbus); + return busa + off; +} + +static __u32 iommu_get_scsi_one_noflush(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + return iommu_get_scsi_one(vaddr, len, sbus); +} + +static __u32 iommu_get_scsi_one_gflush(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + flush_page_for_dma(0); + return iommu_get_scsi_one(vaddr, len, sbus); +} + +static __u32 iommu_get_scsi_one_pflush(char *vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long page = ((unsigned long) vaddr) & PAGE_MASK; + + while(page < ((unsigned long)(vaddr + len))) { + flush_page_for_dma(page); + page += PAGE_SIZE; + } + return iommu_get_scsi_one(vaddr, len, sbus); +} + +static void iommu_get_scsi_sgl_noflush(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + int n; + + while (sz != 0) { + --sz; + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + sg->dvma_address = iommu_get_one(sg->page, n, sbus) + sg->offset; + sg->dvma_length = (__u32) sg->length; + sg++; + } +} + +static void iommu_get_scsi_sgl_gflush(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + int n; + + flush_page_for_dma(0); + while (sz != 0) { + --sz; + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + sg->dvma_address = iommu_get_one(sg->page, n, sbus) + sg->offset; + sg->dvma_length = (__u32) sg->length; + sg++; + } +} + +static void iommu_get_scsi_sgl_pflush(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + unsigned long page, oldpage = 0; + int n, i; + + while(sz != 0) { + --sz; + + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + + /* + * We expect unmapped highmem pages to be not in the cache. + * XXX Is this a good assumption? + * XXX What if someone else unmaps it here and races us? + */ + if ((page = (unsigned long) page_address(sg->page)) != 0) { + for (i = 0; i < n; i++) { + if (page != oldpage) { /* Already flushed? */ + flush_page_for_dma(page); + oldpage = page; + } + page += PAGE_SIZE; + } + } + + sg->dvma_address = iommu_get_one(sg->page, n, sbus) + sg->offset; + sg->dvma_length = (__u32) sg->length; + sg++; + } +} + +static void iommu_release_one(u32 busa, int npages, struct sbus_bus *sbus) +{ + struct iommu_struct *iommu = sbus->iommu; + int ioptex; + int i; + + if (busa < iommu->start) + BUG(); + ioptex = (busa - iommu->start) >> PAGE_SHIFT; + for (i = 0; i < npages; i++) { + iopte_val(iommu->page_table[ioptex + i]) = 0; + iommu_invalidate_page(iommu->regs, busa); + busa += PAGE_SIZE; + } + bit_map_clear(&iommu->usemap, ioptex, npages); +} + +static void iommu_release_scsi_one(__u32 vaddr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long off; + int npages; + + off = vaddr & ~PAGE_MASK; + npages = (off + len + PAGE_SIZE-1) >> PAGE_SHIFT; + iommu_release_one(vaddr & PAGE_MASK, npages, sbus); +} + +static void iommu_release_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + int n; + + while(sz != 0) { + --sz; + + n = (sg->length + sg->offset + PAGE_SIZE-1) >> PAGE_SHIFT; + iommu_release_one(sg->dvma_address & PAGE_MASK, n, sbus); + sg->dvma_address = 0x21212121; + sg++; + } +} + +#ifdef CONFIG_SBUS +static int iommu_map_dma_area(dma_addr_t *pba, unsigned long va, + unsigned long addr, int len) +{ + unsigned long page, end; + struct iommu_struct *iommu = sbus_root->iommu; + iopte_t *iopte = iommu->page_table; + iopte_t *first; + int ioptex; + + if ((va & ~PAGE_MASK) != 0) BUG(); + if ((addr & ~PAGE_MASK) != 0) BUG(); + if ((len & ~PAGE_MASK) != 0) BUG(); + + /* page color = physical address */ + ioptex = bit_map_string_get(&iommu->usemap, len >> PAGE_SHIFT, + addr >> PAGE_SHIFT); + if (ioptex < 0) + panic("iommu out"); + + iopte += ioptex; + first = iopte; + end = addr + len; + while(addr < end) { + page = va; + { + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + if (viking_mxcc_present) + viking_mxcc_flush_page(page); + else if (viking_flush) + viking_flush_page(page); + else + __flush_page_to_ram(page); + + pgdp = pgd_offset(&init_mm, addr); + pmdp = pmd_offset(pgdp, addr); + ptep = pte_offset_map(pmdp, addr); + + set_pte(ptep, mk_pte(virt_to_page(page), dvma_prot)); + } + iopte_val(*iopte++) = + MKIOPTE(page_to_pfn(virt_to_page(page)), ioperm_noc); + addr += PAGE_SIZE; + va += PAGE_SIZE; + } + /* P3: why do we need this? + * + * DAVEM: Because there are several aspects, none of which + * are handled by a single interface. Some cpus are + * completely not I/O DMA coherent, and some have + * virtually indexed caches. The driver DMA flushing + * methods handle the former case, but here during + * IOMMU page table modifications, and usage of non-cacheable + * cpu mappings of pages potentially in the cpu caches, we have + * to handle the latter case as well. + */ + flush_cache_all(); + iommu_flush_iotlb(first, len >> PAGE_SHIFT); + flush_tlb_all(); + iommu_invalidate(iommu->regs); + + *pba = iommu->start + (ioptex << PAGE_SHIFT); + return 0; +} + +static void iommu_unmap_dma_area(unsigned long busa, int len) +{ + struct iommu_struct *iommu = sbus_root->iommu; + iopte_t *iopte = iommu->page_table; + unsigned long end; + int ioptex = (busa - iommu->start) >> PAGE_SHIFT; + + if ((busa & ~PAGE_MASK) != 0) BUG(); + if ((len & ~PAGE_MASK) != 0) BUG(); + + iopte += ioptex; + end = busa + len; + while (busa < end) { + iopte_val(*iopte++) = 0; + busa += PAGE_SIZE; + } + flush_tlb_all(); + iommu_invalidate(iommu->regs); + bit_map_clear(&iommu->usemap, ioptex, len >> PAGE_SHIFT); +} + +static struct page *iommu_translate_dvma(unsigned long busa) +{ + struct iommu_struct *iommu = sbus_root->iommu; + iopte_t *iopte = iommu->page_table; + + iopte += ((busa - iommu->start) >> PAGE_SHIFT); + return pfn_to_page((iopte_val(*iopte) & IOPTE_PAGE) >> (PAGE_SHIFT-4)); +} +#endif + +static char *iommu_lockarea(char *vaddr, unsigned long len) +{ + return vaddr; +} + +static void iommu_unlockarea(char *vaddr, unsigned long len) +{ +} + +void __init ld_mmu_iommu(void) +{ + viking_flush = (BTFIXUPVAL_CALL(flush_page_for_dma) == (unsigned long)viking_flush_page); + BTFIXUPSET_CALL(mmu_lockarea, iommu_lockarea, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(mmu_unlockarea, iommu_unlockarea, BTFIXUPCALL_NOP); + + if (!BTFIXUPVAL_CALL(flush_page_for_dma)) { + /* IO coherent chip */ + BTFIXUPSET_CALL(mmu_get_scsi_one, iommu_get_scsi_one_noflush, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iommu_get_scsi_sgl_noflush, BTFIXUPCALL_NORM); + } else if (flush_page_for_dma_global) { + /* flush_page_for_dma flushes everything, no matter of what page is it */ + BTFIXUPSET_CALL(mmu_get_scsi_one, iommu_get_scsi_one_gflush, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iommu_get_scsi_sgl_gflush, BTFIXUPCALL_NORM); + } else { + BTFIXUPSET_CALL(mmu_get_scsi_one, iommu_get_scsi_one_pflush, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, iommu_get_scsi_sgl_pflush, BTFIXUPCALL_NORM); + } + BTFIXUPSET_CALL(mmu_release_scsi_one, iommu_release_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_sgl, iommu_release_scsi_sgl, BTFIXUPCALL_NORM); + +#ifdef CONFIG_SBUS + BTFIXUPSET_CALL(mmu_map_dma_area, iommu_map_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unmap_dma_area, iommu_unmap_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_translate_dvma, iommu_translate_dvma, BTFIXUPCALL_NORM); +#endif + + if (viking_mxcc_present || srmmu_modtype == HyperSparc) { + dvma_prot = __pgprot(SRMMU_CACHE | SRMMU_ET_PTE | SRMMU_PRIV); + ioperm_noc = IOPTE_CACHE | IOPTE_WRITE | IOPTE_VALID; + } else { + dvma_prot = __pgprot(SRMMU_ET_PTE | SRMMU_PRIV); + ioperm_noc = IOPTE_WRITE | IOPTE_VALID; + } +} diff --git a/arch/sparc/mm/loadmmu.c b/arch/sparc/mm/loadmmu.c new file mode 100644 index 000000000000..e9f9571601ba --- /dev/null +++ b/arch/sparc/mm/loadmmu.c @@ -0,0 +1,46 @@ +/* $Id: loadmmu.c,v 1.56 2000/02/08 20:24:21 davem Exp $ + * loadmmu.c: This code loads up all the mm function pointers once the + * machine type has been determined. It also sets the static + * mmu values such as PAGE_NONE, etc. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/a.out.h> +#include <asm/mmu_context.h> +#include <asm/oplib.h> + +struct ctx_list *ctx_list_pool; +struct ctx_list ctx_free; +struct ctx_list ctx_used; + +unsigned int pg_iobits; + +extern void ld_mmu_sun4c(void); +extern void ld_mmu_srmmu(void); + +void __init load_mmu(void) +{ + switch(sparc_cpu_model) { + case sun4c: + case sun4: + ld_mmu_sun4c(); + break; + case sun4m: + case sun4d: + ld_mmu_srmmu(); + break; + default: + prom_printf("load_mmu: %d unsupported\n", (int)sparc_cpu_model); + prom_halt(); + } + btfixup(); +} diff --git a/arch/sparc/mm/nosrmmu.c b/arch/sparc/mm/nosrmmu.c new file mode 100644 index 000000000000..9e215659697e --- /dev/null +++ b/arch/sparc/mm/nosrmmu.c @@ -0,0 +1,59 @@ +/* $Id: nosrmmu.c,v 1.5 1999/11/19 04:11:54 davem Exp $ + * nosrmmu.c: This file is a bunch of dummies for sun4 compiles, + * so that it does not need srmmu and avoid ifdefs. + * + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/mbus.h> +#include <asm/sbus.h> + +static char shouldnothappen[] __initdata = "SUN4 kernel can only run on SUN4\n"; + +enum mbus_module srmmu_modtype; +void *srmmu_nocache_pool; + +int vac_cache_size = 0; + +static void __init should_not_happen(void) +{ + prom_printf(shouldnothappen); + prom_halt(); +} + +void __init srmmu_frob_mem_map(unsigned long start_mem) +{ + should_not_happen(); +} + +unsigned long __init srmmu_paging_init(unsigned long start_mem, unsigned long end_mem) +{ + should_not_happen(); + return 0; +} + +void __init ld_mmu_srmmu(void) +{ + should_not_happen(); +} + +void srmmu_mapioaddr(unsigned long physaddr, unsigned long virt_addr, int bus_type, int rdonly) +{ +} + +void srmmu_unmapioaddr(unsigned long virt_addr) +{ +} + +__u32 iounit_map_dma_init(struct sbus_bus *sbus, int size) +{ + return 0; +} + +__u32 iounit_map_dma_page(__u32 vaddr, void *addr, struct sbus_bus *sbus) +{ + return 0; +} diff --git a/arch/sparc/mm/nosun4c.c b/arch/sparc/mm/nosun4c.c new file mode 100644 index 000000000000..ea2e2105341d --- /dev/null +++ b/arch/sparc/mm/nosun4c.c @@ -0,0 +1,77 @@ +/* $Id: nosun4c.c,v 1.3 2000/02/14 04:52:36 jj Exp $ + * nosun4c.c: This file is a bunch of dummies for SMP compiles, + * so that it does not need sun4c and avoid ifdefs. + * + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/pgtable.h> + +static char shouldnothappen[] __initdata = "32bit SMP kernel only supports sun4m and sun4d\n"; + +/* Dummies */ +struct sun4c_mmu_ring { + unsigned long xxx1[3]; + unsigned char xxx2[2]; + int xxx3; +}; +struct sun4c_mmu_ring sun4c_kernel_ring; +struct sun4c_mmu_ring sun4c_kfree_ring; +unsigned long sun4c_kernel_faults; +unsigned long *sun4c_memerr_reg; + +static void __init should_not_happen(void) +{ + prom_printf(shouldnothappen); + prom_halt(); +} + +unsigned long __init sun4c_paging_init(unsigned long start_mem, unsigned long end_mem) +{ + should_not_happen(); + return 0; +} + +void __init ld_mmu_sun4c(void) +{ + should_not_happen(); +} + +void sun4c_mapioaddr(unsigned long physaddr, unsigned long virt_addr, int bus_type, int rdonly) +{ +} + +void sun4c_unmapioaddr(unsigned long virt_addr) +{ +} + +void sun4c_complete_all_stores(void) +{ +} + +pte_t *sun4c_pte_offset(pmd_t * dir, unsigned long address) +{ + return NULL; +} + +pte_t *sun4c_pte_offset_kernel(pmd_t *dir, unsigned long address) +{ + return NULL; +} + +void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +{ +} + +void __init sun4c_probe_vac(void) +{ + should_not_happen(); +} + +void __init sun4c_probe_memerr_reg(void) +{ + should_not_happen(); +} diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c new file mode 100644 index 000000000000..c89a803cbc20 --- /dev/null +++ b/arch/sparc/mm/srmmu.c @@ -0,0 +1,2274 @@ +/* + * srmmu.c: SRMMU specific routines for memory management. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1995,2002 Pete Zaitcev (zaitcev@yahoo.com) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1999,2000 Anton Blanchard (anton@samba.org) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/bootmem.h> +#include <linux/fs.h> +#include <linux/seq_file.h> + +#include <asm/bitext.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/kdebug.h> +#include <asm/vaddrs.h> +#include <asm/traps.h> +#include <asm/smp.h> +#include <asm/mbus.h> +#include <asm/cache.h> +#include <asm/oplib.h> +#include <asm/sbus.h> +#include <asm/asi.h> +#include <asm/msi.h> +#include <asm/a.out.h> +#include <asm/mmu_context.h> +#include <asm/io-unit.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +/* Now the cpu specific definitions. */ +#include <asm/viking.h> +#include <asm/mxcc.h> +#include <asm/ross.h> +#include <asm/tsunami.h> +#include <asm/swift.h> +#include <asm/turbosparc.h> + +#include <asm/btfixup.h> + +enum mbus_module srmmu_modtype; +unsigned int hwbug_bitmask; +int vac_cache_size; +int vac_line_size; + +extern struct resource sparc_iomap; + +extern unsigned long last_valid_pfn; + +extern unsigned long page_kernel; + +pgd_t *srmmu_swapper_pg_dir; + +#ifdef CONFIG_SMP +#define FLUSH_BEGIN(mm) +#define FLUSH_END +#else +#define FLUSH_BEGIN(mm) if((mm)->context != NO_CONTEXT) { +#define FLUSH_END } +#endif + +BTFIXUPDEF_CALL(void, flush_page_for_dma, unsigned long) +#define flush_page_for_dma(page) BTFIXUP_CALL(flush_page_for_dma)(page) + +int flush_page_for_dma_global = 1; + +#ifdef CONFIG_SMP +BTFIXUPDEF_CALL(void, local_flush_page_for_dma, unsigned long) +#define local_flush_page_for_dma(page) BTFIXUP_CALL(local_flush_page_for_dma)(page) +#endif + +char *srmmu_name; + +ctxd_t *srmmu_ctx_table_phys; +ctxd_t *srmmu_context_table; + +int viking_mxcc_present; +static DEFINE_SPINLOCK(srmmu_context_spinlock); + +int is_hypersparc; + +/* + * In general all page table modifications should use the V8 atomic + * swap instruction. This insures the mmu and the cpu are in sync + * with respect to ref/mod bits in the page tables. + */ +static inline unsigned long srmmu_swap(unsigned long *addr, unsigned long value) +{ + __asm__ __volatile__("swap [%2], %0" : "=&r" (value) : "0" (value), "r" (addr)); + return value; +} + +static inline void srmmu_set_pte(pte_t *ptep, pte_t pteval) +{ + srmmu_swap((unsigned long *)ptep, pte_val(pteval)); +} + +/* The very generic SRMMU page table operations. */ +static inline int srmmu_device_memory(unsigned long x) +{ + return ((x & 0xF0000000) != 0); +} + +int srmmu_cache_pagetables; + +/* these will be initialized in srmmu_nocache_calcsize() */ +unsigned long srmmu_nocache_size; +unsigned long srmmu_nocache_end; + +/* 1 bit <=> 256 bytes of nocache <=> 64 PTEs */ +#define SRMMU_NOCACHE_BITMAP_SHIFT (PAGE_SHIFT - 4) + +/* The context table is a nocache user with the biggest alignment needs. */ +#define SRMMU_NOCACHE_ALIGN_MAX (sizeof(ctxd_t)*SRMMU_MAX_CONTEXTS) + +void *srmmu_nocache_pool; +void *srmmu_nocache_bitmap; +static struct bit_map srmmu_nocache_map; + +static unsigned long srmmu_pte_pfn(pte_t pte) +{ + if (srmmu_device_memory(pte_val(pte))) { + /* Just return something that will cause + * pfn_valid() to return false. This makes + * copy_one_pte() to just directly copy to + * PTE over. + */ + return ~0UL; + } + return (pte_val(pte) & SRMMU_PTE_PMASK) >> (PAGE_SHIFT-4); +} + +static struct page *srmmu_pmd_page(pmd_t pmd) +{ + + if (srmmu_device_memory(pmd_val(pmd))) + BUG(); + return pfn_to_page((pmd_val(pmd) & SRMMU_PTD_PMASK) >> (PAGE_SHIFT-4)); +} + +static inline unsigned long srmmu_pgd_page(pgd_t pgd) +{ return srmmu_device_memory(pgd_val(pgd))?~0:(unsigned long)__nocache_va((pgd_val(pgd) & SRMMU_PTD_PMASK) << 4); } + + +static inline int srmmu_pte_none(pte_t pte) +{ return !(pte_val(pte) & 0xFFFFFFF); } + +static inline int srmmu_pte_present(pte_t pte) +{ return ((pte_val(pte) & SRMMU_ET_MASK) == SRMMU_ET_PTE); } + +static inline int srmmu_pte_read(pte_t pte) +{ return !(pte_val(pte) & SRMMU_NOREAD); } + +static inline void srmmu_pte_clear(pte_t *ptep) +{ srmmu_set_pte(ptep, __pte(0)); } + +static inline int srmmu_pmd_none(pmd_t pmd) +{ return !(pmd_val(pmd) & 0xFFFFFFF); } + +static inline int srmmu_pmd_bad(pmd_t pmd) +{ return (pmd_val(pmd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; } + +static inline int srmmu_pmd_present(pmd_t pmd) +{ return ((pmd_val(pmd) & SRMMU_ET_MASK) == SRMMU_ET_PTD); } + +static inline void srmmu_pmd_clear(pmd_t *pmdp) { + int i; + for (i = 0; i < PTRS_PER_PTE/SRMMU_REAL_PTRS_PER_PTE; i++) + srmmu_set_pte((pte_t *)&pmdp->pmdv[i], __pte(0)); +} + +static inline int srmmu_pgd_none(pgd_t pgd) +{ return !(pgd_val(pgd) & 0xFFFFFFF); } + +static inline int srmmu_pgd_bad(pgd_t pgd) +{ return (pgd_val(pgd) & SRMMU_ET_MASK) != SRMMU_ET_PTD; } + +static inline int srmmu_pgd_present(pgd_t pgd) +{ return ((pgd_val(pgd) & SRMMU_ET_MASK) == SRMMU_ET_PTD); } + +static inline void srmmu_pgd_clear(pgd_t * pgdp) +{ srmmu_set_pte((pte_t *)pgdp, __pte(0)); } + +static inline pte_t srmmu_pte_wrprotect(pte_t pte) +{ return __pte(pte_val(pte) & ~SRMMU_WRITE);} + +static inline pte_t srmmu_pte_mkclean(pte_t pte) +{ return __pte(pte_val(pte) & ~SRMMU_DIRTY);} + +static inline pte_t srmmu_pte_mkold(pte_t pte) +{ return __pte(pte_val(pte) & ~SRMMU_REF);} + +static inline pte_t srmmu_pte_mkwrite(pte_t pte) +{ return __pte(pte_val(pte) | SRMMU_WRITE);} + +static inline pte_t srmmu_pte_mkdirty(pte_t pte) +{ return __pte(pte_val(pte) | SRMMU_DIRTY);} + +static inline pte_t srmmu_pte_mkyoung(pte_t pte) +{ return __pte(pte_val(pte) | SRMMU_REF);} + +/* + * 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 srmmu_mk_pte(struct page *page, pgprot_t pgprot) +{ return __pte((page_to_pfn(page) << (PAGE_SHIFT-4)) | pgprot_val(pgprot)); } + +static pte_t srmmu_mk_pte_phys(unsigned long page, pgprot_t pgprot) +{ return __pte(((page) >> 4) | pgprot_val(pgprot)); } + +static pte_t srmmu_mk_pte_io(unsigned long page, pgprot_t pgprot, int space) +{ return __pte(((page) >> 4) | (space << 28) | pgprot_val(pgprot)); } + +/* XXX should we hyper_flush_whole_icache here - Anton */ +static inline void srmmu_ctxd_set(ctxd_t *ctxp, pgd_t *pgdp) +{ srmmu_set_pte((pte_t *)ctxp, (SRMMU_ET_PTD | (__nocache_pa((unsigned long) pgdp) >> 4))); } + +static inline void srmmu_pgd_set(pgd_t * pgdp, pmd_t * pmdp) +{ srmmu_set_pte((pte_t *)pgdp, (SRMMU_ET_PTD | (__nocache_pa((unsigned long) pmdp) >> 4))); } + +static void srmmu_pmd_set(pmd_t *pmdp, pte_t *ptep) +{ + unsigned long ptp; /* Physical address, shifted right by 4 */ + int i; + + ptp = __nocache_pa((unsigned long) ptep) >> 4; + for (i = 0; i < PTRS_PER_PTE/SRMMU_REAL_PTRS_PER_PTE; i++) { + srmmu_set_pte((pte_t *)&pmdp->pmdv[i], SRMMU_ET_PTD | ptp); + ptp += (SRMMU_REAL_PTRS_PER_PTE*sizeof(pte_t) >> 4); + } +} + +static void srmmu_pmd_populate(pmd_t *pmdp, struct page *ptep) +{ + unsigned long ptp; /* Physical address, shifted right by 4 */ + int i; + + ptp = page_to_pfn(ptep) << (PAGE_SHIFT-4); /* watch for overflow */ + for (i = 0; i < PTRS_PER_PTE/SRMMU_REAL_PTRS_PER_PTE; i++) { + srmmu_set_pte((pte_t *)&pmdp->pmdv[i], SRMMU_ET_PTD | ptp); + ptp += (SRMMU_REAL_PTRS_PER_PTE*sizeof(pte_t) >> 4); + } +} + +static inline pte_t srmmu_pte_modify(pte_t pte, pgprot_t newprot) +{ return __pte((pte_val(pte) & SRMMU_CHG_MASK) | pgprot_val(newprot)); } + +/* to find an entry in a top-level page table... */ +extern inline pgd_t *srmmu_pgd_offset(struct mm_struct * mm, unsigned long address) +{ return mm->pgd + (address >> SRMMU_PGDIR_SHIFT); } + +/* Find an entry in the second-level page table.. */ +static inline pmd_t *srmmu_pmd_offset(pgd_t * dir, unsigned long address) +{ + return (pmd_t *) srmmu_pgd_page(*dir) + + ((address >> PMD_SHIFT) & (PTRS_PER_PMD - 1)); +} + +/* Find an entry in the third-level page table.. */ +static inline pte_t *srmmu_pte_offset(pmd_t * dir, unsigned long address) +{ + void *pte; + + pte = __nocache_va((dir->pmdv[0] & SRMMU_PTD_PMASK) << 4); + return (pte_t *) pte + + ((address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)); +} + +static unsigned long srmmu_swp_type(swp_entry_t entry) +{ + return (entry.val >> SRMMU_SWP_TYPE_SHIFT) & SRMMU_SWP_TYPE_MASK; +} + +static unsigned long srmmu_swp_offset(swp_entry_t entry) +{ + return (entry.val >> SRMMU_SWP_OFF_SHIFT) & SRMMU_SWP_OFF_MASK; +} + +static swp_entry_t srmmu_swp_entry(unsigned long type, unsigned long offset) +{ + return (swp_entry_t) { + (type & SRMMU_SWP_TYPE_MASK) << SRMMU_SWP_TYPE_SHIFT + | (offset & SRMMU_SWP_OFF_MASK) << SRMMU_SWP_OFF_SHIFT }; +} + +/* + * size: bytes to allocate in the nocache area. + * align: bytes, number to align at. + * Returns the virtual address of the allocated area. + */ +static unsigned long __srmmu_get_nocache(int size, int align) +{ + int offset; + + if (size < SRMMU_NOCACHE_BITMAP_SHIFT) { + printk("Size 0x%x too small for nocache request\n", size); + size = SRMMU_NOCACHE_BITMAP_SHIFT; + } + if (size & (SRMMU_NOCACHE_BITMAP_SHIFT-1)) { + printk("Size 0x%x unaligned int nocache request\n", size); + size += SRMMU_NOCACHE_BITMAP_SHIFT-1; + } + BUG_ON(align > SRMMU_NOCACHE_ALIGN_MAX); + + offset = bit_map_string_get(&srmmu_nocache_map, + size >> SRMMU_NOCACHE_BITMAP_SHIFT, + align >> SRMMU_NOCACHE_BITMAP_SHIFT); + if (offset == -1) { + printk("srmmu: out of nocache %d: %d/%d\n", + size, (int) srmmu_nocache_size, + srmmu_nocache_map.used << SRMMU_NOCACHE_BITMAP_SHIFT); + return 0; + } + + return (SRMMU_NOCACHE_VADDR + (offset << SRMMU_NOCACHE_BITMAP_SHIFT)); +} + +unsigned inline long srmmu_get_nocache(int size, int align) +{ + unsigned long tmp; + + tmp = __srmmu_get_nocache(size, align); + + if (tmp) + memset((void *)tmp, 0, size); + + return tmp; +} + +void srmmu_free_nocache(unsigned long vaddr, int size) +{ + int offset; + + if (vaddr < SRMMU_NOCACHE_VADDR) { + printk("Vaddr %lx is smaller than nocache base 0x%lx\n", + vaddr, (unsigned long)SRMMU_NOCACHE_VADDR); + BUG(); + } + if (vaddr+size > srmmu_nocache_end) { + printk("Vaddr %lx is bigger than nocache end 0x%lx\n", + vaddr, srmmu_nocache_end); + BUG(); + } + if (size & (size-1)) { + printk("Size 0x%x is not a power of 2\n", size); + BUG(); + } + if (size < SRMMU_NOCACHE_BITMAP_SHIFT) { + printk("Size 0x%x is too small\n", size); + BUG(); + } + if (vaddr & (size-1)) { + printk("Vaddr %lx is not aligned to size 0x%x\n", vaddr, size); + BUG(); + } + + offset = (vaddr - SRMMU_NOCACHE_VADDR) >> SRMMU_NOCACHE_BITMAP_SHIFT; + size = size >> SRMMU_NOCACHE_BITMAP_SHIFT; + + bit_map_clear(&srmmu_nocache_map, offset, size); +} + +void srmmu_early_allocate_ptable_skeleton(unsigned long start, unsigned long end); + +extern unsigned long probe_memory(void); /* in fault.c */ + +/* + * Reserve nocache dynamically proportionally to the amount of + * system RAM. -- Tomas Szepe <szepe@pinerecords.com>, June 2002 + */ +void srmmu_nocache_calcsize(void) +{ + unsigned long sysmemavail = probe_memory() / 1024; + int srmmu_nocache_npages; + + srmmu_nocache_npages = + sysmemavail / SRMMU_NOCACHE_ALCRATIO / 1024 * 256; + + /* P3 XXX The 4x overuse: corroborated by /proc/meminfo. */ + // if (srmmu_nocache_npages < 256) srmmu_nocache_npages = 256; + if (srmmu_nocache_npages < SRMMU_MIN_NOCACHE_PAGES) + srmmu_nocache_npages = SRMMU_MIN_NOCACHE_PAGES; + + /* anything above 1280 blows up */ + if (srmmu_nocache_npages > SRMMU_MAX_NOCACHE_PAGES) + srmmu_nocache_npages = SRMMU_MAX_NOCACHE_PAGES; + + srmmu_nocache_size = srmmu_nocache_npages * PAGE_SIZE; + srmmu_nocache_end = SRMMU_NOCACHE_VADDR + srmmu_nocache_size; +} + +void srmmu_nocache_init(void) +{ + unsigned int bitmap_bits; + pgd_t *pgd; + pmd_t *pmd; + pte_t *pte; + unsigned long paddr, vaddr; + unsigned long pteval; + + bitmap_bits = srmmu_nocache_size >> SRMMU_NOCACHE_BITMAP_SHIFT; + + srmmu_nocache_pool = __alloc_bootmem(srmmu_nocache_size, + SRMMU_NOCACHE_ALIGN_MAX, 0UL); + memset(srmmu_nocache_pool, 0, srmmu_nocache_size); + + srmmu_nocache_bitmap = __alloc_bootmem(bitmap_bits >> 3, SMP_CACHE_BYTES, 0UL); + bit_map_init(&srmmu_nocache_map, srmmu_nocache_bitmap, bitmap_bits); + + srmmu_swapper_pg_dir = (pgd_t *)__srmmu_get_nocache(SRMMU_PGD_TABLE_SIZE, SRMMU_PGD_TABLE_SIZE); + memset(__nocache_fix(srmmu_swapper_pg_dir), 0, SRMMU_PGD_TABLE_SIZE); + init_mm.pgd = srmmu_swapper_pg_dir; + + srmmu_early_allocate_ptable_skeleton(SRMMU_NOCACHE_VADDR, srmmu_nocache_end); + + paddr = __pa((unsigned long)srmmu_nocache_pool); + vaddr = SRMMU_NOCACHE_VADDR; + + while (vaddr < srmmu_nocache_end) { + pgd = pgd_offset_k(vaddr); + pmd = srmmu_pmd_offset(__nocache_fix(pgd), vaddr); + pte = srmmu_pte_offset(__nocache_fix(pmd), vaddr); + + pteval = ((paddr >> 4) | SRMMU_ET_PTE | SRMMU_PRIV); + + if (srmmu_cache_pagetables) + pteval |= SRMMU_CACHE; + + srmmu_set_pte(__nocache_fix(pte), __pte(pteval)); + + vaddr += PAGE_SIZE; + paddr += PAGE_SIZE; + } + + flush_cache_all(); + flush_tlb_all(); +} + +static inline pgd_t *srmmu_get_pgd_fast(void) +{ + pgd_t *pgd = NULL; + + pgd = (pgd_t *)__srmmu_get_nocache(SRMMU_PGD_TABLE_SIZE, SRMMU_PGD_TABLE_SIZE); + if (pgd) { + pgd_t *init = pgd_offset_k(0); + memset(pgd, 0, USER_PTRS_PER_PGD * sizeof(pgd_t)); + memcpy(pgd + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD, + (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)); + } + + return pgd; +} + +static void srmmu_free_pgd_fast(pgd_t *pgd) +{ + srmmu_free_nocache((unsigned long)pgd, SRMMU_PGD_TABLE_SIZE); +} + +static pmd_t *srmmu_pmd_alloc_one(struct mm_struct *mm, unsigned long address) +{ + return (pmd_t *)srmmu_get_nocache(SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); +} + +static void srmmu_pmd_free(pmd_t * pmd) +{ + srmmu_free_nocache((unsigned long)pmd, SRMMU_PMD_TABLE_SIZE); +} + +/* + * Hardware needs alignment to 256 only, but we align to whole page size + * to reduce fragmentation problems due to the buddy principle. + * XXX Provide actual fragmentation statistics in /proc. + * + * Alignments up to the page size are the same for physical and virtual + * addresses of the nocache area. + */ +static pte_t * +srmmu_pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address) +{ + return (pte_t *)srmmu_get_nocache(PTE_SIZE, PTE_SIZE); +} + +static struct page * +srmmu_pte_alloc_one(struct mm_struct *mm, unsigned long address) +{ + unsigned long pte; + + if ((pte = (unsigned long)srmmu_pte_alloc_one_kernel(mm, address)) == 0) + return NULL; + return pfn_to_page( __nocache_pa(pte) >> PAGE_SHIFT ); +} + +static void srmmu_free_pte_fast(pte_t *pte) +{ + srmmu_free_nocache((unsigned long)pte, PTE_SIZE); +} + +static void srmmu_pte_free(struct page *pte) +{ + unsigned long p; + + p = (unsigned long)page_address(pte); /* Cached address (for test) */ + if (p == 0) + BUG(); + p = page_to_pfn(pte) << PAGE_SHIFT; /* Physical address */ + p = (unsigned long) __nocache_va(p); /* Nocached virtual */ + srmmu_free_nocache(p, PTE_SIZE); +} + +/* + */ +static inline void alloc_context(struct mm_struct *old_mm, 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 == old_mm) + ctxp = ctxp->next; + if(ctxp == &ctx_used) + panic("out of mmu contexts"); + flush_cache_mm(ctxp->ctx_mm); + flush_tlb_mm(ctxp->ctx_mm); + 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; +} + +static inline void free_context(int context) +{ + struct ctx_list *ctx_old; + + ctx_old = ctx_list_pool + context; + remove_from_ctx_list(ctx_old); + add_to_free_ctxlist(ctx_old); +} + + +static void srmmu_switch_mm(struct mm_struct *old_mm, struct mm_struct *mm, + struct task_struct *tsk, int cpu) +{ + if(mm->context == NO_CONTEXT) { + spin_lock(&srmmu_context_spinlock); + alloc_context(old_mm, mm); + spin_unlock(&srmmu_context_spinlock); + srmmu_ctxd_set(&srmmu_context_table[mm->context], mm->pgd); + } + + if (is_hypersparc) + hyper_flush_whole_icache(); + + srmmu_set_context(mm->context); +} + +/* Low level IO area allocation on the SRMMU. */ +static inline void srmmu_mapioaddr(unsigned long physaddr, + unsigned long virt_addr, int bus_type) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + unsigned long tmp; + + physaddr &= PAGE_MASK; + pgdp = pgd_offset_k(virt_addr); + pmdp = srmmu_pmd_offset(pgdp, virt_addr); + ptep = srmmu_pte_offset(pmdp, virt_addr); + tmp = (physaddr >> 4) | SRMMU_ET_PTE; + + /* + * I need to test whether this is consistent over all + * sun4m's. The bus_type represents the upper 4 bits of + * 36-bit physical address on the I/O space lines... + */ + tmp |= (bus_type << 28); + tmp |= SRMMU_PRIV; + __flush_page_to_ram(virt_addr); + srmmu_set_pte(ptep, __pte(tmp)); +} + +static void srmmu_mapiorange(unsigned int bus, unsigned long xpa, + unsigned long xva, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + srmmu_mapioaddr(xpa, xva, bus); + xva += PAGE_SIZE; + xpa += PAGE_SIZE; + } + flush_tlb_all(); +} + +static inline void srmmu_unmapioaddr(unsigned long virt_addr) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + pgdp = pgd_offset_k(virt_addr); + pmdp = srmmu_pmd_offset(pgdp, virt_addr); + ptep = srmmu_pte_offset(pmdp, virt_addr); + + /* No need to flush uncacheable page. */ + srmmu_pte_clear(ptep); +} + +static void srmmu_unmapiorange(unsigned long virt_addr, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + srmmu_unmapioaddr(virt_addr); + virt_addr += PAGE_SIZE; + } + flush_tlb_all(); +} + +/* + * On the SRMMU we do not have the problems with limited tlb entries + * for mapping kernel pages, so we just take things from the free page + * pool. As a side effect we are putting a little too much pressure + * on the gfp() subsystem. This setup also makes the logic of the + * iommu mapping code a lot easier as we can transparently handle + * mappings on the kernel stack without any special code as we did + * need on the sun4c. + */ +struct thread_info *srmmu_alloc_thread_info(void) +{ + struct thread_info *ret; + + ret = (struct thread_info *)__get_free_pages(GFP_KERNEL, + THREAD_INFO_ORDER); +#ifdef CONFIG_DEBUG_STACK_USAGE + if (ret) + memset(ret, 0, PAGE_SIZE << THREAD_INFO_ORDER); +#endif /* DEBUG_STACK_USAGE */ + + return ret; +} + +static void srmmu_free_thread_info(struct thread_info *ti) +{ + free_pages((unsigned long)ti, THREAD_INFO_ORDER); +} + +/* tsunami.S */ +extern void tsunami_flush_cache_all(void); +extern void tsunami_flush_cache_mm(struct mm_struct *mm); +extern void tsunami_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void tsunami_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void tsunami_flush_page_to_ram(unsigned long page); +extern void tsunami_flush_page_for_dma(unsigned long page); +extern void tsunami_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr); +extern void tsunami_flush_tlb_all(void); +extern void tsunami_flush_tlb_mm(struct mm_struct *mm); +extern void tsunami_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void tsunami_flush_tlb_page(struct vm_area_struct *vma, unsigned long page); +extern void tsunami_setup_blockops(void); + +/* + * Workaround, until we find what's going on with Swift. When low on memory, + * it sometimes loops in fault/handle_mm_fault incl. flush_tlb_page to find + * out it is already in page tables/ fault again on the same instruction. + * I really don't understand it, have checked it and contexts + * are right, flush_tlb_all is done as well, and it faults again... + * Strange. -jj + * + * The following code is a deadwood that may be necessary when + * we start to make precise page flushes again. --zaitcev + */ +static void swift_update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t pte) +{ +#if 0 + static unsigned long last; + unsigned int val; + /* unsigned int n; */ + + if (address == last) { + val = srmmu_hwprobe(address); + if (val != 0 && pte_val(pte) != val) { + printk("swift_update_mmu_cache: " + "addr %lx put %08x probed %08x from %p\n", + address, pte_val(pte), val, + __builtin_return_address(0)); + srmmu_flush_whole_tlb(); + } + } + last = address; +#endif +} + +/* swift.S */ +extern void swift_flush_cache_all(void); +extern void swift_flush_cache_mm(struct mm_struct *mm); +extern void swift_flush_cache_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end); +extern void swift_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void swift_flush_page_to_ram(unsigned long page); +extern void swift_flush_page_for_dma(unsigned long page); +extern void swift_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr); +extern void swift_flush_tlb_all(void); +extern void swift_flush_tlb_mm(struct mm_struct *mm); +extern void swift_flush_tlb_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end); +extern void swift_flush_tlb_page(struct vm_area_struct *vma, unsigned long page); + +#if 0 /* P3: deadwood to debug precise flushes on Swift. */ +void swift_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + int cctx, ctx1; + + page &= PAGE_MASK; + if ((ctx1 = vma->vm_mm->context) != -1) { + cctx = srmmu_get_context(); +/* Is context # ever different from current context? P3 */ + if (cctx != ctx1) { + printk("flush ctx %02x curr %02x\n", ctx1, cctx); + srmmu_set_context(ctx1); + swift_flush_page(page); + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : : + "r" (page), "i" (ASI_M_FLUSH_PROBE)); + srmmu_set_context(cctx); + } else { + /* Rm. prot. bits from virt. c. */ + /* swift_flush_cache_all(); */ + /* swift_flush_cache_page(vma, page); */ + swift_flush_page(page); + + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : : + "r" (page), "i" (ASI_M_FLUSH_PROBE)); + /* same as above: srmmu_flush_tlb_page() */ + } + } +} +#endif + +/* + * The following are all MBUS based SRMMU modules, and therefore could + * be found in a multiprocessor configuration. On the whole, these + * chips seems to be much more touchy about DVMA and page tables + * with respect to cache coherency. + */ + +/* Cypress flushes. */ +static void cypress_flush_cache_all(void) +{ + volatile unsigned long cypress_sucks; + unsigned long faddr, tagval; + + flush_user_windows(); + for(faddr = 0; faddr < 0x10000; faddr += 0x20) { + __asm__ __volatile__("lda [%1 + %2] %3, %0\n\t" : + "=r" (tagval) : + "r" (faddr), "r" (0x40000), + "i" (ASI_M_DATAC_TAG)); + + /* If modified and valid, kick it. */ + if((tagval & 0x60) == 0x60) + cypress_sucks = *(unsigned long *)(0xf0020000 + faddr); + } +} + +static void cypress_flush_cache_mm(struct mm_struct *mm) +{ + register unsigned long a, b, c, d, e, f, g; + unsigned long flags, faddr; + int octx; + + FLUSH_BEGIN(mm) + flush_user_windows(); + local_irq_save(flags); + octx = srmmu_get_context(); + srmmu_set_context(mm->context); + a = 0x20; b = 0x40; c = 0x60; + d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + + faddr = (0x10000 - 0x100); + goto inside; + do { + faddr -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (faddr), "i" (ASI_M_FLUSH_CTX), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while(faddr); + srmmu_set_context(octx); + local_irq_restore(flags); + FLUSH_END +} + +static void cypress_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + register unsigned long a, b, c, d, e, f, g; + unsigned long flags, faddr; + int octx; + + FLUSH_BEGIN(mm) + flush_user_windows(); + local_irq_save(flags); + octx = srmmu_get_context(); + srmmu_set_context(mm->context); + a = 0x20; b = 0x40; c = 0x60; + d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + + start &= SRMMU_REAL_PMD_MASK; + while(start < end) { + faddr = (start + (0x10000 - 0x100)); + goto inside; + do { + faddr -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (faddr), + "i" (ASI_M_FLUSH_SEG), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while (faddr != start); + start += SRMMU_REAL_PMD_SIZE; + } + srmmu_set_context(octx); + local_irq_restore(flags); + FLUSH_END +} + +static void cypress_flush_cache_page(struct vm_area_struct *vma, unsigned long page) +{ + register unsigned long a, b, c, d, e, f, g; + struct mm_struct *mm = vma->vm_mm; + unsigned long flags, line; + int octx; + + FLUSH_BEGIN(mm) + flush_user_windows(); + local_irq_save(flags); + octx = srmmu_get_context(); + srmmu_set_context(mm->context); + a = 0x20; b = 0x40; c = 0x60; + d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + + page &= PAGE_MASK; + line = (page + PAGE_SIZE) - 0x100; + goto inside; + do { + line -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (line), + "i" (ASI_M_FLUSH_PAGE), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while(line != page); + srmmu_set_context(octx); + local_irq_restore(flags); + FLUSH_END +} + +/* Cypress is copy-back, at least that is how we configure it. */ +static void cypress_flush_page_to_ram(unsigned long page) +{ + register unsigned long a, b, c, d, e, f, g; + unsigned long line; + + a = 0x20; b = 0x40; c = 0x60; d = 0x80; e = 0xa0; f = 0xc0; g = 0xe0; + page &= PAGE_MASK; + line = (page + PAGE_SIZE) - 0x100; + goto inside; + do { + line -= 0x100; + inside: + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" + "sta %%g0, [%0 + %2] %1\n\t" + "sta %%g0, [%0 + %3] %1\n\t" + "sta %%g0, [%0 + %4] %1\n\t" + "sta %%g0, [%0 + %5] %1\n\t" + "sta %%g0, [%0 + %6] %1\n\t" + "sta %%g0, [%0 + %7] %1\n\t" + "sta %%g0, [%0 + %8] %1\n\t" : : + "r" (line), + "i" (ASI_M_FLUSH_PAGE), + "r" (a), "r" (b), "r" (c), "r" (d), + "r" (e), "r" (f), "r" (g)); + } while(line != page); +} + +/* Cypress is also IO cache coherent. */ +static void cypress_flush_page_for_dma(unsigned long page) +{ +} + +/* Cypress has unified L2 VIPT, from which both instructions and data + * are stored. It does not have an onboard icache of any sort, therefore + * no flush is necessary. + */ +static void cypress_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr) +{ +} + +static void cypress_flush_tlb_all(void) +{ + srmmu_flush_whole_tlb(); +} + +static void cypress_flush_tlb_mm(struct mm_struct *mm) +{ + FLUSH_BEGIN(mm) + __asm__ __volatile__( + "lda [%0] %3, %%g5\n\t" + "sta %2, [%0] %3\n\t" + "sta %%g0, [%1] %4\n\t" + "sta %%g5, [%0] %3\n" + : /* no outputs */ + : "r" (SRMMU_CTX_REG), "r" (0x300), "r" (mm->context), + "i" (ASI_M_MMUREGS), "i" (ASI_M_FLUSH_PROBE) + : "g5"); + FLUSH_END +} + +static void cypress_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + unsigned long size; + + FLUSH_BEGIN(mm) + start &= SRMMU_PGDIR_MASK; + size = SRMMU_PGDIR_ALIGN(end) - start; + __asm__ __volatile__( + "lda [%0] %5, %%g5\n\t" + "sta %1, [%0] %5\n" + "1:\n\t" + "subcc %3, %4, %3\n\t" + "bne 1b\n\t" + " sta %%g0, [%2 + %3] %6\n\t" + "sta %%g5, [%0] %5\n" + : /* no outputs */ + : "r" (SRMMU_CTX_REG), "r" (mm->context), "r" (start | 0x200), + "r" (size), "r" (SRMMU_PGDIR_SIZE), "i" (ASI_M_MMUREGS), + "i" (ASI_M_FLUSH_PROBE) + : "g5", "cc"); + FLUSH_END +} + +static void cypress_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + + FLUSH_BEGIN(mm) + __asm__ __volatile__( + "lda [%0] %3, %%g5\n\t" + "sta %1, [%0] %3\n\t" + "sta %%g0, [%2] %4\n\t" + "sta %%g5, [%0] %3\n" + : /* no outputs */ + : "r" (SRMMU_CTX_REG), "r" (mm->context), "r" (page & PAGE_MASK), + "i" (ASI_M_MMUREGS), "i" (ASI_M_FLUSH_PROBE) + : "g5"); + FLUSH_END +} + +/* viking.S */ +extern void viking_flush_cache_all(void); +extern void viking_flush_cache_mm(struct mm_struct *mm); +extern void viking_flush_cache_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void viking_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void viking_flush_page_to_ram(unsigned long page); +extern void viking_flush_page_for_dma(unsigned long page); +extern void viking_flush_sig_insns(struct mm_struct *mm, unsigned long addr); +extern void viking_flush_page(unsigned long page); +extern void viking_mxcc_flush_page(unsigned long page); +extern void viking_flush_tlb_all(void); +extern void viking_flush_tlb_mm(struct mm_struct *mm); +extern void viking_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void viking_flush_tlb_page(struct vm_area_struct *vma, + unsigned long page); +extern void sun4dsmp_flush_tlb_all(void); +extern void sun4dsmp_flush_tlb_mm(struct mm_struct *mm); +extern void sun4dsmp_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void sun4dsmp_flush_tlb_page(struct vm_area_struct *vma, + unsigned long page); + +/* hypersparc.S */ +extern void hypersparc_flush_cache_all(void); +extern void hypersparc_flush_cache_mm(struct mm_struct *mm); +extern void hypersparc_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void hypersparc_flush_cache_page(struct vm_area_struct *vma, unsigned long page); +extern void hypersparc_flush_page_to_ram(unsigned long page); +extern void hypersparc_flush_page_for_dma(unsigned long page); +extern void hypersparc_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr); +extern void hypersparc_flush_tlb_all(void); +extern void hypersparc_flush_tlb_mm(struct mm_struct *mm); +extern void hypersparc_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); +extern void hypersparc_flush_tlb_page(struct vm_area_struct *vma, unsigned long page); +extern void hypersparc_setup_blockops(void); + +/* + * NOTE: All of this startup code assumes the low 16mb (approx.) of + * kernel mappings are done with one single contiguous chunk of + * ram. On small ram machines (classics mainly) we only get + * around 8mb mapped for us. + */ + +void __init early_pgtable_allocfail(char *type) +{ + prom_printf("inherit_prom_mappings: Cannot alloc kernel %s.\n", type); + prom_halt(); +} + +void __init srmmu_early_allocate_ptable_skeleton(unsigned long start, unsigned long end) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + while(start < end) { + pgdp = pgd_offset_k(start); + if(srmmu_pgd_none(*(pgd_t *)__nocache_fix(pgdp))) { + pmdp = (pmd_t *) __srmmu_get_nocache( + SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); + if (pmdp == NULL) + early_pgtable_allocfail("pmd"); + memset(__nocache_fix(pmdp), 0, SRMMU_PMD_TABLE_SIZE); + srmmu_pgd_set(__nocache_fix(pgdp), pmdp); + } + pmdp = srmmu_pmd_offset(__nocache_fix(pgdp), start); + if(srmmu_pmd_none(*(pmd_t *)__nocache_fix(pmdp))) { + ptep = (pte_t *)__srmmu_get_nocache(PTE_SIZE, PTE_SIZE); + if (ptep == NULL) + early_pgtable_allocfail("pte"); + memset(__nocache_fix(ptep), 0, PTE_SIZE); + srmmu_pmd_set(__nocache_fix(pmdp), ptep); + } + if (start > (0xffffffffUL - PMD_SIZE)) + break; + start = (start + PMD_SIZE) & PMD_MASK; + } +} + +void __init srmmu_allocate_ptable_skeleton(unsigned long start, unsigned long end) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + + while(start < end) { + pgdp = pgd_offset_k(start); + if(srmmu_pgd_none(*pgdp)) { + pmdp = (pmd_t *)__srmmu_get_nocache(SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); + if (pmdp == NULL) + early_pgtable_allocfail("pmd"); + memset(pmdp, 0, SRMMU_PMD_TABLE_SIZE); + srmmu_pgd_set(pgdp, pmdp); + } + pmdp = srmmu_pmd_offset(pgdp, start); + if(srmmu_pmd_none(*pmdp)) { + ptep = (pte_t *) __srmmu_get_nocache(PTE_SIZE, + PTE_SIZE); + if (ptep == NULL) + early_pgtable_allocfail("pte"); + memset(ptep, 0, PTE_SIZE); + srmmu_pmd_set(pmdp, ptep); + } + if (start > (0xffffffffUL - PMD_SIZE)) + break; + start = (start + PMD_SIZE) & PMD_MASK; + } +} + +/* + * This is much cleaner than poking around physical address space + * looking at the prom's page table directly which is what most + * other OS's do. Yuck... this is much better. + */ +void __init srmmu_inherit_prom_mappings(unsigned long start,unsigned long end) +{ + pgd_t *pgdp; + pmd_t *pmdp; + pte_t *ptep; + int what = 0; /* 0 = normal-pte, 1 = pmd-level pte, 2 = pgd-level pte */ + unsigned long prompte; + + while(start <= end) { + if (start == 0) + break; /* probably wrap around */ + if(start == 0xfef00000) + start = KADB_DEBUGGER_BEGVM; + if(!(prompte = srmmu_hwprobe(start))) { + start += PAGE_SIZE; + continue; + } + + /* A red snapper, see what it really is. */ + what = 0; + + if(!(start & ~(SRMMU_REAL_PMD_MASK))) { + if(srmmu_hwprobe((start-PAGE_SIZE) + SRMMU_REAL_PMD_SIZE) == prompte) + what = 1; + } + + if(!(start & ~(SRMMU_PGDIR_MASK))) { + if(srmmu_hwprobe((start-PAGE_SIZE) + SRMMU_PGDIR_SIZE) == + prompte) + what = 2; + } + + pgdp = pgd_offset_k(start); + if(what == 2) { + *(pgd_t *)__nocache_fix(pgdp) = __pgd(prompte); + start += SRMMU_PGDIR_SIZE; + continue; + } + if(srmmu_pgd_none(*(pgd_t *)__nocache_fix(pgdp))) { + pmdp = (pmd_t *)__srmmu_get_nocache(SRMMU_PMD_TABLE_SIZE, SRMMU_PMD_TABLE_SIZE); + if (pmdp == NULL) + early_pgtable_allocfail("pmd"); + memset(__nocache_fix(pmdp), 0, SRMMU_PMD_TABLE_SIZE); + srmmu_pgd_set(__nocache_fix(pgdp), pmdp); + } + pmdp = srmmu_pmd_offset(__nocache_fix(pgdp), start); + if(srmmu_pmd_none(*(pmd_t *)__nocache_fix(pmdp))) { + ptep = (pte_t *) __srmmu_get_nocache(PTE_SIZE, + PTE_SIZE); + if (ptep == NULL) + early_pgtable_allocfail("pte"); + memset(__nocache_fix(ptep), 0, PTE_SIZE); + srmmu_pmd_set(__nocache_fix(pmdp), ptep); + } + if(what == 1) { + /* + * We bend the rule where all 16 PTPs in a pmd_t point + * inside the same PTE page, and we leak a perfectly + * good hardware PTE piece. Alternatives seem worse. + */ + unsigned int x; /* Index of HW PMD in soft cluster */ + x = (start >> PMD_SHIFT) & 15; + *(unsigned long *)__nocache_fix(&pmdp->pmdv[x]) = prompte; + start += SRMMU_REAL_PMD_SIZE; + continue; + } + ptep = srmmu_pte_offset(__nocache_fix(pmdp), start); + *(pte_t *)__nocache_fix(ptep) = __pte(prompte); + start += PAGE_SIZE; + } +} + +#define KERNEL_PTE(page_shifted) ((page_shifted)|SRMMU_CACHE|SRMMU_PRIV|SRMMU_VALID) + +/* Create a third-level SRMMU 16MB page mapping. */ +static void __init do_large_mapping(unsigned long vaddr, unsigned long phys_base) +{ + pgd_t *pgdp = pgd_offset_k(vaddr); + unsigned long big_pte; + + big_pte = KERNEL_PTE(phys_base >> 4); + *(pgd_t *)__nocache_fix(pgdp) = __pgd(big_pte); +} + +/* Map sp_bank entry SP_ENTRY, starting at virtual address VBASE. */ +static unsigned long __init map_spbank(unsigned long vbase, int sp_entry) +{ + unsigned long pstart = (sp_banks[sp_entry].base_addr & SRMMU_PGDIR_MASK); + unsigned long vstart = (vbase & SRMMU_PGDIR_MASK); + unsigned long vend = SRMMU_PGDIR_ALIGN(vbase + sp_banks[sp_entry].num_bytes); + /* Map "low" memory only */ + const unsigned long min_vaddr = PAGE_OFFSET; + const unsigned long max_vaddr = PAGE_OFFSET + SRMMU_MAXMEM; + + if (vstart < min_vaddr || vstart >= max_vaddr) + return vstart; + + if (vend > max_vaddr || vend < min_vaddr) + vend = max_vaddr; + + while(vstart < vend) { + do_large_mapping(vstart, pstart); + vstart += SRMMU_PGDIR_SIZE; pstart += SRMMU_PGDIR_SIZE; + } + return vstart; +} + +static inline void memprobe_error(char *msg) +{ + prom_printf(msg); + prom_printf("Halting now...\n"); + prom_halt(); +} + +static inline void map_kernel(void) +{ + int i; + + if (phys_base > 0) { + do_large_mapping(PAGE_OFFSET, phys_base); + } + + for (i = 0; sp_banks[i].num_bytes != 0; i++) { + map_spbank((unsigned long)__va(sp_banks[i].base_addr), i); + } + + BTFIXUPSET_SIMM13(user_ptrs_per_pgd, PAGE_OFFSET / SRMMU_PGDIR_SIZE); +} + +/* Paging initialization on the Sparc Reference MMU. */ +extern void sparc_context_init(int); + +void (*poke_srmmu)(void) __initdata = NULL; + +extern unsigned long bootmem_init(unsigned long *pages_avail); + +void __init srmmu_paging_init(void) +{ + int i, cpunode; + char node_str[128]; + pgd_t *pgd; + pmd_t *pmd; + pte_t *pte; + unsigned long pages_avail; + + sparc_iomap.start = SUN4M_IOBASE_VADDR; /* 16MB of IOSPACE on all sun4m's. */ + + if (sparc_cpu_model == sun4d) + num_contexts = 65536; /* We know it is Viking */ + else { + /* Find the number of contexts on the srmmu. */ + cpunode = prom_getchild(prom_root_node); + num_contexts = 0; + while(cpunode != 0) { + prom_getstring(cpunode, "device_type", node_str, sizeof(node_str)); + if(!strcmp(node_str, "cpu")) { + num_contexts = prom_getintdefault(cpunode, "mmu-nctx", 0x8); + break; + } + cpunode = prom_getsibling(cpunode); + } + } + + if(!num_contexts) { + prom_printf("Something wrong, can't find cpu node in paging_init.\n"); + prom_halt(); + } + + pages_avail = 0; + last_valid_pfn = bootmem_init(&pages_avail); + + srmmu_nocache_calcsize(); + srmmu_nocache_init(); + srmmu_inherit_prom_mappings(0xfe400000,(LINUX_OPPROM_ENDVM-PAGE_SIZE)); + map_kernel(); + + /* ctx table has to be physically aligned to its size */ + srmmu_context_table = (ctxd_t *)__srmmu_get_nocache(num_contexts*sizeof(ctxd_t), num_contexts*sizeof(ctxd_t)); + srmmu_ctx_table_phys = (ctxd_t *)__nocache_pa((unsigned long)srmmu_context_table); + + for(i = 0; i < num_contexts; i++) + srmmu_ctxd_set((ctxd_t *)__nocache_fix(&srmmu_context_table[i]), srmmu_swapper_pg_dir); + + flush_cache_all(); + srmmu_set_ctable_ptr((unsigned long)srmmu_ctx_table_phys); + flush_tlb_all(); + poke_srmmu(); + +#ifdef CONFIG_SUN_IO + srmmu_allocate_ptable_skeleton(sparc_iomap.start, IOBASE_END); + srmmu_allocate_ptable_skeleton(DVMA_VADDR, DVMA_END); +#endif + + srmmu_allocate_ptable_skeleton( + __fix_to_virt(__end_of_fixed_addresses - 1), FIXADDR_TOP); + srmmu_allocate_ptable_skeleton(PKMAP_BASE, PKMAP_END); + + pgd = pgd_offset_k(PKMAP_BASE); + pmd = srmmu_pmd_offset(pgd, PKMAP_BASE); + pte = srmmu_pte_offset(pmd, PKMAP_BASE); + pkmap_page_table = pte; + + flush_cache_all(); + flush_tlb_all(); + + sparc_context_init(num_contexts); + + kmap_init(); + + { + unsigned long zones_size[MAX_NR_ZONES]; + unsigned long zholes_size[MAX_NR_ZONES]; + unsigned long npages; + int znum; + + for (znum = 0; znum < MAX_NR_ZONES; znum++) + zones_size[znum] = zholes_size[znum] = 0; + + npages = max_low_pfn - pfn_base; + + zones_size[ZONE_DMA] = npages; + zholes_size[ZONE_DMA] = npages - pages_avail; + + npages = highend_pfn - max_low_pfn; + zones_size[ZONE_HIGHMEM] = npages; + zholes_size[ZONE_HIGHMEM] = npages - calc_highpages(); + + free_area_init_node(0, &contig_page_data, zones_size, + pfn_base, zholes_size); + } +} + +static void srmmu_mmu_info(struct seq_file *m) +{ + seq_printf(m, + "MMU type\t: %s\n" + "contexts\t: %d\n" + "nocache total\t: %ld\n" + "nocache used\t: %d\n", + srmmu_name, + num_contexts, + srmmu_nocache_size, + srmmu_nocache_map.used << SRMMU_NOCACHE_BITMAP_SHIFT); +} + +static void srmmu_update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t pte) +{ +} + +static void srmmu_destroy_context(struct mm_struct *mm) +{ + + if(mm->context != NO_CONTEXT) { + flush_cache_mm(mm); + srmmu_ctxd_set(&srmmu_context_table[mm->context], srmmu_swapper_pg_dir); + flush_tlb_mm(mm); + spin_lock(&srmmu_context_spinlock); + free_context(mm->context); + spin_unlock(&srmmu_context_spinlock); + mm->context = NO_CONTEXT; + } +} + +/* Init various srmmu chip types. */ +static void __init srmmu_is_bad(void) +{ + prom_printf("Could not determine SRMMU chip type.\n"); + prom_halt(); +} + +static void __init init_vac_layout(void) +{ + int nd, cache_lines; + char node_str[128]; +#ifdef CONFIG_SMP + int cpu = 0; + unsigned long max_size = 0; + unsigned long min_line_size = 0x10000000; +#endif + + nd = prom_getchild(prom_root_node); + while((nd = prom_getsibling(nd)) != 0) { + prom_getstring(nd, "device_type", node_str, sizeof(node_str)); + if(!strcmp(node_str, "cpu")) { + vac_line_size = prom_getint(nd, "cache-line-size"); + if (vac_line_size == -1) { + prom_printf("can't determine cache-line-size, " + "halting.\n"); + prom_halt(); + } + cache_lines = prom_getint(nd, "cache-nlines"); + if (cache_lines == -1) { + prom_printf("can't determine cache-nlines, halting.\n"); + prom_halt(); + } + + vac_cache_size = cache_lines * vac_line_size; +#ifdef CONFIG_SMP + if(vac_cache_size > max_size) + max_size = vac_cache_size; + if(vac_line_size < min_line_size) + min_line_size = vac_line_size; + cpu++; + if (cpu >= NR_CPUS || !cpu_online(cpu)) + break; +#else + break; +#endif + } + } + if(nd == 0) { + prom_printf("No CPU nodes found, halting.\n"); + prom_halt(); + } +#ifdef CONFIG_SMP + vac_cache_size = max_size; + vac_line_size = min_line_size; +#endif + printk("SRMMU: Using VAC size of %d bytes, line size %d bytes.\n", + (int)vac_cache_size, (int)vac_line_size); +} + +static void __init poke_hypersparc(void) +{ + volatile unsigned long clear; + unsigned long mreg = srmmu_get_mmureg(); + + hyper_flush_unconditional_combined(); + + mreg &= ~(HYPERSPARC_CWENABLE); + mreg |= (HYPERSPARC_CENABLE | HYPERSPARC_WBENABLE); + mreg |= (HYPERSPARC_CMODE); + + srmmu_set_mmureg(mreg); + +#if 0 /* XXX I think this is bad news... -DaveM */ + hyper_clear_all_tags(); +#endif + + put_ross_icr(HYPERSPARC_ICCR_FTD | HYPERSPARC_ICCR_ICE); + hyper_flush_whole_icache(); + clear = srmmu_get_faddr(); + clear = srmmu_get_fstatus(); +} + +static void __init init_hypersparc(void) +{ + srmmu_name = "ROSS HyperSparc"; + srmmu_modtype = HyperSparc; + + init_vac_layout(); + + is_hypersparc = 1; + + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_all, hypersparc_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, hypersparc_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, hypersparc_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, hypersparc_flush_cache_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_tlb_all, hypersparc_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, hypersparc_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, hypersparc_flush_tlb_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, hypersparc_flush_tlb_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, hypersparc_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, hypersparc_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, hypersparc_flush_page_for_dma, BTFIXUPCALL_NOP); + + + poke_srmmu = poke_hypersparc; + + hypersparc_setup_blockops(); +} + +static void __init poke_cypress(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + unsigned long faddr, tagval; + volatile unsigned long cypress_sucks; + volatile unsigned long clear; + + clear = srmmu_get_faddr(); + clear = srmmu_get_fstatus(); + + if (!(mreg & CYPRESS_CENABLE)) { + for(faddr = 0x0; faddr < 0x10000; faddr += 20) { + __asm__ __volatile__("sta %%g0, [%0 + %1] %2\n\t" + "sta %%g0, [%0] %2\n\t" : : + "r" (faddr), "r" (0x40000), + "i" (ASI_M_DATAC_TAG)); + } + } else { + for(faddr = 0; faddr < 0x10000; faddr += 0x20) { + __asm__ __volatile__("lda [%1 + %2] %3, %0\n\t" : + "=r" (tagval) : + "r" (faddr), "r" (0x40000), + "i" (ASI_M_DATAC_TAG)); + + /* If modified and valid, kick it. */ + if((tagval & 0x60) == 0x60) + cypress_sucks = *(unsigned long *) + (0xf0020000 + faddr); + } + } + + /* And one more, for our good neighbor, Mr. Broken Cypress. */ + clear = srmmu_get_faddr(); + clear = srmmu_get_fstatus(); + + mreg |= (CYPRESS_CENABLE | CYPRESS_CMODE); + srmmu_set_mmureg(mreg); +} + +static void __init init_cypress_common(void) +{ + init_vac_layout(); + + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_all, cypress_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, cypress_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, cypress_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, cypress_flush_cache_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_tlb_all, cypress_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, cypress_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, cypress_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, cypress_flush_tlb_range, BTFIXUPCALL_NORM); + + + BTFIXUPSET_CALL(__flush_page_to_ram, cypress_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, cypress_flush_sig_insns, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_page_for_dma, cypress_flush_page_for_dma, BTFIXUPCALL_NOP); + + poke_srmmu = poke_cypress; +} + +static void __init init_cypress_604(void) +{ + srmmu_name = "ROSS Cypress-604(UP)"; + srmmu_modtype = Cypress; + init_cypress_common(); +} + +static void __init init_cypress_605(unsigned long mrev) +{ + srmmu_name = "ROSS Cypress-605(MP)"; + if(mrev == 0xe) { + srmmu_modtype = Cypress_vE; + hwbug_bitmask |= HWBUG_COPYBACK_BROKEN; + } else { + if(mrev == 0xd) { + srmmu_modtype = Cypress_vD; + hwbug_bitmask |= HWBUG_ASIFLUSH_BROKEN; + } else { + srmmu_modtype = Cypress; + } + } + init_cypress_common(); +} + +static void __init poke_swift(void) +{ + unsigned long mreg; + + /* Clear any crap from the cache or else... */ + swift_flush_cache_all(); + + /* Enable I & D caches */ + mreg = srmmu_get_mmureg(); + mreg |= (SWIFT_IE | SWIFT_DE); + /* + * The Swift branch folding logic is completely broken. At + * trap time, if things are just right, if can mistakenly + * think that a trap is coming from kernel mode when in fact + * it is coming from user mode (it mis-executes the branch in + * the trap code). So you see things like crashme completely + * hosing your machine which is completely unacceptable. Turn + * this shit off... nice job Fujitsu. + */ + mreg &= ~(SWIFT_BF); + srmmu_set_mmureg(mreg); +} + +#define SWIFT_MASKID_ADDR 0x10003018 +static void __init init_swift(void) +{ + unsigned long swift_rev; + + __asm__ __volatile__("lda [%1] %2, %0\n\t" + "srl %0, 0x18, %0\n\t" : + "=r" (swift_rev) : + "r" (SWIFT_MASKID_ADDR), "i" (ASI_M_BYPASS)); + srmmu_name = "Fujitsu Swift"; + switch(swift_rev) { + case 0x11: + case 0x20: + case 0x23: + case 0x30: + srmmu_modtype = Swift_lots_o_bugs; + hwbug_bitmask |= (HWBUG_KERN_ACCBROKEN | HWBUG_KERN_CBITBROKEN); + /* + * Gee george, I wonder why Sun is so hush hush about + * this hardware bug... really braindamage stuff going + * on here. However I think we can find a way to avoid + * all of the workaround overhead under Linux. Basically, + * any page fault can cause kernel pages to become user + * accessible (the mmu gets confused and clears some of + * the ACC bits in kernel ptes). Aha, sounds pretty + * horrible eh? But wait, after extensive testing it appears + * that if you use pgd_t level large kernel pte's (like the + * 4MB pages on the Pentium) the bug does not get tripped + * at all. This avoids almost all of the major overhead. + * Welcome to a world where your vendor tells you to, + * "apply this kernel patch" instead of "sorry for the + * broken hardware, send it back and we'll give you + * properly functioning parts" + */ + break; + case 0x25: + case 0x31: + srmmu_modtype = Swift_bad_c; + hwbug_bitmask |= HWBUG_KERN_CBITBROKEN; + /* + * You see Sun allude to this hardware bug but never + * admit things directly, they'll say things like, + * "the Swift chip cache problems" or similar. + */ + break; + default: + srmmu_modtype = Swift_ok; + break; + }; + + BTFIXUPSET_CALL(flush_cache_all, swift_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, swift_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, swift_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, swift_flush_cache_range, BTFIXUPCALL_NORM); + + + BTFIXUPSET_CALL(flush_tlb_all, swift_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, swift_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, swift_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, swift_flush_tlb_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, swift_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, swift_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, swift_flush_page_for_dma, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(update_mmu_cache, swift_update_mmu_cache, BTFIXUPCALL_NORM); + + flush_page_for_dma_global = 0; + + /* + * Are you now convinced that the Swift is one of the + * biggest VLSI abortions of all time? Bravo Fujitsu! + * Fujitsu, the !#?!%$'d up processor people. I bet if + * you examined the microcode of the Swift you'd find + * XXX's all over the place. + */ + poke_srmmu = poke_swift; +} + +static void turbosparc_flush_cache_all(void) +{ + flush_user_windows(); + turbosparc_idflash_clear(); +} + +static void turbosparc_flush_cache_mm(struct mm_struct *mm) +{ + FLUSH_BEGIN(mm) + flush_user_windows(); + turbosparc_idflash_clear(); + FLUSH_END +} + +static void turbosparc_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + FLUSH_BEGIN(vma->vm_mm) + flush_user_windows(); + turbosparc_idflash_clear(); + FLUSH_END +} + +static void turbosparc_flush_cache_page(struct vm_area_struct *vma, unsigned long page) +{ + FLUSH_BEGIN(vma->vm_mm) + flush_user_windows(); + if (vma->vm_flags & VM_EXEC) + turbosparc_flush_icache(); + turbosparc_flush_dcache(); + FLUSH_END +} + +/* TurboSparc is copy-back, if we turn it on, but this does not work. */ +static void turbosparc_flush_page_to_ram(unsigned long page) +{ +#ifdef TURBOSPARC_WRITEBACK + volatile unsigned long clear; + + if (srmmu_hwprobe(page)) + turbosparc_flush_page_cache(page); + clear = srmmu_get_fstatus(); +#endif +} + +static void turbosparc_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr) +{ +} + +static void turbosparc_flush_page_for_dma(unsigned long page) +{ + turbosparc_flush_dcache(); +} + +static void turbosparc_flush_tlb_all(void) +{ + srmmu_flush_whole_tlb(); +} + +static void turbosparc_flush_tlb_mm(struct mm_struct *mm) +{ + FLUSH_BEGIN(mm) + srmmu_flush_whole_tlb(); + FLUSH_END +} + +static void turbosparc_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + FLUSH_BEGIN(vma->vm_mm) + srmmu_flush_whole_tlb(); + FLUSH_END +} + +static void turbosparc_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + FLUSH_BEGIN(vma->vm_mm) + srmmu_flush_whole_tlb(); + FLUSH_END +} + + +static void __init poke_turbosparc(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + unsigned long ccreg; + + /* Clear any crap from the cache or else... */ + turbosparc_flush_cache_all(); + mreg &= ~(TURBOSPARC_ICENABLE | TURBOSPARC_DCENABLE); /* Temporarily disable I & D caches */ + mreg &= ~(TURBOSPARC_PCENABLE); /* Don't check parity */ + srmmu_set_mmureg(mreg); + + ccreg = turbosparc_get_ccreg(); + +#ifdef TURBOSPARC_WRITEBACK + ccreg |= (TURBOSPARC_SNENABLE); /* Do DVMA snooping in Dcache */ + ccreg &= ~(TURBOSPARC_uS2 | TURBOSPARC_WTENABLE); + /* Write-back D-cache, emulate VLSI + * abortion number three, not number one */ +#else + /* For now let's play safe, optimize later */ + ccreg |= (TURBOSPARC_SNENABLE | TURBOSPARC_WTENABLE); + /* Do DVMA snooping in Dcache, Write-thru D-cache */ + ccreg &= ~(TURBOSPARC_uS2); + /* Emulate VLSI abortion number three, not number one */ +#endif + + switch (ccreg & 7) { + case 0: /* No SE cache */ + case 7: /* Test mode */ + break; + default: + ccreg |= (TURBOSPARC_SCENABLE); + } + turbosparc_set_ccreg (ccreg); + + mreg |= (TURBOSPARC_ICENABLE | TURBOSPARC_DCENABLE); /* I & D caches on */ + mreg |= (TURBOSPARC_ICSNOOP); /* Icache snooping on */ + srmmu_set_mmureg(mreg); +} + +static void __init init_turbosparc(void) +{ + srmmu_name = "Fujitsu TurboSparc"; + srmmu_modtype = TurboSparc; + + BTFIXUPSET_CALL(flush_cache_all, turbosparc_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, turbosparc_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, turbosparc_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, turbosparc_flush_cache_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_tlb_all, turbosparc_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, turbosparc_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, turbosparc_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, turbosparc_flush_tlb_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, turbosparc_flush_page_to_ram, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_sig_insns, turbosparc_flush_sig_insns, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_page_for_dma, turbosparc_flush_page_for_dma, BTFIXUPCALL_NORM); + + poke_srmmu = poke_turbosparc; +} + +static void __init poke_tsunami(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + + tsunami_flush_icache(); + tsunami_flush_dcache(); + mreg &= ~TSUNAMI_ITD; + mreg |= (TSUNAMI_IENAB | TSUNAMI_DENAB); + srmmu_set_mmureg(mreg); +} + +static void __init init_tsunami(void) +{ + /* + * Tsunami's pretty sane, Sun and TI actually got it + * somewhat right this time. Fujitsu should have + * taken some lessons from them. + */ + + srmmu_name = "TI Tsunami"; + srmmu_modtype = Tsunami; + + BTFIXUPSET_CALL(flush_cache_all, tsunami_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, tsunami_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, tsunami_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, tsunami_flush_cache_range, BTFIXUPCALL_NORM); + + + BTFIXUPSET_CALL(flush_tlb_all, tsunami_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, tsunami_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, tsunami_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, tsunami_flush_tlb_range, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__flush_page_to_ram, tsunami_flush_page_to_ram, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_sig_insns, tsunami_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, tsunami_flush_page_for_dma, BTFIXUPCALL_NORM); + + poke_srmmu = poke_tsunami; + + tsunami_setup_blockops(); +} + +static void __init poke_viking(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + static int smp_catch; + + if(viking_mxcc_present) { + unsigned long mxcc_control = mxcc_get_creg(); + + mxcc_control |= (MXCC_CTL_ECE | MXCC_CTL_PRE | MXCC_CTL_MCE); + mxcc_control &= ~(MXCC_CTL_RRC); + mxcc_set_creg(mxcc_control); + + /* + * We don't need memory parity checks. + * XXX This is a mess, have to dig out later. ecd. + viking_mxcc_turn_off_parity(&mreg, &mxcc_control); + */ + + /* We do cache ptables on MXCC. */ + mreg |= VIKING_TCENABLE; + } else { + unsigned long bpreg; + + mreg &= ~(VIKING_TCENABLE); + if(smp_catch++) { + /* Must disable mixed-cmd mode here for other cpu's. */ + bpreg = viking_get_bpreg(); + bpreg &= ~(VIKING_ACTION_MIX); + viking_set_bpreg(bpreg); + + /* Just in case PROM does something funny. */ + msi_set_sync(); + } + } + + mreg |= VIKING_SPENABLE; + mreg |= (VIKING_ICENABLE | VIKING_DCENABLE); + mreg |= VIKING_SBENABLE; + mreg &= ~(VIKING_ACENABLE); + srmmu_set_mmureg(mreg); + +#ifdef CONFIG_SMP + /* Avoid unnecessary cross calls. */ + BTFIXUPCOPY_CALL(flush_cache_all, local_flush_cache_all); + BTFIXUPCOPY_CALL(flush_cache_mm, local_flush_cache_mm); + BTFIXUPCOPY_CALL(flush_cache_range, local_flush_cache_range); + BTFIXUPCOPY_CALL(flush_cache_page, local_flush_cache_page); + BTFIXUPCOPY_CALL(__flush_page_to_ram, local_flush_page_to_ram); + BTFIXUPCOPY_CALL(flush_sig_insns, local_flush_sig_insns); + BTFIXUPCOPY_CALL(flush_page_for_dma, local_flush_page_for_dma); + btfixup(); +#endif +} + +static void __init init_viking(void) +{ + unsigned long mreg = srmmu_get_mmureg(); + + /* Ahhh, the viking. SRMMU VLSI abortion number two... */ + if(mreg & VIKING_MMODE) { + srmmu_name = "TI Viking"; + viking_mxcc_present = 0; + msi_set_sync(); + + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_NORM); + + /* + * We need this to make sure old viking takes no hits + * on it's cache for dma snoops to workaround the + * "load from non-cacheable memory" interrupt bug. + * This is only necessary because of the new way in + * which we use the IOMMU. + */ + BTFIXUPSET_CALL(flush_page_for_dma, viking_flush_page, BTFIXUPCALL_NORM); + + flush_page_for_dma_global = 0; + } else { + srmmu_name = "TI Viking/MXCC"; + viking_mxcc_present = 1; + + srmmu_cache_pagetables = 1; + + /* MXCC vikings lack the DMA snooping bug. */ + BTFIXUPSET_CALL(flush_page_for_dma, viking_flush_page_for_dma, BTFIXUPCALL_NOP); + } + + BTFIXUPSET_CALL(flush_cache_all, viking_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, viking_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, viking_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, viking_flush_cache_range, BTFIXUPCALL_NORM); + +#ifdef CONFIG_SMP + if (sparc_cpu_model == sun4d) { + BTFIXUPSET_CALL(flush_tlb_all, sun4dsmp_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, sun4dsmp_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, sun4dsmp_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, sun4dsmp_flush_tlb_range, BTFIXUPCALL_NORM); + } else +#endif + { + BTFIXUPSET_CALL(flush_tlb_all, viking_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, viking_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, viking_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, viking_flush_tlb_range, BTFIXUPCALL_NORM); + } + + BTFIXUPSET_CALL(__flush_page_to_ram, viking_flush_page_to_ram, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(flush_sig_insns, viking_flush_sig_insns, BTFIXUPCALL_NOP); + + poke_srmmu = poke_viking; +} + +/* Probe for the srmmu chip version. */ +static void __init get_srmmu_type(void) +{ + unsigned long mreg, psr; + unsigned long mod_typ, mod_rev, psr_typ, psr_vers; + + srmmu_modtype = SRMMU_INVAL_MOD; + hwbug_bitmask = 0; + + mreg = srmmu_get_mmureg(); psr = get_psr(); + mod_typ = (mreg & 0xf0000000) >> 28; + mod_rev = (mreg & 0x0f000000) >> 24; + psr_typ = (psr >> 28) & 0xf; + psr_vers = (psr >> 24) & 0xf; + + /* First, check for HyperSparc or Cypress. */ + if(mod_typ == 1) { + switch(mod_rev) { + case 7: + /* UP or MP Hypersparc */ + init_hypersparc(); + break; + case 0: + case 2: + /* Uniprocessor Cypress */ + init_cypress_604(); + break; + case 10: + case 11: + case 12: + /* _REALLY OLD_ Cypress MP chips... */ + case 13: + case 14: + case 15: + /* MP Cypress mmu/cache-controller */ + init_cypress_605(mod_rev); + break; + default: + /* Some other Cypress revision, assume a 605. */ + init_cypress_605(mod_rev); + break; + }; + return; + } + + /* + * Now Fujitsu TurboSparc. It might happen that it is + * in Swift emulation mode, so we will check later... + */ + if (psr_typ == 0 && psr_vers == 5) { + init_turbosparc(); + return; + } + + /* Next check for Fujitsu Swift. */ + if(psr_typ == 0 && psr_vers == 4) { + int cpunode; + char node_str[128]; + + /* Look if it is not a TurboSparc emulating Swift... */ + cpunode = prom_getchild(prom_root_node); + while((cpunode = prom_getsibling(cpunode)) != 0) { + prom_getstring(cpunode, "device_type", node_str, sizeof(node_str)); + if(!strcmp(node_str, "cpu")) { + if (!prom_getintdefault(cpunode, "psr-implementation", 1) && + prom_getintdefault(cpunode, "psr-version", 1) == 5) { + init_turbosparc(); + return; + } + break; + } + } + + init_swift(); + return; + } + + /* Now the Viking family of srmmu. */ + if(psr_typ == 4 && + ((psr_vers == 0) || + ((psr_vers == 1) && (mod_typ == 0) && (mod_rev == 0)))) { + init_viking(); + return; + } + + /* Finally the Tsunami. */ + if(psr_typ == 4 && psr_vers == 1 && (mod_typ || mod_rev)) { + init_tsunami(); + return; + } + + /* Oh well */ + srmmu_is_bad(); +} + +/* don't laugh, static pagetables */ +static void srmmu_check_pgt_cache(int low, int high) +{ +} + +extern unsigned long spwin_mmu_patchme, fwin_mmu_patchme, + tsetup_mmu_patchme, rtrap_mmu_patchme; + +extern unsigned long spwin_srmmu_stackchk, srmmu_fwin_stackchk, + tsetup_srmmu_stackchk, srmmu_rett_stackchk; + +extern unsigned long srmmu_fault; + +#define PATCH_BRANCH(insn, dest) do { \ + iaddr = &(insn); \ + daddr = &(dest); \ + *iaddr = SPARC_BRANCH((unsigned long) daddr, (unsigned long) iaddr); \ + } while(0) + +static void __init patch_window_trap_handlers(void) +{ + unsigned long *iaddr, *daddr; + + PATCH_BRANCH(spwin_mmu_patchme, spwin_srmmu_stackchk); + PATCH_BRANCH(fwin_mmu_patchme, srmmu_fwin_stackchk); + PATCH_BRANCH(tsetup_mmu_patchme, tsetup_srmmu_stackchk); + PATCH_BRANCH(rtrap_mmu_patchme, srmmu_rett_stackchk); + PATCH_BRANCH(sparc_ttable[SP_TRAP_TFLT].inst_three, srmmu_fault); + PATCH_BRANCH(sparc_ttable[SP_TRAP_DFLT].inst_three, srmmu_fault); + PATCH_BRANCH(sparc_ttable[SP_TRAP_DACC].inst_three, srmmu_fault); +} + +#ifdef CONFIG_SMP +/* Local cross-calls. */ +static void smp_flush_page_for_dma(unsigned long page) +{ + xc1((smpfunc_t) BTFIXUP_CALL(local_flush_page_for_dma), page); + local_flush_page_for_dma(page); +} + +#endif + +static pte_t srmmu_pgoff_to_pte(unsigned long pgoff) +{ + return __pte((pgoff << SRMMU_PTE_FILE_SHIFT) | SRMMU_FILE); +} + +static unsigned long srmmu_pte_to_pgoff(pte_t pte) +{ + return pte_val(pte) >> SRMMU_PTE_FILE_SHIFT; +} + +/* Load up routines and constants for sun4m and sun4d mmu */ +void __init ld_mmu_srmmu(void) +{ + extern void ld_mmu_iommu(void); + extern void ld_mmu_iounit(void); + extern void ___xchg32_sun4md(void); + + BTFIXUPSET_SIMM13(pgdir_shift, SRMMU_PGDIR_SHIFT); + BTFIXUPSET_SETHI(pgdir_size, SRMMU_PGDIR_SIZE); + BTFIXUPSET_SETHI(pgdir_mask, SRMMU_PGDIR_MASK); + + BTFIXUPSET_SIMM13(ptrs_per_pmd, SRMMU_PTRS_PER_PMD); + BTFIXUPSET_SIMM13(ptrs_per_pgd, SRMMU_PTRS_PER_PGD); + + BTFIXUPSET_INT(page_none, pgprot_val(SRMMU_PAGE_NONE)); + BTFIXUPSET_INT(page_shared, pgprot_val(SRMMU_PAGE_SHARED)); + BTFIXUPSET_INT(page_copy, pgprot_val(SRMMU_PAGE_COPY)); + BTFIXUPSET_INT(page_readonly, pgprot_val(SRMMU_PAGE_RDONLY)); + BTFIXUPSET_INT(page_kernel, pgprot_val(SRMMU_PAGE_KERNEL)); + page_kernel = pgprot_val(SRMMU_PAGE_KERNEL); + pg_iobits = SRMMU_VALID | SRMMU_WRITE | SRMMU_REF; + + /* Functions */ +#ifndef CONFIG_SMP + BTFIXUPSET_CALL(___xchg32, ___xchg32_sun4md, BTFIXUPCALL_SWAPG1G2); +#endif + BTFIXUPSET_CALL(do_check_pgt_cache, srmmu_check_pgt_cache, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(set_pte, srmmu_set_pte, BTFIXUPCALL_SWAPO0O1); + BTFIXUPSET_CALL(switch_mm, srmmu_switch_mm, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_pfn, srmmu_pte_pfn, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_page, srmmu_pmd_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_page, srmmu_pgd_page, BTFIXUPCALL_NORM); + + BTFIXUPSET_SETHI(none_mask, 0xF0000000); + + BTFIXUPSET_CALL(pte_present, srmmu_pte_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_clear, srmmu_pte_clear, BTFIXUPCALL_SWAPO0G0); + BTFIXUPSET_CALL(pte_read, srmmu_pte_read, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pmd_bad, srmmu_pmd_bad, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_present, srmmu_pmd_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, srmmu_pmd_clear, BTFIXUPCALL_SWAPO0G0); + + BTFIXUPSET_CALL(pgd_none, srmmu_pgd_none, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_bad, srmmu_pgd_bad, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_present, srmmu_pgd_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_clear, srmmu_pgd_clear, BTFIXUPCALL_SWAPO0G0); + + BTFIXUPSET_CALL(mk_pte, srmmu_mk_pte, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_phys, srmmu_mk_pte_phys, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_io, srmmu_mk_pte_io, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgd_set, srmmu_pgd_set, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_set, srmmu_pmd_set, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_populate, srmmu_pmd_populate, BTFIXUPCALL_NORM); + + BTFIXUPSET_INT(pte_modify_mask, SRMMU_CHG_MASK); + BTFIXUPSET_CALL(pmd_offset, srmmu_pmd_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_offset_kernel, srmmu_pte_offset, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(free_pte_fast, srmmu_free_pte_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_free, srmmu_pte_free, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one_kernel, srmmu_pte_alloc_one_kernel, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one, srmmu_pte_alloc_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pmd_fast, srmmu_pmd_free, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_alloc_one, srmmu_pmd_alloc_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pgd_fast, srmmu_free_pgd_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(get_pgd_fast, srmmu_get_pgd_fast, BTFIXUPCALL_NORM); + + BTFIXUPSET_HALF(pte_writei, SRMMU_WRITE); + BTFIXUPSET_HALF(pte_dirtyi, SRMMU_DIRTY); + BTFIXUPSET_HALF(pte_youngi, SRMMU_REF); + BTFIXUPSET_HALF(pte_filei, SRMMU_FILE); + BTFIXUPSET_HALF(pte_wrprotecti, SRMMU_WRITE); + BTFIXUPSET_HALF(pte_mkcleani, SRMMU_DIRTY); + BTFIXUPSET_HALF(pte_mkoldi, SRMMU_REF); + BTFIXUPSET_CALL(pte_mkwrite, srmmu_pte_mkwrite, BTFIXUPCALL_ORINT(SRMMU_WRITE)); + BTFIXUPSET_CALL(pte_mkdirty, srmmu_pte_mkdirty, BTFIXUPCALL_ORINT(SRMMU_DIRTY)); + BTFIXUPSET_CALL(pte_mkyoung, srmmu_pte_mkyoung, BTFIXUPCALL_ORINT(SRMMU_REF)); + BTFIXUPSET_CALL(update_mmu_cache, srmmu_update_mmu_cache, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(destroy_context, srmmu_destroy_context, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(sparc_mapiorange, srmmu_mapiorange, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sparc_unmapiorange, srmmu_unmapiorange, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__swp_type, srmmu_swp_type, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_offset, srmmu_swp_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_entry, srmmu_swp_entry, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_info, srmmu_mmu_info, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(alloc_thread_info, srmmu_alloc_thread_info, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_thread_info, srmmu_free_thread_info, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_to_pgoff, srmmu_pte_to_pgoff, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgoff_to_pte, srmmu_pgoff_to_pte, BTFIXUPCALL_NORM); + + get_srmmu_type(); + patch_window_trap_handlers(); + +#ifdef CONFIG_SMP + /* El switcheroo... */ + + BTFIXUPCOPY_CALL(local_flush_cache_all, flush_cache_all); + BTFIXUPCOPY_CALL(local_flush_cache_mm, flush_cache_mm); + BTFIXUPCOPY_CALL(local_flush_cache_range, flush_cache_range); + BTFIXUPCOPY_CALL(local_flush_cache_page, flush_cache_page); + BTFIXUPCOPY_CALL(local_flush_tlb_all, flush_tlb_all); + BTFIXUPCOPY_CALL(local_flush_tlb_mm, flush_tlb_mm); + BTFIXUPCOPY_CALL(local_flush_tlb_range, flush_tlb_range); + BTFIXUPCOPY_CALL(local_flush_tlb_page, flush_tlb_page); + BTFIXUPCOPY_CALL(local_flush_page_to_ram, __flush_page_to_ram); + BTFIXUPCOPY_CALL(local_flush_sig_insns, flush_sig_insns); + BTFIXUPCOPY_CALL(local_flush_page_for_dma, flush_page_for_dma); + + BTFIXUPSET_CALL(flush_cache_all, smp_flush_cache_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, smp_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, smp_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, smp_flush_cache_page, BTFIXUPCALL_NORM); + if (sparc_cpu_model != sun4d) { + BTFIXUPSET_CALL(flush_tlb_all, smp_flush_tlb_all, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_mm, smp_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, smp_flush_tlb_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, smp_flush_tlb_page, BTFIXUPCALL_NORM); + } + BTFIXUPSET_CALL(__flush_page_to_ram, smp_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_sig_insns, smp_flush_sig_insns, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_page_for_dma, smp_flush_page_for_dma, BTFIXUPCALL_NORM); +#endif + + if (sparc_cpu_model == sun4d) + ld_mmu_iounit(); + else + ld_mmu_iommu(); +#ifdef CONFIG_SMP + if (sparc_cpu_model == sun4d) + sun4d_init_smp(); + else + sun4m_init_smp(); +#endif +} diff --git a/arch/sparc/mm/sun4c.c b/arch/sparc/mm/sun4c.c new file mode 100644 index 000000000000..1d560390e282 --- /dev/null +++ b/arch/sparc/mm/sun4c.c @@ -0,0 +1,2276 @@ +/* $Id: sun4c.c,v 1.212 2001/12/21 04:56:15 davem Exp $ + * sun4c.c: Doing in software what should be done in hardware. + * + * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1996 Andrew Tridgell (Andrew.Tridgell@anu.edu.au) + * Copyright (C) 1997-2000 Anton Blanchard (anton@samba.org) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#define NR_TASK_BUCKETS 512 + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/highmem.h> +#include <linux/fs.h> +#include <linux/seq_file.h> + +#include <asm/scatterlist.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/vaddrs.h> +#include <asm/idprom.h> +#include <asm/machines.h> +#include <asm/memreg.h> +#include <asm/processor.h> +#include <asm/auxio.h> +#include <asm/io.h> +#include <asm/oplib.h> +#include <asm/openprom.h> +#include <asm/mmu_context.h> +#include <asm/sun4paddr.h> +#include <asm/highmem.h> +#include <asm/btfixup.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> + +/* Because of our dynamic kernel TLB miss strategy, and how + * our DVMA mapping allocation works, you _MUST_: + * + * 1) Disable interrupts _and_ not touch any dynamic kernel + * memory while messing with kernel MMU state. By + * dynamic memory I mean any object which is not in + * the kernel image itself or a thread_union (both of + * which are locked into the MMU). + * 2) Disable interrupts while messing with user MMU state. + */ + +extern int num_segmaps, num_contexts; + +extern unsigned long page_kernel; + +#ifdef CONFIG_SUN4 +#define SUN4C_VAC_SIZE sun4c_vacinfo.num_bytes +#else +/* That's it, we prom_halt() on sun4c if the cache size is something other than 65536. + * So let's save some cycles and just use that everywhere except for that bootup + * sanity check. + */ +#define SUN4C_VAC_SIZE 65536 +#endif + +#define SUN4C_KERNEL_BUCKETS 32 + +/* Flushing the cache. */ +struct sun4c_vac_props sun4c_vacinfo; +unsigned long sun4c_kernel_faults; + +/* Invalidate every sun4c cache line tag. */ +static void __init sun4c_flush_all(void) +{ + unsigned long begin, end; + + if (sun4c_vacinfo.on) + panic("SUN4C: AIEEE, trying to invalidate vac while it is on."); + + /* Clear 'valid' bit in all cache line tags */ + begin = AC_CACHETAGS; + end = (AC_CACHETAGS + SUN4C_VAC_SIZE); + while (begin < end) { + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : : + "r" (begin), "i" (ASI_CONTROL)); + begin += sun4c_vacinfo.linesize; + } +} + +static void sun4c_flush_context_hw(void) +{ + unsigned long end = SUN4C_VAC_SIZE; + + __asm__ __volatile__( + "1: addcc %0, -4096, %0\n\t" + " bne 1b\n\t" + " sta %%g0, [%0] %2" + : "=&r" (end) + : "0" (end), "i" (ASI_HWFLUSHCONTEXT) + : "cc"); +} + +/* Must be called minimally with IRQs disabled. */ +static void sun4c_flush_segment_hw(unsigned long addr) +{ + if (sun4c_get_segmap(addr) != invalid_segment) { + unsigned long vac_size = SUN4C_VAC_SIZE; + + __asm__ __volatile__( + "1: addcc %0, -4096, %0\n\t" + " bne 1b\n\t" + " sta %%g0, [%2 + %0] %3" + : "=&r" (vac_size) + : "0" (vac_size), "r" (addr), "i" (ASI_HWFLUSHSEG) + : "cc"); + } +} + +/* File local boot time fixups. */ +BTFIXUPDEF_CALL(void, sun4c_flush_page, unsigned long) +BTFIXUPDEF_CALL(void, sun4c_flush_segment, unsigned long) +BTFIXUPDEF_CALL(void, sun4c_flush_context, void) + +#define sun4c_flush_page(addr) BTFIXUP_CALL(sun4c_flush_page)(addr) +#define sun4c_flush_segment(addr) BTFIXUP_CALL(sun4c_flush_segment)(addr) +#define sun4c_flush_context() BTFIXUP_CALL(sun4c_flush_context)() + +/* Must be called minimally with interrupts disabled. */ +static void sun4c_flush_page_hw(unsigned long addr) +{ + addr &= PAGE_MASK; + if ((int)sun4c_get_pte(addr) < 0) + __asm__ __volatile__("sta %%g0, [%0] %1" + : : "r" (addr), "i" (ASI_HWFLUSHPAGE)); +} + +/* Don't inline the software version as it eats too many cache lines if expanded. */ +static void sun4c_flush_context_sw(void) +{ + unsigned long nbytes = SUN4C_VAC_SIZE; + unsigned long lsize = sun4c_vacinfo.linesize; + + __asm__ __volatile__( + "add %2, %2, %%g1\n\t" + "add %2, %%g1, %%g2\n\t" + "add %2, %%g2, %%g3\n\t" + "add %2, %%g3, %%g4\n\t" + "add %2, %%g4, %%g5\n\t" + "add %2, %%g5, %%o4\n\t" + "add %2, %%o4, %%o5\n" + "1:\n\t" + "subcc %0, %%o5, %0\n\t" + "sta %%g0, [%0] %3\n\t" + "sta %%g0, [%0 + %2] %3\n\t" + "sta %%g0, [%0 + %%g1] %3\n\t" + "sta %%g0, [%0 + %%g2] %3\n\t" + "sta %%g0, [%0 + %%g3] %3\n\t" + "sta %%g0, [%0 + %%g4] %3\n\t" + "sta %%g0, [%0 + %%g5] %3\n\t" + "bg 1b\n\t" + " sta %%g0, [%1 + %%o4] %3\n" + : "=&r" (nbytes) + : "0" (nbytes), "r" (lsize), "i" (ASI_FLUSHCTX) + : "g1", "g2", "g3", "g4", "g5", "o4", "o5", "cc"); +} + +/* Don't inline the software version as it eats too many cache lines if expanded. */ +static void sun4c_flush_segment_sw(unsigned long addr) +{ + if (sun4c_get_segmap(addr) != invalid_segment) { + unsigned long nbytes = SUN4C_VAC_SIZE; + unsigned long lsize = sun4c_vacinfo.linesize; + + __asm__ __volatile__( + "add %2, %2, %%g1\n\t" + "add %2, %%g1, %%g2\n\t" + "add %2, %%g2, %%g3\n\t" + "add %2, %%g3, %%g4\n\t" + "add %2, %%g4, %%g5\n\t" + "add %2, %%g5, %%o4\n\t" + "add %2, %%o4, %%o5\n" + "1:\n\t" + "subcc %1, %%o5, %1\n\t" + "sta %%g0, [%0] %6\n\t" + "sta %%g0, [%0 + %2] %6\n\t" + "sta %%g0, [%0 + %%g1] %6\n\t" + "sta %%g0, [%0 + %%g2] %6\n\t" + "sta %%g0, [%0 + %%g3] %6\n\t" + "sta %%g0, [%0 + %%g4] %6\n\t" + "sta %%g0, [%0 + %%g5] %6\n\t" + "sta %%g0, [%0 + %%o4] %6\n\t" + "bg 1b\n\t" + " add %0, %%o5, %0\n" + : "=&r" (addr), "=&r" (nbytes), "=&r" (lsize) + : "0" (addr), "1" (nbytes), "2" (lsize), + "i" (ASI_FLUSHSEG) + : "g1", "g2", "g3", "g4", "g5", "o4", "o5", "cc"); + } +} + +/* Don't inline the software version as it eats too many cache lines if expanded. */ +static void sun4c_flush_page_sw(unsigned long addr) +{ + addr &= PAGE_MASK; + if ((sun4c_get_pte(addr) & (_SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_VALID)) == + _SUN4C_PAGE_VALID) { + unsigned long left = PAGE_SIZE; + unsigned long lsize = sun4c_vacinfo.linesize; + + __asm__ __volatile__( + "add %2, %2, %%g1\n\t" + "add %2, %%g1, %%g2\n\t" + "add %2, %%g2, %%g3\n\t" + "add %2, %%g3, %%g4\n\t" + "add %2, %%g4, %%g5\n\t" + "add %2, %%g5, %%o4\n\t" + "add %2, %%o4, %%o5\n" + "1:\n\t" + "subcc %1, %%o5, %1\n\t" + "sta %%g0, [%0] %6\n\t" + "sta %%g0, [%0 + %2] %6\n\t" + "sta %%g0, [%0 + %%g1] %6\n\t" + "sta %%g0, [%0 + %%g2] %6\n\t" + "sta %%g0, [%0 + %%g3] %6\n\t" + "sta %%g0, [%0 + %%g4] %6\n\t" + "sta %%g0, [%0 + %%g5] %6\n\t" + "sta %%g0, [%0 + %%o4] %6\n\t" + "bg 1b\n\t" + " add %0, %%o5, %0\n" + : "=&r" (addr), "=&r" (left), "=&r" (lsize) + : "0" (addr), "1" (left), "2" (lsize), + "i" (ASI_FLUSHPG) + : "g1", "g2", "g3", "g4", "g5", "o4", "o5", "cc"); + } +} + +/* 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 read an external hardware + * register. + */ +void sun4c_complete_all_stores(void) +{ + volatile int _unused; + + _unused = sun4c_get_context(); + sun4c_set_context(_unused); +#ifdef CONFIG_SUN_AUXIO + _unused = get_auxio(); +#endif +} + +/* Bootup utility functions. */ +static inline void sun4c_init_clean_segmap(unsigned char pseg) +{ + 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); +} + +static inline void sun4c_init_clean_mmu(unsigned long kernel_end) +{ + 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(savectx); +} + +void __init sun4c_probe_vac(void) +{ + sun4c_disable_vac(); + + if (ARCH_SUN4) { + switch (idprom->id_machtype) { + + case (SM_SUN4|SM_4_110): + sun4c_vacinfo.type = VAC_NONE; + sun4c_vacinfo.num_bytes = 0; + sun4c_vacinfo.linesize = 0; + sun4c_vacinfo.do_hwflushes = 0; + prom_printf("No VAC. Get some bucks and buy a real computer."); + prom_halt(); + break; + + case (SM_SUN4|SM_4_260): + sun4c_vacinfo.type = VAC_WRITE_BACK; + sun4c_vacinfo.num_bytes = 128 * 1024; + sun4c_vacinfo.linesize = 16; + sun4c_vacinfo.do_hwflushes = 0; + break; + + case (SM_SUN4|SM_4_330): + sun4c_vacinfo.type = VAC_WRITE_THROUGH; + sun4c_vacinfo.num_bytes = 128 * 1024; + sun4c_vacinfo.linesize = 16; + sun4c_vacinfo.do_hwflushes = 0; + break; + + case (SM_SUN4|SM_4_470): + sun4c_vacinfo.type = VAC_WRITE_BACK; + sun4c_vacinfo.num_bytes = 128 * 1024; + sun4c_vacinfo.linesize = 32; + sun4c_vacinfo.do_hwflushes = 0; + break; + + default: + prom_printf("Cannot initialize VAC - weird sun4 model idprom->id_machtype = %d", idprom->id_machtype); + prom_halt(); + }; + } else { + sun4c_vacinfo.type = VAC_WRITE_THROUGH; + + if ((idprom->id_machtype == (SM_SUN4C | SM_4C_SS1)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_SS1PLUS))) { + /* PROM on SS1 lacks this info, to be super safe we + * hard code it here since this arch is cast in stone. + */ + sun4c_vacinfo.num_bytes = 65536; + sun4c_vacinfo.linesize = 16; + } else { + 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.do_hwflushes = + prom_getintdefault(prom_root_node, "vac-hwflush", 0); + + if (sun4c_vacinfo.do_hwflushes == 0) + sun4c_vacinfo.do_hwflushes = + prom_getintdefault(prom_root_node, "vac_hwflush", 0); + + if (sun4c_vacinfo.num_bytes != 65536) { + prom_printf("WEIRD Sun4C VAC cache size, " + "tell sparclinux@vger.kernel.org"); + prom_halt(); + } + } + + 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(); + }; + + sun4c_flush_all(); + sun4c_enable_vac(); +} + +/* Patch instructions for the low level kernel fault handler. */ +extern unsigned long invalid_segment_patch1, invalid_segment_patch1_ff; +extern unsigned long invalid_segment_patch2, invalid_segment_patch2_ff; +extern unsigned long invalid_segment_patch1_1ff, invalid_segment_patch2_1ff; +extern unsigned long num_context_patch1, num_context_patch1_16; +extern unsigned long num_context_patch2_16; +extern unsigned long vac_linesize_patch, vac_linesize_patch_32; +extern unsigned long vac_hwflush_patch1, vac_hwflush_patch1_on; +extern unsigned long vac_hwflush_patch2, vac_hwflush_patch2_on; + +#define PATCH_INSN(src, dst) do { \ + daddr = &(dst); \ + iaddr = &(src); \ + *daddr = *iaddr; \ + } while (0) + +static void __init patch_kernel_fault_handler(void) +{ + unsigned long *iaddr, *daddr; + + switch (num_segmaps) { + case 128: + /* Default, nothing to do. */ + break; + case 256: + PATCH_INSN(invalid_segment_patch1_ff, + invalid_segment_patch1); + PATCH_INSN(invalid_segment_patch2_ff, + invalid_segment_patch2); + break; + case 512: + PATCH_INSN(invalid_segment_patch1_1ff, + invalid_segment_patch1); + PATCH_INSN(invalid_segment_patch2_1ff, + invalid_segment_patch2); + break; + default: + prom_printf("Unhandled number of segmaps: %d\n", + num_segmaps); + prom_halt(); + }; + switch (num_contexts) { + case 8: + /* Default, nothing to do. */ + break; + case 16: + PATCH_INSN(num_context_patch1_16, + num_context_patch1); + break; + default: + prom_printf("Unhandled number of contexts: %d\n", + num_contexts); + prom_halt(); + }; + + if (sun4c_vacinfo.do_hwflushes != 0) { + PATCH_INSN(vac_hwflush_patch1_on, vac_hwflush_patch1); + PATCH_INSN(vac_hwflush_patch2_on, vac_hwflush_patch2); + } else { + switch (sun4c_vacinfo.linesize) { + case 16: + /* Default, nothing to do. */ + break; + case 32: + PATCH_INSN(vac_linesize_patch_32, vac_linesize_patch); + break; + default: + prom_printf("Impossible VAC linesize %d, halting...\n", + sun4c_vacinfo.linesize); + prom_halt(); + }; + } +} + +static void __init sun4c_probe_mmu(void) +{ + if (ARCH_SUN4) { + switch (idprom->id_machtype) { + case (SM_SUN4|SM_4_110): + prom_printf("No support for 4100 yet\n"); + prom_halt(); + num_segmaps = 256; + num_contexts = 8; + break; + + case (SM_SUN4|SM_4_260): + /* should be 512 segmaps. when it get fixed */ + num_segmaps = 256; + num_contexts = 16; + break; + + case (SM_SUN4|SM_4_330): + num_segmaps = 256; + num_contexts = 16; + break; + + case (SM_SUN4|SM_4_470): + /* should be 1024 segmaps. when it get fixed */ + num_segmaps = 256; + num_contexts = 64; + break; + default: + prom_printf("Invalid SUN4 model\n"); + prom_halt(); + }; + } else { + if ((idprom->id_machtype == (SM_SUN4C | SM_4C_SS1)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_SS1PLUS))) { + /* Hardcode these just to be safe, PROM on SS1 does + * not have this info available in the root node. + */ + num_segmaps = 128; + num_contexts = 8; + } else { + num_segmaps = + prom_getintdefault(prom_root_node, "mmu-npmg", 128); + num_contexts = + prom_getintdefault(prom_root_node, "mmu-nctx", 0x8); + } + } + patch_kernel_fault_handler(); +} + +volatile unsigned long *sun4c_memerr_reg = NULL; + +void __init sun4c_probe_memerr_reg(void) +{ + int node; + struct linux_prom_registers regs[1]; + + if (ARCH_SUN4) { + sun4c_memerr_reg = ioremap(sun4_memreg_physaddr, PAGE_SIZE); + } else { + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(prom_root_node, "memory-error"); + if (!node) + return; + if (prom_getproperty(node, "reg", (char *)regs, sizeof(regs)) <= 0) + return; + /* hmm I think regs[0].which_io is zero here anyways */ + sun4c_memerr_reg = ioremap(regs[0].phys_addr, regs[0].reg_size); + } +} + +static inline void sun4c_init_ss2_cache_bug(void) +{ + extern unsigned long start; + + if ((idprom->id_machtype == (SM_SUN4C | SM_4C_SS2)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_IPX)) || + (idprom->id_machtype == (SM_SUN4 | SM_4_330)) || + (idprom->id_machtype == (SM_SUN4C | SM_4C_ELC))) { + /* 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)); + } +} + +/* Addr is always aligned on a page boundary for us already. */ +static int sun4c_map_dma_area(dma_addr_t *pba, unsigned long va, + unsigned long addr, int len) +{ + unsigned long page, end; + + *pba = addr; + + end = PAGE_ALIGN((addr + len)); + while (addr < end) { + page = va; + sun4c_flush_page(page); + page -= PAGE_OFFSET; + page >>= PAGE_SHIFT; + page |= (_SUN4C_PAGE_VALID | _SUN4C_PAGE_DIRTY | + _SUN4C_PAGE_NOCACHE | _SUN4C_PAGE_PRIV); + sun4c_put_pte(addr, page); + addr += PAGE_SIZE; + va += PAGE_SIZE; + } + + return 0; +} + +static struct page *sun4c_translate_dvma(unsigned long busa) +{ + /* Fortunately for us, bus_addr == uncached_virt in sun4c. */ + unsigned long pte = sun4c_get_pte(busa); + return pfn_to_page(pte & SUN4C_PFN_MASK); +} + +static void sun4c_unmap_dma_area(unsigned long busa, int len) +{ + /* Fortunately for us, bus_addr == uncached_virt in sun4c. */ + /* XXX Implement this */ +} + +/* TLB management. */ + +/* Don't change this struct without changing entry.S. This is used + * in the in-window kernel fault handler, and you don't want to mess + * with that. (See sun4c_fault in entry.S). + */ +struct sun4c_mmu_entry { + struct sun4c_mmu_entry *next; + struct sun4c_mmu_entry *prev; + unsigned long vaddr; + unsigned char pseg; + unsigned char locked; + + /* For user mappings only, and completely hidden from kernel + * TLB miss code. + */ + unsigned char ctx; + struct sun4c_mmu_entry *lru_next; + struct sun4c_mmu_entry *lru_prev; +}; + +static struct sun4c_mmu_entry mmu_entry_pool[SUN4C_MAX_SEGMAPS]; + +static void __init sun4c_init_mmu_entry_pool(void) +{ + int i; + + for (i=0; i < SUN4C_MAX_SEGMAPS; i++) { + mmu_entry_pool[i].pseg = i; + mmu_entry_pool[i].next = NULL; + mmu_entry_pool[i].prev = NULL; + mmu_entry_pool[i].vaddr = 0; + mmu_entry_pool[i].locked = 0; + mmu_entry_pool[i].ctx = 0; + mmu_entry_pool[i].lru_next = NULL; + mmu_entry_pool[i].lru_prev = NULL; + } + 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; +#ifdef CONFIG_SUN4 + /* sun4/110 and 260 have no kadb. */ + if ((idprom->id_machtype != (SM_SUN4 | SM_4_260)) && + (idprom->id_machtype != (SM_SUN4 | SM_4_110))) { +#endif + 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); + } + } +#ifdef CONFIG_SUN4 + } +#endif + 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 __init 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; + } +} + +/* Don't change this struct without changing entry.S. This is used + * in the in-window kernel fault handler, and you don't want to mess + * with that. (See sun4c_fault in entry.S). + */ +struct sun4c_mmu_ring { + struct sun4c_mmu_entry ringhd; + int num_entries; +}; + +static struct sun4c_mmu_ring sun4c_context_ring[SUN4C_MAX_CONTEXTS]; /* used user entries */ +static struct sun4c_mmu_ring sun4c_ufree_ring; /* free user entries */ +static struct sun4c_mmu_ring sun4c_ulru_ring; /* LRU user entries */ +struct sun4c_mmu_ring sun4c_kernel_ring; /* used kernel entries */ +struct sun4c_mmu_ring sun4c_kfree_ring; /* free kernel entries */ + +static inline void sun4c_init_rings(void) +{ + int i; + + for (i = 0; i < SUN4C_MAX_CONTEXTS; 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_ufree_ring.num_entries = 0; + sun4c_ulru_ring.ringhd.lru_next = sun4c_ulru_ring.ringhd.lru_prev = + &sun4c_ulru_ring.ringhd; + sun4c_ulru_ring.num_entries = 0; + sun4c_kernel_ring.ringhd.next = sun4c_kernel_ring.ringhd.prev = + &sun4c_kernel_ring.ringhd; + sun4c_kernel_ring.num_entries = 0; + sun4c_kfree_ring.ringhd.next = sun4c_kfree_ring.ringhd.prev = + &sun4c_kfree_ring.ringhd; + sun4c_kfree_ring.num_entries = 0; +} + +static 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 add_lru(struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_ring *ring = &sun4c_ulru_ring; + struct sun4c_mmu_entry *head = &ring->ringhd; + + entry->lru_next = head; + (entry->lru_prev = head->lru_prev)->lru_next = entry; + head->lru_prev = entry; +} + +static void add_ring_ordered(struct sun4c_mmu_ring *ring, + struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_entry *head = &ring->ringhd; + unsigned long addr = entry->vaddr; + + while ((head->next != &ring->ringhd) && (head->next->vaddr < addr)) + head = head->next; + + entry->prev = head; + (entry->next = head->next)->prev = entry; + head->next = entry; + ring->num_entries++; + + add_lru(entry); +} + +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 void remove_lru(struct sun4c_mmu_entry *entry) +{ + struct sun4c_mmu_entry *next = entry->lru_next; + + (next->lru_prev = entry->lru_prev)->lru_next = next; +} + +static void free_user_entry(int ctx, struct sun4c_mmu_entry *entry) +{ + remove_ring(sun4c_context_ring+ctx, entry); + remove_lru(entry); + add_ring(&sun4c_ufree_ring, entry); +} + +static 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 void __init 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 __init 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 void sun4c_kernel_unmap(struct sun4c_mmu_entry *kentry) +{ + int savectx, ctx; + + savectx = sun4c_get_context(); + for (ctx = 0; ctx < num_contexts; ctx++) { + sun4c_set_context(ctx); + sun4c_put_segmap(kentry->vaddr, invalid_segment); + } + sun4c_set_context(savectx); +} + +static void sun4c_kernel_map(struct sun4c_mmu_entry *kentry) +{ + int savectx, ctx; + + savectx = sun4c_get_context(); + for (ctx = 0; ctx < num_contexts; ctx++) { + sun4c_set_context(ctx); + sun4c_put_segmap(kentry->vaddr, kentry->pseg); + } + sun4c_set_context(savectx); +} + +#define sun4c_user_unmap(__entry) \ + sun4c_put_segmap((__entry)->vaddr, invalid_segment) + +static void sun4c_demap_context(struct sun4c_mmu_ring *crp, unsigned char ctx) +{ + struct sun4c_mmu_entry *head = &crp->ringhd; + unsigned long flags; + + local_irq_save(flags); + if (head->next != head) { + struct sun4c_mmu_entry *entry = head->next; + int savectx = sun4c_get_context(); + + flush_user_windows(); + sun4c_set_context(ctx); + sun4c_flush_context(); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_user_unmap(entry); + free_user_entry(ctx, entry); + + entry = next; + } while (entry != head); + sun4c_set_context(savectx); + } + local_irq_restore(flags); +} + +static int sun4c_user_taken_entries; /* This is how much we have. */ +static int max_user_taken_entries; /* This limits us and prevents deadlock. */ + +static 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) { + this_entry = sun4c_kfree_ring.ringhd.next; + return this_entry; + } + + /* Else free one up. */ + this_entry = sun4c_kernel_ring.ringhd.prev; + sun4c_flush_segment(this_entry->vaddr); + sun4c_kernel_unmap(this_entry); + free_kernel_entry(this_entry, &sun4c_kernel_ring); + this_entry = sun4c_kfree_ring.ringhd.next; + + return this_entry; +} + +/* Using this method to free up mmu entries eliminates a lot of + * potential races since we have a kernel that incurs tlb + * replacement faults. There may be performance penalties. + * + * NOTE: Must be called with interrupts disabled. + */ +static struct sun4c_mmu_entry *sun4c_user_strategy(void) +{ + struct sun4c_mmu_entry *entry; + unsigned char ctx; + int savectx; + + /* If some are free, return first one. */ + if (sun4c_ufree_ring.num_entries) { + entry = sun4c_ufree_ring.ringhd.next; + goto unlink_out; + } + + if (sun4c_user_taken_entries) { + entry = sun4c_kernel_strategy(); + sun4c_user_taken_entries--; + goto kunlink_out; + } + + /* Grab from the beginning of the LRU list. */ + entry = sun4c_ulru_ring.ringhd.lru_next; + ctx = entry->ctx; + + savectx = sun4c_get_context(); + flush_user_windows(); + sun4c_set_context(ctx); + sun4c_flush_segment(entry->vaddr); + sun4c_user_unmap(entry); + remove_ring(sun4c_context_ring + ctx, entry); + remove_lru(entry); + sun4c_set_context(savectx); + + return entry; + +unlink_out: + remove_ring(&sun4c_ufree_ring, entry); + return entry; +kunlink_out: + remove_ring(&sun4c_kfree_ring, entry); + return entry; +} + +/* NOTE: Must be called with interrupts disabled. */ +void sun4c_grow_kernel_ring(void) +{ + struct sun4c_mmu_entry *entry; + + /* Prevent deadlock condition. */ + if (sun4c_user_taken_entries >= max_user_taken_entries) + return; + + if (sun4c_ufree_ring.num_entries) { + entry = sun4c_ufree_ring.ringhd.next; + remove_ring(&sun4c_ufree_ring, entry); + add_ring(&sun4c_kfree_ring, entry); + sun4c_user_taken_entries++; + } +} + +/* 2 page buckets for task struct and kernel stack allocation. + * + * TASK_STACK_BEGIN + * bucket[0] + * bucket[1] + * [ ... ] + * bucket[NR_TASK_BUCKETS-1] + * TASK_STACK_BEGIN + (sizeof(struct task_bucket) * NR_TASK_BUCKETS) + * + * Each slot looks like: + * + * page 1 -- task struct + beginning of kernel stack + * page 2 -- rest of kernel stack + */ + +union task_union *sun4c_bucket[NR_TASK_BUCKETS]; + +static int sun4c_lowbucket_avail; + +#define BUCKET_EMPTY ((union task_union *) 0) +#define BUCKET_SHIFT (PAGE_SHIFT + 1) /* log2(sizeof(struct task_bucket)) */ +#define BUCKET_SIZE (1 << BUCKET_SHIFT) +#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) & SUN4C_PFN_MASK) << PAGE_SHIFT)) + +static void get_locked_segment(unsigned long addr) +{ + struct sun4c_mmu_entry *stolen; + unsigned long flags; + + local_irq_save(flags); + addr &= SUN4C_REAL_PGDIR_MASK; + stolen = sun4c_user_strategy(); + max_user_taken_entries--; + stolen->vaddr = addr; + flush_user_windows(); + sun4c_kernel_map(stolen); + local_irq_restore(flags); +} + +static void free_locked_segment(unsigned long addr) +{ + struct sun4c_mmu_entry *entry; + unsigned long flags; + unsigned char pseg; + + local_irq_save(flags); + addr &= SUN4C_REAL_PGDIR_MASK; + pseg = sun4c_get_segmap(addr); + entry = &mmu_entry_pool[pseg]; + + flush_user_windows(); + sun4c_flush_segment(addr); + sun4c_kernel_unmap(entry); + add_ring(&sun4c_ufree_ring, entry); + max_user_taken_entries++; + local_irq_restore(flags); +} + +static inline void garbage_collect(int entry) +{ + int start, end; + + /* 32 buckets per segment... */ + entry &= ~31; + start = entry; + for (end = (start + 32); start < end; start++) + if (sun4c_bucket[start] != BUCKET_EMPTY) + return; + + /* Entire segment empty, release it. */ + free_locked_segment(BUCKET_ADDR(entry)); +} + +static struct thread_info *sun4c_alloc_thread_info(void) +{ + unsigned long addr, pages; + int entry; + + pages = __get_free_pages(GFP_KERNEL, THREAD_INFO_ORDER); + if (!pages) + return NULL; + + for (entry = sun4c_lowbucket_avail; entry < NR_TASK_BUCKETS; entry++) + if (sun4c_bucket[entry] == BUCKET_EMPTY) + break; + if (entry == NR_TASK_BUCKETS) { + free_pages(pages, THREAD_INFO_ORDER); + return NULL; + } + if (entry >= sun4c_lowbucket_avail) + sun4c_lowbucket_avail = entry + 1; + + addr = BUCKET_ADDR(entry); + sun4c_bucket[entry] = (union task_union *) addr; + if(sun4c_get_segmap(addr) == invalid_segment) + get_locked_segment(addr); + + /* We are changing the virtual color of the page(s) + * so we must flush the cache to guarantee consistency. + */ + sun4c_flush_page(pages); +#ifndef CONFIG_SUN4 + sun4c_flush_page(pages + PAGE_SIZE); +#endif + + sun4c_put_pte(addr, BUCKET_PTE(pages)); +#ifndef CONFIG_SUN4 + sun4c_put_pte(addr + PAGE_SIZE, BUCKET_PTE(pages + PAGE_SIZE)); +#endif + +#ifdef CONFIG_DEBUG_STACK_USAGE + memset((void *)addr, 0, PAGE_SIZE << THREAD_INFO_ORDER); +#endif /* DEBUG_STACK_USAGE */ + + return (struct thread_info *) addr; +} + +static void sun4c_free_thread_info(struct thread_info *ti) +{ + unsigned long tiaddr = (unsigned long) ti; + unsigned long pages = BUCKET_PTE_PAGE(sun4c_get_pte(tiaddr)); + int entry = BUCKET_NUM(tiaddr); + + /* We are deleting a mapping, so the flush here is mandatory. */ + sun4c_flush_page(tiaddr); +#ifndef CONFIG_SUN4 + sun4c_flush_page(tiaddr + PAGE_SIZE); +#endif + sun4c_put_pte(tiaddr, 0); +#ifndef CONFIG_SUN4 + sun4c_put_pte(tiaddr + PAGE_SIZE, 0); +#endif + sun4c_bucket[entry] = BUCKET_EMPTY; + if (entry < sun4c_lowbucket_avail) + sun4c_lowbucket_avail = entry; + + free_pages(pages, THREAD_INFO_ORDER); + garbage_collect(entry); +} + +static void __init sun4c_init_buckets(void) +{ + int entry; + + if (sizeof(union thread_union) != (PAGE_SIZE << THREAD_INFO_ORDER)) { + extern void thread_info_size_is_bolixed_pete(void); + thread_info_size_is_bolixed_pete(); + } + + for (entry = 0; entry < NR_TASK_BUCKETS; entry++) + sun4c_bucket[entry] = BUCKET_EMPTY; + sun4c_lowbucket_avail = 0; +} + +static unsigned long sun4c_iobuffer_start; +static unsigned long sun4c_iobuffer_end; +static unsigned long sun4c_iobuffer_high; +static unsigned long *sun4c_iobuffer_map; +static int iobuffer_map_size; + +/* + * Alias our pages so they do not cause a trap. + * Also one page may be aliased into several I/O areas and we may + * finish these I/O separately. + */ +static char *sun4c_lockarea(char *vaddr, unsigned long size) +{ + unsigned long base, scan; + unsigned long npages; + unsigned long vpage; + unsigned long pte; + unsigned long apage; + unsigned long high; + unsigned long flags; + + npages = (((unsigned long)vaddr & ~PAGE_MASK) + + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + + scan = 0; + local_irq_save(flags); + for (;;) { + scan = find_next_zero_bit(sun4c_iobuffer_map, + iobuffer_map_size, scan); + if ((base = scan) + npages > iobuffer_map_size) goto abend; + for (;;) { + if (scan >= base + npages) goto found; + if (test_bit(scan, sun4c_iobuffer_map)) break; + scan++; + } + } + +found: + high = ((base + npages) << PAGE_SHIFT) + sun4c_iobuffer_start; + high = SUN4C_REAL_PGDIR_ALIGN(high); + while (high > sun4c_iobuffer_high) { + get_locked_segment(sun4c_iobuffer_high); + sun4c_iobuffer_high += SUN4C_REAL_PGDIR_SIZE; + } + + vpage = ((unsigned long) vaddr) & PAGE_MASK; + for (scan = base; scan < base+npages; scan++) { + pte = ((vpage-PAGE_OFFSET) >> PAGE_SHIFT); + pte |= pgprot_val(SUN4C_PAGE_KERNEL); + pte |= _SUN4C_PAGE_NOCACHE; + set_bit(scan, sun4c_iobuffer_map); + apage = (scan << PAGE_SHIFT) + sun4c_iobuffer_start; + + /* Flush original mapping so we see the right things later. */ + sun4c_flush_page(vpage); + + sun4c_put_pte(apage, pte); + vpage += PAGE_SIZE; + } + local_irq_restore(flags); + return (char *) ((base << PAGE_SHIFT) + sun4c_iobuffer_start + + (((unsigned long) vaddr) & ~PAGE_MASK)); + +abend: + local_irq_restore(flags); + printk("DMA vaddr=0x%p size=%08lx\n", vaddr, size); + panic("Out of iobuffer table"); + return NULL; +} + +static void sun4c_unlockarea(char *vaddr, unsigned long size) +{ + unsigned long vpage, npages; + unsigned long flags; + int scan, high; + + vpage = (unsigned long)vaddr & PAGE_MASK; + npages = (((unsigned long)vaddr & ~PAGE_MASK) + + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; + + local_irq_save(flags); + while (npages != 0) { + --npages; + + /* This mapping is marked non-cachable, no flush necessary. */ + sun4c_put_pte(vpage, 0); + clear_bit((vpage - sun4c_iobuffer_start) >> PAGE_SHIFT, + sun4c_iobuffer_map); + vpage += PAGE_SIZE; + } + + /* garbage collect */ + scan = (sun4c_iobuffer_high - sun4c_iobuffer_start) >> PAGE_SHIFT; + while (scan >= 0 && !sun4c_iobuffer_map[scan >> 5]) + scan -= 32; + scan += 32; + high = sun4c_iobuffer_start + (scan << PAGE_SHIFT); + high = SUN4C_REAL_PGDIR_ALIGN(high) + SUN4C_REAL_PGDIR_SIZE; + while (high < sun4c_iobuffer_high) { + sun4c_iobuffer_high -= SUN4C_REAL_PGDIR_SIZE; + free_locked_segment(sun4c_iobuffer_high); + } + local_irq_restore(flags); +} + +/* 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 __u32 sun4c_get_scsi_one(char *bufptr, unsigned long len, struct sbus_bus *sbus) +{ + unsigned long page; + + page = ((unsigned long)bufptr) & PAGE_MASK; + if (!virt_addr_valid(page)) { + sun4c_flush_page(page); + return (__u32)bufptr; /* already locked */ + } + return (__u32)sun4c_lockarea(bufptr, len); +} + +static void sun4c_get_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + while (sz != 0) { + --sz; + sg[sz].dvma_address = (__u32)sun4c_lockarea(page_address(sg[sz].page) + sg[sz].offset, sg[sz].length); + sg[sz].dvma_length = sg[sz].length; + } +} + +static void sun4c_release_scsi_one(__u32 bufptr, unsigned long len, struct sbus_bus *sbus) +{ + if (bufptr < sun4c_iobuffer_start) + return; /* On kernel stack or similar, see above */ + sun4c_unlockarea((char *)bufptr, len); +} + +static void sun4c_release_scsi_sgl(struct scatterlist *sg, int sz, struct sbus_bus *sbus) +{ + while (sz != 0) { + --sz; + sun4c_unlockarea((char *)sg[sz].dvma_address, sg[sz].length); + } +} + +#define TASK_ENTRY_SIZE BUCKET_SIZE /* see above */ +#define LONG_ALIGN(x) (((x)+(sizeof(long))-1)&~((sizeof(long))-1)) + +struct vm_area_struct sun4c_kstack_vma; + +static void __init sun4c_init_lock_areas(void) +{ + 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_TASK_BUCKETS)); + if (sun4c_taskstack_end >= SUN4C_LOCK_END) { + prom_printf("Too many tasks, decrease NR_TASK_BUCKETS please.\n"); + prom_halt(); + } + + sun4c_iobuffer_start = sun4c_iobuffer_high = + SUN4C_REAL_PGDIR_ALIGN(sun4c_taskstack_end); + sun4c_iobuffer_end = SUN4C_LOCK_END; + 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 = __alloc_bootmem(bitmap_size, SMP_CACHE_BYTES, 0UL); + memset((void *) sun4c_iobuffer_map, 0, bitmap_size); + + sun4c_kstack_vma.vm_mm = &init_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_mm, &sun4c_kstack_vma); +} + +/* Cache flushing on the sun4c. */ +static void sun4c_flush_cache_all(void) +{ + unsigned long begin, end; + + flush_user_windows(); + begin = (KERNBASE + SUN4C_REAL_PGDIR_SIZE); + end = (begin + SUN4C_VAC_SIZE); + + if (sun4c_vacinfo.linesize == 32) { + while (begin < end) { + __asm__ __volatile__( + "ld [%0 + 0x00], %%g0\n\t" + "ld [%0 + 0x20], %%g0\n\t" + "ld [%0 + 0x40], %%g0\n\t" + "ld [%0 + 0x60], %%g0\n\t" + "ld [%0 + 0x80], %%g0\n\t" + "ld [%0 + 0xa0], %%g0\n\t" + "ld [%0 + 0xc0], %%g0\n\t" + "ld [%0 + 0xe0], %%g0\n\t" + "ld [%0 + 0x100], %%g0\n\t" + "ld [%0 + 0x120], %%g0\n\t" + "ld [%0 + 0x140], %%g0\n\t" + "ld [%0 + 0x160], %%g0\n\t" + "ld [%0 + 0x180], %%g0\n\t" + "ld [%0 + 0x1a0], %%g0\n\t" + "ld [%0 + 0x1c0], %%g0\n\t" + "ld [%0 + 0x1e0], %%g0\n" + : : "r" (begin)); + begin += 512; + } + } else { + while (begin < end) { + __asm__ __volatile__( + "ld [%0 + 0x00], %%g0\n\t" + "ld [%0 + 0x10], %%g0\n\t" + "ld [%0 + 0x20], %%g0\n\t" + "ld [%0 + 0x30], %%g0\n\t" + "ld [%0 + 0x40], %%g0\n\t" + "ld [%0 + 0x50], %%g0\n\t" + "ld [%0 + 0x60], %%g0\n\t" + "ld [%0 + 0x70], %%g0\n\t" + "ld [%0 + 0x80], %%g0\n\t" + "ld [%0 + 0x90], %%g0\n\t" + "ld [%0 + 0xa0], %%g0\n\t" + "ld [%0 + 0xb0], %%g0\n\t" + "ld [%0 + 0xc0], %%g0\n\t" + "ld [%0 + 0xd0], %%g0\n\t" + "ld [%0 + 0xe0], %%g0\n\t" + "ld [%0 + 0xf0], %%g0\n" + : : "r" (begin)); + begin += 256; + } + } +} + +static void sun4c_flush_cache_mm(struct mm_struct *mm) +{ + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + flush_user_windows(); + + if (sun4c_context_ring[new_ctx].num_entries) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + unsigned long flags; + + local_irq_save(flags); + if (head->next != head) { + struct sun4c_mmu_entry *entry = head->next; + int savectx = sun4c_get_context(); + + sun4c_set_context(new_ctx); + sun4c_flush_context(); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + + entry = next; + } while (entry != head); + sun4c_set_context(savectx); + } + local_irq_restore(flags); + } + } +} + +static void sun4c_flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + struct sun4c_mmu_entry *entry; + unsigned long flags; + + flush_user_windows(); + + local_irq_save(flags); + /* All user segmap chains are ordered on entry->vaddr. */ + for (entry = head->next; + (entry != head) && ((entry->vaddr+SUN4C_REAL_PGDIR_SIZE) < start); + entry = entry->next) + ; + + /* Tracing various job mixtures showed that this conditional + * only passes ~35% of the time for most worse case situations, + * therefore we avoid all of this gross overhead ~65% of the time. + */ + if ((entry != head) && (entry->vaddr < end)) { + int octx = sun4c_get_context(); + sun4c_set_context(new_ctx); + + /* At this point, always, (start >= entry->vaddr) and + * (entry->vaddr < end), once the latter condition + * ceases to hold, or we hit the end of the list, we + * exit the loop. The ordering of all user allocated + * segmaps makes this all work out so beautifully. + */ + do { + struct sun4c_mmu_entry *next = entry->next; + unsigned long realend; + + /* "realstart" is always >= entry->vaddr */ + realend = entry->vaddr + SUN4C_REAL_PGDIR_SIZE; + if (end < realend) + realend = end; + if ((realend - entry->vaddr) <= (PAGE_SIZE << 3)) { + unsigned long page = entry->vaddr; + while (page < realend) { + sun4c_flush_page(page); + page += PAGE_SIZE; + } + } else { + sun4c_flush_segment(entry->vaddr); + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + } + entry = next; + } while ((entry != head) && (entry->vaddr < end)); + sun4c_set_context(octx); + } + local_irq_restore(flags); + } +} + +static void sun4c_flush_cache_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + /* Sun4c has no separate I/D caches so cannot optimize for non + * text page flushes. + */ + if (new_ctx != NO_CONTEXT) { + int octx = sun4c_get_context(); + unsigned long flags; + + flush_user_windows(); + local_irq_save(flags); + sun4c_set_context(new_ctx); + sun4c_flush_page(page); + sun4c_set_context(octx); + local_irq_restore(flags); + } +} + +static void sun4c_flush_page_to_ram(unsigned long page) +{ + unsigned long flags; + + local_irq_save(flags); + sun4c_flush_page(page); + local_irq_restore(flags); +} + +/* Sun4c cache is unified, both instructions and data live there, so + * no need to flush the on-stack instructions for new signal handlers. + */ +static void sun4c_flush_sig_insns(struct mm_struct *mm, unsigned long insn_addr) +{ +} + +/* TLB flushing on the sun4c. These routines count on the cache + * flushing code to flush the user register windows so that we need + * not do so when we get here. + */ + +static void sun4c_flush_tlb_all(void) +{ + struct sun4c_mmu_entry *this_entry, *next_entry; + unsigned long flags; + int savectx, ctx; + + local_irq_save(flags); + this_entry = sun4c_kernel_ring.ringhd.next; + savectx = sun4c_get_context(); + flush_user_windows(); + while (sun4c_kernel_ring.num_entries) { + next_entry = this_entry->next; + sun4c_flush_segment(this_entry->vaddr); + for (ctx = 0; ctx < num_contexts; ctx++) { + sun4c_set_context(ctx); + sun4c_put_segmap(this_entry->vaddr, invalid_segment); + } + free_kernel_entry(this_entry, &sun4c_kernel_ring); + this_entry = next_entry; + } + sun4c_set_context(savectx); + local_irq_restore(flags); +} + +static void sun4c_flush_tlb_mm(struct mm_struct *mm) +{ + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + unsigned long flags; + + local_irq_save(flags); + if (head->next != head) { + struct sun4c_mmu_entry *entry = head->next; + int savectx = sun4c_get_context(); + + sun4c_set_context(new_ctx); + sun4c_flush_context(); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + + entry = next; + } while (entry != head); + sun4c_set_context(savectx); + } + local_irq_restore(flags); + } +} + +static void sun4c_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + struct sun4c_mmu_entry *head = &sun4c_context_ring[new_ctx].ringhd; + struct sun4c_mmu_entry *entry; + unsigned long flags; + + local_irq_save(flags); + /* See commentary in sun4c_flush_cache_range(). */ + for (entry = head->next; + (entry != head) && ((entry->vaddr+SUN4C_REAL_PGDIR_SIZE) < start); + entry = entry->next) + ; + + if ((entry != head) && (entry->vaddr < end)) { + int octx = sun4c_get_context(); + + sun4c_set_context(new_ctx); + do { + struct sun4c_mmu_entry *next = entry->next; + + sun4c_flush_segment(entry->vaddr); + sun4c_user_unmap(entry); + free_user_entry(new_ctx, entry); + + entry = next; + } while ((entry != head) && (entry->vaddr < end)); + sun4c_set_context(octx); + } + local_irq_restore(flags); + } +} + +static void sun4c_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) +{ + struct mm_struct *mm = vma->vm_mm; + int new_ctx = mm->context; + + if (new_ctx != NO_CONTEXT) { + int savectx = sun4c_get_context(); + unsigned long flags; + + local_irq_save(flags); + sun4c_set_context(new_ctx); + page &= PAGE_MASK; + sun4c_flush_page(page); + sun4c_put_pte(page, 0); + sun4c_set_context(savectx); + local_irq_restore(flags); + } +} + +static inline void sun4c_mapioaddr(unsigned long physaddr, unsigned long virt_addr) +{ + unsigned long page_entry; + + page_entry = ((physaddr >> PAGE_SHIFT) & SUN4C_PFN_MASK); + page_entry |= ((pg_iobits | _SUN4C_PAGE_PRIV) & ~(_SUN4C_PAGE_PRESENT)); + sun4c_put_pte(virt_addr, page_entry); +} + +static void sun4c_mapiorange(unsigned int bus, unsigned long xpa, + unsigned long xva, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + sun4c_mapioaddr(xpa, xva); + xva += PAGE_SIZE; + xpa += PAGE_SIZE; + } +} + +static void sun4c_unmapiorange(unsigned long virt_addr, unsigned int len) +{ + while (len != 0) { + len -= PAGE_SIZE; + sun4c_put_pte(virt_addr, 0); + virt_addr += PAGE_SIZE; + } +} + +static void sun4c_alloc_context(struct mm_struct *old_mm, 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 == old_mm) + ctxp = ctxp->next; + 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); +} + +/* Switch the current MM context. */ +static void sun4c_switch_mm(struct mm_struct *old_mm, struct mm_struct *mm, struct task_struct *tsk, int cpu) +{ + struct ctx_list *ctx; + int dirty = 0; + + if (mm->context == NO_CONTEXT) { + dirty = 1; + sun4c_alloc_context(old_mm, mm); + } else { + /* Update the LRU ring of contexts. */ + ctx = ctx_list_pool + mm->context; + remove_from_ctx_list(ctx); + add_to_used_ctxlist(ctx); + } + if (dirty || old_mm != mm) + sun4c_set_context(mm->context); +} + +static void sun4c_destroy_context(struct mm_struct *mm) +{ + struct ctx_list *ctx_old; + + 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; + } +} + +static void sun4c_mmu_info(struct seq_file *m) +{ + 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; + + seq_printf(m, + "vacsize\t\t: %d bytes\n" + "vachwflush\t: %s\n" + "vaclinesize\t: %d bytes\n" + "mmuctxs\t\t: %d\n" + "mmupsegs\t: %d\n" + "kernelpsegs\t: %d\n" + "kfreepsegs\t: %d\n" + "usedpsegs\t: %d\n" + "ufreepsegs\t: %d\n" + "user_taken\t: %d\n" + "max_taken\t: %d\n", + sun4c_vacinfo.num_bytes, + (sun4c_vacinfo.do_hwflushes ? "yes" : "no"), + sun4c_vacinfo.linesize, + num_contexts, + (invalid_segment + 1), + sun4c_kernel_ring.num_entries, + sun4c_kfree_ring.num_entries, + used_user_entries, + sun4c_ufree_ring.num_entries, + sun4c_user_taken_entries, + max_user_taken_entries); +} + +/* Nothing below here should touch the mmu hardware nor the mmu_entry + * data structures. + */ + +/* 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) + +static void sun4c_set_pte(pte_t *ptep, pte_t pte) +{ + *ptep = pte; +} + +static void sun4c_pgd_set(pgd_t * pgdp, pmd_t * pmdp) +{ +} + +static void sun4c_pmd_set(pmd_t * pmdp, pte_t * ptep) +{ + pmdp->pmdv[0] = PGD_TABLE | (unsigned long) ptep; +} + +static void sun4c_pmd_populate(pmd_t * pmdp, struct page * ptep) +{ + if (page_address(ptep) == NULL) BUG(); /* No highmem on sun4c */ + pmdp->pmdv[0] = PGD_TABLE | (unsigned long) page_address(ptep); +} + +static int sun4c_pte_present(pte_t pte) +{ + return ((pte_val(pte) & (_SUN4C_PAGE_PRESENT | _SUN4C_PAGE_PRIV)) != 0); +} +static void sun4c_pte_clear(pte_t *ptep) { *ptep = __pte(0); } + +static int sun4c_pte_read(pte_t pte) +{ + return (pte_val(pte) & _SUN4C_PAGE_READ); +} + +static int sun4c_pmd_bad(pmd_t pmd) +{ + return (((pmd_val(pmd) & ~PAGE_MASK) != PGD_TABLE) || + (!virt_addr_valid(pmd_val(pmd)))); +} + +static int sun4c_pmd_present(pmd_t pmd) +{ + return ((pmd_val(pmd) & PGD_PRESENT) != 0); +} + +#if 0 /* if PMD takes one word */ +static void sun4c_pmd_clear(pmd_t *pmdp) { *pmdp = __pmd(0); } +#else /* if pmd_t is a longish aggregate */ +static void sun4c_pmd_clear(pmd_t *pmdp) { + memset((void *)pmdp, 0, sizeof(pmd_t)); +} +#endif + +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 void sun4c_pgd_clear(pgd_t * pgdp) { } + +/* + * The following only work if pte_present() is true. + * Undefined behaviour if not.. + */ +static pte_t sun4c_pte_mkwrite(pte_t pte) +{ + pte = __pte(pte_val(pte) | _SUN4C_PAGE_WRITE); + if (pte_val(pte) & _SUN4C_PAGE_MODIFIED) + pte = __pte(pte_val(pte) | _SUN4C_PAGE_SILENT_WRITE); + return pte; +} + +static pte_t sun4c_pte_mkdirty(pte_t pte) +{ + pte = __pte(pte_val(pte) | _SUN4C_PAGE_MODIFIED); + if (pte_val(pte) & _SUN4C_PAGE_WRITE) + pte = __pte(pte_val(pte) | _SUN4C_PAGE_SILENT_WRITE); + return pte; +} + +static pte_t sun4c_pte_mkyoung(pte_t pte) +{ + pte = __pte(pte_val(pte) | _SUN4C_PAGE_ACCESSED); + if (pte_val(pte) & _SUN4C_PAGE_READ) + pte = __pte(pte_val(pte) | _SUN4C_PAGE_SILENT_READ); + return pte; +} + +/* + * 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(struct page *page, pgprot_t pgprot) +{ + return __pte(page_to_pfn(page) | pgprot_val(pgprot)); +} + +static pte_t sun4c_mk_pte_phys(unsigned long phys_page, pgprot_t pgprot) +{ + return __pte((phys_page >> PAGE_SHIFT) | pgprot_val(pgprot)); +} + +static pte_t sun4c_mk_pte_io(unsigned long page, pgprot_t pgprot, int space) +{ + return __pte(((page - PAGE_OFFSET) >> PAGE_SHIFT) | pgprot_val(pgprot)); +} + +static unsigned long sun4c_pte_pfn(pte_t pte) +{ + return pte_val(pte) & SUN4C_PFN_MASK; +} + +static pte_t sun4c_pgoff_to_pte(unsigned long pgoff) +{ + return __pte(pgoff | _SUN4C_PAGE_FILE); +} + +static unsigned long sun4c_pte_to_pgoff(pte_t pte) +{ + return pte_val(pte) & ((1UL << PTE_FILE_MAX_BITS) - 1); +} + + +static __inline__ unsigned long sun4c_pmd_page_v(pmd_t pmd) +{ + return (pmd_val(pmd) & PAGE_MASK); +} + +static struct page *sun4c_pmd_page(pmd_t pmd) +{ + return virt_to_page(sun4c_pmd_page_v(pmd)); +} + +static unsigned long sun4c_pgd_page(pgd_t pgd) { return 0; } + +/* to find an entry in a page-table-directory */ +static inline pgd_t *sun4c_pgd_offset(struct mm_struct * mm, unsigned long address) +{ + return mm->pgd + (address >> SUN4C_PGDIR_SHIFT); +} + +/* Find an entry in the second-level page table.. */ +static pmd_t *sun4c_pmd_offset(pgd_t * dir, unsigned long address) +{ + return (pmd_t *) dir; +} + +/* Find an entry in the third-level page table.. */ +pte_t *sun4c_pte_offset_kernel(pmd_t * dir, unsigned long address) +{ + return (pte_t *) sun4c_pmd_page_v(*dir) + + ((address >> PAGE_SHIFT) & (SUN4C_PTRS_PER_PTE - 1)); +} + +static unsigned long sun4c_swp_type(swp_entry_t entry) +{ + return (entry.val & SUN4C_SWP_TYPE_MASK); +} + +static unsigned long sun4c_swp_offset(swp_entry_t entry) +{ + return (entry.val >> SUN4C_SWP_OFF_SHIFT) & SUN4C_SWP_OFF_MASK; +} + +static swp_entry_t sun4c_swp_entry(unsigned long type, unsigned long offset) +{ + return (swp_entry_t) { + (offset & SUN4C_SWP_OFF_MASK) << SUN4C_SWP_OFF_SHIFT + | (type & SUN4C_SWP_TYPE_MASK) }; +} + +static void sun4c_free_pte_slow(pte_t *pte) +{ + free_page((unsigned long)pte); +} + +static void sun4c_free_pgd_slow(pgd_t *pgd) +{ + free_page((unsigned long)pgd); +} + +static pgd_t *sun4c_get_pgd_fast(void) +{ + unsigned long *ret; + + if ((ret = pgd_quicklist) != NULL) { + pgd_quicklist = (unsigned long *)(*ret); + ret[0] = ret[1]; + pgtable_cache_size--; + } else { + pgd_t *init; + + ret = (unsigned long *)__get_free_page(GFP_KERNEL); + memset (ret, 0, (KERNBASE / SUN4C_PGDIR_SIZE) * sizeof(pgd_t)); + init = sun4c_pgd_offset(&init_mm, 0); + memcpy (((pgd_t *)ret) + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD, + (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)); + } + return (pgd_t *)ret; +} + +static void sun4c_free_pgd_fast(pgd_t *pgd) +{ + *(unsigned long *)pgd = (unsigned long) pgd_quicklist; + pgd_quicklist = (unsigned long *) pgd; + pgtable_cache_size++; +} + + +static __inline__ pte_t * +sun4c_pte_alloc_one_fast(struct mm_struct *mm, unsigned long address) +{ + unsigned long *ret; + + if ((ret = (unsigned long *)pte_quicklist) != NULL) { + pte_quicklist = (unsigned long *)(*ret); + ret[0] = ret[1]; + pgtable_cache_size--; + } + return (pte_t *)ret; +} + +static pte_t *sun4c_pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address) +{ + pte_t *pte; + + if ((pte = sun4c_pte_alloc_one_fast(mm, address)) != NULL) + return pte; + + pte = (pte_t *)__get_free_page(GFP_KERNEL|__GFP_REPEAT); + if (pte) + memset(pte, 0, PAGE_SIZE); + return pte; +} + +static struct page *sun4c_pte_alloc_one(struct mm_struct *mm, unsigned long address) +{ + pte_t *pte = sun4c_pte_alloc_one_kernel(mm, address); + if (pte == NULL) + return NULL; + return virt_to_page(pte); +} + +static __inline__ void sun4c_free_pte_fast(pte_t *pte) +{ + *(unsigned long *)pte = (unsigned long) pte_quicklist; + pte_quicklist = (unsigned long *) pte; + pgtable_cache_size++; +} + +static void sun4c_pte_free(struct page *pte) +{ + sun4c_free_pte_fast(page_address(pte)); +} + +/* + * allocating and freeing a pmd is trivial: the 1-entry pmd is + * inside the pgd, so has no extra memory associated with it. + */ +static pmd_t *sun4c_pmd_alloc_one(struct mm_struct *mm, unsigned long address) +{ + BUG(); + return NULL; +} + +static void sun4c_free_pmd_fast(pmd_t * pmd) { } + +static void sun4c_check_pgt_cache(int low, int high) +{ + if (pgtable_cache_size > high) { + do { + if (pgd_quicklist) + sun4c_free_pgd_slow(sun4c_get_pgd_fast()); + if (pte_quicklist) + sun4c_free_pte_slow(sun4c_pte_alloc_one_fast(NULL, 0)); + } while (pgtable_cache_size > low); + } +} + +/* An experiment, turn off by default for now... -DaveM */ +#define SUN4C_PRELOAD_PSEG + +void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +{ + unsigned long flags; + int pseg; + + local_irq_save(flags); + address &= PAGE_MASK; + if ((pseg = sun4c_get_segmap(address)) == invalid_segment) { + struct sun4c_mmu_entry *entry = sun4c_user_strategy(); + struct mm_struct *mm = vma->vm_mm; + unsigned long start, end; + + entry->vaddr = start = (address & SUN4C_REAL_PGDIR_MASK); + entry->ctx = mm->context; + add_ring_ordered(sun4c_context_ring + mm->context, entry); + sun4c_put_segmap(entry->vaddr, entry->pseg); + end = start + SUN4C_REAL_PGDIR_SIZE; + while (start < end) { +#ifdef SUN4C_PRELOAD_PSEG + pgd_t *pgdp = sun4c_pgd_offset(mm, start); + pte_t *ptep; + + if (!pgdp) + goto no_mapping; + ptep = sun4c_pte_offset_kernel((pmd_t *) pgdp, start); + if (!ptep || !(pte_val(*ptep) & _SUN4C_PAGE_PRESENT)) + goto no_mapping; + sun4c_put_pte(start, pte_val(*ptep)); + goto next; + + no_mapping: +#endif + sun4c_put_pte(start, 0); +#ifdef SUN4C_PRELOAD_PSEG + next: +#endif + start += PAGE_SIZE; + } +#ifndef SUN4C_PRELOAD_PSEG + sun4c_put_pte(address, pte_val(pte)); +#endif + local_irq_restore(flags); + return; + } else { + struct sun4c_mmu_entry *entry = &mmu_entry_pool[pseg]; + + remove_lru(entry); + add_lru(entry); + } + + sun4c_put_pte(address, pte_val(pte)); + local_irq_restore(flags); +} + +extern void sparc_context_init(int); +extern unsigned long end; +extern unsigned long bootmem_init(unsigned long *pages_avail); +extern unsigned long last_valid_pfn; + +void __init sun4c_paging_init(void) +{ + int i, cnt; + unsigned long kernel_end, vaddr; + extern struct resource sparc_iomap; + unsigned long end_pfn, pages_avail; + + kernel_end = (unsigned long) &end; + kernel_end += (SUN4C_REAL_PGDIR_SIZE * 4); + kernel_end = SUN4C_REAL_PGDIR_ALIGN(kernel_end); + + pages_avail = 0; + last_valid_pfn = bootmem_init(&pages_avail); + end_pfn = last_valid_pfn; + + sun4c_probe_mmu(); + invalid_segment = (num_segmaps - 1); + 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(sparc_iomap.start, IOBASE_END); + sun4c_init_lock_area(DVMA_VADDR, DVMA_END); + sun4c_init_lock_areas(); + sun4c_init_fill_user_ring(); + + sun4c_set_context(0); + memset(swapper_pg_dir, 0, PAGE_SIZE); + memset(pg0, 0, PAGE_SIZE); + memset(pg1, 0, PAGE_SIZE); + memset(pg2, 0, PAGE_SIZE); + memset(pg3, 0, PAGE_SIZE); + + /* Save work later. */ + vaddr = VMALLOC_START; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg0); + vaddr += SUN4C_PGDIR_SIZE; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg1); + vaddr += SUN4C_PGDIR_SIZE; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg2); + vaddr += SUN4C_PGDIR_SIZE; + swapper_pg_dir[vaddr>>SUN4C_PGDIR_SHIFT] = __pgd(PGD_TABLE | (unsigned long) pg3); + sun4c_init_ss2_cache_bug(); + sparc_context_init(num_contexts); + + { + unsigned long zones_size[MAX_NR_ZONES]; + unsigned long zholes_size[MAX_NR_ZONES]; + unsigned long npages; + int znum; + + for (znum = 0; znum < MAX_NR_ZONES; znum++) + zones_size[znum] = zholes_size[znum] = 0; + + npages = max_low_pfn - pfn_base; + + zones_size[ZONE_DMA] = npages; + zholes_size[ZONE_DMA] = npages - pages_avail; + + npages = highend_pfn - max_low_pfn; + zones_size[ZONE_HIGHMEM] = npages; + zholes_size[ZONE_HIGHMEM] = npages - calc_highpages(); + + free_area_init_node(0, &contig_page_data, zones_size, + pfn_base, zholes_size); + } + + cnt = 0; + for (i = 0; i < num_segmaps; i++) + if (mmu_entry_pool[i].locked) + cnt++; + + max_user_taken_entries = num_segmaps - cnt - 40 - 1; + + printk("SUN4C: %d mmu entries for the kernel\n", cnt); +} + +/* Load up routines and constants for sun4c mmu */ +void __init ld_mmu_sun4c(void) +{ + extern void ___xchg32_sun4c(void); + + printk("Loading sun4c MMU routines\n"); + + /* First the constants */ + BTFIXUPSET_SIMM13(pgdir_shift, SUN4C_PGDIR_SHIFT); + BTFIXUPSET_SETHI(pgdir_size, SUN4C_PGDIR_SIZE); + BTFIXUPSET_SETHI(pgdir_mask, SUN4C_PGDIR_MASK); + + BTFIXUPSET_SIMM13(ptrs_per_pmd, SUN4C_PTRS_PER_PMD); + BTFIXUPSET_SIMM13(ptrs_per_pgd, SUN4C_PTRS_PER_PGD); + BTFIXUPSET_SIMM13(user_ptrs_per_pgd, KERNBASE / SUN4C_PGDIR_SIZE); + + BTFIXUPSET_INT(page_none, pgprot_val(SUN4C_PAGE_NONE)); + BTFIXUPSET_INT(page_shared, pgprot_val(SUN4C_PAGE_SHARED)); + BTFIXUPSET_INT(page_copy, pgprot_val(SUN4C_PAGE_COPY)); + BTFIXUPSET_INT(page_readonly, pgprot_val(SUN4C_PAGE_READONLY)); + BTFIXUPSET_INT(page_kernel, pgprot_val(SUN4C_PAGE_KERNEL)); + page_kernel = pgprot_val(SUN4C_PAGE_KERNEL); + pg_iobits = _SUN4C_PAGE_PRESENT | _SUN4C_READABLE | _SUN4C_WRITEABLE | + _SUN4C_PAGE_IO | _SUN4C_PAGE_NOCACHE; + + /* Functions */ + BTFIXUPSET_CALL(___xchg32, ___xchg32_sun4c, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(do_check_pgt_cache, sun4c_check_pgt_cache, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_cache_all, sun4c_flush_cache_all, BTFIXUPCALL_NORM); + + if (sun4c_vacinfo.do_hwflushes) { + BTFIXUPSET_CALL(sun4c_flush_page, sun4c_flush_page_hw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_segment, sun4c_flush_segment_hw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_context, sun4c_flush_context_hw, BTFIXUPCALL_NORM); + } else { + BTFIXUPSET_CALL(sun4c_flush_page, sun4c_flush_page_sw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_segment, sun4c_flush_segment_sw, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sun4c_flush_context, sun4c_flush_context_sw, BTFIXUPCALL_NORM); + } + + BTFIXUPSET_CALL(flush_tlb_mm, sun4c_flush_tlb_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_mm, sun4c_flush_cache_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(destroy_context, sun4c_destroy_context, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(switch_mm, sun4c_switch_mm, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_page, sun4c_flush_cache_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_page, sun4c_flush_tlb_page, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_range, sun4c_flush_tlb_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_cache_range, sun4c_flush_cache_range, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__flush_page_to_ram, sun4c_flush_page_to_ram, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(flush_tlb_all, sun4c_flush_tlb_all, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(flush_sig_insns, sun4c_flush_sig_insns, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(set_pte, sun4c_set_pte, BTFIXUPCALL_STO1O0); + + /* The 2.4.18 code does not set this on sun4c, how does it work? XXX */ + /* BTFIXUPSET_SETHI(none_mask, 0x00000000); */ /* Defaults to zero? */ + + BTFIXUPSET_CALL(pte_pfn, sun4c_pte_pfn, BTFIXUPCALL_NORM); +#if 0 /* PAGE_SHIFT <= 12 */ /* Eek. Investigate. XXX */ + BTFIXUPSET_CALL(pmd_page, sun4c_pmd_page, BTFIXUPCALL_ANDNINT(PAGE_SIZE - 1)); +#else + BTFIXUPSET_CALL(pmd_page, sun4c_pmd_page, BTFIXUPCALL_NORM); +#endif + BTFIXUPSET_CALL(pmd_set, sun4c_pmd_set, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_populate, sun4c_pmd_populate, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_present, sun4c_pte_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_clear, sun4c_pte_clear, BTFIXUPCALL_STG0O0); + BTFIXUPSET_CALL(pte_read, sun4c_pte_read, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pmd_bad, sun4c_pmd_bad, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_present, sun4c_pmd_present, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pmd_clear, sun4c_pmd_clear, BTFIXUPCALL_STG0O0); + + BTFIXUPSET_CALL(pgd_none, sun4c_pgd_none, BTFIXUPCALL_RETINT(0)); + BTFIXUPSET_CALL(pgd_bad, sun4c_pgd_bad, BTFIXUPCALL_RETINT(0)); + BTFIXUPSET_CALL(pgd_present, sun4c_pgd_present, BTFIXUPCALL_RETINT(1)); + BTFIXUPSET_CALL(pgd_clear, sun4c_pgd_clear, BTFIXUPCALL_NOP); + + BTFIXUPSET_CALL(mk_pte, sun4c_mk_pte, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_phys, sun4c_mk_pte_phys, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mk_pte_io, sun4c_mk_pte_io, BTFIXUPCALL_NORM); + + BTFIXUPSET_INT(pte_modify_mask, _SUN4C_PAGE_CHG_MASK); + BTFIXUPSET_CALL(pmd_offset, sun4c_pmd_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_offset_kernel, sun4c_pte_offset_kernel, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pte_fast, sun4c_free_pte_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_free, sun4c_pte_free, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one_kernel, sun4c_pte_alloc_one_kernel, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_alloc_one, sun4c_pte_alloc_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_pmd_fast, sun4c_free_pmd_fast, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(pmd_alloc_one, sun4c_pmd_alloc_one, BTFIXUPCALL_RETO0); + BTFIXUPSET_CALL(free_pgd_fast, sun4c_free_pgd_fast, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(get_pgd_fast, sun4c_get_pgd_fast, BTFIXUPCALL_NORM); + + BTFIXUPSET_HALF(pte_writei, _SUN4C_PAGE_WRITE); + BTFIXUPSET_HALF(pte_dirtyi, _SUN4C_PAGE_MODIFIED); + BTFIXUPSET_HALF(pte_youngi, _SUN4C_PAGE_ACCESSED); + BTFIXUPSET_HALF(pte_filei, _SUN4C_PAGE_FILE); + BTFIXUPSET_HALF(pte_wrprotecti, _SUN4C_PAGE_WRITE|_SUN4C_PAGE_SILENT_WRITE); + BTFIXUPSET_HALF(pte_mkcleani, _SUN4C_PAGE_MODIFIED|_SUN4C_PAGE_SILENT_WRITE); + BTFIXUPSET_HALF(pte_mkoldi, _SUN4C_PAGE_ACCESSED|_SUN4C_PAGE_SILENT_READ); + BTFIXUPSET_CALL(pte_mkwrite, sun4c_pte_mkwrite, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_mkdirty, sun4c_pte_mkdirty, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pte_mkyoung, sun4c_pte_mkyoung, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(update_mmu_cache, sun4c_update_mmu_cache, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(pte_to_pgoff, sun4c_pte_to_pgoff, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(pgoff_to_pte, sun4c_pgoff_to_pte, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_lockarea, sun4c_lockarea, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unlockarea, sun4c_unlockarea, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_get_scsi_one, sun4c_get_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_get_scsi_sgl, sun4c_get_scsi_sgl, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_one, sun4c_release_scsi_one, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_release_scsi_sgl, sun4c_release_scsi_sgl, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_map_dma_area, sun4c_map_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_unmap_dma_area, sun4c_unmap_dma_area, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(mmu_translate_dvma, sun4c_translate_dvma, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(sparc_mapiorange, sun4c_mapiorange, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(sparc_unmapiorange, sun4c_unmapiorange, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(__swp_type, sun4c_swp_type, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_offset, sun4c_swp_offset, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(__swp_entry, sun4c_swp_entry, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(alloc_thread_info, sun4c_alloc_thread_info, BTFIXUPCALL_NORM); + BTFIXUPSET_CALL(free_thread_info, sun4c_free_thread_info, BTFIXUPCALL_NORM); + + BTFIXUPSET_CALL(mmu_info, sun4c_mmu_info, BTFIXUPCALL_NORM); + + /* These should _never_ get called with two level tables. */ + BTFIXUPSET_CALL(pgd_set, sun4c_pgd_set, BTFIXUPCALL_NOP); + BTFIXUPSET_CALL(pgd_page, sun4c_pgd_page, BTFIXUPCALL_RETO0); +} diff --git a/arch/sparc/mm/swift.S b/arch/sparc/mm/swift.S new file mode 100644 index 000000000000..2dcaa5ac1a38 --- /dev/null +++ b/arch/sparc/mm/swift.S @@ -0,0 +1,256 @@ +/* $Id: swift.S,v 1.9 2002/01/08 11:11:59 davem Exp $ + * swift.S: MicroSparc-II mmu/cache operations. + * + * Copyright (C) 1999 David S. Miller (davem@redhat.com) + */ + +#include <linux/config.h> +#include <asm/psr.h> +#include <asm/asi.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> +#include <asm/asm_offsets.h> + + .text + .align 4 + +#if 1 /* XXX screw this, I can't get the VAC flushes working + * XXX reliably... -DaveM + */ + .globl swift_flush_cache_all, swift_flush_cache_mm + .globl swift_flush_cache_range, swift_flush_cache_page + .globl swift_flush_page_for_dma + .globl swift_flush_page_to_ram + +swift_flush_cache_all: +swift_flush_cache_mm: +swift_flush_cache_range: +swift_flush_cache_page: +swift_flush_page_for_dma: +swift_flush_page_to_ram: + sethi %hi(0x2000), %o0 +1: subcc %o0, 0x10, %o0 + add %o0, %o0, %o1 + sta %g0, [%o0] ASI_M_DATAC_TAG + bne 1b + sta %g0, [%o1] ASI_M_TXTC_TAG + retl + nop +#else + + .globl swift_flush_cache_all +swift_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) + + /* Just clear out all the tags. */ + sethi %hi(16 * 1024), %o0 +1: subcc %o0, 16, %o0 + sta %g0, [%o0] ASI_M_TXTC_TAG + bne 1b + sta %g0, [%o0] ASI_M_DATAC_TAG + retl + nop + + .globl swift_flush_cache_mm +swift_flush_cache_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be swift_flush_cache_mm_out + WINDOW_FLUSH(%g4, %g5) + rd %psr, %g1 + andn %g1, PSR_ET, %g3 + wr %g3, 0x0, %psr + nop + nop + mov SRMMU_CTX_REG, %g7 + lda [%g7] ASI_M_MMUREGS, %g5 + sta %g2, [%g7] ASI_M_MMUREGS + +#if 1 + sethi %hi(0x2000), %o0 +1: subcc %o0, 0x10, %o0 + sta %g0, [%o0] ASI_M_FLUSH_CTX + bne 1b + nop +#else + clr %o0 + or %g0, 2048, %g7 + or %g0, 2048, %o1 + add %o1, 2048, %o2 + add %o2, 2048, %o3 + mov 16, %o4 + add %o4, 2048, %o5 + add %o5, 2048, %g2 + add %g2, 2048, %g3 +1: sta %g0, [%o0 ] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o1] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o2] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o3] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o4] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %o5] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %g2] ASI_M_FLUSH_CTX + sta %g0, [%o0 + %g3] ASI_M_FLUSH_CTX + subcc %g7, 32, %g7 + bne 1b + add %o0, 32, %o0 +#endif + + mov SRMMU_CTX_REG, %g7 + sta %g5, [%g7] ASI_M_MMUREGS + wr %g1, 0x0, %psr + nop + nop +swift_flush_cache_mm_out: + retl + nop + + .globl swift_flush_cache_range +swift_flush_cache_range: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ + sub %o2, %o1, %o2 + sethi %hi(4096), %o3 + cmp %o2, %o3 + bgu swift_flush_cache_mm + nop + b 70f + nop + + .globl swift_flush_cache_page +swift_flush_cache_page: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +70: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be swift_flush_cache_page_out + WINDOW_FLUSH(%g4, %g5) + rd %psr, %g1 + andn %g1, PSR_ET, %g3 + wr %g3, 0x0, %psr + nop + nop + mov SRMMU_CTX_REG, %g7 + lda [%g7] ASI_M_MMUREGS, %g5 + sta %g2, [%g7] ASI_M_MMUREGS + + andn %o1, (PAGE_SIZE - 1), %o1 +#if 1 + sethi %hi(0x1000), %o0 +1: subcc %o0, 0x10, %o0 + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + bne 1b + nop +#else + or %g0, 512, %g7 + or %g0, 512, %o0 + add %o0, 512, %o2 + add %o2, 512, %o3 + add %o3, 512, %o4 + add %o4, 512, %o5 + add %o5, 512, %g3 + add %g3, 512, %g4 +1: sta %g0, [%o1 ] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o2] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g4] ASI_M_FLUSH_PAGE + subcc %g7, 16, %g7 + bne 1b + add %o1, 16, %o1 +#endif + + mov SRMMU_CTX_REG, %g7 + sta %g5, [%g7] ASI_M_MMUREGS + wr %g1, 0x0, %psr + nop + nop +swift_flush_cache_page_out: + retl + nop + + /* Swift is write-thru, however it is not + * I/O nor TLB-walk coherent. Also it has + * caches which are virtually indexed and tagged. + */ + .globl swift_flush_page_for_dma + .globl swift_flush_page_to_ram +swift_flush_page_for_dma: +swift_flush_page_to_ram: + andn %o0, (PAGE_SIZE - 1), %o1 +#if 1 + sethi %hi(0x1000), %o0 +1: subcc %o0, 0x10, %o0 + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + bne 1b + nop +#else + or %g0, 512, %g7 + or %g0, 512, %o0 + add %o0, 512, %o2 + add %o2, 512, %o3 + add %o3, 512, %o4 + add %o4, 512, %o5 + add %o5, 512, %g3 + add %g3, 512, %g4 +1: sta %g0, [%o1 ] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o0] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o2] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o4] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %o5] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g3] ASI_M_FLUSH_PAGE + sta %g0, [%o1 + %g4] ASI_M_FLUSH_PAGE + subcc %g7, 16, %g7 + bne 1b + add %o1, 16, %o1 +#endif + retl + nop +#endif + + .globl swift_flush_sig_insns +swift_flush_sig_insns: + flush %o1 + retl + flush %o1 + 4 + + .globl swift_flush_tlb_mm + .globl swift_flush_tlb_range + .globl swift_flush_tlb_all +swift_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ +swift_flush_tlb_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be swift_flush_tlb_all_out +swift_flush_tlb_all: + mov 0x400, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +swift_flush_tlb_all_out: + retl + nop + + .globl swift_flush_tlb_page +swift_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + andn %o1, (PAGE_SIZE - 1), %o1 + cmp %o3, -1 + be swift_flush_tlb_page_out + nop +#if 1 + mov 0x400, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +#else + lda [%g1] ASI_M_MMUREGS, %g5 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PAGE /* rem. virt. cache. prot. */ + sta %g0, [%o1] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS +#endif +swift_flush_tlb_page_out: + retl + nop diff --git a/arch/sparc/mm/tsunami.S b/arch/sparc/mm/tsunami.S new file mode 100644 index 000000000000..8acd1787fde2 --- /dev/null +++ b/arch/sparc/mm/tsunami.S @@ -0,0 +1,133 @@ +/* $Id: tsunami.S,v 1.7 2001/12/21 04:56:15 davem Exp $ + * tsunami.S: High speed MicroSparc-I mmu/cache operations. + * + * Copyright (C) 1997 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/config.h> +#include <asm/ptrace.h> +#include <asm/asm_offsets.h> +#include <asm/psr.h> +#include <asm/asi.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> + + .text + .align 4 + + .globl tsunami_flush_cache_all, tsunami_flush_cache_mm + .globl tsunami_flush_cache_range, tsunami_flush_cache_page + .globl tsunami_flush_page_to_ram, tsunami_flush_page_for_dma + .globl tsunami_flush_sig_insns + .globl tsunami_flush_tlb_all, tsunami_flush_tlb_mm + .globl tsunami_flush_tlb_range, tsunami_flush_tlb_page + + /* Sliiick... */ +tsunami_flush_cache_page: +tsunami_flush_cache_range: + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +tsunami_flush_cache_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be tsunami_flush_cache_out +tsunami_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) +tsunami_flush_page_for_dma: + sta %g0, [%g0] ASI_M_IC_FLCLEAR + sta %g0, [%g0] ASI_M_DC_FLCLEAR +tsunami_flush_cache_out: +tsunami_flush_page_to_ram: + retl + nop + +tsunami_flush_sig_insns: + flush %o1 + retl + flush %o1 + 4 + + /* More slick stuff... */ +tsunami_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ +tsunami_flush_tlb_mm: + ld [%o0 + AOFF_mm_context], %g2 + cmp %g2, -1 + be tsunami_flush_tlb_out +tsunami_flush_tlb_all: + mov 0x400, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE + nop + nop + nop + nop + nop +tsunami_flush_tlb_out: + retl + nop + + /* This one can be done in a fine grained manner... */ +tsunami_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + andn %o1, (PAGE_SIZE - 1), %o1 + cmp %o3, -1 + be tsunami_flush_tlb_page_out + lda [%g1] ASI_M_MMUREGS, %g5 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE + nop + nop + nop + nop + nop +tsunami_flush_tlb_page_out: + retl + sta %g5, [%g1] ASI_M_MMUREGS + +#define MIRROR_BLOCK(dst, src, offset, t0, t1, t2, t3) \ + ldd [src + offset + 0x18], t0; \ + std t0, [dst + offset + 0x18]; \ + ldd [src + offset + 0x10], t2; \ + std t2, [dst + offset + 0x10]; \ + ldd [src + offset + 0x08], t0; \ + std t0, [dst + offset + 0x08]; \ + ldd [src + offset + 0x00], t2; \ + std t2, [dst + offset + 0x00]; + + .globl tsunami_copy_1page +tsunami_copy_1page: +/* NOTE: This routine has to be shorter than 70insns --jj */ + or %g0, (PAGE_SIZE >> 8), %g1 +1: + MIRROR_BLOCK(%o0, %o1, 0x00, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x20, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x40, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x60, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0x80, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0xa0, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0xc0, %o2, %o3, %o4, %o5) + MIRROR_BLOCK(%o0, %o1, 0xe0, %o2, %o3, %o4, %o5) + subcc %g1, 1, %g1 + add %o0, 0x100, %o0 + bne 1b + add %o1, 0x100, %o1 + + .globl tsunami_setup_blockops +tsunami_setup_blockops: + sethi %hi(__copy_1page), %o0 + or %o0, %lo(__copy_1page), %o0 + sethi %hi(tsunami_copy_1page), %o1 + or %o1, %lo(tsunami_copy_1page), %o1 + sethi %hi(tsunami_setup_blockops), %o2 + or %o2, %lo(tsunami_setup_blockops), %o2 + ld [%o1], %o4 +1: add %o1, 4, %o1 + st %o4, [%o0] + add %o0, 4, %o0 + cmp %o1, %o2 + bne 1b + ld [%o1], %o4 + sta %g0, [%g0] ASI_M_IC_FLCLEAR + sta %g0, [%g0] ASI_M_DC_FLCLEAR + retl + nop diff --git a/arch/sparc/mm/viking.S b/arch/sparc/mm/viking.S new file mode 100644 index 000000000000..f58712d26bf5 --- /dev/null +++ b/arch/sparc/mm/viking.S @@ -0,0 +1,284 @@ +/* $Id: viking.S,v 1.19 2001/12/21 04:56:15 davem Exp $ + * viking.S: High speed Viking cache/mmu operations + * + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997,1998,1999 Jakub Jelinek (jj@ultra.linux.cz) + * Copyright (C) 1999 Pavel Semerad (semerad@ss1000.ms.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <asm/ptrace.h> +#include <asm/psr.h> +#include <asm/asm_offsets.h> +#include <asm/asi.h> +#include <asm/mxcc.h> +#include <asm/page.h> +#include <asm/pgtsrmmu.h> +#include <asm/viking.h> +#include <asm/btfixup.h> + +#ifdef CONFIG_SMP + .data + .align 4 +sun4dsmp_flush_tlb_spin: + .word 0 +#endif + + .text + .align 4 + + .globl viking_flush_cache_all, viking_flush_cache_mm + .globl viking_flush_cache_range, viking_flush_cache_page + .globl viking_flush_page, viking_mxcc_flush_page + .globl viking_flush_page_for_dma, viking_flush_page_to_ram + .globl viking_flush_sig_insns + .globl viking_flush_tlb_all, viking_flush_tlb_mm + .globl viking_flush_tlb_range, viking_flush_tlb_page + +viking_flush_page: + sethi %hi(PAGE_OFFSET), %g2 + sub %o0, %g2, %g3 + srl %g3, 12, %g1 ! ppage >> 12 + + clr %o1 ! set counter, 0 - 127 + sethi %hi(PAGE_OFFSET + PAGE_SIZE - 0x80000000), %o3 + sethi %hi(0x80000000), %o4 + sethi %hi(VIKING_PTAG_VALID), %o5 + sethi %hi(2*PAGE_SIZE), %o0 + sethi %hi(PAGE_SIZE), %g7 + clr %o2 ! block counter, 0 - 3 +5: + sll %o1, 5, %g4 + or %g4, %o4, %g4 ! 0x80000000 | (set << 5) + + sll %o2, 26, %g5 ! block << 26 +6: + or %g5, %g4, %g5 + ldda [%g5] ASI_M_DATAC_TAG, %g2 + cmp %g3, %g1 ! ptag == ppage? + bne 7f + inc %o2 + + andcc %g2, %o5, %g0 ! ptag VALID? + be 7f + add %g4, %o3, %g2 ! (PAGE_OFFSET + PAGE_SIZE) | (set << 5) + ld [%g2], %g3 + ld [%g2 + %g7], %g3 + add %g2, %o0, %g2 + ld [%g2], %g3 + ld [%g2 + %g7], %g3 + add %g2, %o0, %g2 + ld [%g2], %g3 + ld [%g2 + %g7], %g3 + add %g2, %o0, %g2 + ld [%g2], %g3 + b 8f + ld [%g2 + %g7], %g3 + +7: + cmp %o2, 3 + ble 6b + sll %o2, 26, %g5 ! block << 26 + +8: inc %o1 + cmp %o1, 0x7f + ble 5b + clr %o2 + +9: retl + nop + +viking_mxcc_flush_page: + sethi %hi(PAGE_OFFSET), %g2 + sub %o0, %g2, %g3 + sub %g3, -PAGE_SIZE, %g3 ! ppage + PAGE_SIZE + sethi %hi(MXCC_SRCSTREAM), %o3 ! assume %hi(MXCC_SRCSTREAM) == %hi(MXCC_DESTSTREAM) + mov 0x10, %g2 ! set cacheable bit + or %o3, %lo(MXCC_SRCSTREAM), %o2 + or %o3, %lo(MXCC_DESSTREAM), %o3 + sub %g3, MXCC_STREAM_SIZE, %g3 +6: + stda %g2, [%o2] ASI_M_MXCC + stda %g2, [%o3] ASI_M_MXCC + andncc %g3, PAGE_MASK, %g0 + bne 6b + sub %g3, MXCC_STREAM_SIZE, %g3 + +9: retl + nop + +viking_flush_cache_page: +viking_flush_cache_range: +#ifndef CONFIG_SMP + ld [%o0 + 0x0], %o0 /* XXX vma->vm_mm, GROSS XXX */ +#endif +viking_flush_cache_mm: +#ifndef CONFIG_SMP + ld [%o0 + AOFF_mm_context], %g1 + cmp %g1, -1 + bne viking_flush_cache_all + nop + b,a viking_flush_cache_out +#endif +viking_flush_cache_all: + WINDOW_FLUSH(%g4, %g5) +viking_flush_cache_out: + retl + nop + +viking_flush_tlb_all: + mov 0x400, %g1 + retl + sta %g0, [%g1] ASI_M_FLUSH_PROBE + +viking_flush_tlb_mm: + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o1 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o1, -1 + be 1f +#endif + mov 0x300, %g2 + sta %o1, [%g1] ASI_M_MMUREGS + sta %g0, [%g2] ASI_M_FLUSH_PROBE + retl + sta %g5, [%g1] ASI_M_MMUREGS +#ifndef CONFIG_SMP +1: retl + nop +#endif + +viking_flush_tlb_range: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o3, -1 + be 2f +#endif + sethi %hi(~((1 << SRMMU_PGDIR_SHIFT) - 1)), %o4 + sta %o3, [%g1] ASI_M_MMUREGS + and %o1, %o4, %o1 + add %o1, 0x200, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +1: sub %o1, %o4, %o1 + cmp %o1, %o2 + blu,a 1b + sta %g0, [%o1] ASI_M_FLUSH_PROBE + retl + sta %g5, [%g1] ASI_M_MMUREGS +#ifndef CONFIG_SMP +2: retl + nop +#endif + +viking_flush_tlb_page: + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 +#ifndef CONFIG_SMP + cmp %o3, -1 + be 1f +#endif + and %o1, PAGE_MASK, %o1 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE + retl + sta %g5, [%g1] ASI_M_MMUREGS +#ifndef CONFIG_SMP +1: retl + nop +#endif + +viking_flush_page_to_ram: +viking_flush_page_for_dma: +viking_flush_sig_insns: + retl + nop + +#ifdef CONFIG_SMP + .globl sun4dsmp_flush_tlb_all, sun4dsmp_flush_tlb_mm + .globl sun4dsmp_flush_tlb_range, sun4dsmp_flush_tlb_page +sun4dsmp_flush_tlb_all: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 2f + mov 0x400, %g1 + sta %g0, [%g1] ASI_M_FLUSH_PROBE + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +2: tst %g5 + bne,a 2b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + +sun4dsmp_flush_tlb_mm: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 2f + mov SRMMU_CTX_REG, %g1 + ld [%o0 + AOFF_mm_context], %o1 + lda [%g1] ASI_M_MMUREGS, %g5 + mov 0x300, %g2 + sta %o1, [%g1] ASI_M_MMUREGS + sta %g0, [%g2] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +2: tst %g5 + bne,a 2b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + +sun4dsmp_flush_tlb_range: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 3f + mov SRMMU_CTX_REG, %g1 + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 + sethi %hi(~((1 << SRMMU_PGDIR_SHIFT) - 1)), %o4 + sta %o3, [%g1] ASI_M_MMUREGS + and %o1, %o4, %o1 + add %o1, 0x200, %o1 + sta %g0, [%o1] ASI_M_FLUSH_PROBE +2: sub %o1, %o4, %o1 + cmp %o1, %o2 + blu,a 2b + sta %g0, [%o1] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +3: tst %g5 + bne,a 3b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + +sun4dsmp_flush_tlb_page: + sethi %hi(sun4dsmp_flush_tlb_spin), %g3 +1: ldstub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + tst %g5 + bne 2f + mov SRMMU_CTX_REG, %g1 + ld [%o0 + 0x00], %o0 /* XXX vma->vm_mm GROSS XXX */ + ld [%o0 + AOFF_mm_context], %o3 + lda [%g1] ASI_M_MMUREGS, %g5 + and %o1, PAGE_MASK, %o1 + sta %o3, [%g1] ASI_M_MMUREGS + sta %g0, [%o1] ASI_M_FLUSH_PROBE + sta %g5, [%g1] ASI_M_MMUREGS + retl + stb %g0, [%g3 + %lo(sun4dsmp_flush_tlb_spin)] +2: tst %g5 + bne,a 2b + ldub [%g3 + %lo(sun4dsmp_flush_tlb_spin)], %g5 + b,a 1b + nop +#endif diff --git a/arch/sparc/prom/Makefile b/arch/sparc/prom/Makefile new file mode 100644 index 000000000000..2b217ee40703 --- /dev/null +++ b/arch/sparc/prom/Makefile @@ -0,0 +1,9 @@ +# $Id: Makefile,v 1.8 2000/12/15 00:41:22 davem Exp $ +# Makefile for the Sun Boot PROM interface library under +# Linux. +# + +lib-y := bootstr.o devmap.o devops.o init.o memory.o misc.o mp.o \ + palloc.o ranges.o segment.o console.o printf.o tree.o + +lib-$(CONFIG_SUN4) += sun4prom.o diff --git a/arch/sparc/prom/bootstr.c b/arch/sparc/prom/bootstr.c new file mode 100644 index 000000000000..cfdeac2788d1 --- /dev/null +++ b/arch/sparc/prom/bootstr.c @@ -0,0 +1,63 @@ +/* $Id: bootstr.c,v 1.20 2000/02/08 20:24:23 davem Exp $ + * bootstr.c: Boot string/argument acquisition from the PROM. + * + * Copyright(C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/string.h> +#include <asm/oplib.h> +#include <asm/sun4prom.h> +#include <linux/init.h> + +#define BARG_LEN 256 +static char barg_buf[BARG_LEN] = { 0 }; +static char fetched __initdata = 0; + +extern linux_sun4_romvec *sun4_romvec; + +char * __init +prom_getbootargs(void) +{ + int iter; + char *cp, *arg; + + /* This check saves us from a panic when bootfd patches args. */ + if (fetched) { + return barg_buf; + } + + switch(prom_vers) { + case PROM_V0: + case PROM_SUN4: + cp = barg_buf; + /* Start from 1 and go over fd(0,0,0)kernel */ + for(iter = 1; iter < 8; iter++) { + arg = (*(romvec->pv_v0bootargs))->argv[iter]; + if(arg == 0) break; + while(*arg != 0) { + /* Leave place for space and null. */ + if(cp >= barg_buf + BARG_LEN-2){ + /* We might issue a warning here. */ + break; + } + *cp++ = *arg++; + } + *cp++ = ' '; + } + *cp = 0; + break; + case PROM_V2: + case PROM_V3: + /* + * V3 PROM cannot supply as with more than 128 bytes + * of an argument. But a smart bootstrap loader can. + */ + strlcpy(barg_buf, *romvec->pv_v2bootargs.bootargs, sizeof(barg_buf)); + break; + default: + break; + } + + fetched = 1; + return barg_buf; +} diff --git a/arch/sparc/prom/console.c b/arch/sparc/prom/console.c new file mode 100644 index 000000000000..4e6e41d3291d --- /dev/null +++ b/arch/sparc/prom/console.c @@ -0,0 +1,220 @@ +/* $Id: console.c,v 1.25 2001/10/30 04:54:22 davem 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) + * Copyright (C) 1998 Pete Zaitcev <zaitcev@yahoo.com> + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <asm/openprom.h> +#include <asm/sun4prom.h> +#include <asm/oplib.h> +#include <asm/system.h> +#include <linux/string.h> + +extern void restore_current(void); + +static char con_name_jmc[] = "/obio/su@"; /* "/obio/su@0,3002f8"; */ +#define CON_SIZE_JMC (sizeof(con_name_jmc)) + +/* 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) +{ + static char inc; + int i = -1; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + case PROM_SUN4: + i = (*(romvec->pv_nbgetchar))(); + break; + case PROM_V2: + case PROM_V3: + if( (*(romvec->pv_v2devops).v2_dev_read)(*romvec->pv_v2bootargs.fd_stdin , &inc, 0x1) == 1) { + i = inc; + } else { + i = -1; + } + break; + default: + i = -1; + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, 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) +{ + static char outc; + unsigned long flags; + int i = -1; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + case PROM_SUN4: + i = (*(romvec->pv_nbputchar))(c); + break; + case PROM_V2: + case PROM_V3: + outc = c; + if( (*(romvec->pv_v2devops).v2_dev_write)(*romvec->pv_v2bootargs.fd_stdout, &outc, 0x1) == 1) + i = 0; + else + i = -1; + break; + default: + i = -1; + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, 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 */ +enum prom_input_device +prom_query_input_device(void) +{ + unsigned long flags; + int st_p; + char propb[64]; + char *p; + int propl; + + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + case PROM_SUN4: + 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: + spin_lock_irqsave(&prom_lock, flags); + st_p = (*romvec->pv_v2devops.v2_inst2pkg)(*romvec->pv_v2bootargs.fd_stdin); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + if(prom_node_has_property(st_p, "keyboard")) + return PROMDEV_IKBD; + if (prom_getproperty(st_p, "name", propb, sizeof(propb)) != -1) { + if(strncmp(propb, "keyboard", sizeof("serial")) == 0) + return PROMDEV_IKBD; + } + if (prom_getproperty(st_p, "device_type", propb, sizeof(propb)) != -1) { + if(strncmp(propb, "serial", sizeof("serial"))) + return PROMDEV_I_UNK; + } + propl = prom_getproperty(prom_root_node, "stdin-path", propb, sizeof(propb)); + if(propl > 2) { + 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; + } +} + +/* Query for output device type */ + +enum prom_output_device +prom_query_output_device(void) +{ + unsigned long flags; + int st_p; + char propb[64]; + char *p; + int propl; + + switch(prom_vers) { + case PROM_V0: + case PROM_SUN4: + 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: + spin_lock_irqsave(&prom_lock, flags); + st_p = (*romvec->pv_v2devops.v2_inst2pkg)(*romvec->pv_v2bootargs.fd_stdout); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + propl = prom_getproperty(st_p, "device_type", propb, sizeof(propb)); + if (propl == sizeof("display") && + strncmp("display", propb, sizeof("display")) == 0) + { + return PROMDEV_OSCREEN; + } + if(prom_vers == PROM_V3) { + if(propl >= 0 && + strncmp("serial", propb, sizeof("serial")) != 0) + return PROMDEV_O_UNK; + propl = prom_getproperty(prom_root_node, "stdout-path", + propb, sizeof(propb)); + if(propl == CON_SIZE_JMC && + strncmp(propb, con_name_jmc, CON_SIZE_JMC) == 0) + return PROMDEV_OTTYA; + if(propl > 2) { + 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; + } + } + } else { + switch(*romvec->pv_stdin) { + case PROMDEV_TTYA: return PROMDEV_OTTYA; + case PROMDEV_TTYB: return PROMDEV_OTTYB; + }; + } + break; + default: + ; + }; + return PROMDEV_O_UNK; +} diff --git a/arch/sparc/prom/devmap.c b/arch/sparc/prom/devmap.c new file mode 100644 index 000000000000..eb12073578ad --- /dev/null +++ b/arch/sparc/prom/devmap.c @@ -0,0 +1,54 @@ +/* $Id: devmap.c,v 1.7 2000/08/26 02:38:03 anton Exp $ + * promdevmap.c: Map device/IO areas to virtual addresses. + * + * 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> + +extern void restore_current(void); + +/* Just like the routines in palloc.c, these should not be used + * by the kernel at all. Bootloader facility mainly. And again, + * this is only available on V2 proms and above. + */ + +/* Map physical device address 'paddr' in IO space 'ios' of size + * 'num_bytes' to a virtual address, with 'vhint' being a hint to + * the prom as to where you would prefer the mapping. We return + * where the prom actually mapped it. + */ +char * +prom_mapio(char *vhint, int ios, unsigned int paddr, unsigned int num_bytes) +{ + unsigned long flags; + char *ret; + + spin_lock_irqsave(&prom_lock, flags); + if((num_bytes == 0) || (paddr == 0)) ret = (char *) 0x0; + else + ret = (*(romvec->pv_v2devops.v2_dumb_mmap))(vhint, ios, paddr, + num_bytes); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return ret; +} + +/* Unmap an IO/device area that was mapped using the above routine. */ +void +prom_unmapio(char *vaddr, unsigned int num_bytes) +{ + unsigned long flags; + + if(num_bytes == 0x0) return; + spin_lock_irqsave(&prom_lock, flags); + (*(romvec->pv_v2devops.v2_dumb_munmap))(vaddr, num_bytes); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return; +} diff --git a/arch/sparc/prom/devops.c b/arch/sparc/prom/devops.c new file mode 100644 index 000000000000..61919b54f6cc --- /dev/null +++ b/arch/sparc/prom/devops.c @@ -0,0 +1,89 @@ +/* $Id: devops.c,v 1.13 2000/08/26 02:38:03 anton Exp $ + * devops.c: Device operations 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> + +extern void restore_current(void); + +/* Open the device described by the string 'dstr'. Returns the handle + * to that device used for subsequent operations on that device. + * Returns -1 on failure. + */ +int +prom_devopen(char *dstr) +{ + int handle; + unsigned long flags; + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + handle = (*(romvec->pv_v0devops.v0_devopen))(dstr); + if(handle == 0) handle = -1; + break; + case PROM_V2: + case PROM_V3: + handle = (*(romvec->pv_v2devops.v2_dev_open))(dstr); + break; + default: + handle = -1; + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return handle; +} + +/* Close the device described by device handle 'dhandle'. */ +int +prom_devclose(int dhandle) +{ + unsigned long flags; + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + (*(romvec->pv_v0devops.v0_devclose))(dhandle); + break; + case PROM_V2: + case PROM_V3: + (*(romvec->pv_v2devops.v2_dev_close))(dhandle); + break; + default: + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return 0; +} + +/* Seek to specified location described by 'seekhi' and 'seeklo' + * for device 'dhandle'. + */ +void +prom_seek(int dhandle, unsigned int seekhi, unsigned int seeklo) +{ + unsigned long flags; + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + (*(romvec->pv_v0devops.v0_seekdev))(dhandle, seekhi, seeklo); + break; + case PROM_V2: + case PROM_V3: + (*(romvec->pv_v2devops.v2_dev_seek))(dhandle, seekhi, seeklo); + break; + default: + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return; +} diff --git a/arch/sparc/prom/init.c b/arch/sparc/prom/init.c new file mode 100644 index 000000000000..b83409c81916 --- /dev/null +++ b/arch/sparc/prom/init.c @@ -0,0 +1,95 @@ +/* $Id: init.c,v 1.14 2000/01/29 01:09:12 anton Exp $ + * init.c: Initialize internal variables used by the PROM + * library functions. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/sun4prom.h> + +struct linux_romvec *romvec; +enum prom_major_version prom_vers; +unsigned int prom_rev, prom_prev; +linux_sun4_romvec *sun4_romvec; + +/* The root node of the prom device tree. */ +int prom_root_node; + +int prom_stdin, prom_stdout; + +/* 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_SUN4 + extern struct linux_romvec *sun4_prom_init(void); + rp = sun4_prom_init(); +#endif + romvec = rp; + + 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 40: + prom_vers = PROM_SUN4; + 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(); + + if(prom_vers == PROM_V2 || prom_vers == PROM_V3) { + prom_stdout = *romvec->pv_v2bootargs.fd_stdout; + prom_stdin = *romvec->pv_v2bootargs.fd_stdin; + } + + prom_meminit(); + + prom_ranges_init(); + +#ifndef CONFIG_SUN4 + /* SUN4 prints this in sun4_prom_init */ + printk("PROMLIB: Sun Boot Prom Version %d Revision %d\n", + romvec->pv_romvers, prom_rev); +#endif + + /* Initialization successful. */ + return; +} diff --git a/arch/sparc/prom/memory.c b/arch/sparc/prom/memory.c new file mode 100644 index 000000000000..46aa51afec14 --- /dev/null +++ b/arch/sparc/prom/memory.c @@ -0,0 +1,216 @@ +/* $Id: memory.c,v 1.15 2000/01/29 01:09:12 anton Exp $ + * memory.c: Prom routine for acquiring various bits of information + * about RAM on the machine, both virtual and physical. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1997 Michael A. Griffith (grif@acm.org) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> + +#include <asm/openprom.h> +#include <asm/sun4prom.h> +#include <asm/oplib.h> + +/* This routine, for consistency, returns the ram parameters in the + * V0 prom memory descriptor format. I choose this format because I + * think it was the easiest to work with. I feel the religious + * arguments now... ;) Also, I return the linked lists sorted to + * prevent paging_init() upset stomach as I have not yet written + * the pepto-bismol kernel module yet. + */ + +struct linux_prom_registers prom_reg_memlist[64]; +struct linux_prom_registers prom_reg_tmp[64]; + +struct linux_mlist_v0 prom_phys_total[64]; +struct linux_mlist_v0 prom_prom_taken[64]; +struct linux_mlist_v0 prom_phys_avail[64]; + +struct linux_mlist_v0 *prom_ptot_ptr = prom_phys_total; +struct linux_mlist_v0 *prom_ptak_ptr = prom_prom_taken; +struct linux_mlist_v0 *prom_pavl_ptr = prom_phys_avail; + +struct linux_mem_v0 prom_memlist; + + +/* Internal Prom library routine to sort a linux_mlist_v0 memory + * list. Used below in initialization. + */ +static void __init +prom_sortmemlist(struct linux_mlist_v0 *thislist) +{ + int swapi = 0; + int i, mitr, tmpsize; + char *tmpaddr; + char *lowest; + + for(i=0; thislist[i].theres_more != 0; i++) { + lowest = thislist[i].start_adr; + for(mitr = i+1; thislist[mitr-1].theres_more != 0; mitr++) + if(thislist[mitr].start_adr < lowest) { + lowest = thislist[mitr].start_adr; + swapi = mitr; + } + if(lowest == thislist[i].start_adr) continue; + tmpaddr = thislist[swapi].start_adr; + tmpsize = thislist[swapi].num_bytes; + for(mitr = swapi; mitr > i; mitr--) { + thislist[mitr].start_adr = thislist[mitr-1].start_adr; + thislist[mitr].num_bytes = thislist[mitr-1].num_bytes; + } + thislist[i].start_adr = tmpaddr; + thislist[i].num_bytes = tmpsize; + } + + return; +} + +/* Initialize the memory lists based upon the prom version. */ +void __init prom_meminit(void) +{ + int node = 0; + unsigned int iter, num_regs; + struct linux_mlist_v0 *mptr; /* ptr for traversal */ + + switch(prom_vers) { + case PROM_V0: + /* Nice, kind of easier to do in this case. */ + /* First, the total physical descriptors. */ + for(mptr = (*(romvec->pv_v0mem.v0_totphys)), iter=0; + mptr; mptr=mptr->theres_more, iter++) { + prom_phys_total[iter].start_adr = mptr->start_adr; + prom_phys_total[iter].num_bytes = mptr->num_bytes; + prom_phys_total[iter].theres_more = &prom_phys_total[iter+1]; + } + prom_phys_total[iter-1].theres_more = 0x0; + /* Second, the total prom taken descriptors. */ + for(mptr = (*(romvec->pv_v0mem.v0_prommap)), iter=0; + mptr; mptr=mptr->theres_more, iter++) { + prom_prom_taken[iter].start_adr = mptr->start_adr; + prom_prom_taken[iter].num_bytes = mptr->num_bytes; + prom_prom_taken[iter].theres_more = &prom_prom_taken[iter+1]; + } + prom_prom_taken[iter-1].theres_more = 0x0; + /* Last, the available physical descriptors. */ + for(mptr = (*(romvec->pv_v0mem.v0_available)), iter=0; + mptr; mptr=mptr->theres_more, iter++) { + prom_phys_avail[iter].start_adr = mptr->start_adr; + prom_phys_avail[iter].num_bytes = mptr->num_bytes; + prom_phys_avail[iter].theres_more = &prom_phys_avail[iter+1]; + } + prom_phys_avail[iter-1].theres_more = 0x0; + /* Sort all the lists. */ + prom_sortmemlist(prom_phys_total); + prom_sortmemlist(prom_prom_taken); + prom_sortmemlist(prom_phys_avail); + break; + case PROM_V2: + case PROM_V3: + /* Grrr, have to traverse the prom device tree ;( */ + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(node, "memory"); + num_regs = prom_getproperty(node, "available", + (char *) prom_reg_memlist, + sizeof(prom_reg_memlist)); + num_regs = (num_regs/sizeof(struct linux_prom_registers)); + for(iter=0; iter<num_regs; iter++) { + prom_phys_avail[iter].start_adr = + (char *) prom_reg_memlist[iter].phys_addr; + prom_phys_avail[iter].num_bytes = + (unsigned long) prom_reg_memlist[iter].reg_size; + prom_phys_avail[iter].theres_more = + &prom_phys_avail[iter+1]; + } + prom_phys_avail[iter-1].theres_more = 0x0; + + num_regs = prom_getproperty(node, "reg", + (char *) prom_reg_memlist, + sizeof(prom_reg_memlist)); + num_regs = (num_regs/sizeof(struct linux_prom_registers)); + for(iter=0; iter<num_regs; iter++) { + prom_phys_total[iter].start_adr = + (char *) prom_reg_memlist[iter].phys_addr; + prom_phys_total[iter].num_bytes = + (unsigned long) prom_reg_memlist[iter].reg_size; + prom_phys_total[iter].theres_more = + &prom_phys_total[iter+1]; + } + prom_phys_total[iter-1].theres_more = 0x0; + + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(node, "virtual-memory"); + num_regs = prom_getproperty(node, "available", + (char *) prom_reg_memlist, + sizeof(prom_reg_memlist)); + num_regs = (num_regs/sizeof(struct linux_prom_registers)); + + /* Convert available virtual areas to taken virtual + * areas. First sort, then convert. + */ + for(iter=0; iter<num_regs; iter++) { + prom_prom_taken[iter].start_adr = + (char *) prom_reg_memlist[iter].phys_addr; + prom_prom_taken[iter].num_bytes = + (unsigned long) prom_reg_memlist[iter].reg_size; + prom_prom_taken[iter].theres_more = + &prom_prom_taken[iter+1]; + } + prom_prom_taken[iter-1].theres_more = 0x0; + + prom_sortmemlist(prom_prom_taken); + + /* Finally, convert. */ + for(iter=0; iter<num_regs; iter++) { + prom_prom_taken[iter].start_adr = + prom_prom_taken[iter].start_adr + + prom_prom_taken[iter].num_bytes; + prom_prom_taken[iter].num_bytes = + prom_prom_taken[iter+1].start_adr - + prom_prom_taken[iter].start_adr; + } + prom_prom_taken[iter-1].num_bytes = + 0xffffffff - (unsigned long) prom_prom_taken[iter-1].start_adr; + + /* Sort the other two lists. */ + prom_sortmemlist(prom_phys_total); + prom_sortmemlist(prom_phys_avail); + break; + + case PROM_SUN4: +#ifdef CONFIG_SUN4 + /* how simple :) */ + prom_phys_total[0].start_adr = 0x0; + prom_phys_total[0].num_bytes = *(sun4_romvec->memorysize); + prom_phys_total[0].theres_more = 0x0; + prom_prom_taken[0].start_adr = 0x0; + prom_prom_taken[0].num_bytes = 0x0; + prom_prom_taken[0].theres_more = 0x0; + prom_phys_avail[0].start_adr = 0x0; + prom_phys_avail[0].num_bytes = *(sun4_romvec->memoryavail); + prom_phys_avail[0].theres_more = 0x0; +#endif + break; + + default: + break; + }; + + /* Link all the lists into the top-level descriptor. */ + prom_memlist.v0_totphys=&prom_ptot_ptr; + prom_memlist.v0_prommap=&prom_ptak_ptr; + prom_memlist.v0_available=&prom_pavl_ptr; + + return; +} + +/* This returns a pointer to our libraries internal v0 format + * memory descriptor. + */ +struct linux_mem_v0 * +prom_meminfo(void) +{ + return &prom_memlist; +} diff --git a/arch/sparc/prom/misc.c b/arch/sparc/prom/misc.c new file mode 100644 index 000000000000..c840c2062342 --- /dev/null +++ b/arch/sparc/prom/misc.c @@ -0,0 +1,139 @@ +/* $Id: misc.c,v 1.18 2000/08/26 02:38:03 anton Exp $ + * misc.c: Miscellaneous prom functions that don't belong + * anywhere else. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/auxio.h> +#include <asm/system.h> + +extern void restore_current(void); + +DEFINE_SPINLOCK(prom_lock); + +/* Reset and reboot the machine with the command 'bcommand'. */ +void +prom_reboot(char *bcommand) +{ + unsigned long flags; + spin_lock_irqsave(&prom_lock, flags); + (*(romvec->pv_reboot))(bcommand); + /* Never get here. */ + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); +} + +/* Forth evaluate the expression contained in 'fstring'. */ +void +prom_feval(char *fstring) +{ + unsigned long flags; + if(!fstring || fstring[0] == 0) + return; + spin_lock_irqsave(&prom_lock, flags); + if(prom_vers == PROM_V0) + (*(romvec->pv_fortheval.v0_eval))(strlen(fstring), fstring); + else + (*(romvec->pv_fortheval.v2_eval))(fstring); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); +} + +/* We want to do this more nicely some day. */ +extern void (*prom_palette)(int); + +/* Drop into the prom, with the chance to continue with the 'go' + * prom command. + */ +void +prom_cmdline(void) +{ + extern void install_obp_ticker(void); + extern void install_linux_ticker(void); + unsigned long flags; + + if(!serial_console && prom_palette) + prom_palette (1); + spin_lock_irqsave(&prom_lock, flags); + install_obp_ticker(); + (*(romvec->pv_abort))(); + restore_current(); + install_linux_ticker(); + spin_unlock_irqrestore(&prom_lock, flags); +#ifdef CONFIG_SUN_AUXIO + set_auxio(AUXIO_LED, 0); +#endif + if(!serial_console && prom_palette) + prom_palette (0); +} + +/* Drop into the prom, but completely terminate the program. + * No chance of continuing. + */ +void +prom_halt(void) +{ + unsigned long flags; +again: + spin_lock_irqsave(&prom_lock, flags); + (*(romvec->pv_halt))(); + /* Never get here. */ + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + goto again; /* PROM is out to get me -DaveM */ +} + +typedef void (*sfunc_t)(void); + +/* Set prom sync handler to call function 'funcp'. */ +void +prom_setsync(sfunc_t funcp) +{ + if(!funcp) return; + *romvec->pv_synchook = funcp; +} + +/* 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 len; + + len = prom_getproplen(prom_root_node, "idprom"); + if((len>num_bytes) || (len==-1)) return 0xff; + if(!prom_getproperty(prom_root_node, "idprom", idbuf, num_bytes)) + return idbuf[0]; + + return 0xff; +} + +/* 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/sparc/prom/mp.c b/arch/sparc/prom/mp.c new file mode 100644 index 000000000000..92fe3739fdb8 --- /dev/null +++ b/arch/sparc/prom/mp.c @@ -0,0 +1,121 @@ +/* $Id: mp.c,v 1.12 2000/08/26 02:38:03 anton Exp $ + * mp.c: OpenBoot Prom Multiprocessor support routines. Don't call + * these on a UP or else you will halt and catch fire. ;) + * + * 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> + +extern void restore_current(void); + +/* Start cpu with prom-tree node 'cpunode' using context described + * by 'ctable_reg' in context 'ctx' at program counter 'pc'. + * + * XXX Have to look into what the return values mean. XXX + */ +int +prom_startcpu(int cpunode, struct linux_prom_registers *ctable_reg, int ctx, char *pc) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + default: + ret = -1; + break; + case PROM_V3: + ret = (*(romvec->v3_cpustart))(cpunode, (int) ctable_reg, ctx, pc); + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return ret; +} + +/* Stop CPU with device prom-tree node 'cpunode'. + * XXX Again, what does the return value really mean? XXX + */ +int +prom_stopcpu(int cpunode) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + default: + ret = -1; + break; + case PROM_V3: + ret = (*(romvec->v3_cpustop))(cpunode); + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return ret; +} + +/* Make CPU with device prom-tree node 'cpunode' idle. + * XXX Return value, anyone? XXX + */ +int +prom_idlecpu(int cpunode) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + default: + ret = -1; + break; + case PROM_V3: + ret = (*(romvec->v3_cpuidle))(cpunode); + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return ret; +} + +/* Resume the execution of CPU with nodeid 'cpunode'. + * XXX Come on, somebody has to know... XXX + */ +int +prom_restartcpu(int cpunode) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + switch(prom_vers) { + case PROM_V0: + case PROM_V2: + default: + ret = -1; + break; + case PROM_V3: + ret = (*(romvec->v3_cpuresume))(cpunode); + break; + }; + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return ret; +} diff --git a/arch/sparc/prom/palloc.c b/arch/sparc/prom/palloc.c new file mode 100644 index 000000000000..84ce8bc54473 --- /dev/null +++ b/arch/sparc/prom/palloc.c @@ -0,0 +1,44 @@ +/* $Id: palloc.c,v 1.4 1996/04/25 06:09:48 davem Exp $ + * palloc.c: Memory allocation from the Sun PROM. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#include <asm/openprom.h> +#include <asm/oplib.h> + +/* You should not call these routines after memory management + * has been initialized in the kernel, if fact you should not + * use these if at all possible in the kernel. They are mainly + * to be used for a bootloader for temporary allocations which + * it will free before jumping into the kernel it has loaded. + * + * Also, these routines don't work on V0 proms, only V2 and later. + */ + +/* Allocate a chunk of memory of size 'num_bytes' giving a suggestion + * of virtual_hint as the preferred virtual base address of this chunk. + * There are no guarantees that you will get the allocation, or that + * the prom will abide by your "hint". So check your return value. + */ +char * +prom_alloc(char *virtual_hint, unsigned int num_bytes) +{ + if(prom_vers == PROM_V0) return (char *) 0x0; + if(num_bytes == 0x0) return (char *) 0x0; + return (*(romvec->pv_v2devops.v2_dumb_mem_alloc))(virtual_hint, num_bytes); +} + +/* Free a previously allocated chunk back to the prom at virtual address + * 'vaddr' of size 'num_bytes'. NOTE: This vaddr is not the hint you + * used for the allocation, but the virtual address the prom actually + * returned to you. They may be have been the same, they may have not, + * doesn't matter. + */ +void +prom_free(char *vaddr, unsigned int num_bytes) +{ + if((prom_vers == PROM_V0) || (num_bytes == 0x0)) return; + (*(romvec->pv_v2devops.v2_dumb_mem_free))(vaddr, num_bytes); + return; +} diff --git a/arch/sparc/prom/printf.c b/arch/sparc/prom/printf.c new file mode 100644 index 000000000000..dc8b598bedbb --- /dev/null +++ b/arch/sparc/prom/printf.c @@ -0,0 +1,46 @@ +/* + * printf.c: Internal prom library printf facility. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (c) 2002 Pete Zaitcev (zaitcev@yahoo.com) + * + * We used to warn all over the code: DO NOT USE prom_printf(), + * and yet people do. Anton's banking code was outputing banks + * with prom_printf for most of the 2.4 lifetime. Since an effective + * stick is not available, we deployed a carrot: an early printk + * through PROM by means of -p boot option. This ought to fix it. + * USE printk; if you need, deploy -p. + */ + +#include <linux/kernel.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +static char ppbuf[1024]; + +void +prom_write(const char *buf, unsigned int n) +{ + char ch; + + while (n != 0) { + --n; + if ((ch = *buf++) == '\n') + prom_putchar('\r'); + prom_putchar(ch); + } +} + +void +prom_printf(char *fmt, ...) +{ + va_list args; + int i; + + va_start(args, fmt); + i = vscnprintf(ppbuf, sizeof(ppbuf), fmt, args); + va_end(args); + + prom_write(ppbuf, i); +} diff --git a/arch/sparc/prom/ranges.c b/arch/sparc/prom/ranges.c new file mode 100644 index 000000000000..a2920323c900 --- /dev/null +++ b/arch/sparc/prom/ranges.c @@ -0,0 +1,118 @@ +/* $Id: ranges.c,v 1.15 2001/12/19 00:29:51 davem Exp $ + * ranges.c: Handle ranges in newer proms for obio/sbus. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#include <linux/init.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/types.h> +#include <asm/sbus.h> +#include <asm/system.h> + +struct linux_prom_ranges promlib_obio_ranges[PROMREG_MAX]; +int num_obio_ranges; + +/* Adjust register values based upon the ranges parameters. */ +static void +prom_adjust_regs(struct linux_prom_registers *regp, int nregs, + struct linux_prom_ranges *rangep, int nranges) +{ + int regc, rngc; + + for (regc = 0; regc < nregs; regc++) { + for (rngc = 0; rngc < nranges; rngc++) + if (regp[regc].which_io == rangep[rngc].ot_child_space) + break; /* Fount it */ + if (rngc == nranges) /* oops */ + prom_printf("adjust_regs: Could not find range with matching bus type...\n"); + regp[regc].which_io = rangep[rngc].ot_parent_space; + regp[regc].phys_addr -= rangep[rngc].ot_child_base; + regp[regc].phys_addr += rangep[rngc].ot_parent_base; + } +} + +void +prom_adjust_ranges(struct linux_prom_ranges *ranges1, int nranges1, + struct linux_prom_ranges *ranges2, int nranges2) +{ + int rng1c, rng2c; + + for(rng1c=0; rng1c < nranges1; rng1c++) { + for(rng2c=0; rng2c < nranges2; rng2c++) + if(ranges1[rng1c].ot_parent_space == ranges2[rng2c].ot_child_space && + ranges1[rng1c].ot_parent_base >= ranges2[rng2c].ot_child_base && + ranges2[rng2c].ot_child_base + ranges2[rng2c].or_size - ranges1[rng1c].ot_parent_base > 0U) + break; + if(rng2c == nranges2) /* oops */ + prom_printf("adjust_ranges: Could not find matching bus type...\n"); + else if (ranges1[rng1c].ot_parent_base + ranges1[rng1c].or_size > ranges2[rng2c].ot_child_base + ranges2[rng2c].or_size) + ranges1[rng1c].or_size = + ranges2[rng2c].ot_child_base + ranges2[rng2c].or_size - ranges1[rng1c].ot_parent_base; + ranges1[rng1c].ot_parent_space = ranges2[rng2c].ot_parent_space; + ranges1[rng1c].ot_parent_base += ranges2[rng2c].ot_parent_base; + } +} + +/* Apply probed obio ranges to registers passed, if no ranges return. */ +void +prom_apply_obio_ranges(struct linux_prom_registers *regs, int nregs) +{ + if(num_obio_ranges) + prom_adjust_regs(regs, nregs, promlib_obio_ranges, num_obio_ranges); +} + +void __init prom_ranges_init(void) +{ + int node, obio_node; + int success; + + num_obio_ranges = 0; + + /* Check for obio and sbus ranges. */ + node = prom_getchild(prom_root_node); + obio_node = prom_searchsiblings(node, "obio"); + + if(obio_node) { + success = prom_getproperty(obio_node, "ranges", + (char *) promlib_obio_ranges, + sizeof(promlib_obio_ranges)); + if(success != -1) + num_obio_ranges = (success/sizeof(struct linux_prom_ranges)); + } + + if(num_obio_ranges) + prom_printf("PROMLIB: obio_ranges %d\n", num_obio_ranges); + + return; +} + +void +prom_apply_generic_ranges (int node, int parent, struct linux_prom_registers *regs, int nregs) +{ + int success; + int num_ranges; + struct linux_prom_ranges ranges[PROMREG_MAX]; + + success = prom_getproperty(node, "ranges", + (char *) ranges, + sizeof (ranges)); + if (success != -1) { + num_ranges = (success/sizeof(struct linux_prom_ranges)); + if (parent) { + struct linux_prom_ranges parent_ranges[PROMREG_MAX]; + int num_parent_ranges; + + success = prom_getproperty(parent, "ranges", + (char *) parent_ranges, + sizeof (parent_ranges)); + if (success != -1) { + num_parent_ranges = (success/sizeof(struct linux_prom_ranges)); + prom_adjust_ranges (ranges, num_ranges, parent_ranges, num_parent_ranges); + } + } + prom_adjust_regs(regs, nregs, ranges, num_ranges); + } +} diff --git a/arch/sparc/prom/segment.c b/arch/sparc/prom/segment.c new file mode 100644 index 000000000000..09d6460165ab --- /dev/null +++ b/arch/sparc/prom/segment.c @@ -0,0 +1,29 @@ +/* $Id: segment.c,v 1.7 2000/08/26 02:38:03 anton Exp $ + * segment.c: Prom routine to map segments in other contexts before + * a standalone is completely mapped. This is for sun4 and + * sun4c architectures only. + * + * 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> + +extern void restore_current(void); + +/* Set physical segment 'segment' at virtual address 'vaddr' in + * context 'ctx'. + */ +void +prom_putsegment(int ctx, unsigned long vaddr, int segment) +{ + unsigned long flags; + spin_lock_irqsave(&prom_lock, flags); + (*(romvec->pv_setctxt))(ctx, (char *) vaddr, segment); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return; +} diff --git a/arch/sparc/prom/sun4prom.c b/arch/sparc/prom/sun4prom.c new file mode 100644 index 000000000000..69ca735f0d4e --- /dev/null +++ b/arch/sparc/prom/sun4prom.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 1996 The Australian National University. + * Copyright (C) 1996 Fujitsu Laboratories Limited + * Copyright (C) 1997 Michael A. Griffith (grif@acm.org) + * Copyright (C) 1997 Sun Weenie (ko@ko.reno.nv.us) + * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * + * This software may be distributed under the terms of the Gnu + * Public License version 2 or later + * + * fake a really simple Sun prom for the SUN4 + */ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <asm/oplib.h> +#include <asm/idprom.h> +#include <asm/machines.h> +#include <asm/sun4prom.h> +#include <asm/asi.h> +#include <asm/contregs.h> +#include <linux/init.h> + +static struct linux_romvec sun4romvec; +static struct idprom sun4_idprom; + +struct property { + char *name; + char *value; + int length; +}; + +struct node { + int level; + struct property *properties; +}; + +struct property null_properties = { NULL, NULL, -1 }; + +struct property root_properties[] = { + {"device_type", "cpu", 4}, + {"idprom", (char *)&sun4_idprom, sizeof(struct idprom)}, + {NULL, NULL, -1} +}; + +struct node nodes[] = { + { 0, &null_properties }, + { 0, root_properties }, + { -1,&null_properties } +}; + + +static int no_nextnode(int node) +{ + if (nodes[node].level == nodes[node+1].level) + return node+1; + return -1; +} + +static int no_child(int node) +{ + if (nodes[node].level == nodes[node+1].level-1) + return node+1; + return -1; +} + +static struct property *find_property(int node,char *name) +{ + struct property *prop = &nodes[node].properties[0]; + while (prop && prop->name) { + if (strcmp(prop->name,name) == 0) return prop; + prop++; + } + return NULL; +} + +static int no_proplen(int node,char *name) +{ + struct property *prop = find_property(node,name); + if (prop) return prop->length; + return -1; +} + +static int no_getprop(int node,char *name,char *value) +{ + struct property *prop = find_property(node,name); + if (prop) { + memcpy(value,prop->value,prop->length); + return 1; + } + return -1; +} + +static int no_setprop(int node,char *name,char *value,int len) +{ + return -1; +} + +static char *no_nextprop(int node,char *name) +{ + struct property *prop = find_property(node,name); + if (prop) return prop[1].name; + return NULL; +} + +static struct linux_nodeops sun4_nodeops = { + no_nextnode, + no_child, + no_proplen, + no_getprop, + no_setprop, + no_nextprop +}; + +static int synch_hook; + +struct linux_romvec * __init sun4_prom_init(void) +{ + int i; + unsigned char x; + char *p; + + p = (char *)&sun4_idprom; + for (i = 0; i < sizeof(sun4_idprom); i++) { + __asm__ __volatile__ ("lduba [%1] %2, %0" : "=r" (x) : + "r" (AC_IDPROM + i), "i" (ASI_CONTROL)); + *p++ = x; + } + + memset(&sun4romvec,0,sizeof(sun4romvec)); + + sun4_romvec = (linux_sun4_romvec *) SUN4_PROM_VECTOR; + + sun4romvec.pv_romvers = 40; + sun4romvec.pv_nodeops = &sun4_nodeops; + sun4romvec.pv_reboot = sun4_romvec->reboot; + sun4romvec.pv_abort = sun4_romvec->abortentry; + sun4romvec.pv_halt = sun4_romvec->exittomon; + sun4romvec.pv_synchook = (void (**)(void))&synch_hook; + sun4romvec.pv_setctxt = sun4_romvec->setcxsegmap; + sun4romvec.pv_v0bootargs = sun4_romvec->bootParam; + sun4romvec.pv_nbgetchar = sun4_romvec->mayget; + sun4romvec.pv_nbputchar = sun4_romvec->mayput; + sun4romvec.pv_stdin = sun4_romvec->insource; + sun4romvec.pv_stdout = sun4_romvec->outsink; + + /* + * We turn on the LEDs to let folks without monitors or + * terminals know we booted. Nothing too fancy now. They + * are all on, except for LED 5, which blinks. When we + * have more time, we can teach the penguin to say "By your + * command" or "Activating turbo boost, Michael". :-) + */ + sun4_romvec->setLEDs(0x0); + + printk("PROMLIB: Old Sun4 boot PROM monitor %s, romvec version %d\n", + sun4_romvec->monid, + sun4_romvec->romvecversion); + + return &sun4romvec; +} diff --git a/arch/sparc/prom/tree.c b/arch/sparc/prom/tree.c new file mode 100644 index 000000000000..2bf03ee8cde5 --- /dev/null +++ b/arch/sparc/prom/tree.c @@ -0,0 +1,364 @@ +/* $Id: tree.c,v 1.26 2000/08/26 02:38:03 anton Exp $ + * tree.c: Basic device tree traversal/scanning for the Linux + * prom library. + * + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + */ + +#define PROMLIB_INTERNAL + +#include <linux/string.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/ctype.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> + +extern void restore_current(void); + +static char promlib_buf[128]; + +/* Internal version of prom_getchild that does not alter return values. */ +int __prom_getchild(int node) +{ + unsigned long flags; + int cnode; + + spin_lock_irqsave(&prom_lock, flags); + cnode = prom_nodeops->no_child(node); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return cnode; +} + +/* Return the child of node 'node' or zero if no this node has no + * direct descendent. + */ +int prom_getchild(int node) +{ + int cnode; + + if (node == -1) + return 0; + + cnode = __prom_getchild(node); + if (cnode == 0 || cnode == -1) + return 0; + + return cnode; +} + +/* Internal version of prom_getsibling that does not alter return values. */ +int __prom_getsibling(int node) +{ + unsigned long flags; + int cnode; + + spin_lock_irqsave(&prom_lock, flags); + cnode = prom_nodeops->no_nextnode(node); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return cnode; +} + +/* Return the next sibling of node 'node' or zero if no more siblings + * at this level of depth in the tree. + */ +int prom_getsibling(int node) +{ + int sibnode; + + if (node == -1) + return 0; + + sibnode = __prom_getsibling(node); + if (sibnode == 0 || sibnode == -1) + return 0; + + return sibnode; +} + +/* Return the length in bytes of property 'prop' at node 'node'. + * Return -1 on error. + */ +int prom_getproplen(int node, char *prop) +{ + int ret; + unsigned long flags; + + if((!node) || (!prop)) + return -1; + + spin_lock_irqsave(&prom_lock, flags); + ret = prom_nodeops->no_proplen(node, prop); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return ret; +} + +/* Acquire a property 'prop' at node 'node' and place it in + * 'buffer' which has a size of 'bufsize'. If the acquisition + * was successful the length will be returned, else -1 is returned. + */ +int prom_getproperty(int node, char *prop, char *buffer, int bufsize) +{ + int plen, ret; + unsigned long flags; + + plen = prom_getproplen(node, prop); + if((plen > bufsize) || (plen == 0) || (plen == -1)) + return -1; + /* Ok, things seem all right. */ + spin_lock_irqsave(&prom_lock, flags); + ret = prom_nodeops->no_getprop(node, prop, buffer); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return ret; +} + +/* Acquire an integer property and return its value. Returns -1 + * on failure. + */ +int prom_getint(int node, char *prop) +{ + static int intprop; + + if(prom_getproperty(node, prop, (char *) &intprop, sizeof(int)) != -1) + return intprop; + + return -1; +} + +/* Acquire an integer property, upon error return the passed default + * integer. + */ +int prom_getintdefault(int node, char *property, int deflt) +{ + int retval; + + retval = prom_getint(node, property); + if(retval == -1) return deflt; + + return retval; +} + +/* Acquire a boolean property, 1=TRUE 0=FALSE. */ +int prom_getbool(int node, char *prop) +{ + int retval; + + retval = prom_getproplen(node, prop); + if(retval == -1) return 0; + return 1; +} + +/* Acquire a property whose value is a string, returns a null + * string on error. The char pointer is the user supplied string + * buffer. + */ +void prom_getstring(int node, char *prop, char *user_buf, int ubuf_size) +{ + int len; + + len = prom_getproperty(node, prop, user_buf, ubuf_size); + if(len != -1) return; + user_buf[0] = 0; + return; +} + + +/* Does the device at node 'node' have name 'name'? + * YES = 1 NO = 0 + */ +int prom_nodematch(int node, char *name) +{ + int error; + + static char namebuf[128]; + error = prom_getproperty(node, "name", namebuf, sizeof(namebuf)); + if (error == -1) return 0; + if(strcmp(namebuf, name) == 0) return 1; + return 0; +} + +/* Search siblings at 'node_start' for a node with name + * 'nodename'. Return node if successful, zero if not. + */ +int prom_searchsiblings(int node_start, char *nodename) +{ + + int thisnode, error; + + for(thisnode = node_start; thisnode; + thisnode=prom_getsibling(thisnode)) { + error = prom_getproperty(thisnode, "name", promlib_buf, + sizeof(promlib_buf)); + /* Should this ever happen? */ + if(error == -1) continue; + if(strcmp(nodename, promlib_buf)==0) return thisnode; + } + + return 0; +} + +/* Gets name in the form prom v2+ uses it (name@x,yyyyy or name (if no reg)) */ +int prom_getname (int node, char *buffer, int len) +{ + int i; + struct linux_prom_registers reg[PROMREG_MAX]; + + i = prom_getproperty (node, "name", buffer, len); + if (i <= 0) return -1; + buffer [i] = 0; + len -= i; + i = prom_getproperty (node, "reg", (char *)reg, sizeof (reg)); + if (i <= 0) return 0; + if (len < 11) return -1; + buffer = strchr (buffer, 0); + sprintf (buffer, "@%x,%x", reg[0].which_io, (uint)reg[0].phys_addr); + return 0; +} + +/* Interal version of nextprop that does not alter return values. */ +char * __prom_nextprop(int node, char * oprop) +{ + unsigned long flags; + char *prop; + + spin_lock_irqsave(&prom_lock, flags); + prop = prom_nodeops->no_nextprop(node, oprop); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + + return prop; +} + +/* Return the first property name for node 'node'. */ +/* buffer is unused argument, but as v9 uses it, we need to have the same interface */ +char * prom_firstprop(int node, char *bufer) +{ + if (node == 0 || node == -1) + return ""; + + return __prom_nextprop(node, ""); +} + +/* Return the property type string after property type 'oprop' + * at node 'node' . Returns empty string if no more + * property types for this node. + */ +char * prom_nextprop(int node, char *oprop, char *buffer) +{ + if (node == 0 || node == -1) + return ""; + + return __prom_nextprop(node, oprop); +} + +int prom_finddevice(char *name) +{ + char nbuf[128]; + char *s = name, *d; + int node = prom_root_node, node2; + unsigned int which_io, phys_addr; + struct linux_prom_registers reg[PROMREG_MAX]; + + while (*s++) { + if (!*s) return node; /* path '.../' is legal */ + node = prom_getchild(node); + + for (d = nbuf; *s != 0 && *s != '@' && *s != '/';) + *d++ = *s++; + *d = 0; + + node = prom_searchsiblings(node, nbuf); + if (!node) + return 0; + + if (*s == '@') { + if (isxdigit(s[1]) && s[2] == ',') { + which_io = simple_strtoul(s+1, NULL, 16); + phys_addr = simple_strtoul(s+3, &d, 16); + if (d != s + 3 && (!*d || *d == '/') + && d <= s + 3 + 8) { + node2 = node; + while (node2 && node2 != -1) { + if (prom_getproperty (node2, "reg", (char *)reg, sizeof (reg)) > 0) { + if (which_io == reg[0].which_io && phys_addr == reg[0].phys_addr) { + node = node2; + break; + } + } + node2 = prom_getsibling(node2); + if (!node2 || node2 == -1) + break; + node2 = prom_searchsiblings(prom_getsibling(node2), nbuf); + } + } + } + while (*s != 0 && *s != '/') s++; + } + } + return node; +} + +int prom_node_has_property(int node, char *prop) +{ + char *current_property = ""; + + do { + current_property = prom_nextprop(node, current_property, NULL); + if(!strcmp(current_property, prop)) + return 1; + } while (*current_property); + return 0; +} + +/* Set property 'pname' at node 'node' to value 'value' which has a length + * of 'size' bytes. Return the number of bytes the prom accepted. + */ +int prom_setprop(int node, char *pname, char *value, int size) +{ + unsigned long flags; + int ret; + + if(size == 0) return 0; + if((pname == 0) || (value == 0)) return 0; + spin_lock_irqsave(&prom_lock, flags); + ret = prom_nodeops->no_setprop(node, pname, value, size); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + return ret; +} + +int prom_inst2pkg(int inst) +{ + int node; + unsigned long flags; + + spin_lock_irqsave(&prom_lock, flags); + node = (*romvec->pv_v2devops.v2_inst2pkg)(inst); + restore_current(); + spin_unlock_irqrestore(&prom_lock, flags); + if (node == -1) return 0; + return node; +} + +/* Return 'node' assigned to a particular prom 'path' + * FIXME: Should work for v0 as well + */ +int prom_pathtoinode(char *path) +{ + int node, inst; + + inst = prom_devopen (path); + if (inst == -1) return 0; + node = prom_inst2pkg (inst); + prom_devclose (inst); + if (node == -1) return 0; + return node; +} |