Index: linux-2.6.9/include/linux/time.h =================================================================== --- linux-2.6.9.orig/include/linux/time.h 2004-10-20 19:38:03.000000000 -0700 +++ linux-2.6.9/include/linux/time.h 2004-10-20 20:07:38.000000000 -0700 @@ -160,7 +160,7 @@ /* * The IDs of various hardware clocks */ - +#define CLOCK_SGI_CYCLE 10 #define CLOCK_SGI_CYCLE 10 #define MAX_CLOCKS 16 Index: linux-2.6.9/drivers/char/mmtimer.c =================================================================== --- linux-2.6.9.orig/drivers/char/mmtimer.c 2004-10-20 19:38:01.000000000 -0700 +++ linux-2.6.9/drivers/char/mmtimer.c 2004-10-20 20:23:01.000000000 -0700 @@ -13,6 +13,9 @@ * * 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. */ #include @@ -26,20 +29,23 @@ #include #include #include +#include +#include #include #include #include +#include #include MODULE_AUTHOR("Jesse Barnes "); -MODULE_DESCRIPTION("Multimedia timer support"); +MODULE_DESCRIPTION("SGI Altix RTC Timer"); MODULE_LICENSE("GPL"); /* name of the device, usually in /dev */ #define MMTIMER_NAME "mmtimer" -#define MMTIMER_DESC "IA-PC Multimedia Timer" -#define MMTIMER_VERSION "1.0" +#define MMTIMER_DESC "SGI ALtix RTC Timer" +#define MMTIMER_VERSION "2.0" #define RTC_BITS 55 /* 55 bits for this implementation */ @@ -58,6 +64,171 @@ .ioctl = mmtimer_ioctl, }; +/* + * Comparators and their associated info. Shub has + * three comparison registers. + */ + +/* + * We only have comparison registers RTC1-4 currently available per + * node. RTC0 is used by SAL. + */ +#define NUM_COMPARATORS 3 +/* + * Check for an interrupt and clear the pending bit if + * one is waiting. +*/ +static int inline mmtimer_int_pending(int comparator) { + int pending = 0; + + switch (comparator) { + case 0: + if (HUB_L((unsigned long *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED)) & + SH_EVENT_OCCURRED_RTC1_INT_MASK) { + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED_ALIAS), + SH_EVENT_OCCURRED_RTC1_INT_MASK); + pending = 1; + } + break; + case 1: + if (HUB_L((unsigned long *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED)) & + SH_EVENT_OCCURRED_RTC2_INT_MASK) { + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED_ALIAS), + SH_EVENT_OCCURRED_RTC2_INT_MASK); + pending = 1; + } + break; + case 2: + if (HUB_L((unsigned long *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED)) & + SH_EVENT_OCCURRED_RTC3_INT_MASK) { + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED_ALIAS), + SH_EVENT_OCCURRED_RTC3_INT_MASK); + pending = 1; + } + break; + default: + return -EFAULT; + } + return pending; +} + +static void inline mmtimer_set_comparator(int comparator, unsigned long expires) { + + switch (comparator) { + case 0: + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), expires); + break; + case 1: + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPC), expires); + break; + case 2: + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPD), expires); + } +} + +static void inline mmtimer_set_int_0(long expires) { + u64 val; + + /* Disable interrupt */ + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 0UL); + + val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC1_INT_CONFIG_IDX_SHFT) | + ((u64)cpu_physical_id(smp_processor_id()) << + SH_RTC1_INT_CONFIG_PID_SHFT); + + /* Set configuration */ + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_CONFIG), val); + + /* + * Enable interrupt before setting compare value. + * Doing this after setting the compare value can result + * in an interrupt pending without ever calling the handler + * (probably a timing issue). + */ + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 1UL); + + /* Set compare value */ + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), expires); +} + +static void inline mmtimer_set_int_1(long expires) { + u64 val; + + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 0UL); + + val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC2_INT_CONFIG_IDX_SHFT) | + ((u64)cpu_physical_id(smp_processor_id()) << + 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); +} + +static void inline mmtimer_set_int_2(long expires) { + u64 val; + + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 0UL); + + val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC3_INT_CONFIG_IDX_SHFT) | + ((u64)cpu_physical_id(smp_processor_id()) << + 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); +} + +static int inline mmtimer_set_int(int comparator, long expires) { + switch (comparator) { + case 0: + mmtimer_set_int_0(expires); + break; + case 1: + mmtimer_set_int_1(expires); + break; + case 2: + mmtimer_set_int_2(expires); + break; + default: + return -EFAULT; + } + return 0; +} + +static int inline mmtimer_disable_int(int comparator) { + switch (comparator) { + case 0: + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 0UL); + break; + case 1: + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 0UL); + break; + case 2: + HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 0UL); + break; + default: + return -EFAULT; + } + return 0; +} + +#define TIMER_OFF 0xbadcabLL + +typedef struct mmtimer { + struct k_itimer *timer; + spinlock_t lock; +} mmtimer_t; + +/* + * Total number of comparators is comparators/node * MAX nodes/running kernel + */ +static mmtimer_t timers[NUM_COMPARATORS*MAX_COMPACT_NODES]; + /** * mmtimer_ioctl - ioctl interface for /dev/mmtimer * @inode: inode of the device @@ -183,6 +354,15 @@ static struct timespec sgi_clock_offset; static int sgi_clock_period; +/***** + * Posix Timer Interface + * The posix timer interface provides full support for the posix timer + * interface and allows the scheduling of up to three timers. + */ + +static struct timespec sgi_clock_offset; +static int sgi_clock_period; + static int sgi_clock_get(struct timespec *tp) { u64 nsec; @@ -194,12 +374,12 @@ }; static int sgi_clock_set(struct timespec *tp) { - + u64 nsec; u64 rem; nsec = readq(RTC_COUNTER_ADDR) * sgi_clock_period; - + sgi_clock_offset.tv_sec = tp->tv_sec - div_long_long_rem(nsec, NSEC_PER_SEC, &rem); if (rem <= tp->tv_nsec) @@ -211,12 +391,201 @@ return 0; } +/** + * mmtimer_interrupt - timer interrupt handler + * @irq: irq received + * @dev_id: device the irq came from + * @regs: register state upon receipt of the interrupt + * + * 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 on cpus services + * by the SHUB. It is therefore safe to locally access to SHub + * registers. + */ +static irqreturn_t +mmtimer_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int i; + mmtimer_t *base = timers + cpuid_to_cnodeid(smp_processor_id()) * NUM_COMPARATORS; + int result = IRQ_NONE; + + printk("mmtimer interrupt:\n"); + /* + * Do this once for each comparison register + */ + for (i = 0; i < NUM_COMPARATORS; i++) { + if (mmtimer_int_pending(i)>0) { + struct k_itimer *t = base[i].timer; + unsigned long flags; + + printk("mmtimer: processing int on %d expires=%lu incr=%lu rtc=%lu\n",i,t->it_timer.expires, t->it_incr,readq(RTC_COUNTER_ADDR)); + spin_lock_irqsave(&t->it_lock, flags); + + if (timer_event(t, 0) == 0) { + if(t->it_incr == 0) goto clear_timer; + t->it_timer.expires += t->it_incr; + mmtimer_set_comparator(i, t->it_timer.expires); + } else { + printk(KERN_WARNING "mmtimer unable to deliver signal"); + t->it_overrun++; +clear_timer: + mmtimer_disable_int(i); + t->it_timer.magic = TIMER_OFF; + base[i].timer = NULL; + } + + spin_unlock_irqrestore(&t->it_lock, flags); + result = IRQ_HANDLED; + } + } + return result; +} + +static int sgi_timer_create(struct k_itimer *timer) { + /* Insure that a newly created timer is off */ + timer->it_timer.magic = TIMER_OFF; + return 0; +} + +/* This does not really delete a timer. It just insures + * that the timer is not active + */ +static int sgi_timer_del(struct k_itimer *timr) { + int i = timr->it_timer.magic; + cnodeid_t nodeid = timr->it_timer.data; + mmtimer_t *t = timers + nodeid * NUM_COMPARATORS +i; + + if (i != TIMER_OFF) { + spin_lock(&t->lock); + mmtimer_disable_int(i); + t->timer = NULL; + timr->it_timer.magic = TIMER_OFF; + spin_unlock(&t->lock); + printk("mmtimer: interrupt %d node %d disabled.\n",i, nodeid); + } + return 0; +} + +#define timespec_to_ns(x) ((x).tv_nsec + (x).tv_sec * NSEC_PER_SEC) +#define ns_to_timespec(ts, nsec) (ts).tv_sec = div_long_long_rem(nsec, NSEC_PER_SEC, &(ts).tv_nsec) + +static void sgi_timer_get(struct k_itimer *timr, struct itimerspec *cur_setting) { + + if (timr->it_timer.magic == 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; + } + + ns_to_timespec(cur_setting->it_interval, timr->it_incr * sgi_clock_period); + /* readq is atomic so no locking needed */ + ns_to_timespec(cur_setting->it_value, (timr->it_timer.expires - readq(RTC_COUNTER_ADDR))* sgi_clock_period); + return; +} + + +static int sgi_timer_set(struct k_itimer *timr, int flags, + struct itimerspec * new_setting, + struct itimerspec * old_setting) { + + int i; + unsigned long when, irqflags; + int err = 0; + cnodeid_t nodeid = cpuid_to_cnodeid(smp_processor_id()); + mmtimer_t *base = timers + nodeid * NUM_COMPARATORS; + + if (old_setting) + sgi_timer_get(timr, old_setting); + + sgi_timer_del(timr); + when = timespec_to_ns(new_setting->it_value); + + if (when == 0) + /* Clear timer */ + return 0; + + if (flags & TIMER_ABSTIME) { + struct timespec n; + + getnstimeofday(&n); + when -= timespec_to_ns(n); + } + + /* If the time is less than 100ns, fire now and do not set up */ + if (when < 100) { + timer_event(timr, 0); + return 0; + } + + /* 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(); +retry: + for(i = 0; i< NUM_COMPARATORS; i++) { + if (!base[i].timer) { + break; + } + } + + if (i == NUM_COMPARATORS) { + preempt_enable(); + return -EBUSY; + } + + /* Avoid someone else using this timer by locking .. */ + spin_lock_irqsave(&base[i].lock, irqflags); + + if (base[i].timer) { + spin_unlock_irqrestore(&base[i].lock, irqflags); + goto retry; + } + + /* Check once more in case we got held off by another IRQ */ + if (when < 100) { + spin_unlock_irqrestore(&base[i].lock, irqflags); + preempt_enable(); + timer_event(timr, 0); + return 0; + } + + timr->it_timer.magic = i; + timr->it_timer.data = nodeid; + timr->it_incr = timespec_to_ns(new_setting->it_interval) / sgi_clock_period; + + if (timr->it_incr && timr->it_incr < 3) { + err = -EINVAL; + goto out; + } + + timr->it_timer.expires = when / sgi_clock_period + readq(RTC_COUNTER_ADDR); + base[i].timer = timr; + if (mmtimer_set_int(i, timr->it_timer.expires)) { + err = -EINVAL; + } +out: + spin_unlock_irqrestore(&base[i].lock, irqflags); + preempt_enable(); + printk("mmtimer enabled %d. Comparator=%ld. RTC=%ld\n",i, timr->it_timer.expires,readq(RTC_COUNTER_ADDR)); + + return err; +} + static struct k_clock sgi_clock = { .res = 0, .clock_set = sgi_clock_set, .clock_get = sgi_clock_get, - .timer_create = do_posix_clock_notimer_create, - .nsleep = do_posix_clock_nonanosleep + .timer_create = sgi_timer_create, + .nsleep = do_posix_clock_nonanosleep, + .timer_set = sgi_timer_set, + .timer_del = sgi_timer_del, + .timer_get = sgi_timer_get }; /** @@ -226,6 +595,8 @@ */ static int __init mmtimer_init(void) { + unsigned i; + if (!ia64_platform_is("sn2")) return -1; @@ -241,6 +612,17 @@ mmtimer_femtoperiod = ((unsigned long)1E15 + sn_rtc_cycles_per_second / 2) / sn_rtc_cycles_per_second; + if (request_irq(SGI_MMTIMER_VECTOR, mmtimer_interrupt, SA_PERCPU_IRQ, MMTIMER_NAME, NULL)) { + printk(KERN_WARNING "%s: unable to allocate interrupt.", + MMTIMER_NAME); + return -1; + } + + for (i=0; i< NUM_COMPARATORS*MAX_COMPACT_NODES; i++) { + spin_lock_init(&timers[i].lock); + timers[i].timer = NULL; + } + strcpy(mmtimer_miscdev.devfs_name, MMTIMER_NAME); if (misc_register(&mmtimer_miscdev)) { printk(KERN_ERR "%s: failed to register device\n", @@ -251,6 +633,9 @@ sgi_clock_period = sgi_clock.res = NSEC_PER_SEC / sn_rtc_cycles_per_second; register_posix_clock(CLOCK_SGI_CYCLE, &sgi_clock); + sgi_clock_period = sgi_clock.res = NSEC_PER_SEC / sn_rtc_cycles_per_second; + register_posix_clock(CLOCK_SGI_CYCLE, &sgi_clock); + printk(KERN_INFO "%s: v%s, %ld MHz\n", MMTIMER_DESC, MMTIMER_VERSION, sn_rtc_cycles_per_second/(unsigned long)1E6); Index: linux-2.6.9/include/asm/sn/intr.h =================================================================== --- linux-2.6.9.orig/include/asm/sn/intr.h 2004-10-20 19:38:03.000000000 -0700 +++ linux-2.6.9/include/asm/sn/intr.h 2004-10-20 20:21:32.000000000 -0700 @@ -11,6 +11,7 @@ #define SGI_UART_VECTOR (0xe9) #define SGI_PCIBR_ERROR (0x33) +#define SGI_MMTIMER_VECTOR (0xed) // These two IRQ's are used by partitioning. #define SGI_XPC_ACTIVATE (0x30)