⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 racetimer_x86.c

📁 《嵌入式Linux-硬件
💻 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(&currenttime);
    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 + -