/*
 * (c) Copyright IBM Corp. 2005 All Rights Reserved
 *
 * Physical Memory Information Module
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/**
 * @file mem-task.c
 *
 * @brief Module for task view of physical pages information and usage
 *
 * This part provides the source for the usage of physical pages on a
 * per task basis. It uses the global kernel task list and various
 * kernel internal architecture independent memory management (mm)
 * macros and functions to examine the physical memory usage for
 * each task.
 * @par
 * For each task that is examined, the entire memory map is walked.
 * This includes each virtual memory area (vma) and the physical
 * pages associated with each vma.
 * @par
 * From this information, additional analysis can be performed to
 * determine how each page of physical memory is being utilized
 * by each user space process.
 *
 * @par
 * This is part of the physmem_info module.
 *
 * @author International Business Machines
 * @author Paul Movall <movall@us.ibm.com>
 *
 * @version Current Version: 1.0
 *
 * @date Current Date: 01/2005
 *
 * @version 0.1, 11/2003: File created by Paul Movall <movall@us.ibm.com>
 * @version 1.0, 01/2005: Miscellaneous cleanup for publish
 *
 */

#include <linux/module.h>               /* kernel modules interfaces/macros */
#include <linux/modversions.h>           /* kernel modules interfaces/macros */
#include <linux/slab.h>               /* kmalloc, kfree */
#include <linux/kernel.h>               /* KERN_* */
#include <linux/ctype.h>                /* isprint */
#include <linux/init.h>                 /* __init __exit */
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/mmzone.h>
#include <asm/pgtable.h>
#include <linux/devfs_fs_kernel.h>      /* devfs stuff */
#include <linux/ioctl.h>
#include <asm/uaccess.h>                /* copy_*_user */

#ifndef LINUX_VERSION_CODE
#include <linux/modsetver.h>
#endif

#include "physmem.h"

/* ========================================================================== */
/**
 * @def MAX(x,y) Macro definition to compute the maximum value of x and y.
 */
#define MAX(x,y)	x > y ? x : y

/**
 * @def MIN(x,y) Macro definition to compute the minimum value of x and y.
 */
#define MIN(x,y)	x < y ? x : y

/* various format strings */
/** @def FORMAT_TASK Format string for output of base task information. */
#define FORMAT_TASK    "\nT: %-8.8s, %4d, %p, %5d, %lu, %lu, %lu, %lu, %ld, %ld"
/** @def FORMAT_PTE Format string for output of base PTE information. */
#define FORMAT_PTE     "\nP: %5d, %5d, %5d, %5d, %5d\nM: "
/** @def FORMAT_VMA Format string for output of base VMA information. */
#define FORMAT_VMA     "\nV: %08lX, %08lX, %08lX, %08lX, %08lX, %08lX, %64s"
/** @def FORMAT_MAP Format string for output of virt to real map information. */
#define FORMAT_MAP	"%08lX=%08lX, %5lX, %08lX | "

EXPORT_NO_SYMBOLS;

/* ========================================================================== */
/**
 * @def PHYSMEM_MAJOR_NUM The @c physmem default device major number definition.
 */
#define PHYSMEM_MAJOR_NUM 240

/* ========================================================================== */
MODULE_AUTHOR("Paul Movall <movall@us.ibm.com>");
MODULE_DESCRIPTION("Physical Memory Information module");
MODULE_LICENSE("GPL");

/**
 * @var major
 * @brief Input parameter for the device @c major number.
 *
 * Will use the default value if not set.
 */
int major = PHYSMEM_MAJOR_NUM;
MODULE_PARM(major, "i");
MODULE_PARM_DESC(major, "major device number");


/* ========================================================================== */
#ifdef CONFIG_DEVFS_FS
/** @def DEV_PATH_NAME Path name to the device for reading. */
#define DEV_PATH_NAME	"/dev/task_mem/0"

/**
 * @var devfs_handle
 * @brief The main devfs directory handle for this device.
 */
static devfs_handle_t devfs_handle = NULL;	/* devfs directory handle */
/**
 * @var devfs
 * @brief The devfs node handle for this device.
 */
static devfs_handle_t devfs = NULL;		/* devfs node handle */
#else
/** @def DEV_PATH_NAME Path name to the device for reading. */
#define DEV_PATH_NAME	"/dev/task_mem"
#endif
/**
 * @var procfile_mem_task_debug
 * @brief The /proc node handle for usage / debug information.
 */
