@ -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 @@ out_free_lh:
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 ;
}