diff options
Diffstat (limited to 'arch/arm/vfp')
-rw-r--r-- | arch/arm/vfp/vfphw.S | 63 | ||||
-rw-r--r-- | arch/arm/vfp/vfpmodule.c | 127 |
2 files changed, 114 insertions, 76 deletions
diff --git a/arch/arm/vfp/vfphw.S b/arch/arm/vfp/vfphw.S index 9897dcfc16d6..2d30c7f6edd3 100644 --- a/arch/arm/vfp/vfphw.S +++ b/arch/arm/vfp/vfphw.S | |||
@@ -77,27 +77,27 @@ ENTRY(vfp_support_entry) | |||
77 | bne look_for_VFP_exceptions @ VFP is already enabled | 77 | bne look_for_VFP_exceptions @ VFP is already enabled |
78 | 78 | ||
79 | DBGSTR1 "enable %x", r10 | 79 | DBGSTR1 "enable %x", r10 |
80 | ldr r3, last_VFP_context_address | 80 | ldr r3, vfp_current_hw_state_address |
81 | orr r1, r1, #FPEXC_EN @ user FPEXC has the enable bit set | 81 | orr r1, r1, #FPEXC_EN @ user FPEXC has the enable bit set |
82 | ldr r4, [r3, r11, lsl #2] @ last_VFP_context pointer | 82 | ldr r4, [r3, r11, lsl #2] @ vfp_current_hw_state pointer |
83 | bic r5, r1, #FPEXC_EX @ make sure exceptions are disabled | 83 | bic r5, r1, #FPEXC_EX @ make sure exceptions are disabled |
84 | cmp r4, r10 | 84 | cmp r4, r10 @ this thread owns the hw context? |
85 | beq check_for_exception @ we are returning to the same | 85 | #ifndef CONFIG_SMP |
86 | @ process, so the registers are | 86 | @ For UP, checking that this thread owns the hw context is |
87 | @ still there. In this case, we do | 87 | @ sufficient to determine that the hardware state is valid. |
88 | @ not want to drop a pending exception. | 88 | beq vfp_hw_state_valid |
89 | |||
90 | @ On UP, we lazily save the VFP context. As a different | ||
91 | @ thread wants ownership of the VFP hardware, save the old | ||
92 | @ state if there was a previous (valid) owner. | ||
89 | 93 | ||
90 | VFPFMXR FPEXC, r5 @ enable VFP, disable any pending | 94 | VFPFMXR FPEXC, r5 @ enable VFP, disable any pending |
91 | @ exceptions, so we can get at the | 95 | @ exceptions, so we can get at the |
92 | @ rest of it | 96 | @ rest of it |
93 | 97 | ||
94 | #ifndef CONFIG_SMP | ||
95 | @ Save out the current registers to the old thread state | ||
96 | @ No need for SMP since this is not done lazily | ||
97 | |||
98 | DBGSTR1 "save old state %p", r4 | 98 | DBGSTR1 "save old state %p", r4 |
99 | cmp r4, #0 | 99 | cmp r4, #0 @ if the vfp_current_hw_state is NULL |
100 | beq no_old_VFP_process | 100 | beq vfp_reload_hw @ then the hw state needs reloading |
101 | VFPFSTMIA r4, r5 @ save the working registers | 101 | VFPFSTMIA r4, r5 @ save the working registers |
102 | VFPFMRX r5, FPSCR @ current status | 102 | VFPFMRX r5, FPSCR @ current status |
103 | #ifndef CONFIG_CPU_FEROCEON | 103 | #ifndef CONFIG_CPU_FEROCEON |
@@ -110,13 +110,35 @@ ENTRY(vfp_support_entry) | |||
110 | 1: | 110 | 1: |
111 | #endif | 111 | #endif |
112 | stmia r4, {r1, r5, r6, r8} @ save FPEXC, FPSCR, FPINST, FPINST2 | 112 | stmia r4, {r1, r5, r6, r8} @ save FPEXC, FPSCR, FPINST, FPINST2 |
113 | @ and point r4 at the word at the | 113 | vfp_reload_hw: |
114 | @ start of the register dump | 114 | |
115 | #else | ||
116 | @ For SMP, if this thread does not own the hw context, then we | ||
117 | @ need to reload it. No need to save the old state as on SMP, | ||
118 | @ we always save the state when we switch away from a thread. | ||
119 | bne vfp_reload_hw | ||
120 | |||
121 | @ This thread has ownership of the current hardware context. | ||
122 | @ However, it may have been migrated to another CPU, in which | ||
123 | @ case the saved state is newer than the hardware context. | ||
124 | @ Check this by looking at the CPU number which the state was | ||
125 | @ last loaded onto. | ||
126 | ldr ip, [r10, #VFP_CPU] | ||
127 | teq ip, r11 | ||
128 | beq vfp_hw_state_valid | ||
129 | |||
130 | vfp_reload_hw: | ||
131 | @ We're loading this threads state into the VFP hardware. Update | ||
132 | @ the CPU number which contains the most up to date VFP context. | ||
133 | str r11, [r10, #VFP_CPU] | ||
134 | |||
135 | VFPFMXR FPEXC, r5 @ enable VFP, disable any pending | ||
136 | @ exceptions, so we can get at the | ||
137 | @ rest of it | ||
115 | #endif | 138 | #endif |
116 | 139 | ||
117 | no_old_VFP_process: | ||
118 | DBGSTR1 "load state %p", r10 | 140 | DBGSTR1 "load state %p", r10 |
119 | str r10, [r3, r11, lsl #2] @ update the last_VFP_context pointer | 141 | str r10, [r3, r11, lsl #2] @ update the vfp_current_hw_state pointer |
120 | @ Load the saved state back into the VFP | 142 | @ Load the saved state back into the VFP |
121 | VFPFLDMIA r10, r5 @ reload the working registers while | 143 | VFPFLDMIA r10, r5 @ reload the working registers while |
122 | @ FPEXC is in a safe state | 144 | @ FPEXC is in a safe state |
@@ -132,7 +154,8 @@ no_old_VFP_process: | |||
132 | #endif | 154 | #endif |
133 | VFPFMXR FPSCR, r5 @ restore status | 155 | VFPFMXR FPSCR, r5 @ restore status |
134 | 156 | ||
135 | check_for_exception: | 157 | @ The context stored in the VFP hardware is up to date with this thread |
158 | vfp_hw_state_valid: | ||
136 | tst r1, #FPEXC_EX | 159 | tst r1, #FPEXC_EX |
137 | bne process_exception @ might as well handle the pending | 160 | bne process_exception @ might as well handle the pending |
138 | @ exception before retrying branch | 161 | @ exception before retrying branch |
@@ -207,8 +230,8 @@ ENTRY(vfp_save_state) | |||
207 | ENDPROC(vfp_save_state) | 230 | ENDPROC(vfp_save_state) |
208 | 231 | ||
209 | .align | 232 | .align |
210 | last_VFP_context_address: | 233 | vfp_current_hw_state_address: |
211 | .word last_VFP_context | 234 | .word vfp_current_hw_state |
212 | 235 | ||
213 | .macro tbl_branch, base, tmp, shift | 236 | .macro tbl_branch, base, tmp, shift |
214 | #ifdef CONFIG_THUMB2_KERNEL | 237 | #ifdef CONFIG_THUMB2_KERNEL |
diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c index f25e7ec89416..0a96f71f0abd 100644 --- a/arch/arm/vfp/vfpmodule.c +++ b/arch/arm/vfp/vfpmodule.c | |||
@@ -33,7 +33,6 @@ void vfp_support_entry(void); | |||
33 | void vfp_null_entry(void); | 33 | void vfp_null_entry(void); |
34 | 34 | ||
35 | void (*vfp_vector)(void) = vfp_null_entry; | 35 | void (*vfp_vector)(void) = vfp_null_entry; |
36 | union vfp_state *last_VFP_context[NR_CPUS]; | ||
37 | 36 | ||
38 | /* | 37 | /* |
39 | * Dual-use variable. | 38 | * Dual-use variable. |
@@ -43,6 +42,46 @@ union vfp_state *last_VFP_context[NR_CPUS]; | |||
43 | unsigned int VFP_arch; | 42 | unsigned int VFP_arch; |
44 | 43 | ||
45 | /* | 44 | /* |
45 | * The pointer to the vfpstate structure of the thread which currently | ||
46 | * owns the context held in the VFP hardware, or NULL if the hardware | ||
47 | * context is invalid. | ||
48 | * | ||
49 | * For UP, this is sufficient to tell which thread owns the VFP context. | ||
50 | * However, for SMP, we also need to check the CPU number stored in the | ||
51 | * saved state too to catch migrations. | ||
52 | */ | ||
53 | union vfp_state *vfp_current_hw_state[NR_CPUS]; | ||
54 | |||
55 | /* | ||
56 | * Is 'thread's most up to date state stored in this CPUs hardware? | ||
57 | * Must be called from non-preemptible context. | ||
58 | */ | ||
59 | static bool vfp_state_in_hw(unsigned int cpu, struct thread_info *thread) | ||
60 | { | ||
61 | #ifdef CONFIG_SMP | ||
62 | if (thread->vfpstate.hard.cpu != cpu) | ||
63 | return false; | ||
64 | #endif | ||
65 | return vfp_current_hw_state[cpu] == &thread->vfpstate; | ||
66 | } | ||
67 | |||
68 | /* | ||
69 | * Force a reload of the VFP context from the thread structure. We do | ||
70 | * this by ensuring that access to the VFP hardware is disabled, and | ||
71 | * clear last_VFP_context. Must be called from non-preemptible context. | ||
72 | */ | ||
73 | static void vfp_force_reload(unsigned int cpu, struct thread_info *thread) | ||
74 | { | ||
75 | if (vfp_state_in_hw(cpu, thread)) { | ||
76 | fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); | ||
77 | vfp_current_hw_state[cpu] = NULL; | ||
78 | } | ||
79 | #ifdef CONFIG_SMP | ||
80 | thread->vfpstate.hard.cpu = NR_CPUS; | ||
81 | #endif | ||
82 | } | ||
83 | |||
84 | /* | ||
46 | * Per-thread VFP initialization. | 85 | * Per-thread VFP initialization. |
47 | */ | 86 | */ |
48 | static void vfp_thread_flush(struct thread_info *thread) | 87 | static void vfp_thread_flush(struct thread_info *thread) |
@@ -50,21 +89,27 @@ static void vfp_thread_flush(struct thread_info *thread) | |||
50 | union vfp_state *vfp = &thread->vfpstate; | 89 | union vfp_state *vfp = &thread->vfpstate; |
51 | unsigned int cpu; | 90 | unsigned int cpu; |
52 | 91 | ||
53 | memset(vfp, 0, sizeof(union vfp_state)); | ||
54 | |||
55 | vfp->hard.fpexc = FPEXC_EN; | ||
56 | vfp->hard.fpscr = FPSCR_ROUND_NEAREST; | ||
57 | |||
58 | /* | 92 | /* |
59 | * Disable VFP to ensure we initialize it first. We must ensure | 93 | * Disable VFP to ensure we initialize it first. We must ensure |
60 | * that the modification of last_VFP_context[] and hardware disable | 94 | * that the modification of vfp_current_hw_state[] and hardware |
61 | * are done for the same CPU and without preemption. | 95 | * disable are done for the same CPU and without preemption. |
96 | * | ||
97 | * Do this first to ensure that preemption won't overwrite our | ||
98 | * state saving should access to the VFP be enabled at this point. | ||
62 | */ | 99 | */ |
63 | cpu = get_cpu(); | 100 | cpu = get_cpu(); |
64 | if (last_VFP_context[cpu] == vfp) | 101 | if (vfp_current_hw_state[cpu] == vfp) |
65 | last_VFP_context[cpu] = NULL; | 102 | vfp_current_hw_state[cpu] = NULL; |
66 | fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); | 103 | fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); |
67 | put_cpu(); | 104 | put_cpu(); |
105 | |||
106 | memset(vfp, 0, sizeof(union vfp_state)); | ||
107 | |||
108 | vfp->hard.fpexc = FPEXC_EN; | ||
109 | vfp->hard.fpscr = FPSCR_ROUND_NEAREST; | ||
110 | #ifdef CONFIG_SMP | ||
111 | vfp->hard.cpu = NR_CPUS; | ||
112 | #endif | ||
68 | } | 113 | } |
69 | 114 | ||
70 | static void vfp_thread_exit(struct thread_info *thread) | 115 | static void vfp_thread_exit(struct thread_info *thread) |
@@ -73,8 +118,8 @@ static void vfp_thread_exit(struct thread_info *thread) | |||
73 | union vfp_state *vfp = &thread->vfpstate; | 118 | union vfp_state *vfp = &thread->vfpstate; |
74 | unsigned int cpu = get_cpu(); | 119 | unsigned int cpu = get_cpu(); |
75 | 120 | ||
76 | if (last_VFP_context[cpu] == vfp) | 121 | if (vfp_current_hw_state[cpu] == vfp) |
77 | last_VFP_context[cpu] = NULL; | 122 | vfp_current_hw_state[cpu] = NULL; |
78 | put_cpu(); | 123 | put_cpu(); |
79 | } | 124 | } |
80 | 125 | ||
@@ -84,6 +129,9 @@ static void vfp_thread_copy(struct thread_info *thread) | |||
84 | 129 | ||
85 | vfp_sync_hwstate(parent); | 130 | vfp_sync_hwstate(parent); |
86 | thread->vfpstate = parent->vfpstate; | 131 | thread->vfpstate = parent->vfpstate; |
132 | #ifdef CONFIG_SMP | ||
133 | thread->vfpstate.hard.cpu = NR_CPUS; | ||
134 | #endif | ||
87 | } | 135 | } |
88 | 136 | ||
89 | /* | 137 | /* |
@@ -129,17 +177,8 @@ static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v) | |||
129 | * case the thread migrates to a different CPU. The | 177 | * case the thread migrates to a different CPU. The |
130 | * restoring is done lazily. | 178 | * restoring is done lazily. |
131 | */ | 179 | */ |
132 | if ((fpexc & FPEXC_EN) && last_VFP_context[cpu]) { | 180 | if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu]) |
133 | vfp_save_state(last_VFP_context[cpu], fpexc); | 181 | vfp_save_state(vfp_current_hw_state[cpu], fpexc); |
134 | last_VFP_context[cpu]->hard.cpu = cpu; | ||
135 | } | ||
136 | /* | ||
137 | * Thread migration, just force the reloading of the | ||
138 | * state on the new CPU in case the VFP registers | ||
139 | * contain stale data. | ||
140 | */ | ||
141 | if (thread->vfpstate.hard.cpu != cpu) | ||
142 | last_VFP_context[cpu] = NULL; | ||
143 | #endif | 182 | #endif |
144 | 183 | ||
145 | /* | 184 | /* |
@@ -415,7 +454,7 @@ static int vfp_pm_suspend(void) | |||
415 | } | 454 | } |
416 | 455 | ||
417 | /* clear any information we had about last context state */ | 456 | /* clear any information we had about last context state */ |
418 | memset(last_VFP_context, 0, sizeof(last_VFP_context)); | 457 | memset(vfp_current_hw_state, 0, sizeof(vfp_current_hw_state)); |
419 | 458 | ||
420 | return 0; | 459 | return 0; |
421 | } | 460 | } |
@@ -443,15 +482,15 @@ static void vfp_pm_init(void) | |||
443 | static inline void vfp_pm_init(void) { } | 482 | static inline void vfp_pm_init(void) { } |
444 | #endif /* CONFIG_PM */ | 483 | #endif /* CONFIG_PM */ |
445 | 484 | ||
485 | /* | ||
486 | * Ensure that the VFP state stored in 'thread->vfpstate' is up to date | ||
487 | * with the hardware state. | ||
488 | */ | ||
446 | void vfp_sync_hwstate(struct thread_info *thread) | 489 | void vfp_sync_hwstate(struct thread_info *thread) |
447 | { | 490 | { |
448 | unsigned int cpu = get_cpu(); | 491 | unsigned int cpu = get_cpu(); |
449 | 492 | ||
450 | /* | 493 | if (vfp_state_in_hw(cpu, thread)) { |
451 | * If the thread we're interested in is the current owner of the | ||
452 | * hardware VFP state, then we need to save its state. | ||
453 | */ | ||
454 | if (last_VFP_context[cpu] == &thread->vfpstate) { | ||
455 | u32 fpexc = fmrx(FPEXC); | 494 | u32 fpexc = fmrx(FPEXC); |
456 | 495 | ||
457 | /* | 496 | /* |
@@ -465,36 +504,13 @@ void vfp_sync_hwstate(struct thread_info *thread) | |||
465 | put_cpu(); | 504 | put_cpu(); |
466 | } | 505 | } |
467 | 506 | ||
507 | /* Ensure that the thread reloads the hardware VFP state on the next use. */ | ||
468 | void vfp_flush_hwstate(struct thread_info *thread) | 508 | void vfp_flush_hwstate(struct thread_info *thread) |
469 | { | 509 | { |
470 | unsigned int cpu = get_cpu(); | 510 | unsigned int cpu = get_cpu(); |
471 | 511 | ||
472 | /* | 512 | vfp_force_reload(cpu, thread); |
473 | * If the thread we're interested in is the current owner of the | ||
474 | * hardware VFP state, then we need to save its state. | ||
475 | */ | ||
476 | if (last_VFP_context[cpu] == &thread->vfpstate) { | ||
477 | u32 fpexc = fmrx(FPEXC); | ||
478 | |||
479 | fmxr(FPEXC, fpexc & ~FPEXC_EN); | ||
480 | |||
481 | /* | ||
482 | * Set the context to NULL to force a reload the next time | ||
483 | * the thread uses the VFP. | ||
484 | */ | ||
485 | last_VFP_context[cpu] = NULL; | ||
486 | } | ||
487 | 513 | ||
488 | #ifdef CONFIG_SMP | ||
489 | /* | ||
490 | * For SMP we still have to take care of the case where the thread | ||
491 | * migrates to another CPU and then back to the original CPU on which | ||
492 | * the last VFP user is still the same thread. Mark the thread VFP | ||
493 | * state as belonging to a non-existent CPU so that the saved one will | ||
494 | * be reloaded in the above case. | ||
495 | */ | ||
496 | thread->vfpstate.hard.cpu = NR_CPUS; | ||
497 | #endif | ||
498 | put_cpu(); | 514 | put_cpu(); |
499 | } | 515 | } |
500 | 516 | ||
@@ -513,8 +529,7 @@ static int vfp_hotplug(struct notifier_block *b, unsigned long action, | |||
513 | void *hcpu) | 529 | void *hcpu) |
514 | { | 530 | { |
515 | if (action == CPU_DYING || action == CPU_DYING_FROZEN) { | 531 | if (action == CPU_DYING || action == CPU_DYING_FROZEN) { |
516 | unsigned int cpu = (long)hcpu; | 532 | vfp_force_reload((long)hcpu, current_thread_info()); |
517 | last_VFP_context[cpu] = NULL; | ||
518 | } else if (action == CPU_STARTING || action == CPU_STARTING_FROZEN) | 533 | } else if (action == CPU_STARTING || action == CPU_STARTING_FROZEN) |
519 | vfp_enable(NULL); | 534 | vfp_enable(NULL); |
520 | return NOTIFY_OK; | 535 | return NOTIFY_OK; |