Blame view

drivers/char/mmtimer.c 20.9 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
  /*
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
2
   * Timer device implementation for SGI SN platforms.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
3
4
5
6
7
   *
   * This file is subject to the terms and conditions of the GNU General Public
   * License.  See the file "COPYING" in the main directory of this archive
   * for more details.
   *
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
8
   * Copyright (c) 2001-2006 Silicon Graphics, Inc.  All rights reserved.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
   *
   * This driver exports an API that should be supportable by any HPET or IA-PC
   * multimedia timer.  The code below is currently specific to the SGI Altix
   * SHub RTC, however.
   *
   * 11/01/01 - jbarnes - initial revision
   * 9/10/04 - Christoph Lameter - remove interrupt support for kernel inclusion
   * 10/1/04 - Christoph Lameter - provide posix clock CLOCK_SGI_CYCLE
   * 10/13/04 - Christoph Lameter, Dimitri Sivanich - provide timer interrupt
   *		support via the posix timer interface
   */
  
  #include <linux/types.h>
  #include <linux/kernel.h>
  #include <linux/ioctl.h>
  #include <linux/module.h>
  #include <linux/init.h>
  #include <linux/errno.h>
  #include <linux/mm.h>
4e950f6f0   Alexey Dobriyan   Remove fs.h from ...
28
  #include <linux/fs.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
29
30
31
32
  #include <linux/mmtimer.h>
  #include <linux/miscdevice.h>
  #include <linux/posix-timers.h>
  #include <linux/interrupt.h>
f8bd2258e   Roman Zippel   remove div_long_l...
33
34
  #include <linux/time.h>
  #include <linux/math64.h>
613655fa3   Arnd Bergmann   drivers: autoconv...
35
  #include <linux/mutex.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
36
  #include <linux/slab.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  
  #include <asm/uaccess.h>
  #include <asm/sn/addrs.h>
  #include <asm/sn/intr.h>
  #include <asm/sn/shub_mmr.h>
  #include <asm/sn/nodepda.h>
  #include <asm/sn/shubio.h>
  
  MODULE_AUTHOR("Jesse Barnes <jbarnes@sgi.com>");
  MODULE_DESCRIPTION("SGI Altix RTC Timer");
  MODULE_LICENSE("GPL");
  
  /* name of the device, usually in /dev */
  #define MMTIMER_NAME "mmtimer"
  #define MMTIMER_DESC "SGI Altix RTC Timer"
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
52
  #define MMTIMER_VERSION "2.1"
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
53
54
  
  #define RTC_BITS 55 /* 55 bits for this implementation */
e5e542eea   Thomas Gleixner   posix-timers: Con...
55
  static struct k_clock sgi_clock;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
56
57
58
59
60
  extern unsigned long sn_rtc_cycles_per_second;
  
  #define RTC_COUNTER_ADDR        ((long *)LOCAL_MMR_ADDR(SH_RTC))
  
  #define rtc_time()              (*RTC_COUNTER_ADDR)
613655fa3   Arnd Bergmann   drivers: autoconv...
61
  static DEFINE_MUTEX(mmtimer_mutex);
4cddb886a   Alan Cox   mmtimer: Push BKL...
62
63
  static long mmtimer_ioctl(struct file *file, unsigned int cmd,
  						unsigned long arg);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
64
65
66
67
68
69
  static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma);
  
  /*
   * Period in femtoseconds (10^-15 s)
   */
  static unsigned long mmtimer_femtoperiod = 0;
62322d255   Arjan van de Ven   [PATCH] make more...
70
  static const struct file_operations mmtimer_fops = {
4cddb886a   Alan Cox   mmtimer: Push BKL...
71
72
73
  	.owner = THIS_MODULE,
  	.mmap =	mmtimer_mmap,
  	.unlocked_ioctl = mmtimer_ioctl,
6038f373a   Arnd Bergmann   llseek: automatic...
74
  	.llseek = noop_llseek,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
75
76
77
78
79
80
  };
  
  /*
   * We only have comparison registers RTC1-4 currently available per
   * node.  RTC0 is used by SAL.
   */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
81
  /* Check for an RTC interrupt pending */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
82
  static int mmtimer_int_pending(int comparator)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
83
84
85
86
87
88
89
  {
  	if (HUB_L((unsigned long *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED)) &
  			SH_EVENT_OCCURRED_RTC1_INT_MASK << comparator)
  		return 1;
  	else
  		return 0;
  }
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
90

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
91
  /* Clear the RTC interrupt pending bit */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
