root/drivers/counter/counter-sysfs.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Generic Counter sysfs interface
 * Copyright (C) 2020 William Breathitt Gray
 */
#include <linux/counter.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/kstrtox.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/types.h>

#include "counter-sysfs.h"

static inline struct counter_device *counter_from_dev(struct device *dev)
{
        return container_of(dev, struct counter_device, dev);
}

/**
 * struct counter_attribute - Counter sysfs attribute
 * @dev_attr:   device attribute for sysfs
 * @l:          node to add Counter attribute to attribute group list
 * @comp:       Counter component callbacks and data
 * @scope:      Counter scope of the attribute
 * @parent:     pointer to the parent component
 */
struct counter_attribute {
        struct device_attribute dev_attr;
        struct list_head l;

        struct counter_comp comp;
        enum counter_scope scope;
        void *parent;
};

#define to_counter_attribute(_dev_attr) \
        container_of(_dev_attr, struct counter_attribute, dev_attr)

/**
 * struct counter_attribute_group - container for attribute group
 * @name:       name of the attribute group
 * @attr_list:  list to keep track of created attributes
 * @num_attr:   number of attributes
 */
struct counter_attribute_group {
        const char *name;
        struct list_head attr_list;
        size_t num_attr;
};

static const char *const counter_function_str[] = {
        [COUNTER_FUNCTION_INCREASE] = "increase",
        [COUNTER_FUNCTION_DECREASE] = "decrease",
        [COUNTER_FUNCTION_PULSE_DIRECTION] = "pulse-direction",
        [COUNTER_FUNCTION_QUADRATURE_X1_A] = "quadrature x1 a",
        [COUNTER_FUNCTION_QUADRATURE_X1_B] = "quadrature x1 b",
        [COUNTER_FUNCTION_QUADRATURE_X2_A] = "quadrature x2 a",
        [COUNTER_FUNCTION_QUADRATURE_X2_B] = "quadrature x2 b",
        [COUNTER_FUNCTION_QUADRATURE_X4] = "quadrature x4"
};

static const char *const counter_signal_value_str[] = {
        [COUNTER_SIGNAL_LEVEL_LOW] = "low",
        [COUNTER_SIGNAL_LEVEL_HIGH] = "high"
};

static const char *const counter_synapse_action_str[] = {
        [COUNTER_SYNAPSE_ACTION_NONE] = "none",
        [COUNTER_SYNAPSE_ACTION_RISING_EDGE] = "rising edge",
        [COUNTER_SYNAPSE_ACTION_FALLING_EDGE] = "falling edge",
        [COUNTER_SYNAPSE_ACTION_BOTH_EDGES] = "both edges"
};

static const char *const counter_count_direction_str[] = {
        [COUNTER_COUNT_DIRECTION_FORWARD] = "forward",
        [COUNTER_COUNT_DIRECTION_BACKWARD] = "backward"
};

static const char *const counter_count_mode_str[] = {
        [COUNTER_COUNT_MODE_NORMAL] = "normal",
        [COUNTER_COUNT_MODE_RANGE_LIMIT] = "range limit",
        [COUNTER_COUNT_MODE_NON_RECYCLE] = "non-recycle",
        [COUNTER_COUNT_MODE_MODULO_N] = "modulo-n",
        [COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT] = "interrupt on terminal count",
        [COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT] = "hardware retriggerable one-shot",
        [COUNTER_COUNT_MODE_RATE_GENERATOR] = "rate generator",
        [COUNTER_COUNT_MODE_SQUARE_WAVE_MODE] = "square wave mode",
        [COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE] = "software triggered strobe",
        [COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE] = "hardware triggered strobe",
};

static const char *const counter_signal_polarity_str[] = {
        [COUNTER_SIGNAL_POLARITY_POSITIVE] = "positive",
        [COUNTER_SIGNAL_POLARITY_NEGATIVE] = "negative"
};

