2017-03-17 04:17:31 +01: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 04:17:35 +01:00
# include <linux/uuid.h>
# include <linux/mdev.h>
2017-03-17 04:17:31 +01:00
# include <asm/isc.h>
2017-03-17 04:17:35 +01:00
# include "ioasm.h"
# include "css.h"
2017-03-17 04:17:31 +01:00
# include "vfio_ccw_private.h"
/*
* Helpers
*/
2017-03-17 04:17:33 +01:00
int vfio_ccw_sch_quiesce ( struct subchannel * sch )
2017-03-17 04:17:31 +01: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 ;
ret = cio_cancel_halt_clear ( sch , & iretry ) ;
} ;
ret = cio_disable_subchannel ( sch ) ;
} while ( ret = = - EBUSY ) ;
out_unlock :
spin_unlock_irq ( sch - > lock ) ;
return ret ;
}
2017-03-17 04:17:35 +01:00
static int doing_io ( struct vfio_ccw_private * private , u32 intparm )
{
return ( private - > intparm = = intparm ) ;
}
static int vfio_ccw_sch_io_helper ( struct vfio_ccw_private * private )
{
struct subchannel * sch ;
union orb * orb ;
int ccode ;
__u8 lpm ;
u32 intparm ;
sch = private - > sch ;
orb = cp_get_orb ( & private - > cp , ( u32 ) ( addr_t ) sch , sch - > lpm ) ;
/* Issue "Start Subchannel" */
ccode = ssch ( sch - > schid , orb ) ;
switch ( ccode ) {
case 0 :
/*
* Initialize device status information
*/
sch - > schib . scsw . cmd . actl | = SCSW_ACTL_START_PEND ;
break ;
case 1 : /* Status pending */
case 2 : /* Busy */
return - EBUSY ;
case 3 : /* Device/path not operational */
{
lpm = orb - > cmd . lpm ;
if ( lpm ! = 0 )
sch - > lpm & = ~ lpm ;
else
sch - > lpm = 0 ;
if ( cio_update_schib ( sch ) )
return - ENODEV ;
return sch - > lpm ? - EACCES : - ENODEV ;
}
default :
return ccode ;
}
intparm = ( u32 ) ( addr_t ) sch ;
private - > intparm = 0 ;
wait_event ( private - > wait_q , doing_io ( private , intparm ) ) ;
if ( scsw_is_solicited ( & private - > irb . scsw ) )
cp_update_scsw ( & private - > cp , & private - > irb . scsw ) ;
return 0 ;
}
/* Deal with the ccw command request from the userspace. */
int vfio_ccw_sch_cmd_request ( struct vfio_ccw_private * private )
{
struct mdev_device * mdev = private - > mdev ;
union orb * orb ;
union scsw * scsw = & private - > scsw ;
struct irb * irb = & private - > irb ;
struct ccw_io_region * io_region = & private - > io_region ;
int ret ;
memcpy ( scsw , io_region - > scsw_area , sizeof ( * scsw ) ) ;
if ( scsw - > cmd . fctl & SCSW_FCTL_START_FUNC ) {
orb = ( union orb * ) io_region - > orb_area ;
ret = cp_init ( & private - > cp , mdev_dev ( mdev ) , orb ) ;
if ( ret )
return ret ;
ret = cp_prefetch ( & private - > cp ) ;
if ( ret ) {
cp_free ( & private - > cp ) ;
return ret ;
}
/* Start channel program and wait for I/O interrupt. */
ret = vfio_ccw_sch_io_helper ( private ) ;
if ( ! ret ) {
/* Get irb info and copy it to irb_area. */
memcpy ( io_region - > irb_area , irb , sizeof ( * irb ) ) ;
}
cp_free ( & private - > cp ) ;
} else if ( scsw - > cmd . fctl & SCSW_FCTL_HALT_FUNC ) {
/* XXX: Handle halt. */
ret = - EOPNOTSUPP ;
} else if ( scsw - > cmd . fctl & SCSW_FCTL_CLEAR_FUNC ) {
/* XXX: Handle clear. */
ret = - EOPNOTSUPP ;
} else {
ret = - EOPNOTSUPP ;
}
return ret ;
}
2017-03-17 04:17:31 +01:00
/*
* Sysfs interfaces
*/
static ssize_t chpids_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct subchannel * sch = to_subchannel ( dev ) ;
struct chsc_ssd_info * ssd = & sch - > ssd_info ;
ssize_t ret = 0 ;
int chp ;
int mask ;
for ( chp = 0 ; chp < 8 ; chp + + ) {
mask = 0x80 > > chp ;
if ( ssd - > path_mask & mask )
ret + = sprintf ( buf + ret , " %02x " , ssd - > chpid [ chp ] . id ) ;
else
ret + = sprintf ( buf + ret , " 00 " ) ;
}
ret + = sprintf ( buf + ret , " \n " ) ;
return ret ;
}
static ssize_t pimpampom_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct subchannel * sch = to_subchannel ( dev ) ;
struct pmcw * pmcw = & sch - > schib . pmcw ;
return sprintf ( buf , " %02x %02x %02x \n " ,
pmcw - > pim , pmcw - > pam , pmcw - > pom ) ;
}
static DEVICE_ATTR ( chpids , 0444 , chpids_show , NULL ) ;
static DEVICE_ATTR ( pimpampom , 0444 , pimpampom_show , NULL ) ;
static struct attribute * vfio_subchannel_attrs [ ] = {
& dev_attr_chpids . attr ,
& dev_attr_pimpampom . attr ,
NULL ,
} ;
static struct attribute_group vfio_subchannel_attr_group = {
. attrs = vfio_subchannel_attrs ,
} ;
/*
* Css driver callbacks
*/
static void vfio_ccw_sch_irq ( struct subchannel * sch )
{
struct vfio_ccw_private * private = dev_get_drvdata ( & sch - > dev ) ;
2017-03-17 04:17:35 +01:00
struct irb * irb ;
2017-03-17 04:17:31 +01:00
inc_irq_stat ( IRQIO_CIO ) ;
if ( ! private )
return ;
2017-03-17 04:17:35 +01:00
irb = this_cpu_ptr ( & cio_irb ) ;
memcpy ( & private - > irb , irb , sizeof ( * irb ) ) ;
private - > intparm = ( u32 ) ( addr_t ) sch ;
wake_up ( & private - > wait_q ) ;
2017-03-17 04:17:31 +01:00
if ( private - > completion )
complete ( private - > completion ) ;
}
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 ) ;
sch - > isc = VFIO_CCW_ISC ;
ret = cio_enable_subchannel ( sch , ( u32 ) ( unsigned long ) sch ) ;
spin_unlock_irq ( sch - > lock ) ;
if ( ret )
goto out_free ;
ret = sysfs_create_group ( & sch - > dev . kobj , & vfio_subchannel_attr_group ) ;
if ( ret )
goto out_disable ;
2017-03-17 04:17:33 +01:00
ret = vfio_ccw_mdev_reg ( sch ) ;
if ( ret )
goto out_rm_group ;
2017-03-17 04:17:35 +01:00
init_waitqueue_head ( & private - > wait_q ) ;
2017-03-17 04:17:33 +01:00
atomic_set ( & private - > avail , 1 ) ;
2017-03-17 04:17:31 +01:00
return 0 ;
2017-03-17 04:17:33 +01:00
out_rm_group :
sysfs_remove_group ( & sch - > dev . kobj , & vfio_subchannel_attr_group ) ;
2017-03-17 04:17:31 +01:00
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 04:17:33 +01:00
vfio_ccw_mdev_unreg ( sch ) ;
2017-03-17 04:17:31 +01:00
sysfs_remove_group ( & sch - > dev . kobj , & vfio_subchannel_attr_group ) ;
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 )
{
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 ) ) {
/* Not operational. */
css_sched_sch_todo ( sch , SCH_TODO_UNREG ) ;
/*
* TODO :
* Probably we should send the machine check to the guest .
*/
goto out_unlock ;
}
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 ;
isc_register ( VFIO_CCW_ISC ) ;
ret = css_driver_register ( & vfio_ccw_sch_driver ) ;
if ( ret )
isc_unregister ( VFIO_CCW_ISC ) ;
return ret ;
}
static void __exit vfio_ccw_sch_exit ( void )
{
css_driver_unregister ( & vfio_ccw_sch_driver ) ;
isc_unregister ( VFIO_CCW_ISC ) ;
}
module_init ( vfio_ccw_sch_init ) ;
module_exit ( vfio_ccw_sch_exit ) ;
MODULE_LICENSE ( " GPL v2 " ) ;