
#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif
 
#include <linux/module.h>

#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h>     /* everything... */
#include <linux/errno.h>  /* error codes */
#include <linux/malloc.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/tqueue.h>

#include <asm/io.h>
#include <asm/segment.h>

#include "sysdep.h"

 
#define baud_l data
#define baud_h interrupt_enable
#define I_CHAR_IN       (1<<0)
#define I_TRANS_EMPTY   (1<<1)
#define F_BAUD_LATCH    (1<<7)
#define F_NORMAL        (0<<7)
#define F_BREAK         (1<<6)
#define F_NO_BREAK      (0<<6)
#define F_PARITY_NONE   (0<<3)
#define F_STOP1         (0<<2)
#define O_LOOP          (0<<4) /* Loopback test */
#define O_OUT1          (1<<3) /* Unused */
#define O_OUT2          (1<<2) /* Interrupts used -serial */
#define O_RTS           (1<<1)
#define O_DTR           (1<<0)
#define S_TBE           (1<<5) /* Transmitter buffer empty */
#define S_RxRDY         (1<<0) /* We have got a character */
#define SPEED           0x0c   /* 9600 baud */
#define F_DATA8         (3)
#define COM             ((struct sio *)0x3f8)
#define MAXMESSAGES     2000 //Dynamic buffer allocation would be better! 
#define SERIALIN        0
#define SERIALOUT       1
#define DELAY            ;
#define PIC1_CMND        0x020
#define CLI_INT		asm("cli;");
#define STI_INT		asm("sti;");

#define PIC_EOI         0x20
#define MOD(x,Max)	(x+1>Max)?0:++x



 struct MailBoxType	 /* Mailbox is the strorage of messages */
  {
   short CountMess;        /* Counter of messages in buffer */
   short WeitCount;        /* Counter of waiting processes in box */
   short Head;             /* Head and tail of circular buffer of messages */
   short Tail;
   int Buffer[MAXMESSAGES+1]; /* Message storage */
  } MailBox[2];

struct sio {
	char data;               /* Data register  RBR  THR */
	char interrupt_enable;   /* Interrup enable register  IER */
        char interrupt_id;       /* Interrupt identification register  IIR */
	char format;             /* Line control register LCR */
	char out_control;        /* Modem control register MCR */
	char status;             /* Line status register LSR */
	char i_status;           /* Modem status register MSR */
	char dummy;
};

int GetBuff(struct MailBoxType * ); 
char PutBuff( struct MailBoxType *,char) ;
short GetSerial();
void init_mb(); 
short PutSerial(short );  
void outbyte(short , short );

int serial_major = 0;
int serial_base  = 0x03f8;
int serial_irq   = 0x4;
unsigned long serial_buffer = 0;
struct wait_queue *serial_queue;




short Going      =0;       /* Is serial line outputting */

/* Initialze serial line HW*/
void init_serial() {
   int flags;
   save_flags(flags);
   CLI_INT;
  outbyte(PIC1_CMND,PIC_EOI);

   DELAY;DELAY;
  outbyte((short)&COM->interrupt_enable,0xf);
  DELAY;DELAY;
  outbyte((short)&COM->format,
	   F_BAUD_LATCH|F_NO_BREAK|O_LOOP|F_PARITY_NONE|F_STOP1|F_DATA8);
  DELAY;DELAY;
  outbyte((short)&COM->baud_l,SPEED & 0xff);
  DELAY;DELAY;
  outbyte((short) & COM->baud_h,SPEED >>8);
  DELAY;DELAY;
  outbyte((short) &COM->format,
  F_NORMAL|F_NO_BREAK|F_PARITY_NONE|F_STOP1|F_DATA8);
  DELAY;DELAY;
 outbyte((short) &COM->out_control,O_OUT1|O_OUT2|O_RTS|O_DTR|O_LOOP );

 (void )inb((short)&COM->data);
 (void )inb((short)&COM->interrupt_enable);
 (void )inb((short)&COM->interrupt_id);
 (void )inb((short)&COM->status);
(void )inb((short)&COM->i_status);
 outbyte(PIC1_CMND,PIC_EOI);

 restore_flags(flags);

}

/*
 * The interrupt handler has a different prototype whether it
 * is compiled with kernels older or newer than 1.3.70
 */
void serial_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
 short serial_status;
 int flags;
 char c;
 save_flags(flags);
 
 serial_status = inb((short) &COM->status);
 inb((short) &COM->interrupt_enable);
 inb((short) &COM->interrupt_id);
 inb((unsigned) &COM->i_status);
 if  ( (serial_status & S_RxRDY) != 0) {
   PutBuff(&MailBox[SERIALIN],inb((short)&COM->data));// & 0x7f);
    wake_up_interruptible(&serial_queue); /* awake any reading process */
  }
 if ( (serial_status & S_TBE ) != 0) {     // Is transmitter buffer empty
   if( MailBox[SERIALOUT].CountMess ==0) {
       Going = 0;
      }
	else{
        c=(char)GetBuff(&MailBox[SERIALOUT]);
        outbyte((short)&COM->data,c);
 
	 Going=1;                  // Set flag serial is running
  }
  restore_flags(flags);
  }
}



