/*
 * irq.c -- demonstrate basic irq functionality in the kernel
 */

#include 

#include  /* printk() */
#include   /* error codes */
#include 
#include 
#include 
#include 
#include 

#include 

#define LED_BASE    0x240
#define IRQ_LED_ON  9
#define IRQ_LED_OFF 10

struct tq_struct led_on_tq;
struct tq_struct led_off_tq;

int irq_read_proc(char *buf, char **start, off_t offset, int len, int unused) {
  len = sprintf(buf, "Fill in with you desired information...\n");
  return len;
}

struct proc_dir_entry irq_proc_entry = {
  0,
  3, "irq",
  S_IFREG | S_IRUGO,
  1, 0, 0,
  0, NULL,
  &irq_read_proc,
};


void led_on_handler(int irq, void *dev_id, struct pt_regs *regs) {
  /*
   * if this was a real device we would need to do some kind of 
   * interrupt_acknowledge here (disable hardware that generated this
   * interrupt), but here we don't have to do that because it's free running
   * anyway (expect to service this interrupt in much less time than 1/50
   * sec.)
   */

  printk("<1>led_on_handler: top half; interrupt recieved...\n");

  /*
   * here are the 2 lines of code to queue and mark the bottom half
   */
  queue_task(&led_on_tq, &tq_immediate);
  mark_bh(IMMEDIATE_BH);

  printk("<1>led_on_handler: top half; bottom half queued and marked...\n"); 

}

void led_off_handler(int irq, void *dev_id, struct pt_regs *regs) {
  /*
   * if this was a real device we would need to do some kind of 
   * interrupt_acknowledge here (disable hardware that generated this
   * interrupt), but here we don't have to do that because it's free running
   * anyway (expect to service this interrupt in much less time than 1/50
   * sec.)
   */

  printk("<1>led_off_handler: top half; interrupt recieved...\n");

  /*
   * here are the 2 lines of code to queue and mark the bottom half
   */
  queue_task(&led_off_tq, &tq_immediate);
  mark_bh(IMMEDIATE_BH);

  printk("<1>led_off_handler: top half; bottom half queued and marked...\n"); 

}

void bh_led_on(void *unused) {
    /*
     * turn LED on
     */

    printk("<1>led_on_handler: bottom half; writing 0xaa into %i...\n", LED_BASE);
    outb(0xaa, LED_BASE);

}

void bh_led_off(void *unused) {
    /*
     * turn LED off
     */

    printk("<1>led_off_handler: bottom half; writing 0x55 into %i...\n", LED_BASE);
    outb(0x55, LED_BASE);

}


int init_module(void) {

    int result = check_region(LED_BASE, 32);

    if (result) {
        printk(KERN_INFO "irq: can't get I/O address 0x%x\n", LED_BASE);
        return result;
    }
    request_region(LED_BASE, 32, "irq");

    /*
     * set up irq hadling
     */
    led_on_tq.routine = bh_led_on;
    led_on_tq.data = NULL;

    led_off_tq.routine = bh_led_off;
    led_off_tq.data = NULL;

    /*
     * request led on irq number and install handler
     */
    result = request_irq(IRQ_LED_ON, led_on_handler, 0, "LED on", NULL);
    if (result) {
      printk("<1>led_on_handler: failed install of irq %i handler...\n", IRQ_LED_ON);
      return result;
    }
    printk("<1>led_on_handler: irq %i handler installed...\n", IRQ_LED_ON);

    /*
     * request led off irq number and install handler
     */
    result = request_irq(IRQ_LED_OFF, led_off_handler, 0, "LED off", NULL);
    if (result) {
      printk("<1>led_off_handler: failed install of irq %i handler...\n", IRQ_LED_OFF);
      return result;
    }
    printk("<1>led_off_handler: irq %i handler installed...\n", IRQ_LED_OFF);

    /*
     * register the proc fs inode
     */
    //proc_register_dynamic(&proc_root, &irq_proc_entry);

    return 0;

}

void cleanup_module(void) {

    release_region(LED_BASE, 32);

    /*
     * unregister the proc fs inode
     */
    //proc_unregister(&proc_root, irq_proc_entry.low_ino);

    /*
     * release grabbed irq lines
     */
    free_irq(IRQ_LED_ON, NULL);
    free_irq(IRQ_LED_OFF, NULL);

    printk("<1>irq removed...\n");

}