forked from mirrors/linux
		
	gpio: userspace ABI for reading GPIO line events
This adds an ABI for listening to events on GPIO lines. The mechanism returns an anonymous file handle to a request to listen to a specific offset on a specific gpiochip. To fetch the stream of events from the file handle, userspace simply reads an event. - Events can be requested with the same flags as ordinary handles, i.e. open drain or open source. An ioctl() call GPIO_GET_LINEEVENT_IOCTL is issued indicating the desired line. - Events can be requested for falling edge events, rising edge events, or both. - All events are timestamped using the kernel real time nanosecond timestamp (the same as is used by IIO). - The supplied consumer label will appear in "lsgpio" listings of the lines, and in /proc/interrupts as the mechanism will request an interrupt from the gpio chip. - Events are not supported on gpiochips that do not serve interrupts (no legal .to_irq() call). The event interrupt is threaded to avoid any realtime problems. - It is possible to also directly read the current value of the registered GPIO line by issuing the same GPIOHANDLE_GET_LINE_VALUES_IOCTL as used by the line handles. Setting the value is not supported: we do not listen to events on output lines. This ABI is strongly influenced by Industrial I/O and surpasses the old sysfs ABI by providing proper precision timestamps, making it possible to set flags like open drain, and put consumer names on the GPIO lines. Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
This commit is contained in:
		
							parent
							
								
									2a144dd091
								
							
						
					
					
						commit
						61f922db72
					
				
					 2 changed files with 347 additions and 5 deletions
				
			
		|  | @ -22,6 +22,9 @@ | |||
| #include <linux/uaccess.h> | ||||
| #include <linux/compat.h> | ||||
| #include <linux/anon_inodes.h> | ||||
| #include <linux/kfifo.h> | ||||
| #include <linux/poll.h> | ||||
| #include <linux/timekeeping.h> | ||||
| #include <uapi/linux/gpio.h> | ||||
| 
 | ||||
| #include "gpiolib.h" | ||||
|  | @ -501,6 +504,299 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * GPIO line event management | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * struct lineevent_state - contains the state of a userspace event | ||||
|  * @gdev: the GPIO device the event pertains to | ||||
|  * @label: consumer label used to tag descriptors | ||||
|  * @desc: the GPIO descriptor held by this event | ||||
|  * @eflags: the event flags this line was requested with | ||||
|  * @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 | ||||
|  */ | ||||
| struct lineevent_state { | ||||
| 	struct gpio_device *gdev; | ||||
| 	const char *label; | ||||
| 	struct gpio_desc *desc; | ||||
| 	u32 eflags; | ||||
| 	int irq; | ||||
| 	wait_queue_head_t wait; | ||||
| 	DECLARE_KFIFO(events, struct gpioevent_data, 16); | ||||
| 	struct mutex read_lock; | ||||
| }; | ||||
| 
 | ||||
