/*
 *  This file is part of the SPL: Solaris Porting Layer.
 *
 *  Copyright (c) 2008 Lawrence Livermore National Security, LLC.
 *  Produced at Lawrence Livermore National Laboratory
 *  Written by:
 *          Brian Behlendorf <behlendorf1@llnl.gov>,
 *          Herb Wartens <wartens2@llnl.gov>,
 *          Jim Garlick <garlick@llnl.gov>
 *  UCRL-CODE-235197
 *
 *  This 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 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.,
 *  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 */

#include <sys/proc.h>

#ifdef DEBUG_SUBSYSTEM
#undef DEBUG_SUBSYSTEM
#endif

#define DEBUG_SUBSYSTEM S_PROC

#ifdef DEBUG_KMEM
static unsigned long table_min = 0;
static unsigned long table_max = ~0;
#endif

#ifdef CONFIG_SYSCTL
static struct ctl_table_header *spl_header = NULL;
#endif /* CONFIG_SYSCTL */

#if defined(DEBUG_MUTEX) || defined(DEBUG_KMEM) || defined(DEBUG_KSTAT)
static struct proc_dir_entry *proc_spl = NULL;
#ifdef DEBUG_MUTEX
static struct proc_dir_entry *proc_spl_mutex = NULL;
static struct proc_dir_entry *proc_spl_mutex_stats = NULL;
#endif /* DEBUG_MUTEX */
#ifdef DEBUG_KMEM
static struct proc_dir_entry *proc_spl_kmem = NULL;
static struct proc_dir_entry *proc_spl_kmem_slab = NULL;
#endif /* DEBUG_KMEM */
#ifdef DEBUG_KSTAT
struct proc_dir_entry *proc_spl_kstat = NULL;
#endif /* DEBUG_KSTAT */
#endif /* DEBUG_MUTEX || DEBUG_KMEM || DEBUG_KSTAT */

#ifdef HAVE_CTL_UNNUMBERED

#define CTL_SPL			CTL_UNNUMBERED
#define CTL_SPL_DEBUG		CTL_UNNUMBERED
#define CTL_SPL_VM		CTL_UNNUMBERED
#define CTL_SPL_MUTEX		CTL_UNNUMBERED
#define CTL_SPL_KMEM		CTL_UNNUMBERED
#define CTL_SPL_KSTAT		CTL_UNNUMBERED

#define CTL_VERSION		CTL_UNNUMBERED /* Version */
#define CTL_HOSTID		CTL_UNNUMBERED /* Host id by /usr/bin/hostid */
#define CTL_HW_SERIAL		CTL_UNNUMBERED /* HW serial number by hostid */
#define CTL_KALLSYMS		CTL_UNNUMBERED /* kallsyms_lookup_name addr */

#define CTL_DEBUG_SUBSYS	CTL_UNNUMBERED /* Debug subsystem */
#define CTL_DEBUG_MASK		CTL_UNNUMBERED /* Debug mask */
#define CTL_DEBUG_PRINTK	CTL_UNNUMBERED /* All messages to console */
#define CTL_DEBUG_MB		CTL_UNNUMBERED /* Debug buffer size */
#define CTL_DEBUG_BINARY	CTL_UNNUMBERED /* Binary data in buffer */
#define CTL_DEBUG_CATASTROPHE	CTL_UNNUMBERED /* Set if BUG'd or panic'd */
#define CTL_DEBUG_PANIC_ON_BUG	CTL_UNNUMBERED /* Should panic on BUG */
#define CTL_DEBUG_PATH		CTL_UNNUMBERED /* Dump log location */
#define CTL_DEBUG_DUMP		CTL_UNNUMBERED /* Dump debug buffer to file */
#define CTL_DEBUG_FORCE_BUG	CTL_UNNUMBERED /* Hook to force a BUG */
#define CTL_DEBUG_STACK_SIZE	CTL_UNNUMBERED /* Max observed stack size */

#define CTL_CONSOLE_RATELIMIT	CTL_UNNUMBERED /* Ratelimit console messages */
#define CTL_CONSOLE_MAX_DELAY_CS CTL_UNNUMBERED /* Max delay skip messages */
#define CTL_CONSOLE_MIN_DELAY_CS CTL_UNNUMBERED /* Init delay skip messages */
#define CTL_CONSOLE_BACKOFF	CTL_UNNUMBERED /* Delay increase factor */

#define CTL_VM_MINFREE		CTL_UNNUMBERED /* Minimum free memory */
#define CTL_VM_DESFREE		CTL_UNNUMBERED /* Desired free memory */
#define CTL_VM_LOTSFREE		CTL_UNNUMBERED /* Lots of free memory */
#define CTL_VM_NEEDFREE		CTL_UNNUMBERED /* Need free memory */
#define CTL_VM_SWAPFS_MINFREE	CTL_UNNUMBERED /* Minimum swapfs memory */
#define CTL_VM_SWAPFS_RESERVE	CTL_UNNUMBERED /* Reserved swapfs memory */
#define CTL_VM_AVAILRMEM	CTL_UNNUMBERED /* Easily available memory */
#define CTL_VM_FREEMEM		CTL_UNNUMBERED /* Free memory */
#define CTL_VM_PHYSMEM		CTL_UNNUMBERED /* Total physical memory */

