diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-07-16 03:53:24 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-07-16 03:53:24 +0300 |
commit | b3c0eccb485404d3ea5eaae483b1a2e9e2134d21 (patch) | |
tree | 758c02cabc8614ae879704bd7df68f9f59295ad2 /drivers/gpio | |
parent | 3f32ab146c557f0fd9060b03003d0d4b2815968a (diff) | |
parent | dfda97e37de4c2fa4a079ae77737c6b9ed021f79 (diff) | |
download | linux-b3c0eccb485404d3ea5eaae483b1a2e9e2134d21.tar.xz |
Merge tag 'gpio-updates-for-v6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux
Pull gpio updates from Bartosz Golaszewski:
"The majority of added lines are two new modules: the GPIO virtual
consumer module that improves our ability to add automated tests for
the kernel API and the "sloppy" logic analyzer module that uses the
GPIO API to implement a coarse-grained debugging tool for useful for
remote development.
Other than that we have the usual assortment of various driver
extensions, improvements to the core GPIO code, DT-bindings and other
documentation updates as well as an extension to the interrupt
simulator:
GPIOLIB core:
- rework kfifo handling rework in the character device code
- improve the labeling of GPIOs requested as interrupts and show more
info on interrupt-only GPIOs in debugfs
- remove unused APIs
- unexport interfaces that are only used from the core GPIO code
- drop the return value from gpiochip_set_desc_names() as it cannot
fail
- move a string array definition out of a header and into a specific
compilation unit
- convert the last user of gpiochip_get_desc() other than GPIO core
to using a safer alternative
- use array_index_nospec() where applicable
New drivers:
- add a "virtual GPIO consumer" module that allows requesting GPIOs
from actual hardware and driving tests of the in-kernel GPIO API
from user-space over debugfs
- add a GPIO-based "sloppy" logic analyzer module useful for "first
glance" debugging on remote boards
Driver improvements:
- add support for a new model to gpio-pca953x
- lock GPIOs as interrupts in gpio-sim when the lines are requested
as irqs via the simulator domain + some other minor improvements
- improve error reporting in gpio-syscon
- convert gpio-ath79 to using dynamic GPIO base and range
- use pcibios_err_to_errno() for converting PCIBIOS error codes to
errno vaues in gpio-amd8111 and gpio-rdc321x
- allow building gpio-brcmstb for the BCM2835 architecture
DT bindings:
- convert DT bindings for lsi,zevio, mpc8xxx, and atmel to DT schema
- document new properties for aspeed,gpio, fsl,qoriq-gpio and
gpio-vf610
- document new compatibles for pca953x and fsl,qoriq-gpio
Documentation:
- document stricter behavior of the GPIO character device uAPI with
regards to reconfiguring requested line without direction set
- clarify the effect of the active-low flag on line values and edges
- remove documentation for the legacy GPIO API in order to stop
tempting people to use it
- document the preference for using pread() for reading edge events
in the sysfs API
Other:
- add an extended initializer to the interrupt simulator allowing to
specify a number of callbacks callers can use to be notified about
irqs being requested and released"
* tag 'gpio-updates-for-v6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (41 commits)
gpio: mc33880: Convert comma to semicolon
gpio: virtuser: actually use the "trimmed" local variable
dt-bindings: gpio: convert Atmel GPIO to json-schema
gpio: virtuser: new virtual testing driver for the GPIO API
dt-bindings: gpio: vf610: Allow gpio-line-names to be set
gpio: sim: lock GPIOs as interrupts when they are requested
genirq/irq_sim: add an extended irq_sim initializer
dt-bindings: gpio: fsl,qoriq-gpio: Add compatible string fsl,ls1046a-gpio
gpiolib: unexport gpiochip_get_desc()
gpio: add sloppy logic analyzer using polling
Documentation: gpio: Reconfiguration with unset direction (uAPI v2)
Documentation: gpio: Reconfiguration with unset direction (uAPI v1)
dt-bindings: gpio: fsl,qoriq-gpio: add common property gpio-line-names
gpio: ath79: convert to dynamic GPIO base allocation
pinctrl: da9062: replace gpiochip_get_desc() with gpio_device_get_desc()
gpiolib: put gpio_suffixes in a single compilation unit
Documentation: gpio: Clarify effect of active low flag on line edges
Documentation: gpio: Clarify effect of active low flag on line values
gpiolib: Remove data-less gpiochip_add() function
gpio: sim: use devm_mutex_init()
...
Diffstat (limited to 'drivers/gpio')
-rw-r--r-- | drivers/gpio/Kconfig | 33 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-amd8111.c | 4 | ||||
-rw-r--r-- | drivers/gpio/gpio-ath79.c | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-mc33880.c | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-pca953x.c | 1 | ||||
-rw-r--r-- | drivers/gpio/gpio-rdc321x.c | 6 | ||||
-rw-r--r-- | drivers/gpio/gpio-sim.c | 60 | ||||
-rw-r--r-- | drivers/gpio/gpio-sloppy-logic-analyzer.c | 344 | ||||
-rw-r--r-- | drivers/gpio/gpio-syscon.c | 27 | ||||
-rw-r--r-- | drivers/gpio/gpio-virtuser.c | 1807 | ||||
-rw-r--r-- | drivers/gpio/gpiolib-acpi.c | 4 | ||||
-rw-r--r-- | drivers/gpio/gpiolib-cdev.c | 80 | ||||
-rw-r--r-- | drivers/gpio/gpiolib-of.c | 4 | ||||
-rw-r--r-- | drivers/gpio/gpiolib.c | 36 | ||||
-rw-r--r-- | drivers/gpio/gpiolib.h | 4 |
16 files changed, 2312 insertions, 104 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1c28a48915bb..58f43bcced7c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -219,7 +219,7 @@ config GPIO_BCM_XGS_IPROC config GPIO_BRCMSTB tristate "BRCMSTB GPIO support" default y if (ARCH_BRCMSTB || BMIPS_GENERIC) - depends on OF_GPIO && (ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST) + depends on OF_GPIO && (ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST) select GPIO_GENERIC select IRQ_DOMAIN help @@ -1891,4 +1891,35 @@ config GPIO_SIM endmenu +menu "GPIO Debugging utilities" + +config GPIO_SLOPPY_LOGIC_ANALYZER + tristate "Sloppy GPIO logic analyzer" + depends on (GPIOLIB || COMPILE_TEST) && CPUSETS && DEBUG_FS && EXPERT + help + This option enables support for a sloppy logic analyzer using polled + GPIOs. Use the 'tools/gpio/gpio-sloppy-logic-analyzer' script with + this driver. The script will make it easier to use and will also + isolate a CPU for the polling task. Note that this is a last resort + analyzer which can be affected by latencies, non-deterministic code + paths, or NMIs. However, for e.g. remote development, it may be useful + to get a first view and aid further debugging. + + If this driver is built as a module it will be called + 'gpio-sloppy-logic-analyzer'. + +config GPIO_VIRTUSER + tristate "GPIO Virtual User Testing Module" + select DEBUG_FS + select CONFIGFS_FS + select IRQ_WORK + help + Say Y here to enable the configurable, configfs-based virtual GPIO + consumer testing driver. + + This driver is aimed as a helper in spotting any regressions in + hot-unplug handling in GPIOLIB. + +endmenu + endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index e2a53013780e..64dd6d9d730d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -150,6 +150,7 @@ obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o obj-$(CONFIG_GPIO_SIM) += gpio-sim.o obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o +obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o obj-$(CONFIG_GPIO_SPRD) += gpio-sprd.o @@ -181,6 +182,7 @@ obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o +obj-$(CONFIG_GPIO_VIRTUSER) += gpio-virtuser.o obj-$(CONFIG_GPIO_VIRTIO) += gpio-virtio.o obj-$(CONFIG_GPIO_VISCONTI) += gpio-visconti.o obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o diff --git a/drivers/gpio/gpio-amd8111.c b/drivers/gpio/gpio-amd8111.c index 6f3ded619c8b..3377667a28de 100644 --- a/drivers/gpio/gpio-amd8111.c +++ b/drivers/gpio/gpio-amd8111.c @@ -195,8 +195,10 @@ static int __init amd_gpio_init(void) found: err = pci_read_config_dword(pdev, 0x58, &gp.pmbase); - if (err) + if (err) { + err = pcibios_err_to_errno(err); goto out; + } err = -EIO; gp.pmbase &= 0x0000FF00; if (gp.pmbase == 0) diff --git a/drivers/gpio/gpio-ath79.c b/drivers/gpio/gpio-ath79.c index f0c0c0f77eb0..6211d99a5770 100644 --- a/drivers/gpio/gpio-ath79.c +++ b/drivers/gpio/gpio-ath79.c @@ -273,8 +273,6 @@ static int ath79_gpio_probe(struct platform_device *pdev) dev_err(dev, "bgpio_init failed\n"); return err; } - /* Use base 0 to stay compatible with legacy platforms */ - ctrl->gc.base = 0; /* Optional interrupt setup */ if (!np || of_property_read_bool(np, "interrupt-controller")) { diff --git a/drivers/gpio/gpio-mc33880.c b/drivers/gpio/gpio-mc33880.c index 94f6fefc011b..5fb357d7b78a 100644 --- a/drivers/gpio/gpio-mc33880.c +++ b/drivers/gpio/gpio-mc33880.c @@ -99,7 +99,7 @@ static int mc33880_probe(struct spi_device *spi) mc->spi = spi; - mc->chip.label = DRIVER_NAME, + mc->chip.label = DRIVER_NAME; mc->chip.set = mc33880_set; mc->chip.base = pdata->base; mc->chip.ngpio = PIN_NUMBER; diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index 732a6964748c..8baf3edd5274 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -1315,6 +1315,7 @@ static const struct of_device_id pca953x_dt_ids[] = { { .compatible = "ti,tca6408", .data = OF_953X( 8, PCA_INT), }, { .compatible = "ti,tca6416", .data = OF_953X(16, PCA_INT), }, { .compatible = "ti,tca6424", .data = OF_953X(24, PCA_INT), }, + { .compatible = "ti,tca9535", .data = OF_953X(16, PCA_INT), }, { .compatible = "ti,tca9538", .data = OF_953X( 8, PCA_INT), }, { .compatible = "ti,tca9539", .data = OF_953X(16, PCA_INT), }, diff --git a/drivers/gpio/gpio-rdc321x.c b/drivers/gpio/gpio-rdc321x.c index 01ed2517e9fd..ec7fb9220a47 100644 --- a/drivers/gpio/gpio-rdc321x.c +++ b/drivers/gpio/gpio-rdc321x.c @@ -102,7 +102,7 @@ static int rdc_gpio_config(struct gpio_chip *chip, unlock: spin_unlock(&gpch->lock); - return err; + return pcibios_err_to_errno(err); } /* configure GPIO pin as input */ @@ -170,13 +170,13 @@ static int rdc321x_gpio_probe(struct platform_device *pdev) rdc321x_gpio_dev->reg1_data_base, &rdc321x_gpio_dev->data_reg[0]); if (err) - return err; + return pcibios_err_to_errno(err); err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev, rdc321x_gpio_dev->reg2_data_base, &rdc321x_gpio_dev->data_reg[1]); if (err) - return err; + return pcibios_err_to_errno(err); dev_info(&pdev->dev, "registering %d GPIOs\n", rdc321x_gpio_dev->chip.ngpio); diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index 2ed5cbe7c8a8..dcca1d7f173e 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/array_size.h> #include <linux/bitmap.h> #include <linux/cleanup.h> #include <linux/completion.h> @@ -20,7 +21,6 @@ #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/irq_sim.h> -#include <linux/kernel.h> #include <linux/list.h> #include <linux/lockdep.h> #include <linux/minmax.h> @@ -227,6 +227,27 @@ static void gpio_sim_free(struct gpio_chip *gc, unsigned int offset) } } +static int gpio_sim_irq_requested(struct irq_domain *domain, + irq_hw_number_t hwirq, void *data) +{ + struct gpio_sim_chip *chip = data; + + return gpiochip_lock_as_irq(&chip->gc, hwirq); +} + +static void gpio_sim_irq_released(struct irq_domain *domain, + irq_hw_number_t hwirq, void *data) +{ + struct gpio_sim_chip *chip = data; + + gpiochip_unlock_as_irq(&chip->gc, hwirq); +} + +static const struct irq_sim_ops gpio_sim_irq_sim_ops = { + .irq_sim_irq_requested = gpio_sim_irq_requested, + .irq_sim_irq_released = gpio_sim_irq_released, +}; + static void gpio_sim_dbg_show(struct seq_file *seq, struct gpio_chip *gc) { struct gpio_sim_chip *chip = gpiochip_get_data(gc); @@ -308,13 +329,6 @@ static ssize_t gpio_sim_sysfs_pull_store(struct device *dev, return len; } -static void gpio_sim_mutex_destroy(void *data) -{ - struct mutex *lock = data; - - mutex_destroy(lock); -} - static void gpio_sim_put_device(void *data) { struct device *dev = data; @@ -450,7 +464,9 @@ static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev) if (!chip->pull_map) return -ENOMEM; - chip->irq_sim = devm_irq_domain_create_sim(dev, swnode, num_lines); + chip->irq_sim = devm_irq_domain_create_sim_full(dev, swnode, num_lines, + &gpio_sim_irq_sim_ops, + chip); if (IS_ERR(chip->irq_sim)) return PTR_ERR(chip->irq_sim); @@ -458,9 +474,7 @@ static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev) if (ret) return ret; - mutex_init(&chip->lock); - ret = devm_add_action_or_reset(dev, gpio_sim_mutex_destroy, - &chip->lock); + ret = devm_mutex_init(dev, &chip->lock); if (ret) return ret; @@ -581,19 +595,19 @@ static int gpio_sim_bus_notifier_call(struct notifier_block *nb, snprintf(devname, sizeof(devname), "gpio-sim.%u", simdev->id); - if (strcmp(dev_name(dev), devname) == 0) { - 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; + if (!device_match_name(dev, devname)) + return NOTIFY_DONE; - complete(&simdev->probe_completion); - return NOTIFY_OK; - } + 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_DONE; + return NOTIFY_OK; } static struct gpio_sim_device *to_gpio_sim_device(struct config_item *item) diff --git a/drivers/gpio/gpio-sloppy-logic-analyzer.c b/drivers/gpio/gpio-sloppy-logic-analyzer.c new file mode 100644 index 000000000000..aed6d1f6cfc3 --- /dev/null +++ b/drivers/gpio/gpio-sloppy-logic-analyzer.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sloppy logic analyzer using GPIOs (to be run on an isolated CPU) + * + * Use the 'gpio-sloppy-logic-analyzer' script in the 'tools/gpio' folder for + * easier usage and further documentation. Note that this is a last resort + * analyzer which can be affected by latencies and non-deterministic code + * paths. However, for e.g. remote development, it may be useful to get a first + * view and aid further debugging. + * + * Copyright (C) Wolfram Sang <wsa@sang-engineering.com> + * Copyright (C) Renesas Electronics Corporation + */ + +#include <linux/ctype.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/ktime.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/sizes.h> +#include <linux/timekeeping.h> +#include <linux/types.h> +#include <linux/vmalloc.h> + +#define GPIO_LA_NAME "gpio-sloppy-logic-analyzer" +#define GPIO_LA_DEFAULT_BUF_SIZE SZ_256K +/* can be increased but then we need to extend the u8 buffers */ +#define GPIO_LA_MAX_PROBES 8 +#define GPIO_LA_NUM_TESTS 1024 + +struct gpio_la_poll_priv { + struct mutex blob_lock; /* serialize access to the blob (data) */ + u32 buf_idx; + struct gpio_descs *descs; + unsigned long delay_ns; + unsigned long acq_delay; + struct debugfs_blob_wrapper blob; + struct dentry *debug_dir; + struct dentry *blob_dent; + struct debugfs_blob_wrapper meta; + struct device *dev; + unsigned int trig_len; + u8 *trig_data; +}; + +static struct dentry *gpio_la_poll_debug_dir; + +static __always_inline int gpio_la_get_array(struct gpio_descs *d, unsigned long *sptr) +{ + int ret; + + ret = gpiod_get_array_value(d->ndescs, d->desc, d->info, sptr); + if (ret == 0 && fatal_signal_pending(current)) + ret = -EINTR; + + return ret; +} + +static int fops_capture_set(void *data, u64 val) +{ + struct gpio_la_poll_priv *priv = data; + u8 *la_buf = priv->blob.data; + unsigned long state = 0; /* zeroed because GPIO arrays are bitfields */ + unsigned long delay; + ktime_t start_time; + unsigned int i; + int ret; + + if (!val) + return 0; + + if (!la_buf) + return -ENOMEM; + + if (!priv->delay_ns) + return -EINVAL; + + mutex_lock(&priv->blob_lock); + if (priv->blob_dent) { + debugfs_remove(priv->blob_dent); + priv->blob_dent = NULL; + } + + priv->buf_idx = 0; + + local_irq_disable(); + preempt_disable_notrace(); + + /* Measure delay of reading GPIOs */ + start_time = ktime_get(); + for (i = 0; i < GPIO_LA_NUM_TESTS; i++) { + ret = gpio_la_get_array(priv->descs, &state); + if (ret) + goto out; + } + + priv->acq_delay = ktime_sub(ktime_get(), start_time) / GPIO_LA_NUM_TESTS; + if (priv->delay_ns < priv->acq_delay) { + ret = -ERANGE; + goto out; + } + + delay = priv->delay_ns - priv->acq_delay; + + /* Wait for triggers */ + for (i = 0; i < priv->trig_len; i += 2) { + do { + ret = gpio_la_get_array(priv->descs, &state); + if (ret) + goto out; + + ndelay(delay); + } while ((state & priv->trig_data[i]) != priv->trig_data[i + 1]); + } + + /* With triggers, final state is also the first sample */ + if (priv->trig_len) + la_buf[priv->buf_idx++] = state; + + /* Sample */ + while (priv->buf_idx < priv->blob.size) { + ret = gpio_la_get_array(priv->descs, &state); + if (ret) + goto out; + + la_buf[priv->buf_idx++] = state; + ndelay(delay); + } +out: + preempt_enable_notrace(); + local_irq_enable(); + if (ret) + dev_err(priv->dev, "couldn't read GPIOs: %d\n", ret); + + kfree(priv->trig_data); + priv->trig_data = NULL; + priv->trig_len = 0; + + priv->blob_dent = debugfs_create_blob("sample_data", 0400, priv->debug_dir, &priv->blob); + mutex_unlock(&priv->blob_lock); + + return ret; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_capture, NULL, fops_capture_set, "%llu\n"); + +static int fops_buf_size_get(void *data, u64 *val) +{ + struct gpio_la_poll_priv *priv = data; + + *val = priv->blob.size; + + return 0; +} + +static int fops_buf_size_set(void *data, u64 val) +{ + struct gpio_la_poll_priv *priv = data; + int ret = 0; + void *p; + + if (!val) + return -EINVAL; + + mutex_lock(&priv->blob_lock); + + vfree(priv->blob.data); + p = vzalloc(val); + if (!p) { + val = 0; + ret = -ENOMEM; + } + + priv->blob.data = p; + priv->blob.size = val; + + mutex_unlock(&priv->blob_lock); + return ret; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_buf_size, fops_buf_size_get, fops_buf_size_set, "%llu\n"); + +static int trigger_open(struct inode *inode, struct file *file) +{ + return single_open(file, NULL, inode->i_private); +} + +static ssize_t trigger_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *offset) +{ + struct seq_file *m = file->private_data; + struct gpio_la_poll_priv *priv = m->private; + char *buf; + + /* upper limit is arbitrary but should be less than PAGE_SIZE */ + if (count > 2048 || count & 1) + return -EINVAL; + + buf = memdup_user(ubuf, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + priv->trig_data = buf; + priv->trig_len = count; + + return count; +} + +static const struct file_operations fops_trigger = { + .owner = THIS_MODULE, + .open = trigger_open, + .write = trigger_write, + .llseek = no_llseek, + .release = single_release, +}; + +static int gpio_la_poll_probe(struct platform_device *pdev) +{ + struct gpio_la_poll_priv *priv; + struct device *dev = &pdev->dev; + const char *devname = dev_name(dev); + const char *gpio_names[GPIO_LA_MAX_PROBES]; + char *meta = NULL; + unsigned int i, meta_len = 0; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + devm_mutex_init(dev, &priv->blob_lock); + + fops_buf_size_set(priv, GPIO_LA_DEFAULT_BUF_SIZE); + + priv->descs = devm_gpiod_get_array(dev, "probe", GPIOD_IN); + if (IS_ERR(priv->descs)) + return PTR_ERR(priv->descs); + + /* artificial limit to keep 1 byte per sample for now */ + if (priv->descs->ndescs > GPIO_LA_MAX_PROBES) + return -EFBIG; + + ret = device_property_read_string_array(dev, "probe-names", gpio_names, + priv->descs->ndescs); + if (ret >= 0 && ret != priv->descs->ndescs) + ret = -EBADR; + if (ret < 0) + return dev_err_probe(dev, ret, "error naming the GPIOs"); + + for (i = 0; i < priv->descs->ndescs; i++) { + unsigned int add_len; + char *new_meta, *consumer_name; + + if (gpiod_cansleep(priv->descs->desc[i])) + return -EREMOTE; + + consumer_name = kasprintf(GFP_KERNEL, "%s: %s", devname, gpio_names[i]); + if (!consumer_name) + return -ENOMEM; + gpiod_set_consumer_name(priv->descs->desc[i], consumer_name); + kfree(consumer_name); + + /* '10' is length of 'probe00=\n\0' */ + add_len = strlen(gpio_names[i]) + 10; + + new_meta = devm_krealloc(dev, meta, meta_len + add_len, GFP_KERNEL); + if (!new_meta) + return -ENOMEM; + + meta = new_meta; + meta_len += snprintf(meta + meta_len, add_len, "probe%02u=%s\n", + i + 1, gpio_names[i]); + } + + platform_set_drvdata(pdev, priv); + priv->dev = dev; + + priv->meta.data = meta; + priv->meta.size = meta_len; + priv->debug_dir = debugfs_create_dir(devname, gpio_la_poll_debug_dir); + debugfs_create_blob("meta_data", 0400, priv->debug_dir, &priv->meta); + debugfs_create_ulong("delay_ns", 0600, priv->debug_dir, &priv->delay_ns); + debugfs_create_ulong("delay_ns_acquisition", 0400, priv->debug_dir, &priv->acq_delay); + debugfs_create_file_unsafe("buf_size", 0600, priv->debug_dir, priv, &fops_buf_size); + debugfs_create_file_unsafe("capture", 0200, priv->debug_dir, priv, &fops_capture); + debugfs_create_file_unsafe("trigger", 0200, priv->debug_dir, priv, &fops_trigger); + + return 0; +} + +static void gpio_la_poll_remove(struct platform_device *pdev) +{ + struct gpio_la_poll_priv *priv = platform_get_drvdata(pdev); + + mutex_lock(&priv->blob_lock); + debugfs_remove_recursive(priv->debug_dir); + mutex_unlock(&priv->blob_lock); +} + +static const struct of_device_id gpio_la_poll_of_match[] = { + { .compatible = GPIO_LA_NAME }, + { } +}; +MODULE_DEVICE_TABLE(of, gpio_la_poll_of_match); + +static struct platform_driver gpio_la_poll_device_driver = { + .probe = gpio_la_poll_probe, + .remove_new = gpio_la_poll_remove, + .driver = { + .name = GPIO_LA_NAME, + .of_match_table = gpio_la_poll_of_match, + } +}; + +static int __init gpio_la_poll_init(void) +{ + gpio_la_poll_debug_dir = debugfs_create_dir(GPIO_LA_NAME, NULL); + + return platform_driver_register(&gpio_la_poll_device_driver); +} +/* + * Non-strict pin controllers can read GPIOs while being muxed to something else. + * To support that, we need to claim GPIOs before further pinmuxing happens. So, + * we probe early using 'late_initcall' + */ +late_initcall(gpio_la_poll_init); + +static void __exit gpio_la_poll_exit(void) +{ + platform_driver_unregister(&gpio_la_poll_device_driver); + debugfs_remove_recursive(gpio_la_poll_debug_dir); +} +module_exit(gpio_la_poll_exit); + +MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>"); +MODULE_DESCRIPTION("Sloppy logic analyzer using GPIOs"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-syscon.c b/drivers/gpio/gpio-syscon.c index 6e1a2581e6ae..3a90a3a1caea 100644 --- a/drivers/gpio/gpio-syscon.c +++ b/drivers/gpio/gpio-syscon.c @@ -208,6 +208,7 @@ static int syscon_gpio_probe(struct platform_device *pdev) struct syscon_gpio_priv *priv; struct device_node *np = dev->of_node; int ret; + bool use_parent_regmap = false; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -216,24 +217,28 @@ static int syscon_gpio_probe(struct platform_device *pdev) priv->data = of_device_get_match_data(dev); priv->syscon = syscon_regmap_lookup_by_phandle(np, "gpio,syscon-dev"); - if (IS_ERR(priv->syscon) && np->parent) + if (IS_ERR(priv->syscon) && np->parent) { priv->syscon = syscon_node_to_regmap(np->parent); + use_parent_regmap = true; + } if (IS_ERR(priv->syscon)) return PTR_ERR(priv->syscon); - ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1, - &priv->dreg_offset); - if (ret) - dev_err(dev, "can't read the data register offset!\n"); + if (!use_parent_regmap) { + ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1, + &priv->dreg_offset); + if (ret) + dev_err(dev, "can't read the data register offset!\n"); - priv->dreg_offset <<= 3; + priv->dreg_offset <<= 3; - ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2, - &priv->dir_reg_offset); - if (ret) - dev_dbg(dev, "can't read the dir register offset!\n"); + ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2, + &priv->dir_reg_offset); + if (ret) + dev_dbg(dev, "can't read the dir register offset!\n"); - priv->dir_reg_offset <<= 3; + priv->dir_reg_offset <<= 3; + } priv->chip.parent = dev; priv->chip.owner = THIS_MODULE; diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c new file mode 100644 index 000000000000..0e0d55da4f01 --- /dev/null +++ b/drivers/gpio/gpio-virtuser.c @@ -0,0 +1,1807 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Configurable virtual GPIO consumer module. + * + * Copyright (C) 2023-2024 Bartosz Golaszewski <bartosz.golaszewski@linaro.org> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/array_size.h> +#include <linux/atomic.h> +#include <linux/bitmap.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/configfs.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/machine.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/irq_work.h> +#include <linux/limits.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/string_helpers.h> +#include <linux/types.h> + +#define GPIO_VIRTUSER_NAME_BUF_LEN 32 + +static DEFINE_IDA(gpio_virtuser_ida); +static struct dentry *gpio_virtuser_dbg_root; + +struct gpio_virtuser_attr_data { + union { + struct gpio_desc *desc; + struct gpio_descs *descs; + }; + struct dentry *dbgfs_dir; +}; + +struct gpio_virtuser_line_array_data { + struct gpio_virtuser_attr_data ad; +}; + +struct gpio_virtuser_line_data { + struct gpio_virtuser_attr_data ad; + char consumer[GPIO_VIRTUSER_NAME_BUF_LEN]; + struct mutex consumer_lock; + unsigned int debounce; + atomic_t irq; + atomic_t irq_count; +}; + +struct gpio_virtuser_dbgfs_attr_descr { + const char *name; + const struct file_operations *fops; +}; + +struct gpio_virtuser_irq_work_context { + struct irq_work work; + struct completion work_completion; + union { + struct { + struct gpio_desc *desc; + int dir; + int val; + int ret; + }; + struct { + struct gpio_descs *descs; + unsigned long *values; + }; + }; +}; + +static struct gpio_virtuser_irq_work_context * +to_gpio_virtuser_irq_work_context(struct irq_work *work) +{ + return container_of(work, struct gpio_virtuser_irq_work_context, work); +} + +static void +gpio_virtuser_init_irq_work_context(struct gpio_virtuser_irq_work_context *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + init_completion(&ctx->work_completion); +} + +static void +gpio_virtuser_irq_work_queue_sync(struct gpio_virtuser_irq_work_context *ctx) +{ + irq_work_queue(&ctx->work); + wait_for_completion(&ctx->work_completion); +} + +static void gpio_virtuser_dbgfs_emit_value_array(char *buf, + unsigned long *values, + size_t num_values) +{ + size_t i; + + for (i = 0; i < num_values; i++) + buf[i] = test_bit(i, values) ? '1' : '0'; + + buf[i++] = '\n'; +} + +static void gpio_virtuser_get_value_array_atomic(struct irq_work *work) +{ + struct gpio_virtuser_irq_work_context *ctx = + to_gpio_virtuser_irq_work_context(work); + struct gpio_descs *descs = ctx->descs; + + ctx->ret = gpiod_get_array_value(descs->ndescs, descs->desc, + descs->info, ctx->values); + complete(&ctx->work_completion); +} + +static int gpio_virtuser_get_array_value(struct gpio_descs *descs, + unsigned long *values, bool atomic) +{ + struct gpio_virtuser_irq_work_context ctx; + + if (!atomic) + return gpiod_get_array_value_cansleep(descs->ndescs, + descs->desc, + descs->info, values); + + gpio_virtuser_init_irq_work_context(&ctx); + ctx.work = IRQ_WORK_INIT_HARD(gpio_virtuser_get_value_array_atomic); + ctx.descs = descs; + ctx.values = values; + + gpio_virtuser_irq_work_queue_sync(&ctx); + + return ctx.ret; +} + +static ssize_t gpio_virtuser_value_array_do_read(struct file *file, + char __user *user_buf, + size_t size, loff_t *ppos, + bool atomic) +{ + struct gpio_virtuser_line_data *data = file->private_data; + struct gpio_descs *descs = data->ad.descs; + size_t bufsize; + int ret; + + unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs, + GFP_KERNEL); + if (!values) + return -ENOMEM; + + ret = gpio_virtuser_get_array_value(descs, values, atomic); + if (ret) + return ret; + + bufsize = descs->ndescs + 2; + + char *buf __free(kfree) = kzalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + gpio_virtuser_dbgfs_emit_value_array(buf, values, descs->ndescs); + + return simple_read_from_buffer(user_buf, size, ppos, buf, + descs->ndescs + 1); +} + +static int gpio_virtuser_dbgfs_parse_value_array(const char *buf, + size_t len, + unsigned long *values) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (buf[i] == '0') + clear_bit(i, values); + else if (buf[i] == '1') + set_bit(i, values); + else + return -EINVAL; + } + + return 0; +} + +static void gpio_virtuser_set_value_array_atomic(struct irq_work *work) +{ + struct gpio_virtuser_irq_work_context *ctx = + to_gpio_virtuser_irq_work_context(work); + struct gpio_descs *descs = ctx->descs; + + ctx->ret = gpiod_set_array_value(descs->ndescs, descs->desc, + descs->info, ctx->values); + complete(&ctx->work_completion); +} + +static int gpio_virtuser_set_array_value(struct gpio_descs *descs, + unsigned long *values, bool atomic) +{ + struct gpio_virtuser_irq_work_context ctx; + + if (!atomic) + return gpiod_set_array_value_cansleep(descs->ndescs, + descs->desc, + descs->info, values); + + gpio_virtuser_init_irq_work_context(&ctx); + ctx.work = IRQ_WORK_INIT_HARD(gpio_virtuser_set_value_array_atomic); + ctx.descs = descs; + ctx.values = values; + + gpio_virtuser_irq_work_queue_sync(&ctx); + + return ctx.ret; +} + +static ssize_t gpio_virtuser_value_array_do_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos, + bool atomic) +{ + struct gpio_virtuser_line_data *data = file->private_data; + struct gpio_descs *descs = data->ad.descs; + int ret; + + if (count - 1 != descs->ndescs) + return -EINVAL; + + char *buf __free(kfree) = kzalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = simple_write_to_buffer(buf, count, ppos, user_buf, count); + if (ret < 0) + return ret; + + unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs, + GFP_KERNEL); + if (!values) + return -ENOMEM; + + ret = gpio_virtuser_dbgfs_parse_value_array(buf, count - 1, values); + if (ret) + return ret; + + ret = gpio_virtuser_set_array_value(descs, values, atomic); + if (ret) + return ret; + + return count; +} + +static ssize_t gpio_virtuser_value_array_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return gpio_virtuser_value_array_do_read(file, user_buf, count, ppos, + false); +} + +static ssize_t gpio_virtuser_value_array_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + return gpio_virtuser_value_array_do_write(file, user_buf, count, ppos, + false); +} + +static const struct file_operations gpio_virtuser_value_array_fops = { + .read = gpio_virtuser_value_array_read, + .write = gpio_virtuser_value_array_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t +gpio_virtuser_value_array_atomic_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + return gpio_virtuser_value_array_do_read(file, user_buf, count, ppos, + true); +} + +static ssize_t +gpio_virtuser_value_array_atomic_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + return gpio_virtuser_value_array_do_write(file, user_buf, count, ppos, + true); +} + +static const struct file_operations gpio_virtuser_value_array_atomic_fops = { + .read = gpio_virtuser_value_array_atomic_read, + .write = gpio_virtuser_value_array_atomic_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static void gpio_virtuser_do_get_direction_atomic(struct irq_work *work) +{ + struct gpio_virtuser_irq_work_context *ctx = + to_gpio_virtuser_irq_work_context(work); + + ctx->ret = gpiod_get_direction(ctx->desc); + complete(&ctx->work_completion); +} + +static int gpio_virtuser_get_direction_atomic(struct gpio_desc *desc) +{ + struct gpio_virtuser_irq_work_context ctx; + + gpio_virtuser_init_irq_work_context(&ctx); + ctx.work = IRQ_WORK_INIT_HARD(gpio_virtuser_do_get_direction_atomic); + ctx.desc = desc; + + gpio_virtuser_irq_work_queue_sync(&ctx); + + return ctx.ret; +} + +static ssize_t gpio_virtuser_direction_do_read(struct file *file, + char __user *user_buf, + size_t size, loff_t *ppos, + bool atomic) +{ + struct gpio_virtuser_line_data *data = file->private_data; + struct gpio_desc *desc = data->ad.desc; + char buf[32]; + int dir; + + if (!atomic) + dir = gpiod_get_direction(desc); + else + dir = gpio_virtuser_get_direction_atomic(desc); + if (dir < 0) + return dir; + + snprintf(buf, sizeof(buf), "%s\n", dir ? "input" : "output"); + + return simple_read_from_buffer(user_buf, size, ppos, buf, strlen(buf)); +} + +static int gpio_virtuser_set_direction(struct gpio_desc *desc, int dir, int val) +{ + if (dir) + return gpiod_direction_input(desc); + + return gpiod_direction_output(desc, val); +} + +static void gpio_virtuser_do_set_direction_atomic(struct irq_work *work) +{ + struct gpio_virtuser_irq_work_context *ctx = + to_gpio_virtuser_irq_work_context(work); + + ctx->ret = gpio_virtuser_set_direction(ctx->desc, ctx->dir, ctx->val); + complete(&ctx->work_completion); +} + +static int gpio_virtuser_set_direction_atomic(struct gpio_desc *desc, + int dir, int val) +{ + struct gpio_virtuser_irq_work_context ctx; + + gpio_virtuser_init_irq_work_context(&ctx); + ctx.work = IRQ_WORK_INIT_HARD(gpio_virtuser_do_set_direction_atomic); + ctx.desc = desc; + ctx.dir = dir; + ctx.val = val; + + gpio_virtuser_irq_work_queue_sync(&ctx); + + return ctx.ret; +} + +static ssize_t gpio_virtuser_direction_do_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos, + bool atomic) +{ + struct gpio_virtuser_line_data *data = file->private_data; + struct gpio_desc *desc = data->ad.desc; + char buf[32], *trimmed; + int ret, dir, val = 0; + + ret = simple_write_to_buffer(buf, sizeof(buf), ppos, user_buf, count); + if (ret < 0) + return ret; + + trimmed = strim(buf); + + if (strcmp(trimmed, "input") == 0) { + dir = 1; + } else if (strcmp(trimmed, "output-high") == 0) { + dir = 0; + val = 1; + } else if (strcmp(trimmed, "output-low") == 0) { + dir = val = 0; + } else { + return -EINVAL; + } + + if (!atomic) + ret = gpio_virtuser_set_direction(desc, dir, val); + else + ret = gpio_virtuser_set_direction_atomic(desc, dir, val); + if (ret) + return ret; + + return count; +} + +static ssize_t gpio_virtuser_direction_read(struct file *file, + char __user *user_buf, + size_t size, loff_t *ppos) +{ + return gpio_virtuser_direction_do_read(file, user_buf, size, ppos, + false); +} + +static ssize_t gpio_virtuser_direction_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + return gpio_virtuser_direction_do_write(file, user_buf, count, ppos, + false); +} + +static const struct file_operations gpio_virtuser_direction_fops = { + .read = gpio_virtuser_direction_read, + .write = gpio_virtuser_direction_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t gpio_virtuser_direction_atomic_read(struct file *file, + char __user *user_buf, + size_t size, loff_t *ppos) +{ + return gpio_virtuser_direction_do_read(file, user_buf, size, ppos, + true); +} + +static ssize_t gpio_virtuser_direction_atomic_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + return gpio_virtuser_direction_do_write(file, user_buf, count, ppos, + true); +} + +static const struct file_operations gpio_virtuser_direction_atomic_fops = { + .read = gpio_virtuser_direction_atomic_read, + .write = gpio_virtuser_direction_atomic_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int gpio_virtuser_value_get(void *data, u64 *val) +{ + struct gpio_virtuser_line_data *ld = data; + int ret; + + ret = gpiod_get_value_cansleep(ld->ad.desc); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int gpio_virtuser_value_set(void *data, u64 val) +{ + struct gpio_virtuser_line_data *ld = data; + + if (val > 1) + return -EINVAL; + + gpiod_set_value_cansleep(ld->ad.desc, (int)val); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(gpio_virtuser_value_fops, + gpio_virtuser_value_get, + gpio_virtuser_value_set, + "%llu\n"); + +static void gpio_virtuser_get_value_atomic(struct irq_work *work) +{ + struct gpio_virtuser_irq_work_context *ctx = + to_gpio_virtuser_irq_work_context(work); + + ctx->val = gpiod_get_value(ctx->desc); + complete(&ctx->work_completion); +} + +static int gpio_virtuser_value_atomic_get(void *data, u64 *val) +{ + struct gpio_virtuser_line_data *ld = data; + struct gpio_virtuser_irq_work_context ctx; + + gpio_virtuser_init_irq_work_context(&ctx); + ctx.work = IRQ_WORK_INIT_HARD(gpio_virtuser_get_value_atomic); + ctx.desc = ld->ad.desc; + + gpio_virtuser_irq_work_queue_sync(&ctx); + + if (ctx.val < 0) + return ctx.val; + + *val = ctx.val; + + return 0; +} + +static void gpio_virtuser_set_value_atomic(struct irq_work *work) +{ + struct gpio_virtuser_irq_work_context *ctx = + to_gpio_virtuser_irq_work_context(work); + + gpiod_set_value(ctx->desc, ctx->val); + complete(&ctx->work_completion); +} + +static int gpio_virtuser_value_atomic_set(void *data, u64 val) +{ + struct gpio_virtuser_line_data *ld = data; + struct gpio_virtuser_irq_work_context ctx; + + if (val > 1) + return -EINVAL; + + gpio_virtuser_init_irq_work_context(&ctx); + ctx.work = IRQ_WORK_INIT_HARD(gpio_virtuser_set_value_atomic); + ctx.desc = ld->ad.desc; + ctx.val = (int)val; + + gpio_virtuser_irq_work_queue_sync(&ctx); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(gpio_virtuser_value_atomic_fops, + gpio_virtuser_value_atomic_get, + gpio_virtuser_value_atomic_set, + "%llu\n"); + +static int gpio_virtuser_debounce_get(void *data, u64 *val) +{ + struct gpio_virtuser_line_data *ld = data; + + *val = READ_ONCE(ld->debounce); + + return 0; +} + +static int gpio_virtuser_debounce_set(void *data, u64 val) +{ + struct gpio_virtuser_line_data *ld = data; + int ret; + + if (val > UINT_MAX) + return -E2BIG; + + ret = gpiod_set_debounce(ld->ad.desc, (unsigned int)val); + if (ret) + /* Don't propagate errno unknown to user-space. */ + return ret == -ENOTSUPP ? -EOPNOTSUPP : ret; + + WRITE_ONCE(ld->debounce, (unsigned int)val); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(gpio_virtuser_debounce_fops, + gpio_virtuser_debounce_get, + gpio_virtuser_debounce_set, + "%llu\n"); + +static ssize_t gpio_virtuser_consumer_read(struct file *file, + char __user *user_buf, + size_t size, loff_t *ppos) +{ + struct gpio_virtuser_line_data *data = file->private_data; + char buf[GPIO_VIRTUSER_NAME_BUF_LEN + 1]; + ssize_t ret; + + memset(buf, 0x0, sizeof(buf)); + + scoped_guard(mutex, &data->consumer_lock) + ret = snprintf(buf, sizeof(buf), "%s\n", data->consumer); + + return simple_read_from_buffer(user_buf, size, ppos, buf, ret); +} + +static ssize_t gpio_virtuser_consumer_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct gpio_virtuser_line_data *data = file->private_data; + char buf[GPIO_VIRTUSER_NAME_BUF_LEN + 2]; + int ret; + + ret = simple_write_to_buffer(buf, GPIO_VIRTUSER_NAME_BUF_LEN, ppos, + user_buf, count); + if (ret < 0) + return ret; + + buf[strlen(buf) - 1] = '\0'; + + ret = gpiod_set_consumer_name(data->ad.desc, buf); + if (ret) + return ret; + + scoped_guard(mutex, &data->consumer_lock) + strscpy(data->consumer, buf, sizeof(data->consumer)); + + return count; +} + +static const struct file_operations gpio_virtuser_consumer_fops = { + .read = gpio_virtuser_consumer_read, + .write = gpio_virtuser_consumer_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int gpio_virtuser_interrupts_get(void *data, u64 *val) +{ + struct gpio_virtuser_line_data *ld = data; + + *val = atomic_read(&ld->irq_count); + + return 0; +} + +static irqreturn_t gpio_virtuser_irq_handler(int irq, void *data) +{ + struct gpio_virtuser_line_data *ld = data; + + atomic_inc(&ld->irq_count); + + return IRQ_HANDLED; +} + +static int gpio_virtuser_interrupts_set(void *data, u64 val) +{ + struct gpio_virtuser_line_data *ld = data; + int irq, ret; + + if (val > 1) + return -EINVAL; + + if (val) { + irq = gpiod_to_irq(ld->ad.desc); + if (irq < 0) + return irq; + + ret = request_threaded_irq(irq, NULL, + gpio_virtuser_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + ld->consumer, data); + if (ret) + return ret; + + atomic_set(&ld->irq, irq); + } else { + irq = atomic_xchg(&ld->irq, 0); + free_irq(irq, ld); + } + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(gpio_virtuser_interrupts_fops, + gpio_virtuser_interrupts_get, + gpio_virtuser_interrupts_set, + "%llu\n"); + +static const struct gpio_virtuser_dbgfs_attr_descr +gpio_virtuser_line_array_dbgfs_attrs[] = { + { + .name = "values", + .fops = &gpio_virtuser_value_array_fops, + }, + { + .name = "values_atomic", + .fops = &gpio_virtuser_value_array_atomic_fops, + }, +}; + +static const struct gpio_virtuser_dbgfs_attr_descr +gpio_virtuser_line_dbgfs_attrs[] = { + { + .name = "direction", + .fops = &gpio_virtuser_direction_fops, + }, + { + .name = "direction_atomic", + .fops = &gpio_virtuser_direction_atomic_fops, + }, + { + .name = "value", + .fops = &gpio_virtuser_value_fops, + }, + { + .name = "value_atomic", + .fops = &gpio_virtuser_value_atomic_fops, + }, + { + .name = "debounce", + .fops = &gpio_virtuser_debounce_fops, + }, + { + .name = "consumer", + .fops = &gpio_virtuser_consumer_fops, + }, + { + .name = "interrupts", + .fops = &gpio_virtuser_interrupts_fops, + }, +}; + +static int gpio_virtuser_create_debugfs_attrs( + const struct gpio_virtuser_dbgfs_attr_descr *attr, + size_t num_attrs, struct dentry *parent, void *data) +{ + struct dentry *ret; + size_t i; + + for (i = 0; i < num_attrs; i++, attr++) { + ret = debugfs_create_file(attr->name, 0644, parent, data, + attr->fops); + if (IS_ERR(ret)) + return PTR_ERR(ret); + } + + return 0; +} + +static int gpio_virtuser_dbgfs_init_line_array_attrs(struct device *dev, + struct gpio_descs *descs, + const char *id, + struct dentry *dbgfs_entry) +{ + struct gpio_virtuser_line_array_data *data; + char *name; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->ad.descs = descs; + + name = devm_kasprintf(dev, GFP_KERNEL, "gpiod:%s", id); + if (!name) + return -ENOMEM; + + data->ad.dbgfs_dir = debugfs_create_dir(name, dbgfs_entry); + if (IS_ERR(data->ad.dbgfs_dir)) + return PTR_ERR(data->ad.dbgfs_dir); + + return gpio_virtuser_create_debugfs_attrs( + gpio_virtuser_line_array_dbgfs_attrs, + ARRAY_SIZE(gpio_virtuser_line_array_dbgfs_attrs), + data->ad.dbgfs_dir, data); +} + +static int gpio_virtuser_dbgfs_init_line_attrs(struct device *dev, + struct gpio_desc *desc, + const char *id, + unsigned int index, + struct dentry *dbgfs_entry) +{ + struct gpio_virtuser_line_data *data; + char *name; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->ad.desc = desc; + sprintf(data->consumer, id); + atomic_set(&data->irq, 0); + atomic_set(&data->irq_count, 0); + + name = devm_kasprintf(dev, GFP_KERNEL, "gpiod:%s:%u", id, index); + if (!name) + return -ENOMEM; + + ret = devm_mutex_init(dev, &data->consumer_lock); + if (ret) + return ret; + + data->ad.dbgfs_dir = debugfs_create_dir(name, dbgfs_entry); + if (IS_ERR(data->ad.dbgfs_dir)) + return PTR_ERR(data->ad.dbgfs_dir); + + return gpio_virtuser_create_debugfs_attrs( + gpio_virtuser_line_dbgfs_attrs, + ARRAY_SIZE(gpio_virtuser_line_dbgfs_attrs), + data->ad.dbgfs_dir, data); +} + +static void gpio_virtuser_debugfs_remove(void *data) +{ + struct dentry *dbgfs_entry = data; + + debugfs_remove_recursive(dbgfs_entry); +} + +static int gpio_virtuser_prop_is_gpio(struct property *prop) +{ + char *dash = strrchr(prop->name, '-'); + + return dash && strcmp(dash, "-gpios") == 0; +} + +/* + * If this is an OF-based system, then we iterate over properties and consider + * all whose names end in "-gpios". For configfs we expect an additional string + * array property - "gpio-virtuser,ids" - containing the list of all GPIO IDs + * to request. + */ +static int gpio_virtuser_count_ids(struct device *dev) +{ + struct device_node *of_node = dev_of_node(dev); + struct property *prop; + int ret = 0; + + if (!of_node) + return device_property_string_array_count(dev, + "gpio-virtuser,ids"); + + for_each_property_of_node(of_node, prop) { + if (gpio_virtuser_prop_is_gpio(prop)) + ++ret; + } + + return ret; +} + +static int gpio_virtuser_get_ids(struct device *dev, const char **ids, + int num_ids) +{ + struct device_node *of_node = dev_of_node(dev); + struct property *prop; + size_t pos = 0, diff; + char *dash, *tmp; + + if (!of_node) + return device_property_read_string_array(dev, + "gpio-virtuser,ids", + ids, num_ids); + + for_each_property_of_node(of_node, prop) { + if (!gpio_virtuser_prop_is_gpio(prop)) + continue; + + dash = strrchr(prop->name, '-'); + diff = dash - prop->name; + + tmp = devm_kmemdup(dev, prop->name, diff + 1, + GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + tmp[diff] = '\0'; + ids[pos++] = tmp; + } + + return 0; +} + +static int gpio_virtuser_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dentry *dbgfs_entry; + struct gpio_descs *descs; + int ret, num_ids = 0, i; + const char **ids; + unsigned int j; + + num_ids = gpio_virtuser_count_ids(dev); + if (num_ids < 0) + return dev_err_probe(dev, num_ids, + "Failed to get the number of GPIOs to request\n"); + + if (num_ids == 0) + return dev_err_probe(dev, -EINVAL, "No GPIO IDs specified\n"); + + ids = devm_kcalloc(dev, num_ids, sizeof(*ids), GFP_KERNEL); + if (!ids) + return -ENOMEM; + + ret = gpio_virtuser_get_ids(dev, ids, num_ids); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get the IDs of GPIOs to request\n"); + + dbgfs_entry = debugfs_create_dir(dev_name(dev), gpio_virtuser_dbg_root); + ret = devm_add_action_or_reset(dev, gpio_virtuser_debugfs_remove, + dbgfs_entry); + if (ret) + return ret; + + for (i = 0; i < num_ids; i++) { + descs = devm_gpiod_get_array(dev, ids[i], GPIOD_ASIS); + if (IS_ERR(descs)) + return dev_err_probe(dev, PTR_ERR(descs), + "Failed to request the '%s' GPIOs\n", + ids[i]); + + ret = gpio_virtuser_dbgfs_init_line_array_attrs(dev, descs, + ids[i], + dbgfs_entry); + if (ret) + return dev_err_probe(dev, ret, + "Failed to setup the debugfs array interface for the '%s' GPIOs\n", + ids[i]); + + for (j = 0; j < descs->ndescs; j++) { + ret = gpio_virtuser_dbgfs_init_line_attrs(dev, + descs->desc[j], ids[i], + j, dbgfs_entry); + if (ret) + return dev_err_probe(dev, ret, + "Failed to setup the debugfs line interface for the '%s' GPIOs\n", + ids[i]); + } + } + + return 0; +} + +static const struct of_device_id gpio_virtuser_of_match[] = { + { .compatible = "gpio-virtuser" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpio_virtuser_of_match); + +static struct platform_driver gpio_virtuser_driver = { + .driver = { + .name = "gpio-virtuser", + .of_match_table = gpio_virtuser_of_match, + }, + .probe = gpio_virtuser_probe, +}; + +struct gpio_virtuser_device { + struct config_group group; + + struct platform_device *pdev; + int id; + struct mutex lock; + + struct notifier_block bus_notifier; + struct completion probe_completion; + bool driver_bound; + + struct gpiod_lookup_table *lookup_table; + + struct list_head lookup_list; +}; + +static int gpio_virtuser_bus_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct gpio_virtuser_device *vdev; + struct device *dev = data; + char devname[32]; + + vdev = container_of(nb, struct gpio_virtuser_device, bus_notifier); + snprintf(devname, sizeof(devname), "gpio-virtuser.%d", vdev->id); + + if (!device_match_name(dev, devname)) + return NOTIFY_DONE; + + switch (action) { + case BUS_NOTIFY_BOUND_DRIVER: + vdev->driver_bound = true; + break; + case BUS_NOTIFY_DRIVER_NOT_BOUND: + vdev->driver_bound = false; + break; + default: + return NOTIFY_DONE; + } + + complete(&vdev->probe_completion); + return NOTIFY_OK; +} + +static struct gpio_virtuser_device * +to_gpio_virtuser_device(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + + return container_of(group, struct gpio_virtuser_device, group); +} + +static bool +gpio_virtuser_device_is_live(struct gpio_virtuser_device *dev) +{ + lockdep_assert_held(&dev->lock); + + return !!dev->pdev; +} + +struct gpio_virtuser_lookup { + struct config_group group; + + struct gpio_virtuser_device *parent; + struct list_head siblings; + + char *con_id; + + struct list_head entry_list; +}; + +static struct gpio_virtuser_lookup * +to_gpio_virtuser_lookup(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + + return container_of(group, struct gpio_virtuser_lookup, group); +} + +struct gpio_virtuser_lookup_entry { + struct config_group group; + + struct gpio_virtuser_lookup *parent; + struct list_head siblings; + + char *key; + /* Can be negative to indicate lookup by name. */ + int offset; + enum gpio_lookup_flags flags; +}; + +static struct gpio_virtuser_lookup_entry * +to_gpio_virtuser_lookup_entry(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + + return container_of(group, struct gpio_virtuser_lookup_entry, group); +} + +static ssize_t +gpio_virtuser_lookup_entry_config_key_show(struct config_item *item, char *page) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + + guard(mutex)(&dev->lock); + + return sprintf(page, "%s\n", entry->key ?: ""); +} + +static ssize_t +gpio_virtuser_lookup_entry_config_key_store(struct config_item *item, + const char *page, size_t count) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + + char *key __free(kfree) = kstrndup(skip_spaces(page), count, + GFP_KERNEL); + if (!key) + return -ENOMEM; + + strim(key); + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return -EBUSY; + + kfree(entry->key); + entry->key = no_free_ptr(key); + + return count; +} + +CONFIGFS_ATTR(gpio_virtuser_lookup_entry_config_, key); + +static ssize_t +gpio_virtuser_lookup_entry_config_offset_show(struct config_item *item, + char *page) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + unsigned int offset; + + scoped_guard(mutex, &dev->lock) + offset = entry->offset; + + return sprintf(page, "%d\n", offset); +} + +static ssize_t +gpio_virtuser_lookup_entry_config_offset_store(struct config_item *item, + const char *page, size_t count) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + int offset, ret; + + ret = kstrtoint(page, 0, &offset); + if (ret) + return ret; + + /* + * Negative number here means: 'key' represents a line name to lookup. + * Non-negative means: '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)) + return -EINVAL; + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return -EBUSY; + + entry->offset = offset; + + return count; +} + +CONFIGFS_ATTR(gpio_virtuser_lookup_entry_config_, offset); + +static enum gpio_lookup_flags +gpio_virtuser_lookup_get_flags(struct config_item *item) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + + guard(mutex)(&dev->lock); + + return entry->flags; +} + +static ssize_t +gpio_virtuser_lookup_entry_config_drive_show(struct config_item *item, char *page) +{ + enum gpio_lookup_flags flags = gpio_virtuser_lookup_get_flags(item); + const char *repr; + + if (flags & GPIO_OPEN_DRAIN) + repr = "open-drain"; + else if (flags & GPIO_OPEN_SOURCE) + repr = "open-source"; + else + repr = "push-pull"; + + return sprintf(page, "%s\n", repr); +} + +static ssize_t +gpio_virtuser_lookup_entry_config_drive_store(struct config_item *item, + const char *page, size_t count) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return -EBUSY; + + if (sysfs_streq(page, "push-pull")) { + entry->flags &= ~(GPIO_OPEN_DRAIN | GPIO_OPEN_SOURCE); + } else if (sysfs_streq(page, "open-drain")) { + entry->flags &= ~GPIO_OPEN_SOURCE; + entry->flags |= GPIO_OPEN_DRAIN; + } else if (sysfs_streq(page, "open-source")) { + entry->flags &= ~GPIO_OPEN_DRAIN; + entry->flags |= GPIO_OPEN_SOURCE; + } else { + count = -EINVAL; + } + + return count; +} + +CONFIGFS_ATTR(gpio_virtuser_lookup_entry_config_, drive); + +static ssize_t +gpio_virtuser_lookup_entry_config_pull_show(struct config_item *item, char *page) +{ + enum gpio_lookup_flags flags = gpio_virtuser_lookup_get_flags(item); + const char *repr; + + if (flags & GPIO_PULL_UP) + repr = "pull-up"; + else if (flags & GPIO_PULL_DOWN) + repr = "pull-down"; + else if (flags & GPIO_PULL_DISABLE) + repr = "pull-disabled"; + else + repr = "as-is"; + + return sprintf(page, "%s\n", repr); +} + +static ssize_t +gpio_virtuser_lookup_entry_config_pull_store(struct config_item *item, + const char *page, size_t count) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return -EBUSY; + + if (sysfs_streq(page, "pull-up")) { + entry->flags &= ~(GPIO_PULL_DOWN | GPIO_PULL_DISABLE); + entry->flags |= GPIO_PULL_UP; + } else if (sysfs_streq(page, "pull-down")) { + entry->flags &= ~(GPIO_PULL_UP | GPIO_PULL_DISABLE); + entry->flags |= GPIO_PULL_DOWN; + } else if (sysfs_streq(page, "pull-disabled")) { + entry->flags &= ~(GPIO_PULL_UP | GPIO_PULL_DOWN); + entry->flags |= GPIO_PULL_DISABLE; + } else if (sysfs_streq(page, "as-is")) { + entry->flags &= ~(GPIO_PULL_UP | GPIO_PULL_DOWN | + GPIO_PULL_DISABLE); + } else { + count = -EINVAL; + } + + return count; +} + +CONFIGFS_ATTR(gpio_virtuser_lookup_entry_config_, pull); + +static ssize_t +gpio_virtuser_lookup_entry_config_active_low_show(struct config_item *item, + char *page) +{ + enum gpio_lookup_flags flags = gpio_virtuser_lookup_get_flags(item); + + return sprintf(page, "%c\n", flags & GPIO_ACTIVE_LOW ? '1' : '0'); +} + +static ssize_t +gpio_virtuser_lookup_entry_config_active_low_store(struct config_item *item, + const char *page, + size_t count) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + bool active_low; + int ret; + + ret = kstrtobool(page, &active_low); + if (ret) + return ret; + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return -EBUSY; + + if (active_low) + entry->flags |= GPIO_ACTIVE_LOW; + else + entry->flags &= ~GPIO_ACTIVE_LOW; + + return count; +} + +CONFIGFS_ATTR(gpio_virtuser_lookup_entry_config_, active_low); + +static ssize_t +gpio_virtuser_lookup_entry_config_transitory_show(struct config_item *item, + char *page) +{ + enum gpio_lookup_flags flags = gpio_virtuser_lookup_get_flags(item); + + return sprintf(page, "%c\n", flags & GPIO_TRANSITORY ? '1' : '0'); +} + +static ssize_t +gpio_virtuser_lookup_entry_config_transitory_store(struct config_item *item, + const char *page, + size_t count) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + bool transitory; + int ret; + + ret = kstrtobool(page, &transitory); + if (ret) + return ret; + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return -EBUSY; + + if (transitory) + entry->flags |= GPIO_TRANSITORY; + else + entry->flags &= ~GPIO_TRANSITORY; + + return count; +} + +CONFIGFS_ATTR(gpio_virtuser_lookup_entry_config_, transitory); + +static struct configfs_attribute *gpio_virtuser_lookup_entry_config_attrs[] = { + &gpio_virtuser_lookup_entry_config_attr_key, + &gpio_virtuser_lookup_entry_config_attr_offset, + &gpio_virtuser_lookup_entry_config_attr_drive, + &gpio_virtuser_lookup_entry_config_attr_pull, + &gpio_virtuser_lookup_entry_config_attr_active_low, + &gpio_virtuser_lookup_entry_config_attr_transitory, + NULL +}; + +static ssize_t +gpio_virtuser_device_config_dev_name_show(struct config_item *item, + char *page) +{ + struct gpio_virtuser_device *dev = to_gpio_virtuser_device(item); + struct platform_device *pdev; + + guard(mutex)(&dev->lock); + + pdev = dev->pdev; + if (pdev) + return sprintf(page, "%s\n", dev_name(&pdev->dev)); + + return sprintf(page, "gpio-sim.%d\n", dev->id); +} + +CONFIGFS_ATTR_RO(gpio_virtuser_device_config_, dev_name); + +static ssize_t gpio_virtuser_device_config_live_show(struct config_item *item, + char *page) +{ + struct gpio_virtuser_device *dev = to_gpio_virtuser_device(item); + bool live; + + scoped_guard(mutex, &dev->lock) + live = gpio_virtuser_device_is_live(dev); + + return sprintf(page, "%c\n", live ? '1' : '0'); +} + +static size_t +gpio_virtuser_get_lookup_count(struct gpio_virtuser_device *dev) +{ + struct gpio_virtuser_lookup *lookup; + size_t count = 0; + + lockdep_assert_held(&dev->lock); + + list_for_each_entry(lookup, &dev->lookup_list, siblings) + count += list_count_nodes(&lookup->entry_list); + + return count; +} + +static int +gpio_virtuser_make_lookup_table(struct gpio_virtuser_device *dev) +{ + size_t num_entries = gpio_virtuser_get_lookup_count(dev); + struct gpio_virtuser_lookup_entry *entry; + struct gpio_virtuser_lookup *lookup; + struct gpiod_lookup *curr; + unsigned int i = 0; + + lockdep_assert_held(&dev->lock); + + struct gpiod_lookup_table *table __free(kfree) = + kzalloc(struct_size(table, table, num_entries + 1), GFP_KERNEL); + if (!table) + return -ENOMEM; + + table->dev_id = kasprintf(GFP_KERNEL, "gpio-virtuser.%d", dev->id); + if (!table->dev_id) + return -ENOMEM; + + list_for_each_entry(lookup, &dev->lookup_list, siblings) { + list_for_each_entry(entry, &lookup->entry_list, siblings) { + curr = &table->table[i]; + + curr->con_id = lookup->con_id; + curr->idx = i; + curr->key = entry->key; + curr->chip_hwnum = entry->offset < 0 ? + U16_MAX : entry->offset; + curr->flags = entry->flags; + i++; + } + } + + gpiod_add_lookup_table(table); + dev->lookup_table = no_free_ptr(table); + + return 0; +} + +static struct fwnode_handle * +gpio_virtuser_make_device_swnode(struct gpio_virtuser_device *dev) +{ + struct property_entry properties[2]; + struct gpio_virtuser_lookup *lookup; + unsigned int i = 0; + size_t num_ids; + + memset(properties, 0, sizeof(properties)); + + num_ids = list_count_nodes(&dev->lookup_list); + char **ids __free(kfree) = kcalloc(num_ids + 1, sizeof(*ids), + GFP_KERNEL); + if (!ids) + return ERR_PTR(-ENOMEM); + + list_for_each_entry(lookup, &dev->lookup_list, siblings) + ids[i++] = lookup->con_id; + + properties[0] = PROPERTY_ENTRY_STRING_ARRAY_LEN("gpio-virtuser,ids", + ids, num_ids); + + return fwnode_create_software_node(properties, NULL); +} + +static int +gpio_virtuser_device_activate(struct gpio_virtuser_device *dev) +{ + struct platform_device_info pdevinfo; + struct fwnode_handle *swnode; + struct platform_device *pdev; + int ret; + + lockdep_assert_held(&dev->lock); + + if (list_empty(&dev->lookup_list)) + return -ENODATA; + + swnode = gpio_virtuser_make_device_swnode(dev); + if (IS_ERR(swnode)) + return PTR_ERR(swnode); + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.name = "gpio-virtuser"; + pdevinfo.id = dev->id; + pdevinfo.fwnode = swnode; + + ret = gpio_virtuser_make_lookup_table(dev); + if (ret) { + fwnode_remove_software_node(swnode); + return ret; + } + + 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); + fwnode_remove_software_node(swnode); + return PTR_ERR(pdev); + } + + wait_for_completion(&dev->probe_completion); + bus_unregister_notifier(&platform_bus_type, &dev->bus_notifier); + + if (!dev->driver_bound) { + platform_device_unregister(pdev); + fwnode_remove_software_node(swnode); + return -ENXIO; + } + + dev->pdev = pdev; + + return 0; +} + +static void +gpio_virtuser_device_deactivate(struct gpio_virtuser_device *dev) +{ + struct fwnode_handle *swnode; + + lockdep_assert_held(&dev->lock); + + swnode = dev_fwnode(&dev->pdev->dev); + platform_device_unregister(dev->pdev); + fwnode_remove_software_node(swnode); + dev->pdev = NULL; + gpiod_remove_lookup_table(dev->lookup_table); + kfree(dev->lookup_table); +} + +static ssize_t +gpio_virtuser_device_config_live_store(struct config_item *item, + const char *page, size_t count) +{ + struct gpio_virtuser_device *dev = to_gpio_virtuser_device(item); + int ret = 0; + bool live; + + ret = kstrtobool(page, &live); + if (ret) + return ret; + + guard(mutex)(&dev->lock); + + if (live == gpio_virtuser_device_is_live(dev)) + return -EPERM; + + if (live) + ret = gpio_virtuser_device_activate(dev); + else + gpio_virtuser_device_deactivate(dev); + + return ret ?: count; +} + +CONFIGFS_ATTR(gpio_virtuser_device_config_, live); + +static struct configfs_attribute *gpio_virtuser_device_config_attrs[] = { + &gpio_virtuser_device_config_attr_dev_name, + &gpio_virtuser_device_config_attr_live, + NULL +}; + +static void +gpio_virtuser_lookup_entry_config_group_release(struct config_item *item) +{ + struct gpio_virtuser_lookup_entry *entry = + to_gpio_virtuser_lookup_entry(item); + struct gpio_virtuser_device *dev = entry->parent->parent; + + guard(mutex)(&dev->lock); + + list_del(&entry->siblings); + + kfree(entry->key); + kfree(entry); +} + +static struct +configfs_item_operations gpio_virtuser_lookup_entry_config_item_ops = { + .release = gpio_virtuser_lookup_entry_config_group_release, +}; + +static const struct +config_item_type gpio_virtuser_lookup_entry_config_group_type = { + .ct_item_ops = &gpio_virtuser_lookup_entry_config_item_ops, + .ct_attrs = gpio_virtuser_lookup_entry_config_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group * +gpio_virtuser_make_lookup_entry_group(struct config_group *group, + const char *name) +{ + struct gpio_virtuser_lookup *lookup = + to_gpio_virtuser_lookup(&group->cg_item); + struct gpio_virtuser_device *dev = lookup->parent; + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return ERR_PTR(-EBUSY); + + struct gpio_virtuser_lookup_entry *entry __free(kfree) = + kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&entry->group, name, + &gpio_virtuser_lookup_entry_config_group_type); + entry->flags = GPIO_LOOKUP_FLAGS_DEFAULT; + entry->parent = lookup; + list_add_tail(&entry->siblings, &lookup->entry_list); + + return &no_free_ptr(entry)->group; +} + +static void gpio_virtuser_lookup_config_group_release(struct config_item *item) +{ + struct gpio_virtuser_lookup *lookup = to_gpio_virtuser_lookup(item); + struct gpio_virtuser_device *dev = lookup->parent; + + guard(mutex)(&dev->lock); + + list_del(&lookup->siblings); + + kfree(lookup->con_id); + kfree(lookup); +} + +static struct configfs_item_operations gpio_virtuser_lookup_config_item_ops = { + .release = gpio_virtuser_lookup_config_group_release, +}; + +static struct +configfs_group_operations gpio_virtuser_lookup_config_group_ops = { + .make_group = gpio_virtuser_make_lookup_entry_group, +}; + +static const struct config_item_type gpio_virtuser_lookup_config_group_type = { + .ct_group_ops = &gpio_virtuser_lookup_config_group_ops, + .ct_item_ops = &gpio_virtuser_lookup_config_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group * +gpio_virtuser_make_lookup_group(struct config_group *group, const char *name) +{ + struct gpio_virtuser_device *dev = + to_gpio_virtuser_device(&group->cg_item); + + if (strlen(name) > (GPIO_VIRTUSER_NAME_BUF_LEN - 1)) + return ERR_PTR(-E2BIG); + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + return ERR_PTR(-EBUSY); + + struct gpio_virtuser_lookup *lookup __free(kfree) = + kzalloc(sizeof(*lookup), GFP_KERNEL); + if (!lookup) + return ERR_PTR(-ENOMEM); + + lookup->con_id = kstrdup(name, GFP_KERNEL); + if (!lookup->con_id) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&lookup->group, name, + &gpio_virtuser_lookup_config_group_type); + INIT_LIST_HEAD(&lookup->entry_list); + lookup->parent = dev; + list_add_tail(&lookup->siblings, &dev->lookup_list); + + return &no_free_ptr(lookup)->group; +} + +static void gpio_virtuser_device_config_group_release(struct config_item *item) +{ + struct gpio_virtuser_device *dev = to_gpio_virtuser_device(item); + + guard(mutex)(&dev->lock); + + if (gpio_virtuser_device_is_live(dev)) + gpio_virtuser_device_deactivate(dev); + + mutex_destroy(&dev->lock); + ida_free(&gpio_virtuser_ida, dev->id); + kfree(dev); +} + +static struct configfs_item_operations gpio_virtuser_device_config_item_ops = { + .release = gpio_virtuser_device_config_group_release, +}; + +static struct configfs_group_operations gpio_virtuser_device_config_group_ops = { + .make_group = gpio_virtuser_make_lookup_group, +}; + +static const struct config_item_type gpio_virtuser_device_config_group_type = { + .ct_group_ops = &gpio_virtuser_device_config_group_ops, + .ct_item_ops = &gpio_virtuser_device_config_item_ops, + .ct_attrs = gpio_virtuser_device_config_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group * +gpio_virtuser_config_make_device_group(struct config_group *group, + const char *name) +{ + struct gpio_virtuser_device *dev __free(kfree) = kzalloc(sizeof(*dev), + GFP_KERNEL); + if (!dev) + return ERR_PTR(-ENOMEM); + + dev->id = ida_alloc(&gpio_virtuser_ida, GFP_KERNEL); + if (dev->id < 0) + return ERR_PTR(dev->id); + + config_group_init_type_name(&dev->group, name, + &gpio_virtuser_device_config_group_type); + mutex_init(&dev->lock); + INIT_LIST_HEAD(&dev->lookup_list); + dev->bus_notifier.notifier_call = gpio_virtuser_bus_notifier_call; + init_completion(&dev->probe_completion); + + return &no_free_ptr(dev)->group; +} + +static struct configfs_group_operations gpio_virtuser_config_group_ops = { + .make_group = gpio_virtuser_config_make_device_group, +}; + +static const struct config_item_type gpio_virtuser_config_type = { + .ct_group_ops = &gpio_virtuser_config_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_subsystem gpio_virtuser_config_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "gpio-virtuser", + .ci_type = &gpio_virtuser_config_type, + }, + }, +}; + +static int __init gpio_virtuser_init(void) +{ + int ret; + + ret = platform_driver_register(&gpio_virtuser_driver); + if (ret) { + pr_err("Failed to register the platform driver: %d\n", ret); + return ret; + } + + config_group_init(&gpio_virtuser_config_subsys.su_group); + mutex_init(&gpio_virtuser_config_subsys.su_mutex); + ret = configfs_register_subsystem(&gpio_virtuser_config_subsys); + if (ret) { + pr_err("Failed to register the '%s' configfs subsystem: %d\n", + gpio_virtuser_config_subsys.su_group.cg_item.ci_namebuf, + ret); + goto err_plat_drv_unreg; + } + + gpio_virtuser_dbg_root = debugfs_create_dir("gpio-virtuser", NULL); + if (IS_ERR(gpio_virtuser_dbg_root)) { + ret = PTR_ERR(gpio_virtuser_dbg_root); + pr_err("Failed to create the debugfs tree: %d\n", ret); + goto err_configfs_unreg; + } + + return 0; + +err_configfs_unreg: + configfs_unregister_subsystem(&gpio_virtuser_config_subsys); +err_plat_drv_unreg: + mutex_destroy(&gpio_virtuser_config_subsys.su_mutex); + platform_driver_unregister(&gpio_virtuser_driver); + + return ret; +} +module_init(gpio_virtuser_init); + +static void __exit gpio_virtuser_exit(void) +{ + configfs_unregister_subsystem(&gpio_virtuser_config_subsys); + mutex_destroy(&gpio_virtuser_config_subsys.su_mutex); + platform_driver_unregister(&gpio_virtuser_driver); + debugfs_remove_recursive(gpio_virtuser_dbg_root); +} +module_exit(gpio_virtuser_exit); + +MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>"); +MODULE_DESCRIPTION("Virtual GPIO consumer module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index bb063b81cee6..69cd2be9c7f3 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -976,7 +976,7 @@ __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int int i; /* Try first from _DSD */ - for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { + for (i = 0; i < gpio_suffix_count; i++) { if (con_id) { snprintf(propname, sizeof(propname), "%s-%s", con_id, gpio_suffixes[i]); @@ -1453,7 +1453,7 @@ int acpi_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) unsigned int i; /* Try first from _DSD */ - for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { + for (i = 0; i < gpio_suffix_count; i++) { if (con_id) snprintf(propname, sizeof(propname), "%s-%s", con_id, gpio_suffixes[i]); diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 5639abce6ec5..ef08b23a56e2 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1132,6 +1132,14 @@ static void edge_detector_stop(struct line *line) /* do not change line->level - see comment in debounced_value() */ } +static int edge_detector_fifo_init(struct linereq *req) +{ + if (kfifo_initialized(&req->events)) + return 0; + + return kfifo_alloc(&req->events, req->event_buffer_size, GFP_KERNEL); +} + static int edge_detector_setup(struct line *line, struct gpio_v2_line_config *lc, unsigned int line_idx, u64 edflags) @@ -1143,9 +1151,8 @@ static int edge_detector_setup(struct line *line, char *label; eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS; - if (eflags && !kfifo_initialized(&line->req->events)) { - ret = kfifo_alloc(&line->req->events, - line->req->event_buffer_size, GFP_KERNEL); + if (eflags) { + ret = edge_detector_fifo_init(line->req); if (ret) return ret; } @@ -1197,8 +1204,6 @@ static int edge_detector_update(struct line *line, struct gpio_v2_line_config *lc, unsigned int line_idx, u64 edflags) { - u64 eflags; - int ret; u64 active_edflags = READ_ONCE(line->edflags); unsigned int debounce_period_us = gpio_v2_line_config_debounce_period(lc, line_idx); @@ -1214,14 +1219,9 @@ static int edge_detector_update(struct line *line, * ensure event fifo is initialised if edge detection * is now enabled. */ - eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS; - if (eflags && !kfifo_initialized(&line->req->events)) { - ret = kfifo_alloc(&line->req->events, - line->req->event_buffer_size, - GFP_KERNEL); - if (ret) - return ret; - } + if (edflags & GPIO_V2_LINE_EDGE_FLAGS) + return edge_detector_fifo_init(line->req); + return 0; } @@ -1648,16 +1648,15 @@ static ssize_t linereq_read(struct file *file, char __user *buf, return ret; } - ret = kfifo_out(&lr->events, &le, 1); - } - if (ret != 1) { - /* - * This should never happen - we were holding the - * lock from the moment we learned the fifo is no - * longer empty until now. - */ - ret = -EIO; - break; + if (kfifo_out(&lr->events, &le, 1) != 1) { + /* + * This should never happen - we hold the + * lock from the moment we learned the fifo + * is no longer empty until now. + */ + WARN(1, "failed to read from non-empty kfifo"); + return -EIO; + } } if (copy_to_user(buf + bytes_read, &le, sizeof(le))) @@ -1780,6 +1779,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) mutex_init(&lr->config_mutex); init_waitqueue_head(&lr->wait); + INIT_KFIFO(lr->events); lr->event_buffer_size = ulr.event_buffer_size; if (lr->event_buffer_size == 0) lr->event_buffer_size = ulr.num_lines * 16; @@ -2000,16 +2000,15 @@ static ssize_t lineevent_read(struct file *file, char __user *buf, return ret; } - ret = kfifo_out(&le->events, &ge, 1); - } - if (ret != 1) { - /* - * This should never happen - we were holding the lock - * from the moment we learned the fifo is no longer - * empty until now. - */ - ret = -EIO; - break; + if (kfifo_out(&le->events, &ge, 1) != 1) { + /* + * This should never happen - we hold the + * lock from the moment we learned the fifo + * is no longer empty until now. + */ + WARN(1, "failed to read from non-empty kfifo"); + return -EIO; + } } if (copy_to_user(buf + bytes_read, &ge, ge_size)) @@ -2712,12 +2711,15 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, if (count < event_size) return -EINVAL; #endif - ret = kfifo_out(&cdev->events, &event, 1); - } - if (ret != 1) { - ret = -EIO; - break; - /* We should never get here. See lineevent_read(). */ + if (kfifo_out(&cdev->events, &event, 1) != 1) { + /* + * This should never happen - we hold the + * lock from the moment we learned the fifo + * is no longer empty until now. + */ + WARN(1, "failed to read from non-empty kfifo"); + return -EIO; + } } #ifdef CONFIG_GPIO_CDEV_V1 diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 89d5e64cf68b..f6af5e7be4d1 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -103,7 +103,7 @@ int of_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) if (ret > 0) return ret; - for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { + for (i = 0; i < gpio_suffix_count; i++) { if (con_id) snprintf(propname, sizeof(propname), "%s-%s", con_id, gpio_suffixes[i]); @@ -694,7 +694,7 @@ struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id, unsigned int i; /* Try GPIO property "foo-gpios" and "foo-gpio" */ - for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { + for (i = 0; i < gpio_suffix_count; i++) { if (con_id) snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id, gpio_suffixes[i]); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index fa62367ee929..edaeee53db75 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/acpi.h> +#include <linux/array_size.h> #include <linux/bitmap.h> #include <linux/cleanup.h> #include <linux/compat.h> @@ -17,6 +18,7 @@ #include <linux/list.h> #include <linux/lockdep.h> #include <linux/module.h> +#include <linux/nospec.h> #include <linux/of.h> #include <linux/pinctrl/consumer.h> #include <linux/seq_file.h> @@ -88,6 +90,9 @@ DEFINE_STATIC_SRCU(gpio_devices_srcu); static DEFINE_MUTEX(gpio_machine_hogs_mutex); static LIST_HEAD(gpio_machine_hogs); +const char *const gpio_suffixes[] = { "gpios", "gpio" }; +const size_t gpio_suffix_count = ARRAY_SIZE(gpio_suffixes); + static void gpiochip_free_hogs(struct gpio_chip *gc); static int gpiochip_add_irqchip(struct gpio_chip *gc, struct lock_class_key *lock_key, @@ -105,16 +110,16 @@ const char *gpiod_get_label(struct gpio_desc *desc) unsigned long flags; flags = READ_ONCE(desc->flags); - if (test_bit(FLAG_USED_AS_IRQ, &flags) && - !test_bit(FLAG_REQUESTED, &flags)) - return "interrupt"; - - if (!test_bit(FLAG_REQUESTED, &flags)) - return NULL; label = srcu_dereference_check(desc->label, &desc->gdev->desc_srcu, srcu_read_lock_held(&desc->gdev->desc_srcu)); + if (test_bit(FLAG_USED_AS_IRQ, &flags)) + return label->str ?: "interrupt"; + + if (!test_bit(FLAG_REQUESTED, &flags)) + return NULL; + return label->str; } @@ -174,7 +179,6 @@ struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, { return gpio_device_get_desc(gc->gpiodev, hwnum); } -EXPORT_SYMBOL_GPL(gpiochip_get_desc); /** * gpio_device_get_desc() - get the GPIO descriptor corresponding to the given @@ -198,7 +202,7 @@ gpio_device_get_desc(struct gpio_device *gdev, unsigned int hwnum) if (hwnum >= gdev->ngpio) return ERR_PTR(-EINVAL); - return &gdev->descs[hwnum]; + return &gdev->descs[array_index_nospec(hwnum, gdev->ngpio)]; } EXPORT_SYMBOL_GPL(gpio_device_get_desc); @@ -485,7 +489,7 @@ static struct gpio_desc *gpio_name_to_desc(const char * const name) * 1. Non-unique names are still accepted, * 2. Name collisions within the same GPIO chip are not reported. */ -static int gpiochip_set_desc_names(struct gpio_chip *gc) +static void gpiochip_set_desc_names(struct gpio_chip *gc) { struct gpio_device *gdev = gc->gpiodev; int i; @@ -504,8 +508,6 @@ static int gpiochip_set_desc_names(struct gpio_chip *gc) /* Then add all names to the GPIO descriptors */ for (i = 0; i != gc->ngpio; ++i) gdev->descs[i].name = gc->names[i]; - - return 0; } /* @@ -999,11 +1001,9 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, INIT_LIST_HEAD(&gdev->pin_ranges); #endif - if (gc->names) { - ret = gpiochip_set_desc_names(gc); - if (ret) - goto err_cleanup_desc_srcu; - } + if (gc->names) + gpiochip_set_desc_names(gc); + ret = gpiochip_set_names(gc); if (ret) goto err_cleanup_desc_srcu; @@ -4798,11 +4798,11 @@ static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev) for_each_gpio_desc(gc, desc) { guard(srcu)(&desc->gdev->desc_srcu); - if (test_bit(FLAG_REQUESTED, &desc->flags)) { + is_irq = test_bit(FLAG_USED_AS_IRQ, &desc->flags); + if (is_irq || test_bit(FLAG_REQUESTED, &desc->flags)) { gpiod_get_direction(desc); is_out = test_bit(FLAG_IS_OUT, &desc->flags); value = gpio_chip_get_value(gc, desc); - is_irq = test_bit(FLAG_USED_AS_IRQ, &desc->flags); active_low = test_bit(FLAG_ACTIVE_LOW, &desc->flags); seq_printf(s, " gpio-%-3u (%-20.20s|%-20.20s) %s %s %s%s\n", gpio, desc->name ?: "", gpiod_get_label(desc), diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 48e086c2f416..4de0bf1a62d3 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -90,7 +90,8 @@ static inline struct gpio_device *to_gpio_device(struct device *dev) } /* gpio suffixes used for ACPI and device tree lookup */ -static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" }; +extern const char *const gpio_suffixes[]; +extern const size_t gpio_suffix_count; /** * struct gpio_array - Opaque descriptor for a structure of GPIO array attributes @@ -242,6 +243,7 @@ int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce); int gpiod_hog(struct gpio_desc *desc, const char *name, unsigned long lflags, enum gpiod_flags dflags); int gpiochip_get_ngpios(struct gpio_chip *gc, struct device *dev); +struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum); const char *gpiod_get_label(struct gpio_desc *desc); /* |