diff options
-rw-r--r-- | core/nds32/atomic.h | 56 | ||||
-rw-r--r-- | core/nds32/build.mk | 15 | ||||
-rw-r--r-- | core/nds32/config_core.h | 13 | ||||
-rw-r--r-- | core/nds32/cpu.c | 52 | ||||
-rw-r--r-- | core/nds32/cpu.h | 57 | ||||
-rw-r--r-- | core/nds32/ec.lds.S | 213 | ||||
-rw-r--r-- | core/nds32/init.S | 194 | ||||
-rw-r--r-- | core/nds32/irq_chip.h | 54 | ||||
-rw-r--r-- | core/nds32/panic.c | 42 | ||||
-rw-r--r-- | core/nds32/switch.S | 99 | ||||
-rw-r--r-- | core/nds32/task.c | 515 | ||||
-rw-r--r-- | include/link_defs.h | 1 | ||||
-rw-r--r-- | include/task.h | 9 |
13 files changed, 1320 insertions, 0 deletions
diff --git a/core/nds32/atomic.h b/core/nds32/atomic.h new file mode 100644 index 0000000000..3214067c43 --- /dev/null +++ b/core/nds32/atomic.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Atomic operations for Andes */ + +#ifndef __CROS_EC_ATOMIC_H +#define __CROS_EC_ATOMIC_H + +#include "common.h" +#include "cpu.h" + +static inline void atomic_clear(uint32_t *addr, uint32_t bits) +{ + uint32_t psw = get_psw(); + asm volatile ("setgie.d"); + *addr &= ~bits; + set_psw(psw); +} + +static inline void atomic_or(uint32_t *addr, uint32_t bits) +{ + uint32_t psw = get_psw(); + asm volatile ("setgie.d"); + *addr |= bits; + set_psw(psw); +} + +static inline void atomic_add(uint32_t *addr, uint32_t value) +{ + uint32_t psw = get_psw(); + asm volatile ("setgie.d"); + *addr += value; + set_psw(psw); +} + +static inline void atomic_sub(uint32_t *addr, uint32_t value) +{ + uint32_t psw = get_psw(); + asm volatile ("setgie.d"); + *addr -= value; + set_psw(psw); +} + +static inline uint32_t atomic_read_clear(uint32_t *addr) +{ + uint32_t val; + uint32_t psw = get_psw(); + asm volatile ("setgie.d"); + val = *addr; + *addr = 0; + set_psw(psw); + return val; +} +#endif /* __CROS_EC_ATOMIC_H */ diff --git a/core/nds32/build.mk b/core/nds32/build.mk new file mode 100644 index 0000000000..810d5b8dd3 --- /dev/null +++ b/core/nds32/build.mk @@ -0,0 +1,15 @@ +# -*- makefile -*- +# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Andestar v3m architecture core OS files build +# + +# Select Andes bare-metal toolchain +CROSS_COMPILE?=nds32le-cros-elf- + +# CPU specific compilation flags +CFLAGS_CPU=-march=v3m -Os + +core-y=cpu.o init.o panic.o task.o switch.o diff --git a/core/nds32/config_core.h b/core/nds32/config_core.h new file mode 100644 index 0000000000..57909a263a --- /dev/null +++ b/core/nds32/config_core.h @@ -0,0 +1,13 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __CONFIG_CORE_H +#define __CONFIG_CORE_H + +/* Linker binary architecture and format */ +#define BFD_ARCH nds32 +#define BFD_FORMAT "elf32-nds32le" + +#endif /* __CONFIG_CORE_H */ diff --git a/core/nds32/cpu.c b/core/nds32/cpu.c new file mode 100644 index 0000000000..e4dc5db94c --- /dev/null +++ b/core/nds32/cpu.c @@ -0,0 +1,52 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Set up the N8 core + */ + +#include "cpu.h" + +void cpu_init(void) +{ + /* DLM initialization is done in init.S */ +} + +/** + * Count leading zeros + * + * @param x non null integer. + * @return the number of leading 0-bits in x, + * starting at the most significant bit position. + * + * The Andestar v3m architecture has no CLZ instruction (contrary to v3), + * so let's use the software implementation. + */ +int __clzsi2(int x) +{ + int r = 0; + + if (!x) + return 32; + if (!(x & 0xffff0000u)) { + x <<= 16; + r += 16; + } + if (!(x & 0xff000000u)) { + x <<= 8; + r += 8; + } + if (!(x & 0xf0000000u)) { + x <<= 4; + r += 4; + } + if (!(x & 0xc0000000u)) { + x <<= 2; + r += 2; + } + if (!(x & 0x80000000u)) { + x <<= 1; + r += 1; + } + return r; +} diff --git a/core/nds32/cpu.h b/core/nds32/cpu.h new file mode 100644 index 0000000000..b22ad62cbf --- /dev/null +++ b/core/nds32/cpu.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Registers map and defintions for Andes cores + */ + +#ifndef __CPU_H +#define __CPU_H + +#include <stdint.h> + +/* Process Status Word bits */ +#define PSW_GIE (1 << 0) /* Global Interrupt Enable */ +#define PSW_INTL_SHIFT 1 /* Interrupt Stack Level */ +#define PSW_INTL_MASK (0x3 << PSW_INTL_SHIFT) + +/* write Process Status Word privileged register */ +static inline void set_psw(uint32_t val) +{ + asm volatile ("mtsr %0, $PSW" : : "r"(val)); +} + +/* read Process Status Word privileged register */ +static inline uint32_t get_psw(void) +{ + uint32_t ret; + asm volatile ("mfsr %0, $PSW" : "=r"(ret)); + return ret; +} + +/* write Interruption Program Counter privileged register */ +static inline void set_ipc(uint32_t val) +{ + asm volatile ("mtsr %0, $IPC" : : "r"(val)); +} + +/* read Interruption Program Counter privileged register */ +static inline uint32_t get_ipc(void) +{ + uint32_t ret; + asm volatile ("mfsr %0, $IPC" : "=r"(ret)); + return ret; +} + +/* read Interruption Type privileged register */ +static inline uint32_t get_itype(void) +{ + uint32_t ret; + asm volatile ("mfsr %0, $ITYPE" : "=r"(ret)); + return ret; +} + +/* Generic CPU core initialization */ +void cpu_init(void); + +#endif /* __CPU_H */ diff --git a/core/nds32/ec.lds.S b/core/nds32/ec.lds.S new file mode 100644 index 0000000000..f1d021c3b0 --- /dev/null +++ b/core/nds32/ec.lds.S @@ -0,0 +1,213 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "config.h" + +#define FW_OFF_(section) CONFIG_FW_##section##_OFF +#define FW_OFF(section) (CONFIG_FLASH_BASE + FW_OFF_(section)) + +#define FW_SIZE_(section) CONFIG_FW_##section##_SIZE +#define FW_SIZE(section) FW_SIZE_(section) + + +OUTPUT_FORMAT(BFD_FORMAT, BFD_FORMAT, BFD_FORMAT) +OUTPUT_ARCH(BFD_ARCH) +ENTRY(reset) +MEMORY +{ + FLASH (rx) : ORIGIN = FW_OFF(SECTION), LENGTH = FW_SIZE(SECTION) + IRAM (rw) : ORIGIN = CONFIG_RAM_BASE, LENGTH = CONFIG_RAM_SIZE +} +SECTIONS +{ + .text : { + OUTDIR/core/CORE/init.o (.text.vecttable) + . = ALIGN(4); + __version_struct_offset = .; + *(.rodata.ver) +#ifdef SHIFT_CODE_FOR_TEST + . = ALIGN(256); +#else + . = ALIGN(4); +#endif + OUTDIR/core/CORE/init.o (.text.vectirq) + OUTDIR/core/CORE/init.o (.text) + *(.text*) +#ifdef COMPILE_FOR_RAM + } > IRAM +#else + } > FLASH +#endif + . = ALIGN(4); + .rodata : { + /* Symbols defined here are declared in link_defs.h */ + __irqprio = .; + *(.rodata.irqprio) + __irqprio_end = .; + + . = ALIGN(4); + __irqhandler = .; + OUTDIR/core/CORE/init.o (.rodata.vecthandlers) + + . = ALIGN(4); + __cmds = .; + *(SORT(.rodata.cmds*)) + __cmds_end = .; + + . = ALIGN(4); + __hcmds = .; + *(.rodata.hcmds) + __hcmds_end = .; + + . = ALIGN(4); + __hooks_init = .; + *(.rodata.HOOK_INIT) + __hooks_init_end = .; + + __hooks_pre_freq_change = .; + *(.rodata.HOOK_PRE_FREQ_CHANGE) + __hooks_pre_freq_change_end = .; + + __hooks_freq_change = .; + *(.rodata.HOOK_FREQ_CHANGE) + __hooks_freq_change_end = .; + + __hooks_sysjump = .; + *(.rodata.HOOK_SYSJUMP) + __hooks_sysjump_end = .; + + __hooks_chipset_pre_init = .; + *(.rodata.HOOK_CHIPSET_PRE_INIT) + __hooks_chipset_pre_init_end = .; + + __hooks_chipset_startup = .; + *(.rodata.HOOK_CHIPSET_STARTUP) + __hooks_chipset_startup_end = .; + + __hooks_chipset_resume = .; + *(.rodata.HOOK_CHIPSET_RESUME) + __hooks_chipset_resume_end = .; + + __hooks_chipset_suspend = .; + *(.rodata.HOOK_CHIPSET_SUSPEND) + __hooks_chipset_suspend_end = .; + + __hooks_chipset_shutdown = .; + *(.rodata.HOOK_CHIPSET_SHUTDOWN) + __hooks_chipset_shutdown_end = .; + + __hooks_ac_change = .; + *(.rodata.HOOK_AC_CHANGE) + __hooks_ac_change_end = .; + + __hooks_lid_change = .; + *(.rodata.HOOK_LID_CHANGE) + __hooks_lid_change_end = .; + + __hooks_pwrbtn_change = .; + *(.rodata.HOOK_POWER_BUTTON_CHANGE) + __hooks_pwrbtn_change_end = .; + + __hooks_charge_state_change = .; + *(.rodata.HOOK_CHARGE_STATE_CHANGE) + __hooks_charge_state_change_end = .; + + __hooks_tick = .; + *(.rodata.HOOK_TICK) + __hooks_tick_end = .; + + __hooks_second = .; + *(.rodata.HOOK_SECOND) + __hooks_second_end = .; + + __deferred_funcs = .; + *(.rodata.deferred) + __deferred_funcs_end = .; + + . = ALIGN(4); + *(.rodata*) + +#if defined(SECTION_IS_RO) && defined(CONFIG_FLASH) + . = ALIGN(64); + *(.google) +#endif + . = ALIGN(4); +#ifdef COMPILE_FOR_RAM + } > IRAM +#else + } >FLASH +#endif + __ro_end = . ; + +#ifdef COMPILE_FOR_RAM + .data : { +#else + .data : { +#endif + . = ALIGN(4); + __data_start = .; + *(.data.tasks) + *(.data) +#ifdef CONFIG_MPU + /* It has to be aligned by 32 bytes to be a valid MPU region. */ + . = ALIGN(32); + __iram_text_start = .; +#else + . = ALIGN(4); +#endif + *(.iram.text) +#ifdef CONFIG_MPU + . = ALIGN(32); + __iram_text_end = .; +#else + . = ALIGN(4); +#endif + __data_end = .; + + } > IRAM AT>FLASH + + + __deferred_funcs_count = + (__deferred_funcs_end - __deferred_funcs) / 4; + ASSERT(__deferred_funcs_count <= DEFERRABLE_MAX_COUNT, + "Increase DEFERRABLE_MAX_COUNT") + + .bss : { + /* Stacks must be 64-bit aligned */ + . = ALIGN(8); + __bss_start = .; + *(.bss.tasks) + *(.bss.task_scratchpad) + . = ALIGN(8); + *(.bss.system_stack) + /* Rest of .bss takes care of its own alignment */ + *(.bss) + . = ALIGN(4); + __bss_end = .; + + /* Shared memory buffer must be at the end of preallocated RAM, so it + * can expand to use all the remaining RAM. */ + __shared_mem_buf = .; + + } > IRAM + + .flash.tag : { + /* Tag at end of firmware image so that we can find the image size. + * This may be overwritten by the shared memory buffer; that's ok + * because we only use it to find the image size in flash. */ + . = ALIGN(4); + BYTE(0x45); + BYTE(0x4e); + BYTE(0x44); + BYTE(0xea); + /* NOTHING MAY GO IN FLASH AFTER THIS! */ + } >FLASH +#if !(defined(SECTION_IS_RO) && defined(CONFIG_FLASH)) + /DISCARD/ : { + *(.google) + } +#endif + + /DISCARD/ : { *(.ARM.*) } +} diff --git a/core/nds32/init.S b/core/nds32/init.S new file mode 100644 index 0000000000..bd166ef668 --- /dev/null +++ b/core/nds32/init.S @@ -0,0 +1,194 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * N8 CPU initialization + */ + +#include "config.h" + +/* magic macro to implement IRQ prefix / exit */ +.macro vector name +.weak \name\()_handler +.set \name\()_handler, unhandled_irq +j __entry_\()\name +.pushsection .text.vectirq +.global __entry_\()\name +__entry_\()\name: + /* the context is stored on the current task stack*/ + /* save r15, fp, lp and sp */ + smw.adm $r15, [$sp], $r15, 0xb + /* r0-r5 are caller saved */ + smw.adm $r0, [$sp], $r5, 0 + /* switch to system stack if we are called from process stack */ + la $r3, stack_end + mov55 $fp, $sp + slt45 $r3, $sp /* if sp > end of system stack, then r15 = 1 and */ + cmovn $sp, $r3, $r15 /* point sp to the top of the system stack */ + /* C routine handler */ + jal \name\()_handler + /* check whether we need to change the scheduled task */ + lwi.gp $r2, [ + need_resched] + bnez $r2, __switch_task + /* restore r0-r5 */ + lmw.bim $r0, [$fp], $r5, 0 + /* restore r15, fp, lp and sp */ + lmw.bi $r15, [$fp], $r15, 0xb + /* restore PC and PSW */ + iret +.popsection +.pushsection .rodata.vecthandlers +.long \name\()_handler +.popsection +.endm + +.section .text.vecttable + +/* Exceptions vector */ +vectors: +j reset /* reset / NMI */ +j excep_handler /* TLB fill */ +j excep_handler /* PTE not present */ +j excep_handler /* TLB misc */ +j excep_handler /* TLB VLPT miss */ +j excep_handler /* Machine error */ +j excep_handler /* Debug related */ +j excep_handler /* General exception */ +vector syscall /* Syscall */ +vector irq_0 /* HW 0 */ +vector irq_1 /* HW 1 */ +vector irq_2 /* HW 2 */ +vector irq_3 /* HW 3 */ +vector irq_4 /* HW 4 */ +vector irq_5 /* HW 5 */ +vector irq_6 /* HW 6 */ +vector irq_7 /* HW 7 */ +vector irq_8 /* HW 8 */ +vector irq_9 /* HW 9 */ +vector irq_10 /* HW 10 */ +vector irq_11 /* HW 11 */ +vector irq_12 /* HW 12 */ +vector irq_13 /* HW 13 */ +vector irq_14 /* HW 14 */ +vector irq_15 /* HW 15 */ + +/* E-flash signature */ +.balign 16 +.global eflash_sig +eflash_sig: +.byte 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xB4 +.byte 0x85, 0x12, 0x5A, 0x5A, 0xAA, 0xAA, 0x55, 0x55 +/* flags: internal oscillator + implicit location */ + +.text + +.global reset +reset: + /* GP register is used to access .data and .bss */ + la $gp, _SDA_BASE_ + + /* Set system stack pointer. */ + la $sp, stack_end + + /* map/enable the 16kB of DLM at 0x00080000 */ + li $r0, 0x00080005 + mtsr $r0, $mr7 + + /* Clear BSS */ + la $r0, _bss_start + lwi $r1, [$r0] + la $r0, _bss_end + lwi $r2, [$r0] + movi $r0, #0 +bss_loop: + swi.bi $r0, [$r1], 4 + bne $r1, $r2, bss_loop + + /* Copy initialized data to DLM */ + la $r0, _data_start + lwi $r1, [$r0] + la $r0, _data_end + lwi $r2, [$r0] + la $r0, _ro_end + lwi $r0, [$r0] +data_loop: + lwi.bi $r3, [$r0], 4 + swi.bi $r3, [$r1], 4 + bne $r1, $r2, data_loop + + /* we switch to our own exception vectors */ + /* go back to it level 0 with HW interrupts globally disabled */ + li $r4, 0x70008 + mtsr $r4, $PSW + /* IT8380 specific: set vectors at 0 */ + li $r5, 0x0F02041 /* IVTBAR in GCTRL */ + movi $r15, 0 + sbi $r15, [$r5] + /* Interrupt vectors are every 4 bytes */ + li $r5, 0x00000007 + mtsr $r5, $IVB + + /* Jump to C routine */ + jal main + + /* That should not return. If it does, loop forever. */ + j . + +.global unhandled_irq +unhandled_irq: + mfsr $gp, $ITYPE + sethi $r15, 0xBAD0 + or $r15, $r15, $gp + mtsr $r15, $ITYPE + dsb + j excep_handler /* display exception with ITYPE=bad00<irq> */ + +.global excep_handler +excep_handler: + /* safety: reload GP even though it should be already set */ + la $gp, _SDA_BASE_ + /* save r0 to free one register */ + swi.gp $r0, [ + saved_regs] + /* save the remaining 15 registers */ + la $r0, saved_regs + 4 + smw.bim $r1, [$r0], $r10, 0 + smw.bim $r15,[$r0], $r15, 0xF + /* put a sane stack pointer */ + la $sp, stack_end + /* add IPC, IPSW to the context */ + mfsr $r1, $IPC + mfsr $r2, $IPSW + smw.bi $r1, [$r0], $r2, 0 + /* pass ir6/ITYPE as the second parameter */ + mfsr $r1, $ITYPE + /* exception context pointer as first parameter */ + addi $r0, $r0, -16*4 + /* jump to panic dump C routine */ + jal report_panic + /* we never return: exceptions are fatal */ + j . + +_bss_start: +.long __bss_start +_bss_end: +.long __bss_end +_data_start: +.long __data_start +_data_end: +.long __data_end +_ro_end: +.long __ro_end + +/* Reserve space for system stack */ +.section .bss.system_stack +stack_start: +.space CONFIG_STACK_SIZE, 0 +stack_end: +.global stack_end +/* registers state at exception entry */ +.global saved_regs +saved_regs: +.long 0, 0, 0, 0, 0, 0, 0, 0 +.long 0, 0, 0, 0, 0, 0, 0, 0 +/* IPC, IPSW for convenient access */ +.long 0, 0 diff --git a/core/nds32/irq_chip.h b/core/nds32/irq_chip.h new file mode 100644 index 0000000000..fb3bf1b21b --- /dev/null +++ b/core/nds32/irq_chip.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Chip-specific part of the IRQ handling. + */ + +#ifndef __IRQ_CHIP_H +#define __IRQ_CHIP_H + +/** + * Enable an IRQ in the chip interrupt controller. + * + * @param irq interrupt request index. + * @return CPU interrupt number to enable if any, -1 else. + */ +int chip_enable_irq(int irq); + +/** + * Disable an IRQ in the chip interrupt controller. + * + * @param irq interrupt request index. + * @return CPU interrupt number to disable if any, -1 else. + */ +int chip_disable_irq(int irq); + +/** + * Clear a pending IRQ in the chip interrupt controller. + * + * @param irq interrupt request index. + * @return CPU interrupt number to clear if any, -1 else. + * + * Note that most interrupts can be removed from the pending state simply by + * handling whatever caused the interrupt in the first place. This only needs + * to be called if an interrupt handler disables itself without clearing the + * reason for the interrupt, and then the interrupt is re-enabled from a + * different context. + */ +int chip_clear_pending_irq(int irq); + +/** + * Software-trigger an IRQ in the chip interrupt controller. + * + * @param irq interrupt request index. + * @return CPU interrupt number to trigger if any, -1 else. + */ +int chip_trigger_irq(int irq); + +/** + * Initialize chip interrupt controller. + */ +void chip_init_irqs(void); + +#endif /* __IRQ_CHIP_H */ diff --git a/core/nds32/panic.c b/core/nds32/panic.c new file mode 100644 index 0000000000..ebdffb32ea --- /dev/null +++ b/core/nds32/panic.c @@ -0,0 +1,42 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "common.h" +#include "console.h" +#include "cpu.h" +#include "panic.h" +#include "printf.h" +#include "system.h" +#include "task.h" +#include "timer.h" +#include "uart.h" +#include "util.h" + +void report_panic(uint32_t *regs, uint32_t itype) +{ + panic_printf("=== EXCEP: ITYPE=%x ===\n", itype); + panic_printf("R0 %08x R1 %08x R2 %08x R3 %08x\n", + regs[0], regs[1], regs[2], regs[3]); + panic_printf("R4 %08x R5 %08x R6 %08x R7 %08x\n", + regs[4], regs[5], regs[6], regs[7]); + panic_printf("R8 %08x R9 %08x R10 %08x R15 %08x\n", + regs[8], regs[9], regs[10], regs[11]); + panic_printf("FP %08x GP %08x LP %08x SP %08x\n", + regs[12], regs[13], regs[14], regs[15]); + panic_printf("IPC %08x IPSW %05x\n", regs[16], regs[17]); + if ((regs[17] & PSW_INTL_MASK) == (2 << PSW_INTL_SHIFT)) { + /* 2nd level exception */ + uint32_t oipc; + + asm volatile("mfsr %0, $OIPC" : "=r"(oipc)); + panic_printf("OIPC %08x\n", oipc); + } + + panic_reboot(); +} + +void panic_data_print(const struct panic_data *pdata) +{ +} diff --git a/core/nds32/switch.S b/core/nds32/switch.S new file mode 100644 index 0000000000..2c4ce0c273 --- /dev/null +++ b/core/nds32/switch.S @@ -0,0 +1,99 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Context switching + */ + +#include "config.h" + +.text + +/** + * Task context switching + * + * Change the task scheduled after returning from an interruption. + * + * This function must be called in interrupt context. + * + * Save the registers of the current task below the interrupt context on + * its task, then restore the live registers of the next task and set the + * process stack pointer to the new stack. + * + * $r0: pointer to the task to switch from + * $r1: pointer to the task to switch to + * $r2: pointer to the stack where the interrupt entry context is saved + * + * the structure of the saved context on the stack is : + * (top to bottom) + * sp, lp, fp, r15, r5, r4, r3, r2, r1, r0, r10, r9, r8, r7, r6, ipc, ipsw + * interrupt entry frame <|> + */ +.global __switch_task +__switch_task: + /* get the (new) highest priority task pointer in r0 */ + jal next_sched_task + movi55 $r3, 0 + /* pointer to the current task (which are switching from) */ + lwi.gp $r1, [ + current_task] + /* reset the re-scheduling request */ + swi.gp $r3, [ + need_resched] + /* Nothing to do: let's return to keep the same task scheduled */ + beq $r1, $r0, 1f + /* save our new scheduled task */ + swi.gp $r0, [ + current_task] + /* get the program status word saved at exception entry */ + mfsr $r4, $IPSW /* to save SP_ADJ bit */ + /* get the task program counter saved at exception entry */ + mfsr $r5, $IPC + /* get the new scheduled task stack pointer */ + lw $r3, [$r0] + /* save ipsw, ipc, r6, r7, r8, r9, r10 on the current process stack */ + smw.adm $r4, [$fp], $r10, 0 + /* restore ipsw, ipc, r6, r7, r8, r9, r10 from the next stack context */ + lmw.bim $r4, [$r3], $r10, 0 + /* set the program status word to restore SP_ADJ bit */ + mtsr $r4, $IPSW + /* set the task program counter to restore at exception exit */ + mtsr $r5, $IPC + /* save the task stack pointer in its context */ + sw $fp, [$r1] + /* barrier: ensure IPC is taken into account before IRET */ + dsb + /* exception frame pointer for the new task */ + mov55 $fp, $r3 +1: /* un-pile the interruption entry context */ + /* restore r0-r5 */ + lmw.bim $r0, [$fp], $r5, 0 + /* restore r15, fp, lp and sp */ + lmw.bi $r15, [$fp], $r15, 0xb + /* restore PC and PSW */ + iret + +/** + * Start the task scheduling. + * + * $r0 is a pointer to task_stack_ready, which is set to 1 after + * the task stack is set up. + */ +.global __task_start +__task_start: + /* area used as dummy thread stack for the first switch */ + la $r3, scratchpad + + movi55 $r4, 1 + movi55 $r2, 0 /* syscall 3rd parameter : not an IRQ emulation */ + movi55 $r1, 0 /* syscall 2nd parameter : re-schedule nothing */ + movi55 $r0, 0 /* syscall 1st parameter : de-schedule nothing */ + + /* put the dummy stack pointer at the top of the stack in scratchpad */ + addi $sp, $r3, 4 * 16 + /* we are ready to re-schedule */ + swi.gp $r4, [ + need_resched] + + /* trigger scheduling to execute the task with the highest priority */ + syscall 0 + /* we should never return here: set code to EC_ERROR_UNKNOWN */ + movi55 $r0, 0x1 + ret5 $lp + diff --git a/core/nds32/task.c b/core/nds32/task.c new file mode 100644 index 0000000000..9fe371e716 --- /dev/null +++ b/core/nds32/task.c @@ -0,0 +1,515 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Task scheduling / events module for Chrome EC operating system */ + +#include "atomic.h" +#include "common.h" +#include "console.h" +#include "cpu.h" +#include "irq_chip.h" +#include "link_defs.h" +#include "task.h" +#include "timer.h" +#include "uart.h" +#include "util.h" + +typedef union { + struct { + /* + * Note that sp must be the first element in the task struct + * for __switchto() to work. + */ + uint32_t sp; /* Saved stack pointer for context switch */ + uint32_t events; /* Bitmaps of received events */ + uint64_t runtime; /* Time spent in task */ + uint32_t *stack; /* Start of stack */ + }; +} task_; + +/* Value to store in unused stack */ +#define STACK_UNUSED_VALUE 0xdeadd00d + +/* declare task routine prototypes */ +#define TASK(n, r, d, s) int r(void *); +void __idle(void); +CONFIG_TASK_LIST +CONFIG_TEST_TASK_LIST +#undef TASK + +/* Task names for easier debugging */ +#define TASK(n, r, d, s) #n, +static const char * const task_names[] = { + "<< idle >>", + CONFIG_TASK_LIST + CONFIG_TEST_TASK_LIST +}; +#undef TASK + +extern int __task_start(void); + +#ifndef CONFIG_LOW_POWER_IDLE +/* Idle task. Executed when no tasks are ready to be scheduled. */ +void __idle(void) +{ + /* + * Print when the idle task starts. This is the lowest priority task, + * so this only starts once all other tasks have gotten a chance to do + * their task inits and have gone to sleep. + */ + cprintf(CC_TASK, "[%T idle task started]\n"); + + while (1) { + /* + * Wait for the next irq event. This stops the CPU clock + * (sleep / deep sleep, depending on chip config). + */ + asm("standby no_wake_grant"); + } +} +#endif /* !CONFIG_LOW_POWER_IDLE */ + +static void task_exit_trap(void) +{ + int i = task_get_current(); + cprintf(CC_TASK, "[%T Task %d (%s) exited!]\n", i, task_names[i]); + /* Exited tasks simply sleep forever */ + while (1) + task_wait_event(-1); +} + +/* Startup parameters for all tasks. */ +#define TASK(n, r, d, s) { \ + .r0 = (uint32_t)d, \ + .pc = (uint32_t)r, \ + .stack_size = s, \ +}, +static const struct { + uint32_t r0; + uint32_t pc; + uint16_t stack_size; +} const tasks_init[] = { + TASK(IDLE, __idle, 0, IDLE_TASK_STACK_SIZE) + CONFIG_TASK_LIST + CONFIG_TEST_TASK_LIST +}; +#undef TASK + +/* Contexts for all tasks */ +static task_ tasks[TASK_ID_COUNT]; +/* Sanity checks about static task invariants */ +BUILD_ASSERT(TASK_ID_COUNT <= sizeof(unsigned) * 8); +BUILD_ASSERT(TASK_ID_COUNT < (1 << (sizeof(task_id_t) * 8))); + + +/* Stacks for all tasks */ +#define TASK(n, r, d, s) + s +uint8_t task_stacks[0 + TASK(IDLE, __idle, 0, IDLE_TASK_STACK_SIZE) + CONFIG_TASK_LIST + CONFIG_TEST_TASK_LIST +] __aligned(8); + +#undef TASK + +/* Reserve space to discard context on first context switch. */ +#ifdef CONFIG_FPU +uint32_t scratchpad[17+18]; +#else +uint32_t scratchpad[17]; +#endif + +task_ *current_task = (task_ *)scratchpad; + +/* + * Should IRQs chain to svc_handler()? This should be set if either of the + * following is true: + * + * 1) Task scheduling has started, and task profiling is enabled. Task + * profiling does its tracking in svc_handler(). + * + * 2) An event was set by an interrupt; this could result in a higher-priority + * task unblocking. After checking for a task switch, svc_handler() will clear + * the flag (unless profiling is also enabled; then the flag remains set). + */ +int need_resched; + +/* + * Bitmap of all tasks ready to be run. + * + * Currently all tasks are enabled at startup. + */ +static uint32_t tasks_ready = (1<<TASK_ID_COUNT) - 1; + +static int start_called; /* Has task swapping started */ + +static inline task_ *__task_id_to_ptr(task_id_t id) +{ + return tasks + id; +} + +void interrupt_disable(void) +{ + /* clear GIE (Global Interrupt Enable) bit */ + asm volatile ("setgie.d"); + asm volatile ("dsb"); +} + +void interrupt_enable(void) +{ + /* set GIE (Global Interrupt Enable) bit */ + asm volatile ("setgie.e"); +} + +inline int in_interrupt_context(void) +{ + /* check INTL (Interrupt Stack Level) bits */ + return get_psw() & PSW_INTL_MASK; +} + +task_id_t task_get_current(void) +{ + return current_task - tasks; +} + +uint32_t *task_get_event_bitmap(task_id_t tskid) +{ + task_ *tsk = __task_id_to_ptr(tskid); + return &tsk->events; +} + +int task_start_called(void) +{ + return start_called; +} + +/** + * Scheduling system call + * + * Also includes emulation of software triggering interrupt vector + */ +void syscall_handler(int desched, task_id_t resched, int swirq) +{ + /* are we emulating an interrupt ? */ + if (swirq) { + void (*handler)(void) = __irqhandler[swirq + 1]; + /* adjust IPC to return *after* the syscall instruction */ + set_ipc(get_ipc() + 4); + /* call the regular IRQ handler */ + handler(); + return; + } + + if (desched && !current_task->events) { + /* + * Remove our own ready bit (current - tasks is same as + * task_get_current()) + */ + tasks_ready &= ~(1 << (current_task - tasks)); + } + tasks_ready |= 1 << resched; + + /* trigger a re-scheduling on exit */ + need_resched = 1; + + /* adjust IPC to return *after* the syscall instruction */ + set_ipc(get_ipc() + 4); +} + +task_ *next_sched_task(void) +{ + return __task_id_to_ptr(31 - __builtin_clz(tasks_ready)); +} + +static inline void __schedule(int desched, int resched, int swirq) +{ + register int p0 asm("$r0") = desched; + register int p1 asm("$r1") = resched; + register int p2 asm("$r2") = swirq; + + asm("syscall 0" : : "r"(p0), "r"(p1), "r"(p2)); +} + +static uint32_t __wait_evt(int timeout_us, task_id_t resched) +{ + task_ *tsk = current_task; + task_id_t me = tsk - tasks; + uint32_t evt; + int ret; + + ASSERT(!in_interrupt_context()); + + if (timeout_us > 0) { + timestamp_t deadline = get_time(); + deadline.val += timeout_us; + ret = timer_arm(deadline, me); + ASSERT(ret == EC_SUCCESS); + } + while (!(evt = atomic_read_clear(&tsk->events))) { + /* Remove ourself and get the next task in the scheduler */ + __schedule(1, resched, 0); + resched = TASK_ID_IDLE; + } + if (timeout_us > 0) + timer_cancel(me); + return evt; +} + +uint32_t task_set_event(task_id_t tskid, uint32_t event, int wait) +{ + task_ *receiver = __task_id_to_ptr(tskid); + ASSERT(receiver); + + /* Set the event bit in the receiver message bitmap */ + atomic_or(&receiver->events, event); + + /* Re-schedule if priorities have changed */ + if (in_interrupt_context()) { + /* The receiver might run again */ + atomic_or(&tasks_ready, 1 << tskid); + need_resched = 1; + } else { + if (wait) + return __wait_evt(-1, tskid); + else + __schedule(0, tskid, 0); + } + + return 0; +} + +uint32_t task_wait_event(int timeout_us) +{ + return __wait_evt(timeout_us, TASK_ID_IDLE); +} + +static uint32_t get_int_mask(void) +{ + uint32_t ret; + asm volatile ("mfsr %0, $INT_MASK" : "=r"(ret)); + return ret; +} + +static void set_int_mask(uint32_t val) +{ + asm volatile ("mtsr %0, $INT_MASK" : : "r"(val)); +} + +static void set_int_priority(uint32_t val) +{ + asm volatile ("mtsr %0, $INT_PRI" : : "r"(val)); +} + +void task_enable_irq(int irq) +{ + int cpu_int = chip_enable_irq(irq); + if (cpu_int >= 0) + set_int_mask(get_int_mask() | (1 << cpu_int)); +} + +void task_disable_irq(int irq) +{ + int cpu_int = chip_disable_irq(irq); + if (cpu_int >= 0) + set_int_mask(get_int_mask() & ~(1 << cpu_int)); +} + +void task_clear_pending_irq(int irq) +{ + chip_clear_pending_irq(irq); +} + +void task_trigger_irq(int irq) +{ + int cpu_int = chip_trigger_irq(irq); + if (cpu_int > 0) + __schedule(0, 0, cpu_int); +} + +/* + * Initialize IRQs in the IVIC and set their priorities as defined by the + * DECLARE_IRQ statements. + */ +static void ivic_init_irqs(void) +{ + /* Get the IRQ priorities section from the linker */ + int exc_calls = __irqprio_end - __irqprio; + int i; + uint32_t all_priorities = 0; + + /* chip-specific interrupt controller initialization */ + chip_init_irqs(); + + /* Mask all interrupts, only keep division by zero exception */ + set_int_mask(1 << 30 /* IDIVZ */); + + /* + * Re-enable global interrupts in case they're disabled. On a reboot, + * they're already enabled; if we've jumped here from another image, + * they're not. + */ + interrupt_enable(); + + /* Set priorities */ + for (i = 0; i < exc_calls; i++) { + uint8_t irq = __irqprio[i].irq; + uint8_t prio = __irqprio[i].priority; + all_priorities |= (prio & 0x3) << (irq * 2); + } + set_int_priority(all_priorities); +} + +void mutex_lock(struct mutex *mtx) +{ + uint32_t id = 1 << task_get_current(); + + ASSERT(id != TASK_ID_INVALID); + + /* critical section with interrupts off */ + asm volatile ("setgie.d ; dsb"); + mtx->waiters |= id; + while (1) { + if (!mtx->lock) { /* we got it ! */ + mtx->lock = 2; + mtx->waiters &= ~id; + /* end of critical section : re-enable interrupts */ + asm volatile ("setgie.e"); + return; + } else { /* Contention on the mutex */ + /* end of critical section : re-enable interrupts */ + asm volatile ("setgie.e"); + /* Sleep waiting for our turn */ + task_wait_event(0); + /* re-enter critical section */ + asm volatile ("setgie.d ; dsb"); + } + } +} + +void mutex_unlock(struct mutex *mtx) +{ + uint32_t waiters; + task_ *tsk = current_task; + + waiters = mtx->waiters; + /* give back the lock */ + mtx->lock = 0; + + while (waiters) { + task_id_t id = 31 - __builtin_clz(waiters); + + /* Somebody is waiting on the mutex */ + task_set_event(id, TASK_EVENT_MUTEX, 0); + waiters &= ~(1 << id); + } + + /* Ensure no event is remaining from mutex wake-up */ + atomic_clear(&tsk->events, TASK_EVENT_MUTEX); +} + +void task_print_list(void) +{ + int i; + + ccputs("Task Ready Name Events Time (s) StkUsed\n"); + + for (i = 0; i < TASK_ID_COUNT; i++) { + char is_ready = (tasks_ready & (1<<i)) ? 'R' : ' '; + uint32_t *sp; + + int stackused = tasks_init[i].stack_size; + + for (sp = tasks[i].stack; + sp < (uint32_t *)tasks[i].sp && *sp == STACK_UNUSED_VALUE; + sp++) + stackused -= sizeof(uint32_t); + + ccprintf("%4d %c %-16s %08x %11.6ld %3d/%3d\n", i, is_ready, + task_names[i], tasks[i].events, tasks[i].runtime, + stackused, tasks_init[i].stack_size); + cflush(); + } +} + +int command_task_info(int argc, char **argv) +{ + task_print_list(); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(taskinfo, command_task_info, + NULL, + "Print task info", + NULL); + +static int command_task_ready(int argc, char **argv) +{ + if (argc < 2) { + ccprintf("tasks_ready: 0x%08x\n", tasks_ready); + } else { + tasks_ready = strtoi(argv[1], NULL, 16); + ccprintf("Setting tasks_ready to 0x%08x\n", tasks_ready); + __schedule(0, 0, 0); + } + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(taskready, command_task_ready, + "[setmask]", + "Print/set ready tasks", + NULL); + +void task_pre_init(void) +{ + uint32_t *stack_next = (uint32_t *)task_stacks; + int i; + + /* Fill the task memory with initial values */ + for (i = 0; i < TASK_ID_COUNT; i++) { + uint32_t *sp; + /* Stack size in words */ + uint32_t ssize = tasks_init[i].stack_size / 4; + + tasks[i].stack = stack_next; + + /* + * Update stack used by first frame: 15 regs + PC + PSW + */ + sp = stack_next + ssize - 17; + tasks[i].sp = (uint32_t)sp; + + /* Initial context on stack (see __switchto()) */ + sp[7] = tasks_init[i].r0; /* r0 */ + sp[15] = (uint32_t)task_exit_trap; /* lr */ + sp[1] = tasks_init[i].pc; /* pc */ + sp[0] = 0x70009; /* psw */ + sp[16] = (uint32_t)(sp + 17); /* sp */ + + /* Fill unused stack; also used to detect stack overflow. */ + for (sp = stack_next; sp < (uint32_t *)tasks[i].sp; sp++) + *sp = STACK_UNUSED_VALUE; + + stack_next += ssize; + } + + /* + * Fill in guard value in scratchpad to prevent stack overflow + * detection failure on the first context switch. This works because + * the first word in the scratchpad is where the switcher will store + * sp, so it's ok to blow away. + */ + ((task_ *)scratchpad)->stack = (uint32_t *)scratchpad; + *(uint32_t *)scratchpad = STACK_UNUSED_VALUE; + + /* Initialize IRQs */ + ivic_init_irqs(); +} + +int task_start(void) +{ + start_called = 1; + + return __task_start(); +} diff --git a/include/link_defs.h b/include/link_defs.h index 52782760b1..3346ea8e5a 100644 --- a/include/link_defs.h +++ b/include/link_defs.h @@ -73,6 +73,7 @@ extern const struct host_command __hcmds_end[]; /* IRQs (interrupt handlers) */ extern const struct irq_priority __irqprio[]; extern const struct irq_priority __irqprio_end[]; +extern const void *__irqhandler[]; /* Shared memory buffer. Use via shared_mem.h interface. */ extern uint8_t __shared_mem_buf[]; diff --git a/include/task.h b/include/task.h index edf278c066..1aeb1206b7 100644 --- a/include/task.h +++ b/include/task.h @@ -203,6 +203,14 @@ struct irq_priority { * Macro to connect the interrupt handler "routine" to the irq number "irq" and * ensure it is enabled in the interrupt controller with the right priority. */ +#ifdef __nds32__ +#define DECLARE_IRQ(irq, routine, priority) \ + void IRQ_HANDLER(CPU_INT(irq))(void) \ + __attribute__ ((alias(STRINGIFY(routine)))); \ + const struct irq_priority IRQ_PRIORITY(CPU_INT(irq)) \ + __attribute__((section(".rodata.irqprio"))) \ + = {CPU_INT(irq), priority} +#else #define DECLARE_IRQ(irq, routine, priority) \ void IRQ_HANDLER(irq)(void) \ { \ @@ -214,5 +222,6 @@ struct irq_priority { const struct irq_priority IRQ_PRIORITY(irq) \ __attribute__((section(".rodata.irqprio"))) \ = {irq, priority} +#endif #endif /* __CROS_EC_TASK_H */ |