#ifdef DEBUG_KMEM
#define CTL_KMEM_KMEMUSED	CTL_UNNUMBERED /* Alloc'd kmem bytes */
#define CTL_KMEM_KMEMMAX	CTL_UNNUMBERED /* Max alloc'd by kmem bytes */
#define CTL_KMEM_VMEMUSED	CTL_UNNUMBERED /* Alloc'd vmem bytes */
#define CTL_KMEM_VMEMMAX	CTL_UNNUMBERED /* Max alloc'd by vmem bytes */
#define CTL_KMEM_ALLOC_FAILED	CTL_UNNUMBERED /* Cache allocations failed */
#endif

#define CTL_MUTEX_STATS		CTL_UNNUMBERED /* Global mutex statistics */
#define CTL_MUTEX_STATS_PER	CTL_UNNUMBERED /* Per mutex statistics */
#define CTL_MUTEX_SPIN_MAX	CTL_UNNUMBERED /* Max mutex spin iterations */

#else /* HAVE_CTL_UNNUMBERED */

enum {
	CTL_SPL = 0x87,
	CTL_SPL_DEBUG = 0x88,
	CTL_SPL_VM = 0x89,
	CTL_SPL_MUTEX = 0x90,
	CTL_SPL_KMEM = 0x91,
	CTL_SPL_KSTAT = 0x92,
};

enum {
	CTL_VERSION = 1,		/* Version */
	CTL_HOSTID,			/* Host id reported by /usr/bin/hostid */
	CTL_HW_SERIAL,			/* Hardware serial number from hostid */
	CTL_KALLSYMS,			/* Address of kallsyms_lookup_name */

	CTL_DEBUG_SUBSYS,		/* Debug subsystem */
	CTL_DEBUG_MASK,			/* Debug mask */
	CTL_DEBUG_PRINTK,		/* Force all messages to console */
	CTL_DEBUG_MB,			/* Debug buffer size */
	CTL_DEBUG_BINARY,		/* Include binary data in buffer */
	CTL_DEBUG_CATASTROPHE,		/* Set if we have BUG'd or panic'd */
	CTL_DEBUG_PANIC_ON_BUG,		/* Set if we should panic on BUG */
	CTL_DEBUG_PATH,			/* Dump log location */
	CTL_DEBUG_DUMP,			/* Dump debug buffer to file */
	CTL_DEBUG_FORCE_BUG,		/* Hook to force a BUG */
	CTL_DEBUG_STACK_SIZE,		/* Max observed stack size */

	CTL_CONSOLE_RATELIMIT,		/* Ratelimit console messages */
	CTL_CONSOLE_MAX_DELAY_CS,	/* Max delay which we skip messages */
	CTL_CONSOLE_MIN_DELAY_CS,	/* Init delay which we skip messages */
	CTL_CONSOLE_BACKOFF,		/* Delay increase factor */

	CTL_VM_MINFREE,			/* Minimum free memory threshold */
	CTL_VM_DESFREE,			/* Desired free memory threshold */
	CTL_VM_LOTSFREE,		/* Lots of free memory threshold */
	CTL_VM_NEEDFREE,		/* Need free memory deficit */
	CTL_VM_SWAPFS_MINFREE,		/* Minimum swapfs memory */
	CTL_VM_SWAPFS_RESERVE,		/* Reserved swapfs memory */
	CTL_VM_AVAILRMEM,		/* Easily available memory */
	CTL_VM_FREEMEM,			/* Free memory */
	CTL_VM_PHYSMEM,			/* Total physical memory */

#ifdef DEBUG_KMEM
	CTL_KMEM_KMEMUSED,		/* Alloc'd kmem bytes */
	CTL_KMEM_KMEMMAX,		/* Max alloc'd by kmem bytes */
	CTL_KMEM_VMEMUSED,		/* Alloc'd vmem bytes */
	CTL_KMEM_VMEMMAX,		/* Max alloc'd by vmem bytes */
#endif

	CTL_MUTEX_STATS,		/* Global mutex statistics */
	CTL_MUTEX_STATS_PER,		/* Per mutex statistics */
	CTL_MUTEX_SPIN_MAX,		/* Maximum mutex spin iterations */
};
#endif /* HAVE_CTL_UNNUMBERED */

static int
proc_copyin_string(char *kbuffer, int kbuffer_size,
                   const char *ubuffer, int ubuffer_size)
{
        int size;

        if (ubuffer_size > kbuffer_size)
                return -EOVERFLOW;

        if (copy_from_user((void *)kbuffer, (void *)ubuffer, ubuffer_size))
                return -EFAULT;

        /* strip trailing whitespace */
        size = strnlen(kbuffer, ubuffer_size);
        while (size-- >= 0)
                if (!isspace(kbuffer[size]))
                        break;

        /* empty string */
        if (size < 0)
                return -EINVAL;

        /* no space to terminate */
        if (size == kbuffer_size)
                return -EOVERFLOW;

        kbuffer[size + 1] = 0;
        return 0;
}

