diff options
Diffstat (limited to 'arch/openrisc/kernel')
-rw-r--r-- | arch/openrisc/kernel/.gitignore | 1 | ||||
-rw-r--r-- | arch/openrisc/kernel/Makefile | 17 | ||||
-rw-r--r-- | arch/openrisc/kernel/asm-offsets.c | 66 | ||||
-rw-r--r-- | arch/openrisc/kernel/dma.c | 160 | ||||
-rw-r--r-- | arch/openrisc/kernel/entry.S | 1220 | ||||
-rw-r--r-- | arch/openrisc/kernel/head.S | 1757 | ||||
-rw-r--r-- | arch/openrisc/kernel/irq.c | 47 | ||||
-rw-r--r-- | arch/openrisc/kernel/module.c | 70 | ||||
-rw-r--r-- | arch/openrisc/kernel/or32_ksyms.c | 51 | ||||
-rw-r--r-- | arch/openrisc/kernel/process.c | 284 | ||||
-rw-r--r-- | arch/openrisc/kernel/prom.c | 32 | ||||
-rw-r--r-- | arch/openrisc/kernel/ptrace.c | 205 | ||||
-rw-r--r-- | arch/openrisc/kernel/setup.c | 415 | ||||
-rw-r--r-- | arch/openrisc/kernel/signal.c | 326 | ||||
-rw-r--r-- | arch/openrisc/kernel/smp.c | 259 | ||||
-rw-r--r-- | arch/openrisc/kernel/stacktrace.c | 100 | ||||
-rw-r--r-- | arch/openrisc/kernel/sync-timer.c | 120 | ||||
-rw-r--r-- | arch/openrisc/kernel/sys_call_table.c | 28 | ||||
-rw-r--r-- | arch/openrisc/kernel/time.c | 176 | ||||
-rw-r--r-- | arch/openrisc/kernel/traps.c | 477 | ||||
-rw-r--r-- | arch/openrisc/kernel/unwinder.c | 105 | ||||
-rw-r--r-- | arch/openrisc/kernel/vmlinux.h | 9 | ||||
-rw-r--r-- | arch/openrisc/kernel/vmlinux.lds.S | 126 |
23 files changed, 6051 insertions, 0 deletions
diff --git a/arch/openrisc/kernel/.gitignore b/arch/openrisc/kernel/.gitignore new file mode 100644 index 000000000..c5f676c3c --- /dev/null +++ b/arch/openrisc/kernel/.gitignore @@ -0,0 +1 @@ +vmlinux.lds diff --git a/arch/openrisc/kernel/Makefile b/arch/openrisc/kernel/Makefile new file mode 100644 index 000000000..2d172e79f --- /dev/null +++ b/arch/openrisc/kernel/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux kernel. +# + +extra-y := head.o vmlinux.lds + +obj-y := setup.o or32_ksyms.o process.o dma.o \ + traps.o time.o irq.o entry.o ptrace.o signal.o \ + sys_call_table.o unwinder.o + +obj-$(CONFIG_SMP) += smp.o sync-timer.o +obj-$(CONFIG_STACKTRACE) += stacktrace.o +obj-$(CONFIG_MODULES) += module.o +obj-$(CONFIG_OF) += prom.o + +clean: diff --git a/arch/openrisc/kernel/asm-offsets.c b/arch/openrisc/kernel/asm-offsets.c new file mode 100644 index 000000000..ddb736855 --- /dev/null +++ b/arch/openrisc/kernel/asm-offsets.c @@ -0,0 +1,66 @@ +/* + * OpenRISC asm-offsets.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This program is used to generate definitions needed by + * assembly language modules. + * + * We use the technique used in the OSF Mach kernel code: + * generate asm statements containing #defines, + * compile this file to assembler, and then extract the + * #defines from the assembly-language output. + */ + +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/io.h> +#include <linux/thread_info.h> +#include <linux/kbuild.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/processor.h> + +int main(void) +{ + /* offsets into the task_struct */ + DEFINE(TASK_STATE, offsetof(struct task_struct, state)); + DEFINE(TASK_FLAGS, offsetof(struct task_struct, flags)); + DEFINE(TASK_PTRACE, offsetof(struct task_struct, ptrace)); + DEFINE(TASK_THREAD, offsetof(struct task_struct, thread)); + DEFINE(TASK_MM, offsetof(struct task_struct, mm)); + DEFINE(TASK_ACTIVE_MM, offsetof(struct task_struct, active_mm)); + + /* offsets into thread_info */ + DEFINE(TI_TASK, offsetof(struct thread_info, task)); + DEFINE(TI_FLAGS, offsetof(struct thread_info, flags)); + DEFINE(TI_PREEMPT, offsetof(struct thread_info, preempt_count)); + DEFINE(TI_KSP, offsetof(struct thread_info, ksp)); + + DEFINE(PT_SIZE, sizeof(struct pt_regs)); + + /* Interrupt register frame */ + DEFINE(STACK_FRAME_OVERHEAD, STACK_FRAME_OVERHEAD); + DEFINE(INT_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs)); + + DEFINE(NUM_USER_SEGMENTS, TASK_SIZE >> 28); + return 0; +} diff --git a/arch/openrisc/kernel/dma.c b/arch/openrisc/kernel/dma.c new file mode 100644 index 000000000..159336adf --- /dev/null +++ b/arch/openrisc/kernel/dma.c @@ -0,0 +1,160 @@ +/* + * OpenRISC Linux + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * DMA mapping callbacks... + * As alloc_coherent is the only DMA callback being used currently, that's + * the only thing implemented properly. The rest need looking into... + */ + +#include <linux/dma-noncoherent.h> + +#include <asm/cpuinfo.h> +#include <asm/spr_defs.h> +#include <asm/tlbflush.h> + +static int +page_set_nocache(pte_t *pte, unsigned long addr, + unsigned long next, struct mm_walk *walk) +{ + unsigned long cl; + struct cpuinfo_or1k *cpuinfo = &cpuinfo_or1k[smp_processor_id()]; + + pte_val(*pte) |= _PAGE_CI; + + /* + * Flush the page out of the TLB so that the new page flags get + * picked up next time there's an access + */ + flush_tlb_page(NULL, addr); + + /* Flush page out of dcache */ + for (cl = __pa(addr); cl < __pa(next); cl += cpuinfo->dcache_block_size) + mtspr(SPR_DCBFR, cl); + + return 0; +} + +static int +page_clear_nocache(pte_t *pte, unsigned long addr, + unsigned long next, struct mm_walk *walk) +{ + pte_val(*pte) &= ~_PAGE_CI; + + /* + * Flush the page out of the TLB so that the new page flags get + * picked up next time there's an access + */ + flush_tlb_page(NULL, addr); + + return 0; +} + +/* + * Alloc "coherent" memory, which for OpenRISC means simply uncached. + * + * This function effectively just calls __get_free_pages, sets the + * cache-inhibit bit on those pages, and makes sure that the pages are + * flushed out of the cache before they are used. + * + * If the NON_CONSISTENT attribute is set, then this function just + * returns "normal", cachable memory. + * + * There are additional flags WEAK_ORDERING and WRITE_COMBINE to take + * into consideration here, too. All current known implementations of + * the OR1K support only strongly ordered memory accesses, so that flag + * is being ignored for now; uncached but write-combined memory is a + * missing feature of the OR1K. + */ +void * +arch_dma_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle, + gfp_t gfp, unsigned long attrs) +{ + unsigned long va; + void *page; + struct mm_walk walk = { + .pte_entry = page_set_nocache, + .mm = &init_mm + }; + + page = alloc_pages_exact(size, gfp); + if (!page) + return NULL; + + /* This gives us the real physical address of the first page. */ + *dma_handle = __pa(page); + + va = (unsigned long)page; + + if ((attrs & DMA_ATTR_NON_CONSISTENT) == 0) { + /* + * We need to iterate through the pages, clearing the dcache for + * them and setting the cache-inhibit bit. + */ + if (walk_page_range(va, va + size, &walk)) { + free_pages_exact(page, size); + return NULL; + } + } + + return (void *)va; +} + +void +arch_dma_free(struct device *dev, size_t size, void *vaddr, + dma_addr_t dma_handle, unsigned long attrs) +{ + unsigned long va = (unsigned long)vaddr; + struct mm_walk walk = { + .pte_entry = page_clear_nocache, + .mm = &init_mm + }; + + if ((attrs & DMA_ATTR_NON_CONSISTENT) == 0) { + /* walk_page_range shouldn't be able to fail here */ + WARN_ON(walk_page_range(va, va + size, &walk)); + } + + free_pages_exact(vaddr, size); +} + +void arch_sync_dma_for_device(struct device *dev, phys_addr_t addr, size_t size, + enum dma_data_direction dir) +{ + unsigned long cl; + struct cpuinfo_or1k *cpuinfo = &cpuinfo_or1k[smp_processor_id()]; + + switch (dir) { + case DMA_TO_DEVICE: + /* Flush the dcache for the requested range */ + for (cl = addr; cl < addr + size; + cl += cpuinfo->dcache_block_size) + mtspr(SPR_DCBFR, cl); + break; + case DMA_FROM_DEVICE: + /* Invalidate the dcache for the requested range */ + for (cl = addr; cl < addr + size; + cl += cpuinfo->dcache_block_size) + mtspr(SPR_DCBIR, cl); + break; + default: + /* + * NOTE: If dir == DMA_BIDIRECTIONAL then there's no need to + * flush nor invalidate the cache here as the area will need + * to be manually synced anyway. + */ + break; + } +} diff --git a/arch/openrisc/kernel/entry.S b/arch/openrisc/kernel/entry.S new file mode 100644 index 000000000..c2c3ce8a0 --- /dev/null +++ b/arch/openrisc/kernel/entry.S @@ -0,0 +1,1220 @@ +/* + * OpenRISC entry.S + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2005 Gyorgy Jeney <nog@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/linkage.h> + +#include <asm/processor.h> +#include <asm/unistd.h> +#include <asm/thread_info.h> +#include <asm/errno.h> +#include <asm/spr_defs.h> +#include <asm/page.h> +#include <asm/mmu.h> +#include <asm/pgtable.h> +#include <asm/asm-offsets.h> + +#define DISABLE_INTERRUPTS(t1,t2) \ + l.mfspr t2,r0,SPR_SR ;\ + l.movhi t1,hi(~(SPR_SR_IEE|SPR_SR_TEE)) ;\ + l.ori t1,t1,lo(~(SPR_SR_IEE|SPR_SR_TEE)) ;\ + l.and t2,t2,t1 ;\ + l.mtspr r0,t2,SPR_SR + +#define ENABLE_INTERRUPTS(t1) \ + l.mfspr t1,r0,SPR_SR ;\ + l.ori t1,t1,lo(SPR_SR_IEE|SPR_SR_TEE) ;\ + l.mtspr r0,t1,SPR_SR + +/* =========================================================[ macros ]=== */ + +#ifdef CONFIG_TRACE_IRQFLAGS +/* + * Trace irq on/off creating a stack frame. + */ +#define TRACE_IRQS_OP(trace_op) \ + l.sw -8(r1),r2 /* store frame pointer */ ;\ + l.sw -4(r1),r9 /* store return address */ ;\ + l.addi r2,r1,0 /* move sp to fp */ ;\ + l.jal trace_op ;\ + l.addi r1,r1,-8 ;\ + l.ori r1,r2,0 /* restore sp */ ;\ + l.lwz r9,-4(r1) /* restore return address */ ;\ + l.lwz r2,-8(r1) /* restore fp */ ;\ +/* + * Trace irq on/off and save registers we need that would otherwise be + * clobbered. + */ +#define TRACE_IRQS_SAVE(t1,trace_op) \ + l.sw -12(r1),t1 /* save extra reg */ ;\ + l.sw -8(r1),r2 /* store frame pointer */ ;\ + l.sw -4(r1),r9 /* store return address */ ;\ + l.addi r2,r1,0 /* move sp to fp */ ;\ + l.jal trace_op ;\ + l.addi r1,r1,-12 ;\ + l.ori r1,r2,0 /* restore sp */ ;\ + l.lwz r9,-4(r1) /* restore return address */ ;\ + l.lwz r2,-8(r1) /* restore fp */ ;\ + l.lwz t1,-12(r1) /* restore extra reg */ + +#define TRACE_IRQS_OFF TRACE_IRQS_OP(trace_hardirqs_off) +#define TRACE_IRQS_ON TRACE_IRQS_OP(trace_hardirqs_on) +#define TRACE_IRQS_ON_SYSCALL \ + TRACE_IRQS_SAVE(r10,trace_hardirqs_on) ;\ + l.lwz r3,PT_GPR3(r1) ;\ + l.lwz r4,PT_GPR4(r1) ;\ + l.lwz r5,PT_GPR5(r1) ;\ + l.lwz r6,PT_GPR6(r1) ;\ + l.lwz r7,PT_GPR7(r1) ;\ + l.lwz r8,PT_GPR8(r1) ;\ + l.lwz r11,PT_GPR11(r1) +#define TRACE_IRQS_OFF_ENTRY \ + l.lwz r5,PT_SR(r1) ;\ + l.andi r3,r5,(SPR_SR_IEE|SPR_SR_TEE) ;\ + l.sfeq r5,r0 /* skip trace if irqs were already off */;\ + l.bf 1f ;\ + l.nop ;\ + TRACE_IRQS_SAVE(r4,trace_hardirqs_off) ;\ +1: +#else +#define TRACE_IRQS_OFF +#define TRACE_IRQS_ON +#define TRACE_IRQS_OFF_ENTRY +#define TRACE_IRQS_ON_SYSCALL +#endif + +/* + * We need to disable interrupts at beginning of RESTORE_ALL + * since interrupt might come in after we've loaded EPC return address + * and overwrite EPC with address somewhere in RESTORE_ALL + * which is of course wrong! + */ + +#define RESTORE_ALL \ + DISABLE_INTERRUPTS(r3,r4) ;\ + l.lwz r3,PT_PC(r1) ;\ + l.mtspr r0,r3,SPR_EPCR_BASE ;\ + l.lwz r3,PT_SR(r1) ;\ + l.mtspr r0,r3,SPR_ESR_BASE ;\ + l.lwz r2,PT_GPR2(r1) ;\ + l.lwz r3,PT_GPR3(r1) ;\ + l.lwz r4,PT_GPR4(r1) ;\ + l.lwz r5,PT_GPR5(r1) ;\ + l.lwz r6,PT_GPR6(r1) ;\ + l.lwz r7,PT_GPR7(r1) ;\ + l.lwz r8,PT_GPR8(r1) ;\ + l.lwz r9,PT_GPR9(r1) ;\ + l.lwz r10,PT_GPR10(r1) ;\ + l.lwz r11,PT_GPR11(r1) ;\ + l.lwz r12,PT_GPR12(r1) ;\ + l.lwz r13,PT_GPR13(r1) ;\ + l.lwz r14,PT_GPR14(r1) ;\ + l.lwz r15,PT_GPR15(r1) ;\ + l.lwz r16,PT_GPR16(r1) ;\ + l.lwz r17,PT_GPR17(r1) ;\ + l.lwz r18,PT_GPR18(r1) ;\ + l.lwz r19,PT_GPR19(r1) ;\ + l.lwz r20,PT_GPR20(r1) ;\ + l.lwz r21,PT_GPR21(r1) ;\ + l.lwz r22,PT_GPR22(r1) ;\ + l.lwz r23,PT_GPR23(r1) ;\ + l.lwz r24,PT_GPR24(r1) ;\ + l.lwz r25,PT_GPR25(r1) ;\ + l.lwz r26,PT_GPR26(r1) ;\ + l.lwz r27,PT_GPR27(r1) ;\ + l.lwz r28,PT_GPR28(r1) ;\ + l.lwz r29,PT_GPR29(r1) ;\ + l.lwz r30,PT_GPR30(r1) ;\ + l.lwz r31,PT_GPR31(r1) ;\ + l.lwz r1,PT_SP(r1) ;\ + l.rfe + + +#define EXCEPTION_ENTRY(handler) \ + .global handler ;\ +handler: ;\ + /* r1, EPCR, ESR a already saved */ ;\ + l.sw PT_GPR2(r1),r2 ;\ + l.sw PT_GPR3(r1),r3 ;\ + /* r4 already save */ ;\ + l.sw PT_GPR5(r1),r5 ;\ + l.sw PT_GPR6(r1),r6 ;\ + l.sw PT_GPR7(r1),r7 ;\ + l.sw PT_GPR8(r1),r8 ;\ + l.sw PT_GPR9(r1),r9 ;\ + /* r10 already saved */ ;\ + l.sw PT_GPR11(r1),r11 ;\ + /* r12 already saved */ ;\ + l.sw PT_GPR13(r1),r13 ;\ + l.sw PT_GPR14(r1),r14 ;\ + l.sw PT_GPR15(r1),r15 ;\ + l.sw PT_GPR16(r1),r16 ;\ + l.sw PT_GPR17(r1),r17 ;\ + l.sw PT_GPR18(r1),r18 ;\ + l.sw PT_GPR19(r1),r19 ;\ + l.sw PT_GPR20(r1),r20 ;\ + l.sw PT_GPR21(r1),r21 ;\ + l.sw PT_GPR22(r1),r22 ;\ + l.sw PT_GPR23(r1),r23 ;\ + l.sw PT_GPR24(r1),r24 ;\ + l.sw PT_GPR25(r1),r25 ;\ + l.sw PT_GPR26(r1),r26 ;\ + l.sw PT_GPR27(r1),r27 ;\ + l.sw PT_GPR28(r1),r28 ;\ + l.sw PT_GPR29(r1),r29 ;\ + /* r30 already save */ ;\ +/* l.sw PT_GPR30(r1),r30*/ ;\ + l.sw PT_GPR31(r1),r31 ;\ + TRACE_IRQS_OFF_ENTRY ;\ + /* Store -1 in orig_gpr11 for non-syscall exceptions */ ;\ + l.addi r30,r0,-1 ;\ + l.sw PT_ORIG_GPR11(r1),r30 + +#define UNHANDLED_EXCEPTION(handler,vector) \ + .global handler ;\ +handler: ;\ + /* r1, EPCR, ESR already saved */ ;\ + l.sw PT_GPR2(r1),r2 ;\ + l.sw PT_GPR3(r1),r3 ;\ + l.sw PT_GPR5(r1),r5 ;\ + l.sw PT_GPR6(r1),r6 ;\ + l.sw PT_GPR7(r1),r7 ;\ + l.sw PT_GPR8(r1),r8 ;\ + l.sw PT_GPR9(r1),r9 ;\ + /* r10 already saved */ ;\ + l.sw PT_GPR11(r1),r11 ;\ + /* r12 already saved */ ;\ + l.sw PT_GPR13(r1),r13 ;\ + l.sw PT_GPR14(r1),r14 ;\ + l.sw PT_GPR15(r1),r15 ;\ + l.sw PT_GPR16(r1),r16 ;\ + l.sw PT_GPR17(r1),r17 ;\ + l.sw PT_GPR18(r1),r18 ;\ + l.sw PT_GPR19(r1),r19 ;\ + l.sw PT_GPR20(r1),r20 ;\ + l.sw PT_GPR21(r1),r21 ;\ + l.sw PT_GPR22(r1),r22 ;\ + l.sw PT_GPR23(r1),r23 ;\ + l.sw PT_GPR24(r1),r24 ;\ + l.sw PT_GPR25(r1),r25 ;\ + l.sw PT_GPR26(r1),r26 ;\ + l.sw PT_GPR27(r1),r27 ;\ + l.sw PT_GPR28(r1),r28 ;\ + l.sw PT_GPR29(r1),r29 ;\ + /* r31 already saved */ ;\ + l.sw PT_GPR30(r1),r30 ;\ +/* l.sw PT_GPR31(r1),r31 */ ;\ + /* Store -1 in orig_gpr11 for non-syscall exceptions */ ;\ + l.addi r30,r0,-1 ;\ + l.sw PT_ORIG_GPR11(r1),r30 ;\ + l.addi r3,r1,0 ;\ + /* r4 is exception EA */ ;\ + l.addi r5,r0,vector ;\ + l.jal unhandled_exception ;\ + l.nop ;\ + l.j _ret_from_exception ;\ + l.nop + +/* clobbers 'reg' */ +#define CLEAR_LWA_FLAG(reg) \ + l.movhi reg,hi(lwa_flag) ;\ + l.ori reg,reg,lo(lwa_flag) ;\ + l.sw 0(reg),r0 +/* + * NOTE: one should never assume that SPR_EPC, SPR_ESR, SPR_EEAR + * contain the same values as when exception we're handling + * occured. in fact they never do. if you need them use + * values saved on stack (for SPR_EPC, SPR_ESR) or content + * of r4 (for SPR_EEAR). for details look at EXCEPTION_HANDLE() + * in 'arch/openrisc/kernel/head.S' + */ + +/* =====================================================[ exceptions] === */ + +/* ---[ 0x100: RESET exception ]----------------------------------------- */ + +EXCEPTION_ENTRY(_tng_kernel_start) + l.jal _start + l.andi r0,r0,0 + +/* ---[ 0x200: BUS exception ]------------------------------------------- */ + +EXCEPTION_ENTRY(_bus_fault_handler) + CLEAR_LWA_FLAG(r3) + /* r4: EA of fault (set by EXCEPTION_HANDLE) */ + l.jal do_bus_fault + l.addi r3,r1,0 /* pt_regs */ + + l.j _ret_from_exception + l.nop + +/* ---[ 0x300: Data Page Fault exception ]------------------------------- */ +EXCEPTION_ENTRY(_dtlb_miss_page_fault_handler) + CLEAR_LWA_FLAG(r3) + l.and r5,r5,r0 + l.j 1f + l.nop + +EXCEPTION_ENTRY(_data_page_fault_handler) + CLEAR_LWA_FLAG(r3) + /* set up parameters for do_page_fault */ + l.ori r5,r0,0x300 // exception vector +1: + l.addi r3,r1,0 // pt_regs + /* r4 set be EXCEPTION_HANDLE */ // effective address of fault + +#ifdef CONFIG_OPENRISC_NO_SPR_SR_DSX + l.lwz r6,PT_PC(r3) // address of an offending insn + l.lwz r6,0(r6) // instruction that caused pf + + l.srli r6,r6,26 // check opcode for jump insn + l.sfeqi r6,0 // l.j + l.bf 8f + l.sfeqi r6,1 // l.jal + l.bf 8f + l.sfeqi r6,3 // l.bnf + l.bf 8f + l.sfeqi r6,4 // l.bf + l.bf 8f + l.sfeqi r6,0x11 // l.jr + l.bf 8f + l.sfeqi r6,0x12 // l.jalr + l.bf 8f + l.nop + + l.j 9f + l.nop + +8: // offending insn is in delay slot + l.lwz r6,PT_PC(r3) // address of an offending insn + l.addi r6,r6,4 + l.lwz r6,0(r6) // instruction that caused pf + l.srli r6,r6,26 // get opcode +9: // offending instruction opcode loaded in r6 + +#else + + l.mfspr r6,r0,SPR_SR // SR + l.andi r6,r6,SPR_SR_DSX // check for delay slot exception + l.sfne r6,r0 // exception happened in delay slot + l.bnf 7f + l.lwz r6,PT_PC(r3) // address of an offending insn + + l.addi r6,r6,4 // offending insn is in delay slot +7: + l.lwz r6,0(r6) // instruction that caused pf + l.srli r6,r6,26 // check opcode for write access +#endif + + l.sfgeui r6,0x33 // check opcode for write access + l.bnf 1f + l.sfleui r6,0x37 + l.bnf 1f + l.ori r6,r0,0x1 // write access + l.j 2f + l.nop +1: l.ori r6,r0,0x0 // !write access +2: + + /* call fault.c handler in or32/mm/fault.c */ + l.jal do_page_fault + l.nop + l.j _ret_from_exception + l.nop + +/* ---[ 0x400: Insn Page Fault exception ]------------------------------- */ +EXCEPTION_ENTRY(_itlb_miss_page_fault_handler) + CLEAR_LWA_FLAG(r3) + l.and r5,r5,r0 + l.j 1f + l.nop + +EXCEPTION_ENTRY(_insn_page_fault_handler) + CLEAR_LWA_FLAG(r3) + /* set up parameters for do_page_fault */ + l.ori r5,r0,0x400 // exception vector +1: + l.addi r3,r1,0 // pt_regs + /* r4 set be EXCEPTION_HANDLE */ // effective address of fault + l.ori r6,r0,0x0 // !write access + + /* call fault.c handler in or32/mm/fault.c */ + l.jal do_page_fault + l.nop + l.j _ret_from_exception + l.nop + + +/* ---[ 0x500: Timer exception ]----------------------------------------- */ + +EXCEPTION_ENTRY(_timer_handler) + CLEAR_LWA_FLAG(r3) + l.jal timer_interrupt + l.addi r3,r1,0 /* pt_regs */ + + l.j _ret_from_intr + l.nop + +/* ---[ 0x600: Alignment exception ]-------------------------------------- */ + +EXCEPTION_ENTRY(_alignment_handler) + CLEAR_LWA_FLAG(r3) + /* r4: EA of fault (set by EXCEPTION_HANDLE) */ + l.jal do_unaligned_access + l.addi r3,r1,0 /* pt_regs */ + + l.j _ret_from_exception + l.nop + +#if 0 +EXCEPTION_ENTRY(_alignment_handler) +// l.mfspr r2,r0,SPR_EEAR_BASE /* Load the effective address */ + l.addi r2,r4,0 +// l.mfspr r5,r0,SPR_EPCR_BASE /* Load the insn address */ + l.lwz r5,PT_PC(r1) + + l.lwz r3,0(r5) /* Load insn */ + l.srli r4,r3,26 /* Shift left to get the insn opcode */ + + l.sfeqi r4,0x00 /* Check if the load/store insn is in delay slot */ + l.bf jmp + l.sfeqi r4,0x01 + l.bf jmp + l.sfeqi r4,0x03 + l.bf jmp + l.sfeqi r4,0x04 + l.bf jmp + l.sfeqi r4,0x11 + l.bf jr + l.sfeqi r4,0x12 + l.bf jr + l.nop + l.j 1f + l.addi r5,r5,4 /* Increment PC to get return insn address */ + +jmp: + l.slli r4,r3,6 /* Get the signed extended jump length */ + l.srai r4,r4,4 + + l.lwz r3,4(r5) /* Load the real load/store insn */ + + l.add r5,r5,r4 /* Calculate jump target address */ + + l.j 1f + l.srli r4,r3,26 /* Shift left to get the insn opcode */ + +jr: + l.slli r4,r3,9 /* Shift to get the reg nb */ + l.andi r4,r4,0x7c + + l.lwz r3,4(r5) /* Load the real load/store insn */ + + l.add r4,r4,r1 /* Load the jump register value from the stack */ + l.lwz r5,0(r4) + + l.srli r4,r3,26 /* Shift left to get the insn opcode */ + + +1: +// l.mtspr r0,r5,SPR_EPCR_BASE + l.sw PT_PC(r1),r5 + + l.sfeqi r4,0x26 + l.bf lhs + l.sfeqi r4,0x25 + l.bf lhz + l.sfeqi r4,0x22 + l.bf lws + l.sfeqi r4,0x21 + l.bf lwz + l.sfeqi r4,0x37 + l.bf sh + l.sfeqi r4,0x35 + l.bf sw + l.nop + +1: l.j 1b /* I don't know what to do */ + l.nop + +lhs: l.lbs r5,0(r2) + l.slli r5,r5,8 + l.lbz r6,1(r2) + l.or r5,r5,r6 + l.srli r4,r3,19 + l.andi r4,r4,0x7c + l.add r4,r4,r1 + l.j align_end + l.sw 0(r4),r5 + +lhz: l.lbz r5,0(r2) + l.slli r5,r5,8 + l.lbz r6,1(r2) + l.or r5,r5,r6 + l.srli r4,r3,19 + l.andi r4,r4,0x7c + l.add r4,r4,r1 + l.j align_end + l.sw 0(r4),r5 + +lws: l.lbs r5,0(r2) + l.slli r5,r5,24 + l.lbz r6,1(r2) + l.slli r6,r6,16 + l.or r5,r5,r6 + l.lbz r6,2(r2) + l.slli r6,r6,8 + l.or r5,r5,r6 + l.lbz r6,3(r2) + l.or r5,r5,r6 + l.srli r4,r3,19 + l.andi r4,r4,0x7c + l.add r4,r4,r1 + l.j align_end + l.sw 0(r4),r5 + +lwz: l.lbz r5,0(r2) + l.slli r5,r5,24 + l.lbz r6,1(r2) + l.slli r6,r6,16 + l.or r5,r5,r6 + l.lbz r6,2(r2) + l.slli r6,r6,8 + l.or r5,r5,r6 + l.lbz r6,3(r2) + l.or r5,r5,r6 + l.srli r4,r3,19 + l.andi r4,r4,0x7c + l.add r4,r4,r1 + l.j align_end + l.sw 0(r4),r5 + +sh: + l.srli r4,r3,9 + l.andi r4,r4,0x7c + l.add r4,r4,r1 + l.lwz r5,0(r4) + l.sb 1(r2),r5 + l.srli r5,r5,8 + l.j align_end + l.sb 0(r2),r5 + +sw: + l.srli r4,r3,9 + l.andi r4,r4,0x7c + l.add r4,r4,r1 + l.lwz r5,0(r4) + l.sb 3(r2),r5 + l.srli r5,r5,8 + l.sb 2(r2),r5 + l.srli r5,r5,8 + l.sb 1(r2),r5 + l.srli r5,r5,8 + l.j align_end + l.sb 0(r2),r5 + +align_end: + l.j _ret_from_intr + l.nop +#endif + +/* ---[ 0x700: Illegal insn exception ]---------------------------------- */ + +EXCEPTION_ENTRY(_illegal_instruction_handler) + /* r4: EA of fault (set by EXCEPTION_HANDLE) */ + l.jal do_illegal_instruction + l.addi r3,r1,0 /* pt_regs */ + + l.j _ret_from_exception + l.nop + +/* ---[ 0x800: External interrupt exception ]---------------------------- */ + +EXCEPTION_ENTRY(_external_irq_handler) +#ifdef CONFIG_OPENRISC_ESR_EXCEPTION_BUG_CHECK + l.lwz r4,PT_SR(r1) // were interrupts enabled ? + l.andi r4,r4,SPR_SR_IEE + l.sfeqi r4,0 + l.bnf 1f // ext irq enabled, all ok. + l.nop + +#ifdef CONFIG_PRINTK + l.addi r1,r1,-0x8 + l.movhi r3,hi(42f) + l.ori r3,r3,lo(42f) + l.sw 0x0(r1),r3 + l.jal printk + l.sw 0x4(r1),r4 + l.addi r1,r1,0x8 + + .section .rodata, "a" +42: + .string "\n\rESR interrupt bug: in _external_irq_handler (ESR %x)\n\r" + .align 4 + .previous +#endif + + l.ori r4,r4,SPR_SR_IEE // fix the bug +// l.sw PT_SR(r1),r4 +1: +#endif + CLEAR_LWA_FLAG(r3) + l.addi r3,r1,0 + l.movhi r8,hi(do_IRQ) + l.ori r8,r8,lo(do_IRQ) + l.jalr r8 + l.nop + l.j _ret_from_intr + l.nop + +/* ---[ 0x900: DTLB miss exception ]------------------------------------- */ + + +/* ---[ 0xa00: ITLB miss exception ]------------------------------------- */ + + +/* ---[ 0xb00: Range exception ]----------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0xb00,0xb00) + +/* ---[ 0xc00: Syscall exception ]--------------------------------------- */ + +/* + * Syscalls are a special type of exception in that they are + * _explicitly_ invoked by userspace and can therefore be + * held to conform to the same ABI as normal functions with + * respect to whether registers are preserved across the call + * or not. + */ + +/* Upon syscall entry we just save the callee-saved registers + * and not the call-clobbered ones. + */ + +_string_syscall_return: + .string "syscall return %ld \n\r\0" + .align 4 + +ENTRY(_sys_call_handler) + /* r1, EPCR, ESR a already saved */ + l.sw PT_GPR2(r1),r2 + /* r3-r8 must be saved because syscall restart relies + * on us being able to restart the syscall args... technically + * they should be clobbered, otherwise + */ + l.sw PT_GPR3(r1),r3 + /* + * r4 already saved + * r4 holds the EEAR address of the fault, use it as screatch reg and + * then load the original r4 + */ + CLEAR_LWA_FLAG(r4) + l.lwz r4,PT_GPR4(r1) + l.sw PT_GPR5(r1),r5 + l.sw PT_GPR6(r1),r6 + l.sw PT_GPR7(r1),r7 + l.sw PT_GPR8(r1),r8 + l.sw PT_GPR9(r1),r9 + /* r10 already saved */ + l.sw PT_GPR11(r1),r11 + /* orig_gpr11 must be set for syscalls */ + l.sw PT_ORIG_GPR11(r1),r11 + /* r12,r13 already saved */ + + /* r14-r28 (even) aren't touched by the syscall fast path below + * so we don't need to save them. However, the functions that return + * to userspace via a call to switch() DO need to save these because + * switch() effectively clobbers them... saving these registers for + * such functions is handled in their syscall wrappers (see fork, vfork, + * and clone, below). + + /* r30 is the only register we clobber in the fast path */ + /* r30 already saved */ +/* l.sw PT_GPR30(r1),r30 */ + +_syscall_check_trace_enter: + /* syscalls run with interrupts enabled */ + TRACE_IRQS_ON_SYSCALL + ENABLE_INTERRUPTS(r29) // enable interrupts, r29 is temp + + /* If TIF_SYSCALL_TRACE is set, then we want to do syscall tracing */ + l.lwz r30,TI_FLAGS(r10) + l.andi r30,r30,_TIF_SYSCALL_TRACE + l.sfne r30,r0 + l.bf _syscall_trace_enter + l.nop + +_syscall_check: + /* Ensure that the syscall number is reasonable */ + l.sfgeui r11,__NR_syscalls + l.bf _syscall_badsys + l.nop + +_syscall_call: + l.movhi r29,hi(sys_call_table) + l.ori r29,r29,lo(sys_call_table) + l.slli r11,r11,2 + l.add r29,r29,r11 + l.lwz r29,0(r29) + + l.jalr r29 + l.nop + +_syscall_return: + /* All syscalls return here... just pay attention to ret_from_fork + * which does it in a round-about way. + */ + l.sw PT_GPR11(r1),r11 // save return value + +#if 0 +_syscall_debug: + l.movhi r3,hi(_string_syscall_return) + l.ori r3,r3,lo(_string_syscall_return) + l.ori r27,r0,1 + l.sw -4(r1),r27 + l.sw -8(r1),r11 + l.addi r1,r1,-8 + l.movhi r27,hi(printk) + l.ori r27,r27,lo(printk) + l.jalr r27 + l.nop + l.addi r1,r1,8 +#endif + +_syscall_check_trace_leave: + /* r30 is a callee-saved register so this should still hold the + * _TIF_SYSCALL_TRACE flag from _syscall_check_trace_enter above... + * _syscall_trace_leave expects syscall result to be in pt_regs->r11. + */ + l.sfne r30,r0 + l.bf _syscall_trace_leave + l.nop + +/* This is where the exception-return code begins... interrupts need to be + * disabled the rest of the way here because we can't afford to miss any + * interrupts that set NEED_RESCHED or SIGNALPENDING... really true? */ + +_syscall_check_work: + /* Here we need to disable interrupts */ + DISABLE_INTERRUPTS(r27,r29) + TRACE_IRQS_OFF + l.lwz r30,TI_FLAGS(r10) + l.andi r30,r30,_TIF_WORK_MASK + l.sfne r30,r0 + + l.bnf _syscall_resume_userspace + l.nop + + /* Work pending follows a different return path, so we need to + * make sure that all the call-saved registers get into pt_regs + * before branching... + */ + l.sw PT_GPR14(r1),r14 + l.sw PT_GPR16(r1),r16 + l.sw PT_GPR18(r1),r18 + l.sw PT_GPR20(r1),r20 + l.sw PT_GPR22(r1),r22 + l.sw PT_GPR24(r1),r24 + l.sw PT_GPR26(r1),r26 + l.sw PT_GPR28(r1),r28 + + /* _work_pending needs to be called with interrupts disabled */ + l.j _work_pending + l.nop + +_syscall_resume_userspace: +// ENABLE_INTERRUPTS(r29) + + +/* This is the hot path for returning to userspace from a syscall. If there's + * work to be done and the branch to _work_pending was taken above, then the + * return to userspace will be done via the normal exception return path... + * that path restores _all_ registers and will overwrite the "clobbered" + * registers with whatever garbage is in pt_regs -- that's OK because those + * registers are clobbered anyway and because the extra work is insignificant + * in the context of the extra work that _work_pending is doing. + +/* Once again, syscalls are special and only guarantee to preserve the + * same registers as a normal function call */ + +/* The assumption here is that the registers r14-r28 (even) are untouched and + * don't need to be restored... be sure that that's really the case! + */ + +/* This is still too much... we should only be restoring what we actually + * clobbered... we should even be using 'scratch' (odd) regs above so that + * we don't need to restore anything, hardly... + */ + + l.lwz r2,PT_GPR2(r1) + + /* Restore args */ + /* r3-r8 are technically clobbered, but syscall restart needs these + * to be restored... + */ + l.lwz r3,PT_GPR3(r1) + l.lwz r4,PT_GPR4(r1) + l.lwz r5,PT_GPR5(r1) + l.lwz r6,PT_GPR6(r1) + l.lwz r7,PT_GPR7(r1) + l.lwz r8,PT_GPR8(r1) + + l.lwz r9,PT_GPR9(r1) + l.lwz r10,PT_GPR10(r1) + l.lwz r11,PT_GPR11(r1) + + /* r30 is the only register we clobber in the fast path */ + l.lwz r30,PT_GPR30(r1) + + /* Here we use r13-r19 (odd) as scratch regs */ + l.lwz r13,PT_PC(r1) + l.lwz r15,PT_SR(r1) + l.lwz r1,PT_SP(r1) + /* Interrupts need to be disabled for setting EPCR and ESR + * so that another interrupt doesn't come in here and clobber + * them before we can use them for our l.rfe */ + DISABLE_INTERRUPTS(r17,r19) + l.mtspr r0,r13,SPR_EPCR_BASE + l.mtspr r0,r15,SPR_ESR_BASE + l.rfe + +/* End of hot path! + * Keep the below tracing and error handling out of the hot path... +*/ + +_syscall_trace_enter: + /* Here we pass pt_regs to do_syscall_trace_enter. Make sure + * that function is really getting all the info it needs as + * pt_regs isn't a complete set of userspace regs, just the + * ones relevant to the syscall... + * + * Note use of delay slot for setting argument. + */ + l.jal do_syscall_trace_enter + l.addi r3,r1,0 + + /* Restore arguments (not preserved across do_syscall_trace_enter) + * so that we can do the syscall for real and return to the syscall + * hot path. + */ + l.lwz r11,PT_GPR11(r1) + l.lwz r3,PT_GPR3(r1) + l.lwz r4,PT_GPR4(r1) + l.lwz r5,PT_GPR5(r1) + l.lwz r6,PT_GPR6(r1) + l.lwz r7,PT_GPR7(r1) + + l.j _syscall_check + l.lwz r8,PT_GPR8(r1) + +_syscall_trace_leave: + l.jal do_syscall_trace_leave + l.addi r3,r1,0 + + l.j _syscall_check_work + l.nop + +_syscall_badsys: + /* Here we effectively pretend to have executed an imaginary + * syscall that returns -ENOSYS and then return to the regular + * syscall hot path. + * Note that "return value" is set in the delay slot... + */ + l.j _syscall_return + l.addi r11,r0,-ENOSYS + +/******* END SYSCALL HANDLING *******/ + +/* ---[ 0xd00: Trap exception ]------------------------------------------ */ + +UNHANDLED_EXCEPTION(_vector_0xd00,0xd00) + +/* ---[ 0xe00: Trap exception ]------------------------------------------ */ + +EXCEPTION_ENTRY(_trap_handler) + CLEAR_LWA_FLAG(r3) + /* r4: EA of fault (set by EXCEPTION_HANDLE) */ + l.jal do_trap + l.addi r3,r1,0 /* pt_regs */ + + l.j _ret_from_exception + l.nop + +/* ---[ 0xf00: Reserved exception ]-------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0xf00,0xf00) + +/* ---[ 0x1000: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1000,0x1000) + +/* ---[ 0x1100: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1100,0x1100) + +/* ---[ 0x1200: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1200,0x1200) + +/* ---[ 0x1300: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1300,0x1300) + +/* ---[ 0x1400: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1400,0x1400) + +/* ---[ 0x1500: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1500,0x1500) + +/* ---[ 0x1600: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1600,0x1600) + +/* ---[ 0x1700: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1700,0x1700) + +/* ---[ 0x1800: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1800,0x1800) + +/* ---[ 0x1900: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1900,0x1900) + +/* ---[ 0x1a00: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1a00,0x1a00) + +/* ---[ 0x1b00: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1b00,0x1b00) + +/* ---[ 0x1c00: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1c00,0x1c00) + +/* ---[ 0x1d00: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1d00,0x1d00) + +/* ---[ 0x1e00: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1e00,0x1e00) + +/* ---[ 0x1f00: Reserved exception ]------------------------------------- */ + +UNHANDLED_EXCEPTION(_vector_0x1f00,0x1f00) + +/* ========================================================[ return ] === */ + +_resume_userspace: + DISABLE_INTERRUPTS(r3,r4) + TRACE_IRQS_OFF + l.lwz r4,TI_FLAGS(r10) + l.andi r13,r4,_TIF_WORK_MASK + l.sfeqi r13,0 + l.bf _restore_all + l.nop + +_work_pending: + l.lwz r5,PT_ORIG_GPR11(r1) + l.sfltsi r5,0 + l.bnf 1f + l.nop + l.andi r5,r5,0 +1: + l.jal do_work_pending + l.ori r3,r1,0 /* pt_regs */ + + l.sfeqi r11,0 + l.bf _restore_all + l.nop + l.sfltsi r11,0 + l.bnf 1f + l.nop + l.and r11,r11,r0 + l.ori r11,r11,__NR_restart_syscall + l.j _syscall_check_trace_enter + l.nop +1: + l.lwz r11,PT_ORIG_GPR11(r1) + /* Restore arg registers */ + l.lwz r3,PT_GPR3(r1) + l.lwz r4,PT_GPR4(r1) + l.lwz r5,PT_GPR5(r1) + l.lwz r6,PT_GPR6(r1) + l.lwz r7,PT_GPR7(r1) + l.j _syscall_check_trace_enter + l.lwz r8,PT_GPR8(r1) + +_restore_all: +#ifdef CONFIG_TRACE_IRQFLAGS + l.lwz r4,PT_SR(r1) + l.andi r3,r4,(SPR_SR_IEE|SPR_SR_TEE) + l.sfeq r3,r0 /* skip trace if irqs were off */ + l.bf skip_hardirqs_on + l.nop + TRACE_IRQS_ON +skip_hardirqs_on: +#endif + RESTORE_ALL + /* This returns to userspace code */ + + +ENTRY(_ret_from_intr) +ENTRY(_ret_from_exception) + l.lwz r4,PT_SR(r1) + l.andi r3,r4,SPR_SR_SM + l.sfeqi r3,0 + l.bnf _restore_all + l.nop + l.j _resume_userspace + l.nop + +ENTRY(ret_from_fork) + l.jal schedule_tail + l.nop + + /* Check if we are a kernel thread */ + l.sfeqi r20,0 + l.bf 1f + l.nop + + /* ...we are a kernel thread so invoke the requested callback */ + l.jalr r20 + l.or r3,r22,r0 + +1: + /* _syscall_returns expect r11 to contain return value */ + l.lwz r11,PT_GPR11(r1) + + /* The syscall fast path return expects call-saved registers + * r12-r28 to be untouched, so we restore them here as they + * will have been effectively clobbered when arriving here + * via the call to switch() + */ + l.lwz r12,PT_GPR12(r1) + l.lwz r14,PT_GPR14(r1) + l.lwz r16,PT_GPR16(r1) + l.lwz r18,PT_GPR18(r1) + l.lwz r20,PT_GPR20(r1) + l.lwz r22,PT_GPR22(r1) + l.lwz r24,PT_GPR24(r1) + l.lwz r26,PT_GPR26(r1) + l.lwz r28,PT_GPR28(r1) + + l.j _syscall_return + l.nop + +/* ========================================================[ switch ] === */ + +/* + * This routine switches between two different tasks. The process + * state of one is saved on its kernel stack. Then the state + * of the other is restored from its kernel stack. The memory + * management hardware is updated to the second process's state. + * Finally, we can return to the second process, via the 'return'. + * + * Note: there are two ways to get to the "going out" portion + * of this code; either by coming in via the entry (_switch) + * or via "fork" which must set up an environment equivalent + * to the "_switch" path. If you change this (or in particular, the + * SAVE_REGS macro), you'll have to change the fork code also. + */ + + +/* _switch MUST never lay on page boundry, cause it runs from + * effective addresses and beeing interrupted by iTLB miss would kill it. + * dTLB miss seams to never accour in the bad place since data accesses + * are from task structures which are always page aligned. + * + * The problem happens in RESTORE_ALL_NO_R11 where we first set the EPCR + * register, then load the previous register values and only at the end call + * the l.rfe instruction. If get TLB miss in beetwen the EPCR register gets + * garbled and we end up calling l.rfe with the wrong EPCR. (same probably + * holds for ESR) + * + * To avoid this problems it is sufficient to align _switch to + * some nice round number smaller than it's size... + */ + +/* ABI rules apply here... we either enter _switch via schedule() or via + * an imaginary call to which we shall return at return_from_fork. Either + * way, we are a function call and only need to preserve the callee-saved + * registers when we return. As such, we don't need to save the registers + * on the stack that we won't be returning as they were... + */ + + .align 0x400 +ENTRY(_switch) + /* We don't store SR as _switch only gets called in a context where + * the SR will be the same going in and coming out... */ + + /* Set up new pt_regs struct for saving task state */ + l.addi r1,r1,-(INT_FRAME_SIZE) + + /* No need to store r1/PT_SP as it goes into KSP below */ + l.sw PT_GPR2(r1),r2 + l.sw PT_GPR9(r1),r9 + /* This is wrong, r12 shouldn't be here... but GCC is broken for the time being + * and expects r12 to be callee-saved... */ + l.sw PT_GPR12(r1),r12 + l.sw PT_GPR14(r1),r14 + l.sw PT_GPR16(r1),r16 + l.sw PT_GPR18(r1),r18 + l.sw PT_GPR20(r1),r20 + l.sw PT_GPR22(r1),r22 + l.sw PT_GPR24(r1),r24 + l.sw PT_GPR26(r1),r26 + l.sw PT_GPR28(r1),r28 + l.sw PT_GPR30(r1),r30 + + l.addi r11,r10,0 /* Save old 'current' to 'last' return value*/ + + /* We use thread_info->ksp for storing the address of the above + * structure so that we can get back to it later... we don't want + * to lose the value of thread_info->ksp, though, so store it as + * pt_regs->sp so that we can easily restore it when we are made + * live again... + */ + + /* Save the old value of thread_info->ksp as pt_regs->sp */ + l.lwz r29,TI_KSP(r10) + l.sw PT_SP(r1),r29 + + /* Swap kernel stack pointers */ + l.sw TI_KSP(r10),r1 /* Save old stack pointer */ + l.or r10,r4,r0 /* Set up new current_thread_info */ + l.lwz r1,TI_KSP(r10) /* Load new stack pointer */ + + /* Restore the old value of thread_info->ksp */ + l.lwz r29,PT_SP(r1) + l.sw TI_KSP(r10),r29 + + /* ...and restore the registers, except r11 because the return value + * has already been set above. + */ + l.lwz r2,PT_GPR2(r1) + l.lwz r9,PT_GPR9(r1) + /* No need to restore r10 */ + /* ...and do not restore r11 */ + + /* This is wrong, r12 shouldn't be here... but GCC is broken for the time being + * and expects r12 to be callee-saved... */ + l.lwz r12,PT_GPR12(r1) + l.lwz r14,PT_GPR14(r1) + l.lwz r16,PT_GPR16(r1) + l.lwz r18,PT_GPR18(r1) + l.lwz r20,PT_GPR20(r1) + l.lwz r22,PT_GPR22(r1) + l.lwz r24,PT_GPR24(r1) + l.lwz r26,PT_GPR26(r1) + l.lwz r28,PT_GPR28(r1) + l.lwz r30,PT_GPR30(r1) + + /* Unwind stack to pre-switch state */ + l.addi r1,r1,(INT_FRAME_SIZE) + + /* Return via the link-register back to where we 'came from', where + * that may be either schedule(), ret_from_fork(), or + * ret_from_kernel_thread(). If we are returning to a new thread, + * we are expected to have set up the arg to schedule_tail already, + * hence we do so here unconditionally: + */ + l.lwz r3,TI_TASK(r3) /* Load 'prev' as schedule_tail arg */ + l.jr r9 + l.nop + +/* ==================================================================== */ + +/* These all use the delay slot for setting the argument register, so the + * jump is always happening after the l.addi instruction. + * + * These are all just wrappers that don't touch the link-register r9, so the + * return from the "real" syscall function will return back to the syscall + * code that did the l.jal that brought us here. + */ + +/* fork requires that we save all the callee-saved registers because they + * are all effectively clobbered by the call to _switch. Here we store + * all the registers that aren't touched by the syscall fast path and thus + * weren't saved there. + */ + +_fork_save_extra_regs_and_call: + l.sw PT_GPR14(r1),r14 + l.sw PT_GPR16(r1),r16 + l.sw PT_GPR18(r1),r18 + l.sw PT_GPR20(r1),r20 + l.sw PT_GPR22(r1),r22 + l.sw PT_GPR24(r1),r24 + l.sw PT_GPR26(r1),r26 + l.jr r29 + l.sw PT_GPR28(r1),r28 + +ENTRY(__sys_clone) + l.movhi r29,hi(sys_clone) + l.ori r29,r29,lo(sys_clone) + l.j _fork_save_extra_regs_and_call + l.nop + +ENTRY(__sys_fork) + l.movhi r29,hi(sys_fork) + l.ori r29,r29,lo(sys_fork) + l.j _fork_save_extra_regs_and_call + l.nop + +ENTRY(sys_rt_sigreturn) + l.jal _sys_rt_sigreturn + l.addi r3,r1,0 + l.sfne r30,r0 + l.bnf _no_syscall_trace + l.nop + l.jal do_syscall_trace_leave + l.addi r3,r1,0 +_no_syscall_trace: + l.j _resume_userspace + l.nop + +/* This is a catch-all syscall for atomic instructions for the OpenRISC 1000. + * The functions takes a variable number of parameters depending on which + * particular flavour of atomic you want... parameter 1 is a flag identifying + * the atomic in question. Currently, this function implements the + * following variants: + * + * XCHG: + * @flag: 1 + * @ptr1: + * @ptr2: + * Atomically exchange the values in pointers 1 and 2. + * + */ + +ENTRY(sys_or1k_atomic) + /* FIXME: This ignores r3 and always does an XCHG */ + DISABLE_INTERRUPTS(r17,r19) + l.lwz r29,0(r4) + l.lwz r27,0(r5) + l.sw 0(r4),r27 + l.sw 0(r5),r29 + ENABLE_INTERRUPTS(r17) + l.jr r9 + l.or r11,r0,r0 + +/* ============================================================[ EOF ]=== */ diff --git a/arch/openrisc/kernel/head.S b/arch/openrisc/kernel/head.S new file mode 100644 index 000000000..d7e49b947 --- /dev/null +++ b/arch/openrisc/kernel/head.S @@ -0,0 +1,1757 @@ +/* + * OpenRISC head.S + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/linkage.h> +#include <linux/threads.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/serial_reg.h> +#include <asm/processor.h> +#include <asm/page.h> +#include <asm/mmu.h> +#include <asm/pgtable.h> +#include <asm/thread_info.h> +#include <asm/cache.h> +#include <asm/spr_defs.h> +#include <asm/asm-offsets.h> +#include <linux/of_fdt.h> + +#define tophys(rd,rs) \ + l.movhi rd,hi(-KERNELBASE) ;\ + l.add rd,rd,rs + +#define CLEAR_GPR(gpr) \ + l.movhi gpr,0x0 + +#define LOAD_SYMBOL_2_GPR(gpr,symbol) \ + l.movhi gpr,hi(symbol) ;\ + l.ori gpr,gpr,lo(symbol) + + +#define UART_BASE_ADD 0x90000000 + +#define EXCEPTION_SR (SPR_SR_DME | SPR_SR_IME | SPR_SR_DCE | SPR_SR_ICE | SPR_SR_SM) +#define SYSCALL_SR (SPR_SR_DME | SPR_SR_IME | SPR_SR_DCE | SPR_SR_ICE | SPR_SR_IEE | SPR_SR_TEE | SPR_SR_SM) + +/* ============================================[ tmp store locations ]=== */ + +#define SPR_SHADOW_GPR(x) ((x) + SPR_GPR_BASE + 32) + +/* + * emergency_print temporary stores + */ +#ifdef CONFIG_OPENRISC_HAVE_SHADOW_GPRS +#define EMERGENCY_PRINT_STORE_GPR4 l.mtspr r0,r4,SPR_SHADOW_GPR(14) +#define EMERGENCY_PRINT_LOAD_GPR4 l.mfspr r4,r0,SPR_SHADOW_GPR(14) + +#define EMERGENCY_PRINT_STORE_GPR5 l.mtspr r0,r5,SPR_SHADOW_GPR(15) +#define EMERGENCY_PRINT_LOAD_GPR5 l.mfspr r5,r0,SPR_SHADOW_GPR(15) + +#define EMERGENCY_PRINT_STORE_GPR6 l.mtspr r0,r6,SPR_SHADOW_GPR(16) +#define EMERGENCY_PRINT_LOAD_GPR6 l.mfspr r6,r0,SPR_SHADOW_GPR(16) + +#define EMERGENCY_PRINT_STORE_GPR7 l.mtspr r0,r7,SPR_SHADOW_GPR(7) +#define EMERGENCY_PRINT_LOAD_GPR7 l.mfspr r7,r0,SPR_SHADOW_GPR(7) + +#define EMERGENCY_PRINT_STORE_GPR8 l.mtspr r0,r8,SPR_SHADOW_GPR(8) +#define EMERGENCY_PRINT_LOAD_GPR8 l.mfspr r8,r0,SPR_SHADOW_GPR(8) + +#define EMERGENCY_PRINT_STORE_GPR9 l.mtspr r0,r9,SPR_SHADOW_GPR(9) +#define EMERGENCY_PRINT_LOAD_GPR9 l.mfspr r9,r0,SPR_SHADOW_GPR(9) + +#else /* !CONFIG_OPENRISC_HAVE_SHADOW_GPRS */ +#define EMERGENCY_PRINT_STORE_GPR4 l.sw 0x20(r0),r4 +#define EMERGENCY_PRINT_LOAD_GPR4 l.lwz r4,0x20(r0) + +#define EMERGENCY_PRINT_STORE_GPR5 l.sw 0x24(r0),r5 +#define EMERGENCY_PRINT_LOAD_GPR5 l.lwz r5,0x24(r0) + +#define EMERGENCY_PRINT_STORE_GPR6 l.sw 0x28(r0),r6 +#define EMERGENCY_PRINT_LOAD_GPR6 l.lwz r6,0x28(r0) + +#define EMERGENCY_PRINT_STORE_GPR7 l.sw 0x2c(r0),r7 +#define EMERGENCY_PRINT_LOAD_GPR7 l.lwz r7,0x2c(r0) + +#define EMERGENCY_PRINT_STORE_GPR8 l.sw 0x30(r0),r8 +#define EMERGENCY_PRINT_LOAD_GPR8 l.lwz r8,0x30(r0) + +#define EMERGENCY_PRINT_STORE_GPR9 l.sw 0x34(r0),r9 +#define EMERGENCY_PRINT_LOAD_GPR9 l.lwz r9,0x34(r0) + +#endif + +/* + * TLB miss handlers temorary stores + */ +#ifdef CONFIG_OPENRISC_HAVE_SHADOW_GPRS +#define EXCEPTION_STORE_GPR2 l.mtspr r0,r2,SPR_SHADOW_GPR(2) +#define EXCEPTION_LOAD_GPR2 l.mfspr r2,r0,SPR_SHADOW_GPR(2) + +#define EXCEPTION_STORE_GPR3 l.mtspr r0,r3,SPR_SHADOW_GPR(3) +#define EXCEPTION_LOAD_GPR3 l.mfspr r3,r0,SPR_SHADOW_GPR(3) + +#define EXCEPTION_STORE_GPR4 l.mtspr r0,r4,SPR_SHADOW_GPR(4) +#define EXCEPTION_LOAD_GPR4 l.mfspr r4,r0,SPR_SHADOW_GPR(4) + +#define EXCEPTION_STORE_GPR5 l.mtspr r0,r5,SPR_SHADOW_GPR(5) +#define EXCEPTION_LOAD_GPR5 l.mfspr r5,r0,SPR_SHADOW_GPR(5) + +#define EXCEPTION_STORE_GPR6 l.mtspr r0,r6,SPR_SHADOW_GPR(6) +#define EXCEPTION_LOAD_GPR6 l.mfspr r6,r0,SPR_SHADOW_GPR(6) + +#else /* !CONFIG_OPENRISC_HAVE_SHADOW_GPRS */ +#define EXCEPTION_STORE_GPR2 l.sw 0x64(r0),r2 +#define EXCEPTION_LOAD_GPR2 l.lwz r2,0x64(r0) + +#define EXCEPTION_STORE_GPR3 l.sw 0x68(r0),r3 +#define EXCEPTION_LOAD_GPR3 l.lwz r3,0x68(r0) + +#define EXCEPTION_STORE_GPR4 l.sw 0x6c(r0),r4 +#define EXCEPTION_LOAD_GPR4 l.lwz r4,0x6c(r0) + +#define EXCEPTION_STORE_GPR5 l.sw 0x70(r0),r5 +#define EXCEPTION_LOAD_GPR5 l.lwz r5,0x70(r0) + +#define EXCEPTION_STORE_GPR6 l.sw 0x74(r0),r6 +#define EXCEPTION_LOAD_GPR6 l.lwz r6,0x74(r0) + +#endif + +/* + * EXCEPTION_HANDLE temporary stores + */ + +#ifdef CONFIG_OPENRISC_HAVE_SHADOW_GPRS +#define EXCEPTION_T_STORE_GPR30 l.mtspr r0,r30,SPR_SHADOW_GPR(30) +#define EXCEPTION_T_LOAD_GPR30(reg) l.mfspr reg,r0,SPR_SHADOW_GPR(30) + +#define EXCEPTION_T_STORE_GPR10 l.mtspr r0,r10,SPR_SHADOW_GPR(10) +#define EXCEPTION_T_LOAD_GPR10(reg) l.mfspr reg,r0,SPR_SHADOW_GPR(10) + +#define EXCEPTION_T_STORE_SP l.mtspr r0,r1,SPR_SHADOW_GPR(1) +#define EXCEPTION_T_LOAD_SP(reg) l.mfspr reg,r0,SPR_SHADOW_GPR(1) + +#else /* !CONFIG_OPENRISC_HAVE_SHADOW_GPRS */ +#define EXCEPTION_T_STORE_GPR30 l.sw 0x78(r0),r30 +#define EXCEPTION_T_LOAD_GPR30(reg) l.lwz reg,0x78(r0) + +#define EXCEPTION_T_STORE_GPR10 l.sw 0x7c(r0),r10 +#define EXCEPTION_T_LOAD_GPR10(reg) l.lwz reg,0x7c(r0) + +#define EXCEPTION_T_STORE_SP l.sw 0x80(r0),r1 +#define EXCEPTION_T_LOAD_SP(reg) l.lwz reg,0x80(r0) +#endif + +/* =========================================================[ macros ]=== */ + +#ifdef CONFIG_SMP +#define GET_CURRENT_PGD(reg,t1) \ + LOAD_SYMBOL_2_GPR(reg,current_pgd) ;\ + l.mfspr t1,r0,SPR_COREID ;\ + l.slli t1,t1,2 ;\ + l.add reg,reg,t1 ;\ + tophys (t1,reg) ;\ + l.lwz reg,0(t1) +#else +#define GET_CURRENT_PGD(reg,t1) \ + LOAD_SYMBOL_2_GPR(reg,current_pgd) ;\ + tophys (t1,reg) ;\ + l.lwz reg,0(t1) +#endif + +/* Load r10 from current_thread_info_set - clobbers r1 and r30 */ +#ifdef CONFIG_SMP +#define GET_CURRENT_THREAD_INFO \ + LOAD_SYMBOL_2_GPR(r1,current_thread_info_set) ;\ + tophys (r30,r1) ;\ + l.mfspr r10,r0,SPR_COREID ;\ + l.slli r10,r10,2 ;\ + l.add r30,r30,r10 ;\ + /* r10: current_thread_info */ ;\ + l.lwz r10,0(r30) +#else +#define GET_CURRENT_THREAD_INFO \ + LOAD_SYMBOL_2_GPR(r1,current_thread_info_set) ;\ + tophys (r30,r1) ;\ + /* r10: current_thread_info */ ;\ + l.lwz r10,0(r30) +#endif + +/* + * DSCR: this is a common hook for handling exceptions. it will save + * the needed registers, set up stack and pointer to current + * then jump to the handler while enabling MMU + * + * PRMS: handler - a function to jump to. it has to save the + * remaining registers to kernel stack, call + * appropriate arch-independant exception handler + * and finaly jump to ret_from_except + * + * PREQ: unchanged state from the time exception happened + * + * POST: SAVED the following registers original value + * to the new created exception frame pointed to by r1 + * + * r1 - ksp pointing to the new (exception) frame + * r4 - EEAR exception EA + * r10 - current pointing to current_thread_info struct + * r12 - syscall 0, since we didn't come from syscall + * r30 - handler address of the handler we'll jump to + * + * handler has to save remaining registers to the exception + * ksp frame *before* tainting them! + * + * NOTE: this function is not reentrant per se. reentrancy is guaranteed + * by processor disabling all exceptions/interrupts when exception + * accours. + * + * OPTM: no need to make it so wasteful to extract ksp when in user mode + */ + +#define EXCEPTION_HANDLE(handler) \ + EXCEPTION_T_STORE_GPR30 ;\ + l.mfspr r30,r0,SPR_ESR_BASE ;\ + l.andi r30,r30,SPR_SR_SM ;\ + l.sfeqi r30,0 ;\ + EXCEPTION_T_STORE_GPR10 ;\ + l.bnf 2f /* kernel_mode */ ;\ + EXCEPTION_T_STORE_SP /* delay slot */ ;\ +1: /* user_mode: */ ;\ + GET_CURRENT_THREAD_INFO ;\ + tophys (r30,r10) ;\ + l.lwz r1,(TI_KSP)(r30) ;\ + /* fall through */ ;\ +2: /* kernel_mode: */ ;\ + /* create new stack frame, save only needed gprs */ ;\ + /* r1: KSP, r10: current, r4: EEAR, r31: __pa(KSP) */ ;\ + /* r12: temp, syscall indicator */ ;\ + l.addi r1,r1,-(INT_FRAME_SIZE) ;\ + /* r1 is KSP, r30 is __pa(KSP) */ ;\ + tophys (r30,r1) ;\ + l.sw PT_GPR12(r30),r12 ;\ + /* r4 use for tmp before EA */ ;\ + l.mfspr r12,r0,SPR_EPCR_BASE ;\ + l.sw PT_PC(r30),r12 ;\ + l.mfspr r12,r0,SPR_ESR_BASE ;\ + l.sw PT_SR(r30),r12 ;\ + /* save r30 */ ;\ + EXCEPTION_T_LOAD_GPR30(r12) ;\ + l.sw PT_GPR30(r30),r12 ;\ + /* save r10 as was prior to exception */ ;\ + EXCEPTION_T_LOAD_GPR10(r12) ;\ + l.sw PT_GPR10(r30),r12 ;\ + /* save PT_SP as was prior to exception */ ;\ + EXCEPTION_T_LOAD_SP(r12) ;\ + l.sw PT_SP(r30),r12 ;\ + /* save exception r4, set r4 = EA */ ;\ + l.sw PT_GPR4(r30),r4 ;\ + l.mfspr r4,r0,SPR_EEAR_BASE ;\ + /* r12 == 1 if we come from syscall */ ;\ + CLEAR_GPR(r12) ;\ + /* ----- turn on MMU ----- */ ;\ + /* Carry DSX into exception SR */ ;\ + l.mfspr r30,r0,SPR_SR ;\ + l.andi r30,r30,SPR_SR_DSX ;\ + l.ori r30,r30,(EXCEPTION_SR) ;\ + l.mtspr r0,r30,SPR_ESR_BASE ;\ + /* r30: EA address of handler */ ;\ + LOAD_SYMBOL_2_GPR(r30,handler) ;\ + l.mtspr r0,r30,SPR_EPCR_BASE ;\ + l.rfe + +/* + * this doesn't work + * + * + * #ifdef CONFIG_JUMP_UPON_UNHANDLED_EXCEPTION + * #define UNHANDLED_EXCEPTION(handler) \ + * l.ori r3,r0,0x1 ;\ + * l.mtspr r0,r3,SPR_SR ;\ + * l.movhi r3,hi(0xf0000100) ;\ + * l.ori r3,r3,lo(0xf0000100) ;\ + * l.jr r3 ;\ + * l.nop 1 + * + * #endif + */ + +/* DSCR: this is the same as EXCEPTION_HANDLE(), we are just + * a bit more carefull (if we have a PT_SP or current pointer + * corruption) and set them up from 'current_set' + * + */ +#define UNHANDLED_EXCEPTION(handler) \ + EXCEPTION_T_STORE_GPR30 ;\ + EXCEPTION_T_STORE_GPR10 ;\ + EXCEPTION_T_STORE_SP ;\ + /* temporary store r3, r9 into r1, r10 */ ;\ + l.addi r1,r3,0x0 ;\ + l.addi r10,r9,0x0 ;\ + /* the string referenced by r3 must be low enough */ ;\ + l.jal _emergency_print ;\ + l.ori r3,r0,lo(_string_unhandled_exception) ;\ + l.mfspr r3,r0,SPR_NPC ;\ + l.jal _emergency_print_nr ;\ + l.andi r3,r3,0x1f00 ;\ + /* the string referenced by r3 must be low enough */ ;\ + l.jal _emergency_print ;\ + l.ori r3,r0,lo(_string_epc_prefix) ;\ + l.jal _emergency_print_nr ;\ + l.mfspr r3,r0,SPR_EPCR_BASE ;\ + l.jal _emergency_print ;\ + l.ori r3,r0,lo(_string_nl) ;\ + /* end of printing */ ;\ + l.addi r3,r1,0x0 ;\ + l.addi r9,r10,0x0 ;\ + /* extract current, ksp from current_set */ ;\ + LOAD_SYMBOL_2_GPR(r1,_unhandled_stack_top) ;\ + LOAD_SYMBOL_2_GPR(r10,init_thread_union) ;\ + /* create new stack frame, save only needed gprs */ ;\ + /* r1: KSP, r10: current, r31: __pa(KSP) */ ;\ + /* r12: temp, syscall indicator, r13 temp */ ;\ + l.addi r1,r1,-(INT_FRAME_SIZE) ;\ + /* r1 is KSP, r30 is __pa(KSP) */ ;\ + tophys (r30,r1) ;\ + l.sw PT_GPR12(r30),r12 ;\ + l.mfspr r12,r0,SPR_EPCR_BASE ;\ + l.sw PT_PC(r30),r12 ;\ + l.mfspr r12,r0,SPR_ESR_BASE ;\ + l.sw PT_SR(r30),r12 ;\ + /* save r31 */ ;\ + EXCEPTION_T_LOAD_GPR30(r12) ;\ + l.sw PT_GPR30(r30),r12 ;\ + /* save r10 as was prior to exception */ ;\ + EXCEPTION_T_LOAD_GPR10(r12) ;\ + l.sw PT_GPR10(r30),r12 ;\ + /* save PT_SP as was prior to exception */ ;\ + EXCEPTION_T_LOAD_SP(r12) ;\ + l.sw PT_SP(r30),r12 ;\ + l.sw PT_GPR13(r30),r13 ;\ + /* --> */ ;\ + /* save exception r4, set r4 = EA */ ;\ + l.sw PT_GPR4(r30),r4 ;\ + l.mfspr r4,r0,SPR_EEAR_BASE ;\ + /* r12 == 1 if we come from syscall */ ;\ + CLEAR_GPR(r12) ;\ + /* ----- play a MMU trick ----- */ ;\ + l.ori r30,r0,(EXCEPTION_SR) ;\ + l.mtspr r0,r30,SPR_ESR_BASE ;\ + /* r31: EA address of handler */ ;\ + LOAD_SYMBOL_2_GPR(r30,handler) ;\ + l.mtspr r0,r30,SPR_EPCR_BASE ;\ + l.rfe + +/* =====================================================[ exceptions] === */ + +/* ---[ 0x100: RESET exception ]----------------------------------------- */ + .org 0x100 + /* Jump to .init code at _start which lives in the .head section + * and will be discarded after boot. + */ + LOAD_SYMBOL_2_GPR(r15, _start) + tophys (r13,r15) /* MMU disabled */ + l.jr r13 + l.nop + +/* ---[ 0x200: BUS exception ]------------------------------------------- */ + .org 0x200 +_dispatch_bus_fault: + EXCEPTION_HANDLE(_bus_fault_handler) + +/* ---[ 0x300: Data Page Fault exception ]------------------------------- */ + .org 0x300 +_dispatch_do_dpage_fault: +// totaly disable timer interrupt +// l.mtspr r0,r0,SPR_TTMR +// DEBUG_TLB_PROBE(0x300) +// EXCEPTION_DEBUG_VALUE_ER_ENABLED(0x300) + EXCEPTION_HANDLE(_data_page_fault_handler) + +/* ---[ 0x400: Insn Page Fault exception ]------------------------------- */ + .org 0x400 +_dispatch_do_ipage_fault: +// totaly disable timer interrupt +// l.mtspr r0,r0,SPR_TTMR +// DEBUG_TLB_PROBE(0x400) +// EXCEPTION_DEBUG_VALUE_ER_ENABLED(0x400) + EXCEPTION_HANDLE(_insn_page_fault_handler) + +/* ---[ 0x500: Timer exception ]----------------------------------------- */ + .org 0x500 + EXCEPTION_HANDLE(_timer_handler) + +/* ---[ 0x600: Alignment exception ]-------------------------------------- */ + .org 0x600 + EXCEPTION_HANDLE(_alignment_handler) + +/* ---[ 0x700: Illegal insn exception ]---------------------------------- */ + .org 0x700 + EXCEPTION_HANDLE(_illegal_instruction_handler) + +/* ---[ 0x800: External interrupt exception ]---------------------------- */ + .org 0x800 + EXCEPTION_HANDLE(_external_irq_handler) + +/* ---[ 0x900: DTLB miss exception ]------------------------------------- */ + .org 0x900 + l.j boot_dtlb_miss_handler + l.nop + +/* ---[ 0xa00: ITLB miss exception ]------------------------------------- */ + .org 0xa00 + l.j boot_itlb_miss_handler + l.nop + +/* ---[ 0xb00: Range exception ]----------------------------------------- */ + .org 0xb00 + UNHANDLED_EXCEPTION(_vector_0xb00) + +/* ---[ 0xc00: Syscall exception ]--------------------------------------- */ + .org 0xc00 + EXCEPTION_HANDLE(_sys_call_handler) + +/* ---[ 0xd00: Trap exception ]------------------------------------------ */ + .org 0xd00 + UNHANDLED_EXCEPTION(_vector_0xd00) + +/* ---[ 0xe00: Trap exception ]------------------------------------------ */ + .org 0xe00 +// UNHANDLED_EXCEPTION(_vector_0xe00) + EXCEPTION_HANDLE(_trap_handler) + +/* ---[ 0xf00: Reserved exception ]-------------------------------------- */ + .org 0xf00 + UNHANDLED_EXCEPTION(_vector_0xf00) + +/* ---[ 0x1000: Reserved exception ]------------------------------------- */ + .org 0x1000 + UNHANDLED_EXCEPTION(_vector_0x1000) + +/* ---[ 0x1100: Reserved exception ]------------------------------------- */ + .org 0x1100 + UNHANDLED_EXCEPTION(_vector_0x1100) + +/* ---[ 0x1200: Reserved exception ]------------------------------------- */ + .org 0x1200 + UNHANDLED_EXCEPTION(_vector_0x1200) + +/* ---[ 0x1300: Reserved exception ]------------------------------------- */ + .org 0x1300 + UNHANDLED_EXCEPTION(_vector_0x1300) + +/* ---[ 0x1400: Reserved exception ]------------------------------------- */ + .org 0x1400 + UNHANDLED_EXCEPTION(_vector_0x1400) + +/* ---[ 0x1500: Reserved exception ]------------------------------------- */ + .org 0x1500 + UNHANDLED_EXCEPTION(_vector_0x1500) + +/* ---[ 0x1600: Reserved exception ]------------------------------------- */ + .org 0x1600 + UNHANDLED_EXCEPTION(_vector_0x1600) + +/* ---[ 0x1700: Reserved exception ]------------------------------------- */ + .org 0x1700 + UNHANDLED_EXCEPTION(_vector_0x1700) + +/* ---[ 0x1800: Reserved exception ]------------------------------------- */ + .org 0x1800 + UNHANDLED_EXCEPTION(_vector_0x1800) + +/* ---[ 0x1900: Reserved exception ]------------------------------------- */ + .org 0x1900 + UNHANDLED_EXCEPTION(_vector_0x1900) + +/* ---[ 0x1a00: Reserved exception ]------------------------------------- */ + .org 0x1a00 + UNHANDLED_EXCEPTION(_vector_0x1a00) + +/* ---[ 0x1b00: Reserved exception ]------------------------------------- */ + .org 0x1b00 + UNHANDLED_EXCEPTION(_vector_0x1b00) + +/* ---[ 0x1c00: Reserved exception ]------------------------------------- */ + .org 0x1c00 + UNHANDLED_EXCEPTION(_vector_0x1c00) + +/* ---[ 0x1d00: Reserved exception ]------------------------------------- */ + .org 0x1d00 + UNHANDLED_EXCEPTION(_vector_0x1d00) + +/* ---[ 0x1e00: Reserved exception ]------------------------------------- */ + .org 0x1e00 + UNHANDLED_EXCEPTION(_vector_0x1e00) + +/* ---[ 0x1f00: Reserved exception ]------------------------------------- */ + .org 0x1f00 + UNHANDLED_EXCEPTION(_vector_0x1f00) + + .org 0x2000 +/* ===================================================[ kernel start ]=== */ + +/* .text*/ + +/* This early stuff belongs in HEAD, but some of the functions below definitely + * don't... */ + + __HEAD + .global _start +_start: + /* Init r0 to zero as per spec */ + CLEAR_GPR(r0) + + /* save kernel parameters */ + l.or r25,r0,r3 /* pointer to fdt */ + + /* + * ensure a deterministic start + */ + + l.ori r3,r0,0x1 + l.mtspr r0,r3,SPR_SR + + /* + * Start the TTCR as early as possible, so that the RNG can make use of + * measurements of boot time from the earliest opportunity. Especially + * important is that the TTCR does not return zero by the time we reach + * rand_initialize(). + */ + l.movhi r3,hi(SPR_TTMR_CR) + l.mtspr r0,r3,SPR_TTMR + + CLEAR_GPR(r1) + CLEAR_GPR(r2) + CLEAR_GPR(r3) + CLEAR_GPR(r4) + CLEAR_GPR(r5) + CLEAR_GPR(r6) + CLEAR_GPR(r7) + CLEAR_GPR(r8) + CLEAR_GPR(r9) + CLEAR_GPR(r10) + CLEAR_GPR(r11) + CLEAR_GPR(r12) + CLEAR_GPR(r13) + CLEAR_GPR(r14) + CLEAR_GPR(r15) + CLEAR_GPR(r16) + CLEAR_GPR(r17) + CLEAR_GPR(r18) + CLEAR_GPR(r19) + CLEAR_GPR(r20) + CLEAR_GPR(r21) + CLEAR_GPR(r22) + CLEAR_GPR(r23) + CLEAR_GPR(r24) + CLEAR_GPR(r26) + CLEAR_GPR(r27) + CLEAR_GPR(r28) + CLEAR_GPR(r29) + CLEAR_GPR(r30) + CLEAR_GPR(r31) + +#ifdef CONFIG_SMP + l.mfspr r26,r0,SPR_COREID + l.sfeq r26,r0 + l.bnf secondary_wait + l.nop +#endif + /* + * set up initial ksp and current + */ + /* setup kernel stack */ + LOAD_SYMBOL_2_GPR(r1,init_thread_union + THREAD_SIZE) + LOAD_SYMBOL_2_GPR(r10,init_thread_union) // setup current + tophys (r31,r10) + l.sw TI_KSP(r31), r1 + + l.ori r4,r0,0x0 + + + /* + * .data contains initialized data, + * .bss contains uninitialized data - clear it up + */ +clear_bss: + LOAD_SYMBOL_2_GPR(r24, __bss_start) + LOAD_SYMBOL_2_GPR(r26, _end) + tophys(r28,r24) + tophys(r30,r26) + CLEAR_GPR(r24) + CLEAR_GPR(r26) +1: + l.sw (0)(r28),r0 + l.sfltu r28,r30 + l.bf 1b + l.addi r28,r28,4 + +enable_ic: + l.jal _ic_enable + l.nop + +enable_dc: + l.jal _dc_enable + l.nop + +flush_tlb: + l.jal _flush_tlb + l.nop + +/* The MMU needs to be enabled before or32_early_setup is called */ + +enable_mmu: + /* + * enable dmmu & immu + * SR[5] = 0, SR[6] = 0, 6th and 7th bit of SR set to 0 + */ + l.mfspr r30,r0,SPR_SR + l.movhi r28,hi(SPR_SR_DME | SPR_SR_IME) + l.ori r28,r28,lo(SPR_SR_DME | SPR_SR_IME) + l.or r30,r30,r28 + l.mtspr r0,r30,SPR_SR + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + + // reset the simulation counters + l.nop 5 + + /* check fdt header magic word */ + l.lwz r3,0(r25) /* load magic from fdt into r3 */ + l.movhi r4,hi(OF_DT_HEADER) + l.ori r4,r4,lo(OF_DT_HEADER) + l.sfeq r3,r4 + l.bf _fdt_found + l.nop + /* magic number mismatch, set fdt pointer to null */ + l.or r25,r0,r0 +_fdt_found: + /* pass fdt pointer to or32_early_setup in r3 */ + l.or r3,r0,r25 + LOAD_SYMBOL_2_GPR(r24, or32_early_setup) + l.jalr r24 + l.nop + +clear_regs: + /* + * clear all GPRS to increase determinism + */ + CLEAR_GPR(r2) + CLEAR_GPR(r3) + CLEAR_GPR(r4) + CLEAR_GPR(r5) + CLEAR_GPR(r6) + CLEAR_GPR(r7) + CLEAR_GPR(r8) + CLEAR_GPR(r9) + CLEAR_GPR(r11) + CLEAR_GPR(r12) + CLEAR_GPR(r13) + CLEAR_GPR(r14) + CLEAR_GPR(r15) + CLEAR_GPR(r16) + CLEAR_GPR(r17) + CLEAR_GPR(r18) + CLEAR_GPR(r19) + CLEAR_GPR(r20) + CLEAR_GPR(r21) + CLEAR_GPR(r22) + CLEAR_GPR(r23) + CLEAR_GPR(r24) + CLEAR_GPR(r25) + CLEAR_GPR(r26) + CLEAR_GPR(r27) + CLEAR_GPR(r28) + CLEAR_GPR(r29) + CLEAR_GPR(r30) + CLEAR_GPR(r31) + +jump_start_kernel: + /* + * jump to kernel entry (start_kernel) + */ + LOAD_SYMBOL_2_GPR(r30, start_kernel) + l.jr r30 + l.nop + +_flush_tlb: + /* + * I N V A L I D A T E T L B e n t r i e s + */ + LOAD_SYMBOL_2_GPR(r5,SPR_DTLBMR_BASE(0)) + LOAD_SYMBOL_2_GPR(r6,SPR_ITLBMR_BASE(0)) + l.addi r7,r0,128 /* Maximum number of sets */ +1: + l.mtspr r5,r0,0x0 + l.mtspr r6,r0,0x0 + + l.addi r5,r5,1 + l.addi r6,r6,1 + l.sfeq r7,r0 + l.bnf 1b + l.addi r7,r7,-1 + + l.jr r9 + l.nop + +#ifdef CONFIG_SMP +secondary_wait: + /* Doze the cpu until we are asked to run */ + /* If we dont have power management skip doze */ + l.mfspr r25,r0,SPR_UPR + l.andi r25,r25,SPR_UPR_PMP + l.sfeq r25,r0 + l.bf secondary_check_release + l.nop + + /* Setup special secondary exception handler */ + LOAD_SYMBOL_2_GPR(r3, _secondary_evbar) + tophys(r25,r3) + l.mtspr r0,r25,SPR_EVBAR + + /* Enable Interrupts */ + l.mfspr r25,r0,SPR_SR + l.ori r25,r25,SPR_SR_IEE + l.mtspr r0,r25,SPR_SR + + /* Unmask interrupts interrupts */ + l.mfspr r25,r0,SPR_PICMR + l.ori r25,r25,0xffff + l.mtspr r0,r25,SPR_PICMR + + /* Doze */ + l.mfspr r25,r0,SPR_PMR + LOAD_SYMBOL_2_GPR(r3, SPR_PMR_DME) + l.or r25,r25,r3 + l.mtspr r0,r25,SPR_PMR + + /* Wakeup - Restore exception handler */ + l.mtspr r0,r0,SPR_EVBAR + +secondary_check_release: + /* + * Check if we actually got the release signal, if not go-back to + * sleep. + */ + l.mfspr r25,r0,SPR_COREID + LOAD_SYMBOL_2_GPR(r3, secondary_release) + tophys(r4, r3) + l.lwz r3,0(r4) + l.sfeq r25,r3 + l.bnf secondary_wait + l.nop + /* fall through to secondary_init */ + +secondary_init: + /* + * set up initial ksp and current + */ + LOAD_SYMBOL_2_GPR(r10, secondary_thread_info) + tophys (r30,r10) + l.lwz r10,0(r30) + l.addi r1,r10,THREAD_SIZE + tophys (r30,r10) + l.sw TI_KSP(r30),r1 + + l.jal _ic_enable + l.nop + + l.jal _dc_enable + l.nop + + l.jal _flush_tlb + l.nop + + /* + * enable dmmu & immu + */ + l.mfspr r30,r0,SPR_SR + l.movhi r28,hi(SPR_SR_DME | SPR_SR_IME) + l.ori r28,r28,lo(SPR_SR_DME | SPR_SR_IME) + l.or r30,r30,r28 + /* + * This is a bit tricky, we need to switch over from physical addresses + * to virtual addresses on the fly. + * To do that, we first set up ESR with the IME and DME bits set. + * Then EPCR is set to secondary_start and then a l.rfe is issued to + * "jump" to that. + */ + l.mtspr r0,r30,SPR_ESR_BASE + LOAD_SYMBOL_2_GPR(r30, secondary_start) + l.mtspr r0,r30,SPR_EPCR_BASE + l.rfe + +secondary_start: + LOAD_SYMBOL_2_GPR(r30, secondary_start_kernel) + l.jr r30 + l.nop + +#endif + +/* ========================================[ cache ]=== */ + + /* alignment here so we don't change memory offsets with + * memory controller defined + */ + .align 0x2000 + +_ic_enable: + /* Check if IC present and skip enabling otherwise */ + l.mfspr r24,r0,SPR_UPR + l.andi r26,r24,SPR_UPR_ICP + l.sfeq r26,r0 + l.bf 9f + l.nop + + /* Disable IC */ + l.mfspr r6,r0,SPR_SR + l.addi r5,r0,-1 + l.xori r5,r5,SPR_SR_ICE + l.and r5,r6,r5 + l.mtspr r0,r5,SPR_SR + + /* Establish cache block size + If BS=0, 16; + If BS=1, 32; + r14 contain block size + */ + l.mfspr r24,r0,SPR_ICCFGR + l.andi r26,r24,SPR_ICCFGR_CBS + l.srli r28,r26,7 + l.ori r30,r0,16 + l.sll r14,r30,r28 + + /* Establish number of cache sets + r16 contains number of cache sets + r28 contains log(# of cache sets) + */ + l.andi r26,r24,SPR_ICCFGR_NCS + l.srli r28,r26,3 + l.ori r30,r0,1 + l.sll r16,r30,r28 + + /* Invalidate IC */ + l.addi r6,r0,0 + l.sll r5,r14,r28 +// l.mul r5,r14,r16 +// l.trap 1 +// l.addi r5,r0,IC_SIZE +1: + l.mtspr r0,r6,SPR_ICBIR + l.sfne r6,r5 + l.bf 1b + l.add r6,r6,r14 + // l.addi r6,r6,IC_LINE + + /* Enable IC */ + l.mfspr r6,r0,SPR_SR + l.ori r6,r6,SPR_SR_ICE + l.mtspr r0,r6,SPR_SR + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop + l.nop +9: + l.jr r9 + l.nop + +_dc_enable: + /* Check if DC present and skip enabling otherwise */ + l.mfspr r24,r0,SPR_UPR + l.andi r26,r24,SPR_UPR_DCP + l.sfeq r26,r0 + l.bf 9f + l.nop + + /* Disable DC */ + l.mfspr r6,r0,SPR_SR + l.addi r5,r0,-1 + l.xori r5,r5,SPR_SR_DCE + l.and r5,r6,r5 + l.mtspr r0,r5,SPR_SR + + /* Establish cache block size + If BS=0, 16; + If BS=1, 32; + r14 contain block size + */ + l.mfspr r24,r0,SPR_DCCFGR + l.andi r26,r24,SPR_DCCFGR_CBS + l.srli r28,r26,7 + l.ori r30,r0,16 + l.sll r14,r30,r28 + + /* Establish number of cache sets + r16 contains number of cache sets + r28 contains log(# of cache sets) + */ + l.andi r26,r24,SPR_DCCFGR_NCS + l.srli r28,r26,3 + l.ori r30,r0,1 + l.sll r16,r30,r28 + + /* Invalidate DC */ + l.addi r6,r0,0 + l.sll r5,r14,r28 +1: + l.mtspr r0,r6,SPR_DCBIR + l.sfne r6,r5 + l.bf 1b + l.add r6,r6,r14 + + /* Enable DC */ + l.mfspr r6,r0,SPR_SR + l.ori r6,r6,SPR_SR_DCE + l.mtspr r0,r6,SPR_SR +9: + l.jr r9 + l.nop + +/* ===============================================[ page table masks ]=== */ + +#define DTLB_UP_CONVERT_MASK 0x3fa +#define ITLB_UP_CONVERT_MASK 0x3a + +/* for SMP we'd have (this is a bit subtle, CC must be always set + * for SMP, but since we have _PAGE_PRESENT bit always defined + * we can just modify the mask) + */ +#define DTLB_SMP_CONVERT_MASK 0x3fb +#define ITLB_SMP_CONVERT_MASK 0x3b + +/* ---[ boot dtlb miss handler ]----------------------------------------- */ + +boot_dtlb_miss_handler: + +/* mask for DTLB_MR register: - (0) sets V (valid) bit, + * - (31-12) sets bits belonging to VPN (31-12) + */ +#define DTLB_MR_MASK 0xfffff001 + +/* mask for DTLB_TR register: - (2) sets CI (cache inhibit) bit, + * - (4) sets A (access) bit, + * - (5) sets D (dirty) bit, + * - (8) sets SRE (superuser read) bit + * - (9) sets SWE (superuser write) bit + * - (31-12) sets bits belonging to VPN (31-12) + */ +#define DTLB_TR_MASK 0xfffff332 + +/* These are for masking out the VPN/PPN value from the MR/TR registers... + * it's not the same as the PFN */ +#define VPN_MASK 0xfffff000 +#define PPN_MASK 0xfffff000 + + + EXCEPTION_STORE_GPR6 + +#if 0 + l.mfspr r6,r0,SPR_ESR_BASE // + l.andi r6,r6,SPR_SR_SM // are we in kernel mode ? + l.sfeqi r6,0 // r6 == 0x1 --> SM + l.bf exit_with_no_dtranslation // + l.nop +#endif + + /* this could be optimized by moving storing of + * non r6 registers here, and jumping r6 restore + * if not in supervisor mode + */ + + EXCEPTION_STORE_GPR2 + EXCEPTION_STORE_GPR3 + EXCEPTION_STORE_GPR4 + EXCEPTION_STORE_GPR5 + + l.mfspr r4,r0,SPR_EEAR_BASE // get the offending EA + +immediate_translation: + CLEAR_GPR(r6) + + l.srli r3,r4,0xd // r3 <- r4 / 8192 (sets are relative to page size (8Kb) NOT VPN size (4Kb) + + l.mfspr r6, r0, SPR_DMMUCFGR + l.andi r6, r6, SPR_DMMUCFGR_NTS + l.srli r6, r6, SPR_DMMUCFGR_NTS_OFF + l.ori r5, r0, 0x1 + l.sll r5, r5, r6 // r5 = number DMMU sets + l.addi r6, r5, -1 // r6 = nsets mask + l.and r2, r3, r6 // r2 <- r3 % NSETS_MASK + + l.or r6,r6,r4 // r6 <- r4 + l.ori r6,r6,~(VPN_MASK) // r6 <- VPN :VPN .xfff - clear up lo(r6) to 0x**** *fff + l.movhi r5,hi(DTLB_MR_MASK) // r5 <- ffff:0000.x000 + l.ori r5,r5,lo(DTLB_MR_MASK) // r5 <- ffff:1111.x001 - apply DTLB_MR_MASK + l.and r5,r5,r6 // r5 <- VPN :VPN .x001 - we have DTLBMR entry + l.mtspr r2,r5,SPR_DTLBMR_BASE(0) // set DTLBMR + + /* set up DTLB with no translation for EA <= 0xbfffffff */ + LOAD_SYMBOL_2_GPR(r6,0xbfffffff) + l.sfgeu r6,r4 // flag if r6 >= r4 (if 0xbfffffff >= EA) + l.bf 1f // goto out + l.and r3,r4,r4 // delay slot :: 24 <- r4 (if flag==1) + + tophys(r3,r4) // r3 <- PA +1: + l.ori r3,r3,~(PPN_MASK) // r3 <- PPN :PPN .xfff - clear up lo(r6) to 0x**** *fff + l.movhi r5,hi(DTLB_TR_MASK) // r5 <- ffff:0000.x000 + l.ori r5,r5,lo(DTLB_TR_MASK) // r5 <- ffff:1111.x330 - apply DTLB_MR_MASK + l.and r5,r5,r3 // r5 <- PPN :PPN .x330 - we have DTLBTR entry + l.mtspr r2,r5,SPR_DTLBTR_BASE(0) // set DTLBTR + + EXCEPTION_LOAD_GPR6 + EXCEPTION_LOAD_GPR5 + EXCEPTION_LOAD_GPR4 + EXCEPTION_LOAD_GPR3 + EXCEPTION_LOAD_GPR2 + + l.rfe // SR <- ESR, PC <- EPC + +exit_with_no_dtranslation: + /* EA out of memory or not in supervisor mode */ + EXCEPTION_LOAD_GPR6 + EXCEPTION_LOAD_GPR4 + l.j _dispatch_bus_fault + +/* ---[ boot itlb miss handler ]----------------------------------------- */ + +boot_itlb_miss_handler: + +/* mask for ITLB_MR register: - sets V (valid) bit, + * - sets bits belonging to VPN (15-12) + */ +#define ITLB_MR_MASK 0xfffff001 + +/* mask for ITLB_TR register: - sets A (access) bit, + * - sets SXE (superuser execute) bit + * - sets bits belonging to VPN (15-12) + */ +#define ITLB_TR_MASK 0xfffff050 + +/* +#define VPN_MASK 0xffffe000 +#define PPN_MASK 0xffffe000 +*/ + + + + EXCEPTION_STORE_GPR2 + EXCEPTION_STORE_GPR3 + EXCEPTION_STORE_GPR4 + EXCEPTION_STORE_GPR5 + EXCEPTION_STORE_GPR6 + +#if 0 + l.mfspr r6,r0,SPR_ESR_BASE // + l.andi r6,r6,SPR_SR_SM // are we in kernel mode ? + l.sfeqi r6,0 // r6 == 0x1 --> SM + l.bf exit_with_no_itranslation + l.nop +#endif + + + l.mfspr r4,r0,SPR_EEAR_BASE // get the offending EA + +earlyearly: + CLEAR_GPR(r6) + + l.srli r3,r4,0xd // r3 <- r4 / 8192 (sets are relative to page size (8Kb) NOT VPN size (4Kb) + + l.mfspr r6, r0, SPR_IMMUCFGR + l.andi r6, r6, SPR_IMMUCFGR_NTS + l.srli r6, r6, SPR_IMMUCFGR_NTS_OFF + l.ori r5, r0, 0x1 + l.sll r5, r5, r6 // r5 = number IMMU sets from IMMUCFGR + l.addi r6, r5, -1 // r6 = nsets mask + l.and r2, r3, r6 // r2 <- r3 % NSETS_MASK + + l.or r6,r6,r4 // r6 <- r4 + l.ori r6,r6,~(VPN_MASK) // r6 <- VPN :VPN .xfff - clear up lo(r6) to 0x**** *fff + l.movhi r5,hi(ITLB_MR_MASK) // r5 <- ffff:0000.x000 + l.ori r5,r5,lo(ITLB_MR_MASK) // r5 <- ffff:1111.x001 - apply ITLB_MR_MASK + l.and r5,r5,r6 // r5 <- VPN :VPN .x001 - we have ITLBMR entry + l.mtspr r2,r5,SPR_ITLBMR_BASE(0) // set ITLBMR + + /* + * set up ITLB with no translation for EA <= 0x0fffffff + * + * we need this for head.S mapping (EA = PA). if we move all functions + * which run with mmu enabled into entry.S, we might be able to eliminate this. + * + */ + LOAD_SYMBOL_2_GPR(r6,0x0fffffff) + l.sfgeu r6,r4 // flag if r6 >= r4 (if 0xb0ffffff >= EA) + l.bf 1f // goto out + l.and r3,r4,r4 // delay slot :: 24 <- r4 (if flag==1) + + tophys(r3,r4) // r3 <- PA +1: + l.ori r3,r3,~(PPN_MASK) // r3 <- PPN :PPN .xfff - clear up lo(r6) to 0x**** *fff + l.movhi r5,hi(ITLB_TR_MASK) // r5 <- ffff:0000.x000 + l.ori r5,r5,lo(ITLB_TR_MASK) // r5 <- ffff:1111.x050 - apply ITLB_MR_MASK + l.and r5,r5,r3 // r5 <- PPN :PPN .x050 - we have ITLBTR entry + l.mtspr r2,r5,SPR_ITLBTR_BASE(0) // set ITLBTR + + EXCEPTION_LOAD_GPR6 + EXCEPTION_LOAD_GPR5 + EXCEPTION_LOAD_GPR4 + EXCEPTION_LOAD_GPR3 + EXCEPTION_LOAD_GPR2 + + l.rfe // SR <- ESR, PC <- EPC + +exit_with_no_itranslation: + EXCEPTION_LOAD_GPR4 + EXCEPTION_LOAD_GPR6 + l.j _dispatch_bus_fault + l.nop + +/* ====================================================================== */ +/* + * Stuff below here shouldn't go into .head section... maybe this stuff + * can be moved to entry.S ??? + */ + +/* ==============================================[ DTLB miss handler ]=== */ + +/* + * Comments: + * Exception handlers are entered with MMU off so the following handler + * needs to use physical addressing + * + */ + + .text +ENTRY(dtlb_miss_handler) + EXCEPTION_STORE_GPR2 + EXCEPTION_STORE_GPR3 + EXCEPTION_STORE_GPR4 + /* + * get EA of the miss + */ + l.mfspr r2,r0,SPR_EEAR_BASE + /* + * pmd = (pmd_t *)(current_pgd + pgd_index(daddr)); + */ + GET_CURRENT_PGD(r3,r4) // r3 is current_pgd, r4 is temp + l.srli r4,r2,0x18 // >> PAGE_SHIFT + (PAGE_SHIFT - 2) + l.slli r4,r4,0x2 // to get address << 2 + l.add r3,r4,r3 // r4 is pgd_index(daddr) + /* + * if (pmd_none(*pmd)) + * goto pmd_none: + */ + tophys (r4,r3) + l.lwz r3,0x0(r4) // get *pmd value + l.sfne r3,r0 + l.bnf d_pmd_none + l.addi r3,r0,0xffffe000 // PAGE_MASK + +d_pmd_good: + /* + * pte = *pte_offset(pmd, daddr); + */ + l.lwz r4,0x0(r4) // get **pmd value + l.and r4,r4,r3 // & PAGE_MASK + l.srli r2,r2,0xd // >> PAGE_SHIFT, r2 == EEAR + l.andi r3,r2,0x7ff // (1UL << PAGE_SHIFT - 2) - 1 + l.slli r3,r3,0x2 // to get address << 2 + l.add r3,r3,r4 + l.lwz r3,0x0(r3) // this is pte at last + /* + * if (!pte_present(pte)) + */ + l.andi r4,r3,0x1 + l.sfne r4,r0 // is pte present + l.bnf d_pte_not_present + l.addi r4,r0,0xffffe3fa // PAGE_MASK | DTLB_UP_CONVERT_MASK + /* + * fill DTLB TR register + */ + l.and r4,r3,r4 // apply the mask + // Determine number of DMMU sets + l.mfspr r2, r0, SPR_DMMUCFGR + l.andi r2, r2, SPR_DMMUCFGR_NTS + l.srli r2, r2, SPR_DMMUCFGR_NTS_OFF + l.ori r3, r0, 0x1 + l.sll r3, r3, r2 // r3 = number DMMU sets DMMUCFGR + l.addi r2, r3, -1 // r2 = nsets mask + l.mfspr r3, r0, SPR_EEAR_BASE + l.srli r3, r3, 0xd // >> PAGE_SHIFT + l.and r2, r3, r2 // calc offset: & (NUM_TLB_ENTRIES-1) + //NUM_TLB_ENTRIES + l.mtspr r2,r4,SPR_DTLBTR_BASE(0) + /* + * fill DTLB MR register + */ + l.slli r3, r3, 0xd /* << PAGE_SHIFT => EA & PAGE_MASK */ + l.ori r4,r3,0x1 // set hardware valid bit: DTBL_MR entry + l.mtspr r2,r4,SPR_DTLBMR_BASE(0) + + EXCEPTION_LOAD_GPR2 + EXCEPTION_LOAD_GPR3 + EXCEPTION_LOAD_GPR4 + l.rfe +d_pmd_none: +d_pte_not_present: + EXCEPTION_LOAD_GPR2 + EXCEPTION_LOAD_GPR3 + EXCEPTION_LOAD_GPR4 + EXCEPTION_HANDLE(_dtlb_miss_page_fault_handler) + +/* ==============================================[ ITLB miss handler ]=== */ +ENTRY(itlb_miss_handler) + EXCEPTION_STORE_GPR2 + EXCEPTION_STORE_GPR3 + EXCEPTION_STORE_GPR4 + /* + * get EA of the miss + */ + l.mfspr r2,r0,SPR_EEAR_BASE + + /* + * pmd = (pmd_t *)(current_pgd + pgd_index(daddr)); + * + */ + GET_CURRENT_PGD(r3,r4) // r3 is current_pgd, r5 is temp + l.srli r4,r2,0x18 // >> PAGE_SHIFT + (PAGE_SHIFT - 2) + l.slli r4,r4,0x2 // to get address << 2 + l.add r3,r4,r3 // r4 is pgd_index(daddr) + /* + * if (pmd_none(*pmd)) + * goto pmd_none: + */ + tophys (r4,r3) + l.lwz r3,0x0(r4) // get *pmd value + l.sfne r3,r0 + l.bnf i_pmd_none + l.addi r3,r0,0xffffe000 // PAGE_MASK + +i_pmd_good: + /* + * pte = *pte_offset(pmd, iaddr); + * + */ + l.lwz r4,0x0(r4) // get **pmd value + l.and r4,r4,r3 // & PAGE_MASK + l.srli r2,r2,0xd // >> PAGE_SHIFT, r2 == EEAR + l.andi r3,r2,0x7ff // (1UL << PAGE_SHIFT - 2) - 1 + l.slli r3,r3,0x2 // to get address << 2 + l.add r3,r3,r4 + l.lwz r3,0x0(r3) // this is pte at last + /* + * if (!pte_present(pte)) + * + */ + l.andi r4,r3,0x1 + l.sfne r4,r0 // is pte present + l.bnf i_pte_not_present + l.addi r4,r0,0xffffe03a // PAGE_MASK | ITLB_UP_CONVERT_MASK + /* + * fill ITLB TR register + */ + l.and r4,r3,r4 // apply the mask + l.andi r3,r3,0x7c0 // _PAGE_EXEC | _PAGE_SRE | _PAGE_SWE | _PAGE_URE | _PAGE_UWE + l.sfeq r3,r0 + l.bf itlb_tr_fill //_workaround + // Determine number of IMMU sets + l.mfspr r2, r0, SPR_IMMUCFGR + l.andi r2, r2, SPR_IMMUCFGR_NTS + l.srli r2, r2, SPR_IMMUCFGR_NTS_OFF + l.ori r3, r0, 0x1 + l.sll r3, r3, r2 // r3 = number IMMU sets IMMUCFGR + l.addi r2, r3, -1 // r2 = nsets mask + l.mfspr r3, r0, SPR_EEAR_BASE + l.srli r3, r3, 0xd // >> PAGE_SHIFT + l.and r2, r3, r2 // calc offset: & (NUM_TLB_ENTRIES-1) + +/* + * __PHX__ :: fixme + * we should not just blindly set executable flags, + * but it does help with ping. the clean way would be to find out + * (and fix it) why stack doesn't have execution permissions + */ + +itlb_tr_fill_workaround: + l.ori r4,r4,0xc0 // | (SPR_ITLBTR_UXE | ITLBTR_SXE) +itlb_tr_fill: + l.mtspr r2,r4,SPR_ITLBTR_BASE(0) + /* + * fill DTLB MR register + */ + l.slli r3, r3, 0xd /* << PAGE_SHIFT => EA & PAGE_MASK */ + l.ori r4,r3,0x1 // set hardware valid bit: ITBL_MR entry + l.mtspr r2,r4,SPR_ITLBMR_BASE(0) + + EXCEPTION_LOAD_GPR2 + EXCEPTION_LOAD_GPR3 + EXCEPTION_LOAD_GPR4 + l.rfe + +i_pmd_none: +i_pte_not_present: + EXCEPTION_LOAD_GPR2 + EXCEPTION_LOAD_GPR3 + EXCEPTION_LOAD_GPR4 + EXCEPTION_HANDLE(_itlb_miss_page_fault_handler) + +/* ==============================================[ boot tlb handlers ]=== */ + + +/* =================================================[ debugging aids ]=== */ + + .align 64 +_immu_trampoline: + .space 64 +_immu_trampoline_top: + +#define TRAMP_SLOT_0 (0x0) +#define TRAMP_SLOT_1 (0x4) +#define TRAMP_SLOT_2 (0x8) +#define TRAMP_SLOT_3 (0xc) +#define TRAMP_SLOT_4 (0x10) +#define TRAMP_SLOT_5 (0x14) +#define TRAMP_FRAME_SIZE (0x18) + +ENTRY(_immu_trampoline_workaround) + // r2 EEA + // r6 is physical EEA + tophys(r6,r2) + + LOAD_SYMBOL_2_GPR(r5,_immu_trampoline) + tophys (r3,r5) // r3 is trampoline (physical) + + LOAD_SYMBOL_2_GPR(r4,0x15000000) + l.sw TRAMP_SLOT_0(r3),r4 + l.sw TRAMP_SLOT_1(r3),r4 + l.sw TRAMP_SLOT_4(r3),r4 + l.sw TRAMP_SLOT_5(r3),r4 + + // EPC = EEA - 0x4 + l.lwz r4,0x0(r6) // load op @ EEA + 0x0 (fc address) + l.sw TRAMP_SLOT_3(r3),r4 // store it to _immu_trampoline_data + l.lwz r4,-0x4(r6) // load op @ EEA - 0x4 (f8 address) + l.sw TRAMP_SLOT_2(r3),r4 // store it to _immu_trampoline_data + + l.srli r5,r4,26 // check opcode for write access + l.sfeqi r5,0 // l.j + l.bf 0f + l.sfeqi r5,0x11 // l.jr + l.bf 1f + l.sfeqi r5,1 // l.jal + l.bf 2f + l.sfeqi r5,0x12 // l.jalr + l.bf 3f + l.sfeqi r5,3 // l.bnf + l.bf 4f + l.sfeqi r5,4 // l.bf + l.bf 5f +99: + l.nop + l.j 99b // should never happen + l.nop 1 + + // r2 is EEA + // r3 is trampoline address (physical) + // r4 is instruction + // r6 is physical(EEA) + // + // r5 + +2: // l.jal + + /* 19 20 aa aa l.movhi r9,0xaaaa + * a9 29 bb bb l.ori r9,0xbbbb + * + * where 0xaaaabbbb is EEA + 0x4 shifted right 2 + */ + + l.addi r6,r2,0x4 // this is 0xaaaabbbb + + // l.movhi r9,0xaaaa + l.ori r5,r0,0x1920 // 0x1920 == l.movhi r9 + l.sh (TRAMP_SLOT_0+0x0)(r3),r5 + l.srli r5,r6,16 + l.sh (TRAMP_SLOT_0+0x2)(r3),r5 + + // l.ori r9,0xbbbb + l.ori r5,r0,0xa929 // 0xa929 == l.ori r9 + l.sh (TRAMP_SLOT_1+0x0)(r3),r5 + l.andi r5,r6,0xffff + l.sh (TRAMP_SLOT_1+0x2)(r3),r5 + + /* falthrough, need to set up new jump offset */ + + +0: // l.j + l.slli r6,r4,6 // original offset shifted left 6 - 2 +// l.srli r6,r6,6 // original offset shifted right 2 + + l.slli r4,r2,4 // old jump position: EEA shifted left 4 +// l.srli r4,r4,6 // old jump position: shifted right 2 + + l.addi r5,r3,0xc // new jump position (physical) + l.slli r5,r5,4 // new jump position: shifted left 4 + + // calculate new jump offset + // new_off = old_off + (old_jump - new_jump) + + l.sub r5,r4,r5 // old_jump - new_jump + l.add r5,r6,r5 // orig_off + (old_jump - new_jump) + l.srli r5,r5,6 // new offset shifted right 2 + + // r5 is new jump offset + // l.j has opcode 0x0... + l.sw TRAMP_SLOT_2(r3),r5 // write it back + + l.j trampoline_out + l.nop + +/* ----------------------------- */ + +3: // l.jalr + + /* 19 20 aa aa l.movhi r9,0xaaaa + * a9 29 bb bb l.ori r9,0xbbbb + * + * where 0xaaaabbbb is EEA + 0x4 shifted right 2 + */ + + l.addi r6,r2,0x4 // this is 0xaaaabbbb + + // l.movhi r9,0xaaaa + l.ori r5,r0,0x1920 // 0x1920 == l.movhi r9 + l.sh (TRAMP_SLOT_0+0x0)(r3),r5 + l.srli r5,r6,16 + l.sh (TRAMP_SLOT_0+0x2)(r3),r5 + + // l.ori r9,0xbbbb + l.ori r5,r0,0xa929 // 0xa929 == l.ori r9 + l.sh (TRAMP_SLOT_1+0x0)(r3),r5 + l.andi r5,r6,0xffff + l.sh (TRAMP_SLOT_1+0x2)(r3),r5 + + l.lhz r5,(TRAMP_SLOT_2+0x0)(r3) // load hi part of jump instruction + l.andi r5,r5,0x3ff // clear out opcode part + l.ori r5,r5,0x4400 // opcode changed from l.jalr -> l.jr + l.sh (TRAMP_SLOT_2+0x0)(r3),r5 // write it back + + /* falthrough */ + +1: // l.jr + l.j trampoline_out + l.nop + +/* ----------------------------- */ + +4: // l.bnf +5: // l.bf + l.slli r6,r4,6 // original offset shifted left 6 - 2 +// l.srli r6,r6,6 // original offset shifted right 2 + + l.slli r4,r2,4 // old jump position: EEA shifted left 4 +// l.srli r4,r4,6 // old jump position: shifted right 2 + + l.addi r5,r3,0xc // new jump position (physical) + l.slli r5,r5,4 // new jump position: shifted left 4 + + // calculate new jump offset + // new_off = old_off + (old_jump - new_jump) + + l.add r6,r6,r4 // (orig_off + old_jump) + l.sub r6,r6,r5 // (orig_off + old_jump) - new_jump + l.srli r6,r6,6 // new offset shifted right 2 + + // r6 is new jump offset + l.lwz r4,(TRAMP_SLOT_2+0x0)(r3) // load jump instruction + l.srli r4,r4,16 + l.andi r4,r4,0xfc00 // get opcode part + l.slli r4,r4,16 + l.or r6,r4,r6 // l.b(n)f new offset + l.sw TRAMP_SLOT_2(r3),r6 // write it back + + /* we need to add l.j to EEA + 0x8 */ + tophys (r4,r2) // may not be needed (due to shifts down_ + l.addi r4,r4,(0x8 - 0x8) // jump target = r2 + 0x8 (compensate for 0x8) + // jump position = r5 + 0x8 (0x8 compensated) + l.sub r4,r4,r5 // jump offset = target - new_position + 0x8 + + l.slli r4,r4,4 // the amount of info in imediate of jump + l.srli r4,r4,6 // jump instruction with offset + l.sw TRAMP_SLOT_4(r3),r4 // write it to 4th slot + + /* fallthrough */ + +trampoline_out: + // set up new EPC to point to our trampoline code + LOAD_SYMBOL_2_GPR(r5,_immu_trampoline) + l.mtspr r0,r5,SPR_EPCR_BASE + + // immu_trampoline is (4x) CACHE_LINE aligned + // and only 6 instructions long, + // so we need to invalidate only 2 lines + + /* Establish cache block size + If BS=0, 16; + If BS=1, 32; + r14 contain block size + */ + l.mfspr r21,r0,SPR_ICCFGR + l.andi r21,r21,SPR_ICCFGR_CBS + l.srli r21,r21,7 + l.ori r23,r0,16 + l.sll r14,r23,r21 + + l.mtspr r0,r5,SPR_ICBIR + l.add r5,r5,r14 + l.mtspr r0,r5,SPR_ICBIR + + l.jr r9 + l.nop + + +/* + * DSCR: prints a string referenced by r3. + * + * PRMS: r3 - address of the first character of null + * terminated string to be printed + * + * PREQ: UART at UART_BASE_ADD has to be initialized + * + * POST: caller should be aware that r3, r9 are changed + */ +ENTRY(_emergency_print) + EMERGENCY_PRINT_STORE_GPR4 + EMERGENCY_PRINT_STORE_GPR5 + EMERGENCY_PRINT_STORE_GPR6 + EMERGENCY_PRINT_STORE_GPR7 +2: + l.lbz r7,0(r3) + l.sfeq r7,r0 + l.bf 9f + l.nop + +// putc: + l.movhi r4,hi(UART_BASE_ADD) + + l.addi r6,r0,0x20 +1: l.lbz r5,5(r4) + l.andi r5,r5,0x20 + l.sfeq r5,r6 + l.bnf 1b + l.nop + + l.sb 0(r4),r7 + + l.addi r6,r0,0x60 +1: l.lbz r5,5(r4) + l.andi r5,r5,0x60 + l.sfeq r5,r6 + l.bnf 1b + l.nop + + /* next character */ + l.j 2b + l.addi r3,r3,0x1 + +9: + EMERGENCY_PRINT_LOAD_GPR7 + EMERGENCY_PRINT_LOAD_GPR6 + EMERGENCY_PRINT_LOAD_GPR5 + EMERGENCY_PRINT_LOAD_GPR4 + l.jr r9 + l.nop + +ENTRY(_emergency_print_nr) + EMERGENCY_PRINT_STORE_GPR4 + EMERGENCY_PRINT_STORE_GPR5 + EMERGENCY_PRINT_STORE_GPR6 + EMERGENCY_PRINT_STORE_GPR7 + EMERGENCY_PRINT_STORE_GPR8 + + l.addi r8,r0,32 // shift register + +1: /* remove leading zeros */ + l.addi r8,r8,-0x4 + l.srl r7,r3,r8 + l.andi r7,r7,0xf + + /* don't skip the last zero if number == 0x0 */ + l.sfeqi r8,0x4 + l.bf 2f + l.nop + + l.sfeq r7,r0 + l.bf 1b + l.nop + +2: + l.srl r7,r3,r8 + + l.andi r7,r7,0xf + l.sflts r8,r0 + l.bf 9f + + l.sfgtui r7,0x9 + l.bnf 8f + l.nop + l.addi r7,r7,0x27 + +8: + l.addi r7,r7,0x30 +// putc: + l.movhi r4,hi(UART_BASE_ADD) + + l.addi r6,r0,0x20 +1: l.lbz r5,5(r4) + l.andi r5,r5,0x20 + l.sfeq r5,r6 + l.bnf 1b + l.nop + + l.sb 0(r4),r7 + + l.addi r6,r0,0x60 +1: l.lbz r5,5(r4) + l.andi r5,r5,0x60 + l.sfeq r5,r6 + l.bnf 1b + l.nop + + /* next character */ + l.j 2b + l.addi r8,r8,-0x4 + +9: + EMERGENCY_PRINT_LOAD_GPR8 + EMERGENCY_PRINT_LOAD_GPR7 + EMERGENCY_PRINT_LOAD_GPR6 + EMERGENCY_PRINT_LOAD_GPR5 + EMERGENCY_PRINT_LOAD_GPR4 + l.jr r9 + l.nop + + +/* + * This should be used for debugging only. + * It messes up the Linux early serial output + * somehow, so use it sparingly and essentially + * only if you need to debug something that goes wrong + * before Linux gets the early serial going. + * + * Furthermore, you'll have to make sure you set the + * UART_DEVISOR correctly according to the system + * clock rate. + * + * + */ + + + +#define SYS_CLK 20000000 +//#define SYS_CLK 1843200 +#define OR32_CONSOLE_BAUD 115200 +#define UART_DIVISOR SYS_CLK/(16*OR32_CONSOLE_BAUD) + +ENTRY(_early_uart_init) + l.movhi r3,hi(UART_BASE_ADD) + + l.addi r4,r0,0x7 + l.sb 0x2(r3),r4 + + l.addi r4,r0,0x0 + l.sb 0x1(r3),r4 + + l.addi r4,r0,0x3 + l.sb 0x3(r3),r4 + + l.lbz r5,3(r3) + l.ori r4,r5,0x80 + l.sb 0x3(r3),r4 + l.addi r4,r0,((UART_DIVISOR>>8) & 0x000000ff) + l.sb UART_DLM(r3),r4 + l.addi r4,r0,((UART_DIVISOR) & 0x000000ff) + l.sb UART_DLL(r3),r4 + l.sb 0x3(r3),r5 + + l.jr r9 + l.nop + + .align 0x1000 + .global _secondary_evbar +_secondary_evbar: + + .space 0x800 + /* Just disable interrupts and Return */ + l.ori r3,r0,SPR_SR_SM + l.mtspr r0,r3,SPR_ESR_BASE + l.rfe + + + .section .rodata +_string_unhandled_exception: + .string "\n\rRunarunaround: Unhandled exception 0x\0" + +_string_epc_prefix: + .string ": EPC=0x\0" + +_string_nl: + .string "\n\r\0" + + +/* ========================================[ page aligned structures ]=== */ + +/* + * .data section should be page aligned + * (look into arch/openrisc/kernel/vmlinux.lds.S) + */ + .section .data,"aw" + .align 8192 + .global empty_zero_page +empty_zero_page: + .space 8192 + + .global swapper_pg_dir +swapper_pg_dir: + .space 8192 + + .global _unhandled_stack +_unhandled_stack: + .space 8192 +_unhandled_stack_top: + +/* ============================================================[ EOF ]=== */ diff --git a/arch/openrisc/kernel/irq.c b/arch/openrisc/kernel/irq.c new file mode 100644 index 000000000..5f9445eff --- /dev/null +++ b/arch/openrisc/kernel/irq.c @@ -0,0 +1,47 @@ +/* + * OpenRISC irq.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/ftrace.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/export.h> +#include <linux/irqflags.h> + +/* read interrupt enabled status */ +unsigned long arch_local_save_flags(void) +{ + return mfspr(SPR_SR) & (SPR_SR_IEE|SPR_SR_TEE); +} +EXPORT_SYMBOL(arch_local_save_flags); + +/* set interrupt enabled status */ +void arch_local_irq_restore(unsigned long flags) +{ + mtspr(SPR_SR, ((mfspr(SPR_SR) & ~(SPR_SR_IEE|SPR_SR_TEE)) | flags)); +} +EXPORT_SYMBOL(arch_local_irq_restore); + +void __init init_IRQ(void) +{ + irqchip_init(); +} + +void __irq_entry do_IRQ(struct pt_regs *regs) +{ + handle_arch_irq(regs); +} diff --git a/arch/openrisc/kernel/module.c b/arch/openrisc/kernel/module.c new file mode 100644 index 000000000..ef872ae4c --- /dev/null +++ b/arch/openrisc/kernel/module.c @@ -0,0 +1,70 @@ +/* + * OpenRISC module.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/moduleloader.h> +#include <linux/elf.h> + +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; + uint32_t *location; + uint32_t value; + + pr_debug("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 it is referring to. Note that all + undefined symbols have been resolved. */ + sym = (Elf32_Sym *)sechdrs[symindex].sh_addr + + ELF32_R_SYM(rel[i].r_info); + value = sym->st_value + rel[i].r_addend; + + switch (ELF32_R_TYPE(rel[i].r_info)) { + case R_OR32_32: + *location = value; + break; + case R_OR32_CONST: + *((uint16_t *)location + 1) = value; + break; + case R_OR32_CONSTH: + *((uint16_t *)location + 1) = value >> 16; + break; + case R_OR32_JUMPTARG: + value -= (uint32_t)location; + value >>= 2; + value &= 0x03ffffff; + value |= *location & 0xfc000000; + *location = value; + break; + default: + pr_err("module %s: Unknown relocation: %u\n", + me->name, ELF32_R_TYPE(rel[i].r_info)); + break; + } + } + + return 0; +} diff --git a/arch/openrisc/kernel/or32_ksyms.c b/arch/openrisc/kernel/or32_ksyms.c new file mode 100644 index 000000000..d7260fdb0 --- /dev/null +++ b/arch/openrisc/kernel/or32_ksyms.c @@ -0,0 +1,51 @@ +/* + * OpenRISC or32_ksyms.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/export.h> +#include <linux/elfcore.h> +#include <linux/sched.h> +#include <linux/in6.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <linux/semaphore.h> + +#include <asm/processor.h> +#include <linux/uaccess.h> +#include <asm/checksum.h> +#include <asm/io.h> +#include <asm/hardirq.h> +#include <asm/delay.h> +#include <asm/pgalloc.h> +#include <asm/pgtable.h> + +#define DECLARE_EXPORT(name) extern void name(void); EXPORT_SYMBOL(name) + +/* compiler generated symbols */ +DECLARE_EXPORT(__udivsi3); +DECLARE_EXPORT(__divsi3); +DECLARE_EXPORT(__umodsi3); +DECLARE_EXPORT(__modsi3); +DECLARE_EXPORT(__muldi3); +DECLARE_EXPORT(__ashrdi3); +DECLARE_EXPORT(__ashldi3); +DECLARE_EXPORT(__lshrdi3); +DECLARE_EXPORT(__ucmpdi2); + +EXPORT_SYMBOL(empty_zero_page); +EXPORT_SYMBOL(__copy_tofrom_user); +EXPORT_SYMBOL(__clear_user); +EXPORT_SYMBOL(memset); diff --git a/arch/openrisc/kernel/process.c b/arch/openrisc/kernel/process.c new file mode 100644 index 000000000..8739b6d75 --- /dev/null +++ b/arch/openrisc/kernel/process.c @@ -0,0 +1,284 @@ +/* + * OpenRISC process.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This file handles the architecture-dependent parts of process handling... + */ + +#define __KERNEL_SYSCALLS__ +#include <stdarg.h> + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/sched/debug.h> +#include <linux/sched/task.h> +#include <linux/sched/task_stack.h> +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/elfcore.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/init_task.h> +#include <linux/mqueue.h> +#include <linux/fs.h> + +#include <linux/uaccess.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/processor.h> +#include <asm/spr_defs.h> + +#include <linux/smp.h> + +/* + * Pointer to Current thread info structure. + * + * Used at user space -> kernel transitions. + */ +struct thread_info *current_thread_info_set[NR_CPUS] = { &init_thread_info, }; + +void machine_restart(void) +{ + printk(KERN_INFO "*** MACHINE RESTART ***\n"); + __asm__("l.nop 1"); +} + +/* + * Similar to machine_power_off, but don't shut off power. Add code + * here to freeze the system for e.g. post-mortem debug purpose when + * possible. This halt has nothing to do with the idle halt. + */ +void machine_halt(void) +{ + printk(KERN_INFO "*** MACHINE HALT ***\n"); + __asm__("l.nop 1"); +} + +/* If or when software power-off is implemented, add code here. */ +void machine_power_off(void) +{ + printk(KERN_INFO "*** MACHINE POWER OFF ***\n"); + __asm__("l.nop 1"); +} + +/* + * Send the doze signal to the cpu if available. + * Make sure, that all interrupts are enabled + */ +void arch_cpu_idle(void) +{ + local_irq_enable(); + if (mfspr(SPR_UPR) & SPR_UPR_PMP) + mtspr(SPR_PMR, mfspr(SPR_PMR) | SPR_PMR_DME); +} + +void (*pm_power_off) (void) = machine_power_off; +EXPORT_SYMBOL(pm_power_off); + +/* + * When a process does an "exec", machine state like FPU and debug + * registers need to be reset. This is a hook function for that. + * Currently we don't have any such state to reset, so this is empty. + */ +void flush_thread(void) +{ +} + +void show_regs(struct pt_regs *regs) +{ + extern void show_registers(struct pt_regs *regs); + + show_regs_print_info(KERN_DEFAULT); + /* __PHX__ cleanup this mess */ + show_registers(regs); +} + +void release_thread(struct task_struct *dead_task) +{ +} + +/* + * Copy the thread-specific (arch specific) info from the current + * process to the new one p + */ +extern asmlinkage void ret_from_fork(void); + +/* + * copy_thread + * @clone_flags: flags + * @usp: user stack pointer or fn for kernel thread + * @arg: arg to fn for kernel thread; always NULL for userspace thread + * @p: the newly created task + * @regs: CPU context to copy for userspace thread; always NULL for kthread + * + * At the top of a newly initialized kernel stack are two stacked pt_reg + * structures. The first (topmost) is the userspace context of the thread. + * The second is the kernelspace context of the thread. + * + * A kernel thread will not be returning to userspace, so the topmost pt_regs + * struct can be uninitialized; it _does_ need to exist, though, because + * a kernel thread can become a userspace thread by doing a kernel_execve, in + * which case the topmost context will be initialized and used for 'returning' + * to userspace. + * + * The second pt_reg struct needs to be initialized to 'return' to + * ret_from_fork. A kernel thread will need to set r20 to the address of + * a function to call into (with arg in r22); userspace threads need to set + * r20 to NULL in which case ret_from_fork will just continue a return to + * userspace. + * + * A kernel thread 'fn' may return; this is effectively what happens when + * kernel_execve is called. In that case, the userspace pt_regs must have + * been initialized (which kernel_execve takes care of, see start_thread + * below); ret_from_fork will then continue its execution causing the + * 'kernel thread' to return to userspace as a userspace thread. + */ + +int +copy_thread(unsigned long clone_flags, unsigned long usp, + unsigned long arg, struct task_struct *p) +{ + struct pt_regs *userregs; + struct pt_regs *kregs; + unsigned long sp = (unsigned long)task_stack_page(p) + THREAD_SIZE; + unsigned long top_of_kernel_stack; + + top_of_kernel_stack = sp; + + /* Locate userspace context on stack... */ + sp -= STACK_FRAME_OVERHEAD; /* redzone */ + sp -= sizeof(struct pt_regs); + userregs = (struct pt_regs *) sp; + + /* ...and kernel context */ + sp -= STACK_FRAME_OVERHEAD; /* redzone */ + sp -= sizeof(struct pt_regs); + kregs = (struct pt_regs *)sp; + + if (unlikely(p->flags & PF_KTHREAD)) { + memset(kregs, 0, sizeof(struct pt_regs)); + kregs->gpr[20] = usp; /* fn, kernel thread */ + kregs->gpr[22] = arg; + } else { + *userregs = *current_pt_regs(); + + if (usp) + userregs->sp = usp; + + /* + * For CLONE_SETTLS set "tp" (r10) to the TLS pointer passed to sys_clone. + * + * The kernel entry is: + * int clone (long flags, void *child_stack, int *parent_tid, + * int *child_tid, struct void *tls) + * + * This makes the source r7 in the kernel registers. + */ + if (clone_flags & CLONE_SETTLS) + userregs->gpr[10] = userregs->gpr[7]; + + userregs->gpr[11] = 0; /* Result from fork() */ + + kregs->gpr[20] = 0; /* Userspace thread */ + } + + /* + * _switch wants the kernel stack page in pt_regs->sp so that it + * can restore it to thread_info->ksp... see _switch for details. + */ + kregs->sp = top_of_kernel_stack; + kregs->gpr[9] = (unsigned long)ret_from_fork; + + task_thread_info(p)->ksp = (unsigned long)kregs; + + return 0; +} + +/* + * Set up a thread for executing a new program + */ +void start_thread(struct pt_regs *regs, unsigned long pc, unsigned long sp) +{ + unsigned long sr = mfspr(SPR_SR) & ~SPR_SR_SM; + + memset(regs, 0, sizeof(struct pt_regs)); + + regs->pc = pc; + regs->sr = sr; + regs->sp = sp; +} + +/* Fill in the fpu structure for a core dump. */ +int dump_fpu(struct pt_regs *regs, elf_fpregset_t * fpu) +{ + /* TODO */ + return 0; +} + +extern struct thread_info *_switch(struct thread_info *old_ti, + struct thread_info *new_ti); +extern int lwa_flag; + +struct task_struct *__switch_to(struct task_struct *old, + struct task_struct *new) +{ + struct task_struct *last; + struct thread_info *new_ti, *old_ti; + unsigned long flags; + + local_irq_save(flags); + + /* current_set is an array of saved current pointers + * (one for each cpu). we need them at user->kernel transition, + * while we save them at kernel->user transition + */ + new_ti = new->stack; + old_ti = old->stack; + + lwa_flag = 0; + + current_thread_info_set[smp_processor_id()] = new_ti; + last = (_switch(old_ti, new_ti))->task; + + local_irq_restore(flags); + + return last; +} + +/* + * Write out registers in core dump format, as defined by the + * struct user_regs_struct + */ +void dump_elf_thread(elf_greg_t *dest, struct pt_regs* regs) +{ + dest[0] = 0; /* r0 */ + memcpy(dest+1, regs->gpr+1, 31*sizeof(unsigned long)); + dest[32] = regs->pc; + dest[33] = regs->sr; + dest[34] = 0; + dest[35] = 0; +} + +unsigned long get_wchan(struct task_struct *p) +{ + /* TODO */ + + return 0; +} diff --git a/arch/openrisc/kernel/prom.c b/arch/openrisc/kernel/prom.c new file mode 100644 index 000000000..6a44340d1 --- /dev/null +++ b/arch/openrisc/kernel/prom.c @@ -0,0 +1,32 @@ +/* + * OpenRISC prom.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Architecture specific procedures for creating, accessing and + * interpreting the device tree. + * + */ + +#include <linux/init.h> +#include <linux/types.h> +#include <linux/memblock.h> +#include <linux/of_fdt.h> + +#include <asm/page.h> + +void __init early_init_devtree(void *params) +{ + early_init_dt_scan(params); + memblock_allow_resize(); +} diff --git a/arch/openrisc/kernel/ptrace.c b/arch/openrisc/kernel/ptrace.c new file mode 100644 index 000000000..eb97a8e7c --- /dev/null +++ b/arch/openrisc/kernel/ptrace.c @@ -0,0 +1,205 @@ +/* + * OpenRISC ptrace.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2005 Gyorgy Jeney <nog@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/sched/task_stack.h> +#include <linux/string.h> + +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/audit.h> +#include <linux/regset.h> +#include <linux/tracehook.h> +#include <linux/elf.h> + +#include <asm/thread_info.h> +#include <asm/segment.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +/* + * Copy the thread state to a regset that can be interpreted by userspace. + * + * It doesn't matter what our internal pt_regs structure looks like. The + * important thing is that we export a consistent view of the thread state + * to userspace. As such, we need to make sure that the regset remains + * ABI compatible as defined by the struct user_regs_struct: + * + * (Each item is a 32-bit word) + * r0 = 0 (exported for clarity) + * 31 GPRS r1-r31 + * PC (Program counter) + * SR (Supervision register) + */ +static int genregs_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user * ubuf) +{ + const struct pt_regs *regs = task_pt_regs(target); + int ret; + + /* r0 */ + ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, 0, 4); + + if (!ret) + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + regs->gpr+1, 4, 4*32); + if (!ret) + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + ®s->pc, 4*32, 4*33); + if (!ret) + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + ®s->sr, 4*33, 4*34); + if (!ret) + ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, + 4*34, -1); + + return ret; +} + +/* + * Set the thread state from a regset passed in via ptrace + */ +static int genregs_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, const void __user * ubuf) +{ + struct pt_regs *regs = task_pt_regs(target); + int ret; + + /* ignore r0 */ + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, 0, 4); + /* r1 - r31 */ + if (!ret) + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + regs->gpr+1, 4, 4*32); + /* PC */ + if (!ret) + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + ®s->pc, 4*32, 4*33); + /* + * Skip SR and padding... userspace isn't allowed to changes bits in + * the Supervision register + */ + if (!ret) + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + 4*33, -1); + + return ret; +} + +/* + * Define the register sets available on OpenRISC under Linux + */ +enum or1k_regset { + REGSET_GENERAL, +}; + +static const struct user_regset or1k_regsets[] = { + [REGSET_GENERAL] = { + .core_note_type = NT_PRSTATUS, + .n = ELF_NGREG, + .size = sizeof(long), + .align = sizeof(long), + .get = genregs_get, + .set = genregs_set, + }, +}; + +static const struct user_regset_view user_or1k_native_view = { + .name = "or1k", + .e_machine = EM_OPENRISC, + .regsets = or1k_regsets, + .n = ARRAY_SIZE(or1k_regsets), +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ + return &user_or1k_native_view; +} + +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + + +/* + * Called by kernel/ptrace.c when detaching.. + * + * Make sure the single step bit is not set. + */ +void ptrace_disable(struct task_struct *child) +{ + pr_debug("ptrace_disable(): TODO\n"); + + user_disable_single_step(child); + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); +} + +long arch_ptrace(struct task_struct *child, long request, unsigned long addr, + unsigned long data) +{ + int ret; + + switch (request) { + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +/* + * Notification of system call entry/exit + * - triggered by current->work.syscall_trace + */ +asmlinkage long do_syscall_trace_enter(struct pt_regs *regs) +{ + long ret = 0; + + if (test_thread_flag(TIF_SYSCALL_TRACE) && + tracehook_report_syscall_entry(regs)) + /* + * Tracing decided this syscall should not happen. + * We'll return a bogus call number to get an ENOSYS + * error, but leave the original number in <something>. + */ + ret = -1L; + + audit_syscall_entry(regs->gpr[11], regs->gpr[3], regs->gpr[4], + regs->gpr[5], regs->gpr[6]); + + return ret ? : regs->gpr[11]; +} + +asmlinkage void do_syscall_trace_leave(struct pt_regs *regs) +{ + int step; + + audit_syscall_exit(regs); + + step = test_thread_flag(TIF_SINGLESTEP); + if (step || test_thread_flag(TIF_SYSCALL_TRACE)) + tracehook_report_syscall_exit(regs, step); +} diff --git a/arch/openrisc/kernel/setup.c b/arch/openrisc/kernel/setup.c new file mode 100644 index 000000000..f3a7375ac --- /dev/null +++ b/arch/openrisc/kernel/setup.c @@ -0,0 +1,415 @@ +/* + * OpenRISC setup.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This file handles the architecture-dependent parts of initialization + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/seq_file.h> +#include <linux/serial.h> +#include <linux/initrd.h> +#include <linux/of_fdt.h> +#include <linux/of.h> +#include <linux/memblock.h> +#include <linux/device.h> + +#include <asm/sections.h> +#include <asm/segment.h> +#include <asm/pgtable.h> +#include <asm/types.h> +#include <asm/setup.h> +#include <asm/io.h> +#include <asm/cpuinfo.h> +#include <asm/delay.h> + +#include "vmlinux.h" + +static void __init setup_memory(void) +{ + unsigned long ram_start_pfn; + unsigned long ram_end_pfn; + phys_addr_t memory_start, memory_end; + struct memblock_region *region; + + memory_end = memory_start = 0; + + /* Find main memory where is the kernel, we assume its the only one */ + for_each_memblock(memory, region) { + memory_start = region->base; + memory_end = region->base + region->size; + printk(KERN_INFO "%s: Memory: 0x%x-0x%x\n", __func__, + memory_start, memory_end); + } + + if (!memory_end) { + panic("No memory!"); + } + + ram_start_pfn = PFN_UP(memory_start); + ram_end_pfn = PFN_DOWN(memblock_end_of_DRAM()); + + /* setup bootmem globals (we use no_bootmem, but mm still depends on this) */ + min_low_pfn = ram_start_pfn; + max_low_pfn = ram_end_pfn; + max_pfn = ram_end_pfn; + + /* + * initialize the boot-time allocator (with low memory only). + * + * This makes the memory from the end of the kernel to the end of + * RAM usable. + */ + memblock_reserve(__pa(_stext), _end - _stext); + + early_init_fdt_reserve_self(); + early_init_fdt_scan_reserved_mem(); + + memblock_dump_all(); +} + +struct cpuinfo_or1k cpuinfo_or1k[NR_CPUS]; + +static void print_cpuinfo(void) +{ + unsigned long upr = mfspr(SPR_UPR); + unsigned long vr = mfspr(SPR_VR); + unsigned int version; + unsigned int revision; + struct cpuinfo_or1k *cpuinfo = &cpuinfo_or1k[smp_processor_id()]; + + version = (vr & SPR_VR_VER) >> 24; + revision = (vr & SPR_VR_REV); + + printk(KERN_INFO "CPU: OpenRISC-%x (revision %d) @%d MHz\n", + version, revision, cpuinfo->clock_frequency / 1000000); + + if (!(upr & SPR_UPR_UP)) { + printk(KERN_INFO + "-- no UPR register... unable to detect configuration\n"); + return; + } + + if (upr & SPR_UPR_DCP) + printk(KERN_INFO + "-- dcache: %4d bytes total, %2d bytes/line, %d way(s)\n", + cpuinfo->dcache_size, cpuinfo->dcache_block_size, + cpuinfo->dcache_ways); + else + printk(KERN_INFO "-- dcache disabled\n"); + if (upr & SPR_UPR_ICP) + printk(KERN_INFO + "-- icache: %4d bytes total, %2d bytes/line, %d way(s)\n", + cpuinfo->icache_size, cpuinfo->icache_block_size, + cpuinfo->icache_ways); + else + printk(KERN_INFO "-- icache disabled\n"); + + if (upr & SPR_UPR_DMP) + printk(KERN_INFO "-- dmmu: %4d entries, %lu way(s)\n", + 1 << ((mfspr(SPR_DMMUCFGR) & SPR_DMMUCFGR_NTS) >> 2), + 1 + (mfspr(SPR_DMMUCFGR) & SPR_DMMUCFGR_NTW)); + if (upr & SPR_UPR_IMP) + printk(KERN_INFO "-- immu: %4d entries, %lu way(s)\n", + 1 << ((mfspr(SPR_IMMUCFGR) & SPR_IMMUCFGR_NTS) >> 2), + 1 + (mfspr(SPR_IMMUCFGR) & SPR_IMMUCFGR_NTW)); + + printk(KERN_INFO "-- additional features:\n"); + if (upr & SPR_UPR_DUP) + printk(KERN_INFO "-- debug unit\n"); + if (upr & SPR_UPR_PCUP) + printk(KERN_INFO "-- performance counters\n"); + if (upr & SPR_UPR_PMP) + printk(KERN_INFO "-- power management\n"); + if (upr & SPR_UPR_PICP) + printk(KERN_INFO "-- PIC\n"); + if (upr & SPR_UPR_TTP) + printk(KERN_INFO "-- timer\n"); + if (upr & SPR_UPR_CUP) + printk(KERN_INFO "-- custom unit(s)\n"); +} + +static struct device_node *setup_find_cpu_node(int cpu) +{ + u32 hwid; + struct device_node *cpun; + struct device_node *cpus = of_find_node_by_path("/cpus"); + + for_each_available_child_of_node(cpus, cpun) { + if (of_property_read_u32(cpun, "reg", &hwid)) + continue; + if (hwid == cpu) + return cpun; + } + + return NULL; +} + +void __init setup_cpuinfo(void) +{ + struct device_node *cpu; + unsigned long iccfgr, dccfgr; + unsigned long cache_set_size; + int cpu_id = smp_processor_id(); + struct cpuinfo_or1k *cpuinfo = &cpuinfo_or1k[cpu_id]; + + cpu = setup_find_cpu_node(cpu_id); + if (!cpu) + panic("Couldn't find CPU%d in device tree...\n", cpu_id); + + iccfgr = mfspr(SPR_ICCFGR); + cpuinfo->icache_ways = 1 << (iccfgr & SPR_ICCFGR_NCW); + cache_set_size = 1 << ((iccfgr & SPR_ICCFGR_NCS) >> 3); + cpuinfo->icache_block_size = 16 << ((iccfgr & SPR_ICCFGR_CBS) >> 7); + cpuinfo->icache_size = + cache_set_size * cpuinfo->icache_ways * cpuinfo->icache_block_size; + + dccfgr = mfspr(SPR_DCCFGR); + cpuinfo->dcache_ways = 1 << (dccfgr & SPR_DCCFGR_NCW); + cache_set_size = 1 << ((dccfgr & SPR_DCCFGR_NCS) >> 3); + cpuinfo->dcache_block_size = 16 << ((dccfgr & SPR_DCCFGR_CBS) >> 7); + cpuinfo->dcache_size = + cache_set_size * cpuinfo->dcache_ways * cpuinfo->dcache_block_size; + + if (of_property_read_u32(cpu, "clock-frequency", + &cpuinfo->clock_frequency)) { + printk(KERN_WARNING + "Device tree missing CPU 'clock-frequency' parameter." + "Assuming frequency 25MHZ" + "This is probably not what you want."); + } + + cpuinfo->coreid = mfspr(SPR_COREID); + + of_node_put(cpu); + + print_cpuinfo(); +} + +/** + * or32_early_setup + * + * Handles the pointer to the device tree that this kernel is to use + * for establishing the available platform devices. + * + * Falls back on built-in device tree in case null pointer is passed. + */ + +void __init or32_early_setup(void *fdt) +{ + if (fdt) + pr_info("FDT at %p\n", fdt); + else { + fdt = __dtb_start; + pr_info("Compiled-in FDT at %p\n", fdt); + } + early_init_devtree(fdt); +} + +static inline unsigned long extract_value_bits(unsigned long reg, + short bit_nr, short width) +{ + return (reg >> bit_nr) & (0 << width); +} + +static inline unsigned long extract_value(unsigned long reg, unsigned long mask) +{ + while (!(mask & 0x1)) { + reg = reg >> 1; + mask = mask >> 1; + } + return mask & reg; +} + +void __init detect_unit_config(unsigned long upr, unsigned long mask, + char *text, void (*func) (void)) +{ + if (text != NULL) + printk("%s", text); + + if (upr & mask) { + if (func != NULL) + func(); + else + printk("present\n"); + } else + printk("not present\n"); +} + +/* + * calibrate_delay + * + * Lightweight calibrate_delay implementation that calculates loops_per_jiffy + * from the clock frequency passed in via the device tree + * + */ + +void calibrate_delay(void) +{ + const int *val; + struct device_node *cpu = setup_find_cpu_node(smp_processor_id()); + + val = of_get_property(cpu, "clock-frequency", NULL); + if (!val) + panic("no cpu 'clock-frequency' parameter in device tree"); + loops_per_jiffy = *val / HZ; + pr_cont("%lu.%02lu BogoMIPS (lpj=%lu)\n", + loops_per_jiffy / (500000 / HZ), + (loops_per_jiffy / (5000 / HZ)) % 100, loops_per_jiffy); + + of_node_put(cpu); +} + +void __init setup_arch(char **cmdline_p) +{ + unflatten_and_copy_device_tree(); + + setup_cpuinfo(); + +#ifdef CONFIG_SMP + smp_init_cpus(); +#endif + + /* process 1's initial memory region is the kernel code/data */ + init_mm.start_code = (unsigned long)_stext; + init_mm.end_code = (unsigned long)_etext; + init_mm.end_data = (unsigned long)_edata; + init_mm.brk = (unsigned long)_end; + +#ifdef CONFIG_BLK_DEV_INITRD + initrd_start = (unsigned long)&__initrd_start; + initrd_end = (unsigned long)&__initrd_end; + if (initrd_start == initrd_end) { + initrd_start = 0; + initrd_end = 0; + } + initrd_below_start_ok = 1; +#endif + + /* setup memblock allocator */ + setup_memory(); + + /* paging_init() sets up the MMU and marks all pages as reserved */ + paging_init(); + +#if defined(CONFIG_VT) && defined(CONFIG_DUMMY_CONSOLE) + if (!conswitchp) + conswitchp = &dummy_con; +#endif + + *cmdline_p = boot_command_line; + + printk(KERN_INFO "OpenRISC Linux -- http://openrisc.io\n"); +} + +static int show_cpuinfo(struct seq_file *m, void *v) +{ + unsigned int vr, cpucfgr; + unsigned int avr; + unsigned int version; + struct cpuinfo_or1k *cpuinfo = v; + + vr = mfspr(SPR_VR); + cpucfgr = mfspr(SPR_CPUCFGR); + +#ifdef CONFIG_SMP + seq_printf(m, "processor\t\t: %d\n", cpuinfo->coreid); +#endif + if (vr & SPR_VR_UVRP) { + vr = mfspr(SPR_VR2); + version = vr & SPR_VR2_VER; + avr = mfspr(SPR_AVR); + seq_printf(m, "cpu architecture\t: " + "OpenRISC 1000 (%d.%d-rev%d)\n", + (avr >> 24) & 0xff, + (avr >> 16) & 0xff, + (avr >> 8) & 0xff); + seq_printf(m, "cpu implementation id\t: 0x%x\n", + (vr & SPR_VR2_CPUID) >> 24); + seq_printf(m, "cpu version\t\t: 0x%x\n", version); + } else { + version = (vr & SPR_VR_VER) >> 24; + seq_printf(m, "cpu\t\t\t: OpenRISC-%x\n", version); + seq_printf(m, "revision\t\t: %d\n", vr & SPR_VR_REV); + } + seq_printf(m, "frequency\t\t: %ld\n", loops_per_jiffy * HZ); + seq_printf(m, "dcache size\t\t: %d bytes\n", cpuinfo->dcache_size); + seq_printf(m, "dcache block size\t: %d bytes\n", + cpuinfo->dcache_block_size); + seq_printf(m, "dcache ways\t\t: %d\n", cpuinfo->dcache_ways); + seq_printf(m, "icache size\t\t: %d bytes\n", cpuinfo->icache_size); + seq_printf(m, "icache block size\t: %d bytes\n", + cpuinfo->icache_block_size); + seq_printf(m, "icache ways\t\t: %d\n", cpuinfo->icache_ways); + seq_printf(m, "immu\t\t\t: %d entries, %lu ways\n", + 1 << ((mfspr(SPR_DMMUCFGR) & SPR_DMMUCFGR_NTS) >> 2), + 1 + (mfspr(SPR_DMMUCFGR) & SPR_DMMUCFGR_NTW)); + seq_printf(m, "dmmu\t\t\t: %d entries, %lu ways\n", + 1 << ((mfspr(SPR_IMMUCFGR) & SPR_IMMUCFGR_NTS) >> 2), + 1 + (mfspr(SPR_IMMUCFGR) & SPR_IMMUCFGR_NTW)); + seq_printf(m, "bogomips\t\t: %lu.%02lu\n", + (loops_per_jiffy * HZ) / 500000, + ((loops_per_jiffy * HZ) / 5000) % 100); + + seq_puts(m, "features\t\t: "); + seq_printf(m, "%s ", cpucfgr & SPR_CPUCFGR_OB32S ? "orbis32" : ""); + seq_printf(m, "%s ", cpucfgr & SPR_CPUCFGR_OB64S ? "orbis64" : ""); + seq_printf(m, "%s ", cpucfgr & SPR_CPUCFGR_OF32S ? "orfpx32" : ""); + seq_printf(m, "%s ", cpucfgr & SPR_CPUCFGR_OF64S ? "orfpx64" : ""); + seq_printf(m, "%s ", cpucfgr & SPR_CPUCFGR_OV64S ? "orvdx64" : ""); + seq_puts(m, "\n"); + + seq_puts(m, "\n"); + + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + *pos = cpumask_next(*pos - 1, cpu_online_mask); + if ((*pos) < nr_cpu_ids) + return &cpuinfo_or1k[*pos]; + return 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) +{ +} + +const struct seq_operations cpuinfo_op = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = show_cpuinfo, +}; diff --git a/arch/openrisc/kernel/signal.c b/arch/openrisc/kernel/signal.c new file mode 100644 index 000000000..265f10fb3 --- /dev/null +++ b/arch/openrisc/kernel/signal.c @@ -0,0 +1,326 @@ +/* + * OpenRISC signal.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.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/tracehook.h> + +#include <asm/processor.h> +#include <asm/syscall.h> +#include <asm/ucontext.h> +#include <linux/uaccess.h> + +#define DEBUG_SIG 0 + +struct rt_sigframe { + struct siginfo info; + struct ucontext uc; + unsigned char retcode[16]; /* trampoline code */ +}; + +static int restore_sigcontext(struct pt_regs *regs, + struct sigcontext __user *sc) +{ + int err = 0; + + /* Always make any pending restarted system calls return -EINTR */ + current->restart_block.fn = do_no_restart_syscall; + + /* + * Restore the regs from &sc->regs. + * (sc is already checked for VERIFY_READ since the sigframe was + * checked in sys_sigreturn previously) + */ + err |= __copy_from_user(regs, sc->regs.gpr, 32 * sizeof(unsigned long)); + err |= __copy_from_user(®s->pc, &sc->regs.pc, sizeof(unsigned long)); + err |= __copy_from_user(®s->sr, &sc->regs.sr, sizeof(unsigned long)); + + /* make sure the SM-bit is cleared so user-mode cannot fool us */ + regs->sr &= ~SPR_SR_SM; + + regs->orig_gpr11 = -1; /* Avoid syscall restart checks */ + + /* TODO: the other ports use regs->orig_XX to disable syscall checks + * after this completes, but we don't use that mechanism. maybe we can + * use it now ? + */ + + return err; +} + +asmlinkage long _sys_rt_sigreturn(struct pt_regs *regs) +{ + struct rt_sigframe *frame = (struct rt_sigframe __user *)regs->sp; + sigset_t set; + + /* + * Since we stacked the signal on a dword boundary, + * then frame should be dword aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (((long)frame) & 3) + goto badframe; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + set_current_blocked(&set); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + goto badframe; + + if (restore_altstack(&frame->uc.uc_stack)) + goto badframe; + + return regs->gpr[11]; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* + * Set up a signal frame. + */ + +static int setup_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc) +{ + int err = 0; + + /* copy the regs */ + /* There should be no need to save callee-saved registers here... + * ...but we save them anyway. Revisit this + */ + err |= __copy_to_user(sc->regs.gpr, regs, 32 * sizeof(unsigned long)); + err |= __copy_to_user(&sc->regs.pc, ®s->pc, sizeof(unsigned long)); + err |= __copy_to_user(&sc->regs.sr, ®s->sr, sizeof(unsigned long)); + + return err; +} + +static inline unsigned long align_sigframe(unsigned long sp) +{ + return sp & ~3UL; +} + +/* + * Work out where the signal frame should go. It's either on the user stack + * or the alternate stack. + */ + +static inline void __user *get_sigframe(struct ksignal *ksig, + struct pt_regs *regs, size_t frame_size) +{ + unsigned long sp = regs->sp; + + /* redzone */ + sp -= STACK_FRAME_OVERHEAD; + sp = sigsp(sp, ksig); + sp = align_sigframe(sp - frame_size); + + return (void __user *)sp; +} + +/* grab and setup a signal frame. + * + * basically we stack a lot of state info, and arrange for the + * user-mode program to return to the kernel using either a + * trampoline which performs the syscall sigreturn, or a provided + * user-mode trampoline. + */ +static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, + struct pt_regs *regs) +{ + struct rt_sigframe *frame; + unsigned long return_ip; + int err = 0; + + frame = get_sigframe(ksig, regs, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + return -EFAULT; + + /* Create siginfo. */ + if (ksig->ka.sa.sa_flags & SA_SIGINFO) + err |= copy_siginfo_to_user(&frame->info, &ksig->info); + + /* Create the ucontext. */ + err |= __put_user(0, &frame->uc.uc_flags); + err |= __put_user(NULL, &frame->uc.uc_link); + err |= __save_altstack(&frame->uc.uc_stack, regs->sp); + err |= setup_sigcontext(regs, &frame->uc.uc_mcontext); + + err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + if (err) + return -EFAULT; + + /* trampoline - the desired return ip is the retcode itself */ + return_ip = (unsigned long)&frame->retcode; + /* This is: + l.ori r11,r0,__NR_sigreturn + l.sys 1 + */ + err |= __put_user(0xa960, (short *)(frame->retcode + 0)); + err |= __put_user(__NR_rt_sigreturn, (short *)(frame->retcode + 2)); + err |= __put_user(0x20000001, (unsigned long *)(frame->retcode + 4)); + err |= __put_user(0x15000000, (unsigned long *)(frame->retcode + 8)); + + if (err) + return -EFAULT; + + /* Set up registers for signal handler */ + regs->pc = (unsigned long)ksig->ka.sa.sa_handler; /* what we enter NOW */ + regs->gpr[9] = (unsigned long)return_ip; /* what we enter LATER */ + regs->gpr[3] = (unsigned long)ksig->sig; /* arg 1: signo */ + regs->gpr[4] = (unsigned long)&frame->info; /* arg 2: (siginfo_t*) */ + regs->gpr[5] = (unsigned long)&frame->uc; /* arg 3: ucontext */ + + /* actually move the usp to reflect the stacked frame */ + regs->sp = (unsigned long)frame; + + return 0; +} + +static inline void +handle_signal(struct ksignal *ksig, struct pt_regs *regs) +{ + int ret; + + ret = setup_rt_frame(ksig, sigmask_to_save(), regs); + + signal_setup_done(ret, ksig, test_thread_flag(TIF_SINGLESTEP)); +} + +/* + * Note that 'init' is a special process: it doesn't get signals it doesn't + * want to handle. Thus you cannot kill init even with a SIGKILL even by + * mistake. + * + * Also note that the regs structure given here as an argument, is the latest + * pushed pt_regs. It may or may not be the same as the first pushed registers + * when the initial usermode->kernelmode transition took place. Therefore + * we can use user_mode(regs) to see if we came directly from kernel or user + * mode below. + */ + +int do_signal(struct pt_regs *regs, int syscall) +{ + struct ksignal ksig; + unsigned long continue_addr = 0; + unsigned long restart_addr = 0; + unsigned long retval = 0; + int restart = 0; + + if (syscall) { + continue_addr = regs->pc; + restart_addr = continue_addr - 4; + retval = regs->gpr[11]; + + /* + * Setup syscall restart here so that a debugger will + * see the already changed PC. + */ + switch (retval) { + case -ERESTART_RESTARTBLOCK: + restart = -2; + /* Fall through */ + case -ERESTARTNOHAND: + case -ERESTARTSYS: + case -ERESTARTNOINTR: + restart++; + regs->gpr[11] = regs->orig_gpr11; + regs->pc = restart_addr; + break; + } + } + + /* + * Get the signal to deliver. During the call to get_signal the + * debugger may change all our registers so we may need to revert + * the decision to restart the syscall; specifically, if the PC is + * changed, don't restart the syscall. + */ + if (get_signal(&ksig)) { + if (unlikely(restart) && regs->pc == restart_addr) { + if (retval == -ERESTARTNOHAND || + retval == -ERESTART_RESTARTBLOCK + || (retval == -ERESTARTSYS + && !(ksig.ka.sa.sa_flags & SA_RESTART))) { + /* No automatic restart */ + regs->gpr[11] = -EINTR; + regs->pc = continue_addr; + } + } + handle_signal(&ksig, regs); + } else { + /* no handler */ + restore_saved_sigmask(); + /* + * Restore pt_regs PC as syscall restart will be handled by + * kernel without return to userspace + */ + if (unlikely(restart) && regs->pc == restart_addr) { + regs->pc = continue_addr; + return restart; + } + } + + return 0; +} + +asmlinkage int +do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall) +{ + do { + if (likely(thread_flags & _TIF_NEED_RESCHED)) { + schedule(); + } else { + if (unlikely(!user_mode(regs))) + return 0; + local_irq_enable(); + if (thread_flags & _TIF_SIGPENDING) { + int restart = do_signal(regs, syscall); + if (unlikely(restart)) { + /* + * Restart without handlers. + * Deal with it without leaving + * the kernel space. + */ + return restart; + } + syscall = 0; + } else { + clear_thread_flag(TIF_NOTIFY_RESUME); + tracehook_notify_resume(regs); + } + } + local_irq_disable(); + thread_flags = current_thread_info()->flags; + } while (thread_flags & _TIF_WORK_MASK); + return 0; +} diff --git a/arch/openrisc/kernel/smp.c b/arch/openrisc/kernel/smp.c new file mode 100644 index 000000000..7d518ee8b --- /dev/null +++ b/arch/openrisc/kernel/smp.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2014 Stefan Kristiansson <stefan.kristiansson@saunalahti.fi> + * Copyright (C) 2017 Stafford Horne <shorne@gmail.com> + * + * Based on arm64 and arc implementations + * Copyright (C) 2013 ARM Ltd. + * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com) + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/smp.h> +#include <linux/cpu.h> +#include <linux/sched.h> +#include <linux/irq.h> +#include <asm/cpuinfo.h> +#include <asm/mmu_context.h> +#include <asm/tlbflush.h> +#include <asm/cacheflush.h> +#include <asm/time.h> + +static void (*smp_cross_call)(const struct cpumask *, unsigned int); + +unsigned long secondary_release = -1; +struct thread_info *secondary_thread_info; + +enum ipi_msg_type { + IPI_WAKEUP, + IPI_RESCHEDULE, + IPI_CALL_FUNC, + IPI_CALL_FUNC_SINGLE, +}; + +static DEFINE_SPINLOCK(boot_lock); + +static void boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + /* + * set synchronisation state between this boot processor + * and the secondary one + */ + spin_lock(&boot_lock); + + secondary_release = cpu; + smp_cross_call(cpumask_of(cpu), IPI_WAKEUP); + + /* + * now the secondary core is starting up let it run its + * calibrations, then wait for it to finish + */ + spin_unlock(&boot_lock); +} + +void __init smp_prepare_boot_cpu(void) +{ +} + +void __init smp_init_cpus(void) +{ + int i; + + for (i = 0; i < NR_CPUS; i++) + set_cpu_possible(i, true); +} + +void __init smp_prepare_cpus(unsigned int max_cpus) +{ + int i; + + /* + * Initialise the present map, which describes the set of CPUs + * actually populated at the present time. + */ + for (i = 0; i < max_cpus; i++) + set_cpu_present(i, true); +} + +void __init smp_cpus_done(unsigned int max_cpus) +{ +} + +static DECLARE_COMPLETION(cpu_running); + +int __cpu_up(unsigned int cpu, struct task_struct *idle) +{ + if (smp_cross_call == NULL) { + pr_warn("CPU%u: failed to start, IPI controller missing", + cpu); + return -EIO; + } + + secondary_thread_info = task_thread_info(idle); + current_pgd[cpu] = init_mm.pgd; + + boot_secondary(cpu, idle); + if (!wait_for_completion_timeout(&cpu_running, + msecs_to_jiffies(1000))) { + pr_crit("CPU%u: failed to start\n", cpu); + return -EIO; + } + synchronise_count_master(cpu); + + return 0; +} + +asmlinkage __init void secondary_start_kernel(void) +{ + struct mm_struct *mm = &init_mm; + unsigned int cpu = smp_processor_id(); + /* + * All kernel threads share the same mm context; grab a + * reference and switch to it. + */ + atomic_inc(&mm->mm_count); + current->active_mm = mm; + cpumask_set_cpu(cpu, mm_cpumask(mm)); + + pr_info("CPU%u: Booted secondary processor\n", cpu); + + setup_cpuinfo(); + openrisc_clockevent_init(); + + notify_cpu_starting(cpu); + + /* + * OK, now it's safe to let the boot CPU continue + */ + complete(&cpu_running); + + synchronise_count_slave(cpu); + set_cpu_online(cpu, true); + + local_irq_enable(); + + preempt_disable(); + /* + * OK, it's off to the idle thread for us + */ + cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); +} + +void handle_IPI(unsigned int ipi_msg) +{ + unsigned int cpu = smp_processor_id(); + + switch (ipi_msg) { + case IPI_WAKEUP: + break; + + case IPI_RESCHEDULE: + scheduler_ipi(); + break; + + case IPI_CALL_FUNC: + generic_smp_call_function_interrupt(); + break; + + case IPI_CALL_FUNC_SINGLE: + generic_smp_call_function_single_interrupt(); + break; + + default: + WARN(1, "CPU%u: Unknown IPI message 0x%x\n", cpu, ipi_msg); + break; + } +} + +void smp_send_reschedule(int cpu) +{ + smp_cross_call(cpumask_of(cpu), IPI_RESCHEDULE); +} + +static void stop_this_cpu(void *dummy) +{ + /* Remove this CPU */ + set_cpu_online(smp_processor_id(), false); + + local_irq_disable(); + /* CPU Doze */ + if (mfspr(SPR_UPR) & SPR_UPR_PMP) + mtspr(SPR_PMR, mfspr(SPR_PMR) | SPR_PMR_DME); + /* If that didn't work, infinite loop */ + while (1) + ; +} + +void smp_send_stop(void) +{ + smp_call_function(stop_this_cpu, NULL, 0); +} + +/* not supported, yet */ +int setup_profiling_timer(unsigned int multiplier) +{ + return -EINVAL; +} + +void __init set_smp_cross_call(void (*fn)(const struct cpumask *, unsigned int)) +{ + smp_cross_call = fn; +} + +void arch_send_call_function_single_ipi(int cpu) +{ + smp_cross_call(cpumask_of(cpu), IPI_CALL_FUNC_SINGLE); +} + +void arch_send_call_function_ipi_mask(const struct cpumask *mask) +{ + smp_cross_call(mask, IPI_CALL_FUNC); +} + +/* TLB flush operations - Performed on each CPU*/ +static inline void ipi_flush_tlb_all(void *ignored) +{ + local_flush_tlb_all(); +} + +void flush_tlb_all(void) +{ + on_each_cpu(ipi_flush_tlb_all, NULL, 1); +} + +/* + * FIXME: implement proper functionality instead of flush_tlb_all. + * *But*, as things currently stands, the local_tlb_flush_* functions will + * all boil down to local_tlb_flush_all anyway. + */ +void flush_tlb_mm(struct mm_struct *mm) +{ + on_each_cpu(ipi_flush_tlb_all, NULL, 1); +} + +void flush_tlb_page(struct vm_area_struct *vma, unsigned long uaddr) +{ + on_each_cpu(ipi_flush_tlb_all, NULL, 1); +} + +void flush_tlb_range(struct vm_area_struct *vma, + unsigned long start, unsigned long end) +{ + on_each_cpu(ipi_flush_tlb_all, NULL, 1); +} + +/* Instruction cache invalidate - performed on each cpu */ +static void ipi_icache_page_inv(void *arg) +{ + struct page *page = arg; + + local_icache_page_inv(page); +} + +void smp_icache_page_inv(struct page *page) +{ + on_each_cpu(ipi_icache_page_inv, page, 1); +} +EXPORT_SYMBOL(smp_icache_page_inv); diff --git a/arch/openrisc/kernel/stacktrace.c b/arch/openrisc/kernel/stacktrace.c new file mode 100644 index 000000000..54d38809e --- /dev/null +++ b/arch/openrisc/kernel/stacktrace.c @@ -0,0 +1,100 @@ +/* + * Stack trace utility for OpenRISC + * + * Copyright (C) 2017 Stafford Horne <shorne@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * Losely based on work from sh and powerpc. + */ + +#include <linux/export.h> +#include <linux/sched.h> +#include <linux/sched/debug.h> +#include <linux/sched/task_stack.h> +#include <linux/stacktrace.h> + +#include <asm/processor.h> +#include <asm/unwinder.h> + +/* + * Save stack-backtrace addresses into a stack_trace buffer. + */ +static void +save_stack_address(void *data, unsigned long addr, int reliable) +{ + struct stack_trace *trace = data; + + if (!reliable) + return; + + if (trace->skip > 0) { + trace->skip--; + return; + } + + if (trace->nr_entries < trace->max_entries) + trace->entries[trace->nr_entries++] = addr; +} + +void save_stack_trace(struct stack_trace *trace) +{ + unwind_stack(trace, (unsigned long *) &trace, save_stack_address); +} +EXPORT_SYMBOL_GPL(save_stack_trace); + +static void +save_stack_address_nosched(void *data, unsigned long addr, int reliable) +{ + struct stack_trace *trace = (struct stack_trace *)data; + + if (!reliable) + return; + + if (in_sched_functions(addr)) + return; + + if (trace->skip > 0) { + trace->skip--; + return; + } + + if (trace->nr_entries < trace->max_entries) + trace->entries[trace->nr_entries++] = addr; +} + +void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) +{ + unsigned long *sp = NULL; + + if (!try_get_task_stack(tsk)) + return; + + if (tsk == current) + sp = (unsigned long *) &sp; + else { + unsigned long ksp; + + /* Locate stack from kernel context */ + ksp = task_thread_info(tsk)->ksp; + ksp += STACK_FRAME_OVERHEAD; /* redzone */ + ksp += sizeof(struct pt_regs); + + sp = (unsigned long *) ksp; + } + + unwind_stack(trace, sp, save_stack_address_nosched); + + put_task_stack(tsk); +} +EXPORT_SYMBOL_GPL(save_stack_trace_tsk); + +void +save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace) +{ + unwind_stack(trace, (unsigned long *) regs->sp, + save_stack_address_nosched); +} +EXPORT_SYMBOL_GPL(save_stack_trace_regs); diff --git a/arch/openrisc/kernel/sync-timer.c b/arch/openrisc/kernel/sync-timer.c new file mode 100644 index 000000000..ed8d835ca --- /dev/null +++ b/arch/openrisc/kernel/sync-timer.c @@ -0,0 +1,120 @@ +/* + * OR1K timer synchronisation + * + * Based on work from MIPS implementation. + * + * All CPUs will have their count registers synchronised to the CPU0 next time + * value. This can cause a small timewarp for CPU0. All other CPU's should + * not have done anything significant (but they may have had interrupts + * enabled briefly - prom_smp_finish() should not be responsible for enabling + * interrupts...) + */ + +#include <linux/kernel.h> +#include <linux/irqflags.h> +#include <linux/cpumask.h> + +#include <asm/time.h> +#include <asm/timex.h> +#include <linux/atomic.h> +#include <asm/barrier.h> + +#include <asm/spr.h> + +static unsigned int initcount; +static atomic_t count_count_start = ATOMIC_INIT(0); +static atomic_t count_count_stop = ATOMIC_INIT(0); + +#define COUNTON 100 +#define NR_LOOPS 3 + +void synchronise_count_master(int cpu) +{ + int i; + unsigned long flags; + + pr_info("Synchronize counters for CPU %u: ", cpu); + + local_irq_save(flags); + + /* + * We loop a few times to get a primed instruction cache, + * then the last pass is more or less synchronised and + * the master and slaves each set their cycle counters to a known + * value all at once. This reduces the chance of having random offsets + * between the processors, and guarantees that the maximum + * delay between the cycle counters is never bigger than + * the latency of information-passing (cachelines) between + * two CPUs. + */ + + for (i = 0; i < NR_LOOPS; i++) { + /* slaves loop on '!= 2' */ + while (atomic_read(&count_count_start) != 1) + mb(); + atomic_set(&count_count_stop, 0); + smp_wmb(); + + /* Let the slave writes its count register */ + atomic_inc(&count_count_start); + + /* Count will be initialised to current timer */ + if (i == 1) + initcount = get_cycles(); + + /* + * Everyone initialises count in the last loop: + */ + if (i == NR_LOOPS-1) + openrisc_timer_set(initcount); + + /* + * Wait for slave to leave the synchronization point: + */ + while (atomic_read(&count_count_stop) != 1) + mb(); + atomic_set(&count_count_start, 0); + smp_wmb(); + atomic_inc(&count_count_stop); + } + /* Arrange for an interrupt in a short while */ + openrisc_timer_set_next(COUNTON); + + local_irq_restore(flags); + + /* + * i386 code reported the skew here, but the + * count registers were almost certainly out of sync + * so no point in alarming people + */ + pr_cont("done.\n"); +} + +void synchronise_count_slave(int cpu) +{ + int i; + + /* + * Not every cpu is online at the time this gets called, + * so we first wait for the master to say everyone is ready + */ + + for (i = 0; i < NR_LOOPS; i++) { + atomic_inc(&count_count_start); + while (atomic_read(&count_count_start) != 2) + mb(); + + /* + * Everyone initialises count in the last loop: + */ + if (i == NR_LOOPS-1) + openrisc_timer_set(initcount); + + atomic_inc(&count_count_stop); + while (atomic_read(&count_count_stop) != 2) + mb(); + } + /* Arrange for an interrupt in a short while */ + openrisc_timer_set_next(COUNTON); +} +#undef NR_LOOPS diff --git a/arch/openrisc/kernel/sys_call_table.c b/arch/openrisc/kernel/sys_call_table.c new file mode 100644 index 000000000..e1f8ce8c7 --- /dev/null +++ b/arch/openrisc/kernel/sys_call_table.c @@ -0,0 +1,28 @@ +/* + * OpenRISC sys_call_table.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/syscalls.h> +#include <linux/signal.h> +#include <linux/unistd.h> + +#include <asm/syscalls.h> + +#undef __SYSCALL +#define __SYSCALL(nr, call) [nr] = (call), + +void *sys_call_table[__NR_syscalls] = { +#include <asm/unistd.h> +}; diff --git a/arch/openrisc/kernel/time.c b/arch/openrisc/kernel/time.c new file mode 100644 index 000000000..6baecea27 --- /dev/null +++ b/arch/openrisc/kernel/time.c @@ -0,0 +1,176 @@ +/* + * OpenRISC time.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/time.h> +#include <linux/timex.h> +#include <linux/interrupt.h> +#include <linux/ftrace.h> + +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/irq.h> +#include <linux/io.h> + +#include <asm/cpuinfo.h> + +/* Test the timer ticks to count, used in sync routine */ +inline void openrisc_timer_set(unsigned long count) +{ + mtspr(SPR_TTCR, count); +} + +/* Set the timer to trigger in delta cycles */ +inline void openrisc_timer_set_next(unsigned long delta) +{ + u32 c; + + /* Read 32-bit counter value, add delta, mask off the low 28 bits. + * We're guaranteed delta won't be bigger than 28 bits because the + * generic timekeeping code ensures that for us. + */ + c = mfspr(SPR_TTCR); + c += delta; + c &= SPR_TTMR_TP; + + /* Set counter and enable interrupt. + * Keep timer in continuous mode always. + */ + mtspr(SPR_TTMR, SPR_TTMR_CR | SPR_TTMR_IE | c); +} + +static int openrisc_timer_set_next_event(unsigned long delta, + struct clock_event_device *dev) +{ + openrisc_timer_set_next(delta); + return 0; +} + +/* This is the clock event device based on the OR1K tick timer. + * As the timer is being used as a continuous clock-source (required for HR + * timers) we cannot enable the PERIODIC feature. The tick timer can run using + * one-shot events, so no problem. + */ +DEFINE_PER_CPU(struct clock_event_device, clockevent_openrisc_timer); + +void openrisc_clockevent_init(void) +{ + unsigned int cpu = smp_processor_id(); + struct clock_event_device *evt = + &per_cpu(clockevent_openrisc_timer, cpu); + struct cpuinfo_or1k *cpuinfo = &cpuinfo_or1k[cpu]; + + mtspr(SPR_TTMR, SPR_TTMR_CR); + +#ifdef CONFIG_SMP + evt->broadcast = tick_broadcast; +#endif + evt->name = "openrisc_timer_clockevent", + evt->features = CLOCK_EVT_FEAT_ONESHOT, + evt->rating = 300, + evt->set_next_event = openrisc_timer_set_next_event, + + evt->cpumask = cpumask_of(cpu); + + /* We only have 28 bits */ + clockevents_config_and_register(evt, cpuinfo->clock_frequency, + 100, 0x0fffffff); + +} + +static inline void timer_ack(void) +{ + /* Clear the IP bit and disable further interrupts */ + /* This can be done very simply... we just need to keep the timer + running, so just maintain the CR bits while clearing the rest + of the register + */ + mtspr(SPR_TTMR, SPR_TTMR_CR); +} + +/* + * The timer interrupt is mostly handled in generic code nowadays... this + * function just acknowledges the interrupt and fires the event handler that + * has been set on the clockevent device by the generic time management code. + * + * This function needs to be called by the timer exception handler and that's + * all the exception handler needs to do. + */ + +irqreturn_t __irq_entry timer_interrupt(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + unsigned int cpu = smp_processor_id(); + struct clock_event_device *evt = + &per_cpu(clockevent_openrisc_timer, cpu); + + timer_ack(); + + /* + * update_process_times() expects us to have called irq_enter(). + */ + irq_enter(); + evt->event_handler(evt); + irq_exit(); + + set_irq_regs(old_regs); + + return IRQ_HANDLED; +} + +/** + * Clocksource: Based on OpenRISC timer/counter + * + * This sets up the OpenRISC Tick Timer as a clock source. The tick timer + * is 32 bits wide and runs at the CPU clock frequency. + */ +static u64 openrisc_timer_read(struct clocksource *cs) +{ + return (u64) mfspr(SPR_TTCR); +} + +static struct clocksource openrisc_timer = { + .name = "openrisc_timer", + .rating = 200, + .read = openrisc_timer_read, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static int __init openrisc_timer_init(void) +{ + struct cpuinfo_or1k *cpuinfo = &cpuinfo_or1k[smp_processor_id()]; + + if (clocksource_register_hz(&openrisc_timer, cpuinfo->clock_frequency)) + panic("failed to register clocksource"); + + /* Enable the incrementer: 'continuous' mode with interrupt disabled */ + mtspr(SPR_TTMR, SPR_TTMR_CR); + + return 0; +} + +void __init time_init(void) +{ + u32 upr; + + upr = mfspr(SPR_UPR); + if (!(upr & SPR_UPR_TTP)) + panic("Linux not supported on devices without tick timer"); + + openrisc_timer_init(); + openrisc_clockevent_init(); +} diff --git a/arch/openrisc/kernel/traps.c b/arch/openrisc/kernel/traps.c new file mode 100644 index 000000000..d8981cbb8 --- /dev/null +++ b/arch/openrisc/kernel/traps.c @@ -0,0 +1,477 @@ +/* + * OpenRISC traps.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Here we handle the break vectors not used by the system call + * mechanism, as well as some general stack/register dumping + * things. + * + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/sched/debug.h> +#include <linux/sched/task_stack.h> +#include <linux/kernel.h> +#include <linux/extable.h> +#include <linux/kmod.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/kallsyms.h> +#include <linux/uaccess.h> + +#include <asm/segment.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/unwinder.h> +#include <asm/sections.h> + +int kstack_depth_to_print = 0x180; +int lwa_flag; +unsigned long __user *lwa_addr; + +void print_trace(void *data, unsigned long addr, int reliable) +{ + pr_emerg("[<%p>] %s%pS\n", (void *) addr, reliable ? "" : "? ", + (void *) addr); +} + +/* displays a short stack trace */ +void show_stack(struct task_struct *task, unsigned long *esp) +{ + if (esp == NULL) + esp = (unsigned long *)&esp; + + pr_emerg("Call trace:\n"); + unwind_stack(NULL, esp, print_trace); +} + +void show_trace_task(struct task_struct *tsk) +{ + /* + * TODO: SysRq-T trace dump... + */ +} + +void show_registers(struct pt_regs *regs) +{ + int i; + int in_kernel = 1; + unsigned long esp; + + esp = (unsigned long)(regs->sp); + if (user_mode(regs)) + in_kernel = 0; + + printk("CPU #: %d\n" + " PC: %08lx SR: %08lx SP: %08lx\n", + smp_processor_id(), regs->pc, regs->sr, regs->sp); + printk("GPR00: %08lx GPR01: %08lx GPR02: %08lx GPR03: %08lx\n", + 0L, regs->gpr[1], regs->gpr[2], regs->gpr[3]); + printk("GPR04: %08lx GPR05: %08lx GPR06: %08lx GPR07: %08lx\n", + regs->gpr[4], regs->gpr[5], regs->gpr[6], regs->gpr[7]); + printk("GPR08: %08lx GPR09: %08lx GPR10: %08lx GPR11: %08lx\n", + regs->gpr[8], regs->gpr[9], regs->gpr[10], regs->gpr[11]); + printk("GPR12: %08lx GPR13: %08lx GPR14: %08lx GPR15: %08lx\n", + regs->gpr[12], regs->gpr[13], regs->gpr[14], regs->gpr[15]); + printk("GPR16: %08lx GPR17: %08lx GPR18: %08lx GPR19: %08lx\n", + regs->gpr[16], regs->gpr[17], regs->gpr[18], regs->gpr[19]); + printk("GPR20: %08lx GPR21: %08lx GPR22: %08lx GPR23: %08lx\n", + regs->gpr[20], regs->gpr[21], regs->gpr[22], regs->gpr[23]); + printk("GPR24: %08lx GPR25: %08lx GPR26: %08lx GPR27: %08lx\n", + regs->gpr[24], regs->gpr[25], regs->gpr[26], regs->gpr[27]); + printk("GPR28: %08lx GPR29: %08lx GPR30: %08lx GPR31: %08lx\n", + regs->gpr[28], regs->gpr[29], regs->gpr[30], regs->gpr[31]); + printk(" RES: %08lx oGPR11: %08lx\n", + regs->gpr[11], regs->orig_gpr11); + + printk("Process %s (pid: %d, stackpage=%08lx)\n", + current->comm, current->pid, (unsigned long)current); + /* + * When in-kernel, we also print out the stack and code at the + * time of the fault.. + */ + if (in_kernel) { + + printk("\nStack: "); + show_stack(NULL, (unsigned long *)esp); + + printk("\nCode: "); + if (regs->pc < PAGE_OFFSET) + goto bad; + + for (i = -24; i < 24; i++) { + unsigned char c; + if (__get_user(c, &((unsigned char *)regs->pc)[i])) { +bad: + printk(" Bad PC value."); + break; + } + + if (i == 0) + printk("(%02x) ", c); + else + printk("%02x ", c); + } + } + printk("\n"); +} + +void nommu_dump_state(struct pt_regs *regs, + unsigned long ea, unsigned long vector) +{ + int i; + unsigned long addr, stack = regs->sp; + + printk("\n\r[nommu_dump_state] :: ea %lx, vector %lx\n\r", ea, vector); + + printk("CPU #: %d\n" + " PC: %08lx SR: %08lx SP: %08lx\n", + 0, regs->pc, regs->sr, regs->sp); + printk("GPR00: %08lx GPR01: %08lx GPR02: %08lx GPR03: %08lx\n", + 0L, regs->gpr[1], regs->gpr[2], regs->gpr[3]); + printk("GPR04: %08lx GPR05: %08lx GPR06: %08lx GPR07: %08lx\n", + regs->gpr[4], regs->gpr[5], regs->gpr[6], regs->gpr[7]); + printk("GPR08: %08lx GPR09: %08lx GPR10: %08lx GPR11: %08lx\n", + regs->gpr[8], regs->gpr[9], regs->gpr[10], regs->gpr[11]); + printk("GPR12: %08lx GPR13: %08lx GPR14: %08lx GPR15: %08lx\n", + regs->gpr[12], regs->gpr[13], regs->gpr[14], regs->gpr[15]); + printk("GPR16: %08lx GPR17: %08lx GPR18: %08lx GPR19: %08lx\n", + regs->gpr[16], regs->gpr[17], regs->gpr[18], regs->gpr[19]); + printk("GPR20: %08lx GPR21: %08lx GPR22: %08lx GPR23: %08lx\n", + regs->gpr[20], regs->gpr[21], regs->gpr[22], regs->gpr[23]); + printk("GPR24: %08lx GPR25: %08lx GPR26: %08lx GPR27: %08lx\n", + regs->gpr[24], regs->gpr[25], regs->gpr[26], regs->gpr[27]); + printk("GPR28: %08lx GPR29: %08lx GPR30: %08lx GPR31: %08lx\n", + regs->gpr[28], regs->gpr[29], regs->gpr[30], regs->gpr[31]); + printk(" RES: %08lx oGPR11: %08lx\n", + regs->gpr[11], regs->orig_gpr11); + + printk("Process %s (pid: %d, stackpage=%08lx)\n", + ((struct task_struct *)(__pa(current)))->comm, + ((struct task_struct *)(__pa(current)))->pid, + (unsigned long)current); + + printk("\nStack: "); + printk("Stack dump [0x%08lx]:\n", (unsigned long)stack); + for (i = 0; i < kstack_depth_to_print; i++) { + if (((long)stack & (THREAD_SIZE - 1)) == 0) + break; + stack++; + + printk("%lx :: sp + %02d: 0x%08lx\n", stack, i * 4, + *((unsigned long *)(__pa(stack)))); + } + printk("\n"); + + printk("Call Trace: "); + i = 1; + while (((long)stack & (THREAD_SIZE - 1)) != 0) { + addr = *((unsigned long *)__pa(stack)); + stack++; + + if (kernel_text_address(addr)) { + if (i && ((i % 6) == 0)) + printk("\n "); + printk(" [<%08lx>]", addr); + i++; + } + } + printk("\n"); + + printk("\nCode: "); + + for (i = -24; i < 24; i++) { + unsigned char c; + c = ((unsigned char *)(__pa(regs->pc)))[i]; + + if (i == 0) + printk("(%02x) ", c); + else + printk("%02x ", c); + } + printk("\n"); +} + +/* This is normally the 'Oops' routine */ +void die(const char *str, struct pt_regs *regs, long err) +{ + + console_verbose(); + printk("\n%s#: %04lx\n", str, err & 0xffff); + show_registers(regs); +#ifdef CONFIG_JUMP_UPON_UNHANDLED_EXCEPTION + printk("\n\nUNHANDLED_EXCEPTION: entering infinite loop\n"); + + /* shut down interrupts */ + local_irq_disable(); + + __asm__ __volatile__("l.nop 1"); + do {} while (1); +#endif + do_exit(SIGSEGV); +} + +/* This is normally the 'Oops' routine */ +void die_if_kernel(const char *str, struct pt_regs *regs, long err) +{ + if (user_mode(regs)) + return; + + die(str, regs, err); +} + +void unhandled_exception(struct pt_regs *regs, int ea, int vector) +{ + printk("Unable to handle exception at EA =0x%x, vector 0x%x", + ea, vector); + die("Oops", regs, 9); +} + +void __init trap_init(void) +{ + /* Nothing needs to be done */ +} + +asmlinkage void do_trap(struct pt_regs *regs, unsigned long address) +{ + force_sig_fault(SIGTRAP, TRAP_TRACE, (void __user *)address, current); + + regs->pc += 4; +} + +asmlinkage void do_unaligned_access(struct pt_regs *regs, unsigned long address) +{ + if (user_mode(regs)) { + /* Send a SIGBUS */ + force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)address, current); + } else { + printk("KERNEL: Unaligned Access 0x%.8lx\n", address); + show_registers(regs); + die("Die:", regs, address); + } + +} + +asmlinkage void do_bus_fault(struct pt_regs *regs, unsigned long address) +{ + if (user_mode(regs)) { + /* Send a SIGBUS */ + force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)address, current); + } else { /* Kernel mode */ + printk("KERNEL: Bus error (SIGBUS) 0x%.8lx\n", address); + show_registers(regs); + die("Die:", regs, address); + } +} + +static inline int in_delay_slot(struct pt_regs *regs) +{ +#ifdef CONFIG_OPENRISC_NO_SPR_SR_DSX + /* No delay slot flag, do the old way */ + unsigned int op, insn; + + insn = *((unsigned int *)regs->pc); + op = insn >> 26; + switch (op) { + case 0x00: /* l.j */ + case 0x01: /* l.jal */ + case 0x03: /* l.bnf */ + case 0x04: /* l.bf */ + case 0x11: /* l.jr */ + case 0x12: /* l.jalr */ + return 1; + default: + return 0; + } +#else + return mfspr(SPR_SR) & SPR_SR_DSX; +#endif +} + +static inline void adjust_pc(struct pt_regs *regs, unsigned long address) +{ + int displacement; + unsigned int rb, op, jmp; + + if (unlikely(in_delay_slot(regs))) { + /* In delay slot, instruction at pc is a branch, simulate it */ + jmp = *((unsigned int *)regs->pc); + + displacement = sign_extend32(((jmp) & 0x3ffffff) << 2, 27); + rb = (jmp & 0x0000ffff) >> 11; + op = jmp >> 26; + + switch (op) { + case 0x00: /* l.j */ + regs->pc += displacement; + return; + case 0x01: /* l.jal */ + regs->pc += displacement; + regs->gpr[9] = regs->pc + 8; + return; + case 0x03: /* l.bnf */ + if (regs->sr & SPR_SR_F) + regs->pc += 8; + else + regs->pc += displacement; + return; + case 0x04: /* l.bf */ + if (regs->sr & SPR_SR_F) + regs->pc += displacement; + else + regs->pc += 8; + return; + case 0x11: /* l.jr */ + regs->pc = regs->gpr[rb]; + return; + case 0x12: /* l.jalr */ + regs->pc = regs->gpr[rb]; + regs->gpr[9] = regs->pc + 8; + return; + default: + break; + } + } else { + regs->pc += 4; + } +} + +static inline void simulate_lwa(struct pt_regs *regs, unsigned long address, + unsigned int insn) +{ + unsigned int ra, rd; + unsigned long value; + unsigned long orig_pc; + long imm; + + const struct exception_table_entry *entry; + + orig_pc = regs->pc; + adjust_pc(regs, address); + + ra = (insn >> 16) & 0x1f; + rd = (insn >> 21) & 0x1f; + imm = (short)insn; + lwa_addr = (unsigned long __user *)(regs->gpr[ra] + imm); + + if ((unsigned long)lwa_addr & 0x3) { + do_unaligned_access(regs, address); + return; + } + + if (get_user(value, lwa_addr)) { + if (user_mode(regs)) { + force_sig(SIGSEGV, current); + return; + } + + if ((entry = search_exception_tables(orig_pc))) { + regs->pc = entry->fixup; + return; + } + + /* kernel access in kernel space, load it directly */ + value = *((unsigned long *)lwa_addr); + } + + lwa_flag = 1; + regs->gpr[rd] = value; +} + +static inline void simulate_swa(struct pt_regs *regs, unsigned long address, + unsigned int insn) +{ + unsigned long __user *vaddr; + unsigned long orig_pc; + unsigned int ra, rb; + long imm; + + const struct exception_table_entry *entry; + + orig_pc = regs->pc; + adjust_pc(regs, address); + + ra = (insn >> 16) & 0x1f; + rb = (insn >> 11) & 0x1f; + imm = (short)(((insn & 0x2200000) >> 10) | (insn & 0x7ff)); + vaddr = (unsigned long __user *)(regs->gpr[ra] + imm); + + if (!lwa_flag || vaddr != lwa_addr) { + regs->sr &= ~SPR_SR_F; + return; + } + + if ((unsigned long)vaddr & 0x3) { + do_unaligned_access(regs, address); + return; + } + + if (put_user(regs->gpr[rb], vaddr)) { + if (user_mode(regs)) { + force_sig(SIGSEGV, current); + return; + } + + if ((entry = search_exception_tables(orig_pc))) { + regs->pc = entry->fixup; + return; + } + + /* kernel access in kernel space, store it directly */ + *((unsigned long *)vaddr) = regs->gpr[rb]; + } + + lwa_flag = 0; + regs->sr |= SPR_SR_F; +} + +#define INSN_LWA 0x1b +#define INSN_SWA 0x33 + +asmlinkage void do_illegal_instruction(struct pt_regs *regs, + unsigned long address) +{ + unsigned int op; + unsigned int insn = *((unsigned int *)address); + + op = insn >> 26; + + switch (op) { + case INSN_LWA: + simulate_lwa(regs, address, insn); + return; + + case INSN_SWA: + simulate_swa(regs, address, insn); + return; + + default: + break; + } + + if (user_mode(regs)) { + /* Send a SIGILL */ + force_sig_fault(SIGILL, ILL_ILLOPC, (void __user *)address, current); + } else { /* Kernel mode */ + printk("KERNEL: Illegal instruction (SIGILL) 0x%.8lx\n", + address); + show_registers(regs); + die("Die:", regs, address); + } +} diff --git a/arch/openrisc/kernel/unwinder.c b/arch/openrisc/kernel/unwinder.c new file mode 100644 index 000000000..8ae15c2c1 --- /dev/null +++ b/arch/openrisc/kernel/unwinder.c @@ -0,0 +1,105 @@ +/* + * OpenRISC unwinder.c + * + * Reusable arch specific api for unwinding stacks. + * + * Copyright (C) 2017 Stafford Horne <shorne@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/sched/task_stack.h> +#include <linux/kernel.h> + +#include <asm/unwinder.h> + +#ifdef CONFIG_FRAME_POINTER +struct or1k_frameinfo { + unsigned long *fp; + unsigned long ra; + unsigned long top; +}; + +/* + * Verify a frameinfo structure. The return address should be a valid text + * address. The frame pointer may be null if its the last frame, otherwise + * the frame pointer should point to a location in the stack after the the + * top of the next frame up. + */ +static inline int or1k_frameinfo_valid(struct or1k_frameinfo *frameinfo) +{ + return (frameinfo->fp == NULL || + (!kstack_end(frameinfo->fp) && + frameinfo->fp > &frameinfo->top)) && + __kernel_text_address(frameinfo->ra); +} + +/* + * Create a stack trace doing scanning which is frame pointer aware. We can + * get reliable stack traces by matching the previously found frame + * pointer with the top of the stack address every time we find a valid + * or1k_frameinfo. + * + * Ideally the stack parameter will be passed as FP, but it can not be + * guaranteed. Therefore we scan each address looking for the first sign + * of a return address. + * + * The OpenRISC stack frame looks something like the following. The + * location SP is held in r1 and location FP is held in r2 when frame pointers + * enabled. + * + * SP -> (top of stack) + * - (callee saved registers) + * - (local variables) + * FP-8 -> previous FP \ + * FP-4 -> return address |- or1k_frameinfo + * FP -> (previous top of stack) / + */ +void unwind_stack(void *data, unsigned long *stack, + void (*trace)(void *data, unsigned long addr, int reliable)) +{ + unsigned long *next_fp = NULL; + struct or1k_frameinfo *frameinfo = NULL; + int reliable = 0; + + while (!kstack_end(stack)) { + frameinfo = container_of(stack, + struct or1k_frameinfo, + top); + + if (__kernel_text_address(frameinfo->ra)) { + if (or1k_frameinfo_valid(frameinfo) && + (next_fp == NULL || + next_fp == &frameinfo->top)) { + reliable = 1; + next_fp = frameinfo->fp; + } else + reliable = 0; + + trace(data, frameinfo->ra, reliable); + } + stack++; + } +} + +#else /* CONFIG_FRAME_POINTER */ + +/* + * Create a stack trace by doing a simple scan treating all text addresses + * as return addresses. + */ +void unwind_stack(void *data, unsigned long *stack, + void (*trace)(void *data, unsigned long addr, int reliable)) +{ + unsigned long addr; + + while (!kstack_end(stack)) { + addr = *stack++; + if (__kernel_text_address(addr)) + trace(data, addr, 0); + } +} +#endif /* CONFIG_FRAME_POINTER */ + diff --git a/arch/openrisc/kernel/vmlinux.h b/arch/openrisc/kernel/vmlinux.h new file mode 100644 index 000000000..bdea46c61 --- /dev/null +++ b/arch/openrisc/kernel/vmlinux.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __OPENRISC_VMLINUX_H_ +#define __OPENRISC_VMLINUX_H_ + +#ifdef CONFIG_BLK_DEV_INITRD +extern char __initrd_start, __initrd_end; +#endif + +#endif diff --git a/arch/openrisc/kernel/vmlinux.lds.S b/arch/openrisc/kernel/vmlinux.lds.S new file mode 100644 index 000000000..953bdcd54 --- /dev/null +++ b/arch/openrisc/kernel/vmlinux.lds.S @@ -0,0 +1,126 @@ +/* + * OpenRISC vmlinux.lds.S + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar <phoenix@bsemi.com> + * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * ld script for OpenRISC architecture + */ + +/* TODO + * - clean up __offset & stuff + * - change all 8192 alignment to PAGE !!! + * - recheck if all alignments are really needed + */ + +# define LOAD_OFFSET PAGE_OFFSET +# define LOAD_BASE PAGE_OFFSET + +#include <asm/page.h> +#include <asm/cache.h> +#include <asm/thread_info.h> +#include <asm-generic/vmlinux.lds.h> + +#ifdef __OR1K__ +#define __OUTPUT_FORMAT "elf32-or1k" +#else +#define __OUTPUT_FORMAT "elf32-or32" +#endif + +OUTPUT_FORMAT(__OUTPUT_FORMAT, __OUTPUT_FORMAT, __OUTPUT_FORMAT) +jiffies = jiffies_64 + 4; + +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + . = LOAD_BASE ; + + _text = .; + + /* _s_kernel_ro must be page aligned */ + . = ALIGN(PAGE_SIZE); + _s_kernel_ro = .; + + .text : AT(ADDR(.text) - LOAD_OFFSET) + { + _stext = .; + TEXT_TEXT + SCHED_TEXT + CPUIDLE_TEXT + LOCK_TEXT + KPROBES_TEXT + IRQENTRY_TEXT + SOFTIRQENTRY_TEXT + *(.fixup) + *(.text.__*) + _etext = .; + } + /* TODO: Check if fixup and text.__* are really necessary + * fixup is definitely necessary + */ + + _sdata = .; + + /* Page alignment required for RO_DATA_SECTION */ + RO_DATA_SECTION(PAGE_SIZE) + _e_kernel_ro = .; + + /* Whatever comes after _e_kernel_ro had better be page-aligend, too */ + + /* 32 here is cacheline size... recheck this */ + RW_DATA_SECTION(32, PAGE_SIZE, PAGE_SIZE) + + _edata = .; + + EXCEPTION_TABLE(4) + NOTES + + /* Init code and data */ + . = ALIGN(PAGE_SIZE); + __init_begin = .; + + HEAD_TEXT_SECTION + + /* Page aligned */ + INIT_TEXT_SECTION(PAGE_SIZE) + + /* Align __setup_start on 16 byte boundary */ + INIT_DATA_SECTION(16) + + PERCPU_SECTION(L1_CACHE_BYTES) + + __init_end = .; + + . = ALIGN(PAGE_SIZE); + .initrd : AT(ADDR(.initrd) - LOAD_OFFSET) + { + __initrd_start = .; + *(.initrd) + __initrd_end = .; + FILL (0); + . = ALIGN (PAGE_SIZE); + } + + __vmlinux_end = .; /* last address of the physical file */ + + BSS_SECTION(0, 0, 0x20) + + _end = .; + + /* Throw in the debugging sections */ + STABS_DEBUG + DWARF_DEBUG + + /* Sections to be discarded -- must be last */ + DISCARDS +} |