![Heiko Carstens](/assets/img/avatar_default.png)
The z/VM driver sets bit "63-22" in control register zero to one in order to enable the CP Service interrupt (0x2603). However the irq subclass mask that normally corresponds to the CP Service interrupt is "63-54" (== "63-22-32"). So it looks like the author read the documentation with the 32 bit sized cr0 register bit positions (== 22), but didn't realize that bit numbers change, if applied to a 64 bit register (== 54) due to the numbering scheme. Also use irq_subclass_register() instead if ctl_set_bit() since multiple services depend on the service signal subclass mask, which is the correct bit. This also explains why nobody noticed the bug, since the bit is always enabled anyway (e.g. pfault). Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com> Reviewed-by: Hendrik Brueckner <brueckner@linux.vnet.ibm.com> Acked-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
238 lines
6.1 KiB
C
238 lines
6.1 KiB
C
/*
|
|
* DIAGNOSE X'2C4' 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>
|
|
|
|
#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 4k 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'2C4' service call
|
|
* @fpl: pointer to prepared LDFPL
|
|
* @cmd: FTP command to be executed
|
|
*
|
|
* Performs a DIAGNOSE X'2C4' 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, 4k aligned
|
|
*/
|
|
static int diag_ftp_2c4(struct diag_ftp_ldfpl *fpl,
|
|
enum hmcdrv_ftp_cmdid cmd)
|
|
{
|
|
int rc;
|
|
|
|
asm volatile(
|
|
" diag %[addr],%[cmd],0x2c4\n"
|
|
"0: j 2f\n"
|
|
"1: la %[rc],%[err]\n"
|
|
"2:\n"
|
|
EX_TABLE(0b, 1b)
|
|
: [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'2C4' 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'2C4', 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;
|
|
|
|
irq_subclass_register(IRQ_SUBCLASS_SERVICE_SIGNAL);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* diag_ftp_shutdown() - shutdown of FTP services, when running on z/VM
|
|
*/
|
|
void diag_ftp_shutdown(void)
|
|
{
|
|
irq_subclass_unregister(IRQ_SUBCLASS_SERVICE_SIGNAL);
|
|
unregister_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
|
|
}
|