92
  static void mmtimer_clr_int_pending(int comparator)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
93
94
95
96
97
98
  {
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED_ALIAS),
  		SH_EVENT_OCCURRED_RTC1_INT_MASK << comparator);
  }
  
  /* Setup timer on comparator RTC1 */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
99
  static void mmtimer_setup_int_0(int cpu, u64 expires)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
100
101
102
103
104
105
106
107
108
109
110
111
112
  {
  	u64 val;
  
  	/* Disable interrupt */
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 0UL);
  
  	/* Initialize comparator value */
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), -1L);
  
  	/* Clear pending bit */
  	mmtimer_clr_int_pending(0);
  
  	val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC1_INT_CONFIG_IDX_SHFT) |
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
113
  		((u64)cpu_physical_id(cpu) <<
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
  			SH_RTC1_INT_CONFIG_PID_SHFT);
  
  	/* Set configuration */
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_CONFIG), val);
  
  	/* Enable RTC interrupts */
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 1UL);
  
  	/* Initialize comparator value */
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), expires);
  
  
  }
  
  /* Setup timer on comparator RTC2 */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
129
  static void mmtimer_setup_int_1(int cpu, u64 expires)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
131
132
133
134
135
136
137
138
139
  {
  	u64 val;
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 0UL);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPC), -1L);
  
  	mmtimer_clr_int_pending(1);
  
  	val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC2_INT_CONFIG_IDX_SHFT) |
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
140
  		((u64)cpu_physical_id(cpu) <<
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
141
142
143
144
145
146
147
148
149
150
  			SH_RTC2_INT_CONFIG_PID_SHFT);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_CONFIG), val);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 1UL);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPC), expires);
  }
  
  /* Setup timer on comparator RTC3 */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
151
  static void mmtimer_setup_int_2(int cpu, u64 expires)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
152
153
154
155
156
157
158
159
160
161
  {
  	u64 val;
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 0UL);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPD), -1L);
  
  	mmtimer_clr_int_pending(2);
  
  	val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC3_INT_CONFIG_IDX_SHFT) |
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
162
  		((u64)cpu_physical_id(cpu) <<
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
163
164
165
166
167
168
169
170
171
172
173
174
175
176
  			SH_RTC3_INT_CONFIG_PID_SHFT);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_CONFIG), val);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 1UL);
  
  	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPD), expires);
  }
  
  /*
   * This function must be called with interrupts disabled and preemption off
   * in order to insure that the setup succeeds in a deterministic time frame.
   * It will check if the interrupt setup succeeded.
   */
dcade5ed1   Dimitri Sivanich   SGI Altix IA64 mm...
177
178
  static int mmtimer_setup(int cpu, int comparator, unsigned long expires,
  	u64 *set_completion_time)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
179
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180
181
  	switch (comparator) {
  	case 0:
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
182
  		mmtimer_setup_int_0(cpu, expires);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
183
184
  		break;
  	case 1:
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
185
  		mmtimer_setup_int_1(cpu, expires);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
186
187
  		break;
  	case 2:
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
188
  		mmtimer_setup_int_2(cpu, expires);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
189
190
191
  		break;
  	}
  	/* We might've missed our expiration time */
dcade5ed1   Dimitri Sivanich   SGI Altix IA64 mm...
192
193
  	*set_completion_time = rtc_time();
  	if (*set_completion_time <= expires)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
194
195
196
197
198
199
200
201
  		return 1;
  
  	/*
  	 * If an interrupt is already pending then its okay
  	 * if not then we failed
  	 */
  	return mmtimer_int_pending(comparator);
  }
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
202
  static int mmtimer_disable_int(long nasid, int comparator)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
  {
  	switch (comparator) {
  	case 0:
  		nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE),
  			0UL) : REMOTE_HUB_S(nasid, SH_RTC1_INT_ENABLE, 0UL);
  		break;
  	case 1:
  		nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE),
  			0UL) : REMOTE_HUB_S(nasid, SH_RTC2_INT_ENABLE, 0UL);
  		break;
  	case 2:
  		nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE),
  			0UL) : REMOTE_HUB_S(nasid, SH_RTC3_INT_ENABLE, 0UL);
  		break;
  	default:
  		return -EFAULT;
  	}
  	return 0;
  }
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
222
  #define COMPARATOR	1		/* The comparator to use */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