static struct proc_dir_entry *procfile_mem_task_debug;

/**
 * @struct page_counts_struct
 * @brief Structure to contain the usage summary of physical pages per task.
 *
 * This structure is used to calculate the usage summary of the physical
 * pages. This is used in many instances to figure out the number of
 * pages of each type (as defined by the fields) are used by the process.
 */
/**
 * @typedef pg_counts_t
 * Type definition for the page_counts_struct structure.
 */
typedef struct page_counts_struct {
	int pg_resident;	/**< Number of pages in physical memory */
	int pte_user;		/**< Resident pages for user space	*/
	int pte_exec;		/**< Resident executable pages		*/
	int pte_rdonly;		/**< Resident read-only pages		*/
	int pg_shared;		/**< Resident pages with > 1 user	*/
	int pg_alone;		/**< Resident pages with only 1 user	*/
	int vma_count;		/**< Number of VMAs in this task	*/
} pg_counts_t;
	
/**
 * @struct page_print_struct
 * @brief Structure to contain the abstracted output buffer and flags.
 *
 * This structure is used to keep track of the output buffer, position
 * in the buffer, end of buffer, and error condition such as too much
 * data for the output buffer.
 */
/**
 * @typedef pg_print_t
 * Type definition for the page_print_struct structure.
 */
typedef struct page_print_struct {
	char *buf;	/**< Pointer to output buffer			*/
	int max_len;	/**< Maximum number of characters in @c buf	*/
	int cur_len;	/**< Current number of characters in @c buf	*/
	int trunc;	/**< Tried to put too many bytes in @c buf	*/
} pg_print_t;

/* ========================================================================== */
/**
 * @brief Walk the VMA and physical page list for the given task.
 *
 * @param cur_task The task for which to walk the VMA and physical page list.
 * @param priv_data Opaque data to be passed to calculator functions.
 * @param vma_cb Function to be called on each new VMA.
 * @param page_cb Function to be called on each resident page.
 *
 * @retval None
 */
void for_each_page_in_task(struct task_struct *cur_task,
			   void *priv_data,
			   void (*vma_cb)(struct vm_area_struct *cur_vma,
					  void *data),
			   void (*page_cb)(struct vm_area_struct *cur_vma,
					    unsigned long vaddr,
					    pte_t *ppte,
					    void *data)) {
	struct vm_area_struct *cur_vma;
	unsigned long vaddr;
	pgd_t *this_ppgd;
	pmd_t *this_ppmd;
	pte_t *this_ppte;

	if (cur_task->mm == NULL) {
		return;
	}

	for (cur_vma = cur_task->mm->mmap;
	     cur_vma != NULL;
	     cur_vma = cur_vma->vm_next) {
		if (vma_cb != NULL) {
			vma_cb(cur_vma,
			       priv_data);
		}

		for (vaddr = cur_vma->vm_start;
		     vaddr < cur_vma->vm_end;
		     vaddr += PAGE_SIZE) {
			this_ppgd = pgd_offset(cur_vma->vm_mm, vaddr);
			if (pgd_none(*this_ppgd) ||
			    pgd_bad(*this_ppgd)) {
				/* skip this one */
			} else {
				this_ppmd = pmd_offset(this_ppgd,
						       vaddr);
				if (pmd_none(*this_ppmd) ||
				    pmd_bad(*this_ppmd)) {
					/* skip this one */
				} else {
					this_ppte =
					  pte_offset(this_ppmd,
						     vaddr);
					if (!this_ppte) {
						/* skip */
					} else if (pte_present(*this_ppte)) {
						if (page_cb != NULL) {
							page_cb(cur_vma,
								vaddr,
								this_ppte,
								priv_data);
						}
					}
				}
			}
		}
	}
}

/* ========================================================================== */
/**
 * @brief Calculate the number of resident pages and categorize by type.
 *
 * @param cur_vma The VMA to which this physical page belongs.
 * @param vaddr The virtual address of this physical page in this task.
 * @param ppte Pointer to the PTE for this physical page.
 * @param priv_data Pointer to data. In this case, it is a pg_counts_t pointer.
 *
 * @pre The @c pg_counts_t pointer must be valid and pointed to structure must
 * be appropriately initialize to zero.
 *
 * @retval None. But the fields in the @c pg_counts_t structure will be updated.
 */
