los_dispatch.S 15.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/*
 * Copyright (c) 2013-2019, Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020, Huawei Device Co., Ltd. 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. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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 "asm.h"
#include "arch_config.h"
34 35 36 37 38 39
/******************************************************************************
ARM的指令系统中关于栈指令的内容比较容易引起迷惑,这是因为准确描述一个栈的特点需要两个参数:
	栈地址的增长方向:ARM将向高地址增长的栈称为递增栈(Descendent Stack),
		将向低地址增长的栈称为递减栈(Acendant Stack
	栈指针的指向位置:ARM将栈指针指向栈顶元素位置的栈称为满栈(Full Stack),
		将栈指针指向即将入栈的元素位置的栈称为空栈(Empty Stack
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
栈类型
	根据栈地址增长方向雨栈指针指向位置的不同,自然可以将栈分为四类:

			递增栈	   	递减栈
	空栈		EA		ED
	满栈		FA		FD 
	
栈指令
	栈的操作指令无非两种:入栈和出栈,由于ARM描述了四种不同类型的栈,因此对应的栈指令一共有8条。

			入栈		出栈
	EA		STMEA	LDMEA
	ED		STMED	LDMED
	FA		STMFA	LDMFA
	FD		STMFD	LDMFD
	
	这些指令具有相似的前缀:
	STM:(STore Multiple data)表示存储数据,即入栈。
	LDM:(LoaD Multiple data)表示加载数据,即出栈。
	一般情况下,可以将栈操作指令分解为两步微指令:数据存取和栈指针移动。这两步操作的先后顺序和栈指针的移动方式由栈的类型决定。
	STMFD	SP减少	[SP] STMDB
	LDMFD	[SP] SP增加	LDMIA

参考
	https://www.cnblogs.com/fanzhidongyzby/p/5250116.html
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

用栈方式图 @note_pic
			-----------------<-------------------  高地址 函数 A
			|		PC		|					|
			|---------------|					|	||
			|		LR		|					|	||
			|---------------|					|	||
			|		SP		|					|	||
			|---------------|					|	\/
			|		FP		|					|
			|---------------|					|
			|	参数1 		|					|
			|---------------|					|			
			|	参数2			|					|
			|---------------|					|
			|	变量1			|					|
			|---------------|<----------|		|	函数A调用B  
			|		PC		|			|		|
			|---------------|			|		|
			|		LR		|			|		|
			|---------------|			|		|
			|		SP		|-----------|		|
			|---------------|					|
			|		FP		|-------------------|
			|---------------|
			|	参数1 		|
			|---------------|
			|	参数2			|
			|---------------|
			|	变量1			|
			|---------------|<------SP
			|	变量2			|
			|---------------|
			|---------------|						低地址	
100
******************************************************************************/
101
#ifdef LOSCFG_KERNEL_SMP_LOCKDEP
102
    .extern   OsLockDepCheckOut/*使用外部定义的函数*/
103 104 105
#endif
    .extern   g_taskSpin
    .extern   g_percpu
106 107
    .global   OsStartToRun 		/*任务开始执行  告诉 OsStartToRun()在本文件实现,将被外部函数调用,见于OsStart()   */
    .global   OsTaskSchedule	/*任务调度,见于OsSchedResched()*/
108 109
    .global   OsIrqHandler		/*硬中断处理*/
    .global   ArchSpinUnlock	/*自旋锁解锁*/
110
    .global   OsSchedToUserSpinUnlock /*尚未实现*/
111
	/* @note_why 为何要重新定义OS_TASK_STATUS_RUNNING? */
112 113 114
    .equ OS_TASK_STATUS_RUNNING,       0x0010U 	/* .equ用于把常量值设置为可以在文本段中使用的符号 #define OS_TASK_STATUS_RUNNING 0x0010U */
    .equ OS_PERCPU_STRUCT_SIZE,        0x28U 	/*宏定义 #define OS_PERCPU_STRUCT_SIZE 0x28U*/
    .equ OS_PERCPU_TASK_LOCK_OFFSET,   0x14U	/*宏定义 #define OS_PERCPU_TASK_LOCK_OFFSET 0x14U*/
115 116
    .fpu vfpv4 /* .fpu @note_why 尚未知这句话的含义 */
/* 此宏用于对齐和不对齐8字节边界上的堆栈以符合ABI */
117
/* macros to align and unalign the stack on 8 byte boundary for ABI compliance */
118
.macro STACK_ALIGN, reg /* 栈对齐*/ 
119 120 121
    MOV     \reg, sp 	@reg=sp
    TST     SP, #4		@来检查是否设置了特定的位
    SUBEQ   SP, #4		@表示相等时相减
122 123 124
    PUSH    { \reg }
