Measuring Interrupt Latency

   


Interrupt latency is comprised of hardware propagation time, register saving, and software execution. Propagation time and register saving occur extremely fast on the order of 10s of nanoseconds. Software execution, on the other hand, can be extremely slow. The Linux kernel allows device drivers to disable interrupts by using the cli system call. While interrupts are disabled, other interrupts can occur, but their service routines are not executed until interrupts are re-enabled. Developers disable interrupts to protect critical sections of their code. For example, a video card driver might disable interrupts for 16ms while waiting for video sync. Or a serial card driver might disable interrupts during a byte transmission. Therefore, maximum interrupt latency is a combination of system hardware, peripherals, and the quality of the device driver software. Even with source code availability, this combination and the asynchronous nature of interrupts makes calculating the maximum interrupt latency a difficult, if not impossible, task. On a lightly loaded system with little or no peripherals, it's likely that the average interrupt latency is much smaller than the maximum. Even so, the Project Trailblazer engineers had no idea of the average interrupt latency magnitude. Was it microseconds or milliseconds? They decided to measure it for each target board.

TIP

When you are writing device drivers, you should disable interrupts only when your driver requires absolutely no interruption. While interrupts are disabled, Linux does not update system timers, transfer network packets to and from buffers, or update video information. You should perform your interrupt task as quickly as possible. For lengthy tasks, you should split your handler and use tasklets to perform most of the processing.


Each target board the MZ104, the MediaEngine, and the RPX-CLLF has external connections that are capable of generating processor interrupts. In addition, each board can generate an interrupt signal by using one of its own output ports. In the device driver code we're about to discuss, one function asserts the interrupt signal, which causes the interrupt handler to execute. The handler simply deasserts the interrupt signal and exits. The interrupt signal assertion and deassertion can be viewed on an oscilloscope, to determine the average interrupt latency.

To measure the average interrupt latency, the Project Trailblazer engineers need to do the following:

  1. Write a device driver that contains the following:

    • Configuration code for the board's interrupt controller, if necessary

    • A request for the interrupt from Linux

    • A function that asserts the interrupt signal

    • An interrupt handler that deasserts the interrupt signal

  2. Connect an output port pin to a processor interrupt pin and to the oscilloscope.

  3. Load the device driver.

  4. Assert the interrupt pin.

  5. Watch for the deassertion.

  6. Measure the interrupt latency.

  7. Repeat steps 4, 5, and 6 to determine the average interrupt latency.

As discussed in the section "Linux Timing Sources," earlier in this chapter, the do_gettimeofday function provides timing information with microsecond resolution. By using two calls to do_gettimeofday, the device driver itself can calculate its own instantaneous interrupt latency. (Instantaneous in this case refers to the interrupt latency associated with a single interrupt event.) The instantaneous interrupt latency value will not be the same for interrupt events. The average interrupt latency is the numerical average of several instantaneous interrupt latency values.

The magnitude of the average interrupt latency is what the engineers want to determine for each of their target boards. They need to find out if it is greater than 1ms. A race timer with 1ms accuracy requires a timing source with interrupt latencies of less than 1ms. The engineers need to write an interrupt latency device driver for each of their target boards. They then need to execute this device driver several times, record the instantaneous interrupt latency values, and compute an average interrupt latency value for each board. Using the interrupt latency device driver's output combined with an oscilloscope measurement, the engineers can also verify the microsecond accuracy of the do_gettimeofday function.

The three target board interrupt latency device drivers contain four functions: init, proc_read, interrupt_latency, and cleanup. The init function creates a /proc directory entry called interrupt_latency and configures the processor's interrupt controller and port settings. Reading from the interrupt_latency file calls the function proc_read, which generates an interrupt electrical signal. The device driver handles the interrupt by calling the interrupt_latency function. It then computes and prints the instantaneous interrupt latency value. The driver also maintains an interrupt counter to aid in debugging.

Measuring Interrupt Latency on the MZ104