static int
proc_copyout_string(char *ubuffer, int ubuffer_size,
                    const char *kbuffer, char *append)
{
        /* NB if 'append' != NULL, it's a single character to append to the
         * copied out string - usually "\n", for /proc entries and
         * (i.e. a terminating zero byte) for sysctl entries
         */
        int size = MIN(strlen(kbuffer), ubuffer_size);

        if (copy_to_user(ubuffer, kbuffer, size))
                return -EFAULT;

        if (append != NULL && size < ubuffer_size) {
                if (copy_to_user(ubuffer + size, append, 1))
                        return -EFAULT;

                size++;
        }

        return size;
}

static int
proc_dobitmasks(struct ctl_table *table, int write, struct file *filp,
                void __user *buffer, size_t *lenp, loff_t *ppos)
{
        unsigned long *mask = table->data;
        int is_subsys = (mask == &spl_debug_subsys) ? 1 : 0;
        int is_printk = (mask == &spl_debug_printk) ? 1 : 0;
        int size = 512, rc;
        char *str;
        ENTRY;

        str = kmem_alloc(size, KM_SLEEP);
        if (str == NULL)
                RETURN(-ENOMEM);

        if (write) {
                rc = proc_copyin_string(str, size, buffer, *lenp);
                if (rc < 0)
                        RETURN(rc);

                rc = spl_debug_str2mask(mask, str, is_subsys);
                /* Always print BUG/ASSERT to console, so keep this mask */
                if (is_printk)
                        *mask |= D_EMERG;

                *ppos += *lenp;
        } else {
                rc = spl_debug_mask2str(str, size, *mask, is_subsys);
                if (*ppos >= rc)
                        rc = 0;
                else
                        rc = proc_copyout_string(buffer, *lenp,
                                                 str + *ppos, "\n");
                if (rc >= 0) {
                        *lenp = rc;
                        *ppos += rc;
                }
        }

        kmem_free(str, size);
        RETURN(rc);
}

static int
proc_debug_mb(struct ctl_table *table, int write, struct file *filp,
              void __user *buffer, size_t *lenp, loff_t *ppos)
{
        char str[32];
        int rc, len;
        ENTRY;

        if (write) {
                rc = proc_copyin_string(str, sizeof(str), buffer, *lenp);
                if (rc < 0)
                        RETURN(rc);

                rc = spl_debug_set_mb(simple_strtoul(str, NULL, 0));
                *ppos += *lenp;
        } else {
                len = snprintf(str, sizeof(str), "%d", spl_debug_get_mb());
                if (*ppos >= len)
                        rc = 0;
                else
                        rc = proc_copyout_string(buffer, *lenp, str + *ppos, "\n");

                if (rc >= 0) {
                        *lenp = rc;
                        *ppos += rc;
                }
        }

        RETURN(rc);
}

static int
proc_dump_kernel(struct ctl_table *table, int write, struct file *filp,
                 void __user *buffer, size_t *lenp, loff_t *ppos)
{
	ENTRY;

        if (write) {
               spl_debug_dumplog(0);
                *ppos += *lenp;
        } else {
                *lenp = 0;
        }

        RETURN(0);
}

static int
proc_force_bug(struct ctl_table *table, int write, struct file *filp,
               void __user *buffer, size_t *lenp, loff_t *ppos)
{
	ENTRY;

        if (write) {
               CERROR("Crashing due to forced SBUG\n");
               SBUG();
	       /* Unreachable */
        } else {
                *lenp = 0;
	}

	RETURN(0);
}

static int
proc_console_max_delay_cs(struct ctl_table *table, int write, struct file *filp,
                          void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int rc, max_delay_cs;
        struct ctl_table dummy = *table;
        long d;
	ENTRY;

        dummy.data = &max_delay_cs;
        dummy.proc_handler = &proc_dointvec;

        if (write) {
                max_delay_cs = 0;
                rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
                if (rc < 0)
                        RETURN(rc);

                if (max_delay_cs <= 0)
                        RETURN(-EINVAL);

                d = (max_delay_cs * HZ) / 100;
                if (d == 0 || d < spl_console_min_delay)
                        RETURN(-EINVAL);

                spl_console_max_delay = d;
        } else {
                max_delay_cs = (spl_console_max_delay * 100) / HZ;
                rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
        }

        RETURN(rc);
}

