2013年10月30日 星期三

process switch for stm32f4discovery (1) - 保存所有暫存器

為什麼這系列文章是名為 process switch, 而不是 context switch 呢? 我有自己的想法, 有興趣的朋友可以猜猜看。

雖然基本原理是一樣的, 但細節還是有所不同。這個範例保存所有暫存器, 沒有保存 stack (其實已經可以保存 stack, 不過為了簡單, 還是一步一步來), 而且不談細節。

先來看看 process frame 的資料結構, 從 psr ~ r4 這塊區域是 process frame, 而最上方則是這個 process 的 stack 區域, 由於 stack 會在 proc a 執行時一直變動, 所以 process frame 的位址也會跟著改變, 這是和 x86 有著硬體輔助記住 stack 的機制有所區別。

高 - 高位址
低 - 低位址

process frame         資料結構
高 stack top proc stack addr
proc 可操作的 stack 區域 ...
...
process frame from here psr
pc
lr
r12
r3
r2
r1
r0
r11
r10
r9
r8
r7
r6
r5
低 stack poniterr4

黑色部份在中斷發生時, arm cm3 會自動保存, 綠色部份要靠自己的程式碼負責存起來, 保存到哪裡呢? 目前 sp 指到的位址。

在初始化一個 process 時, 設定好 psr, pc 這兩個欄位即可, ref L31 ~L53。

