diff options
Diffstat (limited to 'drivers/gpio/gpio-sim.c')
| -rw-r--r-- | drivers/gpio/gpio-sim.c | 238 |
1 files changed, 146 insertions, 92 deletions
diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index f387dad81f29..a83f5238427c 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -10,7 +10,6 @@ #include <linux/array_size.h> #include <linux/bitmap.h> #include <linux/cleanup.h> -#include <linux/completion.h> #include <linux/configfs.h> #include <linux/device.h> #include <linux/err.h> @@ -37,8 +36,10 @@ #include <linux/sysfs.h> #include <linux/types.h> +#include "dev-sync-probe.h" + #define GPIO_SIM_NGPIO_MAX 1024 -#define GPIO_SIM_PROP_MAX 4 /* Max 3 properties + sentinel. */ +#define GPIO_SIM_PROP_MAX 5 /* Max 4 properties + sentinel. */ #define GPIO_SIM_NUM_ATTRS 3 /* value, pull and sentinel */ static DEFINE_IDA(gpio_sim_ida); @@ -119,12 +120,14 @@ static int gpio_sim_get(struct gpio_chip *gc, unsigned int offset) return !!test_bit(offset, chip->value_map); } -static void gpio_sim_set(struct gpio_chip *gc, unsigned int offset, int value) +static int gpio_sim_set(struct gpio_chip *gc, unsigned int offset, int value) { struct gpio_sim_chip *chip = gpiochip_get_data(gc); scoped_guard(mutex, &chip->lock) __assign_bit(offset, chip->value_map, value); + + return 0; } static int gpio_sim_get_multiple(struct gpio_chip *gc, @@ -138,14 +141,16 @@ static int gpio_sim_get_multiple(struct gpio_chip *gc, return 0; } -static void gpio_sim_set_multiple(struct gpio_chip *gc, - unsigned long *mask, unsigned long *bits) +static int gpio_sim_set_multiple(struct gpio_chip *gc, + unsigned long *mask, unsigned long *bits) { struct gpio_sim_chip *chip = gpiochip_get_data(gc); scoped_guard(mutex, &chip->lock) bitmap_replace(chip->value_map, chip->value_map, bits, mask, gc->ngpio); + + return 0; } static int gpio_sim_direction_output(struct gpio_chip *gc, @@ -257,8 +262,7 @@ static void gpio_sim_dbg_show(struct seq_file *seq, struct gpio_chip *gc) guard(mutex)(&chip->lock); for_each_hwgpio(gc, i, label) - seq_printf(seq, " gpio-%-3d (%s) %s,%s\n", - gc->base + i, + seq_printf(seq, " gpio-%-3d (%s) %s,%s\n", i, label ?: "<unused>", test_bit(i, chip->direction_map) ? "input" : test_bit(i, chip->value_map) ? "output-high" : @@ -413,11 +417,6 @@ static int gpio_sim_setup_sysfs(struct gpio_sim_chip *chip) return devm_add_action_or_reset(dev, gpio_sim_sysfs_remove, chip); } -static int gpio_sim_dev_match_fwnode(struct device *dev, void *data) -{ - return device_match_fwnode(dev, data); -} - static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev) { struct gpio_sim_chip *chip; @@ -503,7 +502,7 @@ static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev) if (ret) return ret; - chip->dev = device_find_child(dev, swnode, gpio_sim_dev_match_fwnode); + chip->dev = device_find_child(dev, swnode, device_match_fwnode); if (!chip->dev) return -ENODEV; @@ -546,14 +545,9 @@ static struct platform_driver gpio_sim_driver = { }; struct gpio_sim_device { + struct dev_sync_probe_data probe_data; struct config_group group; - /* - * If pdev is NULL, the device is 'pending' (waiting for configuration). - * Once the pointer is assigned, the device has been created and the - * item is 'live'. - */ - struct platform_device *pdev; int id; /* @@ -567,46 +561,11 @@ struct gpio_sim_device { */ struct mutex lock; - /* - * This is used to synchronously wait for the driver's probe to complete - * and notify the user-space about any errors. - */ - struct notifier_block bus_notifier; - struct completion probe_completion; - bool driver_bound; - struct gpiod_hog *hogs; struct list_head bank_list; }; -/* This is called with dev->lock already taken. */ -static int gpio_sim_bus_notifier_call(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct gpio_sim_device *simdev = container_of(nb, - struct gpio_sim_device, - bus_notifier); - struct device *dev = data; - char devname[32]; - - snprintf(devname, sizeof(devname), "gpio-sim.%u", simdev->id); - - if (!device_match_name(dev, devname)) - return NOTIFY_DONE; - - if (action == BUS_NOTIFY_BOUND_DRIVER) - simdev->driver_bound = true; - else if (action == BUS_NOTIFY_DRIVER_NOT_BOUND) - simdev->driver_bound = false; - else - return NOTIFY_DONE; - - complete(&simdev->probe_completion); - - return NOTIFY_OK; -} - static struct gpio_sim_device *to_gpio_sim_device(struct config_item *item) { struct config_group *group = to_config_group(item); @@ -669,6 +628,7 @@ struct gpio_sim_line { unsigned int offset; char *name; + bool valid; /* There can only be one hog per line. */ struct gpio_sim_hog *hog; @@ -713,7 +673,7 @@ static bool gpio_sim_device_is_live(struct gpio_sim_device *dev) { lockdep_assert_held(&dev->lock); - return !!dev->pdev; + return !!dev->probe_data.pdev; } static char *gpio_sim_strdup_trimmed(const char *str, size_t count) @@ -735,7 +695,7 @@ static ssize_t gpio_sim_device_config_dev_name_show(struct config_item *item, guard(mutex)(&dev->lock); - pdev = dev->pdev; + pdev = dev->probe_data.pdev; if (pdev) return sprintf(page, "%s\n", dev_name(&pdev->dev)); @@ -784,6 +744,36 @@ gpio_sim_set_line_names(struct gpio_sim_bank *bank, char **line_names) } } +static unsigned int gpio_sim_get_reserved_ranges_size(struct gpio_sim_bank *bank) +{ + struct gpio_sim_line *line; + unsigned int size = 0; + + list_for_each_entry(line, &bank->line_list, siblings) { + if (line->valid) + continue; + + size += 2; + } + + return size; +} + +static void gpio_sim_set_reserved_ranges(struct gpio_sim_bank *bank, + u32 *ranges) +{ + struct gpio_sim_line *line; + int i = 0; + + list_for_each_entry(line, &bank->line_list, siblings) { + if (line->valid) + continue; + + ranges[i++] = line->offset; + ranges[i++] = 1; + } +} + static void gpio_sim_remove_hogs(struct gpio_sim_device *dev) { struct gpiod_hog *hog; @@ -884,9 +874,10 @@ static struct fwnode_handle * gpio_sim_make_bank_swnode(struct gpio_sim_bank *bank, struct fwnode_handle *parent) { + unsigned int prop_idx = 0, line_names_size, ranges_size; struct property_entry properties[GPIO_SIM_PROP_MAX]; - unsigned int prop_idx = 0, line_names_size; char **line_names __free(kfree) = NULL; + u32 *ranges __free(kfree) = NULL; memset(properties, 0, sizeof(properties)); @@ -910,6 +901,19 @@ gpio_sim_make_bank_swnode(struct gpio_sim_bank *bank, line_names, line_names_size); } + ranges_size = gpio_sim_get_reserved_ranges_size(bank); + if (ranges_size) { + ranges = kcalloc(ranges_size, sizeof(u32), GFP_KERNEL); + if (!ranges) + return ERR_PTR(-ENOMEM); + + gpio_sim_set_reserved_ranges(bank, ranges); + + properties[prop_idx++] = PROPERTY_ENTRY_U32_ARRAY_LEN( + "gpio-reserved-ranges", + ranges, ranges_size); + } + return fwnode_create_software_node(properties, parent); } @@ -944,7 +948,6 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev) { struct platform_device_info pdevinfo; struct fwnode_handle *swnode; - struct platform_device *pdev; struct gpio_sim_bank *bank; int ret; @@ -986,31 +989,13 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev) pdevinfo.fwnode = swnode; pdevinfo.id = dev->id; - reinit_completion(&dev->probe_completion); - dev->driver_bound = false; - bus_register_notifier(&platform_bus_type, &dev->bus_notifier); - - pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) { - bus_unregister_notifier(&platform_bus_type, &dev->bus_notifier); - gpio_sim_remove_hogs(dev); - gpio_sim_remove_swnode_recursive(swnode); - return PTR_ERR(pdev); - } - - wait_for_completion(&dev->probe_completion); - bus_unregister_notifier(&platform_bus_type, &dev->bus_notifier); - - if (!dev->driver_bound) { - /* Probe failed, check kernel log. */ - platform_device_unregister(pdev); + ret = dev_sync_probe_register(&dev->probe_data, &pdevinfo); + if (ret) { gpio_sim_remove_hogs(dev); gpio_sim_remove_swnode_recursive(swnode); - return -ENXIO; + return ret; } - dev->pdev = pdev; - return 0; } @@ -1020,11 +1005,37 @@ static void gpio_sim_device_deactivate(struct gpio_sim_device *dev) lockdep_assert_held(&dev->lock); - swnode = dev_fwnode(&dev->pdev->dev); - platform_device_unregister(dev->pdev); + swnode = dev_fwnode(&dev->probe_data.pdev->dev); + dev_sync_probe_unregister(&dev->probe_data); gpio_sim_remove_hogs(dev); gpio_sim_remove_swnode_recursive(swnode); - dev->pdev = NULL; +} + +static void +gpio_sim_device_lockup_configfs(struct gpio_sim_device *dev, bool lock) +{ + struct configfs_subsystem *subsys = dev->group.cg_subsys; + struct gpio_sim_bank *bank; + struct gpio_sim_line *line; + struct config_item *item; + + /* + * The device only needs to depend on leaf entries. This is + * sufficient to lock up all the configfs entries that the + * instantiated, alive device depends on. + */ + list_for_each_entry(bank, &dev->bank_list, siblings) { + list_for_each_entry(line, &bank->line_list, siblings) { + item = line->hog ? &line->hog->item + : &line->group.cg_item; + + if (lock) + WARN_ON(configfs_depend_item_unlocked(subsys, + item)); + else + configfs_undepend_item_unlocked(item); + } + } } static ssize_t @@ -1039,14 +1050,24 @@ gpio_sim_device_config_live_store(struct config_item *item, if (ret) return ret; - guard(mutex)(&dev->lock); + if (live) + gpio_sim_device_lockup_configfs(dev, true); - if (live == gpio_sim_device_is_live(dev)) - ret = -EPERM; - else if (live) - ret = gpio_sim_device_activate(dev); - else - gpio_sim_device_deactivate(dev); + scoped_guard(mutex, &dev->lock) { + if (live == gpio_sim_device_is_live(dev)) + ret = -EPERM; + else if (live) + ret = gpio_sim_device_activate(dev); + else + gpio_sim_device_deactivate(dev); + } + + /* + * Undepend is required only if device disablement (live == 0) + * succeeds or if device enablement (live == 1) fails. + */ + if (live == !!ret) + gpio_sim_device_lockup_configfs(dev, false); return ret ?: count; } @@ -1088,7 +1109,7 @@ static ssize_t gpio_sim_bank_config_chip_name_show(struct config_item *item, guard(mutex)(&dev->lock); if (gpio_sim_device_is_live(dev)) - return device_for_each_child(&dev->pdev->dev, &ctx, + return device_for_each_child(&dev->probe_data.pdev->dev, &ctx, gpio_sim_emit_chip_name); return sprintf(page, "none\n"); @@ -1212,8 +1233,41 @@ static ssize_t gpio_sim_line_config_name_store(struct config_item *item, CONFIGFS_ATTR(gpio_sim_line_config_, name); +static ssize_t +gpio_sim_line_config_valid_show(struct config_item *item, char *page) +{ + struct gpio_sim_line *line = to_gpio_sim_line(item); + struct gpio_sim_device *dev = gpio_sim_line_get_device(line); + + guard(mutex)(&dev->lock); + + return sprintf(page, "%c\n", line->valid ? '1' : '0'); +} + +static ssize_t gpio_sim_line_config_valid_store(struct config_item *item, + const char *page, size_t count) +{ + struct gpio_sim_line *line = to_gpio_sim_line(item); + struct gpio_sim_device *dev = gpio_sim_line_get_device(line); + bool valid; + int ret; + + ret = kstrtobool(page, &valid); + if (ret) + return ret; + + guard(mutex)(&dev->lock); + + line->valid = valid; + + return count; +} + +CONFIGFS_ATTR(gpio_sim_line_config_, valid); + static struct configfs_attribute *gpio_sim_line_config_attrs[] = { &gpio_sim_line_config_attr_name, + &gpio_sim_line_config_attr_valid, NULL }; @@ -1422,6 +1476,7 @@ gpio_sim_bank_config_make_line_group(struct config_group *group, line->parent = bank; line->offset = offset; + line->valid = true; list_add_tail(&line->siblings, &bank->line_list); return &line->group; @@ -1529,8 +1584,7 @@ gpio_sim_config_make_device_group(struct config_group *group, const char *name) mutex_init(&dev->lock); INIT_LIST_HEAD(&dev->bank_list); - dev->bus_notifier.notifier_call = gpio_sim_bus_notifier_call; - init_completion(&dev->probe_completion); + dev_sync_probe_init(&dev->probe_data); return &no_free_ptr(dev)->group; } |
