Alpha Performance Counters Patch

Phillip Ezolt (ezolt@perf.zko.dec.com)
Thu, 30 Jul 1998 17:26:03 -0400 (EDT)


Hi all,
I work in the performance tools division of Digital/Compaq. I am
currently porting iprobe, a performance monitoring tool, from Digital Unix
to Alpha/Linux. (Iprobe uses information provided by the on-chip hardware
counters of the Alpha to give a more accurate profile of running code.
It can tell approximately where data and instruction cache misses occur,
stalls, as well as a many other hardware events.) This code is Digital
code, and unfortunately it will not be GPLed. Therefore, the driver must
be distributed as a binary module. In order to do that, one must be able
to dynamical load and unload a handler for the performance counter
interrupt, and that is currently not availble with Alpha/Linux.

In order to provide the functionality, I needed to add the following
things to the Alpha/linux kernel:

1) A new PAL call macro, wrperfmon, was added to the system.h file
in /include/system.h. It takes two inputs, and returns one
output. My understanding of gcc/at&t assembly is a little hairy,
but this seems to follow the pattern of the other macroized PAL
calls in the same file.

2) The following functions were added to linux/arch/alpha/kernel/irq.c

First, "static struct irqaction *performance_action" was
added. Since there is only one performance interrupt, an array is
not needed.

This structure holds information about the performance counter
interrupt handler. The performance interrupt uses a subset of the
fields that a device interrupt handler uses, so I used the same
(Currently no there is need for a "device ID", and the "flags"
fields doesn't make sense. Perhaps, if there is ever a need for two
concurrent perfomance drivers this could be used/implemented.)

In order to dynamically install and remove performance interrupts
handlers the following has to be added to the kernel:


int request_perf_irq(void (*handler)(int, void *, struct pt_regs *),
const char * devname)

This will install a handler.

The performance handler should just ignore the first two arguments
it is passed. (This is a because we are using the device irq
structure)

void free_perf_irq(void)

This will remove the handler for the performance counters.

Finally, A function must be added which calls the performance counter
handlersduring during a performance counter interrupt. (With all the
necessary error handling)

This function is:

static inline void performance_counter_interrupt(unsigned long vector,
struct pt_regs * regs)

It checks for the existence of, and then runs the user supplied
performance counter interrupt handler.


3) alpha_ksyms.c has to export the following functions (once
again patch enclosed) to allow external kernel modules access
them..

4) The prototypes for the above exported functions are present in
irq.h.

The patch enclosed is against the 2.1.111 kernel, but it applies cleanly
to 2.1.112 as well.

If anyone has any comments our suggestions, feel free to reply.

Cheers,
Phil

ezolt@perf.zko.dec.com

diff -u --recursive --new-file ./linux/arch/alpha/kernel/alpha_ksyms.c ./linux-perf/arch/alpha/kernel/alpha_ksyms.c
--- ./linux/arch/alpha/kernel/alpha_ksyms.c Mon Jul 20 19:00:02 1998
+++ ./linux-perf/arch/alpha/kernel/alpha_ksyms.c Thu Jul 30 09:14:40 1998
@@ -98,6 +98,10 @@
EXPORT_SYMBOL(alpha_read_fp_reg);
EXPORT_SYMBOL(alpha_write_fp_reg);

+/* These are the Performance Counter handler functions. */
+EXPORT_SYMBOL(request_perf_irq);
+EXPORT_SYMBOL(free_perf_irq);
+
/* In-kernel system calls. */
EXPORT_SYMBOL(__kernel_thread);
EXPORT_SYMBOL(sys_open);
diff -u --recursive --new-file ./linux/arch/alpha/kernel/irq.c ./linux-perf/arch/alpha/kernel/irq.c
--- ./linux/arch/alpha/kernel/irq.c Wed Jun 24 17:30:08 1998
+++ ./linux-perf/arch/alpha/kernel/irq.c Thu Jul 30 16:55:45 1998
@@ -116,6 +116,84 @@
#define IRQ_TO_MASK(irq) (irq)
#endif /* CONFIG_ALPHA_SABLE */

