2013-04-08 09:52:57 +02:00
/*
* DIAGNOSE X ' 2 C4 ' instruction based HMC FTP services , useable on z / VM
*
* Copyright IBM Corp . 2013
* Author ( s ) : Ralf Hoppe ( rhoppe @ de . ibm . com )
*
*/
# define KMSG_COMPONENT "hmcdrv"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
# include <linux/kernel.h>
# include <linux/mm.h>
# include <linux/irq.h>
# include <linux/wait.h>
# include <linux/string.h>
# include <asm/ctl_reg.h>
2015-08-20 17:28:44 +02:00
# include <asm/diag.h>
2013-04-08 09:52:57 +02:00
# include "hmcdrv_ftp.h"
# include "diag_ftp.h"
/* DIAGNOSE X'2C4' return codes in Ry */
# define DIAG_FTP_RET_OK 0 /* HMC FTP started successfully */
# define DIAG_FTP_RET_EBUSY 4 /* HMC FTP service currently busy */
# define DIAG_FTP_RET_EIO 8 /* HMC FTP service I/O error */
/* and an artificial extension */
# define DIAG_FTP_RET_EPERM 2 /* HMC FTP service privilege error */
/* FTP service status codes (after INTR at guest real location 133) */
# define DIAG_FTP_STAT_OK 0U /* request completed successfully */
# define DIAG_FTP_STAT_PGCC 4U /* program check condition */
# define DIAG_FTP_STAT_PGIOE 8U /* paging I/O error */
# define DIAG_FTP_STAT_TIMEOUT 12U /* timeout */
# define DIAG_FTP_STAT_EBASE 16U /* base of error codes from SCLP */
# define DIAG_FTP_STAT_LDFAIL (DIAG_FTP_STAT_EBASE + 1U) /* failed */
# define DIAG_FTP_STAT_LDNPERM (DIAG_FTP_STAT_EBASE + 2U) /* not allowed */
# define DIAG_FTP_STAT_LDRUNS (DIAG_FTP_STAT_EBASE + 3U) /* runs */
# define DIAG_FTP_STAT_LDNRUNS (DIAG_FTP_STAT_EBASE + 4U) /* not runs */
/**
* struct diag_ftp_ldfpl - load file FTP parameter list ( LDFPL )
* @ bufaddr : real buffer address ( at 4 k boundary )
* @ buflen : length of buffer
* @ offset : dir / file offset
* @ intparm : interruption parameter ( unused )
* @ transferred : bytes transferred
* @ fsize : file size , filled on GET
* @ failaddr : failing address
* @ spare : padding
* @ fident : file name - ASCII
*/
struct diag_ftp_ldfpl {
u64 bufaddr ;
u64 buflen ;
u64 offset ;
u64 intparm ;
u64 transferred ;
u64 fsize ;
u64 failaddr ;
u64 spare ;
u8 fident [ HMCDRV_FTP_FIDENT_MAX ] ;
} __packed ;
static DECLARE_COMPLETION ( diag_ftp_rx_complete ) ;
static int diag_ftp_subcode ;
/**
* diag_ftp_handler ( ) - FTP services IRQ handler
* @ extirq : external interrupt ( sub - ) code
* @ param32 : 32 - bit interruption parameter from & struct diag_ftp_ldfpl
* @ param64 : unused ( for 64 - bit interrupt parameters )
*/
static void diag_ftp_handler ( struct ext_code extirq ,
unsigned int param32 ,
unsigned long param64 )
{
if ( ( extirq . subcode > > 8 ) ! = 8 )
return ; /* not a FTP services sub-code */
inc_irq_stat ( IRQEXT_FTP ) ;
diag_ftp_subcode = extirq . subcode & 0xffU ;
complete ( & diag_ftp_rx_complete ) ;
}
/**
* diag_ftp_2c4 ( ) - DIAGNOSE X ' 2 C4 ' service call
* @ fpl : pointer to prepared LDFPL
* @ cmd : FTP command to be executed
*
* Performs a DIAGNOSE X ' 2 C4 ' call with ( input / output ) FTP parameter list
* @ fpl and FTP function code @ cmd . In case of an error the function does
* nothing and returns an ( negative ) error code .
*
* Notes :
* 1. This function only initiates a transfer , so the caller must wait
* for completion ( asynchronous execution ) .
* 2. The FTP parameter list @ fpl must be aligned to a double - word boundary .
* 3. fpl - > bufaddr must be a real address , 4 k aligned
*/
static int diag_ftp_2c4 ( struct diag_ftp_ldfpl * fpl ,
enum hmcdrv_ftp_cmdid cmd )
{
int rc ;
2015-08-20 17:28:44 +02:00
diag_stat_inc ( DIAG_STAT_X2C4 ) ;
2013-04-08 09:52:57 +02:00
asm volatile (
" diag %[addr],%[cmd],0x2c4 \n "
" 0: j 2f \n "
" 1: la %[rc],%[err] \n "
" 2: \n "
EX_TABLE ( 0 b , 1 b )
: [ rc ] " =d " ( rc ) , " +m " ( * fpl )
: [ cmd ] " 0 " ( cmd ) , [ addr ] " d " ( virt_to_phys ( fpl ) ) ,
[ err ] " i " ( DIAG_FTP_RET_EPERM )
: " cc " ) ;
switch ( rc ) {
case DIAG_FTP_RET_OK :
return 0 ;
case DIAG_FTP_RET_EBUSY :
return - EBUSY ;
case DIAG_FTP_RET_EPERM :
return - EPERM ;
case DIAG_FTP_RET_EIO :
default :
return - EIO ;
}
}
/**
* diag_ftp_cmd ( ) - executes a DIAG X ' 2 C4 ' FTP command , targeting a HMC
* @ ftp : pointer to FTP command specification
* @ fsize : return of file size ( or NULL if undesirable )
*
* Attention : Notice that this function is not reentrant - so the caller
* must ensure locking .
*
* Return : number of bytes read / written or a ( negative ) error code
*/
ssize_t diag_ftp_cmd ( const struct hmcdrv_ftp_cmdspec * ftp , size_t * fsize )
{
struct diag_ftp_ldfpl * ldfpl ;
ssize_t len ;
# ifdef DEBUG
unsigned long start_jiffies ;
pr_debug ( " starting DIAG X'2C4' on '%s', requesting %zd bytes \n " ,
ftp - > fname , ftp - > len ) ;
start_jiffies = jiffies ;
# endif
init_completion ( & diag_ftp_rx_complete ) ;
ldfpl = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! ldfpl ) {
len = - ENOMEM ;
goto out ;
}
len = strlcpy ( ldfpl - > fident , ftp - > fname , sizeof ( ldfpl - > fident ) ) ;
if ( len > = HMCDRV_FTP_FIDENT_MAX ) {
len = - EINVAL ;
goto out_free ;
}
ldfpl - > transferred = 0 ;
ldfpl - > fsize = 0 ;
ldfpl - > offset = ftp - > ofs ;
ldfpl - > buflen = ftp - > len ;
ldfpl - > bufaddr = virt_to_phys ( ftp - > buf ) ;
len = diag_ftp_2c4 ( ldfpl , ftp - > id ) ;
if ( len )
goto out_free ;
/*
* There is no way to cancel the running diag X ' 2 C4 ' , the code
* needs to wait unconditionally until the transfer is complete .
*/
wait_for_completion ( & diag_ftp_rx_complete ) ;
# ifdef DEBUG
pr_debug ( " completed DIAG X'2C4' after %lu ms \n " ,
( jiffies - start_jiffies ) * 1000 / HZ ) ;
pr_debug ( " status of DIAG X'2C4' is %u, with %lld/%lld bytes \n " ,
diag_ftp_subcode , ldfpl - > transferred , ldfpl - > fsize ) ;
# endif
switch ( diag_ftp_subcode ) {
case DIAG_FTP_STAT_OK : /* success */
len = ldfpl - > transferred ;
if ( fsize )
* fsize = ldfpl - > fsize ;
break ;
case DIAG_FTP_STAT_LDNPERM :
len = - EPERM ;
break ;
case DIAG_FTP_STAT_LDRUNS :
len = - EBUSY ;
break ;
case DIAG_FTP_STAT_LDFAIL :
len = - ENOENT ; /* no such file or media */
break ;
default :
len = - EIO ;
break ;
}
out_free :
free_page ( ( unsigned long ) ldfpl ) ;
out :
return len ;
}
/**
* diag_ftp_startup ( ) - startup of FTP services , when running on z / VM
*
* Return : 0 on success , else an ( negative ) error code
*/
int diag_ftp_startup ( void )
{
int rc ;
rc = register_external_irq ( EXT_IRQ_CP_SERVICE , diag_ftp_handler ) ;
if ( rc )
return rc ;
2015-08-17 07:56:20 +02:00
irq_subclass_register ( IRQ_SUBCLASS_SERVICE_SIGNAL ) ;
2013-04-08 09:52:57 +02:00
return 0 ;
}
/**
* diag_ftp_shutdown ( ) - shutdown of FTP services , when running on z / VM
*/
void diag_ftp_shutdown ( void )
{
2015-08-17 07:56:20 +02:00
irq_subclass_unregister ( IRQ_SUBCLASS_SERVICE_SIGNAL ) ;
2013-04-08 09:52:57 +02:00
unregister_external_irq ( EXT_IRQ_CP_SERVICE , diag_ftp_handler ) ;
}