diff options
author | David Howells <dhowells@redhat.com> | 2008-02-08 04:19:31 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-08 09:22:30 -0800 |
commit | b920de1b77b72ca9432ac3f97edb26541e65e5dd (patch) | |
tree | 40fa9be1470e929c47927dea7eddf184c0204229 /arch/mn10300/kernel | |
parent | ef3d534754f31fed9c3b976fee1ece1b3bc38282 (diff) | |
download | linux-b920de1b77b72ca9432ac3f97edb26541e65e5dd.tar.gz linux-b920de1b77b72ca9432ac3f97edb26541e65e5dd.tar.bz2 linux-b920de1b77b72ca9432ac3f97edb26541e65e5dd.zip |
mn10300: add the MN10300/AM33 architecture to the kernel
Add architecture support for the MN10300/AM33 CPUs produced by MEI to the
kernel.
This patch also adds board support for the ASB2303 with the ASB2308 daughter
board, and the ASB2305. The only processor supported is the MN103E010, which
is an AM33v2 core plus on-chip devices.
[akpm@linux-foundation.org: nuke cvs control strings]
Signed-off-by: Masakazu Urade <urade.masakazu@jp.panasonic.com>
Signed-off-by: Koichi Yasutake <yasutake.koichi@jp.panasonic.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'arch/mn10300/kernel')
42 files changed, 10913 insertions, 0 deletions
diff --git a/arch/mn10300/kernel/Makefile b/arch/mn10300/kernel/Makefile new file mode 100644 index 000000000000..ef07c956170a --- /dev/null +++ b/arch/mn10300/kernel/Makefile @@ -0,0 +1,27 @@ +# +# Makefile for the MN10300-specific core kernel code +# +extra-y := head.o init_task.o vmlinux.lds + +obj-y := process.o semaphore.o signal.o entry.o fpu.o traps.o irq.o \ + ptrace.o setup.o time.o sys_mn10300.o io.o kthread.o \ + switch_to.o mn10300_ksyms.o kernel_execve.o + +obj-$(CONFIG_MN10300_WD_TIMER) += mn10300-watchdog.o mn10300-watchdog-low.o + +obj-$(CONFIG_FPU) += fpu-low.o + +obj-$(CONFIG_MN10300_TTYSM) += mn10300-serial.o mn10300-serial-low.o \ + mn10300-debug.o +obj-$(CONFIG_GDBSTUB) += gdb-stub.o gdb-low.o +obj-$(CONFIG_GDBSTUB_ON_TTYSx) += gdb-io-serial.o gdb-io-serial-low.o +obj-$(CONFIG_GDBSTUB_ON_TTYSMx) += gdb-io-ttysm.o gdb-io-ttysm-low.o + +ifneq ($(CONFIG_MN10300_CACHE_DISABLED),y) +obj-$(CONFIG_GDBSTUB) += gdb-cache.o +endif + +obj-$(CONFIG_MN10300_RTC) += rtc.o +obj-$(CONFIG_PROFILE) += profile.o profile-low.o +obj-$(CONFIG_MODULES) += module.o +obj-$(CONFIG_KPROBES) += kprobes.o diff --git a/arch/mn10300/kernel/asm-offsets.c b/arch/mn10300/kernel/asm-offsets.c new file mode 100644 index 000000000000..ee2d9f8af5ad --- /dev/null +++ b/arch/mn10300/kernel/asm-offsets.c @@ -0,0 +1,108 @@ +/* + * Generate definitions needed by assembly language modules. + * This code generates raw asm output which is post-processed + * to extract and format the required data. + */ + +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/personality.h> +#include <asm/ucontext.h> +#include <asm/processor.h> +#include <asm/thread_info.h> +#include <asm/ptrace.h> +#include "sigframe.h" +#include "mn10300-serial.h" + +#define DEFINE(sym, val) \ + asm volatile("\n->" #sym " %0 " #val : : "i" (val)) + +#define BLANK() asm volatile("\n->") + +#define OFFSET(sym, str, mem) \ + DEFINE(sym, offsetof(struct str, mem)); + +void foo(void) +{ + OFFSET(SIGCONTEXT_d0, sigcontext, d0); + OFFSET(SIGCONTEXT_d1, sigcontext, d1); + BLANK(); + + OFFSET(TI_task, thread_info, task); + OFFSET(TI_exec_domain, thread_info, exec_domain); + OFFSET(TI_flags, thread_info, flags); + OFFSET(TI_cpu, thread_info, cpu); + OFFSET(TI_preempt_count, thread_info, preempt_count); + OFFSET(TI_addr_limit, thread_info, addr_limit); + OFFSET(TI_restart_block, thread_info, restart_block); + BLANK(); + + OFFSET(REG_D0, pt_regs, d0); + OFFSET(REG_D1, pt_regs, d1); + OFFSET(REG_D2, pt_regs, d2); + OFFSET(REG_D3, pt_regs, d3); + OFFSET(REG_A0, pt_regs, a0); + OFFSET(REG_A1, pt_regs, a1); + OFFSET(REG_A2, pt_regs, a2); + OFFSET(REG_A3, pt_regs, a3); + OFFSET(REG_E0, pt_regs, e0); + OFFSET(REG_E1, pt_regs, e1); + OFFSET(REG_E2, pt_regs, e2); + OFFSET(REG_E3, pt_regs, e3); + OFFSET(REG_E4, pt_regs, e4); + OFFSET(REG_E5, pt_regs, e5); + OFFSET(REG_E6, pt_regs, e6); + OFFSET(REG_E7, pt_regs, e7); + OFFSET(REG_SP, pt_regs, sp); + OFFSET(REG_EPSW, pt_regs, epsw); + OFFSET(REG_PC, pt_regs, pc); + OFFSET(REG_LAR, pt_regs, lar); + OFFSET(REG_LIR, pt_regs, lir); + OFFSET(REG_MDR, pt_regs, mdr); + OFFSET(REG_MCVF, pt_regs, mcvf); + OFFSET(REG_MCRL, pt_regs, mcrl); + OFFSET(REG_MCRH, pt_regs, mcrh); + OFFSET(REG_MDRQ, pt_regs, mdrq); + OFFSET(REG_ORIG_D0, pt_regs, orig_d0); + OFFSET(REG_NEXT, pt_regs, next); + DEFINE(REG__END, sizeof(struct pt_regs)); + BLANK(); + + OFFSET(THREAD_UREGS, thread_struct, uregs); + OFFSET(THREAD_PC, thread_struct, pc); + OFFSET(THREAD_SP, thread_struct, sp); + OFFSET(THREAD_A3, thread_struct, a3); + OFFSET(THREAD_USP, thread_struct, usp); + OFFSET(THREAD_FRAME, thread_struct, __frame); + BLANK(); + + DEFINE(CLONE_VM_asm, CLONE_VM); + DEFINE(CLONE_FS_asm, CLONE_FS); + DEFINE(CLONE_FILES_asm, CLONE_FILES); + DEFINE(CLONE_SIGHAND_asm, CLONE_SIGHAND); + DEFINE(CLONE_UNTRACED_asm, CLONE_UNTRACED); + DEFINE(SIGCHLD_asm, SIGCHLD); + BLANK(); + + OFFSET(EXEC_DOMAIN_handler, exec_domain, handler); + OFFSET(RT_SIGFRAME_sigcontext, rt_sigframe, uc.uc_mcontext); + + DEFINE(PAGE_SIZE_asm, PAGE_SIZE); + + OFFSET(__rx_buffer, mn10300_serial_port, rx_buffer); + OFFSET(__rx_inp, mn10300_serial_port, rx_inp); + OFFSET(__rx_outp, mn10300_serial_port, rx_outp); + OFFSET(__tx_info_buffer, mn10300_serial_port, uart.info); + OFFSET(__tx_xchar, mn10300_serial_port, tx_xchar); + OFFSET(__tx_break, mn10300_serial_port, tx_break); + OFFSET(__intr_flags, mn10300_serial_port, intr_flags); + OFFSET(__rx_icr, mn10300_serial_port, rx_icr); + OFFSET(__tx_icr, mn10300_serial_port, tx_icr); + OFFSET(__tm_icr, mn10300_serial_port, _tmicr); + OFFSET(__iobase, mn10300_serial_port, _iobase); + + DEFINE(__UART_XMIT_SIZE, UART_XMIT_SIZE); + OFFSET(__xmit_buffer, uart_info, xmit.buf); + OFFSET(__xmit_head, uart_info, xmit.head); + OFFSET(__xmit_tail, uart_info, xmit.tail); +} diff --git a/arch/mn10300/kernel/entry.S b/arch/mn10300/kernel/entry.S new file mode 100644 index 000000000000..11de3606eee6 --- /dev/null +++ b/arch/mn10300/kernel/entry.S @@ -0,0 +1,721 @@ +############################################################################### +# +# MN10300 Exception and interrupt entry points +# +# Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Modified by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/smp.h> +#include <asm/system.h> +#include <asm/thread_info.h> +#include <asm/intctl-regs.h> +#include <asm/busctl-regs.h> +#include <asm/timer-regs.h> +#include <asm/unit/leds.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/errno.h> +#include <asm/asm-offsets.h> +#include <asm/frame.inc> + +#ifdef CONFIG_PREEMPT +#define preempt_stop __cli +#else +#define preempt_stop +#define resume_kernel restore_all +#endif + + .macro __cli + and ~EPSW_IM,epsw + or EPSW_IE|MN10300_CLI_LEVEL,epsw + nop + nop + nop + .endm + .macro __sti + or EPSW_IE|EPSW_IM_7,epsw + .endm + + + .am33_2 + +############################################################################### +# +# the return path for a forked child +# - on entry, D0 holds the address of the previous task to run +# +############################################################################### +ENTRY(ret_from_fork) + call schedule_tail[],0 + GET_THREAD_INFO a2 + + # return 0 to indicate child process + clr d0 + mov d0,(REG_D0,fp) + jmp syscall_exit + +############################################################################### +# +# system call handler +# +############################################################################### +ENTRY(system_call) + add -4,sp + SAVE_ALL + mov d0,(REG_ORIG_D0,fp) + GET_THREAD_INFO a2 + cmp nr_syscalls,d0 + bcc syscall_badsys + btst _TIF_SYSCALL_TRACE,(TI_flags,a2) + bne syscall_trace_entry +syscall_call: + add d0,d0,a1 + add a1,a1 + mov (REG_A0,fp),d0 + mov (sys_call_table,a1),a0 + calls (a0) + mov d0,(REG_D0,fp) +syscall_exit: + # make sure we don't miss an interrupt setting need_resched or + # sigpending between sampling and the rti + __cli + mov (TI_flags,a2),d2 + btst _TIF_ALLWORK_MASK,d2 + bne syscall_exit_work +restore_all: + RESTORE_ALL + +############################################################################### +# +# perform work that needs to be done immediately before resumption and syscall +# tracing +# +############################################################################### + ALIGN +syscall_exit_work: + btst _TIF_SYSCALL_TRACE,d2 + beq work_pending + __sti # could let do_syscall_trace() call + # schedule() instead + mov fp,d0 + mov 1,d1 + call do_syscall_trace[],0 # do_syscall_trace(regs,entryexit) + jmp resume_userspace + + ALIGN +work_pending: + btst _TIF_NEED_RESCHED,d2 + beq work_notifysig + +work_resched: + call schedule[],0 + + # make sure we don't miss an interrupt setting need_resched or + # sigpending between sampling and the rti + __cli + + # is there any work to be done other than syscall tracing? + mov (TI_flags,a2),d2 + btst _TIF_WORK_MASK,d2 + beq restore_all + btst _TIF_NEED_RESCHED,d2 + bne work_resched + + # deal with pending signals and notify-resume requests +work_notifysig: + mov fp,d0 + mov d2,d1 + call do_notify_resume[],0 + jmp resume_userspace + + # perform syscall entry tracing +syscall_trace_entry: + mov -ENOSYS,d0 + mov d0,(REG_D0,fp) + mov fp,d0 + clr d1 + call do_syscall_trace[],0 + mov (REG_ORIG_D0,fp),d0 + mov (REG_D1,fp),d1 + cmp nr_syscalls,d0 + bcs syscall_call + jmp syscall_exit + +syscall_badsys: + mov -ENOSYS,d0 + mov d0,(REG_D0,fp) + jmp resume_userspace + + # userspace resumption stub bypassing syscall exit tracing + .globl ret_from_exception, ret_from_intr + ALIGN +ret_from_exception: + preempt_stop +ret_from_intr: + GET_THREAD_INFO a2 + mov (REG_EPSW,fp),d0 # need to deliver signals before + # returning to userspace + and EPSW_nSL,d0 + beq resume_kernel # returning to supervisor mode + +ENTRY(resume_userspace) + # make sure we don't miss an interrupt setting need_resched or + # sigpending between sampling and the rti + __cli + + # is there any work to be done on int/exception return? + mov (TI_flags,a2),d2 + btst _TIF_WORK_MASK,d2 + bne work_pending + jmp restore_all + +#ifdef CONFIG_PREEMPT +ENTRY(resume_kernel) + mov (TI_preempt_count,a2),d0 # non-zero preempt_count ? + cmp 0,d0 + bne restore_all + +need_resched: + btst _TIF_NEED_RESCHED,(TI_flags,a2) + beq restore_all + mov (REG_EPSW,fp),d0 + and EPSW_IM,d0 + cmp EPSW_IM_7,d0 # interrupts off (exception path) ? + beq restore_all + call preempt_schedule_irq[],0 + jmp need_resched +#endif + + +############################################################################### +# +# IRQ handler entry point +# - intended to be entered at multiple priorities +# +############################################################################### +ENTRY(irq_handler) + add -4,sp + SAVE_ALL + + # it's not a syscall + mov 0xffffffff,d0 + mov d0,(REG_ORIG_D0,fp) + + mov fp,d0 + call do_IRQ[],0 # do_IRQ(regs) + + jmp ret_from_intr + +############################################################################### +# +# Monitor Signal handler entry point +# +############################################################################### +ENTRY(monitor_signal) + movbu (0xae000001),d1 + cmp 1,d1 + beq monsignal + ret [],0 + +monsignal: + or EPSW_NMID,epsw + mov d0,a0 + mov a0,sp + mov (REG_EPSW,fp),d1 + and ~EPSW_nSL,d1 + mov d1,(REG_EPSW,fp) + movm (sp),[d2,d3,a2,a3,exreg0,exreg1,exother] + mov (sp),a1 + mov a1,usp + movm (sp),[other] + add 4,sp +here: jmp 0x8e000008-here+0x8e000008 + +############################################################################### +# +# Double Fault handler entry point +# - note that there will not be a stack, D0/A0 will hold EPSW/PC as were +# +############################################################################### + .section .bss + .balign THREAD_SIZE + .space THREAD_SIZE +__df_stack: + .previous + +ENTRY(double_fault) + mov a0,(__df_stack-4) # PC as was + mov d0,(__df_stack-8) # EPSW as was + mn10300_set_dbfleds # display 'db-f' on the LEDs + mov 0xaa55aa55,d0 + mov d0,(__df_stack-12) # no ORIG_D0 + mov sp,a0 # save corrupted SP + mov __df_stack-12,sp # emergency supervisor stack + SAVE_ALL + mov a0,(REG_A0,fp) # save corrupted SP as A0 (which got + # clobbered by the CPU) + mov fp,d0 + calls do_double_fault +double_fault_loop: + bra double_fault_loop + +############################################################################### +# +# Bus Error handler entry point +# - handle external (async) bus errors separately +# +############################################################################### +ENTRY(raw_bus_error) + add -4,sp + mov d0,(sp) + mov (BCBERR),d0 # what + btst BCBERR_BEMR_DMA,d0 # see if it was an external bus error + beq __common_exception_aux # it wasn't + + SAVE_ALL + mov (BCBEAR),d1 # destination of erroneous access + + mov (REG_ORIG_D0,fp),d2 + mov d2,(REG_D0,fp) + mov -1,d2 + mov d2,(REG_ORIG_D0,fp) + + add -4,sp + mov fp,(12,sp) # frame pointer + call io_bus_error[],0 + jmp restore_all + +############################################################################### +# +# Miscellaneous exception entry points +# +############################################################################### +ENTRY(nmi_handler) + add -4,sp + mov d0,(sp) + mov (TBR),d0 + bra __common_exception_nonmi + +ENTRY(__common_exception) + add -4,sp + mov d0,(sp) + +__common_exception_aux: + mov (TBR),d0 + and ~EPSW_NMID,epsw # turn NMIs back on if not NMI + or EPSW_IE,epsw + +__common_exception_nonmi: + and 0x0000FFFF,d0 # turn the exception code into a vector + # table index + + btst 0x00000007,d0 + bne 1f + cmp 0x00000400,d0 + bge 1f + + SAVE_ALL # build the stack frame + + mov (REG_D0,fp),a2 # get the exception number + mov (REG_ORIG_D0,fp),d0 + mov d0,(REG_D0,fp) + mov -1,d0 + mov d0,(REG_ORIG_D0,fp) + +#ifdef CONFIG_GDBSTUB + btst 0x01,(gdbstub_busy) + beq 2f + and ~EPSW_IE,epsw + mov fp,d0 + mov a2,d1 + call gdbstub_exception[],0 # gdbstub itself caused an exception + bra restore_all +2: +#endif + + mov fp,d0 # arg 0: stacked register file + mov a2,d1 # arg 1: exception number + lsr 1,a2 + + mov (exception_table,a2),a2 + calls (a2) + jmp ret_from_exception + +1: pi # BUG() equivalent + +############################################################################### +# +# Exception handler functions table +# +############################################################################### + .data +ENTRY(exception_table) + .rept 0x400>>1 + .long uninitialised_exception + .endr + .previous + +############################################################################### +# +# Change an entry in the exception table +# - D0 exception code, D1 handler +# +############################################################################### +ENTRY(set_excp_vector) + lsr 1,d0 + add exception_table,d0 + mov d1,(d0) + mov 4,d1 +#if defined(CONFIG_MN10300_CACHE_WBACK) + jmp mn10300_dcache_flush_inv_range2 +#else + ret [],0 +#endif + +############################################################################### +# +# System call table +# +############################################################################### + .data +ENTRY(sys_call_table) + .long sys_restart_syscall /* 0 */ + .long sys_exit + .long sys_fork + .long sys_read + .long sys_write + .long sys_open /* 5 */ + .long sys_close + .long sys_waitpid + .long sys_creat + .long sys_link + .long sys_unlink /* 10 */ + .long sys_execve + .long sys_chdir + .long sys_time + .long sys_mknod + .long sys_chmod /* 15 */ + .long sys_lchown16 + .long sys_ni_syscall /* old break syscall holder */ + .long sys_stat + .long sys_lseek + .long sys_getpid /* 20 */ + .long sys_mount + .long sys_oldumount + .long sys_setuid16 + .long sys_getuid16 + .long sys_stime /* 25 */ + .long sys_ptrace + .long sys_alarm + .long sys_fstat + .long sys_pause + .long sys_utime /* 30 */ + .long sys_ni_syscall /* old stty syscall holder */ + .long sys_ni_syscall /* old gtty syscall holder */ + .long sys_access + .long sys_nice + .long sys_ni_syscall /* 35 - old ftime syscall holder */ + .long sys_sync + .long sys_kill + .long sys_rename + .long sys_mkdir + .long sys_rmdir /* 40 */ + .long sys_dup + .long sys_pipe + .long sys_times + .long sys_ni_syscall /* old prof syscall holder */ + .long sys_brk /* 45 */ + .long sys_setgid16 + .long sys_getgid16 + .long sys_signal + .long sys_geteuid16 + .long sys_getegid16 /* 50 */ + .long sys_acct + .long sys_umount /* recycled never used phys() */ + .long sys_ni_syscall /* old lock syscall holder */ + .long sys_ioctl + .long sys_fcntl /* 55 */ + .long sys_ni_syscall /* old mpx syscall holder */ + .long sys_setpgid + .long sys_ni_syscall /* old ulimit syscall holder */ + .long sys_ni_syscall /* old sys_olduname */ + .long sys_umask /* 60 */ + .long sys_chroot + .long sys_ustat + .long sys_dup2 + .long sys_getppid + .long sys_getpgrp /* 65 */ + .long sys_setsid + .long sys_sigaction + .long sys_sgetmask + .long sys_ssetmask + .long sys_setreuid16 /* 70 */ + .long sys_setregid16 + .long sys_sigsuspend + .long sys_sigpending + .long sys_sethostname + .long sys_setrlimit /* 75 */ + .long sys_old_getrlimit + .long sys_getrusage + .long sys_gettimeofday + .long sys_settimeofday + .long sys_getgroups16 /* 80 */ + .long sys_setgroups16 + .long old_select + .long sys_symlink + .long sys_lstat + .long sys_readlink /* 85 */ + .long sys_uselib + .long sys_swapon + .long sys_reboot + .long old_readdir + .long old_mmap /* 90 */ + .long sys_munmap + .long sys_truncate + .long sys_ftruncate + .long sys_fchmod + .long sys_fchown16 /* 95 */ + .long sys_getpriority + .long sys_setpriority + .long sys_ni_syscall /* old profil syscall holder */ + .long sys_statfs + .long sys_fstatfs /* 100 */ + .long sys_ni_syscall /* ioperm */ + .long sys_socketcall + .long sys_syslog + .long sys_setitimer + .long sys_getitimer /* 105 */ + .long sys_newstat + .long sys_newlstat + .long sys_newfstat + .long sys_ni_syscall /* old sys_uname */ + .long sys_ni_syscall /* 110 - iopl */ + .long sys_vhangup + .long sys_ni_syscall /* old "idle" system call */ + .long sys_ni_syscall /* vm86old */ + .long sys_wait4 + .long sys_swapoff /* 115 */ + .long sys_sysinfo + .long sys_ipc + .long sys_fsync + .long sys_sigreturn + .long sys_clone /* 120 */ + .long sys_setdomainname + .long sys_newuname + .long sys_ni_syscall /* modify_ldt */ + .long sys_adjtimex + .long sys_mprotect /* 125 */ + .long sys_sigprocmask + .long sys_ni_syscall /* old "create_module" */ + .long sys_init_module + .long sys_delete_module + .long sys_ni_syscall /* 130: old "get_kernel_syms" */ + .long sys_quotactl + .long sys_getpgid + .long sys_fchdir + .long sys_bdflush + .long sys_sysfs /* 135 */ + .long sys_personality + .long sys_ni_syscall /* reserved for afs_syscall */ + .long sys_setfsuid16 + .long sys_setfsgid16 + .long sys_llseek /* 140 */ + .long sys_getdents + .long sys_select + .long sys_flock + .long sys_msync + .long sys_readv /* 145 */ + .long sys_writev + .long sys_getsid + .long sys_fdatasync + .long sys_sysctl + .long sys_mlock /* 150 */ + .long sys_munlock + .long sys_mlockall + .long sys_munlockall + .long sys_sched_setparam + .long sys_sched_getparam /* 155 */ + .long sys_sched_setscheduler + .long sys_sched_getscheduler + .long sys_sched_yield + .long sys_sched_get_priority_max + .long sys_sched_get_priority_min /* 160 */ + .long sys_sched_rr_get_interval + .long sys_nanosleep + .long sys_mremap + .long sys_setresuid16 + .long sys_getresuid16 /* 165 */ + .long sys_ni_syscall /* vm86 */ + .long sys_ni_syscall /* Old sys_query_module */ + .long sys_poll + .long sys_nfsservctl + .long sys_setresgid16 /* 170 */ + .long sys_getresgid16 + .long sys_prctl + .long sys_rt_sigreturn + .long sys_rt_sigaction + .long sys_rt_sigprocmask /* 175 */ + .long sys_rt_sigpending + .long sys_rt_sigtimedwait + .long sys_rt_sigqueueinfo + .long sys_rt_sigsuspend + .long sys_pread64 /* 180 */ + .long sys_pwrite64 + .long sys_chown16 + .long sys_getcwd + .long sys_capget + .long sys_capset /* 185 */ + .long sys_sigaltstack + .long sys_sendfile + .long sys_ni_syscall /* reserved for streams1 */ + .long sys_ni_syscall /* reserved for streams2 */ + .long sys_vfork /* 190 */ + .long sys_getrlimit + .long sys_mmap2 + .long sys_truncate64 + .long sys_ftruncate64 + .long sys_stat64 /* 195 */ + .long sys_lstat64 + .long sys_fstat64 + .long sys_lchown + .long sys_getuid + .long sys_getgid /* 200 */ + .long sys_geteuid + .long sys_getegid + .long sys_setreuid + .long sys_setregid + .long sys_getgroups /* 205 */ + .long sys_setgroups + .long sys_fchown + .long sys_setresuid + .long sys_getresuid + .long sys_setresgid /* 210 */ + .long sys_getresgid + .long sys_chown + .long sys_setuid + .long sys_setgid + .long sys_setfsuid /* 215 */ + .long sys_setfsgid + .long sys_pivot_root + .long sys_mincore + .long sys_madvise + .long sys_getdents64 /* 220 */ + .long sys_fcntl64 + .long sys_ni_syscall /* reserved for TUX */ + .long sys_ni_syscall + .long sys_gettid + .long sys_readahead /* 225 */ + .long sys_setxattr + .long sys_lsetxattr + .long sys_fsetxattr + .long sys_getxattr + .long sys_lgetxattr /* 230 */ + .long sys_fgetxattr + .long sys_listxattr + .long sys_llistxattr + .long sys_flistxattr + .long sys_removexattr /* 235 */ + .long sys_lremovexattr + .long sys_fremovexattr + .long sys_tkill + .long sys_sendfile64 + .long sys_futex /* 240 */ + .long sys_sched_setaffinity + .long sys_sched_getaffinity + .long sys_ni_syscall /* sys_set_thread_area */ + .long sys_ni_syscall /* sys_get_thread_area */ + .long sys_io_setup /* 245 */ + .long sys_io_destroy + .long sys_io_getevents + .long sys_io_submit + .long sys_io_cancel + .long sys_fadvise64 /* 250 */ + .long sys_ni_syscall + .long sys_exit_group + .long sys_lookup_dcookie + .long sys_epoll_create + .long sys_epoll_ctl /* 255 */ + .long sys_epoll_wait + .long sys_remap_file_pages + .long sys_set_tid_address + .long sys_timer_create + .long sys_timer_settime /* 260 */ + .long sys_timer_gettime + .long sys_timer_getoverrun + .long sys_timer_delete + .long sys_clock_settime + .long sys_clock_gettime /* 265 */ + .long sys_clock_getres + .long sys_clock_nanosleep + .long sys_statfs64 + .long sys_fstatfs64 + .long sys_tgkill /* 270 */ + .long sys_utimes + .long sys_fadvise64_64 + .long sys_ni_syscall /* sys_vserver */ + .long sys_mbind + .long sys_get_mempolicy /* 275 */ + .long sys_set_mempolicy + .long sys_mq_open + .long sys_mq_unlink + .long sys_mq_timedsend + .long sys_mq_timedreceive /* 280 */ + .long sys_mq_notify + .long sys_mq_getsetattr + .long sys_kexec_load + .long sys_waitid + .long sys_ni_syscall /* 285 */ /* available */ + .long sys_add_key + .long sys_request_key + .long sys_keyctl + .long sys_cacheflush + .long sys_ioprio_set /* 290 */ + .long sys_ioprio_get + .long sys_inotify_init + .long sys_inotify_add_watch + .long sys_inotify_rm_watch + .long sys_migrate_pages /* 295 */ + .long sys_openat + .long sys_mkdirat + .long sys_mknodat + .long sys_fchownat + .long sys_futimesat /* 300 */ + .long sys_fstatat64 + .long sys_unlinkat + .long sys_renameat + .long sys_linkat + .long sys_symlinkat /* 305 */ + .long sys_readlinkat + .long sys_fchmodat + .long sys_faccessat + .long sys_pselect6 + .long sys_ppoll /* 310 */ + .long sys_unshare + .long sys_set_robust_list + .long sys_get_robust_list + .long sys_splice + .long sys_sync_file_range /* 315 */ + .long sys_tee + .long sys_vmsplice + .long sys_move_pages + .long sys_getcpu + .long sys_epoll_pwait /* 320 */ + .long sys_utimensat + .long sys_signalfd + .long sys_timerfd_create + .long sys_eventfd + .long sys_fallocate /* 325 */ + .long sys_timerfd_settime + .long sys_timerfd_gettime + + +nr_syscalls=(.-sys_call_table)/4 diff --git a/arch/mn10300/kernel/fpu-low.S b/arch/mn10300/kernel/fpu-low.S new file mode 100644 index 000000000000..96cfd47e68d5 --- /dev/null +++ b/arch/mn10300/kernel/fpu-low.S @@ -0,0 +1,197 @@ +/* MN10300 Low level FPU management operations + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <asm/cpu-regs.h> + +############################################################################### +# +# void fpu_init_state(void) +# - initialise the FPU +# +############################################################################### + .globl fpu_init_state + .type fpu_init_state,@function +fpu_init_state: + mov epsw,d0 + or EPSW_FE,epsw + +#ifdef CONFIG_MN10300_PROC_MN103E010 + nop + nop + nop +#endif + fmov 0,fs0 + fmov fs0,fs1 + fmov fs0,fs2 + fmov fs0,fs3 + fmov fs0,fs4 + fmov fs0,fs5 + fmov fs0,fs6 + fmov fs0,fs7 + fmov fs0,fs8 + fmov fs0,fs9 + fmov fs0,fs10 + fmov fs0,fs11 + fmov fs0,fs12 + fmov fs0,fs13 + fmov fs0,fs14 + fmov fs0,fs15 + fmov fs0,fs16 + fmov fs0,fs17 + fmov fs0,fs18 + fmov fs0,fs19 + fmov fs0,fs20 + fmov fs0,fs21 + fmov fs0,fs22 + fmov fs0,fs23 + fmov fs0,fs24 + fmov fs0,fs25 + fmov fs0,fs26 + fmov fs0,fs27 + fmov fs0,fs28 + fmov fs0,fs29 + fmov fs0,fs30 + fmov fs0,fs31 + fmov FPCR_INIT,fpcr + +#ifdef CONFIG_MN10300_PROC_MN103E010 + nop + nop + nop +#endif + mov d0,epsw + ret [],0 + + .size fpu_init_state,.-fpu_init_state + +############################################################################### +# +# void fpu_save(struct fpu_state_struct *) +# - save the fpu state +# - note that an FPU Operational exception might occur during this process +# +############################################################################### + .globl fpu_save + .type fpu_save,@function +fpu_save: + mov epsw,d1 + or EPSW_FE,epsw /* enable the FPU so we can access it */ + +#ifdef CONFIG_MN10300_PROC_MN103E010 + nop + nop +#endif + mov d0,a0 + fmov fs0,(a0+) + fmov fs1,(a0+) + fmov fs2,(a0+) + fmov fs3,(a0+) + fmov fs4,(a0+) + fmov fs5,(a0+) + fmov fs6,(a0+) + fmov fs7,(a0+) + fmov fs8,(a0+) + fmov fs9,(a0+) + fmov fs10,(a0+) + fmov fs11,(a0+) + fmov fs12,(a0+) + fmov fs13,(a0+) + fmov fs14,(a0+) + fmov fs15,(a0+) + fmov fs16,(a0+) + fmov fs17,(a0+) + fmov fs18,(a0+) + fmov fs19,(a0+) + fmov fs20,(a0+) + fmov fs21,(a0+) + fmov fs22,(a0+) + fmov fs23,(a0+) + fmov fs24,(a0+) + fmov fs25,(a0+) + fmov fs26,(a0+) + fmov fs27,(a0+) + fmov fs28,(a0+) + fmov fs29,(a0+) + fmov fs30,(a0+) + fmov fs31,(a0+) + fmov fpcr,d0 + mov d0,(a0) +#ifdef CONFIG_MN10300_PROC_MN103E010 + nop + nop +#endif + + mov d1,epsw + ret [],0 + + .size fpu_save,.-fpu_save + +############################################################################### +# +# void fpu_restore(struct fpu_state_struct *) +# - restore the fpu state +# - note that an FPU Operational exception might occur during this process +# +############################################################################### + .globl fpu_restore + .type fpu_restore,@function +fpu_restore: + mov epsw,d1 + or EPSW_FE,epsw /* enable the FPU so we can access it */ + +#ifdef CONFIG_MN10300_PROC_MN103E010 + nop + nop +#endif + mov d0,a0 + fmov (a0+),fs0 + fmov (a0+),fs1 + fmov (a0+),fs2 + fmov (a0+),fs3 + fmov (a0+),fs4 + fmov (a0+),fs5 + fmov (a0+),fs6 + fmov (a0+),fs7 + fmov (a0+),fs8 + fmov (a0+),fs9 + fmov (a0+),fs10 + fmov (a0+),fs11 + fmov (a0+),fs12 + fmov (a0+),fs13 + fmov (a0+),fs14 + fmov (a0+),fs15 + fmov (a0+),fs16 + fmov (a0+),fs17 + fmov (a0+),fs18 + fmov (a0+),fs19 + fmov (a0+),fs20 + fmov (a0+),fs21 + fmov (a0+),fs22 + fmov (a0+),fs23 + fmov (a0+),fs24 + fmov (a0+),fs25 + fmov (a0+),fs26 + fmov (a0+),fs27 + fmov (a0+),fs28 + fmov (a0+),fs29 + fmov (a0+),fs30 + fmov (a0+),fs31 + mov (a0),d0 + fmov d0,fpcr +#ifdef CONFIG_MN10300_PROC_MN103E010 + nop + nop + nop +#endif + + mov d1,epsw + ret [],0 + + .size fpu_restore,.-fpu_restore diff --git a/arch/mn10300/kernel/fpu.c b/arch/mn10300/kernel/fpu.c new file mode 100644 index 000000000000..e705f25ad5ff --- /dev/null +++ b/arch/mn10300/kernel/fpu.c @@ -0,0 +1,223 @@ +/* MN10300 FPU management + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <asm/uaccess.h> +#include <asm/fpu.h> +#include <asm/elf.h> +#include <asm/exceptions.h> + +struct task_struct *fpu_state_owner; + +/* + * handle an exception due to the FPU being disabled + */ +asmlinkage void fpu_disabled(struct pt_regs *regs, enum exception_code code) +{ + struct task_struct *tsk = current; + + if (!user_mode(regs)) + die_if_no_fixup("An FPU Disabled exception happened in" + " kernel space\n", + regs, code); + +#ifdef CONFIG_FPU + preempt_disable(); + + /* transfer the last process's FPU state to memory */ + if (fpu_state_owner) { + fpu_save(&fpu_state_owner->thread.fpu_state); + fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE; + } + + /* the current process now owns the FPU state */ + fpu_state_owner = tsk; + regs->epsw |= EPSW_FE; + + /* load the FPU with the current process's FPU state or invent a new + * clean one if the process doesn't have one */ + if (is_using_fpu(tsk)) { + fpu_restore(&tsk->thread.fpu_state); + } else { + fpu_init_state(); + set_using_fpu(tsk); + } + + preempt_enable(); +#else + { + siginfo_t info; + + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_addr = (void *) tsk->thread.uregs->pc; + info.si_code = FPE_FLTINV; + + force_sig_info(SIGFPE, &info, tsk); + } +#endif /* CONFIG_FPU */ +} + +/* + * handle an FPU operational exception + * - there's a possibility that if the FPU is asynchronous, the signal might + * be meant for a process other than the current one + */ +asmlinkage void fpu_exception(struct pt_regs *regs, enum exception_code code) +{ + struct task_struct *tsk = fpu_state_owner; + siginfo_t info; + + if (!user_mode(regs)) + die_if_no_fixup("An FPU Operation exception happened in" + " kernel space\n", + regs, code); + + if (!tsk) + die_if_no_fixup("An FPU Operation exception happened," + " but the FPU is not in use", + regs, code); + + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_addr = (void *) tsk->thread.uregs->pc; + info.si_code = FPE_FLTINV; + +#ifdef CONFIG_FPU + { + u32 fpcr; + + /* get FPCR (we need to enable the FPU whilst we do this) */ + asm volatile(" or %1,epsw \n" +#ifdef CONFIG_MN10300_PROC_MN103E010 + " nop \n" + " nop \n" + " nop \n" +#endif + " fmov fpcr,%0 \n" +#ifdef CONFIG_MN10300_PROC_MN103E010 + " nop \n" + " nop \n" + " nop \n" +#endif + " and %2,epsw \n" + : "=&d"(fpcr) + : "i"(EPSW_FE), "i"(~EPSW_FE) + ); + + if (fpcr & FPCR_EC_Z) + info.si_code = FPE_FLTDIV; + else if (fpcr & FPCR_EC_O) + info.si_code = FPE_FLTOVF; + else if (fpcr & FPCR_EC_U) + info.si_code = FPE_FLTUND; + else if (fpcr & FPCR_EC_I) + info.si_code = FPE_FLTRES; + } +#endif + + force_sig_info(SIGFPE, &info, tsk); +} + +/* + * save the FPU state to a signal context + */ +int fpu_setup_sigcontext(struct fpucontext *fpucontext) +{ +#ifdef CONFIG_FPU + struct task_struct *tsk = current; + + if (!is_using_fpu(tsk)) + return 0; + + /* transfer the current FPU state to memory and cause fpu_init() to be + * triggered by the next attempted FPU operation by the current + * process. + */ + preempt_disable(); + + if (fpu_state_owner == tsk) { + fpu_save(&tsk->thread.fpu_state); + fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE; + fpu_state_owner = NULL; + } + + preempt_enable(); + + /* we no longer have a valid current FPU state */ + clear_using_fpu(tsk); + + /* transfer the saved FPU state onto the userspace stack */ + if (copy_to_user(fpucontext, + &tsk->thread.fpu_state, + min(sizeof(struct fpu_state_struct), + sizeof(struct fpucontext)))) + return -1; + + return 1; +#else + return 0; +#endif +} + +/* + * kill a process's FPU state during restoration after signal handling + */ +void fpu_kill_state(struct task_struct *tsk) +{ +#ifdef CONFIG_FPU + /* disown anything left in the FPU */ + preempt_disable(); + + if (fpu_state_owner == tsk) { + fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE; + fpu_state_owner = NULL; + } + + preempt_enable(); +#endif + /* we no longer have a valid current FPU state */ + clear_using_fpu(tsk); +} + +/* + * restore the FPU state from a signal context + */ +int fpu_restore_sigcontext(struct fpucontext *fpucontext) +{ + struct task_struct *tsk = current; + int ret; + + /* load up the old FPU state */ + ret = copy_from_user(&tsk->thread.fpu_state, + fpucontext, + min(sizeof(struct fpu_state_struct), + sizeof(struct fpucontext))); + if (!ret) + set_using_fpu(tsk); + + return ret; +} + +/* + * fill in the FPU structure for a core dump + */ +int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpreg) +{ + struct task_struct *tsk = current; + int fpvalid; + + fpvalid = is_using_fpu(tsk); + if (fpvalid) { + unlazy_fpu(tsk); + memcpy(fpreg, &tsk->thread.fpu_state, sizeof(*fpreg)); + } + + return fpvalid; +} diff --git a/arch/mn10300/kernel/gdb-cache.S b/arch/mn10300/kernel/gdb-cache.S new file mode 100644 index 000000000000..1108badc3d32 --- /dev/null +++ b/arch/mn10300/kernel/gdb-cache.S @@ -0,0 +1,105 @@ +############################################################################### +# +# MN10300 Low-level cache purging routines for gdbstub +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/smp.h> +#include <asm/cache.h> +#include <asm/cpu-regs.h> +#include <asm/exceptions.h> +#include <asm/frame.inc> +#include <asm/serial-regs.h> + + .text + +############################################################################### +# +# GDB stub cache purge +# +############################################################################### + .type gdbstub_purge_cache,@function +ENTRY(gdbstub_purge_cache) + ####################################################################### + # read the addresses tagged in the cache's tag RAM and attempt to flush + # those addresses specifically + # - we rely on the hardware to filter out invalid tag entry addresses + mov DCACHE_TAG(0,0),a0 # dcache tag RAM access address + mov DCACHE_PURGE(0,0),a1 # dcache purge request address + mov L1_CACHE_NWAYS*L1_CACHE_NENTRIES,d1 # total number of entries + +mn10300_dcache_flush_loop: + mov (a0),d0 + and L1_CACHE_TAG_ADDRESS|L1_CACHE_TAG_ENTRY,d0 + or L1_CACHE_TAG_VALID,d0 # retain valid entries in the + # cache + mov d0,(a1) # conditional purge + +mn10300_dcache_flush_skip: + add L1_CACHE_BYTES,a0 + add L1_CACHE_BYTES,a1 + add -1,d1 + bne mn10300_dcache_flush_loop + +;; # unconditionally flush and invalidate the dcache +;; mov DCACHE_PURGE(0,0),a1 # dcache purge request address +;; mov L1_CACHE_NWAYS*L1_CACHE_NENTRIES,d1 # total number of +;; # entries +;; +;; gdbstub_purge_cache__dcache_loop: +;; mov (a1),d0 # unconditional purge +;; +;; add L1_CACHE_BYTES,a1 +;; add -1,d1 +;; bne gdbstub_purge_cache__dcache_loop + + ####################################################################### + # now invalidate the icache + mov CHCTR,a0 + movhu (a0),a1 + + mov epsw,d1 + and ~EPSW_IE,epsw + nop + nop + + # disable the icache + and ~CHCTR_ICEN,d0 + movhu d0,(a0) + + # and wait for it to calm down + setlb + movhu (a0),d0 + btst CHCTR_ICBUSY,d0 + lne + + # invalidate + or CHCTR_ICINV,d0 + movhu d0,(a0) + + # wait for the cache to finish + mov CHCTR,a0 + setlb + movhu (a0),d0 + btst CHCTR_ICBUSY,d0 + lne + + # and reenable it + movhu a1,(a0) + movhu (a0),d0 # read back to flush + # (SIGILLs all over without this) + + mov d1,epsw + + ret [],0 + + .size gdbstub_purge_cache,.-gdbstub_purge_cache diff --git a/arch/mn10300/kernel/gdb-io-serial-low.S b/arch/mn10300/kernel/gdb-io-serial-low.S new file mode 100644 index 000000000000..c68dcd052201 --- /dev/null +++ b/arch/mn10300/kernel/gdb-io-serial-low.S @@ -0,0 +1,90 @@ +############################################################################### +# +# 16550 serial Rx interrupt handler for gdbstub I/O +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/smp.h> +#include <asm/cpu-regs.h> +#include <asm/thread_info.h> +#include <asm/frame.inc> +#include <asm/intctl-regs.h> +#include <asm/unit/serial.h> + + .text + +############################################################################### +# +# GDB stub serial receive interrupt entry point +# - intended to run at interrupt priority 0 +# +############################################################################### + .globl gdbstub_io_rx_handler + .type gdbstub_io_rx_handler,@function +gdbstub_io_rx_handler: + movm [d2,d3,a2,a3],(sp) + +#if 1 + movbu (GDBPORT_SERIAL_IIR),d2 +#endif + + mov (gdbstub_rx_inp),a3 +gdbstub_io_rx_more: + mov a3,a2 + add 2,a3 + and 0x00000fff,a3 + mov (gdbstub_rx_outp),d3 + cmp a3,d3 + beq gdbstub_io_rx_overflow + + movbu (GDBPORT_SERIAL_LSR),d3 + btst UART_LSR_DR,d3 + beq gdbstub_io_rx_done + movbu (GDBPORT_SERIAL_RX),d2 + movbu d3,(gdbstub_rx_buffer+1,a2) + movbu d2,(gdbstub_rx_buffer,a2) + mov a3,(gdbstub_rx_inp) + bra gdbstub_io_rx_more + +gdbstub_io_rx_done: + mov GxICR_DETECT,d2 + movbu d2,(XIRQxICR(GDBPORT_SERIAL_IRQ)) # ACK the interrupt + movhu (XIRQxICR(GDBPORT_SERIAL_IRQ)),d2 # flush + movm (sp),[d2,d3,a2,a3] + bset 0x01,(gdbstub_busy) + beq gdbstub_io_rx_enter + rti + +gdbstub_io_rx_overflow: + bset 0x01,(gdbstub_rx_overflow) + bra gdbstub_io_rx_done + +gdbstub_io_rx_enter: + or EPSW_IE|EPSW_IM_1,epsw + add -4,sp + SAVE_ALL + + mov 0xffffffff,d0 + mov d0,(REG_ORIG_D0,fp) + mov 0x280,d1 + + mov fp,d0 + call gdbstub_rx_irq[],0 # gdbstub_rx_irq(regs,excep) + + and ~EPSW_IE,epsw + bclr 0x01,(gdbstub_busy) + + .globl gdbstub_return +gdbstub_return: + RESTORE_ALL + + .size gdbstub_io_rx_handler,.-gdbstub_io_rx_handler diff --git a/arch/mn10300/kernel/gdb-io-serial.c b/arch/mn10300/kernel/gdb-io-serial.c new file mode 100644 index 000000000000..9a6d4e8ebe73 --- /dev/null +++ b/arch/mn10300/kernel/gdb-io-serial.c @@ -0,0 +1,155 @@ +/* 16550 serial driver for gdbstub I/O + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/nmi.h> + +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/gdb-stub.h> +#include <asm/exceptions.h> +#include <asm/serial-regs.h> +#include <asm/unit/serial.h> + +/* + * initialise the GDB stub + */ +void gdbstub_io_init(void) +{ + u16 tmp; + + /* set up the serial port */ + GDBPORT_SERIAL_LCR = UART_LCR_WLEN8; /* 1N8 */ + GDBPORT_SERIAL_FCR = (UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT); + + FLOWCTL_CLEAR(DTR); + FLOWCTL_SET(RTS); + + gdbstub_io_set_baud(115200); + + /* we want to get serial receive interrupts */ + XIRQxICR(GDBPORT_SERIAL_IRQ) = 0; + tmp = XIRQxICR(GDBPORT_SERIAL_IRQ); + + IVAR0 = EXCEP_IRQ_LEVEL0; + set_intr_stub(EXCEP_IRQ_LEVEL0, gdbstub_io_rx_handler); + + XIRQxICR(GDBPORT_SERIAL_IRQ) &= ~GxICR_REQUEST; + XIRQxICR(GDBPORT_SERIAL_IRQ) = GxICR_ENABLE | GxICR_LEVEL_0; + tmp = XIRQxICR(GDBPORT_SERIAL_IRQ); + + GDBPORT_SERIAL_IER = UART_IER_RDI | UART_IER_RLSI; + + /* permit level 0 IRQs to take place */ + asm volatile( + " and %0,epsw \n" + " or %1,epsw \n" + : + : "i"(~EPSW_IM), "i"(EPSW_IE | EPSW_IM_1) + ); +} + +/* + * set up the GDB stub serial port baud rate timers + */ +void gdbstub_io_set_baud(unsigned baud) +{ + unsigned value; + u8 lcr; + + value = 18432000 / 16 / baud; + + lcr = GDBPORT_SERIAL_LCR; + GDBPORT_SERIAL_LCR |= UART_LCR_DLAB; + GDBPORT_SERIAL_DLL = value & 0xff; + GDBPORT_SERIAL_DLM = (value >> 8) & 0xff; + GDBPORT_SERIAL_LCR = lcr; +} + +/* + * wait for a character to come from the debugger + */ +int gdbstub_io_rx_char(unsigned char *_ch, int nonblock) +{ + unsigned ix; + u8 ch, st; + + *_ch = 0xff; + + if (gdbstub_rx_unget) { + *_ch = gdbstub_rx_unget; + gdbstub_rx_unget = 0; + return 0; + } + + try_again: + /* pull chars out of the buffer */ + ix = gdbstub_rx_outp; + if (ix == gdbstub_rx_inp) { + if (nonblock) + return -EAGAIN; +#ifdef CONFIG_MN10300_WD_TIMER + watchdog_alert_counter = 0; +#endif /* CONFIG_MN10300_WD_TIMER */ + goto try_again; + } + + ch = gdbstub_rx_buffer[ix++]; + st = gdbstub_rx_buffer[ix++]; + gdbstub_rx_outp = ix & 0x00000fff; + + if (st & UART_LSR_BI) { + gdbstub_proto("### GDB Rx Break Detected ###\n"); + return -EINTR; + } else if (st & (UART_LSR_FE | UART_LSR_OE | UART_LSR_PE)) { + gdbstub_proto("### GDB Rx Error (st=%02x) ###\n", st); + return -EIO; + } else { + gdbstub_proto("### GDB Rx %02x (st=%02x) ###\n", ch, st); + *_ch = ch & 0x7f; + return 0; + } +} + +/* + * send a character to the debugger + */ +void gdbstub_io_tx_char(unsigned char ch) +{ + FLOWCTL_SET(DTR); + LSR_WAIT_FOR(THRE); + /* FLOWCTL_WAIT_FOR(CTS); */ + + if (ch == 0x0a) { + GDBPORT_SERIAL_TX = 0x0d; + LSR_WAIT_FOR(THRE); + /* FLOWCTL_WAIT_FOR(CTS); */ + } + GDBPORT_SERIAL_TX = ch; + + FLOWCTL_CLEAR(DTR); +} + +/* + * send a character to the debugger + */ +void gdbstub_io_tx_flush(void) +{ + LSR_WAIT_FOR(TEMT); + LSR_WAIT_FOR(THRE); + FLOWCTL_CLEAR(DTR); +} diff --git a/arch/mn10300/kernel/gdb-io-ttysm-low.S b/arch/mn10300/kernel/gdb-io-ttysm-low.S new file mode 100644 index 000000000000..677c7876307c --- /dev/null +++ b/arch/mn10300/kernel/gdb-io-ttysm-low.S @@ -0,0 +1,93 @@ +############################################################################### +# +# MN10300 On-chip serial Rx interrupt handler for GDB stub I/O +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/smp.h> +#include <asm/thread_info.h> +#include <asm/cpu-regs.h> +#include <asm/frame.inc> +#include <asm/intctl-regs.h> +#include <asm/unit/serial.h> +#include "mn10300-serial.h" + + .text + +############################################################################### +# +# GDB stub serial receive interrupt entry point +# - intended to run at interrupt priority 0 +# +############################################################################### + .globl gdbstub_io_rx_handler + .type gdbstub_io_rx_handler,@function +gdbstub_io_rx_handler: + movm [d2,d3,a2,a3],(sp) + + mov (gdbstub_rx_inp),a3 +gdbstub_io_rx_more: + mov a3,a2 + add 2,a3 + and PAGE_SIZE_asm-1,a3 + mov (gdbstub_rx_outp),d3 + cmp a3,d3 + beq gdbstub_io_rx_overflow + + movbu (SCgSTR),d3 + btst SC01STR_RBF,d3 + beq gdbstub_io_rx_done + movbu (SCgRXB),d2 + movbu d3,(gdbstub_rx_buffer+1,a2) + movbu d2,(gdbstub_rx_buffer,a2) + mov a3,(gdbstub_rx_inp) + bra gdbstub_io_rx_more + +gdbstub_io_rx_done: + mov GxICR_DETECT,d2 + movbu d2,(GxICR(SCgRXIRQ)) # ACK the interrupt + movhu (GxICR(SCgRXIRQ)),d2 # flush + + movm (sp),[d2,d3,a2,a3] + bset 0x01,(gdbstub_busy) + beq gdbstub_io_rx_enter + rti + +gdbstub_io_rx_overflow: + bset 0x01,(gdbstub_rx_overflow) + bra gdbstub_io_rx_done + +############################################################################### +# +# debugging interrupt - enter the GDB stub proper +# +############################################################################### +gdbstub_io_rx_enter: + or EPSW_IE|EPSW_IM_1,epsw + add -4,sp + SAVE_ALL + + mov 0xffffffff,d0 + mov d0,(REG_ORIG_D0,fp) + mov 0x280,d1 + + mov fp,d0 + call gdbstub_rx_irq[],0 # gdbstub_io_rx_irq(regs,excep) + + and ~EPSW_IE,epsw + bclr 0x01,(gdbstub_busy) + + .globl gdbstub_return +gdbstub_return: + RESTORE_ALL + + .size gdbstub_io_rx_handler,.-gdbstub_io_rx_handler diff --git a/arch/mn10300/kernel/gdb-io-ttysm.c b/arch/mn10300/kernel/gdb-io-ttysm.c new file mode 100644 index 000000000000..c5451592d403 --- /dev/null +++ b/arch/mn10300/kernel/gdb-io-ttysm.c @@ -0,0 +1,299 @@ +/* MN10300 On-chip serial driver for gdbstub I/O + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/tty.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/gdb-stub.h> +#include <asm/exceptions.h> +#include <asm/unit/clock.h> +#include "mn10300-serial.h" + +#if defined(CONFIG_GDBSTUB_ON_TTYSM0) +struct mn10300_serial_port *const gdbstub_port = &mn10300_serial_port_sif0; +#elif defined(CONFIG_GDBSTUB_ON_TTYSM1) +struct mn10300_serial_port *const gdbstub_port = &mn10300_serial_port_sif1; +#else +struct mn10300_serial_port *const gdbstub_port = &mn10300_serial_port_sif2; +#endif + + +/* + * initialise the GDB stub I/O routines + */ +void __init gdbstub_io_init(void) +{ + uint16_t scxctr; + int tmp; + + switch (gdbstub_port->clock_src) { + case MNSCx_CLOCK_SRC_IOCLK: + gdbstub_port->ioclk = MN10300_IOCLK; + break; + +#ifdef MN10300_IOBCLK + case MNSCx_CLOCK_SRC_IOBCLK: + gdbstub_port->ioclk = MN10300_IOBCLK; + break; +#endif + default: + BUG(); + } + + /* set up the serial port */ + gdbstub_io_set_baud(115200); + + /* we want to get serial receive interrupts */ + set_intr_level(gdbstub_port->rx_irq, GxICR_LEVEL_0); + set_intr_level(gdbstub_port->tx_irq, GxICR_LEVEL_0); + set_intr_stub(EXCEP_IRQ_LEVEL0, gdbstub_io_rx_handler); + + *gdbstub_port->rx_icr |= GxICR_ENABLE; + tmp = *gdbstub_port->rx_icr; + + /* enable the device */ + scxctr = SC01CTR_CLN_8BIT; /* 1N8 */ + switch (gdbstub_port->div_timer) { + case MNSCx_DIV_TIMER_16BIT: + scxctr |= SC0CTR_CK_TM8UFLOW_8; /* == SC1CTR_CK_TM9UFLOW_8 + == SC2CTR_CK_TM10UFLOW_8 */ + break; + + case MNSCx_DIV_TIMER_8BIT: + scxctr |= SC0CTR_CK_TM2UFLOW_8; + break; + } + + scxctr |= SC01CTR_TXE | SC01CTR_RXE; + + *gdbstub_port->_control = scxctr; + tmp = *gdbstub_port->_control; + + /* permit level 0 IRQs only */ + asm volatile( + " and %0,epsw \n" + " or %1,epsw \n" + : + : "i"(~EPSW_IM), "i"(EPSW_IE|EPSW_IM_1) + ); +} + +/* + * set up the GDB stub serial port baud rate timers + */ +void gdbstub_io_set_baud(unsigned baud) +{ + const unsigned bits = 10; /* 1 [start] + 8 [data] + 0 [parity] + + * 1 [stop] */ + unsigned long ioclk = gdbstub_port->ioclk; + unsigned xdiv, tmp; + uint16_t tmxbr; + uint8_t tmxmd; + + if (!baud) { + baud = 9600; + } else if (baud == 134) { + baud = 269; /* 134 is really 134.5 */ + xdiv = 2; + } + +try_alternative: + xdiv = 1; + + switch (gdbstub_port->div_timer) { + case MNSCx_DIV_TIMER_16BIT: + tmxmd = TM8MD_SRC_IOCLK; + tmxbr = tmp = (ioclk / (baud * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 65535) + goto timer_okay; + + tmxmd = TM8MD_SRC_IOCLK_8; + tmxbr = tmp = (ioclk / (baud * 8 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 65535) + goto timer_okay; + + tmxmd = TM8MD_SRC_IOCLK_32; + tmxbr = tmp = (ioclk / (baud * 32 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 65535) + goto timer_okay; + + break; + + case MNSCx_DIV_TIMER_8BIT: + tmxmd = TM2MD_SRC_IOCLK; + tmxbr = tmp = (ioclk / (baud * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 255) + goto timer_okay; + + tmxmd = TM2MD_SRC_IOCLK_8; + tmxbr = tmp = (ioclk / (baud * 8 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 255) + goto timer_okay; + + tmxmd = TM2MD_SRC_IOCLK_32; + tmxbr = tmp = (ioclk / (baud * 32 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 255) + goto timer_okay; + break; + } + + /* as a last resort, if the quotient is zero, default to 9600 bps */ + baud = 9600; + goto try_alternative; + +timer_okay: + gdbstub_port->uart.timeout = (2 * bits * HZ) / baud; + gdbstub_port->uart.timeout += HZ / 50; + + /* set the timer to produce the required baud rate */ + switch (gdbstub_port->div_timer) { + case MNSCx_DIV_TIMER_16BIT: + *gdbstub_port->_tmxmd = 0; + *gdbstub_port->_tmxbr = tmxbr; + *gdbstub_port->_tmxmd = TM8MD_INIT_COUNTER; + *gdbstub_port->_tmxmd = tmxmd | TM8MD_COUNT_ENABLE; + break; + + case MNSCx_DIV_TIMER_8BIT: + *gdbstub_port->_tmxmd = 0; + *(volatile u8 *) gdbstub_port->_tmxbr = (u8)tmxbr; + *gdbstub_port->_tmxmd = TM2MD_INIT_COUNTER; + *gdbstub_port->_tmxmd = tmxmd | TM2MD_COUNT_ENABLE; + break; + } +} + +/* + * wait for a character to come from the debugger + */ +int gdbstub_io_rx_char(unsigned char *_ch, int nonblock) +{ + unsigned ix; + u8 ch, st; + + *_ch = 0xff; + + if (gdbstub_rx_unget) { + *_ch = gdbstub_rx_unget; + gdbstub_rx_unget = 0; + return 0; + } + +try_again: + /* pull chars out of the buffer */ + ix = gdbstub_rx_outp; + if (ix == gdbstub_rx_inp) { + if (nonblock) + return -EAGAIN; +#ifdef CONFIG_MN10300_WD_TIMER + watchdog_alert_counter = 0; +#endif /* CONFIG_MN10300_WD_TIMER */ + goto try_again; + } + + ch = gdbstub_rx_buffer[ix++]; + st = gdbstub_rx_buffer[ix++]; + gdbstub_rx_outp = ix & (PAGE_SIZE - 1); + + st &= SC01STR_RXF | SC01STR_RBF | SC01STR_FEF | SC01STR_PEF | + SC01STR_OEF; + + /* deal with what we've got + * - note that the UART doesn't do BREAK-detection for us + */ + if (st & SC01STR_FEF && ch == 0) { + switch (gdbstub_port->rx_brk) { + case 0: gdbstub_port->rx_brk = 1; goto try_again; + case 1: gdbstub_port->rx_brk = 2; goto try_again; + case 2: + gdbstub_port->rx_brk = 3; + gdbstub_proto("### GDB MNSERIAL Rx Break Detected" + " ###\n"); + return -EINTR; + default: + goto try_again; + } + } else if (st & SC01STR_FEF) { + if (gdbstub_port->rx_brk) + goto try_again; + + gdbstub_proto("### GDB MNSERIAL Framing Error ###\n"); + return -EIO; + } else if (st & SC01STR_OEF) { + if (gdbstub_port->rx_brk) + goto try_again; + + gdbstub_proto("### GDB MNSERIAL Overrun Error ###\n"); + return -EIO; + } else if (st & SC01STR_PEF) { + if (gdbstub_port->rx_brk) + goto try_again; + + gdbstub_proto("### GDB MNSERIAL Parity Error ###\n"); + return -EIO; + } else { + /* look for the tail-end char on a break run */ + if (gdbstub_port->rx_brk == 3) { + switch (ch) { + case 0xFF: + case 0xFE: + case 0xFC: + case 0xF8: + case 0xF0: + case 0xE0: + case 0xC0: + case 0x80: + case 0x00: + gdbstub_port->rx_brk = 0; + goto try_again; + default: + break; + } + } + + gdbstub_port->rx_brk = 0; + gdbstub_io("### GDB Rx %02x (st=%02x) ###\n", ch, st); + *_ch = ch & 0x7f; + return 0; + } +} + +/* + * send a character to the debugger + */ +void gdbstub_io_tx_char(unsigned char ch) +{ + while (*gdbstub_port->_status & SC01STR_TBF) + continue; + + if (ch == 0x0a) { + *(u8 *) gdbstub_port->_txb = 0x0d; + while (*gdbstub_port->_status & SC01STR_TBF) + continue; + } + + *(u8 *) gdbstub_port->_txb = ch; +} + +/* + * flush the transmission buffers + */ +void gdbstub_io_tx_flush(void) +{ + while (*gdbstub_port->_status & (SC01STR_TBF | SC01STR_TXF)) + continue; +} diff --git a/arch/mn10300/kernel/gdb-low.S b/arch/mn10300/kernel/gdb-low.S new file mode 100644 index 000000000000..e2725552cd82 --- /dev/null +++ b/arch/mn10300/kernel/gdb-low.S @@ -0,0 +1,115 @@ +############################################################################### +# +# MN10300 Low-level gdbstub routines +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/smp.h> +#include <asm/cache.h> +#include <asm/cpu-regs.h> +#include <asm/exceptions.h> +#include <asm/frame.inc> +#include <asm/serial-regs.h> + + .text + +############################################################################### +# +# GDB stub read memory with guard +# - D0 holds the memory address to read +# - D1 holds the address to store the byte into +# +############################################################################### + .globl gdbstub_read_byte_guard + .globl gdbstub_read_byte_cont +ENTRY(gdbstub_read_byte) + mov d0,a0 + mov d1,a1 + clr d0 +gdbstub_read_byte_guard: + movbu (a0),d1 +gdbstub_read_byte_cont: + movbu d1,(a1) + ret [],0 + + .globl gdbstub_read_word_guard + .globl gdbstub_read_word_cont +ENTRY(gdbstub_read_word) + mov d0,a0 + mov d1,a1 + clr d0 +gdbstub_read_word_guard: + movhu (a0),d1 +gdbstub_read_word_cont: + movhu d1,(a1) + ret [],0 + + .globl gdbstub_read_dword_guard + .globl gdbstub_read_dword_cont +ENTRY(gdbstub_read_dword) + mov d0,a0 + mov d1,a1 + clr d0 +gdbstub_read_dword_guard: + mov (a0),d1 +gdbstub_read_dword_cont: + mov d1,(a1) + ret [],0 + +############################################################################### +# +# GDB stub write memory with guard +# - D0 holds the byte to store +# - D1 holds the memory address to write +# +############################################################################### + .globl gdbstub_write_byte_guard + .globl gdbstub_write_byte_cont +ENTRY(gdbstub_write_byte) + mov d0,a0 + mov d1,a1 + clr d0 +gdbstub_write_byte_guard: + movbu a0,(a1) +gdbstub_write_byte_cont: + ret [],0 + + .globl gdbstub_write_word_guard + .globl gdbstub_write_word_cont +ENTRY(gdbstub_write_word) + mov d0,a0 + mov d1,a1 + clr d0 +gdbstub_write_word_guard: + movhu a0,(a1) +gdbstub_write_word_cont: + ret [],0 + + .globl gdbstub_write_dword_guard + .globl gdbstub_write_dword_cont +ENTRY(gdbstub_write_dword) + mov d0,a0 + mov d1,a1 + clr d0 +gdbstub_write_dword_guard: + mov a0,(a1) +gdbstub_write_dword_cont: + ret [],0 + +############################################################################### +# +# GDB stub BUG() trap +# +############################################################################### +ENTRY(__gdbstub_bug_trap) + .byte 0xF7,0xF7 # don't use 0xFF as the JTAG unit preempts that + ret [],0 diff --git a/arch/mn10300/kernel/gdb-stub.c b/arch/mn10300/kernel/gdb-stub.c new file mode 100644 index 000000000000..21891c71d549 --- /dev/null +++ b/arch/mn10300/kernel/gdb-stub.c @@ -0,0 +1,1947 @@ +/* MN10300 GDB stub + * + * Originally written by Glenn Engel, Lake Stevens Instrument Division + * + * Contributed by HP Systems + * + * Modified for SPARC by Stu Grossman, Cygnus Support. + * + * Modified for Linux/MIPS (and MIPS in general) by Andreas Busse + * Send complaints, suggestions etc. to <andy@waldorf-gmbh.de> + * + * Copyright (C) 1995 Andreas Busse + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Modified for Linux/mn10300 by David Howells <dhowells@redhat.com> + */ + +/* + * To enable debugger support, two things need to happen. One, a + * call to set_debug_traps() is necessary in order to allow any breakpoints + * or error conditions to be properly intercepted and reported to gdb. + * Two, a breakpoint needs to be generated to begin communication. This + * is most easily accomplished by a call to breakpoint(). Breakpoint() + * simulates a breakpoint by executing a BREAK instruction. + * + * + * The following gdb commands are supported: + * + * command function Return value + * + * g return the value of the CPU registers hex data or ENN + * G set the value of the CPU registers OK or ENN + * + * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN + * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN + * + * c Resume at current address SNN ( signal NN) + * cAA..AA Continue at address AA..AA SNN + * + * s Step one instruction SNN + * sAA..AA Step one instruction from AA..AA SNN + * + * k kill + * + * ? What was the last sigval ? SNN (signal NN) + * + * bBB..BB Set baud rate to BB..BB OK or BNN, then sets + * baud rate + * + * All commands and responses are sent with a packet which includes a + * checksum. A packet consists of + * + * $<packet info>#<checksum>. + * + * where + * <packet info> :: <characters representing the command or response> + * <checksum> :: < two hex digits computed as modulo 256 sum of <packetinfo>> + * + * When a packet is received, it is first acknowledged with either '+' or '-'. + * '+' indicates a successful transfer. '-' indicates a failed transfer. + * + * Example: + * + * Host: Reply: + * $m0,10#2a +$00010203040506070809101112131415#42 + * + * + * ============== + * MORE EXAMPLES: + * ============== + * + * For reference -- the following are the steps that one + * company took (RidgeRun Inc) to get remote gdb debugging + * going. In this scenario the host machine was a PC and the + * target platform was a Galileo EVB64120A MIPS evaluation + * board. + * + * Step 1: + * First download gdb-5.0.tar.gz from the internet. + * and then build/install the package. + * + * Example: + * $ tar zxf gdb-5.0.tar.gz + * $ cd gdb-5.0 + * $ ./configure --target=am33_2.0-linux-gnu + * $ make + * $ install + * am33_2.0-linux-gnu-gdb + * + * Step 2: + * Configure linux for remote debugging and build it. + * + * Example: + * $ cd ~/linux + * $ make menuconfig <go to "Kernel Hacking" and turn on remote debugging> + * $ make dep; make vmlinux + * + * Step 3: + * Download the kernel to the remote target and start + * the kernel running. It will promptly halt and wait + * for the host gdb session to connect. It does this + * since the "Kernel Hacking" option has defined + * CONFIG_REMOTE_DEBUG which in turn enables your calls + * to: + * set_debug_traps(); + * breakpoint(); + * + * Step 4: + * Start the gdb session on the host. + * + * Example: + * $ am33_2.0-linux-gnu-gdb vmlinux + * (gdb) set remotebaud 115200 + * (gdb) target remote /dev/ttyS1 + * ...at this point you are connected to + * the remote target and can use gdb + * in the normal fasion. Setting + * breakpoints, single stepping, + * printing variables, etc. + * + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/bug.h> + +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/gdb-stub.h> +#include <asm/exceptions.h> +#include <asm/cacheflush.h> +#include <asm/serial-regs.h> +#include <asm/busctl-regs.h> +#include <asm/unit/leds.h> +#include <asm/unit/serial.h> + +/* define to use F7F7 rather than FF which is subverted by JTAG debugger */ +#undef GDBSTUB_USE_F7F7_AS_BREAKPOINT + +/* + * BUFMAX defines the maximum number of characters in inbound/outbound buffers + * at least NUMREGBYTES*2 are needed for register packets + */ +#define BUFMAX 2048 + +static const char gdbstub_banner[] = + "Linux/MN10300 GDB Stub (c) RedHat 2007\n"; + +u8 gdbstub_rx_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); +u32 gdbstub_rx_inp; +u32 gdbstub_rx_outp; +u8 gdbstub_busy; +u8 gdbstub_rx_overflow; +u8 gdbstub_rx_unget; + +static u8 gdbstub_flush_caches; +static char input_buffer[BUFMAX]; +static char output_buffer[BUFMAX]; +static char trans_buffer[BUFMAX]; + +static const char hexchars[] = "0123456789abcdef"; + +struct gdbstub_bkpt { + u8 *addr; /* address of breakpoint */ + u8 len; /* size of breakpoint */ + u8 origbytes[7]; /* original bytes */ +}; + +static struct gdbstub_bkpt gdbstub_bkpts[256]; + +/* + * local prototypes + */ +static void getpacket(char *buffer); +static int putpacket(char *buffer); +static int computeSignal(enum exception_code excep); +static int hex(unsigned char ch); +static int hexToInt(char **ptr, int *intValue); +static unsigned char *mem2hex(const void *mem, char *buf, int count, + int may_fault); +static const char *hex2mem(const char *buf, void *_mem, int count, + int may_fault); + +/* + * Convert ch from a hex digit to an int + */ +static int hex(unsigned char ch) +{ + if (ch >= 'a' && ch <= 'f') + return ch - 'a' + 10; + if (ch >= '0' && ch <= '9') + return ch - '0'; + if (ch >= 'A' && ch <= 'F') + return ch - 'A' + 10; + return -1; +} + +#ifdef CONFIG_GDBSTUB_DEBUGGING + +void debug_to_serial(const char *p, int n) +{ + __debug_to_serial(p, n); + /* gdbstub_console_write(NULL, p, n); */ +} + +void gdbstub_printk(const char *fmt, ...) +{ + va_list args; + int len; + + /* Emit the output into the temporary buffer */ + va_start(args, fmt); + len = vsnprintf(trans_buffer, sizeof(trans_buffer), fmt, args); + va_end(args); + debug_to_serial(trans_buffer, len); +} + +#endif + +static inline char *gdbstub_strcpy(char *dst, const char *src) +{ + int loop = 0; + while ((dst[loop] = src[loop])) + loop++; + return dst; +} + +/* + * scan for the sequence $<data>#<checksum> + */ +static void getpacket(char *buffer) +{ + unsigned char checksum; + unsigned char xmitcsum; + unsigned char ch; + int count, i, ret, error; + + for (;;) { + /* + * wait around for the start character, + * ignore all other characters + */ + do { + gdbstub_io_rx_char(&ch, 0); + } while (ch != '$'); + + checksum = 0; + xmitcsum = -1; + count = 0; + error = 0; + + /* + * now, read until a # or end of buffer is found + */ + while (count < BUFMAX) { + ret = gdbstub_io_rx_char(&ch, 0); + if (ret < 0) + error = ret; + + if (ch == '#') + break; + checksum += ch; + buffer[count] = ch; + count++; + } + + if (error == -EIO) { + gdbstub_proto("### GDB Rx Error - Skipping packet" + " ###\n"); + gdbstub_proto("### GDB Tx NAK\n"); + gdbstub_io_tx_char('-'); + continue; + } + + if (count >= BUFMAX || error) + continue; + + buffer[count] = 0; + + /* read the checksum */ + ret = gdbstub_io_rx_char(&ch, 0); + if (ret < 0) + error = ret; + xmitcsum = hex(ch) << 4; + + ret = gdbstub_io_rx_char(&ch, 0); + if (ret < 0) + error = ret; + xmitcsum |= hex(ch); + + if (error) { + if (error == -EIO) + gdbstub_io("### GDB Rx Error -" + " Skipping packet\n"); + gdbstub_io("### GDB Tx NAK\n"); + gdbstub_io_tx_char('-'); + continue; + } + + /* check the checksum */ + if (checksum != xmitcsum) { + gdbstub_io("### GDB Tx NAK\n"); + gdbstub_io_tx_char('-'); /* failed checksum */ + continue; + } + + gdbstub_proto("### GDB Rx '$%s#%02x' ###\n", buffer, checksum); + gdbstub_io("### GDB Tx ACK\n"); + gdbstub_io_tx_char('+'); /* successful transfer */ + + /* + * if a sequence char is present, + * reply the sequence ID + */ + if (buffer[2] == ':') { + gdbstub_io_tx_char(buffer[0]); + gdbstub_io_tx_char(buffer[1]); + + /* + * remove sequence chars from buffer + */ + count = 0; + while (buffer[count]) + count++; + for (i = 3; i <= count; i++) + buffer[i - 3] = buffer[i]; + } + + break; + } +} + +/* + * send the packet in buffer. + * - return 0 if successfully ACK'd + * - return 1 if abandoned due to new incoming packet + */ +static int putpacket(char *buffer) +{ + unsigned char checksum; + unsigned char ch; + int count; + + /* + * $<packet info>#<checksum>. + */ + gdbstub_proto("### GDB Tx $'%s'#?? ###\n", buffer); + + do { + gdbstub_io_tx_char('$'); + checksum = 0; + count = 0; + + while ((ch = buffer[count]) != 0) { + gdbstub_io_tx_char(ch); + checksum += ch; + count += 1; + } + + gdbstub_io_tx_char('#'); + gdbstub_io_tx_char(hexchars[checksum >> 4]); + gdbstub_io_tx_char(hexchars[checksum & 0xf]); + + } while (gdbstub_io_rx_char(&ch, 0), + ch == '-' && (gdbstub_io("### GDB Rx NAK\n"), 0), + ch != '-' && ch != '+' && + (gdbstub_io("### GDB Rx ??? %02x\n", ch), 0), + ch != '+' && ch != '$'); + + if (ch == '+') { + gdbstub_io("### GDB Rx ACK\n"); + return 0; + } + + gdbstub_io("### GDB Tx Abandoned\n"); + gdbstub_rx_unget = ch; + return 1; +} + +/* + * While we find nice hex chars, build an int. + * Return number of chars processed. + */ +static int hexToInt(char **ptr, int *intValue) +{ + int numChars = 0; + int hexValue; + + *intValue = 0; + + while (**ptr) { + hexValue = hex(**ptr); + if (hexValue < 0) + break; + + *intValue = (*intValue << 4) | hexValue; + numChars++; + + (*ptr)++; + } + + return (numChars); +} + +/* + * We single-step by setting breakpoints. When an exception + * is handled, we need to restore the instructions hoisted + * when the breakpoints were set. + * + * This is where we save the original instructions. + */ +static struct gdb_bp_save { + u8 *addr; + u8 opcode[2]; +} step_bp[2]; + +static const unsigned char gdbstub_insn_sizes[256] = +{ + /* 1 2 3 4 5 6 7 8 9 a b c d e f */ + 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, /* 0 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1 */ + 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, /* 2 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, /* 3 */ + 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, /* 4 */ + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, /* 5 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 8 */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 9 */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* a */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* b */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2, /* c */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ + 0, 2, 2, 2, 2, 2, 2, 4, 0, 3, 0, 4, 0, 6, 7, 1 /* f */ +}; + +static int __gdbstub_mark_bp(u8 *addr, int ix) +{ + if (addr < (u8 *) 0x70000000UL) + return 0; + /* 70000000-7fffffff: vmalloc area */ + if (addr < (u8 *) 0x80000000UL) + goto okay; + if (addr < (u8 *) 0x8c000000UL) + return 0; + /* 8c000000-93ffffff: SRAM, SDRAM */ + if (addr < (u8 *) 0x94000000UL) + goto okay; + return 0; + +okay: + if (gdbstub_read_byte(addr + 0, &step_bp[ix].opcode[0]) < 0 || + gdbstub_read_byte(addr + 1, &step_bp[ix].opcode[1]) < 0) + return 0; + + step_bp[ix].addr = addr; + return 1; +} + +static inline void __gdbstub_restore_bp(void) +{ +#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT + if (step_bp[0].addr) { + gdbstub_write_byte(step_bp[0].opcode[0], step_bp[0].addr + 0); + gdbstub_write_byte(step_bp[0].opcode[1], step_bp[0].addr + 1); + } + if (step_bp[1].addr) { + gdbstub_write_byte(step_bp[1].opcode[0], step_bp[1].addr + 0); + gdbstub_write_byte(step_bp[1].opcode[1], step_bp[1].addr + 1); + } +#else + if (step_bp[0].addr) + gdbstub_write_byte(step_bp[0].opcode[0], step_bp[0].addr + 0); + if (step_bp[1].addr) + gdbstub_write_byte(step_bp[1].opcode[0], step_bp[1].addr + 0); +#endif + + gdbstub_flush_caches = 1; + + step_bp[0].addr = NULL; + step_bp[0].opcode[0] = 0; + step_bp[0].opcode[1] = 0; + step_bp[1].addr = NULL; + step_bp[1].opcode[0] = 0; + step_bp[1].opcode[1] = 0; +} + +/* + * emulate single stepping by means of breakpoint instructions + */ +static int gdbstub_single_step(struct pt_regs *regs) +{ + unsigned size; + uint32_t x; + uint8_t cur, *pc, *sp; + + step_bp[0].addr = NULL; + step_bp[0].opcode[0] = 0; + step_bp[0].opcode[1] = 0; + step_bp[1].addr = NULL; + step_bp[1].opcode[0] = 0; + step_bp[1].opcode[1] = 0; + x = 0; + + pc = (u8 *) regs->pc; + sp = (u8 *) (regs + 1); + if (gdbstub_read_byte(pc, &cur) < 0) + return -EFAULT; + + gdbstub_bkpt("Single Step from %p { %02x }\n", pc, cur); + + gdbstub_flush_caches = 1; + + size = gdbstub_insn_sizes[cur]; + if (size > 0) { + if (!__gdbstub_mark_bp(pc + size, 0)) + goto fault; + } else { + switch (cur) { + /* Bxx (d8,PC) */ + case 0xc0: + case 0xc1: + case 0xc2: + case 0xc3: + case 0xc4: + case 0xc5: + case 0xc6: + case 0xc7: + case 0xc8: + case 0xc9: + case 0xca: + if (gdbstub_read_byte(pc + 1, (u8 *) &x) < 0) + goto fault; + if (!__gdbstub_mark_bp(pc + 2, 0)) + goto fault; + if ((x < 0 || x > 2) && + !__gdbstub_mark_bp(pc + (s8) x, 1)) + goto fault; + break; + + /* LXX (d8,PC) */ + case 0xd0: + case 0xd1: + case 0xd2: + case 0xd3: + case 0xd4: + case 0xd5: + case 0xd6: + case 0xd7: + case 0xd8: + case 0xd9: + case 0xda: + if (!__gdbstub_mark_bp(pc + 1, 0)) + goto fault; + if (regs->pc != regs->lar && + !__gdbstub_mark_bp((u8 *) regs->lar, 1)) + goto fault; + break; + + /* SETLB - loads the next for bytes into the LIR + * register */ + case 0xdb: + if (!__gdbstub_mark_bp(pc + 1, 0)) + goto fault; + break; + + /* JMP (d16,PC) or CALL (d16,PC) */ + case 0xcc: + case 0xcd: + if (gdbstub_read_byte(pc + 1, ((u8 *) &x) + 0) < 0 || + gdbstub_read_byte(pc + 2, ((u8 *) &x) + 1) < 0) + goto fault; + if (!__gdbstub_mark_bp(pc + (s16) x, 0)) + goto fault; + break; + + /* JMP (d32,PC) or CALL (d32,PC) */ + case 0xdc: + case 0xdd: + if (gdbstub_read_byte(pc + 1, ((u8 *) &x) + 0) < 0 || + gdbstub_read_byte(pc + 2, ((u8 *) &x) + 1) < 0 || + gdbstub_read_byte(pc + 3, ((u8 *) &x) + 2) < 0 || + gdbstub_read_byte(pc + 4, ((u8 *) &x) + 3) < 0) + goto fault; + if (!__gdbstub_mark_bp(pc + (s32) x, 0)) + goto fault; + break; + + /* RETF */ + case 0xde: + if (!__gdbstub_mark_bp((u8 *) regs->mdr, 0)) + goto fault; + break; + + /* RET */ + case 0xdf: + if (gdbstub_read_byte(pc + 2, (u8 *) &x) < 0) + goto fault; + sp += (s8)x; + if (gdbstub_read_byte(sp + 0, ((u8 *) &x) + 0) < 0 || + gdbstub_read_byte(sp + 1, ((u8 *) &x) + 1) < 0 || + gdbstub_read_byte(sp + 2, ((u8 *) &x) + 2) < 0 || + gdbstub_read_byte(sp + 3, ((u8 *) &x) + 3) < 0) + goto fault; + if (!__gdbstub_mark_bp((u8 *) x, 0)) + goto fault; + break; + + case 0xf0: + if (gdbstub_read_byte(pc + 1, &cur) < 0) + goto fault; + + if (cur >= 0xf0 && cur <= 0xf7) { + /* JMP (An) / CALLS (An) */ + switch (cur & 3) { + case 0: x = regs->a0; break; + case 1: x = regs->a1; break; + case 2: x = regs->a2; break; + case 3: x = regs->a3; break; + } + if (!__gdbstub_mark_bp((u8 *) x, 0)) + goto fault; + } else if (cur == 0xfc) { + /* RETS */ + if (gdbstub_read_byte( + sp + 0, ((u8 *) &x) + 0) < 0 || + gdbstub_read_byte( + sp + 1, ((u8 *) &x) + 1) < 0 || + gdbstub_read_byte( + sp + 2, ((u8 *) &x) + 2) < 0 || + gdbstub_read_byte( + sp + 3, ((u8 *) &x) + 3) < 0) + goto fault; + if (!__gdbstub_mark_bp((u8 *) x, 0)) + goto fault; + } else if (cur == 0xfd) { + /* RTI */ + if (gdbstub_read_byte( + sp + 4, ((u8 *) &x) + 0) < 0 || + gdbstub_read_byte( + sp + 5, ((u8 *) &x) + 1) < 0 || + gdbstub_read_byte( + sp + 6, ((u8 *) &x) + 2) < 0 || + gdbstub_read_byte( + sp + 7, ((u8 *) &x) + 3) < 0) + goto fault; + if (!__gdbstub_mark_bp((u8 *) x, 0)) + goto fault; + } else { + if (!__gdbstub_mark_bp(pc + 2, 0)) + goto fault; + } + + break; + + /* potential 3-byte conditional branches */ + case 0xf8: + if (gdbstub_read_byte(pc + 1, &cur) < 0) + goto fault; + if (!__gdbstub_mark_bp(pc + 3, 0)) + goto fault; + + if (cur >= 0xe8 && cur <= 0xeb) { + if (gdbstub_read_byte( + pc + 2, ((u8 *) &x) + 0) < 0) + goto fault; + if ((x < 0 || x > 3) && + !__gdbstub_mark_bp(pc + (s8) x, 1)) + goto fault; + } + break; + + case 0xfa: + if (gdbstub_read_byte(pc + 1, &cur) < 0) + goto fault; + + if (cur == 0xff) { + /* CALLS (d16,PC) */ + if (gdbstub_read_byte( + pc + 2, ((u8 *) &x) + 0) < 0 || + gdbstub_read_byte( + pc + 3, ((u8 *) &x) + 1) < 0) + goto fault; + if (!__gdbstub_mark_bp(pc + (s16) x, 0)) + goto fault; + } else { + if (!__gdbstub_mark_bp(pc + 4, 0)) + goto fault; + } + break; + + case 0xfc: + if (gdbstub_read_byte(pc + 1, &cur) < 0) + goto fault; + if (cur == 0xff) { + /* CALLS (d32,PC) */ + if (gdbstub_read_byte( + pc + 2, ((u8 *) &x) + 0) < 0 || + gdbstub_read_byte( + pc + 3, ((u8 *) &x) + 1) < 0 || + gdbstub_read_byte( + pc + 4, ((u8 *) &x) + 2) < 0 || + gdbstub_read_byte( + pc + 5, ((u8 *) &x) + 3) < 0) + goto fault; + if (!__gdbstub_mark_bp( + pc + (s32) x, 0)) + goto fault; + } else { + if (!__gdbstub_mark_bp( + pc + 6, 0)) + goto fault; + } + break; + + } + } + + gdbstub_bkpt("Step: %02x at %p; %02x at %p\n", + step_bp[0].opcode[0], step_bp[0].addr, + step_bp[1].opcode[0], step_bp[1].addr); + + if (step_bp[0].addr) { +#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT + if (gdbstub_write_byte(0xF7, step_bp[0].addr + 0) < 0 || + gdbstub_write_byte(0xF7, step_bp[0].addr + 1) < 0) + goto fault; +#else + if (gdbstub_write_byte(0xFF, step_bp[0].addr + 0) < 0) + goto fault; +#endif + } + + if (step_bp[1].addr) { +#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT + if (gdbstub_write_byte(0xF7, step_bp[1].addr + 0) < 0 || + gdbstub_write_byte(0xF7, step_bp[1].addr + 1) < 0) + goto fault; +#else + if (gdbstub_write_byte(0xFF, step_bp[1].addr + 0) < 0) + goto fault; +#endif + } + + return 0; + + fault: + /* uh-oh - silly address alert, try and restore things */ + __gdbstub_restore_bp(); + return -EFAULT; +} + +#ifdef CONFIG_GDBSTUB_CONSOLE + +void gdbstub_console_write(struct console *con, const char *p, unsigned n) +{ + static const char gdbstub_cr[] = { 0x0d }; + char outbuf[26]; + int qty; + u8 busy; + + busy = gdbstub_busy; + gdbstub_busy = 1; + + outbuf[0] = 'O'; + + while (n > 0) { + qty = 1; + + while (n > 0 && qty < 20) { + mem2hex(p, outbuf + qty, 2, 0); + qty += 2; + if (*p == 0x0a) { + mem2hex(gdbstub_cr, outbuf + qty, 2, 0); + qty += 2; + } + p++; + n--; + } + + outbuf[qty] = 0; + putpacket(outbuf); + } + + gdbstub_busy = busy; +} + +static kdev_t gdbstub_console_dev(struct console *con) +{ + return MKDEV(1, 3); /* /dev/null */ +} + +static struct console gdbstub_console = { + .name = "gdb", + .write = gdbstub_console_write, + .device = gdbstub_console_dev, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +#endif + +/* + * Convert the memory pointed to by mem into hex, placing result in buf. + * - if successful, return a pointer to the last char put in buf (NUL) + * - in case of mem fault, return NULL + * may_fault is non-zero if we are reading from arbitrary memory, but is + * currently not used. + */ +static +unsigned char *mem2hex(const void *_mem, char *buf, int count, int may_fault) +{ + const u8 *mem = _mem; + u8 ch[4]; + + if ((u32) mem & 1 && count >= 1) { + if (gdbstub_read_byte(mem, ch) != 0) + return 0; + *buf++ = hexchars[ch[0] >> 4]; + *buf++ = hexchars[ch[0] & 0xf]; + mem++; + count--; + } + + if ((u32) mem & 3 && count >= 2) { + if (gdbstub_read_word(mem, ch) != 0) + return 0; + *buf++ = hexchars[ch[0] >> 4]; + *buf++ = hexchars[ch[0] & 0xf]; + *buf++ = hexchars[ch[1] >> 4]; + *buf++ = hexchars[ch[1] & 0xf]; + mem += 2; + count -= 2; + } + + while (count >= 4) { + if (gdbstub_read_dword(mem, ch) != 0) + return 0; + *buf++ = hexchars[ch[0] >> 4]; + *buf++ = hexchars[ch[0] & 0xf]; + *buf++ = hexchars[ch[1] >> 4]; + *buf++ = hexchars[ch[1] & 0xf]; + *buf++ = hexchars[ch[2] >> 4]; + *buf++ = hexchars[ch[2] & 0xf]; + *buf++ = hexchars[ch[3] >> 4]; + *buf++ = hexchars[ch[3] & 0xf]; + mem += 4; + count -= 4; + } + + if (count >= 2) { + if (gdbstub_read_word(mem, ch) != 0) + return 0; + *buf++ = hexchars[ch[0] >> 4]; + *buf++ = hexchars[ch[0] & 0xf]; + *buf++ = hexchars[ch[1] >> 4]; + *buf++ = hexchars[ch[1] & 0xf]; + mem += 2; + count -= 2; + } + + if (count >= 1) { + if (gdbstub_read_byte(mem, ch) != 0) + return 0; + *buf++ = hexchars[ch[0] >> 4]; + *buf++ = hexchars[ch[0] & 0xf]; + } + + *buf = 0; + return buf; +} + +/* + * convert the hex array pointed to by buf into binary to be placed in mem + * return a pointer to the character AFTER the last byte written + * may_fault is non-zero if we are reading from arbitrary memory, but is + * currently not used. + */ +static +const char *hex2mem(const char *buf, void *_mem, int count, int may_fault) +{ + u8 *mem = _mem; + union { + u32 val; + u8 b[4]; + } ch; + + if ((u32) mem & 1 && count >= 1) { + ch.b[0] = hex(*buf++) << 4; + ch.b[0] |= hex(*buf++); + if (gdbstub_write_byte(ch.val, mem) != 0) + return 0; + mem++; + count--; + } + + if ((u32) mem & 3 && count >= 2) { + ch.b[0] = hex(*buf++) << 4; + ch.b[0] |= hex(*buf++); + ch.b[1] = hex(*buf++) << 4; + ch.b[1] |= hex(*buf++); + if (gdbstub_write_word(ch.val, mem) != 0) + return 0; + mem += 2; + count -= 2; + } + + while (count >= 4) { + ch.b[0] = hex(*buf++) << 4; + ch.b[0] |= hex(*buf++); + ch.b[1] = hex(*buf++) << 4; + ch.b[1] |= hex(*buf++); + ch.b[2] = hex(*buf++) << 4; + ch.b[2] |= hex(*buf++); + ch.b[3] = hex(*buf++) << 4; + ch.b[3] |= hex(*buf++); + if (gdbstub_write_dword(ch.val, mem) != 0) + return 0; + mem += 4; + count -= 4; + } + + if (count >= 2) { + ch.b[0] = hex(*buf++) << 4; + ch.b[0] |= hex(*buf++); + ch.b[1] = hex(*buf++) << 4; + ch.b[1] |= hex(*buf++); + if (gdbstub_write_word(ch.val, mem) != 0) + return 0; + mem += 2; + count -= 2; + } + + if (count >= 1) { + ch.b[0] = hex(*buf++) << 4; + ch.b[0] |= hex(*buf++); + if (gdbstub_write_byte(ch.val, mem) != 0) + return 0; + } + + return buf; +} + +/* + * This table contains the mapping between MN10300 exception codes, and + * signals, which are primarily what GDB understands. It also indicates + * which hardware traps we need to commandeer when initializing the stub. + */ +static const struct excep_to_sig_map { + enum exception_code excep; /* MN10300 exception code */ + unsigned char signo; /* Signal that we map this into */ +} excep_to_sig_map[] = { + { EXCEP_ITLBMISS, SIGSEGV }, + { EXCEP_DTLBMISS, SIGSEGV }, + { EXCEP_TRAP, SIGTRAP }, + { EXCEP_ISTEP, SIGTRAP }, + { EXCEP_IBREAK, SIGTRAP }, + { EXCEP_OBREAK, SIGTRAP }, + { EXCEP_UNIMPINS, SIGILL }, + { EXCEP_UNIMPEXINS, SIGILL }, + { EXCEP_MEMERR, SIGSEGV }, + { EXCEP_MISALIGN, SIGSEGV }, + { EXCEP_BUSERROR, SIGBUS }, + { EXCEP_ILLINSACC, SIGSEGV }, + { EXCEP_ILLDATACC, SIGSEGV }, + { EXCEP_IOINSACC, SIGSEGV }, + { EXCEP_PRIVINSACC, SIGSEGV }, + { EXCEP_PRIVDATACC, SIGSEGV }, + { EXCEP_FPU_DISABLED, SIGFPE }, + { EXCEP_FPU_UNIMPINS, SIGFPE }, + { EXCEP_FPU_OPERATION, SIGFPE }, + { EXCEP_WDT, SIGALRM }, + { EXCEP_NMI, SIGQUIT }, + { EXCEP_IRQ_LEVEL0, SIGINT }, + { EXCEP_IRQ_LEVEL1, SIGINT }, + { EXCEP_IRQ_LEVEL2, SIGINT }, + { EXCEP_IRQ_LEVEL3, SIGINT }, + { EXCEP_IRQ_LEVEL4, SIGINT }, + { EXCEP_IRQ_LEVEL5, SIGINT }, + { EXCEP_IRQ_LEVEL6, SIGINT }, + { 0, 0} +}; + +/* + * convert the MN10300 exception code into a UNIX signal number + */ +static int computeSignal(enum exception_code excep) +{ + const struct excep_to_sig_map *map; + + for (map = excep_to_sig_map; map->signo; map++) + if (map->excep == excep) + return map->signo; + + return SIGHUP; /* default for things we don't know about */ +} + +static u32 gdbstub_fpcr, gdbstub_fpufs_array[32]; + +/* + * + */ +static void gdbstub_store_fpu(void) +{ +#ifdef CONFIG_FPU + + asm volatile( + "or %2,epsw\n" +#ifdef CONFIG_MN10300_PROC_MN103E010 + "nop\n" + "nop\n" +#endif + "mov %1, a1\n" + "fmov fs0, (a1+)\n" + "fmov fs1, (a1+)\n" + "fmov fs2, (a1+)\n" + "fmov fs3, (a1+)\n" + "fmov fs4, (a1+)\n" + "fmov fs5, (a1+)\n" + "fmov fs6, (a1+)\n" + "fmov fs7, (a1+)\n" + "fmov fs8, (a1+)\n" + "fmov fs9, (a1+)\n" + "fmov fs10, (a1+)\n" + "fmov fs11, (a1+)\n" + "fmov fs12, (a1+)\n" + "fmov fs13, (a1+)\n" + "fmov fs14, (a1+)\n" + "fmov fs15, (a1+)\n" + "fmov fs16, (a1+)\n" + "fmov fs17, (a1+)\n" + "fmov fs18, (a1+)\n" + "fmov fs19, (a1+)\n" + "fmov fs20, (a1+)\n" + "fmov fs21, (a1+)\n" + "fmov fs22, (a1+)\n" + "fmov fs23, (a1+)\n" + "fmov fs24, (a1+)\n" + "fmov fs25, (a1+)\n" + "fmov fs26, (a1+)\n" + "fmov fs27, (a1+)\n" + "fmov fs28, (a1+)\n" + "fmov fs29, (a1+)\n" + "fmov fs30, (a1+)\n" + "fmov fs31, (a1+)\n" + "fmov fpcr, %0\n" + : "=d"(gdbstub_fpcr) + : "g" (&gdbstub_fpufs_array), "i"(EPSW_FE) + : "a1" + ); +#endif +} + +/* + * + */ +static void gdbstub_load_fpu(void) +{ +#ifdef CONFIG_FPU + + asm volatile( + "or %1,epsw\n" +#ifdef CONFIG_MN10300_PROC_MN103E010 + "nop\n" + "nop\n" +#endif + "mov %0, a1\n" + "fmov (a1+), fs0\n" + "fmov (a1+), fs1\n" + "fmov (a1+), fs2\n" + "fmov (a1+), fs3\n" + "fmov (a1+), fs4\n" + "fmov (a1+), fs5\n" + "fmov (a1+), fs6\n" + "fmov (a1+), fs7\n" + "fmov (a1+), fs8\n" + "fmov (a1+), fs9\n" + "fmov (a1+), fs10\n" + "fmov (a1+), fs11\n" + "fmov (a1+), fs12\n" + "fmov (a1+), fs13\n" + "fmov (a1+), fs14\n" + "fmov (a1+), fs15\n" + "fmov (a1+), fs16\n" + "fmov (a1+), fs17\n" + "fmov (a1+), fs18\n" + "fmov (a1+), fs19\n" + "fmov (a1+), fs20\n" + "fmov (a1+), fs21\n" + "fmov (a1+), fs22\n" + "fmov (a1+), fs23\n" + "fmov (a1+), fs24\n" + "fmov (a1+), fs25\n" + "fmov (a1+), fs26\n" + "fmov (a1+), fs27\n" + "fmov (a1+), fs28\n" + "fmov (a1+), fs29\n" + "fmov (a1+), fs30\n" + "fmov (a1+), fs31\n" + "fmov %2, fpcr\n" + : + : "g" (&gdbstub_fpufs_array), "i"(EPSW_FE), "d"(gdbstub_fpcr) + : "a1" + ); +#endif +} + +/* + * set a software breakpoint + */ +int gdbstub_set_breakpoint(u8 *addr, int len) +{ + int bkpt, loop, xloop; + +#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT + len = (len + 1) & ~1; +#endif + + gdbstub_bkpt("setbkpt(%p,%d)\n", addr, len); + + for (bkpt = 255; bkpt >= 0; bkpt--) + if (!gdbstub_bkpts[bkpt].addr) + break; + if (bkpt < 0) + return -ENOSPC; + + for (loop = 0; loop < len; loop++) + if (gdbstub_read_byte(&addr[loop], + &gdbstub_bkpts[bkpt].origbytes[loop] + ) < 0) + return -EFAULT; + + gdbstub_flush_caches = 1; + +#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT + for (loop = 0; loop < len; loop++) + if (gdbstub_write_byte(0xF7, &addr[loop]) < 0) + goto restore; +#else + for (loop = 0; loop < len; loop++) + if (gdbstub_write_byte(0xFF, &addr[loop]) < 0) + goto restore; +#endif + + gdbstub_bkpts[bkpt].addr = addr; + gdbstub_bkpts[bkpt].len = len; + + gdbstub_bkpt("Set BKPT[%02x]: %p-%p {%02x%02x%02x%02x%02x%02x%02x}\n", + bkpt, + gdbstub_bkpts[bkpt].addr, + gdbstub_bkpts[bkpt].addr + gdbstub_bkpts[bkpt].len - 1, + gdbstub_bkpts[bkpt].origbytes[0], + gdbstub_bkpts[bkpt].origbytes[1], + gdbstub_bkpts[bkpt].origbytes[2], + gdbstub_bkpts[bkpt].origbytes[3], + gdbstub_bkpts[bkpt].origbytes[4], + gdbstub_bkpts[bkpt].origbytes[5], + gdbstub_bkpts[bkpt].origbytes[6] + ); + + return 0; + +restore: + for (xloop = 0; xloop < loop; xloop++) + gdbstub_write_byte(gdbstub_bkpts[bkpt].origbytes[xloop], + addr + xloop); + return -EFAULT; +} + +/* + * clear a software breakpoint + */ +int gdbstub_clear_breakpoint(u8 *addr, int len) +{ + int bkpt, loop; + +#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT + len = (len + 1) & ~1; +#endif + + gdbstub_bkpt("clearbkpt(%p,%d)\n", addr, len); + + for (bkpt = 255; bkpt >= 0; bkpt--) + if (gdbstub_bkpts[bkpt].addr == addr && + gdbstub_bkpts[bkpt].len == len) + break; + if (bkpt < 0) + return -ENOENT; + + gdbstub_bkpts[bkpt].addr = NULL; + + gdbstub_flush_caches = 1; + + for (loop = 0; loop < len; loop++) + if (gdbstub_write_byte(gdbstub_bkpts[bkpt].origbytes[loop], + addr + loop) < 0) + return -EFAULT; + + return 0; +} + +/* + * This function does all command processing for interfacing to gdb + * - returns 1 if the exception should be skipped, 0 otherwise. + */ +static int gdbstub(struct pt_regs *regs, enum exception_code excep) +{ + unsigned long *stack; + unsigned long epsw, mdr; + uint32_t zero, ssp; + uint8_t broke; + char *ptr; + int sigval; + int addr; + int length; + int loop; + + if (excep == EXCEP_FPU_DISABLED) + return 0; + + gdbstub_flush_caches = 0; + + mn10300_set_gdbleds(1); + + asm volatile("mov mdr,%0" : "=d"(mdr)); + asm volatile("mov epsw,%0" : "=d"(epsw)); + asm volatile("mov %0,epsw" + :: "d"((epsw & ~EPSW_IM) | EPSW_IE | EPSW_IM_1)); + + gdbstub_store_fpu(); + +#ifdef CONFIG_GDBSTUB_IMMEDIATE + /* skip the initial pause loop */ + if (regs->pc == (unsigned long) __gdbstub_pause) + regs->pc = (unsigned long) start_kernel; +#endif + + /* if we were single stepping, restore the opcodes hoisted for the + * breakpoint[s] */ + broke = 0; + if ((step_bp[0].addr && step_bp[0].addr == (u8 *) regs->pc) || + (step_bp[1].addr && step_bp[1].addr == (u8 *) regs->pc)) + broke = 1; + + __gdbstub_restore_bp(); + + if (gdbstub_rx_unget) { + sigval = SIGINT; + if (gdbstub_rx_unget != 3) + goto packet_waiting; + gdbstub_rx_unget = 0; + } + + stack = (unsigned long *) regs->sp; + sigval = broke ? SIGTRAP : computeSignal(excep); + + /* send information about a BUG() */ + if (!user_mode(regs) && excep == EXCEP_SYSCALL15) { + const struct bug_entry *bug; + + bug = find_bug(regs->pc); + if (bug) + goto found_bug; + length = snprintf(trans_buffer, sizeof(trans_buffer), + "BUG() at address %lx\n", regs->pc); + goto send_bug_pkt; + + found_bug: + length = snprintf(trans_buffer, sizeof(trans_buffer), + "BUG() at address %lx (%s:%d)\n", + regs->pc, bug->file, bug->line); + + send_bug_pkt: + ptr = output_buffer; + *ptr++ = 'O'; + ptr = mem2hex(trans_buffer, ptr, length, 0); + *ptr = 0; + putpacket(output_buffer); + + regs->pc -= 2; + sigval = SIGABRT; + } else if (regs->pc == (unsigned long) __gdbstub_bug_trap) { + regs->pc = regs->mdr; + sigval = SIGABRT; + } + + /* + * send a message to the debugger's user saying what happened if it may + * not be clear cut (we can't map exceptions onto signals properly) + */ + if (sigval != SIGINT && sigval != SIGTRAP && sigval != SIGILL) { + static const char title[] = "Excep ", tbcberr[] = "BCBERR "; + static const char crlf[] = "\r\n"; + char hx; + u32 bcberr = BCBERR; + + ptr = output_buffer; + *ptr++ = 'O'; + ptr = mem2hex(title, ptr, sizeof(title) - 1, 0); + + hx = hexchars[(excep & 0xf000) >> 12]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(excep & 0x0f00) >> 8]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(excep & 0x00f0) >> 4]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(excep & 0x000f)]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + + ptr = mem2hex(crlf, ptr, sizeof(crlf) - 1, 0); + *ptr = 0; + putpacket(output_buffer); /* send it off... */ + + /* BCBERR */ + ptr = output_buffer; + *ptr++ = 'O'; + ptr = mem2hex(tbcberr, ptr, sizeof(tbcberr) - 1, 0); + + hx = hexchars[(bcberr & 0xf0000000) >> 28]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(bcberr & 0x0f000000) >> 24]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(bcberr & 0x00f00000) >> 20]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(bcberr & 0x000f0000) >> 16]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(bcberr & 0x0000f000) >> 12]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(bcberr & 0x00000f00) >> 8]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(bcberr & 0x000000f0) >> 4]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + hx = hexchars[(bcberr & 0x0000000f)]; + *ptr++ = hexchars[hx >> 4]; *ptr++ = hexchars[hx & 0xf]; + + ptr = mem2hex(crlf, ptr, sizeof(crlf) - 1, 0); + *ptr = 0; + putpacket(output_buffer); /* send it off... */ + } + + /* + * tell the debugger that an exception has occurred + */ + ptr = output_buffer; + + /* + * Send trap type (converted to signal) + */ + *ptr++ = 'T'; + *ptr++ = hexchars[sigval >> 4]; + *ptr++ = hexchars[sigval & 0xf]; + + /* + * Send Error PC + */ + *ptr++ = hexchars[GDB_REGID_PC >> 4]; + *ptr++ = hexchars[GDB_REGID_PC & 0xf]; + *ptr++ = ':'; + ptr = mem2hex(®s->pc, ptr, 4, 0); + *ptr++ = ';'; + + /* + * Send frame pointer + */ + *ptr++ = hexchars[GDB_REGID_FP >> 4]; + *ptr++ = hexchars[GDB_REGID_FP & 0xf]; + *ptr++ = ':'; + ptr = mem2hex(®s->a3, ptr, 4, 0); + *ptr++ = ';'; + + /* + * Send stack pointer + */ + ssp = (unsigned long) (regs + 1); + *ptr++ = hexchars[GDB_REGID_SP >> 4]; + *ptr++ = hexchars[GDB_REGID_SP & 0xf]; + *ptr++ = ':'; + ptr = mem2hex(&ssp, ptr, 4, 0); + *ptr++ = ';'; + + *ptr++ = 0; + putpacket(output_buffer); /* send it off... */ + +packet_waiting: + /* + * Wait for input from remote GDB + */ + while (1) { + output_buffer[0] = 0; + getpacket(input_buffer); + + switch (input_buffer[0]) { + /* request repeat of last signal number */ + case '?': + output_buffer[0] = 'S'; + output_buffer[1] = hexchars[sigval >> 4]; + output_buffer[2] = hexchars[sigval & 0xf]; + output_buffer[3] = 0; + break; + + case 'd': + /* toggle debug flag */ + break; + + /* + * Return the value of the CPU registers + */ + case 'g': + zero = 0; + ssp = (u32) (regs + 1); + ptr = output_buffer; + ptr = mem2hex(®s->d0, ptr, 4, 0); + ptr = mem2hex(®s->d1, ptr, 4, 0); + ptr = mem2hex(®s->d2, ptr, 4, 0); + ptr = mem2hex(®s->d3, ptr, 4, 0); + ptr = mem2hex(®s->a0, ptr, 4, 0); + ptr = mem2hex(®s->a1, ptr, 4, 0); + ptr = mem2hex(®s->a2, ptr, 4, 0); + ptr = mem2hex(®s->a3, ptr, 4, 0); + + ptr = mem2hex(&ssp, ptr, 4, 0); /* 8 */ + ptr = mem2hex(®s->pc, ptr, 4, 0); + ptr = mem2hex(®s->mdr, ptr, 4, 0); + ptr = mem2hex(®s->epsw, ptr, 4, 0); + ptr = mem2hex(®s->lir, ptr, 4, 0); + ptr = mem2hex(®s->lar, ptr, 4, 0); + ptr = mem2hex(®s->mdrq, ptr, 4, 0); + + ptr = mem2hex(®s->e0, ptr, 4, 0); /* 15 */ + ptr = mem2hex(®s->e1, ptr, 4, 0); + ptr = mem2hex(®s->e2, ptr, 4, 0); + ptr = mem2hex(®s->e3, ptr, 4, 0); + ptr = mem2hex(®s->e4, ptr, 4, 0); + ptr = mem2hex(®s->e5, ptr, 4, 0); + ptr = mem2hex(®s->e6, ptr, 4, 0); + ptr = mem2hex(®s->e7, ptr, 4, 0); + + ptr = mem2hex(&ssp, ptr, 4, 0); + ptr = mem2hex(®s, ptr, 4, 0); + ptr = mem2hex(®s->sp, ptr, 4, 0); + ptr = mem2hex(®s->mcrh, ptr, 4, 0); /* 26 */ + ptr = mem2hex(®s->mcrl, ptr, 4, 0); + ptr = mem2hex(®s->mcvf, ptr, 4, 0); + + ptr = mem2hex(&gdbstub_fpcr, ptr, 4, 0); /* 29 - FPCR */ + ptr = mem2hex(&zero, ptr, 4, 0); + ptr = mem2hex(&zero, ptr, 4, 0); + for (loop = 0; loop < 32; loop++) + ptr = mem2hex(&gdbstub_fpufs_array[loop], + ptr, 4, 0); /* 32 - FS0-31 */ + + break; + + /* + * set the value of the CPU registers - return OK + */ + case 'G': + { + const char *ptr; + + ptr = &input_buffer[1]; + ptr = hex2mem(ptr, ®s->d0, 4, 0); + ptr = hex2mem(ptr, ®s->d1, 4, 0); + ptr = hex2mem(ptr, ®s->d2, 4, 0); + ptr = hex2mem(ptr, ®s->d3, 4, 0); + ptr = hex2mem(ptr, ®s->a0, 4, 0); + ptr = hex2mem(ptr, ®s->a1, 4, 0); + ptr = hex2mem(ptr, ®s->a2, 4, 0); + ptr = hex2mem(ptr, ®s->a3, 4, 0); + + ptr = hex2mem(ptr, &ssp, 4, 0); /* 8 */ + ptr = hex2mem(ptr, ®s->pc, 4, 0); + ptr = hex2mem(ptr, ®s->mdr, 4, 0); + ptr = hex2mem(ptr, ®s->epsw, 4, 0); + ptr = hex2mem(ptr, ®s->lir, 4, 0); + ptr = hex2mem(ptr, ®s->lar, 4, 0); + ptr = hex2mem(ptr, ®s->mdrq, 4, 0); + + ptr = hex2mem(ptr, ®s->e0, 4, 0); /* 15 */ + ptr = hex2mem(ptr, ®s->e1, 4, 0); + ptr = hex2mem(ptr, ®s->e2, 4, 0); + ptr = hex2mem(ptr, ®s->e3, 4, 0); + ptr = hex2mem(ptr, ®s->e4, 4, 0); + ptr = hex2mem(ptr, ®s->e5, 4, 0); + ptr = hex2mem(ptr, ®s->e6, 4, 0); + ptr = hex2mem(ptr, ®s->e7, 4, 0); + + ptr = hex2mem(ptr, &ssp, 4, 0); + ptr = hex2mem(ptr, &zero, 4, 0); + ptr = hex2mem(ptr, ®s->sp, 4, 0); + ptr = hex2mem(ptr, ®s->mcrh, 4, 0); /* 26 */ + ptr = hex2mem(ptr, ®s->mcrl, 4, 0); + ptr = hex2mem(ptr, ®s->mcvf, 4, 0); + + ptr = hex2mem(ptr, &zero, 4, 0); /* 29 - FPCR */ + ptr = hex2mem(ptr, &zero, 4, 0); + ptr = hex2mem(ptr, &zero, 4, 0); + for (loop = 0; loop < 32; loop++) /* 32 - FS0-31 */ + ptr = hex2mem(ptr, &zero, 4, 0); + +#if 0 + /* + * See if the stack pointer has moved. If so, then copy + * the saved locals and ins to the new location. + */ + unsigned long *newsp = (unsigned long *) registers[SP]; + if (sp != newsp) + sp = memcpy(newsp, sp, 16 * 4); +#endif + + gdbstub_strcpy(output_buffer, "OK"); + } + break; + + /* + * mAA..AA,LLLL Read LLLL bytes at address AA..AA + */ + case 'm': + ptr = &input_buffer[1]; + + if (hexToInt(&ptr, &addr) && + *ptr++ == ',' && + hexToInt(&ptr, &length) + ) { + if (mem2hex((char *) addr, output_buffer, + length, 1)) + break; + gdbstub_strcpy(output_buffer, "E03"); + } else { + gdbstub_strcpy(output_buffer, "E01"); + } + break; + + /* + * MAA..AA,LLLL: Write LLLL bytes at address AA.AA + * return OK + */ + case 'M': + ptr = &input_buffer[1]; + + if (hexToInt(&ptr, &addr) && + *ptr++ == ',' && + hexToInt(&ptr, &length) && + *ptr++ == ':' + ) { + if (hex2mem(ptr, (char *) addr, length, 1)) + gdbstub_strcpy(output_buffer, "OK"); + else + gdbstub_strcpy(output_buffer, "E03"); + + gdbstub_flush_caches = 1; + } else { + gdbstub_strcpy(output_buffer, "E02"); + } + break; + + /* + * cAA..AA Continue at address AA..AA(optional) + */ + case 'c': + /* try to read optional parameter, pc unchanged if no + * parm */ + + ptr = &input_buffer[1]; + if (hexToInt(&ptr, &addr)) + regs->pc = addr; + goto done; + + /* + * kill the program + */ + case 'k' : + goto done; /* just continue */ + + /* + * Reset the whole machine (FIXME: system dependent) + */ + case 'r': + break; + + /* + * Step to next instruction + */ + case 's': + /* + * using the T flag doesn't seem to perform single + * stepping (it seems to wind up being caught by the + * JTAG unit), so we have to use breakpoints and + * continue instead. + */ + if (gdbstub_single_step(regs) < 0) + /* ignore any fault error for now */ + gdbstub_printk("unable to set single-step" + " bp\n"); + goto done; + + /* + * Set baud rate (bBB) + */ + case 'b': + do { + int baudrate; + + ptr = &input_buffer[1]; + if (!hexToInt(&ptr, &baudrate)) { + gdbstub_strcpy(output_buffer, "B01"); + break; + } + + if (baudrate) { + /* ACK before changing speed */ + putpacket("OK"); + gdbstub_io_set_baud(baudrate); + } + } while (0); + break; + + /* + * Set breakpoint + */ + case 'Z': + ptr = &input_buffer[1]; + + if (!hexToInt(&ptr, &loop) || *ptr++ != ',' || + !hexToInt(&ptr, &addr) || *ptr++ != ',' || + !hexToInt(&ptr, &length) + ) { + gdbstub_strcpy(output_buffer, "E01"); + break; + } + + /* only support software breakpoints */ + gdbstub_strcpy(output_buffer, "E03"); + if (loop != 0 || + length < 1 || + length > 7 || + (unsigned long) addr < 4096) + break; + + if (gdbstub_set_breakpoint((u8 *) addr, length) < 0) + break; + + gdbstub_strcpy(output_buffer, "OK"); + break; + + /* + * Clear breakpoint + */ + case 'z': + ptr = &input_buffer[1]; + + if (!hexToInt(&ptr, &loop) || *ptr++ != ',' || + !hexToInt(&ptr, &addr) || *ptr++ != ',' || + !hexToInt(&ptr, &length) + ) { + gdbstub_strcpy(output_buffer, "E01"); + break; + } + + /* only support software breakpoints */ + gdbstub_strcpy(output_buffer, "E03"); + if (loop != 0 || + length < 1 || + length > 7 || + (unsigned long) addr < 4096) + break; + + if (gdbstub_clear_breakpoint((u8 *) addr, length) < 0) + break; + + gdbstub_strcpy(output_buffer, "OK"); + break; + + default: + gdbstub_proto("### GDB Unsupported Cmd '%s'\n", + input_buffer); + break; + } + + /* reply to the request */ + putpacket(output_buffer); + } + +done: + /* + * Need to flush the instruction cache here, as we may + * have deposited a breakpoint, and the icache probably + * has no way of knowing that a data ref to some location + * may have changed something that is in the instruction + * cache. + * NB: We flush both caches, just to be sure... + */ + if (gdbstub_flush_caches) + gdbstub_purge_cache(); + + gdbstub_load_fpu(); + mn10300_set_gdbleds(0); + if (excep == EXCEP_NMI) + NMICR = NMICR_NMIF; + + touch_softlockup_watchdog(); + + local_irq_restore(epsw); + return 1; +} + +/* + * handle event interception + */ +asmlinkage int gdbstub_intercept(struct pt_regs *regs, + enum exception_code excep) +{ + static u8 notfirst = 1; + int ret; + + if (gdbstub_busy) + gdbstub_printk("--> gdbstub reentered itself\n"); + gdbstub_busy = 1; + + if (notfirst) { + unsigned long mdr; + asm("mov mdr,%0" : "=d"(mdr)); + + gdbstub_entry( + "--> gdbstub_intercept(%p,%04x) [MDR=%lx PC=%lx]\n", + regs, excep, mdr, regs->pc); + + gdbstub_entry( + "PC: %08lx EPSW: %08lx SSP: %08lx mode: %s\n", + regs->pc, regs->epsw, (unsigned long) &ret, + user_mode(regs) ? "User" : "Super"); + gdbstub_entry( + "d0: %08lx d1: %08lx d2: %08lx d3: %08lx\n", + regs->d0, regs->d1, regs->d2, regs->d3); + gdbstub_entry( + "a0: %08lx a1: %08lx a2: %08lx a3: %08lx\n", + regs->a0, regs->a1, regs->a2, regs->a3); + gdbstub_entry( + "e0: %08lx e1: %08lx e2: %08lx e3: %08lx\n", + regs->e0, regs->e1, regs->e2, regs->e3); + gdbstub_entry( + "e4: %08lx e5: %08lx e6: %08lx e7: %08lx\n", + regs->e4, regs->e5, regs->e6, regs->e7); + gdbstub_entry( + "lar: %08lx lir: %08lx mdr: %08lx usp: %08lx\n", + regs->lar, regs->lir, regs->mdr, regs->sp); + gdbstub_entry( + "cvf: %08lx crl: %08lx crh: %08lx drq: %08lx\n", + regs->mcvf, regs->mcrl, regs->mcrh, regs->mdrq); + gdbstub_entry( + "threadinfo=%p task=%p)\n", + current_thread_info(), current); + } else { + notfirst = 1; + } + + ret = gdbstub(regs, excep); + + gdbstub_entry("<-- gdbstub_intercept()\n"); + gdbstub_busy = 0; + return ret; +} + +/* + * handle the GDB stub itself causing an exception + */ +asmlinkage void gdbstub_exception(struct pt_regs *regs, + enum exception_code excep) +{ + unsigned long mdr; + + asm("mov mdr,%0" : "=d"(mdr)); + gdbstub_entry("--> gdbstub exception({%p},%04x) [MDR=%lx]\n", + regs, excep, mdr); + + while ((unsigned long) regs == 0xffffffff) {} + + /* handle guarded memory accesses where we know it might fault */ + if (regs->pc == (unsigned) gdbstub_read_byte_guard) { + regs->pc = (unsigned) gdbstub_read_byte_cont; + goto fault; + } + + if (regs->pc == (unsigned) gdbstub_read_word_guard) { + regs->pc = (unsigned) gdbstub_read_word_cont; + goto fault; + } + + if (regs->pc == (unsigned) gdbstub_read_dword_guard) { + regs->pc = (unsigned) gdbstub_read_dword_cont; + goto fault; + } + + if (regs->pc == (unsigned) gdbstub_write_byte_guard) { + regs->pc = (unsigned) gdbstub_write_byte_cont; + goto fault; + } + + if (regs->pc == (unsigned) gdbstub_write_word_guard) { + regs->pc = (unsigned) gdbstub_write_word_cont; + goto fault; + } + + if (regs->pc == (unsigned) gdbstub_write_dword_guard) { + regs->pc = (unsigned) gdbstub_write_dword_cont; + goto fault; + } + + gdbstub_printk("\n### GDB stub caused an exception ###\n"); + + /* something went horribly wrong */ + console_verbose(); + show_registers(regs); + + panic("GDB Stub caused an unexpected exception - can't continue\n"); + + /* we caught an attempt by the stub to access silly memory */ +fault: + gdbstub_entry("<-- gdbstub exception() = EFAULT\n"); + regs->d0 = -EFAULT; + return; +} + +/* + * send an exit message to GDB + */ +void gdbstub_exit(int status) +{ + unsigned char checksum; + unsigned char ch; + int count; + + gdbstub_busy = 1; + output_buffer[0] = 'W'; + output_buffer[1] = hexchars[(status >> 4) & 0x0F]; + output_buffer[2] = hexchars[status & 0x0F]; + output_buffer[3] = 0; + + gdbstub_io_tx_char('$'); + checksum = 0; + count = 0; + + while ((ch = output_buffer[count]) != 0) { + gdbstub_io_tx_char(ch); + checksum += ch; + count += 1; + } + + gdbstub_io_tx_char('#'); + gdbstub_io_tx_char(hexchars[checksum >> 4]); + gdbstub_io_tx_char(hexchars[checksum & 0xf]); + + /* make sure the output is flushed, or else RedBoot might clobber it */ + gdbstub_io_tx_flush(); + + gdbstub_busy = 0; +} + +/* + * initialise the GDB stub + */ +asmlinkage void __init gdbstub_init(void) +{ +#ifdef CONFIG_GDBSTUB_IMMEDIATE + unsigned char ch; + int ret; +#endif + + gdbstub_busy = 1; + + printk(KERN_INFO "%s", gdbstub_banner); + + gdbstub_io_init(); + + gdbstub_entry("--> gdbstub_init\n"); + + /* try to talk to GDB (or anyone insane enough to want to type GDB + * protocol by hand) */ + gdbstub_io("### GDB Tx ACK\n"); + gdbstub_io_tx_char('+'); /* 'hello world' */ + +#ifdef CONFIG_GDBSTUB_IMMEDIATE + gdbstub_printk("GDB Stub waiting for packet\n"); + + /* in case GDB is started before us, ACK any packets that are already + * sitting there (presumably "$?#xx") + */ + do { gdbstub_io_rx_char(&ch, 0); } while (ch != '$'); + do { gdbstub_io_rx_char(&ch, 0); } while (ch != '#'); + /* eat first csum byte */ + do { ret = gdbstub_io_rx_char(&ch, 0); } while (ret != 0); + /* eat second csum byte */ + do { ret = gdbstub_io_rx_char(&ch, 0); } while (ret != 0); + + gdbstub_io("### GDB Tx NAK\n"); + gdbstub_io_tx_char('-'); /* NAK it */ + +#else + printk("GDB Stub ready\n"); +#endif + + gdbstub_busy = 0; + gdbstub_entry("<-- gdbstub_init\n"); +} + +/* + * register the console at a more appropriate time + */ +#ifdef CONFIG_GDBSTUB_CONSOLE +static int __init gdbstub_postinit(void) +{ + printk(KERN_NOTICE "registering console\n"); + register_console(&gdbstub_console); + return 0; +} + +__initcall(gdbstub_postinit); +#endif + +/* + * handle character reception on GDB serial port + * - jump into the GDB stub if BREAK is detected on the serial line + */ +asmlinkage void gdbstub_rx_irq(struct pt_regs *regs, enum exception_code excep) +{ + char ch; + int ret; + + gdbstub_entry("--> gdbstub_rx_irq\n"); + + do { + ret = gdbstub_io_rx_char(&ch, 1); + if (ret != -EIO && ret != -EAGAIN) { + if (ret != -EINTR) + gdbstub_rx_unget = ch; + gdbstub(regs, excep); + } + } while (ret != -EAGAIN); + + gdbstub_entry("<-- gdbstub_rx_irq\n"); +} diff --git a/arch/mn10300/kernel/head.S b/arch/mn10300/kernel/head.S new file mode 100644 index 000000000000..606bd8c6758d --- /dev/null +++ b/arch/mn10300/kernel/head.S @@ -0,0 +1,255 @@ +/* Boot entry point for MN10300 kernel + * + * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/threads.h> +#include <linux/linkage.h> +#include <linux/serial_reg.h> +#include <asm/thread_info.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/frame.inc> +#include <asm/param.h> +#include <asm/unit/serial.h> + + .section .text.head,"ax" + +############################################################################### +# +# bootloader entry point +# +############################################################################### + .globl _start + .type _start,@function +_start: + # save commandline pointer + mov d0,a3 + + # preload the PGD pointer register + mov swapper_pg_dir,d0 + mov d0,(PTBR) + + # turn on the TLBs + mov MMUCTR_IIV|MMUCTR_DIV,d0 + mov d0,(MMUCTR) + mov MMUCTR_ITE|MMUCTR_DTE|MMUCTR_CE,d0 + mov d0,(MMUCTR) + + # turn on AM33v2 exception handling mode and set the trap table base + movhu (CPUP),d0 + or CPUP_EXM_AM33V2,d0 + movhu d0,(CPUP) + mov CONFIG_INTERRUPT_VECTOR_BASE,d0 + mov d0,(TBR) + + # invalidate and enable both of the caches + mov CHCTR,a0 + clr d0 + movhu d0,(a0) # turn off first + mov CHCTR_ICINV|CHCTR_DCINV,d0 + movhu d0,(a0) + setlb + mov (a0),d0 + btst CHCTR_ICBUSY|CHCTR_DCBUSY,d0 # wait till not busy + lne + +#ifndef CONFIG_MN10300_CACHE_DISABLED +#ifdef CONFIG_MN10300_CACHE_WBACK +#ifndef CONFIG_MN10300_CACHE_WBACK_NOWRALLOC + mov CHCTR_ICEN|CHCTR_DCEN|CHCTR_DCWTMD_WRBACK,d0 +#else + mov CHCTR_ICEN|CHCTR_DCEN|CHCTR_DCWTMD_WRBACK|CHCTR_DCALMD,d0 +#endif /* CACHE_DISABLED */ +#else + mov CHCTR_ICEN|CHCTR_DCEN|CHCTR_DCWTMD_WRTHROUGH,d0 +#endif /* WBACK */ + movhu d0,(a0) # enable +#endif /* NOWRALLOC */ + + # turn on RTS on the debug serial port if applicable +#ifdef CONFIG_MN10300_UNIT_ASB2305 + bset UART_MCR_RTS,(ASB2305_DEBUG_MCR) +#endif + + # clear the BSS area + mov __bss_start,a0 + mov __bss_stop,a1 + clr d0 +bssclear: + cmp a1,a0 + bge bssclear_end + mov d0,(a0) + inc4 a0 + bra bssclear +bssclear_end: + + # retrieve the parameters (including command line) before we overwrite + # them + cmp 0xabadcafe,d1 + bne __no_parameters + +__copy_parameters: + mov redboot_command_line,a0 + mov a0,a1 + add COMMAND_LINE_SIZE,a1 +1: + movbu (a3),d0 + inc a3 + movbu d0,(a0) + inc a0 + cmp a1,a0 + blt 1b + + mov redboot_platform_name,a0 + mov a0,a1 + add COMMAND_LINE_SIZE,a1 + mov d2,a3 +1: + movbu (a3),d0 + inc a3 + movbu d0,(a0) + inc a0 + cmp a1,a0 + blt 1b + +__no_parameters: + + # set up the registers with recognisable rubbish in them + mov init_thread_union+THREAD_SIZE-12,sp + + mov 0xea01eaea,d0 + mov d0,(4,sp) # EPSW save area + mov 0xea02eaea,d0 + mov d0,(8,sp) # PC save area + + mov 0xeb0060ed,d0 + mov d0,mdr + mov 0xeb0061ed,d0 + mov d0,mdrq + mov 0xeb0062ed,d0 + mov d0,mcrh + mov 0xeb0063ed,d0 + mov d0,mcrl + mov 0xeb0064ed,d0 + mov d0,mcvf + mov 0xed0065ed,a3 + mov a3,usp + + mov 0xed00e0ed,e0 + mov 0xed00e1ed,e1 + mov 0xed00e2ed,e2 + mov 0xed00e3ed,e3 + mov 0xed00e4ed,e4 + mov 0xed00e5ed,e5 + mov 0xed00e6ed,e6 + mov 0xed00e7ed,e7 + + mov 0xed00d0ed,d0 + mov 0xed00d1ed,d1 + mov 0xed00d2ed,d2 + mov 0xed00d3ed,d3 + mov 0xed00a0ed,a0 + mov 0xed00a1ed,a1 + mov 0xed00a2ed,a2 + mov 0,a3 + + # set up the initial kernel stack + SAVE_ALL + mov 0xffffffff,d0 + mov d0,(REG_ORIG_D0,fp) + + # put different recognisable rubbish in the regs + mov 0xfb0060ed,d0 + mov d0,mdr + mov 0xfb0061ed,d0 + mov d0,mdrq + mov 0xfb0062ed,d0 + mov d0,mcrh + mov 0xfb0063ed,d0 + mov d0,mcrl + mov 0xfb0064ed,d0 + mov d0,mcvf + mov 0xfd0065ed,a0 + mov a0,usp + + mov 0xfd00e0ed,e0 + mov 0xfd00e1ed,e1 + mov 0xfd00e2ed,e2 + mov 0xfd00e3ed,e3 + mov 0xfd00e4ed,e4 + mov 0xfd00e5ed,e5 + mov 0xfd00e6ed,e6 + mov 0xfd00e7ed,e7 + + mov 0xfd00d0ed,d0 + mov 0xfd00d1ed,d1 + mov 0xfd00d2ed,d2 + mov 0xfd00d3ed,d3 + mov 0xfd00a0ed,a0 + mov 0xfd00a1ed,a1 + mov 0xfd00a2ed,a2 + + # we may be holding current in E2 +#ifdef CONFIG_MN10300_CURRENT_IN_E2 + mov init_task,e2 +#endif + + # initialise the processor and the unit + call processor_init[],0 + call unit_init[],0 + +#ifdef CONFIG_GDBSTUB + call gdbstub_init[],0 + +#ifdef CONFIG_GDBSTUB_IMMEDIATE + .globl __gdbstub_pause +__gdbstub_pause: + bra __gdbstub_pause +#endif +#endif + + jmp start_kernel + .size _start, _start-. +ENTRY(__head_end) + +/* + * This is initialized to disallow all access to the low 2G region + * - the high 2G region is managed directly by the MMU + * - range 0x70000000-0x7C000000 are initialised for use by VMALLOC + */ + .section .bss + .balign PAGE_SIZE +ENTRY(swapper_pg_dir) + .space PTRS_PER_PGD*4 + +/* + * The page tables are initialized to only 8MB here - the final page + * tables are set up later depending on memory size. + */ + + .balign PAGE_SIZE +ENTRY(empty_zero_page) + .space PAGE_SIZE + + .balign PAGE_SIZE +ENTRY(empty_bad_page) + .space PAGE_SIZE + + .balign PAGE_SIZE +ENTRY(empty_bad_pte_table) + .space PAGE_SIZE + + .balign PAGE_SIZE +ENTRY(large_page_table) + .space PAGE_SIZE + + .balign PAGE_SIZE +ENTRY(kernel_vmalloc_ptes) + .space ((VMALLOC_END-VMALLOC_START)/PAGE_SIZE)*4 diff --git a/arch/mn10300/kernel/init_task.c b/arch/mn10300/kernel/init_task.c new file mode 100644 index 000000000000..39fe6882dd1d --- /dev/null +++ b/arch/mn10300/kernel/init_task.c @@ -0,0 +1,45 @@ +/* MN10300 Initial task definitions + * + * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/init_task.h> +#include <linux/fs.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 THREAD_SIZE aligned due to the + * way process stacks are handled. This is done by having a special + * "init_task" linker map entry.. + */ +union thread_union init_thread_union + __attribute__((__section__(".data.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/mn10300/kernel/internal.h b/arch/mn10300/kernel/internal.h new file mode 100644 index 000000000000..eee2eee86267 --- /dev/null +++ b/arch/mn10300/kernel/internal.h @@ -0,0 +1,20 @@ +/* Internal definitions for the arch part of the core kernel + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +/* + * kthread.S + */ +extern int kernel_thread_helper(int); + +/* + * entry.S + */ +extern void ret_from_fork(struct task_struct *) __attribute__((noreturn)); diff --git a/arch/mn10300/kernel/io.c b/arch/mn10300/kernel/io.c new file mode 100644 index 000000000000..e96fdf6bb542 --- /dev/null +++ b/arch/mn10300/kernel/io.c @@ -0,0 +1,30 @@ +/* MN10300 Misaligned multibyte-word I/O + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <asm/io.h> + +/* + * output data from a potentially misaligned buffer + */ +void __outsl(unsigned long addr, const void *buffer, int count) +{ + const unsigned char *buf = buffer; + unsigned long val; + + while (count--) { + memcpy(&val, buf, 4); + outl(val, addr); + buf += 4; + } +} +EXPORT_SYMBOL(__outsl); diff --git a/arch/mn10300/kernel/irq.c b/arch/mn10300/kernel/irq.c new file mode 100644 index 000000000000..761c434a2488 --- /dev/null +++ b/arch/mn10300/kernel/irq.c @@ -0,0 +1,235 @@ +/* MN10300 Arch-specific interrupt handling + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/seq_file.h> +#include <asm/setup.h> + +unsigned long __mn10300_irq_enabled_epsw = EPSW_IE | EPSW_IM_7; +EXPORT_SYMBOL(__mn10300_irq_enabled_epsw); + +atomic_t irq_err_count; + +/* + * MN10300 INTC controller operations + */ +static void mn10300_cpupic_disable(unsigned int irq) +{ + u16 tmp = GxICR(irq); + GxICR(irq) = (tmp & GxICR_LEVEL) | GxICR_DETECT; + tmp = GxICR(irq); +} + +static void mn10300_cpupic_enable(unsigned int irq) +{ + u16 tmp = GxICR(irq); + GxICR(irq) = (tmp & GxICR_LEVEL) | GxICR_ENABLE; + tmp = GxICR(irq); +} + +static void mn10300_cpupic_ack(unsigned int irq) +{ + u16 tmp; + *(volatile u8 *) &GxICR(irq) = GxICR_DETECT; + tmp = GxICR(irq); +} + +static void mn10300_cpupic_mask(unsigned int irq) +{ + u16 tmp = GxICR(irq); + GxICR(irq) = (tmp & GxICR_LEVEL); + tmp = GxICR(irq); +} + +static void mn10300_cpupic_mask_ack(unsigned int irq) +{ + u16 tmp = GxICR(irq); + GxICR(irq) = (tmp & GxICR_LEVEL) | GxICR_DETECT; + tmp = GxICR(irq); +} + +static void mn10300_cpupic_unmask(unsigned int irq) +{ + u16 tmp = GxICR(irq); + GxICR(irq) = (tmp & GxICR_LEVEL) | GxICR_ENABLE | GxICR_DETECT; + tmp = GxICR(irq); +} + +static void mn10300_cpupic_end(unsigned int irq) +{ + u16 tmp = GxICR(irq); + GxICR(irq) = (tmp & GxICR_LEVEL) | GxICR_ENABLE; + tmp = GxICR(irq); +} + +static struct irq_chip mn10300_cpu_pic = { + .name = "cpu", + .disable = mn10300_cpupic_disable, + .enable = mn10300_cpupic_enable, + .ack = mn10300_cpupic_ack, + .mask = mn10300_cpupic_mask, + .mask_ack = mn10300_cpupic_mask_ack, + .unmask = mn10300_cpupic_unmask, + .end = mn10300_cpupic_end, +}; + +/* + * 'what should we do if we get a hw irq event on an illegal vector'. + * each architecture has to answer this themselves. + */ +void ack_bad_irq(int irq) +{ + printk(KERN_WARNING "unexpected IRQ trap at vector %02x\n", irq); +} + +/* + * change the level at which an IRQ executes + * - must not be called whilst interrupts are being processed! + */ +void set_intr_level(int irq, u16 level) +{ + u16 tmp; + + if (in_interrupt()) + BUG(); + + tmp = GxICR(irq); + GxICR(irq) = (tmp & GxICR_ENABLE) | level; + tmp = GxICR(irq); +} + +/* + * mark an interrupt to be ACK'd after interrupt handlers have been run rather + * than before + * - see Documentation/mn10300/features.txt + */ +void set_intr_postackable(int irq) +{ + set_irq_handler(irq, handle_level_irq); +} + +/* + * initialise the interrupt system + */ +void __init init_IRQ(void) +{ + int irq; + + for (irq = 0; irq < NR_IRQS; irq++) + if (irq_desc[irq].chip == &no_irq_type) + set_irq_chip_and_handler(irq, &mn10300_cpu_pic, + handle_edge_irq); + unit_init_IRQ(); +} + +/* + * handle normal device IRQs + */ +asmlinkage void do_IRQ(void) +{ + unsigned long sp, epsw, irq_disabled_epsw, old_irq_enabled_epsw; + int irq; + + sp = current_stack_pointer(); + if (sp - (sp & ~(THREAD_SIZE - 1)) < STACK_WARN) + BUG(); + + /* make sure local_irq_enable() doesn't muck up the interrupt priority + * setting in EPSW */ + old_irq_enabled_epsw = __mn10300_irq_enabled_epsw; + local_save_flags(epsw); + __mn10300_irq_enabled_epsw = EPSW_IE | (EPSW_IM & epsw); + irq_disabled_epsw = EPSW_IE | MN10300_CLI_LEVEL; + + __IRQ_STAT(smp_processor_id(), __irq_count)++; + + irq_enter(); + + for (;;) { + /* ask the interrupt controller for the next IRQ to process + * - the result we get depends on EPSW.IM + */ + irq = IAGR & IAGR_GN; + if (!irq) + break; + + local_irq_restore(irq_disabled_epsw); + + generic_handle_irq(irq >> 2); + + /* restore IRQ controls for IAGR access */ + local_irq_restore(epsw); + } + + __mn10300_irq_enabled_epsw = old_irq_enabled_epsw; + + irq_exit(); +} + +/* + * Display interrupt management information through /proc/interrupts + */ +int show_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v, j, cpu; + struct irqaction *action; + unsigned long flags; + + switch (i) { + /* display column title bar naming CPUs */ + case 0: + seq_printf(p, " "); + for (j = 0; j < NR_CPUS; j++) + if (cpu_online(j)) + seq_printf(p, "CPU%d ", j); + seq_putc(p, '\n'); + break; + + /* display information rows, one per active CPU */ + case 1 ... NR_IRQS - 1: + spin_lock_irqsave(&irq_desc[i].lock, flags); + + action = irq_desc[i].action; + if (action) { + seq_printf(p, "%3d: ", i); + for_each_present_cpu(cpu) + seq_printf(p, "%10u ", kstat_cpu(cpu).irqs[i]); + seq_printf(p, " %14s.%u", irq_desc[i].chip->name, + (GxICR(i) & GxICR_LEVEL) >> + GxICR_LEVEL_SHIFT); + seq_printf(p, " %s", action->name); + + for (action = action->next; + action; + action = action->next) + seq_printf(p, ", %s", action->name); + + seq_putc(p, '\n'); + } + + spin_unlock_irqrestore(&irq_desc[i].lock, flags); + break; + + /* polish off with NMI and error counters */ + case NR_IRQS: + seq_printf(p, "NMI: "); + for (j = 0; j < NR_CPUS; j++) + if (cpu_online(j)) + seq_printf(p, "%10u ", nmi_count(j)); + seq_putc(p, '\n'); + + seq_printf(p, "ERR: %10u\n", atomic_read(&irq_err_count)); + break; + } + + return 0; +} diff --git a/arch/mn10300/kernel/kernel_execve.S b/arch/mn10300/kernel/kernel_execve.S new file mode 100644 index 000000000000..86039f105268 --- /dev/null +++ b/arch/mn10300/kernel/kernel_execve.S @@ -0,0 +1,37 @@ +/* MN10300 In-kernel program execution + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/linkage.h> +#include <asm/unistd.h> + +############################################################################### +# +# Do a system call from kernel instead of calling sys_execve so we end up with +# proper pt_regs. +# +# int kernel_execve(const char *filename, char *const argv[], +# char *const envp[]) +# +# On entry: D0/D1/8(SP): arguments to function +# On return: D0: syscall return. +# +############################################################################### + .globl kernel_execve + .type kernel_execve,@function +kernel_execve: + mov a3,a1 + mov d0,a0 + mov (12,sp),a3 + mov +__NR_execve,d0 + syscall 0 + mov a1,a3 + rets + + .size kernel_execve,.-kernel_execve diff --git a/arch/mn10300/kernel/kprobes.c b/arch/mn10300/kernel/kprobes.c new file mode 100644 index 000000000000..dacafab00eb2 --- /dev/null +++ b/arch/mn10300/kernel/kprobes.c @@ -0,0 +1,653 @@ +/* MN10300 Kernel probes implementation + * + * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved. + * Written by Mark Salter (msalter@redhat.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public Licence as published by + * the Free Software Foundation; either version 2 of the Licence, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public Licence for more details. + * + * You should have received a copy of the GNU General Public Licence + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include <linux/kprobes.h> +#include <linux/ptrace.h> +#include <linux/spinlock.h> +#include <linux/preempt.h> +#include <linux/kdebug.h> +#include <asm/cacheflush.h> + +struct kretprobe_blackpoint kretprobe_blacklist[] = { { NULL, NULL } }; +const int kretprobe_blacklist_size = ARRAY_SIZE(kretprobe_blacklist); + +/* kprobe_status settings */ +#define KPROBE_HIT_ACTIVE 0x00000001 +#define KPROBE_HIT_SS 0x00000002 + +static struct kprobe *current_kprobe; +static unsigned long current_kprobe_orig_pc; +static unsigned long current_kprobe_next_pc; +static int current_kprobe_ss_flags; +static unsigned long kprobe_status; +static kprobe_opcode_t current_kprobe_ss_buf[MAX_INSN_SIZE + 2]; +static unsigned long current_kprobe_bp_addr; + +DEFINE_PER_CPU(struct kprobe *, current_kprobe) = NULL; + + +/* singlestep flag bits */ +#define SINGLESTEP_BRANCH 1 +#define SINGLESTEP_PCREL 2 + +#define READ_BYTE(p, valp) \ + do { *(u8 *)(valp) = *(u8 *)(p); } while (0) + +#define READ_WORD16(p, valp) \ + do { \ + READ_BYTE((p), (valp)); \ + READ_BYTE((u8 *)(p) + 1, (u8 *)(valp) + 1); \ + } while (0) + +#define READ_WORD32(p, valp) \ + do { \ + READ_BYTE((p), (valp)); \ + READ_BYTE((u8 *)(p) + 1, (u8 *)(valp) + 1); \ + READ_BYTE((u8 *)(p) + 2, (u8 *)(valp) + 2); \ + READ_BYTE((u8 *)(p) + 3, (u8 *)(valp) + 3); \ + } while (0) + + +static const u8 mn10300_insn_sizes[256] = +{ + /* 1 2 3 4 5 6 7 8 9 a b c d e f */ + 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, /* 0 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1 */ + 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, /* 2 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, /* 3 */ + 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, /* 4 */ + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, /* 5 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 8 */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 9 */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* a */ + 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* b */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2, /* c */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ + 0, 2, 2, 2, 2, 2, 2, 4, 0, 3, 0, 4, 0, 6, 7, 1 /* f */ +}; + +#define LT (1 << 0) +#define GT (1 << 1) +#define GE (1 << 2) +#define LE (1 << 3) +#define CS (1 << 4) +#define HI (1 << 5) +#define CC (1 << 6) +#define LS (1 << 7) +#define EQ (1 << 8) +#define NE (1 << 9) +#define RA (1 << 10) +#define VC (1 << 11) +#define VS (1 << 12) +#define NC (1 << 13) +#define NS (1 << 14) + +static const u16 cond_table[] = { + /* V C N Z */ + /* 0 0 0 0 */ (NE | NC | CC | VC | GE | GT | HI), + /* 0 0 0 1 */ (EQ | NC | CC | VC | GE | LE | LS), + /* 0 0 1 0 */ (NE | NS | CC | VC | LT | LE | HI), + /* 0 0 1 1 */ (EQ | NS | CC | VC | LT | LE | LS), + /* 0 1 0 0 */ (NE | NC | CS | VC | GE | GT | LS), + /* 0 1 0 1 */ (EQ | NC | CS | VC | GE | LE | LS), + /* 0 1 1 0 */ (NE | NS | CS | VC | LT | LE | LS), + /* 0 1 1 1 */ (EQ | NS | CS | VC | LT | LE | LS), + /* 1 0 0 0 */ (NE | NC | CC | VS | LT | LE | HI), + /* 1 0 0 1 */ (EQ | NC | CC | VS | LT | LE | LS), + /* 1 0 1 0 */ (NE | NS | CC | VS | GE | GT | HI), + /* 1 0 1 1 */ (EQ | NS | CC | VS | GE | LE | LS), + /* 1 1 0 0 */ (NE | NC | CS | VS | LT | LE | LS), + /* 1 1 0 1 */ (EQ | NC | CS | VS | LT | LE | LS), + /* 1 1 1 0 */ (NE | NS | CS | VS | GE | GT | LS), + /* 1 1 1 1 */ (EQ | NS | CS | VS | GE | LE | LS), +}; + +/* + * Calculate what the PC will be after executing next instruction + */ +static unsigned find_nextpc(struct pt_regs *regs, int *flags) +{ + unsigned size; + s8 x8; + s16 x16; + s32 x32; + u8 opc, *pc, *sp, *next; + + next = 0; + *flags = SINGLESTEP_PCREL; + + pc = (u8 *) regs->pc; + sp = (u8 *) (regs + 1); + opc = *pc; + + size = mn10300_insn_sizes[opc]; + if (size > 0) { + next = pc + size; + } else { + switch (opc) { + /* Bxx (d8,PC) */ + case 0xc0 ... 0xca: + x8 = 2; + if (cond_table[regs->epsw & 0xf] & (1 << (opc & 0xf))) + x8 = (s8)pc[1]; + next = pc + x8; + *flags |= SINGLESTEP_BRANCH; + break; + + /* JMP (d16,PC) or CALL (d16,PC) */ + case 0xcc: + case 0xcd: + READ_WORD16(pc + 1, &x16); + next = pc + x16; + *flags |= SINGLESTEP_BRANCH; + break; + + /* JMP (d32,PC) or CALL (d32,PC) */ + case 0xdc: + case 0xdd: + READ_WORD32(pc + 1, &x32); + next = pc + x32; + *flags |= SINGLESTEP_BRANCH; + break; + + /* RETF */ + case 0xde: + next = (u8 *)regs->mdr; + *flags &= ~SINGLESTEP_PCREL; + *flags |= SINGLESTEP_BRANCH; + break; + + /* RET */ + case 0xdf: + sp += pc[2]; + READ_WORD32(sp, &x32); + next = (u8 *)x32; + *flags &= ~SINGLESTEP_PCREL; + *flags |= SINGLESTEP_BRANCH; + break; + + case 0xf0: + next = pc + 2; + opc = pc[1]; + if (opc >= 0xf0 && opc <= 0xf7) { + /* JMP (An) / CALLS (An) */ + switch (opc & 3) { + case 0: + next = (u8 *)regs->a0; + break; + case 1: + next = (u8 *)regs->a1; + break; + case 2: + next = (u8 *)regs->a2; + break; + case 3: + next = (u8 *)regs->a3; + break; + } + *flags &= ~SINGLESTEP_PCREL; + *flags |= SINGLESTEP_BRANCH; + } else if (opc == 0xfc) { + /* RETS */ + READ_WORD32(sp, &x32); + next = (u8 *)x32; + *flags &= ~SINGLESTEP_PCREL; + *flags |= SINGLESTEP_BRANCH; + } else if (opc == 0xfd) { + /* RTI */ + READ_WORD32(sp + 4, &x32); + next = (u8 *)x32; + *flags &= ~SINGLESTEP_PCREL; + *flags |= SINGLESTEP_BRANCH; + } + break; + + /* potential 3-byte conditional branches */ + case 0xf8: + next = pc + 3; + opc = pc[1]; + if (opc >= 0xe8 && opc <= 0xeb && + (cond_table[regs->epsw & 0xf] & + (1 << ((opc & 0xf) + 3))) + ) { + READ_BYTE(pc+2, &x8); + next = pc + x8; + *flags |= SINGLESTEP_BRANCH; + } + break; + + case 0xfa: + if (pc[1] == 0xff) { + /* CALLS (d16,PC) */ + READ_WORD16(pc + 2, &x16); + next = pc + x16; + } else + next = pc + 4; + *flags |= SINGLESTEP_BRANCH; + break; + + case 0xfc: + x32 = 6; + if (pc[1] == 0xff) { + /* CALLS (d32,PC) */ + READ_WORD32(pc + 2, &x32); + } + next = pc + x32; + *flags |= SINGLESTEP_BRANCH; + break; + /* LXX (d8,PC) */ + /* SETLB - loads the next four bytes into the LIR reg */ + case 0xd0 ... 0xda: + case 0xdb: + panic("Can't singlestep Lxx/SETLB\n"); + break; + } + } + return (unsigned)next; + +} + +/* + * set up out of place singlestep of some branching instructions + */ +static unsigned __kprobes singlestep_branch_setup(struct pt_regs *regs) +{ + u8 opc, *pc, *sp, *next; + + next = NULL; + pc = (u8 *) regs->pc; + sp = (u8 *) (regs + 1); + + switch (pc[0]) { + case 0xc0 ... 0xca: /* Bxx (d8,PC) */ + case 0xcc: /* JMP (d16,PC) */ + case 0xdc: /* JMP (d32,PC) */ + case 0xf8: /* Bxx (d8,PC) 3-byte version */ + /* don't really need to do anything except cause trap */ + next = pc; + break; + + case 0xcd: /* CALL (d16,PC) */ + pc[1] = 5; + pc[2] = 0; + next = pc + 5; + break; + + case 0xdd: /* CALL (d32,PC) */ + pc[1] = 7; + pc[2] = 0; + pc[3] = 0; + pc[4] = 0; + next = pc + 7; + break; + + case 0xde: /* RETF */ + next = pc + 3; + regs->mdr = (unsigned) next; + break; + + case 0xdf: /* RET */ + sp += pc[2]; + next = pc + 3; + *(unsigned *)sp = (unsigned) next; + break; + + case 0xf0: + next = pc + 2; + opc = pc[1]; + if (opc >= 0xf0 && opc <= 0xf3) { + /* CALLS (An) */ + /* use CALLS (d16,PC) to avoid mucking with An */ + pc[0] = 0xfa; + pc[1] = 0xff; + pc[2] = 4; + pc[3] = 0; + next = pc + 4; + } else if (opc >= 0xf4 && opc <= 0xf7) { + /* JMP (An) */ + next = pc; + } else if (opc == 0xfc) { + /* RETS */ + next = pc + 2; + *(unsigned *) sp = (unsigned) next; + } else if (opc == 0xfd) { + /* RTI */ + next = pc + 2; + *(unsigned *)(sp + 4) = (unsigned) next; + } + break; + + case 0xfa: /* CALLS (d16,PC) */ + pc[2] = 4; + pc[3] = 0; + next = pc + 4; + break; + + case 0xfc: /* CALLS (d32,PC) */ + pc[2] = 6; + pc[3] = 0; + pc[4] = 0; + pc[5] = 0; + next = pc + 6; + break; + + case 0xd0 ... 0xda: /* LXX (d8,PC) */ + case 0xdb: /* SETLB */ + panic("Can't singlestep Lxx/SETLB\n"); + } + + return (unsigned) next; +} + +int __kprobes arch_prepare_kprobe(struct kprobe *p) +{ + return 0; +} + +void __kprobes arch_copy_kprobe(struct kprobe *p) +{ + memcpy(p->ainsn.insn, p->addr, MAX_INSN_SIZE); +} + +void __kprobes arch_arm_kprobe(struct kprobe *p) +{ + *p->addr = BREAKPOINT_INSTRUCTION; + flush_icache_range((unsigned long) p->addr, + (unsigned long) p->addr + sizeof(kprobe_opcode_t)); +} + +void __kprobes arch_disarm_kprobe(struct kprobe *p) +{ + mn10300_dcache_flush(); + mn10300_icache_inv(); +} + +void arch_remove_kprobe(struct kprobe *p) +{ +} + +static inline +void __kprobes disarm_kprobe(struct kprobe *p, struct pt_regs *regs) +{ + *p->addr = p->opcode; + regs->pc = (unsigned long) p->addr; + mn10300_dcache_flush(); + mn10300_icache_inv(); +} + +static inline +void __kprobes prepare_singlestep(struct kprobe *p, struct pt_regs *regs) +{ + unsigned long nextpc; + + current_kprobe_orig_pc = regs->pc; + memcpy(current_kprobe_ss_buf, &p->ainsn.insn[0], MAX_INSN_SIZE); + regs->pc = (unsigned long) current_kprobe_ss_buf; + + nextpc = find_nextpc(regs, ¤t_kprobe_ss_flags); + if (current_kprobe_ss_flags & SINGLESTEP_PCREL) + current_kprobe_next_pc = + current_kprobe_orig_pc + (nextpc - regs->pc); + else + current_kprobe_next_pc = nextpc; + + /* branching instructions need special handling */ + if (current_kprobe_ss_flags & SINGLESTEP_BRANCH) + nextpc = singlestep_branch_setup(regs); + + current_kprobe_bp_addr = nextpc; + + *(u8 *) nextpc = BREAKPOINT_INSTRUCTION; + mn10300_dcache_flush_range2((unsigned) current_kprobe_ss_buf, + sizeof(current_kprobe_ss_buf)); + mn10300_icache_inv(); +} + +static inline int __kprobes kprobe_handler(struct pt_regs *regs) +{ + struct kprobe *p; + int ret = 0; + unsigned int *addr = (unsigned int *) regs->pc; + + /* We're in an interrupt, but this is clear and BUG()-safe. */ + preempt_disable(); + + /* Check we're not actually recursing */ + if (kprobe_running()) { + /* We *are* holding lock here, so this is safe. + Disarm the probe we just hit, and ignore it. */ + p = get_kprobe(addr); + if (p) { + disarm_kprobe(p, regs); + ret = 1; + } else { + p = current_kprobe; + if (p->break_handler && p->break_handler(p, regs)) + goto ss_probe; + } + /* If it's not ours, can't be delete race, (we hold lock). */ + goto no_kprobe; + } + + p = get_kprobe(addr); + if (!p) { + if (*addr != BREAKPOINT_INSTRUCTION) { + /* The breakpoint instruction was removed right after + * we hit it. Another cpu has removed either a + * probepoint or a debugger breakpoint at this address. + * In either case, no further handling of this + * interrupt is appropriate. + */ + ret = 1; + } + /* Not one of ours: let kernel handle it */ + goto no_kprobe; + } + + kprobe_status = KPROBE_HIT_ACTIVE; + current_kprobe = p; + if (p->pre_handler(p, regs)) { + /* handler has already set things up, so skip ss setup */ + return 1; + } + +ss_probe: + prepare_singlestep(p, regs); + kprobe_status = KPROBE_HIT_SS; + return 1; + +no_kprobe: + preempt_enable_no_resched(); + return ret; +} + +/* + * Called after single-stepping. p->addr is the address of the + * instruction whose first byte has been replaced by the "breakpoint" + * instruction. To avoid the SMP problems that can occur when we + * temporarily put back the original opcode to single-step, we + * single-stepped a copy of the instruction. The address of this + * copy is p->ainsn.insn. + */ +static void __kprobes resume_execution(struct kprobe *p, struct pt_regs *regs) +{ + /* we may need to fixup regs/stack after singlestepping a call insn */ + if (current_kprobe_ss_flags & SINGLESTEP_BRANCH) { + regs->pc = current_kprobe_orig_pc; + switch (p->ainsn.insn[0]) { + case 0xcd: /* CALL (d16,PC) */ + *(unsigned *) regs->sp = regs->mdr = regs->pc + 5; + break; + case 0xdd: /* CALL (d32,PC) */ + /* fixup mdr and return address on stack */ + *(unsigned *) regs->sp = regs->mdr = regs->pc + 7; + break; + case 0xf0: + if (p->ainsn.insn[1] >= 0xf0 && + p->ainsn.insn[1] <= 0xf3) { + /* CALLS (An) */ + /* fixup MDR and return address on stack */ + regs->mdr = regs->pc + 2; + *(unsigned *) regs->sp = regs->mdr; + } + break; + + case 0xfa: /* CALLS (d16,PC) */ + /* fixup MDR and return address on stack */ + *(unsigned *) regs->sp = regs->mdr = regs->pc + 4; + break; + + case 0xfc: /* CALLS (d32,PC) */ + /* fixup MDR and return address on stack */ + *(unsigned *) regs->sp = regs->mdr = regs->pc + 6; + break; + } + } + + regs->pc = current_kprobe_next_pc; + current_kprobe_bp_addr = 0; +} + +static inline int __kprobes post_kprobe_handler(struct pt_regs *regs) +{ + if (!kprobe_running()) + return 0; + + if (current_kprobe->post_handler) + current_kprobe->post_handler(current_kprobe, regs, 0); + + resume_execution(current_kprobe, regs); + reset_current_kprobe(); + preempt_enable_no_resched(); + return 1; +} + +/* Interrupts disabled, kprobe_lock held. */ +static inline +int __kprobes kprobe_fault_handler(struct pt_regs *regs, int trapnr) +{ + if (current_kprobe->fault_handler && + current_kprobe->fault_handler(current_kprobe, regs, trapnr)) + return 1; + + if (kprobe_status & KPROBE_HIT_SS) { + resume_execution(current_kprobe, regs); + reset_current_kprobe(); + preempt_enable_no_resched(); + } + return 0; +} + +/* + * Wrapper routine to for handling exceptions. + */ +int __kprobes kprobe_exceptions_notify(struct notifier_block *self, + unsigned long val, void *data) +{ + struct die_args *args = data; + + switch (val) { + case DIE_BREAKPOINT: + if (current_kprobe_bp_addr != args->regs->pc) { + if (kprobe_handler(args->regs)) + return NOTIFY_STOP; + } else { + if (post_kprobe_handler(args->regs)) + return NOTIFY_STOP; + } + break; + case DIE_GPF: + if (kprobe_running() && + kprobe_fault_handler(args->regs, args->trapnr)) + return NOTIFY_STOP; + break; + default: + break; + } + return NOTIFY_DONE; +} + +/* Jprobes support. */ +static struct pt_regs jprobe_saved_regs; +static struct pt_regs *jprobe_saved_regs_location; +static kprobe_opcode_t jprobe_saved_stack[MAX_STACK_SIZE]; + +int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs) +{ + struct jprobe *jp = container_of(p, struct jprobe, kp); + + jprobe_saved_regs_location = regs; + memcpy(&jprobe_saved_regs, regs, sizeof(struct pt_regs)); + + /* Save a whole stack frame, this gets arguments + * pushed onto the stack after using up all the + * arg registers. + */ + memcpy(&jprobe_saved_stack, regs + 1, sizeof(jprobe_saved_stack)); + + /* setup return addr to the jprobe handler routine */ + regs->pc = (unsigned long) jp->entry; + return 1; +} + +void __kprobes jprobe_return(void) +{ + void *orig_sp = jprobe_saved_regs_location + 1; + + preempt_enable_no_resched(); + asm volatile(" mov %0,sp\n" + ".globl jprobe_return_bp_addr\n" + "jprobe_return_bp_addr:\n\t" + " .byte 0xff\n" + : : "d" (orig_sp)); +} + +extern void jprobe_return_bp_addr(void); + +int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs) +{ + u8 *addr = (u8 *) regs->pc; + + if (addr == (u8 *) jprobe_return_bp_addr) { + if (jprobe_saved_regs_location != regs) { + printk(KERN_ERR"JPROBE:" + " Current regs (%p) does not match saved regs" + " (%p).\n", + regs, jprobe_saved_regs_location); + BUG(); + } + + /* Restore old register state. + */ + memcpy(regs, &jprobe_saved_regs, sizeof(struct pt_regs)); + + memcpy(regs + 1, &jprobe_saved_stack, + sizeof(jprobe_saved_stack)); + return 1; + } + return 0; +} + +int __init arch_init_kprobes(void) +{ + return 0; +} diff --git a/arch/mn10300/kernel/kthread.S b/arch/mn10300/kernel/kthread.S new file mode 100644 index 000000000000..b5ae467ac5ec --- /dev/null +++ b/arch/mn10300/kernel/kthread.S @@ -0,0 +1,31 @@ +/* MN10300 Kernel thread trampoline function + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by Mark Salter (msalter@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + .text + +############################################################################### +# +# kernel_thread_helper - trampoline for kernel_thread() +# +# On entry: +# A2 = address of function to call +# D2 = function argument +# +############################################################################### + .globl kernel_thread_helper + .type kernel_thread_helper,@function +kernel_thread_helper: + mov do_exit,d1 + mov d1,(sp) + mov d1,mdr + mov d2,d0 + jmp (a2) + + .size kernel_thread_helper,.-kernel_thread_helper diff --git a/arch/mn10300/kernel/mn10300-debug.c b/arch/mn10300/kernel/mn10300-debug.c new file mode 100644 index 000000000000..bd8196478cbc --- /dev/null +++ b/arch/mn10300/kernel/mn10300-debug.c @@ -0,0 +1,58 @@ +/* Debugging stuff for the MN10300-based processors + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/sched.h> +#include <asm/serial-regs.h> + +#undef MN10300_CONSOLE_ON_SERIO + +/* + * write a string directly through one of the serial ports on-board the MN10300 + */ +#ifdef MN10300_CONSOLE_ON_SERIO +void debug_to_serial_mnser(const char *p, int n) +{ + char ch; + + for (; n > 0; n--) { + ch = *p++; + +#if MN10300_CONSOLE_ON_SERIO == 0 + while (SC0STR & (SC01STR_TBF)) continue; + SC0TXB = ch; + while (SC0STR & (SC01STR_TBF)) continue; + if (ch == 0x0a) { + SC0TXB = 0x0d; + while (SC0STR & (SC01STR_TBF)) continue; + } + +#elif MN10300_CONSOLE_ON_SERIO == 1 + while (SC1STR & (SC01STR_TBF)) continue; + SC1TXB = ch; + while (SC1STR & (SC01STR_TBF)) continue; + if (ch == 0x0a) { + SC1TXB = 0x0d; + while (SC1STR & (SC01STR_TBF)) continue; + } + +#elif MN10300_CONSOLE_ON_SERIO == 2 + while (SC2STR & (SC2STR_TBF)) continue; + SC2TXB = ch; + while (SC2STR & (SC2STR_TBF)) continue; + if (ch == 0x0a) { + SC2TXB = 0x0d; + while (SC2STR & (SC2STR_TBF)) continue; + } + +#endif + } +} +#endif + diff --git a/arch/mn10300/kernel/mn10300-serial-low.S b/arch/mn10300/kernel/mn10300-serial-low.S new file mode 100644 index 000000000000..ef3f4c1df2a4 --- /dev/null +++ b/arch/mn10300/kernel/mn10300-serial-low.S @@ -0,0 +1,191 @@ +############################################################################### +# +# Virtual DMA driver for MN10300 serial ports +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/page.h> +#include <asm/smp.h> +#include <asm/cpu-regs.h> +#include <asm/frame.inc> +#include <asm/timer-regs.h> +#include <asm/proc/cache.h> +#include <asm/unit/timex.h> +#include "mn10300-serial.h" + +#define SCxCTR 0x00 +#define SCxICR 0x04 +#define SCxTXB 0x08 +#define SCxRXB 0x09 +#define SCxSTR 0x0c +#define SCxTIM 0x0d + + .text + +############################################################################### +# +# serial port interrupt virtual DMA entry point +# - intended to run at interrupt priority 1 (not affected by local_irq_disable) +# +############################################################################### + .balign L1_CACHE_BYTES +ENTRY(mn10300_serial_vdma_interrupt) + or EPSW_IE,psw # permit overriding by + # debugging interrupts + movm [d2,d3,a2,a3,exreg0],(sp) + + movhu (IAGR),a2 # see if which interrupt is + # pending + and IAGR_GN,a2 + add a2,a2 + add mn10300_serial_int_tbl,a2 + + mov (a2+),a3 + mov (__iobase,a3),e2 + mov (a2),a2 + jmp (a2) + +############################################################################### +# +# serial port receive interrupt virtual DMA entry point +# - intended to run at interrupt priority 1 (not affected by local_irq_disable) +# - stores data/status byte pairs in the ring buffer +# - induces a scheduler tick timer interrupt when done, which we then subvert +# on entry: +# A3 struct mn10300_serial_port * +# E2 I/O port base +# +############################################################################### +ENTRY(mn10300_serial_vdma_rx_handler) + mov (__rx_icr,a3),e3 + mov GxICR_DETECT,d2 + movbu d2,(e3) # ACK the interrupt + movhu (e3),d2 # flush + + mov (__rx_inp,a3),d3 + mov d3,a2 + add 2,d3 + and MNSC_BUFFER_SIZE-1,d3 + mov (__rx_outp,a3),d2 + cmp d3,d2 + beq mnsc_vdma_rx_overflow + + mov (__rx_buffer,a3),d2 + add d2,a2 + movhu (SCxSTR,e2),d2 + movbu d2,(1,a2) + movbu (SCxRXB,e2),d2 + movbu d2,(a2) + mov d3,(__rx_inp,a3) + bset MNSCx_RX_AVAIL,(__intr_flags,a3) + +mnsc_vdma_rx_done: + mov (__tm_icr,a3),a2 + mov GxICR_LEVEL_6|GxICR_ENABLE|GxICR_REQUEST|GxICR_DETECT,d2 + movhu d2,(a2) # request a slow interrupt + movhu (a2),d2 # flush + + movm (sp),[d2,d3,a2,a3,exreg0] + rti + +mnsc_vdma_rx_overflow: + bset MNSCx_RX_OVERF,(__intr_flags,a3) + bra mnsc_vdma_rx_done + +############################################################################### +# +# serial port transmit interrupt virtual DMA entry point +# - intended to run at interrupt priority 1 (not affected by local_irq_disable) +# - retrieves data bytes from the ring buffer and passes them to the serial port +# - induces a scheduler tick timer interrupt when done, which we then subvert +# A3 struct mn10300_serial_port * +# E2 I/O port base +# +############################################################################### + .balign L1_CACHE_BYTES +ENTRY(mn10300_serial_vdma_tx_handler) + mov (__tx_icr,a3),e3 + mov GxICR_DETECT,d2 + movbu d2,(e3) # ACK the interrupt + movhu (e3),d2 # flush + + btst 0x01,(__tx_break,a3) # handle transmit break request + bne mnsc_vdma_tx_break + + movbu (SCxSTR,e2),d2 # don't try and transmit a char if the + # buffer is not empty + btst SC01STR_TBF,d2 # (may have tried to jumpstart) + bne mnsc_vdma_tx_noint + + movbu (__tx_xchar,a3),d2 # handle hi-pri XON/XOFF + or d2,d2 + bne mnsc_vdma_tx_xchar + + mov (__tx_info_buffer,a3),a2 # get the uart_info struct for Tx + mov (__xmit_tail,a2),d3 + mov (__xmit_head,a2),d2 + cmp d3,d2 + beq mnsc_vdma_tx_empty + + mov (__xmit_buffer,a2),d2 # get a char from the buffer and + # transmit it + movbu (d3,d2),d2 + movbu d2,(SCxTXB,e2) # Tx + + inc d3 # advance the buffer pointer + and __UART_XMIT_SIZE-1,d3 + mov (__xmit_head,a2),d2 + mov d3,(__xmit_tail,a2) + + sub d3,d2 # see if we've written everything + beq mnsc_vdma_tx_empty + + and __UART_XMIT_SIZE-1,d2 # see if we just made a hole + cmp __UART_XMIT_SIZE-2,d2 + beq mnsc_vdma_tx_made_hole + +mnsc_vdma_tx_done: + mov (__tm_icr,a3),a2 + mov GxICR_LEVEL_6|GxICR_ENABLE|GxICR_REQUEST|GxICR_DETECT,d2 + movhu d2,(a2) # request a slow interrupt + movhu (a2),d2 # flush + +mnsc_vdma_tx_noint: + movm (sp),[d2,d3,a2,a3,exreg0] + rti + +mnsc_vdma_tx_empty: + mov +(GxICR_LEVEL_1|GxICR_DETECT),d2 + movhu d2,(e3) # disable the interrupt + movhu (e3),d2 # flush + + bset MNSCx_TX_EMPTY,(__intr_flags,a3) + bra mnsc_vdma_tx_done + +mnsc_vdma_tx_break: + movhu (SCxCTR,e2),d2 # turn on break mode + or SC01CTR_BKE,d2 + movhu d2,(SCxCTR,e2) + mov +(GxICR_LEVEL_1|GxICR_DETECT),d2 + movhu d2,(e3) # disable transmit interrupts on this + # channel + movhu (e3),d2 # flush + bra mnsc_vdma_tx_noint + +mnsc_vdma_tx_xchar: + bclr 0xff,(__tx_xchar,a3) + movbu d2,(SCxTXB,e2) + bra mnsc_vdma_tx_done + +mnsc_vdma_tx_made_hole: + bset MNSCx_TX_SPACE,(__intr_flags,a3) + bra mnsc_vdma_tx_done diff --git a/arch/mn10300/kernel/mn10300-serial.c b/arch/mn10300/kernel/mn10300-serial.c new file mode 100644 index 000000000000..b9c268c6b2fb --- /dev/null +++ b/arch/mn10300/kernel/mn10300-serial.c @@ -0,0 +1,1480 @@ +/* MN10300 On-chip serial port UART driver + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +static const char serial_name[] = "MN10300 Serial driver"; +static const char serial_version[] = "mn10300_serial-1.0"; +static const char serial_revdate[] = "2007-11-06"; + +#if defined(CONFIG_MN10300_TTYSM_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/serial.h> +#include <linux/circ_buf.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/sysrq.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/bitops.h> +#include <asm/serial-regs.h> +#include <asm/unit/timex.h> +#include "mn10300-serial.h" + +static inline __attribute__((format(printf, 1, 2))) +void no_printk(const char *fmt, ...) +{ +} + +#define kenter(FMT, ...) \ + printk(KERN_DEBUG "-->%s(" FMT ")\n", __func__, ##__VA_ARGS__) +#define _enter(FMT, ...) \ + no_printk(KERN_DEBUG "-->%s(" FMT ")\n", __func__, ##__VA_ARGS__) +#define kdebug(FMT, ...) \ + printk(KERN_DEBUG "--- " FMT "\n", ##__VA_ARGS__) +#define _debug(FMT, ...) \ + no_printk(KERN_DEBUG "--- " FMT "\n", ##__VA_ARGS__) +#define kproto(FMT, ...) \ + printk(KERN_DEBUG "### MNSERIAL " FMT " ###\n", ##__VA_ARGS__) +#define _proto(FMT, ...) \ + no_printk(KERN_DEBUG "### MNSERIAL " FMT " ###\n", ##__VA_ARGS__) + +#define NR_UARTS 3 + +#ifdef CONFIG_MN10300_TTYSM_CONSOLE +static void mn10300_serial_console_write(struct console *co, + const char *s, unsigned count); +static int __init mn10300_serial_console_setup(struct console *co, + char *options); + +static struct uart_driver mn10300_serial_driver; +static struct console mn10300_serial_console = { + .name = "ttySM", + .write = mn10300_serial_console_write, + .device = uart_console_device, + .setup = mn10300_serial_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &mn10300_serial_driver, +}; +#endif + +static struct uart_driver mn10300_serial_driver = { + .owner = NULL, + .driver_name = "mn10300-serial", + .dev_name = "ttySM", + .major = TTY_MAJOR, + .minor = 128, + .nr = NR_UARTS, +#ifdef CONFIG_MN10300_TTYSM_CONSOLE + .cons = &mn10300_serial_console, +#endif +}; + +static unsigned int mn10300_serial_tx_empty(struct uart_port *); +static void mn10300_serial_set_mctrl(struct uart_port *, unsigned int mctrl); +static unsigned int mn10300_serial_get_mctrl(struct uart_port *); +static void mn10300_serial_stop_tx(struct uart_port *); +static void mn10300_serial_start_tx(struct uart_port *); +static void mn10300_serial_send_xchar(struct uart_port *, char ch); +static void mn10300_serial_stop_rx(struct uart_port *); +static void mn10300_serial_enable_ms(struct uart_port *); +static void mn10300_serial_break_ctl(struct uart_port *, int ctl); +static int mn10300_serial_startup(struct uart_port *); +static void mn10300_serial_shutdown(struct uart_port *); +static void mn10300_serial_set_termios(struct uart_port *, + struct ktermios *new, + struct ktermios *old); +static const char *mn10300_serial_type(struct uart_port *); +static void mn10300_serial_release_port(struct uart_port *); +static int mn10300_serial_request_port(struct uart_port *); +static void mn10300_serial_config_port(struct uart_port *, int); +static int mn10300_serial_verify_port(struct uart_port *, + struct serial_struct *); + +static const struct uart_ops mn10300_serial_ops = { + .tx_empty = mn10300_serial_tx_empty, + .set_mctrl = mn10300_serial_set_mctrl, + .get_mctrl = mn10300_serial_get_mctrl, + .stop_tx = mn10300_serial_stop_tx, + .start_tx = mn10300_serial_start_tx, + .send_xchar = mn10300_serial_send_xchar, + .stop_rx = mn10300_serial_stop_rx, + .enable_ms = mn10300_serial_enable_ms, + .break_ctl = mn10300_serial_break_ctl, + .startup = mn10300_serial_startup, + .shutdown = mn10300_serial_shutdown, + .set_termios = mn10300_serial_set_termios, + .type = mn10300_serial_type, + .release_port = mn10300_serial_release_port, + .request_port = mn10300_serial_request_port, + .config_port = mn10300_serial_config_port, + .verify_port = mn10300_serial_verify_port, +}; + +static irqreturn_t mn10300_serial_interrupt(int irq, void *dev_id); + +/* + * the first on-chip serial port: ttySM0 (aka SIF0) + */ +#ifdef CONFIG_MN10300_TTYSM0 +struct mn10300_serial_port mn10300_serial_port_sif0 = { + .uart.ops = &mn10300_serial_ops, + .uart.membase = (void __iomem *) &SC0CTR, + .uart.mapbase = (unsigned long) &SC0CTR, + .uart.iotype = UPIO_MEM, + .uart.irq = 0, + .uart.uartclk = 0, /* MN10300_IOCLK, */ + .uart.fifosize = 1, + .uart.flags = UPF_BOOT_AUTOCONF, + .uart.line = 0, + .uart.type = PORT_MN10300, + .uart.lock = + __SPIN_LOCK_UNLOCKED(mn10300_serial_port_sif0.uart.lock), + .name = "ttySM0", + ._iobase = &SC0CTR, + ._control = &SC0CTR, + ._status = (volatile u8 *) &SC0STR, + ._intr = &SC0ICR, + ._rxb = &SC0RXB, + ._txb = &SC0TXB, + .rx_name = "ttySM0/Rx", + .tx_name = "ttySM0/Tx", +#ifdef CONFIG_MN10300_TTYSM0_TIMER8 + .tm_name = "ttySM0/Timer8", + ._tmxmd = &TM8MD, + ._tmxbr = &TM8BR, + ._tmicr = &TM8ICR, + .tm_irq = TM8IRQ, + .div_timer = MNSCx_DIV_TIMER_16BIT, +#else /* CONFIG_MN10300_TTYSM0_TIMER2 */ + .tm_name = "ttySM0/Timer2", + ._tmxmd = &TM2MD, + ._tmxbr = (volatile u16 *) &TM2BR, + ._tmicr = &TM2ICR, + .tm_irq = TM2IRQ, + .div_timer = MNSCx_DIV_TIMER_8BIT, +#endif + .rx_irq = SC0RXIRQ, + .tx_irq = SC0TXIRQ, + .rx_icr = &GxICR(SC0RXIRQ), + .tx_icr = &GxICR(SC0TXIRQ), + .clock_src = MNSCx_CLOCK_SRC_IOCLK, + .options = 0, +#ifdef CONFIG_GDBSTUB_ON_TTYSM0 + .gdbstub = 1, +#endif +}; +#endif /* CONFIG_MN10300_TTYSM0 */ + +/* + * the second on-chip serial port: ttySM1 (aka SIF1) + */ +#ifdef CONFIG_MN10300_TTYSM1 +struct mn10300_serial_port mn10300_serial_port_sif1 = { + .uart.ops = &mn10300_serial_ops, + .uart.membase = (void __iomem *) &SC1CTR, + .uart.mapbase = (unsigned long) &SC1CTR, + .uart.iotype = UPIO_MEM, + .uart.irq = 0, + .uart.uartclk = 0, /* MN10300_IOCLK, */ + .uart.fifosize = 1, + .uart.flags = UPF_BOOT_AUTOCONF, + .uart.line = 1, + .uart.type = PORT_MN10300, + .uart.lock = + __SPIN_LOCK_UNLOCKED(mn10300_serial_port_sif1.uart.lock), + .name = "ttySM1", + ._iobase = &SC1CTR, + ._control = &SC1CTR, + ._status = (volatile u8 *) &SC1STR, + ._intr = &SC1ICR, + ._rxb = &SC1RXB, + ._txb = &SC1TXB, + .rx_name = "ttySM1/Rx", + .tx_name = "ttySM1/Tx", +#ifdef CONFIG_MN10300_TTYSM1_TIMER9 + .tm_name = "ttySM1/Timer9", + ._tmxmd = &TM9MD, + ._tmxbr = &TM9BR, + ._tmicr = &TM9ICR, + .tm_irq = TM9IRQ, + .div_timer = MNSCx_DIV_TIMER_16BIT, +#else /* CONFIG_MN10300_TTYSM1_TIMER3 */ + .tm_name = "ttySM1/Timer3", + ._tmxmd = &TM3MD, + ._tmxbr = (volatile u16 *) &TM3BR, + ._tmicr = &TM3ICR, + .tm_irq = TM3IRQ, + .div_timer = MNSCx_DIV_TIMER_8BIT, +#endif + .rx_irq = SC1RXIRQ, + .tx_irq = SC1TXIRQ, + .rx_icr = &GxICR(SC1RXIRQ), + .tx_icr = &GxICR(SC1TXIRQ), + .clock_src = MNSCx_CLOCK_SRC_IOCLK, + .options = 0, +#ifdef CONFIG_GDBSTUB_ON_TTYSM1 + .gdbstub = 1, +#endif +}; +#endif /* CONFIG_MN10300_TTYSM1 */ + +/* + * the third on-chip serial port: ttySM2 (aka SIF2) + */ +#ifdef CONFIG_MN10300_TTYSM2 +struct mn10300_serial_port mn10300_serial_port_sif2 = { + .uart.ops = &mn10300_serial_ops, + .uart.membase = (void __iomem *) &SC2CTR, + .uart.mapbase = (unsigned long) &SC2CTR, + .uart.iotype = UPIO_MEM, + .uart.irq = 0, + .uart.uartclk = 0, /* MN10300_IOCLK, */ + .uart.fifosize = 1, + .uart.flags = UPF_BOOT_AUTOCONF, + .uart.line = 2, +#ifdef CONFIG_MN10300_TTYSM2_CTS + .uart.type = PORT_MN10300_CTS, +#else + .uart.type = PORT_MN10300, +#endif + .uart.lock = + __SPIN_LOCK_UNLOCKED(mn10300_serial_port_sif2.uart.lock), + .name = "ttySM2", + .rx_name = "ttySM2/Rx", + .tx_name = "ttySM2/Tx", + .tm_name = "ttySM2/Timer10", + ._iobase = &SC2CTR, + ._control = &SC2CTR, + ._status = &SC2STR, + ._intr = &SC2ICR, + ._rxb = &SC2RXB, + ._txb = &SC2TXB, + ._tmxmd = &TM10MD, + ._tmxbr = &TM10BR, + ._tmicr = &TM10ICR, + .tm_irq = TM10IRQ, + .div_timer = MNSCx_DIV_TIMER_16BIT, + .rx_irq = SC2RXIRQ, + .tx_irq = SC2TXIRQ, + .rx_icr = &GxICR(SC2RXIRQ), + .tx_icr = &GxICR(SC2TXIRQ), + .clock_src = MNSCx_CLOCK_SRC_IOCLK, +#ifdef CONFIG_MN10300_TTYSM2_CTS + .options = MNSCx_OPT_CTS, +#else + .options = 0, +#endif +#ifdef CONFIG_GDBSTUB_ON_TTYSM2 + .gdbstub = 1, +#endif +}; +#endif /* CONFIG_MN10300_TTYSM2 */ + + +/* + * list of available serial ports + */ +struct mn10300_serial_port *mn10300_serial_ports[NR_UARTS + 1] = { +#ifdef CONFIG_MN10300_TTYSM0 + [0] = &mn10300_serial_port_sif0, +#endif +#ifdef CONFIG_MN10300_TTYSM1 + [1] = &mn10300_serial_port_sif1, +#endif +#ifdef CONFIG_MN10300_TTYSM2 + [2] = &mn10300_serial_port_sif2, +#endif + [NR_UARTS] = NULL, +}; + + +/* + * we abuse the serial ports' baud timers' interrupt lines to get the ability + * to deliver interrupts to userspace as we use the ports' interrupt lines to + * do virtual DMA on account of the ports having no hardware FIFOs + * + * we can generate an interrupt manually in the assembly stubs by writing to + * the enable and detect bits in the interrupt control register, so all we need + * to do here is disable the interrupt line + * + * note that we can't just leave the line enabled as the baud rate timer *also* + * generates interrupts + */ +static void mn10300_serial_mask_ack(unsigned int irq) +{ + u16 tmp; + GxICR(irq) = GxICR_LEVEL_6; + tmp = GxICR(irq); /* flush write buffer */ +} + +static void mn10300_serial_nop(unsigned int irq) +{ +} + +static struct irq_chip mn10300_serial_pic = { + .name = "mnserial", + .ack = mn10300_serial_mask_ack, + .mask = mn10300_serial_mask_ack, + .mask_ack = mn10300_serial_mask_ack, + .unmask = mn10300_serial_nop, + .end = mn10300_serial_nop, +}; + + +/* + * serial virtual DMA interrupt jump table + */ +struct mn10300_serial_int mn10300_serial_int_tbl[NR_IRQS]; + +static void mn10300_serial_dis_tx_intr(struct mn10300_serial_port *port) +{ + u16 x; + *port->tx_icr = GxICR_LEVEL_1 | GxICR_DETECT; + x = *port->tx_icr; +} + +static void mn10300_serial_en_tx_intr(struct mn10300_serial_port *port) +{ + u16 x; + *port->tx_icr = GxICR_LEVEL_1 | GxICR_ENABLE; + x = *port->tx_icr; +} + +static void mn10300_serial_dis_rx_intr(struct mn10300_serial_port *port) +{ + u16 x; + *port->rx_icr = GxICR_LEVEL_1 | GxICR_DETECT; + x = *port->rx_icr; +} + +/* + * multi-bit equivalent of test_and_clear_bit() + */ +static int mask_test_and_clear(volatile u8 *ptr, u8 mask) +{ + u32 epsw; + asm volatile(" bclr %1,(%2) \n" + " mov epsw,%0 \n" + : "=d"(epsw) : "d"(mask), "a"(ptr)); + return !(epsw & EPSW_FLAG_Z); +} + +/* + * receive chars from the ring buffer for this serial port + * - must do break detection here (not done in the UART) + */ +static void mn10300_serial_receive_interrupt(struct mn10300_serial_port *port) +{ + struct uart_icount *icount = &port->uart.icount; + struct tty_struct *tty = port->uart.info->tty; + unsigned ix; + int count; + u8 st, ch, push, status, overrun; + + _enter("%s", port->name); + + push = 0; + + count = CIRC_CNT(port->rx_inp, port->rx_outp, MNSC_BUFFER_SIZE); + count = tty_buffer_request_room(tty, count); + if (count == 0) { + if (!tty->low_latency) + tty_flip_buffer_push(tty); + return; + } + +try_again: + /* pull chars out of the hat */ + ix = port->rx_outp; + if (ix == port->rx_inp) { + if (push && !tty->low_latency) + tty_flip_buffer_push(tty); + return; + } + + ch = port->rx_buffer[ix++]; + st = port->rx_buffer[ix++]; + smp_rmb(); + port->rx_outp = ix & (MNSC_BUFFER_SIZE - 1); + port->uart.icount.rx++; + + st &= SC01STR_FEF | SC01STR_PEF | SC01STR_OEF; + status = 0; + overrun = 0; + + /* the UART doesn't detect BREAK, so we have to do that ourselves + * - it starts as a framing error on a NUL character + * - then we count another two NUL characters before issuing TTY_BREAK + * - then we end on a normal char or one that has all the bottom bits + * zero and the top bits set + */ + switch (port->rx_brk) { + case 0: + /* not breaking at the moment */ + break; + + case 1: + if (st & SC01STR_FEF && ch == 0) { + port->rx_brk = 2; + goto try_again; + } + goto not_break; + + case 2: + if (st & SC01STR_FEF && ch == 0) { + port->rx_brk = 3; + _proto("Rx Break Detected"); + icount->brk++; + if (uart_handle_break(&port->uart)) + goto ignore_char; + status |= 1 << TTY_BREAK; + goto insert; + } + goto not_break; + + default: + if (st & (SC01STR_FEF | SC01STR_PEF | SC01STR_OEF)) + goto try_again; /* still breaking */ + + port->rx_brk = 0; /* end of the break */ + + switch (ch) { + case 0xFF: + case 0xFE: + case 0xFC: + case 0xF8: + case 0xF0: + case 0xE0: + case 0xC0: + case 0x80: + case 0x00: + /* discard char at probable break end */ + goto try_again; + } + break; + } + +process_errors: + /* handle framing error */ + if (st & SC01STR_FEF) { + if (ch == 0) { + /* framing error with NUL char is probably a BREAK */ + port->rx_brk = 1; + goto try_again; + } + + _proto("Rx Framing Error"); + icount->frame++; + status |= 1 << TTY_FRAME; + } + + /* handle parity error */ + if (st & SC01STR_PEF) { + _proto("Rx Parity Error"); + icount->parity++; + status = TTY_PARITY; + } + + /* handle normal char */ + if (status == 0) { + if (uart_handle_sysrq_char(&port->uart, ch)) + goto ignore_char; + status = (1 << TTY_NORMAL); + } + + /* handle overrun error */ + if (st & SC01STR_OEF) { + if (port->rx_brk) + goto try_again; + + _proto("Rx Overrun Error"); + icount->overrun++; + overrun = 1; + } + +insert: + status &= port->uart.read_status_mask; + + if (!overrun && !(status & port->uart.ignore_status_mask)) { + int flag; + + if (status & (1 << TTY_BREAK)) + flag = TTY_BREAK; + else if (status & (1 << TTY_PARITY)) + flag = TTY_PARITY; + else if (status & (1 << TTY_FRAME)) + flag = TTY_FRAME; + else + flag = TTY_NORMAL; + + tty_insert_flip_char(tty, ch, flag); + } + + /* overrun is special, since it's reported immediately, and doesn't + * affect the current character + */ + if (overrun) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + + count--; + if (count <= 0) { + if (!tty->low_latency) + tty_flip_buffer_push(tty); + return; + } + +ignore_char: + push = 1; + goto try_again; + +not_break: + port->rx_brk = 0; + goto process_errors; +} + +/* + * handle an interrupt from the serial transmission "virtual DMA" driver + * - note: the interrupt routine will disable its own interrupts when the Tx + * buffer is empty + */ +static void mn10300_serial_transmit_interrupt(struct mn10300_serial_port *port) +{ + _enter("%s", port->name); + + if (uart_tx_stopped(&port->uart) || + uart_circ_empty(&port->uart.info->xmit)) + mn10300_serial_dis_tx_intr(port); + + if (uart_circ_chars_pending(&port->uart.info->xmit) < WAKEUP_CHARS) + uart_write_wakeup(&port->uart); +} + +/* + * deal with a change in the status of the CTS line + */ +static void mn10300_serial_cts_changed(struct mn10300_serial_port *port, u8 st) +{ + u16 ctr; + + port->tx_cts = st; + port->uart.icount.cts++; + + /* flip the CTS state selector flag to interrupt when it changes + * back */ + ctr = *port->_control; + ctr ^= SC2CTR_TWS; + *port->_control = ctr; + + uart_handle_cts_change(&port->uart, st & SC2STR_CTS); + wake_up_interruptible(&port->uart.info->delta_msr_wait); +} + +/* + * handle a virtual interrupt generated by the lower level "virtual DMA" + * routines (irq is the baud timer interrupt) + */ +static irqreturn_t mn10300_serial_interrupt(int irq, void *dev_id) +{ + struct mn10300_serial_port *port = dev_id; + u8 st; + + spin_lock(&port->uart.lock); + + if (port->intr_flags) { + _debug("INT %s: %x", port->name, port->intr_flags); + + if (mask_test_and_clear(&port->intr_flags, MNSCx_RX_AVAIL)) + mn10300_serial_receive_interrupt(port); + + if (mask_test_and_clear(&port->intr_flags, + MNSCx_TX_SPACE | MNSCx_TX_EMPTY)) + mn10300_serial_transmit_interrupt(port); + } + + /* the only modem control line amongst the whole lot is CTS on + * serial port 2 */ + if (port->type == PORT_MN10300_CTS) { + st = *port->_status; + if ((port->tx_cts ^ st) & SC2STR_CTS) + mn10300_serial_cts_changed(port, st); + } + + spin_unlock(&port->uart.lock); + + return IRQ_HANDLED; +} + +/* + * return indication of whether the hardware transmit buffer is empty + */ +static unsigned int mn10300_serial_tx_empty(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s", port->name); + + return (*port->_status & (SC01STR_TXF | SC01STR_TBF)) ? + 0 : TIOCSER_TEMT; +} + +/* + * set the modem control lines (we don't have any) + */ +static void mn10300_serial_set_mctrl(struct uart_port *_port, + unsigned int mctrl) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s,%x", port->name, mctrl); +} + +/* + * get the modem control line statuses + */ +static unsigned int mn10300_serial_get_mctrl(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s", port->name); + + if (port->type == PORT_MN10300_CTS && !(*port->_status & SC2STR_CTS)) + return TIOCM_CAR | TIOCM_DSR; + + return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR; +} + +/* + * stop transmitting characters + */ +static void mn10300_serial_stop_tx(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s", port->name); + + /* disable the virtual DMA */ + mn10300_serial_dis_tx_intr(port); +} + +/* + * start transmitting characters + * - jump-start transmission if it has stalled + * - enable the serial Tx interrupt (used by the virtual DMA controller) + * - force an interrupt to happen if necessary + */ +static void mn10300_serial_start_tx(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + u16 x; + + _enter("%s{%lu}", + port->name, + CIRC_CNT(&port->uart.info->xmit.head, + &port->uart.info->xmit.tail, + UART_XMIT_SIZE)); + + /* kick the virtual DMA controller */ + x = *port->tx_icr; + x |= GxICR_ENABLE; + + if (*port->_status & SC01STR_TBF) + x &= ~(GxICR_REQUEST | GxICR_DETECT); + else + x |= GxICR_REQUEST | GxICR_DETECT; + + _debug("CTR=%04hx ICR=%02hx STR=%04x TMD=%02hx TBR=%04hx ICR=%04hx", + *port->_control, *port->_intr, *port->_status, + *port->_tmxmd, *port->_tmxbr, *port->tx_icr); + + *port->tx_icr = x; + x = *port->tx_icr; +} + +/* + * transmit a high-priority XON/XOFF character + */ +static void mn10300_serial_send_xchar(struct uart_port *_port, char ch) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s,%02x", port->name, ch); + + if (likely(port->gdbstub)) { + port->tx_xchar = ch; + if (ch) + mn10300_serial_en_tx_intr(port); + } +} + +/* + * stop receiving characters + * - called whilst the port is being closed + */ +static void mn10300_serial_stop_rx(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + u16 ctr; + + _enter("%s", port->name); + + ctr = *port->_control; + ctr &= ~SC01CTR_RXE; + *port->_control = ctr; + + mn10300_serial_dis_rx_intr(port); +} + +/* + * enable modem status interrupts + */ +static void mn10300_serial_enable_ms(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + u16 ctr, cts; + + _enter("%s", port->name); + + if (port->type == PORT_MN10300_CTS) { + /* want to interrupt when CTS goes low if CTS is now high and + * vice versa + */ + port->tx_cts = *port->_status; + + cts = (port->tx_cts & SC2STR_CTS) ? + SC2CTR_TWE : SC2CTR_TWE | SC2CTR_TWS; + + ctr = *port->_control; + ctr &= ~SC2CTR_TWS; + ctr |= cts; + *port->_control = ctr; + + mn10300_serial_en_tx_intr(port); + } +} + +/* + * transmit or cease transmitting a break signal + */ +static void mn10300_serial_break_ctl(struct uart_port *_port, int ctl) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s,%d", port->name, ctl); + + if (ctl) { + /* tell the virtual DMA handler to assert BREAK */ + port->tx_break = 1; + mn10300_serial_en_tx_intr(port); + } else { + port->tx_break = 0; + *port->_control &= ~SC01CTR_BKE; + mn10300_serial_en_tx_intr(port); + } +} + +/* + * grab the interrupts and enable the port for reception + */ +static int mn10300_serial_startup(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + struct mn10300_serial_int *pint; + + _enter("%s{%d}", port->name, port->gdbstub); + + if (unlikely(port->gdbstub)) + return -EBUSY; + + /* allocate an Rx buffer for the virtual DMA handler */ + port->rx_buffer = kmalloc(MNSC_BUFFER_SIZE, GFP_KERNEL); + if (!port->rx_buffer) + return -ENOMEM; + + port->rx_inp = port->rx_outp = 0; + + /* finally, enable the device */ + *port->_intr = SC01ICR_TI; + *port->_control |= SC01CTR_TXE | SC01CTR_RXE; + + pint = &mn10300_serial_int_tbl[port->rx_irq]; + pint->port = port; + pint->vdma = mn10300_serial_vdma_rx_handler; + pint = &mn10300_serial_int_tbl[port->tx_irq]; + pint->port = port; + pint->vdma = mn10300_serial_vdma_tx_handler; + + set_intr_level(port->rx_irq, GxICR_LEVEL_1); + set_intr_level(port->tx_irq, GxICR_LEVEL_1); + set_irq_chip(port->tm_irq, &mn10300_serial_pic); + + if (request_irq(port->rx_irq, mn10300_serial_interrupt, + IRQF_DISABLED, port->rx_name, port) < 0) + goto error; + + if (request_irq(port->tx_irq, mn10300_serial_interrupt, + IRQF_DISABLED, port->tx_name, port) < 0) + goto error2; + + if (request_irq(port->tm_irq, mn10300_serial_interrupt, + IRQF_DISABLED, port->tm_name, port) < 0) + goto error3; + mn10300_serial_mask_ack(port->tm_irq); + + return 0; + +error3: + free_irq(port->tx_irq, port); +error2: + free_irq(port->rx_irq, port); +error: + kfree(port->rx_buffer); + port->rx_buffer = NULL; + return -EBUSY; +} + +/* + * shutdown the port and release interrupts + */ +static void mn10300_serial_shutdown(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s", port->name); + + /* disable the serial port and its baud rate timer */ + port->tx_break = 0; + *port->_control &= ~(SC01CTR_TXE | SC01CTR_RXE | SC01CTR_BKE); + *port->_tmxmd = 0; + + if (port->rx_buffer) { + void *buf = port->rx_buffer; + port->rx_buffer = NULL; + kfree(buf); + } + + /* disable all intrs */ + free_irq(port->tm_irq, port); + free_irq(port->rx_irq, port); + free_irq(port->tx_irq, port); + + *port->rx_icr = GxICR_LEVEL_1; + *port->tx_icr = GxICR_LEVEL_1; +} + +/* + * this routine is called to set the UART divisor registers to match the + * specified baud rate for a serial port. + */ +static void mn10300_serial_change_speed(struct mn10300_serial_port *port, + struct ktermios *new, + struct ktermios *old) +{ + unsigned long flags; + unsigned long ioclk = port->ioclk; + unsigned cflag; + int baud, bits, xdiv, tmp; + u16 tmxbr, scxctr; + u8 tmxmd, battempt; + u8 div_timer = port->div_timer; + + _enter("%s{%lu}", port->name, ioclk); + + /* byte size and parity */ + cflag = new->c_cflag; + switch (cflag & CSIZE) { + case CS7: scxctr = SC01CTR_CLN_7BIT; bits = 9; break; + case CS8: scxctr = SC01CTR_CLN_8BIT; bits = 10; break; + default: scxctr = SC01CTR_CLN_8BIT; bits = 10; break; + } + + if (cflag & CSTOPB) { + scxctr |= SC01CTR_STB_2BIT; + bits++; + } + + if (cflag & PARENB) { + bits++; + if (cflag & PARODD) + scxctr |= SC01CTR_PB_ODD; +#ifdef CMSPAR + else if (cflag & CMSPAR) + scxctr |= SC01CTR_PB_FIXED0; +#endif + else + scxctr |= SC01CTR_PB_EVEN; + } + + /* Determine divisor based on baud rate */ + battempt = 0; + + if (div_timer == MNSCx_DIV_TIMER_16BIT) + scxctr |= SC0CTR_CK_TM8UFLOW_8; /* ( == SC1CTR_CK_TM9UFLOW_8 + * == SC2CTR_CK_TM10UFLOW) */ + else if (div_timer == MNSCx_DIV_TIMER_8BIT) + scxctr |= SC0CTR_CK_TM2UFLOW_8; + +try_alternative: + baud = uart_get_baud_rate(&port->uart, new, old, 0, + port->ioclk / 8); + + _debug("ALT %d [baud %d]", battempt, baud); + + if (!baud) + baud = 9600; /* B0 transition handled in rs_set_termios */ + xdiv = 1; + if (baud == 134) { + baud = 269; /* 134 is really 134.5 */ + xdiv = 2; + } + + if (baud == 38400 && + (port->uart.flags & UPF_SPD_MASK) == UPF_SPD_CUST + ) { + _debug("CUSTOM %u", port->uart.custom_divisor); + + if (div_timer == MNSCx_DIV_TIMER_16BIT) { + if (port->uart.custom_divisor <= 65535) { + tmxmd = TM8MD_SRC_IOCLK; + tmxbr = port->uart.custom_divisor; + port->uart.uartclk = ioclk; + goto timer_okay; + } + if (port->uart.custom_divisor / 8 <= 65535) { + tmxmd = TM8MD_SRC_IOCLK_8; + tmxbr = port->uart.custom_divisor / 8; + port->uart.custom_divisor = tmxbr * 8; + port->uart.uartclk = ioclk / 8; + goto timer_okay; + } + if (port->uart.custom_divisor / 32 <= 65535) { + tmxmd = TM8MD_SRC_IOCLK_32; + tmxbr = port->uart.custom_divisor / 32; + port->uart.custom_divisor = tmxbr * 32; + port->uart.uartclk = ioclk / 32; + goto timer_okay; + } + + } else if (div_timer == MNSCx_DIV_TIMER_8BIT) { + if (port->uart.custom_divisor <= 255) { + tmxmd = TM2MD_SRC_IOCLK; + tmxbr = port->uart.custom_divisor; + port->uart.uartclk = ioclk; + goto timer_okay; + } + if (port->uart.custom_divisor / 8 <= 255) { + tmxmd = TM2MD_SRC_IOCLK_8; + tmxbr = port->uart.custom_divisor / 8; + port->uart.custom_divisor = tmxbr * 8; + port->uart.uartclk = ioclk / 8; + goto timer_okay; + } + if (port->uart.custom_divisor / 32 <= 255) { + tmxmd = TM2MD_SRC_IOCLK_32; + tmxbr = port->uart.custom_divisor / 32; + port->uart.custom_divisor = tmxbr * 32; + port->uart.uartclk = ioclk / 32; + goto timer_okay; + } + } + } + + switch (div_timer) { + case MNSCx_DIV_TIMER_16BIT: + port->uart.uartclk = ioclk; + tmxmd = TM8MD_SRC_IOCLK; + tmxbr = tmp = (ioclk / (baud * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 65535) + goto timer_okay; + + port->uart.uartclk = ioclk / 8; + tmxmd = TM8MD_SRC_IOCLK_8; + tmxbr = tmp = (ioclk / (baud * 8 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 65535) + goto timer_okay; + + port->uart.uartclk = ioclk / 32; + tmxmd = TM8MD_SRC_IOCLK_32; + tmxbr = tmp = (ioclk / (baud * 32 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 65535) + goto timer_okay; + break; + + case MNSCx_DIV_TIMER_8BIT: + port->uart.uartclk = ioclk; + tmxmd = TM2MD_SRC_IOCLK; + tmxbr = tmp = (ioclk / (baud * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 255) + goto timer_okay; + + port->uart.uartclk = ioclk / 8; + tmxmd = TM2MD_SRC_IOCLK_8; + tmxbr = tmp = (ioclk / (baud * 8 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 255) + goto timer_okay; + + port->uart.uartclk = ioclk / 32; + tmxmd = TM2MD_SRC_IOCLK_32; + tmxbr = tmp = (ioclk / (baud * 32 * xdiv) + 4) / 8 - 1; + if (tmp > 0 && tmp <= 255) + goto timer_okay; + break; + + default: + BUG(); + return; + } + + /* refuse to change to a baud rate we can't support */ + _debug("CAN'T SUPPORT"); + + switch (battempt) { + case 0: + if (old) { + new->c_cflag &= ~CBAUD; + new->c_cflag |= (old->c_cflag & CBAUD); + battempt = 1; + goto try_alternative; + } + + case 1: + /* as a last resort, if the quotient is zero, default to 9600 + * bps */ + new->c_cflag &= ~CBAUD; + new->c_cflag |= B9600; + battempt = 2; + goto try_alternative; + + default: + /* hmmm... can't seem to support 9600 either + * - we could try iterating through the speeds we know about to + * find the lowest + */ + new->c_cflag &= ~CBAUD; + new->c_cflag |= B0; + + if (div_timer == MNSCx_DIV_TIMER_16BIT) + tmxmd = TM8MD_SRC_IOCLK_32; + else if (div_timer == MNSCx_DIV_TIMER_8BIT) + tmxmd = TM2MD_SRC_IOCLK_32; + tmxbr = 1; + + port->uart.uartclk = ioclk / 32; + break; + } +timer_okay: + + _debug("UARTCLK: %u / %hu", port->uart.uartclk, tmxbr); + + /* make the changes */ + spin_lock_irqsave(&port->uart.lock, flags); + + uart_update_timeout(&port->uart, new->c_cflag, baud); + + /* set the timer to produce the required baud rate */ + switch (div_timer) { + case MNSCx_DIV_TIMER_16BIT: + *port->_tmxmd = 0; + *port->_tmxbr = tmxbr; + *port->_tmxmd = TM8MD_INIT_COUNTER; + *port->_tmxmd = tmxmd | TM8MD_COUNT_ENABLE; + break; + + case MNSCx_DIV_TIMER_8BIT: + *port->_tmxmd = 0; + *(volatile u8 *) port->_tmxbr = (u8) tmxbr; + *port->_tmxmd = TM2MD_INIT_COUNTER; + *port->_tmxmd = tmxmd | TM2MD_COUNT_ENABLE; + break; + } + + /* CTS flow control flag and modem status interrupts */ + scxctr &= ~(SC2CTR_TWE | SC2CTR_TWS); + + if (port->type == PORT_MN10300_CTS && cflag & CRTSCTS) { + /* want to interrupt when CTS goes low if CTS is now + * high and vice versa + */ + port->tx_cts = *port->_status; + + if (port->tx_cts & SC2STR_CTS) + scxctr |= SC2CTR_TWE; + else + scxctr |= SC2CTR_TWE | SC2CTR_TWS; + } + + /* set up parity check flag */ + port->uart.read_status_mask = (1 << TTY_NORMAL) | (1 << TTY_OVERRUN); + if (new->c_iflag & INPCK) + port->uart.read_status_mask |= + (1 << TTY_PARITY) | (1 << TTY_FRAME); + if (new->c_iflag & (BRKINT | PARMRK)) + port->uart.read_status_mask |= (1 << TTY_BREAK); + + /* characters to ignore */ + port->uart.ignore_status_mask = 0; + if (new->c_iflag & IGNPAR) + port->uart.ignore_status_mask |= + (1 << TTY_PARITY) | (1 << TTY_FRAME); + if (new->c_iflag & IGNBRK) { + port->uart.ignore_status_mask |= (1 << TTY_BREAK); + /* + * If we're ignoring parity and break indicators, + * ignore overruns to (for real raw support). + */ + if (new->c_iflag & IGNPAR) + port->uart.ignore_status_mask |= (1 << TTY_OVERRUN); + } + + /* Ignore all characters if CREAD is not set */ + if ((new->c_cflag & CREAD) == 0) + port->uart.ignore_status_mask |= (1 << TTY_NORMAL); + + scxctr |= *port->_control & (SC01CTR_TXE | SC01CTR_RXE | SC01CTR_BKE); + *port->_control = scxctr; + + spin_unlock_irqrestore(&port->uart.lock, flags); +} + +/* + * set the terminal I/O parameters + */ +static void mn10300_serial_set_termios(struct uart_port *_port, + struct ktermios *new, + struct ktermios *old) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s,%p,%p", port->name, new, old); + + mn10300_serial_change_speed(port, new, old); + + /* handle turning off CRTSCTS */ + if (!(new->c_cflag & CRTSCTS)) { + u16 ctr = *port->_control; + ctr &= ~SC2CTR_TWE; + *port->_control = ctr; + } +} + +/* + * return description of port type + */ +static const char *mn10300_serial_type(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + if (port->uart.type == PORT_MN10300_CTS) + return "MN10300 SIF_CTS"; + + return "MN10300 SIF"; +} + +/* + * release I/O and memory regions in use by port + */ +static void mn10300_serial_release_port(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s", port->name); + + release_mem_region((unsigned long) port->_iobase, 16); +} + +/* + * request I/O and memory regions for port + */ +static int mn10300_serial_request_port(struct uart_port *_port) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s", port->name); + + request_mem_region((unsigned long) port->_iobase, 16, port->name); + return 0; +} + +/* + * configure the type and reserve the ports + */ +static void mn10300_serial_config_port(struct uart_port *_port, int type) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + + _enter("%s", port->name); + + port->uart.type = PORT_MN10300; + + if (port->options & MNSCx_OPT_CTS) + port->uart.type = PORT_MN10300_CTS; + + mn10300_serial_request_port(_port); +} + +/* + * verify serial parameters are suitable for this port type + */ +static int mn10300_serial_verify_port(struct uart_port *_port, + struct serial_struct *ss) +{ + struct mn10300_serial_port *port = + container_of(_port, struct mn10300_serial_port, uart); + void *mapbase = (void *) (unsigned long) port->uart.mapbase; + + _enter("%s", port->name); + + /* these things may not be changed */ + if (ss->irq != port->uart.irq || + ss->port != port->uart.iobase || + ss->io_type != port->uart.iotype || + ss->iomem_base != mapbase || + ss->iomem_reg_shift != port->uart.regshift || + ss->hub6 != port->uart.hub6 || + ss->xmit_fifo_size != port->uart.fifosize) + return -EINVAL; + + /* type may be changed on a port that supports CTS */ + if (ss->type != port->uart.type) { + if (!(port->options & MNSCx_OPT_CTS)) + return -EINVAL; + + if (ss->type != PORT_MN10300 && + ss->type != PORT_MN10300_CTS) + return -EINVAL; + } + + return 0; +} + +/* + * initialise the MN10300 on-chip UARTs + */ +static int __init mn10300_serial_init(void) +{ + struct mn10300_serial_port *port; + int ret, i; + + printk(KERN_INFO "%s version %s (%s)\n", + serial_name, serial_version, serial_revdate); + +#ifdef CONFIG_MN10300_TTYSM2 + SC2TIM = 8; /* make the baud base of timer 2 IOCLK/8 */ +#endif + + set_intr_stub(EXCEP_IRQ_LEVEL1, mn10300_serial_vdma_interrupt); + + ret = uart_register_driver(&mn10300_serial_driver); + if (!ret) { + for (i = 0 ; i < NR_PORTS ; i++) { + port = mn10300_serial_ports[i]; + if (!port || port->gdbstub) + continue; + + switch (port->clock_src) { + case MNSCx_CLOCK_SRC_IOCLK: + port->ioclk = MN10300_IOCLK; + break; + +#ifdef MN10300_IOBCLK + case MNSCx_CLOCK_SRC_IOBCLK: + port->ioclk = MN10300_IOBCLK; + break; +#endif + default: + BUG(); + } + + ret = uart_add_one_port(&mn10300_serial_driver, + &port->uart); + + if (ret < 0) { + _debug("ERROR %d", -ret); + break; + } + } + + if (ret) + uart_unregister_driver(&mn10300_serial_driver); + } + + return ret; +} + +__initcall(mn10300_serial_init); + + +#ifdef CONFIG_MN10300_TTYSM_CONSOLE + +/* + * print a string to the serial port without disturbing the real user of the + * port too much + * - the console must be locked by the caller + */ +static void mn10300_serial_console_write(struct console *co, + const char *s, unsigned count) +{ + struct mn10300_serial_port *port; + unsigned i; + u16 scxctr, txicr, tmp; + u8 tmxmd; + + port = mn10300_serial_ports[co->index]; + + /* firstly hijack the serial port from the "virtual DMA" controller */ + txicr = *port->tx_icr; + *port->tx_icr = GxICR_LEVEL_1; + tmp = *port->tx_icr; + + /* the transmitter may be disabled */ + scxctr = *port->_control; + if (!(scxctr & SC01CTR_TXE)) { + /* restart the UART clock */ + tmxmd = *port->_tmxmd; + + switch (port->div_timer) { + case MNSCx_DIV_TIMER_16BIT: + *port->_tmxmd = 0; + *port->_tmxmd = TM8MD_INIT_COUNTER; + *port->_tmxmd = tmxmd | TM8MD_COUNT_ENABLE; + break; + + case MNSCx_DIV_TIMER_8BIT: + *port->_tmxmd = 0; + *port->_tmxmd = TM2MD_INIT_COUNTER; + *port->_tmxmd = tmxmd | TM2MD_COUNT_ENABLE; + break; + } + + /* enable the transmitter */ + *port->_control = (scxctr & ~SC01CTR_BKE) | SC01CTR_TXE; + + } else if (scxctr & SC01CTR_BKE) { + /* stop transmitting BREAK */ + *port->_control = (scxctr & ~SC01CTR_BKE); + } + + /* send the chars into the serial port (with LF -> LFCR conversion) */ + for (i = 0; i < count; i++) { + char ch = *s++; + + while (*port->_status & SC01STR_TBF) + continue; + *(u8 *) port->_txb = ch; + + if (ch == 0x0a) { + while (*port->_status & SC01STR_TBF) + continue; + *(u8 *) port->_txb = 0xd; + } + } + + /* can't let the transmitter be turned off if it's actually + * transmitting */ + while (*port->_status & (SC01STR_TXF | SC01STR_TBF)) + continue; + + /* disable the transmitter if we re-enabled it */ + if (!(scxctr & SC01CTR_TXE)) + *port->_control = scxctr; + + *port->tx_icr = txicr; + tmp = *port->tx_icr; +} + +/* + * set up a serial port as a console + * - construct a cflag setting for the first rs_open() + * - initialize the serial port + * - return non-zero if we didn't find a serial port. + */ +static int __init mn10300_serial_console_setup(struct console *co, + char *options) +{ + struct mn10300_serial_port *port; + int i, parity = 'n', baud = 9600, bits = 8, flow = 0; + + for (i = 0 ; i < NR_PORTS ; i++) { + port = mn10300_serial_ports[i]; + if (port && !port->gdbstub && port->uart.line == co->index) + goto found_device; + } + + return -ENODEV; + +found_device: + switch (port->clock_src) { + case MNSCx_CLOCK_SRC_IOCLK: + port->ioclk = MN10300_IOCLK; + break; + +#ifdef MN10300_IOBCLK + case MNSCx_CLOCK_SRC_IOBCLK: + port->ioclk = MN10300_IOBCLK; + break; +#endif + default: + BUG(); + } + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(&port->uart, co, baud, parity, bits, flow); +} + +/* + * register console + */ +static int __init mn10300_serial_console_init(void) +{ + register_console(&mn10300_serial_console); + return 0; +} + +console_initcall(mn10300_serial_console_init); +#endif diff --git a/arch/mn10300/kernel/mn10300-serial.h b/arch/mn10300/kernel/mn10300-serial.h new file mode 100644 index 000000000000..6796499bf789 --- /dev/null +++ b/arch/mn10300/kernel/mn10300-serial.h @@ -0,0 +1,126 @@ +/* MN10300 On-chip serial port driver definitions + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#ifndef _MN10300_SERIAL_H +#define _MN10300_SERIAL_H + +#ifndef __ASSEMBLY__ +#include <linux/serial_core.h> +#include <linux/termios.h> +#endif + +#include <asm/page.h> +#include <asm/serial-regs.h> + +#define NR_PORTS 3 /* should be set 3 or 9 or 16 */ + +#define MNSC_BUFFER_SIZE +(PAGE_SIZE / 2) + +/* intr_flags bits */ +#define MNSCx_RX_AVAIL 0x01 +#define MNSCx_RX_OVERF 0x02 +#define MNSCx_TX_SPACE 0x04 +#define MNSCx_TX_EMPTY 0x08 + +#ifndef __ASSEMBLY__ + +struct mn10300_serial_port { + char *rx_buffer; /* reception buffer base */ + unsigned rx_inp; /* pointer to rx input offset */ + unsigned rx_outp; /* pointer to rx output offset */ + u8 tx_xchar; /* high-priority XON/XOFF buffer */ + u8 tx_break; /* transmit break request */ + u8 intr_flags; /* interrupt flags */ + volatile u16 *rx_icr; /* Rx interrupt control register */ + volatile u16 *tx_icr; /* Tx interrupt control register */ + int rx_irq; /* reception IRQ */ + int tx_irq; /* transmission IRQ */ + int tm_irq; /* timer IRQ */ + + const char *name; /* name of serial port */ + const char *rx_name; /* Rx interrupt handler name of serial port */ + const char *tx_name; /* Tx interrupt handler name of serial port */ + const char *tm_name; /* Timer interrupt handler name */ + unsigned short type; /* type of serial port */ + unsigned char isconsole; /* T if it's a console */ + volatile void *_iobase; /* pointer to base of I/O control regs */ + volatile u16 *_control; /* control register pointer */ + volatile u8 *_status; /* status register pointer */ + volatile u8 *_intr; /* interrupt register pointer */ + volatile void *_rxb; /* receive buffer register pointer */ + volatile void *_txb; /* transmit buffer register pointer */ + volatile u16 *_tmicr; /* timer interrupt control register */ + volatile u8 *_tmxmd; /* baud rate timer mode register */ + volatile u16 *_tmxbr; /* baud rate timer base register */ + + /* this must come down here so that assembly can use BSET to access the + * above fields */ + struct uart_port uart; + + unsigned short rx_brk; /* current break reception status */ + u16 tx_cts; /* current CTS status */ + int gdbstub; /* preemptively stolen by GDB stub */ + + u8 clock_src; /* clock source */ +#define MNSCx_CLOCK_SRC_IOCLK 0 +#define MNSCx_CLOCK_SRC_IOBCLK 1 + + u8 div_timer; /* timer used as divisor */ +#define MNSCx_DIV_TIMER_16BIT 0 +#define MNSCx_DIV_TIMER_8BIT 1 + + u16 options; /* options */ +#define MNSCx_OPT_CTS 0x0001 + + unsigned long ioclk; /* base clock rate */ +}; + +#ifdef CONFIG_MN10300_TTYSM0 +extern struct mn10300_serial_port mn10300_serial_port_sif0; +#endif + +#ifdef CONFIG_MN10300_TTYSM1 +extern struct mn10300_serial_port mn10300_serial_port_sif1; +#endif + +#ifdef CONFIG_MN10300_TTYSM2 +extern struct mn10300_serial_port mn10300_serial_port_sif2; +#endif + +extern struct mn10300_serial_port *mn10300_serial_ports[]; + +struct mn10300_serial_int { + struct mn10300_serial_port *port; + asmlinkage void (*vdma)(void); +}; + +extern struct mn10300_serial_int mn10300_serial_int_tbl[]; + +extern asmlinkage void mn10300_serial_vdma_interrupt(void); +extern asmlinkage void mn10300_serial_vdma_rx_handler(void); +extern asmlinkage void mn10300_serial_vdma_tx_handler(void); + +#endif /* __ASSEMBLY__ */ + +#if defined(CONFIG_GDBSTUB_ON_TTYSM0) +#define SCgSTR SC0STR +#define SCgRXB SC0RXB +#define SCgRXIRQ SC0RXIRQ +#elif defined(CONFIG_GDBSTUB_ON_TTYSM1) +#define SCgSTR SC1STR +#define SCgRXB SC1RXB +#define SCgRXIRQ SC1RXIRQ +#elif defined(CONFIG_GDBSTUB_ON_TTYSM2) +#define SCgSTR SC2STR +#define SCgRXB SC2RXB +#define SCgRXIRQ SC2RXIRQ +#endif + +#endif /* _MN10300_SERIAL_H */ diff --git a/arch/mn10300/kernel/mn10300-watchdog-low.S b/arch/mn10300/kernel/mn10300-watchdog-low.S new file mode 100644 index 000000000000..996244745cca --- /dev/null +++ b/arch/mn10300/kernel/mn10300-watchdog-low.S @@ -0,0 +1,59 @@ +############################################################################### +# +# MN10300 Watchdog interrupt handler +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/intctl-regs.h> +#include <asm/timer-regs.h> +#include <asm/frame.inc> + + .text + +############################################################################### +# +# Watchdog handler entry point +# - special non-maskable interrupt +# +############################################################################### + .globl watchdog_handler + .type watchdog_handler,@function +watchdog_handler: + add -4,sp + SAVE_ALL + + mov 0xffffffff,d0 + mov d0,(REG_ORIG_D0,fp) + + mov fp,d0 + lsr 2,d1 + call watchdog_interrupt[],0 # watchdog_interrupt(regs,irq) + + jmp ret_from_intr + + .size watchdog_handler,.-watchdog_handler + +############################################################################### +# +# Watchdog touch entry point +# - kept to absolute minimum (unfortunately, it's prototyped in linux/nmi.h so +# we can't inline it) +# +############################################################################### + .globl touch_nmi_watchdog + .type touch_nmi_watchdog,@function +touch_nmi_watchdog: + clr d0 + mov d0,(watchdog_alert_counter) + ret [],0 + + .size touch_nmi_watchdog,.-touch_nmi_watchdog diff --git a/arch/mn10300/kernel/mn10300-watchdog.c b/arch/mn10300/kernel/mn10300-watchdog.c new file mode 100644 index 000000000000..10811e981d20 --- /dev/null +++ b/arch/mn10300/kernel/mn10300-watchdog.c @@ -0,0 +1,183 @@ +/* MN10300 Watchdog timer + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * - Derived from arch/i386/kernel/nmi.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/nmi.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/atomic.h> +#include <asm/intctl-regs.h> +#include <asm/rtc-regs.h> +#include <asm/div64.h> +#include <asm/smp.h> +#include <asm/gdb-stub.h> +#include <asm/proc/clock.h> + +static DEFINE_SPINLOCK(watchdog_print_lock); +static unsigned int watchdog; +static unsigned int watchdog_hz = 1; +unsigned int watchdog_alert_counter; + +EXPORT_SYMBOL(touch_nmi_watchdog); + +/* + * the best way to detect whether a CPU has a 'hard lockup' problem + * is to check its timer makes IRQ counts. If they are not + * changing then that CPU has some problem. + * + * as these watchdog NMI IRQs are generated on every CPU, we only + * have to check the current processor. + * + * since NMIs dont listen to _any_ locks, we have to be extremely + * careful not to rely on unsafe variables. The printk might lock + * up though, so we have to break up any console locks first ... + * [when there will be more tty-related locks, break them up + * here too!] + */ +static unsigned int last_irq_sums[NR_CPUS]; + +int __init check_watchdog(void) +{ + irq_cpustat_t tmp[1]; + + printk(KERN_INFO "Testing Watchdog... "); + + memcpy(tmp, irq_stat, sizeof(tmp)); + local_irq_enable(); + mdelay((10 * 1000) / watchdog_hz); /* wait 10 ticks */ + local_irq_disable(); + + if (nmi_count(0) - tmp[0].__nmi_count <= 5) { + printk(KERN_WARNING "CPU#%d: Watchdog appears to be stuck!\n", + 0); + return -1; + } + + printk(KERN_INFO "OK.\n"); + + /* now that we know it works we can reduce NMI frequency to + * something more reasonable; makes a difference in some configs + */ + watchdog_hz = 1; + + return 0; +} + +static int __init setup_watchdog(char *str) +{ + unsigned tmp; + int opt; + u8 ctr; + + get_option(&str, &opt); + if (opt != 1) + return 0; + + watchdog = opt; + if (watchdog) { + set_intr_stub(EXCEP_WDT, watchdog_handler); + ctr = WDCTR_WDCK_65536th; + WDCTR = WDCTR_WDRST | ctr; + WDCTR = ctr; + tmp = WDCTR; + + tmp = __muldiv64u(1 << (16 + ctr * 2), 1000000, MN10300_WDCLK); + tmp = 1000000000 / tmp; + watchdog_hz = (tmp + 500) / 1000; + } + + return 1; +} + +__setup("watchdog=", setup_watchdog); + +void __init watchdog_go(void) +{ + u8 wdt; + + if (watchdog) { + printk(KERN_INFO "Watchdog: running at %uHz\n", watchdog_hz); + wdt = WDCTR & ~WDCTR_WDCNE; + WDCTR = wdt | WDCTR_WDRST; + wdt = WDCTR; + WDCTR = wdt | WDCTR_WDCNE; + wdt = WDCTR; + + check_watchdog(); + } +} + +asmlinkage +void watchdog_interrupt(struct pt_regs *regs, enum exception_code excep) +{ + + /* + * Since current-> is always on the stack, and we always switch + * the stack NMI-atomically, it's safe to use smp_processor_id(). + */ + int sum, cpu = smp_processor_id(); + u8 wdt, tmp; + + wdt = WDCTR & ~WDCTR_WDCNE; + WDCTR = wdt; + tmp = WDCTR; + NMICR = NMICR_WDIF; + + nmi_count(cpu)++; + kstat_this_cpu.irqs[NMIIRQ]++; + sum = irq_stat[cpu].__irq_count; + + if (last_irq_sums[cpu] == sum) { + /* + * Ayiee, looks like this CPU is stuck ... + * wait a few IRQs (5 seconds) before doing the oops ... + */ + watchdog_alert_counter++; + if (watchdog_alert_counter == 5 * watchdog_hz) { + spin_lock(&watchdog_print_lock); + /* + * We are in trouble anyway, lets at least try + * to get a message out. + */ + bust_spinlocks(1); + printk(KERN_ERR + "NMI Watchdog detected LOCKUP on CPU%d," + " pc %08lx, registers:\n", + cpu, regs->pc); + show_registers(regs); + printk("console shuts up ...\n"); + console_silent(); + spin_unlock(&watchdog_print_lock); + bust_spinlocks(0); +#ifdef CONFIG_GDBSTUB + if (gdbstub_busy) + gdbstub_exception(regs, excep); + else + gdbstub_intercept(regs, excep); +#endif + do_exit(SIGSEGV); + } + } else { + last_irq_sums[cpu] = sum; + watchdog_alert_counter = 0; + } + + WDCTR = wdt | WDCTR_WDRST; + tmp = WDCTR; + WDCTR = wdt | WDCTR_WDCNE; + tmp = WDCTR; +} diff --git a/arch/mn10300/kernel/mn10300_ksyms.c b/arch/mn10300/kernel/mn10300_ksyms.c new file mode 100644 index 000000000000..6d19628634e3 --- /dev/null +++ b/arch/mn10300/kernel/mn10300_ksyms.c @@ -0,0 +1,37 @@ +/* MN10300 Miscellaneous and library kernel exports + * + * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/module.h> +#include <asm/uaccess.h> + + +EXPORT_SYMBOL(change_bit); +EXPORT_SYMBOL(test_and_change_bit); + +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memmove); +EXPORT_SYMBOL(memset); + +EXPORT_SYMBOL(strncpy_from_user); +EXPORT_SYMBOL(__strncpy_from_user); +EXPORT_SYMBOL(clear_user); +EXPORT_SYMBOL(__clear_user); +EXPORT_SYMBOL(__generic_copy_from_user); +EXPORT_SYMBOL(__generic_copy_to_user); +EXPORT_SYMBOL(strnlen_user); + +extern u64 __ashrdi3(u64, unsigned); +extern u64 __ashldi3(u64, unsigned); +extern u64 __lshrdi3(u64, unsigned); +extern s64 __negdi2(s64); +EXPORT_SYMBOL(__ashrdi3); +EXPORT_SYMBOL(__ashldi3); +EXPORT_SYMBOL(__lshrdi3); +EXPORT_SYMBOL(__negdi2); diff --git a/arch/mn10300/kernel/module.c b/arch/mn10300/kernel/module.c new file mode 100644 index 000000000000..0e4d2f6fa6e8 --- /dev/null +++ b/arch/mn10300/kernel/module.c @@ -0,0 +1,206 @@ +/* MN10300 Kernel module helper routines + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by Mark Salter (msalter@redhat.com) + * - Derived from arch/i386/kernel/module.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public Licence as published by + * the Free Software Foundation; either version 2 of the Licence, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public Licence for more details. + * + * You should have received a copy of the GNU General Public Licence + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/moduleloader.h> +#include <linux/elf.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(fmt, ...) +#endif + +/* + * allocate storage for a module + */ +void *module_alloc(unsigned long size) +{ + if (size == 0) + return NULL; + return vmalloc_exec(size); +} + +/* + * free memory returned from module_alloc() + */ +void module_free(struct module *mod, void *module_region) +{ + vfree(module_region); + /* FIXME: If module_region == mod->init_region, trim exception + * table entries. */ +} + +/* + * allow the arch to fix up the section table + * - we don't need anything special + */ +int module_frob_arch_sections(Elf_Ehdr *hdr, + Elf_Shdr *sechdrs, + char *secstrings, + struct module *mod) +{ + return 0; +} + +static uint32_t reloc_get16(uint8_t *p) +{ + return p[0] | (p[1] << 8); +} + +static uint32_t reloc_get24(uint8_t *p) +{ + return reloc_get16(p) | (p[2] << 16); +} + +static uint32_t reloc_get32(uint8_t *p) +{ + return reloc_get16(p) | (reloc_get16(p+2) << 16); +} + +static void reloc_put16(uint8_t *p, uint32_t val) +{ + p[0] = val & 0xff; + p[1] = (val >> 8) & 0xff; +} + +static void reloc_put24(uint8_t *p, uint32_t val) +{ + reloc_put16(p, val); + p[2] = (val >> 16) & 0xff; +} + +static void reloc_put32(uint8_t *p, uint32_t val) +{ + reloc_put16(p, val); + reloc_put16(p+2, val >> 16); +} + +/* + * apply a REL relocation + */ +int apply_relocate(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + printk(KERN_ERR "module %s: RELOCATION unsupported\n", + me->name); + return -ENOEXEC; +} + +/* + * apply a RELA relocation + */ +int apply_relocate_add(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + unsigned int i; + Elf32_Rela *rel = (void *)sechdrs[relsec].sh_addr; + Elf32_Sym *sym; + Elf32_Addr relocation; + uint8_t *location; + uint32_t value; + + DEBUGP("Applying relocate section %u to %u\n", + relsec, sechdrs[relsec].sh_info); + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + /* this is where to make the change */ + location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + + rel[i].r_offset; + + /* this is the symbol the relocation is referring to (note that + * all undefined symbols have been resolved by the caller) */ + sym = (Elf32_Sym *)sechdrs[symindex].sh_addr + + ELF32_R_SYM(rel[i].r_info); + + /* this is the adjustment to be made */ + relocation = sym->st_value + rel[i].r_addend; + + switch (ELF32_R_TYPE(rel[i].r_info)) { + /* for the first four relocation types, we add the + * adjustment into the value at the location given */ + case R_MN10300_32: + value = reloc_get32(location); + value += relocation; + reloc_put32(location, value); + break; + case R_MN10300_24: + value = reloc_get24(location); + value += relocation; + reloc_put24(location, value); + break; + case R_MN10300_16: + value = reloc_get16(location); + value += relocation; + reloc_put16(location, value); + break; + case R_MN10300_8: + *location += relocation; + break; + + /* for the next three relocation types, we write the + * adjustment with the address subtracted over the + * value at the location given */ + case R_MN10300_PCREL32: + value = relocation - (uint32_t) location; + reloc_put32(location, value); + break; + case R_MN10300_PCREL16: + value = relocation - (uint32_t) location; + reloc_put16(location, value); + break; + case R_MN10300_PCREL8: + *location = relocation - (uint32_t) location; + break; + + default: + printk(KERN_ERR "module %s: Unknown relocation: %u\n", + me->name, ELF32_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + } + return 0; +} + +/* + * finish loading the module + */ +int module_finalize(const Elf_Ehdr *hdr, + const Elf_Shdr *sechdrs, + struct module *me) +{ + return 0; +} + +/* + * finish clearing the module + */ +void module_arch_cleanup(struct module *mod) +{ +} diff --git a/arch/mn10300/kernel/process.c b/arch/mn10300/kernel/process.c new file mode 100644 index 000000000000..3b0d579fc15d --- /dev/null +++ b/arch/mn10300/kernel/process.c @@ -0,0 +1,297 @@ +/* MN10300 Process handling code + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.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/interrupt.h> +#include <linux/delay.h> +#include <linux/reboot.h> +#include <linux/percpu.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/processor.h> +#include <asm/mmu_context.h> +#include <asm/fpu.h> +#include <asm/reset-regs.h> +#include <asm/gdb-stub.h> +#include "internal.h" + +/* + * power management idle function, if any.. + */ +void (*pm_idle)(void); +EXPORT_SYMBOL(pm_idle); + +/* + * return saved PC of a blocked thread. + */ +unsigned long thread_saved_pc(struct task_struct *tsk) +{ + return ((unsigned long *) tsk->thread.sp)[3]; +} + +/* + * power off function, if any + */ +void (*pm_power_off)(void); +EXPORT_SYMBOL(pm_power_off); + +/* + * we use this if we don't have any better idle routine + */ +static void default_idle(void) +{ + local_irq_disable(); + if (!need_resched()) + safe_halt(); + else + local_irq_enable(); +} + +/* + * the idle thread + * - there's no useful work to be done, so just try to conserve power and have + * a low exit latency (ie sit in a loop waiting for somebody to say that + * they'd like to reschedule) + */ +void cpu_idle(void) +{ + int cpu = smp_processor_id(); + + /* endless idle loop with no priority at all */ + for (;;) { + while (!need_resched()) { + void (*idle)(void); + + smp_rmb(); + idle = pm_idle; + if (!idle) + idle = default_idle; + + irq_stat[cpu].idle_timestamp = jiffies; + idle(); + } + + preempt_enable_no_resched(); + schedule(); + preempt_disable(); + } +} + +void release_segments(struct mm_struct *mm) +{ +} + +void machine_restart(char *cmd) +{ +#ifdef CONFIG_GDBSTUB + gdbstub_exit(0); +#endif + +#ifdef mn10300_unit_hard_reset + mn10300_unit_hard_reset(); +#else + mn10300_proc_hard_reset(); +#endif +} + +void machine_halt(void) +{ +#ifdef CONFIG_GDBSTUB + gdbstub_exit(0); +#endif +} + +void machine_power_off(void) +{ +#ifdef CONFIG_GDBSTUB + gdbstub_exit(0); +#endif +} + +void show_regs(struct pt_regs *regs) +{ +} + +/* + * create a kernel thread + */ +int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + + regs.a2 = (unsigned long) fn; + regs.d2 = (unsigned long) arg; + regs.pc = (unsigned long) kernel_thread_helper; + local_save_flags(regs.epsw); + regs.epsw |= EPSW_IE | EPSW_IM_7; + + /* Ok, create the new process.. */ + return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, + NULL, NULL); +} + +/* + * free current thread data structures etc.. + */ +void exit_thread(void) +{ + exit_fpu(); +} + +void flush_thread(void) +{ + flush_fpu(); +} + +void release_thread(struct task_struct *dead_task) +{ +} + +/* + * we do not have to muck with descriptors here, that is + * done in switch_mm() as needed. + */ +void copy_segments(struct task_struct *p, struct mm_struct *new_mm) +{ +} + +/* + * this gets called before we allocate a new thread and copy the current task + * into it so that we can store lazy state into memory + */ +void prepare_to_copy(struct task_struct *tsk) +{ + unlazy_fpu(tsk); +} + +/* + * set up the kernel stack for a new thread and copy arch-specific thread + * control information + */ +int copy_thread(int nr, unsigned long clone_flags, + unsigned long c_usp, unsigned long ustk_size, + struct task_struct *p, struct pt_regs *kregs) +{ + struct pt_regs *c_uregs, *c_kregs, *uregs; + unsigned long c_ksp; + + uregs = current->thread.uregs; + + c_ksp = (unsigned long) task_stack_page(p) + THREAD_SIZE; + + /* allocate the userspace exception frame and set it up */ + c_ksp -= sizeof(struct pt_regs); + c_uregs = (struct pt_regs *) c_ksp; + + p->thread.uregs = c_uregs; + *c_uregs = *uregs; + c_uregs->sp = c_usp; + c_uregs->epsw &= ~EPSW_FE; /* my FPU */ + + c_ksp -= 12; /* allocate function call ABI slack */ + + /* the new TLS pointer is passed in as arg #5 to sys_clone() */ + if (clone_flags & CLONE_SETTLS) + c_uregs->e2 = __frame->d3; + + /* set up the return kernel frame if called from kernel_thread() */ + c_kregs = c_uregs; + if (kregs != uregs) { + c_ksp -= sizeof(struct pt_regs); + c_kregs = (struct pt_regs *) c_ksp; + *c_kregs = *kregs; + c_kregs->sp = c_usp; + c_kregs->next = c_uregs; +#ifdef CONFIG_MN10300_CURRENT_IN_E2 + c_kregs->e2 = (unsigned long) p; /* current */ +#endif + + c_ksp -= 12; /* allocate function call ABI slack */ + } + + /* set up things up so the scheduler can start the new task */ + p->thread.__frame = c_kregs; + p->thread.a3 = (unsigned long) c_kregs; + p->thread.sp = c_ksp; + p->thread.pc = (unsigned long) ret_from_fork; + p->thread.wchan = (unsigned long) ret_from_fork; + p->thread.usp = c_usp; + + return 0; +} + +/* + * clone a process + * - tlsptr is retrieved by copy_thread() from __frame->d3 + */ +asmlinkage long sys_clone(unsigned long clone_flags, unsigned long newsp, + int __user *parent_tidptr, int __user *child_tidptr, + int __user *tlsptr) +{ + return do_fork(clone_flags, newsp ?: __frame->sp, __frame, 0, + parent_tidptr, child_tidptr); +} + +asmlinkage long sys_fork(void) +{ + return do_fork(SIGCHLD, __frame->sp, __frame, 0, NULL, NULL); +} + +asmlinkage long sys_vfork(void) +{ + return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, __frame->sp, __frame, + 0, NULL, NULL); +} + +asmlinkage long sys_execve(char __user *name, + char __user * __user *argv, + char __user * __user *envp) +{ + char *filename; + int error; + + lock_kernel(); + + filename = getname(name); + error = PTR_ERR(filename); + if (!IS_ERR(filename)) { + error = do_execve(filename, argv, envp, __frame); + if (error == 0) + current->ptrace &= ~PT_DTRACE; + + putname(filename); + } + + unlock_kernel(); + return error; +} + +unsigned long get_wchan(struct task_struct *p) +{ + return p->thread.wchan; +} diff --git a/arch/mn10300/kernel/profile-low.S b/arch/mn10300/kernel/profile-low.S new file mode 100644 index 000000000000..94ffac12d02d --- /dev/null +++ b/arch/mn10300/kernel/profile-low.S @@ -0,0 +1,72 @@ +############################################################################### +# +# Fast profiling interrupt handler +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/segment.h> +#include <asm/smp.h> +#include <asm/intctl-regs.h> +#include <asm/timer-regs.h> + +#define pi break + + .balign 4 +counter: + .long -1 + +############################################################################### +# +# Profiling interrupt entry point +# - intended to run at interrupt priority 1 +# +############################################################################### +ENTRY(profile_handler) + movm [d2,d3,a2],(sp) + + # ignore userspace + mov (12,sp),d2 + and EPSW_nSL,d2 + bne out + + # do nothing if there's no buffer + mov (prof_buffer),a2 + and a2,a2 + beq out + or 0x20000000,a2 + + # calculate relative position in text segment + mov (16,sp),d2 + sub _stext,d2 + mov (prof_shift),d3 + lsr d3,d2 + mov (prof_len),d3 + cmp d3,d2 + bcc outside_text + + # increment the appropriate profile bucket +do_inc: + asl2 d2 + mov (a2,d2),d3 + inc d3 + mov d3,(a2,d2) +out: + mov GxICR_DETECT,d2 + movbu d2,(TM11ICR) # ACK the interrupt + movbu (TM11ICR),d2 + movm (sp),[d2,d3,a2] + rti + +outside_text: + sub 1,d3 + mov d3,d2 + bra do_inc diff --git a/arch/mn10300/kernel/profile.c b/arch/mn10300/kernel/profile.c new file mode 100644 index 000000000000..20d7d0306b16 --- /dev/null +++ b/arch/mn10300/kernel/profile.c @@ -0,0 +1,51 @@ +/* MN10300 Profiling setup + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +/* + * initialise the profiling if enabled + * - using with gdbstub will give anomalous results + * - can't be used with gdbstub if running at IRQ priority 0 + */ +static __init int profile_init(void) +{ + u16 tmp; + + if (!prof_buffer) + return 0; + + /* use timer 11 to drive the profiling interrupts */ + set_intr_stub(EXCEP_IRQ_LEVEL0, profile_handler); + + /* set IRQ priority at which to run */ + set_intr_level(TM11IRQ, GxICR_LEVEL_0); + + /* set up timer 11 + * - source: (IOCLK 33MHz)*2 = 66MHz + * - frequency: (33330000*2) / 8 / 20625 = 202Hz + */ + TM11BR = 20625 - 1; + TM11MD = TM8MD_SRC_IOCLK_8; + TM11MD |= TM8MD_INIT_COUNTER; + TM11MD &= ~TM8MD_INIT_COUNTER; + TM11MD |= TM8MD_COUNT_ENABLE; + + TM11ICR |= GxICR_ENABLE; + tmp = TM11ICR; + + printk(KERN_INFO "Profiling initiated on timer 11, priority 0, %uHz\n", + mn10300_ioclk / 8 / (TM11BR + 1)); + printk(KERN_INFO "Profile histogram stored %p-%p\n", + prof_buffer, (u8 *)(prof_buffer + prof_len) - 1); + + return 0; +} + +__initcall(profile_init); diff --git a/arch/mn10300/kernel/ptrace.c b/arch/mn10300/kernel/ptrace.c new file mode 100644 index 000000000000..d6d6cdc75c52 --- /dev/null +++ b/arch/mn10300/kernel/ptrace.c @@ -0,0 +1,379 @@ +/* MN10300 Process tracing + * + * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Modified by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/user.h> +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/processor.h> +#include <asm/cacheflush.h> +#include <asm/fpu.h> +#include <asm/asm-offsets.h> + +/* + * translate ptrace register IDs into struct pt_regs offsets + */ +static const u8 ptrace_regid_to_frame[] = { + [PT_A3 << 2] = REG_A3, + [PT_A2 << 2] = REG_A2, + [PT_D3 << 2] = REG_D3, + [PT_D2 << 2] = REG_D2, + [PT_MCVF << 2] = REG_MCVF, + [PT_MCRL << 2] = REG_MCRL, + [PT_MCRH << 2] = REG_MCRH, + [PT_MDRQ << 2] = REG_MDRQ, + [PT_E1 << 2] = REG_E1, + [PT_E0 << 2] = REG_E0, + [PT_E7 << 2] = REG_E7, + [PT_E6 << 2] = REG_E6, + [PT_E5 << 2] = REG_E5, + [PT_E4 << 2] = REG_E4, + [PT_E3 << 2] = REG_E3, + [PT_E2 << 2] = REG_E2, + [PT_SP << 2] = REG_SP, + [PT_LAR << 2] = REG_LAR, + [PT_LIR << 2] = REG_LIR, + [PT_MDR << 2] = REG_MDR, + [PT_A1 << 2] = REG_A1, + [PT_A0 << 2] = REG_A0, + [PT_D1 << 2] = REG_D1, + [PT_D0 << 2] = REG_D0, + [PT_ORIG_D0 << 2] = REG_ORIG_D0, + [PT_EPSW << 2] = REG_EPSW, + [PT_PC << 2] = REG_PC, +}; + +static inline int get_stack_long(struct task_struct *task, int offset) +{ + return *(unsigned long *) + ((unsigned long) task->thread.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 TSS. + * this routine assumes that all the privileged stacks are in our + * data space. + */ +static inline +int put_stack_long(struct task_struct *task, int offset, unsigned long data) +{ + unsigned long stack; + + stack = (unsigned long) task->thread.uregs + offset; + *(unsigned long *) stack = data; + return 0; +} + +static inline unsigned long get_fpregs(struct fpu_state_struct *buf, + struct task_struct *tsk) +{ + return __copy_to_user(buf, &tsk->thread.fpu_state, + sizeof(struct fpu_state_struct)); +} + +static inline unsigned long set_fpregs(struct task_struct *tsk, + struct fpu_state_struct *buf) +{ + return __copy_from_user(&tsk->thread.fpu_state, buf, + sizeof(struct fpu_state_struct)); +} + +static inline void fpsave_init(struct task_struct *task) +{ + memset(&task->thread.fpu_state, 0, sizeof(struct fpu_state_struct)); +} + +/* + * make sure the single step bit is not set + */ +void ptrace_disable(struct task_struct *child) +{ +#ifndef CONFIG_MN10300_USING_JTAG + struct user *dummy = NULL; + long tmp; + + tmp = get_stack_long(child, (unsigned long) &dummy->regs.epsw); + tmp &= ~EPSW_T; + put_stack_long(child, (unsigned long) &dummy->regs.epsw, tmp); +#endif +} + +/* + * set the single step bit + */ +void ptrace_enable(struct task_struct *child) +{ +#ifndef CONFIG_MN10300_USING_JTAG + struct user *dummy = NULL; + long tmp; + + tmp = get_stack_long(child, (unsigned long) &dummy->regs.epsw); + tmp |= EPSW_T; + put_stack_long(child, (unsigned long) &dummy->regs.epsw, tmp); +#endif +} + +/* + * handle the arch-specific side of process tracing + */ +long arch_ptrace(struct task_struct *child, long request, long addr, long data) +{ + struct fpu_state_struct fpu_state; + int i, ret; + + switch (request) { + /* read the word at location addr. */ + case PTRACE_PEEKTEXT: { + unsigned long tmp; + int copied; + + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + ret = -EIO; + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp, (unsigned long *) data); + break; + } + + /* read the word at location addr. */ + case PTRACE_PEEKDATA: { + unsigned long tmp; + int copied; + + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + ret = -EIO; + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp, (unsigned long *) data); + break; + } + + /* read the word at location addr in the USER area. */ + case PTRACE_PEEKUSR: { + unsigned long tmp; + + ret = -EIO; + if ((addr & 3) || addr < 0 || + addr > sizeof(struct user) - 3) + break; + + tmp = 0; /* Default return condition */ + if (addr < NR_PTREGS << 2) + tmp = get_stack_long(child, + ptrace_regid_to_frame[addr]); + ret = put_user(tmp, (unsigned long *) data); + break; + } + + /* write the word at location addr. */ + case PTRACE_POKETEXT: + case PTRACE_POKEDATA: + if (access_process_vm(child, addr, &data, sizeof(data), 1) == + sizeof(data)) + ret = 0; + else + ret = -EIO; + break; + + /* write the word at location addr in the USER area */ + case PTRACE_POKEUSR: + ret = -EIO; + if ((addr & 3) || addr < 0 || + addr > sizeof(struct user) - 3) + break; + + ret = 0; + if (addr < NR_PTREGS << 2) + ret = put_stack_long(child, ptrace_regid_to_frame[addr], + data); + break; + + /* continue and stop at next (return from) syscall */ + case PTRACE_SYSCALL: + /* restart after signal. */ + 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; + ptrace_disable(child); + wake_up_process(child); + ret = 0; + break; + + /* + * make the child exit + * - the 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: + ret = 0; + if (child->exit_state == EXIT_ZOMBIE) /* already dead */ + break; + child->exit_code = SIGKILL; + clear_tsk_thread_flag(child, TIF_SINGLESTEP); + ptrace_disable(child); + wake_up_process(child); + break; + + case PTRACE_SINGLESTEP: /* set the trap flag. */ +#ifndef CONFIG_MN10300_USING_JTAG + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + ptrace_enable(child); + child->exit_code = data; + wake_up_process(child); + ret = 0; +#else + ret = -EINVAL; +#endif + break; + + case PTRACE_DETACH: /* detach a process that was attached. */ + ret = ptrace_detach(child, data); + break; + + /* Get all gp regs from the child. */ + case PTRACE_GETREGS: { + unsigned long tmp; + + if (!access_ok(VERIFY_WRITE, (unsigned *) data, NR_PTREGS << 2)) { + ret = -EIO; + break; + } + + for (i = 0; i < NR_PTREGS << 2; i += 4) { + tmp = get_stack_long(child, ptrace_regid_to_frame[i]); + __put_user(tmp, (unsigned long *) data); + data += sizeof(tmp); + } + ret = 0; + break; + } + + case PTRACE_SETREGS: { /* Set all gp regs in the child. */ + unsigned long tmp; + + if (!access_ok(VERIFY_READ, (unsigned long *)data, + sizeof(struct pt_regs))) { + ret = -EIO; + break; + } + + for (i = 0; i < NR_PTREGS << 2; i += 4) { + __get_user(tmp, (unsigned long *) data); + put_stack_long(child, ptrace_regid_to_frame[i], tmp); + data += sizeof(tmp); + } + ret = 0; + break; + } + + case PTRACE_GETFPREGS: { /* Get the child FPU state. */ + if (is_using_fpu(child)) { + unlazy_fpu(child); + fpu_state = child->thread.fpu_state; + } else { + memset(&fpu_state, 0, sizeof(fpu_state)); + } + + ret = -EIO; + if (copy_to_user((void *) data, &fpu_state, + sizeof(fpu_state)) == 0) + ret = 0; + break; + } + + case PTRACE_SETFPREGS: { /* Set the child FPU state. */ + ret = -EFAULT; + if (copy_from_user(&fpu_state, (const void *) data, + sizeof(fpu_state)) == 0) { + fpu_kill_state(child); + child->thread.fpu_state = fpu_state; + set_using_fpu(child); + ret = 0; + } + break; + } + + case PTRACE_SETOPTIONS: { + if (data & PTRACE_O_TRACESYSGOOD) + child->ptrace |= PT_TRACESYSGOOD; + else + child->ptrace &= ~PT_TRACESYSGOOD; + ret = 0; + break; + } + + default: + ret = -EIO; + break; + } + + return ret; +} + +/* + * notification of system call entry/exit + * - triggered by current->work.syscall_trace + */ +asmlinkage void do_syscall_trace(struct pt_regs *regs, int entryexit) +{ +#if 0 + /* just in case... */ + printk(KERN_DEBUG "[%d] syscall_%lu(%lx,%lx,%lx,%lx) = %lx\n", + current->pid, + regs->orig_d0, + regs->a0, + regs->d1, + regs->a3, + regs->a2, + regs->d0); + return; +#endif + + if (!test_thread_flag(TIF_SYSCALL_TRACE) && + !test_thread_flag(TIF_SINGLESTEP)) + return; + if (!(current->ptrace & PT_PTRACED)) + return; + + /* the 0x80 provides a way for the tracing parent to distinguish + between a syscall stop and SIGTRAP delivery */ + ptrace_notify(SIGTRAP | + ((current->ptrace & PT_TRACESYSGOOD) && + !test_thread_flag(TIF_SINGLESTEP) ? 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; + } +} diff --git a/arch/mn10300/kernel/rtc.c b/arch/mn10300/kernel/rtc.c new file mode 100644 index 000000000000..042f792d8430 --- /dev/null +++ b/arch/mn10300/kernel/rtc.c @@ -0,0 +1,173 @@ +/* MN10300 RTC management + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/mc146818rtc.h> +#include <linux/bcd.h> +#include <linux/timex.h> +#include <asm/rtc-regs.h> +#include <asm/rtc.h> + +DEFINE_SPINLOCK(rtc_lock); +EXPORT_SYMBOL(rtc_lock); + +/* last time the RTC got updated */ +static long last_rtc_update; + +/* time for RTC to update itself in ioclks */ +static unsigned long mn10300_rtc_update_period; + +/* + * read the current RTC time + */ +unsigned long __init get_initial_rtc_time(void) +{ + struct rtc_time tm; + + get_rtc_time(&tm); + + return mktime(tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +/* + * In order to set the CMOS clock precisely, set_rtc_mmss has to be called 500 + * ms after the second nowtime has started, because when nowtime is written + * into the registers of the CMOS clock, it will jump to the next second + * precisely 500 ms later. Check the Motorola MC146818A or Dallas DS12887 data + * sheet for details. + * + * BUG: This routine does not handle hour overflow properly; it just + * sets the minutes. Usually you'll only notice that after reboot! + */ +static int set_rtc_mmss(unsigned long nowtime) +{ + unsigned char save_control, save_freq_select; + int retval = 0; + int real_seconds, real_minutes, cmos_minutes; + + /* gets recalled with irq locally disabled */ + spin_lock(&rtc_lock); + save_control = CMOS_READ(RTC_CONTROL); /* tell the clock it's being + * set */ + CMOS_WRITE(save_control | RTC_SET, RTC_CONTROL); + + save_freq_select = CMOS_READ(RTC_FREQ_SELECT); /* stop and reset + * prescaler */ + CMOS_WRITE(save_freq_select | RTC_DIV_RESET2, RTC_FREQ_SELECT); + + cmos_minutes = CMOS_READ(RTC_MINUTES); + if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) + BCD_TO_BIN(cmos_minutes); + + /* + * since we're only adjusting minutes and seconds, + * don't interfere with hour overflow. This avoids + * messing with unknown time zones but requires your + * RTC not to be off by more than 15 minutes + */ + real_seconds = nowtime % 60; + real_minutes = nowtime / 60; + if (((abs(real_minutes - cmos_minutes) + 15) / 30) & 1) + /* correct for half hour time zone */ + real_minutes += 30; + real_minutes %= 60; + + if (abs(real_minutes - cmos_minutes) < 30) { + if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { + BIN_TO_BCD(real_seconds); + BIN_TO_BCD(real_minutes); + } + CMOS_WRITE(real_seconds, RTC_SECONDS); + CMOS_WRITE(real_minutes, RTC_MINUTES); + } else { + printk(KERN_WARNING + "set_rtc_mmss: can't update from %d to %d\n", + cmos_minutes, real_minutes); + retval = -1; + } + + /* The following flags have to be released exactly in this order, + * otherwise the DS12887 (popular MC146818A clone with integrated + * battery and quartz) will not reset the oscillator and will not + * update precisely 500 ms later. You won't find this mentioned in + * the Dallas Semiconductor data sheets, but who believes data + * sheets anyway ... -- Markus Kuhn + */ + CMOS_WRITE(save_control, RTC_CONTROL); + CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); + spin_unlock(&rtc_lock); + + return retval; +} + +void check_rtc_time(void) +{ + /* the RTC clock just finished ticking over again this second + * - if we have an externally synchronized Linux clock, then update + * RTC clock accordingly every ~11 minutes. set_rtc_mmss() has to be + * called as close as possible to 500 ms before the new second starts. + */ + if ((time_status & STA_UNSYNC) == 0 && + xtime.tv_sec > last_rtc_update + 660 && + xtime.tv_nsec / 1000 >= 500000 - ((unsigned) TICK_SIZE) / 2 && + xtime.tv_nsec / 1000 <= 500000 + ((unsigned) TICK_SIZE) / 2 + ) { + if (set_rtc_mmss(xtime.tv_sec) == 0) + last_rtc_update = xtime.tv_sec; + else + /* do it again in 60s */ + last_rtc_update = xtime.tv_sec - 600; + } +} + +/* + * calibrate the TSC clock against the RTC + */ +void __init calibrate_clock(void) +{ + unsigned long count0, counth, count1; + unsigned char status; + + /* make sure the RTC is running and is set to operate in 24hr mode */ + status = RTSRC; + RTCRB |= RTCRB_SET; + RTCRB |= RTCRB_TM_24HR; + RTCRA |= RTCRA_DVR; + RTCRA &= ~RTCRA_DVR; + RTCRB &= ~RTCRB_SET; + + /* work out the clock speed by counting clock cycles between ends of + * the RTC update cycle - track the RTC through one complete update + * cycle (1 second) + */ + startup_timestamp_counter(); + + while (!(RTCRA & RTCRA_UIP)) {} + while ((RTCRA & RTCRA_UIP)) {} + + count0 = TMTSCBC; + + while (!(RTCRA & RTCRA_UIP)) {} + + counth = TMTSCBC; + + while ((RTCRA & RTCRA_UIP)) {} + + count1 = TMTSCBC; + + shutdown_timestamp_counter(); + + MN10300_TSCCLK = count0 - count1; /* the timers count down */ + mn10300_rtc_update_period = counth - count1; + MN10300_TSC_PER_HZ = MN10300_TSCCLK / HZ; +} diff --git a/arch/mn10300/kernel/semaphore.c b/arch/mn10300/kernel/semaphore.c new file mode 100644 index 000000000000..9153c4039fd2 --- /dev/null +++ b/arch/mn10300/kernel/semaphore.c @@ -0,0 +1,149 @@ +/* MN10300 Semaphore implementation + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/sched.h> +#include <linux/module.h> +#include <asm/semaphore.h> + +struct sem_waiter { + struct list_head list; + struct task_struct *task; +}; + +#if SEMAPHORE_DEBUG +void semtrace(struct semaphore *sem, const char *str) +{ + if (sem->debug) + printk(KERN_DEBUG "[%d] %s({%d,%d})\n", + current->pid, + str, + atomic_read(&sem->count), + list_empty(&sem->wait_list) ? 0 : 1); +} +#else +#define semtrace(SEM, STR) do { } while (0) +#endif + +/* + * wait for a token to be granted from a semaphore + * - entered with lock held and interrupts disabled + */ +void __down(struct semaphore *sem, unsigned long flags) +{ + struct task_struct *tsk = current; + struct sem_waiter waiter; + + semtrace(sem, "Entering __down"); + + /* set up my own style of waitqueue */ + waiter.task = tsk; + get_task_struct(tsk); + + list_add_tail(&waiter.list, &sem->wait_list); + + /* we don't need to touch the semaphore struct anymore */ + spin_unlock_irqrestore(&sem->wait_lock, flags); + + /* wait to be given the semaphore */ + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + + for (;;) { + if (!waiter.task) + break; + schedule(); + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + } + + tsk->state = TASK_RUNNING; + semtrace(sem, "Leaving __down"); +} +EXPORT_SYMBOL(__down); + +/* + * interruptibly wait for a token to be granted from a semaphore + * - entered with lock held and interrupts disabled + */ +int __down_interruptible(struct semaphore *sem, unsigned long flags) +{ + struct task_struct *tsk = current; + struct sem_waiter waiter; + int ret; + + semtrace(sem, "Entering __down_interruptible"); + + /* set up my own style of waitqueue */ + waiter.task = tsk; + get_task_struct(tsk); + + list_add_tail(&waiter.list, &sem->wait_list); + + /* we don't need to touch the semaphore struct anymore */ + set_task_state(tsk, TASK_INTERRUPTIBLE); + + spin_unlock_irqrestore(&sem->wait_lock, flags); + + /* wait to be given the semaphore */ + ret = 0; + for (;;) { + if (!waiter.task) + break; + if (unlikely(signal_pending(current))) + goto interrupted; + schedule(); + set_task_state(tsk, TASK_INTERRUPTIBLE); + } + + out: + tsk->state = TASK_RUNNING; + semtrace(sem, "Leaving __down_interruptible"); + return ret; + + interrupted: + spin_lock_irqsave(&sem->wait_lock, flags); + list_del(&waiter.list); + spin_unlock_irqrestore(&sem->wait_lock, flags); + + ret = 0; + if (!waiter.task) { + put_task_struct(current); + ret = -EINTR; + } + goto out; +} +EXPORT_SYMBOL(__down_interruptible); + +/* + * release a single token back to a semaphore + * - entered with lock held and interrupts disabled + */ +void __up(struct semaphore *sem) +{ + struct task_struct *tsk; + struct sem_waiter *waiter; + + semtrace(sem, "Entering __up"); + + /* grant the token to the process at the front of the queue */ + waiter = list_entry(sem->wait_list.next, struct sem_waiter, list); + + /* We must be careful not to touch 'waiter' after we set ->task = NULL. + * It is an allocated on the waiter's stack and may become invalid at + * any time after that point (due to a wakeup from another source). + */ + list_del_init(&waiter->list); + tsk = waiter->task; + smp_mb(); + waiter->task = NULL; + wake_up_process(tsk); + put_task_struct(tsk); + + semtrace(sem, "Leaving __up"); +} +EXPORT_SYMBOL(__up); diff --git a/arch/mn10300/kernel/setup.c b/arch/mn10300/kernel/setup.c new file mode 100644 index 000000000000..6b7ce2636851 --- /dev/null +++ b/arch/mn10300/kernel/setup.c @@ -0,0 +1,298 @@ +/* MN10300 Arch-specific initialisation + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/tty.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/seq_file.h> +#include <asm/processor.h> +#include <linux/console.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/setup.h> +#include <asm/io.h> +#include <asm/smp.h> +#include <asm/proc/proc.h> +#include <asm/busctl-regs.h> +#include <asm/fpu.h> +#include <asm/sections.h> + +struct mn10300_cpuinfo boot_cpu_data; + +/* For PCI or other memory-mapped resources */ +unsigned long pci_mem_start = 0x18000000; + +char redboot_command_line[COMMAND_LINE_SIZE] = + "console=ttyS0,115200 root=/dev/mtdblock3 rw"; + +char __initdata redboot_platform_name[COMMAND_LINE_SIZE]; + +static struct resource code_resource = { + .start = 0x100000, + .end = 0, + .name = "Kernel code", +}; + +static struct resource data_resource = { + .start = 0, + .end = 0, + .name = "Kernel data", +}; + +static unsigned long __initdata phys_memory_base; +static unsigned long __initdata phys_memory_end; +static unsigned long __initdata memory_end; +unsigned long memory_size; + +struct thread_info *__current_ti = &init_thread_union.thread_info; +struct task_struct *__current = &init_task; + +#define mn10300_known_cpus 3 +static const char *const mn10300_cputypes[] = { + "am33v1", + "am33v2", + "am34v1", + "unknown" +}; + +/* + * + */ +static void __init parse_mem_cmdline(char **cmdline_p) +{ + char *from, *to, c; + + /* save unparsed command line copy for /proc/cmdline */ + strcpy(boot_command_line, redboot_command_line); + + /* see if there's an explicit memory size option */ + from = redboot_command_line; + to = redboot_command_line; + c = ' '; + + for (;;) { + if (c == ' ' && !memcmp(from, "mem=", 4)) { + if (to != redboot_command_line) + to--; + memory_size = memparse(from + 4, &from); + } + + c = *(from++); + if (!c) + break; + + *(to++) = c; + } + + *to = '\0'; + *cmdline_p = redboot_command_line; + + if (memory_size == 0) + panic("Memory size not known\n"); + + memory_end = (unsigned long) CONFIG_KERNEL_RAM_BASE_ADDRESS + + memory_size; + if (memory_end > phys_memory_end) + memory_end = phys_memory_end; +} + +/* + * architecture specific setup + */ +void __init setup_arch(char **cmdline_p) +{ + unsigned long bootmap_size; + unsigned long kstart_pfn, start_pfn, free_pfn, end_pfn; + + cpu_init(); + unit_setup(); + parse_mem_cmdline(cmdline_p); + + init_mm.start_code = (unsigned long)&_text; + init_mm.end_code = (unsigned long) &_etext; + init_mm.end_data = (unsigned long) &_edata; + init_mm.brk = (unsigned long) &_end; + + code_resource.start = virt_to_bus(&_text); + code_resource.end = virt_to_bus(&_etext)-1; + data_resource.start = virt_to_bus(&_etext); + data_resource.end = virt_to_bus(&_edata)-1; + +#define PFN_UP(x) (((x) + PAGE_SIZE-1) >> PAGE_SHIFT) +#define PFN_DOWN(x) ((x) >> PAGE_SHIFT) +#define PFN_PHYS(x) ((x) << PAGE_SHIFT) + + start_pfn = (CONFIG_KERNEL_RAM_BASE_ADDRESS >> PAGE_SHIFT); + kstart_pfn = PFN_UP(__pa(&_text)); + free_pfn = PFN_UP(__pa(&_end)); + end_pfn = PFN_DOWN(__pa(memory_end)); + + bootmap_size = init_bootmem_node(&contig_page_data, + free_pfn, + start_pfn, + end_pfn); + + if (kstart_pfn > start_pfn) + free_bootmem(PFN_PHYS(start_pfn), + PFN_PHYS(kstart_pfn - start_pfn)); + + free_bootmem(PFN_PHYS(free_pfn), + PFN_PHYS(end_pfn - free_pfn)); + + /* If interrupt vector table is in main ram, then we need to + reserve the page it is occupying. */ + if (CONFIG_INTERRUPT_VECTOR_BASE >= CONFIG_KERNEL_RAM_BASE_ADDRESS && + CONFIG_INTERRUPT_VECTOR_BASE < memory_end) + reserve_bootmem(CONFIG_INTERRUPT_VECTOR_BASE, 1, + BOOTMEM_DEFAULT); + + reserve_bootmem(PAGE_ALIGN(PFN_PHYS(free_pfn)), bootmap_size, + BOOTMEM_DEFAULT); + +#ifdef CONFIG_VT +#if defined(CONFIG_VGA_CONSOLE) + conswitchp = &vga_con; +#elif defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +#endif +#endif + + paging_init(); +} + +/* + * perform CPU initialisation + */ +void __init cpu_init(void) +{ + unsigned long cpurev = CPUREV, type; + unsigned long base, size; + + type = (CPUREV & CPUREV_TYPE) >> CPUREV_TYPE_S; + if (type > mn10300_known_cpus) + type = mn10300_known_cpus; + + printk(KERN_INFO "Matsushita %s, rev %ld\n", + mn10300_cputypes[type], + (cpurev & CPUREV_REVISION) >> CPUREV_REVISION_S); + + /* determine the memory size and base from the memory controller regs */ + memory_size = 0; + + base = SDBASE(0); + if (base & SDBASE_CE) { + size = (base & SDBASE_CBAM) << SDBASE_CBAM_SHIFT; + size = ~size + 1; + base &= SDBASE_CBA; + + printk(KERN_INFO "SDRAM[0]: %luMb @%08lx\n", size >> 20, base); + memory_size += size; + phys_memory_base = base; + } + + base = SDBASE(1); + if (base & SDBASE_CE) { + size = (base & SDBASE_CBAM) << SDBASE_CBAM_SHIFT; + size = ~size + 1; + base &= SDBASE_CBA; + + printk(KERN_INFO "SDRAM[1]: %luMb @%08lx\n", size >> 20, base); + memory_size += size; + if (phys_memory_base == 0) + phys_memory_base = base; + } + + phys_memory_end = phys_memory_base + memory_size; + +#ifdef CONFIG_FPU + fpu_init_state(); +#endif +} + +/* + * Get CPU information for use by the procfs. + */ +static int show_cpuinfo(struct seq_file *m, void *v) +{ + unsigned long cpurev = CPUREV, type, icachesz, dcachesz; + + type = (CPUREV & CPUREV_TYPE) >> CPUREV_TYPE_S; + if (type > mn10300_known_cpus) + type = mn10300_known_cpus; + + icachesz = + ((cpurev & CPUREV_ICWAY ) >> CPUREV_ICWAY_S) * + ((cpurev & CPUREV_ICSIZE) >> CPUREV_ICSIZE_S) * + 1024; + + dcachesz = + ((cpurev & CPUREV_DCWAY ) >> CPUREV_DCWAY_S) * + ((cpurev & CPUREV_DCSIZE) >> CPUREV_DCSIZE_S) * + 1024; + + seq_printf(m, + "processor : 0\n" + "vendor_id : Matsushita\n" + "cpu core : %s\n" + "cpu rev : %lu\n" + "model name : " PROCESSOR_MODEL_NAME "\n" + "icache size: %lu\n" + "dcache size: %lu\n", + mn10300_cputypes[type], + (cpurev & CPUREV_REVISION) >> CPUREV_REVISION_S, + icachesz, + dcachesz + ); + + seq_printf(m, + "ioclk speed: %lu.%02luMHz\n" + "bogomips : %lu.%02lu\n\n", + MN10300_IOCLK / 1000000, + (MN10300_IOCLK / 10000) % 100, + loops_per_jiffy / (500000 / HZ), + (loops_per_jiffy / (5000 / HZ)) % 100 + ); + + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < NR_CPUS ? cpu_data + *pos : NULL; +} + +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return c_start(m, pos); +} + +static void c_stop(struct seq_file *m, void *v) +{ +} + +struct seq_operations cpuinfo_op = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = show_cpuinfo, +}; diff --git a/arch/mn10300/kernel/sigframe.h b/arch/mn10300/kernel/sigframe.h new file mode 100644 index 000000000000..0decba28ae84 --- /dev/null +++ b/arch/mn10300/kernel/sigframe.h @@ -0,0 +1,33 @@ +/* MN10300 Signal frame definitions + * + * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +struct sigframe +{ + void (*pretcode)(void); + int sig; + struct sigcontext *psc; + struct sigcontext sc; + struct fpucontext fpuctx; + unsigned long extramask[_NSIG_WORDS-1]; + char retcode[8]; +}; + +struct rt_sigframe +{ + void (*pretcode)(void); + int sig; + struct siginfo *pinfo; + void *puc; + struct siginfo info; + struct ucontext uc; + struct fpucontext fpuctx; + char retcode[8]; +}; diff --git a/arch/mn10300/kernel/signal.c b/arch/mn10300/kernel/signal.c new file mode 100644 index 000000000000..841ca9955a18 --- /dev/null +++ b/arch/mn10300/kernel/signal.c @@ -0,0 +1,564 @@ +/* MN10300 Signal handling + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/ptrace.h> +#include <linux/unistd.h> +#include <linux/stddef.h> +#include <linux/tty.h> +#include <linux/personality.h> +#include <linux/suspend.h> +#include <asm/cacheflush.h> +#include <asm/ucontext.h> +#include <asm/uaccess.h> +#include <asm/fpu.h> +#include "sigframe.h" + +#define DEBUG_SIG 0 + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +/* + * atomically swap in the new signal mask, and wait for a signal. + */ +asmlinkage long sys_sigsuspend(int history0, int history1, old_sigset_t mask) +{ + mask &= _BLOCKABLE; + spin_lock_irq(¤t->sighand->siglock); + current->saved_sigmask = current->blocked; + siginitset(¤t->blocked, mask); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + current->state = TASK_INTERRUPTIBLE; + schedule(); + set_thread_flag(TIF_RESTORE_SIGMASK); + return -ERESTARTNOHAND; +} + +/* + * set signal action syscall + */ +asmlinkage long sys_sigaction(int sig, + const struct old_sigaction __user *act, + struct old_sigaction __user *oact) +{ + struct k_sigaction new_ka, old_ka; + int ret; + + if (act) { + old_sigset_t mask; + if (verify_area(VERIFY_READ, act, sizeof(*act)) || + __get_user(new_ka.sa.sa_handler, &act->sa_handler) || + __get_user(new_ka.sa.sa_restorer, &act->sa_restorer)) + return -EFAULT; + __get_user(new_ka.sa.sa_flags, &act->sa_flags); + __get_user(mask, &act->sa_mask); + siginitset(&new_ka.sa.sa_mask, mask); + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + if (verify_area(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || + __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer)) + return -EFAULT; + __put_user(old_ka.sa.sa_flags, &oact->sa_flags); + __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return ret; +} + +/* + * set alternate signal stack syscall + */ +asmlinkage long sys_sigaltstack(const stack_t __user *uss, stack_t *uoss) +{ + return do_sigaltstack(uss, uoss, __frame->sp); +} + +/* + * do a signal return; undo the signal stack. + */ +static int restore_sigcontext(struct pt_regs *regs, + struct sigcontext __user *sc, long *_d0) +{ + unsigned int err = 0; + + if (is_using_fpu(current)) + fpu_kill_state(current); + +#define COPY(x) err |= __get_user(regs->x, &sc->x) + COPY(d1); COPY(d2); COPY(d3); + COPY(a0); COPY(a1); COPY(a2); COPY(a3); + COPY(e0); COPY(e1); COPY(e2); COPY(e3); + COPY(e4); COPY(e5); COPY(e6); COPY(e7); + COPY(lar); COPY(lir); + COPY(mdr); COPY(mdrq); + COPY(mcvf); COPY(mcrl); COPY(mcrh); + COPY(sp); COPY(pc); +#undef COPY + + { + unsigned int tmpflags; +#ifndef CONFIG_MN10300_USING_JTAG +#define USER_EPSW (EPSW_FLAG_Z | EPSW_FLAG_N | EPSW_FLAG_C | EPSW_FLAG_V | \ + EPSW_T | EPSW_nAR) +#else +#define USER_EPSW (EPSW_FLAG_Z | EPSW_FLAG_N | EPSW_FLAG_C | EPSW_FLAG_V | \ + EPSW_nAR) +#endif + err |= __get_user(tmpflags, &sc->epsw); + regs->epsw = (regs->epsw & ~USER_EPSW) | + (tmpflags & USER_EPSW); + regs->orig_d0 = -1; /* disable syscall checks */ + } + + { + struct fpucontext *buf; + err |= __get_user(buf, &sc->fpucontext); + if (buf) { + if (verify_area(VERIFY_READ, buf, sizeof(*buf))) + goto badframe; + err |= fpu_restore_sigcontext(buf); + } + } + + err |= __get_user(*_d0, &sc->d0); + return err; + +badframe: + return 1; +} + +/* + * standard signal return syscall + */ +asmlinkage long sys_sigreturn(void) +{ + struct sigframe __user *frame = (struct sigframe __user *) __frame->sp; + sigset_t set; + long d0; + + if (verify_area(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + if (__get_user(set.sig[0], &frame->sc.oldmask)) + goto badframe; + + if (_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(__frame, &frame->sc, &d0)) + goto badframe; + + return d0; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* + * realtime signal return syscall + */ +asmlinkage long sys_rt_sigreturn(void) +{ + struct rt_sigframe __user *frame = + (struct rt_sigframe __user *) __frame->sp; + sigset_t set; + unsigned long d0; + + if (verify_area(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(__frame, &frame->uc.uc_mcontext, &d0)) + goto badframe; + + if (do_sigaltstack(&frame->uc.uc_stack, NULL, __frame->sp) == -EFAULT) + goto badframe; + + return d0; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* + * store the userspace context into a signal frame + */ +static int setup_sigcontext(struct sigcontext __user *sc, + struct fpucontext *fpuctx, + struct pt_regs *regs, + unsigned long mask) +{ + int tmp, err = 0; + +#define COPY(x) err |= __put_user(regs->x, &sc->x) + COPY(d0); COPY(d1); COPY(d2); COPY(d3); + COPY(a0); COPY(a1); COPY(a2); COPY(a3); + COPY(e0); COPY(e1); COPY(e2); COPY(e3); + COPY(e4); COPY(e5); COPY(e6); COPY(e7); + COPY(lar); COPY(lir); + COPY(mdr); COPY(mdrq); + COPY(mcvf); COPY(mcrl); COPY(mcrh); + COPY(sp); COPY(epsw); COPY(pc); +#undef COPY + + tmp = fpu_setup_sigcontext(fpuctx); + if (tmp < 0) + err = 1; + else + err |= __put_user(tmp ? fpuctx : NULL, &sc->fpucontext); + + /* non-iBCS2 extensions.. */ + err |= __put_user(mask, &sc->oldmask); + + return err; +} + +/* + * determine which stack to use.. + */ +static inline void __user *get_sigframe(struct k_sigaction *ka, + struct pt_regs *regs, + size_t frame_size) +{ + unsigned long sp; + + /* default to using normal stack */ + sp = regs->sp; + + /* this is the X/Open sanctioned signal stack switching. */ + if (ka->sa.sa_flags & SA_ONSTACK) { + if (!on_sig_stack(sp)) + sp = current->sas_ss_sp + current->sas_ss_size; + } + + return (void __user *) ((sp - frame_size) & ~7UL); +} + +/* + * set up a normal signal frame + */ +static int setup_frame(int sig, struct k_sigaction *ka, sigset_t *set, + struct pt_regs *regs) +{ + struct sigframe __user *frame; + int rsig; + + frame = get_sigframe(ka, regs, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + rsig = sig; + if (sig < 32 && + current_thread_info()->exec_domain && + current_thread_info()->exec_domain->signal_invmap) + rsig = current_thread_info()->exec_domain->signal_invmap[sig]; + + if (__put_user(rsig, &frame->sig) < 0 || + __put_user(&frame->sc, &frame->psc) < 0) + goto give_sigsegv; + + if (setup_sigcontext(&frame->sc, &frame->fpuctx, regs, set->sig[0])) + goto give_sigsegv; + + if (_NSIG_WORDS > 1) { + if (__copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask))) + goto give_sigsegv; + } + + /* set up to return from userspace. If provided, use a stub already in + * userspace */ + if (ka->sa.sa_flags & SA_RESTORER) { + if (__put_user(ka->sa.sa_restorer, &frame->pretcode)) + goto give_sigsegv; + } else { + if (__put_user((void (*)(void))frame->retcode, + &frame->pretcode)) + goto give_sigsegv; + /* this is mov $,d0; syscall 0 */ + if (__put_user(0x2c, (char *)(frame->retcode + 0)) || + __put_user(__NR_sigreturn, (char *)(frame->retcode + 1)) || + __put_user(0x00, (char *)(frame->retcode + 2)) || + __put_user(0xf0, (char *)(frame->retcode + 3)) || + __put_user(0xe0, (char *)(frame->retcode + 4))) + goto give_sigsegv; + flush_icache_range((unsigned long) frame->retcode, + (unsigned long) frame->retcode + 5); + } + + /* set up registers for signal handler */ + regs->sp = (unsigned long) frame; + regs->pc = (unsigned long) ka->sa.sa_handler; + regs->d0 = sig; + regs->d1 = (unsigned long) &frame->sc; + + set_fs(USER_DS); + + /* the tracer may want to single-step inside the handler */ + if (test_thread_flag(TIF_SINGLESTEP)) + ptrace_notify(SIGTRAP); + +#if DEBUG_SIG + printk(KERN_DEBUG "SIG deliver %d (%s:%d): sp=%p pc=%lx ra=%p\n", + sig, current->comm, current->pid, frame, regs->pc, + frame->pretcode); +#endif + + return 0; + +give_sigsegv: + force_sig(SIGSEGV, current); + return -EFAULT; +} + +/* + * set up a realtime signal frame + */ +static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info, + sigset_t *set, struct pt_regs *regs) +{ + struct rt_sigframe __user *frame; + int rsig; + + frame = get_sigframe(ka, regs, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + rsig = sig; + if (sig < 32 && + current_thread_info()->exec_domain && + current_thread_info()->exec_domain->signal_invmap) + rsig = current_thread_info()->exec_domain->signal_invmap[sig]; + + if (__put_user(rsig, &frame->sig) || + __put_user(&frame->info, &frame->pinfo) || + __put_user(&frame->uc, &frame->puc) || + copy_siginfo_to_user(&frame->info, info)) + goto give_sigsegv; + + /* create the ucontext. */ + if (__put_user(0, &frame->uc.uc_flags) || + __put_user(0, &frame->uc.uc_link) || + __put_user((void *)current->sas_ss_sp, &frame->uc.uc_stack.ss_sp) || + __put_user(sas_ss_flags(regs->sp), &frame->uc.uc_stack.ss_flags) || + __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size) || + setup_sigcontext(&frame->uc.uc_mcontext, + &frame->fpuctx, regs, set->sig[0]) || + __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set))) + goto give_sigsegv; + + /* set up to return from userspace. If provided, use a stub already in + * userspace */ + if (ka->sa.sa_flags & SA_RESTORER) { + if (__put_user(ka->sa.sa_restorer, &frame->pretcode)) + goto give_sigsegv; + } else { + if (__put_user((void(*)(void))frame->retcode, + &frame->pretcode) || + /* This is mov $,d0; syscall 0 */ + __put_user(0x2c, (char *)(frame->retcode + 0)) || + __put_user(__NR_rt_sigreturn, + (char *)(frame->retcode + 1)) || + __put_user(0x00, (char *)(frame->retcode + 2)) || + __put_user(0xf0, (char *)(frame->retcode + 3)) || + __put_user(0xe0, (char *)(frame->retcode + 4))) + goto give_sigsegv; + + flush_icache_range((u_long) frame->retcode, + (u_long) frame->retcode + 5); + } + + /* Set up registers for signal handler */ + regs->sp = (unsigned long) frame; + regs->pc = (unsigned long) ka->sa.sa_handler; + regs->d0 = sig; + regs->d1 = (long) &frame->info; + + set_fs(USER_DS); + + /* the tracer may want to single-step inside the handler */ + if (test_thread_flag(TIF_SINGLESTEP)) + ptrace_notify(SIGTRAP); + +#if DEBUG_SIG + printk(KERN_DEBUG "SIG deliver %d (%s:%d): sp=%p pc=%lx ra=%p\n", + sig, current->comm, current->pid, frame, regs->pc, + frame->pretcode); +#endif + + return 0; + +give_sigsegv: + force_sig(SIGSEGV, current); + return -EFAULT; +} + +/* + * handle the actual delivery of a signal to userspace + */ +static int handle_signal(int sig, + siginfo_t *info, struct k_sigaction *ka, + sigset_t *oldset, struct pt_regs *regs) +{ + int ret; + + /* Are we from a system call? */ + if (regs->orig_d0 >= 0) { + /* If so, check system call restarting.. */ + switch (regs->d0) { + case -ERESTART_RESTARTBLOCK: + case -ERESTARTNOHAND: + regs->d0 = -EINTR; + break; + + case -ERESTARTSYS: + if (!(ka->sa.sa_flags & SA_RESTART)) { + regs->d0 = -EINTR; + break; + } + + /* fallthrough */ + case -ERESTARTNOINTR: + regs->d0 = regs->orig_d0; + regs->pc -= 2; + } + } + + /* Set up the stack frame */ + if (ka->sa.sa_flags & SA_SIGINFO) + ret = setup_rt_frame(sig, ka, info, oldset, regs); + else + ret = setup_frame(sig, ka, oldset, regs); + + if (ret == 0) { + spin_lock_irq(¤t->sighand->siglock); + sigorsets(¤t->blocked, ¤t->blocked, + &ka->sa.sa_mask); + if (!(ka->sa.sa_flags & SA_NODEFER)) + sigaddset(¤t->blocked, sig); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } + + return ret; +} + +/* + * handle a potential signal + */ +static void do_signal(struct pt_regs *regs) +{ + struct k_sigaction ka; + siginfo_t info; + sigset_t *oldset; + int signr; + + /* we want the common case to go fast, which is why we may in certain + * cases get here from kernel mode */ + if (!user_mode(regs)) + return; + + if (test_thread_flag(TIF_RESTORE_SIGMASK)) + oldset = ¤t->saved_sigmask; + else + oldset = ¤t->blocked; + + signr = get_signal_to_deliver(&info, &ka, regs, NULL); + if (signr > 0) { + if (handle_signal(signr, &info, &ka, oldset, regs) == 0) { + /* a signal was successfully delivered; the saved + * sigmask will have been stored in the signal frame, + * and will be restored by sigreturn, so we can simply + * clear the TIF_RESTORE_SIGMASK flag */ + if (test_thread_flag(TIF_RESTORE_SIGMASK)) + clear_thread_flag(TIF_RESTORE_SIGMASK); + } + + return; + } + + /* did we come from a system call? */ + if (regs->orig_d0 >= 0) { + /* restart the system call - no handlers present */ + switch (regs->d0) { + case -ERESTARTNOHAND: + case -ERESTARTSYS: + case -ERESTARTNOINTR: + regs->d0 = regs->orig_d0; + regs->pc -= 2; + break; + + case -ERESTART_RESTARTBLOCK: + regs->d0 = __NR_restart_syscall; + regs->pc -= 2; + break; + } + } + + /* if there's no signal to deliver, we just put the saved sigmask + * back */ + if (test_thread_flag(TIF_RESTORE_SIGMASK)) { + clear_thread_flag(TIF_RESTORE_SIGMASK); + sigprocmask(SIG_SETMASK, ¤t->saved_sigmask, NULL); + } +} + +/* + * notification of userspace execution resumption + * - triggered by current->work.notify_resume + */ +asmlinkage void do_notify_resume(struct pt_regs *regs, u32 thread_info_flags) +{ + /* Pending single-step? */ + if (thread_info_flags & _TIF_SINGLESTEP) { +#ifndef CONFIG_MN10300_USING_JTAG + regs->epsw |= EPSW_T; + clear_thread_flag(TIF_SINGLESTEP); +#else + BUG(); /* no h/w single-step if using JTAG unit */ +#endif + } + + /* deal with pending signal delivery */ + if (thread_info_flags & (_TIF_SIGPENDING | _TIF_RESTORE_SIGMASK)) + do_signal(regs); +} diff --git a/arch/mn10300/kernel/switch_to.S b/arch/mn10300/kernel/switch_to.S new file mode 100644 index 000000000000..630aad71b946 --- /dev/null +++ b/arch/mn10300/kernel/switch_to.S @@ -0,0 +1,71 @@ +############################################################################### +# +# MN10300 Context switch operation +# +# Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public Licence +# as published by the Free Software Foundation; either version +# 2 of the Licence, or (at your option) any later version. +# +############################################################################### +#include <linux/sys.h> +#include <linux/linkage.h> +#include <asm/thread_info.h> +#include <asm/cpu-regs.h> + + .text + +############################################################################### +# +# struct task_struct *__switch_to(struct thread_struct *prev, +# struct thread_struct *next, +# struct task_struct *prev_task) +# +############################################################################### +ENTRY(__switch_to) + movm [d2,d3,a2,a3,exreg1],(sp) + or EPSW_NMID,epsw + + mov (44,sp),d2 + + mov d0,a0 + mov d1,a1 + + # save prev context + mov (__frame),d0 + mov d0,(THREAD_FRAME,a0) + mov __switch_back,d0 + mov d0,(THREAD_PC,a0) + mov sp,a2 + mov a2,(THREAD_SP,a0) + mov a3,(THREAD_A3,a0) + + mov (THREAD_A3,a1),a3 + mov (THREAD_SP,a1),a2 + + # switch + mov a2,sp + + # load next context + GET_THREAD_INFO a2 + mov a2,(__current_ti) + mov (TI_task,a2),a2 + mov a2,(__current) +#ifdef CONFIG_MN10300_CURRENT_IN_E2 + mov a2,e2 +#endif + + mov (THREAD_FRAME,a1),a2 + mov a2,(__frame) + mov (THREAD_PC,a1),a2 + mov d2,d0 # for ret_from_fork + mov d0,a0 # for __switch_to + + jmp (a2) + +__switch_back: + and ~EPSW_NMID,epsw + ret [d2,d3,a2,a3,exreg1],32 diff --git a/arch/mn10300/kernel/sys_mn10300.c b/arch/mn10300/kernel/sys_mn10300.c new file mode 100644 index 000000000000..5f17a1ebc825 --- /dev/null +++ b/arch/mn10300/kernel/sys_mn10300.c @@ -0,0 +1,193 @@ +/* MN10300 Weird system calls + * + * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/syscalls.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/sem.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/stat.h> +#include <linux/mman.h> +#include <linux/file.h> +#include <linux/utsname.h> +#include <linux/syscalls.h> +#include <linux/tty.h> + +#include <asm/uaccess.h> + +#define MIN_MAP_ADDR PAGE_SIZE /* minimum fixed mmap address */ + +/* + * sys_pipe() is the normal C calling standard for creating + * a pipe. It's not the way Unix traditionally does this, though. + */ +asmlinkage long sys_pipe(unsigned long __user *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; +} + +/* + * memory mapping syscall + */ +asmlinkage long sys_mmap2(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long pgoff) +{ + struct file *file = NULL; + long error = -EINVAL; + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + + if (flags & MAP_FIXED && addr < MIN_MAP_ADDR) + 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; +} + +asmlinkage long old_mmap(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long offset) +{ + if (offset & ~PAGE_MASK) + return -EINVAL; + return sys_mmap2(addr, len, prot, flags, fd, offset >> PAGE_SHIFT); +} + +struct sel_arg_struct { + unsigned long n; + fd_set *inp; + fd_set *outp; + fd_set *exp; + struct timeval *tvp; +}; + +asmlinkage int old_select(struct sel_arg_struct __user *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 long sys_ipc(uint call, int first, int second, + int third, void __user *ptr, long fifth) +{ + int version, ret; + + version = call >> 16; /* hack for backward compatibility */ + call &= 0xffff; + + switch (call) { + case SEMOP: + return sys_semtimedop(first, (struct sembuf __user *)ptr, + second, NULL); + case SEMTIMEDOP: + return sys_semtimedop(first, (struct sembuf __user *)ptr, + second, + (const struct timespec __user *)fifth); + case SEMGET: + return sys_semget(first, second, third); + case SEMCTL: { + union semun fourth; + if (!ptr) + return -EINVAL; + if (get_user(fourth.__pad, (void __user * __user *) ptr)) + return -EFAULT; + return sys_semctl(first, second, third, fourth); + } + + case MSGSND: + return sys_msgsnd(first, (struct msgbuf __user *) 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 __user *) ptr, + sizeof(tmp))) + return -EFAULT; + return sys_msgrcv(first, tmp.msgp, second, + tmp.msgtyp, third); + } + default: + return sys_msgrcv(first, + (struct msgbuf __user *) ptr, + second, fifth, third); + } + case MSGGET: + return sys_msgget((key_t) first, second); + case MSGCTL: + return sys_msgctl(first, second, + (struct msqid_ds __user *) ptr); + + case SHMAT: + switch (version) { + default: { + ulong raddr; + ret = do_shmat(first, (char __user *) 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 __user *) ptr, second, + (ulong *) third); + } + case SHMDT: + return sys_shmdt((char __user *)ptr); + case SHMGET: + return sys_shmget(first, second, third); + case SHMCTL: + return sys_shmctl(first, second, + (struct shmid_ds __user *) ptr); + default: + return -EINVAL; + } +} diff --git a/arch/mn10300/kernel/time.c b/arch/mn10300/kernel/time.c new file mode 100644 index 000000000000..ff492e3b3457 --- /dev/null +++ b/arch/mn10300/kernel/time.c @@ -0,0 +1,129 @@ +/* MN10300 Low level time management + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * - Derived from arch/i386/kernel/time.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/profile.h> +#include <asm/irq.h> +#include <asm/div64.h> +#include <asm/processor.h> +#include <asm/intctl-regs.h> +#include <asm/rtc.h> + +#ifdef CONFIG_MN10300_RTC +unsigned long mn10300_ioclk; /* system I/O clock frequency */ +unsigned long mn10300_iobclk; /* system I/O clock frequency */ +unsigned long mn10300_tsc_per_HZ; /* number of ioclks per jiffy */ +#endif /* CONFIG_MN10300_RTC */ + +static unsigned long mn10300_last_tsc; /* time-stamp counter at last time + * interrupt occurred */ + +static irqreturn_t timer_interrupt(int irq, void *dev_id); + +static struct irqaction timer_irq = { + .handler = timer_interrupt, + .flags = IRQF_DISABLED | IRQF_SHARED | IRQF_TIMER, + .mask = CPU_MASK_NONE, + .name = "timer", +}; + +/* + * scheduler clock - returns current time in nanosec units. + */ +unsigned long long sched_clock(void) +{ + union { + unsigned long long l; + u32 w[2]; + } quot; + + quot.w[0] = mn10300_last_tsc - get_cycles(); + quot.w[1] = 1000000000; + + asm("mulu %2,%3,%0,%1" + : "=r"(quot.w[1]), "=r"(quot.w[0]) + : "0"(quot.w[1]), "1"(quot.w[0]) + : "cc"); + + do_div(quot.l, MN10300_TSCCLK); + + return quot.l; +} + +/* + * advance the kernel's time keeping clocks (xtime and jiffies) + * - we use Timer 0 & 1 cascaded as a clock to nudge us the next time + * there's a need to update + */ +static irqreturn_t timer_interrupt(int irq, void *dev_id) +{ + unsigned tsc, elapse; + + write_seqlock(&xtime_lock); + + while (tsc = get_cycles(), + elapse = mn10300_last_tsc - tsc, /* time elapsed since last + * tick */ + elapse > MN10300_TSC_PER_HZ + ) { + mn10300_last_tsc -= MN10300_TSC_PER_HZ; + + /* advance the kernel's time tracking system */ + profile_tick(CPU_PROFILING); + do_timer(1); + update_process_times(user_mode(get_irq_regs())); + check_rtc_time(); + } + + write_sequnlock(&xtime_lock); + return IRQ_HANDLED; +} + +/* + * initialise the various timers used by the main part of the kernel + */ +void __init time_init(void) +{ + /* we need the prescalar running to be able to use IOCLK/8 + * - IOCLK runs at 1/4 (ST5 open) or 1/8 (ST5 closed) internal CPU clock + * - IOCLK runs at Fosc rate (crystal speed) + */ + TMPSCNT |= TMPSCNT_ENABLE; + + startup_timestamp_counter(); + + printk(KERN_INFO + "timestamp counter I/O clock running at %lu.%02lu" + " (calibrated against RTC)\n", + MN10300_TSCCLK / 1000000, (MN10300_TSCCLK / 10000) % 100); + + xtime.tv_sec = get_initial_rtc_time(); + xtime.tv_nsec = 0; + + mn10300_last_tsc = TMTSCBC; + + /* use timer 0 & 1 cascaded to tick at as close to HZ as possible */ + setup_irq(TMJCIRQ, &timer_irq); + + set_intr_level(TMJCIRQ, TMJCICR_LEVEL); + + startup_jiffies_counter(); + +#ifdef CONFIG_MN10300_WD_TIMER + /* start the watchdog timer */ + watchdog_go(); +#endif +} diff --git a/arch/mn10300/kernel/traps.c b/arch/mn10300/kernel/traps.c new file mode 100644 index 000000000000..8b9dc6d9dcc6 --- /dev/null +++ b/arch/mn10300/kernel/traps.c @@ -0,0 +1,619 @@ +/* MN10300 Exception handling + * + * Copyright (C) 2007 Matsushita Electric Industrial Co., Ltd. + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Modified by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/kallsyms.h> +#include <linux/pci.h> +#include <linux/kdebug.h> +#include <linux/bug.h> +#include <linux/irq.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/atomic.h> +#include <asm/smp.h> +#include <asm/pgalloc.h> +#include <asm/cacheflush.h> +#include <asm/cpu-regs.h> +#include <asm/busctl-regs.h> +#include <asm/unit/leds.h> +#include <asm/fpu.h> +#include <asm/gdb-stub.h> +#include <asm/sections.h> + +#if (CONFIG_INTERRUPT_VECTOR_BASE & 0xffffff) +#error "INTERRUPT_VECTOR_BASE not aligned to 16MiB boundary!" +#endif + +struct pt_regs *__frame; /* current frame pointer */ +EXPORT_SYMBOL(__frame); + +int kstack_depth_to_print = 24; + +spinlock_t die_lock = __SPIN_LOCK_UNLOCKED(die_lock); + +ATOMIC_NOTIFIER_HEAD(mn10300_die_chain); + +/* + * These constants are for searching for possible module text + * segments. MODULE_RANGE is a guess of how much space is likely + * to be vmalloced. + */ +#define MODULE_RANGE (8 * 1024 * 1024) + +#define DO_ERROR(signr, prologue, str, name) \ +asmlinkage void name(struct pt_regs *regs, u32 intcode) \ +{ \ + prologue; \ + if (die_if_no_fixup(str, regs, intcode)) \ + return; \ + force_sig(signr, current); \ +} + +#define DO_EINFO(signr, prologue, str, name, sicode) \ +asmlinkage void name(struct pt_regs *regs, u32 intcode) \ +{ \ + siginfo_t info; \ + prologue; \ + if (die_if_no_fixup(str, regs, intcode)) \ + return; \ + info.si_signo = signr; \ + if (signr == SIGILL && sicode == ILL_ILLOPC) { \ + uint8_t opcode; \ + if (get_user(opcode, (uint8_t __user *)regs->pc) == 0) \ + if (opcode == 0xff) \ + info.si_signo = SIGTRAP; \ + } \ + info.si_errno = 0; \ + info.si_code = sicode; \ + info.si_addr = (void *) regs->pc; \ + force_sig_info(info.si_signo, &info, current); \ +} + +DO_ERROR(SIGTRAP, {}, "trap", trap); +DO_ERROR(SIGSEGV, {}, "ibreak", ibreak); +DO_ERROR(SIGSEGV, {}, "obreak", obreak); +DO_EINFO(SIGSEGV, {}, "access error", access_error, SEGV_ACCERR); +DO_EINFO(SIGSEGV, {}, "insn access error", insn_acc_error, SEGV_ACCERR); +DO_EINFO(SIGSEGV, {}, "data access error", data_acc_error, SEGV_ACCERR); +DO_EINFO(SIGILL, {}, "privileged opcode", priv_op, ILL_PRVOPC); +DO_EINFO(SIGILL, {}, "invalid opcode", invalid_op, ILL_ILLOPC); +DO_EINFO(SIGILL, {}, "invalid ex opcode", invalid_exop, ILL_ILLOPC); +DO_EINFO(SIGBUS, {}, "invalid address", mem_error, BUS_ADRERR); +DO_EINFO(SIGBUS, {}, "bus error", bus_error, BUS_ADRERR); +DO_EINFO(SIGILL, {}, "FPU invalid opcode", fpu_invalid_op, ILL_COPROC); + +DO_ERROR(SIGTRAP, +#ifndef CONFIG_MN10300_USING_JTAG + DCR &= ~0x0001, +#else + {}, +#endif + "single step", istep); + +/* + * handle NMI + */ +asmlinkage void nmi(struct pt_regs *regs, enum exception_code code) +{ + /* see if gdbstub wants to deal with it */ +#ifdef CONFIG_GDBSTUB + if (gdbstub_intercept(regs, code)) + return; +#endif + + printk(KERN_WARNING "--- Register Dump ---\n"); + show_registers(regs); + printk(KERN_WARNING "---------------------\n"); +} + +/* + * show a stack trace from the specified stack pointer + */ +void show_trace(unsigned long *sp) +{ + unsigned long *stack, addr, module_start, module_end; + int i; + + printk(KERN_EMERG "\n" + KERN_EMERG "Call Trace:"); + + stack = sp; + i = 0; + module_start = VMALLOC_START; + module_end = VMALLOC_END; + + while (((long) stack & (THREAD_SIZE - 1)) != 0) { + addr = *stack++; + if (__kernel_text_address(addr)) { +#if 1 + printk(" [<%08lx>]", addr); + print_symbol(" %s", addr); + printk("\n"); +#else + if ((i % 6) == 0) + printk("\n" KERN_EMERG " "); + printk("[<%08lx>] ", addr); + i++; +#endif + } + } + + printk("\n"); +} + +/* + * show the raw stack from the specified stack pointer + */ +void show_stack(struct task_struct *task, unsigned long *sp) +{ + unsigned long *stack; + int i; + + if (!sp) + sp = (unsigned long *) &sp; + + stack = sp; + printk(KERN_EMERG "Stack:"); + for (i = 0; i < kstack_depth_to_print; i++) { + if (((long) stack & (THREAD_SIZE - 1)) == 0) + break; + if ((i % 8) == 0) + printk("\n" KERN_EMERG " "); + printk("%08lx ", *stack++); + } + + show_trace(sp); +} + +/* + * the architecture-independent dump_stack generator + */ +void dump_stack(void) +{ + unsigned long stack; + + show_stack(current, &stack); +} +EXPORT_SYMBOL(dump_stack); + +/* + * dump the register file in the specified exception frame + */ +void show_registers_only(struct pt_regs *regs) +{ + unsigned long ssp; + + ssp = (unsigned long) regs + sizeof(*regs); + + printk(KERN_EMERG "PC: %08lx EPSW: %08lx SSP: %08lx mode: %s\n", + regs->pc, regs->epsw, ssp, user_mode(regs) ? "User" : "Super"); + printk(KERN_EMERG "d0: %08lx d1: %08lx d2: %08lx d3: %08lx\n", + regs->d0, regs->d1, regs->d2, regs->d3); + printk(KERN_EMERG "a0: %08lx a1: %08lx a2: %08lx a3: %08lx\n", + regs->a0, regs->a1, regs->a2, regs->a3); + printk(KERN_EMERG "e0: %08lx e1: %08lx e2: %08lx e3: %08lx\n", + regs->e0, regs->e1, regs->e2, regs->e3); + printk(KERN_EMERG "e4: %08lx e5: %08lx e6: %08lx e7: %08lx\n", + regs->e4, regs->e5, regs->e6, regs->e7); + printk(KERN_EMERG "lar: %08lx lir: %08lx mdr: %08lx usp: %08lx\n", + regs->lar, regs->lir, regs->mdr, regs->sp); + printk(KERN_EMERG "cvf: %08lx crl: %08lx crh: %08lx drq: %08lx\n", + regs->mcvf, regs->mcrl, regs->mcrh, regs->mdrq); + printk(KERN_EMERG "threadinfo=%p task=%p)\n", + current_thread_info(), current); + + if ((unsigned long) current >= 0x90000000UL && + (unsigned long) current < 0x94000000UL) + printk(KERN_EMERG "Process %s (pid: %d)\n", + current->comm, current->pid); + + printk(KERN_EMERG "CPUP: %04hx\n", CPUP); + printk(KERN_EMERG "TBR: %08x\n", TBR); + printk(KERN_EMERG "DEAR: %08x\n", DEAR); + printk(KERN_EMERG "sISR: %08x\n", sISR); + printk(KERN_EMERG "NMICR: %04hx\n", NMICR); + printk(KERN_EMERG "BCBERR: %08x\n", BCBERR); + printk(KERN_EMERG "BCBEAR: %08x\n", BCBEAR); + printk(KERN_EMERG "MMUFCR: %08x\n", MMUFCR); + printk(KERN_EMERG "IPTEU : %08x IPTEL2: %08x\n", IPTEU, IPTEL2); + printk(KERN_EMERG "DPTEU: %08x DPTEL2: %08x\n", DPTEU, DPTEL2); +} + +/* + * dump the registers and the stack + */ +void show_registers(struct pt_regs *regs) +{ + unsigned long sp; + int i; + + show_registers_only(regs); + + if (!user_mode(regs)) + sp = (unsigned long) regs + sizeof(*regs); + else + sp = regs->sp; + + /* when in-kernel, we also print out the stack and code at the + * time of the fault.. + */ + if (!user_mode(regs)) { + printk(KERN_EMERG "\n"); + show_stack(current, (unsigned long *) sp); + +#if 0 + printk(KERN_EMERG "\n" + KERN_EMERG "Code: "); + if (regs->pc < PAGE_OFFSET) + goto bad; + + for (i = 0; i < 20; i++) { + unsigned char c; + if (__get_user(c, &((unsigned char *) regs->pc)[i])) + goto bad; + printk("%02x ", c); + } +#else + i = 0; +#endif + } + + printk("\n"); + return; + +#if 0 +bad: + printk(KERN_EMERG " Bad PC value."); + break; +#endif +} + +/* + * + */ +void show_trace_task(struct task_struct *tsk) +{ + unsigned long sp = tsk->thread.sp; + + /* User space on another CPU? */ + if ((sp ^ (unsigned long) tsk) & (PAGE_MASK << 1)) + return; + + show_trace((unsigned long *) sp); +} + +/* + * note the untimely death of part of the kernel + */ +void die(const char *str, struct pt_regs *regs, enum exception_code code) +{ + console_verbose(); + spin_lock_irq(&die_lock); + printk(KERN_EMERG "\n" + KERN_EMERG "%s: %04x\n", + str, code & 0xffff); + show_registers(regs); + + if (regs->pc >= 0x02000000 && regs->pc < 0x04000000 && + (regs->epsw & (EPSW_IM | EPSW_IE)) != (EPSW_IM | EPSW_IE)) { + printk(KERN_EMERG "Exception in usermode interrupt handler\n"); + printk(KERN_EMERG "\n" + KERN_EMERG " Please connect to kernel debugger !!\n"); + asm volatile ("0: bra 0b"); + } + + spin_unlock_irq(&die_lock); + do_exit(SIGSEGV); +} + +/* + * see if there's a fixup handler we can force a jump to when an exception + * happens due to something kernel code did + */ +int die_if_no_fixup(const char *str, struct pt_regs *regs, + enum exception_code code) +{ + if (user_mode(regs)) + return 0; + + peripheral_leds_display_exception(code); + + switch (code) { + /* see if we can fixup the kernel accessing memory */ + case EXCEP_ITLBMISS: + case EXCEP_DTLBMISS: + case EXCEP_IAERROR: + case EXCEP_DAERROR: + case EXCEP_MEMERR: + case EXCEP_MISALIGN: + case EXCEP_BUSERROR: + case EXCEP_ILLDATACC: + case EXCEP_IOINSACC: + case EXCEP_PRIVINSACC: + case EXCEP_PRIVDATACC: + case EXCEP_DATINSACC: + if (fixup_exception(regs)) + return 1; + case EXCEP_UNIMPINS: + if (regs->pc && *(uint8_t *)regs->pc == 0xff) + if (notify_die(DIE_BREAKPOINT, str, regs, code, 0, 0)) + return 1; + break; + default: + break; + } + + /* see if gdbstub wants to deal with it */ +#ifdef CONFIG_GDBSTUB + if (gdbstub_intercept(regs, code)) + return 1; +#endif + + if (notify_die(DIE_GPF, str, regs, code, 0, 0)) + return 1; + + /* make the process die as the last resort */ + die(str, regs, code); +} + +/* + * handle unsupported syscall instructions (syscall 1-15) + */ +static asmlinkage void unsupported_syscall(struct pt_regs *regs, + enum exception_code code) +{ + struct task_struct *tsk = current; + siginfo_t info; + + /* catch a kernel BUG() */ + if (code == EXCEP_SYSCALL15 && !user_mode(regs)) { + if (report_bug(regs->pc, regs) == BUG_TRAP_TYPE_BUG) { +#ifdef CONFIG_GDBSTUB + __gdbstub_bug_trap(); +#endif + } + } + + regs->pc -= 2; /* syscall return addr is _after_ the instruction */ + + die_if_no_fixup("An unsupported syscall insn was used by the kernel\n", + regs, code); + + info.si_signo = SIGILL; + info.si_errno = ENOSYS; + info.si_code = ILL_ILLTRP; + info.si_addr = (void *) regs->pc; + force_sig_info(SIGILL, &info, tsk); +} + +/* + * display the register file when the stack pointer gets clobbered + */ +asmlinkage void do_double_fault(struct pt_regs *regs) +{ + struct task_struct *tsk = current; + + strcpy(tsk->comm, "emergency tsk"); + tsk->pid = 0; + console_verbose(); + printk(KERN_EMERG "--- double fault ---\n"); + show_registers(regs); +} + +/* + * asynchronous bus error (external, usually I/O DMA) + */ +asmlinkage void io_bus_error(u32 bcberr, u32 bcbear, struct pt_regs *regs) +{ + console_verbose(); + + printk(KERN_EMERG "\n" + KERN_EMERG "Asynchronous I/O Bus Error\n" + KERN_EMERG "==========================\n"); + + if (bcberr & BCBERR_BEME) + printk(KERN_EMERG "- Multiple recorded errors\n"); + + printk(KERN_EMERG "- Faulting Buses:%s%s%s\n", + bcberr & BCBERR_BEMR_CI ? " CPU-Ins-Fetch" : "", + bcberr & BCBERR_BEMR_CD ? " CPU-Data" : "", + bcberr & BCBERR_BEMR_DMA ? " DMA" : ""); + + printk(KERN_EMERG "- %s %s access made to %s at address %08x\n", + bcberr & BCBERR_BEBST ? "Burst" : "Single", + bcberr & BCBERR_BERW ? "Read" : "Write", + bcberr & BCBERR_BESB_MON ? "Monitor Space" : + bcberr & BCBERR_BESB_IO ? "Internal CPU I/O Space" : + bcberr & BCBERR_BESB_EX ? "External I/O Bus" : + bcberr & BCBERR_BESB_OPEX ? "External Memory Bus" : + "On Chip Memory", + bcbear + ); + + printk(KERN_EMERG "- Detected by the %s\n", + bcberr&BCBERR_BESD ? "Bus Control Unit" : "Slave Bus"); + +#ifdef CONFIG_PCI +#define BRIDGEREGB(X) (*(volatile __u8 *)(0xBE040000 + (X))) +#define BRIDGEREGW(X) (*(volatile __u16 *)(0xBE040000 + (X))) +#define BRIDGEREGL(X) (*(volatile __u32 *)(0xBE040000 + (X))) + + printk(KERN_EMERG "- PCI Memory Paging Reg: %08x\n", + *(volatile __u32 *) (0xBFFFFFF4)); + printk(KERN_EMERG "- PCI Bridge Base Address 0: %08x\n", + BRIDGEREGL(PCI_BASE_ADDRESS_0)); + printk(KERN_EMERG "- PCI Bridge AMPCI Base Address: %08x\n", + BRIDGEREGL(0x48)); + printk(KERN_EMERG "- PCI Bridge Command: %04hx\n", + BRIDGEREGW(PCI_COMMAND)); + printk(KERN_EMERG "- PCI Bridge Status: %04hx\n", + BRIDGEREGW(PCI_STATUS)); + printk(KERN_EMERG "- PCI Bridge Int Status: %08hx\n", + BRIDGEREGL(0x4c)); +#endif + + printk(KERN_EMERG "\n"); + show_registers(regs); + + panic("Halted due to asynchronous I/O Bus Error\n"); +} + +/* + * handle an exception for which a handler has not yet been installed + */ +asmlinkage void uninitialised_exception(struct pt_regs *regs, + enum exception_code code) +{ + + /* see if gdbstub wants to deal with it */ +#ifdef CONFIG_GDBSTUB + if (gdbstub_intercept(regs, code)) + return; +#endif + + peripheral_leds_display_exception(code); + printk(KERN_EMERG "Uninitialised Exception 0x%04x\n", code & 0xFFFF); + show_registers(regs); + + for (;;) + continue; +} + +/* + * set an interrupt stub to jump to a handler + * ! NOTE: this does *not* flush the caches + */ +void __init __set_intr_stub(enum exception_code code, void *handler) +{ + unsigned long addr; + u8 *vector = (u8 *)(CONFIG_INTERRUPT_VECTOR_BASE + code); + + addr = (unsigned long) handler - (unsigned long) vector; + vector[0] = 0xdc; /* JMP handler */ + vector[1] = addr; + vector[2] = addr >> 8; + vector[3] = addr >> 16; + vector[4] = addr >> 24; + vector[5] = 0xcb; + vector[6] = 0xcb; + vector[7] = 0xcb; +} + +/* + * set an interrupt stub to jump to a handler + */ +void __init set_intr_stub(enum exception_code code, void *handler) +{ + unsigned long addr; + u8 *vector = (u8 *)(CONFIG_INTERRUPT_VECTOR_BASE + code); + + addr = (unsigned long) handler - (unsigned long) vector; + vector[0] = 0xdc; /* JMP handler */ + vector[1] = addr; + vector[2] = addr >> 8; + vector[3] = addr >> 16; + vector[4] = addr >> 24; + vector[5] = 0xcb; + vector[6] = 0xcb; + vector[7] = 0xcb; + + mn10300_dcache_flush_inv(); + mn10300_icache_inv(); +} + +/* + * set an interrupt stub to invoke the JTAG unit and then jump to a handler + */ +void __init set_jtag_stub(enum exception_code code, void *handler) +{ + unsigned long addr; + u8 *vector = (u8 *)(CONFIG_INTERRUPT_VECTOR_BASE + code); + + addr = (unsigned long) handler - ((unsigned long) vector + 1); + vector[0] = 0xff; /* PI to jump into JTAG debugger */ + vector[1] = 0xdc; /* jmp handler */ + vector[2] = addr; + vector[3] = addr >> 8; + vector[4] = addr >> 16; + vector[5] = addr >> 24; + vector[6] = 0xcb; + vector[7] = 0xcb; + + mn10300_dcache_flush_inv(); + flush_icache_range((unsigned long) vector, (unsigned long) vector + 8); +} + +/* + * initialise the exception table + */ +void __init trap_init(void) +{ + set_excp_vector(EXCEP_TRAP, trap); + set_excp_vector(EXCEP_ISTEP, istep); + set_excp_vector(EXCEP_IBREAK, ibreak); + set_excp_vector(EXCEP_OBREAK, obreak); + + set_excp_vector(EXCEP_PRIVINS, priv_op); + set_excp_vector(EXCEP_UNIMPINS, invalid_op); + set_excp_vector(EXCEP_UNIMPEXINS, invalid_exop); + set_excp_vector(EXCEP_MEMERR, mem_error); + set_excp_vector(EXCEP_MISALIGN, misalignment); + set_excp_vector(EXCEP_BUSERROR, bus_error); + set_excp_vector(EXCEP_ILLINSACC, insn_acc_error); + set_excp_vector(EXCEP_ILLDATACC, data_acc_error); + set_excp_vector(EXCEP_IOINSACC, insn_acc_error); + set_excp_vector(EXCEP_PRIVINSACC, insn_acc_error); + set_excp_vector(EXCEP_PRIVDATACC, data_acc_error); + set_excp_vector(EXCEP_DATINSACC, insn_acc_error); + set_excp_vector(EXCEP_FPU_DISABLED, fpu_disabled); + set_excp_vector(EXCEP_FPU_UNIMPINS, fpu_invalid_op); + set_excp_vector(EXCEP_FPU_OPERATION, fpu_exception); + + set_excp_vector(EXCEP_NMI, nmi); + + set_excp_vector(EXCEP_SYSCALL1, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL2, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL3, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL4, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL5, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL6, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL7, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL8, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL9, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL10, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL11, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL12, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL13, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL14, unsupported_syscall); + set_excp_vector(EXCEP_SYSCALL15, unsupported_syscall); +} + +/* + * determine if a program counter value is a valid bug address + */ +int is_valid_bugaddr(unsigned long pc) +{ + return pc >= PAGE_OFFSET; +} diff --git a/arch/mn10300/kernel/vmlinux.lds.S b/arch/mn10300/kernel/vmlinux.lds.S new file mode 100644 index 000000000000..a3e80f444f55 --- /dev/null +++ b/arch/mn10300/kernel/vmlinux.lds.S @@ -0,0 +1,159 @@ +/* MN10300 Main kernel linker script + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#define __VMLINUX_LDS__ +#include <asm-generic/vmlinux.lds.h> +#include <asm/thread_info.h> + +OUTPUT_FORMAT("elf32-am33lin", "elf32-am33lin", "elf32-am33lin") +OUTPUT_ARCH(mn10300) +ENTRY(_start) +jiffies = jiffies_64; +#ifndef CONFIG_MN10300_CURRENT_IN_E2 +current = __current; +#endif +SECTIONS +{ + . = CONFIG_KERNEL_TEXT_ADDRESS; + /* read-only */ + _stext = .; + _text = .; /* Text and read-only data */ + .text : { + *( + .text.head + .text + ) + TEXT_TEXT + SCHED_TEXT + LOCK_TEXT + KPROBES_TEXT + *(.fixup) + *(.gnu.warning) + } = 0xcb + + _etext = .; /* End of text section */ + + . = ALIGN(16); /* Exception table */ + __start___ex_table = .; + __ex_table : { *(__ex_table) } + __stop___ex_table = .; + + BUG_TABLE + + RODATA + + /* writeable */ + .data : { /* Data */ + DATA_DATA + CONSTRUCTORS + } + + . = ALIGN(4096); + __nosave_begin = .; + .data_nosave : { *(.data.nosave) } + . = ALIGN(4096); + __nosave_end = .; + + . = ALIGN(4096); + .data.page_aligned : { *(.data.idt) } + + . = ALIGN(32); + .data.cacheline_aligned : { *(.data.cacheline_aligned) } + + /* rarely changed data like cpu maps */ + . = ALIGN(32); + .data.read_mostly : AT(ADDR(.data.read_mostly)) { + *(.data.read_mostly) + _edata = .; /* End of data section */ + } + + . = ALIGN(THREAD_SIZE); /* init_task */ + .data.init_task : { *(.data.init_task) } + + /* might get freed after init */ + . = ALIGN(4096); + .smp_locks : AT(ADDR(.smp_locks) - LOAD_OFFSET) { + __smp_locks = .; + *(.smp_locks) + __smp_locks_end = .; + } + + /* will be freed after init */ + . = ALIGN(4096); /* Init code and data */ + __init_begin = .; + .init.text : { + _sinittext = .; + *(.init.text) + _einittext = .; + } + .init.data : { *(.init.data) } + . = ALIGN(16); + __setup_start = .; + .setup.init : { KEEP(*(.init.setup)) } + __setup_end = .; + + __initcall_start = .; + .initcall.init : { + INITCALLS + } + __initcall_end = .; + __con_initcall_start = .; + .con_initcall.init : { *(.con_initcall.init) } + __con_initcall_end = .; + + SECURITY_INIT + . = ALIGN(4); + __alt_instructions = .; + .altinstructions : { *(.altinstructions) } + __alt_instructions_end = .; + .altinstr_replacement : { *(.altinstr_replacement) } + /* .exit.text is discard at runtime, not link time, to deal with references + from .altinstructions and .eh_frame */ + .exit.text : { *(.exit.text) } + .exit.data : { *(.exit.data) } + +#ifdef CONFIG_BLK_DEV_INITRD + . = ALIGN(4096); + __initramfs_start = .; + .init.ramfs : { *(.init.ramfs) } + __initramfs_end = .; +#endif + + . = ALIGN(32); + __per_cpu_start = .; + .data.percpu : { *(.data.percpu) } + __per_cpu_end = .; + . = ALIGN(4096); + __init_end = .; + /* freed after init ends here */ + + __bss_start = .; /* BSS */ + .bss : { + *(.bss.page_aligned) + *(.bss) + } + . = ALIGN(4); + __bss_stop = .; + + _end = . ; + + /* This is where the kernel creates the early boot page tables */ + . = ALIGN(4096); + pg0 = .; + + /* Sections to be discarded */ + /DISCARD/ : { + *(.exitcall.exit) + } + + STABS_DEBUG + + DWARF_DEBUG +} |