| static unsigned int lineevent_poll(struct file *filep, | ||||
| 				   struct poll_table_struct *wait) | ||||
| { | ||||
| 	struct lineevent_state *le = filep->private_data; | ||||
| 	unsigned int events = 0; | ||||
| 
 | ||||
| 	poll_wait(filep, &le->wait, wait); | ||||
| 
 | ||||
| 	if (!kfifo_is_empty(&le->events)) | ||||
| 		events = POLLIN | POLLRDNORM; | ||||
| 
 | ||||
| 	return events; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static ssize_t lineevent_read(struct file *filep, | ||||
| 			      char __user *buf, | ||||
| 			      size_t count, | ||||
| 			      loff_t *f_ps) | ||||
| { | ||||
| 	struct lineevent_state *le = filep->private_data; | ||||
| 	unsigned int copied; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (count < sizeof(struct gpioevent_data)) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	do { | ||||
| 		if (kfifo_is_empty(&le->events)) { | ||||
| 			if (filep->f_flags & O_NONBLOCK) | ||||
| 				return -EAGAIN; | ||||
| 
 | ||||
| 			ret = wait_event_interruptible(le->wait, | ||||
| 					!kfifo_is_empty(&le->events)); | ||||
| 			if (ret) | ||||
| 				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); | ||||
| 
 | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * 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; | ||||
| } | ||||
| 
 | ||||
| static int lineevent_release(struct inode *inode, struct file *filep) | ||||
| { | ||||
| 	struct lineevent_state *le = filep->private_data; | ||||
| 	struct gpio_device *gdev = le->gdev; | ||||
| 
 | ||||
| 	free_irq(le->irq, le); | ||||
| 	gpiod_free(le->desc); | ||||
| 	kfree(le->label); | ||||
| 	kfree(le); | ||||
| 	put_device(&gdev->dev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static long lineevent_ioctl(struct file *filep, unsigned int cmd, | ||||
| 			    unsigned long arg) | ||||
| { | ||||
| 	struct lineevent_state *le = filep->private_data; | ||||
| 	void __user *ip = (void __user *)arg; | ||||
| 	struct gpiohandle_data ghd; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We can get the value for an event line but not set it, | ||||
| 	 * because it is input by definition. | ||||
| 	 */ | ||||
| 	if (cmd == GPIOHANDLE_GET_LINE_VALUES_IOCTL) { | ||||
| 		int val; | ||||
| 
 | ||||
| 		val = gpiod_get_value_cansleep(le->desc); | ||||
| 		if (val < 0) | ||||
| 			return val; | ||||
| 		ghd.values[0] = val; | ||||
| 
 | ||||
| 		if (copy_to_user(ip, &ghd, sizeof(ghd))) | ||||
| 			return -EFAULT; | ||||
| 
 | ||||
| 		return 0; | ||||
| 	} | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_COMPAT | ||||
| static long lineevent_ioctl_compat(struct file *filep, unsigned int cmd, | ||||
| 				   unsigned long arg) | ||||
| { | ||||
| 	return lineevent_ioctl(filep, cmd, (unsigned long)compat_ptr(arg)); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| static const struct file_operations lineevent_fileops = { | ||||
| 	.release = lineevent_release, | ||||
| 	.read = lineevent_read, | ||||
| 	.poll = lineevent_poll, | ||||
| 	.owner = THIS_MODULE, | ||||
| 	.llseek = noop_llseek, | ||||
| 	.unlocked_ioctl = lineevent_ioctl, | ||||
| #ifdef CONFIG_COMPAT | ||||
| 	.compat_ioctl = lineevent_ioctl_compat, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| irqreturn_t lineevent_irq_thread(int irq, void *p) | ||||
| { | ||||
| 	struct lineevent_state *le = p; | ||||
| 	struct gpioevent_data ge; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ge.timestamp = ktime_get_real_ns(); | ||||
| 
 | ||||
| 	if (le->eflags & GPIOEVENT_REQUEST_BOTH_EDGES) { | ||||
| 		int level = gpiod_get_value_cansleep(le->desc); | ||||
| 
 | ||||
| 		if (level) | ||||
| 			/* Emit low-to-high event */ | ||||
| 			ge.id = GPIOEVENT_EVENT_RISING_EDGE; | ||||
| 		else | ||||
| 			/* Emit high-to-low event */ | ||||
| 			ge.id = GPIOEVENT_EVENT_FALLING_EDGE; | ||||
| 	} else if (le->eflags & GPIOEVENT_REQUEST_RISING_EDGE) { | ||||
| 		/* Emit low-to-high event */ | ||||
| 		ge.id = GPIOEVENT_EVENT_RISING_EDGE; | ||||
| 	} else if (le->eflags & GPIOEVENT_REQUEST_FALLING_EDGE) { | ||||
| 		/* Emit high-to-low event */ | ||||
| 		ge.id = GPIOEVENT_EVENT_FALLING_EDGE; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = kfifo_put(&le->events, ge); | ||||
| 	if (ret != 0) | ||||
| 		wake_up_poll(&le->wait, POLLIN); | ||||
| 
 | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static int lineevent_create(struct gpio_device *gdev, void __user *ip) | ||||
| { | ||||
| 	struct gpioevent_request eventreq; | ||||
| 	struct lineevent_state *le; | ||||
| 	struct gpio_desc *desc; | ||||
| 	u32 offset; | ||||
| 	u32 lflags; | ||||
| 	u32 eflags; | ||||
| 	int fd; | ||||
| 	int ret; | ||||
| 	int irqflags = 0; | ||||
| 
 | ||||
| 	if (copy_from_user(&eventreq, ip, sizeof(eventreq))) | ||||
| 		return -EFAULT; | ||||
| 
 | ||||
| 	le = kzalloc(sizeof(*le), GFP_KERNEL); | ||||
| 	if (!le) | ||||
| 		return -ENOMEM; | ||||
| 	le->gdev = gdev; | ||||
| 	get_device(&gdev->dev); | ||||
| 
 | ||||
| 	/* Make sure this is terminated */ | ||||
| 	eventreq.consumer_label[sizeof(eventreq.consumer_label)-1] = '\0'; | ||||
| 	if (strlen(eventreq.consumer_label)) { | ||||
| 		le->label = kstrdup(eventreq.consumer_label, | ||||
| 				    GFP_KERNEL); | ||||
| 		if (!le->label) { | ||||
| 			ret = -ENOMEM; | ||||
| 			goto out_free_le; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	offset = eventreq.lineoffset; | ||||
| 	lflags = eventreq.handleflags; | ||||
| 	eflags = eventreq.eventflags; | ||||
| 
 | ||||
| 	/* This is just wrong: we don't look for events on output lines */ | ||||
| 	if (lflags & GPIOHANDLE_REQUEST_OUTPUT) { | ||||
| 		ret = -EINVAL; | ||||
| 		goto out_free_label; | ||||
| 	} | ||||
| 
 | ||||
| 	desc = &gdev->descs[offset]; | ||||
| 	ret = gpiod_request(desc, le->label); | ||||
| 	if (ret) | ||||
| 		goto out_free_desc; | ||||
| 	le->desc = desc; | ||||
| 	le->eflags = eflags; | ||||
| 
 | ||||
| 	if (lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW) | ||||
| 		set_bit(FLAG_ACTIVE_LOW, &desc->flags); | ||||
| 	if (lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN) | ||||
| 		set_bit(FLAG_OPEN_DRAIN, &desc->flags); | ||||
| 	if (lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE) | ||||
| 		set_bit(FLAG_OPEN_SOURCE, &desc->flags); | ||||
| 
 | ||||
| 	ret = gpiod_direction_input(desc); | ||||
| 	if (ret) | ||||
| 		goto out_free_desc; | ||||
| 
 | ||||
| 	le->irq = gpiod_to_irq(desc); | ||||
| 	if (le->irq <= 0) { | ||||
| 		ret = -ENODEV; | ||||
| 		goto out_free_desc; | ||||
| 	} | ||||
| 
 | ||||
| 	if (eflags & GPIOEVENT_REQUEST_RISING_EDGE) | ||||
| 		irqflags |= IRQF_TRIGGER_RISING; | ||||
| 	if (eflags & GPIOEVENT_REQUEST_FALLING_EDGE) | ||||
| 		irqflags |= IRQF_TRIGGER_FALLING; | ||||
| 	irqflags |= IRQF_ONESHOT; | ||||
| 	irqflags |= IRQF_SHARED; | ||||
| 
 | ||||
| 	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, | ||||
| 			NULL, | ||||
| 			lineevent_irq_thread, | ||||
| 			irqflags, | ||||
| 			le->label, | ||||
| 			le); | ||||
| 	if (ret) | ||||
| 		goto out_free_desc; | ||||
| 
 | ||||
| 	fd = anon_inode_getfd("gpio-event", | ||||
| 			      &lineevent_fileops, | ||||
| 			      le, | ||||
| 			      O_RDONLY | O_CLOEXEC); | ||||
| 	if (fd < 0) { | ||||
| 		ret = fd; | ||||
| 		goto out_free_irq; | ||||
| 	} | ||||
| 
 | ||||
| 	eventreq.fd = fd; | ||||
| 	if (copy_to_user(ip, &eventreq, sizeof(eventreq))) | ||||
| 		return -EFAULT; | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| out_free_irq: | ||||
| 	free_irq(le->irq, le); | ||||
| out_free_desc: | ||||
| 	gpiod_free(le->desc); | ||||
| out_free_label: | ||||
| 	kfree(le->label); | ||||
| out_free_le: | ||||
| 	kfree(le); | ||||
| 	put_device(&gdev->dev); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * gpio_ioctl() - ioctl handler for the GPIO chardev | ||||
|  */ | ||||
|  | @ -578,6 +874,8 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | |||
| 		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); | ||||
| 	} | ||||
| 	return -EINVAL; | ||||
| } | ||||
|  |  | |||
|  | @ -55,7 +55,7 @@ struct gpioline_info { | |||
| /* Maximum number of requested handles */ | ||||
| #define GPIOHANDLES_MAX 64 | ||||
| 
 | ||||
| /* Request flags */ | ||||
| /* Linerequest flags */ | ||||
| #define GPIOHANDLE_REQUEST_INPUT	(1UL << 0) | ||||
| #define GPIOHANDLE_REQUEST_OUTPUT	(1UL << 1) | ||||
| #define GPIOHANDLE_REQUEST_ACTIVE_LOW	(1UL << 2) | ||||
|  | @ -93,10 +93,6 @@ struct gpiohandle_request { | |||
| 	int fd; | ||||
| }; | ||||
| 
 | ||||
| #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_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request) | ||||
| 
 | ||||
| /**
 | ||||
|  * struct gpiohandle_data - Information of values on a GPIO handle | ||||
|  * @values: when getting the state of lines this contains the current | ||||
|  | @ -110,4 +106,52 @@ struct gpiohandle_data { | |||
| #define GPIOHANDLE_GET_LINE_VALUES_IOCTL _IOWR(0xB4, 0x08, struct gpiohandle_data) | ||||
| #define GPIOHANDLE_SET_LINE_VALUES_IOCTL _IOWR(0xB4, 0x09, struct gpiohandle_data) | ||||
| 
 | ||||
| /* Eventrequest flags */ | ||||
| #define GPIOEVENT_REQUEST_RISING_EDGE	(1UL << 0) | ||||
| #define GPIOEVENT_REQUEST_FALLING_EDGE	(1UL << 1) | ||||
| #define GPIOEVENT_REQUEST_BOTH_EDGES	((1UL << 0) | (1UL << 1)) | ||||
| 
 | ||||
| /**
 | ||||
|  * struct gpioevent_request - Information about a GPIO event request | ||||
|  * @lineoffset: the desired line to subscribe to events from, specified by | ||||
|  * offset index for the associated GPIO device | ||||
|  * @handleflags: desired handle flags for the desired GPIO line, such as | ||||
|  * GPIOHANDLE_REQUEST_ACTIVE_LOW or GPIOHANDLE_REQUEST_OPEN_DRAIN | ||||
|  * @eventflags: desired flags for the desired GPIO event line, such as | ||||
|  * GPIOEVENT_REQUEST_RISING_EDGE or GPIOEVENT_REQUEST_FALLING_EDGE | ||||
|  * @consumer_label: a desired consumer label for the selected GPIO line(s) | ||||
|  * such as "my-listener" | ||||
|  * @fd: if successful this field will contain a valid anonymous file handle | ||||
|  * after a GPIO_GET_LINEEVENT_IOCTL operation, zero or negative value | ||||
|  * means error | ||||
|  */ | ||||
| struct gpioevent_request { | ||||
| 	__u32 lineoffset; | ||||
| 	__u32 handleflags; | ||||
| 	__u32 eventflags; | ||||
| 	char consumer_label[32]; | ||||
| 	int fd; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * GPIO event types | ||||
|  */ | ||||
| #define GPIOEVENT_EVENT_RISING_EDGE 0x01 | ||||
| #define GPIOEVENT_EVENT_FALLING_EDGE 0x02 | ||||
| 
 | ||||
| /**
 | ||||
|  * struct gpioevent_data - The actual event being pushed to userspace | ||||
|  * @timestamp: best estimate of time of event occurrence, in nanoseconds | ||||
|  * @id: event identifier | ||||
|  */ | ||||
| struct gpioevent_data { | ||||
| 	__u64 timestamp; | ||||
| 	__u32 id; | ||||
| }; | ||||
| 
 | ||||
| #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_LINEHANDLE_IOCTL _IOWR(0xB4, 0x03, struct gpiohandle_request) | ||||
| #define GPIO_GET_LINEEVENT_IOCTL _IOWR(0xB4, 0x04, struct gpioevent_request) | ||||
| 
 | ||||
| #endif /* _UAPI_GPIO_H_ */ | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Linus Walleij
						Linus Walleij