linux/drivers/gpio/gpiolib-shared.c

559 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Linaro Ltd.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/auxiliary_bus.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/fwnode.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/idr.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/overflow.h>
#include <linux/printk.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "gpiolib.h"
#include "gpiolib-shared.h"
/* Represents a single reference to a GPIO pin. */
struct gpio_shared_ref {
struct list_head list;
/* Firmware node associated with this GPIO's consumer. */
struct fwnode_handle *fwnode;
/* GPIO flags this consumer uses for the request. */
enum gpiod_flags flags;
char *con_id;
int dev_id;
struct auxiliary_device adev;
struct gpiod_lookup_table *lookup;
};
/* Represents a single GPIO pin. */
struct gpio_shared_entry {
struct list_head list;
/* Firmware node associated with the GPIO controller. */
struct fwnode_handle *fwnode;
/* Hardware offset of the GPIO within its chip. */
unsigned int offset;
/* Index in the property value array. */
size_t index;
struct gpio_shared_desc *shared_desc;
struct kref ref;
struct list_head refs;
};
static LIST_HEAD(gpio_shared_list);
static DEFINE_MUTEX(gpio_shared_lock);
static DEFINE_IDA(gpio_shared_ida);
static struct gpio_shared_entry *
gpio_shared_find_entry(struct fwnode_handle *controller_node,
unsigned int offset)
{
struct gpio_shared_entry *entry;
list_for_each_entry(entry, &gpio_shared_list, list) {
if (entry->fwnode == controller_node && entry->offset == offset)
return entry;
}
return NULL;
}
#if IS_ENABLED(CONFIG_OF)
static int gpio_shared_of_traverse(struct device_node *curr)
{
struct gpio_shared_entry *entry;
size_t con_id_len, suffix_len;
struct fwnode_handle *fwnode;
struct of_phandle_args args;
struct property *prop;
unsigned int offset;
const char *suffix;
int ret, count, i;
for_each_property_of_node(curr, prop) {
/*
* The standard name for a GPIO property is "foo-gpios"
* or "foo-gpio". Some bindings also use "gpios" or "gpio".
* There are some legacy device-trees which have a different
* naming convention and for which we have rename quirks in
* place in gpiolib-of.c. I don't think any of them require
* support for shared GPIOs so for now let's just ignore
* them. We can always just export the quirk list and
* iterate over it here.
*/
if (!strends(prop->name, "-gpios") &&
!strends(prop->name, "-gpio") &&
strcmp(prop->name, "gpios") != 0 &&
strcmp(prop->name, "gpio") != 0)
continue;
count = of_count_phandle_with_args(curr, prop->name,
"#gpio-cells");
if (count <= 0)
continue;
for (i = 0; i < count; i++) {
struct device_node *np __free(device_node) = NULL;
ret = of_parse_phandle_with_args(curr, prop->name,
"#gpio-cells", i,
&args);
if (ret)
continue;
np = args.np;
if (!of_property_present(np, "gpio-controller"))
continue;
/*
* We support 1, 2 and 3 cell GPIO bindings in the
* kernel currently. There's only one old MIPS dts that
* has a one-cell binding but there's no associated
* consumer so it may as well be an error. There don't
* seem to be any 3-cell users of non-exclusive GPIOs,
* so we can skip this as well. Let's occupy ourselves
* with the predominant 2-cell binding with the first
* cell indicating the hardware offset of the GPIO and
* the second defining the GPIO flags of the request.
*/
if (args.args_count != 2)
continue;
fwnode = of_fwnode_handle(args.np);
offset = args.args[0];
entry = gpio_shared_find_entry(fwnode, offset);
if (!entry) {
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
entry->fwnode = fwnode_handle_get(fwnode);
entry->offset = offset;
entry->index = count;
INIT_LIST_HEAD(&entry->refs);
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];
if (strends(prop->name, "gpios"))
suffix = "-gpios";
else if (strends(prop->name, "gpio"))
suffix = "-gpio";
else
suffix = NULL;
if (!suffix)
continue;
/* 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)
return -ENOMEM;
con_id_len = strlen(ref->con_id);
suffix_len = strlen(suffix);
ref->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);
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);
}
}
for_each_child_of_node_scoped(curr, child) {
ret = gpio_shared_of_traverse(child);
if (ret)
return ret;
}
return 0;
}
static int gpio_shared_of_scan(void)
{
return gpio_shared_of_traverse(of_root);
}
#else
static int gpio_shared_of_scan(void)
{
return 0;
}
#endif /* CONFIG_OF */
static void gpio_shared_adev_release(struct device *dev)
{
}
static int gpio_shared_make_adev(struct gpio_device *gdev,
struct gpio_shared_ref *ref)
{
struct auxiliary_device *adev = &ref->adev;
int ret;
lockdep_assert_held(&gpio_shared_lock);
memset(adev, 0, sizeof(*adev));
adev->id = ref->dev_id;
adev->name = "proxy";
adev->dev.parent = gdev->dev.parent;
adev->dev.release = gpio_shared_adev_release;
ret = auxiliary_device_init(adev);
if (ret)
return ret;
ret = auxiliary_device_add(adev);
if (ret) {
auxiliary_device_uninit(adev);
return ret;
}
pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n",
dev_name(&adev->dev), gpio_device_get_label(gdev));
return 0;
}
int gpio_shared_add_proxy_lookup(struct device *consumer, unsigned long lflags)
{
const char *dev_id = dev_name(consumer);
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;
guard(mutex)(&gpio_shared_lock);
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
if (!device_match_fwnode(consumer, ref->fwnode))
continue;
/* We've already done that on a previous request. */
if (ref->lookup)
return 0;
char *key __free(kfree) =
kasprintf(GFP_KERNEL,
KBUILD_MODNAME ".proxy.%u",
ref->adev.id);
if (!key)
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");
lookup->dev_id = dev_id;
lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0,
ref->con_id, lflags);
gpiod_add_lookup_table(no_free_ptr(lookup));
return 0;
}
}
/* We warn here because this can only happen if the programmer borked. */
WARN_ON(1);
return -ENOENT;
}
static void gpio_shared_remove_adev(struct auxiliary_device *adev)
{
lockdep_assert_held(&gpio_shared_lock);
auxiliary_device_uninit(adev);
auxiliary_device_delete(adev);
}
int gpio_device_setup_shared(struct gpio_device *gdev)
{
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
unsigned long *flags;
int ret;
guard(mutex)(&gpio_shared_lock);
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
if (gdev->dev.parent == &ref->adev.dev) {
/*
* This is a shared GPIO proxy. Mark its
* descriptor as such and return here.
*/
__set_bit(GPIOD_FLAG_SHARED_PROXY,
&gdev->descs[0].flags);
return 0;
}
}
}
/*
* This is not a shared GPIO proxy but it still may be the device
* exposing shared pins. Find them and create the proxy devices.
*/
list_for_each_entry(entry, &gpio_shared_list, list) {
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
continue;
if (list_count_nodes(&entry->refs) <= 1)
continue;
flags = &gdev->descs[entry->offset].flags;
__set_bit(GPIOD_FLAG_SHARED, flags);
/*
* Shared GPIOs are not requested via the normal path. Make
* them inaccessible to anyone even before we register the
* chip.
*/
__set_bit(GPIOD_FLAG_REQUESTED, flags);
pr_debug("GPIO %u owned by %s is shared by multiple consumers\n",
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));
ret = gpio_shared_make_adev(gdev, ref);
if (ret)
return ret;
}
}
return 0;
}
void gpio_device_teardown_shared(struct gpio_device *gdev)
{
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
guard(mutex)(&gpio_shared_lock);
list_for_each_entry(entry, &gpio_shared_list, list) {
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
continue;
list_for_each_entry(ref, &entry->refs, list) {
gpiod_remove_lookup_table(ref->lookup);
kfree(ref->lookup->table[0].key);
kfree(ref->lookup);
ref->lookup = NULL;
gpio_shared_remove_adev(&ref->adev);
}
}
}
static void gpio_shared_release(struct kref *kref)
{
struct gpio_shared_entry *entry =
container_of(kref, struct gpio_shared_entry, ref);
struct gpio_shared_desc *shared_desc = entry->shared_desc;
guard(mutex)(&gpio_shared_lock);
gpio_device_put(shared_desc->desc->gdev);
if (shared_desc->can_sleep)
mutex_destroy(&shared_desc->mutex);
kfree(shared_desc);
entry->shared_desc = NULL;
}
static void gpiod_shared_put(void *data)
{
struct gpio_shared_entry *entry = data;
lockdep_assert_not_held(&gpio_shared_lock);
kref_put(&entry->ref, gpio_shared_release);
}
static struct gpio_shared_desc *
gpiod_shared_desc_create(struct gpio_shared_entry *entry)
{
struct gpio_shared_desc *shared_desc;
struct gpio_device *gdev;
shared_desc = kzalloc(sizeof(*shared_desc), GFP_KERNEL);
if (!shared_desc)
return ERR_PTR(-ENOMEM);
gdev = gpio_device_find_by_fwnode(entry->fwnode);
if (!gdev) {
kfree(shared_desc);
return ERR_PTR(-EPROBE_DEFER);
}
shared_desc->desc = &gdev->descs[entry->offset];
shared_desc->can_sleep = gpiod_cansleep(shared_desc->desc);
if (shared_desc->can_sleep)
mutex_init(&shared_desc->mutex);
else
spin_lock_init(&shared_desc->spinlock);
return shared_desc;
}
static struct gpio_shared_entry *gpiod_shared_find(struct auxiliary_device *adev)
{
struct gpio_shared_desc *shared_desc;
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
guard(mutex)(&gpio_shared_lock);
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
if (adev != &ref->adev)
continue;
if (entry->shared_desc) {
kref_get(&entry->ref);
return entry;
}
shared_desc = gpiod_shared_desc_create(entry);
if (IS_ERR(shared_desc))
return ERR_CAST(shared_desc);
kref_init(&entry->ref);
entry->shared_desc = shared_desc;
pr_debug("Device %s acquired a reference to the shared GPIO %u owned by %s\n",
dev_name(&adev->dev), gpiod_hwgpio(shared_desc->desc),
gpio_device_get_label(shared_desc->desc->gdev));
return entry;
}
}
return ERR_PTR(-ENOENT);
}
struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev)
{
struct gpio_shared_entry *entry;
int ret;
entry = gpiod_shared_find(to_auxiliary_dev(dev));
if (IS_ERR(entry))
return ERR_CAST(entry);
ret = devm_add_action_or_reset(dev, gpiod_shared_put, entry);
if (ret)
return ERR_PTR(ret);
return entry->shared_desc;
}
EXPORT_SYMBOL_GPL(devm_gpiod_shared_get);
static void gpio_shared_drop_ref(struct gpio_shared_ref *ref)
{
list_del(&ref->list);
kfree(ref->con_id);
ida_free(&gpio_shared_ida, ref->dev_id);
fwnode_handle_put(ref->fwnode);
kfree(ref);
}
static void gpio_shared_drop_entry(struct gpio_shared_entry *entry)
{
list_del(&entry->list);
fwnode_handle_put(entry->fwnode);
kfree(entry);
}
/*
* This is only called if gpio_shared_init() fails so it's in fact __init and
* not __exit.
*/
static void __init gpio_shared_teardown(void)
{
struct gpio_shared_entry *entry, *epos;
struct gpio_shared_ref *ref, *rpos;
list_for_each_entry_safe(entry, epos, &gpio_shared_list, list) {
list_for_each_entry_safe(ref, rpos, &entry->refs, list)
gpio_shared_drop_ref(ref);
gpio_shared_drop_entry(entry);
}
}
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)
continue;
gpio_shared_drop_ref(list_first_entry(&entry->refs,
struct gpio_shared_ref,
list));
gpio_shared_drop_entry(entry);
}
}
static int __init gpio_shared_init(void)
{
int ret;
/* Right now, we only support OF-based systems. */
ret = gpio_shared_of_scan();
if (ret) {
gpio_shared_teardown();
pr_err("Failed to scan OF nodes for shared GPIOs: %d\n", ret);
return ret;
}
gpio_shared_free_exclusive();
pr_debug("Finished scanning firmware nodes for shared GPIOs\n");
return 0;
}
postcore_initcall(gpio_shared_init);