#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/kernel.h>

static int tracek_on = 0;

struct tracek_rec
{
  unsigned time;
  const char* fmt;
  unsigned p1,p2;
};

#define TRACEK_SIZE_LOG 14

static struct tracek_rec tracek_log[1 << TRACEK_SIZE_LOG];
static unsigned tracek_index = 0;

static void *tracek_seqstart(struct seq_file *s, loff_t *pos)
{
  tracek_halt();
  if (*pos >= (1 << TRACEK_SIZE_LOG)) return 0;
  return tracek_log + *pos;
}

static void *tracek_seqnext(struct seq_file *s, void *v, loff_t *pos)
{
  (*pos)++;
  if (*pos >= (1 << TRACEK_SIZE_LOG)) return 0;
  return tracek_log + *pos;
}

static void tracek_seqstop(struct seq_file *f, void *v)
{
}

static int tracek_seqshow(struct seq_file *s, void *v)
{
  struct tracek_rec *r = (struct tracek_rec*)v;

  if (!r->fmt) return 0;
  seq_printf(s, "%09u: ", r->time / 8 * 7 / 6);
  seq_printf(s, r->fmt, r->p1, r->p2);
  seq_printf(s, "\n");
  return 0;
}

static struct seq_operations timedoctor_seq_ops = {
	.start = tracek_seqstart,
	.next  = tracek_seqnext,
	.stop  = tracek_seqstop,
	.show  = tracek_seqshow
};

static int tracek_openproc(struct inode* inode, struct file *file)
{
	return seq_open(file, &timedoctor_seq_ops);
}

static ssize_t tracek_writeproc(struct file *file, const char __user *buffer,
                                size_t cnt, loff_t *data)
{
  char c;

  if (!cnt) return 0;
  if (copy_from_user(&c, buffer, 1)) return -EFAULT;

  if (c == '0') tracek_halt();
  if (c == '1') tracek_start();

  return cnt;
}

static struct file_operations tracek_proc_fops = {
	.owner   = THIS_MODULE,
	.open    = tracek_openproc,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release,
	.write   = tracek_writeproc,
};

void tracek(const char* fmt, unsigned a, unsigned b)
{
  unsigned long flags;
  local_save_flags(flags);
  local_irq_disable();

  if (tracek_on)
  {
    struct tracek_rec* r = tracek_log + (tracek_index++ & ((1 << TRACEK_SIZE_LOG) - 1));
    r->time = *(volatile unsigned*) 0xb7e07014;
    r->fmt = fmt;
    r->p1 = a;
    r->p2 = b;
  }

  local_irq_restore(flags);
}
EXPORT_SYMBOL(tracek);

void tracek_start() { 
  tracek_on = 1; 
  tracek("tracek_start()",0,0);
}
EXPORT_SYMBOL(tracek_start);

void tracek_halt() { 
  tracek("tracek_halt(%d)",tracek_index >> TRACEK_SIZE_LOG,0);
  tracek_on = 0; 
}
EXPORT_SYMBOL(tracek_halt);

static int __init tracek_init(void)
{
  static struct proc_dir_entry *entry;

  memset(tracek_log, 0, sizeof(tracek_log));
  entry = create_proc_entry("tracelog", S_IFREG | S_IRUGO | S_IWUSR, NULL); 

  if (!entry)
  {
    printk("tracek create_proc_entry : failed\n");
    return -ENOMEM;
  }
  else
  {
    entry->proc_fops = &tracek_proc_fops;
  }

  tracek_start();

  return 0;
} 

module_init(tracek_init);
