diff options
Diffstat (limited to 'drivers/gpio/gpiolib-cdev.c')
| -rw-r--r-- | drivers/gpio/gpiolib-cdev.c | 206 | 
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;  }  | 