process_2.S
  1 # not process switch, this is context switch
  2 
  3 @.equ STACK_TOP, 0x20000800
  4 .text
  5 .global _start
  6 .code 16
  7 .syntax unified
  8 _start:
  9   .word STACK_TOP, start 
 10   .type start, function @ let lsb to 1
 11 
 12   .word int_isr+1
 13   .word hardf_isr+1
 14   .word mmf_isr+1
 15   .word int_isr+1
 16   .word int_isr+1
 17   .word int_isr+1
 18   .word int_isr+1
 19   .word int_isr+1
 20   .word int_isr+1
 21   .word svc_isr+1 @ svc isr
 22   .word int_isr+1
 23   .word int_isr+1
 24   .word int_isr+1
 25   .word int_isr+1
 26   .word int_isr+1
 27   .word int_isr+1
 28   .word int_isr+1
 29 
 30 start:
 31   @ init a proc
 32   ldr r0, =proc_a_stack
 33 
 34   @ setup proc a psr
 35   mov r1, #0x21000000
 36   str r1, [r0, #(-4)]
 37 
 38   @ setup proc a pc
 39   ldr r1, =proc_a
 40   str r1, [r0, #(-8)]
 41 
 42   @@@@@@@@@@@@@@@@@@@
 43 
 44   @ init b proc
 45   ldr r0, =proc_b_stack
 46 
 47   @ setup proc b psr
 48   mov r1, #0x21000000
 49   str r1, [r0, #(-4)]
 50 
 51   @ setup proc b pc
 52   ldr r1, =proc_b
 53   str r1, [r0, #(-8)]
 54 
 55   @@@@@@@@@@@@@@@@@@@
 56 
 57   ldr r0, =proc_a_sp
 58   ldr r1, =proc_a_stack
 59   sub r1, #(16*4)
 60   str r1, [r0]
 61 
 62   ldr r0, =proc_b_sp
 63   ldr r1, =proc_b_stack
 64   sub r1, #(16*4)
 65   str r1, [r0]
 66 
 67   ldr r0, =cur_proc
 68   mov r1, #0
 69   str r1, [r0]
 70 
 71   ldr r1, =proc_a_sp
 72   ldr r0, [r1]
 73 
 74   @ ref: scmrtos-code/Ports/CortexM3/GCC/OS_Target_asm.S
 75   LDR     r4, [r0, #(4 * 14)]     @ Load process entry point into R4
 76   add     r0, #(4 * 16)           @ emulate context restore
 77   mov sp, r0
 78 @  dsb
 79   isb                             @ Insert a barrier
 80 
 81   bx r4                           @ run proc_a
 82 
 83 .type proc_a, function @ let lsb to 1
 84 proc_a:
 85   movs r0, #0x10
 86   movs r1, #0x11
 87   movs r2, #0x12
 88   movs r3, #0x13
 89   movs r4, #0x14
 90   movs r5, #0x15
 91   movs r6, #0x16
 92   movs r7, #0x17
 93   movs r8, #0x18
 94   movs r9, #0x19
 95   movs r10, #0x1a
 96   movs r11, #0x1b
 97   movs r12, #0x1c
 98   svc 0
 99   nop
100   b proc_a
101 
102 .type proc_b, function @ let lsb to 1
103 proc_b:
104   movs r0, #0x20
105   movs r1, #0x21
106   movs r2, #0x22
107   movs r3, #0x23
108   movs r4, #0x24
109   movs r5, #0x25
110   movs r6, #0x26
111   movs r7, #0x27
112   movs r8, #0x28
113   movs r9, #0x29
114   movs r10, #0x2a
115   movs r11, #0x2b
116   movs r12, #0x2c
117   nop
118   svc 0
119   b proc_b
120 
121 
122 .type del_func, function @ let lsb to 1
123 del_func:
124   mov r5, #5
125   b del_func
126 
127 int_isr:
128   mov r0, #67
129   bx lr
130 
131 hardf_isr:
132   mov r0, #0x3b
133   bx lr
134 @ memory manage fault
135 mmf_isr:
136   mov r0, #0x3a
137   bx lr
138 
139 svc_isr:
140   cpsid i @Prevent interruption during context switch
141 
142   push {r4-r11}
143 
144   @ choice another process
145   ldr r1, =cur_proc
146   ldr r0, [r1]
147 
148 
149   cmp r0, #1
150   beq.n 1f
151   @ current proc a, switch to proc b
152   ldr r2, =proc_a_sp
153   str sp, [r2] @ save cur process sp
154 
155   mov r3, #1
156   str r3, [r1]
157 
158   ldr r0, =proc_b_sp
159 
160   b 2f
161 1:
162   @ current proc b, switch to proc a
163   ldr r2, =proc_b_sp
164   str sp, [r2] @ save cur process sp
165 
166   mov r3, #0
167   str r3, [r1]
168 
169   ldr r0, =proc_a_sp
170 
171 2:
172 
173   ldr r0, [r0]
174 
175   mov sp, r0
176   pop {R4-R11}     // Restore r4-11 from new process stack
177 
178   cpsie i
179   bx lr
180 
181 
182 .data
183 
184 proc_a_sp:
185 .word 1
186 proc_b_sp:
187 .word 2
188 
189 proc_a_ptr:
190 .space  0x300, 0
191 proc_a_stack:
192 
193 proc_b_ptr:
194 .space  0x300, 0
195 proc_b_stack:
196 
197 
198 cur_proc:
199 .word 
200 
201 .space  0x300, 0
202 STACK_TOP:

再來要記住 process frame 的 stack poniter (proc_a_sp, proc_b_sp 用來記住 r4 那個欄位的位址), ref: L57 ~ L65。

(gdb) p/x proc_a_sp
$1 = 0x200002c8
(gdb) p/x proc_b_sp
$2 = 0x200005c8

L74 ~ L81 在設定 proc a 的 stack (L76, 把 stack frame pointer 加上 4*16 即可), 並跳到 proc_a 執行。

process 切換時都是在操作 stack pointer, 保存目前 process 的 stack pointer, 取用另外一個 process stack pointer, pop r4 ~ r11, 這樣在 bx lr 而之後, 就會跳到 stack frame 的 pc 欄位裡頭的位址。

在 proc_a 執行 svc 0 之後, 跳到 svc_isr 後, stack 變化如下:
0x20000308 -> (svc 0) 0x200002e8 -> (push {r4-r11) 0x200002c8

最後 proc a process frame 內容, 把所有暫存器內容保存起來 (保存到 process frame 那個資料結構), 等著下次輪到 proc a 執行之後回復。

0x200002c8 :    0x14    0x00    0x00    0x00    0x15    0x00    0x00    0x00
0x200002d0 :    0x16    0x00    0x00    0x00    0x17    0x00    0x00    0x00
0x200002d8 :    0x18    0x00    0x00    0x00    0x19    0x00    0x00    0x00
0x200002e0 :    0x1a    0x00    0x00    0x00    0x1b    0x00    0x00    0x00
0x200002e8 :    0x10    0x00    0x00    0x00    0x11    0x00    0x00    0x00
0x200002f0 :    0x12    0x00    0x00    0x00    0x13    0x00    0x00    0x00
0x200002f8 :    0x1c    0x00    0x00    0x00    0xff    0xff    0xff    0xff
0x20000300 :    0xc0    0x00    0x00    0x00    0x00    0x00    0x00    0x01
0x20000308 :    0x08    0x03    0x00    0x20

綠色部份是 r4 ~ r11, 紅色部份是 r0~ r3, r12, lr, pc, psr。

再來載入 proc b stack poniter, 離開 svc_isr 後, 會執行 proc_b, 再來就是一樣的故事了, 只是主角換成 proc b。

在 proc_b svc 0 之後, 跳到 svc_isr 後, stack 變化如下:
0x20000608 -> (svc 0) 0x200005e8 -> (push {r4-r11) 0x200005c8

手工示意圖


我可能沒有寫得很好, 真要體會, 請在 arm cortex m3 平台跑一次, 才算真懂, qemu 似乎無法模擬 svc 的行為, 需要在真的硬體上測試。

下一篇」會說明最後的動作, 保存 stack, 這才算是功德圓滿。

source code: https://github.com/descent/stm32f4_prog
git commit: b3ed8870ce3dbc50d55909a81f7b4e5af50f9a7d

Note

When changing the stack pointer, software must use an ISB instruction immediately after 
the MSR instruction. This ensures that instructions after the ISB instruction 
execute using the new stack pointer. See ISB

感謝 Wen:
Cortex-M4 Devices Generic User Guide

參考資料:
  • STM32F207 高性能网络型 MCU 嵌入式系统设计 (15.2.2)。
  • scmrtos source code, 以下所列的程式碼為我所參考的部份。
為什麼我不去看較為流行的 freertos 而要看 scmrtos 呢? 除了該書的原因之外, 還因為 scmrtos 是 c++ 寫的, 我打算用 c++ 實作 simple_os for cm3 版本, 並用上 c++11 的新特性, 晤 ... 難度不低。

descent@descent-u:scmrtos-code-git$ git log
commit c8e243cfc666ed7230a874b951f2e719e8f9335c
Author: evgenynest <evgenynest@25b526ac-3511-40f3-916a-959e9aa89f3e>
Date:   Thu Aug 22 05:32:42 2013 +0000

git-svn-id: svn://svn.code.sf.net/p/scmrtos/code/trunk@581 25b526ac-3511-40f3-916a-959e9aa89f3e

這是幾個重點 function:
158 void os_start(stack_item_t* sp)
053 void TBaseProcess::init_stack_frame( stack_item_t * Stack
612 stack_item_t* OS::TKernel::context_switch_hook(stack_item_t* sp)
119 PendSVC_ISR:

OS_Target_asm.S
  1 //******************************************************************************
  2 //*
  3 //*     FULLNAME:  Single-Chip Microcontroller Real-Time Operating System
  4 //*
  5 //*     NICKNAME:  scmRTOS
  6 //*
  7 //*     PROCESSOR: ARM Cortex-M3 
  8 //*
  9 //*     TOOLKIT:   ARM GCC
 10 //*               
 11 //*     PURPOSE:   Target Dependent Low-Level Stuff
 12 //*               
 13 //*     Version: 4.00
 14 //*
 15 //*     $Revision$
 16 //*     $Date::             $
 17 //*
 18 //*     Copyright (c) 2003-2012, Harry E. Zhurov
 19 //*
 20 //*     Permission is hereby granted, free of charge, to any person 
 21 //*     obtaining  a copy of this software and associated documentation 
 22 //*     files (the "Software"), to deal in the Software without restriction, 
 23 //*     including without limitation the rights to use, copy, modify, merge, 
 24 //*     publish, distribute, sublicense, and/or sell copies of the Software, 
 25 //*     and to permit persons to whom the Software is furnished to do so, 
 26 //*     subject to the following conditions:
 27 //*
 28 //*     The above copyright notice and this permission notice shall be included 
 29 //*     in all copies or substantial portions of the Software.
 30 //*
 31 //*     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 32 //*     EXPRESS  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 33 //*     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 34 //*     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
 35 //*     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 36 //*     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
 37 //*     THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 38 //*
 39 //*     =================================================================
 40 //*     See http://scmrtos.sourceforge.net for documentation, latest
 41 //*     information, license and contact details.
 42 //*     =================================================================
 43 //*
 44 //******************************************************************************
 45 //*     Ported by Andrey Chuikin, Copyright (c) 2008-2012
 46 //*     Ported to GCC Ivan A-R <ivan@tuxotronic.org> (l) 2008
 47 //*     gcc port by Anton B. Gusev aka AHTOXA, Copyright (c) 2009-2012
 48 
 49 #include "scmRTOS_TARGET_CFG.h"
 50 
 51 //-----------------------------------------------------------------------------
 52 //      CODE GENERATION DIRECTIVES
 53 //
 54 //        RSEG CODE:CODE(2)
 55     .cpu cortex-m3
 56     .fpu softvfp   
 57     .syntax unified
 58     .thumb
 59     .text 
 60     .align 4
 61 
 62 
 63 //-----------------------------------------------------------------------------
 64 //  EQUATES
 65 //
 66     .equ    NVIC_SYSPRI14        ,     0xE000ED22  // System priority register (priority 14).
 67     .equ    NVIC_PENDSV_PRI      ,           0xFF  // PendSV priority value (lowest).
 68     .equ    NVIC_SYSPRI15        ,     0xE000ED23  // System priority register (priority 15).
 69     .equ    NVIC_ST_PRI          ,           0xFF  // SysTick priority value (lowest).
 70 
 71     .equ    NVIC_ST_CTRL         ,    0xE000E010   // SysTick Ctrl & Status Reg.
 72     .equ    NVIC_ST_RELOAD       ,    0xE000E014   // SysTick Reload  Value Reg.
 73     .equ    NVIC_ST_CTRL_CLK_SRC ,    0x00000004   // Clock Source.
 74     .equ    NVIC_ST_CTRL_INTEN   ,    0x00000002   // Interrupt enable.
 75     .equ    NVIC_ST_CTRL_ENABLE  ,    0x00000001   // Counter mode.
 76 
 77 
 78 //-----------------------------------------------------------------------------
 79 //  PUBLIC FUNCTIONS
 80 //
 81     .section    .text,"ax"
 82     .code 16
 83 
 84     .extern os_context_switch_hook
 85 
 86     .global PendSVC_ISR
 87     .global os_start
 88 
 89 //-----------------------------------------------------------------------------
 90 //      HANDLE PendSV EXCEPTION
 91 //      void PendSVC_ISR(void)
 92 //
 93 // Note(s) : 1) PendSV is used to cause a context switch.  This is a recommended method for performing
 94 //              context switches with Cortex-M3.  This is because the Cortex-M3 auto-saves half of the
 95 //              processor context on any exception, and restores same on return from exception.  So only
 96 //              saving of R4-R11 is required and fixing up the stack pointers.  Using the PendSV exception
 97 //              this way means that context saving and restoring is identical whether it is initiated from
 98 //              a thread or occurs due to an interrupt or exception.
 99 //
100 //           2) Pseudo-code is:
101 //              a) Get the process SP
102 //              b) Save remaining regs r4-r11 on process stack;
103 //              c) Call os_context_switch_hook for save current task SP and get new task SP;
104 //              d) Restore R4-R11 from new process stack;
105 //              e) Perform exception return which will restore remaining context.
106 //
107 //           3) On entry into PendSV handler:
108 //              a) The following have been saved on the process stack (by processor):
109 //                 xPSR, PC, LR, R12, R0-R3
110 //              b) Processor mode is switched to Handler mode (from Thread mode)
111 //              c) Stack is Main stack (switched from Process stack)
112 //
113 //           4) Since PendSV is set to lowest priority in the system (by os_start() below), we
114 //              know that it will only be run when no other exception or interrupt is active, and
115 //              therefore safe to assume that context being switched out was using the process stack (PSP).
116 //
117 
118 .thumb_func
119 PendSVC_ISR:
120     CPSID   I                 // Prevent interruption during context switch
121     MRS     R0, PSP           // PSP is process stack pointer
122     STMDB   R0!, {R4-R11}     // Save remaining regs r4-11 on process stack
123     // At this point, entire context of process has been saved                                                            
124 
125     PUSH    {LR}              // we must save LR (exc_return value) until exception return
126     BL      os_context_switch_hook    // call os_context_switch_hook();
127     
128     // R0 is new process SP;
129     LDMIA   R0!, {R4-R11}     // Restore r4-11 from new process stack
130     MSR     PSP, R0           // Load PSP with new process SP
131     CPSIE   I
132     POP     {PC}              // Return to saved exc_return. Exception return will restore remaining context
133 
134     .align 2
135 
136 //-----------------------------------------------------------------------------
137 //      Initialize system timer.
138 //      void init_system_timer()
139 // Perform systick timer initialization.
140 //
141     .weak  __init_system_timer
142 __init_system_timer:
143 
144     LDR     R1, =NVIC_SYSPRI15      // Set the SysTick exception priority (lowest)
145     LDR     R2, =NVIC_ST_PRI
146     STRB    R2, [R1]
147 
148     LDR     R1, =NVIC_ST_RELOAD     // Setup SysTick
149     LDR     R2, =(SYSTICKFREQ/SYSTICKINTRATE-1)
150     STR     R2, [R1]
151     LDR     R1, =NVIC_ST_CTRL       // Enable and run SysTick
152     LDR     R2, =(NVIC_ST_CTRL_CLK_SRC | NVIC_ST_CTRL_INTEN | NVIC_ST_CTRL_ENABLE)
153     STR     R2, [R1]
154     BX      LR
155 
156 //-----------------------------------------------------------------------------
157 //      START MULTITASKING
158 //      void os_start(stack_item_t* sp)
159 //
160 // Note(s) : 1) os_start() MUST:
161 //              a) Setup PendSV and SysTick exception priority to lowest;
162 //              b) Setup SysTick (reload value);
163 //              c) Enable interrupts (tasks will run with interrupts enabled).
164 //              d) Jump to exec() function of the highest priority process.
165 //
166 .thumb_func
167 os_start:
168     LDR     R1, =NVIC_SYSPRI14      // Set the PendSV exception priority (lowest)
169     LDR     R2, =NVIC_PENDSV_PRI
170     STRB    R2, [R1]
171 
172     LDR     R4, [R0, #(4 * 14)]  // Load process entry point into R4
173     ADD     R0, #(4 * 16)           // emulate context restore
174     MSR     PSP, R0                 // store process SP to PSP
175     MOV     R0, #2                  // set up the current (thread) mode: use PSP as stack pointer, privileged level
176     MSR     CONTROL, R0
177     ISB                             // Insert a barrier
178 
179     BL      __init_system_timer      // run system timer
180 
181     CPSIE   I                       // Enable interrupts at processor level
182 
183     BX      R4                      // Jump to process exec() function
184 
185     .end



Ports/CortexM3/GCC/OS_Target_cpp.cpp
47 
48 
49 #include <scmRTOS.h>
50 
51 using namespace OS;
52 
53 void TBaseProcess::init_stack_frame( stack_item_t * Stack
54                                    , void (*exec)()
55                                 #if scmRTOS_DEBUG_ENABLE == 1
56                                    , stack_item_t * StackBegin
57                                 #endif
58                                    )
59 {
60     // ARM Architecture Procedure Call Standard [AAPCS] requires 8-byte stack alignment:
61     StackPointer = (stack_item_t*)((uintptr_t)Stack & 0xFFFFFFF8);
62     *(--StackPointer)  = 0x01000000L;             // xPSR
63     *(--StackPointer)  = reinterpret_cast<uint32_t>(exec); // Entry Point
64     StackPointer -= 14;                           // emulate "push R14,R12,R3,R2,R1,R0,R11-R4"
65 
66 #if scmRTOS_DEBUG_ENABLE == 1
67     for (stack_item_t* pDst = StackBegin; pDst < StackPointer; pDst++)
68         *pDst = STACK_DEFAULT_PATTERN;
69 #endif // scmRTOS_DEBUG_ENABLE
70 }
71 
72 #pragma weak SystemTimer_ISR = Default_SystemTimer_ISR
73 
74 namespace OS
75 {
76 extern "C" void Default_SystemTimer_ISR();
77 }
78 
79 //------------------------------------------------------------------------------
80 OS_INTERRUPT void OS::Default_SystemTimer_ISR()
81 {
82     scmRTOS_ISRW_TYPE ISR;
83 
84 #if scmRTOS_SYSTIMER_HOOK_ENABLE == 1
85     system_timer_user_hook();
86 #endif
87 
88     Kernel.system_timer();
89 
90 #if scmRTOS_SYSTIMER_NEST_INTS_ENABLE == 0
91     DISABLE_NESTED_INTERRUPTS();
92 #endif
93 }
94 //------------------------------------------------------------------------------
95 


Common/OS_Kernel.h
607 //------------------------------------------------------------------------------
608 #ifndef CONTEXT_SWITCH_HOOK_CRIT_SECT
609 #define CONTEXT_SWITCH_HOOK_CRIT_SECT()
610 #endif
611 
612 stack_item_t* OS::TKernel::context_switch_hook(stack_item_t* sp)
613 {
614     CONTEXT_SWITCH_HOOK_CRIT_SECT();
615 
616     ProcessTable[CurProcPriority]->StackPointer = sp;
617     sp = ProcessTable[SchedProcPriority]->StackPointer;
618     
619 #if scmRTOS_CONTEXT_SWITCH_USER_HOOK_ENABLE == 1
620     context_switch_user_hook();
621 #endif
622 
623     CurProcPriority = SchedProcPriority;
624     return sp;
625 }
626 //------------------------------------------------------------------------------
627 #endif // scmRTOS_CONTEXT_SWITCH_SCHEME
628 
629 //-----------------------------------------------------------------------------
630 bool OS::TBaseProcess::is_sleeping() const
631 {
632     TCritSect cs;
633     return this->Timeout != 0;
634 }
635 //-----------------------------------------------------------------------------
636 bool OS::TBaseProcess::is_suspended() const
637 {
638     TCritSect cs;
639     return (Kernel.ReadyProcessMap & get_prio_tag(this->Priority)) == 0;
640 }
641 
642 //-----------------------------------------------------------------------------
643 INLINE void OS::run()
644 {
645     stack_item_t *sp = Kernel.ProcessTable[pr0]->StackPointer;
646     os_start(sp);
647 }
648 
649 #if scmRTOS_OBSOLETE_NAMES == 1
650 namespace OS
651 {
652     INLINE void Sleep(timeout_t t = 0) { sleep(t); }
653 
654     INLINE bool IsProcessSleeping(const TBaseProcess& p) { return p.is_sleeping(); }
655     INLINE bool IsProcessSuspended(const TBaseProcess& p) { return p.is_suspended(); }
656 }   // namespace OS
657 #endif
658 
659 #include <OS_Services.h>
660 
661 #endif // OS_KERNEL_H

也可以參考這個檔案:scmrtos-code-git/Ports/CortexM4F/GCC/OS_Target_cpp.cpp

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。