int serial_open (struct inode *inode, struct file *filp) {
  extern struct file_operations serial_i_fops;
   MOD_INC_USE_COUNT;
  if (MINOR(inode->i_rdev) & 0x80) {
      filp->f_op = &serial_i_fops; 
   }
   return 0;
 }

int  serial_release (struct inode *inode, struct file *filp) {
    MOD_DEC_USE_COUNT;
    return(0);
}




/* then,  the interrupt-related device */

read_write_t serial_i_read (struct inode *inode, struct file *filp,
                char *buf, count_t count) { 
    char c;
    int i;
 if (MailBox[SERIALIN].CountMess ==0) {
      interruptible_sleep_on(&serial_queue);
    if (current->signal & ~current->blocked) /* a signal arrived */
         return -ERESTARTSYS; /* tell the fs layer to handle it */
        
     }
     i=0;
     while((( c=GetSerial()) != -1) && (i<count)) {
       memcpy_tofs (buf++,&c,1);
       i++;
      }
 if(i>0)
 return((read_write_t)i);
 else return( (read_write_t) -1);
}

read_write_t serial_i_write (struct inode *inode, struct file *filp,
                const char *buf, count_t count) {
 int i;
 char c;
 CLI_INT;
 for (i=0;i<count;i++) {
         if(MailBox[SERIALOUT].CountMess >= MAXMESSAGES-1) {
            printk("<1> BUFF OVERFLOW IN SERIAL WRITE \n");
            STI_INT;
            return((read_write_t)i-1); 
            }
            memcpy_fromfs(&c,buf++,1);
	    PutSerial(c);
       }
STI_INT;
return((read_write_t)count);
}

struct file_operations serial_i_fops = {
    NULL,          /* serial_i_lseek */
    serial_i_read,
    serial_i_write,
    NULL,          /* serial_i_readdir */
    NULL,          /* serial_i_select */
    NULL,          /* serial_i_ioctl */
    NULL,          /* serial_i_mmap */
    serial_open,
    serial_release,
    NULL,          /* serial_i_fsync */
    NULL,          /* serial_i_fasync */
                   /* nothing more, fill with NULLs */
}; 


/*
 * The following two functions are equivalent to the previous one,
 * but split in top and bottom half. First, a few needed variables
 */
int init_module(void)
{ 
    int result;
    release_region(serial_base,8);
    result = check_region(serial_base,8);
    init_serial();
    init_mb(); 
    if (result) {
        return result;
    }
    request_region(serial_base,8,"Ser_own");

    result = register_chrdev(serial_major, "Ser_own", &serial_i_fops);
    if (result < 0) {
        release_region(serial_base,8);
        return result;
    }
    if (serial_major == 0) serial_major = result; /* dynamic */

//    MailBox = (struct MailBoxType *)__get_free_page(GFP_KERNEL); /* never fails */
 //   Dynamic allocation of buffers not used here   
        result = request_irq(serial_irq, serial_interrupt,
                             0, "Ser_own", NULL);
        if (result) {
            serial_irq = -1;
        }
        

    return 0;

}
void cleanup_module(void) {
 free_irq(serial_irq, NULL);
        
    unregister_chrdev(serial_major, "Ser_own");
   release_region(serial_base,8);
  //  if (MailBox) free_page(MailBox);
}



 

int GetBuff(struct MailBoxType *Buff ){ /* Get Message from circular buffer*/
  int j;
  int i;
  if(Buff->CountMess > 0 ){
        --Buff->CountMess;
	i= Buff->Tail= MOD(Buff->Tail,MAXMESSAGES);
	if(i == 0) j = (int)Buff->Buffer[MAXMESSAGES];
	else {
	j =(int) Buff->Buffer[i-1];
        return((int)j);
        }
  }
 else return((int)-1);
}
/* Store message to circular buffer */
char PutBuff( struct MailBoxType *Buff,char Messa) {
 int j;
 if(Buff->CountMess < MAXMESSAGES-1) {
      ++Buff->CountMess;
      j = Buff->Head = MOD(Buff->Head,MAXMESSAGES);
      if( j == 0) Buff->Buffer[MAXMESSAGES] = Messa;
       else
       { 
	Buff->Buffer[j-1] = Messa;
	return((char)1);
      }
  } 
 else
  return((char)-1);
}




short GetSerial() {
 short x;
 x = (short)GetBuff(&MailBox[SERIALIN]); /* Read char from serial port */
 return(x);
}

short PutSerial(short c)             /* Write char to serial port */
{
		  /* We must start serial line if this is first char to serial line */
 CLI_INT;
 if(Going )
 {                                /* If we have incoming character serial line
				     interrupt handler will start also output*/
 
   if((PutBuff(&MailBox[SERIALOUT],(short)c)) == -1){
     STI_INT;
     return(-1);
    }
   else {
   STI_INT;
   return(1); 
   }
 }
  else
  {
    outbyte((short)&COM->data,c); /* Start transfer */
    Going=1;             /* Output is running */
   }
 STI_INT;
 return(1);
}
void init_mb() {
 int i;
 for( i=0;i<2;i++) {
   MailBox[i].CountMess=0;
   MailBox[i].Head=0;
   MailBox[i].Tail=0;
  }
}
void outbyte(short x, short y) {
    outb_p(y,x);
}







