1
0
mirror of https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2026-01-11 17:10:13 +00:00

gpio fixes for v6.19-rc5

- balance superio enter/exit calls in error path in gpio-it87
 - fix a race where we try to take the SRCU read lock of the GPIO device
   before it's been initialized causing a NULL-pointer dereference
 - fix handling of short-pulse interrupts in gpio-pca053x
 - fix a reference leak in error path in gpio-mpsse
 - mark the GPIO controller as sleeping (it calls sleeping functions) in
   gpio-rockchip
 - fix several issues in management of shared GPIOs
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEkeUTLeW1Rh17omX8BZ0uy/82hMMFAmlhBn4ACgkQBZ0uy/82
 hMMUpg//Vzmls7Xn/IT9Qx08yXaezZLl2+e+g2fGzeiQKojLUME4H8+vJIlsMQ9t
 pJ8mv5SqFIk1ScGZLrcPVE4hNi9dVvobX7KtwGsEcsTpQBri/ZhNn1MW+dmoPE8e
 vsIhWA63n5WB24IloM4xSILa49TNNladwKl4yhoHi2A3cWeCysRJncOJ3ZfJ/XXu
 MM9zzIVNnPpNy08zpwKlwy51a+nwsuPNofMARTnINMrEiIaI7Aw5zt+ecNJJCyvX
 Zr+mWxM8jp9bMPMdK2CmuCzKdEapnsoS7Et/RRO8UtcIRnfvigJbntc4o3b/Fk9L
 hJg95WYvwK8s7kXCj5ipoCSk2xFTYGxjSt2gmwR2/6qO+ogwpcnuRGncZuS+Wmz7
 pHN+Y1DEYuaCPbR7mR448yprD7FO8OTjY+a3y1sDXKb8LqxrSu+KT/noBZ8acZkh
 f7nRxXRjwI8dHYyzYppvoxyYNknFN/PAYi+P0V4xESLalIYycBOOrulM3dnpC6VM
 DxrsK0pdTtQNOHEh+Ru9EMB5cZyBMUPg4BIxZ3MvZfcC7kDjEB698galk2EXIslU
 bWIrJ7ayLJgkTIVDN8EAcwEWkdg4uFUhuWqQ/vyfydmIUqLq9Fs4NJVARM5B8ypz
 nmAx3Vvpg4W3swUUzm9xZNSPPi6k5fz6fCL8hzA90oCw/oSD/cE=
 =HN6d
 -----END PGP SIGNATURE-----

Merge tag 'gpio-fixes-for-v6.19-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux

Pull gpio fixes from Bartosz Golaszewski:
 "There are several ordinary driver fixes and a fix to a race between
  the registration of two chips that causes a crash in GPIO core.

  The bulk of the changed lines however, concerns the management of
  shared GPIOs that landed in v6.19-rc1. Enabling it for ARCH_QCOM
  enabled it in defconfig which effectively enabled it for all arm64
  platforms and exposed the code to quite a lot of testing (which is
  good, right? :)).

  As a resukt, I received a number of bug reports, which I progressively
  fixed over the course of last weeks. This explains the number of lines
  higher than what I normally aim for at this stage.

   - balance superio enter/exit calls in error path in gpio-it87

   - fix a race where we try to take the SRCU read lock of the GPIO
     device before it's been initialized causing a NULL-pointer
     dereference

   - fix handling of short-pulse interrupts in gpio-pca053x

   - fix a reference leak in error path in gpio-mpsse

   - mark the GPIO controller as sleeping (it calls sleeping functions)
     in gpio-rockchip

   - fix several issues in management of shared GPIOs"

* tag 'gpio-fixes-for-v6.19-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux:
  gpio: shared: fix a false-positive sharing detection with reset-gpios
  gpiolib: fix lookup table matching
  gpio: shared: don't allocate the lookup table until we really need it
  gpio: shared: fix a race condition
  gpio: shared: assign the correct firmware node for reset-gpio use-case
  gpio: rockchip: mark the GPIO controller as sleeping
  gpio: mpsse: fix reference leak in gpio_mpsse_probe() error paths
  gpio: pca953x: handle short interrupt pulses on PCAL devices
  gpiolib: fix race condition for gdev->srcu
  gpio: shared: allow sharing a reset-gpios pin between reset-gpio and gpiolib
  gpio: shared: verify con_id when adding proxy lookup
  gpiolib: allow multiple lookup tables per consumer
  gpio: it87: balance superio enter/exit calls in error path