void count_pages(struct vm_area_struct *cur_vma,
		 unsigned long vaddr,
		 pte_t *ppte,
		 void *priv_data) {
	pg_counts_t *pg_cnt = (int *)priv_data;
	mem_map_t *this_page = pte_page(*ppte);

	pg_cnt->pg_resident++;

	if (pte_read(*ppte))
		pg_cnt->pte_user++;

	if (pte_exec(*ppte))
		pg_cnt->pte_exec++;

	if (!pte_write(*ppte))
		pg_cnt->pte_rdonly++;

	if (page_count(this_page) > 1)
		pg_cnt->pg_shared++;
	else
		pg_cnt->pg_alone++;
}

/* ========================================================================== */
/**
 * @brief Copy the local buffer to the user's buffer.
 * This function will validate the size of the buffer and set the error
 * flag if it exceeds the user buffer.
 *
 * @param pg_prn Pointer to @c pg_print_t control block.
 * @param i_buf Local kernel buffer to copy from.
 * @param len Number of bytes to copy from @c i_buf to the user buffer.
 *
 * @retval None. Fields in the @c pg_print_t control block will be updated.
 */
void do_print_copy(pg_print_t *pg_prn, char *i_buf, int len) {

	if ((pg_prn->cur_len + len) < pg_prn->max_len) {
		copy_to_user(&(pg_prn->buf[pg_prn->cur_len]),
			     i_buf,
			     len);
		pg_prn->cur_len += len;
	} else {
		/* set flag that truncated */
		pg_prn->trunc = TRUE;
	}
}

/* ========================================================================== */
/**
 * @brief Format one PTE (virt to real mapping) for output.
 *
 * @param cur_vma The VMA to which this physical page belongs.
 * @param vaddr The virtual address of this physical page.
 * @param ppte Pointer to the PTE for this physical page.
 * @param priv_data Pointer to @c pg_print_t output control block.
 *
 * @retval None.
 */
void print_page(struct vm_area_struct *cur_vma,
		unsigned long vaddr,
		pte_t *ppte,
		void *priv_data) {
	pg_print_t *pg_prn = (int *)priv_data;
	mem_map_t *this_page = pte_page(*ppte);
	pte_t this_pte = *ppte;
	char l_buf[256];
	int nprint;

	nprint = sprintf(l_buf,
			 FORMAT_MAP,
			 vaddr,
			 pte_val(this_pte),
			 (unsigned long)page_count(this_page),
			 this_page->flags);

	do_print_copy(pg_prn,
		      l_buf,
		      nprint);
}

/* ========================================================================== */
/**
 * @brief Format one VMA for output (with PTE output as well).
 *
 * This function will walk all virtual addresses within this VMA looking for
 * resident pages to print with the PTE summary.
 *
 * @param cur_vma The VMA to format for output.
 * @param priv_data Pointer to @c pg_print_t output control block.
 *
 * @retval None.
 */
