forked from mirrors/linux
		
	 656cc2e892
			
		
	
	
		656cc2e892
		
	
	
	
	
		
			
			Creating a virtuser device via configfs with multiple conn_ids fails due
to incorrect indexing of lookup entries. Correct the indexing logic to
ensure proper functionality when multiple gpio_virtuser_lookup are
created.
Fixes: 91581c4b3f ("gpio: virtuser: new virtual testing driver for the GPIO API")
Signed-off-by: Koichiro Den <koichiro.den@canonical.com>
Link: https://lore.kernel.org/r/20250103141829.430662-3-koichiro.den@canonical.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
		
	
			
		
			
				
	
	
		
			1816 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1816 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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;
 | |
| 	strscpy(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;
 | |
| 	unsigned int i = 0, idx;
 | |
| 
 | |
| 	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) {
 | |
| 		idx = 0;
 | |
| 		list_for_each_entry(entry, &lookup->entry_list, siblings) {
 | |
| 			table->table[i++] =
 | |
| 				GPIO_LOOKUP_IDX(entry->key,
 | |
| 						entry->offset < 0 ? U16_MAX : entry->offset,
 | |
| 						lookup->con_id, idx++, entry->flags);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	gpiod_add_lookup_table(table);
 | |
| 	dev->lookup_table = no_free_ptr(table);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gpio_virtuser_remove_lookup_table(struct gpio_virtuser_device *dev)
 | |
| {
 | |
| 	gpiod_remove_lookup_table(dev->lookup_table);
 | |
| 	kfree(dev->lookup_table->dev_id);
 | |
| 	kfree(dev->lookup_table);
 | |
| 	dev->lookup_table = NULL;
 | |
| }
 | |
| 
 | |
| 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)
 | |
| 		goto err_remove_swnode;
 | |
| 
 | |
| 	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)) {
 | |
| 		ret = PTR_ERR(pdev);
 | |
| 		bus_unregister_notifier(&platform_bus_type, &dev->bus_notifier);
 | |
| 		goto err_remove_lookup_table;
 | |
| 	}
 | |
| 
 | |
| 	wait_for_completion(&dev->probe_completion);
 | |
| 	bus_unregister_notifier(&platform_bus_type, &dev->bus_notifier);
 | |
| 
 | |
| 	if (!dev->driver_bound) {
 | |
| 		ret = -ENXIO;
 | |
| 		goto err_unregister_pdev;
 | |
| 	}
 | |
| 
 | |
| 	dev->pdev = pdev;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_unregister_pdev:
 | |
| 	platform_device_unregister(pdev);
 | |
| err_remove_lookup_table:
 | |
| 	gpio_virtuser_remove_lookup_table(dev);
 | |
| err_remove_swnode:
 | |
| 	fwnode_remove_software_node(swnode);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 	gpio_virtuser_remove_lookup_table(dev);
 | |
| 	fwnode_remove_software_node(swnode);
 | |
| 	dev->pdev = NULL;
 | |
| }
 | |
| 
 | |
| 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");
 |