This commit is contained in:
Linus Torvalds 2026-01-09 06:10:22 -10:00
commit a81668db9e
7 changed files with 301 additions and 139 deletions

View File

@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cleanup.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
@ -241,23 +242,17 @@ static int it87_gpio_direction_out(struct gpio_chip *chip,
mask = 1 << (gpio_num % 8); mask = 1 << (gpio_num % 8);
group = (gpio_num / 8); group = (gpio_num / 8);
spin_lock(&it87_gpio->lock); guard(spinlock)(&it87_gpio->lock);
rc = superio_enter(); rc = superio_enter();
if (rc) if (rc)
goto exit; return rc;
/* set the output enable bit */ /* set the output enable bit */
superio_set_mask(mask, group + it87_gpio->output_base); superio_set_mask(mask, group + it87_gpio->output_base);
rc = it87_gpio_set(chip, gpio_num, val); rc = it87_gpio_set(chip, gpio_num, val);
if (rc)
goto exit;
superio_exit(); superio_exit();
exit:
spin_unlock(&it87_gpio->lock);
return rc; return rc;
} }

View File

@ -548,6 +548,13 @@ static void gpio_mpsse_ida_remove(void *data)
ida_free(&gpio_mpsse_ida, priv->id); ida_free(&gpio_mpsse_ida, priv->id);
} }
static void gpio_mpsse_usb_put_dev(void *data)
{
struct mpsse_priv *priv = data;
usb_put_dev(priv->udev);
}
static int mpsse_init_valid_mask(struct gpio_chip *chip, static int mpsse_init_valid_mask(struct gpio_chip *chip,
unsigned long *valid_mask, unsigned long *valid_mask,
unsigned int ngpios) unsigned int ngpios)
@ -592,6 +599,10 @@ static int gpio_mpsse_probe(struct usb_interface *interface,
INIT_LIST_HEAD(&priv->workers); INIT_LIST_HEAD(&priv->workers);
priv->udev = usb_get_dev(interface_to_usbdev(interface)); priv->udev = usb_get_dev(interface_to_usbdev(interface));
err = devm_add_action_or_reset(dev, gpio_mpsse_usb_put_dev, priv);
if (err)
return err;
priv->intf = interface; priv->intf = interface;
priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber; priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber;
@ -713,7 +724,6 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf)
priv->intf = NULL; priv->intf = NULL;
usb_set_intfdata(intf, NULL); usb_set_intfdata(intf, NULL);
usb_put_dev(priv->udev);
} }
static struct usb_driver gpio_mpsse_driver = { static struct usb_driver gpio_mpsse_driver = {

View File

@ -943,14 +943,35 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
DECLARE_BITMAP(old_stat, MAX_LINE); DECLARE_BITMAP(old_stat, MAX_LINE);
DECLARE_BITMAP(cur_stat, MAX_LINE); DECLARE_BITMAP(cur_stat, MAX_LINE);
DECLARE_BITMAP(new_stat, MAX_LINE); DECLARE_BITMAP(new_stat, MAX_LINE);
DECLARE_BITMAP(int_stat, MAX_LINE);
DECLARE_BITMAP(trigger, MAX_LINE); DECLARE_BITMAP(trigger, MAX_LINE);
DECLARE_BITMAP(edges, MAX_LINE); DECLARE_BITMAP(edges, MAX_LINE);
int ret; int ret;
if (chip->driver_data & PCA_PCAL) {
/* Read INT_STAT before it is cleared by the input-port read. */
ret = pca953x_read_regs(chip, PCAL953X_INT_STAT, int_stat);
if (ret)
return false;
}
ret = pca953x_read_regs(chip, chip->regs->input, cur_stat); ret = pca953x_read_regs(chip, chip->regs->input, cur_stat);
if (ret) if (ret)
return false; return false;
if (chip->driver_data & PCA_PCAL) {
/* Detect short pulses via INT_STAT. */
bitmap_and(trigger, int_stat, chip->irq_mask, gc->ngpio);
/* Apply filter for rising/falling edge selection. */
bitmap_replace(new_stat, chip->irq_trig_fall, chip->irq_trig_raise,
cur_stat, gc->ngpio);
bitmap_and(int_stat, new_stat, trigger, gc->ngpio);
} else {
bitmap_zero(int_stat, gc->ngpio);
}
/* Remove output pins from the equation */ /* Remove output pins from the equation */
pca953x_read_regs(chip, chip->regs->direction, reg_direction); pca953x_read_regs(chip, chip->regs->direction, reg_direction);
@ -964,7 +985,8 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
if (bitmap_empty(chip->irq_trig_level_high, gc->ngpio) && if (bitmap_empty(chip->irq_trig_level_high, gc->ngpio) &&
bitmap_empty(chip->irq_trig_level_low, gc->ngpio)) { bitmap_empty(chip->irq_trig_level_low, gc->ngpio)) {
if (bitmap_empty(trigger, gc->ngpio)) if (bitmap_empty(trigger, gc->ngpio) &&
bitmap_empty(int_stat, gc->ngpio))
return false; return false;
} }
@ -972,6 +994,7 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio); bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio);
bitmap_or(edges, old_stat, cur_stat, gc->ngpio); bitmap_or(edges, old_stat, cur_stat, gc->ngpio);
bitmap_and(pending, edges, trigger, gc->ngpio); bitmap_and(pending, edges, trigger, gc->ngpio);
bitmap_or(pending, pending, int_stat, gc->ngpio);
bitmap_and(cur_stat, new_stat, chip->irq_trig_level_high, gc->ngpio); bitmap_and(cur_stat, new_stat, chip->irq_trig_level_high, gc->ngpio);
bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio); bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio);

