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.c653
1 files changed, 341 insertions, 312 deletions
diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index 2a88736629ef..e6a289fa0f8f 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -16,17 +16,15 @@
#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/rbtree.h>
-#include <linux/rwsem.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>
@@ -61,11 +59,6 @@ static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_values), 8));
* interface to gpiolib GPIOs via ioctl()s.
*/
-typedef __poll_t (*poll_fn)(struct file *, struct poll_table_struct *);
-typedef long (*ioctl_fn)(struct file *, unsigned int, unsigned long);
-typedef ssize_t (*read_fn)(struct file *, char __user *,
- size_t count, loff_t *);
-
/*
* GPIO line handle management
*/
@@ -95,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 */
@@ -145,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(FLAG_ACTIVE_LOW, &flags,
lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW);
- assign_bit(FLAG_OPEN_DRAIN, flagsp,
+ assign_bit(FLAG_OPEN_DRAIN, &flags,
lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN);
- assign_bit(FLAG_OPEN_SOURCE, flagsp,
+ assign_bit(FLAG_OPEN_SOURCE, &flags,
lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE);
- assign_bit(FLAG_PULL_UP, flagsp,
+ assign_bit(FLAG_PULL_UP, &flags,
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP);
- assign_bit(FLAG_PULL_DOWN, flagsp,
+ assign_bit(FLAG_PULL_DOWN, &flags,
lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN);
- assign_bit(FLAG_BIAS_DISABLE, flagsp,
+ assign_bit(FLAG_BIAS_DISABLE, &flags,
lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE);
+
+ WRITE_ONCE(*flagsp, flags);
}
static long linehandle_set_config(struct linehandle_state *lh,
@@ -175,22 +176,22 @@ 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;
}
@@ -210,9 +211,9 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
unsigned int i;
int ret;
- guard(rwsem_read)(&lh->gdev->sem);
+ guard(srcu)(&lh->gdev->srcu);
- if (!lh->gdev->chip)
+ if (!rcu_access_pointer(lh->gdev->chip))
return -ENODEV;
switch (cmd) {
@@ -337,7 +338,7 @@ 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);
@@ -361,11 +362,11 @@ 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;
} else if (lflags & GPIOHANDLE_REQUEST_INPUT) {
- ret = gpiod_direction_input(desc);
+ ret = gpiod_direction_input_nonotify(desc);
if (ret)
goto out_free_lh;
}
@@ -419,7 +420,6 @@ 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
@@ -432,7 +432,6 @@ 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
@@ -441,7 +440,6 @@ 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 --
@@ -452,7 +450,7 @@ struct line {
* The flags for the active edge detector configuration.
*
* edflags is set by linereq_create(), linereq_free(), and
- * linereq_set_config_unlocked(), which are themselves mutually
+ * 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.
@@ -476,15 +474,6 @@ 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.
@@ -516,17 +505,6 @@ 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
@@ -540,8 +518,7 @@ static DEFINE_SPINLOCK(supinfo_lock);
* 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 and
- * changes to supinfo status.
+ * of configuration, particularly multi-step accesses to desc flags.
* @lines: the lines held by this line request, with @num_lines elements.
*/
struct linereq {
@@ -557,103 +534,6 @@ 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 | \
@@ -734,6 +614,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)
@@ -802,7 +701,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->debounce_period_us)));
+ usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
} else {
if (unlikely(ts->seq < line->line_seqno))
return HTE_CB_HANDLED;
@@ -943,7 +842,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->debounce_period_us)));
+ usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
return IRQ_HANDLED;
}
@@ -1021,13 +920,15 @@ 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) {
- line_set_debounce_period(line, 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;
@@ -1043,11 +944,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);
@@ -1092,7 +999,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;
}
@@ -1104,10 +1011,19 @@ 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);
- line_set_debounce_period(line, 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 edflags)
@@ -1116,11 +1032,11 @@ 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)) {
- ret = kfifo_alloc(&line->req->events,
- line->req->event_buffer_size, GFP_KERNEL);
+ if (eflags) {
+ ret = edge_detector_fifo_init(line->req);
if (ret)
return ret;
}
@@ -1129,7 +1045,7 @@ static int edge_detector_setup(struct line *line,
ret = debounce_setup(line, debounce_period_us);
if (ret)
return ret;
- line_set_debounce_period(line, debounce_period_us);
+ WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
}
/* detection disabled or sw debouncer will provide edge detection */
@@ -1152,11 +1068,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;
@@ -1171,12 +1093,19 @@ static int edge_detector_update(struct line *line,
gpio_v2_line_config_debounce_period(lc, line_idx);
if ((active_edflags == edflags) &&
- (READ_ONCE(line->debounce_period_us) == debounce_period_us))
+ (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)) {
- line_set_debounce_period(line, debounce_period_us);
+ 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;
}
@@ -1286,7 +1215,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++) {
@@ -1303,38 +1232,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);
+
+ assign_bit(FLAG_ACTIVE_LOW, &flags,
+ lflags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
- 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);
+ if (lflags & GPIO_V2_LINE_FLAG_OUTPUT)
+ set_bit(FLAG_IS_OUT, &flags);
+ else if (lflags & GPIO_V2_LINE_FLAG_INPUT)
+ clear_bit(FLAG_IS_OUT, &flags);
- 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);
+ assign_bit(FLAG_EDGE_RISING, &flags,
+ lflags & GPIO_V2_LINE_FLAG_EDGE_RISING);
+ assign_bit(FLAG_EDGE_FALLING, &flags,
+ lflags & GPIO_V2_LINE_FLAG_EDGE_FALLING);
- 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(FLAG_OPEN_DRAIN, &flags,
+ lflags & GPIO_V2_LINE_FLAG_OPEN_DRAIN);
+ assign_bit(FLAG_OPEN_SOURCE, &flags,
+ lflags & GPIO_V2_LINE_FLAG_OPEN_SOURCE);
- 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(FLAG_PULL_UP, &flags,
+ lflags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP);
+ assign_bit(FLAG_PULL_DOWN, &flags,
+ lflags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN);
+ assign_bit(FLAG_BIAS_DISABLE, &flags,
+ lflags & GPIO_V2_LINE_FLAG_BIAS_DISABLED);
- assign_bit(FLAG_EVENT_CLOCK_REALTIME, flagsp,
- flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
- assign_bit(FLAG_EVENT_CLOCK_HTE, flagsp,
- flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
+ assign_bit(FLAG_EVENT_CLOCK_REALTIME, &flags,
+ lflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
+ assign_bit(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)
@@ -1433,9 +1366,6 @@ static long linereq_set_values(struct linereq *lr, void __user *ip)
/* 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)) {
- /* setting inputs is not allowed */
- if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags))
- return -EPERM;
/* add to compacted values */
if (lv.bits & BIT_ULL(i))
__set_bit(num_set, vals);
@@ -1489,21 +1419,23 @@ static long linereq_set_config(struct linereq *lr, void __user *ip)
line = &lr->lines[i];
desc = lr->lines[i].desc;
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;
/*
- * 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);
edge_detector_stop(line);
- ret = gpiod_direction_output(desc, val);
+ 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;
@@ -1525,9 +1457,9 @@ static long linereq_ioctl(struct file *file, unsigned int cmd,
struct linereq *lr = file->private_data;
void __user *ip = (void __user *)arg;
- guard(rwsem_read)(&lr->gdev->sem);
+ guard(srcu)(&lr->gdev->srcu);
- if (!lr->gdev->chip)
+ if (!rcu_access_pointer(lr->gdev->chip))
return -ENODEV;
switch (cmd) {
@@ -1556,9 +1488,9 @@ static __poll_t linereq_poll(struct file *file,
struct linereq *lr = file->private_data;
__poll_t events = 0;
- guard(rwsem_read)(&lr->gdev->sem);
+ guard(srcu)(&lr->gdev->srcu);
- if (!lr->gdev->chip)
+ if (!rcu_access_pointer(lr->gdev->chip))
return EPOLLHUP | EPOLLERR;
poll_wait(file, &lr->wait, wait);
@@ -1578,9 +1510,9 @@ static ssize_t linereq_read(struct file *file, char __user *buf,
ssize_t bytes_read = 0;
int ret;
- guard(rwsem_read)(&lr->gdev->sem);
+ guard(srcu)(&lr->gdev->srcu);
- if (!lr->gdev->chip)
+ if (!rcu_access_pointer(lr->gdev->chip))
return -ENODEV;
if (count < sizeof(le))
@@ -1601,16 +1533,15 @@ static ssize_t linereq_read(struct file *file, char __user *buf,
return ret;
}
- ret = kfifo_out(&lr->events, &le, 1);
- }
- 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 (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;
+ }
}
if (copy_to_user(buf + bytes_read, &le, sizeof(le)))
@@ -1623,7 +1554,6 @@ static ssize_t linereq_read(struct file *file, char __user *buf,
static void linereq_free(struct linereq *lr)
{
- struct line *line;
unsigned int i;
if (lr->device_unregistered_nb.notifier_call)
@@ -1631,14 +1561,10 @@ static void linereq_free(struct linereq *lr)
&lr->device_unregistered_nb);
for (i = 0; i < lr->num_lines; i++) {
- line = &lr->lines[i];
- if (!line->desc)
- continue;
-
- edge_detector_stop(line);
- if (line_has_supinfo(line))
- supinfo_erase(line);
- gpiod_free(line->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);
@@ -1700,7 +1626,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;
@@ -1733,6 +1659,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;
@@ -1744,7 +1671,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
/* 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);
@@ -1771,11 +1698,11 @@ 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;
@@ -1879,9 +1806,9 @@ static __poll_t lineevent_poll(struct file *file,
struct lineevent_state *le = file->private_data;
__poll_t events = 0;
- guard(rwsem_read)(&le->gdev->sem);
+ guard(srcu)(&le->gdev->srcu);
- if (!le->gdev->chip)
+ if (!rcu_access_pointer(le->gdev->chip))
return EPOLLHUP | EPOLLERR;
poll_wait(file, &le->wait, wait);
@@ -1917,9 +1844,9 @@ static ssize_t lineevent_read(struct file *file, char __user *buf,
ssize_t ge_size;
int ret;
- guard(rwsem_read)(&le->gdev->sem);
+ guard(srcu)(&le->gdev->srcu);
- if (!le->gdev->chip)
+ if (!rcu_access_pointer(le->gdev->chip))
return -ENODEV;
/*
@@ -1953,16 +1880,15 @@ static ssize_t lineevent_read(struct file *file, char __user *buf,
return ret;
}
- ret = kfifo_out(&le->events, &ge, 1);
- }
- 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 (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;
+ }
}
if (copy_to_user(buf + bytes_read, &ge, ge_size))
@@ -1979,7 +1905,7 @@ static void lineevent_free(struct lineevent_state *le)
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);
@@ -2000,9 +1926,9 @@ static long lineevent_ioctl(struct file *file, unsigned int cmd,
void __user *ip = (void __user *)arg;
struct gpiohandle_data ghd;
- guard(rwsem_read)(&le->gdev->sem);
+ guard(srcu)(&le->gdev->srcu);
- if (!le->gdev->chip)
+ if (!rcu_access_pointer(le->gdev->chip))
return -ENODEV;
/*
@@ -2120,6 +2046,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;
@@ -2128,7 +2055,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);
@@ -2204,15 +2131,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;
@@ -2298,23 +2233,29 @@ 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;
+ u32 debounce_period_us;
unsigned long dflags;
+ const char *label;
+
+ CLASS(gpio_chip_guard, guard)(desc);
+ if (!guard.gc)
+ return;
memset(info, 0, sizeof(*info));
info->offset = gpio_chip_hwgpio(desc);
- scoped_guard(spinlock_irqsave, &gpio_lock) {
- if (desc->name)
- strscpy(info->name, desc->name, sizeof(info->name));
+ 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);
- dflags = READ_ONCE(desc->flags);
+ scoped_guard(srcu, &desc->gdev->desc_srcu) {
+ label = gpiod_get_label(desc);
+ if (label && test_bit(FLAG_REQUESTED, &dflags))
+ strscpy(info->consumer, label,
+ sizeof(info->consumer));
}
/*
@@ -2331,12 +2272,14 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
*/
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) ||
- !pinctrl_gpio_can_use_line(gc, info->offset))
+ !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, &dflags))
info->flags |= GPIO_V2_LINE_FLAG_OUTPUT;
@@ -2367,6 +2310,14 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
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[info->num_attrs].id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+ info->attrs[info->num_attrs].debounce_period_us =
+ debounce_period_us;
+ info->num_attrs++;
+ }
}
struct gpio_chardev_data {
@@ -2379,6 +2330,7 @@ struct gpio_chardev_data {
#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)
@@ -2422,7 +2374,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);
@@ -2434,7 +2386,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))) {
@@ -2456,10 +2408,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);
@@ -2471,8 +2423,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);
- supinfo_to_lineinfo(desc, &lineinfo);
+ gpio_desc_to_lineinfo(desc, &lineinfo, false);
if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) {
if (watch)
@@ -2508,10 +2459,10 @@ 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(rwsem_read)(&gdev->sem);
+ 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 */
@@ -2549,29 +2500,86 @@ static long gpio_ioctl_compat(struct file *file, unsigned int cmd,
}
#endif
+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)
+{
+ 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 =
container_of(nb, struct gpio_chardev_data, lineinfo_changed_nb);
- struct gpio_v2_line_info_changed chg;
+ struct lineinfo_changed_ctx *ctx;
struct gpio_desc *desc = data;
- int ret;
if (!test_bit(gpio_chip_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);
- supinfo_to_lineinfo(desc, &chg.info);
+ /*
+ * 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.
+ */
- 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");
+ 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;
+ /* Keep the file descriptor alive too. */
+ get_file(ctx->cdev->fp);
+
+ INIT_WORK(&ctx->work, lineinfo_changed_func);
+ queue_work(ctx->gdev->line_state_wq, &ctx->work);
return NOTIFY_OK;
}
@@ -2594,9 +2602,9 @@ static __poll_t lineinfo_watch_poll(struct file *file,
struct gpio_chardev_data *cdev = file->private_data;
__poll_t events = 0;
- guard(rwsem_read)(&cdev->gdev->sem);
+ guard(srcu)(&cdev->gdev->srcu);
- if (!cdev->gdev->chip)
+ if (!rcu_access_pointer(cdev->gdev->chip))
return EPOLLHUP | EPOLLERR;
poll_wait(file, &cdev->wait, pollt);
@@ -2617,9 +2625,9 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
int ret;
size_t event_size;
- guard(rwsem_read)(&cdev->gdev->sem);
+ guard(srcu)(&cdev->gdev->srcu);
- if (!cdev->gdev->chip)
+ if (!rcu_access_pointer(cdev->gdev->chip))
return -ENODEV;
#ifndef CONFIG_GPIO_CDEV_V1
@@ -2651,12 +2659,15 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
if (count < event_size)
return -EINVAL;
#endif
- ret = kfifo_out(&cdev->events, &event, 1);
- }
- 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
@@ -2685,7 +2696,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)
{
@@ -2694,17 +2707,17 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
struct gpio_chardev_data *cdev;
int ret = -ENOMEM;
- guard(rwsem_read)(&gdev->sem);
+ 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 -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;
@@ -2713,8 +2726,9 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
cdev->gdev = gpio_device_get(gdev);
cdev->lineinfo_changed_nb.notifier_call = lineinfo_changed_notify;
- ret = blocking_notifier_chain_register(&gdev->line_state_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;
@@ -2726,6 +2740,7 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
goto out_unregister_line_notifier;
file->private_data = cdev;
+ cdev->fp = file;
ret = nonseekable_open(inode, file);
if (ret)
@@ -2737,8 +2752,9 @@ out_unregister_device_notifier:
blocking_notifier_chain_unregister(&gdev->device_notifier,
&cdev->device_unregistered_nb);
out_unregister_line_notifier:
- blocking_notifier_chain_unregister(&gdev->line_state_notifier,
- &cdev->lineinfo_changed_nb);
+ 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);
@@ -2751,18 +2767,21 @@ 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;
- bitmap_free(cdev->watched_lines);
blocking_notifier_chain_unregister(&gdev->device_notifier,
&cdev->device_unregistered_nb);
- blocking_notifier_chain_unregister(&gdev->line_state_notifier,
- &cdev->lineinfo_changed_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);
gpio_device_put(gdev);
kfree(cdev);
@@ -2775,7 +2794,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,
@@ -2784,24 +2802,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;
+
+ chip_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);
}