static int
proc_console_min_delay_cs(struct ctl_table *table, int write, struct file *filp,
                          void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int rc, min_delay_cs;
        struct ctl_table dummy = *table;
        long d;
	ENTRY;

        dummy.data = &min_delay_cs;
        dummy.proc_handler = &proc_dointvec;

        if (write) {
                min_delay_cs = 0;
                rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
                if (rc < 0)
                        RETURN(rc);

                if (min_delay_cs <= 0)
                        RETURN(-EINVAL);

                d = (min_delay_cs * HZ) / 100;
                if (d == 0 || d > spl_console_max_delay)
                        RETURN(-EINVAL);

                spl_console_min_delay = d;
        } else {
                min_delay_cs = (spl_console_min_delay * 100) / HZ;
                rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
        }

        RETURN(rc);
}

static int
proc_console_backoff(struct ctl_table *table, int write, struct file *filp,
                     void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int rc, backoff;
        struct ctl_table dummy = *table;
	ENTRY;

        dummy.data = &backoff;
        dummy.proc_handler = &proc_dointvec;

        if (write) {
                backoff = 0;
                rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
                if (rc < 0)
                        RETURN(rc);

                if (backoff <= 0)
                        RETURN(-EINVAL);

                spl_console_backoff = backoff;
        } else {
                backoff = spl_console_backoff;
                rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
        }

        RETURN(rc);
}

#ifdef DEBUG_KMEM
static int
proc_doatomic64(struct ctl_table *table, int write, struct file *filp,
                void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int rc = 0;
        unsigned long min = 0, max = ~0, val;
        struct ctl_table dummy = *table;
	ENTRY;

        dummy.data = &val;
        dummy.proc_handler = &proc_dointvec;
        dummy.extra1 = &min;
        dummy.extra2 = &max;

        if (write) {
                *ppos += *lenp;
        } else {
                val = atomic64_read((atomic64_t *)table->data);
                rc = proc_doulongvec_minmax(&dummy, write, filp,
                                            buffer, lenp, ppos);
        }

        RETURN(rc);
}
#endif /* DEBUG_KMEM */

static int
proc_dohostid(struct ctl_table *table, int write, struct file *filp,
              void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int len, rc = 0;
        int32_t val;
        char *end, str[32];
        ENTRY;

        if (write) {
                /* We can't use proc_doulongvec_minmax() in the write
                 * case hear because hostid while a hex value has no
                 * leading 0x which confuses the helper function. */
                rc = proc_copyin_string(str, sizeof(str), buffer, *lenp);
                if (rc < 0)
                        RETURN(rc);

                val = simple_strtol(str, &end, 16);
                if (str == end)
                        RETURN(-EINVAL);

                spl_hostid = (long) val;
                (void) snprintf(hw_serial, HW_HOSTID_LEN, "%u",
                               (val >= 0) ? val : -val);
                hw_serial[HW_HOSTID_LEN - 1] = '\0';
                *ppos += *lenp;
        } else {
                len = snprintf(str, sizeof(str), "%lx", spl_hostid);
                if (*ppos >= len)
                        rc = 0;
                else
                        rc = proc_copyout_string(buffer, *lenp, str + *ppos, "\n");

                if (rc >= 0) {
                        *lenp = rc;
                        *ppos += rc;
                }
        }

        RETURN(rc);
}

#ifndef HAVE_KALLSYMS_LOOKUP_NAME
static int
proc_dokallsyms_lookup_name(struct ctl_table *table, int write,
			    struct file *filp, void __user *buffer,
			    size_t *lenp, loff_t *ppos) {
        int len, rc = 0;
        char *end, str[32];
	ENTRY;

        if (write) {
		/* This may only be set once at module load time */
		if (spl_kallsyms_lookup_name_fn != SYMBOL_POISON)
			RETURN(-EEXIST);

		/* We can't use proc_doulongvec_minmax() in the write
		 * case hear because the address while a hex value has no
		 * leading 0x which confuses the helper function. */
                rc = proc_copyin_string(str, sizeof(str), buffer, *lenp);
                if (rc < 0)
                        RETURN(rc);

                spl_kallsyms_lookup_name_fn =
			(kallsyms_lookup_name_t)simple_strtoul(str, &end, 16);
		if (str == end)
			RETURN(-EINVAL);

                *ppos += *lenp;
        } else {
                len = snprintf(str, sizeof(str), "%lx",
			       (unsigned long)spl_kallsyms_lookup_name_fn);
                if (*ppos >= len)
                        rc = 0;
                else
                        rc = proc_copyout_string(buffer,*lenp,str+*ppos,"\n");

                if (rc >= 0) {
                        *lenp = rc;
                        *ppos += rc;
                }
        }

        RETURN(rc);
}
#endif /* HAVE_KALLSYMS_LOOKUP_NAME */

static int
proc_doavailrmem(struct ctl_table *table, int write, struct file *filp,
                 void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int len, rc = 0;
	char str[32];
	ENTRY;

        if (write) {
                *ppos += *lenp;
        } else {
		len = snprintf(str, sizeof(str), "%lu", (unsigned long)availrmem);
		if (*ppos >= len)
			rc = 0;
		else
			rc = proc_copyout_string(buffer, *lenp, str + *ppos, "\n");

		if (rc >= 0) {
			*lenp = rc;
			*ppos += rc;
		}
        }

        RETURN(rc);
}

