/*
 *
 * linux/drivers/s390/net/qeth_fs.c ($Revision: 1.10 $)
 *
 * Linux on zSeries OSA Express and HiperSockets support
 * This file contains code related to procfs.
 *
 * Copyright 2000,2003 IBM Corporation
 *
 * Author(s): Thomas Spatzier <tspat@de.ibm.com>
 *
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/rwsem.h>

#include "qeth.h"
#include "qeth_mpc.h"
#include "qeth_fs.h"

const char *VERSION_QETH_PROC_C = "$Revision: 1.10 $";

/***** /proc/qeth *****/
#define QETH_PROCFILE_NAME "qeth"
static struct proc_dir_entry *qeth_procfile;

static void *
qeth_procfile_seq_start(struct seq_file *s, loff_t *offset)
{
	struct list_head *next_card = NULL;
	int i = 0;

	down_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);

	if (*offset == 0)
		return SEQ_START_TOKEN;

	/* get card at pos *offset */
	list_for_each(next_card, &qeth_ccwgroup_driver.driver.devices)
		if (++i == *offset)
			return next_card;

	return NULL;
}

static void
qeth_procfile_seq_stop(struct seq_file *s, void* it)
{
	up_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);
}

static void *
qeth_procfile_seq_next(struct seq_file *s, void *it, loff_t *offset)
{
	struct list_head *next_card = NULL;
	struct list_head *current_card;

	if (it == SEQ_START_TOKEN) {
		next_card = qeth_ccwgroup_driver.driver.devices.next;
		if (next_card->next == next_card) /* list empty */
			return NULL;
		(*offset)++;
	} else {
		current_card = (struct list_head *)it;
		if (current_card->next == &qeth_ccwgroup_driver.driver.devices)
			return NULL; /* end of list reached */
		next_card = current_card->next;
		(*offset)++;
	}

	return next_card;
}

static inline const char *
qeth_get_router_str(struct qeth_card *card, int ipv)
{
	int routing_type = 0;

	if (ipv == 4){
		routing_type = card->options.route4.type;
	} else {
#ifdef CONFIG_QETH_IPV6
		routing_type = card->options.route6.type;
#else
		return "n/a";
#endif /* CONFIG_QETH_IPV6 */
	}

	if (routing_type == PRIMARY_ROUTER)
		return "pri";
	else if (routing_type == SECONDARY_ROUTER)
		return "sec";
	else if (routing_type == MULTICAST_ROUTER) {
		if (card->info.broadcast_capable == QETH_BROADCAST_WITHOUT_ECHO)
			return "mc+";
		return "mc";
	} else if (routing_type == PRIMARY_CONNECTOR) {
		if (card->info.broadcast_capable == QETH_BROADCAST_WITHOUT_ECHO)
			return "p+c";
		return "p.c";
	} else if (routing_type == SECONDARY_CONNECTOR) {
		if (card->info.broadcast_capable == QETH_BROADCAST_WITHOUT_ECHO)
			return "s+c";
		return "s.c";
	} else if (routing_type == NO_ROUTER)
		return "no";
	else
		return "unk";
}

static int
qeth_procfile_seq_show(struct seq_file *s, void *it)
{
	struct device *device;
	struct qeth_card *card;
	char tmp[12]; /* for qeth_get_prioq_str */

	if (it == SEQ_START_TOKEN){
		seq_printf(s, "devices                    CHPID interface  "
		              "cardtype       port chksum prio-q'ing rtr4 "
			      "rtr6 fsz   cnt\n");
		seq_printf(s, "-------------------------- ----- ---------- "
			      "-------------- ---- ------ ---------- ---- "
			      "---- ----- -----\n");
	} else {
		device = list_entry(it, struct device, driver_list);
		card = device->driver_data;
		seq_printf(s, "%s/%s/%s x%02X   %-10s %-14s %-4i ",
				CARD_RDEV_ID(card),
				CARD_WDEV_ID(card),
				CARD_DDEV_ID(card),
				card->info.chpid,
				card->info.if_name,
				qeth_get_cardname_short(card),
				card->info.portno);
		if (card->lan_online)
			seq_printf(s, "%-6s %-10s %-4s %-4s %-5s %-5i\n",
					qeth_get_checksum_str(card),
					qeth_get_prioq_str(card, tmp),
					qeth_get_router_str(card, 4),
					qeth_get_router_str(card, 6),
					qeth_get_bufsize_str(card),
					card->qdio.in_buf_pool.buf_count);
		else
			seq_printf(s, "  +++ LAN OFFLINE +++\n");
	}
	return 0;
}

static struct seq_operations qeth_procfile_seq_ops = {
	.start = qeth_procfile_seq_start,
	.stop  = qeth_procfile_seq_stop,
	.next  = qeth_procfile_seq_next,
	.show  = qeth_procfile_seq_show,
};

static int
qeth_procfile_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &qeth_procfile_seq_ops);
}

static struct file_operations qeth_procfile_fops = {
	.owner   = THIS_MODULE,
	.open    = qeth_procfile_open,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release,
};