The x86 parallel printer port can generate a processor interrupt. The port's acknowledge (ACK) status signal, pin 10 on the DB-25 connector, is positive-edge triggered and generates interrupt number 7. The Project Trailblazer engineers decide to drive the ACK status line with the printer port's D7 signal (pin 9 on the DB-25 connector). Figure 11.1 shows the MZ104 connection for measuring interrupt latency.

Figure 11.1. The MZ104 connection for measuring interrupt latency.

graphics/11fig01.gif

Listing 11.1 shows the source code for the interrupt_latency_x86.c device driver for the MZ104.

Listing 11.1 The interrupt_latency_x86.c Device Driver
 /*  * interrupt_latency_x86 v1.0 11/25/01  * www.embeddedlinuxinterfacing.com  *  * The original location of this code is  * http://www.embeddedlinuxinterfacing.com/chapters/11/  *  * Copyright (C) 2001 by Craig Hollabaugh  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library 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  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library 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  */ /*  * interrupt_latency_x86.c is based on procfs_example.c by Erik Mouw.  * For more information, please see, The Linux Kernel Procfs Guide, Erik Mouw  * http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html  */ /* gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/linux/include \ -c interrupt_latency_x86.c -o interrupt_latency_x86.o */ /* interrupt_latency_x86.c module  * This module measures interrupt latency of x86 machines by connecting  * parallel printer port D7 (pin 9) to ACK (pin 10). Enable interrupt  * generation by status register configuration. Positive going edge on  * ACK pin generates interrupt 7.  *  * Kernel source claims microsecond resolution of do_gettimeofday. Viewing  * the D7-ACK connection verifies this, well we're close with 10uS.  *  * After module insertion, reading /proc/interrupt_latency will assert D7  * generating the interrupt. The interrupt handler will deassert this signal.  * View on scope. An interrupt counter is included to help debug a noisy  * interrupt line.  */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <asm/io.h> /* outb */ #define MODULE_VERSION "1.0" #define MODULE_NAME "interrupt_latency_x86" int interruptcount = 0; struct timeval tv1, tv2; /* do_gettimeofday fills these */ #define SPPDATAPORT         0x378 #define SPPSTATUSPORT       (SPPDATAPORT + 1) #define SPPCONTROLPORT      (SPPDATAPORT + 2) #define SSPINTERRUPTENABLE  0x10 #define INTERRUPT 7 static struct proc_dir_entry *interrupt_latency_file; /*  * function interrupt_interrupt_latency  * This function is the interrupt handler for interrupt 7. It sets the tv2  * structure using do_gettimeofday. It then deasserts D7.  */ void interrupt_interrupt_latency(int irq, void *dev_id, struct pt_regs *regs) {   do_gettimeofday(&tv2);   outb(0x00,SPPDATAPORT); /* deassert the interrupt signal */   interruptcount++; } /*  * function proc_read_interrupt_latency  * The kernel executes this function when a read operation occurs on  * /proc/interrupt_latency. This function sets the tv1 structure. It asserts  * D7 which should immediately cause interrupt 7 to occur. The handler  * records tv2 and deasserts D7. This function returns the time differential  * between tv2 and tv1.  */ static int proc_read_interrupt_latency(char *page, char **start, off_t off,                                        int count, int *eof, void *data) {   int len;   do_gettimeofday(&tv1);   outb(0x80,SPPDATAPORT); /* assert the interrupt signal */   len = sprintf(page, "Start   %9i.%06i\nFinish  %9i.%06i\nLatency %17i\n\ Count %19i\n",(int) tv1.tv_sec, (int) tv1.tv_usec, (int) tv2.tv_sec, (int) tv2.tv_usec, (int) (tv2.tv_usec - tv1.tv_usec), interruptcount);   return len; } /*  * function init_interrupt_latency  * This function creates the /proc directory entry interrupt_latency. It  * also configures the parallel port then requests interrupt 7 from Linux.  */ static int __init init_interrupt_latency(void) {   int rv = 0;   interrupt_latency_file = create_proc_entry("interrupt_latency", 0444, NULL);   if(interrupt_latency_file == NULL) {     return -ENOMEM;   }   interrupt_latency_file->data = NULL;   interrupt_latency_file->read_proc = &proc_read_interrupt_latency;   interrupt_latency_file->write_proc = NULL;   interrupt_latency_file->owner = THIS_MODULE;   /* request interrupt from linux */   rv = request_irq(INTERRUPT, interrupt_interrupt_latency, 0,                    "interrupt_latency",NULL);   if ( rv ) {     printk("Can't get interrupt %d\n", INTERRUPT);     goto no_interrupt_latency;   } /* enable parallel port interrupt generation */   outb(SSPINTERRUPTENABLE,SPPCONTROLPORT); /* deassert the interrupt signal */   outb(0x00,SPPDATAPORT); /* everything initialized */   printk(KERN_INFO "%s %s initialized\n",MODULE_NAME, MODULE_VERSION);   return 0; /* remove the proc entry on error */ no_interrupt_latency:   remove_proc_entry("interrupt_latency", NULL); } /*  * function cleanup_interrupt_latency  * This function frees interrupt 7 then removes the /proc directory entry  * interrupt_latency.  */ static void __exit cleanup_interrupt_latency(void) { /* disable parallel port interrupt reporting */   outb(0x00,SPPCONTROLPORT); /* free the interrupt */   free_irq(INTERRUPT,NULL);   remove_proc_entry("interrupt_latency", NULL);   printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION); } module_init(init_interrupt_latency); module_exit(cleanup_interrupt_latency); MODULE_AUTHOR("Craig Hollabaugh"); MODULE_DESCRIPTION("interrupt_latency proc module"); EXPORT_NO_SYMBOLS; 

