diff options
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/Kconfig | 3 | ||||
-rw-r--r-- | arch/arm/include/asm/sched_clock.h | 118 | ||||
-rw-r--r-- | arch/arm/kernel/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/kernel/sched_clock.c | 69 |
4 files changed, 191 insertions, 0 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 49778bb43782..ed7a0a729d9c 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig | |||
@@ -38,6 +38,9 @@ config HAVE_PWM | |||
38 | config SYS_SUPPORTS_APM_EMULATION | 38 | config SYS_SUPPORTS_APM_EMULATION |
39 | bool | 39 | bool |
40 | 40 | ||
41 | config HAVE_SCHED_CLOCK | ||
42 | bool | ||
43 | |||
41 | config GENERIC_GPIO | 44 | config GENERIC_GPIO |
42 | bool | 45 | bool |
43 | 46 | ||
diff --git a/arch/arm/include/asm/sched_clock.h b/arch/arm/include/asm/sched_clock.h new file mode 100644 index 000000000000..a84628be1a7b --- /dev/null +++ b/arch/arm/include/asm/sched_clock.h | |||
@@ -0,0 +1,118 @@ | |||
1 | /* | ||
2 | * sched_clock.h: support for extending counters to full 64-bit ns counter | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | #ifndef ASM_SCHED_CLOCK | ||
9 | #define ASM_SCHED_CLOCK | ||
10 | |||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/types.h> | ||
13 | |||
14 | struct clock_data { | ||
15 | u64 epoch_ns; | ||
16 | u32 epoch_cyc; | ||
17 | u32 epoch_cyc_copy; | ||
18 | u32 mult; | ||
19 | u32 shift; | ||
20 | }; | ||
21 | |||
22 | #define DEFINE_CLOCK_DATA(name) struct clock_data name | ||
23 | |||
24 | static inline u64 cyc_to_ns(u64 cyc, u32 mult, u32 shift) | ||
25 | { | ||
26 | return (cyc * mult) >> shift; | ||
27 | } | ||
28 | |||
29 | /* | ||
30 | * Atomically update the sched_clock epoch. Your update callback will | ||
31 | * be called from a timer before the counter wraps - read the current | ||
32 | * counter value, and call this function to safely move the epochs | ||
33 | * forward. Only use this from the update callback. | ||
34 | */ | ||
35 | static inline void update_sched_clock(struct clock_data *cd, u32 cyc, u32 mask) | ||
36 | { | ||
37 | unsigned long flags; | ||
38 | u64 ns = cd->epoch_ns + | ||
39 | cyc_to_ns((cyc - cd->epoch_cyc) & mask, cd->mult, cd->shift); | ||
40 | |||
41 | /* | ||
42 | * Write epoch_cyc and epoch_ns in a way that the update is | ||
43 | * detectable in cyc_to_fixed_sched_clock(). | ||
44 | */ | ||
45 | raw_local_irq_save(flags); | ||
46 | cd->epoch_cyc = cyc; | ||
47 | smp_wmb(); | ||
48 | cd->epoch_ns = ns; | ||
49 | smp_wmb(); | ||
50 | cd->epoch_cyc_copy = cyc; | ||
51 | raw_local_irq_restore(flags); | ||
52 | } | ||
53 | |||
54 | /* | ||
55 | * If your clock rate is known at compile time, using this will allow | ||
56 | * you to optimize the mult/shift loads away. This is paired with | ||
57 | * init_fixed_sched_clock() to ensure that your mult/shift are correct. | ||
58 | */ | ||
59 | static inline unsigned long long cyc_to_fixed_sched_clock(struct clock_data *cd, | ||
60 | u32 cyc, u32 mask, u32 mult, u32 shift) | ||
61 | { | ||
62 | u64 epoch_ns; | ||
63 | u32 epoch_cyc; | ||
64 | |||
65 | /* | ||
66 | * Load the epoch_cyc and epoch_ns atomically. We do this by | ||
67 | * ensuring that we always write epoch_cyc, epoch_ns and | ||
68 | * epoch_cyc_copy in strict order, and read them in strict order. | ||
69 | * If epoch_cyc and epoch_cyc_copy are not equal, then we're in | ||
70 | * the middle of an update, and we should repeat the load. | ||
71 | */ | ||
72 | do { | ||
73 | epoch_cyc = cd->epoch_cyc; | ||
74 | smp_rmb(); | ||
75 | epoch_ns = cd->epoch_ns; | ||
76 | smp_rmb(); | ||
77 | } while (epoch_cyc != cd->epoch_cyc_copy); | ||
78 | |||
79 | return epoch_ns + cyc_to_ns((cyc - epoch_cyc) & mask, mult, shift); | ||
80 | } | ||
81 | |||
82 | /* | ||
83 | * Otherwise, you need to use this, which will obtain the mult/shift | ||
84 | * from the clock_data structure. Use init_sched_clock() with this. | ||
85 | */ | ||
86 | static inline unsigned long long cyc_to_sched_clock(struct clock_data *cd, | ||
87 | u32 cyc, u32 mask) | ||
88 | { | ||
89 | return cyc_to_fixed_sched_clock(cd, cyc, mask, cd->mult, cd->shift); | ||
90 | } | ||
91 | |||
92 | /* | ||
93 | * Initialize the clock data - calculate the appropriate multiplier | ||
94 | * and shift. Also setup a timer to ensure that the epoch is refreshed | ||
95 | * at the appropriate time interval, which will call your update | ||
96 | * handler. | ||
97 | */ | ||
98 | void init_sched_clock(struct clock_data *, void (*)(void), | ||
99 | unsigned int, unsigned long); | ||
100 | |||
101 | /* | ||
102 | * Use this initialization function rather than init_sched_clock() if | ||
103 | * you're using cyc_to_fixed_sched_clock, which will warn if your | ||
104 | * constants are incorrect. | ||
105 | */ | ||
106 | static inline void init_fixed_sched_clock(struct clock_data *cd, | ||
107 | void (*update)(void), unsigned int bits, unsigned long rate, | ||
108 | u32 mult, u32 shift) | ||
109 | { | ||
110 | init_sched_clock(cd, update, bits, rate); | ||
111 | if (cd->mult != mult || cd->shift != shift) { | ||
112 | pr_crit("sched_clock: wrong multiply/shift: %u>>%u vs calculated %u>>%u\n" | ||
113 | "sched_clock: fix multiply/shift to avoid scheduler hiccups\n", | ||
114 | mult, shift, cd->mult, cd->shift); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | #endif | ||
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 679851a9f589..fd3ec49bfba6 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile | |||
@@ -29,6 +29,7 @@ obj-$(CONFIG_MODULES) += armksyms.o module.o | |||
29 | obj-$(CONFIG_ARTHUR) += arthur.o | 29 | obj-$(CONFIG_ARTHUR) += arthur.o |
30 | obj-$(CONFIG_ISA_DMA) += dma-isa.o | 30 | obj-$(CONFIG_ISA_DMA) += dma-isa.o |
31 | obj-$(CONFIG_PCI) += bios32.o isa.o | 31 | obj-$(CONFIG_PCI) += bios32.o isa.o |
32 | obj-$(CONFIG_HAVE_SCHED_CLOCK) += sched_clock.o | ||
32 | obj-$(CONFIG_SMP) += smp.o | 33 | obj-$(CONFIG_SMP) += smp.o |
33 | obj-$(CONFIG_HAVE_ARM_SCU) += smp_scu.o | 34 | obj-$(CONFIG_HAVE_ARM_SCU) += smp_scu.o |
34 | obj-$(CONFIG_HAVE_ARM_TWD) += smp_twd.o | 35 | obj-$(CONFIG_HAVE_ARM_TWD) += smp_twd.o |
diff --git a/arch/arm/kernel/sched_clock.c b/arch/arm/kernel/sched_clock.c new file mode 100644 index 000000000000..2cdcc9287c74 --- /dev/null +++ b/arch/arm/kernel/sched_clock.c | |||
@@ -0,0 +1,69 @@ | |||
1 | /* | ||
2 | * sched_clock.c: support for extending counters to full 64-bit ns counter | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | #include <linux/clocksource.h> | ||
9 | #include <linux/init.h> | ||
10 | #include <linux/jiffies.h> | ||
11 | #include <linux/kernel.h> | ||
12 | #include <linux/sched.h> | ||
13 | #include <linux/timer.h> | ||
14 | |||
15 | #include <asm/sched_clock.h> | ||
16 | |||
17 | static void sched_clock_poll(unsigned long wrap_ticks); | ||
18 | static DEFINE_TIMER(sched_clock_timer, sched_clock_poll, 0, 0); | ||
19 | static void (*sched_clock_update_fn)(void); | ||
20 | |||
21 | static void sched_clock_poll(unsigned long wrap_ticks) | ||
22 | { | ||
23 | mod_timer(&sched_clock_timer, round_jiffies(jiffies + wrap_ticks)); | ||
24 | sched_clock_update_fn(); | ||
25 | } | ||
26 | |||
27 | void __init init_sched_clock(struct clock_data *cd, void (*update)(void), | ||
28 | unsigned int clock_bits, unsigned long rate) | ||
29 | { | ||
30 | unsigned long r, w; | ||
31 | u64 res, wrap; | ||
32 | char r_unit; | ||
33 | |||
34 | sched_clock_update_fn = update; | ||
35 | |||
36 | /* calculate the mult/shift to convert counter ticks to ns. */ | ||
37 | clocks_calc_mult_shift(&cd->mult, &cd->shift, rate, NSEC_PER_SEC, 60); | ||
38 | |||
39 | r = rate; | ||
40 | if (r >= 4000000) { | ||
41 | r /= 1000000; | ||
42 | r_unit = 'M'; | ||
43 | } else { | ||
44 | r /= 1000; | ||
45 | r_unit = 'k'; | ||
46 | } | ||
47 | |||
48 | /* calculate how many ns until we wrap */ | ||
49 | wrap = cyc_to_ns((1ULL << clock_bits) - 1, cd->mult, cd->shift); | ||
50 | do_div(wrap, NSEC_PER_MSEC); | ||
51 | w = wrap; | ||
52 | |||
53 | /* calculate the ns resolution of this counter */ | ||
54 | res = cyc_to_ns(1ULL, cd->mult, cd->shift); | ||
55 | pr_info("sched_clock: %u bits at %lu%cHz, resolution %lluns, wraps every %lums\n", | ||
56 | clock_bits, r, r_unit, res, w); | ||
57 | |||
58 | /* | ||
59 | * Start the timer to keep sched_clock() properly updated and | ||
60 | * sets the initial epoch. | ||
61 | */ | ||
62 | sched_clock_timer.data = msecs_to_jiffies(w - (w / 10)); | ||
63 | sched_clock_poll(sched_clock_timer.data); | ||
64 | |||
65 | /* | ||
66 | * Ensure that sched_clock() starts off at 0ns | ||
67 | */ | ||
68 | cd->epoch_ns = 0; | ||
69 | } | ||