.endm

125 126 127
.macro STACK_RESTORE, reg /*栈恢复*/ 
    POP     { \reg } 
    MOV     sp, \reg 
128
.endm
129 130 131 132
/* FPU(floating-point processor unit)浮点运算单元*/
/* macros to save and restore fpu regs */ 
.macro PUSH_FPU_REGS reg1 /* 保存fpu寄存器 */ 
#if !defined(LOSCFG_ARCH_FPU_DISABLE) @FPU使能
133 134 135 136 137 138 139 140 141 142 143
    VMRS    \reg1, FPEXC
    PUSH    {\reg1}
    VMRS    \reg1, FPSCR
    PUSH    {\reg1}
#if defined(LOSCFG_ARCH_FPU_VFP_D32)
    VPUSH   {D16-D31}
#endif
    VPUSH   {D0-D15}
#endif
.endm

144
.macro POP_FPU_REGS reg1 /* 恢复fpu寄存器 */ 
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
#if !defined(LOSCFG_ARCH_FPU_DISABLE)
    VPOP    {D0-D15}
#if defined(LOSCFG_ARCH_FPU_VFP_D32)
    VPOP    {D16-D31}
#endif
    POP     {\reg1}
    VMSR    FPSCR, \reg1
    POP     {\reg1}
    VMSR    FPEXC, \reg1
#endif
.endm

/* R0: new task */
OsStartToRun:

160
    MSR     CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE)  @禁止中断并切到管理模式
161