static ssize_t counter_comp_u8_show(struct device *dev,
                                    struct device_attribute *attr, char *buf)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        int err;
        u8 data = 0;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_u8_read(counter, &data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_u8_read(counter, a->parent, &data);
                break;
        case COUNTER_SCOPE_COUNT:
                err = a->comp.count_u8_read(counter, a->parent, &data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        if (a->comp.type == COUNTER_COMP_BOOL)
                /* data should already be boolean but ensure just to be safe */
                data = !!data;

        return sysfs_emit(buf, "%u\n", (unsigned int)data);
}

static ssize_t counter_comp_u8_store(struct device *dev,
                                     struct device_attribute *attr,
                                     const char *buf, size_t len)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        int err;
        bool bool_data = 0;
        u8 data = 0;

        if (a->comp.type == COUNTER_COMP_BOOL) {
                err = kstrtobool(buf, &bool_data);
                data = bool_data;
        } else
                err = kstrtou8(buf, 0, &data);
        if (err < 0)
                return err;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_u8_write(counter, data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_u8_write(counter, a->parent, data);
                break;
        case COUNTER_SCOPE_COUNT:
                err = a->comp.count_u8_write(counter, a->parent, data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        return len;
}

static ssize_t counter_comp_u32_show(struct device *dev,
                                     struct device_attribute *attr, char *buf)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        const struct counter_available *const avail = a->comp.priv;
        int err;
        u32 data = 0;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_u32_read(counter, &data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_u32_read(counter, a->parent, &data);
                break;
        case COUNTER_SCOPE_COUNT:
                if (a->comp.type == COUNTER_COMP_SYNAPSE_ACTION)
                        err = a->comp.action_read(counter, a->parent,
                                                  a->comp.priv, &data);
                else
                        err = a->comp.count_u32_read(counter, a->parent, &data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        switch (a->comp.type) {
        case COUNTER_COMP_FUNCTION:
                return sysfs_emit(buf, "%s\n", counter_function_str[data]);
        case COUNTER_COMP_SIGNAL_LEVEL:
                return sysfs_emit(buf, "%s\n", counter_signal_value_str[data]);
        case COUNTER_COMP_SYNAPSE_ACTION:
                return sysfs_emit(buf, "%s\n", counter_synapse_action_str[data]);
        case COUNTER_COMP_ENUM:
                return sysfs_emit(buf, "%s\n", avail->strs[data]);
        case COUNTER_COMP_COUNT_DIRECTION:
                return sysfs_emit(buf, "%s\n", counter_count_direction_str[data]);
        case COUNTER_COMP_COUNT_MODE:
                return sysfs_emit(buf, "%s\n", counter_count_mode_str[data]);
        case COUNTER_COMP_SIGNAL_POLARITY:
                return sysfs_emit(buf, "%s\n", counter_signal_polarity_str[data]);
        default:
                return sysfs_emit(buf, "%u\n", (unsigned int)data);
        }
}

static int counter_find_enum(u32 *const enum_item, const u32 *const enums,
                             const size_t num_enums, const char *const buf,
                             const char *const string_array[])
{
        size_t index;

        for (index = 0; index < num_enums; index++) {
                *enum_item = enums[index];
                if (sysfs_streq(buf, string_array[*enum_item]))
                        return 0;
        }

        return -EINVAL;
}

static ssize_t counter_comp_u32_store(struct device *dev,
                                      struct device_attribute *attr,
                                      const char *buf, size_t len)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        struct counter_count *const count = a->parent;
        struct counter_synapse *const synapse = a->comp.priv;
        const struct counter_available *const avail = a->comp.priv;
        int err;
        u32 data = 0;

        switch (a->comp.type) {
        case COUNTER_COMP_FUNCTION:
                err = counter_find_enum(&data, count->functions_list,
                                        count->num_functions, buf,
                                        counter_function_str);
                break;
        case COUNTER_COMP_SYNAPSE_ACTION:
                err = counter_find_enum(&data, synapse->actions_list,
                                        synapse->num_actions, buf,
                                        counter_synapse_action_str);
                break;
        case COUNTER_COMP_ENUM:
                err = __sysfs_match_string(avail->strs, avail->num_items, buf);
                data = err;
                break;
        case COUNTER_COMP_COUNT_MODE:
                err = counter_find_enum(&data, avail->enums, avail->num_items,
                                        buf, counter_count_mode_str);
                break;
        case COUNTER_COMP_SIGNAL_POLARITY:
                err = counter_find_enum(&data, avail->enums, avail->num_items,
                                        buf, counter_signal_polarity_str);
                break;
        default:
                err = kstrtou32(buf, 0, &data);
                break;
        }
        if (err < 0)
                return err;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_u32_write(counter, data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_u32_write(counter, a->parent, data);
                break;
        case COUNTER_SCOPE_COUNT:
                if (a->comp.type == COUNTER_COMP_SYNAPSE_ACTION)
                        err = a->comp.action_write(counter, count, synapse,
                                                   data);
                else
                        err = a->comp.count_u32_write(counter, count, data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        return len;
}

static ssize_t counter_comp_u64_show(struct device *dev,
                                     struct device_attribute *attr, char *buf)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        int err;
        u64 data = 0;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_u64_read(counter, &data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_u64_read(counter, a->parent, &data);
                break;
        case COUNTER_SCOPE_COUNT:
                err = a->comp.count_u64_read(counter, a->parent, &data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        return sysfs_emit(buf, "%llu\n", (unsigned long long)data);
}

static ssize_t counter_comp_u64_store(struct device *dev,
                                      struct device_attribute *attr,
                                      const char *buf, size_t len)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        int err;
        u64 data = 0;

        err = kstrtou64(buf, 0, &data);
        if (err < 0)
                return err;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_u64_write(counter, data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_u64_write(counter, a->parent, data);
                break;
        case COUNTER_SCOPE_COUNT:
                err = a->comp.count_u64_write(counter, a->parent, data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        return len;
}

static ssize_t counter_comp_array_u32_show(struct device *dev,
                                           struct device_attribute *attr,
                                           char *buf)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        const struct counter_array *const element = a->comp.priv;
        int err;
        u32 data = 0;

        if (a->scope != COUNTER_SCOPE_SIGNAL ||
            element->type != COUNTER_COMP_SIGNAL_POLARITY)
                return -EINVAL;

        err = a->comp.signal_array_u32_read(counter, a->parent, element->idx,
                                            &data);
        if (err < 0)
                return err;

        return sysfs_emit(buf, "%s\n", counter_signal_polarity_str[data]);
}

static ssize_t counter_comp_array_u32_store(struct device *dev,
                                            struct device_attribute *attr,
                                            const char *buf, size_t len)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        const struct counter_array *const element = a->comp.priv;
        int err;
        u32 data = 0;

        if (element->type != COUNTER_COMP_SIGNAL_POLARITY ||
            a->scope != COUNTER_SCOPE_SIGNAL)
                return -EINVAL;

        err = counter_find_enum(&data, element->avail->enums,
                                element->avail->num_items, buf,
                                counter_signal_polarity_str);
        if (err < 0)
                return err;

        err = a->comp.signal_array_u32_write(counter, a->parent, element->idx,
                                             data);
        if (err < 0)
                return err;

        return len;
}

static ssize_t counter_comp_array_u64_show(struct device *dev,
                                           struct device_attribute *attr,
                                           char *buf)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        const struct counter_array *const element = a->comp.priv;
        int err;
        u64 data = 0;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_array_u64_read(counter, element->idx,
                                                    &data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_array_u64_read(counter, a->parent,
                                                    element->idx, &data);
                break;
        case COUNTER_SCOPE_COUNT:
                err = a->comp.count_array_u64_read(counter, a->parent,
                                                   element->idx, &data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        return sysfs_emit(buf, "%llu\n", (unsigned long long)data);
}

static ssize_t counter_comp_array_u64_store(struct device *dev,
                                            struct device_attribute *attr,
                                            const char *buf, size_t len)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        struct counter_device *const counter = counter_from_dev(dev);
        const struct counter_array *const element = a->comp.priv;
        int err;
        u64 data = 0;

        err = kstrtou64(buf, 0, &data);
        if (err < 0)
                return err;

        switch (a->scope) {
        case COUNTER_SCOPE_DEVICE:
                err = a->comp.device_array_u64_write(counter, element->idx,
                                                     data);
                break;
        case COUNTER_SCOPE_SIGNAL:
                err = a->comp.signal_array_u64_write(counter, a->parent,
                                                     element->idx, data);
                break;
        case COUNTER_SCOPE_COUNT:
                err = a->comp.count_array_u64_write(counter, a->parent,
                                                    element->idx, data);
                break;
        default:
                return -EINVAL;
        }
        if (err < 0)
                return err;

        return len;
}

static ssize_t enums_available_show(const u32 *const enums,
                                    const size_t num_enums,
                                    const char *const strs[], char *buf)
{
        size_t len = 0;
        size_t index;

        for (index = 0; index < num_enums; index++)
                len += sysfs_emit_at(buf, len, "%s\n", strs[enums[index]]);

        return len;
}

static ssize_t strs_available_show(const struct counter_available *const avail,
                                   char *buf)
{
        size_t len = 0;
        size_t index;

        for (index = 0; index < avail->num_items; index++)
                len += sysfs_emit_at(buf, len, "%s\n", avail->strs[index]);

        return len;
}

static ssize_t counter_comp_available_show(struct device *dev,
                                           struct device_attribute *attr,
                                           char *buf)
{
        const struct counter_attribute *const a = to_counter_attribute(attr);
        const struct counter_count *const count = a->parent;
        const struct counter_synapse *const synapse = a->comp.priv;
        const struct counter_available *const avail = a->comp.priv;

        switch (a->comp.type) {
        case COUNTER_COMP_FUNCTION:
                return enums_available_show(count->functions_list,
                                            count->num_functions,
                                            counter_function_str, buf);
        case COUNTER_COMP_SYNAPSE_ACTION:
                return enums_available_show(synapse->actions_list,
                                            synapse->num_actions,
                                            counter_synapse_action_str, buf);
        case COUNTER_COMP_ENUM:
                return strs_available_show(avail, buf);
        case COUNTER_COMP_COUNT_MODE:
                return enums_available_show(avail->enums, avail->num_items,
                                            counter_count_mode_str, buf);
        default:
                return -EINVAL;
        }
}

static int counter_avail_attr_create(struct device *const dev,
        struct counter_attribute_group *const group,
        const struct counter_comp *const comp, void *const parent)
{
        struct counter_attribute *counter_attr;
        struct device_attribute *dev_attr;

        counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
        if (!counter_attr)
                return -ENOMEM;

        /* Configure Counter attribute */
        counter_attr->comp.type = comp->type;
        counter_attr->comp.priv = comp->priv;
        counter_attr->parent = parent;

        /* Initialize sysfs attribute */
        dev_attr = &counter_attr->dev_attr;
        sysfs_attr_init(&dev_attr->attr);

        /* Configure device attribute */
        dev_attr->attr.name = devm_kasprintf(dev, GFP_KERNEL, "%s_available",
                                             comp->name);
        if (!dev_attr->attr.name)
                return -ENOMEM;
        dev_attr->attr.mode = 0444;
        dev_attr->show = counter_comp_available_show;

        /* Store list node */
        list_add(&counter_attr->l, &group->attr_list);
        group->num_attr++;

        return 0;
}

static int counter_attr_create(struct device *const dev,
                               struct counter_attribute_group *const group,
                               const struct counter_comp *const comp,
                               const enum counter_scope scope,
                               void *const parent)
{
        const struct counter_array *const array = comp->priv;
        struct counter_attribute *counter_attr;
        struct device_attribute *dev_attr;

        counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
        if (!counter_attr)
                return -ENOMEM;

        /* Configure Counter attribute */
        counter_attr->comp = *comp;
        counter_attr->scope = scope;
        counter_attr->parent = parent;

        /* Configure device attribute */
        dev_attr = &counter_attr->dev_attr;
        sysfs_attr_init(&dev_attr->attr);
        dev_attr->attr.name = comp->name;
        switch (comp->type) {
        case COUNTER_COMP_U8:
        case COUNTER_COMP_BOOL:
                if (comp->device_u8_read) {
                        dev_attr->attr.mode |= 0444;
                        dev_attr->show = counter_comp_u8_show;
                }
                if (comp->device_u8_write) {
                        dev_attr->attr.mode |= 0200;
                        dev_attr->store = counter_comp_u8_store;
                }
                break;
        case COUNTER_COMP_SIGNAL_LEVEL:
        case COUNTER_COMP_FUNCTION:
        case COUNTER_COMP_SYNAPSE_ACTION:
        case COUNTER_COMP_ENUM:
        case COUNTER_COMP_COUNT_DIRECTION:
        case COUNTER_COMP_COUNT_MODE:
        case COUNTER_COMP_SIGNAL_POLARITY:
                if (comp->device_u32_read) {
                        dev_attr->attr.mode |= 0444;
                        dev_attr->show = counter_comp_u32_show;
                }
                if (comp->device_u32_write) {
                        dev_attr->attr.mode |= 0200;
                        dev_attr->store = counter_comp_u32_store;
                }
                break;
        case COUNTER_COMP_U64:
                if (comp->device_u64_read) {
                        dev_attr->attr.mode |= 0444;
                        dev_attr->show = counter_comp_u64_show;
                }
                if (comp->device_u64_write) {
                        dev_attr->attr.mode |= 0200;
                        dev_attr->store = counter_comp_u64_store;
                }
                break;
        case COUNTER_COMP_ARRAY:
                switch (array->type) {
                case COUNTER_COMP_SIGNAL_POLARITY:
                        if (comp->signal_array_u32_read) {
                                dev_attr->attr.mode |= 0444;
                                dev_attr->show = counter_comp_array_u32_show;
                        }
                        if (comp->signal_array_u32_write) {
                                dev_attr->attr.mode |= 0200;
                                dev_attr->store = counter_comp_array_u32_store;
                        }
                        break;
                case COUNTER_COMP_U64:
                        if (comp->device_array_u64_read) {
                                dev_attr->attr.mode |= 0444;
                                dev_attr->show = counter_comp_array_u64_show;
                        }
                        if (comp->device_array_u64_write) {
                                dev_attr->attr.mode |= 0200;
                                dev_attr->store = counter_comp_array_u64_store;
                        }
                        break;
                default:
                        return -EINVAL;
                }
                break;
        default:
                return -EINVAL;
        }

        /* Store list node */
        list_add(&counter_attr->l, &group->attr_list);
        group->num_attr++;

        /* Create "*_available" attribute if needed */
        switch (comp->type) {
        case COUNTER_COMP_FUNCTION:
        case COUNTER_COMP_SYNAPSE_ACTION:
        case COUNTER_COMP_ENUM:
        case COUNTER_COMP_COUNT_MODE:
                return counter_avail_attr_create(dev, group, comp, parent);
        default:
                return 0;
        }
}

static ssize_t counter_comp_name_show(struct device *dev,
                                      struct device_attribute *attr, char *buf)
{
        return sysfs_emit(buf, "%s\n", to_counter_attribute(attr)->comp.name);
}

static int counter_name_attr_create(struct device *const dev,
                                    struct counter_attribute_group *const group,
                                    const char *const name)
{
        struct counter_attribute *counter_attr;

        counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
        if (!counter_attr)
                return -ENOMEM;

        /* Configure Counter attribute */
        counter_attr->comp.name = name;

        /* Configure device attribute */
        sysfs_attr_init(&counter_attr->dev_attr.attr);
        counter_attr->dev_attr.attr.name = "name";
        counter_attr->dev_attr.attr.mode = 0444;
        counter_attr->dev_attr.show = counter_comp_name_show;

        /* Store list node */
        list_add(&counter_attr->l, &group->attr_list);
        group->num_attr++;

        return 0;
}

static ssize_t counter_comp_id_show(struct device *dev,
                                    struct device_attribute *attr, char *buf)
{
        const size_t id = (size_t)to_counter_attribute(attr)->comp.priv;

        return sysfs_emit(buf, "%zu\n", id);
}

static int counter_comp_id_attr_create(struct device *const dev,
                                       struct counter_attribute_group *const group,
                                       const char *name, const size_t id)
{
        struct counter_attribute *counter_attr;

        /* Allocate Counter attribute */
        counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
        if (!counter_attr)
                return -ENOMEM;

        /* Generate component ID name */
        name = devm_kasprintf(dev, GFP_KERNEL, "%s_component_id", name);
        if (!name)
                return -ENOMEM;

        /* Configure Counter attribute */
        counter_attr->comp.priv = (void *)id;

        /* Configure device attribute */
        sysfs_attr_init(&counter_attr->dev_attr.attr);
        counter_attr->dev_attr.attr.name = name;
        counter_attr->dev_attr.attr.mode = 0444;
        counter_attr->dev_attr.show = counter_comp_id_show;

        /* Store list node */
        list_add(&counter_attr->l, &group->attr_list);
        group->num_attr++;

        return 0;
}

static int counter_ext_attrs_create(struct device *const dev,
                                    struct counter_attribute_group *const group,
                                    const struct counter_comp *const ext,
                                    const enum counter_scope scope,
                                    void *const parent, const size_t id)
{
        int err;

        /* Create main extension attribute */
        err = counter_attr_create(dev, group, ext, scope, parent);
        if (err < 0)
                return err;

        /* Create extension id attribute */
        return counter_comp_id_attr_create(dev, group, ext->name, id);
}

static int counter_array_attrs_create(struct device *const dev,
                                      struct counter_attribute_group *const group,
                                      const struct counter_comp *const comp,
                                      const enum counter_scope scope,
                                      void *const parent, const size_t id)
{
        const struct counter_array *const array = comp->priv;
        struct counter_comp ext = *comp;
        struct counter_array *element;
        size_t idx;
        int err;

        /* Create an attribute for each array element */
        for (idx = 0; idx < array->length; idx++) {
                /* Generate array element attribute name */
                ext.name = devm_kasprintf(dev, GFP_KERNEL, "%s%zu", comp->name,
                                          idx);
                if (!ext.name)
                        return -ENOMEM;

                /* Allocate and configure array element */
                element = devm_kzalloc(dev, sizeof(*element), GFP_KERNEL);
                if (!element)
                        return -ENOMEM;
                element->type = array->type;
                element->avail = array->avail;
                element->idx = idx;
                ext.priv = element;

                /* Create all attributes associated with the array element */
                err = counter_ext_attrs_create(dev, group, &ext, scope, parent,
                                               id + idx);
                if (err < 0)
                        return err;
        }

        return 0;
}

static int counter_sysfs_exts_add(struct device *const dev,
                                  struct counter_attribute_group *const group,
                                  const struct counter_comp *const exts,
                                  const size_t num_ext,
                                  const enum counter_scope scope,
                                  void *const parent)
{
        size_t i;
        const struct counter_comp *ext;
        int err;
        size_t id = 0;
        const struct counter_array *array;

        /* Create attributes for each extension */
        for (i = 0; i < num_ext; i++) {
                ext = &exts[i];
                if (ext->type == COUNTER_COMP_ARRAY) {
                        err = counter_array_attrs_create(dev, group, ext, scope,
                                                         parent, id);
                        array = ext->priv;
                        id += array->length;
                } else {
                        err = counter_ext_attrs_create(dev, group, ext, scope,
                                                       parent, id);
                        id++;
                }
                if (err < 0)
                        return err;
        }

        return 0;
}

static struct counter_comp counter_signal_comp = {
        .type = COUNTER_COMP_SIGNAL_LEVEL,
        .name = "signal",
};

static int counter_signal_attrs_create(struct counter_device *const counter,
        struct counter_attribute_group *const cattr_group,
        struct counter_signal *const signal)
{
        const enum counter_scope scope = COUNTER_SCOPE_SIGNAL;
        struct device *const dev = &counter->dev;
        int err;
        struct counter_comp comp;

        /* Create main Signal attribute */
        comp = counter_signal_comp;
        comp.signal_u32_read = counter->ops->signal_read;
        err = counter_attr_create(dev, cattr_group, &comp, scope, signal);
        if (err < 0)
                return err;

        /* Create Signal name attribute */
        err = counter_name_attr_create(dev, cattr_group, signal->name);
        if (err < 0)
                return err;

        /* Add Signal extensions */
        return counter_sysfs_exts_add(dev, cattr_group, signal->ext,
                                      signal->num_ext, scope, signal);
}

static int counter_sysfs_signals_add(struct counter_device *const counter,
        struct counter_attribute_group *const groups)
{
        size_t i;
        int err;

        /* Add each Signal */
        for (i = 0; i < counter->num_signals; i++) {
                /* Generate Signal attribute directory name */
                groups[i].name = devm_kasprintf(&counter->dev, GFP_KERNEL,
                                                "signal%zu", i);
                if (!groups[i].name)
                        return -ENOMEM;

                /* Create all attributes associated with Signal */
                err = counter_signal_attrs_create(counter, groups + i,
                                                  counter->signals + i);
                if (err < 0)
                        return err;
        }

        return 0;
}

static int counter_sysfs_synapses_add(struct counter_device *const counter,
        struct counter_attribute_group *const group,
        struct counter_count *const count)
{
        size_t i;

        /* Add each Synapse */
        for (i = 0; i < count->num_synapses; i++) {
                struct device *const dev = &counter->dev;
                struct counter_synapse *synapse;
                size_t id;
                struct counter_comp comp;
                int err;

                synapse = count->synapses + i;

                /* Generate Synapse action name */
                id = synapse->signal - counter->signals;
                comp.name = devm_kasprintf(dev, GFP_KERNEL, "signal%zu_action",
                                           id);
                if (!comp.name)
                        return -ENOMEM;

                /* Create action attribute */
                comp.type = COUNTER_COMP_SYNAPSE_ACTION;
                comp.action_read = counter->ops->action_read;
                comp.action_write = counter->ops->action_write;
                comp.priv = synapse;
                err = counter_attr_create(dev, group, &comp,
                                          COUNTER_SCOPE_COUNT, count);
                if (err < 0)
                        return err;

                /* Create Synapse component ID attribute */
                err = counter_comp_id_attr_create(dev, group, comp.name, i);
                if (err < 0)
                        return err;
        }

        return 0;
}

static struct counter_comp counter_count_comp =
        COUNTER_COMP_COUNT_U64("count", NULL, NULL);

static struct counter_comp counter_function_comp = {
        .type = COUNTER_COMP_FUNCTION,
        .name = "function",
};

static int counter_count_attrs_create(struct counter_device *const counter,
        struct counter_attribute_group *const cattr_group,
        struct counter_count *const count)
{
        const enum counter_scope scope = COUNTER_SCOPE_COUNT;
        struct device *const dev = &counter->dev;
        int err;
        struct counter_comp comp;

        /* Create main Count attribute */
        comp = counter_count_comp;
        comp.count_u64_read = counter->ops->count_read;
        comp.count_u64_write = counter->ops->count_write;
        err = counter_attr_create(dev, cattr_group, &comp, scope, count);
        if (err < 0)
                return err;

        /* Create Count name attribute */
        err = counter_name_attr_create(dev, cattr_group, count->name);
        if (err < 0)
                return err;

        /* Create Count function attribute */
        comp = counter_function_comp;
        comp.count_u32_read = counter->ops->function_read;
        comp.count_u32_write = counter->ops->function_write;
        err = counter_attr_create(dev, cattr_group, &comp, scope, count);
        if (err < 0)
                return err;

        /* Add Count extensions */
        return counter_sysfs_exts_add(dev, cattr_group, count->ext,
                                      count->num_ext, scope, count);
}

static int counter_sysfs_counts_add(struct counter_device *const counter,
        struct counter_attribute_group *const groups)
{
        size_t i;
        struct counter_count *count;
        int err;

        /* Add each Count */
        for (i = 0; i < counter->num_counts; i++) {
                count = counter->counts + i;

                /* Generate Count attribute directory name */
                groups[i].name = devm_kasprintf(&counter->dev, GFP_KERNEL,
                                                "count%zu", i);
                if (!groups[i].name)
                        return -ENOMEM;

                /* Add sysfs attributes of the Synapses */
                err = counter_sysfs_synapses_add(counter, groups + i, count);
                if (err < 0)
                        return err;

                /* Create all attributes associated with Count */
                err = counter_count_attrs_create(counter, groups + i, count);
                if (err < 0)
                        return err;
        }

        return 0;
}

static int counter_num_signals_read(struct counter_device *counter, u8 *val)
{
        *val = counter->num_signals;
        return 0;
}

static int counter_num_counts_read(struct counter_device *counter, u8 *val)
{
        *val = counter->num_counts;
        return 0;
}

static int counter_events_queue_size_read(struct counter_device *counter,
                                          u64 *val)
{
        *val = kfifo_size(&counter->events);
        return 0;
}

static int counter_events_queue_size_write(struct counter_device *counter,
                                           u64 val)
{
        DECLARE_KFIFO_PTR(events, struct counter_event);
        int err;
        unsigned long flags;

        /* Allocate new events queue */
        err = kfifo_alloc(&events, val, GFP_KERNEL);
        if (err)
                return err;

        /* Swap in new events queue */
        mutex_lock(&counter->events_out_lock);
        spin_lock_irqsave(&counter->events_in_lock, flags);
        kfifo_free(&counter->events);
        counter->events.kfifo = events.kfifo;
        spin_unlock_irqrestore(&counter->events_in_lock, flags);
        mutex_unlock(&counter->events_out_lock);

        return 0;
}

static struct counter_comp counter_num_signals_comp =
        COUNTER_COMP_DEVICE_U8("num_signals", counter_num_signals_read, NULL);

static struct counter_comp counter_num_counts_comp =
        COUNTER_COMP_DEVICE_U8("num_counts", counter_num_counts_read, NULL);

static struct counter_comp counter_events_queue_size_comp =
        COUNTER_COMP_DEVICE_U64("events_queue_size",
                                counter_events_queue_size_read,
                                counter_events_queue_size_write);

static int counter_sysfs_attr_add(struct counter_device *const counter,
                                  struct counter_attribute_group *cattr_group)
{
        const enum counter_scope scope = COUNTER_SCOPE_DEVICE;
        struct device *const dev = &counter->dev;
        int err;

        /* Add Signals sysfs attributes */
        err = counter_sysfs_signals_add(counter, cattr_group);
        if (err < 0)
                return err;
        cattr_group += counter->num_signals;

        /* Add Counts sysfs attributes */
        err = counter_sysfs_counts_add(counter, cattr_group);
        if (err < 0)
                return err;
        cattr_group += counter->num_counts;

        /* Create name attribute */
        err = counter_name_attr_create(dev, cattr_group, counter->name);
        if (err < 0)
                return err;

        /* Create num_signals attribute */
        err = counter_attr_create(dev, cattr_group, &counter_num_signals_comp,
                                  scope, NULL);
        if (err < 0)
                return err;

        /* Create num_counts attribute */
        err = counter_attr_create(dev, cattr_group, &counter_num_counts_comp,
                                  scope, NULL);
        if (err < 0)
                return err;

        /* Create events_queue_size attribute */
        err = counter_attr_create(dev, cattr_group,
                                  &counter_events_queue_size_comp, scope, NULL);
        if (err < 0)
                return err;

        /* Add device extensions */
        return counter_sysfs_exts_add(dev, cattr_group, counter->ext,
                                      counter->num_ext, scope, NULL);
}

/**
 * counter_sysfs_add - Adds Counter sysfs attributes to the device structure
 * @counter:    Pointer to the Counter device structure
 *
 * Counter sysfs attributes are created and added to the respective device
 * structure for later registration to the system. Resource-managed memory
 * allocation is performed by this function, and this memory should be freed
 * when no longer needed (automatically by a device_unregister call, or
 * manually by a devres_release_all call).
 */
int counter_sysfs_add(struct counter_device *const counter)
{
        struct device *const dev = &counter->dev;
        const size_t num_groups = counter->num_signals + counter->num_counts + 1;
        struct counter_attribute_group *cattr_groups;
        size_t i, j;
        int err;
        struct attribute_group *groups;
        struct counter_attribute *p;

        /* Allocate space for attribute groups (signals, counts, and ext) */
        cattr_groups = devm_kcalloc(dev, num_groups, sizeof(*cattr_groups),
                                    GFP_KERNEL);
        if (!cattr_groups)
                return -ENOMEM;

        /* Initialize attribute lists */
        for (i = 0; i < num_groups; i++)
                INIT_LIST_HEAD(&cattr_groups[i].attr_list);

        /* Add Counter device sysfs attributes */
        err = counter_sysfs_attr_add(counter, cattr_groups);
        if (err < 0)
                return err;

        /* Allocate attribute group pointers for association with device */
        dev->groups = devm_kcalloc(dev, num_groups + 1, sizeof(*dev->groups),
                                   GFP_KERNEL);
        if (!dev->groups)
                return -ENOMEM;

        /* Allocate space for attribute groups */
        groups = devm_kcalloc(dev, num_groups, sizeof(*groups), GFP_KERNEL);
        if (!groups)
                return -ENOMEM;

        /* Prepare each group of attributes for association */
        for (i = 0; i < num_groups; i++) {
                groups[i].name = cattr_groups[i].name;

                /* Allocate space for attribute pointers */
                groups[i].attrs = devm_kcalloc(dev,
                                               cattr_groups[i].num_attr + 1,
                                               sizeof(*groups[i].attrs),
                                               GFP_KERNEL);
                if (!groups[i].attrs)
                        return -ENOMEM;

                /* Add attribute pointers to attribute group */
                j = 0;
                list_for_each_entry(p, &cattr_groups[i].attr_list, l)
                        groups[i].attrs[j++] = &p->dev_attr.attr;

                /* Associate attribute group */
                dev->groups[i] = &groups[i];
        }

        return 0;
}