summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/admin-guide/gpio/gpio-virtuser.rst177
-rw-r--r--Documentation/admin-guide/gpio/index.rst1
-rw-r--r--drivers/gpio/Kconfig12
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-virtuser.c1807
5 files changed, 1998 insertions, 0 deletions
diff --git a/Documentation/admin-guide/gpio/gpio-virtuser.rst b/Documentation/admin-guide/gpio/gpio-virtuser.rst
new file mode 100644
index 000000000000..2aca70db9f3b
--- /dev/null
+++ b/Documentation/admin-guide/gpio/gpio-virtuser.rst
@@ -0,0 +1,177 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+Virtual GPIO Consumer
+=====================
+
+The virtual GPIO Consumer module allows users to instantiate virtual devices
+that request GPIOs and then control their behavior over debugfs. Virtual
+consumer devices can be instantiated from device-tree or over configfs.
+
+A virtual consumer uses the driver-facing GPIO APIs and allows to cover it with
+automated tests driven by user-space. The GPIOs are requested using
+``gpiod_get_array()`` and so we support multiple GPIOs per connector ID.
+
+Creating GPIO consumers
+-----------------------
+
+The gpio-consumer module registers a configfs subsystem called
+``'gpio-virtuser'``. For details of the configfs filesystem, please refer to
+the configfs documentation.
+
+The user can create a hierarchy of configfs groups and items as well as modify
+values of exposed attributes. Once the consumer is instantiated, this hierarchy
+will be translated to appropriate device properties. The general structure is:
+
+**Group:** ``/config/gpio-virtuser``
+
+This is the top directory of the gpio-consumer configfs tree.
+
+**Group:** ``/config/gpio-consumer/example-name``
+
+**Attribute:** ``/config/gpio-consumer/example-name/live``
+
+**Attribute:** ``/config/gpio-consumer/example-name/dev_name``
+
+This is a directory representing a GPIO consumer device.
+
+The read-only ``dev_name`` attribute exposes the name of the device as it will
+appear in the system on the platform bus. This is useful for locating the
+associated debugfs directory under
+``/sys/kernel/debug/gpio-virtuser/$dev_name``.
+
+The ``'live'`` attribute allows to trigger the actual creation of the device
+once it's fully configured. The accepted values are: ``'1'`` to enable the
+virtual device and ``'0'`` to disable and tear it down.
+
+Creating GPIO lookup tables
+---------------------------
+
+Users can create a number of configfs groups under the device group:
+
+**Group:** ``/config/gpio-consumer/example-name/con_id``
+
+The ``'con_id'`` directory represents a single GPIO lookup and its value maps
+to the ``'con_id'`` argument of the ``gpiod_get()`` function. For example:
+``con_id`` == ``'reset'`` maps to the ``reset-gpios`` device property.
+
+Users can assign a number of GPIOs to each lookup. Each GPIO is a sub-directory
+with a user-defined name under the ``'con_id'`` group.
+
+**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/key``
+
+**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/offset``
+
+**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/drive``
+
+**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/pull``
+
+**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/active_low``
+
+**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/transitory``
+
+This is a group describing a single GPIO in the ``con_id-gpios`` property.
+
+For virtual consumers created using configfs we use machine lookup tables so
+this group can be considered as a mapping between the filesystem and the fields
+of a single entry in ``'struct gpiod_lookup'``.
+
+The ``'key'`` attribute represents either the name of the chip this GPIO
+belongs to or the GPIO line name. This depends on the value of the ``'offset'``
+attribute: if its value is >= 0, then ``'key'`` represents the label of the
+chip to lookup while ``'offset'`` represents the offset of the line in that
+chip. If ``'offset'`` is < 0, then ``'key'`` represents the name of the line.
+
+The remaining attributes map to the ``'flags'`` field of the GPIO lookup
+struct. The first two take string values as arguments:
+
+**``'drive'``:** ``'push-pull'``, ``'open-drain'``, ``'open-source'``
+**``'pull'``:** ``'pull-up'``, ``'pull-down'``, ``'pull-disabled'``, ``'as-is'``
+
+``'active_low'`` and ``'transitory'`` are boolean attributes.
+
+Activating GPIO consumers
+-------------------------
+
+Once the confiuration is complete, the ``'live'`` attribute must be set to 1 in
+order to instantiate the consumer. It can be set back to 0 to destroy the
+virtual device. The module will synchronously wait for the new simulated device
+to be successfully probed and if this doesn't happen, writing to ``'live'`` will
+result in an error.
+
+Device-tree
+-----------
+
+Virtual GPIO consumers can also be defined in device-tree. The compatible string
+must be: ``"gpio-virtuser"`` with at least one property following the
+standardized GPIO pattern.
+
+An example device-tree code defining a virtual GPIO consumer:
+
+.. code-block :: none
+
+ gpio-virt-consumer {
+ compatible = "gpio-virtuser";
+
+ foo-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>, <&gpio1 2 0>;
+ bar-gpios = <&gpio0 6 0>;
+ };
+
+Controlling virtual GPIO consumers
+----------------------------------
+
+Once active, the device will export debugfs attributes for controlling GPIO
+arrays as well as each requested GPIO line separately. Let's consider the
+following device property: ``foo-gpios = <&gpio0 0 0>, <&gpio0 4 0>;``.
+
+The following debugfs attribute groups will be created:
+
+**Group:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo/``
+
+This is the group that will contain the attributes for the entire GPIO array.
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo/values``
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo/values_atomic``
+
+Both attributes allow to read and set arrays of GPIO values. User must pass
+exactly the number of values that the array contains in the form of a string
+containing zeroes and ones representing inactive and active GPIO states
+respectively. In this example: ``echo 11 > values``.
+
+The ``values_atomic`` attribute works the same as ``values`` but the kernel
+will execute the GPIO driver callbacks in interrupt context.
+
+**Group:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/``
+
+This is a group that represents a single GPIO with ``$index`` being its offset
+in the array.
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/consumer``
+
+Allows to set and read the consumer label of the GPIO line.
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/debounce``
+
+Allows to set and read the debounce period of the GPIO line.
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/direction``
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/direction_atomic``
+
+These two attributes allow to set the direction of the GPIO line. They accept
+"input" and "output" as values. The atomic variant executes the driver callback
+in interrupt context.
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/interrupts``
+
+If the line is requested in input mode, writing ``1`` to this attribute will
+make the module listen for edge interrupts on the GPIO. Writing ``0`` disables
+the monitoring. Reading this attribute returns the current number of registered
+interrupts (both edges).
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/value``
+
+**Attribute:** ``/sys/kernel/debug/gpio-virtuser/$dev_name/gpiod:foo:$index/value_atomic``
+
+Both attributes allow to read and set values of individual requested GPIO lines.
+They accept the following values: ``1`` and ``0``.
diff --git a/Documentation/admin-guide/gpio/index.rst b/Documentation/admin-guide/gpio/index.rst
index 460afd29617e..712f379731cb 100644
--- a/Documentation/admin-guide/gpio/index.rst
+++ b/Documentation/admin-guide/gpio/index.rst
@@ -10,6 +10,7 @@ GPIO
Character Device Userspace API <../../userspace-api/gpio/chardev>
gpio-aggregator
gpio-sim
+ gpio-virtuser
Obsolete APIs <obsolete>
.. only:: subproject and html
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 2b9bd1893863..ba93e640a8ab 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1908,6 +1908,18 @@ config GPIO_SLOPPY_LOGIC_ANALYZER
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 9889f5b962bf..64dd6d9d730d 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -182,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-virtuser.c b/drivers/gpio/gpio-virtuser.c
new file mode 100644
index 000000000000..2ac09c6a3763
--- /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(buf, "input") == 0) {
+ dir = 1;
+ } else if (strcmp(buf, "output-high") == 0) {
+ dir = 0;
+ val = 1;
+ } else if (strcmp(buf, "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);