mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
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 -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEFp3rbAvDxGAT0sefEacuoBRx13IFAl5FGmsACgkQEacuoBRx 13JPARAApXdvWiknTU+hfwAIrD/hRN7SJnA0QxEQvESis0c0hlP+bpTly1c0lIMe //4hFfTOteE0+rmErWMvwjTXaSyX53KCHMUgSpEk4Jq2PaGLNg+2oWUyXiImte9E NrGDFO/LlB8zaXucAl2NAjAqnO4HEqED8mE1/3NHSYxuNAeJkp19DwZ72csBV2da fPQfpJ6EKcUQXtMtus7VIMXlllijWUgAeKgPA4+BIj6k9n1lgUcR7wQPQxr4qedd ITmNF2FaitmxtH0AQXLnV95moENfz9SATk/3K0EOAf9TGd/81sNsFE73R2ofwKpz ZZwaVadG2NOLmNkvdr8zxPZAKIefmfUVamqHNwO3UWmlaDYVcgVpMPaWgdLdS6en 50tvBtTdovuzKpzzPOpfzyRIewHwitu8HaNPC12WSwbsk9G9yvfXO4EwITM5pwiX IrXRCtcf2x4Qu+TwAhb0A7l2tp/DrKExZrE3nGbVL+cNydXsExXHJxUy4gpQPe3C PHO60h7axWCxRuPWADi3ncZ5Vc2b/9VAXD91sPwl6f5RDRwOZtGftXyhYQ/oGYQP cuCeTVGq8s6gVgbYhP/7OMbMq/zSJeh2XRP0W5V79A7LyRdK079GVp1xCoJbm1rH 7bAeaEeD0YnbQ18pCoB7RywOChc9pPtM9hdmR7jY43Wt0Sg5SbQ= =wD6X -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
b2929a9cb2
@ -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 @@ out_free_lh:
|
||||
* @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)
|
||||
return -EAGAIN;
|
||||
if (bytes_read) {
|
||||
spin_unlock(&le->wait.lock);
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
ret = wait_event_interruptible(le->wait,
|
||||
if (filep->f_flags & O_NONBLOCK) {
|
||||
spin_unlock(&le->wait.lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
ret = wait_event_interruptible_locked(le->wait,
|
||||
!kfifo_is_empty(&le->events));
|
||||
if (ret)
|
||||
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);
|
||||
ret = kfifo_out(&le->events, &event, 1);
|
||||
spin_unlock(&le->wait.lock);
|
||||
if (ret != 1) {
|
||||
/*
|
||||
* This should never happen - we were holding the lock
|
||||
* from the moment we learned the fifo is no longer
|
||||
* empty until now.
|
||||
*/
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
if (copy_to_user(buf + bytes_read, &event, sizeof(event)))
|
||||
return -EFAULT;
|
||||
bytes_read += sizeof(event);
|
||||
} while (count >= bytes_read + sizeof(event));
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
if (copied == 0 && (filep->f_flags & O_NONBLOCK))
|
||||
return -EAGAIN;
|
||||
|
||||
} while (copied == 0);
|
||||
|
||||
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 @@ out_free_le:
|
||||
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;
|
||||
get_device(&gdev->dev);
|
||||
filp->private_data = gdev;
|
||||
|
||||
return nonseekable_open(inode, filp);
|
||||
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 = priv;
|
||||
|
||||
ret = nonseekable_open(inode, filp);
|
||||
if (ret)
|
||||
goto out_unregister_notifier;
|
||||
|
||||
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)
|
||||
|
||||
|
1
tools/gpio/.gitignore
vendored
1
tools/gpio/.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
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
|
||||
|
99
tools/gpio/gpio-watch.c
Normal file
99
tools/gpio/gpio-watch.c
Normal file
@ -0,0 +1,99 @@
|
||||
// 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user