void print_vma(struct vm_area_struct *cur_vma,
		void *priv_data) {
	pg_print_t *pg_prn = (int *)priv_data;
	pg_counts_t pg_cnt;
	unsigned long vaddr;
	pgd_t *this_ppgd;
	pmd_t *this_ppmd;
	pte_t *this_ppte;
	char l_buf[256];
	int nprint;
	char l_name[64];
	char *line;


	if (cur_vma->vm_file != NULL) {
		line = d_path(cur_vma->vm_file->f_dentry,
			      cur_vma->vm_file->f_vfsmnt,
			      l_name,
			      sizeof(l_name));
	} else {
		strcpy(l_name, "NULL");
		line = l_name;
	}

	memset(&pg_cnt, 0, sizeof(pg_cnt));

	for (vaddr = cur_vma->vm_start;
	     vaddr < cur_vma->vm_end;
	     vaddr += PAGE_SIZE) {
		this_ppgd = pgd_offset(cur_vma->vm_mm, vaddr);
		if (pgd_none(*this_ppgd) ||
		    pgd_bad(*this_ppgd)) {
			/* skip this one */
		} else {
			this_ppmd = pmd_offset(this_ppgd,
					       vaddr);
			if (pmd_none(*this_ppmd) ||
			    pmd_bad(*this_ppmd)) {
				/* skip this one */
			} else {
				this_ppte =
				  pte_offset(this_ppmd,
					     vaddr);
				if (!this_ppte) {
					/* skip */
				} else if (pte_present(*this_ppte)) {
					count_pages(cur_vma,
						    vaddr,
						    this_ppte,
						    &pg_cnt);
				}
			}
		}
	}

	if (!pg_prn->trunc) {
		nprint = sprintf(l_buf,
				 FORMAT_VMA,
				 cur_vma->vm_start,
				 cur_vma->vm_end - 1,
				 (cur_vma->vm_end - cur_vma->vm_start),
				 pg_cnt.pg_resident * PAGE_SIZE,
				 pgprot_val(cur_vma->vm_page_prot),
				 cur_vma->vm_flags,
				 line);

		do_print_copy(pg_prn, l_buf, nprint);
	}

	if (!pg_prn->trunc) {
		nprint = sprintf(l_buf,
				 FORMAT_PTE,
				 pg_cnt.pte_user,
				 pg_cnt.pte_exec,
				 pg_cnt.pte_rdonly,
				 pg_cnt.pg_alone,
				 pg_cnt.pg_shared);

		do_print_copy(pg_prn, l_buf, nprint);
	}

}

/* ========================================================================== */
/**
 * @brief The POSIX standard read interface.
 *
 * This function will walk all virtual addresses within the task and
 * use the various print functions to format the output data.
 *
 * @pre The input buffer length must be large enough to hold all
 * the data for one task. This value can be found out from either
 * the /proc node PROCFILE_MEM_TASK_DEBUG or through the ioctl
 * interface.
 *
 * @param filp Pointer to the file descriptor for this open.
 * @param buf Pointer to output buffer.
 * @param length Size of the output buffer.
 * @param f_pos Pointer to the file position variable. For this
 * device, the file position is a pointer to a task structure.
 *
 * @retval >=0		Number of output bytes placed into @c buf.
 * @retval -EFBIG	Output buffer was not large enough to hold
 *			all of the output data.
 * @retval -EIO		The file descriptor is not pointing to
 *			a valid structure.
 *
 * @todo There is currently no protection for tasks coming
 * and / or going between the various reads. There needs to
 * be some protection when changes occur in the task list.
 */
static int mem_task_read(struct file * filp,
			 char * buf,
			 size_t length,
			 loff_t * f_pos)
{
	struct task_struct *cur_task = (struct task_struct *)(*f_pos);
	mem_task_info_t *cur_info = filp->private_data;
	pg_print_t pg_prn;
	int len;
	int nprint;
	char l_buf[256];

	if (cur_info->valid != MEM_TASK_STRUCT_VALID) {
		return(-EIO);
	}

	if (cur_info->done) {
		len = 0;
		cur_info->done = FALSE;
	} else {
		pg_prn.cur_len = 0;
		pg_prn.buf = buf;
		pg_prn.max_len = length;
		pg_prn.trunc = FALSE;
		strcpy(buf, "");

		/* print the task */
		nprint = sprintf(l_buf,
				 FORMAT_TASK,
				 cur_task->comm,
				 cur_task->pid,
				 cur_task->mm,
#ifdef CONFIG_SMP
				 cur_task->processor
#else
				 0
#endif
				 ,
				 cur_task->min_flt,
				 cur_task->maj_flt,
				 cur_task->cmin_flt,
				 cur_task->cmaj_flt,
				 cur_task->per_cpu_utime[0],
				 cur_task->per_cpu_stime[0]
				 );

		do_print_copy(&pg_prn, l_buf, nprint);

		if (!pg_prn.trunc) {
			for_each_page_in_task(cur_task,
					      &pg_prn,
					      print_vma,
					      print_page);

		}

		if (pg_prn.trunc) {
			len = -EFBIG;
		} else {
			len = pg_prn.cur_len;

			cur_task = cur_task->next_task;
			*f_pos = (loff_t)cur_task;

			if (cur_task == &init_task) {
				cur_info->done = TRUE;
			}
		}
	}

	return(len);
}

