søndag den 30. september 2012

Software evolution versus teaching examples

There are numerous good examples readily available on the internet to illustrate many technical points about programming.  Linux kernel programming (which I'm teaching at the moment) is no exception, the only problem is that the Linux kernel API evolves rather quickly, apparently much faster than the examples that are so easy to find.  Fixing such examples is good for building an understanding, but also a bit tedious.  Here then is a contribution: an update of the Linux keyboard interrupt example intrpt.c from Linux Kernel Module Programming to the newest version of the kernel API.  There are a number of stylistic fixes (moving all data to a single struct), updated use of the INIT_WORK macro (no longer passes an argument, stored instead in struct), updates types plus various simplifications, and last but not least no longer any need to try to remove the default keyboard interrupt and proper removal on unloading.  (Thanks to Peter Jay Salzman for the original, updating his code was much easier than writing a new example from scratch.)

/*
 *  interrupt_example.c - An interrupt handler.
 *
 *  Copyright (C) 2012 by Ulrik Pagh Schultz, University of Southern Denmark:
 *  various updates to original provided by Peter Jay Salzman
 *  Copyright (C) 2001 by Peter Jay Salzman
 */

#include 
#include 
#include 
#include 
#include 
#include 

/*
 * Data used by kernel module
 */
struct interrupt_example_data {
  int initialized;                       // flag: init or prepare work
  unsigned char scancode;                // code read from keyboard
  struct work_struct task;               // task structure
  struct workqueue_struct *my_workqueue; // queue holding task
};

static struct interrupt_example_data data;

/* 
 * This will get called by the kernel as soon as it's safe
 * to do everything normally allowed by kernel modules.
 */
static void got_char(struct work_struct *work)
{
  printk(KERN_INFO "Scan Code %x %s.\n",
  (int)data.scancode & 0x7F,
  data.scancode & 0x80 ? "Released" : "Pressed");
}

/* 
 * This function services keyboard interrupts. It reads the relevant
 * information from the keyboard and then puts the non-time-critical
 * part into the work queue. This will be run when the kernel
 * considers it safe.
 */
irqreturn_t irq_handler(int irq, void *arg)
{
  unsigned char status;

  /* 
   * Read keyboard status
   */
  status = inb(0x64);
  data.scancode = inb(0x60);

  /*
   * Schedule work for later
   */
  if (data.initialized == 0) {
    INIT_WORK(&data.task, got_char);
    data.initialized = 1;
  } else {
    PREPARE_WORK(&data.task, got_char);
  }

  queue_work(data.my_workqueue, &data.task);

  return IRQ_HANDLED;
}

/* 
 * Initialize the module - register the IRQ handler 
 */
int init_module()
{
  data.initialized = 0;
  data.my_workqueue = create_workqueue("interrupt_example.c");

  /* 
   * Request IRQ 1, the keyboard IRQ, to go to our irq_handler.
   * IRQF_SHARED means we're willing to have other handlers on this IRQ.
   */
  return request_irq(1,           /* The number of the keyboard IRQ on PCs */
       irq_handler, /* our handler */
       IRQF_SHARED, "test_keyboard_irq_handler",
       irq_handler);
}

/* 
 * Cleanup 
 */
void cleanup_module()
{
  /* 
   * Remove IRQ handler
   */
  free_irq(1, irq_handler);
}

/*
 * Module meta-information
 */
MODULE_AUTHOR("Ulrik Pagh Schultz");
MODULE_DESCRIPTION("Example module for DES/EMB4");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");