/***** /proc/qeth_perf *****/
#define QETH_PERF_PROCFILE_NAME "qeth_perf"
static struct proc_dir_entry *qeth_perf_procfile;

#ifdef CONFIG_QETH_PERF_STATS

static void *
qeth_perf_procfile_seq_start(struct seq_file *s, loff_t *offset)
{
	struct list_head *next_card = NULL;
	int i = 0;

	down_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);
	/* get card at pos *offset */
	list_for_each(next_card, &qeth_ccwgroup_driver.driver.devices){
		if (i == *offset)
			return next_card;
		i++;
	}
	return NULL;
}

static void
qeth_perf_procfile_seq_stop(struct seq_file *s, void* it)
{
	up_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);
}

static void *
qeth_perf_procfile_seq_next(struct seq_file *s, void *it, loff_t *offset)
{
	struct list_head *current_card = (struct list_head *)it;

	if (current_card->next == &qeth_ccwgroup_driver.driver.devices)
		return NULL; /* end of list reached */
	(*offset)++;
	return current_card->next;
}

static int
qeth_perf_procfile_seq_show(struct seq_file *s, void *it)
{
	struct device *device;
	struct qeth_card *card;

	device = list_entry(it, struct device, driver_list);
	card = device->driver_data;
	seq_printf(s, "For card with devnos %s/%s/%s (%s):\n",
			CARD_RDEV_ID(card),
			CARD_WDEV_ID(card),
			CARD_DDEV_ID(card),
			card->info.if_name
		  );
	seq_printf(s, "  Skb's/buffers received                 : %li/%i\n"
		      "  Skb's/buffers sent                     : %li/%i\n\n",
		        card->stats.rx_packets, card->perf_stats.bufs_rec,
		        card->stats.tx_packets, card->perf_stats.bufs_sent
		  );
	seq_printf(s, "  Skb's/buffers sent without packing     : %li/%i\n"
		      "  Skb's/buffers sent with packing        : %i/%i\n\n",
		   card->stats.tx_packets - card->perf_stats.skbs_sent_pack,
		   card->perf_stats.bufs_sent - card->perf_stats.bufs_sent_pack,
		   card->perf_stats.skbs_sent_pack,
		   card->perf_stats.bufs_sent_pack
		  );
	seq_printf(s, "  Packing state changes no pkg.->packing : %i/%i\n"
		      "  Watermarks L/H                         : %i/%i\n"
		      "  Current buffer usage (outbound q's)    : "
		      "%i/%i/%i/%i\n\n",
		        card->perf_stats.sc_dp_p, card->perf_stats.sc_p_dp,
			QETH_LOW_WATERMARK_PACK, QETH_HIGH_WATERMARK_PACK,
			atomic_read(&card->qdio.out_qs[0]->used_buffers),
			(card->qdio.no_out_queues > 1)?
				atomic_read(&card->qdio.out_qs[1]->used_buffers)
				: 0,
			(card->qdio.no_out_queues > 2)?
				atomic_read(&card->qdio.out_qs[2]->used_buffers)
				: 0,
			(card->qdio.no_out_queues > 3)?
				atomic_read(&card->qdio.out_qs[3]->used_buffers)
				: 0
		  );
	seq_printf(s, "  Inbound handler time (in us)           : %i\n"
		      "  Inbound handler count                  : %i\n"
		      "  Inbound do_QDIO time (in us)           : %i\n"
		      "  Inbound do_QDIO count                  : %i\n\n"
		      "  Outbound handler time (in us)          : %i\n"
		      "  Outbound handler count                 : %i\n\n"
		      "  Outbound time (in us, incl QDIO)       : %i\n"
		      "  Outbound count                         : %i\n"
		      "  Outbound do_QDIO time (in us)          : %i\n"
		      "  Outbound do_QDIO count                 : %i\n",
		        card->perf_stats.inbound_time,
			card->perf_stats.inbound_cnt,
		        card->perf_stats.inbound_do_qdio_time,
			card->perf_stats.inbound_do_qdio_cnt,
			card->perf_stats.outbound_handler_time,
			card->perf_stats.outbound_handler_cnt,
			card->perf_stats.outbound_time,
			card->perf_stats.outbound_cnt,
		        card->perf_stats.outbound_do_qdio_time,
			card->perf_stats.outbound_do_qdio_cnt
		  );

	return 0;
}

static struct seq_operations qeth_perf_procfile_seq_ops = {
	.start = qeth_perf_procfile_seq_start,
	.stop  = qeth_perf_procfile_seq_stop,
	.next  = qeth_perf_procfile_seq_next,
	.show  = qeth_perf_procfile_seq_show,
};

static int
qeth_perf_procfile_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &qeth_perf_procfile_seq_ops);
}

static struct file_operations qeth_perf_procfile_fops = {
	.owner   = THIS_MODULE,
	.open    = qeth_perf_procfile_open,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release,
};

#define qeth_perf_procfile_created qeth_perf_procfile
#else
#define qeth_perf_procfile_created 1
#endif /* CONFIG_QETH_PERF_STATS */