/* ========================================================================== */
/**
 * @brief Calculate the number of VMAs in this task.
 *
 * @param cur_vma The current VMA.
 * @param priv_data Pointer to data. In this case, it is a pg_counts_t pointer.
 *
 * @pre The @c pg_counts_t pointer must be valid and pointed to structure must
 * be appropriately initialize to zero.
 *
 * @retval None. But the fields in the @c pg_counts_t structure will be updated.
 */
void count_vmas(struct vm_area_struct *cur_vma,
		void *priv_data) {
	pg_counts_t *pg_cnt = (int *)priv_data;
	pg_cnt->vma_count++;
}

/* ========================================================================== */
/**
 * @brief Calculate the largest single output buffer needed.
 *
 * @retval The number of bytes required to satisfy one read command. This
 * value should then be used as the minimum size of the buffer for the read.
 */
int calc_min_buf(void) {
	char l_buf[256];
	int task_buf;
	int pte_buf;
	int vma_buf;
	int map_buf;
	int this_buf;
	int min_buf = 0;
	struct task_struct *cur_task;
	pg_counts_t pg_cnt;

	task_buf = sprintf(l_buf,
			   FORMAT_TASK,
			   "dummy", 0, NULL, 0,
			   (unsigned long)0, (unsigned long)0, (unsigned long)0,
			   (unsigned long)0, (long)0, (long)0);

	pte_buf = sprintf(l_buf,
			  FORMAT_PTE,
			  0, 0, 0, 0, 0);

	vma_buf = sprintf(l_buf,
			  FORMAT_VMA,
			  (unsigned long)0, (unsigned long)0, (unsigned long)0,
			  (unsigned long)0, (unsigned long)0, (unsigned long)0,
			  "NULL");

	map_buf = sprintf(l_buf,
			  FORMAT_MAP,
			  (unsigned long)0, (unsigned long)0, (unsigned long)0,
			  (unsigned long)0);

	for (cur_task = init_task.next_task;
	     ( (cur_task != &init_task) &&
	       (cur_task != NULL) );
	     cur_task = cur_task->next_task) {

		memset(&pg_cnt, 0, sizeof(pg_cnt));

		for_each_page_in_task(cur_task,
				      &pg_cnt,
				      count_vmas,
				      count_pages);

		this_buf = task_buf + pte_buf +
		  (pg_cnt.vma_count * vma_buf) +
		  (pg_cnt.pg_resident * map_buf);

		min_buf = MAX(min_buf, this_buf);
	}

	return min_buf;
}




/* ========================================================================== */
/**
 * @brief The standard ioctl interface.
 *
 * @param inode The inode that represents this device.
 * @param filp The file descriptor pointer for this open.
 * @param cmd The command to execute against the device.
 * @param arg Arguments to the command. For the PHYSMEM_IOCTL_MIN_SIZE and
 * PHYSMEM_IOCTL_CUR_PID commands, this is a pointer to a user space
 * buffer to copy the 4 bytes of data into.
 *
 * @retval 0		Success
 * @retval -EINVAL	Invalid input parameters.
 * @retval -EBADF	The file descriptor or the private data of the
 *			file descriptor is corrupted.
 */
static int mem_task_ioctl(struct inode * inode,
			  struct file * filp,
			  unsigned int cmd,
			  unsigned long arg) {
	mem_task_info_t *cur_info = filp->private_data;
	struct task_struct *cur_task =
	  (struct task_struct *)(filp->f_pos);
	int rc = 0;
	int buf_sz;

	if (_IOC_TYPE(cmd) != PHYSMEM_IOC_MAGIC) {
		rc = -EINVAL;
	} else if (_IOC_NR(cmd) > PHYSMEM_IOC_MAX) {
		rc = -EINVAL;
	} else if (cur_info->valid != MEM_TASK_STRUCT_VALID) {
		rc = -EBADF;
	} else {
		switch(cmd) {
		case PHYSMEM_IOCTL_MIN_SIZE:
			/* get the minimum size needed for a read */
			buf_sz = calc_min_buf();
			cur_info->min_size = buf_sz;
			copy_to_user((int *)arg,
				    &buf_sz,
				    sizeof(int));
			break;
		case PHYSMEM_IOCTL_CUR_PID:
			/* return the PID of the current task */
			copy_to_user((pid_t *)arg,
				    &(cur_task->pid),
				    sizeof(pid_t));
			break;
		default:
			rc = -EINVAL;
			break;
		}
	}

	return(rc);
}

