/*
  Copyright 2004, 2005, 2006 Jean-Baptiste Note

  Original islog code from Denis Vlasenko.
  Cleaned up by Pete Zaitcev.
  Relayfs from the relayfs democode, which is
  (C) 2005 - Tom Zanussi (zanussi@us.ibm.com), IBM Corp

  This file is part of prism54usb.

  prism54usb is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  prism54usb is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with prism54usb; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

*/

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>

#include "islsm_log.h"

#if ISL_DEBUG

unsigned int     isl_debug = 0;
module_param_named(debug, isl_debug, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Adjust verbosity of the driver.");
EXPORT_SYMBOL(isl_debug);

void isl_dump_bytes(const void *_data, int num)
{
	const unsigned char *const data = _data;
	const unsigned char *ptr = data;

	if (num <= 0) {
		printk("\n");
		return;
	}

	while (num >= 16) {
		printk(KERN_CRIT "%04x:"
		       "%02X %02X %02X %02X %02X %02X %02X %02X "
		       "%02X %02X %02X %02X %02X %02X %02X %02X\n",
		       (short) (ptr - data),
		       ptr[0], ptr[1], ptr[2], ptr[3],
		       ptr[4], ptr[5], ptr[6], ptr[7],
		       ptr[8], ptr[9], ptr[10], ptr[11],
		       ptr[12], ptr[13], ptr[14], ptr[15]);
		num -= 16;
		ptr += 16;
	}
	if (num > 0) {
		printk(KERN_CRIT "%04x:", (short) (ptr - data));
		while (--num > 0)
			printk("%02X ", *ptr++);
		printk("%02X\n", *ptr);
	}
}

EXPORT_SYMBOL(isl_dump_bytes);

#endif /* ISL_DEBUG */

#if ISL_DEBUG > 1

static int isl_debug_func_indent;

#define DEBUG_TSC 0
#define FUNC_INDENT_INCREMENT 2

#if DEBUG_TSC
#define TIMESTAMP(d) unsigned long d; rdtscl(d)
#else
#define TIMESTAMP(d) unsigned long d = jiffies
#endif

static const char
 spaces[] = "          " "          ";	/* Nx10 spaces */

void
isl_fn_enter(const char *funcname)
{
	int indent;
	TIMESTAMP(d);

	indent = isl_debug_func_indent;
	if (indent >= sizeof (spaces))
		indent = sizeof (spaces) - 1;

	islog_printk(L_FUNC, "%08ld %s==> %s\n",
		     d % 100000000,
		     spaces + (sizeof (spaces) - 1) - indent, funcname);

	isl_debug_func_indent += FUNC_INDENT_INCREMENT;
}

EXPORT_SYMBOL(isl_fn_enter);

void
isl_fn_exit(const char *funcname)
{
	int indent;
	TIMESTAMP(d);

	isl_debug_func_indent -= FUNC_INDENT_INCREMENT;

	indent = isl_debug_func_indent;
	if (indent >= sizeof (spaces))
		indent = sizeof (spaces) - 1;

	islog_printk(L_FUNC, "%08ld %s<== %s\n",
		     d % 100000000,
		     spaces + (sizeof (spaces) - 1) - indent, funcname);
}

EXPORT_SYMBOL(isl_fn_exit);

void
isl_fn_exit_v(const char *funcname, int v)
{
	int indent;
	TIMESTAMP(d);

	isl_debug_func_indent -= FUNC_INDENT_INCREMENT;

	indent = isl_debug_func_indent;
	if (indent >= sizeof (spaces))
		indent = sizeof (spaces) - 1;

	islog_printk(L_FUNC, "%08ld %s<== %s: %08X\n",
		     d % 100000000,
		     spaces + (sizeof (spaces) - 1) - indent, funcname, v);
}

EXPORT_SYMBOL(isl_fn_exit_v);

#endif				/* ISL_DEBUG > 1 */

#if ISL_DEBUG_RELAY > 0

#include <linux/relayfs_fs.h>
#include <linux/debugfs.h>

#define HEAVY_DATA (L_BUFR | L_BUFT | L_DATA)
#define LIGHT_DATA (L_SM_INPUT | L_SM_OUTPUT | L_ALLOC)

unsigned int     isl_relay = ( HEAVY_DATA | LIGHT_DATA );
module_param_named(relay, isl_relay, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(relay, "Adjust the sending of debug information to relay channel.");
EXPORT_SYMBOL(isl_relay);

/* relayfs channel will be /debug/islsm/global0 */
#define ISLSM_DEBUG_DIR "islsm"

#define RELAY_BUF_SIZE 128
/* everything that is needed for a channel */
struct relay_chan {
	char name[10];
	struct rchan *chan;
	struct dentry *dir;
	spinlock_t chan_lock;
	char relay_buf[RELAY_BUF_SIZE];
};

static const size_t subbuf_size = 262144;
static const size_t n_subbufs = 4;
static struct relay_chan channels[1];

/*
 * relayfs callbacks
 */

static struct dentry *
create_buf_file_handler(const char *filename, struct dentry *parent,
			int mode, struct rchan_buf *buf, int *is_global)
{
	struct dentry *buf_file;
	buf_file = debugfs_create_file(filename, mode, parent, buf,
				       &relay_file_operations);
	*is_global = 1;
	return buf_file;
}

static int remove_buf_file_handler(struct dentry *dentry)
{
	debugfs_remove(dentry);
	return 0;
}

static struct rchan_callbacks relayfs_callbacks =
{
	.create_buf_file = create_buf_file_handler,
	.remove_buf_file = remove_buf_file_handler,
};

/*
 * logging functions for use by the modules
 */

/* Used as printk, but logs to relayfs if directed to do so */
int islog_relay(const int dbg, const char *fmt, ...)
{
	spinlock_t *chan_lock = &channels[0].chan_lock;
	struct rchan *chan = channels[0].chan;
	char *relay_buf = channels[0].relay_buf;
	va_list args;
	int r = 0;

	va_start(args, fmt);
 	if (dbg & isl_relay) {
		unsigned long flags;
		spin_lock_irqsave(chan_lock, flags);
		(void) vsnprintf(relay_buf, RELAY_BUF_SIZE, fmt, args);
		relay_write(chan, relay_buf, strlen(relay_buf));
		spin_unlock_irqrestore(chan_lock, flags);
	}
	else {
 		r = vprintk(fmt,args);
	}
	va_end(args);
	return r;
};
EXPORT_SYMBOL(islog_relay);

static void
_isl_dump_relay_bytes(const void *_data, int num)
{
	struct rchan *chan = channels[0].chan;
	char *relay_buf = channels[0].relay_buf;

	const u8 *data = (const u8 *) _data;
	const u8 *ptr = data;

	if (num <= 0) {
		sprintf(relay_buf, "\n");
		relay_write(chan, relay_buf, strlen(relay_buf));
		return;
	}

	while (num >= 16) {
		sprintf(relay_buf, "%04x: "
			"%02X %02X %02X %02X %02X %02X %02X %02X "
			"%02X %02X %02X %02X %02X %02X %02X %02X\n",
			(short) (ptr - data),
			ptr[0], ptr[1], ptr[2], ptr[3],
			ptr[4], ptr[5], ptr[6], ptr[7],
			ptr[8], ptr[9], ptr[10], ptr[11],
			ptr[12], ptr[13], ptr[14], ptr[15]);
		relay_write(chan, relay_buf, strlen(relay_buf));
		num -= 16;
		ptr += 16;
	}
	if (num > 0) {
		sprintf(relay_buf, "%04x:", (short) (ptr - data));
		while (num-- > 0)
			sprintf(relay_buf, " %02X", *ptr++);
		sprintf(relay_buf, "\n");
		relay_write(chan, relay_buf, strlen(relay_buf));
	}
}

/*
 * Dump bytes to the relayfs channel or through the standard printk
 * path.
 */

void islog_bytes_relay(const int dbg, const void *_data, int num)
{
 	if (dbg & isl_relay) {
		unsigned long flags;
		spin_lock_irqsave(&channels[0].chan_lock, flags);
		_isl_dump_relay_bytes(_data, num);
		spin_unlock_irqrestore(&channels[0].chan_lock, flags);
	} else {
		isl_dump_bytes(_data, num);
	}
}
EXPORT_SYMBOL(islog_bytes_relay);

/*
 * initialization functions
 */
int islsm_log_init(void)
{
	struct relay_chan *chan = &channels[0];

	spin_lock_init(&chan->chan_lock);
	snprintf(chan->name, sizeof(chan->name), "datalog");

	chan->dir = debugfs_create_dir(ISLSM_DEBUG_DIR, NULL);
	if (!chan->dir) {
		printk(KERN_ERR "Couldn't create debugfs islsm directory\n");
		return -ENOMEM;
	}

	chan->chan = relay_open(chan->name, chan->dir, subbuf_size,
				n_subbufs, &relayfs_callbacks);
	if (!chan->chan) {
		printk(KERN_ERR "islsm relay channel creation failed\n");
		debugfs_remove(chan->dir);
		return -ENOMEM;
	}

	return 0;
}

/* global for now, move to per-instance */
void islsm_log_cleanup(void)
{
	struct relay_chan *chan = &channels[0];
	if (chan->chan)
		relay_close(chan->chan);
	if (chan->dir)
		debugfs_remove(chan->dir);
}

//#else  /* non-relayfs version */

#endif /* ISL_RELAY_DEBUG > 0 */
