/* $Id: divamnt.c,v 1.1.2.2 2002/10/02 14:38:37 armin Exp $
 *
 * Driver for Eicon DIVA Server ISDN cards.
 * Maint module
 *
 * Copyright 2000,2001 by Armin Schindler (mac@melware.de)
 * Copyright 2000,2001 Cytronics & Melware (info@melware.de)
 *
 * This software may be used and distributed according to the terms
 * of the GNU General Public License, incorporated herein by reference.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/skbuff.h>
#include <linux/vmalloc.h>
#include <linux/devfs_fs_kernel.h>

#include "platform.h"
#include "di_defs.h"
#include "divasync.h"
#include "di_defs.h"
#include "debug_if.h"

EXPORT_NO_SYMBOLS;

static char *main_revision = "$Revision: 1.1.2.2 $";

static int major = 241;

MODULE_DESCRIPTION(             "Maint driver for Eicon DIVA Server cards");
MODULE_AUTHOR(                  "Cytronics & Melware, Eicon Networks");
MODULE_SUPPORTED_DEVICE(        "DIVA card driver");
MODULE_LICENSE("GPL");
MODULE_PARM(major, "i");
MODULE_PARM_DESC(major, "Major number for /dev/DivasMAINT");

int buffer_length = 128;
MODULE_PARM(buffer_length, "i");
unsigned long diva_dbg_mem=0;
MODULE_PARM(diva_dbg_mem, "l");

static char *DRIVERNAME = "Eicon DIVA - MAINT module (http://www.melware.net)";
static char *DRIVERLNAME = "diva_mnt";
char *DRIVERRELEASE = "2.0";

static devfs_handle_t devfs_handle;

static wait_queue_head_t msgwaitq;
static atomic_t opened; 
static struct timeval start_time;

extern int mntfunc_init(int *, void **, unsigned long);
extern void mntfunc_finit(void);
extern int maint_read_write(void * buf);

/*
 *  helper functions
 */
static char *
getrev(const char *revision)
{
  char *rev;
  char *p;

  if ((p = strchr(revision, ':'))) {
    rev = p + 2;
    p = strchr(rev, '$');
    *--p = 0;
  } else rev = "1.0";

  return rev;
} 

/*
 * memory alloc
 */
void* diva_os_malloc (unsigned long flags, unsigned long size) {
  return (vmalloc (size));
}
void diva_os_free (unsigned long flags, void* ptr) {
  if (ptr) {
    vfree (ptr);
  }
}
void *diva_os_malloc_tbuffer(unsigned long flags, unsigned long size) {
		return(kmalloc(size, GFP_KERNEL));
}
void diva_os_free_tbuffer(unsigned long flags, void* ptr) {
  if (ptr) {
    kfree (ptr);
  }
}

/*
 * sleep msec
 */
void diva_os_sleep (dword mSec)
{
	unsigned long timeout = HZ * mSec / 1000 + 1;
			 
	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout(timeout);
} 

/*
 * kernel/user space copy functions
 */
int diva_os_copy_to_user(void* os_handle, void* dst, const void* src, int length)
{
	return (copy_to_user(dst, src, length));
}
int diva_os_copy_from_user(void* os_handle, void* dst, const void* src, int length)
{
	return(copy_from_user(dst, src, length));
} 

/*
 * get time
 */
void diva_os_get_time (dword* sec, dword* usec) {
  struct timeval tv;

  do_gettimeofday (&tv);

  if (tv.tv_sec > start_time.tv_sec) {
    if (start_time.tv_usec > tv.tv_usec) {
      tv.tv_sec--;
      tv.tv_usec += 1000000;
    }
    *sec  = (dword)(tv.tv_sec  - start_time.tv_sec);
    *usec = (dword)(tv.tv_usec - start_time.tv_usec);
  } else if (tv.tv_sec == start_time.tv_sec) {
    *sec  = 0;
    if (start_time.tv_usec < tv.tv_usec) {
      *usec = (dword)(tv.tv_usec - start_time.tv_usec);
    } else {
      *usec = 0;
    }
  } else {
    *sec  = (dword)tv.tv_sec;
    *usec = (dword)tv.tv_usec;
  }
}

/*
 * /proc entries
 */

extern struct proc_dir_entry *proc_net_isdn_eicon;
static struct proc_dir_entry *maint_proc_entry = NULL;

/*
	Read function is provided for compatibility reason - this allows
  to read unstructured traces, formated as ascii string only
  */
