aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/vfp
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2011-07-09 11:09:43 -0400
committerRussell King <rmk+kernel@arm.linux.org.uk>2011-07-09 12:22:12 -0400
commitf8f2a8522a88aacd62a310ce49e8dac530d1b403 (patch)
tree0136014b3fb925d729ef3735a3e3fb51dd5f1890 /arch/arm/vfp
parent08409c33d6fdb43fa19d7dbdafd4f280b7835592 (diff)
ARM: vfp: fix a hole in VFP thread migration
Fix a hole in the VFP thread migration. Lets define two threads. Thread 1, we'll call 'interesting_thread' which is a thread which is running on CPU0, using VFP (so vfp_current_hw_state[0] = &interesting_thread->vfpstate) and gets migrated off to CPU1, where it continues execution of VFP instructions. Thread 2, we'll call 'new_cpu0_thread' which is the thread which takes over on CPU0. This has also been using VFP, and last used VFP on CPU0, but doesn't use it again. The following code will be executed twice: cpu = thread->cpu; /* * On SMP, if VFP is enabled, save the old state in * case the thread migrates to a different CPU. The * restoring is done lazily. */ if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu]) { vfp_save_state(vfp_current_hw_state[cpu], fpexc); vfp_current_hw_state[cpu]->hard.cpu = cpu; } /* * Thread migration, just force the reloading of the * state on the new CPU in case the VFP registers * contain stale data. */ if (thread->vfpstate.hard.cpu != cpu) vfp_current_hw_state[cpu] = NULL; The first execution will be on CPU0 to switch away from 'interesting_thread'. interesting_thread->cpu will be 0. So, vfp_current_hw_state[0] points at interesting_thread->vfpstate. The hardware state will be saved, along with the CPU number (0) that it was executing on. 'thread' will be 'new_cpu0_thread' with new_cpu0_thread->cpu = 0. Also, because it was executing on CPU0, new_cpu0_thread->vfpstate.hard.cpu = 0, and so the thread migration check is not triggered. This means that vfp_current_hw_state[0] remains pointing at interesting_thread. The second execution will be on CPU1 to switch _to_ 'interesting_thread'. So, 'thread' will be 'interesting_thread' and interesting_thread->cpu now will be 1. The previous thread executing on CPU1 is not relevant to this so we shall ignore that. We get to the thread migration check. Here, we discover that interesting_thread->vfpstate.hard.cpu = 0, yet interesting_thread->cpu is now 1, indicating thread migration. We set vfp_current_hw_state[1] to NULL. So, at this point vfp_current_hw_state[] contains the following: [0] = &interesting_thread->vfpstate [1] = NULL Our interesting thread now executes a VFP instruction, takes a fault which loads the state into the VFP hardware. Now, through the assembly we now have: [0] = &interesting_thread->vfpstate [1] = &interesting_thread->vfpstate CPU1 stops due to ptrace (and so saves its VFP state) using the thread switch code above), and CPU0 calls vfp_sync_hwstate(). if (vfp_current_hw_state[cpu] == &thread->vfpstate) { vfp_save_state(&thread->vfpstate, fpexc | FPEXC_EN); BANG, we corrupt interesting_thread's VFP state by overwriting the more up-to-date state saved by CPU1 with the old VFP state from CPU0. Fix this by ensuring that we have sane semantics for the various state describing variables: 1. vfp_current_hw_state[] points to the current owner of the context information stored in each CPUs hardware, or NULL if that state information is invalid. 2. thread->vfpstate.hard.cpu always contains the most recent CPU number which the state was loaded into or NR_CPUS if no CPU owns the state. So, for a particular CPU to be a valid owner of the VFP state for a particular thread t, two things must be true: vfp_current_hw_state[cpu] == &t->vfpstate && t->vfpstate.hard.cpu == cpu. and that is valid from the moment a CPU loads the saved VFP context into the hardware. This gives clear and consistent semantics to interpreting these variables. This patch also fixes thread copying, ensuring that t->vfpstate.hard.cpu is invalidated, otherwise CPU0 may believe it was the last owner. The hole can happen thus: - thread1 runs on CPU2 using VFP, migrates to CPU3, exits and thread_info freed. - New thread allocated from a previously running thread on CPU2, reusing memory for thread1 and copying vfp.hard.cpu. At this point, the following are true: new_thread1->vfpstate.hard.cpu == 2 &new_thread1->vfpstate == vfp_current_hw_state[2] Lastly, this also addresses thread flushing in a similar way to thread copying. Hole is: - thread runs on CPU0, using VFP, migrates to CPU1 but does not use VFP. - thread calls execve(), so thread flush happens, leaving vfp_current_hw_state[0] intact. This vfpstate is memset to 0 causing thread->vfpstate.hard.cpu = 0. - thread migrates back to CPU0 before using VFP. At this point, the following are true: thread->vfpstate.hard.cpu == 0 &thread->vfpstate == vfp_current_hw_state[0] Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/vfp')
-rw-r--r--arch/arm/vfp/vfphw.S43
-rw-r--r--arch/arm/vfp/vfpmodule.c98
2 files changed, 86 insertions, 55 deletions
diff --git a/arch/arm/vfp/vfphw.S b/arch/arm/vfp/vfphw.S
index 404538ae591d..2d30c7f6edd3 100644
--- a/arch/arm/vfp/vfphw.S
+++ b/arch/arm/vfp/vfphw.S
@@ -82,19 +82,22 @@ ENTRY(vfp_support_entry)
82 ldr r4, [r3, r11, lsl #2] @ vfp_current_hw_state 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 @ this thread owns the hw context? 84 cmp r4, r10 @ this thread owns the hw context?
85#ifndef CONFIG_SMP
86 @ For UP, checking that this thread owns the hw context is
87 @ sufficient to determine that the hardware state is valid.
85 beq vfp_hw_state_valid 88 beq vfp_hw_state_valid
86 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.
93
87 VFPFMXR FPEXC, r5 @ enable VFP, disable any pending 94 VFPFMXR FPEXC, r5 @ enable VFP, disable any pending
88 @ exceptions, so we can get at the 95 @ exceptions, so we can get at the
89 @ rest of it 96 @ rest of it
90 97
91#ifndef CONFIG_SMP
92 @ Save out the current registers to the old thread state
93 @ No need for SMP since this is not done lazily
94
95 DBGSTR1 "save old state %p", r4 98 DBGSTR1 "save old state %p", r4
96 cmp r4, #0 99 cmp r4, #0 @ if the vfp_current_hw_state is NULL
97 beq no_old_VFP_process 100 beq vfp_reload_hw @ then the hw state needs reloading
98 VFPFSTMIA r4, r5 @ save the working registers 101 VFPFSTMIA r4, r5 @ save the working registers
99 VFPFMRX r5, FPSCR @ current status 102 VFPFMRX r5, FPSCR @ current status
100#ifndef CONFIG_CPU_FEROCEON 103#ifndef CONFIG_CPU_FEROCEON
@@ -107,11 +110,33 @@ ENTRY(vfp_support_entry)
1071: 1101:
108#endif 111#endif
109 stmia r4, {r1, r5, r6, r8} @ save FPEXC, FPSCR, FPINST, FPINST2 112 stmia r4, {r1, r5, r6, r8} @ save FPEXC, FPSCR, FPINST, FPINST2
110 @ and point r4 at the word at the 113vfp_reload_hw:
111 @ 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
130vfp_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
112#endif 138#endif
113 139
114no_old_VFP_process:
115 DBGSTR1 "load state %p", r10 140 DBGSTR1 "load state %p", r10
116 str r10, [r3, r11, lsl #2] @ update the vfp_current_hw_state pointer 141 str r10, [r3, r11, lsl #2] @ update the vfp_current_hw_state pointer
117 @ Load the saved state back into the VFP 142 @ Load the saved state back into the VFP
diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c
index 3640351171b8..08ff93fa533c 100644
--- a/arch/arm/vfp/vfpmodule.c
+++ b/arch/arm/vfp/vfpmodule.c
@@ -35,18 +35,51 @@ void vfp_null_entry(void);
35void (*vfp_vector)(void) = vfp_null_entry; 35void (*vfp_vector)(void) = vfp_null_entry;
36 36
37/* 37/*
38 * Dual-use variable.
39 * Used in startup: set to non-zero if VFP checks fail
40 * After startup, holds VFP architecture
41 */
42unsigned int VFP_arch;
43
44/*
38 * The pointer to the vfpstate structure of the thread which currently 45 * The pointer to the vfpstate structure of the thread which currently
39 * owns the context held in the VFP hardware, or NULL if the hardware 46 * owns the context held in the VFP hardware, or NULL if the hardware
40 * context is invalid. 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.
41 */ 52 */
42union vfp_state *vfp_current_hw_state[NR_CPUS]; 53union vfp_state *vfp_current_hw_state[NR_CPUS];
43 54
44/* 55/*
45 * Dual-use variable. 56 * Is 'thread's most up to date state stored in this CPUs hardware?
46 * Used in startup: set to non-zero if VFP checks fail 57 * Must be called from non-preemptible context.
47 * After startup, holds VFP architecture
48 */ 58 */
49unsigned int VFP_arch; 59static 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 */
73static 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}
50 83
51/* 84/*
52 * Per-thread VFP initialization. 85 * Per-thread VFP initialization.
@@ -60,6 +93,9 @@ static void vfp_thread_flush(struct thread_info *thread)
60 93
61 vfp->hard.fpexc = FPEXC_EN; 94 vfp->hard.fpexc = FPEXC_EN;
62 vfp->hard.fpscr = FPSCR_ROUND_NEAREST; 95 vfp->hard.fpscr = FPSCR_ROUND_NEAREST;
96#ifdef CONFIG_SMP
97 vfp->hard.cpu = NR_CPUS;
98#endif
63 99
64 /* 100 /*
65 * Disable VFP to ensure we initialize it first. We must ensure 101 * Disable VFP to ensure we initialize it first. We must ensure
@@ -90,6 +126,9 @@ static void vfp_thread_copy(struct thread_info *thread)
90 126
91 vfp_sync_hwstate(parent); 127 vfp_sync_hwstate(parent);
92 thread->vfpstate = parent->vfpstate; 128 thread->vfpstate = parent->vfpstate;
129#ifdef CONFIG_SMP
130 thread->vfpstate.hard.cpu = NR_CPUS;
131#endif
93} 132}
94 133
95/* 134/*
@@ -135,17 +174,8 @@ static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v)
135 * case the thread migrates to a different CPU. The 174 * case the thread migrates to a different CPU. The
136 * restoring is done lazily. 175 * restoring is done lazily.
137 */ 176 */
138 if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu]) { 177 if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu])
139 vfp_save_state(vfp_current_hw_state[cpu], fpexc); 178 vfp_save_state(vfp_current_hw_state[cpu], fpexc);
140 vfp_current_hw_state[cpu]->hard.cpu = cpu;
141 }
142 /*
143 * Thread migration, just force the reloading of the
144 * state on the new CPU in case the VFP registers
145 * contain stale data.
146 */
147 if (thread->vfpstate.hard.cpu != cpu)
148 vfp_current_hw_state[cpu] = NULL;
149#endif 179#endif
150 180
151 /* 181 /*
@@ -449,15 +479,15 @@ static void vfp_pm_init(void)
449static inline void vfp_pm_init(void) { } 479static inline void vfp_pm_init(void) { }
450#endif /* CONFIG_PM */ 480#endif /* CONFIG_PM */
451 481
482/*
483 * Ensure that the VFP state stored in 'thread->vfpstate' is up to date
484 * with the hardware state.
485 */
452void vfp_sync_hwstate(struct thread_info *thread) 486void vfp_sync_hwstate(struct thread_info *thread)
453{ 487{
454 unsigned int cpu = get_cpu(); 488 unsigned int cpu = get_cpu();
455 489
456 /* 490 if (vfp_state_in_hw(cpu, thread)) {
457 * If the thread we're interested in is the current owner of the
458 * hardware VFP state, then we need to save its state.
459 */
460 if (vfp_current_hw_state[cpu] == &thread->vfpstate) {
461 u32 fpexc = fmrx(FPEXC); 491 u32 fpexc = fmrx(FPEXC);
462 492
463 /* 493 /*
@@ -471,36 +501,13 @@ void vfp_sync_hwstate(struct thread_info *thread)
471 put_cpu(); 501 put_cpu();
472} 502}
473 503
504/* Ensure that the thread reloads the hardware VFP state on the next use. */
474void vfp_flush_hwstate(struct thread_info *thread) 505void vfp_flush_hwstate(struct thread_info *thread)
475{ 506{
476 unsigned int cpu = get_cpu(); 507 unsigned int cpu = get_cpu();
477 508
478 /* 509 vfp_force_reload(cpu, thread);
479 * If the thread we're interested in is the current owner of the
480 * hardware VFP state, then we need to save its state.
481 */
482 if (vfp_current_hw_state[cpu] == &thread->vfpstate) {
483 u32 fpexc = fmrx(FPEXC);
484 510
485 fmxr(FPEXC, fpexc & ~FPEXC_EN);
486
487 /*
488 * Set the context to NULL to force a reload the next time
489 * the thread uses the VFP.
490 */
491 vfp_current_hw_state[cpu] = NULL;
492 }
493
494#ifdef CONFIG_SMP
495 /*
496 * For SMP we still have to take care of the case where the thread
497 * migrates to another CPU and then back to the original CPU on which
498 * the last VFP user is still the same thread. Mark the thread VFP
499 * state as belonging to a non-existent CPU so that the saved one will
500 * be reloaded in the above case.
501 */
502 thread->vfpstate.hard.cpu = NR_CPUS;
503#endif
504 put_cpu(); 511 put_cpu();
505} 512}
506 513
@@ -519,8 +526,7 @@ static int vfp_hotplug(struct notifier_block *b, unsigned long action,
519 void *hcpu) 526 void *hcpu)
520{ 527{
521 if (action == CPU_DYING || action == CPU_DYING_FROZEN) { 528 if (action == CPU_DYING || action == CPU_DYING_FROZEN) {
522 unsigned int cpu = (long)hcpu; 529 vfp_force_reload((long)hcpu, current_thread_info());
523 vfp_current_hw_state[cpu] = NULL;
524 } else if (action == CPU_STARTING || action == CPU_STARTING_FROZEN) 530 } else if (action == CPU_STARTING || action == CPU_STARTING_FROZEN)
525 vfp_enable(NULL); 531 vfp_enable(NULL);
526 return NOTIFY_OK; 532 return NOTIFY_OK;