Linux-GPIO Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
@ 2019-11-27 13:35 Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 1/8] gpiolib: use 'unsigned int' instead of 'unsigned' in gpio_set_config() Bartosz Golaszewski
                   ` (8 more replies)
  0 siblings, 9 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

When discussing the recent user-space changes with Kent and while working
on dbus API for libgpiod I noticed that we really don't have any way of
keeping the line info synchronized between the kernel and user-space
processes. We can of course periodically re-read the line information or
even do it every time we want to read a property but this isn't optimal.

This series adds a new ioctl() that allows user-space to retrieve a
file-descriptor which can then be polled for events emitted by the kernel
when the line is requested, released or its status changed. This of course
doesn't require the line to be requested. Multiple user-space processes
can watch the same lines.

The first couple patches just fix some issues I noticed when implementing
the new interface. Patch 7/8 provides the actual ioctl() implementation
while patch 8/8 adds a simple user-space program to tools that can be used
to watch the line info changes.

Bartosz Golaszewski (8):
  gpiolib: use 'unsigned int' instead of 'unsigned' in gpio_set_config()
  gpiolib: have a single place of calling set_config()
  gpiolib: convert the type of hwnum to unsigned int in
    gpiochip_get_desc()
  gpiolib: use gpiochip_get_desc() in linehandle_create()
  gpiolib: use gpiochip_get_desc() in lineevent_create()
  gpiolib: actually protect the line event kfifo with mutex
  gpiolib: add new ioctl() for monitoring changes in line info
  tools: gpio: implement gpio-watch

 drivers/gpio/gpiolib.c      | 267 +++++++++++++++++++++++++++++++++---
 drivers/gpio/gpiolib.h      |   4 +-
 include/linux/gpio/driver.h |   3 +-
 include/uapi/linux/gpio.h   |  36 +++++
 tools/gpio/.gitignore       |   1 +
 tools/gpio/Build            |   1 +
 tools/gpio/Makefile         |  11 +-
 tools/gpio/gpio-watch.c     | 114 +++++++++++++++
 8 files changed, 415 insertions(+), 22 deletions(-)
 create mode 100644 tools/gpio/gpio-watch.c

-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 1/8] gpiolib: use 'unsigned int' instead of 'unsigned' in gpio_set_config()
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 2/8] gpiolib: have a single place of calling set_config() Bartosz Golaszewski
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Checkpatch complains about using 'unsigned' instead of 'unsigned int'.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/gpio/gpiolib.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 21b02a0064f8..a31797fe78fa 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -3042,7 +3042,7 @@ EXPORT_SYMBOL_GPL(gpiochip_free_own_desc);
  * rely on gpio_request() having been called beforehand.
  */
 
-static int gpio_set_config(struct gpio_chip *gc, unsigned offset,
+static int gpio_set_config(struct gpio_chip *gc, unsigned int offset,
 			   enum pin_config_param mode)
 {
 	unsigned long config;
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 2/8] gpiolib: have a single place of calling set_config()
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 1/8] gpiolib: use 'unsigned int' instead of 'unsigned' in gpio_set_config() Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 3/8] gpiolib: convert the type of hwnum to unsigned int in gpiochip_get_desc() Bartosz Golaszewski
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Instead of calling the gpiochip's set_config() callback directly and
checking its existence every time - just add a new routine that performs
this check internally. Call it in gpio_set_config() and
gpiod_set_transitory(). Also call it in gpiod_set_debounce() and drop
the check for chip->set() as it's irrelevant to this config option.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/gpio/gpiolib.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index a31797fe78fa..72211407469f 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -3042,6 +3042,15 @@ EXPORT_SYMBOL_GPL(gpiochip_free_own_desc);
  * rely on gpio_request() having been called beforehand.
  */
 
+static int gpio_do_set_config(struct gpio_chip *gc, unsigned int offset,
+			      enum pin_config_param mode)
+{
+	if (!gc->set_config)
+		return -ENOTSUPP;
+
+	return gc->set_config(gc, offset, mode);
+}
+
 static int gpio_set_config(struct gpio_chip *gc, unsigned int offset,
 			   enum pin_config_param mode)
 {
@@ -3060,7 +3069,7 @@ static int gpio_set_config(struct gpio_chip *gc, unsigned int offset,
 	}
 
 	config = PIN_CONF_PACKED(mode, arg);
-	return gc->set_config ? gc->set_config(gc, offset, config) : -ENOTSUPP;
+	return gpio_do_set_config(gc, offset, mode);
 }
 
 static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
@@ -3294,15 +3303,9 @@ int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce)
 
 	VALIDATE_DESC(desc);
 	chip = desc->gdev->chip;
-	if (!chip->set || !chip->set_config) {
-		gpiod_dbg(desc,
-			  "%s: missing set() or set_config() operations\n",
-			  __func__);
-		return -ENOTSUPP;
-	}
 
 	config = pinconf_to_config_packed(PIN_CONFIG_INPUT_DEBOUNCE, debounce);
-	return chip->set_config(chip, gpio_chip_hwgpio(desc), config);
+	return gpio_do_set_config(chip, gpio_chip_hwgpio(desc), config);
 }
 EXPORT_SYMBOL_GPL(gpiod_set_debounce);
 
@@ -3339,7 +3342,7 @@ int gpiod_set_transitory(struct gpio_desc *desc, bool transitory)
 	packed = pinconf_to_config_packed(PIN_CONFIG_PERSIST_STATE,
 					  !transitory);
 	gpio = gpio_chip_hwgpio(desc);
-	rc = chip->set_config(chip, gpio, packed);
+	rc = gpio_do_set_config(chip, gpio, packed);
 	if (rc == -ENOTSUPP) {
 		dev_dbg(&desc->gdev->dev, "Persistence not supported for GPIO %d\n",
 				gpio);
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 3/8] gpiolib: convert the type of hwnum to unsigned int in gpiochip_get_desc()
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 1/8] gpiolib: use 'unsigned int' instead of 'unsigned' in gpio_set_config() Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 2/8] gpiolib: have a single place of calling set_config() Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 4/8] gpiolib: use gpiochip_get_desc() in linehandle_create() Bartosz Golaszewski
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

gpiochip_get_desc() takes a u16 hwnum, but it turns out most users don't
respect that and usually pass an unsigned int. Since implicit casting to
a smaller type is dangerous - let's change the type of hwnum to unsigned
int in gpiochip_get_desc() and in gpiochip_request_own_desc() where the
size of hwnum is not respected either and who's a user of the former.

This is safe as we then check the hwnum against the number of lines
before proceeding in gpiochip_get_desc().

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/gpio/gpiolib.c      | 5 +++--
 drivers/gpio/gpiolib.h      | 3 ++-
 include/linux/gpio/driver.h | 3 ++-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 72211407469f..b3ffb079e323 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -140,7 +140,7 @@ EXPORT_SYMBOL_GPL(gpio_to_desc);
  * in the given chip for the specified hardware number.
  */
 struct gpio_desc *gpiochip_get_desc(struct gpio_chip *chip,
-				    u16 hwnum)
+				    unsigned int hwnum)
 {
 	struct gpio_device *gdev = chip->gpiodev;
 
@@ -2990,7 +2990,8 @@ EXPORT_SYMBOL_GPL(gpiochip_is_requested);
  * A pointer to the GPIO descriptor, or an ERR_PTR()-encoded negative error
  * code on failure.
  */
-struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *chip, u16 hwnum,
+struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *chip,
+					    unsigned int hwnum,
 					    const char *label,
 					    enum gpio_lookup_flags lflags,
 					    enum gpiod_flags dflags)
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index ca9bc1e4803c..a1cbeabadc69 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -78,7 +78,8 @@ struct gpio_array {
 	unsigned long		invert_mask[];
 };
 
-struct gpio_desc *gpiochip_get_desc(struct gpio_chip *chip, u16 hwnum);
+struct gpio_desc *gpiochip_get_desc(struct gpio_chip *chip,
+				    unsigned int hwnum);
 int gpiod_get_array_value_complex(bool raw, bool can_sleep,
 				  unsigned int array_size,
 				  struct gpio_desc **desc_array,
diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h
index e2480ef94c55..4f032de10bae 100644
--- a/include/linux/gpio/driver.h
+++ b/include/linux/gpio/driver.h
@@ -715,7 +715,8 @@ gpiochip_remove_pin_ranges(struct gpio_chip *chip)
 
 #endif /* CONFIG_PINCTRL */
 
-struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *chip, u16 hwnum,
+struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *chip,
+					    unsigned int hwnum,
 					    const char *label,
 					    enum gpio_lookup_flags lflags,
 					    enum gpiod_flags dflags);
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 4/8] gpiolib: use gpiochip_get_desc() in linehandle_create()
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
                   ` (2 preceding siblings ...)
  2019-11-27 13:35 ` [PATCH 3/8] gpiolib: convert the type of hwnum to unsigned int in gpiochip_get_desc() Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 5/8] gpiolib: use gpiochip_get_desc() in lineevent_create() Bartosz Golaszewski
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Unduplicate the ngpio check by simply calling gpiochip_get_desc() and
checking its return value.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/gpio/gpiolib.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index b3ffb079e323..6ef55cc1188b 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -678,14 +678,13 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
 	/* Request each GPIO */
 	for (i = 0; i < handlereq.lines; i++) {
 		u32 offset = handlereq.lineoffsets[i];
-		struct gpio_desc *desc;
+		struct gpio_desc *desc = gpiochip_get_desc(gdev->chip, offset);
 
-		if (offset >= gdev->ngpio) {
-			ret = -EINVAL;
+		if (IS_ERR(desc)) {
+			ret = PTR_ERR(desc);
 			goto out_free_descs;
 		}
 
-		desc = &gdev->descs[offset];
 		ret = gpiod_request(desc, lh->label);
 		if (ret)
 			goto out_free_descs;
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 5/8] gpiolib: use gpiochip_get_desc() in lineevent_create()
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
                   ` (3 preceding siblings ...)
  2019-11-27 13:35 ` [PATCH 4/8] gpiolib: use gpiochip_get_desc() in linehandle_create() Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 6/8] gpiolib: actually protect the line event kfifo with mutex Bartosz Golaszewski
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Unduplicate the ngpio check by simply calling gpiochip_get_desc() and
checking its return value.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/gpio/gpiolib.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 6ef55cc1188b..17796437d7be 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -1009,8 +1009,9 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
 	lflags = eventreq.handleflags;
 	eflags = eventreq.eventflags;
 
-	if (offset >= gdev->ngpio)
-		return -EINVAL;
+	desc = gpiochip_get_desc(gdev->chip, offset);
+	if (IS_ERR(desc))
+		return PTR_ERR(desc);
 
 	/* Return an error if a unknown flag is set */
 	if ((lflags & ~GPIOHANDLE_REQUEST_VALID_FLAGS) ||
@@ -1048,7 +1049,6 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
 		}
 	}
 
-	desc = &gdev->descs[offset];
 	ret = gpiod_request(desc, le->label);
 	if (ret)
 		goto out_free_label;
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 6/8] gpiolib: actually protect the line event kfifo with mutex
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
                   ` (4 preceding siblings ...)
  2019-11-27 13:35 ` [PATCH 5/8] gpiolib: use gpiochip_get_desc() in lineevent_create() Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-27 13:35 ` [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info Bartosz Golaszewski
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

The read_lock mutex is supposed to prevent collisions between reading
and writing to the line event kfifo but it's actually only taken when
the events are being read from it. Also take the lock when adding the
events and checking if kfifo is empty.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/gpio/gpiolib.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 17796437d7be..d094b1be334d 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -818,8 +818,10 @@ static __poll_t lineevent_poll(struct file *filep,
 
 	poll_wait(filep, &le->wait, wait);
 
+	mutex_lock(&le->read_lock);
 	if (!kfifo_is_empty(&le->events))
 		events = EPOLLIN | EPOLLRDNORM;
+	mutex_unlock(&le->read_lock);
 
 	return events;
 }
@@ -838,7 +840,9 @@ static ssize_t lineevent_read(struct file *filep,
 		return -EINVAL;
 
 	do {
+		mutex_lock(&le->read_lock);
 		if (kfifo_is_empty(&le->events)) {
+			mutex_unlock(&le->read_lock);
 			if (filep->f_flags & O_NONBLOCK)
 				return -EAGAIN;
 
@@ -846,6 +850,8 @@ static ssize_t lineevent_read(struct file *filep,
 					!kfifo_is_empty(&le->events));
 			if (ret)
 				return ret;
+		} else {
+			mutex_unlock(&le->read_lock);
 		}
 
 		if (mutex_lock_interruptible(&le->read_lock))
@@ -969,7 +975,9 @@ static irqreturn_t lineevent_irq_thread(int irq, void *p)
 		return IRQ_NONE;
 	}
 
+	mutex_lock(&le->read_lock);
 	ret = kfifo_put(&le->events, ge);
+	mutex_unlock(&le->read_lock);
 	if (ret)
 		wake_up_poll(&le->wait, EPOLLIN);
 
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
                   ` (5 preceding siblings ...)
  2019-11-27 13:35 ` [PATCH 6/8] gpiolib: actually protect the line event kfifo with mutex Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-27 15:24   ` Kent Gibson
  2019-11-27 13:35 ` [PATCH 8/8] tools: gpio: implement gpio-watch Bartosz Golaszewski
  2019-11-29 10:04 ` [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Linus Walleij
  8 siblings, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Currently there is no way for user-space to be informed about changes
in status of GPIO lines e.g. when someone else requests the line or its
config changes. We can only periodically re-read the line-info. This
is fine for simple one-off user-space tools, but any daemon that provides
a centralized access to GPIO chips would benefit hugely from an event
driven line info synchronization.

This patch adds a new ioctl() that allows user-space processes to retrieve
a file-descriptor for given GPIO lines which can be polled for line status
change events.

Currently the events are generated on three types of status changes: when
a line is requested, when it's released and when its config is changed.
The first two are self-explanatory. For the third one: this will only
happen when another user-space process calls the new SET_CONFIG ioctl()
as any changes that can happen from within the kernel (i.e.
set_transitory() or set_debounce()) are of no interest to user-space.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
 drivers/gpio/gpiolib.h    |   1 +
 include/uapi/linux/gpio.h |  36 +++++++
 3 files changed, 255 insertions(+)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index d094b1be334d..be5df4bdf44b 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
 			if (ret)
 				return ret;
 		}
+
+		atomic_notifier_call_chain(&desc->gdev->notifier,
+					   GPIOLINE_CHANGED_CONFIG, desc);
 	}
 	return 0;
 }
@@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
 	return ret;
 }
 
+struct linechanged_fd_state {
+	struct gpio_device *gdev;
+	struct gpio_desc *descs[GPIOHANDLES_MAX];
+	size_t numdescs;
+	wait_queue_head_t waitqueue;
+	DECLARE_KFIFO(events, struct gpioline_changed, 16);
+	struct mutex lock;
+	struct notifier_block changed_nb;
+};
+
+static int linechanged_fd_release(struct inode *inode, struct file *filep)
+{
+	struct linechanged_fd_state *lc_state = filep->private_data;
+
+	atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
+					 &lc_state->changed_nb);
+	put_device(&lc_state->gdev->dev);
+	kfree(lc_state);
+
+	return 0;
+}
+
+static __poll_t linechanged_fd_poll(struct file *filep,
+				    struct poll_table_struct *pollt)
+{
+	struct linechanged_fd_state *lc_state = filep->private_data;
+	__poll_t events = 0;
+
+	poll_wait(filep, &lc_state->waitqueue, pollt);
+
+	mutex_lock(&lc_state->lock);
+	if (!kfifo_is_empty(&lc_state->events))
+		events = EPOLLIN | EPOLLRDNORM;
+	mutex_unlock(&lc_state->lock);
+
+	return events;
+}
+
+static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
+				   size_t count, loff_t *off)
+{
+	struct linechanged_fd_state *lc_state = filep->private_data;
+	unsigned int copied;
+	int ret;
+
+	if (count < sizeof(struct gpioline_changed))
+		return -EINVAL;
+
+	do {
+		mutex_lock(&lc_state->lock);
+		if (kfifo_is_empty(&lc_state->events)) {
+			mutex_unlock(&lc_state->lock);
+			if (filep->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			ret = wait_event_interruptible(lc_state->waitqueue,
+					!kfifo_is_empty(&lc_state->events));
+			if (ret)
+				return ret;
+		} else {
+			mutex_unlock(&lc_state->lock);
+		}
+
+		if (mutex_lock_interruptible(&lc_state->lock))
+			return -ERESTARTSYS;
+
+		ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
+		mutex_unlock(&lc_state->lock);
+		if (ret)
+			return ret;
+
+		if (copied == 0 && (filep->f_flags & O_NONBLOCK))
+			return -EAGAIN;
+	} while (copied == 0);
+
+	return copied;
+}
+
+static const struct file_operations linechanged_fd_fileops = {
+	.release = linechanged_fd_release,
+	.owner = THIS_MODULE,
+	.llseek = noop_llseek,
+	.poll = linechanged_fd_poll,
+	.read = linechanged_fd_read,
+};
+
+static struct linechanged_fd_state *
+to_linechanged_fd_state(struct notifier_block *nb)
+{
+	return container_of(nb, struct linechanged_fd_state, changed_nb);
+}
+
+static int linechanged_fd_notify(struct notifier_block *nb,
+				 unsigned long action, void *data)
+{
+	struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
+	struct gpio_desc *desc = data;
+	struct gpioline_changed chg;
+	int i, ret;
+
+	for (i = 0; i < lc_state->numdescs; i++) {
+		/* Are we watching this desc? */
+		if (desc == lc_state->descs[i]) {
+			/* Yes - prepare the event. */
+			memset(&chg, 0, sizeof(chg));
+			chg.line_offset = gpio_chip_hwgpio(desc);
+			chg.event_type = action;
+
+			mutex_lock(&lc_state->lock);
+			ret = kfifo_put(&lc_state->events, chg);
+			mutex_unlock(&lc_state->lock);
+			if (ret)
+				wake_up_poll(&lc_state->waitqueue, EPOLLIN);
+
+			return NOTIFY_OK;
+		}
+	}
+
+	return NOTIFY_DONE;
+}
+
+static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
+{
+	struct gpioline_changed_fd_request changed_req;
+	struct linechanged_fd_state *lc_state;
+	struct gpio_desc *desc;
+	struct file *file;
+	int ret, i, fd;
+	u32 offset;
+
+	ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
+	if (ret)
+		return -EFAULT;
+
+	if ((changed_req.num_lines == 0) ||
+	    (changed_req.num_lines > GPIOHANDLES_MAX))
+		return -EINVAL;
+
+	lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
+	if (!lc_state)
+		return -ENOMEM;
+
+	lc_state->gdev = gdev;
+	get_device(&gdev->dev);
+
+	for (i = 0; i < changed_req.num_lines; i++) {
+		offset = changed_req.lineoffsets[i];
+		desc = gpiochip_get_desc(gdev->chip, offset);
+		if (IS_ERR(desc)) {
+			ret = PTR_ERR(desc);
+			goto out_free_lc_state;
+		}
+
+		lc_state->descs[i] = desc;
+	}
+
+	lc_state->numdescs = changed_req.num_lines;
+
+	init_waitqueue_head(&lc_state->waitqueue);
+	INIT_KFIFO(lc_state->events);
+	mutex_init(&lc_state->lock);
+
+	lc_state->changed_nb.notifier_call = linechanged_fd_notify;
+
+	ret = atomic_notifier_chain_register(&gdev->notifier,
+					     &lc_state->changed_nb);
+	if (ret)
+		goto out_free_lc_state;
+
+	fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
+	if (fd < 0) {
+		ret = fd;
+		goto out_unregister_notifier;
+	}
+
+	file = anon_inode_getfile("gpio-line-changed-fd",
+				  &linechanged_fd_fileops,
+				  lc_state, O_RDONLY | O_CLOEXEC);
+	if (IS_ERR(file)) {
+		ret = PTR_ERR(file);
+		goto out_put_unused_fd;
+	}
+
+	changed_req.fd = fd;
+	ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
+	if (ret) {
+		fput(file);
+		put_unused_fd(fd);
+		return -EFAULT;
+	}
+
+	fd_install(fd, file);
+
+	return 0;
+
+out_put_unused_fd:
+	put_unused_fd(fd);
+out_unregister_notifier:
+	atomic_notifier_chain_unregister(&gdev->notifier,
+					 &lc_state->changed_nb);
+out_free_lc_state:
+	kfree(lc_state);
+
+	return ret;
+}
+
 /*
  * gpio_ioctl() - ioctl handler for the GPIO chardev
  */
@@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 		return linehandle_create(gdev, ip);
 	} else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
 		return lineevent_create(gdev, ip);
+	} else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
+		return linechanged_fd_create(gdev, ip);
 	}
 	return -EINVAL;
 }
@@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
 	for (i = 0; i < chip->ngpio; i++)
 		gdev->descs[i].gdev = gdev;
 
+	ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
+
 #ifdef CONFIG_PINCTRL
 	INIT_LIST_HEAD(&gdev->pin_ranges);
 #endif
@@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
 		spin_lock_irqsave(&gpio_lock, flags);
 	}
 done:
+	atomic_notifier_call_chain(&desc->gdev->notifier,
+				   GPIOLINE_CHANGED_REQUESTED, desc);
 	spin_unlock_irqrestore(&gpio_lock, flags);
 	return ret;
 }
@@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
 		ret = true;
 	}
 
+	atomic_notifier_call_chain(&desc->gdev->notifier,
+				   GPIOLINE_CHANGED_RELEASED, desc);
 	spin_unlock_irqrestore(&gpio_lock, flags);
 	return ret;
 }
@@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
 		if (ret != -ENOTSUPP)
 			return ret;
 	}
+
 	return 0;
 }
 
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index a1cbeabadc69..8e3969616cfe 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -54,6 +54,7 @@ struct gpio_device {
 	const char		*label;
 	void			*data;
 	struct list_head        list;
+	struct atomic_notifier_head notifier;
 
 #ifdef CONFIG_PINCTRL
 	/*
diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
index 799cf823d493..c61429467dd4 100644
--- a/include/uapi/linux/gpio.h
+++ b/include/uapi/linux/gpio.h
@@ -59,6 +59,40 @@ struct gpioline_info {
 /* Maximum number of requested handles */
 #define GPIOHANDLES_MAX 64
 
+/**
+ * struct gpioline_changed_fd_request - Information about a linechanged fd
+ * request
+ * @lineoffsets: an array of desired lines, specified by offset index for the
+ * associated GPIO device
+ * @num_lines: number of lines requested in this request, i.e. the number of
+ * valid fields in the above arrays, set to 1 to request a single line
+ * @fd: if successful this field will contain a valid anonymous file handle
+ */
+struct gpioline_changed_fd_request {
+	__u32 lineoffsets[GPIOHANDLES_MAX];
+	__u32 num_lines;
+	int fd;
+};
+
+/* Possible line status change events */
+enum {
+	GPIOLINE_CHANGED_REQUESTED = 1,
+	GPIOLINE_CHANGED_RELEASED,
+	GPIOLINE_CHANGED_CONFIG,
+};
+
+/**
+ * struct gpioline_changed - Information about a change in status
+ * of a GPIO line
+ * @line_offset: offset of the line that changed relative to the gpiochip
+ * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
+ * and GPIOLINE_CHANGED_CONFIG
+ */
+struct gpioline_changed {
+	__u32 line_offset;
+	__u32 event_type;
+};
+
 /* Linerequest flags */
 #define GPIOHANDLE_REQUEST_INPUT	(1UL << 0)
 #define GPIOHANDLE_REQUEST_OUTPUT	(1UL << 1)
@@ -176,6 +210,8 @@ struct gpioevent_data {
 
 #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
 #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
+#define GPIO_GET_LINECHANGED_FD_IOCTL \
+		_IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
 #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
 #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
 
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 8/8] tools: gpio: implement gpio-watch
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
                   ` (6 preceding siblings ...)
  2019-11-27 13:35 ` [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info Bartosz Golaszewski
@ 2019-11-27 13:35 ` Bartosz Golaszewski
  2019-11-29 10:04 ` [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Linus Walleij
  8 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 13:35 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij; +Cc: linux-gpio, linux-kernel, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add a simple program that allows to test the new LINECHANGED_FD ioctl().

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 tools/gpio/.gitignore   |   1 +
 tools/gpio/Build        |   1 +
 tools/gpio/Makefile     |  11 +++-
 tools/gpio/gpio-watch.c | 114 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 126 insertions(+), 1 deletion(-)
 create mode 100644 tools/gpio/gpio-watch.c

diff --git a/tools/gpio/.gitignore b/tools/gpio/.gitignore
index a94c0e83b209..fffd32969d62 100644
--- a/tools/gpio/.gitignore
+++ b/tools/gpio/.gitignore
@@ -1,4 +1,5 @@
 gpio-event-mon
 gpio-hammer
 lsgpio
+gpio-watch
 include/linux/gpio.h
diff --git a/tools/gpio/Build b/tools/gpio/Build
index 4141f35837db..67c7b7f6a717 100644
--- a/tools/gpio/Build
+++ b/tools/gpio/Build
@@ -2,3 +2,4 @@ gpio-utils-y += gpio-utils.o
 lsgpio-y += lsgpio.o gpio-utils.o
 gpio-hammer-y += gpio-hammer.o gpio-utils.o
 gpio-event-mon-y += gpio-event-mon.o gpio-utils.o
+gpio-watch-y += gpio-watch.o
diff --git a/tools/gpio/Makefile b/tools/gpio/Makefile
index 6080de58861f..842287e42c83 100644
--- a/tools/gpio/Makefile
+++ b/tools/gpio/Makefile
@@ -18,7 +18,7 @@ MAKEFLAGS += -r
 
 override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
 
-ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon
+ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon gpio-watch
 ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
 
 all: $(ALL_PROGRAMS)
@@ -66,6 +66,15 @@ $(GPIO_EVENT_MON_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
 $(OUTPUT)gpio-event-mon: $(GPIO_EVENT_MON_IN)
 	$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
 
+#
+# gpio-watch
+#
+GPIO_WATCH_IN := $(OUTPUT)gpio-watch-in.o
+$(GPIO_WATCH_IN): prepare FORCE
+	$(Q)$(MAKE) $(build)=gpio-watch
+$(OUTPUT)gpio-watch: $(GPIO_WATCH_IN)
+	$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
+
 clean:
 	rm -f $(ALL_PROGRAMS)
 	rm -f $(OUTPUT)include/linux/gpio.h
diff --git a/tools/gpio/gpio-watch.c b/tools/gpio/gpio-watch.c
new file mode 100644
index 000000000000..7107ab24be52
--- /dev/null
+++ b/tools/gpio/gpio-watch.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gpio-watch - monitor unrequested lines for property changes using the
+ *              character device
+ *
+ * Copyright (C) 2019 BayLibre SAS
+ * Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/gpio.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+static bool isnumber(const char *str)
+{
+	size_t sz = strlen(str);
+	int i;
+
+	for (i = 0; i < sz; i++) {
+		if (!isdigit(str[i]))
+			return false;
+	}
+
+	return true;
+}
+
+int main(int argc, char **argv)
+{
+	struct gpioline_changed_fd_request lc_req;
+	struct gpioline_changed chg;
+	struct pollfd pfd;
+	int fd, i, j, ret;
+	char *event;
+	ssize_t rd;
+
+	if (argc < 3)
+		goto err_usage;
+
+	fd = open(argv[1], O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		perror("unable to open gpiochip");
+		return EXIT_FAILURE;
+	}
+
+	memset(&lc_req, 0, sizeof(lc_req));
+
+	for (i = 0, j = 2; i < argc - 2; i++, j++) {
+		if (!isnumber(argv[j]))
+			goto err_usage;
+
+		lc_req.lineoffsets[i] = atoi(argv[j]);
+	}
+
+	lc_req.num_lines = argc - 2;
+
+	ret = ioctl(fd, GPIO_GET_LINECHANGED_FD_IOCTL, &lc_req);
+	if (ret < 0) {
+		perror("unable to retrieve the linechanged fd");
+		return EXIT_FAILURE;
+	}
+
+	pfd.fd = lc_req.fd;
+	pfd.events = POLLIN | POLLPRI;
+
+	for (;;) {
+		ret = poll(&pfd, 1, 5000);
+		if (ret < 0) {
+			perror("error polling the linechanged fd");
+			return EXIT_FAILURE;
+		} else if (ret > 0) {
+			memset(&chg, 0, sizeof(chg));
+			rd = read(pfd.fd, &chg, sizeof(chg));
+			if (rd < 0 || rd != sizeof(chg)) {
+				if (rd != sizeof(chg))
+					errno = EIO;
+
+				perror("error reading line change event");
+				return EXIT_FAILURE;
+			}
+
+			switch (chg.event_type) {
+			case GPIOLINE_CHANGED_REQUESTED:
+				event = "requested";
+				break;
+			case GPIOLINE_CHANGED_RELEASED:
+				event = "released";
+				break;
+			case GPIOLINE_CHANGED_CONFIG:
+				event = "config changed";
+				break;
+			default:
+				fprintf(stderr,
+					"invalid event type received from the kernel\n");
+				return EXIT_FAILURE;
+			}
+
+			printf("line %u: %s\n", chg.line_offset, event);
+		}
+	}
+
+	return 0;
+
+err_usage:
+	printf("%s: <gpiochip> <line0> <line1> ...\n", argv[0]);
+	return EXIT_FAILURE;
+}
-- 
2.23.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-27 13:35 ` [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info Bartosz Golaszewski
@ 2019-11-27 15:24   ` Kent Gibson
  2019-11-27 15:50     ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Kent Gibson @ 2019-11-27 15:24 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, linux-gpio, linux-kernel, Bartosz Golaszewski

On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> 
> Currently there is no way for user-space to be informed about changes
> in status of GPIO lines e.g. when someone else requests the line or its
> config changes. We can only periodically re-read the line-info. This
> is fine for simple one-off user-space tools, but any daemon that provides
> a centralized access to GPIO chips would benefit hugely from an event
> driven line info synchronization.
> 
> This patch adds a new ioctl() that allows user-space processes to retrieve
> a file-descriptor for given GPIO lines which can be polled for line status
> change events.
> 
> Currently the events are generated on three types of status changes: when
> a line is requested, when it's released and when its config is changed.
> The first two are self-explanatory. For the third one: this will only
> happen when another user-space process calls the new SET_CONFIG ioctl()
> as any changes that can happen from within the kernel (i.e.
> set_transitory() or set_debounce()) are of no interest to user-space.
> 
> Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> ---
>  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
>  drivers/gpio/gpiolib.h    |   1 +
>  include/uapi/linux/gpio.h |  36 +++++++
>  3 files changed, 255 insertions(+)
> 
> diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> index d094b1be334d..be5df4bdf44b 100644
> --- a/drivers/gpio/gpiolib.c
> +++ b/drivers/gpio/gpiolib.c
> @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
>  			if (ret)
>  				return ret;
>  		}
> +
> +		atomic_notifier_call_chain(&desc->gdev->notifier,
> +					   GPIOLINE_CHANGED_CONFIG, desc);
>  	}
>  	return 0;
>  }
> @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
>  	return ret;
>  }
>  
> +struct linechanged_fd_state {
> +	struct gpio_device *gdev;
> +	struct gpio_desc *descs[GPIOHANDLES_MAX];
> +	size_t numdescs;
> +	wait_queue_head_t waitqueue;
> +	DECLARE_KFIFO(events, struct gpioline_changed, 16);
> +	struct mutex lock;
> +	struct notifier_block changed_nb;
> +};
> +
> +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> +{
> +	struct linechanged_fd_state *lc_state = filep->private_data;
> +
> +	atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> +					 &lc_state->changed_nb);
> +	put_device(&lc_state->gdev->dev);
> +	kfree(lc_state);
> +
> +	return 0;
> +}
> +
> +static __poll_t linechanged_fd_poll(struct file *filep,
> +				    struct poll_table_struct *pollt)
> +{
> +	struct linechanged_fd_state *lc_state = filep->private_data;
> +	__poll_t events = 0;
> +
> +	poll_wait(filep, &lc_state->waitqueue, pollt);
> +
> +	mutex_lock(&lc_state->lock);
> +	if (!kfifo_is_empty(&lc_state->events))
> +		events = EPOLLIN | EPOLLRDNORM;
> +	mutex_unlock(&lc_state->lock);
> +
> +	return events;
> +}
> +
> +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> +				   size_t count, loff_t *off)
> +{
> +	struct linechanged_fd_state *lc_state = filep->private_data;
> +	unsigned int copied;
> +	int ret;
> +
> +	if (count < sizeof(struct gpioline_changed))
> +		return -EINVAL;
> +
> +	do {
> +		mutex_lock(&lc_state->lock);
> +		if (kfifo_is_empty(&lc_state->events)) {
> +			mutex_unlock(&lc_state->lock);
> +			if (filep->f_flags & O_NONBLOCK)
> +				return -EAGAIN;
> +
> +			ret = wait_event_interruptible(lc_state->waitqueue,
> +					!kfifo_is_empty(&lc_state->events));
> +			if (ret)
> +				return ret;
> +		} else {
> +			mutex_unlock(&lc_state->lock);
> +		}
> +
> +		if (mutex_lock_interruptible(&lc_state->lock))
> +			return -ERESTARTSYS;
> +
> +		ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> +		mutex_unlock(&lc_state->lock);
> +		if (ret)
> +			return ret;
> +
> +		if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> +			return -EAGAIN;
> +	} while (copied == 0);
> +
> +	return copied;
> +}
> +
> +static const struct file_operations linechanged_fd_fileops = {
> +	.release = linechanged_fd_release,
> +	.owner = THIS_MODULE,
> +	.llseek = noop_llseek,
> +	.poll = linechanged_fd_poll,
> +	.read = linechanged_fd_read,
> +};
> +
> +static struct linechanged_fd_state *
> +to_linechanged_fd_state(struct notifier_block *nb)
> +{
> +	return container_of(nb, struct linechanged_fd_state, changed_nb);
> +}
> +
> +static int linechanged_fd_notify(struct notifier_block *nb,
> +				 unsigned long action, void *data)
> +{
> +	struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> +	struct gpio_desc *desc = data;
> +	struct gpioline_changed chg;
> +	int i, ret;
> +
> +	for (i = 0; i < lc_state->numdescs; i++) {
> +		/* Are we watching this desc? */
> +		if (desc == lc_state->descs[i]) {
> +			/* Yes - prepare the event. */
> +			memset(&chg, 0, sizeof(chg));
> +			chg.line_offset = gpio_chip_hwgpio(desc);
> +			chg.event_type = action;
> +
> +			mutex_lock(&lc_state->lock);
> +			ret = kfifo_put(&lc_state->events, chg);
> +			mutex_unlock(&lc_state->lock);
> +			if (ret)
> +				wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> +
> +			return NOTIFY_OK;
> +		}
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> +{
> +	struct gpioline_changed_fd_request changed_req;
> +	struct linechanged_fd_state *lc_state;
> +	struct gpio_desc *desc;
> +	struct file *file;
> +	int ret, i, fd;
> +	u32 offset;
> +
> +	ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> +	if (ret)
> +		return -EFAULT;
> +
> +	if ((changed_req.num_lines == 0) ||
> +	    (changed_req.num_lines > GPIOHANDLES_MAX))
> +		return -EINVAL;
> +
> +	lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> +	if (!lc_state)
> +		return -ENOMEM;
> +
> +	lc_state->gdev = gdev;
> +	get_device(&gdev->dev);
> +
> +	for (i = 0; i < changed_req.num_lines; i++) {
> +		offset = changed_req.lineoffsets[i];
> +		desc = gpiochip_get_desc(gdev->chip, offset);
> +		if (IS_ERR(desc)) {
> +			ret = PTR_ERR(desc);
> +			goto out_free_lc_state;
> +		}
> +
> +		lc_state->descs[i] = desc;
> +	}
> +
> +	lc_state->numdescs = changed_req.num_lines;
> +
> +	init_waitqueue_head(&lc_state->waitqueue);
> +	INIT_KFIFO(lc_state->events);
> +	mutex_init(&lc_state->lock);
> +
> +	lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> +
> +	ret = atomic_notifier_chain_register(&gdev->notifier,
> +					     &lc_state->changed_nb);
> +	if (ret)
> +		goto out_free_lc_state;
> +
> +	fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> +	if (fd < 0) {
> +		ret = fd;
> +		goto out_unregister_notifier;
> +	}
> +
> +	file = anon_inode_getfile("gpio-line-changed-fd",
> +				  &linechanged_fd_fileops,
> +				  lc_state, O_RDONLY | O_CLOEXEC);
> +	if (IS_ERR(file)) {
> +		ret = PTR_ERR(file);
> +		goto out_put_unused_fd;
> +	}
> +
> +	changed_req.fd = fd;
> +	ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> +	if (ret) {
> +		fput(file);
> +		put_unused_fd(fd);
> +		return -EFAULT;
> +	}
> +
> +	fd_install(fd, file);
> +
> +	return 0;
> +
> +out_put_unused_fd:
> +	put_unused_fd(fd);
> +out_unregister_notifier:
> +	atomic_notifier_chain_unregister(&gdev->notifier,
> +					 &lc_state->changed_nb);
> +out_free_lc_state:
> +	kfree(lc_state);
> +
> +	return ret;
> +}
> +
>  /*
>   * gpio_ioctl() - ioctl handler for the GPIO chardev
>   */
> @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
>  		return linehandle_create(gdev, ip);
>  	} else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
>  		return lineevent_create(gdev, ip);
> +	} else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> +		return linechanged_fd_create(gdev, ip);
>  	}
>  	return -EINVAL;
>  }
> @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
>  	for (i = 0; i < chip->ngpio; i++)
>  		gdev->descs[i].gdev = gdev;
>  
> +	ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> +
>  #ifdef CONFIG_PINCTRL
>  	INIT_LIST_HEAD(&gdev->pin_ranges);
>  #endif
> @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
>  		spin_lock_irqsave(&gpio_lock, flags);
>  	}
>  done:
> +	atomic_notifier_call_chain(&desc->gdev->notifier,
> +				   GPIOLINE_CHANGED_REQUESTED, desc);
>  	spin_unlock_irqrestore(&gpio_lock, flags);
>  	return ret;
>  }
> @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
>  		ret = true;
>  	}
>  
> +	atomic_notifier_call_chain(&desc->gdev->notifier,
> +				   GPIOLINE_CHANGED_RELEASED, desc);
>  	spin_unlock_irqrestore(&gpio_lock, flags);
>  	return ret;
>  }
> @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
>  		if (ret != -ENOTSUPP)
>  			return ret;
>  	}
> +
>  	return 0;
>  }
>  
> diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> index a1cbeabadc69..8e3969616cfe 100644
> --- a/drivers/gpio/gpiolib.h
> +++ b/drivers/gpio/gpiolib.h
> @@ -54,6 +54,7 @@ struct gpio_device {
>  	const char		*label;
>  	void			*data;
>  	struct list_head        list;
> +	struct atomic_notifier_head notifier;
>  
>  #ifdef CONFIG_PINCTRL
>  	/*
> diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> index 799cf823d493..c61429467dd4 100644
> --- a/include/uapi/linux/gpio.h
> +++ b/include/uapi/linux/gpio.h
> @@ -59,6 +59,40 @@ struct gpioline_info {
>  /* Maximum number of requested handles */
>  #define GPIOHANDLES_MAX 64
>  
> +/**
> + * struct gpioline_changed_fd_request - Information about a linechanged fd
> + * request
> + * @lineoffsets: an array of desired lines, specified by offset index for the
> + * associated GPIO device
> + * @num_lines: number of lines requested in this request, i.e. the number of
> + * valid fields in the above arrays, set to 1 to request a single line
> + * @fd: if successful this field will contain a valid anonymous file handle
> + */
> +struct gpioline_changed_fd_request {
> +	__u32 lineoffsets[GPIOHANDLES_MAX];
> +	__u32 num_lines;
> +	int fd;
> +};
> +

Wouldn't the most common case be to watch all the lines on a chip?
How about an easy way to do that, say num_lines=0?

> +/* Possible line status change events */
> +enum {
> +	GPIOLINE_CHANGED_REQUESTED = 1,
> +	GPIOLINE_CHANGED_RELEASED,
> +	GPIOLINE_CHANGED_CONFIG,
> +};
> +
> +/**
> + * struct gpioline_changed - Information about a change in status
> + * of a GPIO line
> + * @line_offset: offset of the line that changed relative to the gpiochip
> + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> + * and GPIOLINE_CHANGED_CONFIG
> + */
> +struct gpioline_changed {
> +	__u32 line_offset;
> +	__u32 event_type;
> +};
> +

Rather than sending an event type, and requiring userspace to poll
LINEINFO, which is racy, how about passing the updated info flags here?
A change in the state of the GPIOLINE_FLAG_KERNEL implies the
GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
event_type is then redundant.
Userspace would then only have to poll LINEINFO if they were interested
in the consumer on GPIOLINE_CHANGED_REQUESTED.

To sync kernel and userspace state the current state of each line 
should be returned immediately via the fd as soon as the fd is created,
and then subsequently on any change.

And a timestamp might be useful, as per gpioevent_data?

Kent.

>  /* Linerequest flags */
>  #define GPIOHANDLE_REQUEST_INPUT	(1UL << 0)
>  #define GPIOHANDLE_REQUEST_OUTPUT	(1UL << 1)
> @@ -176,6 +210,8 @@ struct gpioevent_data {
>  
>  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
>  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> +		_IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
>  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
>  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
>  
> -- 
> 2.23.0
> 

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-27 15:24   ` Kent Gibson
@ 2019-11-27 15:50     ` Bartosz Golaszewski
  2019-11-27 23:23       ` Kent Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-27 15:50 UTC (permalink / raw)
  To: Kent Gibson
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
>
> On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> >
> > Currently there is no way for user-space to be informed about changes
> > in status of GPIO lines e.g. when someone else requests the line or its
> > config changes. We can only periodically re-read the line-info. This
> > is fine for simple one-off user-space tools, but any daemon that provides
> > a centralized access to GPIO chips would benefit hugely from an event
> > driven line info synchronization.
> >
> > This patch adds a new ioctl() that allows user-space processes to retrieve
> > a file-descriptor for given GPIO lines which can be polled for line status
> > change events.
> >
> > Currently the events are generated on three types of status changes: when
> > a line is requested, when it's released and when its config is changed.
> > The first two are self-explanatory. For the third one: this will only
> > happen when another user-space process calls the new SET_CONFIG ioctl()
> > as any changes that can happen from within the kernel (i.e.
> > set_transitory() or set_debounce()) are of no interest to user-space.
> >
> > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > ---
> >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> >  drivers/gpio/gpiolib.h    |   1 +
> >  include/uapi/linux/gpio.h |  36 +++++++
> >  3 files changed, 255 insertions(+)
> >
> > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > index d094b1be334d..be5df4bdf44b 100644
> > --- a/drivers/gpio/gpiolib.c
> > +++ b/drivers/gpio/gpiolib.c
> > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> >                       if (ret)
> >                               return ret;
> >               }
> > +
> > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> >       }
> >       return 0;
> >  }
> > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> >       return ret;
> >  }
> >
> > +struct linechanged_fd_state {
> > +     struct gpio_device *gdev;
> > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > +     size_t numdescs;
> > +     wait_queue_head_t waitqueue;
> > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > +     struct mutex lock;
> > +     struct notifier_block changed_nb;
> > +};
> > +
> > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > +{
> > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > +
> > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > +                                      &lc_state->changed_nb);
> > +     put_device(&lc_state->gdev->dev);
> > +     kfree(lc_state);
> > +
> > +     return 0;
> > +}
> > +
> > +static __poll_t linechanged_fd_poll(struct file *filep,
> > +                                 struct poll_table_struct *pollt)
> > +{
> > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > +     __poll_t events = 0;
> > +
> > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > +
> > +     mutex_lock(&lc_state->lock);
> > +     if (!kfifo_is_empty(&lc_state->events))
> > +             events = EPOLLIN | EPOLLRDNORM;
> > +     mutex_unlock(&lc_state->lock);
> > +
> > +     return events;
> > +}
> > +
> > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > +                                size_t count, loff_t *off)
> > +{
> > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > +     unsigned int copied;
> > +     int ret;
> > +
> > +     if (count < sizeof(struct gpioline_changed))
> > +             return -EINVAL;
> > +
> > +     do {
> > +             mutex_lock(&lc_state->lock);
> > +             if (kfifo_is_empty(&lc_state->events)) {
> > +                     mutex_unlock(&lc_state->lock);
> > +                     if (filep->f_flags & O_NONBLOCK)
> > +                             return -EAGAIN;
> > +
> > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > +                                     !kfifo_is_empty(&lc_state->events));
> > +                     if (ret)
> > +                             return ret;
> > +             } else {
> > +                     mutex_unlock(&lc_state->lock);
> > +             }
> > +
> > +             if (mutex_lock_interruptible(&lc_state->lock))
> > +                     return -ERESTARTSYS;
> > +
> > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > +             mutex_unlock(&lc_state->lock);
> > +             if (ret)
> > +                     return ret;
> > +
> > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > +                     return -EAGAIN;
> > +     } while (copied == 0);
> > +
> > +     return copied;
> > +}
> > +
> > +static const struct file_operations linechanged_fd_fileops = {
> > +     .release = linechanged_fd_release,
> > +     .owner = THIS_MODULE,
> > +     .llseek = noop_llseek,
> > +     .poll = linechanged_fd_poll,
> > +     .read = linechanged_fd_read,
> > +};
> > +
> > +static struct linechanged_fd_state *
> > +to_linechanged_fd_state(struct notifier_block *nb)
> > +{
> > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > +}
> > +
> > +static int linechanged_fd_notify(struct notifier_block *nb,
> > +                              unsigned long action, void *data)
> > +{
> > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > +     struct gpio_desc *desc = data;
> > +     struct gpioline_changed chg;
> > +     int i, ret;
> > +
> > +     for (i = 0; i < lc_state->numdescs; i++) {
> > +             /* Are we watching this desc? */
> > +             if (desc == lc_state->descs[i]) {
> > +                     /* Yes - prepare the event. */
> > +                     memset(&chg, 0, sizeof(chg));
> > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > +                     chg.event_type = action;
> > +
> > +                     mutex_lock(&lc_state->lock);
> > +                     ret = kfifo_put(&lc_state->events, chg);
> > +                     mutex_unlock(&lc_state->lock);
> > +                     if (ret)
> > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > +
> > +                     return NOTIFY_OK;
> > +             }
> > +     }
> > +
> > +     return NOTIFY_DONE;
> > +}
> > +
> > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > +{
> > +     struct gpioline_changed_fd_request changed_req;
> > +     struct linechanged_fd_state *lc_state;
> > +     struct gpio_desc *desc;
> > +     struct file *file;
> > +     int ret, i, fd;
> > +     u32 offset;
> > +
> > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > +     if (ret)
> > +             return -EFAULT;
> > +
> > +     if ((changed_req.num_lines == 0) ||
> > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > +             return -EINVAL;
> > +
> > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > +     if (!lc_state)
> > +             return -ENOMEM;
> > +
> > +     lc_state->gdev = gdev;
> > +     get_device(&gdev->dev);
> > +
> > +     for (i = 0; i < changed_req.num_lines; i++) {
> > +             offset = changed_req.lineoffsets[i];
> > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > +             if (IS_ERR(desc)) {
> > +                     ret = PTR_ERR(desc);
> > +                     goto out_free_lc_state;
> > +             }
> > +
> > +             lc_state->descs[i] = desc;
> > +     }
> > +
> > +     lc_state->numdescs = changed_req.num_lines;
> > +
> > +     init_waitqueue_head(&lc_state->waitqueue);
> > +     INIT_KFIFO(lc_state->events);
> > +     mutex_init(&lc_state->lock);
> > +
> > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > +
> > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > +                                          &lc_state->changed_nb);
> > +     if (ret)
> > +             goto out_free_lc_state;
> > +
> > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > +     if (fd < 0) {
> > +             ret = fd;
> > +             goto out_unregister_notifier;
> > +     }
> > +
> > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > +                               &linechanged_fd_fileops,
> > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > +     if (IS_ERR(file)) {
> > +             ret = PTR_ERR(file);
> > +             goto out_put_unused_fd;
> > +     }
> > +
> > +     changed_req.fd = fd;
> > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > +     if (ret) {
> > +             fput(file);
> > +             put_unused_fd(fd);
> > +             return -EFAULT;
> > +     }
> > +
> > +     fd_install(fd, file);
> > +
> > +     return 0;
> > +
> > +out_put_unused_fd:
> > +     put_unused_fd(fd);
> > +out_unregister_notifier:
> > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > +                                      &lc_state->changed_nb);
> > +out_free_lc_state:
> > +     kfree(lc_state);
> > +
> > +     return ret;
> > +}
> > +
> >  /*
> >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> >   */
> > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> >               return linehandle_create(gdev, ip);
> >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> >               return lineevent_create(gdev, ip);
> > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > +             return linechanged_fd_create(gdev, ip);
> >       }
> >       return -EINVAL;
> >  }
> > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> >       for (i = 0; i < chip->ngpio; i++)
> >               gdev->descs[i].gdev = gdev;
> >
> > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > +
> >  #ifdef CONFIG_PINCTRL
> >       INIT_LIST_HEAD(&gdev->pin_ranges);
> >  #endif
> > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> >               spin_lock_irqsave(&gpio_lock, flags);
> >       }
> >  done:
> > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> >       spin_unlock_irqrestore(&gpio_lock, flags);
> >       return ret;
> >  }
> > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> >               ret = true;
> >       }
> >
> > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > +                                GPIOLINE_CHANGED_RELEASED, desc);
> >       spin_unlock_irqrestore(&gpio_lock, flags);
> >       return ret;
> >  }
> > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> >               if (ret != -ENOTSUPP)
> >                       return ret;
> >       }
> > +
> >       return 0;
> >  }
> >
> > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > index a1cbeabadc69..8e3969616cfe 100644
> > --- a/drivers/gpio/gpiolib.h
> > +++ b/drivers/gpio/gpiolib.h
> > @@ -54,6 +54,7 @@ struct gpio_device {
> >       const char              *label;
> >       void                    *data;
> >       struct list_head        list;
> > +     struct atomic_notifier_head notifier;
> >
> >  #ifdef CONFIG_PINCTRL
> >       /*
> > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > index 799cf823d493..c61429467dd4 100644
> > --- a/include/uapi/linux/gpio.h
> > +++ b/include/uapi/linux/gpio.h
> > @@ -59,6 +59,40 @@ struct gpioline_info {
> >  /* Maximum number of requested handles */
> >  #define GPIOHANDLES_MAX 64
> >
> > +/**
> > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > + * request
> > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > + * associated GPIO device
> > + * @num_lines: number of lines requested in this request, i.e. the number of
> > + * valid fields in the above arrays, set to 1 to request a single line
> > + * @fd: if successful this field will contain a valid anonymous file handle
> > + */
> > +struct gpioline_changed_fd_request {
> > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > +     __u32 num_lines;
> > +     int fd;
> > +};
> > +
>
> Wouldn't the most common case be to watch all the lines on a chip?
> How about an easy way to do that, say num_lines=0?
>

IMO this is too implicit - it's literally a magic value. I'd prefer to
keep it this way for the same reason I didn't want to have implicit
BIAS settings. I prefer the kernel uAPI to be explicit and then we can
wrap it in simple helpers in the library.

> > +/* Possible line status change events */
> > +enum {
> > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > +     GPIOLINE_CHANGED_RELEASED,
> > +     GPIOLINE_CHANGED_CONFIG,
> > +};
> > +
> > +/**
> > + * struct gpioline_changed - Information about a change in status
> > + * of a GPIO line
> > + * @line_offset: offset of the line that changed relative to the gpiochip
> > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > + * and GPIOLINE_CHANGED_CONFIG
> > + */
> > +struct gpioline_changed {
> > +     __u32 line_offset;
> > +     __u32 event_type;
> > +};
> > +
>
> Rather than sending an event type, and requiring userspace to poll
> LINEINFO, which is racy, how about passing the updated info flags here?
> A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> event_type is then redundant.
> Userspace would then only have to poll LINEINFO if they were interested
> in the consumer on GPIOLINE_CHANGED_REQUESTED.
>
> To sync kernel and userspace state the current state of each line
> should be returned immediately via the fd as soon as the fd is created,
> and then subsequently on any change.
>

I guess you're right. You even made me think we could go as far as to
embed the whole gpioline_info structure in struct gpioline_changed.
I'd still keep the event type though - otherwise we'd have to assume
the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
fd.

> And a timestamp might be useful, as per gpioevent_data?

Sure thing!

Bart

>
> Kent.
>
> >  /* Linerequest flags */
> >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > @@ -176,6 +210,8 @@ struct gpioevent_data {
> >
> >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> >
> > --
> > 2.23.0
> >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-27 15:50     ` Bartosz Golaszewski
@ 2019-11-27 23:23       ` Kent Gibson
  2019-11-28  9:45         ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Kent Gibson @ 2019-11-27 23:23 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> >
> > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > >
> > > Currently there is no way for user-space to be informed about changes
> > > in status of GPIO lines e.g. when someone else requests the line or its
> > > config changes. We can only periodically re-read the line-info. This
> > > is fine for simple one-off user-space tools, but any daemon that provides
> > > a centralized access to GPIO chips would benefit hugely from an event
> > > driven line info synchronization.
> > >
> > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > a file-descriptor for given GPIO lines which can be polled for line status
> > > change events.
> > >
> > > Currently the events are generated on three types of status changes: when
> > > a line is requested, when it's released and when its config is changed.
> > > The first two are self-explanatory. For the third one: this will only
> > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > as any changes that can happen from within the kernel (i.e.
> > > set_transitory() or set_debounce()) are of no interest to user-space.
> > >
> > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > ---
> > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > >  drivers/gpio/gpiolib.h    |   1 +
> > >  include/uapi/linux/gpio.h |  36 +++++++
> > >  3 files changed, 255 insertions(+)
> > >
> > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > index d094b1be334d..be5df4bdf44b 100644
> > > --- a/drivers/gpio/gpiolib.c
> > > +++ b/drivers/gpio/gpiolib.c
> > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > >                       if (ret)
> > >                               return ret;
> > >               }
> > > +
> > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > >       }
> > >       return 0;
> > >  }
> > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > >       return ret;
> > >  }
> > >
> > > +struct linechanged_fd_state {
> > > +     struct gpio_device *gdev;
> > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > +     size_t numdescs;
> > > +     wait_queue_head_t waitqueue;
> > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > +     struct mutex lock;
> > > +     struct notifier_block changed_nb;
> > > +};
> > > +
> > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > +{
> > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > +
> > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > +                                      &lc_state->changed_nb);
> > > +     put_device(&lc_state->gdev->dev);
> > > +     kfree(lc_state);
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > +                                 struct poll_table_struct *pollt)
> > > +{
> > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > +     __poll_t events = 0;
> > > +
> > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > +
> > > +     mutex_lock(&lc_state->lock);
> > > +     if (!kfifo_is_empty(&lc_state->events))
> > > +             events = EPOLLIN | EPOLLRDNORM;
> > > +     mutex_unlock(&lc_state->lock);
> > > +
> > > +     return events;
> > > +}
> > > +
> > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > +                                size_t count, loff_t *off)
> > > +{
> > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > +     unsigned int copied;
> > > +     int ret;
> > > +
> > > +     if (count < sizeof(struct gpioline_changed))
> > > +             return -EINVAL;
> > > +
> > > +     do {
> > > +             mutex_lock(&lc_state->lock);
> > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > +                     mutex_unlock(&lc_state->lock);
> > > +                     if (filep->f_flags & O_NONBLOCK)
> > > +                             return -EAGAIN;
> > > +
> > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > +                                     !kfifo_is_empty(&lc_state->events));
> > > +                     if (ret)
> > > +                             return ret;
> > > +             } else {
> > > +                     mutex_unlock(&lc_state->lock);
> > > +             }
> > > +
> > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > +                     return -ERESTARTSYS;
> > > +
> > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > +             mutex_unlock(&lc_state->lock);
> > > +             if (ret)
> > > +                     return ret;
> > > +
> > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > +                     return -EAGAIN;
> > > +     } while (copied == 0);
> > > +
> > > +     return copied;
> > > +}
> > > +
> > > +static const struct file_operations linechanged_fd_fileops = {
> > > +     .release = linechanged_fd_release,
> > > +     .owner = THIS_MODULE,
> > > +     .llseek = noop_llseek,
> > > +     .poll = linechanged_fd_poll,
> > > +     .read = linechanged_fd_read,
> > > +};
> > > +
> > > +static struct linechanged_fd_state *
> > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > +{
> > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > +}
> > > +
> > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > +                              unsigned long action, void *data)
> > > +{
> > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > +     struct gpio_desc *desc = data;
> > > +     struct gpioline_changed chg;
> > > +     int i, ret;
> > > +
> > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > +             /* Are we watching this desc? */
> > > +             if (desc == lc_state->descs[i]) {
> > > +                     /* Yes - prepare the event. */
> > > +                     memset(&chg, 0, sizeof(chg));
> > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > +                     chg.event_type = action;
> > > +
> > > +                     mutex_lock(&lc_state->lock);
> > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > +                     mutex_unlock(&lc_state->lock);
> > > +                     if (ret)
> > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > +
> > > +                     return NOTIFY_OK;
> > > +             }
> > > +     }
> > > +
> > > +     return NOTIFY_DONE;
> > > +}
> > > +
> > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > +{
> > > +     struct gpioline_changed_fd_request changed_req;
> > > +     struct linechanged_fd_state *lc_state;
> > > +     struct gpio_desc *desc;
> > > +     struct file *file;
> > > +     int ret, i, fd;
> > > +     u32 offset;
> > > +
> > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > +     if (ret)
> > > +             return -EFAULT;
> > > +
> > > +     if ((changed_req.num_lines == 0) ||
> > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > +             return -EINVAL;
> > > +
> > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > +     if (!lc_state)
> > > +             return -ENOMEM;
> > > +
> > > +     lc_state->gdev = gdev;
> > > +     get_device(&gdev->dev);
> > > +
> > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > +             offset = changed_req.lineoffsets[i];
> > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > +             if (IS_ERR(desc)) {
> > > +                     ret = PTR_ERR(desc);
> > > +                     goto out_free_lc_state;
> > > +             }
> > > +
> > > +             lc_state->descs[i] = desc;
> > > +     }
> > > +
> > > +     lc_state->numdescs = changed_req.num_lines;
> > > +
> > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > +     INIT_KFIFO(lc_state->events);
> > > +     mutex_init(&lc_state->lock);
> > > +
> > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > +
> > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > +                                          &lc_state->changed_nb);
> > > +     if (ret)
> > > +             goto out_free_lc_state;
> > > +
> > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > +     if (fd < 0) {
> > > +             ret = fd;
> > > +             goto out_unregister_notifier;
> > > +     }
> > > +
> > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > +                               &linechanged_fd_fileops,
> > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > +     if (IS_ERR(file)) {
> > > +             ret = PTR_ERR(file);
> > > +             goto out_put_unused_fd;
> > > +     }
> > > +
> > > +     changed_req.fd = fd;
> > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > +     if (ret) {
> > > +             fput(file);
> > > +             put_unused_fd(fd);
> > > +             return -EFAULT;
> > > +     }
> > > +
> > > +     fd_install(fd, file);
> > > +
> > > +     return 0;
> > > +
> > > +out_put_unused_fd:
> > > +     put_unused_fd(fd);
> > > +out_unregister_notifier:
> > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > +                                      &lc_state->changed_nb);
> > > +out_free_lc_state:
> > > +     kfree(lc_state);
> > > +
> > > +     return ret;
> > > +}
> > > +
> > >  /*
> > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > >   */
> > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > >               return linehandle_create(gdev, ip);
> > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > >               return lineevent_create(gdev, ip);
> > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > +             return linechanged_fd_create(gdev, ip);
> > >       }
> > >       return -EINVAL;
> > >  }
> > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > >       for (i = 0; i < chip->ngpio; i++)
> > >               gdev->descs[i].gdev = gdev;
> > >
> > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > +
> > >  #ifdef CONFIG_PINCTRL
> > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > >  #endif
> > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > >               spin_lock_irqsave(&gpio_lock, flags);
> > >       }
> > >  done:
> > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > >       return ret;
> > >  }
> > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > >               ret = true;
> > >       }
> > >
> > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > >       return ret;
> > >  }
> > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > >               if (ret != -ENOTSUPP)
> > >                       return ret;
> > >       }
> > > +
> > >       return 0;
> > >  }
> > >
> > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > index a1cbeabadc69..8e3969616cfe 100644
> > > --- a/drivers/gpio/gpiolib.h
> > > +++ b/drivers/gpio/gpiolib.h
> > > @@ -54,6 +54,7 @@ struct gpio_device {
> > >       const char              *label;
> > >       void                    *data;
> > >       struct list_head        list;
> > > +     struct atomic_notifier_head notifier;
> > >
> > >  #ifdef CONFIG_PINCTRL
> > >       /*
> > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > index 799cf823d493..c61429467dd4 100644
> > > --- a/include/uapi/linux/gpio.h
> > > +++ b/include/uapi/linux/gpio.h
> > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > >  /* Maximum number of requested handles */
> > >  #define GPIOHANDLES_MAX 64
> > >
> > > +/**
> > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > + * request
> > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > + * associated GPIO device
> > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > + * valid fields in the above arrays, set to 1 to request a single line
> > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > + */
> > > +struct gpioline_changed_fd_request {
> > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > +     __u32 num_lines;
> > > +     int fd;
> > > +};
> > > +
> >
> > Wouldn't the most common case be to watch all the lines on a chip?
> > How about an easy way to do that, say num_lines=0?
> >
> 
> IMO this is too implicit - it's literally a magic value. I'd prefer to
> keep it this way for the same reason I didn't want to have implicit
> BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> wrap it in simple helpers in the library.
> 

Or you could add a watch_all flag to make it explicit.
But fair enough to leave as is - it probably keeps the kernel cleaner.

> > > +/* Possible line status change events */
> > > +enum {
> > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > +     GPIOLINE_CHANGED_RELEASED,
> > > +     GPIOLINE_CHANGED_CONFIG,
> > > +};
> > > +
> > > +/**
> > > + * struct gpioline_changed - Information about a change in status
> > > + * of a GPIO line
> > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > + * and GPIOLINE_CHANGED_CONFIG
> > > + */
> > > +struct gpioline_changed {
> > > +     __u32 line_offset;
> > > +     __u32 event_type;
> > > +};
> > > +
> >
> > Rather than sending an event type, and requiring userspace to poll
> > LINEINFO, which is racy, how about passing the updated info flags here?
> > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > event_type is then redundant.
> > Userspace would then only have to poll LINEINFO if they were interested
> > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> >
> > To sync kernel and userspace state the current state of each line
> > should be returned immediately via the fd as soon as the fd is created,
> > and then subsequently on any change.
> >
> 
> I guess you're right. You even made me think we could go as far as to
> embed the whole gpioline_info structure in struct gpioline_changed.
> I'd still keep the event type though - otherwise we'd have to assume
> the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> fd.
> 

I suggested sending the current state of each line as soon as the fd
is created so that you don't have to assume anything.  That initial change
report is effectively the same as having the user call LINEINFO on each
line at the same time - without all the race issues.

I also considered the full gpioline_info structure, as well as reporting
bulk line changes (replacing line_offset with lineoffsets and num_lines), 
but then I thought it was probably better to keep it minimal and simple.
The name and consumer are relatively large.
The name is immutable(??), and so is pointless to include.
The consumer only changes when the line is requested, so I was willing
to live with still having to poll for that.
And what you gain by reporting bulk lines you might lose in increased
report size and associated code.  OTOH it would make it explicit which
lines are being changed together...
So I could well be wrong on that - a full bulk report may be better.

Kent.

> > And a timestamp might be useful, as per gpioevent_data?
> 
> Sure thing!
> 
> Bart
> 
> >
> > Kent.
> >
> > >  /* Linerequest flags */
> > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > >
> > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > >
> > > --
> > > 2.23.0
> > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-27 23:23       ` Kent Gibson
@ 2019-11-28  9:45         ` Bartosz Golaszewski
  2019-11-28 14:10           ` Kent Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-28  9:45 UTC (permalink / raw)
  To: Kent Gibson
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
>
> On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > >
> > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > >
> > > > Currently there is no way for user-space to be informed about changes
> > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > config changes. We can only periodically re-read the line-info. This
> > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > driven line info synchronization.
> > > >
> > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > change events.
> > > >
> > > > Currently the events are generated on three types of status changes: when
> > > > a line is requested, when it's released and when its config is changed.
> > > > The first two are self-explanatory. For the third one: this will only
> > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > as any changes that can happen from within the kernel (i.e.
> > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > >
> > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > ---
> > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > >  drivers/gpio/gpiolib.h    |   1 +
> > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > >  3 files changed, 255 insertions(+)
> > > >
> > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > index d094b1be334d..be5df4bdf44b 100644
> > > > --- a/drivers/gpio/gpiolib.c
> > > > +++ b/drivers/gpio/gpiolib.c
> > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > >                       if (ret)
> > > >                               return ret;
> > > >               }
> > > > +
> > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > >       }
> > > >       return 0;
> > > >  }
> > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > >       return ret;
> > > >  }
> > > >
> > > > +struct linechanged_fd_state {
> > > > +     struct gpio_device *gdev;
> > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > +     size_t numdescs;
> > > > +     wait_queue_head_t waitqueue;
> > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > +     struct mutex lock;
> > > > +     struct notifier_block changed_nb;
> > > > +};
> > > > +
> > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > +{
> > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > +
> > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > +                                      &lc_state->changed_nb);
> > > > +     put_device(&lc_state->gdev->dev);
> > > > +     kfree(lc_state);
> > > > +
> > > > +     return 0;
> > > > +}
> > > > +
> > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > +                                 struct poll_table_struct *pollt)
> > > > +{
> > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > +     __poll_t events = 0;
> > > > +
> > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > +
> > > > +     mutex_lock(&lc_state->lock);
> > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > +     mutex_unlock(&lc_state->lock);
> > > > +
> > > > +     return events;
> > > > +}
> > > > +
> > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > +                                size_t count, loff_t *off)
> > > > +{
> > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > +     unsigned int copied;
> > > > +     int ret;
> > > > +
> > > > +     if (count < sizeof(struct gpioline_changed))
> > > > +             return -EINVAL;
> > > > +
> > > > +     do {
> > > > +             mutex_lock(&lc_state->lock);
> > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > +                     mutex_unlock(&lc_state->lock);
> > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > +                             return -EAGAIN;
> > > > +
> > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > +                     if (ret)
> > > > +                             return ret;
> > > > +             } else {
> > > > +                     mutex_unlock(&lc_state->lock);
> > > > +             }
> > > > +
> > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > +                     return -ERESTARTSYS;
> > > > +
> > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > +             mutex_unlock(&lc_state->lock);
> > > > +             if (ret)
> > > > +                     return ret;
> > > > +
> > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > +                     return -EAGAIN;
> > > > +     } while (copied == 0);
> > > > +
> > > > +     return copied;
> > > > +}
> > > > +
> > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > +     .release = linechanged_fd_release,
> > > > +     .owner = THIS_MODULE,
> > > > +     .llseek = noop_llseek,
> > > > +     .poll = linechanged_fd_poll,
> > > > +     .read = linechanged_fd_read,
> > > > +};
> > > > +
> > > > +static struct linechanged_fd_state *
> > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > +{
> > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > +}
> > > > +
> > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > +                              unsigned long action, void *data)
> > > > +{
> > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > +     struct gpio_desc *desc = data;
> > > > +     struct gpioline_changed chg;
> > > > +     int i, ret;
> > > > +
> > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > +             /* Are we watching this desc? */
> > > > +             if (desc == lc_state->descs[i]) {
> > > > +                     /* Yes - prepare the event. */
> > > > +                     memset(&chg, 0, sizeof(chg));
> > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > +                     chg.event_type = action;
> > > > +
> > > > +                     mutex_lock(&lc_state->lock);
> > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > +                     mutex_unlock(&lc_state->lock);
> > > > +                     if (ret)
> > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > +
> > > > +                     return NOTIFY_OK;
> > > > +             }
> > > > +     }
> > > > +
> > > > +     return NOTIFY_DONE;
> > > > +}
> > > > +
> > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > +{
> > > > +     struct gpioline_changed_fd_request changed_req;
> > > > +     struct linechanged_fd_state *lc_state;
> > > > +     struct gpio_desc *desc;
> > > > +     struct file *file;
> > > > +     int ret, i, fd;
> > > > +     u32 offset;
> > > > +
> > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > +     if (ret)
> > > > +             return -EFAULT;
> > > > +
> > > > +     if ((changed_req.num_lines == 0) ||
> > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > +             return -EINVAL;
> > > > +
> > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > +     if (!lc_state)
> > > > +             return -ENOMEM;
> > > > +
> > > > +     lc_state->gdev = gdev;
> > > > +     get_device(&gdev->dev);
> > > > +
> > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > +             offset = changed_req.lineoffsets[i];
> > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > +             if (IS_ERR(desc)) {
> > > > +                     ret = PTR_ERR(desc);
> > > > +                     goto out_free_lc_state;
> > > > +             }
> > > > +
> > > > +             lc_state->descs[i] = desc;
> > > > +     }
> > > > +
> > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > +
> > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > +     INIT_KFIFO(lc_state->events);
> > > > +     mutex_init(&lc_state->lock);
> > > > +
> > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > +
> > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > +                                          &lc_state->changed_nb);
> > > > +     if (ret)
> > > > +             goto out_free_lc_state;
> > > > +
> > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > +     if (fd < 0) {
> > > > +             ret = fd;
> > > > +             goto out_unregister_notifier;
> > > > +     }
> > > > +
> > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > +                               &linechanged_fd_fileops,
> > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > +     if (IS_ERR(file)) {
> > > > +             ret = PTR_ERR(file);
> > > > +             goto out_put_unused_fd;
> > > > +     }
> > > > +
> > > > +     changed_req.fd = fd;
> > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > +     if (ret) {
> > > > +             fput(file);
> > > > +             put_unused_fd(fd);
> > > > +             return -EFAULT;
> > > > +     }
> > > > +
> > > > +     fd_install(fd, file);
> > > > +
> > > > +     return 0;
> > > > +
> > > > +out_put_unused_fd:
> > > > +     put_unused_fd(fd);
> > > > +out_unregister_notifier:
> > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > +                                      &lc_state->changed_nb);
> > > > +out_free_lc_state:
> > > > +     kfree(lc_state);
> > > > +
> > > > +     return ret;
> > > > +}
> > > > +
> > > >  /*
> > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > >   */
> > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > >               return linehandle_create(gdev, ip);
> > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > >               return lineevent_create(gdev, ip);
> > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > +             return linechanged_fd_create(gdev, ip);
> > > >       }
> > > >       return -EINVAL;
> > > >  }
> > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > >       for (i = 0; i < chip->ngpio; i++)
> > > >               gdev->descs[i].gdev = gdev;
> > > >
> > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > +
> > > >  #ifdef CONFIG_PINCTRL
> > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > >  #endif
> > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > >       }
> > > >  done:
> > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > >       return ret;
> > > >  }
> > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > >               ret = true;
> > > >       }
> > > >
> > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > >       return ret;
> > > >  }
> > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > >               if (ret != -ENOTSUPP)
> > > >                       return ret;
> > > >       }
> > > > +
> > > >       return 0;
> > > >  }
> > > >
> > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > --- a/drivers/gpio/gpiolib.h
> > > > +++ b/drivers/gpio/gpiolib.h
> > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > >       const char              *label;
> > > >       void                    *data;
> > > >       struct list_head        list;
> > > > +     struct atomic_notifier_head notifier;
> > > >
> > > >  #ifdef CONFIG_PINCTRL
> > > >       /*
> > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > index 799cf823d493..c61429467dd4 100644
> > > > --- a/include/uapi/linux/gpio.h
> > > > +++ b/include/uapi/linux/gpio.h
> > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > >  /* Maximum number of requested handles */
> > > >  #define GPIOHANDLES_MAX 64
> > > >
> > > > +/**
> > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > + * request
> > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > + * associated GPIO device
> > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > + */
> > > > +struct gpioline_changed_fd_request {
> > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > +     __u32 num_lines;
> > > > +     int fd;
> > > > +};
> > > > +
> > >
> > > Wouldn't the most common case be to watch all the lines on a chip?
> > > How about an easy way to do that, say num_lines=0?
> > >
> >
> > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > keep it this way for the same reason I didn't want to have implicit
> > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > wrap it in simple helpers in the library.
> >
>
> Or you could add a watch_all flag to make it explicit.
> But fair enough to leave as is - it probably keeps the kernel cleaner.
>
> > > > +/* Possible line status change events */
> > > > +enum {
> > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > +};
> > > > +
> > > > +/**
> > > > + * struct gpioline_changed - Information about a change in status
> > > > + * of a GPIO line
> > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > + */
> > > > +struct gpioline_changed {
> > > > +     __u32 line_offset;
> > > > +     __u32 event_type;
> > > > +};
> > > > +
> > >
> > > Rather than sending an event type, and requiring userspace to poll
> > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > event_type is then redundant.
> > > Userspace would then only have to poll LINEINFO if they were interested
> > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > >
> > > To sync kernel and userspace state the current state of each line
> > > should be returned immediately via the fd as soon as the fd is created,
> > > and then subsequently on any change.
> > >
> >
> > I guess you're right. You even made me think we could go as far as to
> > embed the whole gpioline_info structure in struct gpioline_changed.
> > I'd still keep the event type though - otherwise we'd have to assume
> > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > fd.
> >
>
> I suggested sending the current state of each line as soon as the fd
> is created so that you don't have to assume anything.  That initial change
> report is effectively the same as having the user call LINEINFO on each
> line at the same time - without all the race issues.
>

Right, I didn't get this. That means you'd have to call the ioctl()
and immediately follow up with a read(). I'm not a fan of this.
Especially if we were to include a timestamp - what would it refer to?

> I also considered the full gpioline_info structure, as well as reporting
> bulk line changes (replacing line_offset with lineoffsets and num_lines),
> but then I thought it was probably better to keep it minimal and simple.
> The name and consumer are relatively large.

In terms of performance - we're not really degrading it as this is
still not a lot of data.

How about the following:
1. Move the code filling out the struct gpioline_info to a separate
function in the kernel.
2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
of GET_LINEINFO.
3. Embed struct gpioline_info in struct gpioline_info_fd so that the
initial info be read when the file descriptor is created.
4. Likewise embed struct gpioline_info in struct gpioline_changed and
on every status change event provide the whole set of information?

> The name is immutable(??), and so is pointless to include.

It is now, but let's be future-proof. I can imagine having modifiable
line names in the future. The code putting this info in struct
gpioline_info wouldn't be really duplicated and the size of such small
structures doesn't matter much - it's still a single context switch to
read it.

> The consumer only changes when the line is requested, so I was willing
> to live with still having to poll for that.
> And what you gain by reporting bulk lines you might lose in increased
> report size and associated code.  OTOH it would make it explicit which
> lines are being changed together...
> So I could well be wrong on that - a full bulk report may be better.

I'm not sure we need bulk reporting - just as we don't provide bulk
GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
pretty complicated soon.

Bartosz

>
> Kent.
>
> > > And a timestamp might be useful, as per gpioevent_data?
> >
> > Sure thing!
> >
> > Bart
> >
> > >
> > > Kent.
> > >
> > > >  /* Linerequest flags */
> > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > >
> > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > >
> > > > --
> > > > 2.23.0
> > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-28  9:45         ` Bartosz Golaszewski
@ 2019-11-28 14:10           ` Kent Gibson
  2019-11-28 14:36             ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Kent Gibson @ 2019-11-28 14:10 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

On Thu, Nov 28, 2019 at 10:45:46AM +0100, Bartosz Golaszewski wrote:
> czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
> >
> > On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > >
> > > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > >
> > > > > Currently there is no way for user-space to be informed about changes
> > > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > > config changes. We can only periodically re-read the line-info. This
> > > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > > driven line info synchronization.
> > > > >
> > > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > > change events.
> > > > >
> > > > > Currently the events are generated on three types of status changes: when
> > > > > a line is requested, when it's released and when its config is changed.
> > > > > The first two are self-explanatory. For the third one: this will only
> > > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > > as any changes that can happen from within the kernel (i.e.
> > > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > > >
> > > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > ---
> > > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > > >  drivers/gpio/gpiolib.h    |   1 +
> > > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > > >  3 files changed, 255 insertions(+)
> > > > >
> > > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > > index d094b1be334d..be5df4bdf44b 100644
> > > > > --- a/drivers/gpio/gpiolib.c
> > > > > +++ b/drivers/gpio/gpiolib.c
> > > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > > >                       if (ret)
> > > > >                               return ret;
> > > > >               }
> > > > > +
> > > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > > >       }
> > > > >       return 0;
> > > > >  }
> > > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > > >       return ret;
> > > > >  }
> > > > >
> > > > > +struct linechanged_fd_state {
> > > > > +     struct gpio_device *gdev;
> > > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > > +     size_t numdescs;
> > > > > +     wait_queue_head_t waitqueue;
> > > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > > +     struct mutex lock;
> > > > > +     struct notifier_block changed_nb;
> > > > > +};
> > > > > +
> > > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > > +{
> > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > +
> > > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > > +                                      &lc_state->changed_nb);
> > > > > +     put_device(&lc_state->gdev->dev);
> > > > > +     kfree(lc_state);
> > > > > +
> > > > > +     return 0;
> > > > > +}
> > > > > +
> > > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > > +                                 struct poll_table_struct *pollt)
> > > > > +{
> > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > +     __poll_t events = 0;
> > > > > +
> > > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > > +
> > > > > +     mutex_lock(&lc_state->lock);
> > > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > > +     mutex_unlock(&lc_state->lock);
> > > > > +
> > > > > +     return events;
> > > > > +}
> > > > > +
> > > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > > +                                size_t count, loff_t *off)
> > > > > +{
> > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > +     unsigned int copied;
> > > > > +     int ret;
> > > > > +
> > > > > +     if (count < sizeof(struct gpioline_changed))
> > > > > +             return -EINVAL;
> > > > > +
> > > > > +     do {
> > > > > +             mutex_lock(&lc_state->lock);
> > > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > > +                             return -EAGAIN;
> > > > > +
> > > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > > +                     if (ret)
> > > > > +                             return ret;
> > > > > +             } else {
> > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > +             }
> > > > > +
> > > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > > +                     return -ERESTARTSYS;
> > > > > +
> > > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > > +             mutex_unlock(&lc_state->lock);
> > > > > +             if (ret)
> > > > > +                     return ret;
> > > > > +
> > > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > > +                     return -EAGAIN;
> > > > > +     } while (copied == 0);
> > > > > +
> > > > > +     return copied;
> > > > > +}
> > > > > +
> > > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > > +     .release = linechanged_fd_release,
> > > > > +     .owner = THIS_MODULE,
> > > > > +     .llseek = noop_llseek,
> > > > > +     .poll = linechanged_fd_poll,
> > > > > +     .read = linechanged_fd_read,
> > > > > +};
> > > > > +
> > > > > +static struct linechanged_fd_state *
> > > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > > +{
> > > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > > +}
> > > > > +
> > > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > > +                              unsigned long action, void *data)
> > > > > +{
> > > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > > +     struct gpio_desc *desc = data;
> > > > > +     struct gpioline_changed chg;
> > > > > +     int i, ret;
> > > > > +
> > > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > > +             /* Are we watching this desc? */
> > > > > +             if (desc == lc_state->descs[i]) {
> > > > > +                     /* Yes - prepare the event. */
> > > > > +                     memset(&chg, 0, sizeof(chg));
> > > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > > +                     chg.event_type = action;
> > > > > +
> > > > > +                     mutex_lock(&lc_state->lock);
> > > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > +                     if (ret)
> > > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > > +
> > > > > +                     return NOTIFY_OK;
> > > > > +             }
> > > > > +     }
> > > > > +
> > > > > +     return NOTIFY_DONE;
> > > > > +}
> > > > > +
> > > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > > +{
> > > > > +     struct gpioline_changed_fd_request changed_req;
> > > > > +     struct linechanged_fd_state *lc_state;
> > > > > +     struct gpio_desc *desc;
> > > > > +     struct file *file;
> > > > > +     int ret, i, fd;
> > > > > +     u32 offset;
> > > > > +
> > > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > > +     if (ret)
> > > > > +             return -EFAULT;
> > > > > +
> > > > > +     if ((changed_req.num_lines == 0) ||
> > > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > > +             return -EINVAL;
> > > > > +
> > > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > > +     if (!lc_state)
> > > > > +             return -ENOMEM;
> > > > > +
> > > > > +     lc_state->gdev = gdev;
> > > > > +     get_device(&gdev->dev);
> > > > > +
> > > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > > +             offset = changed_req.lineoffsets[i];
> > > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > > +             if (IS_ERR(desc)) {
> > > > > +                     ret = PTR_ERR(desc);
> > > > > +                     goto out_free_lc_state;
> > > > > +             }
> > > > > +
> > > > > +             lc_state->descs[i] = desc;
> > > > > +     }
> > > > > +
> > > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > > +
> > > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > > +     INIT_KFIFO(lc_state->events);
> > > > > +     mutex_init(&lc_state->lock);
> > > > > +
> > > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > > +
> > > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > > +                                          &lc_state->changed_nb);
> > > > > +     if (ret)
> > > > > +             goto out_free_lc_state;
> > > > > +
> > > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > > +     if (fd < 0) {
> > > > > +             ret = fd;
> > > > > +             goto out_unregister_notifier;
> > > > > +     }
> > > > > +
> > > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > > +                               &linechanged_fd_fileops,
> > > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > > +     if (IS_ERR(file)) {
> > > > > +             ret = PTR_ERR(file);
> > > > > +             goto out_put_unused_fd;
> > > > > +     }
> > > > > +
> > > > > +     changed_req.fd = fd;
> > > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > > +     if (ret) {
> > > > > +             fput(file);
> > > > > +             put_unused_fd(fd);
> > > > > +             return -EFAULT;
> > > > > +     }
> > > > > +
> > > > > +     fd_install(fd, file);
> > > > > +
> > > > > +     return 0;
> > > > > +
> > > > > +out_put_unused_fd:
> > > > > +     put_unused_fd(fd);
> > > > > +out_unregister_notifier:
> > > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > > +                                      &lc_state->changed_nb);
> > > > > +out_free_lc_state:
> > > > > +     kfree(lc_state);
> > > > > +
> > > > > +     return ret;
> > > > > +}
> > > > > +
> > > > >  /*
> > > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > > >   */
> > > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > >               return linehandle_create(gdev, ip);
> > > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > > >               return lineevent_create(gdev, ip);
> > > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > > +             return linechanged_fd_create(gdev, ip);
> > > > >       }
> > > > >       return -EINVAL;
> > > > >  }
> > > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > > >       for (i = 0; i < chip->ngpio; i++)
> > > > >               gdev->descs[i].gdev = gdev;
> > > > >
> > > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > > +
> > > > >  #ifdef CONFIG_PINCTRL
> > > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > > >  #endif
> > > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > > >       }
> > > > >  done:
> > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > >       return ret;
> > > > >  }
> > > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > > >               ret = true;
> > > > >       }
> > > > >
> > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > >       return ret;
> > > > >  }
> > > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > > >               if (ret != -ENOTSUPP)
> > > > >                       return ret;
> > > > >       }
> > > > > +
> > > > >       return 0;
> > > > >  }
> > > > >
> > > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > > --- a/drivers/gpio/gpiolib.h
> > > > > +++ b/drivers/gpio/gpiolib.h
> > > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > > >       const char              *label;
> > > > >       void                    *data;
> > > > >       struct list_head        list;
> > > > > +     struct atomic_notifier_head notifier;
> > > > >
> > > > >  #ifdef CONFIG_PINCTRL
> > > > >       /*
> > > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > > index 799cf823d493..c61429467dd4 100644
> > > > > --- a/include/uapi/linux/gpio.h
> > > > > +++ b/include/uapi/linux/gpio.h
> > > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > > >  /* Maximum number of requested handles */
> > > > >  #define GPIOHANDLES_MAX 64
> > > > >
> > > > > +/**
> > > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > > + * request
> > > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > > + * associated GPIO device
> > > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > > + */
> > > > > +struct gpioline_changed_fd_request {
> > > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > > +     __u32 num_lines;
> > > > > +     int fd;
> > > > > +};
> > > > > +
> > > >
> > > > Wouldn't the most common case be to watch all the lines on a chip?
> > > > How about an easy way to do that, say num_lines=0?
> > > >
> > >
> > > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > > keep it this way for the same reason I didn't want to have implicit
> > > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > > wrap it in simple helpers in the library.
> > >
> >
> > Or you could add a watch_all flag to make it explicit.
> > But fair enough to leave as is - it probably keeps the kernel cleaner.
> >
> > > > > +/* Possible line status change events */
> > > > > +enum {
> > > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > > +};
> > > > > +
> > > > > +/**
> > > > > + * struct gpioline_changed - Information about a change in status
> > > > > + * of a GPIO line
> > > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > > + */
> > > > > +struct gpioline_changed {
> > > > > +     __u32 line_offset;
> > > > > +     __u32 event_type;
> > > > > +};
> > > > > +
> > > >
> > > > Rather than sending an event type, and requiring userspace to poll
> > > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > > event_type is then redundant.
> > > > Userspace would then only have to poll LINEINFO if they were interested
> > > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > > >
> > > > To sync kernel and userspace state the current state of each line
> > > > should be returned immediately via the fd as soon as the fd is created,
> > > > and then subsequently on any change.
> > > >
> > >
> > > I guess you're right. You even made me think we could go as far as to
> > > embed the whole gpioline_info structure in struct gpioline_changed.
> > > I'd still keep the event type though - otherwise we'd have to assume
> > > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > > fd.
> > >
> >
> > I suggested sending the current state of each line as soon as the fd
> > is created so that you don't have to assume anything.  That initial change
> > report is effectively the same as having the user call LINEINFO on each
> > line at the same time - without all the race issues.
> >
> 
> Right, I didn't get this. That means you'd have to call the ioctl()
> and immediately follow up with a read(). I'm not a fan of this.
> Especially if we were to include a timestamp - what would it refer to?
> 
> > I also considered the full gpioline_info structure, as well as reporting
> > bulk line changes (replacing line_offset with lineoffsets and num_lines),
> > but then I thought it was probably better to keep it minimal and simple.
> > The name and consumer are relatively large.
> 
> In terms of performance - we're not really degrading it as this is
> still not a lot of data.
> 
> How about the following:
> 1. Move the code filling out the struct gpioline_info to a separate
> function in the kernel.
> 2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
> of GET_LINEINFO.
> 3. Embed struct gpioline_info in struct gpioline_info_fd so that the
> initial info be read when the file descriptor is created.
> 4. Likewise embed struct gpioline_info in struct gpioline_changed and
> on every status change event provide the whole set of information?
> 

You mean an array of info in the ioctl return, along with the fd?
That would work too.

I'm used to setting up streams like this in a networked environment
where returning data via the stream setup isn't an option, the only
option is to sync via the stream itself, so I overlooked the possibility
of using the ioctl return.

> > The name is immutable(??), and so is pointless to include.
> 
> It is now, but let's be future-proof. I can imagine having modifiable
> line names in the future. The code putting this info in struct
> gpioline_info wouldn't be really duplicated and the size of such small
> structures doesn't matter much - it's still a single context switch to
> read it.
> 
> > The consumer only changes when the line is requested, so I was willing
> > to live with still having to poll for that.
> > And what you gain by reporting bulk lines you might lose in increased
> > report size and associated code.  OTOH it would make it explicit which
> > lines are being changed together...
> > So I could well be wrong on that - a full bulk report may be better.
> 
> I'm not sure we need bulk reporting - just as we don't provide bulk
> GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> pretty complicated soon.
> 

That was my initial feeling as well.
And bulk is no longer an option if you want to include name in the change
report.

Kent.

> Bartosz
> 
> >
> > Kent.
> >
> > > > And a timestamp might be useful, as per gpioevent_data?
> > >
> > > Sure thing!
> > >
> > > Bart
> > >
> > > >
> > > > Kent.
> > > >
> > > > >  /* Linerequest flags */
> > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > >
> > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > >
> > > > > --
> > > > > 2.23.0
> > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-28 14:10           ` Kent Gibson
@ 2019-11-28 14:36             ` Bartosz Golaszewski
  2019-11-28 15:02               ` Kent Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-28 14:36 UTC (permalink / raw)
  To: Kent Gibson
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

czw., 28 lis 2019 o 15:10 Kent Gibson <warthog618@gmail.com> napisał(a):
>
> On Thu, Nov 28, 2019 at 10:45:46AM +0100, Bartosz Golaszewski wrote:
> > czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
> > >
> > > On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > > > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > >
> > > > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > >
> > > > > > Currently there is no way for user-space to be informed about changes
> > > > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > > > config changes. We can only periodically re-read the line-info. This
> > > > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > > > driven line info synchronization.
> > > > > >
> > > > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > > > change events.
> > > > > >
> > > > > > Currently the events are generated on three types of status changes: when
> > > > > > a line is requested, when it's released and when its config is changed.
> > > > > > The first two are self-explanatory. For the third one: this will only
> > > > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > > > as any changes that can happen from within the kernel (i.e.
> > > > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > > > >
> > > > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > ---
> > > > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > > > >  drivers/gpio/gpiolib.h    |   1 +
> > > > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > > > >  3 files changed, 255 insertions(+)
> > > > > >
> > > > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > > > index d094b1be334d..be5df4bdf44b 100644
> > > > > > --- a/drivers/gpio/gpiolib.c
> > > > > > +++ b/drivers/gpio/gpiolib.c
> > > > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > > > >                       if (ret)
> > > > > >                               return ret;
> > > > > >               }
> > > > > > +
> > > > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > > > >       }
> > > > > >       return 0;
> > > > > >  }
> > > > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > > > >       return ret;
> > > > > >  }
> > > > > >
> > > > > > +struct linechanged_fd_state {
> > > > > > +     struct gpio_device *gdev;
> > > > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > > > +     size_t numdescs;
> > > > > > +     wait_queue_head_t waitqueue;
> > > > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > > > +     struct mutex lock;
> > > > > > +     struct notifier_block changed_nb;
> > > > > > +};
> > > > > > +
> > > > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > > > +{
> > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > +
> > > > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > > > +                                      &lc_state->changed_nb);
> > > > > > +     put_device(&lc_state->gdev->dev);
> > > > > > +     kfree(lc_state);
> > > > > > +
> > > > > > +     return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > > > +                                 struct poll_table_struct *pollt)
> > > > > > +{
> > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > +     __poll_t events = 0;
> > > > > > +
> > > > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > > > +
> > > > > > +     mutex_lock(&lc_state->lock);
> > > > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > > > +     mutex_unlock(&lc_state->lock);
> > > > > > +
> > > > > > +     return events;
> > > > > > +}
> > > > > > +
> > > > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > > > +                                size_t count, loff_t *off)
> > > > > > +{
> > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > +     unsigned int copied;
> > > > > > +     int ret;
> > > > > > +
> > > > > > +     if (count < sizeof(struct gpioline_changed))
> > > > > > +             return -EINVAL;
> > > > > > +
> > > > > > +     do {
> > > > > > +             mutex_lock(&lc_state->lock);
> > > > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > > > +                             return -EAGAIN;
> > > > > > +
> > > > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > > > +                     if (ret)
> > > > > > +                             return ret;
> > > > > > +             } else {
> > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > +             }
> > > > > > +
> > > > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > > > +                     return -ERESTARTSYS;
> > > > > > +
> > > > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > > > +             mutex_unlock(&lc_state->lock);
> > > > > > +             if (ret)
> > > > > > +                     return ret;
> > > > > > +
> > > > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > > > +                     return -EAGAIN;
> > > > > > +     } while (copied == 0);
> > > > > > +
> > > > > > +     return copied;
> > > > > > +}
> > > > > > +
> > > > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > > > +     .release = linechanged_fd_release,
> > > > > > +     .owner = THIS_MODULE,
> > > > > > +     .llseek = noop_llseek,
> > > > > > +     .poll = linechanged_fd_poll,
> > > > > > +     .read = linechanged_fd_read,
> > > > > > +};
> > > > > > +
> > > > > > +static struct linechanged_fd_state *
> > > > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > > > +{
> > > > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > > > +}
> > > > > > +
> > > > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > > > +                              unsigned long action, void *data)
> > > > > > +{
> > > > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > > > +     struct gpio_desc *desc = data;
> > > > > > +     struct gpioline_changed chg;
> > > > > > +     int i, ret;
> > > > > > +
> > > > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > > > +             /* Are we watching this desc? */
> > > > > > +             if (desc == lc_state->descs[i]) {
> > > > > > +                     /* Yes - prepare the event. */
> > > > > > +                     memset(&chg, 0, sizeof(chg));
> > > > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > > > +                     chg.event_type = action;
> > > > > > +
> > > > > > +                     mutex_lock(&lc_state->lock);
> > > > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > +                     if (ret)
> > > > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > > > +
> > > > > > +                     return NOTIFY_OK;
> > > > > > +             }
> > > > > > +     }
> > > > > > +
> > > > > > +     return NOTIFY_DONE;
> > > > > > +}
> > > > > > +
> > > > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > > > +{
> > > > > > +     struct gpioline_changed_fd_request changed_req;
> > > > > > +     struct linechanged_fd_state *lc_state;
> > > > > > +     struct gpio_desc *desc;
> > > > > > +     struct file *file;
> > > > > > +     int ret, i, fd;
> > > > > > +     u32 offset;
> > > > > > +
> > > > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > > > +     if (ret)
> > > > > > +             return -EFAULT;
> > > > > > +
> > > > > > +     if ((changed_req.num_lines == 0) ||
> > > > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > > > +             return -EINVAL;
> > > > > > +
> > > > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > > > +     if (!lc_state)
> > > > > > +             return -ENOMEM;
> > > > > > +
> > > > > > +     lc_state->gdev = gdev;
> > > > > > +     get_device(&gdev->dev);
> > > > > > +
> > > > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > > > +             offset = changed_req.lineoffsets[i];
> > > > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > > > +             if (IS_ERR(desc)) {
> > > > > > +                     ret = PTR_ERR(desc);
> > > > > > +                     goto out_free_lc_state;
> > > > > > +             }
> > > > > > +
> > > > > > +             lc_state->descs[i] = desc;
> > > > > > +     }
> > > > > > +
> > > > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > > > +
> > > > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > > > +     INIT_KFIFO(lc_state->events);
> > > > > > +     mutex_init(&lc_state->lock);
> > > > > > +
> > > > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > > > +
> > > > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > > > +                                          &lc_state->changed_nb);
> > > > > > +     if (ret)
> > > > > > +             goto out_free_lc_state;
> > > > > > +
> > > > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > > > +     if (fd < 0) {
> > > > > > +             ret = fd;
> > > > > > +             goto out_unregister_notifier;
> > > > > > +     }
> > > > > > +
> > > > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > > > +                               &linechanged_fd_fileops,
> > > > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > > > +     if (IS_ERR(file)) {
> > > > > > +             ret = PTR_ERR(file);
> > > > > > +             goto out_put_unused_fd;
> > > > > > +     }
> > > > > > +
> > > > > > +     changed_req.fd = fd;
> > > > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > > > +     if (ret) {
> > > > > > +             fput(file);
> > > > > > +             put_unused_fd(fd);
> > > > > > +             return -EFAULT;
> > > > > > +     }
> > > > > > +
> > > > > > +     fd_install(fd, file);
> > > > > > +
> > > > > > +     return 0;
> > > > > > +
> > > > > > +out_put_unused_fd:
> > > > > > +     put_unused_fd(fd);
> > > > > > +out_unregister_notifier:
> > > > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > > > +                                      &lc_state->changed_nb);
> > > > > > +out_free_lc_state:
> > > > > > +     kfree(lc_state);
> > > > > > +
> > > > > > +     return ret;
> > > > > > +}
> > > > > > +
> > > > > >  /*
> > > > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > > > >   */
> > > > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > > >               return linehandle_create(gdev, ip);
> > > > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > > > >               return lineevent_create(gdev, ip);
> > > > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > > > +             return linechanged_fd_create(gdev, ip);
> > > > > >       }
> > > > > >       return -EINVAL;
> > > > > >  }
> > > > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > > > >       for (i = 0; i < chip->ngpio; i++)
> > > > > >               gdev->descs[i].gdev = gdev;
> > > > > >
> > > > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > > > +
> > > > > >  #ifdef CONFIG_PINCTRL
> > > > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > > > >  #endif
> > > > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > > > >       }
> > > > > >  done:
> > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > >       return ret;
> > > > > >  }
> > > > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > > > >               ret = true;
> > > > > >       }
> > > > > >
> > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > >       return ret;
> > > > > >  }
> > > > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > > > >               if (ret != -ENOTSUPP)
> > > > > >                       return ret;
> > > > > >       }
> > > > > > +
> > > > > >       return 0;
> > > > > >  }
> > > > > >
> > > > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > > > --- a/drivers/gpio/gpiolib.h
> > > > > > +++ b/drivers/gpio/gpiolib.h
> > > > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > > > >       const char              *label;
> > > > > >       void                    *data;
> > > > > >       struct list_head        list;
> > > > > > +     struct atomic_notifier_head notifier;
> > > > > >
> > > > > >  #ifdef CONFIG_PINCTRL
> > > > > >       /*
> > > > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > > > index 799cf823d493..c61429467dd4 100644
> > > > > > --- a/include/uapi/linux/gpio.h
> > > > > > +++ b/include/uapi/linux/gpio.h
> > > > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > > > >  /* Maximum number of requested handles */
> > > > > >  #define GPIOHANDLES_MAX 64
> > > > > >
> > > > > > +/**
> > > > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > > > + * request
> > > > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > > > + * associated GPIO device
> > > > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > > > + */
> > > > > > +struct gpioline_changed_fd_request {
> > > > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > > > +     __u32 num_lines;
> > > > > > +     int fd;
> > > > > > +};
> > > > > > +
> > > > >
> > > > > Wouldn't the most common case be to watch all the lines on a chip?
> > > > > How about an easy way to do that, say num_lines=0?
> > > > >
> > > >
> > > > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > > > keep it this way for the same reason I didn't want to have implicit
> > > > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > > > wrap it in simple helpers in the library.
> > > >
> > >
> > > Or you could add a watch_all flag to make it explicit.
> > > But fair enough to leave as is - it probably keeps the kernel cleaner.
> > >
> > > > > > +/* Possible line status change events */
> > > > > > +enum {
> > > > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > > > +};
> > > > > > +
> > > > > > +/**
> > > > > > + * struct gpioline_changed - Information about a change in status
> > > > > > + * of a GPIO line
> > > > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > > > + */
> > > > > > +struct gpioline_changed {
> > > > > > +     __u32 line_offset;
> > > > > > +     __u32 event_type;
> > > > > > +};
> > > > > > +
> > > > >
> > > > > Rather than sending an event type, and requiring userspace to poll
> > > > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > > > event_type is then redundant.
> > > > > Userspace would then only have to poll LINEINFO if they were interested
> > > > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > > > >
> > > > > To sync kernel and userspace state the current state of each line
> > > > > should be returned immediately via the fd as soon as the fd is created,
> > > > > and then subsequently on any change.
> > > > >
> > > >
> > > > I guess you're right. You even made me think we could go as far as to
> > > > embed the whole gpioline_info structure in struct gpioline_changed.
> > > > I'd still keep the event type though - otherwise we'd have to assume
> > > > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > > > fd.
> > > >
> > >
> > > I suggested sending the current state of each line as soon as the fd
> > > is created so that you don't have to assume anything.  That initial change
> > > report is effectively the same as having the user call LINEINFO on each
> > > line at the same time - without all the race issues.
> > >
> >
> > Right, I didn't get this. That means you'd have to call the ioctl()
> > and immediately follow up with a read(). I'm not a fan of this.
> > Especially if we were to include a timestamp - what would it refer to?
> >
> > > I also considered the full gpioline_info structure, as well as reporting
> > > bulk line changes (replacing line_offset with lineoffsets and num_lines),
> > > but then I thought it was probably better to keep it minimal and simple.
> > > The name and consumer are relatively large.
> >
> > In terms of performance - we're not really degrading it as this is
> > still not a lot of data.
> >
> > How about the following:
> > 1. Move the code filling out the struct gpioline_info to a separate
> > function in the kernel.
> > 2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
> > of GET_LINEINFO.
> > 3. Embed struct gpioline_info in struct gpioline_info_fd so that the
> > initial info be read when the file descriptor is created.
> > 4. Likewise embed struct gpioline_info in struct gpioline_changed and
> > on every status change event provide the whole set of information?
> >
>
> You mean an array of info in the ioctl return, along with the fd?
> That would work too.

