2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-03-16 23:08:48 +03:00
/*
* VFIO platform devices interrupt handling
*
* Copyright ( C ) 2013 - Virtual Open Systems
* Author : Antonios Motakis < a . motakis @ virtualopensystems . com >
*/
# include <linux/eventfd.h>
# include <linux/interrupt.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/vfio.h>
# include <linux/irq.h>
# include "vfio_platform_private.h"
2015-03-16 23:08:50 +03:00
static void vfio_platform_mask ( struct vfio_platform_irq * irq_ctx )
{
unsigned long flags ;
spin_lock_irqsave ( & irq_ctx - > lock , flags ) ;
if ( ! irq_ctx - > masked ) {
disable_irq_nosync ( irq_ctx - > hwirq ) ;
irq_ctx - > masked = true ;
}
spin_unlock_irqrestore ( & irq_ctx - > lock , flags ) ;
}
2015-03-16 23:08:55 +03:00
static int vfio_platform_mask_handler ( void * opaque , void * unused )
{
struct vfio_platform_irq * irq_ctx = opaque ;
vfio_platform_mask ( irq_ctx ) ;
return 0 ;
}
2015-03-16 23:08:49 +03:00
static int vfio_platform_set_irq_mask ( struct vfio_platform_device * vdev ,
unsigned index , unsigned start ,
unsigned count , uint32_t flags ,
void * data )
{
2015-03-16 23:08:50 +03:00
if ( start ! = 0 | | count ! = 1 )
return - EINVAL ;
if ( ! ( vdev - > irqs [ index ] . flags & VFIO_IRQ_INFO_MASKABLE ) )
return - EINVAL ;
2015-03-16 23:08:55 +03:00
if ( flags & VFIO_IRQ_SET_DATA_EVENTFD ) {
int32_t fd = * ( int32_t * ) data ;
if ( fd > = 0 )
return vfio_virqfd_enable ( ( void * ) & vdev - > irqs [ index ] ,
vfio_platform_mask_handler ,
NULL , NULL ,
& vdev - > irqs [ index ] . mask , fd ) ;
vfio_virqfd_disable ( & vdev - > irqs [ index ] . mask ) ;
return 0 ;
}
2015-03-16 23:08:50 +03:00
if ( flags & VFIO_IRQ_SET_DATA_NONE ) {
vfio_platform_mask ( & vdev - > irqs [ index ] ) ;
} else if ( flags & VFIO_IRQ_SET_DATA_BOOL ) {
uint8_t mask = * ( uint8_t * ) data ;
if ( mask )
vfio_platform_mask ( & vdev - > irqs [ index ] ) ;
}
return 0 ;
}
static void vfio_platform_unmask ( struct vfio_platform_irq * irq_ctx )
{
unsigned long flags ;
spin_lock_irqsave ( & irq_ctx - > lock , flags ) ;
if ( irq_ctx - > masked ) {
enable_irq ( irq_ctx - > hwirq ) ;
irq_ctx - > masked = false ;
}
spin_unlock_irqrestore ( & irq_ctx - > lock , flags ) ;
2015-03-16 23:08:49 +03:00
}
2015-03-16 23:08:55 +03:00
static int vfio_platform_unmask_handler ( void * opaque , void * unused )
{
struct vfio_platform_irq * irq_ctx = opaque ;
vfio_platform_unmask ( irq_ctx ) ;
return 0 ;
}
2015-03-16 23:08:49 +03:00
static int vfio_platform_set_irq_unmask ( struct vfio_platform_device * vdev ,
unsigned index , unsigned start ,
unsigned count , uint32_t flags ,
void * data )
{
2015-03-16 23:08:50 +03:00
if ( start ! = 0 | | count ! = 1 )
return - EINVAL ;
if ( ! ( vdev - > irqs [ index ] . flags & VFIO_IRQ_INFO_MASKABLE ) )
return - EINVAL ;
2015-03-16 23:08:55 +03:00
if ( flags & VFIO_IRQ_SET_DATA_EVENTFD ) {
int32_t fd = * ( int32_t * ) data ;
if ( fd > = 0 )
return vfio_virqfd_enable ( ( void * ) & vdev - > irqs [ index ] ,
vfio_platform_unmask_handler ,
NULL , NULL ,
& vdev - > irqs [ index ] . unmask ,
fd ) ;
vfio_virqfd_disable ( & vdev - > irqs [ index ] . unmask ) ;
return 0 ;
}
2015-03-16 23:08:50 +03:00
if ( flags & VFIO_IRQ_SET_DATA_NONE ) {
vfio_platform_unmask ( & vdev - > irqs [ index ] ) ;
} else if ( flags & VFIO_IRQ_SET_DATA_BOOL ) {
uint8_t unmask = * ( uint8_t * ) data ;
if ( unmask )
vfio_platform_unmask ( & vdev - > irqs [ index ] ) ;
}
return 0 ;
}
static irqreturn_t vfio_automasked_irq_handler ( int irq , void * dev_id )
{
struct vfio_platform_irq * irq_ctx = dev_id ;
unsigned long flags ;
int ret = IRQ_NONE ;
spin_lock_irqsave ( & irq_ctx - > lock , flags ) ;
if ( ! irq_ctx - > masked ) {
ret = IRQ_HANDLED ;
/* automask maskable interrupts */
disable_irq_nosync ( irq_ctx - > hwirq ) ;
irq_ctx - > masked = true ;
}
spin_unlock_irqrestore ( & irq_ctx - > lock , flags ) ;
if ( ret = = IRQ_HANDLED )
eventfd_signal ( irq_ctx - > trigger , 1 ) ;
return ret ;
2015-03-16 23:08:49 +03:00
}
2015-03-16 23:08:50 +03:00
static irqreturn_t vfio_irq_handler ( int irq , void * dev_id )
{
struct vfio_platform_irq * irq_ctx = dev_id ;
eventfd_signal ( irq_ctx - > trigger , 1 ) ;
return IRQ_HANDLED ;
}
static int vfio_set_trigger ( struct vfio_platform_device * vdev , int index ,
int fd , irq_handler_t handler )
{
struct vfio_platform_irq * irq = & vdev - > irqs [ index ] ;
struct eventfd_ctx * trigger ;
int ret ;
if ( irq - > trigger ) {
2015-10-14 18:32:56 +03:00
irq_clear_status_flags ( irq - > hwirq , IRQ_NOAUTOEN ) ;
2015-03-16 23:08:50 +03:00
free_irq ( irq - > hwirq , irq ) ;
kfree ( irq - > name ) ;
eventfd_ctx_put ( irq - > trigger ) ;
irq - > trigger = NULL ;
}
if ( fd < 0 ) /* Disable only */
return 0 ;
irq - > name = kasprintf ( GFP_KERNEL , " vfio-irq[%d](%s) " ,
irq - > hwirq , vdev - > name ) ;
if ( ! irq - > name )
return - ENOMEM ;
trigger = eventfd_ctx_fdget ( fd ) ;
if ( IS_ERR ( trigger ) ) {
kfree ( irq - > name ) ;
return PTR_ERR ( trigger ) ;
}
irq - > trigger = trigger ;
2015-03-16 23:08:50 +03:00
irq_set_status_flags ( irq - > hwirq , IRQ_NOAUTOEN ) ;
2015-03-16 23:08:50 +03:00
ret = request_irq ( irq - > hwirq , handler , 0 , irq - > name , irq ) ;
if ( ret ) {
kfree ( irq - > name ) ;
eventfd_ctx_put ( trigger ) ;
irq - > trigger = NULL ;
return ret ;
}
2015-03-16 23:08:50 +03:00
if ( ! irq - > masked )
enable_irq ( irq - > hwirq ) ;
2015-03-16 23:08:50 +03:00
return 0 ;
}
2015-03-16 23:08:49 +03:00
static int vfio_platform_set_irq_trigger ( struct vfio_platform_device * vdev ,
unsigned index , unsigned start ,
unsigned count , uint32_t flags ,
void * data )
{
2015-03-16 23:08:50 +03:00
struct vfio_platform_irq * irq = & vdev - > irqs [ index ] ;
irq_handler_t handler ;
if ( vdev - > irqs [ index ] . flags & VFIO_IRQ_INFO_AUTOMASKED )
2015-03-16 23:08:50 +03:00
handler = vfio_automasked_irq_handler ;
2015-03-16 23:08:50 +03:00
else
handler = vfio_irq_handler ;
if ( ! count & & ( flags & VFIO_IRQ_SET_DATA_NONE ) )
return vfio_set_trigger ( vdev , index , - 1 , handler ) ;
if ( start ! = 0 | | count ! = 1 )
return - EINVAL ;
if ( flags & VFIO_IRQ_SET_DATA_EVENTFD ) {
int32_t fd = * ( int32_t * ) data ;
return vfio_set_trigger ( vdev , index , fd , handler ) ;
}
if ( flags & VFIO_IRQ_SET_DATA_NONE ) {
handler ( irq - > hwirq , irq ) ;
} else if ( flags & VFIO_IRQ_SET_DATA_BOOL ) {
uint8_t trigger = * ( uint8_t * ) data ;
if ( trigger )
handler ( irq - > hwirq , irq ) ;
}
return 0 ;
2015-03-16 23:08:49 +03:00
}
int vfio_platform_set_irqs_ioctl ( struct vfio_platform_device * vdev ,
uint32_t flags , unsigned index , unsigned start ,
unsigned count , void * data )
{
int ( * func ) ( struct vfio_platform_device * vdev , unsigned index ,
unsigned start , unsigned count , uint32_t flags ,
void * data ) = NULL ;
switch ( flags & VFIO_IRQ_SET_ACTION_TYPE_MASK ) {
case VFIO_IRQ_SET_ACTION_MASK :
func = vfio_platform_set_irq_mask ;
break ;
case VFIO_IRQ_SET_ACTION_UNMASK :
func = vfio_platform_set_irq_unmask ;
break ;
case VFIO_IRQ_SET_ACTION_TRIGGER :
func = vfio_platform_set_irq_trigger ;
break ;
}
if ( ! func )
return - ENOTTY ;
return func ( vdev , index , start , count , flags , data ) ;
}
2015-03-16 23:08:48 +03:00
int vfio_platform_irq_init ( struct vfio_platform_device * vdev )
{
int cnt = 0 , i ;
while ( vdev - > get_irq ( vdev , cnt ) > = 0 )
cnt + + ;
vdev - > irqs = kcalloc ( cnt , sizeof ( struct vfio_platform_irq ) , GFP_KERNEL ) ;
if ( ! vdev - > irqs )
return - ENOMEM ;
for ( i = 0 ; i < cnt ; i + + ) {
2015-03-16 23:08:49 +03:00
int hwirq = vdev - > get_irq ( vdev , i ) ;
if ( hwirq < 0 )
goto err ;
2015-03-16 23:08:50 +03:00
spin_lock_init ( & vdev - > irqs [ i ] . lock ) ;
2015-03-16 23:08:50 +03:00
vdev - > irqs [ i ] . flags = VFIO_IRQ_INFO_EVENTFD ;
if ( irq_get_trigger_type ( hwirq ) & IRQ_TYPE_LEVEL_MASK )
vdev - > irqs [ i ] . flags | = VFIO_IRQ_INFO_MASKABLE
| VFIO_IRQ_INFO_AUTOMASKED ;
2015-03-16 23:08:48 +03:00
vdev - > irqs [ i ] . count = 1 ;
2015-03-16 23:08:49 +03:00
vdev - > irqs [ i ] . hwirq = hwirq ;
2015-03-16 23:08:50 +03:00
vdev - > irqs [ i ] . masked = false ;
2015-03-16 23:08:48 +03:00
}
vdev - > num_irqs = cnt ;
return 0 ;
2015-03-16 23:08:49 +03:00
err :
kfree ( vdev - > irqs ) ;
return - EINVAL ;
2015-03-16 23:08:48 +03:00
}
void vfio_platform_irq_cleanup ( struct vfio_platform_device * vdev )
{
2015-03-16 23:08:50 +03:00
int i ;
for ( i = 0 ; i < vdev - > num_irqs ; i + + )
vfio_set_trigger ( vdev , i , - 1 , NULL ) ;
2015-03-16 23:08:48 +03:00
vdev - > num_irqs = 0 ;
kfree ( vdev - > irqs ) ;
}