Implementing the Race Timer


After the Project Trailblazer engineers resolve the average interrupt latency question, they can design the race timer. All the Project Trailblazer target boards have the input/output (I/O) and speed capability to act as the race timer. For this design, the engineers decide to use the MZ104 as the controller. Figure 11.7 shows a schematic of the race timer.

Figure 11.7. A schematic of the race timer.


The race timer should have the following functional features:

  • It must have 1ms accuracy.

  • It should display the current race time at the finish line.

  • It should provide a status display to officials operating the timer.

  • It should use the racer's pass ID in race information.

  • It should provide the racer with a race start indicator.

  • It should log race results.

  • It should provide access to status, race time, and racer number through /proc directory entries.

Figure 11.8 shows the racer controller state map.

Figure 11.8. The race controller state map.


The operational race scenario consists of four states:

  • Ready On power-up, the race controller enters the ready state and awaits a racer. The racer gets ready at the starting gate. The radio frequency identification (RFID) tag reader reads the racer's lift pass ID and sends it via the RS-232 serial link. This places the controller in the set state.

  • Set The Status LED and Racer Go LEDs turn on. The racer proceeds through the starting gate, which sends a rising-edge signal to the ACK line and generates interrupt 7. The interrupt handler routine executes, records the race start time with do_gettimeofday, schedules the bottom-half tasklet for execution, and puts the controller in the timing state.

  • Timing The Status LED and Racer Go LEDs blink at 1-second intervals. When the racer crosses the finish line, the photo detector sends a rising-edge signal to the ACK line and generates interrupt 7. The interrupt handler routine executes, records the race finish time with do_gettimeofday, schedules the bottom-half tasklet for execution, and puts the controller in the done state.

  • Done The Status LED and Racer Go LEDs turn off. The controller waits for another racer to enter the starting gate.

The Project Trailblazer engineers have already completed several pieces of this design. Chapter 6, "Asynchronous Serial Communication Interfacing," addresses asynchronous serial communications for the RFID tag reader. Chapter 7, "Parallel Port Interfacing," addresses parallel port operations. Chapter 10, "Synchronous Serial Communication Interfacing," addresses synchronous communications with the Philips SAA1064 I2C LED controller. Earlier in this chapter, the engineers developed the interrupt and timing routines. Chapter 12, "System Integration," addresses system integration with the Project Trailblazer server. Therefore, these details are not covered here. For the remainder of this chapter, we'll concentrate on two areas: interrupt bottom-half processing using tasklets and kernel timers.

Race Timer Interrupt Processing Using Tasklets

Complex or lengthy interrupt processing tasks are often split into two sections. The top-half routine executes at interrupt time, performs the minimal amount of work, and schedules the remainder of work, called the bottom half routine, to be performed at a later or safer time. The top-half routine can request execution with interrupts disabled; therefore, it should execute and terminate as quickly as possible. The kernel executes bottom-half routines with interrupts enabled. With Linux kernel 2.4 and above, the preferred way to implement bottom-half routine uses tasklets.

Tasklets are declared by using the DECLARE_TASKLET macro. This macro also associates a handler function to the tasklet. The top-half interrupt handler routine should schedule an already declared tasklet for later execution. The kernel executes the scheduled tasklet handler functions after all the interrupt handler routines complete but before execution of the scheduler.

In the race timer design, the interrupt handler, racetimer_interrupt, schedules the race timer tasklet's handler routine, racetimer_do_tasklet, for later execution. The racetimer_do_tasklet function prints race status information to the system log, which is a somewhat lengthy process, and starts a system timer.

Race Timer Status Display Using System Timers

The race timer Status and Racer Go LEDs inform officials, fans, and racers of race activity. A solid on signal means the system is ready for a race to begin. Blinking LEDs mean a race is in progress. When the LEDs are off, a race is complete and the system is ready for another racer to enter the starting gate. The Project Trailblazer engineers need to figure out how to blink the LEDs at a constant 1-second rate.

As you saw at the beginning of this chapter, Linux offers five timing mechanisms, and the engineers chose to use do_gettimeofday for race timing. The race timer driver could sit in a loop calling do_gettimeofday and wait for 1 second to elapse. The race timer driver could also call a system sleep function (usleep, msleep, or sleep) and wait for 1 second to elapse. Both approaches would work, but neither is desirable. The kernel scheduler does not drive code that is executing in the kernel space. A 1-second sleep in the kernel would completely occupy the processor for 1 second, and other processing would not occur during this time. Device drivers that use sleep functions for long delays definitely affect system performance.

