#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/version.h>
#include <linux/wrapper.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/param.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/timer.h>

#include <asm/uaccess.h>   
#include <asm/io.h>

#define true 1
#define false 0

/* This will be the name we choose for our device. We will also
   use this as a prefix on functions such as the entry points 
   appearing in the file_operations struct.
*/
#define DEV_NAME "pp"
  
static int Major;



/* These are prototypes for residents of the file_operations struct
*/
static ssize_t pp_read(struct file *, char *, size_t, loff_t *);
static ssize_t pp_write(struct file *, const char *, size_t, 
                        loff_t *);
static int pp_open(struct inode *, struct file *);
static int pp_close(struct inode *, struct file *);

/* This is the file_operations struct. The init_module function 
   will register this with the kernel so the kernel will know all
   the entry points it contains.
*/
struct file_operations Fops = {
  owner: THIS_MODULE,
  read: pp_read,
  write: pp_write,
  open: pp_open,
  release: pp_close,
};
/* The pp_probe function does nothing here, but reminds us that a 
   'real' driver may need to probe for hardware resources. These
   resources might later be allocated in init_module.
*/
static int pp_probe(void){
  return 0;
}

/* The pp_read function is a stub, but at least does a printk, 
   for tracing purposes, when it is called.
*/
static ssize_t pp_read(struct file *file, char *buff, size_t ctr,
                       loff_t *woof) {
  printk(KERN_ALERT "\npp_read active.\n");
  return 0;  
}

/* The pp_write function is a stub, but at least does a printk, 
   for tracing purposes, when it is called.
*/
static ssize_t pp_write(struct file *file, const char *buff,
                        size_t ctr, loff_t *woof) {
  printk(KERN_ALERT "\npp_write active.\n");
  return 0;  
}

/* The pp_open function does a printk for tracing purposes.
*/
static int pp_open(struct inode *inode, struct file *file) {
  printk(KERN_ALERT "\nAn instance of %s has been opened.\n", 
         DEV_NAME);
  return 0;
}
/* The pp_close function does a printk for tracing purposes.
*/
static int pp_close(struct inode *inode, struct file *file) {
  printk(KERN_ALERT "\nOne instance of %s has been closed.\n", 
         DEV_NAME);
  return 0;
}



/* Next we'll see that that init_module
 * registers the file_operations struct so the kernel will know
   about the entry points therein
 * gets back a major number
 * calls pp_probe, to look for hardware resources
 Had hardware resources been found, they would need to be allocated  
 for use by this driver, probably within the scope of init_module.
*/
int init_module(void) {
  Major = register_chrdev( 0, DEV_NAME, &Fops);
  if (Major < 0) {
    printk("Registration Failure!\n");
    return Major; 
  }
  if (pp_probe() < 0) {
    unregister_chrdev(Major, DEV_NAME);
    printk(KERN_ALERT "pp_probe() failure!\n");
    return -1;
  }
  printk(KERN_ALERT "\nRegistered %s, at major number = %d.\n\n", 
         DEV_NAME, Major);
  printk("To use %s, you must create a device file.\n", DEV_NAME);
  printk("If this has not already been done, then enter:\n");
  printk("  mknod /dev/%s c %d 0\n\n", DEV_NAME, Major);
  printk("Also set appropriate permissions for /dev/%s.\n\n", 
         DEV_NAME);         
  return 0;
}

/* The cleanup_module function unregisters the driver and, in a
   'real' driver would free up any resources allocated by
   init_module.
*/
void cleanup_module(void) {  
  int ret;
  ret = unregister_chrdev(Major, DEV_NAME);
  if (ret < 0)
    printk(KERN_ALERT "\nUnregistration problem where ret =
           %d\n\n", ret);
  else 
    printk(KERN_ALERT "\nUnregistered %s, at major number = 
           %d\n\n", DEV_NAME, Major);    
}
