diff options
Diffstat (limited to 'module/spl/spl-proc.c')
-rw-r--r-- | module/spl/spl-proc.c | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/module/spl/spl-proc.c b/module/spl/spl-proc.c new file mode 100644 index 000000000..bf185c60e --- /dev/null +++ b/module/spl/spl-proc.c @@ -0,0 +1,1049 @@ +/* + * 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 <[email protected]>, + * Herb Wartens <[email protected]>, + * Jim Garlick <[email protected]> + * 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_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_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 */ + +#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 */ + +#define CTL_SPL 0x87 +#define CTL_SPL_DEBUG 0x88 +#define CTL_SPL_MUTEX 0x89 +#define CTL_SPL_KMEM 0x90 +#define CTL_SPL_KSTAT 0x91 + +enum { + CTL_VERSION = 1, /* Version */ + CTL_HOSTID, /* Host id reported by /usr/bin/hostid */ + CTL_HW_SERIAL, /* Hardware serial number from hostid */ + + 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 at which we skip messages */ + CTL_CONSOLE_MIN_DELAY_CS, /* Init delay at which we skip messages */ + CTL_CONSOLE_BACKOFF, /* Delay increase factor */ + +#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, 11, "%u", (val >= 0) ? val : -val); + *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); +} + +#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}, +}; + +#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, + }, + { + .ctl_name = CTL_SPL_DEBUG, + .procname = "debug", + .mode = 0555, + .child = spl_debug_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; +} |