The following patches provide a driver for synchronized RTC clocksource and
clockevents for SGI systems, as well as a generic system interrupt.
With these patches, a module can be installed that registers the system-wide
synchronized RTC clocksource and timers as both a clocksource and clockevents
device running in high resolution mode.
Subject: [PATCH 1/6 v6] SGI RTC: add generic system interrupt
Subject: [PATCH 2/6 v6] SGI RTC: export clocksource_unregister
Subject: [PATCH 3/6 v6] SGI RTC: export clockevents_register_device
Subject: [PATCH 4/6 v6] SGI RTC: export schedule_on_each_cpu
Subject: [PATCH 5/6 v6] SGI RTC: loop through installed UV blades
Subject: [PATCH 6/6 v6] SGI RTC: add clocksource driver
This patch allocates a system interrupt vector for various platform
specific uses.
Signed-off-by: Dimitri Sivanich <[email protected]>
---
Made changes suggested by tglx and refreshed for latest -tip.
arch/x86/include/asm/hardirq.h | 1
arch/x86/include/asm/hw_irq.h | 3 +
arch/x86/include/asm/irq.h | 9 ++++
arch/x86/include/asm/irq_vectors.h | 5 ++
arch/x86/kernel/entry_64.S | 2
arch/x86/kernel/irq.c | 12 +++++
arch/x86/kernel/irqinit_64.c | 74 +++++++++++++++++++++++++++++++++
7 files changed, 106 insertions(+)
Index: linux/arch/x86/kernel/entry_64.S
===================================================================
--- linux.orig/arch/x86/kernel/entry_64.S 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/kernel/entry_64.S 2009-03-02 08:20:52.000000000 -0600
@@ -984,6 +984,8 @@ apicinterrupt UV_BAU_MESSAGE \
#endif
apicinterrupt LOCAL_TIMER_VECTOR \
apic_timer_interrupt smp_apic_timer_interrupt
+apicinterrupt GENERIC_INTERRUPT_VECTOR \
+ generic_interrupt smp_generic_interrupt
#ifdef CONFIG_SMP
apicinterrupt INVALIDATE_TLB_VECTOR_START+0 \
Index: linux/arch/x86/kernel/irqinit_64.c
===================================================================
--- linux.orig/arch/x86/kernel/irqinit_64.c 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/kernel/irqinit_64.c 2009-03-02 08:20:52.000000000 -0600
@@ -22,6 +22,7 @@
#include <asm/desc.h>
#include <asm/apic.h>
#include <asm/i8259.h>
+#include <asm/idle.h>
/*
* ISA PIC or low IO-APIC triggered (INTA-cycle or APIC) interrupts:
@@ -105,6 +106,76 @@ static void __init init_ISA_irqs(void)
void init_IRQ(void) __attribute__((weak, alias("native_init_IRQ")));
+
+/* Function pointer for generic interrupt vector handling */
+static void (*generic_interrupt_extension)(void);
+static char generic_show_string[28];
+static char generic_show_prefix[6];
+
+int is_generic_interrupt_registered()
+{
+ if (generic_interrupt_extension)
+ return 1;
+ else
+ return 0;
+}
+
+char *generic_interrupt_string(void)
+{
+ return generic_show_string;
+}
+
+char *generic_interrupt_prefix(void)
+{
+ return generic_show_prefix;
+}
+
+int register_generic_interrupt_extension(void (*fn)(void),
+ const char *show_prefix,
+ const char *show_string)
+{
+ if (generic_interrupt_extension)
+ return -EBUSY;
+
+ snprintf(generic_show_prefix, sizeof(generic_show_prefix),
+ "%3.3s: ", show_prefix ? show_prefix : "GEN");
+
+ snprintf(generic_show_string, sizeof(generic_show_string),
+ " %s", show_string ? show_string : "Generic interrupts");
+
+ generic_interrupt_extension = fn;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(register_generic_interrupt_extension);
+
+void unregister_generic_interrupt_extension(void)
+{
+ if (generic_interrupt_extension)
+ generic_interrupt_extension = NULL;
+}
+EXPORT_SYMBOL_GPL(unregister_generic_interrupt_extension);
+
+void smp_generic_interrupt(struct pt_regs *regs)
+{
+ struct pt_regs *old_regs = set_irq_regs(regs);
+
+ ack_APIC_irq();
+
+ exit_idle();
+
+ irq_enter();
+
+ inc_irq_stat(generic_irqs);
+
+ if (generic_interrupt_extension)
+ generic_interrupt_extension();
+
+ irq_exit();
+
+ set_irq_regs(old_regs);
+}
+
static void __init smp_intr_init(void)
{
#ifdef CONFIG_SMP
@@ -147,6 +218,9 @@ static void __init apic_intr_init(void)
/* self generated IPI for local APIC timer */
alloc_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt);
+ /* generic IPI for platform specific use */
+ alloc_intr_gate(GENERIC_INTERRUPT_VECTOR, generic_interrupt);
+
/* IPI vectors for APIC spurious and error interrupts */
alloc_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt);
alloc_intr_gate(ERROR_APIC_VECTOR, error_interrupt);
Index: linux/arch/x86/include/asm/irq_vectors.h
===================================================================
--- linux.orig/arch/x86/include/asm/irq_vectors.h 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/include/asm/irq_vectors.h 2009-03-02 08:20:52.000000000 -0600
@@ -112,6 +112,11 @@
#define LOCAL_PERF_VECTOR 0xee
/*
+ * Generic system vector for platform specific use
+ */
+#define GENERIC_INTERRUPT_VECTOR 0xed
+
+/*
* First APIC vector available to drivers: (vectors 0x30-0xee) we
* start at 0x31(0x41) to spread out vectors evenly between priority
* levels. (0x80 is the syscall vector)
Index: linux/arch/x86/include/asm/hw_irq.h
===================================================================
--- linux.orig/arch/x86/include/asm/hw_irq.h 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/include/asm/hw_irq.h 2009-03-02 08:20:52.000000000 -0600
@@ -27,6 +27,9 @@
/* Interrupt handlers registered during init_IRQ */
extern void apic_timer_interrupt(void);
+#ifdef CONFIG_X86_64
+extern void generic_interrupt(void);
+#endif
extern void error_interrupt(void);
extern void perf_counter_interrupt(void);
Index: linux/arch/x86/include/asm/irq.h
===================================================================
--- linux.orig/arch/x86/include/asm/irq.h 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/include/asm/irq.h 2009-03-02 08:20:52.000000000 -0600
@@ -42,6 +42,15 @@ extern bool handle_irq(unsigned irq, str
extern unsigned int do_IRQ(struct pt_regs *regs);
+#ifdef CONFIG_X86_64
+extern int is_generic_interrupt_registered(void);
+extern char *generic_interrupt_string(void);
+extern char *generic_interrupt_prefix(void);
+extern int register_generic_interrupt_extension(void (*fn)(void), const char *,
+ const char *);
+extern void unregister_generic_interrupt_extension(void);
+#endif
+
/* Interrupt vector management */
extern DECLARE_BITMAP(used_vectors, NR_VECTORS);
extern int vector_used_by_percpu_irq(unsigned int vector);
Index: linux/arch/x86/kernel/irq.c
===================================================================
--- linux.orig/arch/x86/kernel/irq.c 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/kernel/irq.c 2009-03-02 08:20:52.000000000 -0600
@@ -60,6 +60,14 @@ static int show_other_interrupts(struct
seq_printf(p, "%10u ", irq_stats(j)->apic_perf_irqs);
seq_printf(p, " Performance counter interrupts\n");
#endif
+#ifdef CONFIG_X86_64
+ if (is_generic_interrupt_registered()) {
+ seq_printf(p, "%s", generic_interrupt_prefix());
+ for_each_online_cpu(j)
+ seq_printf(p, "%10u ", irq_stats(j)->generic_irqs);
+ seq_printf(p, "%s\n", generic_interrupt_string());
+ }
+#endif
#ifdef CONFIG_SMP
seq_printf(p, "RES: ");
for_each_online_cpu(j)
@@ -168,6 +176,10 @@ u64 arch_irq_stat_cpu(unsigned int cpu)
sum += irq_stats(cpu)->apic_timer_irqs;
sum += irq_stats(cpu)->apic_perf_irqs;
#endif
+#ifdef CONFIG_X86_64
+ if (is_generic_interrupt_registered())
+ sum += irq_stats(cpu)->generic_irqs;
+#endif
#ifdef CONFIG_SMP
sum += irq_stats(cpu)->irq_resched_count;
sum += irq_stats(cpu)->irq_call_count;
Index: linux/arch/x86/include/asm/hardirq.h
===================================================================
--- linux.orig/arch/x86/include/asm/hardirq.h 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/include/asm/hardirq.h 2009-03-02 08:20:52.000000000 -0600
@@ -22,6 +22,7 @@ typedef struct {
unsigned int irq_thermal_count;
# ifdef CONFIG_X86_64
unsigned int irq_threshold_count;
+ unsigned int generic_irqs;
# endif
#endif
} ____cacheline_aligned irq_cpustat_t;
Export clocksource_unregister().
Signed-off-by: Dimitri Sivanich <[email protected]>
---
kernel/time/clocksource.c | 1 +
1 file changed, 1 insertion(+)
Index: linux/kernel/time/clocksource.c
===================================================================
--- linux.orig/kernel/time/clocksource.c 2009-03-02 08:20:32.000000000 -0600
+++ linux/kernel/time/clocksource.c 2009-03-02 08:20:53.000000000 -0600
@@ -370,6 +370,7 @@ void clocksource_unregister(struct clock
next_clocksource = select_clocksource();
spin_unlock_irqrestore(&clocksource_lock, flags);
}
+EXPORT_SYMBOL_GPL(clocksource_unregister);
#ifdef CONFIG_SYSFS
/**
Export clockevents_register_device().
Signed-off-by: Dimitri Sivanich <[email protected]>
---
kernel/time/clockevents.c | 1 +
1 file changed, 1 insertion(+)
Index: linux/kernel/time/clockevents.c
===================================================================
--- linux.orig/kernel/time/clockevents.c 2009-03-02 08:20:32.000000000 -0600
+++ linux/kernel/time/clockevents.c 2009-03-02 08:20:56.000000000 -0600
@@ -187,6 +187,7 @@ void clockevents_register_device(struct
spin_unlock(&clockevents_lock);
}
+EXPORT_SYMBOL_GPL(clockevents_register_device);
/*
* Noop handler when we shut down an event device
Export schedule_on_each_cpu().
Signed-off-by: Dimitri Sivanich <[email protected]>
---
kernel/workqueue.c | 1 +
1 file changed, 1 insertion(+)
Index: linux/kernel/workqueue.c
===================================================================
--- linux.orig/kernel/workqueue.c 2009-03-02 08:20:32.000000000 -0600
+++ linux/kernel/workqueue.c 2009-03-02 08:20:57.000000000 -0600
@@ -705,6 +705,7 @@ int schedule_on_each_cpu(work_func_t fun
free_percpu(works);
return 0;
}
+EXPORT_SYMBOL_GPL(schedule_on_each_cpu);
void flush_scheduled_work(void)
{
Add macro to loop through each possible blade.
Signed-off-by: Dimitri Sivanich <[email protected]>
---
arch/x86/include/asm/uv/uv_hub.h | 4 ++++
1 file changed, 4 insertions(+)
Index: linux/arch/x86/include/asm/uv/uv_hub.h
===================================================================
--- linux.orig/arch/x86/include/asm/uv/uv_hub.h 2009-03-02 08:20:20.000000000 -0600
+++ linux/arch/x86/include/asm/uv/uv_hub.h 2009-03-02 08:20:57.000000000 -0600
@@ -179,6 +179,10 @@ DECLARE_PER_CPU(struct uv_hub_info_s, __
#define LOCAL_BUS_BASE 0x1c00000
#define LOCAL_BUS_SIZE (4 * 1024 * 1024)
+/* Loop through all installed blades */
+#define for_each_possible_blade(bid) \
+ for ((bid) = 0; (bid) < uv_num_possible_blades(); (bid)++)
+
/*
* System Controller Interface Reg
*
This patch provides a driver for SGI RTC clocks and timers.
This provides a high resolution clock and timer source using the SGI
system-wide synchronized RTC clock/timer hardware.
Signed-off-by: Dimitri Sivanich <[email protected]>
---
Implemented suggested changes and refreshed for latest -tip.
drivers/clocksource/Makefile | 1
drivers/clocksource/rtc_uv.c | 381 +++++++++++++++++++++++++++++++++++++++
drivers/misc/Kconfig | 9
3 files changed, 391 insertions(+)
Index: linux/drivers/clocksource/Makefile
===================================================================
--- linux.orig/drivers/clocksource/Makefile 2009-03-02 15:02:55.000000000 -0600
+++ linux/drivers/clocksource/Makefile 2009-03-02 15:03:11.000000000 -0600
@@ -2,3 +2,4 @@ obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_cl
obj-$(CONFIG_X86_CYCLONE_TIMER) += cyclone.o
obj-$(CONFIG_X86_PM_TIMER) += acpi_pm.o
obj-$(CONFIG_SCx200HR_TIMER) += scx200_hrt.o
+obj-$(CONFIG_SGI_RTC_TIMER) += rtc_uv.o
Index: linux/drivers/misc/Kconfig
===================================================================
--- linux.orig/drivers/misc/Kconfig 2009-03-02 15:02:55.000000000 -0600
+++ linux/drivers/misc/Kconfig 2009-03-02 15:03:11.000000000 -0600
@@ -225,6 +225,15 @@ config DELL_LAPTOP
This driver adds support for rfkill and backlight control to Dell
laptops.
+config SGI_RTC_TIMER
+ tristate "SGI RTC driver"
+ depends on GENERIC_CLOCKEVENTS_BUILD
+ depends on X86_UV && SMP
+ default m
+ ---help---
+ This option enables RTC event handling and adds the systemwide RTC
+ as a high resolution clock source for SGI systems.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
Index: linux/drivers/clocksource/rtc_uv.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux/drivers/clocksource/rtc_uv.c 2009-03-02 15:16:17.000000000 -0600
@@ -0,0 +1,381 @@
+/*
+ * SGI RTC clock/timer routines.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Copyright (c) 2009 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) Dimitri Sivanich
+ */
+#include <linux/module.h>
+#include <linux/clocksource.h>
+#include <linux/clockchips.h>
+#include <asm/genapic.h>
+#include <asm/uv/uv.h>
+#include <asm/uv/uv_mmrs.h>
+#include <asm/uv/uv_hub.h>
+#include <asm/uv/bios.h>
+
+MODULE_LICENSE("GPL");
+
+#define RTC_NAME "sgi_rtc"
+
+static cycle_t uv_read_rtc(void);
+static int uv_rtc_next_event(unsigned long, struct clock_event_device *);
+static void uv_rtc_timer_setup(enum clock_event_mode,
+ struct clock_event_device *);
+
+static struct clocksource clocksource_uv = {
+ .name = RTC_NAME,
+ .rating = 400,
+ .read = uv_read_rtc,
+ .mask = (cycle_t)UVH_RTC_REAL_TIME_CLOCK_MASK,
+ .shift = 10,
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static struct clock_event_device clock_event_device_uv = {
+ .name = RTC_NAME,
+ .features = CLOCK_EVT_FEAT_ONESHOT,
+ .shift = 20,
+ .rating = 400,
+ .irq = -1,
+ .set_next_event = uv_rtc_next_event,
+ .set_mode = uv_rtc_timer_setup,
+ .event_handler = NULL,
+};
+
+static DEFINE_PER_CPU(struct clock_event_device, cpu_ced);
+
+/* There is one of these allocated per node */
+struct uv_rtc_timer_head {
+ spinlock_t lock;
+ int next_cpu; /* next cpu waiting for timer, local node relative */
+ int ncpus; /* number of cpus on this node */
+ struct {
+ int lcpu; /* systemwide logical cpu number */
+ u64 expires; /* next timer expiration for this cpu */
+ } cpu[1];
+};
+
+/*
+ * Access to uv_rtc_timer_head via blade id.
+ */
+static struct uv_rtc_timer_head **blade_info __read_mostly;
+
+/*
+ * Hardware interface routines
+ */
+
+/* Send IPIs to another node */
+static void uv_rtc_send_IPI(int cpu)
+{
+ unsigned long apicid, val;
+ int pnode;
+
+ apicid = per_cpu(x86_cpu_to_apicid, cpu);
+ pnode = uv_apicid_to_pnode(apicid);
+ val = (1UL << UVH_IPI_INT_SEND_SHFT) |
+ (apicid << UVH_IPI_INT_APIC_ID_SHFT) |
+ (GENERIC_INTERRUPT_VECTOR << UVH_IPI_INT_VECTOR_SHFT);
+
+ uv_write_global_mmr64(pnode, UVH_IPI_INT, val);
+}
+
+/* Check for an RTC interrupt pending */
+static int uv_intr_pending(int pnode)
+{
+ return uv_read_global_mmr64(pnode, UVH_EVENT_OCCURRED0) &
+ UVH_EVENT_OCCURRED0_RTC1_MASK;
+}
+
+/* Setup interrupt and return non-zero if early expiration occurred. */
+static int uv_setup_intr(int cpu, u64 expires)
+{
+ u64 val;
+ int pnode = uv_cpu_to_pnode(cpu);
+
+ uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG,
+ UVH_RTC1_INT_CONFIG_M_MASK);
+ uv_write_global_mmr64(pnode, UVH_INT_CMPB, -1L);
+
+ uv_write_global_mmr64(pnode, UVH_EVENT_OCCURRED0_ALIAS,
+ UVH_EVENT_OCCURRED0_RTC1_MASK);
+
+ val = (GENERIC_INTERRUPT_VECTOR << UVH_RTC1_INT_CONFIG_VECTOR_SHFT) |
+ ((u64)cpu_physical_id(cpu) << UVH_RTC1_INT_CONFIG_APIC_ID_SHFT);
+
+ /* Set configuration */
+ uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG, val);
+ /* Initialize comparator value */
+ uv_write_global_mmr64(pnode, UVH_INT_CMPB, expires);
+
+ return (expires < uv_read_rtc() && !uv_intr_pending(pnode));
+}
+
+/*
+ * Per-cpu timer tracking routines
+ */
+
+static __init void uv_rtc_deallocate_timers(void)
+{
+ int bid;
+
+ for_each_possible_blade(bid) {
+ kfree(blade_info[bid]);
+ }
+ kfree(blade_info);
+}
+
+/* Allocate per-node list of cpu timer expiration times. */
+static __init int uv_rtc_allocate_timers(void)
+{
+ int cpu;
+
+ blade_info = kmalloc(uv_possible_blades * sizeof(void *), GFP_KERNEL);
+ if (!blade_info)
+ return -ENOMEM;
+ memset(blade_info, 0, uv_possible_blades * sizeof(void *));
+
+ for_each_present_cpu(cpu) {
+ int nid = cpu_to_node(cpu);
+ int bid = uv_cpu_to_blade_id(cpu);
+ int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
+ struct uv_rtc_timer_head *head = blade_info[bid];
+
+ if (!head) {
+ head = kmalloc_node(sizeof(struct uv_rtc_timer_head) +
+ (uv_blade_nr_possible_cpus(bid) *
+ 2 * sizeof(u64)),
+ GFP_KERNEL, nid);
+ if (!head) {
+ uv_rtc_deallocate_timers();
+ return -ENOMEM;
+ }
+ spin_lock_init(&head->lock);
+ head->ncpus = uv_blade_nr_possible_cpus(bid);
+ head->next_cpu = -1;
+ blade_info[bid] = head;
+ }
+
+ head->cpu[bcpu].lcpu = cpu;
+ head->cpu[bcpu].expires = ULLONG_MAX;
+ }
+
+ return 0;
+}
+
+/* Find and set the next expiring timer. */
+static void uv_rtc_find_next_timer(struct uv_rtc_timer_head *head, int pnode)
+{
+ u64 lowest = ULLONG_MAX;
+ int c, bcpu = -1;
+
+ head->next_cpu = -1;
+ for (c = 0; c < head->ncpus; c++) {
+ u64 exp = head->cpu[c].expires;
+ if (exp < lowest) {
+ bcpu = c;
+ lowest = exp;
+ }
+ }
+ if (bcpu >= 0) {
+ head->next_cpu = bcpu;
+ c = head->cpu[bcpu].lcpu;
+ if (uv_setup_intr(c, lowest))
+ /* If we didn't set it up in time, trigger */
+ uv_rtc_send_IPI(c);
+ } else
+ uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG,
+ UVH_RTC1_INT_CONFIG_M_MASK);
+}
+
+/*
+ * Set expiration time for current cpu.
+ *
+ * Returns 1 if we missed the expiration time.
+ */
+static int uv_rtc_set_timer(int cpu, u64 expires)
+{
+ int pnode = uv_cpu_to_pnode(cpu);
+ int bid = uv_cpu_to_blade_id(cpu);
+ struct uv_rtc_timer_head *head = blade_info[bid];
+ int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
+ u64 *t = &head->cpu[bcpu].expires;
+ unsigned long flags;
+ int next_cpu;
+
+ spin_lock_irqsave(&head->lock, flags);
+
+ next_cpu = head->next_cpu;
+ *t = expires;
+ /* Will this one be next to go off? */
+ if (next_cpu < 0 || bcpu == next_cpu ||
+ expires < head->cpu[next_cpu].expires) {
+ head->next_cpu = bcpu;
+ if (uv_setup_intr(cpu, expires)) {
+ *t = ULLONG_MAX;
+ uv_rtc_find_next_timer(head, pnode);
+ spin_unlock_irqrestore(&head->lock, flags);
+ return 1;
+ }
+ }
+
+ spin_unlock_irqrestore(&head->lock, flags);
+ return 0;
+}
+
+/*
+ * Unset expiration time for current cpu.
+ *
+ * Returns 1 if this timer was pending.
+ */
+static int uv_rtc_unset_timer(int cpu)
+{
+ int pnode = uv_cpu_to_pnode(cpu);
+ int bid = uv_cpu_to_blade_id(cpu);
+ struct uv_rtc_timer_head *head = blade_info[bid];
+ int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
+ u64 *t = &head->cpu[bcpu].expires;
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&head->lock, flags);
+
+ if (head->next_cpu == bcpu && uv_read_rtc() >= *t)
+ rc = 1;
+
+ *t = ULLONG_MAX;
+
+ /* Was the hardware setup for this timer? */
+ if (head->next_cpu == bcpu)
+ uv_rtc_find_next_timer(head, pnode);
+
+ spin_unlock_irqrestore(&head->lock, flags);
+ return rc;
+}
+
+
+/*
+ * Kernel interface routines.
+ */
+
+/*
+ * Read the RTC.
+ */
+static cycle_t uv_read_rtc(void)
+{
+ return (cycle_t)uv_read_local_mmr(UVH_RTC);
+}
+
+/*
+ * Program the next event, relative to now
+ */
+static int uv_rtc_next_event(unsigned long delta,
+ struct clock_event_device *ced)
+{
+ int ced_cpu = cpumask_first(ced->cpumask);
+
+ return uv_rtc_set_timer(ced_cpu, delta + uv_read_rtc());
+}
+
+/*
+ * Setup the RTC timer in oneshot mode
+ */
+static void uv_rtc_timer_setup(enum clock_event_mode mode,
+ struct clock_event_device *evt)
+{
+ int ced_cpu = cpumask_first(evt->cpumask);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ case CLOCK_EVT_MODE_ONESHOT:
+ case CLOCK_EVT_MODE_RESUME:
+ /* Nothing to do here yet */
+ break;
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ uv_rtc_unset_timer(ced_cpu);
+ break;
+ }
+}
+
+static void uv_rtc_interrupt(void)
+{
+ struct clock_event_device *ced = &__get_cpu_var(cpu_ced);
+ int cpu = smp_processor_id();
+
+ if (!ced || !ced->event_handler)
+ return;
+
+ if (uv_rtc_unset_timer(cpu) != 1)
+ return;
+
+ ced->event_handler(ced);
+}
+
+static __init void uv_rtc_register_clockevents(struct work_struct *dummy)
+{
+ struct clock_event_device *ced = &__get_cpu_var(cpu_ced);
+
+ *ced = clock_event_device_uv;
+ ced->cpumask = cpumask_of(smp_processor_id());
+ clockevents_register_device(ced);
+}
+
+static __init int uv_rtc_setup_clock(void)
+{
+ int rc;
+
+ if (!is_uv_system() ||
+ register_generic_interrupt_extension(uv_rtc_interrupt, "UVT",
+ "UV Timer interrupts"))
+ return -ENODEV;
+
+ clocksource_uv.mult = clocksource_hz2mult(sn_rtc_cycles_per_second,
+ clocksource_uv.shift);
+
+ rc = clocksource_register(&clocksource_uv);
+ if (rc) {
+ unregister_generic_interrupt_extension();
+ return rc;
+ }
+
+ /* Setup and register clockevents */
+ rc = uv_rtc_allocate_timers();
+ if (rc) {
+ clocksource_unregister(&clocksource_uv);
+ unregister_generic_interrupt_extension();
+ return rc;
+ }
+
+ clock_event_device_uv.mult = div_sc(sn_rtc_cycles_per_second,
+ NSEC_PER_SEC, clock_event_device_uv.shift);
+
+ clock_event_device_uv.min_delta_ns = NSEC_PER_SEC /
+ sn_rtc_cycles_per_second;
+
+ clock_event_device_uv.max_delta_ns = clocksource_uv.mask *
+ (NSEC_PER_SEC / sn_rtc_cycles_per_second);
+
+ rc = schedule_on_each_cpu(uv_rtc_register_clockevents);
+ if (rc) {
+ clocksource_unregister(&clocksource_uv);
+ unregister_generic_interrupt_extension();
+ uv_rtc_deallocate_timers();
+ }
+
+ return rc;
+}
+module_init(uv_rtc_setup_clock);
* Dimitri Sivanich <[email protected]> wrote:
> This patch allocates a system interrupt vector for various
> platform specific uses.
this is really ugly.
Also, why are all these symbols exported? There's no need to
build the UV RTC driver as a module. It's either built-in or not
built-in - it's small enough.
this stuff:
> +/* Function pointer for generic interrupt vector handling */
> +static void (*generic_interrupt_extension)(void);
> +static char generic_show_string[28];
> +static char generic_show_prefix[6];
> +
> +int is_generic_interrupt_registered()
> +{
> + if (generic_interrupt_extension)
> + return 1;
> + else
> + return 0;
> +}
> +
> +char *generic_interrupt_string(void)
> +{
> + return generic_show_string;
> +}
> +
> +char *generic_interrupt_prefix(void)
> +{
> + return generic_show_prefix;
> +}
is SMP unsafe, etc. etc. - not something we should ever call
from a module. We just shouldnt do it in this form. What
necessiates it?
All we need is:
> + /* generic IPI for platform specific use */
> + alloc_intr_gate(GENERIC_INTERRUPT_VECTOR, generic_interrupt);
plus one trivial callback function - and then the UV platform
uses it for its own purpose. It's not like two platforms will be
running at once so there's no locking needed, etc.
> +#ifdef CONFIG_X86_64
> +extern void generic_interrupt(void);
> +#endif
in any case please make it symmetric across 32-bit and 64-bit -
even though UV is 64-bit only.
Ingo
* Dimitri Sivanich <[email protected]> wrote:
> drivers/clocksource/Makefile | 1
> drivers/clocksource/rtc_uv.c | 381 +++++++++++++++++++++++++++++++++++++++
> drivers/misc/Kconfig | 9
btw., since this is a platform driver, i'd suggest to move it to
arch/x86/kernel/uv_time.c or so.
Ingo
On Tue, Mar 03, 2009 at 04:34:33PM +0100, Ingo Molnar wrote:
>
> * Dimitri Sivanich <[email protected]> wrote:
>
> > This patch allocates a system interrupt vector for various
> > platform specific uses.
>
> this is really ugly.
>
> Also, why are all these symbols exported? There's no need to
> build the UV RTC driver as a module. It's either built-in or not
> built-in - it's small enough.
OK.
>
> this stuff:
>
> > +/* Function pointer for generic interrupt vector handling */
> > +static void (*generic_interrupt_extension)(void);
> > +static char generic_show_string[28];
> > +static char generic_show_prefix[6];
> > +
> > +int is_generic_interrupt_registered()
> > +{
> > + if (generic_interrupt_extension)
> > + return 1;
> > + else
> > + return 0;
> > +}
> > +
> > +char *generic_interrupt_string(void)
> > +{
> > + return generic_show_string;
> > +}
> > +
> > +char *generic_interrupt_prefix(void)
> > +{
> > + return generic_show_prefix;
> > +}
>
> is SMP unsafe, etc. etc. - not something we should ever call
> from a module. We just shouldnt do it in this form. What
> necessiates it?
I wanted a way to show specific strings when displaying irq statistics. We could show these in a more generic way I suppose.
Any given platform should just be changing these once, hence the lack of need for locking.
>
> All we need is:
>
> > + /* generic IPI for platform specific use */
> > + alloc_intr_gate(GENERIC_INTERRUPT_VECTOR, generic_interrupt);
>
> plus one trivial callback function - and then the UV platform
> uses it for its own purpose. It's not like two platforms will be
> running at once so there's no locking needed, etc.
Right. But still register the callback function as I have it now?
>
> > +#ifdef CONFIG_X86_64
> > +extern void generic_interrupt(void);
> > +#endif
>
> in any case please make it symmetric across 32-bit and 64-bit -
> even though UV is 64-bit only.
OK.
>
> Ingo
* Dimitri Sivanich <[email protected]> wrote:
> On Tue, Mar 03, 2009 at 04:34:33PM +0100, Ingo Molnar wrote:
> >
> > * Dimitri Sivanich <[email protected]> wrote:
> >
> > > This patch allocates a system interrupt vector for various
> > > platform specific uses.
> >
> > this is really ugly.
> >
> > Also, why are all these symbols exported? There's no need to
> > build the UV RTC driver as a module. It's either built-in or not
> > built-in - it's small enough.
>
> OK.
>
> >
> > this stuff:
> >
> > > +/* Function pointer for generic interrupt vector handling */
> > > +static void (*generic_interrupt_extension)(void);
> > > +static char generic_show_string[28];
> > > +static char generic_show_prefix[6];
> > > +
> > > +int is_generic_interrupt_registered()
> > > +{
> > > + if (generic_interrupt_extension)
> > > + return 1;
> > > + else
> > > + return 0;
> > > +}
> > > +
> > > +char *generic_interrupt_string(void)
> > > +{
> > > + return generic_show_string;
> > > +}
> > > +
> > > +char *generic_interrupt_prefix(void)
> > > +{
> > > + return generic_show_prefix;
> > > +}
> >
> > is SMP unsafe, etc. etc. - not something we should ever call
> > from a module. We just shouldnt do it in this form. What
> > necessiates it?
>
> I wanted a way to show specific strings when displaying irq statistics. We could show these in a more generic way I suppose.
>
> Any given platform should just be changing these once, hence the lack of need for locking.
>
> >
> > All we need is:
> >
> > > + /* generic IPI for platform specific use */
> > > + alloc_intr_gate(GENERIC_INTERRUPT_VECTOR, generic_interrupt);
> >
> > plus one trivial callback function - and then the UV platform
> > uses it for its own purpose. It's not like two platforms will be
> > running at once so there's no locking needed, etc.
>
> Right. But still register the callback function as I have it
> now?
i'd suggest to just override that global function pointer from
the UV detection routines.
this way we have it in a minimalistically generic fashion, but
with a minimum amount of fuss around it.
Ingo
On Tue, 2009-03-03 at 09:22 -0600, Dimitri Sivanich wrote:
> This patch provides a driver for SGI RTC clocks and timers.
>
> This provides a high resolution clock and timer source using the SGI
> system-wide synchronized RTC clock/timer hardware.
>
> Signed-off-by: Dimitri Sivanich <[email protected]>
>
> ---
>
> Implemented suggested changes and refreshed for latest -tip.
>
> drivers/clocksource/Makefile | 1
> drivers/clocksource/rtc_uv.c | 381 +++++++++++++++++++++++++++++++++++++++
> drivers/misc/Kconfig | 9
> 3 files changed, 391 insertions(+)
[snip]
> +
> +static struct clocksource clocksource_uv = {
> + .name = RTC_NAME,
> + .rating = 400,
> + .read = uv_read_rtc,
> + .mask = (cycle_t)UVH_RTC_REAL_TIME_CLOCK_MASK,
> + .shift = 10,
What is the expected value for sn_rtc_cycles_per_second ?
I ask because a shift value of 10 seems a bit low. It could very well be
appropriate, but I just wanted to check.
thanks
-john
On Tue, Mar 03, 2009 at 12:01:32PM -0800, john stultz wrote:
> On Tue, 2009-03-03 at 09:22 -0600, Dimitri Sivanich wrote:
> > +
> > +static struct clocksource clocksource_uv = {
> > + .name = RTC_NAME,
> > + .rating = 400,
> > + .read = uv_read_rtc,
> > + .mask = (cycle_t)UVH_RTC_REAL_TIME_CLOCK_MASK,
> > + .shift = 10,
>
>
> What is the expected value for sn_rtc_cycles_per_second ?
>
> I ask because a shift value of 10 seems a bit low. It could very well be
> appropriate, but I just wanted to check.
>
Likely in the range of 20,000,000 - 40,000,000.
Ingo Molnar wrote:
> * Dimitri Sivanich <[email protected]> wrote:
>
>> drivers/clocksource/Makefile | 1
>> drivers/clocksource/rtc_uv.c | 381 +++++++++++++++++++++++++++++++++++++++
>> drivers/misc/Kconfig | 9
>
> btw., since this is a platform driver, i'd suggest to move it to
> arch/x86/kernel/uv_time.c or so.
>
I thought we agreed platforms drivers go in drivers/x86?
-hpa