📄 racetimer_x86.c
字号:
/*
* 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'm 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'm 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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -