summaryrefslogtreecommitdiff
path: root/sys/dev/kstat.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/kstat.c')
-rw-r--r--sys/dev/kstat.c689
1 files changed, 689 insertions, 0 deletions
diff --git a/sys/dev/kstat.c b/sys/dev/kstat.c
new file mode 100644
index 00000000000..11e3ed8dd0d
--- /dev/null
+++ b/sys/dev/kstat.c
@@ -0,0 +1,689 @@
+/* $OpenBSD: kstat.c,v 1.1 2020/07/06 03:56:51 dlg Exp $ */
+
+/*
+ * Copyright (c) 2020 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+#include <sys/malloc.h>
+#include <sys/pool.h>
+#include <sys/time.h>
+
+/* for kstat_set_cpu */
+#include <sys/proc.h>
+#include <sys/sched.h>
+
+#include <sys/kstat.h>
+
+RBT_HEAD(kstat_id_tree, kstat);
+
+static inline int
+kstat_id_cmp(const struct kstat *a, const struct kstat *b)
+{
+ if (a->ks_id > b->ks_id)
+ return (1);
+ if (a->ks_id < b->ks_id)
+ return (-1);
+
+ return (0);
+}
+
+RBT_PROTOTYPE(kstat_id_tree, kstat, ks_id_entry, kstat_id_cmp);
+
+RBT_HEAD(kstat_pv_tree, kstat);
+
+static inline int
+kstat_pv_cmp(const struct kstat *a, const struct kstat *b)
+{
+ int rv;
+
+ rv = strcmp(a->ks_provider, b->ks_provider);
+ if (rv != 0)
+ return (rv);
+
+ if (a->ks_instance > b->ks_instance)
+ return (1);
+ if (a->ks_instance < b->ks_instance)
+ return (-1);
+
+ rv = strcmp(a->ks_name, b->ks_name);
+ if (rv != 0)
+ return (rv);
+
+ if (a->ks_unit > b->ks_unit)
+ return (1);
+ if (a->ks_unit < b->ks_unit)
+ return (-1);
+
+ return (0);
+}
+
+RBT_PROTOTYPE(kstat_pv_tree, kstat, ks_pv_entry, kstat_pv_cmp);
+
+RBT_HEAD(kstat_nm_tree, kstat);
+
+static inline int
+kstat_nm_cmp(const struct kstat *a, const struct kstat *b)
+{
+ int rv;
+
+ rv = strcmp(a->ks_name, b->ks_name);
+ if (rv != 0)
+ return (rv);
+
+ if (a->ks_unit > b->ks_unit)
+ return (1);
+ if (a->ks_unit < b->ks_unit)
+ return (-1);
+
+ rv = strcmp(a->ks_provider, b->ks_provider);
+ if (rv != 0)
+ return (rv);
+
+ if (a->ks_instance > b->ks_instance)
+ return (1);
+ if (a->ks_instance < b->ks_instance)
+ return (-1);
+
+ return (0);
+}
+
+RBT_PROTOTYPE(kstat_nm_tree, kstat, ks_nm_entry, kstat_nm_cmp);
+
+struct kstat_lock_ops {
+ void (*enter)(void *);
+ void (*leave)(void *);
+};
+
+#define kstat_enter(_ks) (_ks)->ks_lock_ops->enter((_ks)->ks_lock)
+#define kstat_leave(_ks) (_ks)->ks_lock_ops->leave((_ks)->ks_lock)
+
+const struct kstat_lock_ops kstat_rlock_ops = {
+ (void (*)(void *))rw_enter_read,
+ (void (*)(void *))rw_exit_read,
+};
+
+const struct kstat_lock_ops kstat_wlock_ops = {
+ (void (*)(void *))rw_enter_write,
+ (void (*)(void *))rw_exit_write,
+};
+
+const struct kstat_lock_ops kstat_mutex_ops = {
+ (void (*)(void *))mtx_enter,
+ (void (*)(void *))mtx_leave,
+};
+
+void kstat_cpu_enter(void *);
+void kstat_cpu_leave(void *);
+
+const struct kstat_lock_ops kstat_cpu_ops = {
+ kstat_cpu_enter,
+ kstat_cpu_leave,
+};
+
+struct rwlock kstat_lock = RWLOCK_INITIALIZER("kstat");
+
+/*
+ * The global state is versioned so changes to the set of kstats
+ * can be detected. This is an int so it can be read atomically on
+ * any arch, which is a ridiculous optimisation, really.
+ */
+unsigned int kstat_version = 0;
+
+/*
+ * kstat structures have a unique identifier so they can be found
+ * quickly. Identifiers are 64bit in the hope that it won't wrap
+ * during the runtime of a system. The identifiers start at 1 so that
+ * 0 can be used as the first value for userland to iterate with.
+ */
+uint64_t kstat_next_id = 1;
+
+struct kstat_id_tree kstat_id_tree = RBT_INITIALIZER();
+struct kstat_pv_tree kstat_pv_tree = RBT_INITIALIZER();
+struct kstat_nm_tree kstat_nm_tree = RBT_INITIALIZER();
+struct pool kstat_pool;
+
+struct rwlock kstat_default_lock = RWLOCK_INITIALIZER("kstatlk");
+
+int kstat_read(struct kstat *);
+int kstat_copy(struct kstat *, void *);
+
+int
+kstatattach(int num)
+{
+ /* XXX install system stats here */
+ return (0);
+}
+
+int
+kstatopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+ return (0);
+}
+
+int
+kstatclose(dev_t dev, int flag, int mode, struct proc *p)
+{
+ return (0);
+}
+
+int
+kstatioc_enter(struct kstat_req *ksreq)
+{
+ int error;
+
+ error = rw_enter(&kstat_lock, RW_READ | RW_INTR);
+ if (error != 0)
+ return (error);
+
+ if (!ISSET(ksreq->ks_rflags, KSTATIOC_F_IGNVER) &&
+ ksreq->ks_version != kstat_version) {
+ error = EINVAL;
+ goto error;
+ }
+
+ return (0);
+
+error:
+ rw_exit(&kstat_lock);
+ return (error);
+}
+
+int
+kstatioc_leave(struct kstat_req *ksreq, struct kstat *ks)
+{
+ void *buf = NULL;
+ size_t klen = 0, ulen = 0;
+ struct timespec updated;
+ int error = 0;
+
+ if (ks == NULL) {
+ error = ENOENT;
+ goto error;
+ }
+
+ switch (ks->ks_state) {
+ case KSTAT_S_CREATED:
+ ksreq->ks_updated = ks->ks_created;
+ ksreq->ks_interval.tv_sec = 0;
+ ksreq->ks_interval.tv_nsec = 0;
+ ksreq->ks_datalen = 0;
+ ksreq->ks_dataver = 0;
+ break;
+
+ case KSTAT_S_INSTALLED:
+ ksreq->ks_dataver = ks->ks_dataver;
+ ksreq->ks_interval = ks->ks_interval;
+
+ if (ksreq->ks_data == NULL) {
+ /* userland doesn't want actual data, so shortcut */
+ kstat_enter(ks);
+ ksreq->ks_datalen = ks->ks_datalen;
+ ksreq->ks_updated = ks->ks_updated;
+ kstat_leave(ks);
+ break;
+ }
+
+ klen = ks->ks_datalen; /* KSTAT_F_REALLOC */
+ buf = malloc(klen, M_TEMP, M_WAITOK|M_CANFAIL);
+ if (buf == NULL) {
+ error = ENOMEM;
+ goto error;
+ }
+
+ kstat_enter(ks);
+ error = (*ks->ks_read)(ks);
+ if (error == 0) {
+ updated = ks->ks_updated;
+
+ /* KSTAT_F_REALLOC */
+ KASSERTMSG(ks->ks_datalen == klen,
+ "kstat doesnt support resized data yet");
+
+ error = (*ks->ks_copy)(ks, buf);
+ }
+ kstat_leave(ks);
+
+ if (error != 0)
+ goto error;
+
+ ulen = ksreq->ks_datalen;
+ ksreq->ks_datalen = klen; /* KSTAT_F_REALLOC */
+ ksreq->ks_updated = updated;
+ break;
+ default:
+ panic("ks %p unexpected state %u", ks, ks->ks_state);
+ }
+
+ ksreq->ks_version = kstat_version;
+ ksreq->ks_id = ks->ks_id;
+
+ if (strlcpy(ksreq->ks_provider, ks->ks_provider,
+ sizeof(ksreq->ks_provider)) >= sizeof(ksreq->ks_provider))
+ panic("kstat %p provider string has grown", ks);
+ ksreq->ks_instance = ks->ks_instance;
+ if (strlcpy(ksreq->ks_name, ks->ks_name,
+ sizeof(ksreq->ks_name)) >= sizeof(ksreq->ks_name))
+ panic("kstat %p name string has grown", ks);
+ ksreq->ks_unit = ks->ks_unit;
+
+ ksreq->ks_created = ks->ks_created;
+ ksreq->ks_type = ks->ks_type;
+ ksreq->ks_state = ks->ks_state;
+
+error:
+ rw_exit(&kstat_lock);
+
+ if (buf != NULL) {
+ if (error == 0)
+ error = copyout(buf, ksreq->ks_data, min(klen, ulen));
+
+ free(buf, M_TEMP, klen);
+ }
+
+ return (error);
+}
+
+int
+kstatioc_find_id(struct kstat_req *ksreq)
+{
+ struct kstat *ks, key;
+ int error;
+
+ error = kstatioc_enter(ksreq);
+ if (error != 0)
+ return (error);
+
+ key.ks_id = ksreq->ks_id;
+
+ ks = RBT_FIND(kstat_id_tree, &kstat_id_tree, &key);
+
+ return (kstatioc_leave(ksreq, ks));
+}
+
+int
+kstatioc_nfind_id(struct kstat_req *ksreq)
+{
+ struct kstat *ks, key;
+ int error;
+
+ error = kstatioc_enter(ksreq);
+ if (error != 0)
+ return (error);
+
+ key.ks_id = ksreq->ks_id;
+
+ ks = RBT_NFIND(kstat_id_tree, &kstat_id_tree, &key);
+
+ return (kstatioc_leave(ksreq, ks));
+}
+
+int
+kstatioc_find_pv(struct kstat_req *ksreq)
+{
+ struct kstat *ks, key;
+ int error;
+
+ error = kstatioc_enter(ksreq);
+ if (error != 0)
+ return (error);
+
+ key.ks_provider = ksreq->ks_provider;
+ key.ks_instance = ksreq->ks_instance;
+ key.ks_name = ksreq->ks_name;
+ key.ks_unit = ksreq->ks_unit;
+
+ ks = RBT_FIND(kstat_pv_tree, &kstat_pv_tree, &key);
+
+ return (kstatioc_leave(ksreq, ks));
+}
+
+int
+kstatioc_nfind_pv(struct kstat_req *ksreq)
+{
+ struct kstat *ks, key;
+ int error;
+
+ error = kstatioc_enter(ksreq);
+ if (error != 0)
+ return (error);
+
+ key.ks_provider = ksreq->ks_provider;
+ key.ks_instance = ksreq->ks_instance;
+ key.ks_name = ksreq->ks_name;
+ key.ks_unit = ksreq->ks_unit;
+
+ ks = RBT_NFIND(kstat_pv_tree, &kstat_pv_tree, &key);
+
+ return (kstatioc_leave(ksreq, ks));
+}
+
+int
+kstatioc_find_nm(struct kstat_req *ksreq)
+{
+ struct kstat *ks, key;
+ int error;
+
+ error = kstatioc_enter(ksreq);
+ if (error != 0)
+ return (error);
+
+ key.ks_name = ksreq->ks_name;
+ key.ks_unit = ksreq->ks_unit;
+ key.ks_provider = ksreq->ks_provider;
+ key.ks_instance = ksreq->ks_instance;
+
+ ks = RBT_FIND(kstat_nm_tree, &kstat_nm_tree, &key);
+
+ return (kstatioc_leave(ksreq, ks));
+}
+
+int
+kstatioc_nfind_nm(struct kstat_req *ksreq)
+{
+ struct kstat *ks, key;
+ int error;
+
+ error = kstatioc_enter(ksreq);
+ if (error != 0)
+ return (error);
+
+ key.ks_name = ksreq->ks_name;
+ key.ks_unit = ksreq->ks_unit;
+ key.ks_provider = ksreq->ks_provider;
+ key.ks_instance = ksreq->ks_instance;
+
+ ks = RBT_NFIND(kstat_nm_tree, &kstat_nm_tree, &key);
+
+ return (kstatioc_leave(ksreq, ks));
+}
+
+int
+kstatioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct kstat_req *ksreq = (struct kstat_req *)data;
+ int error = 0;
+
+ KERNEL_UNLOCK();
+
+ switch (cmd) {
+ case KSTATIOC_VERSION:
+ *(unsigned int *)data = kstat_version;
+ break;
+
+ case KSTATIOC_FIND_ID:
+ error = kstatioc_find_id(ksreq);
+ break;
+ case KSTATIOC_NFIND_ID:
+ error = kstatioc_nfind_id(ksreq);
+ break;
+ case KSTATIOC_FIND_PROVIDER:
+ error = kstatioc_find_pv(ksreq);
+ break;
+ case KSTATIOC_NFIND_PROVIDER:
+ error = kstatioc_nfind_pv(ksreq);
+ break;
+ case KSTATIOC_FIND_NAME:
+ error = kstatioc_find_nm(ksreq);
+ break;
+ case KSTATIOC_NFIND_NAME:
+ error = kstatioc_nfind_nm(ksreq);
+ break;
+
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ KERNEL_LOCK();
+
+ return (error);
+}
+
+void
+kstat_init(void)
+{
+ static int initialized = 0;
+
+ if (initialized)
+ return;
+
+ pool_init(&kstat_pool, sizeof(struct kstat), 0, IPL_NONE,
+ PR_WAITOK | PR_RWLOCK, "kstatmem", NULL);
+
+ initialized = 1;
+}
+
+int
+kstat_strcheck(const char *str)
+{
+ size_t i, l;
+
+ l = strlen(str);
+ if (l == 0 || l >= KSTAT_STRLEN)
+ return (-1);
+ for (i = 0; i < l; i++) {
+ int ch = str[i];
+ if (ch >= 'a' && ch <= 'z')
+ continue;
+ if (ch >= 'A' && ch <= 'Z')
+ continue;
+ if (ch >= '0' && ch <= '9')
+ continue;
+ switch (ch) {
+ case '-':
+ case '_':
+ case '.':
+ break;
+ default:
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+struct kstat *
+kstat_create(const char *provider, unsigned int instance,
+ const char *name, unsigned int unit,
+ unsigned int type, unsigned int flags)
+{
+ struct kstat *ks, *oks;
+
+ if (kstat_strcheck(provider) == -1)
+ panic("invalid provider string");
+ if (kstat_strcheck(name) == -1)
+ panic("invalid name string");
+
+ kstat_init();
+
+ ks = pool_get(&kstat_pool, PR_WAITOK|PR_ZERO);
+
+ ks->ks_provider = provider;
+ ks->ks_instance = instance;
+ ks->ks_name = name;
+ ks->ks_unit = unit;
+ ks->ks_flags = flags;
+ ks->ks_type = type;
+ ks->ks_state = KSTAT_S_CREATED;
+
+ getnanouptime(&ks->ks_created);
+ ks->ks_updated = ks->ks_created;
+
+ ks->ks_lock = &kstat_default_lock;
+ ks->ks_lock_ops = &kstat_wlock_ops;
+ ks->ks_read = kstat_read;
+ ks->ks_copy = kstat_copy;
+
+ rw_enter_write(&kstat_lock);
+ ks->ks_id = kstat_next_id;
+
+ oks = RBT_INSERT(kstat_pv_tree, &kstat_pv_tree, ks);
+ if (oks == NULL) {
+ /* commit */
+ kstat_next_id++;
+ kstat_version++;
+
+ oks = RBT_INSERT(kstat_nm_tree, &kstat_nm_tree, ks);
+ if (oks != NULL)
+ panic("kstat name collision! (%llu)", ks->ks_id);
+
+ oks = RBT_INSERT(kstat_id_tree, &kstat_id_tree, ks);
+ if (oks != NULL)
+ panic("kstat id collision! (%llu)", ks->ks_id);
+ }
+ rw_exit_write(&kstat_lock);
+
+ if (oks != NULL) {
+ pool_put(&kstat_pool, ks);
+ return (NULL);
+ }
+
+ return (ks);
+}
+
+void
+kstat_set_rlock(struct kstat *ks, struct rwlock *rwl)
+{
+ KASSERT(ks->ks_state == KSTAT_S_CREATED);
+
+ ks->ks_lock = rwl;
+ ks->ks_lock_ops = &kstat_rlock_ops;
+}
+
+void
+kstat_set_wlock(struct kstat *ks, struct rwlock *rwl)
+{
+ KASSERT(ks->ks_state == KSTAT_S_CREATED);
+
+ ks->ks_lock = rwl;
+ ks->ks_lock_ops = &kstat_wlock_ops;
+}
+
+void
+kstat_set_mutex(struct kstat *ks, struct mutex *mtx)
+{
+ KASSERT(ks->ks_state == KSTAT_S_CREATED);
+
+ ks->ks_lock = mtx;
+ ks->ks_lock_ops = &kstat_mutex_ops;
+}
+
+void
+kstat_cpu_enter(void *p)
+{
+ struct cpu_info *ci = p;
+ sched_peg_curproc(ci);
+}
+
+void
+kstat_cpu_leave(void *p)
+{
+ atomic_clearbits_int(&curproc->p_flag, P_CPUPEG);
+}
+
+void
+kstat_set_cpu(struct kstat *ks, struct cpu_info *ci)
+{
+ KASSERT(ks->ks_state == KSTAT_S_CREATED);
+
+ ks->ks_lock = ci;
+ ks->ks_lock_ops = &kstat_cpu_ops;
+}
+
+int
+kstat_read_nop(struct kstat *ks)
+{
+ return (0);
+}
+
+void
+kstat_install(struct kstat *ks)
+{
+ if (!ISSET(ks->ks_flags, KSTAT_F_REALLOC)) {
+ KASSERTMSG(ks->ks_copy != NULL || ks->ks_data != NULL,
+ "kstat %p %s:%u:%s:%u must provide ks_copy or ks_data", ks,
+ ks->ks_provider, ks->ks_instance, ks->ks_name, ks->ks_unit);
+ KASSERT(ks->ks_datalen > 0);
+ }
+
+ rw_enter_write(&kstat_lock);
+ ks->ks_state = KSTAT_S_INSTALLED;
+ rw_exit_write(&kstat_lock);
+}
+
+void
+kstat_destroy(struct kstat *ks)
+{
+ rw_enter_write(&kstat_lock);
+ RBT_REMOVE(kstat_id_tree, &kstat_id_tree, ks);
+ RBT_REMOVE(kstat_pv_tree, &kstat_pv_tree, ks);
+ RBT_REMOVE(kstat_nm_tree, &kstat_nm_tree, ks);
+ kstat_version++;
+ rw_exit_write(&kstat_lock);
+
+ pool_put(&kstat_pool, ks);
+}
+
+int
+kstat_read(struct kstat *ks)
+{
+ getnanouptime(&ks->ks_updated);
+ return (0);
+}
+
+int
+kstat_copy(struct kstat *ks, void *buf)
+{
+ memcpy(buf, ks->ks_data, ks->ks_datalen);
+ return (0);
+}
+
+RBT_GENERATE(kstat_id_tree, kstat, ks_id_entry, kstat_id_cmp);
+RBT_GENERATE(kstat_pv_tree, kstat, ks_pv_entry, kstat_pv_cmp);
+RBT_GENERATE(kstat_nm_tree, kstat, ks_nm_entry, kstat_nm_cmp);
+
+void
+kstat_kv_init(struct kstat_kv *kv, const char *name, enum kstat_kv_type type)
+{
+ memset(kv, 0, sizeof(*kv));
+ strlcpy(kv->kv_key, name, sizeof(kv->kv_key)); /* XXX truncated? */
+ kv->kv_type = type;
+ kv->kv_unit = KSTAT_KV_U_NONE;
+}
+
+void
+kstat_kv_unit_init(struct kstat_kv *kv, const char *name,
+ enum kstat_kv_type type, enum kstat_kv_unit unit)
+{
+ switch (type) {
+ case KSTAT_KV_T_COUNTER64:
+ case KSTAT_KV_T_COUNTER32:
+ case KSTAT_KV_T_UINT64:
+ case KSTAT_KV_T_INT64:
+ case KSTAT_KV_T_UINT32:
+ case KSTAT_KV_T_INT32:
+ break;
+ default:
+ panic("kv unit init %s: unit for non-integer type", name);
+ }
+
+ memset(kv, 0, sizeof(*kv));
+ strlcpy(kv->kv_key, name, sizeof(kv->kv_key)); /* XXX truncated? */
+ kv->kv_type = type;
+ kv->kv_unit = unit;
+}