static int
proc_dofreemem(struct ctl_table *table, int write, struct file *filp,
               void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int len, rc = 0;
	char str[32];
	ENTRY;

        if (write) {
                *ppos += *lenp;
        } else {
		len = snprintf(str, sizeof(str), "%lu", (unsigned long)freemem);
		if (*ppos >= len)
			rc = 0;
		else
			rc = proc_copyout_string(buffer, *lenp, str + *ppos, "\n");

		if (rc >= 0) {
			*lenp = rc;
			*ppos += rc;
		}
        }

        RETURN(rc);
}

#ifdef DEBUG_MUTEX
static void
mutex_seq_show_headers(struct seq_file *f)
{
        seq_printf(f, "%-36s %-4s %-16s\t"
                   "e_tot\te_nh\te_sp\te_sl\tte_tot\tte_nh\n",
		   "name", "type", "owner");
}

static int
mutex_seq_show(struct seq_file *f, void *p)
{
        kmutex_t *mp = p;
	char t = 'X';
        int i;

	ASSERT(mp->km_magic == KM_MAGIC);

	switch (mp->km_type) {
		case MUTEX_DEFAULT:	t = 'D';	break;
		case MUTEX_SPIN:	t = 'S';	break;
		case MUTEX_ADAPTIVE:	t = 'A';	break;
		default:
			SBUG();
	}
        seq_printf(f, "%-36s %c    ", mp->km_name, t);
	if (mp->km_owner)
                seq_printf(f, "%p\t", mp->km_owner);
	else
                seq_printf(f, "%-16s\t", "<not held>");

        for (i = 0; i < MUTEX_STATS_SIZE; i++)
                seq_printf(f, "%d%c", mp->km_stats[i],
                           (i + 1 == MUTEX_STATS_SIZE) ? '\n' : '\t');

        return 0;
}

static void *
mutex_seq_start(struct seq_file *f, loff_t *pos)
{
        struct list_head *p;
        loff_t n = *pos;
        ENTRY;

	spin_lock(&mutex_stats_lock);
        if (!n)
                mutex_seq_show_headers(f);

        p = mutex_stats_list.next;
        while (n--) {
                p = p->next;
                if (p == &mutex_stats_list)
                        RETURN(NULL);
        }

        RETURN(list_entry(p, kmutex_t, km_list));
}

static void *
mutex_seq_next(struct seq_file *f, void *p, loff_t *pos)
{
	kmutex_t *mp = p;
        ENTRY;

        ++*pos;
        RETURN((mp->km_list.next == &mutex_stats_list) ?
	       NULL : list_entry(mp->km_list.next, kmutex_t, km_list));
}

static void
mutex_seq_stop(struct seq_file *f, void *v)
{
	spin_unlock(&mutex_stats_lock);
}

static struct seq_operations mutex_seq_ops = {
        .show  = mutex_seq_show,
        .start = mutex_seq_start,
        .next  = mutex_seq_next,
        .stop  = mutex_seq_stop,
};

static int
proc_mutex_open(struct inode *inode, struct file *filp)
{
        return seq_open(filp, &mutex_seq_ops);
}

static struct file_operations proc_mutex_operations = {
        .open           = proc_mutex_open,
        .read           = seq_read,
        .llseek         = seq_lseek,
        .release        = seq_release,
};
#endif /* DEBUG_MUTEX */

#ifdef DEBUG_KMEM
static void
slab_seq_show_headers(struct seq_file *f)
{
        seq_printf(f, "%-36s\n", "name");
}

static int
slab_seq_show(struct seq_file *f, void *p)
{
	spl_kmem_cache_t *skc = p;

	ASSERT(skc->skc_magic == SKC_MAGIC);

	spin_lock(&skc->skc_lock);
        seq_printf(f, "%-36s      ", skc->skc_name);
        seq_printf(f, "%u %u %u - %lu %lu %lu - %lu %lu %lu - %lu %lu %lu\n",
		   (unsigned)skc->skc_obj_size,
		   (unsigned)skc->skc_slab_objs,
		   (unsigned)skc->skc_slab_size,
		   (long unsigned)skc->skc_slab_fail,
		   (long unsigned)skc->skc_slab_create,
		   (long unsigned)skc->skc_slab_destroy,
		   (long unsigned)skc->skc_slab_total,
		   (long unsigned)skc->skc_slab_alloc,
		   (long unsigned)skc->skc_slab_max,
		   (long unsigned)skc->skc_obj_total,
		   (long unsigned)skc->skc_obj_alloc,
		   (long unsigned)skc->skc_obj_max);

	spin_unlock(&skc->skc_lock);

        return 0;
}

static void *
slab_seq_start(struct seq_file *f, loff_t *pos)
{
        struct list_head *p;
        loff_t n = *pos;
        ENTRY;

	down_read(&spl_kmem_cache_sem);
        if (!n)
                slab_seq_show_headers(f);

        p = spl_kmem_cache_list.next;
        while (n--) {
                p = p->next;
                if (p == &spl_kmem_cache_list)
                        RETURN(NULL);
        }

        RETURN(list_entry(p, spl_kmem_cache_t, skc_list));
}

static void *
slab_seq_next(struct seq_file *f, void *p, loff_t *pos)
{
	spl_kmem_cache_t *skc = p;
        ENTRY;

        ++*pos;
        RETURN((skc->skc_list.next == &spl_kmem_cache_list) ?
	       NULL : list_entry(skc->skc_list.next, spl_kmem_cache_t, skc_list));
}

static void
slab_seq_stop(struct seq_file *f, void *v)
{
	up_read(&spl_kmem_cache_sem);
}

static struct seq_operations slab_seq_ops = {
        .show  = slab_seq_show,
        .start = slab_seq_start,
        .next  = slab_seq_next,
        .stop  = slab_seq_stop,
};

static int
proc_slab_open(struct inode *inode, struct file *filp)
{
        return seq_open(filp, &slab_seq_ops);
}

static struct file_operations proc_slab_operations = {
        .open           = proc_slab_open,
        .read           = seq_read,
        .llseek         = seq_lseek,
        .release        = seq_release,
};
#endif /* DEBUG_KMEM */

static struct ctl_table spl_debug_table[] = {
        {
                .ctl_name = CTL_DEBUG_SUBSYS,
                .procname = "subsystem",
                .data     = &spl_debug_subsys,
                .maxlen   = sizeof(unsigned long),
                .mode     = 0644,
                .proc_handler = &proc_dobitmasks
        },
        {
                .ctl_name = CTL_DEBUG_MASK,
                .procname = "mask",
                .data     = &spl_debug_mask,
                .maxlen   = sizeof(unsigned long),
                .mode     = 0644,
                .proc_handler = &proc_dobitmasks
        },
        {
                .ctl_name = CTL_DEBUG_PRINTK,
                .procname = "printk",
                .data     = &spl_debug_printk,
                .maxlen   = sizeof(unsigned long),
                .mode     = 0644,
                .proc_handler = &proc_dobitmasks
        },
        {
                .ctl_name = CTL_DEBUG_MB,
                .procname = "mb",
                .mode     = 0644,
                .proc_handler = &proc_debug_mb,
        },
        {
                .ctl_name = CTL_DEBUG_BINARY,
                .procname = "binary",
                .data     = &spl_debug_binary,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_DEBUG_CATASTROPHE,
                .procname = "catastrophe",
                .data     = &spl_debug_catastrophe,
                .maxlen   = sizeof(int),
                .mode     = 0444,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_DEBUG_PANIC_ON_BUG,
                .procname = "panic_on_bug",
                .data     = &spl_debug_panic_on_bug,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec
        },
        {
                .ctl_name = CTL_DEBUG_PATH,
                .procname = "path",
                .data     = spl_debug_file_path,
                .maxlen   = sizeof(spl_debug_file_path),
                .mode     = 0644,
                .proc_handler = &proc_dostring,
        },
        {
                .ctl_name = CTL_DEBUG_DUMP,
                .procname = "dump",
                .mode     = 0200,
                .proc_handler = &proc_dump_kernel,
        },
        {       .ctl_name = CTL_DEBUG_FORCE_BUG,
                .procname = "force_bug",
                .mode     = 0200,
                .proc_handler = &proc_force_bug,
        },
        {
                .ctl_name = CTL_CONSOLE_RATELIMIT,
                .procname = "console_ratelimit",
                .data     = &spl_console_ratelimit,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_CONSOLE_MAX_DELAY_CS,
                .procname = "console_max_delay_centisecs",
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_console_max_delay_cs,
        },
        {
                .ctl_name = CTL_CONSOLE_MIN_DELAY_CS,
                .procname = "console_min_delay_centisecs",
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_console_min_delay_cs,
        },
        {
                .ctl_name = CTL_CONSOLE_BACKOFF,
                .procname = "console_backoff",
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_console_backoff,
        },
        {
                .ctl_name = CTL_DEBUG_STACK_SIZE,
                .procname = "stack_max",
                .data     = &spl_debug_stack,
                .maxlen   = sizeof(int),
                .mode     = 0444,
                .proc_handler = &proc_dointvec,
        },
	{0},
};

static struct ctl_table spl_vm_table[] = {
        {
                .ctl_name = CTL_VM_MINFREE,
                .procname = "minfree",
                .data     = &minfree,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_VM_DESFREE,
                .procname = "desfree",
                .data     = &desfree,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_VM_LOTSFREE,
                .procname = "lotsfree",
                .data     = &lotsfree,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_VM_NEEDFREE,
                .procname = "needfree",
                .data     = &needfree,
                .maxlen   = sizeof(int),
                .mode     = 0444,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_VM_SWAPFS_MINFREE,
                .procname = "swapfs_minfree",
                .data     = &swapfs_minfree,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_VM_SWAPFS_RESERVE,
                .procname = "swapfs_reserve",
                .data     = &swapfs_reserve,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_VM_AVAILRMEM,
                .procname = "availrmem",
                .mode     = 0444,
                .proc_handler = &proc_doavailrmem,
        },
        {
                .ctl_name = CTL_VM_FREEMEM,
                .procname = "freemem",
                .data     = (void *)2,
                .maxlen   = sizeof(int),
                .mode     = 0444,
                .proc_handler = &proc_dofreemem,
        },
        {
                .ctl_name = CTL_VM_PHYSMEM,
                .procname = "physmem",
                .data     = &physmem,
                .maxlen   = sizeof(int),
                .mode     = 0444,
                .proc_handler = &proc_dointvec,
        },
	{0},
};

