/*
 * procseq.c:  example of using /proc for debug output, using seq_file.
 * Copyright (C) 2002-2003 Randy Dunlap <rddunlap@ieee.org>
 */

/******************************************************************
#   This program 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.
#
#   This program 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 this program; if not, write to the Free Software
#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
******************************************************************/

#define MODULE

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

/*
* This module can be used for printing debug info in /proc/numseq .
* /proc/numseq is currently read-only.
* It could be made to accept written data also.
*
* The seq_file interface for /procfs was introduced in Linux 2.4.15.
* You won't find it in kernels before that version.
*
* Using seq_file for reading /procfs files has advantages over other
* (prior) proc_read methods.  Some of the advantages are:
* - kernel API is cleaner
*   (it is defined in linux/include/linux/seq_file.h)
* - don't have to be concerned about output buffer overflow
*   or truncation
* -
*
*  version 0.2:	add MODULE_PARM(inputstr); for testing.
*  version 0.3: add a little 2.5 external module support + Makefile;
*  version 0.4: add usage/testing of single_open() seq_files;
*/

#define VERSION		"0.4"
#define MODNAME		"numseq-debug"
#define NUMS_SIZE	5

extern struct proc_dir_entry proc_root;
static struct proc_dir_entry *numdir;
static struct proc_dir_entry *sgldir;
static int numbers_current[NUMS_SIZE];
static char *inputstr;	// NULL ptr if not set

/*
 * numseq seq_operations
 */

static void *ns_start(struct seq_file *seq, loff_t *pos)
{
	//loff_t k = *pos;
	// return 0 => nothing to show; _stop will be called
	return *pos < NUMS_SIZE ? &numbers_current[*pos] : 0;
}

static void *ns_next(struct seq_file *seq, void *v, loff_t *pos)
{
	++*pos;
	return *pos < NUMS_SIZE ? &numbers_current[*pos] : 0;
}

/* free, release mem, locks, etc., as needed */
static void ns_stop(struct seq_file *seq, void *v)
{
	seq_puts (seq, "=====\n");
}

/*
 * For a fixed-size output (amount of data), this could
 * all be done at one call of ns_show(), but this exampe
 * uses the iterator method to show the general purpose
 * usefullness of seq_file operations.
 */
static int ns_show(struct seq_file *seq, void *v)
{
	int *valp = v;
	//int val = *valp;	// current value of item
	int ix = valp - numbers_current; // get array index

	if (valp == (int *)&numbers_current) { // or ix == 0
		/* or check for beginning of a data struct or array or list */
        	seq_printf(seq, "module_name: %s\n", MODNAME);
        	seq_printf(seq, "version: %s\n", VERSION);
	}

	/* could seq_puts() or seq_printf() any number of items here */

	/* give numbers_current[] some flavor by incrementing
	 * them differently, then show the new value */
	numbers_current[ix] += ix + 1;

	/* for each <ix> value, show numbers_current[ix] */
	seq_printf(seq, "[%d] = %d\n", ix, numbers_current[ix]);

	return 0;
}

struct seq_operations ns_ops = {
	start:	ns_start,
	next:	ns_next,
	stop:	ns_stop,
	show:	ns_show
};

/*
 * numseq file_operations
 */

/*
 * In this example, the <write> function doesn't write the
 * passed data.  It is only used to reset the <numbers> counters.
 */
static int numseq_write (struct file *file, const char *buf,
		size_t count, loff_t *off)
{
	memset (numbers_current, 0, sizeof (numbers_current));
	return count < sizeof (numbers_current) ? count : sizeof (numbers_current);
}

static int numseq_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &ns_ops);
}

static struct file_operations ns_fops = {
	open:		numseq_open,
	read:		seq_read,
	write:		numseq_write,
	llseek:		seq_lseek,
	release:	seq_release,
};

static int sgl_show (struct seq_file *seq, void *v)
{
	int ix;

	seq_printf (seq, "all %d numbers:\n", NUMS_SIZE);
	for (ix = 0; ix < NUMS_SIZE; ix++)
		seq_printf (seq, "[%d] = %d\n", ix, numbers_current [ix]);
	seq_printf (seq, "fini;\n");
	return 0;
}

static int sglseq_open(struct inode *inode, struct file *file)
{
	return single_open(file, sgl_show, NULL);
}

static struct file_operations ss_fops = {
	open:		sglseq_open,
	read:		seq_read,
	llseek:		seq_lseek,
	release:	single_release,
};

static int __init parse_input (char *input)
{
	int level, next, other, count;

	// format types:	o x=X i* d* u [* = signed]
	printk ("scanning input string:{%s}:\n", input);
	level = next = other = count = 0;
	count = sscanf (input, "%o %i %i", &level, &next, &other);
	printk ("  scanned: %d:  %%o level = 0%o = 0x%x, next = %d, other = %d\n",
			count, level, level, next, other);

	level = next = other = count = 0;
	count = sscanf (input, "%x %i %i", &level, &next, &other);
	printk ("  scanned: %d:  %%x level = %d = 0x%x, next = %d, other = %d\n",
			count, level, level, next, other);

	level = next = other = count = 0;
	count = sscanf (input, "%i %i %i", &level, &next, &other);
	printk ("  scanned: %d:  %%i level = %d = 0x%x, next = %d, other = %d\n",
			count, level, level, next, other);

	level = next = other = count = 0;
	count = sscanf (input, "%d %i %i", &level, &next, &other);
	printk ("  scanned: %d:  %%d level = %d = 0x%x, next = %d, other = %d\n",
			count, level, level, next, other);

	level = next = other = count = 0;
	count = sscanf (input, "%u %i %i", &level, &next, &other);
	printk ("  scanned: %d:  %%u level = %d = 0x%x, next = %d, other = %d\n",
			count, level, level, next, other);
	return 0;
}

static int __init numseq_init (void)
{
	numdir = create_proc_entry ("numseq", 0644, &proc_root);
	if (numdir) {
		numdir->owner = THIS_MODULE;
		numdir->uid = 0;
		numdir->proc_fops = &ns_fops;
	}
	else
		printk (KERN_INFO MODNAME ": numdir create error\n");

	sgldir = create_proc_entry ("sglseq", 0644, &proc_root);
	if (sgldir) {
		sgldir->owner = THIS_MODULE;
		sgldir->uid = 0;
		sgldir->proc_fops = &ss_fops;
	}
	else
		printk (KERN_INFO MODNAME ": sgldir create error\n");

	if (inputstr)
		parse_input (inputstr);
	return 0;
}

static void __exit numseq_exit (void)
{
	if (numdir)
		remove_proc_entry ("numseq", NULL);
	if (sgldir)
		remove_proc_entry ("sglseq", NULL);
}

module_init (numseq_init);
module_exit (numseq_exit);
MODULE_LICENSE ("GPL");
MODULE_DESCRIPTION ("procfseq-debug-example");
MODULE_PARM(inputstr, "s");
MODULE_PARM_DESC(inputstr, "some test input string");
