Struct gpio_device now provides a revocable provider to the underlying struct gpio_chip. Leverage revocable for gpio_fileops so that it doesn't need to handle the synchronization by accessing the SRCU explicitly.
Also, it's unneeded to hold a reference count to the struct gpio_device while the file is opening. The struct gpio_device (i.e., (struct gpio_chip *)->gpiodev)) is valid as long as struct gpio_chip is valid. Signed-off-by: Tzung-Bi Shih <[email protected]> --- drivers/gpio/gpiolib-cdev.c | 87 ++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index e42cfdb47885..832a542c4f7a 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -22,6 +22,7 @@ #include <linux/overflow.h> #include <linux/pinctrl/consumer.h> #include <linux/poll.h> +#include <linux/revocable.h> #include <linux/seq_file.h> #include <linux/spinlock.h> #include <linux/string.h> @@ -2297,7 +2298,7 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, } struct gpio_chardev_data { - struct gpio_device *gdev; + struct revocable *chip_rev; wait_queue_head_t wait; DECLARE_KFIFO(events, struct gpio_v2_line_info_changed, 32); struct notifier_block lineinfo_changed_nb; @@ -2309,9 +2310,8 @@ struct gpio_chardev_data { struct file *fp; }; -static int chipinfo_get(struct gpio_chardev_data *cdev, void __user *ip) +static int chipinfo_get(struct gpio_device *gdev, void __user *ip) { - struct gpio_device *gdev = cdev->gdev; struct gpiochip_info chipinfo; memset(&chipinfo, 0, sizeof(chipinfo)); @@ -2339,7 +2339,8 @@ static int lineinfo_ensure_abi_version(struct gpio_chardev_data *cdata, return abiv; } -static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, +static int lineinfo_get_v1(struct gpio_chardev_data *cdev, + struct gpio_device *gdev, void __user *ip, bool watch) { struct gpio_desc *desc; @@ -2350,7 +2351,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 = gpio_device_get_desc(cdev->gdev, lineinfo.line_offset); + desc = gpio_device_get_desc(gdev, lineinfo.line_offset); if (IS_ERR(desc)) return PTR_ERR(desc); @@ -2375,7 +2376,8 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, } #endif -static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, +static int lineinfo_get(struct gpio_chardev_data *cdev, + struct gpio_device *gdev, void __user *ip, bool watch) { struct gpio_desc *desc; @@ -2387,7 +2389,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, if (!mem_is_zero(lineinfo.padding, sizeof(lineinfo.padding))) return -EINVAL; - desc = gpio_device_get_desc(cdev->gdev, lineinfo.offset); + desc = gpio_device_get_desc(gdev, lineinfo.offset); if (IS_ERR(desc)) return PTR_ERR(desc); @@ -2410,14 +2412,15 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, return 0; } -static int lineinfo_unwatch(struct gpio_chardev_data *cdev, void __user *ip) +static int lineinfo_unwatch(struct gpio_chardev_data *cdev, + struct gpio_device *gdev, void __user *ip) { __u32 offset; if (copy_from_user(&offset, ip, sizeof(offset))) return -EFAULT; - if (offset >= cdev->gdev->ngpio) + if (offset >= gdev->ngpio) return -EINVAL; if (!test_and_clear_bit(offset, cdev->watched_lines)) @@ -2432,37 +2435,38 @@ static int lineinfo_unwatch(struct gpio_chardev_data *cdev, void __user *ip) static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct gpio_chardev_data *cdev = file->private_data; - struct gpio_device *gdev = cdev->gdev; + struct gpio_chip *gc; + struct gpio_device *gdev; void __user *ip = (void __user *)arg; - guard(srcu)(&gdev->srcu); - /* We fail any subsequent ioctl():s when the chip is gone */ - if (!rcu_access_pointer(gdev->chip)) + REVOCABLE_TRY_ACCESS_WITH(cdev->chip_rev, gc); + if (!gc) return -ENODEV; + gdev = gc->gpiodev; /* Fill in the struct and pass to userspace */ switch (cmd) { case GPIO_GET_CHIPINFO_IOCTL: - return chipinfo_get(cdev, ip); + return chipinfo_get(gdev, ip); #ifdef CONFIG_GPIO_CDEV_V1 case GPIO_GET_LINEHANDLE_IOCTL: return linehandle_create(gdev, ip); case GPIO_GET_LINEEVENT_IOCTL: return lineevent_create(gdev, ip); case GPIO_GET_LINEINFO_IOCTL: - return lineinfo_get_v1(cdev, ip, false); + return lineinfo_get_v1(cdev, gdev, ip, false); case GPIO_GET_LINEINFO_WATCH_IOCTL: - return lineinfo_get_v1(cdev, ip, true); + return lineinfo_get_v1(cdev, gdev, ip, true); #endif /* CONFIG_GPIO_CDEV_V1 */ case GPIO_V2_GET_LINEINFO_IOCTL: - return lineinfo_get(cdev, ip, false); + return lineinfo_get(cdev, gdev, ip, false); case GPIO_V2_GET_LINEINFO_WATCH_IOCTL: - return lineinfo_get(cdev, ip, true); + return lineinfo_get(cdev, gdev, ip, true); case GPIO_V2_GET_LINE_IOCTL: return linereq_create(gdev, ip); case GPIO_GET_LINEINFO_UNWATCH_IOCTL: - return lineinfo_unwatch(cdev, ip); + return lineinfo_unwatch(cdev, gdev, ip); default: return -EINVAL; } @@ -2585,10 +2589,10 @@ static __poll_t lineinfo_watch_poll(struct file *file, { struct gpio_chardev_data *cdev = file->private_data; __poll_t events = 0; + struct gpio_chip *gc; - guard(srcu)(&cdev->gdev->srcu); - - if (!rcu_access_pointer(cdev->gdev->chip)) + REVOCABLE_TRY_ACCESS_WITH(cdev->chip_rev, gc); + if (!gc) return EPOLLHUP | EPOLLERR; poll_wait(file, &cdev->wait, pollt); @@ -2608,10 +2612,10 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, ssize_t bytes_read = 0; int ret; size_t event_size; + struct gpio_chip *gc; - guard(srcu)(&cdev->gdev->srcu); - - if (!rcu_access_pointer(cdev->gdev->chip)) + REVOCABLE_TRY_ACCESS_WITH(cdev->chip_rev, gc); + if (!gc) return -ENODEV; #ifndef CONFIG_GPIO_CDEV_V1 @@ -2695,13 +2699,16 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) if (!cdev) return -ENOMEM; + cdev->chip_rev = revocable_alloc(gdev->chip_rp); + if (!cdev->chip_rev) + goto out_free_cdev; + cdev->watched_lines = bitmap_zalloc(gdev->ngpio, GFP_KERNEL); if (!cdev->watched_lines) - goto out_free_cdev; + goto out_free_chip_rev; init_waitqueue_head(&cdev->wait); INIT_KFIFO(cdev->events); - cdev->gdev = gpio_device_get(gdev); cdev->lineinfo_changed_nb.notifier_call = lineinfo_changed_notify; scoped_guard(write_lock_irqsave, &gdev->line_state_lock) @@ -2734,8 +2741,9 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) raw_notifier_chain_unregister(&gdev->line_state_notifier, &cdev->lineinfo_changed_nb); out_free_bitmap: - gpio_device_put(gdev); bitmap_free(cdev->watched_lines); +out_free_chip_rev: + revocable_free(cdev->chip_rev); out_free_cdev: kfree(cdev); return ret; @@ -2752,15 +2760,24 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) 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; + struct gpio_chip *gc; + struct gpio_device *gdev; + + REVOCABLE_TRY_ACCESS_SCOPED(cdev->chip_rev, gc) { + if (!gc) + break; + gdev = gc->gpiodev; + + blocking_notifier_chain_unregister(&gdev->device_notifier, + &cdev->device_unregistered_nb); + + scoped_guard(write_lock_irqsave, &gdev->line_state_lock) + raw_notifier_chain_unregister(&gdev->line_state_notifier, + &cdev->lineinfo_changed_nb); + } + revocable_free(cdev->chip_rev); - blocking_notifier_chain_unregister(&gdev->device_notifier, - &cdev->device_unregistered_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); return 0; -- 2.52.0.457.g6b5491de43-goog