The engineers connected the parallel port's D7 signal to the ACK signal with a wire. The following console output shows an instantaneous result for the MZ104 target board:

 bash-2.04# insmod interrupt_latency_x86.o interrupt_latency_x86 1.0 initialized bash-2.04# cat /proc/interrupts            CPU0   0:    4188487          XT-PIC  timer   1:       2057          XT-PIC  keyboard   2:          0          XT-PIC  cascade   4:       6016          XT-PIC  serial   7:          0          XT-PIC  interrupt_latency  10:      14884          XT-PIC  usb-uhci  12:     102528          XT-PIC  eth0  14:       3518          XT-PIC  ide0  15:       2541          XT-PIC  ide1 NMI:          0 ERR:          0 bash-2.04# cat /proc/interrupt_latency Start   1006763905.483513 Finish  1006763905.483566 Latency                53 Count                   1 

The /proc/interrupts file shows that the interrupt_latency routine is registered on Interrupt 7. Reading from the /proc/interrupt_latency file shows an instantaneous latency of 53ms. Figure 11.2 shows an oscilloscope capture of the interrupt signal.

Figure 11.2. The MZ104's interrupt signal.

graphics/11fig02.gif

The oscilloscope interrupt signal measurement shows a latency of 44.75ms, whereas the driver returns a calculated latency of 53ms. Code execution accounts for the 8.25ms discrepancy. This small inaccuracy, 8.25ms, is not an issue because the race timer requires 1ms, or 1000ms resolution. 8.25 is negligible compared to 1000.

By using a interrupt latency device driver and oscilloscope measurements, the Project Trailblazer engineers calculated the MZ104 average interrupt latency to be approximately 50ms. The x86 version of the do_gettimeofday function has near-microsecond accuracy.

Measuring Interrupt Latency on the MediaEngine

