mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-01-11 09:00:12 +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:
commit
a81668db9e
@ -12,6 +12,7 @@
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
@ -241,23 +242,17 @@ static int it87_gpio_direction_out(struct gpio_chip *chip,
|
||||
mask = 1 << (gpio_num % 8);
|
||||
group = (gpio_num / 8);
|
||||
|
||||
spin_lock(&it87_gpio->lock);
|
||||
guard(spinlock)(&it87_gpio->lock);
|
||||
|
||||
rc = superio_enter();
|
||||
if (rc)
|
||||
goto exit;
|
||||
return rc;
|
||||
|
||||
/* set the output enable bit */
|
||||
superio_set_mask(mask, group + it87_gpio->output_base);
|
||||
|
||||
rc = it87_gpio_set(chip, gpio_num, val);
|
||||
if (rc)
|
||||
goto exit;
|
||||
|
||||
superio_exit();
|
||||
|
||||
exit:
|
||||
spin_unlock(&it87_gpio->lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@ -548,6 +548,13 @@ static void gpio_mpsse_ida_remove(void *data)
|
||||
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,
|
||||
unsigned long *valid_mask,
|
||||
unsigned int ngpios)
|
||||
@ -592,6 +599,10 @@ static int gpio_mpsse_probe(struct usb_interface *interface,
|
||||
INIT_LIST_HEAD(&priv->workers);
|
||||
|
||||
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_id = interface->cur_altsetting->desc.bInterfaceNumber;
|
||||
|
||||
@ -713,7 +724,6 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf)
|
||||
|
||||
priv->intf = NULL;
|
||||
usb_set_intfdata(intf, NULL);
|
||||
usb_put_dev(priv->udev);
|
||||
}
|
||||
|
||||
static struct usb_driver gpio_mpsse_driver = {
|
||||
|
||||
@ -943,14 +943,35 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
|
||||
DECLARE_BITMAP(old_stat, MAX_LINE);
|
||||
DECLARE_BITMAP(cur_stat, MAX_LINE);
|
||||
DECLARE_BITMAP(new_stat, MAX_LINE);
|
||||
DECLARE_BITMAP(int_stat, MAX_LINE);
|
||||
DECLARE_BITMAP(trigger, MAX_LINE);
|
||||
DECLARE_BITMAP(edges, MAX_LINE);
|
||||
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);
|
||||
if (ret)
|
||||
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 */
|
||||
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) &&
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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_or(edges, old_stat, cur_stat, 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, cur_stat, chip->irq_mask, gc->ngpio);
|
||||
|
||||
@ -593,6 +593,7 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank)
|
||||
gc->ngpio = bank->nr_pins;
|
||||
gc->label = bank->name;
|
||||
gc->parent = bank->dev;
|
||||
gc->can_sleep = true;
|
||||
|
||||
ret = gpiochip_add_data(gc, bank);
|
||||
if (ret) {
|
||||
|
||||
@ -38,8 +38,10 @@ struct gpio_shared_ref {
|
||||
int dev_id;
|
||||
/* Protects the auxiliary device struct and the lookup table. */
|
||||
struct mutex lock;
|
||||
struct lock_class_key lock_key;
|
||||
struct auxiliary_device adev;
|
||||
struct gpiod_lookup_table *lookup;
|
||||
bool is_reset_gpio;
|
||||
};
|
||||
|
||||
/* Represents a single GPIO pin. */
|
||||
@ -76,6 +78,60 @@ gpio_shared_find_entry(struct fwnode_handle *controller_node,
|
||||
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. */
|
||||
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;
|
||||
struct fwnode_handle *fwnode;
|
||||
struct of_phandle_args args;
|
||||
struct gpio_shared_ref *ref;
|
||||
struct property *prop;
|
||||
unsigned int offset;
|
||||
const char *suffix;
|
||||
@ -138,6 +195,7 @@ static int gpio_shared_of_traverse(struct device_node *curr)
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
struct device_node *np __free(device_node) = NULL;
|
||||
char *con_id __free(kfree) = NULL;
|
||||
|
||||
ret = of_parse_phandle_with_args(curr, prop->name,
|
||||
"#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);
|
||||
}
|
||||
|
||||
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"))
|
||||
suffix = "-gpios";
|
||||
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. */
|
||||
if (strcmp(prop->name, "gpios") && strcmp(prop->name, "gpio")) {
|
||||
ref->con_id = kstrdup(prop->name, GFP_KERNEL);
|
||||
if (!ref->con_id)
|
||||
con_id = kstrdup(prop->name, GFP_KERNEL);
|
||||
if (!con_id)
|
||||
return -ENOMEM;
|
||||
|
||||
con_id_len = strlen(ref->con_id);
|
||||
con_id_len = strlen(con_id);
|
||||
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);
|
||||
if (ref->dev_id < 0) {
|
||||
kfree(ref->con_id);
|
||||
ref = gpio_shared_make_ref(fwnode_handle_get(of_fwnode_handle(curr)),
|
||||
con_id, args.args[1]);
|
||||
if (!ref)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (!list_empty(&entry->refs))
|
||||
pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n",
|
||||
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_reference_args ref_args, aux_args;
|
||||
struct device *parent = consumer->parent;
|
||||
struct gpio_shared_ref *real_ref;
|
||||
bool match;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ref->lock);
|
||||
|
||||
/* The reset-gpio device must have a parent AND a firmware node. */
|
||||
if (!parent || !reset_fwnode)
|
||||
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
|
||||
* fwnode we stored in the entry structure.
|
||||
@ -328,33 +378,61 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
|
||||
return false;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* Now we need to find the actual pin we want to assign to this
|
||||
* reset-gpio device. To that end: iterate over the list of references
|
||||
* of this entry and see if there's one, whose reset-gpios property's
|
||||
* arguments match the ones from this consumer's node.
|
||||
*/
|
||||
ret = fwnode_property_get_reference_args(ref->fwnode, "reset-gpios",
|
||||
NULL, 2, 0, &ref_args);
|
||||
if (ret)
|
||||
return false;
|
||||
list_for_each_entry(real_ref, &entry->refs, list) {
|
||||
if (real_ref == ref)
|
||||
continue;
|
||||
|
||||
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);
|
||||
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) &&
|
||||
(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;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
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 */
|
||||
|
||||
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);
|
||||
struct gpiod_lookup_table *lookup;
|
||||
struct gpio_shared_entry *entry;
|
||||
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(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);
|
||||
|
||||
/*
|
||||
* 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. */
|
||||
if (ref->lookup)
|
||||
return 0;
|
||||
@ -395,6 +482,10 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags)
|
||||
if (!key)
|
||||
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",
|
||||
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,
|
||||
ref->con_id, lflags);
|
||||
|
||||
ref->lookup = no_free_ptr(lookup);
|
||||
ref->lookup = lookup;
|
||||
gpiod_add_lookup_table(ref->lookup);
|
||||
|
||||
return 0;
|
||||
@ -466,8 +557,9 @@ int gpio_device_setup_shared(struct gpio_device *gdev)
|
||||
entry->offset, gpio_device_get_label(gdev));
|
||||
|
||||
list_for_each_entry(ref, &entry->refs, list) {
|
||||
pr_debug("Setting up a shared GPIO entry for %s\n",
|
||||
fwnode_get_name(ref->fwnode));
|
||||
pr_debug("Setting up a shared GPIO entry for %s (con_id: '%s')\n",
|
||||
fwnode_get_name(ref->fwnode) ?: "(no fwnode)",
|
||||
ref->con_id ?: "(none)");
|
||||
|
||||
ret = gpio_shared_make_adev(gdev, entry, ref);
|
||||
if (ret)
|
||||
@ -487,15 +579,6 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
|
||||
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
|
||||
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) {
|
||||
guard(mutex)(&ref->lock);
|
||||
|
||||
@ -508,7 +591,6 @@ void gpio_device_teardown_shared(struct gpio_device *gdev)
|
||||
|
||||
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);
|
||||
mutex_destroy(&ref->lock);
|
||||
lockdep_unregister_key(&ref->lock_key);
|
||||
kfree(ref->con_id);
|
||||
ida_free(&gpio_shared_ida, ref->dev_id);
|
||||
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)
|
||||
{
|
||||
struct gpio_shared_entry *entry, *epos;
|
||||
|
||||
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;
|
||||
|
||||
gpio_shared_drop_ref(list_first_entry(&entry->refs,
|
||||
|
||||
@ -16,7 +16,8 @@ struct device;
|
||||
|
||||
int gpio_device_setup_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
|
||||
|
||||
@ -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 int gpio_shared_add_proxy_lookup(struct device *consumer,
|
||||
const char *con_id,
|
||||
unsigned long lflags)
|
||||
{
|
||||
return 0;
|
||||
|
||||
@ -1105,6 +1105,18 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
|
||||
gdev->ngpio = gc->ngpio;
|
||||
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) {
|
||||
/*
|
||||
* 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) {
|
||||
ret = base;
|
||||
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);
|
||||
if (ret) {
|
||||
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
|
||||
INIT_LIST_HEAD(&gdev->pin_ranges);
|
||||
#endif
|
||||
@ -1164,11 +1164,11 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
|
||||
|
||||
ret = gpiochip_set_names(gc);
|
||||
if (ret)
|
||||
goto err_cleanup_desc_srcu;
|
||||
goto err_remove_from_list;
|
||||
|
||||
ret = gpiochip_init_valid_mask(gc);
|
||||
if (ret)
|
||||
goto err_cleanup_desc_srcu;
|
||||
goto err_remove_from_list;
|
||||
|
||||
for (desc_index = 0; desc_index < gc->ngpio; desc_index++) {
|
||||
struct gpio_desc *desc = &gdev->descs[desc_index];
|
||||
@ -1248,10 +1248,6 @@ err_remove_of_chip:
|
||||
of_gpiochip_remove(gc);
|
||||
err_free_valid_mask:
|
||||
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:
|
||||
scoped_guard(mutex, &gpio_devices_lock)
|
||||
list_del_rcu(&gdev->list);
|
||||
@ -1261,6 +1257,10 @@ err_remove_from_list:
|
||||
gpio_device_put(gdev);
|
||||
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:
|
||||
kfree_const(gdev->label);
|
||||
err_free_descs:
|
||||
@ -4508,45 +4508,41 @@ void gpiod_remove_hogs(struct gpiod_hog *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;
|
||||
struct gpiod_lookup_table *table;
|
||||
|
||||
list_for_each_entry(table, &gpio_lookup_list, list) {
|
||||
if (table->dev_id && dev_id) {
|
||||
/*
|
||||
* Valid strings on both ends, must be identical to have
|
||||
* a match
|
||||
*/
|
||||
if (!strcmp(table->dev_id, dev_id))
|
||||
return table;
|
||||
} else {
|
||||
/*
|
||||
* One of the pointers is NULL, so both must be to have
|
||||
* a match
|
||||
*/
|
||||
if (dev_id == table->dev_id)
|
||||
return table;
|
||||
}
|
||||
lockdep_assert_held(&gpio_lookup_lock);
|
||||
|
||||
if (table->dev_id && dev_id) {
|
||||
/*
|
||||
* Valid strings on both ends, must be identical to have
|
||||
* a match
|
||||
*/
|
||||
if (!strcmp(table->dev_id, dev_id))
|
||||
return true;
|
||||
} else {
|
||||
/*
|
||||
* One of the pointers is NULL, so both must be to have
|
||||
* a match
|
||||
*/
|
||||
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,
|
||||
unsigned int idx, unsigned long *flags)
|
||||
static struct gpio_desc *gpio_desc_table_match(struct device *dev, const char *con_id,
|
||||
unsigned int idx, unsigned long *flags,
|
||||
struct gpiod_lookup_table *table)
|
||||
{
|
||||
struct gpio_desc *desc = ERR_PTR(-ENOENT);
|
||||
struct gpiod_lookup_table *table;
|
||||
struct gpio_desc *desc;
|
||||
struct gpiod_lookup *p;
|
||||
struct gpio_chip *gc;
|
||||
|
||||
guard(mutex)(&gpio_lookup_lock);
|
||||
|
||||
table = gpiod_find_lookup_table(dev);
|
||||
if (!table)
|
||||
return desc;
|
||||
lockdep_assert_held(&gpio_lookup_lock);
|
||||
|
||||
for (p = &table->table[0]; p->key; p++) {
|
||||
/* 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 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)
|
||||
@ -4610,14 +4629,16 @@ static int platform_gpio_count(struct device *dev, const char *con_id)
|
||||
unsigned int count = 0;
|
||||
|
||||
scoped_guard(mutex, &gpio_lookup_lock) {
|
||||
table = gpiod_find_lookup_table(dev);
|
||||
if (!table)
|
||||
return -ENOENT;
|
||||
list_for_each_entry(table, &gpio_lookup_list, list) {
|
||||
if (!gpiod_match_lookup_table(dev, table))
|
||||
continue;
|
||||
|
||||
for (p = &table->table[0]; p->key; p++) {
|
||||
if ((con_id && p->con_id && !strcmp(con_id, p->con_id)) ||
|
||||
(!con_id && !p->con_id))
|
||||
count++;
|
||||
for (p = &table->table[0]; p->key; p++) {
|
||||
if ((con_id && p->con_id &&
|
||||
!strcmp(con_id, p->con_id)) ||
|
||||
(!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
|
||||
* 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)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user