summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpiolib-cdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio/gpiolib-cdev.c')
-rw-r--r--drivers/gpio/gpiolib-cdev.c1427
1 files changed, 933 insertions, 494 deletions
diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index c7b5446d01fd..3735c9fe1502 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -5,6 +5,7 @@
#include <linux/bitmap.h>
#include <linux/build_bug.h>
#include <linux/cdev.h>
+#include <linux/cleanup.h>
#include <linux/compat.h>
#include <linux/compiler.h>
#include <linux/device.h>
@@ -12,18 +13,22 @@
#include <linux/file.h>
#include <linux/gpio.h>
#include <linux/gpio/driver.h>
+#include <linux/hte.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
-#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/overflow.h>
#include <linux/pinctrl/consumer.h>
#include <linux/poll.h>
+#include <linux/seq_file.h>
#include <linux/spinlock.h>
+#include <linux/string.h>
#include <linux/timekeeping.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
+
#include <uapi/linux/gpio.h>
#include "gpiolib.h"
@@ -83,6 +88,10 @@ struct linehandle_state {
GPIOHANDLE_REQUEST_OPEN_DRAIN | \
GPIOHANDLE_REQUEST_OPEN_SOURCE)
+#define GPIOHANDLE_REQUEST_DIRECTION_FLAGS \
+ (GPIOHANDLE_REQUEST_INPUT | \
+ GPIOHANDLE_REQUEST_OUTPUT)
+
static int linehandle_validate_flags(u32 flags)
{
/* Return an error if an unknown flag is set */
@@ -133,18 +142,22 @@ static int linehandle_validate_flags(u32 flags)
static void linehandle_flags_to_desc_flags(u32 lflags, unsigned long *flagsp)
{
- assign_bit(FLAG_ACTIVE_LOW, flagsp,
+ unsigned long flags = READ_ONCE(*flagsp);
+
+ assign_bit(GPIOD_FLAG_ACTIVE_LOW, &flags,
lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW);
- assign_bit(FLAG_OPEN_DRAIN, flagsp,
+ assign_bit(GPIOD_FLAG_OPEN_DRAIN, &flags,
lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN);
- assign_bit(FLAG_OPEN_SOURCE, flagsp,
+ assign_bit(GPIOD_FLAG_OPEN_SOURCE, &flags,
lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE);
- assign_bit(FLAG_PULL_UP, flagsp,
+ assign_bit(GPIOD_FLAG_PULL_UP, &flags,
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP);
- assign_bit(FLAG_PULL_DOWN, flagsp,
+ assign_bit(GPIOD_FLAG_PULL_DOWN, &flags,
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN);
- assign_bit(FLAG_BIAS_DISABLE, flagsp,
+ assign_bit(GPIOD_FLAG_BIAS_DISABLE, &flags,
lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE);
+
+ WRITE_ONCE(*flagsp, flags);
}
static long linehandle_set_config(struct linehandle_state *lh,
@@ -163,29 +176,27 @@ static long linehandle_set_config(struct linehandle_state *lh,
if (ret)
return ret;
+ /* Lines must be reconfigured explicitly as input or output. */
+ if (!(lflags & GPIOHANDLE_REQUEST_DIRECTION_FLAGS))
+ return -EINVAL;
+
for (i = 0; i < lh->num_descs; i++) {
desc = lh->descs[i];
- linehandle_flags_to_desc_flags(gcnf.flags, &desc->flags);
+ linehandle_flags_to_desc_flags(lflags, &desc->flags);
- /*
- * Lines have to be requested explicitly for input
- * or output, else the line will be treated "as is".
- */
if (lflags & GPIOHANDLE_REQUEST_OUTPUT) {
int val = !!gcnf.default_values[i];
- ret = gpiod_direction_output(desc, val);
+ ret = gpiod_direction_output_nonotify(desc, val);
if (ret)
return ret;
- } else if (lflags & GPIOHANDLE_REQUEST_INPUT) {
- ret = gpiod_direction_input(desc);
+ } else {
+ ret = gpiod_direction_input_nonotify(desc);
if (ret)
return ret;
}
- blocking_notifier_call_chain(&desc->gdev->notifier,
- GPIO_V2_LINE_CHANGED_CONFIG,
- desc);
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
}
return 0;
}
@@ -197,16 +208,20 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
void __user *ip = (void __user *)arg;
struct gpiohandle_data ghd;
DECLARE_BITMAP(vals, GPIOHANDLES_MAX);
- int i;
+ unsigned int i;
+ int ret;
- if (cmd == GPIOHANDLE_GET_LINE_VALUES_IOCTL) {
- /* NOTE: It's ok to read values of output lines. */
- int ret = gpiod_get_array_value_complex(false,
- true,
- lh->num_descs,
- lh->descs,
- NULL,
- vals);
+ guard(srcu)(&lh->gdev->srcu);
+
+ if (!rcu_access_pointer(lh->gdev->chip))
+ return -ENODEV;
+
+ switch (cmd) {
+ case GPIOHANDLE_GET_LINE_VALUES_IOCTL:
+ /* NOTE: It's okay to read values of output lines */
+ ret = gpiod_get_array_value_complex(false, true,
+ lh->num_descs, lh->descs,
+ NULL, vals);
if (ret)
return ret;
@@ -218,12 +233,12 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
return -EFAULT;
return 0;
- } else if (cmd == GPIOHANDLE_SET_LINE_VALUES_IOCTL) {
+ case GPIOHANDLE_SET_LINE_VALUES_IOCTL:
/*
* All line descriptors were created at once with the same
* flags so just check if the first one is really output.
*/
- if (!test_bit(FLAG_IS_OUT, &lh->descs[0]->flags))
+ if (!test_bit(GPIOD_FLAG_IS_OUT, &lh->descs[0]->flags))
return -EPERM;
if (copy_from_user(&ghd, ip, sizeof(ghd)))
@@ -240,10 +255,11 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
lh->descs,
NULL,
vals);
- } else if (cmd == GPIOHANDLE_SET_CONFIG_IOCTL) {
+ case GPIOHANDLE_SET_CONFIG_IOCTL:
return linehandle_set_config(lh, ip);
+ default:
+ return -EINVAL;
}
- return -EINVAL;
}
#ifdef CONFIG_COMPAT
@@ -262,7 +278,7 @@ static void linehandle_free(struct linehandle_state *lh)
if (lh->descs[i])
gpiod_free(lh->descs[i]);
kfree(lh->label);
- put_device(&lh->gdev->dev);
+ gpio_device_put(lh->gdev);
kfree(lh);
}
@@ -282,12 +298,13 @@ static const struct file_operations linehandle_fileops = {
#endif
};
+DEFINE_FREE(linehandle_free, struct linehandle_state *, if (!IS_ERR_OR_NULL(_T)) linehandle_free(_T))
+
static int linehandle_create(struct gpio_device *gdev, void __user *ip)
{
struct gpiohandle_request handlereq;
- struct linehandle_state *lh;
- struct file *file;
- int fd, i, ret;
+ struct linehandle_state *lh __free(linehandle_free) = NULL;
+ int i, ret;
u32 lflags;
if (copy_from_user(&handlereq, ip, sizeof(handlereq)))
@@ -304,18 +321,15 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
lh = kzalloc(sizeof(*lh), GFP_KERNEL);
if (!lh)
return -ENOMEM;
- lh->gdev = gdev;
- get_device(&gdev->dev);
+ lh->gdev = gpio_device_get(gdev);
if (handlereq.consumer_label[0] != '\0') {
/* label is only initialized if consumer_label is set */
lh->label = kstrndup(handlereq.consumer_label,
sizeof(handlereq.consumer_label) - 1,
GFP_KERNEL);
- if (!lh->label) {
- ret = -ENOMEM;
- goto out_free_lh;
- }
+ if (!lh->label)
+ return -ENOMEM;
}
lh->num_descs = handlereq.lines;
@@ -323,22 +337,20 @@ 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 = gpiochip_get_desc(gdev->chip, offset);
+ struct gpio_desc *desc = gpio_device_get_desc(gdev, offset);
- if (IS_ERR(desc)) {
- ret = PTR_ERR(desc);
- goto out_free_lh;
- }
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
- ret = gpiod_request(desc, lh->label);
+ ret = gpiod_request_user(desc, lh->label);
if (ret)
- goto out_free_lh;
+ return ret;
lh->descs[i] = desc;
linehandle_flags_to_desc_flags(handlereq.flags, &desc->flags);
ret = gpiod_set_transitory(desc, false);
if (ret < 0)
- goto out_free_lh;
+ return ret;
/*
* Lines have to be requested explicitly for input
@@ -347,60 +359,38 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
if (lflags & GPIOHANDLE_REQUEST_OUTPUT) {
int val = !!handlereq.default_values[i];
- ret = gpiod_direction_output(desc, val);
+ ret = gpiod_direction_output_nonotify(desc, val);
if (ret)
- goto out_free_lh;
+ return ret;
} else if (lflags & GPIOHANDLE_REQUEST_INPUT) {
- ret = gpiod_direction_input(desc);
+ ret = gpiod_direction_input_nonotify(desc);
if (ret)
- goto out_free_lh;
+ return ret;
}
- blocking_notifier_call_chain(&desc->gdev->notifier,
- GPIO_V2_LINE_CHANGED_REQUESTED, desc);
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
dev_dbg(&gdev->dev, "registered chardev handle for line %d\n",
offset);
}
- fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
- if (fd < 0) {
- ret = fd;
- goto out_free_lh;
- }
-
- file = anon_inode_getfile("gpio-linehandle",
- &linehandle_fileops,
- lh,
- O_RDONLY | O_CLOEXEC);
- if (IS_ERR(file)) {
- ret = PTR_ERR(file);
- goto out_put_unused_fd;
- }
+ FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC,
+ anon_inode_getfile("gpio-linehandle", &linehandle_fileops,
+ lh, O_RDONLY | O_CLOEXEC));
+ if (fdf.err)
+ return fdf.err;
+ retain_and_null_ptr(lh);
- handlereq.fd = fd;
- if (copy_to_user(ip, &handlereq, sizeof(handlereq))) {
- /*
- * fput() will trigger the release() callback, so do not go onto
- * the regular error cleanup path here.
- */
- fput(file);
- put_unused_fd(fd);
+ handlereq.fd = fd_prepare_fd(fdf);
+ if (copy_to_user(ip, &handlereq, sizeof(handlereq)))
return -EFAULT;
- }
- fd_install(fd, file);
+ fd_publish(fdf);
dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n",
lh->num_descs);
return 0;
-
-out_put_unused_fd:
- put_unused_fd(fd);
-out_free_lh:
- linehandle_free(lh);
- return ret;
}
#endif /* CONFIG_GPIO_CDEV_V1 */
@@ -409,7 +399,7 @@ out_free_lh:
* @desc: the GPIO descriptor for this line.
* @req: the corresponding line request
* @irq: the interrupt triggered in response to events on this GPIO
- * @eflags: the edge flags, GPIO_V2_LINE_FLAG_EDGE_RISING and/or
+ * @edflags: the edge flags, GPIO_V2_LINE_FLAG_EDGE_RISING and/or
* GPIO_V2_LINE_FLAG_EDGE_FALLING, indicating the edge detection applied
* @timestamp_ns: cache for the timestamp storing it between hardirq and
* IRQ thread, used to bring the timestamp close to the actual event
@@ -420,6 +410,10 @@ out_free_lh:
* @work: the worker that implements software debouncing
* @sw_debounced: flag indicating if the software debouncer is active
* @level: the current debounced physical level of the line
+ * @hdesc: the Hardware Timestamp Engine (HTE) descriptor
+ * @raw_level: the line level at the time of event
+ * @total_discard_seq: the running counter of the discarded events
+ * @last_seqno: the last sequence number before debounce period expires
*/
struct line {
struct gpio_desc *desc;
@@ -429,12 +423,15 @@ struct line {
struct linereq *req;
unsigned int irq;
/*
- * eflags is set by edge_detector_setup(), edge_detector_stop() and
- * edge_detector_update(), which are themselves mutually exclusive,
- * and is accessed by edge_irq_thread() and debounce_work_func(),
- * which can both live with a slightly stale value.
+ * The flags for the active edge detector configuration.
+ *
+ * edflags is set by linereq_create(), linereq_free(), and
+ * linereq_set_config(), which are themselves mutually
+ * exclusive, and is accessed by edge_irq_thread(),
+ * process_hw_ts_thread() and debounce_work_func(),
+ * which can all live with a slightly stale value.
*/
- u64 eflags;
+ u64 edflags;
/*
* timestamp_ns and req_seqno are accessed only by
* edge_irq_handler() and edge_irq_thread(), which are themselves
@@ -464,6 +461,24 @@ struct line {
* stale value.
*/
unsigned int level;
+#ifdef CONFIG_HTE
+ struct hte_ts_desc hdesc;
+ /*
+ * HTE provider sets line level at the time of event. The valid
+ * value is 0 or 1 and negative value for an error.
+ */
+ int raw_level;
+ /*
+ * when sw_debounce is set on HTE enabled line, this is running
+ * counter of the discarded events.
+ */
+ u32 total_discard_seq;
+ /*
+ * when sw_debounce is set on HTE enabled line, this variable records
+ * last sequence number before debounce period expires.
+ */
+ u32 last_seqno;
+#endif /* CONFIG_HTE */
};
/**
@@ -472,6 +487,7 @@ struct line {
* @label: consumer label used to tag GPIO descriptors
* @num_lines: the number of lines in the lines array
* @wait: wait queue that handles blocking reads of events
+ * @device_unregistered_nb: notifier block for receiving gdev unregister events
* @event_buffer_size: the number of elements allocated in @events
* @events: KFIFO for the GPIO events
* @seqno: the sequence number for edge events generated on all lines in
@@ -486,11 +502,12 @@ struct linereq {
const char *label;
u32 num_lines;
wait_queue_head_t wait;
+ struct notifier_block device_unregistered_nb;
u32 event_buffer_size;
DECLARE_KFIFO_PTR(events, struct gpio_v2_line_event);
atomic_t seqno;
struct mutex config_mutex;
- struct line lines[];
+ struct line lines[] __counted_by(num_lines);
};
#define GPIO_V2_LINE_BIAS_FLAGS \
@@ -518,20 +535,38 @@ struct linereq {
GPIO_V2_LINE_DRIVE_FLAGS | \
GPIO_V2_LINE_EDGE_FLAGS | \
GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME | \
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE | \
GPIO_V2_LINE_BIAS_FLAGS)
+/* subset of flags relevant for edge detector configuration */
+#define GPIO_V2_LINE_EDGE_DETECTOR_FLAGS \
+ (GPIO_V2_LINE_FLAG_ACTIVE_LOW | \
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE | \
+ GPIO_V2_LINE_EDGE_FLAGS)
+
+static int linereq_unregistered_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct linereq *lr = container_of(nb, struct linereq,
+ device_unregistered_nb);
+
+ wake_up_poll(&lr->wait, EPOLLIN | EPOLLERR);
+
+ return NOTIFY_OK;
+}
+
static void linereq_put_event(struct linereq *lr,
struct gpio_v2_line_event *le)
{
bool overflow = false;
- spin_lock(&lr->wait.lock);
- if (kfifo_is_full(&lr->events)) {
- overflow = true;
- kfifo_skip(&lr->events);
+ scoped_guard(spinlock, &lr->wait.lock) {
+ if (kfifo_is_full(&lr->events)) {
+ overflow = true;
+ kfifo_skip(&lr->events);
+ }
+ kfifo_in(&lr->events, le, 1);
}
- kfifo_in(&lr->events, le, 1);
- spin_unlock(&lr->wait.lock);
if (!overflow)
wake_up_poll(&lr->wait, EPOLLIN);
else
@@ -540,18 +575,166 @@ static void linereq_put_event(struct linereq *lr,
static u64 line_event_timestamp(struct line *line)
{
- if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &line->desc->flags))
+ if (test_bit(GPIOD_FLAG_EVENT_CLOCK_REALTIME, &line->desc->flags))
return ktime_get_real_ns();
+ else if (IS_ENABLED(CONFIG_HTE) &&
+ test_bit(GPIOD_FLAG_EVENT_CLOCK_HTE, &line->desc->flags))
+ return line->timestamp_ns;
return ktime_get_ns();
}
+static u32 line_event_id(int level)
+{
+ return level ? GPIO_V2_LINE_EVENT_RISING_EDGE :
+ GPIO_V2_LINE_EVENT_FALLING_EDGE;
+}
+
+static inline char *make_irq_label(const char *orig)
+{
+ char *new;
+
+ if (!orig)
+ return NULL;
+
+ new = kstrdup_and_replace(orig, '/', ':', GFP_KERNEL);
+ if (!new)
+ return ERR_PTR(-ENOMEM);
+
+ return new;
+}
+
+static inline void free_irq_label(const char *label)
+{
+ kfree(label);
+}
+
+#ifdef CONFIG_HTE
+
+static enum hte_return process_hw_ts_thread(void *p)
+{
+ struct line *line;
+ struct linereq *lr;
+ struct gpio_v2_line_event le;
+ u64 edflags;
+ int level;
+
+ if (!p)
+ return HTE_CB_HANDLED;
+
+ line = p;
+ lr = line->req;
+
+ memset(&le, 0, sizeof(le));
+
+ le.timestamp_ns = line->timestamp_ns;
+ edflags = READ_ONCE(line->edflags);
+
+ switch (edflags & GPIO_V2_LINE_EDGE_FLAGS) {
+ case GPIO_V2_LINE_FLAG_EDGE_BOTH:
+ level = (line->raw_level >= 0) ?
+ line->raw_level :
+ gpiod_get_raw_value_cansleep(line->desc);
+
+ if (edflags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+ level = !level;
+
+ le.id = line_event_id(level);
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_RISING:
+ le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_FALLING:
+ le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
+ break;
+ default:
+ return HTE_CB_HANDLED;
+ }
+ le.line_seqno = line->line_seqno;
+ le.seqno = (lr->num_lines == 1) ? le.line_seqno : line->req_seqno;
+ le.offset = gpiod_hwgpio(line->desc);
+
+ linereq_put_event(lr, &le);
+
+ return HTE_CB_HANDLED;
+}
+
+static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p)
+{
+ struct line *line;
+ struct linereq *lr;
+ int diff_seqno = 0;
+
+ if (!ts || !p)
+ return HTE_CB_HANDLED;
+
+ line = p;
+ line->timestamp_ns = ts->tsc;
+ line->raw_level = ts->raw_level;
+ lr = line->req;
+
+ if (READ_ONCE(line->sw_debounced)) {
+ line->total_discard_seq++;
+ line->last_seqno = ts->seq;
+ mod_delayed_work(system_percpu_wq, &line->work,
+ usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
+ } else {
+ if (unlikely(ts->seq < line->line_seqno))
+ return HTE_CB_HANDLED;
+
+ diff_seqno = ts->seq - line->line_seqno;
+ line->line_seqno = ts->seq;
+ if (lr->num_lines != 1)
+ line->req_seqno = atomic_add_return(diff_seqno,
+ &lr->seqno);
+
+ return HTE_RUN_SECOND_CB;
+ }
+
+ return HTE_CB_HANDLED;
+}
+
+static int hte_edge_setup(struct line *line, u64 eflags)
+{
+ int ret;
+ unsigned long flags = 0;
+ struct hte_ts_desc *hdesc = &line->hdesc;
+
+ if (eflags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+ flags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ HTE_FALLING_EDGE_TS :
+ HTE_RISING_EDGE_TS;
+ if (eflags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+ flags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ HTE_RISING_EDGE_TS :
+ HTE_FALLING_EDGE_TS;
+
+ line->total_discard_seq = 0;
+
+ hte_init_line_attr(hdesc, desc_to_gpio(line->desc), flags, NULL,
+ line->desc);
+
+ ret = hte_ts_get(NULL, hdesc, 0);
+ if (ret)
+ return ret;
+
+ return hte_request_ts_ns(hdesc, process_hw_ts, process_hw_ts_thread,
+ line);
+}
+
+#else
+
+static int hte_edge_setup(struct line *line, u64 eflags)
+{
+ return 0;
+}
+#endif /* CONFIG_HTE */
+
static irqreturn_t edge_irq_thread(int irq, void *p)
{
struct line *line = p;
struct linereq *lr = line->req;
struct gpio_v2_line_event le;
- u64 eflags;
/* Do not leak kernel stack to userspace */
memset(&le, 0, sizeof(le));
@@ -570,29 +753,23 @@ static irqreturn_t edge_irq_thread(int irq, void *p)
}
line->timestamp_ns = 0;
- eflags = READ_ONCE(line->eflags);
- if (eflags == GPIO_V2_LINE_FLAG_EDGE_BOTH) {
- int level = gpiod_get_value_cansleep(line->desc);
-
- if (level)
- /* Emit low-to-high event */
- le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
- else
- /* Emit high-to-low event */
- le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
- } else if (eflags == GPIO_V2_LINE_FLAG_EDGE_RISING) {
- /* Emit low-to-high event */
+ switch (READ_ONCE(line->edflags) & GPIO_V2_LINE_EDGE_FLAGS) {
+ case GPIO_V2_LINE_FLAG_EDGE_BOTH:
+ le.id = line_event_id(gpiod_get_value_cansleep(line->desc));
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_RISING:
le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
- } else if (eflags == GPIO_V2_LINE_FLAG_EDGE_FALLING) {
- /* Emit high-to-low event */
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_FALLING:
le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
- } else {
+ break;
+ default:
return IRQ_NONE;
}
line->line_seqno++;
le.line_seqno = line->line_seqno;
le.seqno = (lr->num_lines == 1) ? le.line_seqno : line->req_seqno;
- le.offset = gpio_chip_hwgpio(line->desc);
+ le.offset = gpiod_hwgpio(line->desc);
linereq_put_event(lr, &le);
@@ -630,7 +807,7 @@ static bool debounced_value(struct line *line)
*/
value = READ_ONCE(line->level);
- if (test_bit(FLAG_ACTIVE_LOW, &line->desc->flags))
+ if (test_bit(GPIOD_FLAG_ACTIVE_LOW, &line->desc->flags))
value = !value;
return value;
@@ -640,7 +817,7 @@ static irqreturn_t debounce_irq_handler(int irq, void *p)
{
struct line *line = p;
- mod_delayed_work(system_wq, &line->work,
+ mod_delayed_work(system_percpu_wq, &line->work,
usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
return IRQ_HANDLED;
@@ -651,10 +828,16 @@ static void debounce_work_func(struct work_struct *work)
struct gpio_v2_line_event le;
struct line *line = container_of(work, struct line, work.work);
struct linereq *lr;
- int level;
- u64 eflags;
+ u64 eflags, edflags = READ_ONCE(line->edflags);
+ int level = -1;
+#ifdef CONFIG_HTE
+ int diff_seqno;
- level = gpiod_get_raw_value_cansleep(line->desc);
+ if (edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE)
+ level = line->raw_level;
+#endif
+ if (level < 0)
+ level = gpiod_get_raw_value_cansleep(line->desc);
if (level < 0) {
pr_debug_ratelimited("debouncer failed to read line value\n");
return;
@@ -666,12 +849,12 @@ static void debounce_work_func(struct work_struct *work)
WRITE_ONCE(line->level, level);
/* -- edge detection -- */
- eflags = READ_ONCE(line->eflags);
+ eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS;
if (!eflags)
return;
/* switch from physical level to logical - if they differ */
- if (test_bit(FLAG_ACTIVE_LOW, &line->desc->flags))
+ if (edflags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
level = !level;
/* ignore edges that are not being monitored */
@@ -684,34 +867,44 @@ static void debounce_work_func(struct work_struct *work)
lr = line->req;
le.timestamp_ns = line_event_timestamp(line);
- le.offset = gpio_chip_hwgpio(line->desc);
- line->line_seqno++;
- le.line_seqno = line->line_seqno;
- le.seqno = (lr->num_lines == 1) ?
- le.line_seqno : atomic_inc_return(&lr->seqno);
+ le.offset = gpiod_hwgpio(line->desc);
+#ifdef CONFIG_HTE
+ if (edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE) {
+ /* discard events except the last one */
+ line->total_discard_seq -= 1;
+ diff_seqno = line->last_seqno - line->total_discard_seq -
+ line->line_seqno;
+ line->line_seqno = line->last_seqno - line->total_discard_seq;
+ le.line_seqno = line->line_seqno;
+ le.seqno = (lr->num_lines == 1) ?
+ le.line_seqno : atomic_add_return(diff_seqno, &lr->seqno);
+ } else
+#endif /* CONFIG_HTE */
+ {
+ line->line_seqno++;
+ le.line_seqno = line->line_seqno;
+ le.seqno = (lr->num_lines == 1) ?
+ le.line_seqno : atomic_inc_return(&lr->seqno);
+ }
- if (level)
- /* Emit low-to-high event */
- le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
- else
- /* Emit high-to-low event */
- le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
+ le.id = line_event_id(level);
linereq_put_event(lr, &le);
}
-static int debounce_setup(struct line *line,
- unsigned int debounce_period_us)
+static int debounce_setup(struct line *line, unsigned int debounce_period_us)
{
unsigned long irqflags;
int ret, level, irq;
+ char *label;
- /* try hardware */
- ret = gpiod_set_debounce(line->desc, debounce_period_us);
- if (!ret) {
- WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
- return ret;
- }
+ /*
+ * Try hardware. Skip gpiod_set_config() to avoid emitting two
+ * CHANGED_CONFIG line state events.
+ */
+ ret = gpio_do_set_config(line->desc,
+ pinconf_to_config_packed(PIN_CONFIG_INPUT_DEBOUNCE,
+ debounce_period_us));
if (ret != -ENOTSUPP)
return ret;
@@ -721,19 +914,32 @@ static int debounce_setup(struct line *line,
if (level < 0)
return level;
- irq = gpiod_to_irq(line->desc);
- if (irq < 0)
- return -ENXIO;
+ if (!(IS_ENABLED(CONFIG_HTE) &&
+ test_bit(GPIOD_FLAG_EVENT_CLOCK_HTE, &line->desc->flags))) {
+ irq = gpiod_to_irq(line->desc);
+ if (irq < 0)
+ return -ENXIO;
- WRITE_ONCE(line->level, level);
- irqflags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
- ret = request_irq(irq, debounce_irq_handler, irqflags,
- line->req->label, line);
- if (ret)
- return ret;
+ label = make_irq_label(line->req->label);
+ if (IS_ERR(label))
+ return -ENOMEM;
+
+ irqflags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
+ ret = request_irq(irq, debounce_irq_handler, irqflags,
+ label, line);
+ if (ret) {
+ free_irq_label(label);
+ return ret;
+ }
+ line->irq = irq;
+ } else {
+ ret = hte_edge_setup(line, GPIO_V2_LINE_FLAG_EDGE_BOTH);
+ if (ret)
+ return ret;
+ }
+ WRITE_ONCE(line->level, level);
WRITE_ONCE(line->sw_debounced, 1);
- line->irq = irq;
}
return 0;
}
@@ -769,34 +975,47 @@ static u32 gpio_v2_line_config_debounce_period(struct gpio_v2_line_config *lc,
static void edge_detector_stop(struct line *line)
{
if (line->irq) {
- free_irq(line->irq, line);
+ free_irq_label(free_irq(line->irq, line));
line->irq = 0;
}
+#ifdef CONFIG_HTE
+ if (READ_ONCE(line->edflags) & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE)
+ hte_ts_put(&line->hdesc);
+#endif
+
cancel_delayed_work_sync(&line->work);
WRITE_ONCE(line->sw_debounced, 0);
- WRITE_ONCE(line->eflags, 0);
+ WRITE_ONCE(line->edflags, 0);
if (line->desc)
WRITE_ONCE(line->desc->debounce_period_us, 0);
/* do not change line->level - see comment in debounced_value() */
}
+static int edge_detector_fifo_init(struct linereq *req)
+{
+ if (kfifo_initialized(&req->events))
+ return 0;
+
+ return kfifo_alloc(&req->events, req->event_buffer_size, GFP_KERNEL);
+}
+
static int edge_detector_setup(struct line *line,
struct gpio_v2_line_config *lc,
- unsigned int line_idx,
- u64 eflags)
+ unsigned int line_idx, u64 edflags)
{
u32 debounce_period_us;
unsigned long irqflags = 0;
+ u64 eflags;
int irq, ret;
+ char *label;
- if (eflags && !kfifo_initialized(&line->req->events)) {
- ret = kfifo_alloc(&line->req->events,
- line->req->event_buffer_size, GFP_KERNEL);
+ eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS;
+ if (eflags) {
+ ret = edge_detector_fifo_init(line->req);
if (ret)
return ret;
}
- WRITE_ONCE(line->eflags, eflags);
if (gpio_v2_line_config_debounced(lc, line_idx)) {
debounce_period_us = gpio_v2_line_config_debounce_period(lc, line_idx);
ret = debounce_setup(line, debounce_period_us);
@@ -809,23 +1028,33 @@ static int edge_detector_setup(struct line *line,
if (!eflags || READ_ONCE(line->sw_debounced))
return 0;
+ if (IS_ENABLED(CONFIG_HTE) &&
+ (edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE))
+ return hte_edge_setup(line, edflags);
+
irq = gpiod_to_irq(line->desc);
if (irq < 0)
return -ENXIO;
if (eflags & GPIO_V2_LINE_FLAG_EDGE_RISING)
- irqflags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ irqflags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &line->desc->flags) ?
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
if (eflags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
- irqflags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ irqflags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &line->desc->flags) ?
IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
irqflags |= IRQF_ONESHOT;
+ label = make_irq_label(line->req->label);
+ if (IS_ERR(label))
+ return PTR_ERR(label);
+
/* Request a thread to read the events */
ret = request_threaded_irq(irq, edge_irq_handler, edge_irq_thread,
- irqflags, line->req->label, line);
- if (ret)
+ irqflags, label, line);
+ if (ret) {
+ free_irq_label(label);
return ret;
+ }
line->irq = irq;
return 0;
@@ -833,29 +1062,36 @@ static int edge_detector_setup(struct line *line,
static int edge_detector_update(struct line *line,
struct gpio_v2_line_config *lc,
- unsigned int line_idx,
- u64 eflags, bool polarity_change)
+ unsigned int line_idx, u64 edflags)
{
+ u64 active_edflags = READ_ONCE(line->edflags);
unsigned int debounce_period_us =
- gpio_v2_line_config_debounce_period(lc, line_idx);
+ gpio_v2_line_config_debounce_period(lc, line_idx);
- if ((READ_ONCE(line->eflags) == eflags) && !polarity_change &&
+ if ((active_edflags == edflags) &&
(READ_ONCE(line->desc->debounce_period_us) == debounce_period_us))
return 0;
/* sw debounced and still will be...*/
if (debounce_period_us && READ_ONCE(line->sw_debounced)) {
- WRITE_ONCE(line->eflags, eflags);
WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
+ /*
+ * ensure event fifo is initialised if edge detection
+ * is now enabled.
+ */
+ if (edflags & GPIO_V2_LINE_EDGE_FLAGS)
+ return edge_detector_fifo_init(line->req);
+
return 0;
}
/* reconfiguring edge detection or sw debounce being disabled */
if ((line->irq && !READ_ONCE(line->sw_debounced)) ||
+ (active_edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE) ||
(!debounce_period_us && READ_ONCE(line->sw_debounced)))
edge_detector_stop(line);
- return edge_detector_setup(line, lc, line_idx, eflags);
+ return edge_detector_setup(line, lc, line_idx, edflags);
}
static u64 gpio_v2_line_config_flags(struct gpio_v2_line_config *lc,
@@ -892,6 +1128,10 @@ static int gpio_v2_line_flags_validate(u64 flags)
if (flags & ~GPIO_V2_LINE_VALID_FLAGS)
return -EINVAL;
+ if (!IS_ENABLED(CONFIG_HTE) &&
+ (flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE))
+ return -EOPNOTSUPP;
+
/*
* Do not allow both INPUT and OUTPUT flags to be set as they are
* contradictory.
@@ -900,6 +1140,12 @@ static int gpio_v2_line_flags_validate(u64 flags)
(flags & GPIO_V2_LINE_FLAG_OUTPUT))
return -EINVAL;
+ /* Only allow one event clock source */
+ if (IS_ENABLED(CONFIG_HTE) &&
+ (flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME) &&
+ (flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE))
+ return -EINVAL;
+
/* Edge detection requires explicit input. */
if ((flags & GPIO_V2_LINE_EDGE_FLAGS) &&
!(flags & GPIO_V2_LINE_FLAG_INPUT))
@@ -945,7 +1191,7 @@ static int gpio_v2_line_config_validate(struct gpio_v2_line_config *lc,
if (lc->num_attrs > GPIO_V2_LINE_NUM_ATTRS_MAX)
return -EINVAL;
- if (memchr_inv(lc->padding, 0, sizeof(lc->padding)))
+ if (!mem_is_zero(lc->padding, sizeof(lc->padding)))
return -EINVAL;
for (i = 0; i < num_lines; i++) {
@@ -962,36 +1208,42 @@ static int gpio_v2_line_config_validate(struct gpio_v2_line_config *lc,
return 0;
}
-static void gpio_v2_line_config_flags_to_desc_flags(u64 flags,
+static void gpio_v2_line_config_flags_to_desc_flags(u64 lflags,
unsigned long *flagsp)
{
- assign_bit(FLAG_ACTIVE_LOW, flagsp,
- flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
+ unsigned long flags = READ_ONCE(*flagsp);
- if (flags & GPIO_V2_LINE_FLAG_OUTPUT)
- set_bit(FLAG_IS_OUT, flagsp);
- else if (flags & GPIO_V2_LINE_FLAG_INPUT)
- clear_bit(FLAG_IS_OUT, flagsp);
+ assign_bit(GPIOD_FLAG_ACTIVE_LOW, &flags,
+ lflags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
- assign_bit(FLAG_EDGE_RISING, flagsp,
- flags & GPIO_V2_LINE_FLAG_EDGE_RISING);
- assign_bit(FLAG_EDGE_FALLING, flagsp,
- flags & GPIO_V2_LINE_FLAG_EDGE_FALLING);
+ if (lflags & GPIO_V2_LINE_FLAG_OUTPUT)
+ set_bit(GPIOD_FLAG_IS_OUT, &flags);
+ else if (lflags & GPIO_V2_LINE_FLAG_INPUT)
+ clear_bit(GPIOD_FLAG_IS_OUT, &flags);
- assign_bit(FLAG_OPEN_DRAIN, flagsp,
- flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN);
- assign_bit(FLAG_OPEN_SOURCE, flagsp,
- flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE);
+ assign_bit(GPIOD_FLAG_EDGE_RISING, &flags,
+ lflags & GPIO_V2_LINE_FLAG_EDGE_RISING);
+ assign_bit(GPIOD_FLAG_EDGE_FALLING, &flags,
+ lflags & GPIO_V2_LINE_FLAG_EDGE_FALLING);
- assign_bit(FLAG_PULL_UP, flagsp,
- flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP);
- assign_bit(FLAG_PULL_DOWN, flagsp,
- flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN);
- assign_bit(FLAG_BIAS_DISABLE, flagsp,
- flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED);
+ assign_bit(GPIOD_FLAG_OPEN_DRAIN, &flags,
+ lflags & GPIO_V2_LINE_FLAG_OPEN_DRAIN);
+ assign_bit(GPIOD_FLAG_OPEN_SOURCE, &flags,
+ lflags & GPIO_V2_LINE_FLAG_OPEN_SOURCE);
- assign_bit(FLAG_EVENT_CLOCK_REALTIME, flagsp,
- flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
+ assign_bit(GPIOD_FLAG_PULL_UP, &flags,
+ lflags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP);
+ assign_bit(GPIOD_FLAG_PULL_DOWN, &flags,
+ lflags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN);
+ assign_bit(GPIOD_FLAG_BIAS_DISABLE, &flags,
+ lflags & GPIO_V2_LINE_FLAG_BIAS_DISABLED);
+
+ assign_bit(GPIOD_FLAG_EVENT_CLOCK_REALTIME, &flags,
+ lflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
+ assign_bit(GPIOD_FLAG_EVENT_CLOCK_HTE, &flags,
+ lflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
+
+ WRITE_ONCE(*flagsp, flags);
}
static long linereq_get_values(struct linereq *lr, void __user *ip)
@@ -1007,9 +1259,18 @@ static long linereq_get_values(struct linereq *lr, void __user *ip)
if (copy_from_user(&lv, ip, sizeof(lv)))
return -EFAULT;
+ /*
+ * gpiod_get_array_value_complex() requires compacted desc and val
+ * arrays, rather than the sparse ones in lv.
+ * Calculation of num_get and construction of the desc array is
+ * optimized to avoid allocation for the desc array for the common
+ * num_get == 1 case.
+ */
+ /* scan requested lines to calculate the subset to get */
for (num_get = 0, i = 0; i < lr->num_lines; i++) {
if (lv.mask & BIT_ULL(i)) {
num_get++;
+ /* capture desc for the num_get == 1 case */
descs = &lr->lines[i].desc;
}
}
@@ -1018,6 +1279,7 @@ static long linereq_get_values(struct linereq *lr, void __user *ip)
return -EINVAL;
if (num_get != 1) {
+ /* build compacted desc array */
descs = kmalloc_array(num_get, sizeof(*descs), GFP_KERNEL);
if (!descs)
return -ENOMEM;
@@ -1038,6 +1300,7 @@ static long linereq_get_values(struct linereq *lr, void __user *ip)
lv.bits = 0;
for (didx = 0, i = 0; i < lr->num_lines; i++) {
+ /* unpack compacted vals for the response */
if (lv.mask & BIT_ULL(i)) {
if (lr->lines[i].sw_debounced)
val = debounced_value(&lr->lines[i]);
@@ -1055,22 +1318,35 @@ static long linereq_get_values(struct linereq *lr, void __user *ip)
return 0;
}
-static long linereq_set_values_unlocked(struct linereq *lr,
- struct gpio_v2_line_values *lv)
+static long linereq_set_values(struct linereq *lr, void __user *ip)
{
DECLARE_BITMAP(vals, GPIO_V2_LINES_MAX);
+ struct gpio_v2_line_values lv;
struct gpio_desc **descs;
unsigned int i, didx, num_set;
int ret;
+ if (copy_from_user(&lv, ip, sizeof(lv)))
+ return -EFAULT;
+
+ guard(mutex)(&lr->config_mutex);
+
+ /*
+ * gpiod_set_array_value_complex() requires compacted desc and val
+ * arrays, rather than the sparse ones in lv.
+ * Calculation of num_set and construction of the descs and vals arrays
+ * is optimized to minimize scanning the lv->mask, and to avoid
+ * allocation for the desc array for the common num_set == 1 case.
+ */
bitmap_zero(vals, GPIO_V2_LINES_MAX);
+ /* scan requested lines to determine the subset to be set */
for (num_set = 0, i = 0; i < lr->num_lines; i++) {
- if (lv->mask & BIT_ULL(i)) {
- if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags))
- return -EPERM;
- if (lv->bits & BIT_ULL(i))
+ if (lv.mask & BIT_ULL(i)) {
+ /* add to compacted values */
+ if (lv.bits & BIT_ULL(i))
__set_bit(num_set, vals);
num_set++;
+ /* capture desc for the num_set == 1 case */
descs = &lr->lines[i].desc;
}
}
@@ -1078,12 +1354,12 @@ static long linereq_set_values_unlocked(struct linereq *lr,
return -EINVAL;
if (num_set != 1) {
- /* build compacted desc array and values */
+ /* build compacted desc array */
descs = kmalloc_array(num_set, sizeof(*descs), GFP_KERNEL);
if (!descs)
return -ENOMEM;
for (didx = 0, i = 0; i < lr->num_lines; i++) {
- if (lv->mask & BIT_ULL(i)) {
+ if (lv.mask & BIT_ULL(i)) {
descs[didx] = lr->lines[i].desc;
didx++;
}
@@ -1097,105 +1373,81 @@ static long linereq_set_values_unlocked(struct linereq *lr,
return ret;
}
-static long linereq_set_values(struct linereq *lr, void __user *ip)
+static long linereq_set_config(struct linereq *lr, void __user *ip)
{
- struct gpio_v2_line_values lv;
+ struct gpio_v2_line_config lc;
+ struct gpio_desc *desc;
+ struct line *line;
+ unsigned int i;
+ u64 flags, edflags;
int ret;
- if (copy_from_user(&lv, ip, sizeof(lv)))
+ if (copy_from_user(&lc, ip, sizeof(lc)))
return -EFAULT;
- mutex_lock(&lr->config_mutex);
-
- ret = linereq_set_values_unlocked(lr, &lv);
-
- mutex_unlock(&lr->config_mutex);
+ ret = gpio_v2_line_config_validate(&lc, lr->num_lines);
+ if (ret)
+ return ret;
- return ret;
-}
-
-static long linereq_set_config_unlocked(struct linereq *lr,
- struct gpio_v2_line_config *lc)
-{
- struct gpio_desc *desc;
- unsigned int i;
- u64 flags;
- bool polarity_change;
- int ret;
+ guard(mutex)(&lr->config_mutex);
for (i = 0; i < lr->num_lines; i++) {
+ line = &lr->lines[i];
desc = lr->lines[i].desc;
- flags = gpio_v2_line_config_flags(lc, i);
- polarity_change =
- (!!test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=
- ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));
-
- gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);
+ flags = gpio_v2_line_config_flags(&lc, i);
/*
- * Lines have to be requested explicitly for input
- * or output, else the line will be treated "as is".
+ * Lines not explicitly reconfigured as input or output
+ * are left unchanged.
*/
+ if (!(flags & GPIO_V2_LINE_DIRECTION_FLAGS))
+ continue;
+ gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);
+ edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS;
if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
- int val = gpio_v2_line_config_output_value(lc, i);
+ int val = gpio_v2_line_config_output_value(&lc, i);
- edge_detector_stop(&lr->lines[i]);
- ret = gpiod_direction_output(desc, val);
+ edge_detector_stop(line);
+ ret = gpiod_direction_output_nonotify(desc, val);
if (ret)
return ret;
- } else if (flags & GPIO_V2_LINE_FLAG_INPUT) {
- ret = gpiod_direction_input(desc);
+ } else {
+ ret = gpiod_direction_input_nonotify(desc);
if (ret)
return ret;
- ret = edge_detector_update(&lr->lines[i], lc, i,
- flags & GPIO_V2_LINE_EDGE_FLAGS,
- polarity_change);
+ ret = edge_detector_update(line, &lc, i, edflags);
if (ret)
return ret;
}
- blocking_notifier_call_chain(&desc->gdev->notifier,
- GPIO_V2_LINE_CHANGED_CONFIG,
- desc);
+ WRITE_ONCE(line->edflags, edflags);
+
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
}
return 0;
}
-static long linereq_set_config(struct linereq *lr, void __user *ip)
-{
- struct gpio_v2_line_config lc;
- int ret;
-
- if (copy_from_user(&lc, ip, sizeof(lc)))
- return -EFAULT;
-
- ret = gpio_v2_line_config_validate(&lc, lr->num_lines);
- if (ret)
- return ret;
-
- mutex_lock(&lr->config_mutex);
-
- ret = linereq_set_config_unlocked(lr, &lc);
-
- mutex_unlock(&lr->config_mutex);
-
- return ret;
-}
-
static long linereq_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct linereq *lr = file->private_data;
void __user *ip = (void __user *)arg;
- if (cmd == GPIO_V2_LINE_GET_VALUES_IOCTL)
+ guard(srcu)(&lr->gdev->srcu);
+
+ if (!rcu_access_pointer(lr->gdev->chip))
+ return -ENODEV;
+
+ switch (cmd) {
+ case GPIO_V2_LINE_GET_VALUES_IOCTL:
return linereq_get_values(lr, ip);
- else if (cmd == GPIO_V2_LINE_SET_VALUES_IOCTL)
+ case GPIO_V2_LINE_SET_VALUES_IOCTL:
return linereq_set_values(lr, ip);
- else if (cmd == GPIO_V2_LINE_SET_CONFIG_IOCTL)
+ case GPIO_V2_LINE_SET_CONFIG_IOCTL:
return linereq_set_config(lr, ip);
-
- return -EINVAL;
+ default:
+ return -EINVAL;
+ }
}
#ifdef CONFIG_COMPAT
@@ -1207,11 +1459,16 @@ static long linereq_ioctl_compat(struct file *file, unsigned int cmd,
#endif
static __poll_t linereq_poll(struct file *file,
- struct poll_table_struct *wait)
+ struct poll_table_struct *wait)
{
struct linereq *lr = file->private_data;
__poll_t events = 0;
+ guard(srcu)(&lr->gdev->srcu);
+
+ if (!rcu_access_pointer(lr->gdev->chip))
+ return EPOLLHUP | EPOLLERR;
+
poll_wait(file, &lr->wait, wait);
if (!kfifo_is_empty_spinlocked_noirqsave(&lr->events,
@@ -1221,52 +1478,48 @@ static __poll_t linereq_poll(struct file *file,
return events;
}
-static ssize_t linereq_read(struct file *file,
- char __user *buf,
- size_t count,
- loff_t *f_ps)
+static ssize_t linereq_read(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
{
struct linereq *lr = file->private_data;
struct gpio_v2_line_event le;
ssize_t bytes_read = 0;
int ret;
+ guard(srcu)(&lr->gdev->srcu);
+
+ if (!rcu_access_pointer(lr->gdev->chip))
+ return -ENODEV;
+
if (count < sizeof(le))
return -EINVAL;
do {
- spin_lock(&lr->wait.lock);
- if (kfifo_is_empty(&lr->events)) {
- if (bytes_read) {
- spin_unlock(&lr->wait.lock);
- return bytes_read;
+ scoped_guard(spinlock, &lr->wait.lock) {
+ if (kfifo_is_empty(&lr->events)) {
+ if (bytes_read)
+ return bytes_read;
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible_locked(lr->wait,
+ !kfifo_is_empty(&lr->events));
+ if (ret)
+ return ret;
}
- if (file->f_flags & O_NONBLOCK) {
- spin_unlock(&lr->wait.lock);
- return -EAGAIN;
- }
-
- ret = wait_event_interruptible_locked(lr->wait,
- !kfifo_is_empty(&lr->events));
- if (ret) {
- spin_unlock(&lr->wait.lock);
- return ret;
+ if (kfifo_out(&lr->events, &le, 1) != 1) {
+ /*
+ * This should never happen - we hold the
+ * lock from the moment we learned the fifo
+ * is no longer empty until now.
+ */
+ WARN(1, "failed to read from non-empty kfifo");
+ return -EIO;
}
}
- ret = kfifo_out(&lr->events, &le, 1);
- spin_unlock(&lr->wait.lock);
- if (ret != 1) {
- /*
- * This should never happen - we were holding the
- * lock from the moment we learned the fifo is no
- * longer empty until now.
- */
- ret = -EIO;
- break;
- }
-
if (copy_to_user(buf + bytes_read, &le, sizeof(le)))
return -EFAULT;
bytes_read += sizeof(le);
@@ -1279,15 +1532,20 @@ static void linereq_free(struct linereq *lr)
{
unsigned int i;
+ if (lr->device_unregistered_nb.notifier_call)
+ blocking_notifier_chain_unregister(&lr->gdev->device_notifier,
+ &lr->device_unregistered_nb);
+
for (i = 0; i < lr->num_lines; i++) {
- edge_detector_stop(&lr->lines[i]);
- if (lr->lines[i].desc)
+ if (lr->lines[i].desc) {
+ edge_detector_stop(&lr->lines[i]);
gpiod_free(lr->lines[i].desc);
+ }
}
kfifo_free(&lr->events);
kfree(lr->label);
- put_device(&lr->gdev->dev);
- kfree(lr);
+ gpio_device_put(lr->gdev);
+ kvfree(lr);
}
static int linereq_release(struct inode *inode, struct file *file)
@@ -1298,6 +1556,21 @@ static int linereq_release(struct inode *inode, struct file *file)
return 0;
}
+#ifdef CONFIG_PROC_FS
+static void linereq_show_fdinfo(struct seq_file *out, struct file *file)
+{
+ struct linereq *lr = file->private_data;
+ struct device *dev = &lr->gdev->dev;
+ u16 i;
+
+ seq_printf(out, "gpio-chip:\t%s\n", dev_name(dev));
+
+ for (i = 0; i < lr->num_lines; i++)
+ seq_printf(out, "gpio-line:\t%d\n",
+ gpiod_hwgpio(lr->lines[i].desc));
+}
+#endif
+
static const struct file_operations line_fileops = {
.release = linereq_release,
.read = linereq_read,
@@ -1308,6 +1581,9 @@ static const struct file_operations line_fileops = {
#ifdef CONFIG_COMPAT
.compat_ioctl = linereq_ioctl_compat,
#endif
+#ifdef CONFIG_PROC_FS
+ .show_fdinfo = linereq_show_fdinfo,
+#endif
};
static int linereq_create(struct gpio_device *gdev, void __user *ip)
@@ -1316,7 +1592,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
struct gpio_v2_line_config *lc;
struct linereq *lr;
struct file *file;
- u64 flags;
+ u64 flags, edflags;
unsigned int i;
int fd, ret;
@@ -1326,7 +1602,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
if ((ulr.num_lines == 0) || (ulr.num_lines > GPIO_V2_LINES_MAX))
return -EINVAL;
- if (memchr_inv(ulr.padding, 0, sizeof(ulr.padding)))
+ if (!mem_is_zero(ulr.padding, sizeof(ulr.padding)))
return -EINVAL;
lc = &ulr.config;
@@ -1334,12 +1610,12 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
if (ret)
return ret;
- lr = kzalloc(struct_size(lr, lines, ulr.num_lines), GFP_KERNEL);
+ lr = kvzalloc(struct_size(lr, lines, ulr.num_lines), GFP_KERNEL);
if (!lr)
return -ENOMEM;
+ lr->num_lines = ulr.num_lines;
- lr->gdev = gdev;
- get_device(&gdev->dev);
+ lr->gdev = gpio_device_get(gdev);
for (i = 0; i < ulr.num_lines; i++) {
lr->lines[i].req = lr;
@@ -1359,6 +1635,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
mutex_init(&lr->config_mutex);
init_waitqueue_head(&lr->wait);
+ INIT_KFIFO(lr->events);
lr->event_buffer_size = ulr.event_buffer_size;
if (lr->event_buffer_size == 0)
lr->event_buffer_size = ulr.num_lines * 16;
@@ -1366,19 +1643,18 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
lr->event_buffer_size = GPIO_V2_LINES_MAX * 16;
atomic_set(&lr->seqno, 0);
- lr->num_lines = ulr.num_lines;
/* Request each GPIO */
for (i = 0; i < ulr.num_lines; i++) {
u32 offset = ulr.offsets[i];
- struct gpio_desc *desc = gpiochip_get_desc(gdev->chip, offset);
+ struct gpio_desc *desc = gpio_device_get_desc(gdev, offset);
if (IS_ERR(desc)) {
ret = PTR_ERR(desc);
goto out_free_linereq;
}
- ret = gpiod_request(desc, lr->label);
+ ret = gpiod_request_user(desc, lr->label);
if (ret)
goto out_free_linereq;
@@ -1390,6 +1666,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
if (ret < 0)
goto out_free_linereq;
+ edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS;
/*
* Lines have to be requested explicitly for input
* or output, else the line will be treated "as is".
@@ -1397,27 +1674,34 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
int val = gpio_v2_line_config_output_value(lc, i);
- ret = gpiod_direction_output(desc, val);
+ ret = gpiod_direction_output_nonotify(desc, val);
if (ret)
goto out_free_linereq;
} else if (flags & GPIO_V2_LINE_FLAG_INPUT) {
- ret = gpiod_direction_input(desc);
+ ret = gpiod_direction_input_nonotify(desc);
if (ret)
goto out_free_linereq;
ret = edge_detector_setup(&lr->lines[i], lc, i,
- flags & GPIO_V2_LINE_EDGE_FLAGS);
+ edflags);
if (ret)
goto out_free_linereq;
}
- blocking_notifier_call_chain(&desc->gdev->notifier,
- GPIO_V2_LINE_CHANGED_REQUESTED, desc);
+ lr->lines[i].edflags = edflags;
+
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
dev_dbg(&gdev->dev, "registered chardev handle for line %d\n",
offset);
}
+ lr->device_unregistered_nb.notifier_call = linereq_unregistered_notify;
+ ret = blocking_notifier_chain_register(&gdev->device_notifier,
+ &lr->device_unregistered_nb);
+ if (ret)
+ goto out_free_linereq;
+
fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ret = fd;
@@ -1470,6 +1754,7 @@ out_free_linereq:
* @eflags: the event flags this line was requested with
* @irq: the interrupt that trigger in response to events on this GPIO
* @wait: wait queue that handles blocking reads of events
+ * @device_unregistered_nb: notifier block for receiving gdev unregister events
* @events: KFIFO for the GPIO events
* @timestamp: cache for the timestamp storing it between hardirq
* and IRQ thread, used to bring the timestamp close to the actual
@@ -1482,6 +1767,7 @@ struct lineevent_state {
u32 eflags;
int irq;
wait_queue_head_t wait;
+ struct notifier_block device_unregistered_nb;
DECLARE_KFIFO(events, struct gpioevent_data, 16);
u64 timestamp;
};
@@ -1496,6 +1782,11 @@ static __poll_t lineevent_poll(struct file *file,
struct lineevent_state *le = file->private_data;
__poll_t events = 0;
+ guard(srcu)(&le->gdev->srcu);
+
+ if (!rcu_access_pointer(le->gdev->chip))
+ return EPOLLHUP | EPOLLERR;
+
poll_wait(file, &le->wait, wait);
if (!kfifo_is_empty_spinlocked_noirqsave(&le->events, &le->wait.lock))
@@ -1504,15 +1795,24 @@ static __poll_t lineevent_poll(struct file *file,
return events;
}
+static int lineevent_unregistered_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct lineevent_state *le = container_of(nb, struct lineevent_state,
+ device_unregistered_nb);
+
+ wake_up_poll(&le->wait, EPOLLIN | EPOLLERR);
+
+ return NOTIFY_OK;
+}
+
struct compat_gpioeevent_data {
compat_u64 timestamp;
u32 id;
};
-static ssize_t lineevent_read(struct file *file,
- char __user *buf,
- size_t count,
- loff_t *f_ps)
+static ssize_t lineevent_read(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
{
struct lineevent_state *le = file->private_data;
struct gpioevent_data ge;
@@ -1520,6 +1820,11 @@ static ssize_t lineevent_read(struct file *file,
ssize_t ge_size;
int ret;
+ guard(srcu)(&le->gdev->srcu);
+
+ if (!rcu_access_pointer(le->gdev->chip))
+ return -ENODEV;
+
/*
* When compatible system call is being used the struct gpioevent_data,
* in case of at least ia32, has different size due to the alignment
@@ -1537,38 +1842,31 @@ static ssize_t lineevent_read(struct file *file,
return -EINVAL;
do {
- spin_lock(&le->wait.lock);
- if (kfifo_is_empty(&le->events)) {
- if (bytes_read) {
- spin_unlock(&le->wait.lock);
- return bytes_read;
+ scoped_guard(spinlock, &le->wait.lock) {
+ if (kfifo_is_empty(&le->events)) {
+ if (bytes_read)
+ return bytes_read;
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible_locked(le->wait,
+ !kfifo_is_empty(&le->events));
+ if (ret)
+ return ret;
}
- if (file->f_flags & O_NONBLOCK) {
- spin_unlock(&le->wait.lock);
- return -EAGAIN;
- }
-
- ret = wait_event_interruptible_locked(le->wait,
- !kfifo_is_empty(&le->events));
- if (ret) {
- spin_unlock(&le->wait.lock);
- return ret;
+ if (kfifo_out(&le->events, &ge, 1) != 1) {
+ /*
+ * This should never happen - we hold the
+ * lock from the moment we learned the fifo
+ * is no longer empty until now.
+ */
+ WARN(1, "failed to read from non-empty kfifo");
+ return -EIO;
}
}
- ret = kfifo_out(&le->events, &ge, 1);
- spin_unlock(&le->wait.lock);
- if (ret != 1) {
- /*
- * This should never happen - we were holding the lock
- * from the moment we learned the fifo is no longer
- * empty until now.
- */
- ret = -EIO;
- break;
- }
-
if (copy_to_user(buf + bytes_read, &ge, ge_size))
return -EFAULT;
bytes_read += ge_size;
@@ -1579,12 +1877,15 @@ static ssize_t lineevent_read(struct file *file,
static void lineevent_free(struct lineevent_state *le)
{
+ if (le->device_unregistered_nb.notifier_call)
+ blocking_notifier_chain_unregister(&le->gdev->device_notifier,
+ &le->device_unregistered_nb);
if (le->irq)
- free_irq(le->irq, le);
+ free_irq_label(free_irq(le->irq, le));
if (le->desc)
gpiod_free(le->desc);
kfree(le->label);
- put_device(&le->gdev->dev);
+ gpio_device_put(le->gdev);
kfree(le);
}
@@ -1601,6 +1902,11 @@ static long lineevent_ioctl(struct file *file, unsigned int cmd,
void __user *ip = (void __user *)arg;
struct gpiohandle_data ghd;
+ guard(srcu)(&le->gdev->srcu);
+
+ if (!rcu_access_pointer(le->gdev->chip))
+ return -ENODEV;
+
/*
* We can get the value for an event line but not set it,
* because it is input by definition.
@@ -1716,6 +2022,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
int fd;
int ret;
int irq, irqflags = 0;
+ char *label;
if (copy_from_user(&eventreq, ip, sizeof(eventreq)))
return -EFAULT;
@@ -1724,7 +2031,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
lflags = eventreq.handleflags;
eflags = eventreq.eventflags;
- desc = gpiochip_get_desc(gdev->chip, offset);
+ desc = gpio_device_get_desc(gdev, offset);
if (IS_ERR(desc))
return PTR_ERR(desc);
@@ -1750,8 +2057,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
le = kzalloc(sizeof(*le), GFP_KERNEL);
if (!le)
return -ENOMEM;
- le->gdev = gdev;
- get_device(&gdev->dev);
+ le->gdev = gpio_device_get(gdev);
if (eventreq.consumer_label[0] != '\0') {
/* label is only initialized if consumer_label is set */
@@ -1764,7 +2070,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
}
}
- ret = gpiod_request(desc, le->label);
+ ret = gpiod_request_user(desc, le->label);
if (ret)
goto out_free_le;
le->desc = desc;
@@ -1776,36 +2082,50 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
if (ret)
goto out_free_le;
- blocking_notifier_call_chain(&desc->gdev->notifier,
- GPIO_V2_LINE_CHANGED_REQUESTED, desc);
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
irq = gpiod_to_irq(desc);
if (irq <= 0) {
ret = -ENODEV;
goto out_free_le;
}
- le->irq = irq;
if (eflags & GPIOEVENT_REQUEST_RISING_EDGE)
- irqflags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
+ irqflags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &desc->flags) ?
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
if (eflags & GPIOEVENT_REQUEST_FALLING_EDGE)
- irqflags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
+ irqflags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &desc->flags) ?
IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
irqflags |= IRQF_ONESHOT;
INIT_KFIFO(le->events);
init_waitqueue_head(&le->wait);
+ le->device_unregistered_nb.notifier_call = lineevent_unregistered_notify;
+ ret = blocking_notifier_chain_register(&gdev->device_notifier,
+ &le->device_unregistered_nb);
+ if (ret)
+ goto out_free_le;
+
+ label = make_irq_label(le->label);
+ if (IS_ERR(label)) {
+ ret = PTR_ERR(label);
+ goto out_free_le;
+ }
+
/* Request a thread to read the events */
- ret = request_threaded_irq(le->irq,
+ ret = request_threaded_irq(irq,
lineevent_irq_handler,
lineevent_irq_thread,
irqflags,
- le->label,
+ label,
le);
- if (ret)
+ if (ret) {
+ free_irq_label(label);
goto out_free_le;
+ }
+
+ le->irq = irq;
fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
if (fd < 0) {
@@ -1889,86 +2209,91 @@ static void gpio_v2_line_info_changed_to_v1(
#endif /* CONFIG_GPIO_CDEV_V1 */
static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
- struct gpio_v2_line_info *info)
+ struct gpio_v2_line_info *info, bool atomic)
{
- struct gpio_chip *gc = desc->gdev->chip;
- bool ok_for_pinctrl;
- unsigned long flags;
u32 debounce_period_us;
- unsigned int num_attrs = 0;
-
- memset(info, 0, sizeof(*info));
- info->offset = gpio_chip_hwgpio(desc);
+ unsigned long dflags;
+ const char *label;
- /*
- * This function takes a mutex so we must check this before taking
- * the spinlock.
- *
- * FIXME: find a non-racy way to retrieve this information. Maybe a
- * lock common to both frameworks?
- */
- ok_for_pinctrl =
- pinctrl_gpio_can_use_line(gc->base + info->offset);
+ CLASS(gpio_chip_guard, guard)(desc);
+ if (!guard.gc)
+ return;
- spin_lock_irqsave(&gpio_lock, flags);
+ memset(info, 0, sizeof(*info));
+ info->offset = gpiod_hwgpio(desc);
if (desc->name)
strscpy(info->name, desc->name, sizeof(info->name));
- if (desc->label)
- strscpy(info->consumer, desc->label, sizeof(info->consumer));
+ dflags = READ_ONCE(desc->flags);
+
+ scoped_guard(srcu, &desc->gdev->desc_srcu) {
+ label = gpiod_get_label(desc);
+ if (label && test_bit(GPIOD_FLAG_REQUESTED, &dflags))
+ strscpy(info->consumer, label,
+ sizeof(info->consumer));
+ }
/*
- * Userspace only need to know that the kernel is using this GPIO so
- * it can't use it.
+ * Userspace only need know that the kernel is using this GPIO so it
+ * can't use it.
+ * The calculation of the used flag is slightly racy, as it may read
+ * desc, gc and pinctrl state without a lock covering all three at
+ * once. Worst case if the line is in transition and the calculation
+ * is inconsistent then it looks to the user like they performed the
+ * read on the other side of the transition - but that can always
+ * happen.
+ * The definitive test that a line is available to userspace is to
+ * request it.
*/
- info->flags = 0;
- if (test_bit(FLAG_REQUESTED, &desc->flags) ||
- test_bit(FLAG_IS_HOGGED, &desc->flags) ||
- test_bit(FLAG_USED_AS_IRQ, &desc->flags) ||
- test_bit(FLAG_EXPORT, &desc->flags) ||
- test_bit(FLAG_SYSFS, &desc->flags) ||
- !gpiochip_line_is_valid(gc, info->offset) ||
- !ok_for_pinctrl)
+ if (test_bit(GPIOD_FLAG_REQUESTED, &dflags) ||
+ test_bit(GPIOD_FLAG_IS_HOGGED, &dflags) ||
+ test_bit(GPIOD_FLAG_EXPORT, &dflags) ||
+ test_bit(GPIOD_FLAG_SYSFS, &dflags) ||
+ !gpiochip_line_is_valid(guard.gc, info->offset)) {
info->flags |= GPIO_V2_LINE_FLAG_USED;
+ } else if (!atomic) {
+ if (!pinctrl_gpio_can_use_line(guard.gc, info->offset))
+ info->flags |= GPIO_V2_LINE_FLAG_USED;
+ }
- if (test_bit(FLAG_IS_OUT, &desc->flags))
+ if (test_bit(GPIOD_FLAG_IS_OUT, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_OUTPUT;
else
info->flags |= GPIO_V2_LINE_FLAG_INPUT;
- if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ if (test_bit(GPIOD_FLAG_ACTIVE_LOW, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
- if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
+ if (test_bit(GPIOD_FLAG_OPEN_DRAIN, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
- if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
+ if (test_bit(GPIOD_FLAG_OPEN_SOURCE, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
- if (test_bit(FLAG_BIAS_DISABLE, &desc->flags))
+ if (test_bit(GPIOD_FLAG_BIAS_DISABLE, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
- if (test_bit(FLAG_PULL_DOWN, &desc->flags))
+ if (test_bit(GPIOD_FLAG_PULL_DOWN, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
- if (test_bit(FLAG_PULL_UP, &desc->flags))
+ if (test_bit(GPIOD_FLAG_PULL_UP, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
- if (test_bit(FLAG_EDGE_RISING, &desc->flags))
+ if (test_bit(GPIOD_FLAG_EDGE_RISING, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_EDGE_RISING;
- if (test_bit(FLAG_EDGE_FALLING, &desc->flags))
+ if (test_bit(GPIOD_FLAG_EDGE_FALLING, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING;
- if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &desc->flags))
+ if (test_bit(GPIOD_FLAG_EVENT_CLOCK_REALTIME, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
+ else if (test_bit(GPIOD_FLAG_EVENT_CLOCK_HTE, &dflags))
+ info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE;
debounce_period_us = READ_ONCE(desc->debounce_period_us);
if (debounce_period_us) {
- info->attrs[num_attrs].id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
- info->attrs[num_attrs].debounce_period_us = debounce_period_us;
- num_attrs++;
+ info->attrs[info->num_attrs].id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+ info->attrs[info->num_attrs].debounce_period_us =
+ debounce_period_us;
+ info->num_attrs++;
}
- info->num_attrs = num_attrs;
-
- spin_unlock_irqrestore(&gpio_lock, flags);
}
struct gpio_chardev_data {
@@ -1976,10 +2301,12 @@ struct gpio_chardev_data {
wait_queue_head_t wait;
DECLARE_KFIFO(events, struct gpio_v2_line_info_changed, 32);
struct notifier_block lineinfo_changed_nb;
+ struct notifier_block device_unregistered_nb;
unsigned long *watched_lines;
#ifdef CONFIG_GPIO_CDEV_V1
atomic_t watch_abi_version;
#endif
+ struct file *fp;
};
static int chipinfo_get(struct gpio_chardev_data *cdev, void __user *ip)
@@ -2023,7 +2350,7 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip,
return -EFAULT;
/* this doubles as a range check on line_offset */
- desc = gpiochip_get_desc(cdev->gdev->chip, lineinfo.line_offset);
+ desc = gpio_device_get_desc(cdev->gdev, lineinfo.line_offset);
if (IS_ERR(desc))
return PTR_ERR(desc);
@@ -2035,7 +2362,7 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip,
return -EBUSY;
}
- gpio_desc_to_lineinfo(desc, &lineinfo_v2);
+ gpio_desc_to_lineinfo(desc, &lineinfo_v2, false);
gpio_v2_line_info_to_v1(&lineinfo_v2, &lineinfo);
if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) {
@@ -2057,10 +2384,10 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip,
if (copy_from_user(&lineinfo, ip, sizeof(lineinfo)))
return -EFAULT;
- if (memchr_inv(lineinfo.padding, 0, sizeof(lineinfo.padding)))
+ if (!mem_is_zero(lineinfo.padding, sizeof(lineinfo.padding)))
return -EINVAL;
- desc = gpiochip_get_desc(cdev->gdev->chip, lineinfo.offset);
+ desc = gpio_device_get_desc(cdev->gdev, lineinfo.offset);
if (IS_ERR(desc))
return PTR_ERR(desc);
@@ -2072,7 +2399,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip,
if (test_and_set_bit(lineinfo.offset, cdev->watched_lines))
return -EBUSY;
}
- gpio_desc_to_lineinfo(desc, &lineinfo);
+ gpio_desc_to_lineinfo(desc, &lineinfo, false);
if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) {
if (watch)
@@ -2108,33 +2435,37 @@ static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
struct gpio_device *gdev = cdev->gdev;
void __user *ip = (void __user *)arg;
+ guard(srcu)(&gdev->srcu);
+
/* We fail any subsequent ioctl():s when the chip is gone */
- if (!gdev->chip)
+ if (!rcu_access_pointer(gdev->chip))
return -ENODEV;
/* Fill in the struct and pass to userspace */
- if (cmd == GPIO_GET_CHIPINFO_IOCTL) {
+ switch (cmd) {
+ case GPIO_GET_CHIPINFO_IOCTL:
return chipinfo_get(cdev, ip);
#ifdef CONFIG_GPIO_CDEV_V1
- } else if (cmd == GPIO_GET_LINEHANDLE_IOCTL) {
+ case GPIO_GET_LINEHANDLE_IOCTL:
return linehandle_create(gdev, ip);
- } else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
+ case GPIO_GET_LINEEVENT_IOCTL:
return lineevent_create(gdev, ip);
- } else if (cmd == GPIO_GET_LINEINFO_IOCTL ||
- cmd == GPIO_GET_LINEINFO_WATCH_IOCTL) {
- return lineinfo_get_v1(cdev, ip,
- cmd == GPIO_GET_LINEINFO_WATCH_IOCTL);
+ case GPIO_GET_LINEINFO_IOCTL:
+ return lineinfo_get_v1(cdev, ip, false);
+ case GPIO_GET_LINEINFO_WATCH_IOCTL:
+ return lineinfo_get_v1(cdev, ip, true);
#endif /* CONFIG_GPIO_CDEV_V1 */
- } else if (cmd == GPIO_V2_GET_LINEINFO_IOCTL ||
- cmd == GPIO_V2_GET_LINEINFO_WATCH_IOCTL) {
- return lineinfo_get(cdev, ip,
- cmd == GPIO_V2_GET_LINEINFO_WATCH_IOCTL);
- } else if (cmd == GPIO_V2_GET_LINE_IOCTL) {
+ case GPIO_V2_GET_LINEINFO_IOCTL:
+ return lineinfo_get(cdev, ip, false);
+ case GPIO_V2_GET_LINEINFO_WATCH_IOCTL:
+ return lineinfo_get(cdev, ip, true);
+ case GPIO_V2_GET_LINE_IOCTL:
return linereq_create(gdev, ip);
- } else if (cmd == GPIO_GET_LINEINFO_UNWATCH_IOCTL) {
+ case GPIO_GET_LINEINFO_UNWATCH_IOCTL:
return lineinfo_unwatch(cdev, ip);
+ default:
+ return -EINVAL;
}
- return -EINVAL;
}
#ifdef CONFIG_COMPAT
@@ -2145,33 +2476,103 @@ static long gpio_ioctl_compat(struct file *file, unsigned int cmd,
}
#endif
-static struct gpio_chardev_data *
-to_gpio_chardev_data(struct notifier_block *nb)
+struct lineinfo_changed_ctx {
+ struct work_struct work;
+ struct gpio_v2_line_info_changed chg;
+ struct gpio_device *gdev;
+ struct gpio_chardev_data *cdev;
+};
+
+static void lineinfo_changed_func(struct work_struct *work)
{
- return container_of(nb, struct gpio_chardev_data, lineinfo_changed_nb);
+ struct lineinfo_changed_ctx *ctx =
+ container_of(work, struct lineinfo_changed_ctx, work);
+ struct gpio_chip *gc;
+ int ret;
+
+ if (!(ctx->chg.info.flags & GPIO_V2_LINE_FLAG_USED)) {
+ /*
+ * If nobody set the USED flag earlier, let's see with pinctrl
+ * now. We're doing this late because it's a sleeping function.
+ * Pin functions are in general much more static and while it's
+ * not 100% bullet-proof, it's good enough for most cases.
+ */
+ scoped_guard(srcu, &ctx->gdev->srcu) {
+ gc = srcu_dereference(ctx->gdev->chip, &ctx->gdev->srcu);
+ if (gc &&
+ !pinctrl_gpio_can_use_line(gc, ctx->chg.info.offset))
+ ctx->chg.info.flags |= GPIO_V2_LINE_FLAG_USED;
+ }
+ }
+
+ ret = kfifo_in_spinlocked(&ctx->cdev->events, &ctx->chg, 1,
+ &ctx->cdev->wait.lock);
+ if (ret)
+ wake_up_poll(&ctx->cdev->wait, EPOLLIN);
+ else
+ pr_debug_ratelimited("lineinfo event FIFO is full - event dropped\n");
+
+ gpio_device_put(ctx->gdev);
+ fput(ctx->cdev->fp);
+ kfree(ctx);
}
static int lineinfo_changed_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
- struct gpio_chardev_data *cdev = to_gpio_chardev_data(nb);
- struct gpio_v2_line_info_changed chg;
+ struct gpio_chardev_data *cdev =
+ container_of(nb, struct gpio_chardev_data, lineinfo_changed_nb);
+ struct lineinfo_changed_ctx *ctx;
struct gpio_desc *desc = data;
- int ret;
+ struct file *fp;
- if (!test_bit(gpio_chip_hwgpio(desc), cdev->watched_lines))
+ if (!test_bit(gpiod_hwgpio(desc), cdev->watched_lines))
return NOTIFY_DONE;
- memset(&chg, 0, sizeof(chg));
- chg.event_type = action;
- chg.timestamp_ns = ktime_get_ns();
- gpio_desc_to_lineinfo(desc, &chg.info);
+ /* Keep the file descriptor alive for the duration of the notification. */
+ fp = get_file_active(&cdev->fp);
+ if (!fp)
+ /* Chardev file descriptor was or is being released. */
+ return NOTIFY_DONE;
- ret = kfifo_in_spinlocked(&cdev->events, &chg, 1, &cdev->wait.lock);
- if (ret)
- wake_up_poll(&cdev->wait, EPOLLIN);
- else
- pr_debug_ratelimited("lineinfo event FIFO is full - event dropped\n");
+ /*
+ * If this is called from atomic context (for instance: with a spinlock
+ * taken by the atomic notifier chain), any sleeping calls must be done
+ * outside of this function in process context of the dedicated
+ * workqueue.
+ *
+ * Let's gather as much info as possible from the descriptor and
+ * postpone just the call to pinctrl_gpio_can_use_line() until the work
+ * is executed.
+ */
+
+ ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC);
+ if (!ctx) {
+ pr_err("Failed to allocate memory for line info notification\n");
+ return NOTIFY_DONE;
+ }
+
+ ctx->chg.event_type = action;
+ ctx->chg.timestamp_ns = ktime_get_ns();
+ gpio_desc_to_lineinfo(desc, &ctx->chg.info, true);
+ /* Keep the GPIO device alive until we emit the event. */
+ ctx->gdev = gpio_device_get(desc->gdev);
+ ctx->cdev = cdev;
+
+ INIT_WORK(&ctx->work, lineinfo_changed_func);
+ queue_work(ctx->gdev->line_state_wq, &ctx->work);
+
+ return NOTIFY_OK;
+}
+
+static int gpio_device_unregistered_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gpio_chardev_data *cdev = container_of(nb,
+ struct gpio_chardev_data,
+ device_unregistered_nb);
+
+ wake_up_poll(&cdev->wait, EPOLLIN | EPOLLERR);
return NOTIFY_OK;
}
@@ -2182,6 +2583,11 @@ static __poll_t lineinfo_watch_poll(struct file *file,
struct gpio_chardev_data *cdev = file->private_data;
__poll_t events = 0;
+ guard(srcu)(&cdev->gdev->srcu);
+
+ if (!rcu_access_pointer(cdev->gdev->chip))
+ return EPOLLHUP | EPOLLERR;
+
poll_wait(file, &cdev->wait, pollt);
if (!kfifo_is_empty_spinlocked_noirqsave(&cdev->events,
@@ -2200,6 +2606,11 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
int ret;
size_t event_size;
+ guard(srcu)(&cdev->gdev->srcu);
+
+ if (!rcu_access_pointer(cdev->gdev->chip))
+ return -ENODEV;
+
#ifndef CONFIG_GPIO_CDEV_V1
event_size = sizeof(struct gpio_v2_line_info_changed);
if (count < event_size)
@@ -2207,42 +2618,37 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
#endif
do {
- spin_lock(&cdev->wait.lock);
- if (kfifo_is_empty(&cdev->events)) {
- if (bytes_read) {
- spin_unlock(&cdev->wait.lock);
- return bytes_read;
- }
-
- if (file->f_flags & O_NONBLOCK) {
- spin_unlock(&cdev->wait.lock);
- return -EAGAIN;
+ scoped_guard(spinlock, &cdev->wait.lock) {
+ if (kfifo_is_empty(&cdev->events)) {
+ if (bytes_read)
+ return bytes_read;
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible_locked(cdev->wait,
+ !kfifo_is_empty(&cdev->events));
+ if (ret)
+ return ret;
}
-
- ret = wait_event_interruptible_locked(cdev->wait,
- !kfifo_is_empty(&cdev->events));
- if (ret) {
- spin_unlock(&cdev->wait.lock);
- return ret;
- }
- }
#ifdef CONFIG_GPIO_CDEV_V1
- /* must be after kfifo check so watch_abi_version is set */
- if (atomic_read(&cdev->watch_abi_version) == 2)
- event_size = sizeof(struct gpio_v2_line_info_changed);
- else
- event_size = sizeof(struct gpioline_info_changed);
- if (count < event_size) {
- spin_unlock(&cdev->wait.lock);
- return -EINVAL;
- }
+ /* must be after kfifo check so watch_abi_version is set */
+ if (atomic_read(&cdev->watch_abi_version) == 2)
+ event_size = sizeof(struct gpio_v2_line_info_changed);
+ else
+ event_size = sizeof(struct gpioline_info_changed);
+ if (count < event_size)
+ return -EINVAL;
#endif
- ret = kfifo_out(&cdev->events, &event, 1);
- spin_unlock(&cdev->wait.lock);
- if (ret != 1) {
- ret = -EIO;
- break;
- /* We should never get here. See lineevent_read(). */
+ if (kfifo_out(&cdev->events, &event, 1) != 1) {
+ /*
+ * This should never happen - we hold the
+ * lock from the moment we learned the fifo
+ * is no longer empty until now.
+ */
+ WARN(1, "failed to read from non-empty kfifo");
+ return -EIO;
+ }
}
#ifdef CONFIG_GPIO_CDEV_V1
@@ -2271,7 +2677,9 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
* gpio_chrdev_open() - open the chardev for ioctl operations
* @inode: inode for this chardev
* @file: file struct for storing private data
- * Returns 0 on success
+ *
+ * Returns:
+ * 0 on success, or negative errno on failure.
*/
static int gpio_chrdev_open(struct inode *inode, struct file *file)
{
@@ -2280,41 +2688,56 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
struct gpio_chardev_data *cdev;
int ret = -ENOMEM;
+ guard(srcu)(&gdev->srcu);
+
/* Fail on open if the backing gpiochip is gone */
- if (!gdev->chip)
+ if (!rcu_access_pointer(gdev->chip))
return -ENODEV;
cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
if (!cdev)
- return -ENOMEM;
+ return -ENODEV;
- cdev->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL);
+ cdev->watched_lines = bitmap_zalloc(gdev->ngpio, GFP_KERNEL);
if (!cdev->watched_lines)
goto out_free_cdev;
init_waitqueue_head(&cdev->wait);
INIT_KFIFO(cdev->events);
- cdev->gdev = gdev;
+ cdev->gdev = gpio_device_get(gdev);
cdev->lineinfo_changed_nb.notifier_call = lineinfo_changed_notify;
- ret = blocking_notifier_chain_register(&gdev->notifier,
- &cdev->lineinfo_changed_nb);
+ scoped_guard(write_lock_irqsave, &gdev->line_state_lock)
+ ret = raw_notifier_chain_register(&gdev->line_state_notifier,
+ &cdev->lineinfo_changed_nb);
if (ret)
goto out_free_bitmap;
- get_device(&gdev->dev);
+ cdev->device_unregistered_nb.notifier_call =
+ gpio_device_unregistered_notify;
+ ret = blocking_notifier_chain_register(&gdev->device_notifier,
+ &cdev->device_unregistered_nb);
+ if (ret)
+ goto out_unregister_line_notifier;
+
file->private_data = cdev;
+ cdev->fp = file;
ret = nonseekable_open(inode, file);
if (ret)
- goto out_unregister_notifier;
+ goto out_unregister_device_notifier;
return ret;
-out_unregister_notifier:
- blocking_notifier_chain_unregister(&gdev->notifier,
- &cdev->lineinfo_changed_nb);
+out_unregister_device_notifier:
+ blocking_notifier_chain_unregister(&gdev->device_notifier,
+ &cdev->device_unregistered_nb);
+out_unregister_line_notifier:
+ scoped_guard(write_lock_irqsave, &gdev->line_state_lock)
+ raw_notifier_chain_unregister(&gdev->line_state_notifier,
+ &cdev->lineinfo_changed_nb);
out_free_bitmap:
+ gpio_device_put(gdev);
bitmap_free(cdev->watched_lines);
out_free_cdev:
kfree(cdev);
@@ -2325,17 +2748,22 @@ out_free_cdev:
* gpio_chrdev_release() - close chardev after ioctl operations
* @inode: inode for this chardev
* @file: file struct for storing private data
- * Returns 0 on success
+ *
+ * Returns:
+ * 0 on success, or negative errno on failure.
*/
static int gpio_chrdev_release(struct inode *inode, struct file *file)
{
struct gpio_chardev_data *cdev = file->private_data;
struct gpio_device *gdev = cdev->gdev;
+ blocking_notifier_chain_unregister(&gdev->device_notifier,
+ &cdev->device_unregistered_nb);
+ scoped_guard(write_lock_irqsave, &gdev->line_state_lock)
+ raw_notifier_chain_unregister(&gdev->line_state_notifier,
+ &cdev->lineinfo_changed_nb);
bitmap_free(cdev->watched_lines);
- blocking_notifier_chain_unregister(&gdev->notifier,
- &cdev->lineinfo_changed_nb);
- put_device(&gdev->dev);
+ gpio_device_put(gdev);
kfree(cdev);
return 0;
@@ -2347,7 +2775,6 @@ static const struct file_operations gpio_fileops = {
.poll = lineinfo_watch_poll,
.read = lineinfo_watch_read,
.owner = THIS_MODULE,
- .llseek = no_llseek,
.unlocked_ioctl = gpio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = gpio_ioctl_compat,
@@ -2356,23 +2783,35 @@ static const struct file_operations gpio_fileops = {
int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt)
{
+ struct gpio_chip *gc;
int ret;
cdev_init(&gdev->chrdev, &gpio_fileops);
gdev->chrdev.owner = THIS_MODULE;
gdev->dev.devt = MKDEV(MAJOR(devt), gdev->id);
+ gdev->line_state_wq = alloc_ordered_workqueue("%s", WQ_HIGHPRI,
+ dev_name(&gdev->dev));
+ if (!gdev->line_state_wq)
+ return -ENOMEM;
+
ret = cdev_device_add(&gdev->chrdev, &gdev->dev);
if (ret)
return ret;
- chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
- MAJOR(devt), gdev->id);
+ guard(srcu)(&gdev->srcu);
+ gc = srcu_dereference(gdev->chip, &gdev->srcu);
+ if (!gc)
+ return -ENODEV;
+
+ gpiochip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id);
return 0;
}
void gpiolib_cdev_unregister(struct gpio_device *gdev)
{
+ destroy_workqueue(gdev->line_state_wq);
cdev_device_del(&gdev->chrdev, &gdev->dev);
+ blocking_notifier_call_chain(&gdev->device_notifier, 0, NULL);
}