diff options
author | Thomas Gleixner <tglx@linutronix.de> | 2007-02-16 04:28:01 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-16 11:13:59 -0500 |
commit | 906568c9c668ff994f4078932ec6ae1e3950d1af (patch) | |
tree | 825ac33fc61af55a0fe7485a1df681f5a6126d7b | |
parent | d316c57ff6bfad9557462b9100f25c6260d2b774 (diff) |
[PATCH] tick-management: core functionality
With Ingo Molnar <mingo@elte.hu>
The tick-management code is the first user of the clockevents layer. It takes
clock event devices from the clock events core and uses them to provide the
periodic tick.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Roman Zippel <zippel@linux-m68k.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | include/linux/tick.h | 31 | ||||
-rw-r--r-- | init/main.c | 2 | ||||
-rw-r--r-- | kernel/time/Makefile | 3 | ||||
-rw-r--r-- | kernel/time/tick-common.c | 277 |
4 files changed, 312 insertions, 1 deletions
diff --git a/include/linux/tick.h b/include/linux/tick.h new file mode 100644 index 000000000000..e5c0a4e22706 --- /dev/null +++ b/include/linux/tick.h | |||
@@ -0,0 +1,31 @@ | |||
1 | /* linux/include/linux/tick.h | ||
2 | * | ||
3 | * This file contains the structure definitions for tick related functions | ||
4 | * | ||
5 | */ | ||
6 | #ifndef _LINUX_TICK_H | ||
7 | #define _LINUX_TICK_H | ||
8 | |||
9 | #include <linux/clockchips.h> | ||
10 | |||
11 | #ifdef CONFIG_GENERIC_CLOCKEVENTS | ||
12 | |||
13 | enum tick_device_mode { | ||
14 | TICKDEV_MODE_PERIODIC, | ||
15 | TICKDEV_MODE_ONESHOT, | ||
16 | }; | ||
17 | |||
18 | struct tick_device { | ||
19 | struct clock_event_device *evtdev; | ||
20 | enum tick_device_mode mode; | ||
21 | }; | ||
22 | |||
23 | extern void __init tick_init(void); | ||
24 | |||
25 | #else | ||
26 | |||
27 | static inline void tick_init(void) { } | ||
28 | |||
29 | #endif | ||
30 | |||
31 | #endif | ||
diff --git a/init/main.c b/init/main.c index 2421e1544127..953500b02ac4 100644 --- a/init/main.c +++ b/init/main.c | |||
@@ -40,6 +40,7 @@ | |||
40 | #include <linux/cpu.h> | 40 | #include <linux/cpu.h> |
41 | #include <linux/cpuset.h> | 41 | #include <linux/cpuset.h> |
42 | #include <linux/efi.h> | 42 | #include <linux/efi.h> |
43 | #include <linux/tick.h> | ||
43 | #include <linux/taskstats_kern.h> | 44 | #include <linux/taskstats_kern.h> |
44 | #include <linux/delayacct.h> | 45 | #include <linux/delayacct.h> |
45 | #include <linux/unistd.h> | 46 | #include <linux/unistd.h> |
@@ -515,6 +516,7 @@ asmlinkage void __init start_kernel(void) | |||
515 | * enable them | 516 | * enable them |
516 | */ | 517 | */ |
517 | lock_kernel(); | 518 | lock_kernel(); |
519 | tick_init(); | ||
518 | boot_cpu_init(); | 520 | boot_cpu_init(); |
519 | page_address_init(); | 521 | page_address_init(); |
520 | printk(KERN_NOTICE); | 522 | printk(KERN_NOTICE); |
diff --git a/kernel/time/Makefile b/kernel/time/Makefile index 2bf1805914d0..337daef72755 100644 --- a/kernel/time/Makefile +++ b/kernel/time/Makefile | |||
@@ -1,3 +1,4 @@ | |||
1 | obj-y += ntp.o clocksource.o jiffies.o | 1 | obj-y += ntp.o clocksource.o jiffies.o |
2 | 2 | ||
3 | obj-$(CONFIG_GENERIC_CLOCKEVENTS) += clockevents.o | 3 | obj-$(CONFIG_GENERIC_CLOCKEVENTS) += clockevents.o |
4 | obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o | ||
diff --git a/kernel/time/tick-common.c b/kernel/time/tick-common.c new file mode 100644 index 000000000000..46e4381c26ea --- /dev/null +++ b/kernel/time/tick-common.c | |||
@@ -0,0 +1,277 @@ | |||
1 | /* | ||
2 | * linux/kernel/time/tick-common.c | ||
3 | * | ||
4 | * This file contains the base functions to manage periodic tick | ||
5 | * related events. | ||
6 | * | ||
7 | * Copyright(C) 2005-2006, Thomas Gleixner <tglx@linutronix.de> | ||
8 | * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar | ||
9 | * Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner | ||
10 | * | ||
11 | * This code is licenced under the GPL version 2. For details see | ||
12 | * kernel-base/COPYING. | ||
13 | */ | ||
14 | #include <linux/cpu.h> | ||
15 | #include <linux/err.h> | ||
16 | #include <linux/hrtimer.h> | ||
17 | #include <linux/irq.h> | ||
18 | #include <linux/percpu.h> | ||
19 | #include <linux/profile.h> | ||
20 | #include <linux/sched.h> | ||
21 | #include <linux/tick.h> | ||
22 | |||
23 | /* | ||
24 | * Tick devices | ||
25 | */ | ||
26 | static DEFINE_PER_CPU(struct tick_device, tick_cpu_device); | ||
27 | /* | ||
28 | * Tick next event: keeps track of the tick time | ||
29 | */ | ||
30 | static ktime_t tick_next_period; | ||
31 | static ktime_t tick_period; | ||
32 | static int tick_do_timer_cpu = -1; | ||
33 | static DEFINE_SPINLOCK(tick_device_lock); | ||
34 | |||
35 | /* | ||
36 | * Periodic tick | ||
37 | */ | ||
38 | static void tick_periodic(int cpu) | ||
39 | { | ||
40 | if (tick_do_timer_cpu == cpu) { | ||
41 | write_seqlock(&xtime_lock); | ||
42 | |||
43 | /* Keep track of the next tick event */ | ||
44 | tick_next_period = ktime_add(tick_next_period, tick_period); | ||
45 | |||
46 | do_timer(1); | ||
47 | write_sequnlock(&xtime_lock); | ||
48 | } | ||
49 | |||
50 | update_process_times(user_mode(get_irq_regs())); | ||
51 | profile_tick(CPU_PROFILING); | ||
52 | } | ||
53 | |||
54 | /* | ||
55 | * Event handler for periodic ticks | ||
56 | */ | ||
57 | void tick_handle_periodic(struct clock_event_device *dev) | ||
58 | { | ||
59 | int cpu = smp_processor_id(); | ||
60 | |||
61 | tick_periodic(cpu); | ||
62 | |||
63 | if (dev->mode != CLOCK_EVT_MODE_ONESHOT) | ||
64 | return; | ||
65 | /* | ||
66 | * Setup the next period for devices, which do not have | ||
67 | * periodic mode: | ||
68 | */ | ||
69 | for (;;) { | ||
70 | ktime_t next = ktime_add(dev->next_event, tick_period); | ||
71 | |||
72 | if (!clockevents_program_event(dev, next, ktime_get())) | ||
73 | return; | ||
74 | tick_periodic(cpu); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | /* | ||
79 | * Setup the device for a periodic tick | ||
80 | */ | ||
81 | void tick_setup_periodic(struct clock_event_device *dev) | ||
82 | { | ||
83 | dev->event_handler = tick_handle_periodic; | ||
84 | |||
85 | if (dev->features & CLOCK_EVT_FEAT_PERIODIC) { | ||
86 | clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC); | ||
87 | } else { | ||
88 | unsigned long seq; | ||
89 | ktime_t next; | ||
90 | |||
91 | do { | ||
92 | seq = read_seqbegin(&xtime_lock); | ||
93 | next = tick_next_period; | ||
94 | } while (read_seqretry(&xtime_lock, seq)); | ||
95 | |||
96 | clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT); | ||
97 | |||
98 | for (;;) { | ||
99 | if (!clockevents_program_event(dev, next, ktime_get())) | ||
100 | return; | ||
101 | next = ktime_add(next, tick_period); | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | /* | ||
107 | * Setup the tick device | ||
108 | */ | ||
109 | static void tick_setup_device(struct tick_device *td, | ||
110 | struct clock_event_device *newdev, int cpu, | ||
111 | cpumask_t cpumask) | ||
112 | { | ||
113 | ktime_t next_event; | ||
114 | void (*handler)(struct clock_event_device *) = NULL; | ||
115 | |||
116 | /* | ||
117 | * First device setup ? | ||
118 | */ | ||
119 | if (!td->evtdev) { | ||
120 | /* | ||
121 | * If no cpu took the do_timer update, assign it to | ||
122 | * this cpu: | ||
123 | */ | ||
124 | if (tick_do_timer_cpu == -1) { | ||
125 | tick_do_timer_cpu = cpu; | ||
126 | tick_next_period = ktime_get(); | ||
127 | tick_period = ktime_set(0, NSEC_PER_SEC / HZ); | ||
128 | } | ||
129 | |||
130 | /* | ||
131 | * Startup in periodic mode first. | ||
132 | */ | ||
133 | td->mode = TICKDEV_MODE_PERIODIC; | ||
134 | } else { | ||
135 | handler = td->evtdev->event_handler; | ||
136 | next_event = td->evtdev->next_event; | ||
137 | } | ||
138 | |||
139 | td->evtdev = newdev; | ||
140 | |||
141 | /* | ||
142 | * When the device is not per cpu, pin the interrupt to the | ||
143 | * current cpu: | ||
144 | */ | ||
145 | if (!cpus_equal(newdev->cpumask, cpumask)) | ||
146 | irq_set_affinity(newdev->irq, cpumask); | ||
147 | |||
148 | if (td->mode == TICKDEV_MODE_PERIODIC) | ||
149 | tick_setup_periodic(newdev, 0); | ||
150 | } | ||
151 | |||
152 | /* | ||
153 | * Check, if the new registered device should be used. | ||
154 | */ | ||
155 | static int tick_check_new_device(struct clock_event_device *newdev) | ||
156 | { | ||
157 | struct clock_event_device *curdev; | ||
158 | struct tick_device *td; | ||
159 | int cpu, ret = NOTIFY_OK; | ||
160 | unsigned long flags; | ||
161 | cpumask_t cpumask; | ||
162 | |||
163 | spin_lock_irqsave(&tick_device_lock, flags); | ||
164 | |||
165 | cpu = smp_processor_id(); | ||
166 | if (!cpu_isset(cpu, newdev->cpumask)) | ||
167 | goto out; | ||
168 | |||
169 | td = &per_cpu(tick_cpu_device, cpu); | ||
170 | curdev = td->evtdev; | ||
171 | cpumask = cpumask_of_cpu(cpu); | ||
172 | |||
173 | /* cpu local device ? */ | ||
174 | if (!cpus_equal(newdev->cpumask, cpumask)) { | ||
175 | |||
176 | /* | ||
177 | * If the cpu affinity of the device interrupt can not | ||
178 | * be set, ignore it. | ||
179 | */ | ||
180 | if (!irq_can_set_affinity(newdev->irq)) | ||
181 | goto out_bc; | ||
182 | |||
183 | /* | ||
184 | * If we have a cpu local device already, do not replace it | ||
185 | * by a non cpu local device | ||
186 | */ | ||
187 | if (curdev && cpus_equal(curdev->cpumask, cpumask)) | ||
188 | goto out_bc; | ||
189 | } | ||
190 | |||
191 | /* | ||
192 | * If we have an active device, then check the rating and the oneshot | ||
193 | * feature. | ||
194 | */ | ||
195 | if (curdev) { | ||
196 | /* | ||
197 | * Check the rating | ||
198 | */ | ||
199 | if (curdev->rating >= newdev->rating) | ||
200 | goto out; | ||
201 | } | ||
202 | |||
203 | /* | ||
204 | * Replace the eventually existing device by the new | ||
205 | * device. | ||
206 | */ | ||
207 | clockevents_exchange_device(curdev, newdev); | ||
208 | tick_setup_device(td, newdev, cpu, cpumask); | ||
209 | ret = NOTIFY_STOP; | ||
210 | |||
211 | out: | ||
212 | spin_unlock_irqrestore(&tick_device_lock, flags); | ||
213 | return ret; | ||
214 | } | ||
215 | |||
216 | /* | ||
217 | * Shutdown an event device on a given cpu: | ||
218 | * | ||
219 | * This is called on a life CPU, when a CPU is dead. So we cannot | ||
220 | * access the hardware device itself. | ||
221 | * We just set the mode and remove it from the lists. | ||
222 | */ | ||
223 | static void tick_shutdown(unsigned int *cpup) | ||
224 | { | ||
225 | struct tick_device *td = &per_cpu(tick_cpu_device, *cpup); | ||
226 | struct clock_event_device *dev = td->evtdev; | ||
227 | unsigned long flags; | ||
228 | |||
229 | spin_lock_irqsave(&tick_device_lock, flags); | ||
230 | td->mode = TICKDEV_MODE_PERIODIC; | ||
231 | if (dev) { | ||
232 | /* | ||
233 | * Prevent that the clock events layer tries to call | ||
234 | * the set mode function! | ||
235 | */ | ||
236 | dev->mode = CLOCK_EVT_MODE_UNUSED; | ||
237 | clockevents_exchange_device(dev, NULL); | ||
238 | td->evtdev = NULL; | ||
239 | } | ||
240 | spin_unlock_irqrestore(&tick_device_lock, flags); | ||
241 | } | ||
242 | |||
243 | /* | ||
244 | * Notification about clock event devices | ||
245 | */ | ||
246 | static int tick_notify(struct notifier_block *nb, unsigned long reason, | ||
247 | void *dev) | ||
248 | { | ||
249 | switch (reason) { | ||
250 | |||
251 | case CLOCK_EVT_NOTIFY_ADD: | ||
252 | return tick_check_new_device(dev); | ||
253 | |||
254 | case CLOCK_EVT_NOTIFY_CPU_DEAD: | ||
255 | tick_shutdown(dev); | ||
256 | break; | ||
257 | |||
258 | default: | ||
259 | break; | ||
260 | } | ||
261 | |||
262 | return NOTIFY_OK; | ||
263 | } | ||
264 | |||
265 | static struct notifier_block tick_notifier = { | ||
266 | .notifier_call = tick_notify, | ||
267 | }; | ||
268 | |||
269 | /** | ||
270 | * tick_init - initialize the tick control | ||
271 | * | ||
272 | * Register the notifier with the clockevents framework | ||
273 | */ | ||
274 | void __init tick_init(void) | ||
275 | { | ||
276 | clockevents_register_notifier(&tick_notifier); | ||
277 | } | ||