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:
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:
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 TaskletsComplex 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 TimersThe 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 * 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 */ /* * racetimer_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 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(¤ttime); 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; status_timer.data = (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:
The Project Trailblazer race timer just timed its first race! |
Top |