/*
 * EMLOG: the EMbedded-device LOGger
 *
 * Jeremy Elson
 * USC/ISI
 *
 * $Id: emlog.c,v 1.2 2001/03/01 09:24:52 jelson Exp $
 */


#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/tqueue.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#ifdef MODULE
#include <linux/module.h>
#endif
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/poll.h>

/* network interface headers */
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/random.h>

#include <asm/uaccess.h>


#include "emlog.h"

MODULE_PARM(emlog_debug, "i");

struct emlog_info *emlog_info_list = NULL;
static int emlog_debug;

#define MIN(x, y) ((x) < (y) ? (x) : y)


/* find the emlog-info structure associated with an inode.  returns a
 * pointer to the structure if found, NULL if not found */
static struct emlog_info *get_einfo(struct inode *inode)
{
  struct emlog_info *einfo;

  if (inode == NULL)
    return NULL;

  for (einfo = emlog_info_list; einfo != NULL; einfo = einfo->next)
    if (einfo->i_ino == inode->i_ino)
      return einfo;

  return NULL;
}


/* create a new emlog buffer and its associated info structure.
 * returns an errno on failure, or 0 on success.  on success, the
 * pointer to the new struct is passed back using peinfo */
static int create_einfo(struct inode *inode, int minor,
                        struct emlog_info **peinfo)
{
  struct emlog_info *einfo;

  /* make sure the memory requirement is legal */
  if (minor < 1 || minor > EMLOG_MAX_SIZE)
    return -EINVAL;

  /* allocate space for our metadata */
  if ((einfo = kmalloc(sizeof(struct emlog_info), GFP_KERNEL)) == NULL)
    goto struct_malloc_failed;

  /* figure out how much of a buffer this should be and allocate the buffer */
  einfo->size = 1024 * minor;
  if ((einfo->data = kmalloc(sizeof(char)*einfo->size, GFP_KERNEL))==NULL)
    goto data_malloc_failed;

  /* init the rest of the structure */
  einfo->i_ino = inode->i_ino;
  einfo->refcount = 0;
  einfo->read_point = 0;
  einfo->write_point = 0;

#if defined(DECLARE_WAIT_QUEUE_HEAD)
  init_waitqueue_head(&einfo->read_q);
#else
  init_waitqueue(&einfo->read_q);
#endif

  /* add it to our linked list */
  einfo->next = emlog_info_list;
  emlog_info_list = einfo;

  if (emlog_debug)
    printk("allocating resources associated with inode %d\n", einfo->i_ino);

  /* pass the struct back */
  *peinfo = einfo;
  return 0;

 other_failure: /* if we check for other errors later, jump here */
  kfree(einfo->data);
 data_malloc_failed:
  kfree(einfo);
 struct_malloc_failed:
  return -ENOMEM;
}


/* this frees all data associated with an emlog_info buffer, including
 * the struct that you pass to the function.  don't dereference this
 * structure after calling free_einfo! */
void free_einfo(struct emlog_info *einfo)
{
  struct emlog_info **ptr;

  if (einfo == NULL) {
    printk("null passed to free_einfo... which is bad\n");
    return;
  }

  if (emlog_debug)
    printk("freeing resources associated with inode %d\n", einfo->i_ino);

  kfree(einfo->data);

  /* now delete the 'einfo' structure from the linked list.  'ptr' is
   * the pointer that needs to be changed... which is either the list
   * head or one of the 'next' pointers on the list. */
  ptr = &emlog_info_list;
  while (*ptr != einfo) {
    if (!*ptr) {
      printk("corrupt einfo list!\n");
      break;
    } else
    ptr = &((**ptr).next);

  }
  *ptr = einfo->next;


}



/************************ File Interface Functions ************************/



static int emlog_open(struct inode *inode, struct file *file)
{
  int minor = MINOR(inode->i_rdev);

  struct emlog_info *einfo = NULL;
  int retval;

  if ((einfo = get_einfo(inode)) == NULL) {
    /* never heard of this inode before... create a new record */
    if ((retval = create_einfo(inode, minor, &einfo)) < 0)
      return retval;
  }

  if (einfo == NULL) {
    printk("BUG IN EMLOG!\n");
    return -EIO;
  }

  EMLOG_REFCOUNT(einfo)++;
  MOD_INC_USE_COUNT;
  return 0;
}


/* this is called when a file is closed */
static int emlog_release(struct inode *inode, struct file *file)
{
  struct emlog_info *einfo;

  MOD_DEC_USE_COUNT;

  /* get the buffer info */
  if ((einfo = get_einfo(inode)) == NULL) {
    printk("emlog: releasing unknown file!  zoinks!\n");
    return -EINVAL;
  }

  /* decrement the reference count.  if no one has this file open and
   * it's not holding any data, delete the record. */
  einfo->refcount--;

  if (einfo->refcount == 0 && EMLOG_EMPTY(einfo))
    free_einfo(einfo);

  return 0;
}


/* read_from_emlog reads bytes out of a circular buffer with
 * wraparound.  returns caddr_t, pointer to data read, which the
 * caller must free.  length is the number of bytes to be read, which
 * we assume is <= the number of bytes available. */
