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