+
+/* This is the structure that will hold the information about the performance
+ counter interrupt and handler. Even though the performance interrupt
+ doesn't utilize all of the fields of a standard device irq, they are
+ similar enough to not require a new structure. */
+
+static struct irqaction *performance_action=NULL;
+
+static inline void performance_counter_interrupt(unsigned long vector, struct pt_regs * regs)
+{
+ unsigned long flags;
+ if (performance_action==NULL) {
+ return;
+ }
+
+ if (performance_action->handler==NULL) {
+ return;
+ }
+
+ save_and_cli(flags);
+
+ performance_action->handler(0, 0, regs);
+
+ restore_flags(flags);
+
+}
+
+int request_perf_irq(void (*handler)(int, void *, struct pt_regs *),
+ const char * devname)
+{
+ static struct irqaction *temp_perf_action;
+ unsigned long flags;
+
+ if (!handler)
+ return -EINVAL;
+
+ if (performance_action) {
+ printk("Interrupt handler for Performance Counters already assigned. ");
+ return -EINVAL;
+ }
+
+ temp_perf_action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
+ if (!temp_perf_action)
+ return -ENOMEM;
+
+ temp_perf_action->handler = handler;
+ temp_perf_action->flags = 0;
+ temp_perf_action->mask = 0;
+ temp_perf_action->name = devname;
+ temp_perf_action->next = NULL;
+ temp_perf_action->dev_id = NULL;
+
+ save_and_cli(flags);
+ performance_action = temp_perf_action;
+ restore_flags(flags);
+
+ return 0;
+}
+
+
+void free_perf_irq(void)
+{
+ static struct irqaction *temp_action_pointer;
+ unsigned long flags;
+
+ if (performance_action==NULL) {
+ printk("Trying to free free Performance Interrupt.\n");
+ }
+ temp_action_pointer = performance_action;
+
+ save_and_cli(flags);
+ performance_action = NULL;
+ restore_flags(flags);
+
+ kfree(temp_action_pointer);
+
+}
+
/*
* Update the hardware with the irq mask passed in MASK. The function
* exploits the fact that it is known that only bit IRQ has changed.
@@ -1753,8 +1831,8 @@
#endif
return;
case 4:
- printk("Performance counter interrupt\n");
- break;
+ performance_counter_interrupt(vector, &regs);
+ return;
default:
printk("Hardware intr %ld %lx? Huh?\n", type, vector);
}
diff -u --recursive --new-file ./linux/include/asm-alpha/irq.h ./linux-perf/include/asm-alpha/irq.h
--- ./linux/include/asm-alpha/irq.h Sat Apr 18 01:04:44 1998
+++ ./linux-perf/include/asm-alpha/irq.h Thu Jul 30 09:15:21 1998
@@ -64,4 +64,10 @@
extern void disable_irq(unsigned int);
extern void enable_irq(unsigned int);

+extern int request_perf_irq(void (*handler)(int, void *, struct pt_regs *),
+ const char * devname);
+
+extern void free_perf_irq(void);
+
+
#endif
diff -u --recursive --new-file ./linux/include/asm-alpha/system.h ./linux-perf/include/asm-alpha/system.h
--- ./linux/include/asm-alpha/system.h Mon Mar 30 03:21:41 1998
+++ ./linux-perf/include/asm-alpha/system.h Thu Jul 30 09:15:46 1998
@@ -114,6 +114,24 @@
#define draina() \
__asm__ __volatile__ ("call_pal %0" : : "i" (PAL_draina) : "memory")

+#define wrperfmon(__performance_function, __argument) \
+ ({\
+ register unsigned long __r0 __asm__("$0"); \
+ register unsigned long __r16 __asm__("$16"); \
+ register unsigned long __r17 __asm__("$17"); \
+ __r16 = __performance_function; \
+ __r17 = __argument; \
+ __asm__ __volatile__("bis %1,%1,$16\n\t" \
+ "bis %2,%2,$17\n\t" \
+ "call_pal 57\n\t" \
+ "bis $0,$0,%0" \
+ : "=r" (__r0) \
+ : "r" (__r16),\
+ "r" (__r17) \
+ : "$0", "$1","$22", "$23", "$24", "$25", "$26","memory");\
+ __r0; })
+
+
#define call_pal1(palno,arg) \
({ \
register unsigned long __r0 __asm__("$0"); \

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.altern.org/andrebalsa/doc/lkml-faq.html