/* ========================================================================== */
/**
 * @brief The standard open interface.
 *
 * This function prepares the device for additional operations. The
 * private data structure is initialized and the position is set to the
 * init_task task.
 *
 * @param inode The inode that represents this device.
 * @param filp The file descriptor pointer for this open.
 *
 * @retval 0		Success
 * @retval -ENXIO	The private data structure could not be initialized.
 */
static int mem_task_open(struct inode * inode,
			 struct file * filp) {
	mem_task_info_t *this_info;

	this_info = (mem_task_info_t *)kmalloc(sizeof(mem_task_info_t),
					       GFP_KERNEL);
	if (this_info == NULL) {
		/* no "device" */
		return -ENXIO;
	}

	/* initialize the fields of the structure */
	memset(this_info, 0, sizeof(mem_task_info_t));
	this_info->valid = MEM_TASK_STRUCT_VALID;
	this_info->done = FALSE;
	filp->f_pos = (loff_t)&init_task;
		
	/* stash away the device object in the vfs layer */
	filp->private_data = this_info;

	return(0);
}

/* ========================================================================== */
/**
 * @brief The standard release (or close) interface.
 *
 * This function finalizes use of the device. The private data structure is
 * freed.
 *
 * @param inode The inode that represents this device.
 * @param filp The file descriptor pointer for this open.
 *
 * @retval 0		Success
 * @retval -EBADF	The file descriptor or the private data of the
 *			file descriptor is corrupted.
 */
static int mem_task_release(struct inode * inode,
			    struct file * filp) {
	mem_task_info_t *cur_info = filp->private_data;
	int rc = 0;

	if (cur_info->valid != MEM_TASK_STRUCT_VALID) {
		rc = -EBADF;
	} else {
		/* free the "device" structure */
		kfree(cur_info);
		filp->private_data = NULL;
	}

	return(rc);
}

/* ========================================================================== */
/**
 * @brief The standard lseek interface.
 *
 * This function sets the file offset to a specific task.
 *
 * @param filp The file descriptor pointer for this open.
 * @param off The PID of the task to which to set the file offset.
 * @param whence Where to seek from. This device only supports a value of
 * SEEK_SET (value of 0).
 *
 * @retval 0		Success
 * @retval -EBADF	The file descriptor or the private data of the
 *			file descriptor is corrupted.
 * @retval -EINVAL	Invalid input parameters - i.e., @c whence is not 0.
 * @retval -ENOSPC	No space in file - i.e., PID not found.
 */
static loff_t mem_task_pid_set(struct file *filp,
			       loff_t off,
			       int whence)
{
	struct task_struct *cur_task = init_task.next_task;
	mem_task_info_t *cur_info = filp->private_data;
	int rc = -ENOSPC;

	if (whence != 0) {	/* SEEK_SET */
		rc = -EINVAL;
	} else if (cur_info->valid != MEM_TASK_STRUCT_VALID) {
		rc = -EBADF;
	}

	while ((cur_task != &init_task) &&
	       (cur_task != NULL) &&
	       (rc == -ENOSPC) ) {
		if (cur_task->pid == (pid_t)off) {
			rc = 0;
			filp->f_pos = (loff_t)cur_task;
		} else {
			cur_task = cur_task->next_task;
		}
	}

	return(rc);
}

/* ========================================================================== */
/**
 * @brief Help text for debug / infomational /proc node.
 *
 * This /proc node currently returns the maximum output size, which
 * can be used as the minimum output buffer size on the read.
 *
 * @param buf Character string to place the output
 * @param start Offset into "file" of where to start
 * @param off Offset...
 * @param count Number of characters that can be placed in @c buf
 * @param eof Output flag to indicate if end of file has been reached
 * @param data Input data from /proc registration - not used here
 *
 * @retval Number of characters placed in buf
 *
 */
static int show_task_help(char *buf, char **start, off_t off,
		     int count, int *eof, void *data)
{
    int len;
    char l_buf[256];
    int nprint;
    unsigned int buf_sz;

    *eof = 1;

    buf_sz = calc_min_buf() / 1024;

    len = sprintf(buf,
		  "Minimum buffer size: %dkB\n\tExample extraction: \n",
		  buf_sz);
    nprint = sprintf(l_buf,
		     "dd if=%s ibs=%uk of=<file of your choice>\n",
		     DEV_PATH_NAME,
		     buf_sz + (buf_sz / 10));
    strcat(buf,
	   l_buf);
    len += nprint;
    
    return len;
}

