18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *    DIAGNOSE X'2C4' instruction based HMC FTP services, useable on z/VM
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *    Copyright IBM Corp. 2013
68c2ecf20Sopenharmony_ci *    Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#define KMSG_COMPONENT "hmcdrv"
118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/kernel.h>
148c2ecf20Sopenharmony_ci#include <linux/mm.h>
158c2ecf20Sopenharmony_ci#include <linux/irq.h>
168c2ecf20Sopenharmony_ci#include <linux/wait.h>
178c2ecf20Sopenharmony_ci#include <linux/string.h>
188c2ecf20Sopenharmony_ci#include <asm/ctl_reg.h>
198c2ecf20Sopenharmony_ci#include <asm/diag.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include "hmcdrv_ftp.h"
228c2ecf20Sopenharmony_ci#include "diag_ftp.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/* DIAGNOSE X'2C4' return codes in Ry */
258c2ecf20Sopenharmony_ci#define DIAG_FTP_RET_OK	0 /* HMC FTP started successfully */
268c2ecf20Sopenharmony_ci#define DIAG_FTP_RET_EBUSY	4 /* HMC FTP service currently busy */
278c2ecf20Sopenharmony_ci#define DIAG_FTP_RET_EIO	8 /* HMC FTP service I/O error */
288c2ecf20Sopenharmony_ci/* and an artificial extension */
298c2ecf20Sopenharmony_ci#define DIAG_FTP_RET_EPERM	2 /* HMC FTP service privilege error */
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci/* FTP service status codes (after INTR at guest real location 133) */
328c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_OK	0U /* request completed successfully */
338c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_PGCC	4U /* program check condition */
348c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_PGIOE	8U /* paging I/O error */
358c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_TIMEOUT	12U /* timeout */
368c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_EBASE	16U /* base of error codes from SCLP */
378c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_LDFAIL	(DIAG_FTP_STAT_EBASE + 1U) /* failed */
388c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_LDNPERM	(DIAG_FTP_STAT_EBASE + 2U) /* not allowed */
398c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_LDRUNS	(DIAG_FTP_STAT_EBASE + 3U) /* runs */
408c2ecf20Sopenharmony_ci#define DIAG_FTP_STAT_LDNRUNS	(DIAG_FTP_STAT_EBASE + 4U) /* not runs */
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/**
438c2ecf20Sopenharmony_ci * struct diag_ftp_ldfpl - load file FTP parameter list (LDFPL)
448c2ecf20Sopenharmony_ci * @bufaddr: real buffer address (at 4k boundary)
458c2ecf20Sopenharmony_ci * @buflen: length of buffer
468c2ecf20Sopenharmony_ci * @offset: dir/file offset
478c2ecf20Sopenharmony_ci * @intparm: interruption parameter (unused)
488c2ecf20Sopenharmony_ci * @transferred: bytes transferred
498c2ecf20Sopenharmony_ci * @fsize: file size, filled on GET
508c2ecf20Sopenharmony_ci * @failaddr: failing address
518c2ecf20Sopenharmony_ci * @spare: padding
528c2ecf20Sopenharmony_ci * @fident: file name - ASCII
538c2ecf20Sopenharmony_ci */
548c2ecf20Sopenharmony_cistruct diag_ftp_ldfpl {
558c2ecf20Sopenharmony_ci	u64 bufaddr;
568c2ecf20Sopenharmony_ci	u64 buflen;
578c2ecf20Sopenharmony_ci	u64 offset;
588c2ecf20Sopenharmony_ci	u64 intparm;
598c2ecf20Sopenharmony_ci	u64 transferred;
608c2ecf20Sopenharmony_ci	u64 fsize;
618c2ecf20Sopenharmony_ci	u64 failaddr;
628c2ecf20Sopenharmony_ci	u64 spare;
638c2ecf20Sopenharmony_ci	u8 fident[HMCDRV_FTP_FIDENT_MAX];
648c2ecf20Sopenharmony_ci} __packed;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic DECLARE_COMPLETION(diag_ftp_rx_complete);
678c2ecf20Sopenharmony_cistatic int diag_ftp_subcode;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci/**
708c2ecf20Sopenharmony_ci * diag_ftp_handler() - FTP services IRQ handler
718c2ecf20Sopenharmony_ci * @extirq: external interrupt (sub-) code
728c2ecf20Sopenharmony_ci * @param32: 32-bit interruption parameter from &struct diag_ftp_ldfpl
738c2ecf20Sopenharmony_ci * @param64: unused (for 64-bit interrupt parameters)
748c2ecf20Sopenharmony_ci */
758c2ecf20Sopenharmony_cistatic void diag_ftp_handler(struct ext_code extirq,
768c2ecf20Sopenharmony_ci			     unsigned int param32,
778c2ecf20Sopenharmony_ci			     unsigned long param64)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	if ((extirq.subcode >> 8) != 8)
808c2ecf20Sopenharmony_ci		return; /* not a FTP services sub-code */
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	inc_irq_stat(IRQEXT_FTP);
838c2ecf20Sopenharmony_ci	diag_ftp_subcode = extirq.subcode & 0xffU;
848c2ecf20Sopenharmony_ci	complete(&diag_ftp_rx_complete);
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci/**
888c2ecf20Sopenharmony_ci * diag_ftp_2c4() - DIAGNOSE X'2C4' service call
898c2ecf20Sopenharmony_ci * @fpl: pointer to prepared LDFPL
908c2ecf20Sopenharmony_ci * @cmd: FTP command to be executed
918c2ecf20Sopenharmony_ci *
928c2ecf20Sopenharmony_ci * Performs a DIAGNOSE X'2C4' call with (input/output) FTP parameter list
938c2ecf20Sopenharmony_ci * @fpl and FTP function code @cmd. In case of an error the function does
948c2ecf20Sopenharmony_ci * nothing and returns an (negative) error code.
958c2ecf20Sopenharmony_ci *
968c2ecf20Sopenharmony_ci * Notes:
978c2ecf20Sopenharmony_ci * 1. This function only initiates a transfer, so the caller must wait
988c2ecf20Sopenharmony_ci *    for completion (asynchronous execution).
998c2ecf20Sopenharmony_ci * 2. The FTP parameter list @fpl must be aligned to a double-word boundary.
1008c2ecf20Sopenharmony_ci * 3. fpl->bufaddr must be a real address, 4k aligned
1018c2ecf20Sopenharmony_ci */
1028c2ecf20Sopenharmony_cistatic int diag_ftp_2c4(struct diag_ftp_ldfpl *fpl,
1038c2ecf20Sopenharmony_ci			enum hmcdrv_ftp_cmdid cmd)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	int rc;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	diag_stat_inc(DIAG_STAT_X2C4);
1088c2ecf20Sopenharmony_ci	asm volatile(
1098c2ecf20Sopenharmony_ci		"	diag	%[addr],%[cmd],0x2c4\n"
1108c2ecf20Sopenharmony_ci		"0:	j	2f\n"
1118c2ecf20Sopenharmony_ci		"1:	la	%[rc],%[err]\n"
1128c2ecf20Sopenharmony_ci		"2:\n"
1138c2ecf20Sopenharmony_ci		EX_TABLE(0b, 1b)
1148c2ecf20Sopenharmony_ci		: [rc] "=d" (rc), "+m" (*fpl)
1158c2ecf20Sopenharmony_ci		: [cmd] "0" (cmd), [addr] "d" (virt_to_phys(fpl)),
1168c2ecf20Sopenharmony_ci		  [err] "i" (DIAG_FTP_RET_EPERM)
1178c2ecf20Sopenharmony_ci		: "cc");
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	switch (rc) {
1208c2ecf20Sopenharmony_ci	case DIAG_FTP_RET_OK:
1218c2ecf20Sopenharmony_ci		return 0;
1228c2ecf20Sopenharmony_ci	case DIAG_FTP_RET_EBUSY:
1238c2ecf20Sopenharmony_ci		return -EBUSY;
1248c2ecf20Sopenharmony_ci	case DIAG_FTP_RET_EPERM:
1258c2ecf20Sopenharmony_ci		return -EPERM;
1268c2ecf20Sopenharmony_ci	case DIAG_FTP_RET_EIO:
1278c2ecf20Sopenharmony_ci	default:
1288c2ecf20Sopenharmony_ci		return -EIO;
1298c2ecf20Sopenharmony_ci	}
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci/**
1338c2ecf20Sopenharmony_ci * diag_ftp_cmd() - executes a DIAG X'2C4' FTP command, targeting a HMC
1348c2ecf20Sopenharmony_ci * @ftp: pointer to FTP command specification
1358c2ecf20Sopenharmony_ci * @fsize: return of file size (or NULL if undesirable)
1368c2ecf20Sopenharmony_ci *
1378c2ecf20Sopenharmony_ci * Attention: Notice that this function is not reentrant - so the caller
1388c2ecf20Sopenharmony_ci * must ensure locking.
1398c2ecf20Sopenharmony_ci *
1408c2ecf20Sopenharmony_ci * Return: number of bytes read/written or a (negative) error code
1418c2ecf20Sopenharmony_ci */
1428c2ecf20Sopenharmony_cissize_t diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	struct diag_ftp_ldfpl *ldfpl;
1458c2ecf20Sopenharmony_ci	ssize_t len;
1468c2ecf20Sopenharmony_ci#ifdef DEBUG
1478c2ecf20Sopenharmony_ci	unsigned long start_jiffies;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	pr_debug("starting DIAG X'2C4' on '%s', requesting %zd bytes\n",
1508c2ecf20Sopenharmony_ci		 ftp->fname, ftp->len);
1518c2ecf20Sopenharmony_ci	start_jiffies = jiffies;
1528c2ecf20Sopenharmony_ci#endif
1538c2ecf20Sopenharmony_ci	init_completion(&diag_ftp_rx_complete);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	ldfpl = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
1568c2ecf20Sopenharmony_ci	if (!ldfpl) {
1578c2ecf20Sopenharmony_ci		len = -ENOMEM;
1588c2ecf20Sopenharmony_ci		goto out;
1598c2ecf20Sopenharmony_ci	}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	len = strlcpy(ldfpl->fident, ftp->fname, sizeof(ldfpl->fident));
1628c2ecf20Sopenharmony_ci	if (len >= HMCDRV_FTP_FIDENT_MAX) {
1638c2ecf20Sopenharmony_ci		len = -EINVAL;
1648c2ecf20Sopenharmony_ci		goto out_free;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	ldfpl->transferred = 0;
1688c2ecf20Sopenharmony_ci	ldfpl->fsize = 0;
1698c2ecf20Sopenharmony_ci	ldfpl->offset = ftp->ofs;
1708c2ecf20Sopenharmony_ci	ldfpl->buflen = ftp->len;
1718c2ecf20Sopenharmony_ci	ldfpl->bufaddr = virt_to_phys(ftp->buf);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	len = diag_ftp_2c4(ldfpl, ftp->id);
1748c2ecf20Sopenharmony_ci	if (len)
1758c2ecf20Sopenharmony_ci		goto out_free;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	/*
1788c2ecf20Sopenharmony_ci	 * There is no way to cancel the running diag X'2C4', the code
1798c2ecf20Sopenharmony_ci	 * needs to wait unconditionally until the transfer is complete.
1808c2ecf20Sopenharmony_ci	 */
1818c2ecf20Sopenharmony_ci	wait_for_completion(&diag_ftp_rx_complete);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci#ifdef DEBUG
1848c2ecf20Sopenharmony_ci	pr_debug("completed DIAG X'2C4' after %lu ms\n",
1858c2ecf20Sopenharmony_ci		 (jiffies - start_jiffies) * 1000 / HZ);
1868c2ecf20Sopenharmony_ci	pr_debug("status of DIAG X'2C4' is %u, with %lld/%lld bytes\n",
1878c2ecf20Sopenharmony_ci		 diag_ftp_subcode, ldfpl->transferred, ldfpl->fsize);
1888c2ecf20Sopenharmony_ci#endif
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	switch (diag_ftp_subcode) {
1918c2ecf20Sopenharmony_ci	case DIAG_FTP_STAT_OK: /* success */
1928c2ecf20Sopenharmony_ci		len = ldfpl->transferred;
1938c2ecf20Sopenharmony_ci		if (fsize)
1948c2ecf20Sopenharmony_ci			*fsize = ldfpl->fsize;
1958c2ecf20Sopenharmony_ci		break;
1968c2ecf20Sopenharmony_ci	case DIAG_FTP_STAT_LDNPERM:
1978c2ecf20Sopenharmony_ci		len = -EPERM;
1988c2ecf20Sopenharmony_ci		break;
1998c2ecf20Sopenharmony_ci	case DIAG_FTP_STAT_LDRUNS:
2008c2ecf20Sopenharmony_ci		len = -EBUSY;
2018c2ecf20Sopenharmony_ci		break;
2028c2ecf20Sopenharmony_ci	case DIAG_FTP_STAT_LDFAIL:
2038c2ecf20Sopenharmony_ci		len = -ENOENT; /* no such file or media */
2048c2ecf20Sopenharmony_ci		break;
2058c2ecf20Sopenharmony_ci	default:
2068c2ecf20Sopenharmony_ci		len = -EIO;
2078c2ecf20Sopenharmony_ci		break;
2088c2ecf20Sopenharmony_ci	}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ciout_free:
2118c2ecf20Sopenharmony_ci	free_page((unsigned long) ldfpl);
2128c2ecf20Sopenharmony_ciout:
2138c2ecf20Sopenharmony_ci	return len;
2148c2ecf20Sopenharmony_ci}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci/**
2178c2ecf20Sopenharmony_ci * diag_ftp_startup() - startup of FTP services, when running on z/VM
2188c2ecf20Sopenharmony_ci *
2198c2ecf20Sopenharmony_ci * Return: 0 on success, else an (negative) error code
2208c2ecf20Sopenharmony_ci */
2218c2ecf20Sopenharmony_ciint diag_ftp_startup(void)
2228c2ecf20Sopenharmony_ci{
2238c2ecf20Sopenharmony_ci	int rc;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	rc = register_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
2268c2ecf20Sopenharmony_ci	if (rc)
2278c2ecf20Sopenharmony_ci		return rc;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	irq_subclass_register(IRQ_SUBCLASS_SERVICE_SIGNAL);
2308c2ecf20Sopenharmony_ci	return 0;
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci/**
2348c2ecf20Sopenharmony_ci * diag_ftp_shutdown() - shutdown of FTP services, when running on z/VM
2358c2ecf20Sopenharmony_ci */
2368c2ecf20Sopenharmony_civoid diag_ftp_shutdown(void)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	irq_subclass_unregister(IRQ_SUBCLASS_SERVICE_SIGNAL);
2398c2ecf20Sopenharmony_ci	unregister_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
2408c2ecf20Sopenharmony_ci}
241