The MediaEngine's general-purpose input/output signal 1 (GPIO01), located on target board's connector J10, can generate SA-1110 Interrupt 1 on the rising edge, on the falling edge, or both. The Project Trailblazer engineers decide to configure the interrupt controller for rising-edge operation. (For more information, see Section 9.2, "Interrupt Controller," of Intel SA-1110 Developer's Manual1.) The SA-1110's interrupt controller is connected to the GPIO01's input, which monitors pin status, regardless of whether the pin is configured as input or output. The engineers set GPIO01 as an output and then generate the interrupt signal. Figure 11.3 shows the internal connection of GPIO01 output register (GPSR/GPCR), the connection of the input level register (GPLR), the connection to the interrupt controller and the external connection to the oscilloscope.

Figure 11.3. The MediaEngine connection for measuring interrupt latency.

graphics/11fig03.gif

Listing 11.2 shows the source code for the interrupt_latency_mediaengine.c device driver.

Listing 11.2 The interrupt_latency_mediaengine.c Device Driver
 /*  * interrupt_latency_mediaengine v1.0 11/25/01  * www.embeddedlinuxinterfacing.com  *  * The original location of this code is  * http://www.embeddedlinuxinterfacing.com/chapters/11/  *  * Copyright (C) 2001 by Craig Hollabaugh  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library 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  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library 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  */ /*  * interrupt_latency_mediaengine.c is based on procfs_example.c by Erik Mouw.  * For more information, please see, The Linux Kernel Procfs Guide, Erik Mouw  * http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html  */ /* arm-linux-gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/arm-linux/include \ -c interrupt_latency_mediaengine.c \ -o /tftpboot/arm-rootfs/tmp/interrupt_latency_mediaengine.o */ /* interrupt_latency_mediaengine.c module  * This module measures instantaneous interrupt latency of the MediaEngine  * using GPIO01. Configure the GRER for GPIO01 rising edge interrupt  * generation. Setting GPIO01 as an output then setting GPIO01 causes a  * rising edge that generates the interrupt. GRER is reset by ARM kernel  * code, therefore just setting the register itself is not enough. Use  * the set_GPIO_IRQ_edge  ARM-only function. See kernel source for more info.  *  * After module insertion, reading /proc/interrupt_latency will assert GPIO01  * generating the interrupt. The interrupt handler will deassert this signal.  * View on scope. An interrupt counter is included to help debug noisy a  * interrupt line.  */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <asm/io.h> #define MODULE_VERSION "1.0" #define MODULE_NAME "interrupt_latency_mediaengine" /* see 9.1.1.1 Intel StrongARM SA-1110 Microprocessor Developer's Manual */ /* these are also defined in arch SA-1100.h but differently*/ #define GPIO         0x90040000 /* GPIO registers base address */ #define GPLR_OFFSET  0x00 #define GPDR_OFFSET  0x04 #define GPSR_OFFSET  0x08 #define GPCR_OFFSET  0x0C #define GAFR_OFFSET  0x1C #define GRER_OFFSET  0x10 #define GFER_OFFSET  0x14 #define GEDR_OFFSET  0x18 #define GPIOLEN      0x20 #define GPIO01       0x00000002 #define IC           0x90050000 /* Interrupt controller register base address */ #define ICIP_OFFSET  0x00 #define ICMR_OFFSET  0x04 #define ICLR_OFFSET  0x08 #define ICFP_OFFSET  0x10 #define ICLEN        0x20 static void *ic_base, *gpio_base; unsigned long int gpdr, gafr, grer; int interruptcount = 0; struct timeval tv1, tv2; #define INTERRUPT 1 static struct proc_dir_entry *interrupt_latency_file; /*  * function interrupt_interrupt_latency  * This function is the interrupt handler for interrupt 1. It sets the tv2  * structure using do_gettimeofday. It then clears GPIO01.  */ void interrupt_interrupt_latency(int irq, void *dev_id, struct pt_regs *regs) {   do_gettimeofday(&tv2);   writel(GPIO01, gpio_base + GPCR_OFFSET); /* deassert the interrupt signal */   interruptcount++; } /*  * function proc_read_interrupt_latency  * The kernel executes this function when a read operation occurs on  * /proc/interrupt_latency. This function sets the tv1 structure. It asserts  * GPIO01 which should immediately cause interrupt 1 to occur. The handler  * records tv2 and deasserts GPIO01. This function returns the time  * differential between tv2 and tv1.  */ static int proc_read_interrupt_latency(char *page, char **start, off_t off,                                        int count, int *eof, void *data) {   int len;   do_gettimeofday(&tv1);   writel(GPIO01, gpio_base + GPSR_OFFSET); /* assert the interrupt signal */  len = sprintf(page,"Start %9i.%06i\nFinish %9i.%06i\nLatency %6i\nCount%i\n",                       (int) tv1.tv_sec, (int) tv1.tv_usec,                       (int) tv2.tv_sec, (int) tv2.tv_usec,                       (int) (tv2.tv_usec - tv1.tv_usec),                       interruptcount);   return len; } /*  * function init_interrupt_latency  * This function creates the /proc directory entry interrupt_latency. It  * requests interrupt 1 from Linux then configures the interrupt controller.  */ static int __init init_interrupt_latency(void) {   unsigned long r;   int rv = 0;   interrupt_latency_file = create_proc_entry("interrupt_latency", 0444, NULL);   if(interrupt_latency_file == NULL) {     return -ENOMEM;   }   interrupt_latency_file->data = NULL;   interrupt_latency_file->read_proc = &proc_read_interrupt_latency;   interrupt_latency_file->write_proc = NULL;   interrupt_latency_file->owner = THIS_MODULE;   ic_base = ioremap_nocache(IC,ICLEN);   printk("ic_base       = 0x%08X\n",ic_base);   /* request interrupt from linux */   rv = request_irq(INTERRUPT, interrupt_interrupt_latency, 0,                    "interrupt_latency",NULL);   if ( rv ) {     printk("Can't get interrupt %d\n", INTERRUPT);     goto no_interrupt_latency;   }   /* print out interrupt controller status bits */   r = readl(ic_base + ICIP_OFFSET);   printk("ICIP          = 0x%08X\n",r);   r = readl(ic_base + ICMR_OFFSET);   printk("ICMR          = 0x%08X\n",r); /* bit is set here for INT1 */   r = readl(ic_base + ICLR_OFFSET);   printk("ICLR          = 0x%08X\n",r);   r = readl(ic_base + ICFP_OFFSET);   printk("ICFP          = 0x%08X\n",r);   /* get GPIO base for register changing */   gpio_base = ioremap_nocache(GPIO,GPIOLEN);   printk("\ngpio_base     = 0x%08X\n",gpio_base); /* configuring GPIO01 as output */ /* set GPIO01 as output */   writel(gpdr |  GPIO01, gpio_base + GPDR_OFFSET); /* set GPIO01 with no alt function */   writel(gafr & ~GPIO01, gpio_base + GAFR_OFFSET);   writel(GPIO01             , gpio_base + GPCR_OFFSET);  /* clear GPIO01 */   gpdr = readl(gpio_base + GPDR_OFFSET); /* preserve the gpdr bits */   printk("GPDR          = 0x%08X\n",gpdr);   gafr = readl(gpio_base + GAFR_OFFSET); /* preserve the gafr bits */   printk("GAFR          = 0x%08X\n",gafr);   r = readl(gpio_base + GPLR_OFFSET);   printk("GPLR          = 0x%08X\n",r);   grer = readl(gpio_base + GRER_OFFSET); /* preserve the grer bits */   printk("GRER          = 0x%08X\n",grer); /* set GPIO01 to have rising edge int */   writel(grer | GPIO01, gpio_base + GRER_OFFSET); /* use ARM-only Linux function requesting edge int */   set_GPIO_IRQ_edge(GPIO01, GPIO_RISING_EDGE);   printk("set_GPIO_IRQ_edge\n");   r = readl(gpio_base + GRER_OFFSET);   printk("GRER          = 0x%08X\n",r);   r = readl(gpio_base + GFER_OFFSET);   printk("GFER          = 0x%08X\n",r);   r = readl(gpio_base + GEDR_OFFSET);   printk("GEDR          = 0x%08X\n",r);   /* everything initialized */   printk(KERN_INFO "%s %s initialized\n",MODULE_NAME, MODULE_VERSION);   return 0; no_interrupt_latency:   remove_proc_entry("interrupt_latency", NULL); } /*  * function cleanup_interrupt_latency  * This function frees interrupt 1,  restores registers, then removes the  * /proc directory entry interrupt_latency.  */ static void __exit cleanup_interrupt_latency(void) {   free_irq(INTERRUPT,NULL); /* free the interrupt */   writel(gpdr, gpio_base + GPDR_OFFSET);  /* restore gpdr */   writel(gafr, gpio_base + GAFR_OFFSET);  /* restore gafr */   writel(grer, gpio_base + GRER_OFFSET);  /* restore grer */   iounmap(ic_base);   iounmap(gpio_base);   remove_proc_entry("interrupt_latency", NULL);   printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION); } module_init(init_interrupt_latency); module_exit(cleanup_interrupt_latency); MODULE_AUTHOR("Craig Hollabaugh"); MODULE_DESCRIPTION("interrupt_latency proc module"); EXPORT_NO_SYMBOLS; 

The interrupt_latency_mediaengine.c device driver performs steps that are similar to those performed by the interrupt_latency_x86.c device driver. A slight difference exists with the interrupt_latency_mediaengine.c call to set_GPIO_IRQ_edge. The ARM kernel interrupt code maintains an internal copy of the GPIO Rising-Edge Detect Register (GRER) value. Calling set_GPIO_IRQ_edge informs the ARM kernel to include the GPIO01 bit in GRER register manipulations. The following console output shows an instantaneous result for the MediaEngine target board:

 bash-2.04# insmod interrupt_latency_mediaengine.o ic_base       = 0xC2802000 ICIP          = 0x00000000 ICMR          = 0xD4008803 ICLR          = 0x00000000 ICFP          = 0x00000000 gpio_base     = 0xC2804000 GPDR          = 0x00000002 GAFR          = 0x00000000 GPLR          = 0x0FF8FBFC GRER          = 0x00000003 set_GPIO_IRQ_edge GRER          = 0x00000003 GFER          = 0x00000000 GEDR          = 0x00000000 interrupt_latency_mediaengine 1.0 initialized bash-2.04# cat /proc/interrupts   0:       2205   cs89x0   1:          0   interrupt_latency  11:          0   GPIO 11-27  15:        113   serial  26:       1455   timer  28:          0   OST2 - gp  30:          0   rtc1Hz  31:          0   rtcAlrm Err:          0 bash-2.04# cat /proc/interrupt_latency Start         670.663168 Finish        668.663177 Latency                9 Count                  1 

The /proc/interrupts file shows that the interrupt_latency routine is registered on Interrupt 1. The /proc/interrupt_latency file shows an instantaneous latency of 9ms. Figure 11.4 shows an oscilloscope capture of the interrupt signal.

Figure 11.4. The MediaEngine's interrupt signal.

graphics/11fig04.gif

The interrupt signal measurement shows a latency of 7ms, whereas the driver returns a calculated latency of 9ms. Code execution accounts for this 2ms discrepancy. As with the MZ104, this discrepancy is not a factor in the race timer.

The Project Trailblazer engineers calculated the MediaEngine's average interrupt latency to be approximately 10ms. The StrongARM version of the do_gettimeofday function has near-microsecond accuracy.

Measuring Interrupt Latency on the RPX-CLLF

The RPX-CLLF's MPC860 has seven external interrupt pins. The Project Trailblazers want to drive IRQ2 with PA0, so they configure the interrupt controller for IRQ2 rising-edge operation. Asserting PA0 should generate Interrupt 2. (For more information, see Section 11.5.1, "Interrupt Structure," of Motorola MPC860 PowerQUICC User's Manual.2) Figure 11.5 shows the interrupt signal connection between the RPX-CLLF's PA0 and IRQ2 and the oscilloscope.