223

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
224
225
  #define TIMER_OFF	0xbadcabLL	/* Timer is not setup */
  #define TIMER_SET	0		/* Comparator is set for this timer */
dcade5ed1   Dimitri Sivanich   SGI Altix IA64 mm...
226
  #define MMTIMER_INTERVAL_RETRY_INCREMENT_DEFAULT 40
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
227
228
229
  /* There is one of these for each timer */
  struct mmtimer {
  	struct rb_node list;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
230
  	struct k_itimer *timer;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
231
  	int cpu;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
232
233
234
235
236
237
  };
  
  struct mmtimer_node {
  	spinlock_t lock ____cacheline_aligned;
  	struct rb_root timer_head;
  	struct rb_node *next;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
238
  	struct tasklet_struct tasklet;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
239
240
  };
  static struct mmtimer_node *timers;
dcade5ed1   Dimitri Sivanich   SGI Altix IA64 mm...
241
242
243
244
245
  static unsigned mmtimer_interval_retry_increment =
  	MMTIMER_INTERVAL_RETRY_INCREMENT_DEFAULT;
  module_param(mmtimer_interval_retry_increment, uint, 0644);
  MODULE_PARM_DESC(mmtimer_interval_retry_increment,
  	"RTC ticks to add to expiration on interval retry (default 40)");
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
  
  /*
   * Add a new mmtimer struct to the node's mmtimer list.
   * This function assumes the struct mmtimer_node is locked.
   */
  static void mmtimer_add_list(struct mmtimer *n)
  {
  	int nodeid = n->timer->it.mmtimer.node;
  	unsigned long expires = n->timer->it.mmtimer.expires;
  	struct rb_node **link = &timers[nodeid].timer_head.rb_node;
  	struct rb_node *parent = NULL;
  	struct mmtimer *x;
  
  	/*
  	 * Find the right place in the rbtree:
  	 */
  	while (*link) {
  		parent = *link;
  		x = rb_entry(parent, struct mmtimer, list);
  
  		if (expires < x->timer->it.mmtimer.expires)
  			link = &(*link)->rb_left;
  		else
  			link = &(*link)->rb_right;
  	}
  
  	/*
  	 * Insert the timer to the rbtree and check whether it
  	 * replaces the first pending timer
  	 */
  	rb_link_node(&n->list, parent, link);
  	rb_insert_color(&n->list, &timers[nodeid].timer_head);
  
  	if (!timers[nodeid].next || expires < rb_entry(timers[nodeid].next,
  			struct mmtimer, list)->timer->it.mmtimer.expires)
  		timers[nodeid].next = &n->list;
  }
  
  /*
   * Set the comparator for the next timer.
   * This function assumes the struct mmtimer_node is locked.
   */
  static void mmtimer_set_next_timer(int nodeid)
  {
  	struct mmtimer_node *n = &timers[nodeid];
  	struct mmtimer *x;
  	struct k_itimer *t;
dcade5ed1   Dimitri Sivanich   SGI Altix IA64 mm...
293
294
  	u64 expires, exp, set_completion_time;
  	int i;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
295
296
297
298
  
  restart:
  	if (n->next == NULL)
  		return;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
299

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
300
301
302
303
304
  	x = rb_entry(n->next, struct mmtimer, list);
  	t = x->timer;
  	if (!t->it.mmtimer.incr) {
  		/* Not an interval timer */
  		if (!mmtimer_setup(x->cpu, COMPARATOR,
dcade5ed1   Dimitri Sivanich   SGI Altix IA64 mm...
305
306
  					t->it.mmtimer.expires,
  					&set_completion_time)) {
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
307
308
309
310
311
312
313
  			/* Late setup, fire now */
  			tasklet_schedule(&n->tasklet);
  		}
  		return;
  	}
  
  	/* Interval timer */
dcade5ed1   Dimitri Sivanich   SGI Altix IA64 mm...
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
  	i = 0;
  	expires = exp = t->it.mmtimer.expires;
  	while (!mmtimer_setup(x->cpu, COMPARATOR, expires,
  				&set_completion_time)) {
  		int to;
  
  		i++;
  		expires = set_completion_time +
  				mmtimer_interval_retry_increment + (1 << i);
  		/* Calculate overruns as we go. */
  		to = ((u64)(expires - exp) / t->it.mmtimer.incr);
  		if (to) {
  			t->it_overrun += to;
  			t->it.mmtimer.expires += t->it.mmtimer.incr * to;
  			exp = t->it.mmtimer.expires;
  		}
  		if (i > 20) {
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
331
332
333
334
335
336
337
338
  			printk(KERN_ALERT "mmtimer: cannot reschedule timer
  ");
  			t->it.mmtimer.clock = TIMER_OFF;
  			n->next = rb_next(&x->list);
  			rb_erase(&x->list, &n->timer_head);
  			kfree(x);
  			goto restart;
  		}
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
339
340
  	}
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
341
342
343
  
  /**
   * mmtimer_ioctl - ioctl interface for /dev/mmtimer
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
   * @file: file structure for the device
   * @cmd: command to execute
   * @arg: optional argument to command
   *
   * Executes the command specified by @cmd.  Returns 0 for success, < 0 for
   * failure.
   *
   * Valid commands:
   *
   * %MMTIMER_GETOFFSET - Should return the offset (relative to the start
   * of the page where the registers are mapped) for the counter in question.
   *
   * %MMTIMER_GETRES - Returns the resolution of the clock in femto (10^-15)
   * seconds
   *
   * %MMTIMER_GETFREQ - Copies the frequency of the clock in Hz to the address
   * specified by @arg
   *
   * %MMTIMER_GETBITS - Returns the number of bits in the clock's counter
   *
   * %MMTIMER_MMAPAVAIL - Returns 1 if the registers can be mmap'd into userspace
   *
   * %MMTIMER_GETCOUNTER - Gets the current value in the counter and places it
   * in the address specified by @arg.
   */
4cddb886a   Alan Cox   mmtimer: Push BKL...
369
370
  static long mmtimer_ioctl(struct file *file, unsigned int cmd,
  						unsigned long arg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
371
372
  {
  	int ret = 0;
613655fa3   Arnd Bergmann   drivers: autoconv...
373
  	mutex_lock(&mmtimer_mutex);
4cddb886a   Alan Cox   mmtimer: Push BKL...
374

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
375
376
377
378
379
380
381
382
383
384
385
386
387
388
  	switch (cmd) {
  	case MMTIMER_GETOFFSET:	/* offset of the counter */
  		/*
  		 * SN RTC registers are on their own 64k page
  		 */
  		if(PAGE_SIZE <= (1 << 16))
  			ret = (((long)RTC_COUNTER_ADDR) & (PAGE_SIZE-1)) / 8;
  		else
  			ret = -ENOSYS;
  		break;
  
  	case MMTIMER_GETRES: /* resolution of the clock in 10^-15 s */
  		if(copy_to_user((unsigned long __user *)arg,
  				&mmtimer_femtoperiod, sizeof(unsigned long)))
4cddb886a   Alan Cox   mmtimer: Push BKL...
389
  			ret = -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
390
391
392
393
394
395
  		break;
  
  	case MMTIMER_GETFREQ: /* frequency in Hz */
  		if(copy_to_user((unsigned long __user *)arg,
  				&sn_rtc_cycles_per_second,
  				sizeof(unsigned long)))
4cddb886a   Alan Cox   mmtimer: Push BKL...
396
  			ret = -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
397
398
399
400
401
402
403
404
405
406
407
408
409
  		break;
  
  	case MMTIMER_GETBITS: /* number of bits in the clock */
  		ret = RTC_BITS;
  		break;
  
  	case MMTIMER_MMAPAVAIL: /* can we mmap the clock into userspace? */
  		ret = (PAGE_SIZE <= (1 << 16)) ? 1 : 0;
  		break;
  
  	case MMTIMER_GETCOUNTER:
  		if(copy_to_user((unsigned long __user *)arg,
  				RTC_COUNTER_ADDR, sizeof(unsigned long)))
4cddb886a   Alan Cox   mmtimer: Push BKL...
410
  			ret = -EFAULT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
411
412
  		break;
  	default:
4cddb886a   Alan Cox   mmtimer: Push BKL...
413
  		ret = -ENOTTY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
414
415
  		break;
  	}
613655fa3   Arnd Bergmann   drivers: autoconv...
416
  	mutex_unlock(&mmtimer_mutex);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
  	return ret;
  }
  
  /**
   * mmtimer_mmap - maps the clock's registers into userspace
   * @file: file structure for the device
   * @vma: VMA to map the registers into
   *
   * Calls remap_pfn_range() to map the clock's registers into
   * the calling process' address space.
   */
  static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma)
  {
  	unsigned long mmtimer_addr;
  
  	if (vma->vm_end - vma->vm_start != PAGE_SIZE)
  		return -EINVAL;
  
  	if (vma->vm_flags & VM_WRITE)
  		return -EPERM;
  
  	if (PAGE_SIZE > (1 << 16))
  		return -ENOSYS;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
  	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
  
  	mmtimer_addr = __pa(RTC_COUNTER_ADDR);
  	mmtimer_addr &= ~(PAGE_SIZE - 1);
  	mmtimer_addr &= 0xfffffffffffffffUL;
  
  	if (remap_pfn_range(vma, vma->vm_start, mmtimer_addr >> PAGE_SHIFT,
  					PAGE_SIZE, vma->vm_page_prot)) {
  		printk(KERN_ERR "remap_pfn_range failed in mmtimer.c
  ");
  		return -EAGAIN;
  	}
  
  	return 0;
  }
  
  static struct miscdevice mmtimer_miscdev = {
  	SGI_MMTIMER,
  	MMTIMER_NAME,
  	&mmtimer_fops
  };
  
  static struct timespec sgi_clock_offset;
  static int sgi_clock_period;
  
  /*
   * Posix Timer Interface
   */
  
  static struct timespec sgi_clock_offset;
  static int sgi_clock_period;
  
  static int sgi_clock_get(clockid_t clockid, struct timespec *tp)
  {
  	u64 nsec;
  
  	nsec = rtc_time() * sgi_clock_period
  			+ sgi_clock_offset.tv_nsec;
f8bd2258e   Roman Zippel   remove div_long_l...
478
479
  	*tp = ns_to_timespec(nsec);
  	tp->tv_sec += sgi_clock_offset.tv_sec;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
480
481
  	return 0;
  };
1e6d76792   Richard Cochran   time: Correct the...
482
  static int sgi_clock_set(const clockid_t clockid, const struct timespec *tp)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
483
484
485
  {
  
  	u64 nsec;
f8bd2258e   Roman Zippel   remove div_long_l...
486
  	u32 rem;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
487
488
  
  	nsec = rtc_time() * sgi_clock_period;
f8bd2258e   Roman Zippel   remove div_long_l...
489
  	sgi_clock_offset.tv_sec = tp->tv_sec - div_u64_rem(nsec, NSEC_PER_SEC, &rem);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
490
491
492
493
494
495
496
497
498
  
  	if (rem <= tp->tv_nsec)
  		sgi_clock_offset.tv_nsec = tp->tv_sec - rem;
  	else {
  		sgi_clock_offset.tv_nsec = tp->tv_sec + NSEC_PER_SEC - rem;
  		sgi_clock_offset.tv_sec--;
  	}
  	return 0;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
499
500
501
502
  /**
   * mmtimer_interrupt - timer interrupt handler
   * @irq: irq received
   * @dev_id: device the irq came from
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
503
504
505
506
507
508
509
510
511
512
   *
   * Called when one of the comarators matches the counter, This
   * routine will send signals to processes that have requested
   * them.
   *
   * This interrupt is run in an interrupt context
   * by the SHUB. It is therefore safe to locally access SHub
   * registers.
   */
  static irqreturn_t
7d12e780e   David Howells   IRQ: Maintain reg...
513
  mmtimer_interrupt(int irq, void *dev_id)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
514
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
515
516
  	unsigned long expires = 0;
  	int result = IRQ_NONE;
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
517
  	unsigned indx = cpu_to_node(smp_processor_id());
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
518
  	struct mmtimer *base;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
519

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
  	spin_lock(&timers[indx].lock);
  	base = rb_entry(timers[indx].next, struct mmtimer, list);
  	if (base == NULL) {
  		spin_unlock(&timers[indx].lock);
  		return result;
  	}
  
  	if (base->cpu == smp_processor_id()) {
  		if (base->timer)
  			expires = base->timer->it.mmtimer.expires;
  		/* expires test won't work with shared irqs */
  		if ((mmtimer_int_pending(COMPARATOR) > 0) ||
  			(expires && (expires <= rtc_time()))) {
  			mmtimer_clr_int_pending(COMPARATOR);
  			tasklet_schedule(&timers[indx].tasklet);
  			result = IRQ_HANDLED;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
536
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
537
  	}
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
538
  	spin_unlock(&timers[indx].lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
539
540
  	return result;
  }
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
541
542
543
544
  static void mmtimer_tasklet(unsigned long data)
  {
  	int nodeid = data;
  	struct mmtimer_node *mn = &timers[nodeid];
0fbcae222   Julia Lawall   drivers/char/mmti...
545
  	struct mmtimer *x;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
546
  	struct k_itimer *t;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
547
  	unsigned long flags;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
548
  	/* Send signal and deal with periodic signals */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
549
550
  	spin_lock_irqsave(&mn->lock, flags);
  	if (!mn->next)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
551
  		goto out;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
552

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
553
554
555
556
557
558
559
  	x = rb_entry(mn->next, struct mmtimer, list);
  	t = x->timer;
  
  	if (t->it.mmtimer.clock == TIMER_OFF)
  		goto out;
  
  	t->it_overrun = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
560

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
561
562
  	mn->next = rb_next(&x->list);
  	rb_erase(&x->list, &mn->timer_head);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
563

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
564
  	if (posix_timer_event(t, 0) != 0)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
565
  		t->it_overrun++;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
566

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
567
  	if(t->it.mmtimer.incr) {
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
568
569
  		t->it.mmtimer.expires += t->it.mmtimer.incr;
  		mmtimer_add_list(x);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
570
571
  	} else {
  		/* Ensure we don't false trigger in mmtimer_interrupt */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
572
  		t->it.mmtimer.clock = TIMER_OFF;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
573
  		t->it.mmtimer.expires = 0;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
574
  		kfree(x);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
575
  	}
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
576
577
  	/* Set comparator for next timer, if there is one */
  	mmtimer_set_next_timer(nodeid);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
578
579
  	t->it_overrun_last = t->it_overrun;
  out:
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
580
  	spin_unlock_irqrestore(&mn->lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
  }
  
  static int sgi_timer_create(struct k_itimer *timer)
  {
  	/* Insure that a newly created timer is off */
  	timer->it.mmtimer.clock = TIMER_OFF;
  	return 0;
  }
  
  /* This does not really delete a timer. It just insures
   * that the timer is not active
   *
   * Assumption: it_lock is already held with irq's disabled
   */
  static int sgi_timer_del(struct k_itimer *timr)
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
597
  	cnodeid_t nodeid = timr->it.mmtimer.node;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
598
  	unsigned long irqflags;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
599
600
601
602
603
604
  	spin_lock_irqsave(&timers[nodeid].lock, irqflags);
  	if (timr->it.mmtimer.clock != TIMER_OFF) {
  		unsigned long expires = timr->it.mmtimer.expires;
  		struct rb_node *n = timers[nodeid].timer_head.rb_node;
  		struct mmtimer *uninitialized_var(t);
  		int r = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
605
606
  		timr->it.mmtimer.clock = TIMER_OFF;
  		timr->it.mmtimer.expires = 0;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
  
  		while (n) {
  			t = rb_entry(n, struct mmtimer, list);
  			if (t->timer == timr)
  				break;
  
  			if (expires < t->timer->it.mmtimer.expires)
  				n = n->rb_left;
  			else
  				n = n->rb_right;
  		}
  
  		if (!n) {
  			spin_unlock_irqrestore(&timers[nodeid].lock, irqflags);
  			return 0;
  		}
  
  		if (timers[nodeid].next == n) {
  			timers[nodeid].next = rb_next(n);
  			r = 1;
  		}
  
  		rb_erase(n, &timers[nodeid].timer_head);
  		kfree(t);
  
  		if (r) {
  			mmtimer_disable_int(cnodeid_to_nasid(nodeid),
  				COMPARATOR);
  			mmtimer_set_next_timer(nodeid);
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
637
  	}
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
638
  	spin_unlock_irqrestore(&timers[nodeid].lock, irqflags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
639
640
  	return 0;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
641
642
643
644
645
646
647
648
649
650
651
  /* Assumption: it_lock is already held with irq's disabled */
  static void sgi_timer_get(struct k_itimer *timr, struct itimerspec *cur_setting)
  {
  
  	if (timr->it.mmtimer.clock == TIMER_OFF) {
  		cur_setting->it_interval.tv_nsec = 0;
  		cur_setting->it_interval.tv_sec = 0;
  		cur_setting->it_value.tv_nsec = 0;
  		cur_setting->it_value.tv_sec =0;
  		return;
  	}
f8bd2258e   Roman Zippel   remove div_long_l...
652
653
  	cur_setting->it_interval = ns_to_timespec(timr->it.mmtimer.incr * sgi_clock_period);
  	cur_setting->it_value = ns_to_timespec((timr->it.mmtimer.expires - rtc_time()) * sgi_clock_period);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
654
655
656
657
658
659
660
  }
  
  
  static int sgi_timer_set(struct k_itimer *timr, int flags,
  	struct itimerspec * new_setting,
  	struct itimerspec * old_setting)
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
661
662
663
  	unsigned long when, period, irqflags;
  	int err = 0;
  	cnodeid_t nodeid;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
664
665
  	struct mmtimer *base;
  	struct rb_node *n;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
666
667
668
669
670
  
  	if (old_setting)
  		sgi_timer_get(timr, old_setting);
  
  	sgi_timer_del(timr);
f8bd2258e   Roman Zippel   remove div_long_l...
671
672
  	when = timespec_to_ns(&new_setting->it_value);
  	period = timespec_to_ns(&new_setting->it_interval);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
673
674
675
676
  
  	if (when == 0)
  		/* Clear timer */
  		return 0;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
677
678
679
  	base = kmalloc(sizeof(struct mmtimer), GFP_KERNEL);
  	if (base == NULL)
  		return -ENOMEM;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
680
681
682
683
684
  	if (flags & TIMER_ABSTIME) {
  		struct timespec n;
  		unsigned long now;
  
  		getnstimeofday(&n);
f8bd2258e   Roman Zippel   remove div_long_l...
685
  		now = timespec_to_ns(&n);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
  		if (when > now)
  			when -= now;
  		else
  			/* Fire the timer immediately */
  			when = 0;
  	}
  
  	/*
  	 * Convert to sgi clock period. Need to keep rtc_time() as near as possible
  	 * to getnstimeofday() in order to be as faithful as possible to the time
  	 * specified.
  	 */
  	when = (when + sgi_clock_period - 1) / sgi_clock_period + rtc_time();
  	period = (period + sgi_clock_period - 1)  / sgi_clock_period;
  
  	/*
  	 * We are allocating a local SHub comparator. If we would be moved to another
  	 * cpu then another SHub may be local to us. Prohibit that by switching off
  	 * preemption.
  	 */
  	preempt_disable();
55642d36c   Tony Luck   [IA64] Two more u...
707
  	nodeid =  cpu_to_node(smp_processor_id());
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
708

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
709
710
  	/* Lock the node timer structure */
  	spin_lock_irqsave(&timers[nodeid].lock, irqflags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
711

76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
712
713
  	base->timer = timr;
  	base->cpu = smp_processor_id();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
714

cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
715
  	timr->it.mmtimer.clock = TIMER_SET;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
716
717
718
  	timr->it.mmtimer.node = nodeid;
  	timr->it.mmtimer.incr = period;
  	timr->it.mmtimer.expires = when;
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
719
720
721
722
723
724
725
726
727
728
  	n = timers[nodeid].next;
  
  	/* Add the new struct mmtimer to node's timer list */
  	mmtimer_add_list(base);
  
  	if (timers[nodeid].next == n) {
  		/* No need to reprogram comparator for now */
  		spin_unlock_irqrestore(&timers[nodeid].lock, irqflags);
  		preempt_enable();
  		return err;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
729
  	}
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
730
731
732
733
734
735
736
737
  	/* We need to reprogram the comparator */
  	if (n)
  		mmtimer_disable_int(cnodeid_to_nasid(nodeid), COMPARATOR);
  
  	mmtimer_set_next_timer(nodeid);
  
  	/* Unlock the node timer structure */
  	spin_unlock_irqrestore(&timers[nodeid].lock, irqflags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
738
739
740
741
742
  
  	preempt_enable();
  
  	return err;
  }
e5e542eea   Thomas Gleixner   posix-timers: Con...
743
744
745
  static int sgi_clock_getres(const clockid_t which_clock, struct timespec *tp)
  {
  	tp->tv_sec = 0;
ebaac757a   Thomas Gleixner   posix-timers: Rem...
746
  	tp->tv_nsec = sgi_clock_period;
e5e542eea   Thomas Gleixner   posix-timers: Con...
747
748
  	return 0;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
749
  static struct k_clock sgi_clock = {
2fd1f0408   Thomas Gleixner   posix-timers: Cle...
750
751
  	.clock_set	= sgi_clock_set,
  	.clock_get	= sgi_clock_get,
e5e542eea   Thomas Gleixner   posix-timers: Con...
752
  	.clock_getres	= sgi_clock_getres,
2fd1f0408   Thomas Gleixner   posix-timers: Cle...
753
  	.timer_create	= sgi_timer_create,
2fd1f0408   Thomas Gleixner   posix-timers: Cle...
754
755
756
  	.timer_set	= sgi_timer_set,
  	.timer_del	= sgi_timer_del,
  	.timer_get	= sgi_timer_get
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
757
758
759
760
761
762
763
764
765
  };
  
  /**
   * mmtimer_init - device initialization routine
   *
   * Does initial setup for the mmtimer device.
   */
  static int __init mmtimer_init(void)
  {
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
766
  	cnodeid_t node, maxn = -1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
767
768
  
  	if (!ia64_platform_is("sn2"))
f032f9080   Bjorn Helgaas   [IA64] SGI SN dri...
769
  		return 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
770
771
772
773
774
775
776
777
  
  	/*
  	 * Sanity check the cycles/sec variable
  	 */
  	if (sn_rtc_cycles_per_second < 100000) {
  		printk(KERN_ERR "%s: unable to determine clock frequency
  ",
  		       MMTIMER_NAME);
5d469ec0f   Neil Horman   [PATCH] Correct m...
778
  		goto out1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
779
780
781
782
  	}
  
  	mmtimer_femtoperiod = ((unsigned long)1E15 + sn_rtc_cycles_per_second /
  			       2) / sn_rtc_cycles_per_second;
0f2ed4c6b   Thomas Gleixner   [PATCH] irq-flags...
783
  	if (request_irq(SGI_MMTIMER_VECTOR, mmtimer_interrupt, IRQF_PERCPU, MMTIMER_NAME, NULL)) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
784
785
  		printk(KERN_WARNING "%s: unable to allocate interrupt.",
  			MMTIMER_NAME);
5d469ec0f   Neil Horman   [PATCH] Correct m...
786
  		goto out1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
787
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
788
789
790
791
  	if (misc_register(&mmtimer_miscdev)) {
  		printk(KERN_ERR "%s: failed to register device
  ",
  		       MMTIMER_NAME);
5d469ec0f   Neil Horman   [PATCH] Correct m...
792
  		goto out2;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
793
  	}
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
794
795
796
797
798
799
800
  	/* Get max numbered node, calculate slots needed */
  	for_each_online_node(node) {
  		maxn = node;
  	}
  	maxn++;
  
  	/* Allocate list of node ptrs to mmtimer_t's */
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
801
  	timers = kzalloc(sizeof(struct mmtimer_node)*maxn, GFP_KERNEL);
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
802
803
804
805
  	if (timers == NULL) {
  		printk(KERN_ERR "%s: failed to allocate memory for device
  ",
  				MMTIMER_NAME);
5d469ec0f   Neil Horman   [PATCH] Correct m...
806
  		goto out3;
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
807
  	}
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
808
  	/* Initialize struct mmtimer's for each online node */
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
809
  	for_each_online_node(node) {
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
810
811
812
  		spin_lock_init(&timers[node].lock);
  		tasklet_init(&timers[node].tasklet, mmtimer_tasklet,
  			(unsigned long) node);
76832c28d   Dimitri Sivanich   [PATCH] shrink mm...
813
  	}
ebaac757a   Thomas Gleixner   posix-timers: Rem...
814
  	sgi_clock_period = NSEC_PER_SEC / sn_rtc_cycles_per_second;
527087374   Thomas Gleixner   posix-timers: Cle...
815
  	posix_timers_register_clock(CLOCK_SGI_CYCLE, &sgi_clock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
816
817
818
819
820
821
  
  	printk(KERN_INFO "%s: v%s, %ld MHz
  ", MMTIMER_DESC, MMTIMER_VERSION,
  	       sn_rtc_cycles_per_second/(unsigned long)1E6);
  
  	return 0;
5d469ec0f   Neil Horman   [PATCH] Correct m...
822

5d469ec0f   Neil Horman   [PATCH] Correct m...
823
  out3:
cbacdd957   Dimitri Sivanich   SGI Altix mmtimer...
824
  	kfree(timers);
5d469ec0f   Neil Horman   [PATCH] Correct m...
825
826
827
828
829
  	misc_deregister(&mmtimer_miscdev);
  out2:
  	free_irq(SGI_MMTIMER_VECTOR, NULL);
  out1:
  	return -1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
830
831
832
  }
  
  module_init(mmtimer_init);