279 lines
6.7 KiB
C
279 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* SCLP Event Type (ET) 7 - Diagnostic Test FTP Services, useable on LPAR
|
|
*
|
|
* 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/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/string.h>
|
|
#include <linux/jiffies.h>
|
|
#include <asm/sysinfo.h>
|
|
#include <asm/ebcdic.h>
|
|
|
|
#include "sclp.h"
|
|
#include "sclp_diag.h"
|
|
#include "sclp_ftp.h"
|
|
|
|
static DECLARE_COMPLETION(sclp_ftp_rx_complete);
|
|
static u8 sclp_ftp_ldflg;
|
|
static u64 sclp_ftp_fsize;
|
|
static u64 sclp_ftp_length;
|
|
|
|
/**
|
|
* sclp_ftp_txcb() - Diagnostic Test FTP services SCLP command callback
|
|
* @req: sclp request
|
|
* @data: pointer to struct completion
|
|
*/
|
|
static void sclp_ftp_txcb(struct sclp_req *req, void *data)
|
|
{
|
|
struct completion *completion = data;
|
|
|
|
#ifdef DEBUG
|
|
pr_debug("SCLP (ET7) TX-IRQ, SCCB @ 0x%p: %*phN\n",
|
|
req->sccb, 24, req->sccb);
|
|
#endif
|
|
complete(completion);
|
|
}
|
|
|
|
/**
|
|
* sclp_ftp_rxcb() - Diagnostic Test FTP services receiver event callback
|
|
* @evbuf: pointer to Diagnostic Test (ET7) event buffer
|
|
*/
|
|
static void sclp_ftp_rxcb(struct evbuf_header *evbuf)
|
|
{
|
|
struct sclp_diag_evbuf *diag = (struct sclp_diag_evbuf *) evbuf;
|
|
|
|
/*
|
|
* Check for Diagnostic Test FTP Service
|
|
*/
|
|
if (evbuf->type != EVTYP_DIAG_TEST ||
|
|
diag->route != SCLP_DIAG_FTP_ROUTE ||
|
|
diag->mdd.ftp.pcx != SCLP_DIAG_FTP_XPCX ||
|
|
evbuf->length < SCLP_DIAG_FTP_EVBUF_LEN)
|
|
return;
|
|
|
|
#ifdef DEBUG
|
|
pr_debug("SCLP (ET7) RX-IRQ, Event @ 0x%p: %*phN\n",
|
|
evbuf, 24, evbuf);
|
|
#endif
|
|
|
|
/*
|
|
* Because the event buffer is located in a page which is owned
|
|
* by the SCLP core, all data of interest must be copied. The
|
|
* error indication is in 'sclp_ftp_ldflg'
|
|
*/
|
|
sclp_ftp_ldflg = diag->mdd.ftp.ldflg;
|
|
sclp_ftp_fsize = diag->mdd.ftp.fsize;
|
|
sclp_ftp_length = diag->mdd.ftp.length;
|
|
|
|
complete(&sclp_ftp_rx_complete);
|
|
}
|
|
|
|
/**
|
|
* sclp_ftp_et7() - start a Diagnostic Test FTP Service SCLP request
|
|
* @ftp: pointer to FTP descriptor
|
|
*
|
|
* Return: 0 on success, else a (negative) error code
|
|
*/
|
|
static int sclp_ftp_et7(const struct hmcdrv_ftp_cmdspec *ftp)
|
|
{
|
|
struct completion completion;
|
|
struct sclp_diag_sccb *sccb;
|
|
struct sclp_req *req;
|
|
size_t len;
|
|
int rc;
|
|
|
|
req = kzalloc(sizeof(*req), GFP_KERNEL);
|
|
sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
|
|
if (!req || !sccb) {
|
|
rc = -ENOMEM;
|
|
goto out_free;
|
|
}
|
|
|
|
sccb->hdr.length = SCLP_DIAG_FTP_EVBUF_LEN +
|
|
sizeof(struct sccb_header);
|
|
sccb->evbuf.hdr.type = EVTYP_DIAG_TEST;
|
|
sccb->evbuf.hdr.length = SCLP_DIAG_FTP_EVBUF_LEN;
|
|
sccb->evbuf.hdr.flags = 0; /* clear processed-buffer */
|
|
sccb->evbuf.route = SCLP_DIAG_FTP_ROUTE;
|
|
sccb->evbuf.mdd.ftp.pcx = SCLP_DIAG_FTP_XPCX;
|
|
sccb->evbuf.mdd.ftp.srcflg = 0;
|
|
sccb->evbuf.mdd.ftp.pgsize = 0;
|
|
sccb->evbuf.mdd.ftp.asce = _ASCE_REAL_SPACE;
|
|
sccb->evbuf.mdd.ftp.ldflg = SCLP_DIAG_FTP_LDFAIL;
|
|
sccb->evbuf.mdd.ftp.fsize = 0;
|
|
sccb->evbuf.mdd.ftp.cmd = ftp->id;
|
|
sccb->evbuf.mdd.ftp.offset = ftp->ofs;
|
|
sccb->evbuf.mdd.ftp.length = ftp->len;
|
|
sccb->evbuf.mdd.ftp.bufaddr = virt_to_phys(ftp->buf);
|
|
|
|
len = strlcpy(sccb->evbuf.mdd.ftp.fident, ftp->fname,
|
|
HMCDRV_FTP_FIDENT_MAX);
|
|
if (len >= HMCDRV_FTP_FIDENT_MAX) {
|
|
rc = -EINVAL;
|
|
goto out_free;
|
|
}
|
|
|
|
req->command = SCLP_CMDW_WRITE_EVENT_DATA;
|
|
req->sccb = sccb;
|
|
req->status = SCLP_REQ_FILLED;
|
|
req->callback = sclp_ftp_txcb;
|
|
req->callback_data = &completion;
|
|
|
|
init_completion(&completion);
|
|
|
|
rc = sclp_add_request(req);
|
|
if (rc)
|
|
goto out_free;
|
|
|
|
/* Wait for end of ftp sclp command. */
|
|
wait_for_completion(&completion);
|
|
|
|
#ifdef DEBUG
|
|
pr_debug("status of SCLP (ET7) request is 0x%04x (0x%02x)\n",
|
|
sccb->hdr.response_code, sccb->evbuf.hdr.flags);
|
|
#endif
|
|
|
|
/*
|
|
* Check if sclp accepted the request. The data transfer runs
|
|
* asynchronously and the completion is indicated with an
|
|
* sclp ET7 event.
|
|
*/
|
|
if (req->status != SCLP_REQ_DONE ||
|
|
(sccb->evbuf.hdr.flags & 0x80) == 0 || /* processed-buffer */
|
|
(sccb->hdr.response_code & 0xffU) != 0x20U) {
|
|
rc = -EIO;
|
|
}
|
|
|
|
out_free:
|
|
free_page((unsigned long) sccb);
|
|
kfree(req);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* sclp_ftp_cmd() - executes a HMC related SCLP Diagnose (ET7) FTP command
|
|
* @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 sclp_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize)
|
|
{
|
|
ssize_t len;
|
|
#ifdef DEBUG
|
|
unsigned long start_jiffies;
|
|
|
|
pr_debug("starting SCLP (ET7), cmd %d for '%s' at %lld with %zd bytes\n",
|
|
ftp->id, ftp->fname, (long long) ftp->ofs, ftp->len);
|
|
start_jiffies = jiffies;
|
|
#endif
|
|
|
|
init_completion(&sclp_ftp_rx_complete);
|
|
|
|
/* Start ftp sclp command. */
|
|
len = sclp_ftp_et7(ftp);
|
|
if (len)
|
|
goto out_unlock;
|
|
|
|
/*
|
|
* There is no way to cancel the sclp ET7 request, the code
|
|
* needs to wait unconditionally until the transfer is complete.
|
|
*/
|
|
wait_for_completion(&sclp_ftp_rx_complete);
|
|
|
|
#ifdef DEBUG
|
|
pr_debug("completed SCLP (ET7) request after %lu ms (all)\n",
|
|
(jiffies - start_jiffies) * 1000 / HZ);
|
|
pr_debug("return code of SCLP (ET7) FTP Service is 0x%02x, with %lld/%lld bytes\n",
|
|
sclp_ftp_ldflg, sclp_ftp_length, sclp_ftp_fsize);
|
|
#endif
|
|
|
|
switch (sclp_ftp_ldflg) {
|
|
case SCLP_DIAG_FTP_OK:
|
|
len = sclp_ftp_length;
|
|
if (fsize)
|
|
*fsize = sclp_ftp_fsize;
|
|
break;
|
|
case SCLP_DIAG_FTP_LDNPERM:
|
|
len = -EPERM;
|
|
break;
|
|
case SCLP_DIAG_FTP_LDRUNS:
|
|
len = -EBUSY;
|
|
break;
|
|
case SCLP_DIAG_FTP_LDFAIL:
|
|
len = -ENOENT;
|
|
break;
|
|
default:
|
|
len = -EIO;
|
|
break;
|
|
}
|
|
|
|
out_unlock:
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* ET7 event listener
|
|
*/
|
|
static struct sclp_register sclp_ftp_event = {
|
|
.send_mask = EVTYP_DIAG_TEST_MASK, /* want tx events */
|
|
.receive_mask = EVTYP_DIAG_TEST_MASK, /* want rx events */
|
|
.receiver_fn = sclp_ftp_rxcb, /* async callback (rx) */
|
|
.state_change_fn = NULL,
|
|
};
|
|
|
|
/**
|
|
* sclp_ftp_startup() - startup of FTP services, when running on LPAR
|
|
*/
|
|
int sclp_ftp_startup(void)
|
|
{
|
|
#ifdef DEBUG
|
|
unsigned long info;
|
|
#endif
|
|
int rc;
|
|
|
|
rc = sclp_register(&sclp_ftp_event);
|
|
if (rc)
|
|
return rc;
|
|
|
|
#ifdef DEBUG
|
|
info = get_zeroed_page(GFP_KERNEL);
|
|
|
|
if (info != 0) {
|
|
struct sysinfo_2_2_2 *info222 = (struct sysinfo_2_2_2 *)info;
|
|
|
|
if (!stsi(info222, 2, 2, 2)) { /* get SYSIB 2.2.2 */
|
|
info222->name[sizeof(info222->name) - 1] = '\0';
|
|
EBCASC_500(info222->name, sizeof(info222->name) - 1);
|
|
pr_debug("SCLP (ET7) FTP Service working on LPAR %u (%s)\n",
|
|
info222->lpar_number, info222->name);
|
|
}
|
|
|
|
free_page(info);
|
|
}
|
|
#endif /* DEBUG */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* sclp_ftp_shutdown() - shutdown of FTP services, when running on LPAR
|
|
*/
|
|
void sclp_ftp_shutdown(void)
|
|
{
|
|
sclp_unregister(&sclp_ftp_event);
|
|
}
|