Figure 11.5. The RPX-CLLF connection for measuring interrupt latency.

graphics/11fig05.gif

Listing 11.3 shows the source code for the interrupt_latency_rpxcllf.c device driver.

Listing 11.3 The interrupt_latency_rpxcllf.c Device Driver
 /*  * interrupt_latency_rpxcllf v1.0 11/25/01  * www.embeddedlinuxinterfacing.com  *  * The original location of this code is  * http://www.embeddedlinuxinterfacing.com/chapters/11/  *  * Copyright (C) 2001 by Craig Hollabaugh  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library 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  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library 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  */ /*  * interrupt_latency_rpxcllf.c is based on procfs_example.c by Erik Mouw.  * For more information, please see, The Linux Kernel Procfs Guide, Erik Mouw  * http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html  */ /* powerpc-linux-gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/powerpc-linux/include \ -c interrupt_latency_rpxcllf.c \ -o /tftpboot/powerpc-rootfs/tmp/interrupt_latency_rpxcllf.o */ /* interrupt_latency_rpxcllf  * This module measures instantaneous interrupt latency of the RPX-CLLF  * using PA0 and IRQ2. Configure the SIU for IRQ2 edge interrupt  * generation. Configure PA0 as an output then setting PA0 causes a  * rising edge that generates the interrupt.  *  * Section 11.5.1 of Motorola MPC860 PowerQUICC User's Manual  *  * After module insertion, reading /proc/interrupt_latency will assert PA0  * generating the interrupt. The interrupt handler will deassert this signal.  * View on scope. An interrupt counter is included to help debug noisy a  * interrupt line.  */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <asm/io.h> #include <linux/interrupt.h> #include <asm/irq.h> #include <asm/mpc8xx.h> #include <asm/8xx_immap.h> #include <asm/time.h> #define MODULE_VERSION "1.0" #define MODULE_NAME    "interrupt_latency_rpxcllf" volatile immap_t *immap; static void *io_base; #define PA0         0x8000 #define SIEL_ED2    0x08000000 int interruptcount = 0; #define INTERRUPT SIU_IRQ2 struct timeval tv1, tv2; /* do_gettimeofday fills these */ static struct proc_dir_entry *interrupt_latency_file; /*  * function interrupt_interrupt_latency  * This function is the interrupt handler for interrupt 2. It sets the tv2  * structure using do_gettimeofday. It then clears PA0.  */ void interrupt_interrupt_latency(int irq, void *dev_id, struct pt_regs *regs) {   do_gettimeofday(&tv2);   immap->im_ioport.iop_padat |= PA0; /* deassert the interrupt signal */   interruptcount++; } /*  * function proc_read_interrupt_latency  * The kernel executes this function when a read operation occurs on  * /proc/interrupt_latency. This function sets the tv1 structure. It asserts  * PA0 which should immediately cause interrupt 2 to occur. The handler  * records tv2 and deasserts PA0. This function returns the time  * differential between tv2 and tv1.  */ static int proc_read_interrupt_latency(char *page, char **start, off_t off,                                        int count, int *eof, void *data) {   int len;   do_gettimeofday(&tv1);   immap->im_ioport.iop_padat &= ~PA0; /* assert the interrupt signal */  len = sprintf(page, "Start %9i.%06i\nFinish %9i.%06i\nLatency %16i\n\ Count %18i\n",(int) tv1.tv_sec, (int) tv1.tv_usec,               (int) tv2.tv_sec, (int) tv2.tv_usec,               (int) (tv2.tv_usec - tv1.tv_usec),               interruptcount);   return len; } /*  * function init_interrupt_latency  * This function creates the /proc directory entry interrupt_latency. It  * requests interrupt 2 from Linux then configures the interrupt controller.  */ static int __init init_interrupt_latency(void) {   unsigned long r;   int rv = 0;      interrupt_latency_file = create_proc_entry("interrupt_latency", 0444, NULL);   if(interrupt_latency_file == NULL) {     return -ENOMEM;   }   interrupt_latency_file->data = NULL;   interrupt_latency_file->read_proc = &proc_read_interrupt_latency;   interrupt_latency_file->write_proc = NULL;   interrupt_latency_file->owner = THIS_MODULE;   /* request interrupt from linux */   rv = request_8xxirq(INTERRUPT, interrupt_interrupt_latency, 0,                       "interrupt_latency",NULL);   if ( rv ) {     printk("Can't get interrupt %d\n", INTERRUPT);     goto no_interrupt_latency;   }   /* get the IMMAP register address */   immap = (immap_t *)(mfspr(IMMR) & 0xFFFF0000);   immap->im_ioport.iop_papar &= ~PA0; /* set PA0 to general I/O */   immap->im_ioport.iop_padir |=  PA0; /* set PA0 as output      */   /* set IRQ2 to edge triggering */   immap->im_siu_conf.sc_siel |= SIEL_ED2;   printk("SIEL     = 0x%08X\n",immap->im_siu_conf.sc_siel);   immap->im_ioport.iop_padat |= PA0; /* deassert the interrupt signal */ /* everything initialized */   printk(KERN_INFO "%s %s initialized\n",MODULE_NAME, MODULE_VERSION);   return 0; no_interrupt_latency:   remove_proc_entry("interrupt_latency", NULL); } /*  * function cleanup_interrupt_latency  * This function frees interrupt 2 then removes the  * /proc directory entry interrupt_latency.  */ static void __exit cleanup_interrupt_latency(void) {   free_irq(INTERRUPT,NULL); /* free the interrupt */   remove_proc_entry("interrupt_latency", NULL);   printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION); } module_init(init_interrupt_latency); module_exit(cleanup_interrupt_latency); MODULE_AUTHOR("Craig Hollabaugh"); MODULE_DESCRIPTION("interrupt_latency proc module"); EXPORT_NO_SYMBOLS; 

