//
// masters.c
//
// This is a device driver module for the Linux 2.2.x kernel.
// It is used to send data to the FPGA through the parallel port.
// This can be achieved by opening the /dev/masters file and writing to it.
// This file should have a major number of 111 (not an official number).
// By default this driver uses LPT1 and irq 7. To use different settings
// you must insert the module with the ioport= and irq= parameters.
// This driver should be compiled with the following command:
//   gcc -D__KERNEL__ -DMODULE -O -I/usr/src/linux/include -c masters.c
//

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define NAME "masters"
#define MY_MAJOR 111
#define BUFFER_SIZE 1024

#define NO 0
#define YES 1

#define DATA ioport+0
#define STATUS ioport+1
#define CONTROL ioport+2

#define LPT1 0x378
#define LPT2 0x278

int ioport = LPT1;
int irq = 7;

MODULE_PARM(ioport, "i");
MODULE_PARM(irq, "i");

typedef struct Dev_Struct {
  
  char buffer[BUFFER_SIZE];
  volatile int head, tail;
  volatile char rts;
  char used;
  
} Dev_Struct;

struct Dev_Struct dev;
struct wait_queue *wqueue;

loff_t unsupported(struct file * filp, loff_t offset, int origin)
{
  return -ESPIPE;
}

//
// This routine is executed everytime an irq is requested by the FPGA
//
void irq_handler(int irq, void *dev_id, struct pt_regs *regs) {

  if (dev.head!=dev.tail) { // there is data to send
    outb(*(dev.buffer+dev.head), DATA);
    dev.head = (dev.head+1) % BUFFER_SIZE;
    wake_up_interruptible(&wqueue);
  }
  else { // buffer is empty
    if (dev.rts == NO)
      printk(KERN_ERR NAME ": Unexpected interrupt.\n");
    else {
      // no more data to send to lets...
      outb(0x10, CONTROL); /* ...disable RTS */
      dev.rts = NO;
    }
  }
}

//
// This routine is executed when a process invokes the open system call
//
int open_device(struct inode *inode, struct file *filp) {

  if (dev.used == YES)
    return -EBUSY;

  // struct initialisation
  dev.used = YES;
  dev.head = dev.tail = 0;
  dev.rts = NO;
  
  outb(0x10, CONTROL); /* Enable lpt interrupt, no RTS */

  MOD_INC_USE_COUNT;

  return 0;
}

//
// This routine is executed when a process invokes the close system call
//
int close_device(struct inode *inode, struct file *filp) {

  dev.used = NO;
  MOD_DEC_USE_COUNT;

  return 0;
}

//
// This routine is executed when a process invokes the write system call
//
ssize_t send_data(struct file *filp, const char *user_buf, size_t count, 
                  loff_t *ppos) {

  int free, max;

  if (dev.tail>=dev.head) {
    free=BUFFER_SIZE - (dev.tail-dev.head) - 1;
    max=BUFFER_SIZE - dev.tail;
    if (free<max)
      max=free;
  }
  else {
    max=free=dev.head-dev.tail-1;
  }  

  if (filp->f_flags & O_NONBLOCK & (free==0))
    return -EAGAIN;

  while (free == 0) {

    interruptible_sleep_on(&wqueue);
    if (signal_pending(current)) /* a signal arrived */
      return -ERESTARTSYS; /* tell the fs layer to handle it */

    if (dev.tail>=dev.head) {
      free=BUFFER_SIZE - (dev.tail-dev.head) - 1;
      max=BUFFER_SIZE - dev.tail;
      if (free<max)
        max=free;
    }
    else {
      max=free=dev.head-dev.tail-1;
    }  
  }

  if (count>max)
    count=max;

  if (copy_from_user(dev.buffer+dev.tail, user_buf, count))
    return -EFAULT;

  cli();
  dev.tail = (dev.tail+count) % BUFFER_SIZE;
  if (dev.rts == NO) { // modulator was off
    dev.rts = YES;     // so turn it on and ...
    outb(*(dev.buffer+dev.head), DATA); // ... start sending data
    dev.head = (dev.head+1) % BUFFER_SIZE;
    outb(0x11, CONTROL); /* RTS */
  }
  sti();

  return count;
}

struct file_operations fops = {
  unsupported,  /* llseek */
  NULL,         /* read */
  send_data,    /* write */
  NULL,         /* readdir */
  NULL,         /* poll */
  NULL,         /* ioctl */
  NULL,         /* mmap */
  open_device,  /* open */
  NULL,         /* flush */
  close_device  /* release */
};

//
// This routine is executed when the module is inserted in the kernel
//
int init_module(void) {

  int err;

  EXPORT_NO_SYMBOLS;

  err=check_region(ioport, 3);
  if (err) {
    printk(KERN_WARNING NAME ": IO address already in use.\n");
    goto fail_io_in_use;
  }
  request_region(ioport, 3, NAME);

  dev.used = NO; // No process is using us
  dev.rts = NO;  // RTS is off
  outb(0x00, CONTROL); /* Disable lpt interrupt */

  err=register_chrdev(MY_MAJOR, NAME, &fops);
  if (err<0) {
    printk(KERN_WARNING NAME ": can't get major %d\n",MY_MAJOR);
    goto fail_major;
  }

  /* this should probably be done in open_device */
  err=request_irq(irq, irq_handler, SA_INTERRUPT, NAME, NULL);
  if (err) {
    printk(KERN_WARNING NAME ": can't get IRQ %d.\n", irq);
    goto fail_irq;
  }

  return 0;

  fail_irq: unregister_chrdev(MY_MAJOR, NAME);
  fail_major: release_region(ioport, 3);
  fail_io_in_use: return err;
}

//
// This routine is executed when the module is removed from the kernel
//
void cleanup_module(void) {

  outb(0x00, CONTROL); /* Disable lpt interrupt */
  free_irq(irq, NULL);
  release_region(ioport, 3);
  unregister_chrdev(MY_MAJOR, NAME);
}
