Commit b2929a9c authored by Linus Walleij's avatar Linus Walleij

Merge tag 'gpio-updates-for-v5.7-part1' of...

Merge tag 'gpio-updates-for-v5.7-part1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux into devel

gpio updates for v5.7 part 1

- make irqs optional in gpio-pxa
- improve the logic behind the get() and set() callbacks in gpio-wcd934x
- add new kfifo helpers (acked by kfifo maintainer)
- rework the locking mechanism for lineevent kfifo
- implement a new ioctl() for watching changes on GPIO lines
parents 046e14af 33f0c47b
......@@ -652,8 +652,8 @@ static int pxa_gpio_probe(struct platform_device *pdev)
if (!pchip->irqdomain)
return -ENOMEM;
irq0 = platform_get_irq_byname(pdev, "gpio0");
irq1 = platform_get_irq_byname(pdev, "gpio1");
irq0 = platform_get_irq_byname_optional(pdev, "gpio0");
irq1 = platform_get_irq_byname_optional(pdev, "gpio1");
irq_mux = platform_get_irq_byname(pdev, "gpio_mux");
if ((irq0 > 0 && irq1 <= 0) || (irq0 <= 0 && irq1 > 0)
|| (irq_mux <= 0))
......
......@@ -57,16 +57,19 @@ static int wcd_gpio_direction_output(struct gpio_chip *chip, unsigned int pin,
static int wcd_gpio_get(struct gpio_chip *chip, unsigned int pin)
{
struct wcd_gpio_data *data = gpiochip_get_data(chip);
int value;
unsigned int value;
regmap_read(data->map, WCD_REG_VAL_CTL_OFFSET, &value);
return !!(value && WCD_PIN_MASK(pin));
return !!(value & WCD_PIN_MASK(pin));
}
static void wcd_gpio_set(struct gpio_chip *chip, unsigned int pin, int val)
{
wcd_gpio_direction_output(chip, pin, val);
struct wcd_gpio_data *data = gpiochip_get_data(chip);
regmap_update_bits(data->map, WCD_REG_VAL_CTL_OFFSET,
WCD_PIN_MASK(pin), val ? WCD_PIN_MASK(pin) : 0);
}
static int wcd_gpio_probe(struct platform_device *pdev)
......
......@@ -546,6 +546,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
if (ret)
return ret;
}
atomic_notifier_call_chain(&desc->gdev->notifier,
GPIOLINE_CHANGED_CONFIG, desc);
}
return 0;
}
......@@ -787,8 +790,6 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)
* @irq: the interrupt that trigger in response to events on this GPIO
* @wait: wait queue that handles blocking reads of events
* @events: KFIFO for the GPIO events
* @read_lock: mutex lock to protect reads from colliding with adding
* new events to the FIFO
* @timestamp: cache for the timestamp storing it between hardirq
* and IRQ thread, used to bring the timestamp close to the actual
* event
......@@ -801,7 +802,6 @@ struct lineevent_state {
int irq;
wait_queue_head_t wait;
DECLARE_KFIFO(events, struct gpioevent_data, 16);
struct mutex read_lock;
u64 timestamp;
};
......@@ -817,7 +817,7 @@ static __poll_t lineevent_poll(struct file *filep,
poll_wait(filep, &le->wait, wait);
if (!kfifo_is_empty(&le->events))
if (!kfifo_is_empty_spinlocked_noirqsave(&le->events, &le->wait.lock))
events = EPOLLIN | EPOLLRDNORM;
return events;
......@@ -830,43 +830,52 @@ static ssize_t lineevent_read(struct file *filep,
loff_t *f_ps)
{
struct lineevent_state *le = filep->private_data;
unsigned int copied;
struct gpioevent_data event;
ssize_t bytes_read = 0;
int ret;
if (count < sizeof(struct gpioevent_data))
if (count < sizeof(event))
return -EINVAL;
do {
spin_lock(&le->wait.lock);
if (kfifo_is_empty(&le->events)) {
if (filep->f_flags & O_NONBLOCK)
if (bytes_read) {
spin_unlock(&le->wait.lock);
return bytes_read;
}
if (filep->f_flags & O_NONBLOCK) {
spin_unlock(&le->wait.lock);
return -EAGAIN;
}
ret = wait_event_interruptible(le->wait,
ret = wait_event_interruptible_locked(le->wait,
!kfifo_is_empty(&le->events));
if (ret)
if (ret) {
spin_unlock(&le->wait.lock);
return ret;
}
}
if (mutex_lock_interruptible(&le->read_lock))
return -ERESTARTSYS;
ret = kfifo_to_user(&le->events, buf, count, &copied);
mutex_unlock(&le->read_lock);
if (ret)
return ret;
ret = kfifo_out(&le->events, &event, 1);
spin_unlock(&le->wait.lock);
if (ret != 1) {
/*
* If we couldn't read anything from the fifo (a different
* thread might have been faster) we either return -EAGAIN if
* the file descriptor is non-blocking, otherwise we go back to
* sleep and wait for more data to arrive.
* This should never happen - we were holding the lock
* from the moment we learned the fifo is no longer
* empty until now.
*/
if (copied == 0 && (filep->f_flags & O_NONBLOCK))
return -EAGAIN;
ret = -EIO;
break;
}
} while (copied == 0);
if (copy_to_user(buf + bytes_read, &event, sizeof(event)))
return -EFAULT;
bytes_read += sizeof(event);
} while (count >= bytes_read + sizeof(event));
return copied;
return bytes_read;
}
static int lineevent_release(struct inode *inode, struct file *filep)
......@@ -968,9 +977,12 @@ static irqreturn_t lineevent_irq_thread(int irq, void *p)
return IRQ_NONE;
}
ret = kfifo_put(&le->events, ge);
ret = kfifo_in_spinlocked_noirqsave(&le->events, &ge,
1, &le->wait.lock);
if (ret)
wake_up_poll(&le->wait, EPOLLIN);
else
pr_debug_ratelimited("event FIFO is full - event dropped\n");
return IRQ_HANDLED;
}
......@@ -1083,7 +1095,6 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
INIT_KFIFO(le->events);
init_waitqueue_head(&le->wait);
mutex_init(&le->read_lock);
/* Request a thread to read the events */
ret = request_threaded_irq(le->irq,
......@@ -1139,14 +1150,79 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
return ret;
}
static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
struct gpioline_info *info)
{
struct gpio_chip *chip = desc->gdev->chip;
unsigned long flags;
spin_lock_irqsave(&gpio_lock, flags);
if (desc->name) {
strncpy(info->name, desc->name, sizeof(info->name));
info->name[sizeof(info->name) - 1] = '\0';
} else {
info->name[0] = '\0';
}
if (desc->label) {
strncpy(info->consumer, desc->label, sizeof(info->consumer));
info->consumer[sizeof(info->consumer) - 1] = '\0';
} else {
info->consumer[0] = '\0';
}
/*
* Userspace only need to know that the kernel is using this GPIO so
* it can't use 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) ||
!pinctrl_gpio_can_use_line(chip->base + info->line_offset))
info->flags |= GPIOLINE_FLAG_KERNEL;
if (test_bit(FLAG_IS_OUT, &desc->flags))
info->flags |= GPIOLINE_FLAG_IS_OUT;
if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
info->flags |= GPIOLINE_FLAG_ACTIVE_LOW;
if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
info->flags |= (GPIOLINE_FLAG_OPEN_DRAIN |
GPIOLINE_FLAG_IS_OUT);
if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
info->flags |= (GPIOLINE_FLAG_OPEN_SOURCE |
GPIOLINE_FLAG_IS_OUT);
if (test_bit(FLAG_BIAS_DISABLE, &desc->flags))
info->flags |= GPIOLINE_FLAG_BIAS_DISABLE;
if (test_bit(FLAG_PULL_DOWN, &desc->flags))
info->flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
if (test_bit(FLAG_PULL_UP, &desc->flags))
info->flags |= GPIOLINE_FLAG_BIAS_PULL_UP;
spin_unlock_irqrestore(&gpio_lock, flags);
}
struct gpio_chardev_data {
struct gpio_device *gdev;
wait_queue_head_t wait;
DECLARE_KFIFO(events, struct gpioline_info_changed, 32);
struct notifier_block lineinfo_changed_nb;
unsigned long *watched_lines;
};
/*
* gpio_ioctl() - ioctl handler for the GPIO chardev
*/
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct gpio_device *gdev = filp->private_data;
struct gpio_chardev_data *priv = filp->private_data;
struct gpio_device *gdev = priv->gdev;
struct gpio_chip *chip = gdev->chip;
void __user *ip = (void __user *)arg;
struct gpio_desc *desc;
__u32 offset;
/* We fail any subsequent ioctl():s when the chip is gone */
if (!chip)
......@@ -1168,9 +1244,9 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
if (copy_to_user(ip, &chipinfo, sizeof(chipinfo)))
return -EFAULT;
return 0;
} else if (cmd == GPIO_GET_LINEINFO_IOCTL) {
} else if (cmd == GPIO_GET_LINEINFO_IOCTL ||
cmd == GPIO_GET_LINEINFO_WATCH_IOCTL) {
struct gpioline_info lineinfo;
struct gpio_desc *desc;
if (copy_from_user(&lineinfo, ip, sizeof(lineinfo)))
return -EFAULT;
......@@ -1179,57 +1255,29 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
if (IS_ERR(desc))
return PTR_ERR(desc);
if (desc->name) {
strncpy(lineinfo.name, desc->name,
sizeof(lineinfo.name));
lineinfo.name[sizeof(lineinfo.name)-1] = '\0';
} else {
lineinfo.name[0] = '\0';
}
if (desc->label) {
strncpy(lineinfo.consumer, desc->label,
sizeof(lineinfo.consumer));
lineinfo.consumer[sizeof(lineinfo.consumer)-1] = '\0';
} else {
lineinfo.consumer[0] = '\0';
}
/*
* Userspace only need to know that the kernel is using
* this GPIO so it can't use it.
*/
lineinfo.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) ||
!pinctrl_gpio_can_use_line(chip->base + lineinfo.line_offset))
lineinfo.flags |= GPIOLINE_FLAG_KERNEL;
if (test_bit(FLAG_IS_OUT, &desc->flags))
lineinfo.flags |= GPIOLINE_FLAG_IS_OUT;
if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
lineinfo.flags |= GPIOLINE_FLAG_ACTIVE_LOW;
if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
lineinfo.flags |= (GPIOLINE_FLAG_OPEN_DRAIN |
GPIOLINE_FLAG_IS_OUT);
if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
lineinfo.flags |= (GPIOLINE_FLAG_OPEN_SOURCE |
GPIOLINE_FLAG_IS_OUT);
if (test_bit(FLAG_BIAS_DISABLE, &desc->flags))
lineinfo.flags |= GPIOLINE_FLAG_BIAS_DISABLE;
if (test_bit(FLAG_PULL_DOWN, &desc->flags))
lineinfo.flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
if (test_bit(FLAG_PULL_UP, &desc->flags))
lineinfo.flags |= GPIOLINE_FLAG_BIAS_PULL_UP;
gpio_desc_to_lineinfo(desc, &lineinfo);
if (copy_to_user(ip, &lineinfo, sizeof(lineinfo)))
return -EFAULT;
if (cmd == GPIO_GET_LINEINFO_WATCH_IOCTL)
set_bit(desc_to_gpio(desc), priv->watched_lines);
return 0;
} else if (cmd == GPIO_GET_LINEHANDLE_IOCTL) {
return linehandle_create(gdev, ip);
} else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
return lineevent_create(gdev, ip);
} else if (cmd == GPIO_GET_LINEINFO_UNWATCH_IOCTL) {
if (copy_from_user(&offset, ip, sizeof(offset)))
return -EFAULT;
desc = gpiochip_get_desc(chip, offset);
if (IS_ERR(desc))
return PTR_ERR(desc);
clear_bit(desc_to_gpio(desc), &desc->flags);
return 0;
}
return -EINVAL;
}
......@@ -1242,6 +1290,101 @@ static long gpio_ioctl_compat(struct file *filp, unsigned int cmd,
}
#endif
static struct gpio_chardev_data *
to_gpio_chardev_data(struct notifier_block *nb)
{
return container_of(nb, struct gpio_chardev_data, lineinfo_changed_nb);
}
static int lineinfo_changed_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
struct gpio_chardev_data *priv = to_gpio_chardev_data(nb);
struct gpioline_info_changed chg;
struct gpio_desc *desc = data;
int ret;
if (!test_bit(desc_to_gpio(desc), priv->watched_lines))
return NOTIFY_DONE;
memset(&chg, 0, sizeof(chg));
chg.info.line_offset = gpio_chip_hwgpio(desc);
chg.event_type = action;
chg.timestamp = ktime_get_ns();
gpio_desc_to_lineinfo(desc, &chg.info);
ret = kfifo_in_spinlocked(&priv->events, &chg, 1, &priv->wait.lock);
if (ret)
wake_up_poll(&priv->wait, EPOLLIN);
else
pr_debug_ratelimited("lineinfo event FIFO is full - event dropped\n");
return NOTIFY_OK;
}
static __poll_t lineinfo_watch_poll(struct file *filep,
struct poll_table_struct *pollt)
{
struct gpio_chardev_data *priv = filep->private_data;
__poll_t events = 0;
poll_wait(filep, &priv->wait, pollt);
if (!kfifo_is_empty_spinlocked_noirqsave(&priv->events,
&priv->wait.lock))
events = EPOLLIN | EPOLLRDNORM;
return events;
}
static ssize_t lineinfo_watch_read(struct file *filep, char __user *buf,
size_t count, loff_t *off)
{
struct gpio_chardev_data *priv = filep->private_data;
struct gpioline_info_changed event;
ssize_t bytes_read = 0;
int ret;
if (count < sizeof(event))
return -EINVAL;
do {
spin_lock(&priv->wait.lock);
if (kfifo_is_empty(&priv->events)) {
if (bytes_read) {
spin_unlock(&priv->wait.lock);
return bytes_read;
}
if (filep->f_flags & O_NONBLOCK) {
spin_unlock(&priv->wait.lock);
return -EAGAIN;
}
ret = wait_event_interruptible_locked(priv->wait,
!kfifo_is_empty(&priv->events));
if (ret) {
spin_unlock(&priv->wait.lock);
return ret;
}
}
ret = kfifo_out(&priv->events, &event, 1);
spin_unlock(&priv->wait.lock);
if (ret != 1) {
ret = -EIO;
break;
/* We should never get here. See lineevent_read(). */
}
if (copy_to_user(buf + bytes_read, &event, sizeof(event)))
return -EFAULT;
bytes_read += sizeof(event);
} while (count >= bytes_read + sizeof(event));
return bytes_read;
}
/**
* gpio_chrdev_open() - open the chardev for ioctl operations
* @inode: inode for this chardev
......@@ -1252,14 +1395,48 @@ static int gpio_chrdev_open(struct inode *inode, struct file *filp)
{
struct gpio_device *gdev = container_of(inode->i_cdev,
struct gpio_device, chrdev);
struct gpio_chardev_data *priv;
int ret = -ENOMEM;
/* Fail on open if the backing gpiochip is gone */
if (!gdev->chip)
return -ENODEV;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL);
if (!priv->watched_lines)
goto out_free_priv;
init_waitqueue_head(&priv->wait);
INIT_KFIFO(priv->events);
priv->gdev = gdev;
priv->lineinfo_changed_nb.notifier_call = lineinfo_changed_notify;
ret = atomic_notifier_chain_register(&gdev->notifier,
&priv->lineinfo_changed_nb);
if (ret)
goto out_free_bitmap;
get_device(&gdev->dev);
filp->private_data = gdev;
filp->private_data = priv;
ret = nonseekable_open(inode, filp);
if (ret)
goto out_unregister_notifier;
return nonseekable_open(inode, filp);
return ret;
out_unregister_notifier:
atomic_notifier_chain_unregister(&gdev->notifier,
&priv->lineinfo_changed_nb);
out_free_bitmap:
bitmap_free(priv->watched_lines);
out_free_priv:
kfree(priv);
return ret;
}
/**
......@@ -1270,17 +1447,23 @@ static int gpio_chrdev_open(struct inode *inode, struct file *filp)
*/
static int gpio_chrdev_release(struct inode *inode, struct file *filp)
{
struct gpio_device *gdev = container_of(inode->i_cdev,
struct gpio_device, chrdev);
struct gpio_chardev_data *priv = filp->private_data;
struct gpio_device *gdev = priv->gdev;
bitmap_free(priv->watched_lines);
atomic_notifier_chain_unregister(&gdev->notifier,
&priv->lineinfo_changed_nb);
put_device(&gdev->dev);
kfree(priv);
return 0;
}
static const struct file_operations gpio_fileops = {
.release = gpio_chrdev_release,
.open = gpio_chrdev_open,
.poll = lineinfo_watch_poll,
.read = lineinfo_watch_read,
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_ioctl = gpio_ioctl,
......@@ -1491,6 +1674,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,
spin_unlock_irqrestore(&gpio_lock, flags);
ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);
#ifdef CONFIG_PINCTRL
INIT_LIST_HEAD(&gdev->pin_ranges);
#endif
......@@ -2823,6 +3008,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
}
done:
spin_unlock_irqrestore(&gpio_lock, flags);
atomic_notifier_call_chain(&desc->gdev->notifier,
GPIOLINE_CHANGED_REQUESTED, desc);
return ret;
}
......@@ -2920,6 +3107,9 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
}
spin_unlock_irqrestore(&gpio_lock, flags);
atomic_notifier_call_chain(&desc->gdev->notifier,
GPIOLINE_CHANGED_RELEASED, desc);
return ret;
}
......
......@@ -56,6 +56,7 @@ struct gpio_device {
const char *label;
void *data;
struct list_head list;
struct atomic_notifier_head notifier;
#ifdef CONFIG_PINCTRL
/*
......
......@@ -246,6 +246,37 @@ __kfifo_int_must_check_helper(int val)
__tmpq->kfifo.in == __tmpq->kfifo.out; \
})
/**
* kfifo_is_empty_spinlocked - returns true if the fifo is empty using
* a spinlock for locking
* @fifo: address of the fifo to be used
* @lock: spinlock to be used for locking
*/
#define kfifo_is_empty_spinlocked(fifo, lock) \
({ \
unsigned long __flags; \
bool __ret; \
spin_lock_irqsave(lock, __flags); \
__ret = kfifo_is_empty(fifo); \
spin_unlock_irqrestore(lock, __flags); \
__ret; \
})
/**
* kfifo_is_empty_spinlocked_noirqsave - returns true if the fifo is empty
* using a spinlock for locking, doesn't disable interrupts
* @fifo: address of the fifo to be used
* @lock: spinlock to be used for locking
*/
#define kfifo_is_empty_spinlocked_noirqsave(fifo, lock) \
({ \
bool __ret; \
spin_lock(lock); \
__ret = kfifo_is_empty(fifo); \
spin_unlock(lock); \
__ret; \
})
/**
* kfifo_is_full - returns true if the fifo is full
* @fifo: address of the fifo to be used
......@@ -517,6 +548,26 @@ __kfifo_uint_must_check_helper( \
__ret; \
})
/**
* kfifo_in_spinlocked_noirqsave - put data into fifo using a spinlock for
* locking, don't disable interrupts
* @fifo: address of the fifo to be used
* @buf: the data to be added
* @n: number of elements to be added
* @lock: pointer to the spinlock to use for locking
*
* This is a variant of kfifo_in_spinlocked() but uses spin_lock/unlock()
* for locking and doesn't disable interrupts.
*/
#define kfifo_in_spinlocked_noirqsave(fifo, buf, n, lock) \
({ \
unsigned int __ret; \
spin_lock(lock); \
__ret = kfifo_in(fifo, buf, n); \
spin_unlock(lock); \
__ret; \
})
/* alias for kfifo_in_spinlocked, will be removed in a future release */
#define kfifo_in_locked(fifo, buf, n, lock) \
kfifo_in_spinlocked(fifo, buf, n, lock)
......@@ -569,6 +620,28 @@ __kfifo_uint_must_check_helper( \
}) \
)
/**
* kfifo_out_spinlocked_noirqsave - get data from the fifo using a spinlock
* for locking, don't disable interrupts
* @fifo: address of the fifo to be used
* @buf: pointer to the storage buffer
* @n: max. number of elements to get
* @lock: pointer to the spinlock to use for locking
*
* This is a variant of kfifo_out_spinlocked() which uses spin_lock/unlock()
* for locking and doesn't disable interrupts.
*/
#define kfifo_out_spinlocked_noirqsave(fifo, buf, n, lock) \
__kfifo_uint_must_check_helper( \
({ \
unsigned int __ret; \
spin_lock(lock); \
__ret = kfifo_out(fifo, buf, n); \
spin_unlock(lock); \
__ret; \
}) \
)
/* alias for kfifo_out_spinlocked, will be removed in a future release */
#define kfifo_out_locked(fifo, buf, n, lock) \
kfifo_out_spinlocked(fifo, buf, n, lock)
......
......@@ -59,6 +59,34 @@ struct gpioline_info {
/* Maximum number of requested handles */
#define GPIOHANDLES_MAX 64
/* Possible line status change events */
enum {
GPIOLINE_CHANGED_REQUESTED = 1,
GPIOLINE_CHANGED_RELEASED,
GPIOLINE_CHANGED_CONFIG,
};
/**
* struct gpioline_info_changed - Information about a change in status
* of a GPIO line
* @info: updated line information
* @timestamp: estimate of time of status change occurrence, in nanoseconds
* and GPIOLINE_CHANGED_CONFIG
* @event_type: one of GPIOLINE_CHANGED_REQUESTED, GPIOLINE_CHANGED_RELEASED
*
* Note: struct gpioline_info embedded here has 32-bit alignment on its own,
* but it works fine with 64-bit alignment too. With its 72 byte size, we can
* guarantee there are no implicit holes between it and subsequent members.
* The 20-byte padding at the end makes sure we don't add any implicit padding
* at the end of the structure on 64-bit architectures.
*/
struct gpioline_info_changed {
struct gpioline_info info;
__u64 timestamp;
__u32 event_type;
__u32 padding[5]; /* for future use */
};
/* Linerequest flags */
#define GPIOHANDLE_REQUEST_INPUT (1UL << 0)
#define GPIOHANDLE_REQUEST_OUTPUT (1UL << 1)
......@@ -176,6 +204,8 @@ struct gpioevent_data {
#define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
#define GPIO_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x02, struct gpioline_info)
#define GPIO_GET_LINEINFO_WATCH_IOCTL _IOWR(0xB4, 0x0b, struct gpioline_info)
#define GPIO_GET_LINEINFO_UNWATCH_IOCTL _IOWR(0xB4, 0x0c, __u32)
#define GPIO_GET_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request)
#define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request)
......
gpio-event-mon
gpio-hammer
gpio-watch
lsgpio
include/linux/gpio.h
......@@ -2,3 +2,4 @@ gpio-utils-y += gpio-utils.o
lsgpio-y += lsgpio.o gpio-utils.o
gpio-hammer-y += gpio-hammer.o gpio-utils.o
gpio-event-mon-y += gpio-event-mon.o gpio-utils.o
gpio-watch-y += gpio-watch.o
......@@ -18,7 +18,7 @@ MAKEFLAGS += -r
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon
ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon gpio-watch
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
all: $(ALL_PROGRAMS)
......@@ -66,6 +66,15 @@ $(GPIO_EVENT_MON_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
$(OUTPUT)gpio-event-mon: $(GPIO_EVENT_MON_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
#
# gpio-watch
#
GPIO_WATCH_IN := $(OUTPUT)gpio-watch-in.o
$(GPIO_WATCH_IN): prepare FORCE
$(Q)$(MAKE) $(build)=gpio-watch
$(OUTPUT)gpio-watch: $(GPIO_WATCH_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
clean:
rm -f $(ALL_PROGRAMS)
rm -f $(OUTPUT)include/linux/gpio.h
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* gpio-watch - monitor unrequested lines for property changes using the
* character device
*
* Copyright (C) 2019 BayLibre SAS
* Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/gpio.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
struct gpioline_info_changed chg;
struct gpioline_info req;
struct pollfd pfd;
int fd, i, j, ret;
char *event, *end;
ssize_t rd;
if (argc < 3)
goto err_usage;
fd = open(argv[1], O_RDWR | O_CLOEXEC);
if (fd < 0) {
perror("unable to open gpiochip");
return EXIT_FAILURE;
}
for (i = 0, j = 2; i < argc - 2; i++, j++) {
memset(&req, 0, sizeof(req));
req.line_offset = strtoul(argv[j], &end, 0);
if (*end != '\0')
goto err_usage;
ret = ioctl(fd, GPIO_GET_LINEINFO_WATCH_IOCTL, &req);
if (ret) {
perror("unable to set up line watch");
return EXIT_FAILURE;
}
}
pfd.fd = fd;
pfd.events = POLLIN | POLLPRI;
for (;;) {
ret = poll(&pfd, 1, 5000);
if (ret < 0) {
perror("error polling the linechanged fd");
return EXIT_FAILURE;
} else if (ret > 0) {
memset(&chg, 0, sizeof(chg));
rd = read(pfd.fd, &chg, sizeof(chg));
if (rd < 0 || rd != sizeof(chg)) {
if (rd != sizeof(chg))
errno = EIO;
perror("error reading line change event");
return EXIT_FAILURE;
}
switch (chg.event_type) {
case GPIOLINE_CHANGED_REQUESTED:
event = "requested";
break;
case GPIOLINE_CHANGED_RELEASED:
event = "released";
break;
case GPIOLINE_CHANGED_CONFIG:
event = "config changed";
break;
default:
fprintf(stderr,
"invalid event type received from the kernel\n");
return EXIT_FAILURE;
}
printf("line %u: %s at %llu\n",
chg.info.line_offset, event, chg.timestamp);
}
}
return 0;
err_usage:
printf("%s: <gpiochip> <line0> <line1> ...\n", argv[0]);
return EXIT_FAILURE;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment