linux/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);

	return 0;
}

/**
 * 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;
}