/* * Copyright (c) 2019 AVM GmbH . * * This program 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 program 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, see . */ #include #include #include #include #include "export/errstat.h" struct errstat_domain { struct errstat_domain_storage _dom; }; #define NAMES(dom) (dom)->_dom.names #define NUM(dom) (dom)->_dom.num_entries #define COUNTERS(dom) (dom)->_dom.counters #define UNKNOWN(dom) (dom)->_dom.unknown static void reset(struct errstat_domain *dom) { int i; for (i = 0; i < NUM(dom); i++) { atomic_set(&COUNTERS(dom)[i], 0); } atomic_set(&UNKNOWN(dom), 0); } struct errstat_domain *errstat_domain_init(struct errstat_domain_storage *dom, const char *const *names, atomic_t *counters, unsigned int num_entries) { memset(dom, 0, sizeof(*dom)); dom->num_entries = num_entries; dom->names = names; dom->counters = counters; reset((struct errstat_domain *)dom); return (struct errstat_domain *)dom; } EXPORT_SYMBOL(errstat_domain_init); unsigned long errstat_track(struct errstat_domain *dom, unsigned long err) { if (err >= NUM(dom)) { atomic_inc(&UNKNOWN(dom)); return err; } atomic_inc_unless_negative(&COUNTERS(dom)[err]); return err; } EXPORT_SYMBOL(errstat_track); static ssize_t errstat_sysfs_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t size) { struct errstat_domain *dom = (void *)container_of(attr, struct errstat_domain_storage, attr); reset(dom); return size; } static ssize_t errstat_sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int i, val; ssize_t left = PAGE_SIZE, fmt_len; struct errstat_domain_storage *dom = container_of(attr, struct errstat_domain_storage, attr); for (i = 0; i < dom->num_entries; i++) { const char *name = dom->names[i] ?: ""; val = atomic_read(&dom->counters[i]); /* But if that glacier passes beyond the river, I will * have no choice. * I will have to release the lava. */ if (val == INT_MIN) /* Release the lava. */ fmt_len = snprintf(buf, left, "%s: >%d (maxed out)\n", name, INT_MAX); else fmt_len = snprintf(buf, left, "%s: %d\n", name, val); left = max(left - fmt_len, 0); buf += fmt_len; } val = atomic_read(&dom->unknown); fmt_len = snprintf(buf, left, ": %d\n", val); left = max(left - fmt_len, 0); return PAGE_SIZE - left; } int errstat_sysfs_attach(struct errstat_domain *dom, struct kobject *parent, const char *name) { struct errstat_domain_storage *d = &dom->_dom; name = name ?: "error_stats"; d->attr.show = errstat_sysfs_show; d->attr.store = errstat_sysfs_store; d->attr.attr.name = name; d->attr.attr.mode = 0600; BUG_ON(d->parent); BUG_ON(parent->ktype->sysfs_ops != &kobj_sysfs_ops); d->parent = parent; return sysfs_create_file(parent, &d->attr.attr); } EXPORT_SYMBOL(errstat_sysfs_attach); void errstat_sysfs_detach(struct errstat_domain *dom) { struct errstat_domain_storage *d = &dom->_dom; if (d->parent) sysfs_remove_file(d->parent, &d->attr.attr); } EXPORT_SYMBOL(errstat_sysfs_detach);