/*
    yamm, Yet Another Micro Monitor
    drv_yamm.c
    Copyright (C) 1994, 1995  Riccardo Facchetti

    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.
*/

/*
 * Parts of code derived by kernel:
 *
 * Copyright (C) Linus Torvalds
 */

/*
 * This driver uses a device file to communicate with tasks that want
 * informations.
 * To create the device file you must do:
 *
 * mknod /dev/yamm c 29 0
 * chmod 444 /dev/yamm
 *
 * Read ../../include/drv_yamm.h for informations on how to make programs
 * that can use this module.
 *
 * This module is inspired to drv_hello.c contained in modules package.
 * Whitout it i would never had the idea to use a device (not a filesys
 * please ... i'm not a masochist!!! :) to read the info from the kernel.
 *
 * - Riccardo -
 *
 */


/* Kernel includes */

#include "kversion.h"
#include <linux/autoconf.h>
#include <linux/module.h>

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <asm/segment.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/sched.h>
#if (KERNEL_VERSION < 1001085)
#include <linux/mman.h>
#else
#include <linux/mm.h>
#endif /* KERNEL_VERSION < 1001085 */
#include <linux/tty.h>

#include "yamm.h"
#include "drv_yamm.h"

/*
 * NB. we must include the kernel idenfication string in to install the module.
 */
#if (KERNEL_VERSION < 1001074)
#include "/usr/src/linux/tools/version.h"
#else
#include <linux/version.h>
#endif /* KERNEL_VERSION < 1001074 */

#define YAMM_MAJOR 29
#define KSTK_EIP(stack) (((unsigned long *)stack)[1019])
#define KSTK_ESP(stack) (((unsigned long *)stack)[1022])

#define TTY_VC_BASE      0
#define TTY_MODEM_BASE  64
#define TTY_PTY_BASE   192

#define PZERO		15

char kernel_version[] = UTS_RELEASE;
static int my_errno = 0;
static struct pst_status k;
static int process = 0;
static struct task_struct *Index = NULL;
static struct task_struct *init_task_private = NULL;

/*
 * The driver.
 */

/*
 * RewindTasks is a function that return a pointer to the
 * init task (task 1). Task 0 (swapper) is unkillable,..., un-everything.
 */
struct task_struct *RewindTasks(void)
{
	register struct task_struct *taskp = current;
	while (taskp->pid != 1) {
		/* 1 == init */
		taskp = taskp->prev_task;
	}
	return taskp;
}


/*
 * get_wchan, stolen from the kernel
 */
static unsigned long get_wchan(struct task_struct *p)
{
        unsigned long ebp, eip;
        unsigned long stack_page;
        register int count = 0;

        if (!p || p == current || p->state == TASK_RUNNING)
                return 0;
        stack_page = p->kernel_stack_page;
        if (!stack_page)
                return 0;
        ebp = p->tss.ebp;
        do {
                if (ebp < stack_page || ebp >= 4092+stack_page)
                        return 0;
                eip = *(unsigned long *) (ebp+4);
                if ((void *)eip != sleep_on &&
                    (void *)eip != interruptible_sleep_on)
                        return eip;
                ebp = *(unsigned long *) ebp;
        } while (count++ < 16);
        return 0;
}

/*
 * PutProc is the function that copy the task infos from the
 * kernel to the user space
 */