I overlooked the fact that we can watch multiple lines. In this case
it's either a separate fd for every watched line (not optimal) or
doing what you proposed: read the initial lineinfo directly from the
fd. But in this case again: what about the timestamp?

Bart

>
> I'm used to setting up streams like this in a networked environment
> where returning data via the stream setup isn't an option, the only
> option is to sync via the stream itself, so I overlooked the possibility
> of using the ioctl return.
>
> > > The name is immutable(??), and so is pointless to include.
> >
> > It is now, but let's be future-proof. I can imagine having modifiable
> > line names in the future. The code putting this info in struct
> > gpioline_info wouldn't be really duplicated and the size of such small
> > structures doesn't matter much - it's still a single context switch to
> > read it.
> >
> > > The consumer only changes when the line is requested, so I was willing
> > > to live with still having to poll for that.
> > > And what you gain by reporting bulk lines you might lose in increased
> > > report size and associated code.  OTOH it would make it explicit which
> > > lines are being changed together...
> > > So I could well be wrong on that - a full bulk report may be better.
> >
> > I'm not sure we need bulk reporting - just as we don't provide bulk
> > GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> > pretty complicated soon.
> >
>
> That was my initial feeling as well.
> And bulk is no longer an option if you want to include name in the change
> report.
>
> Kent.
>
> > Bartosz
> >
> > >
> > > Kent.
> > >
> > > > > And a timestamp might be useful, as per gpioevent_data?
> > > >
> > > > Sure thing!
> > > >
> > > > Bart
> > > >
> > > > >
> > > > > Kent.
> > > > >
> > > > > >  /* Linerequest flags */
> > > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > > >
> > > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > > >
> > > > > > --
> > > > > > 2.23.0
> > > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-28 14:36             ` Bartosz Golaszewski
@ 2019-11-28 15:02               ` Kent Gibson
  2019-11-29  9:43                 ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Kent Gibson @ 2019-11-28 15:02 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

