diff options
Diffstat (limited to 'drivers/gpio/gpiolib.c')
| -rw-r--r-- | drivers/gpio/gpiolib.c | 99 | 
1 files changed, 86 insertions, 13 deletions
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index f0fc3a0d37c8..93ed0e00c578 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -21,6 +21,7 @@  #include <linux/uaccess.h>  #include <linux/compat.h>  #include <linux/anon_inodes.h> +#include <linux/file.h>  #include <linux/kfifo.h>  #include <linux/poll.h>  #include <linux/timekeeping.h> @@ -333,6 +334,13 @@ struct linehandle_state {  	u32 numdescs;  }; +#define GPIOHANDLE_REQUEST_VALID_FLAGS \ +	(GPIOHANDLE_REQUEST_INPUT | \ +	GPIOHANDLE_REQUEST_OUTPUT | \ +	GPIOHANDLE_REQUEST_ACTIVE_LOW | \ +	GPIOHANDLE_REQUEST_OPEN_DRAIN | \ +	GPIOHANDLE_REQUEST_OPEN_SOURCE) +  static long linehandle_ioctl(struct file *filep, unsigned int cmd,  			     unsigned long arg)  { @@ -344,6 +352,8 @@ static long linehandle_ioctl(struct file *filep, unsigned int cmd,  	if (cmd == GPIOHANDLE_GET_LINE_VALUES_IOCTL) {  		int val; +		memset(&ghd, 0, sizeof(ghd)); +  		/* TODO: check if descriptors are really input */  		for (i = 0; i < lh->numdescs; i++) {  			val = gpiod_get_value_cansleep(lh->descs[i]); @@ -414,6 +424,7 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)  {  	struct gpiohandle_request handlereq;  	struct linehandle_state *lh; +	struct file *file;  	int fd, i, ret;  	if (copy_from_user(&handlereq, ip, sizeof(handlereq))) @@ -444,6 +455,17 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)  		u32 lflags = handlereq.flags;  		struct gpio_desc *desc; +		if (offset >= gdev->ngpio) { +			ret = -EINVAL; +			goto out_free_descs; +		} + +		/* Return an error if a unknown flag is set */ +		if (lflags & ~GPIOHANDLE_REQUEST_VALID_FLAGS) { +			ret = -EINVAL; +			goto out_free_descs; +		} +  		desc = &gdev->descs[offset];  		ret = gpiod_request(desc, lh->label);  		if (ret) @@ -479,26 +501,41 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip)  	i--;  	lh->numdescs = handlereq.lines; -	fd = anon_inode_getfd("gpio-linehandle", -			      &linehandle_fileops, -			      lh, -			      O_RDONLY | O_CLOEXEC); +	fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);  	if (fd < 0) {  		ret = fd;  		goto out_free_descs;  	} +	file = anon_inode_getfile("gpio-linehandle", +				  &linehandle_fileops, +				  lh, +				  O_RDONLY | O_CLOEXEC); +	if (IS_ERR(file)) { +		ret = PTR_ERR(file); +		goto out_put_unused_fd; +	} +  	handlereq.fd = fd;  	if (copy_to_user(ip, &handlereq, sizeof(handlereq))) { -		ret = -EFAULT; -		goto out_free_descs; +		/* +		 * fput() will trigger the release() callback, so do not go onto +		 * the regular error cleanup path here. +		 */ +		fput(file); +		put_unused_fd(fd); +		return -EFAULT;  	} +	fd_install(fd, file); +  	dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n",  		lh->numdescs);  	return 0; +out_put_unused_fd: +	put_unused_fd(fd);  out_free_descs:  	for (; i >= 0; i--)  		gpiod_free(lh->descs[i]); @@ -536,6 +573,10 @@ struct lineevent_state {  	struct mutex read_lock;  }; +#define GPIOEVENT_REQUEST_VALID_FLAGS \ +	(GPIOEVENT_REQUEST_RISING_EDGE | \ +	GPIOEVENT_REQUEST_FALLING_EDGE) +  static unsigned int lineevent_poll(struct file *filep,  				   struct poll_table_struct *wait)  { @@ -623,6 +664,8 @@ static long lineevent_ioctl(struct file *filep, unsigned int cmd,  	if (cmd == GPIOHANDLE_GET_LINE_VALUES_IOCTL) {  		int val; +		memset(&ghd, 0, sizeof(ghd)); +  		val = gpiod_get_value_cansleep(le->desc);  		if (val < 0)  			return val; @@ -695,6 +738,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)  	struct gpioevent_request eventreq;  	struct lineevent_state *le;  	struct gpio_desc *desc; +	struct file *file;  	u32 offset;  	u32 lflags;  	u32 eflags; @@ -726,6 +770,18 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)  	lflags = eventreq.handleflags;  	eflags = eventreq.eventflags; +	if (offset >= gdev->ngpio) { +		ret = -EINVAL; +		goto out_free_label; +	} + +	/* Return an error if a unknown flag is set */ +	if ((lflags & ~GPIOHANDLE_REQUEST_VALID_FLAGS) || +	    (eflags & ~GPIOEVENT_REQUEST_VALID_FLAGS)) { +		ret = -EINVAL; +		goto out_free_label; +	} +  	/* This is just wrong: we don't look for events on output lines */  	if (lflags & GPIOHANDLE_REQUEST_OUTPUT) {  		ret = -EINVAL; @@ -777,23 +833,38 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)  	if (ret)  		goto out_free_desc; -	fd = anon_inode_getfd("gpio-event", -			      &lineevent_fileops, -			      le, -			      O_RDONLY | O_CLOEXEC); +	fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);  	if (fd < 0) {  		ret = fd;  		goto out_free_irq;  	} +	file = anon_inode_getfile("gpio-event", +				  &lineevent_fileops, +				  le, +				  O_RDONLY | O_CLOEXEC); +	if (IS_ERR(file)) { +		ret = PTR_ERR(file); +		goto out_put_unused_fd; +	} +  	eventreq.fd = fd;  	if (copy_to_user(ip, &eventreq, sizeof(eventreq))) { -		ret = -EFAULT; -		goto out_free_irq; +		/* +		 * fput() will trigger the release() callback, so do not go onto +		 * the regular error cleanup path here. +		 */ +		fput(file); +		put_unused_fd(fd); +		return -EFAULT;  	} +	fd_install(fd, file); +  	return 0; +out_put_unused_fd: +	put_unused_fd(fd);  out_free_irq:  	free_irq(le->irq, le);  out_free_desc: @@ -823,6 +894,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)  	if (cmd == GPIO_GET_CHIPINFO_IOCTL) {  		struct gpiochip_info chipinfo; +		memset(&chipinfo, 0, sizeof(chipinfo)); +  		strncpy(chipinfo.name, dev_name(&gdev->dev),  			sizeof(chipinfo.name));  		chipinfo.name[sizeof(chipinfo.name)-1] = '\0'; @@ -839,7 +912,7 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)  		if (copy_from_user(&lineinfo, ip, sizeof(lineinfo)))  			return -EFAULT; -		if (lineinfo.line_offset > gdev->ngpio) +		if (lineinfo.line_offset >= gdev->ngpio)  			return -EINVAL;  		desc = &gdev->descs[lineinfo.line_offset];  | 