caddr_t read_from_emlog(struct emlog_info *einfo, int length)
{
  caddr_t retval;
  int bytes_copied = 0, n;
  
  if (length > EMLOG_SIZE(einfo)) {
    printk("emlog: trying to read more (%d) than we have (%d)\n",
           length, EMLOG_SIZE(einfo));
    return NULL;
  }

  if ((retval = kmalloc(sizeof(char) * length, GFP_KERNEL)) == NULL)
    return NULL;

  while (length) {
    n = MIN(length, einfo->size - einfo->read_point /* contiguous bytes */);
    memcpy(retval + bytes_copied, einfo->data + einfo->read_point, n);
    bytes_copied += n;
    length -= n;
    einfo->read_point = (einfo->read_point + n) % einfo->size;
  }

  return retval;
}

static ssize_t emlog_read(struct file *file,
    char *buffer,    /* The buffer to fill with data */
    size_t length,   /* The length of the buffer */
    loff_t *offset)  /* Our offset in the file */
{
  int n, retval;
  caddr_t data_to_return;
  struct emlog_info *einfo;

  if ((einfo = get_einfo(file->f_dentry->d_inode)) == NULL) {
    printk("emlog_read: record not found\n");
    return -EIO;
  }

  /* wait until there's data available (unless we do nonblocking reads) */
  while (EMLOG_EMPTY(einfo)) {
    if (file->f_flags & O_NONBLOCK)
      return -EAGAIN;

    interruptible_sleep_on(EMLOG_READQ(einfo));

    /* see if a signal woke us up */
    if (signal_pending(current))
      return -ERESTARTSYS;
  }

  /* read the data out of the internal buffer.  the following two
   * statements must be must be atomic with respect to
   * emlog_info... okay since syscalls are not interrupted */
  n = MIN(length, EMLOG_SIZE(einfo));
  if ((data_to_return = read_from_emlog(einfo, n)) == NULL)
    return -EIO;

  /* another thread might run here if we block accessing userspace (in
   * copy_to_user).  this is why i remove the message from the read
   * queue before copying it back to the user -- to preserve the
   * atomicity of read_from_emlog.  otherwise, a block here might
   * cause the same data to be returned to two different threads
   * trying to read from the same device.
   *
   * note that this function is reentrant by virtue of the fact that
   * each thread has its own stack, and we keep the message
   * temporarily buffered in local variables (i.e. on the stack) */
  if (copy_to_user(buffer, data_to_return, n) > 0)
    retval = -EFAULT;
  else
    retval = n;
  kfree(data_to_return);
  return retval;
}



/* write_to_emlog writes to a circular buffer with wraparound.  in the
 * case of an overflow, it overwrites the oldest unread data. */
void write_to_emlog(struct emlog_info *einfo, caddr_t buf, int length)
{
  caddr_t retval;
  int bytes_copied = 0, n;
  int overflow;

  overflow = length + EMLOG_SIZE(einfo) > einfo->size;

  while (length) {
    n = MIN(length, einfo->size - einfo->write_point /* contiguous bytes */);
    memcpy(einfo->data + einfo->write_point, buf + bytes_copied, n);
    bytes_copied += n;
    length -= n;
    einfo->write_point = (einfo->write_point + n) % einfo->size;
  }

  /* if there is an overflow, reset the read point to read whatever is
   * the oldest data that we have, that has not yet been
   * overwritten. */
  if (overflow)
    einfo->read_point = (einfo->write_point + 1) % einfo->size;
}


static ssize_t emlog_write(struct file *file,
    const char *buffer,
    size_t length,
    loff_t *offset)
{
  caddr_t message = NULL;
  int n;
  struct emlog_info *einfo;

  if ((einfo = get_einfo(file->f_dentry->d_inode)) == NULL)
    return -EIO;

  /* if the message is longer than the buffer, just take the beginning
   * of it, in hopes that the reader (if any) will have time to read
   * before we wrap around and obliterate it */
  n = MIN(length, einfo->size - 1);

  /* make sure we have the memory for it */
  if ((message = kmalloc(n, GFP_KERNEL)) == NULL)
    return -ENOMEM;

  if (copy_from_user(message, buffer, n) > 0) {
    kfree(message);
    return -EFAULT;
  }

  /* another thread might run here if we end up blocking on the
   * user-space access, so it's important for reentrancy that the
   * "message" variable is local.  see note about atomicity and
   * reentrancy in emlog_read. */
  write_to_emlog(einfo, message, n);
  kfree(message);
  wake_up_interruptible(EMLOG_READQ(einfo));
  schedule(); /* hope that a reader wakes up! */
  return n;
}


static unsigned int emlog_poll(struct file *file, poll_table *wait)
{
  struct emlog_info *einfo;

  if ((einfo = get_einfo(file->f_dentry->d_inode)) == NULL)
    return -EIO;

  poll_wait(file, EMLOG_READQ(einfo), wait);

  if (!EMLOG_EMPTY(einfo))
    return POLLIN | POLLRDNORM;
  else
    return 0;
}


static struct file_operations emlog_fops = {
  read   : emlog_read,
  write  : emlog_write,
  open   : emlog_open,
  release: emlog_release,
  poll   : emlog_poll,
};


int init_module(void)
{
  if (register_chrdev(EMLOG_MAJOR_NUMBER, "emlog", &emlog_fops) < 0) {
    printk("emlog: unable to register character device %d\n",
           EMLOG_MAJOR_NUMBER);
    return -EIO;
  }

  return 0;
}


void cleanup_module(void)
{
  unregister_chrdev(EMLOG_MAJOR_NUMBER, "emlog");

  /* clean up any still-allocated memory */
  while (emlog_info_list != NULL)
    free_einfo(emlog_info_list);
}



