/* Line index within the aggregator device */ unsignedint idx;
/* Custom name for the virtual line */ constchar *name; /* GPIO chip label or line name */ constchar *key; /* Can be negative to indicate lookup by line name */ int offset;
/* Only aggregators created via legacy sysfs can be "activating". */ staticbool gpio_aggregator_is_activating(struct gpio_aggregator *aggr)
{
lockdep_assert_held(&aggr->lock);
list_for_each_entry_safe(line, tmp, &aggr->list_head, entry) {
configfs_unregister_group(&line->group); /* * Normally, we acquire aggr->lock within the configfs * callback. However, in the legacy sysfs interface case, * calling configfs_(un)register_group while holding * aggr->lock could cause a deadlock. Fortunately, this is * unnecessary because the new_device/delete_device path * and the module unload path are mutually exclusive, * thanks to an explicit try_module_get. That's why this * minimal scoped_guard suffices.
*/
scoped_guard(mutex, &aggr->lock)
gpio_aggregator_line_del(aggr, line);
kfree(line->key);
kfree(line->name);
kfree(line);
}
}
if (chip->can_sleep)
fsleep(delay_us); else
udelay(delay_us);
}
staticint gpio_fwd_set(struct gpio_chip *chip, unsignedint offset, int value)
{ struct gpiochip_fwd *fwd = gpiochip_get_data(chip); int ret;
if (chip->can_sleep)
ret = gpiod_set_value_cansleep(fwd->descs[offset], value); else
ret = gpiod_set_value(fwd->descs[offset], value); if (ret) return ret;
if (fwd->delay_timings)
gpio_fwd_delay(chip, offset, value);
/* * The GPIO delay provides a way to configure platform specific delays * for the GPIO ramp-up or ramp-down delays. This can serve the following * purposes: * - Open-drain output using an RC filter
*/ #define FWD_FEATURE_DELAY BIT(0)
/** * gpiochip_fwd_create() - Create a new GPIO forwarder * @dev: Parent device pointer * @ngpios: Number of GPIOs in the forwarder. * @descs: Array containing the GPIO descriptors to forward to. * This array must contain @ngpios entries, and must not be deallocated * before the forwarder has been destroyed again. * @features: Bitwise ORed features as defined with FWD_FEATURE_*. * * This function creates a new gpiochip, which forwards all GPIO operations to * the passed GPIO descriptors. * * Return: An opaque object pointer, or an ERR_PTR()-encoded negative error * code on failure.
*/ staticstruct gpiochip_fwd *gpiochip_fwd_create(struct device *dev, unsignedint ngpios, struct gpio_desc *descs[], unsignedlong features)
{ constchar *label = dev_name(dev); struct gpiochip_fwd *fwd; struct gpio_chip *chip; unsignedint i; int error;
/* * If any of the GPIO lines are sleeping, then the entire forwarder * will be sleeping. * If any of the chips support .set_config(), then the forwarder will * support setting configs.
*/ for (i = 0; i < ngpios; i++) { struct gpio_chip *parent = gpiod_to_chip(descs[i]);
dev_dbg(dev, "%u => gpio %d irq %d\n", i,
desc_to_gpio(descs[i]), gpiod_to_irq(descs[i]));
if (gpiod_cansleep(descs[i]))
chip->can_sleep = true; if (parent && parent->set_config)
chip->set_config = gpio_fwd_set_config;
}
/* The list is always sorted as new elements are inserted in order. */
list_for_each_entry(line, &aggr->list_head, entry)
line_names[n++] = line->name ?: "";
/* The list is always sorted as new elements are inserted in order. */
list_for_each_entry(line, &aggr->list_head, entry) { /* * - Either GPIO chip label or line name must be configured * (i.e. line->key must be non-NULL) * - Line directories must be named with sequential numeric * suffixes starting from 0. (i.e. ./line0, ./line1, ...)
*/ if (!line->key || line->idx != n) {
ret = -EINVAL; goto err_remove_swnode;
}
if (line->offset < 0)
ret = gpio_aggregator_add_gpio(aggr, line->key,
U16_MAX, &n); else
ret = gpio_aggregator_add_gpio(aggr, line->key,
line->offset, &n); if (ret) goto err_remove_swnode;
}
aggr->lookups->dev_id = kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, aggr->id); if (!aggr->lookups->dev_id) {
ret = -ENOMEM; goto err_remove_swnode;
}
gpiod_add_lookup_table(aggr->lookups);
ret = dev_sync_probe_register(&aggr->probe_data, &pdevinfo); if (ret) goto err_remove_lookup_table;
/* * The device only needs to depend on leaf lines. This is * sufficient to lock up all the configfs entries that the * instantiated, alive device depends on.
*/
list_for_each_entry(line, &aggr->list_head, entry) { if (lock)
configfs_depend_item_unlocked(
subsys, &line->group.cg_item); else
configfs_undepend_item_unlocked(
&line->group.cg_item);
}
}
ret = kstrtoint(page, 0, &offset); if (ret) return ret;
/* * When offset == -1, 'key' represents a line name to lookup. * When 0 <= offset < 65535, 'key' represents the label of the chip with * the 'offset' value representing the line within that chip. * * GPIOLIB uses the U16_MAX value to indicate lookup by line name so * the greatest offset we can accept is (U16_MAX - 1).
*/ if (offset > (U16_MAX - 1) || offset < -1) return -EINVAL;
guard(mutex)(&aggr->lock);
if (gpio_aggregator_is_activating(aggr) ||
gpio_aggregator_is_active(aggr)) return -EBUSY;
/* * At this point, aggr is neither active nor activating, * so calling gpio_aggregator_deactivate() is always unnecessary.
*/
gpio_aggregator_free(aggr);
}
ret = sscanf(name, "line%u%n", &idx, &nchar); if (ret != 1 || nchar != strlen(name)) return ERR_PTR(-EINVAL);
if (aggr->init_via_sysfs) /* * Aggregators created via legacy sysfs interface are exposed as * default groups, which means rmdir(2) is prohibited for them. * For simplicity, and to avoid confusion, we also prohibit * mkdir(2).
*/ return ERR_PTR(-EPERM);
guard(mutex)(&aggr->lock);
if (gpio_aggregator_is_active(aggr)) return ERR_PTR(-EBUSY);
list_for_each_entry(line, &aggr->list_head, entry) if (line->idx == idx) return ERR_PTR(-EINVAL);
line = gpio_aggregator_line_alloc(aggr, idx, NULL, -1); if (IS_ERR(line)) return ERR_CAST(line);
/* * "_sysfs" prefix is reserved for auto-generated config group * for devices create via legacy sysfs interface.
*/ if (strncmp(name, AGGREGATOR_LEGACY_PREFIX, sizeof(AGGREGATOR_LEGACY_PREFIX) - 1) == 0) return ERR_PTR(-EINVAL);
/* arg space is unneeded */
ret = gpio_aggregator_alloc(&aggr, 0); if (ret) return ERR_PTR(ret);
/* * Since the device created by sysfs might be toggled via configfs * 'live' attribute later, this initialization is needed.
*/
dev_sync_probe_init(&aggr->probe_data);
/* Expose to configfs */
res = configfs_register_group(&gpio_aggregator_subsys.su_group,
&aggr->group); if (res) goto free_dev_id;
res = gpio_aggregator_parse(aggr); if (res) goto unregister_group;
gpiod_add_lookup_table(aggr->lookups);
pdev = platform_device_register_data(NULL, DRV_NAME, aggr->id, &meta, sizeof(meta)); if (IS_ERR(pdev)) {
res = PTR_ERR(pdev); goto remove_table;
}
descs = devm_kmalloc_array(dev, n, sizeof(*descs), GFP_KERNEL); if (!descs) return -ENOMEM;
meta = dev_get_platdata(&pdev->dev); if (meta && meta->init_via_sysfs)
init_via_sysfs = true;
for (i = 0; i < n; i++) {
descs[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS); if (IS_ERR(descs[i])) { /* * Deferred probing is not suitable when the aggregator * is created via configfs. They should just retry later * whenever they like. For device creation via sysfs, * error is propagated without overriding for backward * compatibility. .prevent_deferred_probe is kept unset * for other cases.
*/ if (!init_via_sysfs && !dev_of_node(dev) &&
descs[i] == ERR_PTR(-EPROBE_DEFER)) {
pr_warn("Deferred probe canceled for creation via configfs.\n"); return -ENODEV;
} return PTR_ERR(descs[i]);
}
}
features = (uintptr_t)device_get_match_data(dev);
fwd = gpiochip_fwd_create(dev, n, descs, features); if (IS_ERR(fwd)) return PTR_ERR(fwd);
platform_set_drvdata(pdev, fwd); return 0;
}
staticconststruct of_device_id gpio_aggregator_dt_ids[] = {
{
.compatible = "gpio-delay",
.data = (void *)FWD_FEATURE_DELAY,
}, /* * Add GPIO-operated devices controlled from userspace below, * or use "driver_override" in sysfs.
*/
{}
};
MODULE_DEVICE_TABLE(of, gpio_aggregator_dt_ids);
staticint __exit gpio_aggregator_idr_remove(int id, void *p, void *data)
{ /* * There should be no aggregator created via configfs, as their * presence would prevent module unloading.
*/
gpio_aggregator_destroy(p); return 0;
}
staticvoid __exit gpio_aggregator_remove_all(void)
{ /* * Configfs callbacks acquire gpio_aggregator_lock when accessing * gpio_aggregator_idr, so to prevent lock inversion deadlock, we * cannot protect idr_for_each invocation here with * gpio_aggregator_lock, as gpio_aggregator_idr_remove() accesses * configfs groups. Fortunately, the new_device/delete_device path * and the module unload path are mutually exclusive, thanks to an * explicit try_module_get inside of those driver attr handlers. * Also, when we reach here, no configfs entries present or being * created. Therefore, no need to protect with gpio_aggregator_lock * below.
*/
idr_for_each(&gpio_aggregator_idr, gpio_aggregator_idr_remove, NULL);
idr_destroy(&gpio_aggregator_idr);
}
staticint __init gpio_aggregator_init(void)
{ int ret = 0;
config_group_init(&gpio_aggregator_subsys.su_group);
mutex_init(&gpio_aggregator_subsys.su_mutex);
ret = configfs_register_subsystem(&gpio_aggregator_subsys); if (ret) {
pr_err("Failed to register the '%s' configfs subsystem: %d\n",
gpio_aggregator_subsys.su_group.cg_item.ci_namebuf, ret);
mutex_destroy(&gpio_aggregator_subsys.su_mutex); return ret;
}
/* * CAVEAT: This must occur after configfs registration. Otherwise, * a race condition could arise: driver attribute groups might be * exposed and accessed by users before configfs registration * completes. new_device_store() does not expect a partially * initialized configfs state.
*/
ret = platform_driver_register(&gpio_aggregator_driver); if (ret) {
pr_err("Failed to register the platform driver: %d\n", ret);
mutex_destroy(&gpio_aggregator_subsys.su_mutex);
configfs_unregister_subsystem(&gpio_aggregator_subsys);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.