#ifdef DEBUG_MUTEX
static struct ctl_table spl_mutex_table[] = {
        {
                .ctl_name = CTL_MUTEX_STATS,
                .procname = "stats",
                .data     = &mutex_stats,
                .maxlen   = sizeof(int) * MUTEX_STATS_SIZE,
                .mode     = 0444,
                .proc_handler = &proc_dointvec,
        },
        {
                .ctl_name = CTL_MUTEX_SPIN_MAX,
                .procname = "spin_max",
                .data     = &mutex_spin_max,
                .maxlen   = sizeof(int),
                .mode     = 0644,
                .proc_handler = &proc_dointvec,
        },
	{0},
};
#endif /* DEBUG_MUTEX */

#ifdef DEBUG_KMEM
static struct ctl_table spl_kmem_table[] = {
        {
                .ctl_name = CTL_KMEM_KMEMUSED,
                .procname = "kmem_used",
                .data     = &kmem_alloc_used,
                .maxlen   = sizeof(atomic64_t),
                .mode     = 0444,
                .proc_handler = &proc_doatomic64,
        },
        {
                .ctl_name = CTL_KMEM_KMEMMAX,
                .procname = "kmem_max",
                .data     = &kmem_alloc_max,
                .maxlen   = sizeof(unsigned long),
                .extra1   = &table_min,
                .extra2   = &table_max,
                .mode     = 0444,
                .proc_handler = &proc_doulongvec_minmax,
        },
        {
                .ctl_name = CTL_KMEM_VMEMUSED,
                .procname = "vmem_used",
                .data     = &vmem_alloc_used,
                .maxlen   = sizeof(atomic64_t),
                .mode     = 0444,
                .proc_handler = &proc_doatomic64,
        },
        {
                .ctl_name = CTL_KMEM_VMEMMAX,
                .procname = "vmem_max",
                .data     = &vmem_alloc_max,
                .maxlen   = sizeof(unsigned long),
                .extra1   = &table_min,
                .extra2   = &table_max,
                .mode     = 0444,
                .proc_handler = &proc_doulongvec_minmax,
        },
	{0},
};
#endif /* DEBUG_KMEM */

#ifdef DEBUG_KSTAT
static struct ctl_table spl_kstat_table[] = {
	{0},
};
#endif /* DEBUG_KSTAT */

static struct ctl_table spl_table[] = {
        /* NB No .strategy entries have been provided since
         * sysctl(8) prefers to go via /proc for portability.
         */
        {
                .ctl_name = CTL_VERSION,
                .procname = "version",
                .data     = spl_version,
                .maxlen   = sizeof(spl_version),
                .mode     = 0444,
                .proc_handler = &proc_dostring,
        },
        {
                .ctl_name = CTL_HOSTID,
                .procname = "hostid",
                .data     = &spl_hostid,
                .maxlen   = sizeof(unsigned long),
                .mode     = 0644,
                .proc_handler = &proc_dohostid,
        },
        {
                .ctl_name = CTL_HW_SERIAL,
                .procname = "hw_serial",
                .data     = hw_serial,
                .maxlen   = sizeof(hw_serial),
                .mode     = 0444,
                .proc_handler = &proc_dostring,
        },
#ifndef HAVE_KALLSYMS_LOOKUP_NAME
        {
                .ctl_name = CTL_KALLSYMS,
                .procname = "kallsyms_lookup_name",
                .data     = &spl_kallsyms_lookup_name_fn,
                .maxlen   = sizeof(unsigned long),
                .mode     = 0644,
                .proc_handler = &proc_dokallsyms_lookup_name,
        },
#endif
	{
		.ctl_name = CTL_SPL_DEBUG,
		.procname = "debug",
		.mode     = 0555,
		.child    = spl_debug_table,
	},
	{
		.ctl_name = CTL_SPL_VM,
		.procname = "vm",
		.mode     = 0555,
		.child    = spl_vm_table,
	},
#ifdef DEBUG_MUTEX
	{
		.ctl_name = CTL_SPL_MUTEX,
		.procname = "mutex",
		.mode     = 0555,
		.child    = spl_mutex_table,
	},
#endif
#ifdef DEBUG_KMEM
	{
		.ctl_name = CTL_SPL_KMEM,
		.procname = "kmem",
		.mode     = 0555,
		.child    = spl_kmem_table,
	},
#endif
#ifdef DEBUG_KSTAT
	{
		.ctl_name = CTL_SPL_KSTAT,
		.procname = "kstat",
		.mode     = 0555,
		.child    = spl_kstat_table,
	},
#endif
        { 0 },
};

