summaryrefslogtreecommitdiffstats
path: root/drivers/gpio/gpiolib-cdev.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2022-12-15 09:45:51 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2022-12-15 09:45:51 -0800
commitc0f234ff90a211272138be1611ba53f3155ebd78 (patch)
tree956c32a903675d690631100ae829e810b4b4515b /drivers/gpio/gpiolib-cdev.c
parent9fa4abc9ad2a18410a7087e6cea15ad1ffb172c6 (diff)
parent11e47bbd700f31bd1ee9f8863381bc9e741c0e97 (diff)
downloadlinux-c0f234ff90a211272138be1611ba53f3155ebd78.tar.bz2
Merge tag 'gpio-updates-for-v6.2' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux
Pull gpio updates from Bartosz Golaszewski: "We have a new GPIO multiplexer driver, bunch of driver updates and refactoring in the core GPIO library. GPIO core: - teach gpiolib to work with software nodes for HW description - remove ARCH_NR_GPIOS treewide as we no longer impose any limit on the number of GPIOS since the allocation became entirely dynamic - add support for HW quirks for Cirrus CS42L56 codec, Marvell NFC controller, Freescale PCIe and Ethernet controller, Himax LCDs and Mediatek mt2701 - refactor OF quirk code - some general refactoring of the OF and ACPI code, adding new helpers, minor tweaks and fixes, making fwnode usage consistent etc. GPIO uAPI: - fix an issue where the user-space can trigger a NULL-pointer dereference in the kernel by opening a device file, forcing a driver unbind and then calling one of the syscalls on the associated file descriptor New drivers: - add gpio-latch: a new GPIO multiplexer based on latches connected to other GPIOs Driver updates: - convert i2c GPIO expanders to using .probe_new() - drop the gpio-sta2x11 driver - factor out common code for the ACCES IDIO-16 family of controllers and use this new library wherever applicable in drivers - add DT support to gpio-hisi - allow building gpio-davinci as a module and increase its maxItems property - add support for a new model to gpio-pca9570 - other minor changes to various drivers" * tag 'gpio-updates-for-v6.2' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (66 commits) gpio: sim: set a limit on the number of GPIOs gpiolib: protect the GPIO device against being dropped while in use by user-space gpiolib: cdev: fix NULL-pointer dereferences gpiolib: Provide to_gpio_device() helper gpiolib: Unify access to the device properties gpio: Do not include <linux/kernel.h> when not really needed. gpio: pcf857x: Convert to i2c's .probe_new() gpio: pca953x: Convert to i2c's .probe_new() gpio: max732x: Convert to i2c's .probe_new() dt-bindings: gpio: gpio-davinci: Increase maxItems in gpio-line-names gpiolib: ensure that fwnode is properly set gpio: sl28cpld: Replace irqchip mask_invert with unmask_base gpiolib: of: Use correct fwnode for DT-probed chips gpiolib: of: Drop redundant check in of_mm_gpiochip_remove() gpiolib: of: Prepare of_mm_gpiochip_add_data() for fwnode gpiolib: add support for software nodes gpiolib: consolidate GPIO lookups gpiolib: acpi: avoid leaking ACPI details into upper gpiolib layers gpiolib: acpi: teach acpi_find_gpio() to handle data-only nodes gpiolib: acpi: change acpi_find_gpio() to accept firmware node ...
Diffstat (limited to 'drivers/gpio/gpiolib-cdev.c')
-rw-r--r--drivers/gpio/gpiolib-cdev.c206
1 files changed, 180 insertions, 26 deletions
diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index 65b2c09a4576..e878e3f22b0e 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -57,6 +57,50 @@ 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 *);
+
+static __poll_t call_poll_locked(struct file *file,
+ struct poll_table_struct *wait,
+ struct gpio_device *gdev, poll_fn func)
+{
+ __poll_t ret;
+
+ down_read(&gdev->sem);
+ ret = func(file, wait);
+ up_read(&gdev->sem);
+
+ return ret;
+}
+
+static long call_ioctl_locked(struct file *file, unsigned int cmd,
+ unsigned long arg, struct gpio_device *gdev,
+ ioctl_fn func)
+{
+ long ret;
+
+ down_read(&gdev->sem);
+ ret = func(file, cmd, arg);
+ up_read(&gdev->sem);
+
+ return ret;
+}
+
+static ssize_t call_read_locked(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps,
+ struct gpio_device *gdev, read_fn func)
+{
+ ssize_t ret;
+
+ down_read(&gdev->sem);
+ ret = func(file, buf, count, f_ps);
+ up_read(&gdev->sem);
+
+ return ret;
+}
+
/*
* GPIO line handle management
*/
@@ -193,8 +237,8 @@ static long linehandle_set_config(struct linehandle_state *lh,
return 0;
}
-static long linehandle_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
+static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
{
struct linehandle_state *lh = file->private_data;
void __user *ip = (void __user *)arg;
@@ -203,6 +247,9 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
unsigned int i;
int ret;
+ if (!lh->gdev->chip)
+ return -ENODEV;
+
switch (cmd) {
case GPIOHANDLE_GET_LINE_VALUES_IOCTL:
/* NOTE: It's okay to read values of output lines */
@@ -249,6 +296,15 @@ static long linehandle_ioctl(struct file *file, unsigned int cmd,
}
}
+static long linehandle_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct linehandle_state *lh = file->private_data;
+
+ return call_ioctl_locked(file, cmd, arg, lh->gdev,
+ linehandle_ioctl_unlocked);
+}
+
#ifdef CONFIG_COMPAT
static long linehandle_ioctl_compat(struct file *file, unsigned int cmd,
unsigned long arg)
@@ -412,7 +468,7 @@ out_free_lh:
* @desc: the GPIO descriptor for this line.
* @req: the corresponding line request
* @irq: the interrupt triggered in response to events on this GPIO
- * @eflags: the edge flags, GPIO_V2_LINE_FLAG_EDGE_RISING and/or
+ * @edflags: the edge flags, GPIO_V2_LINE_FLAG_EDGE_RISING and/or
* GPIO_V2_LINE_FLAG_EDGE_FALLING, indicating the edge detection applied
* @timestamp_ns: cache for the timestamp storing it between hardirq and
* IRQ thread, used to bring the timestamp close to the actual event
@@ -1380,12 +1436,15 @@ static long linereq_set_config(struct linereq *lr, void __user *ip)
return ret;
}
-static long linereq_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
+static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
{
struct linereq *lr = file->private_data;
void __user *ip = (void __user *)arg;
+ if (!lr->gdev->chip)
+ return -ENODEV;
+
switch (cmd) {
case GPIO_V2_LINE_GET_VALUES_IOCTL:
return linereq_get_values(lr, ip);
@@ -1398,6 +1457,15 @@ static long linereq_ioctl(struct file *file, unsigned int cmd,
}
}
+static long linereq_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct linereq *lr = file->private_data;
+
+ return call_ioctl_locked(file, cmd, arg, lr->gdev,
+ linereq_ioctl_unlocked);
+}
+
#ifdef CONFIG_COMPAT
static long linereq_ioctl_compat(struct file *file, unsigned int cmd,
unsigned long arg)
@@ -1406,12 +1474,15 @@ static long linereq_ioctl_compat(struct file *file, unsigned int cmd,
}
#endif
-static __poll_t linereq_poll(struct file *file,
- struct poll_table_struct *wait)
+static __poll_t linereq_poll_unlocked(struct file *file,
+ struct poll_table_struct *wait)
{
struct linereq *lr = file->private_data;
__poll_t events = 0;
+ if (!lr->gdev->chip)
+ return EPOLLHUP | EPOLLERR;
+
poll_wait(file, &lr->wait, wait);
if (!kfifo_is_empty_spinlocked_noirqsave(&lr->events,
@@ -1421,16 +1492,25 @@ static __poll_t linereq_poll(struct file *file,
return events;
}
-static ssize_t linereq_read(struct file *file,
- char __user *buf,
- size_t count,
- loff_t *f_ps)
+static __poll_t linereq_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct linereq *lr = file->private_data;
+
+ return call_poll_locked(file, wait, lr->gdev, linereq_poll_unlocked);
+}
+
+static ssize_t linereq_read_unlocked(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
{
struct linereq *lr = file->private_data;
struct gpio_v2_line_event le;
ssize_t bytes_read = 0;
int ret;
+ if (!lr->gdev->chip)
+ return -ENODEV;
+
if (count < sizeof(le))
return -EINVAL;
@@ -1475,6 +1555,15 @@ static ssize_t linereq_read(struct file *file,
return bytes_read;
}
+static ssize_t linereq_read(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
+{
+ struct linereq *lr = file->private_data;
+
+ return call_read_locked(file, buf, count, f_ps, lr->gdev,
+ linereq_read_unlocked);
+}
+
static void linereq_free(struct linereq *lr)
{
unsigned int i;
@@ -1712,12 +1801,15 @@ struct lineevent_state {
(GPIOEVENT_REQUEST_RISING_EDGE | \
GPIOEVENT_REQUEST_FALLING_EDGE)
-static __poll_t lineevent_poll(struct file *file,
- struct poll_table_struct *wait)
+static __poll_t lineevent_poll_unlocked(struct file *file,
+ struct poll_table_struct *wait)
{
struct lineevent_state *le = file->private_data;
__poll_t events = 0;
+ if (!le->gdev->chip)
+ return EPOLLHUP | EPOLLERR;
+
poll_wait(file, &le->wait, wait);
if (!kfifo_is_empty_spinlocked_noirqsave(&le->events, &le->wait.lock))
@@ -1726,15 +1818,21 @@ static __poll_t lineevent_poll(struct file *file,
return events;
}
+static __poll_t lineevent_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct lineevent_state *le = file->private_data;
+
+ return call_poll_locked(file, wait, le->gdev, lineevent_poll_unlocked);
+}
+
struct compat_gpioeevent_data {
compat_u64 timestamp;
u32 id;
};
-static ssize_t lineevent_read(struct file *file,
- char __user *buf,
- size_t count,
- loff_t *f_ps)
+static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
{
struct lineevent_state *le = file->private_data;
struct gpioevent_data ge;
@@ -1742,6 +1840,9 @@ static ssize_t lineevent_read(struct file *file,
ssize_t ge_size;
int ret;
+ if (!le->gdev->chip)
+ return -ENODEV;
+
/*
* When compatible system call is being used the struct gpioevent_data,
* in case of at least ia32, has different size due to the alignment
@@ -1799,6 +1900,15 @@ static ssize_t lineevent_read(struct file *file,
return bytes_read;
}
+static ssize_t lineevent_read(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
+{
+ struct lineevent_state *le = file->private_data;
+
+ return call_read_locked(file, buf, count, f_ps, le->gdev,
+ lineevent_read_unlocked);
+}
+
static void lineevent_free(struct lineevent_state *le)
{
if (le->irq)
@@ -1816,13 +1926,16 @@ static int lineevent_release(struct inode *inode, struct file *file)
return 0;
}
-static long lineevent_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
+static long lineevent_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
{
struct lineevent_state *le = file->private_data;
void __user *ip = (void __user *)arg;
struct gpiohandle_data ghd;
+ if (!le->gdev->chip)
+ return -ENODEV;
+
/*
* We can get the value for an event line but not set it,
* because it is input by definition.
@@ -1845,6 +1958,15 @@ static long lineevent_ioctl(struct file *file, unsigned int cmd,
return -EINVAL;
}
+static long lineevent_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct lineevent_state *le = file->private_data;
+
+ return call_ioctl_locked(file, cmd, arg, le->gdev,
+ lineevent_ioctl_unlocked);
+}
+
#ifdef CONFIG_COMPAT
static long lineevent_ioctl_compat(struct file *file, unsigned int cmd,
unsigned long arg)
@@ -2403,12 +2525,15 @@ static int lineinfo_changed_notify(struct notifier_block *nb,
return NOTIFY_OK;
}
-static __poll_t lineinfo_watch_poll(struct file *file,
- struct poll_table_struct *pollt)
+static __poll_t lineinfo_watch_poll_unlocked(struct file *file,
+ struct poll_table_struct *pollt)
{
struct gpio_chardev_data *cdev = file->private_data;
__poll_t events = 0;
+ if (!cdev->gdev->chip)
+ return EPOLLHUP | EPOLLERR;
+
poll_wait(file, &cdev->wait, pollt);
if (!kfifo_is_empty_spinlocked_noirqsave(&cdev->events,
@@ -2418,8 +2543,17 @@ static __poll_t lineinfo_watch_poll(struct file *file,
return events;
}
-static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
- size_t count, loff_t *off)
+static __poll_t lineinfo_watch_poll(struct file *file,
+ struct poll_table_struct *pollt)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+
+ return call_poll_locked(file, pollt, cdev->gdev,
+ lineinfo_watch_poll_unlocked);
+}
+
+static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf,
+ size_t count, loff_t *off)
{
struct gpio_chardev_data *cdev = file->private_data;
struct gpio_v2_line_info_changed event;
@@ -2427,6 +2561,9 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
int ret;
size_t event_size;
+ if (!cdev->gdev->chip)
+ return -ENODEV;
+
#ifndef CONFIG_GPIO_CDEV_V1
event_size = sizeof(struct gpio_v2_line_info_changed);
if (count < event_size)
@@ -2494,6 +2631,15 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
return bytes_read;
}
+static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
+ size_t count, loff_t *off)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+
+ return call_read_locked(file, buf, count, off, cdev->gdev,
+ lineinfo_watch_read_unlocked);
+}
+
/**
* gpio_chrdev_open() - open the chardev for ioctl operations
* @inode: inode for this chardev
@@ -2507,13 +2653,17 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
struct gpio_chardev_data *cdev;
int ret = -ENOMEM;
+ down_read(&gdev->sem);
+
/* Fail on open if the backing gpiochip is gone */
- if (!gdev->chip)
- return -ENODEV;
+ if (!gdev->chip) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
if (!cdev)
- return -ENOMEM;
+ goto out_unlock;
cdev->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL);
if (!cdev->watched_lines)
@@ -2536,6 +2686,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
if (ret)
goto out_unregister_notifier;
+ up_read(&gdev->sem);
+
return ret;
out_unregister_notifier:
@@ -2545,6 +2697,8 @@ out_free_bitmap:
bitmap_free(cdev->watched_lines);
out_free_cdev:
kfree(cdev);
+out_unlock:
+ up_read(&gdev->sem);
return ret;
}