static ssize_t
maint_read(struct file *file, char *buf, size_t count, loff_t * off)
{
  diva_dbg_entry_head_t* pmsg = 0;
  diva_os_spin_lock_magic_t old_irql;
  word size;
  char* pstr, *dli_label = "UNK";
	int str_length;
	int *str_msg;

  if (off != &file->f_pos)
    return -ESPIPE;

  if (!file->private_data) {
    for (;;) {
      while ((pmsg = diva_maint_get_message (&size, &old_irql))) {
        if (!(pmsg->facility == MSG_TYPE_STRING)) {
          diva_maint_ack_message (1, &old_irql);
        } else {
          break;
        }
      }

      if (!pmsg) {
        if (file->f_flags & O_NONBLOCK) {
          return (-EAGAIN);
        }
        interruptible_sleep_on(&msgwaitq);
        if (signal_pending(current)) {
          return (-ERESTARTSYS);
        }
      } else {
        break;
      }
    }
    /*
      The length of message that shoule be read is:
      pmsg->data_length + label(25) + DrvID(2) + byte CR + trailing zero
      */
    if (!(str_msg = (int*)diva_os_malloc_tbuffer(0, pmsg->data_length + 29 + 2*sizeof(int)))) {
      diva_maint_ack_message (0, &old_irql);
      return (-ENOMEM);
    }
    pstr = (char*)&str_msg[2];

    switch (pmsg->dli) {
      case DLI_LOG :
        dli_label = "LOG";
        break;
      case DLI_FTL :
        dli_label = "FTL";
        break;
      case DLI_ERR :
        dli_label = "ERR";
        break;
      case DLI_TRC :
        dli_label = "TRC";
        break;
      case DLI_REG :
        dli_label = "REG";
        break;
      case DLI_MEM :
        dli_label = "MEM";
        break;
      case DLI_SPL :
        dli_label = "SPL";
        break;
      case DLI_IRP :
        dli_label = "IRP";
        break;
      case DLI_TIM :
        dli_label = "TIM";
        break;
      case DLI_TAPI:
        dli_label = "TAPI";
        break;
      case DLI_NDIS:
        dli_label = "NDIS";
        break;
      case DLI_CONN:
        dli_label = "CONN";
        break;
      case DLI_STAT:
        dli_label = "STAT";
        break;
      case DLI_PRV0:
        dli_label = "PRV0";
        break;
      case DLI_PRV1:
        dli_label = "PRV1";
        break;
      case DLI_PRV2:
        dli_label = "PRV2";
        break;
      case DLI_PRV3:
        dli_label = "PRV3";
        break;
    }
    str_length = sprintf (pstr, "%s %02x %s\n",
                          dli_label, (byte)pmsg->drv_id, (char*)&pmsg[1]);
    str_msg[0] = str_length;
    str_msg[1] = 0;
    file->private_data = str_msg;
    diva_maint_ack_message (1, &old_irql);
  } else {
    str_msg = (int*)file->private_data;
    pstr = (char*)&str_msg[2];
    pstr += str_msg[1]; /* head + offset */
    str_length = str_msg[0] - str_msg[1]; /* length - offset */
  }
  str_length = MIN(str_length, count);

  if (diva_os_copy_to_user(NULL, buf, pstr, str_length)) {
    diva_os_free_tbuffer(0, str_msg);
    file->private_data = 0;
    return (-EFAULT);
  }
  str_msg[1] += str_length;
  if ((str_msg[0] - str_msg[1]) <= 0) {
    diva_os_free_tbuffer(0, str_msg);
    file->private_data = 0;
  }

  return (str_length);
}

static ssize_t
maint_write(struct file *file, const char *buf, size_t count, loff_t * off)
{
  return (-ENODEV);
}                                  

static unsigned int
maint_poll(struct file *file, poll_table * wait)
{
  unsigned int mask = 0;

  poll_wait(file, &msgwaitq, wait);
  mask = POLLOUT | POLLWRNORM;
  if (file->private_data || diva_dbg_q_length ()) {
    mask |= POLLIN | POLLRDNORM;
  }
  return (mask);
}

static int
maint_open(struct inode *ino, struct file *filep)
{
  if (atomic_read(&opened))
      return(-EBUSY);
  else
    atomic_inc(&opened);

  filep->private_data = 0;

  return(0);
}

static int
maint_close(struct inode *ino, struct file *filep)
{
  if (filep->private_data) {
    diva_os_free_tbuffer(0, filep->private_data);
    filep->private_data = 0;
  }

  atomic_dec(&opened);
  return(0);
}