Kernel timers solve long time delay problems. By using a timer, device drivers can schedule a function for execution at a future time. A device driver creates a timer, populates the timer's data, function and expires fields and then adds the timer to the kernel's timer list. The x86 kernel scans the timer list approximately 100 times per second. If the kernel's jiffies value is greater than a timer's expires field value, the kernel executes the timer's function handler. Timers operate in a one-shot mode. The kernel executes timer function handlers only once. Drivers that need periodic function execution need to reschedule their kernel timers in the timer's function handler.

In the race timer, the bottom-half tasklet handler, racetimer_do_tasklet, starts the kernel timer called status_timer. One second later, the status_timer expires and the kernel executes the status_timer's function handler, status_timer_timed_out. If the race is in progress (the timing state where the LEDs blink at 1 second intervals), status_timer_timed_out toggles the Status and Racer Go LEDs, sets the status_timer.expires value, and then reschedules (via the add_timer function) status_timer for execution 1 second later. If the race has completed, status_timer_timed_out turns off the LEDs.

Let's look at how the racetimer_x86.c device driver, shown in Listing 11.4, implements bottom-half interrupt processing using a scheduled tasklet and 1-second timing of the Status and Racer GO LEDs.

Listing 11.4 The racetimer_x86.c Device Driver
 /*  * racetimer_x86 v1.0 11/25/01  *  *  * The original location of this code is  *  *  * 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  */ /*  * racetimer_x86.c is based on procfs_example.c by Erik Mouw.  * For more information, please see The Linux Kernel Procfs Guide, Erik Mouw  *  */ /* gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/linux/include \ -c racetimer_x86.c -o racetimer_x86.o */ /* racetimer_x86  * This module implements a race timer with millisecond accuracy using  * interrupts and bottom half tasklets. The timer also drives a status  * indicator line (parallel port DO) showing the timer's current state.  * Controlling or accessing timer information is provided through /proc  * directory entries.  * Here are the timer's states  * Ready:  Timer ready for racer number entry  * Set:    Timer ready for racer to start, status indicator ON  * Timing: Timer measuring race time, status indicator blinking 1s intervals  * Done:   Race is done, Timer ready for racer number entry  *  * Interrupts or racer number entry forces a state change.  *  * /proc directory entries  * /proc/trailblazer/racernumber  contains the racer number, timer isn't  *                                ready until a number is written to this  *                                file  * /proc/trailblazer/racetime     contains the current or last race time  * /proc/trailblazer/racestatus   contains the race timer state, R, S, T or D  */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/sched.h> #include <linux/interrupt.h> #include <linux/tqueue.h> #define MODULE_VERSION "1.0" #define MODULE_NAME "racetimer" static struct proc_dir_entry *tb_dir,                              *racer_file, *racestatus_file, *racetime_file; #define SPPDATAPORT         0x378 #define SPPSTATUSPORT       (SPPDATAPORT + 1) #define SPPCONTROLPORT      (SPPDATAPORT + 2) #define SSPINTERRUPTENABLE  0x10 #define STATUSLED           0x01    /* DO on the parallel port */ struct timeval starttime, finishtime; unsigned char state; #define STATE_Ready  'R' #define STATE_Set    'S' #define STATE_Timing 'T' #define STATE_Done   'D' /* using letters here instead of numbers, cat /proc/trailblazer/racestatus  * makes things a little easier to read */ #define INTERRUPT 7 struct timer_list status_timer; unsigned char toggle; #define RACERNUMBERLEN 10 unsigned char racernumber[RACERNUMBERLEN+1]; /* status_timer_timed_out  * This function gets called when the status_timer expires, basically every  * 1 second during the race (state = STATE_Timing). Its primary purpose is to  * toggle the status line that blink the LEDs. If we're racing, we need to  * re-schedule the timer for 1 second in the future.  */ void status_timer_timed_out(unsigned long ptr) {   if (state == STATE_Timing)   {     outb(toggle++ & STATUSLED, SPPDATAPORT); /* toggle the statusLED line */     status_timer.expires = jiffies + HZ;     /* 1 second intervals */     add_timer(&status_timer);                /* kicks off the next timer */   }   else     outb(0x00, SPPDATAPORT); /* toggle off statusLED line */ } /* racetimer_do_tasklet  * This function is the interrupt bottom-half tasklet handler. It performs  * the lengthy processing that shouldn't be in the interrupt handler. It  * also starts the status_timer. We only execute this function when an  * interrupt occurs, either the start of the end of the race.  */ void racetimer_do_tasklet(unsigned long unused) {   switch (state) {   case STATE_Timing: /* got into this state from racetimer_interrupt */     status_timer.expires = jiffies + HZ; /* 1 second intervals */     add_timer(&status_timer); /* kicks off the first timer */     printk(KERN_INFO "RaceTimer: Start  %s %9i.%06i\n",racernumber,            (int) starttime.tv_sec, (int) starttime.tv_usec);     break;   case STATE_Done: /* got into this state from racetimer_interrupt */     printk(KERN_INFO "RaceTimer: Finish %s %9i.%06i\n",racernumber,            (int) finishtime.tv_sec, (int) finishtime.tv_usec);     break;   } } /* DECLARE_TASKLET  * This macro actually declares the tasklet and associates it handler  * racetimer_do_tasklet  */ DECLARE_TASKLET(racetimer_tasklet, racetimer_do_tasklet, 0); /* racetimer_interrupt  * Here's the interrupt handler (top-half). It timestamps the race start and  * finish, changes the state and schedules the bottom half tasklet.  */ void racetimer_interrupt(int irq, void *dev_id, struct pt_regs *regs) {   switch (state) {   case STATE_Set:     do_gettimeofday(&starttime);     state = STATE_Timing; /* change state because now we're racing */     tasklet_schedule(&racetimer_tasklet);     break;   case STATE_Timing:     do_gettimeofday(&finishtime);     state = STATE_Done; /* change state because race is over */     tasklet_schedule(&racetimer_tasklet);     break;   } } /* proc_read_racer  * This function returns the racer number if the user does a read on  * /proc/trailblazer/racernumber  */ static int proc_read_racer(char *page, char **start, off_t off,                            int count, int *eof, void *data) {   int len;   len = sprintf(page, "%s\n", racernumber);   return len; } /* proc_write_racer  * This function sets the racer number when the user does a write to  * /proc/trailblazer/racernumber. Writing to racernumber also changes  * the state to set.  */ static int proc_write_racer(struct file *file, const char *buffer,                             unsigned long count, void *data) {   int len;   if(count > RACERNUMBERLEN) /* array range checking here */     len = RACERNUMBERLEN;   else     len = count;   if(copy_from_user(racernumber, buffer, len)) {     return -EFAULT;   }   racernumber[len] = '\0';      /* NULL terminate */   state = STATE_Set;            /* change the state, get set for a new race */   outb(STATUSLED, SPPDATAPORT); /* turn on status LED, solid on means ready */   return len; } /* proc_read_racestatus  * This function returns the state, R, S, T, or D when user reads from  * /proc/trailblazer/racestatus  */ static int proc_read_racestatus(char *page, char **start, off_t off,                                 int count, int *eof, void *data) {   int len;   len = sprintf(page, "%c\n", state);   return len; } /* proc_read_racetime  * This function returns the current or last race time.  * do_gettimeofday fills the timeval with current seconds and microseconds.  * Splitting the time in this way requires a little math because the  * microseconds value is an integer not fraction, (321423 not .321423) So  * we do a little fixup below if the microseconds differential requires a  * carry from the seconds value  */ static int proc_read_racetime(char *page, char **start, off_t off,                               int count, int *eof, void *data) {   int len;   long raceseconds, raceuseconds;   struct timeval currenttime;   switch (state) {   case STATE_Ready:     raceseconds  = 0;     raceuseconds = 0;     break;   case STATE_Set:     raceseconds  = 0;     raceuseconds = 0;     break;   case STATE_Timing:    /* we're racing, give 'em the race time */     do_gettimeofday(&currenttime);     raceseconds  = currenttime.tv_sec - starttime.tv_sec;     raceuseconds = currenttime.tv_usec - starttime.tv_usec;     break;   case STATE_Done:      /* race is over, give 'em the race time */     raceseconds  = finishtime.tv_sec - starttime.tv_sec;     raceuseconds = finishtime.tv_usec - starttime.tv_usec;     break;   } /* need a little fixup here because tv_sec and tv_usec are individual longs */   if (raceuseconds < 0) {     raceuseconds += 1000000;     raceseconds--;   }   len = sprintf(page,"%i.%06i\n", raceseconds, raceuseconds);   return len; } static int __init init_racetimer(void) {   int rv = 0; /* create trailblazer directory */   tb_dir = proc_mkdir("trailblazer", NULL);   if(tb_dir == NULL) {           return -ENOMEM;   }   tb_dir->owner = THIS_MODULE; /* create racer file */   racer_file = create_proc_entry("racer", 0666, tb_dir);   if(racer_file == NULL) {     rv = -ENOMEM;     goto no_racer;   }   racer_file->data = NULL;   racer_file->read_proc = &proc_read_racer;   racer_file->write_proc = &proc_write_racer;   racer_file->owner = THIS_MODULE; /* create racestatus file */   racestatus_file = create_proc_entry("racestatus", 0444, tb_dir);   if(racestatus_file == NULL) {     rv = -ENOMEM;     goto no_racestatus;   }   racestatus_file->data = NULL;   racestatus_file->read_proc = &proc_read_racestatus;   racestatus_file->write_proc = NULL;   racestatus_file->owner = THIS_MODULE; /* create racetime file */   racetime_file = create_proc_entry("racetime", 0444, tb_dir);   if(racestatus_file == NULL) {     rv = -ENOMEM;     goto no_racertime;   }   racetime_file->data = NULL;   racetime_file->read_proc = &proc_read_racetime;   racetime_file->write_proc = NULL;   racetime_file->owner = THIS_MODULE; /* get into reset state */   state = STATE_Ready; /* turn off the status LED */   outb(0x00, SPPDATAPORT); /* Start with a default racer, old number 0 */   sprintf(racernumber,"0000"); /* request the interrupt, use SA_INTERRUPT to disable other interrupts  * while we're running  */   rv = request_irq(INTERRUPT, racetimer_interrupt, SA_INTERRUPT,                    "racetimer",NULL);   if ( rv ) {     printk("Can't get interrupt %d\n", INTERRUPT);     goto no_interrupt;   } /* initialize the status timer but don't start it with add_timer(),  * let someone else do that */   init_timer(&status_timer);   status_timer.function = status_timer_timed_out; = (unsigned long)&state; /* enable parallel port interrupt reporting */   outb(SSPINTERRUPTENABLE,SPPCONTROLPORT);   /* everything OK */   printk(KERN_INFO "%s %s initialized\n",MODULE_NAME, MODULE_VERSION);   return 0; /* clean up /proc directory if we got a error along the way */ no_interrupt:   remove_proc_entry("racetime", tb_dir); no_racertime:   remove_proc_entry("racestatus", tb_dir); no_racestatus:   remove_proc_entry("racer", tb_dir); no_racer:   remove_proc_entry("trailblazer", NULL); } static void __exit cleanup_racetimer(void) { /* turn off the status LED */   outb(0x00, SPPDATAPORT); /* remove the timer */ /* be careful here, if the module is removed and a timer remains  * in the kernel timer list, the kernel will fault when executing  * the timer's handler (because the handler doesn't exist anymore, you  * just removed it from memory.) Force the state to done. Then  * use del_timer_sync, which waits for timer execution completion  * to occur before deleting the timer. Forcing the state to done tells  * timer handler status_timer_timed_out to not reschedule the timer.  */   state = STATE_Done;   del_timer_sync(&status_timer); /* disable parallel port interrupt reporting */   outb(0x00,SPPCONTROLPORT); /* free the interrupt */   free_irq(INTERRUPT,NULL); /* remove the /proc entries */   remove_proc_entry("racetime", tb_dir);   remove_proc_entry("racestatus", tb_dir);   remove_proc_entry("racer", tb_dir);   remove_proc_entry("trailblazer", NULL);   printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION); } module_init(init_racetimer); module_exit(cleanup_racetimer); MODULE_AUTHOR("Craig Hollabaugh"); MODULE_DESCRIPTION("racetimer proc module"); EXPORT_NO_SYMBOLS; 

