summaryrefslogtreecommitdiff
path: root/drivers/s390/char
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-10-14 03:47:00 +0200
committerLinus Torvalds <torvalds@linux-foundation.org>2014-10-14 03:47:00 +0200
commit1ee07ef6b5db7235b133ee257a3adf507697e6b3 (patch)
tree9c7a00cf98462c2a70610da9d09770c835ef8fcd /drivers/s390/char
parent77654908ff1a58cee4886298968b5262884aff0b (diff)
parent0cccdda8d1512af4d3f6913044e8c8e58e15ef37 (diff)
downloadlinux-1ee07ef6b5db7235b133ee257a3adf507697e6b3.tar.gz
linux-1ee07ef6b5db7235b133ee257a3adf507697e6b3.tar.bz2
linux-1ee07ef6b5db7235b133ee257a3adf507697e6b3.zip
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
Pull s390 updates from Martin Schwidefsky: "This patch set contains the main portion of the changes for 3.18 in regard to the s390 architecture. It is a bit bigger than usual, mainly because of a new driver and the vector extension patches. The interesting bits are: - Quite a bit of work on the tracing front. Uprobes is enabled and the ftrace code is reworked to get some of the lost performance back if CONFIG_FTRACE is enabled. - To improve boot time with CONFIG_DEBIG_PAGEALLOC, support for the IPTE range facility is added. - The rwlock code is re-factored to improve writer fairness and to be able to use the interlocked-access instructions. - The kernel part for the support of the vector extension is added. - The device driver to access the CD/DVD on the HMC is added, this will hopefully come in handy to improve the installation process. - Add support for control-unit initiated reconfiguration. - The crypto device driver is enhanced to enable the additional AP domains and to allow the new crypto hardware to be used. - Bug fixes" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux: (39 commits) s390/ftrace: simplify enabling/disabling of ftrace_graph_caller s390/ftrace: remove 31 bit ftrace support s390/kdump: add support for vector extension s390/disassembler: add vector instructions s390: add support for vector extension s390/zcrypt: Toleration of new crypto hardware s390/idle: consolidate idle functions and definitions s390/nohz: use a per-cpu flag for arch_needs_cpu s390/vtime: do not reset idle data on CPU hotplug s390/dasd: add support for control unit initiated reconfiguration s390/dasd: fix infinite loop during format s390/mm: make use of ipte range facility s390/setup: correct 4-level kernel page table detection s390/topology: call set_sched_topology early s390/uprobes: architecture backend for uprobes s390/uprobes: common library for kprobes and uprobes s390/rwlock: use the interlocked-access facility 1 instructions s390/rwlock: improve writer fairness s390/rwlock: remove interrupt-enabling rwlock variant. s390/mm: remove change bit override support ...
Diffstat (limited to 'drivers/s390/char')
-rw-r--r--drivers/s390/char/Kconfig13
-rw-r--r--drivers/s390/char/Makefile3
-rw-r--r--drivers/s390/char/diag_ftp.c237
-rw-r--r--drivers/s390/char/diag_ftp.h21
-rw-r--r--drivers/s390/char/hmcdrv_cache.c252
-rw-r--r--drivers/s390/char/hmcdrv_cache.h24
-rw-r--r--drivers/s390/char/hmcdrv_dev.c370
-rw-r--r--drivers/s390/char/hmcdrv_dev.h14
-rw-r--r--drivers/s390/char/hmcdrv_ftp.c343
-rw-r--r--drivers/s390/char/hmcdrv_ftp.h63
-rw-r--r--drivers/s390/char/hmcdrv_mod.c64
-rw-r--r--drivers/s390/char/sclp.h2
-rw-r--r--drivers/s390/char/sclp_diag.h89
-rw-r--r--drivers/s390/char/sclp_early.c2
-rw-r--r--drivers/s390/char/sclp_ftp.c275
-rw-r--r--drivers/s390/char/sclp_ftp.h21
-rw-r--r--drivers/s390/char/sclp_rw.c13
-rw-r--r--drivers/s390/char/sclp_vt220.c4
-rw-r--r--drivers/s390/char/tape_char.c4
-rw-r--r--drivers/s390/char/zcore.c18
20 files changed, 1810 insertions, 22 deletions
diff --git a/drivers/s390/char/Kconfig b/drivers/s390/char/Kconfig
index 71bf959732fe..dc24ecfac2d1 100644
--- a/drivers/s390/char/Kconfig
+++ b/drivers/s390/char/Kconfig
@@ -102,6 +102,19 @@ config SCLP_ASYNC
want for inform other people about your kernel panics,
need this feature and intend to run your kernel in LPAR.
+config HMC_DRV
+ def_tristate m
+ prompt "Support for file transfers from HMC drive CD/DVD-ROM"
+ depends on 64BIT
+ select CRC16
+ help
+ This option enables support for file transfers from a Hardware
+ Management Console (HMC) drive CD/DVD-ROM. It is available as a
+ module, called 'hmcdrv', and also as kernel built-in. There is one
+ optional parameter for this module: cachesize=N, which modifies the
+ transfer cache size from it's default value 0.5MB to N bytes. If N
+ is zero, then no caching is performed.
+
config S390_TAPE
def_tristate m
prompt "S/390 tape device support"
diff --git a/drivers/s390/char/Makefile b/drivers/s390/char/Makefile
index 78b6ace7edcb..6fa9364d1c07 100644
--- a/drivers/s390/char/Makefile
+++ b/drivers/s390/char/Makefile
@@ -33,3 +33,6 @@ obj-$(CONFIG_S390_VMUR) += vmur.o
zcore_mod-objs := sclp_sdias.o zcore.o
obj-$(CONFIG_CRASH_DUMP) += zcore_mod.o
+
+hmcdrv-objs := hmcdrv_mod.o hmcdrv_dev.o hmcdrv_ftp.o hmcdrv_cache.o diag_ftp.o sclp_ftp.o
+obj-$(CONFIG_HMC_DRV) += hmcdrv.o
diff --git a/drivers/s390/char/diag_ftp.c b/drivers/s390/char/diag_ftp.c
new file mode 100644
index 000000000000..93889632fdf9
--- /dev/null
+++ b/drivers/s390/char/diag_ftp.c
@@ -0,0 +1,237 @@
+/*
+ * 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;
+
+ ctl_set_bit(0, 63 - 22);
+ return 0;
+}
+
+/**
+ * diag_ftp_shutdown() - shutdown of FTP services, when running on z/VM
+ */
+void diag_ftp_shutdown(void)
+{
+ ctl_clear_bit(0, 63 - 22);
+ unregister_external_irq(EXT_IRQ_CP_SERVICE, diag_ftp_handler);
+}
diff --git a/drivers/s390/char/diag_ftp.h b/drivers/s390/char/diag_ftp.h
new file mode 100644
index 000000000000..3abd2614053a
--- /dev/null
+++ b/drivers/s390/char/diag_ftp.h
@@ -0,0 +1,21 @@
+/*
+ * DIAGNOSE X'2C4' instruction based SE/HMC FTP Services, useable on z/VM
+ *
+ * Notice that all functions exported here are not reentrant.
+ * So usage should be exclusive, ensured by the caller (e.g. using a
+ * mutex).
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef __DIAG_FTP_H__
+#define __DIAG_FTP_H__
+
+#include "hmcdrv_ftp.h"
+
+int diag_ftp_startup(void);
+void diag_ftp_shutdown(void);
+ssize_t diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize);
+
+#endif /* __DIAG_FTP_H__ */
diff --git a/drivers/s390/char/hmcdrv_cache.c b/drivers/s390/char/hmcdrv_cache.c
new file mode 100644
index 000000000000..4cda5ada143a
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_cache.c
@@ -0,0 +1,252 @@
+/*
+ * SE/HMC Drive (Read) Cache Functions
+ *
+ * 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/jiffies.h>
+
+#include "hmcdrv_ftp.h"
+#include "hmcdrv_cache.h"
+
+#define HMCDRV_CACHE_TIMEOUT 30 /* aging timeout in seconds */
+
+/**
+ * struct hmcdrv_cache_entry - file cache (only used on read/dir)
+ * @id: FTP command ID
+ * @content: kernel-space buffer, 4k aligned
+ * @len: size of @content cache (0 if caching disabled)
+ * @ofs: start of content within file (-1 if no cached content)
+ * @fname: file name
+ * @fsize: file size
+ * @timeout: cache timeout in jiffies
+ *
+ * Notice that the first three members (id, fname, fsize) are cached on all
+ * read/dir requests. But content is cached only under some preconditions.
+ * Uncached content is signalled by a negative value of @ofs.
+ */
+struct hmcdrv_cache_entry {
+ enum hmcdrv_ftp_cmdid id;
+ char fname[HMCDRV_FTP_FIDENT_MAX];
+ size_t fsize;
+ loff_t ofs;
+ unsigned long timeout;
+ void *content;
+ size_t len;
+};
+
+static int hmcdrv_cache_order; /* cache allocated page order */
+
+static struct hmcdrv_cache_entry hmcdrv_cache_file = {
+ .fsize = SIZE_MAX,
+ .ofs = -1,
+ .len = 0,
+ .fname = {'\0'}
+};
+
+/**
+ * hmcdrv_cache_get() - looks for file data/content in read cache
+ * @ftp: pointer to FTP command specification
+ *
+ * Return: number of bytes read from cache or a negative number if nothing
+ * in content cache (for the file/cmd specified in @ftp)
+ */
+static ssize_t hmcdrv_cache_get(const struct hmcdrv_ftp_cmdspec *ftp)
+{
+ loff_t pos; /* position in cache (signed) */
+ ssize_t len;
+
+ if ((ftp->id != hmcdrv_cache_file.id) ||
+ strcmp(hmcdrv_cache_file.fname, ftp->fname))
+ return -1;
+
+ if (ftp->ofs >= hmcdrv_cache_file.fsize) /* EOF ? */
+ return 0;
+
+ if ((hmcdrv_cache_file.ofs < 0) || /* has content? */
+ time_after(jiffies, hmcdrv_cache_file.timeout))
+ return -1;
+
+ /* there seems to be cached content - calculate the maximum number
+ * of bytes that can be returned (regarding file size and offset)
+ */
+ len = hmcdrv_cache_file.fsize - ftp->ofs;
+
+ if (len > ftp->len)
+ len = ftp->len;
+
+ /* check if the requested chunk falls into our cache (which starts
+ * at offset 'hmcdrv_cache_file.ofs' in the file of interest)
+ */
+ pos = ftp->ofs - hmcdrv_cache_file.ofs;
+
+ if ((pos >= 0) &&
+ ((pos + len) <= hmcdrv_cache_file.len)) {
+
+ memcpy(ftp->buf,
+ hmcdrv_cache_file.content + pos,
+ len);
+ pr_debug("using cached content of '%s', returning %zd/%zd bytes\n",
+ hmcdrv_cache_file.fname, len,
+ hmcdrv_cache_file.fsize);
+
+ return len;
+ }
+
+ return -1;
+}
+
+/**
+ * hmcdrv_cache_do() - do a HMC drive CD/DVD transfer with cache update
+ * @ftp: pointer to FTP command specification
+ * @func: FTP transfer function to be used
+ *
+ * Return: number of bytes read/written or a (negative) error code
+ */
+static ssize_t hmcdrv_cache_do(const struct hmcdrv_ftp_cmdspec *ftp,
+ hmcdrv_cache_ftpfunc func)
+{
+ ssize_t len;
+
+ /* only cache content if the read/dir cache really exists
+ * (hmcdrv_cache_file.len > 0), is large enough to handle the
+ * request (hmcdrv_cache_file.len >= ftp->len) and there is a need
+ * to do so (ftp->len > 0)
+ */
+ if ((ftp->len > 0) && (hmcdrv_cache_file.len >= ftp->len)) {
+
+ /* because the cache is not located at ftp->buf, we have to
+ * assemble a new HMC drive FTP cmd specification (pointing
+ * to our cache, and using the increased size)
+ */
+ struct hmcdrv_ftp_cmdspec cftp = *ftp; /* make a copy */
+ cftp.buf = hmcdrv_cache_file.content; /* and update */
+ cftp.len = hmcdrv_cache_file.len; /* buffer data */
+
+ len = func(&cftp, &hmcdrv_cache_file.fsize); /* now do */
+
+ if (len > 0) {
+ pr_debug("caching %zd bytes content for '%s'\n",
+ len, ftp->fname);
+
+ if (len > ftp->len)
+ len = ftp->len;
+
+ hmcdrv_cache_file.ofs = ftp->ofs;
+ hmcdrv_cache_file.timeout = jiffies +
+ HMCDRV_CACHE_TIMEOUT * HZ;
+ memcpy(ftp->buf, hmcdrv_cache_file.content, len);
+ }
+ } else {
+ len = func(ftp, &hmcdrv_cache_file.fsize);
+ hmcdrv_cache_file.ofs = -1; /* invalidate content */
+ }
+
+ if (len > 0) {
+ /* cache some file info (FTP command, file name and file
+ * size) unconditionally
+ */
+ strlcpy(hmcdrv_cache_file.fname, ftp->fname,
+ HMCDRV_FTP_FIDENT_MAX);
+ hmcdrv_cache_file.id = ftp->id;
+ pr_debug("caching cmd %d, file size %zu for '%s'\n",
+ ftp->id, hmcdrv_cache_file.fsize, ftp->fname);
+ }
+
+ return len;
+}
+
+/**
+ * hmcdrv_cache_cmd() - perform a cached HMC drive CD/DVD transfer
+ * @ftp: pointer to FTP command specification
+ * @func: FTP transfer function to be used
+ *
+ * Attention: Notice that this function is not reentrant - so the caller
+ * must ensure exclusive execution.
+ *
+ * Return: number of bytes read/written or a (negative) error code
+ */
+ssize_t hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec *ftp,
+ hmcdrv_cache_ftpfunc func)
+{
+ ssize_t len;
+
+ if ((ftp->id == HMCDRV_FTP_DIR) || /* read cache */
+ (ftp->id == HMCDRV_FTP_NLIST) ||
+ (ftp->id == HMCDRV_FTP_GET)) {
+
+ len = hmcdrv_cache_get(ftp);
+
+ if (len >= 0) /* got it from cache ? */
+ return len; /* yes */
+
+ len = hmcdrv_cache_do(ftp, func);
+
+ if (len >= 0)
+ return len;
+
+ } else {
+ len = func(ftp, NULL); /* simply do original command */
+ }
+
+ /* invalidate the (read) cache in case there was a write operation
+ * or an error on read/dir
+ */
+ hmcdrv_cache_file.id = HMCDRV_FTP_NOOP;
+ hmcdrv_cache_file.fsize = LLONG_MAX;
+ hmcdrv_cache_file.ofs = -1;
+
+ return len;
+}
+
+/**
+ * hmcdrv_cache_startup() - startup of HMC drive cache
+ * @cachesize: cache size
+ *
+ * Return: 0 on success, else a (negative) error code
+ */
+int hmcdrv_cache_startup(size_t cachesize)
+{
+ if (cachesize > 0) { /* perform caching ? */
+ hmcdrv_cache_order = get_order(cachesize);
+ hmcdrv_cache_file.content =
+ (void *) __get_free_pages(GFP_KERNEL | GFP_DMA,
+ hmcdrv_cache_order);
+
+ if (!hmcdrv_cache_file.content) {
+ pr_err("Allocating the requested cache size of %zu bytes failed\n",
+ cachesize);
+ return -ENOMEM;
+ }
+
+ pr_debug("content cache enabled, size is %zu bytes\n",
+ cachesize);
+ }
+
+ hmcdrv_cache_file.len = cachesize;
+ return 0;
+}
+
+/**
+ * hmcdrv_cache_shutdown() - shutdown of HMC drive cache
+ */
+void hmcdrv_cache_shutdown(void)
+{
+ if (hmcdrv_cache_file.content) {
+ free_pages((unsigned long) hmcdrv_cache_file.content,
+ hmcdrv_cache_order);
+ hmcdrv_cache_file.content = NULL;
+ }
+
+ hmcdrv_cache_file.id = HMCDRV_FTP_NOOP;
+ hmcdrv_cache_file.fsize = LLONG_MAX;
+ hmcdrv_cache_file.ofs = -1;
+ hmcdrv_cache_file.len = 0; /* no cache */
+}
diff --git a/drivers/s390/char/hmcdrv_cache.h b/drivers/s390/char/hmcdrv_cache.h
new file mode 100644
index 000000000000..a14b57526781
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_cache.h
@@ -0,0 +1,24 @@
+/*
+ * SE/HMC Drive (Read) Cache Functions
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ */
+
+#ifndef __HMCDRV_CACHE_H__
+#define __HMCDRV_CACHE_H__
+
+#include <linux/mmzone.h>
+#include "hmcdrv_ftp.h"
+
+#define HMCDRV_CACHE_SIZE_DFLT (MAX_ORDER_NR_PAGES * PAGE_SIZE / 2UL)
+
+typedef ssize_t (*hmcdrv_cache_ftpfunc)(const struct hmcdrv_ftp_cmdspec *ftp,
+ size_t *fsize);
+
+ssize_t hmcdrv_cache_cmd(const struct hmcdrv_ftp_cmdspec *ftp,
+ hmcdrv_cache_ftpfunc func);
+int hmcdrv_cache_startup(size_t cachesize);
+void hmcdrv_cache_shutdown(void);
+
+#endif /* __HMCDRV_CACHE_H__ */
diff --git a/drivers/s390/char/hmcdrv_dev.c b/drivers/s390/char/hmcdrv_dev.c
new file mode 100644
index 000000000000..0c5176179c17
--- /dev/null
+++ b/drivers/s390/char/hmcdrv_dev.c
@@ -0,0 +1,370 @@
+/*
+ * HMC Drive CD/DVD Device
+ *
+ * Copyright IBM Corp. 2013
+ * Author(s): Ralf Hoppe (rhoppe@de.ibm.com)
+ *
+ * This file provides a Linux "misc" character device for access to an
+ * assigned HMC drive CD/DVD-ROM. It works as follows: First create the
+ * device by calling hmcdrv_dev_init(). After open() a lseek(fd, 0,
+ * SEEK_END) indicates that a new FTP command follows (not needed on the
+ * first command after open). Then write() the FTP command ASCII string
+ * to it, e.g. "dir /" or "nls <directory>" or "get <filename>". At the
+ * end read() the response.
+ */
+
+#define KMSG_COMPONENT "hmcdrv"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/miscdevice.h>
+#include <linux/device.h>
+#include <linux/capability.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+
+#include "hmcdrv_dev.h"
+#include "hmcdrv_ftp.h"
+
+/* If the following macro is defined, then the HMC device creates it's own
+ * separated device class (and dynamically assigns a major number). If not
+ * defined then the HMC device is assigned to the "misc" class devices.
+ *
+#define HMCDRV_DEV_CLASS "hmcftp"
+ */
+
+#define HMCDRV_DEV_NAME "hmcdrv"
+#define HMCDRV_DEV_BUSY_DELAY 500 /* delay between -EBUSY trials in ms */
+#define HMCDRV_DEV_BUSY_RETRIES 3 /* number of retries on -EBUSY */
+
+struct hmcdrv_dev_node {
+
+#ifdef HMCDRV_DEV_CLASS
+ struct cdev dev; /* character device structure */
+ umode_t mode; /* mode of device node (unused, zero) */
+#else
+ struct miscdevice dev; /* "misc" device structure */
+#endif
+
+};
+
+static int hmcdrv_dev_open(struct inode *inode, struct file *fp);
+static int hmcdrv_dev_release(struct inode *inode, struct file *fp);
+static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence);
+static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf,
+ size_t len, loff_t *pos);
+static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf,
+ size_t len, loff_t *pos);
+static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset,
+ char __user *buf, size_t len);
+
+/*
+ * device operations
+ */
+static const struct file_operations hmcdrv_dev_fops = {
+ .open = hmcdrv_dev_open,
+ .llseek = hmcdrv_dev_seek,
+ .release = hmcdrv_dev_release,
+ .read = hmcdrv_dev_read,
+ .write = hmcdrv_dev_write,
+};
+
+static struct hmcdrv_dev_node hmcdrv_dev; /* HMC device struct (static) */
+
+#ifdef HMCDRV_DEV_CLASS
+
+static struct class *hmcdrv_dev_class; /* device class pointer */
+static dev_t hmcdrv_dev_no; /* device number (major/minor) */
+
+/**
+ * hmcdrv_dev_name() - provides a naming hint for a device node in /dev
+ * @dev: device for which the naming/mode hint is
+ * @mode: file mode for device node created in /dev
+ *
+ * See: devtmpfs.c, function devtmpfs_create_node()
+ *
+ * Return: recommended device file name in /dev
+ */
+static char *hmcdrv_dev_name(struct device *dev, umode_t *mode)
+{
+ char *nodename = NULL;
+ const char *devname = dev_name(dev); /* kernel device name */
+
+ if (devname)
+ nodename = kasprintf(GFP_KERNEL, "%s", devname);
+
+ /* on device destroy (rmmod) the mode pointer may be NULL
+ */
+ if (mode)
+ *mode = hmcdrv_dev.mode;
+
+ return nodename;
+}
+
+#endif /* HMCDRV_DEV_CLASS */
+
+/*
+ * open()
+ */
+static int hmcdrv_dev_open(struct inode *inode, struct file *fp)
+{
+ int rc;
+
+ /* check for non-blocking access, which is really unsupported
+ */
+ if (fp->f_flags & O_NONBLOCK)
+ return -EINVAL;
+
+ /* Because it makes no sense to open this device read-only (then a
+ * FTP command cannot be emitted), we respond with an error.
+ */
+ if ((fp->f_flags & O_ACCMODE) == O_RDONLY)
+ return -EINVAL;
+
+ /* prevent unloading this module as long as anyone holds the
+ * device file open - so increment the reference count here
+ */
+ if (!try_module_get(THIS_MODULE))
+ return -ENODEV;
+
+ fp->private_data = NULL; /* no command yet */
+ rc = hmcdrv_ftp_startup();
+ if (rc)
+ module_put(THIS_MODULE);
+
+ pr_debug("open file '/dev/%s' with return code %d\n",
+ fp->f_dentry->d_name.name, rc);
+ return rc;
+}
+
+/*
+ * release()
+ */
+static int hmcdrv_dev_release(struct inode *inode, struct file *fp)
+{
+ pr_debug("closing file '/dev/%s'\n", fp->f_dentry->d_name.name);
+ kfree(fp->private_data);
+ fp->private_data = NULL;
+ hmcdrv_ftp_shutdown();
+ module_put(THIS_MODULE);
+ return 0;
+}
+
+/*
+ * lseek()
+ */
+static loff_t hmcdrv_dev_seek(struct file *fp, loff_t pos, int whence)
+{
+ switch (whence) {
+ case SEEK_CUR: /* relative to current file position */
+ pos += fp->f_pos; /* new position stored in 'pos' */
+ break;
+
+ case SEEK_SET: /* absolute (relative to beginning of file) */
+ break; /* SEEK_SET */
+
+ /* We use SEEK_END as a special indicator for a SEEK_SET
+ * (set absolute position), combined with a FTP command
+ * clear.
+ */
+ case SEEK_END:
+ if (fp->private_data) {
+ kfree(fp->private_data);
+ fp->private_data = NULL;
+ }
+
+ break; /* SEEK_END */
+
+ default: /* SEEK_DATA, SEEK_HOLE: unsupported */
+ return -EINVAL;
+ }
+
+ if (pos < 0)
+ return -EINVAL;
+
+ if (fp->f_pos != pos)
+ ++fp->f_version;
+
+ fp->f_pos = pos;
+ return pos;
+}
+
+/*
+ * transfer (helper function)
+ */
+static ssize_t hmcdrv_dev_transfer(char __kernel *cmd, loff_t offset,
+ char __user *buf, size_t len)
+{
+ ssize_t retlen;
+ unsigned trials = HMCDRV_DEV_BUSY_RETRIES;
+
+ do {
+ retlen = hmcdrv_ftp_cmd(cmd, offset, buf, len);
+
+ if (retlen != -EBUSY)
+ break;
+
+ msleep(HMCDRV_DEV_BUSY_DELAY);
+
+ } while (--trials > 0);
+
+ return retlen;
+}
+
+/*
+ * read()
+ */
+static ssize_t hmcdrv_dev_read(struct file *fp, char __user *ubuf,
+ size_t len, loff_t *pos)
+{
+ ssize_t retlen;
+
+ if (((fp->f_flags & O_ACCMODE) == O_WRONLY) ||
+ (fp->private_data == NULL)) { /* no FTP cmd defined ? */
+ return -EBADF;
+ }
+
+ retlen = hmcdrv_dev_transfer((char *) fp->private_data,
+ *pos, ubuf, len);
+
+ pr_debug("read from file '/dev/%s' at %lld returns %zd/%zu\n",
+ fp->f_dentry->d_name.name, (long long) *pos, retlen, len);
+
+ if (retlen > 0)
+ *pos += retlen;
+
+ return retlen;
+}
+
+/*
+ * write()
+ */
+static ssize_t hmcdrv_dev_write(struct file *fp, const char __user *ubuf,
+ size_t len, loff_t *pos)
+{
+ ssize_t retlen;
+
+ pr_debug("writing file '/dev/%s' at pos. %lld with length %zd\n",
+ fp->f_dentry->d_name.name, (long long) *pos, len);
+
+ if (!fp->private_data) { /* first expect a cmd write */
+ fp->private_data = kmalloc(len + 1, GFP_KERNEL);
+
+ if (!fp->private_data)
+ return -ENOMEM;
+
+ if (!copy_from_user(fp->private_data, ubuf, len)) {
+ ((char *)fp->private_data)[len] = '\0';
+ return len;
+ }
+
+ kfree(fp->private_data);
+ fp->private_data = NULL;
+ return -EFAULT;
+ }
+
+ retlen = hmcdrv_dev_transfer((char *) fp->private_data,
+ *pos, (char __user *) ubuf, len);
+ if (retlen > 0)
+ *pos += retlen;
+
+ pr_debug("write to file '/dev/%s' returned %zd\n",
+ fp->f_dentry->d_name.name, retlen);
+
+ return retlen;
+}
+
+/**
+ * hmcdrv_dev_init() - creates a HMC drive CD/DVD device
+ *
+ * This function creates a HMC drive CD/DVD kernel device and an associated
+ * device under /dev, using a dynamically allocated major number.
+ *
+ * Return: 0 on success, else an error code.
+ */
+int hmcdrv_dev_init(void)
+{
+ int rc;
+
+#ifdef HMCDRV_DEV_CLASS
+ struct device *dev;
+
+ rc = alloc_chrdev_region(&hmcdrv_dev_no, 0, 1, HMCDRV_DEV_NAME);
+
+ if (rc)
+ goto out_err;
+
+ cdev_init(&hmcdrv_dev.dev, &hmcdrv_dev_fops);
+ hmcdrv_dev.dev.owner = THIS_MODULE;
+ rc = cdev_add(&hmcdrv_dev.dev, hmcdrv_dev_no, 1);
+
+ if (rc)
+ goto out_unreg;
+
+ /* At this point the character device exists in the kernel (see
+ * /proc/devices), but not under /dev nor /sys/devices/virtual. So
+ * we have to create an associated