/***** /proc/qeth_ipa_takeover *****/
#define QETH_IPATO_PROCFILE_NAME "qeth_ipa_takeover"
static struct proc_dir_entry *qeth_ipato_procfile;

static void *
qeth_ipato_procfile_seq_start(struct seq_file *s, loff_t *offset)
{
	struct list_head *next_card = NULL;
	int i = 0;

	down_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);
	/* TODO: finish this */
	/*
	 * maybe SEQ_SATRT_TOKEN can be returned for offset 0
	 * output driver settings then;
	 * else output setting for respective card
	 */
	/* get card at pos *offset */
	list_for_each(next_card, &qeth_ccwgroup_driver.driver.devices){
		if (i == *offset)
			return next_card;
		i++;
	}
	return NULL;
}

static void
qeth_ipato_procfile_seq_stop(struct seq_file *s, void* it)
{
	up_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);
}

static void *
qeth_ipato_procfile_seq_next(struct seq_file *s, void *it, loff_t *offset)
{
	struct list_head *current_card = (struct list_head *)it;

	/* TODO: finish this */
	/*
	 * maybe SEQ_SATRT_TOKEN can be returned for offset 0
	 * output driver settings then;
	 * else output setting for respective card
	 */
	if (current_card->next == &qeth_ccwgroup_driver.driver.devices)
		return NULL; /* end of list reached */
	(*offset)++;
	return current_card->next;
}

static int
qeth_ipato_procfile_seq_show(struct seq_file *s, void *it)
{
	struct device *device;
	struct qeth_card *card;

	/* TODO: finish this */
	/*
	 * maybe SEQ_SATRT_TOKEN can be returned for offset 0
	 * output driver settings then;
	 * else output setting for respective card
	 */
	device = list_entry(it, struct device, driver_list);
	card = device->driver_data;

	return 0;
}

static struct seq_operations qeth_ipato_procfile_seq_ops = {
	.start = qeth_ipato_procfile_seq_start,
	.stop  = qeth_ipato_procfile_seq_stop,
	.next  = qeth_ipato_procfile_seq_next,
	.show  = qeth_ipato_procfile_seq_show,
};

static int
qeth_ipato_procfile_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &qeth_ipato_procfile_seq_ops);
}

static struct file_operations qeth_ipato_procfile_fops = {
	.owner   = THIS_MODULE,
	.open    = qeth_ipato_procfile_open,
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release,
};

int __init
qeth_create_procfs_entries(void)
{
	qeth_procfile = create_proc_entry(QETH_PROCFILE_NAME,
					   S_IFREG | 0444, NULL);
	if (qeth_procfile)
		qeth_procfile->proc_fops = &qeth_procfile_fops;

#ifdef CONFIG_QETH_PERF_STATS
	qeth_perf_procfile = create_proc_entry(QETH_PERF_PROCFILE_NAME,
					   S_IFREG | 0444, NULL);
	if (qeth_perf_procfile)
		qeth_perf_procfile->proc_fops = &qeth_perf_procfile_fops;
#endif /* CONFIG_QETH_PERF_STATS */

	qeth_ipato_procfile = create_proc_entry(QETH_IPATO_PROCFILE_NAME,
					   S_IFREG | 0444, NULL);
	if (qeth_ipato_procfile)
		qeth_ipato_procfile->proc_fops = &qeth_ipato_procfile_fops;

	if (qeth_procfile &&
	    qeth_ipato_procfile &&
	    qeth_perf_procfile_created)
		return 0;
	else
		return -ENOMEM;
}

void __exit
qeth_remove_procfs_entries(void)
{
	if (qeth_procfile)
		remove_proc_entry(QETH_PROCFILE_NAME, NULL);
	if (qeth_perf_procfile)
		remove_proc_entry(QETH_PERF_PROCFILE_NAME, NULL);
	if (qeth_ipato_procfile)
		remove_proc_entry(QETH_IPATO_PROCFILE_NAME, NULL);
}


/* ONLY FOR DEVELOPMENT! -> make it as module */
/*
static void
qeth_create_sysfs_entries(void)
{
	struct device *dev;

	down_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);

	list_for_each_entry(dev, &qeth_ccwgroup_driver.driver.devices,
			driver_list)
		qeth_create_device_attributes(dev);

	up_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);
}

static void
qeth_remove_sysfs_entries(void)
{
	struct device *dev;

	down_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);

	list_for_each_entry(dev, &qeth_ccwgroup_driver.driver.devices,
			driver_list)
		qeth_remove_device_attributes(dev);

	up_read(&qeth_ccwgroup_driver.driver.bus->subsys.rwsem);
}

static int __init
qeth_fs_init(void)
{
	printk(KERN_INFO "qeth_fs_init\n");
	qeth_create_procfs_entries();
	qeth_create_sysfs_entries();

	return 0;
}

static void __exit
qeth_fs_exit(void)
{
	printk(KERN_INFO "qeth_fs_exit\n");
	qeth_remove_procfs_entries();
	qeth_remove_sysfs_entries();
}


module_init(qeth_fs_init);
module_exit(qeth_fs_exit);

MODULE_LICENSE("GPL");
*/