static int PutProc(struct pst_status *buf, int process)
{
	register int signumber;
	register struct task_struct *p;
	register unsigned long cnt = 0;
	unsigned long esp, eip, sigignore=0, sigcatch=0, bit = 1;
	int tty_minor, tty_base=0;
	char tty_char='\0';

	p = init_task_private;
	my_errno = 0;

	do {
		int do_proc = p->pid;

		if (process && (process != p->pid))
			do_proc = 0;

		if (do_proc) {
/*
 * swapper (task 0) is a special process: do not touch.
 */
			memset(&k, 0, sizeof(struct pst_status));
			k.pst_uid = p->uid;
			k.pst_suid = p->suid;
			k.pst_gid = p->gid;
			k.pst_fsuid = p->fsuid;
			k.pst_pid = p->pid;
			k.pst_ppid = p->p_pptr->pid;
			k.pst_nice = PZERO - p->priority;
			k.pst_pri = 30 - p->counter;
			k.pst_pgrp = p->pgrp;
			k.pst_time = p->timeout;
			k.pst_utime = p->utime;
			k.pst_stime = p->stime;
			k.pst_start = p->start_time;
			k.pst_cpticks = 0;
			k.pst_cptickstotal = 0;
			k.pst_pctcpu = 0;
			k.pst_vsize = p->kernel_stack_page;
			eip = esp = 0;
			if (k.pst_vsize) {
				eip = KSTK_EIP(k.pst_vsize);
				esp = KSTK_ESP(k.pst_vsize);
				k.pst_vsize = p->mm->brk - p->mm->start_code + PAGE_SIZE-1;
				if (esp)
					k.pst_vsize += TASK_SIZE - esp;
			}
			k.pst_vsize/=PAGE_SIZE;
			k.pst_size = 0;
			k.pst_resident = 0;
			k.pst_share = 0;
			k.pst_trs = 0;
			k.pst_lrs = 0;
			k.pst_drs = 0;
			k.pst_dt = 0;
			k.pst_rssize = p->mm->rss;
			if (p->state < 0 || p->state > 5)
				k.pst_stat = '.';
			else
				k.pst_stat = "RSDZTD"[p->state];
			memcpy(k.pst_cmd, p->comm, 16);
			k.pst_k_tty = p->tty;

/*
 * code to get a tty name from the lin_tty
 * (the MAJOR<<?+MINOR number of tty) is OK
 */

			if (!(p->tty)) { /* process detached */
				sprintf(k.pst_tty, "(detached)");
			}
			else {
				tty_minor = MINOR(p->tty->device);
				if (tty_minor < TTY_MODEM_BASE && tty_minor >= TTY_VC_BASE)
					tty_base = TTY_VC_BASE, tty_char = 0;
				else if(tty_minor < TTY_PTY_BASE && tty_minor >= TTY_MODEM_BASE)
					tty_base = TTY_MODEM_BASE, tty_char = 'S';
				else if (tty_minor >= TTY_PTY_BASE)
					tty_base = TTY_PTY_BASE, tty_char = 'p';
	
				if (!tty_char)
					sprintf(k.pst_tty, "tty%d", tty_minor);
				else
					sprintf(k.pst_tty, "tty%c%x", tty_char,
						((tty_base != TTY_PTY_BASE)
						?
						(tty_minor - tty_base)
						:
						((tty_minor - tty_base) % 0x0f)));
			}

			k.pst_min_flt = p->mm->min_flt;
			k.pst_cmin_flt = p->mm->cmin_flt;
			k.pst_maj_flt = p->mm->maj_flt;
			k.pst_cmaj_flt = p->mm->cmaj_flt;
			k.pst_swappable = p->mm->swappable;
			k.pst_cutime = p->cutime;
			k.pst_cstime = p->cstime;
			k.pst_flags = p->flags;
			k.pst_session = p->session;
			k.pst_it_real_value = p->it_real_value;
			k.pst_signal = p->signal;
			k.pst_blocked = p->blocked;
			for(signumber=0; signumber<32; ++signumber) {
				switch((int) p->sigaction[signumber].sa_handler) {
					case 1: sigignore |= bit; break;
					case 0: break;
					default: sigcatch |= bit;
				} bit <<= 1;
			}
			k.pst_sigignore = sigignore;
			k.pst_sigcatch = sigcatch;
			k.pst_wchan = get_wchan(p);

			my_errno = verify_area(VERIFY_WRITE,buf+cnt,
									sizeof(struct pst_status));
			if (my_errno)
				return my_errno;

			memcpy_tofs(buf+cnt, &k, sizeof(struct pst_status));
			if (process)
				return 0;
			cnt ++;
		}
		p = p->next_task;
	} while ( p != init_task_private );

/*
 * If getting a songle task structure, the task was not found.
 * Return error: no such process.
 */
 	if (process)
		return -ESRCH;

/*
 * Append a NULL struct pst_status to identify the end of array
 */
	memset(&k, 0, sizeof(struct pst_status));

	my_errno = verify_area(VERIFY_WRITE,buf+cnt, sizeof(struct pst_status));
	if (my_errno)
		return my_errno;

	memcpy_tofs(buf+cnt, &k, sizeof(struct pst_status));

	return 0;
}

