Commit 1396f154 authored by Philippe Gerum's avatar Philippe Gerum
Browse files

drivers/gpio: evl: add out-of-band interface (CDEV_V1)


Signed-off-by: default avatarPhilippe Gerum <rpm@xenomai.org>
parent 3451d2f3
......@@ -47,6 +47,16 @@ config GPIOLIB_IRQCHIP
select IRQ_DOMAIN
bool
config GPIOLIB_OOB
bool "Out-of-band GPIO calls"
depends on EVL
select CONFIG_GPIO_CDEV
select CONFIG_GPIO_CDEV_V1
help
Enable support for out-of-band GPIO line handling requests via
the user-space interface, which are served by the EVL real-time
core.
config DEBUG_GPIO
bool "Debug GPIO calls"
depends on DEBUG_KERNEL
......
......@@ -25,6 +25,7 @@
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <uapi/linux/gpio.h>
#include <evl/devices/gpio.h>
#include "gpiolib.h"
#include "gpiolib-cdev.h"
......@@ -71,6 +72,8 @@ struct linehandle_state {
const char *label;
struct gpio_desc *descs[GPIOHANDLES_MAX];
u32 num_descs;
u32 lflags;
struct linehandle_oob_state oob_state;
};
#define GPIOHANDLE_REQUEST_VALID_FLAGS \
......@@ -81,7 +84,13 @@ struct linehandle_state {
GPIOHANDLE_REQUEST_BIAS_PULL_DOWN | \
GPIOHANDLE_REQUEST_BIAS_DISABLE | \
GPIOHANDLE_REQUEST_OPEN_DRAIN | \
GPIOHANDLE_REQUEST_OPEN_SOURCE)
GPIOHANDLE_REQUEST_OPEN_SOURCE | \
(IS_ENABLED(CONFIG_GPIOLIB_OOB) ? GPIOHANDLE_REQUEST_OOB : 0))
static inline bool oob_handling_requested(u32 lflags)
{
return IS_ENABLED(CONFIG_GPIOLIB_OOB) && lflags & GPIOHANDLE_REQUEST_OOB;
}
static int linehandle_validate_flags(u32 flags)
{
......@@ -254,6 +263,54 @@ static long linehandle_ioctl_compat(struct file *file, unsigned int cmd,
}
#endif
#ifdef CONFIG_GPIOLIB_OOB
static long linehandle_oob_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct linehandle_state *lh = file->private_data;
DECLARE_BITMAP(valmap, GPIOHANDLES_MAX);
struct gpio_chip *gc = lh->gdev->chip;
void __user *ip = (void __user *)arg;
struct gpiohandle_data ghd;
int i, ret;
if (!oob_handling_requested(lh->lflags))
return -EPERM;
if (cmd == GPIOHANDLE_GET_LINE_VALUES_IOCTL) {
ret = gpiod_get_array_value_oob(gc, valmap,
lh->num_descs, lh->descs);
if (ret)
return ret;
memset(&ghd, 0, sizeof(ghd));
for (i = 0; i < lh->num_descs; i++)
ghd.values[i] = test_bit(i, valmap);
if (raw_copy_to_user(ip, &ghd, sizeof(ghd)))
return -EFAULT;
return 0;
} else if (cmd == GPIOHANDLE_SET_LINE_VALUES_IOCTL) {
if (!test_bit(FLAG_IS_OUT, &lh->descs[0]->flags))
return -EPERM;
if (raw_copy_from_user(&ghd, ip, sizeof(ghd)))
return -EFAULT;
for (i = 0; i < lh->num_descs; i++)
__assign_bit(i, valmap, ghd.values[i]);
return gpiod_set_array_value_oob(gc, valmap,
lh->num_descs, lh->descs);
}
return -EINVAL;
}
#endif /* CONFIG_GPIOLIB_OOB */
static void linehandle_free(struct linehandle_state *lh)
{
int i;
......@@ -268,7 +325,12 @@ static void linehandle_free(struct linehandle_state *lh)
static int linehandle_release(struct inode *inode, struct file *file)
{
linehandle_free(file->private_data);
struct linehandle_state *lh = file->private_data;
if (oob_handling_requested(lh->lflags))
evl_release_file(&lh->oob_state.efile);
linehandle_free(lh);
return 0;
}
......@@ -277,9 +339,15 @@ static const struct file_operations linehandle_fileops = {
.owner = THIS_MODULE,
.llseek = noop_llseek,
.unlocked_ioctl = linehandle_ioctl,
#ifdef CONFIG_GPIOLIB_OOB
.oob_ioctl = linehandle_oob_ioctl,
#endif
#ifdef CONFIG_COMPAT
.compat_ioctl = linehandle_ioctl_compat,
#endif
#ifdef CONFIG_GPIOLIB_OOB
.compat_oob_ioctl = compat_ptr_oob_ioctl,
#endif
};
static int linehandle_create(struct gpio_device *gdev, void __user *ip)
......@@ -297,6 +365,18 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
lflags = handlereq.flags;
if (oob_handling_requested(lflags)) {
if (gdev->chip->ngpio > CONFIG_GPIOLIB_FASTPATH_LIMIT) {
chip_warn(gdev->chip,
"too many lines for out-of-band handling"
" (%u > %u fastpath)\n",
gdev->chip->ngpio, CONFIG_GPIOLIB_FASTPATH_LIMIT);
return -EOPNOTSUPP;
}
if (gdev->chip->can_sleep)
return -EOPNOTSUPP;
}
ret = linehandle_validate_flags(lflags);
if (ret)
return ret;
......@@ -305,6 +385,7 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
if (!lh)
return -ENOMEM;
lh->gdev = gdev;
lh->lflags = lflags;
get_device(&gdev->dev);
if (handlereq.consumer_label[0] != '\0') {
......@@ -378,6 +459,14 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
goto out_put_unused_fd;
}
if (oob_handling_requested(lflags)) {
ret = evl_open_file(&lh->oob_state.efile, file);
if (ret) {
fput(file);
goto out_put_unused_fd;
}
}
handlereq.fd = fd;
if (copy_to_user(ip, &handlereq, sizeof(handlereq))) {
/*
......@@ -1479,11 +1568,13 @@ struct lineevent_state {
struct gpio_device *gdev;
const char *label;
struct gpio_desc *desc;
u32 lflags;
u32 eflags;
int irq;
wait_queue_head_t wait;
DECLARE_KFIFO(events, struct gpioevent_data, 16);
u64 timestamp;
struct lineevent_oob_state oob_state;
};
#define GPIOEVENT_REQUEST_VALID_FLAGS \
......@@ -1520,6 +1611,9 @@ static ssize_t lineevent_read(struct file *file,
ssize_t ge_size;
int ret;
if (oob_handling_requested(le->lflags))
return -EPERM;
/*
* When compatible system call is being used the struct gpioevent_data,
* in case of at least ia32, has different size due to the alignment
......@@ -1577,6 +1671,143 @@ static ssize_t lineevent_read(struct file *file,
return bytes_read;
}
static irqreturn_t lineevent_read_pin(struct lineevent_state *le,
struct gpioevent_data *ge,
bool cansleep)
{
int level;
if (le->eflags & GPIOEVENT_REQUEST_RISING_EDGE
&& le->eflags & GPIOEVENT_REQUEST_FALLING_EDGE) {
if (cansleep)
level = gpiod_get_value_cansleep(le->desc);
else
level = gpiod_get_value(le->desc);
if (level)
/* Emit low-to-high event */
ge->id = GPIOEVENT_EVENT_RISING_EDGE;
else
/* Emit high-to-low event */
ge->id = GPIOEVENT_EVENT_FALLING_EDGE;
} else if (le->eflags & GPIOEVENT_REQUEST_RISING_EDGE) {
/* Emit low-to-high event */
ge->id = GPIOEVENT_EVENT_RISING_EDGE;
} else if (le->eflags & GPIOEVENT_REQUEST_FALLING_EDGE) {
/* Emit high-to-low event */
ge->id = GPIOEVENT_EVENT_FALLING_EDGE;
} else {
return IRQ_NONE;
}
return IRQ_HANDLED;
}
#ifdef CONFIG_GPIOLIB_OOB
static irqreturn_t lineevent_oob_irq_handler(int irq, void *p)
{
struct lineevent_state *le = p;
struct gpioevent_data ge;
ge.timestamp = evl_ktime_monotonic();
if (lineevent_read_pin(le, &ge, false) == IRQ_NONE)
return IRQ_NONE;
evl_spin_lock(&le->oob_state.wait.lock);
kfifo_put(&le->events, ge);
evl_wake_up_head(&le->oob_state.wait);
evl_signal_poll_events(&le->oob_state.poll_head, POLLIN|POLLRDNORM);
evl_spin_unlock(&le->oob_state.wait.lock);
return IRQ_HANDLED;
}
static __poll_t lineevent_oob_poll(struct file *file,
struct oob_poll_wait *wait)
{
struct lineevent_state *le = file->private_data;
unsigned long flags;
__poll_t ready = 0;
evl_poll_watch(&le->oob_state.poll_head, wait, NULL);
evl_spin_lock_irqsave(&le->oob_state.wait.lock, flags);
if (!kfifo_is_empty(&le->events))
ready |= POLLIN|POLLRDNORM;
evl_spin_unlock_irqrestore(&le->oob_state.wait.lock, flags);
return ready;
}
static ssize_t lineevent_oob_read(struct file *file,
char __user *buf,
size_t count)
{
struct lineevent_state *le = file->private_data;
struct gpioevent_data ge = { 0 };
unsigned long flags;
int ret;
if (count < sizeof(struct gpioevent_data))
return -EINVAL;
if (!oob_handling_requested(le->lflags))
return -EPERM;
do {
evl_spin_lock_irqsave(&le->oob_state.wait.lock, flags);
ret = kfifo_get(&le->events, &ge);
/*
* Silly work around to address a false positive
* enabling -Wmaybe-uninitialized w/ gcc 8.3.1.
*/
if (!ret)
ret = 0;
evl_spin_unlock_irqrestore(&le->oob_state.wait.lock, flags);
if (ret) {
ret = raw_copy_to_user(buf, &ge, sizeof(ge));
return ret ? -EFAULT : sizeof(ge);
}
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = evl_wait_event(&le->oob_state.wait,
!kfifo_is_empty(&le->events));
} while (!ret);
return ret;
}
static int lineevent_init_oob_state(struct lineevent_state *le,
int irqflags)
{
evl_init_wait(&le->oob_state.wait, &evl_mono_clock, EVL_WAIT_PRIO);
evl_init_poll_head(&le->oob_state.poll_head);
return request_irq(le->irq,
lineevent_oob_irq_handler,
irqflags | IRQF_OOB,
le->label,
le);
}
#else
static inline int lineevent_init_oob_state(struct lineevent_state *le,
int irqflags)
{
return -EINVAL;
}
#endif /* !CONFIG_GPIOLIB_OOB */
static void lineevent_free(struct lineevent_state *le)
{
if (le->irq)
......@@ -1590,7 +1821,12 @@ static void lineevent_free(struct lineevent_state *le)
static int lineevent_release(struct inode *inode, struct file *file)
{
lineevent_free(file->private_data);
struct lineevent_state *le = file->private_data;
if (oob_handling_requested(le->lflags))
evl_release_file(&le->oob_state.efile);
lineevent_free(le);
return 0;
}
......@@ -1634,6 +1870,10 @@ static long lineevent_ioctl_compat(struct file *file, unsigned int cmd,
static const struct file_operations lineevent_fileops = {
.release = lineevent_release,
.read = lineevent_read,
#ifdef CONFIG_GPIOLIB_OOB
.oob_read = lineevent_oob_read,
.oob_poll = lineevent_oob_poll,
#endif
.poll = lineevent_poll,
.owner = THIS_MODULE,
.llseek = noop_llseek,
......@@ -1661,25 +1901,8 @@ static irqreturn_t lineevent_irq_thread(int irq, void *p)
else
ge.timestamp = le->timestamp;
if (le->eflags & GPIOEVENT_REQUEST_RISING_EDGE
&& le->eflags & GPIOEVENT_REQUEST_FALLING_EDGE) {
int level = gpiod_get_value_cansleep(le->desc);
if (level)
/* Emit low-to-high event */
ge.id = GPIOEVENT_EVENT_RISING_EDGE;
else
/* Emit high-to-low event */
ge.id = GPIOEVENT_EVENT_FALLING_EDGE;
} else if (le->eflags & GPIOEVENT_REQUEST_RISING_EDGE) {
/* Emit low-to-high event */
ge.id = GPIOEVENT_EVENT_RISING_EDGE;
} else if (le->eflags & GPIOEVENT_REQUEST_FALLING_EDGE) {
/* Emit high-to-low event */
ge.id = GPIOEVENT_EVENT_FALLING_EDGE;
} else {
if (lineevent_read_pin(le, &ge, true) == IRQ_NONE)
return IRQ_NONE;
}
ret = kfifo_in_spinlocked_noirqsave(&le->events, &ge,
1, &le->wait.lock);
......@@ -1769,6 +1992,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
goto out_free_le;
le->desc = desc;
le->eflags = eflags;
le->lflags = lflags;
linehandle_flags_to_desc_flags(lflags, &desc->flags);
......@@ -1795,19 +2019,31 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
irqflags |= IRQF_ONESHOT;
INIT_KFIFO(le->events);
init_waitqueue_head(&le->wait);
/* Request a thread to read the events */
ret = request_threaded_irq(le->irq,
lineevent_irq_handler,
lineevent_irq_thread,
irqflags,
le->label,
le);
if (ret)
goto out_free_le;
if (oob_handling_requested(lflags)) {
if (desc->gdev->chip->can_sleep) {
ret = -EOPNOTSUPP;
goto out_free_le;
}
ret = lineevent_init_oob_state(le, irqflags);
if (ret)
goto out_free_le;
} else {
irqflags |= IRQF_ONESHOT;
init_waitqueue_head(&le->wait);
/* Request a thread to read the events */
ret = request_threaded_irq(le->irq,
lineevent_irq_handler,
lineevent_irq_thread,
irqflags,
le->label,
le);
if (ret)
goto out_free_le;
}
fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ret = fd;
goto out_free_le;
......@@ -1822,21 +2058,31 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
goto out_put_unused_fd;
}
if (oob_handling_requested(lflags)) {
ret = evl_open_file(&le->oob_state.efile, file);
if (ret)
goto out_put_file;
}
eventreq.fd = fd;
if (copy_to_user(ip, &eventreq, sizeof(eventreq))) {
/*
* fput() will trigger the release() callback, so do not go onto
* the regular error cleanup path here.
*/
fput(file);
put_unused_fd(fd);
return -EFAULT;
if (oob_handling_requested(lflags))
evl_release_file(&le->oob_state.efile);
ret = -EFAULT;
goto out_put_file;
}
fd_install(fd, file);
return 0;
out_put_file:
fput(file);
out_put_unused_fd:
put_unused_fd(fd);
out_free_le:
......
......@@ -2905,6 +2905,78 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
return 0;
}
#ifdef CONFIG_GPIOLIB_OOB
int gpiod_get_array_value_oob(struct gpio_chip *gc,
unsigned long *value_bitmap,
u32 num_descs,
struct gpio_desc **desc_array)
{
unsigned long mask[2 * BITS_TO_LONGS(CONFIG_GPIOLIB_FASTPATH_LIMIT)];
unsigned long *bits = mask + BITS_TO_LONGS(gc->ngpio);
const struct gpio_desc *desc;
int ret, n, hwgpio, value;
bitmap_zero(mask, gc->ngpio);
for (n = 0; n < num_descs; n++) {
desc = desc_array[n];
hwgpio = gpio_chip_hwgpio(desc);
__set_bit(hwgpio, mask);
}
ret = gpio_chip_get_multiple(gc, mask, bits);
if (ret)
return ret;
for (n = 0; n < num_descs; n++) {
desc = desc_array[n];
hwgpio = gpio_chip_hwgpio(desc);
value = test_bit(hwgpio, bits);
/* We assume non-raw mode. */
if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
value = !value;
__assign_bit(n, value_bitmap, value);
trace_gpio_value(desc_to_gpio(desc), 1, value);
}
return 0;
}
int gpiod_set_array_value_oob(struct gpio_chip *gc,
const unsigned long *value_bitmap,
u32 num_descs,
struct gpio_desc **desc_array)
{
unsigned long mask[2 * BITS_TO_LONGS(CONFIG_GPIOLIB_FASTPATH_LIMIT)];
unsigned long *bits = mask + BITS_TO_LONGS(gc->ngpio);
const struct gpio_desc *desc;
int n, hwgpio, value;
bitmap_zero(mask, gc->ngpio);
for (n = 0; n < num_descs; n++) {
desc = desc_array[n];
hwgpio = gpio_chip_hwgpio(desc);
__set_bit(hwgpio, mask);
/* We assume non-raw mode. */
value = test_bit(n, value_bitmap);
if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
value = !value;
if (value)
__set_bit(hwgpio, bits);
else
__clear_bit(hwgpio, bits);
trace_gpio_value(desc_to_gpio(desc), 0, value);
}
gpio_chip_set_multiple(gc, mask, bits);
return 0;
}
#endif /* CONFIG_GPIOLIB_OOB */
/**
* gpiod_set_raw_value() - assign a gpio's raw value
* @desc: gpio whose value will be assigned
......
......@@ -93,6 +93,17 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
struct gpio_array *array_info,
unsigned long *value_bitmap);
#ifdef CONFIG_GPIOLIB_OOB
int gpiod_get_array_value_oob(struct gpio_chip *gc,
unsigned long *value_bitmap,
u32 num_descs,
struct gpio_desc **desc_array);
int gpiod_set_array_value_oob(struct gpio_chip *gc,
const unsigned long *value_bitmap,
u32 num_descs,
struct gpio_desc **desc_array);
#endif
extern spinlock_t gpio_lock;
extern struct list_head gpio_devices;
......
/*
* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2019 Philippe Gerum <rpm@xenomai.org>
*/
#ifndef _EVL_DEVICES_GPIO_H
#define _EVL_DEVICES_GPIO_H
#include <evl/device.h>
#include <uapi/evl/devices/gpio.h>
#ifdef CONFIG_EVL
#include <evl/poll.h>
#include <evl/wait.h>
#include <evl/irq.h>
struct lineevent_oob_state {
struct evl_file efile;
struct evl_poll_head poll_head;
struct evl_wait_queue wait;
hard_spinlock_t lock;
};
#else
struct lineevent_oob_state {
struct evl_file efile;
};
#endif
struct linehandle_oob_state {
struct evl_file efile;
};
#endif /* !_EVL_DEVICES_GPIO_H */
/*
* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
*/
#ifndef _EVL_UAPI_DEVICES_GPIO_H
#define _EVL_UAPI_DEVICES_GPIO_H
#define GPIOHANDLE_REQUEST_OOB (1UL << 5)
#endif /* !_EVL_UAPI_DEVICES_GPIO_H */