cpuswitch.S revision 1.28.2.6 1 /* $NetBSD: cpuswitch.S,v 1.28.2.6 2002/10/19 15:13:23 bjh21 Exp $ */
2
3 /*
4 * Copyright (c) 1994-1998 Mark Brinicombe.
5 * Copyright (c) 1994 Brini.
6 * All rights reserved.
7 *
8 * This code is derived from software written for Brini by Mark Brinicombe
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by Brini.
21 * 4. The name of the company nor the name of the author may be used to
22 * endorse or promote products derived from this software without specific
23 * prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY BRINI ``AS IS'' AND ANY EXPRESS OR IMPLIED
26 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
27 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28 * IN NO EVENT SHALL BRINI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 *
37 * RiscBSD kernel project
38 *
39 * cpuswitch.S
40 *
41 * cpu switching functions
42 *
43 * Created : 15/10/94
44 */
45
46 #include "opt_armfpe.h"
47 #include "opt_multiprocessor.h"
48
49 #include "assym.h"
50 #include <machine/param.h>
51 #include <machine/cpu.h>
52 #include <machine/frame.h>
53 #include <machine/asm.h>
54
55 #undef IRQdisable
56 #undef IRQenable
57
58 /*
59 * New experimental definitions of IRQdisable and IRQenable
60 * These keep FIQ's enabled since FIQ's are special.
61 */
62
63 #define IRQdisable \
64 mrs r14, cpsr ; \
65 orr r14, r14, #(I32_bit) ; \
66 msr cpsr_c, r14 ; \
67
68 #define IRQenable \
69 mrs r14, cpsr ; \
70 bic r14, r14, #(I32_bit) ; \
71 msr cpsr_c, r14 ; \
72
73 .text
74
75 .Lwhichqs:
76 .word _C_LABEL(sched_whichqs)
77
78 .Lqs:
79 .word _C_LABEL(sched_qs)
80
81 /*
82 * cpuswitch()
83 *
84 * preforms a process context switch.
85 * This function has several entry points
86 */
87
88 #ifdef MULTIPROCESSOR
89 .Lcpu_info:
90 .word _C_LABEL(cpu_info)
91 #else
92 .Lcurproc:
93 .word _C_LABEL(curproc)
94
95 .Lcurpcb:
96 .word _C_LABEL(curpcb)
97 #endif
98
99 .Lwant_resched:
100 .word _C_LABEL(want_resched)
101
102 .Lcpufuncs:
103 .word _C_LABEL(cpufuncs)
104
105 #ifndef MULTIPROCESSOR
106 .data
107 .global _C_LABEL(curpcb)
108 _C_LABEL(curpcb):
109 .word 0x00000000
110 .text
111 #endif
112
113 .Lblock_userspace_access:
114 .word _C_LABEL(block_userspace_access)
115
116 .Lcpu_do_powersave:
117 .word _C_LABEL(cpu_do_powersave)
118
119 /*
120 * Idle loop, exercised while waiting for a process to wake up.
121 *
122 * NOTE: When we jump back to .Lswitch_search, we must have a
123 * pointer to whichqs in r7, which is what it is when we arrive
124 * here.
125 */
126 /* LINTSTUB: Ignore */
127 ASENTRY_NP(idle)
128 #ifdef MULTIPROCESSOR
129 /* Switch to the idle PCB unless we're already running on it. */
130 cmp r8, #0 /* old process was exiting? */
131 beq 1f
132
133 /* Get the user structure for the old process. */
134 ldr r0, [r8, #(P_ADDR)]
135
136 /* Save the remaining registers in the old process's pcb */
137 add r0, r0, #(PCB_R11)
138 stmia r0, {r11-r13}
139 1:
140 ldr r0, [r9, #(CI_IDLEPCB)]
141 add r0, r0, #(PCB_R11)
142 ldmia r0, {r11-r13}
143 mov r8, #0 /* old process is irrelevant now */
144 #endif
145 #if defined(MULTIPROCESSOR) || defined(LOCKDEBUG)
146 bl _C_LABEL(sched_unlock_idle)
147 #endif
148 ldr r3, .Lcpu_do_powersave
149
150 /* Enable interrupts */
151 IRQenable
152
153 /* If we don't want to sleep, use a simpler loop. */
154 ldr r3, [r3] /* r3 = cpu_do_powersave */
155 teq r3, #0
156 bne 2f
157
158 /* Non-powersave idle. */
159 1: /* should maybe do uvm pageidlezero stuff here */
160 ldr r3, [r7] /* r3 = whichqs */
161 teq r3, #0x00000000
162 bne .Lswitch_search
163 b 1b
164
165 2: /* Powersave idle. */
166 ldr r4, .Lcpufuncs
167 3: ldr r3, [r7] /* r3 = whichqs */
168 teq r3, #0x00000000
169 bne .Lswitch_search
170
171 /* if saving power, don't want to pageidlezero */
172 mov r0, #0
173 adr lr, 3b
174 ldr pc, [r4, #(CF_SLEEP)]
175 /* loops back around */
176
177
178 /*
179 * Find a new process to run, save the current context and
180 * load the new context
181 */
182
183 ENTRY(cpu_switch)
184 /*
185 * Local register usage. Some of these registers are out of date.
186 * r0-r2 = scratch
187 * r3 = whichqs
188 * r4 = queue
189 * r5 = &qs[queue]
190 * r6 = newproc
191 * r7 = &whichqs, new first proc in q, old pcb, new pcb
192 * r8 = oldproc
193 * r9 = curcpu()
194 */
195 mov ip, sp
196 stmfd sp!, {r4-r10, fp, ip, lr, pc}
197 sub fp, ip, #4
198
199 #ifdef MULTIPROCESSOR
200 /* XXX Probably not appropriate for non-Hydra SMPs */
201 bl _C_LABEL(cpu_number)
202 ldr r9, .Lcpu_info
203 ldr r9, [r9, r0, lsl #2]
204 #endif
205
206 /*
207 * Get the current process and indicate that there is no longer
208 * a valid process (curproc = 0). Zero the current PCB pointer
209 * while we're at it.
210 */
211 #ifdef MULTIPROCESSOR
212 ldr r8, [r9, #CI_CURPROC] /* r8 = curproc */
213 mov r0, #0
214 str r0, [r9, #CI_CURPROC] /* curproc = NULL */
215 str r0, [r9, #CI_CURPCB] /* curpcb = NULL */
216 #else
217 ldr r1, .Lcurproc
218 ldr r2, .Lcurpcb
219 mov r0, #0x00000000
220 ldr r8, [r1] /* r8 = curproc */
221 str r0, [r1] /* curproc = NULL */
222 str r0, [r2] /* curpcb = NULL */
223 #endif
224
225 #if defined(MULTIPROCESSOR) || defined(LOCKDEBUG)
226 /* release the sched_lock before handling interrupts */
227 bl _C_LABEL(sched_unlock_idle)
228 #endif
229
230 /* Lower the spl level to spl0 and get the current spl level. */
231 #ifdef __NEWINTR
232 mov r0, #(IPL_NONE)
233 bl _C_LABEL(_spllower)
234 #else /* ! __NEWINTR */
235 #ifdef spl0
236 mov r0, #(_SPL_0)
237 bl _C_LABEL(splx)
238 #else
239 bl _C_LABEL(spl0)
240 #endif /* spl0 */
241 #endif /* __NEWINTR */
242
243 /* Push the old spl level onto the stack */
244 str r0, [sp, #-0x0004]!
245
246 /* First phase : find a new process */
247
248 ldr r7, .Lwhichqs
249
250 /* rem: r7 = &whichqs */
251 /* rem: r8 = old proc */
252
253 .Lswitch_search:
254 IRQdisable
255 #if defined(MULTIPROCESSOR) || defined(LOCKDEBUG)
256 bl _C_LABEL(sched_lock_idle)
257 #endif
258
259 /* Do we have any active queues */
260 ldr r3, [r7]
261
262 /* If not we must idle until we do. */
263 teq r3, #0x00000000
264 beq _ASM_LABEL(idle)
265
266 /* rem: r8 = old proc */
267 /* rem: r3 = whichqs */
268 /* rem: interrupts are disabled */
269
270 /*
271 * We have found an active queue. Currently we do not know which queue
272 * is active just that one of them is.
273 */
274 /* this is the ffs algorithm devised by d.seal and posted to
275 * comp.sys.arm on 16 Feb 1994.
276 */
277 rsb r5, r3, #0
278 ands r0, r3, r5
279
280 adr r5, .Lcpu_switch_ffs_table
281
282 /* X = R0 */
283 orr r4, r0, r0, lsl #4 /* r4 = X * 0x11 */
284 orr r4, r4, r4, lsl #6 /* r4 = X * 0x451 */
285 rsb r4, r4, r4, lsl #16 /* r4 = X * 0x0450fbaf */
286
287 /* used further down, saves SA stall */
288 ldr r6, .Lqs
289
290 /* now lookup in table indexed on top 6 bits of a4 */
291 ldrb r4, [ r5, r4, lsr #26 ]
292
293 /* rem: r0 = bit mask of chosen queue (1 << r4) */
294 /* rem: r3 = whichqs */
295 /* rem: r4 = queue number */
296 /* rem: r8 = old proc */
297 /* rem: interrupts are disabled */
298
299 /* Get the address of the queue (&qs[queue]) */
300 add r5, r6, r4, lsl #3
301
302 /*
303 * Get the process from the queue and place the next process in
304 * the queue at the head. This basically unlinks the process at
305 * the head of the queue.
306 */
307 ldr r6, [r5, #(P_FORW)]
308
309 /* rem: r6 = new process */
310 ldr r7, [r6, #(P_FORW)]
311 str r7, [r5, #(P_FORW)]
312
313 /* rem: r7 = new queue head */
314
315 /*
316 * Test to see if the queue is now empty. If the head of the queue
317 * points to the queue itself then there are no more processes in
318 * the queue. We can therefore clear the queue not empty flag held
319 * in r3.
320 */
321
322 teq r5, r7
323 biceq r3, r3, r0
324
325 /* Fix the back pointer for the process now at the head of the queue. */
326 ldr r0, [r6, #(P_BACK)]
327 str r0, [r7, #(P_BACK)]
328
329 /* Update the RAM copy of the queue not empty flags word. */
330 ldr r7, .Lwhichqs
331 str r3, [r7]
332
333 /* rem: r8 = old proc */
334 /* rem: r3 = whichqs - NOT NEEDED ANY MORE */
335 /* rem: r4 = queue number - NOT NEEDED ANY MORE */
336 /* rem: r6 = new process */
337 /* rem: interrupts are disabled */
338
339 /* Clear the want_resched flag */
340 ldr r1, .Lwant_resched
341 mov r0, #0x00000000
342 str r0, [r1]
343
344 /*
345 * Clear the back pointer of the process we have removed from
346 * the head of the queue. The new process is isolated now.
347 */
348 str r0, [r6, #(P_BACK)]
349
350 #if defined(MULTIPROCESSOR) || defined(LOCKDEBUG)
351 /*
352 * unlock the sched_lock, but leave interrupts off, for now.
353 */
354 bl _C_LABEL(sched_unlock_idle)
355 #endif
356
357 #ifdef MULTIPROCESSOR
358 str r9, [r6, #(P_CPU)]
359 #else
360 /* p->p_cpu initialized in fork1() for single-processor */
361 #endif
362
363 /* Process is now on a processor. */
364 mov r0, #SONPROC /* p->p_stat = SONPROC */
365 strb r0, [r6, #(P_STAT)]
366
367 /* We have a new curproc now so make a note it */
368 #ifdef MULTIPROCESSOR
369 str r6, [r9, #CI_CURPROC]
370 #else
371 ldr r7, .Lcurproc
372 str r6, [r7]
373 #endif
374
375 /* Hook in a new pcb */
376 #ifdef MULTIPROCESSOR
377 ldr r0, [r6, #(P_ADDR)]
378 str r0, [r9, #CI_CURPCB]
379 #else
380 ldr r7, .Lcurpcb
381 ldr r0, [r6, #(P_ADDR)]
382 str r0, [r7]
383 #endif
384
385 /* At this point we can allow IRQ's again. */
386 IRQenable
387
388 /* rem: r8 = old proc */
389 /* rem: r6 = new process */
390 /* rem: interrupts are enabled */
391
392 /*
393 * If the new process is the same as the process that called
394 * cpu_switch() then we do not need to save and restore any
395 * contexts. This means we can make a quick exit.
396 * The test is simple if curproc on entry (now in r8) is the
397 * same as the proc removed from the queue we can jump to the exit.
398 */
399 teq r8, r6
400 beq .Lswitch_return
401
402 /*
403 * If the curproc on entry to cpu_switch was zero then the
404 * process that called it was exiting. This means that we do
405 * not need to save the current context. Instead we can jump
406 * straight to restoring the context for the new process.
407 *
408 * We also take this path if we're switching from the idle PCB.
409 */
410 teq r8, #0x00000000
411 beq .Lswitch_exited
412
413 /* rem: r8 = old proc */
414 /* rem: r6 = new process */
415 /* rem: interrupts are enabled */
416
417 /* Stage two : Save old context */
418
419 /* Get the user structure for the old process. */
420 ldr r7, [r8, #(P_ADDR)]
421
422 /* Save the remaining registers in the old process's pcb */
423 add r0, r7, #(PCB_R11)
424 stmia r0, {r11-r13}
425
426 /* rem: r7 = old pcb */
427
428 /*
429 * This can be optimised... We know we want to go from SVC32
430 * mode to UND32 mode
431 */
432 mrs r3, cpsr
433 bic r2, r3, #(PSR_MODE)
434 orr r2, r2, #(PSR_UND32_MODE | I32_bit)
435 msr cpsr_c, r2
436
437 str sp, [r7, #(PCB_UND_SP)]
438
439 msr cpsr_c, r3 /* Restore the old mode */
440
441 /* rem: r6 = new process */
442 /* rem: r7 = old pcb */
443 /* rem: r8 = old proc */
444 /* rem: interrupts are enabled */
445
446 /* What else needs to be saved Only FPA stuff when that is supported */
447
448 /* r7 now free! */
449
450 /* Third phase : restore saved context */
451
452 /* rem: r6 = new process */
453 /* rem: r8 = old proc */
454 /* rem: interrupts are enabled */
455
456 /*
457 * Don't allow user space access between the purge and the switch.
458 */
459 ldr r3, .Lblock_userspace_access
460 mov r1, #0x00000001
461 mov r2, #0x00000000
462 str r1, [r3]
463
464 stmfd sp!, {r0-r3}
465 ldr r1, .Lcpufuncs
466 mov lr, pc
467 ldr pc, [r1, #CF_IDCACHE_WBINV_ALL]
468 ldmfd sp!, {r0-r3}
469
470 .Lcs_cache_purge_skipped:
471 /* At this point we need to kill IRQ's again. */
472 IRQdisable
473
474 /*
475 * Interrupts are disabled so we can allow user space accesses again
476 * as none will occur until interrupts are re-enabled after the
477 * switch.
478 */
479 str r2, [r3]
480
481 /* Get the user structure for the new process in r1 */
482 ldr r7, [r6, #(P_ADDR)]
483
484 /* Get the pagedir physical address for the process. */
485 ldr r0, [r7, #(PCB_PAGEDIR)]
486
487 /* Switch the memory to the new process */
488 ldr r3, .Lcpufuncs
489 mov lr, pc
490 ldr pc, [r3, #CF_CONTEXT_SWITCH]
491
492 /*
493 * This can be optimised... We know we want to go from SVC32
494 * mode to UND32 mode
495 */
496 mrs r3, cpsr
497 bic r2, r3, #(PSR_MODE)
498 orr r2, r2, #(PSR_UND32_MODE)
499 msr cpsr_c, r2
500
501 ldr sp, [r7, #(PCB_UND_SP)]
502
503 msr cpsr_c, r3 /* Restore the old mode */
504
505 /* Restore the saved registers from the PCB */
506 add r0, r7, #PCB_R11
507 ldmia r0, {r11-r13}
508
509 #ifdef ARMFPE
510 add r0, r1, #(USER_SIZE) & 0x00ff
511 add r0, r0, #(USER_SIZE) & 0xff00
512 bl _C_LABEL(arm_fpe_core_changecontext)
513 #endif
514
515 /* We can enable interrupts again */
516 IRQenable
517
518 /* rem: r6 = new proc */
519 /* rem: r7 = new PCB */
520
521 /*
522 * Check for restartable atomic sequences (RAS).
523 */
524
525 ldr r2, [r6, #(P_NRAS)]
526 ldr r4, [r7, #(PCB_TF)] /* r4 = trapframe (used below) */
527 teq r2, #0 /* p->p_nras == 0? */
528 bne .Lswitch_do_ras /* no, check for one */
529
530 .Lswitch_return:
531
532 /* Get the spl level from the stack and update the current spl level */
533 ldr r0, [sp], #0x0004
534 bl _C_LABEL(splx)
535
536 /* cpu_switch returns the proc it switched to. */
537 mov r0, r6
538
539 /*
540 * Pull the registers that got pushed when either savectx() or
541 * cpu_switch() was called and return.
542 */
543 ldmdb fp, {r4-r10, fp, sp, pc}
544
545 .Lswitch_do_ras:
546 ldr r1, [r4, #(TF_PC)] /* second ras_lookup() arg */
547 mov r0, r6 /* first ras_lookup() arg */
548 bl _C_LABEL(ras_lookup)
549 cmn r0, #1 /* -1 means "not in a RAS" */
550 strne r0, [r4, #(TF_PC)]
551 b .Lswitch_return
552
553 .Lswitch_exited:
554 /*
555 * We skip the cache purge because switch_exit() already did
556 * it. Load up registers the way Lcs_cache_purge_skipped
557 * expects. Userspace access already blocked in switch_exit().
558 */
559 ldr r3, .Lblock_userspace_access
560 mov r2, #0x00000000
561 b .Lcs_cache_purge_skipped
562
563 /*
564 * void switch_exit(struct proc *p, struct proc *p0);
565 * Switch to proc0's saved context and deallocate the address space and kernel
566 * stack for p. Then jump into cpu_switch(), as if we were in proc0 all along.
567 */
568
569 /* LINTSTUB: Func: void switch_exit(struct proc *p, struct proc *p0) */
570 ENTRY(switch_exit)
571 /* Could create a stack frame, but we'll never return anyway. */
572
573 mov r4, r0 /* r4 = p */
574 mov r5, r1 /* r5 = p0 */
575
576 #ifdef MULTIPROCESSOR
577 /* XXX Probably not appropriate for non-Hydra SMPs */
578 bl _C_LABEL(cpu_number)
579 ldr r9, .Lcpu_info
580 ldr r9, [r9, r0, lsl #2]
581 #endif
582
583 /* In case we fault */
584 #ifdef MULTIPROCESSOR
585 mov r2, #0
586 str r2, [r9, #CI_CURPROC]
587 /* str r2, [r9, #CI_CURPCB] */
588 #else
589 ldr r0, .Lcurproc
590 mov r2, #0x00000000
591 str r2, [r0]
592
593 /* ldr r0, .Lcurpcb
594 str r2, [r0]*/
595 #endif
596
597 /*
598 * Don't allow user space access between the purge and the switch.
599 */
600 ldr r0, .Lblock_userspace_access
601 mov r2, #0x00000001
602 str r2, [r0]
603
604 /* Switch to proc0 context */
605
606 ldr r0, .Lcpufuncs
607 mov lr, pc
608 ldr pc, [r0, #CF_IDCACHE_WBINV_ALL]
609
610 IRQdisable
611
612 ldr r7, [r5, #(P_ADDR)]
613 ldr r0, [r7, #(PCB_PAGEDIR)]
614
615 /* Switch the memory to the new process */
616 ldr r1, .Lcpufuncs
617 mov lr, pc
618 ldr pc, [r1, #CF_CONTEXT_SWITCH]
619
620 /* Restore all the save registers */
621 add r0, r7, #PCB_R11
622 ldmia r0, {r11-r13}
623
624 /* This is not really needed ! */
625 /* Yes it is for the su and fu routines */
626 #ifdef MULTIPROCESSOR
627 str r7, [r9, #CI_CURPCB]
628 #else
629 ldr r0, .Lcurpcb
630 str r7, [r0]
631 #endif
632
633 IRQenable
634
635 /*
636 * Schedule the vmspace and stack to be freed.
637 */
638 mov r0, r4 /* exit2(p) */
639 bl _C_LABEL(exit2)
640
641 /* Paranoia */
642 #ifdef MULTIPROCESSOR
643 mov r0, #0
644 str r0, [r9, #CI_CURPCB]
645 #else
646 ldr r1, .Lcurproc
647 mov r0, #0x00000000
648 str r0, [r1]
649 #endif
650
651 ldr r7, .Lwhichqs /* r7 = &whichqs */
652 mov r8, #0x00000000 /* r8 = old proc = NULL */
653 b .Lswitch_search
654
655 /* LINTSTUB: Func: void savectx(struct pcb *pcb) */
656 ENTRY(savectx)
657 /*
658 * r0 = pcb
659 */
660
661 /* Push registers.*/
662 mov ip, sp
663 stmfd sp!, {r4-r10, fp, ip, lr, pc}
664 sub fp, ip, #4
665
666 /* Store all the registers in the process's pcb */
667 add r2, r0, #(PCB_R11)
668 stmia r2, {r11-r13}
669
670 /* Pull the regs of the stack */
671 ldmdb fp, {r4-r10, fp, sp, pc}
672
673 ENTRY(proc_trampoline)
674 #ifdef MULTIPROCESSOR
675 bl _C_LABEL(proc_trampoline_mp)
676 #endif
677 mov r0, r5
678 mov r1, sp
679 mov lr, pc
680 mov pc, r4
681
682 /* Kill irq's */
683 mrs r0, cpsr
684 orr r0, r0, #(I32_bit)
685 msr cpsr_c, r0
686
687 PULLFRAME
688
689 movs pc, lr /* Exit */
690
691 .type .Lcpu_switch_ffs_table, _ASM_TYPE_OBJECT;
692 .Lcpu_switch_ffs_table:
693 /* same as ffs table but all nums are -1 from that */
694 /* 0 1 2 3 4 5 6 7 */
695 .byte 0, 0, 1, 12, 2, 6, 0, 13 /* 0- 7 */
696 .byte 3, 0, 7, 0, 0, 0, 0, 14 /* 8-15 */
697 .byte 10, 4, 0, 0, 8, 0, 0, 25 /* 16-23 */
698 .byte 0, 0, 0, 0, 0, 21, 27, 15 /* 24-31 */
699 .byte 31, 11, 5, 0, 0, 0, 0, 0 /* 32-39 */
700 .byte 9, 0, 0, 24, 0, 0, 20, 26 /* 40-47 */
701 .byte 30, 0, 0, 0, 0, 23, 0, 19 /* 48-55 */
702 .byte 29, 0, 22, 18, 28, 17, 16, 0 /* 56-63 */
703
704 /* End of cpuswitch.S */
705