#if 0
static long PutStat(struct file * file, char *buf, int count)
{
	return 0;
}

static long PutVm(struct file * file, char *buf, int count)
{
	return 0;
}
#endif /* 0 */

int yamm_ioctl(struct inode * node,struct file * file,unsigned int cmd, unsigned long arg)
{
	static int task_no;
	register int count;
	int minor = MINOR(node->i_rdev);
	long v = YAMM_NUM;

	my_errno = 0;

	if (minor) {
		printk("no such device");
		return -EIO;
	}
	switch(cmd) {
		case YIOCGVER:
			my_errno = verify_area(VERIFY_WRITE, (long *)arg, sizeof(long));
			if (my_errno)
				return my_errno;

#if (KERNEL_VERSION < 1003004)
			put_fs_long(v, (void *) arg);
#else
            put_user(v, (long *) arg);
#endif
			return 0;
		case YIOCGFTSK:
			task_no = 0;
			Index = init_task_private;
			return PutProc((struct pst_status *)arg, Index->pid);
		case YIOCGNTSK:
			task_no++;
			for(count = 0, Index = init_task_private;
			    (count < task_no) && (Index->pid != 0);
				  count++, Index = Index->next_task);
			if (Index->pid)
				return PutProc((struct pst_status *)arg, Index->pid);
			return -1;
		case YIOCGPROC:
			process = 0;
			return PutProc((struct pst_status *)arg, 0);
		default:
		/*
		 * Check if cmd is built with YIOCGOPROC. In this case cmd contain
		 * not only the command but also the process id of the process entry we
		 * want.
		 */
			if ( IOCGETCMD(cmd) == YIOCGOPROC_cmd) {
				process = IOCSIZE(cmd);
				return PutProc((struct pst_status *)arg, process);
			}
			printk("drv_yamm: unknown ioctl %u\n", IOCGETCMD(cmd));
			return -EIO;
	}

/* STRUTTURE: mettere un MAGIC Nella struttura ritornata!!!*/

}

/*
 * Our special open code.
 * MOD_INC_USE_COUNT make sure that the driver memory is not freed
 * while the device is in use.
 */
static int
yamm_open( struct inode* ino, struct file* filep)
{
   MOD_INC_USE_COUNT;
   return 0;   
}

/*
 * Now decrement the use count.
 */
static void
yamm_close( struct inode* ino, struct file* filep)
{
   MOD_DEC_USE_COUNT;
}

static struct file_operations yamm_fops = {
	NULL,           /* yamm_lseek */
	NULL,           /* yamm_read */
	NULL,           /* yamm_write */
	NULL,           /* yamm_readdir */
	NULL,           /* yamm_select */
	yamm_ioctl,
	NULL,           /* yamm_mmap */
	yamm_open,
	yamm_close,
	NULL            /* fsync */
};

/*
 * And now the modules code and kernel interface.
 */


int
init_module(void) {
	if (register_chrdev(YAMM_MAJOR, "yamm", &yamm_fops)) {
		printk("drv_yamm: register_chrdev failed\n");
		return -EIO;
	}
	else
		printk("YAMM support module %s.\n", YAMM_VERSION );
	
	init_task_private = RewindTasks();
	return 0;
}

void
cleanup_module(void) {
	if (MOD_IN_USE)
		printk("yamm: device busy, remove delayed.\n");
	if (unregister_chrdev(YAMM_MAJOR, "yamm") != 0) {
		printk("drv_yamm: cleanup_module failed.\n");
	} else {
		printk("drv_yamm: removed.\n");
	}    
}