On Thu, Nov 28, 2019 at 03:36:19PM +0100, Bartosz Golaszewski wrote:
> czw., 28 lis 2019 o 15:10 Kent Gibson <warthog618@gmail.com> napisał(a):
> >
> > On Thu, Nov 28, 2019 at 10:45:46AM +0100, Bartosz Golaszewski wrote:
> > > czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > >
> > > > On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > > > > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > >
> > > > > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > >
> > > > > > > Currently there is no way for user-space to be informed about changes
> > > > > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > > > > config changes. We can only periodically re-read the line-info. This
> > > > > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > > > > driven line info synchronization.
> > > > > > >
> > > > > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > > > > change events.
> > > > > > >
> > > > > > > Currently the events are generated on three types of status changes: when
> > > > > > > a line is requested, when it's released and when its config is changed.
> > > > > > > The first two are self-explanatory. For the third one: this will only
> > > > > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > > > > as any changes that can happen from within the kernel (i.e.
> > > > > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > > > > >
> > > > > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > ---
> > > > > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > > > > >  drivers/gpio/gpiolib.h    |   1 +
> > > > > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > > > > >  3 files changed, 255 insertions(+)
> > > > > > >
> > > > > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > > > > index d094b1be334d..be5df4bdf44b 100644
> > > > > > > --- a/drivers/gpio/gpiolib.c
> > > > > > > +++ b/drivers/gpio/gpiolib.c
> > > > > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > > > > >                       if (ret)
> > > > > > >                               return ret;
> > > > > > >               }
> > > > > > > +
> > > > > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > > > > >       }
> > > > > > >       return 0;
> > > > > > >  }
> > > > > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > > > > >       return ret;
> > > > > > >  }
> > > > > > >
> > > > > > > +struct linechanged_fd_state {
> > > > > > > +     struct gpio_device *gdev;
> > > > > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > > > > +     size_t numdescs;
> > > > > > > +     wait_queue_head_t waitqueue;
> > > > > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > > > > +     struct mutex lock;
> > > > > > > +     struct notifier_block changed_nb;
> > > > > > > +};
> > > > > > > +
> > > > > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > > > > +{
> > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > +
> > > > > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > +     put_device(&lc_state->gdev->dev);
> > > > > > > +     kfree(lc_state);
> > > > > > > +
> > > > > > > +     return 0;
> > > > > > > +}
> > > > > > > +
> > > > > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > > > > +                                 struct poll_table_struct *pollt)
> > > > > > > +{
> > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > +     __poll_t events = 0;
> > > > > > > +
> > > > > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > > > > +
> > > > > > > +     mutex_lock(&lc_state->lock);
> > > > > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > > > > +     mutex_unlock(&lc_state->lock);
> > > > > > > +
> > > > > > > +     return events;
> > > > > > > +}
> > > > > > > +
> > > > > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > > > > +                                size_t count, loff_t *off)
> > > > > > > +{
> > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > +     unsigned int copied;
> > > > > > > +     int ret;
> > > > > > > +
> > > > > > > +     if (count < sizeof(struct gpioline_changed))
> > > > > > > +             return -EINVAL;
> > > > > > > +
> > > > > > > +     do {
> > > > > > > +             mutex_lock(&lc_state->lock);
> > > > > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > > > > +                             return -EAGAIN;
> > > > > > > +
> > > > > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > > > > +                     if (ret)
> > > > > > > +                             return ret;
> > > > > > > +             } else {
> > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > +             }
> > > > > > > +
> > > > > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > > > > +                     return -ERESTARTSYS;
> > > > > > > +
> > > > > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > > > > +             mutex_unlock(&lc_state->lock);
> > > > > > > +             if (ret)
> > > > > > > +                     return ret;
> > > > > > > +
> > > > > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > > > > +                     return -EAGAIN;
> > > > > > > +     } while (copied == 0);
> > > > > > > +
> > > > > > > +     return copied;
> > > > > > > +}
> > > > > > > +
> > > > > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > > > > +     .release = linechanged_fd_release,
> > > > > > > +     .owner = THIS_MODULE,
> > > > > > > +     .llseek = noop_llseek,
> > > > > > > +     .poll = linechanged_fd_poll,
> > > > > > > +     .read = linechanged_fd_read,
> > > > > > > +};
> > > > > > > +
> > > > > > > +static struct linechanged_fd_state *
> > > > > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > > > > +{
> > > > > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > > > > +}
> > > > > > > +
> > > > > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > > > > +                              unsigned long action, void *data)
> > > > > > > +{
> > > > > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > > > > +     struct gpio_desc *desc = data;
> > > > > > > +     struct gpioline_changed chg;
> > > > > > > +     int i, ret;
> > > > > > > +
> > > > > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > > > > +             /* Are we watching this desc? */
> > > > > > > +             if (desc == lc_state->descs[i]) {
> > > > > > > +                     /* Yes - prepare the event. */
> > > > > > > +                     memset(&chg, 0, sizeof(chg));
> > > > > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > > > > +                     chg.event_type = action;
> > > > > > > +
> > > > > > > +                     mutex_lock(&lc_state->lock);
> > > > > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > +                     if (ret)
> > > > > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > > > > +
> > > > > > > +                     return NOTIFY_OK;
> > > > > > > +             }
> > > > > > > +     }
> > > > > > > +
> > > > > > > +     return NOTIFY_DONE;
> > > > > > > +}
> > > > > > > +
> > > > > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > +{
> > > > > > > +     struct gpioline_changed_fd_request changed_req;
> > > > > > > +     struct linechanged_fd_state *lc_state;
> > > > > > > +     struct gpio_desc *desc;
> > > > > > > +     struct file *file;
> > > > > > > +     int ret, i, fd;
> > > > > > > +     u32 offset;
> > > > > > > +
> > > > > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > > > > +     if (ret)
> > > > > > > +             return -EFAULT;
> > > > > > > +
> > > > > > > +     if ((changed_req.num_lines == 0) ||
> > > > > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > > > > +             return -EINVAL;
> > > > > > > +
> > > > > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > > > > +     if (!lc_state)
> > > > > > > +             return -ENOMEM;
> > > > > > > +
> > > > > > > +     lc_state->gdev = gdev;
> > > > > > > +     get_device(&gdev->dev);
> > > > > > > +
> > > > > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > > > > +             offset = changed_req.lineoffsets[i];
> > > > > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > > > > +             if (IS_ERR(desc)) {
> > > > > > > +                     ret = PTR_ERR(desc);
> > > > > > > +                     goto out_free_lc_state;
> > > > > > > +             }
> > > > > > > +
> > > > > > > +             lc_state->descs[i] = desc;
> > > > > > > +     }
> > > > > > > +
> > > > > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > > > > +
> > > > > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > > > > +     INIT_KFIFO(lc_state->events);
> > > > > > > +     mutex_init(&lc_state->lock);
> > > > > > > +
> > > > > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > > > > +
> > > > > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > > > > +                                          &lc_state->changed_nb);
> > > > > > > +     if (ret)
> > > > > > > +             goto out_free_lc_state;
> > > > > > > +
> > > > > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > > > > +     if (fd < 0) {
> > > > > > > +             ret = fd;
> > > > > > > +             goto out_unregister_notifier;
> > > > > > > +     }
> > > > > > > +
> > > > > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > > > > +                               &linechanged_fd_fileops,
> > > > > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > > > > +     if (IS_ERR(file)) {
> > > > > > > +             ret = PTR_ERR(file);
> > > > > > > +             goto out_put_unused_fd;
> > > > > > > +     }
> > > > > > > +
> > > > > > > +     changed_req.fd = fd;
> > > > > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > > > > +     if (ret) {
> > > > > > > +             fput(file);
> > > > > > > +             put_unused_fd(fd);
> > > > > > > +             return -EFAULT;
> > > > > > > +     }
> > > > > > > +
> > > > > > > +     fd_install(fd, file);
> > > > > > > +
> > > > > > > +     return 0;
> > > > > > > +
> > > > > > > +out_put_unused_fd:
> > > > > > > +     put_unused_fd(fd);
> > > > > > > +out_unregister_notifier:
> > > > > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > +out_free_lc_state:
> > > > > > > +     kfree(lc_state);
> > > > > > > +
> > > > > > > +     return ret;
> > > > > > > +}
> > > > > > > +
> > > > > > >  /*
> > > > > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > > > > >   */
> > > > > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > > > >               return linehandle_create(gdev, ip);
> > > > > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > > > > >               return lineevent_create(gdev, ip);
> > > > > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > > > > +             return linechanged_fd_create(gdev, ip);
> > > > > > >       }
> > > > > > >       return -EINVAL;
> > > > > > >  }
> > > > > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > > > > >       for (i = 0; i < chip->ngpio; i++)
> > > > > > >               gdev->descs[i].gdev = gdev;
> > > > > > >
> > > > > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > > > > +
> > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > > > > >  #endif
> > > > > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > > > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > > > > >       }
> > > > > > >  done:
> > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > >       return ret;
> > > > > > >  }
> > > > > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > > > > >               ret = true;
> > > > > > >       }
> > > > > > >
> > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > >       return ret;
> > > > > > >  }
> > > > > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > > > > >               if (ret != -ENOTSUPP)
> > > > > > >                       return ret;
> > > > > > >       }
> > > > > > > +
> > > > > > >       return 0;
> > > > > > >  }
> > > > > > >
> > > > > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > > > > --- a/drivers/gpio/gpiolib.h
> > > > > > > +++ b/drivers/gpio/gpiolib.h
> > > > > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > > > > >       const char              *label;
> > > > > > >       void                    *data;
> > > > > > >       struct list_head        list;
> > > > > > > +     struct atomic_notifier_head notifier;
> > > > > > >
> > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > >       /*
> > > > > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > > > > index 799cf823d493..c61429467dd4 100644
> > > > > > > --- a/include/uapi/linux/gpio.h
> > > > > > > +++ b/include/uapi/linux/gpio.h
> > > > > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > > > > >  /* Maximum number of requested handles */
> > > > > > >  #define GPIOHANDLES_MAX 64
> > > > > > >
> > > > > > > +/**
> > > > > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > > > > + * request
> > > > > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > > > > + * associated GPIO device
> > > > > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > > > > + */
> > > > > > > +struct gpioline_changed_fd_request {
> > > > > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > > > > +     __u32 num_lines;
> > > > > > > +     int fd;
> > > > > > > +};
> > > > > > > +
> > > > > >
> > > > > > Wouldn't the most common case be to watch all the lines on a chip?
> > > > > > How about an easy way to do that, say num_lines=0?
> > > > > >
> > > > >
> > > > > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > > > > keep it this way for the same reason I didn't want to have implicit
> > > > > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > > > > wrap it in simple helpers in the library.
> > > > >
> > > >
> > > > Or you could add a watch_all flag to make it explicit.
> > > > But fair enough to leave as is - it probably keeps the kernel cleaner.
> > > >
> > > > > > > +/* Possible line status change events */
> > > > > > > +enum {
> > > > > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > > > > +};
> > > > > > > +
> > > > > > > +/**
> > > > > > > + * struct gpioline_changed - Information about a change in status
> > > > > > > + * of a GPIO line
> > > > > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > > > > + */
> > > > > > > +struct gpioline_changed {
> > > > > > > +     __u32 line_offset;
> > > > > > > +     __u32 event_type;
> > > > > > > +};
> > > > > > > +
> > > > > >
> > > > > > Rather than sending an event type, and requiring userspace to poll
> > > > > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > > > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > > > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > > > > event_type is then redundant.
> > > > > > Userspace would then only have to poll LINEINFO if they were interested
> > > > > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > > > > >
> > > > > > To sync kernel and userspace state the current state of each line
> > > > > > should be returned immediately via the fd as soon as the fd is created,
> > > > > > and then subsequently on any change.
> > > > > >
> > > > >
> > > > > I guess you're right. You even made me think we could go as far as to
> > > > > embed the whole gpioline_info structure in struct gpioline_changed.
> > > > > I'd still keep the event type though - otherwise we'd have to assume
> > > > > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > > > > fd.
> > > > >
> > > >
> > > > I suggested sending the current state of each line as soon as the fd
> > > > is created so that you don't have to assume anything.  That initial change
> > > > report is effectively the same as having the user call LINEINFO on each
> > > > line at the same time - without all the race issues.
> > > >
> > >
> > > Right, I didn't get this. That means you'd have to call the ioctl()
> > > and immediately follow up with a read(). I'm not a fan of this.
> > > Especially if we were to include a timestamp - what would it refer to?
> > >
> > > > I also considered the full gpioline_info structure, as well as reporting
> > > > bulk line changes (replacing line_offset with lineoffsets and num_lines),
> > > > but then I thought it was probably better to keep it minimal and simple.
> > > > The name and consumer are relatively large.
> > >
> > > In terms of performance - we're not really degrading it as this is
> > > still not a lot of data.
> > >
> > > How about the following:
> > > 1. Move the code filling out the struct gpioline_info to a separate
> > > function in the kernel.
> > > 2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
> > > of GET_LINEINFO.
> > > 3. Embed struct gpioline_info in struct gpioline_info_fd so that the
> > > initial info be read when the file descriptor is created.
> > > 4. Likewise embed struct gpioline_info in struct gpioline_changed and
> > > on every status change event provide the whole set of information?
> > >
> >
> > You mean an array of info in the ioctl return, along with the fd?
> > That would work too.
> 
> I overlooked the fact that we can watch multiple lines. In this case
> it's either a separate fd for every watched line (not optimal) or
> doing what you proposed: read the initial lineinfo directly from the
> fd. But in this case again: what about the timestamp?
> 

