diff --git a/ports/cortex-m/Makefile b/ports/cortex-m/Makefile index d4521ad..6929dd6 100644 --- a/ports/cortex-m/Makefile +++ b/ports/cortex-m/Makefile @@ -197,32 +197,32 @@ $(build_dir)/%.elf $(build_dir)/%.map: $(build_dir)/%.o $(build_objs) $(LDSCRIPT $(if $(Q), @echo " (ELF) $(subst $(build_dir)/,,$@)") $(Q)$(LD) $(LDFLAGS) $(ARCH_FLAGS) $(build_objs) $(build_dir)/$(*).o $(LDLIBS) -o $@ -$(build_dir)/%.o: $(src_dir)/%.S +$(build_dir)/%.o: $(src_dir)/%.S Makefile $(Q)mkdir -p `dirname $@` $(if $(Q), @echo " (AS) $(subst $(build_dir)/,,$@)") $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -D__ASSEMBLY__ -I`dirname $<` -c $< -o $@ -$(build_dir)/%.o: $(src_dir)/%.c +$(build_dir)/%.o: $(src_dir)/%.c Makefile $(Q)mkdir -p `dirname $@` $(if $(Q), @echo " (CC) $(subst $(build_dir)/,,$@)") $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -I`dirname $<` -c $< -o $@ -$(build_dir)/%.o: $(board_dir)/%.c +$(build_dir)/%.o: $(board_dir)/%.c Makefile $(Q)mkdir -p `dirname $@` $(if $(Q), @echo " (CC) $(subst $(build_dir)/,,$@)") $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -I`dirname $<` -c $< -o $@ -$(build_dir)/%.o: $(common_dir)/%.c +$(build_dir)/%.o: $(common_dir)/%.c Makefile $(Q)mkdir -p `dirname $@` $(if $(Q), @echo " (CC) $(subst $(build_dir)/,,$@)") $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -I`dirname $<` -c $< -o $@ -$(build_dir)/%.o: $(kernel_dir)/%.c +$(build_dir)/%.o: $(kernel_dir)/%.c Makefile $(Q)mkdir -p `dirname $@` $(if $(Q), @echo " (CC) $(subst $(build_dir)/,,$@)") $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -I`dirname $<` -c $< -o $@ -$(build_dir)/%.o: $(tests_dir)/%.c +$(build_dir)/%.o: $(tests_dir)/%.c Makefile $(Q)mkdir -p `dirname $@` $(if $(Q), @echo " (CC) $(subst $(build_dir)/,,$@)") $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -I`dirname $<` -c $< -o $@ diff --git a/ports/cortex-m/README.md b/ports/cortex-m/README.md index ae3ad3f..52a8f38 100644 --- a/ports/cortex-m/README.md +++ b/ports/cortex-m/README.md @@ -1,7 +1,7 @@ # ARM Cortex-M Port ## Summary and Prerequisites -This port should run on any Cortex-M3/4/4F (but not on M0, sorry). It uses +This port should run on any Cortex-M0/3/4/4F (M0+ not tested). It uses RedHat's [newlib](https://sourceware.org/newlib/) and [libopencm3] (https://github.com/libopencm3/libopencm3). You will also need an ARM compiler toolchain. If you want to flash or debug your target, [openocd](http://openocd.org) diff --git a/ports/cortex-m/atomport-asm.S b/ports/cortex-m/atomport-asm.S index b270b3b..3ff0eeb 100644 --- a/ports/cortex-m/atomport-asm.S +++ b/ports/cortex-m/atomport-asm.S @@ -29,11 +29,24 @@ .syntax unified +#undef THUMB_2 +#undef WITH_FPU + +#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) +#define THUMB_2 +#endif + +#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) +#define WITH_FPU +#endif + .extern CTX_SW_NFO .extern _stack .extern vector_table -.equ FPU_USED, 0x00000010 +.equ FPU_USED, 0x00000010 +.equ SCB_ICSR, 0xE000ED04 +.equ PENDSVCLR, 0x08000000 .text @@ -46,7 +59,7 @@ archFirstThreadRestore: * Disable interrupts. They should be disabled anyways, but just * to make sure... */ - mov r1, #1 + movs r1, #1 msr PRIMASK, r1 /** @@ -63,38 +76,101 @@ archFirstThreadRestore: str r0, [r1, #4] /* Get thread stack pointer from tcb. Conveniently the first element */ - ldr r2, [r0, #0] + ldr r1, [r0, #0] + msr PSP, r1 - /* Discard dummy thread stack frame */ - add r2, r2, #36 - /** - * New threads can't have used the FPU, so no need to check for - * FPU stack frame here + * Set bit #1 in CONTROL. Causes switch to PSP, so we can work directly + * with SP now and use pop/push. + */ + movs r1, #2 + mrs r2, CONTROL + orrs r2, r2, r1 + msr CONTROL, r2 + + /** + * Initialise thread's register context from its stack frame. Since this + * function gets called only once at system start up, execution time is + * not critical. We can get away with using only Thumb-1 instructions that + * will work on all Cortex-M devices. + * + * Initial stack looks like this: + * xPSR + * PC + * lr + * r12 + * r3 + * r2 + * r1 + * r0 + * exc_ret <- ignored here + * r11 + * r10 + * r9 + * r8 + * r7 + * r6 + * r5 + * r4 <- thread's saved_sp points here */ /** - * Load exception stack frame to registers r3-r10. xPSR ends up in r10, - * pc in r9 + * + * Move SP to position of r8 and restore high registers by loading + * them to r4-r7 before moving them to r8-r11 */ - ldmia r2!, {r3-r10} + add SP, #16 + pop {r4-r7} + mov r8, r4 + mov r9, r5 + mov r10, r6 + mov r11, r7 - /* initialise xPSR and store store adjusted stack pointer in PSP */ - msr APSR_nzcvq, r10 - msr PSP, r2 + /* move SP back to top of stack and load r4-r7 */ + sub SP, #32 + pop {r4-r7} - /* set bit #1 in CONTROL. Causes switch to PSP */ - mrs r1, CONTROL - orr r1, r1, #2 - msr CONTROL, r1 + /*load r12, lr, pc and xpsr to r0-r3 and restore r12 and lr */ + add SP, #36 + pop {r0-r3} + mov r12, r0 + mov lr, r1 + + /** + * r2 contains the PC and r3 APSR, SP is now at the bottom of the stack. We + * can't initialise APSR now because we will have to do a movs later when + * enabling interrupts, so r3 must not be touched. We also need an extra + * register holding the value that will be moved to PRIMASK. To do this, + * we build a new stack containing only the initial values of r2, r3 + * and pc. In the end this will be directly popped into the registers, + * finishing the thread restore and branching to the thread's entry point. + */ + + /* Save PC value */ + push {r2} + + /* Move values for r2 and r3 to lie directly below value for pc */ + sub SP, #20 + pop {r1-r2} + add SP, #12 + push {r1-r2} + + /* Load values for r0 and r1 from stack */ + sub SP, #20 + pop {r0-r1} + + /* Move SP to start of our new r2,r3,pc mini stack */ + add SP, #12 + + /* Restore xPSR and enable interrupts */ + movs r2, #0 + msr APSR_nzcvq, r3 + msr PRIMASK, r2 - /* enable interrupts */ - mov r1, #0 - msr PRIMASK, r1 - - /* branch to thread entry point */ - bx r9 + /* Pop r2,r3,pc from stack, thereby jumping to thread entry point */ + pop {r2,r3,pc} nop + .size archFirstThreadRestore, . - archFirstThreadRestore .endfunc @@ -107,13 +183,47 @@ pend_sv_handler: * Disable interrupts. No need to check if they were enabled because, * well, we're an interrupt handler. Duh... */ - mov r0, #1 + movs r0, #1 msr PRIMASK, r0 - /* Get running thread's stack pointer*/ + /** + * Clear PendSv pending bit. There seems to exist a hardware race condition + * in the NVIC that can prevent automatic clearing of the PENDSVSET. See + * http://embeddedgurus.com/state-space/2011/09/whats-the-state-of-your-cortex/ + */ + ldr r0, = SCB_ICSR + ldr r1, = PENDSVCLR + str r1, [r0, #0] + + /** + * Check if running and next thread are really different. + * From here on we have + * r0 = &ctx_switch_info + * r1 = ctx_switch_info.running_tcb + * r2 = ctx_switch_info.next_tcb + * + * If r1 == r2 we can skip the context switch. This may theoretically + * happen if the running thread gets scheduled out and in again by + * multiple nested or tail-chained ISRs before the PendSv handler + * gets called. + */ + ldr r0, = CTX_SW_NFO + ldr r1, [r0, #0] + ldr r2, [r0, #4] + cmp r1, r2 + beq no_switch + + /** + * Copy running thread's process stack pointer to r3 and use it to push + * */ mrs r3, PSP -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) +#if defined(THUMB_2) + /** + * Save old thread's context on Cortex-M[34] + */ + +#if defined(WITH_FPU) /* Check if FPU was used by thread and store registers if necessary */ tst lr, FPU_USED it eq @@ -124,31 +234,68 @@ pend_sv_handler: * fault handler to store the FPU registers if another thread * tries using it */ -#endif +#endif // WITH_FPU /* Push running thread's remaining registers on stack */ stmdb r3!, {r4-r11, lr} - /* Fetch running thread's TCB and store its current stack pointer */ - ldr r2, = CTX_SW_NFO - ldr r1, [r2, #0] +#else // !THUMB2 + + /** + * Save old thread's register context on Cortex-M0. + * Push running thread's remaining registers on stack. + * Thumb-1 can only use stm on low registers, so we + * have to do this in two steps. + */ + + /* Reserve space for r8-r11 + exc_return before storing r4-r7 */ + subs r3, r3, #36 + stmia r3!, {r4-r7} + + /** + * Move r8-r11 to low registers and use store multiple with automatic + * post-increment to push them on the stack + */ + mov r4, r8 + mov r5, r9 + mov r6, r10 + mov r7, r11 + stmia r3!, {r4-r7} + + /** + * Move lr (contains the exc_return code) to low registers and store it + * on the stack. + */ + mov r4, lr + str r4, [r3, #0] + + /* Re-adjust r3 to point at top of stack */ + subs r3, r3, #32 +#endif // !THUMB_2 + /** + * Address of running TCB still in r1. Store threads current stack top + * into its sp_save_ptr, which is the struct's first element + */ str r3, [r1, #0] /** - * Fetch pointer to next thread's tcb from ctx_switch_info.next_tcb and - * use it to update ctx_switch_info.running_tcb. + * ctx_switch_info.next_tcb is going to become ctx_switch_info.running_tcb, + * so we update the pointer. */ - ldr r1, [r2, #4] - str r1, [r2, #0] + str r2, [r0, #0] /** - * Fetch next thread's stack pointer from its TCB and restore + * Fetch next thread's stack pointer from its TCB's sp_save_ptr and restore * the thread's register context. */ - ldr r3, [r1, #0] + ldr r3, [r2, #0] + +#if defined(THUMB_2) + + /* Cortex-M[34], restore thread's task stack frame */ ldmia r3!, {r4-r11, lr} -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) +#if defined(WITH_FPU) /** * Check if FPU was used by new thread and restore registers if necessary. */ @@ -160,13 +307,43 @@ pend_sv_handler: * TODO: only restore FPU registers if FPU was used by another thread * between this thread being scheduled out and now. */ -#endif +#endif // WITH_FPU +#else // !THUMB_2 - /* Restore process stack pointer */ + /** + * Thread restore for Cortex-M0 + * Restore thread's task stack frame. Because thumb 1 only supports + * load multiple on low register, we have to do it in two steps and + * adjust the stack pointer manually. + */ + + /* Restore high registers */ + adds r3, r3, #16 + ldmia r3!, {r4-r7} + mov r8, r4 + mov r9, r5 + mov r10, r6 + mov r11, r7 + + /* Restore lr */ + ldr r4, [r3, #0] + mov lr, r4 + subs r3, r3, #32 + + /** + * Restore r4-r7 and adjust r3 to point at the top of the exception + * stack frame. + */ + ldmia r3!, {r4-r7} + adds r3, r3, #20 +#endif // !THUMB_2 + + /* Set process stack pointer to new thread's stack*/ msr PSP, r3 +no_switch: /* Re-enable interrupts */ - mov r0, #0 + movs r0, #0 msr PRIMASK, r0 /* Return to new thread */ diff --git a/ports/cortex-m/atomport-private.h b/ports/cortex-m/atomport-private.h index 09cc58d..6fe0e86 100644 --- a/ports/cortex-m/atomport-private.h +++ b/ports/cortex-m/atomport-private.h @@ -45,7 +45,7 @@ struct isr_stack { uint32_t lr; uint32_t pc; uint32_t psr; -}; +} __attribute__((packed)); struct isr_fpu_stack { uint32_t s0; @@ -65,7 +65,7 @@ struct isr_fpu_stack { uint32_t s14; uint32_t s15; uint32_t fpscr; -}; +} __attribute__((packed)); /** * remaining context saved by task switch ISR @@ -79,8 +79,8 @@ struct task_stack { uint32_t r9; uint32_t r10; uint32_t r11; - uint32_t lr; -}; + uint32_t exc_ret; +} __attribute__((packed)); struct task_fpu_stack { uint32_t s16; @@ -99,7 +99,7 @@ struct task_fpu_stack { uint32_t s29; uint32_t s30; uint32_t s31; -}; +} __attribute__((packed)); /** * Info needed by pend_sv_handler used for delayed task switching. @@ -112,6 +112,6 @@ struct task_fpu_stack { struct task_switch_info { volatile struct atom_tcb *running_tcb; volatile struct atom_tcb *next_tcb; -}; +} __attribute__((packed)); #endif /* __ATOMPORT_PRIVATE_H_ */ diff --git a/ports/cortex-m/atomport.c b/ports/cortex-m/atomport.c index 4dc612a..ba4a4bf 100644 --- a/ports/cortex-m/atomport.c +++ b/ports/cortex-m/atomport.c @@ -165,28 +165,28 @@ void archThreadContextInit(ATOM_TCB *tcb_ptr, void *stack_top, isr_ctx->pc = (uint32_t) thread_shell; /* initialise unused registers to silly value */ - isr_ctx->lr = 0xDEADBEEF; - isr_ctx->r12 = 0xDEADBEEF; - isr_ctx->r3 = 0xDEADBEEF; - isr_ctx->r2 = 0xDEADBEEF; - isr_ctx->r1 = 0xDEADBEEF; - isr_ctx->r0 = 0xDEADBEEF; + isr_ctx->lr = 0xEEEEEEEE; + isr_ctx->r12 = 0xCCCCCCCC; + isr_ctx->r3 = 0x33333333; + isr_ctx->r2 = 0x22222222; + isr_ctx->r1 = 0x11111111; + isr_ctx->r0 = 0x00000000; /** * We use this special EXC_RETURN code to switch from main stack to our * thread stack on exception return */ - tsk_ctx->lr = 0xFFFFFFFD; + tsk_ctx->exc_ret = 0xFFFFFFFD; /* initialise unused registers to silly value */ - tsk_ctx->r11 = 0xDEADBEEF; - tsk_ctx->r10 = 0xDEADBEEF; - tsk_ctx->r9 = 0xDEADBEEF; - tsk_ctx->r8 = 0xDEADBEEF; - tsk_ctx->r7 = 0xDEADBEEF; - tsk_ctx->r6 = 0xDEADBEEF; - tsk_ctx->r5 = 0xDEADBEEF; - tsk_ctx->r4 = 0xDEADBEEF; + tsk_ctx->r11 = 0xBBBBBBBB; + tsk_ctx->r10 = 0xAAAAAAAA; + tsk_ctx->r9 = 0x99999999; + tsk_ctx->r8 = 0x88888888; + tsk_ctx->r7 = 0x77777777; + tsk_ctx->r6 = 0x66666666; + tsk_ctx->r5 = 0x55555555; + tsk_ctx->r4 = 0x44444444; /** * Stack frames have been initialised, save it to the TCB. Also set diff --git a/ports/cortex-m/boards/nucleo-f072rb/Makefile b/ports/cortex-m/boards/nucleo-f072rb/Makefile new file mode 100644 index 0000000..4b7a6d4 --- /dev/null +++ b/ports/cortex-m/boards/nucleo-f072rb/Makefile @@ -0,0 +1,15 @@ +TARGET := stm32f072rb +LIBNAME = opencm3_stm32f0 +DEFS = -DSTM32F0 +DEFS += -DSTD_CON=USART2 +DEFS += -DMST_SIZE=0x400 + +FP_FLAGS ?= -msoft-float +ARCH_FLAGS = -mthumb -mcpu=cortex-m0 + +OOCD ?= openocd +OOCD_INTERFACE ?= stlink-v2-1 +OOCD_BOARD ?= st_nucleo_f0 + +objs += board_setup.o +objs += stubs.o stm32_con.o diff --git a/ports/cortex-m/boards/nucleo-f072rb/board_setup.c b/ports/cortex-m/boards/nucleo-f072rb/board_setup.c new file mode 100644 index 0000000..ca61717 --- /dev/null +++ b/ports/cortex-m/boards/nucleo-f072rb/board_setup.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2015, Tido Klaassen. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "atomport.h" + +/** + * Set up USART2. + * This one is connected via the virtual serial port on the Nucleo Board + */ +static void usart_setup(uint32_t baud) +{ + rcc_periph_clock_enable(RCC_GPIOA); + rcc_periph_clock_enable(RCC_USART2); + + usart_disable(USART2); + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2 | GPIO3); + gpio_set_af(GPIOA, GPIO_AF1, GPIO2 | GPIO3); + + usart_set_baudrate(USART2, baud); + usart_set_databits(USART2, 8); + usart_set_parity(USART2, USART_PARITY_NONE); + usart_set_stopbits(USART2, USART_CR2_STOP_1_0BIT); + usart_set_mode(USART2, USART_MODE_TX_RX); + usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE); + + usart_enable(USART2); +} + +/** + * initialise and start SysTick counter. This will trigger the + * sys_tick_handler() periodically once interrupts have been enabled + * by archFirstThreadRestore() + */ +static void systick_setup(void) +{ + systick_set_frequency(SYSTEM_TICKS_PER_SEC, 48000000); + + systick_interrupt_enable(); + + systick_counter_enable(); +} + +/** + * Set up the core clock to something other than the internal 16MHz PIOSC. + * Make sure that you use the same clock frequency in systick_setup(). + */ +static void clock_setup(void) +{ + /* set core clock to 72MHz, generated from external 8MHz crystal */ + rcc_clock_setup_in_hsi_out_48mhz(); +} + +/** + * Set up user LED and provide function for toggling it. This is for + * use by the test suit programs + */ +static void test_led_setup(void) +{ + /* LED is connected to GPIO5 on port A */ + rcc_periph_clock_enable(RCC_GPIOA); + + gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO5); + gpio_set(GPIOA, GPIO5); +} + +void test_led_toggle(void) +{ + gpio_toggle(GPIOA, GPIO5); +} + +/** + * Callback from your main program to set up the board's hardware before + * the kernel is started. + */ +int board_setup(void) +{ + /* Disable interrupts. This makes sure that the sys_tick_handler will + * not be called before the first thread has been started. + * Interrupts will be enabled by archFirstThreadRestore(). + */ + cm_mask_interrupts(true); + + /* configure system clock, user LED and UART */ + clock_setup(); + test_led_setup(); + usart_setup(115200); + + /* initialise SysTick counter */ + systick_setup(); + + return 0; +} + diff --git a/ports/cortex-m/linker/stm32f072rb.ld b/ports/cortex-m/linker/stm32f072rb.ld new file mode 100644 index 0000000..a70c131 --- /dev/null +++ b/ports/cortex-m/linker/stm32f072rb.ld @@ -0,0 +1,10 @@ +/* Memory regions for STM32F072RB, 128K flash, 16K RAM. */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 128K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16K +} + +/* Include main opencm3 linker script */ +INCLUDE libopencm3_stm32f0.ld diff --git a/ports/cortex-m/tests-main.c b/ports/cortex-m/tests-main.c index 225ee49..edf7c39 100644 --- a/ports/cortex-m/tests-main.c +++ b/ports/cortex-m/tests-main.c @@ -29,6 +29,7 @@ #include +#include #include "atom.h" #include "atomport-private.h" #include "atomtests.h" @@ -144,6 +145,8 @@ int main ( void ) { int8_t status; + nvic_set_priority(NVIC_PENDSV_IRQ, 0xFF); + nvic_set_priority(NVIC_SYSTICK_IRQ, 0xFE); /** * Note: to protect OS structures and data during initialisation,