| /* |
| * Copyright (C) 2007-2010 Lawrence Livermore National Security, LLC. |
| * Copyright (C) 2007 The Regents of the University of California. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Brian Behlendorf <behlendorf1@llnl.gov>. |
| * UCRL-CODE-235197 |
| * |
| * This file is part of the SPL, Solaris Porting Layer. |
| * For details, see <http://zfsonlinux.org/>. |
| * |
| * The SPL 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. |
| * |
| * The SPL 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 the SPL. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * Solaris Porting Layer (SPL) Kstat Implementation. |
| */ |
| |
| #include <linux/seq_file.h> |
| #include <sys/kstat.h> |
| #include <sys/vmem.h> |
| #include <sys/cmn_err.h> |
| #include <sys/sysmacros.h> |
| |
| static kmutex_t kstat_module_lock; |
| static struct list_head kstat_module_list; |
| static kid_t kstat_id; |
| |
| static int |
| kstat_resize_raw(kstat_t *ksp) |
| { |
| if (ksp->ks_raw_bufsize == KSTAT_RAW_MAX) |
| return (ENOMEM); |
| |
| vmem_free(ksp->ks_raw_buf, ksp->ks_raw_bufsize); |
| ksp->ks_raw_bufsize = MIN(ksp->ks_raw_bufsize * 2, KSTAT_RAW_MAX); |
| ksp->ks_raw_buf = vmem_alloc(ksp->ks_raw_bufsize, KM_SLEEP); |
| |
| return (0); |
| } |
| |
| void |
| kstat_waitq_enter(kstat_io_t *kiop) |
| { |
| hrtime_t new, delta; |
| ulong_t wcnt; |
| |
| new = gethrtime(); |
| delta = new - kiop->wlastupdate; |
| kiop->wlastupdate = new; |
| wcnt = kiop->wcnt++; |
| if (wcnt != 0) { |
| kiop->wlentime += delta * wcnt; |
| kiop->wtime += delta; |
| } |
| } |
| EXPORT_SYMBOL(kstat_waitq_enter); |
| |
| void |
| kstat_waitq_exit(kstat_io_t *kiop) |
| { |
| hrtime_t new, delta; |
| ulong_t wcnt; |
| |
| new = gethrtime(); |
| delta = new - kiop->wlastupdate; |
| kiop->wlastupdate = new; |
| wcnt = kiop->wcnt--; |
| ASSERT((int)wcnt > 0); |
| kiop->wlentime += delta * wcnt; |
| kiop->wtime += delta; |
| } |
| EXPORT_SYMBOL(kstat_waitq_exit); |
| |
| void |
| kstat_runq_enter(kstat_io_t *kiop) |
| { |
| hrtime_t new, delta; |
| ulong_t rcnt; |
| |
| new = gethrtime(); |
| delta = new - kiop->rlastupdate; |
| kiop->rlastupdate = new; |
| rcnt = kiop->rcnt++; |
| if (rcnt != 0) { |
| kiop->rlentime += delta * rcnt; |
| kiop->rtime += delta; |
| } |
| } |
| EXPORT_SYMBOL(kstat_runq_enter); |
| |
| void |
| kstat_runq_exit(kstat_io_t *kiop) |
| { |
| hrtime_t new, delta; |
| ulong_t rcnt; |
| |
| new = gethrtime(); |
| delta = new - kiop->rlastupdate; |
| kiop->rlastupdate = new; |
| rcnt = kiop->rcnt--; |
| ASSERT((int)rcnt > 0); |
| kiop->rlentime += delta * rcnt; |
| kiop->rtime += delta; |
| } |
| EXPORT_SYMBOL(kstat_runq_exit); |
| |
| static int |
| kstat_seq_show_headers(struct seq_file *f) |
| { |
| kstat_t *ksp = (kstat_t *)f->private; |
| int rc = 0; |
| |
| ASSERT(ksp->ks_magic == KS_MAGIC); |
| |
| seq_printf(f, "%d %d 0x%02x %d %d %lld %lld\n", |
| ksp->ks_kid, ksp->ks_type, ksp->ks_flags, |
| ksp->ks_ndata, (int)ksp->ks_data_size, |
| ksp->ks_crtime, ksp->ks_snaptime); |
| |
| switch (ksp->ks_type) { |
| case KSTAT_TYPE_RAW: |
| restart: |
| if (ksp->ks_raw_ops.headers) { |
| rc = ksp->ks_raw_ops.headers( |
| ksp->ks_raw_buf, ksp->ks_raw_bufsize); |
| if (rc == ENOMEM && !kstat_resize_raw(ksp)) |
| goto restart; |
| if (!rc) |
| seq_puts(f, ksp->ks_raw_buf); |
| } else { |
| seq_printf(f, "raw data\n"); |
| } |
| break; |
| case KSTAT_TYPE_NAMED: |
| seq_printf(f, "%-31s %-4s %s\n", |
| "name", "type", "data"); |
| break; |
| case KSTAT_TYPE_INTR: |
| seq_printf(f, "%-8s %-8s %-8s %-8s %-8s\n", |
| "hard", "soft", "watchdog", |
| "spurious", "multsvc"); |
| break; |
| case KSTAT_TYPE_IO: |
| seq_printf(f, |
| "%-8s %-8s %-8s %-8s %-8s %-8s " |
| "%-8s %-8s %-8s %-8s %-8s %-8s\n", |
| "nread", "nwritten", "reads", "writes", |
| "wtime", "wlentime", "wupdate", |
| "rtime", "rlentime", "rupdate", |
| "wcnt", "rcnt"); |
| break; |
| case KSTAT_TYPE_TIMER: |
| seq_printf(f, |
| "%-31s %-8s " |
| "%-8s %-8s %-8s %-8s %-8s\n", |
| "name", "events", "elapsed", |
| "min", "max", "start", "stop"); |
| break; |
| default: |
| PANIC("Undefined kstat type %d\n", ksp->ks_type); |
| } |
| |
| return (-rc); |
| } |
| |
| static int |
| kstat_seq_show_raw(struct seq_file *f, unsigned char *p, int l) |
| { |
| int i, j; |
| |
| for (i = 0; ; i++) { |
| seq_printf(f, "%03x:", i); |
| |
| for (j = 0; j < 16; j++) { |
| if (i * 16 + j >= l) { |
| seq_printf(f, "\n"); |
| goto out; |
| } |
| |
| seq_printf(f, " %02x", (unsigned char)p[i * 16 + j]); |
| } |
| seq_printf(f, "\n"); |
| } |
| out: |
| return (0); |
| } |
| |
| static int |
| kstat_seq_show_named(struct seq_file *f, kstat_named_t *knp) |
| { |
| seq_printf(f, "%-31s %-4d ", knp->name, knp->data_type); |
| |
| switch (knp->data_type) { |
| case KSTAT_DATA_CHAR: |
| knp->value.c[15] = '\0'; /* NULL terminate */ |
| seq_printf(f, "%-16s", knp->value.c); |
| break; |
| /* |
| * NOTE - We need to be more careful able what tokens are |
| * used for each arch, for now this is correct for x86_64. |
| */ |
| case KSTAT_DATA_INT32: |
| seq_printf(f, "%d", knp->value.i32); |
| break; |
| case KSTAT_DATA_UINT32: |
| seq_printf(f, "%u", knp->value.ui32); |
| break; |
| case KSTAT_DATA_INT64: |
| seq_printf(f, "%lld", (signed long long)knp->value.i64); |
| break; |
| case KSTAT_DATA_UINT64: |
| seq_printf(f, "%llu", |
| (unsigned long long)knp->value.ui64); |
| break; |
| case KSTAT_DATA_LONG: |
| seq_printf(f, "%ld", knp->value.l); |
| break; |
| case KSTAT_DATA_ULONG: |
| seq_printf(f, "%lu", knp->value.ul); |
| break; |
| case KSTAT_DATA_STRING: |
| KSTAT_NAMED_STR_PTR(knp) |
| [KSTAT_NAMED_STR_BUFLEN(knp)-1] = '\0'; |
| seq_printf(f, "%s", KSTAT_NAMED_STR_PTR(knp)); |
| break; |
| default: |
| PANIC("Undefined kstat data type %d\n", knp->data_type); |
| } |
| |
| seq_printf(f, "\n"); |
| |
| return (0); |
| } |
| |
| static int |
| kstat_seq_show_intr(struct seq_file *f, kstat_intr_t *kip) |
| { |
| seq_printf(f, "%-8u %-8u %-8u %-8u %-8u\n", |
| kip->intrs[KSTAT_INTR_HARD], |
| kip->intrs[KSTAT_INTR_SOFT], |
| kip->intrs[KSTAT_INTR_WATCHDOG], |
| kip->intrs[KSTAT_INTR_SPURIOUS], |
| kip->intrs[KSTAT_INTR_MULTSVC]); |
| |
| return (0); |
| } |
| |
| static int |
| kstat_seq_show_io(struct seq_file *f, kstat_io_t *kip) |
| { |
| /* though wlentime & friends are signed, they will never be negative */ |
| seq_printf(f, |
| "%-8llu %-8llu %-8u %-8u %-8llu %-8llu " |
| "%-8llu %-8llu %-8llu %-8llu %-8u %-8u\n", |
| kip->nread, kip->nwritten, |
| kip->reads, kip->writes, |
| kip->wtime, kip->wlentime, kip->wlastupdate, |
| kip->rtime, kip->rlentime, kip->rlastupdate, |
| kip->wcnt, kip->rcnt); |
| |
| return (0); |
| } |
| |
| static int |
| kstat_seq_show_timer(struct seq_file *f, kstat_timer_t *ktp) |
| { |
| seq_printf(f, |
| "%-31s %-8llu %-8llu %-8llu %-8llu %-8llu %-8llu\n", |
| ktp->name, ktp->num_events, ktp->elapsed_time, |
| ktp->min_time, ktp->max_time, |
| ktp->start_time, ktp->stop_time); |
| |
| return (0); |
| } |
| |
| static int |
| kstat_seq_show(struct seq_file *f, void *p) |
| { |
| kstat_t *ksp = (kstat_t *)f->private; |
| int rc = 0; |
| |
| ASSERT(ksp->ks_magic == KS_MAGIC); |
| |
| switch (ksp->ks_type) { |
| case KSTAT_TYPE_RAW: |
| restart: |
| if (ksp->ks_raw_ops.data) { |
| rc = ksp->ks_raw_ops.data( |
| ksp->ks_raw_buf, ksp->ks_raw_bufsize, p); |
| if (rc == ENOMEM && !kstat_resize_raw(ksp)) |
| goto restart; |
| if (!rc) |
| seq_puts(f, ksp->ks_raw_buf); |
| } else { |
| ASSERT(ksp->ks_ndata == 1); |
| rc = kstat_seq_show_raw(f, ksp->ks_data, |
| ksp->ks_data_size); |
| } |
| break; |
| case KSTAT_TYPE_NAMED: |
| rc = kstat_seq_show_named(f, (kstat_named_t *)p); |
| break; |
| case KSTAT_TYPE_INTR: |
| rc = kstat_seq_show_intr(f, (kstat_intr_t *)p); |
| break; |
| case KSTAT_TYPE_IO: |
| rc = kstat_seq_show_io(f, (kstat_io_t *)p); |
| break; |
| case KSTAT_TYPE_TIMER: |
| rc = kstat_seq_show_timer(f, (kstat_timer_t *)p); |
| break; |
| default: |
| PANIC("Undefined kstat type %d\n", ksp->ks_type); |
| } |
| |
| return (-rc); |
| } |
| |
| static int |
| kstat_default_update(kstat_t *ksp, int rw) |
| { |
| ASSERT(ksp != NULL); |
| |
| if (rw == KSTAT_WRITE) |
| return (EACCES); |
| |
| return (0); |
| } |
| |
| static void * |
| kstat_seq_data_addr(kstat_t *ksp, loff_t n) |
| { |
| void *rc = NULL; |
| |
| switch (ksp->ks_type) { |
| case KSTAT_TYPE_RAW: |
| if (ksp->ks_raw_ops.addr) |
| rc = ksp->ks_raw_ops.addr(ksp, n); |
| else |
| rc = ksp->ks_data; |
| break; |
| case KSTAT_TYPE_NAMED: |
| rc = ksp->ks_data + n * sizeof (kstat_named_t); |
| break; |
| case KSTAT_TYPE_INTR: |
| rc = ksp->ks_data + n * sizeof (kstat_intr_t); |
| break; |
| case KSTAT_TYPE_IO: |
| rc = ksp->ks_data + n * sizeof (kstat_io_t); |
| break; |
| case KSTAT_TYPE_TIMER: |
| rc = ksp->ks_data + n * sizeof (kstat_timer_t); |
| break; |
| default: |
| PANIC("Undefined kstat type %d\n", ksp->ks_type); |
| } |
| |
| return (rc); |
| } |
| |
| static void * |
| kstat_seq_start(struct seq_file *f, loff_t *pos) |
| { |
| loff_t n = *pos; |
| kstat_t *ksp = (kstat_t *)f->private; |
| ASSERT(ksp->ks_magic == KS_MAGIC); |
| |
| mutex_enter(ksp->ks_lock); |
| |
| if (ksp->ks_type == KSTAT_TYPE_RAW) { |
| ksp->ks_raw_bufsize = PAGE_SIZE; |
| ksp->ks_raw_buf = vmem_alloc(ksp->ks_raw_bufsize, KM_SLEEP); |
| } |
| |
| /* Dynamically update kstat, on error existing kstats are used */ |
| (void) ksp->ks_update(ksp, KSTAT_READ); |
| |
| ksp->ks_snaptime = gethrtime(); |
| |
| if (!(ksp->ks_flags & KSTAT_FLAG_NO_HEADERS) && !n && |
| kstat_seq_show_headers(f)) |
| return (NULL); |
| |
| if (n >= ksp->ks_ndata) |
| return (NULL); |
| |
| return (kstat_seq_data_addr(ksp, n)); |
| } |
| |
| static void * |
| kstat_seq_next(struct seq_file *f, void *p, loff_t *pos) |
| { |
| kstat_t *ksp = (kstat_t *)f->private; |
| ASSERT(ksp->ks_magic == KS_MAGIC); |
| |
| ++*pos; |
| if (*pos >= ksp->ks_ndata) |
| return (NULL); |
| |
| return (kstat_seq_data_addr(ksp, *pos)); |
| } |
| |
| static void |
| kstat_seq_stop(struct seq_file *f, void *v) |
| { |
| kstat_t *ksp = (kstat_t *)f->private; |
| ASSERT(ksp->ks_magic == KS_MAGIC); |
| |
| if (ksp->ks_type == KSTAT_TYPE_RAW) |
| vmem_free(ksp->ks_raw_buf, ksp->ks_raw_bufsize); |
| |
| mutex_exit(ksp->ks_lock); |
| } |
| |
| static struct seq_operations kstat_seq_ops = { |
| .show = kstat_seq_show, |
| .start = kstat_seq_start, |
| .next = kstat_seq_next, |
| .stop = kstat_seq_stop, |
| }; |
| |
| static kstat_module_t * |
| kstat_find_module(char *name) |
| { |
| kstat_module_t *module = NULL; |
| |
| list_for_each_entry(module, &kstat_module_list, ksm_module_list) { |
| if (strncmp(name, module->ksm_name, KSTAT_STRLEN) == 0) |
| return (module); |
| } |
| |
| return (NULL); |
| } |
| |
| static kstat_module_t * |
| kstat_create_module(char *name) |
| { |
| kstat_module_t *module; |
| struct proc_dir_entry *pde; |
| |
| pde = proc_mkdir(name, proc_spl_kstat); |
| if (pde == NULL) |
| return (NULL); |
| |
| module = kmem_alloc(sizeof (kstat_module_t), KM_SLEEP); |
| module->ksm_proc = pde; |
| strlcpy(module->ksm_name, name, KSTAT_STRLEN+1); |
| INIT_LIST_HEAD(&module->ksm_kstat_list); |
| list_add_tail(&module->ksm_module_list, &kstat_module_list); |
| |
| return (module); |
| |
| } |
| |
| static void |
| kstat_delete_module(kstat_module_t *module) |
| { |
| ASSERT(list_empty(&module->ksm_kstat_list)); |
| remove_proc_entry(module->ksm_name, proc_spl_kstat); |
| list_del(&module->ksm_module_list); |
| kmem_free(module, sizeof (kstat_module_t)); |
| } |
| |
| static int |
| proc_kstat_open(struct inode *inode, struct file *filp) |
| { |
| struct seq_file *f; |
| int rc; |
| |
| rc = seq_open(filp, &kstat_seq_ops); |
| if (rc) |
| return (rc); |
| |
| f = filp->private_data; |
| f->private = PDE_DATA(inode); |
| |
| return (rc); |
| } |
| |
| static ssize_t |
| proc_kstat_write(struct file *filp, const char __user *buf, size_t len, |
| loff_t *ppos) |
| { |
| struct seq_file *f = filp->private_data; |
| kstat_t *ksp = f->private; |
| int rc; |
| |
| ASSERT(ksp->ks_magic == KS_MAGIC); |
| |
| mutex_enter(ksp->ks_lock); |
| rc = ksp->ks_update(ksp, KSTAT_WRITE); |
| mutex_exit(ksp->ks_lock); |
| |
| if (rc) |
| return (-rc); |
| |
| *ppos += len; |
| return (len); |
| } |
| |
| static const kstat_proc_op_t proc_kstat_operations = { |
| #ifdef HAVE_PROC_OPS_STRUCT |
| .proc_open = proc_kstat_open, |
| .proc_write = proc_kstat_write, |
| .proc_read = seq_read, |
| .proc_lseek = seq_lseek, |
| .proc_release = seq_release, |
| #else |
| .open = proc_kstat_open, |
| .write = proc_kstat_write, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| #endif |
| }; |
| |
| void |
| __kstat_set_raw_ops(kstat_t *ksp, |
| int (*headers)(char *buf, size_t size), |
| int (*data)(char *buf, size_t size, void *data), |
| void *(*addr)(kstat_t *ksp, loff_t index)) |
| { |
| ksp->ks_raw_ops.headers = headers; |
| ksp->ks_raw_ops.data = data; |
| ksp->ks_raw_ops.addr = addr; |
| } |
| EXPORT_SYMBOL(__kstat_set_raw_ops); |
| |
| void |
| kstat_proc_entry_init(kstat_proc_entry_t *kpep, const char *module, |
| const char *name) |
| { |
| kpep->kpe_owner = NULL; |
| kpep->kpe_proc = NULL; |
| INIT_LIST_HEAD(&kpep->kpe_list); |
| strncpy(kpep->kpe_module, module, KSTAT_STRLEN); |
| strncpy(kpep->kpe_name, name, KSTAT_STRLEN); |
| } |
| EXPORT_SYMBOL(kstat_proc_entry_init); |
| |
| kstat_t * |
| __kstat_create(const char *ks_module, int ks_instance, const char *ks_name, |
| const char *ks_class, uchar_t ks_type, uint_t ks_ndata, |
| uchar_t ks_flags) |
| { |
| kstat_t *ksp; |
| |
| ASSERT(ks_module); |
| ASSERT(ks_instance == 0); |
| ASSERT(ks_name); |
| |
| if ((ks_type == KSTAT_TYPE_INTR) || (ks_type == KSTAT_TYPE_IO)) |
| ASSERT(ks_ndata == 1); |
| |
| ksp = kmem_zalloc(sizeof (*ksp), KM_SLEEP); |
| if (ksp == NULL) |
| return (ksp); |
| |
| mutex_enter(&kstat_module_lock); |
| ksp->ks_kid = kstat_id; |
| kstat_id++; |
| mutex_exit(&kstat_module_lock); |
| |
| ksp->ks_magic = KS_MAGIC; |
| mutex_init(&ksp->ks_private_lock, NULL, MUTEX_DEFAULT, NULL); |
| ksp->ks_lock = &ksp->ks_private_lock; |
| |
| ksp->ks_crtime = gethrtime(); |
| ksp->ks_snaptime = ksp->ks_crtime; |
| ksp->ks_instance = ks_instance; |
| strncpy(ksp->ks_class, ks_class, KSTAT_STRLEN); |
| ksp->ks_type = ks_type; |
| ksp->ks_flags = ks_flags; |
| ksp->ks_update = kstat_default_update; |
| ksp->ks_private = NULL; |
| ksp->ks_raw_ops.headers = NULL; |
| ksp->ks_raw_ops.data = NULL; |
| ksp->ks_raw_ops.addr = NULL; |
| ksp->ks_raw_buf = NULL; |
| ksp->ks_raw_bufsize = 0; |
| kstat_proc_entry_init(&ksp->ks_proc, ks_module, ks_name); |
| |
| switch (ksp->ks_type) { |
| case KSTAT_TYPE_RAW: |
| ksp->ks_ndata = 1; |
| ksp->ks_data_size = ks_ndata; |
| break; |
| case KSTAT_TYPE_NAMED: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_named_t); |
| break; |
| case KSTAT_TYPE_INTR: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_intr_t); |
| break; |
| case KSTAT_TYPE_IO: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_io_t); |
| break; |
| case KSTAT_TYPE_TIMER: |
| ksp->ks_ndata = ks_ndata; |
| ksp->ks_data_size = ks_ndata * sizeof (kstat_timer_t); |
| break; |
| default: |
| PANIC("Undefined kstat type %d\n", ksp->ks_type); |
| } |
| |
| if (ksp->ks_flags & KSTAT_FLAG_VIRTUAL) { |
| ksp->ks_data = NULL; |
| } else { |
| ksp->ks_data = kmem_zalloc(ksp->ks_data_size, KM_SLEEP); |
| if (ksp->ks_data == NULL) { |
| kmem_free(ksp, sizeof (*ksp)); |
| ksp = NULL; |
| } |
| } |
| |
| return (ksp); |
| } |
| EXPORT_SYMBOL(__kstat_create); |
| |
| static int |
| kstat_detect_collision(kstat_proc_entry_t *kpep) |
| { |
| kstat_module_t *module; |
| kstat_proc_entry_t *tmp = NULL; |
| char *parent; |
| char *cp; |
| |
| parent = kmem_asprintf("%s", kpep->kpe_module); |
| |
| if ((cp = strrchr(parent, '/')) == NULL) { |
| strfree(parent); |
| return (0); |
| } |
| |
| cp[0] = '\0'; |
| if ((module = kstat_find_module(parent)) != NULL) { |
| list_for_each_entry(tmp, &module->ksm_kstat_list, kpe_list) { |
| if (strncmp(tmp->kpe_name, cp+1, KSTAT_STRLEN) == 0) { |
| strfree(parent); |
| return (EEXIST); |
| } |
| } |
| } |
| |
| strfree(parent); |
| return (0); |
| } |
| |
| /* |
| * Add a file to the proc filesystem under the kstat namespace (i.e. |
| * /proc/spl/kstat/). The file need not necessarily be implemented as a |
| * kstat. |
| */ |
| void |
| kstat_proc_entry_install(kstat_proc_entry_t *kpep, mode_t mode, |
| const kstat_proc_op_t *proc_ops, void *data) |
| { |
| kstat_module_t *module; |
| kstat_proc_entry_t *tmp = NULL; |
| |
| ASSERT(kpep); |
| |
| mutex_enter(&kstat_module_lock); |
| |
| module = kstat_find_module(kpep->kpe_module); |
| if (module == NULL) { |
| if (kstat_detect_collision(kpep) != 0) { |
| cmn_err(CE_WARN, "kstat_create('%s', '%s'): namespace" \ |
| " collision", kpep->kpe_module, kpep->kpe_name); |
| goto out; |
| } |
| module = kstat_create_module(kpep->kpe_module); |
| if (module == NULL) |
| goto out; |
| } |
| |
| /* |
| * Only one entry by this name per-module, on failure the module |
| * shouldn't be deleted because we know it has at least one entry. |
| */ |
| list_for_each_entry(tmp, &module->ksm_kstat_list, kpe_list) { |
| if (strncmp(tmp->kpe_name, kpep->kpe_name, KSTAT_STRLEN) == 0) |
| goto out; |
| } |
| |
| list_add_tail(&kpep->kpe_list, &module->ksm_kstat_list); |
| |
| kpep->kpe_owner = module; |
| kpep->kpe_proc = proc_create_data(kpep->kpe_name, mode, |
| module->ksm_proc, proc_ops, data); |
| if (kpep->kpe_proc == NULL) { |
| list_del_init(&kpep->kpe_list); |
| if (list_empty(&module->ksm_kstat_list)) |
| kstat_delete_module(module); |
| } |
| out: |
| mutex_exit(&kstat_module_lock); |
| |
| } |
| EXPORT_SYMBOL(kstat_proc_entry_install); |
| |
| void |
| __kstat_install(kstat_t *ksp) |
| { |
| ASSERT(ksp); |
| mode_t mode; |
| /* Specify permission modes for different kstats */ |
| if (strncmp(ksp->ks_proc.kpe_name, "dbufs", KSTAT_STRLEN) == 0) { |
| mode = 0600; |
| } else { |
| mode = 0644; |
| } |
| kstat_proc_entry_install( |
| &ksp->ks_proc, mode, &proc_kstat_operations, ksp); |
| } |
| EXPORT_SYMBOL(__kstat_install); |
| |
| void |
| kstat_proc_entry_delete(kstat_proc_entry_t *kpep) |
| { |
| kstat_module_t *module = kpep->kpe_owner; |
| if (kpep->kpe_proc) |
| remove_proc_entry(kpep->kpe_name, module->ksm_proc); |
| |
| mutex_enter(&kstat_module_lock); |
| list_del_init(&kpep->kpe_list); |
| |
| /* |
| * Remove top level module directory if it wasn't empty before, but now |
| * is. |
| */ |
| if (kpep->kpe_proc && list_empty(&module->ksm_kstat_list)) |
| kstat_delete_module(module); |
| mutex_exit(&kstat_module_lock); |
| |
| } |
| EXPORT_SYMBOL(kstat_proc_entry_delete); |
| |
| void |
| __kstat_delete(kstat_t *ksp) |
| { |
| kstat_proc_entry_delete(&ksp->ks_proc); |
| |
| if (!(ksp->ks_flags & KSTAT_FLAG_VIRTUAL)) |
| kmem_free(ksp->ks_data, ksp->ks_data_size); |
| |
| ksp->ks_lock = NULL; |
| mutex_destroy(&ksp->ks_private_lock); |
| kmem_free(ksp, sizeof (*ksp)); |
| } |
| EXPORT_SYMBOL(__kstat_delete); |
| |
| int |
| spl_kstat_init(void) |
| { |
| mutex_init(&kstat_module_lock, NULL, MUTEX_DEFAULT, NULL); |
| INIT_LIST_HEAD(&kstat_module_list); |
| kstat_id = 0; |
| return (0); |
| } |
| |
| void |
| spl_kstat_fini(void) |
| { |
| ASSERT(list_empty(&kstat_module_list)); |
| mutex_destroy(&kstat_module_lock); |
| } |