diff options
Diffstat (limited to 'arch/arm26/kernel')
-rw-r--r-- | arch/arm26/kernel/Makefile | 17 | ||||
-rw-r--r-- | arch/arm26/kernel/armksyms.c | 220 | ||||
-rw-r--r-- | arch/arm26/kernel/asm-offsets.c | 63 | ||||
-rw-r--r-- | arch/arm26/kernel/calls.S | 265 | ||||
-rw-r--r-- | arch/arm26/kernel/compat.c | 174 | ||||
-rw-r--r-- | arch/arm26/kernel/dma.c | 273 | ||||
-rw-r--r-- | arch/arm26/kernel/ecard.c | 850 | ||||
-rw-r--r-- | arch/arm26/kernel/entry.S | 961 | ||||
-rw-r--r-- | arch/arm26/kernel/fiq.c | 202 | ||||
-rw-r--r-- | arch/arm26/kernel/head.S | 113 | ||||
-rw-r--r-- | arch/arm26/kernel/init_task.c | 49 | ||||
-rw-r--r-- | arch/arm26/kernel/irq.c | 716 | ||||
-rw-r--r-- | arch/arm26/kernel/process.c | 401 | ||||
-rw-r--r-- | arch/arm26/kernel/ptrace.c | 744 | ||||
-rw-r--r-- | arch/arm26/kernel/ptrace.h | 13 | ||||
-rw-r--r-- | arch/arm26/kernel/semaphore.c | 223 | ||||
-rw-r--r-- | arch/arm26/kernel/setup.c | 573 | ||||
-rw-r--r-- | arch/arm26/kernel/signal.c | 540 | ||||
-rw-r--r-- | arch/arm26/kernel/sys_arm.c | 324 | ||||
-rw-r--r-- | arch/arm26/kernel/time.c | 234 | ||||
-rw-r--r-- | arch/arm26/kernel/traps.c | 548 | ||||
-rw-r--r-- | arch/arm26/kernel/vmlinux-arm26-xip.lds.in | 134 | ||||
-rw-r--r-- | arch/arm26/kernel/vmlinux-arm26.lds.in | 127 | ||||
-rw-r--r-- | arch/arm26/kernel/vmlinux.lds.S | 12 |
24 files changed, 7776 insertions, 0 deletions
diff --git a/arch/arm26/kernel/Makefile b/arch/arm26/kernel/Makefile new file mode 100644 index 00000000000..ee9fb49fdb7 --- /dev/null +++ b/arch/arm26/kernel/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for the linux kernel. +# + +# Object file lists. + +AFLAGS_head.o := -DTEXTADDR=$(TEXTADDR) + +obj-y := compat.o dma.o entry.o irq.o process.o ptrace.o \ + semaphore.o setup.o signal.o sys_arm.o time.o traps.o \ + ecard.o dma.o ecard.o fiq.o time.o + +extra-y := head.o init_task.o vmlinux.lds + +obj-$(CONFIG_FIQ) += fiq.o +obj-$(CONFIG_MODULES) += armksyms.o + diff --git a/arch/arm26/kernel/armksyms.c b/arch/arm26/kernel/armksyms.c new file mode 100644 index 00000000000..35514b398e2 --- /dev/null +++ b/arch/arm26/kernel/armksyms.c @@ -0,0 +1,220 @@ +/* + * linux/arch/arm26/kernel/armksyms.c + * + * Copyright (C) 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/config.h> +#include <linux/module.h> +#include <linux/user.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/delay.h> +#include <linux/in6.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/tty.h> +#include <linux/vt_kern.h> +#include <linux/smp_lock.h> +#include <linux/syscalls.h> + +#include <asm/byteorder.h> +#include <asm/elf.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/processor.h> +#include <asm/semaphore.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/checksum.h> +#include <asm/mach-types.h> + +extern void dump_thread(struct pt_regs *, struct user *); +extern int dump_fpu(struct pt_regs *, struct user_fp_struct *); +extern void inswb(unsigned int port, void *to, int len); +extern void outswb(unsigned int port, const void *to, int len); + +extern void __bad_xchg(volatile void *ptr, int size); + +/* + * libgcc functions - functions that are used internally by the + * compiler... (prototypes are not correct though, but that + * doesn't really matter since they're not versioned). + */ +extern void __ashldi3(void); +extern void __ashrdi3(void); +extern void __divsi3(void); +extern void __lshrdi3(void); +extern void __modsi3(void); +extern void __muldi3(void); +extern void __ucmpdi2(void); +extern void __udivdi3(void); +extern void __umoddi3(void); +extern void __udivmoddi4(void); +extern void __udivsi3(void); +extern void __umodsi3(void); +extern void abort(void); + +extern void ret_from_exception(void); +extern void fpundefinstr(void); +extern void fp_enter(void); + +/* + * This has a special calling convention; it doesn't + * modify any of the usual registers, except for LR. + * FIXME - we used to use our own local version - looks to be in kernel/softirq now + */ +//extern void __do_softirq(void); + +#define EXPORT_SYMBOL_ALIAS(sym,orig) \ + const char __kstrtab_##sym[] \ + __attribute__((section(".kstrtab"))) = \ + __MODULE_STRING(sym); \ + const struct module_symbol __ksymtab_##sym \ + __attribute__((section("__ksymtab"))) = \ + { (unsigned long)&orig, __kstrtab_##sym }; + +/* + * floating point math emulator support. + * These symbols will never change their calling convention... + */ +EXPORT_SYMBOL_ALIAS(kern_fp_enter,fp_enter); +EXPORT_SYMBOL_ALIAS(fp_printk,printk); +EXPORT_SYMBOL_ALIAS(fp_send_sig,send_sig); + +EXPORT_SYMBOL(fpundefinstr); +EXPORT_SYMBOL(ret_from_exception); + +#ifdef CONFIG_VT +EXPORT_SYMBOL(kd_mksound); +#endif + +//EXPORT_SYMBOL(__do_softirq); + + /* platform dependent support */ +EXPORT_SYMBOL(dump_thread); +EXPORT_SYMBOL(dump_fpu); +EXPORT_SYMBOL(udelay); +EXPORT_SYMBOL(kernel_thread); +EXPORT_SYMBOL(system_rev); +EXPORT_SYMBOL(system_serial_low); +EXPORT_SYMBOL(system_serial_high); +#ifdef CONFIG_DEBUG_BUGVERBOSE +EXPORT_SYMBOL(__bug); +#endif +EXPORT_SYMBOL(__bad_xchg); +EXPORT_SYMBOL(__readwrite_bug); +EXPORT_SYMBOL(enable_irq); +EXPORT_SYMBOL(disable_irq); +EXPORT_SYMBOL(set_irq_type); +EXPORT_SYMBOL(pm_idle); +EXPORT_SYMBOL(pm_power_off); + + /* processor dependencies */ +EXPORT_SYMBOL(__machine_arch_type); + + /* networking */ +EXPORT_SYMBOL(csum_partial_copy_nocheck); +EXPORT_SYMBOL(__csum_ipv6_magic); + + /* io */ +#ifndef __raw_readsb +EXPORT_SYMBOL(__raw_readsb); +#endif +#ifndef __raw_readsw +EXPORT_SYMBOL(__raw_readsw); +#endif +#ifndef __raw_readsl +EXPORT_SYMBOL(__raw_readsl); +#endif +#ifndef __raw_writesb +EXPORT_SYMBOL(__raw_writesb); +#endif +#ifndef __raw_writesw +EXPORT_SYMBOL(__raw_writesw); +#endif +#ifndef __raw_writesl +EXPORT_SYMBOL(__raw_writesl); +#endif + + /* string / mem functions */ +EXPORT_SYMBOL(strcpy); +EXPORT_SYMBOL(strncpy); +EXPORT_SYMBOL(strcat); +EXPORT_SYMBOL(strncat); +EXPORT_SYMBOL(strcmp); +EXPORT_SYMBOL(strncmp); +EXPORT_SYMBOL(strchr); +EXPORT_SYMBOL(strlen); +EXPORT_SYMBOL(strnlen); +EXPORT_SYMBOL(strpbrk); +EXPORT_SYMBOL(strrchr); +EXPORT_SYMBOL(strstr); +EXPORT_SYMBOL(memset); +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memmove); +EXPORT_SYMBOL(memcmp); +EXPORT_SYMBOL(memscan); +EXPORT_SYMBOL(__memzero); + + /* user mem (segment) */ +EXPORT_SYMBOL(uaccess_kernel); +EXPORT_SYMBOL(uaccess_user); + +EXPORT_SYMBOL(__get_user_1); +EXPORT_SYMBOL(__get_user_2); +EXPORT_SYMBOL(__get_user_4); +EXPORT_SYMBOL(__get_user_8); + +EXPORT_SYMBOL(__put_user_1); +EXPORT_SYMBOL(__put_user_2); +EXPORT_SYMBOL(__put_user_4); +EXPORT_SYMBOL(__put_user_8); + + /* gcc lib functions */ +EXPORT_SYMBOL(__ashldi3); +EXPORT_SYMBOL(__ashrdi3); +EXPORT_SYMBOL(__divsi3); +EXPORT_SYMBOL(__lshrdi3); +EXPORT_SYMBOL(__modsi3); +EXPORT_SYMBOL(__muldi3); +EXPORT_SYMBOL(__ucmpdi2); +EXPORT_SYMBOL(__udivdi3); +EXPORT_SYMBOL(__umoddi3); +EXPORT_SYMBOL(__udivmoddi4); +EXPORT_SYMBOL(__udivsi3); +EXPORT_SYMBOL(__umodsi3); + + /* bitops */ +EXPORT_SYMBOL(_set_bit_le); +EXPORT_SYMBOL(_test_and_set_bit_le); +EXPORT_SYMBOL(_clear_bit_le); +EXPORT_SYMBOL(_test_and_clear_bit_le); +EXPORT_SYMBOL(_change_bit_le); +EXPORT_SYMBOL(_test_and_change_bit_le); +EXPORT_SYMBOL(_find_first_zero_bit_le); +EXPORT_SYMBOL(_find_next_zero_bit_le); + + /* elf */ +EXPORT_SYMBOL(elf_platform); +EXPORT_SYMBOL(elf_hwcap); + + /* syscalls */ +EXPORT_SYMBOL(sys_write); +EXPORT_SYMBOL(sys_read); +EXPORT_SYMBOL(sys_lseek); +EXPORT_SYMBOL(sys_open); +EXPORT_SYMBOL(sys_exit); +EXPORT_SYMBOL(sys_wait4); + +EXPORT_SYMBOL(get_wchan); + +#ifdef CONFIG_PREEMPT +EXPORT_SYMBOL(kernel_flag); +#endif diff --git a/arch/arm26/kernel/asm-offsets.c b/arch/arm26/kernel/asm-offsets.c new file mode 100644 index 00000000000..4ccacaef94d --- /dev/null +++ b/arch/arm26/kernel/asm-offsets.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 1995-2001 Russell King + * 2001-2002 Keith Owens + * 2003 Ian Molton + * + * Generate definitions needed by assembly language modules. + * This code generates raw asm output which is post-processed to extract + * and format the required data. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/mm.h> + +#include <asm/pgtable.h> +#include <asm/uaccess.h> + +/* + * Make sure that the compiler and target are compatible. + */ +#if defined(__APCS_32__) && defined(CONFIG_CPU_26) +#error Sorry, your compiler targets APCS-32 but this kernel requires APCS-26 +#endif +#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 95) +#error Sorry, your compiler is known to miscompile kernels. Only use gcc 2.95.3 and later. +#endif +#if __GNUC__ == 2 && __GNUC_MINOR__ == 95 +/* shame we can't detect the .1 or .2 releases */ +#warning GCC 2.95.2 and earlier miscompiles kernels. +#endif + +/* Use marker if you need to separate the values later */ + +#define DEFINE(sym, val) \ + asm volatile("\n->" #sym " %0 " #val : : "i" (val)) + +#define BLANK() asm volatile("\n->" : : ) + +int main(void) +{ + DEFINE(TSK_ACTIVE_MM, offsetof(struct task_struct, active_mm)); + BLANK(); + DEFINE(VMA_VM_MM, offsetof(struct vm_area_struct, vm_mm)); + DEFINE(VMA_VM_FLAGS, offsetof(struct vm_area_struct, vm_flags)); + BLANK(); + DEFINE(VM_EXEC, VM_EXEC); + BLANK(); + BLANK(); + DEFINE(PAGE_PRESENT, _PAGE_PRESENT); + DEFINE(PAGE_READONLY, _PAGE_READONLY); + DEFINE(PAGE_NOT_USER, _PAGE_NOT_USER); + DEFINE(PAGE_OLD, _PAGE_OLD); + DEFINE(PAGE_CLEAN, _PAGE_CLEAN); + BLANK(); + DEFINE(PAGE_SZ, PAGE_SIZE); + BLANK(); + DEFINE(SYS_ERROR0, 0x9f0000); + return 0; +} diff --git a/arch/arm26/kernel/calls.S b/arch/arm26/kernel/calls.S new file mode 100644 index 00000000000..e3d276827c8 --- /dev/null +++ b/arch/arm26/kernel/calls.S @@ -0,0 +1,265 @@ +/* + * linux/arch/arm26/kernel/calls.S + * + * Copyright (C) 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * FIXME + * This file is included twice in entry.S which may not be necessary + */ + +//FIXME - clearly NR_syscalls is never defined here + +#ifndef NR_syscalls +#define NR_syscalls 256 +#else + +__syscall_start: +/* 0 */ .long sys_ni_syscall + .long sys_exit + .long sys_fork_wrapper + .long sys_read + .long sys_write +/* 5 */ .long sys_open + .long sys_close + .long sys_ni_syscall /* was sys_waitpid */ + .long sys_creat + .long sys_link +/* 10 */ .long sys_unlink + .long sys_execve_wrapper + .long sys_chdir + .long sys_time /* used by libc4 */ + .long sys_mknod +/* 15 */ .long sys_chmod + .long sys_lchown16 + .long sys_ni_syscall /* was sys_break */ + .long sys_ni_syscall /* was sys_stat */ + .long sys_lseek +/* 20 */ .long sys_getpid + .long sys_mount + .long sys_oldumount /* used by libc4 */ + .long sys_setuid16 + .long sys_getuid16 +/* 25 */ .long sys_stime + .long sys_ptrace + .long sys_alarm /* used by libc4 */ + .long sys_ni_syscall /* was sys_fstat */ + .long sys_pause +/* 30 */ .long sys_utime /* used by libc4 */ + .long sys_ni_syscall /* was sys_stty */ + .long sys_ni_syscall /* was sys_getty */ + .long sys_access + .long sys_nice +/* 35 */ .long sys_ni_syscall /* was sys_ftime */ + .long sys_sync + .long sys_kill + .long sys_rename + .long sys_mkdir +/* 40 */ .long sys_rmdir + .long sys_dup + .long sys_pipe + .long sys_times + .long sys_ni_syscall /* was sys_prof */ +/* 45 */ .long sys_brk + .long sys_setgid16 + .long sys_getgid16 + .long sys_ni_syscall /* was sys_signal */ + .long sys_geteuid16 +/* 50 */ .long sys_getegid16 + .long sys_acct + .long sys_umount + .long sys_ni_syscall /* was sys_lock */ + .long sys_ioctl +/* 55 */ .long sys_fcntl + .long sys_ni_syscall /* was sys_mpx */ + .long sys_setpgid + .long sys_ni_syscall /* was sys_ulimit */ + .long sys_ni_syscall /* was sys_olduname */ +/* 60 */ .long sys_umask + .long sys_chroot + .long sys_ustat + .long sys_dup2 + .long sys_getppid +/* 65 */ .long sys_getpgrp + .long sys_setsid + .long sys_sigaction + .long sys_ni_syscall /* was sys_sgetmask */ + .long sys_ni_syscall /* was sys_ssetmask */ +/* 70 */ .long sys_setreuid16 + .long sys_setregid16 + .long sys_sigsuspend_wrapper + .long sys_sigpending + .long sys_sethostname +/* 75 */ .long sys_setrlimit + .long sys_old_getrlimit /* used by libc4 */ + .long sys_getrusage + .long sys_gettimeofday + .long sys_settimeofday +/* 80 */ .long sys_getgroups16 + .long sys_setgroups16 + .long old_select /* used by libc4 */ + .long sys_symlink + .long sys_ni_syscall /* was sys_lstat */ +/* 85 */ .long sys_readlink + .long sys_uselib + .long sys_swapon + .long sys_reboot + .long old_readdir /* used by libc4 */ +/* 90 */ .long old_mmap /* used by libc4 */ + .long sys_munmap + .long sys_truncate + .long sys_ftruncate + .long sys_fchmod +/* 95 */ .long sys_fchown16 + .long sys_getpriority + .long sys_setpriority + .long sys_ni_syscall /* was sys_profil */ + .long sys_statfs +/* 100 */ .long sys_fstatfs + .long sys_ni_syscall + .long sys_socketcall + .long sys_syslog + .long sys_setitimer +/* 105 */ .long sys_getitimer + .long sys_newstat + .long sys_newlstat + .long sys_newfstat + .long sys_ni_syscall /* was sys_uname */ +/* 110 */ .long sys_ni_syscall /* was sys_iopl */ + .long sys_vhangup + .long sys_ni_syscall + .long sys_syscall /* call a syscall */ + .long sys_wait4 +/* 115 */ .long sys_swapoff + .long sys_sysinfo + .long sys_ipc + .long sys_fsync + .long sys_sigreturn_wrapper +/* 120 */ .long sys_clone_wapper + .long sys_setdomainname + .long sys_newuname + .long sys_ni_syscall + .long sys_adjtimex +/* 125 */ .long sys_mprotect + .long sys_sigprocmask + .long sys_ni_syscall /* WAS: sys_create_module */ + .long sys_init_module + .long sys_delete_module +/* 130 */ .long sys_ni_syscall /* WAS: sys_get_kernel_syms */ + .long sys_quotactl + .long sys_getpgid + .long sys_fchdir + .long sys_bdflush +/* 135 */ .long sys_sysfs + .long sys_personality + .long sys_ni_syscall /* .long _sys_afs_syscall */ + .long sys_setfsuid16 + .long sys_setfsgid16 +/* 140 */ .long sys_llseek + .long sys_getdents + .long sys_select + .long sys_flock + .long sys_msync +/* 145 */ .long sys_readv + .long sys_writev + .long sys_getsid + .long sys_fdatasync + .long sys_sysctl +/* 150 */ .long sys_mlock + .long sys_munlock + .long sys_mlockall + .long sys_munlockall + .long sys_sched_setparam +/* 155 */ .long sys_sched_getparam + .long sys_sched_setscheduler + .long sys_sched_getscheduler + .long sys_sched_yield + .long sys_sched_get_priority_max +/* 160 */ .long sys_sched_get_priority_min + .long sys_sched_rr_get_interval + .long sys_nanosleep + .long sys_arm_mremap + .long sys_setresuid16 +/* 165 */ .long sys_getresuid16 + .long sys_ni_syscall + .long sys_ni_syscall /* WAS: sys_query_module */ + .long sys_poll + .long sys_nfsservctl +/* 170 */ .long sys_setresgid16 + .long sys_getresgid16 + .long sys_prctl + .long sys_rt_sigreturn_wrapper + .long sys_rt_sigaction +/* 175 */ .long sys_rt_sigprocmask + .long sys_rt_sigpending + .long sys_rt_sigtimedwait + .long sys_rt_sigqueueinfo + .long sys_rt_sigsuspend_wrapper +/* 180 */ .long sys_pread64 + .long sys_pwrite64 + .long sys_chown16 + .long sys_getcwd + .long sys_capget +/* 185 */ .long sys_capset + .long sys_sigaltstack_wrapper + .long sys_sendfile + .long sys_ni_syscall + .long sys_ni_syscall +/* 190 */ .long sys_vfork_wrapper + .long sys_getrlimit + .long sys_mmap2 + .long sys_truncate64 + .long sys_ftruncate64 +/* 195 */ .long sys_stat64 + .long sys_lstat64 + .long sys_fstat64 + .long sys_lchown + .long sys_getuid +/* 200 */ .long sys_getgid + .long sys_geteuid + .long sys_getegid + .long sys_setreuid + .long sys_setregid +/* 205 */ .long sys_getgroups + .long sys_setgroups + .long sys_fchown + .long sys_setresuid + .long sys_getresuid +/* 210 */ .long sys_setresgid + .long sys_getresgid + .long sys_chown + .long sys_setuid + .long sys_setgid +/* 215 */ .long sys_setfsuid + .long sys_setfsgid + .long sys_getdents64 + .long sys_pivot_root + .long sys_mincore +/* 220 */ .long sys_madvise + .long sys_fcntl64 + .long sys_ni_syscall /* TUX */ + .long sys_ni_syscall /* WAS: sys_security */ + .long sys_gettid +/* 225 */ .long sys_readahead + .long sys_setxattr + .long sys_lsetxattr + .long sys_fsetxattr + .long sys_getxattr +/* 230 */ .long sys_lgetxattr + .long sys_fgetxattr + .long sys_listxattr + .long sys_llistxattr + .long sys_flistxattr +/* 235 */ .long sys_removexattr + .long sys_lremovexattr + .long sys_fremovexattr + .long sys_tkill +__syscall_end: + + .rept NR_syscalls - (__syscall_end - __syscall_start) / 4 + .long sys_ni_syscall + .endr +#endif diff --git a/arch/arm26/kernel/compat.c b/arch/arm26/kernel/compat.c new file mode 100644 index 00000000000..db0310db899 --- /dev/null +++ b/arch/arm26/kernel/compat.c @@ -0,0 +1,174 @@ +/* + * linux/arch/arm26/kernel/compat.c + * + * Copyright (C) 2001 Russell King + * 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * We keep the old params compatibility cruft in one place (here) + * so we don't end up with lots of mess around other places. + * + * NOTE: + * The old struct param_struct is deprecated, but it will be kept in + * the kernel for 5 years from now (2001). This will allow boot loaders + * to convert to the new struct tag way. + */ +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/init.h> + +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/page.h> + +//#include <asm/arch.h> +//#include <asm/mach/irq.h> + +/* + * Usage: + * - do not go blindly adding fields, add them at the end + * - when adding fields, don't rely on the address until + * a patch from me has been released + * - unused fields should be zero (for future expansion) + * - this structure is relatively short-lived - only + * guaranteed to contain useful data in setup_arch() + * + * This is the old deprecated way to pass parameters to the kernel + */ +struct param_struct { + union { + struct { + unsigned long page_size; /* 0 */ + unsigned long nr_pages; /* 4 */ + unsigned long ramdisk_size; /* 8 */ + unsigned long flags; /* 12 */ +#define FLAG_READONLY 1 +#define FLAG_RDLOAD 4 +#define FLAG_RDPROMPT 8 + unsigned long rootdev; /* 16 */ + unsigned long video_num_cols; /* 20 */ + unsigned long video_num_rows; /* 24 */ + unsigned long video_x; /* 28 */ + unsigned long video_y; /* 32 */ + unsigned long memc_control_reg; /* 36 */ + unsigned char sounddefault; /* 40 */ + unsigned char adfsdrives; /* 41 */ + unsigned char bytes_per_char_h; /* 42 */ + unsigned char bytes_per_char_v; /* 43 */ + unsigned long pages_in_bank[4]; /* 44 */ + unsigned long pages_in_vram; /* 60 */ + unsigned long initrd_start; /* 64 */ + unsigned long initrd_size; /* 68 */ + unsigned long rd_start; /* 72 */ + unsigned long system_rev; /* 76 */ + unsigned long system_serial_low; /* 80 */ + unsigned long system_serial_high; /* 84 */ + unsigned long mem_fclk_21285; /* 88 */ + } s; + char unused[256]; + } u1; + union { + char paths[8][128]; + struct { + unsigned long magic; + char n[1024 - sizeof(unsigned long)]; + } s; + } u2; + char commandline[COMMAND_LINE_SIZE]; +}; + +static struct tag * __init memtag(struct tag *tag, unsigned long start, unsigned long size) +{ + tag = tag_next(tag); + tag->hdr.tag = ATAG_MEM; + tag->hdr.size = tag_size(tag_mem32); + tag->u.mem.size = size; + tag->u.mem.start = start; + + return tag; +} + +static void __init build_tag_list(struct param_struct *params, void *taglist) +{ + struct tag *tag = taglist; + + if (params->u1.s.page_size != PAGE_SIZE) { + printk(KERN_WARNING "Warning: bad configuration page, " + "trying to continue\n"); + return; + } + + printk(KERN_DEBUG "Converting old-style param struct to taglist\n"); + + tag->hdr.tag = ATAG_CORE; + tag->hdr.size = tag_size(tag_core); + tag->u.core.flags = params->u1.s.flags & FLAG_READONLY; + tag->u.core.pagesize = params->u1.s.page_size; + tag->u.core.rootdev = params->u1.s.rootdev; + + tag = tag_next(tag); + tag->hdr.tag = ATAG_RAMDISK; + tag->hdr.size = tag_size(tag_ramdisk); + tag->u.ramdisk.flags = (params->u1.s.flags & FLAG_RDLOAD ? 1 : 0) | + (params->u1.s.flags & FLAG_RDPROMPT ? 2 : 0); + tag->u.ramdisk.size = params->u1.s.ramdisk_size; + tag->u.ramdisk.start = params->u1.s.rd_start; + + tag = tag_next(tag); + tag->hdr.tag = ATAG_INITRD; + tag->hdr.size = tag_size(tag_initrd); + tag->u.initrd.start = params->u1.s.initrd_start; + tag->u.initrd.size = params->u1.s.initrd_size; + + tag = tag_next(tag); + tag->hdr.tag = ATAG_SERIAL; + tag->hdr.size = tag_size(tag_serialnr); + tag->u.serialnr.low = params->u1.s.system_serial_low; + tag->u.serialnr.high = params->u1.s.system_serial_high; + + tag = tag_next(tag); + tag->hdr.tag = ATAG_REVISION; + tag->hdr.size = tag_size(tag_revision); + tag->u.revision.rev = params->u1.s.system_rev; + + tag = memtag(tag, PHYS_OFFSET, params->u1.s.nr_pages * PAGE_SIZE); + + tag = tag_next(tag); + tag->hdr.tag = ATAG_ACORN; + tag->hdr.size = tag_size(tag_acorn); + tag->u.acorn.memc_control_reg = params->u1.s.memc_control_reg; + tag->u.acorn.vram_pages = params->u1.s.pages_in_vram; + tag->u.acorn.sounddefault = params->u1.s.sounddefault; + tag->u.acorn.adfsdrives = params->u1.s.adfsdrives; + + tag = tag_next(tag); + tag->hdr.tag = ATAG_CMDLINE; + tag->hdr.size = (strlen(params->commandline) + 3 + + sizeof(struct tag_header)) >> 2; + strcpy(tag->u.cmdline.cmdline, params->commandline); + + tag = tag_next(tag); + tag->hdr.tag = ATAG_NONE; + tag->hdr.size = 0; + + memmove(params, taglist, ((int)tag) - ((int)taglist) + + sizeof(struct tag_header)); +} + +void __init convert_to_tag_list(struct tag *tags) +{ + struct param_struct *params = (struct param_struct *)tags; + build_tag_list(params, ¶ms->u2); +} + +void __init squash_mem_tags(struct tag *tag) +{ + for (; tag->hdr.size; tag = tag_next(tag)) + if (tag->hdr.tag == ATAG_MEM) + tag->hdr.tag = ATAG_NONE; +} diff --git a/arch/arm26/kernel/dma.c b/arch/arm26/kernel/dma.c new file mode 100644 index 00000000000..80b5a774d90 --- /dev/null +++ b/arch/arm26/kernel/dma.c @@ -0,0 +1,273 @@ +/* + * linux/arch/arm26/kernel/dma.c + * + * Copyright (C) 1995-2000 Russell King + * 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Front-end to the DMA handling. This handles the allocation/freeing + * of DMA channels, and provides a unified interface to the machines + * DMA facilities. + */ +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/mman.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/errno.h> + +#include <asm/dma.h> + +DEFINE_SPINLOCK(dma_spin_lock); + +static dma_t dma_chan[MAX_DMA_CHANNELS]; + +/* + * Get dma list for /proc/dma + */ +int get_dma_list(char *buf) +{ + dma_t *dma; + char *p = buf; + int i; + + for (i = 0, dma = dma_chan; i < MAX_DMA_CHANNELS; i++, dma++) + if (dma->lock) + p += sprintf(p, "%2d: %14s %s\n", i, + dma->d_ops->type, dma->device_id); + + return p - buf; +} + +/* + * Request DMA channel + * + * On certain platforms, we have to allocate an interrupt as well... + */ +int request_dma(dmach_t channel, const char *device_id) +{ + dma_t *dma = dma_chan + channel; + int ret; + + if (channel >= MAX_DMA_CHANNELS || !dma->d_ops) + goto bad_dma; + + if (xchg(&dma->lock, 1) != 0) + goto busy; + + dma->device_id = device_id; + dma->active = 0; + dma->invalid = 1; + + ret = 0; + if (dma->d_ops->request) + ret = dma->d_ops->request(channel, dma); + + if (ret) + xchg(&dma->lock, 0); + + return ret; + +bad_dma: + printk(KERN_ERR "dma: trying to allocate DMA%d\n", channel); + return -EINVAL; + +busy: + return -EBUSY; +} + +/* + * Free DMA channel + * + * On certain platforms, we have to free interrupt as well... + */ +void free_dma(dmach_t channel) +{ + dma_t *dma = dma_chan + channel; + + if (channel >= MAX_DMA_CHANNELS || !dma->d_ops) + goto bad_dma; + + if (dma->active) { + printk(KERN_ERR "dma%d: freeing active DMA\n", channel); + dma->d_ops->disable(channel, dma); + dma->active = 0; + } + + if (xchg(&dma->lock, 0) != 0) { + if (dma->d_ops->free) + dma->d_ops->free(channel, dma); + return; + } + + printk(KERN_ERR "dma%d: trying to free free DMA\n", channel); + return; + +bad_dma: + printk(KERN_ERR "dma: trying to free DMA%d\n", channel); +} + +/* Set DMA Scatter-Gather list + */ +void set_dma_sg (dmach_t channel, struct scatterlist *sg, int nr_sg) +{ + dma_t *dma = dma_chan + channel; + + if (dma->active) + printk(KERN_ERR "dma%d: altering DMA SG while " + "DMA active\n", channel); + + dma->sg = sg; + dma->sgcount = nr_sg; + dma->using_sg = 1; + dma->invalid = 1; +} + +/* Set DMA address + * + * Copy address to the structure, and set the invalid bit + */ +void set_dma_addr (dmach_t channel, unsigned long physaddr) +{ + dma_t *dma = dma_chan + channel; + + if (dma->active) + printk(KERN_ERR "dma%d: altering DMA address while " + "DMA active\n", channel); + + dma->sg = &dma->buf; + dma->sgcount = 1; + dma->buf.__address = (char *)physaddr;//FIXME - not pretty + dma->using_sg = 0; + dma->invalid = 1; +} + +/* Set DMA byte count + * + * Copy address to the structure, and set the invalid bit + */ +void set_dma_count (dmach_t channel, unsigned long count) +{ + dma_t *dma = dma_chan + channel; + + if (dma->active) + printk(KERN_ERR "dma%d: altering DMA count while " + "DMA active\n", channel); + + dma->sg = &dma->buf; + dma->sgcount = 1; + dma->buf.length = count; + dma->using_sg = 0; + dma->invalid = 1; +} + +/* Set DMA direction mode + */ +void set_dma_mode (dmach_t channel, dmamode_t mode) +{ + dma_t *dma = dma_chan + channel; + + if (dma->active) + printk(KERN_ERR "dma%d: altering DMA mode while " + "DMA active\n", channel); + + dma->dma_mode = mode; + dma->invalid = 1; +} + +/* Enable DMA channel + */ +void enable_dma (dmach_t channel) +{ + dma_t *dma = dma_chan + channel; + + if (!dma->lock) + goto free_dma; + + if (dma->active == 0) { + dma->active = 1; + dma->d_ops->enable(channel, dma); + } + return; + +free_dma: + printk(KERN_ERR "dma%d: trying to enable free DMA\n", channel); + BUG(); +} + +/* Disable DMA channel + */ +void disable_dma (dmach_t channel) +{ + dma_t *dma = dma_chan + channel; + + if (!dma->lock) + goto free_dma; + + if (dma->active == 1) { + dma->active = 0; + dma->d_ops->disable(channel, dma); + } + return; + +free_dma: + printk(KERN_ERR "dma%d: trying to disable free DMA\n", channel); + BUG(); +} + +/* + * Is the specified DMA channel active? + */ +int dma_channel_active(dmach_t channel) +{ + return dma_chan[channel].active; +} + +void set_dma_page(dmach_t channel, char pagenr) +{ + printk(KERN_ERR "dma%d: trying to set_dma_page\n", channel); +} + +void set_dma_speed(dmach_t channel, int cycle_ns) +{ + dma_t *dma = dma_chan + channel; + int ret = 0; + + if (dma->d_ops->setspeed) + ret = dma->d_ops->setspeed(channel, dma, cycle_ns); + dma->speed = ret; +} + +int get_dma_residue(dmach_t channel) +{ + dma_t *dma = dma_chan + channel; + int ret = 0; + + if (dma->d_ops->residue) + ret = dma->d_ops->residue(channel, dma); + + return ret; +} + +void __init init_dma(void) +{ + arch_dma_init(dma_chan); +} + +EXPORT_SYMBOL(request_dma); +EXPORT_SYMBOL(free_dma); +EXPORT_SYMBOL(enable_dma); +EXPORT_SYMBOL(disable_dma); +EXPORT_SYMBOL(set_dma_addr); +EXPORT_SYMBOL(set_dma_count); +EXPORT_SYMBOL(set_dma_mode); +EXPORT_SYMBOL(set_dma_page); +EXPORT_SYMBOL(get_dma_residue); +EXPORT_SYMBOL(set_dma_sg); +EXPORT_SYMBOL(set_dma_speed); + +EXPORT_SYMBOL(dma_spin_lock); diff --git a/arch/arm26/kernel/ecard.c b/arch/arm26/kernel/ecard.c new file mode 100644 index 00000000000..824c6b571ad --- /dev/null +++ b/arch/arm26/kernel/ecard.c @@ -0,0 +1,850 @@ +/* + * linux/arch/arm26/kernel/ecard.c + * + * Copyright 1995-2001 Russell King + * Copyright 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Find all installed expansion cards, and handle interrupts from them. + * + * Created from information from Acorns RiscOS3 PRMs + * 15-Jun-2003 IM Modified from ARM32 (RiscPC capable) version + * 10-Jan-1999 RMK Run loaders in a simulated RISC OS environment. + * 06-May-1997 RMK Added blacklist for cards whose loader doesn't work. + * 12-Sep-1997 RMK Created new handling of interrupt enables/disables + * - cards can now register their own routine to control + * interrupts (recommended). + * 29-Sep-1997 RMK Expansion card interrupt hardware not being re-enabled + * on reset from Linux. (Caused cards not to respond + * under RiscOS without hard reset). + * + */ +#define ECARD_C + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/reboot.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/device.h> +#include <linux/init.h> + +#include <asm/dma.h> +#include <asm/ecard.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/mmu_context.h> +#include <asm/irqchip.h> +#include <asm/tlbflush.h> + +enum req { + req_readbytes, + req_reset +}; + +struct ecard_request { + enum req req; + ecard_t *ec; + unsigned int address; + unsigned int length; + unsigned int use_loader; + void *buffer; +}; + +struct expcard_blacklist { + unsigned short manufacturer; + unsigned short product; + const char *type; +}; + +static ecard_t *cards; +static ecard_t *slot_to_expcard[MAX_ECARDS]; +static unsigned int ectcr; + +/* List of descriptions of cards which don't have an extended + * identification, or chunk directories containing a description. + */ +static struct expcard_blacklist __initdata blacklist[] = { + { MANU_ACORN, PROD_ACORN_ETHER1, "Acorn Ether1" } +}; + +asmlinkage extern int +ecard_loader_reset(volatile unsigned char *pa, loader_t loader); +asmlinkage extern int +ecard_loader_read(int off, volatile unsigned char *pa, loader_t loader); + +static const struct ecard_id * +ecard_match_device(const struct ecard_id *ids, struct expansion_card *ec); + +static inline unsigned short +ecard_getu16(unsigned char *v) +{ + return v[0] | v[1] << 8; +} + +static inline signed long +ecard_gets24(unsigned char *v) +{ + return v[0] | v[1] << 8 | v[2] << 16 | ((v[2] & 0x80) ? 0xff000000 : 0); +} + +static inline ecard_t * +slot_to_ecard(unsigned int slot) +{ + return slot < MAX_ECARDS ? slot_to_expcard[slot] : NULL; +} + +/* ===================== Expansion card daemon ======================== */ +/* + * Since the loader programs on the expansion cards need to be run + * in a specific environment, create a separate task with this + * environment up, and pass requests to this task as and when we + * need to. + * + * This should allow 99% of loaders to be called from Linux. + * + * From a security standpoint, we trust the card vendors. This + * may be a misplaced trust. + */ +#define BUS_ADDR(x) ((((unsigned long)(x)) << 2) + IO_BASE) +#define POD_INT_ADDR(x) ((volatile unsigned char *)\ + ((BUS_ADDR((x)) - IO_BASE) + IO_START)) + +static inline void ecard_task_reset(struct ecard_request *req) +{ + struct expansion_card *ec = req->ec; + if (ec->loader) + ecard_loader_reset(POD_INT_ADDR(ec->podaddr), ec->loader); +} + +static void +ecard_task_readbytes(struct ecard_request *req) +{ + unsigned char *buf = (unsigned char *)req->buffer; + volatile unsigned char *base_addr = + (volatile unsigned char *)POD_INT_ADDR(req->ec->podaddr); + unsigned int len = req->length; + unsigned int off = req->address; + + if (!req->use_loader || !req->ec->loader) { + off *= 4; + while (len--) { + *buf++ = base_addr[off]; + off += 4; + } + } else { + while(len--) { + /* + * The following is required by some + * expansion card loader programs. + */ + *(unsigned long *)0x108 = 0; + *buf++ = ecard_loader_read(off++, base_addr, + req->ec->loader); + } + } +} + +static void ecard_do_request(struct ecard_request *req) +{ + switch (req->req) { + case req_readbytes: + ecard_task_readbytes(req); + break; + + case req_reset: + ecard_task_reset(req); + break; + } +} + +/* + * On 26-bit processors, we don't need the kcardd thread to access the + * expansion card loaders. We do it directly. + */ +#define ecard_call(req) ecard_do_request(req) + +/* ======================= Mid-level card control ===================== */ + +static void +ecard_readbytes(void *addr, ecard_t *ec, int off, int len, int useld) +{ + struct ecard_request req; + + req.req = req_readbytes; + req.ec = ec; + req.address = off; + req.length = len; + req.use_loader = useld; + req.buffer = addr; + + ecard_call(&req); +} + +int ecard_readchunk(struct in_chunk_dir *cd, ecard_t *ec, int id, int num) +{ + struct ex_chunk_dir excd; + int index = 16; + int useld = 0; + + if (!ec->cid.cd) + return 0; + + while(1) { + ecard_readbytes(&excd, ec, index, 8, useld); + index += 8; + if (c_id(&excd) == 0) { + if (!useld && ec->loader) { + useld = 1; + index = 0; + continue; + } + return 0; + } + if (c_id(&excd) == 0xf0) { /* link */ + index = c_start(&excd); + continue; + } + if (c_id(&excd) == 0x80) { /* loader */ + if (!ec->loader) { + ec->loader = (loader_t)kmalloc(c_len(&excd), + GFP_KERNEL); + if (ec->loader) + ecard_readbytes(ec->loader, ec, + (int)c_start(&excd), + c_len(&excd), useld); + else + return 0; + } + continue; + } + if (c_id(&excd) == id && num-- == 0) + break; + } + + if (c_id(&excd) & 0x80) { + switch (c_id(&excd) & 0x70) { + case 0x70: + ecard_readbytes((unsigned char *)excd.d.string, ec, + (int)c_start(&excd), c_len(&excd), + useld); + break; + case 0x00: + break; + } + } + cd->start_offset = c_start(&excd); + memcpy(cd->d.string, excd.d.string, 256); + return 1; +} + +/* ======================= Interrupt control ============================ */ + +static void ecard_def_irq_enable(ecard_t *ec, int irqnr) +{ +} + +static void ecard_def_irq_disable(ecard_t *ec, int irqnr) +{ +} + +static int ecard_def_irq_pending(ecard_t *ec) +{ + return !ec->irqmask || ec->irqaddr[0] & ec->irqmask; +} + +static void ecard_def_fiq_enable(ecard_t *ec, int fiqnr) +{ + panic("ecard_def_fiq_enable called - impossible"); +} + +static void ecard_def_fiq_disable(ecard_t *ec, int fiqnr) +{ + panic("ecard_def_fiq_disable called - impossible"); +} + +static int ecard_def_fiq_pending(ecard_t *ec) +{ + return !ec->fiqmask || ec->fiqaddr[0] & ec->fiqmask; +} + +static expansioncard_ops_t ecard_default_ops = { + ecard_def_irq_enable, + ecard_def_irq_disable, + ecard_def_irq_pending, + ecard_def_fiq_enable, + ecard_def_fiq_disable, + ecard_def_fiq_pending +}; + +/* + * Enable and disable interrupts from expansion cards. + * (interrupts are disabled for these functions). + * + * They are not meant to be called directly, but via enable/disable_irq. + */ +static void ecard_irq_unmask(unsigned int irqnr) +{ + ecard_t *ec = slot_to_ecard(irqnr - 32); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->claimed && ec->ops->irqenable) + ec->ops->irqenable(ec, irqnr); + else + printk(KERN_ERR "ecard: rejecting request to " + "enable IRQs for %d\n", irqnr); + } +} + +static void ecard_irq_mask(unsigned int irqnr) +{ + ecard_t *ec = slot_to_ecard(irqnr - 32); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->ops && ec->ops->irqdisable) + ec->ops->irqdisable(ec, irqnr); + } +} + +static struct irqchip ecard_chip = { + .ack = ecard_irq_mask, + .mask = ecard_irq_mask, + .unmask = ecard_irq_unmask, +}; + +void ecard_enablefiq(unsigned int fiqnr) +{ + ecard_t *ec = slot_to_ecard(fiqnr); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->claimed && ec->ops->fiqenable) + ec->ops->fiqenable(ec, fiqnr); + else + printk(KERN_ERR "ecard: rejecting request to " + "enable FIQs for %d\n", fiqnr); + } +} + +void ecard_disablefiq(unsigned int fiqnr) +{ + ecard_t *ec = slot_to_ecard(fiqnr); + + if (ec) { + if (!ec->ops) + ec->ops = &ecard_default_ops; + + if (ec->ops->fiqdisable) + ec->ops->fiqdisable(ec, fiqnr); + } +} + +static void +ecard_dump_irq_state(ecard_t *ec) +{ + printk(" %d: %sclaimed, ", + ec->slot_no, + ec->claimed ? "" : "not "); + + if (ec->ops && ec->ops->irqpending && + ec->ops != &ecard_default_ops) + printk("irq %spending\n", + ec->ops->irqpending(ec) ? "" : "not "); + else + printk("irqaddr %p, mask = %02X, status = %02X\n", + ec->irqaddr, ec->irqmask, *ec->irqaddr); +} + +static void ecard_check_lockup(struct irqdesc *desc) +{ + static int last, lockup; + ecard_t *ec; + + /* + * If the timer interrupt has not run since the last million + * unrecognised expansion card interrupts, then there is + * something seriously wrong. Disable the expansion card + * interrupts so at least we can continue. + * + * Maybe we ought to start a timer to re-enable them some time + * later? + */ + if (last == jiffies) { + lockup += 1; + if (lockup > 1000000) { + printk(KERN_ERR "\nInterrupt lockup detected - " + "disabling all expansion card interrupts\n"); + + desc->chip->mask(IRQ_EXPANSIONCARD); + + printk("Expansion card IRQ state:\n"); + + for (ec = cards; ec; ec = ec->next) + ecard_dump_irq_state(ec); + } + } else + lockup = 0; + + /* + * If we did not recognise the source of this interrupt, + * warn the user, but don't flood the user with these messages. + */ + if (!last || time_after(jiffies, (unsigned long)(last + 5*HZ))) { + last = jiffies; + printk(KERN_WARNING "Unrecognised interrupt from backplane\n"); + } +} + +static void +ecard_irq_handler(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs) +{ + ecard_t *ec; + int called = 0; + + desc->chip->mask(irq); + for (ec = cards; ec; ec = ec->next) { + int pending; + + if (!ec->claimed || ec->irq == NO_IRQ) + continue; + + if (ec->ops && ec->ops->irqpending) + pending = ec->ops->irqpending(ec); + else + pending = ecard_default_ops.irqpending(ec); + + if (pending) { + struct irqdesc *d = irq_desc + ec->irq; + d->handle(ec->irq, d, regs); + called ++; + } + } + desc->chip->unmask(irq); + + if (called == 0) + ecard_check_lockup(desc); +} + +#define ecard_irqexp_handler NULL +#define ecard_probeirqhw() (0) + +unsigned int ecard_address(ecard_t *ec, card_type_t type, card_speed_t speed) +{ + unsigned long address = 0; + int slot = ec->slot_no; + + ectcr &= ~(1 << slot); + + switch (type) { + case ECARD_MEMC: + address = IO_EC_MEMC_BASE + (slot << 12); + break; + + case ECARD_IOC: + address = IO_EC_IOC_BASE + (slot << 12) + (speed << 17); + break; + + default: + break; + } + + return address; +} + +static int ecard_prints(char *buffer, ecard_t *ec) +{ + char *start = buffer; + + buffer += sprintf(buffer, " %d: ", ec->slot_no); + + if (ec->cid.id == 0) { + struct in_chunk_dir incd; + + buffer += sprintf(buffer, "[%04X:%04X] ", + ec->cid.manufacturer, ec->cid.product); + + if (!ec->card_desc && ec->cid.cd && + ecard_readchunk(&incd, ec, 0xf5, 0)) { + ec->card_desc = kmalloc(strlen(incd.d.string)+1, GFP_KERNEL); + + if (ec->card_desc) + strcpy((char *)ec->card_desc, incd.d.string); + } + + buffer += sprintf(buffer, "%s\n", ec->card_desc ? ec->card_desc : "*unknown*"); + } else + buffer += sprintf(buffer, "Simple card %d\n", ec->cid.id); + + return buffer - start; +} + +static int get_ecard_dev_info(char *buf, char **start, off_t pos, int count) +{ + ecard_t *ec = cards; + off_t at = 0; + int len, cnt; + + cnt = 0; + while (ec && count > cnt) { + len = ecard_prints(buf, ec); + at += len; + if (at >= pos) { + if (!*start) { + *start = buf + (pos - (at - len)); + cnt = at - pos; + } else + cnt += len; + buf += len; + } + ec = ec->next; + } + return (count > cnt) ? cnt : count; +} + +static struct proc_dir_entry *proc_bus_ecard_dir = NULL; + +static void ecard_proc_init(void) +{ + proc_bus_ecard_dir = proc_mkdir("ecard", proc_bus); + create_proc_info_entry("devices", 0, proc_bus_ecard_dir, + get_ecard_dev_info); +} + +#define ec_set_resource(ec,nr,st,sz,flg) \ + do { \ + (ec)->resource[nr].name = ec->dev.bus_id; \ + (ec)->resource[nr].start = st; \ + (ec)->resource[nr].end = (st) + (sz) - 1; \ + (ec)->resource[nr].flags = flg; \ + } while (0) + +static void __init ecard_init_resources(struct expansion_card *ec) +{ + unsigned long base = PODSLOT_IOC0_BASE; + unsigned int slot = ec->slot_no; + int i; + + ec_set_resource(ec, ECARD_RES_MEMC, + PODSLOT_MEMC_BASE + (slot << 14), + PODSLOT_MEMC_SIZE, IORESOURCE_MEM); + + for (i = 0; i < ECARD_RES_IOCSYNC - ECARD_RES_IOCSLOW; i++) { + ec_set_resource(ec, i + ECARD_RES_IOCSLOW, + base + (slot << 14) + (i << 19), + PODSLOT_IOC_SIZE, IORESOURCE_MEM); + } + + for (i = 0; i < ECARD_NUM_RESOURCES; i++) { + if (ec->resource[i].start && + request_resource(&iomem_resource, &ec->resource[i])) { + printk(KERN_ERR "%s: resource(s) not available\n", + ec->dev.bus_id); + ec->resource[i].end -= ec->resource[i].start; + ec->resource[i].start = 0; + } + } +} + +static ssize_t ecard_show_irq(struct device *dev, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->irq); +} + +static ssize_t ecard_show_vendor(struct device *dev, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->cid.manufacturer); +} + +static ssize_t ecard_show_device(struct device *dev, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->cid.product); +} + +static ssize_t ecard_show_dma(struct device *dev, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + return sprintf(buf, "%u\n", ec->dma); +} + +static ssize_t ecard_show_resources(struct device *dev, char *buf) +{ + struct expansion_card *ec = ECARD_DEV(dev); + char *str = buf; + int i; + + for (i = 0; i < ECARD_NUM_RESOURCES; i++) + str += sprintf(str, "%08lx %08lx %08lx\n", + ec->resource[i].start, + ec->resource[i].end, + ec->resource[i].flags); + + return str - buf; +} + +static DEVICE_ATTR(irq, S_IRUGO, ecard_show_irq, NULL); +static DEVICE_ATTR(vendor, S_IRUGO, ecard_show_vendor, NULL); +static DEVICE_ATTR(device, S_IRUGO, ecard_show_device, NULL); +static DEVICE_ATTR(dma, S_IRUGO, ecard_show_dma, NULL); +static DEVICE_ATTR(resource, S_IRUGO, ecard_show_resources, NULL); + +/* + * Probe for an expansion card. + * + * If bit 1 of the first byte of the card is set, then the + * card does not exist. + */ +static int __init +ecard_probe(int slot, card_type_t type) +{ + ecard_t **ecp; + ecard_t *ec; + struct ex_ecid cid; + int i, rc = -ENOMEM; + + ec = kmalloc(sizeof(ecard_t), GFP_KERNEL); + if (!ec) + goto nomem; + + memset(ec, 0, sizeof(ecard_t)); + + ec->slot_no = slot; + ec->type = type; + ec->irq = NO_IRQ; + ec->fiq = NO_IRQ; + ec->dma = NO_DMA; + ec->card_desc = NULL; + ec->ops = &ecard_default_ops; + + rc = -ENODEV; + if ((ec->podaddr = ecard_address(ec, type, ECARD_SYNC)) == 0) + goto nodev; + + cid.r_zero = 1; + ecard_readbytes(&cid, ec, 0, 16, 0); + if (cid.r_zero) + goto nodev; + + ec->cid.id = cid.r_id; + ec->cid.cd = cid.r_cd; + ec->cid.is = cid.r_is; + ec->cid.w = cid.r_w; + ec->cid.manufacturer = ecard_getu16(cid.r_manu); + ec->cid.product = ecard_getu16(cid.r_prod); + ec->cid.country = cid.r_country; + ec->cid.irqmask = cid.r_irqmask; + ec->cid.irqoff = ecard_gets24(cid.r_irqoff); + ec->cid.fiqmask = cid.r_fiqmask; + ec->cid.fiqoff = ecard_gets24(cid.r_fiqoff); + ec->fiqaddr = + ec->irqaddr = (unsigned char *)ioaddr(ec->podaddr); + + if (ec->cid.is) { + ec->irqmask = ec->cid.irqmask; + ec->irqaddr += ec->cid.irqoff; + ec->fiqmask = ec->cid.fiqmask; + ec->fiqaddr += ec->cid.fiqoff; + } else { + ec->irqmask = 1; + ec->fiqmask = 4; + } + + for (i = 0; i < sizeof(blacklist) / sizeof(*blacklist); i++) + if (blacklist[i].manufacturer == ec->cid.manufacturer && + blacklist[i].product == ec->cid.product) { + ec->card_desc = blacklist[i].type; + break; + } + + snprintf(ec->dev.bus_id, sizeof(ec->dev.bus_id), "ecard%d", slot); + ec->dev.parent = NULL; + ec->dev.bus = &ecard_bus_type; + ec->dev.dma_mask = &ec->dma_mask; + ec->dma_mask = (u64)0xffffffff; + + ecard_init_resources(ec); + + /* + * hook the interrupt handlers + */ + ec->irq = 32 + slot; + set_irq_chip(ec->irq, &ecard_chip); + set_irq_handler(ec->irq, do_level_IRQ); + set_irq_flags(ec->irq, IRQF_VALID); + + for (ecp = &cards; *ecp; ecp = &(*ecp)->next); + + *ecp = ec; + slot_to_expcard[slot] = ec; + + device_register(&ec->dev); + device_create_file(&ec->dev, &dev_attr_dma); + device_create_file(&ec->dev, &dev_attr_irq); + device_create_file(&ec->dev, &dev_attr_resource); + device_create_file(&ec->dev, &dev_attr_vendor); + device_create_file(&ec->dev, &dev_attr_device); + + return 0; + +nodev: + kfree(ec); +nomem: + return rc; +} + +/* + * Initialise the expansion card system. + * Locate all hardware - interrupt management and + * actual cards. + */ +static int __init ecard_init(void) +{ + int slot, irqhw; + + printk("Probing expansion cards\n"); + + for (slot = 0; slot < MAX_ECARDS; slot ++) { + ecard_probe(slot, ECARD_IOC); + } + + irqhw = ecard_probeirqhw(); + + set_irq_chained_handler(IRQ_EXPANSIONCARD, + irqhw ? ecard_irqexp_handler : ecard_irq_handler); + + ecard_proc_init(); + + return 0; +} + +subsys_initcall(ecard_init); + +/* + * ECARD "bus" + */ +static const struct ecard_id * +ecard_match_device(const struct ecard_id *ids, struct expansion_card *ec) +{ + int i; + + for (i = 0; ids[i].manufacturer != 65535; i++) + if (ec->cid.manufacturer == ids[i].manufacturer && + ec->cid.product == ids[i].product) + return ids + i; + + return NULL; +} + +static int ecard_drv_probe(struct device *dev) +{ + struct expansion_card *ec = ECARD_DEV(dev); + struct ecard_driver *drv = ECARD_DRV(dev->driver); + const struct ecard_id *id; + int ret; + + id = ecard_match_device(drv->id_table, ec); + + ecard_claim(ec); + ret = drv->probe(ec, id); + if (ret) + ecard_release(ec); + return ret; +} + +static int ecard_drv_remove(struct device *dev) +{ + struct expansion_card *ec = ECARD_DEV(dev); + struct ecard_driver *drv = ECARD_DRV(dev->driver); + + drv->remove(ec); + ecard_release(ec); + + return 0; +} + +/* + * Before rebooting, we must make sure that the expansion card is in a + * sensible state, so it can be re-detected. This means that the first + * page of the ROM must be visible. We call the expansion cards reset + * handler, if any. + */ +static void ecard_drv_shutdown(struct device *dev) +{ + struct expansion_card *ec = ECARD_DEV(dev); + struct ecard_driver *drv = ECARD_DRV(dev->driver); + struct ecard_request req; + + if (drv->shutdown) + drv->shutdown(ec); + ecard_release(ec); + req.req = req_reset; + req.ec = ec; + ecard_call(&req); +} + +int ecard_register_driver(struct ecard_driver *drv) +{ + drv->drv.bus = &ecard_bus_type; + drv->drv.probe = ecard_drv_probe; + drv->drv.remove = ecard_drv_remove; + drv->drv.shutdown = ecard_drv_shutdown; + + return driver_register(&drv->drv); +} + +void ecard_remove_driver(struct ecard_driver *drv) +{ + driver_unregister(&drv->drv); +} + +static int ecard_match(struct device *_dev, struct device_driver *_drv) +{ + struct expansion_card *ec = ECARD_DEV(_dev); + struct ecard_driver *drv = ECARD_DRV(_drv); + int ret; + + if (drv->id_table) { + ret = ecard_match_device(drv->id_table, ec) != NULL; + } else { + ret = ec->cid.id == drv->id; + } + + return ret; +} + +struct bus_type ecard_bus_type = { + .name = "ecard", + .match = ecard_match, +}; + +static int ecard_bus_init(void) +{ + return bus_register(&ecard_bus_type); +} + +postcore_initcall(ecard_bus_init); + +EXPORT_SYMBOL(ecard_readchunk); +EXPORT_SYMBOL(ecard_address); +EXPORT_SYMBOL(ecard_register_driver); +EXPORT_SYMBOL(ecard_remove_driver); +EXPORT_SYMBOL(ecard_bus_type); diff --git a/arch/arm26/kernel/entry.S b/arch/arm26/kernel/entry.S new file mode 100644 index 00000000000..a231dd88d0e --- /dev/null +++ b/arch/arm26/kernel/entry.S @@ -0,0 +1,961 @@ +/* arch/arm26/kernel/entry.S + * + * Assembled from chunks of code in arch/arm + * + * Copyright (C) 2003 Ian Molton + * Based on the work of RMK. + * + */ + +#include <linux/linkage.h> + +#include <asm/assembler.h> +#include <asm/asm_offsets.h> +#include <asm/errno.h> +#include <asm/hardware.h> +#include <asm/sysirq.h> +#include <asm/thread_info.h> +#include <asm/page.h> +#include <asm/ptrace.h> + + .macro zero_fp +#ifndef CONFIG_NO_FRAME_POINTER + mov fp, #0 +#endif + .endm + + .text + +@ Bad Abort numbers +@ ----------------- +@ +#define BAD_PREFETCH 0 +#define BAD_DATA 1 +#define BAD_ADDREXCPTN 2 +#define BAD_IRQ 3 +#define BAD_UNDEFINSTR 4 + +@ OS version number used in SWIs +@ RISC OS is 0 +@ RISC iX is 8 +@ +#define OS_NUMBER 9 +#define ARMSWI_OFFSET 0x000f0000 + +@ +@ Stack format (ensured by USER_* and SVC_*) +@ PSR and PC are comined on arm26 +@ + +#define S_OFF 8 + +#define S_OLD_R0 64 +#define S_PC 60 +#define S_LR 56 +#define S_SP 52 +#define S_IP 48 +#define S_FP 44 +#define S_R10 40 +#define S_R9 36 +#define S_R8 32 +#define S_R7 28 +#define S_R6 24 +#define S_R5 20 +#define S_R4 16 +#define S_R3 12 +#define S_R2 8 +#define S_R1 4 +#define S_R0 0 + + .macro save_user_regs + str r0, [sp, #-4]! @ Store SVC r0 + str lr, [sp, #-4]! @ Store user mode PC + sub sp, sp, #15*4 + stmia sp, {r0 - lr}^ @ Store the other user-mode regs + mov r0, r0 + .endm + + .macro slow_restore_user_regs + ldmia sp, {r0 - lr}^ @ restore the user regs not including PC + mov r0, r0 + ldr lr, [sp, #15*4] @ get user PC + add sp, sp, #15*4+8 @ free stack + movs pc, lr @ return + .endm + + .macro fast_restore_user_regs + add sp, sp, #S_OFF + ldmib sp, {r1 - lr}^ + mov r0, r0 + ldr lr, [sp, #15*4] + add sp, sp, #15*4+8 + movs pc, lr + .endm + + .macro save_svc_regs + str sp, [sp, #-16]! + str lr, [sp, #8] + str lr, [sp, #4] + stmfd sp!, {r0 - r12} + mov r0, #-1 + str r0, [sp, #S_OLD_R0] + zero_fp + .endm + + .macro save_svc_regs_irq + str sp, [sp, #-16]! + str lr, [sp, #4] + ldr lr, .LCirq + ldr lr, [lr] + str lr, [sp, #8] + stmfd sp!, {r0 - r12} + mov r0, #-1 + str r0, [sp, #S_OLD_R0] + zero_fp + .endm + + .macro restore_svc_regs + ldmfd sp, {r0 - pc}^ + .endm + + .macro mask_pc, rd, rm + bic \rd, \rm, #PCMASK + .endm + + .macro disable_irqs, temp + mov \temp, pc + orr \temp, \temp, #PSR_I_BIT + teqp \temp, #0 + .endm + + .macro enable_irqs, temp + mov \temp, pc + and \temp, \temp, #~PSR_I_BIT + teqp \temp, #0 + .endm + + .macro initialise_traps_extra + .endm + + .macro get_thread_info, rd + mov \rd, sp, lsr #13 + mov \rd, \rd, lsl #13 + .endm + +/* + * These are the registers used in the syscall handler, and allow us to + * have in theory up to 7 arguments to a function - r0 to r6. + * + * Note that tbl == why is intentional. + * + * We must set at least "tsk" and "why" when calling ret_with_reschedule. + */ +scno .req r7 @ syscall number +tbl .req r8 @ syscall table pointer +why .req r8 @ Linux syscall (!= 0) +tsk .req r9 @ current thread_info + +/* + * Get the system call number. + */ + .macro get_scno + mask_pc lr, lr + ldr scno, [lr, #-4] @ get SWI instruction + .endm +/* + * ----------------------------------------------------------------------- + */ + +/* + * We rely on the fact that R0 is at the bottom of the stack (due to + * slow/fast restore user regs). + */ +#if S_R0 != 0 +#error "Please fix" +#endif + +/* + * This is the fast syscall return path. We do as little as + * possible here, and this includes saving r0 back into the SVC + * stack. + */ +ret_fast_syscall: + disable_irqs r1 @ disable interrupts + ldr r1, [tsk, #TI_FLAGS] + tst r1, #_TIF_WORK_MASK + bne fast_work_pending + fast_restore_user_regs + +/* + * Ok, we need to do extra processing, enter the slow path. + */ +fast_work_pending: + str r0, [sp, #S_R0+S_OFF]! @ returned r0 +work_pending: + tst r1, #_TIF_NEED_RESCHED + bne work_resched + tst r1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING + beq no_work_pending + mov r0, sp @ 'regs' + mov r2, why @ 'syscall' + bl do_notify_resume + disable_irqs r1 @ disable interrupts + b no_work_pending + +work_resched: + bl schedule +/* + * "slow" syscall return path. "why" tells us if this was a real syscall. + */ +ENTRY(ret_to_user) +ret_slow_syscall: + disable_irqs r1 @ disable interrupts + ldr r1, [tsk, #TI_FLAGS] + tst r1, #_TIF_WORK_MASK + bne work_pending +no_work_pending: + slow_restore_user_regs + +/* + * This is how we return from a fork. + */ +ENTRY(ret_from_fork) + bl schedule_tail + get_thread_info tsk + ldr r1, [tsk, #TI_FLAGS] @ check for syscall tracing + mov why, #1 + tst r1, #_TIF_SYSCALL_TRACE @ are we tracing syscalls? + beq ret_slow_syscall + mov r1, sp + mov r0, #1 @ trace exit [IP = 1] + bl syscall_trace + b ret_slow_syscall + +// FIXME - is this strictly necessary? +#include "calls.S" + +/*============================================================================= + * SWI handler + *----------------------------------------------------------------------------- + */ + + .align 5 +ENTRY(vector_swi) + save_user_regs + zero_fp + get_scno + +#ifdef CONFIG_ALIGNMENT_TRAP + ldr ip, __cr_alignment + ldr ip, [ip] + mcr p15, 0, ip, c1, c0 @ update control register +#endif + enable_irqs ip + + str r4, [sp, #-S_OFF]! @ push fifth arg + + get_thread_info tsk + ldr ip, [tsk, #TI_FLAGS] @ check for syscall tracing + bic scno, scno, #0xff000000 @ mask off SWI op-code + eor scno, scno, #OS_NUMBER << 20 @ check OS number + adr tbl, sys_call_table @ load syscall table pointer + tst ip, #_TIF_SYSCALL_TRACE @ are we tracing syscalls? + bne __sys_trace + + adral lr, ret_fast_syscall @ set return address + orral lr, lr, #PSR_I_BIT | MODE_SVC26 @ Force SVC mode on return + cmp scno, #NR_syscalls @ check upper syscall limit + ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine + + add r1, sp, #S_OFF +2: mov why, #0 @ no longer a real syscall + cmp scno, #ARMSWI_OFFSET + eor r0, scno, #OS_NUMBER << 20 @ put OS number back + bcs arm_syscall + b sys_ni_syscall @ not private func + + /* + * This is the really slow path. We're going to be doing + * context switches, and waiting for our parent to respond. + */ +__sys_trace: + add r1, sp, #S_OFF + mov r0, #0 @ trace entry [IP = 0] + bl syscall_trace + + adral lr, __sys_trace_return @ set return address + orral lr, lr, #PSR_I_BIT | MODE_SVC26 @ Force SVC mode on return + add r1, sp, #S_R0 + S_OFF @ pointer to regs + cmp scno, #NR_syscalls @ check upper syscall limit + ldmccia r1, {r0 - r3} @ have to reload r0 - r3 + ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine + b 2b + +__sys_trace_return: + str r0, [sp, #S_R0 + S_OFF]! @ save returned r0 + mov r1, sp + mov r0, #1 @ trace exit [IP = 1] + bl syscall_trace + b ret_slow_syscall + + .align 5 +#ifdef CONFIG_ALIGNMENT_TRAP + .type __cr_alignment, #object +__cr_alignment: + .word cr_alignment +#endif + + .type sys_call_table, #object +ENTRY(sys_call_table) +#include "calls.S" + +/*============================================================================ + * Special system call wrappers + */ +@ r0 = syscall number +@ r5 = syscall table + .type sys_syscall, #function +sys_syscall: + eor scno, r0, #OS_NUMBER << 20 + cmp scno, #NR_syscalls @ check range + stmleia sp, {r5, r6} @ shuffle args + movle r0, r1 + movle r1, r2 + movle r2, r3 + movle r3, r4 + ldrle pc, [tbl, scno, lsl #2] + b sys_ni_syscall + +sys_fork_wrapper: + add r0, sp, #S_OFF + b sys_fork + +sys_vfork_wrapper: + add r0, sp, #S_OFF + b sys_vfork + +sys_execve_wrapper: + add r3, sp, #S_OFF + b sys_execve + +sys_clone_wapper: + add r2, sp, #S_OFF + b sys_clone + +sys_sigsuspend_wrapper: + add r3, sp, #S_OFF + b sys_sigsuspend + +sys_rt_sigsuspend_wrapper: + add r2, sp, #S_OFF + b sys_rt_sigsuspend + +sys_sigreturn_wrapper: + add r0, sp, #S_OFF + b sys_sigreturn + +sys_rt_sigreturn_wrapper: + add r0, sp, #S_OFF + b sys_rt_sigreturn + +sys_sigaltstack_wrapper: + ldr r2, [sp, #S_OFF + S_SP] + b do_sigaltstack + +/* + * Note: off_4k (r5) is always units of 4K. If we can't do the requested + * offset, we return EINVAL. FIXME - this lost some stuff from arm32 to + * ifdefs. check it out. + */ +sys_mmap2: + tst r5, #((1 << (PAGE_SHIFT - 12)) - 1) + moveq r5, r5, lsr #PAGE_SHIFT - 12 + streq r5, [sp, #4] + beq do_mmap2 + mov r0, #-EINVAL + RETINSTR(mov,pc, lr) + +/* + * Design issues: + * - We have several modes that each vector can be called from, + * each with its own set of registers. On entry to any vector, + * we *must* save the registers used in *that* mode. + * + * - This code must be as fast as possible. + * + * There are a few restrictions on the vectors: + * - the SWI vector cannot be called from *any* non-user mode + * + * - the FP emulator is *never* called from *any* non-user mode undefined + * instruction. + * + */ + + .text + + .macro handle_irq +1: mov r4, #IOC_BASE + ldrb r6, [r4, #0x24] @ get high priority first + adr r5, irq_prio_h + teq r6, #0 + ldreqb r6, [r4, #0x14] @ get low priority + adreq r5, irq_prio_l + + teq r6, #0 @ If an IRQ happened... + ldrneb r0, [r5, r6] @ get IRQ number + movne r1, sp @ get struct pt_regs + adrne lr, 1b @ Set return address to 1b + orrne lr, lr, #PSR_I_BIT | MODE_SVC26 @ (and force SVC mode) + bne asm_do_IRQ @ process IRQ (if asserted) + .endm + + +/* + * Interrupt table (incorporates priority) + */ + .macro irq_prio_table +irq_prio_l: .byte 0, 0, 1, 0, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 + .byte 4, 0, 1, 0, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 + .byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 + .byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3 + .byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 + .byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + .byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +irq_prio_h: .byte 0, 8, 9, 8,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 12, 8, 9, 8,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 14,14,14,14,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 14,14,14,14,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10 + .endm + +#if 1 +/* + * Uncomment these if you wish to get more debugging into about data aborts. + * FIXME - I bet we can find a way to encode these and keep performance. + */ +#define FAULT_CODE_LDRSTRPOST 0x80 +#define FAULT_CODE_LDRSTRPRE 0x40 +#define FAULT_CODE_LDRSTRREG 0x20 +#define FAULT_CODE_LDMSTM 0x10 +#define FAULT_CODE_LDCSTC 0x08 +#endif +#define FAULT_CODE_PREFETCH 0x04 +#define FAULT_CODE_WRITE 0x02 +#define FAULT_CODE_FORCECOW 0x01 + +/*============================================================================= + * Undefined FIQs + *----------------------------------------------------------------------------- + */ +_unexp_fiq: ldr sp, .LCfiq + mov r12, #IOC_BASE + strb r12, [r12, #0x38] @ Disable FIQ register + teqp pc, #PSR_I_BIT | PSR_F_BIT | MODE_SVC26 + mov r0, r0 + stmfd sp!, {r0 - r3, ip, lr} + adr r0, Lfiqmsg + bl printk + ldmfd sp!, {r0 - r3, ip, lr} + teqp pc, #PSR_I_BIT | PSR_F_BIT | MODE_FIQ26 + mov r0, r0 + movs pc, lr + +Lfiqmsg: .ascii "*** Unexpected FIQ\n\0" + .align + +.LCfiq: .word __temp_fiq +.LCirq: .word __temp_irq + +/*============================================================================= + * Undefined instruction handler + *----------------------------------------------------------------------------- + * Handles floating point instructions + */ +vector_undefinstr: + tst lr, #MODE_SVC26 @ did we come from a non-user mode? + bne __und_svc @ yes - deal with it. +/* Otherwise, fall through for the user-space (common) case. */ + save_user_regs + zero_fp @ zero frame pointer + teqp pc, #PSR_I_BIT | MODE_SVC26 @ disable IRQs +.Lbug_undef: + ldr r4, .LC2 + ldr pc, [r4] @ Call FP module entry point +/* FIXME - should we trap for a null pointer here? */ + +/* The SVC mode case */ +__und_svc: save_svc_regs @ Non-user mode + mask_pc r0, lr + and r2, lr, #3 + sub r0, r0, #4 + mov r1, sp + bl do_undefinstr + restore_svc_regs + +/* We get here if the FP emulator doesnt handle the undef instr. + * If the insn WAS handled, the emulator jumps to ret_from_exception by itself/ + */ + .globl fpundefinstr +fpundefinstr: + mov r0, lr + mov r1, sp + teqp pc, #MODE_SVC26 + bl do_undefinstr + b ret_from_exception @ Normal FP exit + +#if defined CONFIG_FPE_NWFPE || defined CONFIG_FPE_FASTFPE + /* The FPE is always present */ + .equ fpe_not_present, 0 +#else +/* We get here if an undefined instruction happens and the floating + * point emulator is not present. If the offending instruction was + * a WFS, we just perform a normal return as if we had emulated the + * operation. This is a hack to allow some basic userland binaries + * to run so that the emulator module proper can be loaded. --philb + * FIXME - probably a broken useless hack... + */ +fpe_not_present: + adr r10, wfs_mask_data + ldmia r10, {r4, r5, r6, r7, r8} + ldr r10, [sp, #S_PC] @ Load PC + sub r10, r10, #4 + mask_pc r10, r10 + ldrt r10, [r10] @ get instruction + and r5, r10, r5 + teq r5, r4 @ Is it WFS? + beq ret_from_exception + and r5, r10, r8 + teq r5, r6 @ Is it LDF/STF on sp or fp? + teqne r5, r7 + bne fpundefinstr + tst r10, #0x00200000 @ Does it have WB + beq ret_from_exception + and r4, r10, #255 @ get offset + and r6, r10, #0x000f0000 + tst r10, #0x00800000 @ +/- + ldr r5, [sp, r6, lsr #14] @ Load reg + rsbeq r4, r4, #0 + add r5, r5, r4, lsl #2 + str r5, [sp, r6, lsr #14] @ Save reg + b ret_from_exception + +wfs_mask_data: .word 0x0e200110 @ WFS/RFS + .word 0x0fef0fff + .word 0x0d0d0100 @ LDF [sp]/STF [sp] + .word 0x0d0b0100 @ LDF [fp]/STF [fp] + .word 0x0f0f0f00 +#endif + +.LC2: .word fp_enter + +/*============================================================================= + * Prefetch abort handler + *----------------------------------------------------------------------------- + */ +#define DEBUG_UNDEF +/* remember: lr = USR pc */ +vector_prefetch: + sub lr, lr, #4 + tst lr, #MODE_SVC26 + bne __pabt_invalid + save_user_regs + teqp pc, #MODE_SVC26 @ Enable IRQs... + mask_pc r0, lr @ Address of abort + mov r1, sp @ Tasks registers + bl do_PrefetchAbort + teq r0, #0 @ If non-zero, we believe this abort.. + bne ret_from_exception +#ifdef DEBUG_UNDEF + adr r0, t + bl printk +#endif + ldr lr, [sp,#S_PC] @ FIXME program to test this on. I think its + b .Lbug_undef @ broken at the moment though!) + +__pabt_invalid: save_svc_regs + mov r0, sp @ Prefetch aborts are definitely *not* + mov r1, #BAD_PREFETCH @ allowed in non-user modes. We cant + and r2, lr, #3 @ recover from this problem. + b bad_mode + +#ifdef DEBUG_UNDEF +t: .ascii "*** undef ***\r\n\0" + .align +#endif + +/*============================================================================= + * Address exception handler + *----------------------------------------------------------------------------- + * These aren't too critical. + * (they're not supposed to happen). + * In order to debug the reason for address exceptions in non-user modes, + * we have to obtain all the registers so that we can see what's going on. + */ + +vector_addrexcptn: + sub lr, lr, #8 + tst lr, #3 + bne Laddrexcptn_not_user + save_user_regs + teq pc, #MODE_SVC26 + mask_pc r0, lr @ Point to instruction + mov r1, sp @ Point to registers + mov r2, #0x400 + mov lr, pc + bl do_excpt + b ret_from_exception + +Laddrexcptn_not_user: + save_svc_regs + and r2, lr, #3 + teq r2, #3 + bne Laddrexcptn_illegal_mode + teqp pc, #MODE_SVC26 + mask_pc r0, lr + mov r1, sp + orr r2, r2, #0x400 + bl do_excpt + ldmia sp, {r0 - lr} @ I cant remember the reason I changed this... + add sp, sp, #15*4 + movs pc, lr + +Laddrexcptn_illegal_mode: + mov r0, sp + str lr, [sp, #-4]! + orr r1, r2, #PSR_I_BIT | PSR_F_BIT + teqp r1, #0 @ change into mode (wont be user mode) + mov r0, r0 + mov r1, r8 @ Any register from r8 - r14 can be banked + mov r2, r9 + mov r3, r10 + mov r4, r11 + mov r5, r12 + mov r6, r13 + mov r7, r14 + teqp pc, #PSR_F_BIT | MODE_SVC26 @ back to svc + mov r0, r0 + stmfd sp!, {r1-r7} + ldmia r0, {r0-r7} + stmfd sp!, {r0-r7} + mov r0, sp + mov r1, #BAD_ADDREXCPTN + b bad_mode + +/*============================================================================= + * Interrupt (IRQ) handler + *----------------------------------------------------------------------------- + * Note: if the IRQ was taken whilst in user mode, then *no* kernel routine + * is running, so do not have to save svc lr. + * + * Entered in IRQ mode. + */ + +vector_IRQ: ldr sp, .LCirq @ Setup some temporary stack + sub lr, lr, #4 + str lr, [sp] @ push return address + + tst lr, #3 + bne __irq_non_usr + +__irq_usr: teqp pc, #PSR_I_BIT | MODE_SVC26 @ Enter SVC mode + mov r0, r0 + + ldr lr, .LCirq + ldr lr, [lr] @ Restore lr for jump back to USR + + save_user_regs + + handle_irq + + mov why, #0 + get_thread_info tsk + b ret_to_user + +@ Place the IRQ priority table here so that the handle_irq macros above +@ and below here can access it. + + irq_prio_table + +__irq_non_usr: teqp pc, #PSR_I_BIT | MODE_SVC26 @ Enter SVC mode + mov r0, r0 + + save_svc_regs_irq + + and r2, lr, #3 + teq r2, #3 + bne __irq_invalid @ IRQ not from SVC mode + + handle_irq + + restore_svc_regs + +__irq_invalid: mov r0, sp + mov r1, #BAD_IRQ + b bad_mode + +/*============================================================================= + * Data abort handler code + *----------------------------------------------------------------------------- + * + * This handles both exceptions from user and SVC modes, computes the address + * range of the problem, and does any correction that is required. It then + * calls the kernel data abort routine. + * + * This is where I wish that the ARM would tell you which address aborted. + */ + +vector_data: sub lr, lr, #8 @ Correct lr + tst lr, #3 + bne Ldata_not_user + save_user_regs + teqp pc, #MODE_SVC26 + mask_pc r0, lr + bl Ldata_do + b ret_from_exception + +Ldata_not_user: + save_svc_regs + and r2, lr, #3 + teq r2, #3 + bne Ldata_illegal_mode + tst lr, #PSR_I_BIT + teqeqp pc, #MODE_SVC26 + mask_pc r0, lr + bl Ldata_do + restore_svc_regs + +Ldata_illegal_mode: + mov r0, sp + mov r1, #BAD_DATA + b bad_mode + +Ldata_do: mov r3, sp + ldr r4, [r0] @ Get instruction + mov r2, #0 + tst r4, #1 << 20 @ Check to see if it is a write instruction + orreq r2, r2, #FAULT_CODE_WRITE @ Indicate write instruction + mov r1, r4, lsr #22 @ Now branch to the relevent processing routine + and r1, r1, #15 << 2 + add pc, pc, r1 + movs pc, lr + b Ldata_unknown + b Ldata_unknown + b Ldata_unknown + b Ldata_unknown + b Ldata_ldrstr_post @ ldr rd, [rn], #m + b Ldata_ldrstr_numindex @ ldr rd, [rn, #m] @ RegVal + b Ldata_ldrstr_post @ ldr rd, [rn], rm + b Ldata_ldrstr_regindex @ ldr rd, [rn, rm] + b Ldata_ldmstm @ ldm*a rn, <rlist> + b Ldata_ldmstm @ ldm*b rn, <rlist> + b Ldata_unknown + b Ldata_unknown + b Ldata_ldrstr_post @ ldc rd, [rn], #m @ Same as ldr rd, [rn], #m + b Ldata_ldcstc_pre @ ldc rd, [rn, #m] + b Ldata_unknown +Ldata_unknown: @ Part of jumptable + mov r0, r1 + mov r1, r4 + mov r2, r3 + b baddataabort + +Ldata_ldrstr_post: + mov r0, r4, lsr #14 @ Get Rn + and r0, r0, #15 << 2 @ Mask out reg. + teq r0, #15 << 2 + ldr r0, [r3, r0] @ Get register + biceq r0, r0, #PCMASK + mov r1, r0 +#ifdef FAULT_CODE_LDRSTRPOST + orr r2, r2, #FAULT_CODE_LDRSTRPOST +#endif + b do_DataAbort + +Ldata_ldrstr_numindex: + mov r0, r4, lsr #14 @ Get Rn + and r0, r0, #15 << 2 @ Mask out reg. + teq r0, #15 << 2 + ldr r0, [r3, r0] @ Get register + mov r1, r4, lsl #20 + biceq r0, r0, #PCMASK + tst r4, #1 << 23 + addne r0, r0, r1, lsr #20 + subeq r0, r0, r1, lsr #20 + mov r1, r0 +#ifdef FAULT_CODE_LDRSTRPRE + orr r2, r2, #FAULT_CODE_LDRSTRPRE +#endif + b do_DataAbort + +Ldata_ldrstr_regindex: + mov r0, r4, lsr #14 @ Get Rn + and r0, r0, #15 << 2 @ Mask out reg. + teq r0, #15 << 2 + ldr r0, [r3, r0] @ Get register + and r7, r4, #15 + biceq r0, r0, #PCMASK + teq r7, #15 @ Check for PC + ldr r7, [r3, r7, lsl #2] @ Get Rm + and r8, r4, #0x60 @ Get shift types + biceq r7, r7, #PCMASK + mov r9, r4, lsr #7 @ Get shift amount + and r9, r9, #31 + teq r8, #0 + moveq r7, r7, lsl r9 + teq r8, #0x20 @ LSR shift + moveq r7, r7, lsr r9 + teq r8, #0x40 @ ASR shift + moveq r7, r7, asr r9 + teq r8, #0x60 @ ROR shift + moveq r7, r7, ror r9 + tst r4, #1 << 23 + addne r0, r0, r7 + subeq r0, r0, r7 @ Apply correction + mov r1, r0 +#ifdef FAULT_CODE_LDRSTRREG + orr r2, r2, #FAULT_CODE_LDRSTRREG +#endif + b do_DataAbort + +Ldata_ldmstm: + mov r7, #0x11 + orr r7, r7, r7, lsl #8 + and r0, r4, r7 + and r1, r4, r7, lsl #1 + add r0, r0, r1, lsr #1 + and r1, r4, r7, lsl #2 + add r0, r0, r1, lsr #2 + and r1, r4, r7, lsl #3 + add r0, r0, r1, lsr #3 + add r0, r0, r0, lsr #8 + add r0, r0, r0, lsr #4 + and r7, r0, #15 @ r7 = no. of registers to transfer. + mov r5, r4, lsr #14 @ Get Rn + and r5, r5, #15 << 2 + ldr r0, [r3, r5] @ Get reg + eor r6, r4, r4, lsl #2 + tst r6, #1 << 23 @ Check inc/dec ^ writeback + rsbeq r7, r7, #0 + add r7, r0, r7, lsl #2 @ Do correction (signed) + subne r1, r7, #1 + subeq r1, r0, #1 + moveq r0, r7 + tst r4, #1 << 21 @ Check writeback + strne r7, [r3, r5] + eor r6, r4, r4, lsl #1 + tst r6, #1 << 24 @ Check Pre/Post ^ inc/dec + addeq r0, r0, #4 + addeq r1, r1, #4 + teq r5, #15*4 @ CHECK FOR PC + biceq r1, r1, #PCMASK + biceq r0, r0, #PCMASK +#ifdef FAULT_CODE_LDMSTM + orr r2, r2, #FAULT_CODE_LDMSTM +#endif + b do_DataAbort + +Ldata_ldcstc_pre: + mov r0, r4, lsr #14 @ Get Rn + and r0, r0, #15 << 2 @ Mask out reg. + teq r0, #15 << 2 + ldr r0, [r3, r0] @ Get register + mov r1, r4, lsl #24 @ Get offset + biceq r0, r0, #PCMASK + tst r4, #1 << 23 + addne r0, r0, r1, lsr #24 + subeq r0, r0, r1, lsr #24 + mov r1, r0 +#ifdef FAULT_CODE_LDCSTC + orr r2, r2, #FAULT_CODE_LDCSTC +#endif + b do_DataAbort + + +/* + * This is the return code to user mode for abort handlers + */ +ENTRY(ret_from_exception) + get_thread_info tsk + mov why, #0 + b ret_to_user + + .data +ENTRY(fp_enter) + .word fpe_not_present + .text +/* + * Register switch for older 26-bit only ARMs + */ +ENTRY(__switch_to) + add r0, r0, #TI_CPU_SAVE + stmia r0, {r4 - sl, fp, sp, lr} + add r1, r1, #TI_CPU_SAVE + ldmia r1, {r4 - sl, fp, sp, pc}^ + +/* + *============================================================================= + * Low-level interface code + *----------------------------------------------------------------------------- + * Trap initialisation + *----------------------------------------------------------------------------- + * + * Note - FIQ code has changed. The default is a couple of words in 0x1c, 0x20 + * that call _unexp_fiq. Nowever, we now copy the FIQ routine to 0x1c (removes + * some excess cycles). + * + * What we need to put into 0-0x1c are branches to branch to the kernel. + */ + + .section ".init.text",#alloc,#execinstr + +.Ljump_addresses: + swi SYS_ERROR0 + .word vector_undefinstr - 12 + .word vector_swi - 16 + .word vector_prefetch - 20 + .word vector_data - 24 + .word vector_addrexcptn - 28 + .word vector_IRQ - 32 + .word _unexp_fiq - 36 + b . + 8 +/* + * initialise the trap system + */ +ENTRY(__trap_init) + stmfd sp!, {r4 - r7, lr} + adr r1, .Ljump_addresses + ldmia r1, {r1 - r7, ip, lr} + orr r2, lr, r2, lsr #2 + orr r3, lr, r3, lsr #2 + orr r4, lr, r4, lsr #2 + orr r5, lr, r5, lsr #2 + orr r6, lr, r6, lsr #2 + orr r7, lr, r7, lsr #2 + orr ip, lr, ip, lsr #2 + mov r0, #0 + stmia r0, {r1 - r7, ip} + ldmfd sp!, {r4 - r7, pc}^ + + .bss +__temp_irq: .space 4 @ saved lr_irq +__temp_fiq: .space 128 diff --git a/arch/arm26/kernel/fiq.c b/arch/arm26/kernel/fiq.c new file mode 100644 index 00000000000..08a97c9498f --- /dev/null +++ b/arch/arm26/kernel/fiq.c @@ -0,0 +1,202 @@ +/* + * linux/arch/arm26/kernel/fiq.c + * + * Copyright (C) 1998 Russell King + * Copyright (C) 1998, 1999 Phil Blundell + * Copyright (C) 2003 Ian Molton + * + * FIQ support written by Philip Blundell <philb@gnu.org>, 1998. + * + * FIQ support re-written by Russell King to be more generic + * + * We now properly support a method by which the FIQ handlers can + * be stacked onto the vector. We still do not support sharing + * the FIQ vector itself. + * + * Operation is as follows: + * 1. Owner A claims FIQ: + * - default_fiq relinquishes control. + * 2. Owner A: + * - inserts code. + * - sets any registers, + * - enables FIQ. + * 3. Owner B claims FIQ: + * - if owner A has a relinquish function. + * - disable FIQs. + * - saves any registers. + * - returns zero. + * 4. Owner B: + * - inserts code. + * - sets any registers, + * - enables FIQ. + * 5. Owner B releases FIQ: + * - Owner A is asked to reacquire FIQ: + * - inserts code. + * - restores saved registers. + * - enables FIQ. + * 6. Goto 3 + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/init.h> +#include <linux/seq_file.h> + +#include <asm/fiq.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/pgalloc.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +#define FIQ_VECTOR (vectors_base() + 0x1c) + +static unsigned long no_fiq_insn; + +#define unprotect_page_0() +#define protect_page_0() + +/* Default reacquire function + * - we always relinquish FIQ control + * - we always reacquire FIQ control + */ +static int fiq_def_op(void *ref, int relinquish) +{ + if (!relinquish) { + unprotect_page_0(); + *(unsigned long *)FIQ_VECTOR = no_fiq_insn; + protect_page_0(); + } + + return 0; +} + +static struct fiq_handler default_owner = { + .name = "default", + .fiq_op = fiq_def_op, +}; + +static struct fiq_handler *current_fiq = &default_owner; + +int show_fiq_list(struct seq_file *p, void *v) +{ + if (current_fiq != &default_owner) + seq_printf(p, "FIQ: %s\n", current_fiq->name); + + return 0; +} + +void set_fiq_handler(void *start, unsigned int length) +{ + unprotect_page_0(); + + memcpy((void *)FIQ_VECTOR, start, length); + + protect_page_0(); +} + +/* + * Taking an interrupt in FIQ mode is death, so both these functions + * disable irqs for the duration. + */ +void set_fiq_regs(struct pt_regs *regs) +{ + register unsigned long tmp, tmp2; + __asm__ volatile ( + "mov %0, pc + bic %1, %0, #0x3 + orr %1, %1, %3 + teqp %1, #0 @ select FIQ mode + mov r0, r0 + ldmia %2, {r8 - r14} + teqp %0, #0 @ return to SVC mode + mov r0, r0" + : "=&r" (tmp), "=&r" (tmp2) + : "r" (®s->ARM_r8), "I" (PSR_I_BIT | PSR_F_BIT | MODE_FIQ26) + /* These registers aren't modified by the above code in a way + visible to the compiler, but we mark them as clobbers anyway + so that GCC won't put any of the input or output operands in + them. */ + : "r8", "r9", "r10", "r11", "r12", "r13", "r14"); +} + +void get_fiq_regs(struct pt_regs *regs) +{ + register unsigned long tmp, tmp2; + __asm__ volatile ( + "mov %0, pc + bic %1, %0, #0x3 + orr %1, %1, %3 + teqp %1, #0 @ select FIQ mode + mov r0, r0 + stmia %2, {r8 - r14} + teqp %0, #0 @ return to SVC mode + mov r0, r0" + : "=&r" (tmp), "=&r" (tmp2) + : "r" (®s->ARM_r8), "I" (PSR_I_BIT | PSR_F_BIT | MODE_FIQ26) + /* These registers aren't modified by the above code in a way + visible to the compiler, but we mark them as clobbers anyway + so that GCC won't put any of the input or output operands in + them. */ + : "r8", "r9", "r10", "r11", "r12", "r13", "r14"); +} + +int claim_fiq(struct fiq_handler *f) +{ + int ret = 0; + + if (current_fiq) { + ret = -EBUSY; + + if (current_fiq->fiq_op != NULL) + ret = current_fiq->fiq_op(current_fiq->dev_id, 1); + } + + if (!ret) { + f->next = current_fiq; + current_fiq = f; + } + + return ret; +} + +void release_fiq(struct fiq_handler *f) +{ + if (current_fiq != f) { + printk(KERN_ERR "%s FIQ trying to release %s FIQ\n", + f->name, current_fiq->name); +#ifdef CONFIG_DEBUG_ERRORS + __backtrace(); +#endif + return; + } + + do + current_fiq = current_fiq->next; + while (current_fiq->fiq_op(current_fiq->dev_id, 0)); +} + +void enable_fiq(int fiq) +{ + enable_irq(fiq + FIQ_START); +} + +void disable_fiq(int fiq) +{ + disable_irq(fiq + FIQ_START); +} + +EXPORT_SYMBOL(set_fiq_handler); +EXPORT_SYMBOL(set_fiq_regs); +EXPORT_SYMBOL(get_fiq_regs); +EXPORT_SYMBOL(claim_fiq); +EXPORT_SYMBOL(release_fiq); +EXPORT_SYMBOL(enable_fiq); +EXPORT_SYMBOL(disable_fiq); + +void __init init_FIQ(void) +{ + no_fiq_insn = *(unsigned long *)FIQ_VECTOR; + set_fs(get_fs()); +} diff --git a/arch/arm26/kernel/head.S b/arch/arm26/kernel/head.S new file mode 100644 index 00000000000..8bfc62539ba --- /dev/null +++ b/arch/arm26/kernel/head.S @@ -0,0 +1,113 @@ +/* + * linux/arch/arm26/kernel/head.S + * + * Copyright (C) 1994-2000 Russell King + * Copyright (C) 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 26-bit kernel startup code + */ +#include <linux/config.h> +#include <linux/linkage.h> +#include <asm/mach-types.h> + + .globl swapper_pg_dir + .equ swapper_pg_dir, 0x0207d000 + +/* + * Entry point. + */ + .section ".init.text",#alloc,#execinstr +ENTRY(stext) + +__entry: + cmp pc, #0x02000000 + ldrlt pc, LC0 @ if 0x01800000, call at 0x02080000 + teq r0, #0 @ Check for old calling method + blne oldparams @ Move page if old + + adr r0, LC0 + ldmib r0, {r2-r5, sp} @ Setup stack (and fetch other values) + + mov r0, #0 @ Clear BSS +1: cmp r2, r3 + strcc r0, [r2], #4 + bcc 1b + + bl detect_proc_type + str r0, [r4] + bl detect_arch_type + str r0, [r5] + +#ifdef CONFIG_XIP_KERNEL + ldr r3, ETEXT @ data section copy + ldr r4, SDATA + ldr r5, EDATA +1: + ldr r6, [r3], #4 + str r6, [r4], #4 + cmp r4, r5 + blt 1b +#endif + mov fp, #0 + b start_kernel + +LC0: .word _stext + .word __bss_start @ r2 + .word _end @ r3 + .word processor_id @ r4 + .word __machine_arch_type @ r5 + .word init_thread_union+8192 @ sp +#ifdef CONFIG_XIP_KERNEL +ETEXT: .word _endtext +SDATA: .word _sdata +EDATA: .word __bss_start +#endif + +arm2_id: .long 0x41560200 @ ARM2 and 250 dont have a CPUID +arm250_id: .long 0x41560250 @ So we create some after probing for them + .align + +oldparams: mov r4, #0x02000000 + add r3, r4, #0x00080000 + add r4, r4, #0x0007c000 +1: ldmia r0!, {r5 - r12} + stmia r4!, {r5 - r12} + cmp r4, r3 + blt 1b + mov pc, lr + +/* + * We need some way to automatically detect the difference between + * these two machines. Unfortunately, it is not possible to detect + * the presence of the SuperIO chip, because that will hang the old + * Archimedes machines solid. + */ +/* DAG: Outdated, these have been combined !!!!!!! */ +detect_arch_type: +#if defined(CONFIG_ARCH_ARC) + mov r0, #MACH_TYPE_ARCHIMEDES +#elif defined(CONFIG_ARCH_A5K) + mov r0, #MACH_TYPE_A5K +#endif + mov pc, lr + +detect_proc_type: + mov ip, lr + mov r2, #0xea000000 @ Point undef instr to continuation + adr r0, continue - 12 + orr r0, r2, r0, lsr #2 + mov r1, #0 + str r0, [r1, #4] + ldr r0, arm2_id + swp r2, r2, [r1] @ check for swp (ARM2 cant) + ldr r0, arm250_id + mrc 15, 0, r3, c0, c0 @ check for CP#15 (ARM250 cant) + mov r0, r3 +continue: mov r2, #0xeb000000 @ Make undef vector loop + sub r2, r2, #2 + str r2, [r1, #4] + mov pc, ip diff --git a/arch/arm26/kernel/init_task.c b/arch/arm26/kernel/init_task.c new file mode 100644 index 00000000000..4191565b889 --- /dev/null +++ b/arch/arm26/kernel/init_task.c @@ -0,0 +1,49 @@ +/* + * linux/arch/arm26/kernel/init_task.c + * + * Copyright (C) 2003 Ian Molton + * + */ +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/init_task.h> +#include <linux/mqueue.h> + +#include <asm/uaccess.h> +#include <asm/pgtable.h> + +static struct fs_struct init_fs = INIT_FS; +static struct files_struct init_files = INIT_FILES; +static struct signal_struct init_signals = INIT_SIGNALS(init_signals); +static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand); +struct mm_struct init_mm = INIT_MM(init_mm); + +EXPORT_SYMBOL(init_mm); + +/* + * Initial thread structure. + * + * We need to make sure that this is 8192-byte aligned due to the + * way process stacks are handled. This is done by making sure + * the linker maps this in the .text segment right after head.S, + * and making the linker scripts ensure the proper alignment. + * + * FIXME - should this be 32K alignment on arm26? + * + * The things we do for performance... + */ +union thread_union init_thread_union + __attribute__((__section__(".init.task"))) = + { INIT_THREAD_INFO(init_task) }; + +/* + * Initial task structure. + * + * All other task structs will be allocated on slabs in fork.c + */ +struct task_struct init_task = INIT_TASK(init_task); + +EXPORT_SYMBOL(init_task); diff --git a/arch/arm26/kernel/irq.c b/arch/arm26/kernel/irq.c new file mode 100644 index 00000000000..f3cc1036e5b --- /dev/null +++ b/arch/arm26/kernel/irq.c @@ -0,0 +1,716 @@ +/* + * linux/arch/arm/kernel/irq.c + * + * Copyright (C) 1992 Linus Torvalds + * Modifications for ARM processor Copyright (C) 1995-2000 Russell King. + * 'Borrowed' for ARM26 and (C) 2003 Ian Molton. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * 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/module.h> +#include <linux/ptrace.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/seq_file.h> +#include <linux/errno.h> + +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/irqchip.h> + +//FIXME - this ought to be in a header IMO +void __init arc_init_irq(void); + +/* + * Maximum IRQ count. Currently, this is arbitary. However, it should + * not be set too low to prevent false triggering. Conversely, if it + * is set too high, then you could miss a stuck IRQ. + * + * FIXME Maybe we ought to set a timer and re-enable the IRQ at a later time? + */ +#define MAX_IRQ_CNT 100000 + +static volatile unsigned long irq_err_count; +static DEFINE_SPINLOCK(irq_controller_lock); + +struct irqdesc irq_desc[NR_IRQS]; + +/* + * Dummy mask/unmask handler + */ +void dummy_mask_unmask_irq(unsigned int irq) +{ +} + +void do_bad_IRQ(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs) +{ + irq_err_count += 1; + printk(KERN_ERR "IRQ: spurious interrupt %d\n", irq); +} + +static struct irqchip bad_chip = { + .ack = dummy_mask_unmask_irq, + .mask = dummy_mask_unmask_irq, + .unmask = dummy_mask_unmask_irq, +}; + +static struct irqdesc bad_irq_desc = { + .chip = &bad_chip, + .handle = do_bad_IRQ, + .depth = 1, +}; + +/** + * disable_irq - disable an irq and wait for completion + * @irq: Interrupt to disable + * + * Disable the selected interrupt line. We do this lazily. + * + * This function may be called from IRQ context. + */ +void disable_irq(unsigned int irq) +{ + struct irqdesc *desc = irq_desc + irq; + unsigned long flags; + spin_lock_irqsave(&irq_controller_lock, flags); + if (!desc->depth++) + desc->enabled = 0; + spin_unlock_irqrestore(&irq_controller_lock, flags); +} + +/** + * enable_irq - enable interrupt handling on an irq + * @irq: Interrupt to enable + * + * Re-enables the processing of interrupts on this IRQ line. + * Note that this may call the interrupt handler, so you may + * get unexpected results if you hold IRQs disabled. + * + * This function may be called from IRQ context. + */ +void enable_irq(unsigned int irq) +{ + struct irqdesc *desc = irq_desc + irq; + unsigned long flags; + int pending = 0; + + spin_lock_irqsave(&irq_controller_lock, flags); + if (unlikely(!desc->depth)) { + printk("enable_irq(%u) unbalanced from %p\n", irq, + __builtin_return_address(0)); //FIXME bum addresses reported - why? + } else if (!--desc->depth) { + desc->probing = 0; + desc->enabled = 1; + desc->chip->unmask(irq); + pending = desc->pending; + desc->pending = 0; + /* + * If the interrupt was waiting to be processed, + * retrigger it. + */ + if (pending) + desc->chip->rerun(irq); + } + spin_unlock_irqrestore(&irq_controller_lock, flags); +} + +int show_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v; + struct irqaction * action; + + if (i < NR_IRQS) { + action = irq_desc[i].action; + if (!action) + continue; + seq_printf(p, "%3d: %10u ", i, kstat_irqs(i)); + seq_printf(p, " %s", action->name); + for (action = action->next; action; action = action->next) { + seq_printf(p, ", %s", action->name); + } + seq_putc(p, '\n'); + } else if (i == NR_IRQS) { + show_fiq_list(p, v); + seq_printf(p, "Err: %10lu\n", irq_err_count); + } + return 0; +} + +/* + * IRQ lock detection. + * + * Hopefully, this should get us out of a few locked situations. + * However, it may take a while for this to happen, since we need + * a large number if IRQs to appear in the same jiffie with the + * same instruction pointer (or within 2 instructions). + */ +static int check_irq_lock(struct irqdesc *desc, int irq, struct pt_regs *regs) +{ + unsigned long instr_ptr = instruction_pointer(regs); + + if (desc->lck_jif == jiffies && + desc->lck_pc >= instr_ptr && desc->lck_pc < instr_ptr + 8) { + desc->lck_cnt += 1; + + if (desc->lck_cnt > MAX_IRQ_CNT) { + printk(KERN_ERR "IRQ LOCK: IRQ%d is locking the system, disabled\n", irq); + return 1; + } + } else { + desc->lck_cnt = 0; + desc->lck_pc = instruction_pointer(regs); + desc->lck_jif = jiffies; + } + return 0; +} + +static void +__do_irq(unsigned int irq, struct irqaction *action, struct pt_regs *regs) +{ + unsigned int status; + int ret; + + spin_unlock(&irq_controller_lock); + if (!(action->flags & SA_INTERRUPT)) + local_irq_enable(); + + status = 0; + do { + ret = action->handler(irq, action->dev_id, regs); + if (ret == IRQ_HANDLED) + status |= action->flags; + action = action->next; + } while (action); + + if (status & SA_SAMPLE_RANDOM) + add_interrupt_randomness(irq); + + spin_lock_irq(&irq_controller_lock); +} + +/* + * This is for software-decoded IRQs. The caller is expected to + * handle the ack, clear, mask and unmask issues. + */ +void +do_simple_IRQ(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs) +{ + struct irqaction *action; + const int cpu = smp_processor_id(); + + desc->triggered = 1; + + kstat_cpu(cpu).irqs[irq]++; + + action = desc->action; + if (action) + __do_irq(irq, desc->action, regs); +} + +/* + * Most edge-triggered IRQ implementations seem to take a broken + * approach to this. Hence the complexity. + */ +void +do_edge_IRQ(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs) +{ + const int cpu = smp_processor_id(); + + desc->triggered = 1; + + /* + * If we're currently running this IRQ, or its disabled, + * we shouldn't process the IRQ. Instead, turn on the + * hardware masks. + */ + if (unlikely(desc->running || !desc->enabled)) + goto running; + + /* + * Acknowledge and clear the IRQ, but don't mask it. + */ + desc->chip->ack(irq); + + /* + * Mark the IRQ currently in progress. + */ + desc->running = 1; + + kstat_cpu(cpu).irqs[irq]++; + + do { + struct irqaction *action; + + action = desc->action; + if (!action) + break; + + if (desc->pending && desc->enabled) { + desc->pending = 0; + desc->chip->unmask(irq); + } + + __do_irq(irq, action, regs); + } while (desc->pending); + + desc->running = 0; + + /* + * If we were disabled or freed, shut down the handler. + */ + if (likely(desc->action && !check_irq_lock(desc, irq, regs))) + return; + + running: + /* + * We got another IRQ while this one was masked or + * currently running. Delay it. + */ + desc->pending = 1; + desc->chip->mask(irq); + desc->chip->ack(irq); +} + +/* + * Level-based IRQ handler. Nice and simple. + */ +void +do_level_IRQ(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs) +{ + struct irqaction *action; + const int cpu = smp_processor_id(); + + desc->triggered = 1; + + /* + * Acknowledge, clear _AND_ disable the interrupt. + */ + desc->chip->ack(irq); + + if (likely(desc->enabled)) { + kstat_cpu(cpu).irqs[irq]++; + + /* + * Return with this interrupt masked if no action + */ + action = desc->action; + if (action) { + __do_irq(irq, desc->action, regs); + + if (likely(desc->enabled && + !check_irq_lock(desc, irq, regs))) + desc->chip->unmask(irq); + } + } +} + +/* + * do_IRQ handles all hardware IRQ's. Decoded IRQs should not + * come via this function. Instead, they should provide their + * own 'handler' + */ +asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs) +{ + struct irqdesc *desc = irq_desc + irq; + + /* + * Some hardware gives randomly wrong interrupts. Rather + * than crashing, do something sensible. + */ + if (irq >= NR_IRQS) + desc = &bad_irq_desc; + + irq_enter(); + spin_lock(&irq_controller_lock); + desc->handle(irq, desc, regs); + spin_unlock(&irq_controller_lock); + irq_exit(); +} + +void __set_irq_handler(unsigned int irq, irq_handler_t handle, int is_chained) +{ + struct irqdesc *desc; + unsigned long flags; + + if (irq >= NR_IRQS) { + printk(KERN_ERR "Trying to install handler for IRQ%d\n", irq); + return; + } + + if (handle == NULL) + handle = do_bad_IRQ; + + desc = irq_desc + irq; + + if (is_chained && desc->chip == &bad_chip) + printk(KERN_WARNING "Trying to install chained handler for IRQ%d\n", irq); + + spin_lock_irqsave(&irq_controller_lock, flags); + if (handle == do_bad_IRQ) { + desc->chip->mask(irq); + desc->chip->ack(irq); + desc->depth = 1; + desc->enabled = 0; + } + desc->handle = handle; + if (handle != do_bad_IRQ && is_chained) { + desc->valid = 0; + desc->probe_ok = 0; + desc->depth = 0; + desc->chip->unmask(irq); + } + spin_unlock_irqrestore(&irq_controller_lock, flags); +} + +void set_irq_chip(unsigned int irq, struct irqchip *chip) +{ + struct irqdesc *desc; + unsigned long flags; + + if (irq >= NR_IRQS) { + printk(KERN_ERR "Trying to install chip for IRQ%d\n", irq); + return; + } + + if (chip == NULL) + chip = &bad_chip; + + desc = irq_desc + irq; + spin_lock_irqsave(&irq_controller_lock, flags); + desc->chip = chip; + spin_unlock_irqrestore(&irq_controller_lock, flags); +} + +int set_irq_type(unsigned int irq, unsigned int type) +{ + struct irqdesc *desc; + unsigned long flags; + int ret = -ENXIO; + + if (irq >= NR_IRQS) { + printk(KERN_ERR "Trying to set irq type for IRQ%d\n", irq); + return -ENODEV; + } + + desc = irq_desc + irq; + if (desc->chip->type) { + spin_lock_irqsave(&irq_controller_lock, flags); + ret = desc->chip->type(irq, type); + spin_unlock_irqrestore(&irq_controller_lock, flags); + } + + return ret; +} + +void set_irq_flags(unsigned int irq, unsigned int iflags) +{ + struct irqdesc *desc; + unsigned long flags; + + if (irq >= NR_IRQS) { + printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq); + return; + } + + desc = irq_desc + irq; + spin_lock_irqsave(&irq_controller_lock, flags); + desc->valid = (iflags & IRQF_VALID) != 0; + desc->probe_ok = (iflags & IRQF_PROBE) != 0; + desc->noautoenable = (iflags & IRQF_NOAUTOEN) != 0; + spin_unlock_irqrestore(&irq_controller_lock, flags); +} + +int setup_irq(unsigned int irq, struct irqaction *new) +{ + int shared = 0; + struct irqaction *old, **p; + unsigned long flags; + struct irqdesc *desc; + + /* + * Some drivers like serial.c use request_irq() heavily, + * so we have to be careful not to interfere with a + * running system. + */ + if (new->flags & SA_SAMPLE_RANDOM) { + /* + * This function might sleep, we want to call it first, + * outside of the atomic block. + * Yes, this might clear the entropy pool if the wrong + * driver is attempted to be loaded, without actually + * installing a new handler, but is this really a problem, + * only the sysadmin is able to do this. + */ + rand_initialize_irq(irq); + } + + /* + * The following block of code has to be executed atomically + */ + desc = irq_desc + irq; + spin_lock_irqsave(&irq_controller_lock, flags); + p = &desc->action; + if ((old = *p) != NULL) { + /* Can't share interrupts unless both agree to */ + if (!(old->flags & new->flags & SA_SHIRQ)) { + spin_unlock_irqrestore(&irq_controller_lock, flags); + return -EBUSY; + } + + /* add new interrupt at end of irq queue */ + do { + p = &old->next; + old = *p; + } while (old); + shared = 1; + } + + *p = new; + + if (!shared) { + desc->probing = 0; + desc->running = 0; + desc->pending = 0; + desc->depth = 1; + if (!desc->noautoenable) { + desc->depth = 0; + desc->enabled = 1; + desc->chip->unmask(irq); + } + } + + spin_unlock_irqrestore(&irq_controller_lock, flags); + return 0; +} + +/** + * request_irq - allocate an interrupt line + * @irq: Interrupt line to allocate + * @handler: Function to be called when the IRQ occurs + * @irqflags: Interrupt type flags + * @devname: An ascii name for the claiming device + * @dev_id: A cookie passed back to the handler function + * + * This call allocates interrupt resources and enables the + * interrupt line and IRQ handling. From the point this + * call is made your handler function may be invoked. Since + * your handler function must clear any interrupt the board + * raises, you must take care both to initialise your hardware + * and to set up the interrupt handler in the right order. + * + * Dev_id must be globally unique. Normally the address of the + * device data structure is used as the cookie. Since the handler + * receives this value it makes sense to use it. + * + * If your interrupt is shared you must pass a non NULL dev_id + * as this is required when freeing the interrupt. + * + * Flags: + * + * SA_SHIRQ Interrupt is shared + * + * SA_INTERRUPT Disable local interrupts while processing + * + * SA_SAMPLE_RANDOM The interrupt can be used for entropy + * + */ + +//FIXME - handler used to return void - whats the significance of the change? +int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), + unsigned long irq_flags, const char * devname, void *dev_id) +{ + unsigned long retval; + struct irqaction *action; + + if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler || + (irq_flags & SA_SHIRQ && !dev_id)) + return -EINVAL; + + action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL); + if (!action) + return -ENOMEM; + + action->handler = handler; + action->flags = irq_flags; + cpus_clear(action->mask); + action->name = devname; + action->next = NULL; + action->dev_id = dev_id; + + retval = setup_irq(irq, action); + + if (retval) + kfree(action); + return retval; +} + +EXPORT_SYMBOL(request_irq); + +/** + * free_irq - free an interrupt + * @irq: Interrupt line to free + * @dev_id: Device identity to free + * + * Remove an interrupt handler. The handler is removed and if the + * interrupt line is no longer in use by any driver it is disabled. + * On a shared IRQ the caller must ensure the interrupt is disabled + * on the card it drives before calling this function. + * + * This function may be called from interrupt context. + */ +void free_irq(unsigned int irq, void *dev_id) +{ + struct irqaction * action, **p; + unsigned long flags; + + if (irq >= NR_IRQS || !irq_desc[irq].valid) { + printk(KERN_ERR "Trying to free IRQ%d\n",irq); +#ifdef CONFIG_DEBUG_ERRORS + __backtrace(); +#endif + return; + } + + spin_lock_irqsave(&irq_controller_lock, flags); + for (p = &irq_desc[irq].action; (action = *p) != NULL; p = &action->next) { + if (action->dev_id != dev_id) + continue; + + /* Found it - now free it */ + *p = action->next; + kfree(action); + goto out; + } + printk(KERN_ERR "Trying to free free IRQ%d\n",irq); +#ifdef CONFIG_DEBUG_ERRORS + __backtrace(); +#endif +out: + spin_unlock_irqrestore(&irq_controller_lock, flags); +} + +EXPORT_SYMBOL(free_irq); + +/* Start the interrupt probing. Unlike other architectures, + * we don't return a mask of interrupts from probe_irq_on, + * but return the number of interrupts enabled for the probe. + * The interrupts which have been enabled for probing is + * instead recorded in the irq_desc structure. + */ +unsigned long probe_irq_on(void) +{ + unsigned int i, irqs = 0; + unsigned long delay; + + /* + * first snaffle up any unassigned but + * probe-able interrupts + */ + spin_lock_irq(&irq_controller_lock); + for (i = 0; i < NR_IRQS; i++) { + if (!irq_desc[i].probe_ok || irq_desc[i].action) + continue; + + irq_desc[i].probing = 1; + irq_desc[i].triggered = 0; + if (irq_desc[i].chip->type) + irq_desc[i].chip->type(i, IRQT_PROBE); + irq_desc[i].chip->unmask(i); + irqs += 1; + } + spin_unlock_irq(&irq_controller_lock); + + /* + * wait for spurious interrupts to mask themselves out again + */ + for (delay = jiffies + HZ/10; time_before(jiffies, delay); ) + /* min 100ms delay */; + + /* + * now filter out any obviously spurious interrupts + */ + spin_lock_irq(&irq_controller_lock); + for (i = 0; i < NR_IRQS; i++) { + if (irq_desc[i].probing && irq_desc[i].triggered) { + irq_desc[i].probing = 0; + irqs -= 1; + } + } + spin_unlock_irq(&irq_controller_lock); + + return irqs; +} + +EXPORT_SYMBOL(probe_irq_on); + +/* + * Possible return values: + * >= 0 - interrupt number + * -1 - no interrupt/many interrupts + */ +int probe_irq_off(unsigned long irqs) +{ + unsigned int i; + int irq_found = NO_IRQ; + + /* + * look at the interrupts, and find exactly one + * that we were probing has been triggered + */ + spin_lock_irq(&irq_controller_lock); + for (i = 0; i < NR_IRQS; i++) { + if (irq_desc[i].probing && + irq_desc[i].triggered) { + if (irq_found != NO_IRQ) { + irq_found = NO_IRQ; + goto out; + } + irq_found = i; + } + } + + if (irq_found == -1) + irq_found = NO_IRQ; +out: + spin_unlock_irq(&irq_controller_lock); + + return irq_found; +} + +EXPORT_SYMBOL(probe_irq_off); + +void __init init_irq_proc(void) +{ +} + +void __init init_IRQ(void) +{ + struct irqdesc *desc; + extern void init_dma(void); + int irq; + + for (irq = 0, desc = irq_desc; irq < NR_IRQS; irq++, desc++) + *desc = bad_irq_desc; + + arc_init_irq(); + init_dma(); +} diff --git a/arch/arm26/kernel/process.c b/arch/arm26/kernel/process.c new file mode 100644 index 00000000000..46aea6ac194 --- /dev/null +++ b/arch/arm26/kernel/process.c @@ -0,0 +1,401 @@ +/* + * linux/arch/arm26/kernel/process.c + * + * Copyright (C) 2003 Ian Molton - adapted for ARM26 + * Copyright (C) 1996-2000 Russell King - Converted to ARM. + * Origional Copyright (C) 1995 Linus Torvalds + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <stdarg.h> + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/delay.h> +#include <linux/reboot.h> +#include <linux/interrupt.h> +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/leds.h> +#include <asm/processor.h> +#include <asm/uaccess.h> + +extern const char *processor_modes[]; +extern void setup_mm_for_reboot(char mode); + +static volatile int hlt_counter; + +void disable_hlt(void) +{ + hlt_counter++; +} + +EXPORT_SYMBOL(disable_hlt); + +void enable_hlt(void) +{ + hlt_counter--; +} + +EXPORT_SYMBOL(enable_hlt); + +static int __init nohlt_setup(char *__unused) +{ + hlt_counter = 1; + return 1; +} + +static int __init hlt_setup(char *__unused) +{ + hlt_counter = 0; + return 1; +} + +__setup("nohlt", nohlt_setup); +__setup("hlt", hlt_setup); + +/* + * This is our default idle handler. We need to disable + * interrupts here to ensure we don't miss a wakeup call. + */ +void cpu_idle(void) +{ + /* endless idle loop with no priority at all */ + preempt_disable(); + while (1) { + while (!need_resched()) { + local_irq_disable(); + if (!need_resched() && !hlt_counter) + local_irq_enable(); + } + } + schedule(); +} + +static char reboot_mode = 'h'; + +int __init reboot_setup(char *str) +{ + reboot_mode = str[0]; + return 1; +} + +__setup("reboot=", reboot_setup); + +/* ARM26 cant do these but we still need to define them. */ +void machine_halt(void) +{ +} +void machine_power_off(void) +{ +} + +EXPORT_SYMBOL(machine_halt); +EXPORT_SYMBOL(machine_power_off); + +void machine_restart(char * __unused) +{ + /* + * Clean and disable cache, and turn off interrupts + */ + cpu_proc_fin(); + + /* + * Tell the mm system that we are going to reboot - + * we may need it to insert some 1:1 mappings so that + * soft boot works. + */ + setup_mm_for_reboot(reboot_mode); + + /* + * copy branch instruction to reset location and call it + */ + + *(unsigned long *)0 = *(unsigned long *)0x03800000; + ((void(*)(void))0)(); + + /* + * Whoops - the architecture was unable to reboot. + * Tell the user! Should never happen... + */ + mdelay(1000); + printk("Reboot failed -- System halted\n"); + while (1); +} + +EXPORT_SYMBOL(machine_restart); + +void show_regs(struct pt_regs * regs) +{ + unsigned long flags; + + flags = condition_codes(regs); + + printk("pc : [<%08lx>] lr : [<%08lx>] %s\n" + "sp : %08lx ip : %08lx fp : %08lx\n", + instruction_pointer(regs), + regs->ARM_lr, print_tainted(), regs->ARM_sp, + regs->ARM_ip, regs->ARM_fp); + printk("r10: %08lx r9 : %08lx r8 : %08lx\n", + regs->ARM_r10, regs->ARM_r9, + regs->ARM_r8); + printk("r7 : %08lx r6 : %08lx r5 : %08lx r4 : %08lx\n", + regs->ARM_r7, regs->ARM_r6, + regs->ARM_r5, regs->ARM_r4); + printk("r3 : %08lx r2 : %08lx r1 : %08lx r0 : %08lx\n", + regs->ARM_r3, regs->ARM_r2, + regs->ARM_r1, regs->ARM_r0); + printk("Flags: %c%c%c%c", + flags & PSR_N_BIT ? 'N' : 'n', + flags & PSR_Z_BIT ? 'Z' : 'z', + flags & PSR_C_BIT ? 'C' : 'c', + flags & PSR_V_BIT ? 'V' : 'v'); + printk(" IRQs o%s FIQs o%s Mode %s Segment %s\n", + interrupts_enabled(regs) ? "n" : "ff", + fast_interrupts_enabled(regs) ? "n" : "ff", + processor_modes[processor_mode(regs)], + get_fs() == get_ds() ? "kernel" : "user"); +} + +void show_fpregs(struct user_fp *regs) +{ + int i; + + for (i = 0; i < 8; i++) { + unsigned long *p; + char type; + + p = (unsigned long *)(regs->fpregs + i); + + switch (regs->ftype[i]) { + case 1: type = 'f'; break; + case 2: type = 'd'; break; + case 3: type = 'e'; break; + default: type = '?'; break; + } + if (regs->init_flag) + type = '?'; + + printk(" f%d(%c): %08lx %08lx %08lx%c", + i, type, p[0], p[1], p[2], i & 1 ? '\n' : ' '); + } + + + printk("FPSR: %08lx FPCR: %08lx\n", + (unsigned long)regs->fpsr, + (unsigned long)regs->fpcr); +} + +/* + * Task structure and kernel stack allocation. + */ +static unsigned long *thread_info_head; +static unsigned int nr_thread_info; + +extern unsigned long get_page_8k(int priority); +extern void free_page_8k(unsigned long page); + +// FIXME - is this valid? +#define EXTRA_TASK_STRUCT 0 +#define ll_alloc_task_struct() ((struct thread_info *)get_page_8k(GFP_KERNEL)) +#define ll_free_task_struct(p) free_page_8k((unsigned long)(p)) + +//FIXME - do we use *task param below looks like we dont, which is ok? +//FIXME - if EXTRA_TASK_STRUCT is zero we can optimise the below away permanently. *IF* its supposed to be zero. +struct thread_info *alloc_thread_info(struct task_struct *task) +{ + struct thread_info *thread = NULL; + + if (EXTRA_TASK_STRUCT) { + unsigned long *p = thread_info_head; + + if (p) { + thread_info_head = (unsigned long *)p[0]; + nr_thread_info -= 1; + } + thread = (struct thread_info *)p; + } + + if (!thread) + thread = ll_alloc_task_struct(); + +#ifdef CONFIG_MAGIC_SYSRQ + /* + * The stack must be cleared if you want SYSRQ-T to + * give sensible stack usage information + */ + if (thread) { + char *p = (char *)thread; + memzero(p+KERNEL_STACK_SIZE, KERNEL_STACK_SIZE); + } +#endif + return thread; +} + +void free_thread_info(struct thread_info *thread) +{ + if (EXTRA_TASK_STRUCT && nr_thread_info < EXTRA_TASK_STRUCT) { + unsigned long *p = (unsigned long *)thread; + p[0] = (unsigned long)thread_info_head; + thread_info_head = p; + nr_thread_info += 1; + } else + ll_free_task_struct(thread); +} + +/* + * Free current thread data structures etc.. + */ +void exit_thread(void) +{ +} + +void flush_thread(void) +{ + struct thread_info *thread = current_thread_info(); + struct task_struct *tsk = current; + + memset(&tsk->thread.debug, 0, sizeof(struct debug_info)); + memset(&thread->fpstate, 0, sizeof(union fp_state)); + + clear_used_math(); +} + +void release_thread(struct task_struct *dead_task) +{ +} + +asmlinkage void ret_from_fork(void) __asm__("ret_from_fork"); + +int +copy_thread(int nr, unsigned long clone_flags, unsigned long stack_start, + unsigned long unused, struct task_struct *p, struct pt_regs *regs) +{ + struct thread_info *thread = p->thread_info; + struct pt_regs *childregs; + + childregs = __get_user_regs(thread); + *childregs = *regs; + childregs->ARM_r0 = 0; + childregs->ARM_sp = stack_start; + + memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save)); + thread->cpu_context.sp = (unsigned long)childregs; + thread->cpu_context.pc = (unsigned long)ret_from_fork | MODE_SVC26 | PSR_I_BIT; + + return 0; +} + +/* + * fill in the fpe structure for a core dump... + */ +int dump_fpu (struct pt_regs *regs, struct user_fp *fp) +{ + struct thread_info *thread = current_thread_info(); + int used_math = !!used_math(); + + if (used_math) + memcpy(fp, &thread->fpstate.soft, sizeof (*fp)); + + return used_math; +} + +/* + * fill in the user structure for a core dump.. + */ +void dump_thread(struct pt_regs * regs, struct user * dump) +{ + struct task_struct *tsk = current; + + dump->magic = CMAGIC; + dump->start_code = tsk->mm->start_code; + dump->start_stack = regs->ARM_sp & ~(PAGE_SIZE - 1); + + dump->u_tsize = (tsk->mm->end_code - tsk->mm->start_code) >> PAGE_SHIFT; + dump->u_dsize = (tsk->mm->brk - tsk->mm->start_data + PAGE_SIZE - 1) >> PAGE_SHIFT; + dump->u_ssize = 0; + + dump->u_debugreg[0] = tsk->thread.debug.bp[0].address; + dump->u_debugreg[1] = tsk->thread.debug.bp[1].address; + dump->u_debugreg[2] = tsk->thread.debug.bp[0].insn; + dump->u_debugreg[3] = tsk->thread.debug.bp[1].insn; + dump->u_debugreg[4] = tsk->thread.debug.nsaved; + + if (dump->start_stack < 0x04000000) + dump->u_ssize = (0x04000000 - dump->start_stack) >> PAGE_SHIFT; + + dump->regs = *regs; + dump->u_fpvalid = dump_fpu (regs, &dump->u_fp); +} + +/* + * Shuffle the argument into the correct register before calling the + * thread function. r1 is the thread argument, r2 is the pointer to + * the thread function, and r3 points to the exit function. + * FIXME - make sure this is right - the older code used to zero fp + * and cause the parent to call sys_exit (do_exit in this version) + */ +extern void kernel_thread_helper(void); + +asm( ".section .text\n" +" .align\n" +" .type kernel_thread_helper, #function\n" +"kernel_thread_helper:\n" +" mov r0, r1\n" +" mov lr, r3\n" +" mov pc, r2\n" +" .size kernel_thread_helper, . - kernel_thread_helper\n" +" .previous"); + +/* + * Create a kernel thread. + */ +pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + + regs.ARM_r1 = (unsigned long)arg; + regs.ARM_r2 = (unsigned long)fn; + regs.ARM_r3 = (unsigned long)do_exit; + regs.ARM_pc = (unsigned long)kernel_thread_helper | MODE_SVC26; + + return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); +} +EXPORT_SYMBOL(kernel_thread); + + +unsigned long get_wchan(struct task_struct *p) +{ + unsigned long fp, lr; + unsigned long stack_page; + int count = 0; + if (!p || p == current || p->state == TASK_RUNNING) + return 0; + + stack_page = 4096 + (unsigned long)p; + fp = thread_saved_fp(p); + do { + if (fp < stack_page || fp > 4092+stack_page) + return 0; + lr = pc_pointer (((unsigned long *)fp)[-1]); + if (!in_sched_functions(lr)) + return lr; + fp = *(unsigned long *) (fp - 12); + } while (count ++ < 16); + return 0; +} diff --git a/arch/arm26/kernel/ptrace.c b/arch/arm26/kernel/ptrace.c new file mode 100644 index 00000000000..2a137146a77 --- /dev/null +++ b/arch/arm26/kernel/ptrace.c @@ -0,0 +1,744 @@ +/* + * linux/arch/arm26/kernel/ptrace.c + * + * By Ross Biro 1/23/92 + * edited by Linus Torvalds + * ARM modifications Copyright (C) 2000 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/ptrace.h> +#include <linux/user.h> +#include <linux/security.h> + +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/system.h> +//#include <asm/processor.h> + +#include "ptrace.h" + +#define REG_PC 15 +#define REG_PSR 15 +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + +/* + * Breakpoint SWI instruction: SWI &9F0001 + */ +#define BREAKINST_ARM 0xef9f0001 + +/* + * Get the address of the live pt_regs for the specified task. + * These are saved onto the top kernel stack when the process + * is not running. + * + * Note: if a user thread is execve'd from kernel space, the + * kernel stack will not be empty on entry to the kernel, so + * ptracing these tasks will fail. + */ +static inline struct pt_regs * +get_user_regs(struct task_struct *task) +{ + return __get_user_regs(task->thread_info); +} + +/* + * this routine will get a word off of the processes privileged stack. + * the offset is how far from the base addr as stored in the THREAD. + * this routine assumes that all the privileged stacks are in our + * data space. + */ +static inline long get_user_reg(struct task_struct *task, int offset) +{ + return get_user_regs(task)->uregs[offset]; +} + +/* + * this routine will put a word on the processes privileged stack. + * the offset is how far from the base addr as stored in the THREAD. + * this routine assumes that all the privileged stacks are in our + * data space. + */ +static inline int +put_user_reg(struct task_struct *task, int offset, long data) +{ + struct pt_regs newregs, *regs = get_user_regs(task); + int ret = -EINVAL; + + newregs = *regs; + newregs.uregs[offset] = data; + + if (valid_user_regs(&newregs)) { + regs->uregs[offset] = data; + ret = 0; + } + + return ret; +} + +static inline int +read_u32(struct task_struct *task, unsigned long addr, u32 *res) +{ + int ret; + + ret = access_process_vm(task, addr, res, sizeof(*res), 0); + + return ret == sizeof(*res) ? 0 : -EIO; +} + +static inline int +read_instr(struct task_struct *task, unsigned long addr, u32 *res) +{ + int ret; + u32 val; + ret = access_process_vm(task, addr & ~3, &val, sizeof(val), 0); + ret = ret == sizeof(val) ? 0 : -EIO; + *res = val; + return ret; +} + +/* + * Get value of register `rn' (in the instruction) + */ +static unsigned long +ptrace_getrn(struct task_struct *child, unsigned long insn) +{ + unsigned int reg = (insn >> 16) & 15; + unsigned long val; + + val = get_user_reg(child, reg); + if (reg == 15) + val = pc_pointer(val + 8); //FIXME - correct for arm26? + + return val; +} + +/* + * Get value of operand 2 (in an ALU instruction) + */ +static unsigned long +ptrace_getaluop2(struct task_struct *child, unsigned long insn) +{ + unsigned long val; + int shift; + int type; + + if (insn & 1 << 25) { + val = insn & 255; + shift = (insn >> 8) & 15; + type = 3; + } else { + val = get_user_reg (child, insn & 15); + + if (insn & (1 << 4)) + shift = (int)get_user_reg (child, (insn >> 8) & 15); + else + shift = (insn >> 7) & 31; + + type = (insn >> 5) & 3; + } + + switch (type) { + case 0: val <<= shift; break; + case 1: val >>= shift; break; + case 2: + val = (((signed long)val) >> shift); + break; + case 3: + val = (val >> shift) | (val << (32 - shift)); + break; + } + return val; +} + +/* + * Get value of operand 2 (in a LDR instruction) + */ +static unsigned long +ptrace_getldrop2(struct task_struct *child, unsigned long insn) +{ + unsigned long val; + int shift; + int type; + + val = get_user_reg(child, insn & 15); + shift = (insn >> 7) & 31; + type = (insn >> 5) & 3; + + switch (type) { + case 0: val <<= shift; break; + case 1: val >>= shift; break; + case 2: + val = (((signed long)val) >> shift); + break; + case 3: + val = (val >> shift) | (val << (32 - shift)); + break; + } + return val; +} + +#define OP_MASK 0x01e00000 +#define OP_AND 0x00000000 +#define OP_EOR 0x00200000 +#define OP_SUB 0x00400000 +#define OP_RSB 0x00600000 +#define OP_ADD 0x00800000 +#define OP_ADC 0x00a00000 +#define OP_SBC 0x00c00000 +#define OP_RSC 0x00e00000 +#define OP_ORR 0x01800000 +#define OP_MOV 0x01a00000 +#define OP_BIC 0x01c00000 +#define OP_MVN 0x01e00000 + +static unsigned long +get_branch_address(struct task_struct *child, unsigned long pc, unsigned long insn) +{ + u32 alt = 0; + + switch (insn & 0x0e000000) { + case 0x00000000: + case 0x02000000: { + /* + * data processing + */ + long aluop1, aluop2, ccbit; + + if ((insn & 0xf000) != 0xf000) + break; + + aluop1 = ptrace_getrn(child, insn); + aluop2 = ptrace_getaluop2(child, insn); + ccbit = get_user_reg(child, REG_PSR) & PSR_C_BIT ? 1 : 0; + + switch (insn & OP_MASK) { + case OP_AND: alt = aluop1 & aluop2; break; + case OP_EOR: alt = aluop1 ^ aluop2; break; + case OP_SUB: alt = aluop1 - aluop2; break; + case OP_RSB: alt = aluop2 - aluop1; break; + case OP_ADD: alt = aluop1 + aluop2; break; + case OP_ADC: alt = aluop1 + aluop2 + ccbit; break; + case OP_SBC: alt = aluop1 - aluop2 + ccbit; break; + case OP_RSC: alt = aluop2 - aluop1 + ccbit; break; + case OP_ORR: alt = aluop1 | aluop2; break; + case OP_MOV: alt = aluop2; break; + case OP_BIC: alt = aluop1 & ~aluop2; break; + case OP_MVN: alt = ~aluop2; break; + } + break; + } + + case 0x04000000: + case 0x06000000: + /* + * ldr + */ + if ((insn & 0x0010f000) == 0x0010f000) { + unsigned long base; + + base = ptrace_getrn(child, insn); + if (insn & 1 << 24) { + long aluop2; + + if (insn & 0x02000000) + aluop2 = ptrace_getldrop2(child, insn); + else + aluop2 = insn & 0xfff; + + if (insn & 1 << 23) + base += aluop2; + else + base -= aluop2; + } + if (read_u32(child, base, &alt) == 0) + alt = pc_pointer(alt); + } + break; + + case 0x08000000: + /* + * ldm + */ + if ((insn & 0x00108000) == 0x00108000) { + unsigned long base; + unsigned int nr_regs; + + if (insn & (1 << 23)) { + nr_regs = hweight16(insn & 65535) << 2; + + if (!(insn & (1 << 24))) + nr_regs -= 4; + } else { + if (insn & (1 << 24)) + nr_regs = -4; + else + nr_regs = 0; + } + + base = ptrace_getrn(child, insn); + + if (read_u32(child, base + nr_regs, &alt) == 0) + alt = pc_pointer(alt); + break; + } + break; + + case 0x0a000000: { + /* + * bl or b + */ + signed long displ; + /* It's a branch/branch link: instead of trying to + * figure out whether the branch will be taken or not, + * we'll put a breakpoint at both locations. This is + * simpler, more reliable, and probably not a whole lot + * slower than the alternative approach of emulating the + * branch. + */ + displ = (insn & 0x00ffffff) << 8; + displ = (displ >> 6) + 8; + if (displ != 0 && displ != 4) + alt = pc + displ; + } + break; + } + + return alt; +} + +static int +swap_insn(struct task_struct *task, unsigned long addr, + void *old_insn, void *new_insn, int size) +{ + int ret; + + ret = access_process_vm(task, addr, old_insn, size, 0); + if (ret == size) + ret = access_process_vm(task, addr, new_insn, size, 1); + return ret; +} + +static void +add_breakpoint(struct task_struct *task, struct debug_info *dbg, unsigned long addr) +{ + int nr = dbg->nsaved; + + if (nr < 2) { + u32 new_insn = BREAKINST_ARM; + int res; + + res = swap_insn(task, addr, &dbg->bp[nr].insn, &new_insn, 4); + + if (res == 4) { + dbg->bp[nr].address = addr; + dbg->nsaved += 1; + } + } else + printk(KERN_ERR "ptrace: too many breakpoints\n"); +} + +/* + * Clear one breakpoint in the user program. We copy what the hardware + * does and use bit 0 of the address to indicate whether this is a Thumb + * breakpoint or an ARM breakpoint. + */ +static void clear_breakpoint(struct task_struct *task, struct debug_entry *bp) +{ + unsigned long addr = bp->address; + u32 old_insn; + int ret; + + ret = swap_insn(task, addr & ~3, &old_insn, + &bp->insn, 4); + + if (ret != 4 || old_insn != BREAKINST_ARM) + printk(KERN_ERR "%s:%d: corrupted ARM breakpoint at " + "0x%08lx (0x%08x)\n", task->comm, task->pid, + addr, old_insn); +} + +void ptrace_set_bpt(struct task_struct *child) +{ + struct pt_regs *regs; + unsigned long pc; + u32 insn; + int res; + + regs = get_user_regs(child); + pc = instruction_pointer(regs); + + res = read_instr(child, pc, &insn); + if (!res) { + struct debug_info *dbg = &child->thread.debug; + unsigned long alt; + + dbg->nsaved = 0; + + alt = get_branch_address(child, pc, insn); + if (alt) + add_breakpoint(child, dbg, alt); + + /* + * Note that we ignore the result of setting the above + * breakpoint since it may fail. When it does, this is + * not so much an error, but a forewarning that we may + * be receiving a prefetch abort shortly. + * + * If we don't set this breakpoint here, then we can + * lose control of the thread during single stepping. + */ + if (!alt || predicate(insn) != PREDICATE_ALWAYS) + add_breakpoint(child, dbg, pc + 4); + } +} + +/* + * Ensure no single-step breakpoint is pending. Returns non-zero + * value if child was being single-stepped. + */ +void ptrace_cancel_bpt(struct task_struct *child) +{ + int i, nsaved = child->thread.debug.nsaved; + + child->thread.debug.nsaved = 0; + + if (nsaved > 2) { + printk("ptrace_cancel_bpt: bogus nsaved: %d!\n", nsaved); + nsaved = 2; + } + + for (i = 0; i < nsaved; i++) + clear_breakpoint(child, &child->thread.debug.bp[i]); +} + +/* + * Called by kernel/ptrace.c when detaching.. + * + * Make sure the single step bit is not set. + */ +void ptrace_disable(struct task_struct *child) +{ + child->ptrace &= ~PT_SINGLESTEP; + ptrace_cancel_bpt(child); +} + +/* + * Handle hitting a breakpoint. + */ +void ptrace_break(struct task_struct *tsk, struct pt_regs *regs) +{ + siginfo_t info; + + /* + * The PC is always left pointing at the next instruction. Fix this. + */ + regs->ARM_pc -= 4; + + if (tsk->thread.debug.nsaved == 0) + printk(KERN_ERR "ptrace: bogus breakpoint trap\n"); + + ptrace_cancel_bpt(tsk); + + info.si_signo = SIGTRAP; + info.si_errno = 0; + info.si_code = TRAP_BRKPT; + info.si_addr = (void *)instruction_pointer(regs) - 4; + + force_sig_info(SIGTRAP, &info, tsk); +} + +/* + * Read the word at offset "off" into the "struct user". We + * actually access the pt_regs stored on the kernel stack. + */ +static int ptrace_read_user(struct task_struct *tsk, unsigned long off, + unsigned long *ret) +{ + unsigned long tmp; + + if (off & 3 || off >= sizeof(struct user)) + return -EIO; + + tmp = 0; + if (off < sizeof(struct pt_regs)) + tmp = get_user_reg(tsk, off >> 2); + + return put_user(tmp, ret); +} + +/* + * Write the word at offset "off" into "struct user". We + * actually access the pt_regs stored on the kernel stack. + */ +static int ptrace_write_user(struct task_struct *tsk, unsigned long off, + unsigned long val) +{ + if (off & 3 || off >= sizeof(struct user)) + return -EIO; + + if (off >= sizeof(struct pt_regs)) + return 0; + + return put_user_reg(tsk, off >> 2, val); +} + +/* + * Get all user integer registers. + */ +static int ptrace_getregs(struct task_struct *tsk, void *uregs) +{ + struct pt_regs *regs = get_user_regs(tsk); + + return copy_to_user(uregs, regs, sizeof(struct pt_regs)) ? -EFAULT : 0; +} + +/* + * Set all user integer registers. + */ +static int ptrace_setregs(struct task_struct *tsk, void *uregs) +{ + struct pt_regs newregs; + int ret; + + ret = -EFAULT; + if (copy_from_user(&newregs, uregs, sizeof(struct pt_regs)) == 0) { + struct pt_regs *regs = get_user_regs(tsk); + + ret = -EINVAL; + if (valid_user_regs(&newregs)) { + *regs = newregs; + ret = 0; + } + } + + return ret; +} + +/* + * Get the child FPU state. + */ +static int ptrace_getfpregs(struct task_struct *tsk, void *ufp) +{ + return copy_to_user(ufp, &tsk->thread_info->fpstate, + sizeof(struct user_fp)) ? -EFAULT : 0; +} + +/* + * Set the child FPU state. + */ +static int ptrace_setfpregs(struct task_struct *tsk, void *ufp) +{ + set_stopped_child_used_math(tsk); + return copy_from_user(&tsk->thread_info->fpstate, ufp, + sizeof(struct user_fp)) ? -EFAULT : 0; +} + +static int do_ptrace(int request, struct task_struct *child, long addr, long data) +{ + unsigned long tmp; + int ret; + + switch (request) { + /* + * read word at location "addr" in the child process. + */ + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: + ret = access_process_vm(child, addr, &tmp, + sizeof(unsigned long), 0); + if (ret == sizeof(unsigned long)) + ret = put_user(tmp, (unsigned long *) data); + else + ret = -EIO; + break; + + case PTRACE_PEEKUSR: + ret = ptrace_read_user(child, addr, (unsigned long *)data); + break; + + /* + * write the word at location addr. + */ + case PTRACE_POKETEXT: + case PTRACE_POKEDATA: + ret = access_process_vm(child, addr, &data, + sizeof(unsigned long), 1); + if (ret == sizeof(unsigned long)) + ret = 0; + else + ret = -EIO; + break; + + case PTRACE_POKEUSR: + ret = ptrace_write_user(child, addr, data); + break; + + /* + * continue/restart and stop at next (return from) syscall + */ + case PTRACE_SYSCALL: + case PTRACE_CONT: + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + if (request == PTRACE_SYSCALL) + set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + else + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + child->exit_code = data; + /* make sure single-step breakpoint is gone. */ + child->ptrace &= ~PT_SINGLESTEP; + ptrace_cancel_bpt(child); + 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: + /* make sure single-step breakpoint is gone. */ + child->ptrace &= ~PT_SINGLESTEP; + ptrace_cancel_bpt(child); + if (child->exit_state != EXIT_ZOMBIE) { + child->exit_code = SIGKILL; + wake_up_process(child); + } + ret = 0; + break; + + /* + * execute single instruction. + */ + case PTRACE_SINGLESTEP: + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + child->ptrace |= PT_SINGLESTEP; + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + child->exit_code = data; + /* give it a chance to run. */ + wake_up_process(child); + ret = 0; + break; + + case PTRACE_DETACH: + ret = ptrace_detach(child, data); + break; + + case PTRACE_GETREGS: + ret = ptrace_getregs(child, (void *)data); + break; + + case PTRACE_SETREGS: + ret = ptrace_setregs(child, (void *)data); + break; + + case PTRACE_GETFPREGS: + ret = ptrace_getfpregs(child, (void *)data); + break; + + case PTRACE_SETFPREGS: + ret = ptrace_setfpregs(child, (void *)data); + break; + + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +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; + ret = security_ptrace(current->parent, current); + if (ret) + goto out; + /* set the ptrace bit in the process flags. */ + current->ptrace |= PT_PTRACED; + ret = 0; + goto out; + } + ret = -ESRCH; + read_lock(&tasklist_lock); + child = find_task_by_pid(pid); + if (child) + get_task_struct(child); + read_unlock(&tasklist_lock); + if (!child) + goto out; + + ret = -EPERM; + if (pid == 1) /* you may not mess with init */ + goto out_tsk; + + if (request == PTRACE_ATTACH) { + ret = ptrace_attach(child); + goto out_tsk; + } + ret = ptrace_check_attach(child, request == PTRACE_KILL); + if (ret == 0) + ret = do_ptrace(request, child, addr, data); + +out_tsk: + put_task_struct(child); +out: + unlock_kernel(); + return ret; +} + +asmlinkage void syscall_trace(int why, struct pt_regs *regs) +{ + unsigned long ip; + + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + if (!(current->ptrace & PT_PTRACED)) + return; + + /* + * Save IP. IP is used to denote syscall entry/exit: + * IP = 0 -> entry, = 1 -> exit + */ + ip = regs->ARM_ip; + regs->ARM_ip = why; + + /* the 0x80 provides a way for the tracing parent to distinguish + between a syscall stop and SIGTRAP delivery */ + ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) + ? 0x80 : 0)); + /* + * this isn't the same as continuing with a signal, but it will do + * for normal use. 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; + } + regs->ARM_ip = ip; +} diff --git a/arch/arm26/kernel/ptrace.h b/arch/arm26/kernel/ptrace.h new file mode 100644 index 00000000000..846c9d8d36e --- /dev/null +++ b/arch/arm26/kernel/ptrace.h @@ -0,0 +1,13 @@ +/* + * linux/arch/arm26/kernel/ptrace.h + * + * Copyright (C) 2000-2003 Russell King + * Copyright (C) 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +extern void ptrace_cancel_bpt(struct task_struct *); +extern void ptrace_set_bpt(struct task_struct *); +extern void ptrace_break(struct task_struct *, struct pt_regs *); diff --git a/arch/arm26/kernel/semaphore.c b/arch/arm26/kernel/semaphore.c new file mode 100644 index 00000000000..3023a53431f --- /dev/null +++ b/arch/arm26/kernel/semaphore.c @@ -0,0 +1,223 @@ +/* + * ARM semaphore implementation, taken from + * + * i386 semaphore implementation. + * + * (C) Copyright 1999 Linus Torvalds + * (C) Copyright 2003 Ian Molton (ARM26 mods) + * + * Modified for ARM by Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/init.h> + +#include <asm/semaphore.h> + +/* + * Semaphores are implemented using a two-way counter: + * The "count" variable is decremented for each process + * that tries to acquire the semaphore, while the "sleeping" + * variable is a count of such acquires. + * + * Notably, the inline "up()" and "down()" functions can + * efficiently test if they need to do any extra work (up + * needs to do something only if count was negative before + * the increment operation. + * + * "sleeping" and the contention routine ordering is + * protected by the semaphore spinlock. + * + * Note that these functions are only called when there is + * contention on the lock, and as such all this is the + * "non-critical" part of the whole semaphore business. The + * critical part is the inline stuff in <asm/semaphore.h> + * where we want to avoid any extra jumps and calls. + */ + +/* + * Logic: + * - only on a boundary condition do we need to care. When we go + * from a negative count to a non-negative, we wake people up. + * - when we go from a non-negative count to a negative do we + * (a) synchronize with the "sleeper" count and (b) make sure + * that we're on the wakeup list before we synchronize so that + * we cannot lose wakeup events. + */ + +void __up(struct semaphore *sem) +{ + wake_up(&sem->wait); +} + +static DEFINE_SPINLOCK(semaphore_lock); + +void __sched __down(struct semaphore * sem) +{ + struct task_struct *tsk = current; + DECLARE_WAITQUEUE(wait, tsk); + tsk->state = TASK_UNINTERRUPTIBLE; + add_wait_queue_exclusive(&sem->wait, &wait); + + spin_lock_irq(&semaphore_lock); + sem->sleepers++; + for (;;) { + int sleepers = sem->sleepers; + + /* + * Add "everybody else" into it. They aren't + * playing, because we own the spinlock. + */ + if (!atomic_add_negative(sleepers - 1, &sem->count)) { + sem->sleepers = 0; + break; + } + sem->sleepers = 1; /* us - see -1 above */ + spin_unlock_irq(&semaphore_lock); + + schedule(); + tsk->state = TASK_UNINTERRUPTIBLE; + spin_lock_irq(&semaphore_lock); + } + spin_unlock_irq(&semaphore_lock); + remove_wait_queue(&sem->wait, &wait); + tsk->state = TASK_RUNNING; + wake_up(&sem->wait); +} + +int __sched __down_interruptible(struct semaphore * sem) +{ + int retval = 0; + struct task_struct *tsk = current; + DECLARE_WAITQUEUE(wait, tsk); + tsk->state = TASK_INTERRUPTIBLE; + add_wait_queue_exclusive(&sem->wait, &wait); + + spin_lock_irq(&semaphore_lock); + sem->sleepers ++; + for (;;) { + int sleepers = sem->sleepers; + + /* + * With signals pending, this turns into + * the trylock failure case - we won't be + * sleeping, and we* can't get the lock as + * it has contention. Just correct the count + * and exit. + */ + if (signal_pending(current)) { + retval = -EINTR; + sem->sleepers = 0; + atomic_add(sleepers, &sem->count); + break; + } + + /* + * Add "everybody else" into it. They aren't + * playing, because we own the spinlock. The + * "-1" is because we're still hoping to get + * the lock. + */ + if (!atomic_add_negative(sleepers - 1, &sem->count)) { + sem->sleepers = 0; + break; + } + sem->sleepers = 1; /* us - see -1 above */ + spin_unlock_irq(&semaphore_lock); + + schedule(); + tsk->state = TASK_INTERRUPTIBLE; + spin_lock_irq(&semaphore_lock); + } + spin_unlock_irq(&semaphore_lock); + tsk->state = TASK_RUNNING; + remove_wait_queue(&sem->wait, &wait); + wake_up(&sem->wait); + return retval; +} + +/* + * Trylock failed - make sure we correct for + * having decremented the count. + * + * We could have done the trylock with a + * single "cmpxchg" without failure cases, + * but then it wouldn't work on a 386. + */ +int __down_trylock(struct semaphore * sem) +{ + int sleepers; + unsigned long flags; + + spin_lock_irqsave(&semaphore_lock, flags); + sleepers = sem->sleepers + 1; + sem->sleepers = 0; + + /* + * Add "everybody else" and us into it. They aren't + * playing, because we own the spinlock. + */ + if (!atomic_add_negative(sleepers, &sem->count)) + wake_up(&sem->wait); + + spin_unlock_irqrestore(&semaphore_lock, flags); + return 1; +} + +/* + * The semaphore operations have a special calling sequence that + * allow us to do a simpler in-line version of them. These routines + * need to convert that sequence back into the C sequence when + * there is contention on the semaphore. + * + * ip contains the semaphore pointer on entry. Save the C-clobbered + * registers (r0 to r3 and lr), but not ip, as we use it as a return + * value in some cases.. + */ +asm(" .section .sched.text , #alloc, #execinstr \n\ + .align 5 \n\ + .globl __down_failed \n\ +__down_failed: \n\ + stmfd sp!, {r0 - r3, lr} \n\ + mov r0, ip \n\ + bl __down \n\ + ldmfd sp!, {r0 - r3, pc}^ \n\ + \n\ + .align 5 \n\ + .globl __down_interruptible_failed \n\ +__down_interruptible_failed: \n\ + stmfd sp!, {r0 - r3, lr} \n\ + mov r0, ip \n\ + bl __down_interruptible \n\ + mov ip, r0 \n\ + ldmfd sp!, {r0 - r3, pc}^ \n\ + \n\ + .align 5 \n\ + .globl __down_trylock_failed \n\ +__down_trylock_failed: \n\ + stmfd sp!, {r0 - r3, lr} \n\ + mov r0, ip \n\ + bl __down_trylock \n\ + mov ip, r0 \n\ + ldmfd sp!, {r0 - r3, pc}^ \n\ + \n\ + .align 5 \n\ + .globl __up_wakeup \n\ +__up_wakeup: \n\ + stmfd sp!, {r0 - r3, lr} \n\ + mov r0, ip \n\ + bl __up \n\ + ldmfd sp!, {r0 - r3, pc}^ \n\ + "); + +EXPORT_SYMBOL(__down_failed); +EXPORT_SYMBOL(__down_interruptible_failed); +EXPORT_SYMBOL(__down_trylock_failed); +EXPORT_SYMBOL(__up_wakeup); + diff --git a/arch/arm26/kernel/setup.c b/arch/arm26/kernel/setup.c new file mode 100644 index 00000000000..4eb329e3828 --- /dev/null +++ b/arch/arm26/kernel/setup.c @@ -0,0 +1,573 @@ +/* + * linux/arch/arm26/kernel/setup.c + * + * Copyright (C) 1995-2001 Russell King + * Copyright (C) 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/utsname.h> +#include <linux/blkdev.h> +#include <linux/console.h> +#include <linux/bootmem.h> +#include <linux/seq_file.h> +#include <linux/tty.h> +#include <linux/init.h> +#include <linux/root_dev.h> + +#include <asm/elf.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/procinfo.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/tlbflush.h> + +#include <asm/irqchip.h> + +#ifndef MEM_SIZE +#define MEM_SIZE (16*1024*1024) +#endif + +#ifdef CONFIG_PREEMPT +DEFINE_SPINLOCK(kernel_flag); +#endif + +#if defined(CONFIG_FPE_NWFPE) +char fpe_type[8]; + +static int __init fpe_setup(char *line) +{ + memcpy(fpe_type, line, 8); + return 1; +} + +__setup("fpe=", fpe_setup); +#endif + +extern void paging_init(struct meminfo *); +extern void convert_to_tag_list(struct tag *tags); +extern void squash_mem_tags(struct tag *tag); +extern void bootmem_init(struct meminfo *); +extern int root_mountflags; +extern int _stext, _text, _etext, _edata, _end; +#ifdef CONFIG_XIP_KERNEL +extern int _endtext, _sdata; +#endif + + +unsigned int processor_id; +unsigned int __machine_arch_type; +unsigned int system_rev; +unsigned int system_serial_low; +unsigned int system_serial_high; +unsigned int elf_hwcap; +unsigned int memc_ctrl_reg; +unsigned int number_mfm_drives; + +struct processor processor; + +char elf_platform[ELF_PLATFORM_SIZE]; + +unsigned long phys_initrd_start __initdata = 0; +unsigned long phys_initrd_size __initdata = 0; +static struct meminfo meminfo __initdata = { 0, }; +static struct proc_info_item proc_info; +static const char *machine_name; +static char command_line[COMMAND_LINE_SIZE]; + +static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; + +/* + * Standard memory resources + */ +static struct resource mem_res[] = { + { "Video RAM", 0, 0, IORESOURCE_MEM }, + { "Kernel code", 0, 0, IORESOURCE_MEM }, + { "Kernel data", 0, 0, IORESOURCE_MEM } +}; + +#define video_ram mem_res[0] +#define kernel_code mem_res[1] +#define kernel_data mem_res[2] + +static struct resource io_res[] = { + { "reserved", 0x3bc, 0x3be, IORESOURCE_IO | IORESOURCE_BUSY }, + { "reserved", 0x378, 0x37f, IORESOURCE_IO | IORESOURCE_BUSY }, + { "reserved", 0x278, 0x27f, IORESOURCE_IO | IORESOURCE_BUSY } +}; + +#define lp0 io_res[0] +#define lp1 io_res[1] +#define lp2 io_res[2] + +#define dump_cpu_info() do { } while (0) + +static void __init setup_processor(void) +{ + extern struct proc_info_list __proc_info_begin, __proc_info_end; + struct proc_info_list *list; + + /* + * locate processor in the list of supported processor + * types. The linker builds this table for us from the + * entries in arch/arm26/mm/proc-*.S + */ + for (list = &__proc_info_begin; list < &__proc_info_end ; list++) + if ((processor_id & list->cpu_mask) == list->cpu_val) + break; + + /* + * If processor type is unrecognised, then we + * can do nothing... + */ + if (list >= &__proc_info_end) { + printk("CPU configuration botched (ID %08x), unable " + "to continue.\n", processor_id); + while (1); + } + + proc_info = *list->info; + processor = *list->proc; + + + printk("CPU: %s %s revision %d\n", + proc_info.manufacturer, proc_info.cpu_name, + (int)processor_id & 15); + + dump_cpu_info(); + + sprintf(system_utsname.machine, "%s", list->arch_name); + sprintf(elf_platform, "%s", list->elf_name); + elf_hwcap = list->elf_hwcap; + + cpu_proc_init(); +} + +/* + * Initial parsing of the command line. We need to pick out the + * memory size. We look for mem=size@start, where start and size + * are "size[KkMm]" + */ +static void __init +parse_cmdline(struct meminfo *mi, char **cmdline_p, char *from) +{ + char c = ' ', *to = command_line; + int usermem = 0, len = 0; + + for (;;) { + if (c == ' ' && !memcmp(from, "mem=", 4)) { + unsigned long size, start; + + if (to != command_line) + to -= 1; + + /* + * If the user specifies memory size, we + * blow away any automatically generated + * size. + */ + if (usermem == 0) { + usermem = 1; + mi->nr_banks = 0; + } + + start = PHYS_OFFSET; + size = memparse(from + 4, &from); + if (*from == '@') + start = memparse(from + 1, &from); + + mi->bank[mi->nr_banks].start = start; + mi->bank[mi->nr_banks].size = size; + mi->bank[mi->nr_banks].node = PHYS_TO_NID(start); + mi->nr_banks += 1; + } + c = *from++; + if (!c) + break; + if (COMMAND_LINE_SIZE <= ++len) + break; + *to++ = c; + } + *to = '\0'; + *cmdline_p = command_line; +} + +static void __init +setup_ramdisk(int doload, int prompt, int image_start, unsigned int rd_sz) +{ +#ifdef CONFIG_BLK_DEV_RAM + extern int rd_size, rd_image_start, rd_prompt, rd_doload; + + rd_image_start = image_start; + rd_prompt = prompt; + rd_doload = doload; + + if (rd_sz) + rd_size = rd_sz; +#endif +} + +static void __init +request_standard_resources(struct meminfo *mi) +{ + struct resource *res; + int i; + + kernel_code.start = init_mm.start_code; + kernel_code.end = init_mm.end_code - 1; +#ifdef CONFIG_XIP_KERNEL + kernel_data.start = init_mm.start_data; +#else + kernel_data.start = init_mm.end_code; +#endif + kernel_data.end = init_mm.brk - 1; + + for (i = 0; i < mi->nr_banks; i++) { + unsigned long virt_start, virt_end; + + if (mi->bank[i].size == 0) + continue; + + virt_start = mi->bank[i].start; + virt_end = virt_start + mi->bank[i].size - 1; + + res = alloc_bootmem_low(sizeof(*res)); + res->name = "System RAM"; + res->start = virt_start; + res->end = virt_end; + res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + + request_resource(&iomem_resource, res); + + if (kernel_code.start >= res->start && + kernel_code.end <= res->end) + request_resource(res, &kernel_code); + if (kernel_data.start >= res->start && + kernel_data.end <= res->end) + request_resource(res, &kernel_data); + } + +/* FIXME - needed? if (mdesc->video_start) { + video_ram.start = mdesc->video_start; + video_ram.end = mdesc->video_end; + request_resource(&iomem_resource, &video_ram); + }*/ + + /* + * Some machines don't have the possibility of ever + * possessing lp1 or lp2 + */ + if (0) /* FIXME - need to do this for A5k at least */ + request_resource(&ioport_resource, &lp0); +} + +/* + * Tag parsing. + * + * This is the new way of passing data to the kernel at boot time. Rather + * than passing a fixed inflexible structure to the kernel, we pass a list + * of variable-sized tags to the kernel. The first tag must be a ATAG_CORE + * tag for the list to be recognised (to distinguish the tagged list from + * a param_struct). The list is terminated with a zero-length tag (this tag + * is not parsed in any way). + */ +static int __init parse_tag_core(const struct tag *tag) +{ + if (tag->hdr.size > 2) { + if ((tag->u.core.flags & 1) == 0) + root_mountflags &= ~MS_RDONLY; + ROOT_DEV = old_decode_dev(tag->u.core.rootdev); + } + return 0; +} + +__tagtable(ATAG_CORE, parse_tag_core); + +static int __init parse_tag_mem32(const struct tag *tag) +{ + if (meminfo.nr_banks >= NR_BANKS) { + printk(KERN_WARNING + "Ignoring memory bank 0x%08x size %dKB\n", + tag->u.mem.start, tag->u.mem.size / 1024); + return -EINVAL; + } + meminfo.bank[meminfo.nr_banks].start = tag->u.mem.start; + meminfo.bank[meminfo.nr_banks].size = tag->u.mem.size; + meminfo.bank[meminfo.nr_banks].node = PHYS_TO_NID(tag->u.mem.start); + meminfo.nr_banks += 1; + + return 0; +} + +__tagtable(ATAG_MEM, parse_tag_mem32); + +#if defined(CONFIG_DUMMY_CONSOLE) +struct screen_info screen_info = { + .orig_video_lines = 30, + .orig_video_cols = 80, + .orig_video_mode = 0, + .orig_video_ega_bx = 0, + .orig_video_isVGA = 1, + .orig_video_points = 8 +}; + +static int __init parse_tag_videotext(const struct tag *tag) +{ + screen_info.orig_x = tag->u.videotext.x; + screen_info.orig_y = tag->u.videotext.y; + screen_info.orig_video_page = tag->u.videotext.video_page; + screen_info.orig_video_mode = tag->u.videotext.video_mode; + screen_info.orig_video_cols = tag->u.videotext.video_cols; + screen_info.orig_video_ega_bx = tag->u.videotext.video_ega_bx; + screen_info.orig_video_lines = tag->u.videotext.video_lines; + screen_info.orig_video_isVGA = tag->u.videotext.video_isvga; + screen_info.orig_video_points = tag->u.videotext.video_points; + return 0; +} + +__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext); +#endif + +static int __init parse_tag_acorn(const struct tag *tag) +{ + memc_ctrl_reg = tag->u.acorn.memc_control_reg; + number_mfm_drives = tag->u.acorn.adfsdrives; + return 0; +} + +__tagtable(ATAG_ACORN, parse_tag_acorn); + +static int __init parse_tag_ramdisk(const struct tag *tag) +{ + setup_ramdisk((tag->u.ramdisk.flags & 1) == 0, + (tag->u.ramdisk.flags & 2) == 0, + tag->u.ramdisk.start, tag->u.ramdisk.size); + return 0; +} + +__tagtable(ATAG_RAMDISK, parse_tag_ramdisk); + +static int __init parse_tag_initrd(const struct tag *tag) +{ + printk(KERN_WARNING "ATAG_INITRD is deprecated; please update your bootloader. \n"); + phys_initrd_start = (unsigned long)tag->u.initrd.start; + phys_initrd_size = (unsigned long)tag->u.initrd.size; + return 0; +} + +__tagtable(ATAG_INITRD, parse_tag_initrd); + +static int __init parse_tag_initrd2(const struct tag *tag) +{ + printk(KERN_WARNING "ATAG_INITRD is deprecated; please update your bootloader. \n"); + phys_initrd_start = (unsigned long)tag->u.initrd.start; + phys_initrd_size = (unsigned long)tag->u.initrd.size; + return 0; +} + +__tagtable(ATAG_INITRD2, parse_tag_initrd2); + +static int __init parse_tag_serialnr(const struct tag *tag) +{ + system_serial_low = tag->u.serialnr.low; + system_serial_high = tag->u.serialnr.high; + return 0; +} + +__tagtable(ATAG_SERIAL, parse_tag_serialnr); + +static int __init parse_tag_revision(const struct tag *tag) +{ + system_rev = tag->u.revision.rev; + return 0; +} + +__tagtable(ATAG_REVISION, parse_tag_revision); + +static int __init parse_tag_cmdline(const struct tag *tag) +{ + strncpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE); + default_command_line[COMMAND_LINE_SIZE - 1] = '\0'; + return 0; +} + +__tagtable(ATAG_CMDLINE, parse_tag_cmdline); + +/* + * Scan the tag table for this tag, and call its parse function. + * The tag table is built by the linker from all the __tagtable + * declarations. + */ +static int __init parse_tag(const struct tag *tag) +{ + extern struct tagtable __tagtable_begin, __tagtable_end; + struct tagtable *t; + + for (t = &__tagtable_begin; t < &__tagtable_end; t++) + if (tag->hdr.tag == t->tag) { + t->parse(tag); + break; + } + + return t < &__tagtable_end; +} + +/* + * Parse all tags in the list, checking both the global and architecture + * specific tag tables. + */ +static void __init parse_tags(const struct tag *t) +{ + for (; t->hdr.size; t = tag_next(t)) + if (!parse_tag(t)) + printk(KERN_WARNING + "Ignoring unrecognised tag 0x%08x\n", + t->hdr.tag); +} + +/* + * This holds our defaults. + */ +static struct init_tags { + struct tag_header hdr1; + struct tag_core core; + struct tag_header hdr2; + struct tag_mem32 mem; + struct tag_header hdr3; +} init_tags __initdata = { + { tag_size(tag_core), ATAG_CORE }, + { 1, PAGE_SIZE, 0xff }, + { tag_size(tag_mem32), ATAG_MEM }, + { MEM_SIZE, PHYS_OFFSET }, + { 0, ATAG_NONE } +}; + +void __init setup_arch(char **cmdline_p) +{ + struct tag *tags = (struct tag *)&init_tags; + char *from = default_command_line; + + setup_processor(); + if(machine_arch_type == MACH_TYPE_A5K) + machine_name = "A5000"; + else if(machine_arch_type == MACH_TYPE_ARCHIMEDES) + machine_name = "Archimedes"; + else + machine_name = "UNKNOWN"; + + //FIXME - the tag struct is always copied here but this is a block + // of RAM that is accidentally reserved along with video RAM. perhaps + // it would be a good idea to explicitly reserve this? + + tags = (struct tag *)0x0207c000; + + /* + * If we have the old style parameters, convert them to + * a tag list. + */ + if (tags->hdr.tag != ATAG_CORE) + convert_to_tag_list(tags); + if (tags->hdr.tag != ATAG_CORE) + tags = (struct tag *)&init_tags; + if (tags->hdr.tag == ATAG_CORE) { + if (meminfo.nr_banks != 0) + squash_mem_tags(tags); + parse_tags(tags); + } + + init_mm.start_code = (unsigned long) &_text; +#ifndef CONFIG_XIP_KERNEL + init_mm.end_code = (unsigned long) &_etext; +#else + init_mm.end_code = (unsigned long) &_endtext; + init_mm.start_data = (unsigned long) &_sdata; +#endif + init_mm.end_data = (unsigned long) &_edata; + init_mm.brk = (unsigned long) &_end; + + memcpy(saved_command_line, from, COMMAND_LINE_SIZE); + saved_command_line[COMMAND_LINE_SIZE-1] = '\0'; + parse_cmdline(&meminfo, cmdline_p, from); + bootmem_init(&meminfo); + paging_init(&meminfo); + request_standard_resources(&meminfo); + +#ifdef CONFIG_VT +#if defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +#endif +#endif +} + +static const char *hwcap_str[] = { + "swp", + "half", + "thumb", + "26bit", + "fastmult", + "fpa", + "vfp", + "edsp", + NULL +}; + +static int c_show(struct seq_file *m, void *v) +{ + int i; + + seq_printf(m, "Processor\t: %s %s rev %d (%s)\n", + proc_info.manufacturer, proc_info.cpu_name, + (int)processor_id & 15, elf_platform); + + seq_printf(m, "BogoMIPS\t: %lu.%02lu\n", + loops_per_jiffy / (500000/HZ), + (loops_per_jiffy / (5000/HZ)) % 100); + + /* dump out the processor features */ + seq_puts(m, "Features\t: "); + + for (i = 0; hwcap_str[i]; i++) + if (elf_hwcap & (1 << i)) + seq_printf(m, "%s ", hwcap_str[i]); + + seq_puts(m, "\n"); + + seq_printf(m, "CPU part\t\t: %07x\n", processor_id >> 4); + seq_printf(m, "CPU revision\t: %d\n\n", processor_id & 15); + seq_printf(m, "Hardware\t: %s\n", machine_name); + seq_printf(m, "Revision\t: %04x\n", system_rev); + seq_printf(m, "Serial\t\t: %08x%08x\n", + system_serial_high, system_serial_low); + + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < 1 ? (void *)1 : NULL; +} + +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} + +static void c_stop(struct seq_file *m, void *v) +{ +} + +struct seq_operations cpuinfo_op = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_show +}; diff --git a/arch/arm26/kernel/signal.c b/arch/arm26/kernel/signal.c new file mode 100644 index 00000000000..356d9809cc0 --- /dev/null +++ b/arch/arm26/kernel/signal.c @@ -0,0 +1,540 @@ +/* + * linux/arch/arm26/kernel/signal.c + * + * Copyright (C) 1995-2002 Russell King + * Copyright (C) 2003 Ian Molton (ARM26) + * + * FIXME!!! This is probably very broken (13/05/2003) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/wait.h> +#include <linux/ptrace.h> +#include <linux/personality.h> +#include <linux/tty.h> +#include <linux/binfmts.h> +#include <linux/elf.h> + +#include <asm/pgalloc.h> +#include <asm/ucontext.h> +#include <asm/uaccess.h> +#include <asm/unistd.h> + +#include "ptrace.h" + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +/* + * For ARM syscalls, we encode the syscall number into the instruction. + */ +#define SWI_SYS_SIGRETURN (0xef000000|(__NR_sigreturn)) +#define SWI_SYS_RT_SIGRETURN (0xef000000|(__NR_rt_sigreturn)) + +static int do_signal(sigset_t *oldset, struct pt_regs * regs, int syscall); + +/* + * atomically swap in the new signal mask, and wait for a signal. + */ +asmlinkage int sys_sigsuspend(int restart, unsigned long oldmask, old_sigset_t mask, struct pt_regs *regs) +{ + sigset_t saveset; + + mask &= _BLOCKABLE; + spin_lock_irq(¤t->sighand->siglock); + saveset = current->blocked; + siginitset(¤t->blocked, mask); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + regs->ARM_r0 = -EINTR; + + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(&saveset, regs, 0)) + return regs->ARM_r0; + } +} + +asmlinkage int +sys_rt_sigsuspend(sigset_t *unewset, size_t sigsetsize, struct pt_regs *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->sighand->siglock); + saveset = current->blocked; + current->blocked = newset; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + regs->ARM_r0 = -EINTR; + + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(&saveset, regs, 0)) + return regs->ARM_r0; + } +} + +asmlinkage 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 (!access_ok(VERIFY_READ, act, sizeof(*act)) || + __get_user(new_ka.sa.sa_handler, &act->sa_handler) || + __get_user(new_ka.sa.sa_restorer, &act->sa_restorer)) + return -EFAULT; + __get_user(new_ka.sa.sa_flags, &act->sa_flags); + __get_user(mask, &act->sa_mask); + siginitset(&new_ka.sa.sa_mask, mask); + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || + __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer)) + return -EFAULT; + __put_user(old_ka.sa.sa_flags, &oact->sa_flags); + __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return ret; +} + +/* + * Do a signal return; undo the signal stack. + */ +struct sigframe +{ + struct sigcontext sc; + unsigned long extramask[_NSIG_WORDS-1]; + unsigned long retcode; +}; + +struct rt_sigframe +{ + struct siginfo *pinfo; + void *puc; + struct siginfo info; + struct ucontext uc; + unsigned long retcode; +}; + +static int +restore_sigcontext(struct pt_regs *regs, struct sigcontext *sc) +{ + int err = 0; + + __get_user_error(regs->ARM_r0, &sc->arm_r0, err); + __get_user_error(regs->ARM_r1, &sc->arm_r1, err); + __get_user_error(regs->ARM_r2, &sc->arm_r2, err); + __get_user_error(regs->ARM_r3, &sc->arm_r3, err); + __get_user_error(regs->ARM_r4, &sc->arm_r4, err); + __get_user_error(regs->ARM_r5, &sc->arm_r5, err); + __get_user_error(regs->ARM_r6, &sc->arm_r6, err); + __get_user_error(regs->ARM_r7, &sc->arm_r7, err); + __get_user_error(regs->ARM_r8, &sc->arm_r8, err); + __get_user_error(regs->ARM_r9, &sc->arm_r9, err); + __get_user_error(regs->ARM_r10, &sc->arm_r10, err); + __get_user_error(regs->ARM_fp, &sc->arm_fp, err); + __get_user_error(regs->ARM_ip, &sc->arm_ip, err); + __get_user_error(regs->ARM_sp, &sc->arm_sp, err); + __get_user_error(regs->ARM_lr, &sc->arm_lr, err); + __get_user_error(regs->ARM_pc, &sc->arm_pc, err); + + err |= !valid_user_regs(regs); + + return err; +} + +asmlinkage int sys_sigreturn(struct pt_regs *regs) +{ + struct sigframe *frame; + sigset_t set; + + /* + * Since we stacked the signal on a 64-bit boundary, + * then 'sp' should be word aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (regs->ARM_sp & 7) + goto badframe; + + frame = (struct sigframe *)regs->ARM_sp; + + if (!access_ok(VERIFY_READ, frame, sizeof (*frame))) + goto badframe; + if (__get_user(set.sig[0], &frame->sc.oldmask) + || (_NSIG_WORDS > 1 + && __copy_from_user(&set.sig[1], &frame->extramask, + sizeof(frame->extramask)))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->sc)) + goto badframe; + + /* Send SIGTRAP if we're single-stepping */ + if (current->ptrace & PT_SINGLESTEP) { + ptrace_cancel_bpt(current); + send_sig(SIGTRAP, current, 1); + } + + return regs->ARM_r0; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +asmlinkage int sys_rt_sigreturn(struct pt_regs *regs) +{ + struct rt_sigframe *frame; + sigset_t set; + + /* + * Since we stacked the signal on a 64-bit boundary, + * then 'sp' should be word aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (regs->ARM_sp & 7) + goto badframe; + + frame = (struct rt_sigframe *)regs->ARM_sp; + + if (!access_ok(VERIFY_READ, frame, sizeof (*frame))) + goto badframe; + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + goto badframe; + + /* Send SIGTRAP if we're single-stepping */ + if (current->ptrace & PT_SINGLESTEP) { + ptrace_cancel_bpt(current); + send_sig(SIGTRAP, current, 1); + } + + return regs->ARM_r0; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +static int +setup_sigcontext(struct sigcontext *sc, /*struct _fpstate *fpstate,*/ + struct pt_regs *regs, unsigned long mask) +{ + int err = 0; + + __put_user_error(regs->ARM_r0, &sc->arm_r0, err); + __put_user_error(regs->ARM_r1, &sc->arm_r1, err); + __put_user_error(regs->ARM_r2, &sc->arm_r2, err); + __put_user_error(regs->ARM_r3, &sc->arm_r3, err); + __put_user_error(regs->ARM_r4, &sc->arm_r4, err); + __put_user_error(regs->ARM_r5, &sc->arm_r5, err); + __put_user_error(regs->ARM_r6, &sc->arm_r6, err); + __put_user_error(regs->ARM_r7, &sc->arm_r7, err); + __put_user_error(regs->ARM_r8, &sc->arm_r8, err); + __put_user_error(regs->ARM_r9, &sc->arm_r9, err); + __put_user_error(regs->ARM_r10, &sc->arm_r10, err); + __put_user_error(regs->ARM_fp, &sc->arm_fp, err); + __put_user_error(regs->ARM_ip, &sc->arm_ip, err); + __put_user_error(regs->ARM_sp, &sc->arm_sp, err); + __put_user_error(regs->ARM_lr, &sc->arm_lr, err); + __put_user_error(regs->ARM_pc, &sc->arm_pc, err); + + __put_user_error(current->thread.trap_no, &sc->trap_no, err); + __put_user_error(current->thread.error_code, &sc->error_code, err); + __put_user_error(current->thread.address, &sc->fault_address, err); + __put_user_error(mask, &sc->oldmask, err); + + return err; +} + +static inline void * +get_sigframe(struct k_sigaction *ka, struct pt_regs *regs, int framesize) +{ + unsigned long sp = regs->ARM_sp; + + /* + * This is the X/Open sanctioned signal stack switching. + */ + if ((ka->sa.sa_flags & SA_ONSTACK) && !sas_ss_flags(sp)) + sp = current->sas_ss_sp + current->sas_ss_size; + + /* + * ATPCS B01 mandates 8-byte alignment + */ + return (void *)((sp - framesize) & ~7); +} + +static int +setup_return(struct pt_regs *regs, struct k_sigaction *ka, + unsigned long *rc, void *frame, int usig) +{ + unsigned long handler = (unsigned long)ka->sa.sa_handler; + unsigned long retcode; + + if (ka->sa.sa_flags & SA_RESTORER) { + retcode = (unsigned long)ka->sa.sa_restorer; + } else { + + if (__put_user((ka->sa.sa_flags & SA_SIGINFO)?SWI_SYS_RT_SIGRETURN:SWI_SYS_SIGRETURN, rc)) + return 1; + + retcode = ((unsigned long)rc); + } + + regs->ARM_r0 = usig; + regs->ARM_sp = (unsigned long)frame; + regs->ARM_lr = retcode; + regs->ARM_pc = handler & ~3; + + return 0; +} + +static int +setup_frame(int usig, struct k_sigaction *ka, sigset_t *set, struct pt_regs *regs) +{ + struct sigframe *frame = get_sigframe(ka, regs, sizeof(*frame)); + int err = 0; + + if (!access_ok(VERIFY_WRITE, frame, sizeof (*frame))) + return 1; + + err |= setup_sigcontext(&frame->sc, /*&frame->fpstate,*/ regs, set->sig[0]); + + if (_NSIG_WORDS > 1) { + err |= __copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask)); + } + + if (err == 0) + err = setup_return(regs, ka, &frame->retcode, frame, usig); + + return err; +} + +static int +setup_rt_frame(int usig, struct k_sigaction *ka, siginfo_t *info, + sigset_t *set, struct pt_regs *regs) +{ + struct rt_sigframe *frame = get_sigframe(ka, regs, sizeof(*frame)); + int err = 0; + + if (!access_ok(VERIFY_WRITE, frame, sizeof (*frame))) + return 1; + + __put_user_error(&frame->info, &frame->pinfo, err); + __put_user_error(&frame->uc, &frame->puc, err); + err |= copy_siginfo_to_user(&frame->info, info); + + /* 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, /*&frame->fpstate,*/ + regs, set->sig[0]); + err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + if (err == 0) + err = setup_return(regs, ka, &frame->retcode, frame, usig); + + if (err == 0) { + /* + * For realtime signals we must also set the second and third + * arguments for the signal handler. + * -- Peter Maydell <pmaydell@chiark.greenend.org.uk> 2000-12-06 + */ + regs->ARM_r1 = (unsigned long)frame->pinfo; + regs->ARM_r2 = (unsigned long)frame->puc; + } + + return err; +} + +static inline void restart_syscall(struct pt_regs *regs) +{ + regs->ARM_r0 = regs->ARM_ORIG_r0; + regs->ARM_pc -= 4; +} + +/* + * OK, we're invoking a handler + */ +static void +handle_signal(unsigned long sig, siginfo_t *info, sigset_t *oldset, + struct pt_regs * regs, int syscall) +{ + struct thread_info *thread = current_thread_info(); + struct task_struct *tsk = current; + struct k_sigaction *ka = &tsk->sighand->action[sig-1]; + int usig = sig; + int ret; + + /* + * If we were from a system call, check for system call restarting... + */ + if (syscall) { + switch (regs->ARM_r0) { + case -ERESTART_RESTARTBLOCK: + current_thread_info()->restart_block.fn = + do_no_restart_syscall; + case -ERESTARTNOHAND: + regs->ARM_r0 = -EINTR; + break; + case -ERESTARTSYS: + if (!(ka->sa.sa_flags & SA_RESTART)) { + regs->ARM_r0 = -EINTR; + break; + } + /* fallthrough */ + case -ERESTARTNOINTR: + restart_syscall(regs); + } + } + + /* + * translate the signal + */ + if (usig < 32 && thread->exec_domain && thread->exec_domain->signal_invmap) + usig = thread->exec_domain->signal_invmap[usig]; + + /* + * Set up the stack frame + */ + if (ka->sa.sa_flags & SA_SIGINFO) + ret = setup_rt_frame(usig, ka, info, oldset, regs); + else + ret = setup_frame(usig, ka, oldset, regs); + + /* + * Check that the resulting registers are actually sane. + */ + ret |= !valid_user_regs(regs); + + if (ret == 0) { + if (ka->sa.sa_flags & SA_ONESHOT) + ka->sa.sa_handler = SIG_DFL; + + if (!(ka->sa.sa_flags & SA_NODEFER)) { + spin_lock_irq(&tsk->sighand->siglock); + sigorsets(&tsk->blocked, &tsk->blocked, + &ka->sa.sa_mask); + sigaddset(&tsk->blocked, sig); + recalc_sigpending(); + spin_unlock_irq(&tsk->sighand->siglock); + } + return; + } + + force_sigsegv(sig, tsk); +} + +/* + * 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. + * + * Note that we go through the signals twice: once to check the signals that + * the kernel can handle, and then we build all the user-level signal handling + * stack-frames in one go after that. + */ +static int do_signal(sigset_t *oldset, struct pt_regs *regs, int syscall) +{ + siginfo_t info; + int signr; + + /* + * 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 0; + + if (current->ptrace & PT_SINGLESTEP) + ptrace_cancel_bpt(current); + + signr = get_signal_to_deliver(&info, regs, NULL); + if (signr > 0) { + handle_signal(signr, &info, oldset, regs, syscall); + if (current->ptrace & PT_SINGLESTEP) + ptrace_set_bpt(current); + return 1; + } + + /* + * No signal to deliver to the process - restart the syscall. + */ + if (syscall) { + if (regs->ARM_r0 == -ERESTART_RESTARTBLOCK) { + u32 *usp; + + regs->ARM_sp -= 12; + usp = (u32 *)regs->ARM_sp; + + put_user(regs->ARM_pc, &usp[0]); + /* swi __NR_restart_syscall */ + put_user(0xef000000 | __NR_restart_syscall, &usp[1]); + /* ldr pc, [sp], #12 */ +// FIXME!!! is #12 correct there? + put_user(0xe49df00c, &usp[2]); + + regs->ARM_pc = regs->ARM_sp + 4; + } + if (regs->ARM_r0 == -ERESTARTNOHAND || + regs->ARM_r0 == -ERESTARTSYS || + regs->ARM_r0 == -ERESTARTNOINTR) { + restart_syscall(regs); + } + } + if (current->ptrace & PT_SINGLESTEP) + ptrace_set_bpt(current); + return 0; +} + +asmlinkage void +do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall) +{ + if (thread_flags & _TIF_SIGPENDING) + do_signal(¤t->blocked, regs, syscall); +} diff --git a/arch/arm26/kernel/sys_arm.c b/arch/arm26/kernel/sys_arm.c new file mode 100644 index 00000000000..e7edd201579 --- /dev/null +++ b/arch/arm26/kernel/sys_arm.c @@ -0,0 +1,324 @@ +/* + * linux/arch/arm26/kernel/sys_arm.c + * + * Copyright (C) People who wrote linux/arch/i386/kernel/sys_i386.c + * Copyright (C) 1995, 1996 Russell King. + * Copyright (C) 2003 Ian Molton. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This file contains various random system calls that + * have a non-standard calling sequence on the Linux/arm + * platform. + */ +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sem.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/stat.h> +#include <linux/syscalls.h> +#include <linux/mman.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/utsname.h> + +#include <asm/uaccess.h> +#include <asm/ipc.h> + +extern unsigned long do_mremap(unsigned long addr, unsigned long old_len, + unsigned long new_len, unsigned long flags, + unsigned long new_addr); + +/* + * 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; + + error = do_pipe(fd); + if (!error) { + if (copy_to_user(fildes, fd, 2*sizeof(int))) + error = -EFAULT; + } + return error; +} + +/* common code for old and new mmaps */ +inline long do_mmap2( + unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long pgoff) +{ + int error = -EINVAL; + struct file * file = NULL; + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + + /* + * If we are doing a fixed mapping, and address < PAGE_SIZE, + * then deny it. + */ + if (flags & MAP_FIXED && addr < PAGE_SIZE && vectors_base() == 0) + goto out; + + error = -EBADF; + if (!(flags & MAP_ANONYMOUS)) { + file = fget(fd); + if (!file) + goto out; + } + + down_write(¤t->mm->mmap_sem); + error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); + up_write(¤t->mm->mmap_sem); + + if (file) + fput(file); +out: + return error; +} + +struct mmap_arg_struct { + unsigned long addr; + unsigned long len; + unsigned long prot; + unsigned long flags; + unsigned long fd; + unsigned long offset; +}; + +asmlinkage int old_mmap(struct mmap_arg_struct *arg) +{ + int error = -EFAULT; + struct mmap_arg_struct a; + + if (copy_from_user(&a, arg, sizeof(a))) + goto out; + + error = -EINVAL; + if (a.offset & ~PAGE_MASK) + goto out; + + error = do_mmap2(a.addr, a.len, a.prot, a.flags, a.fd, a.offset >> PAGE_SHIFT); +out: + return error; +} + +asmlinkage unsigned long +sys_arm_mremap(unsigned long addr, unsigned long old_len, + unsigned long new_len, unsigned long flags, + unsigned long new_addr) +{ + unsigned long ret = -EINVAL; + + /* + * If we are doing a fixed mapping, and address < PAGE_SIZE, + * then deny it. + */ + if (flags & MREMAP_FIXED && new_addr < PAGE_SIZE && + vectors_base() == 0) + goto out; + + down_write(¤t->mm->mmap_sem); + ret = do_mremap(addr, old_len, new_len, flags, new_addr); + up_write(¤t->mm->mmap_sem); + +out: + return ret; +} + +/* + * Perform the select(nd, in, out, ex, tv) and mmap() system + * calls. + */ + +struct sel_arg_struct { + unsigned long n; + fd_set *inp, *outp, *exp; + struct timeval *tvp; +}; + +asmlinkage int old_select(struct sel_arg_struct *arg) +{ + struct sel_arg_struct a; + + if (copy_from_user(&a, arg, sizeof(a))) + return -EFAULT; + /* sys_select() does the appropriate kernel locking */ + return sys_select(a.n, a.inp, a.outp, a.exp, a.tvp); +} + +/* + * sys_ipc() is the de-multiplexer for the SysV IPC calls.. + * + * This is really horribly ugly. + */ +asmlinkage int sys_ipc (uint call, int first, int second, int third, void *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 = do_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 do_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; + } +} + +/* Fork a new task - this creates a new program thread. + * This is called indirectly via a small wrapper + */ +asmlinkage int sys_fork(struct pt_regs *regs) +{ + return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL); +} + +/* Clone a task - this clones the calling program thread. + * This is called indirectly via a small wrapper + */ +asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp, struct pt_regs *regs) +{ + /* + * We don't support SETTID / CLEARTID (FIXME!!! (nicked from arm32)) + */ + if (clone_flags & (CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID)) + return -EINVAL; + + if (!newsp) + newsp = regs->ARM_sp; + + return do_fork(clone_flags, newsp, regs, 0, NULL, NULL); +} + +asmlinkage int sys_vfork(struct pt_regs *regs) +{ + return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL); +} + +/* sys_execve() executes a new program. + * This is called indirectly via a small wrapper + */ +asmlinkage int sys_execve(char *filenamei, char **argv, char **envp, struct pt_regs *regs) +{ + int error; + char * filename; + + filename = getname(filenamei); + error = PTR_ERR(filename); + if (IS_ERR(filename)) + goto out; + error = do_execve(filename, argv, envp, regs); + putname(filename); +out: + return error; +} + +/* FIXME - see if this is correct for arm26 */ +long execve(const char *filename, char **argv, char **envp) +{ + struct pt_regs regs; + int ret; + memset(®s, 0, sizeof(struct pt_regs)); + ret = do_execve((char *)filename, (char __user * __user *)argv, (char __user * __user *)envp, ®s); + if (ret < 0) + goto out; + + /* + * Save argc to the register structure for userspace. + */ + regs.ARM_r0 = ret; + + /* + * We were successful. We won't be returning to our caller, but + * instead to user space by manipulating the kernel stack. + */ + asm( "add r0, %0, %1\n\t" + "mov r1, %2\n\t" + "mov r2, %3\n\t" + "bl memmove\n\t" /* copy regs to top of stack */ + "mov r8, #0\n\t" /* not a syscall */ + "mov r9, %0\n\t" /* thread structure */ + "mov sp, r0\n\t" /* reposition stack pointer */ + "b ret_to_user" + : + : "r" (current_thread_info()), + "Ir" (THREAD_SIZE - 8 - sizeof(regs)), + "r" (®s), + "Ir" (sizeof(regs)) + : "r0", "r1", "r2", "r3", "ip", "memory"); + + out: + return ret; +} + +EXPORT_SYMBOL(execve); diff --git a/arch/arm26/kernel/time.c b/arch/arm26/kernel/time.c new file mode 100644 index 00000000000..549a6b2e177 --- /dev/null +++ b/arch/arm26/kernel/time.c @@ -0,0 +1,234 @@ +/* + * linux/arch/arm26/kernel/time.c + * + * Copyright (C) 1991, 1992, 1995 Linus Torvalds + * Modifications for ARM (C) 1994-2001 Russell King + * Mods for ARM26 (C) 2003 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This file contains the ARM-specific time handling details: + * reading the RTC at bootup, etc... + * + * 1994-07-02 Alan Modra + * fixed set_rtc_mmss, fixed time.year for >= 2000, new mktime + * 1998-12-20 Updated NTP code according to technical memorandum Jan '96 + * "A Kernel Model for Precision Timekeeping" by Dave Mills + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/timex.h> +#include <linux/errno.h> +#include <linux/profile.h> + +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/ioc.h> + +u64 jiffies_64 = INITIAL_JIFFIES; + +EXPORT_SYMBOL(jiffies_64); + +extern unsigned long wall_jiffies; + +/* this needs a better home */ +DEFINE_SPINLOCK(rtc_lock); + +/* change this if you have some constant time drift */ +#define USECS_PER_JIFFY (1000000/HZ) + +static int dummy_set_rtc(void) +{ + return 0; +} + +/* + * hook for setting the RTC's idea of the current time. + */ +int (*set_rtc)(void) = dummy_set_rtc; + +/* + * Get time offset based on IOCs timer. + * FIXME - if this is called with interrutps off, why the shennanigans + * below ? + */ +static unsigned long gettimeoffset(void) +{ + unsigned int count1, count2, status; + long offset; + + ioc_writeb (0, IOC_T0LATCH); + barrier (); + count1 = ioc_readb(IOC_T0CNTL) | (ioc_readb(IOC_T0CNTH) << 8); + barrier (); + status = ioc_readb(IOC_IRQREQA); + barrier (); + ioc_writeb (0, IOC_T0LATCH); + barrier (); + count2 = ioc_readb(IOC_T0CNTL) | (ioc_readb(IOC_T0CNTH) << 8); + + offset = count2; + if (count2 < count1) { + /* + * We have not had an interrupt between reading count1 + * and count2. + */ + if (status & (1 << 5)) + offset -= LATCH; + } else if (count2 > count1) { + /* + * We have just had another interrupt between reading + * count1 and count2. + */ + offset -= LATCH; + } + + offset = (LATCH - offset) * (tick_nsec / 1000); + return (offset + LATCH/2) / LATCH; +} + +/* + * Scheduler clock - returns current time in nanosec units. + */ +unsigned long long sched_clock(void) +{ + return (unsigned long long)jiffies * (1000000000 / HZ); +} + +static unsigned long next_rtc_update; + +/* + * If we have an externally synchronized linux clock, then update + * CMOS clock accordingly every ~11 minutes. set_rtc() has to be + * called as close as possible to 500 ms before the new second + * starts. + */ +static inline void do_set_rtc(void) +{ + if (time_status & STA_UNSYNC || set_rtc == NULL) + return; + +//FIXME - timespec.tv_sec is a time_t not unsigned long + if (next_rtc_update && + time_before((unsigned long)xtime.tv_sec, next_rtc_update)) + return; + + if (xtime.tv_nsec < 500000000 - ((unsigned) tick_nsec >> 1) && + xtime.tv_nsec >= 500000000 + ((unsigned) tick_nsec >> 1)) + return; + + if (set_rtc()) + /* + * rtc update failed. Try again in 60s + */ + next_rtc_update = xtime.tv_sec + 60; + else + next_rtc_update = xtime.tv_sec + 660; +} + +#define do_leds() + +void do_gettimeofday(struct timeval *tv) +{ + unsigned long flags; + unsigned long seq; + unsigned long usec, sec, lost; + + do { + seq = read_seqbegin_irqsave(&xtime_lock, flags); + usec = gettimeoffset(); + + lost = jiffies - wall_jiffies; + if (lost) + usec += lost * USECS_PER_JIFFY; + + sec = xtime.tv_sec; + usec += xtime.tv_nsec / 1000; + } while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); + + /* usec may have gone up a lot: be safe */ + while (usec >= 1000000) { + usec -= 1000000; + sec++; + } + + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +EXPORT_SYMBOL(do_gettimeofday); + +int do_settimeofday(struct timespec *tv) +{ + if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + write_seqlock_irq(&xtime_lock); + /* + * This is revolting. We need to set "xtime" correctly. However, the + * value in this location is the value at the most recent update of + * wall time. Discover what correction gettimeofday() would have + * done, and then undo it! + */ + tv->tv_nsec -= 1000 * (gettimeoffset() + + (jiffies - wall_jiffies) * USECS_PER_JIFFY); + + while (tv->tv_nsec < 0) { + tv->tv_nsec += NSEC_PER_SEC; + tv->tv_sec--; + } + + xtime.tv_sec = tv->tv_sec; + xtime.tv_nsec = tv->tv_nsec; + time_adjust = 0; /* stop active adjtime() */ + time_status |= STA_UNSYNC; + time_maxerror = NTP_PHASE_LIMIT; + time_esterror = NTP_PHASE_LIMIT; + write_sequnlock_irq(&xtime_lock); + clock_was_set(); + return 0; +} + +EXPORT_SYMBOL(do_settimeofday); + +static irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + do_timer(regs); +#ifndef CONFIG_SMP + update_process_times(user_mode(regs)); +#endif + do_set_rtc(); //FIME - EVERY timer IRQ? + profile_tick(CPU_PROFILING, regs); + return IRQ_HANDLED; //FIXME - is this right? +} + +static struct irqaction timer_irq = { + .name = "timer", + .flags = SA_INTERRUPT, + .handler = timer_interrupt, +}; + +extern void ioctime_init(void); + +/* + * Set up timer interrupt. + */ +void __init time_init(void) +{ + ioc_writeb(LATCH & 255, IOC_T0LTCHL); + ioc_writeb(LATCH >> 8, IOC_T0LTCHH); + ioc_writeb(0, IOC_T0GO); + + + setup_irq(IRQ_TIMER, &timer_irq); +} + diff --git a/arch/arm26/kernel/traps.c b/arch/arm26/kernel/traps.c new file mode 100644 index 00000000000..f64f5902239 --- /dev/null +++ b/arch/arm26/kernel/traps.c @@ -0,0 +1,548 @@ +/* + * linux/arch/arm26/kernel/traps.c + * + * Copyright (C) 1995-2002 Russell King + * Fragments that appear the same as linux/arch/i386/kernel/traps.c (C) Linus Torvalds + * Copyright (C) 2003 Ian Molton (ARM26) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 'traps.c' handles hardware exceptions after we have saved some state in + * 'linux/arch/arm26/lib/traps.S'. Mostly a debugging aid, but will probably + * kill the offending process. + */ + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/spinlock.h> +#include <linux/personality.h> +#include <linux/ptrace.h> +#include <linux/elf.h> +#include <linux/interrupt.h> +#include <linux/init.h> + +#include <asm/atomic.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/unistd.h> +#include <asm/semaphore.h> + +#include "ptrace.h" + +extern void c_backtrace (unsigned long fp, int pmode); +extern void show_pte(struct mm_struct *mm, unsigned long addr); + +const char *processor_modes[] = { "USER_26", "FIQ_26" , "IRQ_26" , "SVC_26" }; + +static const char *handler[]= { "prefetch abort", "data abort", "address exception", "interrupt" "*bad reason*"}; + +/* + * Stack pointers should always be within the kernels view of + * physical memory. If it is not there, then we can't dump + * out any information relating to the stack. + */ +static int verify_stack(unsigned long sp) +{ + if (sp < PAGE_OFFSET || (sp > (unsigned long)high_memory && high_memory != 0)) + return -EFAULT; + + return 0; +} + +/* + * Dump out the contents of some memory nicely... + */ +static void dump_mem(const char *str, unsigned long bottom, unsigned long top) +{ + unsigned long p = bottom & ~31; + mm_segment_t fs; + int i; + + /* + * We need to switch to kernel mode so that we can use __get_user + * to safely read from kernel space. Note that we now dump the + * code first, just in case the backtrace kills us. + */ + fs = get_fs(); + set_fs(KERNEL_DS); + + printk("%s", str); + printk("(0x%08lx to 0x%08lx)\n", bottom, top); + + for (p = bottom & ~31; p < top;) { + printk("%04lx: ", p & 0xffff); + + for (i = 0; i < 8; i++, p += 4) { + unsigned int val; + + if (p < bottom || p >= top) + printk(" "); + else { + __get_user(val, (unsigned long *)p); + printk("%08x ", val); + } + } + printk ("\n"); + } + + set_fs(fs); +} + +static void dump_instr(struct pt_regs *regs) +{ + unsigned long addr = instruction_pointer(regs); + const int width = 8; + mm_segment_t fs; + int i; + + /* + * We need to switch to kernel mode so that we can use __get_user + * to safely read from kernel space. Note that we now dump the + * code first, just in case the backtrace kills us. + */ + fs = get_fs(); + set_fs(KERNEL_DS); + + printk("Code: "); + for (i = -4; i < 1; i++) { + unsigned int val, bad; + + bad = __get_user(val, &((u32 *)addr)[i]); + + if (!bad) + printk(i == 0 ? "(%0*x) " : "%0*x ", width, val); + else { + printk("bad PC value."); + break; + } + } + printk("\n"); + + set_fs(fs); +} + +/*static*/ void __dump_stack(struct task_struct *tsk, unsigned long sp) +{ + dump_mem("Stack: ", sp, 8192+(unsigned long)tsk->thread_info); +} + +void dump_stack(void) +{ +#ifdef CONFIG_DEBUG_ERRORS + __backtrace(); +#endif +} + +EXPORT_SYMBOL(dump_stack); + +//FIXME - was a static fn +void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk) +{ + unsigned int fp; + int ok = 1; + + printk("Backtrace: "); + fp = regs->ARM_fp; + if (!fp) { + printk("no frame pointer"); + ok = 0; + } else if (verify_stack(fp)) { + printk("invalid frame pointer 0x%08x", fp); + ok = 0; + } else if (fp < (unsigned long)(tsk->thread_info + 1)) + printk("frame pointer underflow"); + printk("\n"); + + if (ok) + c_backtrace(fp, processor_mode(regs)); +} + +/* FIXME - this is probably wrong.. */ +void show_stack(struct task_struct *task, unsigned long *sp) { + dump_mem("Stack: ", (unsigned long)sp, 8192+(unsigned long)task->thread_info); +} + +DEFINE_SPINLOCK(die_lock); + +/* + * This function is protected against re-entrancy. + */ +NORET_TYPE void die(const char *str, struct pt_regs *regs, int err) +{ + struct task_struct *tsk = current; + + console_verbose(); + spin_lock_irq(&die_lock); + + printk("Internal error: %s: %x\n", str, err); + printk("CPU: %d\n", smp_processor_id()); + show_regs(regs); + printk("Process %s (pid: %d, stack limit = 0x%p)\n", + current->comm, current->pid, tsk->thread_info + 1); + + if (!user_mode(regs) || in_interrupt()) { + __dump_stack(tsk, (unsigned long)(regs + 1)); + dump_backtrace(regs, tsk); + dump_instr(regs); + } +while(1); + spin_unlock_irq(&die_lock); + do_exit(SIGSEGV); +} + +void die_if_kernel(const char *str, struct pt_regs *regs, int err) +{ + if (user_mode(regs)) + return; + + die(str, regs, err); +} + +static DECLARE_MUTEX(undef_sem); +static int (*undef_hook)(struct pt_regs *); + +int request_undef_hook(int (*fn)(struct pt_regs *)) +{ + int ret = -EBUSY; + + down(&undef_sem); + if (undef_hook == NULL) { + undef_hook = fn; + ret = 0; + } + up(&undef_sem); + + return ret; +} + +int release_undef_hook(int (*fn)(struct pt_regs *)) +{ + int ret = -EINVAL; + + down(&undef_sem); + if (undef_hook == fn) { + undef_hook = NULL; + ret = 0; + } + up(&undef_sem); + + return ret; +} + +static int undefined_extension(struct pt_regs *regs, unsigned int op) +{ + switch (op) { + case 1: /* 0xde01 / 0x?7f001f0 */ + ptrace_break(current, regs); + return 0; + } + return 1; +} + +asmlinkage void do_undefinstr(struct pt_regs *regs) +{ + siginfo_t info; + void *pc; + + regs->ARM_pc -= 4; + + pc = (unsigned long *)instruction_pointer(regs); /* strip PSR */ + + if (user_mode(regs)) { + u32 instr; + + get_user(instr, (u32 *)pc); + + if ((instr & 0x0fff00ff) == 0x07f000f0 && + undefined_extension(regs, (instr >> 8) & 255) == 0) { + regs->ARM_pc += 4; + return; + } + } else { + if (undef_hook && undef_hook(regs) == 0) { + regs->ARM_pc += 4; + return; + } + } + +#ifdef CONFIG_DEBUG_USER + printk(KERN_INFO "%s (%d): undefined instruction: pc=%p\n", + current->comm, current->pid, pc); + dump_instr(regs); +#endif + + current->thread.error_code = 0; + current->thread.trap_no = 6; + + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLOPC; + info.si_addr = pc; + + force_sig_info(SIGILL, &info, current); + + die_if_kernel("Oops - undefined instruction", regs, 0); +} + +asmlinkage void do_excpt(unsigned long address, struct pt_regs *regs, int mode) +{ + siginfo_t info; + +#ifdef CONFIG_DEBUG_USER + printk(KERN_INFO "%s (%d): address exception: pc=%08lx\n", + current->comm, current->pid, instruction_pointer(regs)); + dump_instr(regs); +#endif + + current->thread.error_code = 0; + current->thread.trap_no = 11; + + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_ADRERR; + info.si_addr = (void *)address; + + force_sig_info(SIGBUS, &info, current); + + die_if_kernel("Oops - address exception", regs, mode); +} + +asmlinkage void do_unexp_fiq (struct pt_regs *regs) +{ +#ifndef CONFIG_IGNORE_FIQ + printk("Hmm. Unexpected FIQ received, but trying to continue\n"); + printk("You may have a hardware problem...\n"); +#endif +} + +/* + * bad_mode handles the impossible case in the vectors. If you see one of + * these, then it's extremely serious, and could mean you have buggy hardware. + * It never returns, and never tries to sync. We hope that we can at least + * dump out some state information... + */ +asmlinkage void bad_mode(struct pt_regs *regs, int reason, int proc_mode) +{ + unsigned int vectors = vectors_base(); + + console_verbose(); + + printk(KERN_CRIT "Bad mode in %s handler detected: mode %s\n", + handler[reason<5?reason:4], processor_modes[proc_mode]); + + /* + * Dump out the vectors and stub routines. Maybe a better solution + * would be to dump them out only if we detect that they are corrupted. + */ + dump_mem(KERN_CRIT "Vectors: ", vectors, vectors + 0x40); + dump_mem(KERN_CRIT "Stubs: ", vectors + 0x200, vectors + 0x4b8); + + die("Oops", regs, 0); + local_irq_disable(); + panic("bad mode"); +} + +static int bad_syscall(int n, struct pt_regs *regs) +{ + struct thread_info *thread = current_thread_info(); + siginfo_t info; + + if (current->personality != PER_LINUX && thread->exec_domain->handler) { + thread->exec_domain->handler(n, regs); + return regs->ARM_r0; + } + +#ifdef CONFIG_DEBUG_USER + printk(KERN_ERR "[%d] %s: obsolete system call %08x.\n", + current->pid, current->comm, n); + dump_instr(regs); +#endif + + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLTRP; + info.si_addr = (void *)instruction_pointer(regs) - 4; + + force_sig_info(SIGILL, &info, current); + die_if_kernel("Oops", regs, n); + return regs->ARM_r0; +} + +static inline void +do_cache_op(unsigned long start, unsigned long end, int flags) +{ + struct vm_area_struct *vma; + + if (end < start) + return; + + vma = find_vma(current->active_mm, start); + if (vma && vma->vm_start < end) { + if (start < vma->vm_start) + start = vma->vm_start; + if (end > vma->vm_end) + end = vma->vm_end; + } +} + +/* + * Handle all unrecognised system calls. + * 0x9f0000 - 0x9fffff are some more esoteric system calls + */ +#define NR(x) ((__ARM_NR_##x) - __ARM_NR_BASE) +asmlinkage int arm_syscall(int no, struct pt_regs *regs) +{ + siginfo_t info; + + if ((no >> 16) != 0x9f) + return bad_syscall(no, regs); + + switch (no & 0xffff) { + case 0: /* branch through 0 */ + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = SEGV_MAPERR; + info.si_addr = NULL; + + force_sig_info(SIGSEGV, &info, current); + + die_if_kernel("branch through zero", regs, 0); + return 0; + + case NR(breakpoint): /* SWI BREAK_POINT */ + ptrace_break(current, regs); + return regs->ARM_r0; + + case NR(cacheflush): + return 0; + + case NR(usr26): + break; + + default: + /* Calls 9f00xx..9f07ff are defined to return -ENOSYS + if not implemented, rather than raising SIGILL. This + way the calling program can gracefully determine whether + a feature is supported. */ + if (no <= 0x7ff) + return -ENOSYS; + break; + } +#ifdef CONFIG_DEBUG_USER + /* + * experience shows that these seem to indicate that + * something catastrophic has happened + */ + printk("[%d] %s: arm syscall %d\n", current->pid, current->comm, no); + dump_instr(regs); + if (user_mode(regs)) { + show_regs(regs); + c_backtrace(regs->ARM_fp, processor_mode(regs)); + } +#endif + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLTRP; + info.si_addr = (void *)instruction_pointer(regs) - 4; + + force_sig_info(SIGILL, &info, current); + die_if_kernel("Oops", regs, no); + return 0; +} + +void __bad_xchg(volatile void *ptr, int size) +{ + printk("xchg: bad data size: pc 0x%p, ptr 0x%p, size %d\n", + __builtin_return_address(0), ptr, size); + BUG(); +} + +/* + * A data abort trap was taken, but we did not handle the instruction. + * Try to abort the user program, or panic if it was the kernel. + */ +asmlinkage void +baddataabort(int code, unsigned long instr, struct pt_regs *regs) +{ + unsigned long addr = instruction_pointer(regs); + siginfo_t info; + +#ifdef CONFIG_DEBUG_USER + printk(KERN_ERR "[%d] %s: bad data abort: code %d instr 0x%08lx\n", + current->pid, current->comm, code, instr); + dump_instr(regs); + show_pte(current->mm, addr); +#endif + + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLOPC; + info.si_addr = (void *)addr; + + force_sig_info(SIGILL, &info, current); + die_if_kernel("unknown data abort code", regs, instr); +} + +volatile void __bug(const char *file, int line, void *data) +{ + printk(KERN_CRIT"kernel BUG at %s:%d!", file, line); + if (data) + printk(KERN_CRIT" - extra data = %p", data); + printk("\n"); + *(int *)0 = 0; +} + +void __readwrite_bug(const char *fn) +{ + printk("%s called, but not implemented", fn); + BUG(); +} + +void __pte_error(const char *file, int line, unsigned long val) +{ + printk("%s:%d: bad pte %08lx.\n", file, line, val); +} + +void __pmd_error(const char *file, int line, unsigned long val) +{ + printk("%s:%d: bad pmd %08lx.\n", file, line, val); +} + +void __pgd_error(const char *file, int line, unsigned long val) +{ + printk("%s:%d: bad pgd %08lx.\n", file, line, val); +} + +asmlinkage void __div0(void) +{ + printk("Division by zero in kernel.\n"); + dump_stack(); +} + +void abort(void) +{ + BUG(); + + /* if that doesn't kill us, halt */ + panic("Oops failed to kill thread"); +} + +void __init trap_init(void) +{ + extern void __trap_init(unsigned long); + unsigned long base = vectors_base(); + + __trap_init(base); + if (base != 0) + printk(KERN_DEBUG "Relocating machine vectors to 0x%08lx\n", + base); +} diff --git a/arch/arm26/kernel/vmlinux-arm26-xip.lds.in b/arch/arm26/kernel/vmlinux-arm26-xip.lds.in new file mode 100644 index 00000000000..ca61ec8218f --- /dev/null +++ b/arch/arm26/kernel/vmlinux-arm26-xip.lds.in @@ -0,0 +1,134 @@ +/* ld script to make ARM Linux kernel + * taken from the i386 version by Russell King + * Written by Martin Mares <mj@atrey.karlin.mff.cuni.cz> + * borrowed from Russels ARM port by Ian Molton + */ + +#include <asm-generic/vmlinux.lds.h> + +OUTPUT_ARCH(arm) +ENTRY(stext) +jiffies = jiffies_64; +SECTIONS +{ + . = TEXTADDR; + .init : { /* Init code and data */ + _stext = .; + __init_begin = .; + _sinittext = .; + *(.init.text) + _einittext = .; + __proc_info_begin = .; + *(.proc.info) + __proc_info_end = .; + __arch_info_begin = .; + *(.arch.info) + __arch_info_end = .; + __tagtable_begin = .; + *(.taglist) + __tagtable_end = .; + . = ALIGN(16); + __setup_start = .; + *(.init.setup) + __setup_end = .; + __early_begin = .; + *(__early_param) + __early_end = .; + __initcall_start = .; + *(.initcall1.init) + *(.initcall2.init) + *(.initcall3.init) + *(.initcall4.init) + *(.initcall5.init) + *(.initcall6.init) + *(.initcall7.init) + __initcall_end = .; + __con_initcall_start = .; + *(.con_initcall.init) + __con_initcall_end = .; + . = ALIGN(32); + __initramfs_start = .; + usr/built-in.o(.init.ramfs) + __initramfs_end = .; + . = ALIGN(32768); + __init_end = .; + } + + /DISCARD/ : { /* Exit code and data */ + *(.exit.text) + *(.exit.data) + *(.exitcall.exit) + } + + .text : { /* Real text segment */ + _text = .; /* Text and read-only data */ + *(.text) + SCHED_TEXT + LOCK_TEXT /* FIXME - borrowed from arm32 - check*/ + *(.fixup) + *(.gnu.warning) + *(.rodata) + *(.rodata.*) + *(.glue_7) + *(.glue_7t) + *(.got) /* Global offset table */ + + _etext = .; /* End of text section */ + } + + . = ALIGN(16); + __ex_table : { /* Exception table */ + __start___ex_table = .; + *(__ex_table) + __stop___ex_table = .; + } + + RODATA + + _endtext = .; + + . = DATAADDR; + + _sdata = .; + + .data : { + . = ALIGN(8192); + /* + * first, the init thread union, aligned + * to an 8192 byte boundary. (see arm26/kernel/init_task.c) + * FIXME - sould this be 32K aligned on arm26? + */ + *(.init.task) + + /* + * The cacheline aligned data + */ + . = ALIGN(32); + *(.data.cacheline_aligned) + + /* + * and the usual data section + */ + *(.data) + CONSTRUCTORS + + *(.init.data) + + _edata = .; + } + + .bss : { + __bss_start = .; /* BSS */ + *(.bss) + *(COMMON) + _end = . ; + } + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } +} diff --git a/arch/arm26/kernel/vmlinux-arm26.lds.in b/arch/arm26/kernel/vmlinux-arm26.lds.in new file mode 100644 index 00000000000..d1d3418d7eb --- /dev/null +++ b/arch/arm26/kernel/vmlinux-arm26.lds.in @@ -0,0 +1,127 @@ +/* ld script to make ARM Linux kernel + * taken from the i386 version by Russell King + * Written by Martin Mares <mj@atrey.karlin.mff.cuni.cz> + * borrowed from Russels ARM port by Ian Molton and subsequently modified. + */ + +#include <asm-generic/vmlinux.lds.h> + +OUTPUT_ARCH(arm) +ENTRY(stext) +jiffies = jiffies_64; +SECTIONS +{ + . = TEXTADDR; + .init : { /* Init code and data */ + _stext = .; + __init_begin = .; + _sinittext = .; + *(.init.text) + _einittext = .; + __proc_info_begin = .; + *(.proc.info) + __proc_info_end = .; + __arch_info_begin = .; + *(.arch.info) + __arch_info_end = .; + __tagtable_begin = .; + *(.taglist) + __tagtable_end = .; + *(.init.data) + . = ALIGN(16); + __setup_start = .; + *(.init.setup) + __setup_end = .; + __early_begin = .; + *(__early_param) + __early_end = .; + __initcall_start = .; + *(.initcall1.init) + *(.initcall2.init) + *(.initcall3.init) + *(.initcall4.init) + *(.initcall5.init) + *(.initcall6.init) + *(.initcall7.init) + __initcall_end = .; + __con_initcall_start = .; + *(.con_initcall.init) + __con_initcall_end = .; + . = ALIGN(32); + __initramfs_start = .; + usr/built-in.o(.init.ramfs) + __initramfs_end = .; + . = ALIGN(32768); + __init_end = .; + } + + /DISCARD/ : { /* Exit code and data */ + *(.exit.text) + *(.exit.data) + *(.exitcall.exit) + } + + .text : { /* Real text segment */ + _text = .; /* Text and read-only data */ + *(.text) + SCHED_TEXT + LOCK_TEXT + *(.fixup) + *(.gnu.warning) + *(.rodata) + *(.rodata.*) + *(.glue_7) + *(.glue_7t) + *(.got) /* Global offset table */ + + _etext = .; /* End of text section */ + } + + . = ALIGN(16); + __ex_table : { /* Exception table */ + __start___ex_table = .; + *(__ex_table) + __stop___ex_table = .; + } + + RODATA + + . = ALIGN(8192); + + .data : { + /* + * first, the init task union, aligned + * to an 8192 byte boundary. (see arm26/kernel/init_task.c) + */ + *(.init.task) + + /* + * The cacheline aligned data + */ + . = ALIGN(32); + *(.data.cacheline_aligned) + + /* + * and the usual data section + */ + *(.data) + CONSTRUCTORS + + _edata = .; + } + + .bss : { + __bss_start = .; /* BSS */ + *(.bss) + *(COMMON) + _end = . ; + } + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } +} diff --git a/arch/arm26/kernel/vmlinux.lds.S b/arch/arm26/kernel/vmlinux.lds.S new file mode 100644 index 00000000000..811a6904801 --- /dev/null +++ b/arch/arm26/kernel/vmlinux.lds.S @@ -0,0 +1,12 @@ +#include <linux/config.h> + +#ifdef CONFIG_XIP_KERNEL + +#include "vmlinux-arm26-xip.lds.in" + +#else + +#include "vmlinux-arm26.lds.in" + +#endif + |