It is set to the time the fd was created.
If you want you could also return that timestamp via the ioctl return
for comparison.
But most likely the userspace code will just ignore the timestamp for
those initial num_lines reports - those reports will get special
treatment anyway.

Those initial reports are really part of the fd request handshake, so
I would expect the reading and handling of them to be part of the fd
request function in libgpiod, and they would be hidden from the end user.
The fd would only be returned to the user after the initial reports
have already been read and the line states synchronised.
OTOH I haven't put much thought into how the libgpiod API would look...

Kent.

> Bart
> 
> >
> > I'm used to setting up streams like this in a networked environment
> > where returning data via the stream setup isn't an option, the only
> > option is to sync via the stream itself, so I overlooked the possibility
> > of using the ioctl return.
> >
> > > > The name is immutable(??), and so is pointless to include.
> > >
> > > It is now, but let's be future-proof. I can imagine having modifiable
> > > line names in the future. The code putting this info in struct
> > > gpioline_info wouldn't be really duplicated and the size of such small
> > > structures doesn't matter much - it's still a single context switch to
> > > read it.
> > >
> > > > The consumer only changes when the line is requested, so I was willing
> > > > to live with still having to poll for that.
> > > > And what you gain by reporting bulk lines you might lose in increased
> > > > report size and associated code.  OTOH it would make it explicit which
> > > > lines are being changed together...
> > > > So I could well be wrong on that - a full bulk report may be better.
> > >
> > > I'm not sure we need bulk reporting - just as we don't provide bulk
> > > GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> > > pretty complicated soon.
> > >
> >
> > That was my initial feeling as well.
> > And bulk is no longer an option if you want to include name in the change
> > report.
> >
> > Kent.
> >
> > > Bartosz
> > >
> > > >
> > > > Kent.
> > > >
> > > > > > And a timestamp might be useful, as per gpioevent_data?
> > > > >
> > > > > Sure thing!
> > > > >
> > > > > Bart
> > > > >
> > > > > >
> > > > > > Kent.
> > > > > >
> > > > > > >  /* Linerequest flags */
> > > > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > > > >
> > > > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > > > >
> > > > > > > --
> > > > > > > 2.23.0
> > > > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-28 15:02               ` Kent Gibson
@ 2019-11-29  9:43                 ` Bartosz Golaszewski
  2019-11-29 13:49                   ` Kent Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-29  9:43 UTC (permalink / raw)
  To: Kent Gibson
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

czw., 28 lis 2019 o 16:02 Kent Gibson <warthog618@gmail.com> napisał(a):
>
> On Thu, Nov 28, 2019 at 03:36:19PM +0100, Bartosz Golaszewski wrote:
> > czw., 28 lis 2019 o 15:10 Kent Gibson <warthog618@gmail.com> napisał(a):
> > >
> > > On Thu, Nov 28, 2019 at 10:45:46AM +0100, Bartosz Golaszewski wrote:
> > > > czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > >
> > > > > On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > > > > > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > > >
> > > > > > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > > > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > >
> > > > > > > > Currently there is no way for user-space to be informed about changes
> > > > > > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > > > > > config changes. We can only periodically re-read the line-info. This
> > > > > > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > > > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > > > > > driven line info synchronization.
> > > > > > > >
> > > > > > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > > > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > > > > > change events.
> > > > > > > >
> > > > > > > > Currently the events are generated on three types of status changes: when
> > > > > > > > a line is requested, when it's released and when its config is changed.
> > > > > > > > The first two are self-explanatory. For the third one: this will only
> > > > > > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > > > > > as any changes that can happen from within the kernel (i.e.
> > > > > > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > > > > > >
> > > > > > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > > ---
> > > > > > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > > > > > >  drivers/gpio/gpiolib.h    |   1 +
> > > > > > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > > > > > >  3 files changed, 255 insertions(+)
> > > > > > > >
> > > > > > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > > > > > index d094b1be334d..be5df4bdf44b 100644
> > > > > > > > --- a/drivers/gpio/gpiolib.c
> > > > > > > > +++ b/drivers/gpio/gpiolib.c
> > > > > > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > > > > > >                       if (ret)
> > > > > > > >                               return ret;
> > > > > > > >               }
> > > > > > > > +
> > > > > > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > > > > > >       }
> > > > > > > >       return 0;
> > > > > > > >  }
> > > > > > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > >       return ret;
> > > > > > > >  }
> > > > > > > >
> > > > > > > > +struct linechanged_fd_state {
> > > > > > > > +     struct gpio_device *gdev;
> > > > > > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > > > > > +     size_t numdescs;
> > > > > > > > +     wait_queue_head_t waitqueue;
> > > > > > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > > > > > +     struct mutex lock;
> > > > > > > > +     struct notifier_block changed_nb;
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > > > > > +{
> > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > +
> > > > > > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > +     put_device(&lc_state->gdev->dev);
> > > > > > > > +     kfree(lc_state);
> > > > > > > > +
> > > > > > > > +     return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > > > > > +                                 struct poll_table_struct *pollt)
> > > > > > > > +{
> > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > +     __poll_t events = 0;
> > > > > > > > +
> > > > > > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > > > > > +
> > > > > > > > +     mutex_lock(&lc_state->lock);
> > > > > > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > > > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > > > > > +     mutex_unlock(&lc_state->lock);
> > > > > > > > +
> > > > > > > > +     return events;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > > > > > +                                size_t count, loff_t *off)
> > > > > > > > +{
> > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > +     unsigned int copied;
> > > > > > > > +     int ret;
> > > > > > > > +
> > > > > > > > +     if (count < sizeof(struct gpioline_changed))
> > > > > > > > +             return -EINVAL;
> > > > > > > > +
> > > > > > > > +     do {
> > > > > > > > +             mutex_lock(&lc_state->lock);
> > > > > > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > > > > > +                             return -EAGAIN;
> > > > > > > > +
> > > > > > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > > > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > > > > > +                     if (ret)
> > > > > > > > +                             return ret;
> > > > > > > > +             } else {
> > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > +             }
> > > > > > > > +
> > > > > > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > > > > > +                     return -ERESTARTSYS;
> > > > > > > > +
> > > > > > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > > > > > +             mutex_unlock(&lc_state->lock);
> > > > > > > > +             if (ret)
> > > > > > > > +                     return ret;
> > > > > > > > +
> > > > > > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > > > > > +                     return -EAGAIN;
> > > > > > > > +     } while (copied == 0);
> > > > > > > > +
> > > > > > > > +     return copied;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > > > > > +     .release = linechanged_fd_release,
> > > > > > > > +     .owner = THIS_MODULE,
> > > > > > > > +     .llseek = noop_llseek,
> > > > > > > > +     .poll = linechanged_fd_poll,
> > > > > > > > +     .read = linechanged_fd_read,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +static struct linechanged_fd_state *
> > > > > > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > > > > > +{
> > > > > > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > > > > > +                              unsigned long action, void *data)
> > > > > > > > +{
> > > > > > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > > > > > +     struct gpio_desc *desc = data;
> > > > > > > > +     struct gpioline_changed chg;
> > > > > > > > +     int i, ret;
> > > > > > > > +
> > > > > > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > > > > > +             /* Are we watching this desc? */
> > > > > > > > +             if (desc == lc_state->descs[i]) {
> > > > > > > > +                     /* Yes - prepare the event. */
> > > > > > > > +                     memset(&chg, 0, sizeof(chg));
> > > > > > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > > > > > +                     chg.event_type = action;
> > > > > > > > +
> > > > > > > > +                     mutex_lock(&lc_state->lock);
> > > > > > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > +                     if (ret)
> > > > > > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > > > > > +
> > > > > > > > +                     return NOTIFY_OK;
> > > > > > > > +             }
> > > > > > > > +     }
> > > > > > > > +
> > > > > > > > +     return NOTIFY_DONE;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > > +{
> > > > > > > > +     struct gpioline_changed_fd_request changed_req;
> > > > > > > > +     struct linechanged_fd_state *lc_state;
> > > > > > > > +     struct gpio_desc *desc;
> > > > > > > > +     struct file *file;
> > > > > > > > +     int ret, i, fd;
> > > > > > > > +     u32 offset;
> > > > > > > > +
> > > > > > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > > > > > +     if (ret)
> > > > > > > > +             return -EFAULT;
> > > > > > > > +
> > > > > > > > +     if ((changed_req.num_lines == 0) ||
> > > > > > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > > > > > +             return -EINVAL;
> > > > > > > > +
> > > > > > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > > > > > +     if (!lc_state)
> > > > > > > > +             return -ENOMEM;
> > > > > > > > +
> > > > > > > > +     lc_state->gdev = gdev;
> > > > > > > > +     get_device(&gdev->dev);
> > > > > > > > +
> > > > > > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > > > > > +             offset = changed_req.lineoffsets[i];
> > > > > > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > > > > > +             if (IS_ERR(desc)) {
> > > > > > > > +                     ret = PTR_ERR(desc);
> > > > > > > > +                     goto out_free_lc_state;
> > > > > > > > +             }
> > > > > > > > +
> > > > > > > > +             lc_state->descs[i] = desc;
> > > > > > > > +     }
> > > > > > > > +
> > > > > > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > > > > > +
> > > > > > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > > > > > +     INIT_KFIFO(lc_state->events);
> > > > > > > > +     mutex_init(&lc_state->lock);
> > > > > > > > +
> > > > > > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > > > > > +
> > > > > > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > > > > > +                                          &lc_state->changed_nb);
> > > > > > > > +     if (ret)
> > > > > > > > +             goto out_free_lc_state;
> > > > > > > > +
> > > > > > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > > > > > +     if (fd < 0) {
> > > > > > > > +             ret = fd;
> > > > > > > > +             goto out_unregister_notifier;
> > > > > > > > +     }
> > > > > > > > +
> > > > > > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > > > > > +                               &linechanged_fd_fileops,
> > > > > > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > > > > > +     if (IS_ERR(file)) {
> > > > > > > > +             ret = PTR_ERR(file);
> > > > > > > > +             goto out_put_unused_fd;
> > > > > > > > +     }
> > > > > > > > +
> > > > > > > > +     changed_req.fd = fd;
> > > > > > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > > > > > +     if (ret) {
> > > > > > > > +             fput(file);
> > > > > > > > +             put_unused_fd(fd);
> > > > > > > > +             return -EFAULT;
> > > > > > > > +     }
> > > > > > > > +
> > > > > > > > +     fd_install(fd, file);
> > > > > > > > +
> > > > > > > > +     return 0;
> > > > > > > > +
> > > > > > > > +out_put_unused_fd:
> > > > > > > > +     put_unused_fd(fd);
> > > > > > > > +out_unregister_notifier:
> > > > > > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > +out_free_lc_state:
> > > > > > > > +     kfree(lc_state);
> > > > > > > > +
> > > > > > > > +     return ret;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > >  /*
> > > > > > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > > > > > >   */
> > > > > > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > > > > >               return linehandle_create(gdev, ip);
> > > > > > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > > > > > >               return lineevent_create(gdev, ip);
> > > > > > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > > > > > +             return linechanged_fd_create(gdev, ip);
> > > > > > > >       }
> > > > > > > >       return -EINVAL;
> > > > > > > >  }
> > > > > > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > > > > > >       for (i = 0; i < chip->ngpio; i++)
> > > > > > > >               gdev->descs[i].gdev = gdev;
> > > > > > > >
> > > > > > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > > > > > +
> > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > > > > > >  #endif
> > > > > > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > > > > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > > > > > >       }
> > > > > > > >  done:
> > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > >       return ret;
> > > > > > > >  }
> > > > > > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > > > > > >               ret = true;
> > > > > > > >       }
> > > > > > > >
> > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > >       return ret;
> > > > > > > >  }
> > > > > > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > > > > > >               if (ret != -ENOTSUPP)
> > > > > > > >                       return ret;
> > > > > > > >       }
> > > > > > > > +
> > > > > > > >       return 0;
> > > > > > > >  }
> > > > > > > >
> > > > > > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > > > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > > > > > --- a/drivers/gpio/gpiolib.h
> > > > > > > > +++ b/drivers/gpio/gpiolib.h
> > > > > > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > > > > > >       const char              *label;
> > > > > > > >       void                    *data;
> > > > > > > >       struct list_head        list;
> > > > > > > > +     struct atomic_notifier_head notifier;
> > > > > > > >
> > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > >       /*
> > > > > > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > > > > > index 799cf823d493..c61429467dd4 100644
> > > > > > > > --- a/include/uapi/linux/gpio.h
> > > > > > > > +++ b/include/uapi/linux/gpio.h
> > > > > > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > > > > > >  /* Maximum number of requested handles */
> > > > > > > >  #define GPIOHANDLES_MAX 64
> > > > > > > >
> > > > > > > > +/**
> > > > > > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > > > > > + * request
> > > > > > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > > > > > + * associated GPIO device
> > > > > > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > > > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > > > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > > > > > + */
> > > > > > > > +struct gpioline_changed_fd_request {
> > > > > > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > > > > > +     __u32 num_lines;
> > > > > > > > +     int fd;
> > > > > > > > +};
> > > > > > > > +
> > > > > > >
> > > > > > > Wouldn't the most common case be to watch all the lines on a chip?
> > > > > > > How about an easy way to do that, say num_lines=0?
> > > > > > >
> > > > > >
> > > > > > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > > > > > keep it this way for the same reason I didn't want to have implicit
> > > > > > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > > > > > wrap it in simple helpers in the library.
> > > > > >
> > > > >
> > > > > Or you could add a watch_all flag to make it explicit.
> > > > > But fair enough to leave as is - it probably keeps the kernel cleaner.
> > > > >
> > > > > > > > +/* Possible line status change events */
> > > > > > > > +enum {
> > > > > > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > > > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > > > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > > +/**
> > > > > > > > + * struct gpioline_changed - Information about a change in status
> > > > > > > > + * of a GPIO line
> > > > > > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > > > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > > > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > > > > > + */
> > > > > > > > +struct gpioline_changed {
> > > > > > > > +     __u32 line_offset;
> > > > > > > > +     __u32 event_type;
> > > > > > > > +};
> > > > > > > > +
> > > > > > >
> > > > > > > Rather than sending an event type, and requiring userspace to poll
> > > > > > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > > > > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > > > > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > > > > > event_type is then redundant.
> > > > > > > Userspace would then only have to poll LINEINFO if they were interested
> > > > > > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > > > > > >
> > > > > > > To sync kernel and userspace state the current state of each line
> > > > > > > should be returned immediately via the fd as soon as the fd is created,
> > > > > > > and then subsequently on any change.
> > > > > > >
> > > > > >
> > > > > > I guess you're right. You even made me think we could go as far as to
> > > > > > embed the whole gpioline_info structure in struct gpioline_changed.
> > > > > > I'd still keep the event type though - otherwise we'd have to assume
> > > > > > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > > > > > fd.
> > > > > >
> > > > >
> > > > > I suggested sending the current state of each line as soon as the fd
> > > > > is created so that you don't have to assume anything.  That initial change
> > > > > report is effectively the same as having the user call LINEINFO on each
> > > > > line at the same time - without all the race issues.
> > > > >
> > > >
> > > > Right, I didn't get this. That means you'd have to call the ioctl()
> > > > and immediately follow up with a read(). I'm not a fan of this.
> > > > Especially if we were to include a timestamp - what would it refer to?
> > > >
> > > > > I also considered the full gpioline_info structure, as well as reporting
> > > > > bulk line changes (replacing line_offset with lineoffsets and num_lines),
> > > > > but then I thought it was probably better to keep it minimal and simple.
> > > > > The name and consumer are relatively large.
> > > >
> > > > In terms of performance - we're not really degrading it as this is
> > > > still not a lot of data.
> > > >
> > > > How about the following:
> > > > 1. Move the code filling out the struct gpioline_info to a separate
> > > > function in the kernel.
> > > > 2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
> > > > of GET_LINEINFO.
> > > > 3. Embed struct gpioline_info in struct gpioline_info_fd so that the
> > > > initial info be read when the file descriptor is created.
> > > > 4. Likewise embed struct gpioline_info in struct gpioline_changed and
> > > > on every status change event provide the whole set of information?
> > > >
> > >
> > > You mean an array of info in the ioctl return, along with the fd?
> > > That would work too.
> >
> > I overlooked the fact that we can watch multiple lines. In this case
> > it's either a separate fd for every watched line (not optimal) or
> > doing what you proposed: read the initial lineinfo directly from the
> > fd. But in this case again: what about the timestamp?
> >
>
> It is set to the time the fd was created.
> If you want you could also return that timestamp via the ioctl return
> for comparison.
> But most likely the userspace code will just ignore the timestamp for
> those initial num_lines reports - those reports will get special
> treatment anyway.
>
> Those initial reports are really part of the fd request handshake, so
> I would expect the reading and handling of them to be part of the fd
> request function in libgpiod, and they would be hidden from the end user.
> The fd would only be returned to the user after the initial reports
> have already been read and the line states synchronised.
> OTOH I haven't put much thought into how the libgpiod API would look...

So I can imagine a situation where we'd run into problems with this:
when we start watching the maximum number of lines - 64 - the kfifo
would be filled with reports from the start and we'd risk missing some
later ones if we don't make this kfifo much larger and if the user
doesn't manage to consume them all fast.

How about reusing the already existing file descriptor associated with
the chip itself? We currently only implement the ioctl() operation on
it - the poll() and read() callbacks are empty.

We'd need to add two new ioctls(): GPIOLINE_WATCH_IOCTL and
GPIOLINE_UNWATCH_IOCTL. The structures for both would look like this:

struct gpioline_watch_request {
    __u32 lineoffset
    struct gpioline_info info;
};

struct gpioline_unwatch_request {
    __u32 lineoffset;
};

When GPIOLINE_WATCH_IOCTL is called, we'd setup a watch for given
line: the embedded gpioline_info structure would be filled with
initial info and we can now poll the gpiochip descriptor for events
and read them. The event structure would looks like this:

struct gpioline_changed {
    __u32 event_type;
    __u64 timestamp;
    struct gpioline_info info;
};

Calling GPIOLINE_UNWATCH_IOCTL would of course make the kernel stop
emitting events for given line.

Does it make sense?

Bart

>
> Kent.
>
> > Bart
> >
> > >
> > > I'm used to setting up streams like this in a networked environment
> > > where returning data via the stream setup isn't an option, the only
> > > option is to sync via the stream itself, so I overlooked the possibility
> > > of using the ioctl return.
> > >
> > > > > The name is immutable(??), and so is pointless to include.
> > > >
> > > > It is now, but let's be future-proof. I can imagine having modifiable
> > > > line names in the future. The code putting this info in struct
> > > > gpioline_info wouldn't be really duplicated and the size of such small
> > > > structures doesn't matter much - it's still a single context switch to
> > > > read it.
> > > >
> > > > > The consumer only changes when the line is requested, so I was willing
> > > > > to live with still having to poll for that.
> > > > > And what you gain by reporting bulk lines you might lose in increased
> > > > > report size and associated code.  OTOH it would make it explicit which
> > > > > lines are being changed together...
> > > > > So I could well be wrong on that - a full bulk report may be better.
> > > >
> > > > I'm not sure we need bulk reporting - just as we don't provide bulk
> > > > GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> > > > pretty complicated soon.
> > > >
> > >
> > > That was my initial feeling as well.
> > > And bulk is no longer an option if you want to include name in the change
> > > report.
> > >
> > > Kent.
> > >
> > > > Bartosz
> > > >
> > > > >
> > > > > Kent.
> > > > >
> > > > > > > And a timestamp might be useful, as per gpioevent_data?
> > > > > >
> > > > > > Sure thing!
> > > > > >
> > > > > > Bart
> > > > > >
> > > > > > >
> > > > > > > Kent.
> > > > > > >
> > > > > > > >  /* Linerequest flags */
> > > > > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > > > > >
> > > > > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > > > > >
> > > > > > > > --
> > > > > > > > 2.23.0
> > > > > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
                   ` (7 preceding siblings ...)
  2019-11-27 13:35 ` [PATCH 8/8] tools: gpio: implement gpio-watch Bartosz Golaszewski
@ 2019-11-29 10:04 ` Linus Walleij
  2019-11-29 10:58   ` Bartosz Golaszewski
  8 siblings, 1 reply; 32+ messages in thread
From: Linus Walleij @ 2019-11-29 10:04 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Kent Gibson, open list:GPIO SUBSYSTEM, linux-kernel, Bartosz Golaszewski

On Wed, Nov 27, 2019 at 2:35 PM Bartosz Golaszewski <brgl@bgdev.pl> wrote:

> This series adds a new ioctl() that allows user-space to retrieve a
> file-descriptor which can then be polled for events emitted by the kernel
> when the line is requested, released or its status changed. This of course
> doesn't require the line to be requested. Multiple user-space processes
> can watch the same lines.

So if I understand correctly all the series do is expose metadata about
all GPIO lines to userspace?

I think up until now the use case assumptions have been:

- The kernel will pick off some GPIO lines, mostly during boot but
  occasionally at runtime (by users such as kernel modules or
  hotlugged devices).

- Userspace will pick some lines from those that are available,
  after the kernel picked those it wants. If it tries to pick one of
  those that the kernel already picked, the request will be denied.

The assumption (at least in my head) was that the GPIOs the
kernel picks will not be a very dynamic business.

So this appears to be dealing with this very dynamic business.

Is the *main* use case different userspace processes trying
to use the same pins and getting denied? Because in that
case we might be putting a bit too much userspace plumbing
into the kernel and we need to think about that for a while.
(Binder and kdbus etc comes to mind.)

So there is some feature growth happening here and I want
to be aware of the whole picture.

On a side track:

There is a bit about policy that needs to happen here I suppose,
like for example what if the kernel actually wants one of the
lines that userspace has picked? Should userspace be kicked
out and kernel get what it wants? (Arguably yes.)

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-11-29 10:04 ` [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Linus Walleij
@ 2019-11-29 10:58   ` Bartosz Golaszewski
  2019-11-29 12:57     ` Linus Walleij
  2019-12-04 12:06     ` Enrico Weigelt, metux IT consult
  0 siblings, 2 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-11-29 10:58 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Kent Gibson, open list:GPIO SUBSYSTEM, linux-kernel, Bartosz Golaszewski

pt., 29 lis 2019 o 11:04 Linus Walleij <linus.walleij@linaro.org> napisał(a):
>
> On Wed, Nov 27, 2019 at 2:35 PM Bartosz Golaszewski <brgl@bgdev.pl> wrote:
>
> > This series adds a new ioctl() that allows user-space to retrieve a
> > file-descriptor which can then be polled for events emitted by the kernel
> > when the line is requested, released or its status changed. This of course
> > doesn't require the line to be requested. Multiple user-space processes
> > can watch the same lines.
>
> So if I understand correctly all the series do is expose metadata about
> all GPIO lines to userspace?
>

I'd say it's about adding a way to dynamically watch changes in this metadata.

> I think up until now the use case assumptions have been:
>
> - The kernel will pick off some GPIO lines, mostly during boot but
>   occasionally at runtime (by users such as kernel modules or
>   hotlugged devices).
>

Indeed.

> - Userspace will pick some lines from those that are available,
>   after the kernel picked those it wants. If it tries to pick one of
>   those that the kernel already picked, the request will be denied.
>
> The assumption (at least in my head) was that the GPIOs the
> kernel picks will not be a very dynamic business.
>
> So this appears to be dealing with this very dynamic business.
>
> Is the *main* use case different userspace processes trying
> to use the same pins and getting denied? Because in that
> case we might be putting a bit too much userspace plumbing
> into the kernel and we need to think about that for a while.
> (Binder and kdbus etc comes to mind.)
>

No, it really is just about keeping the line information in user-space
synchronized with the one in the kernel without re-reading the line
info periodically. Whether it's the kernel that requests the line or
other user-space process doesn't matter. We just want to stay
up-to-date with the information we already do have access to.

> So there is some feature growth happening here and I want
> to be aware of the whole picture.
>

It may seem like a feature-creep because this is the third new feature
being added to the character device in a short span of time. But at
the same time: user-space wants to use GPIOs and they're mostly doing
it over sysfs. If you want people to switch over to the character
device - we must make it at least as useful as sysfs.

These new features are not unjustified: I receive a significant amount
of mail with feature-requests for libgpiod (also from people who are
not well aware that I can only support features exposed by mainline
kernel).

It turns out that RPi people really wanted the BIAS settings because
the downstream RPi GPIO interface supports it. Having added this may
now make them switch to libgpiod.

Old sysfs interface allows to change the direction of lines or their
active-low setting at run-time and it turned out this too is a
functionality people want to see in libgpiod. Thanks to Kent's effort
we now have it.

Last thing that users often complain about is the fact that with the
character device, the state of GPIO lines used by user-space needs to
be kept by the process using them. This unfortunately often comes from
the lack of understanding of how a character device functions, but it
really seems users want to have a centralized agent that takes control
of the lines, provides standardized interface to interact with them
and exports their metadata. Recognizing this fact, I implemented a
proof-of-concept dbus daemon, but one thing that it could really
benefit from is dynamic, event-based synchronization and this series
tries to add just that (BTW please take a look at the discussion under
patch 7/8 - the code in this series will probably change a lot).

I believe this may be the last missing piece and after that we can
really consider this ABI feature-complete.

> On a side track:
>
> There is a bit about policy that needs to happen here I suppose,
> like for example what if the kernel actually wants one of the
> lines that userspace has picked? Should userspace be kicked
> out and kernel get what it wants? (Arguably yes.)
>

We should probably start a new thread on this. I'll play the
user-space's advocate and say: has anyone ever needed this? Do other
kernel sub-systems do this?

> Yours,
> Linus Walleij

Best regards,
Bartosz

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-11-29 10:58   ` Bartosz Golaszewski
@ 2019-11-29 12:57     ` Linus Walleij
  2019-12-04 12:32       ` Enrico Weigelt, metux IT consult
  2019-12-04 12:06     ` Enrico Weigelt, metux IT consult
  1 sibling, 1 reply; 32+ messages in thread
From: Linus Walleij @ 2019-11-29 12:57 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Kent Gibson, open list:GPIO SUBSYSTEM, linux-kernel, Bartosz Golaszewski

On Fri, Nov 29, 2019 at 11:58 AM Bartosz Golaszewski <brgl@bgdev.pl> wrote:
> pt., 29 lis 2019 o 11:04 Linus Walleij <linus.walleij@linaro.org> napisał(a):

> > Is the *main* use case different userspace processes trying
> > to use the same pins and getting denied? Because in that
> > case we might be putting a bit too much userspace plumbing
> > into the kernel and we need to think about that for a while.
> > (Binder and kdbus etc comes to mind.)
>
> No, it really is just about keeping the line information in user-space
> synchronized with the one in the kernel without re-reading the line
> info periodically. Whether it's the kernel that requests the line or
> other user-space process doesn't matter. We just want to stay
> up-to-date with the information we already do have access to.

I was more after whether the expected conflict we resolve
will be mostly between kernel and userspace or between
userspace and userspace, statistically speaking. Like: what
is happening to actual systems people are trying to deploy.

> > So there is some feature growth happening here and I want
> > to be aware of the whole picture.
>
> It may seem like a feature-creep because this is the third new feature
> being added to the character device in a short span of time. But at
> the same time: user-space wants to use GPIOs and they're mostly doing
> it over sysfs. If you want people to switch over to the character
> device - we must make it at least as useful as sysfs.

So a part of the design criteria is to provide the same functions
as e.g. putting inotify's on /sys/class/gpio/* and watching as
files come and go or something like this?

(That sounds reasonable.)

> These new features are not unjustified: I receive a significant amount
> of mail with feature-requests for libgpiod (also from people who are
> not well aware that I can only support features exposed by mainline
> kernel).

OK

> It turns out that RPi people really wanted the BIAS settings because
> the downstream RPi GPIO interface supports it. Having added this may
> now make them switch to libgpiod.

That's good news, I think Drew said the same thing about
Beagle.

> Old sysfs interface allows to change the direction of lines or their
> active-low setting at run-time and it turned out this too is a
> functionality people want to see in libgpiod. Thanks to Kent's effort
> we now have it.

Yeah that's a win.

> Last thing that users often complain about is the fact that with the
> character device, the state of GPIO lines used by user-space needs to
> be kept by the process using them. This unfortunately often comes from
> the lack of understanding of how a character device functions, but it
> really seems users want to have a centralized agent that takes control
> of the lines, provides standardized interface to interact with them
> and exports their metadata. Recognizing this fact, I implemented a
> proof-of-concept dbus daemon, but one thing that it could really
> benefit from is dynamic, event-based synchronization and this series
> tries to add just that (BTW please take a look at the discussion under
> patch 7/8 - the code in this series will probably change a lot).

OK

> I believe this may be the last missing piece and after that we can
> really consider this ABI feature-complete.

OK that is a good point.

My own pet peeve is the industrial automation and control use
case: here we have the design space where people today use
either PLC:s or RaspberryPi's or Beagle boards, or even some
custom computers.

For me personally that is a design space we should cover and
if this helps the RaspberryPi to do that better I'm all for it.

An especially interesting case is multiple GPIO expanders
plugged in on pluggable busses such as PCI or USB. I think
that kind of discoverability and dynamically expandable GPIO
blocks is something people in the industry are quite keen to
get.

What we need to do is to make it dirt simple to use GPIOs for
custom hacks and construction of factories and doorstops
and what not, while at the same time strongly discouraging
it to be used to manage hardware such as laptops, tablets
or phones from userspace. That's maybe hard, and we might
be victims of our own success ...

However the eradication of the sysfs ABI seems to be well
on track!

> > On a side track:
> >
> > There is a bit about policy that needs to happen here I suppose,
> > like for example what if the kernel actually wants one of the
> > lines that userspace has picked? Should userspace be kicked
> > out and kernel get what it wants? (Arguably yes.)
> >
>
> We should probably start a new thread on this. I'll play the
> user-space's advocate and say: has anyone ever needed this? Do other
> kernel sub-systems do this?

So far it was designed under the assumption that this kind of
collisions are handled on a first-come-first serve basis, and
that is indeed how it works today.

I'm just not sure that this assumption is going to hold in the
future so wanted to make a little check-up.

Linus

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-29  9:43                 ` Bartosz Golaszewski
@ 2019-11-29 13:49                   ` Kent Gibson
  2019-12-01 15:25                     ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Kent Gibson @ 2019-11-29 13:49 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List, Bartosz Golaszewski

On Fri, Nov 29, 2019 at 10:43:32AM +0100, Bartosz Golaszewski wrote:
> czw., 28 lis 2019 o 16:02 Kent Gibson <warthog618@gmail.com> napisał(a):
> >
> > On Thu, Nov 28, 2019 at 03:36:19PM +0100, Bartosz Golaszewski wrote:
> > > czw., 28 lis 2019 o 15:10 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > >
> > > > On Thu, Nov 28, 2019 at 10:45:46AM +0100, Bartosz Golaszewski wrote:
> > > > > czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > >
> > > > > > On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > > > > > > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > > > >
> > > > > > > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > > > > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > > >
> > > > > > > > > Currently there is no way for user-space to be informed about changes
> > > > > > > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > > > > > > config changes. We can only periodically re-read the line-info. This
> > > > > > > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > > > > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > > > > > > driven line info synchronization.
> > > > > > > > >
> > > > > > > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > > > > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > > > > > > change events.
> > > > > > > > >
> > > > > > > > > Currently the events are generated on three types of status changes: when
> > > > > > > > > a line is requested, when it's released and when its config is changed.
> > > > > > > > > The first two are self-explanatory. For the third one: this will only
> > > > > > > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > > > > > > as any changes that can happen from within the kernel (i.e.
> > > > > > > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > > > > > > >
> > > > > > > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > > > ---
> > > > > > > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > > > > > > >  drivers/gpio/gpiolib.h    |   1 +
> > > > > > > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > > > > > > >  3 files changed, 255 insertions(+)
> > > > > > > > >
> > > > > > > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > > > > > > index d094b1be334d..be5df4bdf44b 100644
> > > > > > > > > --- a/drivers/gpio/gpiolib.c
> > > > > > > > > +++ b/drivers/gpio/gpiolib.c
> > > > > > > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > > > > > > >                       if (ret)
> > > > > > > > >                               return ret;
> > > > > > > > >               }
> > > > > > > > > +
> > > > > > > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > > > > > > >       }
> > > > > > > > >       return 0;
> > > > > > > > >  }
> > > > > > > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > > >       return ret;
> > > > > > > > >  }
> > > > > > > > >
> > > > > > > > > +struct linechanged_fd_state {
> > > > > > > > > +     struct gpio_device *gdev;
> > > > > > > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > > > > > > +     size_t numdescs;
> > > > > > > > > +     wait_queue_head_t waitqueue;
> > > > > > > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > > > > > > +     struct mutex lock;
> > > > > > > > > +     struct notifier_block changed_nb;
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > > > > > > +{
> > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > +
> > > > > > > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > > +     put_device(&lc_state->gdev->dev);
> > > > > > > > > +     kfree(lc_state);
> > > > > > > > > +
> > > > > > > > > +     return 0;
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > > > > > > +                                 struct poll_table_struct *pollt)
> > > > > > > > > +{
> > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > +     __poll_t events = 0;
> > > > > > > > > +
> > > > > > > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > > > > > > +
> > > > > > > > > +     mutex_lock(&lc_state->lock);
> > > > > > > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > > > > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > > > > > > +     mutex_unlock(&lc_state->lock);
> > > > > > > > > +
> > > > > > > > > +     return events;
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > > > > > > +                                size_t count, loff_t *off)
> > > > > > > > > +{
> > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > +     unsigned int copied;
> > > > > > > > > +     int ret;
> > > > > > > > > +
> > > > > > > > > +     if (count < sizeof(struct gpioline_changed))
> > > > > > > > > +             return -EINVAL;
> > > > > > > > > +
> > > > > > > > > +     do {
> > > > > > > > > +             mutex_lock(&lc_state->lock);
> > > > > > > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > > > > > > +                             return -EAGAIN;
> > > > > > > > > +
> > > > > > > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > > > > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > > > > > > +                     if (ret)
> > > > > > > > > +                             return ret;
> > > > > > > > > +             } else {
> > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > +             }
> > > > > > > > > +
> > > > > > > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > > > > > > +                     return -ERESTARTSYS;
> > > > > > > > > +
> > > > > > > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > > > > > > +             mutex_unlock(&lc_state->lock);
> > > > > > > > > +             if (ret)
> > > > > > > > > +                     return ret;
> > > > > > > > > +
> > > > > > > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > > > > > > +                     return -EAGAIN;
> > > > > > > > > +     } while (copied == 0);
> > > > > > > > > +
> > > > > > > > > +     return copied;
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > > > > > > +     .release = linechanged_fd_release,
> > > > > > > > > +     .owner = THIS_MODULE,
> > > > > > > > > +     .llseek = noop_llseek,
> > > > > > > > > +     .poll = linechanged_fd_poll,
> > > > > > > > > +     .read = linechanged_fd_read,
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > > > +static struct linechanged_fd_state *
> > > > > > > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > > > > > > +{
> > > > > > > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > > > > > > +                              unsigned long action, void *data)
> > > > > > > > > +{
> > > > > > > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > > > > > > +     struct gpio_desc *desc = data;
> > > > > > > > > +     struct gpioline_changed chg;
> > > > > > > > > +     int i, ret;
> > > > > > > > > +
> > > > > > > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > > > > > > +             /* Are we watching this desc? */
> > > > > > > > > +             if (desc == lc_state->descs[i]) {
> > > > > > > > > +                     /* Yes - prepare the event. */
> > > > > > > > > +                     memset(&chg, 0, sizeof(chg));
> > > > > > > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > > > > > > +                     chg.event_type = action;
> > > > > > > > > +
> > > > > > > > > +                     mutex_lock(&lc_state->lock);
> > > > > > > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > +                     if (ret)
> > > > > > > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > > > > > > +
> > > > > > > > > +                     return NOTIFY_OK;
> > > > > > > > > +             }
> > > > > > > > > +     }
> > > > > > > > > +
> > > > > > > > > +     return NOTIFY_DONE;
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > > > +{
> > > > > > > > > +     struct gpioline_changed_fd_request changed_req;
> > > > > > > > > +     struct linechanged_fd_state *lc_state;
> > > > > > > > > +     struct gpio_desc *desc;
> > > > > > > > > +     struct file *file;
> > > > > > > > > +     int ret, i, fd;
> > > > > > > > > +     u32 offset;
> > > > > > > > > +
> > > > > > > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > > > > > > +     if (ret)
> > > > > > > > > +             return -EFAULT;
> > > > > > > > > +
> > > > > > > > > +     if ((changed_req.num_lines == 0) ||
> > > > > > > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > > > > > > +             return -EINVAL;
> > > > > > > > > +
> > > > > > > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > > > > > > +     if (!lc_state)
> > > > > > > > > +             return -ENOMEM;
> > > > > > > > > +
> > > > > > > > > +     lc_state->gdev = gdev;
> > > > > > > > > +     get_device(&gdev->dev);
> > > > > > > > > +
> > > > > > > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > > > > > > +             offset = changed_req.lineoffsets[i];
> > > > > > > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > > > > > > +             if (IS_ERR(desc)) {
> > > > > > > > > +                     ret = PTR_ERR(desc);
> > > > > > > > > +                     goto out_free_lc_state;
> > > > > > > > > +             }
> > > > > > > > > +
> > > > > > > > > +             lc_state->descs[i] = desc;
> > > > > > > > > +     }
> > > > > > > > > +
> > > > > > > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > > > > > > +
> > > > > > > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > > > > > > +     INIT_KFIFO(lc_state->events);
> > > > > > > > > +     mutex_init(&lc_state->lock);
> > > > > > > > > +
> > > > > > > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > > > > > > +
> > > > > > > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > > > > > > +                                          &lc_state->changed_nb);
> > > > > > > > > +     if (ret)
> > > > > > > > > +             goto out_free_lc_state;
> > > > > > > > > +
> > > > > > > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > > > > > > +     if (fd < 0) {
> > > > > > > > > +             ret = fd;
> > > > > > > > > +             goto out_unregister_notifier;
> > > > > > > > > +     }
> > > > > > > > > +
> > > > > > > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > > > > > > +                               &linechanged_fd_fileops,
> > > > > > > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > > > > > > +     if (IS_ERR(file)) {
> > > > > > > > > +             ret = PTR_ERR(file);
> > > > > > > > > +             goto out_put_unused_fd;
> > > > > > > > > +     }
> > > > > > > > > +
> > > > > > > > > +     changed_req.fd = fd;
> > > > > > > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > > > > > > +     if (ret) {
> > > > > > > > > +             fput(file);
> > > > > > > > > +             put_unused_fd(fd);
> > > > > > > > > +             return -EFAULT;
> > > > > > > > > +     }
> > > > > > > > > +
> > > > > > > > > +     fd_install(fd, file);
> > > > > > > > > +
> > > > > > > > > +     return 0;
> > > > > > > > > +
> > > > > > > > > +out_put_unused_fd:
> > > > > > > > > +     put_unused_fd(fd);
> > > > > > > > > +out_unregister_notifier:
> > > > > > > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > > +out_free_lc_state:
> > > > > > > > > +     kfree(lc_state);
> > > > > > > > > +
> > > > > > > > > +     return ret;
> > > > > > > > > +}
> > > > > > > > > +
> > > > > > > > >  /*
> > > > > > > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > > > > > > >   */
> > > > > > > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > > > > > >               return linehandle_create(gdev, ip);
> > > > > > > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > > > > > > >               return lineevent_create(gdev, ip);
> > > > > > > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > > > > > > +             return linechanged_fd_create(gdev, ip);
> > > > > > > > >       }
> > > > > > > > >       return -EINVAL;
> > > > > > > > >  }
> > > > > > > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > > > > > > >       for (i = 0; i < chip->ngpio; i++)
> > > > > > > > >               gdev->descs[i].gdev = gdev;
> > > > > > > > >
> > > > > > > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > > > > > > +
> > > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > > > > > > >  #endif
> > > > > > > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > > > > > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > > > > > > >       }
> > > > > > > > >  done:
> > > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > > >       return ret;
> > > > > > > > >  }
> > > > > > > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > > > > > > >               ret = true;
> > > > > > > > >       }
> > > > > > > > >
> > > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > > >       return ret;
> > > > > > > > >  }
> > > > > > > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > > > > > > >               if (ret != -ENOTSUPP)
> > > > > > > > >                       return ret;
> > > > > > > > >       }
> > > > > > > > > +
> > > > > > > > >       return 0;
> > > > > > > > >  }
> > > > > > > > >
> > > > > > > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > > > > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > > > > > > --- a/drivers/gpio/gpiolib.h
> > > > > > > > > +++ b/drivers/gpio/gpiolib.h
> > > > > > > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > > > > > > >       const char              *label;
> > > > > > > > >       void                    *data;
> > > > > > > > >       struct list_head        list;
> > > > > > > > > +     struct atomic_notifier_head notifier;
> > > > > > > > >
> > > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > > >       /*
> > > > > > > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > > > > > > index 799cf823d493..c61429467dd4 100644
> > > > > > > > > --- a/include/uapi/linux/gpio.h
> > > > > > > > > +++ b/include/uapi/linux/gpio.h
> > > > > > > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > > > > > > >  /* Maximum number of requested handles */
> > > > > > > > >  #define GPIOHANDLES_MAX 64
> > > > > > > > >
> > > > > > > > > +/**
> > > > > > > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > > > > > > + * request
> > > > > > > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > > > > > > + * associated GPIO device
> > > > > > > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > > > > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > > > > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > > > > > > + */
> > > > > > > > > +struct gpioline_changed_fd_request {
> > > > > > > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > > > > > > +     __u32 num_lines;
> > > > > > > > > +     int fd;
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > >
> > > > > > > > Wouldn't the most common case be to watch all the lines on a chip?
> > > > > > > > How about an easy way to do that, say num_lines=0?
> > > > > > > >
> > > > > > >
> > > > > > > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > > > > > > keep it this way for the same reason I didn't want to have implicit
> > > > > > > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > > > > > > wrap it in simple helpers in the library.
> > > > > > >
> > > > > >
> > > > > > Or you could add a watch_all flag to make it explicit.
> > > > > > But fair enough to leave as is - it probably keeps the kernel cleaner.
> > > > > >
> > > > > > > > > +/* Possible line status change events */
> > > > > > > > > +enum {
> > > > > > > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > > > > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > > > > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > > > +/**
> > > > > > > > > + * struct gpioline_changed - Information about a change in status
> > > > > > > > > + * of a GPIO line
> > > > > > > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > > > > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > > > > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > > > > > > + */
> > > > > > > > > +struct gpioline_changed {
> > > > > > > > > +     __u32 line_offset;
> > > > > > > > > +     __u32 event_type;
> > > > > > > > > +};
> > > > > > > > > +
> > > > > > > >
> > > > > > > > Rather than sending an event type, and requiring userspace to poll
> > > > > > > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > > > > > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > > > > > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > > > > > > event_type is then redundant.
> > > > > > > > Userspace would then only have to poll LINEINFO if they were interested
> > > > > > > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > > > > > > >
> > > > > > > > To sync kernel and userspace state the current state of each line
> > > > > > > > should be returned immediately via the fd as soon as the fd is created,
> > > > > > > > and then subsequently on any change.
> > > > > > > >
> > > > > > >
> > > > > > > I guess you're right. You even made me think we could go as far as to
> > > > > > > embed the whole gpioline_info structure in struct gpioline_changed.
> > > > > > > I'd still keep the event type though - otherwise we'd have to assume
> > > > > > > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > > > > > > fd.
> > > > > > >
> > > > > >
> > > > > > I suggested sending the current state of each line as soon as the fd
> > > > > > is created so that you don't have to assume anything.  That initial change
> > > > > > report is effectively the same as having the user call LINEINFO on each
> > > > > > line at the same time - without all the race issues.
> > > > > >
> > > > >
> > > > > Right, I didn't get this. That means you'd have to call the ioctl()
> > > > > and immediately follow up with a read(). I'm not a fan of this.
> > > > > Especially if we were to include a timestamp - what would it refer to?
> > > > >
> > > > > > I also considered the full gpioline_info structure, as well as reporting
> > > > > > bulk line changes (replacing line_offset with lineoffsets and num_lines),
> > > > > > but then I thought it was probably better to keep it minimal and simple.
> > > > > > The name and consumer are relatively large.
> > > > >
> > > > > In terms of performance - we're not really degrading it as this is
> > > > > still not a lot of data.
> > > > >
> > > > > How about the following:
> > > > > 1. Move the code filling out the struct gpioline_info to a separate
> > > > > function in the kernel.
> > > > > 2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
> > > > > of GET_LINEINFO.
> > > > > 3. Embed struct gpioline_info in struct gpioline_info_fd so that the
> > > > > initial info be read when the file descriptor is created.
> > > > > 4. Likewise embed struct gpioline_info in struct gpioline_changed and
> > > > > on every status change event provide the whole set of information?
> > > > >
> > > >
> > > > You mean an array of info in the ioctl return, along with the fd?
> > > > That would work too.
> > >
> > > I overlooked the fact that we can watch multiple lines. In this case
> > > it's either a separate fd for every watched line (not optimal) or
> > > doing what you proposed: read the initial lineinfo directly from the
> > > fd. But in this case again: what about the timestamp?
> > >
> >
> > It is set to the time the fd was created.
> > If you want you could also return that timestamp via the ioctl return
> > for comparison.
> > But most likely the userspace code will just ignore the timestamp for
> > those initial num_lines reports - those reports will get special
> > treatment anyway.
> >
> > Those initial reports are really part of the fd request handshake, so
> > I would expect the reading and handling of them to be part of the fd
> > request function in libgpiod, and they would be hidden from the end user.
> > The fd would only be returned to the user after the initial reports
> > have already been read and the line states synchronised.
> > OTOH I haven't put much thought into how the libgpiod API would look...
> 
> So I can imagine a situation where we'd run into problems with this:
> when we start watching the maximum number of lines - 64 - the kfifo
> would be filled with reports from the start and we'd risk missing some
> later ones if we don't make this kfifo much larger and if the user
> doesn't manage to consume them all fast.
> 
> How about reusing the already existing file descriptor associated with
> the chip itself? We currently only implement the ioctl() operation on
> it - the poll() and read() callbacks are empty.
> 
> We'd need to add two new ioctls(): GPIOLINE_WATCH_IOCTL and
> GPIOLINE_UNWATCH_IOCTL. The structures for both would look like this:
> 
> struct gpioline_watch_request {
>     __u32 lineoffset
>     struct gpioline_info info;
> };
> 
> struct gpioline_unwatch_request {
>     __u32 lineoffset;
> };
> 
> When GPIOLINE_WATCH_IOCTL is called, we'd setup a watch for given
> line: the embedded gpioline_info structure would be filled with
> initial info and we can now poll the gpiochip descriptor for events
> and read them. The event structure would looks like this:
> 
> struct gpioline_changed {
>     __u32 event_type;
>     __u64 timestamp;
>     struct gpioline_info info;
> };
> 
> Calling GPIOLINE_UNWATCH_IOCTL would of course make the kernel stop
> emitting events for given line.
> 
> Does it make sense?
> 

That makes sense.  But it doesn't really address the underlying problem
that you have identified - it just makes it less likely that you will
fill the kfifo.

Correct me if I'm wrong, but a pathological process or processes could
still swamp your kfifo with events, particularly if they are operating
on bulks.

I'd be happier with a solution that addresses what happens when the
kfifo is full, or even better prevents it from filling, and then see
how that feeds back to the startup case.

Time to revisit your requirements...  Userspace doesn't need the details
of each change, all you are after is an asynchronous mechanism to
bring userspace up to date.  You only need to inform userspace that the
line has changed since it was last informed of a change (or the fd was
created).  You can collapse a set of changes that haven't reached
userspace down to the last one.  Then the outstanding messages for
userspace is at worst the number of lines being monitored.

Wrt how to implement that, how about this:
When a line is changed you set a "needs_update" flag on that line, and
if not already set then issue a change to userspace via the fd.  That only
contains the line offset - no info.
The userspace would then perform a LINEINFO ioctl to read the line state
and clear the "needs_update" flag in kernel.
At that point the kernel and userspace are back in sync for that line -
until the next change.
The LINEINFO ioctl would be on the monitor fd, not the chip fd, but
could otherwise be the same as the existing ioctl from the userspace pov.
This logic would apply for each monitoring fd which is monitoring that
line.

Wrt the startup case, the userspace would create the monitor fd and then
call the LINEINFO ioctl on each monitored line.
Then you are in sync and good to go...

There may well be better ways to implement that handshake - that is just
the first that comes to mind.

Kent.

> Bart
> 
> >
> > Kent.
> >
> > > Bart
> > >
> > > >
> > > > I'm used to setting up streams like this in a networked environment
> > > > where returning data via the stream setup isn't an option, the only
> > > > option is to sync via the stream itself, so I overlooked the possibility
> > > > of using the ioctl return.
> > > >
> > > > > > The name is immutable(??), and so is pointless to include.
> > > > >
> > > > > It is now, but let's be future-proof. I can imagine having modifiable
> > > > > line names in the future. The code putting this info in struct
> > > > > gpioline_info wouldn't be really duplicated and the size of such small
> > > > > structures doesn't matter much - it's still a single context switch to
> > > > > read it.
> > > > >
> > > > > > The consumer only changes when the line is requested, so I was willing
> > > > > > to live with still having to poll for that.
> > > > > > And what you gain by reporting bulk lines you might lose in increased
> > > > > > report size and associated code.  OTOH it would make it explicit which
> > > > > > lines are being changed together...
> > > > > > So I could well be wrong on that - a full bulk report may be better.
> > > > >
> > > > > I'm not sure we need bulk reporting - just as we don't provide bulk
> > > > > GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> > > > > pretty complicated soon.
> > > > >
> > > >
> > > > That was my initial feeling as well.
> > > > And bulk is no longer an option if you want to include name in the change
> > > > report.
> > > >
> > > > Kent.
> > > >
> > > > > Bartosz
> > > > >
> > > > > >
> > > > > > Kent.
> > > > > >
> > > > > > > > And a timestamp might be useful, as per gpioevent_data?
> > > > > > >
> > > > > > > Sure thing!
> > > > > > >
> > > > > > > Bart
> > > > > > >
> > > > > > > >
> > > > > > > > Kent.
> > > > > > > >
> > > > > > > > >  /* Linerequest flags */
> > > > > > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > > > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > > > > > >
> > > > > > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > > > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > > > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > > > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > > > > > >
> > > > > > > > > --
> > > > > > > > > 2.23.0
> > > > > > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-11-29 13:49                   ` Kent Gibson
@ 2019-12-01 15:25                     ` Bartosz Golaszewski
  2019-12-01 23:43                       ` Kent Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-12-01 15:25 UTC (permalink / raw)
  To: Kent Gibson
  Cc: Bartosz Golaszewski, Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

pt., 29 lis 2019 o 14:49 Kent Gibson <warthog618@gmail.com> napisał(a):
>
> On Fri, Nov 29, 2019 at 10:43:32AM +0100, Bartosz Golaszewski wrote:
> > czw., 28 lis 2019 o 16:02 Kent Gibson <warthog618@gmail.com> napisał(a):
> > >
> > > On Thu, Nov 28, 2019 at 03:36:19PM +0100, Bartosz Golaszewski wrote:
> > > > czw., 28 lis 2019 o 15:10 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > >
> > > > > On Thu, Nov 28, 2019 at 10:45:46AM +0100, Bartosz Golaszewski wrote:
> > > > > > czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > > >
> > > > > > > On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > > > > > > > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > > > > >
> > > > > > > > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > > > > > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > > > >
> > > > > > > > > > Currently there is no way for user-space to be informed about changes
> > > > > > > > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > > > > > > > config changes. We can only periodically re-read the line-info. This
> > > > > > > > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > > > > > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > > > > > > > driven line info synchronization.
> > > > > > > > > >
> > > > > > > > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > > > > > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > > > > > > > change events.
> > > > > > > > > >
> > > > > > > > > > Currently the events are generated on three types of status changes: when
> > > > > > > > > > a line is requested, when it's released and when its config is changed.
> > > > > > > > > > The first two are self-explanatory. For the third one: this will only
> > > > > > > > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > > > > > > > as any changes that can happen from within the kernel (i.e.
> > > > > > > > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > > > > > > > >
> > > > > > > > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > > > > ---
> > > > > > > > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > > > > > > > >  drivers/gpio/gpiolib.h    |   1 +
> > > > > > > > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > > > > > > > >  3 files changed, 255 insertions(+)
> > > > > > > > > >
> > > > > > > > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > > > > > > > index d094b1be334d..be5df4bdf44b 100644
> > > > > > > > > > --- a/drivers/gpio/gpiolib.c
> > > > > > > > > > +++ b/drivers/gpio/gpiolib.c
> > > > > > > > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > > > > > > > >                       if (ret)
> > > > > > > > > >                               return ret;
> > > > > > > > > >               }
> > > > > > > > > > +
> > > > > > > > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > > > > > > > >       }
> > > > > > > > > >       return 0;
> > > > > > > > > >  }
> > > > > > > > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > > > >       return ret;
> > > > > > > > > >  }
> > > > > > > > > >
> > > > > > > > > > +struct linechanged_fd_state {
> > > > > > > > > > +     struct gpio_device *gdev;
> > > > > > > > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > > > > > > > +     size_t numdescs;
> > > > > > > > > > +     wait_queue_head_t waitqueue;
> > > > > > > > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > > > > > > > +     struct mutex lock;
> > > > > > > > > > +     struct notifier_block changed_nb;
> > > > > > > > > > +};
> > > > > > > > > > +
> > > > > > > > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > > > > > > > +{
> > > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > > +
> > > > > > > > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > > > +     put_device(&lc_state->gdev->dev);
> > > > > > > > > > +     kfree(lc_state);
> > > > > > > > > > +
> > > > > > > > > > +     return 0;
> > > > > > > > > > +}
> > > > > > > > > > +
> > > > > > > > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > > > > > > > +                                 struct poll_table_struct *pollt)
> > > > > > > > > > +{
> > > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > > +     __poll_t events = 0;
> > > > > > > > > > +
> > > > > > > > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > > > > > > > +
> > > > > > > > > > +     mutex_lock(&lc_state->lock);
> > > > > > > > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > > > > > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > > > > > > > +     mutex_unlock(&lc_state->lock);
> > > > > > > > > > +
> > > > > > > > > > +     return events;
> > > > > > > > > > +}
> > > > > > > > > > +
> > > > > > > > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > > > > > > > +                                size_t count, loff_t *off)
> > > > > > > > > > +{
> > > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > > +     unsigned int copied;
> > > > > > > > > > +     int ret;
> > > > > > > > > > +
> > > > > > > > > > +     if (count < sizeof(struct gpioline_changed))
> > > > > > > > > > +             return -EINVAL;
> > > > > > > > > > +
> > > > > > > > > > +     do {
> > > > > > > > > > +             mutex_lock(&lc_state->lock);
> > > > > > > > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > > > > > > > +                             return -EAGAIN;
> > > > > > > > > > +
> > > > > > > > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > > > > > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > > > > > > > +                     if (ret)
> > > > > > > > > > +                             return ret;
> > > > > > > > > > +             } else {
> > > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > > +             }
> > > > > > > > > > +
> > > > > > > > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > > > > > > > +                     return -ERESTARTSYS;
> > > > > > > > > > +
> > > > > > > > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > > > > > > > +             mutex_unlock(&lc_state->lock);
> > > > > > > > > > +             if (ret)
> > > > > > > > > > +                     return ret;
> > > > > > > > > > +
> > > > > > > > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > > > > > > > +                     return -EAGAIN;
> > > > > > > > > > +     } while (copied == 0);
> > > > > > > > > > +
> > > > > > > > > > +     return copied;
> > > > > > > > > > +}
> > > > > > > > > > +
> > > > > > > > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > > > > > > > +     .release = linechanged_fd_release,
> > > > > > > > > > +     .owner = THIS_MODULE,
> > > > > > > > > > +     .llseek = noop_llseek,
> > > > > > > > > > +     .poll = linechanged_fd_poll,
> > > > > > > > > > +     .read = linechanged_fd_read,
> > > > > > > > > > +};
> > > > > > > > > > +
> > > > > > > > > > +static struct linechanged_fd_state *
> > > > > > > > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > > > > > > > +{
> > > > > > > > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > > > > > > > +}
> > > > > > > > > > +
> > > > > > > > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > > > > > > > +                              unsigned long action, void *data)
> > > > > > > > > > +{
> > > > > > > > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > > > > > > > +     struct gpio_desc *desc = data;
> > > > > > > > > > +     struct gpioline_changed chg;
> > > > > > > > > > +     int i, ret;
> > > > > > > > > > +
> > > > > > > > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > > > > > > > +             /* Are we watching this desc? */
> > > > > > > > > > +             if (desc == lc_state->descs[i]) {
> > > > > > > > > > +                     /* Yes - prepare the event. */
> > > > > > > > > > +                     memset(&chg, 0, sizeof(chg));
> > > > > > > > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > > > > > > > +                     chg.event_type = action;
> > > > > > > > > > +
> > > > > > > > > > +                     mutex_lock(&lc_state->lock);
> > > > > > > > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > > +                     if (ret)
> > > > > > > > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > > > > > > > +
> > > > > > > > > > +                     return NOTIFY_OK;
> > > > > > > > > > +             }
> > > > > > > > > > +     }
> > > > > > > > > > +
> > > > > > > > > > +     return NOTIFY_DONE;
> > > > > > > > > > +}
> > > > > > > > > > +
> > > > > > > > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > > > > +{
> > > > > > > > > > +     struct gpioline_changed_fd_request changed_req;
> > > > > > > > > > +     struct linechanged_fd_state *lc_state;
> > > > > > > > > > +     struct gpio_desc *desc;
> > > > > > > > > > +     struct file *file;
> > > > > > > > > > +     int ret, i, fd;
> > > > > > > > > > +     u32 offset;
> > > > > > > > > > +
> > > > > > > > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > > > > > > > +     if (ret)
> > > > > > > > > > +             return -EFAULT;
> > > > > > > > > > +
> > > > > > > > > > +     if ((changed_req.num_lines == 0) ||
> > > > > > > > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > > > > > > > +             return -EINVAL;
> > > > > > > > > > +
> > > > > > > > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > > > > > > > +     if (!lc_state)
> > > > > > > > > > +             return -ENOMEM;
> > > > > > > > > > +
> > > > > > > > > > +     lc_state->gdev = gdev;
> > > > > > > > > > +     get_device(&gdev->dev);
> > > > > > > > > > +
> > > > > > > > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > > > > > > > +             offset = changed_req.lineoffsets[i];
> > > > > > > > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > > > > > > > +             if (IS_ERR(desc)) {
> > > > > > > > > > +                     ret = PTR_ERR(desc);
> > > > > > > > > > +                     goto out_free_lc_state;
> > > > > > > > > > +             }
> > > > > > > > > > +
> > > > > > > > > > +             lc_state->descs[i] = desc;
> > > > > > > > > > +     }
> > > > > > > > > > +
> > > > > > > > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > > > > > > > +
> > > > > > > > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > > > > > > > +     INIT_KFIFO(lc_state->events);
> > > > > > > > > > +     mutex_init(&lc_state->lock);
> > > > > > > > > > +
> > > > > > > > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > > > > > > > +
> > > > > > > > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > > > > > > > +                                          &lc_state->changed_nb);
> > > > > > > > > > +     if (ret)
> > > > > > > > > > +             goto out_free_lc_state;
> > > > > > > > > > +
> > > > > > > > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > > > > > > > +     if (fd < 0) {
> > > > > > > > > > +             ret = fd;
> > > > > > > > > > +             goto out_unregister_notifier;
> > > > > > > > > > +     }
> > > > > > > > > > +
> > > > > > > > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > > > > > > > +                               &linechanged_fd_fileops,
> > > > > > > > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > > > > > > > +     if (IS_ERR(file)) {
> > > > > > > > > > +             ret = PTR_ERR(file);
> > > > > > > > > > +             goto out_put_unused_fd;
> > > > > > > > > > +     }
> > > > > > > > > > +
> > > > > > > > > > +     changed_req.fd = fd;
> > > > > > > > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > > > > > > > +     if (ret) {
> > > > > > > > > > +             fput(file);
> > > > > > > > > > +             put_unused_fd(fd);
> > > > > > > > > > +             return -EFAULT;
> > > > > > > > > > +     }
> > > > > > > > > > +
> > > > > > > > > > +     fd_install(fd, file);
> > > > > > > > > > +
> > > > > > > > > > +     return 0;
> > > > > > > > > > +
> > > > > > > > > > +out_put_unused_fd:
> > > > > > > > > > +     put_unused_fd(fd);
> > > > > > > > > > +out_unregister_notifier:
> > > > > > > > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > > > +out_free_lc_state:
> > > > > > > > > > +     kfree(lc_state);
> > > > > > > > > > +
> > > > > > > > > > +     return ret;
> > > > > > > > > > +}
> > > > > > > > > > +
> > > > > > > > > >  /*
> > > > > > > > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > > > > > > > >   */
> > > > > > > > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > > > > > > >               return linehandle_create(gdev, ip);
> > > > > > > > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > > > > > > > >               return lineevent_create(gdev, ip);
> > > > > > > > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > > > > > > > +             return linechanged_fd_create(gdev, ip);
> > > > > > > > > >       }
> > > > > > > > > >       return -EINVAL;
> > > > > > > > > >  }
> > > > > > > > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > > > > > > > >       for (i = 0; i < chip->ngpio; i++)
> > > > > > > > > >               gdev->descs[i].gdev = gdev;
> > > > > > > > > >
> > > > > > > > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > > > > > > > +
> > > > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > > > > > > > >  #endif
> > > > > > > > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > > > > > > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > > > > > > > >       }
> > > > > > > > > >  done:
> > > > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > > > >       return ret;
> > > > > > > > > >  }
> > > > > > > > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > > > > > > > >               ret = true;
> > > > > > > > > >       }
> > > > > > > > > >
> > > > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > > > >       return ret;
> > > > > > > > > >  }
> > > > > > > > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > > > > > > > >               if (ret != -ENOTSUPP)
> > > > > > > > > >                       return ret;
> > > > > > > > > >       }
> > > > > > > > > > +
> > > > > > > > > >       return 0;
> > > > > > > > > >  }
> > > > > > > > > >
> > > > > > > > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > > > > > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > > > > > > > --- a/drivers/gpio/gpiolib.h
> > > > > > > > > > +++ b/drivers/gpio/gpiolib.h
> > > > > > > > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > > > > > > > >       const char              *label;
> > > > > > > > > >       void                    *data;
> > > > > > > > > >       struct list_head        list;
> > > > > > > > > > +     struct atomic_notifier_head notifier;
> > > > > > > > > >
> > > > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > > > >       /*
> > > > > > > > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > > > > > > > index 799cf823d493..c61429467dd4 100644
> > > > > > > > > > --- a/include/uapi/linux/gpio.h
> > > > > > > > > > +++ b/include/uapi/linux/gpio.h
> > > > > > > > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > > > > > > > >  /* Maximum number of requested handles */
> > > > > > > > > >  #define GPIOHANDLES_MAX 64
> > > > > > > > > >
> > > > > > > > > > +/**
> > > > > > > > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > > > > > > > + * request
> > > > > > > > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > > > > > > > + * associated GPIO device
> > > > > > > > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > > > > > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > > > > > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > > > > > > > + */
> > > > > > > > > > +struct gpioline_changed_fd_request {
> > > > > > > > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > > > > > > > +     __u32 num_lines;
> > > > > > > > > > +     int fd;
> > > > > > > > > > +};
> > > > > > > > > > +
> > > > > > > > >
> > > > > > > > > Wouldn't the most common case be to watch all the lines on a chip?
> > > > > > > > > How about an easy way to do that, say num_lines=0?
> > > > > > > > >
> > > > > > > >
> > > > > > > > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > > > > > > > keep it this way for the same reason I didn't want to have implicit
> > > > > > > > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > > > > > > > wrap it in simple helpers in the library.
> > > > > > > >
> > > > > > >
> > > > > > > Or you could add a watch_all flag to make it explicit.
> > > > > > > But fair enough to leave as is - it probably keeps the kernel cleaner.
> > > > > > >
> > > > > > > > > > +/* Possible line status change events */
> > > > > > > > > > +enum {
> > > > > > > > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > > > > > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > > > > > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > > > > > > > +};
> > > > > > > > > > +
> > > > > > > > > > +/**
> > > > > > > > > > + * struct gpioline_changed - Information about a change in status
> > > > > > > > > > + * of a GPIO line
> > > > > > > > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > > > > > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > > > > > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > > > > > > > + */
> > > > > > > > > > +struct gpioline_changed {
> > > > > > > > > > +     __u32 line_offset;
> > > > > > > > > > +     __u32 event_type;
> > > > > > > > > > +};
> > > > > > > > > > +
> > > > > > > > >
> > > > > > > > > Rather than sending an event type, and requiring userspace to poll
> > > > > > > > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > > > > > > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > > > > > > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > > > > > > > event_type is then redundant.
> > > > > > > > > Userspace would then only have to poll LINEINFO if they were interested
> > > > > > > > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > > > > > > > >
> > > > > > > > > To sync kernel and userspace state the current state of each line
> > > > > > > > > should be returned immediately via the fd as soon as the fd is created,
> > > > > > > > > and then subsequently on any change.
> > > > > > > > >
> > > > > > > >
> > > > > > > > I guess you're right. You even made me think we could go as far as to
> > > > > > > > embed the whole gpioline_info structure in struct gpioline_changed.
> > > > > > > > I'd still keep the event type though - otherwise we'd have to assume
> > > > > > > > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > > > > > > > fd.
> > > > > > > >
> > > > > > >
> > > > > > > I suggested sending the current state of each line as soon as the fd
> > > > > > > is created so that you don't have to assume anything.  That initial change
> > > > > > > report is effectively the same as having the user call LINEINFO on each
> > > > > > > line at the same time - without all the race issues.
> > > > > > >
> > > > > >
> > > > > > Right, I didn't get this. That means you'd have to call the ioctl()
> > > > > > and immediately follow up with a read(). I'm not a fan of this.
> > > > > > Especially if we were to include a timestamp - what would it refer to?
> > > > > >
> > > > > > > I also considered the full gpioline_info structure, as well as reporting
> > > > > > > bulk line changes (replacing line_offset with lineoffsets and num_lines),
> > > > > > > but then I thought it was probably better to keep it minimal and simple.
> > > > > > > The name and consumer are relatively large.
> > > > > >
> > > > > > In terms of performance - we're not really degrading it as this is
> > > > > > still not a lot of data.
> > > > > >
> > > > > > How about the following:
> > > > > > 1. Move the code filling out the struct gpioline_info to a separate
> > > > > > function in the kernel.
> > > > > > 2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
> > > > > > of GET_LINEINFO.
> > > > > > 3. Embed struct gpioline_info in struct gpioline_info_fd so that the
> > > > > > initial info be read when the file descriptor is created.
> > > > > > 4. Likewise embed struct gpioline_info in struct gpioline_changed and
> > > > > > on every status change event provide the whole set of information?
> > > > > >
> > > > >
> > > > > You mean an array of info in the ioctl return, along with the fd?
> > > > > That would work too.
> > > >
> > > > I overlooked the fact that we can watch multiple lines. In this case
> > > > it's either a separate fd for every watched line (not optimal) or
> > > > doing what you proposed: read the initial lineinfo directly from the
> > > > fd. But in this case again: what about the timestamp?
> > > >
> > >
> > > It is set to the time the fd was created.
> > > If you want you could also return that timestamp via the ioctl return
> > > for comparison.
> > > But most likely the userspace code will just ignore the timestamp for
> > > those initial num_lines reports - those reports will get special
> > > treatment anyway.
> > >
> > > Those initial reports are really part of the fd request handshake, so
> > > I would expect the reading and handling of them to be part of the fd
> > > request function in libgpiod, and they would be hidden from the end user.
> > > The fd would only be returned to the user after the initial reports
> > > have already been read and the line states synchronised.
> > > OTOH I haven't put much thought into how the libgpiod API would look...
> >
> > So I can imagine a situation where we'd run into problems with this:
> > when we start watching the maximum number of lines - 64 - the kfifo
> > would be filled with reports from the start and we'd risk missing some
> > later ones if we don't make this kfifo much larger and if the user
> > doesn't manage to consume them all fast.
> >
> > How about reusing the already existing file descriptor associated with
> > the chip itself? We currently only implement the ioctl() operation on
> > it - the poll() and read() callbacks are empty.
> >
> > We'd need to add two new ioctls(): GPIOLINE_WATCH_IOCTL and
> > GPIOLINE_UNWATCH_IOCTL. The structures for both would look like this:
> >
> > struct gpioline_watch_request {
> >     __u32 lineoffset
> >     struct gpioline_info info;
> > };
> >
> > struct gpioline_unwatch_request {
> >     __u32 lineoffset;
> > };
> >
> > When GPIOLINE_WATCH_IOCTL is called, we'd setup a watch for given
> > line: the embedded gpioline_info structure would be filled with
> > initial info and we can now poll the gpiochip descriptor for events
> > and read them. The event structure would looks like this:
> >
> > struct gpioline_changed {
> >     __u32 event_type;
> >     __u64 timestamp;
> >     struct gpioline_info info;
> > };
> >
> > Calling GPIOLINE_UNWATCH_IOCTL would of course make the kernel stop
> > emitting events for given line.
> >
> > Does it make sense?
> >
>
> That makes sense.  But it doesn't really address the underlying problem
> that you have identified - it just makes it less likely that you will
> fill the kfifo.
>
> Correct me if I'm wrong, but a pathological process or processes could
> still swamp your kfifo with events, particularly if they are operating
> on bulks.