/* ========================================================================== */
/**
 * @var mem_task_fops
 * @brief File operations structure for the device.
 */
static struct file_operations mem_task_fops = {
	owner:		THIS_MODULE,		/* struct module *owner */
	ioctl:		mem_task_ioctl,		/* ioctl */
	open:		mem_task_open,		/* open */
	release:	mem_task_release,	/* release */
	read:		mem_task_read,		/* read */
	llseek:		mem_task_pid_set,	/* llseek */
};

/* ========================================================================== */
/**
 * @brief Initialize the task portion of the module.
 *
 * @retval 0 Success.
 * @retval !=0 Failure from device support layer when adding to device tree.
 */
int __init mem_task_module_init(void) {
	int rc = 0;

	/* Cause insmod to fail if we are unable to register the device */
#if defined(CONFIG_DEVFS_FS)
	if ((rc = devfs_register_chrdev(major,
					"task_mem",
					&mem_task_fops)) < 0) {
		printk(KERN_ERR
		       "unable to register 'task_mem' devfs "
		       "character driver using major[%d]\n",
		       major);
		return(rc);
	}

	devfs_handle = devfs_mk_dir(NULL, "task_mem", NULL);

	if (devfs_handle == NULL) {
		printk(KERN_ERR
		       "unable to create devfs directory 'task_mem'\n");
		rc = -ENODEV;
	} else {

		devfs = devfs_register(devfs_handle,
				       "0",
				       DEVFS_FL_DEFAULT,
				       major,
				       0,
				       S_IFCHR | S_IRUGO | S_IWUGO,
				       &mem_task_fops,
				       NULL);
		if ( devfs == NULL){
			printk(KERN_ERR
			       "unable to create devfs node for 'task_mem'\n");
			rc = -ENODEV;
		}
	}
#else
	if ((rc = register_chrdev(major, "task_mem", &mem_task_fops)) < 0) {
		printk(KERN_ERR,
		       "unable to register 'task_mem' character "
		       "driver using major[%d]\n",
		       major);
		return(rc);
	}
#endif    

	procfile_mem_task_debug =
	  create_proc_read_entry(PROCFILE_MEM_TASK_DEBUG,
				 0444,
				 procfile_physmem_dir,
				 show_task_help,
				 NULL);

	return rc;
}

/* ========================================================================== */
/**
 * @brief Cleanup function of the task portion of the module.
 */
void __exit mem_task_module_exit(void) {
#if defined(CONFIG_DEVFS_FS)
	devfs_unregister(devfs_handle);
	devfs_unregister_chrdev(major, "task_mem");
#else
	unregister_chrdev(major, "task_mem");
#endif

	remove_proc_entry(PROCFILE_MEM_TASK_DEBUG, procfile_physmem_dir);
  
	return;
}

/* ========================================================================== */
/**
 * @brief Initialize the physical memory information module.
 *
 * This function creates the main directory in the /proc tree.
 * This function will also call the init routines for the various pieces of
 * the module.
 *
 * @note this function is called by insmod and shouldn't be explicitly called.
 *
 * @return 0 if successfull, non-0 otherwise
 */
static int __init physmem_info_module_init(void) {
	int rc = 0;

	procfile_physmem_dir = proc_mkdir(PROCFILE_PHYSMEM_DIR,
					    &proc_root);

	if (procfile_physmem_dir == NULL) {
		rc = -EIO;
	}

	if (rc == 0) {
		rc = mem_task_module_init();
	}

	return rc;
}

/* ========================================================================== */
/**
 * @brief Cleanup the physical memory information module.
 *
 * This function will call the exit routines for the various pieces of
 * the module.
 * This function also removes the main directory in the /proc tree.
 *
 * @note this function is called by rmmod and shouldn't be explicitly called.
 */
void __exit physmem_info_module_exit(void) {
	mem_task_module_exit();
	remove_proc_entry(PROCFILE_PHYSMEM_DIR, &proc_root);
}

module_init(physmem_info_module_init);
module_exit(physmem_info_module_exit);
