diff options
author | Russell King <rmk@dyn-67.arm.linux.org.uk> | 2006-06-21 08:31:52 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2006-06-22 05:24:18 -0400 |
commit | d6551e884cf66de072b81f8b6d23259462c40baf (patch) | |
tree | fd8af193bd045e4b16ce911d392d7ffd109d7284 | |
parent | 52ab3f3dc711eeccbfbcc5d4f5c5d9b9ff59650f (diff) |
[ARM] Add thread_notify infrastructure
Some machine classes need to allow VFP support to be built into the
kernel, but still allow the kernel to run even though VFP isn't
present. Unfortunately, the kernel hard-codes VFP instructions
into the thread switch, which prevents this being run-time selectable.
Solve this by introducing a notifier which things such as VFP can
hook into to be informed of events which affect the VFP subsystem
(eg, creation and destruction of threads, switches between threads.)
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r-- | arch/arm/kernel/entry-armv.S | 24 | ||||
-rw-r--r-- | arch/arm/kernel/iwmmxt.S | 2 | ||||
-rw-r--r-- | arch/arm/kernel/process.c | 24 | ||||
-rw-r--r-- | arch/arm/nwfpe/fpmodule.c | 25 | ||||
-rw-r--r-- | arch/arm/vfp/vfpmodule.c | 71 | ||||
-rw-r--r-- | include/asm-arm/thread_notify.h | 48 |
6 files changed, 135 insertions, 59 deletions
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index ab8e600c18c8..86c92523a346 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S | |||
@@ -20,6 +20,7 @@ | |||
20 | #include <asm/glue.h> | 20 | #include <asm/glue.h> |
21 | #include <asm/vfpmacros.h> | 21 | #include <asm/vfpmacros.h> |
22 | #include <asm/arch/entry-macro.S> | 22 | #include <asm/arch/entry-macro.S> |
23 | #include <asm/thread_notify.h> | ||
23 | 24 | ||
24 | #include "entry-header.S" | 25 | #include "entry-header.S" |
25 | 26 | ||
@@ -560,10 +561,8 @@ ENTRY(__switch_to) | |||
560 | add ip, r1, #TI_CPU_SAVE | 561 | add ip, r1, #TI_CPU_SAVE |
561 | ldr r3, [r2, #TI_TP_VALUE] | 562 | ldr r3, [r2, #TI_TP_VALUE] |
562 | stmia ip!, {r4 - sl, fp, sp, lr} @ Store most regs on stack | 563 | stmia ip!, {r4 - sl, fp, sp, lr} @ Store most regs on stack |
563 | #ifndef CONFIG_MMU | 564 | #ifdef CONFIG_MMU |
564 | add r2, r2, #TI_CPU_DOMAIN | 565 | ldr r6, [r2, #TI_CPU_DOMAIN] |
565 | #else | ||
566 | ldr r6, [r2, #TI_CPU_DOMAIN]! | ||
567 | #endif | 566 | #endif |
568 | #if __LINUX_ARM_ARCH__ >= 6 | 567 | #if __LINUX_ARM_ARCH__ >= 6 |
569 | #ifdef CONFIG_CPU_32v6K | 568 | #ifdef CONFIG_CPU_32v6K |
@@ -585,21 +584,20 @@ ENTRY(__switch_to) | |||
585 | #ifdef CONFIG_MMU | 584 | #ifdef CONFIG_MMU |
586 | mcr p15, 0, r6, c3, c0, 0 @ Set domain register | 585 | mcr p15, 0, r6, c3, c0, 0 @ Set domain register |
587 | #endif | 586 | #endif |
588 | #ifdef CONFIG_VFP | ||
589 | @ Always disable VFP so we can lazily save/restore the old | ||
590 | @ state. This occurs in the context of the previous thread. | ||
591 | VFPFMRX r4, FPEXC | ||
592 | bic r4, r4, #FPEXC_ENABLE | ||
593 | VFPFMXR FPEXC, r4 | ||
594 | #endif | ||
595 | #if defined(CONFIG_IWMMXT) | 587 | #if defined(CONFIG_IWMMXT) |
596 | bl iwmmxt_task_switch | 588 | bl iwmmxt_task_switch |
597 | #elif defined(CONFIG_CPU_XSCALE) | 589 | #elif defined(CONFIG_CPU_XSCALE) |
598 | add r4, r2, #40 @ cpu_context_save->extra | 590 | add r4, r2, #TI_CPU_DOMAIN + 40 @ cpu_context_save->extra |
599 | ldmib r4, {r4, r5} | 591 | ldmib r4, {r4, r5} |
600 | mar acc0, r4, r5 | 592 | mar acc0, r4, r5 |
601 | #endif | 593 | #endif |
602 | ldmib r2, {r4 - sl, fp, sp, pc} @ Load all regs saved previously | 594 | mov r5, r0 |
595 | add r4, r2, #TI_CPU_SAVE | ||
596 | ldr r0, =thread_notify_head | ||
597 | mov r1, #THREAD_NOTIFY_SWITCH | ||
598 | bl atomic_notifier_call_chain | ||
599 | mov r0, r5 | ||
600 | ldmia r4, {r4 - sl, fp, sp, pc} @ Load all regs saved previously | ||
603 | 601 | ||
604 | __INIT | 602 | __INIT |
605 | 603 | ||
diff --git a/arch/arm/kernel/iwmmxt.S b/arch/arm/kernel/iwmmxt.S index 24c7b0477a09..af9e0ae952d5 100644 --- a/arch/arm/kernel/iwmmxt.S +++ b/arch/arm/kernel/iwmmxt.S | |||
@@ -285,7 +285,7 @@ ENTRY(iwmmxt_task_switch) | |||
285 | bne 1f @ yes: block them for next task | 285 | bne 1f @ yes: block them for next task |
286 | 286 | ||
287 | ldr r5, =concan_owner | 287 | ldr r5, =concan_owner |
288 | add r6, r2, #(TI_IWMMXT_STATE - TI_CPU_DOMAIN) @ get next task Concan save area | 288 | add r6, r2, #TI_IWMMXT_STATE @ get next task Concan save area |
289 | ldr r5, [r5] @ get current Concan owner | 289 | ldr r5, [r5] @ get current Concan owner |
290 | teq r5, r6 @ next task owns it? | 290 | teq r5, r6 @ next task owns it? |
291 | movne pc, lr @ no: leave Concan disabled | 291 | movne pc, lr @ no: leave Concan disabled |
diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 17c38dbf2f3c..e1c77ee885a7 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c | |||
@@ -33,6 +33,7 @@ | |||
33 | #include <asm/leds.h> | 33 | #include <asm/leds.h> |
34 | #include <asm/processor.h> | 34 | #include <asm/processor.h> |
35 | #include <asm/system.h> | 35 | #include <asm/system.h> |
36 | #include <asm/thread_notify.h> | ||
36 | #include <asm/uaccess.h> | 37 | #include <asm/uaccess.h> |
37 | #include <asm/mach/time.h> | 38 | #include <asm/mach/time.h> |
38 | 39 | ||
@@ -338,13 +339,9 @@ void exit_thread(void) | |||
338 | { | 339 | { |
339 | } | 340 | } |
340 | 341 | ||
341 | static void default_fp_init(union fp_state *fp) | 342 | ATOMIC_NOTIFIER_HEAD(thread_notify_head); |
342 | { | ||
343 | memset(fp, 0, sizeof(union fp_state)); | ||
344 | } | ||
345 | 343 | ||
346 | void (*fp_init)(union fp_state *) = default_fp_init; | 344 | EXPORT_SYMBOL_GPL(thread_notify_head); |
347 | EXPORT_SYMBOL(fp_init); | ||
348 | 345 | ||
349 | void flush_thread(void) | 346 | void flush_thread(void) |
350 | { | 347 | { |
@@ -353,22 +350,21 @@ void flush_thread(void) | |||
353 | 350 | ||
354 | memset(thread->used_cp, 0, sizeof(thread->used_cp)); | 351 | memset(thread->used_cp, 0, sizeof(thread->used_cp)); |
355 | memset(&tsk->thread.debug, 0, sizeof(struct debug_info)); | 352 | memset(&tsk->thread.debug, 0, sizeof(struct debug_info)); |
353 | memset(&thread->fpstate, 0, sizeof(union fp_state)); | ||
354 | |||
355 | thread_notify(THREAD_NOTIFY_FLUSH, thread); | ||
356 | #if defined(CONFIG_IWMMXT) | 356 | #if defined(CONFIG_IWMMXT) |
357 | iwmmxt_task_release(thread); | 357 | iwmmxt_task_release(thread); |
358 | #endif | 358 | #endif |
359 | fp_init(&thread->fpstate); | ||
360 | #if defined(CONFIG_VFP) | ||
361 | vfp_flush_thread(&thread->vfpstate); | ||
362 | #endif | ||
363 | } | 359 | } |
364 | 360 | ||
365 | void release_thread(struct task_struct *dead_task) | 361 | void release_thread(struct task_struct *dead_task) |
366 | { | 362 | { |
367 | #if defined(CONFIG_VFP) | 363 | struct thread_info *thread = task_thread_info(dead_task); |
368 | vfp_release_thread(&task_thread_info(dead_task)->vfpstate); | 364 | |
369 | #endif | 365 | thread_notify(THREAD_NOTIFY_RELEASE, thread); |
370 | #if defined(CONFIG_IWMMXT) | 366 | #if defined(CONFIG_IWMMXT) |
371 | iwmmxt_task_release(task_thread_info(dead_task)); | 367 | iwmmxt_task_release(thread); |
372 | #endif | 368 | #endif |
373 | } | 369 | } |
374 | 370 | ||
diff --git a/arch/arm/nwfpe/fpmodule.c b/arch/arm/nwfpe/fpmodule.c index 2dfe1ac42ee8..7d977d23f026 100644 --- a/arch/arm/nwfpe/fpmodule.c +++ b/arch/arm/nwfpe/fpmodule.c | |||
@@ -33,7 +33,8 @@ | |||
33 | #include <linux/signal.h> | 33 | #include <linux/signal.h> |
34 | #include <linux/sched.h> | 34 | #include <linux/sched.h> |
35 | #include <linux/init.h> | 35 | #include <linux/init.h> |
36 | /* XXX */ | 36 | |
37 | #include <asm/thread_notify.h> | ||
37 | 38 | ||
38 | #include "softfloat.h" | 39 | #include "softfloat.h" |
39 | #include "fpopcode.h" | 40 | #include "fpopcode.h" |
@@ -56,16 +57,28 @@ void fp_send_sig(unsigned long sig, struct task_struct *p, int priv); | |||
56 | extern char fpe_type[]; | 57 | extern char fpe_type[]; |
57 | #endif | 58 | #endif |
58 | 59 | ||
60 | static int nwfpe_notify(struct notifier_block *self, unsigned long cmd, void *v) | ||
61 | { | ||
62 | struct thread_info *thread = v; | ||
63 | |||
64 | if (cmd == THREAD_NOTIFY_FLUSH) | ||
65 | nwfpe_init_fpa(&thread->fpstate); | ||
66 | |||
67 | return NOTIFY_DONE; | ||
68 | } | ||
69 | |||
70 | static struct notifier_block nwfpe_notifier_block = { | ||
71 | .notifier_call = nwfpe_notify, | ||
72 | }; | ||
73 | |||
59 | /* kernel function prototypes required */ | 74 | /* kernel function prototypes required */ |
60 | void fp_setup(void); | 75 | void fp_setup(void); |
61 | 76 | ||
62 | /* external declarations for saved kernel symbols */ | 77 | /* external declarations for saved kernel symbols */ |
63 | extern void (*kern_fp_enter)(void); | 78 | extern void (*kern_fp_enter)(void); |
64 | extern void (*fp_init)(union fp_state *); | ||
65 | 79 | ||
66 | /* Original value of fp_enter from kernel before patched by fpe_init. */ | 80 | /* Original value of fp_enter from kernel before patched by fpe_init. */ |
67 | static void (*orig_fp_enter)(void); | 81 | static void (*orig_fp_enter)(void); |
68 | static void (*orig_fp_init)(union fp_state *); | ||
69 | 82 | ||
70 | /* forward declarations */ | 83 | /* forward declarations */ |
71 | extern void nwfpe_enter(void); | 84 | extern void nwfpe_enter(void); |
@@ -88,20 +101,20 @@ static int __init fpe_init(void) | |||
88 | printk(KERN_WARNING "NetWinder Floating Point Emulator V0.97 (" | 101 | printk(KERN_WARNING "NetWinder Floating Point Emulator V0.97 (" |
89 | NWFPE_BITS " precision)\n"); | 102 | NWFPE_BITS " precision)\n"); |
90 | 103 | ||
104 | thread_register_notifier(&nwfpe_notifier_block); | ||
105 | |||
91 | /* Save pointer to the old FP handler and then patch ourselves in */ | 106 | /* Save pointer to the old FP handler and then patch ourselves in */ |
92 | orig_fp_enter = kern_fp_enter; | 107 | orig_fp_enter = kern_fp_enter; |
93 | orig_fp_init = fp_init; | ||
94 | kern_fp_enter = nwfpe_enter; | 108 | kern_fp_enter = nwfpe_enter; |
95 | fp_init = nwfpe_init_fpa; | ||
96 | 109 | ||
97 | return 0; | 110 | return 0; |
98 | } | 111 | } |
99 | 112 | ||
100 | static void __exit fpe_exit(void) | 113 | static void __exit fpe_exit(void) |
101 | { | 114 | { |
115 | thread_unregister_notifier(&nwfpe_notifier_block); | ||
102 | /* Restore the values we saved earlier. */ | 116 | /* Restore the values we saved earlier. */ |
103 | kern_fp_enter = orig_fp_enter; | 117 | kern_fp_enter = orig_fp_enter; |
104 | fp_init = orig_fp_init; | ||
105 | } | 118 | } |
106 | 119 | ||
107 | /* | 120 | /* |
diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c index 03486be04193..2476f4c2e760 100644 --- a/arch/arm/vfp/vfpmodule.c +++ b/arch/arm/vfp/vfpmodule.c | |||
@@ -15,6 +15,8 @@ | |||
15 | #include <linux/signal.h> | 15 | #include <linux/signal.h> |
16 | #include <linux/sched.h> | 16 | #include <linux/sched.h> |
17 | #include <linux/init.h> | 17 | #include <linux/init.h> |
18 | |||
19 | #include <asm/thread_notify.h> | ||
18 | #include <asm/vfp.h> | 20 | #include <asm/vfp.h> |
19 | 21 | ||
20 | #include "vfpinstr.h" | 22 | #include "vfpinstr.h" |
@@ -36,38 +38,55 @@ union vfp_state *last_VFP_context; | |||
36 | */ | 38 | */ |
37 | unsigned int VFP_arch; | 39 | unsigned int VFP_arch; |
38 | 40 | ||
39 | /* | 41 | static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v) |
40 | * Per-thread VFP initialisation. | ||
41 | */ | ||
42 | void vfp_flush_thread(union vfp_state *vfp) | ||
43 | { | 42 | { |
44 | memset(vfp, 0, sizeof(union vfp_state)); | 43 | struct thread_info *thread = v; |
44 | union vfp_state *vfp = &thread->vfpstate; | ||
45 | 45 | ||
46 | vfp->hard.fpexc = FPEXC_ENABLE; | 46 | switch (cmd) { |
47 | vfp->hard.fpscr = FPSCR_ROUND_NEAREST; | 47 | case THREAD_NOTIFY_FLUSH: |
48 | /* | ||
49 | * Per-thread VFP initialisation. | ||
50 | */ | ||
51 | memset(vfp, 0, sizeof(union vfp_state)); | ||
48 | 52 | ||
49 | /* | 53 | vfp->hard.fpexc = FPEXC_ENABLE; |
50 | * Disable VFP to ensure we initialise it first. | 54 | vfp->hard.fpscr = FPSCR_ROUND_NEAREST; |
51 | */ | ||
52 | fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_ENABLE); | ||
53 | 55 | ||
54 | /* | 56 | /* |
55 | * Ensure we don't try to overwrite our newly initialised | 57 | * Disable VFP to ensure we initialise it first. |
56 | * state information on the first fault. | 58 | */ |
57 | */ | 59 | fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_ENABLE); |
58 | if (last_VFP_context == vfp) | ||
59 | last_VFP_context = NULL; | ||
60 | } | ||
61 | 60 | ||
62 | /* | 61 | /* |
63 | * Per-thread VFP cleanup. | 62 | * FALLTHROUGH: Ensure we don't try to overwrite our newly |
64 | */ | 63 | * initialised state information on the first fault. |
65 | void vfp_release_thread(union vfp_state *vfp) | 64 | */ |
66 | { | 65 | |
67 | if (last_VFP_context == vfp) | 66 | case THREAD_NOTIFY_RELEASE: |
68 | last_VFP_context = NULL; | 67 | /* |
68 | * Per-thread VFP cleanup. | ||
69 | */ | ||
70 | if (last_VFP_context == vfp) | ||
71 | last_VFP_context = NULL; | ||
72 | break; | ||
73 | |||
74 | case THREAD_NOTIFY_SWITCH: | ||
75 | /* | ||
76 | * Always disable VFP so we can lazily save/restore the | ||
77 | * old state. | ||
78 | */ | ||
79 | fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_ENABLE); | ||
80 | break; | ||
81 | } | ||
82 | |||
83 | return NOTIFY_DONE; | ||
69 | } | 84 | } |
70 | 85 | ||
86 | static struct notifier_block vfp_notifier_block = { | ||
87 | .notifier_call = vfp_notifier, | ||
88 | }; | ||
89 | |||
71 | /* | 90 | /* |
72 | * Raise a SIGFPE for the current process. | 91 | * Raise a SIGFPE for the current process. |
73 | * sicode describes the signal being raised. | 92 | * sicode describes the signal being raised. |
@@ -281,6 +300,8 @@ static int __init vfp_init(void) | |||
281 | (vfpsid & FPSID_VARIANT_MASK) >> FPSID_VARIANT_BIT, | 300 | (vfpsid & FPSID_VARIANT_MASK) >> FPSID_VARIANT_BIT, |
282 | (vfpsid & FPSID_REV_MASK) >> FPSID_REV_BIT); | 301 | (vfpsid & FPSID_REV_MASK) >> FPSID_REV_BIT); |
283 | vfp_vector = vfp_support_entry; | 302 | vfp_vector = vfp_support_entry; |
303 | |||
304 | thread_register_notifier(&vfp_notifier_block); | ||
284 | } | 305 | } |
285 | return 0; | 306 | return 0; |
286 | } | 307 | } |
diff --git a/include/asm-arm/thread_notify.h b/include/asm-arm/thread_notify.h new file mode 100644 index 000000000000..8866e5216840 --- /dev/null +++ b/include/asm-arm/thread_notify.h | |||
@@ -0,0 +1,48 @@ | |||
1 | /* | ||
2 | * linux/include/asm-arm/thread_notify.h | ||
3 | * | ||
4 | * Copyright (C) 2006 Russell King. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | #ifndef ASMARM_THREAD_NOTIFY_H | ||
11 | #define ASMARM_THREAD_NOTIFY_H | ||
12 | |||
13 | #ifdef __KERNEL__ | ||
14 | |||
15 | #ifndef __ASSEMBLY__ | ||
16 | |||
17 | #include <linux/notifier.h> | ||
18 | #include <asm/thread_info.h> | ||
19 | |||
20 | static inline int thread_register_notifier(struct notifier_block *n) | ||
21 | { | ||
22 | extern struct atomic_notifier_head thread_notify_head; | ||
23 | return atomic_notifier_chain_register(&thread_notify_head, n); | ||
24 | } | ||
25 | |||
26 | static inline void thread_unregister_notifier(struct notifier_block *n) | ||
27 | { | ||
28 | extern struct atomic_notifier_head thread_notify_head; | ||
29 | atomic_notifier_chain_unregister(&thread_notify_head, n); | ||
30 | } | ||
31 | |||
32 | static inline void thread_notify(unsigned long rc, struct thread_info *thread) | ||
33 | { | ||
34 | extern struct atomic_notifier_head thread_notify_head; | ||
35 | atomic_notifier_call_chain(&thread_notify_head, rc, thread); | ||
36 | } | ||
37 | |||
38 | #endif | ||
39 | |||
40 | /* | ||
41 | * These are the reason codes for the thread notifier. | ||
42 | */ | ||
43 | #define THREAD_NOTIFY_FLUSH 0 | ||
44 | #define THREAD_NOTIFY_RELEASE 1 | ||
45 | #define THREAD_NOTIFY_SWITCH 2 | ||
46 | |||
47 | #endif | ||
48 | #endif | ||