1/*
2 *  Copyright (C) 2007-2010 Lawrence Livermore National Security, LLC.
3 *  Copyright (C) 2007 The Regents of the University of California.
4 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
5 *  Written by Brian Behlendorf <behlendorf1@llnl.gov>.
6 *  UCRL-CODE-235197
7 *
8 *  This file is part of the SPL, Solaris Porting Layer.
9 *
10 *  The SPL is free software; you can redistribute it and/or modify it
11 *  under the terms of the GNU General Public License as published by the
12 *  Free Software Foundation; either version 2 of the License, or (at your
13 *  option) any later version.
14 *
15 *  The SPL is distributed in the hope that it will be useful, but WITHOUT
16 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
18 *  for more details.
19 *
20 *  You should have received a copy of the GNU General Public License along
21 *  with the SPL.  If not, see <http://www.gnu.org/licenses/>.
22 *
23 *  Solaris Porting Layer (SPL) Proc Implementation.
24 */
25
26#include <sys/systeminfo.h>
27#include <sys/kstat.h>
28#include <sys/kmem.h>
29#include <sys/kmem_cache.h>
30#include <sys/vmem.h>
31#include <sys/taskq.h>
32#include <sys/proc.h>
33#include <linux/ctype.h>
34#include <linux/kmod.h>
35#include <linux/seq_file.h>
36#include <linux/uaccess.h>
37#include <linux/version.h>
38#include "zfs_gitrev.h"
39
40#if defined(CONSTIFY_PLUGIN) && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0)
41typedef struct ctl_table __no_const spl_ctl_table;
42#else
43typedef struct ctl_table spl_ctl_table;
44#endif
45
46static unsigned long table_min = 0;
47static unsigned long table_max = ~0;
48
49static struct ctl_table_header *spl_header = NULL;
50#ifndef HAVE_REGISTER_SYSCTL_TABLE
51static struct ctl_table_header *spl_kmem = NULL;
52static struct ctl_table_header *spl_kstat = NULL;
53#endif
54static struct proc_dir_entry *proc_spl = NULL;
55static struct proc_dir_entry *proc_spl_kmem = NULL;
56static struct proc_dir_entry *proc_spl_kmem_slab = NULL;
57static struct proc_dir_entry *proc_spl_taskq_all = NULL;
58static struct proc_dir_entry *proc_spl_taskq = NULL;
59struct proc_dir_entry *proc_spl_kstat = NULL;
60
61#ifdef DEBUG_KMEM
62static int
63proc_domemused(struct ctl_table *table, int write,
64    void __user *buffer, size_t *lenp, loff_t *ppos)
65{
66	int rc = 0;
67	unsigned long val;
68	spl_ctl_table dummy = *table;
69
70	dummy.data = &val;
71	dummy.proc_handler = &proc_dointvec;
72	dummy.extra1 = &table_min;
73	dummy.extra2 = &table_max;
74
75	if (write) {
76		*ppos += *lenp;
77	} else {
78#ifdef HAVE_ATOMIC64_T
79		val = atomic64_read((atomic64_t *)table->data);
80#else
81		val = atomic_read((atomic_t *)table->data);
82#endif /* HAVE_ATOMIC64_T */
83		rc = proc_doulongvec_minmax(&dummy, write, buffer, lenp, ppos);
84	}
85
86	return (rc);
87}
88#endif /* DEBUG_KMEM */
89
90static int
91proc_doslab(struct ctl_table *table, int write,
92    void __user *buffer, size_t *lenp, loff_t *ppos)
93{
94	int rc = 0;
95	unsigned long val = 0, mask;
96	spl_ctl_table dummy = *table;
97	spl_kmem_cache_t *skc = NULL;
98
99	dummy.data = &val;
100	dummy.proc_handler = &proc_dointvec;
101	dummy.extra1 = &table_min;
102	dummy.extra2 = &table_max;
103
104	if (write) {
105		*ppos += *lenp;
106	} else {
107		down_read(&spl_kmem_cache_sem);
108		mask = (unsigned long)table->data;
109
110		list_for_each_entry(skc, &spl_kmem_cache_list, skc_list) {
111
112			/* Only use slabs of the correct kmem/vmem type */
113			if (!(skc->skc_flags & mask))
114				continue;
115
116			/* Sum the specified field for selected slabs */
117			switch (mask & (KMC_TOTAL | KMC_ALLOC | KMC_MAX)) {
118			case KMC_TOTAL:
119				val += skc->skc_slab_size * skc->skc_slab_total;
120				break;
121			case KMC_ALLOC:
122				val += skc->skc_obj_size * skc->skc_obj_alloc;
123				break;
124			case KMC_MAX:
125				val += skc->skc_obj_size * skc->skc_obj_max;
126				break;
127			}
128		}
129
130		up_read(&spl_kmem_cache_sem);
131		rc = proc_doulongvec_minmax(&dummy, write, buffer, lenp, ppos);
132	}
133
134	return (rc);
135}
136
137static int
138proc_dohostid(struct ctl_table *table, int write,
139    void __user *buffer, size_t *lenp, loff_t *ppos)
140{
141	char *end, str[32];
142	unsigned long hid;
143	spl_ctl_table dummy = *table;
144
145	dummy.data = str;
146	dummy.maxlen = sizeof (str) - 1;
147
148	if (!write)
149		snprintf(str, sizeof (str), "%lx",
150		    (unsigned long) zone_get_hostid(NULL));
151
152	/* always returns 0 */
153	proc_dostring(&dummy, write, buffer, lenp, ppos);
154
155	if (write) {
156		/*
157		 * We can't use proc_doulongvec_minmax() in the write
158		 * case here because hostid, while a hex value, has no
159		 * leading 0x, which confuses the helper function.
160		 */
161
162		hid = simple_strtoul(str, &end, 16);
163		if (str == end)
164			return (-EINVAL);
165		spl_hostid = hid;
166	}
167
168	return (0);
169}
170
171static void
172taskq_seq_show_headers(struct seq_file *f)
173{
174	seq_printf(f, "%-25s %5s %5s %5s %5s %5s %5s %12s %5s %10s\n",
175	    "taskq", "act", "nthr", "spwn", "maxt", "pri",
176	    "mina", "maxa", "cura", "flags");
177}
178
179/* indices into the lheads array below */
180#define	LHEAD_PEND	0
181#define	LHEAD_PRIO	1
182#define	LHEAD_DELAY	2
183#define	LHEAD_WAIT	3
184#define	LHEAD_ACTIVE	4
185#define	LHEAD_SIZE	5
186
187static unsigned int spl_max_show_tasks = 512;
188/* CSTYLED */
189module_param(spl_max_show_tasks, uint, 0644);
190MODULE_PARM_DESC(spl_max_show_tasks, "Max number of tasks shown in taskq proc");
191
192static int
193taskq_seq_show_impl(struct seq_file *f, void *p, boolean_t allflag)
194{
195	taskq_t *tq = p;
196	taskq_thread_t *tqt = NULL;
197	spl_wait_queue_entry_t *wq;
198	struct task_struct *tsk;
199	taskq_ent_t *tqe;
200	char name[100];
201	struct list_head *lheads[LHEAD_SIZE], *lh;
202	static char *list_names[LHEAD_SIZE] =
203	    {"pend", "prio", "delay", "wait", "active" };
204	int i, j, have_lheads = 0;
205	unsigned long wflags, flags;
206
207	spin_lock_irqsave_nested(&tq->tq_lock, flags, tq->tq_lock_class);
208	spin_lock_irqsave(&tq->tq_wait_waitq.lock, wflags);
209
210	/* get the various lists and check whether they're empty */
211	lheads[LHEAD_PEND] = &tq->tq_pend_list;
212	lheads[LHEAD_PRIO] = &tq->tq_prio_list;
213	lheads[LHEAD_DELAY] = &tq->tq_delay_list;
214#ifdef HAVE_WAIT_QUEUE_HEAD_ENTRY
215	lheads[LHEAD_WAIT] = &tq->tq_wait_waitq.head;
216#else
217	lheads[LHEAD_WAIT] = &tq->tq_wait_waitq.task_list;
218#endif
219	lheads[LHEAD_ACTIVE] = &tq->tq_active_list;
220
221	for (i = 0; i < LHEAD_SIZE; ++i) {
222		if (list_empty(lheads[i]))
223			lheads[i] = NULL;
224		else
225			++have_lheads;
226	}
227
228	/* early return in non-"all" mode if lists are all empty */
229	if (!allflag && !have_lheads) {
230		spin_unlock_irqrestore(&tq->tq_wait_waitq.lock, wflags);
231		spin_unlock_irqrestore(&tq->tq_lock, flags);
232		return (0);
233	}
234
235	/* unlock the waitq quickly */
236	if (!lheads[LHEAD_WAIT])
237		spin_unlock_irqrestore(&tq->tq_wait_waitq.lock, wflags);
238
239	/* show the base taskq contents */
240	snprintf(name, sizeof (name), "%s/%d", tq->tq_name, tq->tq_instance);
241	seq_printf(f, "%-25s ", name);
242	seq_printf(f, "%5d %5d %5d %5d %5d %5d %12d %5d %10x\n",
243	    tq->tq_nactive, tq->tq_nthreads, tq->tq_nspawn,
244	    tq->tq_maxthreads, tq->tq_pri, tq->tq_minalloc, tq->tq_maxalloc,
245	    tq->tq_nalloc, tq->tq_flags);
246
247	/* show the active list */
248	if (lheads[LHEAD_ACTIVE]) {
249		j = 0;
250		list_for_each_entry(tqt, &tq->tq_active_list, tqt_active_list) {
251			if (j == 0)
252				seq_printf(f, "\t%s:",
253				    list_names[LHEAD_ACTIVE]);
254			else if (j == 2) {
255				seq_printf(f, "\n\t       ");
256				j = 0;
257			}
258			seq_printf(f, " [%d]%pf(%ps)",
259			    tqt->tqt_thread->pid,
260			    tqt->tqt_task->tqent_func,
261			    tqt->tqt_task->tqent_arg);
262			++j;
263		}
264		seq_printf(f, "\n");
265	}
266
267	for (i = LHEAD_PEND; i <= LHEAD_WAIT; ++i)
268		if (lheads[i]) {
269			j = 0;
270			list_for_each(lh, lheads[i]) {
271				if (spl_max_show_tasks != 0 &&
272				    j >= spl_max_show_tasks) {
273					seq_printf(f, "\n\t(truncated)");
274					break;
275				}
276				/* show the wait waitq list */
277				if (i == LHEAD_WAIT) {
278#ifdef HAVE_WAIT_QUEUE_HEAD_ENTRY
279					wq = list_entry(lh,
280					    spl_wait_queue_entry_t, entry);
281#else
282					wq = list_entry(lh,
283					    spl_wait_queue_entry_t, task_list);
284#endif
285					if (j == 0)
286						seq_printf(f, "\t%s:",
287						    list_names[i]);
288					else if (j % 8 == 0)
289						seq_printf(f, "\n\t     ");
290
291					tsk = wq->private;
292					seq_printf(f, " %d", tsk->pid);
293				/* pend, prio and delay lists */
294				} else {
295					tqe = list_entry(lh, taskq_ent_t,
296					    tqent_list);
297					if (j == 0)
298						seq_printf(f, "\t%s:",
299						    list_names[i]);
300					else if (j % 2 == 0)
301						seq_printf(f, "\n\t     ");
302
303					seq_printf(f, " %pf(%ps)",
304					    tqe->tqent_func,
305					    tqe->tqent_arg);
306				}
307				++j;
308			}
309			seq_printf(f, "\n");
310		}
311	if (lheads[LHEAD_WAIT])
312		spin_unlock_irqrestore(&tq->tq_wait_waitq.lock, wflags);
313	spin_unlock_irqrestore(&tq->tq_lock, flags);
314
315	return (0);
316}
317
318static int
319taskq_all_seq_show(struct seq_file *f, void *p)
320{
321	return (taskq_seq_show_impl(f, p, B_TRUE));
322}
323
324static int
325taskq_seq_show(struct seq_file *f, void *p)
326{
327	return (taskq_seq_show_impl(f, p, B_FALSE));
328}
329
330static void *
331taskq_seq_start(struct seq_file *f, loff_t *pos)
332{
333	struct list_head *p;
334	loff_t n = *pos;
335
336	down_read(&tq_list_sem);
337	if (!n)
338		taskq_seq_show_headers(f);
339
340	p = tq_list.next;
341	while (n--) {
342		p = p->next;
343		if (p == &tq_list)
344		return (NULL);
345	}
346
347	return (list_entry(p, taskq_t, tq_taskqs));
348}
349
350static void *
351taskq_seq_next(struct seq_file *f, void *p, loff_t *pos)
352{
353	taskq_t *tq = p;
354
355	++*pos;
356	return ((tq->tq_taskqs.next == &tq_list) ?
357	    NULL : list_entry(tq->tq_taskqs.next, taskq_t, tq_taskqs));
358}
359
360static void
361slab_seq_show_headers(struct seq_file *f)
362{
363	seq_printf(f,
364	    "--------------------- cache ----------"
365	    "---------------------------------------------  "
366	    "----- slab ------  "
367	    "---- object -----  "
368	    "--- emergency ---\n");
369	seq_printf(f,
370	    "name                                  "
371	    "  flags      size     alloc slabsize  objsize  "
372	    "total alloc   max  "
373	    "total alloc   max  "
374	    "dlock alloc   max\n");
375}
376
377static int
378slab_seq_show(struct seq_file *f, void *p)
379{
380	spl_kmem_cache_t *skc = p;
381
382	ASSERT(skc->skc_magic == SKC_MAGIC);
383
384	if (skc->skc_flags & KMC_SLAB) {
385		/*
386		 * This cache is backed by a generic Linux kmem cache which
387		 * has its own accounting. For these caches we only track
388		 * the number of active allocated objects that exist within
389		 * the underlying Linux slabs. For the overall statistics of
390		 * the underlying Linux cache please refer to /proc/slabinfo.
391		 */
392		spin_lock(&skc->skc_lock);
393		uint64_t objs_allocated =
394		    percpu_counter_sum(&skc->skc_linux_alloc);
395		seq_printf(f, "%-36s  ", skc->skc_name);
396		seq_printf(f, "0x%05lx %9s %9lu %8s %8u  "
397		    "%5s %5s %5s  %5s %5lu %5s  %5s %5s %5s\n",
398		    (long unsigned)skc->skc_flags,
399		    "-",
400		    (long unsigned)(skc->skc_obj_size * objs_allocated),
401		    "-",
402		    (unsigned)skc->skc_obj_size,
403		    "-", "-", "-", "-",
404		    (long unsigned)objs_allocated,
405		    "-", "-", "-", "-");
406		spin_unlock(&skc->skc_lock);
407		return (0);
408	}
409
410	spin_lock(&skc->skc_lock);
411	seq_printf(f, "%-36s  ", skc->skc_name);
412	seq_printf(f, "0x%05lx %9lu %9lu %8u %8u  "
413	    "%5lu %5lu %5lu  %5lu %5lu %5lu  %5lu %5lu %5lu\n",
414	    (long unsigned)skc->skc_flags,
415	    (long unsigned)(skc->skc_slab_size * skc->skc_slab_total),
416	    (long unsigned)(skc->skc_obj_size * skc->skc_obj_alloc),
417	    (unsigned)skc->skc_slab_size,
418	    (unsigned)skc->skc_obj_size,
419	    (long unsigned)skc->skc_slab_total,
420	    (long unsigned)skc->skc_slab_alloc,
421	    (long unsigned)skc->skc_slab_max,
422	    (long unsigned)skc->skc_obj_total,
423	    (long unsigned)skc->skc_obj_alloc,
424	    (long unsigned)skc->skc_obj_max,
425	    (long unsigned)skc->skc_obj_deadlock,
426	    (long unsigned)skc->skc_obj_emergency,
427	    (long unsigned)skc->skc_obj_emergency_max);
428	spin_unlock(&skc->skc_lock);
429	return (0);
430}
431
432static void *
433slab_seq_start(struct seq_file *f, loff_t *pos)
434{
435	struct list_head *p;
436	loff_t n = *pos;
437
438	down_read(&spl_kmem_cache_sem);
439	if (!n)
440		slab_seq_show_headers(f);
441
442	p = spl_kmem_cache_list.next;
443	while (n--) {
444		p = p->next;
445		if (p == &spl_kmem_cache_list)
446			return (NULL);
447	}
448
449	return (list_entry(p, spl_kmem_cache_t, skc_list));
450}
451
452static void *
453slab_seq_next(struct seq_file *f, void *p, loff_t *pos)
454{
455	spl_kmem_cache_t *skc = p;
456
457	++*pos;
458	return ((skc->skc_list.next == &spl_kmem_cache_list) ?
459	    NULL : list_entry(skc->skc_list.next, spl_kmem_cache_t, skc_list));
460}
461
462static void
463slab_seq_stop(struct seq_file *f, void *v)
464{
465	up_read(&spl_kmem_cache_sem);
466}
467
468static const struct seq_operations slab_seq_ops = {
469	.show  = slab_seq_show,
470	.start = slab_seq_start,
471	.next  = slab_seq_next,
472	.stop  = slab_seq_stop,
473};
474
475static int
476proc_slab_open(struct inode *inode, struct file *filp)
477{
478	return (seq_open(filp, &slab_seq_ops));
479}
480
481static const kstat_proc_op_t proc_slab_operations = {
482#ifdef HAVE_PROC_OPS_STRUCT
483	.proc_open	= proc_slab_open,
484	.proc_read	= seq_read,
485	.proc_lseek	= seq_lseek,
486	.proc_release	= seq_release,
487#else
488	.open		= proc_slab_open,
489	.read		= seq_read,
490	.llseek		= seq_lseek,
491	.release	= seq_release,
492#endif
493};
494
495static void
496taskq_seq_stop(struct seq_file *f, void *v)
497{
498	up_read(&tq_list_sem);
499}
500
501static const struct seq_operations taskq_all_seq_ops = {
502	.show	= taskq_all_seq_show,
503	.start	= taskq_seq_start,
504	.next	= taskq_seq_next,
505	.stop	= taskq_seq_stop,
506};
507
508static const struct seq_operations taskq_seq_ops = {
509	.show	= taskq_seq_show,
510	.start	= taskq_seq_start,
511	.next	= taskq_seq_next,
512	.stop	= taskq_seq_stop,
513};
514
515static int
516proc_taskq_all_open(struct inode *inode, struct file *filp)
517{
518	return (seq_open(filp, &taskq_all_seq_ops));
519}
520
521static int
522proc_taskq_open(struct inode *inode, struct file *filp)
523{
524	return (seq_open(filp, &taskq_seq_ops));
525}
526
527static const kstat_proc_op_t proc_taskq_all_operations = {
528#ifdef HAVE_PROC_OPS_STRUCT
529	.proc_open	= proc_taskq_all_open,
530	.proc_read	= seq_read,
531	.proc_lseek	= seq_lseek,
532	.proc_release	= seq_release,
533#else
534	.open		= proc_taskq_all_open,
535	.read		= seq_read,
536	.llseek		= seq_lseek,
537	.release	= seq_release,
538#endif
539};
540
541static const kstat_proc_op_t proc_taskq_operations = {
542#ifdef HAVE_PROC_OPS_STRUCT
543	.proc_open	= proc_taskq_open,
544	.proc_read	= seq_read,
545	.proc_lseek	= seq_lseek,
546	.proc_release	= seq_release,
547#else
548	.open		= proc_taskq_open,
549	.read		= seq_read,
550	.llseek		= seq_lseek,
551	.release	= seq_release,
552#endif
553};
554
555static struct ctl_table spl_kmem_table[] = {
556#ifdef DEBUG_KMEM
557	{
558		.procname	= "kmem_used",
559		.data		= &kmem_alloc_used,
560#ifdef HAVE_ATOMIC64_T
561		.maxlen		= sizeof (atomic64_t),
562#else
563		.maxlen		= sizeof (atomic_t),
564#endif /* HAVE_ATOMIC64_T */
565		.mode		= 0444,
566		.proc_handler	= &proc_domemused,
567	},
568	{
569		.procname	= "kmem_max",
570		.data		= &kmem_alloc_max,
571		.maxlen		= sizeof (unsigned long),
572		.extra1		= &table_min,
573		.extra2		= &table_max,
574		.mode		= 0444,
575		.proc_handler	= &proc_doulongvec_minmax,
576	},
577#endif /* DEBUG_KMEM */
578	{
579		.procname	= "slab_kvmem_total",
580		.data		= (void *)(KMC_KVMEM | KMC_TOTAL),
581		.maxlen		= sizeof (unsigned long),
582		.extra1		= &table_min,
583		.extra2		= &table_max,
584		.mode		= 0444,
585		.proc_handler	= &proc_doslab,
586	},
587	{
588		.procname	= "slab_kvmem_alloc",
589		.data		= (void *)(KMC_KVMEM | KMC_ALLOC),
590		.maxlen		= sizeof (unsigned long),
591		.extra1		= &table_min,
592		.extra2		= &table_max,
593		.mode		= 0444,
594		.proc_handler	= &proc_doslab,
595	},
596	{
597		.procname	= "slab_kvmem_max",
598		.data		= (void *)(KMC_KVMEM | KMC_MAX),
599		.maxlen		= sizeof (unsigned long),
600		.extra1		= &table_min,
601		.extra2		= &table_max,
602		.mode		= 0444,
603		.proc_handler	= &proc_doslab,
604	},
605	{},
606};
607
608static struct ctl_table spl_kstat_table[] = {
609	{},
610};
611
612static struct ctl_table spl_table[] = {
613	/*
614	 * NB No .strategy entries have been provided since
615	 * sysctl(8) prefers to go via /proc for portability.
616	 */
617	{
618		.procname	= "gitrev",
619		.data		= (char *)ZFS_META_GITREV,
620		.maxlen		= sizeof (ZFS_META_GITREV),
621		.mode		= 0444,
622		.proc_handler	= &proc_dostring,
623	},
624	{
625		.procname	= "hostid",
626		.data		= &spl_hostid,
627		.maxlen		= sizeof (unsigned long),
628		.mode		= 0644,
629		.proc_handler	= &proc_dohostid,
630	},
631#ifdef HAVE_REGISTER_SYSCTL_TABLE
632	{
633		.procname	= "kmem",
634		.mode		= 0555,
635		.child		= spl_kmem_table,
636	},
637	{
638		.procname	= "kstat",
639		.mode		= 0555,
640		.child		= spl_kstat_table,
641	},
642#endif
643	{},
644};
645
646#ifdef HAVE_REGISTER_SYSCTL_TABLE
647static struct ctl_table spl_dir[] = {
648	{
649		.procname	= "spl",
650		.mode		= 0555,
651		.child		= spl_table,
652	},
653	{}
654};
655
656static struct ctl_table spl_root[] = {
657	{
658		.procname	= "kernel",
659		.mode		= 0555,
660		.child		= spl_dir,
661	},
662	{}
663};
664#endif
665
666static void spl_proc_cleanup(void)
667{
668	remove_proc_entry("kstat", proc_spl);
669	remove_proc_entry("slab", proc_spl_kmem);
670	remove_proc_entry("kmem", proc_spl);
671	remove_proc_entry("taskq-all", proc_spl);
672	remove_proc_entry("taskq", proc_spl);
673	remove_proc_entry("spl", NULL);
674
675#ifndef HAVE_REGISTER_SYSCTL_TABLE
676	if (spl_kstat) {
677		unregister_sysctl_table(spl_kstat);
678		spl_kstat = NULL;
679	}
680	if (spl_kmem) {
681		unregister_sysctl_table(spl_kmem);
682		spl_kmem = NULL;
683	}
684#endif
685	if (spl_header) {
686		unregister_sysctl_table(spl_header);
687		spl_header = NULL;
688	}
689}
690
691int
692spl_proc_init(void)
693{
694	int rc = 0;
695
696#ifdef HAVE_REGISTER_SYSCTL_TABLE
697	spl_header = register_sysctl_table(spl_root);
698	if (spl_header == NULL)
699		return (-EUNATCH);
700#else
701	spl_header = register_sysctl("kernel/spl", spl_table);
702	if (spl_header == NULL)
703		return (-EUNATCH);
704
705	spl_kmem = register_sysctl("kernel/spl/kmem", spl_kmem_table);
706	if (spl_kmem == NULL) {
707		rc = -EUNATCH;
708		goto out;
709	}
710	spl_kstat = register_sysctl("kernel/spl/kstat", spl_kstat_table);
711	if (spl_kstat == NULL) {
712		rc = -EUNATCH;
713		goto out;
714	}
715#endif
716
717	proc_spl = proc_mkdir("spl", NULL);
718	if (proc_spl == NULL) {
719		rc = -EUNATCH;
720		goto out;
721	}
722
723	proc_spl_taskq_all = proc_create_data("taskq-all", 0444, proc_spl,
724	    &proc_taskq_all_operations, NULL);
725	if (proc_spl_taskq_all == NULL) {
726		rc = -EUNATCH;
727		goto out;
728	}
729
730	proc_spl_taskq = proc_create_data("taskq", 0444, proc_spl,
731	    &proc_taskq_operations, NULL);
732	if (proc_spl_taskq == NULL) {
733		rc = -EUNATCH;
734		goto out;
735	}
736
737	proc_spl_kmem = proc_mkdir("kmem", proc_spl);
738	if (proc_spl_kmem == NULL) {
739		rc = -EUNATCH;
740		goto out;
741	}
742
743	proc_spl_kmem_slab = proc_create_data("slab", 0444, proc_spl_kmem,
744	    &proc_slab_operations, NULL);
745	if (proc_spl_kmem_slab == NULL) {
746		rc = -EUNATCH;
747		goto out;
748	}
749
750	proc_spl_kstat = proc_mkdir("kstat", proc_spl);
751	if (proc_spl_kstat == NULL) {
752		rc = -EUNATCH;
753		goto out;
754	}
755out:
756	if (rc)
757		spl_proc_cleanup();
758
759	return (rc);
760}
761
762void
763spl_proc_fini(void)
764{
765	spl_proc_cleanup();
766}
767