Don't get me wrong - the assumption is that a process knows what it's
doing. We expect that if it watches lines for events, then it will
actually read them as soon as they arrive on the other end of the
FIFO. If not - then this won't affect others, it will fill up the FIFO
associated with this process' file descriptor and we'll just drop new
events until it consumes old ones. In other words: I'm not worried
about pathological processes.

The problem here is that the file descriptor is created and there are
already several (up to 64) events waiting to be read. This just
doesn't feel right. If the process doesn't manage to consume all
initial events in time, we'll drop new ones. The alternative is to
allocate a larger FIFO but I just have a feeling that this whole
approach is wrong. I'm not sure any other subsystem does something
like this.

>
> I'd be happier with a solution that addresses what happens when the
> kfifo is full, or even better prevents it from filling, and then see
> how that feeds back to the startup case.
>

You can't really prevent it from overflowing as you can't
update/modify elements in the middle.

> Time to revisit your requirements...  Userspace doesn't need the details
> of each change, all you are after is an asynchronous mechanism to
> bring userspace up to date.  You only need to inform userspace that the
> line has changed since it was last informed of a change (or the fd was
> created).  You can collapse a set of changes that haven't reached
> userspace down to the last one.  Then the outstanding messages for
> userspace is at worst the number of lines being monitored.
>
> Wrt how to implement that, how about this:
> When a line is changed you set a "needs_update" flag on that line, and
> if not already set then issue a change to userspace via the fd.  That only
> contains the line offset - no info.