static int maint_ioctl (struct inode *inode, struct file *file, uint cmd, ulong arg)
{
		return(-EINVAL);
}

static loff_t
maint_lseek(struct file *file, loff_t offset, int orig)
{
  return(-ESPIPE);
}

/*
 * fops
 */
static struct file_operations maint_fops =
{
	owner:          THIS_MODULE,
  llseek:         maint_lseek,
  read:           maint_read,
  write:          maint_write,
  poll:           maint_poll,
  ioctl:          maint_ioctl,
  open:           maint_open,
  release:        maint_close,
};

static int DIVA_INIT_FUNCTION
create_maint_proc(void)
{
  maint_proc_entry = create_proc_entry("maint", S_IFREG | S_IRUGO | S_IWUSR, proc_net_isdn_eicon);
  if (!maint_proc_entry)
    return(0);

  maint_proc_entry->proc_fops = &maint_fops;   
  maint_proc_entry->owner = THIS_MODULE;

  return(1);
}

static void
remove_maint_proc(void)
{
  if (maint_proc_entry) {
    remove_proc_entry("maint", proc_net_isdn_eicon);
    maint_proc_entry = NULL;
  }
}

/*
 * device node operations
 */
static ssize_t divas_maint_write(struct file * file, const char * buf,
            size_t count, loff_t *ppos)
{
		return(maint_read_write((char *)buf));
}

static ssize_t divas_maint_read(struct file * file, char * buf,
            size_t count, loff_t *ppos)
{
		return(maint_read_write(buf));
}

static struct file_operations divas_maint_fops =
{
		owner:			THIS_MODULE,
		llseek:			maint_lseek,
		read:				divas_maint_read,
		write:			divas_maint_write,
		poll:				maint_poll,
		ioctl:			maint_ioctl,
		open:				maint_open,
		release:		maint_close,
};

static void divas_maint_unregister_chrdev(void)
{
		if (devfs_handle)
				devfs_unregister (devfs_handle);
		unregister_chrdev(major, "DivasMAINT");
}

static int DIVA_INIT_FUNCTION divas_maint_register_chrdev(void)
{
		if (register_chrdev (major, "DivasMAINT", &divas_maint_fops))
		{
				printk(KERN_ERR "%s: failed to create /dev entry.\n", DRIVERLNAME);
				return(0);
		}
		devfs_handle = devfs_register(NULL, "DivasMAINT", DEVFS_FL_DEFAULT, major, 0,
					S_IFCHR | S_IRUSR | S_IWUSR, &divas_maint_fops, NULL);

		return(1);
}

/*
 * wake up reader
 */
void diva_maint_wakeup_read (void)
{
		wake_up_interruptible (&msgwaitq);
}

/*
 *  Driver Load
 */
static int DIVA_INIT_FUNCTION maint_init(void)
{
  char tmprev[50];
  int ret = 0;
  void* buffer = 0;

  MOD_INC_USE_COUNT;

  do_gettimeofday (&start_time);
  init_waitqueue_head(&msgwaitq);

  printk(KERN_INFO "%s\n", DRIVERNAME);
  printk(KERN_INFO "%s: Rel:%s  Rev:", DRIVERLNAME, DRIVERRELEASE);
  strcpy(tmprev, main_revision);
  printk("%s  Build: %s  Major: %d\n", getrev(tmprev), DIVA_BUILD, major);

	if (!divas_maint_register_chrdev())
	{
			ret = -EIO;
			goto out;
	}
  if (!create_maint_proc()) {
    printk(KERN_ERR "%s: failed to create proc entry.\n", DRIVERLNAME);
		divas_maint_unregister_chrdev();
    ret = -EIO;
    goto out;
  }

  if (!(mntfunc_init(&buffer_length, &buffer, diva_dbg_mem))) {
    printk(KERN_ERR "%s: failed to connect to DIDD.\n", DRIVERLNAME);
  	remove_maint_proc();
		divas_maint_unregister_chrdev();
    ret = -EIO;
    goto out;
  }

  printk(KERN_INFO "%s: trace buffer = %p - %d kBytes, %s \n",
         DRIVERLNAME, buffer, buffer_length, (diva_dbg_mem == 0) ? "internal" : "external");

out:
  MOD_DEC_USE_COUNT;
  return(ret);  
}

/*
**  Driver Unload
*/
static void DIVA_EXIT_FUNCTION maint_exit(void)
{
  remove_maint_proc();
	divas_maint_unregister_chrdev();
	mntfunc_finit();

  printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME);
}

module_init(maint_init);
module_exit(maint_exit);