static struct ctl_table spl_dir[] = {
        {
                .ctl_name = CTL_SPL,
                .procname = "spl",
                .mode     = 0555,
                .child    = spl_table,
        },
        { 0 }
};

static struct ctl_table spl_root[] = {
	{
	.ctl_name = CTL_KERN,
	.procname = "kernel",
	.mode = 0555,
	.child = spl_dir,
	},
	{ 0 }
};

static int
proc_dir_entry_match(int len, const char *name, struct proc_dir_entry *de)
{
        if (de->namelen != len)
                return 0;

        return !memcmp(name, de->name, len);
}

struct proc_dir_entry *
proc_dir_entry_find(struct proc_dir_entry *root, const char *str)
{
	struct proc_dir_entry *de;

	for (de = root->subdir; de; de = de->next)
		if (proc_dir_entry_match(strlen(str), str, de))
			return de;

	return NULL;
}

int
proc_dir_entries(struct proc_dir_entry *root)
{
	struct proc_dir_entry *de;
	int i = 0;

	for (de = root->subdir; de; de = de->next)
		i++;

	return i;
}

int
proc_init(void)
{
	int rc = 0;
        ENTRY;

#ifdef CONFIG_SYSCTL
        spl_header = spl_register_sysctl_table(spl_root, 0);
	if (spl_header == NULL)
		RETURN(-EUNATCH);
#endif /* CONFIG_SYSCTL */

#if defined(DEBUG_MUTEX) || defined(DEBUG_KMEM) || defined(DEBUG_KSTAT)
	proc_spl = proc_mkdir("spl", NULL);
	if (proc_spl == NULL)
		GOTO(out, rc = -EUNATCH);

#ifdef DEBUG_MUTEX
	proc_spl_mutex = proc_mkdir("mutex", proc_spl);
	if (proc_spl_mutex == NULL)
		GOTO(out, rc = -EUNATCH);

	proc_spl_mutex_stats = create_proc_entry("stats_per", 0444,
						 proc_spl_mutex);
        if (proc_spl_mutex_stats == NULL)
		GOTO(out, rc = -EUNATCH);

        proc_spl_mutex_stats->proc_fops = &proc_mutex_operations;
#endif /* DEBUG_MUTEX */

#ifdef DEBUG_KMEM
        proc_spl_kmem = proc_mkdir("kmem", proc_spl);
        if (proc_spl_kmem == NULL)
                GOTO(out, rc = -EUNATCH);

	proc_spl_kmem_slab = create_proc_entry("slab", 0444, proc_spl_kmem);
        if (proc_spl_kmem_slab == NULL)
		GOTO(out, rc = -EUNATCH);

        proc_spl_kmem_slab->proc_fops = &proc_slab_operations;
#endif /* DEBUG_KMEM */

#ifdef DEBUG_KSTAT
        proc_spl_kstat = proc_mkdir("kstat", proc_spl);
        if (proc_spl_kstat == NULL)
                GOTO(out, rc = -EUNATCH);
#endif /* DEBUG_KSTAT */

out:
	if (rc) {
		remove_proc_entry("kstat", proc_spl);
#ifdef DEBUG_KMEM
	        remove_proc_entry("slab", proc_spl_kmem);
#endif
		remove_proc_entry("kmem", proc_spl);
#ifdef DEBUG_MUTEX
	        remove_proc_entry("stats_per", proc_spl_mutex);
#endif
		remove_proc_entry("mutex", proc_spl);
		remove_proc_entry("spl", NULL);
#ifdef CONFIG_SYSCTL
	        spl_unregister_sysctl_table(spl_header);
#endif /* CONFIG_SYSCTL */
	}
#endif /* DEBUG_MUTEX || DEBUG_KMEM || DEBUG_KSTAT */

        RETURN(rc);
}

void
proc_fini(void)
{
        ENTRY;

#if defined(DEBUG_MUTEX) || defined(DEBUG_KMEM) || defined(DEBUG_KSTAT)
	remove_proc_entry("kstat", proc_spl);
#ifdef DEBUG_KMEM
        remove_proc_entry("slab", proc_spl_kmem);
#endif
	remove_proc_entry("kmem", proc_spl);
#ifdef DEBUG_MUTEX
        remove_proc_entry("stats_per", proc_spl_mutex);
#endif
	remove_proc_entry("mutex", proc_spl);
	remove_proc_entry("spl", NULL);
#endif /* DEBUG_MUTEX || DEBUG_KMEM || DEBUG_KSTAT */

#ifdef CONFIG_SYSCTL
        ASSERT(spl_header != NULL);
        spl_unregister_sysctl_table(spl_header);
#endif /* CONFIG_SYSCTL */

        EXIT;
}