2017-11-14 20:38:02 +03:00
// SPDX-License-Identifier: GPL-2.0
2017-03-17 06:17:31 +03:00
/*
* VFIO based Physical Subchannel device driver
*
* Copyright IBM Corp . 2017
*
* Author ( s ) : Dong Jia Shi < bjsdjshi @ linux . vnet . ibm . com >
* Xiao Feng Ren < renxiaof @ linux . vnet . ibm . com >
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/device.h>
# include <linux/slab.h>
2017-03-17 06:17:35 +03:00
# include <linux/uuid.h>
# include <linux/mdev.h>
2017-03-17 06:17:31 +03:00
# include <asm/isc.h>
2017-03-17 06:17:35 +03:00
# include "ioasm.h"
# include "css.h"
2017-03-17 06:17:31 +03:00
# include "vfio_ccw_private.h"
2017-03-17 06:17:39 +03:00
struct workqueue_struct * vfio_ccw_work_q ;
2017-03-17 06:17:31 +03:00
/*
* Helpers
*/
2017-03-17 06:17:33 +03:00
int vfio_ccw_sch_quiesce ( struct subchannel * sch )
2017-03-17 06:17:31 +03:00
{
struct vfio_ccw_private * private = dev_get_drvdata ( & sch - > dev ) ;
DECLARE_COMPLETION_ONSTACK ( completion ) ;
int iretry , ret = 0 ;
spin_lock_irq ( sch - > lock ) ;
if ( ! sch - > schib . pmcw . ena )
goto out_unlock ;
ret = cio_disable_subchannel ( sch ) ;
if ( ret ! = - EBUSY )
goto out_unlock ;
do {
iretry = 255 ;
ret = cio_cancel_halt_clear ( sch , & iretry ) ;
while ( ret = = - EBUSY ) {
/*
* Flush all I / O and wait for
* cancel / halt / clear completion .
*/
private - > completion = & completion ;
spin_unlock_irq ( sch - > lock ) ;
wait_for_completion_timeout ( & completion , 3 * HZ ) ;
spin_lock_irq ( sch - > lock ) ;
private - > completion = NULL ;
2017-03-17 06:17:39 +03:00
flush_workqueue ( vfio_ccw_work_q ) ;
2017-03-17 06:17:31 +03:00
ret = cio_cancel_halt_clear ( sch , & iretry ) ;
} ;
ret = cio_disable_subchannel ( sch ) ;
} while ( ret = = - EBUSY ) ;
out_unlock :
2017-03-17 06:17:40 +03:00
private - > state = VFIO_CCW_STATE_NOT_OPER ;
2017-03-17 06:17:31 +03:00
spin_unlock_irq ( sch - > lock ) ;
return ret ;
}
2017-03-17 06:17:39 +03:00
static void vfio_ccw_sch_io_todo ( struct work_struct * work )
{
struct vfio_ccw_private * private ;
struct irb * irb ;
2017-03-17 06:17:35 +03:00
2017-03-17 06:17:39 +03:00
private = container_of ( work , struct vfio_ccw_private , io_work ) ;
irb = & private - > irb ;
2017-03-17 06:17:35 +03:00
2017-03-17 06:17:39 +03:00
if ( scsw_is_solicited ( & irb - > scsw ) ) {
cp_update_scsw ( & private - > cp , & irb - > scsw ) ;
cp_free ( & private - > cp ) ;
}
memcpy ( private - > io_region . irb_area , irb , sizeof ( * irb ) ) ;
if ( private - > io_trigger )
eventfd_signal ( private - > io_trigger , 1 ) ;
2017-03-17 06:17:35 +03:00
2017-03-17 06:17:40 +03:00
if ( private - > mdev )
private - > state = VFIO_CCW_STATE_IDLE ;
2017-03-17 06:17:35 +03:00
}
2017-03-17 06:17:31 +03:00
/*
* Css driver callbacks
*/
static void vfio_ccw_sch_irq ( struct subchannel * sch )
{
struct vfio_ccw_private * private = dev_get_drvdata ( & sch - > dev ) ;
inc_irq_stat ( IRQIO_CIO ) ;
2017-03-17 06:17:40 +03:00
vfio_ccw_fsm_event ( private , VFIO_CCW_EVENT_INTERRUPT ) ;
2017-03-17 06:17:31 +03:00
}
static int vfio_ccw_sch_probe ( struct subchannel * sch )
{
struct pmcw * pmcw = & sch - > schib . pmcw ;
struct vfio_ccw_private * private ;
int ret ;
if ( pmcw - > qf ) {
dev_warn ( & sch - > dev , " vfio: ccw: does not support QDIO: %s \n " ,
dev_name ( & sch - > dev ) ) ;
return - ENODEV ;
}
private = kzalloc ( sizeof ( * private ) , GFP_KERNEL | GFP_DMA ) ;
if ( ! private )
return - ENOMEM ;
private - > sch = sch ;
dev_set_drvdata ( & sch - > dev , private ) ;
spin_lock_irq ( sch - > lock ) ;
2017-03-17 06:17:40 +03:00
private - > state = VFIO_CCW_STATE_NOT_OPER ;
2017-03-17 06:17:31 +03:00
sch - > isc = VFIO_CCW_ISC ;
ret = cio_enable_subchannel ( sch , ( u32 ) ( unsigned long ) sch ) ;
spin_unlock_irq ( sch - > lock ) ;
if ( ret )
goto out_free ;
2017-03-17 06:17:33 +03:00
ret = vfio_ccw_mdev_reg ( sch ) ;
if ( ret )
2017-05-15 16:49:07 +03:00
goto out_disable ;
2017-03-17 06:17:33 +03:00
2017-03-17 06:17:39 +03:00
INIT_WORK ( & private - > io_work , vfio_ccw_sch_io_todo ) ;
2017-03-17 06:17:33 +03:00
atomic_set ( & private - > avail , 1 ) ;
2017-03-17 06:17:40 +03:00
private - > state = VFIO_CCW_STATE_STANDBY ;
2017-03-17 06:17:33 +03:00
2017-03-17 06:17:31 +03:00
return 0 ;
out_disable :
cio_disable_subchannel ( sch ) ;
out_free :
dev_set_drvdata ( & sch - > dev , NULL ) ;
kfree ( private ) ;
return ret ;
}
static int vfio_ccw_sch_remove ( struct subchannel * sch )
{
struct vfio_ccw_private * private = dev_get_drvdata ( & sch - > dev ) ;
vfio_ccw_sch_quiesce ( sch ) ;
2017-03-17 06:17:33 +03:00
vfio_ccw_mdev_unreg ( sch ) ;
2017-03-17 06:17:31 +03:00
dev_set_drvdata ( & sch - > dev , NULL ) ;
kfree ( private ) ;
return 0 ;
}
static void vfio_ccw_sch_shutdown ( struct subchannel * sch )
{
vfio_ccw_sch_quiesce ( sch ) ;
}
/**
* vfio_ccw_sch_event - process subchannel event
* @ sch : subchannel
* @ process : non - zero if function is called in process context
*
* An unspecified event occurred for this subchannel . Adjust data according
* to the current operational state of the subchannel . Return zero when the
* event has been handled sufficiently or - EAGAIN when this function should
* be called again in process context .
*/
static int vfio_ccw_sch_event ( struct subchannel * sch , int process )
{
2017-03-17 06:17:40 +03:00
struct vfio_ccw_private * private = dev_get_drvdata ( & sch - > dev ) ;
2017-03-17 06:17:31 +03:00
unsigned long flags ;
spin_lock_irqsave ( sch - > lock , flags ) ;
if ( ! device_is_registered ( & sch - > dev ) )
goto out_unlock ;
if ( work_pending ( & sch - > todo_work ) )
goto out_unlock ;
if ( cio_update_schib ( sch ) ) {
2017-03-17 06:17:40 +03:00
vfio_ccw_fsm_event ( private , VFIO_CCW_EVENT_NOT_OPER ) ;
2017-03-17 06:17:31 +03:00
goto out_unlock ;
}
2017-03-17 06:17:40 +03:00
private = dev_get_drvdata ( & sch - > dev ) ;
if ( private - > state = = VFIO_CCW_STATE_NOT_OPER ) {
private - > state = private - > mdev ? VFIO_CCW_STATE_IDLE :
VFIO_CCW_STATE_STANDBY ;
}
2017-03-17 06:17:31 +03:00
out_unlock :
spin_unlock_irqrestore ( sch - > lock , flags ) ;
return 0 ;
}
static struct css_device_id vfio_ccw_sch_ids [ ] = {
{ . match_flags = 0x1 , . type = SUBCHANNEL_TYPE_IO , } ,
{ /* end of list */ } ,
} ;
MODULE_DEVICE_TABLE ( css , vfio_ccw_sch_ids ) ;
static struct css_driver vfio_ccw_sch_driver = {
. drv = {
. name = " vfio_ccw " ,
. owner = THIS_MODULE ,
} ,
. subchannel_type = vfio_ccw_sch_ids ,
. irq = vfio_ccw_sch_irq ,
. probe = vfio_ccw_sch_probe ,
. remove = vfio_ccw_sch_remove ,
. shutdown = vfio_ccw_sch_shutdown ,
. sch_event = vfio_ccw_sch_event ,
} ;
static int __init vfio_ccw_sch_init ( void )
{
int ret ;
2017-03-17 06:17:39 +03:00
vfio_ccw_work_q = create_singlethread_workqueue ( " vfio-ccw " ) ;
if ( ! vfio_ccw_work_q )
return - ENOMEM ;
2017-03-17 06:17:31 +03:00
isc_register ( VFIO_CCW_ISC ) ;
ret = css_driver_register ( & vfio_ccw_sch_driver ) ;
2017-03-17 06:17:39 +03:00
if ( ret ) {
2017-03-17 06:17:31 +03:00
isc_unregister ( VFIO_CCW_ISC ) ;
2017-03-17 06:17:39 +03:00
destroy_workqueue ( vfio_ccw_work_q ) ;
}
2017-03-17 06:17:31 +03:00
return ret ;
}
static void __exit vfio_ccw_sch_exit ( void )
{
css_driver_unregister ( & vfio_ccw_sch_driver ) ;
isc_unregister ( VFIO_CCW_ISC ) ;
2017-03-17 06:17:39 +03:00
destroy_workqueue ( vfio_ccw_work_q ) ;
2017-03-17 06:17:31 +03:00
}
module_init ( vfio_ccw_sch_init ) ;
module_exit ( vfio_ccw_sch_exit ) ;
MODULE_LICENSE ( " GPL v2 " ) ;