diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2001-03-09 20:33:35 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2001-03-09 20:33:35 +0000 |
commit | 116674acc97ba75a720329996877077d988443a2 (patch) | |
tree | 6a3f2ff0b612ae2ee8a3f3509370c9e6333a53b3 /arch/cris | |
parent | 71118c319fcae4a138f16e35b4f7e0a6d53ce2ca (diff) |
Merge with Linux 2.4.2.
Diffstat (limited to 'arch/cris')
48 files changed, 15689 insertions, 0 deletions
diff --git a/arch/cris/Makefile b/arch/cris/Makefile new file mode 100644 index 000000000..b63a44711 --- /dev/null +++ b/arch/cris/Makefile @@ -0,0 +1,96 @@ +# $Id: Makefile,v 1.11 2000/11/27 17:58:30 bjornw Exp $ +# cris/Makefile +# +# This file is included by the global makefile so that you can add your own +# architecture-specific flags and dependencies. Remember to do have actions +# for "archclean" and "archdep" for cleaning up and making dependencies for +# this architecture +# +# This file is subject to the terms and conditions of the GNU General Public +# License. See the file "COPYING" in the main directory of this archive +# for more details. + +LD_SCRIPT=$(TOPDIR)/arch/cris/cris.ld + +# A bug in ld prevents us from having a (constant-value) symbol in a +# "ORIGIN =" or "LENGTH =" expression. We fix that by generating a +# linker file with the symbolic part of those expressions evaluated. +# Unfortunately, there is trouble making vmlinux depend on anything we +# generate here, so we *always* regenerate the final linker script and +# replace the LD macro to get what we want. Thankfully(?) vmlinux is +# always rebuilt (due to calling make recursively and not knowing if +# anything was rebuilt). +# The shell script to build in some kind of dependency is really not +# necessary for reasons of speed. It's there because always +# regenerating stuff (even for incremental linking of subsystems!) is +# even more nauseating. +LD = if [ ! -e $(LD_SCRIPT).tmp -o $(LD_SCRIPT) -nt $(LD_SCRIPT).tmp ]; then \ + sed -e s/@ETRAX_DRAM_BASE@/0x$(ETRAX_DRAM_BASE)/ \ + -e s/@ETRAX_DRAM_SIZE_M@/$(ETRAX_DRAM_SIZE)/ \ + < $(LD_SCRIPT) > $(LD_SCRIPT).tmp; \ + else true; \ + fi && $(CROSS_COMPILE)ld -mcriself + +LINKFLAGS =-qmagic -mcriself -T $(LD_SCRIPT).tmp + +# objcopy is used to make binary images from the resulting linked file + +OBJCOPY := $(CROSS_COMPILE)objcopy -O binary -R .note -R .comment -S + +# normally, gcc on a linux box adds __linux__ but we do it "manually" +# gcc-cris defaults to a.out, we need ELF, so -melf + +CFLAGS := $(CFLAGS) -march=v10 -fno-strict-aliasing -pipe -D__linux__ + +ifdef CONFIG_KGDB +CFLAGS := $(subst -fomit-frame-pointer,,$(CFLAGS)) -g +CFLAGS += -fno-omit-frame-pointer +endif + +HEAD := arch/cris/kernel/head.o + +SUBDIRS += arch/cris/kernel arch/cris/mm arch/cris/lib arch/cris/drivers +CORE_FILES += arch/cris/kernel/kernel.o arch/cris/mm/mm.o arch/cris/drivers/drivers.o +LIBGCC = $(shell $(CC) $(CFLAGS) -print-file-name=libgcc.a) +LIBS := $(TOPDIR)/arch/cris/lib/lib.a $(LIBS) $(TOPDIR)/arch/cris/lib/lib.a $(LIBGCC) + +arch/cris/kernel: dummy + $(MAKE) linuxsubdirs SUBDIRS=arch/cris/kernel + +arch/cris/mm: dummy + $(MAKE) linuxsubdirs SUBDIRS=arch/cris/mm + +MAKEBOOT = $(MAKE) -C arch/$(ARCH)/boot + +vmlinux.bin: vmlinux + $(OBJCOPY) vmlinux vmlinux.bin + +timage: vmlinux.bin + cat vmlinux.bin cramfs.img >timage + +simimage: timage + cp vmlinux.bin simvmlinux.bin + +# the following will remake timage without compiling the kernel +# it does of course require that all object files exist... + +cramfs: +## cramfs - Creates a cramfs image + mkcramfs -p 8192 root cramfs.img + cat vmlinux.bin cramfs.img >timage + +zImage: vmlinux +## zImage - Compressed kernel (gzip) + @$(MAKEBOOT) zImage + +compressed: zImage + +archclean: + @$(MAKEBOOT) clean + rm -f timage vmlinux.bin cramfs.img + rm -rf $(LD_SCRIPT).tmp + +archmrproper: + +archdep: + @$(MAKEBOOT) dep diff --git a/arch/cris/README.mm b/arch/cris/README.mm new file mode 100644 index 000000000..6de4e7077 --- /dev/null +++ b/arch/cris/README.mm @@ -0,0 +1,241 @@ +Memory management for CRIS/MMU +------------------------------ +HISTORY: + +$Log: README.mm,v $ +Revision 1.1 2000/07/10 16:25:21 bjornw +Initial revision + +Revision 1.4 2000/01/17 02:31:59 bjornw +Added discussion of paging and VM. + +Revision 1.3 1999/12/03 16:43:23 hp +Blurb about that the 3.5G-limitation is not a MMU limitation + +Revision 1.2 1999/12/03 16:04:21 hp +Picky comment about not mapping the first page + +Revision 1.1 1999/12/03 15:41:30 bjornw +First version of CRIS/MMU memory layout specification. + + + + + +------------------------------ + +See the ETRAX-NG HSDD for reference. + +We use the page-size of 8 kbytes, as opposed to the i386 page-size of 4 kbytes. + +The MMU can, apart from the normal mapping of pages, also do a top-level +segmentation of the kernel memory space. We use this feature to avoid having +to use page-tables to map the physical memory into the kernel's address +space. We also use it to keep the user-mode virtual mapping in the same +map during kernel-mode, so that the kernel easily can access the corresponding +user-mode process' data. + +As a comparision, the Linux/i386 2.0 puts the kernel and physical RAM at +address 0, overlapping with the user-mode virtual space, so that descriptor +registers are needed for each memory access to specify which MMU space to +map through. That changed in 2.2, putting the kernel/physical RAM at +0xc0000000, to co-exist with the user-mode mapping. We will do something +quite similar, but with the additional complexity of having to map the +internal chip I/O registers and the flash memory area (including SRAM +and peripherial chip-selets). + +The kernel-mode segmentation map: + + ------------------------ ------------------------ +FFFFFFFF| | => cached | | + | kernel seg_f | flash | | +F0000000|______________________| | | +EFFFFFFF| | => uncached | | + | kernel seg_e | flash | | +E0000000|______________________| | DRAM | +DFFFFFFF| | paged to any | Un-cached | + | kernel seg_d | =======> | | +D0000000|______________________| | | +CFFFFFFF| | | | + | kernel seg_c |==\ | | +C0000000|______________________| \ |______________________| +BFFFFFFF| | uncached | | + | kernel seg_b |=====\=========>| Registers | +B0000000|______________________| \c |______________________| +AFFFFFFF| | \a | | + | | \c | FLASH/SRAM/Peripheral| + | | \h |______________________| + | | \e | | + | | \d | | + | kernel seg_0 - seg_a | \==>| DRAM | + | | | Cached | + | | paged to any | | + | | =======> |______________________| + | | | | + | | | Illegal | + | | |______________________| + | | | | + | | | FLASH/SRAM/Peripheral| +00000000|______________________| |______________________| + +In user-mode it looks the same except that only the space 0-AFFFFFFF is +available. Therefore, in this model, the virtual address space per process +is limited to 0xb0000000 bytes (minus 8192 bytes, since the first page, +0..8191, is never mapped, in order to trap NULL references). + +It also means that the total physical RAM that can be mapped is 256 MB +(kseg_c above). More RAM can be mapped by choosing a different segmentation +and shrinking the user-mode memory space. + +The MMU can map all 4 GB in user mode, but doing that would mean that a +few extra instructions would be needed for each access to user mode +memory. + +The kernel needs access to both cached and uncached flash. Uncached is +necessary because of the special write/erase sequences. Also, the +peripherial chip-selects are decoded from that region. + +The kernel also needs its own virtual memory space. That is kseg_d. It +is used by the vmalloc() kernel function to allocate virtual contiguous +chunks of memory not possible using the normal kmalloc physical RAM +allocator. + +The setting of the actual MMU control registers to use this layout would +be something like this: + +R_MMU_KSEG = ( ( seg_f, seg ) | // Flash cached + ( seg_e, seg ) | // Flash uncached + ( seg_d, page ) | // kernel vmalloc area + ( seg_c, seg ) | // kernel linear segment + ( seg_b, seg ) | // kernel linear segment + ( seg_a, page ) | + ( seg_9, page ) | + ( seg_8, page ) | + ( seg_7, page ) | + ( seg_6, page ) | + ( seg_5, page ) | + ( seg_4, page ) | + ( seg_3, page ) | + ( seg_2, page ) | + ( seg_1, page ) | + ( seg_0, page ) ); + +R_MMU_KBASE_HI = ( ( base_f, 0x0 ) | // flash/sram/periph cached + ( base_e, 0x8 ) | // flash/sram/periph uncached + ( base_d, 0x0 ) | // don't care + ( base_c, 0x4 ) | // physical RAM cached area + ( base_b, 0xb ) | // uncached on-chip registers + ( base_a, 0x0 ) | // don't care + ( base_9, 0x0 ) | // don't care + ( base_8, 0x0 ) ); // don't care + +R_MMU_KBASE_LO = ( ( base_7, 0x0 ) | // don't care + ( base_6, 0x0 ) | // don't care + ( base_5, 0x0 ) | // don't care + ( base_4, 0x0 ) | // don't care + ( base_3, 0x0 ) | // don't care + ( base_2, 0x0 ) | // don't care + ( base_1, 0x0 ) | // don't care + ( base_0, 0x0 ) ); // don't care + +NOTE: while setting up the MMU, we run in a non-mapped mode in the DRAM (0x40 +segment) and need to setup the seg_4 to a unity mapping, so that we don't get +a fault before we have had time to jump into the real kernel segment (0xc0). This +is done in head.S temporarily, but fixed by the kernel later in paging_init. + + +Paging - PTE's, PMD's and PGD's +------------------------------- + +[ References: asm/pgtable.h, asm/page.h, asm/mmu.h ] + +The paging mechanism uses virtual addresses to split a process memory-space into +pages, a page being the smallest unit that can be freely remapped in memory. On +Linux/CRIS, a page is 8192 bytes (for technical reasons not equal to 4096 as in +most other 32-bit architectures). It would be inefficient to let a virtual memory +mapping be controlled by a long table of page mappings, so it is broken down into +a 2-level structure with a Page Directory containing pointers to Page Tables which +each have maps of up to 2048 pages (8192 / sizeof(void *)). Linux can actually +handle 3-level structures as well, with a Page Middle Directory in between, but +in many cases, this is folded into a two-level structure by excluding the Middle +Directory. + +We'll take a look at how an address is translated while we discuss how it's handled +in the Linux kernel. + +The example address is 0xd004000c; in binary this is: + +31 23 15 7 0 +11010000 00000100 00000000 00001100 + +|______| |__________||____________| + PGD PTE page offset + +Given the top-level Page Directory, the offset in that directory is calculated +using the upper 8 bits: + +extern inline pgd_t * pgd_offset(struct mm_struct * mm, unsigned long address) +{ + return mm->pgd + (address >> PGDIR_SHIFT); +} + +PGDIR_SHIFT is the log2 of the amount of memory an entry in the PGD can map; in our +case it is 24, corresponding to 16 MB. This means that each entry in the PGD +corresponds to 16 MB of virtual memory. + +The pgd_t from our example will therefore be the 208'th (0xd0) entry in mm->pgd. + +Since the Middle Directory does not exist, it is a unity mapping: + +extern inline pmd_t * pmd_offset(pgd_t * dir, unsigned long address) +{ + return (pmd_t *) dir; +} + +The Page Table provides the final lookup by using bits 13 to 23 as index: + +extern inline pte_t * pte_offset(pmd_t * dir, unsigned long address) +{ + return (pte_t *) pmd_page(*dir) + ((address >> PAGE_SHIFT) & + (PTRS_PER_PTE - 1)); +} + +PAGE_SHIFT is the log2 of the size of a page; 13 in our case. PTRS_PER_PTE is +the number of pointers that fit in a Page Table and is used to mask off the +PGD-part of the address. + +The so-far unused bits 0 to 12 are used to index inside a page linearily. + +The VM system +------------- + +The kernels own page-directory is the swapper_pg_dir, cleared in paging_init, +and contains the kernels virtual mappings (the kernel itself is not paged - it +is mapped linearily using kseg_c as described above). Architectures without +kernel segments like the i386, need to setup swapper_pg_dir directly in head.S +to map the kernel itself. swapper_pg_dir is pointed to by init_mm.pgd as the +init-task's PGD. + +To see what support functions are used to setup a page-table, let's look at the +kernel's internal paged memory system, vmalloc/vfree. + +void * vmalloc(unsigned long size) + +The vmalloc-system keeps a paged segment in kernel-space at 0xd0000000. What +happens first is that a virtual address chunk is allocated to the request using +get_vm_area(size). After that, physical RAM pages are allocated and put into +the kernel's page-table using alloc_area_pages(addr, size). + +static int alloc_area_pages(unsigned long address, unsigned long size) + +First the PGD entry is found using init_mm.pgd. This is passed to +alloc_area_pmd (remember the 3->2 folding). It uses pte_alloc_kernel to +check if the PGD entry points anywhere - if not, a page table page is +allocated and the PGD entry updated. Then the alloc_area_pte function is +used just like alloc_area_pmd to check which page table entry is desired, +and a physical page is allocated and the table entry updated. All of this +is repeated at the top-level until the entire address range specified has +been mapped. + + + diff --git a/arch/cris/boot/Makefile b/arch/cris/boot/Makefile new file mode 100644 index 000000000..77b59012e --- /dev/null +++ b/arch/cris/boot/Makefile @@ -0,0 +1,14 @@ +# +# arch/cris/boot/Makefile +# + +zImage: compressed/vmlinuz + +compressed/vmlinuz: $(TOPDIR)/vmlinux + @$(MAKE) -C compressed vmlinuz + +dep: + +clean: + rm -f zImage tools/build compressed/vmlinux.out + @$(MAKE) -C compressed clean diff --git a/arch/cris/boot/compressed/Makefile b/arch/cris/boot/compressed/Makefile new file mode 100644 index 000000000..fbf2bc36b --- /dev/null +++ b/arch/cris/boot/compressed/Makefile @@ -0,0 +1,37 @@ +# +# linux/arch/etrax100/boot/compressed/Makefile +# +# create a compressed vmlinux image from the original vmlinux files and romfs +# + +CC = gcc-cris -melf -I $(TOPDIR)/include +CFLAGS = -O2 +LD = ld-cris +OBJCOPY = objcopy-cris + +OBJECTS = head.o misc.o + +# files to compress +SYSTEM = $(TOPDIR)/vmlinux.bin + +all: vmlinuz + +vmlinuz: piggy.img $(OBJECTS) + $(LD) -mcriself -T decompress.ld -o decompress.o $(OBJECTS) + $(OBJCOPY) -O binary --remove-section=.bss decompress.o decompress.bin +# save it for mkprod in the topdir. + cp decompress.bin $(TOPDIR) + cat decompress.bin piggy.img $(TOPDIR)/cramfs.img > vmlinuz + rm -f piggy.img + +head.o: head.S + $(CC) -D__ASSEMBLY__ -traditional -c head.S -o head.o + +# gzip the kernel image + +piggy.img: $(SYSTEM) + cat $(SYSTEM) | gzip -f -9 > piggy.img + +clean: + rm -f piggy.img vmlinuz vmlinuz.o + diff --git a/arch/cris/boot/compressed/README b/arch/cris/boot/compressed/README new file mode 100644 index 000000000..83e1f1e51 --- /dev/null +++ b/arch/cris/boot/compressed/README @@ -0,0 +1,25 @@ +Creation of the self-extracting compressed kernel image (vmlinuz) +----------------------------------------------------------------- +$Id: README,v 1.1 2000/11/22 17:20:46 bjornw 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/boot/compressed/decompress.ld b/arch/cris/boot/compressed/decompress.ld new file mode 100644 index 000000000..8cdef6a60 --- /dev/null +++ b/arch/cris/boot/compressed/decompress.ld @@ -0,0 +1,26 @@ +MEMORY + { + dram : ORIGIN = 0x40700000, + LENGTH = 0x00100000 + } + +SECTIONS +{ + .text : + { + _stext = . ; + *(.text) + *(.rodata) + _etext = . ; + } > dram + .data : + { + *(.data) + _edata = . ; + } > dram + .bss : + { + *(.bss) + _end = ALIGN( 0x10 ) ; + } > dram +} diff --git a/arch/cris/boot/compressed/head.S b/arch/cris/boot/compressed/head.S new file mode 100644 index 000000000..97243ee13 --- /dev/null +++ b/arch/cris/boot/compressed/head.S @@ -0,0 +1,100 @@ +/* + * arch/etrax100/boot/compressed/head.S + * + * Copyright (C) 1999 Axis Communications AB + * + * Code that sets up the DRAM registers, calls the + * decompressor to unpack the piggybacked kernel, and jumps. + * + */ + +#include <linux/config.h> +#define ASSEMBLER_MACROS_ONLY +#include <asm/sv_addr_ag.h> + + ;; Exported symbols + + .globl _input_data + + + .text + + nop + di + +#ifndef CONFIG_SVINTO_SIM + + ;; We need to setup the bus registers before we start using the DRAM + + move.d DEF_R_WAITSTATES, r0 + move.d r0, [R_WAITSTATES] + + move.d DEF_R_BUS_CONFIG, r0 + move.d r0, [R_BUS_CONFIG] + + move.d DEF_R_DRAM_CONFIG, r0 + move.d r0, [R_DRAM_CONFIG] + + move.d DEF_R_DRAM_TIMING, r0 + move.d r0, [R_DRAM_TIMING] + +#endif + + ;; Setup the stack to a suitably high address. + ;; We assume 8 MB is the minimum DRAM in an eLinux + ;; product 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 - basse) as offset to the current PC. + +basse: move.d pc, r5 + and.d 0x7fffffff, r5 ; strip any non-cache bit + subq 2, r5 ; compensate for the move.d pc instr + move.d r5, r0 ; save for later - flash address of 'basse' + add.d _edata, r5 + sub.d basse, r5 ; r5 = flash address of '_edata' + + ;; Copy text+data to DRAM + + move.d basse, r1 ; destination + move.d _edata, r2 ; end destination +1: move.w [r0+], r3 + move.w r3, [r1+] + cmp.d r2, r1 + bcs 1b + nop + + move.d r5, [_input_data] ; 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 + + ;; Do the decompression and save compressed size in _inptr + + jsr _decompress_kernel + + ;; Put start address of cramfs in r9 so the kernel can use it + ;; when mounting from flash + + move.d [_input_data], r9 ; flash address of compressed kernel + add.d [_inptr], r9 ; size of compressed kernel + + ;; Enter the decompressed kernel + + jump 0x40004000 ; kernel is linked to this address + + .data + +_input_data: + .dword 0 ; used by the decompressor diff --git a/arch/cris/boot/compressed/misc.c b/arch/cris/boot/compressed/misc.c new file mode 100644 index 000000000..87ce3d8a0 --- /dev/null +++ b/arch/cris/boot/compressed/misc.c @@ -0,0 +1,260 @@ +/* + * misc.c + * + * $Id: misc.c,v 1.3 2001/01/17 15:54:18 jonashg 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 etrax100.ld. + */ + +#define KERNEL_LOAD_ADR 0x40004000 + +#include <linux/config.h> +#include <linux/types.h> +#include <asm/svinto.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\n"); + + 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 void +puts(const char *s) +{ +#ifndef CONFIG_DEBUG_PORT_NULL + while(*s) { +#ifdef CONFIG_DEBUG_PORT0 + while(!(*R_SERIAL0_STATUS & (1 << 5))) ; + *R_SERIAL0_TR_DATA = *s++; +#endif +#ifdef CONFIG_DEBUG_PORT1 + while(!(*R_SERIAL1_STATUS & (1 << 5))) ; + *R_SERIAL1_TR_DATA = *s++; +#endif +#ifdef CONFIG_DEBUG_PORT2 + while(!(*R_SERIAL2_STATUS & (1 << 5))) ; + *R_SERIAL2_TR_DATA = *s++; +#endif +#ifdef CONFIG_DEBUG_PORT3 + while(!(*R_SERIAL3_STATUS & (1 << 5))) ; + *R_SERIAL3_TR_DATA = *s++; +#endif + } +#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; +} + +void +decompress_kernel() +{ + /* input_data is set in head.S */ + inbuf = input_data; + +#ifdef CONFIG_DEBUG_PORT0 + *R_SERIAL0_XOFF = 0; + *R_SERIAL0_BAUD = 0x99; + *R_SERIAL0_TR_CTRL = 0x40; +#endif +#ifdef CONFIG_DEBUG_PORT1 + *R_SERIAL1_XOFF = 0; + *R_SERIAL1_BAUD = 0x99; + *R_SERIAL1_TR_CTRL = 0x40; +#endif +#ifdef CONFIG_DEBUG_PORT2 + *R_SERIAL2_XOFF = 0; + *R_SERIAL2_BAUD = 0x99; + *R_SERIAL2_TR_CTRL = 0x40; +#endif +#ifdef CONFIG_DEBUG_PORT3 + *R_SERIAL3_XOFF = 0; + *R_SERIAL3_BAUD = 0x99; + *R_SERIAL3_TR_CTRL = 0x40; +#endif + + setup_normal_output_buffer(); + + makecrc(); + puts("Uncompressing Linux...\n"); + gunzip(); + puts("Done. Now booting the kernel.\n"); +} diff --git a/arch/cris/config.in b/arch/cris/config.in new file mode 100644 index 000000000..3e234ec43 --- /dev/null +++ b/arch/cris/config.in @@ -0,0 +1,223 @@ +# +# For a description of the syntax of this configuration file, +# see the Configure script. +# +mainmenu_name "Linux/CRIS Kernel Configuration" + +define_bool CONFIG_UID16 y + +mainmenu_option next_comment +comment 'Code maturity level options' +bool 'Prompt for development and/or incomplete code/drivers' CONFIG_EXPERIMENTAL +endmenu + +mainmenu_option next_comment +comment 'General setup' + +bool 'Networking support' CONFIG_NET +bool 'System V IPC' CONFIG_SYSVIPC + +tristate 'Kernel support for ELF binaries' CONFIG_BINFMT_ELF +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + tristate 'Kernel support for JAVA binaries' CONFIG_BINFMT_JAVA +fi + +bool 'Use kernel gdb debugger' CONFIG_KGDB + +bool 'Enable Etrax100 watchdog' CONFIG_ETRAX_WATCHDOG + +bool 'Use serial console (on the debug port)' CONFIG_USE_SERIAL_CONSOLE + +bool 'Use in-kernel ifconfig/route setup' CONFIG_KERNEL_IFCONFIG + +endmenu + +mainmenu_option next_comment +comment 'Hardware setup' + +choice 'Processor type' \ + "Etrax-100-LX CONFIG_ETRAX100LX \ + Etrax-100-LX-for-xsim-simulator CONFIG_SVINTO_SIM" Etrax-100-LX + +# For both LX version 1 and the current simulator we enable the low VM mapping +# Later when LX version 2 and above exist, this should be done with an if + +define_bool CONFIG_CRIS_LOW_MAP y + +hex 'DRAM base (hex)' ETRAX_DRAM_BASE 40000000 +int 'DRAM size (dec, in MB)' ETRAX_DRAM_SIZE 8 + +int 'Max possible flash size (dec, in MB)' CONFIG_ETRAX_FLASH_LENGTH 2 +int 'Buswidth of flash in bytes' CONFIG_ETRAX_FLASH_BUSWIDTH 2 + +choice 'Product LED port' \ + "Port-PA-LEDs CONFIG_ETRAX_PA_LEDS \ + Port-PB-LEDs CONFIG_ETRAX_PB_LEDS \ + Mem-0x90000000-LEDs CONFIG_ETRAX_90000000_LEDS \ + None CONFIG_ETRAX_NO_LEDS" Port-PA-LEDs + +if [ "$CONFIG_ETRAX_NO_LEDS" != "y" ]; then + int ' First green LED bit' CONFIG_ETRAX_LED1G 2 + int ' First red LED bit' CONFIG_ETRAX_LED1R 3 + int ' Second green LED bit' CONFIG_ETRAX_LED2G 4 + int ' Second red LED bit' CONFIG_ETRAX_LED2R 5 + int ' Third green LED bit' CONFIG_ETRAX_LED3R 2 + int ' Third red LED bit' CONFIG_ETRAX_LED3G 2 +fi + +choice 'Product debug-port' \ + "Serial-0 CONFIG_DEBUG_PORT0 \ + Serial-1 CONFIG_DEBUG_PORT1 \ + Serial-2 CONFIG_DEBUG_PORT2 \ + Serial-3 CONFIG_DEBUG_PORT3" Serial-0 + +hex 'R_WAITSTATES' DEF_R_WAITSTATES 95a6 +hex 'R_BUS_CONFIG' DEF_R_BUS_CONFIG 104 +hex 'R_DRAM_CONFIG' DEF_R_DRAM_CONFIG 1a200040 +hex 'R_DRAM_TIMING' DEF_R_DRAM_TIMING 5611 +hex 'R_PORT_PA_DIR' DEF_R_PORT_PA_DIR 1c +hex 'R_PORT_PA_DATA' DEF_R_PORT_PA_DATA 00 +hex 'R_PORT_PB_CONFIG' DEF_R_PORT_PB_CONFIG 00 +hex 'R_PORT_PB_DIR' DEF_R_PORT_PB_DIR 00 +hex 'R_PORT_PB_DATA' DEF_R_PORT_PB_DATA ff + +endmenu + +# only configure IP numbers if the kernel ifconfig/route setup is enabled + +if [ "$CONFIG_KERNEL_IFCONFIG" = "y" ]; then + mainmenu_option next_comment + comment 'IP address selection' + + comment 'All addresses are in hexadecimal form without 0x prefix' + + hex 'IP address' ELTEST_IPADR ab1005af + hex 'Network' ELTEST_NETWORK ab100000 + hex 'Netmask' ELTEST_NETMASK ffff0000 + hex 'Broadcast' ELTEST_BROADCAST ab10ffff + hex 'Gateway' ELTEST_GATEWAY ab100101 + hwaddr 'Ethernet address' ELTEST_ETHADR 00408ccd0000 + + endmenu +fi + +# bring in Etrax built-in drivers + +source arch/cris/drivers/Config.in + +# standard linux drivers + +source drivers/mtd/Config.in + +source drivers/parport/Config.in + +source drivers/pnp/Config.in + +source drivers/block/Config.in + +source drivers/md/Config.in + +if [ "$CONFIG_NET" = "y" ]; then + source net/Config.in +fi + +source drivers/telephony/Config.in + +mainmenu_option next_comment +comment 'ATA/IDE/MFM/RLL support' + +tristate 'ATA/IDE/MFM/RLL support' CONFIG_IDE + +if [ "$CONFIG_IDE" != "n" ]; then + source drivers/ide/Config.in +else + define_bool CONFIG_BLK_DEV_IDE_MODES n + define_bool CONFIG_BLK_DEV_HD n +fi +endmenu + +mainmenu_option next_comment +comment 'SCSI support' + +tristate 'SCSI support' CONFIG_SCSI + +if [ "$CONFIG_SCSI" != "n" ]; then + source drivers/scsi/Config.in +fi +endmenu + +source drivers/ieee1394/Config.in + +source drivers/i2o/Config.in + +if [ "$CONFIG_NET" = "y" ]; then + mainmenu_option next_comment + comment 'Network device support' + + bool 'Network device support' CONFIG_NETDEVICES + if [ "$CONFIG_NETDEVICES" = "y" ]; then + source drivers/net/Config.in + if [ "$CONFIG_ATM" = "y" ]; then + source drivers/atm/Config.in + fi + fi + endmenu +fi + +source net/ax25/Config.in + +source net/irda/Config.in + +mainmenu_option next_comment +comment 'ISDN subsystem' +if [ "$CONFIG_NET" != "n" ]; then + tristate 'ISDN support' CONFIG_ISDN + if [ "$CONFIG_ISDN" != "n" ]; then + source drivers/isdn/Config.in + fi +fi +endmenu + +mainmenu_option next_comment +comment 'Old CD-ROM drivers (not SCSI, not IDE)' + +bool 'Support non-SCSI/IDE/ATAPI CDROM drives' CONFIG_CD_NO_IDESCSI +if [ "$CONFIG_CD_NO_IDESCSI" != "n" ]; then + source drivers/cdrom/Config.in +fi +endmenu + +# +# input before char - char/joystick depends on it. As does USB. +# +source drivers/input/Config.in +source drivers/char/Config.in + +#source drivers/misc/Config.in + +source drivers/media/Config.in + +source fs/Config.in + +source drivers/char/Config.in + +mainmenu_option next_comment +comment 'Sound' + +tristate 'Sound card support' CONFIG_SOUND +if [ "$CONFIG_SOUND" != "n" ]; then + source drivers/sound/Config.in +fi +endmenu + +source drivers/usb/Config.in + +mainmenu_option next_comment +comment 'Kernel hacking' + +#bool 'Debug kmalloc/kfree' CONFIG_DEBUG_MALLOC +bool 'Kernel profiling support' CONFIG_PROFILE +if [ "$CONFIG_PROFILE" = "y" ]; then + int ' Profile shift count' CONFIG_PROFILE_SHIFT 2 +fi +endmenu diff --git a/arch/cris/cris.ld b/arch/cris/cris.ld new file mode 100644 index 000000000..5ba215959 --- /dev/null +++ b/arch/cris/cris.ld @@ -0,0 +1,81 @@ +/* ld script to make the Linux/CRIS kernel + * Authors: Bjorn Wesen (bjornw@axis.com) + * + * For now, on Etrax-100 LX, the DRAM starts virtually at 0x6. Normally + * it should be at 0xc. + */ + +SECTIONS +{ + . = 0x60000000; /* DRAM starts virtually at 0x60000000 */ + _dram_start = .; + _ibr_start = .; + . = . + 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) + *(.fixup) + *(.text.__*) + *(.rodata) + } + + . = ALIGN(4); /* Exception table */ + __start___ex_table = .; + __ex_table : { *(__ex_table) } + __stop___ex_table = .; + + _etext = . ; /* End of text section */ + __etext = .; + + . = ALIGN (4); + ___data_rom_start = . ; + ___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 = .; + .text.init : { *(.text.init) } + .data.init : { *(.data.init) } + . = ALIGN(16); + ___setup_start = .; + .setup.init : { *(.setup.init) } + ___setup_end = .; + ___initcall_start = .; + .initcall.init : { *(.initcall.init) } + ___initcall_end = .; + __vmlinux_end = .; /* last address of the physical file */ + . = ALIGN(8192); + ___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 = 0x60000000 + @ETRAX_DRAM_SIZE_M@*1024*1024; +} diff --git a/arch/cris/defconfig b/arch/cris/defconfig new file mode 100644 index 000000000..91a25ee23 --- /dev/null +++ b/arch/cris/defconfig @@ -0,0 +1,319 @@ +# +# Automatically generated make config: don't edit +# +CONFIG_UID16=y + +# +# Code maturity level options +# +CONFIG_EXPERIMENTAL=y + +# +# General setup +# +CONFIG_NET=y +CONFIG_SYSVIPC=y +CONFIG_BINFMT_ELF=y +# CONFIG_BINFMT_JAVA is not set +# CONFIG_KGDB is not set +# CONFIG_ETRAX_WATCHDOG is not set +CONFIG_USE_SERIAL_CONSOLE=y +# CONFIG_KERNEL_IFCONFIG is not set + +# +# Hardware setup +# +CONFIG_ETRAX100LX=y +# CONFIG_SVINTO_SIM is not set +CONFIG_CRIS_LOW_MAP=y +ETRAX_DRAM_BASE=40000000 +ETRAX_DRAM_SIZE=8 +CONFIG_ETRAX_PA_LEDS=y +# CONFIG_ETRAX_PB_LEDS is not set +# CONFIG_ETRAX_90000000_LEDS is not set +# CONFIG_ETRAX_NO_LEDS is not set +CONFIG_ETRAX_LED1G=2 +CONFIG_ETRAX_LED1R=2 +CONFIG_ETRAX_LED2G=2 +CONFIG_ETRAX_LED2R=2 +CONFIG_DEBUG_PORT0=y +# CONFIG_DEBUG_PORT1 is not set +# CONFIG_DEBUG_PORT2 is not set +# CONFIG_DEBUG_PORT3 is not set +DEF_R_WAITSTATES=95a6 +DEF_R_BUS_CONFIG=104 +DEF_R_DRAM_CONFIG=1a200040 +DEF_R_DRAM_TIMING=5611 +DEF_R_PORT_PA_DIR=1d +DEF_R_PORT_PA_DATA=f0 +DEF_R_PORT_PB_CONFIG=00 +DEF_R_PORT_PB_DIR=1e +DEF_R_PORT_PB_DATA=f3 + +# +# Drivers for Etrax built-in interfaces +# +CONFIG_ETRAX_ETHERNET=y +CONFIG_NET_ETHERNET=y +CONFIG_ETRAX_SERIAL=y + +# +# Block devices +# +# CONFIG_BLK_DEV_FD is not set +# CONFIG_BLK_DEV_XD is not set +# CONFIG_PARIDE is not set +# CONFIG_BLK_CPQ_DA is not set +# CONFIG_BLK_CPQ_CISS_DA is not set +# CONFIG_BLK_DEV_DAC960 is not set +# CONFIG_BLK_DEV_LOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_BLK_DEV_INITRD is not set + +# +# Networking options +# +# CONFIG_PACKET is not set +# CONFIG_NETLINK is not set +# CONFIG_NETFILTER is not set +# CONFIG_FILTER is not set +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_IP_MULTICAST is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +# CONFIG_IP_PNP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_INET_ECN is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_IPV6 is not set +# CONFIG_KHTTPD is not set +# CONFIG_ATM is not set + +# +# +# +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_DECNET is not set +# CONFIG_BRIDGE is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_LLC is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_FASTROUTE is not set +# CONFIG_NET_HW_FLOWCONTROL is not set + +# +# QoS and/or fair queueing +# +# CONFIG_NET_SCHED is not set + +# +# SCSI support +# +# CONFIG_SCSI is not set + +# +# Network device support +# +CONFIG_NETDEVICES=y + +# +# ARCnet devices +# +# CONFIG_ARCNET is not set +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_EQUALIZER is not set +# CONFIG_TUN is not set +# CONFIG_NET_SB1000 is not set + +# +# Ethernet (10 or 100Mbit) +# +CONFIG_NET_ETHERNET=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_LANCE is not set +# CONFIG_NET_VENDOR_SMC is not set +# CONFIG_NET_VENDOR_RACAL is not set +# CONFIG_AT1700 is not set +# CONFIG_DEPCA is not set +# CONFIG_NET_ISA is not set +# CONFIG_NET_PCI is not set +# CONFIG_NET_POCKET is not set + +# +# Ethernet (1000 Mbit) +# +# CONFIG_ACENIC is not set +# CONFIG_HAMACHI is not set +# CONFIG_YELLOWFIN is not set +# CONFIG_SK98LIN is not set +# CONFIG_FDDI is not set +# CONFIG_HIPPI is not set +# CONFIG_PLIP is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set + +# +# Wireless LAN (non-hamradio) +# +# CONFIG_NET_RADIO is not set + +# +# Token Ring devices +# +# CONFIG_TR is not set +# CONFIG_NET_FC is not set +# CONFIG_RCPCI is not set +# CONFIG_SHAPER is not set + +# +# Wan interfaces +# +# CONFIG_WAN is not set + +# +# ISDN subsystem +# +# CONFIG_ISDN is not set + +# +# CD-ROM drivers (not for SCSI or IDE/ATAPI drives) +# +# CONFIG_CD_NO_IDESCSI is not set + +# +# File systems +# +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +# CONFIG_ADFS_FS is not set +# CONFIG_ADFS_FS_RW is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_FAT_FS is not set +# CONFIG_MSDOS_FS is not set +# CONFIG_UMSDOS_FS is not set +# CONFIG_VFAT_FS is not set +# CONFIG_EFS_FS is not set +# CONFIG_JFFS_FS is not set +CONFIG_CRAMFS=y +CONFIG_RAMFS=y +# CONFIG_ISO9660_FS is not set +# CONFIG_JOLIET is not set +# CONFIG_MINIX_FS is not set +# CONFIG_NTFS_FS is not set +# CONFIG_NTFS_RW is not set +# CONFIG_HPFS_FS is not set +CONFIG_PROC_FS=y +# CONFIG_DEVFS_FS is not set +# CONFIG_DEVFS_MOUNT is not set +# CONFIG_DEVFS_DEBUG is not set +# CONFIG_DEVPTS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_QNX4FS_RW is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_EXT2_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_SYSV_FS_WRITE is not set +# CONFIG_UDF_FS is not set +# CONFIG_UDF_RW is not set +# CONFIG_UFS_FS is not set +# CONFIG_UFS_FS_WRITE is not set + +# +# Network File Systems +# +# CONFIG_CODA_FS is not set +# CONFIG_NFS_FS is not set +# CONFIG_NFS_V3 is not set +# CONFIG_ROOT_NFS is not set +# CONFIG_NFSD is not set +# CONFIG_NFSD_V3 is not set +# CONFIG_SUNRPC is not set +# CONFIG_LOCKD is not set +# CONFIG_SMB_FS is not set +# CONFIG_NCP_FS is not set +# CONFIG_NCPFS_PACKET_SIGNING is not set +# CONFIG_NCPFS_IOCTL_LOCKING is not set +# CONFIG_NCPFS_STRONG is not set +# CONFIG_NCPFS_NFS_NS is not set +# CONFIG_NCPFS_OS2_NS is not set +# CONFIG_NCPFS_SMALLDOS is not set +# CONFIG_NCPFS_MOUNT_SUBDIR is not set +# CONFIG_NCPFS_NDS_DOMAINS is not set +# CONFIG_NCPFS_NLS is not set +# CONFIG_NCPFS_EXTRAS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +# CONFIG_NLS is not set + +# +# Character devices +# +# CONFIG_VT is not set +# CONFIG_SERIAL is not set +# CONFIG_SERIAL_EXTENDED is not set +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_UNIX98_PTYS is not set +# CONFIG_PRINTER is not set +# CONFIG_PPDEV is not set + +# +# I2C support +# +# CONFIG_I2C is not set + +# +# Mice +# +# CONFIG_BUSMOUSE is not set +# CONFIG_MOUSE is not set + +# +# Joysticks +# +# CONFIG_JOYSTICK is not set + +# +# Input core support is needed for joysticks +# +# CONFIG_QIC02_TAPE is not set + +# +# Watchdog Cards +# +# CONFIG_WATCHDOG is not set +# CONFIG_INTEL_RNG is not set +# CONFIG_NVRAM is not set +# CONFIG_RTC is not set +# CONFIG_DTLK is not set +# CONFIG_R3964 is not set +# CONFIG_APPLICOM is not set + +# +# Ftape, the floppy tape device driver +# +# CONFIG_FTAPE is not set +# CONFIG_AGP is not set +# CONFIG_DRM is not set + +# +# Sound +# +# CONFIG_SOUND is not set + +# +# Kernel hacking +# +# CONFIG_PROFILE is not set diff --git a/arch/cris/drivers/Config.in b/arch/cris/drivers/Config.in new file mode 100644 index 000000000..36055ae04 --- /dev/null +++ b/arch/cris/drivers/Config.in @@ -0,0 +1,49 @@ +mainmenu_option next_comment +comment 'Drivers for Etrax built-in interfaces' + +bool 'Ethernet support' CONFIG_ETRAX_ETHERNET y +if [ "$CONFIG_ETRAX_ETHERNET" = "y" ]; then +# this is just so that the user does not have to go into the +# normal ethernet driver section just to enable ethernetworking + define_bool CONFIG_NET_ETHERNET y +fi + +bool 'Serial-port support' CONFIG_ETRAX_SERIAL y + +bool 'ATA/IDE support' CONFIG_ETRAX_IDE n + +if [ "$CONFIG_ETRAX_IDE" = "y" ]; then +# here we should add the CONFIG_'s necessary to enable the basic +# general ide drivers so the common case does not need to go +# into that config submenu. enable disk and CD support. others +# need to go fiddle in the submenu.. + define_bool CONFIG_IDE y + + define_bool CONFIG_BLK_DEV_IDE y + define_bool CONFIG_BLK_DEV_IDEDISK y + define_bool CONFIG_BLK_DEV_IDECD y + + define_bool CONFIG_BLK_DEV_IDEDMA y + define_bool CONFIG_DMA_NONPCI y + + choice 'IDE reset pin' \ + "Port_PB_Bit_7 CONFIG_ETRAX_IDE_PB7_RESET\ + Port_G_Bit_27 CONFIG_ETRAX_IDE_G27_RESET\ + Port_CSE1_Bit_16 CONFIG_ETRAX_IDE_CSE1_16_RESET" Port_PB_Bit_7 +fi + +bool 'Axis flash-map support' CONFIG_ETRAX_AXISFLASHMAP n + +if [ "$CONFIG_ETRAX_AXISFLASHMAP" = "y" ]; then +# here we define the CONFIG_'s necessary to enable MTD support +# for the flash + define_bool CONFIG_MTD y + + define_bool CONFIG_MTD_CFI y + define_bool CONFIG_MTD_CFI_INTELEXT n + define_bool CONFIG_MTD_CFI_AMDSTD y + + define_bool CONFIG_MTD_CHAR y +fi + +endmenu diff --git a/arch/cris/drivers/Makefile b/arch/cris/drivers/Makefile new file mode 100644 index 000000000..47754f647 --- /dev/null +++ b/arch/cris/drivers/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for Etrax-specific drivers +# + +O_TARGET := drivers.o + +obj-y := + +obj-$(CONFIG_ETRAX_ETHERNET) += ethernet.o +obj-$(CONFIG_ETRAX_SERIAL) += serial.o +obj-$(CONFIG_ETRAX_IDE) += ide.o +obj-$(CONFIG_ETRAX_AXISFLASHMAP) += axisflashmap.o + +include $(TOPDIR)/Rules.make diff --git a/arch/cris/drivers/axisflashmap.c b/arch/cris/drivers/axisflashmap.c new file mode 100644 index 000000000..15b0be262 --- /dev/null +++ b/arch/cris/drivers/axisflashmap.c @@ -0,0 +1,311 @@ +/* + * Physical mapping layer for MTD using the Axis partitiontable format + * + * Copyright (c) 2001 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. + * + * $Log: axisflashmap.c,v $ + * Revision 1.1 2001/01/12 17:01:18 bjornw + * * Added axisflashmap.c, a physical mapping for MTD that reads and understands + * Axis partition-table format. + * + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/config.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> + +#include <asm/axisflashmap.h> +#include <asm/mmu.h> + +#ifdef CONFIG_CRIS_LOW_MAP +#define FLASH_UNCACHED_ADDR KSEG_E +#define FLASH_CACHED_ADDR KSEG_5 +#else +#define FLASH_UNCACHED_ADDR KSEG_E +#define FLASH_CACHED_ADDR KSEG_F +#endif + +/* + * WINDOW_SIZE is the total size where the flash chips are mapped, + * my guess is that this can be the total memory area even if there + * are many flash chips inside the area or if they are not all mounted. + * So possibly we can get rid of the CONFIG_ here and just write something + * like 32 MB always. + */ + +#define WINDOW_SIZE (CONFIG_ETRAX_FLASH_LENGTH * 1024 * 1024) + +/* Byte-offset where the partition-table is placed in the first chip + */ + +#define PTABLE_SECTOR 65536 + +/* + * Map driver + * + * Ok this is the scoop - we need to access the flash both with and without + * the cache - without when doing all the fancy flash interfacing, and with + * when we do actual copying because otherwise it will be slow like molasses. + * I hope this works the way it's intended, so that there won't be any cases + * of non-synchronicity because of the different access modes below... + */ + +static __u8 flash_read8(struct map_info *map, unsigned long ofs) +{ + return *(__u8 *)(FLASH_UNCACHED_ADDR + ofs); +} + +static __u16 flash_read16(struct map_info *map, unsigned long ofs) +{ + return *(__u16 *)(FLASH_UNCACHED_ADDR + ofs); +} + +static __u32 flash_read32(struct map_info *map, unsigned long ofs) +{ + return *(volatile unsigned int *)(FLASH_UNCACHED_ADDR + ofs); +} + +static void flash_copy_from(struct map_info *map, void *to, + unsigned long from, ssize_t len) +{ + memcpy(to, (void *)(FLASH_CACHED_ADDR + from), len); +} + +static void flash_write8(struct map_info *map, __u8 d, unsigned long adr) +{ + *(__u8 *)(FLASH_UNCACHED_ADDR + adr) = d; +} + +static void flash_write16(struct map_info *map, __u16 d, unsigned long adr) +{ + *(__u16 *)(FLASH_UNCACHED_ADDR + adr) = d; +} + +static void flash_write32(struct map_info *map, __u32 d, unsigned long adr) +{ + *(__u32 *)(FLASH_UNCACHED_ADDR + adr) = d; +} + +static void flash_copy_to(struct map_info *map, unsigned long to, + const void *from, ssize_t len) +{ + memcpy((void *)(FLASH_CACHED_ADDR + to), from, len); +} + +static struct map_info axis_map = { + name: "Axis flash", + size: WINDOW_SIZE, + buswidth: CONFIG_ETRAX_FLASH_BUSWIDTH, + read8: flash_read8, + read16: flash_read16, + read32: flash_read32, + copy_from: flash_copy_from, + write8: flash_write8, + write16: flash_write16, + write32: flash_write32, + copy_to: flash_copy_to +}; + +/* If no partition-table was found, we use this default-set. + */ + +#define MAX_PARTITIONS 7 +#define NUM_DEFAULT_PARTITIONS 3 + +static struct mtd_partition axis_default_partitions[NUM_DEFAULT_PARTITIONS] = { + { + name: "boot firmware", + size: PTABLE_SECTOR, + offset: 0 + }, + { + name: "kernel", + size: 0x1a0000, + offset: PTABLE_SECTOR + }, + { + name: "filesystem", + size: 0x50000, + offset: (0x1a0000 + PTABLE_SECTOR) + } +}; + +static struct mtd_partition axis_partitions[MAX_PARTITIONS] = { + { + name: "part0", + size: 0, + 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 + }, +}; + +/* + * This is the master MTD device for which all the others are just + * auto-relocating aliases. + */ +static struct mtd_info *mymtd; + +/* CFI-scan the flash, and if there was a chip, read the partition-table + * and register the partitions with MTD. + */ + +static int __init +init_axis_flash(void) +{ + int pidx = 0; + struct partitiontable_head *ptable_head; + struct partitiontable_entry *ptable; + int use_default_ptable = 1; /* Until proven otherwise */ + const char *pmsg = " /dev/flash%d at 0x%x, size 0x%x\n"; + + printk(KERN_NOTICE "Axis flash mapping: %x at %x\n", + WINDOW_SIZE, FLASH_CACHED_ADDR); + + mymtd = do_cfi_probe(&axis_map); + + if(!mymtd) { + printk("cfi_probe erred %d\n", mymtd); + return -ENXIO; + } + + mymtd->module = THIS_MODULE; + + /* The partition-table is at an offset within the second + * sector of the flash. We _define_ this to be at offset 64k + * even if the actual sector-size in the flash changes.. for + * now at least. + */ + + ptable_head = FLASH_CACHED_ADDR + PTABLE_SECTOR + PARTITION_TABLE_OFFSET; + pidx++; /* first partition is always set to the default */ + + if ((ptable_head->magic == PARTITION_TABLE_MAGIC) + && (ptable_head->size + < (MAX_PARTITIONS + * sizeof(struct partitiontable_entry) + 4)) + && (*(unsigned long*) + ((void*)ptable_head + + sizeof(*ptable_head) + + ptable_head->size - 4) + == 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 = 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++; + } + /* printk(" total csum: 0x%08X 0x%08X\n", + csum, ptable_head->checksum); */ + ptable_ok = (csum == ptable_head->checksum); + + /* Read the entries and use/show the info. */ + ptable = (struct partitiontable_entry *) + ((unsigned long)ptable_head + sizeof(*ptable_head)); + + printk(" Found %s partition table at 0x%08lX-0x%08lX.\n", + (ptable_ok ? "valid" : "invalid"), + (unsigned long)ptable_head, + (unsigned long)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) { +#if 0 + /* wait with multi-chip support until we know + * how mtd detects multiple chips + */ + if ((offset + ptable->offset) >= chips[0].size) { + partitions[pidx].start + = offset + chips[1].start + + ptable->offset - chips[0].size; + } +#endif + axis_partitions[pidx].offset = offset + ptable->offset; + 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(use_default_ptable) { + printk(" Using default partition table\n"); + return add_mtd_partitions(mymtd, axis_default_partitions, + NUM_DEFAULT_PARTITIONS); + } else { + return add_mtd_partitions(mymtd, axis_partitions, pidx); + } +} + +/* This adds the above to the kernels init-call chain */ + +module_init(init_axis_flash); + diff --git a/arch/cris/drivers/ethernet.c b/arch/cris/drivers/ethernet.c new file mode 100644 index 000000000..53a9798ae --- /dev/null +++ b/arch/cris/drivers/ethernet.c @@ -0,0 +1,1041 @@ +/* $Id: ethernet.c,v 1.5 2000/11/29 17:22:22 bjornw Exp $ + * + * e100net.c: A network driver for the ETRAX 100LX network controller. + * + * Copyright (c) 1998-2000 Axis Communications AB. + * + * The outline of this driver comes from skeleton.c. + * + * $Log: ethernet.c,v $ + * Revision 1.5 2000/11/29 17:22:22 bjornw + * Get rid of the udword types legacy stuff + * + * Revision 1.4 2000/11/22 16:36:09 bjornw + * Please marketing by using the correct case when spelling Etrax. + * + * Revision 1.3 2000/11/21 16:43:04 bjornw + * Minor short->int change + * + * Revision 1.2 2000/11/08 14:27:57 bjornw + * 2.4 port + * + * Revision 1.1 2000/11/06 13:56:00 bjornw + * Verbatim copy of the 1.24 version of e100net.c from elinux + * + * Revision 1.24 2000/10/04 15:55:23 bjornw + * * Use virt_to_phys etc. for DMA addresses + * * Removed bogus CHECKSUM_UNNECESSARY + * + * + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <linux/spinlock.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> +#include <linux/init.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/svinto.h> + +//#define ETHDEBUG + +#define D(x) + +/* + * The name of the card. Is used for messages and in the requests for + * io regions, irqs and dma channels + */ + +static const char* cardname = "ETRAX 100LX built-in ethernet controller"; + +/* A default ethernet address. Highlevel SW will set the real one later */ + +static struct sockaddr default_mac = { + 0, + { 0x00, 0x40, 0x8C, 0xCD, 0x00, 0x00 } +}; + +/* Information that need to be kept for each board. */ +struct net_local { + struct net_device_stats stats; + + /* Tx control lock. This protects the transmit buffer ring + * state along with the "tx full" state of the driver. This + * means all netif_queue flow control actions are protected + * by this lock as well. + */ + spinlock_t lock; +}; + +#define NETWORK_DMARX_IRQ 17 /* irq 17 is the DMA1 irq */ +#define NETWORK_DMATX_IRQ 16 /* irq 16 is the DMA0 irq */ +#define NETWORK_STATUS_IRQ 6 /* irq 6 is the network irq */ + +/* Dma descriptors etc. */ + +#define RX_BUF_SIZE 32768 + +#define MAX_MEDIA_DATA_SIZE 1518 + +#define MIN_PACKET_LEN 46 +#define ETHER_HEAD_LEN 14 + +/* +** MDIO constants. +*/ +#define MDIO_BASE_STATUS_REG 0x1 +#define MDIO_BASE_CONTROL_REG 0x0 +#define MDIO_LINK_UP_MASK 0x4 +#define MDIO_START 0x1 +#define MDIO_READ 0x2 +#define MDIO_WRITE 0x1 +#define MDIO_PREAMBLE 0xfffffffful + +/* Broadcom specific */ +#define MDIO_AUX_CTRL_STATUS_REG 0x18 +#define MDIO_SPEED 0x2 +#define MDIO_PHYS_ADDR 0x0 + +/* Network flash constants */ +#define NET_FLASH_TIME 2 /* 20 ms */ +#define NET_LINK_UP_CHECK_INTERVAL 200 /* 2 s */ + +/* RX_DESC_BUF_SIZE should be a multiple of four to avoid the buffer + * alignment bug in Etrax 100 release 1 + */ + +#define RX_DESC_BUF_SIZE 256 +#define NBR_OF_RX_DESC (RX_BUF_SIZE / \ + RX_DESC_BUF_SIZE) + +#define GET_BIT(bit,val) (((val) >> (bit)) & 0x01) + +static etrax_dma_descr *myNextRxDesc; /* Points to the next descriptor to + to be processed */ +static etrax_dma_descr *myLastRxDesc; /* The last processed descriptor */ +static etrax_dma_descr *myPrevRxDesc; /* The descriptor right before myNextRxDesc */ + +static unsigned char RxBuf[RX_BUF_SIZE]; + +static etrax_dma_descr RxDescList[NBR_OF_RX_DESC]; +static etrax_dma_descr TxDesc; + +static struct sk_buff *tx_skb; + +/* Network speed indication. */ +static struct timer_list speed_timer; +static struct timer_list clear_led_timer; +static int current_speed; +static int led_clear_time; +static int nolink; + +/* Index to functions, as function prototypes. */ + +static int etrax_ethernet_init(struct net_device *dev); + +static int e100_open(struct net_device *dev); +static int e100_set_mac_address(struct net_device *dev, void *addr); +static int e100_send_packet(struct sk_buff *skb, struct net_device *dev); +static void e100rx_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void e100tx_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void e100nw_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void e100_rx(struct net_device *dev); +static int e100_close(struct net_device *dev); +static struct net_device_stats *e100_get_stats(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static void e100_hardware_send_packet(char *buf, int length); +static void update_rx_stats(struct net_device_stats *); +static void update_tx_stats(struct net_device_stats *); + +static void e100_check_speed(unsigned long dummy); +static unsigned short e100_get_mdio_reg(unsigned char reg_num); +static void e100_send_mdio_cmd(unsigned short cmd, int write_cmd); +static void e100_send_mdio_bit(unsigned char bit); +static unsigned char e100_receive_mdio_bit(void); +static void e100_reset_tranceiver(void); + +static void e100_clear_network_leds(unsigned long dummy); + +#define tx_done(dev) (*R_DMA_CH0_CMD == 0) + +/* + * Check for a network adaptor of this type, and return '0' if one exists. + * If dev->base_addr == 0, probe all likely locations. + * If dev->base_addr == 1, always return failure. + * If dev->base_addr == 2, allocate space for the device and return success + * (detachable devices only). + */ + +static int __init +etrax_ethernet_init(struct net_device *dev) +{ + int i; + int anOffset = 0; + + printk("ETRAX 100LX 10/100MBit ethernet v2.0 (c) 2000 Axis Communications AB\n"); + + dev->base_addr = (unsigned int)R_NETWORK_SA_0; /* just to have something to show */ + + printk("%s initialized\n", dev->name); + + /* make Linux aware of the new hardware */ + + if (!dev) { + printk("dev == NULL. Should this happen?\n"); + dev = init_etherdev(dev, sizeof(struct net_local)); + } + + /* setup generic handlers and stuff in the dev struct */ + + ether_setup(dev); + + /* make room for the local structure containing stats etc */ + + dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + memset(dev->priv, 0, sizeof(struct net_local)); + + /* now setup our etrax specific stuff */ + + dev->irq = NETWORK_DMARX_IRQ; /* we really use DMATX as well... */ + dev->dma = 1; + + /* fill in our handlers so the network layer can talk to us in the future */ + + dev->open = e100_open; + dev->hard_start_xmit = e100_send_packet; + dev->stop = e100_close; + dev->get_stats = e100_get_stats; + dev->set_multicast_list = set_multicast_list; + dev->set_mac_address = e100_set_mac_address; + + /* set the default MAC address */ + + e100_set_mac_address(dev, &default_mac); + + /* Initialise the list of Etrax DMA-descriptors */ + + /* Initialise receive descriptors */ + + for(i = 0; i < (NBR_OF_RX_DESC - 1); i++) { + RxDescList[i].ctrl = 0; + RxDescList[i].sw_len = RX_DESC_BUF_SIZE; + RxDescList[i].next = virt_to_phys(&RxDescList[i + 1]); + RxDescList[i].buf = virt_to_phys(RxBuf + anOffset); + RxDescList[i].status = 0; + RxDescList[i].hw_len = 0; + anOffset += RX_DESC_BUF_SIZE; + } + + RxDescList[i].ctrl = d_eol; + RxDescList[i].sw_len = RX_DESC_BUF_SIZE; + RxDescList[i].next = virt_to_phys(&RxDescList[0]); + RxDescList[i].buf = virt_to_phys(RxBuf + anOffset); + RxDescList[i].status = 0; + RxDescList[i].hw_len = 0; + + /* Initialise initial pointers */ + + myNextRxDesc = &RxDescList[0]; + myLastRxDesc = &RxDescList[NBR_OF_RX_DESC - 1]; + myPrevRxDesc = &RxDescList[NBR_OF_RX_DESC - 1]; + + /* Initialize speed indicator stuff. */ + + nolink = 0; + current_speed = 10; + speed_timer.expires = jiffies + NET_LINK_UP_CHECK_INTERVAL; + speed_timer.function = e100_check_speed; + add_timer(&speed_timer); + clear_led_timer.function = e100_clear_network_leds; + clear_led_timer.expires = jiffies + 10; + add_timer(&clear_led_timer); + + return 0; +} + +/* set MAC address of the interface. called from the core after a + * SIOCSIFADDR ioctl, and from the bootup above. + */ + +static int +e100_set_mac_address(struct net_device *dev, void *p) +{ + struct sockaddr *addr = p; + int i; + + /* remember it */ + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + /* Write it to the hardware. + * Note the way the address is wrapped: + * *R_NETWORK_SA_0 = a0_0 | (a0_1 << 8) | (a0_2 << 16) | (a0_3 << 24); + * *R_NETWORK_SA_1 = a0_4 | (a0_5 << 8); + */ + + *R_NETWORK_SA_0 = dev->dev_addr[0] | (dev->dev_addr[1] << 8) | + (dev->dev_addr[2] << 16) | (dev->dev_addr[3] << 24); + *R_NETWORK_SA_1 = dev->dev_addr[4] | (dev->dev_addr[5] << 8); + *R_NETWORK_SA_2 = 0; + + /* show it in the log as well */ + + printk("%s: changed MAC to ", dev->name); + + for (i = 0; i < 5; i++) + printk("%02X:", dev->dev_addr[i]); + + printk("%02X\n", dev->dev_addr[i]); + + return 0; +} + +/* + * Open/initialize the board. This is called (in the current kernel) + * sometime after booting when the 'ifconfig' program is run. + * + * This routine should set everything up anew at each open, even + * registers that "should" only need to be set once at boot, so that + * there is non-reboot way to recover if something goes wrong. + */ + +static int +e100_open(struct net_device *dev) +{ + unsigned long flags; + + /* disable the ethernet interface while we configure it */ + + *R_NETWORK_GEN_CONFIG = + IO_STATE(R_NETWORK_GEN_CONFIG, phy, mii_clk) | + IO_STATE(R_NETWORK_GEN_CONFIG, enable, off); + + /* enable the MDIO output pin */ + + *R_NETWORK_MGM_CTRL = IO_STATE(R_NETWORK_MGM_CTRL, mdoe, enable); + + *R_IRQ_MASK0_CLR = 0xe0000; /* clear excessive_col, over/underrun irq mask */ + *R_IRQ_MASK2_CLR = 0xf; /* clear dma0 and 1 eop and descr irq masks */ + + /* Reset and wait for the DMA channels */ + + RESET_DMA(0); + RESET_DMA(1); + WAIT_DMA(0); + WAIT_DMA(1); + + /* Initialise the etrax network controller */ + + /* allocate the irq corresponding to the receiving DMA */ + + if (request_irq(NETWORK_DMARX_IRQ, e100rx_interrupt, 0, + cardname, (void *)dev)) { + goto grace_exit; + } + + /* allocate the irq corresponding to the transmitting DMA */ + + if (request_irq(NETWORK_DMATX_IRQ, e100tx_interrupt, 0, + cardname, (void *)dev)) { + goto grace_exit; + } + + /* allocate the irq corresponding to the network errors etc */ + + if (request_irq(NETWORK_STATUS_IRQ, e100nw_interrupt, 0, + cardname, (void *)dev)) { + goto grace_exit; + } + + /* + * Always allocate the DMA channels after the IRQ, + * and clean up on failure. + */ + + if(request_dma(0, cardname)) { + goto grace_exit; + } + + if(request_dma(1, cardname)) { + grace_exit: + /* this will cause some 'trying to free free irq' but what the heck... */ + free_dma(0); + free_irq(NETWORK_DMARX_IRQ, NULL); + free_irq(NETWORK_DMATX_IRQ, NULL); + free_irq(NETWORK_STATUS_IRQ, NULL); + return -EAGAIN; + } + + /* give the HW an idea of what MAC address we want */ + + *R_NETWORK_SA_0 = dev->dev_addr[0] | (dev->dev_addr[1] << 8) | + (dev->dev_addr[2] << 16) | (dev->dev_addr[3] << 24); + *R_NETWORK_SA_1 = dev->dev_addr[4] | (dev->dev_addr[5] << 8); + *R_NETWORK_SA_2 = 0; + +#if 0 + /* use promiscuous mode for testing */ + *R_NETWORK_GA_0 = 0xffffffff; + *R_NETWORK_GA_1 = 0xffffffff; + + *R_NETWORK_REC_CONFIG = 0xd; /* broadcast rec, individ. rec, ma0 enabled */ +#else + *R_NETWORK_REC_CONFIG = + IO_STATE(R_NETWORK_REC_CONFIG, broadcast, receive) | + IO_STATE(R_NETWORK_REC_CONFIG, ma0, enable); +#endif + + *R_NETWORK_GEN_CONFIG = + IO_STATE(R_NETWORK_GEN_CONFIG, phy, mii_clk) | + IO_STATE(R_NETWORK_GEN_CONFIG, enable, on); + + save_flags(flags); + cli(); + + /* enable the irq's for ethernet DMA */ + + *R_IRQ_MASK2_SET = + IO_STATE(R_IRQ_MASK2_SET, dma0_eop, set) | + IO_STATE(R_IRQ_MASK2_SET, dma1_eop, set); + + *R_IRQ_MASK0_SET = + IO_STATE(R_IRQ_MASK0_SET, overrun, set) | + IO_STATE(R_IRQ_MASK0_SET, underrun, set) | + IO_STATE(R_IRQ_MASK0_SET, excessive_col, set); + + tx_skb = 0; + + /* make sure the irqs are cleared */ + + *R_DMA_CH0_CLR_INTR = IO_STATE(R_DMA_CH0_CLR_INTR, clr_eop, do); + *R_DMA_CH1_CLR_INTR = IO_STATE(R_DMA_CH1_CLR_INTR, clr_eop, do); + + /* make sure the rec and transmit error counters are cleared */ + + (void)*R_REC_COUNTERS; /* dummy read */ + (void)*R_TR_COUNTERS; /* dummy read */ + + /* start the receiving DMA channel so we can receive packets from now on */ + + *R_DMA_CH1_FIRST = virt_to_phys(myNextRxDesc); + *R_DMA_CH1_CMD = IO_STATE(R_DMA_CH1_CMD, cmd, start); + + restore_flags(flags); + + /* We are now ready to accept transmit requeusts from + * the queueing layer of the networking. + */ + netif_start_queue(dev); + + return 0; +} + + +static void +e100_check_speed(unsigned long dummy) +{ + unsigned long data; + data = e100_get_mdio_reg(MDIO_BASE_STATUS_REG); + if (!(data & MDIO_LINK_UP_MASK)) { + nolink = 1; + LED_NETWORK_TX_SET(1); /* Make it red, link is down. */ + } else { + nolink = 0; + LED_NETWORK_TX_SET(0); /* Link is up again, clear red LED. */ + data = e100_get_mdio_reg(MDIO_AUX_CTRL_STATUS_REG); + if (data & MDIO_SPEED) { + current_speed = 100; + } else { + current_speed = 10; + } + } + + /* Reinitialize the timer. */ + speed_timer.expires = jiffies + NET_LINK_UP_CHECK_INTERVAL; + add_timer(&speed_timer); +} + +static unsigned short +e100_get_mdio_reg(unsigned char reg_num) +{ + unsigned long flags; + unsigned short cmd; /* Data to be sent on MDIO port */ + unsigned short data; /* Data read from MDIO */ + int bitCounter; + + save_flags(flags); + cli(); + + /* Start of frame, OP Code, Physical Address, Register Address */ + cmd = (MDIO_START << 14) | (MDIO_READ << 12) | (MDIO_PHYS_ADDR << 7) | + (reg_num << 2); + + e100_send_mdio_cmd(cmd, 0); + + data = 0; + + /* Data... */ + for(bitCounter=15; bitCounter>=0 ; bitCounter--) { + data |= (e100_receive_mdio_bit() << bitCounter); + } + + restore_flags(flags); + return data; +} + +static void +e100_send_mdio_cmd(unsigned short cmd, int write_cmd) +{ + int bitCounter; + unsigned char data = 0x2; + + /* Preamble */ + for(bitCounter = 31; bitCounter>= 0; bitCounter--) + e100_send_mdio_bit(GET_BIT(bitCounter, MDIO_PREAMBLE)); + + for(bitCounter = 15; bitCounter >= 2; bitCounter--) + e100_send_mdio_bit(GET_BIT(bitCounter, cmd)); + + /* Turnaround */ + for(bitCounter = 1; bitCounter >= 0 ; bitCounter--) + if (write_cmd) + e100_send_mdio_bit(GET_BIT(bitCounter, data)); + else + e100_receive_mdio_bit(); +} + +static void +e100_send_mdio_bit(unsigned char bit) +{ + volatile int i; + *R_NETWORK_MGM_CTRL = 2 | bit&1; + for (i=40; i; i--); + *R_NETWORK_MGM_CTRL = 6 | bit&1; + for (i=40; i; i--); +} + +static unsigned char +e100_receive_mdio_bit() +{ + unsigned char bit; + volatile int i; + *R_NETWORK_MGM_CTRL = 0; + bit = *R_NETWORK_STAT & 1; + for (i=40; i; i--); + *R_NETWORK_MGM_CTRL = 4; + for (i=40; i; i--); + return bit; +} + +static void +e100_reset_tranceiver(void) +{ + unsigned long flags; + unsigned short cmd; + unsigned short data; + int bitCounter; + + save_flags(flags); + cli(); + + data = e100_get_mdio_reg(MDIO_BASE_CONTROL_REG); + + cmd = (MDIO_START << 14) | (MDIO_WRITE << 12) | (MDIO_PHYS_ADDR << 7) | (MDIO_BASE_CONTROL_REG << 2); + + e100_send_mdio_cmd(cmd, 0); + + data |= 0x8000; + + for(bitCounter = 15; bitCounter >= 0 ; bitCounter--) { + e100_send_mdio_bit(GET_BIT(bitCounter, data)); + } + + restore_flags(flags); +} + +/* Called by upper layers if they decide it took too long to complete + * sending a packet - we need to reset and stuff. + */ + +static void +e100_tx_timeout(struct net_device *dev) +{ + struct net_local *np = (struct net_local *)dev->priv; + + printk(KERN_WARNING "%s: transmit timed out, %s?\n", dev->name, + tx_done(dev) ? "IRQ problem" : "network cable problem"); + + /* remember we got an error */ + + np->stats.tx_errors++; + + /* reset the TX DMA in case it has hung on something */ + + RESET_DMA(0); + WAIT_DMA(0); + + /* Reset the tranceiver. */ + + e100_reset_tranceiver(); + + /* and get rid of the packet that never got an interrupt */ + + dev_kfree_skb(tx_skb); + tx_skb = 0; + + /* tell the upper layers we're ok again */ + + netif_wake_queue(dev); +} + + +/* This will only be invoked if the driver is _not_ in XOFF state. + * What this means is that we need not check it, and that this + * invariant will hold if we make sure that the netif_*_queue() + * calls are done at the proper times. + */ + +static int +e100_send_packet(struct sk_buff *skb, struct net_device *dev) +{ + struct net_local *np = (struct net_local *)dev->priv; + int length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + unsigned char *buf = skb->data; + +#ifdef ETHDEBUG + printk("send packet len %d\n", length); +#endif + spin_lock_irq(&np->lock); /* protect from tx_interrupt */ + + tx_skb = skb; /* remember it so we can free it in the tx irq handler later */ + dev->trans_start = jiffies; + + e100_hardware_send_packet(buf, length); + + /* this simple TX driver has only one send-descriptor so we're full + * directly. If this had a send-ring instead, we would only do this if + * the ring got full. + */ + + netif_stop_queue(dev); + + spin_unlock_irq(&np->lock); + + return 0; +} + +/* + * The typical workload of the driver: + * Handle the network interface interrupts. + */ + +static void +e100rx_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct net_device *dev = (struct net_device *)dev_id; + unsigned long irqbits = *R_IRQ_MASK2_RD; + + if(irqbits & (1U << 3)) { + + /* acknowledge the eop interrupt */ + + *R_DMA_CH1_CLR_INTR = IO_STATE(R_DMA_CH1_CLR_INTR, clr_eop, do); + + /* check if one or more complete packets were indeed received */ + + while(*R_DMA_CH1_FIRST != virt_to_phys(myNextRxDesc)) { + /* Take out the buffer and give it to the OS, then + * allocate a new buffer to put a packet in. + */ + e100_rx(dev); + ((struct net_local *)dev->priv)->stats.rx_packets++; + *R_DMA_CH1_CMD = 3; /* restart/continue on the channel, for safety */ + *R_DMA_CH1_CLR_INTR = 3; /* clear dma channel 1 eop/descr irq bits */ + /* now, we might have gotten another packet so we have to loop back + and check if so */ + } + } +} + +/* the transmit dma channel interrupt + * + * this is supposed to free the skbuff which was pending during transmission, + * and inform the kernel that we can send one more buffer + */ + +static void +e100tx_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct net_device *dev = (struct net_device *)dev_id; + unsigned long irqbits = *R_IRQ_MASK2_RD; + struct net_local *np = (struct net_local *)dev->priv; + + if(irqbits & 2) { /* check for a dma0_eop interrupt */ + + /* This protects us from concurrent execution of + * our dev->hard_start_xmit function above. + */ + + spin_lock(&np->lock); + + /* acknowledge the eop interrupt */ + + *R_DMA_CH0_CLR_INTR = IO_STATE(R_DMA_CH0_CLR_INTR, clr_eop, do); + + if(*R_DMA_CH0_FIRST == 0 && tx_skb) { + np->stats.tx_bytes += tx_skb->len; + np->stats.tx_packets++; + /* dma is ready with the transmission of the data in tx_skb, so now + we can release the skb memory */ + dev_kfree_skb_irq(tx_skb); + tx_skb = 0; + netif_wake_queue(dev); + } else { + printk("tx weird interrupt\n"); + } + + spin_unlock(&np->lock); + } +} + +static void +e100nw_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct net_local *np = (struct net_local *)dev->priv; + unsigned long irqbits = *R_IRQ_MASK0_RD; + + if(irqbits & (1 << 19)) { /* check for overrun irq */ + update_rx_stats(&np->stats); /* this will ack the irq */ + D(printk("ethernet receiver overrun!\n")); + } + if(irqbits & (1 << 17)) { /* check for excessive collision irq */ + *R_NETWORK_TR_CTRL = 1 << 8; /* clear the interrupt */ + np->stats.tx_errors++; + D(printk("ethernet excessive collisions!\n")); + } + +} + +/* We have a good packet(s), get it/them out of the buffers. */ +static void +e100_rx(struct net_device *dev) +{ + struct sk_buff *skb; + int length=0; + int i; + struct etrax_dma_descr *mySaveRxDesc = myNextRxDesc; + unsigned char *skb_data_ptr; + + /* light the network rx packet depending on the current speed. + ** But only if link has been detected. + */ + if (!nolink) + if (current_speed == 10) { + LED_NETWORK_TX_SET(1); + LED_NETWORK_RX_SET(1); + } else + LED_NETWORK_RX_SET(1); + + /* Set the earliest time we may clear the LED */ + + led_clear_time = jiffies + NET_FLASH_TIME; + + /* If the packet is broken down in many small packages then merge + * count how much space we will need to alloc with skb_alloc() for + * it to fit. + */ + + while (!(myNextRxDesc->status & d_eop)) { + length += myNextRxDesc->sw_len; /* use sw_len for the first descs */ + myNextRxDesc->status = 0; + myNextRxDesc = phys_to_virt(myNextRxDesc->next); + } + + length += myNextRxDesc->hw_len; /* use hw_len for the last descr */ + +#ifdef ETHDEBUG + printk("Got a packet of length %d:\n", length); + /* dump the first bytes in the packet */ + skb_data_ptr = (unsigned char *)phys_to_virt(mySaveRxDesc->buf); + for(i = 0; i < 8; i++) { + printk("%d: %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x\n", i * 8, + skb_data_ptr[0],skb_data_ptr[1],skb_data_ptr[2],skb_data_ptr[3], + skb_data_ptr[4],skb_data_ptr[5],skb_data_ptr[6],skb_data_ptr[7]); + skb_data_ptr += 8; + } +#endif + + skb = dev_alloc_skb(length - ETHER_HEAD_LEN); + if (!skb) { + printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", + dev->name); + return; + } + + skb_put(skb, length - ETHER_HEAD_LEN); /* allocate room for the packet body */ + skb_data_ptr = skb_push(skb, ETHER_HEAD_LEN); /* allocate room for the header */ + +#if 0 + printk("head = 0x%x, data = 0x%x, tail = 0x%x, end = 0x%x\n", + skb->head, skb->data, skb->tail, skb->end); + printk("copying packet to 0x%x.\n", skb_data_ptr); +#endif + + /* this loop can be made using max two memcpy's if optimized */ + + while(mySaveRxDesc != myNextRxDesc) { + memcpy(skb_data_ptr, phys_to_virt(mySaveRxDesc->buf), + mySaveRxDesc->sw_len); + skb_data_ptr += mySaveRxDesc->sw_len; + mySaveRxDesc = phys_to_virt(mySaveRxDesc->next); + } + + memcpy(skb_data_ptr, phys_to_virt(mySaveRxDesc->buf), + mySaveRxDesc->hw_len); + + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + + /* Send the packet to the upper layers */ + + netif_rx(skb); + + /* Prepare for next packet */ + + myNextRxDesc->status = 0; + myPrevRxDesc = myNextRxDesc; + myNextRxDesc = phys_to_virt(myNextRxDesc->next); + + myPrevRxDesc->ctrl |= d_eol; + myLastRxDesc->ctrl &= ~d_eol; + myLastRxDesc = myPrevRxDesc; + + return; +} + +/* The inverse routine to net_open(). */ +static int +e100_close(struct net_device *dev) +{ + struct net_local *np = (struct net_local *)dev->priv; + + printk("Closing %s.\n", dev->name); + + netif_stop_queue(dev); + + *R_NETWORK_GEN_CONFIG = + IO_STATE(R_NETWORK_GEN_CONFIG, phy, mii_clk) | + IO_STATE(R_NETWORK_GEN_CONFIG, enable, off); + + *R_IRQ_MASK0_CLR = 0xe0000; /* clear excessive_col, over/underrun irq mask */ + *R_IRQ_MASK2_CLR = 0xf; /* clear dma0 and 1 eop and descr irq masks */ + + /* Stop the receiver and the transmitter */ + + RESET_DMA(0); + RESET_DMA(1); + + /* Flush the Tx and disable Rx here. */ + + free_irq(NETWORK_DMARX_IRQ, NULL); + free_irq(NETWORK_DMATX_IRQ, NULL); + free_irq(NETWORK_STATUS_IRQ, NULL); + + free_dma(0); + free_dma(1); + + /* Update the statistics here. */ + + update_rx_stats(&np->stats); + update_tx_stats(&np->stats); + + return 0; +} + +static void +update_rx_stats(struct net_device_stats *es) +{ + unsigned long r = *R_REC_COUNTERS; + /* update stats relevant to reception errors */ + es->rx_fifo_errors += r >> 24; /* fifo overrun */ + es->rx_crc_errors += r & 0xff; /* crc error */ + es->rx_frame_errors += (r >> 8) & 0xff; /* alignment error */ + es->rx_length_errors += (r >> 16) & 0xff; /* oversized frames */ +} + +static void +update_tx_stats(struct net_device_stats *es) +{ + unsigned long r = *R_TR_COUNTERS; + /* update stats relevant to transmission errors */ + es->collisions += (r & 0xff) + ((r >> 8) & 0xff); /* single_col + multiple_col */ + es->tx_errors += (r >> 24) & 0xff; /* deferred transmit frames */ +} + +/* + * Get the current statistics. + * This may be called with the card open or closed. + */ +static struct net_device_stats * +e100_get_stats(struct net_device *dev) +{ + struct net_local *lp = (struct net_local *)dev->priv; + + update_rx_stats(&lp->stats); + update_tx_stats(&lp->stats); + + return &lp->stats; +} + +/* + * Set or clear the multicast filter for this adaptor. + * num_addrs == -1 Promiscuous mode, receive all packets + * num_addrs == 0 Normal mode, clear multicast list + * num_addrs > 0 Multicast mode, receive normal and MC packets, + * and do best-effort filtering. + */ +static void +set_multicast_list(struct net_device *dev) +{ + int num_addr = dev->mc_count; + unsigned long int lo_bits; + unsigned long int hi_bits; + if (num_addr == -1) + { + /* promiscuous mode */ + lo_bits = 0xfffffffful; + hi_bits = 0xfffffffful; + } else if (num_addr == 0) { + /* Normal, clear the mc list */ + lo_bits = 0x00000000ul; + hi_bits = 0x00000000ul; + } else { + /* MC mode, receive normal and MC packets */ + char hash_ix; + struct dev_mc_list *dmi = dev->mc_list; + int i; + char *baddr; + lo_bits = 0x00000000ul; + hi_bits = 0x00000000ul; + for (i=0; i<num_addr; i++) { + /* Calculate the hash index for the GA registers */ + + hash_ix = 0; + baddr = dmi->dmi_addr; + hash_ix ^= (*baddr) & 0x3f; + hash_ix ^= ((*baddr) >> 6) & 0x03; + ++baddr; + hash_ix ^= ((*baddr) << 2) & 0x03c; + hash_ix ^= ((*baddr) >> 4) & 0xf; + ++baddr; + hash_ix ^= ((*baddr) << 4) & 0x30; + hash_ix ^= ((*baddr) >> 2) & 0x3f; + ++baddr; + hash_ix ^= (*baddr) & 0x3f; + hash_ix ^= ((*baddr) >> 6) & 0x03; + ++baddr; + hash_ix ^= ((*baddr) << 2) & 0x03c; + hash_ix ^= ((*baddr) >> 4) & 0xf; + ++baddr; + hash_ix ^= ((*baddr) << 4) & 0x30; + hash_ix ^= ((*baddr) >> 2) & 0x3f; + + hash_ix &= 0x3f; + + if (hash_ix > 32) { + hi_bits |= (1 << (hash_ix-32)); + } + else { + lo_bits |= (1 << hash_ix); + } + dmi = dmi->next; + } + } + *R_NETWORK_GA_0 = lo_bits; + *R_NETWORK_GA_1 = hi_bits; +} + +void +e100_hardware_send_packet(char *buf, int length) +{ + D(printk("e100 send pack, buf 0x%x len %d\n", buf, length)); + /* light the network leds depending on the current speed. + ** But only if link has been detected. + */ + if (!nolink) { + if (current_speed == 10) { + LED_NETWORK_TX_SET(1); + LED_NETWORK_RX_SET(1); + } else { + LED_NETWORK_RX_SET(1); + } + } + + /* Set the earliest time we may clear the LED */ + + led_clear_time = jiffies + NET_FLASH_TIME; + + /* configure the tx dma descriptor */ + + TxDesc.sw_len = length; + TxDesc.ctrl = d_eop | d_eol | d_wait; + TxDesc.buf = virt_to_phys(buf); + + /* setup the dma channel and start it */ + + *R_DMA_CH0_FIRST = virt_to_phys(&TxDesc); + *R_DMA_CH0_CMD = IO_STATE(R_DMA_CH0_CMD, cmd, start); +} + +static void +e100_clear_network_leds(unsigned long dummy) +{ + if (jiffies > led_clear_time) { + if (nolink) + LED_NETWORK_TX_SET(1); + else + LED_NETWORK_TX_SET(0); + LED_NETWORK_RX_SET(0); + } + + clear_led_timer.expires = jiffies + 10; + add_timer(&clear_led_timer); +} + +static struct net_device dev_etrax_ethernet; /* only got one */ + +static int +etrax_init_module(void) +{ + struct net_device *d = &dev_etrax_ethernet; + + d->init = etrax_ethernet_init; + + if(register_netdev(d) == 0) + return 0; + else + return -ENODEV; +} + +module_init(etrax_init_module); diff --git a/arch/cris/drivers/ide.c b/arch/cris/drivers/ide.c new file mode 100644 index 000000000..e352fdf32 --- /dev/null +++ b/arch/cris/drivers/ide.c @@ -0,0 +1,818 @@ +/* $Id: ide.c,v 1.4 2001/01/10 21:14:32 bjornw Exp $ + * + * Etrax specific IDE functions, like init and PIO-mode setting etc. + * Almost the entire ide.c is used for the rest of the Etrax ATA driver. + * Copyright (c) 2000, 2001 Axis Communications AB + * + * Authors: Bjorn Wesen (initial version) + * Mikael Starvik (pio setup stuff) + * + * $Log: ide.c,v $ + * Revision 1.4 2001/01/10 21:14:32 bjornw + * Initialize hwif->ideproc, for the new way of handling ide_xxx_data + * + * Revision 1.3 2000/12/01 17:48:18 bjornw + * - atapi_output_bytes now uses DMA + * - dma_active check removed - the kernel does proper serializing and it had + * a race-condition anyway + * - ide_build_dmatable had a nameclash + * - re-added the RESET_DMA thingys because sometimes the interface can get + * stuck apparently + * - added ide_release_dma + * + * Revision 1.2 2000/11/29 17:31:29 bjornw + * 2.4 port + * + * - The "register addresses" stored in the hwif are now 32-bit fields that + * don't need to be shifted into correct positions in R_ATA_CTRL_DATA + * - PIO-mode detection temporarily disabled since ide-modes.c is not compiled + * - All DMA uses virt_to_phys conversions for DMA buffers and descriptor ptrs + * - Probably correct ide_dma_begin semantics in dmaproc now for ATAPI devices + * - Removed RESET_DMA when starting a new transfer - why was this necessary ? + * - Indentation fix + * + * + */ + +/* Regarding DMA: + * + * There are two forms of DMA - "DMA handshaking" between the interface and the drive, + * and DMA between the memory and the interface. We can ALWAYS use the latter, since it's + * something built-in in the Etrax. However only some drives support the DMA-mode handshaking + * on the ATA-bus. The normal PC driver and Triton interface disables memory-if DMA when the + * device can't do DMA handshaking for some stupid reason. We don't need to do that. + */ + +#undef REALLY_SLOW_IO /* most systems can safely undef this */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/svinto.h> + +/* number of Etrax DMA descriptors */ +#define MAX_DMA_DESCRS 64 + +#define LOWDB(x) +#define D(x) + +void OUT_BYTE(unsigned char data, ide_ioreg_t reg) { + LOWDB(printk("ob: data 0x%x, reg 0x%x\n", data, reg)); + while(*R_ATA_STATUS_DATA & IO_MASK(R_ATA_STATUS_DATA, busy)); /* wait for busy flag */ + *R_ATA_CTRL_DATA = reg | data; /* write data to the drive's register */ + while(!(*R_ATA_STATUS_DATA & + IO_MASK(R_ATA_STATUS_DATA, tr_rdy))); /* wait for transmitter ready */ +} + +unsigned char IN_BYTE(ide_ioreg_t reg) { + int status; + while(*R_ATA_STATUS_DATA & IO_MASK(R_ATA_STATUS_DATA, busy)); /* wait for busy flag */ + *R_ATA_CTRL_DATA = reg | IO_STATE(R_ATA_CTRL_DATA, rw, read); /* read data */ + while(!((status = *R_ATA_STATUS_DATA) & + IO_MASK(R_ATA_STATUS_DATA, dav))); /* wait for available */ + LOWDB(printk("inb: 0x%x from reg 0x%x\n", status & 0xff, reg)); + return (unsigned char)status; /* data was in the lower 16 bits in the status reg */ +} + +/* PIO timing (in R_ATA_CONFIG) + * + * _____________________________ + * ADDRESS : ________/ + * + * _______________ + * DIOR : ____________/ \__________ + * + * _______________ + * DATA : XXXXXXXXXXXXXXXX_______________XXXXXXXX + * + * + * DIOR is unbuffered while address and data is buffered. + * This creates two problems: + * 1. The DIOR pulse is to early (because it is unbuffered) + * 2. The rise time of DIOR is long + * + * There are at least three different plausible solutions + * 1. Use a pad capable of larger currents in Etrax + * 2. Use an external buffer + * 3. Make the strobe pulse longer + * + * Some of the strobe timings below are modified to compensate + * for this. This implies a slight performance decrease. + * + * THIS SHOULD NEVER BE CHANGED! + * + * TODO: Is this true for the latest LX boards still ? + */ + +#define ATA_DMA2_STROBE 4 +#define ATA_DMA2_HOLD 0 +#define ATA_DMA1_STROBE 4 +#define ATA_DMA1_HOLD 1 +#define ATA_DMA0_STROBE 12 +#define ATA_DMA0_HOLD 9 +#define ATA_PIO4_SETUP 1 +#define ATA_PIO4_STROBE 5 +#define ATA_PIO4_HOLD 0 +#define ATA_PIO3_SETUP 1 +#define ATA_PIO3_STROBE 5 +#define ATA_PIO3_HOLD 1 +#define ATA_PIO2_SETUP 1 +#define ATA_PIO2_STROBE 6 +#define ATA_PIO2_HOLD 2 +#define ATA_PIO1_SETUP 2 +#define ATA_PIO1_STROBE 11 +#define ATA_PIO1_HOLD 4 +#define ATA_PIO0_SETUP 4 +#define ATA_PIO0_STROBE 19 +#define ATA_PIO0_HOLD 4 + +/* + * good_dma_drives() lists the model names (from "hdparm -i") + * of drives which do not support mword2 DMA but which are + * known to work fine with this interface under Linux. + */ + +const char *good_dma_drives[] = {"Micropolis 2112A", + "CONNER CTMA 4000", + "CONNER CTT8000-A", + NULL}; + +static void tune_e100_ide(ide_drive_t *drive, byte pio) +{ + unsigned long flags; + + pio = 4; + //pio = ide_get_best_pio_mode(drive, pio, 4, NULL); + + save_flags(flags); + cli(); + + /* set pio mode! */ + + switch(pio) { + case 0: + *R_ATA_CONFIG = ( IO_FIELD( R_ATA_CONFIG, enable, 1 ) | + IO_FIELD( R_ATA_CONFIG, dma_strobe, ATA_DMA2_STROBE ) | + IO_FIELD( R_ATA_CONFIG, dma_hold, ATA_DMA2_HOLD ) | + IO_FIELD( R_ATA_CONFIG, pio_setup, ATA_PIO0_SETUP ) | + IO_FIELD( R_ATA_CONFIG, pio_strobe, ATA_PIO0_STROBE ) | + IO_FIELD( R_ATA_CONFIG, pio_hold, ATA_PIO0_HOLD ) ); + break; + case 1: + *R_ATA_CONFIG = ( IO_FIELD( R_ATA_CONFIG, enable, 1 ) | + IO_FIELD( R_ATA_CONFIG, dma_strobe, ATA_DMA2_STROBE ) | + IO_FIELD( R_ATA_CONFIG, dma_hold, ATA_DMA2_HOLD ) | + IO_FIELD( R_ATA_CONFIG, pio_setup, ATA_PIO1_SETUP ) | + IO_FIELD( R_ATA_CONFIG, pio_strobe, ATA_PIO1_STROBE ) | + IO_FIELD( R_ATA_CONFIG, pio_hold, ATA_PIO1_HOLD ) ); + break; + case 2: + *R_ATA_CONFIG = ( IO_FIELD( R_ATA_CONFIG, enable, 1 ) | + IO_FIELD( R_ATA_CONFIG, dma_strobe, ATA_DMA2_STROBE ) | + IO_FIELD( R_ATA_CONFIG, dma_hold, ATA_DMA2_HOLD ) | + IO_FIELD( R_ATA_CONFIG, pio_setup, ATA_PIO2_SETUP ) | + IO_FIELD( R_ATA_CONFIG, pio_strobe, ATA_PIO2_STROBE ) | + IO_FIELD( R_ATA_CONFIG, pio_hold, ATA_PIO2_HOLD ) ); + break; + case 3: + *R_ATA_CONFIG = ( IO_FIELD( R_ATA_CONFIG, enable, 1 ) | + IO_FIELD( R_ATA_CONFIG, dma_strobe, ATA_DMA2_STROBE ) | + IO_FIELD( R_ATA_CONFIG, dma_hold, ATA_DMA2_HOLD ) | + IO_FIELD( R_ATA_CONFIG, pio_setup, ATA_PIO3_SETUP ) | + IO_FIELD( R_ATA_CONFIG, pio_strobe, ATA_PIO3_STROBE ) | + IO_FIELD( R_ATA_CONFIG, pio_hold, ATA_PIO3_HOLD ) ); + break; + case 4: + *R_ATA_CONFIG = ( IO_FIELD( R_ATA_CONFIG, enable, 1 ) | + IO_FIELD( R_ATA_CONFIG, dma_strobe, ATA_DMA2_STROBE ) | + IO_FIELD( R_ATA_CONFIG, dma_hold, ATA_DMA2_HOLD ) | + IO_FIELD( R_ATA_CONFIG, pio_setup, ATA_PIO4_SETUP ) | + IO_FIELD( R_ATA_CONFIG, pio_strobe, ATA_PIO4_STROBE ) | + IO_FIELD( R_ATA_CONFIG, pio_hold, ATA_PIO4_HOLD ) ); + break; + } + restore_flags(flags); +} + +static int e100_dmaproc (ide_dma_action_t func, ide_drive_t *drive); /* defined below */ +static void e100_ideproc (ide_ide_action_t func, ide_drive_t *drive, + void *buffer, unsigned int length); /* defined below */ + +void __init init_e100_ide (void) +{ + volatile unsigned int dummy; + int h; + + printk("ide: ETRAX 100LX built-in ATA DMA controller\n"); + + /* first fill in some stuff in the ide_hwifs fields */ + + for(h = 0; h < MAX_HWIFS; h++) { + ide_hwif_t *hwif = &ide_hwifs[h]; + hwif->chipset = ide_etrax100; + hwif->tuneproc = &tune_e100_ide; + hwif->dmaproc = &e100_dmaproc; + hwif->ideproc = &e100_ideproc; + } + /* actually reset and configure the etrax100 ide/ata interface */ + + /* de-assert bus-reset */ +#ifdef CONFIG_ETRAX_IDE_PB7_RESET + port_pb_dir_shadow = port_pb_dir_shadow | + IO_STATE(R_PORT_PB_DIR, dir7, output); + *R_PORT_PB_DIR = port_pb_dir_shadow; + REG_SHADOW_SET(R_PORT_PB_DATA, port_pb_data_shadow, 7, 1); +#endif +#ifdef CONFIG_ETRAX_IDE_G27_RESET + *R_PORT_G_DATA = 0; +#endif + + *R_ATA_CTRL_DATA = 0; + *R_ATA_TRANSFER_CNT = 0; + *R_ATA_CONFIG = 0; + + genconfig_shadow = (genconfig_shadow & + ~IO_MASK(R_GEN_CONFIG, dma2) & + ~IO_MASK(R_GEN_CONFIG, dma3) & + ~IO_MASK(R_GEN_CONFIG, ata)) | + ( IO_STATE( R_GEN_CONFIG, dma3, ata ) | + IO_STATE( R_GEN_CONFIG, dma2, ata ) | + IO_STATE( R_GEN_CONFIG, ata, select ) ); + + *R_GEN_CONFIG = genconfig_shadow; + +#ifdef CONFIG_ETRAX_IDE_CSE1_16_RESET + *(volatile long *)(MEM_CSE1_START | MEM_NON_CACHEABLE) = 0; +#endif + + /* wait some */ + + dummy = 1; + dummy = 2; + dummy = 3; + +#ifdef CONFIG_ETRAX_IDE_CSE1_16_RESET + *(volatile long *)(MEM_CSE1_START | MEM_NON_CACHEABLE) = 1 << 16; + *R_PORT_G_DATA = 0; /* de-assert bus-reset */ +#endif + + /* make a dummy read to set the ata controller in a proper state */ + dummy = *R_ATA_STATUS_DATA; + + *R_ATA_CONFIG = ( IO_FIELD( R_ATA_CONFIG, enable, 1 ) | + IO_FIELD( R_ATA_CONFIG, dma_strobe, ATA_DMA2_STROBE ) | + IO_FIELD( R_ATA_CONFIG, dma_hold, ATA_DMA2_HOLD ) | + IO_FIELD( R_ATA_CONFIG, pio_setup, ATA_PIO4_SETUP ) | + IO_FIELD( R_ATA_CONFIG, pio_strobe, ATA_PIO4_STROBE ) | + IO_FIELD( R_ATA_CONFIG, pio_hold, ATA_PIO4_HOLD ) ); + + *R_ATA_CTRL_DATA = ( IO_STATE( R_ATA_CTRL_DATA, rw, read) | + IO_FIELD( R_ATA_CTRL_DATA, addr, 1 ) ); + + while(*R_ATA_STATUS_DATA & IO_MASK(R_ATA_STATUS_DATA, busy)); /* wait for busy flag*/ + + *R_IRQ_MASK0_SET = ( IO_STATE( R_IRQ_MASK0_SET, ata_irq0, set ) | + IO_STATE( R_IRQ_MASK0_SET, ata_irq1, set ) | + IO_STATE( R_IRQ_MASK0_SET, ata_irq2, set ) | + IO_STATE( R_IRQ_MASK0_SET, ata_irq3, set ) ); + + printk("ide: waiting 10 seconds for drives to regain consciousness\n"); + + h = jiffies + 1000; + while(jiffies < h) ; + + /* reset the dma channels we will use */ + + RESET_DMA(2); + RESET_DMA(3); + WAIT_DMA(2); + WAIT_DMA(3); + +} + +static etrax_dma_descr mydescr; + +/* + * The following routines are mainly used by the ATAPI drivers. + * + * These routines will round up any request for an odd number of bytes, + * so if an odd bytecount is specified, be sure that there's at least one + * extra byte allocated for the buffer. + */ +static void +e100_atapi_input_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount) +{ + ide_ioreg_t data_reg = IDE_DATA_REG; + unsigned long status; + + D(printk("atapi_input_bytes, dreg 0x%x, buffer 0x%x, count %d\n", + data_reg, buffer, bytecount)); + + if(bytecount & 1) { + printk("warning, odd bytecount in cdrom_in_bytes = %d.\n", bytecount); + bytecount++; /* to round off */ + } + + /* make sure the DMA channel is available */ + RESET_DMA(3); + WAIT_DMA(3); + + /* setup DMA descriptor */ + + mydescr.sw_len = bytecount; + mydescr.ctrl = d_eol; + mydescr.buf = virt_to_phys(buffer); + + /* start the dma channel */ + + *R_DMA_CH3_FIRST = virt_to_phys(&mydescr); + *R_DMA_CH3_CMD = IO_STATE(R_DMA_CH3_CMD, cmd, start); + + /* initiate a multi word dma read using PIO handshaking */ + + *R_ATA_TRANSFER_CNT = bytecount >> 1; + + *R_ATA_CTRL_DATA = data_reg | + IO_STATE(R_ATA_CTRL_DATA, rw, read) | + IO_STATE(R_ATA_CTRL_DATA, src_dst, dma) | + IO_STATE(R_ATA_CTRL_DATA, handsh, pio) | + IO_STATE(R_ATA_CTRL_DATA, multi, on) | + IO_STATE(R_ATA_CTRL_DATA, dma_size, word); + + /* wait for completion */ + + LED_DISK_READ(1); + WAIT_DMA(3); + LED_DISK_READ(0); + +#if 0 + /* old polled transfer code */ + + /* initiate a multi word read */ + + *R_ATA_TRANSFER_CNT = wcount << 1; + + *R_ATA_CTRL_DATA = data_reg | + IO_STATE(R_ATA_CTRL_DATA, rw, read) | + IO_STATE(R_ATA_CTRL_DATA, src_dst, register) | + IO_STATE(R_ATA_CTRL_DATA, handsh, pio) | + IO_STATE(R_ATA_CTRL_DATA, multi, on) | + IO_STATE(R_ATA_CTRL_DATA, dma_size, word); + + /* svinto has a latency until the busy bit actually is set */ + + nop(); nop(); + nop(); nop(); + nop(); nop(); + nop(); nop(); + nop(); nop(); + + /* unit should be busy during multi transfer */ + while((status = *R_ATA_STATUS_DATA) & IO_MASK(R_ATA_STATUS_DATA, busy)) { + while(!(status & IO_MASK(R_ATA_STATUS_DATA, dav))) + status = *R_ATA_STATUS_DATA; + *ptr++ = (unsigned short)(status & 0xffff); + } +#endif +} + +static void +e100_atapi_output_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount) +{ + ide_ioreg_t data_reg = IDE_DATA_REG; + unsigned short *ptr = (unsigned short *)buffer; + unsigned long ctrl; + + D(printk("atapi_output_bytes, dreg 0x%x, buffer 0x%x, count %d\n", + data_reg, buffer, bytecount)); + + if(bytecount & 1) { + printk("odd bytecount %d in atapi_out_bytes!\n", bytecount); + bytecount++; + } + + /* make sure the DMA channel is available */ + RESET_DMA(2); + WAIT_DMA(2); + + /* setup DMA descriptor */ + + mydescr.sw_len = bytecount; + mydescr.ctrl = d_eol; + mydescr.buf = virt_to_phys(buffer); + + /* start the dma channel */ + + *R_DMA_CH2_FIRST = virt_to_phys(&mydescr); + *R_DMA_CH2_CMD = IO_STATE(R_DMA_CH2_CMD, cmd, start); + + /* initiate a multi word dma write using PIO handshaking */ + + *R_ATA_TRANSFER_CNT = bytecount >> 1; + + *R_ATA_CTRL_DATA = data_reg | + IO_STATE(R_ATA_CTRL_DATA, rw, write) | + IO_STATE(R_ATA_CTRL_DATA, src_dst, dma) | + IO_STATE(R_ATA_CTRL_DATA, handsh, pio) | + IO_STATE(R_ATA_CTRL_DATA, multi, on) | + IO_STATE(R_ATA_CTRL_DATA, dma_size, word); + + /* wait for completion */ + + LED_DISK_WRITE(1); + WAIT_DMA(2); + LED_DISK_WRITE(0); + +#if 0 + /* old polled write code */ + + while(*R_ATA_STATUS_DATA & IO_MASK(R_ATA_STATUS_DATA, busy)); /* wait for busy flag */ + + /* initiate a multi word write */ + + *R_ATA_TRANSFER_CNT = bytecount >> 1; + + ctrl = data_reg | + IO_STATE(R_ATA_CTRL_DATA, rw, write) | + IO_STATE(R_ATA_CTRL_DATA, src_dst, register) | + IO_STATE(R_ATA_CTRL_DATA, handsh, pio) | + IO_STATE(R_ATA_CTRL_DATA, multi, on) | + IO_STATE(R_ATA_CTRL_DATA, dma_size, word); + + LED_DISK_WRITE(1); + + /* Etrax will set busy = 1 until the multi pio transfer has finished + * and tr_rdy = 1 after each succesful word transfer. + * When the last byte has been transferred Etrax will first set tr_tdy = 1 + * and then busy = 0 (not in the same cycle). If we read busy before it + * has been set to 0 we will think that we should transfer more bytes + * and then tr_rdy would be 0 forever. This is solved by checking busy + * in the inner loop. + */ + + do { + *R_ATA_CTRL_DATA = ctrl | *ptr++; + while(!(*R_ATA_STATUS_DATA & IO_MASK(R_ATA_STATUS_DATA, tr_rdy)) && + (*R_ATA_STATUS_DATA & IO_MASK(R_ATA_STATUS_DATA, busy))); + } while(*R_ATA_STATUS_DATA & IO_MASK(R_ATA_STATUS_DATA, busy)); + + LED_DISK_WRITE(0); +#endif +} + +/* + * This is used for most PIO data transfers *from* the IDE interface + */ +static void +e100_ide_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount) +{ + e100_atapi_input_bytes(drive, buffer, wcount << 2); +} + +/* + * This is used for most PIO data transfers *to* the IDE interface + */ +static void +e100_ide_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount) +{ + e100_atapi_output_bytes(drive, buffer, wcount << 2); +} + +/* + * The multiplexor for ide_xxxput_data and atapi calls + */ +static void +e100_ideproc (ide_ide_action_t func, ide_drive_t *drive, + void *buffer, unsigned int length) +{ + switch (func) { + case ideproc_ide_input_data: + e100_ide_input_data(drive, buffer, length); + break; + case ideproc_ide_output_data: + e100_ide_input_data(drive, buffer, length); + break; + case ideproc_atapi_input_bytes: + e100_atapi_input_bytes(drive, buffer, length); + break; + case ideproc_atapi_output_bytes: + e100_atapi_output_bytes(drive, buffer, length); + break; + default: + printk("e100_ideproc: unsupported func %d!\n", func); + break; + } +} + +/* we only have one DMA channel on the chip for ATA, so we can keep these statically */ +static etrax_dma_descr ata_descrs[MAX_DMA_DESCRS]; +static unsigned int ata_tot_size; + +/* + * e100_ide_build_dmatable() prepares a dma request. + * Returns 0 if all went okay, returns 1 otherwise. + */ +static int e100_ide_build_dmatable (ide_drive_t *drive) +{ + struct request *rq = HWGROUP(drive)->rq; + struct buffer_head *bh = rq->bh; + unsigned long size, addr; + unsigned int count = 0; + + ata_tot_size = 0; + + do { + /* + * Determine addr and size of next buffer area. We assume that + * individual virtual buffers are always composed linearly in + * physical memory. For example, we assume that any 8kB buffer + * is always composed of two adjacent physical 4kB pages rather + * than two possibly non-adjacent physical 4kB pages. + */ + if (bh == NULL) { /* paging and tape requests have (rq->bh == NULL) */ + addr = virt_to_phys (rq->buffer); + size = rq->nr_sectors << 9; + } else { + /* group sequential buffers into one large buffer */ + addr = virt_to_phys (bh->b_data); + size = bh->b_size; + while ((bh = bh->b_reqnext) != NULL) { + if ((addr + size) != virt_to_phys (bh->b_data)) + break; + size += bh->b_size; + } + } + + /* did we run out of descriptors? */ + + if(count >= MAX_DMA_DESCRS) { + printk("%s: too few DMA descriptors\n", drive->name); + return 1; + } + + /* uh.. I'm lazy.. if size >= 65536, it should loop below and split it in + more than one descriptor */ + + if(size >= 65536) { + printk("too large ATA DMA request block, size = %d!\n", size); + return 1; + } + + /* however, this case is more difficult - R_ATA_TRANSFER_CNT cannot be more + than 65536 words per transfer, so in that case we need to either + 1) use a DMA interrupt to re-trigger R_ATA_TRANSFER_CNT and continue with + the descriptors, or + 2) simply do the request here, and get dma_intr to only ide_end_request on + those blocks that were actually set-up for transfer. + */ + + if(ata_tot_size + size >= 131072) { + printk("too large total ATA DMA request, %d + %d!\n", ata_tot_size, size); + return 1; + } + + /* ok we want to do IO at addr, size bytes. set up a new descriptor entry */ + + ata_descrs[count].sw_len = size; + ata_descrs[count].ctrl = 0; + ata_descrs[count].buf = addr; + ata_descrs[count].next = virt_to_phys(&ata_descrs[count + 1]); + count++; + ata_tot_size += size; + + } while (bh != NULL); + + if (count) { + /* set the end-of-list flag on the last descriptor */ + ata_descrs[count - 1].ctrl |= d_eol; + /* return and say all is ok */ + return 0; + } + + printk("%s: empty DMA table?\n", drive->name); + return 1; /* let the PIO routines handle this weirdness */ +} + +static int config_drive_for_dma (ide_drive_t *drive) +{ + const char **list; + struct hd_driveid *id = drive->id; + + if (id && (id->capability & 1)) { + /* Enable DMA on any drive that supports mword2 DMA */ + if ((id->field_valid & 2) && (id->dma_mword & 0x404) == 0x404) { + drive->using_dma = 1; + return 0; /* DMA enabled */ + } + + /* Consult the list of known "good" drives */ + list = good_dma_drives; + while (*list) { + if (!strcmp(*list++,id->model)) { + drive->using_dma = 1; + return 0; /* DMA enabled */ + } + } + } + return 1; /* DMA not enabled */ +} + +/* + * etrax_dma_intr() is the handler for disk read/write DMA interrupts + */ +static ide_startstop_t etrax_dma_intr (ide_drive_t *drive) +{ + int i, dma_stat; + byte stat; + + LED_DISK_READ(0); + LED_DISK_WRITE(0); + + dma_stat = HWIF(drive)->dmaproc(ide_dma_end, drive); + stat = GET_STAT(); /* get drive status */ + if (OK_STAT(stat,DRIVE_READY,drive->bad_wstat|DRQ_STAT)) { + if (!dma_stat) { + struct request *rq; + rq = HWGROUP(drive)->rq; + for (i = rq->nr_sectors; i > 0;) { + i -= rq->current_nr_sectors; + ide_end_request(1, HWGROUP(drive)); + } + return ide_stopped; + } + printk("%s: bad DMA status\n", drive->name); + } + return ide_error(drive, "dma_intr", stat); +} + +/* + * e100_dmaproc() initiates/aborts DMA read/write operations on a drive. + * + * The caller is assumed to have selected the drive and programmed the drive's + * sector address using CHS or LBA. All that remains is to prepare for DMA + * and then issue the actual read/write DMA/PIO command to the drive. + * + * For ATAPI devices, we just prepare for DMA and return. The caller should + * then issue the packet command to the drive and call us again with + * ide_dma_begin afterwards. + * + * Returns 0 if all went well. + * Returns 1 if DMA read/write could not be started, in which case + * the caller should revert to PIO for the current request. + */ + +static int e100_dmaproc (ide_dma_action_t func, ide_drive_t *drive) +{ + static unsigned int reading; /* static to support ide_dma_begin semantics */ + int atapi = 0; + + D(printk("e100_dmaproc func %d\n", func)); + + switch (func) { + case ide_dma_verbose: + return 0; + case ide_dma_check: + return config_drive_for_dma (drive); + case ide_dma_off: + case ide_dma_off_quietly: + /* ok.. we don't really need to do anything I think. */ + return 0; + case ide_dma_write: + reading = 0; + break; + case ide_dma_read: + reading = 1; + break; + case ide_dma_begin: + /* begin DMA, used by ATAPI devices which want to issue the + * appropriate IDE command themselves. + * + * they have already called ide_dma_read/write to set the + * static reading flag, now they call ide_dma_begin to do + * the real stuff. we tell our code below not to issue + * any IDE commands itself and jump into it. + */ + atapi++; + goto dma_begin; + case ide_dma_end: /* returns 1 on error, 0 otherwise */ + /* TODO: check if something went wrong with the DMA */ + return 0; + + default: + printk("e100_dmaproc: unsupported func %d\n", func); + return 1; + } + + /* ATAPI-devices (not disks) first call ide_dma_read/write to set the direction + * then they call ide_dma_begin after they have issued the appropriate drive command + * themselves to actually start the chipset DMA. so we just return here if we're + * not a diskdrive. + */ + + if (drive->media != ide_disk) + return 0; + + dma_begin: + + if(reading) { + + RESET_DMA(3); /* sometimes the DMA channel get stuck so we need to do this */ + WAIT_DMA(3); + + /* set up the Etrax DMA descriptors */ + + if(e100_ide_build_dmatable (drive)) + return 1; + + if(!atapi) { + /* set the irq handler which will finish the request when DMA is done */ + + ide_set_handler(drive, &etrax_dma_intr, WAIT_CMD, NULL); + + /* issue cmd to drive */ + + OUT_BYTE(WIN_READDMA, IDE_COMMAND_REG); + } + + /* begin DMA */ + + *R_DMA_CH3_FIRST = virt_to_phys(ata_descrs); + *R_DMA_CH3_CMD = IO_STATE(R_DMA_CH3_CMD, cmd, start); + + /* initiate a multi word dma read using DMA handshaking */ + + *R_ATA_TRANSFER_CNT = ata_tot_size >> 1; + + *R_ATA_CTRL_DATA = IDE_DATA_REG | + IO_STATE(R_ATA_CTRL_DATA, rw, read) | + IO_STATE(R_ATA_CTRL_DATA, src_dst, dma) | + IO_STATE(R_ATA_CTRL_DATA, handsh, dma) | + IO_STATE(R_ATA_CTRL_DATA, multi, on) | + IO_STATE(R_ATA_CTRL_DATA, dma_size, word); + + LED_DISK_READ(1); + + D(printk("dma read of %d bytes.\n", ata_tot_size)); + + } else { + /* writing */ + + RESET_DMA(2); /* sometimes the DMA channel get stuck so we need to do this */ + WAIT_DMA(2); + + /* set up the Etrax DMA descriptors */ + + if(e100_ide_build_dmatable (drive)) + return 1; + + if(!atapi) { + /* set the irq handler which will finish the request when DMA is done */ + + ide_set_handler(drive, &etrax_dma_intr, WAIT_CMD, NULL); + + /* issue cmd to drive */ + + OUT_BYTE(WIN_WRITEDMA, IDE_COMMAND_REG); + } + + /* begin DMA */ + + *R_DMA_CH2_FIRST = virt_to_phys(ata_descrs); + *R_DMA_CH2_CMD = IO_STATE(R_DMA_CH2_CMD, cmd, start); + + /* initiate a multi word dma write using DMA handshaking */ + + *R_ATA_TRANSFER_CNT = ata_tot_size >> 1; + + *R_ATA_CTRL_DATA = IDE_DATA_REG | + IO_STATE(R_ATA_CTRL_DATA, rw, write) | + IO_STATE(R_ATA_CTRL_DATA, src_dst, dma) | + IO_STATE(R_ATA_CTRL_DATA, handsh, dma) | + IO_STATE(R_ATA_CTRL_DATA, multi, on) | + IO_STATE(R_ATA_CTRL_DATA, dma_size, word); + + LED_DISK_WRITE(1); + + D(printk("dma write of %d bytes.\n", ata_tot_size)); + } + + /* DMA started successfully */ + return 0; +} + +/* ide.c calls this, but we don't need to do anything particular */ + +int ide_release_dma (ide_hwif_t *hwif) +{ + return 1; +} diff --git a/arch/cris/drivers/serial.c b/arch/cris/drivers/serial.c new file mode 100644 index 000000000..2dcb17a5e --- /dev/null +++ b/arch/cris/drivers/serial.c @@ -0,0 +1,3039 @@ +/* $Id: serial.c,v 1.6 2000/11/22 16:36:09 bjornw Exp $ + * + * Serial port driver for the ETRAX 100LX chip + * + * Copyright (C) 1998, 1999, 2000 Axis Communications AB + * + * Many, many authors. Based once upon a time on serial.c for 16x50. + * + * $Log: serial.c,v $ + * Revision 1.6 2000/11/22 16:36:09 bjornw + * Please marketing by using the correct case when spelling Etrax. + * + * Revision 1.5 2000/11/21 16:43:37 bjornw + * Fixed so it compiles under CONFIG_SVINTO_SIM + * + * Revision 1.4 2000/11/15 17:34:12 bjornw + * Added a timeout timer for flushing input channels. The interrupt-based + * fast flush system should be easy to merge with this later (works the same + * way, only with an irq instead of a system timer_list) + * + * Revision 1.3 2000/11/13 17:19:57 bjornw + * * Incredibly, this almost complete rewrite of serial.c worked (at least + * for output) the first time. + * + * Items worth noticing: + * + * No Etrax100 port 1 workarounds (does only compile on 2.4 anyway now) + * RS485 is not ported (why cant it be done in userspace as on x86 ?) + * Statistics done through async_icount - if any more stats are needed, + * that's the place to put them or in an arch-dep version of it. + * timeout_interrupt and the other fast timeout stuff not ported yet + * There be dragons in this 3k+ line driver + * + * Revision 1.2 2000/11/10 16:50:28 bjornw + * First shot at a 2.4 port, does not compile totally yet + * + * Revision 1.1 2000/11/10 16:47:32 bjornw + * Added verbatim copy of rev 1.49 etrax100ser.c from elinux + * + * Revision 1.49 2000/10/30 15:47:14 tobiasa + * Changed version number. + * + * Revision 1.48 2000/10/25 11:02:43 johana + * Changed %ul to %lu in printf's + * + * Revision 1.47 2000/10/18 15:06:53 pkj + * Compile correctly with CONFIG_ETRAX100_SERIAL_FLUSH_DMA_FAST and + * CONFIG_SERIAL_PROC_ENTRY together. + * Some clean-up of the /proc/serial file. + * + * Revision 1.46 2000/10/16 12:59:40 johana + * Added CONFIG_SERIAL_PROC_ENTRY for statistics and debug info. + * + * Revision 1.45 2000/10/13 17:10:59 pkj + * Do not flush DMAs while flipping TTY buffers. + * + * Revision 1.44 2000/10/13 16:34:29 pkj + * Added a delay in ser_interrupt() for 2.3ms when an error is detected. + * We do not know why this delay is required yet, but without it the + * irmaflash program does not work (this was the program that needed + * the ser_interrupt() to be needed in the first place). This should not + * affect normal use of the serial ports. + * + * Revision 1.43 2000/10/13 16:30:44 pkj + * New version of the fast flush of serial buffers code. This time + * it is localized to the serial driver and uses a fast timer to + * do the work. + * + * Revision 1.42 2000/10/13 14:54:26 bennyo + * Fix for switching RTS when using rs485 + * + * Revision 1.41 2000/10/12 11:43:44 pkj + * Cleaned up a number of comments. + * + * Revision 1.40 2000/10/10 11:58:39 johana + * Made RS485 support generic for all ports. + * Toggle rts in interrupt if no delay wanted. + * WARNING: No true transmitter empty check?? + * Set d_wait bit when sending data so interrupt is delayed until + * fifo flushed. (Fix tcdrain() problem) + * + * Revision 1.39 2000/10/04 16:08:02 bjornw + * * Use virt_to_phys etc. for DMA addresses + * * Removed CONFIG_FLUSH_DMA_FAST hacks + * * Indentation fix + * + * Revision 1.38 2000/10/02 12:27:10 mattias + * * added variable used when using fast flush on serial dma. + * (CONFIG_FLUSH_DMA_FAST) + * + * Revision 1.37 2000/09/27 09:44:24 pkj + * Uncomment definition of SERIAL_HANDLE_EARLY_ERRORS. + * + * Revision 1.36 2000/09/20 13:12:52 johana + * Support for CONFIG_ETRAX100_SERIAL_RX_TIMEOUT_TICKS: + * Number of timer ticks between flush of receive fifo (1 tick = 10ms). + * Try 0-3 for low latency applications. Approx 5 for high load + * applications (e.g. PPP). Maybe this should be more adaptive some day... + * + * Revision 1.35 2000/09/20 10:36:08 johana + * Typo in get_lsr_info() + * + * Revision 1.34 2000/09/20 10:29:59 johana + * Let rs_chars_in_buffer() check fifo content as well. + * get_lsr_info() might work now (not tested). + * Easier to change the port to debug. + * + * Revision 1.33 2000/09/13 07:52:11 torbjore + * Support RS485 + * + * Revision 1.32 2000/08/31 14:45:37 bjornw + * After sending a break we need to reset the transmit DMA channel + * + * Revision 1.31 2000/06/21 12:13:29 johana + * Fixed wait for all chars sent when closing port. + * (Used to always take 1 second!) + * Added shadows for directions of status/ctrl signals. + * + * Revision 1.30 2000/05/29 16:27:55 bjornw + * Simulator ifdef moved a bit + * + * Revision 1.29 2000/05/09 09:40:30 mattias + * * Added description of dma registers used in timeout_interrupt + * * Removed old code + * + * Revision 1.28 2000/05/08 16:38:58 mattias + * * Bugfix for flushing fifo in timeout_interrupt + * Problem occurs when bluetooth stack waits for a small number of bytes + * containing an event acknowledging free buffers in bluetooth HW + * As before, data was stuck in fifo until more data came on uart and + * flushed it up to the stack. + * + * Revision 1.27 2000/05/02 09:52:28 jonasd + * Added fix for peculiar etrax behaviour when eop is forced on an empty + * fifo. This is used when flashing the IRMA chip. Disabled by default. + * + * Revision 1.26 2000/03/29 15:32:02 bjornw + * 2.0.34 updates + * + * Revision 1.25 2000/02/16 16:59:36 bjornw + * * Receive DMA directly into the flip-buffer, eliminating an intermediary + * receive buffer and a memcpy. Will avoid some overruns. + * * Error message on debug port if an overrun or flip buffer overrun occurs. + * * Just use the first byte in the flag flip buffer for errors. + * * Check for timeout on the serial ports only each 5/100 s, not 1/100. + * + * Revision 1.24 2000/02/09 18:02:28 bjornw + * * Clear serial errors (overrun, framing, parity) correctly. Before, the + * receiver would get stuck if an error occured and we did not restart + * the input DMA. + * * Cosmetics (indentation, some code made into inlines) + * * Some more debug options + * * Actually shut down the serial port (DMA irq, DMA reset, receiver stop) + * when the last open is closed. Corresponding fixes in startup(). + * * rs_close() "tx FIFO wait" code moved into right place, bug & -> && fixed + * and make a special case out of port 1 (R_DMA_CHx_STATUS is broken for that) + * * e100_disable_rx/enable_rx just disables/enables the receiver, not RTS + * + * Revision 1.23 2000/01/24 17:46:19 johana + * Wait for flush of DMA/FIFO when closing port. + * + * Revision 1.22 2000/01/20 18:10:23 johana + * Added TIOCMGET ioctl to return modem status. + * Implemented modem status/control that works with the extra signals + * (DTR, DSR, RI,CD) as well. + * 3 different modes supported: + * ser0 on PB (Bundy), ser1 on PB (Lisa) and ser2 on PA (Bundy) + * Fixed DEF_TX value that caused the serial transmitter pin (txd) to go to 0 when + * closing the last filehandle, NASTY!. + * Added break generation, not tested though! + * Use SA_SHIRQ when request_irq() for ser2 and ser3 (shared with) par0 and par1. + * You can't use them at the same time (yet..), but you can hopefully switch + * between ser2/par0, ser3/par1 with the same kernel config. + * Replaced some magic constants with defines + * + * + */ + +static char *serial_version = "$Revision: 1.6 $"; + +#include <linux/config.h> +#include <linux/version.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#if (LINUX_VERSION_CODE >= 131343) +#include <linux/init.h> +#endif +#if (LINUX_VERSION_CODE >= 131336) +#include <asm/uaccess.h> +#endif +#include <linux/kernel.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/bitops.h> +#include <asm/delay.h> + +#include <asm/svinto.h> + +/* non-arch dependant serial structures are in linux/serial.h */ +#include <linux/serial.h> +/* while we keep our own stuff (struct e100_serial) in a local .h file */ +#include "serial.h" + +/* + * All of the compatibilty code so we can compile serial.c against + * older kernels is hidden in serial_compat.h + */ +#if defined(LOCAL_HEADERS) || (LINUX_VERSION_CODE < 0x020317) /* 2.3.23 */ +#include "serial_compat.h" +#endif + +static DECLARE_TASK_QUEUE(tq_serial); + +struct tty_driver serial_driver, callout_driver; +static int serial_refcount; + +/* serial subtype definitions */ +#ifndef SERIAL_TYPE_NORMAL +#define SERIAL_TYPE_NORMAL 1 +#define SERIAL_TYPE_CALLOUT 2 +#endif + +/* number of characters left in xmit buffer before we ask for more */ +#define WAKEUP_CHARS 256 + +//#define SERIAL_DEBUG_INTR +//#define SERIAL_DEBUG_OPEN +//#define SERIAL_DEBUG_FLOW +//#define SERIAL_DEBUG_DATA +//#define SERIAL_DEBUG_THROTTLE +//#define SERIAL_DEBUG_IO /* Debug for Extra control and status pins */ +#define SERIAL_DEBUG_LINE 0 /* What serport we want to debug */ + +/* Enable this to use serial interrupts to handle when you + expect the first received event on the serial port to + be an error, break or similar. Used to be able to flash IRMA + from eLinux */ +//#define SERIAL_HANDLE_EARLY_ERRORS + + +#ifndef CONFIG_ETRAX100_SERIAL_RX_TIMEOUT_TICKS +/* Default number of timer ticks before flushing rx fifo + * When using "little data, low latency applications: use 0 + * When using "much data applications (PPP)" use ~5 + */ +#define CONFIG_ETRAX100_SERIAL_RX_TIMEOUT_TICKS 5 +#endif + +#define MAX_FLUSH_TIME 8 + +#define _INLINE_ inline + +static void change_speed(struct e100_serial *info); +static void rs_wait_until_sent(struct tty_struct *tty, int timeout); +static int rs_write(struct tty_struct * tty, int from_user, + const unsigned char *buf, int count); + +#define DEF_BAUD 0x99 /* 115.2 kbit/s */ +#define STD_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST ) +#define DEF_RX 0x20 /* or SERIAL_CTRL_W >> 8 */ +/* Default value of tx_ctrl register: has txd(bit 7)=1 (idle) as default */ +#define DEF_TX 0x80 /* or SERIAL_CTRL_B */ + +/* offsets from R_SERIALx_CTRL */ + +#define REG_DATA 0 +#define REG_TR_DATA 0 +#define REG_STATUS 1 +#define REG_TR_CTRL 1 +#define REG_REC_CTRL 2 +#define REG_BAUD 3 +#define REG_XOFF 4 /* this is a 32 bit register */ + +/* this is the data for the four serial ports in the etrax100 */ +/* DMA2(ser2), DMA4(ser3), DMA6(ser0) or DMA8(ser1) */ +/* R_DMA_CHx_CLR_INTR, R_DMA_CHx_FIRST, R_DMA_CHx_CMD */ + +static struct e100_serial rs_table[] = { + { DEF_BAUD, (unsigned char *)R_SERIAL0_CTRL, 1U << 12, /* uses DMA 6 and 7 */ + R_DMA_CH6_CLR_INTR, R_DMA_CH6_FIRST, R_DMA_CH6_CMD, + R_DMA_CH6_STATUS, R_DMA_CH6_HWSW, + R_DMA_CH7_CLR_INTR, R_DMA_CH7_FIRST, R_DMA_CH7_CMD, + R_DMA_CH7_STATUS, R_DMA_CH7_HWSW, + STD_FLAGS, DEF_RX, DEF_TX, 2 }, /* ttyS0 */ +#ifndef CONFIG_SVINTO_SIM + { DEF_BAUD, (unsigned char *)R_SERIAL1_CTRL, 1U << 16, /* uses DMA 8 and 9 */ + R_DMA_CH8_CLR_INTR, R_DMA_CH8_FIRST, R_DMA_CH8_CMD, + R_DMA_CH8_STATUS, R_DMA_CH8_HWSW, + R_DMA_CH9_CLR_INTR, R_DMA_CH9_FIRST, R_DMA_CH9_CMD, + R_DMA_CH9_STATUS, R_DMA_CH9_HWSW, + STD_FLAGS, DEF_RX, DEF_TX, 3 }, /* ttyS1 */ + + { DEF_BAUD, (unsigned char *)R_SERIAL2_CTRL, 1U << 4, /* uses DMA 2 and 3 */ + R_DMA_CH2_CLR_INTR, R_DMA_CH2_FIRST, R_DMA_CH2_CMD, + R_DMA_CH2_STATUS, R_DMA_CH2_HWSW, + R_DMA_CH3_CLR_INTR, R_DMA_CH3_FIRST, R_DMA_CH3_CMD, + R_DMA_CH3_STATUS, R_DMA_CH3_HWSW, + STD_FLAGS, DEF_RX, DEF_TX, 0 }, /* ttyS2 */ + + { DEF_BAUD, (unsigned char *)R_SERIAL3_CTRL, 1U << 8, /* uses DMA 4 and 5 */ + R_DMA_CH4_CLR_INTR, R_DMA_CH4_FIRST, R_DMA_CH4_CMD, + R_DMA_CH4_STATUS, R_DMA_CH4_HWSW, + R_DMA_CH5_CLR_INTR, R_DMA_CH5_FIRST, R_DMA_CH5_CMD, + R_DMA_CH5_STATUS, R_DMA_CH5_HWSW, + STD_FLAGS, DEF_RX, DEF_TX, 1 } /* ttyS3 */ +#endif +}; + + +#define NR_PORTS (sizeof(rs_table)/sizeof(struct e100_serial)) + +static struct tty_struct *serial_table[NR_PORTS]; +static struct termios *serial_termios[NR_PORTS]; +static struct termios *serial_termios_locked[NR_PORTS]; + + +/* RS-485 */ +#if defined(CONFIG_RS485) +#if defined(CONFIG_RS485_ON_PA) +static int rs485_pa_bit = CONFIG_RS485_ON_PA_BIT; +#endif +#endif + + +/* For now we assume that all bits are on the same port for each serial port */ + +/* Dummy shadow variables */ +static unsigned char dummy_ser0 = 0x00; +static unsigned char dummy_ser1 = 0x00; +static unsigned char dummy_ser2 = 0x00; +static unsigned char dummy_ser3 = 0x00; + +static unsigned char dummy_dir_ser0 = 0x00; +static unsigned char dummy_dir_ser1 = 0x00; +static unsigned char dummy_dir_ser2 = 0x00; +static unsigned char dummy_dir_ser3 = 0x00; + +/* Info needed for each ports extra control/status signals. + We only supports that all pins uses same register for each port */ +struct control_pins +{ + volatile unsigned char *port; + volatile unsigned char *shadow; + volatile unsigned char *dir_shadow; + + unsigned char dtr_bit; + unsigned char ri_bit; + unsigned char dsr_bit; + unsigned char cd_bit; +}; + +static const struct control_pins e100_modem_pins[NR_PORTS] = +{ +/* Ser 0 */ + { +#if defined(CONFIG_ETRAX100_SER0_DTR_RI_DSR_CD_ON_PB) + R_PORT_PB_DATA, &port_pb_data_shadow, &port_pb_dir_shadow, + CONFIG_ETRAX100_SER0_DTR_ON_PB_BIT, + CONFIG_ETRAX100_SER0_RI_ON_PB_BIT, + CONFIG_ETRAX100_SER0_DSR_ON_PB_BIT, + CONFIG_ETRAX100_SER0_CD_ON_PB_BIT +#else + &dummy_ser0, &dummy_ser0, &dummy_dir_ser0, 0, 1, 2, 3 +#endif + }, +/* Ser 1 */ + { +#if defined(CONFIG_ETRAX100_SER1_DTR_RI_DSR_CD_ON_PB) + R_PORT_PB_DATA, &port_pb_data_shadow, &port_pb_dir_shadow, + CONFIG_ETRAX100_SER1_DTR_ON_PB_BIT, + CONFIG_ETRAX100_SER1_RI_ON_PB_BIT, + CONFIG_ETRAX100_SER1_DSR_ON_PB_BIT, + CONFIG_ETRAX100_SER1_CD_ON_PB_BIT +#else + &dummy_ser1, &dummy_ser1, &dummy_dir_ser1, 0, 1, 2, 3 +#endif + }, +/* Ser 2 */ + { +#if defined(CONFIG_ETRAX100_SER2_DTR_RI_DSR_CD_ON_PA) + R_PORT_PA_DATA, &port_pa_data_shadow, &port_pa_dir_shadow, + CONFIG_ETRAX100_SER2_DTR_ON_PA_BIT, + CONFIG_ETRAX100_SER2_RI_ON_PA_BIT, + CONFIG_ETRAX100_SER2_DSR_ON_PA_BIT, + CONFIG_ETRAX100_SER2_CD_ON_PA_BIT +#else + &dummy_ser2, &dummy_ser2, &dummy_dir_ser2, 0, 1, 2, 3 +#endif + }, +/* Ser 3 */ + { + &dummy_ser3, &dummy_ser3, &dummy_dir_ser3, 0, 1, 2, 3 + } +}; + +#if defined(CONFIG_RS485) && defined(CONFIG_RS485_ON_PA) +unsigned char rs485_pa_port = CONFIG_RS485_ON_PA_BIT; +#endif + +#define E100_RTS_MASK 0x20 +#define E100_CTS_MASK 0x40 + + +/* All serial port signals are active low: + * active = 0 -> 3.3V to RS-232 driver -> -12V on RS-232 level + * inactive = 1 -> 0V to RS-232 driver -> +12V on RS-232 level + * + * These macros returns the pin value: 0=0V, >=1 = 3.3V on ETRAX chip + */ + +/* Output */ +#define E100_RTS_GET(info) ((info)->rx_ctrl & E100_RTS_MASK) +/* Input */ +#define E100_CTS_GET(info) ((info)->port[REG_STATUS] & E100_CTS_MASK) + +/* These are typically PA or PB and 0 means 0V, 1 means 3.3V */ +/* Is an output */ +#define E100_DTR_GET(info) ((*e100_modem_pins[(info)->line].shadow) & (1 << e100_modem_pins[(info)->line].dtr_bit)) + +/* Normally inputs */ +#define E100_RI_GET(info) ((*e100_modem_pins[(info)->line].port) & (1 << e100_modem_pins[(info)->line].ri_bit)) +#define E100_CD_GET(info) ((*e100_modem_pins[(info)->line].port) & (1 << e100_modem_pins[(info)->line].cd_bit)) + +/* Input */ +#define E100_DSR_GET(info) ((*e100_modem_pins[(info)->line].port) & (1 << e100_modem_pins[(info)->line].dsr_bit)) + + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +/* + * tmp_buf is used as a temporary buffer by serial_write. We need to + * lock it in case the memcpy_fromfs blocks while swapping in a page, + * and some other program tries to do a serial write at the same time. + * Since the lock will only come under contention when the system is + * swapping and available memory is low, it makes sense to share one + * buffer across all the serial ports, since it significantly saves + * memory if large numbers of serial ports are open. + */ +static unsigned char *tmp_buf; +#ifdef DECLARE_MUTEX +static DECLARE_MUTEX(tmp_buf_sem); +#else +static struct semaphore tmp_buf_sem = MUTEX; +#endif + +#ifdef CONFIG_ETRAX100_SERIAL_FLUSH_DMA_FAST +#define TIMER1_IRQ_NBR 3 + +/* clock select 10 for timer 1 gives 230400 Hz */ +#define FASTTIMER_SELECT (10) +/* we use a source of 230400 Hz and a divider of 15 => 15360 Hz */ +#define FASTTIMER_DIV (15) + +/* fast flush timer stuff */ +static int fast_timer_started = 0; +static unsigned long int fast_timer_ints = 0; + +static void _INLINE_ start_flush_timer(void) +{ + if (fast_timer_started) + return; + + *R_TIMER_CTRL = r_timer_ctrl_shadow = + (r_timer_ctrl_shadow & + ~IO_MASK(R_TIMER_CTRL, timerdiv1) & + ~IO_MASK(R_TIMER_CTRL, tm1) & + ~IO_MASK(R_TIMER_CTRL, clksel1)) | + IO_FIELD(R_TIMER_CTRL, timerdiv1, FASTTIMER_DIV) | + IO_STATE(R_TIMER_CTRL, tm1, stop_ld) | + IO_FIELD(R_TIMER_CTRL, clksel1, FASTTIMER_SELECT); + + *R_TIMER_CTRL = r_timer_ctrl_shadow = + (r_timer_ctrl_shadow & ~IO_MASK(R_TIMER_CTRL, tm1)) | + IO_STATE(R_TIMER_CTRL, tm1, run); + + /* enable timer1 irq */ + + *R_IRQ_MASK0_SET = IO_STATE(R_IRQ_MASK0_SET, timer1, set); + fast_timer_started = 1; +} +#endif /* CONFIG_ETRAX100_SERIAL_FLUSH_DMA_FAST */ + +/* + * This function maps from the Bxxxx defines in asm/termbits.h into real + * baud rates. + */ + +static int +cflag_to_baud(unsigned int cflag) +{ + static int baud_table[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }; + + static int ext_baud_table[] = { + 0, 57600, 115200, 230400, 460800, 921600, 1843200, 6250000 }; + + if(cflag & CBAUDEX) + return ext_baud_table[(cflag & CBAUD) & ~CBAUDEX]; + else + return baud_table[cflag & CBAUD]; +} + +/* and this maps to an etrax100 hardware baud constant */ + +static unsigned char +cflag_to_etrax_baud(unsigned int cflag) +{ + char retval; + + static char baud_table[] = { + -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, -1, 3, 4, 5, 6, 7 }; + + static char ext_baud_table[] = { + -1, 8, 9, 10, 11, 12, 13, 14 }; + + if(cflag & CBAUDEX) + retval = ext_baud_table[(cflag & CBAUD) & ~CBAUDEX]; + else + retval = baud_table[cflag & CBAUD]; + + if(retval < 0) { + printk("serdriver tried setting invalid baud rate, flags %x.\n", cflag); + retval = 5; /* choose default 9600 instead */ + } + + return retval | (retval << 4); /* choose same for both TX and RX */ +} + + +/* Various static support functions */ + +/* Functions to set or clear DTR/RTS on the requested line */ +/* It is complicated by the fact that RTS is a serial port register, while + * DTR might not be implemented in the HW at all, and if it is, it can be on + * any general port. + */ + +static inline void +e100_dtr(struct e100_serial *info, int set) +{ +#ifndef CONFIG_SVINTO_SIM + unsigned char mask = ( 1 << e100_modem_pins[info->line].dtr_bit); +#ifdef SERIAL_DEBUG_IO + printk("ser%i dtr %i mask: 0x%02X\n", info->line, set, mask); + printk("ser%i shadow before 0x%02X get: %i\n", + info->line, *e100_modem_pins[info->line].shadow, + E100_DTR_GET(info)); +#endif + /* DTR is active low */ + { + unsigned long flags; + save_flags(flags); + cli(); + *e100_modem_pins[info->line].shadow &= ~mask; + *e100_modem_pins[info->line].shadow |= (set ? 0 : mask); + *e100_modem_pins[info->line].port = *e100_modem_pins[info->line].shadow; + restore_flags(flags); + } + +#if 0 + REG_SHADOW_SET(e100_modem_pins[info->line].port, + *e100_modem_pins[info->line].shadow, + e100_modem_pins[info->line].dtr_bit, !set); +#endif +#ifdef SERIAL_DEBUG_IO + printk("ser%i shadow after 0x%02X get: %i\n", + info->line, *e100_modem_pins[info->line].shadow, + E100_DTR_GET(info)); +#endif +#endif +} + +/* set = 0 means 3.3V on the pin, bitvalue: 0=active, 1=inactive + * 0=0V , 1=3.3V + */ +static inline void +e100_rts(struct e100_serial *info, int set) +{ +#ifndef CONFIG_SVINTO_SIM +#ifdef SERIAL_DEBUG_IO + printk("ser%i rts %i\n", info->line, set); +#endif + info->rx_ctrl &= ~E100_RTS_MASK; + info->rx_ctrl |= (set ? 0 : E100_RTS_MASK); /* RTS is active low */ + info->port[REG_REC_CTRL] = info->rx_ctrl; +#endif +} + +/* If this behaves as a modem, RI and CD is an output */ +static inline void +e100_ri_out(struct e100_serial *info, int set) +{ +#ifndef CONFIG_SVINTO_SIM + /* RI is active low */ + { + unsigned char mask = ( 1 << e100_modem_pins[info->line].ri_bit); + unsigned long flags; + save_flags(flags); + cli(); + *e100_modem_pins[info->line].shadow &= ~mask; + *e100_modem_pins[info->line].shadow |= (set ? 0 : mask); + *e100_modem_pins[info->line].port = *e100_modem_pins[info->line].shadow; + restore_flags(flags); + } +#if 0 + REG_SHADOW_SET(e100_modem_pins[info->line].port, + *e100_modem_pins[info->line].shadow, + e100_modem_pins[info->line].ri_bit, !set); +#endif +#endif +} +static inline void +e100_cd_out(struct e100_serial *info, int set) +{ +#ifndef CONFIG_SVINTO_SIM + /* CD is active low */ + { + unsigned char mask = ( 1 << e100_modem_pins[info->line].cd_bit); + unsigned long flags; + save_flags(flags); + cli(); + *e100_modem_pins[info->line].shadow &= ~mask; + *e100_modem_pins[info->line].shadow |= (set ? 0 : mask); + *e100_modem_pins[info->line].port = *e100_modem_pins[info->line].shadow; + restore_flags(flags); + } +#if 0 + REG_SHADOW_SET(e100_modem_pins[info->line].port, + *e100_modem_pins[info->line].shadow, + e100_modem_pins[info->line].cd_bit, !set); +#endif +#endif +} + +static inline void +e100_disable_rx(struct e100_serial *info) +{ +#ifndef CONFIG_SVINTO_SIM + /* disable the receiver */ + info->port[REG_REC_CTRL] = (info->rx_ctrl &= ~0x40); +#endif +} + +static inline void +e100_enable_rx(struct e100_serial *info) +{ +#ifndef CONFIG_SVINTO_SIM + /* enable the receiver */ + info->port[REG_REC_CTRL] = (info->rx_ctrl |= 0x40); +#endif +} + +/* the rx DMA uses both the dma_descr and the dma_eop interrupts */ + +static inline void +e100_disable_rxdma_irq(struct e100_serial *info) +{ +#ifdef SERIAL_DEBUG_INTR + printk("rxdma_irq(%d): 0\n",info->line); +#endif + *R_IRQ_MASK2_CLR = (info->irq << 2) | (info->irq << 3); +} + +static inline void +e100_enable_rxdma_irq(struct e100_serial *info) +{ +#ifdef SERIAL_DEBUG_INTR + printk("rxdma_irq(%d): 1\n",info->line); +#endif + *R_IRQ_MASK2_SET = (info->irq << 2) | (info->irq << 3); +} + +/* the tx DMA uses only dma_descr interrupt */ + +static inline void +e100_disable_txdma_irq(struct e100_serial *info) +{ +#ifdef SERIAL_DEBUG_INTR + printk("txdma_irq(%d): 0\n",info->line); +#endif + *R_IRQ_MASK2_CLR = info->irq; +} + +static inline void +e100_enable_txdma_irq(struct e100_serial *info) +{ +#ifdef SERIAL_DEBUG_INTR + printk("txdma_irq(%d): 1\n",info->line); +#endif + *R_IRQ_MASK2_SET = info->irq; +} + +#ifdef SERIAL_HANDLE_EARLY_ERRORS +/* in order to detect and fix errors on the first byte + we have to use the serial interrupts as well. */ + +static inline void +e100_disable_serial_data_irq(struct e100_serial *info) +{ +#ifdef SERIAL_DEBUG_INTR + printk("ser_irq(%d): 0\n",info->line); +#endif + *R_IRQ_MASK1_CLR = (1U << (8+2*info->line)); +} + +static inline void +e100_enable_serial_data_irq(struct e100_serial *info) +{ +#ifdef SERIAL_DEBUG_INTR + printk("ser_irq(%d): 1\n",info->line); + printk("**** %d = %d\n", + (8+2*info->line), + (1U << (8+2*info->line))); +#endif + *R_IRQ_MASK1_SET = (1U << (8+2*info->line)); +} +#endif + +#if defined(CONFIG_RS485) +/* Enable RS-485 mode on selected port. This is UGLY. */ +static int +e100_enable_rs485(struct tty_struct *tty,struct rs485_control *r) +{ + struct e100_serial * info = (struct e100_serial *)tty->driver_data; + +#if defined(CONFIG_RS485_ON_PA) + *R_PORT_PA_DATA = port_pa_data_shadow |= (1 << rs485_pa_bit); +#endif + + info->rs485.rts_on_send = 0x01 & r->rts_on_send; + info->rs485.rts_after_sent = 0x01 & r->rts_after_sent; + info->rs485.delay_rts_before_send = r->delay_rts_before_send; + info->rs485.enabled = r->enabled; + + return 0; +} + +/* Enable RS-485 mode on selected port. This is UGLY. */ +static int +e100_write_rs485(struct tty_struct *tty,struct rs485_write *r) +{ + int stop_delay; + int total, i; + int max_j, delay_ms, bits; + tcflag_t cflags; + int size = (*r).outc_size; + struct e100_serial * info = (struct e100_serial *)tty->driver_data; + struct wait_queue wait = { current, NULL }; + + /* If we are in RS-485 mode, we need to toggle RTS and disable + * the receiver before initiating a DMA transfer + */ + e100_rts(info, info->rs485.rts_on_send); +#if defined(CONFIG_RS485_DISABLE_RECEIVER) + e100_disable_rx(info); + e100_disable_rxdma_irq(info); +#endif + + if (info->rs485.delay_rts_before_send > 0){ + current->timeout = jiffies + (info->rs485.delay_rts_before_send * HZ)/1000; + current->state = TASK_INTERRUPTIBLE; + schedule(); + current->timeout = 0; + } + total = rs_write(tty, 1, (*r).outc, (*r).outc_size); + + /* If we are in RS-485 mode the following things has to be done: + * wait until DMA is ready + * wait on transmit shift register + * wait to toggle RTS + * enable the receiver + */ + + /* wait on transmit shift register */ + /* All is sent, check if we should wait more before toggling rts */ + + /* calc. number of bits / data byte */ + cflags = info->tty->termios->c_cflag; + /* databits + startbit and 1 stopbit */ + if((cflags & CSIZE) == CS7) + bits = 9; + else + bits = 10; + + if(cflags & CSTOPB) /* 2 stopbits ? */ + bits++; + + if(cflags & PARENB) /* parity bit ? */ + bits++; + + /* calc timeout */ + delay_ms = ((bits * size * 1000) / info->baud) + 1; + max_j = jiffies + (delay_ms * HZ)/1000 + 10; + + while (jiffies < max_j ) { + if (info->port[REG_STATUS] & 0x20) { + for( i=0 ; i<100; i++ ) {}; + if (info->port[REG_STATUS] & 0x20) { + /* ~25 for loops per usec */ + stop_delay = 25 * (1000000 / info->baud); + if(cflags & CSTOPB) + stop_delay *= 2; + for( i=0 ; i<stop_delay; i++ ) {}; + break; + } + } + } + + e100_rts(info, info->rs485.rts_after_sent); + +#if defined(CONFIG_RS485_DISABLE_RECEIVER) + e100_enable_rx(info); + e100_enable_rxdma_irq(info); +#endif + + return total; +} +#endif + +/* + * ------------------------------------------------------------ + * rs_stop() and rs_start() + * + * This routines are called before setting or resetting tty->stopped. + * They enable or disable transmitter interrupts, as necessary. + * ------------------------------------------------------------ + */ + +/* FIXME - when are these used and what is the purpose ? + * In rs_stop we probably just can block the transmit DMA ready irq + * and in rs_start we re-enable it (and then the old one will come). + */ + +static void +rs_stop(struct tty_struct *tty) +{ +} + +static void +rs_start(struct tty_struct *tty) +{ +} + +/* + * ---------------------------------------------------------------------- + * + * Here starts the interrupt handling routines. All of the following + * subroutines are declared as inline and are folded into + * rs_interrupt(). They were separated out for readability's sake. + * + * Note: rs_interrupt() is a "fast" interrupt, which means that it + * runs with interrupts turned off. People who may want to modify + * rs_interrupt() should try to keep the interrupt handler as fast as + * possible. After you are done making modifications, it is not a bad + * idea to do: + * + * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c + * + * and look at the resulting assemble code in serial.s. + * + * - Ted Ts'o (tytso@mit.edu), 7-Mar-93 + * ----------------------------------------------------------------------- + */ + +/* + * This routine is used by the interrupt handler to schedule + * processing in the software interrupt portion of the driver. + */ +static _INLINE_ void +rs_sched_event(struct e100_serial *info, + int event) +{ + info->event |= 1 << event; + queue_task(&info->tqueue, &tq_serial); + mark_bh(SERIAL_BH); +} + +/* The output DMA channel is free - use it to send as many chars as possible + * NOTES: + * We don't pay attention to info->x_char, which means if the TTY wants to + * use XON/XOFF it will set info->x_char but we won't send any X char! + * + * To implement this, we'd just start a DMA send of 1 byte pointing at a + * buffer containing the X char, and skip updating xmit. We'd also have to + * check if the last sent char was the X char when we enter this function + * the next time, to avoid updating xmit with the sent X value. + */ + +static void +transmit_chars(struct e100_serial *info) +{ + unsigned int c, sentl; + struct etrax_dma_descr *descr; + +#ifdef CONFIG_SVINTO_SIM + /* This will output too little if tail is not 0 always since + * we don't reloop to send the other part. Anyway this SHOULD be a + * no-op - transmit_chars would never really be called during sim + * since rs_write does not write into the xmit buffer then. + */ + if(info->xmit.tail) + printk("Error in serial.c:transmit_chars(), tail!=0\n"); + if(info->xmit.head != info->xmit.tail) { + SIMCOUT(info->xmit.buf + info->xmit.tail, + CIRC_CNT(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE)); + info->xmit.head = info->xmit.tail; /* move back head */ + info->tr_running = 0; + } + return; +#endif + /* acknowledge both a dma_descr and dma_eop irq in R_DMAx_CLRINTR */ + *info->oclrintradr = 3; + +#ifdef SERIAL_DEBUG_INTR + if(info->line == SERIAL_DEBUG_LINE) + printk("tc\n"); +#endif + if(!info->tr_running) { + /* weirdo... we shouldn't get here! */ + printk("Achtung: transmit_chars with !tr_running\n"); + return; + } + + descr = &info->tr_descr; + + /* first get the amount of bytes sent during the last DMA transfer, + and update xmit accordingly */ + + /* if the stop bit was not set, all data has been sent */ + if(!(descr->status & d_stop)) { + sentl = descr->sw_len; + } else + /* otherwise we find the amount of data sent here */ + sentl = descr->hw_len; + + /* update stats */ + info->icount.tx += sentl; + + /* update xmit buffer */ + info->xmit.tail = (info->xmit.tail + sentl) & (SERIAL_XMIT_SIZE - 1); + + /* if there is only a few chars left in the buf, wake up the blocked + write if any */ + if (CIRC_CNT(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE) < WAKEUP_CHARS) + rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); + + /* find out the largest amount of consecutive bytes we want to send now */ + + c = CIRC_CNT_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); + + if(c <= 0) { + /* our job here is done, don't schedule any new DMA transfer */ + info->tr_running = 0; + +#if defined(CONFIG_RS485) + /* Check if we should toggle RTS now */ + if (info->rs485.enabled) + { + /* Make sure fifo is empty */ + int in_fifo = 0 ; + do{ + in_fifo = (*info->ostatusadr) & 0x007F ; + } while (in_fifo > 0) ; + /* Any way to really check transmitter empty? (TEMT) */ + /* Control RTS to set to RX mode */ + e100_rts(info, info->rs485.rts_after_sent); +#if defined(CONFIG_RS485_DISABLE_RECEIVER) + e100_enable_rx(info); + e100_enable_rxdma_irq(info); +#endif + } +#endif /* RS485 */ + + return; + } + + /* ok we can schedule a dma send of c chars starting at info->xmit.tail */ + /* set up the descriptor correctly for output */ + + descr->ctrl = d_int | d_eol | d_wait; /* Wait needed for tty_wait_until_sent() */ + descr->sw_len = c; + descr->buf = virt_to_phys(info->xmit.buf + info->xmit.tail); + descr->status = 0; + + *info->ofirstadr = virt_to_phys(descr); /* write to R_DMAx_FIRST */ + *info->ocmdadr = 1; /* dma command start -> R_DMAx_CMD */ + + /* DMA is now running (hopefully) */ + +} + +static void +start_transmit(struct e100_serial *info) +{ +#if 0 + if(info->line == SERIAL_DEBUG_LINE) + printk("x\n"); +#endif + + info->tr_descr.sw_len = 0; + info->tr_descr.hw_len = 0; + info->tr_descr.status = 0; + info->tr_running = 1; + + transmit_chars(info); +} + + +static _INLINE_ void +receive_chars(struct e100_serial *info) +{ + struct tty_struct *tty; + unsigned char rstat; + unsigned int recvl; + struct etrax_dma_descr *descr; + +#ifdef CONFIG_SVINTO_SIM + /* No receive in the simulator. Will probably be when the rest of + * the serial interface works, and this piece will just be removed. + */ + return; +#endif + + tty = info->tty; + + /* acknowledge both a dma_descr and dma_eop irq in R_DMAx_CLRINTR */ + + *info->iclrintradr = 3; + + if(!tty) /* something wrong... */ + return; + + descr = &info->rec_descr; + + /* find out how many bytes were read */ + + /* if the eop bit was not set, all data has been received */ + if(!(descr->status & d_eop)) { + recvl = descr->sw_len; + } else { + /* otherwise we find the amount of data received here */ + recvl = descr->hw_len; + } + if(recvl) { + unsigned char *buf; + struct async_icount *icount; + + icount = &info->icount; + + /* update stats */ + icount->rx += recvl; + + /* read the status register so we can detect errors */ + rstat = info->port[REG_STATUS]; + + if(rstat & 0xe) { + /* if we got an error, we must reset it by reading the + * data_in field + */ + (void)info->port[REG_DATA]; + } + + /* we only ever write errors into the first byte in the flip + * flag buffer, so we dont have to clear it all every time + */ + + if(rstat & 0x04) { + icount->parity++; + *tty->flip.flag_buf_ptr = TTY_PARITY; + } else if(rstat & 0x08) { + icount->overrun++; + *tty->flip.flag_buf_ptr = TTY_OVERRUN; + } else if(rstat & 0x02) { + icount->frame++; + *tty->flip.flag_buf_ptr = TTY_FRAME; + } else + *tty->flip.flag_buf_ptr = 0; + + /* use the flip buffer next in turn to restart DMA into */ + + if (tty->flip.buf_num) { + buf = tty->flip.char_buf; + } else { + buf = tty->flip.char_buf + TTY_FLIPBUF_SIZE; + } + + if(buf == phys_to_virt(descr->buf)) { + printk("ttyS%d flip-buffer overrun!\n", info->line); + icount->overrun++; + *tty->flip.flag_buf_ptr = TTY_OVERRUN; + /* restart old buffer */ + } else { + descr->buf = virt_to_phys(buf); + + /* schedule or push a flip of the buffer */ + + info->tty->flip.count = recvl; + +#if (LINUX_VERSION_CODE > 131394) /* 2.1.66 */ + /* this includes a check for low-latency */ + tty_flip_buffer_push(tty); +#else + queue_task_irq_off(&tty->flip.tqueue, &tq_timer); +#endif + } + } + + /* restart the receiving dma */ + + descr->sw_len = TTY_FLIPBUF_SIZE; + descr->ctrl = d_int | d_eol | d_eop; + descr->hw_len = 0; + descr->status = 0; + + *info->ifirstadr = virt_to_phys(descr); + *info->icmdadr = 1; /* start */ + +#ifdef SERIAL_HANDLE_EARLY_ERRORS + e100_enable_serial_data_irq(info); +#endif + /* input dma should be running now */ +} + +static void +start_receive(struct e100_serial *info) +{ + struct etrax_dma_descr *descr; + +#ifdef CONFIG_SVINTO_SIM + /* No receive in the simulator. Will probably be when the rest of + * the serial interface works, and this piece will just be removed. + */ + return; +#endif + + /* reset the input dma channel to be sure it works */ + + *info->icmdadr = 4; + while((*info->icmdadr & 7) == 4); + + descr = &info->rec_descr; + + /* start the receiving dma into the flip buffer */ + + descr->ctrl = d_int | d_eol | d_eop; + descr->sw_len = TTY_FLIPBUF_SIZE; + descr->buf = virt_to_phys(info->tty->flip.char_buf_ptr); + descr->hw_len = 0; + descr->status = 0; + + info->tty->flip.count = 0; + + *info->ifirstadr = virt_to_phys(descr); + *info->icmdadr = 1; /* start */ +} + + +static _INLINE_ void +status_handle(struct e100_serial *info, unsigned short status) +{ +} + +/* the bits in the MASK2 register are laid out like this: + DMAI_EOP DMAI_DESCR DMAO_EOP DMAO_DESCR + where I is the input channel and O is the output channel for the port. + info->irq is the bit number for the DMAO_DESCR so to check the others we + shift info->irq to the left. +*/ + +/* dma output channel interrupt handler + this interrupt is called from DMA2(ser2), DMA4(ser3), DMA6(ser0) or + DMA8(ser1) when they have finished a descriptor with the intr flag set. +*/ + +static void +tr_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct e100_serial *info; + unsigned long ireg; + int i; + +#ifdef CONFIG_SVINTO_SIM + /* No receive in the simulator. Will probably be when the rest of + * the serial interface works, and this piece will just be removed. + */ + { + const char *s = "What? tr_interrupt in simulator??\n"; + SIMCOUT(s,strlen(s)); + } + return; +#endif + + /* find out the line that caused this irq and get it from rs_table */ + + ireg = *R_IRQ_MASK2_RD; /* get the active irq bits for the dma channels */ + + for(i = 0; i < NR_PORTS; i++) { + info = rs_table + i; + /* check for dma_descr (dont need to check for dma_eop in output dma for serial */ + if(ireg & info->irq) { + /* we can send a new dma bunch. make it so. */ + transmit_chars(info); + } + + /* FIXME: here we should really check for a change in the + status lines and if so call status_handle(info) */ + } +} + +/* dma input channel interrupt handler */ + +static void +rec_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct e100_serial *info; + unsigned long ireg; + int i; + +#ifdef CONFIG_SVINTO_SIM + /* No receive in the simulator. Will probably be when the rest of + * the serial interface works, and this piece will just be removed. + */ + { + const char *s = "What? rec_interrupt in simulator??\n"; + SIMCOUT(s,strlen(s)); + } + return; +#endif + + /* find out the line that caused this irq and get it from rs_table */ + + ireg = *R_IRQ_MASK2_RD; /* get the active irq bits for the dma channels */ + + for(i = 0; i < NR_PORTS; i++) { + info = rs_table + i; + /* check for both dma_eop and dma_descr for the input dma channel */ + if(ireg & ((info->irq << 2) | (info->irq << 3))) { + /* we have received something */ + receive_chars(info); + } + + /* FIXME: here we should really check for a change in the + status lines and if so call status_handle(info) */ + } +} + +/* dma fifo/buffer timeout handler + forces an end-of-packet for the dma input channel if no chars + have been received for CONFIG_ETRAX100_RX_TIMEOUT_TICKS/100 s. + If CONFIG_ETRAX100_SERIAL_FLUSH_DMA_FAST is configured then this + handler is instead run at 15360 Hz. +*/ + +#ifndef CONFIG_ETRAX100_SERIAL_FLUSH_DMA_FAST +static int timeout_divider = 0; +#endif + +static struct timer_list flush_timer; + +static void +timed_flush_handler(void) +{ + struct e100_serial *info; + int i; + unsigned int magic; + +#ifdef CONFIG_SVINTO_SIM + return; +#endif + + for(i = 0; i < NR_PORTS; i++) { + info = rs_table + i; + if(!(info->flags & ASYNC_INITIALIZED)) + continue; + + /* istatusadr (bit 6-0) hold number of bytes in fifo + * ihwswadr (bit 31-16) holds number of bytes in dma buffer + * ihwswadr (bit 15-0) specifies size of dma buffer + */ + + magic = (*info->istatusadr & 0x3f); + magic += ((*info->ihwswadr&0xffff ) - (*info->ihwswadr >> 16)); + + /* if magic is equal to fifo_magic (magic in previous + * timeout_interrupt) then no new data has arrived since last + * interrupt and we'll force eop to flush fifo+dma buffers + */ + + if(magic != info->fifo_magic) { + info->fifo_magic = magic; + info->fifo_didmagic = 0; + } else { + /* hit the timeout, force an EOP for the input + * dma channel if we haven't already + */ + if(!info->fifo_didmagic && magic) { + info->fifo_didmagic = 1; + info->fifo_magic = 0; + *R_SET_EOP = 1U << info->iseteop; + } + } + } + + /* restart flush timer */ + + mod_timer(&flush_timer, jiffies + MAX_FLUSH_TIME); +} + + +#ifdef SERIAL_HANDLE_EARLY_ERRORS + +/* If there is an error (ie break) when the DMA is running and + * there are no bytes in the fifo the DMA is stopped and we get no + * eop interrupt. Thus we have to monitor the first bytes on a DMA + * transfer, and if it is without error we can turn the serial + * interrupts off. + */ + +static void +ser_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct e100_serial *info; + int i; + unsigned char rstat; + + for(i = 0; i < NR_PORTS; i++) { + + info = rs_table + i; + rstat = info->port[REG_STATUS]; + + if(*R_IRQ_MASK1_RD & (1U << (8+2*info->line))) { /* This line caused the irq */ +#ifdef SERIAL_DEBUG_INTR + printk("Interrupt from serport %d\n", i); +#endif + if(rstat & 0x0e) { + /* FIXME: This is weird, but if this delay is + * not present then irmaflash does not work... + */ + udelay(2300); + + /* if we got an error, we must reset it by + * reading the data_in field + */ + (void)info->port[REG_DATA]; + + PROCSTAT(early_errors_cnt[info->line]++); + + /* restart the DMA */ + *info->icmdadr = 3; + } + else { /* it was a valid byte, now let the dma do the rest */ +#ifdef SERIAL_DEBUG_INTR + printk("** OK, disabling ser_interupts\n"); +#endif + e100_disable_serial_data_irq(info); + } + } + } +} +#endif + +/* + * ------------------------------------------------------------------- + * Here ends the serial interrupt routines. + * ------------------------------------------------------------------- + */ + +/* + * This routine is used to handle the "bottom half" processing for the + * serial driver, known also the "software interrupt" processing. + * This processing is done at the kernel interrupt level, after the + * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This + * is where time-consuming activities which can not be done in the + * interrupt driver proper are done; the interrupt driver schedules + * them using rs_sched_event(), and they get done here. + */ +static void +do_serial_bh(void) +{ + run_task_queue(&tq_serial); +} + +static void +do_softint(void *private_) +{ + struct e100_serial *info = (struct e100_serial *) private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); + wake_up_interruptible(&tty->write_wait); + } +} + +/* + * This routine is called from the scheduler tqueue when the interrupt + * routine has signalled that a hangup has occurred. The path of + * hangup processing is: + * + * serial interrupt routine -> (scheduler tqueue) -> + * do_serial_hangup() -> tty->hangup() -> rs_hangup() + * + */ +static void +do_serial_hangup(void *private_) +{ + struct e100_serial *info = (struct e100_serial *) private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + tty_hangup(tty); +} + +static int +startup(struct e100_serial * info) +{ + unsigned long flags; + unsigned long page; + + page = get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + save_flags(flags); cli(); + + /* if it was already initialized, skip this */ + + if (info->flags & ASYNC_INITIALIZED) { + free_page(page); + restore_flags(flags); + return -EBUSY; + } + + if (info->xmit.buf) + free_page(page); + else + info->xmit.buf = (unsigned char *) page; + +#ifdef SERIAL_DEBUG_OPEN + printk("starting up ttyS%d (xmit_buf 0x%x)...\n", info->line, info->xmit_buf); +#endif + + if(info->tty) { + + /* clear the tty flip flag buffer since we will not + * be using it (we only use the first byte..) + */ + + memset(info->tty->flip.flag_buf, 0, TTY_FLIPBUF_SIZE * 2); + } + + save_flags(flags); + cli(); + +#ifdef CONFIG_SVINTO_SIM + /* Bits and pieces collected from below. Better to have them + in one ifdef:ed clause than to mix in a lot of ifdefs, + right? */ + if (info->tty) + clear_bit(TTY_IO_ERROR, &info->tty->flags); + info->xmit.head = info->xmit.tail = 0; + + /* No real action in the simulator, but may set info important + to ioctl. */ + change_speed(info); +#else + + /* + * Clear the FIFO buffers and disable them + * (they will be reenabled in change_speed()) + */ + + /* + * Reset the DMA channels and make sure their interrupts are cleared + */ + + *info->icmdadr = 4; /* reset command */ + *info->ocmdadr = 4; /* reset command */ + + while((*info->icmdadr & 7) == 4); /* wait until reset cycle is complete */ + while((*info->ocmdadr & 7) == 4); + + *info->iclrintradr = 3; /* make sure the irqs are cleared */ + *info->oclrintradr = 3; + + if (info->tty) + clear_bit(TTY_IO_ERROR, &info->tty->flags); + + info->xmit.head = info->xmit.tail = 0; + + /* + * and set the speed and other flags of the serial port + * this will start the rx/tx as well + */ +#ifdef SERIAL_HANDLE_EARLY_ERRORS + e100_enable_serial_data_irq(info); +#endif + change_speed(info); + + /* dummy read to reset any serial errors */ + + (void)info->port[REG_DATA]; + + /* enable the interrupts */ + + e100_enable_txdma_irq(info); + e100_enable_rxdma_irq(info); + + info->tr_running = 0; /* to be sure we dont lock up the transmitter */ + + /* setup the dma input descriptor and start dma */ + + start_receive(info); + + /* for safety, make sure the descriptors last result is 0 bytes written */ + + info->tr_descr.sw_len = 0; + info->tr_descr.hw_len = 0; + info->tr_descr.status = 0; + + /* enable RTS/DTR last */ + + e100_rts(info, 1); + e100_dtr(info, 1); + +#endif /* CONFIG_SVINTO_SIM */ + + info->flags |= ASYNC_INITIALIZED; + + restore_flags(flags); + return 0; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void +shutdown(struct e100_serial * info) +{ + unsigned long flags; + +#ifndef CONFIG_SVINTO_SIM + /* shut down the transmitter and receiver */ + + e100_disable_rx(info); + info->port[REG_TR_CTRL] = (info->tx_ctrl &= ~0x40); + + e100_disable_rxdma_irq(info); + e100_disable_txdma_irq(info); + + info->tr_running = 0; + + /* reset both dma channels */ + + *info->icmdadr = 4; + *info->ocmdadr = 4; + +#endif /* CONFIG_SVINTO_SIM */ + + if (!(info->flags & ASYNC_INITIALIZED)) + return; + +#ifdef SERIAL_DEBUG_OPEN + printk("Shutting down serial port %d (irq %d)....\n", info->line, + info->irq); +#endif + + save_flags(flags); + cli(); /* Disable interrupts */ + + if (info->xmit.buf) { + unsigned long pg = (unsigned long) info->xmit.buf; + info->xmit.buf = 0; + free_page(pg); + } + + if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) { + /* hang up DTR and RTS if HUPCL is enabled */ + e100_dtr(info, 0); + e100_rts(info, 0); /* could check CRTSCTS before doing this */ + } + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + info->flags &= ~ASYNC_INITIALIZED; + restore_flags(flags); +} + + +/* change baud rate and other assorted parameters */ + +static void +change_speed(struct e100_serial *info) +{ + unsigned int cflag; + + /* first some safety checks */ + + if(!info->tty || !info->tty->termios) + return; + if (!info->port) + return; + + cflag = info->tty->termios->c_cflag; + + /* possibly, the tx/rx should be disabled first to do this safely */ + + /* change baud-rate and write it to the hardware */ + + info->baud = cflag_to_baud(cflag); + +#ifndef CONFIG_SVINTO_SIM + info->port[REG_BAUD] = cflag_to_etrax_baud(cflag); + /* start with default settings and then fill in changes */ + + info->rx_ctrl &= ~(0x07); /* 8 bit, no/even parity */ + info->tx_ctrl &= ~(0x37); /* 8 bit, no/even parity, 1 stop bit, no cts */ + + if ((cflag & CSIZE) == CS7) { + /* set 7 bit mode */ + info->tx_ctrl |= 0x01; + info->rx_ctrl |= 0x01; + } + + if (cflag & CSTOPB) { + /* set 2 stop bit mode */ + info->tx_ctrl |= 0x10; + } + + if (cflag & PARENB) { + /* enable parity */ + info->tx_ctrl |= 0x02; + info->rx_ctrl |= 0x02; + } + + if (cflag & PARODD) { + /* set odd parity */ + info->tx_ctrl |= 0x04; + info->rx_ctrl |= 0x04; + } + + if (cflag & CRTSCTS) { + /* enable automatic CTS handling */ + info->tx_ctrl |= 0x20; + } + + /* make sure the tx and rx are enabled */ + + info->tx_ctrl |= 0x40; + info->rx_ctrl |= 0x40; + + /* actually write the control regs to the hardware */ + + info->port[REG_TR_CTRL] = info->tx_ctrl; + info->port[REG_REC_CTRL] = info->rx_ctrl; + *((unsigned long *)&info->port[REG_XOFF]) = 0; + +#endif /* CONFIG_SVINTO_SIM */ +} + +/* start transmitting chars NOW */ + +static void +rs_flush_chars(struct tty_struct *tty) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + unsigned long flags; + + if (info->tr_running + || info->xmit.head == info->xmit.tail + || tty->stopped + || tty->hw_stopped + || !info->xmit.buf) + return; + +#ifdef SERIAL_DEBUG_FLOW + printk("rs_flush_chars\n"); +#endif + + /* this protection might not exactly be necessary here */ + + save_flags(flags); + cli(); + start_transmit(info); + restore_flags(flags); +} + +static int +rs_write(struct tty_struct * tty, int from_user, + const unsigned char *buf, int count) +{ + int c, ret = 0; + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + unsigned long flags; + + /* first some sanity checks */ + + if (!tty || !info->xmit.buf || !tmp_buf) + return 0; + +#ifdef SERIAL_DEBUG_DATA + if(info->line == SERIAL_DEBUG_LINE) + printk("rs_write (%d), status %d\n", + count, info->port[REG_STATUS]); +#endif + +#ifdef CONFIG_SVINTO_SIM + /* Really simple. The output is here and now. */ + SIMCOUT(buf, count); + return; +#endif + save_flags(flags); + + /* the cli/restore_flags pairs below are needed because the + * DMA interrupt handler moves the info->xmit values. the memcpy + * needs to be in the critical region unfortunately, because we + * need to read xmit values, memcpy, write xmit values in one + * atomic operation... this could perhaps be avoided by more clever + * design. + */ + if(from_user) { + down(&tmp_buf_sem); + while (1) { + int c1; + c = CIRC_SPACE_TO_END(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE); + if (count < c) + c = count; + if (c <= 0) + break; + + c -= copy_from_user(tmp_buf, buf, c); + if (!c) { + if (!ret) + ret = -EFAULT; + break; + } + cli(); + c1 = CIRC_SPACE_TO_END(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE); + if (c1 < c) + c = c1; + memcpy(info->xmit.buf + info->xmit.head, tmp_buf, c); + info->xmit.head = ((info->xmit.head + c) & + (SERIAL_XMIT_SIZE-1)); + restore_flags(flags); + buf += c; + count -= c; + ret += c; + } + up(&tmp_buf_sem); + } else { + cli(); + while(1) { + c = CIRC_SPACE_TO_END(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE); + + if (count < c) + c = count; + if (c <= 0) + break; + + memcpy(info->xmit.buf + info->xmit.head, buf, c); + info->xmit.head = (info->xmit.head + c) & + (SERIAL_XMIT_SIZE-1); + buf += c; + count -= c; + ret += c; + } + restore_flags(flags); + } + + /* enable transmitter if not running, unless the tty is stopped + * this does not need IRQ protection since if tr_running == 0 + * the IRQ's are not running anyway for this port. + */ + + if(info->xmit.head != info->xmit.tail + && !tty->stopped && + !tty->hw_stopped && + !info->tr_running) { + start_transmit(info); + } + + return ret; +} + +/* how much space is available in the xmit buffer? */ + +static int +rs_write_room(struct tty_struct *tty) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + + return CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); +} + +/* How many chars are in the xmit buffer? + * This does not include any chars in the transmitter FIFO. + * Use wait_until_sent for waiting for FIFO drain. + */ + +static int +rs_chars_in_buffer(struct tty_struct *tty) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + + return CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); +} + +/* discard everything in the xmit buffer */ + +static void +rs_flush_buffer(struct tty_struct *tty) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + unsigned long flags; + + save_flags(flags); + cli(); + info->xmit.head = info->xmit.tail = 0; + restore_flags(flags); + + wake_up_interruptible(&tty->write_wait); + + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); +} + +/* + * This function is used to send a high-priority XON/XOFF character to + * the device + * + * Since we don't bother to check for info->x_char in transmit_chars yet, + * we don't really implement this function yet. + */ +static void rs_send_xchar(struct tty_struct *tty, char ch) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + + printk("serial.c:rs_send_xchar not implemented!\n"); + + info->x_char = ch; + if (ch) { + /* Make sure transmit interrupts are on */ + /* TODO. */ + } +} + +/* + * ------------------------------------------------------------ + * rs_throttle() + * + * This routine is called by the upper-layer tty layer to signal that + * incoming characters should be throttled. + * ------------------------------------------------------------ + */ +static void +rs_throttle(struct tty_struct * tty) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + unsigned long flags; +#ifdef SERIAL_DEBUG_THROTTLE + char buf[64]; + + printk("throttle %s: %d....\n", _tty_name(tty, buf), + tty->ldisc.chars_in_buffer(tty)); +#endif + + if (I_IXOFF(tty)) + info->x_char = STOP_CHAR(tty); + + /* Turn off RTS line (do this atomic) should here be an else ?? */ + + save_flags(flags); + cli(); + e100_rts(info, 0); + restore_flags(flags); +} + +static void +rs_unthrottle(struct tty_struct * tty) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + unsigned long flags; +#ifdef SERIAL_DEBUG_THROTTLE + char buf[64]; + + printk("unthrottle %s: %d....\n", _tty_name(tty, buf), + tty->ldisc.chars_in_buffer(tty)); +#endif + + if (I_IXOFF(tty)) { + if (info->x_char) + info->x_char = 0; + else + info->x_char = START_CHAR(tty); + } + + /* Assert RTS line (do this atomic) */ + + save_flags(flags); + cli(); + e100_rts(info, 1); + restore_flags(flags); +} + +/* + * ------------------------------------------------------------ + * rs_ioctl() and friends + * ------------------------------------------------------------ + */ + +static int +get_serial_info(struct e100_serial * info, + struct serial_struct * retinfo) +{ + struct serial_struct tmp; + + /* this is all probably wrong, there are a lot of fields + * here that we don't have in e100_serial and maybe we + * should set them to something else than 0. + */ + + if (!retinfo) + return -EFAULT; + memset(&tmp, 0, sizeof(tmp)); + tmp.type = info->type; + tmp.line = info->line; + tmp.port = (int)info->port; + tmp.irq = info->irq; + tmp.flags = info->flags; + tmp.close_delay = info->close_delay; + tmp.closing_wait = info->closing_wait; + if (copy_to_user(retinfo,&tmp,sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int +set_serial_info(struct e100_serial * info, + struct serial_struct * new_info) +{ + struct serial_struct new_serial; + struct e100_serial old_info; + int retval = 0; + + if (copy_from_user(&new_serial,new_info,sizeof(new_serial))) + return -EFAULT; + + old_info = *info; + + if(!capable(CAP_SYS_ADMIN)) { + if((new_serial.type != info->type) || + (new_serial.close_delay != info->close_delay) || + ((new_serial.flags & ~ASYNC_USR_MASK) != + (info->flags & ~ASYNC_USR_MASK))) + return -EPERM; + info->flags = ((info->flags & ~ASYNC_USR_MASK) | + (new_serial.flags & ASYNC_USR_MASK)); + goto check_and_exit; + } + + if (info->count > 1) + return -EBUSY; + + /* + * OK, past this point, all the error checking has been done. + * At this point, we start making changes..... + */ + + info->flags = ((info->flags & ~ASYNC_FLAGS) | + (new_serial.flags & ASYNC_FLAGS)); + info->type = new_serial.type; + info->close_delay = new_serial.close_delay; + info->closing_wait = new_serial.closing_wait; +#if (LINUX_VERSION_CODE > 0x20100) + info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0; +#endif + + check_and_exit: + if(info->flags & ASYNC_INITIALIZED) { + change_speed(info); + } else + retval = startup(info); + return retval; +} + +/* + * get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows an RS485 driver to be written in user space. + */ +static int +get_lsr_info(struct e100_serial * info, unsigned int *value) +{ + unsigned int result; + +#ifdef CONFIG_SVINTO_SIM + /* Always open. */ + result = TIOCSER_TEMT; +#else + if (*info->ostatusadr & 0x007F) /* something in fifo */ + result = 0; + else + result = TIOCSER_TEMT; +#endif + + if (copy_to_user(value, &result, sizeof(int))) + return -EFAULT; + return 0; +} + +#ifdef SERIAL_DEBUG_IO +struct state_str +{ + int state; + const char *str; + +}; + +const struct state_str control_state_str[]={ + {TIOCM_DTR, "DTR" }, + {TIOCM_RTS, "RTS"}, + {TIOCM_ST, "ST?" }, + {TIOCM_SR, "SR?" }, + {TIOCM_CTS, "CTS" }, + {TIOCM_CD, "CD" }, + {TIOCM_RI, "RI" }, + {TIOCM_DSR, "DSR" }, + {0, NULL } +}; + +char *get_control_state_str(int MLines, char *s) +{ + int i = 0; + s[0]='\0'; + while (control_state_str[i].str != NULL) { + if (MLines & control_state_str[i].state) { + if (s[0] != '\0') { + strcat(s, ", "); + } + strcat(s, control_state_str[i].str); + } + i++; + } + return s; +} +#endif + +static int +get_modem_info(struct e100_serial * info, unsigned int *value) +{ + unsigned int result; + /* Polarity isn't verified */ +#if 0 /*def SERIAL_DEBUG_IO */ + + printk("get_modem_info: RTS: %i DTR: %i CD: %i RI: %i DSR: %i CTS: %i\n", + E100_RTS_GET(info), + E100_DTR_GET(info), + E100_CD_GET(info), + E100_RI_GET(info), + E100_DSR_GET(info), + E100_CTS_GET(info)); +#endif + result = + (!E100_RTS_GET(info) ? TIOCM_RTS : 0) + | (!E100_DTR_GET(info) ? TIOCM_DTR : 0) + | (!E100_CD_GET(info) ? TIOCM_CAR : 0) + | (!E100_RI_GET(info) ? TIOCM_RNG : 0) + | (!E100_DSR_GET(info) ? TIOCM_DSR : 0) + | (!E100_CTS_GET(info) ? TIOCM_CTS : 0); + +#ifdef SERIAL_DEBUG_IO + printk("e100ser: modem state: %i 0x%08X\n", result, result); + { + char s[100]; + + get_control_state_str(result, s); + printk("state: %s\n", s); + } +#endif + if (copy_to_user(value, &result, sizeof(int))) + return -EFAULT; + return 0; +} + + +static int +set_modem_info(struct e100_serial * info, unsigned int cmd, + unsigned int *value) +{ + unsigned int arg; + + if (copy_from_user(&arg, value, sizeof(int))) + return -EFAULT; + + switch (cmd) { + case TIOCMBIS: + if (arg & TIOCM_RTS) { + e100_rts(info, 1); + } + if (arg & TIOCM_DTR) { + e100_dtr(info, 1); + } + /* Handle FEMALE behaviour */ + if (arg & TIOCM_RI) { + e100_ri_out(info, 1); + } + if (arg & TIOCM_CD) { + e100_cd_out(info, 1); + } + break; + case TIOCMBIC: + if (arg & TIOCM_RTS) { + e100_rts(info, 0); + } + if (arg & TIOCM_DTR) { + e100_dtr(info, 0); + } + /* Handle FEMALE behaviour */ + if (arg & TIOCM_RI) { + e100_ri_out(info, 0); + } + if (arg & TIOCM_CD) { + e100_cd_out(info, 0); + } + break; + case TIOCMSET: + e100_rts(info, arg & TIOCM_RTS); + e100_dtr(info, arg & TIOCM_DTR); + /* Handle FEMALE behaviour */ + e100_ri_out(info, arg & TIOCM_RI); + e100_cd_out(info, arg & TIOCM_CD); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * This routine sends a break character out the serial port. + */ +#if (LINUX_VERSION_CODE < 131394) /* Linux 2.1.66 */ +static void +send_break(struct e100_serial * info, int duration) +{ + unsigned long flags; + + if (!info->port) + return; + + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + duration; + + save_flags(flags); + cli(); + + /* Go to manual mode and set the txd pin to 0 */ + + info->tx_ctrl &= 0x3F; /* Clear bit 7 (txd) and 6 (tr_enable) */ + info->port[REG_TR_CTRL] = info->tx_ctrl; + + /* wait for "duration" jiffies */ + + schedule(); + + info->tx_ctrl |= (0x80 | 0x40); /* Set bit 7 (txd) and 6 (tr_enable) */ + info->port[REG_TR_CTRL] = info->tx_ctrl; + + /* the DMA gets awfully confused if we toggle the tranceiver like this + * so we need to reset it + */ + *info->ocmdadr = 4; + + restore_flags(flags); +} +#else +static void +rs_break(struct tty_struct *tty, int break_state) +{ + struct e100_serial * info = (struct e100_serial *)tty->driver_data; + unsigned long flags; + + if (!info->port) + return; + + save_flags(flags); + cli(); + if (break_state == -1) { + /* Go to manual mode and set the txd pin to 0 */ + info->tx_ctrl &= 0x3F; /* Clear bit 7 (txd) and 6 (tr_enable) */ + } else { + info->tx_ctrl |= (0x80 | 0x40); /* Set bit 7 (txd) and 6 (tr_enable) */ + } + info->port[REG_TR_CTRL] = info->tx_ctrl; + restore_flags(flags); +} +#endif + +static int +rs_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + int error; + struct e100_serial * info = (struct e100_serial *)tty->driver_data; + int retval; + + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) && + (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + } + + switch (cmd) { +#if (LINUX_VERSION_CODE < 131394) /* Linux 2.1.66 */ + case TCSBRK: /* SVID version: non-zero arg --> no break */ + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + if (signal_pending(current)) + return -EINTR; + if (!arg) { + send_break(info, HZ/4); /* 1/4 second */ + if (signal_pending(current)) + return -EINTR; + } + return 0; + case TCSBRKP: /* support for POSIX tcsendbreak() */ + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + if (signal_pending(current)) + return -EINTR; + send_break(info, arg ? arg*(HZ/10) : HZ/4); + if (signal_pending(current)) + return -EINTR; + return 0; + case TIOCGSOFTCAR: + error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(long)); + if (error) + return error; + put_fs_long(C_CLOCAL(tty) ? 1 : 0, + (unsigned long *) arg); + return 0; + case TIOCSSOFTCAR: + arg = get_fs_long((unsigned long *) arg); + tty->termios->c_cflag = + ((tty->termios->c_cflag & ~CLOCAL) | + (arg ? CLOCAL : 0)); + return 0; +#endif + case TIOCMGET: + return get_modem_info(info, (unsigned int *) arg); + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + return set_modem_info(info, cmd, (unsigned int *) arg); + case TIOCGSERIAL: + return get_serial_info(info, + (struct serial_struct *) arg); + case TIOCSSERIAL: + return set_serial_info(info, + (struct serial_struct *) arg); + case TIOCSERGETLSR: /* Get line status register */ + return get_lsr_info(info, (unsigned int *) arg); + + case TIOCSERGSTRUCT: + if (copy_to_user((struct e100_serial *) arg, + info, sizeof(struct e100_serial))) + return -EFAULT; + return 0; + +#if defined(CONFIG_RS485) + case TIOCSERSETRS485: + error = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(struct rs485_control)); + + if (error) + return error; + + return e100_enable_rs485(tty, (struct rs485_control *) arg); + + case TIOCSERWRRS485: + error = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(struct rs485_write)); + + if (error) + return error; + + return e100_write_rs485(tty, (struct rs485_write *) arg); +#endif + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static void +rs_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + + if (tty->termios->c_cflag == old_termios->c_cflag) + return; + + change_speed(info); + + if ((old_termios->c_cflag & CRTSCTS) && + !(tty->termios->c_cflag & CRTSCTS)) { + tty->hw_stopped = 0; + rs_start(tty); + } + +} + +/* + * ------------------------------------------------------------ + * rs_close() + * + * This routine is called when the serial port gets closed. First, we + * wait for the last remaining data to be sent. Then, we unlink its + * S structure from the interrupt chain if necessary, and we free + * that IRQ if nothing is left in the chain. + * ------------------------------------------------------------ + */ +static void +rs_close(struct tty_struct *tty, struct file * filp) +{ + struct e100_serial * info = (struct e100_serial *)tty->driver_data; + unsigned long flags; + + if (!info) + return; + + /* interrupts are disabled for this entire function */ + + save_flags(flags); + cli(); + + if (tty_hung_up_p(filp)) { + restore_flags(flags); + return; + } + +#ifdef SERIAL_DEBUG_OPEN + printk("[%d] rs_close ttyS%d, count = %d\n", current->pid, + info->line, info->count); +#endif + if ((tty->count == 1) && (info->count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. Info->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + printk("rs_close: bad serial port count; tty->count is 1, " + "info->count is %d\n", info->count); + info->count = 1; + } + if (--info->count < 0) { + printk("rs_close: bad serial port count for ttyS%d: %d\n", + info->line, info->count); + info->count = 0; + } + if (info->count) { + restore_flags(flags); + return; + } + info->flags |= ASYNC_CLOSING; + /* + * Save the termios structure, since this port may have + * separate termios for callout and dialin. + */ + if (info->flags & ASYNC_NORMAL_ACTIVE) + info->normal_termios = *tty->termios; + if (info->flags & ASYNC_CALLOUT_ACTIVE) + info->callout_termios = *tty->termios; + /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + tty->closing = 1; + if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, info->closing_wait); + /* + * At this point we stop accepting input. To do this, we + * disable the serial receiver and the DMA receive interrupt. + */ +#ifdef SERIAL_HANDLE_EARLY_ERRORS + e100_disable_serial_data_irq(info); +#endif + +#ifndef CONFIG_SVINTO_SIM + e100_disable_rx(info); + e100_disable_rxdma_irq(info); + + if (info->flags & ASYNC_INITIALIZED) { + /* + * Before we drop DTR, make sure the UART transmitter + * has completely drained; this is especially + * important as we have a transmit FIFO! + */ + rs_wait_until_sent(tty, HZ); + } +#endif + + shutdown(info); + if (tty->driver.flush_buffer) + tty->driver.flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + tty->closing = 0; + info->event = 0; + info->tty = 0; + if (info->blocked_open) { + if (info->close_delay) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(info->close_delay); + } + wake_up_interruptible(&info->open_wait); + } + info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE| + ASYNC_CLOSING); + wake_up_interruptible(&info->close_wait); + restore_flags(flags); + + /* port closed */ + +#if defined(CONFIG_RS485) + if (info->rs485.enabled) { + info->rs485.enabled = 0; +#if defined(CONFIG_RS485_ON_PA) + *R_PORT_PA_DATA = port_pa_data_shadow &= ~(1 << rs485_pa_bit); +#endif + } +#endif +} + +/* + * rs_wait_until_sent() --- wait until the transmitter is empty + */ +static void rs_wait_until_sent(struct tty_struct *tty, int timeout) +{ + unsigned long orig_jiffies; + struct e100_serial *info = (struct e100_serial *)tty->driver_data; + + /* + * Check R_DMA_CHx_STATUS bit 0-6=number of available bytes in FIFO + * R_DMA_CHx_HWSW bit 31-16=nbr of bytes left in DMA buffer (0=64k) + */ + orig_jiffies = jiffies; + while(info->xmit.head != info->xmit.tail || /* More in send queue */ + (*info->ostatusadr & 0x007f)) { /* more in FIFO */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + if (timeout && time_after(jiffies, orig_jiffies + timeout)) + break; + } + set_current_state(TASK_RUNNING); +} + +/* + * rs_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +void +rs_hangup(struct tty_struct *tty) +{ + struct e100_serial * info = (struct e100_serial *)tty->driver_data; + + rs_flush_buffer(tty); + shutdown(info); + info->event = 0; + info->count = 0; + info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE); + info->tty = 0; + wake_up_interruptible(&info->open_wait); +} + +/* + * ------------------------------------------------------------ + * rs_open() and friends + * ------------------------------------------------------------ + */ +static int +block_til_ready(struct tty_struct *tty, struct file * filp, + struct e100_serial *info) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int retval; + int do_clocal = 0, extra_count = 0; + + /* + * If the device is in the middle of being closed, then block + * until it's done, and then try again. + */ + if (tty_hung_up_p(filp) || + (info->flags & ASYNC_CLOSING)) { + if (info->flags & ASYNC_CLOSING) + interruptible_sleep_on(&info->close_wait); +#ifdef SERIAL_DO_RESTART + if (info->flags & ASYNC_HUP_NOTIFY) + return -EAGAIN; + else + return -ERESTARTSYS; +#else + return -EAGAIN; +#endif + } + + /* + * If this is a callout device, then just make sure the normal + * device isn't being used. + */ + if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) { + if (info->flags & ASYNC_NORMAL_ACTIVE) + return -EBUSY; + if ((info->flags & ASYNC_CALLOUT_ACTIVE) && + (info->flags & ASYNC_SESSION_LOCKOUT) && + (info->session != current->session)) + return -EBUSY; + if ((info->flags & ASYNC_CALLOUT_ACTIVE) && + (info->flags & ASYNC_PGRP_LOCKOUT) && + (info->pgrp != current->pgrp)) + return -EBUSY; + info->flags |= ASYNC_CALLOUT_ACTIVE; + return 0; + } + + /* + * If non-blocking mode is set, or the port is not enabled, + * then make the check up front and then exit. + */ + if ((filp->f_flags & O_NONBLOCK) || + (tty->flags & (1 << TTY_IO_ERROR))) { + if (info->flags & ASYNC_CALLOUT_ACTIVE) + return -EBUSY; + info->flags |= ASYNC_NORMAL_ACTIVE; + return 0; + } + + if (info->flags & ASYNC_CALLOUT_ACTIVE) { + if (info->normal_termios.c_cflag & CLOCAL) + do_clocal = 1; + } else { + if (tty->termios->c_cflag & CLOCAL) + do_clocal = 1; + } + + /* + * Block waiting for the carrier detect and the line to become + * free (i.e., not in use by the callout). While we are in + * this loop, info->count is dropped by one, so that + * rs_close() knows when to free things. We restore it upon + * exit, either normal or abnormal. + */ + retval = 0; + add_wait_queue(&info->open_wait, &wait); +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready before block: ttyS%d, count = %d\n", + info->line, info->count); +#endif + save_flags(flags); + cli(); + if (!tty_hung_up_p(filp)) { + extra_count++; + info->count--; + } + restore_flags(flags); + info->blocked_open++; + while (1) { + save_flags(flags); + cli(); + if (!(info->flags & ASYNC_CALLOUT_ACTIVE)) { + /* assert RTS and DTR */ + e100_rts(info, 1); + e100_dtr(info, 1); + } + restore_flags(flags); + set_current_state(TASK_INTERRUPTIBLE); + if (tty_hung_up_p(filp) || + !(info->flags & ASYNC_INITIALIZED)) { +#ifdef SERIAL_DO_RESTART + if (info->flags & ASYNC_HUP_NOTIFY) + retval = -EAGAIN; + else + retval = -ERESTARTSYS; +#else + retval = -EAGAIN; +#endif + break; + } + if (!(info->flags & ASYNC_CALLOUT_ACTIVE) && + !(info->flags & ASYNC_CLOSING) && do_clocal) + /* && (do_clocal || DCD_IS_ASSERTED) */ + break; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready blocking: ttyS%d, count = %d\n", + info->line, info->count); +#endif + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&info->open_wait, &wait); + if (extra_count) + info->count++; + info->blocked_open--; +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready after blocking: ttyS%d, count = %d\n", + info->line, info->count); +#endif + if (retval) + return retval; + info->flags |= ASYNC_NORMAL_ACTIVE; + return 0; +} + +/* + * This routine is called whenever a serial port is opened. + * It performs the serial-specific initialization for the tty structure. + */ +static int +rs_open(struct tty_struct *tty, struct file * filp) +{ + struct e100_serial *info; + int retval, line; + unsigned long page; + + /* find which port we want to open */ + + line = MINOR(tty->device) - tty->driver.minor_start; + + if (line < 0 || line >= NR_PORTS) + return -ENODEV; + + /* dont allow opening ports that are not enabled in the HW config */ + +#ifndef CONFIG_ETRAX100_SERIAL_PORT2 + if (line == 2) + return -ENODEV; +#endif +#ifndef CONFIG_ETRAX100_SERIAL_PORT3 + if (line == 3) + return -ENODEV; +#endif + + /* find the corresponding e100_serial struct in the table */ + + info = rs_table + line; + +#ifdef SERIAL_DEBUG_OPEN + printk("[%d] rs_open %s%d, count = %d\n", current->pid, + tty->driver.name, info->line, + info->count); +#endif + + info->count++; + tty->driver_data = info; + info->tty = tty; + +#if (LINUX_VERSION_CODE > 0x20100) + info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0; +#endif + + if (!tmp_buf) { + page = get_zeroed_page(GFP_KERNEL); + if (!page) { + return -ENOMEM; + } + if (tmp_buf) + free_page(page); + else + tmp_buf = (unsigned char *) page; + } + + /* + * If the port is the middle of closing, bail out now + */ + if (tty_hung_up_p(filp) || + (info->flags & ASYNC_CLOSING)) { + if (info->flags & ASYNC_CLOSING) + interruptible_sleep_on(&info->close_wait); +#ifdef SERIAL_DO_RESTART + return ((info->flags & ASYNC_HUP_NOTIFY) ? + -EAGAIN : -ERESTARTSYS); +#else + return -EAGAIN; +#endif + } + + /* + * Start up the serial port + */ + + retval = startup(info); + if (retval) + return retval; + + retval = block_til_ready(tty, filp, info); + if (retval) { +#ifdef SERIAL_DEBUG_OPEN + printk("rs_open returning after block_til_ready with %d\n", + retval); +#endif + return retval; + } + + if ((info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) { + if (tty->driver.subtype == SERIAL_TYPE_NORMAL) + *tty->termios = info->normal_termios; + else + *tty->termios = info->callout_termios; + change_speed(info); + } + + info->session = current->session; + info->pgrp = current->pgrp; + +#ifdef SERIAL_DEBUG_OPEN + printk("rs_open ttyS%d successful...\n", info->line); +#endif + return 0; +} + +/* + * /proc fs routines.... + */ + +static inline int line_info(char *buf, struct e100_serial *info) +{ + char stat_buf[30], control, status; + int ret; + unsigned long flags; + + ret = sprintf(buf, "%d: uart:E100 port:%lX irq:%d", + info->line, info->port, info->irq); + + if (!info->port || (info->type == PORT_UNKNOWN)) { + ret += sprintf(buf+ret, "\n"); + return ret; + } + + stat_buf[0] = 0; + stat_buf[1] = 0; + if (E100_RTS_GET(info)) + strcat(stat_buf, "|RTS"); + if (E100_CTS_GET(info)) + strcat(stat_buf, "|CTS"); + if (E100_DTR_GET(info)) + strcat(stat_buf, "|DTR"); + if (E100_DSR_GET(info)) + strcat(stat_buf, "|DSR"); + if (E100_CD_GET(info)) + strcat(stat_buf, "|CD"); + if (E100_RI_GET(info)) + strcat(stat_buf, "|RI"); + + ret += sprintf(buf+ret, " baud:%d", info->baud); + + ret += sprintf(buf+ret, " tx:%d rx:%d", + info->icount.tx, info->icount.rx); + + if (info->icount.frame) + ret += sprintf(buf+ret, " fe:%d", info->icount.frame); + + if (info->icount.parity) + ret += sprintf(buf+ret, " pe:%d", info->icount.parity); + + if (info->icount.brk) + ret += sprintf(buf+ret, " brk:%d", info->icount.brk); + + if (info->icount.overrun) + ret += sprintf(buf+ret, " oe:%d", info->icount.overrun); + + /* + * Last thing is the RS-232 status lines + */ + ret += sprintf(buf+ret, " %s\n", stat_buf+1); + return ret; +} + +int rs_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + int i, len = 0, l; + off_t begin = 0; + + len += sprintf(page, "serinfo:1.0 driver:%s\n", + serial_version); + for (i = 0; i < NR_PORTS && len < 4000; i++) { + l = line_info(page + len, &rs_table[i]); + len += l; + if (len+begin > off+count) + goto done; + if (len+begin < off) { + begin += len; + len = 0; + } + } + *eof = 1; +done: + if (off >= len+begin) + return 0; + *start = page + (off-begin); + return ((count < begin+len-off) ? count : begin+len-off); +} + +/* Finally, routines used to initialize the serial driver. */ + +static void +show_serial_version(void) +{ + printk("ETRAX 100LX serial-driver %s, (c) 2000 Axis Communications AB\r\n", + serial_version); +} + +/* rs_init inits the driver at boot (using the module_init chain) */ + +static int __init +rs_init(void) +{ + int i; + struct e100_serial *info; + + show_serial_version(); + + init_bh(SERIAL_BH, do_serial_bh); + + /* Setup the timed flush handler system */ + + init_timer(&flush_timer); + flush_timer.function = timed_flush_handler; + mod_timer(&flush_timer, jiffies + MAX_FLUSH_TIME); + + /* Initialize the tty_driver structure */ + + memset(&serial_driver, 0, sizeof(struct tty_driver)); + serial_driver.magic = TTY_DRIVER_MAGIC; +#if (LINUX_VERSION_CODE > 0x20100) + serial_driver.driver_name = "serial"; +#endif + serial_driver.name = "ttyS"; + serial_driver.major = TTY_MAJOR; + serial_driver.minor_start = 64; + serial_driver.num = NR_PORTS; /* etrax100 has 4 serial ports */ + serial_driver.type = TTY_DRIVER_TYPE_SERIAL; + serial_driver.subtype = SERIAL_TYPE_NORMAL; + serial_driver.init_termios = tty_std_termios; + serial_driver.init_termios.c_cflag = + B115200 | CS8 | CREAD | HUPCL | CLOCAL; /* is normally B9600 default... */ + serial_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; + serial_driver.refcount = &serial_refcount; + serial_driver.table = serial_table; + serial_driver.termios = serial_termios; + serial_driver.termios_locked = serial_termios_locked; + + serial_driver.open = rs_open; + serial_driver.close = rs_close; + serial_driver.write = rs_write; + /* should we have an rs_put_char as well here ? */ + serial_driver.flush_chars = rs_flush_chars; + serial_driver.write_room = rs_write_room; + serial_driver.chars_in_buffer = rs_chars_in_buffer; + serial_driver.flush_buffer = rs_flush_buffer; + serial_driver.ioctl = rs_ioctl; + serial_driver.throttle = rs_throttle; + serial_driver.unthrottle = rs_unthrottle; + serial_driver.set_termios = rs_set_termios; + serial_driver.stop = rs_stop; + serial_driver.start = rs_start; + serial_driver.hangup = rs_hangup; +#if (LINUX_VERSION_CODE >= 131394) /* Linux 2.1.66 */ + serial_driver.break_ctl = rs_break; +#endif +#if (LINUX_VERSION_CODE >= 131343) + serial_driver.send_xchar = rs_send_xchar; + serial_driver.wait_until_sent = rs_wait_until_sent; + serial_driver.read_proc = rs_read_proc; +#endif + + /* + * The callout device is just like normal device except for + * major number and the subtype code. + */ + callout_driver = serial_driver; + callout_driver.name = "cua"; + callout_driver.major = TTYAUX_MAJOR; + callout_driver.subtype = SERIAL_TYPE_CALLOUT; +#if (LINUX_VERSION_CODE >= 131343) + callout_driver.read_proc = 0; + callout_driver.proc_entry = 0; +#endif + + if (tty_register_driver(&serial_driver)) + panic("Couldn't register serial driver\n"); + if (tty_register_driver(&callout_driver)) + panic("Couldn't register callout driver\n"); + + /* do some initializing for the separate ports */ + + for (i = 0, info = rs_table; i < NR_PORTS; i++,info++) { + info->line = i; + info->tty = 0; + info->type = PORT_ETRAX100; + info->tr_running = 0; + info->fifo_magic = 0; + info->fifo_didmagic = 0; + info->flags = 0; + info->close_delay = 5*HZ/10; + info->closing_wait = 30*HZ; + info->x_char = 0; + info->event = 0; + info->count = 0; + info->blocked_open = 0; + info->tqueue.routine = do_softint; + info->tqueue.data = info; + info->callout_termios = callout_driver.init_termios; + info->normal_termios = serial_driver.init_termios; + init_waitqueue_head(&info->open_wait); + init_waitqueue_head(&info->close_wait); + info->xmit.buf = 0; + info->xmit.tail = info->xmit.head = 0; + + printk(KERN_INFO "%s%d at 0x%x is a builtin UART with DMA\n", + serial_driver.name, info->line, (unsigned int)info->port); + } + +#ifndef CONFIG_SVINTO_SIM + /* Not needed in simulator. May only complicate stuff. */ + /* hook the irq's for DMA channel 6 and 7, serial output and input, and some more... */ + if(request_irq(22, tr_interrupt, SA_INTERRUPT, "serial 0 dma tr", NULL)) + panic("irq22"); + if(request_irq(23, rec_interrupt, SA_INTERRUPT, "serial 0 dma rec", NULL)) + panic("irq23"); +#ifdef SERIAL_HANDLE_EARLY_ERRORS + if(request_irq(8, ser_interrupt, SA_INTERRUPT, "serial ", NULL)) + panic("irq8"); +#endif + if(request_irq(24, tr_interrupt, SA_INTERRUPT, "serial 1 dma tr", NULL)) + panic("irq24"); + if(request_irq(25, rec_interrupt, SA_INTERRUPT, "serial 1 dma rec", NULL)) + panic("irq25"); +#ifdef CONFIG_ETRAX100_SERIAL_PORT2 + /* DMA Shared with par0 (and SCSI0 and ATA) */ + if(request_irq(18, tr_interrupt, SA_SHIRQ, "serial 2 dma tr", NULL)) + panic("irq18"); + if(request_irq(19, rec_interrupt, SA_SHIRQ, "serial 2 dma rec", NULL)) + panic("irq19"); +#endif +#ifdef CONFIG_ETRAX100_SERIAL_PORT3 + /* DMA Shared with par1 (and SCSI1 and Extern DMA 0) */ + if(request_irq(20, tr_interrupt, SA_SHIRQ, "serial 3 dma tr", NULL)) + panic("irq20"); + if(request_irq(21, rec_interrupt, SA_SHIRQ, "serial 3 dma rec", NULL)) + panic("irq21"); +#endif +#ifdef CONFIG_ETRAX100_SERIAL_FLUSH_DMA_FAST + /* TODO: a timeout_interrupt needs to be written that calls timeout_handler */ + if(request_irq(TIMER1_IRQ_NBR, timeout_interrupt, SA_SHIRQ, + "fast serial dma timeout", NULL)) { + printk("err: timer1 irq\n"); + } +#endif +#endif /* CONFIG_SVINTO_SIM */ + + return 0; +} + +/* this makes sure that rs_init is called during kernel boot */ + +module_init(rs_init); + +/* + * register_serial and unregister_serial allows for serial ports to be + * configured at run-time, to support PCMCIA modems. + */ +int +register_serial(struct serial_struct *req) +{ + return -1; +} + +void unregister_serial(int line) +{ +} diff --git a/arch/cris/drivers/serial.h b/arch/cris/drivers/serial.h new file mode 100644 index 000000000..650c5e9b4 --- /dev/null +++ b/arch/cris/drivers/serial.h @@ -0,0 +1,106 @@ +/* + * serial.h: Arch-dep definitions for the Etrax100 serial driver. + * + * Copyright (C) 1998, 1999, 2000 Axis Communications AB + */ + +#ifndef _ETRAX100_SERIAL_H +#define _ETRAX100_SERIAL_H + +#include <linux/config.h> +#include <linux/circ_buf.h> +#include <asm/termios.h> + +/* Software state per channel */ + +#ifdef __KERNEL__ +/* + * This is our internal structure for each serial port's state. + * + * Many fields are paralleled by the structure used by the serial_struct + * structure. + * + * For definitions of the flags field, see tty.h + */ + +struct e100_serial { + int baud; + volatile unsigned char * port; /* R_SERIALx_CTRL */ + unsigned long irq; /* bitnr in R_IRQ_MASK2 for dmaX_descr */ + + volatile char *oclrintradr; /* adr to R_DMA_CHx_CLR_INTR, output */ + volatile unsigned long *ofirstadr; /* adr to R_DMA_CHx_FIRST, output */ + volatile char *ocmdadr; /* adr to R_DMA_CHx_CMD, output */ + const volatile unsigned short *ostatusadr; /* adr to R_DMA_CHx_STATUS, output */ + volatile unsigned long *ohwswadr; /* adr to R_DMA_CHx_HWSW, output */ + + volatile char *iclrintradr; /* adr to R_DMA_CHx_CLR_INTR, input */ + volatile unsigned long *ifirstadr; /* adr to R_DMA_CHx_FIRST, input */ + volatile char *icmdadr; /* adr to R_DMA_CHx_CMD, input */ + const volatile unsigned short *istatusadr; /* adr to R_DMA_CHx_STATUS, input */ + volatile unsigned long *ihwswadr; /* adr to R_DMA_CHx_HWSW, input */ + + int flags; /* defined in tty.h */ + + unsigned char rx_ctrl; /* shadow for R_SERIALx_REC_CTRL */ + unsigned char tx_ctrl; /* shadow for R_SERIALx_TR_CTRL */ + unsigned char iseteop; /* bit number for R_SET_EOP for the input dma */ +/* end of fields defined in rs_table[] in .c-file */ + unsigned char fifo_didmagic; /* a fifo eop has been forced */ + + struct etrax_dma_descr tr_descr, rec_descr; + + int fifo_magic; /* fifo amount - bytes left in dma buffer */ + + volatile int tr_running; /* 1 if output is running */ + + struct tty_struct *tty; + int read_status_mask; + int ignore_status_mask; + int x_char; /* xon/xoff character */ + int close_delay; + unsigned short closing_wait; + unsigned short closing_wait2; + unsigned long event; + unsigned long last_active; + int line; + int type; /* PORT_ETRAX100 */ + int count; /* # of fd on device */ + int blocked_open; /* # of blocked opens */ + long session; /* Session of opening process */ + long pgrp; /* pgrp of opening process */ + struct circ_buf xmit; + + struct tq_struct tqueue; + struct async_icount icount; /* error-statistics etc.*/ + struct termios normal_termios; + struct termios callout_termios; +#ifdef DECLARE_WAITQUEUE + wait_queue_head_t open_wait; + wait_queue_head_t close_wait; +#else + struct wait_queue *open_wait; + struct wait_queue *close_wait; +#endif + +#ifdef CONFIG_RS485 + struct rs485_control rs485; /* RS-485 support */ +#endif +}; + +/* this PORT is not in the standard serial.h. it's not actually used for + * anything since we only have one type of async serial-port anyway in this + * system. + */ + +#define PORT_ETRAX100 1 + +/* + * Events are used to schedule things to happen at timer-interrupt + * time, instead of at rs interrupt time. + */ +#define RS_EVENT_WRITE_WAKEUP 0 + +#endif /* __KERNEL__ */ + +#endif /* !(_ETRAX100_SERIAL_H) */ diff --git a/arch/cris/kernel/Makefile b/arch/cris/kernel/Makefile new file mode 100644 index 000000000..0681e4807 --- /dev/null +++ b/arch/cris/kernel/Makefile @@ -0,0 +1,25 @@ +# $Id: Makefile,v 1.3 2001/01/10 21:11:07 bjornw Exp $ +# +# Makefile for the linux kernel. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +.S.o: + $(CC) $(AFLAGS) -traditional -c $< -o $*.o + +all: kernel.o head.o + +O_TARGET := kernel.o +obj-y := process.o signal.o entry.o traps.o irq.o \ + ptrace.o setup.o time.o sys_cris.o shadows.o \ + debugport.o semaphore.o + +obj-$(CONFIG_KGDB) += kgdb.o + +clean: + +include $(TOPDIR)/Rules.make diff --git a/arch/cris/kernel/debugport.c b/arch/cris/kernel/debugport.c new file mode 100644 index 000000000..a2b29be95 --- /dev/null +++ b/arch/cris/kernel/debugport.c @@ -0,0 +1,242 @@ +/* Serialport functions for debugging + * + * Copyright (c) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen + * + * Exports: + * console_print_etrax(char *buf) + * int getDebugChar() + * putDebugChar(int) + * enableDebugIRQ() + * init_etrax_debug() + * + * $Log: debugport.c,v $ + * Revision 1.4 2000/10/06 12:37:26 bjornw + * Use physical addresses when talking to DMA + * + * + */ + +#include <linux/config.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/major.h> + +#include <asm/system.h> +#include <asm/svinto.h> +#include <asm/io.h> /* Get SIMCOUT. */ + +/* Which serial-port is our debug port ? */ + +#if defined(CONFIG_DEBUG_PORT0) || defined(CONFIG_DEBUG_PORT_NULL) +#define DEBUG_PORT_IDX 0 +#define DEBUG_OCMD R_DMA_CH6_CMD +#define DEBUG_FIRST R_DMA_CH6_FIRST +#define DEBUG_OCLRINT R_DMA_CH6_CLR_INTR +#define DEBUG_STATUS R_DMA_CH6_STATUS +#define DEBUG_READ R_SERIAL0_READ +#define DEBUG_WRITE R_SERIAL0_TR_DATA +#define DEBUG_TR_CTRL R_SERIAL0_TR_CTRL +#define DEBUG_REC_CTRL R_SERIAL0_REC_CTRL +#define DEBUG_IRQ IO_STATE(R_IRQ_MASK1_SET, ser0_data, set) +#define DEBUG_DMA_IRQ_CLR IO_STATE(R_IRQ_MASK2_CLR, dma6_descr, clr) +#endif + +#ifdef CONFIG_DEBUG_PORT1 +#define DEBUG_PORT_IDX 1 +#define DEBUG_OCMD R_DMA_CH8_CMD +#define DEBUG_FIRST R_DMA_CH8_FIRST +#define DEBUG_OCLRINT R_DMA_CH8_CLR_INTR +#define DEBUG_STATUS R_DMA_CH8_STATUS +#define DEBUG_READ R_SERIAL1_READ +#define DEBUG_WRITE R_SERIAL1_TR_DATA +#define DEBUG_TR_CTRL R_SERIAL1_TR_CTRL +#define DEBUG_REC_CTRL R_SERIAL1_REC_CTRL +#define DEBUG_IRQ IO_STATE(R_IRQ_MASK1_SET, ser1_data, set) +#define DEBUG_DMA_IRQ_CLR IO_STATE(R_IRQ_MASK2_CLR, dma8_descr, clr) +#endif + +#ifdef CONFIG_DEBUG_PORT2 +#define DEBUG_PORT_IDX 2 +#define DEBUG_OCMD R_DMA_CH2_CMD +#define DEBUG_FIRST R_DMA_CH2_FIRST +#define DEBUG_OCLRINT R_DMA_CH2_CLR_INTR +#define DEBUG_STATUS R_DMA_CH2_STATUS +#define DEBUG_READ R_SERIAL2_READ +#define DEBUG_WRITE R_SERIAL2_TR_DATA +#define DEBUG_TR_CTRL R_SERIAL2_TR_CTRL +#define DEBUG_REC_CTRL R_SERIAL2_REC_CTRL +#define DEBUG_IRQ IO_STATE(R_IRQ_MASK1_SET, ser2_data, set) +#define DEBUG_DMA_IRQ_CLR IO_STATE(R_IRQ_MASK2_CLR, dma2_descr, clr) +#endif + +#ifdef CONFIG_DEBUG_PORT3 +#define DEBUG_PORT_IDX 3 +#define DEBUG_OCMD R_DMA_CH4_CMD +#define DEBUG_FIRST R_DMA_CH4_FIRST +#define DEBUG_OCLRINT R_DMA_CH4_CLR_INTR +#define DEBUG_STATUS R_DMA_CH4_STATUS +#define DEBUG_READ R_SERIAL3_READ +#define DEBUG_WRITE R_SERIAL3_TR_DATA +#define DEBUG_TR_CTRL R_SERIAL3_TR_CTRL +#define DEBUG_REC_CTRL R_SERIAL3_REC_CTRL +#define DEBUG_IRQ IO_STATE(R_IRQ_MASK1_SET, ser3_data, set) +#define DEBUG_DMA_IRQ_CLR IO_STATE(R_IRQ_MASK2_CLR, dma4_descr, clr) +#endif + +/* Write a string of count length to the console (debug port) using DMA, polled + * for completion. Interrupts are disabled during the whole process. Some + * caution needs to be taken to not interfere with ttyS business on this port. + */ + +static void +console_write(struct console *co, const char *buf, unsigned int len) +{ + static struct etrax_dma_descr descr; + unsigned long flags; + int in_progress; + +#ifdef CONFIG_DEBUG_PORT_NULL + /* no debug printout at all */ + return; +#endif + +#ifdef CONFIG_SVINTO_SIM + /* no use to simulate the serial debug output */ + SIMCOUT(buf,len); + return; +#endif + + save_flags(flags); + cli(); + +#ifdef CONFIG_KGDB + /* kgdb needs to output debug info using the gdb protocol */ + putDebugString(buf, len); + restore_flags(flags); + return; +#endif + + /* make sure the transmitter is enabled. + * NOTE: this overrides any setting done in ttySx, to 8N1, no auto-CTS. + * in the future, move the tr/rec_ctrl shadows from etrax100ser.c to + * shadows.c and use it here as well... + */ + + *DEBUG_TR_CTRL = 0x40; + + /* if the tty has some ongoing business, remember it */ + + in_progress = *DEBUG_OCMD & 7; + + if(in_progress) { + /* wait until the output dma channel is ready */ + + while(*DEBUG_OCMD & 7) /* nothing */ ; + } + + descr.ctrl = d_eol; + descr.sw_len = len; + descr.buf = __pa(buf); + + *DEBUG_FIRST = __pa(&descr); /* write to R_DMAx_FIRST */ + *DEBUG_OCMD = 1; /* dma command start -> R_DMAx_CMD */ + + /* wait until the output dma channel is ready again */ + + while(*DEBUG_OCMD & 7) /* nothing */; + + /* clear pending interrupts so we don't get a surprise below */ + + if(in_progress) + *DEBUG_OCLRINT = 2; /* only clear EOP, leave DESCR for the tty */ + else + *DEBUG_OCLRINT = 3; /* clear both EOP and DESCR */ + + while(*DEBUG_STATUS & 0x7f); /* wait until output FIFO is empty as well */ + + restore_flags(flags); +} + +/* legacy function */ + +void +console_print_etrax(const char *buf) +{ + console_write(NULL, buf, strlen(buf)); +} + +/* Use polling to get a single character FROM the debug port */ + +int +getDebugChar(void) +{ + unsigned long readval; + + do { + readval = *DEBUG_READ; + } while(!(readval & IO_MASK(R_SERIAL0_READ, data_avail))); + + return (readval & IO_MASK(R_SERIAL0_READ, data_in)); +} + +/* Use polling to put a single character to the debug port */ + +void +putDebugChar(int val) +{ + while(!(*DEBUG_READ & IO_MASK(R_SERIAL0_READ, tr_ready))) ; +; + *DEBUG_WRITE = val; +} + +/* Enable irq for receiving chars on the debug port, used by kgdb */ + +void +enableDebugIRQ(void) +{ + *R_IRQ_MASK1_SET = DEBUG_IRQ; + /* use R_VECT_MASK directly, since we really bypass Linux normal + * IRQ handling in kgdb anyway, we don't need to use enable_irq + */ + *R_VECT_MASK_SET = IO_STATE(R_VECT_MASK_SET, serial, set); + + *DEBUG_REC_CTRL = IO_STATE(R_SERIAL0_REC_CTRL, rec_enable, enable); +} + +static kdev_t +console_device(struct console *c) +{ + return MKDEV(TTY_MAJOR, 64 + c->index); +} + +static int __init +console_setup(struct console *co, char *options) +{ + return 0; +} + +static struct console sercons = { + "ttyS", + console_write, + NULL, + console_device, + NULL, + NULL, + console_setup, + CON_PRINTBUFFER, + DEBUG_PORT_IDX, + 0, + NULL +}; + +/* + * Register console (for printk's etc) + */ + +void __init +init_etrax_debug(void) +{ + register_console(&sercons); +} diff --git a/arch/cris/kernel/entry.S b/arch/cris/kernel/entry.S new file mode 100644 index 000000000..1af62eb2c --- /dev/null +++ b/arch/cris/kernel/entry.S @@ -0,0 +1,738 @@ +/* $Id: entry.S,v 1.11 2001/01/10 21:13:29 bjornw Exp $ + * + * linux/arch/cris/entry.S + * + * Copyright (C) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + * $Log: entry.S,v $ + * Revision 1.11 2001/01/10 21:13:29 bjornw + * SYMBOL_NAME is defined incorrectly for the compiler options we currently use + * + * Revision 1.10 2000/12/18 23:47:56 bjornw + * * Added syscall trace support (ptrace), completely untested of course + * * Removed redundant check for NULL entries in syscall_table + * + * Revision 1.9 2000/11/21 16:40:51 bjornw + * * New frame type used when an SBFS frame needs to be popped without + * actually restarting the instruction + * * Enable interrupts in signal_return (they did so in x86, I hope it's a good + * idea) + * + * Revision 1.8 2000/11/17 16:53:35 bjornw + * Added detection of frame-type in Rexit, so that mmu_bus_fault can + * use ret_from_intr in the return-path to check for signals (like SEGV) + * and other foul things that might have occured during the fault. + * + * Revision 1.7 2000/10/06 15:04:28 bjornw + * Include mof in register savings + * + * Revision 1.6 2000/09/12 16:02:44 bjornw + * Linux-2.4.0-test7 derived updates + * + * Revision 1.5 2000/08/17 15:35:15 bjornw + * 2.4.0-test6 changed local_irq_count and friends API + * + * Revision 1.4 2000/08/02 13:59:30 bjornw + * Removed olduname and uname from the syscall list + * + * Revision 1.3 2000/07/31 13:32:58 bjornw + * * Export ret_from_intr + * * _resume updated (prev/last tjohejsan) + * * timer_interrupt obsolete + * * SIGSEGV detection in mmu_bus_fault temporarily disabled + * + * + */ + +/* + * entry.S contains 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/linkage.h> +#include <linux/sys.h> + + ;; functions exported from this file + + .globl _system_call + .globl _ret_from_intr + .globl _ret_from_sys_call + .globl _resume + .globl _multiple_interrupt + .globl _hwbreakpoint + .globl _IRQ1_interrupt + .globl _timer_interrupt + .globl _timer_shortcut + .globl _spurious_interrupt + .globl _hw_bp_trigs + .globl _mmu_bus_fault + + .globl _sys_call_table + + ;; syscall error codes + +LENOSYS = 38 + + ;; offsets into the task_struct (found at sp aligned to THREAD_SIZE, 8192) + ;; linux/sched.h + +LTASK_SIGPENDING = 8 +LTASK_NEEDRESCHED = 20 +LTASK_PTRACE = 24 + + ;; some pt_regs offsets (from ptrace.h) + +LORIG_R10 = 4 +LR13 = 8 +LR12 = 12 +LR11 = 16 +LR10 = 20 +LR1 = 56 +LR0 = 60 +LDCCR = 68 +LSRP = 72 +LIRP = 76 + + ;; below are various parts of system_call which are not in the fast-path + + ;; handle software irqs + +handle_softirq: + push r9 + jsr _do_softirq ; call the C routine for softirq handling + pop r9 + + ;; fall-through + +_ret_from_intr: + ;; check for resched only if we're going back to user-mode + + move ccr, r0 + btstq 8, r0 ; U-flag + bpl Rexit ; go back directly + nop + ba ret_with_reschedule ; go back but check schedule and signals first + nop + +reschedule: + ;; keep r9 intact + push r9 + jsr _schedule + pop r9 + ba _ret_from_sys_call + nop + + ;; return but call do_signal first +signal_return: + ei ; we can get here from an interrupt + move.d r9,r10 ; do_signals syscall/irq param + moveq 0,r11 ; oldset param - 0 in this case + move.d sp,r12 ; another argument to do_signal (the regs param) + jsr _do_signal ; arch/cris/kernel/signal.c + ba Rexit + nop + + ;; The system_call is called by a BREAK instruction, which works like + ;; an interrupt call but it stores the return PC in BRP instead of IRP. + ;; Since we dont really want to have two epilogues (one for system calls + ;; and one for interrupts) we push the contents of BRP instead of IRP in the + ;; system call prologue, to make it look like an ordinary interrupt on the + ;; stackframe. + ;; + ;; Since we can't have system calls inside interrupts, it should not matter + ;; that we don't stack IRP. + ;; + ;; In r1 we have the wanted syscall number. Arguments come in r10,r11,r12,r13,r0 + ;; + ;; 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 + push brp ; this is normally push irp + push srp + push dccr + push mof + subq 14*4,sp ; make room for r0-r13 + movem r13,[sp] ; push r0-r13 + push r10 ; push orig_r10 + clear.d [sp=sp-4] ; frametype == 0, normal stackframe + + move.d r10,r2 ; save for later + + movs.w -LENOSYS,r10 + move.d r10,[sp+LR10] ; put the default return value in r10 in the frame + + move.d sp,r10 + jsr _set_esp0 ; save top of frame (clobbers r9...) + + ;; check if this process is syscall-traced + + move.d sp, r10 + and.d -8192, r10 ; THREAD_SIZE == 8192 + move.d [r10+LTASK_PTRACE],r10 + btstq 2, r10 ; PT_TRACESYS + bmi tracesys + nop + + ;; check for sanity in the requested syscall number + + cmpu.w NR_syscalls,r1 + bcc _ret_from_sys_call + lslq 2,r1 ; multiply by 4, in the delay slot + + ;; read the system call vector into r1 + + move.d [r1+_sys_call_table],r1 + + ;; the parameter carrying registers r11, r12 and 13 are intact - restore r10. + ;; the fifth parameter (if any) was in r0, and we need to put it on the stack + + push r0 + move.d r2,r10 + + jsr r1 ; actually call the corresponding system call + addq 4,sp ; pop the r0 parameter + move.d r10,[sp+LR10] ; 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 - if 1, we came from a syscall, if 0, from an irq + + ;; check if any bottom halves need service + + move.d [_irq_stat],r0 ; softirq_active + and.d [_irq_stat+4],r0 ; softirq_mask + bne handle_softirq + nop + +ret_with_reschedule: + ;; first get the current task-struct pointer (see top for defs) + + move.d sp, r0 + and.d -8192, r0 ; THREAD_SIZE == 8192 + + ;; see if we want to reschedule into another process + + test.d [r0+LTASK_NEEDRESCHED] + bne reschedule + nop + + ;; see if we need to run signal checks (important that r9 is intact here) + + test.d [r0+LTASK_SIGPENDING] + bne signal_return + nop + +Rexit: + ;; this epilogue MUST match the prologues in multiple_interrupt, irq.h and ptregs.h + pop r10 ; frametype + bne RBFexit ; was not CRIS_FRAME_NORMAL, handle otherwise + addq 4,sp ; skip orig_r10, in delayslot + movem [sp+],r13 ; registers r0-r13 + pop mof ; multiply overflow register + pop dccr ; condition codes + pop srp ; subroutine return pointer + jmpu [sp+] ; return by popping irp and jumping there + ;; jmpu takes the U-flag into account to see if we return to + ;; user-mode or kernel mode. + +RBFexit: + cmpq 2, r10 ; was it CRIS_FRAME_FIXUP ? + beq 2f + movem [sp+],r13 ; registers r0-r13, in delay slot + pop mof ; multiply overflow register + pop dccr ; condition codes + pop srp ; subroutine return pointer + rbf [sp+] ; return by popping the CPU status + +2: pop mof ; multiply overflow register + pop dccr ; condition codes + pop srp ; subroutine return pointer + ;; now we have a 4-word SBFS frame which we do not want to restore + ;; using RBF since we have made a fixup. instead we would like to + ;; just get the PC value to restart it with, and skip the rest of + ;; the frame. + pop irp ; fixup location will be here + pop p8 ; null pop + pop p8 ; null pop + reti ; return to IRP, taking U-flag into account + pop p8 ; null pop in delayslot + + +tracesys: + ;; this first invocation of syscall_trace _requires_ that + ;; LR10 in the frame contains -LENOSYS (as is set in the beginning + ;; of system_call + + jsr _syscall_trace + + ;; now we should more or less do the same things as in the system_call + ;; but since our argument regs got clobbered during syscall_trace and + ;; because syscall_trace might want to alter them, we need to reload them + ;; from the stack-frame as we use them. + + ;; check for sanity in the requested syscall number + + move.d [sp+LR1], r1 + movs.w -LENOSYS, r10 + cmpu.w NR_syscalls,r1 + bcc 1f + lslq 2,r1 ; multiply by 4, in the delay slot + + ;; read the system call vector entry into r1 + + move.d [r1+_sys_call_table],r1 + + ;; restore r10, r11, r12, r13 and r0 into the needed registers + + move.d [sp+LORIG_R10], r10 ; LR10 is already filled with -LENOSYS + move.d [sp+LR11], r11 + move.d [sp+LR12], r12 + move.d [sp+LR13], r13 + move.d [sp+LR0], r0 + + ;; the fifth parameter needs to be put on the stack for the system + ;; call to find it + + push r0 + jsr r1 ; actually call the system-call + addq 4,sp ; pop the r0 parameter + +1: move.d r10,[sp+LR10] ; save the return value + + ;; second call of syscall_trace, to let it grab the results + + jsr _syscall_trace + + moveq 1,r9 ; "parameter" to ret_from_sys_call to show it was a sys call + ba _ret_from_sys_call + nop + + ;; from asm/processor.h, the thread_struct + +LTHREAD_KSP = 0 +LTHREAD_USP = 4 +LTHREAD_ESP0 = 8 +LTHREAD_DCCR = 12 + + ;; _resume performs the actual task-switching, by switching stack pointers + ;; input arguments: r10 = prev, r11 = next, r12 = thread offset in task struct + ;; returns old current in r10 + ;; + ;; TODO: see the i386 version. The switch_to which calls resume in our version + ;; could really be an inline asm of this. + +_resume: + push srp ; we keep the old/new PC on the stack + add.d r12, r10 ; r10 = current tasks tss + move dccr, [r10+LTHREAD_DCCR] ; save irq enable state + di + + move usp, [r10+LTHREAD_USP] ; save user-mode stackpointer + + subq 10*4, sp + movem r9, [sp] ; save non-scratch registers + + move.d sp, [r10+LTHREAD_KSP] ; save the kernel stack pointer for the old task + move.d sp, r10 ; return last running task in r10 + and.d -8192, r10 ; get task ptr from stackpointer + add.d r12, r11 ; find the new tasks tss + move.d [r11+LTHREAD_KSP], sp ; switch into the new stackframe by restoring kernel sp + + movem [sp+], r9 ; restore non-scratch registers + + move [r11+LTHREAD_USP], usp ; restore user-mode stackpointer + + move [r11+LTHREAD_DCCR], dccr ; restore irq enable status + jump [sp+] ; restore PC + + ;; This is the MMU bus fault handler. + ;; It needs to stack the CPU status and overall is different + ;; from the other interrupt handlers. + +_mmu_bus_fault: + sbfs [sp=sp-16] ; push the internal CPU status + ;; the first longword in the sbfs frame was the interrupted PC + ;; which fits nicely with the "IRP" slot in pt_regs normally used to + ;; contain the return address. used by Oops to print kernel errors.. + push srp ; make a stackframe similar to pt_regs + push dccr + push mof + di + subq 14*4, sp + movem r13, [sp] + push r10 ; dummy orig_r10 + moveq 1, r10 + push r10 ; frametype == 1, BUSFAULT frame type + + moveq 0, r9 ; busfault is equivalent to an irq + + move.d sp, r10 ; pt_regs argument to handle_mmu_bus_fault + + jsr _handle_mmu_bus_fault ; in arch/cris/mm/fault.c + + ;; now we need to return through the normal path, we cannot just + ;; do the RBFexit since we might have killed off the running + ;; process due to a SEGV, scheduled due to a page blocking or + ;; whatever. + + ba _ret_from_intr + nop + + ;; special handlers for breakpoint and NMI +#if 0 +_hwbreakpoint: + push dccr + di + push r10 + push r11 + push r12 + push r13 + clearf b + move brp,r11 + move.d [_hw_bp_msg],r10 + jsr _printk + setf b + pop r13 + pop r12 + pop r11 + pop r10 + pop dccr + retb + nop +#else +_hwbreakpoint: + push dccr + di +#if 1 + push r10 + push r11 + move.d [_hw_bp_trig_ptr],r10 + move.d [r10],r11 + cmp.d 42,r11 + beq nobp + nop + move brp,r11 + move.d r11,[r10+] + move.d r10,[_hw_bp_trig_ptr] +nobp: pop r11 + pop r10 +#endif + pop dccr + retb + nop +#endif + +_IRQ1_interrupt: +_spurious_interrupt: + di + move.b 4,r0 + move.b r0,[0xb0000030] +basse2: ba basse2 + nop + + ;; this handles the case when multiple interrupts arrive at the same time + ;; we jump to the first set interrupt bit in a priority 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!!! + push irp + push srp + push dccr + push mof + di + subq 14*4,sp + movem r13,[sp] + push r10 ; push orig_r10 + clear.d [sp=sp-4] ; frametype == 0, normal frame + + move.d _irq_shortcuts + 8,r1 + moveq 2,r2 ; first bit we care about is the timer0 irq + move.d [0xb00000d8],r0 ; read the irq bits that triggered the multiple irq +multloop: + btst r2,r0 ; check for the irq given by bit r2 + bmi do_shortcut ; actually do the shortcut + nop + addq 1,r2 ; next vector bit - remember this is in the delay slot! + addq 4,r1 ; next vector + cmpq 26,r2 + bne multloop ; process all irq's up to and including number 25 + nop + + ;; strange, we didn't get any set vector bits.. oh well, just return + + ba Rexit + nop + +do_shortcut: + test.d [r1] + beq Rexit + nop + jump [r1] ; jump to the irq handlers shortcut + + + .data + +_hw_bp_trigs: + .space 64*4 +_hw_bp_trig_ptr: + .dword _hw_bp_trigs + +/* linux/linkage.h got it wrong for this compiler currently */ + +#undef SYMBOL_NAME +#define SYMBOL_NAME(X) _/**/X + +_sys_call_table: + .long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/ + .long SYMBOL_NAME(sys_exit) + .long SYMBOL_NAME(sys_fork) + .long SYMBOL_NAME(sys_read) + .long SYMBOL_NAME(sys_write) + .long SYMBOL_NAME(sys_open) /* 5 */ + .long SYMBOL_NAME(sys_close) + .long SYMBOL_NAME(sys_waitpid) + .long SYMBOL_NAME(sys_creat) + .long SYMBOL_NAME(sys_link) + .long SYMBOL_NAME(sys_unlink) /* 10 */ + .long SYMBOL_NAME(sys_execve) + .long SYMBOL_NAME(sys_chdir) + .long SYMBOL_NAME(sys_time) + .long SYMBOL_NAME(sys_mknod) + .long SYMBOL_NAME(sys_chmod) /* 15 */ + .long SYMBOL_NAME(sys_lchown16) + .long SYMBOL_NAME(sys_ni_syscall) /* old break syscall holder */ + .long SYMBOL_NAME(sys_stat) + .long SYMBOL_NAME(sys_lseek) + .long SYMBOL_NAME(sys_getpid) /* 20 */ + .long SYMBOL_NAME(sys_mount) + .long SYMBOL_NAME(sys_oldumount) + .long SYMBOL_NAME(sys_setuid16) + .long SYMBOL_NAME(sys_getuid16) + .long SYMBOL_NAME(sys_stime) /* 25 */ + .long SYMBOL_NAME(sys_ptrace) + .long SYMBOL_NAME(sys_alarm) + .long SYMBOL_NAME(sys_fstat) + .long SYMBOL_NAME(sys_pause) + .long SYMBOL_NAME(sys_utime) /* 30 */ + .long SYMBOL_NAME(sys_ni_syscall) /* old stty syscall holder */ + .long SYMBOL_NAME(sys_ni_syscall) /* old gtty syscall holder */ + .long SYMBOL_NAME(sys_access) + .long SYMBOL_NAME(sys_nice) + .long SYMBOL_NAME(sys_ni_syscall) /* 35 old ftime syscall holder */ + .long SYMBOL_NAME(sys_sync) + .long SYMBOL_NAME(sys_kill) + .long SYMBOL_NAME(sys_rename) + .long SYMBOL_NAME(sys_mkdir) + .long SYMBOL_NAME(sys_rmdir) /* 40 */ + .long SYMBOL_NAME(sys_dup) + .long SYMBOL_NAME(sys_pipe) + .long SYMBOL_NAME(sys_times) + .long SYMBOL_NAME(sys_ni_syscall) /* old prof syscall holder */ + .long SYMBOL_NAME(sys_brk) /* 45 */ + .long SYMBOL_NAME(sys_setgid16) + .long SYMBOL_NAME(sys_getgid16) + .long SYMBOL_NAME(sys_signal) + .long SYMBOL_NAME(sys_geteuid16) + .long SYMBOL_NAME(sys_getegid16) /* 50 */ + .long SYMBOL_NAME(sys_acct) + .long SYMBOL_NAME(sys_umount) /* recycled never used phys() */ + .long SYMBOL_NAME(sys_ni_syscall) /* old lock syscall holder */ + .long SYMBOL_NAME(sys_ioctl) + .long SYMBOL_NAME(sys_fcntl) /* 55 */ + .long SYMBOL_NAME(sys_ni_syscall) /* old mpx syscall holder */ + .long SYMBOL_NAME(sys_setpgid) + .long SYMBOL_NAME(sys_ni_syscall) /* old ulimit syscall holder */ + .long SYMBOL_NAME(sys_ni_syscall) /* old sys_olduname holder */ + .long SYMBOL_NAME(sys_umask) /* 60 */ + .long SYMBOL_NAME(sys_chroot) + .long SYMBOL_NAME(sys_ustat) + .long SYMBOL_NAME(sys_dup2) + .long SYMBOL_NAME(sys_getppid) + .long SYMBOL_NAME(sys_getpgrp) /* 65 */ + .long SYMBOL_NAME(sys_setsid) + .long SYMBOL_NAME(sys_sigaction) + .long SYMBOL_NAME(sys_sgetmask) + .long SYMBOL_NAME(sys_ssetmask) + .long SYMBOL_NAME(sys_setreuid16) /* 70 */ + .long SYMBOL_NAME(sys_setregid16) + .long SYMBOL_NAME(sys_sigsuspend) + .long SYMBOL_NAME(sys_sigpending) + .long SYMBOL_NAME(sys_sethostname) + .long SYMBOL_NAME(sys_setrlimit) /* 75 */ + .long SYMBOL_NAME(sys_old_getrlimit) + .long SYMBOL_NAME(sys_getrusage) + .long SYMBOL_NAME(sys_gettimeofday) + .long SYMBOL_NAME(sys_settimeofday) + .long SYMBOL_NAME(sys_getgroups16) /* 80 */ + .long SYMBOL_NAME(sys_setgroups16) + .long SYMBOL_NAME(sys_select) /* was old_select in Linux/E100 */ + .long SYMBOL_NAME(sys_symlink) + .long SYMBOL_NAME(sys_lstat) + .long SYMBOL_NAME(sys_readlink) /* 85 */ + .long SYMBOL_NAME(sys_uselib) + .long SYMBOL_NAME(sys_swapon) + .long SYMBOL_NAME(sys_reboot) + .long SYMBOL_NAME(old_readdir) + .long SYMBOL_NAME(old_mmap) /* 90 */ + .long SYMBOL_NAME(sys_munmap) + .long SYMBOL_NAME(sys_truncate) + .long SYMBOL_NAME(sys_ftruncate) + .long SYMBOL_NAME(sys_fchmod) + .long SYMBOL_NAME(sys_fchown16) /* 95 */ + .long SYMBOL_NAME(sys_getpriority) + .long SYMBOL_NAME(sys_setpriority) + .long SYMBOL_NAME(sys_ni_syscall) /* old profil syscall holder */ + .long SYMBOL_NAME(sys_statfs) + .long SYMBOL_NAME(sys_fstatfs) /* 100 */ + .long SYMBOL_NAME(sys_ni_syscall) /* sys_ioperm in i386 */ + .long SYMBOL_NAME(sys_socketcall) + .long SYMBOL_NAME(sys_syslog) + .long SYMBOL_NAME(sys_setitimer) + .long SYMBOL_NAME(sys_getitimer) /* 105 */ + .long SYMBOL_NAME(sys_newstat) + .long SYMBOL_NAME(sys_newlstat) + .long SYMBOL_NAME(sys_newfstat) + .long SYMBOL_NAME(sys_ni_syscall) /* old sys_uname holder */ + .long SYMBOL_NAME(sys_ni_syscall) /* sys_iopl in i386 */ + .long SYMBOL_NAME(sys_vhangup) + .long SYMBOL_NAME(sys_ni_syscall) /* old "idle" system call */ + .long SYMBOL_NAME(sys_ni_syscall) /* vm86old in i386 */ + .long SYMBOL_NAME(sys_wait4) + .long SYMBOL_NAME(sys_swapoff) /* 115 */ + .long SYMBOL_NAME(sys_sysinfo) + .long SYMBOL_NAME(sys_ipc) + .long SYMBOL_NAME(sys_fsync) + .long SYMBOL_NAME(sys_sigreturn) + .long SYMBOL_NAME(sys_clone) /* 120 */ + .long SYMBOL_NAME(sys_setdomainname) + .long SYMBOL_NAME(sys_newuname) + .long SYMBOL_NAME(sys_ni_syscall) /* TODO sys_modify_ldt - do something ?*/ + .long SYMBOL_NAME(sys_adjtimex) + .long SYMBOL_NAME(sys_mprotect) /* 125 */ + .long SYMBOL_NAME(sys_sigprocmask) + .long SYMBOL_NAME(sys_create_module) + .long SYMBOL_NAME(sys_init_module) + .long SYMBOL_NAME(sys_delete_module) + .long SYMBOL_NAME(sys_get_kernel_syms) /* 130 */ + .long SYMBOL_NAME(sys_quotactl) + .long SYMBOL_NAME(sys_getpgid) + .long SYMBOL_NAME(sys_fchdir) + .long SYMBOL_NAME(sys_bdflush) + .long SYMBOL_NAME(sys_sysfs) /* 135 */ + .long SYMBOL_NAME(sys_personality) + .long SYMBOL_NAME(sys_ni_syscall) /* for afs_syscall */ + .long SYMBOL_NAME(sys_setfsuid16) + .long SYMBOL_NAME(sys_setfsgid16) + .long SYMBOL_NAME(sys_llseek) /* 140 */ + .long SYMBOL_NAME(sys_getdents) + .long SYMBOL_NAME(sys_select) + .long SYMBOL_NAME(sys_flock) + .long SYMBOL_NAME(sys_msync) + .long SYMBOL_NAME(sys_readv) /* 145 */ + .long SYMBOL_NAME(sys_writev) + .long SYMBOL_NAME(sys_getsid) + .long SYMBOL_NAME(sys_fdatasync) + .long SYMBOL_NAME(sys_sysctl) + .long SYMBOL_NAME(sys_mlock) /* 150 */ + .long SYMBOL_NAME(sys_munlock) + .long SYMBOL_NAME(sys_mlockall) + .long SYMBOL_NAME(sys_munlockall) + .long SYMBOL_NAME(sys_sched_setparam) + .long SYMBOL_NAME(sys_sched_getparam) /* 155 */ + .long SYMBOL_NAME(sys_sched_setscheduler) + .long SYMBOL_NAME(sys_sched_getscheduler) + .long SYMBOL_NAME(sys_sched_yield) + .long SYMBOL_NAME(sys_sched_get_priority_max) + .long SYMBOL_NAME(sys_sched_get_priority_min) /* 160 */ + .long SYMBOL_NAME(sys_sched_rr_get_interval) + .long SYMBOL_NAME(sys_nanosleep) + .long SYMBOL_NAME(sys_mremap) + .long SYMBOL_NAME(sys_setresuid16) + .long SYMBOL_NAME(sys_getresuid16) /* 165 */ + .long SYMBOL_NAME(sys_ni_syscall) /* sys_vm86 */ + .long SYMBOL_NAME(sys_query_module) + .long SYMBOL_NAME(sys_poll) + .long SYMBOL_NAME(sys_nfsservctl) + .long SYMBOL_NAME(sys_setresgid16) /* 170 */ + .long SYMBOL_NAME(sys_getresgid16) + .long SYMBOL_NAME(sys_prctl) + .long SYMBOL_NAME(sys_rt_sigreturn) + .long SYMBOL_NAME(sys_rt_sigaction) + .long SYMBOL_NAME(sys_rt_sigprocmask) /* 175 */ + .long SYMBOL_NAME(sys_rt_sigpending) + .long SYMBOL_NAME(sys_rt_sigtimedwait) + .long SYMBOL_NAME(sys_rt_sigqueueinfo) + .long SYMBOL_NAME(sys_rt_sigsuspend) + .long SYMBOL_NAME(sys_pread) /* 180 */ + .long SYMBOL_NAME(sys_pwrite) + .long SYMBOL_NAME(sys_chown16) + .long SYMBOL_NAME(sys_getcwd) + .long SYMBOL_NAME(sys_capget) + .long SYMBOL_NAME(sys_capset) /* 185 */ + .long SYMBOL_NAME(sys_sigaltstack) + .long SYMBOL_NAME(sys_sendfile) + .long SYMBOL_NAME(sys_ni_syscall) /* streams1 */ + .long SYMBOL_NAME(sys_ni_syscall) /* streams2 */ + .long SYMBOL_NAME(sys_vfork) /* 190 */ + .long SYMBOL_NAME(sys_getrlimit) + .long SYMBOL_NAME(sys_mmap2) + .long SYMBOL_NAME(sys_truncate64) + .long SYMBOL_NAME(sys_ftruncate64) + .long SYMBOL_NAME(sys_stat64) /* 195 */ + .long SYMBOL_NAME(sys_lstat64) + .long SYMBOL_NAME(sys_fstat64) + .long SYMBOL_NAME(sys_lchown) + .long SYMBOL_NAME(sys_getuid) + .long SYMBOL_NAME(sys_getgid) /* 200 */ + .long SYMBOL_NAME(sys_geteuid) + .long SYMBOL_NAME(sys_getegid) + .long SYMBOL_NAME(sys_setreuid) + .long SYMBOL_NAME(sys_setregid) + .long SYMBOL_NAME(sys_getgroups) /* 205 */ + .long SYMBOL_NAME(sys_setgroups) + .long SYMBOL_NAME(sys_fchown) + .long SYMBOL_NAME(sys_setresuid) + .long SYMBOL_NAME(sys_getresuid) + .long SYMBOL_NAME(sys_setresgid) /* 210 */ + .long SYMBOL_NAME(sys_getresgid) + .long SYMBOL_NAME(sys_chown) + .long SYMBOL_NAME(sys_setuid) + .long SYMBOL_NAME(sys_setgid) + .long SYMBOL_NAME(sys_setfsuid) /* 215 */ + .long SYMBOL_NAME(sys_setfsgid) + .long SYMBOL_NAME(sys_pivot_root) + .long SYMBOL_NAME(sys_mincore) + .long SYMBOL_NAME(sys_madvise) + .long SYMBOL_NAME(sys_getdents64) /* 220 */ + + /* + * 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. + */ + + ;; TODO: this needs to actually generate sys_ni_syscall entires + ;; since we now have removed the check for NULL entries in this + ;; table in system_call! + + .space (NR_syscalls-220)*4 + diff --git a/arch/cris/kernel/head.S b/arch/cris/kernel/head.S new file mode 100644 index 000000000..b436aa843 --- /dev/null +++ b/arch/cris/kernel/head.S @@ -0,0 +1,519 @@ + ;; $Id: head.S,v 1.11 2001/01/16 16:31:38 bjornw Exp $ + ;; + ;; Head of the kernel - alter with care + ;; + ;; Copyright (C) 2000, 2001 Axis Communications AB + ;; + ;; Authors: Bjorn Wesen (bjornw@axis.com) + ;; + ;; $Log: head.S,v $ + ;; Revision 1.11 2001/01/16 16:31:38 bjornw + ;; * Changed name and semantics of running_from_flash to romfs_in_flash, + ;; set by head.S to indicate to setup.c whether there is a cramfs image + ;; after the kernels BSS or not. Should work for all three boot-cases + ;; (DRAM with cramfs in DRAM, DRAM with cramfs in flash (compressed boot), + ;; and flash with cramfs in flash) + ;; + ;; Revision 1.10 2001/01/16 14:12:21 bjornw + ;; * Check for cramfs start passed in r9 from the decompressor, if all other + ;; cramfs options fail (if we boot from DRAM but don't find a cramfs image + ;; after the kernel in DRAM, it is probably still in the flash) + ;; * Check magic in cramfs detection when booting from flash directly + ;; + ;; Revision 1.9 2001/01/15 17:17:02 bjornw + ;; * Corrected the code that detects the cramfs lengths + ;; * Added a comment saying that the above does not work due to other + ;; reasons.. + ;; + ;; Revision 1.8 2001/01/15 16:27:51 jonashg + ;; Made boot after flashing work. + ;; * end destination is __vmlinux_end in RAM. + ;; * _romfs_start moved because of virtual memory. + ;; + ;; Revision 1.7 2000/11/21 13:55:29 bjornw + ;; Use CONFIG_CRIS_LOW_MAP for the low VM map instead of explicit CPU type + ;; + ;; Revision 1.6 2000/10/06 12:36:55 bjornw + ;; Forgot swapper_pg_dir when changing memory map.. + ;; + ;; Revision 1.5 2000/10/04 16:49:30 bjornw + ;; * Fixed memory mapping in LX + ;; * Check for cramfs instead of romfs + ;; + ;; + +#include <linux/config.h> +#define ASSEMBLER_MACROS_ONLY +#include <asm/sv_addr_ag.h> + +#define CRAMFS_MAGIC 0x28cd3d45 + + ;; exported symbols + + .globl _etrax_irv + .globl _romfs_start + .globl _romfs_length + .globl _romfs_in_flash + .globl _swapper_pg_dir + + .text + + ;; This is the entry point of the kernel. We are in supervisor mode. + ;; 0x00000000 if Flash, 0x40004000 if DRAM + ;; since etrax actually starts at address 2 when booting from flash, we + ;; put a nop (2 bytes) here first so we dont accidentally skip the di + ;; + ;; NOTICE! The register r9 is used as a parameter carrying register from + ;; the decompressor (if the kernel was compressed). It should not be + ;; used in the code below until it is read. + + nop + di + + ;; First setup the kseg_c mapping from where the kernel is linked + ;; to 0x40000000 (where the actual DRAM resides) otherwise + ;; we cannot do very much! See arch/cris/README.mm + ;; + ;; Notice that since we're potentially running at 0x00 or 0x40 right now, + ;; we will get a fault as soon as we enable the MMU if we dont + ;; temporarily map those segments linearily. + ;; + ;; Due to a bug in Etrax-100 LX version 1 we need to map the memory + ;; slightly different. We also let the simulator get this mapping for now. + +#ifdef CONFIG_CRIS_LOW_MAP + move.d 0x0800b000, r0 ; kseg mappings + move.d r0, [R_MMU_KBASE_HI] + + move.d 0x04040000, r0 ; temporary map of 0x40->0x40 and 0x00->0x00 + move.d r0, [R_MMU_KBASE_LO] + + move.d 0x80074871, r0 ; mmu enable, segs e,b,6,5,4,0 segment mapped + move.d r0, [R_MMU_CONFIG] +#else + move.d 0x0804b000, r0 ; kseg mappings + move.d r0, [R_MMU_KBASE_HI] + + move.d 0x00040000, r0 ; temporary map of 0x40->0x40 and 0x00->0x00 + move.d r0, [R_MMU_KBASE_LO] + + move.d 0x8007d811, r0 ; mmu enable, segs f,e,c,b,4,0 segment mapped + move.d r0, [R_MMU_CONFIG] +#endif + + ;; Now we need to sort out the segments and their locations in RAM or + ;; Flash. The image in the Flash (or in DRAM) consists of 3 pieces: + ;; 1) kernel text, 2) kernel data, 3) ROM filesystem image + ;; But the linker has linked the kernel to expect this layout in + ;; DRAM memory: + ;; 1) kernel text, 2) kernel data, 3) kernel BSS + ;; (the location of the ROM filesystem is determined by the krom driver) + ;; If we boot this from Flash, we want to keep the ROM filesystem in + ;; the flash, we want to copy the text and need to copy the data to DRAM. + ;; But if we boot from DRAM, we need to move the ROMFS image + ;; from its position after kernel data, to after kernel BSS, BEFORE the + ;; kernel starts using the BSS area (since its "overlayed" with the ROMFS) + ;; + ;; In both cases, we start in un-cached mode, and need to jump into a + ;; cached PC after we're done fiddling around with the segments. + ;; + ;; arch/etrax100/etrax100.ld sets some symbols that define the start + ;; and end of each segment. + + ;; Check if we start from DRAM or FLASH by testing PC + + move.d pc,r0 + and.d 0x7fffffff,r0 ; get rid of the non-cache bit + cmp.d 0x10000,r0 ; arbitrary... just something above this code + bcs inflash + nop + + jump inram ; enter cached ram + +inflash: + +#ifndef CONFIG_SVINTO_SIM + + ;; We need to setup the bus registers before we start using the DRAM + + move.d DEF_R_WAITSTATES, r0 + move.d r0, [R_WAITSTATES] + + move.d DEF_R_BUS_CONFIG, r0 + move.d r0, [R_BUS_CONFIG] + + move.d DEF_R_DRAM_CONFIG, r0 + move.d r0, [R_DRAM_CONFIG] + + move.d DEF_R_DRAM_TIMING, r0 + move.d r0, [R_DRAM_TIMING] + +#endif + ;; Copy text+data to DRAM + ;; This is fragile - the calculation of r4 as the image size depends + ;; on that the labels below actually are the first and last positions + ;; in the linker-script. + ;; + ;; Then the locating of the cramfs image depends on the aforementioned + ;; image being located in the flash at 0. This is most often not true, + ;; thus the following does not work (normally there is a rescue-block + ;; between the physical start of the flash and the flash-image start, + ;; and when run with compression, the kernel is actually unpacked to + ;; DRAM and we never get here in the first place :)) + + moveq 0, r0 ; source + move.d _text_start, r1 ; destination + move.d __vmlinux_end, r2 ; end destination + move.d r2, r4 + sub.d r1, r4 ; r4=__vmlinux_end in flash, used below +1: move.w [r0+], r3 + move.w r3, [r1+] + cmp.d r2, r1 + bcs 1b + nop + + ;; We keep the cramfs in the flash. + ;; There might be none, but that does not matter because + ;; we don't do anything than read some bytes here. + + moveq 0, r0 + move.d r0, [_romfs_length] ; default if there is no cramfs + + move.d [r4], r0 ; cramfs_super.magic + cmp.d CRAMFS_MAGIC, r0 + bne 1f + nop + move.d [r4 + 4], r0 ; cramfs_super.size + move.d r0, [_romfs_length] +#ifdef CONFIG_CRIS_LOW_MAP + add.d 0x50000000, r4 ; add flash start in virtual memory (cached) +#else + add.d 0xf0000000, r4 ; add flash start in virtual memory (cached) +#endif + move.d r4, [_romfs_start] +1: + moveq 1, r0 + move.d r0, [_romfs_in_flash] + + jump start_it ; enter code, cached this time + +inram: + ;; Move the ROM fs to after BSS end. This assumes that the cramfs + ;; second longword contains the length of the cramfs + + moveq 0, r0 + move.d r0, [_romfs_length] ; default if there is no cramfs + + ;; First check if there is a cramfs (magic value) + ;; Notice that we check for cramfs magic value - which is + ;; the "rom fs" we'll possibly use in 2.4 if not JFFS (which does + ;; not need this mechanism anyway) + + move.d __vmlinux_end, r0 ; the image will be after the vmlinux end address + move.d [r0], r1 ; cramfs assumes same endian on host/target + cmp.d CRAMFS_MAGIC, r1; magic value in cramfs superblock + bne no_romfs_in_ram + nop + + ;; Ok. What is its size ? + + move.d [r0 + 4], r2 ; cramfs_super.size (again, no need to swapwb) + + ;; We want to copy it to the end of the BSS + + move.d _end, r1 + + ;; Remember values so cramfs and setup can find this info + + move.d r1, [_romfs_start] ; new romfs location + move.d r2, [_romfs_length] + + ;; We need to copy it backwards, since they can be overlapping + + add.d r2, r0 + add.d r2, r1 + + ;; Go ahead. Make my loop. + + lsrq 1, r2 ; size is in bytes, we copy words + +1: move.w [r0=r0-2],r3 + move.w r3,[r1=r1-2] + subq 1, r2 + bne 1b + nop + + ;; Dont worry that the BSS is tainted. It will be cleared later. + + moveq 0, r0 + move.d r0, [_romfs_in_flash] + + jump start_it ; better skip the additional cramfs check below + +no_romfs_in_ram: + + ;; We have still one other possibility at this point - the kernel + ;; could have been unpacked to DRAM by the loader, but the cramfs + ;; image was still in the Flash directly after the compressed kernel + ;; image. The loader passes the address of the byte succeeding the + ;; last compressed byte in the flash in the register r9 when starting + ;; the kernel. Check if r9 points to a decent cramfs image! + ;; (Notice that if this is not booted from the loader, r9 will be + ;; garbage but we do sanity checks on it, the chance that it points + ;; to a cramfs magic is small.. ) + + cmp.d 0x0ffffff8, r9 + bcc 1f ; r9 points outside the flash area + nop + move.d [r9], r0 ; cramfs_super.magic + cmp.d CRAMFS_MAGIC, r0 + bne 1f + nop + move.d [r9+4], r0 ; cramfs_super.length + move.d r0, [_romfs_length] +#ifdef CONFIG_CRIS_LOW_MAP + add.d 0x50000000, r9 ; add flash start in virtual memory (cached) +#else + add.d 0xf0000000, r9 ; add flash start in virtual memory (cached) +#endif + move.d r9, [_romfs_start] + + moveq 1, r0 + move.d r0, [_romfs_in_flash] +1: + + jump start_it ; enter code, cached this time + +start_it: + ;; the kernel stack is overlayed with the task structure for each + ;; task. thus the initial kernel stack is in the same page as the + ;; init_task (but starts in the top of the page, size 8192) + move.d _init_task_union + 8192,sp + move.d _ibr_start,r0 ; this symbol is set by the linker script + move r0,ibr + move.d r0,[_etrax_irv] ; set the interrupt base register and pointer + + ;; Clear BSS region, from _bss_start to _end + + move.d __bss_start, r0 + move.d _end, r1 +1: clear.d [r0+] + cmp.d r1, r0 + bcs 1b + nop + +#ifdef CONFIG_BLK_DEV_ETRAXIDE + ;; disable ATA before enabling it in genconfig below + moveq 0,r0 + move.d r0,[R_ATA_CTRL_DATA] + move.d r0,[R_ATA_TRANSFER_CNT] + move.d r0,[R_ATA_CONFIG] +#if 0 + move.d R_PORT_G_DATA,r1 + move.d r0,[r1]; assert ATA bus-reset + nop + nop + nop + nop + nop + nop + move.d 0x08000000,r0 + move.d r0,[r1] +#endif +#endif + +#ifdef CONFIG_JULIETTE + ;; configure external DMA channel 0 before enabling it in genconfig + + moveq 0,r0 + move.d r0,[R_EXT_DMA_0_ADDR] + move.d 0x860000,r0 ; cnt enable, word size, output, stop, size 0 + move.d r0,[R_EXT_DMA_0_CMD] + + ;; reset dma4 and wait for completion + + moveq 4,r0 + move.b r0,[R_DMA_CH4_CMD] +w4u: move.b [R_DMA_CH4_CMD],r0 + and.b 7,r0 + cmp.b 4,r0 + beq w4u + nop + + ;; reset dma5 and wait for completion + + moveq 4,r0 + move.b r0,[R_DMA_CH5_CMD] +w5u: move.b [R_DMA_CH5_CMD],r0 + and.b 7,r0 + cmp.b 4,r0 + beq w5u + nop +#endif + + ;; Etrax product HW genconfig setup + + moveq 0,r0 +#if !defined(CONFIG_KGDB) && !defined(CONFIG_DMA_MEMCPY) + or.d 0x140000,r0 ; DMA channels 6 and 7 to ser0, kgdb doesnt want DMA +#endif +#if !defined(CONFIG_KGDB) || !defined(CONFIG_DEBUG_PORT1) + or.d 0xc00000,r0 ; DMA channels 8 and 9 to ser1, kgdb doesnt want DMA +#endif +#ifdef CONFIG_DMA_MEMCPY + or.d 0x003c0000,r0 ; 6/7 memory-memory DMA +#endif +#ifdef CONFIG_ETRAX100_SERIAL_PORT2 + or.d 0x2808,r0 ; DMA channels 2 and 3 to serport 2, port 2 enabled +#endif +#ifdef CONFIG_ETRAX100_SERIAL_PORT3 + or.d 0x28100,r0 ; DMA channels 4 and 5 to serport 3, port 3 enabled +#endif +#if defined(CONFIG_ETRAX100_PARALLEL_PORT0) || defined(CONFIG_ETRAX_ETHERNET_LPSLAVE) + or.w 0x4,r0 ; parport 0 enabled using DMA 2/3 +#endif +#if defined(CONFIG_ETRAX100_PARALLEL_PORT1) || defined(CONFIG_ETRAX_ETHERNET_LPSLAVE) + or.w 0x80,r0 ; parport 1 enabled using DMA 4/5 +#endif +#ifdef CONFIG_BLK_DEV_ETRAXIDE + or.d 0x3c02,r0 ; DMA channels 2 and 3 to ATA, ATA enabled +#endif +#ifdef CONFIG_JULIETTE + or.d 0x3c000,r0 ; DMA channels 4 and 5 to EXTDMA0, for Juliette +#ifndef CONFIG_BLK_DEV_ETRAXIDE + or.d 0x41,r0 ; HACK for now! To make G27 connected for the RTC +#endif +#endif + move.d r0,[_genconfig_shadow] ; init a shadow register of R_GEN_CONFIG + +#ifndef CONFIG_SVINTO_SIM + move.d r0,[R_GEN_CONFIG] + +#if 0 + moveq 4,r0 + move.b r0,[R_DMA_CH6_CMD] ; reset (ser0 dma out) + move.b r0,[R_DMA_CH7_CMD] ; reset (ser0 dma in) +w61: move.b [R_DMA_CH6_CMD],r0 ; wait for reset cycle to finish + and.b 7,r0 + cmp.b 4,r0 + beq w61 + nop +w71: move.b [R_DMA_CH7_CMD],r0 ; wait for reset cycle to finish + and.b 7,r0 + cmp.b 4,r0 + beq w71 + nop +#endif + + moveq 4,r0 + move.b r0,[R_DMA_CH8_CMD] ; reset (ser1 dma out) + move.b r0,[R_DMA_CH9_CMD] ; reset (ser1 dma in) +w81: move.b [R_DMA_CH8_CMD],r0 ; wait for reset cycle to finish + and.b 7,r0 + cmp.b 4,r0 + beq w81 + nop +w91: move.b [R_DMA_CH9_CMD],r0 ; wait for reset cycle to finish + and.b 7,r0 + cmp.b 4,r0 + beq w91 + nop + + ;; setup port PA and PB default initial directions and data + ;; including their shadow registers + + move.b DEF_R_PORT_PA_DIR,r0 + move.b r0,[_port_pa_dir_shadow] + move.b r0,[R_PORT_PA_DIR] + move.b DEF_R_PORT_PA_DATA,r0 + move.b r0,[_port_pa_data_shadow] + move.b r0,[R_PORT_PA_DATA] + + move.b DEF_R_PORT_PB_CONFIG,r0 + move.b r0,[_port_pb_config_shadow] + move.b r0,[R_PORT_PB_CONFIG] + move.b DEF_R_PORT_PB_DIR,r0 + move.b r0,[_port_pb_dir_shadow] + move.b r0,[R_PORT_PB_DIR] + move.b DEF_R_PORT_PB_DATA,r0 + move.b r0,[_port_pb_data_shadow] + move.b r0,[R_PORT_PB_DATA] + + moveq 0,r0 + move.d r0,[_port_g_data_shadow] + move.d r0,[R_PORT_G_DATA] + + ;; setup the serial port 0 at 115200 baud for debug purposes + + moveq 0,r0 + move.d r0,[R_SERIAL0_XOFF] + + move.b 0x99,r0 + move.b r0,[R_SERIAL0_BAUD] ; 115.2kbaud for both transmit and receive + + move.b 0x40,r0 ; rec enable + move.b r0,[R_SERIAL0_REC_CTRL] + + move.b 0x40,r0 ; tr enable + move.b r0,[R_SERIAL0_TR_CTRL] + + ;; setup the serial port 1 at 115200 baud for debug purposes + + moveq 0,r0 + move.d r0,[R_SERIAL1_XOFF] + + move.b 0x99,r0 + move.b r0,[R_SERIAL1_BAUD] ; 115.2kbaud for both transmit and receive + + move.b 0x40,r0 ; rec enable + move.b r0,[R_SERIAL1_REC_CTRL] + + move.b 0x40,r0 ; tr enable + move.b r0,[R_SERIAL1_TR_CTRL] + +#ifdef CONFIG_ETRAX_90000000_LEDS + ;; clear LED's on Stallone and Olga boards + moveq -1,r0 + move.d r0,[_port_90000000_shadow] + move.d r0,[0x90000000] +#endif + +#ifdef CONFIG_ETRAX100_SERIAL_PORT3 + ;; setup the serial port 3 at 115200 baud for debug purposes + + moveq 0,r0 + move.d r0,[R_SERIAL3_XOFF] + + move.b 0x99,r0 + move.b r0,[R_SERIAL3_BAUD] ; 115.2kbaud for both transmit and receive + + move.b 0x40,r0 ; rec enable + move.b r0,[R_SERIAL3_REC_CTRL] + + move.b 0x40,r0 ; tr enable + move.b r0,[R_SERIAL3_TR_CTRL] +#endif + +#endif /* CONFIG_SVINTO_SIM */ + + jump _start_kernel ; jump into the C-function _start_kernel in init/main.c + + + .data +_etrax_irv: + .dword 0 +_romfs_start: + .dword 0 +_romfs_length: + .dword 0 +_romfs_in_flash: + .dword 0 + + ;; put some special pages at the beginning of the kernel aligned + ;; to page boundaries - the kernel cannot start until after this + +#ifdef CONFIG_CRIS_LOW_MAP +_swapper_pg_dir = 0x60002000 +#else +_swapper_pg_dir = 0xc0002000 +#endif diff --git a/arch/cris/kernel/hexify.c b/arch/cris/kernel/hexify.c new file mode 100644 index 000000000..daa331fec --- /dev/null +++ b/arch/cris/kernel/hexify.c @@ -0,0 +1,31 @@ +#include <stdio.h> + + +void main() +{ + int c; + int comma=0; + int count=0; + while((c=getchar())!=EOF) + { + unsigned char x=c; + if(comma) + printf(","); + else + comma=1; + if(count==8) + { + count=0; + printf("\n"); + } + if(count==0) + printf("\t"); + printf("0x%02X",c); + count++; + } + if(count) + printf("\n"); + exit(0); +} + + diff --git a/arch/cris/kernel/irq.c b/arch/cris/kernel/irq.c new file mode 100644 index 000000000..e55f94d20 --- /dev/null +++ b/arch/cris/kernel/irq.c @@ -0,0 +1,467 @@ +/* $Id: irq.c,v 1.5 2000/08/17 15:35:15 bjornw Exp $ + * + * linux/arch/cris/kernel/irq.c + * + * Copyright (c) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + * This file contains the code used by various IRQ handling routines: + * asking for different IRQ's should be done through these routines + * instead of just grabbing them. Thus setups with different IRQ numbers + * shouldn't result in any weird surprises, and installing new handlers + * should be easier. + * + * Notice Linux/CRIS: these routines do not care about SMP + * + */ + +/* + * IRQ's are in fact implemented a bit like signal handlers for the kernel. + * Naturally it's not a 1:1 relation, but there are similarities. + */ + +#include <linux/config.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/timex.h> +#include <linux/malloc.h> +#include <linux/random.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/bitops.h> + +#include <asm/svinto.h> + +char *hw_bp_msg = "BP 0x%x\n"; + +static inline void +mask_irq(unsigned int irq_nr) +{ + *R_VECT_MASK_CLR = 1 << irq_nr; +} + +static inline void +unmask_irq(unsigned int irq_nr) +{ + *R_VECT_MASK_SET = 1 << irq_nr; +} + +void +disable_irq(unsigned int irq_nr) +{ + unsigned long flags; + + save_flags(flags); + cli(); + mask_irq(irq_nr); + restore_flags(flags); +} + +void +enable_irq(unsigned int irq_nr) +{ + unsigned long flags; + save_flags(flags); + cli(); + unmask_irq(irq_nr); + restore_flags(flags); +} + +unsigned long +probe_irq_on() +{ + return 0; +} + +int +probe_irq_off(unsigned long x) +{ + return 0; +} + +irqvectptr irq_shortcuts[NR_IRQS]; /* vector of shortcut jumps after the irq prologue */ + +/* don't use set_int_vector, it bypasses the linux interrupt handlers. it is + * global just so that the kernel gdb can use it. + */ + +void +set_int_vector(int n, irqvectptr addr, irqvectptr saddr) +{ + /* remember the shortcut entry point, after the prologue */ + + irq_shortcuts[n] = saddr; + + etrax_irv->v[n + 0x20] = (irqvectptr)addr; +} + +/* the breakpoint vector is obviously not made just like the normal irq handlers + * but needs to contain _code_ to jump to addr. + * + * the BREAK n instruction jumps to IBR + n * 8 + */ + +void +set_break_vector(int n, irqvectptr addr) +{ + unsigned short *jinstr = (unsigned short *)&etrax_irv->v[n*2]; + unsigned long *jaddr = (unsigned long *)(jinstr + 1); + + /* if you don't know what this does, do not touch it! */ + + *jinstr = 0x0d3f; + *jaddr = (unsigned long)addr; + + /* 00000026 <clrlop+1a> 3f0d82000000 jump 0x82 */ +} + + +/* + * This builds up the IRQ handler stubs using some ugly macros in irq.h + * + * These macros create the low-level assembly IRQ routines that do all + * the operations that are needed. They are also written to be fast - and to + * disable interrupts as little as humanly possible. + * + */ + +/* IRQ0 and 1 are special traps */ +void hwbreakpoint(void); +void IRQ1_interrupt(void); +BUILD_IRQ(2, 0x04) /* the timer interrupt */ +BUILD_IRQ(3, 0x08) +BUILD_IRQ(4, 0x10) +BUILD_IRQ(5, 0x20) +BUILD_IRQ(6, 0x40) +BUILD_IRQ(7, 0x80) +BUILD_IRQ(8, 0x100) +BUILD_IRQ(9, 0x200) +BUILD_IRQ(10, 0x400) +BUILD_IRQ(11, 0x800) +BUILD_IRQ(12, 0x1000) +BUILD_IRQ(13, 0x2000) +void mmu_bus_fault(void); /* IRQ 14 is the bus fault interrupt */ +void multiple_interrupt(void); /* IRQ 15 is the multiple IRQ interrupt */ +BUILD_IRQ(16, 0x10000) +BUILD_IRQ(17, 0x20000) +BUILD_IRQ(18, 0x40000) +BUILD_IRQ(19, 0x80000) +BUILD_IRQ(20, 0x100000) +BUILD_IRQ(21, 0x200000) +BUILD_IRQ(22, 0x400000) +BUILD_IRQ(23, 0x800000) +BUILD_IRQ(24, 0x1000000) +BUILD_IRQ(25, 0x2000000) + +/* + * Pointers to the low-level handlers + */ + +static void (*interrupt[NR_IRQS])(void) = { + NULL, NULL, IRQ2_interrupt, IRQ3_interrupt, + IRQ4_interrupt, IRQ5_interrupt, IRQ6_interrupt, IRQ7_interrupt, + IRQ8_interrupt, IRQ9_interrupt, IRQ10_interrupt, IRQ11_interrupt, + IRQ12_interrupt, IRQ13_interrupt, NULL, NULL, + IRQ16_interrupt, IRQ17_interrupt, IRQ18_interrupt, IRQ19_interrupt, + IRQ20_interrupt, IRQ21_interrupt, IRQ22_interrupt, IRQ23_interrupt, + IRQ24_interrupt, IRQ25_interrupt +}; + +static void (*sinterrupt[NR_IRQS])(void) = { + NULL, NULL, sIRQ2_interrupt, sIRQ3_interrupt, + sIRQ4_interrupt, sIRQ5_interrupt, sIRQ6_interrupt, sIRQ7_interrupt, + sIRQ8_interrupt, sIRQ9_interrupt, sIRQ10_interrupt, sIRQ11_interrupt, + sIRQ12_interrupt, sIRQ13_interrupt, NULL, NULL, + sIRQ16_interrupt, sIRQ17_interrupt, sIRQ18_interrupt, sIRQ19_interrupt, + sIRQ20_interrupt, sIRQ21_interrupt, sIRQ22_interrupt, sIRQ23_interrupt, + sIRQ24_interrupt, sIRQ25_interrupt +}; + +static void (*bad_interrupt[NR_IRQS])(void) = { + NULL, NULL, + NULL, bad_IRQ3_interrupt, + bad_IRQ4_interrupt, bad_IRQ5_interrupt, + bad_IRQ6_interrupt, bad_IRQ7_interrupt, + bad_IRQ8_interrupt, bad_IRQ9_interrupt, + bad_IRQ10_interrupt, bad_IRQ11_interrupt, + bad_IRQ12_interrupt, bad_IRQ13_interrupt, + NULL, NULL, + bad_IRQ16_interrupt, bad_IRQ17_interrupt, + bad_IRQ18_interrupt, bad_IRQ19_interrupt, + bad_IRQ20_interrupt, bad_IRQ21_interrupt, + bad_IRQ22_interrupt, bad_IRQ23_interrupt, + bad_IRQ24_interrupt, bad_IRQ25_interrupt +}; + +/* + * Initial irq handlers. + */ + +static struct irqaction *irq_action[NR_IRQS] = { + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL +}; + +int get_irq_list(char *buf) +{ + int i, len = 0; + struct irqaction * action; + + for (i = 0; i < NR_IRQS; i++) { + action = irq_action[i]; + if (!action) + continue; + len += sprintf(buf+len, "%2d: %10u %c %s", + i, kstat.irqs[0][i], + (action->flags & SA_INTERRUPT) ? '+' : ' ', + action->name); + for (action = action->next; action; action = action->next) { + len += sprintf(buf+len, ",%s %s", + (action->flags & SA_INTERRUPT) ? " +" : "", + action->name); + } + len += sprintf(buf+len, "\n"); + } + return len; +} + +/* called by the assembler IRQ entry functions defined in irq.h + * to dispatch the interrupts to registred handlers + * interrupts are disabled upon entry - depending on if the + * interrupt was registred with SA_INTERRUPT or not, interrupts + * are re-enabled or not. + */ + +asmlinkage void do_IRQ(int irq, struct pt_regs * regs) +{ + struct irqaction *action; + int do_random, cpu; + + cpu = smp_processor_id(); + irq_enter(cpu); + kstat.irqs[cpu][irq]++; + + action = *(irq + irq_action); + if (action) { + if (!(action->flags & SA_INTERRUPT)) + __sti(); + action = *(irq + irq_action); + do_random = 0; + do { + do_random |= action->flags; + action->handler(irq, action->dev_id, regs); + action = action->next; + } while (action); + if (do_random & SA_SAMPLE_RANDOM) + add_interrupt_randomness(irq); + __cli(); + } + irq_exit(cpu); + + if (softirq_active(cpu) & softirq_mask(cpu)) + do_softirq(); + + /* unmasking and bottom half handling is done magically for us. */ +} + +/* this function links in a handler into the chain of handlers for the + given irq, and if the irq has never been registred, the appropriate + handler is entered into the interrupt vector +*/ + +int setup_etrax_irq(int irq, struct irqaction * new) +{ + int shared = 0; + struct irqaction *old, **p; + unsigned long flags; + + p = irq_action + irq; + if ((old = *p) != NULL) { + /* Can't share interrupts unless both agree to */ + if (!(old->flags & new->flags & SA_SHIRQ)) + return -EBUSY; + + /* Can't share interrupts unless both are same type */ + if ((old->flags ^ new->flags) & SA_INTERRUPT) + return -EBUSY; + + /* add new interrupt at end of irq queue */ + do { + p = &old->next; + old = *p; + } while (old); + shared = 1; + } + + if (new->flags & SA_SAMPLE_RANDOM) + rand_initialize_irq(irq); + + save_flags(flags); + cli(); + *p = new; + + if (!shared) { + /* if the irq wasn't registred before, enter it into the vector table + and unmask it physically + */ + set_int_vector(irq, interrupt[irq], sinterrupt[irq]); + unmask_irq(irq); + } + + restore_flags(flags); + return 0; +} + +/* this function is called by a driver to register an irq handler + Valid flags: + SA_INTERRUPT -> it's a fast interrupt, handler called with irq disabled and + no signal checking etc is performed upon exit + SA_SHIRQ -> the interrupt can be shared between different handlers, the handler + is required to check if the irq was "aimed" at it explicitely + SA_RANDOM -> the interrupt will add to the random generators entropy +*/ + +int request_irq(unsigned int irq, + void (*handler)(int, void *, struct pt_regs *), + unsigned long irqflags, + const char * devname, + void *dev_id) +{ + int retval; + struct irqaction * action; + + /* interrupts 0 and 1 are hardware breakpoint and NMI and we can't support + these yet. interrupt 15 is the multiple irq, it's special. */ + + if(irq < 2 || irq == 15 || irq >= NR_IRQS) + return -EINVAL; + + if(!handler) + return -EINVAL; + + /* allocate and fill in a handler structure and setup the irq */ + + action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL); + if (!action) + return -ENOMEM; + + action->handler = handler; + action->flags = irqflags; + action->mask = 0; + action->name = devname; + action->next = NULL; + action->dev_id = dev_id; + + retval = setup_etrax_irq(irq, action); + + if (retval) + kfree(action); + return retval; +} + +void free_irq(unsigned int irq, void *dev_id) +{ + struct irqaction * action, **p; + unsigned long flags; + + if (irq >= NR_IRQS) { + printk("Trying to free IRQ%d\n",irq); + return; + } + for (p = irq + irq_action; (action = *p) != NULL; p = &action->next) { + if (action->dev_id != dev_id) + continue; + + /* Found it - now free it */ + save_flags(flags); + cli(); + *p = action->next; + if (!irq[irq_action]) { + mask_irq(irq); + set_int_vector(irq, bad_interrupt[irq], 0); + } + restore_flags(flags); + kfree(action); + return; + } + printk("Trying to free free IRQ%d\n",irq); +} + +void weird_irq(void) +{ + __asm__("di"); + printk("weird irq\n"); + while(1); +} + +/* init_IRQ() is called by start_kernel and is responsible for fixing IRQ masks and + setting the irq vector table to point to bad_interrupt ptrs. +*/ + +void system_call(void); /* from entry.S */ + +void init_IRQ(void) +{ + int i; + + /* clear all interrupt masks */ + +#ifndef CONFIG_SVINTO_SIM + *R_IRQ_MASK0_CLR = 0xffffffff; + *R_IRQ_MASK1_CLR = 0xffffffff; + *R_IRQ_MASK2_CLR = 0xffffffff; +#endif + + *R_VECT_MASK_CLR = 0xffffffff; + + /* clear the shortcut entry points */ + + for(i = 0; i < NR_IRQS; i++) + irq_shortcuts[i] = NULL; + + for (i = 0; i < 256; i++) + etrax_irv->v[i] = weird_irq; + + /* set all etrax irq's to the bad handlers */ + for (i = 2; i < NR_IRQS; i++) + set_int_vector(i, bad_interrupt[i], 0); + + /* except IRQ 15 which is the multiple-IRQ handler on Etrax100 */ + + set_int_vector(15, multiple_interrupt, 0); + + /* 0 and 1 which are special breakpoint/NMI traps */ + + set_int_vector(0, hwbreakpoint, 0); + set_int_vector(1, IRQ1_interrupt, 0); + + /* and irq 14 which is the mmu bus fault handler */ + + set_int_vector(14, mmu_bus_fault, 0); + + /* setup the system-call trap, which is reached by BREAK 13 */ + + set_break_vector(13, system_call); + +#ifdef CONFIG_KGDB + /* setup kgdb if its enabled, and break into the debugger */ + + kgdb_init(); + + breakpoint(); +#endif + +} diff --git a/arch/cris/kernel/kgdb.c b/arch/cris/kernel/kgdb.c new file mode 100644 index 000000000..98d427b05 --- /dev/null +++ b/arch/cris/kernel/kgdb.c @@ -0,0 +1,1540 @@ +/*!************************************************************************** +*! +*! FILE NAME : kgdb.c +*! +*! DESCRIPTION: Implementation of the gdb stub with respect to ETRAX 100. +*! It is a mix of arch/m68k/kernel/kgdb.c and cris_stub.c. +*! +*!--------------------------------------------------------------------------- +*! HISTORY +*! +*! DATE NAME CHANGES +*! ---- ---- ------- +*! Apr 26 1999 Hendrik Ruijter Initial version. +*! May 6 1999 Hendrik Ruijter Removed call to strlen in libc and removed +*! struct assignment as it generates calls to +*! memcpy in libc. +*! Jun 17 1999 Hendrik Ruijter Added gdb 4.18 support. 'X', 'qC' and 'qL'. +*! Jul 21 1999 Bjorn Wesen eLinux port +*! +*! $Log: kgdb.c,v $ +*! Revision 1.2 2001/01/12 14:22:25 orjanf +*! Updated kernel debugging support to work with ETRAX 100LX. +*! +*! Revision 1.1 2000/07/10 16:25:21 bjornw +*! Initial revision +*! +*! Revision 1.1.1.1 1999/12/03 14:57:31 bjornw +*! * Initial version of arch/cris, the latest CRIS architecture with an MMU. +*! Mostly copied from arch/etrax100 with appropriate renames of files. +*! The mm/ subdir is copied from arch/i386. +*! This does not compile yet at all. +*! +*! +*! Revision 1.4 1999/07/22 17:25:25 bjornw +*! Dont wait for + in putpacket if we havent hit the initial breakpoint yet. Added a kgdb_init function which sets up the break and irq vectors. +*! +*! Revision 1.3 1999/07/21 19:51:18 bjornw +*! Check if the interrupting char is a ctrl-C, ignore otherwise. +*! +*! Revision 1.2 1999/07/21 18:09:39 bjornw +*! Ported to eLinux architecture, and added some kgdb documentation. +*! +*! +*!--------------------------------------------------------------------------- +*! +*! $Id: kgdb.c,v 1.2 2001/01/12 14:22:25 orjanf Exp $ +*! +*! (C) Copyright 1999, Axis Communications AB, LUND, SWEDEN +*! +*!**************************************************************************/ +/* @(#) cris_stub.c 1.3 06/17/99 */ + +/* + * kgdb usage notes: + * ----------------- + * + * If you select CONFIG_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. accesing 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 <asm/setup.h> +#include <asm/ptrace.h> + +#include <asm/svinto.h> +#include <asm/irq.h> + +static int kgdb_started = 0; + +/********************************* Register image ****************************/ +/* Use the order of registers as defined in "AXIS ETRAX CRIS Programmer's + Reference", p. 1-1, with the additional register definitions of the + ETRAX 100LX in cris-opc.h. + There are 16 general 32-bit registers, R0-R15, where R14 is the stack + pointer, SP, and R15 is the program counter, PC. + There are 16 special registers, P0-P15, where three of the unimplemented + registers, P0, P4 and P8, are reserved as zero-registers. A read from + any of these registers returns zero and a write has no effect. */ + +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 */ + unsigned int r9; /* 0x24 */ + unsigned int r10; /* 0x28 */ + unsigned int r11; /* 0x2C */ + unsigned int r12; /* 0x30 */ + unsigned int r13; /* 0x34 */ + unsigned int sp; /* 0x38 Stack pointer */ + unsigned int pc; /* 0x3C Program counter */ + + unsigned char p0; /* 0x40 8-bit zero-register */ + unsigned char vr; /* 0x41 Version register */ + + unsigned short p4; /* 0x42 16-bit zero-register */ + unsigned short ccr; /* 0x44 Condition code register */ + + unsigned int mof; /* 0x46 Multiply overflow register */ + + unsigned int p8; /* 0x4A 32-bit zero-register */ + unsigned int ibr; /* 0x4E Interrupt base register */ + unsigned int irp; /* 0x52 Interrupt return pointer */ + unsigned int srp; /* 0x56 Subroutine return pointer */ + unsigned int bar; /* 0x5A Breakpoint address register */ + unsigned int dccr; /* 0x5E Double condition code register */ + unsigned int brp; /* 0x62 Breakpoint return pointer (pc in caller) */ + unsigned int usp; /* 0x66 User mode stack pointer */ +} registers; + +/************** 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. **********************/ +/* Copy the content of a register image into another. The size n is + the size of the register image. Due to struct assignment generation of + memcpy in libc. */ +static void copy_registers (registers *dptr, registers *sptr, int n); + +/* Copy the stored registers from the stack. Put the register contents + of thread thread_id in the struct reg. */ +static void copy_registers_from_stack (int thread_id, registers *reg); + +/* Copy the registers to the stack. Put the register contents of thread + thread_id from struct reg to the stack. */ +static void copy_registers_to_stack (int thread_id, registers *reg); + +/* Write a value to a specified register regno in the register image + of the current thread. */ +static int write_register (int regno, char *val); + +/* Write a value to a specified register in the stack of a thread other + than the current thread. */ +static write_stack_register (int thread_id, int regno, char *valptr); + +/* 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); + +/* Serial port, writes one character. ETRAX 100 specific. from debugport.c */ +void putDebugChar (int val); + +void enableDebugIRQ (void); + +/* 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. */ +static 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 length); /* used by etrax100ser.c */ + +/* The hook for both static (compiled) and dynamic breakpoints set by GDB. + ETRAX 100 specific. */ +void handle_breakpoint (void); /* used by irq.c */ + +/* The hook for an interrupt generated by GDB. ETRAX 100 specific. */ +void handle_interrupt (void); /* used by irq.c */ + +/* A static breakpoint to be used at startup. */ +void breakpoint (void); /* called by init/main.c */ + +/* From osys_int.c, executing_task contains the number of the current + executing task in osys. Does not know of object-oriented threads. */ +extern unsigned char executing_task; + +/* The number of characters used for a 64 bit thread identifier. */ +#define HEXCHARS_IN_THREAD_ID 16 + +/* 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 */ +#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 remcomInBuffer[BUFMAX]; +static char remcomOutBuffer[BUFMAX]; + +/* Error and warning messages. */ +enum error_type +{ + SUCCESS, E01, E02, E03, E04, E05, E06, E07 +}; +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.", + "E07 Change register content - P - the register is not stored on the stack" +}; +/********************************* Register image ****************************/ +/* Use the order of registers as defined in "AXIS ETRAX CRIS Programmer's + Reference", p. 1-1, with the additional register definitions of the + ETRAX 100LX in cris-opc.h. + There are 16 general 32-bit registers, R0-R15, where R14 is the stack + pointer, SP, and R15 is the program counter, PC. + There are 16 special registers, P0-P15, where three of the unimplemented + registers, P0, P4 and P8, are reserved as zero-registers. A read from + any of these registers returns zero and a write has no effect. */ +enum register_name +{ + R0, R1, R2, R3, + R4, R5, R6, R7, + R8, R9, R10, R11, + R12, R13, SP, PC, + P0, VR, P2, P3, + P4, CCR, P6, MOF, + P8, IBR, IRP, SRP, + BAR, DCCR, BRP, USP +}; + +/* 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, 0, 0, + 2, 2, 0, 4, + 4, 4, 4, 4, + 4, 4, 4, 4 +}; + +/* Contains the register image of the executing thread in the assembler + part of the code in order to avoid horrible addressing modes. */ +static registers reg; + +/* FIXME: Should this be used? Delete otherwise. */ +/* Contains the assumed consistency state of the register image. Uses the + enum error_type for state information. */ +static int consistency_status = SUCCESS; + +/********************************** Handle exceptions ************************/ +/* The variable reg contains the register image associated with the + current_thread_c variable. It is a complete register image created at + entry. The reg_g contains a register image of a task where the general + registers are taken from the stack and all special registers are taken + from the executing task. It is associated with current_thread_g and used + in order to provide access mainly for 'g', 'G' and 'P'. +*/ + +/* Need two task id pointers in order to handle Hct and Hgt commands. */ +static int current_thread_c = 0; +static int current_thread_g = 0; + +/* Need two register images in order to handle Hct and Hgt commands. The + variable reg_g is in addition to reg above. */ +static registers reg_g; + +/********************************** Breakpoint *******************************/ +/* Use an internal stack in the breakpoint and interrupt response routines */ +#define INTERNAL_STACK_SIZE 1024 +static 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 + BRP as it is whereas a dynamic breakpoint requires subtraction with 2 + in order to execute the instruction. The first breakpoint is static. */ +static unsigned char is_dyn_brkp = 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; +} + +int +double_this(int x) +{ + return 2 * x; +} + +/********************************* Register image ****************************/ +/* Copy the content of a register image into another. The size n is + the size of the register image. Due to struct assignment generation of + memcpy in libc. */ +static void +copy_registers (registers *dptr, registers *sptr, int n) +{ + unsigned char *dreg; + unsigned char *sreg; + + for (dreg = (unsigned char*)dptr, sreg = (unsigned char*)sptr; n > 0; n--) + *dreg++ = *sreg++; +} + +#ifdef PROCESS_SUPPORT +/* Copy the stored registers from the stack. Put the register contents + of thread thread_id in the struct reg. */ +static void +copy_registers_from_stack (int thread_id, registers *regptr) +{ + int j; + stack_registers *s = (stack_registers *)stack_list[thread_id]; + unsigned int *d = (unsigned int *)regptr; + + for (j = 13; j >= 0; j--) + *d++ = s->r[j]; + regptr->sp = (unsigned int)stack_list[thread_id]; + regptr->pc = s->pc; + regptr->dccr = s->dccr; + regptr->srp = s->srp; +} + +/* Copy the registers to the stack. Put the register contents of thread + thread_id from struct reg to the stack. */ +static void +copy_registers_to_stack (int thread_id, registers *regptr) +{ + int i; + stack_registers *d = (stack_registers *)stack_list[thread_id]; + unsigned int *s = (unsigned int *)regptr; + + for (i = 0; i < 14; i++) { + d->r[i] = *s++; + } + d->pc = regptr->pc; + d->dccr = regptr->dccr; + d->srp = regptr->srp; +} +#endif + +/* 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; + registers *current_reg = ® + + if (regno >= R0 && regno <= PC) { + /* 32-bit register with simple offset. */ + hex2mem ((unsigned char *)current_reg + regno * sizeof(unsigned int), + val, sizeof(unsigned int)); + } + else if (regno == P0 || regno == VR || regno == P4 || regno == P8) { + /* Do not support read-only registers. */ + status = E02; + } + else if (regno == CCR) { + /* 16 bit register with complex offset. (P4 is read-only, P6 is not implemented, + and P7 (MOF) is 32 bits in ETRAX 100LX. */ + hex2mem ((unsigned char *)&(current_reg->ccr) + (regno-CCR) * sizeof(unsigned short), + val, sizeof(unsigned short)); + } + else if (regno >= MOF && regno <= USP) { + /* 32 bit register with complex offset. (P8 has been taken care of.) */ + hex2mem ((unsigned char *)&(current_reg->ibr) + (regno-IBR) * sizeof(unsigned int), + val, sizeof(unsigned int)); + } + else { + /* Do not support nonexisting or unimplemented registers (P2, P3, and P6). */ + status = E05; + } + return status; +} + +#ifdef PROCESS_SUPPORT +/* Write a value to a specified register in the stack of a thread other + than the current thread. Returns status code SUCCESS or E07. */ +static int +write_stack_register (int thread_id, int regno, char *valptr) +{ + int status = SUCCESS; + stack_registers *d = (stack_registers *)stack_list[thread_id]; + unsigned int val; + + hex2mem ((unsigned char *)&val, valptr, sizeof(unsigned int)); + if (regno >= R0 && regno < SP) { + d->r[regno] = val; + } + else if (regno == SP) { + stack_list[thread_id] = val; + } + else if (regno == PC) { + d->pc = val; + } + else if (regno == SRP) { + d->srp = val; + } + else if (regno == DCCR) { + d->dccr = val; + } + else { + /* Do not support registers in the current thread. */ + status = E07; + } + return status; +} +#endif + +/* Read a value from a specified register in the register image. Returns the + value in the register or -1 for non-implemented registers. + Should check consistency_status after a call which may be E05 after changes + in the implementation. */ +static int +read_register (char regno, unsigned int *valptr) +{ + registers *current_reg = ® + + if (regno >= R0 && regno <= PC) { + /* 32-bit register with simple offset. */ + *valptr = *(unsigned int *)((char *)current_reg + regno * sizeof(unsigned int)); + return SUCCESS; + } + else if (regno == P0 || regno == VR) { + /* 8 bit register with complex offset. */ + *valptr = (unsigned int)(*(unsigned char *) + ((char *)&(current_reg->p0) + (regno-P0) * sizeof(char))); + return SUCCESS; + } + else if (regno == P4 || regno == CCR) { + /* 16 bit register with complex offset. */ + *valptr = (unsigned int)(*(unsigned short *) + ((char *)&(current_reg->p4) + (regno-P4) * sizeof(unsigned short))); + return SUCCESS; + } + else if (regno >= MOF && regno <= USP) { + /* 32 bit register with complex offset. */ + *valptr = *(unsigned int *)((char *)&(current_reg->p8) + + (regno-P8) * sizeof(unsigned int)); + return SUCCESS; + } + else { + /* Do not support nonexisting or unimplemented registers (P2, P3, and P6). */ + consistency_status = E05; + return E05; + } +} + +/********************************** 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 int do_printk = 0; + +static char * +mem2hex(char *buf, unsigned char *mem, int count) +{ + int i; + int ch; + + if (mem == NULL) { + /* Bogus read from m0. FIXME: What constitutes a valid address? */ + 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); +} + +/* 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; + } + 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 length) +{ + remcomOutBuffer[0] = 'O'; + mem2hex(&remcomOutBuffer[1], (unsigned char *)str, length); + putpacket(remcomOutBuffer); +} + +/********************************** 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 = remcomOutBuffer; + int regno; + + unsigned int reg_cont; + int status; + + /* Send trap type (converted to signal) */ + + *ptr++ = 'T'; + *ptr++ = highhex (sigval); + *ptr++ = lowhex (sigval); + + /* Send register contents. We probably only need to send the + * PC, frame pointer and stack pointer here. Other registers will be + * explicitely asked for. But for now, send all. + */ + + for (regno = R0; regno <= USP; regno++) { + /* Store n...:r...; for the registers in the buffer. */ + + status = read_register (regno, ®_cont); + + if (status == SUCCESS) { + + *ptr++ = highhex (regno); + *ptr++ = lowhex (regno); + *ptr++ = ':'; + + ptr = mem2hex(ptr, (unsigned char *)®_cont, + register_size[regno]); + *ptr++ = ';'; + } + + } + +#ifdef PROCESS_SUPPORT + /* Store the registers of the executing thread. Assume that both step, + continue, and register content requests are with respect to this + thread. The executing task is from the operating system scheduler. */ + + current_thread_c = executing_task; + current_thread_g = executing_task; + + /* A struct assignment translates into a libc memcpy call. Avoid + all libc functions in order to prevent recursive break points. */ + copy_registers (®_g, ®, sizeof(registers)); + + /* Store thread:r...; with the executing task TID. */ + gdb_cris_strcpy (&remcomOutBuffer[pos], "thread:"); + pos += gdb_cris_strlen ("thread:"); + remcomOutBuffer[pos++] = highhex (executing_task); + remcomOutBuffer[pos++] = lowhex (executing_task); + gdb_cris_strcpy (&remcomOutBuffer[pos], ";"); +#endif + + /* null-terminate and send it off */ + + *ptr = 0; + + putpacket (remcomOutBuffer); +} + +/* All expected commands are sent from remote.c. Send a response according + to the description in remote.c. */ +static void +handle_exception (int sigval) +{ + /* Avoid warning of not used. */ + + USEDFUN(handle_exception); + USEDVAR(internal_stack[0]); + + /* Send response. */ + + stub_is_stopped (sigval); + + for (;;) { + remcomOutBuffer[0] = '\0'; + getpacket (remcomInBuffer); + switch (remcomInBuffer[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. */ + + { +#ifdef PROCESS_SUPPORT + /* Use the special register content in the executing thread. */ + copy_registers (®_g, ®, sizeof(registers)); + /* Replace the content available on the stack. */ + if (current_thread_g != executing_task) { + copy_registers_from_stack (current_thread_g, ®_g); + } + mem2hex ((unsigned char *)remcomOutBuffer, (unsigned char *)®_g, sizeof(registers)); +#else + mem2hex(remcomOutBuffer, (char *)®, sizeof(registers)); +#endif + } + break; + + case 'G': + /* Write registers. GXX..XX + Each byte of register data is described by two hex digits. + Success: OK + Failure: void. */ +#ifdef PROCESS_SUPPORT + hex2mem ((unsigned char *)®_g, &remcomInBuffer[1], sizeof(registers)); + if (current_thread_g == executing_task) { + copy_registers (®, ®_g, sizeof(registers)); + } + else { + copy_registers_to_stack(current_thread_g, ®_g); + } +#else + hex2mem((char *)®, &remcomInBuffer[1], sizeof(registers)); +#endif + gdb_cris_strcpy (remcomOutBuffer, "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 (&remcomInBuffer[1], &suffix, 16); + int status; +#ifdef PROCESS_SUPPORT + if (current_thread_g =! executing_task) + status = write_stack_register (current_thread_g, regno, suffix+1); + else +#endif + status = write_register (regno, suffix+1); + + switch (status) { + case E02: + /* Do not support read-only registers. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E02]); + break; + case E05: + /* Do not support non-existing registers. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E05]); + break; + case E07: + /* Do not support non-existing registers on the stack. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E07]); + break; + default: + /* Valid register number. */ + gdb_cris_strcpy (remcomOutBuffer, "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(&remcomInBuffer[1], + &suffix, 16); int length = gdb_cris_strtol(suffix+1, 0, 16); + + mem2hex(remcomOutBuffer, addr, length); + } + 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(&remcomInBuffer[1], + &lenptr, 16); + int length = gdb_cris_strtol(lenptr+1, &dataptr, 16); + if (*lenptr == ',' && *dataptr == ':') { + if (remcomInBuffer[0] == 'M') { + hex2mem(addr, dataptr + 1, length); + } + else /* X */ { + bin2mem(addr, dataptr + 1, length); + } + gdb_cris_strcpy (remcomOutBuffer, "OK"); + } + else { + gdb_cris_strcpy (remcomOutBuffer, 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 (remcomInBuffer[1] != '\0') { + reg.pc = gdb_cris_strtol (&remcomInBuffer[1], 0, 16); + } + enableDebugIRQ(); + 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. + + Should never be invoked. The single-step is implemented on + the host side. If ever invoked, it is an internal error E04. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E04]); + putpacket (remcomOutBuffer); + return; + + case '?': + /* The last signal which caused a stop. ? + Success: SAA, where AA is the signal number. + Failure: void. */ + remcomOutBuffer[0] = 'S'; + remcomOutBuffer[1] = highhex (sigval); + remcomOutBuffer[2] = lowhex (sigval); + remcomOutBuffer[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 */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E04]); + break; +#ifdef PROCESS_SUPPORT + + case 'T': + /* Thread alive. TXX + Is thread XX alive? + Success: OK, thread XX is alive. + Failure: E03, thread XX is dead. */ + { + int thread_id = (int)gdb_cris_strtol (&remcomInBuffer[1], 0, 16); + /* Cannot tell whether it is alive or not. */ + if (thread_id >= 0 && thread_id < number_of_tasks) + gdb_cris_strcpy (remcomOutBuffer, "OK"); + } + break; + + case 'H': + /* Set thread for subsequent operations: Hct + c = 'c' for thread used in step and continue; + t can be -1 for all threads. + c = 'g' for thread used in other operations. + t = 0 means pick any thread. + Success: OK + Failure: E01 */ + { + int thread_id = gdb_cris_strtol (&remcomInBuffer[2], 0, 16); + if (remcomInBuffer[1] == 'c') { + /* c = 'c' for thread used in step and continue */ + /* Do not change current_thread_c here. It would create a mess in + the scheduler. */ + gdb_cris_strcpy (remcomOutBuffer, "OK"); + } + else if (remcomInBuffer[1] == 'g') { + /* c = 'g' for thread used in other operations. + t = 0 means pick any thread. Impossible since the scheduler does + not allow that. */ + if (thread_id >= 0 && thread_id < number_of_tasks) { + current_thread_g = thread_id; + gdb_cris_strcpy (remcomOutBuffer, "OK"); + } + else { + /* Not expected - send an error message. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E01]); + } + } + else { + /* Not expected - send an error message. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E01]); + } + } + break; + + case 'q': + case 'Q': + /* Query of general interest. qXXXX + Set general value XXXX. QXXXX=yyyy */ + { + int pos; + int nextpos; + int thread_id; + + switch (remcomInBuffer[1]) { + case 'C': + /* Identify the remote current thread. */ + gdb_cris_strcpy (&remcomOutBuffer[0], "QC"); + remcomOutBuffer[2] = highhex (current_thread_c); + remcomOutBuffer[3] = lowhex (current_thread_c); + remcomOutBuffer[4] = '\0'; + break; + case 'L': + gdb_cris_strcpy (&remcomOutBuffer[0], "QM"); + /* Reply with number of threads. */ + if (os_is_started()) { + remcomOutBuffer[2] = highhex (number_of_tasks); + remcomOutBuffer[3] = lowhex (number_of_tasks); + } + else { + remcomOutBuffer[2] = highhex (0); + remcomOutBuffer[3] = lowhex (1); + } + /* Done with the reply. */ + remcomOutBuffer[4] = lowhex (1); + pos = 5; + /* Expects the argument thread id. */ + for (; pos < (5 + HEXCHARS_IN_THREAD_ID); pos++) + remcomOutBuffer[pos] = remcomInBuffer[pos]; + /* Reply with the thread identifiers. */ + if (os_is_started()) { + /* Store the thread identifiers of all tasks. */ + for (thread_id = 0; thread_id < number_of_tasks; thread_id++) { + nextpos = pos + HEXCHARS_IN_THREAD_ID - 1; + for (; pos < nextpos; pos ++) + remcomOutBuffer[pos] = lowhex (0); + remcomOutBuffer[pos++] = lowhex (thread_id); + } + } + else { + /* Store the thread identifier of the boot task. */ + nextpos = pos + HEXCHARS_IN_THREAD_ID - 1; + for (; pos < nextpos; pos ++) + remcomOutBuffer[pos] = lowhex (0); + remcomOutBuffer[pos++] = lowhex (current_thread_c); + } + remcomOutBuffer[pos] = '\0'; + break; + default: + /* Not supported: "" */ + /* Request information about section offsets: qOffsets. */ + remcomOutBuffer[0] = 0; + break; + } + } + break; +#endif /* PROCESS_SUPPORT */ + + 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. */ + remcomOutBuffer[0] = 0; + break; + } + putpacket(remcomOutBuffer); + } +} + +/* The jump is to the address 0x00000002. Performs a complete re-start + from scratch. */ +static void +kill_restart () +{ + __asm__ volatile ("jump 2"); +} + +/********************************** Breakpoint *******************************/ +/* The hook for both a static (compiled) and a dynamic breakpoint set by GDB. + An internal stack is used by the stub. The register image of the caller is + stored in the structure register_image. + Interactive communication with the host is handled by handle_exception and + finally the register image is restored. */ + +void kgdb_handle_breakpoint(void); + +asm (" + .global _kgdb_handle_breakpoint +_kgdb_handle_breakpoint: +;; +;; Response to the break-instruction +;; +;; Create a register image of the caller +;; + move dccr,[_reg+0x5E] ; Save the flags in DCCR before disable interrupts + di ; Disable interrupts + move.d r0,[_reg] ; Save R0 + move.d r1,[_reg+0x04] ; Save R1 + move.d r2,[_reg+0x08] ; Save R2 + move.d r3,[_reg+0x0C] ; Save R3 + move.d r4,[_reg+0x10] ; Save R4 + move.d r5,[_reg+0x14] ; Save R5 + move.d r6,[_reg+0x18] ; Save R6 + move.d r7,[_reg+0x1C] ; Save R7 + move.d r8,[_reg+0x20] ; Save R8 + move.d r9,[_reg+0x24] ; Save R9 + move.d r10,[_reg+0x28] ; Save R10 + move.d r11,[_reg+0x2C] ; Save R11 + move.d r12,[_reg+0x30] ; Save R12 + move.d r13,[_reg+0x34] ; Save R13 + move.d sp,[_reg+0x38] ; Save SP (R14) +;; Due to the old assembler-versions BRP might not be recognized + .word 0xE670 ; move brp,r0 + subq 2,r0 ; Set to address of previous instruction. + move.d r0,[_reg+0x3c] ; Save the address in PC (R15) + clear.b [_reg+0x40] ; Clear P0 + move vr,[_reg+0x41] ; Save special register P1 + clear.w [_reg+0x42] ; Clear P4 + move ccr,[_reg+0x44] ; Save special register CCR + move mof,[_reg+0x46] ; P7 + clear.d [_reg+0x4A] ; Clear P8 + move ibr,[_reg+0x4E] ; P9, + move irp,[_reg+0x52] ; P10, + move srp,[_reg+0x56] ; P11, + move dtp0,[_reg+0x5A] ; P12, register BAR, assembler might not know BAR + ; P13, register DCCR already saved +;; Due to the old assembler-versions BRP might not be recognized + .word 0xE670 ; move brp,r0 +;; Static (compiled) breakpoints must return to the next instruction in order +;; to avoid infinite loops. Dynamic (gdb-invoked) must restore the instruction +;; in order to execute it when execution is continued. + test.b [_is_dyn_brkp] ; Is this a dynamic breakpoint? + beq is_static ; No, a static breakpoint + nop + subq 2,r0 ; rerun the instruction the break replaced +is_static: + moveq 1,r1 + move.b r1,[_is_dyn_brkp] ; Set the state variable to dynamic breakpoint + move.d r0,[_reg+0x62] ; Save the return address in BRP + move usp,[_reg+0x66] ; USP +;; +;; Handle the communication +;; + move.d _internal_stack+1020,sp ; Use the internal stack which grows upward + moveq 5,r10 ; SIGTRAP + jsr _handle_exception ; Interactive routine +;; +;; Return to the caller +;; + move.d [_reg],r0 ; Restore R0 + move.d [_reg+0x04],r1 ; Restore R1 + move.d [_reg+0x08],r2 ; Restore R2 + move.d [_reg+0x0C],r3 ; Restore R3 + move.d [_reg+0x10],r4 ; Restore R4 + move.d [_reg+0x14],r5 ; Restore R5 + move.d [_reg+0x18],r6 ; Restore R6 + move.d [_reg+0x1C],r7 ; Restore R7 + move.d [_reg+0x20],r8 ; Restore R8 + move.d [_reg+0x24],r9 ; Restore R9 + move.d [_reg+0x28],r10 ; Restore R10 + move.d [_reg+0x2C],r11 ; Restore R11 + move.d [_reg+0x30],r12 ; Restore R12 + move.d [_reg+0x34],r13 ; Restore R13 +;; +;; FIXME: Which registers should be restored? +;; + move.d [_reg+0x38],sp ; Restore SP (R14) + move [_reg+0x56],srp ; Restore the subroutine return pointer. + move [_reg+0x5E],dccr ; Restore DCCR + move [_reg+0x66],usp ; Restore USP + jump [_reg+0x62] ; A jump to the content in register BRP works. + nop ; +"); + +/* The hook for an interrupt generated by GDB. An internal stack is used + by the stub. The register image of the caller is stored in the structure + register_image. Interactive communication with the host is handled by + handle_exception and finally the register image is restored. Due to the + old assembler which does not recognise the break instruction and the + breakpoint return pointer hex-code is used. */ + +void kgdb_handle_serial(void); + +asm (" + .global _kgdb_handle_serial +_kgdb_handle_serial: +;; +;; Response to a serial interrupt +;; + + move dccr,[_reg+0x5E] ; Save the flags in DCCR + di ; Disable interrupts + move.d r0,[_reg] ; Save R0 + move.d r1,[_reg+0x04] ; Save R1 + move.d r2,[_reg+0x08] ; Save R2 + move.d r3,[_reg+0x0C] ; Save R3 + move.d r4,[_reg+0x10] ; Save R4 + move.d r5,[_reg+0x14] ; Save R5 + move.d r6,[_reg+0x18] ; Save R6 + move.d r7,[_reg+0x1C] ; Save R7 + move.d r8,[_reg+0x20] ; Save R8 + move.d r9,[_reg+0x24] ; Save R9 + move.d r10,[_reg+0x28] ; Save R10 + move.d r11,[_reg+0x2C] ; Save R11 + move.d r12,[_reg+0x30] ; Save R12 + move.d r13,[_reg+0x34] ; Save R13 + move.d sp,[_reg+0x38] ; Save SP (R14) + move irp,[_reg+0x3c] ; Save the address in PC (R15) + clear.b [_reg+0x40] ; Clear P0 + move vr,[_reg+0x41] ; Save special register P1, + clear.w [_reg+0x42] ; Clear P4 + move ccr,[_reg+0x44] ; Save special register CCR + move mof,[_reg+0x46] ; P7 + clear.d [_reg+0x4A] ; Clear P8 + move ibr,[_reg+0x4E] ; P9, + move irp,[_reg+0x52] ; P10, + move srp,[_reg+0x56] ; P11, + move dtp0,[_reg+0x5A] ; P12, register BAR, assembler might not know BAR + ; P13, register DCCR already saved +;; Due to the old assembler-versions BRP might not be recognized + .word 0xE670 ; move brp,r0 + move.d r0,[_reg+0x62] ; Save the return address in BRP + move usp,[_reg+0x66] ; USP + +;; get the serial character (from debugport.c) and check if its a ctrl-c + + jsr _getDebugChar + cmp.b 3, r10 + bne goback + nop + +;; +;; Handle the communication +;; + move.d _internal_stack+1020,sp ; Use the internal stack + moveq 2,r10 ; SIGINT + jsr _handle_exception ; Interactive routine + +goback: +;; +;; Return to the caller +;; + move.d [_reg],r0 ; Restore R0 + move.d [_reg+0x04],r1 ; Restore R1 + move.d [_reg+0x08],r2 ; Restore R2 + move.d [_reg+0x0C],r3 ; Restore R3 + move.d [_reg+0x10],r4 ; Restore R4 + move.d [_reg+0x14],r5 ; Restore R5 + move.d [_reg+0x18],r6 ; Restore R6 + move.d [_reg+0x1C],r7 ; Restore R7 + move.d [_reg+0x20],r8 ; Restore R8 + move.d [_reg+0x24],r9 ; Restore R9 + move.d [_reg+0x28],r10 ; Restore R10 + move.d [_reg+0x2C],r11 ; Restore R11 + move.d [_reg+0x30],r12 ; Restore R12 + move.d [_reg+0x34],r13 ; Restore R13 +;; +;; FIXME: Which registers should be restored? +;; + move.d [_reg+0x38],sp ; Restore SP (R14) + move [_reg+0x56],srp ; Restore the subroutine return pointer. + move [_reg+0x5E],dccr ; Restore DCCR + move [_reg+0x66],usp ; Restore USP + reti ; Return from the interrupt routine + nop +"); + +/* Use this static breakpoint in the start-up only. */ + +void +breakpoint(void) +{ + kgdb_started = 1; + is_dyn_brkp = 0; /* This is a static, not a dynamic breakpoint. */ + __asm__ volatile ("break 8"); /* Jump to handle_breakpoint. */ +} + +/* initialize kgdb. doesn't break into the debugger, but sets up irq and ports */ + +void +kgdb_init(void) +{ + /* could initialize debug port as well but it's done in head.S already... */ + + set_break_vector(8, kgdb_handle_breakpoint); + set_int_vector(8, kgdb_handle_serial, 0); + + enableDebugIRQ(); +} + +/****************************** End of file **********************************/ diff --git a/arch/cris/kernel/ksyms.c b/arch/cris/kernel/ksyms.c new file mode 100644 index 000000000..fa48796dc --- /dev/null +++ b/arch/cris/kernel/ksyms.c @@ -0,0 +1,2 @@ +/* no kernel support yet */ + diff --git a/arch/cris/kernel/process.c b/arch/cris/kernel/process.c new file mode 100644 index 000000000..901449879 --- /dev/null +++ b/arch/cris/kernel/process.c @@ -0,0 +1,327 @@ +/* $Id: process.c,v 1.8 2000/09/13 14:34:13 bjornw Exp $ + * + * linux/arch/cris/kernel/process.c + * + * Copyright (C) 1995 Linus Torvalds + * Copyright (C) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + */ + +/* + * This file handles the architecture-dependent parts of process handling.. + */ + +#define __KERNEL_SYSCALLS__ +#include <stdarg.h> + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/malloc.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/io.h> +#include <linux/smp.h> + +//#define DEBUG + +/* + * Initial task structure. Make this a per-architecture thing, + * because different architectures tend to have different + * alignment requirements and potentially different initial + * setup. + */ + +static struct vm_area_struct init_mmap = INIT_MMAP; +static struct fs_struct init_fs = INIT_FS; +static struct files_struct init_files = INIT_FILES; +static struct signal_struct init_signals = INIT_SIGNALS; +struct mm_struct init_mm = INIT_MM(init_mm); + +/* + * Initial task structure. + * + * We need to make sure that this is 8192-byte aligned due to the + * way process stacks are handled. This is done by having a special + * "init_task" linker map entry.. + */ + +union task_union init_task_union + __attribute__((__section__(".data.init_task"))) = + { INIT_TASK(init_task_union.task) }; + +static int hlt_counter=0; + +/* in a system call, set_esp0 is called to remember the stack frame, therefore + in the implementation of syscalls we can use that value to access the stack + frame and saved registers. +*/ + +#define currentregs ((struct pt_regs *)current->thread.esp0) + +asmlinkage void set_esp0(unsigned long ssp) +{ + current->thread.esp0 = ssp; +} + +void disable_hlt(void) +{ + hlt_counter++; +} + +void enable_hlt(void) +{ + hlt_counter--; +} + +int cpu_idle(void *unused) +{ + while(1) { + current->counter = -100; + schedule(); + } +} + +/* if the watchdog is enabled, we can simply disable interrupts and go + * into an eternal loop, and the watchdog will reset the CPU after 0.1s + */ + +void hard_reset_now (void) +{ + printk("*** HARD RESET ***\n"); + cli(); + while(1) /* waiting for RETRIBUTION! */ ; +} + +void machine_restart(void) +{ + hard_reset_now(); +} + +/* can't do much here... */ + +void machine_halt(void) +{ +} + +void machine_power_off(void) +{ +} + +/* + * This is the mechanism for creating a new kernel thread. + * + * NOTE! Only a kernel-only process(ie the swapper or direct descendants + * who haven't done an "execve()") should use this: it will work within + * a system call from a "real" process, but the process memory space will + * not be free'd until both the parent and the child have exited. + */ + +int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) +{ + register long __a __asm__ ("r10"); + + __asm__ __volatile__ + ("movu.w %1,r1\n\t" /* r1 contains syscall number, to sys_clone */ + "clear.d r10\n\t" /* r10 is argument 1 to clone */ + "move.d %2,r11\n\t" /* r11 is argument 2 to clone, the flags */ + "break 13\n\t" /* call sys_clone, this will fork */ + "test.d r10\n\t" /* parent or child? child returns 0 here. */ + "bne 1f\n\t" /* jump if parent */ + "nop\n\t" /* delay slot */ + "move.d %4,r10\n\t" /* set argument to function to call */ + "jsr %5\n\t" /* call specified function */ + "movu.w %3,r1\n\t" /* r1 is sys_exit syscall number */ + "moveq -1,r10\n\t" /* Give a really bad exit-value */ + "break 13\n\t" /* call sys_exit, killing the child */ + "1:\n\t" + : "=r" (__a) + : "g" (__NR_clone), "r" (flags | CLONE_VM), "g" (__NR_exit), + "r" (arg), "r" (fn) + : "r10", "r11", "r1"); + + return __a; +} + + + +void flush_thread(void) +{ +} + +asmlinkage void ret_from_sys_call(void); + +/* setup the child's kernel stack with a pt_regs and switch_stack on it. + * it will be un-nested 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. + * + */ + +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 + * remember that the task_struct doubles as the kernel stack for the task + */ + + childregs = ((struct pt_regs *) ((unsigned long)p + THREAD_SIZE)) - 1; + + *childregs = *regs; /* struct copy of pt_regs */ + + childregs->r10 = 0; /* child returns 0 after a fork/clone */ + + /* put the switch stack right below the pt_regs */ + + swstack = ((struct switch_stack *)childregs) - 1; + + swstack->r9 = 0; /* parameter to ret_from_sys_call, 0 == dont restart the syscall */ + + /* we want to return into ret_from_sys_call after the _resume */ + + swstack->return_ip = (unsigned long) ret_from_sys_call; + + /* fix the user-mode stackpointer */ + + p->thread.usp = usp; + + /* and the kernel-mode one */ + + p->thread.ksp = (unsigned long) swstack; + + /* esp0 keeps the pt_regs stacked structure pointer */ + + p->thread.esp0 = (unsigned long) childregs; + +#ifdef DEBUG + printk("kern_stack_page 0x%x, used stack %d, thread.usp 0x%x, usp 0x%x\n", + current->kernel_stack_page, usedstack, p->thread.usp, usp); +#endif + return 0; +} + +/* + * fill in the user structure for a core dump.. + */ +void dump_thread(struct pt_regs * regs, struct user * dump) +{ + int i; +#if 0 +/* changed the size calculations - should hopefully work better. lbt */ + dump->magic = CMAGIC; + dump->start_code = 0; + dump->start_stack = regs->esp & ~(PAGE_SIZE - 1); + dump->u_tsize = ((unsigned long) current->mm->end_code) >> PAGE_SHIFT; + dump->u_dsize = ((unsigned long) (current->mm->brk + (PAGE_SIZE-1))) >> PAGE_SHIFT; + dump->u_dsize -= dump->u_tsize; + dump->u_ssize = 0; + for (i = 0; i < 8; i++) + dump->u_debugreg[i] = current->debugreg[i]; + + if (dump->start_stack < TASK_SIZE) + dump->u_ssize = ((unsigned long) (TASK_SIZE - dump->start_stack)) >> PAGE_SHIFT; + + dump->regs = *regs; + + dump->u_fpvalid = dump_fpu (regs, &dump->i387); +#endif +} + +asmlinkage int sys_fork(void) +{ + return do_fork(SIGCHLD, rdusp(), currentregs, 0); +} + +/* if newusp is 0, we just grab the old usp */ + +asmlinkage int sys_clone(unsigned long newusp, unsigned long flags) +{ + if (!newusp) + newusp = rdusp(); + return do_fork(flags, newusp, currentregs, 0); +} + +/* 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(void) +{ + return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, rdusp(), currentregs, 0); +} + +/* + * sys_execve() executes a new program. + */ +asmlinkage int sys_execve(const char *fname, char **argv, char **envp) +{ + int error; + char *filename; + + filename = getname(fname); + error = PTR_ERR(filename); + + if (IS_ERR(filename)) + goto out; + error = do_execve(filename, argv, envp, currentregs); + putname(filename); + out: + return error; +} + +/* + * These bracket the sleeping functions.. + */ + +extern void scheduling_functions_start_here(void); +extern void scheduling_functions_end_here(void); +#define first_sched ((unsigned long) scheduling_functions_start_here) +#define last_sched ((unsigned long) scheduling_functions_end_here) + +unsigned long get_wchan(struct task_struct *p) +{ +#if 0 + /* YURGH. TODO. */ + + unsigned long ebp, esp, eip; + unsigned long stack_page; + int count = 0; + if (!p || p == current || p->state == TASK_RUNNING) + return 0; + stack_page = (unsigned long)p; + esp = p->thread.esp; + if (!stack_page || esp < stack_page || esp > 8188+stack_page) + return 0; + /* include/asm-i386/system.h:switch_to() pushes ebp last. */ + ebp = *(unsigned long *) esp; + do { + if (ebp < stack_page || ebp > 8184+stack_page) + return 0; + eip = *(unsigned long *) (ebp+4); + if (eip < first_sched || eip >= last_sched) + return eip; + ebp = *(unsigned long *) ebp; + } while (count++ < 16); +#endif + return 0; +} +#undef last_sched +#undef first_sched diff --git a/arch/cris/kernel/ptrace.c b/arch/cris/kernel/ptrace.c new file mode 100644 index 000000000..384952211 --- /dev/null +++ b/arch/cris/kernel/ptrace.c @@ -0,0 +1,340 @@ +/* + * linux/arch/cris/kernel/ptrace.c + * + * Parts taken from the m68k port. + * + * Copyright (c) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen + * + * $Log: ptrace.c,v $ + * Revision 1.3 2000/12/18 23:45:25 bjornw + * Linux/CRIS first version + * + * + */ + +#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 <asm/uaccess.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/processor.h> + +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + +/* determines which bits in DCCR the user has access to. */ +/* 1 = access 0 = no access */ +#define DCCR_MASK 0x0000001f /* XNZVC */ + +/* + * Get contents of register REGNO in task TASK. + */ +static inline 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 + */ + + if (regno == PT_USP) + return task->thread.usp; + else if (regno <= PT_MAX) + return ((unsigned long *)(task->thread.esp0))[regno]; + else + return 0; +} + +/* + * Write contents of register REGNO in task TASK. + */ +static inline int put_reg(struct task_struct *task, unsigned int regno, + unsigned long data) +{ + unsigned long *addr; + + if (regno == PT_USP) + task->thread.usp = data; + else if (regno <= PT_MAX) + ((unsigned long *)(task->thread.esp0))[regno] = data; + else + return -1; + return 0; +} + +asmlinkage int sys_ptrace(long request, long pid, long addr, long data) +{ + struct task_struct *child; + int ret; + + lock_kernel(); + ret = -EPERM; + if (request == PTRACE_TRACEME) { + /* are we already being traced? */ + if (current->ptrace & PT_PTRACED) + 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) /* you may not mess with init */ + goto out_tsk; + if (request == PTRACE_ATTACH) { + if (child == current) + goto out_tsk; + if ((!child->dumpable || + (current->uid != child->euid) || + (current->uid != child->suid) || + (current->uid != child->uid) || + (current->gid != child->egid) || + (current->gid != child->sgid) || + (!cap_issubset(child->cap_permitted, current->cap_permitted)) || + (current->gid != child->gid)) && !capable(CAP_SYS_PTRACE)) + goto out_tsk; + /* the same process cannot be attached many times */ + if (child->ptrace & PT_PTRACED) + goto out_tsk; + child->ptrace |= PT_PTRACED; + + write_lock_irq(&tasklist_lock); + if (child->p_pptr != current) { + REMOVE_LINKS(child); + child->p_pptr = current; + SET_LINKS(child); + } + write_unlock_irq(&tasklist_lock); + + send_sig(SIGSTOP, child, 1); + ret = 0; + goto out_tsk; + } + ret = -ESRCH; + if (!(child->ptrace & PT_PTRACED)) + goto out_tsk; + if (child->state != TASK_STOPPED) { + if (request != PTRACE_KILL) + goto out_tsk; + } + if (child->p_pptr != current) + goto out_tsk; + + switch (request) { + /* when I and D space are separate, these will need to be fixed. */ + case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKDATA: { + unsigned long tmp; + int copied; + + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + ret = -EIO; + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp,(unsigned long *) data); + break; + } + + /* read the word at location addr in the USER area. */ + case PTRACE_PEEKUSR: { + unsigned long tmp; + + ret = -EIO; + if ((addr & 3) || addr < 0 || addr >= sizeof(struct user)) + break; + + tmp = 0; /* Default return condition */ + ret = -EIO; + if (addr < sizeof(struct pt_regs)) { + tmp = get_reg(child, addr >> 2); + ret = put_user(tmp, (unsigned long *)data); + } + break; + } + + /* when I and D space are separate, this will have to be fixed. */ + case PTRACE_POKETEXT: /* write the word at location addr. */ + case PTRACE_POKEDATA: + ret = 0; + if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data)) + break; + ret = -EIO; + break; + + case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ + ret = -EIO; + if ((addr & 3) || addr < 0 || addr >= sizeof(struct user)) + break; + + if (addr < sizeof(struct pt_regs)) { + addr >>= 2; + + if (addr == PT_DCCR) { + /* don't allow the tracing process to change stuff like + * interrupt enable, kernel/user bit, dma enables etc. + */ + data &= DCCR_MASK; + data |= get_reg(child, PT_DCCR) & ~DCCR_MASK; + } + if (put_reg(child, addr, data)) + break; + ret = 0; + } + break; + + case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ + case PTRACE_CONT: { /* restart after signal. */ + long tmp; + + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + if (request == PTRACE_SYSCALL) + child->ptrace |= PT_TRACESYS; + else + child->ptrace &= ~PT_TRACESYS; + child->exit_code = data; + /* TODO: make sure any pending breakpoint is killed */ + wake_up_process(child); + ret = 0; + break; + } + +/* + * make the child exit. Best I can do is send it a sigkill. + * perhaps it should be put in the status that it wants to + * exit. + */ + case PTRACE_KILL: { + long tmp; + + ret = 0; + if (child->state == TASK_ZOMBIE) /* already dead */ + break; + child->exit_code = SIGKILL; + /* TODO: make sure any pending breakpoint is killed */ + wake_up_process(child); + break; + } + + case PTRACE_SINGLESTEP: { /* set the trap flag. */ + long tmp; + + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + child->ptrace &= ~PT_TRACESYS; + + /* TODO: set some clever breakpoint mechanism... */ + + child->exit_code = data; + /* give it a chance to run. */ + wake_up_process(child); + ret = 0; + break; + } + + case PTRACE_DETACH: { /* detach a process that was attached. */ + long tmp; + + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + child->ptrace &= ~(PT_PTRACED | PT_TRACESYS); + child->exit_code = data; + write_lock_irq(&tasklist_lock); + REMOVE_LINKS(child); + child->p_pptr = child->p_opptr; + SET_LINKS(child); + write_unlock_irq(&tasklist_lock); + /* TODO: make sure any pending breakpoint is killed */ + wake_up_process(child); + ret = 0; + break; + } + + case PTRACE_GETREGS: { /* Get all gp regs from the child. */ + int i; + unsigned long tmp; + for (i = 0; i <= PT_MAX; i++) { + tmp = get_reg(child, i); + if (put_user(tmp, (unsigned long *) data)) { + ret = -EFAULT; + break; + } + data += sizeof(long); + } + ret = 0; + break; + } + + case PTRACE_SETREGS: { /* Set all gp regs in the child. */ + int i; + unsigned long tmp; + for (i = 0; i <= PT_MAX; i++) { + if (get_user(tmp, (unsigned long *) data)) { + ret = -EFAULT; + break; + } + if (i == PT_DCCR) { + tmp &= DCCR_MASK; + tmp |= get_reg(child, PT_DCCR) & ~DCCR_MASK; + } + put_reg(child, i, tmp); + data += sizeof(long); + } + ret = 0; + break; + } + + default: + ret = -EIO; + break; + } +out_tsk: + free_task_struct(child); +out: + unlock_kernel(); + return ret; +} + +asmlinkage void syscall_trace(void) +{ + if ((current->ptrace & (PT_PTRACED | PT_TRACESYS)) != + (PT_PTRACED | PT_TRACESYS)) + return; + /* TODO: make a way to distinguish between a syscall stop and SIGTRAP + * delivery like in the i386 port ? + */ + current->exit_code = SIGTRAP; + current->state = TASK_STOPPED; + notify_parent(current, SIGCHLD); + schedule(); + /* + * this isn't the same as continuing with a signal, but it will do + * for normal use. strace only continues with a signal if the + * stopping signal is not SIGTRAP. -brl + */ + if (current->exit_code) { + send_sig(current->exit_code, current, 1); + current->exit_code = 0; + } +} diff --git a/arch/cris/kernel/semaphore.c b/arch/cris/kernel/semaphore.c new file mode 100644 index 000000000..5a9478f03 --- /dev/null +++ b/arch/cris/kernel/semaphore.c @@ -0,0 +1,238 @@ +/* + * Generic semaphore code. Buyer beware. Do your own + * specific changes in <asm/semaphore-helper.h> + */ + +#include <linux/sched.h> +#include <asm/semaphore-helper.h> + +/* + * Semaphores are implemented using a two-way counter: + * The "count" variable is decremented for each process + * that tries to sleep, while the "waking" variable is + * incremented when the "up()" code goes to wake up waiting + * processes. + * + * Notably, the inline "up()" and "down()" functions can + * efficiently test if they need to do any extra work (up + * needs to do something only if count was negative before + * the increment operation. + * + * waking_non_zero() (from asm/semaphore.h) must execute + * atomically. + * + * When __up() is called, the count was negative before + * incrementing it, and we need to wake up somebody. + * + * This routine adds one to the count of processes that need to + * wake up and exit. ALL waiting processes actually wake up but + * only the one that gets to the "waking" field first will gate + * through and acquire the semaphore. The others will go back + * to sleep. + * + * Note that these functions are only called when there is + * contention on the lock, and as such all this is the + * "non-critical" part of the whole semaphore business. The + * critical part is the inline stuff in <asm/semaphore.h> + * where we want to avoid any extra jumps and calls. + */ +void __up(struct semaphore *sem) +{ + wake_one_more(sem); + wake_up(&sem->wait); +} + +/* + * Perform the "down" function. Return zero for semaphore acquired, + * return negative for signalled out of the function. + * + * If called from __down, the return is ignored and the wait loop is + * not interruptible. This means that a task waiting on a semaphore + * using "down()" cannot be killed until someone does an "up()" on + * the semaphore. + * + * If called from __down_interruptible, the return value gets checked + * upon return. If the return value is negative then the task continues + * with the negative value in the return register (it can be tested by + * the caller). + * + * Either form may be used in conjunction with "up()". + * + */ + +#define DOWN_VAR \ + struct task_struct *tsk = current; \ + wait_queue_t wait; \ + init_waitqueue_entry(&wait, tsk); + +#define DOWN_HEAD(task_state) \ + \ + \ + tsk->state = (task_state); \ + add_wait_queue(&sem->wait, &wait); \ + \ + /* \ + * Ok, we're set up. sem->count is known to be less than zero \ + * so we must wait. \ + * \ + * We can let go the lock for purposes of waiting. \ + * We re-acquire it after awaking so as to protect \ + * all semaphore operations. \ + * \ + * If "up()" is called before we call waking_non_zero() then \ + * we will catch it right away. If it is called later then \ + * we will have to go through a wakeup cycle to catch it. \ + * \ + * Multiple waiters contend for the semaphore lock to see \ + * who gets to gate through and who has to wait some more. \ + */ \ + for (;;) { + +#define DOWN_TAIL(task_state) \ + tsk->state = (task_state); \ + } \ + tsk->state = TASK_RUNNING; \ + remove_wait_queue(&sem->wait, &wait); + +void __down(struct semaphore * sem) +{ + DOWN_VAR + DOWN_HEAD(TASK_UNINTERRUPTIBLE) + if (waking_non_zero(sem)) + break; + schedule(); + DOWN_TAIL(TASK_UNINTERRUPTIBLE) +} + +int __down_interruptible(struct semaphore * sem) +{ + int ret = 0; + DOWN_VAR + DOWN_HEAD(TASK_INTERRUPTIBLE) + + ret = waking_non_zero_interruptible(sem, tsk); + if (ret) + { + if (ret == 1) + /* ret != 0 only if we get interrupted -arca */ + ret = 0; + break; + } + schedule(); + DOWN_TAIL(TASK_INTERRUPTIBLE) + return ret; +} + +int __down_trylock(struct semaphore * sem) +{ + return waking_non_zero_trylock(sem); +} + +/* + * RW Semaphores + */ +void +__down_read(struct rw_semaphore *sem, int count) +{ + DOWN_VAR; + + retry_down: + if (count < 0) { + /* Wait for the lock to become unbiased. Readers + are non-exclusive. */ + + /* This takes care of granting the lock. */ + up_read(sem); + + add_wait_queue(&sem->wait, &wait); + while (atomic_read(&sem->count) < 0) { + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + if (atomic_read(&sem->count) >= 0) + break; + schedule(); + } + + remove_wait_queue(&sem->wait, &wait); + tsk->state = TASK_RUNNING; + + mb(); + count = atomic_dec_return(&sem->count); + if (count <= 0) + goto retry_down; + } else { + add_wait_queue(&sem->wait, &wait); + + while (1) { + if (test_and_clear_bit(0, &sem->granted)) + break; + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + if ((sem->granted & 1) == 0) + schedule(); + } + + remove_wait_queue(&sem->wait, &wait); + tsk->state = TASK_RUNNING; + } +} + +void +__down_write(struct rw_semaphore *sem, int count) +{ + DOWN_VAR; + + retry_down: + if (count + RW_LOCK_BIAS < 0) { + up_write(sem); + + add_wait_queue_exclusive(&sem->wait, &wait); + + while (atomic_read(&sem->count) < 0) { + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + if (atomic_read(&sem->count) >= RW_LOCK_BIAS) + break; + schedule(); + } + + remove_wait_queue(&sem->wait, &wait); + tsk->state = TASK_RUNNING; + + mb(); + count = atomic_sub_return(RW_LOCK_BIAS, &sem->count); + if (count != 0) + goto retry_down; + } else { + /* Put ourselves at the end of the list. */ + add_wait_queue_exclusive(&sem->write_bias_wait, &wait); + + while (1) { + if (test_and_clear_bit(1, &sem->granted)) + break; + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + if ((sem->granted & 2) == 0) + schedule(); + } + + remove_wait_queue(&sem->write_bias_wait, &wait); + tsk->state = TASK_RUNNING; + + /* If the lock is currently unbiased, awaken the sleepers. + FIXME: This wakes up the readers early in a bit of a + stampede -> bad! */ + if (atomic_read(&sem->count) >= 0) + wake_up(&sem->wait); + } +} + +void +__rwsem_wake(struct rw_semaphore *sem, unsigned long readers) +{ + if (readers) { + if (test_and_set_bit(0, &sem->granted)) + BUG(); + wake_up(&sem->wait); + } else { + if (test_and_set_bit(1, &sem->granted)) + BUG(); + wake_up(&sem->write_bias_wait); + } +} diff --git a/arch/cris/kernel/setup.c b/arch/cris/kernel/setup.c new file mode 100644 index 000000000..ff7b3e6ac --- /dev/null +++ b/arch/cris/kernel/setup.c @@ -0,0 +1,264 @@ +/* $Id: setup.c,v 1.8 2001/01/16 16:31:38 bjornw Exp $ + * + * linux/arch/cris/kernel/setup.c + * + * Copyright (C) 1995 Linus Torvalds + * Copyright (c) 2000 Axis Communications AB + */ + +/* + * This file handles the architecture-dependent parts of initialization + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/malloc.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/tty.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/bootmem.h> + +#include <asm/segment.h> +#include <asm/system.h> +#include <asm/smp.h> +#include <asm/types.h> +#include <asm/svinto.h> + +/* + * Setup options + */ +struct drive_info_struct { char dummy[32]; } drive_info; +struct screen_info screen_info; + +unsigned char aux_device_present; + +#ifdef CONFIG_BLK_DEV_RAM +extern int rd_doload; /* 1 = load ramdisk, 0 = don't load */ +extern int rd_prompt; /* 1 = prompt for ramdisk, 0 = don't prompt */ +extern int rd_image_start; /* starting block # of image */ +#endif + +extern int root_mountflags; +extern char _etext, _edata, _end; + +#define COMMAND_LINE_SIZE 256 + +static char command_line[COMMAND_LINE_SIZE] = { 0, }; + char saved_command_line[COMMAND_LINE_SIZE]; + +extern const unsigned long text_start, edata; /* set by the linker script */ + +extern unsigned long romfs_start, romfs_length, romfs_in_flash; /* from head.S */ + +/* This mainly sets up the memory area, and can be really confusing. + * + * The physical DRAM is virtually mapped into dram_start to dram_end + * (usually c0000000 to c0000000 + DRAM size). The physical address is + * given by the macro __pa(). + * + * In this DRAM, the kernel code and data is loaded, in the beginning. + * It really starts at c00a0000 to make room for some special pages - + * the start address is text_start. The kernel data ends at _end. After + * this the ROM filesystem is appended (if there is any). + * + * Between this address and dram_end, we have RAM pages usable to the + * boot code and the system. + * + */ + +void __init setup_arch(char **cmdline_p) +{ + unsigned long bootmap_size; + unsigned long start_pfn, max_pfn; + unsigned long memory_start; + extern void console_print_etrax(const char *b); + +#if (defined(CONFIG_CHR_DEV_FLASH) || defined(CONFIG_BLK_DEV_FLASH)) + /* TODO: move this into flash_init I think */ + flash_probe(); +#endif + + /* register an initial console printing routine for printk's */ + + init_etrax_debug(); + + /* we should really poll for DRAM size! */ + + high_memory = &dram_end; + + if(romfs_in_flash || !romfs_length) { + /* if we have the romfs in flash, or if there is no rom filesystem, + * our free area starts directly after the BSS + */ + memory_start = (unsigned long) &_end; + } else { + /* otherwise the free area starts after the ROM filesystem */ + printk("ROM fs in RAM, size %d bytes\n", romfs_length); + memory_start = romfs_start + romfs_length; + } + + /* process 1's initial memory region is the kernel code/data */ + + init_mm.start_code = (unsigned long) &text_start; + init_mm.end_code = (unsigned long) &_etext; + init_mm.end_data = (unsigned long) &_edata; + init_mm.brk = (unsigned long) &_end; + +#define PFN_UP(x) (((x) + PAGE_SIZE-1) >> PAGE_SHIFT) +#define PFN_DOWN(x) ((x) >> PAGE_SHIFT) +#define PFN_PHYS(x) ((x) << PAGE_SHIFT) + + /* min_low_pfn points to the start of DRAM, start_pfn points + * to the first DRAM pages after the kernel, and max_low_pfn + * to the end of DRAM. + */ + + /* + * partially used pages are not usable - thus + * we are rounding upwards: + */ + + start_pfn = PFN_UP(memory_start); /* usually c0000000 + kernel + romfs */ + max_pfn = PFN_DOWN((unsigned long)high_memory); /* usually c0000000 + dram size */ + + /* + * Initialize the boot-time allocator (start, end) + * + * We give it access to all our DRAM, but we could as well just have + * given it a small slice. No point in doing that though, unless we + * have non-contiguous memory and want the boot-stuff to be in, say, + * the smallest area. + * + * It will put a bitmap of the allocated pages in the beginning + * of the range we give it, but it won't mark the bitmaps pages + * as reserved. We have to do that ourselves below. + * + * We need to use init_bootmem_node instead of init_bootmem + * because our map starts at a quite high address (min_low_pfn). + */ + + max_low_pfn = max_pfn; + min_low_pfn = PAGE_OFFSET >> PAGE_SHIFT; + + bootmap_size = init_bootmem_node(NODE_DATA(0), start_pfn, + min_low_pfn, + max_low_pfn); + + /* And free all memory not belonging to the kernel (addr, size) */ + + free_bootmem(PFN_PHYS(start_pfn), PFN_PHYS(max_pfn - start_pfn)); + + /* + * Reserve the bootmem bitmap itself as well. We do this in two + * steps (first step was init_bootmem()) because this catches + * the (very unlikely) case of us accidentally initializing the + * bootmem allocator with an invalid RAM area. + * + * Arguments are start, size + */ + + reserve_bootmem(PFN_PHYS(start_pfn), bootmap_size); + + /* paging_init() sets up the MMU and marks all pages as reserved */ + + paging_init(); + + /* we dont use a command line yet, so just let it be an empty string */ + + *cmdline_p = command_line; + strcpy(command_line, "root=/dev/rom"); /* use the appended romdisk as root */ + + /* give credit for the CRIS port */ + + printk("Linux/CRIS port on ETRAX 100LX (c) 2000 Axis Communications AB\n"); + +} + +#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 + +static struct cpu_info { + char *model; + unsigned short cache; + unsigned short flags; +} cpu_info[] = { + { "ETRAX 1", 0, 0 }, + { "ETRAX 2", 0, 0 }, /* Don't say it HAS_TOKENRING - there are + lethal bugs in that chip that + prevents T-R from ever working. + Never go there, and never lead anyone + into believing it can work. BTW: + Anyone working on a T-R network + driver? :-) :-) :-) :-/ */ + { "ETRAX 3", 0, HAS_TOKENRING }, + { "ETRAX 4", 0, HAS_TOKENRING | HAS_SCSI }, + { "Unknown", 0, 0 }, + { "Unknown", 0, 0 }, + { "Unknown", 0, 0 }, + { "Simulator", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA }, + { "ETRAX 100", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_IRQ_BUG }, + { "ETRAX 100", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA }, + { "ETRAX 100LX", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_USB | HAS_MMU }, + { "Unknown", 0, 0 }, +}; + +/* + * BUFFER is PAGE_SIZE bytes long. + */ +int get_cpuinfo(char *buffer) +{ + int revision; +#ifndef CONFIG_SVINTO_SIM + unsigned char tmp; + + __asm__ volatile ("move vr,%0" : "=rm" (tmp)); + revision = tmp; +#else + /* Fake a revision for the simulator */ + revision = 7; +#endif + + return sprintf(buffer, + "cpu\t\t: CRIS\n" + "cpu revision\t: %d\n" + "cpu model\t: %s\n" + "cache size\t: %d kB\n" + "fpu\t\t: %s\n" + "mmu\t\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", + + revision, + cpu_info[revision].model, + cpu_info[revision].cache, + cpu_info[revision].flags & HAS_FPU ? "yes" : "no", + cpu_info[revision].flags & HAS_MMU ? "yes" : "no", + cpu_info[revision].flags & HAS_ETHERNET100 ? "10/100" : "10", + cpu_info[revision].flags & HAS_TOKENRING ? "4/16 Mbps" : "no", + cpu_info[revision].flags & HAS_SCSI ? "yes" : "no", + cpu_info[revision].flags & HAS_ATA ? "yes" : "no", + cpu_info[revision].flags & HAS_USB ? "yes" : "no", + (loops_per_jiffy * HZ + 500) / 100000, + ((loops_per_jiffy * HZ + 500) / 1000) % 100); +} +#endif /* CONFIG_PROC_FS */ diff --git a/arch/cris/kernel/shadows.c b/arch/cris/kernel/shadows.c new file mode 100644 index 000000000..0a6449f4c --- /dev/null +++ b/arch/cris/kernel/shadows.c @@ -0,0 +1,20 @@ +/* $Id: shadows.c,v 1.1 2000/07/10 16:25:21 bjornw Exp $ + * + * Various Etrax shadow registers. Defines for these are in include/asm-etrax100/io.h + */ + +#include <linux/config.h> + +unsigned long genconfig_shadow = 42; +unsigned long port_g_data_shadow = 42; +unsigned char port_pa_dir_shadow = 42; +unsigned char port_pa_data_shadow = 42; +unsigned char port_pb_i2c_shadow = 42; +unsigned char port_pb_config_shadow = 42; +unsigned char port_pb_dir_shadow = 42; +unsigned char port_pb_data_shadow = 42; +unsigned long r_timer_ctrl_shadow = 42; + +#ifdef CONFIG_ETRAX_90000000_LEDS +unsigned long port_90000000_shadow = 42; +#endif diff --git a/arch/cris/kernel/signal.c b/arch/cris/kernel/signal.c new file mode 100644 index 000000000..8aab31a45 --- /dev/null +++ b/arch/cris/kernel/signal.c @@ -0,0 +1,667 @@ +/* + * linux/arch/cris/kernel/signal.c + * + * Based on arch/i386/kernel/signal.c by + * Copyright (C) 1991, 1992 Linus Torvalds + * 1997-11-28 Modified for POSIX.1b signals by Richard Henderson * + * + * Ideas also taken from arch/arm. + * + * Copyright (C) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + */ + +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.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 <asm/processor.h> +#include <asm/ucontext.h> +#include <asm/uaccess.h> + +#define DEBUG_SIG 0 + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +/* a syscall in Linux/CRIS is a break 13 instruction which is 2 bytes */ +/* manipulate regs so that upon return, it will be re-executed */ + +#define RESTART_CRIS_SYS(regs) regs->r10 = regs->orig_r10; regs->irp -= 2; + +int sys_wait4(pid_t pid, unsigned long *stat_addr, + int options, unsigned long *ru); + +int do_signal(int canrestart, sigset_t *oldset, struct pt_regs *regs); + +int copy_siginfo_to_user(siginfo_t *to, siginfo_t *from) +{ + if (!access_ok (VERIFY_WRITE, to, sizeof(siginfo_t))) + return -EFAULT; + if (from->si_code < 0) + return __copy_to_user(to, from, sizeof(siginfo_t)); + else { + int err; + + /* If you change siginfo_t structure, please be sure + this code is fixed accordingly. + It should never copy any pad contained in the structure + to avoid security leaks, but must copy the generic + 3 ints plus the relevant union member. */ + err = __put_user(from->si_signo, &to->si_signo); + err |= __put_user(from->si_errno, &to->si_errno); + err |= __put_user((short)from->si_code, &to->si_code); + /* First 32bits of unions are always present. */ + err |= __put_user(from->si_pid, &to->si_pid); + switch (from->si_code >> 16) { + case __SI_FAULT >> 16: + break; + case __SI_CHLD >> 16: + err |= __put_user(from->si_utime, &to->si_utime); + err |= __put_user(from->si_stime, &to->si_stime); + err |= __put_user(from->si_status, &to->si_status); + default: + err |= __put_user(from->si_uid, &to->si_uid); + break; + /* case __SI_RT: This is not generated by the kernel as of now. */ + } + return err; + } +} + +/* + * Atomically swap in the new signal mask, and wait for a signal. + */ +int +sys_sigsuspend(old_sigset_t mask) +{ + struct pt_regs * regs = (struct pt_regs *)current_regs(); + sigset_t saveset; + + mask &= _BLOCKABLE; + spin_lock_irq(¤t->sigmask_lock); + saveset = current->blocked; + siginitset(¤t->blocked, mask); + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + + regs->r10 = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(0, &saveset, regs)) + return -EINTR; + } +} + +int +sys_rt_sigsuspend(sigset_t *unewset, size_t sigsetsize) +{ + struct pt_regs * regs = (struct pt_regs *)current_regs(); + sigset_t saveset, newset; + + /* XXX: Don't preclude handling different sized sigset_t's. */ + if (sigsetsize != sizeof(sigset_t)) + return -EINVAL; + + if (copy_from_user(&newset, unewset, sizeof(newset))) + return -EFAULT; + sigdelsetmask(&newset, ~_BLOCKABLE); + + spin_lock_irq(¤t->sigmask_lock); + saveset = current->blocked; + current->blocked = newset; + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + + regs->r10 = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(0, &saveset, regs)) + return -EINTR; + } +} + +int +sys_sigaction(int sig, const struct old_sigaction *act, + struct old_sigaction *oact) +{ + struct k_sigaction new_ka, old_ka; + int ret; + + if (act) { + old_sigset_t mask; + if (verify_area(VERIFY_READ, act, sizeof(*act)) || + __get_user(new_ka.sa.sa_handler, &act->sa_handler) || + __get_user(new_ka.sa.sa_restorer, &act->sa_restorer)) + return -EFAULT; + __get_user(new_ka.sa.sa_flags, &act->sa_flags); + __get_user(mask, &act->sa_mask); + siginitset(&new_ka.sa.sa_mask, mask); + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + if (verify_area(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || + __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer)) + return -EFAULT; + __put_user(old_ka.sa.sa_flags, &oact->sa_flags); + __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return ret; +} + +int +sys_sigaltstack(const stack_t *uss, stack_t *uoss) +{ + return do_sigaltstack(uss, uoss, rdusp()); +} + + +/* + * Do a signal return; undo the signal stack. + */ + +struct sigframe { + struct sigcontext sc; + unsigned long extramask[_NSIG_WORDS-1]; + unsigned char retcode[8]; /* trampoline code */ +}; + +struct rt_sigframe { + struct siginfo *pinfo; + void *puc; + struct siginfo info; + struct ucontext uc; + unsigned char retcode[8]; /* trampoline code */ +}; + + +static int +restore_sigcontext(struct pt_regs *regs, struct sigcontext *sc) +{ + unsigned int err = 0; + unsigned long old_usp; + + /* restore the regs from &sc->regs (same as sc, since regs is first) + * (sc is already checked for VERIFY_READ since the sigframe was + * checked in sys_sigreturn previously) + */ + + if (__copy_from_user(regs, sc, sizeof(struct pt_regs))) + goto badframe; + + /* make sure the U-flag is set so user-mode cannot fool us */ + + regs->dccr |= 1 << 8; + + /* restore the old USP as it was before we stacked the sc etc. + * (we cannot just pop the sigcontext since we aligned the sp and + * stuff after pushing it) + */ + + err |= __get_user(old_usp, &sc->usp); + + wrusp(old_usp); + + /* TODO: the other ports use regs->orig_XX to disable syscall checks + * after this completes, but we don't use that mechanism. maybe we can + * use it now ? + */ + + return err; + +badframe: + return 1; +} + +asmlinkage int sys_sigreturn(void) +{ + struct pt_regs *regs = (struct pt_regs *)current_regs(); + struct sigframe *frame = (struct sigframe *)rdusp(); + sigset_t set; + + /* + * Since we stacked the signal on a dword boundary, + * then frame should be dword aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (((long)frame) & 3) + goto badframe; + + if (verify_area(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(¤t->sigmask_lock); + current->blocked = set; + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + + if (restore_sigcontext(regs, &frame->sc)) + goto badframe; + + /* TODO: SIGTRAP when single-stepping as in arm ? */ + + return regs->r10; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +asmlinkage int sys_rt_sigreturn(void) +{ + struct pt_regs *regs = (struct pt_regs *)current_regs(); + struct rt_sigframe *frame = (struct rt_sigframe *)rdusp(); + sigset_t set; + stack_t st; + + /* + * Since we stacked the signal on a dword boundary, + * then frame should be dword aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (((long)frame) & 3) + goto badframe; + + if (verify_area(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(¤t->sigmask_lock); + current->blocked = set; + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + goto badframe; + + if (__copy_from_user(&st, &frame->uc.uc_stack, sizeof(st))) + goto badframe; + /* It is more difficult to avoid calling this function than to + call it and ignore errors. */ + do_sigaltstack(&st, NULL, rdusp()); + + return regs->r10; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* + * Set up a signal frame. + */ + +static int +setup_sigcontext(struct sigcontext *sc, struct pt_regs *regs, unsigned long mask) +{ + int err = 0; + unsigned long usp = rdusp(); + + /* copy the regs. they are first in sc so we can use sc directly */ + + err |= __copy_to_user(sc, regs, sizeof(struct pt_regs)); + + /* then some other stuff */ + + err |= __put_user(mask, &sc->oldmask); + + err |= __put_user(usp, &sc->usp); + + return err; +} + +/* figure out where we want to put the new signal frame - usually on the stack */ + +static inline void * +get_sigframe(struct k_sigaction *ka, struct pt_regs * regs, size_t frame_size) +{ + unsigned long 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 *)(sp - frame_size); +} + +/* grab and setup a signal frame. + * + * basically we stack a lot of state info, and arrange for the + * user-mode program to return to the kernel using either a + * trampoline 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) +{ + struct sigframe *frame; + unsigned long return_ip; + int 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 userspace. If provided, use a stub + already in userspace. */ + if (ka->sa.sa_flags & SA_RESTORER) { + return_ip = (unsigned long)ka->sa.sa_restorer; + } else { + /* trampoline - the desired return ip is the retcode itself */ + return_ip = (unsigned long)&frame->retcode; + /* This is movu.w __NR_sigreturn, r1; break 13; */ + /* TODO: check byteorder */ + err |= __put_user(0x1c5f, (short *)(frame->retcode+0)); + err |= __put_user(__NR_sigreturn, (short *)(frame->retcode+2)); + err |= __put_user(0xe93d, (short *)(frame->retcode+4)); + } + + if (err) + goto give_sigsegv; + + /* Set up registers for signal handler */ + + regs->irp = (unsigned long) ka->sa.sa_handler; /* what we enter NOW */ + regs->srp = return_ip; /* what we enter LATER */ + + /* 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) +{ + struct rt_sigframe *frame; + unsigned long return_ip; + int err = 0; + + frame = get_sigframe(ka, regs, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + 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 userspace. If provided, use a stub + already in userspace. */ + if (ka->sa.sa_flags & SA_RESTORER) { + return_ip = (unsigned long)ka->sa.sa_restorer; + } else { + /* trampoline - the desired return ip is the retcode itself */ + return_ip = (unsigned long)&frame->retcode; + /* This is movu.w __NR_sigreturn, r1; break 13; */ + /* TODO: check byteorder */ + err |= __put_user(0x1c5f, (short *)(frame->retcode+0)); + err |= __put_user(__NR_sigreturn, (short *)(frame->retcode+2)); + err |= __put_user(0xe93d, (short *)(frame->retcode+4)); + } + + if (err) + goto give_sigsegv; + + /* TODO what is the current->exec_domain stuff and invmap ? */ + + /* Set up registers for signal handler */ + + regs->irp = (unsigned long) ka->sa.sa_handler; /* what we enter NOW */ + regs->srp = return_ip; /* what we enter LATER */ + + /* 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); +} + +/* + * OK, we're invoking a handler + */ + +static inline void +handle_signal(int canrestart, unsigned long sig, struct k_sigaction *ka, + siginfo_t *info, sigset_t *oldset, struct pt_regs * regs) +{ + /* Are we from a system call? */ + if (canrestart) { + /* If so, check system call restarting.. */ + switch (regs->r10) { + case -ERESTARTNOHAND: + /* ERESTARTNOHAND means that the syscall should only be + restarted if there was no handler for the signal, and since + we only get here if there is a handler, we dont restart */ + regs->r10 = -EINTR; + break; + + case -ERESTARTSYS: + /* ERESTARTSYS means to 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; + } + /* fallthrough */ + case -ERESTARTNOINTR: + /* ERESTARTNOINTR means that the syscall should be called again + after the signal handler returns. */ + RESTART_CRIS_SYS(regs); + } + } + + /* 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(¤t->sigmask_lock); + sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask); + sigaddset(¤t->blocked,sig); + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + } +} + +/* + * 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. + */ +int do_signal(int canrestart, sigset_t *oldset, struct pt_regs *regs) +{ + siginfo_t info; + struct k_sigaction *ka; + + /* + * We want the common case to go fast, which + * is why we may in certain cases get here from + * kernel mode. Just return without doing anything + * if so. + */ + if (!user_mode(regs)) + return 1; + + if (!oldset) + oldset = ¤t->blocked; + + for (;;) { + unsigned long signr; + + spin_lock_irq(¤t->sigmask_lock); + signr = dequeue_signal(¤t->blocked, &info); + spin_unlock_irq(¤t->sigmask_lock); + + if (!signr) + break; + + if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) { + /* Let the debugger run. */ + current->exit_code = signr; + current->state = TASK_STOPPED; + notify_parent(current, SIGCHLD); + schedule(); + + /* We're back. Did the debugger cancel the sig? */ + if (!(signr = current->exit_code)) + continue; + current->exit_code = 0; + + /* The debugger continued. Ignore SIGSTOP. */ + if (signr == SIGSTOP) + continue; + + /* Update the siginfo structure. Is this good? */ + if (signr != info.si_signo) { + info.si_signo = signr; + info.si_errno = 0; + info.si_code = SI_USER; + info.si_pid = current->p_pptr->pid; + info.si_uid = current->p_pptr->uid; + } + + /* If the (new) signal is now blocked, requeue it. */ + if (sigismember(¤t->blocked, signr)) { + send_sig_info(signr, &info, current); + continue; + } + } + + ka = ¤t->sig->action[signr-1]; + if (ka->sa.sa_handler == SIG_IGN) { + if (signr != SIGCHLD) + continue; + /* Check for SIGCHLD: it's special. */ + while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0) + /* nothing */; + continue; + } + + if (ka->sa.sa_handler == SIG_DFL) { + int exit_code = signr; + + /* Init gets no signals it doesn't want. */ + if (current->pid == 1) + continue; + + switch (signr) { + case SIGCONT: case SIGCHLD: case SIGWINCH: + continue; + + case SIGTSTP: case SIGTTIN: case SIGTTOU: + if (is_orphaned_pgrp(current->pgrp)) + continue; + /* FALLTHRU */ + + case SIGSTOP: + current->state = TASK_STOPPED; + current->exit_code = signr; + if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP)) + notify_parent(current, SIGCHLD); + schedule(); + continue; + + case SIGQUIT: case SIGILL: case SIGTRAP: + case SIGABRT: case SIGFPE: case SIGSEGV: + case SIGBUS: case SIGSYS: case SIGXCPU: case SIGXFSZ: + if (do_coredump(signr, regs)) + exit_code |= 0x80; + /* FALLTHRU */ + + default: + lock_kernel(); + sigaddset(¤t->pending.signal, signr); + recalc_sigpending(current); + current->flags |= PF_SIGNALED; + do_exit(exit_code); + /* NOTREACHED */ + } + } + + /* Whee! Actually deliver the signal. */ + handle_signal(canrestart, signr, ka, &info, oldset, regs); + return 1; + } + + /* Did we come 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); + } + } + return 0; +} diff --git a/arch/cris/kernel/sys_cris.c b/arch/cris/kernel/sys_cris.c new file mode 100644 index 000000000..cfcb097f9 --- /dev/null +++ b/arch/cris/kernel/sys_cris.c @@ -0,0 +1,201 @@ +/* $Id: sys_cris.c,v 1.3 2000/08/02 13:59:02 bjornw Exp $ + * + * linux/arch/cris/kernel/sys_etrax.c + * + * This file contains various random system calls that + * have a non-standard calling sequence on some platforms. + * Since we don't have to do any backwards compatibility, our + * versions are done in the most "normal" way possible. + * + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/sem.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/stat.h> +#include <linux/mman.h> +#include <linux/file.h> + +#include <asm/uaccess.h> +#include <asm/ipc.h> +#include <asm/segment.h> + +/* + * sys_pipe() is the normal C calling standard for creating + * a pipe. It's not the way Unix traditionally does this, though. + */ +asmlinkage int sys_pipe(unsigned long * fildes) +{ + int fd[2]; + int error; + + lock_kernel(); + error = do_pipe(fd); + unlock_kernel(); + if (!error) { + if (copy_to_user(fildes, fd, 2*sizeof(int))) + error = -EFAULT; + } + return error; +} + +/* sys_mmap used to take a ptr to a buffer instead containing the args + * but we support syscalls with 6 arguments now + */ + +asmlinkage unsigned long sys_mmap(unsigned long addr, size_t len, + unsigned long prot, unsigned long flags, + unsigned long fd, off_t offset) +{ + struct file * file = NULL; + int ret = -EBADF; + + lock_kernel(); + if (!(flags & MAP_ANONYMOUS)) { + if (!(file = fget(fd))) + goto out; + } + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + down(¤t->mm->mmap_sem); + ret = do_mmap(file, addr, len, prot, flags, offset); + up(¤t->mm->mmap_sem); + if (file) + fput(file); + out: + unlock_kernel(); + return ret; +} + +/* common code for old and new mmaps */ +static inline long +do_mmap2(unsigned long addr, unsigned long len, unsigned long prot, + unsigned long flags, unsigned long fd, unsigned long pgoff) +{ + int error = -EBADF; + struct file * file = NULL; + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + if (!(flags & MAP_ANONYMOUS)) { + file = fget(fd); + if (!file) + goto out; + } + + down(¤t->mm->mmap_sem); + error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); + up(¤t->mm->mmap_sem); + + if (file) + fput(file); +out: + return error; +} + +asmlinkage unsigned long old_mmap(unsigned long addr, size_t len, int prot, + int flags, int fd, off_t offset) +{ + return do_mmap2(addr, len, prot, flags, fd, offset >> PAGE_SHIFT); +} + +asmlinkage long +sys_mmap2(unsigned long addr, unsigned long len, unsigned long prot, + unsigned long flags, unsigned long fd, unsigned long pgoff) +{ + return do_mmap2(addr, len, prot, flags, fd, pgoff); +} + +/* + * sys_ipc() is the de-multiplexer for the SysV IPC calls.. + * + * This is really horribly ugly. (same as arch/i386) + */ + +asmlinkage int sys_ipc (uint call, int first, int second, + int third, void *ptr, long fifth) +{ + int version, ret; + + version = call >> 16; /* hack for backward compatibility */ + call &= 0xffff; + + switch (call) { + case SEMOP: + return sys_semop (first, (struct sembuf *)ptr, second); + case SEMGET: + return sys_semget (first, second, third); + case SEMCTL: { + union semun fourth; + if (!ptr) + return -EINVAL; + if (get_user(fourth.__pad, (void **) ptr)) + return -EFAULT; + return sys_semctl (first, second, third, fourth); + } + + case MSGSND: + return sys_msgsnd (first, (struct msgbuf *) ptr, + second, third); + case MSGRCV: + switch (version) { + case 0: { + struct ipc_kludge tmp; + if (!ptr) + return -EINVAL; + + if (copy_from_user(&tmp, + (struct ipc_kludge *) ptr, + sizeof (tmp))) + return -EFAULT; + return sys_msgrcv (first, tmp.msgp, second, + tmp.msgtyp, third); + } + default: + return sys_msgrcv (first, + (struct msgbuf *) ptr, + second, fifth, third); + } + case MSGGET: + return sys_msgget ((key_t) first, second); + case MSGCTL: + return sys_msgctl (first, second, (struct msqid_ds *) ptr); + + case SHMAT: + switch (version) { + default: { + ulong raddr; + ret = sys_shmat (first, (char *) ptr, second, &raddr); + if (ret) + return ret; + return put_user (raddr, (ulong *) third); + } + case 1: /* iBCS2 emulator entry point */ + if (!segment_eq(get_fs(), get_ds())) + return -EINVAL; + return sys_shmat (first, (char *) ptr, second, (ulong *) third); + } + case SHMDT: + return sys_shmdt ((char *)ptr); + case SHMGET: + return sys_shmget (first, second, third); + case SHMCTL: + return sys_shmctl (first, second, + (struct shmid_ds *) ptr); + default: + return -EINVAL; + } +} + +/* apparently this is legacy - if we don't need this in Linux/CRIS we can remove it. */ + +asmlinkage int sys_pause(void) +{ + current->state = TASK_INTERRUPTIBLE; + schedule(); + return -ERESTARTNOHAND; +} diff --git a/arch/cris/kernel/time.c b/arch/cris/kernel/time.c new file mode 100644 index 000000000..d46ed47fa --- /dev/null +++ b/arch/cris/kernel/time.c @@ -0,0 +1,453 @@ +/* $Id: time.c,v 1.4 2000/10/17 14:44:58 bjornw Exp $ + * + * linux/arch/cris/kernel/time.c + * + * Copyright (C) 1991, 1992, 1995 Linus Torvalds + * Copyright (C) 1999, 2000 Axis Communications AB + * + * 1994-07-02 Alan Modra + * fixed set_rtc_mmss, fixed time.year for >= 2000, new mktime + * 1995-03-26 Markus Kuhn + * fixed 500 ms bug at call to set_rtc_mmss, fixed DS12887 + * precision CMOS clock update + * 1996-05-03 Ingo Molnar + * fixed time warps in do_[slow|fast]_gettimeoffset() + * 1997-09-10 Updated NTP code according to technical memorandum Jan '96 + * "A Kernel Model for Precision Timekeeping" by Dave Mills + * + * Linux/CRIS specific code: + * + * Authors: Bjorn Wesen + * + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/delay.h> + +#include <asm/segment.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/delay.h> +#include <asm/rtc.h> + +#include <linux/timex.h> +#include <linux/config.h> + +#include <asm/svinto.h> + +static int have_rtc; /* used to remember if we have an RTC or not */ + +/* define this if you need to use print_timestamp */ +/* it will make jiffies at 96 hz instead of 100 hz though */ +#undef USE_CASCADE_TIMERS + +extern int setup_etrax_irq(int, struct irqaction *); + +#define TICK_SIZE tick + +static unsigned long do_slow_gettimeoffset(void) +{ + unsigned long count; + + static unsigned long count_p = LATCH; /* 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. + */ + +#ifndef CONFIG_SVINTO_SIM + /* Not available in the xsim simulator. */ + count = *R_TIMER0_DATA; +#else + count = 0; +#endif + + jiffies_t = jiffies; + + /* + * avoiding timer inconsistencies (they are rare, but they happen)... + * there are three kinds of problems that must be avoided here: + * 1. the timer counter underflows + * 2. hardware problem with the timer, not giving us continuous time, + * the counter does small "jumps" upwards on some Pentium systems, + * thus causes time warps + * 3. we are after the timer interrupt, but the bottom half handler + * hasn't executed yet. + */ + if( jiffies_t == jiffies_p ) { + if( count > count_p ) { + } + } else + jiffies_p = jiffies_t; + + count_p = count; + + count = ((LATCH-1) - count) * TICK_SIZE; + count = (count + LATCH/2) / LATCH; + + return count; +} + +static unsigned long (*do_gettimeoffset)(void) = do_slow_gettimeoffset; + +/* + * This version of gettimeofday has near microsecond resolution. + */ +void do_gettimeofday(struct timeval *tv) +{ + unsigned long flags; + + save_flags(flags); + cli(); + *tv = xtime; + tv->tv_usec += do_gettimeoffset(); + if (tv->tv_usec >= 1000000) { + tv->tv_usec -= 1000000; + tv->tv_sec++; + } + restore_flags(flags); +} + +void do_settimeofday(struct timeval *tv) +{ + cli(); + /* This is revolting. We need to set the xtime.tv_usec + * correctly. However, the value in this location is + * is value at the last tick. + * Discover what correction gettimeofday + * would have done, and then undo it! + */ + tv->tv_usec -= do_gettimeoffset(); + + if (tv->tv_usec < 0) { + tv->tv_usec += 1000000; + tv->tv_sec--; + } + + xtime = *tv; + time_adjust = 0; /* stop active adjtime() */ + time_status |= STA_UNSYNC; + time_state = TIME_ERROR; /* p. 24, (a) */ + time_maxerror = NTP_PHASE_LIMIT; + time_esterror = NTP_PHASE_LIMIT; + sti(); +} + + +/* + * BUG: This routine does not handle hour overflow properly; it just + * sets the minutes. Usually you'll only notice that after reboot! + */ + +static int set_rtc_mmss(unsigned long nowtime) +{ + int retval = 0; + int real_seconds, real_minutes, cmos_minutes; + unsigned char save_control, save_freq_select; + + printk("set_rtc_mmss(%d)\n", nowtime); + + if(!have_rtc) + return 0; + + cmos_minutes = CMOS_READ(RTC_MINUTES); + BCD_TO_BIN(cmos_minutes); + + /* + * since we're only adjusting minutes and seconds, + * don't interfere with hour overflow. This avoids + * messing with unknown time zones but requires your + * RTC not to be off by more than 15 minutes + */ + real_seconds = nowtime % 60; + real_minutes = nowtime / 60; + if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1) + real_minutes += 30; /* correct for half hour time zone */ + real_minutes %= 60; + + if (abs(real_minutes - cmos_minutes) < 30) { + BIN_TO_BCD(real_seconds); + BIN_TO_BCD(real_minutes); + CMOS_WRITE(real_seconds,RTC_SECONDS); + CMOS_WRITE(real_minutes,RTC_MINUTES); + } else { + printk(KERN_WARNING + "set_rtc_mmss: can't update from %d to %d\n", + cmos_minutes, real_minutes); + retval = -1; + } + + return retval; +} + +/* Except from the Etrax100 HSDD about the built-in watchdog: + * + * 3.10.4 Watchdog timer + + * When the watchdog timer is started, it generates an NMI if the watchdog + * isn't restarted or stopped within 0.1 s. If it still isn't restarted or + * stopped after an additional 3.3 ms, the watchdog resets the chip. + * The watchdog timer is stopped after reset. The watchdog timer is controlled + * by the R_WATCHDOG register. The R_WATCHDOG register contains an enable bit + * and a 3-bit key value. The effect of writing to the R_WATCHDOG register is + * described in the table below: + * + * Watchdog Value written: + * state: To enable: To key: Operation: + * -------- ---------- ------- ---------- + * stopped 0 X No effect. + * stopped 1 key_val Start watchdog with key = key_val. + * started 0 ~key Stop watchdog + * started 1 ~key Restart watchdog with key = ~key. + * started X new_key_val Change key to new_key_val. + * + * Note: '~' is the bitwise NOT operator. + * + */ + +/* right now, starting the watchdog is the same as resetting it */ +#define start_watchdog reset_watchdog + +static int watchdog_key = 0; /* arbitrary number */ + +/* 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 + +extern int nr_free_pages; + +static inline void +reset_watchdog(void) +{ +#if defined(CONFIG_ETRAX_WATCHDOG) && !defined(CONFIG_SVINTO_SIM) + /* 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 ^= 0x7; /* invert key, which is 3 bits */ + *R_WATCHDOG = IO_FIELD(R_WATCHDOG, key, watchdog_key) | + IO_STATE(R_WATCHDOG, enable, start); + } +#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 */ + +static inline void +timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + /* acknowledge the timer irq */ + +#ifdef USE_CASCADE_TIMERS + *R_TIMER_CTRL = + IO_FIELD( R_TIMER_CTRL, timerdiv1, 0) | + IO_FIELD( R_TIMER_CTRL, timerdiv0, 0) | + IO_STATE( R_TIMER_CTRL, i1, clr) | + IO_STATE( R_TIMER_CTRL, tm1, run) | + IO_STATE( R_TIMER_CTRL, clksel1, cascade0) | + IO_STATE( R_TIMER_CTRL, i0, clr) | + IO_STATE( R_TIMER_CTRL, tm0, run) | + IO_STATE( R_TIMER_CTRL, clksel0, c6250kHz); +#else + *R_TIMER_CTRL = r_timer_ctrl_shadow | + IO_STATE(R_TIMER_CTRL, i0, clr); +#endif + + /* reset watchdog otherwise it resets us! */ + + reset_watchdog(); + + /* 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. + */ + + if ((time_status & STA_UNSYNC) == 0 && + xtime.tv_sec > last_rtc_update + 660 && + xtime.tv_usec > 500000 - (tick >> 1) && + xtime.tv_usec < 500000 + (tick >> 1)) + if (set_rtc_mmss(xtime.tv_sec) == 0) + last_rtc_update = xtime.tv_sec; + else + last_rtc_update = xtime.tv_sec - 600; + +} + +#if 0 +/* some old debug code for testing the microsecond timing of packets */ +static unsigned int lastjiff; + +void print_timestamp(const char *s) +{ + unsigned long flags; + unsigned int newjiff; + save_flags(flags); + cli(); + newjiff = (myjiff << 16) | (unsigned short)(-*R_TIMER01_DATA); + printk("%s: %x (%x)\n", s, newjiff, newjiff - lastjiff); + lastjiff = newjiff; + restore_flags(flags); +} +#endif + +/* grab the time from the RTC chip */ + +unsigned long +get_cmos_time(void) +{ + unsigned int year, mon, day, hour, min, sec; + int i; + + sec = CMOS_READ(RTC_SECONDS); + min = CMOS_READ(RTC_MINUTES); + hour = CMOS_READ(RTC_HOURS); + day = CMOS_READ(RTC_DAY_OF_MONTH); + mon = CMOS_READ(RTC_MONTH); + year = CMOS_READ(RTC_YEAR); + + printk("rtc: sec 0x%x min 0x%x hour 0x%x day 0x%x mon 0x%x year 0x%x\n", + sec, min, hour, day, mon, year); + + BCD_TO_BIN(sec); + BCD_TO_BIN(min); + BCD_TO_BIN(hour); + BCD_TO_BIN(day); + BCD_TO_BIN(mon); + BCD_TO_BIN(year); + + if ((year += 1900) < 1970) + year += 100; + + return mktime(year, mon, day, hour, min, sec); +} + +/* update xtime from the CMOS settings. used when /dev/rtc gets a SET_TIME. + * TODO: this doesn't reset the fancy NTP phase stuff as do_settimeofday does. + */ + +void +update_xtime_from_cmos(void) +{ + if(have_rtc) { + xtime.tv_sec = get_cmos_time(); + xtime.tv_usec = 0; + } +} + +/* 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 irq2 = { timer_interrupt, SA_SHIRQ | SA_INTERRUPT, + 0, "timer", NULL, NULL}; + +void __init +time_init(void) +{ + /* probe for the RTC and read it if it exists */ + + if(RTC_INIT() < 0) { + /* no RTC, start at 1980 */ + xtime.tv_sec = 0; + xtime.tv_usec = 0; + have_rtc = 0; + } else { + /* get the current time */ + have_rtc = 1; + update_xtime_from_cmos(); + } + + /* Setup the etrax timers + * Base frequency is 19200 hz, divider 192 -> 100 hz as Linux wants + * In normal mode, we use timer0, so timer1 is free. In cascade + * mode (which we sometimes use for debugging) both timers are used. + * Remember that linux/timex.h contains #defines that rely on the + * timer settings below (hz and divide factor) !!! + */ + +#ifdef USE_CASCADE_TIMERS + *R_TIMER_CTRL = + IO_FIELD( R_TIMER_CTRL, timerdiv1, 0) | + IO_FIELD( R_TIMER_CTRL, timerdiv0, 0) | + IO_STATE( R_TIMER_CTRL, i1, nop) | + IO_STATE( R_TIMER_CTRL, tm1, stop_ld) | + IO_STATE( R_TIMER_CTRL, clksel1, cascade0) | + IO_STATE( R_TIMER_CTRL, i0, nop) | + IO_STATE( R_TIMER_CTRL, tm0, stop_ld) | + IO_STATE( R_TIMER_CTRL, clksel0, c6250kHz); + + *R_TIMER_CTRL = r_timer_ctrl_shadow = + IO_FIELD( R_TIMER_CTRL, timerdiv1, 0) | + IO_FIELD( R_TIMER_CTRL, timerdiv0, 0) | + IO_STATE( R_TIMER_CTRL, i1, nop) | + IO_STATE( R_TIMER_CTRL, tm1, run) | + IO_STATE( R_TIMER_CTRL, clksel1, cascade0) | + IO_STATE( R_TIMER_CTRL, i0, nop) | + IO_STATE( R_TIMER_CTRL, tm0, run) | + IO_STATE( R_TIMER_CTRL, clksel0, c6250kHz); +#else + *R_TIMER_CTRL = + IO_FIELD(R_TIMER_CTRL, timerdiv1, 192) | + IO_FIELD(R_TIMER_CTRL, timerdiv0, 192) | + IO_STATE(R_TIMER_CTRL, i1, nop) | + IO_STATE(R_TIMER_CTRL, tm1, stop_ld) | + IO_STATE(R_TIMER_CTRL, clksel1, c19k2Hz) | + IO_STATE(R_TIMER_CTRL, i0, nop) | + IO_STATE(R_TIMER_CTRL, tm0, stop_ld) | + IO_STATE(R_TIMER_CTRL, clksel0, c19k2Hz); + + *R_TIMER_CTRL = r_timer_ctrl_shadow = + IO_FIELD(R_TIMER_CTRL, timerdiv1, 192) | + IO_FIELD(R_TIMER_CTRL, timerdiv0, 192) | + IO_STATE(R_TIMER_CTRL, i1, nop) | + IO_STATE(R_TIMER_CTRL, tm1, run) | + IO_STATE(R_TIMER_CTRL, clksel1, c19k2Hz) | + IO_STATE(R_TIMER_CTRL, i0, nop) | + IO_STATE(R_TIMER_CTRL, tm0, run) | + IO_STATE(R_TIMER_CTRL, clksel0, c19k2Hz); +#endif + + *R_IRQ_MASK0_SET = + IO_STATE(R_IRQ_MASK0_SET, timer0, set); /* unmask the timer irq */ + + /* now actually register the timer irq handler that calls timer_interrupt() */ + + setup_etrax_irq(2, &irq2); /* irq 2 is the timer0 irq in etrax */ + + /* enable watchdog if we should use one */ + +#if defined(CONFIG_ETRAX_WATCHDOG) && !defined(CONFIG_SVINTO_SIM) + printk("Enabling watchdog...\n"); + start_watchdog(); +#endif + +} diff --git a/arch/cris/kernel/traps.c b/arch/cris/kernel/traps.c new file mode 100644 index 000000000..9994487d4 --- /dev/null +++ b/arch/cris/kernel/traps.c @@ -0,0 +1,167 @@ +/* $Id: traps.c,v 1.3 2000/10/04 16:50:06 bjornw Exp $ + * + * linux/arch/cris/traps.c + * + * Etrax100 does not have any hardware traps, only IRQ's, which we setup + * in irq.c instead. Here we just define the die_if_kernel Oops'er. + * + * Copyright (C) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen + * + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/timer.h> +#include <linux/mm.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/io.h> +#include <asm/pgtable.h> + +int kstack_depth_to_print = 24; + +/* + * These constants are for searching for possible module text + * segments. MODULE_RANGE is a guess of how much space is likely + * to be vmalloced. + */ + +#define MODULE_RANGE (8*1024*1024) + +void show_stack(unsigned long *sp) +{ + unsigned long *stack, addr, module_start, module_end; + int i; + extern char _stext, _etext; + + // debugging aid: "show_stack(NULL);" prints the + // back trace for this cpu. + + if(sp == NULL) + sp = (unsigned long*)rdsp(); + + stack = sp; + + for(i = 0; i < kstack_depth_to_print; i++) { + if (((long) stack & (THREAD_SIZE-1)) == 0) + break; + if (i && ((i % 8) == 0)) + printk("\n "); + printk("%08lx ", *stack++); + } + + printk("\nCall Trace: "); + stack = sp; + i = 1; + module_start = VMALLOC_START; + module_end = VMALLOC_END; + while (((long) stack & (THREAD_SIZE-1)) != 0) { + addr = *stack++; + /* + * If the address is either in the text segment of the + * kernel, or in the region which contains vmalloc'ed + * memory, it *may* be the address of a calling + * routine; if so, print it so that someone tracing + * down the cause of the crash will be able to figure + * out the call path that was taken. + */ + if (((addr >= (unsigned long) &_stext) && + (addr <= (unsigned long) &_etext)) || + ((addr >= module_start) && (addr <= module_end))) { + if (i && ((i % 8) == 0)) + printk("\n "); + printk("[<%08lx>] ", addr); + i++; + } + } +} + +#if 0 +/* displays a short stack trace */ + +int show_stack() +{ + unsigned long *sp = (unsigned long *)rdusp(); + int i; + printk("Stack dump [0x%08lx]:\n", (unsigned long)sp); + for(i = 0; i < 16; i++) + printk("sp + %d: 0x%08lx\n", i*4, sp[i]); + return 0; +} +#endif + +void show_registers(struct pt_regs * regs) +{ + unsigned long usp = rdusp(); + + printk("IRP: %08lx SRP: %08lx CCR: %08lx USP: %08lx\n", + regs->irp, regs->srp, regs->dccr, usp ); + 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); + printk("Process %s (pid: %d, stackpage=%08lx)\n", + current->comm, current->pid, (unsigned long)current); + + // TODO, fix in_kernel detection + +#if 0 + /* + * When in-kernel, we also print out the stack and code at the + * time of the fault.. + */ + if (1) { + + printk("\nStack: "); + show_stack((unsigned long*)usp); + + printk("\nCode: "); + if(regs->irp < PAGE_OFFSET) + goto bad; + + for(i = 0; i < 20; i++) + { + unsigned char c; + if(__get_user(c, &((unsigned char*)regs->irp)[i])) { +bad: + printk(" Bad IP value."); + break; + } + printk("%02x ", c); + } + } + printk("\n"); +#endif +} + + + +void die_if_kernel(const char * str, struct pt_regs * regs, long err) +{ + if(user_mode(regs)) + return; + + printk("%s: %04lx\n", str, err & 0xffff); + + show_registers(regs); + show_stack(NULL); /* print backtrace for kernel stack on this CPU */ + + do_exit(SIGSEGV); +} + +void __init trap_init(void) +{ + + +} diff --git a/arch/cris/lib/Makefile b/arch/cris/lib/Makefile new file mode 100644 index 000000000..6ede712e3 --- /dev/null +++ b/arch/cris/lib/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for Etrax-specific library files.. +# + +.S.o: + $(CC) -D__ASSEMBLY__ $(AFLAGS) -traditional -c $< -o $*.o + +L_TARGET = lib.a +obj-y = checksum.o checksumcopy.o string.o usercopy.o memset.o + +include $(TOPDIR)/Rules.make diff --git a/arch/cris/lib/checksum.S b/arch/cris/lib/checksum.S new file mode 100644 index 000000000..4ee0daa0c --- /dev/null +++ b/arch/cris/lib/checksum.S @@ -0,0 +1,113 @@ + ;; $Id: checksum.S,v 1.1 2000/07/10 16:25:21 bjornw Exp $ + ;; A fast checksum routine using movem + ;; Copyright (c) 1998 Bjorn Wesen/Axis Communications AB + + ;; csum_partial(const unsigned char * buff, int len, unsigned int sum) + + .globl _csum_partial +_csum_partial: + + ;; check for breakeven length between movem and normal word looping versions + + cmpu.w 80,r11 + bcs no_movem + 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 + + subq 9*4,sp + movem r8,[sp] + + ;; do a movem checksum + + ;; r10 - src + ;; r11 - length + ;; r12 - checksum + + subq 10*4,r11 ; update length for the first loop + +mloop: movem [r10+],r9 ; read 10 longwords + + ;; perform dword checksumming on the 10 longwords + + add.d r0,r12 + ax + add.d r1,r12 + ax + add.d r2,r12 + ax + add.d r3,r12 + ax + add.d r4,r12 + ax + add.d r5,r12 + ax + add.d r6,r12 + ax + add.d r7,r12 + ax + add.d r8,r12 + ax + add.d r9,r12 + + ;; fold the carry into the checksum, to avoid having to loop the carry + ;; back into the top + + ax + addq 0,r12 + ax ; do it again, since we might have generated a carry + addq 0,r12 + + subq 10*4,r11 + bge mloop + nop + + addq 10*4,r11 ; compensate for last loop underflowing length + + ;; fold 32-bit checksum into a 16-bit checksum, to avoid carries below + + moveq -1,r1 ; put 0xffff in r1, faster than move.d 0xffff,r1 + lsrq 16,r1 + + move.d r12,r0 + lsrq 16,r0 ; r0 = checksum >> 16 + and.d r1,r12 ; checksum = checksum & 0xffff + add.d r0,r12 ; checksum += r0 + move.d r12,r0 ; do the same again, maybe we got a carry last add + lsrq 16,r0 + and.d r1,r12 + add.d r0,r12 + + movem [sp+],r8 ; restore regs + +no_movem: + 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 + +
\ No newline at end of file diff --git a/arch/cris/lib/checksumcopy.S b/arch/cris/lib/checksumcopy.S new file mode 100644 index 000000000..eae9c7ace --- /dev/null +++ b/arch/cris/lib/checksumcopy.S @@ -0,0 +1,120 @@ + ;; $Id: checksumcopy.S,v 1.2 2000/08/08 16:57:31 bjornw Exp $ + ;; A fast checksum+copy routine using movem + ;; Copyright (c) 1998, 2000 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: + + ;; check for breakeven length between movem and normal word looping versions + + cmpu.w 80,r12 + bcs no_movem + 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 + + subq 9*4,sp + movem r8,[sp] + + ;; do a movem copy and checksum + + ;; r10 - src + ;; r11 - dst + ;; r12 - length + ;; r13 - checksum + + subq 10*4,r12 ; update length for the first loop + +mloop: movem [r10+],r9 ; read 10 longwords + movem r9,[r11+] ; write 10 longwords + + ;; perform dword checksumming on the 10 longwords + + add.d r0,r13 + ax + add.d r1,r13 + ax + add.d r2,r13 + ax + add.d r3,r13 + ax + add.d r4,r13 + ax + add.d r5,r13 + ax + add.d r6,r13 + ax + add.d r7,r13 + ax + add.d r8,r13 + ax + add.d r9,r13 + + ;; fold the carry into the checksum, to avoid having to loop the carry + ;; back into the top + + ax + addq 0,r13 + + subq 10*4,r12 + bge mloop + nop + + addq 10*4,r12 ; compensate for last loop underflowing length + + ;; fold 32-bit checksum into a 16-bit checksum, to avoid carries below + + moveq -1,r1 ; put 0xffff in r1, faster than move.d 0xffff,r1 + lsrq 16,r1 + + move.d r13,r0 + lsrq 16,r0 ; r0 = checksum >> 16 + and.d r1,r13 ; checksum = checksum & 0xffff + add.d r0,r13 ; checksum += r0 + move.d r13,r0 ; do the same again, maybe we got a carry last add + lsrq 16,r0 + and.d r1,r13 + add.d r0,r13 + + movem [sp+],r8 ; restore regs + +no_movem: + cmpq 2,r12 + blt no_words + nop + + ;; copy and checksum the rest of the words + + subq 2,r12 + +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 + move.b [r10],r9 + addu.b r9,r13 + move.b r9,[r11] + ret + move.d r13, r10 + +
\ No newline at end of file diff --git a/arch/cris/lib/dmacopy.c b/arch/cris/lib/dmacopy.c new file mode 100644 index 000000000..318577a2d --- /dev/null +++ b/arch/cris/lib/dmacopy.c @@ -0,0 +1,43 @@ +/* $Id: dmacopy.c,v 1.1 2000/07/10 16:25:21 bjornw Exp $ + * + * memcpy for large blocks, using memory-memory DMA channels 6 and 7 in Etrax + */ + +#include <asm/svinto.h> +#include <asm/io.h> + +#define D(x) + +void *dma_memcpy(void *pdst, + const void *psrc, + unsigned int pn) +{ + static etrax_dma_descr indma, outdma; + + D(printk("dma_memcpy %d bytes... ", pn)); + +#if 0 + *R_GEN_CONFIG = genconfig_shadow = + (genconfig_shadow & ~0x3c0000) | + IO_STATE(R_GEN_CONFIG, dma6, intdma7) | + IO_STATE(R_GEN_CONFIG, dma7, intdma6); +#endif + indma.sw_len = outdma.sw_len = pn; + indma.ctrl = d_eol | d_eop; + outdma.ctrl = d_eol; + indma.buf = psrc; + outdma.buf = pdst; + + *R_DMA_CH6_FIRST = &indma; + *R_DMA_CH7_FIRST = &outdma; + *R_DMA_CH6_CMD = IO_STATE(R_DMA_CH6_CMD, cmd, start); + *R_DMA_CH7_CMD = IO_STATE(R_DMA_CH7_CMD, cmd, start); + + while(*R_DMA_CH7_CMD == 1) /* wait for completion */ ; + + D(printk("done\n")); + +} + + + diff --git a/arch/cris/lib/memset.c b/arch/cris/lib/memset.c new file mode 100644 index 000000000..2f9f3fe37 --- /dev/null +++ b/arch/cris/lib/memset.c @@ -0,0 +1,245 @@ +/*#************************************************************************#*/ +/*#-------------------------------------------------------------------------*/ +/*# */ +/*# 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. */ +/*# */ +/*#-------------------------------------------------------------------------*/ + +/* 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, + unsigned int 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\tlslq 8,r13\n\tmove.b %0,r13\n\tmove.d r13,%0\n\tlslq 16,r13\n\tor.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 (" + ;; Check that the following is true (same register names on + ;; both sides of equal sign, as in r8=r8): + ;; %0=r13, %1=r12, %4=r11 + ;; + ;; Save the registers we'll clobber in the movem process + ;; on the stack. Don't mention them to gcc, it will only be + ;; upset. + subq 11*4,sp + movem r10,[sp] + + move.d r11,r0 + move.d r11,r1 + move.d r11,r2 + move.d r11,r3 + move.d r11,r4 + move.d r11,r5 + move.d r11,r6 + move.d r11,r7 + move.d r11,r8 + move.d r11,r9 + move.d r11,r10 + + ;; Now we've got this: + ;; r13 - dst + ;; r12 - n + + ;; Update n for the first loop + subq 12*4,r12 +0: + subq 12*4,r12 + bge 0b + movem r11,[r13+] + + addq 12*4,r12 ;; compensate for last loop underflowing n + + ;; Restore registers from stack + 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/lib/old_checksum.c b/arch/cris/lib/old_checksum.c new file mode 100644 index 000000000..6035a48ae --- /dev/null +++ b/arch/cris/lib/old_checksum.c @@ -0,0 +1,127 @@ +/* $Id: old_checksum.c,v 1.1 2000/07/10 16:25:21 bjornw Exp $ + * + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * IP/TCP/UDP checksumming routines + * + * Authors: Jorge Cwik, <jorge@laser.satlink.net> + * Arnt Gulbrandsen, <agulbra@nvg.unit.no> + * Tom May, <ftom@netcom.com> + * Lots of code moved from tcp.c and ip.c; see those files + * for more names. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <net/checksum.h> + +#undef PROFILE_CHECKSUM + +#ifdef PROFILE_CHECKSUM +/* these are just for profiling the checksum code with an oscillioscope.. uh */ +#if 0 +#define BITOFF *((unsigned char *)0xb0000030) = 0xff +#define BITON *((unsigned char *)0xb0000030) = 0x0 +#endif +#include <asm/io.h> +#define CBITON LED_ACTIVE_SET(1) +#define CBITOFF LED_ACTIVE_SET(0) +#define BITOFF +#define BITON +#else +#define BITOFF +#define BITON +#define CBITOFF +#define CBITON +#endif + +/* + * computes a partial checksum, e.g. for TCP/UDP fragments + */ + +#include <asm/delay.h> + +unsigned int csum_partial(const unsigned char * buff, int len, unsigned int sum) +{ + /* + * Experiments with ethernet and slip connections show that buff + * is aligned on either a 2-byte or 4-byte boundary. + */ + const unsigned char *endMarker = buff + len; + const unsigned char *marker = endMarker - (len % 16); +#if 0 + if((int)buff & 0x3) + printk("unaligned buff %p\n", buff); + __delay(900); /* extra delay of 90 us to test performance hit */ +#endif + BITON; + while (buff < marker) { + sum += *((unsigned short *)buff)++; + sum += *((unsigned short *)buff)++; + sum += *((unsigned short *)buff)++; + sum += *((unsigned short *)buff)++; + sum += *((unsigned short *)buff)++; + sum += *((unsigned short *)buff)++; + sum += *((unsigned short *)buff)++; + sum += *((unsigned short *)buff)++; + } + marker = endMarker - (len % 2); + while(buff < marker) { + sum += *((unsigned short *)buff)++; + } + if(endMarker - buff > 0) { + sum += *buff; /* add extra byte seperately */ + } + BITOFF; + return(sum); +} + +#if 0 + +/* + * copy while checksumming, otherwise like csum_partial + */ + +unsigned int csum_partial_copy(const unsigned char *src, unsigned char *dst, + int len, unsigned int sum) +{ + const unsigned char *endMarker; + const unsigned char *marker; + printk("csum_partial_copy len %d.\n", len); +#if 0 + if((int)src & 0x3) + printk("unaligned src %p\n", src); + if((int)dst & 0x3) + printk("unaligned dst %p\n", dst); + __delay(1800); /* extra delay of 90 us to test performance hit */ +#endif + endMarker = src + len; + marker = endMarker - (len % 16); + CBITON; + while(src < marker) { + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + } + marker = endMarker - (len % 2); + while(src < marker) { + sum += (*((unsigned short *)dst)++ = *((unsigned short *)src)++); + } + if(endMarker - src > 0) { + sum += (*dst = *src); /* add extra byte seperately */ + } + CBITOFF; + return(sum); +} + +#endif diff --git a/arch/cris/lib/string.c b/arch/cris/lib/string.c new file mode 100644 index 000000000..6218cad56 --- /dev/null +++ b/arch/cris/lib/string.c @@ -0,0 +1,223 @@ +/*#************************************************************************#*/ +/*#-------------------------------------------------------------------------*/ +/*# */ +/*# 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. */ +/*# */ +/*#-------------------------------------------------------------------------*/ + +void *memcpy(void *pdst, + const void *psrc, + unsigned int 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. */ + if (n >= 44*2) /* Break even between movem and + move16 is at 38.7*2, but modulo 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. + + 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, r11=r11, r12=r12" */ + __asm__ volatile (" + ;; Check that the following is true (same register names on + ;; both sides of equal sign, as in r8=r8): + ;; %0=r13, %1=r11, %2=r12 + ;; + ;; Save the registers we'll use in the movem process + ;; on the stack. + subq 11*4,sp + movem r10,[sp] + + ;; Now we've got this: + ;; r11 - src + ;; r13 - dst + ;; r12 - n + + ;; Update n for the first loop + subq 44,r12 +0: + movem [r11+],r10 + subq 44,r12 + bge 0b + movem r10,[r13+] + + addq 44,r12 ;; compensate for last loop underflowing n + + ;; Restore registers from stack + 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/lib/usercopy.c b/arch/cris/lib/usercopy.c new file mode 100644 index 000000000..17eebf2ee --- /dev/null +++ b/arch/cris/lib/usercopy.c @@ -0,0 +1,501 @@ +/* + * User address space access functions. + * The non-inlined parts of asm-cris/uaccess.h are here. + * + * Copyright (C) 2000, 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 2.96 20000427 (experimental)". + + Check regularly... + + Note that the PC saved at a bus-fault is the address *after* the + faulting instruction, which means the branch-target for instructions in + delay-slots for taken branches. Note also that the postincrement in + the instruction is performed regardless of bus-fault; the register is + seen updated in fault handlers. + + Oh, and on the code formatting issue, to whomever feels like "fixing + it" to Conformity: I'm too "lazy", but why don't you go ahead and "fix" + string.c too. I just don't think too many people will hack this file + for the code format to be an issue. */ + + +/* Copy to userspace. This is based on the memcpy used for + kernel-to-kernel copying; see "string.c". */ + +unsigned long +__copy_user (void *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; + } + } + + /* Decide which copying method to use. */ + if (n >= 44*2) /* Break even between movem and + move16 is at 38.7*2, but modulo 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. + + 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, r11=r11, r12=r12". */ + __asm__ volatile (" + ;; Check that the following is true (same register names on + ;; both sides of equal sign, as in r8=r8): + ;; %0=r13, %1=r11, %2=r12 %3=r10 + ;; + ;; Save the registers we'll use in the movem process + ;; on the stack. + subq 11*4,sp + movem r10,[sp] + + ;; Now we've got this: + ;; r11 - src + ;; r13 - dst + ;; r12 - n + + ;; Update n for the first loop + subq 44,r12 + +; Since the noted PC of a faulting instruction in a delay-slot of a taken +; branch, is that of the branch target, we actually point at the from-movem +; for this case. There is no ambiguity here; if there was a fault in that +; instruction (meaning a kernel oops), the faulted PC would be the address +; after *that* movem. + +0: + movem [r11+],r10 + subq 44,r12 + bge 0b + movem r10,[r13+] +1: + addq 44,r12 ;; compensate for last loop underflowing n + + ;; Restore registers from stack + movem [sp+],r10 +2: + .section .fixup,\"ax\" + +; To provide a correct count in r10 of bytes that failed to be copied, +; we jump back into the loop if the loop-branch was taken. There is no +; performance penalty for sany use; the program will segfault soon enough. + +3: + move.d [sp],r10 + addq 44,r10 + move.d r10,[sp] + jump 0b +4: + movem [sp+],r10 + addq 44,r10 + addq 44,r12 + jump 2b + + .previous + .section __ex_table,\"a\" + .dword 0b,3b + .dword 1b,4b + .previous" + + /* Outputs */ : "=r" (dst), "=r" (src), "=r" (n), "=r" (retn) + /* Inputs */ : "0" (dst), "1" (src), "2" (n), "3" (retn)); + + } + + /* Either we directly start 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 and N. */ + + 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. */ + +unsigned long +__copy_user_zeroing (void *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_from_user_1 (dst, src, retn); + n--; + } + + if ((unsigned long) dst & 2) + { + __asm_copy_from_user_2 (dst, src, retn); + n -= 2; + } + } + + /* Decide which copying method to use. */ + if (n >= 44*2) /* Break even between movem and + move16 is at 38.7*2, but modulo 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. + + 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, r11=r11, r12=r12" */ + __asm__ volatile (" + ;; Check that the following is true (same register names on + ;; both sides of equal sign, as in r8=r8): + ;; %0=r13, %1=r11, %2=r12 %3=r10 + ;; + ;; Save the registers we'll use in the movem process + ;; on the stack. + subq 11*4,sp + movem r10,[sp] + + ;; Now we've got this: + ;; r11 - src + ;; r13 - dst + ;; r12 - n + + ;; Update n for the first loop + subq 44,r12 +0: + movem [r11+],r10 +1: + subq 44,r12 + bge 0b + movem r10,[r13+] + + addq 44,r12 ;; compensate for last loop underflowing n + + ;; Restore registers from stack + movem [sp+],r10 + + .section .fixup,\"ax\" + +; To provide a correct count in r10 of bytes that failed to be copied, +; we jump back into the loop if the loop-branch was taken. +; There is no performance penalty; the program will segfault soon +; enough. + +3: + move.d [sp],r10 + addq 44,r10 + move.d r10,[sp] + clear.d r0 + clear.d r1 + clear.d r2 + clear.d r3 + clear.d r4 + clear.d r5 + clear.d r6 + clear.d r7 + clear.d r8 + clear.d r9 + clear.d r10 + jump 1b + + .previous + .section __ex_table,\"a\" + .dword 1b,3b + .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. */ + + while (n >= 16) + { + __asm_copy_from_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_from_user_4 (dst, src, retn); + n -= 4; + } + + switch (n) + { + case 0: + 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; + } + + return retn; +} + +/* Zero userspace. */ + +unsigned long +__do_clear_user (void *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 >= (1*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 (" + ;; Check that the following is true (same register names on + ;; both sides of equal sign, as in r8=r8): + ;; %0=r13, %1=r12 %2=r10 + ;; + ;; Save the registers we'll clobber in the movem process + ;; on the stack. Don't mention them to gcc, it will only be + ;; upset. + subq 11*4,sp + movem r10,[sp] + + clear.d r0 + clear.d r1 + clear.d r2 + clear.d r3 + clear.d r4 + clear.d r5 + clear.d r6 + clear.d r7 + clear.d r8 + clear.d r9 + clear.d r10 + clear.d r11 + + ;; Now we've got this: + ;; r13 - dst + ;; r12 - n + + ;; Update n for the first loop + subq 12*4,r12 +0: + subq 12*4,r12 + bge 0b + movem r11,[r13+] +1: + addq 12*4,r12 ;; compensate for last loop underflowing n + + ;; Restore registers from stack + movem [sp+],r10 +2: + .section .fixup,\"ax\" +3: + move.d [sp],r10 + addq 12*4,r10 + move.d r10,[sp] + clear.d r10 + jump 0b + +4: + movem [sp+],r10 + addq 12*4,r10 + addq 12*4,r12 + jump 2b + + .previous + .section __ex_table,\"a\" + .dword 0b,3b + .dword 1b,4b + .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/mm/Makefile b/arch/cris/mm/Makefile new file mode 100644 index 000000000..d1d21a7b7 --- /dev/null +++ b/arch/cris/mm/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for the linux cris-specific parts of the memory manager. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definition is now in the main makefile... + +O_TARGET := mm.o +obj-y := init.o fault.o tlb.o extable.o + +include $(TOPDIR)/Rules.make diff --git a/arch/cris/mm/extable.c b/arch/cris/mm/extable.c new file mode 100644 index 000000000..a4cf00f14 --- /dev/null +++ b/arch/cris/mm/extable.c @@ -0,0 +1,55 @@ +/* + * linux/arch/cris/mm/extable.c + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/uaccess.h> + +extern const struct exception_table_entry _start___ex_table[]; +extern const struct exception_table_entry _stop___ex_table[]; + +static inline unsigned long +search_one_table(const struct exception_table_entry *first, + const struct exception_table_entry *last, + unsigned long value) +{ + while (first <= last) { + const struct exception_table_entry *mid; + long diff; + + mid = (last - first) / 2 + first; + diff = mid->insn - value; + if (diff == 0) + return mid->fixup; + else if (diff < 0) + first = mid+1; + else + last = mid-1; + } + return 0; +} + +unsigned long +search_exception_table(unsigned long addr) +{ + unsigned long ret; + +#ifndef CONFIG_MODULES + /* There is only the kernel to search. */ + ret = search_one_table(_start___ex_table, _stop___ex_table-1, addr); + if (ret) return ret; +#else + /* The kernel is the last "module" -- no need to treat it special. */ + struct module *mp; + for (mp = module_list; mp != NULL; mp = mp->next) { + if (mp->ex_table_start == NULL) + continue; + ret = search_one_table(mp->ex_table_start, + mp->ex_table_end - 1, addr); + if (ret) return ret; + } +#endif + + return 0; +} diff --git a/arch/cris/mm/fault.c b/arch/cris/mm/fault.c new file mode 100644 index 000000000..a4bb237b4 --- /dev/null +++ b/arch/cris/mm/fault.c @@ -0,0 +1,390 @@ +/* + * linux/arch/cris/mm/fault.c + * + * Copyright (C) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen + * + * $Log: fault.c,v $ + * Revision 1.8 2000/11/22 14:45:31 bjornw + * * 2.4.0-test10 removed the set_pgdir instantaneous kernel global mapping + * into all processes. Instead we fill in the missing PTE entries on demand. + * + * Revision 1.7 2000/11/21 16:39:09 bjornw + * fixup switches frametype + * + * Revision 1.6 2000/11/17 16:54:08 bjornw + * More detailed siginfo reporting + * + * + */ + +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/interrupt.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/pgtable.h> +#include <asm/uaccess.h> +#include <asm/svinto.h> + +extern void die_if_kernel(const char *,struct pt_regs *,long); + +asmlinkage void do_invalid_op (struct pt_regs *, unsigned long); +asmlinkage void do_page_fault(unsigned long address, struct pt_regs *regs, + int error_code); + +/* debug of low-level TLB reload */ +#define D(x) +/* debug of higher-level faults */ +#define DPG(x) + +/* fast TLB-fill fault handler */ + +void +handle_mmu_bus_fault(struct pt_regs *regs) +{ + int cause, select; + int index; + int page_id; + int miss, we, acc, inv; + struct mm_struct *mm = current->active_mm; + pmd_t *pmd; + pte_t pte; + int errcode = 0; + unsigned long address; + + cause = *R_MMU_CAUSE; + select = *R_TLB_SELECT; + + address = cause & PAGE_MASK; /* get faulting address */ + + page_id = IO_EXTRACT(R_MMU_CAUSE, page_id, cause); + miss = IO_EXTRACT(R_MMU_CAUSE, miss_excp, cause); + we = IO_EXTRACT(R_MMU_CAUSE, we_excp, cause); + acc = IO_EXTRACT(R_MMU_CAUSE, acc_excp, cause); + inv = IO_EXTRACT(R_MMU_CAUSE, inv_excp, cause); + index = IO_EXTRACT(R_TLB_SELECT, index, select); + + D(printk("bus_fault from IRP 0x%x: addr 0x%x, miss %d, inv %d, we %d, acc %d, " + "idx %d pid %d\n", + regs->irp, address, miss, inv, we, acc, index, page_id)); + + /* for a miss, we need to reload the TLB entry */ + + if(miss) { + + /* see if the pte exists at all */ + + pmd = (pmd_t *)pgd_offset(mm, address); + if(pmd_none(*pmd)) + goto dofault; + if(pmd_bad(*pmd)) { + printk("bad pgdir entry 0x%x at 0x%x\n", *pmd, pmd); + pmd_clear(pmd); + return; + } + pte = *pte_offset(pmd, address); + if(!pte_present(pte)) + goto dofault; + + D(printk(" found pte %x pg %x ", pte_val(pte), pte_page(pte))); + D( + { + if(pte_val(pte) & _PAGE_SILENT_WRITE) + printk("Silent-W "); + if(pte_val(pte) & _PAGE_KERNEL) + printk("Kernel "); + if(pte_val(pte) & _PAGE_SILENT_READ) + printk("Silent-R "); + if(pte_val(pte) & _PAGE_GLOBAL) + printk("Global "); + if(pte_val(pte) & _PAGE_PRESENT) + printk("Present "); + if(pte_val(pte) & _PAGE_ACCESSED) + printk("Accessed "); + if(pte_val(pte) & _PAGE_MODIFIED) + printk("Modified "); + if(pte_val(pte) & _PAGE_READ) + printk("Readable "); + if(pte_val(pte) & _PAGE_WRITE) + printk("Writeable "); + printk("\n"); + }); + + /* load up the chosen TLB entry + * this assumes the pte format is the same as the TLB_LO layout. + * + * the write to R_TLB_LO also writes the vpn and page_id fields from + * R_MMU_CAUSE, which we in this case obviously want to keep + */ + + *R_TLB_LO = pte_val(pte); + + return; + } + + errcode = 0x01 | (we << 1); + + dofault: + /* leave it to the MM system fault handler below */ + D(printk("do_page_fault %p errcode %d\n", address, errcode)); + do_page_fault(address, regs, errcode); +} + +/* + * This routine handles page faults. It determines the address, + * and the problem, and then passes it off to one of the appropriate + * routines. + * + * Notice that the address we're given is aligned to the page the fault + * occured in, since we only get the PFN in R_MMU_CAUSE not the complete + * address. + * + * error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * + * If this routine detects a bad access, it returns 1, otherwise it + * returns 0. + */ + +asmlinkage void +do_page_fault(unsigned long address, struct pt_regs *regs, + int error_code) +{ + struct task_struct *tsk; + struct mm_struct *mm; + struct vm_area_struct * vma; + int writeaccess; + int fault; + unsigned long fixup; + siginfo_t info; + + tsk = current; + + /* + * We fault-in kernel-space virtual memory on-demand. The + * 'reference' page table is init_mm.pgd. + * + * NOTE! We MUST NOT take any locks for this case. We may + * be in an interrupt or a critical region, and should + * only copy the information from the master page table, + * nothing more. + * + * NOTE2: This is done so that, when updating the vmalloc + * mappings we don't have to walk all processes pgdirs and + * add the high mappings all at once. Instead we do it as they + * are used. + * + * TODO: On CRIS, we have a PTE Global bit which should be set in + * all the PTE's related to vmalloc in all processes - that means if + * we switch process and a vmalloc PTE is still in the TLB, it won't + * need to be reloaded. It's an optimization. + * + * Linux/CRIS's kernel is not page-mapped, so the comparision below + * should really be >= VMALLOC_START, however, kernel fixup errors + * will be handled more quickly by going through vmalloc_fault and then + * into bad_area_nosemaphore than falling through the find_vma user-mode + * tests. + */ + + if (address >= TASK_SIZE) + goto vmalloc_fault; + + mm = tsk->mm; + writeaccess = error_code & 2; + info.si_code = SEGV_MAPERR; + + /* + * If we're in an interrupt or have no user + * context, we must not take the fault.. + */ + + if (in_interrupt() || !mm) + goto no_context; + + down(&mm->mmap_sem); + vma = find_vma(mm, address); + if (!vma) + goto bad_area; + if (vma->vm_start <= address) + goto good_area; + if (!(vma->vm_flags & VM_GROWSDOWN)) + goto bad_area; + if (user_mode(regs)) { + /* + * accessing the stack below usp is always a bug. + * we get page-aligned addresses so we can only check + * if we're within a page from usp, but that might be + * enough to catch brutal errors at least. + */ + if (address + PAGE_SIZE < rdusp()) + goto bad_area; + } + if (expand_stack(vma, address)) + goto bad_area; + + /* + * Ok, we have a good vm_area for this memory access, so + * we can handle it.. + */ + + good_area: + info.si_code = SEGV_ACCERR; + + /* first do some preliminary protection checks */ + + if (writeaccess) { + if (!(vma->vm_flags & VM_WRITE)) + goto bad_area; + } else { + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto bad_area; + } + + /* + * If for any reason at all we couldn't handle the fault, + * make sure we exit gracefully rather than endlessly redo + * the fault. + */ + + switch (handle_mm_fault(mm, vma, address, writeaccess)) { + case 1: + tsk->min_flt++; + break; + case 2: + tsk->maj_flt++; + break; + case 0: + goto do_sigbus; + default: + goto out_of_memory; + } + + up(&mm->mmap_sem); + return; + + /* + * Something tried to access memory that isn't in our memory map.. + * Fix it, but check if it's kernel or user first.. + */ + + bad_area: + + up(&mm->mmap_sem); + + bad_area_nosemaphore: + DPG(show_registers(regs)); + + /* User mode accesses just cause a SIGSEGV */ + + if (user_mode(regs)) { + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* info.si_code has been set above */ + info.si_addr = (void *)address; + force_sig_info(SIGSEGV, &info, tsk); + return; + } + + no_context: + + /* Are we prepared to handle this kernel fault? + * + * (The kernel has valid exception-points in the source + * when it acesses user-memory. When it fails in one + * of those points, we find it in a table and do a jump + * to some fixup code that loads an appropriate error + * code) + */ + + if ((fixup = search_exception_table(regs->irp)) != 0) { + regs->irp = fixup; + regs->frametype = CRIS_FRAME_FIXUP; + D(printk("doing fixup to 0x%x\n", fixup)); + return; + } + +/* + * Oops. The kernel tried to access some bad page. We'll have to + * terminate things with extreme prejudice. + */ + if ((unsigned long) (address) < PAGE_SIZE) + printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference"); + else + printk(KERN_ALERT "Unable to handle kernel access"); + printk(" at virtual address %08lx\n",address); + + die_if_kernel("Oops", regs, error_code); + + do_exit(SIGKILL); + + /* + * We ran out of memory, or some other thing happened to us that made + * us unable to handle the page fault gracefully. + */ + + out_of_memory: + up(&mm->mmap_sem); + printk("VM: killing process %s\n", tsk->comm); + if(user_mode(regs)) + do_exit(SIGKILL); + goto no_context; + + do_sigbus: + up(&mm->mmap_sem); + + /* + * Send a sigbus, regardless of whether we were in kernel + * or user mode. + */ + info.si_code = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRERR; + info.si_addr = (void *)address; + force_sig_info(SIGBUS, &info, tsk); + + /* Kernel mode? Handle exceptions or die */ + if (!user_mode(regs)) + goto no_context; + return; + +vmalloc_fault: + { + /* + * Synchronize this task's top level page-table + * with the 'reference' page table. + */ + int offset = pgd_index(address); + pgd_t *pgd, *pgd_k; + pmd_t *pmd, *pmd_k; + + pgd = tsk->active_mm->pgd + offset; + pgd_k = init_mm.pgd + offset; + + if (!pgd_present(*pgd)) { + if (!pgd_present(*pgd_k)) + goto bad_area_nosemaphore; + set_pgd(pgd, *pgd_k); + return; + } + + pmd = pmd_offset(pgd, address); + pmd_k = pmd_offset(pgd_k, address); + + if (pmd_present(*pmd) || !pmd_present(*pmd_k)) + goto bad_area_nosemaphore; + set_pmd(pmd, *pmd_k); + return; + } + +} diff --git a/arch/cris/mm/init.c b/arch/cris/mm/init.c new file mode 100644 index 000000000..3d0ceeffb --- /dev/null +++ b/arch/cris/mm/init.c @@ -0,0 +1,506 @@ +/* + * linux/arch/cris/mm/init.c + * + * Copyright (C) 1995 Linus Torvalds + * Copyright (C) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + * $Log: init.c,v $ + * Revision 1.15 2001/01/10 21:12:10 bjornw + * loops_per_sec -> loops_per_jiffy + * + * Revision 1.14 2000/11/22 16:23:20 bjornw + * Initialize totalhigh counters to 0 to make /proc/meminfo look nice. + * + * Revision 1.13 2000/11/21 16:37:51 bjornw + * Temporarily disable initmem freeing + * + * Revision 1.12 2000/11/21 13:55:07 bjornw + * Use CONFIG_CRIS_LOW_MAP for the low VM map instead of explicit CPU type + * + * Revision 1.11 2000/10/06 12:38:22 bjornw + * Cast empty_bad_page correctly (should really be of * type from the start.. + * + * Revision 1.10 2000/10/04 16:53:57 bjornw + * Fix memory-map due to LX features + * + * Revision 1.9 2000/09/13 15:47:49 bjornw + * Wrong count in reserved-pages loop + * + * Revision 1.8 2000/09/13 14:35:10 bjornw + * 2.4.0-test8 added a new arg to free_area_init_node + * + * Revision 1.7 2000/08/17 15:35:55 bjornw + * 2.4.0-test6 removed MAP_NR and inserted virt_to_page + * + * + */ + +#include <linux/config.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/smp.h> +#include <linux/bootmem.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> +#include <asm/dma.h> +#include <asm/svinto.h> + +static unsigned long totalram_pages; + +struct pgtable_cache_struct quicklists; /* see asm/pgalloc.h */ + +const char bad_pmd_string[] = "Bad pmd in pte_alloc: %08lx\n"; + +extern void die_if_kernel(char *,struct pt_regs *,long); +extern void show_net_buffers(void); +extern void tlb_init(void); + +/* + * empty_bad_page is the page that is used for page faults when linux + * is out-of-memory. Older versions of linux just did a + * do_exit(), but using this instead means there is less risk + * for a process dying in kernel mode, possibly leaving a inode + * unused etc.. + * + * the main point is that when a page table error occurs, we want to get + * out of the kernel safely before killing the process, so we need something + * to feed the MMU with when the fault occurs even if we don't have any + * real PTE's or page tables. + * + * empty_bad_page_table is the accompanying page-table: it is initialized + * to point to empty_bad_page writable-shared entries. + * + * empty_zero_page is a special page that is used for zero-initialized + * data and COW. + */ + +unsigned long empty_bad_page_table; +unsigned long empty_bad_page; +unsigned long empty_zero_page; + +pte_t * __bad_pagetable(void) +{ + /* somehow it is enough to just clear it and not fill it with + * bad page PTE's... + */ + memset((void *)empty_bad_page_table, 0, PAGE_SIZE); + + return (pte_t *) empty_bad_page_table; +} + +pte_t __bad_page(void) +{ + + /* clear the empty_bad_page page. this should perhaps be + * a more simple inlined loop like it is on the other + * architectures. + */ + + memset((void *)empty_bad_page, 0, PAGE_SIZE); + + return pte_mkdirty(__mk_pte((void *)empty_bad_page, PAGE_SHARED)); +} + +static pte_t * get_bad_pte_table(void) +{ + pte_t *empty_bad_pte_table = (pte_t *)empty_bad_page_table; + pte_t v; + int i; + + v = __bad_page(); + + for (i = 0; i < PAGE_SIZE/sizeof(pte_t); i++) + empty_bad_pte_table[i] = v; + + return empty_bad_pte_table; +} + +void __handle_bad_pmd(pmd_t *pmd) +{ + pmd_ERROR(*pmd); + pmd_set(pmd, get_bad_pte_table()); +} + +void __handle_bad_pmd_kernel(pmd_t *pmd) +{ + pmd_ERROR(*pmd); + pmd_set_kernel(pmd, get_bad_pte_table()); +} + +pte_t *get_pte_kernel_slow(pmd_t *pmd, unsigned long offset) +{ + pte_t *pte; + + pte = (pte_t *) __get_free_page(GFP_KERNEL); + if (pmd_none(*pmd)) { + if (pte) { + clear_page(pte); + pmd_set_kernel(pmd, pte); + return pte + offset; + } + pmd_set_kernel(pmd, get_bad_pte_table()); + return NULL; + } + free_page((unsigned long)pte); + if (pmd_bad(*pmd)) { + __handle_bad_pmd_kernel(pmd); + return NULL; + } + return (pte_t *) pmd_page(*pmd) + offset; +} + +pte_t *get_pte_slow(pmd_t *pmd, unsigned long offset) +{ + pte_t *pte; + + pte = (pte_t *) __get_free_page(GFP_KERNEL); + if (pmd_none(*pmd)) { + if (pte) { + clear_page(pte); + pmd_set(pmd, pte); + return pte + offset; + } + pmd_set(pmd, get_bad_pte_table()); + return NULL; + } + free_page((unsigned long)pte); + if (pmd_bad(*pmd)) { + __handle_bad_pmd(pmd); + return NULL; + } + return (pte_t *) pmd_page(*pmd) + offset; +} + +#ifndef CONFIG_NO_PGT_CACHE +struct pgtable_cache_struct quicklists; + +/* trim the page-table cache if necessary */ + +int do_check_pgt_cache(int low, int high) +{ + int freed = 0; + + if(pgtable_cache_size > high) { + do { + if(pgd_quicklist) { + free_pgd_slow(get_pgd_fast()); + freed++; + } + if(pmd_quicklist) { + free_pmd_slow(get_pmd_fast()); + freed++; + } + if(pte_quicklist) { + free_pte_slow(get_pte_fast()); + freed++; + } + } while(pgtable_cache_size > low); + } + return freed; +} +#else +int do_check_pgt_cache(int low, int high) +{ + return 0; +} +#endif + +void show_mem(void) +{ + int i,free = 0,total = 0,cached = 0, reserved = 0, nonshared = 0; + int shared = 0; + + printk("\nMem-info:\n"); + show_free_areas(); + printk("Free swap: %6dkB\n",nr_swap_pages<<(PAGE_SHIFT-10)); + i = max_mapnr; + while (i-- > 0) { + total++; + if (PageReserved(mem_map+i)) + reserved++; + else if (PageSwapCache(mem_map+i)) + cached++; + else if (!page_count(mem_map+i)) + free++; + else if (page_count(mem_map+i) == 1) + nonshared++; + else + shared += page_count(mem_map+i) - 1; + } + printk("%d pages of RAM\n",total); + printk("%d free pages\n",free); + printk("%d reserved pages\n",reserved); + printk("%d pages nonshared\n",nonshared); + printk("%d pages shared\n",shared); + printk("%d pages swap cached\n",cached); + printk("%ld pages in page table cache\n",pgtable_cache_size); + show_buffers(); +} + +/* + * The kernel is already mapped with a kernel segment at kseg_c so + * we don't need to map it with a page table. However head.S also + * temporarily mapped it at kseg_4 so we should set up the ksegs again, + * clear the TLB and do some other paging setup stuff. + */ + +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); + + /* initialise the TLB (tlb.c) */ + + tlb_init(); + + /* see README.mm for details on the KSEG setup */ + +#ifdef CONFIG_CRIS_LOW_MAP + + /* Etrax-100 LX version 1 has a bug so that we cannot map anything + * across the 0x80000000 boundary, so we need to shrink the user-virtual + * area to 0x50000000 instead of 0xb0000000 and map things slightly + * different. The unused areas are marked as paged so that we can catch + * freak kernel accesses there. + * + * The Juliette chip is mapped at 0xa so we pass that segment straight + * through. We cannot vremap it because the vmalloc area is below 0x8 + * and Juliette needs an uncached area above 0x8. + */ + + *R_MMU_KSEG = ( IO_STATE(R_MMU_KSEG, seg_f, page ) | + IO_STATE(R_MMU_KSEG, seg_e, seg ) | /* uncached flash */ + IO_STATE(R_MMU_KSEG, seg_d, page ) | + IO_STATE(R_MMU_KSEG, seg_c, page ) | + IO_STATE(R_MMU_KSEG, seg_b, seg ) | /* kernel reg area */ + IO_STATE(R_MMU_KSEG, seg_a, seg ) | /* Juliette etc. */ + IO_STATE(R_MMU_KSEG, seg_9, page ) | + IO_STATE(R_MMU_KSEG, seg_8, page ) | + IO_STATE(R_MMU_KSEG, seg_7, page ) | /* kernel vmalloc area */ + IO_STATE(R_MMU_KSEG, seg_6, seg ) | /* kernel DRAM area */ + IO_STATE(R_MMU_KSEG, seg_5, seg ) | /* cached flash */ + IO_STATE(R_MMU_KSEG, seg_4, page ) | /* user area */ + IO_STATE(R_MMU_KSEG, seg_3, page ) | /* user area */ + IO_STATE(R_MMU_KSEG, seg_2, page ) | /* user area */ + IO_STATE(R_MMU_KSEG, seg_1, page ) | /* user area */ + IO_STATE(R_MMU_KSEG, seg_0, page ) ); /* user area */ + + *R_MMU_KBASE_HI = ( IO_FIELD(R_MMU_KBASE_HI, base_f, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_e, 0x8 ) | + IO_FIELD(R_MMU_KBASE_HI, base_d, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_c, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_b, 0xb ) | + IO_FIELD(R_MMU_KBASE_HI, base_a, 0xa ) | + IO_FIELD(R_MMU_KBASE_HI, base_9, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_8, 0x0 ) ); + + *R_MMU_KBASE_LO = ( IO_FIELD(R_MMU_KBASE_LO, base_7, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_6, 0x4 ) | + IO_FIELD(R_MMU_KBASE_LO, base_5, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_4, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_3, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_2, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_1, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_0, 0x0 ) ); +#else + /* This code is for the hopefully corrected Etrax-100 LX version 2... */ + + *R_MMU_KSEG = ( IO_STATE(R_MMU_KSEG, seg_f, seg ) | /* cached flash */ + IO_STATE(R_MMU_KSEG, seg_e, seg ) | /* uncached flash */ + IO_STATE(R_MMU_KSEG, seg_d, page ) | /* vmalloc area */ + IO_STATE(R_MMU_KSEG, seg_c, seg ) | /* kernel area */ + IO_STATE(R_MMU_KSEG, seg_b, seg ) | /* kernel reg area */ + IO_STATE(R_MMU_KSEG, seg_a, page ) | /* user area */ + IO_STATE(R_MMU_KSEG, seg_9, page ) | + IO_STATE(R_MMU_KSEG, seg_8, page ) | + IO_STATE(R_MMU_KSEG, seg_7, page ) | + IO_STATE(R_MMU_KSEG, seg_6, page ) | + IO_STATE(R_MMU_KSEG, seg_5, page ) | + IO_STATE(R_MMU_KSEG, seg_4, page ) | + IO_STATE(R_MMU_KSEG, seg_3, page ) | + IO_STATE(R_MMU_KSEG, seg_2, page ) | + IO_STATE(R_MMU_KSEG, seg_1, page ) | + IO_STATE(R_MMU_KSEG, seg_0, page ) ); + + *R_MMU_KBASE_HI = ( IO_FIELD(R_MMU_KBASE_HI, base_f, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_e, 0x8 ) | + IO_FIELD(R_MMU_KBASE_HI, base_d, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_c, 0x4 ) | + IO_FIELD(R_MMU_KBASE_HI, base_b, 0xb ) | + IO_FIELD(R_MMU_KBASE_HI, base_a, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_9, 0x0 ) | + IO_FIELD(R_MMU_KBASE_HI, base_8, 0x0 ) ); + + *R_MMU_KBASE_LO = ( IO_FIELD(R_MMU_KBASE_LO, base_7, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_6, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_5, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_4, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_3, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_2, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_1, 0x0 ) | + IO_FIELD(R_MMU_KBASE_LO, base_0, 0x0 ) ); +#endif + + *R_MMU_CONTEXT = ( IO_FIELD(R_MMU_CONTEXT, page_id, 0 ) ); + + /* The MMU has been enabled ever since head.S but just to make + * it totally obvious we do it here as well. + */ + + *R_MMU_CTRL = ( IO_STATE(R_MMU_CTRL, inv_excp, enable ) | + IO_STATE(R_MMU_CTRL, acc_excp, enable ) | + IO_STATE(R_MMU_CTRL, we_excp, enable ) ); + + *R_MMU_ENABLE = IO_STATE(R_MMU_ENABLE, mmu_enable, enable); + + /* + * initialize the bad page table and bad page to point + * to a couple of allocated pages + */ + + empty_bad_page_table = (unsigned long)alloc_bootmem_pages(PAGE_SIZE); + empty_bad_page = (unsigned long)alloc_bootmem_pages(PAGE_SIZE); + 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 the former + * 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, 0, 0, zones_size, PAGE_OFFSET, 0); +} + +extern unsigned long loops_per_jiffy; /* init/main.c */ +unsigned long loops_per_usec; + +extern char _stext, _edata, _etext; +extern char __init_begin, __init_end; + +void __init +mem_init(void) +{ + int codesize, reservedpages, datasize, initsize; + unsigned long tmp; + + if(!mem_map) + BUG(); + + /* max/min_low_pfn was set by setup.c + * now we just copy it to some other necessary places... + * + * high_memory was also set in setup.c + */ + + max_mapnr = num_physpages = max_low_pfn - min_low_pfn; + + /* this will put all memory onto the freelists */ + totalram_pages = free_all_bootmem(); + + reservedpages = 0; + for (tmp = 0; tmp < max_mapnr; tmp++) { + /* + * Only count reserved RAM pages + */ + if (PageReserved(mem_map + tmp)) + reservedpages++; + } + + codesize = (unsigned long) &_etext - (unsigned long) &_stext; + datasize = (unsigned long) &_edata - (unsigned long) &_etext; + initsize = (unsigned long) &__init_end - (unsigned long) &__init_begin; + + printk("Memory: %luk/%luk available (%dk kernel code, %dk reserved, %dk data, " + "%dk init)\n" , + (unsigned long) nr_free_pages() << (PAGE_SHIFT-10), + max_mapnr << (PAGE_SHIFT-10), + codesize >> 10, + reservedpages << (PAGE_SHIFT-10), + datasize >> 10, + initsize >> 10 + ); + + /* HACK alert - calculate a loops_per_usec for asm/delay.h here + * since this is called just after calibrate_delay in init/main.c + * but before places which use udelay. cannot be in time.c since + * that is called _before_ calibrate_delay + */ + + loops_per_usec = (loops_per_jiffy * HZ) / 1000000; + + return; +} + +/* free the pages occupied by initialization code */ + +void free_initmem(void) +{ +#if 0 + /* currently this is a bad idea since the cramfs image is catted onto + * the vmlinux image, and the end of that image is not page-padded so + * part of the cramfs image will be freed here + */ + unsigned long addr; + + addr = (unsigned long)(&__init_begin); + for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) { + ClearPageReserved(virt_to_page(addr)); + set_page_count(virt_to_page(addr), 1); + free_page(addr); + totalram_pages++; + } + printk ("Freeing unused kernel memory: %dk freed\n", + (&__init_end - &__init_begin) >> 10); +#endif +} + +void si_meminfo(struct sysinfo *val) +{ + int i; + + i = max_mapnr; + val->totalram = 0; + val->sharedram = 0; + val->freeram = nr_free_pages(); + val->bufferram = atomic_read(&buffermem_pages); + while (i-- > 0) { + if (PageReserved(mem_map+i)) + continue; + val->totalram++; + if (!atomic_read(&mem_map[i].count)) + continue; + val->sharedram += atomic_read(&mem_map[i].count) - 1; + } + val->mem_unit = PAGE_SIZE; + val->totalhigh = 0; + val->freehigh = 0; +} diff --git a/arch/cris/mm/tlb.c b/arch/cris/mm/tlb.c new file mode 100644 index 000000000..ed74305ae --- /dev/null +++ b/arch/cris/mm/tlb.c @@ -0,0 +1,301 @@ +/* + * linux/arch/cris/mm/tlb.c + * + * Copyright (C) 2000 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + */ + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/pgtable.h> +#include <asm/svinto.h> + +#define D(x) + +/* CRIS in Etrax100LX TLB */ + +#define NUM_TLB_ENTRIES 64 +#define NUM_PAGEID 64 +#define INVALID_PAGEID 63 +#define NO_CONTEXT -1 + +/* The TLB can host up to 64 different mm contexts at the same time. + * The running context is R_MMU_CONTEXT, and each TLB entry contains a + * page_id that has to match to give a hit. In page_id_map, we keep track + * of which mm's we have assigned which page_id's, so that we know when + * to invalidate TLB entries. + * + * The last page_id is never running - it is used as an invalid page_id + * so we can make TLB entries that will never match. + */ + +struct mm_struct *page_id_map[NUM_PAGEID]; + +static int map_replace_ptr = 1; /* which page_id_map entry to replace next */ + +/* invalidate all TLB entries */ + +void +flush_tlb_all() +{ + int i; + + /* the vpn of i & 0xf is so we dont write similar TLB entries + * in the same 4-way entry group. details.. + */ + + for(i = 0; i < NUM_TLB_ENTRIES; i++) { + *R_TLB_SELECT = ( IO_FIELD(R_TLB_SELECT, index, i) ); + + *R_TLB_HI = ( IO_FIELD(R_TLB_HI, page_id, INVALID_PAGEID ) | + IO_FIELD(R_TLB_HI, vpn, i & 0xf ) ); + + *R_TLB_LO = ( IO_STATE(R_TLB_LO, global,no ) | + IO_STATE(R_TLB_LO, valid, no ) | + IO_STATE(R_TLB_LO, kernel,no ) | + IO_STATE(R_TLB_LO, we, no ) | + IO_FIELD(R_TLB_LO, pfn, 0 ) ); + } + D(printk("tlb: flushed all\n")); +} + +/* invalidate the selected mm context only */ + +void +flush_tlb_mm(struct mm_struct *mm) +{ + int i; + int page_id = mm->context; + + D(printk("tlb: flush mm context %d (%p)\n", page_id, mm)); + + if(page_id == NO_CONTEXT) + return; + + /* mark the TLB entries that match the page_id as invalid. + * here we could also check the _PAGE_GLOBAL bit and NOT flush + * global pages. is it worth the extra I/O ? + */ + + for(i = 0; i < NUM_TLB_ENTRIES; i++) { + *R_TLB_SELECT = IO_FIELD(R_TLB_SELECT, index, i); + if (IO_EXTRACT(R_TLB_HI, page_id, *R_TLB_HI) == page_id) { + *R_TLB_HI = ( IO_FIELD(R_TLB_HI, page_id, INVALID_PAGEID ) | + IO_FIELD(R_TLB_HI, vpn, i & 0xf ) ); + + *R_TLB_LO = ( IO_STATE(R_TLB_LO, global,no ) | + IO_STATE(R_TLB_LO, valid, no ) | + IO_STATE(R_TLB_LO, kernel,no ) | + IO_STATE(R_TLB_LO, we, no ) | + IO_FIELD(R_TLB_LO, pfn, 0 ) ); + } + } +} + +/* invalidate a single page */ + +void +flush_tlb_page(struct vm_area_struct *vma, + unsigned long addr) +{ + struct mm_struct *mm = vma->vm_mm; + int page_id = mm->context; + int i; + + D(printk("tlb: flush page %p in context %d (%p)\n", addr, page_id, mm)); + + if(page_id == NO_CONTEXT) + return; + + addr &= PAGE_MASK; /* perhaps not necessary */ + + /* invalidate those TLB entries that match both the mm context + * and the virtual address requested + */ + + for(i = 0; i < NUM_TLB_ENTRIES; i++) { + unsigned long tlb_hi; + *R_TLB_SELECT = IO_FIELD(R_TLB_SELECT, index, i); + tlb_hi = *R_TLB_HI; + if (IO_EXTRACT(R_TLB_HI, page_id, tlb_hi) == page_id && + (tlb_hi & PAGE_MASK) == addr) { + *R_TLB_HI = IO_FIELD(R_TLB_HI, page_id, INVALID_PAGEID ) | + addr; /* same addr as before works. */ + + *R_TLB_LO = ( IO_STATE(R_TLB_LO, global,no ) | + IO_STATE(R_TLB_LO, valid, no ) | + IO_STATE(R_TLB_LO, kernel,no ) | + IO_STATE(R_TLB_LO, we, no ) | + IO_FIELD(R_TLB_LO, pfn, 0 ) ); + } + } +} + +/* invalidate a page range */ + +void +flush_tlb_range(struct mm_struct *mm, + unsigned long start, + unsigned long end) +{ + int page_id = mm->context; + int i; + + D(printk("tlb: flush range %p<->%p in context %d (%p)\n", + start, end, page_id, mm)); + + if(page_id == NO_CONTEXT) + return; + + start &= PAGE_MASK; /* probably not necessary */ + end &= PAGE_MASK; /* dito */ + + /* invalidate those TLB entries that match both the mm context + * and the virtual address range + */ + + for(i = 0; i < NUM_TLB_ENTRIES; i++) { + unsigned long tlb_hi, vpn; + *R_TLB_SELECT = IO_FIELD(R_TLB_SELECT, index, i); + tlb_hi = *R_TLB_HI; + vpn = tlb_hi & PAGE_MASK; + if (IO_EXTRACT(R_TLB_HI, page_id, tlb_hi) == page_id && + vpn >= start && vpn < end) { + *R_TLB_HI = ( IO_FIELD(R_TLB_HI, page_id, INVALID_PAGEID ) | + IO_FIELD(R_TLB_HI, vpn, i & 0xf ) ); + + *R_TLB_LO = ( IO_STATE(R_TLB_LO, global,no ) | + IO_STATE(R_TLB_LO, valid, no ) | + IO_STATE(R_TLB_LO, kernel,no ) | + IO_STATE(R_TLB_LO, we, no ) | + IO_FIELD(R_TLB_LO, pfn, 0 ) ); + } + } +} + +/* + * 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 = NO_CONTEXT; + return 0; +} + +/* the following functions are similar to those used in the PPC port */ + +static inline void +alloc_context(struct mm_struct *mm) +{ + struct mm_struct *old_mm; + + D(printk("tlb: alloc context %d (%p)\n", map_replace_ptr, mm)); + + /* did we replace an mm ? */ + + old_mm = page_id_map[map_replace_ptr]; + + if(old_mm) { + /* throw out any TLB entries belonging to the mm we replace + * in the map + */ + flush_tlb_mm(old_mm); + + old_mm->context = NO_CONTEXT; + } + + /* insert it into the page_id_map */ + + mm->context = map_replace_ptr; + page_id_map[map_replace_ptr] = mm; + + map_replace_ptr++; + + if(map_replace_ptr == INVALID_PAGEID) + map_replace_ptr = 0; /* wrap around */ + +} + +/* + * if needed, get a new MMU context for the mm. otherwise nothing is done. + */ + +void +get_mmu_context(struct mm_struct *mm) +{ + if(mm->context == NO_CONTEXT) + alloc_context(mm); +} + +/* 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) +{ + /* make sure we have a context */ + + get_mmu_context(next); + + /* switch context in the MMU */ + + D(printk("switching mmu_context to %d (%p)\n", next->context, next)); + + *R_MMU_CONTEXT = IO_FIELD(R_MMU_CONTEXT, page_id, next->context); +} + + +/* called by __exit_mm to destroy the used MMU context if any before + * destroying the mm itself. this is only called when the last user of the mm + * drops it. + * + * the only thing we really need to do here is mark the used PID slot + * as empty. + */ + +void +destroy_context(struct mm_struct *mm) +{ + if(mm->context != NO_CONTEXT) { + D(printk("destroy_context %d (%p)\n", mm->context, mm)); + flush_tlb_mm(mm); /* TODO this might be redundant ? */ + page_id_map[mm->context] = NULL; + /* mm->context = NO_CONTEXT; redundant.. mm will be freed */ + } +} + +/* called once during VM initialization, from init.c */ + +void __init +tlb_init(void) +{ + int i; + + /* clear the page_id map */ + + for(i = 0; i < 64; i++) + page_id_map[i] = NULL; + + /* invalidate the entire TLB */ + + flush_tlb_all(); + + /* the init_mm has context 0 from the boot */ + + page_id_map[0] = &init_mm; +} |