Doesn't this bring us right back to my racy initial implementation?

> The userspace would then perform a LINEINFO ioctl to read the line state
> and clear the "needs_update" flag in kernel.
> At that point the kernel and userspace are back in sync for that line -
> until the next change.
> The LINEINFO ioctl would be on the monitor fd, not the chip fd, but
> could otherwise be the same as the existing ioctl from the userspace pov.
> This logic would apply for each monitoring fd which is monitoring that
> line.

But then the kernel would assume that the user-space does a
responsible thing with the event it reads, while the process may as
well just discard all arriving data.

Bart

>
> Wrt the startup case, the userspace would create the monitor fd and then
> call the LINEINFO ioctl on each monitored line.
> Then you are in sync and good to go...
>
> There may well be better ways to implement that handshake - that is just
> the first that comes to mind.
>
> Kent.
>
> > Bart
> >
> > >
> > > Kent.
> > >
> > > > Bart
> > > >
> > > > >
> > > > > I'm used to setting up streams like this in a networked environment
> > > > > where returning data via the stream setup isn't an option, the only
> > > > > option is to sync via the stream itself, so I overlooked the possibility
> > > > > of using the ioctl return.
> > > > >
> > > > > > > The name is immutable(??), and so is pointless to include.
> > > > > >
> > > > > > It is now, but let's be future-proof. I can imagine having modifiable
> > > > > > line names in the future. The code putting this info in struct
> > > > > > gpioline_info wouldn't be really duplicated and the size of such small
> > > > > > structures doesn't matter much - it's still a single context switch to
> > > > > > read it.
> > > > > >
> > > > > > > The consumer only changes when the line is requested, so I was willing
> > > > > > > to live with still having to poll for that.
> > > > > > > And what you gain by reporting bulk lines you might lose in increased
> > > > > > > report size and associated code.  OTOH it would make it explicit which
> > > > > > > lines are being changed together...
> > > > > > > So I could well be wrong on that - a full bulk report may be better.
> > > > > >
> > > > > > I'm not sure we need bulk reporting - just as we don't provide bulk
> > > > > > GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> > > > > > pretty complicated soon.
> > > > > >
> > > > >
> > > > > That was my initial feeling as well.
> > > > > And bulk is no longer an option if you want to include name in the change
> > > > > report.
> > > > >
> > > > > Kent.
> > > > >
> > > > > > Bartosz
> > > > > >
> > > > > > >
> > > > > > > Kent.
> > > > > > >
> > > > > > > > > And a timestamp might be useful, as per gpioevent_data?
> > > > > > > >
> > > > > > > > Sure thing!
> > > > > > > >
> > > > > > > > Bart
> > > > > > > >
> > > > > > > > >
> > > > > > > > > Kent.
> > > > > > > > >
> > > > > > > > > >  /* Linerequest flags */
> > > > > > > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > > > > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > > > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > > > > > > >
> > > > > > > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > > > > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > > > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > > > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > > > > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > > > > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > > > > > > >
> > > > > > > > > > --
> > > > > > > > > > 2.23.0
> > > > > > > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-12-01 15:25                     ` Bartosz Golaszewski
@ 2019-12-01 23:43                       ` Kent Gibson
  2019-12-02 17:11                         ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Kent Gibson @ 2019-12-01 23:43 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Bartosz Golaszewski, Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Sun, Dec 01, 2019 at 04:25:08PM +0100, Bartosz Golaszewski wrote:
> pt., 29 lis 2019 o 14:49 Kent Gibson <warthog618@gmail.com> napisał(a):
> >
> > On Fri, Nov 29, 2019 at 10:43:32AM +0100, Bartosz Golaszewski wrote:
> > > czw., 28 lis 2019 o 16:02 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > >
> > > > On Thu, Nov 28, 2019 at 03:36:19PM +0100, Bartosz Golaszewski wrote:
> > > > > czw., 28 lis 2019 o 15:10 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > >
> > > > > > On Thu, Nov 28, 2019 at 10:45:46AM +0100, Bartosz Golaszewski wrote:
> > > > > > > czw., 28 lis 2019 o 00:23 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > > > >
> > > > > > > > On Wed, Nov 27, 2019 at 04:50:43PM +0100, Bartosz Golaszewski wrote:
> > > > > > > > > śr., 27 lis 2019 o 16:24 Kent Gibson <warthog618@gmail.com> napisał(a):
> > > > > > > > > >
> > > > > > > > > > On Wed, Nov 27, 2019 at 02:35:09PM +0100, Bartosz Golaszewski wrote:
> > > > > > > > > > > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > > > > >
> > > > > > > > > > > Currently there is no way for user-space to be informed about changes
> > > > > > > > > > > in status of GPIO lines e.g. when someone else requests the line or its
> > > > > > > > > > > config changes. We can only periodically re-read the line-info. This
> > > > > > > > > > > is fine for simple one-off user-space tools, but any daemon that provides
> > > > > > > > > > > a centralized access to GPIO chips would benefit hugely from an event
> > > > > > > > > > > driven line info synchronization.
> > > > > > > > > > >
> > > > > > > > > > > This patch adds a new ioctl() that allows user-space processes to retrieve
> > > > > > > > > > > a file-descriptor for given GPIO lines which can be polled for line status
> > > > > > > > > > > change events.
> > > > > > > > > > >
> > > > > > > > > > > Currently the events are generated on three types of status changes: when
> > > > > > > > > > > a line is requested, when it's released and when its config is changed.
> > > > > > > > > > > The first two are self-explanatory. For the third one: this will only
> > > > > > > > > > > happen when another user-space process calls the new SET_CONFIG ioctl()
> > > > > > > > > > > as any changes that can happen from within the kernel (i.e.
> > > > > > > > > > > set_transitory() or set_debounce()) are of no interest to user-space.
> > > > > > > > > > >
> > > > > > > > > > > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > > > > > > > > > > ---
> > > > > > > > > > >  drivers/gpio/gpiolib.c    | 218 ++++++++++++++++++++++++++++++++++++++
> > > > > > > > > > >  drivers/gpio/gpiolib.h    |   1 +
> > > > > > > > > > >  include/uapi/linux/gpio.h |  36 +++++++
> > > > > > > > > > >  3 files changed, 255 insertions(+)
> > > > > > > > > > >
> > > > > > > > > > > diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> > > > > > > > > > > index d094b1be334d..be5df4bdf44b 100644
> > > > > > > > > > > --- a/drivers/gpio/gpiolib.c
> > > > > > > > > > > +++ b/drivers/gpio/gpiolib.c
> > > > > > > > > > > @@ -547,6 +547,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
> > > > > > > > > > >                       if (ret)
> > > > > > > > > > >                               return ret;
> > > > > > > > > > >               }
> > > > > > > > > > > +
> > > > > > > > > > > +             atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > > > +                                        GPIOLINE_CHANGED_CONFIG, desc);
> > > > > > > > > > >       }
> > > > > > > > > > >       return 0;
> > > > > > > > > > >  }
> > > > > > > > > > > @@ -1148,6 +1151,212 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > > > > >       return ret;
> > > > > > > > > > >  }
> > > > > > > > > > >
> > > > > > > > > > > +struct linechanged_fd_state {
> > > > > > > > > > > +     struct gpio_device *gdev;
> > > > > > > > > > > +     struct gpio_desc *descs[GPIOHANDLES_MAX];
> > > > > > > > > > > +     size_t numdescs;
> > > > > > > > > > > +     wait_queue_head_t waitqueue;
> > > > > > > > > > > +     DECLARE_KFIFO(events, struct gpioline_changed, 16);
> > > > > > > > > > > +     struct mutex lock;
> > > > > > > > > > > +     struct notifier_block changed_nb;
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +static int linechanged_fd_release(struct inode *inode, struct file *filep)
> > > > > > > > > > > +{
> > > > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > > > +
> > > > > > > > > > > +     atomic_notifier_chain_unregister(&lc_state->gdev->notifier,
> > > > > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > > > > +     put_device(&lc_state->gdev->dev);
> > > > > > > > > > > +     kfree(lc_state);
> > > > > > > > > > > +
> > > > > > > > > > > +     return 0;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static __poll_t linechanged_fd_poll(struct file *filep,
> > > > > > > > > > > +                                 struct poll_table_struct *pollt)
> > > > > > > > > > > +{
> > > > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > > > +     __poll_t events = 0;
> > > > > > > > > > > +
> > > > > > > > > > > +     poll_wait(filep, &lc_state->waitqueue, pollt);
> > > > > > > > > > > +
> > > > > > > > > > > +     mutex_lock(&lc_state->lock);
> > > > > > > > > > > +     if (!kfifo_is_empty(&lc_state->events))
> > > > > > > > > > > +             events = EPOLLIN | EPOLLRDNORM;
> > > > > > > > > > > +     mutex_unlock(&lc_state->lock);
> > > > > > > > > > > +
> > > > > > > > > > > +     return events;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static ssize_t linechanged_fd_read(struct file *filep, char __user *buf,
> > > > > > > > > > > +                                size_t count, loff_t *off)
> > > > > > > > > > > +{
> > > > > > > > > > > +     struct linechanged_fd_state *lc_state = filep->private_data;
> > > > > > > > > > > +     unsigned int copied;
> > > > > > > > > > > +     int ret;
> > > > > > > > > > > +
> > > > > > > > > > > +     if (count < sizeof(struct gpioline_changed))
> > > > > > > > > > > +             return -EINVAL;
> > > > > > > > > > > +
> > > > > > > > > > > +     do {
> > > > > > > > > > > +             mutex_lock(&lc_state->lock);
> > > > > > > > > > > +             if (kfifo_is_empty(&lc_state->events)) {
> > > > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > > > +                     if (filep->f_flags & O_NONBLOCK)
> > > > > > > > > > > +                             return -EAGAIN;
> > > > > > > > > > > +
> > > > > > > > > > > +                     ret = wait_event_interruptible(lc_state->waitqueue,
> > > > > > > > > > > +                                     !kfifo_is_empty(&lc_state->events));
> > > > > > > > > > > +                     if (ret)
> > > > > > > > > > > +                             return ret;
> > > > > > > > > > > +             } else {
> > > > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > > > +             }
> > > > > > > > > > > +
> > > > > > > > > > > +             if (mutex_lock_interruptible(&lc_state->lock))
> > > > > > > > > > > +                     return -ERESTARTSYS;
> > > > > > > > > > > +
> > > > > > > > > > > +             ret = kfifo_to_user(&lc_state->events, buf, count, &copied);
> > > > > > > > > > > +             mutex_unlock(&lc_state->lock);
> > > > > > > > > > > +             if (ret)
> > > > > > > > > > > +                     return ret;
> > > > > > > > > > > +
> > > > > > > > > > > +             if (copied == 0 && (filep->f_flags & O_NONBLOCK))
> > > > > > > > > > > +                     return -EAGAIN;
> > > > > > > > > > > +     } while (copied == 0);
> > > > > > > > > > > +
> > > > > > > > > > > +     return copied;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static const struct file_operations linechanged_fd_fileops = {
> > > > > > > > > > > +     .release = linechanged_fd_release,
> > > > > > > > > > > +     .owner = THIS_MODULE,
> > > > > > > > > > > +     .llseek = noop_llseek,
> > > > > > > > > > > +     .poll = linechanged_fd_poll,
> > > > > > > > > > > +     .read = linechanged_fd_read,
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +static struct linechanged_fd_state *
> > > > > > > > > > > +to_linechanged_fd_state(struct notifier_block *nb)
> > > > > > > > > > > +{
> > > > > > > > > > > +     return container_of(nb, struct linechanged_fd_state, changed_nb);
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int linechanged_fd_notify(struct notifier_block *nb,
> > > > > > > > > > > +                              unsigned long action, void *data)
> > > > > > > > > > > +{
> > > > > > > > > > > +     struct linechanged_fd_state *lc_state = to_linechanged_fd_state(nb);
> > > > > > > > > > > +     struct gpio_desc *desc = data;
> > > > > > > > > > > +     struct gpioline_changed chg;
> > > > > > > > > > > +     int i, ret;
> > > > > > > > > > > +
> > > > > > > > > > > +     for (i = 0; i < lc_state->numdescs; i++) {
> > > > > > > > > > > +             /* Are we watching this desc? */
> > > > > > > > > > > +             if (desc == lc_state->descs[i]) {
> > > > > > > > > > > +                     /* Yes - prepare the event. */
> > > > > > > > > > > +                     memset(&chg, 0, sizeof(chg));
> > > > > > > > > > > +                     chg.line_offset = gpio_chip_hwgpio(desc);
> > > > > > > > > > > +                     chg.event_type = action;
> > > > > > > > > > > +
> > > > > > > > > > > +                     mutex_lock(&lc_state->lock);
> > > > > > > > > > > +                     ret = kfifo_put(&lc_state->events, chg);
> > > > > > > > > > > +                     mutex_unlock(&lc_state->lock);
> > > > > > > > > > > +                     if (ret)
> > > > > > > > > > > +                             wake_up_poll(&lc_state->waitqueue, EPOLLIN);
> > > > > > > > > > > +
> > > > > > > > > > > +                     return NOTIFY_OK;
> > > > > > > > > > > +             }
> > > > > > > > > > > +     }
> > > > > > > > > > > +
> > > > > > > > > > > +     return NOTIFY_DONE;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > > +static int linechanged_fd_create(struct gpio_device *gdev, void __user *ip)
> > > > > > > > > > > +{
> > > > > > > > > > > +     struct gpioline_changed_fd_request changed_req;
> > > > > > > > > > > +     struct linechanged_fd_state *lc_state;
> > > > > > > > > > > +     struct gpio_desc *desc;
> > > > > > > > > > > +     struct file *file;
> > > > > > > > > > > +     int ret, i, fd;
> > > > > > > > > > > +     u32 offset;
> > > > > > > > > > > +
> > > > > > > > > > > +     ret = copy_from_user(&changed_req, ip, sizeof(changed_req));
> > > > > > > > > > > +     if (ret)
> > > > > > > > > > > +             return -EFAULT;
> > > > > > > > > > > +
> > > > > > > > > > > +     if ((changed_req.num_lines == 0) ||
> > > > > > > > > > > +         (changed_req.num_lines > GPIOHANDLES_MAX))
> > > > > > > > > > > +             return -EINVAL;
> > > > > > > > > > > +
> > > > > > > > > > > +     lc_state = kzalloc(sizeof(*lc_state), GFP_KERNEL);
> > > > > > > > > > > +     if (!lc_state)
> > > > > > > > > > > +             return -ENOMEM;
> > > > > > > > > > > +
> > > > > > > > > > > +     lc_state->gdev = gdev;
> > > > > > > > > > > +     get_device(&gdev->dev);
> > > > > > > > > > > +
> > > > > > > > > > > +     for (i = 0; i < changed_req.num_lines; i++) {
> > > > > > > > > > > +             offset = changed_req.lineoffsets[i];
> > > > > > > > > > > +             desc = gpiochip_get_desc(gdev->chip, offset);
> > > > > > > > > > > +             if (IS_ERR(desc)) {
> > > > > > > > > > > +                     ret = PTR_ERR(desc);
> > > > > > > > > > > +                     goto out_free_lc_state;
> > > > > > > > > > > +             }
> > > > > > > > > > > +
> > > > > > > > > > > +             lc_state->descs[i] = desc;
> > > > > > > > > > > +     }
> > > > > > > > > > > +
> > > > > > > > > > > +     lc_state->numdescs = changed_req.num_lines;
> > > > > > > > > > > +
> > > > > > > > > > > +     init_waitqueue_head(&lc_state->waitqueue);
> > > > > > > > > > > +     INIT_KFIFO(lc_state->events);
> > > > > > > > > > > +     mutex_init(&lc_state->lock);
> > > > > > > > > > > +
> > > > > > > > > > > +     lc_state->changed_nb.notifier_call = linechanged_fd_notify;
> > > > > > > > > > > +
> > > > > > > > > > > +     ret = atomic_notifier_chain_register(&gdev->notifier,
> > > > > > > > > > > +                                          &lc_state->changed_nb);
> > > > > > > > > > > +     if (ret)
> > > > > > > > > > > +             goto out_free_lc_state;
> > > > > > > > > > > +
> > > > > > > > > > > +     fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
> > > > > > > > > > > +     if (fd < 0) {
> > > > > > > > > > > +             ret = fd;
> > > > > > > > > > > +             goto out_unregister_notifier;
> > > > > > > > > > > +     }
> > > > > > > > > > > +
> > > > > > > > > > > +     file = anon_inode_getfile("gpio-line-changed-fd",
> > > > > > > > > > > +                               &linechanged_fd_fileops,
> > > > > > > > > > > +                               lc_state, O_RDONLY | O_CLOEXEC);
> > > > > > > > > > > +     if (IS_ERR(file)) {
> > > > > > > > > > > +             ret = PTR_ERR(file);
> > > > > > > > > > > +             goto out_put_unused_fd;
> > > > > > > > > > > +     }
> > > > > > > > > > > +
> > > > > > > > > > > +     changed_req.fd = fd;
> > > > > > > > > > > +     ret = copy_to_user(ip, &changed_req, sizeof(changed_req));
> > > > > > > > > > > +     if (ret) {
> > > > > > > > > > > +             fput(file);
> > > > > > > > > > > +             put_unused_fd(fd);
> > > > > > > > > > > +             return -EFAULT;
> > > > > > > > > > > +     }
> > > > > > > > > > > +
> > > > > > > > > > > +     fd_install(fd, file);
> > > > > > > > > > > +
> > > > > > > > > > > +     return 0;
> > > > > > > > > > > +
> > > > > > > > > > > +out_put_unused_fd:
> > > > > > > > > > > +     put_unused_fd(fd);
> > > > > > > > > > > +out_unregister_notifier:
> > > > > > > > > > > +     atomic_notifier_chain_unregister(&gdev->notifier,
> > > > > > > > > > > +                                      &lc_state->changed_nb);
> > > > > > > > > > > +out_free_lc_state:
> > > > > > > > > > > +     kfree(lc_state);
> > > > > > > > > > > +
> > > > > > > > > > > +     return ret;
> > > > > > > > > > > +}
> > > > > > > > > > > +
> > > > > > > > > > >  /*
> > > > > > > > > > >   * gpio_ioctl() - ioctl handler for the GPIO chardev
> > > > > > > > > > >   */
> > > > > > > > > > > @@ -1238,6 +1447,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > > > > > > > >               return linehandle_create(gdev, ip);
> > > > > > > > > > >       } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
> > > > > > > > > > >               return lineevent_create(gdev, ip);
> > > > > > > > > > > +     } else if (cmd == GPIO_GET_LINECHANGED_FD_IOCTL) {
> > > > > > > > > > > +             return linechanged_fd_create(gdev, ip);
> > > > > > > > > > >       }
> > > > > > > > > > >       return -EINVAL;
> > > > > > > > > > >  }
> > > > > > > > > > > @@ -1499,6 +1710,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
> > > > > > > > > > >       for (i = 0; i < chip->ngpio; i++)
> > > > > > > > > > >               gdev->descs[i].gdev = gdev;
> > > > > > > > > > >
> > > > > > > > > > > +     ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
> > > > > > > > > > > +
> > > > > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > > > > >       INIT_LIST_HEAD(&gdev->pin_ranges);
> > > > > > > > > > >  #endif
> > > > > > > > > > > @@ -2837,6 +3050,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
> > > > > > > > > > >               spin_lock_irqsave(&gpio_lock, flags);
> > > > > > > > > > >       }
> > > > > > > > > > >  done:
> > > > > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > > > +                                GPIOLINE_CHANGED_REQUESTED, desc);
> > > > > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > > > > >       return ret;
> > > > > > > > > > >  }
> > > > > > > > > > > @@ -2934,6 +3149,8 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
> > > > > > > > > > >               ret = true;
> > > > > > > > > > >       }
> > > > > > > > > > >
> > > > > > > > > > > +     atomic_notifier_call_chain(&desc->gdev->notifier,
> > > > > > > > > > > +                                GPIOLINE_CHANGED_RELEASED, desc);
> > > > > > > > > > >       spin_unlock_irqrestore(&gpio_lock, flags);
> > > > > > > > > > >       return ret;
> > > > > > > > > > >  }
> > > > > > > > > > > @@ -3097,6 +3314,7 @@ static int gpio_set_bias(struct gpio_chip *chip, struct gpio_desc *desc)
> > > > > > > > > > >               if (ret != -ENOTSUPP)
> > > > > > > > > > >                       return ret;
> > > > > > > > > > >       }
> > > > > > > > > > > +
> > > > > > > > > > >       return 0;
> > > > > > > > > > >  }
> > > > > > > > > > >
> > > > > > > > > > > diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
> > > > > > > > > > > index a1cbeabadc69..8e3969616cfe 100644
> > > > > > > > > > > --- a/drivers/gpio/gpiolib.h
> > > > > > > > > > > +++ b/drivers/gpio/gpiolib.h
> > > > > > > > > > > @@ -54,6 +54,7 @@ struct gpio_device {
> > > > > > > > > > >       const char              *label;
> > > > > > > > > > >       void                    *data;
> > > > > > > > > > >       struct list_head        list;
> > > > > > > > > > > +     struct atomic_notifier_head notifier;
> > > > > > > > > > >
> > > > > > > > > > >  #ifdef CONFIG_PINCTRL
> > > > > > > > > > >       /*
> > > > > > > > > > > diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h
> > > > > > > > > > > index 799cf823d493..c61429467dd4 100644
> > > > > > > > > > > --- a/include/uapi/linux/gpio.h
> > > > > > > > > > > +++ b/include/uapi/linux/gpio.h
> > > > > > > > > > > @@ -59,6 +59,40 @@ struct gpioline_info {
> > > > > > > > > > >  /* Maximum number of requested handles */
> > > > > > > > > > >  #define GPIOHANDLES_MAX 64
> > > > > > > > > > >
> > > > > > > > > > > +/**
> > > > > > > > > > > + * struct gpioline_changed_fd_request - Information about a linechanged fd
> > > > > > > > > > > + * request
> > > > > > > > > > > + * @lineoffsets: an array of desired lines, specified by offset index for the
> > > > > > > > > > > + * associated GPIO device
> > > > > > > > > > > + * @num_lines: number of lines requested in this request, i.e. the number of
> > > > > > > > > > > + * valid fields in the above arrays, set to 1 to request a single line
> > > > > > > > > > > + * @fd: if successful this field will contain a valid anonymous file handle
> > > > > > > > > > > + */
> > > > > > > > > > > +struct gpioline_changed_fd_request {
> > > > > > > > > > > +     __u32 lineoffsets[GPIOHANDLES_MAX];
> > > > > > > > > > > +     __u32 num_lines;
> > > > > > > > > > > +     int fd;
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > >
> > > > > > > > > > Wouldn't the most common case be to watch all the lines on a chip?
> > > > > > > > > > How about an easy way to do that, say num_lines=0?
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > IMO this is too implicit - it's literally a magic value. I'd prefer to
> > > > > > > > > keep it this way for the same reason I didn't want to have implicit
> > > > > > > > > BIAS settings. I prefer the kernel uAPI to be explicit and then we can
> > > > > > > > > wrap it in simple helpers in the library.
> > > > > > > > >
> > > > > > > >
> > > > > > > > Or you could add a watch_all flag to make it explicit.
> > > > > > > > But fair enough to leave as is - it probably keeps the kernel cleaner.
> > > > > > > >
> > > > > > > > > > > +/* Possible line status change events */
> > > > > > > > > > > +enum {
> > > > > > > > > > > +     GPIOLINE_CHANGED_REQUESTED = 1,
> > > > > > > > > > > +     GPIOLINE_CHANGED_RELEASED,
> > > > > > > > > > > +     GPIOLINE_CHANGED_CONFIG,
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > > > +/**
> > > > > > > > > > > + * struct gpioline_changed - Information about a change in status
> > > > > > > > > > > + * of a GPIO line
> > > > > > > > > > > + * @line_offset: offset of the line that changed relative to the gpiochip
> > > > > > > > > > > + * @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
> > > > > > > > > > > + * and GPIOLINE_CHANGED_CONFIG
> > > > > > > > > > > + */
> > > > > > > > > > > +struct gpioline_changed {
> > > > > > > > > > > +     __u32 line_offset;
> > > > > > > > > > > +     __u32 event_type;
> > > > > > > > > > > +};
> > > > > > > > > > > +
> > > > > > > > > >
> > > > > > > > > > Rather than sending an event type, and requiring userspace to poll
> > > > > > > > > > LINEINFO, which is racy, how about passing the updated info flags here?
> > > > > > > > > > A change in the state of the GPIOLINE_FLAG_KERNEL implies the
> > > > > > > > > > GPIOLINE_CHANGED_REQUESTED or GPIOLINE_CHANGED_RELEASED, so the
> > > > > > > > > > event_type is then redundant.
> > > > > > > > > > Userspace would then only have to poll LINEINFO if they were interested
> > > > > > > > > > in the consumer on GPIOLINE_CHANGED_REQUESTED.
> > > > > > > > > >
> > > > > > > > > > To sync kernel and userspace state the current state of each line
> > > > > > > > > > should be returned immediately via the fd as soon as the fd is created,
> > > > > > > > > > and then subsequently on any change.
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > I guess you're right. You even made me think we could go as far as to
> > > > > > > > > embed the whole gpioline_info structure in struct gpioline_changed.
> > > > > > > > > I'd still keep the event type though - otherwise we'd have to assume
> > > > > > > > > the user always calls LINEINFO_IOCTL before obtaining the LINECHANGED
> > > > > > > > > fd.
> > > > > > > > >
> > > > > > > >
> > > > > > > > I suggested sending the current state of each line as soon as the fd
> > > > > > > > is created so that you don't have to assume anything.  That initial change
> > > > > > > > report is effectively the same as having the user call LINEINFO on each
> > > > > > > > line at the same time - without all the race issues.
> > > > > > > >
> > > > > > >
> > > > > > > Right, I didn't get this. That means you'd have to call the ioctl()
> > > > > > > and immediately follow up with a read(). I'm not a fan of this.
> > > > > > > Especially if we were to include a timestamp - what would it refer to?
> > > > > > >
> > > > > > > > I also considered the full gpioline_info structure, as well as reporting
> > > > > > > > bulk line changes (replacing line_offset with lineoffsets and num_lines),
> > > > > > > > but then I thought it was probably better to keep it minimal and simple.
> > > > > > > > The name and consumer are relatively large.
> > > > > > >
> > > > > > > In terms of performance - we're not really degrading it as this is
> > > > > > > still not a lot of data.
> > > > > > >
> > > > > > > How about the following:
> > > > > > > 1. Move the code filling out the struct gpioline_info to a separate
> > > > > > > function in the kernel.
> > > > > > > 2. Call the new ioctl() GET_LINEINFO_FD to indicate it's an extension
> > > > > > > of GET_LINEINFO.
> > > > > > > 3. Embed struct gpioline_info in struct gpioline_info_fd so that the
> > > > > > > initial info be read when the file descriptor is created.
> > > > > > > 4. Likewise embed struct gpioline_info in struct gpioline_changed and
> > > > > > > on every status change event provide the whole set of information?
> > > > > > >
> > > > > >
> > > > > > You mean an array of info in the ioctl return, along with the fd?
> > > > > > That would work too.
> > > > >
> > > > > I overlooked the fact that we can watch multiple lines. In this case
> > > > > it's either a separate fd for every watched line (not optimal) or
> > > > > doing what you proposed: read the initial lineinfo directly from the
> > > > > fd. But in this case again: what about the timestamp?
> > > > >
> > > >
> > > > It is set to the time the fd was created.
> > > > If you want you could also return that timestamp via the ioctl return
> > > > for comparison.
> > > > But most likely the userspace code will just ignore the timestamp for
> > > > those initial num_lines reports - those reports will get special
> > > > treatment anyway.
> > > >
> > > > Those initial reports are really part of the fd request handshake, so
> > > > I would expect the reading and handling of them to be part of the fd
> > > > request function in libgpiod, and they would be hidden from the end user.
> > > > The fd would only be returned to the user after the initial reports
> > > > have already been read and the line states synchronised.
> > > > OTOH I haven't put much thought into how the libgpiod API would look...
> > >
> > > So I can imagine a situation where we'd run into problems with this:
> > > when we start watching the maximum number of lines - 64 - the kfifo
> > > would be filled with reports from the start and we'd risk missing some
> > > later ones if we don't make this kfifo much larger and if the user
> > > doesn't manage to consume them all fast.
> > >
> > > How about reusing the already existing file descriptor associated with
> > > the chip itself? We currently only implement the ioctl() operation on
> > > it - the poll() and read() callbacks are empty.
> > >
> > > We'd need to add two new ioctls(): GPIOLINE_WATCH_IOCTL and
> > > GPIOLINE_UNWATCH_IOCTL. The structures for both would look like this:
> > >
> > > struct gpioline_watch_request {
> > >     __u32 lineoffset
> > >     struct gpioline_info info;
> > > };
> > >
> > > struct gpioline_unwatch_request {
> > >     __u32 lineoffset;
> > > };
> > >
> > > When GPIOLINE_WATCH_IOCTL is called, we'd setup a watch for given
> > > line: the embedded gpioline_info structure would be filled with
> > > initial info and we can now poll the gpiochip descriptor for events
> > > and read them. The event structure would looks like this:
> > >
> > > struct gpioline_changed {
> > >     __u32 event_type;
> > >     __u64 timestamp;
> > >     struct gpioline_info info;
> > > };
> > >
> > > Calling GPIOLINE_UNWATCH_IOCTL would of course make the kernel stop
> > > emitting events for given line.
> > >
> > > Does it make sense?
> > >
> >
> > That makes sense.  But it doesn't really address the underlying problem
> > that you have identified - it just makes it less likely that you will
> > fill the kfifo.
> >
> > Correct me if I'm wrong, but a pathological process or processes could
> > still swamp your kfifo with events, particularly if they are operating
> > on bulks.
> 
> Don't get me wrong - the assumption is that a process knows what it's
> doing. We expect that if it watches lines for events, then it will
> actually read them as soon as they arrive on the other end of the
> FIFO. If not - then this won't affect others, it will fill up the FIFO
> associated with this process' file descriptor and we'll just drop new
> events until it consumes old ones. In other words: I'm not worried
> about pathological processes.
> 

The reader can't guarantee that it can read faster than changes can occur,
no matter how well intentioned it is.

I am a bit worried if you just drop events, as there is no indication to
userspace that that has occured.

> The problem here is that the file descriptor is created and there are
> already several (up to 64) events waiting to be read. This just
> doesn't feel right. If the process doesn't manage to consume all
> initial events in time, we'll drop new ones. The alternative is to
> allocate a larger FIFO but I just have a feeling that this whole
> approach is wrong. I'm not sure any other subsystem does something
> like this.
> 
> >
> > I'd be happier with a solution that addresses what happens when the
> > kfifo is full, or even better prevents it from filling, and then see
> > how that feeds back to the startup case.
> >
> 
> You can't really prevent it from overflowing as you can't
> update/modify elements in the middle.
> 

You can if you let go of the idea of adding something to the fifo for
every change.  If you know the change you want to signal is already in
the fifo then you don't need to add it again.

The idea I'm suggesting now is for the fifo to contain "your info on
line X is stale" messages.  If that hasn't been ACKed by userspace then
there is no point adding another for that line.  So worst case you have 
num_lines messages in the fifo at any time.

> > Time to revisit your requirements...  Userspace doesn't need the details
> > of each change, all you are after is an asynchronous mechanism to
> > bring userspace up to date.  You only need to inform userspace that the
> > line has changed since it was last informed of a change (or the fd was
> > created).  You can collapse a set of changes that haven't reached
> > userspace down to the last one.  Then the outstanding messages for
> > userspace is at worst the number of lines being monitored.
> >
> > Wrt how to implement that, how about this:
> > When a line is changed you set a "needs_update" flag on that line, and
> > if not already set then issue a change to userspace via the fd.  That only
> > contains the line offset - no info.
> 
> Doesn't this bring us right back to my racy initial implementation?
> 

It is closer to your original, but the race is only relevant when you
are trying to match line info with changed events.  I'm suggesting we
give up trying to do that.  It was probably a mistake to go down that
particular rat hole - my bad.

All we do here is notify to userspace that their info is stale.
They ACK that by performing a LINEINFO ioctl on the monitoring fd (it
can't be the chip fd as we are introducing side effects - the ACK).
When the kernel sends the response to the LINEINFO ioctl we know the
kernel and userspace are in sync at that time.  And isn't that what you
want to achieve - bringing userspace back into sync with the kernel
without having to poll?

> > The userspace would then perform a LINEINFO ioctl to read the line state
> > and clear the "needs_update" flag in kernel.
> > At that point the kernel and userspace are back in sync for that line -
> > until the next change.
> > The LINEINFO ioctl would be on the monitor fd, not the chip fd, but
> > could otherwise be the same as the existing ioctl from the userspace pov.
> > This logic would apply for each monitoring fd which is monitoring that
> > line.
> 
> But then the kernel would assume that the user-space does a
> responsible thing with the event it reads, while the process may as
> well just discard all arriving data.
> 

The kernel doesn't assume anything - it just wont send another changed
event for a line until the userspace ACKs the previous.
If userspace doesn't ACK a changed event then they will not get any
more for that line, so they have a strong incentive to ACK it.
Note that the "your info is stale" message only contains the line
offset so the userspace is forced to perform the LINEINFO ioctl to find
out that the current state actually is.

Kent.

> Bart
> 
> >
> > Wrt the startup case, the userspace would create the monitor fd and then
> > call the LINEINFO ioctl on each monitored line.
> > Then you are in sync and good to go...
> >
> > There may well be better ways to implement that handshake - that is just
> > the first that comes to mind.
> >
> > Kent.
> >
> > > Bart
> > >
> > > >
> > > > Kent.
> > > >
> > > > > Bart
> > > > >
> > > > > >
> > > > > > I'm used to setting up streams like this in a networked environment
> > > > > > where returning data via the stream setup isn't an option, the only
> > > > > > option is to sync via the stream itself, so I overlooked the possibility
> > > > > > of using the ioctl return.
> > > > > >
> > > > > > > > The name is immutable(??), and so is pointless to include.
> > > > > > >
> > > > > > > It is now, but let's be future-proof. I can imagine having modifiable
> > > > > > > line names in the future. The code putting this info in struct
> > > > > > > gpioline_info wouldn't be really duplicated and the size of such small
> > > > > > > structures doesn't matter much - it's still a single context switch to
> > > > > > > read it.
> > > > > > >
> > > > > > > > The consumer only changes when the line is requested, so I was willing
> > > > > > > > to live with still having to poll for that.
> > > > > > > > And what you gain by reporting bulk lines you might lose in increased
> > > > > > > > report size and associated code.  OTOH it would make it explicit which
> > > > > > > > lines are being changed together...
> > > > > > > > So I could well be wrong on that - a full bulk report may be better.
> > > > > > >
> > > > > > > I'm not sure we need bulk reporting - just as we don't provide bulk
> > > > > > > GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> > > > > > > pretty complicated soon.
> > > > > > >
> > > > > >
> > > > > > That was my initial feeling as well.
> > > > > > And bulk is no longer an option if you want to include name in the change
> > > > > > report.
> > > > > >
> > > > > > Kent.
> > > > > >
> > > > > > > Bartosz
> > > > > > >
> > > > > > > >
> > > > > > > > Kent.
> > > > > > > >
> > > > > > > > > > And a timestamp might be useful, as per gpioevent_data?
> > > > > > > > >
> > > > > > > > > Sure thing!
> > > > > > > > >
> > > > > > > > > Bart
> > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > Kent.
> > > > > > > > > >
> > > > > > > > > > >  /* Linerequest flags */
> > > > > > > > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > > > > > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > > > > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > > > > > > > >
> > > > > > > > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > > > > > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > > > > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > > > > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > > > > > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > > > > > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > > > > > > > >
> > > > > > > > > > > --
> > > > > > > > > > > 2.23.0
> > > > > > > > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-12-01 23:43                       ` Kent Gibson
@ 2019-12-02 17:11                         ` Bartosz Golaszewski
  2019-12-03  2:09                           ` Kent Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-12-02 17:11 UTC (permalink / raw)
  To: Kent Gibson
  Cc: Bartosz Golaszewski, Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

pon., 2 gru 2019 o 00:43 Kent Gibson <warthog618@gmail.com> napisał(a):
>

[snip!]

> > > >
> > > > How about reusing the already existing file descriptor associated with
> > > > the chip itself? We currently only implement the ioctl() operation on
> > > > it - the poll() and read() callbacks are empty.
> > > >
> > > > We'd need to add two new ioctls(): GPIOLINE_WATCH_IOCTL and
> > > > GPIOLINE_UNWATCH_IOCTL. The structures for both would look like this:
> > > >
> > > > struct gpioline_watch_request {
> > > >     __u32 lineoffset
> > > >     struct gpioline_info info;
> > > > };
> > > >
> > > > struct gpioline_unwatch_request {
> > > >     __u32 lineoffset;
> > > > };
> > > >
> > > > When GPIOLINE_WATCH_IOCTL is called, we'd setup a watch for given
> > > > line: the embedded gpioline_info structure would be filled with
> > > > initial info and we can now poll the gpiochip descriptor for events
> > > > and read them. The event structure would looks like this:
> > > >
> > > > struct gpioline_changed {
> > > >     __u32 event_type;
> > > >     __u64 timestamp;
> > > >     struct gpioline_info info;
> > > > };
> > > >
> > > > Calling GPIOLINE_UNWATCH_IOCTL would of course make the kernel stop
> > > > emitting events for given line.
> > > >
> > > > Does it make sense?
> > > >
> > >
> > > That makes sense.  But it doesn't really address the underlying problem
> > > that you have identified - it just makes it less likely that you will
> > > fill the kfifo.
> > >
> > > Correct me if I'm wrong, but a pathological process or processes could
> > > still swamp your kfifo with events, particularly if they are operating
> > > on bulks.
> >
> > Don't get me wrong - the assumption is that a process knows what it's
> > doing. We expect that if it watches lines for events, then it will
> > actually read them as soon as they arrive on the other end of the
> > FIFO. If not - then this won't affect others, it will fill up the FIFO
> > associated with this process' file descriptor and we'll just drop new
> > events until it consumes old ones. In other words: I'm not worried
> > about pathological processes.
> >
>
> The reader can't guarantee that it can read faster than changes can occur,
> no matter how well intentioned it is.
>
> I am a bit worried if you just drop events, as there is no indication to
> userspace that that has occured.

This is what happens now with line events anyway. I added a patch to
the v2 of this series that adds a ratelimited debug message when the
kfifo is full. At least that will leave a trace in the kernel log.
Unfortunately there's no other way than limiting the FIFO's size -
otherwise a malicious process could hog all the memory by not reading
events.

>
> > The problem here is that the file descriptor is created and there are
> > already several (up to 64) events waiting to be read. This just
> > doesn't feel right. If the process doesn't manage to consume all
> > initial events in time, we'll drop new ones. The alternative is to
> > allocate a larger FIFO but I just have a feeling that this whole
> > approach is wrong. I'm not sure any other subsystem does something
> > like this.
> >
> > >
> > > I'd be happier with a solution that addresses what happens when the
> > > kfifo is full, or even better prevents it from filling, and then see
> > > how that feeds back to the startup case.
> > >
> >
> > You can't really prevent it from overflowing as you can't
> > update/modify elements in the middle.
> >
>
> You can if you let go of the idea of adding something to the fifo for
> every change.  If you know the change you want to signal is already in
> the fifo then you don't need to add it again.
>
> The idea I'm suggesting now is for the fifo to contain "your info on
> line X is stale" messages.  If that hasn't been ACKed by userspace then
> there is no point adding another for that line.  So worst case you have
> num_lines messages in the fifo at any time.

I see. But in this case I'm not sure a file descriptor is the right
interface. When POLLIN events are detected by poll() called on an fd -
it means there's data to read on the file descriptor: there's data
already in the FIFO waiting to be consumed by the user-space.

Let's imagine the following situation: we detect one of the conditions
for emitting the event in the kernel, we set the "needs_update" flag,
we then wake up the polling process, but it calls the LINE_INFO
ioctl() on the changed line without ever reading the event from the
fd. What would happen now? Does the unread event disappear from the fd
because the user "acked" the event? What about ordering of events when
line X gets updated, then line Y, then X again but the process didn't
read the first event?

IIRC the way the line events are handled in sysfs (polling
'gpioXYZ/value', while 'gpioXYZ/value' doesn't work as a FIFO) was
criticized for its unreliability and was one of the reasons for
developing the chardev.

I would be much happier with your previous proposal: getting line_info
when setting the watch and then getting it all again every time the
status changes. We also get the "history" of changes that way.

Bart

>
> > > Time to revisit your requirements...  Userspace doesn't need the details
> > > of each change, all you are after is an asynchronous mechanism to
> > > bring userspace up to date.  You only need to inform userspace that the
> > > line has changed since it was last informed of a change (or the fd was
> > > created).  You can collapse a set of changes that haven't reached
> > > userspace down to the last one.  Then the outstanding messages for
> > > userspace is at worst the number of lines being monitored.
> > >
> > > Wrt how to implement that, how about this:
> > > When a line is changed you set a "needs_update" flag on that line, and
> > > if not already set then issue a change to userspace via the fd.  That only
> > > contains the line offset - no info.
> >
> > Doesn't this bring us right back to my racy initial implementation?
> >
>
> It is closer to your original, but the race is only relevant when you
> are trying to match line info with changed events.  I'm suggesting we
> give up trying to do that.  It was probably a mistake to go down that
> particular rat hole - my bad.
>
> All we do here is notify to userspace that their info is stale.
> They ACK that by performing a LINEINFO ioctl on the monitoring fd (it
> can't be the chip fd as we are introducing side effects - the ACK).
> When the kernel sends the response to the LINEINFO ioctl we know the
> kernel and userspace are in sync at that time.  And isn't that what you
> want to achieve - bringing userspace back into sync with the kernel
> without having to poll?
>
> > > The userspace would then perform a LINEINFO ioctl to read the line state
> > > and clear the "needs_update" flag in kernel.
> > > At that point the kernel and userspace are back in sync for that line -
> > > until the next change.
> > > The LINEINFO ioctl would be on the monitor fd, not the chip fd, but
> > > could otherwise be the same as the existing ioctl from the userspace pov.
> > > This logic would apply for each monitoring fd which is monitoring that
> > > line.
> >
> > But then the kernel would assume that the user-space does a
> > responsible thing with the event it reads, while the process may as
> > well just discard all arriving data.
> >
>
> The kernel doesn't assume anything - it just wont send another changed
> event for a line until the userspace ACKs the previous.
> If userspace doesn't ACK a changed event then they will not get any
> more for that line, so they have a strong incentive to ACK it.
> Note that the "your info is stale" message only contains the line
> offset so the userspace is forced to perform the LINEINFO ioctl to find
> out that the current state actually is.
>
> Kent.
>
> > Bart
> >
> > >
> > > Wrt the startup case, the userspace would create the monitor fd and then
> > > call the LINEINFO ioctl on each monitored line.
> > > Then you are in sync and good to go...
> > >
> > > There may well be better ways to implement that handshake - that is just
> > > the first that comes to mind.
> > >
> > > Kent.
> > >
> > > > Bart
> > > >
> > > > >
> > > > > Kent.
> > > > >
> > > > > > Bart
> > > > > >
> > > > > > >
> > > > > > > I'm used to setting up streams like this in a networked environment
> > > > > > > where returning data via the stream setup isn't an option, the only
> > > > > > > option is to sync via the stream itself, so I overlooked the possibility
> > > > > > > of using the ioctl return.
> > > > > > >
> > > > > > > > > The name is immutable(??), and so is pointless to include.
> > > > > > > >
> > > > > > > > It is now, but let's be future-proof. I can imagine having modifiable
> > > > > > > > line names in the future. The code putting this info in struct
> > > > > > > > gpioline_info wouldn't be really duplicated and the size of such small
> > > > > > > > structures doesn't matter much - it's still a single context switch to
> > > > > > > > read it.
> > > > > > > >
> > > > > > > > > The consumer only changes when the line is requested, so I was willing
> > > > > > > > > to live with still having to poll for that.
> > > > > > > > > And what you gain by reporting bulk lines you might lose in increased
> > > > > > > > > report size and associated code.  OTOH it would make it explicit which
> > > > > > > > > lines are being changed together...
> > > > > > > > > So I could well be wrong on that - a full bulk report may be better.
> > > > > > > >
> > > > > > > > I'm not sure we need bulk reporting - just as we don't provide bulk
> > > > > > > > GET_GPIOLINE_INFO. The corresponding ioctl() structure would get
> > > > > > > > pretty complicated soon.
> > > > > > > >
> > > > > > >
> > > > > > > That was my initial feeling as well.
> > > > > > > And bulk is no longer an option if you want to include name in the change
> > > > > > > report.
> > > > > > >
> > > > > > > Kent.
> > > > > > >
> > > > > > > > Bartosz
> > > > > > > >
> > > > > > > > >
> > > > > > > > > Kent.
> > > > > > > > >
> > > > > > > > > > > And a timestamp might be useful, as per gpioevent_data?
> > > > > > > > > >
> > > > > > > > > > Sure thing!
> > > > > > > > > >
> > > > > > > > > > Bart
> > > > > > > > > >
> > > > > > > > > > >
> > > > > > > > > > > Kent.
> > > > > > > > > > >
> > > > > > > > > > > >  /* Linerequest flags */
> > > > > > > > > > > >  #define GPIOHANDLE_REQUEST_INPUT     (1UL << 0)
> > > > > > > > > > > >  #define GPIOHANDLE_REQUEST_OUTPUT    (1UL << 1)
> > > > > > > > > > > > @@ -176,6 +210,8 @@ struct gpioevent_data {
> > > > > > > > > > > >
> > > > > > > > > > > >  #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
> > > > > > > > > > > >  #define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
> > > > > > > > > > > > +#define GPIO_GET_LINECHANGED_FD_IOCTL \
> > > > > > > > > > > > +             _IOWR(0xB4, 0x0b, struct gpioline_changed_fd_request)
> > > > > > > > > > > >  #define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
> > > > > > > > > > > >  #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
> > > > > > > > > > > >
> > > > > > > > > > > > --
> > > > > > > > > > > > 2.23.0
> > > > > > > > > > > >

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-12-02 17:11                         ` Bartosz Golaszewski
@ 2019-12-03  2:09                           ` Kent Gibson
  2019-12-03  8:58                             ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Kent Gibson @ 2019-12-03  2:09 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Bartosz Golaszewski, Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Mon, Dec 02, 2019 at 06:11:06PM +0100, Bartosz Golaszewski wrote:
> pon., 2 gru 2019 o 00:43 Kent Gibson <warthog618@gmail.com> napisał(a):
> >
> 
> [snip!]
> 
> > > > >
> > > > > How about reusing the already existing file descriptor associated with
> > > > > the chip itself? We currently only implement the ioctl() operation on
> > > > > it - the poll() and read() callbacks are empty.
> > > > >
> > > > > We'd need to add two new ioctls(): GPIOLINE_WATCH_IOCTL and
> > > > > GPIOLINE_UNWATCH_IOCTL. The structures for both would look like this:
> > > > >
> > > > > struct gpioline_watch_request {
> > > > >     __u32 lineoffset
> > > > >     struct gpioline_info info;
> > > > > };
> > > > >
> > > > > struct gpioline_unwatch_request {
> > > > >     __u32 lineoffset;
> > > > > };
> > > > >
> > > > > When GPIOLINE_WATCH_IOCTL is called, we'd setup a watch for given
> > > > > line: the embedded gpioline_info structure would be filled with
> > > > > initial info and we can now poll the gpiochip descriptor for events
> > > > > and read them. The event structure would looks like this:
> > > > >
> > > > > struct gpioline_changed {
> > > > >     __u32 event_type;
> > > > >     __u64 timestamp;
> > > > >     struct gpioline_info info;
> > > > > };
> > > > >
> > > > > Calling GPIOLINE_UNWATCH_IOCTL would of course make the kernel stop
> > > > > emitting events for given line.
> > > > >
> > > > > Does it make sense?
> > > > >
> > > >
> > > > That makes sense.  But it doesn't really address the underlying problem
> > > > that you have identified - it just makes it less likely that you will
> > > > fill the kfifo.
> > > >
> > > > Correct me if I'm wrong, but a pathological process or processes could
> > > > still swamp your kfifo with events, particularly if they are operating
> > > > on bulks.
> > >
> > > Don't get me wrong - the assumption is that a process knows what it's
> > > doing. We expect that if it watches lines for events, then it will
> > > actually read them as soon as they arrive on the other end of the
> > > FIFO. If not - then this won't affect others, it will fill up the FIFO
> > > associated with this process' file descriptor and we'll just drop new
> > > events until it consumes old ones. In other words: I'm not worried
> > > about pathological processes.
> > >
> >
> > The reader can't guarantee that it can read faster than changes can occur,
> > no matter how well intentioned it is.
> >
> > I am a bit worried if you just drop events, as there is no indication to
> > userspace that that has occured.
> 
> This is what happens now with line events anyway. I added a patch to
> the v2 of this series that adds a ratelimited debug message when the
> kfifo is full. At least that will leave a trace in the kernel log.
> Unfortunately there's no other way than limiting the FIFO's size -
> otherwise a malicious process could hog all the memory by not reading
> events.
> 
> >
> > > The problem here is that the file descriptor is created and there are
> > > already several (up to 64) events waiting to be read. This just
> > > doesn't feel right. If the process doesn't manage to consume all
> > > initial events in time, we'll drop new ones. The alternative is to
> > > allocate a larger FIFO but I just have a feeling that this whole
> > > approach is wrong. I'm not sure any other subsystem does something
> > > like this.
> > >
> > > >
> > > > I'd be happier with a solution that addresses what happens when the
> > > > kfifo is full, or even better prevents it from filling, and then see
> > > > how that feeds back to the startup case.
> > > >
> > >
> > > You can't really prevent it from overflowing as you can't
> > > update/modify elements in the middle.
> > >
> >
> > You can if you let go of the idea of adding something to the fifo for
> > every change.  If you know the change you want to signal is already in
> > the fifo then you don't need to add it again.
> >
> > The idea I'm suggesting now is for the fifo to contain "your info on
> > line X is stale" messages.  If that hasn't been ACKed by userspace then
> > there is no point adding another for that line.  So worst case you have
> > num_lines messages in the fifo at any time.
> 
> I see. But in this case I'm not sure a file descriptor is the right
> interface. When POLLIN events are detected by poll() called on an fd -
> it means there's data to read on the file descriptor: there's data
> already in the FIFO waiting to be consumed by the user-space.
> 

Agree with file descriptors not being ideal for this, but what other
options are there?

> Let's imagine the following situation: we detect one of the conditions
> for emitting the event in the kernel, we set the "needs_update" flag,
> we then wake up the polling process, but it calls the LINE_INFO
> ioctl() on the changed line without ever reading the event from the
> fd. What would happen now? Does the unread event disappear from the fd
> because the user "acked" the event? What about ordering of events when
> line X gets updated, then line Y, then X again but the process didn't
> read the first event?
> 

The unread event can't disappear from the fifo. The fifo is write only
from the kernel side, right?

You are right that things don't go well if userspace doesn't strictly
follow the read from fd then LINEINFO ioctl ordering.

So probably best to keep things simple.

And we should accept that overflows may occur.  As that would leave
userspace with stale info, userspace should poll the LINEINFO ioctl
occassionally to check that it is still in sync.

> IIRC the way the line events are handled in sysfs (polling
> 'gpioXYZ/value', while 'gpioXYZ/value' doesn't work as a FIFO) was
> criticized for its unreliability and was one of the reasons for
> developing the chardev.
> 

Tarring it with the sysfs brush is a bit harsh!
You are comparing apples and oranges.
In the sysfs case the problem was losing events.
In this case losing events is not critical.

> I would be much happier with your previous proposal: getting line_info
> when setting the watch and then getting it all again every time the
> status changes. We also get the "history" of changes that way.
> 

I believe the previous proposal was yours - adding watch and unwatch
ioctls to the chip fd.

Kent.

</snip>

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info
  2019-12-03  2:09                           ` Kent Gibson
@ 2019-12-03  8:58                             ` Bartosz Golaszewski
  0 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-12-03  8:58 UTC (permalink / raw)
  To: Kent Gibson
  Cc: Bartosz Golaszewski, Linus Walleij, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

wt., 3 gru 2019 o 03:09 Kent Gibson <warthog618@gmail.com> napisał(a):
>
> On Mon, Dec 02, 2019 at 06:11:06PM +0100, Bartosz Golaszewski wrote:
> > pon., 2 gru 2019 o 00:43 Kent Gibson <warthog618@gmail.com> napisał(a):
> > >
> >
> > [snip!]
> >
> > > > > >
> > > > > > How about reusing the already existing file descriptor associated with
> > > > > > the chip itself? We currently only implement the ioctl() operation on
> > > > > > it - the poll() and read() callbacks are empty.
> > > > > >
> > > > > > We'd need to add two new ioctls(): GPIOLINE_WATCH_IOCTL and
> > > > > > GPIOLINE_UNWATCH_IOCTL. The structures for both would look like this:
> > > > > >
> > > > > > struct gpioline_watch_request {
> > > > > >     __u32 lineoffset
> > > > > >     struct gpioline_info info;
> > > > > > };
> > > > > >
> > > > > > struct gpioline_unwatch_request {
> > > > > >     __u32 lineoffset;
> > > > > > };
> > > > > >
> > > > > > When GPIOLINE_WATCH_IOCTL is called, we'd setup a watch for given
> > > > > > line: the embedded gpioline_info structure would be filled with
> > > > > > initial info and we can now poll the gpiochip descriptor for events
> > > > > > and read them. The event structure would looks like this:
> > > > > >
> > > > > > struct gpioline_changed {
> > > > > >     __u32 event_type;
> > > > > >     __u64 timestamp;
> > > > > >     struct gpioline_info info;
> > > > > > };
> > > > > >
> > > > > > Calling GPIOLINE_UNWATCH_IOCTL would of course make the kernel stop
> > > > > > emitting events for given line.
> > > > > >
> > > > > > Does it make sense?
> > > > > >
> > > > >
> > > > > That makes sense.  But it doesn't really address the underlying problem
> > > > > that you have identified - it just makes it less likely that you will
> > > > > fill the kfifo.
> > > > >
> > > > > Correct me if I'm wrong, but a pathological process or processes could
> > > > > still swamp your kfifo with events, particularly if they are operating
> > > > > on bulks.
> > > >
> > > > Don't get me wrong - the assumption is that a process knows what it's
> > > > doing. We expect that if it watches lines for events, then it will
> > > > actually read them as soon as they arrive on the other end of the
> > > > FIFO. If not - then this won't affect others, it will fill up the FIFO
> > > > associated with this process' file descriptor and we'll just drop new
> > > > events until it consumes old ones. In other words: I'm not worried
> > > > about pathological processes.
> > > >
> > >
> > > The reader can't guarantee that it can read faster than changes can occur,
> > > no matter how well intentioned it is.
> > >
> > > I am a bit worried if you just drop events, as there is no indication to
> > > userspace that that has occured.
> >
> > This is what happens now with line events anyway. I added a patch to
> > the v2 of this series that adds a ratelimited debug message when the
> > kfifo is full. At least that will leave a trace in the kernel log.
> > Unfortunately there's no other way than limiting the FIFO's size -
> > otherwise a malicious process could hog all the memory by not reading
> > events.
> >
> > >
> > > > The problem here is that the file descriptor is created and there are
> > > > already several (up to 64) events waiting to be read. This just
> > > > doesn't feel right. If the process doesn't manage to consume all
> > > > initial events in time, we'll drop new ones. The alternative is to
> > > > allocate a larger FIFO but I just have a feeling that this whole
> > > > approach is wrong. I'm not sure any other subsystem does something
> > > > like this.
> > > >
> > > > >
> > > > > I'd be happier with a solution that addresses what happens when the
> > > > > kfifo is full, or even better prevents it from filling, and then see
> > > > > how that feeds back to the startup case.
> > > > >
> > > >
> > > > You can't really prevent it from overflowing as you can't
> > > > update/modify elements in the middle.
> > > >
> > >
> > > You can if you let go of the idea of adding something to the fifo for
> > > every change.  If you know the change you want to signal is already in
> > > the fifo then you don't need to add it again.
> > >
> > > The idea I'm suggesting now is for the fifo to contain "your info on
> > > line X is stale" messages.  If that hasn't been ACKed by userspace then
> > > there is no point adding another for that line.  So worst case you have
> > > num_lines messages in the fifo at any time.
> >
> > I see. But in this case I'm not sure a file descriptor is the right
> > interface. When POLLIN events are detected by poll() called on an fd -
> > it means there's data to read on the file descriptor: there's data
> > already in the FIFO waiting to be consumed by the user-space.
> >
>
> Agree with file descriptors not being ideal for this, but what other
> options are there?
>
> > Let's imagine the following situation: we detect one of the conditions
> > for emitting the event in the kernel, we set the "needs_update" flag,
> > we then wake up the polling process, but it calls the LINE_INFO
> > ioctl() on the changed line without ever reading the event from the
> > fd. What would happen now? Does the unread event disappear from the fd
> > because the user "acked" the event? What about ordering of events when
> > line X gets updated, then line Y, then X again but the process didn't
> > read the first event?
> >
>
> The unread event can't disappear from the fifo. The fifo is write only
> from the kernel side, right?
>
> You are right that things don't go well if userspace doesn't strictly
> follow the read from fd then LINEINFO ioctl ordering.
>
> So probably best to keep things simple.
>
> And we should accept that overflows may occur.  As that would leave
> userspace with stale info, userspace should poll the LINEINFO ioctl
> occassionally to check that it is still in sync.
>
> > IIRC the way the line events are handled in sysfs (polling
> > 'gpioXYZ/value', while 'gpioXYZ/value' doesn't work as a FIFO) was
> > criticized for its unreliability and was one of the reasons for
> > developing the chardev.
> >
>
> Tarring it with the sysfs brush is a bit harsh!
> You are comparing apples and oranges.
> In the sysfs case the problem was losing events.
> In this case losing events is not critical.
>
> > I would be much happier with your previous proposal: getting line_info
> > when setting the watch and then getting it all again every time the
> > status changes. We also get the "history" of changes that way.
> >
>
> I believe the previous proposal was yours - adding watch and unwatch
> ioctls to the chip fd.

The idea was yours, the concrete proposal was mine. :)

I'll try to prepare a v2 and let's discuss the code again.

Bart

>
> Kent.
>
> </snip>

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-11-29 10:58   ` Bartosz Golaszewski
  2019-11-29 12:57     ` Linus Walleij
@ 2019-12-04 12:06     ` Enrico Weigelt, metux IT consult
  2019-12-04 16:26       ` Bartosz Golaszewski
  2019-12-06 15:44       ` Linus Walleij
  1 sibling, 2 replies; 32+ messages in thread
From: Enrico Weigelt, metux IT consult @ 2019-12-04 12:06 UTC (permalink / raw)
  To: Bartosz Golaszewski, Linus Walleij
  Cc: Kent Gibson, open list:GPIO SUBSYSTEM, linux-kernel, Bartosz Golaszewski

On 29.11.19 11:58, Bartosz Golaszewski wrote:

Hi folks,


missed much of this thread, so please excuse me if I'm going to say
something stupid ;-)

> No, it really is just about keeping the line information in user-space
> synchronized with the one in the kernel without re-reading the line
> info periodically. Whether it's the kernel that requests the line or
> other user-space process doesn't matter. We just want to stay
> up-to-date with the information we already do have access to.

In that case, why not just using inotify+friends or some blocking read
for that ?

Personally, I'm a really big friend of synthentic filesystems instead of
ioctl()s, because they're so simple to use (don't wanna repeat the whole
Plan9 literature here ;-)), so I'd do it in this way:

* have a file in /sys/class/gpio/... holding the current gpio
  information, in some easily parsable format. (either one file
  globally or one per chip)
* a) this file can be monitored via inotify (just one event when
     something had changed, or maybe have mtime reflect the time of
     last change)
* b) allow read after EOF, which is blocking, until something changes

That way, userland implementation would be trivial enough to do it
in a simple shell script.

> It may seem like a feature-creep because this is the third new feature
> being added to the character device in a short span of time. 

Personally, I'm not a fan of such chardevs at all, but this raises
another interesting pretty general topic: sidechannel/out-of-band data
of chardevs.

Originally, Unix chardevs have been designed as something operates on
streams of bytes, maybe with some extra operations (eg. setting baud
rate, etc). ioctl()s have been implemented as some escape route for such
extra functions. Over the many years, there's been a massive ioctl()
inflation, even hardware specific ones. IMHO, this is a huge mess, which
requires to lots of extra work for userland applications, which should
instead get generic interfaces, even leading to ridiculous things like
HAL, etc.

Seriously, we should lean back and think of better ways. One idea would
be making devices more directly-like, where things like oob-streams
or config attributes can be enumerated and addressed by names.
(actually, if I could change history, I'd make them actual directories)

> But at
> the same time: user-space wants to use GPIOs and they're mostly doing
> it over sysfs. If you want people to switch over to the character
> device - we must make it at least as useful as sysfs.

Personally, I don't see any practical reason, why I should use the
chardev at all - sysfs is much simpler. The only reason I see is when
needing to set several lines at once - but I haven't seen practical a
usecase where there isn't some specific device behind them, that
deservers it's own driver, attaching to some suitable subsystem.
(actually, I rarely talk to gpios directly from userland - except for
some lowlevel debugging purposes).

> These new features are not unjustified: I receive a significant amount
> of mail with feature-requests for libgpiod (also from people who are
> not well aware that I can only support features exposed by mainline
> kernel).

Do you have more detailed information on what these folks are really
up to, what their actual usecases are ?

> Last thing that users often complain about is the fact that with the
> character device, the state of GPIO lines used by user-space needs to
> be kept by the process using them. This unfortunately often comes from
> the lack of understanding of how a character device functions, but it
> really seems users want to have a centralized agent that takes control
> of the lines, provides standardized interface to interact with them
> and exports their metadata.

Yeah, I also sometimes have those kinds of clients. So far, in all cases
turned out that they actually needed a more high level device driver
(or extend an existing one) instead of touching gpios directly from
within the application. (usually things like relais or extra uart
signalling lines, power control, etc, via gpio, etc).

> Recognizing this fact, I implemented a proof-of-concept dbus daemon, 
> but one thing that it could really
> benefit from is dynamic, event-based synchronization and this series
> tries to add just that (BTW please take a look at the discussion under
> patch 7/8 - the code in this series will probably change a lot).

Oh, no, I've been afraid that this would be coming ... desktop bus in
embedded system is a real nightmare. (yes, some people are even insane
enough to put that thing on an public IP interface and so make critical
machinery like cards easily remote controllable by average school kids)

Seriously, I've seen a lot of similar constructs in the field, all of
them have quickly turned out as complete crap - usually coming from
folks who need >50kLoc ontop of trivial cansocket with silly excuses
like formal certification :o). Please, don't give these kids a machine
gun, they will just hurt innocent people with it :P

> We should probably start a new thread on this. I'll play the
> user-space's advocate and say: has anyone ever needed this? Do other
> kernel sub-systems do this?

Hotplug could lead to such situations.

Playing the kernel advocate here: better don't even consider opening
this apocalypse box and focus on clean kernel drivers instead :p


--mtx

---
Enrico Weigelt, metux IT consult
Free software and Linux embedded engineering
info@metux.net -- +49-151-27565287

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-11-29 12:57     ` Linus Walleij
@ 2019-12-04 12:32       ` Enrico Weigelt, metux IT consult
  0 siblings, 0 replies; 32+ messages in thread
From: Enrico Weigelt, metux IT consult @ 2019-12-04 12:32 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Kent Gibson, open list:GPIO SUBSYSTEM, linux-kernel, Bartosz Golaszewski

On 29.11.19 13:57, Linus Walleij wrote:

Hi,

> My own pet peeve is the industrial automation and control use
> case: here we have the design space where people today use
> either PLC:s or RaspberryPi's or Beagle boards, or even some
> custom computers.
> 
> For me personally that is a design space we should cover and
> if this helps the RaspberryPi to do that better I'm all for it.

Yeah, I also have clients in that field. The main problem here,
IMHO is their way of thinking: no device abstraction at all, but all
pretty much hardwired for specific installations (also the reason,
why they'll fail miserable on the new IOT field :p). These folks are
still used to the ancient pure PLC way, where there isn't even anything
like an operating system.

IMHO, it's an educational problem: people need to understand that
there're device abstractions for good reasons, and they should respect
and use them. Basically, they have to understand the concept of
modularization and abstractions. IEC 1131-3 obviously isn't made for
that.

For example, take simple heating installation. Pumps won't be controlled
by raw inverter configuration anymore, but rpm or m²/sec. Temp or flow
sensors don't give raw numbers, but degress Kelvin or m^/sec, etc.
In that case, the corresponding subsystem would be IIO - no need to ever
care about gpios, pwms, etc, directly.

Following this modular approach, everything suddenly gets much easier,
eg. replacing a pump by a different model just requires a minor
reconfiguration instead of rewriting huge parts of the plc code.

I've managed to teach this to an Siemens field technician in one evening
with a few beers, so it can't be that hard to understand.

> An especially interesting case is multiple GPIO expanders
> plugged in on pluggable busses such as PCI or USB. I think
> that kind of discoverability and dynamically expandable GPIO
> blocks is something people in the industry are quite keen to
> get.

Smells like a case for oftree overlays ...

> What we need to do is to make it dirt simple to use GPIOs for
> custom hacks and construction of factories and doorstops
> and what not, while at the same time strongly discouraging
> it to be used to manage hardware such as laptops, tablets
> or phones from userspace. That's maybe hard, and we might
> be victims of our own success ...

I contradict. We should encourage industrial/construction folks to
do decently structured, professional engineering - IOW: use
modularization and highlevel drivers, instead of tinkering with
raw gpios directly like a school kid.


--mtx

-- 
Dringender Hinweis: aufgrund existenzieller Bedrohung durch "Emotet"
sollten Sie *niemals* MS-Office-Dokumente via E-Mail annehmen/öffenen,
selbst wenn diese von vermeintlich vertrauenswürdigen Absendern zu
stammen scheinen. Andernfalls droht Totalschaden.
---
Hinweis: unverschlüsselte E-Mails können leicht abgehört und manipuliert
werden ! Für eine vertrauliche Kommunikation senden Sie bitte ihren
GPG/PGP-Schlüssel zu.
---
Enrico Weigelt, metux IT consult
Free software and Linux embedded engineering
info@metux.net -- +49-151-27565287

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-12-04 12:06     ` Enrico Weigelt, metux IT consult
@ 2019-12-04 16:26       ` Bartosz Golaszewski
  2019-12-06 12:52         ` Enrico Weigelt, metux IT consult
  2019-12-06 15:44       ` Linus Walleij
  1 sibling, 1 reply; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-12-04 16:26 UTC (permalink / raw)
  To: Enrico Weigelt, metux IT consult
  Cc: Linus Walleij, Kent Gibson, open list:GPIO SUBSYSTEM,
	linux-kernel, Bartosz Golaszewski

śr., 4 gru 2019 o 13:06 Enrico Weigelt, metux IT consult
<lkml@metux.net> napisał(a):
>
> On 29.11.19 11:58, Bartosz Golaszewski wrote:
>
> Hi folks,
>
>
> missed much of this thread, so please excuse me if I'm going to say
> something stupid ;-)
>
> > No, it really is just about keeping the line information in user-space
> > synchronized with the one in the kernel without re-reading the line
> > info periodically. Whether it's the kernel that requests the line or
> > other user-space process doesn't matter. We just want to stay
> > up-to-date with the information we already do have access to.
>
> In that case, why not just using inotify+friends or some blocking read
> for that ?
>
> Personally, I'm a really big friend of synthentic filesystems instead of
> ioctl()s, because they're so simple to use (don't wanna repeat the whole
> Plan9 literature here ;-)), so I'd do it in this way:
>
> * have a file in /sys/class/gpio/... holding the current gpio
>   information, in some easily parsable format. (either one file
>   globally or one per chip)
> * a) this file can be monitored via inotify (just one event when
>      something had changed, or maybe have mtime reflect the time of
>      last change)
> * b) allow read after EOF, which is blocking, until something changes
>
> That way, userland implementation would be trivial enough to do it
> in a simple shell script.
>
> > It may seem like a feature-creep because this is the third new feature
> > being added to the character device in a short span of time.
>
> Personally, I'm not a fan of such chardevs at all, but this raises
> another interesting pretty general topic: sidechannel/out-of-band data
> of chardevs.
>
> Originally, Unix chardevs have been designed as something operates on
> streams of bytes, maybe with some extra operations (eg. setting baud
> rate, etc). ioctl()s have been implemented as some escape route for such
> extra functions. Over the many years, there's been a massive ioctl()
> inflation, even hardware specific ones. IMHO, this is a huge mess, which
> requires to lots of extra work for userland applications, which should
> instead get generic interfaces, even leading to ridiculous things like
> HAL, etc.
>
> Seriously, we should lean back and think of better ways. One idea would
> be making devices more directly-like, where things like oob-streams
> or config attributes can be enumerated and addressed by names.
> (actually, if I could change history, I'd make them actual directories)
>
> > But at
> > the same time: user-space wants to use GPIOs and they're mostly doing
> > it over sysfs. If you want people to switch over to the character
> > device - we must make it at least as useful as sysfs.
>
> Personally, I don't see any practical reason, why I should use the
> chardev at all - sysfs is much simpler. The only reason I see is when
> needing to set several lines at once - but I haven't seen practical a
> usecase where there isn't some specific device behind them, that
> deservers it's own driver, attaching to some suitable subsystem.
> (actually, I rarely talk to gpios directly from userland - except for
> some lowlevel debugging purposes).
>
> > These new features are not unjustified: I receive a significant amount
> > of mail with feature-requests for libgpiod (also from people who are
> > not well aware that I can only support features exposed by mainline
> > kernel).
>
> Do you have more detailed information on what these folks are really
> up to, what their actual usecases are ?
>
> > Last thing that users often complain about is the fact that with the
> > character device, the state of GPIO lines used by user-space needs to
> > be kept by the process using them. This unfortunately often comes from
> > the lack of understanding of how a character device functions, but it
> > really seems users want to have a centralized agent that takes control
> > of the lines, provides standardized interface to interact with them
> > and exports their metadata.
>
> Yeah, I also sometimes have those kinds of clients. So far, in all cases
> turned out that they actually needed a more high level device driver
> (or extend an existing one) instead of touching gpios directly from
> within the application. (usually things like relais or extra uart
> signalling lines, power control, etc, via gpio, etc).
>
> > Recognizing this fact, I implemented a proof-of-concept dbus daemon,
> > but one thing that it could really
> > benefit from is dynamic, event-based synchronization and this series
> > tries to add just that (BTW please take a look at the discussion under
> > patch 7/8 - the code in this series will probably change a lot).
>
> Oh, no, I've been afraid that this would be coming ... desktop bus in
> embedded system is a real nightmare. (yes, some people are even insane
> enough to put that thing on an public IP interface and so make critical
> machinery like cards easily remote controllable by average school kids)
>

Nah, you're overreacting, nobody is putting anything on the network.
Most of my work isn't in upstream kernel development - it's actually
launching CE products. And it's been a long time since I've worked
with a client that could be convinced not to use systemd and dbus on
an embedded system. This just makes things so easy for people writing
high-level application code that it's become de-facto standard -
whether we like it or not.

> Seriously, I've seen a lot of similar constructs in the field, all of
> them have quickly turned out as complete crap - usually coming from
> folks who need >50kLoc ontop of trivial cansocket with silly excuses
> like formal certification :o). Please, don't give these kids a machine
> gun, they will just hurt innocent people with it :P
>

This is just your personal anecdote. I could bring up several examples
of good implementations of event-based, dbus-oriented systems all
right. Bad programmers don't make tools they're using bad.

As for the sysfs vs chardev: this has been discussed a lot. We won't
be adding any new features to the sysfs interface. I want to add this
new feature to allow for creating a central entity controlling GPIO
lines in the user-space. In case of sysfs - IT IS such a central
entity except that it lives in the kernel. It doesn't need to be
notified about changes in line properties.

Bartosz

> > We should probably start a new thread on this. I'll play the
> > user-space's advocate and say: has anyone ever needed this? Do other
> > kernel sub-systems do this?
>
> Hotplug could lead to such situations.
>
> Playing the kernel advocate here: better don't even consider opening
> this apocalypse box and focus on clean kernel drivers instead :p
>
>
> --mtx
>
> ---
> Enrico Weigelt, metux IT consult
> Free software and Linux embedded engineering
> info@metux.net -- +49-151-27565287

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-12-04 16:26       ` Bartosz Golaszewski
@ 2019-12-06 12:52         ` Enrico Weigelt, metux IT consult
  2019-12-06 15:57           ` Bartosz Golaszewski
  0 siblings, 1 reply; 32+ messages in thread
From: Enrico Weigelt, metux IT consult @ 2019-12-06 12:52 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Kent Gibson, open list:GPIO SUBSYSTEM,
	linux-kernel, Bartosz Golaszewski

Hi,

>> Oh, no, I've been afraid that this would be coming ... desktop bus in
>> embedded system is a real nightmare. (yes, some people are even insane
>> enough to put that thing on an public IP interface and so make critical
>> machinery like cards easily remote controllable by average school kids)
> 
> Nah, you're overreacting, nobody is putting anything on the network.

Maybe you've just missed that (maybe better for mental health ;-)), but
people actually do put desktop bus on open networks. Chrysler/Harmann
are an famous example (rendering the cars easily remote-controllable)
I've had lots of similar cases w/ my clients. Whats going on in critical
industries like automotive or medical often is a real nightmare. One of
the reasons why I only buy old cars and do a careful inspection of
physicians before allowing them to treat me or my family.

IMHO, we should be careful about not encouraging folks out there to do
silly things, just because they look so easy. (embedded development
isn't entirely easy)

> Most of my work isn't in upstream kernel development - it's actually
> launching CE products. And it's been a long time since I've worked
> with a client that could be convinced not to use systemd and dbus on
> an embedded system. 

Yeah, same problem here. But actually, it's easier than one might think:
let them run into a scenario where that malware really does horrible
things and let the client drive against the wall. Some will learn,
others won't - just let evolution optimize that :p

> This just makes things so easy for people writing
> high-level application code that it's become de-facto standard -
> whether we like it or not.

Yeah, "hilevel" code, that completely ignores vital aspects of the
machine it then runs on, eg. RT or memory constraints. Seen that so
many times. Emperical experience tells: sooner or later it will go
nuts and things go horribly wrong. The unplesant question just is: how
many people have get hurt or killed in the process.

Lennartware isn't the only problem here, but also ancient and completely
unmaintained vendor kernels, patched-to-deah by rookies (eg. ublox).
OTOH, the positive side, eg. if I really need to get a *new* car, I'd
pick one where I can disable surveillance stuff like "ecall" easily :p

In general, what those industries completely ignore is maintainability
and stability. There, this only exists on the paper (cubic meters of
papers that formally look nice, but don't really tell anything about
the *actual* important facts) - formally proven bullshit.

I've made the personal decision that I don't wanna become yet another
Edward Teller, neither do I wanna allow some BSG-type scenario. That's
the reason I'm spending so much time in this discussion.

> This is just your personal anecdote. I could bring up several examples
> of good implementations of event-based, dbus-oriented systems all
> right. Bad programmers don't make tools they're using bad.

Problem is: good programmers who can really deal with those concepts
are hard to find. And those systems usually are hard to test. Desktop
bus is magnitudes more complex than isolated FSMs.

Within the last decades, I never had a single case where desktop bus had
been the superior choice (actually, only few cases where purely event-
driven architecture was a good idea).

> I want to add this
> new feature to allow for creating a central entity controlling GPIO
> lines in the user-space. In case of sysfs - IT IS such a central
> entity except that it lives in the kernel. It doesn't need to be
> notified about changes in line properties.

I'm still wondering why exactly one would want such an central entity
in userspace and then letting it talk to others via desktop bus. At
least in critical embedded systems, not talking about self-knittet
hobby projects. Seems those ideas originate from people who don't know
what the OS/Kernel is for :p

Actually, I had a case where somebody attemted to do so (folks who tried
linux embedded development from a windows machine :o). Fortunately,
before they could write their planned "GPIO HAL" (several 10kLoc for
just doing some trivial sysfs ops), I've configured the lines to the
proper drivers in DT (basically, just some LEDs, inputs, rs485 signal
lines, etc). Later on they started to write some "HAL" for LEDs,
inputs, rs485 ... the manager of neighboring department on the other
end of the room then ask me: "don't they know that they've got an
operating system" ;-)

Seriously, we shouldn't do everything that some strange folks are asking
for, just to make them happy. We don't wanna desktop bus in the kernel,
do we ? :p

By the way: we *do* need some improvements for PLC-like applications:
Highlevel provisioning of platform devices, eg. configure which devices
are attached to certain interfaces in an specific installation, so the
applications don't ever need any knowledge about that, but just talk
to specific devices.

Most of what's needed (on kernel side) is already there: DT overlays.
Just yet have to get this working on non-DT (eg. ACPI platform).

In fact that's already in my pipeline, also for other purposes, eg.
replace simple board or mfd drivers by just a DT snippet. Yet lacked the
time to work out a suitable way for mixing DT and ACPI and allowing DT
to override ACPI. But that's a topic for a completely different thread.


--mtx--

---
Enrico Weigelt, metux IT consult
Free software and Linux embedded engineering
info@metux.net -- +49-151-27565287

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-12-04 12:06     ` Enrico Weigelt, metux IT consult
  2019-12-04 16:26       ` Bartosz Golaszewski
@ 2019-12-06 15:44       ` Linus Walleij
  1 sibling, 0 replies; 32+ messages in thread
From: Linus Walleij @ 2019-12-06 15:44 UTC (permalink / raw)
  To: Enrico Weigelt, metux IT consult
  Cc: Bartosz Golaszewski, Kent Gibson, open list:GPIO SUBSYSTEM,
	linux-kernel, Bartosz Golaszewski

On Wed, Dec 4, 2019 at 1:06 PM Enrico Weigelt, metux IT consult
<lkml@metux.net> wrote:

> Personally, I'm a really big friend of synthentic filesystems instead of
> ioctl()s, because they're so simple to use (don't wanna repeat the whole
> Plan9 literature here ;-)), so I'd do it in this way:
>
> * have a file in /sys/class/gpio/... holding the current gpio
>   information, in some easily parsable format. (either one file
>   globally or one per chip)

The topology for the current solution is in /sys/bus/gpio actually,
but I get what you mean.

> * a) this file can be monitored via inotify (just one event when
>      something had changed, or maybe have mtime reflect the time of
>      last change)
> * b) allow read after EOF, which is blocking, until something changes
>
> That way, userland implementation would be trivial enough to do it
> in a simple shell script.

The current (deprecated) sysfs pretty much does this.

The main issue sysfs in its current form had to die was that it relied
on global GPIO numbers. An alternative to the character device
would be to use e.g. subdirs for each GPIO chip and export
local offset numbers from there, but well we
reached a fork in the road with the chardev I'd say.

The main problem solved with the chardev was that scripts that
died/crashed left the sysfs nodes explicitly exported and
populated and everything just in the general mess it was at the time
the application crashed.

Of course it is easy to pose things like that the application should
register crash handlers or whatnot, but it turns out people weren't
doing that and with a character device, then it cleans up automatically
if the application dies or get terminated by a signal for example,
and the same application can just be relaunched without problems.

> > But at
> > the same time: user-space wants to use GPIOs and they're mostly doing
> > it over sysfs. If you want people to switch over to the character
> > device - we must make it at least as useful as sysfs.
>
> Personally, I don't see any practical reason, why I should use the
> chardev at all - sysfs is much simpler.

I see your stance, but it also makes it much easier to shoot
yourself in the foot.

> (actually, I rarely talk to gpios directly from userland - except for
> some lowlevel debugging purposes).

Nobody should. The users of userspace GPIO are factory lines,
industrial control and automation, maker communities and odd
prototypes. Not deployed products like phones or computers.

> Do you have more detailed information on what these folks are really
> up to, what their actual usecases are ?

The typical cases involves rigging a few relays and sensors
up in a lab to perform some automation, not dissimilar to e.g.
PLC (programmable logic controllers) and such. The world is
full of these one-offs, some in more expensive and intimidating
environments than others. Some are the lab bench of a few
select makers. Makers are not important to big capital and
big business (who are not talking to us) but they are important
to the community exactly because they are talking to us.

> > Recognizing this fact, I implemented a proof-of-concept dbus daemon,
> > but one thing that it could really
> > benefit from is dynamic, event-based synchronization and this series
> > tries to add just that (BTW please take a look at the discussion under
> > patch 7/8 - the code in this series will probably change a lot).
>
> Oh, no, I've been afraid that this would be coming ... desktop bus in
> embedded system is a real nightmare. (yes, some people are even insane
> enough to put that thing on an public IP interface and so make critical
> machinery like cards easily remote controllable by average school kids)
>
> Seriously, I've seen a lot of similar constructs in the field, all of
> them have quickly turned out as complete crap - usually coming from
> folks who need >50kLoc ontop of trivial cansocket with silly excuses
> like formal certification :o). Please, don't give these kids a machine
> gun, they will just hurt innocent people with it :P

I don't think that argument by likeness (with something you don't
like) is a very good one.

What we are discussing is what is nowadays referred to as
the "service layer" including D-Bus etc and systemd, whether
it uses D-Bus or something else is irrelevant.

OpenWrt has a service layer, it is called ubus, and it has a
system layer daemon as well (similar to systemd). This is deployed
in millions of routers as router manufacturers almost exclusively
build on top of OpenWrt these days. It is not a lot of code.
https://openwrt.org/docs/techref/ubus

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes
  2019-12-06 12:52         ` Enrico Weigelt, metux IT consult
@ 2019-12-06 15:57           ` Bartosz Golaszewski
  0 siblings, 0 replies; 32+ messages in thread
From: Bartosz Golaszewski @ 2019-12-06 15:57 UTC (permalink / raw)
  To: Enrico Weigelt, metux IT consult
  Cc: Linus Walleij, Kent Gibson, open list:GPIO SUBSYSTEM,
	linux-kernel, Bartosz Golaszewski

pt., 6 gru 2019 o 13:53 Enrico Weigelt, metux IT consult
<lkml@metux.net> napisał(a):
>
> Hi,
>
> >> Oh, no, I've been afraid that this would be coming ... desktop bus in
> >> embedded system is a real nightmare. (yes, some people are even insane
> >> enough to put that thing on an public IP interface and so make critical
> >> machinery like cards easily remote controllable by average school kids)
> >
> > Nah, you're overreacting, nobody is putting anything on the network.
>
> Maybe you've just missed that (maybe better for mental health ;-)), but
> people actually do put desktop bus on open networks. Chrysler/Harmann
> are an famous example (rendering the cars easily remote-controllable)
> I've had lots of similar cases w/ my clients. Whats going on in critical
> industries like automotive or medical often is a real nightmare. One of
> the reasons why I only buy old cars and do a careful inspection of
> physicians before allowing them to treat me or my family.
>

I admit - I mostly work in consumer electronics. The two times I
contracted for automotive companies I was actually surprised by how
seriously security was approached. Nothing like your stories. Maybe I
was just lucky though.

> IMHO, we should be careful about not encouraging folks out there to do
> silly things, just because they look so easy. (embedded development
> isn't entirely easy)
>
> > Most of my work isn't in upstream kernel development - it's actually
> > launching CE products. And it's been a long time since I've worked
> > with a client that could be convinced not to use systemd and dbus on
> > an embedded system.
>
> Yeah, same problem here. But actually, it's easier than one might think:
> let them run into a scenario where that malware really does horrible
> things and let the client drive against the wall. Some will learn,
> others won't - just let evolution optimize that :p
>
> > This just makes things so easy for people writing
> > high-level application code that it's become de-facto standard -
> > whether we like it or not.
>
> Yeah, "hilevel" code, that completely ignores vital aspects of the
> machine it then runs on, eg. RT or memory constraints. Seen that so
> many times. Emperical experience tells: sooner or later it will go
> nuts and things go horribly wrong. The unplesant question just is: how
> many people have get hurt or killed in the process.
>
> Lennartware isn't the only problem here, but also ancient and completely
> unmaintained vendor kernels, patched-to-deah by rookies (eg. ublox).
> OTOH, the positive side, eg. if I really need to get a *new* car, I'd
> pick one where I can disable surveillance stuff like "ecall" easily :p
>
> In general, what those industries completely ignore is maintainability
> and stability. There, this only exists on the paper (cubic meters of
> papers that formally look nice, but don't really tell anything about
> the *actual* important facts) - formally proven bullshit.
>
> I've made the personal decision that I don't wanna become yet another
> Edward Teller, neither do I wanna allow some BSG-type scenario. That's
> the reason I'm spending so much time in this discussion.
>

So I'm going through this e-mail for the third time now and I'm under
the impression it's mostly just an unrelated rant. I'm not even sure
how to respond.

> > This is just your personal anecdote. I could bring up several examples
> > of good implementations of event-based, dbus-oriented systems all
> > right. Bad programmers don't make tools they're using bad.
>
> Problem is: good programmers who can really deal with those concepts
> are hard to find. And those systems usually are hard to test. Desktop
> bus is magnitudes more complex than isolated FSMs.
>
> Within the last decades, I never had a single case where desktop bus had
> been the superior choice (actually, only few cases where purely event-
> driven architecture was a good idea).
>

It depends on what you're hired to do. Most of the time our clients
will have the entire applicative layer done by the time they reach out
to us. What they need is the BSP, OTA updates, provisioning and
factory tests. Good luck signing a deal if you start your pitch by
saying "your architecture is bad and you should feel bad". :)

> > I want to add this
> > new feature to allow for creating a central entity controlling GPIO
> > lines in the user-space. In case of sysfs - IT IS such a central
> > entity except that it lives in the kernel. It doesn't need to be
> > notified about changes in line properties.
>
> I'm still wondering why exactly one would want such an central entity
> in userspace and then letting it talk to others via desktop bus. At
> least in critical embedded systems, not talking about self-knittet
> hobby projects. Seems those ideas originate from people who don't know
> what the OS/Kernel is for :p
>
> Actually, I had a case where somebody attemted to do so (folks who tried
> linux embedded development from a windows machine :o). Fortunately,
> before they could write their planned "GPIO HAL" (several 10kLoc for
> just doing some trivial sysfs ops), I've configured the lines to the
> proper drivers in DT (basically, just some LEDs, inputs, rs485 signal
> lines, etc). Later on they started to write some "HAL" for LEDs,
> inputs, rs485 ... the manager of neighboring department on the other
> end of the room then ask me: "don't they know that they've got an
> operating system" ;-)
>
> Seriously, we shouldn't do everything that some strange folks are asking
> for, just to make them happy. We don't wanna desktop bus in the kernel,
> do we ? :p
>
> By the way: we *do* need some improvements for PLC-like applications:
> Highlevel provisioning of platform devices, eg. configure which devices
> are attached to certain interfaces in an specific installation, so the
> applications don't ever need any knowledge about that, but just talk
> to specific devices.
>
> Most of what's needed (on kernel side) is already there: DT overlays.
> Just yet have to get this working on non-DT (eg. ACPI platform).
>

Sure, but this has been discussed a lot already on linux-gpio. Linus
has just pointed out where GPIOs are used from user-space.

Bart

> In fact that's already in my pipeline, also for other purposes, eg.
> replace simple board or mfd drivers by just a DT snippet. Yet lacked the
> time to work out a suitable way for mixing DT and ACPI and allowing DT
> to override ACPI. But that's a topic for a completely different thread.
>
>
> --mtx--
>
> ---
> Enrico Weigelt, metux IT consult
> Free software and Linux embedded engineering
> info@metux.net -- +49-151-27565287

^ permalink raw reply	[flat|nested] 32+ messages in thread

end of thread, back to index

Thread overview: 32+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-27 13:35 [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 1/8] gpiolib: use 'unsigned int' instead of 'unsigned' in gpio_set_config() Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 2/8] gpiolib: have a single place of calling set_config() Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 3/8] gpiolib: convert the type of hwnum to unsigned int in gpiochip_get_desc() Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 4/8] gpiolib: use gpiochip_get_desc() in linehandle_create() Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 5/8] gpiolib: use gpiochip_get_desc() in lineevent_create() Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 6/8] gpiolib: actually protect the line event kfifo with mutex Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 7/8] gpiolib: add new ioctl() for monitoring changes in line info Bartosz Golaszewski
2019-11-27 15:24   ` Kent Gibson
2019-11-27 15:50     ` Bartosz Golaszewski
2019-11-27 23:23       ` Kent Gibson
2019-11-28  9:45         ` Bartosz Golaszewski
2019-11-28 14:10           ` Kent Gibson
2019-11-28 14:36             ` Bartosz Golaszewski
2019-11-28 15:02               ` Kent Gibson
2019-11-29  9:43                 ` Bartosz Golaszewski
2019-11-29 13:49                   ` Kent Gibson
2019-12-01 15:25                     ` Bartosz Golaszewski
2019-12-01 23:43                       ` Kent Gibson
2019-12-02 17:11                         ` Bartosz Golaszewski
2019-12-03  2:09                           ` Kent Gibson
2019-12-03  8:58                             ` Bartosz Golaszewski
2019-11-27 13:35 ` [PATCH 8/8] tools: gpio: implement gpio-watch Bartosz Golaszewski
2019-11-29 10:04 ` [PATCH 0/8] gpiolib: add an ioctl() for monitoring line status changes Linus Walleij
2019-11-29 10:58   ` Bartosz Golaszewski
2019-11-29 12:57     ` Linus Walleij
2019-12-04 12:32       ` Enrico Weigelt, metux IT consult
2019-12-04 12:06     ` Enrico Weigelt, metux IT consult
2019-12-04 16:26       ` Bartosz Golaszewski
2019-12-06 12:52         ` Enrico Weigelt, metux IT consult
2019-12-06 15:57           ` Bartosz Golaszewski
2019-12-06 15:44       ` Linus Walleij

Linux-GPIO Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-gpio/0 linux-gpio/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-gpio linux-gpio/ https://lore.kernel.org/linux-gpio \
		linux-gpio@vger.kernel.org
	public-inbox-index linux-gpio

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-gpio


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git