View File

@ -593,6 +593,7 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank)
gc->ngpio = bank->nr_pins; gc->ngpio = bank->nr_pins;
gc->label = bank->name; gc->label = bank->name;
gc->parent = bank->dev; gc->parent = bank->dev;
gc->can_sleep = true;
ret = gpiochip_add_data(gc, bank); ret = gpiochip_add_data(gc, bank);
if (ret) { if (ret) {

View File

@ -38,8 +38,10 @@ struct gpio_shared_ref {
int dev_id; int dev_id;
/* Protects the auxiliary device struct and the lookup table. */ /* Protects the auxiliary device struct and the lookup table. */
struct mutex lock; struct mutex lock;
struct lock_class_key lock_key;
struct auxiliary_device adev; struct auxiliary_device adev;
struct gpiod_lookup_table *lookup; struct gpiod_lookup_table *lookup;
bool is_reset_gpio;
}; };
/* Represents a single GPIO pin. */ /* Represents a single GPIO pin. */
@ -76,6 +78,60 @@ gpio_shared_find_entry(struct fwnode_handle *controller_node,
return NULL; return NULL;
} }
static struct gpio_shared_ref *gpio_shared_make_ref(struct fwnode_handle *fwnode,
const char *con_id,
enum gpiod_flags flags)
{
char *con_id_cpy __free(kfree) = NULL;
struct gpio_shared_ref *ref __free(kfree) = kzalloc(sizeof(*ref), GFP_KERNEL);
if (!ref)
return NULL;
if (con_id) {
con_id_cpy = kstrdup(con_id, GFP_KERNEL);
if (!con_id_cpy)
return NULL;
}
ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
if (ref->dev_id < 0)
return NULL;
ref->flags = flags;
ref->con_id = no_free_ptr(con_id_cpy);
ref->fwnode = fwnode;
lockdep_register_key(&ref->lock_key);
mutex_init_with_key(&ref->lock, &ref->lock_key);
return no_free_ptr(ref);
}
static int gpio_shared_setup_reset_proxy(struct gpio_shared_entry *entry,
enum gpiod_flags flags)
{
struct gpio_shared_ref *ref;
list_for_each_entry(ref, &entry->refs, list) {
if (ref->is_reset_gpio)
/* Already set-up. */
return 0;
}
ref = gpio_shared_make_ref(NULL, "reset", flags);
if (!ref)
return -ENOMEM;
ref->is_reset_gpio = true;
list_add_tail(&ref->list, &entry->refs);
pr_debug("Created a secondary shared GPIO reference for potential reset-gpio device for GPIO %u at %s\n",
entry->offset, fwnode_get_name(entry->fwnode));
return 0;
}
/* Handle all special nodes that we should ignore. */ /* Handle all special nodes that we should ignore. */
static bool gpio_shared_of_node_ignore(struct device_node *node) static bool gpio_shared_of_node_ignore(struct device_node *node)
{ {
@ -106,6 +162,7 @@ static int gpio_shared_of_traverse(struct device_node *curr)
size_t con_id_len, suffix_len; size_t con_id_len, suffix_len;
struct fwnode_handle *fwnode; struct fwnode_handle *fwnode;
struct of_phandle_args args; struct of_phandle_args args;
struct gpio_shared_ref *ref;
struct property *prop; struct property *prop;
unsigned int offset; unsigned int offset;
const char *suffix; const char *suffix;
@ -138,6 +195,7 @@ static int gpio_shared_of_traverse(struct device_node *curr)
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
struct device_node *np __free(device_node) = NULL; struct device_node *np __free(device_node) = NULL;
char *con_id __free(kfree) = NULL;
ret = of_parse_phandle_with_args(curr, prop->name, ret = of_parse_phandle_with_args(curr, prop->name,
"#gpio-cells", i, "#gpio-cells", i,
@ -182,15 +240,6 @@ static int gpio_shared_of_traverse(struct device_node *curr)
list_add_tail(&entry->list, &gpio_shared_list); list_add_tail(&entry->list, &gpio_shared_list);
} }
struct gpio_shared_ref *ref __free(kfree) =
kzalloc(sizeof(*ref), GFP_KERNEL);
if (!ref)
return -ENOMEM;
ref->fwnode = fwnode_handle_get(of_fwnode_handle(curr));
ref->flags = args.args[1];
mutex_init(&ref->lock);
if (strends(prop->name, "gpios")) if (strends(prop->name, "gpios"))
suffix = "-gpios"; suffix = "-gpios";
else if (strends(prop->name, "gpio")) else if (strends(prop->name, "gpio"))
@ -202,27 +251,32 @@ static int gpio_shared_of_traverse(struct device_node *curr)
/* We only set con_id if there's actually one. */ /* We only set con_id if there's actually one. */
if (strcmp(prop->name, "gpios") && strcmp(prop->name, "gpio")) { if (strcmp(prop->name, "gpios") && strcmp(prop->name, "gpio")) {
ref->con_id = kstrdup(prop->name, GFP_KERNEL); con_id = kstrdup(prop->name, GFP_KERNEL);
if (!ref->con_id) if (!con_id)
return -ENOMEM; return -ENOMEM;
con_id_len = strlen(ref->con_id); con_id_len = strlen(con_id);
suffix_len = strlen(suffix); suffix_len = strlen(suffix);
ref->con_id[con_id_len - suffix_len] = '\0'; con_id[con_id_len - suffix_len] = '\0';
} }
ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL); ref = gpio_shared_make_ref(fwnode_handle_get(of_fwnode_handle(curr)),
if (ref->dev_id < 0) { con_id, args.args[1]);
kfree(ref->con_id); if (!ref)
return -ENOMEM; return -ENOMEM;
}
if (!list_empty(&entry->refs)) if (!list_empty(&entry->refs))
pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n", pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n",
entry->offset, fwnode_get_name(entry->fwnode)); entry->offset, fwnode_get_name(entry->fwnode));
list_add_tail(&no_free_ptr(ref)->list, &entry->refs); list_add_tail(&ref->list, &entry->refs);
if (strcmp(prop->name, "reset-gpios") == 0) {
ret = gpio_shared_setup_reset_proxy(entry, args.args[1]);
if (ret)
return ret;
}
} }
} }
@ -306,20 +360,16 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
struct fwnode_handle *reset_fwnode = dev_fwnode(consumer); struct fwnode_handle *reset_fwnode = dev_fwnode(consumer);
struct fwnode_reference_args ref_args, aux_args; struct fwnode_reference_args ref_args, aux_args;
struct device *parent = consumer->parent; struct device *parent = consumer->parent;
struct gpio_shared_ref *real_ref;
bool match; bool match;
int ret; int ret;
lockdep_assert_held(&ref->lock);
/* The reset-gpio device must have a parent AND a firmware node. */ /* The reset-gpio device must have a parent AND a firmware node. */
if (!parent || !reset_fwnode) if (!parent || !reset_fwnode)
return false; return false;
/*
* FIXME: use device_is_compatible() once the reset-gpio drivers gains
* a compatible string which it currently does not have.
*/
if (!strstarts(dev_name(consumer), "reset.gpio."))
return false;
/* /*
* Parent of the reset-gpio auxiliary device is the GPIO chip whose * Parent of the reset-gpio auxiliary device is the GPIO chip whose
* fwnode we stored in the entry structure. * fwnode we stored in the entry structure.
@ -328,33 +378,61 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
return false; return false;
/* /*
* The device associated with the shared reference's firmware node is * Now we need to find the actual pin we want to assign to this
* the consumer of the reset control exposed by the reset-gpio device. * reset-gpio device. To that end: iterate over the list of references
* It must have a "reset-gpios" property that's referencing the entry's * of this entry and see if there's one, whose reset-gpios property's
* firmware node. * arguments match the ones from this consumer's node.
*
* The reference args must agree between the real consumer and the
* auxiliary reset-gpio device.
*/ */
ret = fwnode_property_get_reference_args(ref->fwnode, "reset-gpios", list_for_each_entry(real_ref, &entry->refs, list) {
NULL, 2, 0, &ref_args); if (real_ref == ref)
if (ret) continue;
return false;
guard(mutex)(&real_ref->lock);
if (!real_ref->fwnode)
continue;
/*
* The device associated with the shared reference's firmware
* node is the consumer of the reset control exposed by the
* reset-gpio device. It must have a "reset-gpios" property
* that's referencing the entry's firmware node.
*
* The reference args must agree between the real consumer and
* the auxiliary reset-gpio device.
*/
ret = fwnode_property_get_reference_args(real_ref->fwnode,
"reset-gpios",
NULL, 2, 0, &ref_args);
if (ret)
continue;
ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios",
NULL, 2, 0, &aux_args);
if (ret) {
fwnode_handle_put(ref_args.fwnode);
continue;
}
match = ((ref_args.fwnode == entry->fwnode) &&
(aux_args.fwnode == entry->fwnode) &&
(ref_args.args[0] == aux_args.args[0]));
ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios",
NULL, 2, 0, &aux_args);
if (ret) {
fwnode_handle_put(ref_args.fwnode); fwnode_handle_put(ref_args.fwnode);
return false; fwnode_handle_put(aux_args.fwnode);
if (!match)
continue;
/*
* Reuse the fwnode of the real device, next time we'll use it
* in the normal path.
*/
ref->fwnode = fwnode_handle_get(reset_fwnode);
return true;
} }
match = ((ref_args.fwnode == entry->fwnode) && return false;
(aux_args.fwnode == entry->fwnode) &&
(ref_args.args[0] == aux_args.args[0]));
fwnode_handle_put(ref_args.fwnode);
fwnode_handle_put(aux_args.fwnode);
return match;
} }
#else #else
static bool gpio_shared_dev_is_reset_gpio(struct device *consumer, static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
@ -365,25 +443,34 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
} }
#endif /* CONFIG_RESET_GPIO */ #endif /* CONFIG_RESET_GPIO */
int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags) int gpio_shared_add_proxy_lookup(struct device *consumer, const char *con_id,
unsigned long lflags)
{ {
const char *dev_id = dev_name(consumer); const char *dev_id = dev_name(consumer);
struct gpiod_lookup_table *lookup;
struct gpio_shared_entry *entry; struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref; struct gpio_shared_ref *ref;
struct gpiod_lookup_table *lookup __free(kfree) =
kzalloc(struct_size(lookup, table, 2), GFP_KERNEL);
if (!lookup)
return -ENOMEM;
list_for_each_entry(entry, &gpio_shared_list, list) { list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) { list_for_each_entry(ref, &entry->refs, list) {
if (!device_match_fwnode(consumer, ref->fwnode) &&
!gpio_shared_dev_is_reset_gpio(consumer, entry, ref))
continue;
guard(mutex)(&ref->lock); guard(mutex)(&ref->lock);
/*
* FIXME: use device_is_compatible() once the reset-gpio
* drivers gains a compatible string which it currently
* does not have.
*/
if (!ref->fwnode && strstarts(dev_name(consumer), "reset.gpio.")) {
if (!gpio_shared_dev_is_reset_gpio(consumer, entry, ref))
continue;
} else if (!device_match_fwnode(consumer, ref->fwnode)) {
continue;
}
if ((!con_id && ref->con_id) || (con_id && !ref->con_id) ||
(con_id && ref->con_id && strcmp(con_id, ref->con_id) != 0))
continue;
/* We've already done that on a previous request. */ /* We've already done that on a previous request. */
if (ref->lookup) if (ref->lookup)
return 0; return 0;
@ -395,6 +482,10 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags)
if (!key) if (!key)
return -ENOMEM; return -ENOMEM;
lookup = kzalloc(struct_size(lookup, table, 2), GFP_KERNEL);
if (!lookup)
return -ENOMEM;
pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n", pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n",
dev_id, key, ref->con_id ?: "none"); dev_id, key, ref->con_id ?: "none");
@ -402,7 +493,7 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags)
lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0, lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0,
ref->con_id, lflags); ref->con_id, lflags);
ref->lookup = no_free_ptr(lookup); ref->lookup = lookup;
gpiod_add_lookup_table(ref->lookup); gpiod_add_lookup_table(ref->lookup);
return 0; return 0;
@ -466,8 +557,9 @@ int gpio_device_setup_shared(struct gpio_device *gdev)
entry->offset, gpio_device_get_label(gdev)); entry->offset, gpio_device_get_label(gdev));
list_for_each_entry(ref, &entry->refs, list) { list_for_each_entry(ref, &entry->refs, list) {
pr_debug("Setting up a shared GPIO entry for %s\n", pr_debug("Setting up a shared GPIO entry for %s (con_id: '%s')\n",
fwnode_get_name(ref->fwnode)); fwnode_get_name(ref->fwnode) ?: "(no fwnode)",
ref->con_id ?: "(none)");
ret = gpio_shared_make_adev(gdev, entry, ref); ret = gpio_shared_make_adev(gdev, entry, ref);
if (ret) if (ret)
@ -487,15 +579,6 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
if (!device_match_fwnode(&gdev->dev, entry->fwnode)) if (!device_match_fwnode(&gdev->dev, entry->fwnode))
continue; continue;
/*
* For some reason if we call synchronize_srcu() in GPIO core,
* descent here and take this mutex and then recursively call
* synchronize_srcu() again from gpiochip_remove() (which is
* totally fine) called after gpio_shared_remove_adev(),
* lockdep prints a false positive deadlock splat. Disable
* lockdep here.
*/
lockdep_off();
list_for_each_entry(ref, &entry->refs, list) { list_for_each_entry(ref, &entry->refs, list) {
guard(mutex)(&ref->lock); guard(mutex)(&ref->lock);
@ -508,7 +591,6 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
gpio_shared_remove_adev(&ref->adev); gpio_shared_remove_adev(&ref->adev);
} }
lockdep_on();
} }
} }
@ -604,6 +686,7 @@ static void gpio_shared_drop_ref(struct gpio_shared_ref *ref)
{ {
list_del(&ref->list); list_del(&ref->list);
mutex_destroy(&ref->lock); mutex_destroy(&ref->lock);
lockdep_unregister_key(&ref->lock_key);
kfree(ref->con_id); kfree(ref->con_id);
ida_free(&gpio_shared_ida, ref->dev_id); ida_free(&gpio_shared_ida, ref->dev_id);
fwnode_handle_put(ref->fwnode); fwnode_handle_put(ref->fwnode);
@ -635,12 +718,38 @@ static void __init gpio_shared_teardown(void)
} }
} }
static bool gpio_shared_entry_is_really_shared(struct gpio_shared_entry *entry)
{
size_t num_nodes = list_count_nodes(&entry->refs);
struct gpio_shared_ref *ref;
if (num_nodes <= 1)
return false;
if (num_nodes > 2)
return true;
/* Exactly two references: */
list_for_each_entry(ref, &entry->refs, list) {
/*
* Corner-case: the second reference comes from the potential
* reset-gpio instance. However, this pin is not really shared
* as it would have three references in this case. Avoid
* creating unnecessary proxies.
*/
if (ref->is_reset_gpio)
return false;
}
return true;
}
static void gpio_shared_free_exclusive(void) static void gpio_shared_free_exclusive(void)
{ {
struct gpio_shared_entry *entry, *epos; struct gpio_shared_entry *entry, *epos;
list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) { list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) {
if (list_count_nodes(&entry->refs) > 1) if (gpio_shared_entry_is_really_shared(entry))
continue; continue;
gpio_shared_drop_ref(list_first_entry(&entry->refs, gpio_shared_drop_ref(list_first_entry(&entry->refs,

View File

@ -16,7 +16,8 @@ struct device;
int gpio_device_setup_shared(struct gpio_device *gdev); int gpio_device_setup_shared(struct gpio_device *gdev);
void gpio_device_teardown_shared(struct gpio_device *gdev); void gpio_device_teardown_shared(struct gpio_device *gdev);
int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags); int gpio_shared_add_proxy_lookup(struct device *consumer, const char *con_id,
unsigned long lflags);
#else #else
@ -28,6 +29,7 @@ static inline int gpio_device_setup_shared(struct gpio_device *gdev)
static inline void gpio_device_teardown_shared(struct gpio_device *gdev) { } static inline void gpio_device_teardown_shared(struct gpio_device *gdev) { }
static inline int gpio_shared_add_proxy_lookup(struct device *consumer, static inline int gpio_shared_add_proxy_lookup(struct device *consumer,
const char *con_id,
unsigned long lflags) unsigned long lflags)
{ {
return 0; return 0;

View File

@ -1105,6 +1105,18 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
gdev->ngpio = gc->ngpio; gdev->ngpio = gc->ngpio;
gdev->can_sleep = gc->can_sleep; gdev->can_sleep = gc->can_sleep;
rwlock_init(&gdev->line_state_lock);
RAW_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier);
ret = init_srcu_struct(&gdev->srcu);
if (ret)
goto err_free_label;
ret = init_srcu_struct(&gdev->desc_srcu);
if (ret)
goto err_cleanup_gdev_srcu;
scoped_guard(mutex, &gpio_devices_lock) { scoped_guard(mutex, &gpio_devices_lock) {
/* /*
* TODO: this allocates a Linux GPIO number base in the global * TODO: this allocates a Linux GPIO number base in the global
@ -1119,7 +1131,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
if (base < 0) { if (base < 0) {
ret = base; ret = base;
base = 0; base = 0;
goto err_free_label; goto err_cleanup_desc_srcu;
} }
/* /*
@ -1139,22 +1151,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
ret = gpiodev_add_to_list_unlocked(gdev); ret = gpiodev_add_to_list_unlocked(gdev);
if (ret) { if (ret) {
gpiochip_err(gc, "GPIO integer space overlap, cannot add chip\n"); gpiochip_err(gc, "GPIO integer space overlap, cannot add chip\n");
goto err_free_label; goto err_cleanup_desc_srcu;
} }
} }
rwlock_init(&gdev->line_state_lock);
RAW_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier);
ret = init_srcu_struct(&gdev->srcu);
if (ret)
goto err_remove_from_list;
ret = init_srcu_struct(&gdev->desc_srcu);
if (ret)
goto err_cleanup_gdev_srcu;
#ifdef CONFIG_PINCTRL #ifdef CONFIG_PINCTRL
INIT_LIST_HEAD(&gdev->pin_ranges); INIT_LIST_HEAD(&gdev->pin_ranges);
#endif #endif
@ -1164,11 +1164,11 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
ret = gpiochip_set_names(gc); ret = gpiochip_set_names(gc);
if (ret) if (ret)
goto err_cleanup_desc_srcu; goto err_remove_from_list;
ret = gpiochip_init_valid_mask(gc); ret = gpiochip_init_valid_mask(gc);
if (ret) if (ret)
goto err_cleanup_desc_srcu; goto err_remove_from_list;
for (desc_index = 0; desc_index < gc->ngpio; desc_index++) { for (desc_index = 0; desc_index < gc->ngpio; desc_index++) {
struct gpio_desc *desc = &gdev->descs[desc_index]; struct gpio_desc *desc = &gdev->descs[desc_index];
@ -1248,10 +1248,6 @@ err_remove_of_chip:
of_gpiochip_remove(gc); of_gpiochip_remove(gc);
err_free_valid_mask: err_free_valid_mask:
gpiochip_free_valid_mask(gc); gpiochip_free_valid_mask(gc);
err_cleanup_desc_srcu:
cleanup_srcu_struct(&gdev->desc_srcu);
err_cleanup_gdev_srcu:
cleanup_srcu_struct(&gdev->srcu);
err_remove_from_list: err_remove_from_list:
scoped_guard(mutex, &gpio_devices_lock) scoped_guard(mutex, &gpio_devices_lock)
list_del_rcu(&gdev->list); list_del_rcu(&gdev->list);
@ -1261,6 +1257,10 @@ err_remove_from_list:
gpio_device_put(gdev); gpio_device_put(gdev);
goto err_print_message; goto err_print_message;
} }
err_cleanup_desc_srcu:
cleanup_srcu_struct(&gdev->desc_srcu);
err_cleanup_gdev_srcu:
cleanup_srcu_struct(&gdev->srcu);
err_free_label: err_free_label:
kfree_const(gdev->label); kfree_const(gdev->label);
err_free_descs: err_free_descs:
@ -4508,45 +4508,41 @@ void gpiod_remove_hogs(struct gpiod_hog *hogs)
} }
EXPORT_SYMBOL_GPL(gpiod_remove_hogs); EXPORT_SYMBOL_GPL(gpiod_remove_hogs);
static struct gpiod_lookup_table *gpiod_find_lookup_table(struct device *dev) static bool gpiod_match_lookup_table(struct device *dev,
const struct gpiod_lookup_table *table)
{ {
const char *dev_id = dev ? dev_name(dev) : NULL; const char *dev_id = dev ? dev_name(dev) : NULL;
struct gpiod_lookup_table *table;
list_for_each_entry(table, &gpio_lookup_list, list) { lockdep_assert_held(&gpio_lookup_lock);
if (table->dev_id && dev_id) {
/* if (table->dev_id && dev_id) {
* Valid strings on both ends, must be identical to have /*
* a match * Valid strings on both ends, must be identical to have
*/ * a match
if (!strcmp(table->dev_id, dev_id)) */
return table; if (!strcmp(table->dev_id, dev_id))
} else { return true;
/* } else {
* One of the pointers is NULL, so both must be to have /*
* a match * One of the pointers is NULL, so both must be to have
*/ * a match
if (dev_id == table->dev_id) */
return table; if (dev_id == table->dev_id)
} return true;
} }
return NULL; return false;
} }
static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id, static struct gpio_desc *gpio_desc_table_match(struct device *dev, const char *con_id,
unsigned int idx, unsigned long *flags) unsigned int idx, unsigned long *flags,
struct gpiod_lookup_table *table)
{ {
struct gpio_desc *desc = ERR_PTR(-ENOENT); struct gpio_desc *desc;
struct gpiod_lookup_table *table;
struct gpiod_lookup *p; struct gpiod_lookup *p;
struct gpio_chip *gc; struct gpio_chip *gc;
guard(mutex)(&gpio_lookup_lock); lockdep_assert_held(&gpio_lookup_lock);
table = gpiod_find_lookup_table(dev);
if (!table)
return desc;
for (p = &table->table[0]; p->key; p++) { for (p = &table->table[0]; p->key; p++) {
/* idx must always match exactly */ /* idx must always match exactly */
@ -4600,7 +4596,30 @@ static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
return desc; return desc;
} }
return desc; return NULL;
}
static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
unsigned int idx, unsigned long *flags)
{
struct gpiod_lookup_table *table;
struct gpio_desc *desc;
guard(mutex)(&gpio_lookup_lock);
list_for_each_entry(table, &gpio_lookup_list, list) {
if (!gpiod_match_lookup_table(dev, table))
continue;
desc = gpio_desc_table_match(dev, con_id, idx, flags, table);
if (!desc)
continue;
/* On IS_ERR() or match. */
return desc;
}
return ERR_PTR(-ENOENT);
} }
static int platform_gpio_count(struct device *dev, const char *con_id) static int platform_gpio_count(struct device *dev, const char *con_id)
@ -4610,14 +4629,16 @@ static int platform_gpio_count(struct device *dev, const char *con_id)
unsigned int count = 0; unsigned int count = 0;
scoped_guard(mutex, &gpio_lookup_lock) { scoped_guard(mutex, &gpio_lookup_lock) {
table = gpiod_find_lookup_table(dev); list_for_each_entry(table, &gpio_lookup_list, list) {
if (!table) if (!gpiod_match_lookup_table(dev, table))
return -ENOENT; continue;
for (p = &table->table[0]; p->key; p++) { for (p = &table->table[0]; p->key; p++) {
if ((con_id && p->con_id && !strcmp(con_id, p->con_id)) || if ((con_id && p->con_id &&
(!con_id && !p->con_id)) !strcmp(con_id, p->con_id)) ||
count++; (!con_id && !p->con_id))
count++;
}
} }
} }
@ -4696,7 +4717,8 @@ struct gpio_desc *gpiod_find_and_request(struct device *consumer,
* lookup table for the proxy device as previously * lookup table for the proxy device as previously
* we only knew the consumer's fwnode. * we only knew the consumer's fwnode.
*/ */
ret = gpio_shared_add_proxy_lookup(consumer, lookupflags); ret = gpio_shared_add_proxy_lookup(consumer, con_id,
lookupflags);
if (ret) if (ret)
return ERR_PTR(ret); return ERR_PTR(ret);