summaryrefslogtreecommitdiffstats
path: root/arch/cris
diff options
context:
space:
mode:
authorMikael Starvik <mikael.starvik@axis.com>2005-07-27 11:44:44 -0700
committerLinus Torvalds <torvalds@g5.osdl.org>2005-07-27 16:26:01 -0700
commit51533b615e605d86154ec1b4e585c8ca1b0b15b7 (patch)
tree4a6d7d8494d2017632d83624fb71b36031e0e7e5 /arch/cris
parent5d01e6ce785884a5db5792cd2e5bb36fa82fe23c (diff)
downloadblackbird-op-linux-51533b615e605d86154ec1b4e585c8ca1b0b15b7.tar.gz
blackbird-op-linux-51533b615e605d86154ec1b4e585c8ca1b0b15b7.zip
[PATCH] CRIS update: new subarchitecture v32
New CRIS sub architecture named v32. From: Dave Jones <davej@redhat.com> Fix swapped kmalloc args Signed-off-by: Mikael Starvik <starvik@axis.com> Signed-off-by: Dave Jones <davej@redhat.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'arch/cris')
-rw-r--r--arch/cris/arch-v32/Kconfig296
-rw-r--r--arch/cris/arch-v32/boot/Makefile14
-rw-r--r--arch/cris/arch-v32/boot/compressed/Makefile41
-rw-r--r--arch/cris/arch-v32/boot/compressed/README25
-rw-r--r--arch/cris/arch-v32/boot/compressed/decompress.ld30
-rw-r--r--arch/cris/arch-v32/boot/compressed/head.S193
-rw-r--r--arch/cris/arch-v32/boot/compressed/misc.c318
-rw-r--r--arch/cris/arch-v32/boot/rescue/Makefile36
-rw-r--r--arch/cris/arch-v32/boot/rescue/head.S39
-rw-r--r--arch/cris/arch-v32/boot/rescue/rescue.ld20
-rw-r--r--arch/cris/arch-v32/drivers/Kconfig625
-rw-r--r--arch/cris/arch-v32/drivers/Makefile13
-rw-r--r--arch/cris/arch-v32/drivers/axisflashmap.c455
-rw-r--r--arch/cris/arch-v32/drivers/cryptocop.c3522
-rw-r--r--arch/cris/arch-v32/drivers/gpio.c766
-rw-r--r--arch/cris/arch-v32/drivers/i2c.c611
-rw-r--r--arch/cris/arch-v32/drivers/i2c.h15
-rw-r--r--arch/cris/arch-v32/drivers/iop_fw_load.c219
-rw-r--r--arch/cris/arch-v32/drivers/nandflash.c157
-rw-r--r--arch/cris/arch-v32/drivers/pcf8563.c341
-rw-r--r--arch/cris/arch-v32/drivers/pci/Makefile5
-rw-r--r--arch/cris/arch-v32/drivers/pci/bios.c131
-rw-r--r--arch/cris/arch-v32/drivers/pci/dma.c149
-rw-r--r--arch/cris/arch-v32/drivers/sync_serial.c1283
-rw-r--r--arch/cris/arch-v32/kernel/Makefile21
-rw-r--r--arch/cris/arch-v32/kernel/arbiter.c297
-rw-r--r--arch/cris/arch-v32/kernel/asm-offsets.c49
-rw-r--r--arch/cris/arch-v32/kernel/crisksyms.c24
-rw-r--r--arch/cris/arch-v32/kernel/debugport.c461
-rw-r--r--arch/cris/arch-v32/kernel/dma.c224
-rw-r--r--arch/cris/arch-v32/kernel/entry.S820
-rw-r--r--arch/cris/arch-v32/kernel/fasttimer.c996
-rw-r--r--arch/cris/arch-v32/kernel/head.S448
-rw-r--r--arch/cris/arch-v32/kernel/io.c154
-rw-r--r--arch/cris/arch-v32/kernel/irq.c413
-rw-r--r--arch/cris/arch-v32/kernel/kgdb.c1660
-rw-r--r--arch/cris/arch-v32/kernel/kgdb_asm.S552
-rw-r--r--arch/cris/arch-v32/kernel/pinmux.c229
-rw-r--r--arch/cris/arch-v32/kernel/process.c270
-rw-r--r--arch/cris/arch-v32/kernel/ptrace.c597
-rw-r--r--arch/cris/arch-v32/kernel/setup.c118
-rw-r--r--arch/cris/arch-v32/kernel/signal.c708
-rw-r--r--arch/cris/arch-v32/kernel/smp.c348
-rw-r--r--arch/cris/arch-v32/kernel/time.c341
-rw-r--r--arch/cris/arch-v32/kernel/traps.c160
-rw-r--r--arch/cris/arch-v32/kernel/vcs_hook.c96
-rw-r--r--arch/cris/arch-v32/kernel/vcs_hook.h42
-rw-r--r--arch/cris/arch-v32/lib/Makefile6
-rw-r--r--arch/cris/arch-v32/lib/checksum.S111
-rw-r--r--arch/cris/arch-v32/lib/checksumcopy.S120
-rw-r--r--arch/cris/arch-v32/lib/csumcpfruser.S69
-rw-r--r--arch/cris/arch-v32/lib/dram_init.S120
-rw-r--r--arch/cris/arch-v32/lib/hw_settings.S73
-rw-r--r--arch/cris/arch-v32/lib/memset.c253
-rw-r--r--arch/cris/arch-v32/lib/nand_init.S179
-rw-r--r--arch/cris/arch-v32/lib/spinlock.S33
-rw-r--r--arch/cris/arch-v32/lib/string.c219
-rw-r--r--arch/cris/arch-v32/lib/usercopy.c470
-rw-r--r--arch/cris/arch-v32/mm/Makefile3
-rw-r--r--arch/cris/arch-v32/mm/init.c174
-rw-r--r--arch/cris/arch-v32/mm/intmem.c139
-rw-r--r--arch/cris/arch-v32/mm/mmu.S141
-rw-r--r--arch/cris/arch-v32/mm/tlb.c208
-rw-r--r--arch/cris/arch-v32/output_arch.ld2
-rw-r--r--arch/cris/arch-v32/vmlinux.lds.S134
65 files changed, 20786 insertions, 0 deletions
diff --git a/arch/cris/arch-v32/Kconfig b/arch/cris/arch-v32/Kconfig
new file mode 100644
index 000000000000..22f0ddc04c50
--- /dev/null
+++ b/arch/cris/arch-v32/Kconfig
@@ -0,0 +1,296 @@
+config ETRAX_DRAM_VIRTUAL_BASE
+ hex
+ depends on ETRAX_ARCH_V32
+ default "c0000000"
+
+config ETRAX_LED1G
+ string "First green LED bit"
+ depends on ETRAX_ARCH_V32
+ default "PA3"
+ help
+ Bit to use for the first green LED (network LED).
+ Most Axis products use bit A3 here.
+
+config ETRAX_LED1R
+ string "First red LED bit"
+ depends on ETRAX_ARCH_V32
+ default "PA4"
+ help
+ Bit to use for the first red LED (network LED).
+ Most Axis products use bit A4 here.
+
+config ETRAX_LED2G
+ string "Second green LED bit"
+ depends on ETRAX_ARCH_V32
+ default "PA5"
+ help
+ Bit to use for the first green LED (status LED).
+ Most Axis products use bit A5 here.
+
+config ETRAX_LED2R
+ string "Second red LED bit"
+ depends on ETRAX_ARCH_V32
+ default "PA6"
+ help
+ Bit to use for the first red LED (network LED).
+ Most Axis products use bit A6 here.
+
+config ETRAX_LED3G
+ string "Third green LED bit"
+ depends on ETRAX_ARCH_V32
+ default "PA7"
+ help
+ Bit to use for the first green LED (drive/power LED).
+ Most Axis products use bit A7 here.
+
+config ETRAX_LED3R
+ string "Third red LED bit"
+ depends on ETRAX_ARCH_V32
+ default "PA7"
+ help
+ Bit to use for the first red LED (drive/power LED).
+ Most Axis products use bit A7 here.
+
+choice
+ prompt "Product debug-port"
+ depends on ETRAX_ARCH_V32
+ default ETRAX_DEBUG_PORT0
+
+config ETRAX_DEBUG_PORT0
+ bool "Serial-0"
+ help
+ Choose a serial port for the ETRAX debug console. Default to
+ port 0.
+
+config ETRAX_DEBUG_PORT1
+ bool "Serial-1"
+ help
+ Use serial port 1 for the console.
+
+config ETRAX_DEBUG_PORT2
+ bool "Serial-2"
+ help
+ Use serial port 2 for the console.
+
+config ETRAX_DEBUG_PORT3
+ bool "Serial-3"
+ help
+ Use serial port 3 for the console.
+
+config ETRAX_DEBUG_PORT_NULL
+ bool "disabled"
+ help
+ Disable serial-port debugging.
+
+endchoice
+
+choice
+ prompt "Kernel GDB port"
+ depends on ETRAX_KGDB
+ default ETRAX_KGDB_PORT0
+ help
+ Choose a serial port for kernel debugging. NOTE: This port should
+ not be enabled under Drivers for built-in interfaces (as it has its
+ own initialization code) and should not be the same as the debug port.
+
+config ETRAX_KGDB_PORT0
+ bool "Serial-0"
+ help
+ Use serial port 0 for kernel debugging.
+
+config ETRAX_KGDB_PORT1
+ bool "Serial-1"
+ help
+ Use serial port 1 for kernel debugging.
+
+config ETRAX_KGDB_PORT2
+ bool "Serial-2"
+ help
+ Use serial port 2 for kernel debugging.
+
+config ETRAX_KGDB_PORT3
+ bool "Serial-3"
+ help
+ Use serial port 3 for kernel debugging.
+
+endchoice
+
+config ETRAX_MEM_GRP1_CONFIG
+ hex "MEM_GRP1_CONFIG"
+ depends on ETRAX_ARCH_V32
+ default "4044a"
+ help
+ Waitstates for flash. The default value is suitable for the
+ standard flashes used in axis products (120 ns).
+
+config ETRAX_MEM_GRP2_CONFIG
+ hex "MEM_GRP2_CONFIG"
+ depends on ETRAX_ARCH_V32
+ default "0"
+ help
+ Waitstates for SRAM. 0 is a good choice for most Axis products.
+
+config ETRAX_MEM_GRP3_CONFIG
+ hex "MEM_GRP3_CONFIG"
+ depends on ETRAX_ARCH_V32
+ default "0"
+ help
+ Waitstates for CSP0-3. 0 is a good choice for most Axis products.
+ It may need to be changed if external devices such as extra
+ register-mapped LEDs are used.
+
+config ETRAX_MEM_GRP4_CONFIG
+ hex "MEM_GRP4_CONFIG"
+ depends on ETRAX_ARCH_V32
+ default "0"
+ help
+ Waitstates for CSP4-6. 0 is a good choice for most Axis products.
+
+config ETRAX_SDRAM_GRP0_CONFIG
+ hex "SDRAM_GRP0_CONFIG"
+ depends on ETRAX_ARCH_V32
+ default "336"
+ help
+ SDRAM configuration for group 0. The value depends on the
+ hardware configuration. The default value is suitable
+ for 32 MB organized as two 16 bits chips (e.g. Axis
+ part number 18550) connected as one 32 bit device (i.e. in
+ the same group).
+
+config ETRAX_SDRAM_GRP1_CONFIG
+ hex "SDRAM_GRP1_CONFIG"
+ depends on ETRAX_ARCH_V32
+ default "0"
+ help
+ SDRAM configuration for group 1. The defult value is 0
+ because group 1 is not used in the default configuration,
+ described in the help for SDRAM_GRP0_CONFIG.
+
+config ETRAX_SDRAM_TIMING
+ hex "SDRAM_TIMING"
+ depends on ETRAX_ARCH_V32
+ default "104a"
+ help
+ SDRAM timing parameters. The default value is ok for
+ most hardwares but large SDRAMs may require a faster
+ refresh (a.k.a 8K refresh). The default value implies
+ 100MHz clock and SDR mode.
+
+config ETRAX_SDRAM_COMMAND
+ hex "SDRAM_COMMAND"
+ depends on ETRAX_ARCH_V32
+ default "0"
+ help
+ SDRAM command. Should be 0 unless you really know what
+ you are doing (may be != 0 for unusual address line
+ mappings such as in a MCM)..
+
+config ETRAX_DEF_GIO_PA_OE
+ hex "GIO_PA_OE"
+ depends on ETRAX_ARCH_V32
+ default "1c"
+ help
+ Configures the direction of general port A bits. 1 is out, 0 is in.
+ This is often totally different depending on the product used.
+ There are some guidelines though - if you know that only LED's are
+ connected to port PA, then they are usually connected to bits 2-4
+ and you can therefore use 1c. On other boards which don't have the
+ LED's at the general ports, these bits are used for all kinds of
+ stuff. If you don't know what to use, it is always safe to put all
+ as inputs, although floating inputs isn't good.
+
+config ETRAX_DEF_GIO_PA_OUT
+ hex "GIO_PA_OUT"
+ depends on ETRAX_ARCH_V32
+ default "00"
+ help
+ Configures the initial data for the general port A bits. Most
+ products should use 00 here.
+
+config ETRAX_DEF_GIO_PB_OE
+ hex "GIO_PB_OE"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the direction of general port B bits. 1 is out, 0 is in.
+ This is often totally different depending on the product used.
+ There are some guidelines though - if you know that only LED's are
+ connected to port PA, then they are usually connected to bits 2-4
+ and you can therefore use 1c. On other boards which don't have the
+ LED's at the general ports, these bits are used for all kinds of
+ stuff. If you don't know what to use, it is always safe to put all
+ as inputs, although floating inputs isn't good.
+
+config ETRAX_DEF_GIO_PB_OUT
+ hex "GIO_PB_OUT"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the initial data for the general port B bits. Most
+ products should use 00000 here.
+
+config ETRAX_DEF_GIO_PC_OE
+ hex "GIO_PC_OE"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the direction of general port C bits. 1 is out, 0 is in.
+ This is often totally different depending on the product used.
+ There are some guidelines though - if you know that only LED's are
+ connected to port PA, then they are usually connected to bits 2-4
+ and you can therefore use 1c. On other boards which don't have the
+ LED's at the general ports, these bits are used for all kinds of
+ stuff. If you don't know what to use, it is always safe to put all
+ as inputs, although floating inputs isn't good.
+
+config ETRAX_DEF_GIO_PC_OUT
+ hex "GIO_PC_OUT"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the initial data for the general port C bits. Most
+ products should use 00000 here.
+
+config ETRAX_DEF_GIO_PD_OE
+ hex "GIO_PD_OE"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the direction of general port D bits. 1 is out, 0 is in.
+ This is often totally different depending on the product used.
+ There are some guidelines though - if you know that only LED's are
+ connected to port PA, then they are usually connected to bits 2-4
+ and you can therefore use 1c. On other boards which don't have the
+ LED's at the general ports, these bits are used for all kinds of
+ stuff. If you don't know what to use, it is always safe to put all
+ as inputs, although floating inputs isn't good.
+
+config ETRAX_DEF_GIO_PD_OUT
+ hex "GIO_PD_OUT"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the initial data for the general port D bits. Most
+ products should use 00000 here.
+
+config ETRAX_DEF_GIO_PE_OE
+ hex "GIO_PE_OE"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the direction of general port E bits. 1 is out, 0 is in.
+ This is often totally different depending on the product used.
+ There are some guidelines though - if you know that only LED's are
+ connected to port PA, then they are usually connected to bits 2-4
+ and you can therefore use 1c. On other boards which don't have the
+ LED's at the general ports, these bits are used for all kinds of
+ stuff. If you don't know what to use, it is always safe to put all
+ as inputs, although floating inputs isn't good.
+
+config ETRAX_DEF_GIO_PE_OUT
+ hex "GIO_PE_OUT"
+ depends on ETRAX_ARCH_V32
+ default "00000"
+ help
+ Configures the initial data for the general port E bits. Most
+ products should use 00000 here.
diff --git a/arch/cris/arch-v32/boot/Makefile b/arch/cris/arch-v32/boot/Makefile
new file mode 100644
index 000000000000..26f293ab9617
--- /dev/null
+++ b/arch/cris/arch-v32/boot/Makefile
@@ -0,0 +1,14 @@
+#
+# arch/cris/arch-v32/boot/Makefile
+#
+target = $(target_boot_dir)
+src = $(src_boot_dir)
+
+zImage: compressed/vmlinuz
+
+compressed/vmlinuz: $(objtree)/vmlinux
+ @$(MAKE) -f $(src)/compressed/Makefile $(objtree)/vmlinuz
+
+clean:
+ rm -f zImage tools/build compressed/vmlinux.out
+ @$(MAKE) -f $(src)/compressed/Makefile clean
diff --git a/arch/cris/arch-v32/boot/compressed/Makefile b/arch/cris/arch-v32/boot/compressed/Makefile
new file mode 100644
index 000000000000..9f77eda914ba
--- /dev/null
+++ b/arch/cris/arch-v32/boot/compressed/Makefile
@@ -0,0 +1,41 @@
+#
+# lx25/arch/cris/arch-v32/boot/compressed/Makefile
+#
+# create a compressed vmlinux image from the original vmlinux files and romfs
+#
+
+target = $(target_compressed_dir)
+src = $(src_compressed_dir)
+
+CC = gcc-cris -mlinux -march=v32 -I $(TOPDIR)/include
+CFLAGS = -O2
+LD = gcc-cris -mlinux -march=v32 -nostdlib
+OBJCOPY = objcopy-cris
+OBJCOPYFLAGS = -O binary --remove-section=.bss
+OBJECTS = $(target)/head.o $(target)/misc.o
+
+# files to compress
+SYSTEM = $(objtree)/vmlinux.bin
+
+all: vmlinuz
+
+$(target)/decompress.bin: $(OBJECTS)
+ $(LD) -T $(src)/decompress.ld -o $(target)/decompress.o $(OBJECTS)
+ $(OBJCOPY) $(OBJCOPYFLAGS) $(target)/decompress.o $(target)/decompress.bin
+
+$(objtree)/vmlinuz: $(target) piggy.img $(target)/decompress.bin
+ cat $(target)/decompress.bin piggy.img > $(objtree)/vmlinuz
+ rm -f piggy.img
+ cp $(objtree)/vmlinuz $(src)
+
+$(target)/head.o: $(src)/head.S
+ $(CC) -D__ASSEMBLY__ -c $< -o $@
+
+# gzip the kernel image
+
+piggy.img: $(SYSTEM)
+ cat $(SYSTEM) | gzip -f -9 > piggy.img
+
+clean:
+ rm -f piggy.img $(objtree)/vmlinuz vmlinuz.o decompress.o decompress.bin $(OBJECTS)
+
diff --git a/arch/cris/arch-v32/boot/compressed/README b/arch/cris/arch-v32/boot/compressed/README
new file mode 100644
index 000000000000..e33691d15c57
--- /dev/null
+++ b/arch/cris/arch-v32/boot/compressed/README
@@ -0,0 +1,25 @@
+Creation of the self-extracting compressed kernel image (vmlinuz)
+-----------------------------------------------------------------
+$Id: README,v 1.1 2003/08/21 09:37:03 johana Exp $
+
+This can be slightly confusing because it's a process with many steps.
+
+The kernel object built by the arch/etrax100/Makefile, vmlinux, is split
+by that makefile into text and data binary files, vmlinux.text and
+vmlinux.data.
+
+Those files together with a ROM filesystem can be catted together and
+burned into a flash or executed directly at the DRAM origin.
+
+They can also be catted together and compressed with gzip, which is what
+happens in this makefile. Together they make up piggy.img.
+
+The decompressor is built into the file decompress.o. It is turned into
+the binary file decompress.bin, which is catted together with piggy.img
+into the file vmlinuz. It can be executed in an arbitrary place in flash.
+
+Be careful - it assumes some things about free locations in DRAM. It
+assumes the DRAM starts at 0x40000000 and that it is at least 8 MB,
+so it puts its code at 0x40700000, and initial stack at 0x40800000.
+
+-Bjorn
diff --git a/arch/cris/arch-v32/boot/compressed/decompress.ld b/arch/cris/arch-v32/boot/compressed/decompress.ld
new file mode 100644
index 000000000000..3c837feca3ac
--- /dev/null
+++ b/arch/cris/arch-v32/boot/compressed/decompress.ld
@@ -0,0 +1,30 @@
+/*#OUTPUT_FORMAT(elf32-us-cris) */
+OUTPUT_ARCH (crisv32)
+
+MEMORY
+ {
+ dram : ORIGIN = 0x40700000,
+ LENGTH = 0x00100000
+ }
+
+SECTIONS
+{
+ .text :
+ {
+ _stext = . ;
+ *(.text)
+ *(.rodata)
+ *(.rodata.*)
+ _etext = . ;
+ } > dram
+ .data :
+ {
+ *(.data)
+ _edata = . ;
+ } > dram
+ .bss :
+ {
+ *(.bss)
+ _end = ALIGN( 0x10 ) ;
+ } > dram
+}
diff --git a/arch/cris/arch-v32/boot/compressed/head.S b/arch/cris/arch-v32/boot/compressed/head.S
new file mode 100644
index 000000000000..0c55b83b8287
--- /dev/null
+++ b/arch/cris/arch-v32/boot/compressed/head.S
@@ -0,0 +1,193 @@
+/*
+ * Code that sets up the DRAM registers, calls the
+ * decompressor to unpack the piggybacked kernel, and jumps.
+ *
+ * Copyright (C) 1999 - 2003, Axis Communications AB
+ */
+
+#include <linux/config.h>
+#define ASSEMBLER_MACROS_ONLY
+#include <asm/arch/hwregs/asm/reg_map_asm.h>
+#include <asm/arch/hwregs/asm/gio_defs_asm.h>
+#include <asm/arch/hwregs/asm/config_defs_asm.h>
+
+#define RAM_INIT_MAGIC 0x56902387
+#define COMMAND_LINE_MAGIC 0x87109563
+
+ ;; Exported symbols
+
+ .globl input_data
+
+ .text
+start:
+ di
+
+ ;; Start clocks for used blocks.
+ move.d REG_ADDR(config, regi_config, rw_clk_ctrl), $r1
+ move.d [$r1], $r0
+ or.d REG_STATE(config, rw_clk_ctrl, cpu, yes) | \
+ REG_STATE(config, rw_clk_ctrl, bif, yes) | \
+ REG_STATE(config, rw_clk_ctrl, fix_io, yes), $r0
+ move.d $r0, [$r1]
+
+ ;; If booting from NAND flash we first have to copy some
+ ;; data from NAND flash to internal RAM to get the code
+ ;; that initializes the SDRAM. Lets copy 20 KB. This
+ ;; code executes at 0x38010000 if booting from NAND and
+ ;; we are guaranted that at least 0x200 bytes are good so
+ ;; lets start from there. The first 8192 bytes in the nand
+ ;; flash is spliced with zeroes and is thus 16384 bytes.
+ move.d 0x38010200, $r10
+ move.d 0x14200, $r11 ; Start offset in NAND flash 0x10200 + 16384
+ move.d 0x5000, $r12 ; Length of copy
+
+ ;; Before this code the tools add a partitiontable so the PC
+ ;; has an offset from the linked address.
+offset1:
+ lapcq ., $r13 ; get PC
+ add.d first_copy_complete-offset1, $r13
+
+#include "../../lib/nand_init.S"
+
+first_copy_complete:
+ ;; Initialze the DRAM registers.
+ cmp.d RAM_INIT_MAGIC, $r8 ; Already initialized?
+ beq dram_init_finished
+ nop
+
+#include "../../lib/dram_init.S"
+
+dram_init_finished:
+ lapcq ., $r13 ; get PC
+ add.d second_copy_complete-dram_init_finished, $r13
+
+ move.d REG_ADDR(config, regi_config, r_bootsel), $r0
+ move.d [$r0], $r0
+ and.d REG_MASK(config, r_bootsel, boot_mode), $r0
+ cmp.d REG_STATE(config, r_bootsel, boot_mode, nand), $r0
+ bne second_copy_complete ; No NAND boot
+ nop
+
+ ;; Copy 2MB from NAND flash to SDRAM (at 2-4MB into the SDRAM)
+ move.d 0x40204000, $r10
+ move.d 0x8000, $r11
+ move.d 0x200000, $r12
+ ba copy_nand_to_ram
+ nop
+second_copy_complete:
+
+ ;; Initiate the PA port.
+ move.d CONFIG_ETRAX_DEF_GIO_PA_OUT, $r0
+ move.d REG_ADDR(gio, regi_gio, rw_pa_dout), $r1
+ move.d $r0, [$r1]
+
+ move.d CONFIG_ETRAX_DEF_GIO_PA_OE, $r0
+ move.d REG_ADDR(gio, regi_gio, rw_pa_oe), $r1
+ move.d $r0, [$r1]
+
+ ;; Setup the stack to a suitably high address.
+ ;; We assume 8 MB is the minimum DRAM and put
+ ;; the SP at the top for now.
+
+ move.d 0x40800000, $sp
+
+ ;; Figure out where the compressed piggyback image is
+ ;; in the flash (since we wont try to copy it to DRAM
+ ;; before unpacking). It is at _edata, but in flash.
+ ;; Use (_edata - herami) as offset to the current PC.
+
+ move.d REG_ADDR(config, regi_config, r_bootsel), $r0
+ move.d [$r0], $r0
+ and.d REG_MASK(config, r_bootsel, boot_mode), $r0
+ cmp.d REG_STATE(config, r_bootsel, boot_mode, nand), $r0
+ beq hereami2
+ nop
+hereami:
+ lapcq ., $r5 ; get PC
+ and.d 0x7fffffff, $r5 ; strip any non-cache bit
+ move.d $r5, $r0 ; save for later - flash address of 'herami'
+ add.d _edata, $r5
+ sub.d hereami, $r5 ; r5 = flash address of '_edata'
+ move.d hereami, $r1 ; destination
+ ba 2f
+ nop
+hereami2:
+ lapcq ., $r5 ; get PC
+ and.d 0x00ffffff, $r5 ; strip any non-cache bit
+ move.d $r5, $r6
+ or.d 0x40200000, $r6
+ move.d $r6, $r0 ; save for later - flash address of 'herami'
+ add.d _edata, $r5
+ sub.d hereami2, $r5 ; r5 = flash address of '_edata'
+ add.d 0x40200000, $r5
+ move.d hereami2, $r1 ; destination
+2:
+ ;; Copy text+data to DRAM
+
+ move.d _edata, $r2 ; end destination
+1: move.w [$r0+], $r3
+ move.w $r3, [$r1+]
+ cmp.d $r2, $r1
+ bcs 1b
+ nop
+
+ move.d input_data, $r0 ; for the decompressor
+ move.d $r5, [$r0] ; for the decompressor
+
+ ;; Clear the decompressors BSS (between _edata and _end)
+
+ moveq 0, $r0
+ move.d _edata, $r1
+ move.d _end, $r2
+1: move.w $r0, [$r1+]
+ cmp.d $r2, $r1
+ bcs 1b
+ nop
+
+ ;; Save command line magic and address.
+ move.d _cmd_line_magic, $r12
+ move.d $r10, [$r12]
+ move.d _cmd_line_addr, $r12
+ move.d $r11, [$r12]
+
+ ;; Do the decompression and save compressed size in _inptr
+
+ jsr decompress_kernel
+ nop
+
+ ;; Restore command line magic and address.
+ move.d _cmd_line_magic, $r10
+ move.d [$r10], $r10
+ move.d _cmd_line_addr, $r11
+ move.d [$r11], $r11
+
+ ;; Put start address of root partition in r9 so the kernel can use it
+ ;; when mounting from flash
+ move.d input_data, $r0
+ move.d [$r0], $r9 ; flash address of compressed kernel
+ move.d inptr, $r0
+ add.d [$r0], $r9 ; size of compressed kernel
+ cmp.d 0x40200000, $r9
+ blo enter_kernel
+ nop
+ sub.d 0x40200000, $r9
+ add.d 0x4000, $r9
+
+enter_kernel:
+ ;; Enter the decompressed kernel
+ move.d RAM_INIT_MAGIC, $r8 ; Tell kernel that DRAM is initialized
+ jump 0x40004000 ; kernel is linked to this address
+ nop
+
+ .data
+
+input_data:
+ .dword 0 ; used by the decompressor
+_cmd_line_magic:
+ .dword 0
+_cmd_line_addr:
+ .dword 0
+is_nand_boot:
+ .dword 0
+
+#include "../../lib/hw_settings.S"
diff --git a/arch/cris/arch-v32/boot/compressed/misc.c b/arch/cris/arch-v32/boot/compressed/misc.c
new file mode 100644
index 000000000000..54644238ed59
--- /dev/null
+++ b/arch/cris/arch-v32/boot/compressed/misc.c
@@ -0,0 +1,318 @@
+/*
+ * misc.c
+ *
+ * $Id: misc.c,v 1.8 2005/04/24 18:34:29 starvik Exp $
+ *
+ * This is a collection of several routines from gzip-1.0.3
+ * adapted for Linux.
+ *
+ * malloc by Hannu Savolainen 1993 and Matthias Urlichs 1994
+ * puts by Nick Holloway 1993, better puts by Martin Mares 1995
+ * adoptation for Linux/CRIS Axis Communications AB, 1999
+ *
+ */
+
+/* where the piggybacked kernel image expects itself to live.
+ * it is the same address we use when we network load an uncompressed
+ * image into DRAM, and it is the address the kernel is linked to live
+ * at by vmlinux.lds.S
+ */
+
+#define KERNEL_LOAD_ADR 0x40004000
+
+#include <linux/config.h>
+
+#include <linux/types.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/ser_defs.h>
+
+/*
+ * gzip declarations
+ */
+
+#define OF(args) args
+#define STATIC static
+
+void* memset(void* s, int c, size_t n);
+void* memcpy(void* __dest, __const void* __src,
+ size_t __n);
+
+#define memzero(s, n) memset ((s), 0, (n))
+
+
+typedef unsigned char uch;
+typedef unsigned short ush;
+typedef unsigned long ulg;
+
+#define WSIZE 0x8000 /* Window size must be at least 32k, */
+ /* and a power of two */
+
+static uch *inbuf; /* input buffer */
+static uch window[WSIZE]; /* Sliding window buffer */
+
+unsigned inptr = 0; /* index of next byte to be processed in inbuf
+ * After decompression it will contain the
+ * compressed size, and head.S will read it.
+ */
+
+static unsigned outcnt = 0; /* bytes in output buffer */
+
+/* gzip flag byte */
+#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
+#define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */
+#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
+#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
+#define COMMENT 0x10 /* bit 4 set: file comment present */
+#define ENCRYPTED 0x20 /* bit 5 set: file is encrypted */
+#define RESERVED 0xC0 /* bit 6,7: reserved */
+
+#define get_byte() inbuf[inptr++]
+
+/* Diagnostic functions */
+#ifdef DEBUG
+# define Assert(cond,msg) {if(!(cond)) error(msg);}
+# define Trace(x) fprintf x
+# define Tracev(x) {if (verbose) fprintf x ;}
+# define Tracevv(x) {if (verbose>1) fprintf x ;}
+# define Tracec(c,x) {if (verbose && (c)) fprintf x ;}
+# define Tracecv(c,x) {if (verbose>1 && (c)) fprintf x ;}
+#else
+# define Assert(cond,msg)
+# define Trace(x)
+# define Tracev(x)
+# define Tracevv(x)
+# define Tracec(c,x)
+# define Tracecv(c,x)
+#endif
+
+static int fill_inbuf(void);
+static void flush_window(void);
+static void error(char *m);
+static void gzip_mark(void **);
+static void gzip_release(void **);
+
+extern char *input_data; /* lives in head.S */
+
+static long bytes_out = 0;
+static uch *output_data;
+static unsigned long output_ptr = 0;
+
+static void *malloc(int size);
+static void free(void *where);
+static void error(char *m);
+static void gzip_mark(void **);
+static void gzip_release(void **);
+
+static void puts(const char *);
+
+/* the "heap" is put directly after the BSS ends, at end */
+
+extern int _end;
+static long free_mem_ptr = (long)&_end;
+
+#include "../../../../../lib/inflate.c"
+
+static void *malloc(int size)
+{
+ void *p;
+
+ if (size <0) error("Malloc error");
+
+ free_mem_ptr = (free_mem_ptr + 3) & ~3; /* Align */
+
+ p = (void *)free_mem_ptr;
+ free_mem_ptr += size;
+
+ return p;
+}
+
+static void free(void *where)
+{ /* Don't care */
+}
+
+static void gzip_mark(void **ptr)
+{
+ *ptr = (void *) free_mem_ptr;
+}
+
+static void gzip_release(void **ptr)
+{
+ free_mem_ptr = (long) *ptr;
+}
+
+/* decompressor info and error messages to serial console */
+
+static inline void
+serout(const char *s, reg_scope_instances regi_ser)
+{
+ reg_ser_rs_stat_din rs;
+ reg_ser_rw_dout dout = {.data = *s};
+
+ do {
+ rs = REG_RD(ser, regi_ser, rs_stat_din);
+ }
+ while (!rs.tr_rdy);/* Wait for tranceiver. */
+
+ REG_WR(ser, regi_ser, rw_dout, dout);
+}
+
+static void
+puts(const char *s)
+{
+#ifndef CONFIG_ETRAX_DEBUG_PORT_NULL
+ while (*s) {
+#ifdef CONFIG_ETRAX_DEBUG_PORT0
+ serout(s, regi_ser0);
+#endif
+#ifdef CONFIG_ETRAX_DEBUG_PORT1
+ serout(s, regi_ser1);
+#endif
+#ifdef CONFIG_ETRAX_DEBUG_PORT2
+ serout(s, regi_ser2);
+#endif
+#ifdef CONFIG_ETRAX_DEBUG_PORT3
+ serout(s, regi_ser3);
+#endif
+ *s++;
+ }
+/* CONFIG_ETRAX_DEBUG_PORT_NULL */
+#endif
+}
+
+void*
+memset(void* s, int c, size_t n)
+{
+ int i;
+ char *ss = (char*)s;
+
+ for (i=0;i<n;i++) ss[i] = c;
+}
+
+void*
+memcpy(void* __dest, __const void* __src,
+ size_t __n)
+{
+ int i;
+ char *d = (char *)__dest, *s = (char *)__src;
+
+ for (i=0;i<__n;i++) d[i] = s[i];
+}
+
+/* ===========================================================================
+ * Write the output window window[0..outcnt-1] and update crc and bytes_out.
+ * (Used for the decompressed data only.)
+ */
+
+static void
+flush_window()
+{
+ ulg c = crc; /* temporary variable */
+ unsigned n;
+ uch *in, *out, ch;
+
+ in = window;
+ out = &output_data[output_ptr];
+ for (n = 0; n < outcnt; n++) {
+ ch = *out++ = *in++;
+ c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8);
+ }
+ crc = c;
+ bytes_out += (ulg)outcnt;
+ output_ptr += (ulg)outcnt;
+ outcnt = 0;
+}
+
+static void
+error(char *x)
+{
+ puts("\n\n");
+ puts(x);
+ puts("\n\n -- System halted\n");
+
+ while(1); /* Halt */
+}
+
+void
+setup_normal_output_buffer()
+{
+ output_data = (char *)KERNEL_LOAD_ADR;
+}
+
+static inline void
+serial_setup(reg_scope_instances regi_ser)
+{
+ reg_ser_rw_xoff xoff;
+ reg_ser_rw_tr_ctrl tr_ctrl;
+ reg_ser_rw_rec_ctrl rec_ctrl;
+ reg_ser_rw_tr_baud_div tr_baud;
+ reg_ser_rw_rec_baud_div rec_baud;
+
+ /* Turn off XOFF. */
+ xoff = REG_RD(ser, regi_ser, rw_xoff);
+
+ xoff.chr = 0;
+ xoff.automatic = regk_ser_no;
+
+ REG_WR(ser, regi_ser, rw_xoff, xoff);
+
+ /* Set baudrate and stopbits. */
+ tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl);
+ rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl);
+ tr_baud = REG_RD(ser, regi_ser, rw_tr_baud_div);
+ rec_baud = REG_RD(ser, regi_ser, rw_rec_baud_div);
+
+ tr_ctrl.stop_bits = 1; /* 2 stop bits. */
+
+ /*
+ * The baudrate setup is a bit fishy, but in the end the tranceiver is
+ * set to 4800 and the receiver to 115200. The magic value is
+ * 29.493 MHz.
+ */
+ tr_ctrl.base_freq = regk_ser_f29_493;
+ rec_ctrl.base_freq = regk_ser_f29_493;
+ tr_baud.div = (29493000 / 8) / 4800;
+ rec_baud.div = (29493000 / 8) / 115200;
+
+ REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl);
+ REG_WR(ser, regi_ser, rw_tr_baud_div, tr_baud);
+ REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl);
+ REG_WR(ser, regi_ser, rw_rec_baud_div, rec_baud);
+}
+
+void
+decompress_kernel()
+{
+ char revision;
+
+ /* input_data is set in head.S */
+ inbuf = input_data;
+
+#ifdef CONFIG_ETRAX_DEBUG_PORT0
+ serial_setup(regi_ser0);
+#endif
+#ifdef CONFIG_ETRAX_DEBUG_PORT1
+ serial_setup(regi_ser1);
+#endif
+#ifdef CONFIG_ETRAX_DEBUG_PORT2
+ serial_setup(regi_ser2);
+#endif
+#ifdef CONFIG_ETRAX_DEBUG_PORT3
+ serial_setup(regi_ser3);
+#endif
+
+ setup_normal_output_buffer();
+
+ makecrc();
+
+ __asm__ volatile ("move $vr,%0" : "=rm" (revision));
+ if (revision < 32)
+ {
+ puts("You need an ETRAX FS to run Linux 2.6/crisv32.\n");
+ while(1);
+ }
+
+ puts("Uncompressing Linux...\n");
+ gunzip();
+ puts("Done. Now booting the kernel.\n");
+}
diff --git a/arch/cris/arch-v32/boot/rescue/Makefile b/arch/cris/arch-v32/boot/rescue/Makefile
new file mode 100644
index 000000000000..f668a8198724
--- /dev/null
+++ b/arch/cris/arch-v32/boot/rescue/Makefile
@@ -0,0 +1,36 @@
+#
+# Makefile for rescue code
+#
+target = $(target_rescue_dir)
+src = $(src_rescue_dir)
+
+CC = gcc-cris -mlinux -march=v32 $(LINUXINCLUDE)
+CFLAGS = -O2
+LD = gcc-cris -mlinux -march=v32 -nostdlib
+OBJCOPY = objcopy-cris
+OBJCOPYFLAGS = -O binary --remove-section=.bss
+
+all: $(target)/rescue.bin
+
+rescue: rescue.bin
+ # do nothing
+
+$(target)/rescue.bin: $(target) $(target)/head.o
+ $(LD) -T $(src)/rescue.ld -o $(target)/rescue.o $(target)/head.o
+ $(OBJCOPY) $(OBJCOPYFLAGS) $(target)/rescue.o $(target)/rescue.bin
+ cp -p $(target)/rescue.bin $(objtree)
+
+$(target):
+ mkdir -p $(target)
+
+$(target)/head.o: $(src)/head.S
+ $(CC) -D__ASSEMBLY__ -c $< -o $*.o
+
+clean:
+ rm -f $(target)/*.o $(target)/*.bin
+
+fastdep:
+
+modules:
+
+modules-install:
diff --git a/arch/cris/arch-v32/boot/rescue/head.S b/arch/cris/arch-v32/boot/rescue/head.S
new file mode 100644
index 000000000000..61ede5f30f99
--- /dev/null
+++ b/arch/cris/arch-v32/boot/rescue/head.S
@@ -0,0 +1,39 @@
+/* $Id: head.S,v 1.4 2004/11/01 16:10:28 starvik Exp $
+ *
+ * This used to be the rescue code but now that is handled by the
+ * RedBoot based RFL instead. Nothing to see here, move along.
+ */
+
+#include <linux/config.h>
+#include <asm/arch/hwregs/reg_map_asm.h>
+#include <asm/arch/hwregs/config_defs_asm.h>
+
+ .text
+
+ ;; Start clocks for used blocks.
+ move.d REG_ADDR(config, regi_config, rw_clk_ctrl), $r1
+ move.d [$r1], $r0
+ or.d REG_STATE(config, rw_clk_ctrl, cpu, yes) | \
+ REG_STATE(config, rw_clk_ctrl, bif, yes) | \
+ REG_STATE(config, rw_clk_ctrl, fix_io, yes), $r0
+ move.d $r0, [$r1]
+
+ ;; Copy 68KB NAND flash to Internal RAM (if NAND boot)
+ move.d 0x38004000, $r10
+ move.d 0x8000, $r11
+ move.d 0x11000, $r12
+ move.d copy_complete, $r13
+ and.d 0x000fffff, $r13
+ or.d 0x38000000, $r13
+
+#include "../../lib/nand_init.S"
+
+ ;; No NAND found
+ move.d CONFIG_ETRAX_PTABLE_SECTOR, $r10
+ jump $r10 ; Jump to decompresser
+ nop
+
+copy_complete:
+ move.d 0x38000000 + CONFIG_ETRAX_PTABLE_SECTOR, $r10
+ jump $r10 ; Jump to decompresser
+ nop
diff --git a/arch/cris/arch-v32/boot/rescue/rescue.ld b/arch/cris/arch-v32/boot/rescue/rescue.ld
new file mode 100644
index 000000000000..42b11aa122b2
--- /dev/null
+++ b/arch/cris/arch-v32/boot/rescue/rescue.ld
@@ -0,0 +1,20 @@
+MEMORY
+ {
+ flash : ORIGIN = 0x00000000,
+ LENGTH = 0x00100000
+ }
+
+SECTIONS
+{
+ .text :
+ {
+ stext = . ;
+ *(.text)
+ etext = . ;
+ } > flash
+ .data :
+ {
+ *(.data)
+ edata = . ;
+ } > flash
+}
diff --git a/arch/cris/arch-v32/drivers/Kconfig b/arch/cris/arch-v32/drivers/Kconfig
new file mode 100644
index 000000000000..a33097f95362
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/Kconfig
@@ -0,0 +1,625 @@
+config ETRAX_ETHERNET
+ bool "Ethernet support"
+ depends on ETRAX_ARCH_V32
+ select NET_ETHERNET
+ help
+ This option enables the ETRAX FS built-in 10/100Mbit Ethernet
+ controller.
+
+config ETRAX_ETHERNET_HW_CSUM
+ bool "Hardware accelerated ethernet checksum and scatter/gather"
+ depends on ETRAX_ETHERNET
+ depends on ETRAX_STREAMCOPROC
+ default y
+ help
+ Hardware acceleration of checksumming and scatter/gather
+
+config ETRAX_ETHERNET_IFACE0
+ depends on ETRAX_ETHERNET
+ bool "Enable network interface 0"
+
+config ETRAX_ETHERNET_IFACE1
+ depends on ETRAX_ETHERNET
+ bool "Enable network interface 1 (uses DMA6 and DMA7)"
+
+choice
+ prompt "Network LED behavior"
+ depends on ETRAX_ETHERNET
+ default ETRAX_NETWORK_LED_ON_WHEN_ACTIVITY
+
+config ETRAX_NETWORK_LED_ON_WHEN_LINK
+ bool "LED_on_when_link"
+ help
+ Selecting LED_on_when_link will light the LED when there is a
+ connection and will flash off when there is activity.
+
+ Selecting LED_on_when_activity will light the LED only when
+ there is activity.
+
+ This setting will also affect the behaviour of other activity LEDs
+ e.g. Bluetooth.
+
+config ETRAX_NETWORK_LED_ON_WHEN_ACTIVITY
+ bool "LED_on_when_activity"
+ help
+ Selecting LED_on_when_link will light the LED when there is a
+ connection and will flash off when there is activity.
+
+ Selecting LED_on_when_activity will light the LED only when
+ there is activity.
+
+ This setting will also affect the behaviour of other activity LEDs
+ e.g. Bluetooth.
+
+endchoice
+
+config ETRAXFS_SERIAL
+ bool "Serial-port support"
+ depends on ETRAX_ARCH_V32
+ help
+ Enables the ETRAX FS serial driver for ser0 (ttyS0)
+ You probably want this enabled.
+
+config ETRAX_SERIAL_PORT0
+ bool "Serial port 0 enabled"
+ depends on ETRAXFS_SERIAL
+ help
+ Enables the ETRAX FS serial driver for ser0 (ttyS0)
+ Normally you want this on. You can control what DMA channels to use
+ if you do not need DMA to something else.
+ ser0 can use dma4 or dma6 for output and dma5 or dma7 for input.
+
+choice
+ prompt "Ser0 DMA in channel "
+ depends on ETRAX_SERIAL_PORT0
+ default ETRAX_SERIAL_PORT0_NO_DMA_IN
+ help
+ What DMA channel to use for ser0.
+
+
+config ETRAX_SERIAL_PORT0_NO_DMA_IN
+ bool "Ser0 uses no DMA for input"
+ help
+ Do not use DMA for ser0 input.
+
+config ETRAX_SERIAL_PORT0_DMA7_IN
+ bool "Ser0 uses DMA7 for input"
+ depends on ETRAX_SERIAL_PORT0
+ help
+ Enables the DMA7 input channel for ser0 (ttyS0).
+ If you do not enable DMA, an interrupt for each character will be
+ used when receiveing data.
+ Normally you want to use DMA, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+choice
+ prompt "Ser0 DMA out channel"
+ depends on ETRAX_SERIAL_PORT0
+ default ETRAX_SERIAL_PORT0_NO_DMA_OUT
+
+config ETRAX_SERIAL_PORT0_NO_DMA_OUT
+ bool "Ser0 uses no DMA for output"
+ help
+ Do not use DMA for ser0 output.
+
+config ETRAX_SERIAL_PORT0_DMA6_OUT
+ bool "Ser0 uses DMA6 for output"
+ depends on ETRAX_SERIAL_PORT0
+ help
+ Enables the DMA6 output channel for ser0 (ttyS0).
+ If you do not enable DMA, an interrupt for each character will be
+ used when transmitting data.
+ Normally you want to use DMA, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+config ETRAX_SER0_DTR_BIT
+ string "Ser 0 DTR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT0
+
+config ETRAX_SER0_RI_BIT
+ string "Ser 0 RI bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT0
+
+config ETRAX_SER0_DSR_BIT
+ string "Ser 0 DSR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT0
+
+config ETRAX_SER0_CD_BIT
+ string "Ser 0 CD bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT0
+
+config ETRAX_SERIAL_PORT1
+ bool "Serial port 1 enabled"
+ depends on ETRAXFS_SERIAL
+ help
+ Enables the ETRAX FS serial driver for ser1 (ttyS1).
+
+choice
+ prompt "Ser1 DMA in channel "
+ depends on ETRAX_SERIAL_PORT1
+ default ETRAX_SERIAL_PORT1_NO_DMA_IN
+ help
+ What DMA channel to use for ser1.
+
+
+config ETRAX_SERIAL_PORT1_NO_DMA_IN
+ bool "Ser1 uses no DMA for input"
+ help
+ Do not use DMA for ser1 input.
+
+config ETRAX_SERIAL_PORT1_DMA5_IN
+ bool "Ser1 uses DMA5 for input"
+ depends on ETRAX_SERIAL_PORT1
+ help
+ Enables the DMA5 input channel for ser1 (ttyS1).
+ If you do not enable DMA, an interrupt for each character will be
+ used when receiveing data.
+ Normally you want this on, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+choice
+ prompt "Ser1 DMA out channel "
+ depends on ETRAX_SERIAL_PORT1
+ default ETRAX_SERIAL_PORT1_NO_DMA_OUT
+ help
+ What DMA channel to use for ser1.
+
+config ETRAX_SERIAL_PORT1_NO_DMA_OUT
+ bool "Ser1 uses no DMA for output"
+ help
+ Do not use DMA for ser1 output.
+
+config ETRAX_SERIAL_PORT1_DMA4_OUT
+ bool "Ser1 uses DMA4 for output"
+ depends on ETRAX_SERIAL_PORT1
+ help
+ Enables the DMA4 output channel for ser1 (ttyS1).
+ If you do not enable DMA, an interrupt for each character will be
+ used when transmitting data.
+ Normally you want this on, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+config ETRAX_SER1_DTR_BIT
+ string "Ser 1 DTR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT1
+
+config ETRAX_SER1_RI_BIT
+ string "Ser 1 RI bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT1
+
+config ETRAX_SER1_DSR_BIT
+ string "Ser 1 DSR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT1
+
+config ETRAX_SER1_CD_BIT
+ string "Ser 1 CD bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT1
+
+config ETRAX_SERIAL_PORT2
+ bool "Serial port 2 enabled"
+ depends on ETRAXFS_SERIAL
+ help
+ Enables the ETRAX FS serial driver for ser2 (ttyS2).
+
+choice
+ prompt "Ser2 DMA in channel "
+ depends on ETRAX_SERIAL_PORT2
+ default ETRAX_SERIAL_PORT2_NO_DMA_IN
+ help
+ What DMA channel to use for ser2.
+
+
+config ETRAX_SERIAL_PORT2_NO_DMA_IN
+ bool "Ser2 uses no DMA for input"
+ help
+ Do not use DMA for ser2 input.
+
+config ETRAX_SERIAL_PORT2_DMA3_IN
+ bool "Ser2 uses DMA3 for input"
+ depends on ETRAX_SERIAL_PORT2
+ help
+ Enables the DMA3 input channel for ser2 (ttyS2).
+ If you do not enable DMA, an interrupt for each character will be
+ used when receiveing data.
+ Normally you want to use DMA, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+choice
+ prompt "Ser2 DMA out channel"
+ depends on ETRAX_SERIAL_PORT2
+ default ETRAX_SERIAL_PORT2_NO_DMA_OUT
+
+config ETRAX_SERIAL_PORT2_NO_DMA_OUT
+ bool "Ser2 uses no DMA for output"
+ help
+ Do not use DMA for ser2 output.
+
+config ETRAX_SERIAL_PORT2_DMA2_OUT
+ bool "Ser2 uses DMA2 for output"
+ depends on ETRAX_SERIAL_PORT2
+ help
+ Enables the DMA2 output channel for ser2 (ttyS2).
+ If you do not enable DMA, an interrupt for each character will be
+ used when transmitting data.
+ Normally you want to use DMA, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+config ETRAX_SER2_DTR_BIT
+ string "Ser 2 DTR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT2
+
+config ETRAX_SER2_RI_BIT
+ string "Ser 2 RI bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT2
+
+config ETRAX_SER2_DSR_BIT
+ string "Ser 2 DSR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT2
+
+config ETRAX_SER2_CD_BIT
+ string "Ser 2 CD bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT2
+
+config ETRAX_SERIAL_PORT3
+ bool "Serial port 3 enabled"
+ depends on ETRAXFS_SERIAL
+ help
+ Enables the ETRAX FS serial driver for ser3 (ttyS3).
+
+choice
+ prompt "Ser3 DMA in channel "
+ depends on ETRAX_SERIAL_PORT3
+ default ETRAX_SERIAL_PORT3_NO_DMA_IN
+ help
+ What DMA channel to use for ser3.
+
+
+config ETRAX_SERIAL_PORT3_NO_DMA_IN
+ bool "Ser3 uses no DMA for input"
+ help
+ Do not use DMA for ser3 input.
+
+config ETRAX_SERIAL_PORT3_DMA9_IN
+ bool "Ser3 uses DMA9 for input"
+ depends on ETRAX_SERIAL_PORT3
+ help
+ Enables the DMA9 input channel for ser3 (ttyS3).
+ If you do not enable DMA, an interrupt for each character will be
+ used when receiveing data.
+ Normally you want to use DMA, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+choice
+ prompt "Ser3 DMA out channel"
+ depends on ETRAX_SERIAL_PORT3
+ default ETRAX_SERIAL_PORT3_NO_DMA_OUT
+
+config ETRAX_SERIAL_PORT3_NO_DMA_OUT
+ bool "Ser3 uses no DMA for output"
+ help
+ Do not use DMA for ser3 output.
+
+config ETRAX_SERIAL_PORT3_DMA8_OUT
+ bool "Ser3 uses DMA8 for output"
+ depends on ETRAX_SERIAL_PORT3
+ help
+ Enables the DMA8 output channel for ser3 (ttyS3).
+ If you do not enable DMA, an interrupt for each character will be
+ used when transmitting data.
+ Normally you want to use DMA, unless you use the DMA channel for
+ something else.
+
+endchoice
+
+config ETRAX_SER3_DTR_BIT
+ string "Ser 3 DTR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT3
+
+config ETRAX_SER3_RI_BIT
+ string "Ser 3 RI bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT3
+
+config ETRAX_SER3_DSR_BIT
+ string "Ser 3 DSR bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT3
+
+config ETRAX_SER3_CD_BIT
+ string "Ser 3 CD bit (empty = not used)"
+ depends on ETRAX_SERIAL_PORT3
+
+config ETRAX_RS485
+ bool "RS-485 support"
+ depends on ETRAX_SERIAL
+ help
+ Enables support for RS-485 serial communication. For a primer on
+ RS-485, see <http://www.hw.cz/english/docs/rs485/rs485.html>.
+
+config ETRAX_RS485_DISABLE_RECEIVER
+ bool "Disable serial receiver"
+ depends on ETRAX_RS485
+ help
+ It is necessary to disable the serial receiver to avoid serial
+ loopback. Not all products are able to do this in software only.
+ Axis 2400/2401 must disable receiver.
+
+config ETRAX_AXISFLASHMAP
+ bool "Axis flash-map support"
+ depends on ETRAX_ARCH_V32
+ select MTD
+ select MTD_CFI
+ select MTD_CFI_AMDSTD
+ select MTD_OBSOLETE_CHIPS
+ select MTD_AMDSTD
+ select MTD_CHAR
+ select MTD_BLOCK
+ select MTD_PARTITIONS
+ select MTD_CONCAT
+ select MTD_COMPLEX_MAPPINGS
+ help
+ This option enables MTD mapping of flash devices. Needed to use
+ flash memories. If unsure, say Y.
+
+config ETRAX_SYNCHRONOUS_SERIAL
+ bool "Synchronous serial-port support"
+ depends on ETRAX_ARCH_V32
+ help
+ Enables the ETRAX FS synchronous serial driver.
+
+config ETRAX_SYNCHRONOUS_SERIAL_PORT0
+ bool "Synchronous serial port 0 enabled"
+ depends on ETRAX_SYNCHRONOUS_SERIAL
+ help
+ Enabled synchronous serial port 0.
+
+config ETRAX_SYNCHRONOUS_SERIAL0_DMA
+ bool "Enable DMA on synchronous serial port 0."
+ depends on ETRAX_SYNCHRONOUS_SERIAL_PORT0
+ help
+ A synchronous serial port can run in manual or DMA mode.
+ Selecting this option will make it run in DMA mode.
+
+config ETRAX_SYNCHRONOUS_SERIAL_PORT1
+ bool "Synchronous serial port 1 enabled"
+ depends on ETRAX_SYNCHRONOUS_SERIAL
+ help
+ Enabled synchronous serial port 1.
+
+config ETRAX_SYNCHRONOUS_SERIAL1_DMA
+ bool "Enable DMA on synchronous serial port 1."
+ depends on ETRAX_SYNCHRONOUS_SERIAL_PORT1
+ help
+ A synchronous serial port can run in manual or DMA mode.
+ Selecting this option will make it run in DMA mode.
+
+config ETRAX_PTABLE_SECTOR
+ int "Byte-offset of partition table sector"
+ depends on ETRAX_AXISFLASHMAP
+ default "65536"
+ help
+ Byte-offset of the partition table in the first flash chip.
+ The default value is 64kB and should not be changed unless
+ you know exactly what you are doing. The only valid reason
+ for changing this is when the flash block size is bigger
+ than 64kB (e.g. when using two parallel 16 bit flashes).
+
+config ETRAX_NANDFLASH
+ bool "NAND flash support"
+ depends on ETRAX_ARCH_V32
+ select MTD_NAND
+ select MTD_NAND_IDS
+ help
+ This option enables MTD mapping of NAND flash devices. Needed to use
+ NAND flash memories. If unsure, say Y.
+
+config ETRAX_I2C
+ bool "I2C driver"
+ depends on ETRAX_ARCH_V32
+ help
+ This option enabled the I2C driver used by e.g. the RTC driver.
+
+config ETRAX_I2C_DATA_PORT
+ string "I2C data pin"
+ depends on ETRAX_I2C
+ help
+ The pin to use for I2C data.
+
+config ETRAX_I2C_CLK_PORT
+ string "I2C clock pin"
+ depends on ETRAX_I2C
+ help
+ The pin to use for I2C clock.
+
+config ETRAX_RTC
+ bool "Real Time Clock support"
+ depends on ETRAX_ARCH_V32
+ help
+ Enabled RTC support.
+
+choice
+ prompt "RTC chip"
+ depends on ETRAX_RTC
+ default ETRAX_PCF8563
+
+config ETRAX_PCF8563
+ bool "PCF8563"
+ help
+ Philips PCF8563 RTC
+
+endchoice
+
+config ETRAX_GPIO
+ bool "GPIO support"
+ depends on ETRAX_ARCH_V32
+ ---help---
+ Enables the ETRAX general port device (major 120, minors 0-4).
+ You can use this driver to access the general port bits. It supports
+ these ioctl's:
+ #include <linux/etraxgpio.h>
+ fd = open("/dev/gpioa", O_RDWR); // or /dev/gpiob
+ ioctl(fd, _IO(ETRAXGPIO_IOCTYPE, IO_SETBITS), bits_to_set);
+ ioctl(fd, _IO(ETRAXGPIO_IOCTYPE, IO_CLRBITS), bits_to_clear);
+ err = ioctl(fd, _IO(ETRAXGPIO_IOCTYPE, IO_READ_INBITS), &val);
+ Remember that you need to setup the port directions appropriately in
+ the General configuration.
+
+config ETRAX_PA_BUTTON_BITMASK
+ hex "PA-buttons bitmask"
+ depends on ETRAX_GPIO
+ default "0x02"
+ help
+ This is a bitmask (8 bits) with information about what bits on PA
+ that are used for buttons.
+ Most products has a so called TEST button on PA1, if that is true
+ use 0x02 here.
+ Use 00 if there are no buttons on PA.
+ If the bitmask is <> 00 a button driver will be included in the gpio
+ driver. ETRAX general I/O support must be enabled.
+
+config ETRAX_PA_CHANGEABLE_DIR
+ hex "PA user changeable dir mask"
+ depends on ETRAX_GPIO
+ default "0x00"
+ help
+ This is a bitmask (8 bits) with information of what bits in PA that a
+ user can change direction on using ioctl's.
+ Bit set = changeable.
+ You probably want 0x00 here, but it depends on your hardware.
+
+config ETRAX_PA_CHANGEABLE_BITS
+ hex "PA user changeable bits mask"
+ depends on ETRAX_GPIO
+ default "0x00"
+ help
+ This is a bitmask (8 bits) with information of what bits in PA
+ that a user can change the value on using ioctl's.
+ Bit set = changeable.
+
+config ETRAX_PB_CHANGEABLE_DIR
+ hex "PB user changeable dir mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PB
+ that a user can change direction on using ioctl's.
+ Bit set = changeable.
+ You probably want 0x00000 here, but it depends on your hardware.
+
+config ETRAX_PB_CHANGEABLE_BITS
+ hex "PB user changeable bits mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PB
+ that a user can change the value on using ioctl's.
+ Bit set = changeable.
+
+config ETRAX_PC_CHANGEABLE_DIR
+ hex "PC user changeable dir mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PC
+ that a user can change direction on using ioctl's.
+ Bit set = changeable.
+ You probably want 0x00000 here, but it depends on your hardware.
+
+config ETRAX_PC_CHANGEABLE_BITS
+ hex "PC user changeable bits mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PC
+ that a user can change the value on using ioctl's.
+ Bit set = changeable.
+
+config ETRAX_PD_CHANGEABLE_DIR
+ hex "PD user changeable dir mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PD
+ that a user can change direction on using ioctl's.
+ Bit set = changeable.
+ You probably want 0x00000 here, but it depends on your hardware.
+
+config ETRAX_PD_CHANGEABLE_BITS
+ hex "PD user changeable bits mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PD
+ that a user can change the value on using ioctl's.
+ Bit set = changeable.
+
+config ETRAX_PE_CHANGEABLE_DIR
+ hex "PE user changeable dir mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PE
+ that a user can change direction on using ioctl's.
+ Bit set = changeable.
+ You probably want 0x00000 here, but it depends on your hardware.
+
+config ETRAX_PE_CHANGEABLE_BITS
+ hex "PE user changeable bits mask"
+ depends on ETRAX_GPIO
+ default "0x00000"
+ help
+ This is a bitmask (18 bits) with information of what bits in PE
+ that a user can change the value on using ioctl's.
+ Bit set = changeable.
+
+config ETRAX_IDE
+ bool "ATA/IDE support"
+ depends on ETRAX_ARCH_V32
+ select IDE
+ select BLK_DEV_IDE
+ select BLK_DEV_IDEDISK
+ select BLK_DEV_IDECD
+ select BLK_DEV_IDEDMA
+ help
+ Enables the ETRAX IDE driver.
+
+config ETRAX_CARDBUS
+ bool "Cardbus support"
+ depends on ETRAX_ARCH_V32
+ select PCCARD
+ select CARDBUS
+ select HOTPLUG
+ select PCCARD_NONSTATIC
+ help
+ Enabled the ETRAX Carbus driver.
+
+config PCI
+ bool
+ depends on ETRAX_CARDBUS
+ default y
+
+config ETRAX_IOP_FW_LOAD
+ tristate "IO-processor hotplug firmware loading support"
+ depends on ETRAX_ARCH_V32
+ select FW_LOADER
+ help
+ Enables IO-processor hotplug firmware loading support.
+
+config ETRAX_STREAMCOPROC
+ tristate "Stream co-processor driver enabled"
+ depends on ETRAX_ARCH_V32
+ help
+ This option enables a driver for the stream co-processor
+ for cryptographic operations.
diff --git a/arch/cris/arch-v32/drivers/Makefile b/arch/cris/arch-v32/drivers/Makefile
new file mode 100644
index 000000000000..a359cd20ae75
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for Etrax-specific drivers
+#
+
+obj-$(CONFIG_ETRAX_STREAMCOPROC) += cryptocop.o
+obj-$(CONFIG_ETRAX_AXISFLASHMAP) += axisflashmap.o
+obj-$(CONFIG_ETRAX_NANDFLASH) += nandflash.o
+obj-$(CONFIG_ETRAX_GPIO) += gpio.o
+obj-$(CONFIG_ETRAX_IOP_FW_LOAD) += iop_fw_load.o
+obj-$(CONFIG_ETRAX_PCF8563) += pcf8563.o
+obj-$(CONFIG_ETRAX_I2C) += i2c.o
+obj-$(CONFIG_ETRAX_SYNCHRONOUS_SERIAL) += sync_serial.o
+obj-$(CONFIG_PCI) += pci/
diff --git a/arch/cris/arch-v32/drivers/axisflashmap.c b/arch/cris/arch-v32/drivers/axisflashmap.c
new file mode 100644
index 000000000000..78ed52b1cdac
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/axisflashmap.c
@@ -0,0 +1,455 @@
+/*
+ * Physical mapping layer for MTD using the Axis partitiontable format
+ *
+ * Copyright (c) 2001, 2002, 2003 Axis Communications AB
+ *
+ * This file is under the GPL.
+ *
+ * First partition is always sector 0 regardless of if we find a partitiontable
+ * or not. In the start of the next sector, there can be a partitiontable that
+ * tells us what other partitions to define. If there isn't, we use a default
+ * partition split defined below.
+ *
+ * Copy of os/lx25/arch/cris/arch-v10/drivers/axisflashmap.c 1.5
+ * with minor changes.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/config.h>
+#include <linux/init.h>
+
+#include <linux/mtd/concat.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/mtdram.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/arch/hwregs/config_defs.h>
+#include <asm/axisflashmap.h>
+#include <asm/mmu.h>
+
+#define MEM_CSE0_SIZE (0x04000000)
+#define MEM_CSE1_SIZE (0x04000000)
+
+#define FLASH_UNCACHED_ADDR KSEG_E
+#define FLASH_CACHED_ADDR KSEG_F
+
+#if CONFIG_ETRAX_FLASH_BUSWIDTH==1
+#define flash_data __u8
+#elif CONFIG_ETRAX_FLASH_BUSWIDTH==2
+#define flash_data __u16
+#elif CONFIG_ETRAX_FLASH_BUSWIDTH==4
+#define flash_data __u16
+#endif
+
+/* From head.S */
+extern unsigned long romfs_start, romfs_length, romfs_in_flash;
+
+/* The master mtd for the entire flash. */
+struct mtd_info* axisflash_mtd = NULL;
+
+/* Map driver functions. */
+
+static map_word flash_read(struct map_info *map, unsigned long ofs)
+{
+ map_word tmp;
+ tmp.x[0] = *(flash_data *)(map->map_priv_1 + ofs);
+ return tmp;
+}
+
+static void flash_copy_from(struct map_info *map, void *to,
+ unsigned long from, ssize_t len)
+{
+ memcpy(to, (void *)(map->map_priv_1 + from), len);
+}
+
+static void flash_write(struct map_info *map, map_word d, unsigned long adr)
+{
+ *(flash_data *)(map->map_priv_1 + adr) = (flash_data)d.x[0];
+}
+
+/*
+ * The map for chip select e0.
+ *
+ * We run into tricky coherence situations if we mix cached with uncached
+ * accesses to we only use the uncached version here.
+ *
+ * The size field is the total size where the flash chips may be mapped on the
+ * chip select. MTD probes should find all devices there and it does not matter
+ * if there are unmapped gaps or aliases (mirrors of flash devices). The MTD
+ * probes will ignore them.
+ *
+ * The start address in map_priv_1 is in virtual memory so we cannot use
+ * MEM_CSE0_START but must rely on that FLASH_UNCACHED_ADDR is the start
+ * address of cse0.
+ */
+static struct map_info map_cse0 = {
+ .name = "cse0",
+ .size = MEM_CSE0_SIZE,
+ .bankwidth = CONFIG_ETRAX_FLASH_BUSWIDTH,
+ .read = flash_read,
+ .copy_from = flash_copy_from,
+ .write = flash_write,
+ .map_priv_1 = FLASH_UNCACHED_ADDR
+};
+
+/*
+ * The map for chip select e1.
+ *
+ * If there was a gap between cse0 and cse1, map_priv_1 would get the wrong
+ * address, but there isn't.
+ */
+static struct map_info map_cse1 = {
+ .name = "cse1",
+ .size = MEM_CSE1_SIZE,
+ .bankwidth = CONFIG_ETRAX_FLASH_BUSWIDTH,
+ .read = flash_read,
+ .copy_from = flash_copy_from,
+ .write = flash_write,
+ .map_priv_1 = FLASH_UNCACHED_ADDR + MEM_CSE0_SIZE
+};
+
+/* If no partition-table was found, we use this default-set. */
+#define MAX_PARTITIONS 7
+#define NUM_DEFAULT_PARTITIONS 3
+
+/*
+ * Default flash size is 2MB. CONFIG_ETRAX_PTABLE_SECTOR is most likely the
+ * size of one flash block and "filesystem"-partition needs 5 blocks to be able
+ * to use JFFS.
+ */
+static struct mtd_partition axis_default_partitions[NUM_DEFAULT_PARTITIONS] = {
+ {
+ .name = "boot firmware",
+ .size = CONFIG_ETRAX_PTABLE_SECTOR,
+ .offset = 0
+ },
+ {
+ .name = "kernel",
+ .size = 0x200000 - (6 * CONFIG_ETRAX_PTABLE_SECTOR),
+ .offset = CONFIG_ETRAX_PTABLE_SECTOR
+ },
+ {
+ .name = "filesystem",
+ .size = 5 * CONFIG_ETRAX_PTABLE_SECTOR,
+ .offset = 0x200000 - (5 * CONFIG_ETRAX_PTABLE_SECTOR)
+ }
+};
+
+/* Initialize the ones normally used. */
+static struct mtd_partition axis_partitions[MAX_PARTITIONS] = {
+ {
+ .name = "part0",
+ .size = CONFIG_ETRAX_PTABLE_SECTOR,
+ .offset = 0
+ },
+ {
+ .name = "part1",
+ .size = 0,
+ .offset = 0
+ },
+ {
+ .name = "part2",
+ .size = 0,
+ .offset = 0
+ },
+ {
+ .name = "part3",
+ .size = 0,
+ .offset = 0
+ },
+ {
+ .name = "part4",
+ .size = 0,
+ .offset = 0
+ },
+ {
+ .name = "part5",
+ .size = 0,
+ .offset = 0
+ },
+ {
+ .name = "part6",
+ .size = 0,
+ .offset = 0
+ },
+};
+
+/*
+ * Probe a chip select for AMD-compatible (JEDEC) or CFI-compatible flash
+ * chips in that order (because the amd_flash-driver is faster).
+ */
+static struct mtd_info *probe_cs(struct map_info *map_cs)
+{
+ struct mtd_info *mtd_cs = NULL;
+
+ printk(KERN_INFO
+ "%s: Probing a 0x%08lx bytes large window at 0x%08lx.\n",
+ map_cs->name, map_cs->size, map_cs->map_priv_1);
+
+#ifdef CONFIG_MTD_AMDSTD
+ mtd_cs = do_map_probe("amd_flash", map_cs);
+#endif
+#ifdef CONFIG_MTD_CFI
+ if (!mtd_cs) {
+ mtd_cs = do_map_probe("cfi_probe", map_cs);
+ }
+#endif
+
+ return mtd_cs;
+}
+
+/*
+ * Probe each chip select individually for flash chips. If there are chips on
+ * both cse0 and cse1, the mtd_info structs will be concatenated to one struct
+ * so that MTD partitions can cross chip boundries.
+ *
+ * The only known restriction to how you can mount your chips is that each
+ * chip select must hold similar flash chips. But you need external hardware
+ * to do that anyway and you can put totally different chips on cse0 and cse1
+ * so it isn't really much of a restriction.
+ */
+extern struct mtd_info* __init crisv32_nand_flash_probe (void);
+static struct mtd_info *flash_probe(void)
+{
+ struct mtd_info *mtd_cse0;
+ struct mtd_info *mtd_cse1;
+ struct mtd_info *mtd_nand = NULL;
+ struct mtd_info *mtd_total;
+ struct mtd_info *mtds[3];
+ int count = 0;
+
+ if ((mtd_cse0 = probe_cs(&map_cse0)) != NULL)
+ mtds[count++] = mtd_cse0;
+ if ((mtd_cse1 = probe_cs(&map_cse1)) != NULL)
+ mtds[count++] = mtd_cse1;
+
+#ifdef CONFIG_ETRAX_NANDFLASH
+ if ((mtd_nand = crisv32_nand_flash_probe()) != NULL)
+ mtds[count++] = mtd_nand;
+#endif
+
+ if (!mtd_cse0 && !mtd_cse1 && !mtd_nand) {
+ /* No chip found. */
+ return NULL;
+ }
+
+ if (count > 1) {
+#ifdef CONFIG_MTD_CONCAT
+ /* Since the concatenation layer adds a small overhead we
+ * could try to figure out if the chips in cse0 and cse1 are
+ * identical and reprobe the whole cse0+cse1 window. But since
+ * flash chips are slow, the overhead is relatively small.
+ * So we use the MTD concatenation layer instead of further
+ * complicating the probing procedure.
+ */
+ mtd_total = mtd_concat_create(mtds,
+ count,
+ "cse0+cse1+nand");
+#else
+ printk(KERN_ERR "%s and %s: Cannot concatenate due to kernel "
+ "(mis)configuration!\n", map_cse0.name, map_cse1.name);
+ mtd_toal = NULL;
+#endif
+ if (!mtd_total) {
+ printk(KERN_ERR "%s and %s: Concatenation failed!\n",
+ map_cse0.name, map_cse1.name);
+
+ /* The best we can do now is to only use what we found
+ * at cse0.
+ */
+ mtd_total = mtd_cse0;
+ map_destroy(mtd_cse1);
+ }
+ } else {
+ mtd_total = mtd_cse0? mtd_cse0 : mtd_cse1 ? mtd_cse1 : mtd_nand;
+
+ }
+
+ return mtd_total;
+}
+
+extern unsigned long crisv32_nand_boot;
+extern unsigned long crisv32_nand_cramfs_offset;
+
+/*
+ * Probe the flash chip(s) and, if it succeeds, read the partition-table
+ * and register the partitions with MTD.
+ */
+static int __init init_axis_flash(void)
+{
+ struct mtd_info *mymtd;
+ int err = 0;
+ int pidx = 0;
+ struct partitiontable_head *ptable_head = NULL;
+ struct partitiontable_entry *ptable;
+ int use_default_ptable = 1; /* Until proven otherwise. */
+ const char *pmsg = KERN_INFO " /dev/flash%d at 0x%08x, size 0x%08x\n";
+ static char page[512];
+ size_t len;
+
+#ifndef CONFIG_ETRAXFS_SIM
+ mymtd = flash_probe();
+ mymtd->read(mymtd, CONFIG_ETRAX_PTABLE_SECTOR, 512, &len, page);
+ ptable_head = (struct partitiontable_head *)(page + PARTITION_TABLE_OFFSET);
+
+ if (!mymtd) {
+ /* There's no reason to use this module if no flash chip can
+ * be identified. Make sure that's understood.
+ */
+ printk(KERN_INFO "axisflashmap: Found no flash chip.\n");
+ } else {
+ printk(KERN_INFO "%s: 0x%08x bytes of flash memory.\n",
+ mymtd->name, mymtd->size);
+ axisflash_mtd = mymtd;
+ }
+
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+ }
+ pidx++; /* First partition is always set to the default. */
+
+ if (ptable_head && (ptable_head->magic == PARTITION_TABLE_MAGIC)
+ && (ptable_head->size <
+ (MAX_PARTITIONS * sizeof(struct partitiontable_entry) +
+ PARTITIONTABLE_END_MARKER_SIZE))
+ && (*(unsigned long*)((void*)ptable_head + sizeof(*ptable_head) +
+ ptable_head->size -
+ PARTITIONTABLE_END_MARKER_SIZE)
+ == PARTITIONTABLE_END_MARKER)) {
+ /* Looks like a start, sane length and end of a
+ * partition table, lets check csum etc.
+ */
+ int ptable_ok = 0;
+ struct partitiontable_entry *max_addr =
+ (struct partitiontable_entry *)
+ ((unsigned long)ptable_head + sizeof(*ptable_head) +
+ ptable_head->size);
+ unsigned long offset = CONFIG_ETRAX_PTABLE_SECTOR;
+ unsigned char *p;
+ unsigned long csum = 0;
+
+ ptable = (struct partitiontable_entry *)
+ ((unsigned long)ptable_head + sizeof(*ptable_head));
+
+ /* Lets be PARANOID, and check the checksum. */
+ p = (unsigned char*) ptable;
+
+ while (p <= (unsigned char*)max_addr) {
+ csum += *p++;
+ csum += *p++;
+ csum += *p++;
+ csum += *p++;
+ }
+ ptable_ok = (csum == ptable_head->checksum);
+
+ /* Read the entries and use/show the info. */
+ printk(KERN_INFO " Found a%s partition table at 0x%p-0x%p.\n",
+ (ptable_ok ? " valid" : "n invalid"), ptable_head,
+ max_addr);
+
+ /* We have found a working bootblock. Now read the
+ * partition table. Scan the table. It ends when
+ * there is 0xffffffff, that is, empty flash.
+ */
+ while (ptable_ok
+ && ptable->offset != 0xffffffff
+ && ptable < max_addr
+ && pidx < MAX_PARTITIONS) {
+
+ axis_partitions[pidx].offset = offset + ptable->offset + (crisv32_nand_boot ? 16384 : 0);
+ axis_partitions[pidx].size = ptable->size;
+
+ printk(pmsg, pidx, axis_partitions[pidx].offset,
+ axis_partitions[pidx].size);
+ pidx++;
+ ptable++;
+ }
+ use_default_ptable = !ptable_ok;
+ }
+
+ if (romfs_in_flash) {
+ /* Add an overlapping device for the root partition (romfs). */
+
+ axis_partitions[pidx].name = "romfs";
+ if (crisv32_nand_boot) {
+ char* data = kmalloc(1024, GFP_KERNEL);
+ int len;
+ int offset = crisv32_nand_cramfs_offset & ~(1024-1);
+ char* tmp;
+
+ mymtd->read(mymtd, offset, 1024, &len, data);
+ tmp = &data[crisv32_nand_cramfs_offset % 512];
+ axis_partitions[pidx].size = *(unsigned*)(tmp + 4);
+ axis_partitions[pidx].offset = crisv32_nand_cramfs_offset;
+ kfree(data);
+ } else {
+ axis_partitions[pidx].size = romfs_length;
+ axis_partitions[pidx].offset = romfs_start - FLASH_CACHED_ADDR;
+ }
+
+ axis_partitions[pidx].mask_flags |= MTD_WRITEABLE;
+
+ printk(KERN_INFO
+ " Adding readonly flash partition for romfs image:\n");
+ printk(pmsg, pidx, axis_partitions[pidx].offset,
+ axis_partitions[pidx].size);
+ pidx++;
+ }
+
+ if (mymtd) {
+ if (use_default_ptable) {
+ printk(KERN_INFO " Using default partition table.\n");
+ err = add_mtd_partitions(mymtd, axis_default_partitions,
+ NUM_DEFAULT_PARTITIONS);
+ } else {
+ err = add_mtd_partitions(mymtd, axis_partitions, pidx);
+ }
+
+ if (err) {
+ panic("axisflashmap could not add MTD partitions!\n");
+ }
+ }
+/* CONFIG_EXTRAXFS_SIM */
+#endif
+
+ if (!romfs_in_flash) {
+ /* Create an RAM device for the root partition (romfs). */
+
+#if !defined(CONFIG_MTD_MTDRAM) || (CONFIG_MTDRAM_TOTAL_SIZE != 0) || (CONFIG_MTDRAM_ABS_POS != 0)
+ /* No use trying to boot this kernel from RAM. Panic! */
+ printk(KERN_EMERG "axisflashmap: Cannot create an MTD RAM "
+ "device due to kernel (mis)configuration!\n");
+ panic("This kernel cannot boot from RAM!\n");
+#else
+ struct mtd_info *mtd_ram;
+
+ mtd_ram = (struct mtd_info *)kmalloc(sizeof(struct mtd_info),
+ GFP_KERNEL);
+ if (!mtd_ram) {
+ panic("axisflashmap couldn't allocate memory for "
+ "mtd_info!\n");
+ }
+
+ printk(KERN_INFO " Adding RAM partition for romfs image:\n");
+ printk(pmsg, pidx, romfs_start, romfs_length);
+
+ err = mtdram_init_device(mtd_ram, (void*)romfs_start,
+ romfs_length, "romfs");
+ if (err) {
+ panic("axisflashmap could not initialize MTD RAM "
+ "device!\n");
+ }
+#endif
+ }
+
+ return err;
+}
+
+/* This adds the above to the kernels init-call chain. */
+module_init(init_axis_flash);
+
+EXPORT_SYMBOL(axisflash_mtd);
diff --git a/arch/cris/arch-v32/drivers/cryptocop.c b/arch/cris/arch-v32/drivers/cryptocop.c
new file mode 100644
index 000000000000..ca72076c630a
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/cryptocop.c
@@ -0,0 +1,3522 @@
+/* $Id: cryptocop.c,v 1.13 2005/04/21 17:27:55 henriken Exp $
+ *
+ * Stream co-processor driver for the ETRAX FS
+ *
+ * Copyright (C) 2003-2005 Axis Communications AB
+ */
+
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/spinlock.h>
+#include <linux/stddef.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+
+#include <linux/list.h>
+#include <linux/interrupt.h>
+
+#include <asm/signal.h>
+#include <asm/irq.h>
+
+#include <asm/arch/dma.h>
+#include <asm/arch/hwregs/dma.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+
+#include <asm/arch/hwregs/strcop.h>
+#include <asm/arch/hwregs/strcop_defs.h>
+#include <asm/arch/cryptocop.h>
+
+
+
+#define DESCR_ALLOC_PAD (31)
+
+struct cryptocop_dma_desc {
+ char *free_buf; /* If non-null will be kfreed in free_cdesc() */
+ dma_descr_data *dma_descr;
+
+ unsigned char dma_descr_buf[sizeof(dma_descr_data) + DESCR_ALLOC_PAD];
+
+ unsigned int from_pool:1; /* If 1 'allocated' from the descriptor pool. */
+ struct cryptocop_dma_desc *next;
+};
+
+
+struct cryptocop_int_operation{
+ void *alloc_ptr;
+ cryptocop_session_id sid;
+
+ dma_descr_context ctx_out;
+ dma_descr_context ctx_in;
+
+ /* DMA descriptors allocated by driver. */
+ struct cryptocop_dma_desc *cdesc_out;
+ struct cryptocop_dma_desc *cdesc_in;
+
+ /* Strcop config to use. */
+ cryptocop_3des_mode tdes_mode;
+ cryptocop_csum_type csum_mode;
+
+ /* DMA descrs provided by consumer. */
+ dma_descr_data *ddesc_out;
+ dma_descr_data *ddesc_in;
+};
+
+
+struct cryptocop_tfrm_ctx {
+ cryptocop_tfrm_id tid;
+ unsigned int blocklength;
+
+ unsigned int start_ix;
+
+ struct cryptocop_tfrm_cfg *tcfg;
+ struct cryptocop_transform_ctx *tctx;
+
+ unsigned char previous_src;
+ unsigned char current_src;
+
+ /* Values to use in metadata out. */
+ unsigned char hash_conf;
+ unsigned char hash_mode;
+ unsigned char ciph_conf;
+ unsigned char cbcmode;
+ unsigned char decrypt;
+
+ unsigned int requires_padding:1;
+ unsigned int strict_block_length:1;
+ unsigned int active:1;
+ unsigned int done:1;
+ size_t consumed;
+ size_t produced;
+
+ /* Pad (input) descriptors to put in the DMA out list when the transform
+ * output is put on the DMA in list. */
+ struct cryptocop_dma_desc *pad_descs;
+
+ struct cryptocop_tfrm_ctx *prev_src;
+ struct cryptocop_tfrm_ctx *curr_src;
+
+ /* Mapping to HW. */
+ unsigned char unit_no;
+};
+
+
+struct cryptocop_private{
+ cryptocop_session_id sid;
+ struct cryptocop_private *next;
+};
+
+/* Session list. */
+
+struct cryptocop_transform_ctx{
+ struct cryptocop_transform_init init;
+ unsigned char dec_key[CRYPTOCOP_MAX_KEY_LENGTH];
+ unsigned int dec_key_set:1;
+
+ struct cryptocop_transform_ctx *next;
+};
+
+
+struct cryptocop_session{
+ cryptocop_session_id sid;
+
+ struct cryptocop_transform_ctx *tfrm_ctx;
+
+ struct cryptocop_session *next;
+};
+
+/* Priority levels for jobs sent to the cryptocop. Checksum operations from
+ kernel have highest priority since TCPIP stack processing must not
+ be a bottleneck. */
+typedef enum {
+ cryptocop_prio_kernel_csum = 0,
+ cryptocop_prio_kernel = 1,
+ cryptocop_prio_user = 2,
+ cryptocop_prio_no_prios = 3
+} cryptocop_queue_priority;
+
+struct cryptocop_prio_queue{
+ struct list_head jobs;
+ cryptocop_queue_priority prio;
+};
+
+struct cryptocop_prio_job{
+ struct list_head node;
+ cryptocop_queue_priority prio;
+
+ struct cryptocop_operation *oper;
+ struct cryptocop_int_operation *iop;
+};
+
+struct ioctl_job_cb_ctx {
+ unsigned int processed:1;
+};
+
+
+static struct cryptocop_session *cryptocop_sessions = NULL;
+spinlock_t cryptocop_sessions_lock;
+
+/* Next Session ID to assign. */
+static cryptocop_session_id next_sid = 1;
+
+/* Pad for checksum. */
+static const char csum_zero_pad[1] = {0x00};
+
+/* Trash buffer for mem2mem operations. */
+#define MEM2MEM_DISCARD_BUF_LENGTH (512)
+static unsigned char mem2mem_discard_buf[MEM2MEM_DISCARD_BUF_LENGTH];
+
+/* Descriptor pool. */
+/* FIXME Tweak this value. */
+#define CRYPTOCOP_DESCRIPTOR_POOL_SIZE (100)
+static struct cryptocop_dma_desc descr_pool[CRYPTOCOP_DESCRIPTOR_POOL_SIZE];
+static struct cryptocop_dma_desc *descr_pool_free_list;
+static int descr_pool_no_free;
+static spinlock_t descr_pool_lock;
+
+/* Lock to stop cryptocop to start processing of a new operation. The holder
+ of this lock MUST call cryptocop_start_job() after it is unlocked. */
+spinlock_t cryptocop_process_lock;
+
+static struct cryptocop_prio_queue cryptocop_job_queues[cryptocop_prio_no_prios];
+static spinlock_t cryptocop_job_queue_lock;
+static struct cryptocop_prio_job *cryptocop_running_job = NULL;
+static spinlock_t running_job_lock;
+
+/* The interrupt handler appends completed jobs to this list. The scehduled
+ * tasklet removes them upon sending the response to the crypto consumer. */
+static struct list_head cryptocop_completed_jobs;
+static spinlock_t cryptocop_completed_jobs_lock;
+
+DECLARE_WAIT_QUEUE_HEAD(cryptocop_ioc_process_wq);
+
+
+/** Local functions. **/
+
+static int cryptocop_open(struct inode *, struct file *);
+
+static int cryptocop_release(struct inode *, struct file *);
+
+static int cryptocop_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static void cryptocop_start_job(void);
+
+static int cryptocop_job_queue_insert(cryptocop_queue_priority prio, struct cryptocop_operation *operation);
+static int cryptocop_job_setup(struct cryptocop_prio_job **pj, struct cryptocop_operation *operation);
+
+static int cryptocop_job_queue_init(void);
+static void cryptocop_job_queue_close(void);
+
+static int create_md5_pad(int alloc_flag, unsigned long long hashed_length, char **pad, size_t *pad_length);
+
+static int create_sha1_pad(int alloc_flag, unsigned long long hashed_length, char **pad, size_t *pad_length);
+
+static int transform_ok(struct cryptocop_transform_init *tinit);
+
+static struct cryptocop_session *get_session(cryptocop_session_id sid);
+
+static struct cryptocop_transform_ctx *get_transform_ctx(struct cryptocop_session *sess, cryptocop_tfrm_id tid);
+
+static void delete_internal_operation(struct cryptocop_int_operation *iop);
+
+static void get_aes_decrypt_key(unsigned char *dec_key, const unsigned char *key, unsigned int keylength);
+
+static int init_stream_coprocessor(void);
+
+static void __exit exit_stream_coprocessor(void);
+
+/*#define LDEBUG*/
+#ifdef LDEBUG
+#define DEBUG(s) s
+#define DEBUG_API(s) s
+static void print_cryptocop_operation(struct cryptocop_operation *cop);
+static void print_dma_descriptors(struct cryptocop_int_operation *iop);
+static void print_strcop_crypto_op(struct strcop_crypto_op *cop);
+static void print_lock_status(void);
+static void print_user_dma_lists(struct cryptocop_dma_list_operation *dma_op);
+#define assert(s) do{if (!(s)) panic(#s);} while(0);
+#else
+#define DEBUG(s)
+#define DEBUG_API(s)
+#define assert(s)
+#endif
+
+
+/* Transform constants. */
+#define DES_BLOCK_LENGTH (8)
+#define AES_BLOCK_LENGTH (16)
+#define MD5_BLOCK_LENGTH (64)
+#define SHA1_BLOCK_LENGTH (64)
+#define CSUM_BLOCK_LENGTH (2)
+#define MD5_STATE_LENGTH (16)
+#define SHA1_STATE_LENGTH (20)
+
+/* The device number. */
+#define CRYPTOCOP_MAJOR (254)
+#define CRYPTOCOP_MINOR (0)
+
+
+
+struct file_operations cryptocop_fops = {
+ owner: THIS_MODULE,
+ open: cryptocop_open,
+ release: cryptocop_release,
+ ioctl: cryptocop_ioctl
+};
+
+
+static void free_cdesc(struct cryptocop_dma_desc *cdesc)
+{
+ DEBUG(printk("free_cdesc: cdesc 0x%p, from_pool=%d\n", cdesc, cdesc->from_pool));
+ if (cdesc->free_buf) kfree(cdesc->free_buf);
+
+ if (cdesc->from_pool) {
+ unsigned long int flags;
+ spin_lock_irqsave(&descr_pool_lock, flags);
+ cdesc->next = descr_pool_free_list;
+ descr_pool_free_list = cdesc;
+ ++descr_pool_no_free;
+ spin_unlock_irqrestore(&descr_pool_lock, flags);
+ } else {
+ kfree(cdesc);
+ }
+}
+
+
+static struct cryptocop_dma_desc *alloc_cdesc(int alloc_flag)
+{
+ int use_pool = (alloc_flag & GFP_ATOMIC) ? 1 : 0;
+ struct cryptocop_dma_desc *cdesc;
+
+ if (use_pool) {
+ unsigned long int flags;
+ spin_lock_irqsave(&descr_pool_lock, flags);
+ if (!descr_pool_free_list) {
+ spin_unlock_irqrestore(&descr_pool_lock, flags);
+ DEBUG_API(printk("alloc_cdesc: pool is empty\n"));
+ return NULL;
+ }
+ cdesc = descr_pool_free_list;
+ descr_pool_free_list = descr_pool_free_list->next;
+ --descr_pool_no_free;
+ spin_unlock_irqrestore(&descr_pool_lock, flags);
+ cdesc->from_pool = 1;
+ } else {
+ cdesc = kmalloc(sizeof(struct cryptocop_dma_desc), alloc_flag);
+ if (!cdesc) {
+ DEBUG_API(printk("alloc_cdesc: kmalloc\n"));
+ return NULL;
+ }
+ cdesc->from_pool = 0;
+ }
+ cdesc->dma_descr = (dma_descr_data*)(((unsigned long int)cdesc + offsetof(struct cryptocop_dma_desc, dma_descr_buf) + DESCR_ALLOC_PAD) & ~0x0000001F);
+
+ cdesc->next = NULL;
+
+ cdesc->free_buf = NULL;
+ cdesc->dma_descr->out_eop = 0;
+ cdesc->dma_descr->in_eop = 0;
+ cdesc->dma_descr->intr = 0;
+ cdesc->dma_descr->eol = 0;
+ cdesc->dma_descr->wait = 0;
+ cdesc->dma_descr->buf = NULL;
+ cdesc->dma_descr->after = NULL;
+
+ DEBUG_API(printk("alloc_cdesc: return 0x%p, cdesc->dma_descr=0x%p, from_pool=%d\n", cdesc, cdesc->dma_descr, cdesc->from_pool));
+ return cdesc;
+}
+
+
+static void setup_descr_chain(struct cryptocop_dma_desc *cd)
+{
+ DEBUG(printk("setup_descr_chain: entering\n"));
+ while (cd) {
+ if (cd->next) {
+ cd->dma_descr->next = (dma_descr_data*)virt_to_phys(cd->next->dma_descr);
+ } else {
+ cd->dma_descr->next = NULL;
+ }
+ cd = cd->next;
+ }
+ DEBUG(printk("setup_descr_chain: exit\n"));
+}
+
+
+/* Create a pad descriptor for the transform.
+ * Return -1 for error, 0 if pad created. */
+static int create_pad_descriptor(struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **pad_desc, int alloc_flag)
+{
+ struct cryptocop_dma_desc *cdesc = NULL;
+ int error = 0;
+ struct strcop_meta_out mo = {
+ .ciphsel = src_none,
+ .hashsel = src_none,
+ .csumsel = src_none
+ };
+ char *pad;
+ size_t plen;
+
+ DEBUG(printk("create_pad_descriptor: start.\n"));
+ /* Setup pad descriptor. */
+
+ DEBUG(printk("create_pad_descriptor: setting up padding.\n"));
+ cdesc = alloc_cdesc(alloc_flag);
+ if (!cdesc){
+ DEBUG_API(printk("create_pad_descriptor: alloc pad desc\n"));
+ goto error_cleanup;
+ }
+ switch (tc->unit_no) {
+ case src_md5:
+ error = create_md5_pad(alloc_flag, tc->consumed, &pad, &plen);
+ if (error){
+ DEBUG_API(printk("create_pad_descriptor: create_md5_pad_failed\n"));
+ goto error_cleanup;
+ }
+ cdesc->free_buf = pad;
+ mo.hashsel = src_dma;
+ mo.hashconf = tc->hash_conf;
+ mo.hashmode = tc->hash_mode;
+ break;
+ case src_sha1:
+ error = create_sha1_pad(alloc_flag, tc->consumed, &pad, &plen);
+ if (error){
+ DEBUG_API(printk("create_pad_descriptor: create_sha1_pad_failed\n"));
+ goto error_cleanup;
+ }
+ cdesc->free_buf = pad;
+ mo.hashsel = src_dma;
+ mo.hashconf = tc->hash_conf;
+ mo.hashmode = tc->hash_mode;
+ break;
+ case src_csum:
+ if (tc->consumed % tc->blocklength){
+ pad = (char*)csum_zero_pad;
+ plen = 1;
+ } else {
+ pad = (char*)cdesc; /* Use any pointer. */
+ plen = 0;
+ }
+ mo.csumsel = src_dma;
+ break;
+ }
+ cdesc->dma_descr->wait = 1;
+ cdesc->dma_descr->out_eop = 1; /* Since this is a pad output is pushed. EOP is ok here since the padded unit is the only one active. */
+ cdesc->dma_descr->buf = (char*)virt_to_phys((char*)pad);
+ cdesc->dma_descr->after = cdesc->dma_descr->buf + plen;
+
+ cdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, mo);
+ *pad_desc = cdesc;
+
+ return 0;
+
+ error_cleanup:
+ if (cdesc) free_cdesc(cdesc);
+ return -1;
+}
+
+
+static int setup_key_dl_desc(struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **kd, int alloc_flag)
+{
+ struct cryptocop_dma_desc *key_desc = alloc_cdesc(alloc_flag);
+ struct strcop_meta_out mo = {0};
+
+ DEBUG(printk("setup_key_dl_desc\n"));
+
+ if (!key_desc) {
+ DEBUG_API(printk("setup_key_dl_desc: failed descriptor allocation.\n"));
+ return -ENOMEM;
+ }
+
+ /* Download key. */
+ if ((tc->tctx->init.alg == cryptocop_alg_aes) && (tc->tcfg->flags & CRYPTOCOP_DECRYPT)) {
+ /* Precook the AES decrypt key. */
+ if (!tc->tctx->dec_key_set){
+ get_aes_decrypt_key(tc->tctx->dec_key, tc->tctx->init.key, tc->tctx->init.keylen);
+ tc->tctx->dec_key_set = 1;
+ }
+ key_desc->dma_descr->buf = (char*)virt_to_phys(tc->tctx->dec_key);
+ key_desc->dma_descr->after = key_desc->dma_descr->buf + tc->tctx->init.keylen/8;
+ } else {
+ key_desc->dma_descr->buf = (char*)virt_to_phys(tc->tctx->init.key);
+ key_desc->dma_descr->after = key_desc->dma_descr->buf + tc->tctx->init.keylen/8;
+ }
+ /* Setup metadata. */
+ mo.dlkey = 1;
+ switch (tc->tctx->init.keylen) {
+ case 64:
+ mo.decrypt = 0;
+ mo.hashmode = 0;
+ break;
+ case 128:
+ mo.decrypt = 0;
+ mo.hashmode = 1;
+ break;
+ case 192:
+ mo.decrypt = 1;
+ mo.hashmode = 0;
+ break;
+ case 256:
+ mo.decrypt = 1;
+ mo.hashmode = 1;
+ break;
+ default:
+ break;
+ }
+ mo.ciphsel = mo.hashsel = mo.csumsel = src_none;
+ key_desc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, mo);
+
+ key_desc->dma_descr->out_eop = 1;
+ key_desc->dma_descr->wait = 1;
+ key_desc->dma_descr->intr = 0;
+
+ *kd = key_desc;
+ return 0;
+}
+
+static int setup_cipher_iv_desc(struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **id, int alloc_flag)
+{
+ struct cryptocop_dma_desc *iv_desc = alloc_cdesc(alloc_flag);
+ struct strcop_meta_out mo = {0};
+
+ DEBUG(printk("setup_cipher_iv_desc\n"));
+
+ if (!iv_desc) {
+ DEBUG_API(printk("setup_cipher_iv_desc: failed CBC IV descriptor allocation.\n"));
+ return -ENOMEM;
+ }
+ /* Download IV. */
+ iv_desc->dma_descr->buf = (char*)virt_to_phys(tc->tcfg->iv);
+ iv_desc->dma_descr->after = iv_desc->dma_descr->buf + tc->blocklength;
+
+ /* Setup metadata. */
+ mo.hashsel = mo.csumsel = src_none;
+ mo.ciphsel = src_dma;
+ mo.ciphconf = tc->ciph_conf;
+ mo.cbcmode = tc->cbcmode;
+
+ iv_desc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, mo);
+
+ iv_desc->dma_descr->out_eop = 0;
+ iv_desc->dma_descr->wait = 1;
+ iv_desc->dma_descr->intr = 0;
+
+ *id = iv_desc;
+ return 0;
+}
+
+/* Map the ouput length of the transform to operation output starting on the inject index. */
+static int create_input_descriptors(struct cryptocop_operation *operation, struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **id, int alloc_flag)
+{
+ int err = 0;
+ struct cryptocop_dma_desc head = {0};
+ struct cryptocop_dma_desc *outdesc = &head;
+ size_t iov_offset = 0;
+ size_t out_ix = 0;
+ int outiov_ix = 0;
+ struct strcop_meta_in mi = {0};
+
+ size_t out_length = tc->produced;
+ int rem_length;
+ int dlength;
+
+ assert(out_length != 0);
+ if (((tc->produced + tc->tcfg->inject_ix) > operation->tfrm_op.outlen) || (tc->produced && (operation->tfrm_op.outlen == 0))) {
+ DEBUG_API(printk("create_input_descriptors: operation outdata too small\n"));
+ return -EINVAL;
+ }
+ /* Traverse the out iovec until the result inject index is reached. */
+ while ((outiov_ix < operation->tfrm_op.outcount) && ((out_ix + operation->tfrm_op.outdata[outiov_ix].iov_len) <= tc->tcfg->inject_ix)){
+ out_ix += operation->tfrm_op.outdata[outiov_ix].iov_len;
+ outiov_ix++;
+ }
+ if (outiov_ix >= operation->tfrm_op.outcount){
+ DEBUG_API(printk("create_input_descriptors: operation outdata too small\n"));
+ return -EINVAL;
+ }
+ iov_offset = tc->tcfg->inject_ix - out_ix;
+ mi.dmasel = tc->unit_no;
+
+ /* Setup the output descriptors. */
+ while ((out_length > 0) && (outiov_ix < operation->tfrm_op.outcount)) {
+ outdesc->next = alloc_cdesc(alloc_flag);
+ if (!outdesc->next) {
+ DEBUG_API(printk("create_input_descriptors: alloc_cdesc\n"));
+ err = -ENOMEM;
+ goto error_cleanup;
+ }
+ outdesc = outdesc->next;
+ rem_length = operation->tfrm_op.outdata[outiov_ix].iov_len - iov_offset;
+ dlength = (out_length < rem_length) ? out_length : rem_length;
+
+ DEBUG(printk("create_input_descriptors:\n"
+ "outiov_ix=%d, rem_length=%d, dlength=%d\n"
+ "iov_offset=%d, outdata[outiov_ix].iov_len=%d\n"
+ "outcount=%d, outiov_ix=%d\n",
+ outiov_ix, rem_length, dlength, iov_offset, operation->tfrm_op.outdata[outiov_ix].iov_len, operation->tfrm_op.outcount, outiov_ix));
+
+ outdesc->dma_descr->buf = (char*)virt_to_phys(operation->tfrm_op.outdata[outiov_ix].iov_base + iov_offset);
+ outdesc->dma_descr->after = outdesc->dma_descr->buf + dlength;
+ outdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi);
+
+ out_length -= dlength;
+ iov_offset += dlength;
+ if (iov_offset >= operation->tfrm_op.outdata[outiov_ix].iov_len) {
+ iov_offset = 0;
+ ++outiov_ix;
+ }
+ }
+ if (out_length > 0){
+ DEBUG_API(printk("create_input_descriptors: not enough room for output, %d remained\n", out_length));
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+ /* Set sync in last descriptor. */
+ mi.sync = 1;
+ outdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi);
+
+ *id = head.next;
+ return 0;
+
+ error_cleanup:
+ while (head.next) {
+ outdesc = head.next->next;
+ free_cdesc(head.next);
+ head.next = outdesc;
+ }
+ return err;
+}
+
+
+static int create_output_descriptors(struct cryptocop_operation *operation, int *iniov_ix, int *iniov_offset, size_t desc_len, struct cryptocop_dma_desc **current_out_cdesc, struct strcop_meta_out *meta_out, int alloc_flag)
+{
+ while (desc_len != 0) {
+ struct cryptocop_dma_desc *cdesc;
+ int rem_length = operation->tfrm_op.indata[*iniov_ix].iov_len - *iniov_offset;
+ int dlength = (desc_len < rem_length) ? desc_len : rem_length;
+
+ cdesc = alloc_cdesc(alloc_flag);
+ if (!cdesc) {
+ DEBUG_API(printk("create_output_descriptors: alloc_cdesc\n"));
+ return -ENOMEM;
+ }
+ (*current_out_cdesc)->next = cdesc;
+ (*current_out_cdesc) = cdesc;
+
+ cdesc->free_buf = NULL;
+
+ cdesc->dma_descr->buf = (char*)virt_to_phys(operation->tfrm_op.indata[*iniov_ix].iov_base + *iniov_offset);
+ cdesc->dma_descr->after = cdesc->dma_descr->buf + dlength;
+
+ desc_len -= dlength;
+ *iniov_offset += dlength;
+ assert(desc_len >= 0);
+ if (*iniov_offset >= operation->tfrm_op.indata[*iniov_ix].iov_len) {
+ *iniov_offset = 0;
+ ++(*iniov_ix);
+ if (*iniov_ix > operation->tfrm_op.incount) {
+ DEBUG_API(printk("create_output_descriptors: not enough indata in operation."));
+ return -EINVAL;
+ }
+ }
+ cdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, (*meta_out));
+ } /* while (desc_len != 0) */
+ /* Last DMA descriptor gets a 'wait' bit to signal expected change in metadata. */
+ (*current_out_cdesc)->dma_descr->wait = 1; /* This will set extraneous WAIT in some situations, e.g. when padding hashes and checksums. */
+
+ return 0;
+}
+
+
+static int append_input_descriptors(struct cryptocop_operation *operation, struct cryptocop_dma_desc **current_in_cdesc, struct cryptocop_dma_desc **current_out_cdesc, struct cryptocop_tfrm_ctx *tc, int alloc_flag)
+{
+ DEBUG(printk("append_input_descriptors, tc=0x%p, unit_no=%d\n", tc, tc->unit_no));
+ if (tc->tcfg) {
+ int failed = 0;
+ struct cryptocop_dma_desc *idescs = NULL;
+ DEBUG(printk("append_input_descriptors: pushing output, consumed %d produced %d bytes.\n", tc->consumed, tc->produced));
+ if (tc->pad_descs) {
+ DEBUG(printk("append_input_descriptors: append pad descriptors to DMA out list.\n"));
+ while (tc->pad_descs) {
+ DEBUG(printk("append descriptor 0x%p\n", tc->pad_descs));
+ (*current_out_cdesc)->next = tc->pad_descs;
+ tc->pad_descs = tc->pad_descs->next;
+ (*current_out_cdesc) = (*current_out_cdesc)->next;
+ }
+ }
+
+ /* Setup and append output descriptors to DMA in list. */
+ if (tc->unit_no == src_dma){
+ /* mem2mem. Setup DMA in descriptors to discard all input prior to the requested mem2mem data. */
+ struct strcop_meta_in mi = {.sync = 0, .dmasel = src_dma};
+ unsigned int start_ix = tc->start_ix;
+ while (start_ix){
+ unsigned int desclen = start_ix < MEM2MEM_DISCARD_BUF_LENGTH ? start_ix : MEM2MEM_DISCARD_BUF_LENGTH;
+ (*current_in_cdesc)->next = alloc_cdesc(alloc_flag);
+ if (!(*current_in_cdesc)->next){
+ DEBUG_API(printk("append_input_descriptors: alloc_cdesc mem2mem discard failed\n"));
+ return -ENOMEM;
+ }
+ (*current_in_cdesc) = (*current_in_cdesc)->next;
+ (*current_in_cdesc)->dma_descr->buf = (char*)virt_to_phys(mem2mem_discard_buf);
+ (*current_in_cdesc)->dma_descr->after = (*current_in_cdesc)->dma_descr->buf + desclen;
+ (*current_in_cdesc)->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi);
+ start_ix -= desclen;
+ }
+ mi.sync = 1;
+ (*current_in_cdesc)->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi);
+ }
+
+ failed = create_input_descriptors(operation, tc, &idescs, alloc_flag);
+ if (failed){
+ DEBUG_API(printk("append_input_descriptors: output descriptor setup failed\n"));
+ return failed;
+ }
+ DEBUG(printk("append_input_descriptors: append output descriptors to DMA in list.\n"));
+ while (idescs) {
+ DEBUG(printk("append descriptor 0x%p\n", idescs));
+ (*current_in_cdesc)->next = idescs;
+ idescs = idescs->next;
+ (*current_in_cdesc) = (*current_in_cdesc)->next;
+ }
+ }
+ return 0;
+}
+
+
+
+static int cryptocop_setup_dma_list(struct cryptocop_operation *operation, struct cryptocop_int_operation **int_op, int alloc_flag)
+{
+ struct cryptocop_session *sess;
+ struct cryptocop_transform_ctx *tctx;
+
+ struct cryptocop_tfrm_ctx digest_ctx = {
+ .previous_src = src_none,
+ .current_src = src_none,
+ .start_ix = 0,
+ .requires_padding = 1,
+ .strict_block_length = 0,
+ .hash_conf = 0,
+ .hash_mode = 0,
+ .ciph_conf = 0,
+ .cbcmode = 0,
+ .decrypt = 0,
+ .consumed = 0,
+ .produced = 0,
+ .pad_descs = NULL,
+ .active = 0,
+ .done = 0,
+ .prev_src = NULL,
+ .curr_src = NULL,
+ .tcfg = NULL};
+ struct cryptocop_tfrm_ctx cipher_ctx = {
+ .previous_src = src_none,
+ .current_src = src_none,
+ .start_ix = 0,
+ .requires_padding = 0,
+ .strict_block_length = 1,
+ .hash_conf = 0,
+ .hash_mode = 0,
+ .ciph_conf = 0,
+ .cbcmode = 0,
+ .decrypt = 0,
+ .consumed = 0,
+ .produced = 0,
+ .pad_descs = NULL,
+ .active = 0,
+ .done = 0,
+ .prev_src = NULL,
+ .curr_src = NULL,
+ .tcfg = NULL};
+ struct cryptocop_tfrm_ctx csum_ctx = {
+ .previous_src = src_none,
+ .current_src = src_none,
+ .start_ix = 0,
+ .blocklength = 2,
+ .requires_padding = 1,
+ .strict_block_length = 0,
+ .hash_conf = 0,
+ .hash_mode = 0,
+ .ciph_conf = 0,
+ .cbcmode = 0,
+ .decrypt = 0,
+ .consumed = 0,
+ .produced = 0,
+ .pad_descs = NULL,
+ .active = 0,
+ .done = 0,
+ .tcfg = NULL,
+ .prev_src = NULL,
+ .curr_src = NULL,
+ .unit_no = src_csum};
+ struct cryptocop_tfrm_cfg *tcfg = operation->tfrm_op.tfrm_cfg;
+
+ unsigned int indata_ix = 0;
+
+ /* iovec accounting. */
+ int iniov_ix = 0;
+ int iniov_offset = 0;
+
+ /* Operation descriptor cfg traversal pointer. */
+ struct cryptocop_desc *odsc;
+
+ int failed = 0;
+ /* List heads for allocated descriptors. */
+ struct cryptocop_dma_desc out_cdesc_head = {0};
+ struct cryptocop_dma_desc in_cdesc_head = {0};
+
+ struct cryptocop_dma_desc *current_out_cdesc = &out_cdesc_head;
+ struct cryptocop_dma_desc *current_in_cdesc = &in_cdesc_head;
+
+ struct cryptocop_tfrm_ctx *output_tc = NULL;
+ void *iop_alloc_ptr;
+
+ assert(operation != NULL);
+ assert(int_op != NULL);
+
+ DEBUG(printk("cryptocop_setup_dma_list: start\n"));
+ DEBUG(print_cryptocop_operation(operation));
+
+ sess = get_session(operation->sid);
+ if (!sess) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: no session found for operation.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ iop_alloc_ptr = kmalloc(DESCR_ALLOC_PAD + sizeof(struct cryptocop_int_operation), alloc_flag);
+ if (!iop_alloc_ptr) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: kmalloc cryptocop_int_operation\n"));
+ failed = -ENOMEM;
+ goto error_cleanup;
+ }
+ (*int_op) = (struct cryptocop_int_operation*)(((unsigned long int)(iop_alloc_ptr + DESCR_ALLOC_PAD + offsetof(struct cryptocop_int_operation, ctx_out)) & ~0x0000001F) - offsetof(struct cryptocop_int_operation, ctx_out));
+ DEBUG(memset((*int_op), 0xff, sizeof(struct cryptocop_int_operation)));
+ (*int_op)->alloc_ptr = iop_alloc_ptr;
+ DEBUG(printk("cryptocop_setup_dma_list: *int_op=0x%p, alloc_ptr=0x%p\n", *int_op, (*int_op)->alloc_ptr));
+
+ (*int_op)->sid = operation->sid;
+ (*int_op)->cdesc_out = NULL;
+ (*int_op)->cdesc_in = NULL;
+ (*int_op)->tdes_mode = cryptocop_3des_ede;
+ (*int_op)->csum_mode = cryptocop_csum_le;
+ (*int_op)->ddesc_out = NULL;
+ (*int_op)->ddesc_in = NULL;
+
+ /* Scan operation->tfrm_op.tfrm_cfg for bad configuration and set up the local contexts. */
+ if (!tcfg) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: no configured transforms in operation.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ while (tcfg) {
+ tctx = get_transform_ctx(sess, tcfg->tid);
+ if (!tctx) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: no transform id %d in session.\n", tcfg->tid));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (tcfg->inject_ix > operation->tfrm_op.outlen){
+ DEBUG_API(printk("cryptocop_setup_dma_list: transform id %d inject_ix (%d) > operation->tfrm_op.outlen(%d)", tcfg->tid, tcfg->inject_ix, operation->tfrm_op.outlen));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ switch (tctx->init.alg){
+ case cryptocop_alg_mem2mem:
+ if (cipher_ctx.tcfg != NULL){
+ DEBUG_API(printk("cryptocop_setup_dma_list: multiple ciphers in operation.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ /* mem2mem is handled as a NULL cipher. */
+ cipher_ctx.cbcmode = 0;
+ cipher_ctx.decrypt = 0;
+ cipher_ctx.blocklength = 1;
+ cipher_ctx.ciph_conf = 0;
+ cipher_ctx.unit_no = src_dma;
+ cipher_ctx.tcfg = tcfg;
+ cipher_ctx.tctx = tctx;
+ break;
+ case cryptocop_alg_des:
+ case cryptocop_alg_3des:
+ case cryptocop_alg_aes:
+ /* cipher */
+ if (cipher_ctx.tcfg != NULL){
+ DEBUG_API(printk("cryptocop_setup_dma_list: multiple ciphers in operation.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ cipher_ctx.tcfg = tcfg;
+ cipher_ctx.tctx = tctx;
+ if (cipher_ctx.tcfg->flags & CRYPTOCOP_DECRYPT){
+ cipher_ctx.decrypt = 1;
+ }
+ switch (tctx->init.cipher_mode) {
+ case cryptocop_cipher_mode_ecb:
+ cipher_ctx.cbcmode = 0;
+ break;
+ case cryptocop_cipher_mode_cbc:
+ cipher_ctx.cbcmode = 1;
+ break;
+ default:
+ DEBUG_API(printk("cryptocop_setup_dma_list: cipher_ctx, bad cipher mode==%d\n", tctx->init.cipher_mode));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ DEBUG(printk("cryptocop_setup_dma_list: cipher_ctx, set CBC mode==%d\n", cipher_ctx.cbcmode));
+ switch (tctx->init.alg){
+ case cryptocop_alg_des:
+ cipher_ctx.ciph_conf = 0;
+ cipher_ctx.unit_no = src_des;
+ cipher_ctx.blocklength = DES_BLOCK_LENGTH;
+ break;
+ case cryptocop_alg_3des:
+ cipher_ctx.ciph_conf = 1;
+ cipher_ctx.unit_no = src_des;
+ cipher_ctx.blocklength = DES_BLOCK_LENGTH;
+ break;
+ case cryptocop_alg_aes:
+ cipher_ctx.ciph_conf = 2;
+ cipher_ctx.unit_no = src_aes;
+ cipher_ctx.blocklength = AES_BLOCK_LENGTH;
+ break;
+ default:
+ panic("cryptocop_setup_dma_list: impossible algorithm %d\n", tctx->init.alg);
+ }
+ (*int_op)->tdes_mode = tctx->init.tdes_mode;
+ break;
+ case cryptocop_alg_md5:
+ case cryptocop_alg_sha1:
+ /* digest */
+ if (digest_ctx.tcfg != NULL){
+ DEBUG_API(printk("cryptocop_setup_dma_list: multiple digests in operation.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ digest_ctx.tcfg = tcfg;
+ digest_ctx.tctx = tctx;
+ digest_ctx.hash_mode = 0; /* Don't use explicit IV in this API. */
+ switch (tctx->init.alg){
+ case cryptocop_alg_md5:
+ digest_ctx.blocklength = MD5_BLOCK_LENGTH;
+ digest_ctx.unit_no = src_md5;
+ digest_ctx.hash_conf = 1; /* 1 => MD-5 */
+ break;
+ case cryptocop_alg_sha1:
+ digest_ctx.blocklength = SHA1_BLOCK_LENGTH;
+ digest_ctx.unit_no = src_sha1;
+ digest_ctx.hash_conf = 0; /* 0 => SHA-1 */
+ break;
+ default:
+ panic("cryptocop_setup_dma_list: impossible digest algorithm\n");
+ }
+ break;
+ case cryptocop_alg_csum:
+ /* digest */
+ if (csum_ctx.tcfg != NULL){
+ DEBUG_API(printk("cryptocop_setup_dma_list: multiple checksums in operation.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ (*int_op)->csum_mode = tctx->init.csum_mode;
+ csum_ctx.tcfg = tcfg;
+ csum_ctx.tctx = tctx;
+ break;
+ default:
+ /* no algorithm. */
+ DEBUG_API(printk("cryptocop_setup_dma_list: invalid algorithm %d specified in tfrm %d.\n", tctx->init.alg, tcfg->tid));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ tcfg = tcfg->next;
+ }
+ /* Download key if a cipher is used. */
+ if (cipher_ctx.tcfg && (cipher_ctx.tctx->init.alg != cryptocop_alg_mem2mem)){
+ struct cryptocop_dma_desc *key_desc = NULL;
+
+ failed = setup_key_dl_desc(&cipher_ctx, &key_desc, alloc_flag);
+ if (failed) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: setup key dl\n"));
+ goto error_cleanup;
+ }
+ current_out_cdesc->next = key_desc;
+ current_out_cdesc = key_desc;
+ indata_ix += (unsigned int)(key_desc->dma_descr->after - key_desc->dma_descr->buf);
+
+ /* Download explicit IV if a cipher is used and CBC mode and explicit IV selected. */
+ if ((cipher_ctx.tctx->init.cipher_mode == cryptocop_cipher_mode_cbc) && (cipher_ctx.tcfg->flags & CRYPTOCOP_EXPLICIT_IV)) {
+ struct cryptocop_dma_desc *iv_desc = NULL;
+
+ DEBUG(printk("cryptocop_setup_dma_list: setup cipher CBC IV descriptor.\n"));
+
+ failed = setup_cipher_iv_desc(&cipher_ctx, &iv_desc, alloc_flag);
+ if (failed) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: CBC IV descriptor.\n"));
+ goto error_cleanup;
+ }
+ current_out_cdesc->next = iv_desc;
+ current_out_cdesc = iv_desc;
+ indata_ix += (unsigned int)(iv_desc->dma_descr->after - iv_desc->dma_descr->buf);
+ }
+ }
+
+ /* Process descriptors. */
+ odsc = operation->tfrm_op.desc;
+ while (odsc) {
+ struct cryptocop_desc_cfg *dcfg = odsc->cfg;
+ struct strcop_meta_out meta_out = {0};
+ size_t desc_len = odsc->length;
+ int active_count, eop_needed_count;
+
+ output_tc = NULL;
+
+ DEBUG(printk("cryptocop_setup_dma_list: parsing an operation descriptor\n"));
+
+ while (dcfg) {
+ struct cryptocop_tfrm_ctx *tc = NULL;
+
+ DEBUG(printk("cryptocop_setup_dma_list: parsing an operation descriptor configuration.\n"));
+ /* Get the local context for the transform and mark it as the output unit if it produces output. */
+ if (digest_ctx.tcfg && (digest_ctx.tcfg->tid == dcfg->tid)){
+ tc = &digest_ctx;
+ } else if (cipher_ctx.tcfg && (cipher_ctx.tcfg->tid == dcfg->tid)){
+ tc = &cipher_ctx;
+ } else if (csum_ctx.tcfg && (csum_ctx.tcfg->tid == dcfg->tid)){
+ tc = &csum_ctx;
+ }
+ if (!tc) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: invalid transform %d specified in descriptor.\n", dcfg->tid));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (tc->done) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: completed transform %d reused.\n", dcfg->tid));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (!tc->active) {
+ tc->start_ix = indata_ix;
+ tc->active = 1;
+ }
+
+ tc->previous_src = tc->current_src;
+ tc->prev_src = tc->curr_src;
+ /* Map source unit id to DMA source config. */
+ switch (dcfg->src){
+ case cryptocop_source_dma:
+ tc->current_src = src_dma;
+ break;
+ case cryptocop_source_des:
+ tc->current_src = src_des;
+ break;
+ case cryptocop_source_3des:
+ tc->current_src = src_des;
+ break;
+ case cryptocop_source_aes:
+ tc->current_src = src_aes;
+ break;
+ case cryptocop_source_md5:
+ case cryptocop_source_sha1:
+ case cryptocop_source_csum:
+ case cryptocop_source_none:
+ default:
+ /* We do not allow using accumulating style units (SHA-1, MD5, checksum) as sources to other units.
+ */
+ DEBUG_API(printk("cryptocop_setup_dma_list: bad unit source configured %d.\n", dcfg->src));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (tc->current_src != src_dma) {
+ /* Find the unit we are sourcing from. */
+ if (digest_ctx.unit_no == tc->current_src){
+ tc->curr_src = &digest_ctx;
+ } else if (cipher_ctx.unit_no == tc->current_src){
+ tc->curr_src = &cipher_ctx;
+ } else if (csum_ctx.unit_no == tc->current_src){
+ tc->curr_src = &csum_ctx;
+ }
+ if ((tc->curr_src == tc) && (tc->unit_no != src_dma)){
+ DEBUG_API(printk("cryptocop_setup_dma_list: unit %d configured to source from itself.\n", tc->unit_no));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ } else {
+ tc->curr_src = NULL;
+ }
+
+ /* Detect source switch. */
+ DEBUG(printk("cryptocop_setup_dma_list: tc->active=%d tc->unit_no=%d tc->current_src=%d tc->previous_src=%d, tc->curr_src=0x%p, tc->prev_srv=0x%p\n", tc->active, tc->unit_no, tc->current_src, tc->previous_src, tc->curr_src, tc->prev_src));
+ if (tc->active && (tc->current_src != tc->previous_src)) {
+ /* Only allow source switch when both the old source unit and the new one have
+ * no pending data to process (i.e. the consumed length must be a multiple of the
+ * transform blocklength). */
+ /* Note: if the src == NULL we are actually sourcing from DMA out. */
+ if (((tc->prev_src != NULL) && (tc->prev_src->consumed % tc->prev_src->blocklength)) ||
+ ((tc->curr_src != NULL) && (tc->curr_src->consumed % tc->curr_src->blocklength)))
+ {
+ DEBUG_API(printk("cryptocop_setup_dma_list: can only disconnect from or connect to a unit on a multiple of the blocklength, old: cons=%d, prod=%d, block=%d, new: cons=%d prod=%d, block=%d.\n", tc->prev_src ? tc->prev_src->consumed : INT_MIN, tc->prev_src ? tc->prev_src->produced : INT_MIN, tc->prev_src ? tc->prev_src->blocklength : INT_MIN, tc->curr_src ? tc->curr_src->consumed : INT_MIN, tc->curr_src ? tc->curr_src->produced : INT_MIN, tc->curr_src ? tc->curr_src->blocklength : INT_MIN));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ }
+ /* Detect unit deactivation. */
+ if (dcfg->last) {
+ /* Length check of this is handled below. */
+ tc->done = 1;
+ }
+ dcfg = dcfg->next;
+ } /* while (dcfg) */
+ DEBUG(printk("cryptocop_setup_dma_list: parsing operation descriptor configuration complete.\n"));
+
+ if (cipher_ctx.active && (cipher_ctx.curr_src != NULL) && !cipher_ctx.curr_src->active){
+ DEBUG_API(printk("cryptocop_setup_dma_list: cipher source from inactive unit %d\n", cipher_ctx.curr_src->unit_no));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (digest_ctx.active && (digest_ctx.curr_src != NULL) && !digest_ctx.curr_src->active){
+ DEBUG_API(printk("cryptocop_setup_dma_list: digest source from inactive unit %d\n", digest_ctx.curr_src->unit_no));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (csum_ctx.active && (csum_ctx.curr_src != NULL) && !csum_ctx.curr_src->active){
+ DEBUG_API(printk("cryptocop_setup_dma_list: cipher source from inactive unit %d\n", csum_ctx.curr_src->unit_no));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+
+ /* Update consumed and produced lengths.
+
+ The consumed length accounting here is actually cheating. If a unit source from DMA (or any
+ other unit that process data in blocks of one octet) it is correct, but if it source from a
+ block processing unit, i.e. a cipher, it will be temporarily incorrect at some times. However
+ since it is only allowed--by the HW--to change source to or from a block processing unit at times where that
+ unit has processed an exact multiple of its block length the end result will be correct.
+ Beware that if the source change restriction change this code will need to be (much) reworked.
+ */
+ DEBUG(printk("cryptocop_setup_dma_list: desc->length=%d, desc_len=%d.\n", odsc->length, desc_len));
+
+ if (csum_ctx.active) {
+ csum_ctx.consumed += desc_len;
+ if (csum_ctx.done) {
+ csum_ctx.produced = 2;
+ }
+ DEBUG(printk("cryptocop_setup_dma_list: csum_ctx producing: consumed=%d, produced=%d, blocklength=%d.\n", csum_ctx.consumed, csum_ctx.produced, csum_ctx.blocklength));
+ }
+ if (digest_ctx.active) {
+ digest_ctx.consumed += desc_len;
+ if (digest_ctx.done) {
+ if (digest_ctx.unit_no == src_md5) {
+ digest_ctx.produced = MD5_STATE_LENGTH;
+ } else {
+ digest_ctx.produced = SHA1_STATE_LENGTH;
+ }
+ }
+ DEBUG(printk("cryptocop_setup_dma_list: digest_ctx producing: consumed=%d, produced=%d, blocklength=%d.\n", digest_ctx.consumed, digest_ctx.produced, digest_ctx.blocklength));
+ }
+ if (cipher_ctx.active) {
+ /* Ciphers are allowed only to source from DMA out. That is filtered above. */
+ assert(cipher_ctx.current_src == src_dma);
+ cipher_ctx.consumed += desc_len;
+ cipher_ctx.produced = cipher_ctx.blocklength * (cipher_ctx.consumed / cipher_ctx.blocklength);
+ if (cipher_ctx.cbcmode && !(cipher_ctx.tcfg->flags & CRYPTOCOP_EXPLICIT_IV) && cipher_ctx.produced){
+ cipher_ctx.produced -= cipher_ctx.blocklength; /* Compensate for CBC iv. */
+ }
+ DEBUG(printk("cryptocop_setup_dma_list: cipher_ctx producing: consumed=%d, produced=%d, blocklength=%d.\n", cipher_ctx.consumed, cipher_ctx.produced, cipher_ctx.blocklength));
+ }
+
+ /* Setup the DMA out descriptors. */
+ /* Configure the metadata. */
+ active_count = 0;
+ eop_needed_count = 0;
+ if (cipher_ctx.active) {
+ ++active_count;
+ if (cipher_ctx.unit_no == src_dma){
+ /* mem2mem */
+ meta_out.ciphsel = src_none;
+ } else {
+ meta_out.ciphsel = cipher_ctx.current_src;
+ }
+ meta_out.ciphconf = cipher_ctx.ciph_conf;
+ meta_out.cbcmode = cipher_ctx.cbcmode;
+ meta_out.decrypt = cipher_ctx.decrypt;
+ DEBUG(printk("set ciphsel=%d ciphconf=%d cbcmode=%d decrypt=%d\n", meta_out.ciphsel, meta_out.ciphconf, meta_out.cbcmode, meta_out.decrypt));
+ if (cipher_ctx.done) ++eop_needed_count;
+ } else {
+ meta_out.ciphsel = src_none;
+ }
+
+ if (digest_ctx.active) {
+ ++active_count;
+ meta_out.hashsel = digest_ctx.current_src;
+ meta_out.hashconf = digest_ctx.hash_conf;
+ meta_out.hashmode = 0; /* Explicit mode is not used here. */
+ DEBUG(printk("set hashsel=%d hashconf=%d hashmode=%d\n", meta_out.hashsel, meta_out.hashconf, meta_out.hashmode));
+ if (digest_ctx.done) {
+ assert(digest_ctx.pad_descs == NULL);
+ failed = create_pad_descriptor(&digest_ctx, &digest_ctx.pad_descs, alloc_flag);
+ if (failed) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: failed digest pad creation.\n"));
+ goto error_cleanup;
+ }
+ }
+ } else {
+ meta_out.hashsel = src_none;
+ }
+
+ if (csum_ctx.active) {
+ ++active_count;
+ meta_out.csumsel = csum_ctx.current_src;
+ if (csum_ctx.done) {
+ assert(csum_ctx.pad_descs == NULL);
+ failed = create_pad_descriptor(&csum_ctx, &csum_ctx.pad_descs, alloc_flag);
+ if (failed) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: failed csum pad creation.\n"));
+ goto error_cleanup;
+ }
+ }
+ } else {
+ meta_out.csumsel = src_none;
+ }
+ DEBUG(printk("cryptocop_setup_dma_list: %d eop needed, %d active units\n", eop_needed_count, active_count));
+ /* Setup DMA out descriptors for the indata. */
+ failed = create_output_descriptors(operation, &iniov_ix, &iniov_offset, desc_len, &current_out_cdesc, &meta_out, alloc_flag);
+ if (failed) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: create_output_descriptors %d\n", failed));
+ goto error_cleanup;
+ }
+ /* Setup out EOP. If there are active units that are not done here they cannot get an EOP
+ * so we ust setup a zero length descriptor to DMA to signal EOP only to done units.
+ * If there is a pad descriptor EOP for the padded unit will be EOPed by it.
+ */
+ assert(active_count >= eop_needed_count);
+ assert((eop_needed_count == 0) || (eop_needed_count == 1));
+ if (eop_needed_count) {
+ /* This means that the bulk operation (cipeher/m2m) is terminated. */
+ if (active_count > 1) {
+ /* Use zero length EOP descriptor. */
+ struct cryptocop_dma_desc *ed = alloc_cdesc(alloc_flag);
+ struct strcop_meta_out ed_mo = {0};
+ if (!ed) {
+ DEBUG_API(printk("cryptocop_setup_dma_list: alloc EOP descriptor for cipher\n"));
+ failed = -ENOMEM;
+ goto error_cleanup;
+ }
+
+ assert(cipher_ctx.active && cipher_ctx.done);
+
+ if (cipher_ctx.unit_no == src_dma){
+ /* mem2mem */
+ ed_mo.ciphsel = src_none;
+ } else {
+ ed_mo.ciphsel = cipher_ctx.current_src;
+ }
+ ed_mo.ciphconf = cipher_ctx.ciph_conf;
+ ed_mo.cbcmode = cipher_ctx.cbcmode;
+ ed_mo.decrypt = cipher_ctx.decrypt;
+
+ ed->free_buf = NULL;
+ ed->dma_descr->wait = 1;
+ ed->dma_descr->out_eop = 1;
+
+ ed->dma_descr->buf = (char*)virt_to_phys(&ed); /* Use any valid physical address for zero length descriptor. */
+ ed->dma_descr->after = ed->dma_descr->buf;
+ ed->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, ed_mo);
+ current_out_cdesc->next = ed;
+ current_out_cdesc = ed;
+ } else {
+ /* Set EOP in the current out descriptor since the only active module is
+ * the one needing the EOP. */
+
+ current_out_cdesc->dma_descr->out_eop = 1;
+ }
+ }
+
+ if (cipher_ctx.done && cipher_ctx.active) cipher_ctx.active = 0;
+ if (digest_ctx.done && digest_ctx.active) digest_ctx.active = 0;
+ if (csum_ctx.done && csum_ctx.active) csum_ctx.active = 0;
+ indata_ix += odsc->length;
+ odsc = odsc->next;
+ } /* while (odsc) */ /* Process descriptors. */
+ DEBUG(printk("cryptocop_setup_dma_list: done parsing operation descriptors\n"));
+ if (cipher_ctx.tcfg && (cipher_ctx.active || !cipher_ctx.done)){
+ DEBUG_API(printk("cryptocop_setup_dma_list: cipher operation not terminated.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (digest_ctx.tcfg && (digest_ctx.active || !digest_ctx.done)){
+ DEBUG_API(printk("cryptocop_setup_dma_list: digest operation not terminated.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+ if (csum_ctx.tcfg && (csum_ctx.active || !csum_ctx.done)){
+ DEBUG_API(printk("cryptocop_setup_dma_list: csum operation not terminated.\n"));
+ failed = -EINVAL;
+ goto error_cleanup;
+ }
+
+ failed = append_input_descriptors(operation, &current_in_cdesc, &current_out_cdesc, &cipher_ctx, alloc_flag);
+ if (failed){
+ DEBUG_API(printk("cryptocop_setup_dma_list: append_input_descriptors cipher_ctx %d\n", failed));
+ goto error_cleanup;
+ }
+ failed = append_input_descriptors(operation, &current_in_cdesc, &current_out_cdesc, &digest_ctx, alloc_flag);
+ if (failed){
+ DEBUG_API(printk("cryptocop_setup_dma_list: append_input_descriptors cipher_ctx %d\n", failed));
+ goto error_cleanup;
+ }
+ failed = append_input_descriptors(operation, &current_in_cdesc, &current_out_cdesc, &csum_ctx, alloc_flag);
+ if (failed){
+ DEBUG_API(printk("cryptocop_setup_dma_list: append_input_descriptors cipher_ctx %d\n", failed));
+ goto error_cleanup;
+ }
+
+ DEBUG(printk("cryptocop_setup_dma_list: int_op=0x%p, *int_op=0x%p\n", int_op, *int_op));
+ (*int_op)->cdesc_out = out_cdesc_head.next;
+ (*int_op)->cdesc_in = in_cdesc_head.next;
+ DEBUG(printk("cryptocop_setup_dma_list: out_cdesc_head=0x%p in_cdesc_head=0x%p\n", (*int_op)->cdesc_out, (*int_op)->cdesc_in));
+
+ setup_descr_chain(out_cdesc_head.next);
+ setup_descr_chain(in_cdesc_head.next);
+
+ /* Last but not least: mark the last DMA in descriptor for a INTR and EOL and the the
+ * last DMA out descriptor for EOL.
+ */
+ current_in_cdesc->dma_descr->intr = 1;
+ current_in_cdesc->dma_descr->eol = 1;
+ current_out_cdesc->dma_descr->eol = 1;
+
+ /* Setup DMA contexts. */
+ (*int_op)->ctx_out.next = NULL;
+ (*int_op)->ctx_out.eol = 1;
+ (*int_op)->ctx_out.intr = 0;
+ (*int_op)->ctx_out.store_mode = 0;
+ (*int_op)->ctx_out.en = 0;
+ (*int_op)->ctx_out.dis = 0;
+ (*int_op)->ctx_out.md0 = 0;
+ (*int_op)->ctx_out.md1 = 0;
+ (*int_op)->ctx_out.md2 = 0;
+ (*int_op)->ctx_out.md3 = 0;
+ (*int_op)->ctx_out.md4 = 0;
+ (*int_op)->ctx_out.saved_data = (dma_descr_data*)virt_to_phys((*int_op)->cdesc_out->dma_descr);
+ (*int_op)->ctx_out.saved_data_buf = (*int_op)->cdesc_out->dma_descr->buf; /* Already physical address. */
+
+ (*int_op)->ctx_in.next = NULL;
+ (*int_op)->ctx_in.eol = 1;
+ (*int_op)->ctx_in.intr = 0;
+ (*int_op)->ctx_in.store_mode = 0;
+ (*int_op)->ctx_in.en = 0;
+ (*int_op)->ctx_in.dis = 0;
+ (*int_op)->ctx_in.md0 = 0;
+ (*int_op)->ctx_in.md1 = 0;
+ (*int_op)->ctx_in.md2 = 0;
+ (*int_op)->ctx_in.md3 = 0;
+ (*int_op)->ctx_in.md4 = 0;
+
+ (*int_op)->ctx_in.saved_data = (dma_descr_data*)virt_to_phys((*int_op)->cdesc_in->dma_descr);
+ (*int_op)->ctx_in.saved_data_buf = (*int_op)->cdesc_in->dma_descr->buf; /* Already physical address. */
+
+ DEBUG(printk("cryptocop_setup_dma_list: done\n"));
+ return 0;
+
+error_cleanup:
+ {
+ /* Free all allocated resources. */
+ struct cryptocop_dma_desc *tmp_cdesc;
+ while (digest_ctx.pad_descs){
+ tmp_cdesc = digest_ctx.pad_descs->next;
+ free_cdesc(digest_ctx.pad_descs);
+ digest_ctx.pad_descs = tmp_cdesc;
+ }
+ while (csum_ctx.pad_descs){
+ tmp_cdesc = csum_ctx.pad_descs->next;
+ free_cdesc(csum_ctx.pad_descs);
+ csum_ctx.pad_descs = tmp_cdesc;
+ }
+ assert(cipher_ctx.pad_descs == NULL); /* The ciphers are never padded. */
+
+ if (*int_op != NULL) delete_internal_operation(*int_op);
+ }
+ DEBUG_API(printk("cryptocop_setup_dma_list: done with error %d\n", failed));
+ return failed;
+}
+
+
+static void delete_internal_operation(struct cryptocop_int_operation *iop)
+{
+ void *ptr = iop->alloc_ptr;
+ struct cryptocop_dma_desc *cd = iop->cdesc_out;
+ struct cryptocop_dma_desc *next;
+
+ DEBUG(printk("delete_internal_operation: iop=0x%p, alloc_ptr=0x%p\n", iop, ptr));
+
+ while (cd) {
+ next = cd->next;
+ free_cdesc(cd);
+ cd = next;
+ }
+ cd = iop->cdesc_in;
+ while (cd) {
+ next = cd->next;
+ free_cdesc(cd);
+ cd = next;
+ }
+ kfree(ptr);
+}
+
+#define MD5_MIN_PAD_LENGTH (9)
+#define MD5_PAD_LENGTH_FIELD_LENGTH (8)
+
+static int create_md5_pad(int alloc_flag, unsigned long long hashed_length, char **pad, size_t *pad_length)
+{
+ size_t padlen = MD5_BLOCK_LENGTH - (hashed_length % MD5_BLOCK_LENGTH);
+ unsigned char *p;
+ int i;
+ unsigned long long int bit_length = hashed_length << 3;
+
+ if (padlen < MD5_MIN_PAD_LENGTH) padlen += MD5_BLOCK_LENGTH;
+
+ p = kmalloc(padlen, alloc_flag);
+ if (!pad) return -ENOMEM;
+
+ *p = 0x80;
+ memset(p+1, 0, padlen - 1);
+
+ DEBUG(printk("create_md5_pad: hashed_length=%lld bits == %lld bytes\n", bit_length, hashed_length));
+
+ i = padlen - MD5_PAD_LENGTH_FIELD_LENGTH;
+ while (bit_length != 0){
+ p[i++] = bit_length % 0x100;
+ bit_length >>= 8;
+ }
+
+ *pad = (char*)p;
+ *pad_length = padlen;
+
+ return 0;
+}
+
+#define SHA1_MIN_PAD_LENGTH (9)
+#define SHA1_PAD_LENGTH_FIELD_LENGTH (8)
+
+static int create_sha1_pad(int alloc_flag, unsigned long long hashed_length, char **pad, size_t *pad_length)
+{
+ size_t padlen = SHA1_BLOCK_LENGTH - (hashed_length % SHA1_BLOCK_LENGTH);
+ unsigned char *p;
+ int i;
+ unsigned long long int bit_length = hashed_length << 3;
+
+ if (padlen < SHA1_MIN_PAD_LENGTH) padlen += SHA1_BLOCK_LENGTH;
+
+ p = kmalloc(padlen, alloc_flag);
+ if (!pad) return -ENOMEM;
+
+ *p = 0x80;
+ memset(p+1, 0, padlen - 1);
+
+ DEBUG(printk("create_sha1_pad: hashed_length=%lld bits == %lld bytes\n", bit_length, hashed_length));
+
+ i = padlen - 1;
+ while (bit_length != 0){
+ p[i--] = bit_length % 0x100;
+ bit_length >>= 8;
+ }
+
+ *pad = (char*)p;
+ *pad_length = padlen;
+
+ return 0;
+}
+
+
+static int transform_ok(struct cryptocop_transform_init *tinit)
+{
+ switch (tinit->alg){
+ case cryptocop_alg_csum:
+ switch (tinit->csum_mode){
+ case cryptocop_csum_le:
+ case cryptocop_csum_be:
+ break;
+ default:
+ DEBUG_API(printk("transform_ok: Bad mode set for csum transform\n"));
+ return -EINVAL;
+ }
+ case cryptocop_alg_mem2mem:
+ case cryptocop_alg_md5:
+ case cryptocop_alg_sha1:
+ if (tinit->keylen != 0) {
+ DEBUG_API(printk("transform_ok: non-zero keylength, %d, for a digest/csum algorithm\n", tinit->keylen));
+ return -EINVAL; /* This check is a bit strict. */
+ }
+ break;
+ case cryptocop_alg_des:
+ if (tinit->keylen != 64) {
+ DEBUG_API(printk("transform_ok: keylen %d invalid for DES\n", tinit->keylen));
+ return -EINVAL;
+ }
+ break;
+ case cryptocop_alg_3des:
+ if (tinit->keylen != 192) {
+ DEBUG_API(printk("transform_ok: keylen %d invalid for 3DES\n", tinit->keylen));
+ return -EINVAL;
+ }
+ break;
+ case cryptocop_alg_aes:
+ if (tinit->keylen != 128 && tinit->keylen != 192 && tinit->keylen != 256) {
+ DEBUG_API(printk("transform_ok: keylen %d invalid for AES\n", tinit->keylen));
+ return -EINVAL;
+ }
+ break;
+ case cryptocop_no_alg:
+ default:
+ DEBUG_API(printk("transform_ok: no such algorithm %d\n", tinit->alg));
+ return -EINVAL;
+ }
+
+ switch (tinit->alg){
+ case cryptocop_alg_des:
+ case cryptocop_alg_3des:
+ case cryptocop_alg_aes:
+ if (tinit->cipher_mode != cryptocop_cipher_mode_ecb && tinit->cipher_mode != cryptocop_cipher_mode_cbc) return -EINVAL;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+int cryptocop_new_session(cryptocop_session_id *sid, struct cryptocop_transform_init *tinit, int alloc_flag)
+{
+ struct cryptocop_session *sess;
+ struct cryptocop_transform_init *tfrm_in = tinit;
+ struct cryptocop_transform_init *tmp_in;
+ int no_tfrms = 0;
+ int i;
+ unsigned long int flags;
+
+ init_stream_coprocessor(); /* For safety if we are called early */
+
+ while (tfrm_in){
+ int err;
+ ++no_tfrms;
+ if ((err = transform_ok(tfrm_in))) {
+ DEBUG_API(printk("cryptocop_new_session, bad transform\n"));
+ return err;
+ }
+ tfrm_in = tfrm_in->next;
+ }
+ if (0 == no_tfrms) {
+ DEBUG_API(printk("cryptocop_new_session, no transforms specified\n"));
+ return -EINVAL;
+ }
+
+ sess = kmalloc(sizeof(struct cryptocop_session), alloc_flag);
+ if (!sess){
+ DEBUG_API(printk("cryptocop_new_session, kmalloc cryptocop_session\n"));
+ return -ENOMEM;
+ }
+
+ sess->tfrm_ctx = kmalloc(no_tfrms * sizeof(struct cryptocop_transform_ctx), alloc_flag);
+ if (!sess->tfrm_ctx) {
+ DEBUG_API(printk("cryptocop_new_session, kmalloc cryptocop_transform_ctx\n"));
+ kfree(sess);
+ return -ENOMEM;
+ }
+
+ tfrm_in = tinit;
+ for (i = 0; i < no_tfrms; i++){
+ tmp_in = tfrm_in->next;
+ while (tmp_in){
+ if (tmp_in->tid == tfrm_in->tid) {
+ DEBUG_API(printk("cryptocop_new_session, duplicate transform ids\n"));
+ kfree(sess->tfrm_ctx);
+ kfree(sess);
+ return -EINVAL;
+ }
+ tmp_in = tmp_in->next;
+ }
+ memcpy(&sess->tfrm_ctx[i].init, tfrm_in, sizeof(struct cryptocop_transform_init));
+ sess->tfrm_ctx[i].dec_key_set = 0;
+ sess->tfrm_ctx[i].next = &sess->tfrm_ctx[i] + 1;
+
+ tfrm_in = tfrm_in->next;
+ }
+ sess->tfrm_ctx[i-1].next = NULL;
+
+ spin_lock_irqsave(&cryptocop_sessions_lock, flags);
+ sess->sid = next_sid;
+ next_sid++;
+ /* TODO If we are really paranoid we should do duplicate check to handle sid wraparound.
+ * OTOH 2^64 is a really large number of session. */
+ if (next_sid == 0) next_sid = 1;
+
+ /* Prepend to session list. */
+ sess->next = cryptocop_sessions;
+ cryptocop_sessions = sess;
+ spin_unlock_irqrestore(&cryptocop_sessions_lock, flags);
+ *sid = sess->sid;
+ return 0;
+}
+
+
+int cryptocop_free_session(cryptocop_session_id sid)
+{
+ struct cryptocop_transform_ctx *tc;
+ struct cryptocop_session *sess = NULL;
+ struct cryptocop_session *psess = NULL;
+ unsigned long int flags;
+ int i;
+ LIST_HEAD(remove_list);
+ struct list_head *node, *tmp;
+ struct cryptocop_prio_job *pj;
+
+ DEBUG(printk("cryptocop_free_session: sid=%lld\n", sid));
+
+ spin_lock_irqsave(&cryptocop_sessions_lock, flags);
+ sess = cryptocop_sessions;
+ while (sess && sess->sid != sid){
+ psess = sess;
+ sess = sess->next;
+ }
+ if (sess){
+ if (psess){
+ psess->next = sess->next;
+ } else {
+ cryptocop_sessions = sess->next;
+ }
+ }
+ spin_unlock_irqrestore(&cryptocop_sessions_lock, flags);
+
+ if (!sess) return -EINVAL;
+
+ /* Remove queued jobs. */
+ spin_lock_irqsave(&cryptocop_job_queue_lock, flags);
+
+ for (i = 0; i < cryptocop_prio_no_prios; i++){
+ if (!list_empty(&(cryptocop_job_queues[i].jobs))){
+ list_for_each_safe(node, tmp, &(cryptocop_job_queues[i].jobs)) {
+ pj = list_entry(node, struct cryptocop_prio_job, node);
+ if (pj->oper->sid == sid) {
+ list_move_tail(node, &remove_list);
+ }
+ }
+ }
+ }
+ spin_unlock_irqrestore(&cryptocop_job_queue_lock, flags);
+
+ list_for_each_safe(node, tmp, &remove_list) {
+ list_del(node);
+ pj = list_entry(node, struct cryptocop_prio_job, node);
+ pj->oper->operation_status = -EAGAIN; /* EAGAIN is not ideal for job/session terminated but it's the best choice I know of. */
+ DEBUG(printk("cryptocop_free_session: pj=0x%p, pj->oper=0x%p, pj->iop=0x%p\n", pj, pj->oper, pj->iop));
+ pj->oper->cb(pj->oper, pj->oper->cb_data);
+ delete_internal_operation(pj->iop);
+ kfree(pj);
+ }
+
+ tc = sess->tfrm_ctx;
+ /* Erase keying data. */
+ while (tc){
+ DEBUG(printk("cryptocop_free_session: memset keys, tfrm id=%d\n", tc->init.tid));
+ memset(tc->init.key, 0xff, CRYPTOCOP_MAX_KEY_LENGTH);
+ memset(tc->dec_key, 0xff, CRYPTOCOP_MAX_KEY_LENGTH);
+ tc = tc->next;
+ }
+ kfree(sess->tfrm_ctx);
+ kfree(sess);
+
+ return 0;
+}
+
+static struct cryptocop_session *get_session(cryptocop_session_id sid)
+{
+ struct cryptocop_session *sess;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&cryptocop_sessions_lock, flags);
+ sess = cryptocop_sessions;
+ while (sess && (sess->sid != sid)){
+ sess = sess->next;
+ }
+ spin_unlock_irqrestore(&cryptocop_sessions_lock, flags);
+
+ return sess;
+}
+
+static struct cryptocop_transform_ctx *get_transform_ctx(struct cryptocop_session *sess, cryptocop_tfrm_id tid)
+{
+ struct cryptocop_transform_ctx *tc = sess->tfrm_ctx;
+
+ DEBUG(printk("get_transform_ctx, sess=0x%p, tid=%d\n", sess, tid));
+ assert(sess != NULL);
+ while (tc && tc->init.tid != tid){
+ DEBUG(printk("tc=0x%p, tc->next=0x%p\n", tc, tc->next));
+ tc = tc->next;
+ }
+ DEBUG(printk("get_transform_ctx, returning tc=0x%p\n", tc));
+ return tc;
+}
+
+
+
+/* The AES s-transform matrix (s-box). */
+static const u8 aes_sbox[256] = {
+ 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118,
+ 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192,
+ 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21,
+ 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117,
+ 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132,
+ 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207,
+ 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168,
+ 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210,
+ 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115,
+ 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219,
+ 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121,
+ 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8,
+ 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138,
+ 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158,
+ 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
+ 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22
+};
+
+/* AES has a 32 bit word round constants for each round in the
+ * key schedule. round_constant[i] is really Rcon[i+1] in FIPS187.
+ */
+static u32 round_constant[11] = {
+ 0x01000000, 0x02000000, 0x04000000, 0x08000000,
+ 0x10000000, 0x20000000, 0x40000000, 0x80000000,
+ 0x1B000000, 0x36000000, 0x6C000000
+};
+
+/* Apply the s-box to each of the four occtets in w. */
+static u32 aes_ks_subword(const u32 w)
+{
+ u8 bytes[4];
+
+ *(u32*)(&bytes[0]) = w;
+ bytes[0] = aes_sbox[bytes[0]];
+ bytes[1] = aes_sbox[bytes[1]];
+ bytes[2] = aes_sbox[bytes[2]];
+ bytes[3] = aes_sbox[bytes[3]];
+ return *(u32*)(&bytes[0]);
+}
+
+/* The encrypt (forward) Rijndael key schedule algorithm pseudo code:
+ * (Note that AES words are 32 bit long)
+ *
+ * KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk){
+ * word temp
+ * i = 0
+ * while (i < Nk) {
+ * w[i] = word(key[4*i, 4*i + 1, 4*i + 2, 4*i + 3])
+ * i = i + 1
+ * }
+ * i = Nk
+ *
+ * while (i < (Nb * (Nr + 1))) {
+ * temp = w[i - 1]
+ * if ((i mod Nk) == 0) {
+ * temp = SubWord(RotWord(temp)) xor Rcon[i/Nk]
+ * }
+ * else if ((Nk > 6) && ((i mod Nk) == 4)) {
+ * temp = SubWord(temp)
+ * }
+ * w[i] = w[i - Nk] xor temp
+ * }
+ * RotWord(t) does a 8 bit cyclic shift left on a 32 bit word.
+ * SubWord(t) applies the AES s-box individually to each octet
+ * in a 32 bit word.
+ *
+ * For AES Nk can have the values 4, 6, and 8 (corresponding to
+ * values for Nr of 10, 12, and 14). Nb is always 4.
+ *
+ * To construct w[i], w[i - 1] and w[i - Nk] must be
+ * available. Consequently we must keep a state of the last Nk words
+ * to be able to create the last round keys.
+ */
+static void get_aes_decrypt_key(unsigned char *dec_key, const unsigned char *key, unsigned int keylength)
+{
+ u32 temp;
+ u32 w_ring[8]; /* nk is max 8, use elements 0..(nk - 1) as a ringbuffer */
+ u8 w_last_ix;
+ int i;
+ u8 nr, nk;
+
+ switch (keylength){
+ case 128:
+ nk = 4;
+ nr = 10;
+ break;
+ case 192:
+ nk = 6;
+ nr = 12;
+ break;
+ case 256:
+ nk = 8;
+ nr = 14;
+ break;
+ default:
+ panic("stream co-processor: bad aes key length in get_aes_decrypt_key\n");
+ };
+
+ /* Need to do host byte order correction here since key is byte oriented and the
+ * kx algorithm is word (u32) oriented. */
+ for (i = 0; i < nk; i+=1) {
+ w_ring[i] = be32_to_cpu(*(u32*)&key[4*i]);
+ }
+
+ i = (int)nk;
+ w_last_ix = i - 1;
+ while (i < (4 * (nr + 2))) {
+ temp = w_ring[w_last_ix];
+ if (!(i % nk)) {
+ /* RotWord(temp) */
+ temp = (temp << 8) | (temp >> 24);
+ temp = aes_ks_subword(temp);
+ temp ^= round_constant[i/nk - 1];
+ } else if ((nk > 6) && ((i % nk) == 4)) {
+ temp = aes_ks_subword(temp);
+ }
+ w_last_ix = (w_last_ix + 1) % nk; /* This is the same as (i-Nk) mod Nk */
+ temp ^= w_ring[w_last_ix];
+ w_ring[w_last_ix] = temp;
+
+ /* We need the round keys for round Nr+1 and Nr+2 (round key
+ * Nr+2 is the round key beyond the last one used when
+ * encrypting). Rounds are numbered starting from 0, Nr=10
+ * implies 11 rounds are used in encryption/decryption.
+ */
+ if (i >= (4 * nr)) {
+ /* Need to do host byte order correction here, the key
+ * is byte oriented. */
+ *(u32*)dec_key = cpu_to_be32(temp);
+ dec_key += 4;
+ }
+ ++i;
+ }
+}
+
+
+/**** Job/operation management. ****/
+
+int cryptocop_job_queue_insert_csum(struct cryptocop_operation *operation)
+{
+ return cryptocop_job_queue_insert(cryptocop_prio_kernel_csum, operation);
+}
+
+int cryptocop_job_queue_insert_crypto(struct cryptocop_operation *operation)
+{
+ return cryptocop_job_queue_insert(cryptocop_prio_kernel, operation);
+}
+
+int cryptocop_job_queue_insert_user_job(struct cryptocop_operation *operation)
+{
+ return cryptocop_job_queue_insert(cryptocop_prio_user, operation);
+}
+
+static int cryptocop_job_queue_insert(cryptocop_queue_priority prio, struct cryptocop_operation *operation)
+{
+ int ret;
+ struct cryptocop_prio_job *pj = NULL;
+ unsigned long int flags;
+
+ DEBUG(printk("cryptocop_job_queue_insert(%d, 0x%p)\n", prio, operation));
+
+ if (!operation || !operation->cb){
+ DEBUG_API(printk("cryptocop_job_queue_insert oper=0x%p, NULL operation or callback\n", operation));
+ return -EINVAL;
+ }
+
+ if ((ret = cryptocop_job_setup(&pj, operation)) != 0){
+ DEBUG_API(printk("cryptocop_job_queue_insert: job setup failed\n"));
+ return ret;
+ }
+ assert(pj != NULL);
+
+ spin_lock_irqsave(&cryptocop_job_queue_lock, flags);
+ list_add_tail(&pj->node, &cryptocop_job_queues[prio].jobs);
+ spin_unlock_irqrestore(&cryptocop_job_queue_lock, flags);
+
+ /* Make sure a job is running */
+ cryptocop_start_job();
+ return 0;
+}
+
+static void cryptocop_do_tasklet(unsigned long unused);
+DECLARE_TASKLET (cryptocop_tasklet, cryptocop_do_tasklet, 0);
+
+static void cryptocop_do_tasklet(unsigned long unused)
+{
+ struct list_head *node;
+ struct cryptocop_prio_job *pj = NULL;
+ unsigned long flags;
+
+ DEBUG(printk("cryptocop_do_tasklet: entering\n"));
+
+ do {
+ spin_lock_irqsave(&cryptocop_completed_jobs_lock, flags);
+ if (!list_empty(&cryptocop_completed_jobs)){
+ node = cryptocop_completed_jobs.next;
+ list_del(node);
+ pj = list_entry(node, struct cryptocop_prio_job, node);
+ } else {
+ pj = NULL;
+ }
+ spin_unlock_irqrestore(&cryptocop_completed_jobs_lock, flags);
+ if (pj) {
+ assert(pj->oper != NULL);
+
+ /* Notify consumer of operation completeness. */
+ DEBUG(printk("cryptocop_do_tasklet: callback 0x%p, data 0x%p\n", pj->oper->cb, pj->oper->cb_data));
+
+ pj->oper->operation_status = 0; /* Job is completed. */
+ pj->oper->cb(pj->oper, pj->oper->cb_data);
+ delete_internal_operation(pj->iop);
+ kfree(pj);
+ }
+ } while (pj != NULL);
+
+ DEBUG(printk("cryptocop_do_tasklet: exiting\n"));
+}
+
+static irqreturn_t
+dma_done_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ struct cryptocop_prio_job *done_job;
+ reg_dma_rw_ack_intr ack_intr = {
+ .data = 1,
+ };
+
+ REG_WR (dma, regi_dma9, rw_ack_intr, ack_intr);
+
+ DEBUG(printk("cryptocop DMA done\n"));
+
+ spin_lock(&running_job_lock);
+ if (cryptocop_running_job == NULL){
+ printk("stream co-processor got interrupt when not busy\n");
+ spin_unlock(&running_job_lock);
+ return IRQ_HANDLED;
+ }
+ done_job = cryptocop_running_job;
+ cryptocop_running_job = NULL;
+ spin_unlock(&running_job_lock);
+
+ /* Start processing a job. */
+ if (!spin_trylock(&cryptocop_process_lock)){
+ DEBUG(printk("cryptocop irq handler, not starting a job\n"));
+ } else {
+ cryptocop_start_job();
+ spin_unlock(&cryptocop_process_lock);
+ }
+
+ done_job->oper->operation_status = 0; /* Job is completed. */
+ if (done_job->oper->fast_callback){
+ /* This operation wants callback from interrupt. */
+ done_job->oper->cb(done_job->oper, done_job->oper->cb_data);
+ delete_internal_operation(done_job->iop);
+ kfree(done_job);
+ } else {
+ spin_lock(&cryptocop_completed_jobs_lock);
+ list_add_tail(&(done_job->node), &cryptocop_completed_jobs);
+ spin_unlock(&cryptocop_completed_jobs_lock);
+ tasklet_schedule(&cryptocop_tasklet);
+ }
+
+ DEBUG(printk("cryptocop leave irq handler\n"));
+ return IRQ_HANDLED;
+}
+
+
+/* Setup interrupts and DMA channels. */
+static int init_cryptocop(void)
+{
+ unsigned long flags;
+ reg_intr_vect_rw_mask intr_mask;
+ reg_dma_rw_cfg dma_cfg = {.en = 1};
+ reg_dma_rw_intr_mask intr_mask_in = {.data = regk_dma_yes}; /* Only want descriptor interrupts from the DMA in channel. */
+ reg_dma_rw_ack_intr ack_intr = {.data = 1,.in_eop = 1 };
+ reg_strcop_rw_cfg strcop_cfg = {
+ .ipend = regk_strcop_little,
+ .td1 = regk_strcop_e,
+ .td2 = regk_strcop_d,
+ .td3 = regk_strcop_e,
+ .ignore_sync = 0,
+ .en = 1
+ };
+
+ if (request_irq(DMA9_INTR_VECT, dma_done_interrupt, 0, "stream co-processor DMA", NULL)) panic("request_irq stream co-processor irq dma9");
+
+ (void)crisv32_request_dma(8, "strcop", DMA_PANIC_ON_ERROR, 0, dma_strp);
+ (void)crisv32_request_dma(9, "strcop", DMA_PANIC_ON_ERROR, 0, dma_strp);
+
+ local_irq_save(flags);
+
+ /* Reset and enable the cryptocop. */
+ strcop_cfg.en = 0;
+ REG_WR(strcop, regi_strcop, rw_cfg, strcop_cfg);
+ strcop_cfg.en = 1;
+ REG_WR(strcop, regi_strcop, rw_cfg, strcop_cfg);
+
+ /* Enable DMA9 interrupt */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.dma9 = 1;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ /* Enable DMAs. */
+ REG_WR(dma, regi_dma9, rw_cfg, dma_cfg); /* input DMA */
+ REG_WR(dma, regi_dma8, rw_cfg, dma_cfg); /* output DMA */
+
+ /* Set up wordsize = 4 for DMAs. */
+ DMA_WR_CMD (regi_dma8, regk_dma_set_w_size4);
+ DMA_WR_CMD (regi_dma9, regk_dma_set_w_size4);
+
+ /* Enable interrupts. */
+ REG_WR(dma, regi_dma9, rw_intr_mask, intr_mask_in);
+
+ /* Clear intr ack. */
+ REG_WR(dma, regi_dma9, rw_ack_intr, ack_intr);
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+/* Free used cryptocop hw resources (interrupt and DMA channels). */
+static void release_cryptocop(void)
+{
+ unsigned long flags;
+ reg_intr_vect_rw_mask intr_mask;
+ reg_dma_rw_cfg dma_cfg = {.en = 0};
+ reg_dma_rw_intr_mask intr_mask_in = {0};
+ reg_dma_rw_ack_intr ack_intr = {.data = 1,.in_eop = 1 };
+
+ local_irq_save(flags);
+
+ /* Clear intr ack. */
+ REG_WR(dma, regi_dma9, rw_ack_intr, ack_intr);
+
+ /* Disable DMA9 interrupt */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.dma9 = 0;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ /* Disable DMAs. */
+ REG_WR(dma, regi_dma9, rw_cfg, dma_cfg); /* input DMA */
+ REG_WR(dma, regi_dma8, rw_cfg, dma_cfg); /* output DMA */
+
+ /* Disable interrupts. */
+ REG_WR(dma, regi_dma9, rw_intr_mask, intr_mask_in);
+
+ local_irq_restore(flags);
+
+ free_irq(DMA9_INTR_VECT, NULL);
+
+ (void)crisv32_free_dma(8);
+ (void)crisv32_free_dma(9);
+}
+
+
+/* Init job queue. */
+static int cryptocop_job_queue_init(void)
+{
+ int i;
+
+ INIT_LIST_HEAD(&cryptocop_completed_jobs);
+
+ for (i = 0; i < cryptocop_prio_no_prios; i++){
+ cryptocop_job_queues[i].prio = (cryptocop_queue_priority)i;
+ INIT_LIST_HEAD(&cryptocop_job_queues[i].jobs);
+ }
+ return 0;
+}
+
+
+static void cryptocop_job_queue_close(void)
+{
+ struct list_head *node, *tmp;
+ struct cryptocop_prio_job *pj = NULL;
+ unsigned long int process_flags, flags;
+ int i;
+
+ /* FIXME: This is as yet untested code. */
+
+ /* Stop strcop from getting an operation to process while we are closing the
+ module. */
+ spin_lock_irqsave(&cryptocop_process_lock, process_flags);
+
+ /* Empty the job queue. */
+ spin_lock_irqsave(&cryptocop_process_lock, process_flags);
+ for (i = 0; i < cryptocop_prio_no_prios; i++){
+ if (!list_empty(&(cryptocop_job_queues[i].jobs))){
+ list_for_each_safe(node, tmp, &(cryptocop_job_queues[i].jobs)) {
+ pj = list_entry(node, struct cryptocop_prio_job, node);
+ list_del(node);
+
+ /* Call callback to notify consumer of job removal. */
+ DEBUG(printk("cryptocop_job_queue_close: callback 0x%p, data 0x%p\n", pj->oper->cb, pj->oper->cb_data));
+ pj->oper->operation_status = -EINTR; /* Job is terminated without completion. */
+ pj->oper->cb(pj->oper, pj->oper->cb_data);
+
+ delete_internal_operation(pj->iop);
+ kfree(pj);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&cryptocop_process_lock, process_flags);
+
+ /* Remove the running job, if any. */
+ spin_lock_irqsave(&running_job_lock, flags);
+ if (cryptocop_running_job){
+ reg_strcop_rw_cfg rw_cfg;
+ reg_dma_rw_cfg dma_out_cfg, dma_in_cfg;
+
+ /* Stop DMA. */
+ dma_out_cfg = REG_RD(dma, regi_dma8, rw_cfg);
+ dma_out_cfg.en = regk_dma_no;
+ REG_WR(dma, regi_dma8, rw_cfg, dma_out_cfg);
+
+ dma_in_cfg = REG_RD(dma, regi_dma9, rw_cfg);
+ dma_in_cfg.en = regk_dma_no;
+ REG_WR(dma, regi_dma9, rw_cfg, dma_in_cfg);
+
+ /* Disble the cryptocop. */
+ rw_cfg = REG_RD(strcop, regi_strcop, rw_cfg);
+ rw_cfg.en = 0;
+ REG_WR(strcop, regi_strcop, rw_cfg, rw_cfg);
+
+ pj = cryptocop_running_job;
+ cryptocop_running_job = NULL;
+
+ /* Call callback to notify consumer of job removal. */
+ DEBUG(printk("cryptocop_job_queue_close: callback 0x%p, data 0x%p\n", pj->oper->cb, pj->oper->cb_data));
+ pj->oper->operation_status = -EINTR; /* Job is terminated without completion. */
+ pj->oper->cb(pj->oper, pj->oper->cb_data);
+
+ delete_internal_operation(pj->iop);
+ kfree(pj);
+ }
+ spin_unlock_irqrestore(&running_job_lock, flags);
+
+ /* Remove completed jobs, if any. */
+ spin_lock_irqsave(&cryptocop_completed_jobs_lock, flags);
+
+ list_for_each_safe(node, tmp, &cryptocop_completed_jobs) {
+ pj = list_entry(node, struct cryptocop_prio_job, node);
+ list_del(node);
+ /* Call callback to notify consumer of job removal. */
+ DEBUG(printk("cryptocop_job_queue_close: callback 0x%p, data 0x%p\n", pj->oper->cb, pj->oper->cb_data));
+ pj->oper->operation_status = -EINTR; /* Job is terminated without completion. */
+ pj->oper->cb(pj->oper, pj->oper->cb_data);
+
+ delete_internal_operation(pj->iop);
+ kfree(pj);
+ }
+ spin_unlock_irqrestore(&cryptocop_completed_jobs_lock, flags);
+}
+
+
+static void cryptocop_start_job(void)
+{
+ int i;
+ struct cryptocop_prio_job *pj;
+ unsigned long int flags;
+ unsigned long int running_job_flags;
+ reg_strcop_rw_cfg rw_cfg = {.en = 1, .ignore_sync = 0};
+
+ DEBUG(printk("cryptocop_start_job: entering\n"));
+
+ spin_lock_irqsave(&running_job_lock, running_job_flags);
+ if (cryptocop_running_job != NULL){
+ /* Already running. */
+ DEBUG(printk("cryptocop_start_job: already running, exit\n"));
+ spin_unlock_irqrestore(&running_job_lock, running_job_flags);
+ return;
+ }
+ spin_lock_irqsave(&cryptocop_job_queue_lock, flags);
+
+ /* Check the queues in priority order. */
+ for (i = cryptocop_prio_kernel_csum; (i < cryptocop_prio_no_prios) && list_empty(&cryptocop_job_queues[i].jobs); i++);
+ if (i == cryptocop_prio_no_prios) {
+ spin_unlock_irqrestore(&cryptocop_job_queue_lock, flags);
+ spin_unlock_irqrestore(&running_job_lock, running_job_flags);
+ DEBUG(printk("cryptocop_start_job: no jobs to run\n"));
+ return; /* No jobs to run */
+ }
+ DEBUG(printk("starting job for prio %d\n", i));
+
+ /* TODO: Do not starve lower priority jobs. Let in a lower
+ * prio job for every N-th processed higher prio job or some
+ * other scheduling policy. This could reasonably be
+ * tweakable since the optimal balance would depend on the
+ * type of load on the system. */
+
+ /* Pull the DMA lists from the job and start the DMA client. */
+ pj = list_entry(cryptocop_job_queues[i].jobs.next, struct cryptocop_prio_job, node);
+ list_del(&pj->node);
+ spin_unlock_irqrestore(&cryptocop_job_queue_lock, flags);
+ cryptocop_running_job = pj;
+
+ /* Set config register (3DES and CSUM modes). */
+ switch (pj->iop->tdes_mode){
+ case cryptocop_3des_eee:
+ rw_cfg.td1 = regk_strcop_e;
+ rw_cfg.td2 = regk_strcop_e;
+ rw_cfg.td3 = regk_strcop_e;
+ break;
+ case cryptocop_3des_eed:
+ rw_cfg.td1 = regk_strcop_e;
+ rw_cfg.td2 = regk_strcop_e;
+ rw_cfg.td3 = regk_strcop_d;
+ break;
+ case cryptocop_3des_ede:
+ rw_cfg.td1 = regk_strcop_e;
+ rw_cfg.td2 = regk_strcop_d;
+ rw_cfg.td3 = regk_strcop_e;
+ break;
+ case cryptocop_3des_edd:
+ rw_cfg.td1 = regk_strcop_e;
+ rw_cfg.td2 = regk_strcop_d;
+ rw_cfg.td3 = regk_strcop_d;
+ break;
+ case cryptocop_3des_dee:
+ rw_cfg.td1 = regk_strcop_d;
+ rw_cfg.td2 = regk_strcop_e;
+ rw_cfg.td3 = regk_strcop_e;
+ break;
+ case cryptocop_3des_ded:
+ rw_cfg.td1 = regk_strcop_d;
+ rw_cfg.td2 = regk_strcop_e;
+ rw_cfg.td3 = regk_strcop_d;
+ break;
+ case cryptocop_3des_dde:
+ rw_cfg.td1 = regk_strcop_d;
+ rw_cfg.td2 = regk_strcop_d;
+ rw_cfg.td3 = regk_strcop_e;
+ break;
+ case cryptocop_3des_ddd:
+ rw_cfg.td1 = regk_strcop_d;
+ rw_cfg.td2 = regk_strcop_d;
+ rw_cfg.td3 = regk_strcop_d;
+ break;
+ default:
+ DEBUG(printk("cryptocop_setup_dma_list: bad 3DES mode\n"));
+ }
+ switch (pj->iop->csum_mode){
+ case cryptocop_csum_le:
+ rw_cfg.ipend = regk_strcop_little;
+ break;
+ case cryptocop_csum_be:
+ rw_cfg.ipend = regk_strcop_big;
+ break;
+ default:
+ DEBUG(printk("cryptocop_setup_dma_list: bad checksum mode\n"));
+ }
+ REG_WR(strcop, regi_strcop, rw_cfg, rw_cfg);
+
+ DEBUG(printk("cryptocop_start_job: starting DMA, new cryptocop_running_job=0x%p\n"
+ "ctx_in: 0x%p, phys: 0x%p\n"
+ "ctx_out: 0x%p, phys: 0x%p\n",
+ pj,
+ &pj->iop->ctx_in, (char*)virt_to_phys(&pj->iop->ctx_in),
+ &pj->iop->ctx_out, (char*)virt_to_phys(&pj->iop->ctx_out)));
+
+ /* Start input DMA. */
+ DMA_START_CONTEXT(regi_dma9, virt_to_phys(&pj->iop->ctx_in));
+
+ /* Start output DMA. */
+ DMA_START_CONTEXT(regi_dma8, virt_to_phys(&pj->iop->ctx_out));
+
+ spin_unlock_irqrestore(&running_job_lock, running_job_flags);
+ DEBUG(printk("cryptocop_start_job: exiting\n"));
+}
+
+
+static int cryptocop_job_setup(struct cryptocop_prio_job **pj, struct cryptocop_operation *operation)
+{
+ int err;
+ int alloc_flag = operation->in_interrupt ? GFP_ATOMIC : GFP_KERNEL;
+ void *iop_alloc_ptr = NULL;
+
+ *pj = kmalloc(sizeof (struct cryptocop_prio_job), alloc_flag);
+ if (!*pj) return -ENOMEM;
+
+ DEBUG(printk("cryptocop_job_setup: operation=0x%p\n", operation));
+
+ (*pj)->oper = operation;
+ DEBUG(printk("cryptocop_job_setup, cb=0x%p cb_data=0x%p\n", (*pj)->oper->cb, (*pj)->oper->cb_data));
+
+ if (operation->use_dmalists) {
+ DEBUG(print_user_dma_lists(&operation->list_op));
+ if (!operation->list_op.inlist || !operation->list_op.outlist || !operation->list_op.out_data_buf || !operation->list_op.in_data_buf){
+ DEBUG_API(printk("cryptocop_job_setup: bad indata (use_dmalists)\n"));
+ kfree(*pj);
+ return -EINVAL;
+ }
+ iop_alloc_ptr = kmalloc(DESCR_ALLOC_PAD + sizeof(struct cryptocop_int_operation), alloc_flag);
+ if (!iop_alloc_ptr) {
+ DEBUG_API(printk("cryptocop_job_setup: kmalloc cryptocop_int_operation\n"));
+ kfree(*pj);
+ return -ENOMEM;
+ }
+ (*pj)->iop = (struct cryptocop_int_operation*)(((unsigned long int)(iop_alloc_ptr + DESCR_ALLOC_PAD + offsetof(struct cryptocop_int_operation, ctx_out)) & ~0x0000001F) - offsetof(struct cryptocop_int_operation, ctx_out));
+ DEBUG(memset((*pj)->iop, 0xff, sizeof(struct cryptocop_int_operation)));
+ (*pj)->iop->alloc_ptr = iop_alloc_ptr;
+ (*pj)->iop->sid = operation->sid;
+ (*pj)->iop->cdesc_out = NULL;
+ (*pj)->iop->cdesc_in = NULL;
+ (*pj)->iop->tdes_mode = operation->list_op.tdes_mode;
+ (*pj)->iop->csum_mode = operation->list_op.csum_mode;
+ (*pj)->iop->ddesc_out = operation->list_op.outlist;
+ (*pj)->iop->ddesc_in = operation->list_op.inlist;
+
+ /* Setup DMA contexts. */
+ (*pj)->iop->ctx_out.next = NULL;
+ (*pj)->iop->ctx_out.eol = 1;
+ (*pj)->iop->ctx_out.saved_data = operation->list_op.outlist;
+ (*pj)->iop->ctx_out.saved_data_buf = operation->list_op.out_data_buf;
+
+ (*pj)->iop->ctx_in.next = NULL;
+ (*pj)->iop->ctx_in.eol = 1;
+ (*pj)->iop->ctx_in.saved_data = operation->list_op.inlist;
+ (*pj)->iop->ctx_in.saved_data_buf = operation->list_op.in_data_buf;
+ } else {
+ if ((err = cryptocop_setup_dma_list(operation, &(*pj)->iop, alloc_flag))) {
+ DEBUG_API(printk("cryptocop_job_setup: cryptocop_setup_dma_list failed %d\n", err));
+ kfree(*pj);
+ return err;
+ }
+ }
+ DEBUG(print_dma_descriptors((*pj)->iop));
+
+ DEBUG(printk("cryptocop_job_setup, DMA list setup successful\n"));
+
+ return 0;
+}
+
+
+static int cryptocop_open(struct inode *inode, struct file *filp)
+{
+ int p = MINOR(inode->i_rdev);
+
+ if (p != CRYPTOCOP_MINOR) return -EINVAL;
+
+ filp->private_data = NULL;
+ return 0;
+}
+
+
+static int cryptocop_release(struct inode *inode, struct file *filp)
+{
+ struct cryptocop_private *dev = filp->private_data;
+ struct cryptocop_private *dev_next;
+
+ while (dev){
+ dev_next = dev->next;
+ if (dev->sid != CRYPTOCOP_SESSION_ID_NONE) {
+ (void)cryptocop_free_session(dev->sid);
+ }
+ kfree(dev);
+ dev = dev_next;
+ }
+
+ return 0;
+}
+
+
+static int cryptocop_ioctl_close_session(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct cryptocop_private *dev = filp->private_data;
+ struct cryptocop_private *prev_dev = NULL;
+ struct strcop_session_op *sess_op = (struct strcop_session_op *)arg;
+ struct strcop_session_op sop;
+ int err;
+
+ DEBUG(printk("cryptocop_ioctl_close_session\n"));
+
+ if (!access_ok(VERIFY_READ, sess_op, sizeof(struct strcop_session_op)))
+ return -EFAULT;
+ err = copy_from_user(&sop, sess_op, sizeof(struct strcop_session_op));
+ if (err) return -EFAULT;
+
+ while (dev && (dev->sid != sop.ses_id)) {
+ prev_dev = dev;
+ dev = dev->next;
+ }
+ if (dev){
+ if (prev_dev){
+ prev_dev->next = dev->next;
+ } else {
+ filp->private_data = dev->next;
+ }
+ err = cryptocop_free_session(dev->sid);
+ if (err) return -EFAULT;
+ } else {
+ DEBUG_API(printk("cryptocop_ioctl_close_session: session %lld not found\n", sop.ses_id));
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+static void ioctl_process_job_callback(struct cryptocop_operation *op, void*cb_data)
+{
+ struct ioctl_job_cb_ctx *jc = (struct ioctl_job_cb_ctx *)cb_data;
+
+ DEBUG(printk("ioctl_process_job_callback: op=0x%p, cb_data=0x%p\n", op, cb_data));
+
+ jc->processed = 1;
+ wake_up(&cryptocop_ioc_process_wq);
+}
+
+
+#define CRYPTOCOP_IOCTL_CIPHER_TID (1)
+#define CRYPTOCOP_IOCTL_DIGEST_TID (2)
+#define CRYPTOCOP_IOCTL_CSUM_TID (3)
+
+static size_t first_cfg_change_ix(struct strcop_crypto_op *crp_op)
+{
+ size_t ch_ix = 0;
+
+ if (crp_op->do_cipher) ch_ix = crp_op->cipher_start;
+ if (crp_op->do_digest && (crp_op->digest_start < ch_ix)) ch_ix = crp_op->digest_start;
+ if (crp_op->do_csum && (crp_op->csum_start < ch_ix)) ch_ix = crp_op->csum_start;
+
+ DEBUG(printk("first_cfg_change_ix: ix=%d\n", ch_ix));
+ return ch_ix;
+}
+
+
+static size_t next_cfg_change_ix(struct strcop_crypto_op *crp_op, size_t ix)
+{
+ size_t ch_ix = INT_MAX;
+ size_t tmp_ix = 0;
+
+ if (crp_op->do_cipher && ((crp_op->cipher_start + crp_op->cipher_len) > ix)){
+ if (crp_op->cipher_start > ix) {
+ ch_ix = crp_op->cipher_start;
+ } else {
+ ch_ix = crp_op->cipher_start + crp_op->cipher_len;
+ }
+ }
+ if (crp_op->do_digest && ((crp_op->digest_start + crp_op->digest_len) > ix)){
+ if (crp_op->digest_start > ix) {
+ tmp_ix = crp_op->digest_start;
+ } else {
+ tmp_ix = crp_op->digest_start + crp_op->digest_len;
+ }
+ if (tmp_ix < ch_ix) ch_ix = tmp_ix;
+ }
+ if (crp_op->do_csum && ((crp_op->csum_start + crp_op->csum_len) > ix)){
+ if (crp_op->csum_start > ix) {
+ tmp_ix = crp_op->csum_start;
+ } else {
+ tmp_ix = crp_op->csum_start + crp_op->csum_len;
+ }
+ if (tmp_ix < ch_ix) ch_ix = tmp_ix;
+ }
+ if (ch_ix == INT_MAX) ch_ix = ix;
+ DEBUG(printk("next_cfg_change_ix prev ix=%d, next ix=%d\n", ix, ch_ix));
+ return ch_ix;
+}
+
+
+/* Map map_length bytes from the pages starting on *pageix and *pageoffset to iovecs starting on *iovix.
+ * Return -1 for ok, 0 for fail. */
+static int map_pages_to_iovec(struct iovec *iov, int iovlen, int *iovix, struct page **pages, int nopages, int *pageix, int *pageoffset, int map_length )
+{
+ int tmplen;
+
+ assert(iov != NULL);
+ assert(iovix != NULL);
+ assert(pages != NULL);
+ assert(pageix != NULL);
+ assert(pageoffset != NULL);
+
+ DEBUG(printk("map_pages_to_iovec, map_length=%d, iovlen=%d, *iovix=%d, nopages=%d, *pageix=%d, *pageoffset=%d\n", map_length, iovlen, *iovix, nopages, *pageix, *pageoffset));
+
+ while (map_length > 0){
+ DEBUG(printk("map_pages_to_iovec, map_length=%d, iovlen=%d, *iovix=%d, nopages=%d, *pageix=%d, *pageoffset=%d\n", map_length, iovlen, *iovix, nopages, *pageix, *pageoffset));
+ if (*iovix >= iovlen){
+ DEBUG_API(printk("map_page_to_iovec: *iovix=%d >= iovlen=%d\n", *iovix, iovlen));
+ return 0;
+ }
+ if (*pageix >= nopages){
+ DEBUG_API(printk("map_page_to_iovec: *pageix=%d >= nopages=%d\n", *pageix, nopages));
+ return 0;
+ }
+ iov[*iovix].iov_base = (unsigned char*)page_address(pages[*pageix]) + *pageoffset;
+ tmplen = PAGE_SIZE - *pageoffset;
+ if (tmplen < map_length){
+ (*pageoffset) = 0;
+ (*pageix)++;
+ } else {
+ tmplen = map_length;
+ (*pageoffset) += map_length;
+ }
+ DEBUG(printk("mapping %d bytes from page %d (or %d) to iovec %d\n", tmplen, *pageix, *pageix-1, *iovix));
+ iov[*iovix].iov_len = tmplen;
+ map_length -= tmplen;
+ (*iovix)++;
+ }
+ DEBUG(printk("map_page_to_iovec, exit, *iovix=%d\n", *iovix));
+ return -1;
+}
+
+
+
+static int cryptocop_ioctl_process(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int i;
+ struct cryptocop_private *dev = filp->private_data;
+ struct strcop_crypto_op *crp_oper = (struct strcop_crypto_op *)arg;
+ struct strcop_crypto_op oper = {0};
+ int err = 0;
+ struct cryptocop_operation *cop = NULL;
+
+ struct ioctl_job_cb_ctx *jc = NULL;
+
+ struct page **inpages = NULL;
+ struct page **outpages = NULL;
+ int noinpages = 0;
+ int nooutpages = 0;
+
+ struct cryptocop_desc descs[5]; /* Max 5 descriptors are needed, there are three transforms that
+ * can get connected/disconnected on different places in the indata. */
+ struct cryptocop_desc_cfg dcfgs[5*3];
+ int desc_ix = 0;
+ int dcfg_ix = 0;
+ struct cryptocop_tfrm_cfg ciph_tcfg = {0};
+ struct cryptocop_tfrm_cfg digest_tcfg = {0};
+ struct cryptocop_tfrm_cfg csum_tcfg = {0};
+
+ unsigned char *digest_result = NULL;
+ int digest_length = 0;
+ int cblocklen = 0;
+ unsigned char csum_result[CSUM_BLOCK_LENGTH];
+ struct cryptocop_session *sess;
+
+ int iovlen = 0;
+ int iovix = 0;
+ int pageix = 0;
+ int pageoffset = 0;
+
+ size_t prev_ix = 0;
+ size_t next_ix;
+
+ int cipher_active, digest_active, csum_active;
+ int end_digest, end_csum;
+ int digest_done = 0;
+ int cipher_done = 0;
+ int csum_done = 0;
+
+ DEBUG(printk("cryptocop_ioctl_process\n"));
+
+ if (!access_ok(VERIFY_WRITE, crp_oper, sizeof(struct strcop_crypto_op))){
+ DEBUG_API(printk("cryptocop_ioctl_process: !access_ok crp_oper!\n"));
+ return -EFAULT;
+ }
+ if (copy_from_user(&oper, crp_oper, sizeof(struct strcop_crypto_op))) {
+ DEBUG_API(printk("cryptocop_ioctl_process: copy_from_user\n"));
+ return -EFAULT;
+ }
+ DEBUG(print_strcop_crypto_op(&oper));
+
+ while (dev && dev->sid != oper.ses_id) dev = dev->next;
+ if (!dev){
+ DEBUG_API(printk("cryptocop_ioctl_process: session %lld not found\n", oper.ses_id));
+ return -EINVAL;
+ }
+
+ /* Check buffers. */
+ if (((oper.indata + oper.inlen) < oper.indata) || ((oper.cipher_outdata + oper.cipher_outlen) < oper.cipher_outdata)){
+ DEBUG_API(printk("cryptocop_ioctl_process: user buffers wrapped around, bad user!\n"));
+ return -EINVAL;
+ }
+
+ if (!access_ok(VERIFY_WRITE, oper.cipher_outdata, oper.cipher_outlen)){
+ DEBUG_API(printk("cryptocop_ioctl_process: !access_ok out data!\n"));
+ return -EFAULT;
+ }
+ if (!access_ok(VERIFY_READ, oper.indata, oper.inlen)){
+ DEBUG_API(printk("cryptocop_ioctl_process: !access_ok in data!\n"));
+ return -EFAULT;
+ }
+
+ cop = kmalloc(sizeof(struct cryptocop_operation), GFP_KERNEL);
+ if (!cop) {
+ DEBUG_API(printk("cryptocop_ioctl_process: kmalloc\n"));
+ return -ENOMEM;
+ }
+ jc = kmalloc(sizeof(struct ioctl_job_cb_ctx), GFP_KERNEL);
+ if (!jc) {
+ DEBUG_API(printk("cryptocop_ioctl_process: kmalloc\n"));
+ err = -ENOMEM;
+ goto error_cleanup;
+ }
+ jc->processed = 0;
+
+ cop->cb_data = jc;
+ cop->cb = ioctl_process_job_callback;
+ cop->operation_status = 0;
+ cop->use_dmalists = 0;
+ cop->in_interrupt = 0;
+ cop->fast_callback = 0;
+ cop->tfrm_op.tfrm_cfg = NULL;
+ cop->tfrm_op.desc = NULL;
+ cop->tfrm_op.indata = NULL;
+ cop->tfrm_op.incount = 0;
+ cop->tfrm_op.inlen = 0;
+ cop->tfrm_op.outdata = NULL;
+ cop->tfrm_op.outcount = 0;
+ cop->tfrm_op.outlen = 0;
+
+ sess = get_session(oper.ses_id);
+ if (!sess){
+ DEBUG_API(printk("cryptocop_ioctl_process: bad session id.\n"));
+ kfree(cop);
+ kfree(jc);
+ return -EINVAL;
+ }
+
+ if (oper.do_cipher) {
+ unsigned int cipher_outlen = 0;
+ struct cryptocop_transform_ctx *tc = get_transform_ctx(sess, CRYPTOCOP_IOCTL_CIPHER_TID);
+ if (!tc) {
+ DEBUG_API(printk("cryptocop_ioctl_process: no cipher transform in session.\n"));
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+ ciph_tcfg.tid = CRYPTOCOP_IOCTL_CIPHER_TID;
+ ciph_tcfg.inject_ix = 0;
+ ciph_tcfg.flags = 0;
+ if ((oper.cipher_start < 0) || (oper.cipher_len <= 0) || (oper.cipher_start > oper.inlen) || ((oper.cipher_start + oper.cipher_len) > oper.inlen)){
+ DEBUG_API(printk("cryptocop_ioctl_process: bad cipher length\n"));
+ kfree(cop);
+ kfree(jc);
+ return -EINVAL;
+ }
+ cblocklen = tc->init.alg == cryptocop_alg_aes ? AES_BLOCK_LENGTH : DES_BLOCK_LENGTH;
+ if (oper.cipher_len % cblocklen) {
+ kfree(cop);
+ kfree(jc);
+ DEBUG_API(printk("cryptocop_ioctl_process: cipher inlength not multiple of block length.\n"));
+ return -EINVAL;
+ }
+ cipher_outlen = oper.cipher_len;
+ if (tc->init.cipher_mode == cryptocop_cipher_mode_cbc){
+ if (oper.cipher_explicit) {
+ ciph_tcfg.flags |= CRYPTOCOP_EXPLICIT_IV;
+ memcpy(ciph_tcfg.iv, oper.cipher_iv, cblocklen);
+ } else {
+ cipher_outlen = oper.cipher_len - cblocklen;
+ }
+ } else {
+ if (oper.cipher_explicit){
+ kfree(cop);
+ kfree(jc);
+ DEBUG_API(printk("cryptocop_ioctl_process: explicit_iv when not CBC mode\n"));
+ return -EINVAL;
+ }
+ }
+ if (oper.cipher_outlen != cipher_outlen) {
+ kfree(cop);
+ kfree(jc);
+ DEBUG_API(printk("cryptocop_ioctl_process: cipher_outlen incorrect, should be %d not %d.\n", cipher_outlen, oper.cipher_outlen));
+ return -EINVAL;
+ }
+
+ if (oper.decrypt){
+ ciph_tcfg.flags |= CRYPTOCOP_DECRYPT;
+ } else {
+ ciph_tcfg.flags |= CRYPTOCOP_ENCRYPT;
+ }
+ ciph_tcfg.next = cop->tfrm_op.tfrm_cfg;
+ cop->tfrm_op.tfrm_cfg = &ciph_tcfg;
+ }
+ if (oper.do_digest){
+ struct cryptocop_transform_ctx *tc = get_transform_ctx(sess, CRYPTOCOP_IOCTL_DIGEST_TID);
+ if (!tc) {
+ DEBUG_API(printk("cryptocop_ioctl_process: no digest transform in session.\n"));
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+ digest_length = tc->init.alg == cryptocop_alg_md5 ? 16 : 20;
+ digest_result = kmalloc(digest_length, GFP_KERNEL);
+ if (!digest_result) {
+ DEBUG_API(printk("cryptocop_ioctl_process: kmalloc digest_result\n"));
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+ DEBUG(memset(digest_result, 0xff, digest_length));
+
+ digest_tcfg.tid = CRYPTOCOP_IOCTL_DIGEST_TID;
+ digest_tcfg.inject_ix = 0;
+ ciph_tcfg.inject_ix += digest_length;
+ if ((oper.digest_start < 0) || (oper.digest_len <= 0) || (oper.digest_start > oper.inlen) || ((oper.digest_start + oper.digest_len) > oper.inlen)){
+ DEBUG_API(printk("cryptocop_ioctl_process: bad digest length\n"));
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+
+ digest_tcfg.next = cop->tfrm_op.tfrm_cfg;
+ cop->tfrm_op.tfrm_cfg = &digest_tcfg;
+ }
+ if (oper.do_csum){
+ csum_tcfg.tid = CRYPTOCOP_IOCTL_CSUM_TID;
+ csum_tcfg.inject_ix = digest_length;
+ ciph_tcfg.inject_ix += 2;
+
+ if ((oper.csum_start < 0) || (oper.csum_len <= 0) || (oper.csum_start > oper.inlen) || ((oper.csum_start + oper.csum_len) > oper.inlen)){
+ DEBUG_API(printk("cryptocop_ioctl_process: bad csum length\n"));
+ kfree(cop);
+ kfree(jc);
+ return -EINVAL;
+ }
+
+ csum_tcfg.next = cop->tfrm_op.tfrm_cfg;
+ cop->tfrm_op.tfrm_cfg = &csum_tcfg;
+ }
+
+ prev_ix = first_cfg_change_ix(&oper);
+ if (prev_ix > oper.inlen) {
+ DEBUG_API(printk("cryptocop_ioctl_process: length mismatch\n"));
+ nooutpages = noinpages = 0;
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+ DEBUG(printk("cryptocop_ioctl_process: inlen=%d, cipher_outlen=%d\n", oper.inlen, oper.cipher_outlen));
+
+ /* Map user pages for in and out data of the operation. */
+ noinpages = (((unsigned long int)(oper.indata + prev_ix) & ~PAGE_MASK) + oper.inlen - 1 - prev_ix + ~PAGE_MASK) >> PAGE_SHIFT;
+ DEBUG(printk("cryptocop_ioctl_process: noinpages=%d\n", noinpages));
+ inpages = kmalloc(noinpages * sizeof(struct page*), GFP_KERNEL);
+ if (!inpages){
+ DEBUG_API(printk("cryptocop_ioctl_process: kmalloc inpages\n"));
+ nooutpages = noinpages = 0;
+ err = -ENOMEM;
+ goto error_cleanup;
+ }
+ if (oper.do_cipher){
+ nooutpages = (((unsigned long int)oper.cipher_outdata & ~PAGE_MASK) + oper.cipher_outlen - 1 + ~PAGE_MASK) >> PAGE_SHIFT;
+ DEBUG(printk("cryptocop_ioctl_process: nooutpages=%d\n", nooutpages));
+ outpages = kmalloc(nooutpages * sizeof(struct page*), GFP_KERNEL);
+ if (!outpages){
+ DEBUG_API(printk("cryptocop_ioctl_process: kmalloc outpages\n"));
+ nooutpages = noinpages = 0;
+ err = -ENOMEM;
+ goto error_cleanup;
+ }
+ }
+
+ /* Acquire the mm page semaphore. */
+ down_read(&current->mm->mmap_sem);
+
+ err = get_user_pages(current,
+ current->mm,
+ (unsigned long int)(oper.indata + prev_ix),
+ noinpages,
+ 0, /* read access only for in data */
+ 0, /* no force */
+ inpages,
+ NULL);
+
+ if (err < 0) {
+ up_read(&current->mm->mmap_sem);
+ nooutpages = noinpages = 0;
+ DEBUG_API(printk("cryptocop_ioctl_process: get_user_pages indata\n"));
+ goto error_cleanup;
+ }
+ noinpages = err;
+ if (oper.do_cipher){
+ err = get_user_pages(current,
+ current->mm,
+ (unsigned long int)oper.cipher_outdata,
+ nooutpages,
+ 1, /* write access for out data */
+ 0, /* no force */
+ outpages,
+ NULL);
+ up_read(&current->mm->mmap_sem);
+ if (err < 0) {
+ nooutpages = 0;
+ DEBUG_API(printk("cryptocop_ioctl_process: get_user_pages outdata\n"));
+ goto error_cleanup;
+ }
+ nooutpages = err;
+ } else {
+ up_read(&current->mm->mmap_sem);
+ }
+
+ /* Add 6 to nooutpages to make room for possibly inserted buffers for storing digest and
+ * csum output and splits when units are (dis-)connected. */
+ cop->tfrm_op.indata = kmalloc((noinpages) * sizeof(struct iovec), GFP_KERNEL);
+ cop->tfrm_op.outdata = kmalloc((6 + nooutpages) * sizeof(struct iovec), GFP_KERNEL);
+ if (!cop->tfrm_op.indata || !cop->tfrm_op.outdata) {
+ DEBUG_API(printk("cryptocop_ioctl_process: kmalloc iovecs\n"));
+ err = -ENOMEM;
+ goto error_cleanup;
+ }
+
+ cop->tfrm_op.inlen = oper.inlen - prev_ix;
+ cop->tfrm_op.outlen = 0;
+ if (oper.do_cipher) cop->tfrm_op.outlen += oper.cipher_outlen;
+ if (oper.do_digest) cop->tfrm_op.outlen += digest_length;
+ if (oper.do_csum) cop->tfrm_op.outlen += 2;
+
+ /* Setup the in iovecs. */
+ cop->tfrm_op.incount = noinpages;
+ if (noinpages > 1){
+ size_t tmplen = cop->tfrm_op.inlen;
+
+ cop->tfrm_op.indata[0].iov_len = PAGE_SIZE - ((unsigned long int)(oper.indata + prev_ix) & ~PAGE_MASK);
+ cop->tfrm_op.indata[0].iov_base = (unsigned char*)page_address(inpages[0]) + ((unsigned long int)(oper.indata + prev_ix) & ~PAGE_MASK);
+ tmplen -= cop->tfrm_op.indata[0].iov_len;
+ for (i = 1; i<noinpages; i++){
+ cop->tfrm_op.indata[i].iov_len = tmplen < PAGE_SIZE ? tmplen : PAGE_SIZE;
+ cop->tfrm_op.indata[i].iov_base = (unsigned char*)page_address(inpages[i]);
+ tmplen -= PAGE_SIZE;
+ }
+ } else {
+ cop->tfrm_op.indata[0].iov_len = oper.inlen - prev_ix;
+ cop->tfrm_op.indata[0].iov_base = (unsigned char*)page_address(inpages[0]) + ((unsigned long int)(oper.indata + prev_ix) & ~PAGE_MASK);
+ }
+
+ iovlen = nooutpages + 6;
+ pageoffset = oper.do_cipher ? ((unsigned long int)oper.cipher_outdata & ~PAGE_MASK) : 0;
+
+ next_ix = next_cfg_change_ix(&oper, prev_ix);
+ if (prev_ix == next_ix){
+ DEBUG_API(printk("cryptocop_ioctl_process: length configuration broken.\n"));
+ err = -EINVAL; /* This should be impossible barring bugs. */
+ goto error_cleanup;
+ }
+ while (prev_ix != next_ix){
+ end_digest = end_csum = cipher_active = digest_active = csum_active = 0;
+ descs[desc_ix].cfg = NULL;
+ descs[desc_ix].length = next_ix - prev_ix;
+
+ if (oper.do_cipher && (oper.cipher_start < next_ix) && (prev_ix < (oper.cipher_start + oper.cipher_len))) {
+ dcfgs[dcfg_ix].tid = CRYPTOCOP_IOCTL_CIPHER_TID;
+ dcfgs[dcfg_ix].src = cryptocop_source_dma;
+ cipher_active = 1;
+
+ if (next_ix == (oper.cipher_start + oper.cipher_len)){
+ cipher_done = 1;
+ dcfgs[dcfg_ix].last = 1;
+ } else {
+ dcfgs[dcfg_ix].last = 0;
+ }
+ dcfgs[dcfg_ix].next = descs[desc_ix].cfg;
+ descs[desc_ix].cfg = &dcfgs[dcfg_ix];
+ ++dcfg_ix;
+ }
+ if (oper.do_digest && (oper.digest_start < next_ix) && (prev_ix < (oper.digest_start + oper.digest_len))) {
+ digest_active = 1;
+ dcfgs[dcfg_ix].tid = CRYPTOCOP_IOCTL_DIGEST_TID;
+ dcfgs[dcfg_ix].src = cryptocop_source_dma;
+ if (next_ix == (oper.digest_start + oper.digest_len)){
+ assert(!digest_done);
+ digest_done = 1;
+ dcfgs[dcfg_ix].last = 1;
+ } else {
+ dcfgs[dcfg_ix].last = 0;
+ }
+ dcfgs[dcfg_ix].next = descs[desc_ix].cfg;
+ descs[desc_ix].cfg = &dcfgs[dcfg_ix];
+ ++dcfg_ix;
+ }
+ if (oper.do_csum && (oper.csum_start < next_ix) && (prev_ix < (oper.csum_start + oper.csum_len))){
+ csum_active = 1;
+ dcfgs[dcfg_ix].tid = CRYPTOCOP_IOCTL_CSUM_TID;
+ dcfgs[dcfg_ix].src = cryptocop_source_dma;
+ if (next_ix == (oper.csum_start + oper.csum_len)){
+ csum_done = 1;
+ dcfgs[dcfg_ix].last = 1;
+ } else {
+ dcfgs[dcfg_ix].last = 0;
+ }
+ dcfgs[dcfg_ix].next = descs[desc_ix].cfg;
+ descs[desc_ix].cfg = &dcfgs[dcfg_ix];
+ ++dcfg_ix;
+ }
+ if (!descs[desc_ix].cfg){
+ DEBUG_API(printk("cryptocop_ioctl_process: data segment %d (%d to %d) had no active transforms\n", desc_ix, prev_ix, next_ix));
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+ descs[desc_ix].next = &(descs[desc_ix]) + 1;
+ ++desc_ix;
+ prev_ix = next_ix;
+ next_ix = next_cfg_change_ix(&oper, prev_ix);
+ }
+ if (desc_ix > 0){
+ descs[desc_ix-1].next = NULL;
+ } else {
+ descs[0].next = NULL;
+ }
+ if (oper.do_digest) {
+ DEBUG(printk("cryptocop_ioctl_process: mapping %d byte digest output to iovec %d\n", digest_length, iovix));
+ /* Add outdata iovec, length == <length of type of digest> */
+ cop->tfrm_op.outdata[iovix].iov_base = digest_result;
+ cop->tfrm_op.outdata[iovix].iov_len = digest_length;
+ ++iovix;
+ }
+ if (oper.do_csum) {
+ /* Add outdata iovec, length == 2, the length of csum. */
+ DEBUG(printk("cryptocop_ioctl_process: mapping 2 byte csum output to iovec %d\n", iovix));
+ /* Add outdata iovec, length == <length of type of digest> */
+ cop->tfrm_op.outdata[iovix].iov_base = csum_result;
+ cop->tfrm_op.outdata[iovix].iov_len = 2;
+ ++iovix;
+ }
+ if (oper.do_cipher) {
+ if (!map_pages_to_iovec(cop->tfrm_op.outdata, iovlen, &iovix, outpages, nooutpages, &pageix, &pageoffset, oper.cipher_outlen)){
+ DEBUG_API(printk("cryptocop_ioctl_process: failed to map pages to iovec.\n"));
+ err = -ENOSYS; /* This should be impossible barring bugs. */
+ goto error_cleanup;
+ }
+ }
+ DEBUG(printk("cryptocop_ioctl_process: setting cop->tfrm_op.outcount %d\n", iovix));
+ cop->tfrm_op.outcount = iovix;
+ assert(iovix <= (nooutpages + 6));
+
+ cop->sid = oper.ses_id;
+ cop->tfrm_op.desc = &descs[0];
+
+ DEBUG(printk("cryptocop_ioctl_process: inserting job, cb_data=0x%p\n", cop->cb_data));
+
+ if ((err = cryptocop_job_queue_insert_user_job(cop)) != 0) {
+ DEBUG_API(printk("cryptocop_ioctl_process: insert job %d\n", err));
+ err = -EINVAL;
+ goto error_cleanup;
+ }
+
+ DEBUG(printk("cryptocop_ioctl_process: begin wait for result\n"));
+
+ wait_event(cryptocop_ioc_process_wq, (jc->processed != 0));
+ DEBUG(printk("cryptocop_ioctl_process: end wait for result\n"));
+ if (!jc->processed){
+ printk(KERN_WARNING "cryptocop_ioctl_process: job not processed at completion\n");
+ err = -EIO;
+ goto error_cleanup;
+ }
+
+ /* Job process done. Cipher output should already be correct in job so no post processing of outdata. */
+ DEBUG(printk("cryptocop_ioctl_process: operation_status = %d\n", cop->operation_status));
+ if (cop->operation_status == 0){
+ if (oper.do_digest){
+ DEBUG(printk("cryptocop_ioctl_process: copy %d bytes digest to user\n", digest_length));
+ err = copy_to_user((unsigned char*)crp_oper + offsetof(struct strcop_crypto_op, digest), digest_result, digest_length);
+ if (0 != err){
+ DEBUG_API(printk("cryptocop_ioctl_process: copy_to_user, digest length %d, err %d\n", digest_length, err));
+ err = -EFAULT;
+ goto error_cleanup;
+ }
+ }
+ if (oper.do_csum){
+ DEBUG(printk("cryptocop_ioctl_process: copy 2 bytes checksum to user\n"));
+ err = copy_to_user((unsigned char*)crp_oper + offsetof(struct strcop_crypto_op, csum), csum_result, 2);
+ if (0 != err){
+ DEBUG_API(printk("cryptocop_ioctl_process: copy_to_user, csum, err %d\n", err));
+ err = -EFAULT;
+ goto error_cleanup;
+ }
+ }
+ err = 0;
+ } else {
+ DEBUG(printk("cryptocop_ioctl_process: returning err = operation_status = %d\n", cop->operation_status));
+ err = cop->operation_status;
+ }
+
+ error_cleanup:
+ /* Release page caches. */
+ for (i = 0; i < noinpages; i++){
+ put_page(inpages[i]);
+ }
+ for (i = 0; i < nooutpages; i++){
+ int spdl_err;
+ /* Mark output pages dirty. */
+ spdl_err = set_page_dirty_lock(outpages[i]);
+ DEBUG(if (spdl_err)printk("cryptocop_ioctl_process: set_page_dirty_lock returned %d\n", spdl_err));
+ }
+ for (i = 0; i < nooutpages; i++){
+ put_page(outpages[i]);
+ }
+
+ if (digest_result) kfree(digest_result);
+ if (inpages) kfree(inpages);
+ if (outpages) kfree(outpages);
+ if (cop){
+ if (cop->tfrm_op.indata) kfree(cop->tfrm_op.indata);
+ if (cop->tfrm_op.outdata) kfree(cop->tfrm_op.outdata);
+ kfree(cop);
+ }
+ if (jc) kfree(jc);
+
+ DEBUG(print_lock_status());
+
+ return err;
+}
+
+
+static int cryptocop_ioctl_create_session(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ cryptocop_session_id sid;
+ int err;
+ struct cryptocop_private *dev;
+ struct strcop_session_op *sess_op = (struct strcop_session_op *)arg;
+ struct strcop_session_op sop;
+ struct cryptocop_transform_init *tis = NULL;
+ struct cryptocop_transform_init ti_cipher = {0};
+ struct cryptocop_transform_init ti_digest = {0};
+ struct cryptocop_transform_init ti_csum = {0};
+
+ if (!access_ok(VERIFY_WRITE, sess_op, sizeof(struct strcop_session_op)))
+ return -EFAULT;
+ err = copy_from_user(&sop, sess_op, sizeof(struct strcop_session_op));
+ if (err) return -EFAULT;
+ if (sop.cipher != cryptocop_cipher_none) {
+ if (!access_ok(VERIFY_READ, sop.key, sop.keylen)) return -EFAULT;
+ }
+ DEBUG(printk("cryptocop_ioctl_create_session, sess_op:\n"));
+
+ DEBUG(printk("\tcipher:%d\n"
+ "\tcipher_mode:%d\n"
+ "\tdigest:%d\n"
+ "\tcsum:%d\n",
+ (int)sop.cipher,
+ (int)sop.cmode,
+ (int)sop.digest,
+ (int)sop.csum));
+
+ if (sop.cipher != cryptocop_cipher_none){
+ /* Init the cipher. */
+ switch (sop.cipher){
+ case cryptocop_cipher_des:
+ ti_cipher.alg = cryptocop_alg_des;
+ break;
+ case cryptocop_cipher_3des:
+ ti_cipher.alg = cryptocop_alg_3des;
+ break;
+ case cryptocop_cipher_aes:
+ ti_cipher.alg = cryptocop_alg_aes;
+ break;
+ default:
+ DEBUG_API(printk("create session, bad cipher algorithm %d\n", sop.cipher));
+ return -EINVAL;
+ };
+ DEBUG(printk("setting cipher transform %d\n", ti_cipher.alg));
+ copy_from_user(ti_cipher.key, sop.key, sop.keylen/8);
+ ti_cipher.keylen = sop.keylen;
+ switch (sop.cmode){
+ case cryptocop_cipher_mode_cbc:
+ case cryptocop_cipher_mode_ecb:
+ ti_cipher.cipher_mode = sop.cmode;
+ break;
+ default:
+ DEBUG_API(printk("create session, bad cipher mode %d\n", sop.cmode));
+ return -EINVAL;
+ }
+ DEBUG(printk("cryptocop_ioctl_create_session: setting CBC mode %d\n", ti_cipher.cipher_mode));
+ switch (sop.des3_mode){
+ case cryptocop_3des_eee:
+ case cryptocop_3des_eed:
+ case cryptocop_3des_ede:
+ case cryptocop_3des_edd:
+ case cryptocop_3des_dee:
+ case cryptocop_3des_ded:
+ case cryptocop_3des_dde:
+ case cryptocop_3des_ddd:
+ ti_cipher.tdes_mode = sop.des3_mode;
+ break;
+ default:
+ DEBUG_API(printk("create session, bad 3DES mode %d\n", sop.des3_mode));
+ return -EINVAL;
+ }
+ ti_cipher.tid = CRYPTOCOP_IOCTL_CIPHER_TID;
+ ti_cipher.next = tis;
+ tis = &ti_cipher;
+ } /* if (sop.cipher != cryptocop_cipher_none) */
+ if (sop.digest != cryptocop_digest_none){
+ DEBUG(printk("setting digest transform\n"));
+ switch (sop.digest){
+ case cryptocop_digest_md5:
+ ti_digest.alg = cryptocop_alg_md5;
+ break;
+ case cryptocop_digest_sha1:
+ ti_digest.alg = cryptocop_alg_sha1;
+ break;
+ default:
+ DEBUG_API(printk("create session, bad digest algorithm %d\n", sop.digest));
+ return -EINVAL;
+ }
+ ti_digest.tid = CRYPTOCOP_IOCTL_DIGEST_TID;
+ ti_digest.next = tis;
+ tis = &ti_digest;
+ } /* if (sop.digest != cryptocop_digest_none) */
+ if (sop.csum != cryptocop_csum_none){
+ DEBUG(printk("setting csum transform\n"));
+ switch (sop.csum){
+ case cryptocop_csum_le:
+ case cryptocop_csum_be:
+ ti_csum.csum_mode = sop.csum;
+ break;
+ default:
+ DEBUG_API(printk("create session, bad checksum algorithm %d\n", sop.csum));
+ return -EINVAL;
+ }
+ ti_csum.alg = cryptocop_alg_csum;
+ ti_csum.tid = CRYPTOCOP_IOCTL_CSUM_TID;
+ ti_csum.next = tis;
+ tis = &ti_csum;
+ } /* (sop.csum != cryptocop_csum_none) */
+ dev = kmalloc(sizeof(struct cryptocop_private), GFP_KERNEL);
+ if (!dev){
+ DEBUG_API(printk("create session, alloc dev\n"));
+ return -ENOMEM;
+ }
+
+ err = cryptocop_new_session(&sid, tis, GFP_KERNEL);
+ DEBUG({ if (err) printk("create session, cryptocop_new_session %d\n", err);});
+
+ if (err) {
+ kfree(dev);
+ return err;
+ }
+ sess_op->ses_id = sid;
+ dev->sid = sid;
+ dev->next = filp->private_data;
+ filp->private_data = dev;
+
+ return 0;
+}
+
+static int cryptocop_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ if (_IOC_TYPE(cmd) != ETRAXCRYPTOCOP_IOCTYPE) {
+ DEBUG_API(printk("cryptocop_ioctl: wrong type\n"));
+ return -ENOTTY;
+ }
+ if (_IOC_NR(cmd) > CRYPTOCOP_IO_MAXNR){
+ return -ENOTTY;
+ }
+ /* Access check of the argument. Some commands, e.g. create session and process op,
+ needs additional checks. Those are handled in the command handling functions. */
+ if (_IOC_DIR(cmd) & _IOC_READ)
+ err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
+ else if (_IOC_DIR(cmd) & _IOC_WRITE)
+ err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
+ if (err) return -EFAULT;
+
+ switch (cmd) {
+ case CRYPTOCOP_IO_CREATE_SESSION:
+ return cryptocop_ioctl_create_session(inode, filp, cmd, arg);
+ case CRYPTOCOP_IO_CLOSE_SESSION:
+ return cryptocop_ioctl_close_session(inode, filp, cmd, arg);
+ case CRYPTOCOP_IO_PROCESS_OP:
+ return cryptocop_ioctl_process(inode, filp, cmd, arg);
+ default:
+ DEBUG_API(printk("cryptocop_ioctl: unknown command\n"));
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+
+#ifdef LDEBUG
+static void print_dma_descriptors(struct cryptocop_int_operation *iop)
+{
+ struct cryptocop_dma_desc *cdesc_out = iop->cdesc_out;
+ struct cryptocop_dma_desc *cdesc_in = iop->cdesc_in;
+ int i;
+
+ printk("print_dma_descriptors start\n");
+
+ printk("iop:\n");
+ printk("\tsid: 0x%lld\n", iop->sid);
+
+ printk("\tcdesc_out: 0x%p\n", iop->cdesc_out);
+ printk("\tcdesc_in: 0x%p\n", iop->cdesc_in);
+ printk("\tddesc_out: 0x%p\n", iop->ddesc_out);
+ printk("\tddesc_in: 0x%p\n", iop->ddesc_in);
+
+ printk("\niop->ctx_out: 0x%p phys: 0x%p\n", &iop->ctx_out, (char*)virt_to_phys(&iop->ctx_out));
+ printk("\tnext: 0x%p\n"
+ "\tsaved_data: 0x%p\n"
+ "\tsaved_data_buf: 0x%p\n",
+ iop->ctx_out.next,
+ iop->ctx_out.saved_data,
+ iop->ctx_out.saved_data_buf);
+
+ printk("\niop->ctx_in: 0x%p phys: 0x%p\n", &iop->ctx_in, (char*)virt_to_phys(&iop->ctx_in));
+ printk("\tnext: 0x%p\n"
+ "\tsaved_data: 0x%p\n"
+ "\tsaved_data_buf: 0x%p\n",
+ iop->ctx_in.next,
+ iop->ctx_in.saved_data,
+ iop->ctx_in.saved_data_buf);
+
+ i = 0;
+ while (cdesc_out) {
+ dma_descr_data *td;
+ printk("cdesc_out %d, desc=0x%p\n", i, cdesc_out->dma_descr);
+ printk("\n\tvirt_to_phys(desc): 0x%p\n", (char*)virt_to_phys(cdesc_out->dma_descr));
+ td = cdesc_out->dma_descr;
+ printk("\n\tbuf: 0x%p\n"
+ "\tafter: 0x%p\n"
+ "\tmd: 0x%04x\n"
+ "\tnext: 0x%p\n",
+ td->buf,
+ td->after,
+ td->md,
+ td->next);
+ printk("flags:\n"
+ "\twait:\t%d\n"
+ "\teol:\t%d\n"
+ "\touteop:\t%d\n"
+ "\tineop:\t%d\n"
+ "\tintr:\t%d\n",
+ td->wait,
+ td->eol,
+ td->out_eop,
+ td->in_eop,
+ td->intr);
+ cdesc_out = cdesc_out->next;
+ i++;
+ }
+ i = 0;
+ while (cdesc_in) {
+ dma_descr_data *td;
+ printk("cdesc_in %d, desc=0x%p\n", i, cdesc_in->dma_descr);
+ printk("\n\tvirt_to_phys(desc): 0x%p\n", (char*)virt_to_phys(cdesc_in->dma_descr));
+ td = cdesc_in->dma_descr;
+ printk("\n\tbuf: 0x%p\n"
+ "\tafter: 0x%p\n"
+ "\tmd: 0x%04x\n"
+ "\tnext: 0x%p\n",
+ td->buf,
+ td->after,
+ td->md,
+ td->next);
+ printk("flags:\n"
+ "\twait:\t%d\n"
+ "\teol:\t%d\n"
+ "\touteop:\t%d\n"
+ "\tineop:\t%d\n"
+ "\tintr:\t%d\n",
+ td->wait,
+ td->eol,
+ td->out_eop,
+ td->in_eop,
+ td->intr);
+ cdesc_in = cdesc_in->next;
+ i++;
+ }
+
+ printk("print_dma_descriptors end\n");
+}
+
+
+static void print_strcop_crypto_op(struct strcop_crypto_op *cop)
+{
+ printk("print_strcop_crypto_op, 0x%p\n", cop);
+
+ /* Indata. */
+ printk("indata=0x%p\n"
+ "inlen=%d\n"
+ "do_cipher=%d\n"
+ "decrypt=%d\n"
+ "cipher_explicit=%d\n"
+ "cipher_start=%d\n"
+ "cipher_len=%d\n"
+ "outdata=0x%p\n"
+ "outlen=%d\n",
+ cop->indata,
+ cop->inlen,
+ cop->do_cipher,
+ cop->decrypt,
+ cop->cipher_explicit,
+ cop->cipher_start,
+ cop->cipher_len,
+ cop->cipher_outdata,
+ cop->cipher_outlen);
+
+ printk("do_digest=%d\n"
+ "digest_start=%d\n"
+ "digest_len=%d\n",
+ cop->do_digest,
+ cop->digest_start,
+ cop->digest_len);
+
+ printk("do_csum=%d\n"
+ "csum_start=%d\n"
+ "csum_len=%d\n",
+ cop->do_csum,
+ cop->csum_start,
+ cop->csum_len);
+}
+
+static void print_cryptocop_operation(struct cryptocop_operation *cop)
+{
+ struct cryptocop_desc *d;
+ struct cryptocop_tfrm_cfg *tc;
+ struct cryptocop_desc_cfg *dc;
+ int i;
+
+ printk("print_cryptocop_operation, cop=0x%p\n\n", cop);
+ printk("sid: %lld\n", cop->sid);
+ printk("operation_status=%d\n"
+ "use_dmalists=%d\n"
+ "in_interrupt=%d\n"
+ "fast_callback=%d\n",
+ cop->operation_status,
+ cop->use_dmalists,
+ cop->in_interrupt,
+ cop->fast_callback);
+
+ if (cop->use_dmalists){
+ print_user_dma_lists(&cop->list_op);
+ } else {
+ printk("cop->tfrm_op\n"
+ "tfrm_cfg=0x%p\n"
+ "desc=0x%p\n"
+ "indata=0x%p\n"
+ "incount=%d\n"
+ "inlen=%d\n"
+ "outdata=0x%p\n"
+ "outcount=%d\n"
+ "outlen=%d\n\n",
+ cop->tfrm_op.tfrm_cfg,
+ cop->tfrm_op.desc,
+ cop->tfrm_op.indata,
+ cop->tfrm_op.incount,
+ cop->tfrm_op.inlen,
+ cop->tfrm_op.outdata,
+ cop->tfrm_op.outcount,
+ cop->tfrm_op.outlen);
+
+ tc = cop->tfrm_op.tfrm_cfg;
+ while (tc){
+ printk("tfrm_cfg, 0x%p\n"
+ "tid=%d\n"
+ "flags=%d\n"
+ "inject_ix=%d\n"
+ "next=0x%p\n",
+ tc,
+ tc->tid,
+ tc->flags,
+ tc->inject_ix,
+ tc->next);
+ tc = tc->next;
+ }
+ d = cop->tfrm_op.desc;
+ while (d){
+ printk("\n======================desc, 0x%p\n"
+ "length=%d\n"
+ "cfg=0x%p\n"
+ "next=0x%p\n",
+ d,
+ d->length,
+ d->cfg,
+ d->next);
+ dc = d->cfg;
+ while (dc){
+ printk("=========desc_cfg, 0x%p\n"
+ "tid=%d\n"
+ "src=%d\n"
+ "last=%d\n"
+ "next=0x%p\n",
+ dc,
+ dc->tid,
+ dc->src,
+ dc->last,
+ dc->next);
+ dc = dc->next;
+ }
+ d = d->next;
+ }
+ printk("\n====iniov\n");
+ for (i = 0; i < cop->tfrm_op.incount; i++){
+ printk("indata[%d]\n"
+ "base=0x%p\n"
+ "len=%d\n",
+ i,
+ cop->tfrm_op.indata[i].iov_base,
+ cop->tfrm_op.indata[i].iov_len);
+ }
+ printk("\n====outiov\n");
+ for (i = 0; i < cop->tfrm_op.outcount; i++){
+ printk("outdata[%d]\n"
+ "base=0x%p\n"
+ "len=%d\n",
+ i,
+ cop->tfrm_op.outdata[i].iov_base,
+ cop->tfrm_op.outdata[i].iov_len);
+ }
+ }
+ printk("------------end print_cryptocop_operation\n");
+}
+
+
+static void print_user_dma_lists(struct cryptocop_dma_list_operation *dma_op)
+{
+ dma_descr_data *dd;
+ int i;
+
+ printk("print_user_dma_lists, dma_op=0x%p\n", dma_op);
+
+ printk("out_data_buf = 0x%p, phys_to_virt(out_data_buf) = 0x%p\n", dma_op->out_data_buf, phys_to_virt((unsigned long int)dma_op->out_data_buf));
+ printk("in_data_buf = 0x%p, phys_to_virt(in_data_buf) = 0x%p\n", dma_op->in_data_buf, phys_to_virt((unsigned long int)dma_op->in_data_buf));
+
+ printk("##############outlist\n");
+ dd = phys_to_virt((unsigned long int)dma_op->outlist);
+ i = 0;
+ while (dd != NULL) {
+ printk("#%d phys_to_virt(desc) 0x%p\n", i, dd);
+ printk("\n\tbuf: 0x%p\n"
+ "\tafter: 0x%p\n"
+ "\tmd: 0x%04x\n"
+ "\tnext: 0x%p\n",
+ dd->buf,
+ dd->after,
+ dd->md,
+ dd->next);
+ printk("flags:\n"
+ "\twait:\t%d\n"
+ "\teol:\t%d\n"
+ "\touteop:\t%d\n"
+ "\tineop:\t%d\n"
+ "\tintr:\t%d\n",
+ dd->wait,
+ dd->eol,
+ dd->out_eop,
+ dd->in_eop,
+ dd->intr);
+ if (dd->eol)
+ dd = NULL;
+ else
+ dd = phys_to_virt((unsigned long int)dd->next);
+ ++i;
+ }
+
+ printk("##############inlist\n");
+ dd = phys_to_virt((unsigned long int)dma_op->inlist);
+ i = 0;
+ while (dd != NULL) {
+ printk("#%d phys_to_virt(desc) 0x%p\n", i, dd);
+ printk("\n\tbuf: 0x%p\n"
+ "\tafter: 0x%p\n"
+ "\tmd: 0x%04x\n"
+ "\tnext: 0x%p\n",
+ dd->buf,
+ dd->after,
+ dd->md,
+ dd->next);
+ printk("flags:\n"
+ "\twait:\t%d\n"
+ "\teol:\t%d\n"
+ "\touteop:\t%d\n"
+ "\tineop:\t%d\n"
+ "\tintr:\t%d\n",
+ dd->wait,
+ dd->eol,
+ dd->out_eop,
+ dd->in_eop,
+ dd->intr);
+ if (dd->eol)
+ dd = NULL;
+ else
+ dd = phys_to_virt((unsigned long int)dd->next);
+ ++i;
+ }
+}
+
+
+static void print_lock_status(void)
+{
+ printk("**********************print_lock_status\n");
+ printk("cryptocop_completed_jobs_lock %d\n", spin_is_locked(&cryptocop_completed_jobs_lock));
+ printk("cryptocop_job_queue_lock %d\n", spin_is_locked(&cryptocop_job_queue_lock));
+ printk("descr_pool_lock %d\n", spin_is_locked(&descr_pool_lock));
+ printk("cryptocop_sessions_lock %d\n", spin_is_locked(cryptocop_sessions_lock));
+ printk("running_job_lock %d\n", spin_is_locked(running_job_lock));
+ printk("cryptocop_process_lock %d\n", spin_is_locked(cryptocop_process_lock));
+}
+#endif /* LDEBUG */
+
+
+static const char cryptocop_name[] = "ETRAX FS stream co-processor";
+
+static int init_stream_coprocessor(void)
+{
+ int err;
+ int i;
+ static int initialized = 0;
+
+ if (initialized)
+ return 0;
+
+ initialized = 1;
+
+ printk("ETRAX FS stream co-processor driver v0.01, (c) 2003 Axis Communications AB\n");
+
+ err = register_chrdev(CRYPTOCOP_MAJOR, cryptocop_name, &cryptocop_fops);
+ if (err < 0) {
+ printk(KERN_ERR "stream co-processor: could not get major number.\n");
+ return err;
+ }
+
+ err = init_cryptocop();
+ if (err) {
+ (void)unregister_chrdev(CRYPTOCOP_MAJOR, cryptocop_name);
+ return err;
+ }
+ err = cryptocop_job_queue_init();
+ if (err) {
+ release_cryptocop();
+ (void)unregister_chrdev(CRYPTOCOP_MAJOR, cryptocop_name);
+ return err;
+ }
+ /* Init the descriptor pool. */
+ for (i = 0; i < CRYPTOCOP_DESCRIPTOR_POOL_SIZE - 1; i++) {
+ descr_pool[i].from_pool = 1;
+ descr_pool[i].next = &descr_pool[i + 1];
+ }
+ descr_pool[i].from_pool = 1;
+ descr_pool[i].next = NULL;
+ descr_pool_free_list = &descr_pool[0];
+ descr_pool_no_free = CRYPTOCOP_DESCRIPTOR_POOL_SIZE;
+
+ spin_lock_init(&cryptocop_completed_jobs_lock);
+ spin_lock_init(&cryptocop_job_queue_lock);
+ spin_lock_init(&descr_pool_lock);
+ spin_lock_init(&cryptocop_sessions_lock);
+ spin_lock_init(&running_job_lock);
+ spin_lock_init(&cryptocop_process_lock);
+
+ cryptocop_sessions = NULL;
+ next_sid = 1;
+
+ cryptocop_running_job = NULL;
+
+ printk("stream co-processor: init done.\n");
+ return 0;
+}
+
+static void __exit exit_stream_coprocessor(void)
+{
+ release_cryptocop();
+ cryptocop_job_queue_close();
+}
+
+module_init(init_stream_coprocessor);
+module_exit(exit_stream_coprocessor);
+
diff --git a/arch/cris/arch-v32/drivers/gpio.c b/arch/cris/arch-v32/drivers/gpio.c
new file mode 100644
index 000000000000..a551237dcb5e
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/gpio.c
@@ -0,0 +1,766 @@
+/* $Id: gpio.c,v 1.16 2005/06/19 17:06:49 starvik Exp $
+ *
+ * ETRAX CRISv32 general port I/O device
+ *
+ * Copyright (c) 1999, 2000, 2001, 2002, 2003 Axis Communications AB
+ *
+ * Authors: Bjorn Wesen (initial version)
+ * Ola Knutsson (LED handling)
+ * Johan Adolfsson (read/set directions, write, port G,
+ * port to ETRAX FS.
+ *
+ * $Log: gpio.c,v $
+ * Revision 1.16 2005/06/19 17:06:49 starvik
+ * Merge of Linux 2.6.12.
+ *
+ * Revision 1.15 2005/05/25 08:22:20 starvik
+ * Changed GPIO port order to fit packages/devices/axis-2.4.
+ *
+ * Revision 1.14 2005/04/24 18:35:08 starvik
+ * Updated with final register headers.
+ *
+ * Revision 1.13 2005/03/15 15:43:00 starvik
+ * dev_id needs to be supplied for shared IRQs.
+ *
+ * Revision 1.12 2005/03/10 17:12:00 starvik
+ * Protect alarm list with spinlock.
+ *
+ * Revision 1.11 2005/01/05 06:08:59 starvik
+ * No need to do local_irq_disable after local_irq_save.
+ *
+ * Revision 1.10 2004/11/19 08:38:31 starvik
+ * Removed old crap.
+ *
+ * Revision 1.9 2004/05/14 07:58:02 starvik
+ * Merge of changes from 2.4
+ *
+ * Revision 1.8 2003/09/11 07:29:50 starvik
+ * Merge of Linux 2.6.0-test5
+ *
+ * Revision 1.7 2003/07/10 13:25:46 starvik
+ * Compiles for 2.5.74
+ * Lindented ethernet.c
+ *
+ * Revision 1.6 2003/07/04 08:27:46 starvik
+ * Merge of Linux 2.5.74
+ *
+ * Revision 1.5 2003/06/10 08:26:37 johana
+ * Etrax -> ETRAX CRISv32
+ *
+ * Revision 1.4 2003/06/05 14:22:48 johana
+ * Initialise some_alarms.
+ *
+ * Revision 1.3 2003/06/05 10:15:46 johana
+ * New INTR_VECT macros.
+ * Enable interrupts in global config.
+ *
+ * Revision 1.2 2003/06/03 15:52:50 johana
+ * Initial CRIS v32 version.
+ *
+ * Revision 1.1 2003/06/03 08:53:15 johana
+ * Copy of os/lx25/arch/cris/arch-v10/drivers/gpio.c version 1.7.
+ *
+ */
+
+#include <linux/config.h>
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include <asm/etraxgpio.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/gio_defs.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+
+/* The following gio ports on ETRAX FS is available:
+ * pa 8 bits, supports interrupts off, hi, low, set, posedge, negedge anyedge
+ * pb 18 bits
+ * pc 18 bits
+ * pd 18 bits
+ * pe 18 bits
+ * each port has a rw_px_dout, r_px_din and rw_px_oe register.
+ */
+
+#define GPIO_MAJOR 120 /* experimental MAJOR number */
+
+#define D(x)
+
+#if 0
+static int dp_cnt;
+#define DP(x) do { dp_cnt++; if (dp_cnt % 1000 == 0) x; }while(0)
+#else
+#define DP(x)
+#endif
+
+static char gpio_name[] = "etrax gpio";
+
+#if 0
+static wait_queue_head_t *gpio_wq;
+#endif
+
+static int gpio_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static ssize_t gpio_write(struct file * file, const char * buf, size_t count,
+ loff_t *off);
+static int gpio_open(struct inode *inode, struct file *filp);
+static int gpio_release(struct inode *inode, struct file *filp);
+static unsigned int gpio_poll(struct file *filp, struct poll_table_struct *wait);
+
+/* private data per open() of this driver */
+
+struct gpio_private {
+ struct gpio_private *next;
+ /* The IO_CFG_WRITE_MODE_VALUE only support 8 bits: */
+ unsigned char clk_mask;
+ unsigned char data_mask;
+ unsigned char write_msb;
+ unsigned char pad1;
+ /* These fields are generic */
+ unsigned long highalarm, lowalarm;
+ wait_queue_head_t alarm_wq;
+ int minor;
+};
+
+/* linked list of alarms to check for */
+
+static struct gpio_private *alarmlist = 0;
+
+static int gpio_some_alarms = 0; /* Set if someone uses alarm */
+static unsigned long gpio_pa_high_alarms = 0;
+static unsigned long gpio_pa_low_alarms = 0;
+
+static DEFINE_SPINLOCK(alarm_lock);
+
+#define NUM_PORTS (GPIO_MINOR_LAST+1)
+#define GIO_REG_RD_ADDR(reg) (volatile unsigned long*) (regi_gio + REG_RD_ADDR_gio_##reg )
+#define GIO_REG_WR_ADDR(reg) (volatile unsigned long*) (regi_gio + REG_RD_ADDR_gio_##reg )
+unsigned long led_dummy;
+
+static volatile unsigned long *data_out[NUM_PORTS] = {
+ GIO_REG_WR_ADDR(rw_pa_dout),
+ GIO_REG_WR_ADDR(rw_pb_dout),
+ &led_dummy,
+ GIO_REG_WR_ADDR(rw_pc_dout),
+ GIO_REG_WR_ADDR(rw_pd_dout),
+ GIO_REG_WR_ADDR(rw_pe_dout),
+};
+
+static volatile unsigned long *data_in[NUM_PORTS] = {
+ GIO_REG_RD_ADDR(r_pa_din),
+ GIO_REG_RD_ADDR(r_pb_din),
+ &led_dummy,
+ GIO_REG_RD_ADDR(r_pc_din),
+ GIO_REG_RD_ADDR(r_pd_din),
+ GIO_REG_RD_ADDR(r_pe_din),
+};
+
+static unsigned long changeable_dir[NUM_PORTS] = {
+ CONFIG_ETRAX_PA_CHANGEABLE_DIR,
+ CONFIG_ETRAX_PB_CHANGEABLE_DIR,
+ 0,
+ CONFIG_ETRAX_PC_CHANGEABLE_DIR,
+ CONFIG_ETRAX_PD_CHANGEABLE_DIR,
+ CONFIG_ETRAX_PE_CHANGEABLE_DIR,
+};
+
+static unsigned long changeable_bits[NUM_PORTS] = {
+ CONFIG_ETRAX_PA_CHANGEABLE_BITS,
+ CONFIG_ETRAX_PB_CHANGEABLE_BITS,
+ 0,
+ CONFIG_ETRAX_PC_CHANGEABLE_BITS,
+ CONFIG_ETRAX_PD_CHANGEABLE_BITS,
+ CONFIG_ETRAX_PE_CHANGEABLE_BITS,
+};
+
+static volatile unsigned long *dir_oe[NUM_PORTS] = {
+ GIO_REG_WR_ADDR(rw_pa_oe),
+ GIO_REG_WR_ADDR(rw_pb_oe),
+ &led_dummy,
+ GIO_REG_WR_ADDR(rw_pc_oe),
+ GIO_REG_WR_ADDR(rw_pd_oe),
+ GIO_REG_WR_ADDR(rw_pe_oe),
+};
+
+
+
+static unsigned int
+gpio_poll(struct file *file,
+ poll_table *wait)
+{
+ unsigned int mask = 0;
+ struct gpio_private *priv = (struct gpio_private *)file->private_data;
+ unsigned long data;
+ poll_wait(file, &priv->alarm_wq, wait);
+ if (priv->minor == GPIO_MINOR_A) {
+ reg_gio_rw_intr_cfg intr_cfg;
+ unsigned long tmp;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ data = REG_TYPE_CONV(unsigned long, reg_gio_r_pa_din, REG_RD(gio, regi_gio, r_pa_din));
+ /* PA has support for interrupt
+ * lets activate high for those low and with highalarm set
+ */
+ intr_cfg = REG_RD(gio, regi_gio, rw_intr_cfg);
+
+ tmp = ~data & priv->highalarm & 0xFF;
+ if (tmp & (1 << 0)) {
+ intr_cfg.pa0 = regk_gio_hi;
+ }
+ if (tmp & (1 << 1)) {
+ intr_cfg.pa1 = regk_gio_hi;
+ }
+ if (tmp & (1 << 2)) {
+ intr_cfg.pa2 = regk_gio_hi;
+ }
+ if (tmp & (1 << 3)) {
+ intr_cfg.pa3 = regk_gio_hi;
+ }
+ if (tmp & (1 << 4)) {
+ intr_cfg.pa4 = regk_gio_hi;
+ }
+ if (tmp & (1 << 5)) {
+ intr_cfg.pa5 = regk_gio_hi;
+ }
+ if (tmp & (1 << 6)) {
+ intr_cfg.pa6 = regk_gio_hi;
+ }
+ if (tmp & (1 << 7)) {
+ intr_cfg.pa7 = regk_gio_hi;
+ }
+ /*
+ * lets activate low for those high and with lowalarm set
+ */
+ tmp = data & priv->lowalarm & 0xFF;
+ if (tmp & (1 << 0)) {
+ intr_cfg.pa0 = regk_gio_lo;
+ }
+ if (tmp & (1 << 1)) {
+ intr_cfg.pa1 = regk_gio_lo;
+ }
+ if (tmp & (1 << 2)) {
+ intr_cfg.pa2 = regk_gio_lo;
+ }
+ if (tmp & (1 << 3)) {
+ intr_cfg.pa3 = regk_gio_lo;
+ }
+ if (tmp & (1 << 4)) {
+ intr_cfg.pa4 = regk_gio_lo;
+ }
+ if (tmp & (1 << 5)) {
+ intr_cfg.pa5 = regk_gio_lo;
+ }
+ if (tmp & (1 << 6)) {
+ intr_cfg.pa6 = regk_gio_lo;
+ }
+ if (tmp & (1 << 7)) {
+ intr_cfg.pa7 = regk_gio_lo;
+ }
+
+ REG_WR(gio, regi_gio, rw_intr_cfg, intr_cfg);
+ local_irq_restore(flags);
+ } else if (priv->minor <= GPIO_MINOR_E)
+ data = *data_in[priv->minor];
+ else
+ return 0;
+
+ if ((data & priv->highalarm) ||
+ (~data & priv->lowalarm)) {
+ mask = POLLIN|POLLRDNORM;
+ }
+
+ DP(printk("gpio_poll ready: mask 0x%08X\n", mask));
+ return mask;
+}
+
+int etrax_gpio_wake_up_check(void)
+{
+ struct gpio_private *priv = alarmlist;
+ unsigned long data = 0;
+ int ret = 0;
+ while (priv) {
+ data = *data_in[priv->minor];
+ if ((data & priv->highalarm) ||
+ (~data & priv->lowalarm)) {
+ DP(printk("etrax_gpio_wake_up_check %i\n",priv->minor));
+ wake_up_interruptible(&priv->alarm_wq);
+ ret = 1;
+ }
+ priv = priv->next;
+ }
+ return ret;
+}
+
+static irqreturn_t
+gpio_poll_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ if (gpio_some_alarms) {
+ return IRQ_RETVAL(etrax_gpio_wake_up_check());
+ }
+ return IRQ_NONE;
+}
+
+static irqreturn_t
+gpio_pa_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ reg_gio_rw_intr_mask intr_mask;
+ reg_gio_r_masked_intr masked_intr;
+ reg_gio_rw_ack_intr ack_intr;
+ unsigned long tmp;
+ unsigned long tmp2;
+
+ /* Find what PA interrupts are active */
+ masked_intr = REG_RD(gio, regi_gio, r_masked_intr);
+ tmp = REG_TYPE_CONV(unsigned long, reg_gio_r_masked_intr, masked_intr);
+
+ /* Find those that we have enabled */
+ spin_lock(&alarm_lock);
+ tmp &= (gpio_pa_high_alarms | gpio_pa_low_alarms);
+ spin_unlock(&alarm_lock);
+
+ /* Ack them */
+ ack_intr = REG_TYPE_CONV(reg_gio_rw_ack_intr, unsigned long, tmp);
+ REG_WR(gio, regi_gio, rw_ack_intr, ack_intr);
+
+ /* Disable those interrupts.. */
+ intr_mask = REG_RD(gio, regi_gio, rw_intr_mask);
+ tmp2 = REG_TYPE_CONV(unsigned long, reg_gio_rw_intr_mask, intr_mask);
+ tmp2 &= ~tmp;
+ intr_mask = REG_TYPE_CONV(reg_gio_rw_intr_mask, unsigned long, tmp2);
+ REG_WR(gio, regi_gio, rw_intr_mask, intr_mask);
+
+ if (gpio_some_alarms) {
+ return IRQ_RETVAL(etrax_gpio_wake_up_check());
+ }
+ return IRQ_NONE;
+}
+
+
+static ssize_t gpio_write(struct file * file, const char * buf, size_t count,
+ loff_t *off)
+{
+ struct gpio_private *priv = (struct gpio_private *)file->private_data;
+ unsigned char data, clk_mask, data_mask, write_msb;
+ unsigned long flags;
+ unsigned long shadow;
+ volatile unsigned long *port;
+ ssize_t retval = count;
+ /* Only bits 0-7 may be used for write operations but allow all
+ devices except leds... */
+ if (priv->minor == GPIO_MINOR_LEDS) {
+ return -EFAULT;
+ }
+
+ if (!access_ok(VERIFY_READ, buf, count)) {
+ return -EFAULT;
+ }
+ clk_mask = priv->clk_mask;
+ data_mask = priv->data_mask;
+ /* It must have been configured using the IO_CFG_WRITE_MODE */
+ /* Perhaps a better error code? */
+ if (clk_mask == 0 || data_mask == 0) {
+ return -EPERM;
+ }
+ write_msb = priv->write_msb;
+ D(printk("gpio_write: %lu to data 0x%02X clk 0x%02X msb: %i\n",count, data_mask, clk_mask, write_msb));
+ port = data_out[priv->minor];
+
+ while (count--) {
+ int i;
+ data = *buf++;
+ if (priv->write_msb) {
+ for (i = 7; i >= 0;i--) {
+ local_irq_save(flags);
+ shadow = *port;
+ *port = shadow &= ~clk_mask;
+ if (data & 1<<i)
+ *port = shadow |= data_mask;
+ else
+ *port = shadow &= ~data_mask;
+ /* For FPGA: min 5.0ns (DCC) before CCLK high */
+ *port = shadow |= clk_mask;
+ local_irq_restore(flags);
+ }
+ } else {
+ for (i = 0; i <= 7;i++) {
+ local_irq_save(flags);
+ shadow = *port;
+ *port = shadow &= ~clk_mask;
+ if (data & 1<<i)
+ *port = shadow |= data_mask;
+ else
+ *port = shadow &= ~data_mask;
+ /* For FPGA: min 5.0ns (DCC) before CCLK high */
+ *port = shadow |= clk_mask;
+ local_irq_restore(flags);
+ }
+ }
+ }
+ return retval;
+}
+
+
+
+static int
+gpio_open(struct inode *inode, struct file *filp)
+{
+ struct gpio_private *priv;
+ int p = MINOR(inode->i_rdev);
+
+ if (p > GPIO_MINOR_LAST)
+ return -EINVAL;
+
+ priv = (struct gpio_private *)kmalloc(sizeof(struct gpio_private),
+ GFP_KERNEL);
+
+ if (!priv)
+ return -ENOMEM;
+
+ priv->minor = p;
+
+ /* initialize the io/alarm struct and link it into our alarmlist */
+
+ priv->next = alarmlist;
+ alarmlist = priv;
+ priv->clk_mask = 0;
+ priv->data_mask = 0;
+ priv->highalarm = 0;
+ priv->lowalarm = 0;
+ init_waitqueue_head(&priv->alarm_wq);
+
+ filp->private_data = (void *)priv;
+
+ return 0;
+}
+
+static int
+gpio_release(struct inode *inode, struct file *filp)
+{
+ struct gpio_private *p = alarmlist;
+ struct gpio_private *todel = (struct gpio_private *)filp->private_data;
+ /* local copies while updating them: */
+ unsigned long a_high, a_low;
+ unsigned long some_alarms;
+
+ /* unlink from alarmlist and free the private structure */
+
+ if (p == todel) {
+ alarmlist = todel->next;
+ } else {
+ while (p->next != todel)
+ p = p->next;
+ p->next = todel->next;
+ }
+
+ kfree(todel);
+ /* Check if there are still any alarms set */
+ p = alarmlist;
+ some_alarms = 0;
+ a_high = 0;
+ a_low = 0;
+ while (p) {
+ if (p->minor == GPIO_MINOR_A) {
+ a_high |= p->highalarm;
+ a_low |= p->lowalarm;
+ }
+
+ if (p->highalarm | p->lowalarm) {
+ some_alarms = 1;
+ }
+ p = p->next;
+ }
+
+ spin_lock(&alarm_lock);
+ gpio_some_alarms = some_alarms;
+ gpio_pa_high_alarms = a_high;
+ gpio_pa_low_alarms = a_low;
+ spin_unlock(&alarm_lock);
+
+ return 0;
+}
+
+/* Main device API. ioctl's to read/set/clear bits, as well as to
+ * set alarms to wait for using a subsequent select().
+ */
+
+unsigned long inline setget_input(struct gpio_private *priv, unsigned long arg)
+{
+ /* Set direction 0=unchanged 1=input,
+ * return mask with 1=input
+ */
+ unsigned long flags;
+ unsigned long dir_shadow;
+
+ local_irq_save(flags);
+ dir_shadow = *dir_oe[priv->minor];
+ dir_shadow &= ~(arg & changeable_dir[priv->minor]);
+ *dir_oe[priv->minor] = dir_shadow;
+ local_irq_restore(flags);
+
+ if (priv->minor == GPIO_MINOR_A)
+ dir_shadow ^= 0xFF; /* Only 8 bits */
+ else
+ dir_shadow ^= 0x3FFFF; /* Only 18 bits */
+ return dir_shadow;
+
+} /* setget_input */
+
+unsigned long inline setget_output(struct gpio_private *priv, unsigned long arg)
+{
+ unsigned long flags;
+ unsigned long dir_shadow;
+
+ local_irq_save(flags);
+ dir_shadow = *dir_oe[priv->minor];
+ dir_shadow |= (arg & changeable_dir[priv->minor]);
+ *dir_oe[priv->minor] = dir_shadow;
+ local_irq_restore(flags);
+ return dir_shadow;
+} /* setget_output */
+
+static int
+gpio_leds_ioctl(unsigned int cmd, unsigned long arg);
+
+static int
+gpio_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned long flags;
+ unsigned long val;
+ unsigned long shadow;
+ struct gpio_private *priv = (struct gpio_private *)file->private_data;
+ if (_IOC_TYPE(cmd) != ETRAXGPIO_IOCTYPE) {
+ return -EINVAL;
+ }
+
+ switch (_IOC_NR(cmd)) {
+ case IO_READBITS: /* Use IO_READ_INBITS and IO_READ_OUTBITS instead */
+ // read the port
+ return *data_in[priv->minor];
+ break;
+ case IO_SETBITS:
+ local_irq_save(flags);
+ if (arg & 0x04)
+ printk("GPIO SET 2\n");
+ // set changeable bits with a 1 in arg
+ shadow = *data_out[priv->minor];
+ shadow |= (arg & changeable_bits[priv->minor]);
+ *data_out[priv->minor] = shadow;
+ local_irq_restore(flags);
+ break;
+ case IO_CLRBITS:
+ local_irq_save(flags);
+ if (arg & 0x04)
+ printk("GPIO CLR 2\n");
+ // clear changeable bits with a 1 in arg
+ shadow = *data_out[priv->minor];
+ shadow &= ~(arg & changeable_bits[priv->minor]);
+ *data_out[priv->minor] = shadow;
+ local_irq_restore(flags);
+ break;
+ case IO_HIGHALARM:
+ // set alarm when bits with 1 in arg go high
+ priv->highalarm |= arg;
+ spin_lock(&alarm_lock);
+ gpio_some_alarms = 1;
+ if (priv->minor == GPIO_MINOR_A) {
+ gpio_pa_high_alarms |= arg;
+ }
+ spin_unlock(&alarm_lock);
+ break;
+ case IO_LOWALARM:
+ // set alarm when bits with 1 in arg go low
+ priv->lowalarm |= arg;
+ spin_lock(&alarm_lock);
+ gpio_some_alarms = 1;
+ if (priv->minor == GPIO_MINOR_A) {
+ gpio_pa_low_alarms |= arg;
+ }
+ spin_unlock(&alarm_lock);
+ break;
+ case IO_CLRALARM:
+ // clear alarm for bits with 1 in arg
+ priv->highalarm &= ~arg;
+ priv->lowalarm &= ~arg;
+ spin_lock(&alarm_lock);
+ if (priv->minor == GPIO_MINOR_A) {
+ if (gpio_pa_high_alarms & arg ||
+ gpio_pa_low_alarms & arg) {
+ /* Must update the gpio_pa_*alarms masks */
+ }
+ }
+ spin_unlock(&alarm_lock);
+ break;
+ case IO_READDIR: /* Use IO_SETGET_INPUT/OUTPUT instead! */
+ /* Read direction 0=input 1=output */
+ return *dir_oe[priv->minor];
+ case IO_SETINPUT: /* Use IO_SETGET_INPUT instead! */
+ /* Set direction 0=unchanged 1=input,
+ * return mask with 1=input
+ */
+ return setget_input(priv, arg);
+ break;
+ case IO_SETOUTPUT: /* Use IO_SETGET_OUTPUT instead! */
+ /* Set direction 0=unchanged 1=output,
+ * return mask with 1=output
+ */
+ return setget_output(priv, arg);
+
+ case IO_CFG_WRITE_MODE:
+ {
+ unsigned long dir_shadow;
+ dir_shadow = *dir_oe[priv->minor];
+
+ priv->clk_mask = arg & 0xFF;
+ priv->data_mask = (arg >> 8) & 0xFF;
+ priv->write_msb = (arg >> 16) & 0x01;
+ /* Check if we're allowed to change the bits and
+ * the direction is correct
+ */
+ if (!((priv->clk_mask & changeable_bits[priv->minor]) &&
+ (priv->data_mask & changeable_bits[priv->minor]) &&
+ (priv->clk_mask & dir_shadow) &&
+ (priv->data_mask & dir_shadow)))
+ {
+ priv->clk_mask = 0;
+ priv->data_mask = 0;
+ return -EPERM;
+ }
+ break;
+ }
+ case IO_READ_INBITS:
+ /* *arg is result of reading the input pins */
+ val = *data_in[priv->minor];
+ if (copy_to_user((unsigned long*)arg, &val, sizeof(val)))
+ return -EFAULT;
+ return 0;
+ break;
+ case IO_READ_OUTBITS:
+ /* *arg is result of reading the output shadow */
+ val = *data_out[priv->minor];
+ if (copy_to_user((unsigned long*)arg, &val, sizeof(val)))
+ return -EFAULT;
+ break;
+ case IO_SETGET_INPUT:
+ /* bits set in *arg is set to input,
+ * *arg updated with current input pins.
+ */
+ if (copy_from_user(&val, (unsigned long*)arg, sizeof(val)))
+ return -EFAULT;
+ val = setget_input(priv, val);
+ if (copy_to_user((unsigned long*)arg, &val, sizeof(val)))
+ return -EFAULT;
+ break;
+ case IO_SETGET_OUTPUT:
+ /* bits set in *arg is set to output,
+ * *arg updated with current output pins.
+ */
+ if (copy_from_user(&val, (unsigned long*)arg, sizeof(val)))
+ return -EFAULT;
+ val = setget_output(priv, val);
+ if (copy_to_user((unsigned long*)arg, &val, sizeof(val)))
+ return -EFAULT;
+ break;
+ default:
+ if (priv->minor == GPIO_MINOR_LEDS)
+ return gpio_leds_ioctl(cmd, arg);
+ else
+ return -EINVAL;
+ } /* switch */
+
+ return 0;
+}
+
+static int
+gpio_leds_ioctl(unsigned int cmd, unsigned long arg)
+{
+ unsigned char green;
+ unsigned char red;
+
+ switch (_IOC_NR(cmd)) {
+ case IO_LEDACTIVE_SET:
+ green = ((unsigned char) arg) & 1;
+ red = (((unsigned char) arg) >> 1) & 1;
+ LED_ACTIVE_SET_G(green);
+ LED_ACTIVE_SET_R(red);
+ break;
+
+ default:
+ return -EINVAL;
+ } /* switch */
+
+ return 0;
+}
+
+struct file_operations gpio_fops = {
+ .owner = THIS_MODULE,
+ .poll = gpio_poll,
+ .ioctl = gpio_ioctl,
+ .write = gpio_write,
+ .open = gpio_open,
+ .release = gpio_release,
+};
+
+
+/* main driver initialization routine, called from mem.c */
+
+static __init int
+gpio_init(void)
+{
+ int res;
+ reg_intr_vect_rw_mask intr_mask;
+
+ /* do the formalities */
+
+ res = register_chrdev(GPIO_MAJOR, gpio_name, &gpio_fops);
+ if (res < 0) {
+ printk(KERN_ERR "gpio: couldn't get a major number.\n");
+ return res;
+ }
+
+ /* Clear all leds */
+ LED_NETWORK_SET(0);
+ LED_ACTIVE_SET(0);
+ LED_DISK_READ(0);
+ LED_DISK_WRITE(0);
+
+ printk("ETRAX FS GPIO driver v2.5, (c) 2003-2005 Axis Communications AB\n");
+ /* We call etrax_gpio_wake_up_check() from timer interrupt and
+ * from cpu_idle() in kernel/process.c
+ * The check in cpu_idle() reduces latency from ~15 ms to ~6 ms
+ * in some tests.
+ */
+ if (request_irq(TIMER_INTR_VECT, gpio_poll_timer_interrupt,
+ SA_SHIRQ | SA_INTERRUPT,"gpio poll", &alarmlist)) {
+ printk("err: timer0 irq for gpio\n");
+ }
+ if (request_irq(GEN_IO_INTR_VECT, gpio_pa_interrupt,
+ SA_SHIRQ | SA_INTERRUPT,"gpio PA", &alarmlist)) {
+ printk("err: PA irq for gpio\n");
+ }
+ /* enable the gio and timer irq in global config */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.timer = 1;
+ intr_mask.gen_io = 1;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ return res;
+}
+
+/* this makes sure that gpio_init is called during kernel boot */
+
+module_init(gpio_init);
diff --git a/arch/cris/arch-v32/drivers/i2c.c b/arch/cris/arch-v32/drivers/i2c.c
new file mode 100644
index 000000000000..440c20a94963
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/i2c.c
@@ -0,0 +1,611 @@
+/*!***************************************************************************
+*!
+*! FILE NAME : i2c.c
+*!
+*! DESCRIPTION: implements an interface for IIC/I2C, both directly from other
+*! kernel modules (i2c_writereg/readreg) and from userspace using
+*! ioctl()'s
+*!
+*! Nov 30 1998 Torbjorn Eliasson Initial version.
+*! Bjorn Wesen Elinux kernel version.
+*! Jan 14 2000 Johan Adolfsson Fixed PB shadow register stuff -
+*! don't use PB_I2C if DS1302 uses same bits,
+*! use PB.
+*| June 23 2003 Pieter Grimmerink Added 'i2c_sendnack'. i2c_readreg now
+*| generates nack on last received byte,
+*| instead of ack.
+*| i2c_getack changed data level while clock
+*| was high, causing DS75 to see a stop condition
+*!
+*! ---------------------------------------------------------------------------
+*!
+*! (C) Copyright 1999-2002 Axis Communications AB, LUND, SWEDEN
+*!
+*!***************************************************************************/
+/* $Id: i2c.c,v 1.2 2005/05/09 15:29:49 starvik Exp $ */
+/****************** INCLUDE FILES SECTION ***********************************/
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/config.h>
+
+#include <asm/etraxi2c.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/delay.h>
+
+#include "i2c.h"
+
+/****************** I2C DEFINITION SECTION *************************/
+
+#define D(x)
+
+#define I2C_MAJOR 123 /* LOCAL/EXPERIMENTAL */
+static const char i2c_name[] = "i2c";
+
+#define CLOCK_LOW_TIME 8
+#define CLOCK_HIGH_TIME 8
+#define START_CONDITION_HOLD_TIME 8
+#define STOP_CONDITION_HOLD_TIME 8
+#define ENABLE_OUTPUT 0x01
+#define ENABLE_INPUT 0x00
+#define I2C_CLOCK_HIGH 1
+#define I2C_CLOCK_LOW 0
+#define I2C_DATA_HIGH 1
+#define I2C_DATA_LOW 0
+
+#define i2c_enable()
+#define i2c_disable()
+
+/* enable or disable output-enable, to select output or input on the i2c bus */
+
+#define i2c_dir_out() crisv32_io_set_dir(&cris_i2c_data, crisv32_io_dir_out)
+#define i2c_dir_in() crisv32_io_set_dir(&cris_i2c_data, crisv32_io_dir_in)
+
+/* control the i2c clock and data signals */
+
+#define i2c_clk(x) crisv32_io_set(&cris_i2c_clk, x)
+#define i2c_data(x) crisv32_io_set(&cris_i2c_data, x)
+
+/* read a bit from the i2c interface */
+
+#define i2c_getbit() crisv32_io_rd(&cris_i2c_data)
+
+#define i2c_delay(usecs) udelay(usecs)
+
+/****************** VARIABLE SECTION ************************************/
+
+static struct crisv32_iopin cris_i2c_clk;
+static struct crisv32_iopin cris_i2c_data;
+
+/****************** FUNCTION DEFINITION SECTION *************************/
+
+
+/* generate i2c start condition */
+
+void
+i2c_start(void)
+{
+ /*
+ * SCL=1 SDA=1
+ */
+ i2c_dir_out();
+ i2c_delay(CLOCK_HIGH_TIME/6);
+ i2c_data(I2C_DATA_HIGH);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ /*
+ * SCL=1 SDA=0
+ */
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(START_CONDITION_HOLD_TIME);
+ /*
+ * SCL=0 SDA=0
+ */
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+}
+
+/* generate i2c stop condition */
+
+void
+i2c_stop(void)
+{
+ i2c_dir_out();
+
+ /*
+ * SCL=0 SDA=0
+ */
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(CLOCK_LOW_TIME*2);
+ /*
+ * SCL=1 SDA=0
+ */
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME*2);
+ /*
+ * SCL=1 SDA=1
+ */
+ i2c_data(I2C_DATA_HIGH);
+ i2c_delay(STOP_CONDITION_HOLD_TIME);
+
+ i2c_dir_in();
+}
+
+/* write a byte to the i2c interface */
+
+void
+i2c_outbyte(unsigned char x)
+{
+ int i;
+
+ i2c_dir_out();
+
+ for (i = 0; i < 8; i++) {
+ if (x & 0x80) {
+ i2c_data(I2C_DATA_HIGH);
+ } else {
+ i2c_data(I2C_DATA_LOW);
+ }
+
+ i2c_delay(CLOCK_LOW_TIME/2);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME/2);
+ x <<= 1;
+ }
+ i2c_data(I2C_DATA_LOW);
+ i2c_delay(CLOCK_LOW_TIME/2);
+
+ /*
+ * enable input
+ */
+ i2c_dir_in();
+}
+
+/* read a byte from the i2c interface */
+
+unsigned char
+i2c_inbyte(void)
+{
+ unsigned char aBitByte = 0;
+ int i;
+
+ /* Switch off I2C to get bit */
+ i2c_disable();
+ i2c_dir_in();
+ i2c_delay(CLOCK_HIGH_TIME/2);
+
+ /* Get bit */
+ aBitByte |= i2c_getbit();
+
+ /* Enable I2C */
+ i2c_enable();
+ i2c_delay(CLOCK_LOW_TIME/2);
+
+ for (i = 1; i < 8; i++) {
+ aBitByte <<= 1;
+ /* Clock pulse */
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+
+ /* Switch off I2C to get bit */
+ i2c_disable();
+ i2c_dir_in();
+ i2c_delay(CLOCK_HIGH_TIME/2);
+
+ /* Get bit */
+ aBitByte |= i2c_getbit();
+
+ /* Enable I2C */
+ i2c_enable();
+ i2c_delay(CLOCK_LOW_TIME/2);
+ }
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+
+ /*
+ * we leave the clock low, getbyte is usually followed
+ * by sendack/nack, they assume the clock to be low
+ */
+ i2c_clk(I2C_CLOCK_LOW);
+ return aBitByte;
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_getack
+*#
+*# DESCRIPTION : checks if ack was received from ic2
+*#
+*#--------------------------------------------------------------------------*/
+
+int
+i2c_getack(void)
+{
+ int ack = 1;
+ /*
+ * enable output
+ */
+ i2c_dir_out();
+ /*
+ * Release data bus by setting
+ * data high
+ */
+ i2c_data(I2C_DATA_HIGH);
+ /*
+ * enable input
+ */
+ i2c_dir_in();
+ i2c_delay(CLOCK_HIGH_TIME/4);
+ /*
+ * generate ACK clock pulse
+ */
+ i2c_clk(I2C_CLOCK_HIGH);
+ /*
+ * Use PORT PB instead of I2C
+ * for input. (I2C not working)
+ */
+ i2c_clk(1);
+ i2c_data(1);
+ /*
+ * switch off I2C
+ */
+ i2c_data(1);
+ i2c_disable();
+ i2c_dir_in();
+ /*
+ * now wait for ack
+ */
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ /*
+ * check for ack
+ */
+ if(i2c_getbit())
+ ack = 0;
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ if(!ack){
+ if(!i2c_getbit()) /* receiver pulld SDA low */
+ ack = 1;
+ i2c_delay(CLOCK_HIGH_TIME/2);
+ }
+
+ /*
+ * our clock is high now, make sure data is low
+ * before we enable our output. If we keep data high
+ * and enable output, we would generate a stop condition.
+ */
+ i2c_data(I2C_DATA_LOW);
+
+ /*
+ * end clock pulse
+ */
+ i2c_enable();
+ i2c_dir_out();
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_HIGH_TIME/4);
+ /*
+ * enable output
+ */
+ i2c_dir_out();
+ /*
+ * remove ACK clock pulse
+ */
+ i2c_data(I2C_DATA_HIGH);
+ i2c_delay(CLOCK_LOW_TIME/2);
+ return ack;
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: I2C::sendAck
+*#
+*# DESCRIPTION : Send ACK on received data
+*#
+*#--------------------------------------------------------------------------*/
+void
+i2c_sendack(void)
+{
+ /*
+ * enable output
+ */
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_dir_out();
+ /*
+ * set ack pulse high
+ */
+ i2c_data(I2C_DATA_LOW);
+ /*
+ * generate clock pulse
+ */
+ i2c_delay(CLOCK_HIGH_TIME/6);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME/6);
+ /*
+ * reset data out
+ */
+ i2c_data(I2C_DATA_HIGH);
+ i2c_delay(CLOCK_LOW_TIME);
+
+ i2c_dir_in();
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_sendnack
+*#
+*# DESCRIPTION : Sends NACK on received data
+*#
+*#--------------------------------------------------------------------------*/
+void
+i2c_sendnack(void)
+{
+ /*
+ * enable output
+ */
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_dir_out();
+ /*
+ * set data high
+ */
+ i2c_data(I2C_DATA_HIGH);
+ /*
+ * generate clock pulse
+ */
+ i2c_delay(CLOCK_HIGH_TIME/6);
+ i2c_clk(I2C_CLOCK_HIGH);
+ i2c_delay(CLOCK_HIGH_TIME);
+ i2c_clk(I2C_CLOCK_LOW);
+ i2c_delay(CLOCK_LOW_TIME);
+
+ i2c_dir_in();
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_writereg
+*#
+*# DESCRIPTION : Writes a value to an I2C device
+*#
+*#--------------------------------------------------------------------------*/
+int
+i2c_writereg(unsigned char theSlave, unsigned char theReg,
+ unsigned char theValue)
+{
+ int error, cntr = 3;
+ unsigned long flags;
+
+ do {
+ error = 0;
+ /*
+ * we don't like to be interrupted
+ */
+ local_irq_save(flags);
+
+ i2c_start();
+ /*
+ * send slave address
+ */
+ i2c_outbyte((theSlave & 0xfe));
+ /*
+ * wait for ack
+ */
+ if(!i2c_getack())
+ error = 1;
+ /*
+ * now select register
+ */
+ i2c_dir_out();
+ i2c_outbyte(theReg);
+ /*
+ * now it's time to wait for ack
+ */
+ if(!i2c_getack())
+ error |= 2;
+ /*
+ * send register register data
+ */
+ i2c_outbyte(theValue);
+ /*
+ * now it's time to wait for ack
+ */
+ if(!i2c_getack())
+ error |= 4;
+ /*
+ * end byte stream
+ */
+ i2c_stop();
+ /*
+ * enable interrupt again
+ */
+ local_irq_restore(flags);
+
+ } while(error && cntr--);
+
+ i2c_delay(CLOCK_LOW_TIME);
+
+ return -error;
+}
+
+/*#---------------------------------------------------------------------------
+*#
+*# FUNCTION NAME: i2c_readreg
+*#
+*# DESCRIPTION : Reads a value from the decoder registers.
+*#
+*#--------------------------------------------------------------------------*/
+unsigned char
+i2c_readreg(unsigned char theSlave, unsigned char theReg)
+{
+ unsigned char b = 0;
+ int error, cntr = 3;
+ unsigned long flags;
+
+ do {
+ error = 0;
+ /*
+ * we don't like to be interrupted
+ */
+ local_irq_save(flags);
+ /*
+ * generate start condition
+ */
+ i2c_start();
+
+ /*
+ * send slave address
+ */
+ i2c_outbyte((theSlave & 0xfe));
+ /*
+ * wait for ack
+ */
+ if(!i2c_getack())
+ error = 1;
+ /*
+ * now select register
+ */
+ i2c_dir_out();
+ i2c_outbyte(theReg);
+ /*
+ * now it's time to wait for ack
+ */
+ if(!i2c_getack())
+ error = 1;
+ /*
+ * repeat start condition
+ */
+ i2c_delay(CLOCK_LOW_TIME);
+ i2c_start();
+ /*
+ * send slave address
+ */
+ i2c_outbyte(theSlave | 0x01);
+ /*
+ * wait for ack
+ */
+ if(!i2c_getack())
+ error = 1;
+ /*
+ * fetch register
+ */
+ b = i2c_inbyte();
+ /*
+ * last received byte needs to be nacked
+ * instead of acked
+ */
+ i2c_sendnack();
+ /*
+ * end sequence
+ */
+ i2c_stop();
+ /*
+ * enable interrupt again
+ */
+ local_irq_restore(flags);
+
+ } while(error && cntr--);
+
+ return b;
+}
+
+static int
+i2c_open(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+static int
+i2c_release(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+/* Main device API. ioctl's to write or read to/from i2c registers.
+ */
+
+static int
+i2c_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ if(_IOC_TYPE(cmd) != ETRAXI2C_IOCTYPE) {
+ return -EINVAL;
+ }
+
+ switch (_IOC_NR(cmd)) {
+ case I2C_WRITEREG:
+ /* write to an i2c slave */
+ D(printk("i2cw %d %d %d\n",
+ I2C_ARGSLAVE(arg),
+ I2C_ARGREG(arg),
+ I2C_ARGVALUE(arg)));
+
+ return i2c_writereg(I2C_ARGSLAVE(arg),
+ I2C_ARGREG(arg),
+ I2C_ARGVALUE(arg));
+ case I2C_READREG:
+ {
+ unsigned char val;
+ /* read from an i2c slave */
+ D(printk("i2cr %d %d ",
+ I2C_ARGSLAVE(arg),
+ I2C_ARGREG(arg)));
+ val = i2c_readreg(I2C_ARGSLAVE(arg), I2C_ARGREG(arg));
+ D(printk("= %d\n", val));
+ return val;
+ }
+ default:
+ return -EINVAL;
+
+ }
+
+ return 0;
+}
+
+static struct file_operations i2c_fops = {
+ owner: THIS_MODULE,
+ ioctl: i2c_ioctl,
+ open: i2c_open,
+ release: i2c_release,
+};
+
+int __init
+i2c_init(void)
+{
+ int res;
+
+ /* Setup and enable the Port B I2C interface */
+
+ crisv32_io_get_name(&cris_i2c_data, CONFIG_ETRAX_I2C_DATA_PORT);
+ crisv32_io_get_name(&cris_i2c_clk, CONFIG_ETRAX_I2C_CLK_PORT);
+
+ /* register char device */
+
+ res = register_chrdev(I2C_MAJOR, i2c_name, &i2c_fops);
+ if(res < 0) {
+ printk(KERN_ERR "i2c: couldn't get a major number.\n");
+ return res;
+ }
+
+ printk(KERN_INFO "I2C driver v2.2, (c) 1999-2001 Axis Communications AB\n");
+
+ return 0;
+}
+
+/* this makes sure that i2c_init is called during boot */
+
+module_init(i2c_init);
+
+/****************** END OF FILE i2c.c ********************************/
diff --git a/arch/cris/arch-v32/drivers/i2c.h b/arch/cris/arch-v32/drivers/i2c.h
new file mode 100644
index 000000000000..bfe1a13f9f35
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/i2c.h
@@ -0,0 +1,15 @@
+
+#include <linux/init.h>
+
+/* High level I2C actions */
+int __init i2c_init(void);
+int i2c_writereg(unsigned char theSlave, unsigned char theReg, unsigned char theValue);
+unsigned char i2c_readreg(unsigned char theSlave, unsigned char theReg);
+
+/* Low level I2C */
+void i2c_start(void);
+void i2c_stop(void);
+void i2c_outbyte(unsigned char x);
+unsigned char i2c_inbyte(void);
+int i2c_getack(void);
+void i2c_sendack(void);
diff --git a/arch/cris/arch-v32/drivers/iop_fw_load.c b/arch/cris/arch-v32/drivers/iop_fw_load.c
new file mode 100644
index 000000000000..11f9895ded50
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/iop_fw_load.c
@@ -0,0 +1,219 @@
+/* $Id: iop_fw_load.c,v 1.4 2005/04/07 09:27:46 larsv Exp $
+ *
+ * Firmware loader for ETRAX FS IO-Processor
+ *
+ * Copyright (C) 2004 Axis Communications AB
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/iop/iop_reg_space.h>
+#include <asm/arch/hwregs/iop/iop_mpu_macros.h>
+#include <asm/arch/hwregs/iop/iop_mpu_defs.h>
+#include <asm/arch/hwregs/iop/iop_spu_defs.h>
+#include <asm/arch/hwregs/iop/iop_sw_cpu_defs.h>
+
+#define IOP_TIMEOUT 100
+
+static struct device iop_spu_device[2] = {
+ { .bus_id = "iop-spu0", },
+ { .bus_id = "iop-spu1", },
+};
+
+static struct device iop_mpu_device = {
+ .bus_id = "iop-mpu",
+};
+
+static int wait_mpu_idle(void)
+{
+ reg_iop_mpu_r_stat mpu_stat;
+ unsigned int timeout = IOP_TIMEOUT;
+
+ do {
+ mpu_stat = REG_RD(iop_mpu, regi_iop_mpu, r_stat);
+ } while (mpu_stat.instr_reg_busy == regk_iop_mpu_yes && --timeout > 0);
+ if (timeout == 0) {
+ printk(KERN_ERR "Timeout waiting for MPU to be idle\n");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+int iop_fw_load_spu(const unsigned char *fw_name, unsigned int spu_inst)
+{
+ reg_iop_sw_cpu_rw_mc_ctrl mc_ctrl = {
+ .wr_spu0_mem = regk_iop_sw_cpu_no,
+ .wr_spu1_mem = regk_iop_sw_cpu_no,
+ .size = 4,
+ .cmd = regk_iop_sw_cpu_reg_copy,
+ .keep_owner = regk_iop_sw_cpu_yes
+ };
+ reg_iop_spu_rw_ctrl spu_ctrl = {
+ .en = regk_iop_spu_no,
+ .fsm = regk_iop_spu_no,
+ };
+ reg_iop_sw_cpu_r_mc_stat mc_stat;
+ const struct firmware *fw_entry;
+ u32 *data;
+ unsigned int timeout;
+ int retval, i;
+
+ if (spu_inst > 1)
+ return -ENODEV;
+
+ /* get firmware */
+ retval = request_firmware(&fw_entry,
+ fw_name,
+ &iop_spu_device[spu_inst]);
+ if (retval != 0)
+ {
+ printk(KERN_ERR
+ "iop_load_spu: Failed to load firmware \"%s\"\n",
+ fw_name);
+ return retval;
+ }
+ data = (u32 *) fw_entry->data;
+
+ /* acquire ownership of memory controller */
+ switch (spu_inst) {
+ case 0:
+ mc_ctrl.wr_spu0_mem = regk_iop_sw_cpu_yes;
+ REG_WR(iop_spu, regi_iop_spu0, rw_ctrl, spu_ctrl);
+ break;
+ case 1:
+ mc_ctrl.wr_spu1_mem = regk_iop_sw_cpu_yes;
+ REG_WR(iop_spu, regi_iop_spu1, rw_ctrl, spu_ctrl);
+ break;
+ }
+ timeout = IOP_TIMEOUT;
+ do {
+ REG_WR(iop_sw_cpu, regi_iop_sw_cpu, rw_mc_ctrl, mc_ctrl);
+ mc_stat = REG_RD(iop_sw_cpu, regi_iop_sw_cpu, r_mc_stat);
+ } while (mc_stat.owned_by_cpu == regk_iop_sw_cpu_no && --timeout > 0);
+ if (timeout == 0) {
+ printk(KERN_ERR "Timeout waiting to acquire MC\n");
+ retval = -EBUSY;
+ goto out;
+ }
+
+ /* write to SPU memory */
+ for (i = 0; i < (fw_entry->size/4); i++) {
+ switch (spu_inst) {
+ case 0:
+ REG_WR_INT(iop_spu, regi_iop_spu0, rw_seq_pc, (i*4));
+ break;
+ case 1:
+ REG_WR_INT(iop_spu, regi_iop_spu1, rw_seq_pc, (i*4));
+ break;
+ }
+ REG_WR_INT(iop_sw_cpu, regi_iop_sw_cpu, rw_mc_data, *data);
+ data++;
+ }
+
+ /* release ownership of memory controller */
+ (void) REG_RD(iop_sw_cpu, regi_iop_sw_cpu, rs_mc_data);
+
+ out:
+ release_firmware(fw_entry);
+ return retval;
+}
+
+int iop_fw_load_mpu(unsigned char *fw_name)
+{
+ const unsigned int start_addr = 0;
+ reg_iop_mpu_rw_ctrl mpu_ctrl;
+ const struct firmware *fw_entry;
+ u32 *data;
+ int retval, i;
+
+ /* get firmware */
+ retval = request_firmware(&fw_entry, fw_name, &iop_mpu_device);
+ if (retval != 0)
+ {
+ printk(KERN_ERR
+ "iop_load_spu: Failed to load firmware \"%s\"\n",
+ fw_name);
+ return retval;
+ }
+ data = (u32 *) fw_entry->data;
+
+ /* disable MPU */
+ mpu_ctrl.en = regk_iop_mpu_no;
+ REG_WR(iop_mpu, regi_iop_mpu, rw_ctrl, mpu_ctrl);
+ /* put start address in R0 */
+ REG_WR_VECT(iop_mpu, regi_iop_mpu, rw_r, 0, start_addr);
+ /* write to memory by executing 'SWX i, 4, R0' for each word */
+ if ((retval = wait_mpu_idle()) != 0)
+ goto out;
+ REG_WR(iop_mpu, regi_iop_mpu, rw_instr, MPU_SWX_IIR_INSTR(0, 4, 0));
+ for (i = 0; i < (fw_entry->size / 4); i++) {
+ REG_WR_INT(iop_mpu, regi_iop_mpu, rw_immediate, *data);
+ if ((retval = wait_mpu_idle()) != 0)
+ goto out;
+ data++;
+ }
+
+ out:
+ release_firmware(fw_entry);
+ return retval;
+}
+
+int iop_start_mpu(unsigned int start_addr)
+{
+ reg_iop_mpu_rw_ctrl mpu_ctrl = { .en = regk_iop_mpu_yes };
+ int retval;
+
+ /* disable MPU */
+ if ((retval = wait_mpu_idle()) != 0)
+ goto out;
+ REG_WR(iop_mpu, regi_iop_mpu, rw_instr, MPU_HALT());
+ if ((retval = wait_mpu_idle()) != 0)
+ goto out;
+ /* set PC and wait for it to bite */
+ if ((retval = wait_mpu_idle()) != 0)
+ goto out;
+ REG_WR_INT(iop_mpu, regi_iop_mpu, rw_instr, MPU_BA_I(start_addr));
+ if ((retval = wait_mpu_idle()) != 0)
+ goto out;
+ /* make sure the MPU starts executing with interrupts disabled */
+ REG_WR(iop_mpu, regi_iop_mpu, rw_instr, MPU_DI());
+ if ((retval = wait_mpu_idle()) != 0)
+ goto out;
+ /* enable MPU */
+ REG_WR(iop_mpu, regi_iop_mpu, rw_ctrl, mpu_ctrl);
+ out:
+ return retval;
+}
+
+static int __init iop_fw_load_init(void)
+{
+ device_initialize(&iop_spu_device[0]);
+ kobject_set_name(&iop_spu_device[0].kobj, "iop-spu0");
+ kobject_add(&iop_spu_device[0].kobj);
+ device_initialize(&iop_spu_device[1]);
+ kobject_set_name(&iop_spu_device[1].kobj, "iop-spu1");
+ kobject_add(&iop_spu_device[1].kobj);
+ device_initialize(&iop_mpu_device);
+ kobject_set_name(&iop_mpu_device.kobj, "iop-mpu");
+ kobject_add(&iop_mpu_device.kobj);
+ return 0;
+}
+
+static void __exit iop_fw_load_exit(void)
+{
+}
+
+module_init(iop_fw_load_init);
+module_exit(iop_fw_load_exit);
+
+MODULE_DESCRIPTION("ETRAX FS IO-Processor Firmware Loader");
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(iop_fw_load_spu);
+EXPORT_SYMBOL(iop_fw_load_mpu);
+EXPORT_SYMBOL(iop_start_mpu);
diff --git a/arch/cris/arch-v32/drivers/nandflash.c b/arch/cris/arch-v32/drivers/nandflash.c
new file mode 100644
index 000000000000..fc2a619b035d
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/nandflash.c
@@ -0,0 +1,157 @@
+/*
+ * arch/cris/arch-v32/drivers/nandflash.c
+ *
+ * Copyright (c) 2004
+ *
+ * Derived from drivers/mtd/nand/spia.c
+ * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
+ *
+ * $Id: nandflash.c,v 1.3 2005/06/01 10:57:12 starvik Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/arch/memmap.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/gio_defs.h>
+#include <asm/arch/hwregs/bif_core_defs.h>
+#include <asm/io.h>
+
+#define CE_BIT 4
+#define CLE_BIT 5
+#define ALE_BIT 6
+#define BY_BIT 7
+
+static struct mtd_info *crisv32_mtd = NULL;
+/*
+ * hardware specific access to control-lines
+*/
+static void crisv32_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ unsigned long flags;
+ reg_gio_rw_pa_dout dout = REG_RD(gio, regi_gio, rw_pa_dout);
+
+ local_irq_save(flags);
+ switch(cmd){
+ case NAND_CTL_SETCLE:
+ dout.data |= (1<<CLE_BIT);
+ break;
+ case NAND_CTL_CLRCLE:
+ dout.data &= ~(1<<CLE_BIT);
+ break;
+ case NAND_CTL_SETALE:
+ dout.data |= (1<<ALE_BIT);
+ break;
+ case NAND_CTL_CLRALE:
+ dout.data &= ~(1<<ALE_BIT);
+ break;
+ case NAND_CTL_SETNCE:
+ dout.data |= (1<<CE_BIT);
+ break;
+ case NAND_CTL_CLRNCE:
+ dout.data &= ~(1<<CE_BIT);
+ break;
+ }
+ REG_WR(gio, regi_gio, rw_pa_dout, dout);
+ local_irq_restore(flags);
+}
+
+/*
+* read device ready pin
+*/
+int crisv32_device_ready(struct mtd_info *mtd)
+{
+ reg_gio_r_pa_din din = REG_RD(gio, regi_gio, r_pa_din);
+ return ((din.data & (1 << BY_BIT)) >> BY_BIT);
+}
+
+/*
+ * Main initialization routine
+ */
+struct mtd_info* __init crisv32_nand_flash_probe (void)
+{
+ void __iomem *read_cs;
+ void __iomem *write_cs;
+
+ reg_bif_core_rw_grp3_cfg bif_cfg = REG_RD(bif_core, regi_bif_core, rw_grp3_cfg);
+ reg_gio_rw_pa_oe pa_oe = REG_RD(gio, regi_gio, rw_pa_oe);
+ struct nand_chip *this;
+ int err = 0;
+
+ /* Allocate memory for MTD device structure and private data */
+ crisv32_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
+ GFP_KERNEL);
+ if (!crisv32_mtd) {
+ printk ("Unable to allocate CRISv32 NAND MTD device structure.\n");
+ err = -ENOMEM;
+ return NULL;
+ }
+
+ read_cs = ioremap(MEM_CSP0_START | MEM_NON_CACHEABLE, 8192);
+ write_cs = ioremap(MEM_CSP1_START | MEM_NON_CACHEABLE, 8192);
+
+ if (!read_cs || !write_cs) {
+ printk("CRISv32 NAND ioremap failed\n");
+ err = -EIO;
+ goto out_mtd;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&crisv32_mtd[1]);
+
+ pa_oe.oe |= 1 << CE_BIT;
+ pa_oe.oe |= 1 << ALE_BIT;
+ pa_oe.oe |= 1 << CLE_BIT;
+ pa_oe.oe &= ~ (1 << BY_BIT);
+ REG_WR(gio, regi_gio, rw_pa_oe, pa_oe);
+
+ bif_cfg.gated_csp0 = regk_bif_core_rd;
+ bif_cfg.gated_csp1 = regk_bif_core_wr;
+ REG_WR(bif_core, regi_bif_core, rw_grp3_cfg, bif_cfg);
+
+ /* Initialize structures */
+ memset((char *) crisv32_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ crisv32_mtd->priv = this;
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = read_cs;
+ this->IO_ADDR_W = write_cs;
+ this->hwcontrol = crisv32_hwcontrol;
+ this->dev_ready = crisv32_device_ready;
+ /* 20 us command delay time */
+ this->chip_delay = 20;
+ this->eccmode = NAND_ECC_SOFT;
+
+ /* Enable the following for a flash based bad block table */
+ this->options = NAND_USE_FLASH_BBT;
+
+ /* Scan to find existance of the device */
+ if (nand_scan (crisv32_mtd, 1)) {
+ err = -ENXIO;
+ goto out_ior;
+ }
+
+ return crisv32_mtd;
+
+out_ior:
+ iounmap((void *)read_cs);
+ iounmap((void *)write_cs);
+out_mtd:
+ kfree (crisv32_mtd);
+ return NULL;
+}
+
diff --git a/arch/cris/arch-v32/drivers/pcf8563.c b/arch/cris/arch-v32/drivers/pcf8563.c
new file mode 100644
index 000000000000..f894580b648b
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/pcf8563.c
@@ -0,0 +1,341 @@
+/*
+ * PCF8563 RTC
+ *
+ * From Phillips' datasheet:
+ *
+ * The PCF8563 is a CMOS real-time clock/calendar optimized for low power
+ * consumption. A programmable clock output, interupt output and voltage
+ * low detector are also provided. All address and data are transferred
+ * serially via two-line bidirectional I2C-bus. Maximum bus speed is
+ * 400 kbits/s. The built-in word address register is incremented
+ * automatically after each written or read byte.
+ *
+ * Copyright (c) 2002-2003, Axis Communications AB
+ * All rights reserved.
+ *
+ * Author: Tobias Anderberg <tobiasa@axis.com>.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <linux/bcd.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/rtc.h>
+
+#include "i2c.h"
+
+#define PCF8563_MAJOR 121 /* Local major number. */
+#define DEVICE_NAME "rtc" /* Name which is registered in /proc/devices. */
+#define PCF8563_NAME "PCF8563"
+#define DRIVER_VERSION "$Revision: 1.1 $"
+
+/* Two simple wrapper macros, saves a few keystrokes. */
+#define rtc_read(x) i2c_readreg(RTC_I2C_READ, x)
+#define rtc_write(x,y) i2c_writereg(RTC_I2C_WRITE, x, y)
+
+static const unsigned char days_in_month[] =
+ { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+int pcf8563_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
+int pcf8563_open(struct inode *, struct file *);
+int pcf8563_release(struct inode *, struct file *);
+
+static struct file_operations pcf8563_fops = {
+ owner: THIS_MODULE,
+ ioctl: pcf8563_ioctl,
+ open: pcf8563_open,
+ release: pcf8563_release,
+};
+
+unsigned char
+pcf8563_readreg(int reg)
+{
+ unsigned char res = rtc_read(reg);
+
+ /* The PCF8563 does not return 0 for unimplemented bits */
+ switch (reg) {
+ case RTC_SECONDS:
+ case RTC_MINUTES:
+ res &= 0x7F;
+ break;
+ case RTC_HOURS:
+ case RTC_DAY_OF_MONTH:
+ res &= 0x3F;
+ break;
+ case RTC_WEEKDAY:
+ res &= 0x07;
+ break;
+ case RTC_MONTH:
+ res &= 0x1F;
+ break;
+ case RTC_CONTROL1:
+ res &= 0xA8;
+ break;
+ case RTC_CONTROL2:
+ res &= 0x1F;
+ break;
+ case RTC_CLOCKOUT_FREQ:
+ case RTC_TIMER_CONTROL:
+ res &= 0x83;
+ break;
+ }
+ return res;
+}
+
+void
+pcf8563_writereg(int reg, unsigned char val)
+{
+#ifdef CONFIG_ETRAX_RTC_READONLY
+ if (reg == RTC_CONTROL1 || (reg >= RTC_SECONDS && reg <= RTC_YEAR))
+ return;
+#endif
+
+ rtc_write(reg, val);
+}
+
+void
+get_rtc_time(struct rtc_time *tm)
+{
+ tm->tm_sec = rtc_read(RTC_SECONDS);
+ tm->tm_min = rtc_read(RTC_MINUTES);
+ tm->tm_hour = rtc_read(RTC_HOURS);
+ tm->tm_mday = rtc_read(RTC_DAY_OF_MONTH);
+ tm->tm_wday = rtc_read(RTC_WEEKDAY);
+ tm->tm_mon = rtc_read(RTC_MONTH);
+ tm->tm_year = rtc_read(RTC_YEAR);
+
+ if (tm->tm_sec & 0x80)
+ printk(KERN_WARNING "%s: RTC Voltage Low - reliable date/time "
+ "information is no longer guaranteed!\n", PCF8563_NAME);
+
+ tm->tm_year = BCD_TO_BIN(tm->tm_year) + ((tm->tm_mon & 0x80) ? 100 : 0);
+ tm->tm_sec &= 0x7F;
+ tm->tm_min &= 0x7F;
+ tm->tm_hour &= 0x3F;
+ tm->tm_mday &= 0x3F;
+ tm->tm_wday &= 0x07; /* Not coded in BCD. */
+ tm->tm_mon &= 0x1F;
+
+ BCD_TO_BIN(tm->tm_sec);
+ BCD_TO_BIN(tm->tm_min);
+ BCD_TO_BIN(tm->tm_hour);
+ BCD_TO_BIN(tm->tm_mday);
+ BCD_TO_BIN(tm->tm_mon);
+ tm->tm_mon--; /* Month is 1..12 in RTC but 0..11 in linux */
+}
+
+int __init
+pcf8563_init(void)
+{
+ /* Initiate the i2c protocol. */
+ i2c_init();
+
+ /*
+ * First of all we need to reset the chip. This is done by
+ * clearing control1, control2 and clk freq and resetting
+ * all alarms.
+ */
+ if (rtc_write(RTC_CONTROL1, 0x00) < 0)
+ goto err;
+
+ if (rtc_write(RTC_CONTROL2, 0x00) < 0)
+ goto err;
+
+ if (rtc_write(RTC_CLOCKOUT_FREQ, 0x00) < 0)
+ goto err;
+
+ if (rtc_write(RTC_TIMER_CONTROL, 0x03) < 0)
+ goto err;
+
+ /* Reset the alarms. */
+ if (rtc_write(RTC_MINUTE_ALARM, 0x80) < 0)
+ goto err;
+
+ if (rtc_write(RTC_HOUR_ALARM, 0x80) < 0)
+ goto err;
+
+ if (rtc_write(RTC_DAY_ALARM, 0x80) < 0)
+ goto err;
+
+ if (rtc_write(RTC_WEEKDAY_ALARM, 0x80) < 0)
+ goto err;
+
+ if (register_chrdev(PCF8563_MAJOR, DEVICE_NAME, &pcf8563_fops) < 0) {
+ printk(KERN_INFO "%s: Unable to get major numer %d for RTC device.\n",
+ PCF8563_NAME, PCF8563_MAJOR);
+ return -1;
+ }
+
+ printk(KERN_INFO "%s Real-Time Clock Driver, %s\n", PCF8563_NAME, DRIVER_VERSION);
+
+ /* Check for low voltage, and warn about it.. */
+ if (rtc_read(RTC_SECONDS) & 0x80)
+ printk(KERN_WARNING "%s: RTC Voltage Low - reliable date/time "
+ "information is no longer guaranteed!\n", PCF8563_NAME);
+
+ return 0;
+
+err:
+ printk(KERN_INFO "%s: Error initializing chip.\n", PCF8563_NAME);
+ return -1;
+}
+
+void __exit
+pcf8563_exit(void)
+{
+ if (unregister_chrdev(PCF8563_MAJOR, DEVICE_NAME) < 0) {
+ printk(KERN_INFO "%s: Unable to unregister device.\n", PCF8563_NAME);
+ }
+}
+
+/*
+ * ioctl calls for this driver. Why return -ENOTTY upon error? Because
+ * POSIX says so!
+ */
+int
+pcf8563_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ /* Some sanity checks. */
+ if (_IOC_TYPE(cmd) != RTC_MAGIC)
+ return -ENOTTY;
+
+ if (_IOC_NR(cmd) > RTC_MAX_IOCTL)
+ return -ENOTTY;
+
+ switch (cmd) {
+ case RTC_RD_TIME:
+ {
+ struct rtc_time tm;
+
+ memset(&tm, 0, sizeof (struct rtc_time));
+ get_rtc_time(&tm);
+
+ if (copy_to_user((struct rtc_time *) arg, &tm, sizeof tm)) {
+ return -EFAULT;
+ }
+
+ return 0;
+ }
+
+ case RTC_SET_TIME:
+ {
+#ifdef CONFIG_ETRAX_RTC_READONLY
+ return -EPERM;
+#else
+ int leap;
+ int year;
+ int century;
+ struct rtc_time tm;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+
+ if (copy_from_user(&tm, (struct rtc_time *) arg, sizeof tm))
+ return -EFAULT;
+
+ /* Convert from struct tm to struct rtc_time. */
+ tm.tm_year += 1900;
+ tm.tm_mon += 1;
+
+ /*
+ * Check if tm.tm_year is a leap year. A year is a leap
+ * year if it is divisible by 4 but not 100, except
+ * that years divisible by 400 _are_ leap years.
+ */
+ year = tm.tm_year;
+ leap = (tm.tm_mon == 2) && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
+
+ /* Perform some sanity checks. */
+ if ((tm.tm_year < 1970) ||
+ (tm.tm_mon > 12) ||
+ (tm.tm_mday == 0) ||
+ (tm.tm_mday > days_in_month[tm.tm_mon] + leap) ||
+ (tm.tm_wday >= 7) ||
+ (tm.tm_hour >= 24) ||
+ (tm.tm_min >= 60) ||
+ (tm.tm_sec >= 60))
+ return -EINVAL;
+
+ century = (tm.tm_year >= 2000) ? 0x80 : 0;
+ tm.tm_year = tm.tm_year % 100;
+
+ BIN_TO_BCD(tm.tm_year);
+ BIN_TO_BCD(tm.tm_mday);
+ BIN_TO_BCD(tm.tm_hour);
+ BIN_TO_BCD(tm.tm_min);
+ BIN_TO_BCD(tm.tm_sec);
+ tm.tm_mon |= century;
+
+ rtc_write(RTC_YEAR, tm.tm_year);
+ rtc_write(RTC_MONTH, tm.tm_mon);
+ rtc_write(RTC_WEEKDAY, tm.tm_wday); /* Not coded in BCD. */
+ rtc_write(RTC_DAY_OF_MONTH, tm.tm_mday);
+ rtc_write(RTC_HOURS, tm.tm_hour);
+ rtc_write(RTC_MINUTES, tm.tm_min);
+ rtc_write(RTC_SECONDS, tm.tm_sec);
+
+ return 0;
+#endif /* !CONFIG_ETRAX_RTC_READONLY */
+ }
+
+ case RTC_VLOW_RD:
+ {
+ int vl_bit = 0;
+
+ if (rtc_read(RTC_SECONDS) & 0x80) {
+ vl_bit = 1;
+ printk(KERN_WARNING "%s: RTC Voltage Low - reliable "
+ "date/time information is no longer guaranteed!\n",
+ PCF8563_NAME);
+ }
+ if (copy_to_user((int *) arg, &vl_bit, sizeof(int)))
+ return -EFAULT;
+
+ return 0;
+ }
+
+ case RTC_VLOW_SET:
+ {
+ /* Clear the VL bit in the seconds register */
+ int ret = rtc_read(RTC_SECONDS);
+
+ rtc_write(RTC_SECONDS, (ret & 0x7F));
+
+ return 0;
+ }
+
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+int
+pcf8563_open(struct inode *inode, struct file *filp)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+int
+pcf8563_release(struct inode *inode, struct file *filp)
+{
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+module_init(pcf8563_init);
+module_exit(pcf8563_exit);
diff --git a/arch/cris/arch-v32/drivers/pci/Makefile b/arch/cris/arch-v32/drivers/pci/Makefile
new file mode 100644
index 000000000000..bff7482f2444
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/pci/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for Etrax cardbus driver
+#
+
+obj-$(CONFIG_ETRAX_CARDBUS) += bios.o dma.o
diff --git a/arch/cris/arch-v32/drivers/pci/bios.c b/arch/cris/arch-v32/drivers/pci/bios.c
new file mode 100644
index 000000000000..24bc149889b6
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/pci/bios.c
@@ -0,0 +1,131 @@
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <asm/arch/hwregs/intr_vect.h>
+
+void __devinit pcibios_fixup_bus(struct pci_bus *b)
+{
+}
+
+char * __devinit pcibios_setup(char *str)
+{
+ return NULL;
+}
+
+void pcibios_set_master(struct pci_dev *dev)
+{
+ u8 lat;
+ pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat);
+ printk(KERN_DEBUG "PCI: Setting latency timer of device %s to %d\n", pci_name(dev), lat);
+ pci_write_config_byte(dev, PCI_LATENCY_TIMER, lat);
+}
+
+int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma,
+ enum pci_mmap_state mmap_state, int write_combine)
+{
+ unsigned long prot;
+
+ /* Leave vm_pgoff as-is, the PCI space address is the physical
+ * address on this platform.
+ */
+ vma->vm_flags |= (VM_SHM | VM_LOCKED | VM_IO);
+
+ prot = pgprot_val(vma->vm_page_prot);
+ vma->vm_page_prot = __pgprot(prot);
+
+ /* Write-combine setting is ignored, it is changed via the mtrr
+ * interfaces on this platform.
+ */
+ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot))
+ return -EAGAIN;
+
+ return 0;
+}
+
+void
+pcibios_align_resource(void *data, struct resource *res,
+ unsigned long size, unsigned long align)
+{
+ if (res->flags & IORESOURCE_IO) {
+ unsigned long start = res->start;
+
+ if (start & 0x300) {
+ start = (start + 0x3ff) & ~0x3ff;
+ res->start = start;
+ }
+ }
+}
+
+int pcibios_enable_resources(struct pci_dev *dev, int mask)
+{
+ u16 cmd, old_cmd;
+ int idx;
+ struct resource *r;
+
+ pci_read_config_word(dev, PCI_COMMAND, &cmd);
+ old_cmd = cmd;
+ for(idx=0; idx<6; idx++) {
+ /* Only set up the requested stuff */
+ if (!(mask & (1<<idx)))
+ continue;
+
+ r = &dev->resource[idx];
+ if (!r->start && r->end) {
+ printk(KERN_ERR "PCI: Device %s not available because of resource collisions\n", pci_name(dev));
+ return -EINVAL;
+ }
+ if (r->flags & IORESOURCE_IO)
+ cmd |= PCI_COMMAND_IO;
+ if (r->flags & IORESOURCE_MEM)
+ cmd |= PCI_COMMAND_MEMORY;
+ }
+ if (dev->resource[PCI_ROM_RESOURCE].start)
+ cmd |= PCI_COMMAND_MEMORY;
+ if (cmd != old_cmd) {
+ printk("PCI: Enabling device %s (%04x -> %04x)\n", pci_name(dev), old_cmd, cmd);
+ pci_write_config_word(dev, PCI_COMMAND, cmd);
+ }
+ return 0;
+}
+
+int pcibios_enable_irq(struct pci_dev *dev)
+{
+ dev->irq = EXT_INTR_VECT;
+ return 0;
+}
+
+int pcibios_enable_device(struct pci_dev *dev, int mask)
+{
+ int err;
+
+ if ((err = pcibios_enable_resources(dev, mask)) < 0)
+ return err;
+
+ return pcibios_enable_irq(dev);
+}
+
+int pcibios_assign_resources(void)
+{
+ struct pci_dev *dev = NULL;
+ int idx;
+ struct resource *r;
+
+ while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
+ int class = dev->class >> 8;
+
+ /* Don't touch classless devices and host bridges */
+ if (!class || class == PCI_CLASS_BRIDGE_HOST)
+ continue;
+
+ for(idx=0; idx<6; idx++) {
+ r = &dev->resource[idx];
+
+ if (!r->start && r->end)
+ pci_assign_resource(dev, idx);
+ }
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(pcibios_assign_resources);
diff --git a/arch/cris/arch-v32/drivers/pci/dma.c b/arch/cris/arch-v32/drivers/pci/dma.c
new file mode 100644
index 000000000000..10329306d23c
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/pci/dma.c
@@ -0,0 +1,149 @@
+/*
+ * Dynamic DMA mapping support.
+ *
+ * On cris there is no hardware dynamic DMA address translation,
+ * so consistent alloc/free are merely page allocation/freeing.
+ * The rest of the dynamic DMA mapping interface is implemented
+ * in asm/pci.h.
+ *
+ * Borrowed from i386.
+ */
+
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+
+struct dma_coherent_mem {
+ void *virt_base;
+ u32 device_base;
+ int size;
+ int flags;
+ unsigned long *bitmap;
+};
+
+void *dma_alloc_coherent(struct device *dev, size_t size,
+ dma_addr_t *dma_handle, unsigned int __nocast gfp)
+{
+ void *ret;
+ struct dma_coherent_mem *mem = dev ? dev->dma_mem : NULL;
+ int order = get_order(size);
+ /* ignore region specifiers */
+ gfp &= ~(__GFP_DMA | __GFP_HIGHMEM);
+
+ if (mem) {
+ int page = bitmap_find_free_region(mem->bitmap, mem->size,
+ order);
+ if (page >= 0) {
+ *dma_handle = mem->device_base + (page << PAGE_SHIFT);
+ ret = mem->virt_base + (page << PAGE_SHIFT);
+ memset(ret, 0, size);
+ return ret;
+ }
+ if (mem->flags & DMA_MEMORY_EXCLUSIVE)
+ return NULL;
+ }
+
+ if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff))
+ gfp |= GFP_DMA;
+
+ ret = (void *)__get_free_pages(gfp, order);
+
+ if (ret != NULL) {
+ memset(ret, 0, size);
+ *dma_handle = virt_to_phys(ret);
+ }
+ return ret;
+}
+
+void dma_free_coherent(struct device *dev, size_t size,
+ void *vaddr, dma_addr_t dma_handle)
+{
+ struct dma_coherent_mem *mem = dev ? dev->dma_mem : NULL;
+ int order = get_order(size);
+
+ if (mem && vaddr >= mem->virt_base && vaddr < (mem->virt_base + (mem->size << PAGE_SHIFT))) {
+ int page = (vaddr - mem->virt_base) >> PAGE_SHIFT;
+
+ bitmap_release_region(mem->bitmap, page, order);
+ } else
+ free_pages((unsigned long)vaddr, order);
+}
+
+int dma_declare_coherent_memory(struct device *dev, dma_addr_t bus_addr,
+ dma_addr_t device_addr, size_t size, int flags)
+{
+ void __iomem *mem_base;
+ int pages = size >> PAGE_SHIFT;
+ int bitmap_size = (pages + 31)/32;
+
+ if ((flags & (DMA_MEMORY_MAP | DMA_MEMORY_IO)) == 0)
+ goto out;
+ if (!size)
+ goto out;
+ if (dev->dma_mem)
+ goto out;
+
+ /* FIXME: this routine just ignores DMA_MEMORY_INCLUDES_CHILDREN */
+
+ mem_base = ioremap(bus_addr, size);
+ if (!mem_base)
+ goto out;
+
+ dev->dma_mem = kmalloc(sizeof(struct dma_coherent_mem), GFP_KERNEL);
+ if (!dev->dma_mem)
+ goto out;
+ memset(dev->dma_mem, 0, sizeof(struct dma_coherent_mem));
+ dev->dma_mem->bitmap = kmalloc(bitmap_size, GFP_KERNEL);
+ if (!dev->dma_mem->bitmap)
+ goto free1_out;
+ memset(dev->dma_mem->bitmap, 0, bitmap_size);
+
+ dev->dma_mem->virt_base = mem_base;
+ dev->dma_mem->device_base = device_addr;
+ dev->dma_mem->size = pages;
+ dev->dma_mem->flags = flags;
+
+ if (flags & DMA_MEMORY_MAP)
+ return DMA_MEMORY_MAP;
+
+ return DMA_MEMORY_IO;
+
+ free1_out:
+ kfree(dev->dma_mem->bitmap);
+ out:
+ return 0;
+}
+EXPORT_SYMBOL(dma_declare_coherent_memory);
+
+void dma_release_declared_memory(struct device *dev)
+{
+ struct dma_coherent_mem *mem = dev->dma_mem;
+
+ if(!mem)
+ return;
+ dev->dma_mem = NULL;
+ iounmap(mem->virt_base);
+ kfree(mem->bitmap);
+ kfree(mem);
+}
+EXPORT_SYMBOL(dma_release_declared_memory);
+
+void *dma_mark_declared_memory_occupied(struct device *dev,
+ dma_addr_t device_addr, size_t size)
+{
+ struct dma_coherent_mem *mem = dev->dma_mem;
+ int pages = (size + (device_addr & ~PAGE_MASK) + PAGE_SIZE - 1) >> PAGE_SHIFT;
+ int pos, err;
+
+ if (!mem)
+ return ERR_PTR(-EINVAL);
+
+ pos = (device_addr - mem->device_base) >> PAGE_SHIFT;
+ err = bitmap_allocate_region(mem->bitmap, pos, get_order(pages));
+ if (err != 0)
+ return ERR_PTR(err);
+ return mem->virt_base + (pos << PAGE_SHIFT);
+}
+EXPORT_SYMBOL(dma_mark_declared_memory_occupied);
diff --git a/arch/cris/arch-v32/drivers/sync_serial.c b/arch/cris/arch-v32/drivers/sync_serial.c
new file mode 100644
index 000000000000..c85a6df8558f
--- /dev/null
+++ b/arch/cris/arch-v32/drivers/sync_serial.c
@@ -0,0 +1,1283 @@
+/*
+ * Simple synchronous serial port driver for ETRAX FS.
+ *
+ * Copyright (c) 2005 Axis Communications AB
+ *
+ * Author: Mikael Starvik
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+
+#include <asm/io.h>
+#include <asm/arch/dma.h>
+#include <asm/arch/pinmux.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/sser_defs.h>
+#include <asm/arch/hwregs/dma_defs.h>
+#include <asm/arch/hwregs/dma.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+#include <asm/arch/hwregs/intr_vect.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/sync_serial.h>
+
+/* The receiver is a bit tricky beacuse of the continuous stream of data.*/
+/* */
+/* Three DMA descriptors are linked together. Each DMA descriptor is */
+/* responsible for port->bufchunk of a common buffer. */
+/* */
+/* +---------------------------------------------+ */
+/* | +----------+ +----------+ +----------+ | */
+/* +-> | Descr[0] |-->| Descr[1] |-->| Descr[2] |-+ */
+/* +----------+ +----------+ +----------+ */
+/* | | | */
+/* v v v */
+/* +-------------------------------------+ */
+/* | BUFFER | */
+/* +-------------------------------------+ */
+/* |<- data_avail ->| */
+/* readp writep */
+/* */
+/* If the application keeps up the pace readp will be right after writep.*/
+/* If the application can't keep the pace we have to throw away data. */
+/* The idea is that readp should be ready with the data pointed out by */
+/* Descr[i] when the DMA has filled in Descr[i+1]. */
+/* Otherwise we will discard */
+/* the rest of the data pointed out by Descr1 and set readp to the start */
+/* of Descr2 */
+
+#define SYNC_SERIAL_MAJOR 125
+
+/* IN_BUFFER_SIZE should be a multiple of 6 to make sure that 24 bit */
+/* words can be handled */
+#define IN_BUFFER_SIZE 12288
+#define IN_DESCR_SIZE 256
+#define NUM_IN_DESCR (IN_BUFFER_SIZE/IN_DESCR_SIZE)
+#define OUT_BUFFER_SIZE 4096
+
+#define DEFAULT_FRAME_RATE 0
+#define DEFAULT_WORD_RATE 7
+
+/* NOTE: Enabling some debug will likely cause overrun or underrun,
+ * especially if manual mode is use.
+ */
+#define DEBUG(x)
+#define DEBUGREAD(x)
+#define DEBUGWRITE(x)
+#define DEBUGPOLL(x)
+#define DEBUGRXINT(x)
+#define DEBUGTXINT(x)
+
+typedef struct sync_port
+{
+ reg_scope_instances regi_sser;
+ reg_scope_instances regi_dmain;
+ reg_scope_instances regi_dmaout;
+
+ char started; /* 1 if port has been started */
+ char port_nbr; /* Port 0 or 1 */
+ char busy; /* 1 if port is busy */
+
+ char enabled; /* 1 if port is enabled */
+ char use_dma; /* 1 if port uses dma */
+ char tr_running;
+
+ char init_irqs;
+ int output;
+ int input;
+
+ volatile unsigned int out_count; /* Remaining bytes for current transfer */
+ unsigned char* outp; /* Current position in out_buffer */
+ volatile unsigned char* volatile readp; /* Next byte to be read by application */
+ volatile unsigned char* volatile writep; /* Next byte to be written by etrax */
+ unsigned int in_buffer_size;
+ unsigned int inbufchunk;
+ unsigned char out_buffer[OUT_BUFFER_SIZE] __attribute__ ((aligned(32)));
+ unsigned char in_buffer[IN_BUFFER_SIZE]__attribute__ ((aligned(32)));
+ unsigned char flip[IN_BUFFER_SIZE] __attribute__ ((aligned(32)));
+ struct dma_descr_data* next_rx_desc;
+ struct dma_descr_data* prev_rx_desc;
+ int full;
+
+ dma_descr_data in_descr[NUM_IN_DESCR] __attribute__ ((__aligned__(16)));
+ dma_descr_context in_context __attribute__ ((__aligned__(32)));
+ dma_descr_data out_descr __attribute__ ((__aligned__(16)));
+ dma_descr_context out_context __attribute__ ((__aligned__(32)));
+ wait_queue_head_t out_wait_q;
+ wait_queue_head_t in_wait_q;
+
+ spinlock_t lock;
+} sync_port;
+
+static int etrax_sync_serial_init(void);
+static void initialize_port(int portnbr);
+static inline int sync_data_avail(struct sync_port *port);
+
+static int sync_serial_open(struct inode *, struct file*);
+static int sync_serial_release(struct inode*, struct file*);
+static unsigned int sync_serial_poll(struct file *filp, poll_table *wait);
+
+static int sync_serial_ioctl(struct inode*, struct file*,
+ unsigned int cmd, unsigned long arg);
+static ssize_t sync_serial_write(struct file * file, const char * buf,
+ size_t count, loff_t *ppos);
+static ssize_t sync_serial_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos);
+
+#if (defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL_PORT0) && \
+ defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL0_DMA)) || \
+ (defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL_PORT1) && \
+ defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL1_DMA))
+#define SYNC_SER_DMA
+#endif
+
+static void send_word(sync_port* port);
+static void start_dma(struct sync_port *port, const char* data, int count);
+static void start_dma_in(sync_port* port);
+#ifdef SYNC_SER_DMA
+static irqreturn_t tr_interrupt(int irq, void *dev_id, struct pt_regs * regs);
+static irqreturn_t rx_interrupt(int irq, void *dev_id, struct pt_regs * regs);
+#endif
+
+#if (defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL_PORT0) && \
+ !defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL0_DMA)) || \
+ (defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL_PORT1) && \
+ !defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL1_DMA))
+#define SYNC_SER_MANUAL
+#endif
+#ifdef SYNC_SER_MANUAL
+static irqreturn_t manual_interrupt(int irq, void *dev_id, struct pt_regs * regs);
+#endif
+
+/* The ports */
+static struct sync_port ports[]=
+{
+ {
+ .regi_sser = regi_sser0,
+ .regi_dmaout = regi_dma4,
+ .regi_dmain = regi_dma5,
+#if defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL0_DMA)
+ .use_dma = 1,
+#else
+ .use_dma = 0,
+#endif
+ },
+ {
+ .regi_sser = regi_sser1,
+ .regi_dmaout = regi_dma6,
+ .regi_dmain = regi_dma7,
+#if defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL1_DMA)
+ .use_dma = 1,
+#else
+ .use_dma = 0,
+#endif
+ }
+};
+
+#define NUMBER_OF_PORTS (sizeof(ports)/sizeof(sync_port))
+
+static struct file_operations sync_serial_fops = {
+ .owner = THIS_MODULE,
+ .write = sync_serial_write,
+ .read = sync_serial_read,
+ .poll = sync_serial_poll,
+ .ioctl = sync_serial_ioctl,
+ .open = sync_serial_open,
+ .release = sync_serial_release
+};
+
+static int __init etrax_sync_serial_init(void)
+{
+ ports[0].enabled = 0;
+ ports[1].enabled = 0;
+
+ if (register_chrdev(SYNC_SERIAL_MAJOR,"sync serial", &sync_serial_fops) <0 )
+ {
+ printk("unable to get major for synchronous serial port\n");
+ return -EBUSY;
+ }
+
+ /* Initialize Ports */
+#if defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL_PORT0)
+ if (crisv32_pinmux_alloc_fixed(pinmux_sser0))
+ {
+ printk("Unable to allocate pins for syncrhronous serial port 0\n");
+ return -EIO;
+ }
+ ports[0].enabled = 1;
+ initialize_port(0);
+#endif
+
+#if defined(CONFIG_ETRAX_SYNCHRONOUS_SERIAL_PORT1)
+ if (crisv32_pinmux_alloc_fixed(pinmux_sser1))
+ {
+ printk("Unable to allocate pins for syncrhronous serial port 0\n");
+ return -EIO;
+ }
+ ports[1].enabled = 1;
+ initialize_port(1);
+#endif
+
+ printk("ETRAX FS synchronous serial port driver\n");
+ return 0;
+}
+
+static void __init initialize_port(int portnbr)
+{
+ struct sync_port* port = &ports[portnbr];
+ reg_sser_rw_cfg cfg = {0};
+ reg_sser_rw_frm_cfg frm_cfg = {0};
+ reg_sser_rw_tr_cfg tr_cfg = {0};
+ reg_sser_rw_rec_cfg rec_cfg = {0};
+
+ DEBUG(printk("Init sync serial port %d\n", portnbr));
+
+ port->port_nbr = portnbr;
+ port->init_irqs = 1;
+
+ port->outp = port->out_buffer;
+ port->output = 1;
+ port->input = 0;
+
+ port->readp = port->flip;
+ port->writep = port->flip;
+ port->in_buffer_size = IN_BUFFER_SIZE;
+ port->inbufchunk = IN_DESCR_SIZE;
+ port->next_rx_desc = &port->in_descr[0];
+ port->prev_rx_desc = &port->in_descr[NUM_IN_DESCR-1];
+ port->prev_rx_desc->eol = 1;
+
+ init_waitqueue_head(&port->out_wait_q);
+ init_waitqueue_head(&port->in_wait_q);
+
+ spin_lock_init(&port->lock);
+
+ cfg.out_clk_src = regk_sser_intern_clk;
+ cfg.out_clk_pol = regk_sser_pos;
+ cfg.clk_od_mode = regk_sser_no;
+ cfg.clk_dir = regk_sser_out;
+ cfg.gate_clk = regk_sser_no;
+ cfg.base_freq = regk_sser_f29_493;
+ cfg.clk_div = 256;
+ REG_WR(sser, port->regi_sser, rw_cfg, cfg);
+
+ frm_cfg.wordrate = DEFAULT_WORD_RATE;
+ frm_cfg.type = regk_sser_edge;
+ frm_cfg.frame_pin_dir = regk_sser_out;
+ frm_cfg.frame_pin_use = regk_sser_frm;
+ frm_cfg.status_pin_dir = regk_sser_in;
+ frm_cfg.status_pin_use = regk_sser_hold;
+ frm_cfg.out_on = regk_sser_tr;
+ frm_cfg.tr_delay = 1;
+ REG_WR(sser, port->regi_sser, rw_frm_cfg, frm_cfg);
+
+ tr_cfg.urun_stop = regk_sser_no;
+ tr_cfg.sample_size = 7;
+ tr_cfg.sh_dir = regk_sser_msbfirst;
+ tr_cfg.use_dma = port->use_dma ? regk_sser_yes : regk_sser_no;
+ tr_cfg.rate_ctrl = regk_sser_bulk;
+ tr_cfg.data_pin_use = regk_sser_dout;
+ tr_cfg.bulk_wspace = 1;
+ REG_WR(sser, port->regi_sser, rw_tr_cfg, tr_cfg);
+
+ rec_cfg.sample_size = 7;
+ rec_cfg.sh_dir = regk_sser_msbfirst;
+ rec_cfg.use_dma = port->use_dma ? regk_sser_yes : regk_sser_no;
+ rec_cfg.fifo_thr = regk_sser_inf;
+ REG_WR(sser, port->regi_sser, rw_rec_cfg, rec_cfg);
+}
+
+static inline int sync_data_avail(struct sync_port *port)
+{
+ int avail;
+ unsigned char *start;
+ unsigned char *end;
+
+ start = (unsigned char*)port->readp; /* cast away volatile */
+ end = (unsigned char*)port->writep; /* cast away volatile */
+ /* 0123456789 0123456789
+ * ----- - -----
+ * ^rp ^wp ^wp ^rp
+ */
+
+ if (end >= start)
+ avail = end - start;
+ else
+ avail = port->in_buffer_size - (start - end);
+ return avail;
+}
+
+static inline int sync_data_avail_to_end(struct sync_port *port)
+{
+ int avail;
+ unsigned char *start;
+ unsigned char *end;
+
+ start = (unsigned char*)port->readp; /* cast away volatile */
+ end = (unsigned char*)port->writep; /* cast away volatile */
+ /* 0123456789 0123456789
+ * ----- -----
+ * ^rp ^wp ^wp ^rp
+ */
+
+ if (end >= start)
+ avail = end - start;
+ else
+ avail = port->flip + port->in_buffer_size - start;
+ return avail;
+}
+
+static int sync_serial_open(struct inode *inode, struct file *file)
+{
+ int dev = MINOR(inode->i_rdev);
+ sync_port* port;
+ reg_dma_rw_cfg cfg = {.en = regk_dma_yes};
+ reg_dma_rw_intr_mask intr_mask = {.data = regk_dma_yes};
+
+ DEBUG(printk("Open sync serial port %d\n", dev));
+
+ if (dev < 0 || dev >= NUMBER_OF_PORTS || !ports[dev].enabled)
+ {
+ DEBUG(printk("Invalid minor %d\n", dev));
+ return -ENODEV;
+ }
+ port = &ports[dev];
+ /* Allow open this device twice (assuming one reader and one writer) */
+ if (port->busy == 2)
+ {
+ DEBUG(printk("Device is busy.. \n"));
+ return -EBUSY;
+ }
+ if (port->init_irqs) {
+ if (port->use_dma) {
+ if (port == &ports[0]){
+#ifdef SYNC_SER_DMA
+ if(request_irq(DMA4_INTR_VECT,
+ tr_interrupt,
+ 0,
+ "synchronous serial 0 dma tr",
+ &ports[0])) {
+ printk(KERN_CRIT "Can't allocate sync serial port 0 IRQ");
+ return -EBUSY;
+ } else if(request_irq(DMA5_INTR_VECT,
+ rx_interrupt,
+ 0,
+ "synchronous serial 1 dma rx",
+ &ports[0])) {
+ free_irq(DMA4_INTR_VECT, &port[0]);
+ printk(KERN_CRIT "Can't allocate sync serial port 0 IRQ");
+ return -EBUSY;
+ } else if (crisv32_request_dma(SYNC_SER0_TX_DMA_NBR,
+ "synchronous serial 0 dma tr",
+ DMA_VERBOSE_ON_ERROR,
+ 0,
+ dma_sser0)) {
+ free_irq(DMA4_INTR_VECT, &port[0]);
+ free_irq(DMA5_INTR_VECT, &port[0]);
+ printk(KERN_CRIT "Can't allocate sync serial port 0 TX DMA channel");
+ return -EBUSY;
+ } else if (crisv32_request_dma(SYNC_SER0_RX_DMA_NBR,
+ "synchronous serial 0 dma rec",
+ DMA_VERBOSE_ON_ERROR,
+ 0,
+ dma_sser0)) {
+ crisv32_free_dma(SYNC_SER0_TX_DMA_NBR);
+ free_irq(DMA4_INTR_VECT, &port[0]);
+ free_irq(DMA5_INTR_VECT, &port[0]);
+ printk(KERN_CRIT "Can't allocate sync serial port 1 RX DMA channel");
+ return -EBUSY;
+ }
+#endif
+ }
+ else if (port == &ports[1]){
+#ifdef SYNC_SER_DMA
+ if (request_irq(DMA6_INTR_VECT,
+ tr_interrupt,
+ 0,
+ "synchronous serial 1 dma tr",
+ &ports[1])) {
+ printk(KERN_CRIT "Can't allocate sync serial port 1 IRQ");
+ return -EBUSY;
+ } else if (request_irq(DMA7_INTR_VECT,
+ rx_interrupt,
+ 0,
+ "synchronous serial 1 dma rx",
+ &ports[1])) {
+ free_irq(DMA6_INTR_VECT, &ports[1]);
+ printk(KERN_CRIT "Can't allocate sync serial port 3 IRQ");
+ return -EBUSY;
+ } else if (crisv32_request_dma(SYNC_SER1_TX_DMA_NBR,
+ "synchronous serial 1 dma tr",
+ DMA_VERBOSE_ON_ERROR,
+ 0,
+ dma_sser1)) {
+ free_irq(21, &ports[1]);
+ free_irq(20, &ports[1]);
+ printk(KERN_CRIT "Can't allocate sync serial port 3 TX DMA channel");
+ return -EBUSY;
+ } else if (crisv32_request_dma(SYNC_SER1_RX_DMA_NBR,
+ "synchronous serial 3 dma rec",
+ DMA_VERBOSE_ON_ERROR,
+ 0,
+ dma_sser1)) {
+ crisv32_free_dma(SYNC_SER1_TX_DMA_NBR);
+ free_irq(DMA6_INTR_VECT, &ports[1]);
+ free_irq(DMA7_INTR_VECT, &ports[1]);
+ printk(KERN_CRIT "Can't allocate sync serial port 3 RX DMA channel");
+ return -EBUSY;
+ }
+#endif
+ }
+
+ /* Enable DMAs */
+ REG_WR(dma, port->regi_dmain, rw_cfg, cfg);
+ REG_WR(dma, port->regi_dmaout, rw_cfg, cfg);
+ /* Enable DMA IRQs */
+ REG_WR(dma, port->regi_dmain, rw_intr_mask, intr_mask);
+ REG_WR(dma, port->regi_dmaout, rw_intr_mask, intr_mask);
+ /* Set up wordsize = 2 for DMAs. */
+ DMA_WR_CMD (port->regi_dmain, regk_dma_set_w_size1);
+ DMA_WR_CMD (port->regi_dmaout, regk_dma_set_w_size1);
+
+ start_dma_in(port);
+ port->init_irqs = 0;
+ } else { /* !port->use_dma */
+#ifdef SYNC_SER_MANUAL
+ if (port == &ports[0]) {
+ if (request_irq(SSER0_INTR_VECT,
+ manual_interrupt,
+ 0,
+ "synchronous serial manual irq",
+ &ports[0])) {
+ printk("Can't allocate sync serial manual irq");
+ return -EBUSY;
+ }
+ } else if (port == &ports[1]) {
+ if (request_irq(SSER1_INTR_VECT,
+ manual_interrupt,
+ 0,
+ "synchronous serial manual irq",
+ &ports[1])) {
+ printk(KERN_CRIT "Can't allocate sync serial manual irq");
+ return -EBUSY;
+ }
+ }
+ port->init_irqs = 0;
+#else
+ panic("sync_serial: Manual mode not supported.\n");
+#endif /* SYNC_SER_MANUAL */
+ }
+ } /* port->init_irqs */
+
+ port->busy++;
+ return 0;
+}
+
+static int sync_serial_release(struct inode *inode, struct file *file)
+{
+ int dev = MINOR(inode->i_rdev);
+ sync_port* port;
+
+ if (dev < 0 || dev >= NUMBER_OF_PORTS || !ports[dev].enabled)
+ {
+ DEBUG(printk("Invalid minor %d\n", dev));
+ return -ENODEV;
+ }
+ port = &ports[dev];
+ if (port->busy)
+ port->busy--;
+ if (!port->busy)
+ /* XXX */ ;
+ return 0;
+}
+
+static unsigned int sync_serial_poll(struct file *file, poll_table *wait)
+{
+ int dev = MINOR(file->f_dentry->d_inode->i_rdev);
+ unsigned int mask = 0;
+ sync_port* port;
+ DEBUGPOLL( static unsigned int prev_mask = 0; );
+
+ port = &ports[dev];
+ poll_wait(file, &port->out_wait_q, wait);
+ poll_wait(file, &port->in_wait_q, wait);
+ /* Some room to write */
+ if (port->out_count < OUT_BUFFER_SIZE)
+ mask |= POLLOUT | POLLWRNORM;
+ /* At least an inbufchunk of data */
+ if (sync_data_avail(port) >= port->inbufchunk)
+ mask |= POLLIN | POLLRDNORM;
+
+ DEBUGPOLL(if (mask != prev_mask)
+ printk("sync_serial_poll: mask 0x%08X %s %s\n", mask,
+ mask&POLLOUT?"POLLOUT":"", mask&POLLIN?"POLLIN":"");
+ prev_mask = mask;
+ );
+ return mask;
+}
+
+static int sync_serial_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int return_val = 0;
+ int dev = MINOR(file->f_dentry->d_inode->i_rdev);
+ sync_port* port;
+ reg_sser_rw_tr_cfg tr_cfg;
+ reg_sser_rw_rec_cfg rec_cfg;
+ reg_sser_rw_frm_cfg frm_cfg;
+ reg_sser_rw_cfg gen_cfg;
+ reg_sser_rw_intr_mask intr_mask;
+
+ if (dev < 0 || dev >= NUMBER_OF_PORTS || !ports[dev].enabled)
+ {
+ DEBUG(printk("Invalid minor %d\n", dev));
+ return -1;
+ }
+ port = &ports[dev];
+ spin_lock_irq(&port->lock);
+
+ tr_cfg = REG_RD(sser, port->regi_sser, rw_tr_cfg);
+ rec_cfg = REG_RD(sser, port->regi_sser, rw_rec_cfg);
+ frm_cfg = REG_RD(sser, port->regi_sser, rw_frm_cfg);
+ gen_cfg = REG_RD(sser, port->regi_sser, rw_cfg);
+ intr_mask = REG_RD(sser, port->regi_sser, rw_intr_mask);
+
+ switch(cmd)
+ {
+ case SSP_SPEED:
+ if (GET_SPEED(arg) == CODEC)
+ {
+ gen_cfg.base_freq = regk_sser_f32;
+ /* FREQ = 0 => 4 MHz => clk_div = 7*/
+ gen_cfg.clk_div = 6 + (1 << GET_FREQ(arg));
+ }
+ else
+ {
+ gen_cfg.base_freq = regk_sser_f29_493;
+ switch (GET_SPEED(arg))
+ {
+ case SSP150:
+ gen_cfg.clk_div = 29493000 / (150 * 8) - 1;
+ break;
+ case SSP300:
+ gen_cfg.clk_div = 29493000 / (300 * 8) - 1;
+ break;
+ case SSP600:
+ gen_cfg.clk_div = 29493000 / (600 * 8) - 1;
+ break;
+ case SSP1200:
+ gen_cfg.clk_div = 29493000 / (1200 * 8) - 1;
+ break;
+ case SSP2400:
+ gen_cfg.clk_div = 29493000 / (2400 * 8) - 1;
+ break;
+ case SSP4800:
+ gen_cfg.clk_div = 29493000 / (4800 * 8) - 1;
+ break;
+ case SSP9600:
+ gen_cfg.clk_div = 29493000 / (9600 * 8) - 1;
+ break;
+ case SSP19200:
+ gen_cfg.clk_div = 29493000 / (19200 * 8) - 1;
+ break;
+ case SSP28800:
+ gen_cfg.clk_div = 29493000 / (28800 * 8) - 1;
+ break;
+ case SSP57600:
+ gen_cfg.clk_div = 29493000 / (57600 * 8) - 1;
+ break;
+ case SSP115200:
+ gen_cfg.clk_div = 29493000 / (115200 * 8) - 1;
+ break;
+ case SSP230400:
+ gen_cfg.clk_div = 29493000 / (230400 * 8) - 1;
+ break;
+ case SSP460800:
+ gen_cfg.clk_div = 29493000 / (460800 * 8) - 1;
+ break;
+ case SSP921600:
+ gen_cfg.clk_div = 29493000 / (921600 * 8) - 1;
+ break;
+ case SSP3125000:
+ gen_cfg.base_freq = regk_sser_f100;
+ gen_cfg.clk_div = 100000000 / (3125000 * 8) - 1;
+ break;
+
+ }
+ }
+ frm_cfg.wordrate = GET_WORD_RATE(arg);
+
+ break;
+ case SSP_MODE:
+ switch(arg)
+ {
+ case MASTER_OUTPUT:
+ port->output = 1;
+ port->input = 0;
+ gen_cfg.clk_dir = regk_sser_out;
+ break;
+ case SLAVE_OUTPUT:
+ port->output = 1;
+ port->input = 0;
+ gen_cfg.clk_dir = regk_sser_in;
+ break;
+ case MASTER_INPUT:
+ port->output = 0;
+ port->input = 1;
+ gen_cfg.clk_dir = regk_sser_out;
+ break;
+ case SLAVE_INPUT:
+ port->output = 0;
+ port->input = 1;
+ gen_cfg.clk_dir = regk_sser_in;
+ break;
+ case MASTER_BIDIR:
+ port->output = 1;
+ port->input = 1;
+ gen_cfg.clk_dir = regk_sser_out;
+ break;
+ case SLAVE_BIDIR:
+ port->output = 1;
+ port->input = 1;
+ gen_cfg.clk_dir = regk_sser_in;
+ break;
+ default:
+ spin_unlock_irq(&port->lock);
+ return -EINVAL;
+
+ }
+ if (!port->use_dma || (arg == MASTER_OUTPUT || arg == SLAVE_OUTPUT))
+ intr_mask.rdav = regk_sser_yes;
+ break;
+ case SSP_FRAME_SYNC:
+ if (arg & NORMAL_SYNC)
+ frm_cfg.tr_delay = 1;
+ else if (arg & EARLY_SYNC)
+ frm_cfg.tr_delay = 0;
+
+ tr_cfg.bulk_wspace = frm_cfg.tr_delay;
+ frm_cfg.early_wend = regk_sser_yes;
+ if (arg & BIT_SYNC)
+ frm_cfg.type = regk_sser_edge;
+ else if (arg & WORD_SYNC)
+ frm_cfg.type = regk_sser_level;
+ else if (arg & EXTENDED_SYNC)
+ frm_cfg.early_wend = regk_sser_no;
+
+ if (arg & SYNC_ON)
+ frm_cfg.frame_pin_use = regk_sser_frm;
+ else if (arg & SYNC_OFF)
+ frm_cfg.frame_pin_use = regk_sser_gio0;
+
+ if (arg & WORD_SIZE_8)
+ rec_cfg.sample_size = tr_cfg.sample_size = 7;
+ else if (arg & WORD_SIZE_12)
+ rec_cfg.sample_size = tr_cfg.sample_size = 11;
+ else if (arg & WORD_SIZE_16)
+ rec_cfg.sample_size = tr_cfg.sample_size = 15;
+ else if (arg & WORD_SIZE_24)
+ rec_cfg.sample_size = tr_cfg.sample_size = 23;
+ else if (arg & WORD_SIZE_32)
+ rec_cfg.sample_size = tr_cfg.sample_size = 31;
+
+ if (arg & BIT_ORDER_MSB)
+ rec_cfg.sh_dir = tr_cfg.sh_dir = regk_sser_msbfirst;
+ else if (arg & BIT_ORDER_LSB)
+ rec_cfg.sh_dir = tr_cfg.sh_dir = regk_sser_lsbfirst;
+
+ if (arg & FLOW_CONTROL_ENABLE)
+ rec_cfg.fifo_thr = regk_sser_thr16;
+ else if (arg & FLOW_CONTROL_DISABLE)
+ rec_cfg.fifo_thr = regk_sser_inf;
+
+ if (arg & CLOCK_NOT_GATED)
+ gen_cfg.gate_clk = regk_sser_no;
+ else if (arg & CLOCK_GATED)
+ gen_cfg.gate_clk = regk_sser_yes;
+
+ break;
+ case SSP_IPOLARITY:
+ /* NOTE!! negedge is considered NORMAL */
+ if (arg & CLOCK_NORMAL)
+ rec_cfg.clk_pol = regk_sser_neg;
+ else if (arg & CLOCK_INVERT)
+ rec_cfg.clk_pol = regk_sser_pos;
+
+ if (arg & FRAME_NORMAL)
+ frm_cfg.level = regk_sser_pos_hi;
+ else if (arg & FRAME_INVERT)
+ frm_cfg.level = regk_sser_neg_lo;
+
+ if (arg & STATUS_NORMAL)
+ gen_cfg.hold_pol = regk_sser_pos;
+ else if (arg & STATUS_INVERT)
+ gen_cfg.hold_pol = regk_sser_neg;
+ break;
+ case SSP_OPOLARITY:
+ if (arg & CLOCK_NORMAL)
+ gen_cfg.out_clk_pol = regk_sser_neg;
+ else if (arg & CLOCK_INVERT)
+ gen_cfg.out_clk_pol = regk_sser_pos;
+
+ if (arg & FRAME_NORMAL)
+ frm_cfg.level = regk_sser_pos_hi;
+ else if (arg & FRAME_INVERT)
+ frm_cfg.level = regk_sser_neg_lo;
+
+ if (arg & STATUS_NORMAL)
+ gen_cfg.hold_pol = regk_sser_pos;
+ else if (arg & STATUS_INVERT)
+ gen_cfg.hold_pol = regk_sser_neg;
+ break;
+ case SSP_SPI:
+ rec_cfg.fifo_thr = regk_sser_inf;
+ rec_cfg.sh_dir = tr_cfg.sh_dir = regk_sser_msbfirst;
+ rec_cfg.sample_size = tr_cfg.sample_size = 7;
+ frm_cfg.frame_pin_use = regk_sser_frm;
+ frm_cfg.type = regk_sser_level;
+ frm_cfg.tr_delay = 1;
+ frm_cfg.level = regk_sser_neg_lo;
+ if (arg & SPI_SLAVE)
+ {
+ rec_cfg.clk_pol = regk_sser_neg;
+ gen_cfg.clk_dir = regk_sser_in;
+ port->input = 1;
+ port->output = 0;
+ }
+ else
+ {
+ gen_cfg.out_clk_pol = regk_sser_pos;
+ port->input = 0;
+ port->output = 1;
+ gen_cfg.clk_dir = regk_sser_out;
+ }
+ break;
+ case SSP_INBUFCHUNK:
+ break;
+ default:
+ return_val = -1;
+ }
+
+
+ if (port->started)
+ {
+ tr_cfg.tr_en = port->output;
+ rec_cfg.rec_en = port->input;
+ }
+
+ REG_WR(sser, port->regi_sser, rw_tr_cfg, tr_cfg);
+ REG_WR(sser, port->regi_sser, rw_rec_cfg, rec_cfg);
+ REG_WR(sser, port->regi_sser, rw_frm_cfg, frm_cfg);
+ REG_WR(sser, port->regi_sser, rw_intr_mask, intr_mask);
+ REG_WR(sser, port->regi_sser, rw_cfg, gen_cfg);
+
+ spin_unlock_irq(&port->lock);
+ return return_val;
+}
+
+static ssize_t sync_serial_write(struct file * file, const char * buf,
+ size_t count, loff_t *ppos)
+{
+ int dev = MINOR(file->f_dentry->d_inode->i_rdev);
+ DECLARE_WAITQUEUE(wait, current);
+ sync_port *port;
+ unsigned long c, c1;
+ unsigned long free_outp;
+ unsigned long outp;
+ unsigned long out_buffer;
+ unsigned long flags;
+
+ if (dev < 0 || dev >= NUMBER_OF_PORTS || !ports[dev].enabled)
+ {
+ DEBUG(printk("Invalid minor %d\n", dev));
+ return -ENODEV;
+ }
+ port = &ports[dev];
+
+ DEBUGWRITE(printk("W d%d c %lu (%d/%d)\n", port->port_nbr, count, port->out_count, OUT_BUFFER_SIZE));
+ /* Space to end of buffer */
+ /*
+ * out_buffer <c1>012345<- c ->OUT_BUFFER_SIZE
+ * outp^ +out_count
+ ^free_outp
+ * out_buffer 45<- c ->0123OUT_BUFFER_SIZE
+ * +out_count outp^
+ * free_outp
+ *
+ */
+
+ /* Read variables that may be updated by interrupts */
+ spin_lock_irqsave(&port->lock, flags);
+ count = count > OUT_BUFFER_SIZE - port->out_count ? OUT_BUFFER_SIZE - port->out_count : count;
+ outp = (unsigned long)port->outp;
+ free_outp = outp + port->out_count;
+ spin_unlock_irqrestore(&port->lock, flags);
+ out_buffer = (unsigned long)port->out_buffer;
+
+ /* Find out where and how much to write */
+ if (free_outp >= out_buffer + OUT_BUFFER_SIZE)
+ free_outp -= OUT_BUFFER_SIZE;
+ if (free_outp >= outp)
+ c = out_buffer + OUT_BUFFER_SIZE - free_outp;
+ else
+ c = outp - free_outp;
+ if (c > count)
+ c = count;
+
+// DEBUGWRITE(printk("w op %08lX fop %08lX c %lu\n", outp, free_outp, c));
+ if (copy_from_user((void*)free_outp, buf, c))
+ return -EFAULT;
+
+ if (c != count) {
+ buf += c;
+ c1 = count - c;
+ DEBUGWRITE(printk("w2 fi %lu c %lu c1 %lu\n", free_outp-out_buffer, c, c1));
+ if (copy_from_user((void*)out_buffer, buf, c1))
+ return -EFAULT;
+ }
+ spin_lock_irqsave(&port->lock, flags);
+ port->out_count += count;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ /* Make sure transmitter/receiver is running */
+ if (!port->started)
+ {
+ reg_sser_rw_cfg cfg = REG_RD(sser, port->regi_sser, rw_cfg);
+ reg_sser_rw_tr_cfg tr_cfg = REG_RD(sser, port->regi_sser, rw_tr_cfg);
+ reg_sser_rw_rec_cfg rec_cfg = REG_RD(sser, port->regi_sser, rw_rec_cfg);
+ cfg.en = regk_sser_yes;
+ tr_cfg.tr_en = port->output;
+ rec_cfg.rec_en = port->input;
+ REG_WR(sser, port->regi_sser, rw_cfg, cfg);
+ REG_WR(sser, port->regi_sser, rw_tr_cfg, tr_cfg);
+ REG_WR(sser, port->regi_sser, rw_rec_cfg, rec_cfg);
+ port->started = 1;
+ }
+
+ if (file->f_flags & O_NONBLOCK) {
+ spin_lock_irqsave(&port->lock, flags);
+ if (!port->tr_running) {
+ if (!port->use_dma) {
+ reg_sser_rw_intr_mask intr_mask;
+ intr_mask = REG_RD(sser, port->regi_sser, rw_intr_mask);
+ /* Start sender by writing data */
+ send_word(port);
+ /* and enable transmitter ready IRQ */
+ intr_mask.trdy = 1;
+ REG_WR(sser, port->regi_sser, rw_intr_mask, intr_mask);
+ } else {
+ start_dma(port, (unsigned char* volatile )port->outp, c);
+ }
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+ DEBUGWRITE(printk("w d%d c %lu NB\n",
+ port->port_nbr, count));
+ return count;
+ }
+
+ /* Sleep until all sent */
+
+ add_wait_queue(&port->out_wait_q, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_lock_irqsave(&port->lock, flags);
+ if (!port->tr_running) {
+ if (!port->use_dma) {
+ reg_sser_rw_intr_mask intr_mask;
+ intr_mask = REG_RD(sser, port->regi_sser, rw_intr_mask);
+ /* Start sender by writing data */
+ send_word(port);
+ /* and enable transmitter ready IRQ */
+ intr_mask.trdy = 1;
+ REG_WR(sser, port->regi_sser, rw_intr_mask, intr_mask);
+ } else {
+ start_dma(port, port->outp, c);
+ }
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+ schedule();
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&port->out_wait_q, &wait);
+ if (signal_pending(current))
+ {
+ return -EINTR;
+ }
+ DEBUGWRITE(printk("w d%d c %lu\n", port->port_nbr, count));
+ return count;
+}
+
+static ssize_t sync_serial_read(struct file * file, char * buf,
+ size_t count, loff_t *ppos)
+{
+ int dev = MINOR(file->f_dentry->d_inode->i_rdev);
+ int avail;
+ sync_port *port;
+ unsigned char* start;
+ unsigned char* end;
+ unsigned long flags;
+
+ if (dev < 0 || dev >= NUMBER_OF_PORTS || !ports[dev].enabled)
+ {
+ DEBUG(printk("Invalid minor %d\n", dev));
+ return -ENODEV;
+ }
+ port = &ports[dev];
+
+ DEBUGREAD(printk("R%d c %d ri %lu wi %lu /%lu\n", dev, count, port->readp - port->flip, port->writep - port->flip, port->in_buffer_size));
+
+ if (!port->started)
+ {
+ reg_sser_rw_cfg cfg = REG_RD(sser, port->regi_sser, rw_cfg);
+ reg_sser_rw_tr_cfg tr_cfg = REG_RD(sser, port->regi_sser, rw_tr_cfg);
+ reg_sser_rw_rec_cfg rec_cfg = REG_RD(sser, port->regi_sser, rw_rec_cfg);
+ cfg.en = regk_sser_yes;
+ tr_cfg.tr_en = regk_sser_yes;
+ rec_cfg.rec_en = regk_sser_yes;
+ REG_WR(sser, port->regi_sser, rw_cfg, cfg);
+ REG_WR(sser, port->regi_sser, rw_tr_cfg, tr_cfg);
+ REG_WR(sser, port->regi_sser, rw_rec_cfg, rec_cfg);
+ port->started = 1;
+ }
+
+
+ /* Calculate number of available bytes */
+ /* Save pointers to avoid that they are modified by interrupt */
+ spin_lock_irqsave(&port->lock, flags);
+ start = (unsigned char*)port->readp; /* cast away volatile */
+ end = (unsigned char*)port->writep; /* cast away volatile */
+ spin_unlock_irqrestore(&port->lock, flags);
+ while ((start == end) && !port->full) /* No data */
+ {
+ if (file->f_flags & O_NONBLOCK)
+ {
+ return -EAGAIN;
+ }
+
+ interruptible_sleep_on(&port->in_wait_q);
+ if (signal_pending(current))
+ {
+ return -EINTR;
+ }
+ spin_lock_irqsave(&port->lock, flags);
+ start = (unsigned char*)port->readp; /* cast away volatile */
+ end = (unsigned char*)port->writep; /* cast away volatile */
+ spin_unlock_irqrestore(&port->lock, flags);
+ }
+
+ /* Lazy read, never return wrapped data. */
+ if (port->full)
+ avail = port->in_buffer_size;
+ else if (end > start)
+ avail = end - start;
+ else
+ avail = port->flip + port->in_buffer_size - start;
+
+ count = count > avail ? avail : count;
+ if (copy_to_user(buf, start, count))
+ return -EFAULT;
+ /* Disable interrupts while updating readp */
+ spin_lock_irqsave(&port->lock, flags);
+ port->readp += count;
+ if (port->readp >= port->flip + port->in_buffer_size) /* Wrap? */
+ port->readp = port->flip;
+ port->full = 0;
+ spin_unlock_irqrestore(&port->lock, flags);
+ DEBUGREAD(printk("r %d\n", count));
+ return count;
+}
+
+static void send_word(sync_port* port)
+{
+ reg_sser_rw_tr_cfg tr_cfg = REG_RD(sser, port->regi_sser, rw_tr_cfg);
+ reg_sser_rw_tr_data tr_data = {0};
+
+ switch(tr_cfg.sample_size)
+ {
+ case 8:
+ port->out_count--;
+ tr_data.data = *port->outp++;
+ REG_WR(sser, port->regi_sser, rw_tr_data, tr_data);
+ if (port->outp >= port->out_buffer + OUT_BUFFER_SIZE)
+ port->outp = port->out_buffer;
+ break;
+ case 12:
+ {
+ int data = (*port->outp++) << 8;
+ data |= *port->outp++;
+ port->out_count-=2;
+ tr_data.data = data;
+ REG_WR(sser, port->regi_sser, rw_tr_data, tr_data);
+ if (port->outp >= port->out_buffer + OUT_BUFFER_SIZE)
+ port->outp = port->out_buffer;
+ }
+ break;
+ case 16:
+ port->out_count-=2;
+ tr_data.data = *(unsigned short *)port->outp;
+ REG_WR(sser, port->regi_sser, rw_tr_data, tr_data);
+ port->outp+=2;
+ if (port->outp >= port->out_buffer + OUT_BUFFER_SIZE)
+ port->outp = port->out_buffer;
+ break;
+ case 24:
+ port->out_count-=3;
+ tr_data.data = *(unsigned short *)port->outp;
+ REG_WR(sser, port->regi_sser, rw_tr_data, tr_data);
+ port->outp+=2;
+ tr_data.data = *port->outp++;
+ REG_WR(sser, port->regi_sser, rw_tr_data, tr_data);
+ if (port->outp >= port->out_buffer + OUT_BUFFER_SIZE)
+ port->outp = port->out_buffer;
+ break;
+ case 32:
+ port->out_count-=4;
+ tr_data.data = *(unsigned short *)port->outp;
+ REG_WR(sser, port->regi_sser, rw_tr_data, tr_data);
+ port->outp+=2;
+ tr_data.data = *(unsigned short *)port->outp;
+ REG_WR(sser, port->regi_sser, rw_tr_data, tr_data);
+ port->outp+=2;
+ if (port->outp >= port->out_buffer + OUT_BUFFER_SIZE)
+ port->outp = port->out_buffer;
+ break;
+ }
+}
+
+
+static void start_dma(struct sync_port* port, const char* data, int count)
+{
+ port->tr_running = 1;
+ port->out_descr.buf = (char*)virt_to_phys((char*)data);
+ port->out_descr.after = port->out_descr.buf + count;
+ port->out_descr.eol = port->out_descr.intr = 1;
+
+ port->out_context.saved_data = (dma_descr_data*)virt_to_phys(&port->out_descr);
+ port->out_context.saved_data_buf = port->out_descr.buf;
+
+ DMA_START_CONTEXT(port->regi_dmaout, virt_to_phys((char*)&port->out_context));
+ DEBUGTXINT(printk("dma %08lX c %d\n", (unsigned long)data, count));
+}
+
+static void start_dma_in(sync_port* port)
+{
+ int i;
+ char* buf;
+ port->writep = port->flip;
+
+ if (port->writep > port->flip + port->in_buffer_size)
+ {
+ panic("Offset too large in sync serial driver\n");
+ return;
+ }
+ buf = (char*)virt_to_phys(port->in_buffer);
+ for (i = 0; i < NUM_IN_DESCR; i++) {
+ port->in_descr[i].buf = buf;
+ port->in_descr[i].after = buf + port->inbufchunk;
+ port->in_descr[i].intr = 1;
+ port->in_descr[i].next = (dma_descr_data*)virt_to_phys(&port->in_descr[i+1]);
+ port->in_descr[i].buf = buf;
+ buf += port->inbufchunk;
+ }
+ /* Link the last descriptor to the first */
+ port->in_descr[i-1].next = (dma_descr_data*)virt_to_phys(&port->in_descr[0]);
+ port->in_descr[i-1].eol = regk_sser_yes;
+ port->next_rx_desc = &port->in_descr[0];
+ port->prev_rx_desc = &port->in_descr[NUM_IN_DESCR - 1];
+ port->in_context.saved_data = (dma_descr_data*)virt_to_phys(&port->in_descr[0]);
+ port->in_context.saved_data_buf = port->in_descr[0].buf;
+ DMA_START_CONTEXT(port->regi_dmain, virt_to_phys(&port->in_context));
+}
+
+#ifdef SYNC_SER_DMA
+static irqreturn_t tr_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ reg_dma_r_masked_intr masked;
+ reg_dma_rw_ack_intr ack_intr = {.data = regk_dma_yes};
+ int i;
+ struct dma_descr_data *descr;
+ unsigned int sentl;
+ int found = 0;
+
+ for (i = 0; i < NUMBER_OF_PORTS; i++)
+ {
+ sync_port *port = &ports[i];
+ if (!port->enabled || !port->use_dma )
+ continue;
+
+ masked = REG_RD(dma, port->regi_dmaout, r_masked_intr);
+
+ if (masked.data) /* IRQ active for the port? */
+ {
+ found = 1;
+ /* Clear IRQ */
+ REG_WR(dma, port->regi_dmaout, rw_ack_intr, ack_intr);
+ descr = &port->out_descr;
+ sentl = descr->after - descr->buf;
+ port->out_count -= sentl;
+ port->outp += sentl;
+ if (port->outp >= port->out_buffer + OUT_BUFFER_SIZE)
+ port->outp = port->out_buffer;
+ if (port->out_count) {
+ int c;
+ c = port->out_buffer + OUT_BUFFER_SIZE - port->outp;
+ if (c > port->out_count)
+ c = port->out_count;
+ DEBUGTXINT(printk("tx_int DMAWRITE %i %i\n", sentl, c));
+ start_dma(port, port->outp, c);
+ } else {
+ DEBUGTXINT(printk("tx_int DMA stop %i\n", sentl));
+ port->tr_running = 0;
+ }
+ wake_up_interruptible(&port->out_wait_q); /* wake up the waiting process */
+ }
+ }
+ return IRQ_RETVAL(found);
+} /* tr_interrupt */
+
+static irqreturn_t rx_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ reg_dma_r_masked_intr masked;
+ reg_dma_rw_ack_intr ack_intr = {.data = regk_dma_yes};
+
+ int i;
+ int found = 0;
+
+ for (i = 0; i < NUMBER_OF_PORTS; i++)
+ {
+ sync_port *port = &ports[i];
+
+ if (!port->enabled || !port->use_dma )
+ continue;
+
+ masked = REG_RD(dma, port->regi_dmain, r_masked_intr);
+
+ if (masked.data) /* Descriptor interrupt */
+ {
+ found = 1;
+ while (REG_RD(dma, port->regi_dmain, rw_data) !=
+ virt_to_phys(port->next_rx_desc)) {
+
+ if (port->writep + port->inbufchunk > port->flip + port->in_buffer_size) {
+ int first_size = port->flip + port->in_buffer_size - port->writep;
+ memcpy((char*)port->writep, phys_to_virt((unsigned)port->next_rx_desc->buf), first_size);
+ memcpy(port->flip, phys_to_virt((unsigned)port->next_rx_desc->buf+first_size), port->inbufchunk - first_size);
+ port->writep = port->flip + port->inbufchunk - first_size;
+ } else {
+ memcpy((char*)port->writep,
+ phys_to_virt((unsigned)port->next_rx_desc->buf),
+ port->inbufchunk);
+ port->writep += port->inbufchunk;
+ if (port->writep >= port->flip + port->in_buffer_size)
+ port->writep = port->flip;
+ }
+ if (port->writep == port->readp)
+ {
+ port->full = 1;
+ }
+
+ port->next_rx_desc->eol = 0;
+ port->prev_rx_desc->eol = 1;
+ port->prev_rx_desc = phys_to_virt((unsigned)port->next_rx_desc);
+ port->next_rx_desc = phys_to_virt((unsigned)port->next_rx_desc->next);
+ wake_up_interruptible(&port->in_wait_q); /* wake up the waiting process */
+ DMA_CONTINUE(port->regi_dmain);
+ REG_WR(dma, port->regi_dmain, rw_ack_intr, ack_intr);
+
+ }
+ }
+ }
+ return IRQ_RETVAL(found);
+} /* rx_interrupt */
+#endif /* SYNC_SER_DMA */
+
+#ifdef SYNC_SER_MANUAL
+static irqreturn_t manual_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ int i;
+ int found = 0;
+ reg_sser_r_masked_intr masked;
+
+ for (i = 0; i < NUMBER_OF_PORTS; i++)
+ {
+ sync_port* port = &ports[i];
+
+ if (!port->enabled || port->use_dma)
+ {
+ continue;
+ }
+
+ masked = REG_RD(sser, port->regi_sser, r_masked_intr);
+ if (masked.rdav) /* Data received? */
+ {
+ reg_sser_rw_rec_cfg rec_cfg = REG_RD(sser, port->regi_sser, rw_rec_cfg);
+ reg_sser_r_rec_data data = REG_RD(sser, port->regi_sser, r_rec_data);
+ found = 1;
+ /* Read data */
+ switch(rec_cfg.sample_size)
+ {
+ case 8:
+ *port->writep++ = data.data & 0xff;
+ break;
+ case 12:
+ *port->writep = (data.data & 0x0ff0) >> 4;
+ *(port->writep + 1) = data.data & 0x0f;
+ port->writep+=2;
+ break;
+ case 16:
+ *(unsigned short*)port->writep = data.data;
+ port->writep+=2;
+ break;
+ case 24:
+ *(unsigned int*)port->writep = data.data;
+ port->writep+=3;
+ break;
+ case 32:
+ *(unsigned int*)port->writep = data.data;
+ port->writep+=4;
+ break;
+ }
+
+ if (port->writep >= port->flip + port->in_buffer_size) /* Wrap? */
+ port->writep = port->flip;
+ if (port->writep == port->readp) {
+ /* receive buffer overrun, discard oldest data
+ */
+ port->readp++;
+ if (port->readp >= port->flip + port->in_buffer_size) /* Wrap? */
+ port->readp = port->flip;
+ }
+ if (sync_data_avail(port) >= port->inbufchunk)
+ wake_up_interruptible(&port->in_wait_q); /* Wake up application */
+ }
+
+ if (masked.trdy) /* Transmitter ready? */
+ {
+ found = 1;
+ if (port->out_count > 0) /* More data to send */
+ send_word(port);
+ else /* transmission finished */
+ {
+ reg_sser_rw_intr_mask intr_mask;
+ intr_mask = REG_RD(sser, port->regi_sser, rw_intr_mask);
+ intr_mask.trdy = 0;
+ REG_WR(sser, port->regi_sser, rw_intr_mask, intr_mask);
+ wake_up_interruptible(&port->out_wait_q); /* Wake up application */
+ }
+ }
+ }
+ return IRQ_RETVAL(found);
+}
+#endif
+
+module_init(etrax_sync_serial_init);
diff --git a/arch/cris/arch-v32/kernel/Makefile b/arch/cris/arch-v32/kernel/Makefile
new file mode 100644
index 000000000000..5d5b613cde8c
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/Makefile
@@ -0,0 +1,21 @@
+# $Id: Makefile,v 1.11 2004/12/17 10:16:13 starvik Exp $
+#
+# Makefile for the linux kernel.
+#
+
+extra-y := head.o
+
+
+obj-y := entry.o traps.o irq.o debugport.o dma.o pinmux.o \
+ process.o ptrace.o setup.o signal.o traps.o time.o \
+ arbiter.o io.o
+
+obj-$(CONFIG_ETRAXFS_SIM) += vcs_hook.o
+
+obj-$(CONFIG_SMP) += smp.o
+obj-$(CONFIG_ETRAX_KGDB) += kgdb.o kgdb_asm.o
+obj-$(CONFIG_ETRAX_FAST_TIMER) += fasttimer.o
+obj-$(CONFIG_MODULES) += crisksyms.o
+
+clean:
+
diff --git a/arch/cris/arch-v32/kernel/arbiter.c b/arch/cris/arch-v32/kernel/arbiter.c
new file mode 100644
index 000000000000..3870d2fd5160
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/arbiter.c
@@ -0,0 +1,297 @@
+/*
+ * Memory arbiter functions. Allocates bandwith through the
+ * arbiter and sets up arbiter breakpoints.
+ *
+ * The algorithm first assigns slots to the clients that has specified
+ * bandwith (e.g. ethernet) and then the remaining slots are divided
+ * on all the active clients.
+ *
+ * Copyright (c) 2004, 2005 Axis Communications AB.
+ */
+
+#include <linux/config.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/marb_defs.h>
+#include <asm/arch/arbiter.h>
+#include <asm/arch/hwregs/intr_vect.h>
+#include <linux/interrupt.h>
+#include <linux/signal.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+
+struct crisv32_watch_entry
+{
+ unsigned long instance;
+ watch_callback* cb;
+ unsigned long start;
+ unsigned long end;
+ int used;
+};
+
+#define NUMBER_OF_BP 4
+#define NBR_OF_CLIENTS 14
+#define NBR_OF_SLOTS 64
+#define SDRAM_BANDWIDTH 100000000 /* Some kind of expected value */
+#define INTMEM_BANDWIDTH 400000000
+#define NBR_OF_REGIONS 2
+
+static struct crisv32_watch_entry watches[NUMBER_OF_BP] =
+{
+ {regi_marb_bp0},
+ {regi_marb_bp1},
+ {regi_marb_bp2},
+ {regi_marb_bp3}
+};
+
+static int requested_slots[NBR_OF_REGIONS][NBR_OF_CLIENTS];
+static int active_clients[NBR_OF_REGIONS][NBR_OF_CLIENTS];
+static int max_bandwidth[NBR_OF_REGIONS] = {SDRAM_BANDWIDTH, INTMEM_BANDWIDTH};
+
+DEFINE_SPINLOCK(arbiter_lock);
+
+static irqreturn_t
+crisv32_arbiter_irq(int irq, void* dev_id, struct pt_regs* regs);
+
+static void crisv32_arbiter_config(int region)
+{
+ int slot;
+ int client;
+ int interval = 0;
+ int val[NBR_OF_SLOTS];
+
+ for (slot = 0; slot < NBR_OF_SLOTS; slot++)
+ val[slot] = NBR_OF_CLIENTS + 1;
+
+ for (client = 0; client < NBR_OF_CLIENTS; client++)
+ {
+ int pos;
+ if (!requested_slots[region][client])
+ continue;
+ interval = NBR_OF_SLOTS / requested_slots[region][client];
+ pos = 0;
+ while (pos < NBR_OF_SLOTS)
+ {
+ if (val[pos] != NBR_OF_CLIENTS + 1)
+ pos++;
+ else
+ {
+ val[pos] = client;
+ pos += interval;
+ }
+ }
+ }
+
+ client = 0;
+ for (slot = 0; slot < NBR_OF_SLOTS; slot++)
+ {
+ if (val[slot] == NBR_OF_CLIENTS + 1)
+ {
+ int first = client;
+ while(!active_clients[region][client]) {
+ client = (client + 1) % NBR_OF_CLIENTS;
+ if (client == first)
+ break;
+ }
+ val[slot] = client;
+ client = (client + 1) % NBR_OF_CLIENTS;
+ }
+ if (region == EXT_REGION)
+ REG_WR_INT_VECT(marb, regi_marb, rw_ext_slots, slot, val[slot]);
+ else if (region == INT_REGION)
+ REG_WR_INT_VECT(marb, regi_marb, rw_int_slots, slot, val[slot]);
+ }
+}
+
+extern char _stext, _etext;
+
+static void crisv32_arbiter_init(void)
+{
+ static int initialized = 0;
+
+ if (initialized)
+ return;
+
+ initialized = 1;
+
+ /* CPU caches are active. */
+ active_clients[EXT_REGION][10] = active_clients[EXT_REGION][11] = 1;
+ crisv32_arbiter_config(EXT_REGION);
+ crisv32_arbiter_config(INT_REGION);
+
+ if (request_irq(MEMARB_INTR_VECT, crisv32_arbiter_irq, SA_INTERRUPT,
+ "arbiter", NULL))
+ printk(KERN_ERR "Couldn't allocate arbiter IRQ\n");
+
+#ifndef CONFIG_ETRAX_KGDB
+ /* Global watch for writes to kernel text segment. */
+ crisv32_arbiter_watch(virt_to_phys(&_stext), &_etext - &_stext,
+ arbiter_all_clients, arbiter_all_write, NULL);
+#endif
+}
+
+
+
+int crisv32_arbiter_allocate_bandwith(int client, int region,
+ unsigned long bandwidth)
+{
+ int i;
+ int total_assigned = 0;
+ int total_clients = 0;
+ int req;
+
+ crisv32_arbiter_init();
+
+ for (i = 0; i < NBR_OF_CLIENTS; i++)
+ {
+ total_assigned += requested_slots[region][i];
+ total_clients += active_clients[region][i];
+ }
+ req = NBR_OF_SLOTS / (max_bandwidth[region] / bandwidth);
+
+ if (total_assigned + total_clients + req + 1 > NBR_OF_SLOTS)
+ return -ENOMEM;
+
+ active_clients[region][client] = 1;
+ requested_slots[region][client] = req;
+ crisv32_arbiter_config(region);
+
+ return 0;
+}
+
+int crisv32_arbiter_watch(unsigned long start, unsigned long size,
+ unsigned long clients, unsigned long accesses,
+ watch_callback* cb)
+{
+ int i;
+
+ crisv32_arbiter_init();
+
+ if (start > 0x80000000) {
+ printk("Arbiter: %lX doesn't look like a physical address", start);
+ return -EFAULT;
+ }
+
+ spin_lock(&arbiter_lock);
+
+ for (i = 0; i < NUMBER_OF_BP; i++) {
+ if (!watches[i].used) {
+ reg_marb_rw_intr_mask intr_mask = REG_RD(marb, regi_marb, rw_intr_mask);
+
+ watches[i].used = 1;
+ watches[i].start = start;
+ watches[i].end = start + size;
+ watches[i].cb = cb;
+
+ REG_WR_INT(marb_bp, watches[i].instance, rw_first_addr, watches[i].start);
+ REG_WR_INT(marb_bp, watches[i].instance, rw_last_addr, watches[i].end);
+ REG_WR_INT(marb_bp, watches[i].instance, rw_op, accesses);
+ REG_WR_INT(marb_bp, watches[i].instance, rw_clients, clients);
+
+ if (i == 0)
+ intr_mask.bp0 = regk_marb_yes;
+ else if (i == 1)
+ intr_mask.bp1 = regk_marb_yes;
+ else if (i == 2)
+ intr_mask.bp2 = regk_marb_yes;
+ else if (i == 3)
+ intr_mask.bp3 = regk_marb_yes;
+
+ REG_WR(marb, regi_marb, rw_intr_mask, intr_mask);
+ spin_unlock(&arbiter_lock);
+
+ return i;
+ }
+ }
+ spin_unlock(&arbiter_lock);
+ return -ENOMEM;
+}
+
+int crisv32_arbiter_unwatch(int id)
+{
+ reg_marb_rw_intr_mask intr_mask = REG_RD(marb, regi_marb, rw_intr_mask);
+
+ crisv32_arbiter_init();
+
+ spin_lock(&arbiter_lock);
+
+ if ((id < 0) || (id >= NUMBER_OF_BP) || (!watches[id].used)) {
+ spin_unlock(&arbiter_lock);
+ return -EINVAL;
+ }
+
+ memset(&watches[id], 0, sizeof(struct crisv32_watch_entry));
+
+ if (id == 0)
+ intr_mask.bp0 = regk_marb_no;
+ else if (id == 1)
+ intr_mask.bp2 = regk_marb_no;
+ else if (id == 2)
+ intr_mask.bp2 = regk_marb_no;
+ else if (id == 3)
+ intr_mask.bp3 = regk_marb_no;
+
+ REG_WR(marb, regi_marb, rw_intr_mask, intr_mask);
+
+ spin_unlock(&arbiter_lock);
+ return 0;
+}
+
+extern void show_registers(struct pt_regs *regs);
+
+static irqreturn_t
+crisv32_arbiter_irq(int irq, void* dev_id, struct pt_regs* regs)
+{
+ reg_marb_r_masked_intr masked_intr = REG_RD(marb, regi_marb, r_masked_intr);
+ reg_marb_bp_r_brk_clients r_clients;
+ reg_marb_bp_r_brk_addr r_addr;
+ reg_marb_bp_r_brk_op r_op;
+ reg_marb_bp_r_brk_first_client r_first;
+ reg_marb_bp_r_brk_size r_size;
+ reg_marb_bp_rw_ack ack = {0};
+ reg_marb_rw_ack_intr ack_intr = {.bp0=1,.bp1=1,.bp2=1,.bp3=1};
+ struct crisv32_watch_entry* watch;
+
+ if (masked_intr.bp0) {
+ watch = &watches[0];
+ ack_intr.bp0 = regk_marb_yes;
+ } else if (masked_intr.bp1) {
+ watch = &watches[1];
+ ack_intr.bp1 = regk_marb_yes;
+ } else if (masked_intr.bp2) {
+ watch = &watches[2];
+ ack_intr.bp2 = regk_marb_yes;
+ } else if (masked_intr.bp3) {
+ watch = &watches[3];
+ ack_intr.bp3 = regk_marb_yes;
+ } else {
+ return IRQ_NONE;
+ }
+
+ /* Retrieve all useful information and print it. */
+ r_clients = REG_RD(marb_bp, watch->instance, r_brk_clients);
+ r_addr = REG_RD(marb_bp, watch->instance, r_brk_addr);
+ r_op = REG_RD(marb_bp, watch->instance, r_brk_op);
+ r_first = REG_RD(marb_bp, watch->instance, r_brk_first_client);
+ r_size = REG_RD(marb_bp, watch->instance, r_brk_size);
+
+ printk("Arbiter IRQ\n");
+ printk("Clients %X addr %X op %X first %X size %X\n",
+ REG_TYPE_CONV(int, reg_marb_bp_r_brk_clients, r_clients),
+ REG_TYPE_CONV(int, reg_marb_bp_r_brk_addr, r_addr),
+ REG_TYPE_CONV(int, reg_marb_bp_r_brk_op, r_op),
+ REG_TYPE_CONV(int, reg_marb_bp_r_brk_first_client, r_first),
+ REG_TYPE_CONV(int, reg_marb_bp_r_brk_size, r_size));
+
+ REG_WR(marb_bp, watch->instance, rw_ack, ack);
+ REG_WR(marb, regi_marb, rw_ack_intr, ack_intr);
+
+ printk("IRQ occured at %lX\n", regs->erp);
+
+ if (watch->cb)
+ watch->cb();
+
+
+ return IRQ_HANDLED;
+}
diff --git a/arch/cris/arch-v32/kernel/asm-offsets.c b/arch/cris/arch-v32/kernel/asm-offsets.c
new file mode 100644
index 000000000000..15b3d93a0496
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/asm-offsets.c
@@ -0,0 +1,49 @@
+#include <linux/sched.h>
+#include <asm/thread_info.h>
+
+/*
+ * Generate definitions needed by assembly language modules.
+ * This code generates raw asm output which is post-processed to extract
+ * and format the required data.
+ */
+
+#define DEFINE(sym, val) \
+ asm volatile("\n->" #sym " %0 " #val : : "i" (val))
+
+#define BLANK() asm volatile("\n->" : : )
+
+int main(void)
+{
+#define ENTRY(entry) DEFINE(PT_ ## entry, offsetof(struct pt_regs, entry))
+ ENTRY(orig_r10);
+ ENTRY(r13);
+ ENTRY(r12);
+ ENTRY(r11);
+ ENTRY(r10);
+ ENTRY(r9);
+ ENTRY(acr);
+ ENTRY(srs);
+ ENTRY(mof);
+ ENTRY(ccs);
+ ENTRY(srp);
+ BLANK();
+#undef ENTRY
+#define ENTRY(entry) DEFINE(TI_ ## entry, offsetof(struct thread_info, entry))
+ ENTRY(task);
+ ENTRY(flags);
+ ENTRY(preempt_count);
+ BLANK();
+#undef ENTRY
+#define ENTRY(entry) DEFINE(THREAD_ ## entry, offsetof(struct thread_struct, entry))
+ ENTRY(ksp);
+ ENTRY(usp);
+ ENTRY(ccs);
+ BLANK();
+#undef ENTRY
+#define ENTRY(entry) DEFINE(TASK_ ## entry, offsetof(struct task_struct, entry))
+ ENTRY(pid);
+ BLANK();
+ DEFINE(LCLONE_VM, CLONE_VM);
+ DEFINE(LCLONE_UNTRACED, CLONE_UNTRACED);
+ return 0;
+}
diff --git a/arch/cris/arch-v32/kernel/crisksyms.c b/arch/cris/arch-v32/kernel/crisksyms.c
new file mode 100644
index 000000000000..2c3bb9a0afe2
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/crisksyms.c
@@ -0,0 +1,24 @@
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <asm/arch/dma.h>
+#include <asm/arch/intmem.h>
+#include <asm/arch/pinmux.h>
+
+/* Functions for allocating DMA channels */
+EXPORT_SYMBOL(crisv32_request_dma);
+EXPORT_SYMBOL(crisv32_free_dma);
+
+/* Functions for handling internal RAM */
+EXPORT_SYMBOL(crisv32_intmem_alloc);
+EXPORT_SYMBOL(crisv32_intmem_free);
+EXPORT_SYMBOL(crisv32_intmem_phys_to_virt);
+EXPORT_SYMBOL(crisv32_intmem_virt_to_phys);
+
+/* Functions for handling pinmux */
+EXPORT_SYMBOL(crisv32_pinmux_alloc);
+EXPORT_SYMBOL(crisv32_pinmux_dealloc);
+
+/* Functions masking/unmasking interrupts */
+EXPORT_SYMBOL(mask_irq);
+EXPORT_SYMBOL(unmask_irq);
diff --git a/arch/cris/arch-v32/kernel/debugport.c b/arch/cris/arch-v32/kernel/debugport.c
new file mode 100644
index 000000000000..ffc1ebf2dfee
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/debugport.c
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2003, Axis Communications AB.
+ */
+
+#include <linux/config.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/tty.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/arch/hwregs/ser_defs.h>
+#include <asm/arch/hwregs/dma_defs.h>
+#include <asm/arch/pinmux.h>
+
+#include <asm/irq.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+
+struct dbg_port
+{
+ unsigned char nbr;
+ unsigned long instance;
+ unsigned int started;
+ unsigned long baudrate;
+ unsigned char parity;
+ unsigned int bits;
+};
+
+struct dbg_port ports[] =
+{
+ {
+ 0,
+ regi_ser0,
+ 0,
+ 115200,
+ 'N',
+ 8
+ },
+ {
+ 1,
+ regi_ser1,
+ 0,
+ 115200,
+ 'N',
+ 8
+ },
+ {
+ 2,
+ regi_ser2,
+ 0,
+ 115200,
+ 'N',
+ 8
+ },
+ {
+ 3,
+ regi_ser3,
+ 0,
+ 115200,
+ 'N',
+ 8
+ }
+};
+static struct dbg_port *port =
+#if defined(CONFIG_ETRAX_DEBUG_PORT0)
+&ports[0];
+#elif defined(CONFIG_ETRAX_DEBUG_PORT1)
+&ports[1];
+#elif defined(CONFIG_ETRAX_DEBUG_PORT2)
+&ports[2];
+#elif defined(CONFIG_ETRAX_DEBUG_PORT3)
+&ports[3];
+#else
+NULL;
+#endif
+
+#ifdef CONFIG_ETRAX_KGDB
+static struct dbg_port *kgdb_port =
+#if defined(CONFIG_ETRAX_KGDB_PORT0)
+&ports[0];
+#elif defined(CONFIG_ETRAX_KGDB_PORT1)
+&ports[1];
+#elif defined(CONFIG_ETRAX_KGDB_PORT2)
+&ports[2];
+#elif defined(CONFIG_ETRAX_KGDB_PORT3)
+&ports[3];
+#else
+NULL;
+#endif
+#endif
+
+#ifdef CONFIG_ETRAXFS_SIM
+extern void print_str( const char *str );
+static char buffer[1024];
+static char msg[] = "Debug: ";
+static int buffer_pos = sizeof(msg) - 1;
+#endif
+
+extern struct tty_driver *serial_driver;
+
+static void
+start_port(struct dbg_port* p)
+{
+ if (!p)
+ return;
+
+ if (p->started)
+ return;
+ p->started = 1;
+
+ if (p->nbr == 1)
+ crisv32_pinmux_alloc_fixed(pinmux_ser1);
+ else if (p->nbr == 2)
+ crisv32_pinmux_alloc_fixed(pinmux_ser2);
+ else if (p->nbr == 3)
+ crisv32_pinmux_alloc_fixed(pinmux_ser3);
+
+ /* Set up serial port registers */
+ reg_ser_rw_tr_ctrl tr_ctrl = {0};
+ reg_ser_rw_tr_dma_en tr_dma_en = {0};
+
+ reg_ser_rw_rec_ctrl rec_ctrl = {0};
+ reg_ser_rw_tr_baud_div tr_baud_div = {0};
+ reg_ser_rw_rec_baud_div rec_baud_div = {0};
+
+ tr_ctrl.base_freq = rec_ctrl.base_freq = regk_ser_f29_493;
+ tr_dma_en.en = rec_ctrl.dma_mode = regk_ser_no;
+ tr_baud_div.div = rec_baud_div.div = 29493000 / p->baudrate / 8;
+ tr_ctrl.en = rec_ctrl.en = 1;
+
+ if (p->parity == 'O')
+ {
+ tr_ctrl.par_en = regk_ser_yes;
+ tr_ctrl.par = regk_ser_odd;
+ rec_ctrl.par_en = regk_ser_yes;
+ rec_ctrl.par = regk_ser_odd;
+ }
+ else if (p->parity == 'E')
+ {
+ tr_ctrl.par_en = regk_ser_yes;
+ tr_ctrl.par = regk_ser_even;
+ rec_ctrl.par_en = regk_ser_yes;
+ rec_ctrl.par = regk_ser_odd;
+ }
+
+ if (p->bits == 7)
+ {
+ tr_ctrl.data_bits = regk_ser_bits7;
+ rec_ctrl.data_bits = regk_ser_bits7;
+ }
+
+ REG_WR (ser, p->instance, rw_tr_baud_div, tr_baud_div);
+ REG_WR (ser, p->instance, rw_rec_baud_div, rec_baud_div);
+ REG_WR (ser, p->instance, rw_tr_dma_en, tr_dma_en);
+ REG_WR (ser, p->instance, rw_tr_ctrl, tr_ctrl);
+ REG_WR (ser, p->instance, rw_rec_ctrl, rec_ctrl);
+}
+
+/* No debug */
+#ifdef CONFIG_ETRAX_DEBUG_PORT_NULL
+
+static void
+console_write(struct console *co, const char *buf, unsigned int len)
+{
+ return;
+}
+
+/* Target debug */
+#elif !defined(CONFIG_ETRAXFS_SIM)
+
+static void
+console_write_direct(struct console *co, const char *buf, unsigned int len)
+{
+ int i;
+ reg_ser_r_stat_din stat;
+ reg_ser_rw_tr_dma_en tr_dma_en, old;
+
+ /* Switch to manual mode */
+ tr_dma_en = old = REG_RD (ser, port->instance, rw_tr_dma_en);
+ if (tr_dma_en.en == regk_ser_yes) {
+ tr_dma_en.en = regk_ser_no;
+ REG_WR(ser, port->instance, rw_tr_dma_en, tr_dma_en);
+ }
+
+ /* Send data */
+ for (i = 0; i < len; i++) {
+ /* LF -> CRLF */
+ if (buf[i] == '\n') {
+ do {
+ stat = REG_RD (ser, port->instance, r_stat_din);
+ } while (!stat.tr_rdy);
+ REG_WR_INT (ser, port->instance, rw_dout, '\r');
+ }
+ /* Wait until transmitter is ready and send.*/
+ do {
+ stat = REG_RD (ser, port->instance, r_stat_din);
+ } while (!stat.tr_rdy);
+ REG_WR_INT (ser, port->instance, rw_dout, buf[i]);
+ }
+
+ /* Restore mode */
+ if (tr_dma_en.en != old.en)
+ REG_WR(ser, port->instance, rw_tr_dma_en, old);
+}
+
+static void
+console_write(struct console *co, const char *buf, unsigned int len)
+{
+ if (!port)
+ return;
+ console_write_direct(co, buf, len);
+}
+
+
+
+#else
+
+/* VCS debug */
+
+static void
+console_write(struct console *co, const char *buf, unsigned int len)
+{
+ char* pos;
+ pos = memchr(buf, '\n', len);
+ if (pos) {
+ int l = ++pos - buf;
+ memcpy(buffer + buffer_pos, buf, l);
+ memcpy(buffer, msg, sizeof(msg) - 1);
+ buffer[buffer_pos + l] = '\0';
+ print_str(buffer);
+ buffer_pos = sizeof(msg) - 1;
+ if (pos - buf != len) {
+ memcpy(buffer + buffer_pos, pos, len - l);
+ buffer_pos += len - l;
+ }
+ } else {
+ memcpy(buffer + buffer_pos, buf, len);
+ buffer_pos += len;
+ }
+}
+
+#endif
+
+int raw_printk(const char *fmt, ...)
+{
+ static char buf[1024];
+ int printed_len;
+ va_list args;
+ va_start(args, fmt);
+ printed_len = vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+ console_write(NULL, buf, strlen(buf));
+ return printed_len;
+}
+
+void
+stupid_debug(char* buf)
+{
+ console_write(NULL, buf, strlen(buf));
+}
+
+#ifdef CONFIG_ETRAX_KGDB
+/* Use polling to get a single character from the kernel debug port */
+int
+getDebugChar(void)
+{
+ reg_ser_rs_status_data stat;
+ reg_ser_rw_ack_intr ack_intr = { 0 };
+
+ do {
+ stat = REG_RD(ser, kgdb_instance, rs_status_data);
+ } while (!stat.data_avail);
+
+ /* Ack the data_avail interrupt. */
+ ack_intr.data_avail = 1;
+ REG_WR(ser, kgdb_instance, rw_ack_intr, ack_intr);
+
+ return stat.data;
+}
+
+/* Use polling to put a single character to the kernel debug port */
+void
+putDebugChar(int val)
+{
+ reg_ser_r_status_data stat;
+ do {
+ stat = REG_RD (ser, kgdb_instance, r_status_data);
+ } while (!stat.tr_ready);
+ REG_WR (ser, kgdb_instance, rw_data_out, REG_TYPE_CONV(reg_ser_rw_data_out, int, val));
+}
+#endif /* CONFIG_ETRAX_KGDB */
+
+static int __init
+console_setup(struct console *co, char *options)
+{
+ char* s;
+
+ if (options) {
+ port = &ports[co->index];
+ port->baudrate = 115200;
+ port->parity = 'N';
+ port->bits = 8;
+ port->baudrate = simple_strtoul(options, NULL, 10);
+ s = options;
+ while(*s >= '0' && *s <= '9')
+ s++;
+ if (*s) port->parity = *s++;
+ if (*s) port->bits = *s++ - '0';
+ port->started = 0;
+ start_port(port);
+ }
+ return 0;
+}
+
+/* This is a dummy serial device that throws away anything written to it.
+ * This is used when no debug output is wanted.
+ */
+static struct tty_driver dummy_driver;
+
+static int dummy_open(struct tty_struct *tty, struct file * filp)
+{
+ return 0;
+}
+
+static void dummy_close(struct tty_struct *tty, struct file * filp)
+{
+}
+
+static int dummy_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ return count;
+}
+
+static int
+dummy_write_room(struct tty_struct *tty)
+{
+ return 8192;
+}
+
+void __init
+init_dummy_console(void)
+{
+ memset(&dummy_driver, 0, sizeof(struct tty_driver));
+ dummy_driver.driver_name = "serial";
+ dummy_driver.name = "ttyS";
+ dummy_driver.major = TTY_MAJOR;
+ dummy_driver.minor_start = 68;
+ dummy_driver.num = 1; /* etrax100 has 4 serial ports */
+ dummy_driver.type = TTY_DRIVER_TYPE_SERIAL;
+ dummy_driver.subtype = SERIAL_TYPE_NORMAL;
+ dummy_driver.init_termios = tty_std_termios;
+ dummy_driver.init_termios.c_cflag =
+ B115200 | CS8 | CREAD | HUPCL | CLOCAL; /* is normally B9600 default... */
+ dummy_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
+
+ dummy_driver.open = dummy_open;
+ dummy_driver.close = dummy_close;
+ dummy_driver.write = dummy_write;
+ dummy_driver.write_room = dummy_write_room;
+ if (tty_register_driver(&dummy_driver))
+ panic("Couldn't register dummy serial driver\n");
+}
+
+static struct tty_driver*
+crisv32_console_device(struct console* co, int *index)
+{
+ if (port)
+ *index = port->nbr;
+ return port ? serial_driver : &dummy_driver;
+}
+
+static struct console sercons = {
+ name : "ttyS",
+ write: console_write,
+ read : NULL,
+ device : crisv32_console_device,
+ unblank : NULL,
+ setup : console_setup,
+ flags : CON_PRINTBUFFER,
+ index : -1,
+ cflag : 0,
+ next : NULL
+};
+static struct console sercons0 = {
+ name : "ttyS",
+ write: console_write,
+ read : NULL,
+ device : crisv32_console_device,
+ unblank : NULL,
+ setup : console_setup,
+ flags : CON_PRINTBUFFER,
+ index : 0,
+ cflag : 0,
+ next : NULL
+};
+
+static struct console sercons1 = {
+ name : "ttyS",
+ write: console_write,
+ read : NULL,
+ device : crisv32_console_device,
+ unblank : NULL,
+ setup : console_setup,
+ flags : CON_PRINTBUFFER,
+ index : 1,
+ cflag : 0,
+ next : NULL
+};
+static struct console sercons2 = {
+ name : "ttyS",
+ write: console_write,
+ read : NULL,
+ device : crisv32_console_device,
+ unblank : NULL,
+ setup : console_setup,
+ flags : CON_PRINTBUFFER,
+ index : 2,
+ cflag : 0,
+ next : NULL
+};
+static struct console sercons3 = {
+ name : "ttyS",
+ write: console_write,
+ read : NULL,
+ device : crisv32_console_device,
+ unblank : NULL,
+ setup : console_setup,
+ flags : CON_PRINTBUFFER,
+ index : 3,
+ cflag : 0,
+ next : NULL
+};
+
+/* Register console for printk's, etc. */
+int __init
+init_etrax_debug(void)
+{
+ static int first = 1;
+
+ if (!first) {
+ unregister_console(&sercons);
+ register_console(&sercons0);
+ register_console(&sercons1);
+ register_console(&sercons2);
+ register_console(&sercons3);
+ init_dummy_console();
+ return 0;
+ }
+ first = 0;
+ register_console(&sercons);
+ start_port(port);
+
+#ifdef CONFIG_ETRAX_KGDB
+ start_port(kgdb_port);
+#endif /* CONFIG_ETRAX_KGDB */
+ return 0;
+}
+
+__initcall(init_etrax_debug);
diff --git a/arch/cris/arch-v32/kernel/dma.c b/arch/cris/arch-v32/kernel/dma.c
new file mode 100644
index 000000000000..b92e85799b44
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/dma.c
@@ -0,0 +1,224 @@
+/* Wrapper for DMA channel allocator that starts clocks etc */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <asm/dma.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/marb_defs.h>
+#include <asm/arch/hwregs/config_defs.h>
+#include <asm/arch/hwregs/strmux_defs.h>
+#include <linux/errno.h>
+#include <asm/system.h>
+#include <asm/arch/arbiter.h>
+
+static char used_dma_channels[MAX_DMA_CHANNELS];
+static const char * used_dma_channels_users[MAX_DMA_CHANNELS];
+
+static DEFINE_SPINLOCK(dma_lock);
+
+int crisv32_request_dma(unsigned int dmanr, const char * device_id,
+ unsigned options, unsigned int bandwidth,
+ enum dma_owner owner)
+{
+ unsigned long flags;
+ reg_config_rw_clk_ctrl clk_ctrl;
+ reg_strmux_rw_cfg strmux_cfg;
+
+ if (crisv32_arbiter_allocate_bandwith(dmanr,
+ options & DMA_INT_MEM ? INT_REGION : EXT_REGION,
+ bandwidth))
+ return -ENOMEM;
+
+ spin_lock_irqsave(&dma_lock, flags);
+
+ if (used_dma_channels[dmanr]) {
+ spin_unlock_irqrestore(&dma_lock, flags);
+ if (options & DMA_VERBOSE_ON_ERROR) {
+ printk("Failed to request DMA %i for %s, already allocated by %s\n", dmanr, device_id, used_dma_channels_users[dmanr]);
+ }
+ if (options & DMA_PANIC_ON_ERROR)
+ panic("request_dma error!");
+ return -EBUSY;
+ }
+ clk_ctrl = REG_RD(config, regi_config, rw_clk_ctrl);
+ strmux_cfg = REG_RD(strmux, regi_strmux, rw_cfg);
+
+ switch(dmanr)
+ {
+ case 0:
+ case 1:
+ clk_ctrl.dma01_eth0 = 1;
+ break;
+ case 2:
+ case 3:
+ clk_ctrl.dma23 = 1;
+ break;
+ case 4:
+ case 5:
+ clk_ctrl.dma45 = 1;
+ break;
+ case 6:
+ case 7:
+ clk_ctrl.dma67 = 1;
+ break;
+ case 8:
+ case 9:
+ clk_ctrl.dma89_strcop = 1;
+ break;
+#if MAX_DMA_CHANNELS-1 != 9
+#error Check dma.c
+#endif
+ default:
+ spin_unlock_irqrestore(&dma_lock, flags);
+ if (options & DMA_VERBOSE_ON_ERROR) {
+ printk("Failed to request DMA %i for %s, only 0-%i valid)\n", dmanr, device_id, MAX_DMA_CHANNELS-1);
+ }
+
+ if (options & DMA_PANIC_ON_ERROR)
+ panic("request_dma error!");
+ return -EINVAL;
+ }
+
+ switch(owner)
+ {
+ case dma_eth0:
+ if (dmanr == 0)
+ strmux_cfg.dma0 = regk_strmux_eth0;
+ else if (dmanr == 1)
+ strmux_cfg.dma1 = regk_strmux_eth0;
+ else
+ panic("Invalid DMA channel for eth0\n");
+ break;
+ case dma_eth1:
+ if (dmanr == 6)
+ strmux_cfg.dma6 = regk_strmux_eth1;
+ else if (dmanr == 7)
+ strmux_cfg.dma7 = regk_strmux_eth1;
+ else
+ panic("Invalid DMA channel for eth1\n");
+ break;
+ case dma_iop0:
+ if (dmanr == 2)
+ strmux_cfg.dma2 = regk_strmux_iop0;
+ else if (dmanr == 3)
+ strmux_cfg.dma3 = regk_strmux_iop0;
+ else
+ panic("Invalid DMA channel for iop0\n");
+ break;
+ case dma_iop1:
+ if (dmanr == 4)
+ strmux_cfg.dma4 = regk_strmux_iop1;
+ else if (dmanr == 5)
+ strmux_cfg.dma5 = regk_strmux_iop1;
+ else
+ panic("Invalid DMA channel for iop1\n");
+ break;
+ case dma_ser0:
+ if (dmanr == 6)
+ strmux_cfg.dma6 = regk_strmux_ser0;
+ else if (dmanr == 7)
+ strmux_cfg.dma7 = regk_strmux_ser0;
+ else
+ panic("Invalid DMA channel for ser0\n");
+ break;
+ case dma_ser1:
+ if (dmanr == 4)
+ strmux_cfg.dma4 = regk_strmux_ser1;
+ else if (dmanr == 5)
+ strmux_cfg.dma5 = regk_strmux_ser1;
+ else
+ panic("Invalid DMA channel for ser1\n");
+ break;
+ case dma_ser2:
+ if (dmanr == 2)
+ strmux_cfg.dma2 = regk_strmux_ser2;
+ else if (dmanr == 3)
+ strmux_cfg.dma3 = regk_strmux_ser2;
+ else
+ panic("Invalid DMA channel for ser2\n");
+ break;
+ case dma_ser3:
+ if (dmanr == 8)
+ strmux_cfg.dma8 = regk_strmux_ser3;
+ else if (dmanr == 9)
+ strmux_cfg.dma9 = regk_strmux_ser3;
+ else
+ panic("Invalid DMA channel for ser3\n");
+ break;
+ case dma_sser0:
+ if (dmanr == 4)
+ strmux_cfg.dma4 = regk_strmux_sser0;
+ else if (dmanr == 5)
+ strmux_cfg.dma5 = regk_strmux_sser0;
+ else
+ panic("Invalid DMA channel for sser0\n");
+ break;
+ case dma_sser1:
+ if (dmanr == 6)
+ strmux_cfg.dma6 = regk_strmux_sser1;
+ else if (dmanr == 7)
+ strmux_cfg.dma7 = regk_strmux_sser1;
+ else
+ panic("Invalid DMA channel for sser1\n");
+ break;
+ case dma_ata:
+ if (dmanr == 2)
+ strmux_cfg.dma2 = regk_strmux_ata;
+ else if (dmanr == 3)
+ strmux_cfg.dma3 = regk_strmux_ata;
+ else
+ panic("Invalid DMA channel for ata\n");
+ break;
+ case dma_strp:
+ if (dmanr == 8)
+ strmux_cfg.dma8 = regk_strmux_strcop;
+ else if (dmanr == 9)
+ strmux_cfg.dma9 = regk_strmux_strcop;
+ else
+ panic("Invalid DMA channel for strp\n");
+ break;
+ case dma_ext0:
+ if (dmanr == 6)
+ strmux_cfg.dma6 = regk_strmux_ext0;
+ else
+ panic("Invalid DMA channel for ext0\n");
+ break;
+ case dma_ext1:
+ if (dmanr == 7)
+ strmux_cfg.dma7 = regk_strmux_ext1;
+ else
+ panic("Invalid DMA channel for ext1\n");
+ break;
+ case dma_ext2:
+ if (dmanr == 2)
+ strmux_cfg.dma2 = regk_strmux_ext2;
+ else if (dmanr == 8)
+ strmux_cfg.dma8 = regk_strmux_ext2;
+ else
+ panic("Invalid DMA channel for ext2\n");
+ break;
+ case dma_ext3:
+ if (dmanr == 3)
+ strmux_cfg.dma3 = regk_strmux_ext3;
+ else if (dmanr == 9)
+ strmux_cfg.dma9 = regk_strmux_ext2;
+ else
+ panic("Invalid DMA channel for ext2\n");
+ break;
+ }
+
+ used_dma_channels[dmanr] = 1;
+ used_dma_channels_users[dmanr] = device_id;
+ REG_WR(config, regi_config, rw_clk_ctrl, clk_ctrl);
+ REG_WR(strmux, regi_strmux, rw_cfg, strmux_cfg);
+ spin_unlock_irqrestore(&dma_lock,flags);
+ return 0;
+}
+
+void crisv32_free_dma(unsigned int dmanr)
+{
+ spin_lock(&dma_lock);
+ used_dma_channels[dmanr] = 0;
+ spin_unlock(&dma_lock);
+}
diff --git a/arch/cris/arch-v32/kernel/entry.S b/arch/cris/arch-v32/kernel/entry.S
new file mode 100644
index 000000000000..a8ed55e5b403
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/entry.S
@@ -0,0 +1,820 @@
+/*
+ * Copyright (C) 2000-2003 Axis Communications AB
+ *
+ * Authors: Bjorn Wesen (bjornw@axis.com)
+ * Tobias Anderberg (tobiasa@axis.com), CRISv32 port.
+ *
+ * Code for the system-call and fault low-level handling routines.
+ *
+ * NOTE: This code handles signal-recognition, which happens every time
+ * after a timer-interrupt and after each system call.
+ *
+ * Stack layout in 'ret_from_system_call':
+ * ptrace needs to have all regs on the stack.
+ * if the order here is changed, it needs to be
+ * updated in fork.c:copy_process, signal.c:do_signal,
+ * ptrace.c and ptrace.h
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/linkage.h>
+#include <linux/sys.h>
+#include <asm/unistd.h>
+#include <asm/errno.h>
+#include <asm/thread_info.h>
+#include <asm/arch/offset.h>
+
+#include <asm/arch/hwregs/asm/reg_map_asm.h>
+#include <asm/arch/hwregs/asm/intr_vect_defs_asm.h>
+
+ ;; Exported functions.
+ .globl system_call
+ .globl ret_from_intr
+ .globl ret_from_fork
+ .globl resume
+ .globl multiple_interrupt
+ .globl nmi_interrupt
+ .globl spurious_interrupt
+ .globl do_sigtrap
+ .globl gdb_handle_exception
+ .globl sys_call_table
+
+ ; Check if preemptive kernel scheduling should be done.
+#ifdef CONFIG_PREEMPT
+_resume_kernel:
+ di
+ ; Load current task struct.
+ movs.w -8192, $r0 ; THREAD_SIZE = 8192
+ and.d $sp, $r0
+
+ addoq +TI_preempt_count, $r0, $acr
+ move.d [$acr], $r10 ; Preemption disabled?
+ bne _Rexit
+ nop
+
+_need_resched:
+ addoq +TI_flags, $r0, $acr
+ move.d [$acr], $r10
+ btstq TIF_NEED_RESCHED, $r10 ; Check if need_resched is set.
+ bpl _Rexit
+ nop
+
+ ; Do preemptive kernel scheduling.
+ jsr preempt_schedule_irq
+ nop
+
+ ; Load new task struct.
+ movs.w -8192, $r0 ; THREAD_SIZE = 8192.
+ and.d $sp, $r0
+
+ ; One more time with new task.
+ ba _need_resched
+ nop
+#else
+#define _resume_kernel _Rexit
+#endif
+
+ ; Called at exit from fork. schedule_tail must be called to drop
+ ; spinlock if CONFIG_PREEMPT.
+ret_from_fork:
+ jsr schedule_tail
+ nop
+ ba ret_from_sys_call
+ nop
+
+ret_from_intr:
+ ;; Check for resched if preemptive kernel, or if we're going back to
+ ;; user-mode. This test matches the user_regs(regs) macro. Don't simply
+ ;; test CCS since that doesn't necessarily reflect what mode we'll
+ ;; return into.
+ addoq +PT_ccs, $sp, $acr
+ move.d [$acr], $r0
+ btstq 16, $r0 ; User-mode flag.
+ bpl _resume_kernel
+
+ ; Note that di below is in delay slot.
+
+_resume_userspace:
+ di ; So need_resched and sigpending don't change.
+
+ movs.w -8192, $r0 ; THREAD_SIZE == 8192
+ and.d $sp, $r0
+
+ addoq +TI_flags, $r0, $acr ; current->work
+ move.d [$acr], $r10
+ and.d _TIF_WORK_MASK, $r10 ; Work to be done on return?
+ bne _work_pending
+ nop
+ ba _Rexit
+ nop
+
+ ;; The system_call is called by a BREAK instruction, which looks pretty
+ ;; much like any other exception.
+ ;;
+ ;; System calls can't be made from interrupts but we still stack ERP
+ ;; to have a complete stack frame.
+ ;;
+ ;; In r9 we have the wanted syscall number. Arguments come in r10,r11,r12,
+ ;; r13,mof,srp
+ ;;
+ ;; This function looks on the _surface_ like spaghetti programming, but it's
+ ;; really designed so that the fast-path does not force cache-loading of
+ ;; non-used instructions. Only the non-common cases cause the outlined code
+ ;; to run..
+
+system_call:
+ ;; Stack-frame similar to the irq heads, which is reversed in
+ ;; ret_from_sys_call.
+ subq 12, $sp ; Skip EXS, EDA.
+ move $erp, [$sp]
+ subq 4, $sp
+ move $srp, [$sp]
+ subq 4, $sp
+ move $ccs, [$sp]
+ subq 4, $sp
+ ei ; Allow IRQs while handling system call
+ move $spc, [$sp]
+ subq 4, $sp
+ move $mof, [$sp]
+ subq 4, $sp
+ move $srs, [$sp]
+ subq 4, $sp
+ move.d $acr, [$sp]
+ subq 14*4, $sp ; Make room for R0-R13.
+ movem $r13, [$sp] ; Push R0-R13
+ subq 4, $sp
+ move.d $r10, [$sp] ; Push orig_r10.
+
+; Set S-bit when kernel debugging to keep hardware breakpoints active.
+#ifdef CONFIG_ETRAX_KGDB
+ move $ccs, $r0
+ or.d (1<<9), $r0
+ move $r0, $ccs
+#endif
+
+ movs.w -ENOSYS, $r0
+ addoq +PT_r10, $sp, $acr
+ move.d $r0, [$acr]
+
+ ;; Check if this process is syscall-traced.
+ movs.w -8192, $r0 ; THREAD_SIZE == 8192
+ and.d $sp, $r0
+
+ addoq +TI_flags, $r0, $acr
+ move.d [$acr], $r0
+ btstq TIF_SYSCALL_TRACE, $r0
+ bmi _syscall_trace_entry
+ nop
+
+_syscall_traced:
+ ;; Check for sanity in the requested syscall number.
+ cmpu.w NR_syscalls, $r9
+ bhs ret_from_sys_call
+ lslq 2, $r9 ; Multiply by 4, in the delay slot.
+
+ ;; The location on the stack for the register structure is passed as a
+ ;; seventh argument. Some system calls need this.
+ move.d $sp, $r0
+ subq 4, $sp
+ move.d $r0, [$sp]
+
+ ;; The registers carrying parameters (R10-R13) are intact. The optional
+ ;; fifth and sixth parameters is in MOF and SRP respectivly. Put them
+ ;; back on the stack.
+ subq 4, $sp
+ move $srp, [$sp]
+ subq 4, $sp
+ move $mof, [$sp]
+
+ ;; Actually to the system call.
+ addo.d +sys_call_table, $r9, $acr
+ move.d [$acr], $acr
+ jsr $acr
+ nop
+
+ addq 3*4, $sp ; Pop the mof, srp and regs parameters.
+ addoq +PT_r10, $sp, $acr
+ move.d $r10, [$acr] ; Save the return value.
+
+ moveq 1, $r9 ; "Parameter" to ret_from_sys_call to
+ ; show it was a sys call.
+
+ ;; Fall through into ret_from_sys_call to return.
+
+ret_from_sys_call:
+ ;; R9 is a parameter:
+ ;; >= 1 from syscall
+ ;; 0 from irq
+
+ ;; Get the current task-struct pointer.
+ movs.w -8192, $r0 ; THREAD_SIZE == 8192
+ and.d $sp, $r0
+
+ di ; Make sure need_resched and sigpending don't change.
+
+ addoq +TI_flags, $r0, $acr
+ move.d [$acr], $r1
+ and.d _TIF_ALLWORK_MASK, $r1
+ bne _syscall_exit_work
+ nop
+
+_Rexit:
+ ;; This epilogue MUST match the prologues in multiple_interrupt, irq.h
+ ;; and ptregs.h.
+ addq 4, $sp ; Skip orig_r10.
+ movem [$sp+], $r13 ; Registers R0-R13.
+ move.d [$sp+], $acr
+ move [$sp], $srs
+ addq 4, $sp
+ move [$sp+], $mof
+ move [$sp+], $spc
+ move [$sp+], $ccs
+ move [$sp+], $srp
+ move [$sp+], $erp
+ addq 8, $sp ; Skip EXS, EDA.
+ jump $erp
+ rfe ; Restore condition code stack in delay-slot.
+
+ ;; We get here after doing a syscall if extra work might need to be done
+ ;; perform syscall exit tracing if needed.
+
+_syscall_exit_work:
+ ;; R0 contains current at this point and irq's are disabled.
+
+ addoq +TI_flags, $r0, $acr
+ move.d [$acr], $r1
+ btstq TIF_SYSCALL_TRACE, $r1
+ bpl _work_pending
+ nop
+ ei
+ move.d $r9, $r1 ; Preserve R9.
+ jsr do_syscall_trace
+ nop
+ move.d $r1, $r9
+ ba _resume_userspace
+ nop
+
+_work_pending:
+ addoq +TI_flags, $r0, $acr
+ move.d [$acr], $r10
+ btstq TIF_NEED_RESCHED, $r10 ; Need resched?
+ bpl _work_notifysig ; No, must be signal/notify.
+ nop
+
+_work_resched:
+ move.d $r9, $r1 ; Preserve R9.
+ jsr schedule
+ nop
+ move.d $r1, $r9
+ di
+
+ addoq +TI_flags, $r0, $acr
+ move.d [$acr], $r1
+ and.d _TIF_WORK_MASK, $r1 ; Ignore sycall trace counter.
+ beq _Rexit
+ nop
+ btstq TIF_NEED_RESCHED, $r1
+ bmi _work_resched ; current->work.need_resched.
+ nop
+
+_work_notifysig:
+ ;; Deal with pending signals and notify-resume requests.
+
+ addoq +TI_flags, $r0, $acr
+ move.d [$acr], $r13 ; The thread_info_flags parameter.
+ move.d $r9, $r10 ; do_notify_resume syscall/irq param.
+ moveq 0, $r11 ; oldset param - 0 in this case.
+ move.d $sp, $r12 ; The regs param.
+ jsr do_notify_resume
+ nop
+
+ ba _Rexit
+ nop
+
+ ;; We get here as a sidetrack when we've entered a syscall with the
+ ;; trace-bit set. We need to call do_syscall_trace and then continue
+ ;; with the call.
+
+_syscall_trace_entry:
+ ;; PT_r10 in the frame contains -ENOSYS as required, at this point.
+
+ jsr do_syscall_trace
+ nop
+
+ ;; Now re-enter the syscall code to do the syscall itself. We need to
+ ;; restore R9 here to contain the wanted syscall, and the other
+ ;; parameter-bearing registers.
+ addoq +PT_r9, $sp, $acr
+ move.d [$acr], $r9
+ addoq +PT_orig_r10, $sp, $acr
+ move.d [$acr], $r10 ; PT_r10 is already -ENOSYS.
+ addoq +PT_r11, $sp, $acr
+ move.d [$acr], $r11
+ addoq +PT_r12, $sp, $acr
+ move.d [$acr], $r12
+ addoq +PT_r13, $sp, $acr
+ move.d [$acr], $r13
+ addoq +PT_mof, $sp, $acr
+ move [$acr], $mof
+ addoq +PT_srp, $sp, $acr
+ move [$acr], $srp
+
+ ba _syscall_traced
+ nop
+
+ ;; Resume performs the actual task-switching, by switching stack
+ ;; pointers. Input arguments are:
+ ;;
+ ;; R10 = prev
+ ;; R11 = next
+ ;; R12 = thread offset in task struct.
+ ;;
+ ;; Returns old current in R10.
+
+resume:
+ subq 4, $sp
+ move $srp, [$sp] ; Keep old/new PC on the stack.
+ add.d $r12, $r10 ; R10 = current tasks tss.
+ addoq +THREAD_ccs, $r10, $acr
+ move $ccs, [$acr] ; Save IRQ enable state.
+ di
+
+ addoq +THREAD_usp, $r10, $acr
+ move $usp, [$acr] ; Save user-mode stackpointer.
+
+ ;; See copy_thread for the reason why register R9 is saved.
+ subq 10*4, $sp
+ movem $r9, [$sp] ; Save non-scratch registers and R9.
+
+ addoq +THREAD_ksp, $r10, $acr
+ move.d $sp, [$acr] ; Save kernel SP for old task.
+
+ move.d $sp, $r10 ; Return last running task in R10.
+ and.d -8192, $r10 ; Get thread_info from stackpointer.
+ addoq +TI_task, $r10, $acr
+ move.d [$acr], $r10 ; Get task.
+ add.d $r12, $r11 ; Find the new tasks tss.
+ addoq +THREAD_ksp, $r11, $acr
+ move.d [$acr], $sp ; Switch to new stackframe.
+ movem [$sp+], $r9 ; Restore non-scratch registers and R9.
+
+ addoq +THREAD_usp, $r11, $acr
+ move [$acr], $usp ; Restore user-mode stackpointer.
+
+ addoq +THREAD_ccs, $r11, $acr
+ move [$acr], $ccs ; Restore IRQ enable status.
+ move.d [$sp+], $acr
+ jump $acr ; Restore PC.
+ nop
+
+nmi_interrupt:
+
+;; If we receive a watchdog interrupt while it is not expected, then set
+;; up a canonical frame and dump register contents before dying.
+
+ ;; This prologue MUST match the one in irq.h and the struct in ptregs.h!
+ subq 12, $sp ; Skip EXS, EDA.
+ move $nrp, [$sp]
+ subq 4, $sp
+ move $srp, [$sp]
+ subq 4, $sp
+ move $ccs, [$sp]
+ subq 4, $sp
+ move $spc, [$sp]
+ subq 4, $sp
+ move $mof, [$sp]
+ subq 4, $sp
+ move $srs, [$sp]
+ subq 4, $sp
+ move.d $acr, [$sp]
+ subq 14*4, $sp ; Make room for R0-R13.
+ movem $r13, [$sp] ; Push R0-R13.
+ subq 4, $sp
+ move.d $r10, [$sp] ; Push orig_r10.
+ move.d REG_ADDR(intr_vect, regi_irq, r_nmi), $r0
+ move.d [$r0], $r0
+ btstq REG_BIT(intr_vect, r_nmi, watchdog), $r0
+ bpl 1f
+ nop
+ jsr handle_watchdog_bite ; In time.c.
+ move.d $sp, $r10 ; Pointer to registers
+1: btstq REG_BIT(intr_vect, r_nmi, ext), $r0
+ bpl 1f
+ nop
+ jsr handle_nmi
+ move.d $sp, $r10 ; Pointer to registers
+1: addq 4, $sp ; Skip orig_r10
+ movem [$sp+], $r13
+ move.d [$sp+], $acr
+ move [$sp], $srs
+ addq 4, $sp
+ move [$sp+], $mof
+ move [$sp+], $spc
+ move [$sp+], $ccs
+ move [$sp+], $srp
+ move [$sp+], $nrp
+ addq 8, $sp ; Skip EXS, EDA.
+ jump $nrp
+ rfn
+
+ .comm cause_of_death, 4 ;; Don't declare this anywhere.
+
+spurious_interrupt:
+ di
+ jump hard_reset_now
+ nop
+
+ ;; This handles the case when multiple interrupts arrive at the same
+ ;; time. Jump to the first set interrupt bit in a priotiry fashion. The
+ ;; hardware will call the unserved interrupts after the handler
+ ;; finishes.
+multiple_interrupt:
+ ;; This prologue MUST match the one in irq.h and the struct in ptregs.h!
+ subq 12, $sp ; Skip EXS, EDA.
+ move $erp, [$sp]
+ subq 4, $sp
+ move $srp, [$sp]
+ subq 4, $sp
+ move $ccs, [$sp]
+ subq 4, $sp
+ move $spc, [$sp]
+ subq 4, $sp
+ move $mof, [$sp]
+ subq 4, $sp
+ move $srs, [$sp]
+ subq 4, $sp
+ move.d $acr, [$sp]
+ subq 14*4, $sp ; Make room for R0-R13.
+ movem $r13, [$sp] ; Push R0-R13.
+ subq 4, $sp
+ move.d $r10, [$sp] ; Push orig_r10.
+
+; Set S-bit when kernel debugging to keep hardware breakpoints active.
+#ifdef CONFIG_ETRAX_KGDB
+ move $ccs, $r0
+ or.d (1<<9), $r0
+ move $r0, $ccs
+#endif
+
+ jsr crisv32_do_multiple
+ move.d $sp, $r10
+ jump ret_from_intr
+ nop
+
+do_sigtrap:
+ ;; Sigtraps the process that executed the BREAK instruction. Creates a
+ ;; frame that Rexit expects.
+ subq 4, $sp
+ move $eda, [$sp]
+ subq 4, $sp
+ move $exs, [$sp]
+ subq 4, $sp
+ move $erp, [$sp]
+ subq 4, $sp
+ move $srp, [$sp]
+ subq 4, $sp
+ move $ccs, [$sp]
+ subq 4, $sp
+ move $spc, [$sp]
+ subq 4, $sp
+ move $mof, [$sp]
+ subq 4, $sp
+ move $srs, [$sp]
+ subq 4, $sp
+ move.d $acr, [$sp]
+ di ; Need to disable irq's at this point.
+ subq 14*4, $sp ; Make room for r0-r13.
+ movem $r13, [$sp] ; Push the r0-r13 registers.
+ subq 4, $sp
+ move.d $r10, [$sp] ; Push orig_r10.
+
+ movs.w -8192, $r9 ; THREAD_SIZE == 8192
+ and.d $sp, $r9
+
+ ;; thread_info as first parameter
+ move.d $r9, $r10
+ moveq 5, $r11 ; SIGTRAP as second argument.
+ jsr ugdb_trap_user
+ nop
+ jump ret_from_intr ; Use the return routine for interrupts.
+ nop
+
+gdb_handle_exception:
+ subq 4, $sp
+ move.d $r0, [$sp]
+#ifdef CONFIG_ETRAX_KGDB
+ move $ccs, $r0 ; U-flag not affected by previous insns.
+ btstq 16, $r0 ; Test the U-flag.
+ bmi _ugdb_handle_exception ; Go to user mode debugging.
+ nop ; Empty delay-slot (cannot pop R0 here).
+ ba kgdb_handle_exception ; Go to kernel debugging.
+ move.d [$sp+], $r0 ; Restore R0 in delay slot.
+#endif
+
+_ugdb_handle_exception:
+ ba do_sigtrap ; SIGTRAP the offending process.
+ move.d [$sp+], $r0 ; Restore R0 in delay slot.
+
+ .data
+
+ .section .rodata,"a"
+sys_call_table:
+ .long sys_restart_syscall ; 0 - old "setup()" system call, used
+ ; for restarting.
+ .long sys_exit
+ .long sys_fork
+ .long sys_read
+ .long sys_write
+ .long sys_open /* 5 */
+ .long sys_close
+ .long sys_waitpid
+ .long sys_creat
+ .long sys_link
+ .long sys_unlink /* 10 */
+ .long sys_execve
+ .long sys_chdir
+ .long sys_time
+ .long sys_mknod
+ .long sys_chmod /* 15 */
+ .long sys_lchown16
+ .long sys_ni_syscall /* old break syscall holder */
+ .long sys_stat
+ .long sys_lseek
+ .long sys_getpid /* 20 */
+ .long sys_mount
+ .long sys_oldumount
+ .long sys_setuid16
+ .long sys_getuid16
+ .long sys_stime /* 25 */
+ .long sys_ptrace
+ .long sys_alarm
+ .long sys_fstat
+ .long sys_pause
+ .long sys_utime /* 30 */
+ .long sys_ni_syscall /* old stty syscall holder */
+ .long sys_ni_syscall /* old gtty syscall holder */
+ .long sys_access
+ .long sys_nice
+ .long sys_ni_syscall /* 35 old ftime syscall holder */
+ .long sys_sync
+ .long sys_kill
+ .long sys_rename
+ .long sys_mkdir
+ .long sys_rmdir /* 40 */
+ .long sys_dup
+ .long sys_pipe
+ .long sys_times
+ .long sys_ni_syscall /* old prof syscall holder */
+ .long sys_brk /* 45 */
+ .long sys_setgid16
+ .long sys_getgid16
+ .long sys_signal
+ .long sys_geteuid16
+ .long sys_getegid16 /* 50 */
+ .long sys_acct
+ .long sys_umount /* recycled never used phys( */
+ .long sys_ni_syscall /* old lock syscall holder */
+ .long sys_ioctl
+ .long sys_fcntl /* 55 */
+ .long sys_ni_syscall /* old mpx syscall holder */
+ .long sys_setpgid
+ .long sys_ni_syscall /* old ulimit syscall holder */
+ .long sys_ni_syscall /* old sys_olduname holder */
+ .long sys_umask /* 60 */
+ .long sys_chroot
+ .long sys_ustat
+ .long sys_dup2
+ .long sys_getppid
+ .long sys_getpgrp /* 65 */
+ .long sys_setsid
+ .long sys_sigaction
+ .long sys_sgetmask
+ .long sys_ssetmask
+ .long sys_setreuid16 /* 70 */
+ .long sys_setregid16
+ .long sys_sigsuspend
+ .long sys_sigpending
+ .long sys_sethostname
+ .long sys_setrlimit /* 75 */
+ .long sys_old_getrlimit
+ .long sys_getrusage
+ .long sys_gettimeofday
+ .long sys_settimeofday
+ .long sys_getgroups16 /* 80 */
+ .long sys_setgroups16
+ .long sys_select /* was old_select in Linux/E100 */
+ .long sys_symlink
+ .long sys_lstat
+ .long sys_readlink /* 85 */
+ .long sys_uselib
+ .long sys_swapon
+ .long sys_reboot
+ .long old_readdir
+ .long old_mmap /* 90 */
+ .long sys_munmap
+ .long sys_truncate
+ .long sys_ftruncate
+ .long sys_fchmod
+ .long sys_fchown16 /* 95 */
+ .long sys_getpriority
+ .long sys_setpriority
+ .long sys_ni_syscall /* old profil syscall holder */
+ .long sys_statfs
+ .long sys_fstatfs /* 100 */
+ .long sys_ni_syscall /* sys_ioperm in i386 */
+ .long sys_socketcall
+ .long sys_syslog
+ .long sys_setitimer
+ .long sys_getitimer /* 105 */
+ .long sys_newstat
+ .long sys_newlstat
+ .long sys_newfstat
+ .long sys_ni_syscall /* old sys_uname holder */
+ .long sys_ni_syscall /* sys_iopl in i386 */
+ .long sys_vhangup
+ .long sys_ni_syscall /* old "idle" system call */
+ .long sys_ni_syscall /* vm86old in i386 */
+ .long sys_wait4
+ .long sys_swapoff /* 115 */
+ .long sys_sysinfo
+ .long sys_ipc
+ .long sys_fsync
+ .long sys_sigreturn
+ .long sys_clone /* 120 */
+ .long sys_setdomainname
+ .long sys_newuname
+ .long sys_ni_syscall /* sys_modify_ldt */
+ .long sys_adjtimex
+ .long sys_mprotect /* 125 */
+ .long sys_sigprocmask
+ .long sys_ni_syscall /* old "create_module" */
+ .long sys_init_module
+ .long sys_delete_module
+ .long sys_ni_syscall /* 130: old "get_kernel_syms" */
+ .long sys_quotactl
+ .long sys_getpgid
+ .long sys_fchdir
+ .long sys_bdflush
+ .long sys_sysfs /* 135 */
+ .long sys_personality
+ .long sys_ni_syscall /* for afs_syscall */
+ .long sys_setfsuid16
+ .long sys_setfsgid16
+ .long sys_llseek /* 140 */
+ .long sys_getdents
+ .long sys_select
+ .long sys_flock
+ .long sys_msync
+ .long sys_readv /* 145 */
+ .long sys_writev
+ .long sys_getsid
+ .long sys_fdatasync
+ .long sys_sysctl
+ .long sys_mlock /* 150 */
+ .long sys_munlock
+ .long sys_mlockall
+ .long sys_munlockall
+ .long sys_sched_setparam
+ .long sys_sched_getparam /* 155 */
+ .long sys_sched_setscheduler
+ .long sys_sched_getscheduler
+ .long sys_sched_yield
+ .long sys_sched_get_priority_max
+ .long sys_sched_get_priority_min /* 160 */
+ .long sys_sched_rr_get_interval
+ .long sys_nanosleep
+ .long sys_mremap
+ .long sys_setresuid16
+ .long sys_getresuid16 /* 165 */
+ .long sys_ni_syscall /* sys_vm86 */
+ .long sys_ni_syscall /* Old sys_query_module */
+ .long sys_poll
+ .long sys_nfsservctl
+ .long sys_setresgid16 /* 170 */
+ .long sys_getresgid16
+ .long sys_prctl
+ .long sys_rt_sigreturn
+ .long sys_rt_sigaction
+ .long sys_rt_sigprocmask /* 175 */
+ .long sys_rt_sigpending
+ .long sys_rt_sigtimedwait
+ .long sys_rt_sigqueueinfo
+ .long sys_rt_sigsuspend
+ .long sys_pread64 /* 180 */
+ .long sys_pwrite64
+ .long sys_chown16
+ .long sys_getcwd
+ .long sys_capget
+ .long sys_capset /* 185 */
+ .long sys_sigaltstack
+ .long sys_sendfile
+ .long sys_ni_syscall /* streams1 */
+ .long sys_ni_syscall /* streams2 */
+ .long sys_vfork /* 190 */
+ .long sys_getrlimit
+ .long sys_mmap2
+ .long sys_truncate64
+ .long sys_ftruncate64
+ .long sys_stat64 /* 195 */
+ .long sys_lstat64
+ .long sys_fstat64
+ .long sys_lchown
+ .long sys_getuid
+ .long sys_getgid /* 200 */
+ .long sys_geteuid
+ .long sys_getegid
+ .long sys_setreuid
+ .long sys_setregid
+ .long sys_getgroups /* 205 */
+ .long sys_setgroups
+ .long sys_fchown
+ .long sys_setresuid
+ .long sys_getresuid
+ .long sys_setresgid /* 210 */
+ .long sys_getresgid
+ .long sys_chown
+ .long sys_setuid
+ .long sys_setgid
+ .long sys_setfsuid /* 215 */
+ .long sys_setfsgid
+ .long sys_pivot_root
+ .long sys_mincore
+ .long sys_madvise
+ .long sys_getdents64 /* 220 */
+ .long sys_fcntl64
+ .long sys_ni_syscall /* reserved for TUX */
+ .long sys_ni_syscall
+ .long sys_gettid
+ .long sys_readahead /* 225 */
+ .long sys_setxattr
+ .long sys_lsetxattr
+ .long sys_fsetxattr
+ .long sys_getxattr
+ .long sys_lgetxattr /* 230 */
+ .long sys_fgetxattr
+ .long sys_listxattr
+ .long sys_llistxattr
+ .long sys_flistxattr
+ .long sys_removexattr /* 235 */
+ .long sys_lremovexattr
+ .long sys_fremovexattr
+ .long sys_tkill
+ .long sys_sendfile64
+ .long sys_futex /* 240 */
+ .long sys_sched_setaffinity
+ .long sys_sched_getaffinity
+ .long sys_ni_syscall /* sys_set_thread_area */
+ .long sys_ni_syscall /* sys_get_thread_area */
+ .long sys_io_setup /* 245 */
+ .long sys_io_destroy
+ .long sys_io_getevents
+ .long sys_io_submit
+ .long sys_io_cancel
+ .long sys_fadvise64 /* 250 */
+ .long sys_ni_syscall
+ .long sys_exit_group
+ .long sys_lookup_dcookie
+ .long sys_epoll_create
+ .long sys_epoll_ctl /* 255 */
+ .long sys_epoll_wait
+ .long sys_remap_file_pages
+ .long sys_set_tid_address
+ .long sys_timer_create
+ .long sys_timer_settime /* 260 */
+ .long sys_timer_gettime
+ .long sys_timer_getoverrun
+ .long sys_timer_delete
+ .long sys_clock_settime
+ .long sys_clock_gettime /* 265 */
+ .long sys_clock_getres
+ .long sys_clock_nanosleep
+ .long sys_statfs64
+ .long sys_fstatfs64
+ .long sys_tgkill /* 270 */
+ .long sys_utimes
+ .long sys_fadvise64_64
+ .long sys_ni_syscall /* sys_vserver */
+ .long sys_ni_syscall /* sys_mbind */
+ .long sys_ni_syscall /* 275 sys_get_mempolicy */
+ .long sys_ni_syscall /* sys_set_mempolicy */
+ .long sys_mq_open
+ .long sys_mq_unlink
+ .long sys_mq_timedsend
+ .long sys_mq_timedreceive /* 280 */
+ .long sys_mq_notify
+ .long sys_mq_getsetattr
+ .long sys_ni_syscall /* reserved for kexec */
+ .long sys_waitid
+
+ /*
+ * NOTE!! This doesn't have to be exact - we just have
+ * to make sure we have _enough_ of the "sys_ni_syscall"
+ * entries. Don't panic if you notice that this hasn't
+ * been shrunk every time we add a new system call.
+ */
+
+ .rept NR_syscalls - (.-sys_call_table) / 4
+ .long sys_ni_syscall
+ .endr
+
diff --git a/arch/cris/arch-v32/kernel/fasttimer.c b/arch/cris/arch-v32/kernel/fasttimer.c
new file mode 100644
index 000000000000..ea2b4a97c8c7
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/fasttimer.c
@@ -0,0 +1,996 @@
+/* $Id: fasttimer.c,v 1.11 2005/01/04 11:15:46 starvik Exp $
+ * linux/arch/cris/kernel/fasttimer.c
+ *
+ * Fast timers for ETRAX FS
+ * This may be useful in other OS than Linux so use 2 space indentation...
+ *
+ * $Log: fasttimer.c,v $
+ * Revision 1.11 2005/01/04 11:15:46 starvik
+ * Don't share timer IRQ.
+ *
+ * Revision 1.10 2004/12/07 09:19:38 starvik
+ * Corrected includes.
+ * Use correct interrupt macros.
+ *
+ * Revision 1.9 2004/05/14 10:18:58 starvik
+ * Export fast_timer_list
+ *
+ * Revision 1.8 2004/05/14 07:58:03 starvik
+ * Merge of changes from 2.4
+ *
+ * Revision 1.7 2003/07/10 12:06:14 starvik
+ * Return IRQ_NONE if irq wasn't handled
+ *
+ * Revision 1.6 2003/07/04 08:27:49 starvik
+ * Merge of Linux 2.5.74
+ *
+ * Revision 1.5 2003/06/05 10:16:22 johana
+ * New INTR_VECT macros.
+ *
+ * Revision 1.4 2003/06/03 08:49:45 johana
+ * Fixed typo.
+ *
+ * Revision 1.3 2003/06/02 12:51:27 johana
+ * Now compiles.
+ * Commented some include files that probably can be removed.
+ *
+ * Revision 1.2 2003/06/02 12:09:41 johana
+ * Ported to ETRAX FS using the trig interrupt instead of timer1.
+ *
+ * Revision 1.3 2002/12/12 08:26:32 starvik
+ * Don't use C-comments inside CVS comments
+ *
+ * Revision 1.2 2002/12/11 15:42:02 starvik
+ * Extracted v10 (ETRAX 100LX) specific stuff from arch/cris/kernel/
+ *
+ * Revision 1.1 2002/11/18 07:58:06 starvik
+ * Fast timers (from Linux 2.4)
+ *
+ * Revision 1.5 2002/10/15 06:21:39 starvik
+ * Added call to init_waitqueue_head
+ *
+ * Revision 1.4 2002/05/28 17:47:59 johana
+ * Added del_fast_timer()
+ *
+ * Revision 1.3 2002/05/28 16:16:07 johana
+ * Handle empty fast_timer_list
+ *
+ * Revision 1.2 2002/05/27 15:38:42 johana
+ * Made it compile without warnings on Linux 2.4.
+ * (includes, wait_queue, PROC_FS and snprintf)
+ *
+ * Revision 1.1 2002/05/27 15:32:25 johana
+ * arch/etrax100/kernel/fasttimer.c v1.8 from the elinux tree.
+ *
+ * Revision 1.8 2001/11/27 13:50:40 pkj
+ * Disable interrupts while stopping the timer and while modifying the
+ * list of active timers in timer1_handler() as it may be interrupted
+ * by other interrupts (e.g., the serial interrupt) which may add fast
+ * timers.
+ *
+ * Revision 1.7 2001/11/22 11:50:32 pkj
+ * * Only store information about the last 16 timers.
+ * * proc_fasttimer_read() now uses an allocated buffer, since it
+ * requires more space than just a page even for only writing the
+ * last 16 timers. The buffer is only allocated on request, so
+ * unless /proc/fasttimer is read, it is never allocated.
+ * * Renamed fast_timer_started to fast_timers_started to match
+ * fast_timers_added and fast_timers_expired.
+ * * Some clean-up.
+ *
+ * Revision 1.6 2000/12/13 14:02:08 johana
+ * Removed volatile for fast_timer_list
+ *
+ * Revision 1.5 2000/12/13 13:55:35 johana
+ * Added DEBUG_LOG, added som cli() and cleanup
+ *
+ * Revision 1.4 2000/12/05 13:48:50 johana
+ * Added range check when writing proc file, modified timer int handling
+ *
+ * Revision 1.3 2000/11/23 10:10:20 johana
+ * More debug/logging possibilities.
+ * Moved GET_JIFFIES_USEC() to timex.h and time.c
+ *
+ * Revision 1.2 2000/11/01 13:41:04 johana
+ * Clean up and bugfixes.
+ * Created new do_gettimeofday_fast() that gets a timeval struct
+ * with time based on jiffies and *R_TIMER0_DATA, uses a table
+ * for fast conversion of timer value to microseconds.
+ * (Much faster the standard do_gettimeofday() and we don't really
+ * wan't to use the true time - we wan't the "uptime" so timers don't screw up
+ * when we change the time.
+ * TODO: Add efficient support for continuous timers as well.
+ *
+ * Revision 1.1 2000/10/26 15:49:16 johana
+ * Added fasttimer, highresolution timers.
+ *
+ * Copyright (C) 2000,2001 2002, 2003 Axis Communications AB, Lund, Sweden
+ */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/param.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#include <linux/config.h>
+#include <linux/version.h>
+
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/timer_defs.h>
+#include <asm/fasttimer.h>
+#include <linux/proc_fs.h>
+
+/*
+ * timer0 is running at 100MHz and generating jiffies timer ticks
+ * at 100 or 1000 HZ.
+ * fasttimer gives an API that gives timers that expire "between" the jiffies
+ * giving microsecond resolution (10 ns).
+ * fasttimer uses reg_timer_rw_trig register to get interrupt when
+ * r_time reaches a certain value.
+ */
+
+
+#define DEBUG_LOG_INCLUDED
+#define FAST_TIMER_LOG
+//#define FAST_TIMER_TEST
+
+#define FAST_TIMER_SANITY_CHECKS
+
+#ifdef FAST_TIMER_SANITY_CHECKS
+#define SANITYCHECK(x) x
+static int sanity_failed = 0;
+#else
+#define SANITYCHECK(x)
+#endif
+
+#define D1(x)
+#define D2(x)
+#define DP(x)
+
+#define __INLINE__ inline
+
+static int fast_timer_running = 0;
+static int fast_timers_added = 0;
+static int fast_timers_started = 0;
+static int fast_timers_expired = 0;
+static int fast_timers_deleted = 0;
+static int fast_timer_is_init = 0;
+static int fast_timer_ints = 0;
+
+struct fast_timer *fast_timer_list = NULL;
+
+#ifdef DEBUG_LOG_INCLUDED
+#define DEBUG_LOG_MAX 128
+static const char * debug_log_string[DEBUG_LOG_MAX];
+static unsigned long debug_log_value[DEBUG_LOG_MAX];
+static int debug_log_cnt = 0;
+static int debug_log_cnt_wrapped = 0;
+
+#define DEBUG_LOG(string, value) \
+{ \
+ unsigned long log_flags; \
+ local_irq_save(log_flags); \
+ debug_log_string[debug_log_cnt] = (string); \
+ debug_log_value[debug_log_cnt] = (unsigned long)(value); \
+ if (++debug_log_cnt >= DEBUG_LOG_MAX) \
+ { \
+ debug_log_cnt = debug_log_cnt % DEBUG_LOG_MAX; \
+ debug_log_cnt_wrapped = 1; \
+ } \
+ local_irq_restore(log_flags); \
+}
+#else
+#define DEBUG_LOG(string, value)
+#endif
+
+
+#define NUM_TIMER_STATS 16
+#ifdef FAST_TIMER_LOG
+struct fast_timer timer_added_log[NUM_TIMER_STATS];
+struct fast_timer timer_started_log[NUM_TIMER_STATS];
+struct fast_timer timer_expired_log[NUM_TIMER_STATS];
+#endif
+
+int timer_div_settings[NUM_TIMER_STATS];
+int timer_delay_settings[NUM_TIMER_STATS];
+
+
+static void
+timer_trig_handler(void);
+
+
+
+/* Not true gettimeofday, only checks the jiffies (uptime) + useconds */
+void __INLINE__ do_gettimeofday_fast(struct timeval *tv)
+{
+ unsigned long sec = jiffies;
+ unsigned long usec = GET_JIFFIES_USEC();
+
+ usec += (sec % HZ) * (1000000 / HZ);
+ sec = sec / HZ;
+
+ if (usec > 1000000)
+ {
+ usec -= 1000000;
+ sec++;
+ }
+ tv->tv_sec = sec;
+ tv->tv_usec = usec;
+}
+
+int __INLINE__ timeval_cmp(struct timeval *t0, struct timeval *t1)
+{
+ if (t0->tv_sec < t1->tv_sec)
+ {
+ return -1;
+ }
+ else if (t0->tv_sec > t1->tv_sec)
+ {
+ return 1;
+ }
+ if (t0->tv_usec < t1->tv_usec)
+ {
+ return -1;
+ }
+ else if (t0->tv_usec > t1->tv_usec)
+ {
+ return 1;
+ }
+ return 0;
+}
+
+/* Called with ints off */
+void __INLINE__ start_timer_trig(unsigned long delay_us)
+{
+ reg_timer_rw_ack_intr ack_intr = { 0 };
+ reg_timer_rw_intr_mask intr_mask;
+ reg_timer_rw_trig trig;
+ reg_timer_rw_trig_cfg trig_cfg = { 0 };
+ reg_timer_r_time r_time;
+
+ r_time = REG_RD(timer, regi_timer, r_time);
+
+ D1(printk("start_timer_trig : %d us freq: %i div: %i\n",
+ delay_us, freq_index, div));
+ /* Clear trig irq */
+ intr_mask = REG_RD(timer, regi_timer, rw_intr_mask);
+ intr_mask.trig = 0;
+ REG_WR(timer, regi_timer, rw_intr_mask, intr_mask);
+
+ /* Set timer values */
+ /* r_time is 100MHz (10 ns resolution) */
+ trig = r_time + delay_us*(1000/10);
+
+ timer_div_settings[fast_timers_started % NUM_TIMER_STATS] = trig;
+ timer_delay_settings[fast_timers_started % NUM_TIMER_STATS] = delay_us;
+
+ /* Ack interrupt */
+ ack_intr.trig = 1;
+ REG_WR(timer, regi_timer, rw_ack_intr, ack_intr);
+
+ /* Start timer */
+ REG_WR(timer, regi_timer, rw_trig, trig);
+ trig_cfg.tmr = regk_timer_time;
+ REG_WR(timer, regi_timer, rw_trig_cfg, trig_cfg);
+
+ /* Check if we have already passed the trig time */
+ r_time = REG_RD(timer, regi_timer, r_time);
+ if (r_time < trig) {
+ /* No, Enable trig irq */
+ intr_mask = REG_RD(timer, regi_timer, rw_intr_mask);
+ intr_mask.trig = 1;
+ REG_WR(timer, regi_timer, rw_intr_mask, intr_mask);
+ fast_timers_started++;
+ fast_timer_running = 1;
+ }
+ else
+ {
+ /* We have passed the time, disable trig point, ack intr */
+ trig_cfg.tmr = regk_timer_off;
+ REG_WR(timer, regi_timer, rw_trig_cfg, trig_cfg);
+ REG_WR(timer, regi_timer, rw_ack_intr, ack_intr);
+ /* call the int routine directly */
+ timer_trig_handler();
+ }
+
+}
+
+/* In version 1.4 this function takes 27 - 50 us */
+void start_one_shot_timer(struct fast_timer *t,
+ fast_timer_function_type *function,
+ unsigned long data,
+ unsigned long delay_us,
+ const char *name)
+{
+ unsigned long flags;
+ struct fast_timer *tmp;
+
+ D1(printk("sft %s %d us\n", name, delay_us));
+
+ local_irq_save(flags);
+
+ do_gettimeofday_fast(&t->tv_set);
+ tmp = fast_timer_list;
+
+ SANITYCHECK({ /* Check so this is not in the list already... */
+ while (tmp != NULL)
+ {
+ if (tmp == t)
+ {
+ printk("timer name: %s data: 0x%08lX already in list!\n", name, data);
+ sanity_failed++;
+ return;
+ }
+ else
+ {
+ tmp = tmp->next;
+ }
+ }
+ tmp = fast_timer_list;
+ });
+
+ t->delay_us = delay_us;
+ t->function = function;
+ t->data = data;
+ t->name = name;
+
+ t->tv_expires.tv_usec = t->tv_set.tv_usec + delay_us % 1000000;
+ t->tv_expires.tv_sec = t->tv_set.tv_sec + delay_us / 1000000;
+ if (t->tv_expires.tv_usec > 1000000)
+ {
+ t->tv_expires.tv_usec -= 1000000;
+ t->tv_expires.tv_sec++;
+ }
+#ifdef FAST_TIMER_LOG
+ timer_added_log[fast_timers_added % NUM_TIMER_STATS] = *t;
+#endif
+ fast_timers_added++;
+
+ /* Check if this should timeout before anything else */
+ if (tmp == NULL || timeval_cmp(&t->tv_expires, &tmp->tv_expires) < 0)
+ {
+ /* Put first in list and modify the timer value */
+ t->prev = NULL;
+ t->next = fast_timer_list;
+ if (fast_timer_list)
+ {
+ fast_timer_list->prev = t;
+ }
+ fast_timer_list = t;
+#ifdef FAST_TIMER_LOG
+ timer_started_log[fast_timers_started % NUM_TIMER_STATS] = *t;
+#endif
+ start_timer_trig(delay_us);
+ } else {
+ /* Put in correct place in list */
+ while (tmp->next &&
+ timeval_cmp(&t->tv_expires, &tmp->next->tv_expires) > 0)
+ {
+ tmp = tmp->next;
+ }
+ /* Insert t after tmp */
+ t->prev = tmp;
+ t->next = tmp->next;
+ if (tmp->next)
+ {
+ tmp->next->prev = t;
+ }
+ tmp->next = t;
+ }
+
+ D2(printk("start_one_shot_timer: %d us done\n", delay_us));
+
+ local_irq_restore(flags);
+} /* start_one_shot_timer */
+
+static inline int fast_timer_pending (const struct fast_timer * t)
+{
+ return (t->next != NULL) || (t->prev != NULL) || (t == fast_timer_list);
+}
+
+static inline int detach_fast_timer (struct fast_timer *t)
+{
+ struct fast_timer *next, *prev;
+ if (!fast_timer_pending(t))
+ return 0;
+ next = t->next;
+ prev = t->prev;
+ if (next)
+ next->prev = prev;
+ if (prev)
+ prev->next = next;
+ else
+ fast_timer_list = next;
+ fast_timers_deleted++;
+ return 1;
+}
+
+int del_fast_timer(struct fast_timer * t)
+{
+ unsigned long flags;
+ int ret;
+
+ local_irq_save(flags);
+ ret = detach_fast_timer(t);
+ t->next = t->prev = NULL;
+ local_irq_restore(flags);
+ return ret;
+} /* del_fast_timer */
+
+
+/* Interrupt routines or functions called in interrupt context */
+
+/* Timer interrupt handler for trig interrupts */
+
+static irqreturn_t
+timer_trig_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ reg_timer_r_masked_intr masked_intr;
+
+ /* Check if the timer interrupt is for us (a trig int) */
+ masked_intr = REG_RD(timer, regi_timer, r_masked_intr);
+ if (!masked_intr.trig)
+ return IRQ_NONE;
+ timer_trig_handler();
+ return IRQ_HANDLED;
+}
+
+static void timer_trig_handler(void)
+{
+ reg_timer_rw_ack_intr ack_intr = { 0 };
+ reg_timer_rw_intr_mask intr_mask;
+ reg_timer_rw_trig_cfg trig_cfg = { 0 };
+ struct fast_timer *t;
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ /* Clear timer trig interrupt */
+ intr_mask = REG_RD(timer, regi_timer, rw_intr_mask);
+ intr_mask.trig = 0;
+ REG_WR(timer, regi_timer, rw_intr_mask, intr_mask);
+
+ /* First stop timer, then ack interrupt */
+ /* Stop timer */
+ trig_cfg.tmr = regk_timer_off;
+ REG_WR(timer, regi_timer, rw_trig_cfg, trig_cfg);
+
+ /* Ack interrupt */
+ ack_intr.trig = 1;
+ REG_WR(timer, regi_timer, rw_ack_intr, ack_intr);
+
+ fast_timer_running = 0;
+ fast_timer_ints++;
+
+ local_irq_restore(flags);
+
+ t = fast_timer_list;
+ while (t)
+ {
+ struct timeval tv;
+
+ /* Has it really expired? */
+ do_gettimeofday_fast(&tv);
+ D1(printk("t: %is %06ius\n", tv.tv_sec, tv.tv_usec));
+
+ if (timeval_cmp(&t->tv_expires, &tv) <= 0)
+ {
+ /* Yes it has expired */
+#ifdef FAST_TIMER_LOG
+ timer_expired_log[fast_timers_expired % NUM_TIMER_STATS] = *t;
+#endif
+ fast_timers_expired++;
+
+ /* Remove this timer before call, since it may reuse the timer */
+ local_irq_save(flags);
+ if (t->prev)
+ {
+ t->prev->next = t->next;
+ }
+ else
+ {
+ fast_timer_list = t->next;
+ }
+ if (t->next)
+ {
+ t->next->prev = t->prev;
+ }
+ t->prev = NULL;
+ t->next = NULL;
+ local_irq_restore(flags);
+
+ if (t->function != NULL)
+ {
+ t->function(t->data);
+ }
+ else
+ {
+ DEBUG_LOG("!trimertrig %i function==NULL!\n", fast_timer_ints);
+ }
+ }
+ else
+ {
+ /* Timer is to early, let's set it again using the normal routines */
+ D1(printk(".\n"));
+ }
+
+ local_irq_save(flags);
+ if ((t = fast_timer_list) != NULL)
+ {
+ /* Start next timer.. */
+ long us;
+ struct timeval tv;
+
+ do_gettimeofday_fast(&tv);
+ us = ((t->tv_expires.tv_sec - tv.tv_sec) * 1000000 +
+ t->tv_expires.tv_usec - tv.tv_usec);
+ if (us > 0)
+ {
+ if (!fast_timer_running)
+ {
+#ifdef FAST_TIMER_LOG
+ timer_started_log[fast_timers_started % NUM_TIMER_STATS] = *t;
+#endif
+ start_timer_trig(us);
+ }
+ local_irq_restore(flags);
+ break;
+ }
+ else
+ {
+ /* Timer already expired, let's handle it better late than never.
+ * The normal loop handles it
+ */
+ D1(printk("e! %d\n", us));
+ }
+ }
+ local_irq_restore(flags);
+ }
+
+ if (!t)
+ {
+ D1(printk("ttrig stop!\n"));
+ }
+}
+
+static void wake_up_func(unsigned long data)
+{
+#ifdef DECLARE_WAITQUEUE
+ wait_queue_head_t *sleep_wait_p = (wait_queue_head_t*)data;
+#else
+ struct wait_queue **sleep_wait_p = (struct wait_queue **)data;
+#endif
+ wake_up(sleep_wait_p);
+}
+
+
+/* Useful API */
+
+void schedule_usleep(unsigned long us)
+{
+ struct fast_timer t;
+#ifdef DECLARE_WAITQUEUE
+ wait_queue_head_t sleep_wait;
+ init_waitqueue_head(&sleep_wait);
+ {
+ DECLARE_WAITQUEUE(wait, current);
+#else
+ struct wait_queue *sleep_wait = NULL;
+ struct wait_queue wait = { current, NULL };
+#endif
+
+ D1(printk("schedule_usleep(%d)\n", us));
+ add_wait_queue(&sleep_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ start_one_shot_timer(&t, wake_up_func, (unsigned long)&sleep_wait, us,
+ "usleep");
+ schedule();
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&sleep_wait, &wait);
+ D1(printk("done schedule_usleep(%d)\n", us));
+#ifdef DECLARE_WAITQUEUE
+ }
+#endif
+}
+
+#ifdef CONFIG_PROC_FS
+static int proc_fasttimer_read(char *buf, char **start, off_t offset, int len
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
+ ,int *eof, void *data_unused
+#else
+ ,int unused
+#endif
+ );
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
+static struct proc_dir_entry *fasttimer_proc_entry;
+#else
+static struct proc_dir_entry fasttimer_proc_entry =
+{
+ 0, 9, "fasttimer",
+ S_IFREG | S_IRUGO, 1, 0, 0,
+ 0, NULL /* ops -- default to array */,
+ &proc_fasttimer_read /* get_info */,
+};
+#endif
+#endif /* CONFIG_PROC_FS */
+
+#ifdef CONFIG_PROC_FS
+
+/* This value is very much based on testing */
+#define BIG_BUF_SIZE (500 + NUM_TIMER_STATS * 300)
+
+static int proc_fasttimer_read(char *buf, char **start, off_t offset, int len
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
+ ,int *eof, void *data_unused
+#else
+ ,int unused
+#endif
+ )
+{
+ unsigned long flags;
+ int i = 0;
+ int num_to_show;
+ struct timeval tv;
+ struct fast_timer *t, *nextt;
+ static char *bigbuf = NULL;
+ static unsigned long used;
+
+ if (!bigbuf && !(bigbuf = vmalloc(BIG_BUF_SIZE)))
+ {
+ used = 0;
+ bigbuf[0] = '\0';
+ return 0;
+ }
+
+ if (!offset || !used)
+ {
+ do_gettimeofday_fast(&tv);
+
+ used = 0;
+ used += sprintf(bigbuf + used, "Fast timers added: %i\n",
+ fast_timers_added);
+ used += sprintf(bigbuf + used, "Fast timers started: %i\n",
+ fast_timers_started);
+ used += sprintf(bigbuf + used, "Fast timer interrupts: %i\n",
+ fast_timer_ints);
+ used += sprintf(bigbuf + used, "Fast timers expired: %i\n",
+ fast_timers_expired);
+ used += sprintf(bigbuf + used, "Fast timers deleted: %i\n",
+ fast_timers_deleted);
+ used += sprintf(bigbuf + used, "Fast timer running: %s\n",
+ fast_timer_running ? "yes" : "no");
+ used += sprintf(bigbuf + used, "Current time: %lu.%06lu\n",
+ (unsigned long)tv.tv_sec,
+ (unsigned long)tv.tv_usec);
+#ifdef FAST_TIMER_SANITY_CHECKS
+ used += sprintf(bigbuf + used, "Sanity failed: %i\n",
+ sanity_failed);
+#endif
+ used += sprintf(bigbuf + used, "\n");
+
+#ifdef DEBUG_LOG_INCLUDED
+ {
+ int end_i = debug_log_cnt;
+ i = 0;
+
+ if (debug_log_cnt_wrapped)
+ {
+ i = debug_log_cnt;
+ }
+
+ while ((i != end_i || (debug_log_cnt_wrapped && !used)) &&
+ used+100 < BIG_BUF_SIZE)
+ {
+ used += sprintf(bigbuf + used, debug_log_string[i],
+ debug_log_value[i]);
+ i = (i+1) % DEBUG_LOG_MAX;
+ }
+ }
+ used += sprintf(bigbuf + used, "\n");
+#endif
+
+ num_to_show = (fast_timers_started < NUM_TIMER_STATS ? fast_timers_started:
+ NUM_TIMER_STATS);
+ used += sprintf(bigbuf + used, "Timers started: %i\n", fast_timers_started);
+ for (i = 0; i < num_to_show && (used+100 < BIG_BUF_SIZE) ; i++)
+ {
+ int cur = (fast_timers_started - i - 1) % NUM_TIMER_STATS;
+
+#if 1 //ndef FAST_TIMER_LOG
+ used += sprintf(bigbuf + used, "div: %i delay: %i"
+ "\n",
+ timer_div_settings[cur],
+ timer_delay_settings[cur]
+ );
+#endif
+#ifdef FAST_TIMER_LOG
+ t = &timer_started_log[cur];
+ used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu "
+ "d: %6li us data: 0x%08lX"
+ "\n",
+ t->name,
+ (unsigned long)t->tv_set.tv_sec,
+ (unsigned long)t->tv_set.tv_usec,
+ (unsigned long)t->tv_expires.tv_sec,
+ (unsigned long)t->tv_expires.tv_usec,
+ t->delay_us,
+ t->data
+ );
+#endif
+ }
+ used += sprintf(bigbuf + used, "\n");
+
+#ifdef FAST_TIMER_LOG
+ num_to_show = (fast_timers_added < NUM_TIMER_STATS ? fast_timers_added:
+ NUM_TIMER_STATS);
+ used += sprintf(bigbuf + used, "Timers added: %i\n", fast_timers_added);
+ for (i = 0; i < num_to_show && (used+100 < BIG_BUF_SIZE); i++)
+ {
+ t = &timer_added_log[(fast_timers_added - i - 1) % NUM_TIMER_STATS];
+ used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu "
+ "d: %6li us data: 0x%08lX"
+ "\n",
+ t->name,
+ (unsigned long)t->tv_set.tv_sec,
+ (unsigned long)t->tv_set.tv_usec,
+ (unsigned long)t->tv_expires.tv_sec,
+ (unsigned long)t->tv_expires.tv_usec,
+ t->delay_us,
+ t->data
+ );
+ }
+ used += sprintf(bigbuf + used, "\n");
+
+ num_to_show = (fast_timers_expired < NUM_TIMER_STATS ? fast_timers_expired:
+ NUM_TIMER_STATS);
+ used += sprintf(bigbuf + used, "Timers expired: %i\n", fast_timers_expired);
+ for (i = 0; i < num_to_show && (used+100 < BIG_BUF_SIZE); i++)
+ {
+ t = &timer_expired_log[(fast_timers_expired - i - 1) % NUM_TIMER_STATS];
+ used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu "
+ "d: %6li us data: 0x%08lX"
+ "\n",
+ t->name,
+ (unsigned long)t->tv_set.tv_sec,
+ (unsigned long)t->tv_set.tv_usec,
+ (unsigned long)t->tv_expires.tv_sec,
+ (unsigned long)t->tv_expires.tv_usec,
+ t->delay_us,
+ t->data
+ );
+ }
+ used += sprintf(bigbuf + used, "\n");
+#endif
+
+ used += sprintf(bigbuf + used, "Active timers:\n");
+ local_irq_save(flags);
+ local_irq_save(flags);
+ t = fast_timer_list;
+ while (t != NULL && (used+100 < BIG_BUF_SIZE))
+ {
+ nextt = t->next;
+ local_irq_restore(flags);
+ used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu "
+ "d: %6li us data: 0x%08lX"
+/* " func: 0x%08lX" */
+ "\n",
+ t->name,
+ (unsigned long)t->tv_set.tv_sec,
+ (unsigned long)t->tv_set.tv_usec,
+ (unsigned long)t->tv_expires.tv_sec,
+ (unsigned long)t->tv_expires.tv_usec,
+ t->delay_us,
+ t->data
+/* , t->function */
+ );
+ local_irq_disable();
+ if (t->next != nextt)
+ {
+ printk("timer removed!\n");
+ }
+ t = nextt;
+ }
+ local_irq_restore(flags);
+ }
+
+ if (used - offset < len)
+ {
+ len = used - offset;
+ }
+
+ memcpy(buf, bigbuf + offset, len);
+ *start = buf;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
+ *eof = 1;
+#endif
+
+ return len;
+}
+#endif /* PROC_FS */
+
+#ifdef FAST_TIMER_TEST
+static volatile unsigned long i = 0;
+static volatile int num_test_timeout = 0;
+static struct fast_timer tr[10];
+static int exp_num[10];
+
+static struct timeval tv_exp[100];
+
+static void test_timeout(unsigned long data)
+{
+ do_gettimeofday_fast(&tv_exp[data]);
+ exp_num[data] = num_test_timeout;
+
+ num_test_timeout++;
+}
+
+static void test_timeout1(unsigned long data)
+{
+ do_gettimeofday_fast(&tv_exp[data]);
+ exp_num[data] = num_test_timeout;
+ if (data < 7)
+ {
+ start_one_shot_timer(&tr[i], test_timeout1, i, 1000, "timeout1");
+ i++;
+ }
+ num_test_timeout++;
+}
+
+DP(
+static char buf0[2000];
+static char buf1[2000];
+static char buf2[2000];
+static char buf3[2000];
+static char buf4[2000];
+);
+
+static char buf5[6000];
+static int j_u[1000];
+
+static void fast_timer_test(void)
+{
+ int prev_num;
+ int j;
+
+ struct timeval tv, tv0, tv1, tv2;
+
+ printk("fast_timer_test() start\n");
+ do_gettimeofday_fast(&tv);
+
+ for (j = 0; j < 1000; j++)
+ {
+ j_u[j] = GET_JIFFIES_USEC();
+ }
+ for (j = 0; j < 100; j++)
+ {
+ do_gettimeofday_fast(&tv_exp[j]);
+ }
+ printk("fast_timer_test() %is %06i\n", tv.tv_sec, tv.tv_usec);
+
+ for (j = 0; j < 1000; j++)
+ {
+ printk("%i %i %i %i %i\n",j_u[j], j_u[j+1], j_u[j+2], j_u[j+3], j_u[j+4]);
+ j += 4;
+ }
+ for (j = 0; j < 100; j++)
+ {
+ printk("%i.%i %i.%i %i.%i %i.%i %i.%i\n",
+ tv_exp[j].tv_sec,tv_exp[j].tv_usec,
+ tv_exp[j+1].tv_sec,tv_exp[j+1].tv_usec,
+ tv_exp[j+2].tv_sec,tv_exp[j+2].tv_usec,
+ tv_exp[j+3].tv_sec,tv_exp[j+3].tv_usec,
+ tv_exp[j+4].tv_sec,tv_exp[j+4].tv_usec);
+ j += 4;
+ }
+ do_gettimeofday_fast(&tv0);
+ start_one_shot_timer(&tr[i], test_timeout, i, 50000, "test0");
+ DP(proc_fasttimer_read(buf0, NULL, 0, 0, 0));
+ i++;
+ start_one_shot_timer(&tr[i], test_timeout, i, 70000, "test1");
+ DP(proc_fasttimer_read(buf1, NULL, 0, 0, 0));
+ i++;
+ start_one_shot_timer(&tr[i], test_timeout, i, 40000, "test2");
+ DP(proc_fasttimer_read(buf2, NULL, 0, 0, 0));
+ i++;
+ start_one_shot_timer(&tr[i], test_timeout, i, 60000, "test3");
+ DP(proc_fasttimer_read(buf3, NULL, 0, 0, 0));
+ i++;
+ start_one_shot_timer(&tr[i], test_timeout1, i, 55000, "test4xx");
+ DP(proc_fasttimer_read(buf4, NULL, 0, 0, 0));
+ i++;
+ do_gettimeofday_fast(&tv1);
+
+ proc_fasttimer_read(buf5, NULL, 0, 0, 0);
+
+ prev_num = num_test_timeout;
+ while (num_test_timeout < i)
+ {
+ if (num_test_timeout != prev_num)
+ {
+ prev_num = num_test_timeout;
+ }
+ }
+ do_gettimeofday_fast(&tv2);
+ printk("Timers started %is %06i\n", tv0.tv_sec, tv0.tv_usec);
+ printk("Timers started at %is %06i\n", tv1.tv_sec, tv1.tv_usec);
+ printk("Timers done %is %06i\n", tv2.tv_sec, tv2.tv_usec);
+ DP(printk("buf0:\n");
+ printk(buf0);
+ printk("buf1:\n");
+ printk(buf1);
+ printk("buf2:\n");
+ printk(buf2);
+ printk("buf3:\n");
+ printk(buf3);
+ printk("buf4:\n");
+ printk(buf4);
+ );
+ printk("buf5:\n");
+ printk(buf5);
+
+ printk("timers set:\n");
+ for(j = 0; j<i; j++)
+ {
+ struct fast_timer *t = &tr[j];
+ printk("%-10s set: %6is %06ius exp: %6is %06ius "
+ "data: 0x%08X func: 0x%08X\n",
+ t->name,
+ t->tv_set.tv_sec,
+ t->tv_set.tv_usec,
+ t->tv_expires.tv_sec,
+ t->tv_expires.tv_usec,
+ t->data,
+ t->function
+ );
+
+ printk(" del: %6ius did exp: %6is %06ius as #%i error: %6li\n",
+ t->delay_us,
+ tv_exp[j].tv_sec,
+ tv_exp[j].tv_usec,
+ exp_num[j],
+ (tv_exp[j].tv_sec - t->tv_expires.tv_sec)*1000000 + tv_exp[j].tv_usec - t->tv_expires.tv_usec);
+ }
+ proc_fasttimer_read(buf5, NULL, 0, 0, 0);
+ printk("buf5 after all done:\n");
+ printk(buf5);
+ printk("fast_timer_test() done\n");
+}
+#endif
+
+
+void fast_timer_init(void)
+{
+ /* For some reason, request_irq() hangs when called froom time_init() */
+ if (!fast_timer_is_init)
+ {
+ printk("fast_timer_init()\n");
+
+#ifdef CONFIG_PROC_FS
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
+ if ((fasttimer_proc_entry = create_proc_entry( "fasttimer", 0, 0 )))
+ fasttimer_proc_entry->read_proc = proc_fasttimer_read;
+#else
+ proc_register_dynamic(&proc_root, &fasttimer_proc_entry);
+#endif
+#endif /* PROC_FS */
+ if(request_irq(TIMER_INTR_VECT, timer_trig_interrupt, SA_INTERRUPT,
+ "fast timer int", NULL))
+ {
+ printk("err: timer1 irq\n");
+ }
+ fast_timer_is_init = 1;
+#ifdef FAST_TIMER_TEST
+ printk("do test\n");
+ fast_timer_test();
+#endif
+ }
+}
diff --git a/arch/cris/arch-v32/kernel/head.S b/arch/cris/arch-v32/kernel/head.S
new file mode 100644
index 000000000000..3cfe57dc391d
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/head.S
@@ -0,0 +1,448 @@
+/*
+ * CRISv32 kernel startup code.
+ *
+ * Copyright (C) 2003, Axis Communications AB
+ */
+
+#include <linux/config.h>
+
+#define ASSEMBLER_MACROS_ONLY
+
+/*
+ * The macros found in mmu_defs_asm.h uses the ## concatenation operator, so
+ * -traditional must not be used when assembling this file.
+ */
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/asm/mmu_defs_asm.h>
+#include <asm/arch/hwregs/asm/reg_map_asm.h>
+#include <asm/arch/hwregs/asm/config_defs_asm.h>
+#include <asm/arch/hwregs/asm/bif_core_defs_asm.h>
+
+#define CRAMFS_MAGIC 0x28cd3d45
+#define RAM_INIT_MAGIC 0x56902387
+#define COMMAND_LINE_MAGIC 0x87109563
+
+ ;; NOTE: R8 and R9 carry information from the decompressor (if the
+ ;; kernel was compressed). They must not be used in the code below
+ ;; until they are read!
+
+ ;; Exported symbols.
+ .global etrax_irv
+ .global romfs_start
+ .global romfs_length
+ .global romfs_in_flash
+ .global swapper_pg_dir
+ .global crisv32_nand_boot
+ .global crisv32_nand_cramfs_offset
+
+ ;; Dummy section to make it bootable with current VCS simulator
+#ifdef CONFIG_ETRAXFS_SIM
+ .section ".boot", "ax"
+ ba tstart
+ nop
+#endif
+
+ .text
+tstart:
+ ;; This is the entry point of the kernel. The CPU is currently in
+ ;; supervisor mode.
+ ;;
+ ;; 0x00000000 if flash.
+ ;; 0x40004000 if DRAM.
+ ;;
+ di
+
+ ;; Start clocks for used blocks.
+ move.d REG_ADDR(config, regi_config, rw_clk_ctrl), $r1
+ move.d [$r1], $r0
+ or.d REG_STATE(config, rw_clk_ctrl, cpu, yes) | \
+ REG_STATE(config, rw_clk_ctrl, bif, yes) | \
+ REG_STATE(config, rw_clk_ctrl, fix_io, yes), $r0
+ move.d $r0, [$r1]
+
+ ;; Set up waitstates etc
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_grp1_cfg), $r0
+ move.d CONFIG_ETRAX_MEM_GRP1_CONFIG, $r1
+ move.d $r1, [$r0]
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_grp2_cfg), $r0
+ move.d CONFIG_ETRAX_MEM_GRP2_CONFIG, $r1
+ move.d $r1, [$r0]
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_grp3_cfg), $r0
+ move.d CONFIG_ETRAX_MEM_GRP3_CONFIG, $r1
+ move.d $r1, [$r0]
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_grp4_cfg), $r0
+ move.d CONFIG_ETRAX_MEM_GRP4_CONFIG, $r1
+ move.d $r1, [$r0]
+
+#ifdef CONFIG_ETRAXFS_SIM
+ ;; Set up minimal flash waitstates
+ move.d 0, $r10
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_grp1_cfg), $r11
+ move.d $r10, [$r11]
+#endif
+
+ ;; Setup and enable the MMU. Use same configuration for both the data
+ ;; and the instruction MMU.
+ ;;
+ ;; Note; 3 cycles is needed for a bank-select to take effect. Further;
+ ;; bank 1 is the instruction MMU, bank 2 is the data MMU.
+#ifndef CONFIG_ETRAXFS_SIM
+ move.d REG_FIELD(mmu, rw_mm_kbase_hi, base_e, 8) \
+ | REG_FIELD(mmu, rw_mm_kbase_hi, base_c, 4) \
+ | REG_FIELD(mmu, rw_mm_kbase_hi, base_b, 0xb), $r0
+#else
+ ;; Map the virtual DRAM to the RW eprom area at address 0.
+ ;; Also map 0xa for the hook calls,
+ move.d REG_FIELD(mmu, rw_mm_kbase_hi, base_e, 8) \
+ | REG_FIELD(mmu, rw_mm_kbase_hi, base_c, 0) \
+ | REG_FIELD(mmu, rw_mm_kbase_hi, base_b, 0xb) \
+ | REG_FIELD(mmu, rw_mm_kbase_hi, base_a, 0xa), $r0
+#endif
+
+ ;; Temporary map of 0x40 -> 0x40 and 0x00 -> 0x00.
+ move.d REG_FIELD(mmu, rw_mm_kbase_lo, base_4, 4) \
+ | REG_FIELD(mmu, rw_mm_kbase_lo, base_0, 0), $r1
+
+ ;; Enable certain page protections and setup linear mapping
+ ;; for f,e,c,b,4,0.
+#ifndef CONFIG_ETRAXFS_SIM
+ move.d REG_STATE(mmu, rw_mm_cfg, we, on) \
+ | REG_STATE(mmu, rw_mm_cfg, acc, on) \
+ | REG_STATE(mmu, rw_mm_cfg, ex, on) \
+ | REG_STATE(mmu, rw_mm_cfg, inv, on) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_f, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_e, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_d, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_c, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_b, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_a, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_9, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_8, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_7, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_6, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_5, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_4, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_3, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_2, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_1, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_0, linear), $r2
+#else
+ move.d REG_STATE(mmu, rw_mm_cfg, we, on) \
+ | REG_STATE(mmu, rw_mm_cfg, acc, on) \
+ | REG_STATE(mmu, rw_mm_cfg, ex, on) \
+ | REG_STATE(mmu, rw_mm_cfg, inv, on) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_f, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_e, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_d, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_c, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_b, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_a, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_9, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_8, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_7, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_6, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_5, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_4, linear) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_3, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_2, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_1, page) \
+ | REG_STATE(mmu, rw_mm_cfg, seg_0, linear), $r2
+#endif
+
+ ;; Update instruction MMU.
+ move 1, $srs
+ nop
+ nop
+ nop
+ move $r0, $s2 ; kbase_hi.
+ move $r1, $s1 ; kbase_lo.
+ move $r2, $s0 ; mm_cfg, virtual memory configuration.
+
+ ;; Update data MMU.
+ move 2, $srs
+ nop
+ nop
+ nop
+ move $r0, $s2 ; kbase_hi.
+ move $r1, $s1 ; kbase_lo
+ move $r2, $s0 ; mm_cfg, virtual memory configuration.
+
+ ;; Enable data and instruction MMU.
+ move 0, $srs
+ moveq 0xf, $r0 ; IMMU, DMMU, DCache, Icache on
+ nop
+ nop
+ nop
+ move $r0, $s0
+ nop
+ nop
+ nop
+
+#ifdef CONFIG_SMP
+ ;; Read CPU ID
+ move 0, $srs
+ nop
+ nop
+ nop
+ move $s10, $r0
+ cmpq 0, $r0
+ beq master_cpu
+ nop
+slave_cpu:
+ ; A slave waits for cpu_now_booting to be equal to CPU ID.
+ move.d cpu_now_booting, $r1
+slave_wait:
+ cmp.d [$r1], $r0
+ bne slave_wait
+ nop
+ ; Time to boot-up. Get stack location provided by master CPU.
+ move.d smp_init_current_idle_thread, $r1
+ move.d [$r1], $sp
+ add.d 8192, $sp
+ move.d ebp_start, $r0 ; Defined in linker-script.
+ move $r0, $ebp
+ jsr smp_callin
+ nop
+master_cpu:
+#endif
+#ifndef CONFIG_ETRAXFS_SIM
+ ;; Check if starting from DRAM or flash.
+ lapcq ., $r0
+ and.d 0x7fffffff, $r0 ; Mask off the non-cache bit.
+ cmp.d 0x10000, $r0 ; Arbitrary, something above this code.
+ blo _inflash0
+ nop
+#endif
+
+ jump _inram ; Jump to cached RAM.
+ nop
+
+ ;; Jumpgate.
+_inflash0:
+ jump _inflash
+ nop
+
+ ;; Put the following in a section so that storage for it can be
+ ;; reclaimed after init is finished.
+ .section ".init.text", "ax"
+
+_inflash:
+
+ ;; Initialize DRAM.
+ cmp.d RAM_INIT_MAGIC, $r8 ; Already initialized?
+ beq _dram_initialized
+ nop
+
+#include "../lib/dram_init.S"
+
+_dram_initialized:
+ ;; Copy the text and data section to DRAM. This depends on that the
+ ;; variables used below are correctly set up by the linker script.
+ ;; The calculated value stored in R4 is used below.
+ moveq 0, $r0 ; Source.
+ move.d text_start, $r1 ; Destination.
+ move.d __vmlinux_end, $r2
+ move.d $r2, $r4
+ sub.d $r1, $r4
+1: move.w [$r0+], $r3
+ move.w $r3, [$r1+]
+ cmp.d $r2, $r1
+ blo 1b
+ nop
+
+ ;; Keep CRAMFS in flash.
+ moveq 0, $r0
+ move.d romfs_length, $r1
+ move.d $r0, [$r1]
+ move.d [$r4], $r0 ; cramfs_super.magic
+ cmp.d CRAMFS_MAGIC, $r0
+ bne 1f
+ nop
+
+ addoq +4, $r4, $acr
+ move.d [$acr], $r0
+ move.d romfs_length, $r1
+ move.d $r0, [$r1]
+ add.d 0xf0000000, $r4 ; Add cached flash start in virtual memory.
+ move.d romfs_start, $r1
+ move.d $r4, [$r1]
+1: moveq 1, $r0
+ move.d romfs_in_flash, $r1
+ move.d $r0, [$r1]
+
+ jump _start_it ; Jump to cached code.
+ nop
+
+_inram:
+ ;; Check if booting from NAND flash (in that case we just remember the offset
+ ;; into the flash where cramfs should be).
+ move.d REG_ADDR(config, regi_config, r_bootsel), $r0
+ move.d [$r0], $r0
+ and.d REG_MASK(config, r_bootsel, boot_mode), $r0
+ cmp.d REG_STATE(config, r_bootsel, boot_mode, nand), $r0
+ bne move_cramfs
+ moveq 1,$r0
+ move.d crisv32_nand_boot, $r1
+ move.d $r0, [$r1]
+ move.d crisv32_nand_cramfs_offset, $r1
+ move.d $r9, [$r1]
+ moveq 1, $r0
+ move.d romfs_in_flash, $r1
+ move.d $r0, [$r1]
+ jump _start_it
+ nop
+
+move_cramfs:
+ ;; Move the cramfs after BSS.
+ moveq 0, $r0
+ move.d romfs_length, $r1
+ move.d $r0, [$r1]
+
+#ifndef CONFIG_ETRAXFS_SIM
+ ;; The kernel could have been unpacked to DRAM by the loader, but
+ ;; the cramfs image could still be inte the flash immediately
+ ;; following the compressed kernel image. The loaded passes the address
+ ;; of the bute succeeding the last compressed byte in the flash in
+ ;; register R9 when starting the kernel.
+ cmp.d 0x0ffffff8, $r9
+ bhs _no_romfs_in_flash ; R9 points outside the flash area.
+ nop
+#else
+ ba _no_romfs_in_flash
+ nop
+#endif
+ move.d [$r9], $r0 ; cramfs_super.magic
+ cmp.d CRAMFS_MAGIC, $r0
+ bne _no_romfs_in_flash
+ nop
+
+ addoq +4, $r9, $acr
+ move.d [$acr], $r0
+ move.d romfs_length, $r1
+ move.d $r0, [$r1]
+ add.d 0xf0000000, $r9 ; Add cached flash start in virtual memory.
+ move.d romfs_start, $r1
+ move.d $r9, [$r1]
+ moveq 1, $r0
+ move.d romfs_in_flash, $r1
+ move.d $r0, [$r1]
+
+ jump _start_it ; Jump to cached code.
+ nop
+
+_no_romfs_in_flash:
+ ;; Look for cramfs.
+#ifndef CONFIG_ETRAXFS_SIM
+ move.d __vmlinux_end, $r0
+#else
+ move.d __end, $r0
+#endif
+ move.d [$r0], $r1
+ cmp.d CRAMFS_MAGIC, $r1
+ bne 2f
+ nop
+
+ addoq +4, $r0, $acr
+ move.d [$acr], $r2
+ move.d _end, $r1
+ move.d romfs_start, $r3
+ move.d $r1, [$r3]
+ move.d romfs_length, $r3
+ move.d $r2, [$r3]
+
+#ifndef CONFIG_ETRAXFS_SIM
+ add.d $r2, $r0
+ add.d $r2, $r1
+
+ lsrq 1, $r2 ; Size is in bytes, we copy words.
+ addq 1, $r2
+1:
+ move.w [$r0], $r3
+ move.w $r3, [$r1]
+ subq 2, $r0
+ subq 2, $r1
+ subq 1, $r2
+ bne 1b
+ nop
+#endif
+
+2:
+ moveq 0, $r0
+ move.d romfs_in_flash, $r1
+ move.d $r0, [$r1]
+
+ jump _start_it ; Jump to cached code.
+ nop
+
+_start_it:
+
+ ;; Check if kernel command line is supplied
+ cmp.d COMMAND_LINE_MAGIC, $r10
+ bne no_command_line
+ nop
+
+ move.d 256, $r13
+ move.d cris_command_line, $r10
+ or.d 0x80000000, $r11 ; Make it virtual
+1:
+ move.b [$r11+], $r12
+ move.b $r12, [$r10+]
+ subq 1, $r13
+ bne 1b
+ nop
+
+no_command_line:
+
+ ;; The kernel stack contains a task structure for each task. This
+ ;; the initial kernel stack is in the same page as the init_task,
+ ;; but starts at the top of the page, i.e. + 8192 bytes.
+ move.d init_thread_union + 8192, $sp
+ move.d ebp_start, $r0 ; Defined in linker-script.
+ move $r0, $ebp
+ move.d etrax_irv, $r1 ; Set the exception base register and pointer.
+ move.d $r0, [$r1]
+
+#ifndef CONFIG_ETRAXFS_SIM
+ ;; Clear the BSS region from _bss_start to _end.
+ move.d __bss_start, $r0
+ move.d _end, $r1
+1: clear.d [$r0+]
+ cmp.d $r1, $r0
+ blo 1b
+ nop
+#endif
+
+#ifdef CONFIG_ETRAXFS_SIM
+ /* Set the watchdog timeout to something big. Will be removed when */
+ /* watchdog can be disabled with command line option */
+ move.d 0x7fffffff, $r10
+ jsr CPU_WATCHDOG_TIMEOUT
+ nop
+#endif
+
+ ; Initialize registers to increase determinism
+ move.d __bss_start, $r0
+ movem [$r0], $r13
+
+ jump start_kernel ; Jump to start_kernel() in init/main.c.
+ nop
+
+ .data
+etrax_irv:
+ .dword 0
+romfs_start:
+ .dword 0
+romfs_length:
+ .dword 0
+romfs_in_flash:
+ .dword 0
+crisv32_nand_boot:
+ .dword 0
+crisv32_nand_cramfs_offset:
+ .dword 0
+
+swapper_pg_dir = 0xc0002000
+
+ .section ".init.data", "aw"
+
+#include "../lib/hw_settings.S"
diff --git a/arch/cris/arch-v32/kernel/io.c b/arch/cris/arch-v32/kernel/io.c
new file mode 100644
index 000000000000..6bc9f263c3d6
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/io.c
@@ -0,0 +1,154 @@
+/*
+ * Helper functions for I/O pins.
+ *
+ * Copyright (c) 2004 Axis Communications AB.
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/io.h>
+#include <asm/arch/pinmux.h>
+#include <asm/arch/hwregs/gio_defs.h>
+
+struct crisv32_ioport crisv32_ioports[] =
+{
+ {
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pa_oe),
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pa_dout),
+ (unsigned long*)REG_ADDR(gio, regi_gio, r_pa_din),
+ 8
+ },
+ {
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pb_oe),
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pb_dout),
+ (unsigned long*)REG_ADDR(gio, regi_gio, r_pb_din),
+ 18
+ },
+ {
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pc_oe),
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pc_dout),
+ (unsigned long*)REG_ADDR(gio, regi_gio, r_pc_din),
+ 18
+ },
+ {
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pd_oe),
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pd_dout),
+ (unsigned long*)REG_ADDR(gio, regi_gio, r_pd_din),
+ 18
+ },
+ {
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pe_oe),
+ (unsigned long*)REG_ADDR(gio, regi_gio, rw_pe_dout),
+ (unsigned long*)REG_ADDR(gio, regi_gio, r_pe_din),
+ 18
+ }
+};
+
+#define NBR_OF_PORTS sizeof(crisv32_ioports)/sizeof(struct crisv32_ioport)
+
+struct crisv32_iopin crisv32_led1_green;
+struct crisv32_iopin crisv32_led1_red;
+struct crisv32_iopin crisv32_led2_green;
+struct crisv32_iopin crisv32_led2_red;
+struct crisv32_iopin crisv32_led3_green;
+struct crisv32_iopin crisv32_led3_red;
+
+/* Dummy port used when green LED and red LED is on the same bit */
+static unsigned long io_dummy;
+static struct crisv32_ioport dummy_port =
+{
+ &io_dummy,
+ &io_dummy,
+ &io_dummy,
+ 18
+};
+static struct crisv32_iopin dummy_led =
+{
+ &dummy_port,
+ 0
+};
+
+static int __init crisv32_io_init(void)
+{
+ int ret = 0;
+ /* Initialize LEDs */
+ ret += crisv32_io_get_name(&crisv32_led1_green, CONFIG_ETRAX_LED1G);
+ ret += crisv32_io_get_name(&crisv32_led1_red, CONFIG_ETRAX_LED1R);
+ ret += crisv32_io_get_name(&crisv32_led2_green, CONFIG_ETRAX_LED2G);
+ ret += crisv32_io_get_name(&crisv32_led2_red, CONFIG_ETRAX_LED2R);
+ ret += crisv32_io_get_name(&crisv32_led3_green, CONFIG_ETRAX_LED3G);
+ ret += crisv32_io_get_name(&crisv32_led3_red, CONFIG_ETRAX_LED3R);
+ crisv32_io_set_dir(&crisv32_led1_green, crisv32_io_dir_out);
+ crisv32_io_set_dir(&crisv32_led1_red, crisv32_io_dir_out);
+ crisv32_io_set_dir(&crisv32_led2_green, crisv32_io_dir_out);
+ crisv32_io_set_dir(&crisv32_led2_red, crisv32_io_dir_out);
+ crisv32_io_set_dir(&crisv32_led3_green, crisv32_io_dir_out);
+ crisv32_io_set_dir(&crisv32_led3_red, crisv32_io_dir_out);
+
+ if (!strcmp(CONFIG_ETRAX_LED1G, CONFIG_ETRAX_LED1R))
+ crisv32_led1_red = dummy_led;
+ if (!strcmp(CONFIG_ETRAX_LED2G, CONFIG_ETRAX_LED2R))
+ crisv32_led2_red = dummy_led;
+
+ return ret;
+}
+
+__initcall(crisv32_io_init);
+
+int crisv32_io_get(struct crisv32_iopin* iopin,
+ unsigned int port, unsigned int pin)
+{
+ if (port > NBR_OF_PORTS)
+ return -EINVAL;
+ if (port > crisv32_ioports[port].pin_count)
+ return -EINVAL;
+
+ iopin->bit = 1 << pin;
+ iopin->port = &crisv32_ioports[port];
+
+ if (crisv32_pinmux_alloc(port, pin, pin, pinmux_gpio))
+ return -EIO;
+
+ return 0;
+}
+
+int crisv32_io_get_name(struct crisv32_iopin* iopin,
+ char* name)
+{
+ int port;
+ int pin;
+
+ if (toupper(*name) == 'P')
+ name++;
+
+ if (toupper(*name) < 'A' || toupper(*name) > 'E')
+ return -EINVAL;
+
+ port = toupper(*name) - 'A';
+ name++;
+ pin = simple_strtoul(name, NULL, 10);
+
+ if (pin < 0 || pin > crisv32_ioports[port].pin_count)
+ return -EINVAL;
+
+ iopin->bit = 1 << pin;
+ iopin->port = &crisv32_ioports[port];
+
+ if (crisv32_pinmux_alloc(port, pin, pin, pinmux_gpio))
+ return -EIO;
+
+ return 0;
+}
+
+#ifdef CONFIG_PCI
+/* PCI I/O access stuff */
+struct cris_io_operations* cris_iops = NULL;
+EXPORT_SYMBOL(cris_iops);
+#endif
+
diff --git a/arch/cris/arch-v32/kernel/irq.c b/arch/cris/arch-v32/kernel/irq.c
new file mode 100644
index 000000000000..c78cc2685133
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/irq.c
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2003, Axis Communications AB.
+ */
+
+#include <asm/irq.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/smp.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/profile.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/threads.h>
+#include <linux/spinlock.h>
+#include <linux/kernel_stat.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/intr_vect.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+
+#define CPU_FIXED -1
+
+/* IRQ masks (refer to comment for crisv32_do_multiple) */
+#define TIMER_MASK (1 << (TIMER_INTR_VECT - FIRST_IRQ))
+#ifdef CONFIG_ETRAX_KGDB
+#if defined(CONFIG_ETRAX_KGDB_PORT0)
+#define IGNOREMASK (1 << (SER0_INTR_VECT - FIRST_IRQ))
+#elif defined(CONFIG_ETRAX_KGDB_PORT1)
+#define IGNOREMASK (1 << (SER1_INTR_VECT - FIRST_IRQ))
+#elif defined(CONFIG_ETRAX_KGB_PORT2)
+#define IGNOREMASK (1 << (SER2_INTR_VECT - FIRST_IRQ))
+#elif defined(CONFIG_ETRAX_KGDB_PORT3)
+#define IGNOREMASK (1 << (SER3_INTR_VECT - FIRST_IRQ))
+#endif
+#endif
+
+DEFINE_SPINLOCK(irq_lock);
+
+struct cris_irq_allocation
+{
+ int cpu; /* The CPU to which the IRQ is currently allocated. */
+ cpumask_t mask; /* The CPUs to which the IRQ may be allocated. */
+};
+
+struct cris_irq_allocation irq_allocations[NR_IRQS] =
+ {[0 ... NR_IRQS - 1] = {0, CPU_MASK_ALL}};
+
+static unsigned long irq_regs[NR_CPUS] =
+{
+ regi_irq,
+#ifdef CONFIG_SMP
+ regi_irq2,
+#endif
+};
+
+unsigned long cpu_irq_counters[NR_CPUS];
+unsigned long irq_counters[NR_REAL_IRQS];
+
+/* From irq.c. */
+extern void weird_irq(void);
+
+/* From entry.S. */
+extern void system_call(void);
+extern void nmi_interrupt(void);
+extern void multiple_interrupt(void);
+extern void gdb_handle_exception(void);
+extern void i_mmu_refill(void);
+extern void i_mmu_invalid(void);
+extern void i_mmu_access(void);
+extern void i_mmu_execute(void);
+extern void d_mmu_refill(void);
+extern void d_mmu_invalid(void);
+extern void d_mmu_access(void);
+extern void d_mmu_write(void);
+
+/* From kgdb.c. */
+extern void kgdb_init(void);
+extern void breakpoint(void);
+
+/*
+ * Build the IRQ handler stubs using macros from irq.h. First argument is the
+ * IRQ number, the second argument is the corresponding bit in
+ * intr_rw_vect_mask found in asm/arch/hwregs/intr_vect_defs.h.
+ */
+BUILD_IRQ(0x31, (1 << 0)) /* memarb */
+BUILD_IRQ(0x32, (1 << 1)) /* gen_io */
+BUILD_IRQ(0x33, (1 << 2)) /* iop0 */
+BUILD_IRQ(0x34, (1 << 3)) /* iop1 */
+BUILD_IRQ(0x35, (1 << 4)) /* iop2 */
+BUILD_IRQ(0x36, (1 << 5)) /* iop3 */
+BUILD_IRQ(0x37, (1 << 6)) /* dma0 */
+BUILD_IRQ(0x38, (1 << 7)) /* dma1 */
+BUILD_IRQ(0x39, (1 << 8)) /* dma2 */
+BUILD_IRQ(0x3a, (1 << 9)) /* dma3 */
+BUILD_IRQ(0x3b, (1 << 10)) /* dma4 */
+BUILD_IRQ(0x3c, (1 << 11)) /* dma5 */
+BUILD_IRQ(0x3d, (1 << 12)) /* dma6 */
+BUILD_IRQ(0x3e, (1 << 13)) /* dma7 */
+BUILD_IRQ(0x3f, (1 << 14)) /* dma8 */
+BUILD_IRQ(0x40, (1 << 15)) /* dma9 */
+BUILD_IRQ(0x41, (1 << 16)) /* ata */
+BUILD_IRQ(0x42, (1 << 17)) /* sser0 */
+BUILD_IRQ(0x43, (1 << 18)) /* sser1 */
+BUILD_IRQ(0x44, (1 << 19)) /* ser0 */
+BUILD_IRQ(0x45, (1 << 20)) /* ser1 */
+BUILD_IRQ(0x46, (1 << 21)) /* ser2 */
+BUILD_IRQ(0x47, (1 << 22)) /* ser3 */
+BUILD_IRQ(0x48, (1 << 23))
+BUILD_IRQ(0x49, (1 << 24)) /* eth0 */
+BUILD_IRQ(0x4a, (1 << 25)) /* eth1 */
+BUILD_TIMER_IRQ(0x4b, (1 << 26))/* timer */
+BUILD_IRQ(0x4c, (1 << 27)) /* bif_arb */
+BUILD_IRQ(0x4d, (1 << 28)) /* bif_dma */
+BUILD_IRQ(0x4e, (1 << 29)) /* ext */
+BUILD_IRQ(0x4f, (1 << 29)) /* ipi */
+
+/* Pointers to the low-level handlers. */
+static void (*interrupt[NR_IRQS])(void) = {
+ IRQ0x31_interrupt, IRQ0x32_interrupt, IRQ0x33_interrupt,
+ IRQ0x34_interrupt, IRQ0x35_interrupt, IRQ0x36_interrupt,
+ IRQ0x37_interrupt, IRQ0x38_interrupt, IRQ0x39_interrupt,
+ IRQ0x3a_interrupt, IRQ0x3b_interrupt, IRQ0x3c_interrupt,
+ IRQ0x3d_interrupt, IRQ0x3e_interrupt, IRQ0x3f_interrupt,
+ IRQ0x40_interrupt, IRQ0x41_interrupt, IRQ0x42_interrupt,
+ IRQ0x43_interrupt, IRQ0x44_interrupt, IRQ0x45_interrupt,
+ IRQ0x46_interrupt, IRQ0x47_interrupt, IRQ0x48_interrupt,
+ IRQ0x49_interrupt, IRQ0x4a_interrupt, IRQ0x4b_interrupt,
+ IRQ0x4c_interrupt, IRQ0x4d_interrupt, IRQ0x4e_interrupt,
+ IRQ0x4f_interrupt
+};
+
+void
+block_irq(int irq, int cpu)
+{
+ int intr_mask;
+ unsigned long flags;
+
+ spin_lock_irqsave(&irq_lock, flags);
+ intr_mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask);
+
+ /* Remember; 1 let thru, 0 block. */
+ intr_mask &= ~(1 << (irq - FIRST_IRQ));
+
+ REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, intr_mask);
+ spin_unlock_irqrestore(&irq_lock, flags);
+}
+
+void
+unblock_irq(int irq, int cpu)
+{
+ int intr_mask;
+ unsigned long flags;
+
+ spin_lock_irqsave(&irq_lock, flags);
+ intr_mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask);
+
+ /* Remember; 1 let thru, 0 block. */
+ intr_mask |= (1 << (irq - FIRST_IRQ));
+
+ REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, intr_mask);
+ spin_unlock_irqrestore(&irq_lock, flags);
+}
+
+/* Find out which CPU the irq should be allocated to. */
+static int irq_cpu(int irq)
+{
+ int cpu;
+ unsigned long flags;
+
+ spin_lock_irqsave(&irq_lock, flags);
+ cpu = irq_allocations[irq - FIRST_IRQ].cpu;
+
+ /* Fixed interrupts stay on the local CPU. */
+ if (cpu == CPU_FIXED)
+ {
+ spin_unlock_irqrestore(&irq_lock, flags);
+ return smp_processor_id();
+ }
+
+
+ /* Let the interrupt stay if possible */
+ if (cpu_isset(cpu, irq_allocations[irq - FIRST_IRQ].mask))
+ goto out;
+
+ /* IRQ must be moved to another CPU. */
+ cpu = first_cpu(irq_allocations[irq - FIRST_IRQ].mask);
+ irq_allocations[irq - FIRST_IRQ].cpu = cpu;
+out:
+ spin_unlock_irqrestore(&irq_lock, flags);
+ return cpu;
+}
+
+void
+mask_irq(int irq)
+{
+ int cpu;
+
+ for (cpu = 0; cpu < NR_CPUS; cpu++)
+ block_irq(irq, cpu);
+}
+
+void
+unmask_irq(int irq)
+{
+ unblock_irq(irq, irq_cpu(irq));
+}
+
+
+static unsigned int startup_crisv32_irq(unsigned int irq)
+{
+ unmask_irq(irq);
+ return 0;
+}
+
+static void shutdown_crisv32_irq(unsigned int irq)
+{
+ mask_irq(irq);
+}
+
+static void enable_crisv32_irq(unsigned int irq)
+{
+ unmask_irq(irq);
+}
+
+static void disable_crisv32_irq(unsigned int irq)
+{
+ mask_irq(irq);
+}
+
+static void ack_crisv32_irq(unsigned int irq)
+{
+}
+
+static void end_crisv32_irq(unsigned int irq)
+{
+}
+
+void set_affinity_crisv32_irq(unsigned int irq, cpumask_t dest)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&irq_lock, flags);
+ irq_allocations[irq - FIRST_IRQ].mask = dest;
+ spin_unlock_irqrestore(&irq_lock, flags);
+}
+
+static struct hw_interrupt_type crisv32_irq_type = {
+ .typename = "CRISv32",
+ .startup = startup_crisv32_irq,
+ .shutdown = shutdown_crisv32_irq,
+ .enable = enable_crisv32_irq,
+ .disable = disable_crisv32_irq,
+ .ack = ack_crisv32_irq,
+ .end = end_crisv32_irq,
+ .set_affinity = set_affinity_crisv32_irq
+};
+
+void
+set_exception_vector(int n, irqvectptr addr)
+{
+ etrax_irv->v[n] = (irqvectptr) addr;
+}
+
+extern void do_IRQ(int irq, struct pt_regs * regs);
+
+void
+crisv32_do_IRQ(int irq, int block, struct pt_regs* regs)
+{
+ /* Interrupts that may not be moved to another CPU and
+ * are SA_INTERRUPT may skip blocking. This is currently
+ * only valid for the timer IRQ and the IPI and is used
+ * for the timer interrupt to avoid watchdog starvation.
+ */
+ if (!block) {
+ do_IRQ(irq, regs);
+ return;
+ }
+
+ block_irq(irq, smp_processor_id());
+ do_IRQ(irq, regs);
+
+ unblock_irq(irq, irq_cpu(irq));
+}
+
+/* If multiple interrupts occur simultaneously we get a multiple
+ * interrupt from the CPU and software has to sort out which
+ * interrupts that happened. There are two special cases here:
+ *
+ * 1. Timer interrupts may never be blocked because of the
+ * watchdog (refer to comment in include/asr/arch/irq.h)
+ * 2. GDB serial port IRQs are unhandled here and will be handled
+ * as a single IRQ when it strikes again because the GDB
+ * stubb wants to save the registers in its own fashion.
+ */
+void
+crisv32_do_multiple(struct pt_regs* regs)
+{
+ int cpu;
+ int mask;
+ int masked;
+ int bit;
+
+ cpu = smp_processor_id();
+
+ /* An extra irq_enter here to prevent softIRQs to run after
+ * each do_IRQ. This will decrease the interrupt latency.
+ */
+ irq_enter();
+
+ /* Get which IRQs that happend. */
+ masked = REG_RD_INT(intr_vect, irq_regs[cpu], r_masked_vect);
+
+ /* Calculate new IRQ mask with these IRQs disabled. */
+ mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask);
+ mask &= ~masked;
+
+ /* Timer IRQ is never masked */
+ if (masked & TIMER_MASK)
+ mask |= TIMER_MASK;
+
+ /* Block all the IRQs */
+ REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, mask);
+
+ /* Check for timer IRQ and handle it special. */
+ if (masked & TIMER_MASK) {
+ masked &= ~TIMER_MASK;
+ do_IRQ(TIMER_INTR_VECT, regs);
+ }
+
+#ifdef IGNORE_MASK
+ /* Remove IRQs that can't be handled as multiple. */
+ masked &= ~IGNORE_MASK;
+#endif
+
+ /* Handle the rest of the IRQs. */
+ for (bit = 0; bit < 32; bit++)
+ {
+ if (masked & (1 << bit))
+ do_IRQ(bit + FIRST_IRQ, regs);
+ }
+
+ /* Unblock all the IRQs. */
+ mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask);
+ mask |= masked;
+ REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, mask);
+
+ /* This irq_exit() will trigger the soft IRQs. */
+ irq_exit();
+}
+
+/*
+ * This is called by start_kernel. It fixes the IRQ masks and setup the
+ * interrupt vector table to point to bad_interrupt pointers.
+ */
+void __init
+init_IRQ(void)
+{
+ int i;
+ int j;
+ reg_intr_vect_rw_mask vect_mask = {0};
+
+ /* Clear all interrupts masks. */
+ REG_WR(intr_vect, regi_irq, rw_mask, vect_mask);
+
+ for (i = 0; i < 256; i++)
+ etrax_irv->v[i] = weird_irq;
+
+ /* Point all IRQ's to bad handlers. */
+ for (i = FIRST_IRQ, j = 0; j < NR_IRQS; i++, j++) {
+ irq_desc[j].handler = &crisv32_irq_type;
+ set_exception_vector(i, interrupt[j]);
+ }
+
+ /* Mark Timer and IPI IRQs as CPU local */
+ irq_allocations[TIMER_INTR_VECT - FIRST_IRQ].cpu = CPU_FIXED;
+ irq_desc[TIMER_INTR_VECT].status |= IRQ_PER_CPU;
+ irq_allocations[IPI_INTR_VECT - FIRST_IRQ].cpu = CPU_FIXED;
+ irq_desc[IPI_INTR_VECT].status |= IRQ_PER_CPU;
+
+ set_exception_vector(0x00, nmi_interrupt);
+ set_exception_vector(0x30, multiple_interrupt);
+
+ /* Set up handler for various MMU bus faults. */
+ set_exception_vector(0x04, i_mmu_refill);
+ set_exception_vector(0x05, i_mmu_invalid);
+ set_exception_vector(0x06, i_mmu_access);
+ set_exception_vector(0x07, i_mmu_execute);
+ set_exception_vector(0x08, d_mmu_refill);
+ set_exception_vector(0x09, d_mmu_invalid);
+ set_exception_vector(0x0a, d_mmu_access);
+ set_exception_vector(0x0b, d_mmu_write);
+
+ /* The system-call trap is reached by "break 13". */
+ set_exception_vector(0x1d, system_call);
+
+ /* Exception handlers for debugging, both user-mode and kernel-mode. */
+
+ /* Break 8. */
+ set_exception_vector(0x18, gdb_handle_exception);
+ /* Hardware single step. */
+ set_exception_vector(0x3, gdb_handle_exception);
+ /* Hardware breakpoint. */
+ set_exception_vector(0xc, gdb_handle_exception);
+
+#ifdef CONFIG_ETRAX_KGDB
+ kgdb_init();
+ /* Everything is set up; now trap the kernel. */
+ breakpoint();
+#endif
+}
+
diff --git a/arch/cris/arch-v32/kernel/kgdb.c b/arch/cris/arch-v32/kernel/kgdb.c
new file mode 100644
index 000000000000..480e56348be2
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/kgdb.c
@@ -0,0 +1,1660 @@
+/*
+ * arch/cris/arch-v32/kernel/kgdb.c
+ *
+ * CRIS v32 version by Orjan Friberg, Axis Communications AB.
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com),
+ *
+ * Originally written by Glenn Engel, Lake Stevens Instrument Division
+ *
+ * Contributed by HP Systems
+ *
+ * Modified for SPARC by Stu Grossman, Cygnus Support.
+ *
+ * Modified for Linux/MIPS (and MIPS in general) by Andreas Busse
+ * Send complaints, suggestions etc. to <andy@waldorf-gmbh.de>
+ *
+ * Copyright (C) 1995 Andreas Busse
+ */
+
+/* FIXME: Check the documentation. */
+
+/*
+ * kgdb usage notes:
+ * -----------------
+ *
+ * If you select CONFIG_ETRAX_KGDB in the configuration, the kernel will be
+ * built with different gcc flags: "-g" is added to get debug infos, and
+ * "-fomit-frame-pointer" is omitted to make debugging easier. Since the
+ * resulting kernel will be quite big (approx. > 7 MB), it will be stripped
+ * before compresion. Such a kernel will behave just as usually, except if
+ * given a "debug=<device>" command line option. (Only serial devices are
+ * allowed for <device>, i.e. no printers or the like; possible values are
+ * machine depedend and are the same as for the usual debug device, the one
+ * for logging kernel messages.) If that option is given and the device can be
+ * initialized, the kernel will connect to the remote gdb in trap_init(). The
+ * serial parameters are fixed to 8N1 and 115200 bps, for easyness of
+ * implementation.
+ *
+ * To start a debugging session, start that gdb with the debugging kernel
+ * image (the one with the symbols, vmlinux.debug) named on the command line.
+ * This file will be used by gdb to get symbol and debugging infos about the
+ * kernel. Next, select remote debug mode by
+ * target remote <device>
+ * where <device> is the name of the serial device over which the debugged
+ * machine is connected. Maybe you have to adjust the baud rate by
+ * set remotebaud <rate>
+ * or also other parameters with stty:
+ * shell stty ... </dev/...
+ * If the kernel to debug has already booted, it waited for gdb and now
+ * connects, and you'll see a breakpoint being reported. If the kernel isn't
+ * running yet, start it now. The order of gdb and the kernel doesn't matter.
+ * Another thing worth knowing about in the getting-started phase is how to
+ * debug the remote protocol itself. This is activated with
+ * set remotedebug 1
+ * gdb will then print out each packet sent or received. You'll also get some
+ * messages about the gdb stub on the console of the debugged machine.
+ *
+ * If all that works, you can use lots of the usual debugging techniques on
+ * the kernel, e.g. inspecting and changing variables/memory, setting
+ * breakpoints, single stepping and so on. It's also possible to interrupt the
+ * debugged kernel by pressing C-c in gdb. Have fun! :-)
+ *
+ * The gdb stub is entered (and thus the remote gdb gets control) in the
+ * following situations:
+ *
+ * - If breakpoint() is called. This is just after kgdb initialization, or if
+ * a breakpoint() call has been put somewhere into the kernel source.
+ * (Breakpoints can of course also be set the usual way in gdb.)
+ * In eLinux, we call breakpoint() in init/main.c after IRQ initialization.
+ *
+ * - If there is a kernel exception, i.e. bad_super_trap() or die_if_kernel()
+ * are entered. All the CPU exceptions are mapped to (more or less..., see
+ * the hard_trap_info array below) appropriate signal, which are reported
+ * to gdb. die_if_kernel() is usually called after some kind of access
+ * error and thus is reported as SIGSEGV.
+ *
+ * - When panic() is called. This is reported as SIGABRT.
+ *
+ * - If C-c is received over the serial line, which is treated as
+ * SIGINT.
+ *
+ * Of course, all these signals are just faked for gdb, since there is no
+ * signal concept as such for the kernel. It also isn't possible --obviously--
+ * to set signal handlers from inside gdb, or restart the kernel with a
+ * signal.
+ *
+ * Current limitations:
+ *
+ * - While the kernel is stopped, interrupts are disabled for safety reasons
+ * (i.e., variables not changing magically or the like). But this also
+ * means that the clock isn't running anymore, and that interrupts from the
+ * hardware may get lost/not be served in time. This can cause some device
+ * errors...
+ *
+ * - When single-stepping, only one instruction of the current thread is
+ * executed, but interrupts are allowed for that time and will be serviced
+ * if pending. Be prepared for that.
+ *
+ * - All debugging happens in kernel virtual address space. There's no way to
+ * access physical memory not mapped in kernel space, or to access user
+ * space. A way to work around this is using get_user_long & Co. in gdb
+ * expressions, but only for the current process.
+ *
+ * - Interrupting the kernel only works if interrupts are currently allowed,
+ * and the interrupt of the serial line isn't blocked by some other means
+ * (IPL too high, disabled, ...)
+ *
+ * - The gdb stub is currently not reentrant, i.e. errors that happen therein
+ * (e.g. accessing invalid memory) may not be caught correctly. This could
+ * be removed in future by introducing a stack of struct registers.
+ *
+ */
+
+/*
+ * To enable debugger support, two things need to happen. One, a
+ * call to kgdb_init() 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().
+ *
+ * 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/string.h>
+#include <linux/signal.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/linkage.h>
+#include <linux/reboot.h>
+
+#include <asm/setup.h>
+#include <asm/ptrace.h>
+
+#include <asm/irq.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+#include <asm/arch/hwregs/ser_defs.h>
+
+/* From entry.S. */
+extern void gdb_handle_exception(void);
+/* From kgdb_asm.S. */
+extern void kgdb_handle_exception(void);
+
+static int kgdb_started = 0;
+
+/********************************* Register image ****************************/
+
+typedef
+struct register_image
+{
+ /* Offset */
+ unsigned int r0; /* 0x00 */
+ unsigned int r1; /* 0x04 */
+ unsigned int r2; /* 0x08 */
+ unsigned int r3; /* 0x0C */
+ unsigned int r4; /* 0x10 */
+ unsigned int r5; /* 0x14 */
+ unsigned int r6; /* 0x18 */
+ unsigned int r7; /* 0x1C */
+ unsigned int r8; /* 0x20; Frame pointer (if any) */
+ unsigned int r9; /* 0x24 */
+ unsigned int r10; /* 0x28 */
+ unsigned int r11; /* 0x2C */
+ unsigned int r12; /* 0x30 */
+ unsigned int r13; /* 0x34 */
+ unsigned int sp; /* 0x38; R14, Stack pointer */
+ unsigned int acr; /* 0x3C; R15, Address calculation register. */
+
+ unsigned char bz; /* 0x40; P0, 8-bit zero register */
+ unsigned char vr; /* 0x41; P1, Version register (8-bit) */
+ unsigned int pid; /* 0x42; P2, Process ID */
+ unsigned char srs; /* 0x46; P3, Support register select (8-bit) */
+ unsigned short wz; /* 0x47; P4, 16-bit zero register */
+ unsigned int exs; /* 0x49; P5, Exception status */
+ unsigned int eda; /* 0x4D; P6, Exception data address */
+ unsigned int mof; /* 0x51; P7, Multiply overflow register */
+ unsigned int dz; /* 0x55; P8, 32-bit zero register */
+ unsigned int ebp; /* 0x59; P9, Exception base pointer */
+ unsigned int erp; /* 0x5D; P10, Exception return pointer. Contains the PC we are interested in. */
+ unsigned int srp; /* 0x61; P11, Subroutine return pointer */
+ unsigned int nrp; /* 0x65; P12, NMI return pointer */
+ unsigned int ccs; /* 0x69; P13, Condition code stack */
+ unsigned int usp; /* 0x6D; P14, User mode stack pointer */
+ unsigned int spc; /* 0x71; P15, Single step PC */
+ unsigned int pc; /* 0x75; Pseudo register (for the most part set to ERP). */
+
+} registers;
+
+typedef
+struct bp_register_image
+{
+ /* Support register bank 0. */
+ unsigned int s0_0;
+ unsigned int s1_0;
+ unsigned int s2_0;
+ unsigned int s3_0;
+ unsigned int s4_0;
+ unsigned int s5_0;
+ unsigned int s6_0;
+ unsigned int s7_0;
+ unsigned int s8_0;
+ unsigned int s9_0;
+ unsigned int s10_0;
+ unsigned int s11_0;
+ unsigned int s12_0;
+ unsigned int s13_0;
+ unsigned int s14_0;
+ unsigned int s15_0;
+
+ /* Support register bank 1. */
+ unsigned int s0_1;
+ unsigned int s1_1;
+ unsigned int s2_1;
+ unsigned int s3_1;
+ unsigned int s4_1;
+ unsigned int s5_1;
+ unsigned int s6_1;
+ unsigned int s7_1;
+ unsigned int s8_1;
+ unsigned int s9_1;
+ unsigned int s10_1;
+ unsigned int s11_1;
+ unsigned int s12_1;
+ unsigned int s13_1;
+ unsigned int s14_1;
+ unsigned int s15_1;
+
+ /* Support register bank 2. */
+ unsigned int s0_2;
+ unsigned int s1_2;
+ unsigned int s2_2;
+ unsigned int s3_2;
+ unsigned int s4_2;
+ unsigned int s5_2;
+ unsigned int s6_2;
+ unsigned int s7_2;
+ unsigned int s8_2;
+ unsigned int s9_2;
+ unsigned int s10_2;
+ unsigned int s11_2;
+ unsigned int s12_2;
+ unsigned int s13_2;
+ unsigned int s14_2;
+ unsigned int s15_2;
+
+ /* Support register bank 3. */
+ unsigned int s0_3; /* BP_CTRL */
+ unsigned int s1_3; /* BP_I0_START */
+ unsigned int s2_3; /* BP_I0_END */
+ unsigned int s3_3; /* BP_D0_START */
+ unsigned int s4_3; /* BP_D0_END */
+ unsigned int s5_3; /* BP_D1_START */
+ unsigned int s6_3; /* BP_D1_END */
+ unsigned int s7_3; /* BP_D2_START */
+ unsigned int s8_3; /* BP_D2_END */
+ unsigned int s9_3; /* BP_D3_START */
+ unsigned int s10_3; /* BP_D3_END */
+ unsigned int s11_3; /* BP_D4_START */
+ unsigned int s12_3; /* BP_D4_END */
+ unsigned int s13_3; /* BP_D5_START */
+ unsigned int s14_3; /* BP_D5_END */
+ unsigned int s15_3; /* BP_RESERVED */
+
+} support_registers;
+
+enum register_name
+{
+ R0, R1, R2, R3,
+ R4, R5, R6, R7,
+ R8, R9, R10, R11,
+ R12, R13, SP, ACR,
+
+ BZ, VR, PID, SRS,
+ WZ, EXS, EDA, MOF,
+ DZ, EBP, ERP, SRP,
+ NRP, CCS, USP, SPC,
+ PC,
+
+ S0, S1, S2, S3,
+ S4, S5, S6, S7,
+ S8, S9, S10, S11,
+ S12, S13, S14, S15
+
+};
+
+/* The register sizes of the registers in register_name. An unimplemented register
+ is designated by size 0 in this array. */
+static int register_size[] =
+{
+ 4, 4, 4, 4,
+ 4, 4, 4, 4,
+ 4, 4, 4, 4,
+ 4, 4, 4, 4,
+
+ 1, 1, 4, 1,
+ 2, 4, 4, 4,
+ 4, 4, 4, 4,
+ 4, 4, 4, 4,
+
+ 4,
+
+ 4, 4, 4, 4,
+ 4, 4, 4, 4,
+ 4, 4, 4, 4,
+ 4, 4, 4
+
+};
+
+/* Contains the register image of the kernel.
+ (Global so that they can be reached from assembler code.) */
+registers reg;
+support_registers sreg;
+
+/************** Prototypes for local library functions ***********************/
+
+/* Copy of strcpy from libc. */
+static char *gdb_cris_strcpy(char *s1, const char *s2);
+
+/* Copy of strlen from libc. */
+static int gdb_cris_strlen(const char *s);
+
+/* Copy of memchr from libc. */
+static void *gdb_cris_memchr(const void *s, int c, int n);
+
+/* Copy of strtol from libc. Does only support base 16. */
+static int gdb_cris_strtol(const char *s, char **endptr, int base);
+
+/********************** Prototypes for local functions. **********************/
+
+/* Write a value to a specified register regno in the register image
+ of the current thread. */
+static int write_register(int regno, char *val);
+
+/* Read a value from a specified register in the register image. Returns the
+ status of the read operation. The register value is returned in valptr. */
+static int read_register(char regno, unsigned int *valptr);
+
+/* Serial port, reads one character. ETRAX 100 specific. from debugport.c */
+int getDebugChar(void);
+
+#ifdef CONFIG_ETRAXFS_SIM
+int getDebugChar(void)
+{
+ return socketread();
+}
+#endif
+
+/* Serial port, writes one character. ETRAX 100 specific. from debugport.c */
+void putDebugChar(int val);
+
+#ifdef CONFIG_ETRAXFS_SIM
+void putDebugChar(int val)
+{
+ socketwrite((char *)&val, 1);
+}
+#endif
+
+/* Returns the character equivalent of a nibble, bit 7, 6, 5, and 4 of a byte,
+ represented by int x. */
+static char highhex(int x);
+
+/* Returns the character equivalent of a nibble, bit 3, 2, 1, and 0 of a byte,
+ represented by int x. */
+static char lowhex(int x);
+
+/* Returns the integer equivalent of a hexadecimal character. */
+static int hex(char ch);
+
+/* Convert the memory, pointed to by mem into hexadecimal representation.
+ Put the result in buf, and return a pointer to the last character
+ in buf (null). */
+static char *mem2hex(char *buf, unsigned char *mem, int count);
+
+/* Convert the array, in hexadecimal representation, pointed to by buf into
+ binary representation. Put the result in mem, and return a pointer to
+ the character after the last byte written. */
+static unsigned char *hex2mem(unsigned char *mem, char *buf, int count);
+
+/* Put the content of the array, in binary representation, pointed to by buf
+ into memory pointed to by mem, and return a pointer to
+ the character after the last byte written. */
+static unsigned char *bin2mem(unsigned char *mem, unsigned char *buf, int count);
+
+/* Await the sequence $<data>#<checksum> and store <data> in the array buffer
+ returned. */
+static void getpacket(char *buffer);
+
+/* Send $<data>#<checksum> from the <data> in the array buffer. */
+static void putpacket(char *buffer);
+
+/* Build and send a response packet in order to inform the host the
+ stub is stopped. */
+static void stub_is_stopped(int sigval);
+
+/* All expected commands are sent from remote.c. Send a response according
+ to the description in remote.c. Not static since it needs to be reached
+ from assembler code. */
+void handle_exception(int sigval);
+
+/* Performs a complete re-start from scratch. ETRAX specific. */
+static void kill_restart(void);
+
+/******************** Prototypes for global functions. ***********************/
+
+/* The string str is prepended with the GDB printout token and sent. */
+void putDebugString(const unsigned char *str, int len);
+
+/* A static breakpoint to be used at startup. */
+void breakpoint(void);
+
+/* Avoid warning as the internal_stack is not used in the C-code. */
+#define USEDVAR(name) { if (name) { ; } }
+#define USEDFUN(name) { void (*pf)(void) = (void *)name; USEDVAR(pf) }
+
+/********************************** Packet I/O ******************************/
+/* BUFMAX defines the maximum number of characters in
+ inbound/outbound buffers */
+/* FIXME: How do we know it's enough? */
+#define BUFMAX 512
+
+/* Run-length encoding maximum length. Send 64 at most. */
+#define RUNLENMAX 64
+
+/* Definition of all valid hexadecimal characters */
+static const char hexchars[] = "0123456789abcdef";
+
+/* The inbound/outbound buffers used in packet I/O */
+static char input_buffer[BUFMAX];
+static char output_buffer[BUFMAX];
+
+/* Error and warning messages. */
+enum error_type
+{
+ SUCCESS, E01, E02, E03, E04, E05, E06,
+};
+
+static char *error_message[] =
+{
+ "",
+ "E01 Set current or general thread - H[c,g] - internal error.",
+ "E02 Change register content - P - cannot change read-only register.",
+ "E03 Thread is not alive.", /* T, not used. */
+ "E04 The command is not supported - [s,C,S,!,R,d,r] - internal error.",
+ "E05 Change register content - P - the register is not implemented..",
+ "E06 Change memory content - M - internal error.",
+};
+
+/********************************** Breakpoint *******************************/
+/* Use an internal stack in the breakpoint and interrupt response routines.
+ FIXME: How do we know the size of this stack is enough?
+ Global so it can be reached from assembler code. */
+#define INTERNAL_STACK_SIZE 1024
+char internal_stack[INTERNAL_STACK_SIZE];
+
+/* Due to the breakpoint return pointer, a state variable is needed to keep
+ track of whether it is a static (compiled) or dynamic (gdb-invoked)
+ breakpoint to be handled. A static breakpoint uses the content of register
+ ERP as it is whereas a dynamic breakpoint requires subtraction with 2
+ in order to execute the instruction. The first breakpoint is static; all
+ following are assumed to be dynamic. */
+static int dynamic_bp = 0;
+
+/********************************* String library ****************************/
+/* Single-step over library functions creates trap loops. */
+
+/* Copy char s2[] to s1[]. */
+static char*
+gdb_cris_strcpy(char *s1, const char *s2)
+{
+ char *s = s1;
+
+ for (s = s1; (*s++ = *s2++) != '\0'; )
+ ;
+ return s1;
+}
+
+/* Find length of s[]. */
+static int
+gdb_cris_strlen(const char *s)
+{
+ const char *sc;
+
+ for (sc = s; *sc != '\0'; sc++)
+ ;
+ return (sc - s);
+}
+
+/* Find first occurrence of c in s[n]. */
+static void*
+gdb_cris_memchr(const void *s, int c, int n)
+{
+ const unsigned char uc = c;
+ const unsigned char *su;
+
+ for (su = s; 0 < n; ++su, --n)
+ if (*su == uc)
+ return (void *)su;
+ return NULL;
+}
+/******************************* Standard library ****************************/
+/* Single-step over library functions creates trap loops. */
+/* Convert string to long. */
+static int
+gdb_cris_strtol(const char *s, char **endptr, int base)
+{
+ char *s1;
+ char *sd;
+ int x = 0;
+
+ for (s1 = (char*)s; (sd = gdb_cris_memchr(hexchars, *s1, base)) != NULL; ++s1)
+ x = x * base + (sd - hexchars);
+
+ if (endptr) {
+ /* Unconverted suffix is stored in endptr unless endptr is NULL. */
+ *endptr = s1;
+ }
+
+ return x;
+}
+
+/********************************* Register image ****************************/
+
+/* Write a value to a specified register in the register image of the current
+ thread. Returns status code SUCCESS, E02 or E05. */
+static int
+write_register(int regno, char *val)
+{
+ int status = SUCCESS;
+
+ if (regno >= R0 && regno <= ACR) {
+ /* Consecutive 32-bit registers. */
+ hex2mem((unsigned char *)&reg.r0 + (regno - R0) * sizeof(unsigned int),
+ val, sizeof(unsigned int));
+
+ } else if (regno == BZ || regno == VR || regno == WZ || regno == DZ) {
+ /* Read-only registers. */
+ status = E02;
+
+ } else if (regno == PID) {
+ /* 32-bit register. (Even though we already checked SRS and WZ, we cannot
+ combine this with the EXS - SPC write since SRS and WZ have different size.) */
+ hex2mem((unsigned char *)&reg.pid, val, sizeof(unsigned int));
+
+ } else if (regno == SRS) {
+ /* 8-bit register. */
+ hex2mem((unsigned char *)&reg.srs, val, sizeof(unsigned char));
+
+ } else if (regno >= EXS && regno <= SPC) {
+ /* Consecutive 32-bit registers. */
+ hex2mem((unsigned char *)&reg.exs + (regno - EXS) * sizeof(unsigned int),
+ val, sizeof(unsigned int));
+
+ } else if (regno == PC) {
+ /* Pseudo-register. Treat as read-only. */
+ status = E02;
+
+ } else if (regno >= S0 && regno <= S15) {
+ /* 32-bit registers. */
+ hex2mem((unsigned char *)&sreg.s0_0 + (reg.srs * 16 * sizeof(unsigned int)) + (regno - S0) * sizeof(unsigned int), val, sizeof(unsigned int));
+ } else {
+ /* Non-existing register. */
+ status = E05;
+ }
+ return status;
+}
+
+/* Read a value from a specified register in the register image. Returns the
+ value in the register or -1 for non-implemented registers. */
+static int
+read_register(char regno, unsigned int *valptr)
+{
+ int status = SUCCESS;
+
+ /* We read the zero registers from the register struct (instead of just returning 0)
+ to catch errors. */
+
+ if (regno >= R0 && regno <= ACR) {
+ /* Consecutive 32-bit registers. */
+ *valptr = *(unsigned int *)((char *)&reg.r0 + (regno - R0) * sizeof(unsigned int));
+
+ } else if (regno == BZ || regno == VR) {
+ /* Consecutive 8-bit registers. */
+ *valptr = (unsigned int)(*(unsigned char *)
+ ((char *)&reg.bz + (regno - BZ) * sizeof(char)));
+
+ } else if (regno == PID) {
+ /* 32-bit register. */
+ *valptr = *(unsigned int *)((char *)&reg.pid);
+
+ } else if (regno == SRS) {
+ /* 8-bit register. */
+ *valptr = (unsigned int)(*(unsigned char *)((char *)&reg.srs));
+
+ } else if (regno == WZ) {
+ /* 16-bit register. */
+ *valptr = (unsigned int)(*(unsigned short *)(char *)&reg.wz);
+
+ } else if (regno >= EXS && regno <= PC) {
+ /* Consecutive 32-bit registers. */
+ *valptr = *(unsigned int *)((char *)&reg.exs + (regno - EXS) * sizeof(unsigned int));
+
+ } else if (regno >= S0 && regno <= S15) {
+ /* Consecutive 32-bit registers, located elsewhere. */
+ *valptr = *(unsigned int *)((char *)&sreg.s0_0 + (reg.srs * 16 * sizeof(unsigned int)) + (regno - S0) * sizeof(unsigned int));
+
+ } else {
+ /* Non-existing register. */
+ status = E05;
+ }
+ return status;
+
+}
+
+/********************************** Packet I/O ******************************/
+/* Returns the character equivalent of a nibble, bit 7, 6, 5, and 4 of a byte,
+ represented by int x. */
+static inline char
+highhex(int x)
+{
+ return hexchars[(x >> 4) & 0xf];
+}
+
+/* Returns the character equivalent of a nibble, bit 3, 2, 1, and 0 of a byte,
+ represented by int x. */
+static inline char
+lowhex(int x)
+{
+ return hexchars[x & 0xf];
+}
+
+/* Returns the integer equivalent of a hexadecimal character. */
+static int
+hex(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;
+}
+
+/* Convert the memory, pointed to by mem into hexadecimal representation.
+ Put the result in buf, and return a pointer to the last character
+ in buf (null). */
+
+static char *
+mem2hex(char *buf, unsigned char *mem, int count)
+{
+ int i;
+ int ch;
+
+ if (mem == NULL) {
+ /* Invalid address, caught by 'm' packet handler. */
+ for (i = 0; i < count; i++) {
+ *buf++ = '0';
+ *buf++ = '0';
+ }
+ } else {
+ /* Valid mem address. */
+ for (i = 0; i < count; i++) {
+ ch = *mem++;
+ *buf++ = highhex (ch);
+ *buf++ = lowhex (ch);
+ }
+ }
+ /* Terminate properly. */
+ *buf = '\0';
+ return buf;
+}
+
+/* Same as mem2hex, but puts it in network byte order. */
+static char *
+mem2hex_nbo(char *buf, unsigned char *mem, int count)
+{
+ int i;
+ int ch;
+
+ mem += count - 1;
+ for (i = 0; i < count; i++) {
+ ch = *mem--;
+ *buf++ = highhex (ch);
+ *buf++ = lowhex (ch);
+ }
+
+ /* Terminate properly. */
+ *buf = '\0';
+ return buf;
+}
+
+/* Convert the array, in hexadecimal representation, pointed to by buf into
+ binary representation. Put the result in mem, and return a pointer to
+ the character after the last byte written. */
+static unsigned char*
+hex2mem(unsigned char *mem, char *buf, int count)
+{
+ int i;
+ unsigned char ch;
+ for (i = 0; i < count; i++) {
+ ch = hex (*buf++) << 4;
+ ch = ch + hex (*buf++);
+ *mem++ = ch;
+ }
+ return mem;
+}
+
+/* Put the content of the array, in binary representation, pointed to by buf
+ into memory pointed to by mem, and return a pointer to the character after
+ the last byte written.
+ Gdb will escape $, #, and the escape char (0x7d). */
+static unsigned char*
+bin2mem(unsigned char *mem, unsigned char *buf, int count)
+{
+ int i;
+ unsigned char *next;
+ for (i = 0; i < count; i++) {
+ /* Check for any escaped characters. Be paranoid and
+ only unescape chars that should be escaped. */
+ if (*buf == 0x7d) {
+ next = buf + 1;
+ if (*next == 0x3 || *next == 0x4 || *next == 0x5D) {
+ /* #, $, ESC */
+ buf++;
+ *buf += 0x20;
+ }
+ }
+ *mem++ = *buf++;
+ }
+ return mem;
+}
+
+/* Await the sequence $<data>#<checksum> and store <data> in the array buffer
+ returned. */
+static void
+getpacket(char *buffer)
+{
+ unsigned char checksum;
+ unsigned char xmitcsum;
+ int i;
+ int count;
+ char ch;
+
+ do {
+ while((ch = getDebugChar ()) != '$')
+ /* Wait for the start character $ and ignore all other characters */;
+ checksum = 0;
+ xmitcsum = -1;
+ count = 0;
+ /* Read until a # or the end of the buffer is reached */
+ while (count < BUFMAX) {
+ ch = getDebugChar();
+ if (ch == '#')
+ break;
+ checksum = checksum + ch;
+ buffer[count] = ch;
+ count = count + 1;
+ }
+
+ if (count >= BUFMAX)
+ continue;
+
+ buffer[count] = 0;
+
+ if (ch == '#') {
+ xmitcsum = hex(getDebugChar()) << 4;
+ xmitcsum += hex(getDebugChar());
+ if (checksum != xmitcsum) {
+ /* Wrong checksum */
+ putDebugChar('-');
+ } else {
+ /* Correct checksum */
+ putDebugChar('+');
+ /* If sequence characters are received, reply with them */
+ if (buffer[2] == ':') {
+ putDebugChar(buffer[0]);
+ putDebugChar(buffer[1]);
+ /* Remove the sequence characters from the buffer */
+ count = gdb_cris_strlen(buffer);
+ for (i = 3; i <= count; i++)
+ buffer[i - 3] = buffer[i];
+ }
+ }
+ }
+ } while (checksum != xmitcsum);
+}
+
+/* Send $<data>#<checksum> from the <data> in the array buffer. */
+
+static void
+putpacket(char *buffer)
+{
+ int checksum;
+ int runlen;
+ int encode;
+
+ do {
+ char *src = buffer;
+ putDebugChar('$');
+ checksum = 0;
+ while (*src) {
+ /* Do run length encoding */
+ putDebugChar(*src);
+ checksum += *src;
+ runlen = 0;
+ while (runlen < RUNLENMAX && *src == src[runlen]) {
+ runlen++;
+ }
+ if (runlen > 3) {
+ /* Got a useful amount */
+ putDebugChar ('*');
+ checksum += '*';
+ encode = runlen + ' ' - 4;
+ putDebugChar(encode);
+ checksum += encode;
+ src += runlen;
+ } else {
+ src++;
+ }
+ }
+ putDebugChar('#');
+ putDebugChar(highhex (checksum));
+ putDebugChar(lowhex (checksum));
+ } while(kgdb_started && (getDebugChar() != '+'));
+}
+
+/* The string str is prepended with the GDB printout token and sent. Required
+ in traditional implementations. */
+void
+putDebugString(const unsigned char *str, int len)
+{
+ /* Move SPC forward if we are single-stepping. */
+ asm("spchere:");
+ asm("move $spc, $r10");
+ asm("cmp.d spchere, $r10");
+ asm("bne nosstep");
+ asm("nop");
+ asm("move.d spccont, $r10");
+ asm("move $r10, $spc");
+ asm("nosstep:");
+
+ output_buffer[0] = 'O';
+ mem2hex(&output_buffer[1], (unsigned char *)str, len);
+ putpacket(output_buffer);
+
+ asm("spccont:");
+}
+
+/********************************** Handle exceptions ************************/
+/* Build and send a response packet in order to inform the host the
+ stub is stopped. TAAn...:r...;n...:r...;n...:r...;
+ AA = signal number
+ n... = register number (hex)
+ r... = register contents
+ n... = `thread'
+ r... = thread process ID. This is a hex integer.
+ n... = other string not starting with valid hex digit.
+ gdb should ignore this n,r pair and go on to the next.
+ This way we can extend the protocol. */
+static void
+stub_is_stopped(int sigval)
+{
+ char *ptr = output_buffer;
+ unsigned int reg_cont;
+
+ /* Send trap type (converted to signal) */
+
+ *ptr++ = 'T';
+ *ptr++ = highhex(sigval);
+ *ptr++ = lowhex(sigval);
+
+ if (((reg.exs & 0xff00) >> 8) == 0xc) {
+
+ /* Some kind of hardware watchpoint triggered. Find which one
+ and determine its type (read/write/access). */
+ int S, bp, trig_bits = 0, rw_bits = 0;
+ int trig_mask = 0;
+ unsigned int *bp_d_regs = &sreg.s3_3;
+ /* In a lot of cases, the stopped data address will simply be EDA.
+ In some cases, we adjust it to match the watched data range.
+ (We don't want to change the actual EDA though). */
+ unsigned int stopped_data_address;
+ /* The S field of EXS. */
+ S = (reg.exs & 0xffff0000) >> 16;
+
+ if (S & 1) {
+ /* Instruction watchpoint. */
+ /* FIXME: Check against, and possibly adjust reported EDA. */
+ } else {
+ /* Data watchpoint. Find the one that triggered. */
+ for (bp = 0; bp < 6; bp++) {
+
+ /* Dx_RD, Dx_WR in the S field of EXS for this BP. */
+ int bitpos_trig = 1 + bp * 2;
+ /* Dx_BPRD, Dx_BPWR in BP_CTRL for this BP. */
+ int bitpos_config = 2 + bp * 4;
+
+ /* Get read/write trig bits for this BP. */
+ trig_bits = (S & (3 << bitpos_trig)) >> bitpos_trig;
+
+ /* Read/write config bits for this BP. */
+ rw_bits = (sreg.s0_3 & (3 << bitpos_config)) >> bitpos_config;
+ if (trig_bits) {
+ /* Sanity check: the BP shouldn't trigger for accesses
+ that it isn't configured for. */
+ if ((rw_bits == 0x1 && trig_bits != 0x1) ||
+ (rw_bits == 0x2 && trig_bits != 0x2))
+ panic("Invalid r/w trigging for this BP");
+
+ /* Mark this BP as trigged for future reference. */
+ trig_mask |= (1 << bp);
+
+ if (reg.eda >= bp_d_regs[bp * 2] &&
+ reg.eda <= bp_d_regs[bp * 2 + 1]) {
+ /* EDA withing range for this BP; it must be the one
+ we're looking for. */
+ stopped_data_address = reg.eda;
+ break;
+ }
+ }
+ }
+ if (bp < 6) {
+ /* Found a trigged BP with EDA within its configured data range. */
+ } else if (trig_mask) {
+ /* Something triggered, but EDA doesn't match any BP's range. */
+ for (bp = 0; bp < 6; bp++) {
+ /* Dx_BPRD, Dx_BPWR in BP_CTRL for this BP. */
+ int bitpos_config = 2 + bp * 4;
+
+ /* Read/write config bits for this BP (needed later). */
+ rw_bits = (sreg.s0_3 & (3 << bitpos_config)) >> bitpos_config;
+
+ if (trig_mask & (1 << bp)) {
+ /* EDA within 31 bytes of the configured start address? */
+ if (reg.eda + 31 >= bp_d_regs[bp * 2]) {
+ /* Changing the reported address to match
+ the start address of the first applicable BP. */
+ stopped_data_address = bp_d_regs[bp * 2];
+ break;
+ } else {
+ /* We continue since we might find another useful BP. */
+ printk("EDA doesn't match trigged BP's range");
+ }
+ }
+ }
+ }
+
+ /* No match yet? */
+ BUG_ON(bp >= 6);
+ /* Note that we report the type according to what the BP is configured
+ for (otherwise we'd never report an 'awatch'), not according to how
+ it trigged. We did check that the trigged bits match what the BP is
+ configured for though. */
+ if (rw_bits == 0x1) {
+ /* read */
+ strncpy(ptr, "rwatch", 6);
+ ptr += 6;
+ } else if (rw_bits == 0x2) {
+ /* write */
+ strncpy(ptr, "watch", 5);
+ ptr += 5;
+ } else if (rw_bits == 0x3) {
+ /* access */
+ strncpy(ptr, "awatch", 6);
+ ptr += 6;
+ } else {
+ panic("Invalid r/w bits for this BP.");
+ }
+
+ *ptr++ = ':';
+ /* Note that we don't read_register(EDA, ...) */
+ ptr = mem2hex_nbo(ptr, (unsigned char *)&stopped_data_address, register_size[EDA]);
+ *ptr++ = ';';
+ }
+ }
+ /* Only send PC, frame and stack pointer. */
+ read_register(PC, &reg_cont);
+ *ptr++ = highhex(PC);
+ *ptr++ = lowhex(PC);
+ *ptr++ = ':';
+ ptr = mem2hex(ptr, (unsigned char *)&reg_cont, register_size[PC]);
+ *ptr++ = ';';
+
+ read_register(R8, &reg_cont);
+ *ptr++ = highhex(R8);
+ *ptr++ = lowhex(R8);
+ *ptr++ = ':';
+ ptr = mem2hex(ptr, (unsigned char *)&reg_cont, register_size[R8]);
+ *ptr++ = ';';
+
+ read_register(SP, &reg_cont);
+ *ptr++ = highhex(SP);
+ *ptr++ = lowhex(SP);
+ *ptr++ = ':';
+ ptr = mem2hex(ptr, (unsigned char *)&reg_cont, register_size[SP]);
+ *ptr++ = ';';
+
+ /* Send ERP as well; this will save us an entire register fetch in some cases. */
+ read_register(ERP, &reg_cont);
+ *ptr++ = highhex(ERP);
+ *ptr++ = lowhex(ERP);
+ *ptr++ = ':';
+ ptr = mem2hex(ptr, (unsigned char *)&reg_cont, register_size[ERP]);
+ *ptr++ = ';';
+
+ /* null-terminate and send it off */
+ *ptr = 0;
+ putpacket(output_buffer);
+}
+
+/* Returns the size of an instruction that has a delay slot. */
+
+int insn_size(unsigned long pc)
+{
+ unsigned short opcode = *(unsigned short *)pc;
+ int size = 0;
+
+ switch ((opcode & 0x0f00) >> 8) {
+ case 0x0:
+ case 0x9:
+ case 0xb:
+ size = 2;
+ break;
+ case 0xe:
+ case 0xf:
+ size = 6;
+ break;
+ case 0xd:
+ /* Could be 4 or 6; check more bits. */
+ if ((opcode & 0xff) == 0xff)
+ size = 4;
+ else
+ size = 6;
+ break;
+ default:
+ panic("Couldn't find size of opcode 0x%x at 0x%lx\n", opcode, pc);
+ }
+
+ return size;
+}
+
+void register_fixup(int sigval)
+{
+ /* Compensate for ACR push at the beginning of exception handler. */
+ reg.sp += 4;
+
+ /* Standard case. */
+ reg.pc = reg.erp;
+ if (reg.erp & 0x1) {
+ /* Delay slot bit set. Report as stopped on proper instruction. */
+ if (reg.spc) {
+ /* Rely on SPC if set. */
+ reg.pc = reg.spc;
+ } else {
+ /* Calculate the PC from the size of the instruction
+ that the delay slot we're in belongs to. */
+ reg.pc += insn_size(reg.erp & ~1) - 1 ;
+ }
+ }
+
+ if ((reg.exs & 0x3) == 0x0) {
+ /* Bits 1 - 0 indicate the type of memory operation performed
+ by the interrupted instruction. 0 means no memory operation,
+ and EDA is undefined in that case. We zero it to avoid confusion. */
+ reg.eda = 0;
+ }
+
+ if (sigval == SIGTRAP) {
+ /* Break 8, single step or hardware breakpoint exception. */
+
+ /* Check IDX field of EXS. */
+ if (((reg.exs & 0xff00) >> 8) == 0x18) {
+
+ /* Break 8. */
+
+ /* Static (compiled) breakpoints must return to the next instruction
+ in order to avoid infinite loops (default value of ERP). Dynamic
+ (gdb-invoked) must subtract the size of the break instruction from
+ the ERP so that the instruction that was originally in the break
+ instruction's place will be run when we return from the exception. */
+ if (!dynamic_bp) {
+ /* Assuming that all breakpoints are dynamic from now on. */
+ dynamic_bp = 1;
+ } else {
+
+ /* Only if not in a delay slot. */
+ if (!(reg.erp & 0x1)) {
+ reg.erp -= 2;
+ reg.pc -= 2;
+ }
+ }
+
+ } else if (((reg.exs & 0xff00) >> 8) == 0x3) {
+ /* Single step. */
+ /* Don't fiddle with S1. */
+
+ } else if (((reg.exs & 0xff00) >> 8) == 0xc) {
+
+ /* Hardware watchpoint exception. */
+
+ /* SPC has been updated so that we will get a single step exception
+ when we return, but we don't want that. */
+ reg.spc = 0;
+
+ /* Don't fiddle with S1. */
+ }
+
+ } else if (sigval == SIGINT) {
+ /* Nothing special. */
+ }
+}
+
+static void insert_watchpoint(char type, int addr, int len)
+{
+ /* Breakpoint/watchpoint types (GDB terminology):
+ 0 = memory breakpoint for instructions
+ (not supported; done via memory write instead)
+ 1 = hardware breakpoint for instructions (supported)
+ 2 = write watchpoint (supported)
+ 3 = read watchpoint (supported)
+ 4 = access watchpoint (supported) */
+
+ if (type < '1' || type > '4') {
+ output_buffer[0] = 0;
+ return;
+ }
+
+ /* Read watchpoints are set as access watchpoints, because of GDB's
+ inability to deal with pure read watchpoints. */
+ if (type == '3')
+ type = '4';
+
+ if (type == '1') {
+ /* Hardware (instruction) breakpoint. */
+ /* Bit 0 in BP_CTRL holds the configuration for I0. */
+ if (sreg.s0_3 & 0x1) {
+ /* Already in use. */
+ gdb_cris_strcpy(output_buffer, error_message[E04]);
+ return;
+ }
+ /* Configure. */
+ sreg.s1_3 = addr;
+ sreg.s2_3 = (addr + len - 1);
+ sreg.s0_3 |= 1;
+ } else {
+ int bp;
+ unsigned int *bp_d_regs = &sreg.s3_3;
+
+ /* The watchpoint allocation scheme is the simplest possible.
+ For example, if a region is watched for read and
+ a write watch is requested, a new watchpoint will
+ be used. Also, if a watch for a region that is already
+ covered by one or more existing watchpoints, a new
+ watchpoint will be used. */
+
+ /* First, find a free data watchpoint. */
+ for (bp = 0; bp < 6; bp++) {
+ /* Each data watchpoint's control registers occupy 2 bits
+ (hence the 3), starting at bit 2 for D0 (hence the 2)
+ with 4 bits between for each watchpoint (yes, the 4). */
+ if (!(sreg.s0_3 & (0x3 << (2 + (bp * 4))))) {
+ break;
+ }
+ }
+
+ if (bp > 5) {
+ /* We're out of watchpoints. */
+ gdb_cris_strcpy(output_buffer, error_message[E04]);
+ return;
+ }
+
+ /* Configure the control register first. */
+ if (type == '3' || type == '4') {
+ /* Trigger on read. */
+ sreg.s0_3 |= (1 << (2 + bp * 4));
+ }
+ if (type == '2' || type == '4') {
+ /* Trigger on write. */
+ sreg.s0_3 |= (2 << (2 + bp * 4));
+ }
+
+ /* Ugly pointer arithmetics to configure the watched range. */
+ bp_d_regs[bp * 2] = addr;
+ bp_d_regs[bp * 2 + 1] = (addr + len - 1);
+ }
+
+ /* Set the S1 flag to enable watchpoints. */
+ reg.ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT));
+ gdb_cris_strcpy(output_buffer, "OK");
+}
+
+static void remove_watchpoint(char type, int addr, int len)
+{
+ /* Breakpoint/watchpoint types:
+ 0 = memory breakpoint for instructions
+ (not supported; done via memory write instead)
+ 1 = hardware breakpoint for instructions (supported)
+ 2 = write watchpoint (supported)
+ 3 = read watchpoint (supported)
+ 4 = access watchpoint (supported) */
+ if (type < '1' || type > '4') {
+ output_buffer[0] = 0;
+ return;
+ }
+
+ /* Read watchpoints are set as access watchpoints, because of GDB's
+ inability to deal with pure read watchpoints. */
+ if (type == '3')
+ type = '4';
+
+ if (type == '1') {
+ /* Hardware breakpoint. */
+ /* Bit 0 in BP_CTRL holds the configuration for I0. */
+ if (!(sreg.s0_3 & 0x1)) {
+ /* Not in use. */
+ gdb_cris_strcpy(output_buffer, error_message[E04]);
+ return;
+ }
+ /* Deconfigure. */
+ sreg.s1_3 = 0;
+ sreg.s2_3 = 0;
+ sreg.s0_3 &= ~1;
+ } else {
+ int bp;
+ unsigned int *bp_d_regs = &sreg.s3_3;
+ /* Try to find a watchpoint that is configured for the
+ specified range, then check that read/write also matches. */
+
+ /* Ugly pointer arithmetic, since I cannot rely on a
+ single switch (addr) as there may be several watchpoints with
+ the same start address for example. */
+
+ for (bp = 0; bp < 6; bp++) {
+ if (bp_d_regs[bp * 2] == addr &&
+ bp_d_regs[bp * 2 + 1] == (addr + len - 1)) {
+ /* Matching range. */
+ int bitpos = 2 + bp * 4;
+ int rw_bits;
+
+ /* Read/write bits for this BP. */
+ rw_bits = (sreg.s0_3 & (0x3 << bitpos)) >> bitpos;
+
+ if ((type == '3' && rw_bits == 0x1) ||
+ (type == '2' && rw_bits == 0x2) ||
+ (type == '4' && rw_bits == 0x3)) {
+ /* Read/write matched. */
+ break;
+ }
+ }
+ }
+
+ if (bp > 5) {
+ /* No watchpoint matched. */
+ gdb_cris_strcpy(output_buffer, error_message[E04]);
+ return;
+ }
+
+ /* Found a matching watchpoint. Now, deconfigure it by
+ both disabling read/write in bp_ctrl and zeroing its
+ start/end addresses. */
+ sreg.s0_3 &= ~(3 << (2 + (bp * 4)));
+ bp_d_regs[bp * 2] = 0;
+ bp_d_regs[bp * 2 + 1] = 0;
+ }
+
+ /* Note that we don't clear the S1 flag here. It's done when continuing. */
+ gdb_cris_strcpy(output_buffer, "OK");
+}
+
+
+
+/* All expected commands are sent from remote.c. Send a response according
+ to the description in remote.c. */
+void
+handle_exception(int sigval)
+{
+ /* Avoid warning of not used. */
+
+ USEDFUN(handle_exception);
+ USEDVAR(internal_stack[0]);
+
+ register_fixup(sigval);
+
+ /* Send response. */
+ stub_is_stopped(sigval);
+
+ for (;;) {
+ output_buffer[0] = '\0';
+ getpacket(input_buffer);
+ switch (input_buffer[0]) {
+ case 'g':
+ /* Read registers: g
+ Success: Each byte of register data is described by two hex digits.
+ Registers are in the internal order for GDB, and the bytes
+ in a register are in the same order the machine uses.
+ Failure: void. */
+ {
+ char *buf;
+ /* General and special registers. */
+ buf = mem2hex(output_buffer, (char *)&reg, sizeof(registers));
+ /* Support registers. */
+ /* -1 because of the null termination that mem2hex adds. */
+ mem2hex(buf,
+ (char *)&sreg + (reg.srs * 16 * sizeof(unsigned int)),
+ 16 * sizeof(unsigned int));
+ break;
+ }
+ case 'G':
+ /* Write registers. GXX..XX
+ Each byte of register data is described by two hex digits.
+ Success: OK
+ Failure: void. */
+ /* General and special registers. */
+ hex2mem((char *)&reg, &input_buffer[1], sizeof(registers));
+ /* Support registers. */
+ hex2mem((char *)&sreg + (reg.srs * 16 * sizeof(unsigned int)),
+ &input_buffer[1] + sizeof(registers),
+ 16 * sizeof(unsigned int));
+ gdb_cris_strcpy(output_buffer, "OK");
+ break;
+
+ case 'P':
+ /* Write register. Pn...=r...
+ Write register n..., hex value without 0x, with value r...,
+ which contains a hex value without 0x and two hex digits
+ for each byte in the register (target byte order). P1f=11223344 means
+ set register 31 to 44332211.
+ Success: OK
+ Failure: E02, E05 */
+ {
+ char *suffix;
+ int regno = gdb_cris_strtol(&input_buffer[1], &suffix, 16);
+ int status;
+
+ status = write_register(regno, suffix+1);
+
+ switch (status) {
+ case E02:
+ /* Do not support read-only registers. */
+ gdb_cris_strcpy(output_buffer, error_message[E02]);
+ break;
+ case E05:
+ /* Do not support non-existing registers. */
+ gdb_cris_strcpy(output_buffer, error_message[E05]);
+ break;
+ default:
+ /* Valid register number. */
+ gdb_cris_strcpy(output_buffer, "OK");
+ break;
+ }
+ }
+ break;
+
+ case 'm':
+ /* Read from memory. mAA..AA,LLLL
+ AA..AA is the address and LLLL is the length.
+ Success: XX..XX is the memory content. Can be fewer bytes than
+ requested if only part of the data may be read. m6000120a,6c means
+ retrieve 108 byte from base address 6000120a.
+ Failure: void. */
+ {
+ char *suffix;
+ unsigned char *addr = (unsigned char *)gdb_cris_strtol(&input_buffer[1],
+ &suffix, 16);
+ int len = gdb_cris_strtol(suffix+1, 0, 16);
+
+ /* Bogus read (i.e. outside the kernel's
+ segment)? . */
+ if (!((unsigned int)addr >= 0xc0000000 &&
+ (unsigned int)addr < 0xd0000000))
+ addr = NULL;
+
+ mem2hex(output_buffer, addr, len);
+ }
+ break;
+
+ case 'X':
+ /* Write to memory. XAA..AA,LLLL:XX..XX
+ AA..AA is the start address, LLLL is the number of bytes, and
+ XX..XX is the binary data.
+ Success: OK
+ Failure: void. */
+ case 'M':
+ /* Write to memory. MAA..AA,LLLL:XX..XX
+ AA..AA is the start address, LLLL is the number of bytes, and
+ XX..XX is the hexadecimal data.
+ Success: OK
+ Failure: void. */
+ {
+ char *lenptr;
+ char *dataptr;
+ unsigned char *addr = (unsigned char *)gdb_cris_strtol(&input_buffer[1],
+ &lenptr, 16);
+ int len = gdb_cris_strtol(lenptr+1, &dataptr, 16);
+ if (*lenptr == ',' && *dataptr == ':') {
+ if (input_buffer[0] == 'M') {
+ hex2mem(addr, dataptr + 1, len);
+ } else /* X */ {
+ bin2mem(addr, dataptr + 1, len);
+ }
+ gdb_cris_strcpy(output_buffer, "OK");
+ }
+ else {
+ gdb_cris_strcpy(output_buffer, error_message[E06]);
+ }
+ }
+ break;
+
+ case 'c':
+ /* Continue execution. cAA..AA
+ AA..AA is the address where execution is resumed. If AA..AA is
+ omitted, resume at the present address.
+ Success: return to the executing thread.
+ Failure: will never know. */
+
+ if (input_buffer[1] != '\0') {
+ /* FIXME: Doesn't handle address argument. */
+ gdb_cris_strcpy(output_buffer, error_message[E04]);
+ break;
+ }
+
+ /* Before continuing, make sure everything is set up correctly. */
+
+ /* Set the SPC to some unlikely value. */
+ reg.spc = 0;
+ /* Set the S1 flag to 0 unless some watchpoint is enabled (since setting
+ S1 to 0 would also disable watchpoints). (Note that bits 26-31 in BP_CTRL
+ are reserved, so don't check against those). */
+ if ((sreg.s0_3 & 0x3fff) == 0) {
+ reg.ccs &= ~(1 << (S_CCS_BITNR + CCS_SHIFT));
+ }
+
+ return;
+
+ case 's':
+ /* Step. sAA..AA
+ AA..AA is the address where execution is resumed. If AA..AA is
+ omitted, resume at the present address. Success: return to the
+ executing thread. Failure: will never know. */
+
+ if (input_buffer[1] != '\0') {
+ /* FIXME: Doesn't handle address argument. */
+ gdb_cris_strcpy(output_buffer, error_message[E04]);
+ break;
+ }
+
+ /* Set the SPC to PC, which is where we'll return
+ (deduced previously). */
+ reg.spc = reg.pc;
+
+ /* Set the S1 (first stacked, not current) flag, which will
+ kick into action when we rfe. */
+ reg.ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT));
+ return;
+
+ case 'Z':
+
+ /* Insert breakpoint or watchpoint, Ztype,addr,length.
+ Remote protocol says: A remote target shall return an empty string
+ for an unrecognized breakpoint or watchpoint packet type. */
+ {
+ char *lenptr;
+ char *dataptr;
+ int addr = gdb_cris_strtol(&input_buffer[3], &lenptr, 16);
+ int len = gdb_cris_strtol(lenptr + 1, &dataptr, 16);
+ char type = input_buffer[1];
+
+ insert_watchpoint(type, addr, len);
+ break;
+ }
+
+ case 'z':
+ /* Remove breakpoint or watchpoint, Ztype,addr,length.
+ Remote protocol says: A remote target shall return an empty string
+ for an unrecognized breakpoint or watchpoint packet type. */
+ {
+ char *lenptr;
+ char *dataptr;
+ int addr = gdb_cris_strtol(&input_buffer[3], &lenptr, 16);
+ int len = gdb_cris_strtol(lenptr + 1, &dataptr, 16);
+ char type = input_buffer[1];
+
+ remove_watchpoint(type, addr, len);
+ break;
+ }
+
+
+ case '?':
+ /* The last signal which caused a stop. ?
+ Success: SAA, where AA is the signal number.
+ Failure: void. */
+ output_buffer[0] = 'S';
+ output_buffer[1] = highhex(sigval);
+ output_buffer[2] = lowhex(sigval);
+ output_buffer[3] = 0;
+ break;
+
+ case 'D':
+ /* Detach from host. D
+ Success: OK, and return to the executing thread.
+ Failure: will never know */
+ putpacket("OK");
+ return;
+
+ case 'k':
+ case 'r':
+ /* kill request or reset request.
+ Success: restart of target.
+ Failure: will never know. */
+ kill_restart();
+ break;
+
+ case 'C':
+ case 'S':
+ case '!':
+ case 'R':
+ case 'd':
+ /* Continue with signal sig. Csig;AA..AA
+ Step with signal sig. Ssig;AA..AA
+ Use the extended remote protocol. !
+ Restart the target system. R0
+ Toggle debug flag. d
+ Search backwards. tAA:PP,MM
+ Not supported: E04 */
+
+ /* FIXME: What's the difference between not supported
+ and ignored (below)? */
+ gdb_cris_strcpy(output_buffer, error_message[E04]);
+ break;
+
+ default:
+ /* The stub should ignore other request and send an empty
+ response ($#<checksum>). This way we can extend the protocol and GDB
+ can tell whether the stub it is talking to uses the old or the new. */
+ output_buffer[0] = 0;
+ break;
+ }
+ putpacket(output_buffer);
+ }
+}
+
+void
+kgdb_init(void)
+{
+ reg_intr_vect_rw_mask intr_mask;
+ reg_ser_rw_intr_mask ser_intr_mask;
+
+ /* Configure the kgdb serial port. */
+#if defined(CONFIG_ETRAX_KGDB_PORT0)
+ /* Note: no shortcut registered (not handled by multiple_interrupt).
+ See entry.S. */
+ set_exception_vector(SER0_INTR_VECT, kgdb_handle_exception);
+ /* Enable the ser irq in the global config. */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.ser0 = 1;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ ser_intr_mask = REG_RD(ser, regi_ser0, rw_intr_mask);
+ ser_intr_mask.data_avail = regk_ser_yes;
+ REG_WR(ser, regi_ser0, rw_intr_mask, ser_intr_mask);
+#elif defined(CONFIG_ETRAX_KGDB_PORT1)
+ /* Note: no shortcut registered (not handled by multiple_interrupt).
+ See entry.S. */
+ set_exception_vector(SER1_INTR_VECT, kgdb_handle_exception);
+ /* Enable the ser irq in the global config. */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.ser1 = 1;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ ser_intr_mask = REG_RD(ser, regi_ser1, rw_intr_mask);
+ ser_intr_mask.data_avail = regk_ser_yes;
+ REG_WR(ser, regi_ser1, rw_intr_mask, ser_intr_mask);
+#elif defined(CONFIG_ETRAX_KGDB_PORT2)
+ /* Note: no shortcut registered (not handled by multiple_interrupt).
+ See entry.S. */
+ set_exception_vector(SER2_INTR_VECT, kgdb_handle_exception);
+ /* Enable the ser irq in the global config. */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.ser2 = 1;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ ser_intr_mask = REG_RD(ser, regi_ser2, rw_intr_mask);
+ ser_intr_mask.data_avail = regk_ser_yes;
+ REG_WR(ser, regi_ser2, rw_intr_mask, ser_intr_mask);
+#elif defined(CONFIG_ETRAX_KGDB_PORT3)
+ /* Note: no shortcut registered (not handled by multiple_interrupt).
+ See entry.S. */
+ set_exception_vector(SER3_INTR_VECT, kgdb_handle_exception);
+ /* Enable the ser irq in the global config. */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.ser3 = 1;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ ser_intr_mask = REG_RD(ser, regi_ser3, rw_intr_mask);
+ ser_intr_mask.data_avail = regk_ser_yes;
+ REG_WR(ser, regi_ser3, rw_intr_mask, ser_intr_mask);
+#endif
+
+}
+/* Performs a complete re-start from scratch. */
+static void
+kill_restart(void)
+{
+ machine_restart("");
+}
+
+/* Use this static breakpoint in the start-up only. */
+
+void
+breakpoint(void)
+{
+ kgdb_started = 1;
+ dynamic_bp = 0; /* This is a static, not a dynamic breakpoint. */
+ __asm__ volatile ("break 8"); /* Jump to kgdb_handle_breakpoint. */
+}
+
+/****************************** End of file **********************************/
diff --git a/arch/cris/arch-v32/kernel/kgdb_asm.S b/arch/cris/arch-v32/kernel/kgdb_asm.S
new file mode 100644
index 000000000000..b350dd279ed2
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/kgdb_asm.S
@@ -0,0 +1,552 @@
+/*
+ * Copyright (C) 2004 Axis Communications AB
+ *
+ * Code for handling break 8, hardware breakpoint, single step, and serial
+ * port exceptions for kernel debugging purposes.
+ */
+
+#include <linux/config.h>
+#include <asm/arch/hwregs/intr_vect.h>
+
+ ;; Exported functions.
+ .globl kgdb_handle_exception
+
+kgdb_handle_exception:
+
+;; Create a register image of the caller.
+;;
+;; First of all, save the ACR on the stack since we need it for address calculations.
+;; We put it into the register struct later.
+
+ subq 4, $sp
+ move.d $acr, [$sp]
+
+;; Now we are free to use ACR all we want.
+;; If we were running this handler with interrupts on, we would have to be careful
+;; to save and restore CCS manually, but since we aren't we treat it like every other
+;; register.
+
+ move.d reg, $acr
+ move.d $r0, [$acr] ; Save R0 (start of register struct)
+ addq 4, $acr
+ move.d $r1, [$acr] ; Save R1
+ addq 4, $acr
+ move.d $r2, [$acr] ; Save R2
+ addq 4, $acr
+ move.d $r3, [$acr] ; Save R3
+ addq 4, $acr
+ move.d $r4, [$acr] ; Save R4
+ addq 4, $acr
+ move.d $r5, [$acr] ; Save R5
+ addq 4, $acr
+ move.d $r6, [$acr] ; Save R6
+ addq 4, $acr
+ move.d $r7, [$acr] ; Save R7
+ addq 4, $acr
+ move.d $r8, [$acr] ; Save R8
+ addq 4, $acr
+ move.d $r9, [$acr] ; Save R9
+ addq 4, $acr
+ move.d $r10, [$acr] ; Save R10
+ addq 4, $acr
+ move.d $r11, [$acr] ; Save R11
+ addq 4, $acr
+ move.d $r12, [$acr] ; Save R12
+ addq 4, $acr
+ move.d $r13, [$acr] ; Save R13
+ addq 4, $acr
+ move.d $sp, [$acr] ; Save SP (R14)
+ addq 4, $acr
+
+ ;; The ACR register is already saved on the stack, so pop it from there.
+ move.d [$sp],$r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+
+ move $bz, [$acr]
+ addq 1, $acr
+ move $vr, [$acr]
+ addq 1, $acr
+ move $pid, [$acr]
+ addq 4, $acr
+ move $srs, [$acr]
+ addq 1, $acr
+ move $wz, [$acr]
+ addq 2, $acr
+ move $exs, [$acr]
+ addq 4, $acr
+ move $eda, [$acr]
+ addq 4, $acr
+ move $mof, [$acr]
+ addq 4, $acr
+ move $dz, [$acr]
+ addq 4, $acr
+ move $ebp, [$acr]
+ addq 4, $acr
+ move $erp, [$acr]
+ addq 4, $acr
+ move $srp, [$acr]
+ addq 4, $acr
+ move $nrp, [$acr]
+ addq 4, $acr
+ move $ccs, [$acr]
+ addq 4, $acr
+ move $usp, [$acr]
+ addq 4, $acr
+ move $spc, [$acr]
+ addq 4, $acr
+
+;; Skip the pseudo-PC.
+ addq 4, $acr
+
+;; Save the support registers in bank 0 - 3.
+ clear.d $r1 ; Bank counter
+ move.d sreg, $acr
+
+;; Bank 0
+ move $r1, $srs
+ nop
+ nop
+ nop
+ move $s0, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s1, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s2, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s3, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s4, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s5, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s6, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s7, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s8, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s9, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s10, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s11, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s12, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+
+ ;; Nothing in S13 - S15, bank 0
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+
+;; Bank 1 and bank 2 have the same layout, hence the loop.
+ addq 1, $r1
+1:
+ move $r1, $srs
+ nop
+ nop
+ nop
+ move $s0, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s1, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s2, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s3, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s4, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s5, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s6, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+
+ ;; Nothing in S7 - S15, bank 1 and 2
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+ clear.d [$acr]
+ addq 4, $acr
+
+ addq 1, $r1
+ cmpq 3, $r1
+ bne 1b
+ nop
+
+;; Bank 3
+ move $r1, $srs
+ nop
+ nop
+ nop
+ move $s0, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s1, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s2, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s3, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s4, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s5, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s6, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s7, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s8, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s9, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s10, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s11, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s12, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s13, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+ move $s14, $r0
+ move.d $r0, [$acr]
+ addq 4, $acr
+;; Nothing in S15, bank 3
+ clear.d [$acr]
+ addq 4, $acr
+
+;; Check what got us here: get IDX field of EXS.
+ move $exs, $r10
+ and.d 0xff00, $r10
+ lsrq 8, $r10
+#if defined(CONFIG_ETRAX_KGDB_PORT0)
+ cmp.d SER0_INTR_VECT, $r10 ; IRQ for serial port 0
+ beq sigint
+ nop
+#elif defined(CONFIG_ETRAX_KGDB_PORT1)
+ cmp.d SER1_INTR_VECT, $r10 ; IRQ for serial port 1
+ beq sigint
+ nop
+#elif defined(CONFIG_ETRAX_KGDB_PORT2)
+ cmp.d SER2_INTR_VECT, $r10 ; IRQ for serial port 2
+ beq sigint
+ nop
+#elif defined(CONFIG_ETRAX_KGDB_PORT3)
+ cmp.d SER3_INTR_VECT, $r10 ; IRQ for serial port 3
+ beq sigint
+ nop
+#endif
+;; Multiple interrupt must be due to serial break.
+ cmp.d 0x30, $r10 ; Multiple interrupt
+ beq sigint
+ nop
+;; Neither of those? Then it's a sigtrap.
+ ba handle_comm
+ moveq 5, $r10 ; Set SIGTRAP (delay slot)
+
+sigint:
+ ;; Serial interrupt; get character
+ jsr getDebugChar
+ nop ; Delay slot
+ cmp.b 3, $r10 ; \003 (Ctrl-C)?
+ bne return ; No, get out of here
+ nop
+ moveq 2, $r10 ; Set SIGINT
+
+;;
+;; Handle the communication
+;;
+handle_comm:
+ move.d internal_stack+1020, $sp ; Use the internal stack which grows upwards
+ jsr handle_exception ; Interactive routine
+ nop
+
+;;
+;; Return to the caller
+;;
+return:
+
+;; First of all, write the support registers.
+ clear.d $r1 ; Bank counter
+ move.d sreg, $acr
+
+;; Bank 0
+ move $r1, $srs
+ nop
+ nop
+ nop
+ move.d [$acr], $r0
+ move $r0, $s0
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s1
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s2
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s3
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s4
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s5
+ addq 4, $acr
+
+;; Nothing in S6 - S7, bank 0.
+ addq 4, $acr
+ addq 4, $acr
+
+ move.d [$acr], $r0
+ move $r0, $s8
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s9
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s10
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s11
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s12
+ addq 4, $acr
+
+;; Nothing in S13 - S15, bank 0
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+
+;; Bank 1 and bank 2 have the same layout, hence the loop.
+ addq 1, $r1
+2:
+ move $r1, $srs
+ nop
+ nop
+ nop
+ move.d [$acr], $r0
+ move $r0, $s0
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s1
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s2
+ addq 4, $acr
+
+;; S3 (MM_CAUSE) is read-only.
+ addq 4, $acr
+
+ move.d [$acr], $r0
+ move $r0, $s4
+ addq 4, $acr
+
+;; FIXME: Actually write S5/S6? (Affects MM_CAUSE.)
+ addq 4, $acr
+ addq 4, $acr
+
+;; Nothing in S7 - S15, bank 1 and 2
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+ addq 4, $acr
+
+ addq 1, $r1
+ cmpq 3, $r1
+ bne 2b
+ nop
+
+;; Bank 3
+ move $r1, $srs
+ nop
+ nop
+ nop
+ move.d [$acr], $r0
+ move $r0, $s0
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s1
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s2
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s3
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s4
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s5
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s6
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s7
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s8
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s9
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s10
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s11
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s12
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s13
+ addq 4, $acr
+ move.d [$acr], $r0
+ move $r0, $s14
+ addq 4, $acr
+
+;; Nothing in S15, bank 3
+ addq 4, $acr
+
+;; Now, move on to the regular register restoration process.
+
+ move.d reg, $acr ; Reset ACR to point at the beginning of the register image
+ move.d [$acr], $r0 ; Restore R0
+ addq 4, $acr
+ move.d [$acr], $r1 ; Restore R1
+ addq 4, $acr
+ move.d [$acr], $r2 ; Restore R2
+ addq 4, $acr
+ move.d [$acr], $r3 ; Restore R3
+ addq 4, $acr
+ move.d [$acr], $r4 ; Restore R4
+ addq 4, $acr
+ move.d [$acr], $r5 ; Restore R5
+ addq 4, $acr
+ move.d [$acr], $r6 ; Restore R6
+ addq 4, $acr
+ move.d [$acr], $r7 ; Restore R7
+ addq 4, $acr
+ move.d [$acr], $r8 ; Restore R8
+ addq 4, $acr
+ move.d [$acr], $r9 ; Restore R9
+ addq 4, $acr
+ move.d [$acr], $r10 ; Restore R10
+ addq 4, $acr
+ move.d [$acr], $r11 ; Restore R11
+ addq 4, $acr
+ move.d [$acr], $r12 ; Restore R12
+ addq 4, $acr
+ move.d [$acr], $r13 ; Restore R13
+
+;;
+;; We restore all registers, even though some of them probably haven't changed.
+;;
+
+ addq 4, $acr
+ move.d [$acr], $sp ; Restore SP (R14)
+
+ ;; ACR cannot be restored just yet.
+ addq 8, $acr
+
+ ;; Skip BZ, VR.
+ addq 2, $acr
+
+ move [$acr], $pid ; Restore PID
+ addq 4, $acr
+ move [$acr], $srs ; Restore SRS
+ nop
+ nop
+ nop
+ addq 1, $acr
+
+ ;; Skip WZ.
+ addq 2, $acr
+
+ move [$acr], $exs ; Restore EXS.
+ addq 4, $acr
+ move [$acr], $eda ; Restore EDA.
+ addq 4, $acr
+ move [$acr], $mof ; Restore MOF.
+
+ ;; Skip DZ.
+ addq 8, $acr
+
+ move [$acr], $ebp ; Restore EBP.
+ addq 4, $acr
+ move [$acr], $erp ; Restore ERP.
+ addq 4, $acr
+ move [$acr], $srp ; Restore SRP.
+ addq 4, $acr
+ move [$acr], $nrp ; Restore NRP.
+ addq 4, $acr
+ move [$acr], $ccs ; Restore CCS like an ordinary register.
+ addq 4, $acr
+ move [$acr], $usp ; Restore USP
+ addq 4, $acr
+ move [$acr], $spc ; Restore SPC
+ ; No restoration of pseudo-PC of course.
+
+ move.d reg, $acr ; Reset ACR to point at the beginning of the register image
+ add.d 15*4, $acr
+ move.d [$acr], $acr ; Finally, restore ACR.
+ rete ; Same as jump ERP
+ rfe ; Shifts CCS
diff --git a/arch/cris/arch-v32/kernel/pinmux.c b/arch/cris/arch-v32/kernel/pinmux.c
new file mode 100644
index 000000000000..a2b8aa37c1bf
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/pinmux.c
@@ -0,0 +1,229 @@
+/*
+ * Allocator for I/O pins. All pins are allocated to GPIO at bootup.
+ * Unassigned pins and GPIO pins can be allocated to a fixed interface
+ * or the I/O processor instead.
+ *
+ * Copyright (c) 2004 Axis Communications AB.
+ */
+
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/pinmux.h>
+#include <asm/arch/hwregs/pinmux_defs.h>
+
+#undef DEBUG
+
+#define PORT_PINS 18
+#define PORTS 4
+
+static char pins[PORTS][PORT_PINS];
+static DEFINE_SPINLOCK(pinmux_lock);
+
+static void crisv32_pinmux_set(int port);
+
+int
+crisv32_pinmux_init(void)
+{
+ static int initialized = 0;
+
+ if (!initialized) {
+ reg_pinmux_rw_pa pa = REG_RD(pinmux, regi_pinmux, rw_pa);
+ initialized = 1;
+ pa.pa0 = pa.pa1 = pa.pa2 = pa.pa3 =
+ pa.pa4 = pa.pa5 = pa.pa6 = pa.pa7 = regk_pinmux_yes;
+ REG_WR(pinmux, regi_pinmux, rw_pa, pa);
+ crisv32_pinmux_alloc(PORT_B, 0, PORT_PINS - 1, pinmux_gpio);
+ crisv32_pinmux_alloc(PORT_C, 0, PORT_PINS - 1, pinmux_gpio);
+ crisv32_pinmux_alloc(PORT_D, 0, PORT_PINS - 1, pinmux_gpio);
+ crisv32_pinmux_alloc(PORT_E, 0, PORT_PINS - 1, pinmux_gpio);
+ }
+
+ return 0;
+}
+
+int
+crisv32_pinmux_alloc(int port, int first_pin, int last_pin, enum pin_mode mode)
+{
+ int i;
+ unsigned long flags;
+
+ crisv32_pinmux_init();
+
+ if (port > PORTS)
+ return -EINVAL;
+
+ spin_lock_irqsave(&pinmux_lock, flags);
+
+ for (i = first_pin; i <= last_pin; i++)
+ {
+ if ((pins[port][i] != pinmux_none) && (pins[port][i] != pinmux_gpio) &&
+ (pins[port][i] != mode))
+ {
+ spin_unlock_irqrestore(&pinmux_lock, flags);
+#ifdef DEBUG
+ panic("Pinmux alloc failed!\n");
+#endif
+ return -EPERM;
+ }
+ }
+
+ for (i = first_pin; i <= last_pin; i++)
+ pins[port][i] = mode;
+
+ crisv32_pinmux_set(port);
+
+ spin_unlock_irqrestore(&pinmux_lock, flags);
+
+ return 0;
+}
+
+int
+crisv32_pinmux_alloc_fixed(enum fixed_function function)
+{
+ int ret = -EINVAL;
+ char saved[sizeof pins];
+ unsigned long flags;
+
+ spin_lock_irqsave(&pinmux_lock, flags);
+
+ /* Save internal data for recovery */
+ memcpy(saved, pins, sizeof pins);
+
+ reg_pinmux_rw_hwprot hwprot = REG_RD(pinmux, regi_pinmux, rw_hwprot);
+
+ switch(function)
+ {
+ case pinmux_ser1:
+ ret = crisv32_pinmux_alloc(PORT_C, 4, 7, pinmux_fixed);
+ hwprot.ser1 = regk_pinmux_yes;
+ break;
+ case pinmux_ser2:
+ ret = crisv32_pinmux_alloc(PORT_C, 8, 11, pinmux_fixed);
+ hwprot.ser2 = regk_pinmux_yes;
+ break;
+ case pinmux_ser3:
+ ret = crisv32_pinmux_alloc(PORT_C, 12, 15, pinmux_fixed);
+ hwprot.ser3 = regk_pinmux_yes;
+ break;
+ case pinmux_sser0:
+ ret = crisv32_pinmux_alloc(PORT_C, 0, 3, pinmux_fixed);
+ ret |= crisv32_pinmux_alloc(PORT_C, 16, 16, pinmux_fixed);
+ hwprot.sser0 = regk_pinmux_yes;
+ break;
+ case pinmux_sser1:
+ ret = crisv32_pinmux_alloc(PORT_D, 0, 4, pinmux_fixed);
+ hwprot.sser1 = regk_pinmux_yes;
+ break;
+ case pinmux_ata0:
+ ret = crisv32_pinmux_alloc(PORT_D, 5, 7, pinmux_fixed);
+ ret |= crisv32_pinmux_alloc(PORT_D, 15, 17, pinmux_fixed);
+ hwprot.ata0 = regk_pinmux_yes;
+ break;
+ case pinmux_ata1:
+ ret = crisv32_pinmux_alloc(PORT_D, 0, 4, pinmux_fixed);
+ ret |= crisv32_pinmux_alloc(PORT_E, 17, 17, pinmux_fixed);
+ hwprot.ata1 = regk_pinmux_yes;
+ break;
+ case pinmux_ata2:
+ ret = crisv32_pinmux_alloc(PORT_C, 11, 15, pinmux_fixed);
+ ret |= crisv32_pinmux_alloc(PORT_E, 3, 3, pinmux_fixed);
+ hwprot.ata2 = regk_pinmux_yes;
+ break;
+ case pinmux_ata3:
+ ret = crisv32_pinmux_alloc(PORT_C, 8, 10, pinmux_fixed);
+ ret |= crisv32_pinmux_alloc(PORT_C, 0, 2, pinmux_fixed);
+ hwprot.ata2 = regk_pinmux_yes;
+ break;
+ case pinmux_ata:
+ ret = crisv32_pinmux_alloc(PORT_B, 0, 15, pinmux_fixed);
+ ret |= crisv32_pinmux_alloc(PORT_D, 8, 15, pinmux_fixed);
+ hwprot.ata = regk_pinmux_yes;
+ break;
+ case pinmux_eth1:
+ ret = crisv32_pinmux_alloc(PORT_E, 0, 17, pinmux_fixed);
+ hwprot.eth1 = regk_pinmux_yes;
+ hwprot.eth1_mgm = regk_pinmux_yes;
+ break;
+ case pinmux_timer:
+ ret = crisv32_pinmux_alloc(PORT_C, 16, 16, pinmux_fixed);
+ hwprot.timer = regk_pinmux_yes;
+ spin_unlock_irqrestore(&pinmux_lock, flags);
+ return ret;
+ }
+
+ if (!ret)
+ REG_WR(pinmux, regi_pinmux, rw_hwprot, hwprot);
+ else
+ memcpy(pins, saved, sizeof pins);
+
+ spin_unlock_irqrestore(&pinmux_lock, flags);
+
+ return ret;
+}
+
+void
+crisv32_pinmux_set(int port)
+{
+ int i;
+ int gpio_val = 0;
+ int iop_val = 0;
+
+ for (i = 0; i < PORT_PINS; i++)
+ {
+ if (pins[port][i] == pinmux_gpio)
+ gpio_val |= (1 << i);
+ else if (pins[port][i] == pinmux_iop)
+ iop_val |= (1 << i);
+ }
+
+ REG_WRITE(int, regi_pinmux + REG_RD_ADDR_pinmux_rw_pb_gio + 8*port, gpio_val);
+ REG_WRITE(int, regi_pinmux + REG_RD_ADDR_pinmux_rw_pb_iop + 8*port, iop_val);
+
+#ifdef DEBUG
+ crisv32_pinmux_dump();
+#endif
+}
+
+int
+crisv32_pinmux_dealloc(int port, int first_pin, int last_pin)
+{
+ int i;
+ unsigned long flags;
+
+ crisv32_pinmux_init();
+
+ if (port > PORTS)
+ return -EINVAL;
+
+ spin_lock_irqsave(&pinmux_lock, flags);
+
+ for (i = first_pin; i <= last_pin; i++)
+ pins[port][i] = pinmux_none;
+
+ crisv32_pinmux_set(port);
+ spin_unlock_irqrestore(&pinmux_lock, flags);
+
+ return 0;
+}
+
+void
+crisv32_pinmux_dump(void)
+{
+ int i, j;
+
+ crisv32_pinmux_init();
+
+ for (i = 0; i < PORTS; i++)
+ {
+ printk("Port %c\n", 'B'+i);
+ for (j = 0; j < PORT_PINS; j++)
+ printk(" Pin %d = %d\n", j, pins[i][j]);
+ }
+}
+
+__initcall(crisv32_pinmux_init);
diff --git a/arch/cris/arch-v32/kernel/process.c b/arch/cris/arch-v32/kernel/process.c
new file mode 100644
index 000000000000..882be42114f7
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/process.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2000-2003 Axis Communications AB
+ *
+ * Authors: Bjorn Wesen (bjornw@axis.com)
+ * Mikael Starvik (starvik@axis.com)
+ * Tobias Anderberg (tobiasa@axis.com), CRISv32 port.
+ *
+ * This file handles the architecture-dependent parts of process handling..
+ */
+
+#include <linux/config.h>
+#include <linux/sched.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/timer_defs.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+
+extern void stop_watchdog(void);
+
+#ifdef CONFIG_ETRAX_GPIO
+extern void etrax_gpio_wake_up_check(void); /* Defined in drivers/gpio.c. */
+#endif
+
+extern int cris_hlt_counter;
+
+/* We use this if we don't have any better idle routine. */
+void default_idle(void)
+{
+ local_irq_disable();
+ if (!need_resched() && !cris_hlt_counter) {
+ /* Halt until exception. */
+ __asm__ volatile("ei \n\t"
+ "halt ");
+ }
+ local_irq_enable();
+}
+
+/*
+ * Free current thread data structures etc..
+ */
+
+extern void deconfigure_bp(long pid);
+void exit_thread(void)
+{
+ deconfigure_bp(current->pid);
+}
+
+/*
+ * If the watchdog is enabled, disable interrupts and enter an infinite loop.
+ * The watchdog will reset the CPU after 0.1s. If the watchdog isn't enabled
+ * then enable it and wait.
+ */
+extern void arch_enable_nmi(void);
+
+void
+hard_reset_now(void)
+{
+ /*
+ * Don't declare this variable elsewhere. We don't want any other
+ * code to know about it than the watchdog handler in entry.S and
+ * this code, implementing hard reset through the watchdog.
+ */
+#if defined(CONFIG_ETRAX_WATCHDOG)
+ extern int cause_of_death;
+#endif
+
+ printk("*** HARD RESET ***\n");
+ local_irq_disable();
+
+#if defined(CONFIG_ETRAX_WATCHDOG)
+ cause_of_death = 0xbedead;
+#else
+{
+ reg_timer_rw_wd_ctrl wd_ctrl = {0};
+
+ stop_watchdog();
+
+ wd_ctrl.key = 16; /* Arbitrary key. */
+ wd_ctrl.cnt = 1; /* Minimum time. */
+ wd_ctrl.cmd = regk_timer_start;
+
+ arch_enable_nmi();
+ REG_WR(timer, regi_timer, rw_wd_ctrl, wd_ctrl);
+}
+#endif
+
+ while (1)
+ ; /* Wait for reset. */
+}
+
+/*
+ * Return saved PC of a blocked thread.
+ */
+unsigned long thread_saved_pc(struct task_struct *t)
+{
+ return (unsigned long)user_regs(t->thread_info)->erp;
+}
+
+static void
+kernel_thread_helper(void* dummy, int (*fn)(void *), void * arg)
+{
+ fn(arg);
+ do_exit(-1); /* Should never be called, return bad exit value. */
+}
+
+/* Create a kernel thread. */
+int
+kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
+{
+ struct pt_regs regs;
+
+ memset(&regs, 0, sizeof(regs));
+
+ /* Don't use r10 since that is set to 0 in copy_thread. */
+ regs.r11 = (unsigned long) fn;
+ regs.r12 = (unsigned long) arg;
+ regs.erp = (unsigned long) kernel_thread_helper;
+ regs.ccs = 1 << (I_CCS_BITNR + CCS_SHIFT);
+
+ /* Create the new process. */
+ return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
+}
+
+/*
+ * Setup the child's kernel stack with a pt_regs and call switch_stack() on it.
+ * It will be unnested during _resume and _ret_from_sys_call when the new thread
+ * is scheduled.
+ *
+ * Also setup the thread switching structure which is used to keep
+ * thread-specific data during _resumes.
+ */
+
+extern asmlinkage void ret_from_fork(void);
+
+int
+copy_thread(int nr, unsigned long clone_flags, unsigned long usp,
+ unsigned long unused,
+ struct task_struct *p, struct pt_regs *regs)
+{
+ struct pt_regs *childregs;
+ struct switch_stack *swstack;
+
+ /*
+ * Put the pt_regs structure at the end of the new kernel stack page and
+ * fix it up. Note: the task_struct doubles as the kernel stack for the
+ * task.
+ */
+ childregs = user_regs(p->thread_info);
+ *childregs = *regs; /* Struct copy of pt_regs. */
+ p->set_child_tid = p->clear_child_tid = NULL;
+ childregs->r10 = 0; /* Child returns 0 after a fork/clone. */
+
+ /* Set a new TLS ?
+ * The TLS is in $mof beacuse it is the 5th argument to sys_clone.
+ */
+ if (p->mm && (clone_flags & CLONE_SETTLS)) {
+ p->thread_info->tls = regs->mof;
+ }
+
+ /* Put the switch stack right below the pt_regs. */
+ swstack = ((struct switch_stack *) childregs) - 1;
+
+ /* Paramater to ret_from_sys_call. 0 is don't restart the syscall. */
+ swstack->r9 = 0;
+
+ /*
+ * We want to return into ret_from_sys_call after the _resume.
+ * ret_from_fork will call ret_from_sys_call.
+ */
+ swstack->return_ip = (unsigned long) ret_from_fork;
+
+ /* Fix the user-mode and kernel-mode stackpointer. */
+ p->thread.usp = usp;
+ p->thread.ksp = (unsigned long) swstack;
+
+ return 0;
+}
+
+/*
+ * Be aware of the "magic" 7th argument in the four system-calls below.
+ * They need the latest stackframe, which is put as the 7th argument by
+ * entry.S. The previous arguments are dummies or actually used, but need
+ * to be defined to reach the 7th argument.
+ *
+ * N.B.: Another method to get the stackframe is to use current_regs(). But
+ * it returns the latest stack-frame stacked when going from _user mode_ and
+ * some of these (at least sys_clone) are called from kernel-mode sometimes
+ * (for example during kernel_thread, above) and thus cannot use it. Thus,
+ * to be sure not to get any surprises, we use the method for the other calls
+ * as well.
+ */
+asmlinkage int
+sys_fork(long r10, long r11, long r12, long r13, long mof, long srp,
+ struct pt_regs *regs)
+{
+ return do_fork(SIGCHLD, rdusp(), regs, 0, NULL, NULL);
+}
+
+/* FIXME: Is parent_tid/child_tid really third/fourth argument? Update lib? */
+asmlinkage int
+sys_clone(unsigned long newusp, unsigned long flags, int *parent_tid, int *child_tid,
+ unsigned long tls, long srp, struct pt_regs *regs)
+{
+ if (!newusp)
+ newusp = rdusp();
+
+ return do_fork(flags, newusp, regs, 0, parent_tid, child_tid);
+}
+
+/*
+ * vfork is a system call in i386 because of register-pressure - maybe
+ * we can remove it and handle it in libc but we put it here until then.
+ */
+asmlinkage int
+sys_vfork(long r10, long r11, long r12, long r13, long mof, long srp,
+ struct pt_regs *regs)
+{
+ return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, rdusp(), regs, 0, NULL, NULL);
+}
+
+/* sys_execve() executes a new program. */
+asmlinkage int
+sys_execve(const char *fname, char **argv, char **envp, long r13, long mof, long srp,
+ struct pt_regs *regs)
+{
+ int error;
+ char *filename;
+
+ filename = getname(fname);
+ error = PTR_ERR(filename);
+
+ if (IS_ERR(filename))
+ goto out;
+
+ error = do_execve(filename, argv, envp, regs);
+ putname(filename);
+ out:
+ return error;
+}
+
+unsigned long
+get_wchan(struct task_struct *p)
+{
+ /* TODO */
+ return 0;
+}
+#undef last_sched
+#undef first_sched
+
+void show_regs(struct pt_regs * regs)
+{
+ unsigned long usp = rdusp();
+ printk("ERP: %08lx SRP: %08lx CCS: %08lx USP: %08lx MOF: %08lx\n",
+ regs->erp, regs->srp, regs->ccs, usp, regs->mof);
+
+ printk(" r0: %08lx r1: %08lx r2: %08lx r3: %08lx\n",
+ regs->r0, regs->r1, regs->r2, regs->r3);
+
+ printk(" r4: %08lx r5: %08lx r6: %08lx r7: %08lx\n",
+ regs->r4, regs->r5, regs->r6, regs->r7);
+
+ printk(" r8: %08lx r9: %08lx r10: %08lx r11: %08lx\n",
+ regs->r8, regs->r9, regs->r10, regs->r11);
+
+ printk("r12: %08lx r13: %08lx oR10: %08lx\n",
+ regs->r12, regs->r13, regs->orig_r10);
+}
diff --git a/arch/cris/arch-v32/kernel/ptrace.c b/arch/cris/arch-v32/kernel/ptrace.c
new file mode 100644
index 000000000000..208489da2a87
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/ptrace.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2000-2003, Axis Communications AB.
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/smp.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/ptrace.h>
+#include <linux/user.h>
+#include <linux/signal.h>
+#include <linux/security.h>
+
+#include <asm/uaccess.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/system.h>
+#include <asm/processor.h>
+#include <asm/arch/hwregs/supp_reg.h>
+
+/*
+ * Determines which bits in CCS the user has access to.
+ * 1 = access, 0 = no access.
+ */
+#define CCS_MASK 0x00087c00 /* SXNZVC */
+
+#define SBIT_USER (1 << (S_CCS_BITNR + CCS_SHIFT))
+
+static int put_debugreg(long pid, unsigned int regno, long data);
+static long get_debugreg(long pid, unsigned int regno);
+static unsigned long get_pseudo_pc(struct task_struct *child);
+void deconfigure_bp(long pid);
+
+extern unsigned long cris_signal_return_page;
+
+/*
+ * Get contents of register REGNO in task TASK.
+ */
+long get_reg(struct task_struct *task, unsigned int regno)
+{
+ /* USP is a special case, it's not in the pt_regs struct but
+ * in the tasks thread struct
+ */
+ unsigned long ret;
+
+ if (regno <= PT_EDA)
+ ret = ((unsigned long *)user_regs(task->thread_info))[regno];
+ else if (regno == PT_USP)
+ ret = task->thread.usp;
+ else if (regno == PT_PPC)
+ ret = get_pseudo_pc(task);
+ else if (regno <= PT_MAX)
+ ret = get_debugreg(task->pid, regno);
+ else
+ ret = 0;
+
+ return ret;
+}
+
+/*
+ * Write contents of register REGNO in task TASK.
+ */
+int put_reg(struct task_struct *task, unsigned int regno, unsigned long data)
+{
+ if (regno <= PT_EDA)
+ ((unsigned long *)user_regs(task->thread_info))[regno] = data;
+ else if (regno == PT_USP)
+ task->thread.usp = data;
+ else if (regno == PT_PPC) {
+ /* Write pseudo-PC to ERP only if changed. */
+ if (data != get_pseudo_pc(task))
+ ((unsigned long *)user_regs(task->thread_info))[PT_ERP] = data;
+ } else if (regno <= PT_MAX)
+ return put_debugreg(task->pid, regno, data);
+ else
+ return -1;
+ return 0;
+}
+
+/*
+ * Called by kernel/ptrace.c when detaching.
+ *
+ * Make sure the single step bit is not set.
+ */
+void
+ptrace_disable(struct task_struct *child)
+{
+ unsigned long tmp;
+
+ /* Deconfigure SPC and S-bit. */
+ tmp = get_reg(child, PT_CCS) & ~SBIT_USER;
+ put_reg(child, PT_CCS, tmp);
+ put_reg(child, PT_SPC, 0);
+
+ /* Deconfigure any watchpoints associated with the child. */
+ deconfigure_bp(child->pid);
+}
+
+
+asmlinkage int
+sys_ptrace(long request, long pid, long addr, long data)
+{
+ struct task_struct *child;
+ int ret;
+ unsigned long __user *datap = (unsigned long __user *)data;
+
+ lock_kernel();
+ ret = -EPERM;
+
+ if (request == PTRACE_TRACEME) {
+ /* are we already being traced? */
+ if (current->ptrace & PT_PTRACED)
+ goto out;
+ ret = security_ptrace(current->parent, current);
+ if (ret)
+ goto out;
+ /* set the ptrace bit in the process flags. */
+ current->ptrace |= PT_PTRACED;
+ ret = 0;
+ goto out;
+ }
+
+ ret = -ESRCH;
+ read_lock(&tasklist_lock);
+ child = find_task_by_pid(pid);
+
+ if (child)
+ get_task_struct(child);
+
+ read_unlock(&tasklist_lock);
+
+ if (!child)
+ goto out;
+
+ ret = -EPERM;
+
+ if (pid == 1) /* Leave the init process alone! */
+ goto out_tsk;
+
+ if (request == PTRACE_ATTACH) {
+ ret = ptrace_attach(child);
+ goto out_tsk;
+ }
+
+ ret = ptrace_check_attach(child, request == PTRACE_KILL);
+ if (ret < 0)
+ goto out_tsk;
+
+ switch (request) {
+ /* Read word at location address. */
+ case PTRACE_PEEKTEXT:
+ case PTRACE_PEEKDATA: {
+ unsigned long tmp;
+ int copied;
+
+ ret = -EIO;
+
+ /* The signal trampoline page is outside the normal user-addressable
+ * space but still accessible. This is hack to make it possible to
+ * access the signal handler code in GDB.
+ */
+ if ((addr & PAGE_MASK) == cris_signal_return_page) {
+ /* The trampoline page is globally mapped, no page table to traverse.*/
+ tmp = *(unsigned long*)addr;
+ } else {
+ copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
+
+ if (copied != sizeof(tmp))
+ break;
+ }
+
+ ret = put_user(tmp,datap);
+ break;
+ }
+
+ /* Read the word at location address in the USER area. */
+ case PTRACE_PEEKUSR: {
+ unsigned long tmp;
+
+ ret = -EIO;
+ if ((addr & 3) || addr < 0 || addr > PT_MAX << 2)
+ break;
+
+ tmp = get_reg(child, addr >> 2);
+ ret = put_user(tmp, datap);
+ break;
+ }
+
+ /* Write the word at location address. */
+ case PTRACE_POKETEXT:
+ case PTRACE_POKEDATA:
+ ret = 0;
+
+ if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data))
+ break;
+
+ ret = -EIO;
+ break;
+
+ /* Write the word at location address in the USER area. */
+ case PTRACE_POKEUSR:
+ ret = -EIO;
+ if ((addr & 3) || addr < 0 || addr > PT_MAX << 2)
+ break;
+
+ addr >>= 2;
+
+ if (addr == PT_CCS) {
+ /* don't allow the tracing process to change stuff like
+ * interrupt enable, kernel/user bit, dma enables etc.
+ */
+ data &= CCS_MASK;
+ data |= get_reg(child, PT_CCS) & ~CCS_MASK;
+ }
+ if (put_reg(child, addr, data))
+ break;
+ ret = 0;
+ break;
+
+ case PTRACE_SYSCALL:
+ case PTRACE_CONT:
+ ret = -EIO;
+
+ if (!valid_signal(data))
+ break;
+
+ /* Continue means no single-step. */
+ put_reg(child, PT_SPC, 0);
+
+ if (!get_debugreg(child->pid, PT_BP_CTRL)) {
+ unsigned long tmp;
+ /* If no h/w bp configured, disable S bit. */
+ tmp = get_reg(child, PT_CCS) & ~SBIT_USER;
+ put_reg(child, PT_CCS, tmp);
+ }
+
+ 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;
+
+ /* TODO: make sure any pending breakpoint is killed */
+ wake_up_process(child);
+ ret = 0;
+
+ break;
+
+ /* Make the child exit by sending it a sigkill. */
+ case PTRACE_KILL:
+ ret = 0;
+
+ if (child->exit_state == EXIT_ZOMBIE)
+ break;
+
+ child->exit_code = SIGKILL;
+
+ /* Deconfigure single-step and h/w bp. */
+ ptrace_disable(child);
+
+ /* TODO: make sure any pending breakpoint is killed */
+ wake_up_process(child);
+ break;
+
+ /* Set the trap flag. */
+ case PTRACE_SINGLESTEP: {
+ unsigned long tmp;
+ ret = -EIO;
+
+ /* Set up SPC if not set already (in which case we have
+ no other choice but to trust it). */
+ if (!get_reg(child, PT_SPC)) {
+ /* In case we're stopped in a delay slot. */
+ tmp = get_reg(child, PT_ERP) & ~1;
+ put_reg(child, PT_SPC, tmp);
+ }
+ tmp = get_reg(child, PT_CCS) | SBIT_USER;
+ put_reg(child, PT_CCS, tmp);
+
+ if (!valid_signal(data))
+ break;
+
+ clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
+
+ /* TODO: set some clever breakpoint mechanism... */
+
+ child->exit_code = data;
+ wake_up_process(child);
+ ret = 0;
+ break;
+
+ }
+ case PTRACE_DETACH:
+ ret = ptrace_detach(child, data);
+ break;
+
+ /* Get all GP registers from the child. */
+ case PTRACE_GETREGS: {
+ int i;
+ unsigned long tmp;
+
+ for (i = 0; i <= PT_MAX; i++) {
+ tmp = get_reg(child, i);
+
+ if (put_user(tmp, datap)) {
+ ret = -EFAULT;
+ goto out_tsk;
+ }
+
+ datap++;
+ }
+
+ ret = 0;
+ break;
+ }
+
+ /* Set all GP registers in the child. */
+ case PTRACE_SETREGS: {
+ int i;
+ unsigned long tmp;
+
+ for (i = 0; i <= PT_MAX; i++) {
+ if (get_user(tmp, datap)) {
+ ret = -EFAULT;
+ goto out_tsk;
+ }
+
+ if (i == PT_CCS) {
+ tmp &= CCS_MASK;
+ tmp |= get_reg(child, PT_CCS) & ~CCS_MASK;
+ }
+
+ put_reg(child, i, tmp);
+ datap++;
+ }
+
+ ret = 0;
+ break;
+ }
+
+ default:
+ ret = ptrace_request(child, request, addr, data);
+ break;
+ }
+out_tsk:
+ put_task_struct(child);
+out:
+ unlock_kernel();
+ return ret;
+}
+
+void do_syscall_trace(void)
+{
+ if (!test_thread_flag(TIF_SYSCALL_TRACE))
+ return;
+
+ if (!(current->ptrace & PT_PTRACED))
+ return;
+
+ /* the 0x80 provides a way for the tracing parent to distinguish
+ between a syscall stop and SIGTRAP delivery */
+ 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.
+ */
+ if (current->exit_code) {
+ send_sig(current->exit_code, current, 1);
+ current->exit_code = 0;
+ }
+}
+
+/* Returns the size of an instruction that has a delay slot. */
+
+static int insn_size(struct task_struct *child, unsigned long pc)
+{
+ unsigned long opcode;
+ int copied;
+ int opsize = 0;
+
+ /* Read the opcode at pc (do what PTRACE_PEEKTEXT would do). */
+ copied = access_process_vm(child, pc, &opcode, sizeof(opcode), 0);
+ if (copied != sizeof(opcode))
+ return 0;
+
+ switch ((opcode & 0x0f00) >> 8) {
+ case 0x0:
+ case 0x9:
+ case 0xb:
+ opsize = 2;
+ break;
+ case 0xe:
+ case 0xf:
+ opsize = 6;
+ break;
+ case 0xd:
+ /* Could be 4 or 6; check more bits. */
+ if ((opcode & 0xff) == 0xff)
+ opsize = 4;
+ else
+ opsize = 6;
+ break;
+ default:
+ panic("ERROR: Couldn't find size of opcode 0x%lx at 0x%lx\n",
+ opcode, pc);
+ }
+
+ return opsize;
+}
+
+static unsigned long get_pseudo_pc(struct task_struct *child)
+{
+ /* Default value for PC is ERP. */
+ unsigned long pc = get_reg(child, PT_ERP);
+
+ if (pc & 0x1) {
+ unsigned long spc = get_reg(child, PT_SPC);
+ /* Delay slot bit set. Report as stopped on proper
+ instruction. */
+ if (spc) {
+ /* Rely on SPC if set. FIXME: We might want to check
+ that EXS indicates we stopped due to a single-step
+ exception. */
+ pc = spc;
+ } else {
+ /* Calculate the PC from the size of the instruction
+ that the delay slot we're in belongs to. */
+ pc += insn_size(child, pc & ~1) - 1;
+ }
+ }
+ return pc;
+}
+
+static long bp_owner = 0;
+
+/* Reachable from exit_thread in signal.c, so not static. */
+void deconfigure_bp(long pid)
+{
+ int bp;
+
+ /* Only deconfigure if the pid is the owner. */
+ if (bp_owner != pid)
+ return;
+
+ for (bp = 0; bp < 6; bp++) {
+ unsigned long tmp;
+ /* Deconfigure start and end address (also gets rid of ownership). */
+ put_debugreg(pid, PT_BP + 3 + (bp * 2), 0);
+ put_debugreg(pid, PT_BP + 4 + (bp * 2), 0);
+
+ /* Deconfigure relevant bits in control register. */
+ tmp = get_debugreg(pid, PT_BP_CTRL) & ~(3 << (2 + (bp * 4)));
+ put_debugreg(pid, PT_BP_CTRL, tmp);
+ }
+ /* No owner now. */
+ bp_owner = 0;
+}
+
+static int put_debugreg(long pid, unsigned int regno, long data)
+{
+ int ret = 0;
+ register int old_srs;
+
+#ifdef CONFIG_ETRAX_KGDB
+ /* Ignore write, but pretend it was ok if value is 0
+ (we don't want POKEUSR/SETREGS failing unnessecarily). */
+ return (data == 0) ? ret : -1;
+#endif
+
+ /* Simple owner management. */
+ if (!bp_owner)
+ bp_owner = pid;
+ else if (bp_owner != pid) {
+ /* Ignore write, but pretend it was ok if value is 0
+ (we don't want POKEUSR/SETREGS failing unnessecarily). */
+ return (data == 0) ? ret : -1;
+ }
+
+ /* Remember old SRS. */
+ SPEC_REG_RD(SPEC_REG_SRS, old_srs);
+ /* Switch to BP bank. */
+ SUPP_BANK_SEL(BANK_BP);
+
+ switch (regno - PT_BP) {
+ case 0:
+ SUPP_REG_WR(0, data); break;
+ case 1:
+ case 2:
+ if (data)
+ ret = -1;
+ break;
+ case 3:
+ SUPP_REG_WR(3, data); break;
+ case 4:
+ SUPP_REG_WR(4, data); break;
+ case 5:
+ SUPP_REG_WR(5, data); break;
+ case 6:
+ SUPP_REG_WR(6, data); break;
+ case 7:
+ SUPP_REG_WR(7, data); break;
+ case 8:
+ SUPP_REG_WR(8, data); break;
+ case 9:
+ SUPP_REG_WR(9, data); break;
+ case 10:
+ SUPP_REG_WR(10, data); break;
+ case 11:
+ SUPP_REG_WR(11, data); break;
+ case 12:
+ SUPP_REG_WR(12, data); break;
+ case 13:
+ SUPP_REG_WR(13, data); break;
+ case 14:
+ SUPP_REG_WR(14, data); break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ /* Restore SRS. */
+ SPEC_REG_WR(SPEC_REG_SRS, old_srs);
+ /* Just for show. */
+ NOP();
+ NOP();
+ NOP();
+
+ return ret;
+}
+
+static long get_debugreg(long pid, unsigned int regno)
+{
+ register int old_srs;
+ register long data;
+
+ if (pid != bp_owner) {
+ return 0;
+ }
+
+ /* Remember old SRS. */
+ SPEC_REG_RD(SPEC_REG_SRS, old_srs);
+ /* Switch to BP bank. */
+ SUPP_BANK_SEL(BANK_BP);
+
+ switch (regno - PT_BP) {
+ case 0:
+ SUPP_REG_RD(0, data); break;
+ case 1:
+ case 2:
+ /* error return value? */
+ data = 0;
+ break;
+ case 3:
+ SUPP_REG_RD(3, data); break;
+ case 4:
+ SUPP_REG_RD(4, data); break;
+ case 5:
+ SUPP_REG_RD(5, data); break;
+ case 6:
+ SUPP_REG_RD(6, data); break;
+ case 7:
+ SUPP_REG_RD(7, data); break;
+ case 8:
+ SUPP_REG_RD(8, data); break;
+ case 9:
+ SUPP_REG_RD(9, data); break;
+ case 10:
+ SUPP_REG_RD(10, data); break;
+ case 11:
+ SUPP_REG_RD(11, data); break;
+ case 12:
+ SUPP_REG_RD(12, data); break;
+ case 13:
+ SUPP_REG_RD(13, data); break;
+ case 14:
+ SUPP_REG_RD(14, data); break;
+ default:
+ /* error return value? */
+ data = 0;
+ }
+
+ /* Restore SRS. */
+ SPEC_REG_WR(SPEC_REG_SRS, old_srs);
+ /* Just for show. */
+ NOP();
+ NOP();
+ NOP();
+
+ return data;
+}
diff --git a/arch/cris/arch-v32/kernel/setup.c b/arch/cris/arch-v32/kernel/setup.c
new file mode 100644
index 000000000000..b17a39a2e164
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/setup.c
@@ -0,0 +1,118 @@
+/*
+ * Display CPU info in /proc/cpuinfo.
+ *
+ * Copyright (C) 2003, Axis Communications AB.
+ */
+
+#include <linux/config.h>
+#include <linux/seq_file.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/param.h>
+
+#ifdef CONFIG_PROC_FS
+
+#define HAS_FPU 0x0001
+#define HAS_MMU 0x0002
+#define HAS_ETHERNET100 0x0004
+#define HAS_TOKENRING 0x0008
+#define HAS_SCSI 0x0010
+#define HAS_ATA 0x0020
+#define HAS_USB 0x0040
+#define HAS_IRQ_BUG 0x0080
+#define HAS_MMU_BUG 0x0100
+
+struct cpu_info {
+ char *cpu_model;
+ unsigned short rev;
+ unsigned short cache_size;
+ unsigned short flags;
+};
+
+/* Some of these model are here for historical reasons only. */
+static struct cpu_info cpinfo[] = {
+ {"ETRAX 1", 0, 0, 0},
+ {"ETRAX 2", 1, 0, 0},
+ {"ETRAX 3", 2, 0, 0},
+ {"ETRAX 4", 3, 0, 0},
+ {"Simulator", 7, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA},
+ {"ETRAX 100", 8, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_IRQ_BUG},
+ {"ETRAX 100", 9, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA},
+
+ {"ETRAX 100LX", 10, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_USB
+ | HAS_MMU | HAS_MMU_BUG},
+
+ {"ETRAX 100LX v2", 11, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_USB
+ | HAS_MMU},
+
+ {"ETRAX FS", 32, 32, HAS_ETHERNET100 | HAS_ATA | HAS_MMU},
+
+ {"Unknown", 0, 0, 0}
+};
+
+int
+show_cpuinfo(struct seq_file *m, void *v)
+{
+ int i;
+ int cpu = (int)v - 1;
+ int entries;
+ unsigned long revision;
+ struct cpu_info *info;
+
+ entries = sizeof cpinfo / sizeof(struct cpu_info);
+ info = &cpinfo[entries - 1];
+
+#ifdef CONFIG_SMP
+ if (!cpu_online(cpu))
+ return 0;
+#endif
+
+ revision = rdvr();
+
+ for (i = 0; i < entries; i++) {
+ if (cpinfo[i].rev == revision) {
+ info = &cpinfo[i];
+ break;
+ }
+ }
+
+ return seq_printf(m,
+ "processor\t: %d\n"
+ "cpu\t\t: CRIS\n"
+ "cpu revision\t: %lu\n"
+ "cpu model\t: %s\n"
+ "cache size\t: %d KB\n"
+ "fpu\t\t: %s\n"
+ "mmu\t\t: %s\n"
+ "mmu DMA bug\t: %s\n"
+ "ethernet\t: %s Mbps\n"
+ "token ring\t: %s\n"
+ "scsi\t\t: %s\n"
+ "ata\t\t: %s\n"
+ "usb\t\t: %s\n"
+ "bogomips\t: %lu.%02lu\n\n",
+
+ cpu,
+ revision,
+ info->cpu_model,
+ info->cache_size,
+ info->flags & HAS_FPU ? "yes" : "no",
+ info->flags & HAS_MMU ? "yes" : "no",
+ info->flags & HAS_MMU_BUG ? "yes" : "no",
+ info->flags & HAS_ETHERNET100 ? "10/100" : "10",
+ info->flags & HAS_TOKENRING ? "4/16 Mbps" : "no",
+ info->flags & HAS_SCSI ? "yes" : "no",
+ info->flags & HAS_ATA ? "yes" : "no",
+ info->flags & HAS_USB ? "yes" : "no",
+ (loops_per_jiffy * HZ + 500) / 500000,
+ ((loops_per_jiffy * HZ + 500) / 5000) % 100);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+void
+show_etrax_copyright(void)
+{
+ printk(KERN_INFO
+ "Linux/CRISv32 port on ETRAX FS (C) 2003, 2004 Axis Communications AB\n");
+}
diff --git a/arch/cris/arch-v32/kernel/signal.c b/arch/cris/arch-v32/kernel/signal.c
new file mode 100644
index 000000000000..fb4c79d5b76b
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/signal.c
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2003, Axis Communications AB.
+ */
+
+#include <linux/sched.h>
+#include <linux/mm.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/stddef.h>
+#include <linux/syscalls.h>
+#include <linux/vmalloc.h>
+
+#include <asm/io.h>
+#include <asm/processor.h>
+#include <asm/ucontext.h>
+#include <asm/uaccess.h>
+#include <asm/arch/ptrace.h>
+#include <asm/arch/hwregs/cpu_vect.h>
+
+extern unsigned long cris_signal_return_page;
+
+/* Flag to check if a signal is blockable. */
+#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP)))
+
+/*
+ * A syscall in CRIS is really a "break 13" instruction, which is 2
+ * bytes. The registers is manipulated so upon return the instruction
+ * will be executed again.
+ *
+ * This relies on that PC points to the instruction after the break call.
+ */
+#define RESTART_CRIS_SYS(regs) regs->r10 = regs->orig_r10; regs->erp -= 2;
+
+/* Signal frames. */
+struct signal_frame {
+ struct sigcontext sc;
+ unsigned long extramask[_NSIG_WORDS - 1];
+ unsigned char retcode[8]; /* Trampoline code. */
+};
+
+struct rt_signal_frame {
+ struct siginfo *pinfo;
+ void *puc;
+ struct siginfo info;
+ struct ucontext uc;
+ unsigned char retcode[8]; /* Trampoline code. */
+};
+
+int do_signal(int restart, sigset_t *oldset, struct pt_regs *regs);
+void keep_debug_flags(unsigned long oldccs, unsigned long oldspc,
+ struct pt_regs *regs);
+/*
+ * Swap in the new signal mask, and wait for a signal. Define some
+ * dummy arguments to be able to reach the regs argument.
+ */
+int
+sys_sigsuspend(old_sigset_t mask, long r11, long r12, long r13, long mof,
+ long srp, struct pt_regs *regs)
+{
+ sigset_t saveset;
+
+ mask &= _BLOCKABLE;
+
+ spin_lock_irq(&current->sighand->siglock);
+
+ saveset = current->blocked;
+
+ siginitset(&current->blocked, mask);
+
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+
+ regs->r10 = -EINTR;
+
+ while (1) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule();
+
+ if (do_signal(0, &saveset, regs)) {
+ /*
+ * This point is reached twice: once to call
+ * the signal handler, then again to return
+ * from the sigsuspend system call. When
+ * calling the signal handler, R10 hold the
+ * signal number as set by do_signal(). The
+ * sigsuspend call will always return with
+ * the restored value above; -EINTR.
+ */
+ return regs->r10;
+ }
+ }
+}
+
+/* Define some dummy arguments to be able to reach the regs argument. */
+int
+sys_rt_sigsuspend(sigset_t *unewset, size_t sigsetsize, long r12, long r13,
+ long mof, long srp, struct pt_regs *regs)
+{
+ sigset_t saveset;
+ sigset_t newset;
+
+ if (sigsetsize != sizeof(sigset_t))
+ return -EINVAL;
+
+ if (copy_from_user(&newset, unewset, sizeof(newset)))
+ return -EFAULT;
+
+ sigdelsetmask(&newset, ~_BLOCKABLE);
+ spin_lock_irq(&current->sighand->siglock);
+
+ saveset = current->blocked;
+ current->blocked = newset;
+
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+
+ regs->r10 = -EINTR;
+
+ while (1) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule();
+
+ if (do_signal(0, &saveset, regs)) {
+ /* See comment in function above. */
+ return regs->r10;
+ }
+ }
+}
+
+int
+sys_sigaction(int signal, const struct old_sigaction *act,
+ struct old_sigaction *oact)
+{
+ int retval;
+ struct k_sigaction newk;
+ struct k_sigaction oldk;
+
+ if (act) {
+ old_sigset_t mask;
+
+ if (!access_ok(VERIFY_READ, act, sizeof(*act)) ||
+ __get_user(newk.sa.sa_handler, &act->sa_handler) ||
+ __get_user(newk.sa.sa_restorer, &act->sa_restorer))
+ return -EFAULT;
+
+ __get_user(newk.sa.sa_flags, &act->sa_flags);
+ __get_user(mask, &act->sa_mask);
+ siginitset(&newk.sa.sa_mask, mask);
+ }
+
+ retval = do_sigaction(signal, act ? &newk : NULL, oact ? &oldk : NULL);
+
+ if (!retval && oact) {
+ if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) ||
+ __put_user(oldk.sa.sa_handler, &oact->sa_handler) ||
+ __put_user(oldk.sa.sa_restorer, &oact->sa_restorer))
+ return -EFAULT;
+
+ __put_user(oldk.sa.sa_flags, &oact->sa_flags);
+ __put_user(oldk.sa.sa_mask.sig[0], &oact->sa_mask);
+ }
+
+ return retval;
+}
+
+int
+sys_sigaltstack(const stack_t __user *uss, stack_t __user *uoss)
+{
+ return do_sigaltstack(uss, uoss, rdusp());
+}
+
+static int
+restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc)
+{
+ unsigned int err = 0;
+ unsigned long old_usp;
+
+ /* Always make any pending restarted system calls return -EINTR */
+ current_thread_info()->restart_block.fn = do_no_restart_syscall;
+
+ /*
+ * Restore the registers from &sc->regs. sc is already checked
+ * for VERIFY_READ since the signal_frame was previously
+ * checked in sys_sigreturn().
+ */
+ if (__copy_from_user(regs, sc, sizeof(struct pt_regs)))
+ goto badframe;
+
+ /* Make that the user-mode flag is set. */
+ regs->ccs |= (1 << (U_CCS_BITNR + CCS_SHIFT));
+
+ /* Restore the old USP. */
+ err |= __get_user(old_usp, &sc->usp);
+ wrusp(old_usp);
+
+ return err;
+
+badframe:
+ return 1;
+}
+
+/* Define some dummy arguments to be able to reach the regs argument. */
+asmlinkage int
+sys_sigreturn(long r10, long r11, long r12, long r13, long mof, long srp,
+ struct pt_regs *regs)
+{
+ sigset_t set;
+ struct signal_frame __user *frame;
+ unsigned long oldspc = regs->spc;
+ unsigned long oldccs = regs->ccs;
+
+ frame = (struct signal_frame *) rdusp();
+
+ /*
+ * Since the signal is stacked on a dword boundary, the frame
+ * should be dword aligned here as well. It it's not, then the
+ * user is trying some funny business.
+ */
+ if (((long)frame) & 3)
+ goto badframe;
+
+ if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
+ goto badframe;
+
+ if (__get_user(set.sig[0], &frame->sc.oldmask) ||
+ (_NSIG_WORDS > 1 && __copy_from_user(&set.sig[1],
+ frame->extramask,
+ sizeof(frame->extramask))))
+ goto badframe;
+
+ sigdelsetmask(&set, ~_BLOCKABLE);
+ spin_lock_irq(&current->sighand->siglock);
+
+ current->blocked = set;
+
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+
+ if (restore_sigcontext(regs, &frame->sc))
+ goto badframe;
+
+ keep_debug_flags(oldccs, oldspc, regs);
+
+ return regs->r10;
+
+badframe:
+ force_sig(SIGSEGV, current);
+ return 0;
+}
+
+/* Define some dummy variables to be able to reach the regs argument. */
+asmlinkage int
+sys_rt_sigreturn(long r10, long r11, long r12, long r13, long mof, long srp,
+ struct pt_regs *regs)
+{
+ sigset_t set;
+ struct rt_signal_frame __user *frame;
+ unsigned long oldspc = regs->spc;
+ unsigned long oldccs = regs->ccs;
+
+ frame = (struct rt_signal_frame *) rdusp();
+
+ /*
+ * Since the signal is stacked on a dword boundary, the frame
+ * should be dword aligned here as well. It it's not, then the
+ * user is trying some funny business.
+ */
+ if (((long)frame) & 3)
+ goto badframe;
+
+ if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
+ goto badframe;
+
+ if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set)))
+ goto badframe;
+
+ sigdelsetmask(&set, ~_BLOCKABLE);
+ spin_lock_irq(&current->sighand->siglock);
+
+ current->blocked = set;
+
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+
+ if (restore_sigcontext(regs, &frame->uc.uc_mcontext))
+ goto badframe;
+
+ if (do_sigaltstack(&frame->uc.uc_stack, NULL, rdusp()) == -EFAULT)
+ goto badframe;
+
+ keep_debug_flags(oldccs, oldspc, regs);
+
+ return regs->r10;
+
+badframe:
+ force_sig(SIGSEGV, current);
+ return 0;
+}
+
+/* Setup a signal frame. */
+static int
+setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs,
+ unsigned long mask)
+{
+ int err;
+ unsigned long usp;
+
+ err = 0;
+ usp = rdusp();
+
+ /*
+ * Copy the registers. They are located first in sc, so it's
+ * possible to use sc directly.
+ */
+ err |= __copy_to_user(sc, regs, sizeof(struct pt_regs));
+
+ err |= __put_user(mask, &sc->oldmask);
+ err |= __put_user(usp, &sc->usp);
+
+ return err;
+}
+
+/* Figure out where to put the new signal frame - usually on the stack. */
+static inline void __user *
+get_sigframe(struct k_sigaction *ka, struct pt_regs * regs, size_t frame_size)
+{
+ unsigned long sp;
+
+ sp = rdusp();
+
+ /* This is the X/Open sanctioned signal stack switching. */
+ if (ka->sa.sa_flags & SA_ONSTACK) {
+ if (!on_sig_stack(sp))
+ sp = current->sas_ss_sp + current->sas_ss_size;
+ }
+
+ /* Make sure the frame is dword-aligned. */
+ sp &= ~3;
+
+ return (void __user *)(sp - frame_size);
+}
+
+/* Grab and setup a signal frame.
+ *
+ * Basically a lot of state-info is stacked, and arranged for the
+ * user-mode program to return to the kernel using either a trampiline
+ * which performs the syscall sigreturn(), or a provided user-mode
+ * trampoline.
+ */
+static void
+setup_frame(int sig, struct k_sigaction *ka, sigset_t *set,
+ struct pt_regs * regs)
+{
+ int err;
+ unsigned long return_ip;
+ struct signal_frame __user *frame;
+
+ err = 0;
+ frame = get_sigframe(ka, regs, sizeof(*frame));
+
+ if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
+ goto give_sigsegv;
+
+ err |= setup_sigcontext(&frame->sc, regs, set->sig[0]);
+
+ if (err)
+ goto give_sigsegv;
+
+ if (_NSIG_WORDS > 1) {
+ err |= __copy_to_user(frame->extramask, &set->sig[1],
+ sizeof(frame->extramask));
+ }
+
+ if (err)
+ goto give_sigsegv;
+
+ /*
+ * Set up to return from user-space. If provided, use a stub
+ * already located in user-space.
+ */
+ if (ka->sa.sa_flags & SA_RESTORER) {
+ return_ip = (unsigned long)ka->sa.sa_restorer;
+ } else {
+ /* Trampoline - the desired return ip is in the signal return page. */
+ return_ip = cris_signal_return_page;
+
+ /*
+ * This is movu.w __NR_sigreturn, r9; break 13;
+ *
+ * WE DO NOT USE IT ANY MORE! It's only left here for historical
+ * reasons and because gdb uses it as a signature to notice
+ * signal handler stack frames.
+ */
+ err |= __put_user(0x9c5f, (short __user*)(frame->retcode+0));
+ err |= __put_user(__NR_sigreturn, (short __user*)(frame->retcode+2));
+ err |= __put_user(0xe93d, (short __user*)(frame->retcode+4));
+ }
+
+ if (err)
+ goto give_sigsegv;
+
+ /*
+ * Set up registers for signal handler.
+ *
+ * Where the code enters now.
+ * Where the code enter later.
+ * First argument, signo.
+ */
+ regs->erp = (unsigned long) ka->sa.sa_handler;
+ regs->srp = return_ip;
+ regs->r10 = sig;
+
+ /* Actually move the USP to reflect the stacked frame. */
+ wrusp((unsigned long)frame);
+
+ return;
+
+give_sigsegv:
+ if (sig == SIGSEGV)
+ ka->sa.sa_handler = SIG_DFL;
+
+ force_sig(SIGSEGV, current);
+}
+
+static void
+setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
+ sigset_t *set, struct pt_regs * regs)
+{
+ int err;
+ unsigned long return_ip;
+ struct rt_signal_frame __user *frame;
+
+ err = 0;
+ frame = get_sigframe(ka, regs, sizeof(*frame));
+
+ if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
+ goto give_sigsegv;
+
+ /* TODO: what is the current->exec_domain stuff and invmap ? */
+
+ err |= __put_user(&frame->info, &frame->pinfo);
+ err |= __put_user(&frame->uc, &frame->puc);
+ err |= copy_siginfo_to_user(&frame->info, info);
+
+ if (err)
+ goto give_sigsegv;
+
+ /* Clear all the bits of the ucontext we don't use. */
+ err |= __clear_user(&frame->uc, offsetof(struct ucontext, uc_mcontext));
+ err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, set->sig[0]);
+ err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
+
+ if (err)
+ goto give_sigsegv;
+
+ /*
+ * Set up to return from user-space. If provided, use a stub
+ * already located in user-space.
+ */
+ if (ka->sa.sa_flags & SA_RESTORER) {
+ return_ip = (unsigned long) ka->sa.sa_restorer;
+ } else {
+ /* Trampoline - the desired return ip is in the signal return page. */
+ return_ip = cris_signal_return_page + 6;
+
+ /*
+ * This is movu.w __NR_rt_sigreturn, r9; break 13;
+ *
+ * WE DO NOT USE IT ANY MORE! It's only left here for historical
+ * reasons and because gdb uses it as a signature to notice
+ * signal handler stack frames.
+ */
+ err |= __put_user(0x9c5f, (short __user*)(frame->retcode+0));
+
+ err |= __put_user(__NR_rt_sigreturn,
+ (short __user*)(frame->retcode+2));
+
+ err |= __put_user(0xe93d, (short __user*)(frame->retcode+4));
+ }
+
+ if (err)
+ goto give_sigsegv;
+
+ /*
+ * Set up registers for signal handler.
+ *
+ * Where the code enters now.
+ * Where the code enters later.
+ * First argument is signo.
+ * Second argument is (siginfo_t *).
+ * Third argument is unused.
+ */
+ regs->erp = (unsigned long) ka->sa.sa_handler;
+ regs->srp = return_ip;
+ regs->r10 = sig;
+ regs->r11 = (unsigned long) &frame->info;
+ regs->r12 = 0;
+
+ /* Actually move the usp to reflect the stacked frame. */
+ wrusp((unsigned long)frame);
+
+ return;
+
+give_sigsegv:
+ if (sig == SIGSEGV)
+ ka->sa.sa_handler = SIG_DFL;
+
+ force_sig(SIGSEGV, current);
+}
+
+/* Invoke a singal handler to, well, handle the signal. */
+extern inline void
+handle_signal(int canrestart, unsigned long sig,
+ siginfo_t *info, struct k_sigaction *ka,
+ sigset_t *oldset, struct pt_regs * regs)
+{
+ /* Check if this got called from a system call. */
+ if (canrestart) {
+ /* If so, check system call restarting. */
+ switch (regs->r10) {
+ case -ERESTART_RESTARTBLOCK:
+ case -ERESTARTNOHAND:
+ /*
+ * This means that the syscall should
+ * only be restarted if there was no
+ * handler for the signal, and since
+ * this point isn't reached unless
+ * there is a handler, there's no need
+ * to restart.
+ */
+ regs->r10 = -EINTR;
+ break;
+
+ case -ERESTARTSYS:
+ /*
+ * This means restart the syscall if
+ * there is no handler, or the handler
+ * was registered with SA_RESTART.
+ */
+ if (!(ka->sa.sa_flags & SA_RESTART)) {
+ regs->r10 = -EINTR;
+ break;
+ }
+
+ /* Fall through. */
+
+ case -ERESTARTNOINTR:
+ /*
+ * This means that the syscall should
+ * be called again after the signal
+ * handler returns.
+ */
+ RESTART_CRIS_SYS(regs);
+ break;
+ }
+ }
+
+ /* Set up the stack frame. */
+ if (ka->sa.sa_flags & SA_SIGINFO)
+ setup_rt_frame(sig, ka, info, oldset, regs);
+ else
+ setup_frame(sig, ka, oldset, regs);
+
+ if (ka->sa.sa_flags & SA_ONESHOT)
+ ka->sa.sa_handler = SIG_DFL;
+
+ if (!(ka->sa.sa_flags & SA_NODEFER)) {
+ spin_lock_irq(&current->sighand->siglock);
+ sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
+ sigaddset(&current->blocked,sig);
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+ }
+}
+
+/*
+ * 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.
+ *
+ * Also note that the regs structure given here as an argument, is the latest
+ * pushed pt_regs. It may or may not be the same as the first pushed registers
+ * when the initial usermode->kernelmode transition took place. Therefore
+ * we can use user_mode(regs) to see if we came directly from kernel or user
+ * mode below.
+ */
+int
+do_signal(int canrestart, sigset_t *oldset, struct pt_regs *regs)
+{
+ int signr;
+ siginfo_t info;
+ struct k_sigaction ka;
+
+ /*
+ * The common case should go fast, which is why this point is
+ * reached from kernel-mode. If that's the case, just return
+ * without doing anything.
+ */
+ if (!user_mode(regs))
+ return 1;
+
+ if (!oldset)
+ oldset = &current->blocked;
+
+ signr = get_signal_to_deliver(&info, &ka, regs, NULL);
+
+ if (signr > 0) {
+ /* Deliver the signal. */
+ handle_signal(canrestart, signr, &info, &ka, oldset, regs);
+ return 1;
+ }
+
+ /* Got here from a system call? */
+ if (canrestart) {
+ /* Restart the system call - no handlers present. */
+ if (regs->r10 == -ERESTARTNOHAND ||
+ regs->r10 == -ERESTARTSYS ||
+ regs->r10 == -ERESTARTNOINTR) {
+ RESTART_CRIS_SYS(regs);
+ }
+
+ if (regs->r10 == -ERESTART_RESTARTBLOCK){
+ regs->r10 = __NR_restart_syscall;
+ regs->erp -= 2;
+ }
+ }
+
+ return 0;
+}
+
+asmlinkage void
+ugdb_trap_user(struct thread_info *ti, int sig)
+{
+ if (((user_regs(ti)->exs & 0xff00) >> 8) != SINGLE_STEP_INTR_VECT) {
+ /* Zero single-step PC if the reason we stopped wasn't a single
+ step exception. This is to avoid relying on it when it isn't
+ reliable. */
+ user_regs(ti)->spc = 0;
+ }
+ /* FIXME: Filter out false h/w breakpoint hits (i.e. EDA
+ not withing any configured h/w breakpoint range). Synchronize with
+ what already exists for kernel debugging. */
+ if (((user_regs(ti)->exs & 0xff00) >> 8) == BREAK_8_INTR_VECT) {
+ /* Break 8: subtract 2 from ERP unless in a delay slot. */
+ if (!(user_regs(ti)->erp & 0x1))
+ user_regs(ti)->erp -= 2;
+ }
+ sys_kill(ti->task->pid, sig);
+}
+
+void
+keep_debug_flags(unsigned long oldccs, unsigned long oldspc,
+ struct pt_regs *regs)
+{
+ if (oldccs & (1 << Q_CCS_BITNR)) {
+ /* Pending single step due to single-stepping the break 13
+ in the signal trampoline: keep the Q flag. */
+ regs->ccs |= (1 << Q_CCS_BITNR);
+ /* S flag should be set - complain if it's not. */
+ if (!(oldccs & (1 << (S_CCS_BITNR + CCS_SHIFT)))) {
+ printk("Q flag but no S flag?");
+ }
+ regs->ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT));
+ /* Assume the SPC is valid and interesting. */
+ regs->spc = oldspc;
+
+ } else if (oldccs & (1 << (S_CCS_BITNR + CCS_SHIFT))) {
+ /* If a h/w bp was set in the signal handler we need
+ to keep the S flag. */
+ regs->ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT));
+ /* Don't keep the old SPC though; if we got here due to
+ a single-step, the Q flag should have been set. */
+ } else if (regs->spc) {
+ /* If we were single-stepping *before* the signal was taken,
+ we don't want to restore that state now, because GDB will
+ have forgotten all about it. */
+ regs->spc = 0;
+ regs->ccs &= ~(1 << (S_CCS_BITNR + CCS_SHIFT));
+ }
+}
+
+/* Set up the trampolines on the signal return page. */
+int __init
+cris_init_signal(void)
+{
+ u16* data = (u16*)kmalloc(PAGE_SIZE, GFP_KERNEL);
+
+ /* This is movu.w __NR_sigreturn, r9; break 13; */
+ data[0] = 0x9c5f;
+ data[1] = __NR_sigreturn;
+ data[2] = 0xe93d;
+ /* This is movu.w __NR_rt_sigreturn, r9; break 13; */
+ data[3] = 0x9c5f;
+ data[4] = __NR_rt_sigreturn;
+ data[5] = 0xe93d;
+
+ /* Map to userspace with appropriate permissions (no write access...) */
+ cris_signal_return_page = (unsigned long)
+ __ioremap_prot(virt_to_phys(data), PAGE_SIZE, PAGE_SIGNAL_TRAMPOLINE);
+
+ return 0;
+}
+
+__initcall(cris_init_signal);
diff --git a/arch/cris/arch-v32/kernel/smp.c b/arch/cris/arch-v32/kernel/smp.c
new file mode 100644
index 000000000000..2c5cae04a95c
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/smp.c
@@ -0,0 +1,348 @@
+#include <asm/delay.h>
+#include <asm/arch/irq.h>
+#include <asm/arch/hwregs/intr_vect.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+#include <asm/tlbflush.h>
+#include <asm/mmu_context.h>
+#include <asm/arch/hwregs/mmu_defs_asm.h>
+#include <asm/arch/hwregs/supp_reg.h>
+#include <asm/atomic.h>
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/timex.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/cpumask.h>
+#include <linux/interrupt.h>
+
+#define IPI_SCHEDULE 1
+#define IPI_CALL 2
+#define IPI_FLUSH_TLB 4
+
+#define FLUSH_ALL (void*)0xffffffff
+
+/* Vector of locks used for various atomic operations */
+spinlock_t cris_atomic_locks[] = { [0 ... LOCK_COUNT - 1] = SPIN_LOCK_UNLOCKED};
+
+/* CPU masks */
+cpumask_t cpu_online_map = CPU_MASK_NONE;
+cpumask_t phys_cpu_present_map = CPU_MASK_NONE;
+
+/* Variables used during SMP boot */
+volatile int cpu_now_booting = 0;
+volatile struct thread_info *smp_init_current_idle_thread;
+
+/* Variables used during IPI */
+static DEFINE_SPINLOCK(call_lock);
+static DEFINE_SPINLOCK(tlbstate_lock);
+
+struct call_data_struct {
+ void (*func) (void *info);
+ void *info;
+ int wait;
+};
+
+static struct call_data_struct * call_data;
+
+static struct mm_struct* flush_mm;
+static struct vm_area_struct* flush_vma;
+static unsigned long flush_addr;
+
+extern int setup_irq(int, struct irqaction *);
+
+/* Mode registers */
+static unsigned long irq_regs[NR_CPUS] =
+{
+ regi_irq,
+ regi_irq2
+};
+
+static irqreturn_t crisv32_ipi_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static int send_ipi(int vector, int wait, cpumask_t cpu_mask);
+static struct irqaction irq_ipi = { crisv32_ipi_interrupt, SA_INTERRUPT,
+ CPU_MASK_NONE, "ipi", NULL, NULL};
+
+extern void cris_mmu_init(void);
+extern void cris_timer_init(void);
+
+/* SMP initialization */
+void __init smp_prepare_cpus(unsigned int max_cpus)
+{
+ int i;
+
+ /* From now on we can expect IPIs so set them up */
+ setup_irq(IPI_INTR_VECT, &irq_ipi);
+
+ /* Mark all possible CPUs as present */
+ for (i = 0; i < max_cpus; i++)
+ cpu_set(i, phys_cpu_present_map);
+}
+
+void __devinit smp_prepare_boot_cpu(void)
+{
+ /* PGD pointer has moved after per_cpu initialization so
+ * update the MMU.
+ */
+ pgd_t **pgd;
+ pgd = (pgd_t**)&per_cpu(current_pgd, smp_processor_id());
+
+ SUPP_BANK_SEL(1);
+ SUPP_REG_WR(RW_MM_TLB_PGD, pgd);
+ SUPP_BANK_SEL(2);
+ SUPP_REG_WR(RW_MM_TLB_PGD, pgd);
+
+ cpu_set(0, cpu_online_map);
+ cpu_set(0, phys_cpu_present_map);
+}
+
+void __init smp_cpus_done(unsigned int max_cpus)
+{
+}
+
+/* Bring one cpu online.*/
+static int __init
+smp_boot_one_cpu(int cpuid)
+{
+ unsigned timeout;
+ struct task_struct *idle;
+
+ idle = fork_idle(cpuid);
+ if (IS_ERR(idle))
+ panic("SMP: fork failed for CPU:%d", cpuid);
+
+ idle->thread_info->cpu = cpuid;
+
+ /* Information to the CPU that is about to boot */
+ smp_init_current_idle_thread = idle->thread_info;
+ cpu_now_booting = cpuid;
+
+ /* Wait for CPU to come online */
+ for (timeout = 0; timeout < 10000; timeout++) {
+ if(cpu_online(cpuid)) {
+ cpu_now_booting = 0;
+ smp_init_current_idle_thread = NULL;
+ return 0; /* CPU online */
+ }
+ udelay(100);
+ barrier();
+ }
+
+ put_task_struct(idle);
+ idle = NULL;
+
+ printk(KERN_CRIT "SMP: CPU:%d is stuck.\n", cpuid);
+ return -1;
+}
+
+/* Secondary CPUs starts uing C here. Here we need to setup CPU
+ * specific stuff such as the local timer and the MMU. */
+void __init smp_callin(void)
+{
+ extern void cpu_idle(void);
+
+ int cpu = cpu_now_booting;
+ reg_intr_vect_rw_mask vect_mask = {0};
+
+ /* Initialise the idle task for this CPU */
+ atomic_inc(&init_mm.mm_count);
+ current->active_mm = &init_mm;
+
+ /* Set up MMU */
+ cris_mmu_init();
+ __flush_tlb_all();
+
+ /* Setup local timer. */
+ cris_timer_init();
+
+ /* Enable IRQ and idle */
+ REG_WR(intr_vect, irq_regs[cpu], rw_mask, vect_mask);
+ unmask_irq(IPI_INTR_VECT);
+ unmask_irq(TIMER_INTR_VECT);
+ local_irq_enable();
+
+ cpu_set(cpu, cpu_online_map);
+ cpu_idle();
+}
+
+/* Stop execution on this CPU.*/
+void stop_this_cpu(void* dummy)
+{
+ local_irq_disable();
+ asm volatile("halt");
+}
+
+/* Other calls */
+void smp_send_stop(void)
+{
+ smp_call_function(stop_this_cpu, NULL, 1, 0);
+}
+
+int setup_profiling_timer(unsigned int multiplier)
+{
+ return -EINVAL;
+}
+
+
+/* cache_decay_ticks is used by the scheduler to decide if a process
+ * is "hot" on one CPU. A higher value means a higher penalty to move
+ * a process to another CPU. Our cache is rather small so we report
+ * 1 tick.
+ */
+unsigned long cache_decay_ticks = 1;
+
+int __devinit __cpu_up(unsigned int cpu)
+{
+ smp_boot_one_cpu(cpu);
+ return cpu_online(cpu) ? 0 : -ENOSYS;
+}
+
+void smp_send_reschedule(int cpu)
+{
+ cpumask_t cpu_mask = CPU_MASK_NONE;
+ cpu_set(cpu, cpu_mask);
+ send_ipi(IPI_SCHEDULE, 0, cpu_mask);
+}
+
+/* TLB flushing
+ *
+ * Flush needs to be done on the local CPU and on any other CPU that
+ * may have the same mapping. The mm->cpu_vm_mask is used to keep track
+ * of which CPUs that a specific process has been executed on.
+ */
+void flush_tlb_common(struct mm_struct* mm, struct vm_area_struct* vma, unsigned long addr)
+{
+ unsigned long flags;
+ cpumask_t cpu_mask;
+
+ spin_lock_irqsave(&tlbstate_lock, flags);
+ cpu_mask = (mm == FLUSH_ALL ? CPU_MASK_ALL : mm->cpu_vm_mask);
+ cpu_clear(smp_processor_id(), cpu_mask);
+ flush_mm = mm;
+ flush_vma = vma;
+ flush_addr = addr;
+ send_ipi(IPI_FLUSH_TLB, 1, cpu_mask);
+ spin_unlock_irqrestore(&tlbstate_lock, flags);
+}
+
+void flush_tlb_all(void)
+{
+ __flush_tlb_all();
+ flush_tlb_common(FLUSH_ALL, FLUSH_ALL, 0);
+}
+
+void flush_tlb_mm(struct mm_struct *mm)
+{
+ __flush_tlb_mm(mm);
+ flush_tlb_common(mm, FLUSH_ALL, 0);
+ /* No more mappings in other CPUs */
+ cpus_clear(mm->cpu_vm_mask);
+ cpu_set(smp_processor_id(), mm->cpu_vm_mask);
+}
+
+void flush_tlb_page(struct vm_area_struct *vma,
+ unsigned long addr)
+{
+ __flush_tlb_page(vma, addr);
+ flush_tlb_common(vma->vm_mm, vma, addr);
+}
+
+/* Inter processor interrupts
+ *
+ * The IPIs are used for:
+ * * Force a schedule on a CPU
+ * * FLush TLB on other CPUs
+ * * Call a function on other CPUs
+ */
+
+int send_ipi(int vector, int wait, cpumask_t cpu_mask)
+{
+ int i = 0;
+ reg_intr_vect_rw_ipi ipi = REG_RD(intr_vect, irq_regs[i], rw_ipi);
+ int ret = 0;
+
+ /* Calculate CPUs to send to. */
+ cpus_and(cpu_mask, cpu_mask, cpu_online_map);
+
+ /* Send the IPI. */
+ for_each_cpu_mask(i, cpu_mask)
+ {
+ ipi.vector |= vector;
+ REG_WR(intr_vect, irq_regs[i], rw_ipi, ipi);
+ }
+
+ /* Wait for IPI to finish on other CPUS */
+ if (wait) {
+ for_each_cpu_mask(i, cpu_mask) {
+ int j;
+ for (j = 0 ; j < 1000; j++) {
+ ipi = REG_RD(intr_vect, irq_regs[i], rw_ipi);
+ if (!ipi.vector)
+ break;
+ udelay(100);
+ }
+
+ /* Timeout? */
+ if (ipi.vector) {
+ printk("SMP call timeout from %d to %d\n", smp_processor_id(), i);
+ ret = -ETIMEDOUT;
+ dump_stack();
+ }
+ }
+ }
+ return ret;
+}
+
+/*
+ * You must not call this function with disabled interrupts or from a
+ * hardware interrupt handler or from a bottom half handler.
+ */
+int smp_call_function(void (*func)(void *info), void *info,
+ int nonatomic, int wait)
+{
+ cpumask_t cpu_mask = CPU_MASK_ALL;
+ struct call_data_struct data;
+ int ret;
+
+ cpu_clear(smp_processor_id(), cpu_mask);
+
+ WARN_ON(irqs_disabled());
+
+ data.func = func;
+ data.info = info;
+ data.wait = wait;
+
+ spin_lock(&call_lock);
+ call_data = &data;
+ ret = send_ipi(IPI_CALL, wait, cpu_mask);
+ spin_unlock(&call_lock);
+
+ return ret;
+}
+
+irqreturn_t crisv32_ipi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ void (*func) (void *info) = call_data->func;
+ void *info = call_data->info;
+ reg_intr_vect_rw_ipi ipi;
+
+ ipi = REG_RD(intr_vect, irq_regs[smp_processor_id()], rw_ipi);
+
+ if (ipi.vector & IPI_CALL) {
+ func(info);
+ }
+ if (ipi.vector & IPI_FLUSH_TLB) {
+ if (flush_mm == FLUSH_ALL)
+ __flush_tlb_all();
+ else if (flush_vma == FLUSH_ALL)
+ __flush_tlb_mm(flush_mm);
+ else
+ __flush_tlb_page(flush_vma, flush_addr);
+ }
+
+ ipi.vector = 0;
+ REG_WR(intr_vect, irq_regs[smp_processor_id()], rw_ipi, ipi);
+
+ return IRQ_HANDLED;
+}
+
diff --git a/arch/cris/arch-v32/kernel/time.c b/arch/cris/arch-v32/kernel/time.c
new file mode 100644
index 000000000000..d48e397f5fa4
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/time.c
@@ -0,0 +1,341 @@
+/* $Id: time.c,v 1.19 2005/04/29 05:40:09 starvik Exp $
+ *
+ * linux/arch/cris/arch-v32/kernel/time.c
+ *
+ * Copyright (C) 2003 Axis Communications AB
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/timex.h>
+#include <linux/time.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/swap.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/threads.h>
+#include <asm/types.h>
+#include <asm/signal.h>
+#include <asm/io.h>
+#include <asm/delay.h>
+#include <asm/rtc.h>
+#include <asm/irq.h>
+
+#include <asm/arch/hwregs/reg_map.h>
+#include <asm/arch/hwregs/reg_rdwr.h>
+#include <asm/arch/hwregs/timer_defs.h>
+#include <asm/arch/hwregs/intr_vect_defs.h>
+
+/* Watchdog defines */
+#define ETRAX_WD_KEY_MASK 0x7F /* key is 7 bit */
+#define ETRAX_WD_HZ 763 /* watchdog counts at 763 Hz */
+#define ETRAX_WD_CNT ((2*ETRAX_WD_HZ)/HZ + 1) /* Number of 763 counts before watchdog bites */
+
+unsigned long timer_regs[NR_CPUS] =
+{
+ regi_timer,
+#ifdef CONFIG_SMP
+ regi_timer2
+#endif
+};
+
+extern void update_xtime_from_cmos(void);
+extern int set_rtc_mmss(unsigned long nowtime);
+extern int setup_irq(int, struct irqaction *);
+extern int have_rtc;
+
+unsigned long get_ns_in_jiffie(void)
+{
+ reg_timer_r_tmr0_data data;
+ unsigned long ns;
+
+ data = REG_RD(timer, regi_timer, r_tmr0_data);
+ ns = (TIMER0_DIV - data) * 10;
+ return ns;
+}
+
+unsigned long do_slow_gettimeoffset(void)
+{
+ unsigned long count;
+ unsigned long usec_count = 0;
+
+ static unsigned long count_p = TIMER0_DIV;/* for the first call after boot */
+ static unsigned long jiffies_p = 0;
+
+ /*
+ * cache volatile jiffies temporarily; we have IRQs turned off.
+ */
+ unsigned long jiffies_t;
+
+ /* The timer interrupt comes from Etrax timer 0. In order to get
+ * better precision, we check the current value. It might have
+ * underflowed already though.
+ */
+
+ count = REG_RD(timer, regi_timer, r_tmr0_data);
+ jiffies_t = jiffies;
+
+ /*
+ * avoiding timer inconsistencies (they are rare, but they happen)...
+ * there are one problem that must be avoided here:
+ * 1. the timer counter underflows
+ */
+ if( jiffies_t == jiffies_p ) {
+ if( count > count_p ) {
+ /* Timer wrapped, use new count and prescale
+ * increase the time corresponding to one jiffie
+ */
+ usec_count = 1000000/HZ;
+ }
+ } else
+ jiffies_p = jiffies_t;
+ count_p = count;
+ /* Convert timer value to usec */
+ /* 100 MHz timer, divide by 100 to get usec */
+ usec_count += (TIMER0_DIV - count) / 100;
+ return usec_count;
+}
+
+/* From timer MDS describing the hardware watchdog:
+ * 4.3.1 Watchdog Operation
+ * The watchdog timer is an 8-bit timer with a configurable start value.
+ * Once started the whatchdog counts downwards with a frequency of 763 Hz
+ * (100/131072 MHz). When the watchdog counts down to 1, it generates an
+ * NMI (Non Maskable Interrupt), and when it counts down to 0, it resets the
+ * chip.
+ */
+/* This gives us 1.3 ms to do something useful when the NMI comes */
+
+/* right now, starting the watchdog is the same as resetting it */
+#define start_watchdog reset_watchdog
+
+#if defined(CONFIG_ETRAX_WATCHDOG)
+static short int watchdog_key = 42; /* arbitrary 7 bit number */
+#endif
+
+/* number of pages to consider "out of memory". it is normal that the memory
+ * is used though, so put this really low.
+ */
+
+#define WATCHDOG_MIN_FREE_PAGES 8
+
+void
+reset_watchdog(void)
+{
+#if defined(CONFIG_ETRAX_WATCHDOG)
+ reg_timer_rw_wd_ctrl wd_ctrl = { 0 };
+
+ /* only keep watchdog happy as long as we have memory left! */
+ if(nr_free_pages() > WATCHDOG_MIN_FREE_PAGES) {
+ /* reset the watchdog with the inverse of the old key */
+ watchdog_key ^= ETRAX_WD_KEY_MASK; /* invert key, which is 7 bits */
+ wd_ctrl.cnt = ETRAX_WD_CNT;
+ wd_ctrl.cmd = regk_timer_start;
+ wd_ctrl.key = watchdog_key;
+ REG_WR(timer, regi_timer, rw_wd_ctrl, wd_ctrl);
+ }
+#endif
+}
+
+/* stop the watchdog - we still need the correct key */
+
+void
+stop_watchdog(void)
+{
+#if defined(CONFIG_ETRAX_WATCHDOG)
+ reg_timer_rw_wd_ctrl wd_ctrl = { 0 };
+ watchdog_key ^= ETRAX_WD_KEY_MASK; /* invert key, which is 7 bits */
+ wd_ctrl.cnt = ETRAX_WD_CNT;
+ wd_ctrl.cmd = regk_timer_stop;
+ wd_ctrl.key = watchdog_key;
+ REG_WR(timer, regi_timer, rw_wd_ctrl, wd_ctrl);
+#endif
+}
+
+extern void show_registers(struct pt_regs *regs);
+
+void
+handle_watchdog_bite(struct pt_regs* regs)
+{
+#if defined(CONFIG_ETRAX_WATCHDOG)
+ extern int cause_of_death;
+
+ raw_printk("Watchdog bite\n");
+
+ /* Check if forced restart or unexpected watchdog */
+ if (cause_of_death == 0xbedead) {
+ while(1);
+ }
+
+ /* Unexpected watchdog, stop the watchdog and dump registers*/
+ stop_watchdog();
+ raw_printk("Oops: bitten by watchdog\n");
+ show_registers(regs);
+#ifndef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY
+ reset_watchdog();
+#endif
+ while(1) /* nothing */;
+#endif
+}
+
+/* last time the cmos clock got updated */
+static long last_rtc_update = 0;
+
+/*
+ * timer_interrupt() needs to keep up the real-time clock,
+ * as well as call the "do_timer()" routine every clocktick
+ */
+
+//static unsigned short myjiff; /* used by our debug routine print_timestamp */
+
+extern void cris_do_profile(struct pt_regs *regs);
+
+static inline irqreturn_t
+timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ int cpu = smp_processor_id();
+ reg_timer_r_masked_intr masked_intr;
+ reg_timer_rw_ack_intr ack_intr = { 0 };
+
+ /* Check if the timer interrupt is for us (a tmr0 int) */
+ masked_intr = REG_RD(timer, timer_regs[cpu], r_masked_intr);
+ if (!masked_intr.tmr0)
+ return IRQ_NONE;
+
+ /* acknowledge the timer irq */
+ ack_intr.tmr0 = 1;
+ REG_WR(timer, timer_regs[cpu], rw_ack_intr, ack_intr);
+
+ /* reset watchdog otherwise it resets us! */
+ reset_watchdog();
+
+ /* Update statistics. */
+ update_process_times(user_mode(regs));
+
+ cris_do_profile(regs); /* Save profiling information */
+
+ /* The master CPU is responsible for the time keeping. */
+ if (cpu != 0)
+ return IRQ_HANDLED;
+
+ /* call the real timer interrupt handler */
+ do_timer(regs);
+
+ /*
+ * If we have an externally synchronized Linux clock, then update
+ * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
+ * called as close as possible to 500 ms before the new second starts.
+ *
+ * The division here is not time critical since it will run once in
+ * 11 minutes
+ */
+ if ((time_status & STA_UNSYNC) == 0 &&
+ xtime.tv_sec > last_rtc_update + 660 &&
+ (xtime.tv_nsec / 1000) >= 500000 - (tick_nsec / 1000) / 2 &&
+ (xtime.tv_nsec / 1000) <= 500000 + (tick_nsec / 1000) / 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 */
+ }
+ return IRQ_HANDLED;
+}
+
+/* timer is SA_SHIRQ so drivers can add stuff to the timer irq chain
+ * it needs to be SA_INTERRUPT to make the jiffies update work properly
+ */
+
+static struct irqaction irq_timer = { timer_interrupt, SA_SHIRQ | SA_INTERRUPT,
+ CPU_MASK_NONE, "timer", NULL, NULL};
+
+void __init
+cris_timer_init(void)
+{
+ int cpu = smp_processor_id();
+ reg_timer_rw_tmr0_ctrl tmr0_ctrl = { 0 };
+ reg_timer_rw_tmr0_div tmr0_div = TIMER0_DIV;
+ reg_timer_rw_intr_mask timer_intr_mask;
+
+ /* Setup the etrax timers
+ * Base frequency is 100MHz, divider 1000000 -> 100 HZ
+ * We use timer0, so timer1 is free.
+ * The trig timer is used by the fasttimer API if enabled.
+ */
+
+ tmr0_ctrl.op = regk_timer_ld;
+ tmr0_ctrl.freq = regk_timer_f100;
+ REG_WR(timer, timer_regs[cpu], rw_tmr0_div, tmr0_div);
+ REG_WR(timer, timer_regs[cpu], rw_tmr0_ctrl, tmr0_ctrl); /* Load */
+ tmr0_ctrl.op = regk_timer_run;
+ REG_WR(timer, timer_regs[cpu], rw_tmr0_ctrl, tmr0_ctrl); /* Start */
+
+ /* enable the timer irq */
+ timer_intr_mask = REG_RD(timer, timer_regs[cpu], rw_intr_mask);
+ timer_intr_mask.tmr0 = 1;
+ REG_WR(timer, timer_regs[cpu], rw_intr_mask, timer_intr_mask);
+}
+
+void __init
+time_init(void)
+{
+ reg_intr_vect_rw_mask intr_mask;
+
+ /* probe for the RTC and read it if it exists
+ * Before the RTC can be probed the loops_per_usec variable needs
+ * to be initialized to make usleep work. A better value for
+ * loops_per_usec is calculated by the kernel later once the
+ * clock has started.
+ */
+ loops_per_usec = 50;
+
+ if(RTC_INIT() < 0) {
+ /* no RTC, start at 1980 */
+ xtime.tv_sec = 0;
+ xtime.tv_nsec = 0;
+ have_rtc = 0;
+ } else {
+ /* get the current time */
+ have_rtc = 1;
+ update_xtime_from_cmos();
+ }
+
+ /*
+ * Initialize wall_to_monotonic such that adding it to xtime will yield zero, the
+ * tv_nsec field must be normalized (i.e., 0 <= nsec < NSEC_PER_SEC).
+ */
+ set_normalized_timespec(&wall_to_monotonic, -xtime.tv_sec, -xtime.tv_nsec);
+
+ /* Start CPU local timer */
+ cris_timer_init();
+
+ /* enable the timer irq in global config */
+ intr_mask = REG_RD(intr_vect, regi_irq, rw_mask);
+ intr_mask.timer = 1;
+ REG_WR(intr_vect, regi_irq, rw_mask, intr_mask);
+
+ /* now actually register the timer irq handler that calls timer_interrupt() */
+
+ setup_irq(TIMER_INTR_VECT, &irq_timer);
+
+ /* enable watchdog if we should use one */
+
+#if defined(CONFIG_ETRAX_WATCHDOG)
+ printk("Enabling watchdog...\n");
+ start_watchdog();
+
+ /* If we use the hardware watchdog, we want to trap it as an NMI
+ and dump registers before it resets us. For this to happen, we
+ must set the "m" NMI enable flag (which once set, is unset only
+ when an NMI is taken).
+
+ The same goes for the external NMI, but that doesn't have any
+ driver or infrastructure support yet. */
+ {
+ unsigned long flags;
+ local_save_flags(flags);
+ flags |= (1<<30); /* NMI M flag is at bit 30 */
+ local_irq_restore(flags);
+ }
+#endif
+}
diff --git a/arch/cris/arch-v32/kernel/traps.c b/arch/cris/arch-v32/kernel/traps.c
new file mode 100644
index 000000000000..6e3787045560
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/traps.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2003, Axis Communications AB.
+ */
+
+#include <linux/config.h>
+#include <linux/ptrace.h>
+#include <asm/uaccess.h>
+
+#include <asm/arch/hwregs/supp_reg.h>
+
+extern void reset_watchdog(void);
+extern void stop_watchdog(void);
+
+extern int raw_printk(const char *fmt, ...);
+
+void
+show_registers(struct pt_regs *regs)
+{
+ /*
+ * It's possible to use either the USP register or current->thread.usp.
+ * USP might not correspond to the current proccess for all cases this
+ * function is called, and current->thread.usp isn't up to date for the
+ * current proccess. Experience shows that using USP is the way to go.
+ */
+ unsigned long usp;
+ unsigned long d_mmu_cause;
+ unsigned long i_mmu_cause;
+
+ usp = rdusp();
+
+ raw_printk("CPU: %d\n", smp_processor_id());
+
+ raw_printk("ERP: %08lx SRP: %08lx CCS: %08lx USP: %08lx MOF: %08lx\n",
+ regs->erp, regs->srp, regs->ccs, usp, regs->mof);
+
+ raw_printk(" r0: %08lx r1: %08lx r2: %08lx r3: %08lx\n",
+ regs->r0, regs->r1, regs->r2, regs->r3);
+
+ raw_printk(" r4: %08lx r5: %08lx r6: %08lx r7: %08lx\n",
+ regs->r4, regs->r5, regs->r6, regs->r7);
+
+ raw_printk(" r8: %08lx r9: %08lx r10: %08lx r11: %08lx\n",
+ regs->r8, regs->r9, regs->r10, regs->r11);
+
+ raw_printk("r12: %08lx r13: %08lx oR10: %08lx acr: %08lx\n",
+ regs->r12, regs->r13, regs->orig_r10, regs->acr);
+
+ raw_printk("sp: %08lx\n", regs);
+
+ SUPP_BANK_SEL(BANK_IM);
+ SUPP_REG_RD(RW_MM_CAUSE, i_mmu_cause);
+
+ SUPP_BANK_SEL(BANK_DM);
+ SUPP_REG_RD(RW_MM_CAUSE, d_mmu_cause);
+
+ raw_printk(" Data MMU Cause: %08lx\n", d_mmu_cause);
+ raw_printk("Instruction MMU Cause: %08lx\n", i_mmu_cause);
+
+ raw_printk("Process %s (pid: %d, stackpage: %08lx)\n",
+ current->comm, current->pid, (unsigned long) current);
+
+ /* Show additional info if in kernel-mode. */
+ if (!user_mode(regs)) {
+ int i;
+ unsigned char c;
+
+ show_stack(NULL, (unsigned long *) usp);
+
+ /*
+ * If the previous stack-dump wasn't a kernel one, dump the
+ * kernel stack now.
+ */
+ if (usp != 0)
+ show_stack(NULL, NULL);
+
+ raw_printk("\nCode: ");
+
+ if (regs->erp < PAGE_OFFSET)
+ goto bad_value;
+
+ /*
+ * Quite often the value at regs->erp doesn't point to the
+ * interesting instruction, which often is the previous
+ * instruction. So dump at an offset large enough that the
+ * instruction decoding should be in sync at the interesting
+ * point, but small enough to fit on a row. The regs->erp
+ * location is pointed out in a ksymoops-friendly way by
+ * wrapping the byte for that address in parenthesis.
+ */
+ for (i = -12; i < 12; i++) {
+ if (__get_user(c, &((unsigned char *) regs->erp)[i])) {
+bad_value:
+ raw_printk(" Bad IP value.");
+ break;
+ }
+
+ if (i == 0)
+ raw_printk("(%02x) ", c);
+ else
+ raw_printk("%02x ", c);
+ }
+
+ raw_printk("\n");
+ }
+}
+
+/*
+ * This gets called from entry.S when the watchdog has bitten. Show something
+ * similiar to an Oops dump, and if the kernel if configured to be a nice doggy;
+ * halt instead of reboot.
+ */
+void
+watchdog_bite_hook(struct pt_regs *regs)
+{
+#ifdef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY
+ local_irq_disable();
+ stop_watchdog();
+ show_registers(regs);
+
+ while (1)
+ ; /* Do nothing. */
+#else
+ show_registers(regs);
+#endif
+}
+
+/* This is normally the Oops function. */
+void
+die_if_kernel(const char *str, struct pt_regs *regs, long err)
+{
+ if (user_mode(regs))
+ return;
+
+#ifdef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY
+ /*
+ * This printout might take too long and could trigger
+ * the watchdog normally. If NICE_DOGGY is set, simply
+ * stop the watchdog during the printout.
+ */
+ stop_watchdog();
+#endif
+
+ raw_printk("%s: %04lx\n", str, err & 0xffff);
+
+ show_registers(regs);
+
+#ifdef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY
+ reset_watchdog();
+#endif
+
+ do_exit(SIGSEGV);
+}
+
+void arch_enable_nmi(void)
+{
+ unsigned long flags;
+ local_save_flags(flags);
+ flags |= (1<<30); /* NMI M flag is at bit 30 */
+ local_irq_restore(flags);
+}
diff --git a/arch/cris/arch-v32/kernel/vcs_hook.c b/arch/cris/arch-v32/kernel/vcs_hook.c
new file mode 100644
index 000000000000..64d71c54c22c
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/vcs_hook.c
@@ -0,0 +1,96 @@
+// $Id: vcs_hook.c,v 1.2 2003/08/12 12:01:06 starvik Exp $
+//
+// Call simulator hook. This is the part running in the
+// simulated program.
+//
+
+#include "vcs_hook.h"
+#include <stdarg.h>
+#include <asm/arch-v32/hwregs/reg_map.h>
+#include <asm/arch-v32/hwregs/intr_vect_defs.h>
+
+#define HOOK_TRIG_ADDR 0xb7000000 /* hook cvlog model reg address */
+#define HOOK_MEM_BASE_ADDR 0xa0000000 /* csp4 (shared mem) base addr */
+
+#define HOOK_DATA(offset) ((unsigned*) HOOK_MEM_BASE_ADDR)[offset]
+#define VHOOK_DATA(offset) ((volatile unsigned*) HOOK_MEM_BASE_ADDR)[offset]
+#define HOOK_TRIG(funcid) do { *((unsigned *) HOOK_TRIG_ADDR) = funcid; } while(0)
+#define HOOK_DATA_BYTE(offset) ((unsigned char*) HOOK_MEM_BASE_ADDR)[offset]
+
+
+// ------------------------------------------------------------------ hook_call
+int hook_call( unsigned id, unsigned pcnt, ...) {
+ va_list ap;
+ unsigned i;
+ unsigned ret;
+#ifdef USING_SOS
+ PREEMPT_OFF_SAVE();
+#endif
+
+ // pass parameters
+ HOOK_DATA(0) = id;
+
+ /* Have to make hook_print_str a special case since we call with a
+ parameter of byte type. Should perhaps be a separate
+ hook_call. */
+
+ if (id == hook_print_str) {
+ int i;
+ char *str;
+
+ HOOK_DATA(1) = pcnt;
+
+ va_start(ap, pcnt);
+ str = (char*)va_arg(ap,unsigned);
+
+ for (i=0; i!=pcnt; i++) {
+ HOOK_DATA_BYTE(8+i) = str[i];
+ }
+ HOOK_DATA_BYTE(8+i) = 0; /* null byte */
+ }
+ else {
+ va_start(ap, pcnt);
+ for( i = 1; i <= pcnt; i++ ) HOOK_DATA(i) = va_arg(ap,unsigned);
+ va_end(ap);
+ }
+
+ // read from mem to make sure data has propagated to memory before trigging
+ *((volatile unsigned*) HOOK_MEM_BASE_ADDR);
+
+ // trigger hook
+ HOOK_TRIG(id);
+
+ // wait for call to finish
+ while( VHOOK_DATA(0) > 0 ) {}
+
+ // extract return value
+
+ ret = VHOOK_DATA(1);
+
+#ifdef USING_SOS
+ PREEMPT_RESTORE();
+#endif
+ return ret;
+}
+
+unsigned
+hook_buf(unsigned i)
+{
+ return (HOOK_DATA(i));
+}
+
+void print_str( const char *str ) {
+ int i;
+ for (i=1; str[i]; i++); /* find null at end of string */
+ hook_call(hook_print_str, i, str);
+}
+
+// --------------------------------------------------------------- CPU_KICK_DOG
+void CPU_KICK_DOG(void) {
+ (void) hook_call( hook_kick_dog, 0 );
+}
+
+// ------------------------------------------------------- CPU_WATCHDOG_TIMEOUT
+void CPU_WATCHDOG_TIMEOUT( unsigned t ) {
+ (void) hook_call( hook_dog_timeout, 1, t );
+}
diff --git a/arch/cris/arch-v32/kernel/vcs_hook.h b/arch/cris/arch-v32/kernel/vcs_hook.h
new file mode 100644
index 000000000000..7d73709e3cc6
--- /dev/null
+++ b/arch/cris/arch-v32/kernel/vcs_hook.h
@@ -0,0 +1,42 @@
+// $Id: vcs_hook.h,v 1.1 2003/08/12 12:01:06 starvik Exp $
+//
+// Call simulator hook functions
+
+#ifndef HOOK_H
+#define HOOK_H
+
+int hook_call( unsigned id, unsigned pcnt, ...);
+
+enum hook_ids {
+ hook_debug_on = 1,
+ hook_debug_off,
+ hook_stop_sim_ok,
+ hook_stop_sim_fail,
+ hook_alloc_shared,
+ hook_ptr_shared,
+ hook_free_shared,
+ hook_file2shared,
+ hook_cmp_shared,
+ hook_print_params,
+ hook_sim_time,
+ hook_stop_sim,
+ hook_kick_dog,
+ hook_dog_timeout,
+ hook_rand,
+ hook_srand,
+ hook_rand_range,
+ hook_print_str,
+ hook_print_hex,
+ hook_cmp_offset_shared,
+ hook_fill_random_shared,
+ hook_alloc_random_data,
+ hook_calloc_random_data,
+ hook_print_int,
+ hook_print_uint,
+ hook_fputc,
+ hook_init_fd,
+ hook_sbrk
+
+};
+
+#endif
diff --git a/arch/cris/arch-v32/lib/Makefile b/arch/cris/arch-v32/lib/Makefile
new file mode 100644
index 000000000000..05b3ec6978d6
--- /dev/null
+++ b/arch/cris/arch-v32/lib/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for Etrax-specific library files..
+#
+
+lib-y = checksum.o checksumcopy.o string.o usercopy.o memset.o csumcpfruser.o spinlock.o
+
diff --git a/arch/cris/arch-v32/lib/checksum.S b/arch/cris/arch-v32/lib/checksum.S
new file mode 100644
index 000000000000..32e66181b826
--- /dev/null
+++ b/arch/cris/arch-v32/lib/checksum.S
@@ -0,0 +1,111 @@
+/*
+ * A fast checksum routine using movem
+ * Copyright (c) 1998-2001, 2003 Axis Communications AB
+ *
+ * csum_partial(const unsigned char * buff, int len, unsigned int sum)
+ */
+
+ .globl csum_partial
+csum_partial:
+
+ ;; r10 - src
+ ;; r11 - length
+ ;; r12 - checksum
+
+ ;; check for breakeven length between movem and normal word looping versions
+ ;; we also do _NOT_ want to compute a checksum over more than the
+ ;; actual length when length < 40
+
+ cmpu.w 80,$r11
+ blo _word_loop
+ nop
+
+ ;; need to save the registers we use below in the movem loop
+ ;; this overhead is why we have a check above for breakeven length
+ ;; only r0 - r8 have to be saved, the other ones are clobber-able
+ ;; according to the ABI
+
+ subq 9*4,$sp
+ subq 10*4,$r11 ; update length for the first loop
+ movem $r8,[$sp]
+
+ ;; do a movem checksum
+
+_mloop: movem [$r10+],$r9 ; read 10 longwords
+
+ ;; perform dword checksumming on the 10 longwords
+
+ add.d $r0,$r12
+ addc $r1,$r12
+ addc $r2,$r12
+ addc $r3,$r12
+ addc $r4,$r12
+ addc $r5,$r12
+ addc $r6,$r12
+ addc $r7,$r12
+ addc $r8,$r12
+ addc $r9,$r12
+
+ ;; fold the carry into the checksum, to avoid having to loop the carry
+ ;; back into the top
+
+ addc 0,$r12
+ addc 0,$r12 ; do it again, since we might have generated a carry
+
+ subq 10*4,$r11
+ bge _mloop
+ nop
+
+ addq 10*4,$r11 ; compensate for last loop underflowing length
+
+ movem [$sp+],$r8 ; restore regs
+
+_word_loop:
+ ;; only fold if there is anything to fold.
+
+ cmpq 0,$r12
+ beq _no_fold
+
+ ;; fold 32-bit checksum into a 16-bit checksum, to avoid carries below.
+ ;; r9 and r13 can be used as temporaries.
+
+ moveq -1,$r9 ; put 0xffff in r9, faster than move.d 0xffff,r9
+ lsrq 16,$r9
+
+ move.d $r12,$r13
+ lsrq 16,$r13 ; r13 = checksum >> 16
+ and.d $r9,$r12 ; checksum = checksum & 0xffff
+ add.d $r13,$r12 ; checksum += r13
+ move.d $r12,$r13 ; do the same again, maybe we got a carry last add
+ lsrq 16,$r13
+ and.d $r9,$r12
+ add.d $r13,$r12
+
+_no_fold:
+ cmpq 2,$r11
+ blt _no_words
+ nop
+
+ ;; checksum the rest of the words
+
+ subq 2,$r11
+
+_wloop: subq 2,$r11
+ bge _wloop
+ addu.w [$r10+],$r12
+
+ addq 2,$r11
+
+_no_words:
+ ;; see if we have one odd byte more
+ cmpq 1,$r11
+ beq _do_byte
+ nop
+ ret
+ move.d $r12,$r10
+
+_do_byte:
+ ;; copy and checksum the last byte
+ addu.b [$r10],$r12
+ ret
+ move.d $r12,$r10
diff --git a/arch/cris/arch-v32/lib/checksumcopy.S b/arch/cris/arch-v32/lib/checksumcopy.S
new file mode 100644
index 000000000000..9303ccbadc6d
--- /dev/null
+++ b/arch/cris/arch-v32/lib/checksumcopy.S
@@ -0,0 +1,120 @@
+/*
+ * A fast checksum+copy routine using movem
+ * Copyright (c) 1998, 2001, 2003 Axis Communications AB
+ *
+ * Authors: Bjorn Wesen
+ *
+ * csum_partial_copy_nocheck(const char *src, char *dst,
+ * int len, unsigned int sum)
+ */
+
+ .globl csum_partial_copy_nocheck
+csum_partial_copy_nocheck:
+
+ ;; r10 - src
+ ;; r11 - dst
+ ;; r12 - length
+ ;; r13 - checksum
+
+ ;; check for breakeven length between movem and normal word looping versions
+ ;; we also do _NOT_ want to compute a checksum over more than the
+ ;; actual length when length < 40
+
+ cmpu.w 80,$r12
+ blo _word_loop
+ nop
+
+ ;; need to save the registers we use below in the movem loop
+ ;; this overhead is why we have a check above for breakeven length
+ ;; only r0 - r8 have to be saved, the other ones are clobber-able
+ ;; according to the ABI
+
+ subq 9*4,$sp
+ subq 10*4,$r12 ; update length for the first loop
+ movem $r8,[$sp]
+
+ ;; do a movem copy and checksum
+
+1: ;; A failing userspace access (the read) will have this as PC.
+_mloop: movem [$r10+],$r9 ; read 10 longwords
+ movem $r9,[$r11+] ; write 10 longwords
+
+ ;; perform dword checksumming on the 10 longwords
+
+ add.d $r0,$r13
+ addc $r1,$r13
+ addc $r2,$r13
+ addc $r3,$r13
+ addc $r4,$r13
+ addc $r5,$r13
+ addc $r6,$r13
+ addc $r7,$r13
+ addc $r8,$r13
+ addc $r9,$r13
+
+ ;; fold the carry into the checksum, to avoid having to loop the carry
+ ;; back into the top
+
+ addc 0,$r13
+ addc 0,$r13 ; do it again, since we might have generated a carry
+
+ subq 10*4,$r12
+ bge _mloop
+ nop
+
+ addq 10*4,$r12 ; compensate for last loop underflowing length
+
+ movem [$sp+],$r8 ; restore regs
+
+_word_loop:
+ ;; only fold if there is anything to fold.
+
+ cmpq 0,$r13
+ beq _no_fold
+
+ ;; fold 32-bit checksum into a 16-bit checksum, to avoid carries below
+ ;; r9 can be used as temporary.
+
+ move.d $r13,$r9
+ lsrq 16,$r9 ; r0 = checksum >> 16
+ and.d 0xffff,$r13 ; checksum = checksum & 0xffff
+ add.d $r9,$r13 ; checksum += r0
+ move.d $r13,$r9 ; do the same again, maybe we got a carry last add
+ lsrq 16,$r9
+ and.d 0xffff,$r13
+ add.d $r9,$r13
+
+_no_fold:
+ cmpq 2,$r12
+ blt _no_words
+ nop
+
+ ;; copy and checksum the rest of the words
+
+ subq 2,$r12
+
+2: ;; A failing userspace access for the read below will have this as PC.
+_wloop: move.w [$r10+],$r9
+ addu.w $r9,$r13
+ subq 2,$r12
+ bge _wloop
+ move.w $r9,[$r11+]
+
+ addq 2,$r12
+
+_no_words:
+ ;; see if we have one odd byte more
+ cmpq 1,$r12
+ beq _do_byte
+ nop
+ ret
+ move.d $r13,$r10
+
+_do_byte:
+ ;; copy and checksum the last byte
+3: ;; A failing userspace access for the read below will have this as PC.
+ move.b [$r10],$r9
+ addu.b $r9,$r13
+ move.b $r9,[$r11]
+ ret
+ move.d $r13,$r10
diff --git a/arch/cris/arch-v32/lib/csumcpfruser.S b/arch/cris/arch-v32/lib/csumcpfruser.S
new file mode 100644
index 000000000000..600ec16b9f28
--- /dev/null
+++ b/arch/cris/arch-v32/lib/csumcpfruser.S
@@ -0,0 +1,69 @@
+/*
+ * Add-on to transform csum_partial_copy_nocheck in checksumcopy.S into
+ * csum_partial_copy_from_user by adding exception records.
+ *
+ * Copyright (C) 2001, 2003 Axis Communications AB.
+ *
+ * Author: Hans-Peter Nilsson.
+ */
+
+#include <asm/errno.h>
+
+/* Same function body, but a different name. If we just added exception
+ records to _csum_partial_copy_nocheck and made it generic, we wouldn't
+ know a user fault from a kernel fault and we would have overhead in
+ each kernel caller for the error-pointer argument.
+
+ unsigned int csum_partial_copy_from_user
+ (const char *src, char *dst, int len, unsigned int sum, int *errptr);
+
+ Note that the errptr argument is only set if we encounter an error.
+ It is conveniently located on the stack, so the normal function body
+ does not have to handle it. */
+
+#define csum_partial_copy_nocheck csum_partial_copy_from_user
+
+/* There are local labels numbered 1, 2 and 3 present to mark the
+ different from-user accesses. */
+#include "checksumcopy.S"
+
+ .section .fixup,"ax"
+
+;; Here from the movem loop; restore stack.
+4:
+ movem [$sp+],$r8
+;; r12 is already decremented. Add back chunk_size-2.
+ addq 40-2,$r12
+
+;; Here from the word loop; r12 is off by 2; add it back.
+5:
+ addq 2,$r12
+
+;; Here from a failing single byte.
+6:
+
+;; Signal in *errptr that we had a failing access.
+ move.d [$sp],$acr
+ moveq -EFAULT,$r9
+ subq 4,$sp
+ move.d $r9,[$acr]
+
+;; Clear the rest of the destination area using memset. Preserve the
+;; checksum for the readable bytes.
+ move.d $r13,[$sp]
+ subq 4,$sp
+ move.d $r11,$r10
+ move $srp,[$sp]
+ jsr memset
+ clear.d $r11
+
+ move [$sp+],$srp
+ ret
+ move.d [$sp+],$r10
+
+ .previous
+ .section __ex_table,"a"
+ .dword 1b,4b
+ .dword 2b,5b
+ .dword 3b,6b
+ .previous
diff --git a/arch/cris/arch-v32/lib/dram_init.S b/arch/cris/arch-v32/lib/dram_init.S
new file mode 100644
index 000000000000..47b6cf5f4afd
--- /dev/null
+++ b/arch/cris/arch-v32/lib/dram_init.S
@@ -0,0 +1,120 @@
+/* $Id: dram_init.S,v 1.4 2005/04/24 18:48:32 starvik Exp $
+ *
+ * DRAM/SDRAM initialization - alter with care
+ * This file is intended to be included from other assembler files
+ *
+ * Note: This file may not modify r8 or r9 because they are used to
+ * carry information from the decompresser to the kernel
+ *
+ * Copyright (C) 2000-2003 Axis Communications AB
+ *
+ * Authors: Mikael Starvik (starvik@axis.com)
+ */
+
+/* Just to be certain the config file is included, we include it here
+ * explicitely instead of depending on it being included in the file that
+ * uses this code.
+ */
+
+#include <linux/config.h>
+#include <asm/arch/hwregs/asm/reg_map_asm.h>
+#include <asm/arch/hwregs/asm/bif_core_defs_asm.h>
+
+ ;; WARNING! The registers r8 and r9 are used as parameters carrying
+ ;; information from the decompressor (if the kernel was compressed).
+ ;; They should not be used in the code below.
+
+ ; Refer to BIF MDS for a description of SDRAM initialization
+
+ ; Bank configuration
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_sdram_cfg_grp0), $r0
+ move.d CONFIG_ETRAX_SDRAM_GRP0_CONFIG, $r1
+ move.d $r1, [$r0]
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_sdram_cfg_grp1), $r0
+ move.d CONFIG_ETRAX_SDRAM_GRP1_CONFIG, $r1
+ move.d $r1, [$r0]
+
+ ; Calculate value of mrs_data
+ ; CAS latency = 2 && bus_width = 32 => 0x40
+ ; CAS latency = 3 && bus_width = 32 => 0x60
+ ; CAS latency = 2 && bus_width = 16 => 0x20
+ ; CAS latency = 3 && bus_width = 16 => 0x30
+
+ ; Check if value is already supplied in kernel config
+ move.d CONFIG_ETRAX_SDRAM_COMMAND, $r2
+ bne _set_timing
+ nop
+
+ move.d 0x40, $r4 ; Assume 32 bits and CAS latency = 2
+ move.d CONFIG_ETRAX_SDRAM_TIMING, $r1
+ and.d 0x07, $r1 ; Get CAS latency
+ cmpq 2, $r1 ; CL = 2 ?
+ beq _bw_check
+ nop
+ move.d 0x60, $r4
+
+_bw_check:
+ ; Assume that group 0 width is equal to group 1. This assumption
+ ; is wrong for a group 1 only hardware (such as the grand old
+ ; StorPoint+).
+ move.d CONFIG_ETRAX_SDRAM_GRP0_CONFIG, $r1
+ and.d 0x200, $r1 ; DRAM width is bit 9
+ beq _set_timing
+ lslq 2, $r4 ; mrs_data starts at bit 2
+ lsrq 1, $r4 ; 16 bits. Shift down value.
+
+ ; Set timing parameters (refresh off to avoid Guinness TR 83)
+_set_timing:
+ move.d CONFIG_ETRAX_SDRAM_TIMING, $r1
+ and.d ~(3 << reg_bif_core_rw_sdram_timing___ref___lsb), $r1
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_sdram_timing), $r0
+ move.d $r1, [$r0]
+
+ ; Issue NOP command
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_sdram_cmd), $r5
+ moveq regk_bif_core_nop, $r1
+ move.d $r1, [$r5]
+
+ ; Wait 200us
+ move.d 10000, $r2
+1: bne 1b
+ subq 1, $r2
+
+ ; Issue initialization command sequence
+ move.d _sdram_commands_start, $r2
+ and.d 0x000fffff, $r2 ; Make sure commands are read from flash
+ move.d _sdram_commands_end, $r3
+ and.d 0x000fffff, $r3
+1: clear.d $r6
+ move.b [$r2+], $r6 ; Load command
+ or.d $r4, $r6 ; Add calculated mrs
+ move.d $r6, [$r5] ; Write rw_sdram_cmd
+ ; Wait 80 ns between each command
+ move.d 4000, $r7
+2: bne 2b
+ subq 1, $r7
+ cmp.d $r2, $r3 ; Last command?
+ bne 1b
+ nop
+
+ ; Start refresh
+ move.d CONFIG_ETRAX_SDRAM_TIMING, $r1
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_sdram_timing), $r0
+ move.d $r1, [$r0]
+
+ ; Initialization finished
+ ba _sdram_commands_end
+ nop
+
+_sdram_commands_start:
+ .byte regk_bif_core_pre ; Precharge
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_ref ; refresh
+ .byte regk_bif_core_mrs ; mrs
+_sdram_commands_end:
diff --git a/arch/cris/arch-v32/lib/hw_settings.S b/arch/cris/arch-v32/lib/hw_settings.S
new file mode 100644
index 000000000000..5182e8c2cff2
--- /dev/null
+++ b/arch/cris/arch-v32/lib/hw_settings.S
@@ -0,0 +1,73 @@
+/*
+ * $Id: hw_settings.S,v 1.3 2005/04/24 18:36:57 starvik Exp $
+ *
+ * This table is used by some tools to extract hardware parameters.
+ * The table should be included in the kernel and the decompressor.
+ * Don't forget to update the tools if you change this table.
+ *
+ * Copyright (C) 2001 Axis Communications AB
+ *
+ * Authors: Mikael Starvik (starvik@axis.com)
+ */
+
+#include <linux/config.h>
+#include <asm/arch/hwregs/asm/reg_map_asm.h>
+#include <asm/arch/hwregs/asm/bif_core_defs_asm.h>
+#include <asm/arch/hwregs/asm/gio_defs_asm.h>
+
+ .ascii "HW_PARAM_MAGIC" ; Magic number
+ .dword 0xc0004000 ; Kernel start address
+
+ ; Debug port
+#ifdef CONFIG_ETRAX_DEBUG_PORT0
+ .dword 0
+#elif defined(CONFIG_ETRAX_DEBUG_PORT1)
+ .dword 1
+#elif defined(CONFIG_ETRAX_DEBUG_PORT2)
+ .dword 2
+#elif defined(CONFIG_ETRAX_DEBUG_PORT3)
+ .dword 3
+#else
+ .dword 4 ; No debug
+#endif
+
+ ; Register values
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_grp1_cfg)
+ .dword CONFIG_ETRAX_MEM_GRP1_CONFIG
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_grp2_cfg)
+ .dword CONFIG_ETRAX_MEM_GRP2_CONFIG
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_grp3_cfg)
+ .dword CONFIG_ETRAX_MEM_GRP3_CONFIG
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_grp4_cfg)
+ .dword CONFIG_ETRAX_MEM_GRP4_CONFIG
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_sdram_cfg_grp0)
+ .dword CONFIG_ETRAX_SDRAM_GRP0_CONFIG
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_sdram_cfg_grp1)
+ .dword CONFIG_ETRAX_SDRAM_GRP1_CONFIG
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_sdram_timing)
+ .dword CONFIG_ETRAX_SDRAM_TIMING
+ .dword REG_ADDR(bif_core, regi_bif_core, rw_sdram_cmd)
+ .dword CONFIG_ETRAX_SDRAM_COMMAND
+
+ .dword REG_ADDR(gio, regi_gio, rw_pa_dout)
+ .dword CONFIG_ETRAX_DEF_GIO_PA_OUT
+ .dword REG_ADDR(gio, regi_gio, rw_pa_oe)
+ .dword CONFIG_ETRAX_DEF_GIO_PA_OE
+ .dword REG_ADDR(gio, regi_gio, rw_pb_dout)
+ .dword CONFIG_ETRAX_DEF_GIO_PB_OUT
+ .dword REG_ADDR(gio, regi_gio, rw_pb_oe)
+ .dword CONFIG_ETRAX_DEF_GIO_PB_OE
+ .dword REG_ADDR(gio, regi_gio, rw_pc_dout)
+ .dword CONFIG_ETRAX_DEF_GIO_PC_OUT
+ .dword REG_ADDR(gio, regi_gio, rw_pc_oe)
+ .dword CONFIG_ETRAX_DEF_GIO_PC_OE
+ .dword REG_ADDR(gio, regi_gio, rw_pd_dout)
+ .dword CONFIG_ETRAX_DEF_GIO_PD_OUT
+ .dword REG_ADDR(gio, regi_gio, rw_pd_oe)
+ .dword CONFIG_ETRAX_DEF_GIO_PD_OE
+ .dword REG_ADDR(gio, regi_gio, rw_pe_dout)
+ .dword CONFIG_ETRAX_DEF_GIO_PE_OUT
+ .dword REG_ADDR(gio, regi_gio, rw_pe_oe)
+ .dword CONFIG_ETRAX_DEF_GIO_PE_OE
+
+ .dword 0 ; No more register values
diff --git a/arch/cris/arch-v32/lib/memset.c b/arch/cris/arch-v32/lib/memset.c
new file mode 100644
index 000000000000..ffca1214674e
--- /dev/null
+++ b/arch/cris/arch-v32/lib/memset.c
@@ -0,0 +1,253 @@
+/*#************************************************************************#*/
+/*#-------------------------------------------------------------------------*/
+/*# */
+/*# FUNCTION NAME: memset() */
+/*# */
+/*# PARAMETERS: void* dst; Destination address. */
+/*# int c; Value of byte to write. */
+/*# int len; Number of bytes to write. */
+/*# */
+/*# RETURNS: dst. */
+/*# */
+/*# DESCRIPTION: Sets the memory dst of length len bytes to c, as standard. */
+/*# Framework taken from memcpy. This routine is */
+/*# very sensitive to compiler changes in register allocation. */
+/*# Should really be rewritten to avoid this problem. */
+/*# */
+/*#-------------------------------------------------------------------------*/
+/*# */
+/*# HISTORY */
+/*# */
+/*# DATE NAME CHANGES */
+/*# ---- ---- ------- */
+/*# 990713 HP Tired of watching this function (or */
+/*# really, the nonoptimized generic */
+/*# implementation) take up 90% of simulator */
+/*# output. Measurements needed. */
+/*# */
+/*#-------------------------------------------------------------------------*/
+
+#include <linux/types.h>
+
+/* No, there's no macro saying 12*4, since it is "hard" to get it into
+ the asm in a good way. Thus better to expose the problem everywhere.
+ */
+
+/* Assuming 1 cycle per dword written or read (ok, not really true), and
+ one per instruction, then 43+3*(n/48-1) <= 24+24*(n/48-1)
+ so n >= 45.7; n >= 0.9; we win on the first full 48-byte block to set. */
+
+#define ZERO_BLOCK_SIZE (1*12*4)
+
+void *memset(void *pdst,
+ int c,
+ size_t plen)
+{
+ /* Ok. Now we want the parameters put in special registers.
+ Make sure the compiler is able to make something useful of this. */
+
+ register char *return_dst __asm__ ("r10") = pdst;
+ register int n __asm__ ("r12") = plen;
+ register int lc __asm__ ("r11") = c;
+
+ /* Most apps use memset sanely. Only those memsetting about 3..4
+ bytes or less get penalized compared to the generic implementation
+ - and that's not really sane use. */
+
+ /* Ugh. This is fragile at best. Check with newer GCC releases, if
+ they compile cascaded "x |= x << 8" sanely! */
+ __asm__("movu.b %0,$r13 \n\
+ lslq 8,$r13 \n\
+ move.b %0,$r13 \n\
+ move.d $r13,%0 \n\
+ lslq 16,$r13 \n\
+ or.d $r13,%0"
+ : "=r" (lc) : "0" (lc) : "r13");
+
+ {
+ register char *dst __asm__ ("r13") = pdst;
+
+ /* This is NONPORTABLE, but since this whole routine is */
+ /* grossly nonportable that doesn't matter. */
+
+ if (((unsigned long) pdst & 3) != 0
+ /* Oops! n=0 must be a legal call, regardless of alignment. */
+ && n >= 3)
+ {
+ if ((unsigned long)dst & 1)
+ {
+ *dst = (char) lc;
+ n--;
+ dst++;
+ }
+
+ if ((unsigned long)dst & 2)
+ {
+ *(short *)dst = lc;
+ n -= 2;
+ dst += 2;
+ }
+ }
+
+ /* Now the fun part. For the threshold value of this, check the equation
+ above. */
+ /* Decide which copying method to use. */
+ if (n >= ZERO_BLOCK_SIZE)
+ {
+ /* For large copies we use 'movem' */
+
+ /* It is not optimal to tell the compiler about clobbering any
+ registers; that will move the saving/restoring of those registers
+ to the function prologue/epilogue, and make non-movem sizes
+ suboptimal.
+
+ This method is not foolproof; it assumes that the "asm reg"
+ declarations at the beginning of the function really are used
+ here (beware: they may be moved to temporary registers).
+ This way, we do not have to save/move the registers around into
+ temporaries; we can safely use them straight away.
+
+ If you want to check that the allocation was right; then
+ check the equalities in the first comment. It should say
+ "r13=r13, r12=r12, r11=r11" */
+ __asm__ volatile (" \n\
+ ;; Check that the register asm declaration got right. \n\
+ ;; The GCC manual says it will work, but there *has* been bugs. \n\
+ .ifnc %0-%1-%4,$r13-$r12-$r11 \n\
+ .err \n\
+ .endif \n\
+ \n\
+ ;; Save the registers we'll clobber in the movem process \n\
+ ;; on the stack. Don't mention them to gcc, it will only be \n\
+ ;; upset. \n\
+ subq 11*4,$sp \n\
+ movem $r10,[$sp] \n\
+ \n\
+ move.d $r11,$r0 \n\
+ move.d $r11,$r1 \n\
+ move.d $r11,$r2 \n\
+ move.d $r11,$r3 \n\
+ move.d $r11,$r4 \n\
+ move.d $r11,$r5 \n\
+ move.d $r11,$r6 \n\
+ move.d $r11,$r7 \n\
+ move.d $r11,$r8 \n\
+ move.d $r11,$r9 \n\
+ move.d $r11,$r10 \n\
+ \n\
+ ;; Now we've got this: \n\
+ ;; r13 - dst \n\
+ ;; r12 - n \n\
+ \n\
+ ;; Update n for the first loop \n\
+ subq 12*4,$r12 \n\
+0: \n\
+ subq 12*4,$r12 \n\
+ bge 0b \n\
+ movem $r11,[$r13+] \n\
+ \n\
+ addq 12*4,$r12 ;; compensate for last loop underflowing n \n\
+ \n\
+ ;; Restore registers from stack \n\
+ movem [$sp+],$r10"
+
+ /* Outputs */ : "=r" (dst), "=r" (n)
+ /* Inputs */ : "0" (dst), "1" (n), "r" (lc));
+ }
+
+ /* Either we directly starts copying, using dword copying
+ in a loop, or we copy as much as possible with 'movem'
+ and then the last block (<44 bytes) is copied here.
+ This will work since 'movem' will have updated src,dst,n. */
+
+ while ( n >= 16 )
+ {
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ n -= 16;
+ }
+
+ /* A switch() is definitely the fastest although it takes a LOT of code.
+ * Particularly if you inline code this.
+ */
+ switch (n)
+ {
+ case 0:
+ break;
+ case 1:
+ *(char*)dst = (char) lc;
+ break;
+ case 2:
+ *(short*)dst = (short) lc;
+ break;
+ case 3:
+ *((short*)dst)++ = (short) lc;
+ *(char*)dst = (char) lc;
+ break;
+ case 4:
+ *((long*)dst)++ = lc;
+ break;
+ case 5:
+ *((long*)dst)++ = lc;
+ *(char*)dst = (char) lc;
+ break;
+ case 6:
+ *((long*)dst)++ = lc;
+ *(short*)dst = (short) lc;
+ break;
+ case 7:
+ *((long*)dst)++ = lc;
+ *((short*)dst)++ = (short) lc;
+ *(char*)dst = (char) lc;
+ break;
+ case 8:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ break;
+ case 9:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *(char*)dst = (char) lc;
+ break;
+ case 10:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *(short*)dst = (short) lc;
+ break;
+ case 11:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((short*)dst)++ = (short) lc;
+ *(char*)dst = (char) lc;
+ break;
+ case 12:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ break;
+ case 13:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *(char*)dst = (char) lc;
+ break;
+ case 14:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *(short*)dst = (short) lc;
+ break;
+ case 15:
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((long*)dst)++ = lc;
+ *((short*)dst)++ = (short) lc;
+ *(char*)dst = (char) lc;
+ break;
+ }
+ }
+
+ return return_dst; /* destination pointer. */
+} /* memset() */
diff --git a/arch/cris/arch-v32/lib/nand_init.S b/arch/cris/arch-v32/lib/nand_init.S
new file mode 100644
index 000000000000..aba5c751c282
--- /dev/null
+++ b/arch/cris/arch-v32/lib/nand_init.S
@@ -0,0 +1,179 @@
+##=============================================================================
+##
+## nand_init.S
+##
+## The bootrom copies data from the NAND flash to the internal RAM but
+## due to a bug/feature we can only trust the 256 first bytes. So this
+## code copies more data from NAND flash to internal RAM. Obvioulsy this
+## code must fit in the first 256 bytes so alter with care.
+##
+## Some notes about the bug/feature for future reference:
+## The bootrom copies the first 127 KB from NAND flash to internal
+## memory. The problem is that it does a bytewise copy. NAND flashes
+## does autoincrement on the address so for a 16-bite device each
+## read/write increases the address by two. So the copy loop in the
+## bootrom will discard every second byte. This is solved by inserting
+## zeroes in every second byte in the first erase block.
+##
+## The bootrom also incorrectly assumes that it can read the flash
+## linear with only one read command but the flash will actually
+## switch between normal area and spare area if you do that so we
+## can't trust more than the first 256 bytes.
+##
+##=============================================================================
+
+#include <asm/arch/hwregs/asm/reg_map_asm.h>
+#include <asm/arch/hwregs/asm/gio_defs_asm.h>
+#include <asm/arch/hwregs/asm/pinmux_defs_asm.h>
+#include <asm/arch/hwregs/asm/bif_core_defs_asm.h>
+#include <asm/arch/hwregs/asm/config_defs_asm.h>
+#include <linux/config.h>
+
+;; There are 8-bit NAND flashes and 16-bit NAND flashes.
+;; We need to treat them slightly different.
+#if CONFIG_ETRAX_FLASH_BUSWIDTH==2
+#define PAGE_SIZE 256
+#else
+#error 2
+#define PAGE_SIZE 512
+#endif
+#define ERASE_BLOCK 16384
+
+;; GPIO pins connected to NAND flash
+#define CE 4
+#define CLE 5
+#define ALE 6
+#define BY 7
+
+;; Address space for NAND flash
+#define NAND_RD_ADDR 0x90000000
+#define NAND_WR_ADDR 0x94000000
+
+#define READ_CMD 0x00
+
+;; Readability macros
+#define CSP_MASK \
+ REG_MASK(bif_core, rw_grp3_cfg, gated_csp0) | \
+ REG_MASK(bif_core, rw_grp3_cfg, gated_csp1)
+#define CSP_VAL \
+ REG_STATE(bif_core, rw_grp3_cfg, gated_csp0, rd) | \
+ REG_STATE(bif_core, rw_grp3_cfg, gated_csp1, wr)
+
+;;----------------------------------------------------------------------------
+;; Macros to set/clear GPIO bits
+
+.macro SET x
+ or.b (1<<\x),$r9
+ move.d $r9, [$r2]
+.endm
+
+.macro CLR x
+ and.b ~(1<<\x),$r9
+ move.d $r9, [$r2]
+.endm
+
+;;----------------------------------------------------------------------------
+
+nand_boot:
+ ;; Check if nand boot was selected
+ move.d REG_ADDR(config, regi_config, r_bootsel), $r0
+ move.d [$r0], $r0
+ and.d REG_MASK(config, r_bootsel, boot_mode), $r0
+ cmp.d REG_STATE(config, r_bootsel, boot_mode, nand), $r0
+ bne normal_boot ; No NAND boot
+ nop
+
+copy_nand_to_ram:
+ ;; copy_nand_to_ram
+ ;; Arguments
+ ;; r10 - destination
+ ;; r11 - source offset
+ ;; r12 - size
+ ;; r13 - Address to jump to after completion
+ ;; Note : r10-r12 are clobbered on return
+ ;; Registers used:
+ ;; r0 - NAND_RD_ADDR
+ ;; r1 - NAND_WR_ADDR
+ ;; r2 - reg_gio_rw_pa_dout
+ ;; r3 - reg_gio_r_pa_din
+ ;; r4 - tmp
+ ;; r5 - byte counter within a page
+ ;; r6 - reg_pinmux_rw_pa
+ ;; r7 - reg_gio_rw_pa_oe
+ ;; r8 - reg_bif_core_rw_grp3_cfg
+ ;; r9 - reg_gio_rw_pa_dout shadow
+ move.d 0x90000000, $r0
+ move.d 0x94000000, $r1
+ move.d REG_ADDR(gio, regi_gio, rw_pa_dout), $r2
+ move.d REG_ADDR(gio, regi_gio, r_pa_din), $r3
+ move.d REG_ADDR(pinmux, regi_pinmux, rw_pa), $r6
+ move.d REG_ADDR(gio, regi_gio, rw_pa_oe), $r7
+ move.d REG_ADDR(bif_core, regi_bif_core, rw_grp3_cfg), $r8
+
+#if CONFIG_ETRAX_FLASH_BUSWIDTH==2
+ lsrq 1, $r11
+#endif
+ ;; Set up GPIO
+ move.d [$r2], $r9
+ move.d [$r7], $r4
+ or.b (1<<ALE) | (1 << CLE) | (1<<CE), $r4
+ move.d $r4, [$r7]
+
+ ;; Set up bif
+ move.d [$r8], $r4
+ and.d CSP_MASK, $r4
+ or.d CSP_VAL, $r4
+ move.d $r4, [$r8]
+
+1: ;; Copy one page
+ CLR CE
+ SET CLE
+ moveq READ_CMD, $r4
+ move.b $r4, [$r1]
+ moveq 20, $r4
+2: bne 2b
+ subq 1, $r4
+ CLR CLE
+ SET ALE
+ clear.w [$r1] ; Column address = 0
+ move.d $r11, $r4
+ lsrq 8, $r4
+ move.b $r4, [$r1] ; Row address
+ lsrq 8, $r4
+ move.b $r4, [$r1] ; Row adddress
+ moveq 20, $r4
+2: bne 2b
+ subq 1, $r4
+ CLR ALE
+2: move.d [$r3], $r4
+ and.d 1 << BY, $r4
+ beq 2b
+ movu.w PAGE_SIZE, $r5
+2: ; Copy one byte/word
+#if CONFIG_ETRAX_FLASH_BUSWIDTH==2
+ move.w [$r0], $r4
+#else
+ move.b [$r0], $r4
+#endif
+ subq 1, $r5
+ bne 2b
+#if CONFIG_ETRAX_FLASH_BUSWIDTH==2
+ move.w $r4, [$r10+]
+ subu.w PAGE_SIZE*2, $r12
+#else
+ move.b $r4, [$r10+]
+ subu.w PAGE_SIZE, $r12
+#endif
+ bpl 1b
+ addu.w PAGE_SIZE, $r11
+
+ ;; End of copy
+ jump $r13
+ nop
+
+ ;; This will warn if the code above is too large. If you consider
+ ;; to remove this you don't understand the bug/feature.
+ .org 256
+ .org ERASE_BLOCK
+
+normal_boot:
diff --git a/arch/cris/arch-v32/lib/spinlock.S b/arch/cris/arch-v32/lib/spinlock.S
new file mode 100644
index 000000000000..2437ae7f6ed2
--- /dev/null
+++ b/arch/cris/arch-v32/lib/spinlock.S
@@ -0,0 +1,33 @@
+;; Core of the spinlock implementation
+;;
+;; Copyright (C) 2004 Axis Communications AB.
+;;
+;; Author: Mikael Starvik
+
+
+ .global cris_spin_lock
+ .global cris_spin_trylock
+
+ .text
+
+cris_spin_lock:
+ clearf p
+1: test.d [$r10]
+ beq 1b
+ clearf p
+ ax
+ clear.d [$r10]
+ bcs 1b
+ clearf p
+ ret
+ nop
+
+cris_spin_trylock:
+ clearf p
+1: move.d [$r10], $r11
+ ax
+ clear.d [$r10]
+ bcs 1b
+ clearf p
+ ret
+ move.d $r11,$r10
diff --git a/arch/cris/arch-v32/lib/string.c b/arch/cris/arch-v32/lib/string.c
new file mode 100644
index 000000000000..98e282ac824a
--- /dev/null
+++ b/arch/cris/arch-v32/lib/string.c
@@ -0,0 +1,219 @@
+/*#************************************************************************#*/
+/*#-------------------------------------------------------------------------*/
+/*# */
+/*# FUNCTION NAME: memcpy() */
+/*# */
+/*# PARAMETERS: void* dst; Destination address. */
+/*# void* src; Source address. */
+/*# int len; Number of bytes to copy. */
+/*# */
+/*# RETURNS: dst. */
+/*# */
+/*# DESCRIPTION: Copies len bytes of memory from src to dst. No guarantees */
+/*# about copying of overlapping memory areas. This routine is */
+/*# very sensitive to compiler changes in register allocation. */
+/*# Should really be rewritten to avoid this problem. */
+/*# */
+/*#-------------------------------------------------------------------------*/
+/*# */
+/*# HISTORY */
+/*# */
+/*# DATE NAME CHANGES */
+/*# ---- ---- ------- */
+/*# 941007 Kenny R Creation */
+/*# 941011 Kenny R Lots of optimizations and inlining. */
+/*# 941129 Ulf A Adapted for use in libc. */
+/*# 950216 HP N==0 forgotten if non-aligned src/dst. */
+/*# Added some optimizations. */
+/*# 001025 HP Make src and dst char *. Align dst to */
+/*# dword, not just word-if-both-src-and-dst- */
+/*# are-misaligned. */
+/*# */
+/*#-------------------------------------------------------------------------*/
+
+#include <linux/types.h>
+
+void *memcpy(void *pdst,
+ const void *psrc,
+ size_t pn)
+{
+ /* Ok. Now we want the parameters put in special registers.
+ Make sure the compiler is able to make something useful of this.
+ As it is now: r10 -> r13; r11 -> r11 (nop); r12 -> r12 (nop).
+
+ If gcc was allright, it really would need no temporaries, and no
+ stack space to save stuff on. */
+
+ register void *return_dst __asm__ ("r10") = pdst;
+ register char *dst __asm__ ("r13") = pdst;
+ register const char *src __asm__ ("r11") = psrc;
+ register int n __asm__ ("r12") = pn;
+
+
+ /* When src is aligned but not dst, this makes a few extra needless
+ cycles. I believe it would take as many to check that the
+ re-alignment was unnecessary. */
+ if (((unsigned long) dst & 3) != 0
+ /* Don't align if we wouldn't copy more than a few bytes; so we
+ don't have to check further for overflows. */
+ && n >= 3)
+ {
+ if ((unsigned long) dst & 1)
+ {
+ n--;
+ *(char*)dst = *(char*)src;
+ src++;
+ dst++;
+ }
+
+ if ((unsigned long) dst & 2)
+ {
+ n -= 2;
+ *(short*)dst = *(short*)src;
+ src += 2;
+ dst += 2;
+ }
+ }
+
+ /* Decide which copying method to use. Movem is dirt cheap, so the
+ overheap is low enough to always use the minimum block size as the
+ threshold. */
+ if (n >= 44)
+ {
+ /* For large copies we use 'movem' */
+
+ /* It is not optimal to tell the compiler about clobbering any
+ registers; that will move the saving/restoring of those registers
+ to the function prologue/epilogue, and make non-movem sizes
+ suboptimal. */
+ __asm__ volatile (" \n\
+ ;; Check that the register asm declaration got right. \n\
+ ;; The GCC manual explicitly says TRT will happen. \n\
+ .ifnc %0-%1-%2,$r13-$r11-$r12 \n\
+ .err \n\
+ .endif \n\
+ \n\
+ ;; Save the registers we'll use in the movem process \n\
+ \n\
+ ;; on the stack. \n\
+ subq 11*4,$sp \n\
+ movem $r10,[$sp] \n\
+ \n\
+ ;; Now we've got this: \n\
+ ;; r11 - src \n\
+ ;; r13 - dst \n\
+ ;; r12 - n \n\
+ \n\
+ ;; Update n for the first loop \n\
+ subq 44,$r12 \n\
+0: \n\
+ movem [$r11+],$r10 \n\
+ subq 44,$r12 \n\
+ bge 0b \n\
+ movem $r10,[$r13+] \n\
+ \n\
+ addq 44,$r12 ;; compensate for last loop underflowing n \n\
+ \n\
+ ;; Restore registers from stack \n\
+ movem [$sp+],$r10"
+
+ /* Outputs */ : "=r" (dst), "=r" (src), "=r" (n)
+ /* Inputs */ : "0" (dst), "1" (src), "2" (n));
+
+ }
+
+ /* Either we directly starts copying, using dword copying
+ in a loop, or we copy as much as possible with 'movem'
+ and then the last block (<44 bytes) is copied here.
+ This will work since 'movem' will have updated src,dst,n. */
+
+ while ( n >= 16 )
+ {
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ n -= 16;
+ }
+
+ /* A switch() is definitely the fastest although it takes a LOT of code.
+ * Particularly if you inline code this.
+ */
+ switch (n)
+ {
+ case 0:
+ break;
+ case 1:
+ *(char*)dst = *(char*)src;
+ break;
+ case 2:
+ *(short*)dst = *(short*)src;
+ break;
+ case 3:
+ *((short*)dst)++ = *((short*)src)++;
+ *(char*)dst = *(char*)src;
+ break;
+ case 4:
+ *((long*)dst)++ = *((long*)src)++;
+ break;
+ case 5:
+ *((long*)dst)++ = *((long*)src)++;
+ *(char*)dst = *(char*)src;
+ break;
+ case 6:
+ *((long*)dst)++ = *((long*)src)++;
+ *(short*)dst = *(short*)src;
+ break;
+ case 7:
+ *((long*)dst)++ = *((long*)src)++;
+ *((short*)dst)++ = *((short*)src)++;
+ *(char*)dst = *(char*)src;
+ break;
+ case 8:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ break;
+ case 9:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *(char*)dst = *(char*)src;
+ break;
+ case 10:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *(short*)dst = *(short*)src;
+ break;
+ case 11:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((short*)dst)++ = *((short*)src)++;
+ *(char*)dst = *(char*)src;
+ break;
+ case 12:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ break;
+ case 13:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *(char*)dst = *(char*)src;
+ break;
+ case 14:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *(short*)dst = *(short*)src;
+ break;
+ case 15:
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((long*)dst)++ = *((long*)src)++;
+ *((short*)dst)++ = *((short*)src)++;
+ *(char*)dst = *(char*)src;
+ break;
+ }
+
+ return return_dst; /* destination pointer. */
+} /* memcpy() */
diff --git a/arch/cris/arch-v32/lib/usercopy.c b/arch/cris/arch-v32/lib/usercopy.c
new file mode 100644
index 000000000000..f0b08460c1be
--- /dev/null
+++ b/arch/cris/arch-v32/lib/usercopy.c
@@ -0,0 +1,470 @@
+/*
+ * User address space access functions.
+ * The non-inlined parts of asm-cris/uaccess.h are here.
+ *
+ * Copyright (C) 2000, 2003 Axis Communications AB.
+ *
+ * Written by Hans-Peter Nilsson.
+ * Pieces used from memcpy, originally by Kenny Ranerup long time ago.
+ */
+
+#include <asm/uaccess.h>
+
+/* Asm:s have been tweaked (within the domain of correctness) to give
+ satisfactory results for "gcc version 3.2.1 Axis release R53/1.53-v32".
+
+ Check regularly...
+
+ Note that for CRISv32, the PC saved at a bus-fault is the address
+ *at* the faulting instruction, with a special case for instructions
+ in delay slots: then it's the address of the branch. Note also that
+ in contrast to v10, a postincrement in the instruction is *not*
+ performed at a bus-fault; the register is seen having the original
+ value in fault handlers. */
+
+
+/* Copy to userspace. This is based on the memcpy used for
+ kernel-to-kernel copying; see "string.c". */
+
+unsigned long
+__copy_user (void __user *pdst, const void *psrc, unsigned long pn)
+{
+ /* We want the parameters put in special registers.
+ Make sure the compiler is able to make something useful of this.
+ As it is now: r10 -> r13; r11 -> r11 (nop); r12 -> r12 (nop).
+
+ FIXME: Comment for old gcc version. Check.
+ If gcc was allright, it really would need no temporaries, and no
+ stack space to save stuff on. */
+
+ register char *dst __asm__ ("r13") = pdst;
+ register const char *src __asm__ ("r11") = psrc;
+ register int n __asm__ ("r12") = pn;
+ register int retn __asm__ ("r10") = 0;
+
+
+ /* When src is aligned but not dst, this makes a few extra needless
+ cycles. I believe it would take as many to check that the
+ re-alignment was unnecessary. */
+ if (((unsigned long) dst & 3) != 0
+ /* Don't align if we wouldn't copy more than a few bytes; so we
+ don't have to check further for overflows. */
+ && n >= 3)
+ {
+ if ((unsigned long) dst & 1)
+ {
+ __asm_copy_to_user_1 (dst, src, retn);
+ n--;
+ }
+
+ if ((unsigned long) dst & 2)
+ {
+ __asm_copy_to_user_2 (dst, src, retn);
+ n -= 2;
+ }
+ }
+
+ /* Movem is dirt cheap. The overheap is low enough to always use the
+ minimum possible block size as the threshold. */
+ if (n >= 44)
+ {
+ /* For large copies we use 'movem'. */
+
+ /* It is not optimal to tell the compiler about clobbering any
+ registers; that will move the saving/restoring of those registers
+ to the function prologue/epilogue, and make non-movem sizes
+ suboptimal. */
+ __asm__ volatile ("\
+ ;; Check that the register asm declaration got right. \n\
+ ;; The GCC manual explicitly says TRT will happen. \n\
+ .ifnc %0%1%2%3,$r13$r11$r12$r10 \n\
+ .err \n\
+ .endif \n\
+ \n\
+ ;; Save the registers we'll use in the movem process \n\
+ ;; on the stack. \n\
+ subq 11*4,$sp \n\
+ movem $r10,[$sp] \n\
+ \n\
+ ;; Now we've got this: \n\
+ ;; r11 - src \n\
+ ;; r13 - dst \n\
+ ;; r12 - n \n\
+ \n\
+ ;; Update n for the first loop \n\
+ subq 44,$r12 \n\
+0: \n\
+ movem [$r11+],$r10 \n\
+ subq 44,$r12 \n\
+1: bge 0b \n\
+ movem $r10,[$r13+] \n\
+3: \n\
+ addq 44,$r12 ;; compensate for last loop underflowing n \n\
+ \n\
+ ;; Restore registers from stack \n\
+ movem [$sp+],$r10 \n\
+2: \n\
+ .section .fixup,\"ax\" \n\
+4: \n\
+; When failing on any of the 1..44 bytes in a chunk, we adjust back the \n\
+; source pointer and just drop through to the by-16 and by-4 loops to \n\
+; get the correct number of failing bytes. This necessarily means a \n\
+; few extra exceptions, but invalid user pointers shouldn't happen in \n\
+; time-critical code anyway. \n\
+ jump 3b \n\
+ subq 44,$r11 \n\
+ \n\
+ .previous \n\
+ .section __ex_table,\"a\" \n\
+ .dword 1b,4b \n\
+ .previous"
+
+ /* Outputs */ : "=r" (dst), "=r" (src), "=r" (n), "=r" (retn)
+ /* Inputs */ : "0" (dst), "1" (src), "2" (n), "3" (retn));
+
+ }
+
+ while (n >= 16)
+ {
+ __asm_copy_to_user_16 (dst, src, retn);
+ n -= 16;
+ }
+
+ /* Having a separate by-four loops cuts down on cache footprint.
+ FIXME: Test with and without; increasing switch to be 0..15. */
+ while (n >= 4)
+ {
+ __asm_copy_to_user_4 (dst, src, retn);
+ n -= 4;
+ }
+
+ switch (n)
+ {
+ case 0:
+ break;
+ case 1:
+ __asm_copy_to_user_1 (dst, src, retn);
+ break;
+ case 2:
+ __asm_copy_to_user_2 (dst, src, retn);
+ break;
+ case 3:
+ __asm_copy_to_user_3 (dst, src, retn);
+ break;
+ }
+
+ return retn;
+}
+
+/* Copy from user to kernel, zeroing the bytes that were inaccessible in
+ userland. The return-value is the number of bytes that were
+ inaccessible. */
+
+unsigned long
+__copy_user_zeroing (void __user *pdst, const void *psrc, unsigned long pn)
+{
+ /* We want the parameters put in special registers.
+ Make sure the compiler is able to make something useful of this.
+ As it is now: r10 -> r13; r11 -> r11 (nop); r12 -> r12 (nop).
+
+ FIXME: Comment for old gcc version. Check.
+ If gcc was allright, it really would need no temporaries, and no
+ stack space to save stuff on. */
+
+ register char *dst __asm__ ("r13") = pdst;
+ register const char *src __asm__ ("r11") = psrc;
+ register int n __asm__ ("r12") = pn;
+ register int retn __asm__ ("r10") = 0;
+
+ /* The best reason to align src is that we then know that a read-fault
+ was for aligned bytes; there's no 1..3 remaining good bytes to
+ pickle. */
+ if (((unsigned long) src & 3) != 0)
+ {
+ if (((unsigned long) src & 1) && n != 0)
+ {
+ __asm_copy_from_user_1 (dst, src, retn);
+ n--;
+ }
+
+ if (((unsigned long) src & 2) && n >= 2)
+ {
+ __asm_copy_from_user_2 (dst, src, retn);
+ n -= 2;
+ }
+
+ /* We only need one check after the unalignment-adjustments, because
+ if both adjustments were done, either both or neither reference
+ had an exception. */
+ if (retn != 0)
+ goto copy_exception_bytes;
+ }
+
+ /* Movem is dirt cheap. The overheap is low enough to always use the
+ minimum possible block size as the threshold. */
+ if (n >= 44)
+ {
+ /* It is not optimal to tell the compiler about clobbering any
+ registers; that will move the saving/restoring of those registers
+ to the function prologue/epilogue, and make non-movem sizes
+ suboptimal. */
+ __asm__ volatile ("\
+ .ifnc %0%1%2%3,$r13$r11$r12$r10 \n\
+ .err \n\
+ .endif \n\
+ \n\
+ ;; Save the registers we'll use in the movem process \n\
+ ;; on the stack. \n\
+ subq 11*4,$sp \n\
+ movem $r10,[$sp] \n\
+ \n\
+ ;; Now we've got this: \n\
+ ;; r11 - src \n\
+ ;; r13 - dst \n\
+ ;; r12 - n \n\
+ \n\
+ ;; Update n for the first loop \n\
+ subq 44,$r12 \n\
+0: \n\
+ movem [$r11+],$r10 \n\
+ \n\
+ subq 44,$r12 \n\
+ bge 0b \n\
+ movem $r10,[$r13+] \n\
+ \n\
+4: \n\
+ addq 44,$r12 ;; compensate for last loop underflowing n \n\
+ \n\
+ ;; Restore registers from stack \n\
+ movem [$sp+],$r10 \n\
+ .section .fixup,\"ax\" \n\
+ \n\
+;; Do not jump back into the loop if we fail. For some uses, we get a \n\
+;; page fault somewhere on the line. Without checking for page limits, \n\
+;; we don't know where, but we need to copy accurately and keep an \n\
+;; accurate count; not just clear the whole line. To do that, we fall \n\
+;; down in the code below, proceeding with smaller amounts. It should \n\
+;; be kept in mind that we have to cater to code like what at one time \n\
+;; was in fs/super.c: \n\
+;; i = size - copy_from_user((void *)page, data, size); \n\
+;; which would cause repeated faults while clearing the remainder of \n\
+;; the SIZE bytes at PAGE after the first fault. \n\
+;; A caveat here is that we must not fall through from a failing page \n\
+;; to a valid page. \n\
+ \n\
+3: \n\
+ jump 4b ;; Fall through, pretending the fault didn't happen. \n\
+ nop \n\
+ \n\
+ .previous \n\
+ .section __ex_table,\"a\" \n\
+ .dword 0b,3b \n\
+ .previous"
+
+ /* Outputs */ : "=r" (dst), "=r" (src), "=r" (n), "=r" (retn)
+ /* Inputs */ : "0" (dst), "1" (src), "2" (n), "3" (retn));
+ }
+
+ /* Either we directly start copying here, using dword copying in a loop,
+ or we copy as much as possible with 'movem' and then the last block
+ (<44 bytes) is copied here. This will work since 'movem' will have
+ updated src, dst and n. (Except with failing src.)
+
+ Since we want to keep src accurate, we can't use
+ __asm_copy_from_user_N with N != (1, 2, 4); it updates dst and
+ retn, but not src (by design; it's value is ignored elsewhere). */
+
+ while (n >= 4)
+ {
+ __asm_copy_from_user_4 (dst, src, retn);
+ n -= 4;
+
+ if (retn)
+ goto copy_exception_bytes;
+ }
+
+ /* If we get here, there were no memory read faults. */
+ switch (n)
+ {
+ /* These copies are at least "naturally aligned" (so we don't have
+ to check each byte), due to the src alignment code before the
+ movem loop. The *_3 case *will* get the correct count for retn. */
+ case 0:
+ /* This case deliberately left in (if you have doubts check the
+ generated assembly code). */
+ break;
+ case 1:
+ __asm_copy_from_user_1 (dst, src, retn);
+ break;
+ case 2:
+ __asm_copy_from_user_2 (dst, src, retn);
+ break;
+ case 3:
+ __asm_copy_from_user_3 (dst, src, retn);
+ break;
+ }
+
+ /* If we get here, retn correctly reflects the number of failing
+ bytes. */
+ return retn;
+
+copy_exception_bytes:
+ /* We already have "retn" bytes cleared, and need to clear the
+ remaining "n" bytes. A non-optimized simple byte-for-byte in-line
+ memset is preferred here, since this isn't speed-critical code and
+ we'd rather have this a leaf-function than calling memset. */
+ {
+ char *endp;
+ for (endp = dst + n; dst < endp; dst++)
+ *dst = 0;
+ }
+
+ return retn + n;
+}
+
+/* Zero userspace. */
+
+unsigned long
+__do_clear_user (void __user *pto, unsigned long pn)
+{
+ /* We want the parameters put in special registers.
+ Make sure the compiler is able to make something useful of this.
+ As it is now: r10 -> r13; r11 -> r11 (nop); r12 -> r12 (nop).
+
+ FIXME: Comment for old gcc version. Check.
+ If gcc was allright, it really would need no temporaries, and no
+ stack space to save stuff on. */
+
+ register char *dst __asm__ ("r13") = pto;
+ register int n __asm__ ("r12") = pn;
+ register int retn __asm__ ("r10") = 0;
+
+
+ if (((unsigned long) dst & 3) != 0
+ /* Don't align if we wouldn't copy more than a few bytes. */
+ && n >= 3)
+ {
+ if ((unsigned long) dst & 1)
+ {
+ __asm_clear_1 (dst, retn);
+ n--;
+ }
+
+ if ((unsigned long) dst & 2)
+ {
+ __asm_clear_2 (dst, retn);
+ n -= 2;
+ }
+ }
+
+ /* Decide which copying method to use.
+ FIXME: This number is from the "ordinary" kernel memset. */
+ if (n >= 48)
+ {
+ /* For large clears we use 'movem' */
+
+ /* It is not optimal to tell the compiler about clobbering any
+ call-saved registers; that will move the saving/restoring of
+ those registers to the function prologue/epilogue, and make
+ non-movem sizes suboptimal.
+
+ This method is not foolproof; it assumes that the "asm reg"
+ declarations at the beginning of the function really are used
+ here (beware: they may be moved to temporary registers).
+ This way, we do not have to save/move the registers around into
+ temporaries; we can safely use them straight away.
+
+ If you want to check that the allocation was right; then
+ check the equalities in the first comment. It should say
+ something like "r13=r13, r11=r11, r12=r12". */
+ __asm__ volatile ("\
+ .ifnc %0%1%2,$r13$r12$r10 \n\
+ .err \n\
+ .endif \n\
+ \n\
+ ;; Save the registers we'll clobber in the movem process \n\
+ ;; on the stack. Don't mention them to gcc, it will only be \n\
+ ;; upset. \n\
+ subq 11*4,$sp \n\
+ movem $r10,[$sp] \n\
+ \n\
+ clear.d $r0 \n\
+ clear.d $r1 \n\
+ clear.d $r2 \n\
+ clear.d $r3 \n\
+ clear.d $r4 \n\
+ clear.d $r5 \n\
+ clear.d $r6 \n\
+ clear.d $r7 \n\
+ clear.d $r8 \n\
+ clear.d $r9 \n\
+ clear.d $r10 \n\
+ clear.d $r11 \n\
+ \n\
+ ;; Now we've got this: \n\
+ ;; r13 - dst \n\
+ ;; r12 - n \n\
+ \n\
+ ;; Update n for the first loop \n\
+ subq 12*4,$r12 \n\
+0: \n\
+ subq 12*4,$r12 \n\
+1: \n\
+ bge 0b \n\
+ movem $r11,[$r13+] \n\
+ \n\
+ addq 12*4,$r12 ;; compensate for last loop underflowing n \n\
+ \n\
+ ;; Restore registers from stack \n\
+ movem [$sp+],$r10 \n\
+2: \n\
+ .section .fixup,\"ax\" \n\
+3: \n\
+ movem [$sp],$r10 \n\
+ addq 12*4,$r10 \n\
+ addq 12*4,$r13 \n\
+ movem $r10,[$sp] \n\
+ jump 0b \n\
+ clear.d $r10 \n\
+ \n\
+ .previous \n\
+ .section __ex_table,\"a\" \n\
+ .dword 1b,3b \n\
+ .previous"
+
+ /* Outputs */ : "=r" (dst), "=r" (n), "=r" (retn)
+ /* Inputs */ : "0" (dst), "1" (n), "2" (retn)
+ /* Clobber */ : "r11");
+ }
+
+ while (n >= 16)
+ {
+ __asm_clear_16 (dst, retn);
+ n -= 16;
+ }
+
+ /* Having a separate by-four loops cuts down on cache footprint.
+ FIXME: Test with and without; increasing switch to be 0..15. */
+ while (n >= 4)
+ {
+ __asm_clear_4 (dst, retn);
+ n -= 4;
+ }
+
+ switch (n)
+ {
+ case 0:
+ break;
+ case 1:
+ __asm_clear_1 (dst, retn);
+ break;
+ case 2:
+ __asm_clear_2 (dst, retn);
+ break;
+ case 3:
+ __asm_clear_3 (dst, retn);
+ break;
+ }
+
+ return retn;
+}
diff --git a/arch/cris/arch-v32/mm/Makefile b/arch/cris/arch-v32/mm/Makefile
new file mode 100644
index 000000000000..9146f88484b1
--- /dev/null
+++ b/arch/cris/arch-v32/mm/Makefile
@@ -0,0 +1,3 @@
+# Makefile for the Linux/cris parts of the memory manager.
+
+obj-y := mmu.o init.o tlb.o intmem.o
diff --git a/arch/cris/arch-v32/mm/init.c b/arch/cris/arch-v32/mm/init.c
new file mode 100644
index 000000000000..f2fba27d822c
--- /dev/null
+++ b/arch/cris/arch-v32/mm/init.c
@@ -0,0 +1,174 @@
+/*
+ * Set up paging and the MMU.
+ *
+ * Copyright (C) 2000-2003, Axis Communications AB.
+ *
+ * Authors: Bjorn Wesen <bjornw@axis.com>
+ * Tobias Anderberg <tobiasa@axis.com>, CRISv32 port.
+ */
+#include <linux/config.h>
+#include <linux/mmzone.h>
+#include <linux/init.h>
+#include <linux/bootmem.h>
+#include <linux/mm.h>
+#include <linux/config.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <asm/types.h>
+#include <asm/mmu.h>
+#include <asm/io.h>
+#include <asm/mmu_context.h>
+#include <asm/arch/hwregs/asm/mmu_defs_asm.h>
+#include <asm/arch/hwregs/supp_reg.h>
+
+extern void tlb_init(void);
+
+/*
+ * The kernel is already mapped with linear mapping at kseg_c so there's no
+ * need to map it with a page table. However, head.S also temporarily mapped it
+ * at kseg_4 thus the ksegs are set up again. Also clear the TLB and do various
+ * other paging stuff.
+ */
+void __init
+cris_mmu_init(void)
+{
+ unsigned long mmu_config;
+ unsigned long mmu_kbase_hi;
+ unsigned long mmu_kbase_lo;
+ unsigned short mmu_page_id;
+
+ /*
+ * Make sure the current pgd table points to something sane, even if it
+ * is most probably not used until the next switch_mm.
+ */
+ per_cpu(current_pgd, smp_processor_id()) = init_mm.pgd;
+
+#ifdef CONFIG_SMP
+ {
+ pgd_t **pgd;
+ pgd = (pgd_t**)&per_cpu(current_pgd, smp_processor_id());
+ SUPP_BANK_SEL(1);
+ SUPP_REG_WR(RW_MM_TLB_PGD, pgd);
+ SUPP_BANK_SEL(2);
+ SUPP_REG_WR(RW_MM_TLB_PGD, pgd);
+ }
+#endif
+
+ /* Initialise the TLB. Function found in tlb.c. */
+ tlb_init();
+
+ /* Enable exceptions and initialize the kernel segments. */
+ mmu_config = ( REG_STATE(mmu, rw_mm_cfg, we, on) |
+ REG_STATE(mmu, rw_mm_cfg, acc, on) |
+ REG_STATE(mmu, rw_mm_cfg, ex, on) |
+ REG_STATE(mmu, rw_mm_cfg, inv, on) |
+ REG_STATE(mmu, rw_mm_cfg, seg_f, linear) |
+ REG_STATE(mmu, rw_mm_cfg, seg_e, linear) |
+ REG_STATE(mmu, rw_mm_cfg, seg_d, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_c, linear) |
+ REG_STATE(mmu, rw_mm_cfg, seg_b, linear) |
+#ifndef CONFIG_ETRAXFS_SIM
+ REG_STATE(mmu, rw_mm_cfg, seg_a, page) |
+#else
+ REG_STATE(mmu, rw_mm_cfg, seg_a, linear) |
+#endif
+ REG_STATE(mmu, rw_mm_cfg, seg_9, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_8, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_7, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_6, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_5, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_4, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_3, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_2, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_1, page) |
+ REG_STATE(mmu, rw_mm_cfg, seg_0, page));
+
+ mmu_kbase_hi = ( REG_FIELD(mmu, rw_mm_kbase_hi, base_f, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_e, 0x8) |
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_d, 0x0) |
+#ifndef CONFIG_ETRAXFS_SIM
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_c, 0x4) |
+#else
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_c, 0x0) |
+#endif
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_b, 0xb) |
+#ifndef CONFIG_ETRAXFS_SIM
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_a, 0x0) |
+#else
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_a, 0xa) |
+#endif
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_9, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_hi, base_8, 0x0));
+
+ mmu_kbase_lo = ( REG_FIELD(mmu, rw_mm_kbase_lo, base_7, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_lo, base_6, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_lo, base_5, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_lo, base_4, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_lo, base_3, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_lo, base_2, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_lo, base_1, 0x0) |
+ REG_FIELD(mmu, rw_mm_kbase_lo, base_0, 0x0));
+
+ mmu_page_id = REG_FIELD(mmu, rw_mm_tlb_hi, pid, 0);
+
+ /* Update the instruction MMU. */
+ SUPP_BANK_SEL(BANK_IM);
+ SUPP_REG_WR(RW_MM_CFG, mmu_config);
+ SUPP_REG_WR(RW_MM_KBASE_HI, mmu_kbase_hi);
+ SUPP_REG_WR(RW_MM_KBASE_LO, mmu_kbase_lo);
+ SUPP_REG_WR(RW_MM_TLB_HI, mmu_page_id);
+
+ /* Update the data MMU. */
+ SUPP_BANK_SEL(BANK_DM);
+ SUPP_REG_WR(RW_MM_CFG, mmu_config);
+ SUPP_REG_WR(RW_MM_KBASE_HI, mmu_kbase_hi);
+ SUPP_REG_WR(RW_MM_KBASE_LO, mmu_kbase_lo);
+ SUPP_REG_WR(RW_MM_TLB_HI, mmu_page_id);
+
+ SPEC_REG_WR(SPEC_REG_PID, 0);
+
+ /*
+ * The MMU has been enabled ever since head.S but just to make it
+ * totally obvious enable it here as well.
+ */
+ SUPP_BANK_SEL(BANK_GC);
+ SUPP_REG_WR(RW_GC_CFG, 0xf); /* IMMU, DMMU, ICache, DCache on */
+}
+
+void __init
+paging_init(void)
+{
+ int i;
+ unsigned long zones_size[MAX_NR_ZONES];
+
+ printk("Setting up paging and the MMU.\n");
+
+ /* Clear out the init_mm.pgd that will contain the kernel's mappings. */
+ for(i = 0; i < PTRS_PER_PGD; i++)
+ swapper_pg_dir[i] = __pgd(0);
+
+ cris_mmu_init();
+
+ /*
+ * Initialize the bad page table and bad page to point to a couple of
+ * allocated pages.
+ */
+ empty_zero_page = (unsigned long) alloc_bootmem_pages(PAGE_SIZE);
+ memset((void *) empty_zero_page, 0, PAGE_SIZE);
+
+ /* All pages are DMA'able in Etrax, so put all in the DMA'able zone. */
+ zones_size[0] = ((unsigned long) high_memory - PAGE_OFFSET) >> PAGE_SHIFT;
+
+ for (i = 1; i < MAX_NR_ZONES; i++)
+ zones_size[i] = 0;
+
+ /*
+ * Use free_area_init_node instead of free_area_init, because it is
+ * designed for systems where the DRAM starts at an address
+ * substantially higher than 0, like us (we start at PAGE_OFFSET). This
+ * saves space in the mem_map page array.
+ */
+ free_area_init_node(0, &contig_page_data, zones_size, PAGE_OFFSET >> PAGE_SHIFT, 0);
+
+ mem_map = contig_page_data.node_mem_map;
+}
diff --git a/arch/cris/arch-v32/mm/intmem.c b/arch/cris/arch-v32/mm/intmem.c
new file mode 100644
index 000000000000..41ee7f7997fd
--- /dev/null
+++ b/arch/cris/arch-v32/mm/intmem.c
@@ -0,0 +1,139 @@
+/*
+ * Simple allocator for internal RAM in ETRAX FS
+ *
+ * Copyright (c) 2004 Axis Communications AB.
+ */
+
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <asm/arch/memmap.h>
+
+#define STATUS_FREE 0
+#define STATUS_ALLOCATED 1
+
+struct intmem_allocation {
+ struct list_head entry;
+ unsigned int size;
+ unsigned offset;
+ char status;
+};
+
+
+static struct list_head intmem_allocations;
+static void* intmem_virtual;
+
+static void crisv32_intmem_init(void)
+{
+ static int initiated = 0;
+ if (!initiated) {
+ struct intmem_allocation* alloc =
+ (struct intmem_allocation*)kmalloc(sizeof *alloc, GFP_KERNEL);
+ INIT_LIST_HEAD(&intmem_allocations);
+ intmem_virtual = ioremap(MEM_INTMEM_START, MEM_INTMEM_SIZE);
+ initiated = 1;
+ alloc->size = MEM_INTMEM_SIZE;
+ alloc->offset = 0;
+ alloc->status = STATUS_FREE;
+ list_add_tail(&alloc->entry, &intmem_allocations);
+ }
+}
+
+void* crisv32_intmem_alloc(unsigned size, unsigned align)
+{
+ struct intmem_allocation* allocation;
+ struct intmem_allocation* tmp;
+ void* ret = NULL;
+
+ preempt_disable();
+ crisv32_intmem_init();
+
+ list_for_each_entry_safe(allocation, tmp, &intmem_allocations, entry) {
+ int alignment = allocation->offset % align;
+ alignment = alignment ? align - alignment : alignment;
+
+ if (allocation->status == STATUS_FREE &&
+ allocation->size >= size + alignment) {
+ if (allocation->size > size + alignment) {
+ struct intmem_allocation* alloc =
+ (struct intmem_allocation*)
+ kmalloc(sizeof *alloc, GFP_ATOMIC);
+ alloc->status = STATUS_FREE;
+ alloc->size = allocation->size - size - alignment;
+ alloc->offset = allocation->offset + size;
+ list_add(&alloc->entry, &allocation->entry);
+
+ if (alignment) {
+ struct intmem_allocation* tmp;
+ tmp = (struct intmem_allocation*)
+ kmalloc(sizeof *tmp, GFP_ATOMIC);
+ tmp->offset = allocation->offset;
+ tmp->size = alignment;
+ tmp->status = STATUS_FREE;
+ allocation->offset += alignment;
+ list_add_tail(&tmp->entry, &allocation->entry);
+ }
+ }
+ allocation->status = STATUS_ALLOCATED;
+ allocation->size = size;
+ ret = (void*)((int)intmem_virtual + allocation->offset);
+ }
+ }
+ preempt_enable();
+ return ret;
+}
+
+void crisv32_intmem_free(void* addr)
+{
+ struct intmem_allocation* allocation;
+ struct intmem_allocation* tmp;
+
+ if (addr == NULL)
+ return;
+
+ preempt_disable();
+ crisv32_intmem_init();
+
+ list_for_each_entry_safe(allocation, tmp, &intmem_allocations, entry) {
+ if (allocation->offset == (int)(addr - intmem_virtual)) {
+ struct intmem_allocation* prev =
+ list_entry(allocation->entry.prev,
+ struct intmem_allocation, entry);
+ struct intmem_allocation* next =
+ list_entry(allocation->entry.next,
+ struct intmem_allocation, entry);
+
+ allocation->status = STATUS_FREE;
+ /* Join with prev and/or next if also free */
+ if (prev->status == STATUS_FREE) {
+ prev->size += allocation->size;
+ list_del(&allocation->entry);
+ kfree(allocation);
+ allocation = prev;
+ }
+ if (next->status == STATUS_FREE) {
+ allocation->size += next->size;
+ list_del(&next->entry);
+ kfree(next);
+ }
+ preempt_enable();
+ return;
+ }
+ }
+ preempt_enable();
+}
+
+void* crisv32_intmem_phys_to_virt(unsigned long addr)
+{
+ return (void*)(addr - MEM_INTMEM_START+
+ (unsigned long)intmem_virtual);
+}
+
+unsigned long crisv32_intmem_virt_to_phys(void* addr)
+{
+ return (unsigned long)((unsigned long )addr -
+ (unsigned long)intmem_virtual + MEM_INTMEM_START);
+}
+
+
+
diff --git a/arch/cris/arch-v32/mm/mmu.S b/arch/cris/arch-v32/mm/mmu.S
new file mode 100644
index 000000000000..27b70e5006af
--- /dev/null
+++ b/arch/cris/arch-v32/mm/mmu.S
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2003 Axis Communications AB
+ *
+ * Authors: Mikael Starvik (starvik@axis.com)
+ *
+ * Code for the fault low-level handling routines.
+ *
+ */
+
+#include <asm/page.h>
+#include <asm/pgtable.h>
+
+; Save all register. Must save in same order as struct pt_regs.
+.macro SAVE_ALL
+ subq 12, $sp
+ move $erp, [$sp]
+ subq 4, $sp
+ move $srp, [$sp]
+ subq 4, $sp
+ move $ccs, [$sp]
+ subq 4, $sp
+ move $spc, [$sp]
+ subq 4, $sp
+ move $mof, [$sp]
+ subq 4, $sp
+ move $srs, [$sp]
+ subq 4, $sp
+ move.d $acr, [$sp]
+ subq 14*4, $sp
+ movem $r13, [$sp]
+ subq 4, $sp
+ move.d $r10, [$sp]
+.endm
+
+; Bus fault handler. Extracts relevant information and calls mm subsystem
+; to handle the fault.
+.macro MMU_BUS_FAULT_HANDLER handler, mmu, we, ex
+ .globl \handler
+\handler:
+ SAVE_ALL
+ move \mmu, $srs ; Select MMU support register bank
+ move.d $sp, $r11 ; regs
+ moveq 1, $r12 ; protection fault
+ moveq \we, $r13 ; write exception?
+ orq \ex << 1, $r13 ; execute?
+ move $s3, $r10 ; rw_mm_cause
+ and.d ~8191, $r10 ; Get faulting page start address
+
+ jsr do_page_fault
+ nop
+ ba ret_from_intr
+ nop
+.endm
+
+; Refill handler. Three cases may occur:
+; 1. PMD and PTE exists in mm subsystem but not in TLB
+; 2. PMD exists but not PTE
+; 3. PMD doesn't exist
+; The code below handles case 1 and calls the mm subsystem for case 2 and 3.
+; Do not touch this code without very good reasons and extensive testing.
+; Note that the code is optimized to minimize stalls (makes the code harder
+; to read).
+;
+; Each page is 8 KB. Each PMD holds 8192/4 PTEs (each PTE is 4 bytes) so each
+; PMD holds 16 MB of virtual memory.
+; Bits 0-12 : Offset within a page
+; Bits 13-23 : PTE offset within a PMD
+; Bits 24-31 : PMD offset within the PGD
+
+.macro MMU_REFILL_HANDLER handler, mmu
+ .globl \handler
+\handler:
+ subq 4, $sp
+; (The pipeline stalls for one cycle; $sp used as address in the next cycle.)
+ move $srs, [$sp]
+ subq 4, $sp
+ move \mmu, $srs ; Select MMU support register bank
+ move.d $acr, [$sp]
+ subq 4, $sp
+ move.d $r0, [$sp]
+#ifdef CONFIG_SMP
+ move $s7, $acr ; PGD
+#else
+ move.d per_cpu__current_pgd, $acr ; PGD
+#endif
+ ; Look up PMD in PGD
+ move $s3, $r0 ; rw_mm_cause
+ lsrq 24, $r0 ; Get PMD index into PGD (bit 24-31)
+ move.d [$acr], $acr ; PGD for the current process
+ addi $r0.d, $acr, $acr
+ move $s3, $r0 ; rw_mm_cause
+ move.d [$acr], $acr ; Get PMD
+ beq 1f
+ ; Look up PTE in PMD
+ lsrq PAGE_SHIFT, $r0
+ and.w PAGE_MASK, $acr ; Remove PMD flags
+ and.d 0x7ff, $r0 ; Get PTE index into PMD (bit 13-23)
+ addi $r0.d, $acr, $acr
+ move.d [$acr], $acr ; Get PTE
+ beq 2f
+ move.d [$sp+], $r0 ; Pop r0 in delayslot
+ ; Store in TLB
+ move $acr, $s5
+ ; Return
+ move.d [$sp+], $acr
+ move [$sp], $srs
+ addq 4, $sp
+ rete
+ rfe
+1: ; PMD missing, let the mm subsystem fix it up.
+ move.d [$sp+], $r0 ; Pop r0
+2: ; PTE missing, let the mm subsystem fix it up.
+ move.d [$sp+], $acr
+ move [$sp], $srs
+ addq 4, $sp
+ SAVE_ALL
+ move \mmu, $srs
+ move.d $sp, $r11 ; regs
+ clear.d $r12 ; Not a protection fault
+ move.w PAGE_MASK, $acr
+ move $s3, $r10 ; rw_mm_cause
+ btstq 9, $r10 ; Check if write access
+ smi $r13
+ and.w PAGE_MASK, $r10 ; Get VPN (virtual address)
+ jsr do_page_fault
+ and.w $acr, $r10
+ ; Return
+ ba ret_from_intr
+ nop
+.endm
+
+ ; This is the MMU bus fault handlers.
+
+MMU_REFILL_HANDLER i_mmu_refill, 1
+MMU_BUS_FAULT_HANDLER i_mmu_invalid, 1, 0, 0
+MMU_BUS_FAULT_HANDLER i_mmu_access, 1, 0, 0
+MMU_BUS_FAULT_HANDLER i_mmu_execute, 1, 0, 1
+MMU_REFILL_HANDLER d_mmu_refill, 2
+MMU_BUS_FAULT_HANDLER d_mmu_invalid, 2, 0, 0
+MMU_BUS_FAULT_HANDLER d_mmu_access, 2, 0, 0
+MMU_BUS_FAULT_HANDLER d_mmu_write, 2, 1, 0
diff --git a/arch/cris/arch-v32/mm/tlb.c b/arch/cris/arch-v32/mm/tlb.c
new file mode 100644
index 000000000000..8233406798d3
--- /dev/null
+++ b/arch/cris/arch-v32/mm/tlb.c
@@ -0,0 +1,208 @@
+/*
+ * Low level TLB handling.
+ *
+ * Copyright (C) 2000-2003, Axis Communications AB.
+ *
+ * Authors: Bjorn Wesen <bjornw@axis.com>
+ * Tobias Anderberg <tobiasa@axis.com>, CRISv32 port.
+ */
+
+#include <asm/tlb.h>
+#include <asm/mmu_context.h>
+#include <asm/arch/hwregs/asm/mmu_defs_asm.h>
+#include <asm/arch/hwregs/supp_reg.h>
+
+#define UPDATE_TLB_SEL_IDX(val) \
+do { \
+ unsigned long tlb_sel; \
+ \
+ tlb_sel = REG_FIELD(mmu, rw_mm_tlb_sel, idx, val); \
+ SUPP_REG_WR(RW_MM_TLB_SEL, tlb_sel); \
+} while(0)
+
+#define UPDATE_TLB_HILO(tlb_hi, tlb_lo) \
+do { \
+ SUPP_REG_WR(RW_MM_TLB_HI, tlb_hi); \
+ SUPP_REG_WR(RW_MM_TLB_LO, tlb_lo); \
+} while(0)
+
+/*
+ * The TLB can host up to 256 different mm contexts at the same time. The running
+ * context is found in the PID register. Each TLB entry contains a page_id that
+ * has to match the PID register to give a hit. page_id_map keeps track of which
+ * mm's is assigned to which page_id's, making sure it's known when to
+ * invalidate TLB entries.
+ *
+ * The last page_id is never running, it is used as an invalid page_id so that
+ * it's possible to make TLB entries that will nerver match.
+ *
+ * Note; the flushes needs to be atomic otherwise an interrupt hander that uses
+ * vmalloc'ed memory might cause a TLB load in the middle of a flush.
+ */
+
+/* Flush all TLB entries. */
+void
+__flush_tlb_all(void)
+{
+ int i;
+ int mmu;
+ unsigned long flags;
+ unsigned long mmu_tlb_hi;
+ unsigned long mmu_tlb_sel;
+
+ /*
+ * Mask with 0xf so similar TLB entries aren't written in the same 4-way
+ * entry group.
+ */
+ local_save_flags(flags);
+ local_irq_disable();
+
+ for (mmu = 1; mmu <= 2; mmu++) {
+ SUPP_BANK_SEL(mmu); /* Select the MMU */
+ for (i = 0; i < NUM_TLB_ENTRIES; i++) {
+ /* Store invalid entry */
+ mmu_tlb_sel = REG_FIELD(mmu, rw_mm_tlb_sel, idx, i);
+
+ mmu_tlb_hi = (REG_FIELD(mmu, rw_mm_tlb_hi, pid, INVALID_PAGEID)
+ | REG_FIELD(mmu, rw_mm_tlb_hi, vpn, i & 0xf));
+
+ SUPP_REG_WR(RW_MM_TLB_SEL, mmu_tlb_sel);
+ SUPP_REG_WR(RW_MM_TLB_HI, mmu_tlb_hi);
+ SUPP_REG_WR(RW_MM_TLB_LO, 0);
+ }
+ }
+
+ local_irq_restore(flags);
+}
+
+/* Flush an entire user address space. */
+void
+__flush_tlb_mm(struct mm_struct *mm)
+{
+ int i;
+ int mmu;
+ unsigned long flags;
+ unsigned long page_id;
+ unsigned long tlb_hi;
+ unsigned long mmu_tlb_hi;
+
+ page_id = mm->context.page_id;
+
+ if (page_id == NO_CONTEXT)
+ return;
+
+ /* Mark the TLB entries that match the page_id as invalid. */
+ local_save_flags(flags);
+ local_irq_disable();
+
+ for (mmu = 1; mmu <= 2; mmu++) {
+ SUPP_BANK_SEL(mmu);
+ for (i = 0; i < NUM_TLB_ENTRIES; i++) {
+ UPDATE_TLB_SEL_IDX(i);
+
+ /* Get the page_id */
+ SUPP_REG_RD(RW_MM_TLB_HI, tlb_hi);
+
+ /* Check if the page_id match. */
+ if ((tlb_hi & 0xff) == page_id) {
+ mmu_tlb_hi = (REG_FIELD(mmu, rw_mm_tlb_hi, pid,
+ INVALID_PAGEID)
+ | REG_FIELD(mmu, rw_mm_tlb_hi, vpn,
+ i & 0xf));
+
+ UPDATE_TLB_HILO(mmu_tlb_hi, 0);
+ }
+ }
+ }
+
+ local_irq_restore(flags);
+}
+
+/* Invalidate a single page. */
+void
+__flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)
+{
+ int i;
+ int mmu;
+ unsigned long page_id;
+ unsigned long flags;
+ unsigned long tlb_hi;
+ unsigned long mmu_tlb_hi;
+
+ page_id = vma->vm_mm->context.page_id;
+
+ if (page_id == NO_CONTEXT)
+ return;
+
+ addr &= PAGE_MASK;
+
+ /*
+ * Invalidate those TLB entries that match both the mm context and the
+ * requested virtual address.
+ */
+ local_save_flags(flags);
+ local_irq_disable();
+
+ for (mmu = 1; mmu <= 2; mmu++) {
+ SUPP_BANK_SEL(mmu);
+ for (i = 0; i < NUM_TLB_ENTRIES; i++) {
+ UPDATE_TLB_SEL_IDX(i);
+ SUPP_REG_RD(RW_MM_TLB_HI, tlb_hi);
+
+ /* Check if page_id and address matches */
+ if (((tlb_hi & 0xff) == page_id) &&
+ ((tlb_hi & PAGE_MASK) == addr)) {
+ mmu_tlb_hi = REG_FIELD(mmu, rw_mm_tlb_hi, pid,
+ INVALID_PAGEID) | addr;
+
+ UPDATE_TLB_HILO(mmu_tlb_hi, 0);
+ }
+ }
+ }
+
+ local_irq_restore(flags);
+}
+
+/*
+ * Initialize the context related info for a new mm_struct
+ * instance.
+ */
+
+int
+init_new_context(struct task_struct *tsk, struct mm_struct *mm)
+{
+ mm->context.page_id = NO_CONTEXT;
+ return 0;
+}
+
+/* Called in schedule() just before actually doing the switch_to. */
+void
+switch_mm(struct mm_struct *prev, struct mm_struct *next,
+ struct task_struct *tsk)
+{
+ int cpu = smp_processor_id();
+
+ /* Make sure there is a MMU context. */
+ spin_lock(&next->page_table_lock);
+ get_mmu_context(next);
+ cpu_set(cpu, next->cpu_vm_mask);
+ spin_unlock(&next->page_table_lock);
+
+ /*
+ * Remember the pgd for the fault handlers. Keep a seperate copy of it
+ * because current and active_mm might be invalid at points where
+ * there's still a need to derefer the pgd.
+ */
+ per_cpu(current_pgd, cpu) = next->pgd;
+
+ /* Switch context in the MMU. */
+ if (tsk && tsk->thread_info)
+ {
+ SPEC_REG_WR(SPEC_REG_PID, next->context.page_id | tsk->thread_info->tls);
+ }
+ else
+ {
+ SPEC_REG_WR(SPEC_REG_PID, next->context.page_id);
+ }
+}
+
diff --git a/arch/cris/arch-v32/output_arch.ld b/arch/cris/arch-v32/output_arch.ld
new file mode 100644
index 000000000000..d60a57db0ec2
--- /dev/null
+++ b/arch/cris/arch-v32/output_arch.ld
@@ -0,0 +1,2 @@
+/* At the time of this writing, there's no equivalent ld option. */
+OUTPUT_ARCH (crisv32)
diff --git a/arch/cris/arch-v32/vmlinux.lds.S b/arch/cris/arch-v32/vmlinux.lds.S
new file mode 100644
index 000000000000..adb94605d92a
--- /dev/null
+++ b/arch/cris/arch-v32/vmlinux.lds.S
@@ -0,0 +1,134 @@
+/* ld script to make the Linux/CRIS kernel
+ * Authors: Bjorn Wesen (bjornw@axis.com)
+ *
+ * It is VERY DANGEROUS to fiddle around with the symbols in this
+ * script. It is for example quite vital that all generated sections
+ * that are used are actually named here, otherwise the linker will
+ * put them at the end, where the init stuff is which is FREED after
+ * the kernel has booted.
+ */
+
+#include <linux/config.h>
+#include <asm-generic/vmlinux.lds.h>
+
+jiffies = jiffies_64;
+SECTIONS
+{
+ . = DRAM_VIRTUAL_BASE;
+ dram_start = .;
+ ebp_start = .;
+
+ /* The boot section is only necessary until the VCS top level testbench */
+ /* includes both flash and DRAM. */
+ .boot : { *(.boot) }
+
+ . = DRAM_VIRTUAL_BASE + 0x4000; /* See head.S and pages reserved at the start. */
+
+ _text = .; /* Text and read-only data. */
+ text_start = .; /* Lots of aliases. */
+ _stext = .;
+ __stext = .;
+ .text : {
+ *(.text)
+ SCHED_TEXT
+ LOCK_TEXT
+ *(.fixup)
+ *(.text.__*)
+ }
+
+ _etext = . ; /* End of text section. */
+ __etext = .;
+
+ . = ALIGN(4); /* Exception table. */
+ __start___ex_table = .;
+ __ex_table : { *(__ex_table) }
+ __stop___ex_table = .;
+
+ RODATA
+
+ . = ALIGN (4);
+ ___data_start = . ;
+ __Sdata = . ;
+ .data : { /* Data */
+ *(.data)
+ }
+ __edata = . ; /* End of data section. */
+ _edata = . ;
+
+ . = ALIGN(8192); /* init_task and stack, must be aligned. */
+ .data.init_task : { *(.data.init_task) }
+
+ . = ALIGN(8192); /* Init code and data. */
+ __init_begin = .;
+ .init.text : {
+ _sinittext = .;
+ *(.init.text)
+ _einittext = .;
+ }
+ .init.data : { *(.init.data) }
+ . = ALIGN(16);
+ __setup_start = .;
+ .init.setup : { *(.init.setup) }
+ __setup_end = .;
+ __start___param = .;
+ __param : { *(__param) }
+ __stop___param = .;
+ .initcall.init : {
+ __initcall_start = .;
+ *(.initcall1.init);
+ *(.initcall2.init);
+ *(.initcall3.init);
+ *(.initcall4.init);
+ *(.initcall5.init);
+ *(.initcall6.init);
+ *(.initcall7.init);
+ __initcall_end = .;
+ }
+
+ .con_initcall.init : {
+ __con_initcall_start = .;
+ *(.con_initcall.init)
+ __con_initcall_end = .;
+ }
+ SECURITY_INIT
+
+ __per_cpu_start = .;
+ .data.percpu : { *(.data.percpu) }
+ __per_cpu_end = .;
+
+ .init.ramfs : {
+ __initramfs_start = .;
+ *(.init.ramfs)
+ __initramfs_end = .;
+ /*
+ * We fill to the next page, so we can discard all init
+ * pages without needing to consider what payload might be
+ * appended to the kernel image.
+ */
+ FILL (0);
+ . = ALIGN (8192);
+ }
+
+ __vmlinux_end = .; /* Last address of the physical file. */
+ __init_end = .;
+
+ __data_end = . ; /* Move to _edata? */
+ __bss_start = .; /* BSS. */
+ .bss : {
+ *(COMMON)
+ *(.bss)
+ }
+
+ . = ALIGN (0x20);
+ _end = .;
+ __end = .;
+
+ /* Sections to be discarded */
+ /DISCARD/ : {
+ *(.text.exit)
+ *(.data.exit)
+ *(.exitcall.exit)
+ }
+
+ dram_end = dram_start + CONFIG_ETRAX_DRAM_SIZE*1024*1024;
+}
OpenPOWER on IntegriCloud