uml: virtualized time fix
[linux-2.6.git] / arch / um / kernel / time.c
1 /*
2  * Copyright (C) 2000 Jeff Dike (jdike@karaya.com)
3  * Licensed under the GPL
4  */
5
6 #include "linux/kernel.h"
7 #include "linux/module.h"
8 #include "linux/unistd.h"
9 #include "linux/stddef.h"
10 #include "linux/spinlock.h"
11 #include "linux/time.h"
12 #include "linux/sched.h"
13 #include "linux/interrupt.h"
14 #include "linux/init.h"
15 #include "linux/delay.h"
16 #include "linux/hrtimer.h"
17 #include "asm/irq.h"
18 #include "asm/param.h"
19 #include "asm/current.h"
20 #include "kern_util.h"
21 #include "mode.h"
22 #include "os.h"
23
24 int hz(void)
25 {
26         return(HZ);
27 }
28
29 /*
30  * Scheduler clock - returns current time in nanosec units.
31  */
32 unsigned long long sched_clock(void)
33 {
34         return (unsigned long long)jiffies_64 * (1000000000 / HZ);
35 }
36
37 #ifdef CONFIG_UML_REAL_TIME_CLOCK
38 static unsigned long long prev_nsecs[NR_CPUS];
39 static long long delta[NR_CPUS];                /* Deviation per interval */
40 #endif
41
42 void timer_irq(union uml_pt_regs *regs)
43 {
44         unsigned long long ticks = 0;
45 #ifdef CONFIG_UML_REAL_TIME_CLOCK
46         int c = cpu();
47         if(prev_nsecs[c]){
48                 /* We've had 1 tick */
49                 unsigned long long nsecs = os_nsecs();
50
51                 delta[c] += nsecs - prev_nsecs[c];
52                 prev_nsecs[c] = nsecs;
53
54                 /* Protect against the host clock being set backwards */
55                 if(delta[c] < 0)
56                         delta[c] = 0;
57
58                 ticks += (delta[c] * HZ) / BILLION;
59                 delta[c] -= (ticks * BILLION) / HZ;
60         }
61         else prev_nsecs[c] = os_nsecs();
62 #else
63         ticks = 1;
64 #endif
65         while(ticks > 0){
66                 do_IRQ(TIMER_IRQ, regs);
67                 ticks--;
68         }
69 }
70
71 /* Protects local_offset */
72 static DEFINE_SPINLOCK(timer_spinlock);
73 static unsigned long long local_offset = 0;
74
75 static inline unsigned long long get_time(void)
76 {
77         unsigned long long nsecs;
78         unsigned long flags;
79
80         spin_lock_irqsave(&timer_spinlock, flags);
81         nsecs = os_nsecs();
82         nsecs += local_offset;
83         spin_unlock_irqrestore(&timer_spinlock, flags);
84
85         return nsecs;
86 }
87
88 irqreturn_t um_timer(int irq, void *dev)
89 {
90         unsigned long long nsecs;
91         unsigned long flags;
92
93         write_seqlock_irqsave(&xtime_lock, flags);
94
95         do_timer(1);
96
97 #ifdef CONFIG_UML_REAL_TIME_CLOCK
98         nsecs = get_time();
99 #else
100         nsecs = (unsigned long long) xtime.tv_sec * BILLION + xtime.tv_nsec +
101                 BILLION / HZ;
102 #endif
103         xtime.tv_sec = nsecs / NSEC_PER_SEC;
104         xtime.tv_nsec = nsecs - xtime.tv_sec * NSEC_PER_SEC;
105
106         write_sequnlock_irqrestore(&xtime_lock, flags);
107
108         return IRQ_HANDLED;
109 }
110
111 static void register_timer(void)
112 {
113         int err;
114
115         err = request_irq(TIMER_IRQ, um_timer, IRQF_DISABLED, "timer", NULL);
116         if(err != 0)
117                 printk(KERN_ERR "register_timer : request_irq failed - "
118                        "errno = %d\n", -err);
119
120         err = set_interval(1);
121         if(err != 0)
122                 printk(KERN_ERR "register_timer : set_interval failed - "
123                        "errno = %d\n", -err);
124 }
125
126 extern void (*late_time_init)(void);
127
128 void time_init(void)
129 {
130         long long nsecs;
131
132         nsecs = os_nsecs();
133         set_normalized_timespec(&wall_to_monotonic, -nsecs / BILLION,
134                                 -nsecs % BILLION);
135         set_normalized_timespec(&xtime, nsecs / BILLION, nsecs % BILLION);
136         late_time_init = register_timer;
137 }
138
139 void do_gettimeofday(struct timeval *tv)
140 {
141 #ifdef CONFIG_UML_REAL_TIME_CLOCK
142         unsigned long long nsecs = get_time();
143 #else
144         unsigned long long nsecs = (unsigned long long) xtime.tv_sec * BILLION +
145                 xtime.tv_nsec;
146 #endif
147         tv->tv_sec = nsecs / NSEC_PER_SEC;
148         /* Careful about calculations here - this was originally done as
149          * (nsecs - tv->tv_sec * NSEC_PER_SEC) / NSEC_PER_USEC
150          * which gave bogus (> 1000000) values.  Dunno why, suspect gcc
151          * (4.0.0) miscompiled it, or there's a subtle 64/32-bit conversion
152          * problem that I missed.
153          */
154         nsecs -= tv->tv_sec * NSEC_PER_SEC;
155         tv->tv_usec = (unsigned long) nsecs / NSEC_PER_USEC;
156 }
157
158 static inline void set_time(unsigned long long nsecs)
159 {
160         unsigned long long now;
161         unsigned long flags;
162
163         spin_lock_irqsave(&timer_spinlock, flags);
164         now = os_nsecs();
165         local_offset = nsecs - now;
166         spin_unlock_irqrestore(&timer_spinlock, flags);
167
168         clock_was_set();
169 }
170
171 int do_settimeofday(struct timespec *tv)
172 {
173         set_time((unsigned long long) tv->tv_sec * NSEC_PER_SEC + tv->tv_nsec);
174
175         return 0;
176 }
177
178 void timer_handler(int sig, union uml_pt_regs *regs)
179 {
180         local_irq_disable();
181         irq_enter();
182         update_process_times(CHOOSE_MODE(
183                              (UPT_SC(regs) && user_context(UPT_SP(regs))),
184                              (regs)->skas.is_user));
185         irq_exit();
186         local_irq_enable();
187         if(current_thread->cpu == 0)
188                 timer_irq(regs);
189 }