162
    LDRH    R1, [R0, #4] 	@将存储器地址为R0+4 的低16位数据读入寄存器R1,并将R1 的高16 位清零
163 164
    ORR     R1, #OS_TASK_STATUS_RUNNING @或指令 R1=R1|OS_TASK_STATUS_RUNNING
    STRH    R1, [R0, #4] 	@将寄存器R1中的低16位写入以R0+4地址的存储器中
165 166

    /* R0 is new task, save it on tpidrprw */
167 168
    MCR     p15, 0, R0, c13, c0, 4 @ C5=C4=R0
    ISB 	@指令同步屏障,清除流水线并且确保在新指令执行时,之前的指令都已经执行完毕。
169 170 171 172 173 174

    VPUSH   {S0}                                     /* fpu */
    VPOP    {S0}
    VPUSH   {D0}
    VPOP    {D0}

175
    B       OsTaskContextLoad @加载任务的上下文
176 177 178 179 180 181 182 183 184 185 186 187 188

/*
 * R0: new task
 * R1: run task
 */
OsTaskSchedule:			/*新老任务切换上下文*/
    MRS      R2, CPSR 	/*MRS 指令用于将特殊寄存器( CPSR  SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令*/
    STMFD    SP!, {LR}	/*返回地址入栈*/
    STMFD    SP!, {LR}	/*为何返回地址要再次入栈 @note_why*/
    /* jump sp */
    SUB      SP, SP, #4 /*SP=SP-4 4是向低地址偏移4个字节的位置的地址,即堆栈的头部的地址*/

    /* push r0-r12*/
189
    STMFD    SP!, {R0-R12} 	 @将寄存器列表中的寄存器(R0-R12)压栈。
190 191 192 193 194 195 196 197 198
    STMFD    SP!, {R2}		/*R2 入栈*/

    /* 8 bytes stack align */
    SUB      SP, SP, #4	/*SP=SP-4*/

    /* save fpu registers */
    PUSH_FPU_REGS   R2	/*保存fpu寄存器*/

    /* store sp on running task */
199
    STR     SP, [R1] @存储任务到栈
200 201

OsTaskContextLoad:
202 203
    /* clear the flag of ldrex */ @LDREX 可从内存加载数据,如果物理地址有共享TLB属性,则LDREX会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的任何独占访问标记。
    CLREX @清除ldrex指令的标记
204 205

    /* switch to new task's sp */
206
    LDR     SP, [R0] @从栈中取出任务
207 208

    /* restore fpu registers */
209
    POP_FPU_REGS    R2 @恢复fpu寄存器,这里用了汇编宏R2是宏的参数
210 211

    /* 8 bytes stack align */
212
    ADD     SP, SP, #4 /*SP=SP+4*/
213

214 215 216 217
    LDMFD   SP!, {R0}  @将堆栈内容出栈保存到寄存器R0
    MOV     R4, R0	@R4=R0 说明R4也记录了CPSR内容,这个内容将用于 OsKernelTaskLoad中保存到SPSR 
    AND     R0, R0, #CPSR_MASK_MODE @R0 =R0&CPSR_MASK_MODE ,目的是清除高16
    CMP     R0, #CPSR_USER_MODE @比较R0是否为用户模式
218
    BNE     OsKernelTaskLoad @不相等则跳转到OsKernelTaskLoad执行,return回去了
219 220 221 222

#ifdef LOSCFG_KERNEL_SMP
#ifdef LOSCFG_KERNEL_SMP_LOCKDEP
    /* 8 bytes stack align */
223 224 225 226
    SUB     SP, SP, #4 @sp = sp -4
    LDR     R0, =g_taskSpin @R0 = g_taskSpin
    BL      OsLockDepCheckOut @带链接的跳转指令。指令将下一条指令的地址拷贝到R14(LR)链接寄存器中,然后跳转到指定地址运行程序
    ADD     SP, SP, #4 @sp=sp+4
227 228
#endif
    /* R0 = &g_taskSpin.rawLock */
229 230 231 232 233 234 235
    LDR     R0, =g_taskSpin @ R0 = &g_taskSpin.rawLock 将作为 ArchSpinUnlock的参数
    BL      ArchSpinUnlock 	@释放自旋锁g_taskSpin
    LDR     R2, =g_percpu 
    MRC     P15, 0, R3, C0, C0, 5 @获取CPUIDR3
    UXTB    R3, R3 @字节被无符号扩展到32位(高24位清0
    MOV     R1, #OS_PERCPU_STRUCT_SIZE @R1 = sizeof(Percpu)
    MLA     R3, R1, R3, R2 @R3=R1*R3 
236 237 238 239 240 241 242 243 244 245 246 247 248 249
    MOV     R2, #0
    STR     R2, [R3, #OS_PERCPU_TASK_LOCK_OFFSET]
#endif

    MVN     R3, #CPSR_INT_DISABLE
    AND     R4, R4, R3
    MSR     SPSR_cxsf, R4

    /* restore r0-r12, lr */
    LDMFD   SP!, {R0-R12}
    LDMFD   SP, {R13, R14}^
    ADD     SP, SP, #(2 * 4)
    LDMFD   SP!, {PC}^

250 251
OsKernelTaskLoad: 			@内核任务的加载
    MSR     SPSR_cxsf, R4 	@R4保存到程序状态保存寄存器32
252
    /* restore r0-r12, lr */
253 254 255
    LDMFD   SP!, {R0-R12} 	@出栈,依次保存到 R0-R12,其实就是恢复现场
    ADD     SP, SP, #4 		@sp=SP+4
    LDMFD   SP!, {LR, PC}^ 	@返回地址赋给pc指针
256

257
OsIrqHandler:	@硬中断处理,此时已切换到硬中断栈
258 259 260
    SUB     LR, LR, #4

    /* push r0-r3 to irq stack */
261 262
    STMFD   SP, {R0-R3}		@r0-r3寄存器入 irq 
    SUB     R0, SP, #(4 * 4)@r0 = sp - 16
鸿蒙内核源码分析's avatar
鸿蒙内核源码分析 已提交
263
    MRS     R1, SPSR		@获取程序状态控制寄存器
264
    MOV     R2, LR			@r2=lr
265

266 267
    /* disable irq, switch to svc mode */@超级用户模式(SVC 模式),主要用于 SWI(软件中断) OS(操作系统)
    CPSID   i, #0x13				@切换到SVC模式,此处一切换,后续指令将入SVC的栈
268
									@CPSID i为关中断指令,对应的是CPSIE
269
    /* push spsr and pc in svc stack */
270 271
    STMFD   SP!, {R1, R2} @实际是将 SPSR,LR入栈,入栈顺序为 R1,R2,SP自增
    STMFD   SP, {LR}	  @LR再入栈,SP不自增
272

273
    AND     R3, R1, #CPSR_MASK_MODE	@获取CPU的运行模式
鸿蒙内核源码分析's avatar
鸿蒙内核源码分析 已提交
274 275
    CMP     R3, #CPSR_USER_MODE		@中断是否发生在用户模式
    BNE     OsIrqFromKernel			@中断不发生在用户模式下则跳转到OsIrqFromKernel
276 277

    /* push user sp, lr in svc stack */
278
    STMFD   SP, {R13, R14}^ 		@spLRsvc
279

鸿蒙内核源码分析's avatar
鸿蒙内核源码分析 已提交
280
OsIrqFromKernel:	@从内核发起中断
281 282
    /* from svc not need save sp and lr */@svc模式下发生的中断不需要保存splr寄存器值
    SUB     SP, SP, #(2 * 4)	@申请8个栈空间
283

284 285
    /* pop r0-r3 from irq stack*/
    LDMFD   R0, {R0-R3}	@出栈
286 287 288 289 290 291 292 293 294 295 296 297

    /* push caller saved regs as trashed regs in svc stack */
    STMFD   SP!, {R0-R3, R12}

    /* 8 bytes stack align */
    SUB     SP, SP, #4

    /*
     * save fpu regs in case in case those been
     * altered in interrupt handlers.
     */
    PUSH_FPU_REGS   R0
298
#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @IRQ使用独立栈
299 300 301 302
    PUSH    {R4}
    MOV     R4, SP
    EXC_SP_SET __svc_stack_top, OS_EXC_SVC_STACK_SIZE, R1, R2
#endif
303 304
	/*BLX 带链接和状态切换的跳转*/
    BLX     HalIrqHandler /* 调用硬中断处理程序,无参 */
305

306
#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了独立的IRQ栈
307 308 309 310
    MOV     SP, R4
    POP     {R4}
#endif

311 312
    /* process pending signals */ 	@处理挂起信号
    BL      OsTaskProcSignal 		@跳转至C代码 
313

314 315 316
    /* check if needs to schedule */@检查是否需要调度
    CMP     R0, #0	@是否需要调度,R0为参数保存值
    BLNE    OsSchedPreempt @不相等,R00,一般是 1
317

318 319 320
    MOV     R0,SP	@ OsSaveSignalContextIrq 参数1来源
    MOV     R1,R7	@ OsSaveSignalContextIrq 参数2来源
    BL      OsSaveSignalContextIrq @跳转至C代码 
321 322

    /* restore fpu regs */
323
    POP_FPU_REGS    R0 @恢复fpu寄存器值
324

325
    ADD     SP, SP, #4 @sp = sp + 4 
326

327
OsIrqContextRestore:	@恢复硬中断环境
328 329
    LDR     R0, [SP, #(4 * 7)]	@读取 SP+28 位置数据给R0
    MSR     SPSR_cxsf, R0		@恢复spsr
330
    AND     R0, R0, #CPSR_MASK_MODE
331
    CMP     R0, #CPSR_USER_MODE	@
332

333
    LDMFD   SP!, {R0-R3, R12}	@
334

335
    BNE     OsIrqContextRestoreToKernel @CPU工作在非用户模式
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351

    /* load user sp and lr, and jump cpsr */
    LDMFD   SP, {R13, R14}^
    ADD     SP, SP, #(3 * 4)

    /* return to user mode */
    LDMFD   SP!, {PC}^

OsIrqContextRestoreToKernel:
    /* svc mode not load sp */
    ADD     SP, SP, #4
    LDMFD   SP!, {LR}
    /* jump cpsr and return to svc mode */
    ADD     SP, SP, #4
    LDMFD   SP!, {PC}^

352
FUNCTION(ArchSpinLock)	@非要拿到锁
353 354 355 356 357 358 359 360 361 362 363 364
	mov 	r1, #1		@r1=1
1:						@循环的作用,因SEV是广播事件.不一定lock->rawLock的值已经改变了
	ldrex	r2, [r0]	@r0 = &lock->rawLock,  r2 = lock->rawLock
	cmp 	r2, #0		@r20比较
	wfene				@不相等时,说明资源被占用,CPU核进入睡眠状态
	strexeq r2, r1, [r0]@此时CPU被重新唤醒,尝试令lock->rawLock=1,成功写入则r2=0
	cmpeq	r2, #0		@再来比较r2是否等于0,如果相等则获取到了锁
	bne 	1b			@如果不相等,继续进入循环
	dmb 				@DMB指令来隔离,以保证缓冲中的数据已经落实到RAM
	bx		lr			@此时是一定拿到锁了,跳回调用ArchSpinLock函数


365 366

FUNCTION(ArchSpinTrylock)	@尝试拿锁
367 368 369 370 371 372 373 374 375
	mov 	r1, #1			@r1=1
	mov 	r2, r0			@r2 = r0	   
	ldrex	r0, [r2]		@r2 = &lock->rawLock,  r0 = lock->rawLock
	cmp 	r0, #0			@r00比较
	strexeq r0, r1, [r2]	@尝试令lock->rawLock=1,成功写入则r0=0,否则 r0 =1
	dmb 					@数据存储隔离,以保证缓冲中的数据已经落实到RAM
	bx		lr				@跳回调用ArchSpinLock函数


376

377 378 379 380 381 382 383 384
FUNCTION(ArchSpinUnlock)	@释放锁
	mov 	r1, #0			@r1=0				
	dmb 					@数据存储隔离,以保证缓冲中的数据已经落实到RAM
	str 	r1, [r0]		@lock->rawLock = 0
	dsb 					@数据同步隔离
	sev 					@给各CPU广播事件,唤醒沉睡的CPU
	bx		lr				@跳回调用ArchSpinLock函数

385