You can download, compile, and test the racetimer_x86 device driver by using tbdev1. Follow these steps:

  1. Download the racetimer_x86.c source by using wget:

     root@tbdev1[509]: cd /root root@tbdev1[510]: wget chapters/11/racetimer_x86.c 
  2. Compile the source code by using gcc:

     root@tbdev1[511]: gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/linux/include -c racetimer_x86. graphics/ccc.gifc -o racetimer_x86.o 
  3. Insert the device driver by using insmod:

     root@tbdev1[512]: insmod racetimer_x86.o 
  4. Look for interrupt registration on Interrupt 7 and the /proc directory entries by using these commands:

     root@tbdev1[513]: cat /proc/interrupts            CPU0   0:    8163519          XT-PIC  timer   1:       2195          XT-PIC  keyboard   2:          0          XT-PIC  cascade   4:       6016          XT-PIC  serial   7:          0          XT-PIC  racetimer  10:      23132          XT-PIC  usb-uhci  12:     314011          XT-PIC  eth0  14:      54205          XT-PIC  ide0  15:       8457          XT-PIC  ide1 NMI:          0 ERR:          0 root@tbdev1[514]: ls /proc/trailblazer/ racer  racestatus  racetime 

    The race timer is registered on Interrupt 7 and the /proc/trailblazer directory contains the race timer files.

  5. Check the internal state of the race timer status:

     root@tbdev1[515]: cat /proc/trailblazer/racestatus R 

    The output R means that the timer is in the ready state.

  6. Start a race by simulating a racer entering the starting gate with this command:

     root@tbdev1[516]: echo -n "1234" > /proc/trailblazer/racer 
  7. Again check the internal state of the race timer status:

     root@tbdev1[517]: cat /proc/trailblazer/racestatus S 

    The output S means that the timer is in the Set state. You can tell from this output that setting the racer number occurred correctly, and it changed the internal state to S, or set. The status LED also came on. Measure the voltage of the parallel port's D0 signal, pin 2 on the DB-25 connector. Your voltmeter should read between +3V and +5V.

  8. Now check the race time by using this command:

     root@tbdev1[518]: cat /proc/trailblazer/racetime 0.000000 
  9. The racer number is entered in the racer file, the race time is 0.00000 and the timer is in the set state. You are set to start the race. Simulate the racer proceeding through the starting gate by generating the interrupt signal on the parallel port ACK line, pin 10 on the DB-25 connector. Use a debounced switch to generate a positive going signal on the ACK line.


    With interrupt latencies in the microsecond range, Linux interrupt routines can easily count mechanical switch bounces. If you would like to see this for yourself, use a toggle switch to generate the interrupt signal on the parallel port's ACK line. Then examine the interrupt count in the /proc/interrupts file. You should see that one switch closure results in more than one interrupt. Mechanical switches alone should not be used to generate interrupt signals in interrupt-based designs or during interrupt driver testing. For testing, use a switch debounce circuit or write a device driver, again for the parallel port, that drives the ACK line from another parallel port pin. The gate_x86 device driver at generates signals for interrupt device driver testing.

  10. Again check the internal state of the race timer status:

     root@tbdev1[519]: cat /proc/trailblazer/racestatus T 

    The output T means that the timer is in the timing state.

  11. Check the race time by using this command:

     root@tbdev1[520]: cat /proc/trailblazer/racetime 6.331849 

    You are timing a race, and 6.331849 seconds have elapsed.

  12. Continue checking the race time:

     root@tbdev1[521]: cat /proc/trailblazer/racetime 58.101223 

    You are still timing the race. The status LEDs are blinking at 1-second intervals. Figure 11.9 shows an oscilloscope capture of the parallel port's D0 signal. This confirms the Status LED blink rate. As you can see, the system timer works perfectly.

    Figure 11.9. An oscilloscope reading that confirms the 1-second interval timer performance.


  13. Simulate the racer crossing the finish line by using your debounced switch. Again, generate an interrupt signal on the parallel port's ACK line.

  14. Check the internal state of the race timer status:

     root@tbdev1[522]: cat /proc/trailblazer/racestatus D 

    The output D means the race is done.

  15. Check the race time and the system log by using these commands:

     root@tbdev1[523]: cat /proc/trailblazer/racetime 88.141655 root@tbdev1[524]: grep RaceTime /var/log/messages Nov 26 22:54:14 tbdev1 kernel: RaceTimer: Start  1234 1006840454.722554 Nov 26 22:55:42 tbdev1 kernel: RaceTimer: Finish 1234 1006840542.864209 

    The race time of your first simulated race was 88.141655 seconds.

  16. One last check of /proc/interrupts shows that tbdev1 received two interrupts from the race start and finish:

     root@tbdev1[525]: cat /proc/interrupts            CPU0   0:    8262755          XT-PIC  timer   1:       2195          XT-PIC  keyboard   2:          0          XT-PIC  cascade   4:       6016          XT-PIC  serial   7:          2          XT-PIC  racetimer  10:      23132          XT-PIC  usb-uhci  12:     314556          XT-PIC  eth0  14:      54257          XT-PIC  ide0  15:       8457          XT-PIC  ide1 NMI:          0 ERR:          0 

The Project Trailblazer race timer just timed its first race!


    Embedded LinuxR. Hardware, Software, and Interfacing
    Embedded LinuxR. Hardware, Software, and Interfacing
    ISBN: N/A
    EAN: N/A
    Year: 2001
    Pages: 103 © 2008-2017.
    If you may any questions please contact us: