diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 18:50:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 18:50:12 +0000 |
commit | 8665bd53f2f2e27e5511d90428cb3f60e6d0ce15 (patch) | |
tree | 8d58900dc0ebd4a3011f92c128d2fe45bc7c4bf2 /drivers/gpio/gpiolib-cdev.c | |
parent | Adding debian version 6.7.12-1. (diff) | |
download | linux-8665bd53f2f2e27e5511d90428cb3f60e6d0ce15.tar.xz linux-8665bd53f2f2e27e5511d90428cb3f60e6d0ce15.zip |
Merging upstream version 6.8.9.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpio/gpiolib-cdev.c')
-rw-r--r-- | drivers/gpio/gpiolib-cdev.c | 729 |
1 files changed, 378 insertions, 351 deletions
diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index f713d1ef77..1438fdca0b 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> @@ -19,8 +20,11 @@ #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/rbtree.h> +#include <linux/rwsem.h> #include <linux/seq_file.h> #include <linux/spinlock.h> #include <linux/timekeeping.h> @@ -62,45 +66,6 @@ typedef long (*ioctl_fn)(struct file *, unsigned int, unsigned long); typedef ssize_t (*read_fn)(struct file *, char __user *, size_t count, loff_t *); -static __poll_t call_poll_locked(struct file *file, - struct poll_table_struct *wait, - struct gpio_device *gdev, poll_fn func) -{ - __poll_t ret; - - down_read(&gdev->sem); - ret = func(file, wait); - up_read(&gdev->sem); - - return ret; -} - -static long call_ioctl_locked(struct file *file, unsigned int cmd, - unsigned long arg, struct gpio_device *gdev, - ioctl_fn func) -{ - long ret; - - down_read(&gdev->sem); - ret = func(file, cmd, arg); - up_read(&gdev->sem); - - return ret; -} - -static ssize_t call_read_locked(struct file *file, char __user *buf, - size_t count, loff_t *f_ps, - struct gpio_device *gdev, read_fn func) -{ - ssize_t ret; - - down_read(&gdev->sem); - ret = func(file, buf, count, f_ps); - up_read(&gdev->sem); - - return ret; -} - /* * GPIO line handle management */ @@ -235,8 +200,8 @@ static long linehandle_set_config(struct linehandle_state *lh, return 0; } -static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd, - unsigned long arg) +static long linehandle_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { struct linehandle_state *lh = file->private_data; void __user *ip = (void __user *)arg; @@ -245,6 +210,8 @@ static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned int i; int ret; + guard(rwsem_read)(&lh->gdev->sem); + if (!lh->gdev->chip) return -ENODEV; @@ -294,15 +261,6 @@ static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd, } } -static long linehandle_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct linehandle_state *lh = file->private_data; - - return call_ioctl_locked(file, cmd, arg, lh->gdev, - linehandle_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long linehandle_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -461,6 +419,7 @@ out_free_lh: /** * struct line - contains the state of a requested line + * @node: to store the object in supinfo_tree if supplemental * @desc: the GPIO descriptor for this line. * @req: the corresponding line request * @irq: the interrupt triggered in response to events on this GPIO @@ -473,6 +432,7 @@ out_free_lh: * @line_seqno: the seqno for the current edge event in the sequence of * events for this line. * @work: the worker that implements software debouncing + * @debounce_period_us: the debounce period in microseconds * @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 @@ -481,6 +441,7 @@ out_free_lh: * @last_seqno: the last sequence number before debounce period expires */ struct line { + struct rb_node node; struct gpio_desc *desc; /* * -- edge detector specific fields -- @@ -515,6 +476,15 @@ struct line { */ struct delayed_work work; /* + * debounce_period_us is accessed by debounce_irq_handler() and + * process_hw_ts() which are disabled when modified by + * debounce_setup(), edge_detector_setup() or edge_detector_stop() + * or can live with a stale version when updated by + * edge_detector_update(). + * The modifying functions are themselves mutually exclusive. + */ + unsigned int debounce_period_us; + /* * sw_debounce is accessed by linereq_set_config(), which is the * only setter, and linereq_get_values(), which can live with a * slightly stale value. @@ -546,6 +516,17 @@ struct line { #endif /* CONFIG_HTE */ }; +/* + * a rbtree of the struct lines containing supplemental info. + * Used to populate gpio_v2_line_info with cdev specific fields not contained + * in the struct gpio_desc. + * A line is determined to contain supplemental information by + * line_has_supinfo(). + */ +static struct rb_root supinfo_tree = RB_ROOT; +/* covers supinfo_tree */ +static DEFINE_SPINLOCK(supinfo_lock); + /** * struct linereq - contains the state of a userspace line request * @gdev: the GPIO device the line request pertains to @@ -559,7 +540,8 @@ struct line { * this line request. Note that this is not used when @num_lines is 1, as * the line_seqno is then the same and is cheaper to calculate. * @config_mutex: mutex for serializing ioctl() calls to ensure consistency - * of configuration, particularly multi-step accesses to desc flags. + * of configuration, particularly multi-step accesses to desc flags and + * changes to supinfo status. * @lines: the lines held by this line request, with @num_lines elements. */ struct linereq { @@ -575,6 +557,103 @@ struct linereq { struct line lines[] __counted_by(num_lines); }; +static void supinfo_insert(struct line *line) +{ + struct rb_node **new = &(supinfo_tree.rb_node), *parent = NULL; + struct line *entry; + + guard(spinlock)(&supinfo_lock); + + while (*new) { + entry = container_of(*new, struct line, node); + + parent = *new; + if (line->desc < entry->desc) { + new = &((*new)->rb_left); + } else if (line->desc > entry->desc) { + new = &((*new)->rb_right); + } else { + /* this should never happen */ + WARN(1, "duplicate line inserted"); + return; + } + } + + rb_link_node(&line->node, parent, new); + rb_insert_color(&line->node, &supinfo_tree); +} + +static void supinfo_erase(struct line *line) +{ + guard(spinlock)(&supinfo_lock); + + rb_erase(&line->node, &supinfo_tree); +} + +static struct line *supinfo_find(struct gpio_desc *desc) +{ + struct rb_node *node = supinfo_tree.rb_node; + struct line *line; + + while (node) { + line = container_of(node, struct line, node); + if (desc < line->desc) + node = node->rb_left; + else if (desc > line->desc) + node = node->rb_right; + else + return line; + } + return NULL; +} + +static void supinfo_to_lineinfo(struct gpio_desc *desc, + struct gpio_v2_line_info *info) +{ + struct gpio_v2_line_attribute *attr; + struct line *line; + + guard(spinlock)(&supinfo_lock); + + line = supinfo_find(desc); + if (!line) + return; + + attr = &info->attrs[info->num_attrs]; + attr->id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE; + attr->debounce_period_us = READ_ONCE(line->debounce_period_us); + info->num_attrs++; +} + +static inline bool line_has_supinfo(struct line *line) +{ + return READ_ONCE(line->debounce_period_us); +} + +/* + * Checks line_has_supinfo() before and after the change to avoid unnecessary + * supinfo_tree access. + * Called indirectly by linereq_create() or linereq_set_config() so line + * is already protected from concurrent changes. + */ +static void line_set_debounce_period(struct line *line, + unsigned int debounce_period_us) +{ + bool was_suppl = line_has_supinfo(line); + + WRITE_ONCE(line->debounce_period_us, debounce_period_us); + + /* if supinfo status is unchanged then we're done */ + if (line_has_supinfo(line) == was_suppl) + return; + + /* supinfo status has changed, so update the tree */ + if (was_suppl) + supinfo_erase(line); + else + supinfo_insert(line); +} + #define GPIO_V2_LINE_BIAS_FLAGS \ (GPIO_V2_LINE_FLAG_BIAS_PULL_UP | \ GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN | \ @@ -625,13 +704,13 @@ static void linereq_put_event(struct linereq *lr, { 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 @@ -655,6 +734,25 @@ static u32 line_event_id(int level) 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) @@ -723,7 +821,7 @@ static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p) line->total_discard_seq++; line->last_seqno = ts->seq; mod_delayed_work(system_wq, &line->work, - usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us))); + usecs_to_jiffies(READ_ONCE(line->debounce_period_us))); } else { if (unlikely(ts->seq < line->line_seqno)) return HTE_CB_HANDLED; @@ -864,7 +962,7 @@ static irqreturn_t debounce_irq_handler(int irq, void *p) struct line *line = p; mod_delayed_work(system_wq, &line->work, - usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us))); + usecs_to_jiffies(READ_ONCE(line->debounce_period_us))); return IRQ_HANDLED; } @@ -942,11 +1040,12 @@ 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); + line_set_debounce_period(line, debounce_period_us); return ret; } if (ret != -ENOTSUPP) @@ -964,11 +1063,17 @@ static int debounce_setup(struct line *line, unsigned int debounce_period_us) if (irq < 0) return -ENXIO; + 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, - line->req->label, line); - if (ret) + 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); @@ -1013,7 +1118,7 @@ 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; } @@ -1025,8 +1130,7 @@ static void edge_detector_stop(struct line *line) cancel_delayed_work_sync(&line->work); WRITE_ONCE(line->sw_debounced, 0); WRITE_ONCE(line->edflags, 0); - if (line->desc) - WRITE_ONCE(line->desc->debounce_period_us, 0); + line_set_debounce_period(line, 0); /* do not change line->level - see comment in debounced_value() */ } @@ -1038,6 +1142,7 @@ static int edge_detector_setup(struct line *line, unsigned long irqflags = 0; u64 eflags; int irq, ret; + char *label; eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS; if (eflags && !kfifo_initialized(&line->req->events)) { @@ -1051,7 +1156,7 @@ static int edge_detector_setup(struct line *line, ret = debounce_setup(line, debounce_period_us); if (ret) return ret; - WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us); + line_set_debounce_period(line, debounce_period_us); } /* detection disabled or sw debouncer will provide edge detection */ @@ -1074,11 +1179,17 @@ static int edge_detector_setup(struct line *line, 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; @@ -1093,12 +1204,12 @@ static int edge_detector_update(struct line *line, gpio_v2_line_config_debounce_period(lc, line_idx); if ((active_edflags == edflags) && - (READ_ONCE(line->desc->debounce_period_us) == debounce_period_us)) + (READ_ONCE(line->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->desc->debounce_period_us, debounce_period_us); + line_set_debounce_period(line, debounce_period_us); return 0; } @@ -1272,9 +1383,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; } } @@ -1283,6 +1403,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; @@ -1303,6 +1424,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]); @@ -1320,22 +1442,38 @@ 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 (lv.mask & BIT_ULL(i)) { + /* setting inputs is not allowed */ if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags)) return -EPERM; - if (lv->bits & 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; } } @@ -1343,12 +1481,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++; } @@ -1362,36 +1500,28 @@ static long linereq_set_values_unlocked(struct linereq *lr, return ret; } -static long linereq_set_values(struct linereq *lr, void __user *ip) -{ - struct gpio_v2_line_values lv; - int ret; - - if (copy_from_user(&lv, ip, sizeof(lv))) - return -EFAULT; - - mutex_lock(&lr->config_mutex); - - ret = linereq_set_values_unlocked(lr, &lv); - - mutex_unlock(&lr->config_mutex); - - return ret; -} - -static long linereq_set_config_unlocked(struct linereq *lr, - struct gpio_v2_line_config *lc) +static long linereq_set_config(struct linereq *lr, void __user *ip) { + 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(&lc, ip, sizeof(lc))) + return -EFAULT; + + ret = gpio_v2_line_config_validate(&lc, lr->num_lines); + if (ret) + return 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); + flags = gpio_v2_line_config_flags(&lc, i); gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags); edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS; /* @@ -1399,7 +1529,7 @@ static long linereq_set_config_unlocked(struct linereq *lr, * or output, else the line will be treated "as is". */ 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(line); ret = gpiod_direction_output(desc, val); @@ -1410,7 +1540,7 @@ static long linereq_set_config_unlocked(struct linereq *lr, if (ret) return ret; - ret = edge_detector_update(line, lc, i, edflags); + ret = edge_detector_update(line, &lc, i, edflags); if (ret) return ret; } @@ -1422,33 +1552,14 @@ static long linereq_set_config_unlocked(struct linereq *lr, 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_unlocked(struct file *file, unsigned int cmd, - unsigned long arg) +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; + guard(rwsem_read)(&lr->gdev->sem); + if (!lr->gdev->chip) return -ENODEV; @@ -1464,15 +1575,6 @@ static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd, } } -static long linereq_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct linereq *lr = file->private_data; - - return call_ioctl_locked(file, cmd, arg, lr->gdev, - linereq_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long linereq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -1481,12 +1583,14 @@ static long linereq_ioctl_compat(struct file *file, unsigned int cmd, } #endif -static __poll_t linereq_poll_unlocked(struct file *file, - struct poll_table_struct *wait) +static __poll_t linereq_poll(struct file *file, + struct poll_table_struct *wait) { struct linereq *lr = file->private_data; __poll_t events = 0; + guard(rwsem_read)(&lr->gdev->sem); + if (!lr->gdev->chip) return EPOLLHUP | EPOLLERR; @@ -1499,22 +1603,16 @@ static __poll_t linereq_poll_unlocked(struct file *file, return events; } -static __poll_t linereq_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct linereq *lr = file->private_data; - - return call_poll_locked(file, wait, lr->gdev, linereq_poll_unlocked); -} - -static ssize_t linereq_read_unlocked(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(rwsem_read)(&lr->gdev->sem); + if (!lr->gdev->chip) return -ENODEV; @@ -1522,28 +1620,22 @@ static ssize_t linereq_read_unlocked(struct file *file, char __user *buf, 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; - } - - if (file->f_flags & O_NONBLOCK) { - spin_unlock(&lr->wait.lock); - return -EAGAIN; + 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; } - ret = wait_event_interruptible_locked(lr->wait, - !kfifo_is_empty(&lr->events)); - if (ret) { - spin_unlock(&lr->wait.lock); - return ret; - } + ret = kfifo_out(&lr->events, &le, 1); } - - ret = kfifo_out(&lr->events, &le, 1); - spin_unlock(&lr->wait.lock); if (ret != 1) { /* * This should never happen - we were holding the @@ -1562,17 +1654,9 @@ static ssize_t linereq_read_unlocked(struct file *file, char __user *buf, return bytes_read; } -static ssize_t linereq_read(struct file *file, char __user *buf, - size_t count, loff_t *f_ps) -{ - struct linereq *lr = file->private_data; - - return call_read_locked(file, buf, count, f_ps, lr->gdev, - linereq_read_unlocked); -} - static void linereq_free(struct linereq *lr) { + struct line *line; unsigned int i; if (lr->device_unregistered_nb.notifier_call) @@ -1580,15 +1664,19 @@ static void linereq_free(struct linereq *lr) &lr->device_unregistered_nb); for (i = 0; i < lr->num_lines; i++) { - if (lr->lines[i].desc) { - edge_detector_stop(&lr->lines[i]); - gpiod_free(lr->lines[i].desc); - } + line = &lr->lines[i]; + if (!line->desc) + continue; + + edge_detector_stop(line); + if (line_has_supinfo(line)) + supinfo_erase(line); + gpiod_free(line->desc); } kfifo_free(&lr->events); kfree(lr->label); gpio_device_put(lr->gdev); - kfree(lr); + kvfree(lr); } static int linereq_release(struct inode *inode, struct file *file) @@ -1653,7 +1741,7 @@ 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; @@ -1818,12 +1906,14 @@ struct lineevent_state { (GPIOEVENT_REQUEST_RISING_EDGE | \ GPIOEVENT_REQUEST_FALLING_EDGE) -static __poll_t lineevent_poll_unlocked(struct file *file, - struct poll_table_struct *wait) +static __poll_t lineevent_poll(struct file *file, + struct poll_table_struct *wait) { struct lineevent_state *le = file->private_data; __poll_t events = 0; + guard(rwsem_read)(&le->gdev->sem); + if (!le->gdev->chip) return EPOLLHUP | EPOLLERR; @@ -1835,14 +1925,6 @@ static __poll_t lineevent_poll_unlocked(struct file *file, return events; } -static __poll_t lineevent_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct lineevent_state *le = file->private_data; - - return call_poll_locked(file, wait, le->gdev, lineevent_poll_unlocked); -} - static int lineevent_unregistered_notify(struct notifier_block *nb, unsigned long action, void *data) { @@ -1859,8 +1941,8 @@ struct compat_gpioeevent_data { u32 id; }; -static ssize_t lineevent_read_unlocked(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; @@ -1868,6 +1950,8 @@ static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf, ssize_t ge_size; int ret; + guard(rwsem_read)(&le->gdev->sem); + if (!le->gdev->chip) return -ENODEV; @@ -1888,28 +1972,22 @@ static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf, 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; - } - - if (file->f_flags & O_NONBLOCK) { - spin_unlock(&le->wait.lock); - return -EAGAIN; + 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; } - ret = wait_event_interruptible_locked(le->wait, - !kfifo_is_empty(&le->events)); - if (ret) { - spin_unlock(&le->wait.lock); - return ret; - } + ret = kfifo_out(&le->events, &ge, 1); } - - ret = kfifo_out(&le->events, &ge, 1); - spin_unlock(&le->wait.lock); if (ret != 1) { /* * This should never happen - we were holding the lock @@ -1928,22 +2006,13 @@ static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf, return bytes_read; } -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; - - return call_read_locked(file, buf, count, f_ps, le->gdev, - lineevent_read_unlocked); -} - 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); @@ -1957,13 +2026,15 @@ static int lineevent_release(struct inode *inode, struct file *file) return 0; } -static long lineevent_ioctl_unlocked(struct file *file, unsigned int cmd, - unsigned long arg) +static long lineevent_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { struct lineevent_state *le = file->private_data; void __user *ip = (void __user *)arg; struct gpiohandle_data ghd; + guard(rwsem_read)(&le->gdev->sem); + if (!le->gdev->chip) return -ENODEV; @@ -1989,15 +2060,6 @@ static long lineevent_ioctl_unlocked(struct file *file, unsigned int cmd, return -EINVAL; } -static long lineevent_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct lineevent_state *le = file->private_data; - - return call_ioctl_locked(file, cmd, arg, le->gdev, - lineevent_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long lineevent_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -2091,6 +2153,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; @@ -2175,15 +2238,23 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) 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(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; @@ -2272,84 +2343,72 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, struct gpio_v2_line_info *info) { struct gpio_chip *gc = desc->gdev->chip; - bool ok_for_pinctrl; - unsigned long flags; - u32 debounce_period_us; - unsigned int num_attrs = 0; + unsigned long dflags; memset(info, 0, sizeof(*info)); info->offset = gpio_chip_hwgpio(desc); - /* - * 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, info->offset); + scoped_guard(spinlock_irqsave, &gpio_lock) { + if (desc->name) + strscpy(info->name, desc->name, sizeof(info->name)); - spin_lock_irqsave(&gpio_lock, flags); + if (desc->label) + strscpy(info->consumer, desc->label, + sizeof(info->consumer)); - 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); + } /* - * 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) || + if (test_bit(FLAG_REQUESTED, &dflags) || + test_bit(FLAG_IS_HOGGED, &dflags) || + test_bit(FLAG_USED_AS_IRQ, &dflags) || + test_bit(FLAG_EXPORT, &dflags) || + test_bit(FLAG_SYSFS, &dflags) || !gpiochip_line_is_valid(gc, info->offset) || - !ok_for_pinctrl) + !pinctrl_gpio_can_use_line(gc, info->offset)) info->flags |= GPIO_V2_LINE_FLAG_USED; - if (test_bit(FLAG_IS_OUT, &desc->flags)) + if (test_bit(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(FLAG_ACTIVE_LOW, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW; - if (test_bit(FLAG_OPEN_DRAIN, &desc->flags)) + if (test_bit(FLAG_OPEN_DRAIN, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN; - if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) + if (test_bit(FLAG_OPEN_SOURCE, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE; - if (test_bit(FLAG_BIAS_DISABLE, &desc->flags)) + if (test_bit(FLAG_BIAS_DISABLE, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED; - if (test_bit(FLAG_PULL_DOWN, &desc->flags)) + if (test_bit(FLAG_PULL_DOWN, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; - if (test_bit(FLAG_PULL_UP, &desc->flags)) + if (test_bit(FLAG_PULL_UP, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; - if (test_bit(FLAG_EDGE_RISING, &desc->flags)) + if (test_bit(FLAG_EDGE_RISING, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_EDGE_RISING; - if (test_bit(FLAG_EDGE_FALLING, &desc->flags)) + if (test_bit(FLAG_EDGE_FALLING, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING; - if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &desc->flags)) + if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &dflags)) info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME; - else if (test_bit(FLAG_EVENT_CLOCK_HTE, &desc->flags)) + else if (test_bit(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->num_attrs = num_attrs; - - spin_unlock_irqrestore(&gpio_lock, flags); } struct gpio_chardev_data { @@ -2455,6 +2514,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, return -EBUSY; } gpio_desc_to_lineinfo(desc, &lineinfo); + supinfo_to_lineinfo(desc, &lineinfo); if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { if (watch) @@ -2481,12 +2541,17 @@ static int lineinfo_unwatch(struct gpio_chardev_data *cdev, void __user *ip) return 0; } -static long gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg) +/* + * gpio_ioctl() - ioctl handler for the GPIO chardev + */ +static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct gpio_chardev_data *cdev = file->private_data; struct gpio_device *gdev = cdev->gdev; void __user *ip = (void __user *)arg; + guard(rwsem_read)(&gdev->sem); + /* We fail any subsequent ioctl():s when the chip is gone */ if (!gdev->chip) return -ENODEV; @@ -2518,17 +2583,6 @@ static long gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned lo } } -/* - * gpio_ioctl() - ioctl handler for the GPIO chardev - */ -static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - struct gpio_chardev_data *cdev = file->private_data; - - return call_ioctl_locked(file, cmd, arg, cdev->gdev, - gpio_ioctl_unlocked); -} - #ifdef CONFIG_COMPAT static long gpio_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -2553,6 +2607,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, chg.event_type = action; chg.timestamp_ns = ktime_get_ns(); gpio_desc_to_lineinfo(desc, &chg.info); + supinfo_to_lineinfo(desc, &chg.info); ret = kfifo_in_spinlocked(&cdev->events, &chg, 1, &cdev->wait.lock); if (ret) @@ -2575,12 +2630,14 @@ static int gpio_device_unregistered_notify(struct notifier_block *nb, return NOTIFY_OK; } -static __poll_t lineinfo_watch_poll_unlocked(struct file *file, - struct poll_table_struct *pollt) +static __poll_t lineinfo_watch_poll(struct file *file, + struct poll_table_struct *pollt) { struct gpio_chardev_data *cdev = file->private_data; __poll_t events = 0; + guard(rwsem_read)(&cdev->gdev->sem); + if (!cdev->gdev->chip) return EPOLLHUP | EPOLLERR; @@ -2593,17 +2650,8 @@ static __poll_t lineinfo_watch_poll_unlocked(struct file *file, return events; } -static __poll_t lineinfo_watch_poll(struct file *file, - struct poll_table_struct *pollt) -{ - struct gpio_chardev_data *cdev = file->private_data; - - return call_poll_locked(file, pollt, cdev->gdev, - lineinfo_watch_poll_unlocked); -} - -static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf, - size_t count, loff_t *off) +static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, + size_t count, loff_t *off) { struct gpio_chardev_data *cdev = file->private_data; struct gpio_v2_line_info_changed event; @@ -2611,6 +2659,8 @@ static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf, int ret; size_t event_size; + guard(rwsem_read)(&cdev->gdev->sem); + if (!cdev->gdev->chip) return -ENODEV; @@ -2621,38 +2671,30 @@ static ssize_t lineinfo_watch_read_unlocked(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; + 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; } - - if (file->f_flags & O_NONBLOCK) { - spin_unlock(&cdev->wait.lock); - return -EAGAIN; - } - - 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); + ret = kfifo_out(&cdev->events, &event, 1); + } if (ret != 1) { ret = -EIO; break; @@ -2681,15 +2723,6 @@ static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf, return bytes_read; } -static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, - size_t count, loff_t *off) -{ - struct gpio_chardev_data *cdev = file->private_data; - - return call_read_locked(file, buf, count, off, cdev->gdev, - lineinfo_watch_read_unlocked); -} - /** * gpio_chrdev_open() - open the chardev for ioctl operations * @inode: inode for this chardev @@ -2703,17 +2736,15 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) struct gpio_chardev_data *cdev; int ret = -ENOMEM; - down_read(&gdev->sem); + guard(rwsem_read)(&gdev->sem); /* Fail on open if the backing gpiochip is gone */ - if (!gdev->chip) { - ret = -ENODEV; - goto out_unlock; - } + if (!gdev->chip) + return -ENODEV; cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); if (!cdev) - goto out_unlock; + return -ENODEV; cdev->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL); if (!cdev->watched_lines) @@ -2742,8 +2773,6 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) if (ret) goto out_unregister_device_notifier; - up_read(&gdev->sem); - return ret; out_unregister_device_notifier: @@ -2757,8 +2786,6 @@ out_free_bitmap: bitmap_free(cdev->watched_lines); out_free_cdev: kfree(cdev); -out_unlock: - up_read(&gdev->sem); return ret; } |