The interrupt_latency_rpxcllf.c device driver performs steps similar to those of the interrupt_latency_x86.c device driver. However, significant differences exist between the x86 and PowerPC interrupt controllers. The request_8xxirq function is a specialized PowerPC 8xx version of request_irq. request_8xxirq sets all the required MPC860 interrupt registers. When using an 8xx processor, you must explicitly call the request_8xxirq function. The following console output shows an instantaneous latency test result for the RPX-CLLF target board:

 bash-2.04# insmod interrupt_latency_rpxcllf.o SIEL     = 0x08000000 interrupt_latency_rpxcllf 1.0 initialized bash-2.04# cat /proc/interrupts            CPU0   3:          0   8xx SIU   Edge      fec   4:          0   8xx SIU   Edge      interrupt_latency   5:       3111   8xx SIU   Edge      cpm  15:          0   8xx SIU   Edge      tbint BAD:          0 bash-2.04# cat /proc/interrupt_latency Start         112.477152 Finish        112.477187 Latency               35 Count                  1 

The /proc/interrupts file shows that the interrupt_latency routine is registered on Interrupt 4, not on Interrupt 2. This kernel version incorrectly reports interrupt values. The /proc/interrupt_latency file shows an instantaneous latency of 35ms. Figure 11.6 shows an oscilloscope capture of the interrupt signal.

