// SPDX-License-Identifier: GPL-2.0 /* * Generic Counter interface * Copyright (C) 2018 William Breathitt Gray */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char *const counter_count_direction_str[2] = { [COUNTER_COUNT_DIRECTION_FORWARD] = "forward", [COUNTER_COUNT_DIRECTION_BACKWARD] = "backward" }; EXPORT_SYMBOL_GPL(counter_count_direction_str); const char *const counter_count_mode_str[4] = { [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" }; EXPORT_SYMBOL_GPL(counter_count_mode_str); ssize_t counter_signal_enum_read(struct counter_device *counter, struct counter_signal *signal, void *priv, char *buf) { const struct counter_signal_enum_ext *const e = priv; int err; size_t index; if (!e->get) return -EINVAL; err = e->get(counter, signal, &index); if (err) return err; if (index >= e->num_items) return -EINVAL; return sprintf(buf, "%s\n", e->items[index]); } EXPORT_SYMBOL_GPL(counter_signal_enum_read); ssize_t counter_signal_enum_write(struct counter_device *counter, struct counter_signal *signal, void *priv, const char *buf, size_t len) { const struct counter_signal_enum_ext *const e = priv; ssize_t index; int err; if (!e->set) return -EINVAL; index = __sysfs_match_string(e->items, e->num_items, buf); if (index < 0) return index; err = e->set(counter, signal, index); if (err) return err; return len; } EXPORT_SYMBOL_GPL(counter_signal_enum_write); ssize_t counter_signal_enum_available_read(struct counter_device *counter, struct counter_signal *signal, void *priv, char *buf) { const struct counter_signal_enum_ext *const e = priv; size_t i; size_t len = 0; if (!e->num_items) return 0; for (i = 0; i < e->num_items; i++) len += sprintf(buf + len, "%s\n", e->items[i]); return len; } EXPORT_SYMBOL_GPL(counter_signal_enum_available_read); ssize_t counter_count_enum_read(struct counter_device *counter, struct counter_count *count, void *priv, char *buf) { const struct counter_count_enum_ext *const e = priv; int err; size_t index; if (!e->get) return -EINVAL; err = e->get(counter, count, &index); if (err) return err; if (index >= e->num_items) return -EINVAL; return sprintf(buf, "%s\n", e->items[index]); } EXPORT_SYMBOL_GPL(counter_count_enum_read); ssize_t counter_count_enum_write(struct counter_device *counter, struct counter_count *count, void *priv, const char *buf, size_t len) { const struct counter_count_enum_ext *const e = priv; ssize_t index; int err; if (!e->set) return -EINVAL; index = __sysfs_match_string(e->items, e->num_items, buf); if (index < 0) return index; err = e->set(counter, count, index); if (err) return err; return len; } EXPORT_SYMBOL_GPL(counter_count_enum_write); ssize_t counter_count_enum_available_read(struct counter_device *counter, struct counter_count *count, void *priv, char *buf) { const struct counter_count_enum_ext *const e = priv; size_t i; size_t len = 0; if (!e->num_items) return 0; for (i = 0; i < e->num_items; i++) len += sprintf(buf + len, "%s\n", e->items[i]); return len; } EXPORT_SYMBOL_GPL(counter_count_enum_available_read); ssize_t counter_device_enum_read(struct counter_device *counter, void *priv, char *buf) { const struct counter_device_enum_ext *const e = priv; int err; size_t index; if (!e->get) return -EINVAL; err = e->get(counter, &index); if (err) return err; if (index >= e->num_items) return -EINVAL; return sprintf(buf, "%s\n", e->items[index]); } EXPORT_SYMBOL_GPL(counter_device_enum_read); ssize_t counter_device_enum_write(struct counter_device *counter, void *priv, const char *buf, size_t len) { const struct counter_device_enum_ext *const e = priv; ssize_t index; int err; if (!e->set) return -EINVAL; index = __sysfs_match_string(e->items, e->num_items, buf); if (index < 0) return index; err = e->set(counter, index); if (err) return err; return len; } EXPORT_SYMBOL_GPL(counter_device_enum_write); ssize_t counter_device_enum_available_read(struct counter_device *counter, void *priv, char *buf) { const struct counter_device_enum_ext *const e = priv; size_t i; size_t len = 0; if (!e->num_items) return 0; for (i = 0; i < e->num_items; i++) len += sprintf(buf + len, "%s\n", e->items[i]); return len; } EXPORT_SYMBOL_GPL(counter_device_enum_available_read); struct counter_attr_parm { struct counter_device_attr_group *group; const char *prefix; const char *name; ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t len); void *component; }; struct counter_device_attr { struct device_attribute dev_attr; struct list_head l; void *component; }; static int counter_attribute_create(const struct counter_attr_parm *const parm) { struct counter_device_attr *counter_attr; struct device_attribute *dev_attr; int err; struct list_head *const attr_list = &parm->group->attr_list; /* Allocate a Counter device attribute */ counter_attr = kzalloc(sizeof(*counter_attr), GFP_KERNEL); if (!counter_attr) return -ENOMEM; dev_attr = &counter_attr->dev_attr; sysfs_attr_init(&dev_attr->attr); /* Configure device attribute */ dev_attr->attr.name = kasprintf(GFP_KERNEL, "%s%s", parm->prefix, parm->name); if (!dev_attr->attr.name) { err = -ENOMEM; goto err_free_counter_attr; } if (parm->show) { dev_attr->attr.mode |= 0444; dev_attr->show = parm->show; } if (parm->store) { dev_attr->attr.mode |= 0200; dev_attr->store = parm->store; } /* Store associated Counter component with attribute */ counter_attr->component = parm->component; /* Keep track of the attribute for later cleanup */ list_add(&counter_attr->l, attr_list); parm->group->num_attr++; return 0; err_free_counter_attr: kfree(counter_attr); return err; } #define to_counter_attr(_dev_attr) \ container_of(_dev_attr, struct counter_device_attr, dev_attr) struct counter_signal_unit { struct counter_signal *signal; }; static const char *const counter_signal_value_str[] = { [COUNTER_SIGNAL_LOW] = "low", [COUNTER_SIGNAL_HIGH] = "high" }; static ssize_t counter_signal_show(struct device *dev, struct device_attribute *attr, char *buf) { struct counter_device *const counter = dev_get_drvdata(dev); const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_signal_unit *const component = devattr->component; struct counter_signal *const signal = component->signal; int err; enum counter_signal_value val; err = counter->ops->signal_read(counter, signal, &val); if (err) return err; return sprintf(buf, "%s\n", counter_signal_value_str[val]); } struct counter_name_unit { const char *name; }; static ssize_t counter_device_attr_name_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_name_unit *const comp = to_counter_attr(attr)->component; return sprintf(buf, "%s\n", comp->name); } static int counter_name_attribute_create( struct counter_device_attr_group *const group, const char *const name) { struct counter_name_unit *name_comp; struct counter_attr_parm parm; int err; /* Skip if no name */ if (!name) return 0; /* Allocate name attribute component */ name_comp = kmalloc(sizeof(*name_comp), GFP_KERNEL); if (!name_comp) return -ENOMEM; name_comp->name = name; /* Allocate Signal name attribute */ parm.group = group; parm.prefix = ""; parm.name = "name"; parm.show = counter_device_attr_name_show; parm.store = NULL; parm.component = name_comp; err = counter_attribute_create(&parm); if (err) goto err_free_name_comp; return 0; err_free_name_comp: kfree(name_comp); return err; } struct counter_signal_ext_unit { struct counter_signal *signal; const struct counter_signal_ext *ext; }; static ssize_t counter_signal_ext_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_signal_ext_unit *const comp = devattr->component; const struct counter_signal_ext *const ext = comp->ext; return ext->read(dev_get_drvdata(dev), comp->signal, ext->priv, buf); } static ssize_t counter_signal_ext_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_signal_ext_unit *const comp = devattr->component; const struct counter_signal_ext *const ext = comp->ext; return ext->write(dev_get_drvdata(dev), comp->signal, ext->priv, buf, len); } static void counter_device_attr_list_free(struct list_head *attr_list) { struct counter_device_attr *p, *n; list_for_each_entry_safe(p, n, attr_list, l) { /* free attribute name and associated component memory */ kfree(p->dev_attr.attr.name); kfree(p->component); list_del(&p->l); kfree(p); } } static int counter_signal_ext_register( struct counter_device_attr_group *const group, struct counter_signal *const signal) { const size_t num_ext = signal->num_ext; size_t i; const struct counter_signal_ext *ext; struct counter_signal_ext_unit *signal_ext_comp; struct counter_attr_parm parm; int err; /* Create an attribute for each extension */ for (i = 0 ; i < num_ext; i++) { ext = signal->ext + i; /* Allocate signal_ext attribute component */ signal_ext_comp = kmalloc(sizeof(*signal_ext_comp), GFP_KERNEL); if (!signal_ext_comp) { err = -ENOMEM; goto err_free_attr_list; } signal_ext_comp->signal = signal; signal_ext_comp->ext = ext; /* Allocate a Counter device attribute */ parm.group = group; parm.prefix = ""; parm.name = ext->name; parm.show = (ext->read) ? counter_signal_ext_show : NULL; parm.store = (ext->write) ? counter_signal_ext_store : NULL; parm.component = signal_ext_comp; err = counter_attribute_create(&parm); if (err) { kfree(signal_ext_comp); goto err_free_attr_list; } } return 0; err_free_attr_list: counter_device_attr_list_free(&group->attr_list); return err; } static int counter_signal_attributes_create( struct counter_device_attr_group *const group, const struct counter_device *const counter, struct counter_signal *const signal) { struct counter_signal_unit *signal_comp; struct counter_attr_parm parm; int err; /* Allocate Signal attribute component */ signal_comp = kmalloc(sizeof(*signal_comp), GFP_KERNEL); if (!signal_comp) return -ENOMEM; signal_comp->signal = signal; /* Create main Signal attribute */ parm.group = group; parm.prefix = ""; parm.name = "signal"; parm.show = (counter->ops->signal_read) ? counter_signal_show : NULL; parm.store = NULL; parm.component = signal_comp; err = counter_attribute_create(&parm); if (err) { kfree(signal_comp); return err; } /* Create Signal name attribute */ err = counter_name_attribute_create(group, signal->name); if (err) goto err_free_attr_list; /* Register Signal extension attributes */ err = counter_signal_ext_register(group, signal); if (err) goto err_free_attr_list; return 0; err_free_attr_list: counter_device_attr_list_free(&group->attr_list); return err; } static int counter_signals_register( struct counter_device_attr_group *const groups_list, const struct counter_device *const counter) { const size_t num_signals = counter->num_signals; size_t i; struct counter_signal *signal; const char *name; int err; /* Register each Signal */ for (i = 0; i < num_signals; i++) { signal = counter->signals + i; /* Generate Signal attribute directory name */ name = kasprintf(GFP_KERNEL, "signal%d", signal->id); if (!name) { err = -ENOMEM; goto err_free_attr_groups; } groups_list[i].attr_group.name = name; /* Create all attributes associated with Signal */ err = counter_signal_attributes_create(groups_list + i, counter, signal); if (err) goto err_free_attr_groups; } return 0; err_free_attr_groups: do { kfree(groups_list[i].attr_group.name); counter_device_attr_list_free(&groups_list[i].attr_list); } while (i--); return err; } 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" }; struct counter_action_unit { struct counter_synapse *synapse; struct counter_count *count; }; static ssize_t counter_action_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_device_attr *const devattr = to_counter_attr(attr); int err; struct counter_device *const counter = dev_get_drvdata(dev); const struct counter_action_unit *const component = devattr->component; struct counter_count *const count = component->count; struct counter_synapse *const synapse = component->synapse; size_t action_index; enum counter_synapse_action action; err = counter->ops->action_get(counter, count, synapse, &action_index); if (err) return err; synapse->action = action_index; action = synapse->actions_list[action_index]; return sprintf(buf, "%s\n", counter_synapse_action_str[action]); } static ssize_t counter_action_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_action_unit *const component = devattr->component; struct counter_synapse *const synapse = component->synapse; size_t action_index; const size_t num_actions = synapse->num_actions; enum counter_synapse_action action; int err; struct counter_device *const counter = dev_get_drvdata(dev); struct counter_count *const count = component->count; /* Find requested action mode */ for (action_index = 0; action_index < num_actions; action_index++) { action = synapse->actions_list[action_index]; if (sysfs_streq(buf, counter_synapse_action_str[action])) break; } /* If requested action mode not found */ if (action_index >= num_actions) return -EINVAL; err = counter->ops->action_set(counter, count, synapse, action_index); if (err) return err; synapse->action = action_index; return len; } struct counter_action_avail_unit { const enum counter_synapse_action *actions_list; size_t num_actions; }; static ssize_t counter_synapse_action_available_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_action_avail_unit *const component = devattr->component; size_t i; enum counter_synapse_action action; ssize_t len = 0; for (i = 0; i < component->num_actions; i++) { action = component->actions_list[i]; len += sprintf(buf + len, "%s\n", counter_synapse_action_str[action]); } return len; } static int counter_synapses_register( struct counter_device_attr_group *const group, const struct counter_device *const counter, struct counter_count *const count, const char *const count_attr_name) { size_t i; struct counter_synapse *synapse; const char *prefix; struct counter_action_unit *action_comp; struct counter_attr_parm parm; int err; struct counter_action_avail_unit *avail_comp; /* Register each Synapse */ for (i = 0; i < count->num_synapses; i++) { synapse = count->synapses + i; /* Generate attribute prefix */ prefix = kasprintf(GFP_KERNEL, "signal%d_", synapse->signal->id); if (!prefix) { err = -ENOMEM; goto err_free_attr_list; } /* Allocate action attribute component */ action_comp = kmalloc(sizeof(*action_comp), GFP_KERNEL); if (!action_comp) { err = -ENOMEM; goto err_free_prefix; } action_comp->synapse = synapse; action_comp->count = count; /* Create action attribute */ parm.group = group; parm.prefix = prefix; parm.name = "action"; parm.show = (counter->ops->action_get) ? counter_action_show : NULL; parm.store = (counter->ops->action_set) ? counter_action_store : NULL; parm.component = action_comp; err = counter_attribute_create(&parm); if (err) { kfree(action_comp); goto err_free_prefix; } /* Allocate action available attribute component */ avail_comp = kmalloc(sizeof(*avail_comp), GFP_KERNEL); if (!avail_comp) { err = -ENOMEM; goto err_free_prefix; } avail_comp->actions_list = synapse->actions_list; avail_comp->num_actions = synapse->num_actions; /* Create action_available attribute */ parm.group = group; parm.prefix = prefix; parm.name = "action_available"; parm.show = counter_synapse_action_available_show; parm.store = NULL; parm.component = avail_comp; err = counter_attribute_create(&parm); if (err) { kfree(avail_comp); goto err_free_prefix; } kfree(prefix); } return 0; err_free_prefix: kfree(prefix); err_free_attr_list: counter_device_attr_list_free(&group->attr_list); return err; } struct counter_count_unit { struct counter_count *count; }; static ssize_t counter_count_show(struct device *dev, struct device_attribute *attr, char *buf) { struct counter_device *const counter = dev_get_drvdata(dev); const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_count_unit *const component = devattr->component; struct counter_count *const count = component->count; int err; unsigned long val; err = counter->ops->count_read(counter, count, &val); if (err) return err; return sprintf(buf, "%lu\n", val); } static ssize_t counter_count_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct counter_device *const counter = dev_get_drvdata(dev); const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_count_unit *const component = devattr->component; struct counter_count *const count = component->count; int err; unsigned long val; err = kstrtoul(buf, 0, &val); if (err) return err; err = counter->ops->count_write(counter, count, val); if (err) return err; return len; } static const char *const counter_count_function_str[] = { [COUNTER_COUNT_FUNCTION_INCREASE] = "increase", [COUNTER_COUNT_FUNCTION_DECREASE] = "decrease", [COUNTER_COUNT_FUNCTION_PULSE_DIRECTION] = "pulse-direction", [COUNTER_COUNT_FUNCTION_QUADRATURE_X1_A] = "quadrature x1 a", [COUNTER_COUNT_FUNCTION_QUADRATURE_X1_B] = "quadrature x1 b", [COUNTER_COUNT_FUNCTION_QUADRATURE_X2_A] = "quadrature x2 a", [COUNTER_COUNT_FUNCTION_QUADRATURE_X2_B] = "quadrature x2 b", [COUNTER_COUNT_FUNCTION_QUADRATURE_X4] = "quadrature x4" }; static ssize_t counter_function_show(struct device *dev, struct device_attribute *attr, char *buf) { int err; struct counter_device *const counter = dev_get_drvdata(dev); const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_count_unit *const component = devattr->component; struct counter_count *const count = component->count; size_t func_index; enum counter_count_function function; err = counter->ops->function_get(counter, count, &func_index); if (err) return err; count->function = func_index; function = count->functions_list[func_index]; return sprintf(buf, "%s\n", counter_count_function_str[function]); } static ssize_t counter_function_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_count_unit *const component = devattr->component; struct counter_count *const count = component->count; const size_t num_functions = count->num_functions; size_t func_index; enum counter_count_function function; int err; struct counter_device *const counter = dev_get_drvdata(dev); /* Find requested Count function mode */ for (func_index = 0; func_index < num_functions; func_index++) { function = count->functions_list[func_index]; if (sysfs_streq(buf, counter_count_function_str[function])) break; } /* Return error if requested Count function mode not found */ if (func_index >= num_functions) return -EINVAL; err = counter->ops->function_set(counter, count, func_index); if (err) return err; count->function = func_index; return len; } struct counter_count_ext_unit { struct counter_count *count; const struct counter_count_ext *ext; }; static ssize_t counter_count_ext_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_count_ext_unit *const comp = devattr->component; const struct counter_count_ext *const ext = comp->ext; return ext->read(dev_get_drvdata(dev), comp->count, ext->priv, buf); } static ssize_t counter_count_ext_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_count_ext_unit *const comp = devattr->component; const struct counter_count_ext *const ext = comp->ext; return ext->write(dev_get_drvdata(dev), comp->count, ext->priv, buf, len); } static int counter_count_ext_register( struct counter_device_attr_group *const group, struct counter_count *const count) { size_t i; const struct counter_count_ext *ext; struct counter_count_ext_unit *count_ext_comp; struct counter_attr_parm parm; int err; /* Create an attribute for each extension */ for (i = 0 ; i < count->num_ext; i++) { ext = count->ext + i; /* Allocate count_ext attribute component */ count_ext_comp = kmalloc(sizeof(*count_ext_comp), GFP_KERNEL); if (!count_ext_comp) { err = -ENOMEM; goto err_free_attr_list; } count_ext_comp->count = count; count_ext_comp->ext = ext; /* Allocate count_ext attribute */ parm.group = group; parm.prefix = ""; parm.name = ext->name; parm.show = (ext->read) ? counter_count_ext_show : NULL; parm.store = (ext->write) ? counter_count_ext_store : NULL; parm.component = count_ext_comp; err = counter_attribute_create(&parm); if (err) { kfree(count_ext_comp); goto err_free_attr_list; } } return 0; err_free_attr_list: counter_device_attr_list_free(&group->attr_list); return err; } struct counter_func_avail_unit { const enum counter_count_function *functions_list; size_t num_functions; }; static ssize_t counter_count_function_available_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_func_avail_unit *const component = devattr->component; const enum counter_count_function *const func_list = component->functions_list; const size_t num_functions = component->num_functions; size_t i; enum counter_count_function function; ssize_t len = 0; for (i = 0; i < num_functions; i++) { function = func_list[i]; len += sprintf(buf + len, "%s\n", counter_count_function_str[function]); } return len; } static int counter_count_attributes_create( struct counter_device_attr_group *const group, const struct counter_device *const counter, struct counter_count *const count) { struct counter_count_unit *count_comp; struct counter_attr_parm parm; int err; struct counter_count_unit *func_comp; struct counter_func_avail_unit *avail_comp; /* Allocate count attribute component */ count_comp = kmalloc(sizeof(*count_comp), GFP_KERNEL); if (!count_comp) return -ENOMEM; count_comp->count = count; /* Create main Count attribute */ parm.group = group; parm.prefix = ""; parm.name = "count"; parm.show = (counter->ops->count_read) ? counter_count_show : NULL; parm.store = (counter->ops->count_write) ? counter_count_store : NULL; parm.component = count_comp; err = counter_attribute_create(&parm); if (err) { kfree(count_comp); return err; } /* Allocate function attribute component */ func_comp = kmalloc(sizeof(*func_comp), GFP_KERNEL); if (!func_comp) { err = -ENOMEM; goto err_free_attr_list; } func_comp->count = count; /* Create Count function attribute */ parm.group = group; parm.prefix = ""; parm.name = "function"; parm.show = (counter->ops->function_get) ? counter_function_show : NULL; parm.store = (counter->ops->function_set) ? counter_function_store : NULL; parm.component = func_comp; err = counter_attribute_create(&parm); if (err) { kfree(func_comp); goto err_free_attr_list; } /* Allocate function available attribute component */ avail_comp = kmalloc(sizeof(*avail_comp), GFP_KERNEL); if (!avail_comp) { err = -ENOMEM; goto err_free_attr_list; } avail_comp->functions_list = count->functions_list; avail_comp->num_functions = count->num_functions; /* Create Count function_available attribute */ parm.group = group; parm.prefix = ""; parm.name = "function_available"; parm.show = counter_count_function_available_show; parm.store = NULL; parm.component = avail_comp; err = counter_attribute_create(&parm); if (err) { kfree(avail_comp); goto err_free_attr_list; } /* Create Count name attribute */ err = counter_name_attribute_create(group, count->name); if (err) goto err_free_attr_list; /* Register Count extension attributes */ err = counter_count_ext_register(group, count); if (err) goto err_free_attr_list; return 0; err_free_attr_list: counter_device_attr_list_free(&group->attr_list); return err; } static int counter_counts_register( struct counter_device_attr_group *const groups_list, const struct counter_device *const counter) { size_t i; struct counter_count *count; const char *name; int err; /* Register each Count */ for (i = 0; i < counter->num_counts; i++) { count = counter->counts + i; /* Generate Count attribute directory name */ name = kasprintf(GFP_KERNEL, "count%d", count->id); if (!name) { err = -ENOMEM; goto err_free_attr_groups; } groups_list[i].attr_group.name = name; /* Register the Synapses associated with each Count */ err = counter_synapses_register(groups_list + i, counter, count, name); if (err) goto err_free_attr_groups; /* Create all attributes associated with Count */ err = counter_count_attributes_create(groups_list + i, counter, count); if (err) goto err_free_attr_groups; } return 0; err_free_attr_groups: do { kfree(groups_list[i].attr_group.name); counter_device_attr_list_free(&groups_list[i].attr_list); } while (i--); return err; } struct counter_size_unit { size_t size; }; static ssize_t counter_device_attr_size_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_size_unit *const comp = to_counter_attr(attr)->component; return sprintf(buf, "%zu\n", comp->size); } static int counter_size_attribute_create( struct counter_device_attr_group *const group, const size_t size, const char *const name) { struct counter_size_unit *size_comp; struct counter_attr_parm parm; int err; /* Allocate size attribute component */ size_comp = kmalloc(sizeof(*size_comp), GFP_KERNEL); if (!size_comp) return -ENOMEM; size_comp->size = size; parm.group = group; parm.prefix = ""; parm.name = name; parm.show = counter_device_attr_size_show; parm.store = NULL; parm.component = size_comp; err = counter_attribute_create(&parm); if (err) goto err_free_size_comp; return 0; err_free_size_comp: kfree(size_comp); return err; } struct counter_ext_unit { const struct counter_device_ext *ext; }; static ssize_t counter_device_ext_show(struct device *dev, struct device_attribute *attr, char *buf) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_ext_unit *const component = devattr->component; const struct counter_device_ext *const ext = component->ext; return ext->read(dev_get_drvdata(dev), ext->priv, buf); } static ssize_t counter_device_ext_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { const struct counter_device_attr *const devattr = to_counter_attr(attr); const struct counter_ext_unit *const component = devattr->component; const struct counter_device_ext *const ext = component->ext; return ext->write(dev_get_drvdata(dev), ext->priv, buf, len); } static int counter_device_ext_register( struct counter_device_attr_group *const group, struct counter_device *const counter) { size_t i; struct counter_ext_unit *ext_comp; struct counter_attr_parm parm; int err; /* Create an attribute for each extension */ for (i = 0 ; i < counter->num_ext; i++) { /* Allocate extension attribute component */ ext_comp = kmalloc(sizeof(*ext_comp), GFP_KERNEL); if (!ext_comp) { err = -ENOMEM; goto err_free_attr_list; } ext_comp->ext = counter->ext + i; /* Allocate extension attribute */ parm.group = group; parm.prefix = ""; parm.name = counter->ext[i].name; parm.show = (counter->ext[i].read) ? counter_device_ext_show : NULL; parm.store = (counter->ext[i].write) ? counter_device_ext_store : NULL; parm.component = ext_comp; err = counter_attribute_create(&parm); if (err) { kfree(ext_comp); goto err_free_attr_list; } } return 0; err_free_attr_list: counter_device_attr_list_free(&group->attr_list); return err; } static int counter_global_attr_register( struct counter_device_attr_group *const group, struct counter_device *const counter) { int err; /* Create name attribute */ err = counter_name_attribute_create(group, counter->name); if (err) return err; /* Create num_counts attribute */ err = counter_size_attribute_create(group, counter->num_counts, "num_counts"); if (err) goto err_free_attr_list; /* Create num_signals attribute */ err = counter_size_attribute_create(group, counter->num_signals, "num_signals"); if (err) goto err_free_attr_list; /* Register Counter device extension attributes */ err = counter_device_ext_register(group, counter); if (err) goto err_free_attr_list; return 0; err_free_attr_list: counter_device_attr_list_free(&group->attr_list); return err; } static void counter_device_groups_list_free( struct counter_device_attr_group *const groups_list, const size_t num_groups) { struct counter_device_attr_group *group; size_t i; /* loop through all attribute groups (signals, counts, global, etc.) */ for (i = 0; i < num_groups; i++) { group = groups_list + i; /* free all attribute group and associated attributes memory */ kfree(group->attr_group.name); kfree(group->attr_group.attrs); counter_device_attr_list_free(&group->attr_list); } kfree(groups_list); } static int counter_device_groups_list_prepare( struct counter_device *const counter) { const size_t total_num_groups = counter->num_signals + counter->num_counts + 1; struct counter_device_attr_group *groups_list; size_t i; int err; size_t num_groups = 0; /* Allocate space for attribute groups (signals, counts, and ext) */ groups_list = kcalloc(total_num_groups, sizeof(*groups_list), GFP_KERNEL); if (!groups_list) return -ENOMEM; /* Initialize attribute lists */ for (i = 0; i < total_num_groups; i++) INIT_LIST_HEAD(&groups_list[i].attr_list); /* Register Signals */ err = counter_signals_register(groups_list, counter); if (err) goto err_free_groups_list; num_groups += counter->num_signals; /* Register Counts and respective Synapses */ err = counter_counts_register(groups_list + num_groups, counter); if (err) goto err_free_groups_list; num_groups += counter->num_counts; /* Register Counter global attributes */ err = counter_global_attr_register(groups_list + num_groups, counter); if (err) goto err_free_groups_list; num_groups++; /* Store groups_list in device_state */ counter->device_state->groups_list = groups_list; counter->device_state->num_groups = num_groups; return 0; err_free_groups_list: counter_device_groups_list_free(groups_list, num_groups); return err; } static int counter_device_groups_prepare( struct counter_device_state *const device_state) { size_t i, j; struct counter_device_attr_group *group; int err; struct counter_device_attr *p; /* Allocate attribute groups for association with device */ device_state->groups = kcalloc(device_state->num_groups + 1, sizeof(*device_state->groups), GFP_KERNEL); if (!device_state->groups) return -ENOMEM; /* Prepare each group of attributes for association */ for (i = 0; i < device_state->num_groups; i++) { group = device_state->groups_list + i; /* Allocate space for attribute pointers in attribute group */ group->attr_group.attrs = kcalloc(group->num_attr + 1, sizeof(*group->attr_group.attrs), GFP_KERNEL); if (!group->attr_group.attrs) { err = -ENOMEM; goto err_free_groups; } /* Add attribute pointers to attribute group */ j = 0; list_for_each_entry(p, &group->attr_list, l) group->attr_group.attrs[j++] = &p->dev_attr.attr; /* Group attributes in attribute group */ device_state->groups[i] = &group->attr_group; } /* Associate attributes with device */ device_state->dev.groups = device_state->groups; return 0; err_free_groups: do { group = device_state->groups_list + i; kfree(group->attr_group.attrs); group->attr_group.attrs = NULL; } while (i--); kfree(device_state->groups); return err; } /* Provides a unique ID for each counter device */ static DEFINE_IDA(counter_ida); static void counter_device_release(struct device *dev) { struct counter_device *const counter = dev_get_drvdata(dev); struct counter_device_state *const device_state = counter->device_state; kfree(device_state->groups); counter_device_groups_list_free(device_state->groups_list, device_state->num_groups); ida_simple_remove(&counter_ida, device_state->id); kfree(device_state); } static struct device_type counter_device_type = { .name = "counter_device", .release = counter_device_release }; static struct bus_type counter_bus_type = { .name = "counter" }; /** * counter_register - register Counter to the system * @counter: pointer to Counter to register * * This function registers a Counter to the system. A sysfs "counter" directory * will be created and populated with sysfs attributes correlating with the * Counter Signals, Synapses, and Counts respectively. */ int counter_register(struct counter_device *const counter) { struct counter_device_state *device_state; int err; /* Allocate internal state container for Counter device */ device_state = kzalloc(sizeof(*device_state), GFP_KERNEL); if (!device_state) return -ENOMEM; counter->device_state = device_state; /* Acquire unique ID */ device_state->id = ida_simple_get(&counter_ida, 0, 0, GFP_KERNEL); if (device_state->id < 0) { err = device_state->id; goto err_free_device_state; } /* Configure device structure for Counter */ device_state->dev.type = &counter_device_type; device_state->dev.bus = &counter_bus_type; if (counter->parent) { device_state->dev.parent = counter->parent; device_state->dev.of_node = counter->parent->of_node; } dev_set_name(&device_state->dev, "counter%d", device_state->id); device_initialize(&device_state->dev); dev_set_drvdata(&device_state->dev, counter); /* Prepare device attributes */ err = counter_device_groups_list_prepare(counter); if (err) goto err_free_id; /* Organize device attributes to groups and match to device */ err = counter_device_groups_prepare(device_state); if (err) goto err_free_groups_list; /* Add device to system */ err = device_add(&device_state->dev); if (err) goto err_free_groups; return 0; err_free_groups: kfree(device_state->groups); err_free_groups_list: counter_device_groups_list_free(device_state->groups_list, device_state->num_groups); err_free_id: ida_simple_remove(&counter_ida, device_state->id); err_free_device_state: kfree(device_state); return err; } EXPORT_SYMBOL_GPL(counter_register); /** * counter_unregister - unregister Counter from the system * @counter: pointer to Counter to unregister * * The Counter is unregistered from the system; all allocated memory is freed. */ void counter_unregister(struct counter_device *const counter) { if (counter) device_del(&counter->device_state->dev); } EXPORT_SYMBOL_GPL(counter_unregister); static void devm_counter_unreg(struct device *dev, void *res) { counter_unregister(*(struct counter_device **)res); } /** * devm_counter_register - Resource-managed counter_register * @dev: device to allocate counter_device for * @counter: pointer to Counter to register * * Managed counter_register. The Counter registered with this function is * automatically unregistered on driver detach. This function calls * counter_register internally. Refer to that function for more information. * * If an Counter registered with this function needs to be unregistered * separately, devm_counter_unregister must be used. * * RETURNS: * 0 on success, negative error number on failure. */ int devm_counter_register(struct device *dev, struct counter_device *const counter) { struct counter_device **ptr; int ret; ptr = devres_alloc(devm_counter_unreg, sizeof(*ptr), GFP_KERNEL); if (!ptr) return -ENOMEM; ret = counter_register(counter); if (!ret) { *ptr = counter; devres_add(dev, ptr); } else { devres_free(ptr); } return ret; } EXPORT_SYMBOL_GPL(devm_counter_register); static int devm_counter_match(struct device *dev, void *res, void *data) { struct counter_device **r = res; if (!r || !*r) { WARN_ON(!r || !*r); return 0; } return *r == data; } /** * devm_counter_unregister - Resource-managed counter_unregister * @dev: device this counter_device belongs to * @counter: pointer to Counter associated with the device * * Unregister Counter registered with devm_counter_register. */ void devm_counter_unregister(struct device *dev, struct counter_device *const counter) { int rc; rc = devres_release(dev, devm_counter_unreg, devm_counter_match, counter); WARN_ON(rc); } EXPORT_SYMBOL_GPL(devm_counter_unregister); static int __init counter_init(void) { return bus_register(&counter_bus_type); } static void __exit counter_exit(void) { bus_unregister(&counter_bus_type); } subsys_initcall(counter_init); module_exit(counter_exit); MODULE_AUTHOR("William Breathitt Gray "); MODULE_DESCRIPTION("Generic Counter interface"); MODULE_LICENSE("GPL v2");