2005-04-17 02:20:36 +04:00
/ *
* linux/ a r c h / a r m / v f p / v f p h w . S
*
* Copyright ( C ) 2 0 0 4 A R M L i m i t e d .
* Written b y D e e p B l u e S o l u t i o n s L i m i t e d .
*
* This p r o g r a m i s f r e e s o f t w a r e ; you can redistribute it and/or modify
* it u n d e r t h e t e r m s o f t h e G N U G e n e r a l P u b l i c L i c e n s e v e r s i o n 2 a s
* published b y t h e F r e e S o f t w a r e F o u n d a t i o n .
*
* This c o d e i s c a l l e d f r o m t h e k e r n e l ' s u n d e f i n e d i n s t r u c t i o n t r a p .
* r9 h o l d s t h e r e t u r n a d d r e s s f o r s u c c e s s f u l h a n d l i n g .
* lr h o l d s t h e r e t u r n a d d r e s s f o r u n r e c o g n i s e d i n s t r u c t i o n s .
* r1 0 p o i n t s a t t h e s t a r t o f t h e p r i v a t e F P w o r k s p a c e i n t h e t h r e a d s t r u c t u r e
* sp p o i n t s t o a s t r u c t p t _ r e g s ( a s d e f i n e d i n i n c l u d e / a s m / p r o c / p t r a c e . h )
* /
2014-04-02 13:57:48 +04:00
# include < l i n u x / i n i t . h >
# include < l i n u x / l i n k a g e . h >
2005-04-17 02:20:36 +04:00
# include < a s m / t h r e a d _ i n f o . h >
# include < a s m / v f p m a c r o s . h >
2012-07-31 01:40:12 +04:00
# include < l i n u x / k e r n _ l e v e l s . h >
2014-04-02 13:57:48 +04:00
# include < a s m / a s s e m b l e r . h >
# include < a s m / a s m - o f f s e t s . h >
2005-04-17 02:20:36 +04:00
.macro DBGSTR, s t r
# ifdef D E B U G
stmfd s p ! , { r0 - r3 , i p , l r }
2013-02-26 18:41:41 +04:00
ldr r0 , =1f
2005-04-17 02:20:36 +04:00
bl p r i n t k
2013-02-26 18:41:41 +04:00
ldmfd s p ! , { r0 - r3 , i p , l r }
.pushsection .rodata , " a"
1 : .ascii K E R N _ D E B U G " VFP: \str\n "
.byte 0
.previous
2005-04-17 02:20:36 +04:00
# endif
.endm
.macro DBGSTR1 , s t r , a r g
# ifdef D E B U G
stmfd s p ! , { r0 - r3 , i p , l r }
mov r1 , \ a r g
2013-02-26 18:41:41 +04:00
ldr r0 , =1f
2005-04-17 02:20:36 +04:00
bl p r i n t k
2013-02-26 18:41:41 +04:00
ldmfd s p ! , { r0 - r3 , i p , l r }
.pushsection .rodata , " a"
1 : .ascii K E R N _ D E B U G " VFP: \str\n "
.byte 0
.previous
2005-04-17 02:20:36 +04:00
# endif
.endm
.macro DBGSTR3 , s t r , a r g 1 , a r g 2 , a r g 3
# ifdef D E B U G
stmfd s p ! , { r0 - r3 , i p , l r }
mov r3 , \ a r g 3
mov r2 , \ a r g 2
mov r1 , \ a r g 1
2013-02-26 18:41:41 +04:00
ldr r0 , =1f
2005-04-17 02:20:36 +04:00
bl p r i n t k
2013-02-26 18:41:41 +04:00
ldmfd s p ! , { r0 - r3 , i p , l r }
.pushsection .rodata , " a"
1 : .ascii K E R N _ D E B U G " VFP: \str\n "
.byte 0
.previous
2005-04-17 02:20:36 +04:00
# endif
.endm
@ VFP hardware support entry point.
@
2012-07-30 22:42:10 +04:00
@ r0 = instruction opcode (32-bit ARM or two 16-bit Thumb)
@ r2 = PC value to resume execution after successful emulation
@ r9 = normal "successful" return address
2005-04-17 02:20:36 +04:00
@ r10 = vfp_state union
2007-01-24 20:47:08 +03:00
@ r11 = CPU number
2012-07-30 22:42:10 +04:00
@ lr = unrecognised instruction return address
@ IRQs enabled.
2008-08-28 14:22:32 +04:00
ENTRY( v f p _ s u p p o r t _ e n t r y )
2005-04-17 02:20:36 +04:00
DBGSTR3 " i n s t r % 0 8 x p c % 0 8 x s t a t e % p " , r0 , r2 , r10
2013-05-24 18:23:28 +04:00
ldr r3 , [ s p , #S _ P S R ] @ N e i t h e r l a z y r e s t o r e n o r F P e x c e p t i o n s
and r3 , r3 , #M O D E _ M A S K @ a r e s u p p o r t e d i n k e r n e l m o d e
teq r3 , #U S R _ M O D E
bne v f p _ k m o d e _ e x c e p t i o n @ Returns through lr
2005-04-17 02:20:36 +04:00
VFPFMRX r1 , F P E X C @ Is the VFP enabled?
DBGSTR1 " f p e x c % 0 8 x " , r1
2007-07-18 12:37:10 +04:00
tst r1 , #F P E X C _ E N
2005-04-17 02:20:36 +04:00
bne l o o k _ f o r _ V F P _ e x c e p t i o n s @ VFP is already enabled
DBGSTR1 " e n a b l e % x " , r10
2011-07-09 16:44:04 +04:00
ldr r3 , v f p _ c u r r e n t _ h w _ s t a t e _ a d d r e s s
2007-07-18 12:37:10 +04:00
orr r1 , r1 , #F P E X C _ E N @ u s e r F P E X C h a s t h e e n a b l e b i t s e t
2011-07-09 16:44:04 +04:00
ldr r4 , [ r3 , r11 , l s l #2 ] @ vfp_current_hw_state pointer
2007-07-18 12:37:10 +04:00
bic r5 , r1 , #F P E X C _ E X @ m a k e s u r e e x c e p t i o n s a r e d i s a b l e d
2011-07-09 17:24:36 +04:00
cmp r4 , r10 @ this thread owns the hw context?
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>
2011-07-09 19:09:43 +04:00
# ifndef C O N F I G _ S M P
@ For UP, checking that this thread owns the hw context is
@ sufficient to determine that the hardware state is valid.
2011-07-09 17:24:36 +04:00
beq v f p _ h w _ s t a t e _ v a l i d
2005-04-17 02:20:36 +04:00
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>
2011-07-09 19:09:43 +04:00
@ On UP, we lazily save the VFP context. As a different
@ thread wants ownership of the VFP hardware, save the old
@ state if there was a previous (valid) owner.
2005-04-17 02:20:36 +04:00
VFPFMXR F P E X C , r5 @ enable VFP, disable any pending
@ exceptions, so we can get at the
@ rest of it
DBGSTR1 " s a v e o l d s t a t e % p " , r4
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>
2011-07-09 19:09:43 +04:00
cmp r4 , #0 @ if the vfp_current_hw_state is NULL
beq v f p _ r e l o a d _ h w @ then the hw state needs reloading
2007-09-25 18:22:24 +04:00
VFPFSTMIA r4 , r5 @ save the working registers
2005-04-17 02:20:36 +04:00
VFPFMRX r5 , F P S C R @ current status
2009-05-30 17:00:18 +04:00
# ifndef C O N F I G _ C P U _ F E R O C E O N
2007-11-22 20:32:01 +03:00
tst r1 , #F P E X C _ E X @ i s t h e r e a d d i t i o n a l s t a t e t o s a v e ?
2008-11-06 16:23:08 +03:00
beq 1 f
VFPFMRX r6 , F P I N S T @ FPINST (only if FPEXC.EX is set)
tst r1 , #F P E X C _ F P 2 V @ is there an FPINST2 to read?
beq 1 f
VFPFMRX r8 , F P I N S T 2 @ FPINST2 if needed (and present)
1 :
2009-05-30 17:00:18 +04:00
# endif
2005-04-17 02:20:36 +04:00
stmia r4 , { r1 , r5 , r6 , r8 } @ save FPEXC, FPSCR, FPINST, FPINST2
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>
2011-07-09 19:09:43 +04:00
vfp_reload_hw :
# else
@ For SMP, if this thread does not own the hw context, then we
@ need to reload it. No need to save the old state as on SMP,
@ we always save the state when we switch away from a thread.
bne v f p _ r e l o a d _ h w
@ This thread has ownership of the current hardware context.
@ However, it may have been migrated to another CPU, in which
@ case the saved state is newer than the hardware context.
@ Check this by looking at the CPU number which the state was
@ last loaded onto.
ldr i p , [ r10 , #V F P _ C P U ]
teq i p , r11
beq v f p _ h w _ s t a t e _ v a l i d
vfp_reload_hw :
@ We're loading this threads state into the VFP hardware. Update
@ the CPU number which contains the most up to date VFP context.
str r11 , [ r10 , #V F P _ C P U ]
VFPFMXR F P E X C , r5 @ enable VFP, disable any pending
@ exceptions, so we can get at the
@ rest of it
2007-01-24 20:47:08 +03:00
# endif
2005-04-17 02:20:36 +04:00
DBGSTR1 " l o a d s t a t e % p " , r10
2011-07-09 16:44:04 +04:00
str r10 , [ r3 , r11 , l s l #2 ] @ update the vfp_current_hw_state pointer
2005-04-17 02:20:36 +04:00
@ Load the saved state back into the VFP
2007-09-25 18:22:24 +04:00
VFPFLDMIA r10 , r5 @ reload the working registers while
2005-04-17 02:20:36 +04:00
@ FPEXC is in a safe state
2006-03-26 00:58:00 +03:00
ldmia r10 , { r1 , r5 , r6 , r8 } @ load FPEXC, FPSCR, FPINST, FPINST2
2009-05-30 17:00:18 +04:00
# ifndef C O N F I G _ C P U _ F E R O C E O N
2007-11-22 20:32:01 +03:00
tst r1 , #F P E X C _ E X @ i s t h e r e a d d i t i o n a l s t a t e t o r e s t o r e ?
2008-11-06 16:23:08 +03:00
beq 1 f
VFPFMXR F P I N S T , r6 @ restore FPINST (only if FPEXC.EX is set)
tst r1 , #F P E X C _ F P 2 V @ is there an FPINST2 to write?
beq 1 f
VFPFMXR F P I N S T 2 , r8 @ FPINST2 if needed (and present)
1 :
2009-05-30 17:00:18 +04:00
# endif
2005-04-17 02:20:36 +04:00
VFPFMXR F P S C R , r5 @ restore status
2011-07-09 17:24:36 +04:00
@ The context stored in the VFP hardware is up to date with this thread
vfp_hw_state_valid :
2007-07-18 12:37:10 +04:00
tst r1 , #F P E X C _ E X
2005-04-17 02:20:36 +04:00
bne p r o c e s s _ e x c e p t i o n @ might as well handle the pending
@ exception before retrying branch
@ out before setting an FPEXC that
@ stops us reading stuff
2012-07-30 22:42:10 +04:00
VFPFMXR F P E X C , r1 @ Restore FPEXC last
sub r2 , r2 , #4 @ Retry current instruction - if Thumb
str r2 , [ s p , #S _ P C ] @ m o d e i t ' s t w o 16 - b i t i n s t r u c t i o n s ,
@ else it's one 32-bit instruction, so
@ always subtract 4 from the following
@ instruction address.
2014-04-02 13:57:49 +04:00
dec_ p r e e m p t _ c o u n t _ t i r10 , r4
2005-04-17 02:20:36 +04:00
mov p c , r9 @ we think we have handled things
look_for_VFP_exceptions :
2007-11-22 20:32:01 +03:00
@ Check for synchronous or asynchronous exception
tst r1 , #F P E X C _ E X | F P E X C _ D E X
2005-04-17 02:20:36 +04:00
bne p r o c e s s _ e x c e p t i o n
2007-11-22 20:32:01 +03:00
@ On some implementations of the VFP subarch 1, setting FPSCR.IXE
@ causes all the CDP instructions to be bounced synchronously without
@ setting the FPEXC.EX bit
2005-04-17 02:20:36 +04:00
VFPFMRX r5 , F P S C R
2007-11-22 20:32:01 +03:00
tst r5 , #F P S C R _ I X E
2005-04-17 02:20:36 +04:00
bne p r o c e s s _ e x c e p t i o n
@ Fall into hand on to next handler - appropriate coproc instr
@ not recognised by VFP
DBGSTR " n o t V F P "
2014-04-02 13:57:49 +04:00
dec_ p r e e m p t _ c o u n t _ t i r10 , r4
2005-04-17 02:20:36 +04:00
mov p c , l r
process_exception :
DBGSTR " b o u n c e "
mov r2 , s p @ nothing stacked - regdump is at TOS
mov l r , r9 @ setup for a return to the user code.
@ Now call the C code to package up the bounce to the support code
@ r0 holds the trigger instruction
@ r1 holds the FPEXC value
@ r2 pointer to register dump
2007-11-22 20:32:01 +03:00
b V F P _ b o u n c e @ we have handled this - the support
2005-04-17 02:20:36 +04:00
@ code will raise an exception if
@ required. If not, the user code will
@ retry the faulted instruction
2008-08-28 14:22:32 +04:00
ENDPROC( v f p _ s u p p o r t _ e n t r y )
2005-04-17 02:20:36 +04:00
2008-08-28 14:22:32 +04:00
ENTRY( v f p _ s a v e _ s t a t e )
2007-01-24 20:47:08 +03:00
@ Save the current VFP state
@ r0 - save location
@ r1 - FPEXC
DBGSTR1 " s a v e V F P s t a t e % p " , r0
2007-09-25 18:22:24 +04:00
VFPFSTMIA r0 , r2 @ save the working registers
2007-01-24 20:47:08 +03:00
VFPFMRX r2 , F P S C R @ current status
2007-11-22 20:32:01 +03:00
tst r1 , #F P E X C _ E X @ i s t h e r e a d d i t i o n a l s t a t e t o s a v e ?
2008-11-06 16:23:08 +03:00
beq 1 f
VFPFMRX r3 , F P I N S T @ FPINST (only if FPEXC.EX is set)
tst r1 , #F P E X C _ F P 2 V @ is there an FPINST2 to read?
beq 1 f
VFPFMRX r12 , F P I N S T 2 @ FPINST2 if needed (and present)
1 :
2007-01-24 20:47:08 +03:00
stmia r0 , { r1 , r2 , r3 , r12 } @ save FPEXC, FPSCR, FPINST, FPINST2
mov p c , l r
2008-08-28 14:22:32 +04:00
ENDPROC( v f p _ s a v e _ s t a t e )
2007-01-24 20:47:08 +03:00
2010-11-29 21:43:22 +03:00
.align
2011-07-09 16:44:04 +04:00
vfp_current_hw_state_address :
.word vfp_current_hw_state
2005-04-17 02:20:36 +04:00
2009-07-24 15:32:57 +04:00
.macro tbl_ b r a n c h , b a s e , t m p , s h i f t
# ifdef C O N F I G _ T H U M B 2 _ K E R N E L
adr \ t m p , 1 f
add \ t m p , \ t m p , \ b a s e , l s l \ s h i f t
mov p c , \ t m p
# else
add p c , p c , \ b a s e , l s l \ s h i f t
2005-04-17 02:20:36 +04:00
mov r0 , r0
2009-07-24 15:32:57 +04:00
# endif
1 :
.endm
ENTRY( v f p _ g e t _ f l o a t )
tbl_ b r a n c h r0 , r3 , #3
2005-04-17 02:20:36 +04:00
.irp dr,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,1 0 ,1 1 ,1 2 ,1 3 ,1 4 ,1 5
2009-07-24 15:32:57 +04:00
1 : mrc p10 , 0 , r0 , c \ d r , c0 , 0 @ fmrs r0, s0
2005-04-17 02:20:36 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
1 : mrc p10 , 0 , r0 , c \ d r , c0 , 4 @ fmrs r0, s1
2005-04-17 02:20:36 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
2005-04-17 02:20:36 +04:00
.endr
2008-08-28 14:22:32 +04:00
ENDPROC( v f p _ g e t _ f l o a t )
2005-04-17 02:20:36 +04:00
2008-08-28 14:22:32 +04:00
ENTRY( v f p _ p u t _ f l o a t )
2009-07-24 15:32:57 +04:00
tbl_ b r a n c h r1 , r3 , #3
2005-04-17 02:20:36 +04:00
.irp dr,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,1 0 ,1 1 ,1 2 ,1 3 ,1 4 ,1 5
2009-07-24 15:32:57 +04:00
1 : mcr p10 , 0 , r0 , c \ d r , c0 , 0 @ fmsr r0, s0
2005-04-17 02:20:36 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
1 : mcr p10 , 0 , r0 , c \ d r , c0 , 4 @ fmsr r0, s1
2005-04-17 02:20:36 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
2005-04-17 02:20:36 +04:00
.endr
2008-08-28 14:22:32 +04:00
ENDPROC( v f p _ p u t _ f l o a t )
2005-04-17 02:20:36 +04:00
2008-08-28 14:22:32 +04:00
ENTRY( v f p _ g e t _ d o u b l e )
2009-07-24 15:32:57 +04:00
tbl_ b r a n c h r0 , r3 , #3
2005-04-17 02:20:36 +04:00
.irp dr,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,1 0 ,1 1 ,1 2 ,1 3 ,1 4 ,1 5
2009-07-24 15:32:57 +04:00
1 : fmrrd r0 , r1 , d \ d r
2005-04-17 02:20:36 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
2005-04-17 02:20:36 +04:00
.endr
2007-09-25 18:22:24 +04:00
# ifdef C O N F I G _ V F P v3
@ d16 - d31 registers
.irp dr,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,1 0 ,1 1 ,1 2 ,1 3 ,1 4 ,1 5
2009-07-24 15:32:57 +04:00
1 : mrrc p11 , 3 , r0 , r1 , c \ d r @ fmrrd r0, r1, d\dr
2007-09-25 18:22:24 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
2007-09-25 18:22:24 +04:00
.endr
# endif
2005-04-17 02:20:36 +04:00
2007-09-25 18:22:24 +04:00
@ virtual register 16 (or 32 if VFPv3) for compare with zero
2005-04-17 02:20:36 +04:00
mov r0 , #0
mov r1 , #0
mov p c , l r
2008-08-28 14:22:32 +04:00
ENDPROC( v f p _ g e t _ d o u b l e )
2005-04-17 02:20:36 +04:00
2008-08-28 14:22:32 +04:00
ENTRY( v f p _ p u t _ d o u b l e )
2009-07-24 15:32:57 +04:00
tbl_ b r a n c h r2 , r3 , #3
2005-04-17 02:20:36 +04:00
.irp dr,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,1 0 ,1 1 ,1 2 ,1 3 ,1 4 ,1 5
2009-07-24 15:32:57 +04:00
1 : fmdrr d \ d r , r0 , r1
2005-04-17 02:20:36 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
2005-04-17 02:20:36 +04:00
.endr
2007-09-25 18:22:24 +04:00
# ifdef C O N F I G _ V F P v3
@ d16 - d31 registers
.irp dr,0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,1 0 ,1 1 ,1 2 ,1 3 ,1 4 ,1 5
2010-05-27 11:23:29 +04:00
1 : mcrr p11 , 3 , r0 , r1 , c \ d r @ fmdrr r0, r1, d\dr
2007-09-25 18:22:24 +04:00
mov p c , l r
2009-07-24 15:32:57 +04:00
.org 1b + 8
2007-09-25 18:22:24 +04:00
.endr
# endif
2008-08-28 14:22:32 +04:00
ENDPROC( v f p _ p u t _ d o u b l e )