Figure 11.6. The RPX-CLLF's interrupt signal.

graphics/11fig06.gif

The interrupt signal measurement shows a latency of 30.75ms, whereas the driver returns a calculated latency of 35ms. Code execution accounts for the 4.25ms discrepancy. As with the x86, this discrepancy is not a factor in the race timer.

The Project Trailblazer engineers calculated the RPX-CLLF's average interrupt latency to be approximately 35ms. The PowerPC version of the do_gettimeofday function has near microsecond accuracy.

Interrupt Latency Test Summary

The MZ104, the MediaEngine, and the RPX-CLLF exhibit fast average interrupt latencies, and their do_gettimeofday functions return near-microsecond accuracy. By using the interrupt latency device drivers and an oscilloscope, the engineers found the average interrupt latency for the MZ104, the MediaEngine, and the RPX-CLLF to be 50ms, 10ms, and 35ms, respectively. With average interrupt latencies at or below 50ms, each target board is capable of providing millisecond timing accuracy for the race timer. The engineers breathe a sign of relief because they feared that they would have to move to a real-time solution, such as RTLinux or Real Time Application Interface (RTAI), which would mean a significant increase in their development time. However, the stock Linux kernel running on the Project Trailblazer target boards provides their required functionality. The engineers are now ready to tackle the design of the race timer.


       
    Top


    Embedded LinuxR. Hardware, Software, and Interfacing
    Embedded LinuxR. Hardware, Software, and Interfacing
    ISBN: N/A
    EAN: N/A
    Year: 2001
    Pages: 103

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net