2018-01-23 12:31:42 +01:00
// SPDX-License-Identifier: GPL-2.0+
// Copyright 2017 IBM Corp.
# include <linux/interrupt.h>
# include <linux/eventfd.h>
# include "ocxl_internal.h"
2018-01-23 12:31:44 +01:00
# include "trace.h"
2018-01-23 12:31:42 +01:00
struct afu_irq {
int id ;
int hw_irq ;
unsigned int virq ;
char * name ;
u64 trigger_page ;
struct eventfd_ctx * ev_ctx ;
} ;
static int irq_offset_to_id ( struct ocxl_context * ctx , u64 offset )
{
return ( offset - ctx - > afu - > irq_base_offset ) > > PAGE_SHIFT ;
}
static u64 irq_id_to_offset ( struct ocxl_context * ctx , int id )
{
return ctx - > afu - > irq_base_offset + ( id < < PAGE_SHIFT ) ;
}
static irqreturn_t afu_irq_handler ( int virq , void * data )
{
struct afu_irq * irq = ( struct afu_irq * ) data ;
2018-01-23 12:31:44 +01:00
trace_ocxl_afu_irq_receive ( virq ) ;
2018-01-23 12:31:42 +01:00
if ( irq - > ev_ctx )
eventfd_signal ( irq - > ev_ctx , 1 ) ;
return IRQ_HANDLED ;
}
static int setup_afu_irq ( struct ocxl_context * ctx , struct afu_irq * irq )
{
int rc ;
irq - > virq = irq_create_mapping ( NULL , irq - > hw_irq ) ;
if ( ! irq - > virq ) {
pr_err ( " irq_create_mapping failed \n " ) ;
return - ENOMEM ;
}
pr_debug ( " hw_irq %d mapped to virq %u \n " , irq - > hw_irq , irq - > virq ) ;
irq - > name = kasprintf ( GFP_KERNEL , " ocxl-afu-%u " , irq - > virq ) ;
if ( ! irq - > name ) {
irq_dispose_mapping ( irq - > virq ) ;
return - ENOMEM ;
}
rc = request_irq ( irq - > virq , afu_irq_handler , 0 , irq - > name , irq ) ;
if ( rc ) {
kfree ( irq - > name ) ;
irq - > name = NULL ;
irq_dispose_mapping ( irq - > virq ) ;
pr_err ( " request_irq failed: %d \n " , rc ) ;
return rc ;
}
return 0 ;
}
static void release_afu_irq ( struct afu_irq * irq )
{
free_irq ( irq - > virq , irq ) ;
irq_dispose_mapping ( irq - > virq ) ;
kfree ( irq - > name ) ;
}
int ocxl_afu_irq_alloc ( struct ocxl_context * ctx , u64 * irq_offset )
{
struct afu_irq * irq ;
int rc ;
irq = kzalloc ( sizeof ( struct afu_irq ) , GFP_KERNEL ) ;
if ( ! irq )
return - ENOMEM ;
/*
* We limit the number of afu irqs per context and per link to
* avoid a single process or user depleting the pool of IPIs
*/
mutex_lock ( & ctx - > irq_lock ) ;
irq - > id = idr_alloc ( & ctx - > irq_idr , irq , 0 , MAX_IRQ_PER_CONTEXT ,
GFP_KERNEL ) ;
if ( irq - > id < 0 ) {
rc = - ENOSPC ;
goto err_unlock ;
}
rc = ocxl_link_irq_alloc ( ctx - > afu - > fn - > link , & irq - > hw_irq ,
& irq - > trigger_page ) ;
if ( rc )
goto err_idr ;
rc = setup_afu_irq ( ctx , irq ) ;
if ( rc )
goto err_alloc ;
* irq_offset = irq_id_to_offset ( ctx , irq - > id ) ;
2018-01-23 12:31:44 +01:00
trace_ocxl_afu_irq_alloc ( ctx - > pasid , irq - > id , irq - > virq , irq - > hw_irq ,
* irq_offset ) ;
2018-01-23 12:31:42 +01:00
mutex_unlock ( & ctx - > irq_lock ) ;
return 0 ;
err_alloc :
ocxl_link_free_irq ( ctx - > afu - > fn - > link , irq - > hw_irq ) ;
err_idr :
idr_remove ( & ctx - > irq_idr , irq - > id ) ;
err_unlock :
mutex_unlock ( & ctx - > irq_lock ) ;
kfree ( irq ) ;
return rc ;
}
static void afu_irq_free ( struct afu_irq * irq , struct ocxl_context * ctx )
{
2018-01-23 12:31:44 +01:00
trace_ocxl_afu_irq_free ( ctx - > pasid , irq - > id ) ;
2018-01-23 12:31:42 +01:00
if ( ctx - > mapping )
unmap_mapping_range ( ctx - > mapping ,
irq_id_to_offset ( ctx , irq - > id ) ,
1 < < PAGE_SHIFT , 1 ) ;
release_afu_irq ( irq ) ;
if ( irq - > ev_ctx )
eventfd_ctx_put ( irq - > ev_ctx ) ;
ocxl_link_free_irq ( ctx - > afu - > fn - > link , irq - > hw_irq ) ;
kfree ( irq ) ;
}
int ocxl_afu_irq_free ( struct ocxl_context * ctx , u64 irq_offset )
{
struct afu_irq * irq ;
int id = irq_offset_to_id ( ctx , irq_offset ) ;
mutex_lock ( & ctx - > irq_lock ) ;
irq = idr_find ( & ctx - > irq_idr , id ) ;
if ( ! irq ) {
mutex_unlock ( & ctx - > irq_lock ) ;
return - EINVAL ;
}
idr_remove ( & ctx - > irq_idr , irq - > id ) ;
afu_irq_free ( irq , ctx ) ;
mutex_unlock ( & ctx - > irq_lock ) ;
return 0 ;
}
void ocxl_afu_irq_free_all ( struct ocxl_context * ctx )
{
struct afu_irq * irq ;
int id ;
mutex_lock ( & ctx - > irq_lock ) ;
idr_for_each_entry ( & ctx - > irq_idr , irq , id )
afu_irq_free ( irq , ctx ) ;
mutex_unlock ( & ctx - > irq_lock ) ;
}
int ocxl_afu_irq_set_fd ( struct ocxl_context * ctx , u64 irq_offset , int eventfd )
{
struct afu_irq * irq ;
struct eventfd_ctx * ev_ctx ;
int rc = 0 , id = irq_offset_to_id ( ctx , irq_offset ) ;
mutex_lock ( & ctx - > irq_lock ) ;
irq = idr_find ( & ctx - > irq_idr , id ) ;
if ( ! irq ) {
rc = - EINVAL ;
goto unlock ;
}
ev_ctx = eventfd_ctx_fdget ( eventfd ) ;
if ( IS_ERR ( ev_ctx ) ) {
rc = - EINVAL ;
goto unlock ;
}
irq - > ev_ctx = ev_ctx ;
unlock :
mutex_unlock ( & ctx - > irq_lock ) ;
return rc ;
}
u64 ocxl_afu_irq_get_addr ( struct ocxl_context * ctx , u64 irq_offset )
{
struct afu_irq * irq ;
int id = irq_offset_to_id ( ctx , irq_offset ) ;
u64 addr = 0 ;
mutex_lock ( & ctx - > irq_lock ) ;
irq = idr_find ( & ctx - > irq_idr , id ) ;
if ( irq )
addr = irq - > trigger_page ;
mutex_unlock ( & ctx - > irq_lock ) ;
return addr ;
}