summaryrefslogtreecommitdiffstats
path: root/drivers/s390/char
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-17 00:20:36 +0200
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-17 00:20:36 +0200
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/char
downloadlinux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz
linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/s390/char')
-rw-r--r--drivers/s390/char/Makefile28
-rw-r--r--drivers/s390/char/con3215.c1192
-rw-r--r--drivers/s390/char/con3270.c638
-rw-r--r--drivers/s390/char/ctrlchar.c75
-rw-r--r--drivers/s390/char/ctrlchar.h20
-rw-r--r--drivers/s390/char/defkeymap.c156
-rw-r--r--drivers/s390/char/defkeymap.map191
-rw-r--r--drivers/s390/char/fs3270.c373
-rw-r--r--drivers/s390/char/keyboard.c519
-rw-r--r--drivers/s390/char/keyboard.h57
-rw-r--r--drivers/s390/char/monreader.c662
-rw-r--r--drivers/s390/char/raw3270.c1335
-rw-r--r--drivers/s390/char/raw3270.h274
-rw-r--r--drivers/s390/char/sclp.c915
-rw-r--r--drivers/s390/char/sclp.h159
-rw-r--r--drivers/s390/char/sclp_con.c252
-rw-r--r--drivers/s390/char/sclp_cpi.c254
-rw-r--r--drivers/s390/char/sclp_quiesce.c99
-rw-r--r--drivers/s390/char/sclp_rw.c471
-rw-r--r--drivers/s390/char/sclp_rw.h96
-rw-r--r--drivers/s390/char/sclp_tty.c813
-rw-r--r--drivers/s390/char/sclp_tty.h71
-rw-r--r--drivers/s390/char/sclp_vt220.c785
-rw-r--r--drivers/s390/char/tape.h384
-rw-r--r--drivers/s390/char/tape_34xx.c1385
-rw-r--r--drivers/s390/char/tape_block.c492
-rw-r--r--drivers/s390/char/tape_char.c492
-rw-r--r--drivers/s390/char/tape_class.c126
-rw-r--r--drivers/s390/char/tape_class.h61
-rw-r--r--drivers/s390/char/tape_core.c1242
-rw-r--r--drivers/s390/char/tape_proc.c145
-rw-r--r--drivers/s390/char/tape_std.c765
-rw-r--r--drivers/s390/char/tape_std.h152
-rw-r--r--drivers/s390/char/tty3270.c1836
-rw-r--r--drivers/s390/char/vmlogrdr.c920
-rw-r--r--drivers/s390/char/vmwatchdog.c292
36 files changed, 17727 insertions, 0 deletions
diff --git a/drivers/s390/char/Makefile b/drivers/s390/char/Makefile
new file mode 100644
index 000000000000..14e8cce9f862
--- /dev/null
+++ b/drivers/s390/char/Makefile
@@ -0,0 +1,28 @@
+#
+# S/390 character devices
+#
+
+obj-y += ctrlchar.o keyboard.o defkeymap.o
+
+obj-$(CONFIG_TN3270) += raw3270.o
+obj-$(CONFIG_TN3270_CONSOLE) += con3270.o
+obj-$(CONFIG_TN3270_TTY) += tty3270.o
+obj-$(CONFIG_TN3270_FS) += fs3270.o
+
+obj-$(CONFIG_TN3215) += con3215.o
+
+obj-$(CONFIG_SCLP) += sclp.o sclp_rw.o sclp_quiesce.o
+obj-$(CONFIG_SCLP_TTY) += sclp_tty.o
+obj-$(CONFIG_SCLP_CONSOLE) += sclp_con.o
+obj-$(CONFIG_SCLP_VT220_TTY) += sclp_vt220.o
+obj-$(CONFIG_SCLP_CPI) += sclp_cpi.o
+
+obj-$(CONFIG_ZVM_WATCHDOG) += vmwatchdog.o
+obj-$(CONFIG_VMLOGRDR) += vmlogrdr.o
+
+tape-$(CONFIG_S390_TAPE_BLOCK) += tape_block.o
+tape-$(CONFIG_PROC_FS) += tape_proc.o
+tape-objs := tape_core.o tape_std.o tape_char.o $(tape-y)
+obj-$(CONFIG_S390_TAPE) += tape.o tape_class.o
+obj-$(CONFIG_S390_TAPE_34XX) += tape_34xx.o
+obj-$(CONFIG_MONREADER) += monreader.o
diff --git a/drivers/s390/char/con3215.c b/drivers/s390/char/con3215.c
new file mode 100644
index 000000000000..022f17bff731
--- /dev/null
+++ b/drivers/s390/char/con3215.c
@@ -0,0 +1,1192 @@
+/*
+ * drivers/s390/char/con3215.c
+ * 3215 line mode terminal driver.
+ *
+ * S390 version
+ * Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
+ *
+ * Updated:
+ * Aug-2000: Added tab support
+ * Dan Morrison, IBM Corporation (dmorriso@cse.buffalo.edu)
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kdev_t.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/interrupt.h>
+
+#include <linux/slab.h>
+#include <linux/bootmem.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+#include <asm/io.h>
+#include <asm/ebcdic.h>
+#include <asm/uaccess.h>
+#include <asm/delay.h>
+#include <asm/cpcmd.h>
+#include <asm/setup.h>
+
+#include "ctrlchar.h"
+
+#define NR_3215 1
+#define NR_3215_REQ (4*NR_3215)
+#define RAW3215_BUFFER_SIZE 65536 /* output buffer size */
+#define RAW3215_INBUF_SIZE 256 /* input buffer size */
+#define RAW3215_MIN_SPACE 128 /* minimum free space for wakeup */
+#define RAW3215_MIN_WRITE 1024 /* min. length for immediate output */
+#define RAW3215_MAX_BYTES 3968 /* max. bytes to write with one ssch */
+#define RAW3215_MAX_NEWLINE 50 /* max. lines to write with one ssch */
+#define RAW3215_NR_CCWS 3
+#define RAW3215_TIMEOUT HZ/10 /* time for delayed output */
+
+#define RAW3215_FIXED 1 /* 3215 console device is not be freed */
+#define RAW3215_ACTIVE 2 /* set if the device is in use */
+#define RAW3215_WORKING 4 /* set if a request is being worked on */
+#define RAW3215_THROTTLED 8 /* set if reading is disabled */
+#define RAW3215_STOPPED 16 /* set if writing is disabled */
+#define RAW3215_CLOSING 32 /* set while in close process */
+#define RAW3215_TIMER_RUNS 64 /* set if the output delay timer is on */
+#define RAW3215_FLUSHING 128 /* set to flush buffer (no delay) */
+
+#define TAB_STOP_SIZE 8 /* tab stop size */
+
+/*
+ * Request types for a 3215 device
+ */
+enum raw3215_type {
+ RAW3215_FREE, RAW3215_READ, RAW3215_WRITE
+};
+
+/*
+ * Request structure for a 3215 device
+ */
+struct raw3215_req {
+ enum raw3215_type type; /* type of the request */
+ int start, len; /* start index & len in output buffer */
+ int delayable; /* indication to wait for more data */
+ int residual; /* residual count for read request */
+ struct ccw1 ccws[RAW3215_NR_CCWS]; /* space for the channel program */
+ struct raw3215_info *info; /* pointer to main structure */
+ struct raw3215_req *next; /* pointer to next request */
+} __attribute__ ((aligned(8)));
+
+struct raw3215_info {
+ struct ccw_device *cdev; /* device for tty driver */
+ spinlock_t *lock; /* pointer to irq lock */
+ int flags; /* state flags */
+ char *buffer; /* pointer to output buffer */
+ char *inbuf; /* pointer to input buffer */
+ int head; /* first free byte in output buffer */
+ int count; /* number of bytes in output buffer */
+ int written; /* number of bytes in write requests */
+ struct tty_struct *tty; /* pointer to tty structure if present */
+ struct tasklet_struct tasklet;
+ struct raw3215_req *queued_read; /* pointer to queued read requests */
+ struct raw3215_req *queued_write;/* pointer to queued write requests */
+ wait_queue_head_t empty_wait; /* wait queue for flushing */
+ struct timer_list timer; /* timer for delayed output */
+ char *message; /* pending message from raw3215_irq */
+ int msg_dstat; /* dstat for pending message */
+ int msg_cstat; /* cstat for pending message */
+ int line_pos; /* position on the line (for tabs) */
+ char ubuffer[80]; /* copy_from_user buffer */
+};
+
+/* array of 3215 devices structures */
+static struct raw3215_info *raw3215[NR_3215];
+/* spinlock to protect the raw3215 array */
+static DEFINE_SPINLOCK(raw3215_device_lock);
+/* list of free request structures */
+static struct raw3215_req *raw3215_freelist;
+/* spinlock to protect free list */
+static spinlock_t raw3215_freelist_lock;
+
+static struct tty_driver *tty3215_driver;
+
+/*
+ * Get a request structure from the free list
+ */
+static inline struct raw3215_req *
+raw3215_alloc_req(void) {
+ struct raw3215_req *req;
+ unsigned long flags;
+
+ spin_lock_irqsave(&raw3215_freelist_lock, flags);
+ req = raw3215_freelist;
+ raw3215_freelist = req->next;
+ spin_unlock_irqrestore(&raw3215_freelist_lock, flags);
+ return req;
+}
+
+/*
+ * Put a request structure back to the free list
+ */
+static inline void
+raw3215_free_req(struct raw3215_req *req) {
+ unsigned long flags;
+
+ if (req->type == RAW3215_FREE)
+ return; /* don't free a free request */
+ req->type = RAW3215_FREE;
+ spin_lock_irqsave(&raw3215_freelist_lock, flags);
+ req->next = raw3215_freelist;
+ raw3215_freelist = req;
+ spin_unlock_irqrestore(&raw3215_freelist_lock, flags);
+}
+
+/*
+ * Set up a read request that reads up to 160 byte from the 3215 device.
+ * If there is a queued read request it is used, but that shouldn't happen
+ * because a 3215 terminal won't accept a new read before the old one is
+ * completed.
+ */
+static void
+raw3215_mk_read_req(struct raw3215_info *raw)
+{
+ struct raw3215_req *req;
+ struct ccw1 *ccw;
+
+ /* there can only be ONE read request at a time */
+ req = raw->queued_read;
+ if (req == NULL) {
+ /* no queued read request, use new req structure */
+ req = raw3215_alloc_req();
+ req->type = RAW3215_READ;
+ req->info = raw;
+ raw->queued_read = req;
+ }
+
+ ccw = req->ccws;
+ ccw->cmd_code = 0x0A; /* read inquiry */
+ ccw->flags = 0x20; /* ignore incorrect length */
+ ccw->count = 160;
+ ccw->cda = (__u32) __pa(raw->inbuf);
+}
+
+/*
+ * Set up a write request with the information from the main structure.
+ * A ccw chain is created that writes as much as possible from the output
+ * buffer to the 3215 device. If a queued write exists it is replaced by
+ * the new, probably lengthened request.
+ */
+static void
+raw3215_mk_write_req(struct raw3215_info *raw)
+{
+ struct raw3215_req *req;
+ struct ccw1 *ccw;
+ int len, count, ix, lines;
+
+ if (raw->count <= raw->written)
+ return;
+ /* check if there is a queued write request */
+ req = raw->queued_write;
+ if (req == NULL) {
+ /* no queued write request, use new req structure */
+ req = raw3215_alloc_req();
+ req->type = RAW3215_WRITE;
+ req->info = raw;
+ raw->queued_write = req;
+ } else {
+ raw->written -= req->len;
+ }
+
+ ccw = req->ccws;
+ req->start = (raw->head - raw->count + raw->written) &
+ (RAW3215_BUFFER_SIZE - 1);
+ /*
+ * now we have to count newlines. We can at max accept
+ * RAW3215_MAX_NEWLINE newlines in a single ssch due to
+ * a restriction in VM
+ */
+ lines = 0;
+ ix = req->start;
+ while (lines < RAW3215_MAX_NEWLINE && ix != raw->head) {
+ if (raw->buffer[ix] == 0x15)
+ lines++;
+ ix = (ix + 1) & (RAW3215_BUFFER_SIZE - 1);
+ }
+ len = ((ix - 1 - req->start) & (RAW3215_BUFFER_SIZE - 1)) + 1;
+ if (len > RAW3215_MAX_BYTES)
+ len = RAW3215_MAX_BYTES;
+ req->len = len;
+ raw->written += len;
+
+ /* set the indication if we should try to enlarge this request */
+ req->delayable = (ix == raw->head) && (len < RAW3215_MIN_WRITE);
+
+ ix = req->start;
+ while (len > 0) {
+ if (ccw > req->ccws)
+ ccw[-1].flags |= 0x40; /* use command chaining */
+ ccw->cmd_code = 0x01; /* write, auto carrier return */
+ ccw->flags = 0x20; /* ignore incorrect length ind. */
+ ccw->cda =
+ (__u32) __pa(raw->buffer + ix);
+ count = len;
+ if (ix + count > RAW3215_BUFFER_SIZE)
+ count = RAW3215_BUFFER_SIZE - ix;
+ ccw->count = count;
+ len -= count;
+ ix = (ix + count) & (RAW3215_BUFFER_SIZE - 1);
+ ccw++;
+ }
+ /*
+ * Add a NOP to the channel program. 3215 devices are purely
+ * emulated and its much better to avoid the channel end
+ * interrupt in this case.
+ */
+ if (ccw > req->ccws)
+ ccw[-1].flags |= 0x40; /* use command chaining */
+ ccw->cmd_code = 0x03; /* NOP */
+ ccw->flags = 0;
+ ccw->cda = 0;
+ ccw->count = 1;
+}
+
+/*
+ * Start a read or a write request
+ */
+static void
+raw3215_start_io(struct raw3215_info *raw)
+{
+ struct raw3215_req *req;
+ int res;
+
+ req = raw->queued_read;
+ if (req != NULL &&
+ !(raw->flags & (RAW3215_WORKING | RAW3215_THROTTLED))) {
+ /* dequeue request */
+ raw->queued_read = NULL;
+ res = ccw_device_start(raw->cdev, req->ccws,
+ (unsigned long) req, 0, 0);
+ if (res != 0) {
+ /* do_IO failed, put request back to queue */
+ raw->queued_read = req;
+ } else {
+ raw->flags |= RAW3215_WORKING;
+ }
+ }
+ req = raw->queued_write;
+ if (req != NULL &&
+ !(raw->flags & (RAW3215_WORKING | RAW3215_STOPPED))) {
+ /* dequeue request */
+ raw->queued_write = NULL;
+ res = ccw_device_start(raw->cdev, req->ccws,
+ (unsigned long) req, 0, 0);
+ if (res != 0) {
+ /* do_IO failed, put request back to queue */
+ raw->queued_write = req;
+ } else {
+ raw->flags |= RAW3215_WORKING;
+ }
+ }
+}
+
+/*
+ * Function to start a delayed output after RAW3215_TIMEOUT seconds
+ */
+static void
+raw3215_timeout(unsigned long __data)
+{
+ struct raw3215_info *raw = (struct raw3215_info *) __data;
+ unsigned long flags;
+
+ spin_lock_irqsave(raw->lock, flags);
+ if (raw->flags & RAW3215_TIMER_RUNS) {
+ del_timer(&raw->timer);
+ raw->flags &= ~RAW3215_TIMER_RUNS;
+ raw3215_mk_write_req(raw);
+ raw3215_start_io(raw);
+ }
+ spin_unlock_irqrestore(raw->lock, flags);
+}
+
+/*
+ * Function to conditionally start an IO. A read is started immediately,
+ * a write is only started immediately if the flush flag is on or the
+ * amount of data is bigger than RAW3215_MIN_WRITE. If a write is not
+ * done immediately a timer is started with a delay of RAW3215_TIMEOUT.
+ */
+static inline void
+raw3215_try_io(struct raw3215_info *raw)
+{
+ if (!(raw->flags & RAW3215_ACTIVE))
+ return;
+ if (raw->queued_read != NULL)
+ raw3215_start_io(raw);
+ else if (raw->queued_write != NULL) {
+ if ((raw->queued_write->delayable == 0) ||
+ (raw->flags & RAW3215_FLUSHING)) {
+ /* execute write requests bigger than minimum size */
+ raw3215_start_io(raw);
+ if (raw->flags & RAW3215_TIMER_RUNS) {
+ del_timer(&raw->timer);
+ raw->flags &= ~RAW3215_TIMER_RUNS;
+ }
+ } else if (!(raw->flags & RAW3215_TIMER_RUNS)) {
+ /* delay small writes */
+ init_timer(&raw->timer);
+ raw->timer.expires = RAW3215_TIMEOUT + jiffies;
+ raw->timer.data = (unsigned long) raw;
+ raw->timer.function = raw3215_timeout;
+ add_timer(&raw->timer);
+ raw->flags |= RAW3215_TIMER_RUNS;
+ }
+ }
+}
+
+/*
+ * The bottom half handler routine for 3215 devices. It tries to start
+ * the next IO and wakes up processes waiting on the tty.
+ */
+static void
+raw3215_tasklet(void *data)
+{
+ struct raw3215_info *raw;
+ struct tty_struct *tty;
+ unsigned long flags;
+
+ raw = (struct raw3215_info *) data;
+ spin_lock_irqsave(raw->lock, flags);
+ raw3215_mk_write_req(raw);
+ raw3215_try_io(raw);
+ spin_unlock_irqrestore(raw->lock, flags);
+ /* Check for pending message from raw3215_irq */
+ if (raw->message != NULL) {
+ printk(raw->message, raw->msg_dstat, raw->msg_cstat);
+ raw->message = NULL;
+ }
+ tty = raw->tty;
+ if (tty != NULL &&
+ RAW3215_BUFFER_SIZE - raw->count >= RAW3215_MIN_SPACE) {
+ tty_wakeup(tty);
+ }
+}
+
+/*
+ * Interrupt routine, called from common io layer
+ */
+static void
+raw3215_irq(struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
+{
+ struct raw3215_info *raw;
+ struct raw3215_req *req;
+ struct tty_struct *tty;
+ int cstat, dstat;
+ int count, slen;
+
+ raw = cdev->dev.driver_data;
+ req = (struct raw3215_req *) intparm;
+ cstat = irb->scsw.cstat;
+ dstat = irb->scsw.dstat;
+ if (cstat != 0) {
+ raw->message = KERN_WARNING
+ "Got nonzero channel status in raw3215_irq "
+ "(dev sts 0x%2x, sch sts 0x%2x)";
+ raw->msg_dstat = dstat;
+ raw->msg_cstat = cstat;
+ tasklet_schedule(&raw->tasklet);
+ }
+ if (dstat & 0x01) { /* we got a unit exception */
+ dstat &= ~0x01; /* we can ignore it */
+ }
+ switch (dstat) {
+ case 0x80:
+ if (cstat != 0)
+ break;
+ /* Attention interrupt, someone hit the enter key */
+ raw3215_mk_read_req(raw);
+ if (MACHINE_IS_P390)
+ memset(raw->inbuf, 0, RAW3215_INBUF_SIZE);
+ tasklet_schedule(&raw->tasklet);
+ break;
+ case 0x08:
+ case 0x0C:
+ /* Channel end interrupt. */
+ if ((raw = req->info) == NULL)
+ return; /* That shouldn't happen ... */
+ if (req->type == RAW3215_READ) {
+ /* store residual count, then wait for device end */
+ req->residual = irb->scsw.count;
+ }
+ if (dstat == 0x08)
+ break;
+ case 0x04:
+ /* Device end interrupt. */
+ if ((raw = req->info) == NULL)
+ return; /* That shouldn't happen ... */
+ if (req->type == RAW3215_READ && raw->tty != NULL) {
+ unsigned int cchar;
+
+ tty = raw->tty;
+ count = 160 - req->residual;
+ if (MACHINE_IS_P390) {
+ slen = strnlen(raw->inbuf, RAW3215_INBUF_SIZE);
+ if (count > slen)
+ count = slen;
+ } else
+ if (count >= TTY_FLIPBUF_SIZE - tty->flip.count)
+ count = TTY_FLIPBUF_SIZE - tty->flip.count - 1;
+ EBCASC(raw->inbuf, count);
+ cchar = ctrlchar_handle(raw->inbuf, count, tty);
+ switch (cchar & CTRLCHAR_MASK) {
+ case CTRLCHAR_SYSRQ:
+ break;
+
+ case CTRLCHAR_CTRL:
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+ *tty->flip.char_buf_ptr++ = cchar;
+ tty_flip_buffer_push(raw->tty);
+ break;
+
+ case CTRLCHAR_NONE:
+ memcpy(tty->flip.char_buf_ptr,
+ raw->inbuf, count);
+ if (count < 2 ||
+ (strncmp(raw->inbuf+count-2, "^n", 2) &&
+ strncmp(raw->inbuf+count-2, "\252n", 2)) ) {
+ /* don't add the auto \n */
+ tty->flip.char_buf_ptr[count] = '\n';
+ memset(tty->flip.flag_buf_ptr,
+ TTY_NORMAL, count + 1);
+ count++;
+ } else
+ count-=2;
+ tty->flip.char_buf_ptr += count;
+ tty->flip.flag_buf_ptr += count;
+ tty->flip.count += count;
+ tty_flip_buffer_push(raw->tty);
+ break;
+ }
+ } else if (req->type == RAW3215_WRITE) {
+ raw->count -= req->len;
+ raw->written -= req->len;
+ }
+ raw->flags &= ~RAW3215_WORKING;
+ raw3215_free_req(req);
+ /* check for empty wait */
+ if (waitqueue_active(&raw->empty_wait) &&
+ raw->queued_write == NULL &&
+ raw->queued_read == NULL) {
+ wake_up_interruptible(&raw->empty_wait);
+ }
+ tasklet_schedule(&raw->tasklet);
+ break;
+ default:
+ /* Strange interrupt, I'll do my best to clean up */
+ if (req != NULL && req->type != RAW3215_FREE) {
+ if (req->type == RAW3215_WRITE) {
+ raw->count -= req->len;
+ raw->written -= req->len;
+ }
+ raw->flags &= ~RAW3215_WORKING;
+ raw3215_free_req(req);
+ }
+ raw->message = KERN_WARNING
+ "Spurious interrupt in in raw3215_irq "
+ "(dev sts 0x%2x, sch sts 0x%2x)";
+ raw->msg_dstat = dstat;
+ raw->msg_cstat = cstat;
+ tasklet_schedule(&raw->tasklet);
+ }
+ return;
+}
+
+/*
+ * Wait until length bytes are available int the output buffer.
+ * Has to be called with the s390irq lock held. Can be called
+ * disabled.
+ */
+static void
+raw3215_make_room(struct raw3215_info *raw, unsigned int length)
+{
+ while (RAW3215_BUFFER_SIZE - raw->count < length) {
+ /* there might be a request pending */
+ raw->flags |= RAW3215_FLUSHING;
+ raw3215_mk_write_req(raw);
+ raw3215_try_io(raw);
+ raw->flags &= ~RAW3215_FLUSHING;
+#ifdef CONFIG_TN3215_CONSOLE
+ wait_cons_dev();
+#endif
+ /* Enough room freed up ? */
+ if (RAW3215_BUFFER_SIZE - raw->count >= length)
+ break;
+ /* there might be another cpu waiting for the lock */
+ spin_unlock(raw->lock);
+ udelay(100);
+ spin_lock(raw->lock);
+ }
+}
+
+/*
+ * String write routine for 3215 devices
+ */
+static void
+raw3215_write(struct raw3215_info *raw, const char *str, unsigned int length)
+{
+ unsigned long flags;
+ int c, count;
+
+ while (length > 0) {
+ spin_lock_irqsave(raw->lock, flags);
+ count = (length > RAW3215_BUFFER_SIZE) ?
+ RAW3215_BUFFER_SIZE : length;
+ length -= count;
+
+ raw3215_make_room(raw, count);
+
+ /* copy string to output buffer and convert it to EBCDIC */
+ while (1) {
+ c = min_t(int, count,
+ min(RAW3215_BUFFER_SIZE - raw->count,
+ RAW3215_BUFFER_SIZE - raw->head));
+ if (c <= 0)
+ break;
+ memcpy(raw->buffer + raw->head, str, c);
+ ASCEBC(raw->buffer + raw->head, c);
+ raw->head = (raw->head + c) & (RAW3215_BUFFER_SIZE - 1);
+ raw->count += c;
+ raw->line_pos += c;
+ str += c;
+ count -= c;
+ }
+ if (!(raw->flags & RAW3215_WORKING)) {
+ raw3215_mk_write_req(raw);
+ /* start or queue request */
+ raw3215_try_io(raw);
+ }
+ spin_unlock_irqrestore(raw->lock, flags);
+ }
+}
+
+/*
+ * Put character routine for 3215 devices
+ */
+static void
+raw3215_putchar(struct raw3215_info *raw, unsigned char ch)
+{
+ unsigned long flags;
+ unsigned int length, i;
+
+ spin_lock_irqsave(raw->lock, flags);
+ if (ch == '\t') {
+ length = TAB_STOP_SIZE - (raw->line_pos%TAB_STOP_SIZE);
+ raw->line_pos += length;
+ ch = ' ';
+ } else if (ch == '\n') {
+ length = 1;
+ raw->line_pos = 0;
+ } else {
+ length = 1;
+ raw->line_pos++;
+ }
+ raw3215_make_room(raw, length);
+
+ for (i = 0; i < length; i++) {
+ raw->buffer[raw->head] = (char) _ascebc[(int) ch];
+ raw->head = (raw->head + 1) & (RAW3215_BUFFER_SIZE - 1);
+ raw->count++;
+ }
+ if (!(raw->flags & RAW3215_WORKING)) {
+ raw3215_mk_write_req(raw);
+ /* start or queue request */
+ raw3215_try_io(raw);
+ }
+ spin_unlock_irqrestore(raw->lock, flags);
+}
+
+/*
+ * Flush routine, it simply sets the flush flag and tries to start
+ * pending IO.
+ */
+static void
+raw3215_flush_buffer(struct raw3215_info *raw)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(raw->lock, flags);
+ if (raw->count > 0) {
+ raw->flags |= RAW3215_FLUSHING;
+ raw3215_try_io(raw);
+ raw->flags &= ~RAW3215_FLUSHING;
+ }
+ spin_unlock_irqrestore(raw->lock, flags);
+}
+
+/*
+ * Fire up a 3215 device.
+ */
+static int
+raw3215_startup(struct raw3215_info *raw)
+{
+ unsigned long flags;
+
+ if (raw->flags & RAW3215_ACTIVE)
+ return 0;
+ raw->line_pos = 0;
+ raw->flags |= RAW3215_ACTIVE;
+ spin_lock_irqsave(raw->lock, flags);
+ raw3215_try_io(raw);
+ spin_unlock_irqrestore(raw->lock, flags);
+
+ return 0;
+}
+
+/*
+ * Shutdown a 3215 device.
+ */
+static void
+raw3215_shutdown(struct raw3215_info *raw)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long flags;
+
+ if (!(raw->flags & RAW3215_ACTIVE) || (raw->flags & RAW3215_FIXED))
+ return;
+ /* Wait for outstanding requests, then free irq */
+ spin_lock_irqsave(raw->lock, flags);
+ if ((raw->flags & RAW3215_WORKING) ||
+ raw->queued_write != NULL ||
+ raw->queued_read != NULL) {
+ raw->flags |= RAW3215_CLOSING;
+ add_wait_queue(&raw->empty_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(raw->lock, flags);
+ schedule();
+ spin_lock_irqsave(raw->lock, flags);
+ remove_wait_queue(&raw->empty_wait, &wait);
+ set_current_state(TASK_RUNNING);
+ raw->flags &= ~(RAW3215_ACTIVE | RAW3215_CLOSING);
+ }
+ spin_unlock_irqrestore(raw->lock, flags);
+}
+
+static int
+raw3215_probe (struct ccw_device *cdev)
+{
+ struct raw3215_info *raw;
+ int line;
+
+ raw = kmalloc(sizeof(struct raw3215_info) +
+ RAW3215_INBUF_SIZE, GFP_KERNEL|GFP_DMA);
+ if (raw == NULL)
+ return -ENOMEM;
+
+ spin_lock(&raw3215_device_lock);
+ for (line = 0; line < NR_3215; line++) {
+ if (!raw3215[line]) {
+ raw3215[line] = raw;
+ break;
+ }
+ }
+ spin_unlock(&raw3215_device_lock);
+ if (line == NR_3215) {
+ kfree(raw);
+ return -ENODEV;
+ }
+
+ raw->cdev = cdev;
+ raw->lock = get_ccwdev_lock(cdev);
+ raw->inbuf = (char *) raw + sizeof(struct raw3215_info);
+ memset(raw, 0, sizeof(struct raw3215_info));
+ raw->buffer = (char *) kmalloc(RAW3215_BUFFER_SIZE,
+ GFP_KERNEL|GFP_DMA);
+ if (raw->buffer == NULL) {
+ spin_lock(&raw3215_device_lock);
+ raw3215[line] = 0;
+ spin_unlock(&raw3215_device_lock);
+ kfree(raw);
+ return -ENOMEM;
+ }
+ tasklet_init(&raw->tasklet,
+ (void (*)(unsigned long)) raw3215_tasklet,
+ (unsigned long) raw);
+ init_waitqueue_head(&raw->empty_wait);
+
+ cdev->dev.driver_data = raw;
+ cdev->handler = raw3215_irq;
+
+ return 0;
+}
+
+static void
+raw3215_remove (struct ccw_device *cdev)
+{
+ struct raw3215_info *raw;
+
+ ccw_device_set_offline(cdev);
+ raw = cdev->dev.driver_data;
+ if (raw) {
+ cdev->dev.driver_data = NULL;
+ if (raw->buffer)
+ kfree(raw->buffer);
+ kfree(raw);
+ }
+}
+
+static int
+raw3215_set_online (struct ccw_device *cdev)
+{
+ struct raw3215_info *raw;
+
+ raw = cdev->dev.driver_data;
+ if (!raw)
+ return -ENODEV;
+
+ return raw3215_startup(raw);
+}
+
+static int
+raw3215_set_offline (struct ccw_device *cdev)
+{
+ struct raw3215_info *raw;
+
+ raw = cdev->dev.driver_data;
+ if (!raw)
+ return -ENODEV;
+
+ raw3215_shutdown(raw);
+
+ return 0;
+}
+
+static struct ccw_device_id raw3215_id[] = {
+ { CCW_DEVICE(0x3215, 0) },
+ { /* end of list */ },
+};
+
+static struct ccw_driver raw3215_ccw_driver = {
+ .name = "3215",
+ .owner = THIS_MODULE,
+ .ids = raw3215_id,
+ .probe = &raw3215_probe,
+ .remove = &raw3215_remove,
+ .set_online = &raw3215_set_online,
+ .set_offline = &raw3215_set_offline,
+};
+
+#ifdef CONFIG_TN3215_CONSOLE
+/*
+ * Write a string to the 3215 console
+ */
+static void
+con3215_write(struct console *co, const char *str, unsigned int count)
+{
+ struct raw3215_info *raw;
+ int i;
+
+ if (count <= 0)
+ return;
+ raw = raw3215[0]; /* console 3215 is the first one */
+ while (count > 0) {
+ for (i = 0; i < count; i++)
+ if (str[i] == '\t' || str[i] == '\n')
+ break;
+ raw3215_write(raw, str, i);
+ count -= i;
+ str += i;
+ if (count > 0) {
+ raw3215_putchar(raw, *str);
+ count--;
+ str++;
+ }
+ }
+}
+
+static struct tty_driver *con3215_device(struct console *c, int *index)
+{
+ *index = c->index;
+ return tty3215_driver;
+}
+
+/*
+ * panic() calls console_unblank before the system enters a
+ * disabled, endless loop.
+ */
+static void
+con3215_unblank(void)
+{
+ struct raw3215_info *raw;
+ unsigned long flags;
+
+ raw = raw3215[0]; /* console 3215 is the first one */
+ spin_lock_irqsave(raw->lock, flags);
+ raw3215_make_room(raw, RAW3215_BUFFER_SIZE);
+ spin_unlock_irqrestore(raw->lock, flags);
+}
+
+static int __init
+con3215_consetup(struct console *co, char *options)
+{
+ return 0;
+}
+
+/*
+ * The console structure for the 3215 console
+ */
+static struct console con3215 = {
+ .name = "ttyS",
+ .write = con3215_write,
+ .device = con3215_device,
+ .unblank = con3215_unblank,
+ .setup = con3215_consetup,
+ .flags = CON_PRINTBUFFER,
+};
+
+/*
+ * 3215 console initialization code called from console_init().
+ * NOTE: This is called before kmalloc is available.
+ */
+static int __init
+con3215_init(void)
+{
+ struct ccw_device *cdev;
+ struct raw3215_info *raw;
+ struct raw3215_req *req;
+ int i;
+
+ /* Check if 3215 is to be the console */
+ if (!CONSOLE_IS_3215)
+ return -ENODEV;
+
+ /* Set the console mode for VM */
+ if (MACHINE_IS_VM) {
+ cpcmd("TERM CONMODE 3215", NULL, 0);
+ cpcmd("TERM AUTOCR OFF", NULL, 0);
+ }
+
+ /* allocate 3215 request structures */
+ raw3215_freelist = NULL;
+ spin_lock_init(&raw3215_freelist_lock);
+ for (i = 0; i < NR_3215_REQ; i++) {
+ req = (struct raw3215_req *) alloc_bootmem_low(sizeof(struct raw3215_req));
+ req->next = raw3215_freelist;
+ raw3215_freelist = req;
+ }
+
+ cdev = ccw_device_probe_console();
+ if (!cdev)
+ return -ENODEV;
+
+ raw3215[0] = raw = (struct raw3215_info *)
+ alloc_bootmem_low(sizeof(struct raw3215_info));
+ memset(raw, 0, sizeof(struct raw3215_info));
+ raw->buffer = (char *) alloc_bootmem_low(RAW3215_BUFFER_SIZE);
+ raw->inbuf = (char *) alloc_bootmem_low(RAW3215_INBUF_SIZE);
+ raw->cdev = cdev;
+ raw->lock = get_ccwdev_lock(cdev);
+ cdev->dev.driver_data = raw;
+ cdev->handler = raw3215_irq;
+
+ raw->flags |= RAW3215_FIXED;
+ tasklet_init(&raw->tasklet,
+ (void (*)(unsigned long)) raw3215_tasklet,
+ (unsigned long) raw);
+ init_waitqueue_head(&raw->empty_wait);
+
+ /* Request the console irq */
+ if (raw3215_startup(raw) != 0) {
+ free_bootmem((unsigned long) raw->inbuf, RAW3215_INBUF_SIZE);
+ free_bootmem((unsigned long) raw->buffer, RAW3215_BUFFER_SIZE);
+ free_bootmem((unsigned long) raw, sizeof(struct raw3215_info));
+ raw3215[0] = NULL;
+ printk("Couldn't find a 3215 console device\n");
+ return -ENODEV;
+ }
+ register_console(&con3215);
+ return 0;
+}
+console_initcall(con3215_init);
+#endif
+
+/*
+ * tty3215_open
+ *
+ * This routine is called whenever a 3215 tty is opened.
+ */
+static int
+tty3215_open(struct tty_struct *tty, struct file * filp)
+{
+ struct raw3215_info *raw;
+ int retval, line;
+
+ line = tty->index;
+ if ((line < 0) || (line >= NR_3215))
+ return -ENODEV;
+
+ raw = raw3215[line];
+ if (raw == NULL)
+ return -ENODEV;
+
+ tty->driver_data = raw;
+ raw->tty = tty;
+
+ tty->low_latency = 0; /* don't use bottom half for pushing chars */
+ /*
+ * Start up 3215 device
+ */
+ retval = raw3215_startup(raw);
+ if (retval)
+ return retval;
+
+ return 0;
+}
+
+/*
+ * tty3215_close()
+ *
+ * This routine is called when the 3215 tty is closed. We wait
+ * for the remaining request to be completed. Then we clean up.
+ */
+static void
+tty3215_close(struct tty_struct *tty, struct file * filp)
+{
+ struct raw3215_info *raw;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+ if (raw == NULL || tty->count > 1)
+ return;
+ tty->closing = 1;
+ /* Shutdown the terminal */
+ raw3215_shutdown(raw);
+ tty->closing = 0;
+ raw->tty = NULL;
+}
+
+/*
+ * Returns the amount of free space in the output buffer.
+ */
+static int
+tty3215_write_room(struct tty_struct *tty)
+{
+ struct raw3215_info *raw;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+
+ /* Subtract TAB_STOP_SIZE to allow for a tab, 8 <<< 64K */
+ if ((RAW3215_BUFFER_SIZE - raw->count - TAB_STOP_SIZE) >= 0)
+ return RAW3215_BUFFER_SIZE - raw->count - TAB_STOP_SIZE;
+ else
+ return 0;
+}
+
+/*
+ * String write routine for 3215 ttys
+ */
+static int
+tty3215_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct raw3215_info *raw;
+
+ if (!tty)
+ return 0;
+ raw = (struct raw3215_info *) tty->driver_data;
+ raw3215_write(raw, buf, count);
+ return count;
+}
+
+/*
+ * Put character routine for 3215 ttys
+ */
+static void
+tty3215_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct raw3215_info *raw;
+
+ if (!tty)
+ return;
+ raw = (struct raw3215_info *) tty->driver_data;
+ raw3215_putchar(raw, ch);
+}
+
+static void
+tty3215_flush_chars(struct tty_struct *tty)
+{
+}
+
+/*
+ * Returns the number of characters in the output buffer
+ */
+static int
+tty3215_chars_in_buffer(struct tty_struct *tty)
+{
+ struct raw3215_info *raw;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+ return raw->count;
+}
+
+static void
+tty3215_flush_buffer(struct tty_struct *tty)
+{
+ struct raw3215_info *raw;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+ raw3215_flush_buffer(raw);
+ tty_wakeup(tty);
+}
+
+/*
+ * Currently we don't have any io controls for 3215 ttys
+ */
+static int
+tty3215_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+/*
+ * Disable reading from a 3215 tty
+ */
+static void
+tty3215_throttle(struct tty_struct * tty)
+{
+ struct raw3215_info *raw;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+ raw->flags |= RAW3215_THROTTLED;
+}
+
+/*
+ * Enable reading from a 3215 tty
+ */
+static void
+tty3215_unthrottle(struct tty_struct * tty)
+{
+ struct raw3215_info *raw;
+ unsigned long flags;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+ if (raw->flags & RAW3215_THROTTLED) {
+ spin_lock_irqsave(raw->lock, flags);
+ raw->flags &= ~RAW3215_THROTTLED;
+ raw3215_try_io(raw);
+ spin_unlock_irqrestore(raw->lock, flags);
+ }
+}
+
+/*
+ * Disable writing to a 3215 tty
+ */
+static void
+tty3215_stop(struct tty_struct *tty)
+{
+ struct raw3215_info *raw;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+ raw->flags |= RAW3215_STOPPED;
+}
+
+/*
+ * Enable writing to a 3215 tty
+ */
+static void
+tty3215_start(struct tty_struct *tty)
+{
+ struct raw3215_info *raw;
+ unsigned long flags;
+
+ raw = (struct raw3215_info *) tty->driver_data;
+ if (raw->flags & RAW3215_STOPPED) {
+ spin_lock_irqsave(raw->lock, flags);
+ raw->flags &= ~RAW3215_STOPPED;
+ raw3215_try_io(raw);
+ spin_unlock_irqrestore(raw->lock, flags);
+ }
+}
+
+static struct tty_operations tty3215_ops = {
+ .open = tty3215_open,
+ .close = tty3215_close,
+ .write = tty3215_write,
+ .put_char = tty3215_put_char,
+ .flush_chars = tty3215_flush_chars,
+ .write_room = tty3215_write_room,
+ .chars_in_buffer = tty3215_chars_in_buffer,
+ .flush_buffer = tty3215_flush_buffer,
+ .ioctl = tty3215_ioctl,
+ .throttle = tty3215_throttle,
+ .unthrottle = tty3215_unthrottle,
+ .stop = tty3215_stop,
+ .start = tty3215_start,
+};
+
+/*
+ * 3215 tty registration code called from tty_init().
+ * Most kernel services (incl. kmalloc) are available at this poimt.
+ */
+int __init
+tty3215_init(void)
+{
+ struct tty_driver *driver;
+ int ret;
+
+ if (!CONSOLE_IS_3215)
+ return 0;
+
+ driver = alloc_tty_driver(NR_3215);
+ if (!driver)
+ return -ENOMEM;
+
+ ret = ccw_driver_register(&raw3215_ccw_driver);
+ if (ret) {
+ put_tty_driver(driver);
+ return ret;
+ }
+ /*
+ * Initialize the tty_driver structure
+ * Entries in tty3215_driver that are NOT initialized:
+ * proc_entry, set_termios, flush_buffer, set_ldisc, write_proc
+ */
+
+ driver->owner = THIS_MODULE;
+ driver->driver_name = "tty3215";
+ driver->name = "ttyS";
+ driver->major = TTY_MAJOR;
+ driver->minor_start = 64;
+ driver->type = TTY_DRIVER_TYPE_SYSTEM;
+ driver->subtype = SYSTEM_TYPE_TTY;
+ driver->init_termios = tty_std_termios;
+ driver->init_termios.c_iflag = IGNBRK | IGNPAR;
+ driver->init_termios.c_oflag = ONLCR | XTABS;
+ driver->init_termios.c_lflag = ISIG;
+ driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(driver, &tty3215_ops);
+ ret = tty_register_driver(driver);
+ if (ret) {
+ printk("Couldn't register tty3215 driver\n");
+ put_tty_driver(driver);
+ return ret;
+ }
+ tty3215_driver = driver;
+ return 0;
+}
+
+static void __exit
+tty3215_exit(void)
+{
+ tty_unregister_driver(tty3215_driver);
+ put_tty_driver(tty3215_driver);
+ ccw_driver_unregister(&raw3215_ccw_driver);
+}
+
+module_init(tty3215_init);
+module_exit(tty3215_exit);
diff --git a/drivers/s390/char/con3270.c b/drivers/s390/char/con3270.c
new file mode 100644
index 000000000000..d52fb57a6b19
--- /dev/null
+++ b/drivers/s390/char/con3270.c
@@ -0,0 +1,638 @@
+/*
+ * drivers/s390/char/con3270.c
+ * IBM/3270 Driver - console view.
+ *
+ * Author(s):
+ * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
+ * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ */
+
+#include <linux/config.h>
+#include <linux/bootmem.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/types.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+#include <asm/cpcmd.h>
+#include <asm/ebcdic.h>
+
+#include "raw3270.h"
+#include "ctrlchar.h"
+
+#define CON3270_OUTPUT_BUFFER_SIZE 1024
+#define CON3270_STRING_PAGES 4
+
+static struct raw3270_fn con3270_fn;
+
+/*
+ * Main 3270 console view data structure.
+ */
+struct con3270 {
+ struct raw3270_view view;
+ spinlock_t lock;
+ struct list_head freemem; /* list of free memory for strings. */
+
+ /* Output stuff. */
+ struct list_head lines; /* list of lines. */
+ struct list_head update; /* list of lines to update. */
+ int line_nr; /* line number for next update. */
+ int nr_lines; /* # lines in list. */
+ int nr_up; /* # lines up in history. */
+ unsigned long update_flags; /* Update indication bits. */
+ struct string *cline; /* current output line. */
+ struct string *status; /* last line of display. */
+ struct raw3270_request *write; /* single write request. */
+ struct timer_list timer;
+
+ /* Input stuff. */
+ struct string *input; /* input string for read request. */
+ struct raw3270_request *read; /* single read request. */
+ struct raw3270_request *kreset; /* single keyboard reset request. */
+ struct tasklet_struct readlet; /* tasklet to issue read request. */
+};
+
+static struct con3270 *condev;
+
+/* con3270->update_flags. See con3270_update for details. */
+#define CON_UPDATE_ERASE 1 /* Use EWRITEA instead of WRITE. */
+#define CON_UPDATE_LIST 2 /* Update lines in tty3270->update. */
+#define CON_UPDATE_STATUS 4 /* Update status line. */
+#define CON_UPDATE_ALL 7
+
+static void con3270_update(struct con3270 *);
+
+/*
+ * Setup timeout for a device. On timeout trigger an update.
+ */
+void
+con3270_set_timer(struct con3270 *cp, int expires)
+{
+ if (expires == 0) {
+ if (timer_pending(&cp->timer))
+ del_timer(&cp->timer);
+ return;
+ }
+ if (timer_pending(&cp->timer) &&
+ mod_timer(&cp->timer, jiffies + expires))
+ return;
+ cp->timer.function = (void (*)(unsigned long)) con3270_update;
+ cp->timer.data = (unsigned long) cp;
+ cp->timer.expires = jiffies + expires;
+ add_timer(&cp->timer);
+}
+
+/*
+ * The status line is the last line of the screen. It shows the string
+ * "console view" in the lower left corner and "Running"/"More..."/"Holding"
+ * in the lower right corner of the screen.
+ */
+static void
+con3270_update_status(struct con3270 *cp)
+{
+ char *str;
+
+ str = (cp->nr_up != 0) ? "History" : "Running";
+ memcpy(cp->status->string + 24, str, 7);
+ codepage_convert(cp->view.ascebc, cp->status->string + 24, 7);
+ cp->update_flags |= CON_UPDATE_STATUS;
+}
+
+static void
+con3270_create_status(struct con3270 *cp)
+{
+ static const unsigned char blueprint[] =
+ { TO_SBA, 0, 0, TO_SF,TF_LOG,TO_SA,TAT_COLOR, TAC_GREEN,
+ 'c','o','n','s','o','l','e',' ','v','i','e','w',
+ TO_RA,0,0,0,'R','u','n','n','i','n','g',TO_SF,TF_LOG };
+
+ cp->status = alloc_string(&cp->freemem, sizeof(blueprint));
+ /* Copy blueprint to status line */
+ memcpy(cp->status->string, blueprint, sizeof(blueprint));
+ /* Set TO_RA addresses. */
+ raw3270_buffer_address(cp->view.dev, cp->status->string + 1,
+ cp->view.cols * (cp->view.rows - 1));
+ raw3270_buffer_address(cp->view.dev, cp->status->string + 21,
+ cp->view.cols * cp->view.rows - 8);
+ /* Convert strings to ebcdic. */
+ codepage_convert(cp->view.ascebc, cp->status->string + 8, 12);
+ codepage_convert(cp->view.ascebc, cp->status->string + 24, 7);
+}
+
+/*
+ * Set output offsets to 3270 datastream fragment of a console string.
+ */
+static void
+con3270_update_string(struct con3270 *cp, struct string *s, int nr)
+{
+ if (s->len >= cp->view.cols - 5)
+ return;
+ raw3270_buffer_address(cp->view.dev, s->string + s->len - 3,
+ cp->view.cols * (nr + 1));
+}
+
+/*
+ * Rebuild update list to print all lines.
+ */
+static void
+con3270_rebuild_update(struct con3270 *cp)
+{
+ struct string *s, *n;
+ int nr;
+
+ /*
+ * Throw away update list and create a new one,
+ * containing all lines that will fit on the screen.
+ */
+ list_for_each_entry_safe(s, n, &cp->update, update)
+ list_del_init(&s->update);
+ nr = cp->view.rows - 2 + cp->nr_up;
+ list_for_each_entry_reverse(s, &cp->lines, list) {
+ if (nr < cp->view.rows - 1)
+ list_add(&s->update, &cp->update);
+ if (--nr < 0)
+ break;
+ }
+ cp->line_nr = 0;
+ cp->update_flags |= CON_UPDATE_LIST;
+}
+
+/*
+ * Alloc string for size bytes. Free strings from history if necessary.
+ */
+static struct string *
+con3270_alloc_string(struct con3270 *cp, size_t size)
+{
+ struct string *s, *n;
+
+ s = alloc_string(&cp->freemem, size);
+ if (s)
+ return s;
+ list_for_each_entry_safe(s, n, &cp->lines, list) {
+ list_del(&s->list);
+ if (!list_empty(&s->update))
+ list_del(&s->update);
+ cp->nr_lines--;
+ if (free_string(&cp->freemem, s) >= size)
+ break;
+ }
+ s = alloc_string(&cp->freemem, size);
+ BUG_ON(!s);
+ if (cp->nr_up != 0 && cp->nr_up + cp->view.rows > cp->nr_lines) {
+ cp->nr_up = cp->nr_lines - cp->view.rows + 1;
+ con3270_rebuild_update(cp);
+ con3270_update_status(cp);
+ }
+ return s;
+}
+
+/*
+ * Write completion callback.
+ */
+static void
+con3270_write_callback(struct raw3270_request *rq, void *data)
+{
+ raw3270_request_reset(rq);
+ xchg(&((struct con3270 *) rq->view)->write, rq);
+}
+
+/*
+ * Update console display.
+ */
+static void
+con3270_update(struct con3270 *cp)
+{
+ struct raw3270_request *wrq;
+ char wcc, prolog[6];
+ unsigned long flags;
+ unsigned long updated;
+ struct string *s, *n;
+ int rc;
+
+ wrq = xchg(&cp->write, 0);
+ if (!wrq) {
+ con3270_set_timer(cp, 1);
+ return;
+ }
+
+ spin_lock_irqsave(&cp->view.lock, flags);
+ updated = 0;
+ if (cp->update_flags & CON_UPDATE_ERASE) {
+ /* Use erase write alternate to initialize display. */
+ raw3270_request_set_cmd(wrq, TC_EWRITEA);
+ updated |= CON_UPDATE_ERASE;
+ } else
+ raw3270_request_set_cmd(wrq, TC_WRITE);
+
+ wcc = TW_NONE;
+ raw3270_request_add_data(wrq, &wcc, 1);
+
+ /*
+ * Update status line.
+ */
+ if (cp->update_flags & CON_UPDATE_STATUS)
+ if (raw3270_request_add_data(wrq, cp->status->string,
+ cp->status->len) == 0)
+ updated |= CON_UPDATE_STATUS;
+
+ if (cp->update_flags & CON_UPDATE_LIST) {
+ prolog[0] = TO_SBA;
+ prolog[3] = TO_SA;
+ prolog[4] = TAT_COLOR;
+ prolog[5] = TAC_TURQ;
+ raw3270_buffer_address(cp->view.dev, prolog + 1,
+ cp->view.cols * cp->line_nr);
+ raw3270_request_add_data(wrq, prolog, 6);
+ /* Write strings in the update list to the screen. */
+ list_for_each_entry_safe(s, n, &cp->update, update) {
+ if (s != cp->cline)
+ con3270_update_string(cp, s, cp->line_nr);
+ if (raw3270_request_add_data(wrq, s->string,
+ s->len) != 0)
+ break;
+ list_del_init(&s->update);
+ if (s != cp->cline)
+ cp->line_nr++;
+ }
+ if (list_empty(&cp->update))
+ updated |= CON_UPDATE_LIST;
+ }
+ wrq->callback = con3270_write_callback;
+ rc = raw3270_start(&cp->view, wrq);
+ if (rc == 0) {
+ cp->update_flags &= ~updated;
+ if (cp->update_flags)
+ con3270_set_timer(cp, 1);
+ } else {
+ raw3270_request_reset(wrq);
+ xchg(&cp->write, wrq);
+ }
+ spin_unlock_irqrestore(&cp->view.lock, flags);
+}
+
+/*
+ * Read tasklet.
+ */
+static void
+con3270_read_tasklet(struct raw3270_request *rrq)
+{
+ static char kreset_data = TW_KR;
+ struct con3270 *cp;
+ unsigned long flags;
+ int nr_up, deactivate;
+
+ cp = (struct con3270 *) rrq->view;
+ spin_lock_irqsave(&cp->view.lock, flags);
+ nr_up = cp->nr_up;
+ deactivate = 0;
+ /* Check aid byte. */
+ switch (cp->input->string[0]) {
+ case 0x7d: /* enter: jump to bottom. */
+ nr_up = 0;
+ break;
+ case 0xf3: /* PF3: deactivate the console view. */
+ deactivate = 1;
+ break;
+ case 0x6d: /* clear: start from scratch. */
+ con3270_rebuild_update(cp);
+ cp->update_flags = CON_UPDATE_ALL;
+ con3270_set_timer(cp, 1);
+ break;
+ case 0xf7: /* PF7: do a page up in the console log. */
+ nr_up += cp->view.rows - 2;
+ if (nr_up + cp->view.rows - 1 > cp->nr_lines) {
+ nr_up = cp->nr_lines - cp->view.rows + 1;
+ if (nr_up < 0)
+ nr_up = 0;
+ }
+ break;
+ case 0xf8: /* PF8: do a page down in the console log. */
+ nr_up -= cp->view.rows - 2;
+ if (nr_up < 0)
+ nr_up = 0;
+ break;
+ }
+ if (nr_up != cp->nr_up) {
+ cp->nr_up = nr_up;
+ con3270_rebuild_update(cp);
+ con3270_update_status(cp);
+ con3270_set_timer(cp, 1);
+ }
+ spin_unlock_irqrestore(&cp->view.lock, flags);
+
+ /* Start keyboard reset command. */
+ raw3270_request_reset(cp->kreset);
+ raw3270_request_set_cmd(cp->kreset, TC_WRITE);
+ raw3270_request_add_data(cp->kreset, &kreset_data, 1);
+ raw3270_start(&cp->view, cp->kreset);
+
+ if (deactivate)
+ raw3270_deactivate_view(&cp->view);
+
+ raw3270_request_reset(rrq);
+ xchg(&cp->read, rrq);
+ raw3270_put_view(&cp->view);
+}
+
+/*
+ * Read request completion callback.
+ */
+static void
+con3270_read_callback(struct raw3270_request *rq, void *data)
+{
+ raw3270_get_view(rq->view);
+ /* Schedule tasklet to pass input to tty. */
+ tasklet_schedule(&((struct con3270 *) rq->view)->readlet);
+}
+
+/*
+ * Issue a read request. Called only from interrupt function.
+ */
+static void
+con3270_issue_read(struct con3270 *cp)
+{
+ struct raw3270_request *rrq;
+ int rc;
+
+ rrq = xchg(&cp->read, 0);
+ if (!rrq)
+ /* Read already scheduled. */
+ return;
+ rrq->callback = con3270_read_callback;
+ rrq->callback_data = cp;
+ raw3270_request_set_cmd(rrq, TC_READMOD);
+ raw3270_request_set_data(rrq, cp->input->string, cp->input->len);
+ /* Issue the read modified request. */
+ rc = raw3270_start_irq(&cp->view, rrq);
+ if (rc)
+ raw3270_request_reset(rrq);
+}
+
+/*
+ * Switch to the console view.
+ */
+static int
+con3270_activate(struct raw3270_view *view)
+{
+ unsigned long flags;
+ struct con3270 *cp;
+
+ cp = (struct con3270 *) view;
+ spin_lock_irqsave(&cp->view.lock, flags);
+ cp->nr_up = 0;
+ con3270_rebuild_update(cp);
+ con3270_update_status(cp);
+ cp->update_flags = CON_UPDATE_ALL;
+ con3270_set_timer(cp, 1);
+ spin_unlock_irqrestore(&cp->view.lock, flags);
+ return 0;
+}
+
+static void
+con3270_deactivate(struct raw3270_view *view)
+{
+ unsigned long flags;
+ struct con3270 *cp;
+
+ cp = (struct con3270 *) view;
+ spin_lock_irqsave(&cp->view.lock, flags);
+ del_timer(&cp->timer);
+ spin_unlock_irqrestore(&cp->view.lock, flags);
+}
+
+static int
+con3270_irq(struct con3270 *cp, struct raw3270_request *rq, struct irb *irb)
+{
+ /* Handle ATTN. Schedule tasklet to read aid. */
+ if (irb->scsw.dstat & DEV_STAT_ATTENTION)
+ con3270_issue_read(cp);
+
+ if (rq) {
+ if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
+ rq->rc = -EIO;
+ else
+ /* Normal end. Copy residual count. */
+ rq->rescnt = irb->scsw.count;
+ }
+ return RAW3270_IO_DONE;
+}
+
+/* Console view to a 3270 device. */
+static struct raw3270_fn con3270_fn = {
+ .activate = con3270_activate,
+ .deactivate = con3270_deactivate,
+ .intv = (void *) con3270_irq
+};
+
+static inline void
+con3270_cline_add(struct con3270 *cp)
+{
+ if (!list_empty(&cp->cline->list))
+ /* Already added. */
+ return;
+ list_add_tail(&cp->cline->list, &cp->lines);
+ cp->nr_lines++;
+ con3270_rebuild_update(cp);
+}
+
+static inline void
+con3270_cline_insert(struct con3270 *cp, unsigned char c)
+{
+ cp->cline->string[cp->cline->len++] =
+ cp->view.ascebc[(c < ' ') ? ' ' : c];
+ if (list_empty(&cp->cline->update)) {
+ list_add_tail(&cp->cline->update, &cp->update);
+ cp->update_flags |= CON_UPDATE_LIST;
+ }
+}
+
+static inline void
+con3270_cline_end(struct con3270 *cp)
+{
+ struct string *s;
+ unsigned int size;
+
+ /* Copy cline. */
+ size = (cp->cline->len < cp->view.cols - 5) ?
+ cp->cline->len + 4 : cp->view.cols;
+ s = con3270_alloc_string(cp, size);
+ memcpy(s->string, cp->cline->string, cp->cline->len);
+ if (s->len < cp->view.cols - 5) {
+ s->string[s->len - 4] = TO_RA;
+ s->string[s->len - 1] = 0;
+ } else {
+ while (--size > cp->cline->len)
+ s->string[size] = cp->view.ascebc[' '];
+ }
+ /* Replace cline with allocated line s and reset cline. */
+ list_add(&s->list, &cp->cline->list);
+ list_del_init(&cp->cline->list);
+ if (!list_empty(&cp->cline->update)) {
+ list_add(&s->update, &cp->cline->update);
+ list_del_init(&cp->cline->update);
+ }
+ cp->cline->len = 0;
+}
+
+/*
+ * Write a string to the 3270 console
+ */
+static void
+con3270_write(struct console *co, const char *str, unsigned int count)
+{
+ struct con3270 *cp;
+ unsigned long flags;
+ unsigned char c;
+
+ cp = condev;
+ if (cp->view.dev)
+ raw3270_activate_view(&cp->view);
+ spin_lock_irqsave(&cp->view.lock, flags);
+ while (count-- > 0) {
+ c = *str++;
+ if (cp->cline->len == 0)
+ con3270_cline_add(cp);
+ if (c != '\n')
+ con3270_cline_insert(cp, c);
+ if (c == '\n' || cp->cline->len >= cp->view.cols)
+ con3270_cline_end(cp);
+ }
+ /* Setup timer to output current console buffer after 1/10 second */
+ if (cp->view.dev && !timer_pending(&cp->timer))
+ con3270_set_timer(cp, HZ/10);
+ spin_unlock_irqrestore(&cp->view.lock,flags);
+}
+
+extern struct tty_driver *tty3270_driver;
+
+static struct tty_driver *
+con3270_device(struct console *c, int *index)
+{
+ *index = c->index;
+ return tty3270_driver;
+}
+
+/*
+ * Wait for end of write request.
+ */
+static void
+con3270_wait_write(struct con3270 *cp)
+{
+ while (!cp->write) {
+ raw3270_wait_cons_dev(cp->view.dev);
+ barrier();
+ }
+}
+
+/*
+ * panic() calls console_unblank before the system enters a
+ * disabled, endless loop.
+ */
+static void
+con3270_unblank(void)
+{
+ struct con3270 *cp;
+ unsigned long flags;
+
+ cp = condev;
+ if (!cp->view.dev)
+ return;
+ spin_lock_irqsave(&cp->view.lock, flags);
+ con3270_wait_write(cp);
+ cp->nr_up = 0;
+ con3270_rebuild_update(cp);
+ con3270_update_status(cp);
+ while (cp->update_flags != 0) {
+ spin_unlock_irqrestore(&cp->view.lock, flags);
+ con3270_update(cp);
+ spin_lock_irqsave(&cp->view.lock, flags);
+ con3270_wait_write(cp);
+ }
+ spin_unlock_irqrestore(&cp->view.lock, flags);
+}
+
+static int __init
+con3270_consetup(struct console *co, char *options)
+{
+ return 0;
+}
+
+/*
+ * The console structure for the 3270 console
+ */
+static struct console con3270 = {
+ .name = "tty3270",
+ .write = con3270_write,
+ .device = con3270_device,
+ .unblank = con3270_unblank,
+ .setup = con3270_consetup,
+ .flags = CON_PRINTBUFFER,
+};
+
+/*
+ * 3270 console initialization code called from console_init().
+ * NOTE: This is called before kmalloc is available.
+ */
+static int __init
+con3270_init(void)
+{
+ struct ccw_device *cdev;
+ struct raw3270 *rp;
+ void *cbuf;
+ int i;
+
+ /* Check if 3270 is to be the console */
+ if (!CONSOLE_IS_3270)
+ return -ENODEV;
+
+ /* Set the console mode for VM */
+ if (MACHINE_IS_VM) {
+ cpcmd("TERM CONMODE 3270", 0, 0);
+ cpcmd("TERM AUTOCR OFF", 0, 0);
+ }
+
+ cdev = ccw_device_probe_console();
+ if (!cdev)
+ return -ENODEV;
+ rp = raw3270_setup_console(cdev);
+ if (IS_ERR(rp))
+ return PTR_ERR(rp);
+
+ condev = (struct con3270 *) alloc_bootmem_low(sizeof(struct con3270));
+ memset(condev, 0, sizeof(struct con3270));
+ condev->view.dev = rp;
+
+ condev->read = raw3270_request_alloc_bootmem(0);
+ condev->read->callback = con3270_read_callback;
+ condev->read->callback_data = condev;
+ condev->write =
+ raw3270_request_alloc_bootmem(CON3270_OUTPUT_BUFFER_SIZE);
+ condev->kreset = raw3270_request_alloc_bootmem(1);
+
+ INIT_LIST_HEAD(&condev->lines);
+ INIT_LIST_HEAD(&condev->update);
+ init_timer(&condev->timer);
+ tasklet_init(&condev->readlet,
+ (void (*)(unsigned long)) con3270_read_tasklet,
+ (unsigned long) condev->read);
+
+ raw3270_add_view(&condev->view, &con3270_fn, 0);
+
+ INIT_LIST_HEAD(&condev->freemem);
+ for (i = 0; i < CON3270_STRING_PAGES; i++) {
+ cbuf = (void *) alloc_bootmem_low_pages(PAGE_SIZE);
+ add_string_memory(&condev->freemem, cbuf, PAGE_SIZE);
+ }
+ condev->cline = alloc_string(&condev->freemem, condev->view.cols);
+ condev->cline->len = 0;
+ con3270_create_status(condev);
+ condev->input = alloc_string(&condev->freemem, 80);
+ register_console(&con3270);
+ return 0;
+}
+
+console_initcall(con3270_init);
diff --git a/drivers/s390/char/ctrlchar.c b/drivers/s390/char/ctrlchar.c
new file mode 100644
index 000000000000..be463242cf0f
--- /dev/null
+++ b/drivers/s390/char/ctrlchar.c
@@ -0,0 +1,75 @@
+/*
+ * drivers/s390/char/ctrlchar.c
+ * Unified handling of special chars.
+ *
+ * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Fritz Elfert <felfert@millenux.com> <elfert@de.ibm.com>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <asm/errno.h>
+#include <linux/sysrq.h>
+#include <linux/ctype.h>
+
+#include "ctrlchar.h"
+
+#ifdef CONFIG_MAGIC_SYSRQ
+static int ctrlchar_sysrq_key;
+
+static void
+ctrlchar_handle_sysrq(void *tty)
+{
+ handle_sysrq(ctrlchar_sysrq_key, NULL, (struct tty_struct *) tty);
+}
+
+static DECLARE_WORK(ctrlchar_work, ctrlchar_handle_sysrq, 0);
+#endif
+
+
+/**
+ * Check for special chars at start of input.
+ *
+ * @param buf Console input buffer.
+ * @param len Length of valid data in buffer.
+ * @param tty The tty struct for this console.
+ * @return CTRLCHAR_NONE, if nothing matched,
+ * CTRLCHAR_SYSRQ, if sysrq was encountered
+ * otherwise char to be inserted logically or'ed
+ * with CTRLCHAR_CTRL
+ */
+unsigned int
+ctrlchar_handle(const unsigned char *buf, int len, struct tty_struct *tty)
+{
+ if ((len < 2) || (len > 3))
+ return CTRLCHAR_NONE;
+
+ /* hat is 0xb1 in codepage 037 (US etc.) and thus */
+ /* converted to 0x5e in ascii ('^') */
+ if ((buf[0] != '^') && (buf[0] != '\252'))
+ return CTRLCHAR_NONE;
+
+#ifdef CONFIG_MAGIC_SYSRQ
+ /* racy */
+ if (len == 3 && buf[1] == '-') {
+ ctrlchar_sysrq_key = buf[2];
+ ctrlchar_work.data = tty;
+ schedule_work(&ctrlchar_work);
+ return CTRLCHAR_SYSRQ;
+ }
+#endif
+
+ if (len != 2)
+ return CTRLCHAR_NONE;
+
+ switch (tolower(buf[1])) {
+ case 'c':
+ return INTR_CHAR(tty) | CTRLCHAR_CTRL;
+ case 'd':
+ return EOF_CHAR(tty) | CTRLCHAR_CTRL;
+ case 'z':
+ return SUSP_CHAR(tty) | CTRLCHAR_CTRL;
+ }
+ return CTRLCHAR_NONE;
+}
diff --git a/drivers/s390/char/ctrlchar.h b/drivers/s390/char/ctrlchar.h
new file mode 100644
index 000000000000..935ffa0ea7c6
--- /dev/null
+++ b/drivers/s390/char/ctrlchar.h
@@ -0,0 +1,20 @@
+/*
+ * drivers/s390/char/ctrlchar.c
+ * Unified handling of special chars.
+ *
+ * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Fritz Elfert <felfert@millenux.com> <elfert@de.ibm.com>
+ *
+ */
+
+#include <linux/tty.h>
+
+extern unsigned int
+ctrlchar_handle(const unsigned char *buf, int len, struct tty_struct *tty);
+
+
+#define CTRLCHAR_NONE (1 << 8)
+#define CTRLCHAR_CTRL (2 << 8)
+#define CTRLCHAR_SYSRQ (3 << 8)
+
+#define CTRLCHAR_MASK (~0xffu)
diff --git a/drivers/s390/char/defkeymap.c b/drivers/s390/char/defkeymap.c
new file mode 100644
index 000000000000..ca15adb140d1
--- /dev/null
+++ b/drivers/s390/char/defkeymap.c
@@ -0,0 +1,156 @@
+
+/* Do not edit this file! It was automatically generated by */
+/* loadkeys --mktable defkeymap.map > defkeymap.c */
+
+#include <linux/types.h>
+#include <linux/keyboard.h>
+#include <linux/kd.h>
+
+u_short plain_map[NR_KEYS] = {
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
+ 0xf020, 0xf000, 0xf0e2, 0xf0e4, 0xf0e0, 0xf0e1, 0xf0e3, 0xf0e5,
+ 0xf0e7, 0xf0f1, 0xf0a2, 0xf02e, 0xf03c, 0xf028, 0xf02b, 0xf07c,
+ 0xf026, 0xf0e9, 0xf0e2, 0xf0eb, 0xf0e8, 0xf0ed, 0xf0ee, 0xf0ef,
+ 0xf0ec, 0xf0df, 0xf021, 0xf024, 0xf02a, 0xf029, 0xf03b, 0xf0ac,
+ 0xf02d, 0xf02f, 0xf0c2, 0xf0c4, 0xf0c0, 0xf0c1, 0xf0c3, 0xf0c5,
+ 0xf0c7, 0xf0d1, 0xf0a6, 0xf02c, 0xf025, 0xf05f, 0xf03e, 0xf03f,
+ 0xf0f8, 0xf0c9, 0xf0ca, 0xf0cb, 0xf0c8, 0xf0cd, 0xf0ce, 0xf0cf,
+ 0xf0cc, 0xf060, 0xf03a, 0xf023, 0xf040, 0xf027, 0xf03d, 0xf022,
+};
+
+static u_short shift_map[NR_KEYS] = {
+ 0xf0d8, 0xf061, 0xf062, 0xf063, 0xf064, 0xf065, 0xf066, 0xf067,
+ 0xf068, 0xf069, 0xf0ab, 0xf0bb, 0xf0f0, 0xf0fd, 0xf0fe, 0xf0b1,
+ 0xf0b0, 0xf06a, 0xf06b, 0xf06c, 0xf06d, 0xf06e, 0xf06f, 0xf070,
+ 0xf071, 0xf072, 0xf000, 0xf000, 0xf0e6, 0xf0b8, 0xf0c6, 0xf0a4,
+ 0xf0b5, 0xf07e, 0xf073, 0xf074, 0xf075, 0xf076, 0xf077, 0xf078,
+ 0xf079, 0xf07a, 0xf0a1, 0xf0bf, 0xf0d0, 0xf0dd, 0xf0de, 0xf0ae,
+ 0xf402, 0xf0a3, 0xf0a5, 0xf0b7, 0xf0a9, 0xf0a7, 0xf0b6, 0xf0bc,
+ 0xf0bd, 0xf0be, 0xf05b, 0xf05d, 0xf000, 0xf0a8, 0xf0b4, 0xf0d7,
+ 0xf07b, 0xf041, 0xf042, 0xf043, 0xf044, 0xf045, 0xf046, 0xf047,
+ 0xf048, 0xf049, 0xf000, 0xf0f4, 0xf0f6, 0xf0f2, 0xf0f3, 0xf0f5,
+ 0xf07d, 0xf04a, 0xf04b, 0xf04c, 0xf04d, 0xf04e, 0xf04f, 0xf050,
+ 0xf051, 0xf052, 0xf0b9, 0xf0fb, 0xf0fc, 0xf0f9, 0xf0fa, 0xf0ff,
+ 0xf05c, 0xf0f7, 0xf053, 0xf054, 0xf055, 0xf056, 0xf057, 0xf058,
+ 0xf059, 0xf05a, 0xf0b2, 0xf0d4, 0xf0d6, 0xf0d2, 0xf0d3, 0xf0d5,
+ 0xf030, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf037,
+ 0xf038, 0xf039, 0xf0b3, 0xf0db, 0xf0dc, 0xf0d9, 0xf0da, 0xf000,
+};
+
+static u_short ctrl_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf11f, 0xf120, 0xf121, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf01a, 0xf003, 0xf212, 0xf004, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf109, 0xf10a, 0xf206, 0xf00a, 0xf200, 0xf200,
+};
+
+static u_short shift_ctrl_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf10c, 0xf10d, 0xf10e, 0xf10f, 0xf110, 0xf111, 0xf112,
+ 0xf113, 0xf11e, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf100, 0xf101, 0xf211, 0xf103, 0xf104, 0xf105, 0xf20b,
+ 0xf20a, 0xf108, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+ushort *key_maps[MAX_NR_KEYMAPS] = {
+ plain_map, shift_map, 0, 0,
+ ctrl_map, shift_ctrl_map, 0
+};
+
+unsigned int keymap_count = 4;
+
+
+/*
+ * Philosophy: most people do not define more strings, but they who do
+ * often want quite a lot of string space. So, we statically allocate
+ * the default and allocate dynamically in chunks of 512 bytes.
+ */
+
+char func_buf[] = {
+ '\033', '[', '[', 'A', 0,
+ '\033', '[', '[', 'B', 0,
+ '\033', '[', '[', 'C', 0,
+ '\033', '[', '[', 'D', 0,
+ '\033', '[', '[', 'E', 0,
+ '\033', '[', '1', '7', '~', 0,
+ '\033', '[', '1', '8', '~', 0,
+ '\033', '[', '1', '9', '~', 0,
+ '\033', '[', '2', '0', '~', 0,
+ '\033', '[', '2', '1', '~', 0,
+ '\033', '[', '2', '3', '~', 0,
+ '\033', '[', '2', '4', '~', 0,
+ '\033', '[', '2', '5', '~', 0,
+ '\033', '[', '2', '6', '~', 0,
+ '\033', '[', '2', '8', '~', 0,
+ '\033', '[', '2', '9', '~', 0,
+ '\033', '[', '3', '1', '~', 0,
+ '\033', '[', '3', '2', '~', 0,
+ '\033', '[', '3', '3', '~', 0,
+ '\033', '[', '3', '4', '~', 0,
+};
+
+
+char *funcbufptr = func_buf;
+int funcbufsize = sizeof(func_buf);
+int funcbufleft = 0; /* space left */
+
+char *func_table[MAX_NR_FUNC] = {
+ func_buf + 0,
+ func_buf + 5,
+ func_buf + 10,
+ func_buf + 15,
+ func_buf + 20,
+ func_buf + 25,
+ func_buf + 31,
+ func_buf + 37,
+ func_buf + 43,
+ func_buf + 49,
+ func_buf + 55,
+ func_buf + 61,
+ func_buf + 67,
+ func_buf + 73,
+ func_buf + 79,
+ func_buf + 85,
+ func_buf + 91,
+ func_buf + 97,
+ func_buf + 103,
+ func_buf + 109,
+ 0,
+};
+
+struct kbdiacr accent_table[MAX_DIACR] = {
+ {'^', 'c', '\003'}, {'^', 'd', '\004'},
+ {'^', 'z', '\032'}, {'^', '\012', '\000'},
+};
+
+unsigned int accent_table_size = 4;
diff --git a/drivers/s390/char/defkeymap.map b/drivers/s390/char/defkeymap.map
new file mode 100644
index 000000000000..353b3f268824
--- /dev/null
+++ b/drivers/s390/char/defkeymap.map
@@ -0,0 +1,191 @@
+# Default keymap for 3270 (ebcdic codepage 037).
+keymaps 0-1,4-5
+
+keycode 0 = nul Oslash
+keycode 1 = nul a
+keycode 2 = nul b
+keycode 3 = nul c
+keycode 4 = nul d
+keycode 5 = nul e
+keycode 6 = nul f
+keycode 7 = nul g
+keycode 8 = nul h
+keycode 9 = nul i
+keycode 10 = nul guillemotleft
+keycode 11 = nul guillemotright
+keycode 12 = nul eth
+keycode 13 = nul yacute
+keycode 14 = nul thorn
+keycode 15 = nul plusminus
+keycode 16 = nul degree
+keycode 17 = nul j
+keycode 18 = nul k
+keycode 19 = nul l
+keycode 20 = nul m
+keycode 21 = nul n
+keycode 22 = nul o
+keycode 23 = nul p
+keycode 24 = nul q
+keycode 25 = nul r
+keycode 26 = nul nul
+keycode 27 = nul nul
+keycode 28 = nul ae
+keycode 29 = nul cedilla
+keycode 30 = nul AE
+keycode 31 = nul currency
+keycode 32 = nul mu
+keycode 33 = nul tilde
+keycode 34 = nul s
+keycode 35 = nul t
+keycode 36 = nul u
+keycode 37 = nul v
+keycode 38 = nul w
+keycode 39 = nul x
+keycode 40 = nul y
+keycode 41 = nul z
+keycode 42 = nul exclamdown
+keycode 43 = nul questiondown
+keycode 44 = nul ETH
+keycode 45 = nul Yacute
+keycode 46 = nul THORN
+keycode 47 = nul registered
+keycode 48 = nul dead_circumflex
+keycode 49 = nul sterling
+keycode 50 = nul yen
+keycode 51 = nul periodcentered
+keycode 52 = nul copyright
+keycode 53 = nul section
+keycode 54 = nul paragraph
+keycode 55 = nul onequarter
+keycode 56 = nul onehalf
+keycode 57 = nul threequarters
+keycode 58 = nul bracketleft
+keycode 59 = nul bracketright
+keycode 60 = nul nul
+keycode 61 = nul diaeresis
+keycode 62 = nul acute
+keycode 63 = nul multiply
+keycode 64 = space braceleft
+keycode 65 = nul A
+keycode 66 = acircumflex B
+keycode 67 = adiaeresis C
+keycode 68 = agrave D
+keycode 69 = aacute E
+keycode 70 = atilde F
+keycode 71 = aring G
+keycode 72 = ccedilla H
+keycode 73 = ntilde I
+keycode 74 = cent nul
+keycode 75 = period ocircumflex
+keycode 76 = less odiaeresis
+keycode 77 = parenleft ograve
+keycode 78 = plus oacute
+keycode 79 = bar otilde
+keycode 80 = ampersand braceright
+keycode 81 = eacute J
+keycode 82 = acircumflex K
+keycode 83 = ediaeresis L
+keycode 84 = egrave M
+keycode 85 = iacute N
+keycode 86 = icircumflex O
+keycode 87 = idiaeresis P
+keycode 88 = igrave Q
+keycode 89 = ssharp R
+keycode 90 = exclam onesuperior
+keycode 91 = dollar ucircumflex
+keycode 92 = asterisk udiaeresis
+keycode 93 = parenright ugrave
+keycode 94 = semicolon uacute
+keycode 95 = notsign ydiaeresis
+keycode 96 = minus backslash
+keycode 97 = slash division
+keycode 98 = Acircumflex S
+keycode 99 = Adiaeresis T
+keycode 100 = Agrave U
+keycode 101 = Aacute V
+keycode 102 = Atilde W
+keycode 103 = Aring X
+keycode 104 = Ccedilla Y
+keycode 105 = Ntilde Z
+keycode 106 = brokenbar twosuperior
+keycode 107 = comma Ocircumflex
+keycode 108 = percent Odiaeresis
+keycode 109 = underscore Ograve
+keycode 110 = greater Oacute
+keycode 111 = question Otilde
+keycode 112 = oslash zero
+keycode 113 = Eacute one
+keycode 114 = Ecircumflex two
+keycode 115 = Ediaeresis three
+keycode 116 = Egrave four
+keycode 117 = Iacute five
+keycode 118 = Icircumflex six
+keycode 119 = Idiaeresis seven
+keycode 120 = Igrave eight
+keycode 121 = grave nine
+keycode 122 = colon threesuperior
+keycode 123 = numbersign Ucircumflex
+keycode 124 = at Udiaeresis
+keycode 125 = apostrophe Ugrave
+keycode 126 = equal Uacute
+keycode 127 = quotedbl nul
+
+# AID keys
+control keycode 74 = F22
+control keycode 75 = F23
+control keycode 76 = F24
+control keycode 107 = Control_z # PA3
+control keycode 108 = Control_c # PA1
+control keycode 109 = KeyboardSignal # Clear
+control keycode 110 = Control_d # PA2
+control keycode 122 = F10
+control keycode 123 = F11 # F11
+control keycode 124 = Last_Console # F12
+control keycode 125 = Linefeed
+shift control keycode 65 = F13
+shift control keycode 66 = F14
+shift control keycode 67 = F15
+shift control keycode 68 = F16
+shift control keycode 69 = F17
+shift control keycode 70 = F18
+shift control keycode 71 = F19
+shift control keycode 72 = F20
+shift control keycode 73 = F21
+shift control keycode 113 = F1
+shift control keycode 114 = F2
+shift control keycode 115 = Incr_Console
+shift control keycode 116 = F4
+shift control keycode 117 = F5
+shift control keycode 118 = F6
+shift control keycode 119 = Scroll_Backward
+shift control keycode 120 = Scroll_Forward
+shift control keycode 121 = F9
+
+string F1 = "\033[[A"
+string F2 = "\033[[B"
+string F3 = "\033[[C"
+string F4 = "\033[[D"
+string F5 = "\033[[E"
+string F6 = "\033[17~"
+string F7 = "\033[18~"
+string F8 = "\033[19~"
+string F9 = "\033[20~"
+string F10 = "\033[21~"
+string F11 = "\033[23~"
+string F12 = "\033[24~"
+string F13 = "\033[25~"
+string F14 = "\033[26~"
+string F15 = "\033[28~"
+string F16 = "\033[29~"
+string F17 = "\033[31~"
+string F18 = "\033[32~"
+string F19 = "\033[33~"
+string F20 = "\033[34~"
+# string F21 ??
+# string F22 ??
+# string F23 ??
+# string F24 ??
+compose '^' 'c' to Control_c
+compose '^' 'd' to Control_d
+compose '^' 'z' to Control_z
+compose '^' '\012' to nul
diff --git a/drivers/s390/char/fs3270.c b/drivers/s390/char/fs3270.c
new file mode 100644
index 000000000000..60afcdcf91c2
--- /dev/null
+++ b/drivers/s390/char/fs3270.c
@@ -0,0 +1,373 @@
+/*
+ * drivers/s390/char/fs3270.c
+ * IBM/3270 Driver - fullscreen driver.
+ *
+ * Author(s):
+ * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
+ * Rewritten for 2.5/2.6 by Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ */
+
+#include <linux/config.h>
+#include <linux/bootmem.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/types.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+#include <asm/cpcmd.h>
+#include <asm/ebcdic.h>
+#include <asm/idals.h>
+
+#include "raw3270.h"
+#include "ctrlchar.h"
+
+struct raw3270_fn fs3270_fn;
+
+struct fs3270 {
+ struct raw3270_view view;
+ pid_t fs_pid; /* Pid of controlling program. */
+ int read_command; /* ccw command to use for reads. */
+ int write_command; /* ccw command to use for writes. */
+ int attention; /* Got attention. */
+ struct raw3270_request *clear; /* single clear request. */
+ wait_queue_head_t attn_wait; /* Attention wait queue. */
+};
+
+static void
+fs3270_wake_up(struct raw3270_request *rq, void *data)
+{
+ wake_up((wait_queue_head_t *) data);
+}
+
+static int
+fs3270_do_io(struct raw3270_view *view, struct raw3270_request *rq)
+{
+ wait_queue_head_t wq;
+ int rc;
+
+ init_waitqueue_head(&wq);
+ rq->callback = fs3270_wake_up;
+ rq->callback_data = &wq;
+ rc = raw3270_start(view, rq);
+ if (rc)
+ return rc;
+ /* Started sucessfully. Now wait for completion. */
+ wait_event(wq, raw3270_request_final(rq));
+ return rq->rc;
+}
+
+static void
+fs3270_reset_callback(struct raw3270_request *rq, void *data)
+{
+ raw3270_request_reset(rq);
+}
+
+/*
+ * Switch to the fullscreen view.
+ */
+static int
+fs3270_activate(struct raw3270_view *view)
+{
+ struct fs3270 *fp;
+
+ fp = (struct fs3270 *) view;
+ raw3270_request_set_cmd(fp->clear, TC_EWRITEA);
+ fp->clear->callback = fs3270_reset_callback;
+ return raw3270_start(view, fp->clear);
+}
+
+/*
+ * Shutdown fullscreen view.
+ */
+static void
+fs3270_deactivate(struct raw3270_view *view)
+{
+ // FIXME: is this a good idea? The user program using fullscreen 3270
+ // will die just because a console message appeared. On the other
+ // hand the fullscreen device is unoperational now.
+ struct fs3270 *fp;
+
+ fp = (struct fs3270 *) view;
+ if (fp->fs_pid != 0)
+ kill_proc(fp->fs_pid, SIGHUP, 1);
+ fp->fs_pid = 0;
+}
+
+static int
+fs3270_irq(struct fs3270 *fp, struct raw3270_request *rq, struct irb *irb)
+{
+ /* Handle ATTN. Set indication and wake waiters for attention. */
+ if (irb->scsw.dstat & DEV_STAT_ATTENTION) {
+ fp->attention = 1;
+ wake_up(&fp->attn_wait);
+ }
+
+ if (rq) {
+ if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
+ rq->rc = -EIO;
+ else
+ /* Normal end. Copy residual count. */
+ rq->rescnt = irb->scsw.count;
+ }
+ return RAW3270_IO_DONE;
+}
+
+/*
+ * Process reads from fullscreen 3270.
+ */
+static ssize_t
+fs3270_read(struct file *filp, char *data, size_t count, loff_t *off)
+{
+ struct fs3270 *fp;
+ struct raw3270_request *rq;
+ struct idal_buffer *ib;
+ int rc;
+
+ if (count == 0 || count > 65535)
+ return -EINVAL;
+ fp = filp->private_data;
+ if (!fp)
+ return -ENODEV;
+ ib = idal_buffer_alloc(count, 0);
+ if (!ib)
+ return -ENOMEM;
+ rq = raw3270_request_alloc(0);
+ if (!IS_ERR(rq)) {
+ if (fp->read_command == 0 && fp->write_command != 0)
+ fp->read_command = 6;
+ raw3270_request_set_cmd(rq, fp->read_command ? : 2);
+ raw3270_request_set_idal(rq, ib);
+ wait_event(fp->attn_wait, fp->attention);
+ rc = fs3270_do_io(&fp->view, rq);
+ if (rc == 0 && idal_buffer_to_user(ib, data, count))
+ rc = -EFAULT;
+ raw3270_request_free(rq);
+ } else
+ rc = PTR_ERR(rq);
+ idal_buffer_free(ib);
+ return rc;
+}
+
+/*
+ * Process writes to fullscreen 3270.
+ */
+static ssize_t
+fs3270_write(struct file *filp, const char *data, size_t count, loff_t *off)
+{
+ struct fs3270 *fp;
+ struct raw3270_request *rq;
+ struct idal_buffer *ib;
+ int write_command;
+ int rc;
+
+ fp = filp->private_data;
+ if (!fp)
+ return -ENODEV;
+ ib = idal_buffer_alloc(count, 0);
+ if (!ib)
+ return -ENOMEM;
+ rq = raw3270_request_alloc(0);
+ if (!IS_ERR(rq)) {
+ if (idal_buffer_from_user(ib, data, count) == 0) {
+ write_command = fp->write_command ? : 1;
+ if (write_command == 5)
+ write_command = 13;
+ raw3270_request_set_cmd(rq, write_command);
+ raw3270_request_set_idal(rq, ib);
+ rc = fs3270_do_io(&fp->view, rq);
+ } else
+ rc = -EFAULT;
+ raw3270_request_free(rq);
+ } else
+ rc = PTR_ERR(rq);
+ idal_buffer_free(ib);
+ return rc;
+}
+
+/*
+ * process ioctl commands for the tube driver
+ */
+static int
+fs3270_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct fs3270 *fp;
+ struct raw3270_iocb iocb;
+ int rc;
+
+ fp = filp->private_data;
+ if (!fp)
+ return -ENODEV;
+ rc = 0;
+ switch (cmd) {
+ case TUBICMD:
+ fp->read_command = arg;
+ break;
+ case TUBOCMD:
+ fp->write_command = arg;
+ break;
+ case TUBGETI:
+ rc = put_user(fp->read_command, (char *) arg);
+ break;
+ case TUBGETO:
+ rc = put_user(fp->write_command,(char *) arg);
+ break;
+ case TUBGETMOD:
+ iocb.model = fp->view.model;
+ iocb.line_cnt = fp->view.rows;
+ iocb.col_cnt = fp->view.cols;
+ iocb.pf_cnt = 24;
+ iocb.re_cnt = 20;
+ iocb.map = 0;
+ if (copy_to_user((char *) arg, &iocb,
+ sizeof(struct raw3270_iocb)))
+ rc = -EFAULT;
+ break;
+ }
+ return rc;
+}
+
+/*
+ * Allocate tty3270 structure.
+ */
+static struct fs3270 *
+fs3270_alloc_view(void)
+{
+ struct fs3270 *fp;
+
+ fp = (struct fs3270 *) kmalloc(sizeof(struct fs3270),GFP_KERNEL);
+ if (!fp)
+ return ERR_PTR(-ENOMEM);
+ memset(fp, 0, sizeof(struct fs3270));
+ fp->clear = raw3270_request_alloc(0);
+ if (!IS_ERR(fp->clear)) {
+ kfree(fp);
+ return ERR_PTR(-ENOMEM);
+ }
+ return fp;
+}
+
+/*
+ * Free tty3270 structure.
+ */
+static void
+fs3270_free_view(struct raw3270_view *view)
+{
+ raw3270_request_free(((struct fs3270 *) view)->clear);
+ kfree(view);
+}
+
+/*
+ * Unlink fs3270 data structure from filp.
+ */
+static void
+fs3270_release(struct raw3270_view *view)
+{
+}
+
+/* View to a 3270 device. Can be console, tty or fullscreen. */
+struct raw3270_fn fs3270_fn = {
+ .activate = fs3270_activate,
+ .deactivate = fs3270_deactivate,
+ .intv = (void *) fs3270_irq,
+ .release = fs3270_release,
+ .free = fs3270_free_view
+};
+
+/*
+ * This routine is called whenever a 3270 fullscreen device is opened.
+ */
+static int
+fs3270_open(struct inode *inode, struct file *filp)
+{
+ struct fs3270 *fp;
+ int minor, rc;
+
+ if (imajor(filp->f_dentry->d_inode) != IBM_FS3270_MAJOR)
+ return -ENODEV;
+ minor = iminor(filp->f_dentry->d_inode);
+ /* Check if some other program is already using fullscreen mode. */
+ fp = (struct fs3270 *) raw3270_find_view(&fs3270_fn, minor);
+ if (!IS_ERR(fp)) {
+ raw3270_put_view(&fp->view);
+ return -EBUSY;
+ }
+ /* Allocate fullscreen view structure. */
+ fp = fs3270_alloc_view();
+ if (IS_ERR(fp))
+ return PTR_ERR(fp);
+
+ init_waitqueue_head(&fp->attn_wait);
+ fp->fs_pid = current->pid;
+ rc = raw3270_add_view(&fp->view, &fs3270_fn, minor);
+ if (rc) {
+ fs3270_free_view(&fp->view);
+ return rc;
+ }
+
+ rc = raw3270_activate_view(&fp->view);
+ if (rc) {
+ raw3270_del_view(&fp->view);
+ return rc;
+ }
+ filp->private_data = fp;
+ return 0;
+}
+
+/*
+ * This routine is called when the 3270 tty is closed. We wait
+ * for the remaining request to be completed. Then we clean up.
+ */
+static int
+fs3270_close(struct inode *inode, struct file *filp)
+{
+ struct fs3270 *fp;
+
+ fp = filp->private_data;
+ filp->private_data = 0;
+ if (fp)
+ raw3270_del_view(&fp->view);
+ return 0;
+}
+
+static struct file_operations fs3270_fops = {
+ .owner = THIS_MODULE, /* owner */
+ .read = fs3270_read, /* read */
+ .write = fs3270_write, /* write */
+ .ioctl = fs3270_ioctl, /* ioctl */
+ .open = fs3270_open, /* open */
+ .release = fs3270_close, /* release */
+};
+
+/*
+ * 3270 fullscreen driver initialization.
+ */
+static int __init
+fs3270_init(void)
+{
+ int rc;
+
+ rc = register_chrdev(IBM_FS3270_MAJOR, "fs3270", &fs3270_fops);
+ if (rc) {
+ printk(KERN_ERR "fs3270 can't get major number %d: errno %d\n",
+ IBM_FS3270_MAJOR, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static void __exit
+fs3270_exit(void)
+{
+ unregister_chrdev(IBM_FS3270_MAJOR, "fs3270");
+}
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CHARDEV_MAJOR(IBM_FS3270_MAJOR);
+
+module_init(fs3270_init);
+module_exit(fs3270_exit);
diff --git a/drivers/s390/char/keyboard.c b/drivers/s390/char/keyboard.c
new file mode 100644
index 000000000000..fd43d99b45a3
--- /dev/null
+++ b/drivers/s390/char/keyboard.c
@@ -0,0 +1,519 @@
+/*
+ * drivers/s390/char/keyboard.c
+ * ebcdic keycode functions for s390 console drivers
+ *
+ * S390 version
+ * Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/sysrq.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/kbd_diacr.h>
+#include <asm/uaccess.h>
+
+#include "keyboard.h"
+
+/*
+ * Handler Tables.
+ */
+#define K_HANDLERS\
+ k_self, k_fn, k_spec, k_ignore,\
+ k_dead, k_ignore, k_ignore, k_ignore,\
+ k_ignore, k_ignore, k_ignore, k_ignore,\
+ k_ignore, k_ignore, k_ignore, k_ignore
+
+typedef void (k_handler_fn)(struct kbd_data *, unsigned char);
+static k_handler_fn K_HANDLERS;
+static k_handler_fn *k_handler[16] = { K_HANDLERS };
+
+/* maximum values each key_handler can handle */
+static const int kbd_max_vals[] = {
+ 255, ARRAY_SIZE(func_table) - 1, NR_FN_HANDLER - 1, 0,
+ NR_DEAD - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+static const int KBD_NR_TYPES = ARRAY_SIZE(kbd_max_vals);
+
+static unsigned char ret_diacr[NR_DEAD] = {
+ '`', '\'', '^', '~', '"', ','
+};
+
+/*
+ * Alloc/free of kbd_data structures.
+ */
+struct kbd_data *
+kbd_alloc(void) {
+ struct kbd_data *kbd;
+ int i, len;
+
+ kbd = kmalloc(sizeof(struct kbd_data), GFP_KERNEL);
+ if (!kbd)
+ goto out;
+ memset(kbd, 0, sizeof(struct kbd_data));
+ kbd->key_maps = kmalloc(sizeof(key_maps), GFP_KERNEL);
+ if (!key_maps)
+ goto out_kbd;
+ memset(kbd->key_maps, 0, sizeof(key_maps));
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
+ if (key_maps[i]) {
+ kbd->key_maps[i] =
+ kmalloc(sizeof(u_short)*NR_KEYS, GFP_KERNEL);
+ if (!kbd->key_maps[i])
+ goto out_maps;
+ memcpy(kbd->key_maps[i], key_maps[i],
+ sizeof(u_short)*NR_KEYS);
+ }
+ }
+ kbd->func_table = kmalloc(sizeof(func_table), GFP_KERNEL);
+ if (!kbd->func_table)
+ goto out_maps;
+ memset(kbd->func_table, 0, sizeof(func_table));
+ for (i = 0; i < ARRAY_SIZE(func_table); i++) {
+ if (func_table[i]) {
+ len = strlen(func_table[i]) + 1;
+ kbd->func_table[i] = kmalloc(len, GFP_KERNEL);
+ if (!kbd->func_table[i])
+ goto out_func;
+ memcpy(kbd->func_table[i], func_table[i], len);
+ }
+ }
+ kbd->fn_handler =
+ kmalloc(sizeof(fn_handler_fn *) * NR_FN_HANDLER, GFP_KERNEL);
+ if (!kbd->fn_handler)
+ goto out_func;
+ memset(kbd->fn_handler, 0, sizeof(fn_handler_fn *) * NR_FN_HANDLER);
+ kbd->accent_table =
+ kmalloc(sizeof(struct kbdiacr)*MAX_DIACR, GFP_KERNEL);
+ if (!kbd->accent_table)
+ goto out_fn_handler;
+ memcpy(kbd->accent_table, accent_table,
+ sizeof(struct kbdiacr)*MAX_DIACR);
+ kbd->accent_table_size = accent_table_size;
+ return kbd;
+
+out_fn_handler:
+ kfree(kbd->fn_handler);
+out_func:
+ for (i = 0; i < ARRAY_SIZE(func_table); i++)
+ if (kbd->func_table[i])
+ kfree(kbd->func_table[i]);
+ kfree(kbd->func_table);
+out_maps:
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++)
+ if (kbd->key_maps[i])
+ kfree(kbd->key_maps[i]);
+ kfree(kbd->key_maps);
+out_kbd:
+ kfree(kbd);
+out:
+ return 0;
+}
+
+void
+kbd_free(struct kbd_data *kbd)
+{
+ int i;
+
+ kfree(kbd->accent_table);
+ kfree(kbd->fn_handler);
+ for (i = 0; i < ARRAY_SIZE(func_table); i++)
+ if (kbd->func_table[i])
+ kfree(kbd->func_table[i]);
+ kfree(kbd->func_table);
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++)
+ if (kbd->key_maps[i])
+ kfree(kbd->key_maps[i]);
+ kfree(kbd->key_maps);
+ kfree(kbd);
+}
+
+/*
+ * Generate ascii -> ebcdic translation table from kbd_data.
+ */
+void
+kbd_ascebc(struct kbd_data *kbd, unsigned char *ascebc)
+{
+ unsigned short *keymap, keysym;
+ int i, j, k;
+
+ memset(ascebc, 0x40, 256);
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
+ keymap = kbd->key_maps[i];
+ if (!keymap)
+ continue;
+ for (j = 0; j < NR_KEYS; j++) {
+ k = ((i & 1) << 7) + j;
+ keysym = keymap[j];
+ if (KTYP(keysym) == (KT_LATIN | 0xf0) ||
+ KTYP(keysym) == (KT_LETTER | 0xf0))
+ ascebc[KVAL(keysym)] = k;
+ else if (KTYP(keysym) == (KT_DEAD | 0xf0))
+ ascebc[ret_diacr[KVAL(keysym)]] = k;
+ }
+ }
+}
+
+/*
+ * Generate ebcdic -> ascii translation table from kbd_data.
+ */
+void
+kbd_ebcasc(struct kbd_data *kbd, unsigned char *ebcasc)
+{
+ unsigned short *keymap, keysym;
+ int i, j, k;
+
+ memset(ebcasc, ' ', 256);
+ for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
+ keymap = kbd->key_maps[i];
+ if (!keymap)
+ continue;
+ for (j = 0; j < NR_KEYS; j++) {
+ keysym = keymap[j];
+ k = ((i & 1) << 7) + j;
+ if (KTYP(keysym) == (KT_LATIN | 0xf0) ||
+ KTYP(keysym) == (KT_LETTER | 0xf0))
+ ebcasc[k] = KVAL(keysym);
+ else if (KTYP(keysym) == (KT_DEAD | 0xf0))
+ ebcasc[k] = ret_diacr[KVAL(keysym)];
+ }
+ }
+}
+
+/*
+ * We have a combining character DIACR here, followed by the character CH.
+ * If the combination occurs in the table, return the corresponding value.
+ * Otherwise, if CH is a space or equals DIACR, return DIACR.
+ * Otherwise, conclude that DIACR was not combining after all,
+ * queue it and return CH.
+ */
+static unsigned char
+handle_diacr(struct kbd_data *kbd, unsigned char ch)
+{
+ int i, d;
+
+ d = kbd->diacr;
+ kbd->diacr = 0;
+
+ for (i = 0; i < kbd->accent_table_size; i++) {
+ if (kbd->accent_table[i].diacr == d &&
+ kbd->accent_table[i].base == ch)
+ return kbd->accent_table[i].result;
+ }
+
+ if (ch == ' ' || ch == d)
+ return d;
+
+ kbd_put_queue(kbd->tty, d);
+ return ch;
+}
+
+/*
+ * Handle dead key.
+ */
+static void
+k_dead(struct kbd_data *kbd, unsigned char value)
+{
+ value = ret_diacr[value];
+ kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value);
+}
+
+/*
+ * Normal character handler.
+ */
+static void
+k_self(struct kbd_data *kbd, unsigned char value)
+{
+ if (kbd->diacr)
+ value = handle_diacr(kbd, value);
+ kbd_put_queue(kbd->tty, value);
+}
+
+/*
+ * Special key handlers
+ */
+static void
+k_ignore(struct kbd_data *kbd, unsigned char value)
+{
+}
+
+/*
+ * Function key handler.
+ */
+static void
+k_fn(struct kbd_data *kbd, unsigned char value)
+{
+ if (kbd->func_table[value])
+ kbd_puts_queue(kbd->tty, kbd->func_table[value]);
+}
+
+static void
+k_spec(struct kbd_data *kbd, unsigned char value)
+{
+ if (value >= NR_FN_HANDLER)
+ return;
+ if (kbd->fn_handler[value])
+ kbd->fn_handler[value](kbd);
+}
+
+/*
+ * Put utf8 character to tty flip buffer.
+ * UTF-8 is defined for words of up to 31 bits,
+ * but we need only 16 bits here
+ */
+static void
+to_utf8(struct tty_struct *tty, ushort c)
+{
+ if (c < 0x80)
+ /* 0******* */
+ kbd_put_queue(tty, c);
+ else if (c < 0x800) {
+ /* 110***** 10****** */
+ kbd_put_queue(tty, 0xc0 | (c >> 6));
+ kbd_put_queue(tty, 0x80 | (c & 0x3f));
+ } else {
+ /* 1110**** 10****** 10****** */
+ kbd_put_queue(tty, 0xe0 | (c >> 12));
+ kbd_put_queue(tty, 0x80 | ((c >> 6) & 0x3f));
+ kbd_put_queue(tty, 0x80 | (c & 0x3f));
+ }
+}
+
+/*
+ * Process keycode.
+ */
+void
+kbd_keycode(struct kbd_data *kbd, unsigned int keycode)
+{
+ unsigned short keysym;
+ unsigned char type, value;
+
+ if (!kbd || !kbd->tty)
+ return;
+
+ if (keycode >= 384)
+ keysym = kbd->key_maps[5][keycode - 384];
+ else if (keycode >= 256)
+ keysym = kbd->key_maps[4][keycode - 256];
+ else if (keycode >= 128)
+ keysym = kbd->key_maps[1][keycode - 128];
+ else
+ keysym = kbd->key_maps[0][keycode];
+
+ type = KTYP(keysym);
+ if (type >= 0xf0) {
+ type -= 0xf0;
+ if (type == KT_LETTER)
+ type = KT_LATIN;
+ value = KVAL(keysym);
+#ifdef CONFIG_MAGIC_SYSRQ /* Handle the SysRq Hack */
+ if (kbd->sysrq) {
+ if (kbd->sysrq == K(KT_LATIN, '-')) {
+ kbd->sysrq = 0;
+ handle_sysrq(value, 0, kbd->tty);
+ return;
+ }
+ if (value == '-') {
+ kbd->sysrq = K(KT_LATIN, '-');
+ return;
+ }
+ /* Incomplete sysrq sequence. */
+ (*k_handler[KTYP(kbd->sysrq)])(kbd, KVAL(kbd->sysrq));
+ kbd->sysrq = 0;
+ } else if ((type == KT_LATIN && value == '^') ||
+ (type == KT_DEAD && ret_diacr[value] == '^')) {
+ kbd->sysrq = K(type, value);
+ return;
+ }
+#endif
+ (*k_handler[type])(kbd, value);
+ } else
+ to_utf8(kbd->tty, keysym);
+}
+
+/*
+ * Ioctl stuff.
+ */
+static int
+do_kdsk_ioctl(struct kbd_data *kbd, struct kbentry __user *user_kbe,
+ int cmd, int perm)
+{
+ struct kbentry tmp;
+ ushort *key_map, val, ov;
+
+ if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
+ return -EFAULT;
+#if NR_KEYS < 256
+ if (tmp.kb_index >= NR_KEYS)
+ return -EINVAL;
+#endif
+#if MAX_NR_KEYMAPS < 256
+ if (tmp.kb_table >= MAX_NR_KEYMAPS)
+ return -EINVAL;
+#endif
+
+ switch (cmd) {
+ case KDGKBENT:
+ key_map = kbd->key_maps[tmp.kb_table];
+ if (key_map) {
+ val = U(key_map[tmp.kb_index]);
+ if (KTYP(val) >= KBD_NR_TYPES)
+ val = K_HOLE;
+ } else
+ val = (tmp.kb_index ? K_HOLE : K_NOSUCHMAP);
+ return put_user(val, &user_kbe->kb_value);
+ case KDSKBENT:
+ if (!perm)
+ return -EPERM;
+ if (!tmp.kb_index && tmp.kb_value == K_NOSUCHMAP) {
+ /* disallocate map */
+ key_map = kbd->key_maps[tmp.kb_table];
+ if (key_map) {
+ kbd->key_maps[tmp.kb_table] = 0;
+ kfree(key_map);
+ }
+ break;
+ }
+
+ if (KTYP(tmp.kb_value) >= KBD_NR_TYPES)
+ return -EINVAL;
+ if (KVAL(tmp.kb_value) > kbd_max_vals[KTYP(tmp.kb_value)])
+ return -EINVAL;
+
+ if (!(key_map = kbd->key_maps[tmp.kb_table])) {
+ int j;
+
+ key_map = (ushort *) kmalloc(sizeof(plain_map),
+ GFP_KERNEL);
+ if (!key_map)
+ return -ENOMEM;
+ kbd->key_maps[tmp.kb_table] = key_map;
+ for (j = 0; j < NR_KEYS; j++)
+ key_map[j] = U(K_HOLE);
+ }
+ ov = U(key_map[tmp.kb_index]);
+ if (tmp.kb_value == ov)
+ break; /* nothing to do */
+ /*
+ * Attention Key.
+ */
+ if (((ov == K_SAK) || (tmp.kb_value == K_SAK)) &&
+ !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ key_map[tmp.kb_index] = U(tmp.kb_value);
+ break;
+ }
+ return 0;
+}
+
+static int
+do_kdgkb_ioctl(struct kbd_data *kbd, struct kbsentry __user *u_kbs,
+ int cmd, int perm)
+{
+ unsigned char kb_func;
+ char *p;
+ int len;
+
+ /* Get u_kbs->kb_func. */
+ if (get_user(kb_func, &u_kbs->kb_func))
+ return -EFAULT;
+#if MAX_NR_FUNC < 256
+ if (kb_func >= MAX_NR_FUNC)
+ return -EINVAL;
+#endif
+
+ switch (cmd) {
+ case KDGKBSENT:
+ p = kbd->func_table[kb_func];
+ if (p) {
+ len = strlen(p);
+ if (len >= sizeof(u_kbs->kb_string))
+ len = sizeof(u_kbs->kb_string) - 1;
+ if (copy_to_user(u_kbs->kb_string, p, len))
+ return -EFAULT;
+ } else
+ len = 0;
+ if (put_user('\0', u_kbs->kb_string + len))
+ return -EFAULT;
+ break;
+ case KDSKBSENT:
+ if (!perm)
+ return -EPERM;
+ len = strnlen_user(u_kbs->kb_string,
+ sizeof(u_kbs->kb_string) - 1);
+ p = kmalloc(len, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ if (copy_from_user(p, u_kbs->kb_string, len)) {
+ kfree(p);
+ return -EFAULT;
+ }
+ p[len] = 0;
+ if (kbd->func_table[kb_func])
+ kfree(kbd->func_table[kb_func]);
+ kbd->func_table[kb_func] = p;
+ break;
+ }
+ return 0;
+}
+
+int
+kbd_ioctl(struct kbd_data *kbd, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct kbdiacrs __user *a;
+ void __user *argp;
+ int ct, perm;
+
+ argp = (void __user *)arg;
+
+ /*
+ * To have permissions to do most of the vt ioctls, we either have
+ * to be the owner of the tty, or have CAP_SYS_TTY_CONFIG.
+ */
+ perm = current->signal->tty == kbd->tty || capable(CAP_SYS_TTY_CONFIG);
+ switch (cmd) {
+ case KDGKBTYPE:
+ return put_user(KB_101, (char __user *)argp);
+ case KDGKBENT:
+ case KDSKBENT:
+ return do_kdsk_ioctl(kbd, argp, cmd, perm);
+ case KDGKBSENT:
+ case KDSKBSENT:
+ return do_kdgkb_ioctl(kbd, argp, cmd, perm);
+ case KDGKBDIACR:
+ a = argp;
+
+ if (put_user(kbd->accent_table_size, &a->kb_cnt))
+ return -EFAULT;
+ ct = kbd->accent_table_size;
+ if (copy_to_user(a->kbdiacr, kbd->accent_table,
+ ct * sizeof(struct kbdiacr)))
+ return -EFAULT;
+ return 0;
+ case KDSKBDIACR:
+ a = argp;
+ if (!perm)
+ return -EPERM;
+ if (get_user(ct, &a->kb_cnt))
+ return -EFAULT;
+ if (ct >= MAX_DIACR)
+ return -EINVAL;
+ kbd->accent_table_size = ct;
+ if (copy_from_user(kbd->accent_table, a->kbdiacr,
+ ct * sizeof(struct kbdiacr)))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+EXPORT_SYMBOL(kbd_ioctl);
+EXPORT_SYMBOL(kbd_ascebc);
+EXPORT_SYMBOL(kbd_free);
+EXPORT_SYMBOL(kbd_alloc);
+EXPORT_SYMBOL(kbd_keycode);
diff --git a/drivers/s390/char/keyboard.h b/drivers/s390/char/keyboard.h
new file mode 100644
index 000000000000..3b4da5a9cf79
--- /dev/null
+++ b/drivers/s390/char/keyboard.h
@@ -0,0 +1,57 @@
+/*
+ * drivers/s390/char/keyboard.h
+ * ebcdic keycode functions for s390 console drivers
+ *
+ * Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
+ */
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/keyboard.h>
+
+#define NR_FN_HANDLER 20
+
+struct kbd_data;
+
+typedef void (fn_handler_fn)(struct kbd_data *);
+
+/*
+ * FIXME: explain key_maps tricks.
+ */
+
+struct kbd_data {
+ struct tty_struct *tty;
+ unsigned short **key_maps;
+ char **func_table;
+ fn_handler_fn **fn_handler;
+ struct kbdiacr *accent_table;
+ unsigned int accent_table_size;
+ unsigned char diacr;
+ unsigned short sysrq;
+};
+
+struct kbd_data *kbd_alloc(void);
+void kbd_free(struct kbd_data *);
+void kbd_ascebc(struct kbd_data *, unsigned char *);
+
+void kbd_keycode(struct kbd_data *, unsigned int);
+int kbd_ioctl(struct kbd_data *, struct file *, unsigned int, unsigned long);
+
+/*
+ * Helper Functions.
+ */
+extern inline void
+kbd_put_queue(struct tty_struct *tty, int ch)
+{
+ tty_insert_flip_char(tty, ch, 0);
+ tty_schedule_flip(tty);
+}
+
+extern inline void
+kbd_puts_queue(struct tty_struct *tty, char *cp)
+{
+ while (*cp)
+ tty_insert_flip_char(tty, *cp++, 0);
+ tty_schedule_flip(tty);
+}
diff --git a/drivers/s390/char/monreader.c b/drivers/s390/char/monreader.c
new file mode 100644
index 000000000000..5fd3ad867386
--- /dev/null
+++ b/drivers/s390/char/monreader.c
@@ -0,0 +1,662 @@
+/*
+ * drivers/s390/char/monreader.c
+ *
+ * Character device driver for reading z/VM *MONITOR service records.
+ *
+ * Copyright (C) 2004 IBM Corporation, IBM Deutschland Entwicklung GmbH.
+ *
+ * Author: Gerald Schaefer <geraldsc@de.ibm.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/ctype.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+#include <asm/ebcdic.h>
+#include <asm/extmem.h>
+#include <linux/poll.h>
+#include "../net/iucv.h"
+
+
+//#define MON_DEBUG /* Debug messages on/off */
+
+#define MON_NAME "monreader"
+
+#define P_INFO(x...) printk(KERN_INFO MON_NAME " info: " x)
+#define P_ERROR(x...) printk(KERN_ERR MON_NAME " error: " x)
+#define P_WARNING(x...) printk(KERN_WARNING MON_NAME " warning: " x)
+
+#ifdef MON_DEBUG
+#define P_DEBUG(x...) printk(KERN_DEBUG MON_NAME " debug: " x)
+#else
+#define P_DEBUG(x...) do {} while (0)
+#endif
+
+#define MON_COLLECT_SAMPLE 0x80
+#define MON_COLLECT_EVENT 0x40
+#define MON_SERVICE "*MONITOR"
+#define MON_IN_USE 0x01
+#define MON_MSGLIM 255
+
+static char mon_dcss_name[9] = "MONDCSS\0";
+
+struct mon_msg {
+ u32 pos;
+ u32 mca_offset;
+ iucv_MessagePending local_eib;
+ char msglim_reached;
+ char replied_msglim;
+};
+
+struct mon_private {
+ u16 pathid;
+ iucv_handle_t iucv_handle;
+ struct mon_msg *msg_array[MON_MSGLIM];
+ unsigned int write_index;
+ unsigned int read_index;
+ atomic_t msglim_count;
+ atomic_t read_ready;
+ atomic_t iucv_connected;
+ atomic_t iucv_severed;
+};
+
+static unsigned long mon_in_use = 0;
+
+static unsigned long mon_dcss_start;
+static unsigned long mon_dcss_end;
+
+static DECLARE_WAIT_QUEUE_HEAD(mon_read_wait_queue);
+static DECLARE_WAIT_QUEUE_HEAD(mon_conn_wait_queue);
+
+static u8 iucv_host[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static u8 user_data_connect[16] = {
+ /* Version code, must be 0x01 for shared mode */
+ 0x01,
+ /* what to collect */
+ MON_COLLECT_SAMPLE | MON_COLLECT_EVENT,
+ /* DCSS name in EBCDIC, 8 bytes padded with blanks */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+
+static u8 user_data_sever[16] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+
+
+/******************************************************************************
+ * helper functions *
+ *****************************************************************************/
+/*
+ * Create the 8 bytes EBCDIC DCSS segment name from
+ * an ASCII name, incl. padding
+ */
+static inline void
+dcss_mkname(char *ascii_name, char *ebcdic_name)
+{
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ if (ascii_name[i] == '\0')
+ break;
+ ebcdic_name[i] = toupper(ascii_name[i]);
+ };
+ for (; i < 8; i++)
+ ebcdic_name[i] = ' ';
+ ASCEBC(ebcdic_name, 8);
+}
+
+/*
+ * print appropriate error message for segment_load()/segment_type()
+ * return code
+ */
+static void
+mon_segment_warn(int rc, char* seg_name)
+{
+ switch (rc) {
+ case -ENOENT:
+ P_WARNING("cannot load/query segment %s, does not exist\n",
+ seg_name);
+ break;
+ case -ENOSYS:
+ P_WARNING("cannot load/query segment %s, not running on VM\n",
+ seg_name);
+ break;
+ case -EIO:
+ P_WARNING("cannot load/query segment %s, hardware error\n",
+ seg_name);
+ break;
+ case -ENOTSUPP:
+ P_WARNING("cannot load/query segment %s, is a multi-part "
+ "segment\n", seg_name);
+ break;
+ case -ENOSPC:
+ P_WARNING("cannot load/query segment %s, overlaps with "
+ "storage\n", seg_name);
+ break;
+ case -EBUSY:
+ P_WARNING("cannot load/query segment %s, overlaps with "
+ "already loaded dcss\n", seg_name);
+ break;
+ case -EPERM:
+ P_WARNING("cannot load/query segment %s, already loaded in "
+ "incompatible mode\n", seg_name);
+ break;
+ case -ENOMEM:
+ P_WARNING("cannot load/query segment %s, out of memory\n",
+ seg_name);
+ break;
+ case -ERANGE:
+ P_WARNING("cannot load/query segment %s, exceeds kernel "
+ "mapping range\n", seg_name);
+ break;
+ default:
+ P_WARNING("cannot load/query segment %s, return value %i\n",
+ seg_name, rc);
+ break;
+ }
+}
+
+static inline unsigned long
+mon_mca_start(struct mon_msg *monmsg)
+{
+ return monmsg->local_eib.ln1msg1.iprmmsg1_u32;
+}
+
+static inline unsigned long
+mon_mca_end(struct mon_msg *monmsg)
+{
+ return monmsg->local_eib.ln1msg2.ipbfln1f;
+}
+
+static inline u8
+mon_mca_type(struct mon_msg *monmsg, u8 index)
+{
+ return *((u8 *) mon_mca_start(monmsg) + monmsg->mca_offset + index);
+}
+
+static inline u32
+mon_mca_size(struct mon_msg *monmsg)
+{
+ return mon_mca_end(monmsg) - mon_mca_start(monmsg) + 1;
+}
+
+static inline u32
+mon_rec_start(struct mon_msg *monmsg)
+{
+ return *((u32 *) (mon_mca_start(monmsg) + monmsg->mca_offset + 4));
+}
+
+static inline u32
+mon_rec_end(struct mon_msg *monmsg)
+{
+ return *((u32 *) (mon_mca_start(monmsg) + monmsg->mca_offset + 8));
+}
+
+static inline int
+mon_check_mca(struct mon_msg *monmsg)
+{
+ if ((mon_rec_end(monmsg) <= mon_rec_start(monmsg)) ||
+ (mon_rec_start(monmsg) < mon_dcss_start) ||
+ (mon_rec_end(monmsg) > mon_dcss_end) ||
+ (mon_mca_type(monmsg, 0) == 0) ||
+ (mon_mca_size(monmsg) % 12 != 0) ||
+ (mon_mca_end(monmsg) <= mon_mca_start(monmsg)) ||
+ (mon_mca_end(monmsg) > mon_dcss_end) ||
+ (mon_mca_start(monmsg) < mon_dcss_start) ||
+ ((mon_mca_type(monmsg, 1) == 0) && (mon_mca_type(monmsg, 2) == 0)))
+ {
+ P_DEBUG("READ, IGNORED INVALID MCA\n\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static inline int
+mon_send_reply(struct mon_msg *monmsg, struct mon_private *monpriv)
+{
+ u8 prmmsg[8];
+ int rc;
+
+ P_DEBUG("read, REPLY: pathid = 0x%04X, msgid = 0x%08X, trgcls = "
+ "0x%08X\n\n",
+ monmsg->local_eib.ippathid, monmsg->local_eib.ipmsgid,
+ monmsg->local_eib.iptrgcls);
+ rc = iucv_reply_prmmsg(monmsg->local_eib.ippathid,
+ monmsg->local_eib.ipmsgid,
+ monmsg->local_eib.iptrgcls,
+ 0, prmmsg);
+ atomic_dec(&monpriv->msglim_count);
+ if (likely(!monmsg->msglim_reached)) {
+ monmsg->pos = 0;
+ monmsg->mca_offset = 0;
+ monpriv->read_index = (monpriv->read_index + 1) %
+ MON_MSGLIM;
+ atomic_dec(&monpriv->read_ready);
+ } else
+ monmsg->replied_msglim = 1;
+ if (rc) {
+ P_ERROR("read, IUCV reply failed with rc = %i\n\n", rc);
+ return -EIO;
+ }
+ return 0;
+}
+
+static inline struct mon_private *
+mon_alloc_mem(void)
+{
+ int i,j;
+ struct mon_private *monpriv;
+
+ monpriv = kmalloc(sizeof(struct mon_private), GFP_KERNEL);
+ if (!monpriv) {
+ P_ERROR("no memory for monpriv\n");
+ return NULL;
+ }
+ memset(monpriv, 0, sizeof(struct mon_private));
+ for (i = 0; i < MON_MSGLIM; i++) {
+ monpriv->msg_array[i] = kmalloc(sizeof(struct mon_msg),
+ GFP_KERNEL);
+ if (!monpriv->msg_array[i]) {
+ P_ERROR("open, no memory for msg_array\n");
+ for (j = 0; j < i; j++)
+ kfree(monpriv->msg_array[j]);
+ return NULL;
+ }
+ memset(monpriv->msg_array[i], 0, sizeof(struct mon_msg));
+ }
+ return monpriv;
+}
+
+static inline void
+mon_read_debug(struct mon_msg *monmsg, struct mon_private *monpriv)
+{
+#ifdef MON_DEBUG
+ u8 msg_type[2], mca_type;
+ unsigned long records_len;
+
+ records_len = mon_rec_end(monmsg) - mon_rec_start(monmsg) + 1;
+
+ memcpy(msg_type, &monmsg->local_eib.iptrgcls, 2);
+ EBCASC(msg_type, 2);
+ mca_type = mon_mca_type(monmsg, 0);
+ EBCASC(&mca_type, 1);
+
+ P_DEBUG("read, mon_read_index = %i, mon_write_index = %i\n",
+ monpriv->read_index, monpriv->write_index);
+ P_DEBUG("read, pathid = 0x%04X, msgid = 0x%08X, trgcls = 0x%08X\n",
+ monmsg->local_eib.ippathid, monmsg->local_eib.ipmsgid,
+ monmsg->local_eib.iptrgcls);
+ P_DEBUG("read, msg_type = '%c%c', mca_type = '%c' / 0x%X / 0x%X\n",
+ msg_type[0], msg_type[1], mca_type ? mca_type : 'X',
+ mon_mca_type(monmsg, 1), mon_mca_type(monmsg, 2));
+ P_DEBUG("read, MCA: start = 0x%lX, end = 0x%lX\n",
+ mon_mca_start(monmsg), mon_mca_end(monmsg));
+ P_DEBUG("read, REC: start = 0x%X, end = 0x%X, len = %lu\n\n",
+ mon_rec_start(monmsg), mon_rec_end(monmsg), records_len);
+ if (mon_mca_size(monmsg) > 12)
+ P_DEBUG("READ, MORE THAN ONE MCA\n\n");
+#endif
+}
+
+static inline void
+mon_next_mca(struct mon_msg *monmsg)
+{
+ if (likely((mon_mca_size(monmsg) - monmsg->mca_offset) == 12))
+ return;
+ P_DEBUG("READ, NEXT MCA\n\n");
+ monmsg->mca_offset += 12;
+ monmsg->pos = 0;
+}
+
+static inline struct mon_msg *
+mon_next_message(struct mon_private *monpriv)
+{
+ struct mon_msg *monmsg;
+
+ if (!atomic_read(&monpriv->read_ready))
+ return NULL;
+ monmsg = monpriv->msg_array[monpriv->read_index];
+ if (unlikely(monmsg->replied_msglim)) {
+ monmsg->replied_msglim = 0;
+ monmsg->msglim_reached = 0;
+ monmsg->pos = 0;
+ monmsg->mca_offset = 0;
+ P_WARNING("read, message limit reached\n");
+ monpriv->read_index = (monpriv->read_index + 1) %
+ MON_MSGLIM;
+ atomic_dec(&monpriv->read_ready);
+ return ERR_PTR(-EOVERFLOW);
+ }
+ return monmsg;
+}
+
+
+/******************************************************************************
+ * IUCV handler *
+ *****************************************************************************/
+static void
+mon_iucv_ConnectionComplete(iucv_ConnectionComplete *eib, void *pgm_data)
+{
+ struct mon_private *monpriv = (struct mon_private *) pgm_data;
+
+ P_DEBUG("IUCV connection completed\n");
+ P_DEBUG("IUCV ACCEPT (from *MONITOR): Version = 0x%02X, Event = "
+ "0x%02X, Sample = 0x%02X\n",
+ eib->ipuser[0], eib->ipuser[1], eib->ipuser[2]);
+ atomic_set(&monpriv->iucv_connected, 1);
+ wake_up(&mon_conn_wait_queue);
+}
+
+static void
+mon_iucv_ConnectionSevered(iucv_ConnectionSevered *eib, void *pgm_data)
+{
+ struct mon_private *monpriv = (struct mon_private *) pgm_data;
+
+ P_ERROR("IUCV connection severed with rc = 0x%X\n",
+ (u8) eib->ipuser[0]);
+ atomic_set(&monpriv->iucv_severed, 1);
+ wake_up(&mon_conn_wait_queue);
+ wake_up_interruptible(&mon_read_wait_queue);
+}
+
+static void
+mon_iucv_MessagePending(iucv_MessagePending *eib, void *pgm_data)
+{
+ struct mon_private *monpriv = (struct mon_private *) pgm_data;
+
+ P_DEBUG("IUCV message pending\n");
+ memcpy(&monpriv->msg_array[monpriv->write_index]->local_eib, eib,
+ sizeof(iucv_MessagePending));
+ if (atomic_inc_return(&monpriv->msglim_count) == MON_MSGLIM) {
+ P_WARNING("IUCV message pending, message limit (%i) reached\n",
+ MON_MSGLIM);
+ monpriv->msg_array[monpriv->write_index]->msglim_reached = 1;
+ }
+ monpriv->write_index = (monpriv->write_index + 1) % MON_MSGLIM;
+ atomic_inc(&monpriv->read_ready);
+ wake_up_interruptible(&mon_read_wait_queue);
+}
+
+static iucv_interrupt_ops_t mon_iucvops = {
+ .ConnectionComplete = mon_iucv_ConnectionComplete,
+ .ConnectionSevered = mon_iucv_ConnectionSevered,
+ .MessagePending = mon_iucv_MessagePending,
+};
+
+/******************************************************************************
+ * file operations *
+ *****************************************************************************/
+static int
+mon_open(struct inode *inode, struct file *filp)
+{
+ int rc, i;
+ struct mon_private *monpriv;
+
+ /*
+ * only one user allowed
+ */
+ if (test_and_set_bit(MON_IN_USE, &mon_in_use))
+ return -EBUSY;
+
+ monpriv = mon_alloc_mem();
+ if (!monpriv)
+ return -ENOMEM;
+
+ /*
+ * Register with IUCV and connect to *MONITOR service
+ */
+ monpriv->iucv_handle = iucv_register_program("my_monreader ",
+ MON_SERVICE,
+ NULL,
+ &mon_iucvops,
+ monpriv);
+ if (!monpriv->iucv_handle) {
+ P_ERROR("failed to register with iucv driver\n");
+ rc = -EIO;
+ goto out_error;
+ }
+ P_INFO("open, registered with IUCV\n");
+
+ rc = iucv_connect(&monpriv->pathid, MON_MSGLIM, user_data_connect,
+ MON_SERVICE, iucv_host, IPRMDATA, NULL, NULL,
+ monpriv->iucv_handle, NULL);
+ if (rc) {
+ P_ERROR("iucv connection to *MONITOR failed with "
+ "IPUSER SEVER code = %i\n", rc);
+ rc = -EIO;
+ goto out_unregister;
+ }
+ /*
+ * Wait for connection confirmation
+ */
+ wait_event(mon_conn_wait_queue,
+ atomic_read(&monpriv->iucv_connected) ||
+ atomic_read(&monpriv->iucv_severed));
+ if (atomic_read(&monpriv->iucv_severed)) {
+ atomic_set(&monpriv->iucv_severed, 0);
+ atomic_set(&monpriv->iucv_connected, 0);
+ rc = -EIO;
+ goto out_unregister;
+ }
+ P_INFO("open, established connection to *MONITOR service\n\n");
+ filp->private_data = monpriv;
+ return nonseekable_open(inode, filp);
+
+out_unregister:
+ iucv_unregister_program(monpriv->iucv_handle);
+out_error:
+ for (i = 0; i < MON_MSGLIM; i++)
+ kfree(monpriv->msg_array[i]);
+ kfree(monpriv);
+ clear_bit(MON_IN_USE, &mon_in_use);
+ return rc;
+}
+
+static int
+mon_close(struct inode *inode, struct file *filp)
+{
+ int rc, i;
+ struct mon_private *monpriv = filp->private_data;
+
+ /*
+ * Close IUCV connection and unregister
+ */
+ rc = iucv_sever(monpriv->pathid, user_data_sever);
+ if (rc)
+ P_ERROR("close, iucv_sever failed with rc = %i\n", rc);
+ else
+ P_INFO("close, terminated connection to *MONITOR service\n");
+
+ rc = iucv_unregister_program(monpriv->iucv_handle);
+ if (rc)
+ P_ERROR("close, iucv_unregister failed with rc = %i\n", rc);
+ else
+ P_INFO("close, unregistered with IUCV\n");
+
+ atomic_set(&monpriv->iucv_severed, 0);
+ atomic_set(&monpriv->iucv_connected, 0);
+ atomic_set(&monpriv->read_ready, 0);
+ atomic_set(&monpriv->msglim_count, 0);
+ monpriv->write_index = 0;
+ monpriv->read_index = 0;
+
+ for (i = 0; i < MON_MSGLIM; i++)
+ kfree(monpriv->msg_array[i]);
+ kfree(monpriv);
+ clear_bit(MON_IN_USE, &mon_in_use);
+ return 0;
+}
+
+static ssize_t
+mon_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
+{
+ struct mon_private *monpriv = filp->private_data;
+ struct mon_msg *monmsg;
+ int ret;
+ u32 mce_start;
+
+ monmsg = mon_next_message(monpriv);
+ if (IS_ERR(monmsg))
+ return PTR_ERR(monmsg);
+
+ if (!monmsg) {
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ ret = wait_event_interruptible(mon_read_wait_queue,
+ atomic_read(&monpriv->read_ready) ||
+ atomic_read(&monpriv->iucv_severed));
+ if (ret)
+ return ret;
+ if (unlikely(atomic_read(&monpriv->iucv_severed)))
+ return -EIO;
+ monmsg = monpriv->msg_array[monpriv->read_index];
+ }
+
+ if (!monmsg->pos) {
+ monmsg->pos = mon_mca_start(monmsg) + monmsg->mca_offset;
+ mon_read_debug(monmsg, monpriv);
+ }
+ if (mon_check_mca(monmsg))
+ goto reply;
+
+ /* read monitor control element (12 bytes) first */
+ mce_start = mon_mca_start(monmsg) + monmsg->mca_offset;
+ if ((monmsg->pos >= mce_start) && (monmsg->pos < mce_start + 12)) {
+ count = min(count, (size_t) mce_start + 12 - monmsg->pos);
+ ret = copy_to_user(data, (void *) (unsigned long) monmsg->pos,
+ count);
+ if (ret)
+ return -EFAULT;
+ monmsg->pos += count;
+ if (monmsg->pos == mce_start + 12)
+ monmsg->pos = mon_rec_start(monmsg);
+ goto out_copy;
+ }
+
+ /* read records */
+ if (monmsg->pos <= mon_rec_end(monmsg)) {
+ count = min(count, (size_t) mon_rec_end(monmsg) - monmsg->pos
+ + 1);
+ ret = copy_to_user(data, (void *) (unsigned long) monmsg->pos,
+ count);
+ if (ret)
+ return -EFAULT;
+ monmsg->pos += count;
+ if (monmsg->pos > mon_rec_end(monmsg))
+ mon_next_mca(monmsg);
+ goto out_copy;
+ }
+reply:
+ ret = mon_send_reply(monmsg, monpriv);
+ return ret;
+
+out_copy:
+ *ppos += count;
+ return count;
+}
+
+static unsigned int
+mon_poll(struct file *filp, struct poll_table_struct *p)
+{
+ struct mon_private *monpriv = filp->private_data;
+
+ poll_wait(filp, &mon_read_wait_queue, p);
+ if (unlikely(atomic_read(&monpriv->iucv_severed)))
+ return POLLERR;
+ if (atomic_read(&monpriv->read_ready))
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+static struct file_operations mon_fops = {
+ .owner = THIS_MODULE,
+ .open = &mon_open,
+ .release = &mon_close,
+ .read = &mon_read,
+ .poll = &mon_poll,
+};
+
+static struct miscdevice mon_dev = {
+ .name = "monreader",
+ .devfs_name = "monreader",
+ .fops = &mon_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
+
+/******************************************************************************
+ * module init/exit *
+ *****************************************************************************/
+static int __init
+mon_init(void)
+{
+ int rc;
+
+ if (!MACHINE_IS_VM) {
+ P_ERROR("not running under z/VM, driver not loaded\n");
+ return -ENODEV;
+ }
+
+ rc = segment_type(mon_dcss_name);
+ if (rc < 0) {
+ mon_segment_warn(rc, mon_dcss_name);
+ return rc;
+ }
+ if (rc != SEG_TYPE_SC) {
+ P_ERROR("segment %s has unsupported type, should be SC\n",
+ mon_dcss_name);
+ return -EINVAL;
+ }
+
+ rc = segment_load(mon_dcss_name, SEGMENT_SHARED,
+ &mon_dcss_start, &mon_dcss_end);
+ if (rc < 0) {
+ mon_segment_warn(rc, mon_dcss_name);
+ return -EINVAL;
+ }
+ dcss_mkname(mon_dcss_name, &user_data_connect[8]);
+
+ rc = misc_register(&mon_dev);
+ if (rc < 0 ) {
+ P_ERROR("misc_register failed, rc = %i\n", rc);
+ goto out;
+ }
+ P_INFO("Loaded segment %s from %p to %p, size = %lu Byte\n",
+ mon_dcss_name, (void *) mon_dcss_start, (void *) mon_dcss_end,
+ mon_dcss_end - mon_dcss_start + 1);
+ return 0;
+
+out:
+ segment_unload(mon_dcss_name);
+ return rc;
+}
+
+static void __exit
+mon_exit(void)
+{
+ segment_unload(mon_dcss_name);
+ WARN_ON(misc_deregister(&mon_dev) != 0);
+ return;
+}
+
+
+module_init(mon_init);
+module_exit(mon_exit);
+
+module_param_string(mondcss, mon_dcss_name, 9, 0444);
+MODULE_PARM_DESC(mondcss, "Name of DCSS segment to be used for *MONITOR "
+ "service, max. 8 chars. Default is MONDCSS");
+
+MODULE_AUTHOR("Gerald Schaefer <geraldsc@de.ibm.com>");
+MODULE_DESCRIPTION("Character device driver for reading z/VM "
+ "monitor service records.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/s390/char/raw3270.c b/drivers/s390/char/raw3270.c
new file mode 100644
index 000000000000..8e16a9716686
--- /dev/null
+++ b/drivers/s390/char/raw3270.c
@@ -0,0 +1,1335 @@
+/*
+ * drivers/s390/char/raw3270.c
+ * IBM/3270 Driver - core functions.
+ *
+ * Author(s):
+ * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
+ * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ */
+
+#include <linux/config.h>
+#include <linux/bootmem.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+#include <asm/ebcdic.h>
+
+#include "raw3270.h"
+
+/* The main 3270 data structure. */
+struct raw3270 {
+ struct list_head list;
+ struct ccw_device *cdev;
+ int minor;
+
+ short model, rows, cols;
+ unsigned long flags;
+
+ struct list_head req_queue; /* Request queue. */
+ struct list_head view_list; /* List of available views. */
+ struct raw3270_view *view; /* Active view. */
+
+ struct timer_list timer; /* Device timer. */
+
+ unsigned char *ascebc; /* ascii -> ebcdic table */
+};
+
+/* raw3270->flags */
+#define RAW3270_FLAGS_14BITADDR 0 /* 14-bit buffer addresses */
+#define RAW3270_FLAGS_BUSY 1 /* Device busy, leave it alone */
+#define RAW3270_FLAGS_ATTN 2 /* Device sent an ATTN interrupt */
+#define RAW3270_FLAGS_READY 4 /* Device is useable by views */
+#define RAW3270_FLAGS_CONSOLE 8 /* Device is the console. */
+
+/* Semaphore to protect global data of raw3270 (devices, views, etc). */
+static DECLARE_MUTEX(raw3270_sem);
+
+/* List of 3270 devices. */
+static struct list_head raw3270_devices = LIST_HEAD_INIT(raw3270_devices);
+
+/*
+ * Flag to indicate if the driver has been registered. Some operations
+ * like waiting for the end of i/o need to be done differently as long
+ * as the kernel is still starting up (console support).
+ */
+static int raw3270_registered;
+
+/* Module parameters */
+static int tubxcorrect = 0;
+module_param(tubxcorrect, bool, 0);
+
+/*
+ * Wait queue for device init/delete, view delete.
+ */
+DECLARE_WAIT_QUEUE_HEAD(raw3270_wait_queue);
+
+/*
+ * Encode array for 12 bit 3270 addresses.
+ */
+unsigned char raw3270_ebcgraf[64] = {
+ 0x40, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
+};
+
+void
+raw3270_buffer_address(struct raw3270 *rp, char *cp, unsigned short addr)
+{
+ if (test_bit(RAW3270_FLAGS_14BITADDR, &rp->flags)) {
+ cp[0] = (addr >> 8) & 0x3f;
+ cp[1] = addr & 0xff;
+ } else {
+ cp[0] = raw3270_ebcgraf[(addr >> 6) & 0x3f];
+ cp[1] = raw3270_ebcgraf[addr & 0x3f];
+ }
+}
+
+/*
+ * Allocate a new 3270 ccw request
+ */
+struct raw3270_request *
+raw3270_request_alloc(size_t size)
+{
+ struct raw3270_request *rq;
+
+ /* Allocate request structure */
+ rq = kmalloc(sizeof(struct raw3270_request), GFP_KERNEL | GFP_DMA);
+ if (!rq)
+ return ERR_PTR(-ENOMEM);
+ memset(rq, 0, sizeof(struct raw3270_request));
+
+ /* alloc output buffer. */
+ if (size > 0) {
+ rq->buffer = kmalloc(size, GFP_KERNEL | GFP_DMA);
+ if (!rq->buffer) {
+ kfree(rq);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+ rq->size = size;
+ INIT_LIST_HEAD(&rq->list);
+
+ /*
+ * Setup ccw.
+ */
+ rq->ccw.cda = __pa(rq->buffer);
+ rq->ccw.flags = CCW_FLAG_SLI;
+
+ return rq;
+}
+
+#ifdef CONFIG_TN3270_CONSOLE
+/*
+ * Allocate a new 3270 ccw request from bootmem. Only works very
+ * early in the boot process. Only con3270.c should be using this.
+ */
+struct raw3270_request *
+raw3270_request_alloc_bootmem(size_t size)
+{
+ struct raw3270_request *rq;
+
+ rq = alloc_bootmem_low(sizeof(struct raw3270));
+ if (!rq)
+ return ERR_PTR(-ENOMEM);
+ memset(rq, 0, sizeof(struct raw3270_request));
+
+ /* alloc output buffer. */
+ if (size > 0) {
+ rq->buffer = alloc_bootmem_low(size);
+ if (!rq->buffer) {
+ free_bootmem((unsigned long) rq,
+ sizeof(struct raw3270));
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+ rq->size = size;
+ INIT_LIST_HEAD(&rq->list);
+
+ /*
+ * Setup ccw.
+ */
+ rq->ccw.cda = __pa(rq->buffer);
+ rq->ccw.flags = CCW_FLAG_SLI;
+
+ return rq;
+}
+#endif
+
+/*
+ * Free 3270 ccw request
+ */
+void
+raw3270_request_free (struct raw3270_request *rq)
+{
+ if (rq->buffer)
+ kfree(rq->buffer);
+ kfree(rq);
+}
+
+/*
+ * Reset request to initial state.
+ */
+void
+raw3270_request_reset(struct raw3270_request *rq)
+{
+ BUG_ON(!list_empty(&rq->list));
+ rq->ccw.cmd_code = 0;
+ rq->ccw.count = 0;
+ rq->ccw.cda = __pa(rq->buffer);
+ rq->ccw.flags = CCW_FLAG_SLI;
+ rq->rescnt = 0;
+ rq->rc = 0;
+}
+
+/*
+ * Set command code to ccw of a request.
+ */
+void
+raw3270_request_set_cmd(struct raw3270_request *rq, u8 cmd)
+{
+ rq->ccw.cmd_code = cmd;
+}
+
+/*
+ * Add data fragment to output buffer.
+ */
+int
+raw3270_request_add_data(struct raw3270_request *rq, void *data, size_t size)
+{
+ if (size + rq->ccw.count > rq->size)
+ return -E2BIG;
+ memcpy(rq->buffer + rq->ccw.count, data, size);
+ rq->ccw.count += size;
+ return 0;
+}
+
+/*
+ * Set address/length pair to ccw of a request.
+ */
+void
+raw3270_request_set_data(struct raw3270_request *rq, void *data, size_t size)
+{
+ rq->ccw.cda = __pa(data);
+ rq->ccw.count = size;
+}
+
+/*
+ * Set idal buffer to ccw of a request.
+ */
+void
+raw3270_request_set_idal(struct raw3270_request *rq, struct idal_buffer *ib)
+{
+ rq->ccw.cda = __pa(ib->data);
+ rq->ccw.count = ib->size;
+ rq->ccw.flags |= CCW_FLAG_IDA;
+}
+
+/*
+ * Stop running ccw.
+ */
+static int
+raw3270_halt_io_nolock(struct raw3270 *rp, struct raw3270_request *rq)
+{
+ int retries;
+ int rc;
+
+ if (raw3270_request_final(rq))
+ return 0;
+ /* Check if interrupt has already been processed */
+ for (retries = 0; retries < 5; retries++) {
+ if (retries < 2)
+ rc = ccw_device_halt(rp->cdev, (long) rq);
+ else
+ rc = ccw_device_clear(rp->cdev, (long) rq);
+ if (rc == 0)
+ break; /* termination successful */
+ }
+ return rc;
+}
+
+static int
+raw3270_halt_io(struct raw3270 *rp, struct raw3270_request *rq)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ rc = raw3270_halt_io_nolock(rp, rq);
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+ return rc;
+}
+
+/*
+ * Add the request to the request queue, try to start it if the
+ * 3270 device is idle. Return without waiting for end of i/o.
+ */
+static int
+__raw3270_start(struct raw3270 *rp, struct raw3270_view *view,
+ struct raw3270_request *rq)
+{
+ rq->view = view;
+ raw3270_get_view(view);
+ if (list_empty(&rp->req_queue) &&
+ !test_bit(RAW3270_FLAGS_BUSY, &rp->flags)) {
+ /* No other requests are on the queue. Start this one. */
+ rq->rc = ccw_device_start(rp->cdev, &rq->ccw,
+ (unsigned long) rq, 0, 0);
+ if (rq->rc) {
+ raw3270_put_view(view);
+ return rq->rc;
+ }
+ }
+ list_add_tail(&rq->list, &rp->req_queue);
+ return 0;
+}
+
+int
+raw3270_start(struct raw3270_view *view, struct raw3270_request *rq)
+{
+ unsigned long flags;
+ struct raw3270 *rp;
+ int rc;
+
+ spin_lock_irqsave(get_ccwdev_lock(view->dev->cdev), flags);
+ rp = view->dev;
+ if (!rp || rp->view != view)
+ rc = -EACCES;
+ else if (!test_bit(RAW3270_FLAGS_READY, &rp->flags))
+ rc = -ENODEV;
+ else
+ rc = __raw3270_start(rp, view, rq);
+ spin_unlock_irqrestore(get_ccwdev_lock(view->dev->cdev), flags);
+ return rc;
+}
+
+int
+raw3270_start_irq(struct raw3270_view *view, struct raw3270_request *rq)
+{
+ struct raw3270 *rp;
+
+ rp = view->dev;
+ rq->view = view;
+ raw3270_get_view(view);
+ list_add_tail(&rq->list, &rp->req_queue);
+ return 0;
+}
+
+/*
+ * 3270 interrupt routine, called from the ccw_device layer
+ */
+static void
+raw3270_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
+{
+ struct raw3270 *rp;
+ struct raw3270_view *view;
+ struct raw3270_request *rq;
+ int rc;
+
+ rp = (struct raw3270 *) cdev->dev.driver_data;
+ if (!rp)
+ return;
+ rq = (struct raw3270_request *) intparm;
+ view = rq ? rq->view : rp->view;
+
+ if (IS_ERR(irb))
+ rc = RAW3270_IO_RETRY;
+ else if (irb->scsw.fctl & SCSW_FCTL_HALT_FUNC) {
+ rq->rc = -EIO;
+ rc = RAW3270_IO_DONE;
+ } else if (irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END |
+ DEV_STAT_UNIT_EXCEP)) {
+ /* Handle CE-DE-UE and subsequent UDE */
+ set_bit(RAW3270_FLAGS_BUSY, &rp->flags);
+ rc = RAW3270_IO_BUSY;
+ } else if (test_bit(RAW3270_FLAGS_BUSY, &rp->flags)) {
+ /* Wait for UDE if busy flag is set. */
+ if (irb->scsw.dstat & DEV_STAT_DEV_END) {
+ clear_bit(RAW3270_FLAGS_BUSY, &rp->flags);
+ /* Got it, now retry. */
+ rc = RAW3270_IO_RETRY;
+ } else
+ rc = RAW3270_IO_BUSY;
+ } else if (view)
+ rc = view->fn->intv(view, rq, irb);
+ else
+ rc = RAW3270_IO_DONE;
+
+ switch (rc) {
+ case RAW3270_IO_DONE:
+ break;
+ case RAW3270_IO_BUSY:
+ /*
+ * Intervention required by the operator. We have to wait
+ * for unsolicited device end.
+ */
+ return;
+ case RAW3270_IO_RETRY:
+ if (!rq)
+ break;
+ rq->rc = ccw_device_start(rp->cdev, &rq->ccw,
+ (unsigned long) rq, 0, 0);
+ if (rq->rc == 0)
+ return; /* Sucessfully restarted. */
+ break;
+ case RAW3270_IO_STOP:
+ if (!rq)
+ break;
+ raw3270_halt_io_nolock(rp, rq);
+ rq->rc = -EIO;
+ break;
+ default:
+ BUG();
+ }
+ if (rq) {
+ BUG_ON(list_empty(&rq->list));
+ /* The request completed, remove from queue and do callback. */
+ list_del_init(&rq->list);
+ if (rq->callback)
+ rq->callback(rq, rq->callback_data);
+ /* Do put_device for get_device in raw3270_start. */
+ raw3270_put_view(view);
+ }
+ /*
+ * Try to start each request on request queue until one is
+ * started successful.
+ */
+ while (!list_empty(&rp->req_queue)) {
+ rq = list_entry(rp->req_queue.next,struct raw3270_request,list);
+ rq->rc = ccw_device_start(rp->cdev, &rq->ccw,
+ (unsigned long) rq, 0, 0);
+ if (rq->rc == 0)
+ break;
+ /* Start failed. Remove request and do callback. */
+ list_del_init(&rq->list);
+ if (rq->callback)
+ rq->callback(rq, rq->callback_data);
+ /* Do put_device for get_device in raw3270_start. */
+ raw3270_put_view(view);
+ }
+}
+
+/*
+ * Size sensing.
+ */
+
+struct raw3270_ua { /* Query Reply structure for Usable Area */
+ struct { /* Usable Area Query Reply Base */
+ short l; /* Length of this structured field */
+ char sfid; /* 0x81 if Query Reply */
+ char qcode; /* 0x81 if Usable Area */
+ char flags0;
+ char flags1;
+ short w; /* Width of usable area */
+ short h; /* Heigth of usavle area */
+ char units; /* 0x00:in; 0x01:mm */
+ int xr;
+ int yr;
+ char aw;
+ char ah;
+ short buffsz; /* Character buffer size, bytes */
+ char xmin;
+ char ymin;
+ char xmax;
+ char ymax;
+ } __attribute__ ((packed)) uab;
+ struct { /* Alternate Usable Area Self-Defining Parameter */
+ char l; /* Length of this Self-Defining Parm */
+ char sdpid; /* 0x02 if Alternate Usable Area */
+ char res;
+ char auaid; /* 0x01 is Id for the A U A */
+ short wauai; /* Width of AUAi */
+ short hauai; /* Height of AUAi */
+ char auaunits; /* 0x00:in, 0x01:mm */
+ int auaxr;
+ int auayr;
+ char awauai;
+ char ahauai;
+ } __attribute__ ((packed)) aua;
+} __attribute__ ((packed));
+
+static unsigned char raw3270_init_data[256];
+static struct raw3270_request raw3270_init_request;
+static struct diag210 raw3270_init_diag210;
+static DECLARE_MUTEX(raw3270_init_sem);
+
+static int
+raw3270_init_irq(struct raw3270_view *view, struct raw3270_request *rq,
+ struct irb *irb)
+{
+ /*
+ * Unit-Check Processing:
+ * Expect Command Reject or Intervention Required.
+ */
+ if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK) {
+ /* Request finished abnormally. */
+ if (irb->ecw[0] & SNS0_INTERVENTION_REQ) {
+ set_bit(RAW3270_FLAGS_BUSY, &view->dev->flags);
+ return RAW3270_IO_BUSY;
+ }
+ }
+ if (rq) {
+ if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK) {
+ if (irb->ecw[0] & SNS0_CMD_REJECT)
+ rq->rc = -EOPNOTSUPP;
+ else
+ rq->rc = -EIO;
+ } else
+ /* Request finished normally. Copy residual count. */
+ rq->rescnt = irb->scsw.count;
+ }
+ if (irb->scsw.dstat & DEV_STAT_ATTENTION) {
+ set_bit(RAW3270_FLAGS_ATTN, &view->dev->flags);
+ wake_up(&raw3270_wait_queue);
+ }
+ return RAW3270_IO_DONE;
+}
+
+static struct raw3270_fn raw3270_init_fn = {
+ .intv = raw3270_init_irq
+};
+
+static struct raw3270_view raw3270_init_view = {
+ .fn = &raw3270_init_fn
+};
+
+/*
+ * raw3270_wait/raw3270_wait_interruptible/__raw3270_wakeup
+ * Wait for end of request. The request must have been started
+ * with raw3270_start, rc = 0. The device lock may NOT have been
+ * released between calling raw3270_start and raw3270_wait.
+ */
+static void
+raw3270_wake_init(struct raw3270_request *rq, void *data)
+{
+ wake_up((wait_queue_head_t *) data);
+}
+
+/*
+ * Special wait function that can cope with console initialization.
+ */
+static int
+raw3270_start_init(struct raw3270 *rp, struct raw3270_view *view,
+ struct raw3270_request *rq)
+{
+ unsigned long flags;
+ wait_queue_head_t wq;
+ int rc;
+
+#ifdef CONFIG_TN3270_CONSOLE
+ if (raw3270_registered == 0) {
+ spin_lock_irqsave(get_ccwdev_lock(view->dev->cdev), flags);
+ rq->callback = 0;
+ rc = __raw3270_start(rp, view, rq);
+ if (rc == 0)
+ while (!raw3270_request_final(rq)) {
+ wait_cons_dev();
+ barrier();
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(view->dev->cdev), flags);
+ return rq->rc;
+ }
+#endif
+ init_waitqueue_head(&wq);
+ rq->callback = raw3270_wake_init;
+ rq->callback_data = &wq;
+ spin_lock_irqsave(get_ccwdev_lock(view->dev->cdev), flags);
+ rc = __raw3270_start(rp, view, rq);
+ spin_unlock_irqrestore(get_ccwdev_lock(view->dev->cdev), flags);
+ if (rc)
+ return rc;
+ /* Now wait for the completion. */
+ rc = wait_event_interruptible(wq, raw3270_request_final(rq));
+ if (rc == -ERESTARTSYS) { /* Interrupted by a signal. */
+ raw3270_halt_io(view->dev, rq);
+ /* No wait for the halt to complete. */
+ wait_event(wq, raw3270_request_final(rq));
+ return -ERESTARTSYS;
+ }
+ return rq->rc;
+}
+
+static int
+__raw3270_size_device_vm(struct raw3270 *rp)
+{
+ int rc, model;
+
+ raw3270_init_diag210.vrdcdvno =
+ _ccw_device_get_device_number(rp->cdev);
+ raw3270_init_diag210.vrdclen = sizeof(struct diag210);
+ rc = diag210(&raw3270_init_diag210);
+ if (rc)
+ return rc;
+ model = raw3270_init_diag210.vrdccrmd;
+ switch (model) {
+ case 2:
+ rp->model = model;
+ rp->rows = 24;
+ rp->cols = 80;
+ break;
+ case 3:
+ rp->model = model;
+ rp->rows = 32;
+ rp->cols = 80;
+ break;
+ case 4:
+ rp->model = model;
+ rp->rows = 43;
+ rp->cols = 80;
+ break;
+ case 5:
+ rp->model = model;
+ rp->rows = 27;
+ rp->cols = 132;
+ break;
+ default:
+ printk(KERN_WARNING "vrdccrmd is 0x%.8x\n", model);
+ rc = -EOPNOTSUPP;
+ break;
+ }
+ return rc;
+}
+
+static int
+__raw3270_size_device(struct raw3270 *rp)
+{
+ static const unsigned char wbuf[] =
+ { 0x00, 0x07, 0x01, 0xff, 0x03, 0x00, 0x81 };
+ struct raw3270_ua *uap;
+ unsigned short count;
+ int rc;
+
+ /*
+ * To determine the size of the 3270 device we need to do:
+ * 1) send a 'read partition' data stream to the device
+ * 2) wait for the attn interrupt that preceeds the query reply
+ * 3) do a read modified to get the query reply
+ * To make things worse we have to cope with intervention
+ * required (3270 device switched to 'stand-by') and command
+ * rejects (old devices that can't do 'read partition').
+ */
+ memset(&raw3270_init_request, 0, sizeof(raw3270_init_request));
+ memset(raw3270_init_data, 0, sizeof(raw3270_init_data));
+ /* Store 'read partition' data stream to raw3270_init_data */
+ memcpy(raw3270_init_data, wbuf, sizeof(wbuf));
+ INIT_LIST_HEAD(&raw3270_init_request.list);
+ raw3270_init_request.ccw.cmd_code = TC_WRITESF;
+ raw3270_init_request.ccw.flags = CCW_FLAG_SLI;
+ raw3270_init_request.ccw.count = sizeof(wbuf);
+ raw3270_init_request.ccw.cda = (__u32) __pa(raw3270_init_data);
+
+ rc = raw3270_start_init(rp, &raw3270_init_view, &raw3270_init_request);
+ if (rc) {
+ /* Check error cases: -ERESTARTSYS, -EIO and -EOPNOTSUPP */
+ if (rc == -EOPNOTSUPP && MACHINE_IS_VM)
+ return __raw3270_size_device_vm(rp);
+ return rc;
+ }
+
+ /* Wait for attention interrupt. */
+#ifdef CONFIG_TN3270_CONSOLE
+ if (raw3270_registered == 0) {
+ unsigned long flags;
+
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ while (!test_and_clear_bit(RAW3270_FLAGS_ATTN, &rp->flags))
+ wait_cons_dev();
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+ } else
+#endif
+ rc = wait_event_interruptible(raw3270_wait_queue,
+ test_and_clear_bit(RAW3270_FLAGS_ATTN, &rp->flags));
+ if (rc)
+ return rc;
+
+ /*
+ * The device accepted the 'read partition' command. Now
+ * set up a read ccw and issue it.
+ */
+ raw3270_init_request.ccw.cmd_code = TC_READMOD;
+ raw3270_init_request.ccw.flags = CCW_FLAG_SLI;
+ raw3270_init_request.ccw.count = sizeof(raw3270_init_data);
+ raw3270_init_request.ccw.cda = (__u32) __pa(raw3270_init_data);
+ rc = raw3270_start_init(rp, &raw3270_init_view, &raw3270_init_request);
+ if (rc)
+ return rc;
+ /* Got a Query Reply */
+ count = sizeof(raw3270_init_data) - raw3270_init_request.rescnt;
+ uap = (struct raw3270_ua *) (raw3270_init_data + 1);
+ /* Paranoia check. */
+ if (raw3270_init_data[0] != 0x88 || uap->uab.qcode != 0x81)
+ return -EOPNOTSUPP;
+ /* Copy rows/columns of default Usable Area */
+ rp->rows = uap->uab.h;
+ rp->cols = uap->uab.w;
+ /* Check for 14 bit addressing */
+ if ((uap->uab.flags0 & 0x0d) == 0x01)
+ set_bit(RAW3270_FLAGS_14BITADDR, &rp->flags);
+ /* Check for Alternate Usable Area */
+ if (uap->uab.l == sizeof(struct raw3270_ua) &&
+ uap->aua.sdpid == 0x02) {
+ rp->rows = uap->aua.hauai;
+ rp->cols = uap->aua.wauai;
+ }
+ return 0;
+}
+
+static int
+raw3270_size_device(struct raw3270 *rp)
+{
+ int rc;
+
+ down(&raw3270_init_sem);
+ rp->view = &raw3270_init_view;
+ raw3270_init_view.dev = rp;
+ rc = __raw3270_size_device(rp);
+ raw3270_init_view.dev = 0;
+ rp->view = 0;
+ up(&raw3270_init_sem);
+ if (rc == 0) { /* Found something. */
+ /* Try to find a model. */
+ rp->model = 0;
+ if (rp->rows == 24 && rp->cols == 80)
+ rp->model = 2;
+ if (rp->rows == 32 && rp->cols == 80)
+ rp->model = 3;
+ if (rp->rows == 43 && rp->cols == 80)
+ rp->model = 4;
+ if (rp->rows == 27 && rp->cols == 132)
+ rp->model = 5;
+ }
+ return rc;
+}
+
+static int
+raw3270_reset_device(struct raw3270 *rp)
+{
+ int rc;
+
+ down(&raw3270_init_sem);
+ memset(&raw3270_init_request, 0, sizeof(raw3270_init_request));
+ memset(raw3270_init_data, 0, sizeof(raw3270_init_data));
+ /* Store reset data stream to raw3270_init_data/raw3270_init_request */
+ raw3270_init_data[0] = TW_KR;
+ INIT_LIST_HEAD(&raw3270_init_request.list);
+ raw3270_init_request.ccw.cmd_code = TC_EWRITEA;
+ raw3270_init_request.ccw.flags = CCW_FLAG_SLI;
+ raw3270_init_request.ccw.count = 1;
+ raw3270_init_request.ccw.cda = (__u32) __pa(raw3270_init_data);
+ rp->view = &raw3270_init_view;
+ raw3270_init_view.dev = rp;
+ rc = raw3270_start_init(rp, &raw3270_init_view, &raw3270_init_request);
+ raw3270_init_view.dev = 0;
+ rp->view = 0;
+ up(&raw3270_init_sem);
+ return rc;
+}
+
+/*
+ * Setup new 3270 device.
+ */
+static int
+raw3270_setup_device(struct ccw_device *cdev, struct raw3270 *rp, char *ascebc)
+{
+ struct list_head *l;
+ struct raw3270 *tmp;
+ int minor;
+
+ memset(rp, 0, sizeof(struct raw3270));
+ /* Copy ebcdic -> ascii translation table. */
+ memcpy(ascebc, _ascebc, 256);
+ if (tubxcorrect) {
+ /* correct brackets and circumflex */
+ ascebc['['] = 0xad;
+ ascebc[']'] = 0xbd;
+ ascebc['^'] = 0xb0;
+ }
+ rp->ascebc = ascebc;
+
+ /* Set defaults. */
+ rp->rows = 24;
+ rp->cols = 80;
+
+ INIT_LIST_HEAD(&rp->req_queue);
+ INIT_LIST_HEAD(&rp->view_list);
+
+ /*
+ * Add device to list and find the smallest unused minor
+ * number for it.
+ */
+ down(&raw3270_sem);
+ /* Keep the list sorted. */
+ minor = 0;
+ rp->minor = -1;
+ list_for_each(l, &raw3270_devices) {
+ tmp = list_entry(l, struct raw3270, list);
+ if (tmp->minor > minor) {
+ rp->minor = minor;
+ __list_add(&rp->list, l->prev, l);
+ break;
+ }
+ minor++;
+ }
+ if (rp->minor == -1 && minor < RAW3270_MAXDEVS) {
+ rp->minor = minor;
+ list_add_tail(&rp->list, &raw3270_devices);
+ }
+ up(&raw3270_sem);
+ /* No free minor number? Then give up. */
+ if (rp->minor == -1)
+ return -EUSERS;
+ rp->cdev = cdev;
+ cdev->dev.driver_data = rp;
+ cdev->handler = raw3270_irq;
+ return 0;
+}
+
+#ifdef CONFIG_TN3270_CONSOLE
+/*
+ * Setup 3270 device configured as console.
+ */
+struct raw3270 *
+raw3270_setup_console(struct ccw_device *cdev)
+{
+ struct raw3270 *rp;
+ char *ascebc;
+ int rc;
+
+ rp = (struct raw3270 *) alloc_bootmem(sizeof(struct raw3270));
+ ascebc = (char *) alloc_bootmem(256);
+ rc = raw3270_setup_device(cdev, rp, ascebc);
+ if (rc)
+ return ERR_PTR(rc);
+ set_bit(RAW3270_FLAGS_CONSOLE, &rp->flags);
+ rc = raw3270_reset_device(rp);
+ if (rc)
+ return ERR_PTR(rc);
+ rc = raw3270_size_device(rp);
+ if (rc)
+ return ERR_PTR(rc);
+ rc = raw3270_reset_device(rp);
+ if (rc)
+ return ERR_PTR(rc);
+ set_bit(RAW3270_FLAGS_READY, &rp->flags);
+ return rp;
+}
+
+void
+raw3270_wait_cons_dev(struct raw3270 *rp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ wait_cons_dev();
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+}
+
+#endif
+
+/*
+ * Create a 3270 device structure.
+ */
+static struct raw3270 *
+raw3270_create_device(struct ccw_device *cdev)
+{
+ struct raw3270 *rp;
+ char *ascebc;
+ int rc;
+
+ rp = kmalloc(sizeof(struct raw3270), GFP_KERNEL);
+ if (!rp)
+ return ERR_PTR(-ENOMEM);
+ ascebc = kmalloc(256, GFP_KERNEL);
+ if (!ascebc) {
+ kfree(rp);
+ return ERR_PTR(-ENOMEM);
+ }
+ rc = raw3270_setup_device(cdev, rp, ascebc);
+ if (rc) {
+ kfree(rp->ascebc);
+ kfree(rp);
+ rp = ERR_PTR(rc);
+ }
+ /* Get reference to ccw_device structure. */
+ get_device(&cdev->dev);
+ return rp;
+}
+
+/*
+ * Activate a view.
+ */
+int
+raw3270_activate_view(struct raw3270_view *view)
+{
+ struct raw3270 *rp;
+ struct raw3270_view *oldview, *nv;
+ unsigned long flags;
+ int rc;
+
+ rp = view->dev;
+ if (!rp)
+ return -ENODEV;
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ if (rp->view == view)
+ rc = 0;
+ else if (!test_bit(RAW3270_FLAGS_READY, &rp->flags))
+ rc = -ENODEV;
+ else {
+ oldview = 0;
+ if (rp->view) {
+ oldview = rp->view;
+ oldview->fn->deactivate(oldview);
+ }
+ rp->view = view;
+ rc = view->fn->activate(view);
+ if (rc) {
+ /* Didn't work. Try to reactivate the old view. */
+ rp->view = oldview;
+ if (!oldview || oldview->fn->activate(oldview) != 0) {
+ /* Didn't work as well. Try any other view. */
+ list_for_each_entry(nv, &rp->view_list, list)
+ if (nv != view && nv != oldview) {
+ rp->view = nv;
+ if (nv->fn->activate(nv) == 0)
+ break;
+ rp->view = 0;
+ }
+ }
+ }
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+ return rc;
+}
+
+/*
+ * Deactivate current view.
+ */
+void
+raw3270_deactivate_view(struct raw3270_view *view)
+{
+ unsigned long flags;
+ struct raw3270 *rp;
+
+ rp = view->dev;
+ if (!rp)
+ return;
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ if (rp->view == view) {
+ view->fn->deactivate(view);
+ rp->view = 0;
+ /* Move deactivated view to end of list. */
+ list_del_init(&view->list);
+ list_add_tail(&view->list, &rp->view_list);
+ /* Try to activate another view. */
+ if (test_bit(RAW3270_FLAGS_READY, &rp->flags)) {
+ list_for_each_entry(view, &rp->view_list, list)
+ if (view->fn->activate(view) == 0) {
+ rp->view = view;
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+}
+
+/*
+ * Add view to device with minor "minor".
+ */
+int
+raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor)
+{
+ unsigned long flags;
+ struct raw3270 *rp;
+ int rc;
+
+ down(&raw3270_sem);
+ rc = -ENODEV;
+ list_for_each_entry(rp, &raw3270_devices, list) {
+ if (rp->minor != minor)
+ continue;
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ if (test_bit(RAW3270_FLAGS_READY, &rp->flags)) {
+ atomic_set(&view->ref_count, 2);
+ view->dev = rp;
+ view->fn = fn;
+ view->model = rp->model;
+ view->rows = rp->rows;
+ view->cols = rp->cols;
+ view->ascebc = rp->ascebc;
+ spin_lock_init(&view->lock);
+ list_add_tail(&view->list, &rp->view_list);
+ rc = 0;
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+ break;
+ }
+ up(&raw3270_sem);
+ return rc;
+}
+
+/*
+ * Find specific view of device with minor "minor".
+ */
+struct raw3270_view *
+raw3270_find_view(struct raw3270_fn *fn, int minor)
+{
+ struct raw3270 *rp;
+ struct raw3270_view *view, *tmp;
+ unsigned long flags;
+
+ down(&raw3270_sem);
+ view = ERR_PTR(-ENODEV);
+ list_for_each_entry(rp, &raw3270_devices, list) {
+ if (rp->minor != minor)
+ continue;
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ if (test_bit(RAW3270_FLAGS_READY, &rp->flags)) {
+ view = ERR_PTR(-ENOENT);
+ list_for_each_entry(tmp, &rp->view_list, list) {
+ if (tmp->fn == fn) {
+ raw3270_get_view(tmp);
+ view = tmp;
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+ break;
+ }
+ up(&raw3270_sem);
+ return view;
+}
+
+/*
+ * Remove view from device and free view structure via call to view->fn->free.
+ */
+void
+raw3270_del_view(struct raw3270_view *view)
+{
+ unsigned long flags;
+ struct raw3270 *rp;
+ struct raw3270_view *nv;
+
+ rp = view->dev;
+ spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
+ if (rp->view == view) {
+ view->fn->deactivate(view);
+ rp->view = 0;
+ }
+ list_del_init(&view->list);
+ if (!rp->view && test_bit(RAW3270_FLAGS_READY, &rp->flags)) {
+ /* Try to activate another view. */
+ list_for_each_entry(nv, &rp->view_list, list) {
+ if (nv->fn->activate(view) == 0) {
+ rp->view = nv;
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
+ /* Wait for reference counter to drop to zero. */
+ atomic_dec(&view->ref_count);
+ wait_event(raw3270_wait_queue, atomic_read(&view->ref_count) == 0);
+ if (view->fn->free)
+ view->fn->free(view);
+}
+
+/*
+ * Remove a 3270 device structure.
+ */
+static void
+raw3270_delete_device(struct raw3270 *rp)
+{
+ struct ccw_device *cdev;
+
+ /* Remove from device chain. */
+ down(&raw3270_sem);
+ list_del_init(&rp->list);
+ up(&raw3270_sem);
+
+ /* Disconnect from ccw_device. */
+ cdev = rp->cdev;
+ rp->cdev = 0;
+ cdev->dev.driver_data = 0;
+ cdev->handler = 0;
+
+ /* Put ccw_device structure. */
+ put_device(&cdev->dev);
+
+ /* Now free raw3270 structure. */
+ kfree(rp->ascebc);
+ kfree(rp);
+}
+
+static int
+raw3270_probe (struct ccw_device *cdev)
+{
+ return 0;
+}
+
+/*
+ * Additional attributes for a 3270 device
+ */
+static ssize_t
+raw3270_model_show(struct device *dev, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%i\n",
+ ((struct raw3270 *) dev->driver_data)->model);
+}
+static DEVICE_ATTR(model, 0444, raw3270_model_show, 0);
+
+static ssize_t
+raw3270_rows_show(struct device *dev, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%i\n",
+ ((struct raw3270 *) dev->driver_data)->rows);
+}
+static DEVICE_ATTR(rows, 0444, raw3270_rows_show, 0);
+
+static ssize_t
+raw3270_columns_show(struct device *dev, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%i\n",
+ ((struct raw3270 *) dev->driver_data)->cols);
+}
+static DEVICE_ATTR(columns, 0444, raw3270_columns_show, 0);
+
+static struct attribute * raw3270_attrs[] = {
+ &dev_attr_model.attr,
+ &dev_attr_rows.attr,
+ &dev_attr_columns.attr,
+ NULL,
+};
+
+static struct attribute_group raw3270_attr_group = {
+ .attrs = raw3270_attrs,
+};
+
+static void
+raw3270_create_attributes(struct raw3270 *rp)
+{
+ //FIXME: check return code
+ sysfs_create_group(&rp->cdev->dev.kobj, &raw3270_attr_group);
+}
+
+/*
+ * Notifier for device addition/removal
+ */
+struct raw3270_notifier {
+ struct list_head list;
+ void (*notifier)(int, int);
+};
+
+static struct list_head raw3270_notifier = LIST_HEAD_INIT(raw3270_notifier);
+
+int raw3270_register_notifier(void (*notifier)(int, int))
+{
+ struct raw3270_notifier *np;
+ struct raw3270 *rp;
+
+ np = kmalloc(sizeof(struct raw3270_notifier), GFP_KERNEL);
+ if (!np)
+ return -ENOMEM;
+ np->notifier = notifier;
+ down(&raw3270_sem);
+ list_add_tail(&np->list, &raw3270_notifier);
+ list_for_each_entry(rp, &raw3270_devices, list) {
+ get_device(&rp->cdev->dev);
+ notifier(rp->minor, 1);
+ }
+ up(&raw3270_sem);
+ return 0;
+}
+
+void raw3270_unregister_notifier(void (*notifier)(int, int))
+{
+ struct raw3270_notifier *np;
+
+ down(&raw3270_sem);
+ list_for_each_entry(np, &raw3270_notifier, list)
+ if (np->notifier == notifier) {
+ list_del(&np->list);
+ kfree(np);
+ break;
+ }
+ up(&raw3270_sem);
+}
+
+/*
+ * Set 3270 device online.
+ */
+static int
+raw3270_set_online (struct ccw_device *cdev)
+{
+ struct raw3270 *rp;
+ struct raw3270_notifier *np;
+ int rc;
+
+ rp = raw3270_create_device(cdev);
+ if (IS_ERR(rp))
+ return PTR_ERR(rp);
+ rc = raw3270_reset_device(rp);
+ if (rc)
+ return rc;
+ rc = raw3270_size_device(rp);
+ if (rc)
+ return rc;
+ rc = raw3270_reset_device(rp);
+ if (rc)
+ return rc;
+ raw3270_create_attributes(rp);
+ set_bit(RAW3270_FLAGS_READY, &rp->flags);
+ down(&raw3270_sem);
+ list_for_each_entry(np, &raw3270_notifier, list)
+ np->notifier(rp->minor, 1);
+ up(&raw3270_sem);
+ return 0;
+}
+
+/*
+ * Remove 3270 device structure.
+ */
+static void
+raw3270_remove (struct ccw_device *cdev)
+{
+ unsigned long flags;
+ struct raw3270 *rp;
+ struct raw3270_view *v;
+ struct raw3270_notifier *np;
+
+ rp = cdev->dev.driver_data;
+ clear_bit(RAW3270_FLAGS_READY, &rp->flags);
+
+ sysfs_remove_group(&cdev->dev.kobj, &raw3270_attr_group);
+
+ /* Deactivate current view and remove all views. */
+ spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+ if (rp->view) {
+ rp->view->fn->deactivate(rp->view);
+ rp->view = 0;
+ }
+ while (!list_empty(&rp->view_list)) {
+ v = list_entry(rp->view_list.next, struct raw3270_view, list);
+ if (v->fn->release)
+ v->fn->release(v);
+ spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+ raw3270_del_view(v);
+ spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+ }
+ spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+
+ down(&raw3270_sem);
+ list_for_each_entry(np, &raw3270_notifier, list)
+ np->notifier(rp->minor, 0);
+ up(&raw3270_sem);
+
+ /* Reset 3270 device. */
+ raw3270_reset_device(rp);
+ /* And finally remove it. */
+ raw3270_delete_device(rp);
+}
+
+/*
+ * Set 3270 device offline.
+ */
+static int
+raw3270_set_offline (struct ccw_device *cdev)
+{
+ struct raw3270 *rp;
+
+ rp = cdev->dev.driver_data;
+ if (test_bit(RAW3270_FLAGS_CONSOLE, &rp->flags))
+ return -EBUSY;
+ raw3270_remove(cdev);
+ return 0;
+}
+
+static struct ccw_device_id raw3270_id[] = {
+ { CCW_DEVICE(0x3270, 0) },
+ { CCW_DEVICE(0x3271, 0) },
+ { CCW_DEVICE(0x3272, 0) },
+ { CCW_DEVICE(0x3273, 0) },
+ { CCW_DEVICE(0x3274, 0) },
+ { CCW_DEVICE(0x3275, 0) },
+ { CCW_DEVICE(0x3276, 0) },
+ { CCW_DEVICE(0x3277, 0) },
+ { CCW_DEVICE(0x3278, 0) },
+ { CCW_DEVICE(0x3279, 0) },
+ { CCW_DEVICE(0x3174, 0) },
+ { /* end of list */ },
+};
+
+static struct ccw_driver raw3270_ccw_driver = {
+ .name = "3270",
+ .owner = THIS_MODULE,
+ .ids = raw3270_id,
+ .probe = &raw3270_probe,
+ .remove = &raw3270_remove,
+ .set_online = &raw3270_set_online,
+ .set_offline = &raw3270_set_offline,
+};
+
+static int
+raw3270_init(void)
+{
+ struct raw3270 *rp;
+ int rc;
+
+ if (raw3270_registered)
+ return 0;
+ raw3270_registered = 1;
+ rc = ccw_driver_register(&raw3270_ccw_driver);
+ if (rc == 0) {
+ /* Create attributes for early (= console) device. */
+ down(&raw3270_sem);
+ list_for_each_entry(rp, &raw3270_devices, list) {
+ get_device(&rp->cdev->dev);
+ raw3270_create_attributes(rp);
+ }
+ up(&raw3270_sem);
+ }
+ return rc;
+}
+
+static void
+raw3270_exit(void)
+{
+ ccw_driver_unregister(&raw3270_ccw_driver);
+}
+
+MODULE_LICENSE("GPL");
+
+module_init(raw3270_init);
+module_exit(raw3270_exit);
+
+EXPORT_SYMBOL(raw3270_request_alloc);
+EXPORT_SYMBOL(raw3270_request_free);
+EXPORT_SYMBOL(raw3270_request_reset);
+EXPORT_SYMBOL(raw3270_request_set_cmd);
+EXPORT_SYMBOL(raw3270_request_add_data);
+EXPORT_SYMBOL(raw3270_request_set_data);
+EXPORT_SYMBOL(raw3270_request_set_idal);
+EXPORT_SYMBOL(raw3270_buffer_address);
+EXPORT_SYMBOL(raw3270_add_view);
+EXPORT_SYMBOL(raw3270_del_view);
+EXPORT_SYMBOL(raw3270_find_view);
+EXPORT_SYMBOL(raw3270_activate_view);
+EXPORT_SYMBOL(raw3270_deactivate_view);
+EXPORT_SYMBOL(raw3270_start);
+EXPORT_SYMBOL(raw3270_start_irq);
+EXPORT_SYMBOL(raw3270_register_notifier);
+EXPORT_SYMBOL(raw3270_unregister_notifier);
+EXPORT_SYMBOL(raw3270_wait_queue);
diff --git a/drivers/s390/char/raw3270.h b/drivers/s390/char/raw3270.h
new file mode 100644
index 000000000000..ed5d4eb9f623
--- /dev/null
+++ b/drivers/s390/char/raw3270.h
@@ -0,0 +1,274 @@
+/*
+ * drivers/s390/char/raw3270.h
+ * IBM/3270 Driver
+ *
+ * Author(s):
+ * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
+ * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ */
+
+#include <asm/idals.h>
+#include <asm/ioctl.h>
+
+/* ioctls for fullscreen 3270 */
+#define TUBICMD _IO('3', 3) /* set ccw command for fs reads. */
+#define TUBOCMD _IO('3', 4) /* set ccw command for fs writes. */
+#define TUBGETI _IO('3', 7) /* get ccw command for fs reads. */
+#define TUBGETO _IO('3', 8) /* get ccw command for fs writes. */
+#define TUBSETMOD _IO('3',12) /* FIXME: what does it do ?*/
+#define TUBGETMOD _IO('3',13) /* FIXME: what does it do ?*/
+
+/* Local Channel Commands */
+#define TC_WRITE 0x01 /* Write */
+#define TC_EWRITE 0x05 /* Erase write */
+#define TC_READMOD 0x06 /* Read modified */
+#define TC_EWRITEA 0x0d /* Erase write alternate */
+#define TC_WRITESF 0x11 /* Write structured field */
+
+/* Buffer Control Orders */
+#define TO_SF 0x1d /* Start field */
+#define TO_SBA 0x11 /* Set buffer address */
+#define TO_IC 0x13 /* Insert cursor */
+#define TO_PT 0x05 /* Program tab */
+#define TO_RA 0x3c /* Repeat to address */
+#define TO_SFE 0x29 /* Start field extended */
+#define TO_EUA 0x12 /* Erase unprotected to address */
+#define TO_MF 0x2c /* Modify field */
+#define TO_SA 0x28 /* Set attribute */
+
+/* Field Attribute Bytes */
+#define TF_INPUT 0x40 /* Visible input */
+#define TF_INPUTN 0x4c /* Invisible input */
+#define TF_INMDT 0xc1 /* Visible, Set-MDT */
+#define TF_LOG 0x60
+
+/* Character Attribute Bytes */
+#define TAT_RESET 0x00
+#define TAT_FIELD 0xc0
+#define TAT_EXTHI 0x41
+#define TAT_COLOR 0x42
+#define TAT_CHARS 0x43
+#define TAT_TRANS 0x46
+
+/* Extended-Highlighting Bytes */
+#define TAX_RESET 0x00
+#define TAX_BLINK 0xf1
+#define TAX_REVER 0xf2
+#define TAX_UNDER 0xf4
+
+/* Reset value */
+#define TAR_RESET 0x00
+
+/* Color values */
+#define TAC_RESET 0x00
+#define TAC_BLUE 0xf1
+#define TAC_RED 0xf2
+#define TAC_PINK 0xf3
+#define TAC_GREEN 0xf4
+#define TAC_TURQ 0xf5
+#define TAC_YELLOW 0xf6
+#define TAC_WHITE 0xf7
+#define TAC_DEFAULT 0x00
+
+/* Write Control Characters */
+#define TW_NONE 0x40 /* No particular action */
+#define TW_KR 0xc2 /* Keyboard restore */
+#define TW_PLUSALARM 0x04 /* Add this bit for alarm */
+
+#define RAW3270_MAXDEVS 256
+
+/* For TUBGETMOD and TUBSETMOD. Should include. */
+struct raw3270_iocb {
+ short model;
+ short line_cnt;
+ short col_cnt;
+ short pf_cnt;
+ short re_cnt;
+ short map;
+};
+
+struct raw3270;
+struct raw3270_view;
+
+/* 3270 CCW request */
+struct raw3270_request {
+ struct list_head list; /* list head for request queueing. */
+ struct raw3270_view *view; /* view of this request */
+ struct ccw1 ccw; /* single ccw. */
+ void *buffer; /* output buffer. */
+ size_t size; /* size of output buffer. */
+ int rescnt; /* residual count from devstat. */
+ int rc; /* return code for this request. */
+
+ /* Callback for delivering final status. */
+ void (*callback)(struct raw3270_request *, void *);
+ void *callback_data;
+};
+
+struct raw3270_request *raw3270_request_alloc(size_t size);
+struct raw3270_request *raw3270_request_alloc_bootmem(size_t size);
+void raw3270_request_free(struct raw3270_request *);
+void raw3270_request_reset(struct raw3270_request *);
+void raw3270_request_set_cmd(struct raw3270_request *, u8 cmd);
+int raw3270_request_add_data(struct raw3270_request *, void *, size_t);
+void raw3270_request_set_data(struct raw3270_request *, void *, size_t);
+void raw3270_request_set_idal(struct raw3270_request *, struct idal_buffer *);
+
+static inline int
+raw3270_request_final(struct raw3270_request *rq)
+{
+ return list_empty(&rq->list);
+}
+
+void raw3270_buffer_address(struct raw3270 *, char *, unsigned short);
+
+/* Return value of *intv (see raw3270_fn below) can be one of the following: */
+#define RAW3270_IO_DONE 0 /* request finished */
+#define RAW3270_IO_BUSY 1 /* request still active */
+#define RAW3270_IO_RETRY 2 /* retry current request */
+#define RAW3270_IO_STOP 3 /* kill current request */
+
+/*
+ * Functions of a 3270 view.
+ */
+struct raw3270_fn {
+ int (*activate)(struct raw3270_view *);
+ void (*deactivate)(struct raw3270_view *);
+ int (*intv)(struct raw3270_view *,
+ struct raw3270_request *, struct irb *);
+ void (*release)(struct raw3270_view *);
+ void (*free)(struct raw3270_view *);
+};
+
+/*
+ * View structure chaining. The raw3270_view structure is meant to
+ * be embedded at the start of the real view data structure, e.g.:
+ * struct example {
+ * struct raw3270_view view;
+ * ...
+ * };
+ */
+struct raw3270_view {
+ struct list_head list;
+ spinlock_t lock;
+ atomic_t ref_count;
+ struct raw3270 *dev;
+ struct raw3270_fn *fn;
+ unsigned int model;
+ unsigned int rows, cols; /* # of rows & colums of the view */
+ unsigned char *ascebc; /* ascii -> ebcdic table */
+};
+
+int raw3270_add_view(struct raw3270_view *, struct raw3270_fn *, int);
+int raw3270_activate_view(struct raw3270_view *);
+void raw3270_del_view(struct raw3270_view *);
+void raw3270_deactivate_view(struct raw3270_view *);
+struct raw3270_view *raw3270_find_view(struct raw3270_fn *, int);
+int raw3270_start(struct raw3270_view *, struct raw3270_request *);
+int raw3270_start_irq(struct raw3270_view *, struct raw3270_request *);
+
+/* Reference count inliner for view structures. */
+static inline void
+raw3270_get_view(struct raw3270_view *view)
+{
+ atomic_inc(&view->ref_count);
+}
+
+extern wait_queue_head_t raw3270_wait_queue;
+
+static inline void
+raw3270_put_view(struct raw3270_view *view)
+{
+ if (atomic_dec_return(&view->ref_count) == 0)
+ wake_up(&raw3270_wait_queue);
+}
+
+struct raw3270 *raw3270_setup_console(struct ccw_device *cdev);
+void raw3270_wait_cons_dev(struct raw3270 *);
+
+/* Notifier for device addition/removal */
+int raw3270_register_notifier(void (*notifier)(int, int));
+void raw3270_unregister_notifier(void (*notifier)(int, int));
+
+/*
+ * Little memory allocator for string objects.
+ */
+struct string
+{
+ struct list_head list;
+ struct list_head update;
+ unsigned long size;
+ unsigned long len;
+ char string[0];
+} __attribute__ ((aligned(8)));
+
+static inline struct string *
+alloc_string(struct list_head *free_list, unsigned long len)
+{
+ struct string *cs, *tmp;
+ unsigned long size;
+
+ size = (len + 7L) & -8L;
+ list_for_each_entry(cs, free_list, list) {
+ if (cs->size < size)
+ continue;
+ if (cs->size > size + sizeof(struct string)) {
+ char *endaddr = (char *) (cs + 1) + cs->size;
+ tmp = (struct string *) (endaddr - size) - 1;
+ tmp->size = size;
+ cs->size -= size + sizeof(struct string);
+ cs = tmp;
+ } else
+ list_del(&cs->list);
+ cs->len = len;
+ INIT_LIST_HEAD(&cs->list);
+ INIT_LIST_HEAD(&cs->update);
+ return cs;
+ }
+ return 0;
+}
+
+static inline unsigned long
+free_string(struct list_head *free_list, struct string *cs)
+{
+ struct string *tmp;
+ struct list_head *p, *left;
+
+ /* Find out the left neighbour in free memory list. */
+ left = free_list;
+ list_for_each(p, free_list) {
+ if (list_entry(p, struct string, list) > cs)
+ break;
+ left = p;
+ }
+ /* Try to merge with right neighbour = next element from left. */
+ if (left->next != free_list) {
+ tmp = list_entry(left->next, struct string, list);
+ if ((char *) (cs + 1) + cs->size == (char *) tmp) {
+ list_del(&tmp->list);
+ cs->size += tmp->size + sizeof(struct string);
+ }
+ }
+ /* Try to merge with left neighbour. */
+ if (left != free_list) {
+ tmp = list_entry(left, struct string, list);
+ if ((char *) (tmp + 1) + tmp->size == (char *) cs) {
+ tmp->size += cs->size + sizeof(struct string);
+ return tmp->size;
+ }
+ }
+ __list_add(&cs->list, left, left->next);
+ return cs->size;
+}
+
+static inline void
+add_string_memory(struct list_head *free_list, void *mem, unsigned long size)
+{
+ struct string *cs;
+
+ cs = (struct string *) mem;
+ cs->size = size - sizeof(struct string);
+ free_string(free_list, cs);
+}
+
diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c
new file mode 100644
index 000000000000..ceb0e474fde4
--- /dev/null
+++ b/drivers/s390/char/sclp.c
@@ -0,0 +1,915 @@
+/*
+ * drivers/s390/char/sclp.c
+ * core function to access sclp interface
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/reboot.h>
+#include <linux/jiffies.h>
+#include <asm/types.h>
+#include <asm/s390_ext.h>
+
+#include "sclp.h"
+
+#define SCLP_HEADER "sclp: "
+
+/* Structure for register_early_external_interrupt. */
+static ext_int_info_t ext_int_info_hwc;
+
+/* Lock to protect internal data consistency. */
+static DEFINE_SPINLOCK(sclp_lock);
+
+/* Mask of events that we can receive from the sclp interface. */
+static sccb_mask_t sclp_receive_mask;
+
+/* Mask of events that we can send to the sclp interface. */
+static sccb_mask_t sclp_send_mask;
+
+/* List of registered event listeners and senders. */
+static struct list_head sclp_reg_list;
+
+/* List of queued requests. */
+static struct list_head sclp_req_queue;
+
+/* Data for read and and init requests. */
+static struct sclp_req sclp_read_req;
+static struct sclp_req sclp_init_req;
+static char sclp_read_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
+static char sclp_init_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
+
+/* Timer for request retries. */
+static struct timer_list sclp_request_timer;
+
+/* Internal state: is the driver initialized? */
+static volatile enum sclp_init_state_t {
+ sclp_init_state_uninitialized,
+ sclp_init_state_initializing,
+ sclp_init_state_initialized
+} sclp_init_state = sclp_init_state_uninitialized;
+
+/* Internal state: is a request active at the sclp? */
+static volatile enum sclp_running_state_t {
+ sclp_running_state_idle,
+ sclp_running_state_running
+} sclp_running_state = sclp_running_state_idle;
+
+/* Internal state: is a read request pending? */
+static volatile enum sclp_reading_state_t {
+ sclp_reading_state_idle,
+ sclp_reading_state_reading
+} sclp_reading_state = sclp_reading_state_idle;
+
+/* Internal state: is the driver currently serving requests? */
+static volatile enum sclp_activation_state_t {
+ sclp_activation_state_active,
+ sclp_activation_state_deactivating,
+ sclp_activation_state_inactive,
+ sclp_activation_state_activating
+} sclp_activation_state = sclp_activation_state_active;
+
+/* Internal state: is an init mask request pending? */
+static volatile enum sclp_mask_state_t {
+ sclp_mask_state_idle,
+ sclp_mask_state_initializing
+} sclp_mask_state = sclp_mask_state_idle;
+
+/* Maximum retry counts */
+#define SCLP_INIT_RETRY 3
+#define SCLP_MASK_RETRY 3
+#define SCLP_REQUEST_RETRY 3
+
+/* Timeout intervals in seconds.*/
+#define SCLP_BUSY_INTERVAL 2
+#define SCLP_RETRY_INTERVAL 5
+
+static void sclp_process_queue(void);
+static int sclp_init_mask(int calculate);
+static int sclp_init(void);
+
+/* Perform service call. Return 0 on success, non-zero otherwise. */
+static int
+service_call(sclp_cmdw_t command, void *sccb)
+{
+ int cc;
+
+ __asm__ __volatile__(
+ " .insn rre,0xb2200000,%1,%2\n" /* servc %1,%2 */
+ " ipm %0\n"
+ " srl %0,28"
+ : "=&d" (cc)
+ : "d" (command), "a" (__pa(sccb))
+ : "cc", "memory" );
+ if (cc == 3)
+ return -EIO;
+ if (cc == 2)
+ return -EBUSY;
+ return 0;
+}
+
+/* Request timeout handler. Restart the request queue. If DATA is non-zero,
+ * force restart of running request. */
+static void
+sclp_request_timeout(unsigned long data)
+{
+ unsigned long flags;
+
+ if (data) {
+ spin_lock_irqsave(&sclp_lock, flags);
+ sclp_running_state = sclp_running_state_idle;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ }
+ sclp_process_queue();
+}
+
+/* Set up request retry timer. Called while sclp_lock is locked. */
+static inline void
+__sclp_set_request_timer(unsigned long time, void (*function)(unsigned long),
+ unsigned long data)
+{
+ del_timer(&sclp_request_timer);
+ sclp_request_timer.function = function;
+ sclp_request_timer.data = data;
+ sclp_request_timer.expires = jiffies + time;
+ add_timer(&sclp_request_timer);
+}
+
+/* Try to start a request. Return zero if the request was successfully
+ * started or if it will be started at a later time. Return non-zero otherwise.
+ * Called while sclp_lock is locked. */
+static int
+__sclp_start_request(struct sclp_req *req)
+{
+ int rc;
+
+ if (sclp_running_state != sclp_running_state_idle)
+ return 0;
+ del_timer(&sclp_request_timer);
+ if (req->start_count <= SCLP_REQUEST_RETRY) {
+ rc = service_call(req->command, req->sccb);
+ req->start_count++;
+ } else
+ rc = -EIO;
+ if (rc == 0) {
+ /* Sucessfully started request */
+ req->status = SCLP_REQ_RUNNING;
+ sclp_running_state = sclp_running_state_running;
+ __sclp_set_request_timer(SCLP_RETRY_INTERVAL * HZ,
+ sclp_request_timeout, 1);
+ return 0;
+ } else if (rc == -EBUSY) {
+ /* Try again later */
+ __sclp_set_request_timer(SCLP_BUSY_INTERVAL * HZ,
+ sclp_request_timeout, 0);
+ return 0;
+ }
+ /* Request failed */
+ req->status = SCLP_REQ_FAILED;
+ return rc;
+}
+
+/* Try to start queued requests. */
+static void
+sclp_process_queue(void)
+{
+ struct sclp_req *req;
+ int rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ if (sclp_running_state != sclp_running_state_idle) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return;
+ }
+ del_timer(&sclp_request_timer);
+ while (!list_empty(&sclp_req_queue)) {
+ req = list_entry(sclp_req_queue.next, struct sclp_req, list);
+ rc = __sclp_start_request(req);
+ if (rc == 0)
+ break;
+ /* Request failed. */
+ list_del(&req->list);
+ if (req->callback) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ req->callback(req, req->callback_data);
+ spin_lock_irqsave(&sclp_lock, flags);
+ }
+ }
+ spin_unlock_irqrestore(&sclp_lock, flags);
+}
+
+/* Queue a new request. Return zero on success, non-zero otherwise. */
+int
+sclp_add_request(struct sclp_req *req)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ if ((sclp_init_state != sclp_init_state_initialized ||
+ sclp_activation_state != sclp_activation_state_active) &&
+ req != &sclp_init_req) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return -EIO;
+ }
+ req->status = SCLP_REQ_QUEUED;
+ req->start_count = 0;
+ list_add_tail(&req->list, &sclp_req_queue);
+ rc = 0;
+ /* Start if request is first in list */
+ if (req->list.prev == &sclp_req_queue) {
+ rc = __sclp_start_request(req);
+ if (rc)
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+}
+
+EXPORT_SYMBOL(sclp_add_request);
+
+/* Dispatch events found in request buffer to registered listeners. Return 0
+ * if all events were dispatched, non-zero otherwise. */
+static int
+sclp_dispatch_evbufs(struct sccb_header *sccb)
+{
+ unsigned long flags;
+ struct evbuf_header *evbuf;
+ struct list_head *l;
+ struct sclp_register *reg;
+ int offset;
+ int rc;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ rc = 0;
+ for (offset = sizeof(struct sccb_header); offset < sccb->length;
+ offset += evbuf->length) {
+ /* Search for event handler */
+ evbuf = (struct evbuf_header *) ((addr_t) sccb + offset);
+ reg = NULL;
+ list_for_each(l, &sclp_reg_list) {
+ reg = list_entry(l, struct sclp_register, list);
+ if (reg->receive_mask & (1 << (32 - evbuf->type)))
+ break;
+ else
+ reg = NULL;
+ }
+ if (reg && reg->receiver_fn) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ reg->receiver_fn(evbuf);
+ spin_lock_irqsave(&sclp_lock, flags);
+ } else if (reg == NULL)
+ rc = -ENOSYS;
+ }
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+}
+
+/* Read event data request callback. */
+static void
+sclp_read_cb(struct sclp_req *req, void *data)
+{
+ unsigned long flags;
+ struct sccb_header *sccb;
+
+ sccb = (struct sccb_header *) req->sccb;
+ if (req->status == SCLP_REQ_DONE && (sccb->response_code == 0x20 ||
+ sccb->response_code == 0x220))
+ sclp_dispatch_evbufs(sccb);
+ spin_lock_irqsave(&sclp_lock, flags);
+ sclp_reading_state = sclp_reading_state_idle;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+}
+
+/* Prepare read event data request. Called while sclp_lock is locked. */
+static inline void
+__sclp_make_read_req(void)
+{
+ struct sccb_header *sccb;
+
+ sccb = (struct sccb_header *) sclp_read_sccb;
+ clear_page(sccb);
+ memset(&sclp_read_req, 0, sizeof(struct sclp_req));
+ sclp_read_req.command = SCLP_CMDW_READDATA;
+ sclp_read_req.status = SCLP_REQ_QUEUED;
+ sclp_read_req.start_count = 0;
+ sclp_read_req.callback = sclp_read_cb;
+ sclp_read_req.sccb = sccb;
+ sccb->length = PAGE_SIZE;
+ sccb->function_code = 0;
+ sccb->control_mask[2] = 0x80;
+}
+
+/* Search request list for request with matching sccb. Return request if found,
+ * NULL otherwise. Called while sclp_lock is locked. */
+static inline struct sclp_req *
+__sclp_find_req(u32 sccb)
+{
+ struct list_head *l;
+ struct sclp_req *req;
+
+ list_for_each(l, &sclp_req_queue) {
+ req = list_entry(l, struct sclp_req, list);
+ if (sccb == (u32) (addr_t) req->sccb)
+ return req;
+ }
+ return NULL;
+}
+
+/* Handler for external interruption. Perform request post-processing.
+ * Prepare read event data request if necessary. Start processing of next
+ * request on queue. */
+static void
+sclp_interrupt_handler(struct pt_regs *regs, __u16 code)
+{
+ struct sclp_req *req;
+ u32 finished_sccb;
+ u32 evbuf_pending;
+
+ spin_lock(&sclp_lock);
+ finished_sccb = S390_lowcore.ext_params & 0xfffffff8;
+ evbuf_pending = S390_lowcore.ext_params & 0x3;
+ if (finished_sccb) {
+ req = __sclp_find_req(finished_sccb);
+ if (req) {
+ /* Request post-processing */
+ list_del(&req->list);
+ req->status = SCLP_REQ_DONE;
+ if (req->callback) {
+ spin_unlock(&sclp_lock);
+ req->callback(req, req->callback_data);
+ spin_lock(&sclp_lock);
+ }
+ }
+ sclp_running_state = sclp_running_state_idle;
+ }
+ if (evbuf_pending && sclp_receive_mask != 0 &&
+ sclp_reading_state == sclp_reading_state_idle &&
+ sclp_activation_state == sclp_activation_state_active ) {
+ sclp_reading_state = sclp_reading_state_reading;
+ __sclp_make_read_req();
+ /* Add request to head of queue */
+ list_add(&sclp_read_req.list, &sclp_req_queue);
+ }
+ spin_unlock(&sclp_lock);
+ sclp_process_queue();
+}
+
+/* Return current Time-Of-Day clock. */
+static inline u64
+sclp_get_clock(void)
+{
+ u64 result;
+
+ asm volatile ("STCK 0(%1)" : "=m" (result) : "a" (&(result)) : "cc");
+ return result;
+}
+
+/* Convert interval in jiffies to TOD ticks. */
+static inline u64
+sclp_tod_from_jiffies(unsigned long jiffies)
+{
+ return (u64) (jiffies / HZ) << 32;
+}
+
+/* Wait until a currently running request finished. Note: while this function
+ * is running, no timers are served on the calling CPU. */
+void
+sclp_sync_wait(void)
+{
+ unsigned long psw_mask;
+ unsigned long cr0, cr0_sync;
+ u64 timeout;
+
+ /* We'll be disabling timer interrupts, so we need a custom timeout
+ * mechanism */
+ timeout = 0;
+ if (timer_pending(&sclp_request_timer)) {
+ /* Get timeout TOD value */
+ timeout = sclp_get_clock() +
+ sclp_tod_from_jiffies(sclp_request_timer.expires -
+ jiffies);
+ }
+ /* Prevent bottom half from executing once we force interrupts open */
+ local_bh_disable();
+ /* Enable service-signal interruption, disable timer interrupts */
+ __ctl_store(cr0, 0, 0);
+ cr0_sync = cr0;
+ cr0_sync |= 0x00000200;
+ cr0_sync &= 0xFFFFF3AC;
+ __ctl_load(cr0_sync, 0, 0);
+ asm volatile ("STOSM 0(%1),0x01"
+ : "=m" (psw_mask) : "a" (&psw_mask) : "memory");
+ /* Loop until driver state indicates finished request */
+ while (sclp_running_state != sclp_running_state_idle) {
+ /* Check for expired request timer */
+ if (timer_pending(&sclp_request_timer) &&
+ sclp_get_clock() > timeout &&
+ del_timer(&sclp_request_timer))
+ sclp_request_timer.function(sclp_request_timer.data);
+ barrier();
+ cpu_relax();
+ }
+ /* Restore interrupt settings */
+ asm volatile ("SSM 0(%0)"
+ : : "a" (&psw_mask) : "memory");
+ __ctl_load(cr0, 0, 0);
+ __local_bh_enable();
+}
+
+EXPORT_SYMBOL(sclp_sync_wait);
+
+/* Dispatch changes in send and receive mask to registered listeners. */
+static inline void
+sclp_dispatch_state_change(void)
+{
+ struct list_head *l;
+ struct sclp_register *reg;
+ unsigned long flags;
+ sccb_mask_t receive_mask;
+ sccb_mask_t send_mask;
+
+ do {
+ spin_lock_irqsave(&sclp_lock, flags);
+ reg = NULL;
+ list_for_each(l, &sclp_reg_list) {
+ reg = list_entry(l, struct sclp_register, list);
+ receive_mask = reg->receive_mask & sclp_receive_mask;
+ send_mask = reg->send_mask & sclp_send_mask;
+ if (reg->sclp_receive_mask != receive_mask ||
+ reg->sclp_send_mask != send_mask) {
+ reg->sclp_receive_mask = receive_mask;
+ reg->sclp_send_mask = send_mask;
+ break;
+ } else
+ reg = NULL;
+ }
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ if (reg && reg->state_change_fn)
+ reg->state_change_fn(reg);
+ } while (reg);
+}
+
+struct sclp_statechangebuf {
+ struct evbuf_header header;
+ u8 validity_sclp_active_facility_mask : 1;
+ u8 validity_sclp_receive_mask : 1;
+ u8 validity_sclp_send_mask : 1;
+ u8 validity_read_data_function_mask : 1;
+ u16 _zeros : 12;
+ u16 mask_length;
+ u64 sclp_active_facility_mask;
+ sccb_mask_t sclp_receive_mask;
+ sccb_mask_t sclp_send_mask;
+ u32 read_data_function_mask;
+} __attribute__((packed));
+
+
+/* State change event callback. Inform listeners of changes. */
+static void
+sclp_state_change_cb(struct evbuf_header *evbuf)
+{
+ unsigned long flags;
+ struct sclp_statechangebuf *scbuf;
+
+ scbuf = (struct sclp_statechangebuf *) evbuf;
+ if (scbuf->mask_length != sizeof(sccb_mask_t))
+ return;
+ spin_lock_irqsave(&sclp_lock, flags);
+ if (scbuf->validity_sclp_receive_mask)
+ sclp_receive_mask = scbuf->sclp_receive_mask;
+ if (scbuf->validity_sclp_send_mask)
+ sclp_send_mask = scbuf->sclp_send_mask;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ sclp_dispatch_state_change();
+}
+
+static struct sclp_register sclp_state_change_event = {
+ .receive_mask = EvTyp_StateChange_Mask,
+ .receiver_fn = sclp_state_change_cb
+};
+
+/* Calculate receive and send mask of currently registered listeners.
+ * Called while sclp_lock is locked. */
+static inline void
+__sclp_get_mask(sccb_mask_t *receive_mask, sccb_mask_t *send_mask)
+{
+ struct list_head *l;
+ struct sclp_register *t;
+
+ *receive_mask = 0;
+ *send_mask = 0;
+ list_for_each(l, &sclp_reg_list) {
+ t = list_entry(l, struct sclp_register, list);
+ *receive_mask |= t->receive_mask;
+ *send_mask |= t->send_mask;
+ }
+}
+
+/* Register event listener. Return 0 on success, non-zero otherwise. */
+int
+sclp_register(struct sclp_register *reg)
+{
+ unsigned long flags;
+ sccb_mask_t receive_mask;
+ sccb_mask_t send_mask;
+ int rc;
+
+ rc = sclp_init();
+ if (rc)
+ return rc;
+ spin_lock_irqsave(&sclp_lock, flags);
+ /* Check event mask for collisions */
+ __sclp_get_mask(&receive_mask, &send_mask);
+ if (reg->receive_mask & receive_mask || reg->send_mask & send_mask) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return -EBUSY;
+ }
+ /* Trigger initial state change callback */
+ reg->sclp_receive_mask = 0;
+ reg->sclp_send_mask = 0;
+ list_add(&reg->list, &sclp_reg_list);
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ rc = sclp_init_mask(1);
+ if (rc) {
+ spin_lock_irqsave(&sclp_lock, flags);
+ list_del(&reg->list);
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ }
+ return rc;
+}
+
+EXPORT_SYMBOL(sclp_register);
+
+/* Unregister event listener. */
+void
+sclp_unregister(struct sclp_register *reg)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ list_del(&reg->list);
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ sclp_init_mask(1);
+}
+
+EXPORT_SYMBOL(sclp_unregister);
+
+/* Remove event buffers which are marked processed. Return the number of
+ * remaining event buffers. */
+int
+sclp_remove_processed(struct sccb_header *sccb)
+{
+ struct evbuf_header *evbuf;
+ int unprocessed;
+ u16 remaining;
+
+ evbuf = (struct evbuf_header *) (sccb + 1);
+ unprocessed = 0;
+ remaining = sccb->length - sizeof(struct sccb_header);
+ while (remaining > 0) {
+ remaining -= evbuf->length;
+ if (evbuf->flags & 0x80) {
+ sccb->length -= evbuf->length;
+ memcpy(evbuf, (void *) ((addr_t) evbuf + evbuf->length),
+ remaining);
+ } else {
+ unprocessed++;
+ evbuf = (struct evbuf_header *)
+ ((addr_t) evbuf + evbuf->length);
+ }
+ }
+ return unprocessed;
+}
+
+EXPORT_SYMBOL(sclp_remove_processed);
+
+struct init_sccb {
+ struct sccb_header header;
+ u16 _reserved;
+ u16 mask_length;
+ sccb_mask_t receive_mask;
+ sccb_mask_t send_mask;
+ sccb_mask_t sclp_send_mask;
+ sccb_mask_t sclp_receive_mask;
+} __attribute__((packed));
+
+/* Prepare init mask request. Called while sclp_lock is locked. */
+static inline void
+__sclp_make_init_req(u32 receive_mask, u32 send_mask)
+{
+ struct init_sccb *sccb;
+
+ sccb = (struct init_sccb *) sclp_init_sccb;
+ clear_page(sccb);
+ memset(&sclp_init_req, 0, sizeof(struct sclp_req));
+ sclp_init_req.command = SCLP_CMDW_WRITEMASK;
+ sclp_init_req.status = SCLP_REQ_FILLED;
+ sclp_init_req.start_count = 0;
+ sclp_init_req.callback = NULL;
+ sclp_init_req.callback_data = NULL;
+ sclp_init_req.sccb = sccb;
+ sccb->header.length = sizeof(struct init_sccb);
+ sccb->mask_length = sizeof(sccb_mask_t);
+ sccb->receive_mask = receive_mask;
+ sccb->send_mask = send_mask;
+ sccb->sclp_receive_mask = 0;
+ sccb->sclp_send_mask = 0;
+}
+
+/* Start init mask request. If calculate is non-zero, calculate the mask as
+ * requested by registered listeners. Use zero mask otherwise. Return 0 on
+ * success, non-zero otherwise. */
+static int
+sclp_init_mask(int calculate)
+{
+ unsigned long flags;
+ struct init_sccb *sccb = (struct init_sccb *) sclp_init_sccb;
+ sccb_mask_t receive_mask;
+ sccb_mask_t send_mask;
+ int retry;
+ int rc;
+ unsigned long wait;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ /* Check if interface is in appropriate state */
+ if (sclp_mask_state != sclp_mask_state_idle) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return -EBUSY;
+ }
+ if (sclp_activation_state == sclp_activation_state_inactive) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return -EINVAL;
+ }
+ sclp_mask_state = sclp_mask_state_initializing;
+ /* Determine mask */
+ if (calculate)
+ __sclp_get_mask(&receive_mask, &send_mask);
+ else {
+ receive_mask = 0;
+ send_mask = 0;
+ }
+ rc = -EIO;
+ for (retry = 0; retry <= SCLP_MASK_RETRY; retry++) {
+ /* Prepare request */
+ __sclp_make_init_req(receive_mask, send_mask);
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ if (sclp_add_request(&sclp_init_req)) {
+ /* Try again later */
+ wait = jiffies + SCLP_BUSY_INTERVAL * HZ;
+ while (time_before(jiffies, wait))
+ sclp_sync_wait();
+ spin_lock_irqsave(&sclp_lock, flags);
+ continue;
+ }
+ while (sclp_init_req.status != SCLP_REQ_DONE &&
+ sclp_init_req.status != SCLP_REQ_FAILED)
+ sclp_sync_wait();
+ spin_lock_irqsave(&sclp_lock, flags);
+ if (sclp_init_req.status == SCLP_REQ_DONE &&
+ sccb->header.response_code == 0x20) {
+ /* Successful request */
+ if (calculate) {
+ sclp_receive_mask = sccb->sclp_receive_mask;
+ sclp_send_mask = sccb->sclp_send_mask;
+ } else {
+ sclp_receive_mask = 0;
+ sclp_send_mask = 0;
+ }
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ sclp_dispatch_state_change();
+ spin_lock_irqsave(&sclp_lock, flags);
+ rc = 0;
+ break;
+ }
+ }
+ sclp_mask_state = sclp_mask_state_idle;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+}
+
+/* Deactivate SCLP interface. On success, new requests will be rejected,
+ * events will no longer be dispatched. Return 0 on success, non-zero
+ * otherwise. */
+int
+sclp_deactivate(void)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ /* Deactivate can only be called when active */
+ if (sclp_activation_state != sclp_activation_state_active) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return -EINVAL;
+ }
+ sclp_activation_state = sclp_activation_state_deactivating;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ rc = sclp_init_mask(0);
+ spin_lock_irqsave(&sclp_lock, flags);
+ if (rc == 0)
+ sclp_activation_state = sclp_activation_state_inactive;
+ else
+ sclp_activation_state = sclp_activation_state_active;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+}
+
+EXPORT_SYMBOL(sclp_deactivate);
+
+/* Reactivate SCLP interface after sclp_deactivate. On success, new
+ * requests will be accepted, events will be dispatched again. Return 0 on
+ * success, non-zero otherwise. */
+int
+sclp_reactivate(void)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ /* Reactivate can only be called when inactive */
+ if (sclp_activation_state != sclp_activation_state_inactive) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return -EINVAL;
+ }
+ sclp_activation_state = sclp_activation_state_activating;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ rc = sclp_init_mask(1);
+ spin_lock_irqsave(&sclp_lock, flags);
+ if (rc == 0)
+ sclp_activation_state = sclp_activation_state_active;
+ else
+ sclp_activation_state = sclp_activation_state_inactive;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+}
+
+EXPORT_SYMBOL(sclp_reactivate);
+
+/* Handler for external interruption used during initialization. Modify
+ * request state to done. */
+static void
+sclp_check_handler(struct pt_regs *regs, __u16 code)
+{
+ u32 finished_sccb;
+
+ finished_sccb = S390_lowcore.ext_params & 0xfffffff8;
+ /* Is this the interrupt we are waiting for? */
+ if (finished_sccb == 0)
+ return;
+ if (finished_sccb != (u32) (addr_t) sclp_init_sccb) {
+ printk(KERN_WARNING SCLP_HEADER "unsolicited interrupt "
+ "for buffer at 0x%x\n", finished_sccb);
+ return;
+ }
+ spin_lock(&sclp_lock);
+ if (sclp_running_state == sclp_running_state_running) {
+ sclp_init_req.status = SCLP_REQ_DONE;
+ sclp_running_state = sclp_running_state_idle;
+ }
+ spin_unlock(&sclp_lock);
+}
+
+/* Initial init mask request timed out. Modify request state to failed. */
+static void
+sclp_check_timeout(unsigned long data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ if (sclp_running_state == sclp_running_state_running) {
+ sclp_init_req.status = SCLP_REQ_FAILED;
+ sclp_running_state = sclp_running_state_idle;
+ }
+ spin_unlock_irqrestore(&sclp_lock, flags);
+}
+
+/* Perform a check of the SCLP interface. Return zero if the interface is
+ * available and there are no pending requests from a previous instance.
+ * Return non-zero otherwise. */
+static int
+sclp_check_interface(void)
+{
+ struct init_sccb *sccb;
+ unsigned long flags;
+ int retry;
+ int rc;
+
+ spin_lock_irqsave(&sclp_lock, flags);
+ /* Prepare init mask command */
+ rc = register_early_external_interrupt(0x2401, sclp_check_handler,
+ &ext_int_info_hwc);
+ if (rc) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+ }
+ for (retry = 0; retry <= SCLP_INIT_RETRY; retry++) {
+ __sclp_make_init_req(0, 0);
+ sccb = (struct init_sccb *) sclp_init_req.sccb;
+ rc = service_call(sclp_init_req.command, sccb);
+ if (rc == -EIO)
+ break;
+ sclp_init_req.status = SCLP_REQ_RUNNING;
+ sclp_running_state = sclp_running_state_running;
+ __sclp_set_request_timer(SCLP_RETRY_INTERVAL * HZ,
+ sclp_check_timeout, 0);
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ /* Enable service-signal interruption - needs to happen
+ * with IRQs enabled. */
+ ctl_set_bit(0, 9);
+ /* Wait for signal from interrupt or timeout */
+ sclp_sync_wait();
+ /* Disable service-signal interruption - needs to happen
+ * with IRQs enabled. */
+ ctl_clear_bit(0,9);
+ spin_lock_irqsave(&sclp_lock, flags);
+ del_timer(&sclp_request_timer);
+ if (sclp_init_req.status == SCLP_REQ_DONE &&
+ sccb->header.response_code == 0x20) {
+ rc = 0;
+ break;
+ } else
+ rc = -EBUSY;
+ }
+ unregister_early_external_interrupt(0x2401, sclp_check_handler,
+ &ext_int_info_hwc);
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+}
+
+/* Reboot event handler. Reset send and receive mask to prevent pending SCLP
+ * events from interfering with rebooted system. */
+static int
+sclp_reboot_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ sclp_deactivate();
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sclp_reboot_notifier = {
+ .notifier_call = sclp_reboot_event
+};
+
+/* Initialize SCLP driver. Return zero if driver is operational, non-zero
+ * otherwise. */
+static int
+sclp_init(void)
+{
+ unsigned long flags;
+ int rc;
+
+ if (!MACHINE_HAS_SCLP)
+ return -ENODEV;
+ spin_lock_irqsave(&sclp_lock, flags);
+ /* Check for previous or running initialization */
+ if (sclp_init_state != sclp_init_state_uninitialized) {
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return 0;
+ }
+ sclp_init_state = sclp_init_state_initializing;
+ /* Set up variables */
+ INIT_LIST_HEAD(&sclp_req_queue);
+ INIT_LIST_HEAD(&sclp_reg_list);
+ list_add(&sclp_state_change_event.list, &sclp_reg_list);
+ init_timer(&sclp_request_timer);
+ /* Check interface */
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ rc = sclp_check_interface();
+ spin_lock_irqsave(&sclp_lock, flags);
+ if (rc) {
+ sclp_init_state = sclp_init_state_uninitialized;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+ }
+ /* Register reboot handler */
+ rc = register_reboot_notifier(&sclp_reboot_notifier);
+ if (rc) {
+ sclp_init_state = sclp_init_state_uninitialized;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+ }
+ /* Register interrupt handler */
+ rc = register_early_external_interrupt(0x2401, sclp_interrupt_handler,
+ &ext_int_info_hwc);
+ if (rc) {
+ unregister_reboot_notifier(&sclp_reboot_notifier);
+ sclp_init_state = sclp_init_state_uninitialized;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ return rc;
+ }
+ sclp_init_state = sclp_init_state_initialized;
+ spin_unlock_irqrestore(&sclp_lock, flags);
+ /* Enable service-signal external interruption - needs to happen with
+ * IRQs enabled. */
+ ctl_set_bit(0, 9);
+ sclp_init_mask(1);
+ return 0;
+}
diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h
new file mode 100644
index 000000000000..2c71d6ee7b5b
--- /dev/null
+++ b/drivers/s390/char/sclp.h
@@ -0,0 +1,159 @@
+/*
+ * drivers/s390/char/sclp.h
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#ifndef __SCLP_H__
+#define __SCLP_H__
+
+#include <linux/types.h>
+#include <linux/list.h>
+
+#include <asm/ebcdic.h>
+
+/* maximum number of pages concerning our own memory management */
+#define MAX_KMEM_PAGES (sizeof(unsigned long) << 3)
+#define MAX_CONSOLE_PAGES 4
+
+#define EvTyp_OpCmd 0x01
+#define EvTyp_Msg 0x02
+#define EvTyp_StateChange 0x08
+#define EvTyp_PMsgCmd 0x09
+#define EvTyp_CntlProgOpCmd 0x20
+#define EvTyp_CntlProgIdent 0x0B
+#define EvTyp_SigQuiesce 0x1D
+#define EvTyp_VT220Msg 0x1A
+
+#define EvTyp_OpCmd_Mask 0x80000000
+#define EvTyp_Msg_Mask 0x40000000
+#define EvTyp_StateChange_Mask 0x01000000
+#define EvTyp_PMsgCmd_Mask 0x00800000
+#define EvTyp_CtlProgOpCmd_Mask 0x00000001
+#define EvTyp_CtlProgIdent_Mask 0x00200000
+#define EvTyp_SigQuiesce_Mask 0x00000008
+#define EvTyp_VT220Msg_Mask 0x00000040
+
+#define GnrlMsgFlgs_DOM 0x8000
+#define GnrlMsgFlgs_SndAlrm 0x4000
+#define GnrlMsgFlgs_HoldMsg 0x2000
+
+#define LnTpFlgs_CntlText 0x8000
+#define LnTpFlgs_LabelText 0x4000
+#define LnTpFlgs_DataText 0x2000
+#define LnTpFlgs_EndText 0x1000
+#define LnTpFlgs_PromptText 0x0800
+
+typedef unsigned int sclp_cmdw_t;
+
+#define SCLP_CMDW_READDATA 0x00770005
+#define SCLP_CMDW_WRITEDATA 0x00760005
+#define SCLP_CMDW_WRITEMASK 0x00780005
+
+#define GDS_ID_MDSMU 0x1310
+#define GDS_ID_MDSRouteInfo 0x1311
+#define GDS_ID_AgUnWrkCorr 0x1549
+#define GDS_ID_SNACondReport 0x1532
+#define GDS_ID_CPMSU 0x1212
+#define GDS_ID_RoutTargInstr 0x154D
+#define GDS_ID_OpReq 0x8070
+#define GDS_ID_TextCmd 0x1320
+
+#define GDS_KEY_SelfDefTextMsg 0x31
+
+typedef u32 sccb_mask_t; /* ATTENTION: assumes 32bit mask !!! */
+
+struct sccb_header {
+ u16 length;
+ u8 function_code;
+ u8 control_mask[3];
+ u16 response_code;
+} __attribute__((packed));
+
+struct gds_subvector {
+ u8 length;
+ u8 key;
+} __attribute__((packed));
+
+struct gds_vector {
+ u16 length;
+ u16 gds_id;
+} __attribute__((packed));
+
+struct evbuf_header {
+ u16 length;
+ u8 type;
+ u8 flags;
+ u16 _reserved;
+} __attribute__((packed));
+
+struct sclp_req {
+ struct list_head list; /* list_head for request queueing. */
+ sclp_cmdw_t command; /* sclp command to execute */
+ void *sccb; /* pointer to the sccb to execute */
+ char status; /* status of this request */
+ int start_count; /* number of SVCs done for this req */
+ /* Callback that is called after reaching final status. */
+ void (*callback)(struct sclp_req *, void *data);
+ void *callback_data;
+};
+
+#define SCLP_REQ_FILLED 0x00 /* request is ready to be processed */
+#define SCLP_REQ_QUEUED 0x01 /* request is queued to be processed */
+#define SCLP_REQ_RUNNING 0x02 /* request is currently running */
+#define SCLP_REQ_DONE 0x03 /* request is completed successfully */
+#define SCLP_REQ_FAILED 0x05 /* request is finally failed */
+
+/* function pointers that a high level driver has to use for registration */
+/* of some routines it wants to be called from the low level driver */
+struct sclp_register {
+ struct list_head list;
+ /* event masks this user is registered for */
+ sccb_mask_t receive_mask;
+ sccb_mask_t send_mask;
+ /* actually present events */
+ sccb_mask_t sclp_receive_mask;
+ sccb_mask_t sclp_send_mask;
+ /* called if event type availability changes */
+ void (*state_change_fn)(struct sclp_register *);
+ /* called for events in cp_receive_mask/sclp_receive_mask */
+ void (*receiver_fn)(struct evbuf_header *);
+};
+
+/* externals from sclp.c */
+int sclp_add_request(struct sclp_req *req);
+void sclp_sync_wait(void);
+int sclp_register(struct sclp_register *reg);
+void sclp_unregister(struct sclp_register *reg);
+int sclp_remove_processed(struct sccb_header *sccb);
+int sclp_deactivate(void);
+int sclp_reactivate(void);
+
+/* useful inlines */
+
+/* VM uses EBCDIC 037, LPAR+native(SE+HMC) use EBCDIC 500 */
+/* translate single character from ASCII to EBCDIC */
+static inline unsigned char
+sclp_ascebc(unsigned char ch)
+{
+ return (MACHINE_IS_VM) ? _ascebc[ch] : _ascebc_500[ch];
+}
+
+/* translate string from EBCDIC to ASCII */
+static inline void
+sclp_ebcasc_str(unsigned char *str, int nr)
+{
+ (MACHINE_IS_VM) ? EBCASC(str, nr) : EBCASC_500(str, nr);
+}
+
+/* translate string from ASCII to EBCDIC */
+static inline void
+sclp_ascebc_str(unsigned char *str, int nr)
+{
+ (MACHINE_IS_VM) ? ASCEBC(str, nr) : ASCEBC_500(str, nr);
+}
+
+#endif /* __SCLP_H__ */
diff --git a/drivers/s390/char/sclp_con.c b/drivers/s390/char/sclp_con.c
new file mode 100644
index 000000000000..10ef22f13541
--- /dev/null
+++ b/drivers/s390/char/sclp_con.c
@@ -0,0 +1,252 @@
+/*
+ * drivers/s390/char/sclp_con.c
+ * SCLP line mode console driver
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/kmod.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/bootmem.h>
+#include <linux/err.h>
+
+#include "sclp.h"
+#include "sclp_rw.h"
+#include "sclp_tty.h"
+
+#define SCLP_CON_PRINT_HEADER "sclp console driver: "
+
+#define sclp_console_major 4 /* TTYAUX_MAJOR */
+#define sclp_console_minor 64
+#define sclp_console_name "ttyS"
+
+/* Lock to guard over changes to global variables */
+static spinlock_t sclp_con_lock;
+/* List of free pages that can be used for console output buffering */
+static struct list_head sclp_con_pages;
+/* List of full struct sclp_buffer structures ready for output */
+static struct list_head sclp_con_outqueue;
+/* Counter how many buffers are emitted (max 1) and how many */
+/* are on the output queue. */
+static int sclp_con_buffer_count;
+/* Pointer to current console buffer */
+static struct sclp_buffer *sclp_conbuf;
+/* Timer for delayed output of console messages */
+static struct timer_list sclp_con_timer;
+
+/* Output format for console messages */
+static unsigned short sclp_con_columns;
+static unsigned short sclp_con_width_htab;
+
+static void
+sclp_conbuf_callback(struct sclp_buffer *buffer, int rc)
+{
+ unsigned long flags;
+ void *page;
+
+ do {
+ page = sclp_unmake_buffer(buffer);
+ spin_lock_irqsave(&sclp_con_lock, flags);
+ /* Remove buffer from outqueue */
+ list_del(&buffer->list);
+ sclp_con_buffer_count--;
+ list_add_tail((struct list_head *) page, &sclp_con_pages);
+ /* Check if there is a pending buffer on the out queue. */
+ buffer = NULL;
+ if (!list_empty(&sclp_con_outqueue))
+ buffer = list_entry(sclp_con_outqueue.next,
+ struct sclp_buffer, list);
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+ } while (buffer && sclp_emit_buffer(buffer, sclp_conbuf_callback));
+}
+
+static inline void
+sclp_conbuf_emit(void)
+{
+ struct sclp_buffer* buffer;
+ unsigned long flags;
+ int count;
+ int rc;
+
+ spin_lock_irqsave(&sclp_con_lock, flags);
+ buffer = sclp_conbuf;
+ sclp_conbuf = NULL;
+ if (buffer == NULL) {
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+ return;
+ }
+ list_add_tail(&buffer->list, &sclp_con_outqueue);
+ count = sclp_con_buffer_count++;
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+ if (count)
+ return;
+ rc = sclp_emit_buffer(buffer, sclp_conbuf_callback);
+ if (rc)
+ sclp_conbuf_callback(buffer, rc);
+}
+
+/*
+ * When this routine is called from the timer then we flush the
+ * temporary write buffer without further waiting on a final new line.
+ */
+static void
+sclp_console_timeout(unsigned long data)
+{
+ sclp_conbuf_emit();
+}
+
+/*
+ * Writes the given message to S390 system console
+ */
+static void
+sclp_console_write(struct console *console, const char *message,
+ unsigned int count)
+{
+ unsigned long flags;
+ void *page;
+ int written;
+
+ if (count == 0)
+ return;
+ spin_lock_irqsave(&sclp_con_lock, flags);
+ /*
+ * process escape characters, write message into buffer,
+ * send buffer to SCLP
+ */
+ do {
+ /* make sure we have a console output buffer */
+ if (sclp_conbuf == NULL) {
+ while (list_empty(&sclp_con_pages)) {
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+ sclp_sync_wait();
+ spin_lock_irqsave(&sclp_con_lock, flags);
+ }
+ page = sclp_con_pages.next;
+ list_del((struct list_head *) page);
+ sclp_conbuf = sclp_make_buffer(page, sclp_con_columns,
+ sclp_con_width_htab);
+ }
+ /* try to write the string to the current output buffer */
+ written = sclp_write(sclp_conbuf, (const unsigned char *)
+ message, count);
+ if (written == count)
+ break;
+ /*
+ * Not all characters could be written to the current
+ * output buffer. Emit the buffer, create a new buffer
+ * and then output the rest of the string.
+ */
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+ sclp_conbuf_emit();
+ spin_lock_irqsave(&sclp_con_lock, flags);
+ message += written;
+ count -= written;
+ } while (count > 0);
+ /* Setup timer to output current console buffer after 1/10 second */
+ if (sclp_conbuf != NULL && sclp_chars_in_buffer(sclp_conbuf) != 0 &&
+ !timer_pending(&sclp_con_timer)) {
+ init_timer(&sclp_con_timer);
+ sclp_con_timer.function = sclp_console_timeout;
+ sclp_con_timer.data = 0UL;
+ sclp_con_timer.expires = jiffies + HZ/10;
+ add_timer(&sclp_con_timer);
+ }
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+}
+
+static struct tty_driver *
+sclp_console_device(struct console *c, int *index)
+{
+ *index = c->index;
+ return sclp_tty_driver;
+}
+
+/*
+ * This routine is called from panic when the kernel
+ * is going to give up. We have to make sure that all buffers
+ * will be flushed to the SCLP.
+ */
+static void
+sclp_console_unblank(void)
+{
+ unsigned long flags;
+
+ sclp_conbuf_emit();
+ spin_lock_irqsave(&sclp_con_lock, flags);
+ if (timer_pending(&sclp_con_timer))
+ del_timer(&sclp_con_timer);
+ while (sclp_con_buffer_count > 0) {
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+ sclp_sync_wait();
+ spin_lock_irqsave(&sclp_con_lock, flags);
+ }
+ spin_unlock_irqrestore(&sclp_con_lock, flags);
+}
+
+/*
+ * used to register the SCLP console to the kernel and to
+ * give printk necessary information
+ */
+static struct console sclp_console =
+{
+ .name = sclp_console_name,
+ .write = sclp_console_write,
+ .device = sclp_console_device,
+ .unblank = sclp_console_unblank,
+ .flags = CON_PRINTBUFFER,
+ .index = 0 /* ttyS0 */
+};
+
+/*
+ * called by console_init() in drivers/char/tty_io.c at boot-time.
+ */
+static int __init
+sclp_console_init(void)
+{
+ void *page;
+ int i;
+ int rc;
+
+ if (!CONSOLE_IS_SCLP)
+ return 0;
+ rc = sclp_rw_init();
+ if (rc)
+ return rc;
+ /* Allocate pages for output buffering */
+ INIT_LIST_HEAD(&sclp_con_pages);
+ for (i = 0; i < MAX_CONSOLE_PAGES; i++) {
+ page = alloc_bootmem_low_pages(PAGE_SIZE);
+ if (page == NULL)
+ return -ENOMEM;
+ list_add_tail((struct list_head *) page, &sclp_con_pages);
+ }
+ INIT_LIST_HEAD(&sclp_con_outqueue);
+ spin_lock_init(&sclp_con_lock);
+ sclp_con_buffer_count = 0;
+ sclp_conbuf = NULL;
+ init_timer(&sclp_con_timer);
+
+ /* Set output format */
+ if (MACHINE_IS_VM)
+ /*
+ * save 4 characters for the CPU number
+ * written at start of each line by VM/CP
+ */
+ sclp_con_columns = 76;
+ else
+ sclp_con_columns = 80;
+ sclp_con_width_htab = 8;
+
+ /* enable printk-access to this driver */
+ register_console(&sclp_console);
+ return 0;
+}
+
+console_initcall(sclp_console_init);
diff --git a/drivers/s390/char/sclp_cpi.c b/drivers/s390/char/sclp_cpi.c
new file mode 100644
index 000000000000..5a6cef2dfa13
--- /dev/null
+++ b/drivers/s390/char/sclp_cpi.c
@@ -0,0 +1,254 @@
+/*
+ * Author: Martin Peschke <mpeschke@de.ibm.com>
+ * Copyright (C) 2001 IBM Entwicklung GmbH, IBM Corporation
+ *
+ * SCLP Control-Program Identification.
+ */
+
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/kmod.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <asm/ebcdic.h>
+#include <asm/semaphore.h>
+
+#include "sclp.h"
+#include "sclp_rw.h"
+
+#define CPI_LENGTH_SYSTEM_TYPE 8
+#define CPI_LENGTH_SYSTEM_NAME 8
+#define CPI_LENGTH_SYSPLEX_NAME 8
+
+struct cpi_evbuf {
+ struct evbuf_header header;
+ u8 id_format;
+ u8 reserved0;
+ u8 system_type[CPI_LENGTH_SYSTEM_TYPE];
+ u64 reserved1;
+ u8 system_name[CPI_LENGTH_SYSTEM_NAME];
+ u64 reserved2;
+ u64 system_level;
+ u64 reserved3;
+ u8 sysplex_name[CPI_LENGTH_SYSPLEX_NAME];
+ u8 reserved4[16];
+} __attribute__((packed));
+
+struct cpi_sccb {
+ struct sccb_header header;
+ struct cpi_evbuf cpi_evbuf;
+} __attribute__((packed));
+
+/* Event type structure for write message and write priority message */
+static struct sclp_register sclp_cpi_event =
+{
+ .send_mask = EvTyp_CtlProgIdent_Mask
+};
+
+MODULE_AUTHOR(
+ "Martin Peschke, IBM Deutschland Entwicklung GmbH "
+ "<mpeschke@de.ibm.com>");
+
+MODULE_DESCRIPTION(
+ "identify this operating system instance to the S/390 "
+ "or zSeries hardware");
+
+static char *system_name = NULL;
+module_param(system_name, charp, 0);
+MODULE_PARM_DESC(system_name, "e.g. hostname - max. 8 characters");
+
+static char *sysplex_name = NULL;
+#ifdef ALLOW_SYSPLEX_NAME
+module_param(sysplex_name, charp, 0);
+MODULE_PARM_DESC(sysplex_name, "if applicable - max. 8 characters");
+#endif
+
+/* use default value for this field (as well as for system level) */
+static char *system_type = "LINUX";
+
+static int
+cpi_check_parms(void)
+{
+ /* reject if no system type specified */
+ if (!system_type) {
+ printk("cpi: bug: no system type specified\n");
+ return -EINVAL;
+ }
+
+ /* reject if system type larger than 8 characters */
+ if (strlen(system_type) > CPI_LENGTH_SYSTEM_NAME) {
+ printk("cpi: bug: system type has length of %li characters - "
+ "only %i characters supported\n",
+ strlen(system_type), CPI_LENGTH_SYSTEM_TYPE);
+ return -EINVAL;
+ }
+
+ /* reject if no system name specified */
+ if (!system_name) {
+ printk("cpi: no system name specified\n");
+ return -EINVAL;
+ }
+
+ /* reject if system name larger than 8 characters */
+ if (strlen(system_name) > CPI_LENGTH_SYSTEM_NAME) {
+ printk("cpi: system name has length of %li characters - "
+ "only %i characters supported\n",
+ strlen(system_name), CPI_LENGTH_SYSTEM_NAME);
+ return -EINVAL;
+ }
+
+ /* reject if specified sysplex name larger than 8 characters */
+ if (sysplex_name && strlen(sysplex_name) > CPI_LENGTH_SYSPLEX_NAME) {
+ printk("cpi: sysplex name has length of %li characters"
+ " - only %i characters supported\n",
+ strlen(sysplex_name), CPI_LENGTH_SYSPLEX_NAME);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void
+cpi_callback(struct sclp_req *req, void *data)
+{
+ struct semaphore *sem;
+
+ sem = (struct semaphore *) data;
+ up(sem);
+}
+
+static struct sclp_req *
+cpi_prepare_req(void)
+{
+ struct sclp_req *req;
+ struct cpi_sccb *sccb;
+ struct cpi_evbuf *evb;
+
+ req = (struct sclp_req *) kmalloc(sizeof(struct sclp_req), GFP_KERNEL);
+ if (req == NULL)
+ return ERR_PTR(-ENOMEM);
+ sccb = (struct cpi_sccb *) __get_free_page(GFP_KERNEL | GFP_DMA);
+ if (sccb == NULL) {
+ kfree(req);
+ return ERR_PTR(-ENOMEM);
+ }
+ memset(sccb, 0, sizeof(struct cpi_sccb));
+
+ /* setup SCCB for Control-Program Identification */
+ sccb->header.length = sizeof(struct cpi_sccb);
+ sccb->cpi_evbuf.header.length = sizeof(struct cpi_evbuf);
+ sccb->cpi_evbuf.header.type = 0x0B;
+ evb = &sccb->cpi_evbuf;
+
+ /* set system type */
+ memset(evb->system_type, ' ', CPI_LENGTH_SYSTEM_TYPE);
+ memcpy(evb->system_type, system_type, strlen(system_type));
+ sclp_ascebc_str(evb->system_type, CPI_LENGTH_SYSTEM_TYPE);
+ EBC_TOUPPER(evb->system_type, CPI_LENGTH_SYSTEM_TYPE);
+
+ /* set system name */
+ memset(evb->system_name, ' ', CPI_LENGTH_SYSTEM_NAME);
+ memcpy(evb->system_name, system_name, strlen(system_name));
+ sclp_ascebc_str(evb->system_name, CPI_LENGTH_SYSTEM_NAME);
+ EBC_TOUPPER(evb->system_name, CPI_LENGTH_SYSTEM_NAME);
+
+ /* set sytem level */
+ evb->system_level = LINUX_VERSION_CODE;
+
+ /* set sysplex name */
+ if (sysplex_name) {
+ memset(evb->sysplex_name, ' ', CPI_LENGTH_SYSPLEX_NAME);
+ memcpy(evb->sysplex_name, sysplex_name, strlen(sysplex_name));
+ sclp_ascebc_str(evb->sysplex_name, CPI_LENGTH_SYSPLEX_NAME);
+ EBC_TOUPPER(evb->sysplex_name, CPI_LENGTH_SYSPLEX_NAME);
+ }
+
+ /* prepare request data structure presented to SCLP driver */
+ req->command = SCLP_CMDW_WRITEDATA;
+ req->sccb = sccb;
+ req->status = SCLP_REQ_FILLED;
+ req->callback = cpi_callback;
+ return req;
+}
+
+static void
+cpi_free_req(struct sclp_req *req)
+{
+ free_page((unsigned long) req->sccb);
+ kfree(req);
+}
+
+static int __init
+cpi_module_init(void)
+{
+ struct semaphore sem;
+ struct sclp_req *req;
+ int rc;
+
+ rc = cpi_check_parms();
+ if (rc)
+ return rc;
+
+ rc = sclp_register(&sclp_cpi_event);
+ if (rc) {
+ /* could not register sclp event. Die. */
+ printk(KERN_WARNING "cpi: could not register to hardware "
+ "console.\n");
+ return -EINVAL;
+ }
+ if (!(sclp_cpi_event.sclp_send_mask & EvTyp_CtlProgIdent_Mask)) {
+ printk(KERN_WARNING "cpi: no control program identification "
+ "support\n");
+ sclp_unregister(&sclp_cpi_event);
+ return -ENOTSUPP;
+ }
+
+ req = cpi_prepare_req();
+ if (IS_ERR(req)) {
+ printk(KERN_WARNING "cpi: couldn't allocate request\n");
+ sclp_unregister(&sclp_cpi_event);
+ return PTR_ERR(req);
+ }
+
+ /* Prepare semaphore */
+ sema_init(&sem, 0);
+ req->callback_data = &sem;
+ /* Add request to sclp queue */
+ rc = sclp_add_request(req);
+ if (rc) {
+ printk(KERN_WARNING "cpi: could not start request\n");
+ cpi_free_req(req);
+ sclp_unregister(&sclp_cpi_event);
+ return rc;
+ }
+ /* make "insmod" sleep until callback arrives */
+ down(&sem);
+
+ rc = ((struct cpi_sccb *) req->sccb)->header.response_code;
+ if (rc != 0x0020) {
+ printk(KERN_WARNING "cpi: failed with response code 0x%x\n",
+ rc);
+ rc = -ECOMM;
+ } else
+ rc = 0;
+
+ cpi_free_req(req);
+ sclp_unregister(&sclp_cpi_event);
+
+ return rc;
+}
+
+
+static void __exit cpi_module_exit(void)
+{
+}
+
+
+/* declare driver module init/cleanup functions */
+module_init(cpi_module_init);
+module_exit(cpi_module_exit);
+
diff --git a/drivers/s390/char/sclp_quiesce.c b/drivers/s390/char/sclp_quiesce.c
new file mode 100644
index 000000000000..83f75774df60
--- /dev/null
+++ b/drivers/s390/char/sclp_quiesce.c
@@ -0,0 +1,99 @@
+/*
+ * drivers/s390/char/sclp_quiesce.c
+ * signal quiesce handler
+ *
+ * (C) Copyright IBM Corp. 1999,2004
+ * Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/cpumask.h>
+#include <linux/smp.h>
+#include <linux/init.h>
+#include <asm/atomic.h>
+#include <asm/ptrace.h>
+#include <asm/sigp.h>
+
+#include "sclp.h"
+
+
+#ifdef CONFIG_SMP
+/* Signal completion of shutdown process. All CPUs except the first to enter
+ * this function: go to stopped state. First CPU: wait until all other
+ * CPUs are in stopped or check stop state. Afterwards, load special PSW
+ * to indicate completion. */
+static void
+do_load_quiesce_psw(void * __unused)
+{
+ static atomic_t cpuid = ATOMIC_INIT(-1);
+ psw_t quiesce_psw;
+ int cpu;
+
+ if (atomic_compare_and_swap(-1, smp_processor_id(), &cpuid))
+ signal_processor(smp_processor_id(), sigp_stop);
+ /* Wait for all other cpus to enter stopped state */
+ for_each_online_cpu(cpu) {
+ if (cpu == smp_processor_id())
+ continue;
+ while(!smp_cpu_not_running(cpu))
+ cpu_relax();
+ }
+ /* Quiesce the last cpu with the special psw */
+ quiesce_psw.mask = PSW_BASE_BITS | PSW_MASK_WAIT;
+ quiesce_psw.addr = 0xfff;
+ __load_psw(quiesce_psw);
+}
+
+/* Shutdown handler. Perform shutdown function on all CPUs. */
+static void
+do_machine_quiesce(void)
+{
+ on_each_cpu(do_load_quiesce_psw, NULL, 0, 0);
+}
+#else
+/* Shutdown handler. Signal completion of shutdown by loading special PSW. */
+static void
+do_machine_quiesce(void)
+{
+ psw_t quiesce_psw;
+
+ quiesce_psw.mask = PSW_BASE_BITS | PSW_MASK_WAIT;
+ quiesce_psw.addr = 0xfff;
+ __load_psw(quiesce_psw);
+}
+#endif
+
+extern void ctrl_alt_del(void);
+
+/* Handler for quiesce event. Start shutdown procedure. */
+static void
+sclp_quiesce_handler(struct evbuf_header *evbuf)
+{
+ _machine_restart = (void *) do_machine_quiesce;
+ _machine_halt = do_machine_quiesce;
+ _machine_power_off = do_machine_quiesce;
+ ctrl_alt_del();
+}
+
+static struct sclp_register sclp_quiesce_event = {
+ .receive_mask = EvTyp_SigQuiesce_Mask,
+ .receiver_fn = sclp_quiesce_handler
+};
+
+/* Initialize quiesce driver. */
+static int __init
+sclp_quiesce_init(void)
+{
+ int rc;
+
+ rc = sclp_register(&sclp_quiesce_event);
+ if (rc)
+ printk(KERN_WARNING "sclp: could not register quiesce handler "
+ "(rc=%d)\n", rc);
+ return rc;
+}
+
+module_init(sclp_quiesce_init);
diff --git a/drivers/s390/char/sclp_rw.c b/drivers/s390/char/sclp_rw.c
new file mode 100644
index 000000000000..ac10dfb20a62
--- /dev/null
+++ b/drivers/s390/char/sclp_rw.c
@@ -0,0 +1,471 @@
+/*
+ * drivers/s390/char/sclp_rw.c
+ * driver: reading from and writing to system console on S/390 via SCLP
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/kmod.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/ctype.h>
+#include <asm/uaccess.h>
+
+#include "sclp.h"
+#include "sclp_rw.h"
+
+#define SCLP_RW_PRINT_HEADER "sclp low level driver: "
+
+/*
+ * The room for the SCCB (only for writing) is not equal to a pages size
+ * (as it is specified as the maximum size in the the SCLP ducumentation)
+ * because of the additional data structure described above.
+ */
+#define MAX_SCCB_ROOM (PAGE_SIZE - sizeof(struct sclp_buffer))
+
+/* Event type structure for write message and write priority message */
+static struct sclp_register sclp_rw_event = {
+ .send_mask = EvTyp_Msg_Mask | EvTyp_PMsgCmd_Mask
+};
+
+/*
+ * Setup a sclp write buffer. Gets a page as input (4K) and returns
+ * a pointer to a struct sclp_buffer structure that is located at the
+ * end of the input page. This reduces the buffer space by a few
+ * bytes but simplifies things.
+ */
+struct sclp_buffer *
+sclp_make_buffer(void *page, unsigned short columns, unsigned short htab)
+{
+ struct sclp_buffer *buffer;
+ struct write_sccb *sccb;
+
+ sccb = (struct write_sccb *) page;
+ /*
+ * We keep the struct sclp_buffer structure at the end
+ * of the sccb page.
+ */
+ buffer = ((struct sclp_buffer *) ((addr_t) sccb + PAGE_SIZE)) - 1;
+ buffer->sccb = sccb;
+ buffer->retry_count = 0;
+ buffer->mto_number = 0;
+ buffer->mto_char_sum = 0;
+ buffer->current_line = NULL;
+ buffer->current_length = 0;
+ buffer->columns = columns;
+ buffer->htab = htab;
+
+ /* initialize sccb */
+ memset(sccb, 0, sizeof(struct write_sccb));
+ sccb->header.length = sizeof(struct write_sccb);
+ sccb->msg_buf.header.length = sizeof(struct msg_buf);
+ sccb->msg_buf.header.type = EvTyp_Msg;
+ sccb->msg_buf.mdb.header.length = sizeof(struct mdb);
+ sccb->msg_buf.mdb.header.type = 1;
+ sccb->msg_buf.mdb.header.tag = 0xD4C4C240; /* ebcdic "MDB " */
+ sccb->msg_buf.mdb.header.revision_code = 1;
+ sccb->msg_buf.mdb.go.length = sizeof(struct go);
+ sccb->msg_buf.mdb.go.type = 1;
+
+ return buffer;
+}
+
+/*
+ * Return a pointer to the orignal page that has been used to create
+ * the buffer.
+ */
+void *
+sclp_unmake_buffer(struct sclp_buffer *buffer)
+{
+ return buffer->sccb;
+}
+
+/*
+ * Initialize a new Message Text Object (MTO) at the end of the provided buffer
+ * with enough room for max_len characters. Return 0 on success.
+ */
+static int
+sclp_initialize_mto(struct sclp_buffer *buffer, int max_len)
+{
+ struct write_sccb *sccb;
+ struct mto *mto;
+ int mto_size;
+
+ /* max size of new Message Text Object including message text */
+ mto_size = sizeof(struct mto) + max_len;
+
+ /* check if current buffer sccb can contain the mto */
+ sccb = buffer->sccb;
+ if ((MAX_SCCB_ROOM - sccb->header.length) < mto_size)
+ return -ENOMEM;
+
+ /* find address of new message text object */
+ mto = (struct mto *)(((addr_t) sccb) + sccb->header.length);
+
+ /*
+ * fill the new Message-Text Object,
+ * starting behind the former last byte of the SCCB
+ */
+ memset(mto, 0, sizeof(struct mto));
+ mto->length = sizeof(struct mto);
+ mto->type = 4; /* message text object */
+ mto->line_type_flags = LnTpFlgs_EndText; /* end text */
+
+ /* set pointer to first byte after struct mto. */
+ buffer->current_line = (char *) (mto + 1);
+ buffer->current_length = 0;
+
+ return 0;
+}
+
+/*
+ * Finalize MTO initialized by sclp_initialize_mto(), updating the sizes of
+ * MTO, enclosing MDB, event buffer and SCCB.
+ */
+static void
+sclp_finalize_mto(struct sclp_buffer *buffer)
+{
+ struct write_sccb *sccb;
+ struct mto *mto;
+ int str_len, mto_size;
+
+ str_len = buffer->current_length;
+ buffer->current_line = NULL;
+ buffer->current_length = 0;
+
+ /* real size of new Message Text Object including message text */
+ mto_size = sizeof(struct mto) + str_len;
+
+ /* find address of new message text object */
+ sccb = buffer->sccb;
+ mto = (struct mto *)(((addr_t) sccb) + sccb->header.length);
+
+ /* set size of message text object */
+ mto->length = mto_size;
+
+ /*
+ * update values of sizes
+ * (SCCB, Event(Message) Buffer, Message Data Block)
+ */
+ sccb->header.length += mto_size;
+ sccb->msg_buf.header.length += mto_size;
+ sccb->msg_buf.mdb.header.length += mto_size;
+
+ /*
+ * count number of buffered messages (= number of Message Text
+ * Objects) and number of buffered characters
+ * for the SCCB currently used for buffering and at all
+ */
+ buffer->mto_number++;
+ buffer->mto_char_sum += str_len;
+}
+
+/*
+ * processing of a message including escape characters,
+ * returns number of characters written to the output sccb
+ * ("processed" means that is not guaranteed that the character have already
+ * been sent to the SCLP but that it will be done at least next time the SCLP
+ * is not busy)
+ */
+int
+sclp_write(struct sclp_buffer *buffer, const unsigned char *msg, int count)
+{
+ int spaces, i_msg;
+ int rc;
+
+ /*
+ * parse msg for escape sequences (\t,\v ...) and put formated
+ * msg into an mto (created by sclp_initialize_mto).
+ *
+ * We have to do this work ourselfs because there is no support for
+ * these characters on the native machine and only partial support
+ * under VM (Why does VM interpret \n but the native machine doesn't ?)
+ *
+ * Depending on i/o-control setting the message is always written
+ * immediately or we wait for a final new line maybe coming with the
+ * next message. Besides we avoid a buffer overrun by writing its
+ * content.
+ *
+ * RESTRICTIONS:
+ *
+ * \r and \b work within one line because we are not able to modify
+ * previous output that have already been accepted by the SCLP.
+ *
+ * \t combined with following \r is not correctly represented because
+ * \t is expanded to some spaces but \r does not know about a
+ * previous \t and decreases the current position by one column.
+ * This is in order to a slim and quick implementation.
+ */
+ for (i_msg = 0; i_msg < count; i_msg++) {
+ switch (msg[i_msg]) {
+ case '\n': /* new line, line feed (ASCII) */
+ /* check if new mto needs to be created */
+ if (buffer->current_line == NULL) {
+ rc = sclp_initialize_mto(buffer, 0);
+ if (rc)
+ return i_msg;
+ }
+ sclp_finalize_mto(buffer);
+ break;
+ case '\a': /* bell, one for several times */
+ /* set SCLP sound alarm bit in General Object */
+ buffer->sccb->msg_buf.mdb.go.general_msg_flags |=
+ GnrlMsgFlgs_SndAlrm;
+ break;
+ case '\t': /* horizontal tabulator */
+ /* check if new mto needs to be created */
+ if (buffer->current_line == NULL) {
+ rc = sclp_initialize_mto(buffer,
+ buffer->columns);
+ if (rc)
+ return i_msg;
+ }
+ /* "go to (next htab-boundary + 1, same line)" */
+ do {
+ if (buffer->current_length >= buffer->columns)
+ break;
+ /* ok, add a blank */
+ *buffer->current_line++ = 0x40;
+ buffer->current_length++;
+ } while (buffer->current_length % buffer->htab);
+ break;
+ case '\f': /* form feed */
+ case '\v': /* vertical tabulator */
+ /* "go to (actual column, actual line + 1)" */
+ /* = new line, leading spaces */
+ if (buffer->current_line != NULL) {
+ spaces = buffer->current_length;
+ sclp_finalize_mto(buffer);
+ rc = sclp_initialize_mto(buffer,
+ buffer->columns);
+ if (rc)
+ return i_msg;
+ memset(buffer->current_line, 0x40, spaces);
+ buffer->current_line += spaces;
+ buffer->current_length = spaces;
+ } else {
+ /* one an empty line this is the same as \n */
+ rc = sclp_initialize_mto(buffer,
+ buffer->columns);
+ if (rc)
+ return i_msg;
+ sclp_finalize_mto(buffer);
+ }
+ break;
+ case '\b': /* backspace */
+ /* "go to (actual column - 1, actual line)" */
+ /* decrement counter indicating position, */
+ /* do not remove last character */
+ if (buffer->current_line != NULL &&
+ buffer->current_length > 0) {
+ buffer->current_length--;
+ buffer->current_line--;
+ }
+ break;
+ case 0x00: /* end of string */
+ /* transfer current line to SCCB */
+ if (buffer->current_line != NULL)
+ sclp_finalize_mto(buffer);
+ /* skip the rest of the message including the 0 byte */
+ i_msg = count - 1;
+ break;
+ default: /* no escape character */
+ /* do not output unprintable characters */
+ if (!isprint(msg[i_msg]))
+ break;
+ /* check if new mto needs to be created */
+ if (buffer->current_line == NULL) {
+ rc = sclp_initialize_mto(buffer,
+ buffer->columns);
+ if (rc)
+ return i_msg;
+ }
+ *buffer->current_line++ = sclp_ascebc(msg[i_msg]);
+ buffer->current_length++;
+ break;
+ }
+ /* check if current mto is full */
+ if (buffer->current_line != NULL &&
+ buffer->current_length >= buffer->columns)
+ sclp_finalize_mto(buffer);
+ }
+
+ /* return number of processed characters */
+ return i_msg;
+}
+
+/*
+ * Return the number of free bytes in the sccb
+ */
+int
+sclp_buffer_space(struct sclp_buffer *buffer)
+{
+ int count;
+
+ count = MAX_SCCB_ROOM - buffer->sccb->header.length;
+ if (buffer->current_line != NULL)
+ count -= sizeof(struct mto) + buffer->current_length;
+ return count;
+}
+
+/*
+ * Return number of characters in buffer
+ */
+int
+sclp_chars_in_buffer(struct sclp_buffer *buffer)
+{
+ int count;
+
+ count = buffer->mto_char_sum;
+ if (buffer->current_line != NULL)
+ count += buffer->current_length;
+ return count;
+}
+
+/*
+ * sets or provides some values that influence the drivers behaviour
+ */
+void
+sclp_set_columns(struct sclp_buffer *buffer, unsigned short columns)
+{
+ buffer->columns = columns;
+ if (buffer->current_line != NULL &&
+ buffer->current_length > buffer->columns)
+ sclp_finalize_mto(buffer);
+}
+
+void
+sclp_set_htab(struct sclp_buffer *buffer, unsigned short htab)
+{
+ buffer->htab = htab;
+}
+
+/*
+ * called by sclp_console_init and/or sclp_tty_init
+ */
+int
+sclp_rw_init(void)
+{
+ static int init_done = 0;
+ int rc;
+
+ if (init_done)
+ return 0;
+
+ rc = sclp_register(&sclp_rw_event);
+ if (rc == 0)
+ init_done = 1;
+ return rc;
+}
+
+#define SCLP_BUFFER_MAX_RETRY 1
+
+/*
+ * second half of Write Event Data-function that has to be done after
+ * interruption indicating completion of Service Call.
+ */
+static void
+sclp_writedata_callback(struct sclp_req *request, void *data)
+{
+ int rc;
+ struct sclp_buffer *buffer;
+ struct write_sccb *sccb;
+
+ buffer = (struct sclp_buffer *) data;
+ sccb = buffer->sccb;
+
+ if (request->status == SCLP_REQ_FAILED) {
+ if (buffer->callback != NULL)
+ buffer->callback(buffer, -EIO);
+ return;
+ }
+ /* check SCLP response code and choose suitable action */
+ switch (sccb->header.response_code) {
+ case 0x0020 :
+ /* Normal completion, buffer processed, message(s) sent */
+ rc = 0;
+ break;
+
+ case 0x0340: /* Contained SCLP equipment check */
+ if (++buffer->retry_count > SCLP_BUFFER_MAX_RETRY) {
+ rc = -EIO;
+ break;
+ }
+ /* remove processed buffers and requeue rest */
+ if (sclp_remove_processed((struct sccb_header *) sccb) > 0) {
+ /* not all buffers were processed */
+ sccb->header.response_code = 0x0000;
+ buffer->request.status = SCLP_REQ_FILLED;
+ rc = sclp_add_request(request);
+ if (rc == 0)
+ return;
+ } else
+ rc = 0;
+ break;
+
+ case 0x0040: /* SCLP equipment check */
+ case 0x05f0: /* Target resource in improper state */
+ if (++buffer->retry_count > SCLP_BUFFER_MAX_RETRY) {
+ rc = -EIO;
+ break;
+ }
+ /* retry request */
+ sccb->header.response_code = 0x0000;
+ buffer->request.status = SCLP_REQ_FILLED;
+ rc = sclp_add_request(request);
+ if (rc == 0)
+ return;
+ break;
+ default:
+ if (sccb->header.response_code == 0x71f0)
+ rc = -ENOMEM;
+ else
+ rc = -EINVAL;
+ break;
+ }
+ if (buffer->callback != NULL)
+ buffer->callback(buffer, rc);
+}
+
+/*
+ * Setup the request structure in the struct sclp_buffer to do SCLP Write
+ * Event Data and pass the request to the core SCLP loop. Return zero on
+ * success, non-zero otherwise.
+ */
+int
+sclp_emit_buffer(struct sclp_buffer *buffer,
+ void (*callback)(struct sclp_buffer *, int))
+{
+ struct write_sccb *sccb;
+
+ /* add current line if there is one */
+ if (buffer->current_line != NULL)
+ sclp_finalize_mto(buffer);
+
+ /* Are there messages in the output buffer ? */
+ if (buffer->mto_number == 0)
+ return -EIO;
+
+ sccb = buffer->sccb;
+ if (sclp_rw_event.sclp_send_mask & EvTyp_Msg_Mask)
+ /* Use normal write message */
+ sccb->msg_buf.header.type = EvTyp_Msg;
+ else if (sclp_rw_event.sclp_send_mask & EvTyp_PMsgCmd_Mask)
+ /* Use write priority message */
+ sccb->msg_buf.header.type = EvTyp_PMsgCmd;
+ else
+ return -ENOSYS;
+ buffer->request.command = SCLP_CMDW_WRITEDATA;
+ buffer->request.status = SCLP_REQ_FILLED;
+ buffer->request.callback = sclp_writedata_callback;
+ buffer->request.callback_data = buffer;
+ buffer->request.sccb = sccb;
+ buffer->callback = callback;
+ return sclp_add_request(&buffer->request);
+}
diff --git a/drivers/s390/char/sclp_rw.h b/drivers/s390/char/sclp_rw.h
new file mode 100644
index 000000000000..6aa7a6948bc9
--- /dev/null
+++ b/drivers/s390/char/sclp_rw.h
@@ -0,0 +1,96 @@
+/*
+ * drivers/s390/char/sclp_rw.h
+ * interface to the SCLP-read/write driver
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#ifndef __SCLP_RW_H__
+#define __SCLP_RW_H__
+
+#include <linux/list.h>
+
+struct mto {
+ u16 length;
+ u16 type;
+ u16 line_type_flags;
+ u8 alarm_control;
+ u8 _reserved[3];
+} __attribute__((packed));
+
+struct go {
+ u16 length;
+ u16 type;
+ u32 domid;
+ u8 hhmmss_time[8];
+ u8 th_time[3];
+ u8 reserved_0;
+ u8 dddyyyy_date[7];
+ u8 _reserved_1;
+ u16 general_msg_flags;
+ u8 _reserved_2[10];
+ u8 originating_system_name[8];
+ u8 job_guest_name[8];
+} __attribute__((packed));
+
+struct mdb_header {
+ u16 length;
+ u16 type;
+ u32 tag;
+ u32 revision_code;
+} __attribute__((packed));
+
+struct mdb {
+ struct mdb_header header;
+ struct go go;
+} __attribute__((packed));
+
+struct msg_buf {
+ struct evbuf_header header;
+ struct mdb mdb;
+} __attribute__((packed));
+
+struct write_sccb {
+ struct sccb_header header;
+ struct msg_buf msg_buf;
+} __attribute__((packed));
+
+/* The number of empty mto buffers that can be contained in a single sccb. */
+#define NR_EMPTY_MTO_PER_SCCB ((PAGE_SIZE - sizeof(struct sclp_buffer) - \
+ sizeof(struct write_sccb)) / sizeof(struct mto))
+
+/*
+ * data structure for information about list of SCCBs (only for writing),
+ * will be located at the end of a SCCBs page
+ */
+struct sclp_buffer {
+ struct list_head list; /* list_head for sccb_info chain */
+ struct sclp_req request;
+ struct write_sccb *sccb;
+ char *current_line;
+ int current_length;
+ int retry_count;
+ /* output format settings */
+ unsigned short columns;
+ unsigned short htab;
+ /* statistics about this buffer */
+ unsigned int mto_char_sum; /* # chars in sccb */
+ unsigned int mto_number; /* # mtos in sccb */
+ /* Callback that is called after reaching final status. */
+ void (*callback)(struct sclp_buffer *, int);
+};
+
+int sclp_rw_init(void);
+struct sclp_buffer *sclp_make_buffer(void *, unsigned short, unsigned short);
+void *sclp_unmake_buffer(struct sclp_buffer *);
+int sclp_buffer_space(struct sclp_buffer *);
+int sclp_write(struct sclp_buffer *buffer, const unsigned char *, int);
+int sclp_emit_buffer(struct sclp_buffer *,void (*)(struct sclp_buffer *,int));
+void sclp_set_columns(struct sclp_buffer *, unsigned short);
+void sclp_set_htab(struct sclp_buffer *, unsigned short);
+int sclp_chars_in_buffer(struct sclp_buffer *);
+
+#endif /* __SCLP_RW_H__ */
diff --git a/drivers/s390/char/sclp_tty.c b/drivers/s390/char/sclp_tty.c
new file mode 100644
index 000000000000..a20d7c89341d
--- /dev/null
+++ b/drivers/s390/char/sclp_tty.c
@@ -0,0 +1,813 @@
+/*
+ * drivers/s390/char/sclp_tty.c
+ * SCLP line mode terminal driver.
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+
+#include "ctrlchar.h"
+#include "sclp.h"
+#include "sclp_rw.h"
+#include "sclp_tty.h"
+
+#define SCLP_TTY_PRINT_HEADER "sclp tty driver: "
+
+/*
+ * size of a buffer that collects single characters coming in
+ * via sclp_tty_put_char()
+ */
+#define SCLP_TTY_BUF_SIZE 512
+
+/*
+ * There is exactly one SCLP terminal, so we can keep things simple
+ * and allocate all variables statically.
+ */
+
+/* Lock to guard over changes to global variables. */
+static spinlock_t sclp_tty_lock;
+/* List of free pages that can be used for console output buffering. */
+static struct list_head sclp_tty_pages;
+/* List of full struct sclp_buffer structures ready for output. */
+static struct list_head sclp_tty_outqueue;
+/* Counter how many buffers are emitted. */
+static int sclp_tty_buffer_count;
+/* Pointer to current console buffer. */
+static struct sclp_buffer *sclp_ttybuf;
+/* Timer for delayed output of console messages. */
+static struct timer_list sclp_tty_timer;
+/* Waitqueue to wait for buffers to get empty. */
+static wait_queue_head_t sclp_tty_waitq;
+
+static struct tty_struct *sclp_tty;
+static unsigned char sclp_tty_chars[SCLP_TTY_BUF_SIZE];
+static unsigned short int sclp_tty_chars_count;
+
+struct tty_driver *sclp_tty_driver;
+
+extern struct termios tty_std_termios;
+
+static struct sclp_ioctls sclp_ioctls;
+static struct sclp_ioctls sclp_ioctls_init =
+{
+ 8, /* 1 hor. tab. = 8 spaces */
+ 0, /* no echo of input by this driver */
+ 80, /* 80 characters/line */
+ 1, /* write after 1/10 s without final new line */
+ MAX_KMEM_PAGES, /* quick fix: avoid __alloc_pages */
+ MAX_KMEM_PAGES, /* take 32/64 pages from kernel memory, */
+ 0, /* do not convert to lower case */
+ 0x6c /* to seprate upper and lower case */
+ /* ('%' in EBCDIC) */
+};
+
+/* This routine is called whenever we try to open a SCLP terminal. */
+static int
+sclp_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ sclp_tty = tty;
+ tty->driver_data = NULL;
+ tty->low_latency = 0;
+ return 0;
+}
+
+/* This routine is called when the SCLP terminal is closed. */
+static void
+sclp_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ if (tty->count > 1)
+ return;
+ sclp_tty = NULL;
+}
+
+/* execute commands to control the i/o behaviour of the SCLP tty at runtime */
+static int
+sclp_tty_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned long flags;
+ unsigned int obuf;
+ int check;
+ int rc;
+
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ rc = 0;
+ check = 0;
+ switch (cmd) {
+ case TIOCSCLPSHTAB:
+ /* set width of horizontal tab */
+ if (get_user(sclp_ioctls.htab, (unsigned short __user *) arg))
+ rc = -EFAULT;
+ else
+ check = 1;
+ break;
+ case TIOCSCLPGHTAB:
+ /* get width of horizontal tab */
+ if (put_user(sclp_ioctls.htab, (unsigned short __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPSECHO:
+ /* enable/disable echo of input */
+ if (get_user(sclp_ioctls.echo, (unsigned char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPGECHO:
+ /* Is echo of input enabled ? */
+ if (put_user(sclp_ioctls.echo, (unsigned char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPSCOLS:
+ /* set number of columns for output */
+ if (get_user(sclp_ioctls.columns, (unsigned short __user *) arg))
+ rc = -EFAULT;
+ else
+ check = 1;
+ break;
+ case TIOCSCLPGCOLS:
+ /* get number of columns for output */
+ if (put_user(sclp_ioctls.columns, (unsigned short __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPSNL:
+ /* enable/disable writing without final new line character */
+ if (get_user(sclp_ioctls.final_nl, (signed char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPGNL:
+ /* Is writing without final new line character enabled ? */
+ if (put_user(sclp_ioctls.final_nl, (signed char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPSOBUF:
+ /*
+ * set the maximum buffers size for output, will be rounded
+ * up to next 4kB boundary and stored as number of SCCBs
+ * (4kB Buffers) limitation: 256 x 4kB
+ */
+ if (get_user(obuf, (unsigned int __user *) arg) == 0) {
+ if (obuf & 0xFFF)
+ sclp_ioctls.max_sccb = (obuf >> 12) + 1;
+ else
+ sclp_ioctls.max_sccb = (obuf >> 12);
+ } else
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPGOBUF:
+ /* get the maximum buffers size for output */
+ obuf = sclp_ioctls.max_sccb << 12;
+ if (put_user(obuf, (unsigned int __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPGKBUF:
+ /* get the number of buffers got from kernel at startup */
+ if (put_user(sclp_ioctls.kmem_sccb, (unsigned short __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPSCASE:
+ /* enable/disable conversion from upper to lower case */
+ if (get_user(sclp_ioctls.tolower, (unsigned char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPGCASE:
+ /* Is conversion from upper to lower case of input enabled? */
+ if (put_user(sclp_ioctls.tolower, (unsigned char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPSDELIM:
+ /*
+ * set special character used for separating upper and
+ * lower case, 0x00 disables this feature
+ */
+ if (get_user(sclp_ioctls.delim, (unsigned char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPGDELIM:
+ /*
+ * get special character used for separating upper and
+ * lower case, 0x00 disables this feature
+ */
+ if (put_user(sclp_ioctls.delim, (unsigned char __user *) arg))
+ rc = -EFAULT;
+ break;
+ case TIOCSCLPSINIT:
+ /* set initial (default) sclp ioctls */
+ sclp_ioctls = sclp_ioctls_init;
+ check = 1;
+ break;
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+ if (check) {
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ if (sclp_ttybuf != NULL) {
+ sclp_set_htab(sclp_ttybuf, sclp_ioctls.htab);
+ sclp_set_columns(sclp_ttybuf, sclp_ioctls.columns);
+ }
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ }
+ return rc;
+}
+
+/*
+ * This routine returns the numbers of characters the tty driver
+ * will accept for queuing to be written. This number is subject
+ * to change as output buffers get emptied, or if the output flow
+ * control is acted. This is not an exact number because not every
+ * character needs the same space in the sccb. The worst case is
+ * a string of newlines. Every newlines creates a new mto which
+ * needs 8 bytes.
+ */
+static int
+sclp_tty_write_room (struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct list_head *l;
+ int count;
+
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ count = 0;
+ if (sclp_ttybuf != NULL)
+ count = sclp_buffer_space(sclp_ttybuf) / sizeof(struct mto);
+ list_for_each(l, &sclp_tty_pages)
+ count += NR_EMPTY_MTO_PER_SCCB;
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ return count;
+}
+
+static void
+sclp_ttybuf_callback(struct sclp_buffer *buffer, int rc)
+{
+ unsigned long flags;
+ void *page;
+
+ do {
+ page = sclp_unmake_buffer(buffer);
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ /* Remove buffer from outqueue */
+ list_del(&buffer->list);
+ sclp_tty_buffer_count--;
+ list_add_tail((struct list_head *) page, &sclp_tty_pages);
+ /* Check if there is a pending buffer on the out queue. */
+ buffer = NULL;
+ if (!list_empty(&sclp_tty_outqueue))
+ buffer = list_entry(sclp_tty_outqueue.next,
+ struct sclp_buffer, list);
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ } while (buffer && sclp_emit_buffer(buffer, sclp_ttybuf_callback));
+ wake_up(&sclp_tty_waitq);
+ /* check if the tty needs a wake up call */
+ if (sclp_tty != NULL) {
+ tty_wakeup(sclp_tty);
+ }
+}
+
+static inline void
+__sclp_ttybuf_emit(struct sclp_buffer *buffer)
+{
+ unsigned long flags;
+ int count;
+ int rc;
+
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ list_add_tail(&buffer->list, &sclp_tty_outqueue);
+ count = sclp_tty_buffer_count++;
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ if (count)
+ return;
+ rc = sclp_emit_buffer(buffer, sclp_ttybuf_callback);
+ if (rc)
+ sclp_ttybuf_callback(buffer, rc);
+}
+
+/*
+ * When this routine is called from the timer then we flush the
+ * temporary write buffer.
+ */
+static void
+sclp_tty_timeout(unsigned long data)
+{
+ unsigned long flags;
+ struct sclp_buffer *buf;
+
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ buf = sclp_ttybuf;
+ sclp_ttybuf = NULL;
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+
+ if (buf != NULL) {
+ __sclp_ttybuf_emit(buf);
+ }
+}
+
+/*
+ * Write a string to the sclp tty.
+ */
+static void
+sclp_tty_write_string(const unsigned char *str, int count)
+{
+ unsigned long flags;
+ void *page;
+ int written;
+ struct sclp_buffer *buf;
+
+ if (count <= 0)
+ return;
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ do {
+ /* Create a sclp output buffer if none exists yet */
+ if (sclp_ttybuf == NULL) {
+ while (list_empty(&sclp_tty_pages)) {
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ if (in_interrupt())
+ sclp_sync_wait();
+ else
+ wait_event(sclp_tty_waitq,
+ !list_empty(&sclp_tty_pages));
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ }
+ page = sclp_tty_pages.next;
+ list_del((struct list_head *) page);
+ sclp_ttybuf = sclp_make_buffer(page,
+ sclp_ioctls.columns,
+ sclp_ioctls.htab);
+ }
+ /* try to write the string to the current output buffer */
+ written = sclp_write(sclp_ttybuf, str, count);
+ if (written == count)
+ break;
+ /*
+ * Not all characters could be written to the current
+ * output buffer. Emit the buffer, create a new buffer
+ * and then output the rest of the string.
+ */
+ buf = sclp_ttybuf;
+ sclp_ttybuf = NULL;
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ __sclp_ttybuf_emit(buf);
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ str += written;
+ count -= written;
+ } while (count > 0);
+ /* Setup timer to output current console buffer after 1/10 second */
+ if (sclp_ioctls.final_nl) {
+ if (sclp_ttybuf != NULL &&
+ sclp_chars_in_buffer(sclp_ttybuf) != 0 &&
+ !timer_pending(&sclp_tty_timer)) {
+ init_timer(&sclp_tty_timer);
+ sclp_tty_timer.function = sclp_tty_timeout;
+ sclp_tty_timer.data = 0UL;
+ sclp_tty_timer.expires = jiffies + HZ/10;
+ add_timer(&sclp_tty_timer);
+ }
+ } else {
+ if (sclp_ttybuf != NULL &&
+ sclp_chars_in_buffer(sclp_ttybuf) != 0) {
+ buf = sclp_ttybuf;
+ sclp_ttybuf = NULL;
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ __sclp_ttybuf_emit(buf);
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ }
+ }
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+}
+
+/*
+ * This routine is called by the kernel to write a series of characters to the
+ * tty device. The characters may come from user space or kernel space. This
+ * routine will return the number of characters actually accepted for writing.
+ */
+static int
+sclp_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ if (sclp_tty_chars_count > 0) {
+ sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+ sclp_tty_chars_count = 0;
+ }
+ sclp_tty_write_string(buf, count);
+ return count;
+}
+
+/*
+ * This routine is called by the kernel to write a single character to the tty
+ * device. If the kernel uses this routine, it must call the flush_chars()
+ * routine (if defined) when it is done stuffing characters into the driver.
+ *
+ * Characters provided to sclp_tty_put_char() are buffered by the SCLP driver.
+ * If the given character is a '\n' the contents of the SCLP write buffer
+ * - including previous characters from sclp_tty_put_char() and strings from
+ * sclp_write() without final '\n' - will be written.
+ */
+static void
+sclp_tty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ sclp_tty_chars[sclp_tty_chars_count++] = ch;
+ if (ch == '\n' || sclp_tty_chars_count >= SCLP_TTY_BUF_SIZE) {
+ sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+ sclp_tty_chars_count = 0;
+ }
+}
+
+/*
+ * This routine is called by the kernel after it has written a series of
+ * characters to the tty device using put_char().
+ */
+static void
+sclp_tty_flush_chars(struct tty_struct *tty)
+{
+ if (sclp_tty_chars_count > 0) {
+ sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+ sclp_tty_chars_count = 0;
+ }
+}
+
+/*
+ * This routine returns the number of characters in the write buffer of the
+ * SCLP driver. The provided number includes all characters that are stored
+ * in the SCCB (will be written next time the SCLP is not busy) as well as
+ * characters in the write buffer (will not be written as long as there is a
+ * final line feed missing).
+ */
+static int
+sclp_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct list_head *l;
+ struct sclp_buffer *t;
+ int count;
+
+ spin_lock_irqsave(&sclp_tty_lock, flags);
+ count = 0;
+ if (sclp_ttybuf != NULL)
+ count = sclp_chars_in_buffer(sclp_ttybuf);
+ list_for_each(l, &sclp_tty_outqueue) {
+ t = list_entry(l, struct sclp_buffer, list);
+ count += sclp_chars_in_buffer(t);
+ }
+ spin_unlock_irqrestore(&sclp_tty_lock, flags);
+ return count;
+}
+
+/*
+ * removes all content from buffers of low level driver
+ */
+static void
+sclp_tty_flush_buffer(struct tty_struct *tty)
+{
+ if (sclp_tty_chars_count > 0) {
+ sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+ sclp_tty_chars_count = 0;
+ }
+}
+
+/*
+ * push input to tty
+ */
+static void
+sclp_tty_input(unsigned char* buf, unsigned int count)
+{
+ unsigned int cchar;
+
+ /*
+ * If this tty driver is currently closed
+ * then throw the received input away.
+ */
+ if (sclp_tty == NULL)
+ return;
+ cchar = ctrlchar_handle(buf, count, sclp_tty);
+ switch (cchar & CTRLCHAR_MASK) {
+ case CTRLCHAR_SYSRQ:
+ break;
+ case CTRLCHAR_CTRL:
+ sclp_tty->flip.count++;
+ *sclp_tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+ *sclp_tty->flip.char_buf_ptr++ = cchar;
+ tty_flip_buffer_push(sclp_tty);
+ break;
+ case CTRLCHAR_NONE:
+ /* send (normal) input to line discipline */
+ memcpy(sclp_tty->flip.char_buf_ptr, buf, count);
+ if (count < 2 ||
+ (strncmp ((const char *) buf + count - 2, "^n", 2) &&
+ strncmp ((const char *) buf + count - 2, "\0252n", 2))) {
+ sclp_tty->flip.char_buf_ptr[count] = '\n';
+ count++;
+ } else
+ count -= 2;
+ memset(sclp_tty->flip.flag_buf_ptr, TTY_NORMAL, count);
+ sclp_tty->flip.char_buf_ptr += count;
+ sclp_tty->flip.flag_buf_ptr += count;
+ sclp_tty->flip.count += count;
+ tty_flip_buffer_push(sclp_tty);
+ break;
+ }
+}
+
+/*
+ * get a EBCDIC string in upper/lower case,
+ * find out characters in lower/upper case separated by a special character,
+ * modifiy original string,
+ * returns length of resulting string
+ */
+static int
+sclp_switch_cases(unsigned char *buf, int count,
+ unsigned char delim, int tolower)
+{
+ unsigned char *ip, *op;
+ int toggle;
+
+ /* initially changing case is off */
+ toggle = 0;
+ ip = op = buf;
+ while (count-- > 0) {
+ /* compare with special character */
+ if (*ip == delim) {
+ /* followed by another special character? */
+ if (count && ip[1] == delim) {
+ /*
+ * ... then put a single copy of the special
+ * character to the output string
+ */
+ *op++ = *ip++;
+ count--;
+ } else
+ /*
+ * ... special character follower by a normal
+ * character toggles the case change behaviour
+ */
+ toggle = ~toggle;
+ /* skip special character */
+ ip++;
+ } else
+ /* not the special character */
+ if (toggle)
+ /* but case switching is on */
+ if (tolower)
+ /* switch to uppercase */
+ *op++ = _ebc_toupper[(int) *ip++];
+ else
+ /* switch to lowercase */
+ *op++ = _ebc_tolower[(int) *ip++];
+ else
+ /* no case switching, copy the character */
+ *op++ = *ip++;
+ }
+ /* return length of reformatted string. */
+ return op - buf;
+}
+
+static void
+sclp_get_input(unsigned char *start, unsigned char *end)
+{
+ int count;
+
+ count = end - start;
+ /*
+ * if set in ioctl convert EBCDIC to lower case
+ * (modify original input in SCCB)
+ */
+ if (sclp_ioctls.tolower)
+ EBC_TOLOWER(start, count);
+
+ /*
+ * if set in ioctl find out characters in lower or upper case
+ * (depends on current case) separated by a special character,
+ * works on EBCDIC
+ */
+ if (sclp_ioctls.delim)
+ count = sclp_switch_cases(start, count,
+ sclp_ioctls.delim,
+ sclp_ioctls.tolower);
+
+ /* convert EBCDIC to ASCII (modify original input in SCCB) */
+ sclp_ebcasc_str(start, count);
+
+ /* if set in ioctl write operators input to console */
+ if (sclp_ioctls.echo)
+ sclp_tty_write(sclp_tty, start, count);
+
+ /* transfer input to high level driver */
+ sclp_tty_input(start, count);
+}
+
+static inline struct gds_vector *
+find_gds_vector(struct gds_vector *start, struct gds_vector *end, u16 id)
+{
+ struct gds_vector *vec;
+
+ for (vec = start; vec < end; vec = (void *) vec + vec->length)
+ if (vec->gds_id == id)
+ return vec;
+ return NULL;
+}
+
+static inline struct gds_subvector *
+find_gds_subvector(struct gds_subvector *start,
+ struct gds_subvector *end, u8 key)
+{
+ struct gds_subvector *subvec;
+
+ for (subvec = start; subvec < end;
+ subvec = (void *) subvec + subvec->length)
+ if (subvec->key == key)
+ return subvec;
+ return NULL;
+}
+
+static inline void
+sclp_eval_selfdeftextmsg(struct gds_subvector *start,
+ struct gds_subvector *end)
+{
+ struct gds_subvector *subvec;
+
+ subvec = start;
+ while (subvec < end) {
+ subvec = find_gds_subvector(subvec, end, 0x30);
+ if (!subvec)
+ break;
+ sclp_get_input((unsigned char *)(subvec + 1),
+ (unsigned char *) subvec + subvec->length);
+ subvec = (void *) subvec + subvec->length;
+ }
+}
+
+static inline void
+sclp_eval_textcmd(struct gds_subvector *start,
+ struct gds_subvector *end)
+{
+ struct gds_subvector *subvec;
+
+ subvec = start;
+ while (subvec < end) {
+ subvec = find_gds_subvector(subvec, end,
+ GDS_KEY_SelfDefTextMsg);
+ if (!subvec)
+ break;
+ sclp_eval_selfdeftextmsg((struct gds_subvector *)(subvec + 1),
+ (void *)subvec + subvec->length);
+ subvec = (void *) subvec + subvec->length;
+ }
+}
+
+static inline void
+sclp_eval_cpmsu(struct gds_vector *start, struct gds_vector *end)
+{
+ struct gds_vector *vec;
+
+ vec = start;
+ while (vec < end) {
+ vec = find_gds_vector(vec, end, GDS_ID_TextCmd);
+ if (!vec)
+ break;
+ sclp_eval_textcmd((struct gds_subvector *)(vec + 1),
+ (void *) vec + vec->length);
+ vec = (void *) vec + vec->length;
+ }
+}
+
+
+static inline void
+sclp_eval_mdsmu(struct gds_vector *start, void *end)
+{
+ struct gds_vector *vec;
+
+ vec = find_gds_vector(start, end, GDS_ID_CPMSU);
+ if (vec)
+ sclp_eval_cpmsu(vec + 1, (void *) vec + vec->length);
+}
+
+static void
+sclp_tty_receiver(struct evbuf_header *evbuf)
+{
+ struct gds_vector *start, *end, *vec;
+
+ start = (struct gds_vector *)(evbuf + 1);
+ end = (void *) evbuf + evbuf->length;
+ vec = find_gds_vector(start, end, GDS_ID_MDSMU);
+ if (vec)
+ sclp_eval_mdsmu(vec + 1, (void *) vec + vec->length);
+}
+
+static void
+sclp_tty_state_change(struct sclp_register *reg)
+{
+}
+
+static struct sclp_register sclp_input_event =
+{
+ .receive_mask = EvTyp_OpCmd_Mask | EvTyp_PMsgCmd_Mask,
+ .state_change_fn = sclp_tty_state_change,
+ .receiver_fn = sclp_tty_receiver
+};
+
+static struct tty_operations sclp_ops = {
+ .open = sclp_tty_open,
+ .close = sclp_tty_close,
+ .write = sclp_tty_write,
+ .put_char = sclp_tty_put_char,
+ .flush_chars = sclp_tty_flush_chars,
+ .write_room = sclp_tty_write_room,
+ .chars_in_buffer = sclp_tty_chars_in_buffer,
+ .flush_buffer = sclp_tty_flush_buffer,
+ .ioctl = sclp_tty_ioctl,
+};
+
+int __init
+sclp_tty_init(void)
+{
+ struct tty_driver *driver;
+ void *page;
+ int i;
+ int rc;
+
+ if (!CONSOLE_IS_SCLP)
+ return 0;
+ driver = alloc_tty_driver(1);
+ if (!driver)
+ return -ENOMEM;
+
+ rc = sclp_rw_init();
+ if (rc) {
+ printk(KERN_ERR SCLP_TTY_PRINT_HEADER
+ "could not register tty - "
+ "sclp_rw_init returned %d\n", rc);
+ put_tty_driver(driver);
+ return rc;
+ }
+ /* Allocate pages for output buffering */
+ INIT_LIST_HEAD(&sclp_tty_pages);
+ for (i = 0; i < MAX_KMEM_PAGES; i++) {
+ page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (page == NULL) {
+ put_tty_driver(driver);
+ return -ENOMEM;
+ }
+ list_add_tail((struct list_head *) page, &sclp_tty_pages);
+ }
+ INIT_LIST_HEAD(&sclp_tty_outqueue);
+ spin_lock_init(&sclp_tty_lock);
+ init_waitqueue_head(&sclp_tty_waitq);
+ init_timer(&sclp_tty_timer);
+ sclp_ttybuf = NULL;
+ sclp_tty_buffer_count = 0;
+ if (MACHINE_IS_VM) {
+ /*
+ * save 4 characters for the CPU number
+ * written at start of each line by VM/CP
+ */
+ sclp_ioctls_init.columns = 76;
+ /* case input lines to lowercase */
+ sclp_ioctls_init.tolower = 1;
+ }
+ sclp_ioctls = sclp_ioctls_init;
+ sclp_tty_chars_count = 0;
+ sclp_tty = NULL;
+
+ rc = sclp_register(&sclp_input_event);
+ if (rc) {
+ put_tty_driver(driver);
+ return rc;
+ }
+
+ driver->owner = THIS_MODULE;
+ driver->driver_name = "sclp_line";
+ driver->name = "sclp_line";
+ driver->major = TTY_MAJOR;
+ driver->minor_start = 64;
+ driver->type = TTY_DRIVER_TYPE_SYSTEM;
+ driver->subtype = SYSTEM_TYPE_TTY;
+ driver->init_termios = tty_std_termios;
+ driver->init_termios.c_iflag = IGNBRK | IGNPAR;
+ driver->init_termios.c_oflag = ONLCR | XTABS;
+ driver->init_termios.c_lflag = ISIG | ECHO;
+ driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(driver, &sclp_ops);
+ rc = tty_register_driver(driver);
+ if (rc) {
+ printk(KERN_ERR SCLP_TTY_PRINT_HEADER
+ "could not register tty - "
+ "tty_register_driver returned %d\n", rc);
+ put_tty_driver(driver);
+ return rc;
+ }
+ sclp_tty_driver = driver;
+ return 0;
+}
+module_init(sclp_tty_init);
diff --git a/drivers/s390/char/sclp_tty.h b/drivers/s390/char/sclp_tty.h
new file mode 100644
index 000000000000..0ce2c1fc5340
--- /dev/null
+++ b/drivers/s390/char/sclp_tty.h
@@ -0,0 +1,71 @@
+/*
+ * drivers/s390/char/sclp_tty.h
+ * interface to the SCLP-read/write driver
+ *
+ * S390 version
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Martin Peschke <mpeschke@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#ifndef __SCLP_TTY_H__
+#define __SCLP_TTY_H__
+
+#include <linux/ioctl.h>
+#include <linux/termios.h>
+#include <linux/tty_driver.h>
+
+/* This is the type of data structures storing sclp ioctl setting. */
+struct sclp_ioctls {
+ unsigned short htab;
+ unsigned char echo;
+ unsigned short columns;
+ unsigned char final_nl;
+ unsigned short max_sccb;
+ unsigned short kmem_sccb; /* can't be modified at run time */
+ unsigned char tolower;
+ unsigned char delim;
+};
+
+/* must be unique, FIXME: must be added in Documentation/ioctl_number.txt */
+#define SCLP_IOCTL_LETTER 'B'
+
+/* set width of horizontal tabulator */
+#define TIOCSCLPSHTAB _IOW(SCLP_IOCTL_LETTER, 0, unsigned short)
+/* enable/disable echo of input (independent from line discipline) */
+#define TIOCSCLPSECHO _IOW(SCLP_IOCTL_LETTER, 1, unsigned char)
+/* set number of colums for output */
+#define TIOCSCLPSCOLS _IOW(SCLP_IOCTL_LETTER, 2, unsigned short)
+/* enable/disable writing without final new line character */
+#define TIOCSCLPSNL _IOW(SCLP_IOCTL_LETTER, 4, signed char)
+/* set the maximum buffers size for output, rounded up to next 4kB boundary */
+#define TIOCSCLPSOBUF _IOW(SCLP_IOCTL_LETTER, 5, unsigned short)
+/* set initial (default) sclp ioctls */
+#define TIOCSCLPSINIT _IO(SCLP_IOCTL_LETTER, 6)
+/* enable/disable conversion from upper to lower case of input */
+#define TIOCSCLPSCASE _IOW(SCLP_IOCTL_LETTER, 7, unsigned char)
+/* set special character used for separating upper and lower case, */
+/* 0x00 disables this feature */
+#define TIOCSCLPSDELIM _IOW(SCLP_IOCTL_LETTER, 9, unsigned char)
+
+/* get width of horizontal tabulator */
+#define TIOCSCLPGHTAB _IOR(SCLP_IOCTL_LETTER, 10, unsigned short)
+/* Is echo of input enabled ? (independent from line discipline) */
+#define TIOCSCLPGECHO _IOR(SCLP_IOCTL_LETTER, 11, unsigned char)
+/* get number of colums for output */
+#define TIOCSCLPGCOLS _IOR(SCLP_IOCTL_LETTER, 12, unsigned short)
+/* Is writing without final new line character enabled ? */
+#define TIOCSCLPGNL _IOR(SCLP_IOCTL_LETTER, 14, signed char)
+/* get the maximum buffers size for output */
+#define TIOCSCLPGOBUF _IOR(SCLP_IOCTL_LETTER, 15, unsigned short)
+/* Is conversion from upper to lower case of input enabled ? */
+#define TIOCSCLPGCASE _IOR(SCLP_IOCTL_LETTER, 17, unsigned char)
+/* get special character used for separating upper and lower case, */
+/* 0x00 disables this feature */
+#define TIOCSCLPGDELIM _IOR(SCLP_IOCTL_LETTER, 19, unsigned char)
+/* get the number of buffers/pages got from kernel at startup */
+#define TIOCSCLPGKBUF _IOR(SCLP_IOCTL_LETTER, 20, unsigned short)
+
+extern struct tty_driver *sclp_tty_driver;
+
+#endif /* __SCLP_TTY_H__ */
diff --git a/drivers/s390/char/sclp_vt220.c b/drivers/s390/char/sclp_vt220.c
new file mode 100644
index 000000000000..06bd85824d7b
--- /dev/null
+++ b/drivers/s390/char/sclp_vt220.c
@@ -0,0 +1,785 @@
+/*
+ * drivers/s390/char/sclp_vt220.c
+ * SCLP VT220 terminal driver.
+ *
+ * S390 version
+ * Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/timer.h>
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/major.h>
+#include <linux/console.h>
+#include <linux/kdev_t.h>
+#include <linux/bootmem.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include "sclp.h"
+
+#define SCLP_VT220_PRINT_HEADER "sclp vt220 tty driver: "
+#define SCLP_VT220_MAJOR TTY_MAJOR
+#define SCLP_VT220_MINOR 65
+#define SCLP_VT220_DRIVER_NAME "sclp_vt220"
+#define SCLP_VT220_DEVICE_NAME "ttysclp"
+#define SCLP_VT220_CONSOLE_NAME "ttyS"
+#define SCLP_VT220_CONSOLE_INDEX 1 /* console=ttyS1 */
+#define SCLP_VT220_BUF_SIZE 80
+
+/* Representation of a single write request */
+struct sclp_vt220_request {
+ struct list_head list;
+ struct sclp_req sclp_req;
+ int retry_count;
+};
+
+/* VT220 SCCB */
+struct sclp_vt220_sccb {
+ struct sccb_header header;
+ struct evbuf_header evbuf;
+};
+
+#define SCLP_VT220_MAX_CHARS_PER_BUFFER (PAGE_SIZE - \
+ sizeof(struct sclp_vt220_request) - \
+ sizeof(struct sclp_vt220_sccb))
+
+/* Structures and data needed to register tty driver */
+static struct tty_driver *sclp_vt220_driver;
+
+/* The tty_struct that the kernel associated with us */
+static struct tty_struct *sclp_vt220_tty;
+
+/* Lock to protect internal data from concurrent access */
+static spinlock_t sclp_vt220_lock;
+
+/* List of empty pages to be used as write request buffers */
+static struct list_head sclp_vt220_empty;
+
+/* List of pending requests */
+static struct list_head sclp_vt220_outqueue;
+
+/* Number of requests in outqueue */
+static int sclp_vt220_outqueue_count;
+
+/* Wait queue used to delay write requests while we've run out of buffers */
+static wait_queue_head_t sclp_vt220_waitq;
+
+/* Timer used for delaying write requests to merge subsequent messages into
+ * a single buffer */
+static struct timer_list sclp_vt220_timer;
+
+/* Pointer to current request buffer which has been partially filled but not
+ * yet sent */
+static struct sclp_vt220_request *sclp_vt220_current_request;
+
+/* Number of characters in current request buffer */
+static int sclp_vt220_buffered_chars;
+
+/* Flag indicating whether this driver has already been initialized */
+static int sclp_vt220_initialized = 0;
+
+/* Flag indicating that sclp_vt220_current_request should really
+ * have been already queued but wasn't because the SCLP was processing
+ * another buffer */
+static int sclp_vt220_flush_later;
+
+static void sclp_vt220_receiver_fn(struct evbuf_header *evbuf);
+static int __sclp_vt220_emit(struct sclp_vt220_request *request);
+static void sclp_vt220_emit_current(void);
+
+/* Registration structure for our interest in SCLP event buffers */
+static struct sclp_register sclp_vt220_register = {
+ .send_mask = EvTyp_VT220Msg_Mask,
+ .receive_mask = EvTyp_VT220Msg_Mask,
+ .state_change_fn = NULL,
+ .receiver_fn = sclp_vt220_receiver_fn
+};
+
+
+/*
+ * Put provided request buffer back into queue and check emit pending
+ * buffers if necessary.
+ */
+static void
+sclp_vt220_process_queue(struct sclp_vt220_request *request)
+{
+ unsigned long flags;
+ void *page;
+
+ do {
+ /* Put buffer back to list of empty buffers */
+ page = request->sclp_req.sccb;
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ /* Move request from outqueue to empty queue */
+ list_del(&request->list);
+ sclp_vt220_outqueue_count--;
+ list_add_tail((struct list_head *) page, &sclp_vt220_empty);
+ /* Check if there is a pending buffer on the out queue. */
+ request = NULL;
+ if (!list_empty(&sclp_vt220_outqueue))
+ request = list_entry(sclp_vt220_outqueue.next,
+ struct sclp_vt220_request, list);
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ } while (request && __sclp_vt220_emit(request));
+ if (request == NULL && sclp_vt220_flush_later)
+ sclp_vt220_emit_current();
+ wake_up(&sclp_vt220_waitq);
+ /* Check if the tty needs a wake up call */
+ if (sclp_vt220_tty != NULL) {
+ tty_wakeup(sclp_vt220_tty);
+ }
+}
+
+#define SCLP_BUFFER_MAX_RETRY 1
+
+/*
+ * Callback through which the result of a write request is reported by the
+ * SCLP.
+ */
+static void
+sclp_vt220_callback(struct sclp_req *request, void *data)
+{
+ struct sclp_vt220_request *vt220_request;
+ struct sclp_vt220_sccb *sccb;
+
+ vt220_request = (struct sclp_vt220_request *) data;
+ if (request->status == SCLP_REQ_FAILED) {
+ sclp_vt220_process_queue(vt220_request);
+ return;
+ }
+ sccb = (struct sclp_vt220_sccb *) vt220_request->sclp_req.sccb;
+
+ /* Check SCLP response code and choose suitable action */
+ switch (sccb->header.response_code) {
+ case 0x0020 :
+ break;
+
+ case 0x05f0: /* Target resource in improper state */
+ break;
+
+ case 0x0340: /* Contained SCLP equipment check */
+ if (++vt220_request->retry_count > SCLP_BUFFER_MAX_RETRY)
+ break;
+ /* Remove processed buffers and requeue rest */
+ if (sclp_remove_processed((struct sccb_header *) sccb) > 0) {
+ /* Not all buffers were processed */
+ sccb->header.response_code = 0x0000;
+ vt220_request->sclp_req.status = SCLP_REQ_FILLED;
+ if (sclp_add_request(request) == 0)
+ return;
+ }
+ break;
+
+ case 0x0040: /* SCLP equipment check */
+ if (++vt220_request->retry_count > SCLP_BUFFER_MAX_RETRY)
+ break;
+ sccb->header.response_code = 0x0000;
+ vt220_request->sclp_req.status = SCLP_REQ_FILLED;
+ if (sclp_add_request(request) == 0)
+ return;
+ break;
+
+ default:
+ break;
+ }
+ sclp_vt220_process_queue(vt220_request);
+}
+
+/*
+ * Emit vt220 request buffer to SCLP. Return zero on success, non-zero
+ * otherwise.
+ */
+static int
+__sclp_vt220_emit(struct sclp_vt220_request *request)
+{
+ if (!(sclp_vt220_register.sclp_send_mask & EvTyp_VT220Msg_Mask)) {
+ request->sclp_req.status = SCLP_REQ_FAILED;
+ return -EIO;
+ }
+ request->sclp_req.command = SCLP_CMDW_WRITEDATA;
+ request->sclp_req.status = SCLP_REQ_FILLED;
+ request->sclp_req.callback = sclp_vt220_callback;
+ request->sclp_req.callback_data = (void *) request;
+
+ return sclp_add_request(&request->sclp_req);
+}
+
+/*
+ * Queue and emit given request.
+ */
+static void
+sclp_vt220_emit(struct sclp_vt220_request *request)
+{
+ unsigned long flags;
+ int count;
+
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ list_add_tail(&request->list, &sclp_vt220_outqueue);
+ count = sclp_vt220_outqueue_count++;
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ /* Emit only the first buffer immediately - callback takes care of
+ * the rest */
+ if (count == 0 && __sclp_vt220_emit(request))
+ sclp_vt220_process_queue(request);
+}
+
+/*
+ * Queue and emit current request. Return zero on success, non-zero otherwise.
+ */
+static void
+sclp_vt220_emit_current(void)
+{
+ unsigned long flags;
+ struct sclp_vt220_request *request;
+ struct sclp_vt220_sccb *sccb;
+
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ request = NULL;
+ if (sclp_vt220_current_request != NULL) {
+ sccb = (struct sclp_vt220_sccb *)
+ sclp_vt220_current_request->sclp_req.sccb;
+ /* Only emit buffers with content */
+ if (sccb->header.length != sizeof(struct sclp_vt220_sccb)) {
+ request = sclp_vt220_current_request;
+ sclp_vt220_current_request = NULL;
+ if (timer_pending(&sclp_vt220_timer))
+ del_timer(&sclp_vt220_timer);
+ }
+ sclp_vt220_flush_later = 0;
+ }
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ if (request != NULL)
+ sclp_vt220_emit(request);
+}
+
+#define SCLP_NORMAL_WRITE 0x00
+
+/*
+ * Helper function to initialize a page with the sclp request structure.
+ */
+static struct sclp_vt220_request *
+sclp_vt220_initialize_page(void *page)
+{
+ struct sclp_vt220_request *request;
+ struct sclp_vt220_sccb *sccb;
+
+ /* Place request structure at end of page */
+ request = ((struct sclp_vt220_request *)
+ ((addr_t) page + PAGE_SIZE)) - 1;
+ request->retry_count = 0;
+ request->sclp_req.sccb = page;
+ /* SCCB goes at start of page */
+ sccb = (struct sclp_vt220_sccb *) page;
+ memset((void *) sccb, 0, sizeof(struct sclp_vt220_sccb));
+ sccb->header.length = sizeof(struct sclp_vt220_sccb);
+ sccb->header.function_code = SCLP_NORMAL_WRITE;
+ sccb->header.response_code = 0x0000;
+ sccb->evbuf.type = EvTyp_VT220Msg;
+ sccb->evbuf.length = sizeof(struct evbuf_header);
+
+ return request;
+}
+
+static inline unsigned int
+sclp_vt220_space_left(struct sclp_vt220_request *request)
+{
+ struct sclp_vt220_sccb *sccb;
+ sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb;
+ return PAGE_SIZE - sizeof(struct sclp_vt220_request) -
+ sccb->header.length;
+}
+
+static inline unsigned int
+sclp_vt220_chars_stored(struct sclp_vt220_request *request)
+{
+ struct sclp_vt220_sccb *sccb;
+ sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb;
+ return sccb->evbuf.length - sizeof(struct evbuf_header);
+}
+
+/*
+ * Add msg to buffer associated with request. Return the number of characters
+ * added.
+ */
+static int
+sclp_vt220_add_msg(struct sclp_vt220_request *request,
+ const unsigned char *msg, int count, int convertlf)
+{
+ struct sclp_vt220_sccb *sccb;
+ void *buffer;
+ unsigned char c;
+ int from;
+ int to;
+
+ if (count > sclp_vt220_space_left(request))
+ count = sclp_vt220_space_left(request);
+ if (count <= 0)
+ return 0;
+
+ sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb;
+ buffer = (void *) ((addr_t) sccb + sccb->header.length);
+
+ if (convertlf) {
+ /* Perform Linefeed conversion (0x0a -> 0x0a 0x0d)*/
+ for (from=0, to=0;
+ (from < count) && (to < sclp_vt220_space_left(request));
+ from++) {
+ /* Retrieve character */
+ c = msg[from];
+ /* Perform conversion */
+ if (c == 0x0a) {
+ if (to + 1 < sclp_vt220_space_left(request)) {
+ ((unsigned char *) buffer)[to++] = c;
+ ((unsigned char *) buffer)[to++] = 0x0d;
+ } else
+ break;
+
+ } else
+ ((unsigned char *) buffer)[to++] = c;
+ }
+ sccb->header.length += to;
+ sccb->evbuf.length += to;
+ return from;
+ } else {
+ memcpy(buffer, (const void *) msg, count);
+ sccb->header.length += count;
+ sccb->evbuf.length += count;
+ return count;
+ }
+}
+
+/*
+ * Emit buffer after having waited long enough for more data to arrive.
+ */
+static void
+sclp_vt220_timeout(unsigned long data)
+{
+ sclp_vt220_emit_current();
+}
+
+#define BUFFER_MAX_DELAY HZ/2
+
+/*
+ * Internal implementation of the write function. Write COUNT bytes of data
+ * from memory at BUF
+ * to the SCLP interface. In case that the data does not fit into the current
+ * write buffer, emit the current one and allocate a new one. If there are no
+ * more empty buffers available, wait until one gets emptied. If DO_SCHEDULE
+ * is non-zero, the buffer will be scheduled for emitting after a timeout -
+ * otherwise the user has to explicitly call the flush function.
+ * A non-zero CONVERTLF parameter indicates that 0x0a characters in the message
+ * buffer should be converted to 0x0a 0x0d. After completion, return the number
+ * of bytes written.
+ */
+static int
+__sclp_vt220_write(const unsigned char *buf, int count, int do_schedule,
+ int convertlf)
+{
+ unsigned long flags;
+ void *page;
+ int written;
+ int overall_written;
+
+ if (count <= 0)
+ return 0;
+ overall_written = 0;
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ do {
+ /* Create a sclp output buffer if none exists yet */
+ if (sclp_vt220_current_request == NULL) {
+ while (list_empty(&sclp_vt220_empty)) {
+ spin_unlock_irqrestore(&sclp_vt220_lock,
+ flags);
+ if (in_interrupt())
+ sclp_sync_wait();
+ else
+ wait_event(sclp_vt220_waitq,
+ !list_empty(&sclp_vt220_empty));
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ }
+ page = (void *) sclp_vt220_empty.next;
+ list_del((struct list_head *) page);
+ sclp_vt220_current_request =
+ sclp_vt220_initialize_page(page);
+ }
+ /* Try to write the string to the current request buffer */
+ written = sclp_vt220_add_msg(sclp_vt220_current_request,
+ buf, count, convertlf);
+ overall_written += written;
+ if (written == count)
+ break;
+ /*
+ * Not all characters could be written to the current
+ * output buffer. Emit the buffer, create a new buffer
+ * and then output the rest of the string.
+ */
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ sclp_vt220_emit_current();
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ buf += written;
+ count -= written;
+ } while (count > 0);
+ /* Setup timer to output current console buffer after some time */
+ if (sclp_vt220_current_request != NULL &&
+ !timer_pending(&sclp_vt220_timer) && do_schedule) {
+ sclp_vt220_timer.function = sclp_vt220_timeout;
+ sclp_vt220_timer.data = 0UL;
+ sclp_vt220_timer.expires = jiffies + BUFFER_MAX_DELAY;
+ add_timer(&sclp_vt220_timer);
+ }
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ return overall_written;
+}
+
+/*
+ * This routine is called by the kernel to write a series of
+ * characters to the tty device. The characters may come from
+ * user space or kernel space. This routine will return the
+ * number of characters actually accepted for writing.
+ */
+static int
+sclp_vt220_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ return __sclp_vt220_write(buf, count, 1, 0);
+}
+
+#define SCLP_VT220_SESSION_ENDED 0x01
+#define SCLP_VT220_SESSION_STARTED 0x80
+#define SCLP_VT220_SESSION_DATA 0x00
+
+/*
+ * Called by the SCLP to report incoming event buffers.
+ */
+static void
+sclp_vt220_receiver_fn(struct evbuf_header *evbuf)
+{
+ char *buffer;
+ unsigned int count;
+
+ /* Ignore input if device is not open */
+ if (sclp_vt220_tty == NULL)
+ return;
+
+ buffer = (char *) ((addr_t) evbuf + sizeof(struct evbuf_header));
+ count = evbuf->length - sizeof(struct evbuf_header);
+
+ switch (*buffer) {
+ case SCLP_VT220_SESSION_ENDED:
+ case SCLP_VT220_SESSION_STARTED:
+ break;
+ case SCLP_VT220_SESSION_DATA:
+ /* Send input to line discipline */
+ buffer++;
+ count--;
+ /* Prevent buffer overrun by discarding input. Note that
+ * because buffer_push works asynchronously, we cannot wait
+ * for the buffer to be emptied. */
+ if (count + sclp_vt220_tty->flip.count > TTY_FLIPBUF_SIZE)
+ count = TTY_FLIPBUF_SIZE - sclp_vt220_tty->flip.count;
+ memcpy(sclp_vt220_tty->flip.char_buf_ptr, buffer, count);
+ memset(sclp_vt220_tty->flip.flag_buf_ptr, TTY_NORMAL, count);
+ sclp_vt220_tty->flip.char_buf_ptr += count;
+ sclp_vt220_tty->flip.flag_buf_ptr += count;
+ sclp_vt220_tty->flip.count += count;
+ tty_flip_buffer_push(sclp_vt220_tty);
+ break;
+ }
+}
+
+/*
+ * This routine is called when a particular tty device is opened.
+ */
+static int
+sclp_vt220_open(struct tty_struct *tty, struct file *filp)
+{
+ if (tty->count == 1) {
+ sclp_vt220_tty = tty;
+ tty->driver_data = kmalloc(SCLP_VT220_BUF_SIZE, GFP_KERNEL);
+ if (tty->driver_data == NULL)
+ return -ENOMEM;
+ tty->low_latency = 0;
+ }
+ return 0;
+}
+
+/*
+ * This routine is called when a particular tty device is closed.
+ */
+static void
+sclp_vt220_close(struct tty_struct *tty, struct file *filp)
+{
+ if (tty->count == 1) {
+ sclp_vt220_tty = NULL;
+ kfree(tty->driver_data);
+ tty->driver_data = NULL;
+ }
+}
+
+/*
+ * This routine is called by the kernel to write a single
+ * character to the tty device. If the kernel uses this routine,
+ * it must call the flush_chars() routine (if defined) when it is
+ * done stuffing characters into the driver.
+ *
+ * NOTE: include/linux/tty_driver.h specifies that a character should be
+ * ignored if there is no room in the queue. This driver implements a different
+ * semantic in that it will block when there is no more room left.
+ */
+static void
+sclp_vt220_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ __sclp_vt220_write(&ch, 1, 0, 0);
+}
+
+/*
+ * This routine is called by the kernel after it has written a
+ * series of characters to the tty device using put_char().
+ */
+static void
+sclp_vt220_flush_chars(struct tty_struct *tty)
+{
+ if (sclp_vt220_outqueue_count == 0)
+ sclp_vt220_emit_current();
+ else
+ sclp_vt220_flush_later = 1;
+}
+
+/*
+ * This routine returns the numbers of characters the tty driver
+ * will accept for queuing to be written. This number is subject
+ * to change as output buffers get emptied, or if the output flow
+ * control is acted.
+ */
+static int
+sclp_vt220_write_room(struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct list_head *l;
+ int count;
+
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ count = 0;
+ if (sclp_vt220_current_request != NULL)
+ count = sclp_vt220_space_left(sclp_vt220_current_request);
+ list_for_each(l, &sclp_vt220_empty)
+ count += SCLP_VT220_MAX_CHARS_PER_BUFFER;
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ return count;
+}
+
+/*
+ * Return number of buffered chars.
+ */
+static int
+sclp_vt220_chars_in_buffer(struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct list_head *l;
+ struct sclp_vt220_request *r;
+ int count;
+
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ count = 0;
+ if (sclp_vt220_current_request != NULL)
+ count = sclp_vt220_chars_stored(sclp_vt220_current_request);
+ list_for_each(l, &sclp_vt220_outqueue) {
+ r = list_entry(l, struct sclp_vt220_request, list);
+ count += sclp_vt220_chars_stored(r);
+ }
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ return count;
+}
+
+static void
+__sclp_vt220_flush_buffer(void)
+{
+ unsigned long flags;
+
+ sclp_vt220_emit_current();
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ if (timer_pending(&sclp_vt220_timer))
+ del_timer(&sclp_vt220_timer);
+ while (sclp_vt220_outqueue_count > 0) {
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+ sclp_sync_wait();
+ spin_lock_irqsave(&sclp_vt220_lock, flags);
+ }
+ spin_unlock_irqrestore(&sclp_vt220_lock, flags);
+}
+
+/*
+ * Pass on all buffers to the hardware. Return only when there are no more
+ * buffers pending.
+ */
+static void
+sclp_vt220_flush_buffer(struct tty_struct *tty)
+{
+ sclp_vt220_emit_current();
+}
+
+/*
+ * Initialize all relevant components and register driver with system.
+ */
+static int
+__sclp_vt220_init(int early)
+{
+ void *page;
+ int i;
+
+ if (sclp_vt220_initialized)
+ return 0;
+ sclp_vt220_initialized = 1;
+ spin_lock_init(&sclp_vt220_lock);
+ INIT_LIST_HEAD(&sclp_vt220_empty);
+ INIT_LIST_HEAD(&sclp_vt220_outqueue);
+ init_waitqueue_head(&sclp_vt220_waitq);
+ init_timer(&sclp_vt220_timer);
+ sclp_vt220_current_request = NULL;
+ sclp_vt220_buffered_chars = 0;
+ sclp_vt220_outqueue_count = 0;
+ sclp_vt220_tty = NULL;
+ sclp_vt220_flush_later = 0;
+
+ /* Allocate pages for output buffering */
+ for (i = 0; i < (early ? MAX_CONSOLE_PAGES : MAX_KMEM_PAGES); i++) {
+ if (early)
+ page = alloc_bootmem_low_pages(PAGE_SIZE);
+ else
+ page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!page)
+ return -ENOMEM;
+ list_add_tail((struct list_head *) page, &sclp_vt220_empty);
+ }
+ return 0;
+}
+
+static struct tty_operations sclp_vt220_ops = {
+ .open = sclp_vt220_open,
+ .close = sclp_vt220_close,
+ .write = sclp_vt220_write,
+ .put_char = sclp_vt220_put_char,
+ .flush_chars = sclp_vt220_flush_chars,
+ .write_room = sclp_vt220_write_room,
+ .chars_in_buffer = sclp_vt220_chars_in_buffer,
+ .flush_buffer = sclp_vt220_flush_buffer
+};
+
+/*
+ * Register driver with SCLP and Linux and initialize internal tty structures.
+ */
+int __init
+sclp_vt220_tty_init(void)
+{
+ struct tty_driver *driver;
+ int rc;
+
+ /* Note: we're not testing for CONSOLE_IS_SCLP here to preserve
+ * symmetry between VM and LPAR systems regarding ttyS1. */
+ driver = alloc_tty_driver(1);
+ if (!driver)
+ return -ENOMEM;
+ rc = __sclp_vt220_init(0);
+ if (rc) {
+ put_tty_driver(driver);
+ return rc;
+ }
+ rc = sclp_register(&sclp_vt220_register);
+ if (rc) {
+ printk(KERN_ERR SCLP_VT220_PRINT_HEADER
+ "could not register tty - "
+ "sclp_register returned %d\n", rc);
+ put_tty_driver(driver);
+ return rc;
+ }
+
+ driver->owner = THIS_MODULE;
+ driver->driver_name = SCLP_VT220_DRIVER_NAME;
+ driver->name = SCLP_VT220_DEVICE_NAME;
+ driver->major = SCLP_VT220_MAJOR;
+ driver->minor_start = SCLP_VT220_MINOR;
+ driver->type = TTY_DRIVER_TYPE_SYSTEM;
+ driver->subtype = SYSTEM_TYPE_TTY;
+ driver->init_termios = tty_std_termios;
+ driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(driver, &sclp_vt220_ops);
+
+ rc = tty_register_driver(driver);
+ if (rc) {
+ printk(KERN_ERR SCLP_VT220_PRINT_HEADER
+ "could not register tty - "
+ "tty_register_driver returned %d\n", rc);
+ put_tty_driver(driver);
+ return rc;
+ }
+ sclp_vt220_driver = driver;
+ return 0;
+}
+
+module_init(sclp_vt220_tty_init);
+
+#ifdef CONFIG_SCLP_VT220_CONSOLE
+
+static void
+sclp_vt220_con_write(struct console *con, const char *buf, unsigned int count)
+{
+ __sclp_vt220_write((const unsigned char *) buf, count, 1, 1);
+}
+
+static struct tty_driver *
+sclp_vt220_con_device(struct console *c, int *index)
+{
+ *index = 0;
+ return sclp_vt220_driver;
+}
+
+/*
+ * This routine is called from panic when the kernel is going to give up.
+ * We have to make sure that all buffers will be flushed to the SCLP.
+ * Note that this function may be called from within an interrupt context.
+ */
+static void
+sclp_vt220_con_unblank(void)
+{
+ __sclp_vt220_flush_buffer();
+}
+
+/* Structure needed to register with printk */
+static struct console sclp_vt220_console =
+{
+ .name = SCLP_VT220_CONSOLE_NAME,
+ .write = sclp_vt220_con_write,
+ .device = sclp_vt220_con_device,
+ .unblank = sclp_vt220_con_unblank,
+ .flags = CON_PRINTBUFFER,
+ .index = SCLP_VT220_CONSOLE_INDEX
+};
+
+static int __init
+sclp_vt220_con_init(void)
+{
+ int rc;
+
+ if (!CONSOLE_IS_SCLP)
+ return 0;
+ rc = __sclp_vt220_init(1);
+ if (rc)
+ return rc;
+ /* Attach linux console */
+ register_console(&sclp_vt220_console);
+ return 0;
+}
+
+console_initcall(sclp_vt220_con_init);
+#endif /* CONFIG_SCLP_VT220_CONSOLE */
+
diff --git a/drivers/s390/char/tape.h b/drivers/s390/char/tape.h
new file mode 100644
index 000000000000..d04e6c2c3cc1
--- /dev/null
+++ b/drivers/s390/char/tape.h
@@ -0,0 +1,384 @@
+/*
+ * drivers/s390/char/tape.h
+ * tape device driver for 3480/3490E/3590 tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#ifndef _TAPE_H
+#define _TAPE_H
+
+#include <asm/ccwdev.h>
+#include <asm/debug.h>
+#include <asm/idals.h>
+#include <linux/config.h>
+#include <linux/blkdev.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtio.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+
+struct gendisk;
+
+/*
+ * Define DBF_LIKE_HELL for lots of messages in the debug feature.
+ */
+#define DBF_LIKE_HELL
+#ifdef DBF_LIKE_HELL
+#define DBF_LH(level, str, ...) \
+do { \
+ debug_sprintf_event(TAPE_DBF_AREA, level, str, ## __VA_ARGS__); \
+} while (0)
+#else
+#define DBF_LH(level, str, ...) do {} while(0)
+#endif
+
+/*
+ * macros s390 debug feature (dbf)
+ */
+#define DBF_EVENT(d_level, d_str...) \
+do { \
+ debug_sprintf_event(TAPE_DBF_AREA, d_level, d_str); \
+} while (0)
+
+#define DBF_EXCEPTION(d_level, d_str...) \
+do { \
+ debug_sprintf_exception(TAPE_DBF_AREA, d_level, d_str); \
+} while (0)
+
+#define TAPE_VERSION_MAJOR 2
+#define TAPE_VERSION_MINOR 0
+#define TAPE_MAGIC "tape"
+
+#define TAPE_MINORS_PER_DEV 2 /* two minors per device */
+#define TAPEBLOCK_HSEC_SIZE 2048
+#define TAPEBLOCK_HSEC_S2B 2
+#define TAPEBLOCK_RETRIES 5
+
+enum tape_medium_state {
+ MS_UNKNOWN,
+ MS_LOADED,
+ MS_UNLOADED,
+ MS_SIZE
+};
+
+enum tape_state {
+ TS_UNUSED=0,
+ TS_IN_USE,
+ TS_BLKUSE,
+ TS_INIT,
+ TS_NOT_OPER,
+ TS_SIZE
+};
+
+enum tape_op {
+ TO_BLOCK, /* Block read */
+ TO_BSB, /* Backward space block */
+ TO_BSF, /* Backward space filemark */
+ TO_DSE, /* Data security erase */
+ TO_FSB, /* Forward space block */
+ TO_FSF, /* Forward space filemark */
+ TO_LBL, /* Locate block label */
+ TO_NOP, /* No operation */
+ TO_RBA, /* Read backward */
+ TO_RBI, /* Read block information */
+ TO_RFO, /* Read forward */
+ TO_REW, /* Rewind tape */
+ TO_RUN, /* Rewind and unload tape */
+ TO_WRI, /* Write block */
+ TO_WTM, /* Write tape mark */
+ TO_MSEN, /* Medium sense */
+ TO_LOAD, /* Load tape */
+ TO_READ_CONFIG, /* Read configuration data */
+ TO_READ_ATTMSG, /* Read attention message */
+ TO_DIS, /* Tape display */
+ TO_ASSIGN, /* Assign tape to channel path */
+ TO_UNASSIGN, /* Unassign tape from channel path */
+ TO_SIZE /* #entries in tape_op_t */
+};
+
+/* Forward declaration */
+struct tape_device;
+
+/* tape_request->status can be: */
+enum tape_request_status {
+ TAPE_REQUEST_INIT, /* request is ready to be processed */
+ TAPE_REQUEST_QUEUED, /* request is queued to be processed */
+ TAPE_REQUEST_IN_IO, /* request is currently in IO */
+ TAPE_REQUEST_DONE, /* request is completed. */
+};
+
+/* Tape CCW request */
+struct tape_request {
+ struct list_head list; /* list head for request queueing. */
+ struct tape_device *device; /* tape device of this request */
+ struct ccw1 *cpaddr; /* address of the channel program. */
+ void *cpdata; /* pointer to ccw data. */
+ enum tape_request_status status;/* status of this request */
+ int options; /* options for execution. */
+ int retries; /* retry counter for error recovery. */
+ int rescnt; /* residual count from devstat. */
+
+ /* Callback for delivering final status. */
+ void (*callback)(struct tape_request *, void *);
+ void *callback_data;
+
+ enum tape_op op;
+ int rc;
+};
+
+/* Function type for magnetic tape commands */
+typedef int (*tape_mtop_fn)(struct tape_device *, int);
+
+/* Size of the array containing the mtops for a discipline */
+#define TAPE_NR_MTOPS (MTMKPART+1)
+
+/* Tape Discipline */
+struct tape_discipline {
+ struct module *owner;
+ int (*setup_device)(struct tape_device *);
+ void (*cleanup_device)(struct tape_device *);
+ int (*irq)(struct tape_device *, struct tape_request *, struct irb *);
+ struct tape_request *(*read_block)(struct tape_device *, size_t);
+ struct tape_request *(*write_block)(struct tape_device *, size_t);
+ void (*process_eov)(struct tape_device*);
+#ifdef CONFIG_S390_TAPE_BLOCK
+ /* Block device stuff. */
+ struct tape_request *(*bread)(struct tape_device *, struct request *);
+ void (*check_locate)(struct tape_device *, struct tape_request *);
+ void (*free_bread)(struct tape_request *);
+#endif
+ /* ioctl function for additional ioctls. */
+ int (*ioctl_fn)(struct tape_device *, unsigned int, unsigned long);
+ /* Array of tape commands with TAPE_NR_MTOPS entries */
+ tape_mtop_fn *mtop_array;
+};
+
+/*
+ * The discipline irq function either returns an error code (<0) which
+ * means that the request has failed with an error or one of the following:
+ */
+#define TAPE_IO_SUCCESS 0 /* request successful */
+#define TAPE_IO_PENDING 1 /* request still running */
+#define TAPE_IO_RETRY 2 /* retry to current request */
+#define TAPE_IO_STOP 3 /* stop the running request */
+
+/* Char Frontend Data */
+struct tape_char_data {
+ struct idal_buffer *idal_buf; /* idal buffer for user char data */
+ int block_size; /* of size block_size. */
+};
+
+#ifdef CONFIG_S390_TAPE_BLOCK
+/* Block Frontend Data */
+struct tape_blk_data
+{
+ /* Block device request queue. */
+ request_queue_t * request_queue;
+ spinlock_t request_queue_lock;
+
+ /* Task to move entries from block request to CCS request queue. */
+ struct work_struct requeue_task;
+ atomic_t requeue_scheduled;
+
+ /* Current position on the tape. */
+ long block_position;
+ int medium_changed;
+ struct gendisk * disk;
+};
+#endif
+
+/* Tape Info */
+struct tape_device {
+ /* entry in tape_device_list */
+ struct list_head node;
+
+ int cdev_id;
+ struct ccw_device * cdev;
+ struct tape_class_device * nt;
+ struct tape_class_device * rt;
+
+ /* Device discipline information. */
+ struct tape_discipline * discipline;
+ void * discdata;
+
+ /* Generic status flags */
+ long tape_generic_status;
+
+ /* Device state information. */
+ wait_queue_head_t state_change_wq;
+ enum tape_state tape_state;
+ enum tape_medium_state medium_state;
+ unsigned char * modeset_byte;
+
+ /* Reference count. */
+ atomic_t ref_count;
+
+ /* Request queue. */
+ struct list_head req_queue;
+
+ /* Each tape device has (currently) two minor numbers. */
+ int first_minor;
+
+ /* Number of tapemarks required for correct termination. */
+ int required_tapemarks;
+
+ /* Block ID of the BOF */
+ unsigned int bof;
+
+ /* Character device frontend data */
+ struct tape_char_data char_data;
+#ifdef CONFIG_S390_TAPE_BLOCK
+ /* Block dev frontend data */
+ struct tape_blk_data blk_data;
+#endif
+};
+
+/* Externals from tape_core.c */
+extern struct tape_request *tape_alloc_request(int cplength, int datasize);
+extern void tape_free_request(struct tape_request *);
+extern int tape_do_io(struct tape_device *, struct tape_request *);
+extern int tape_do_io_async(struct tape_device *, struct tape_request *);
+extern int tape_do_io_interruptible(struct tape_device *, struct tape_request *);
+void tape_hotplug_event(struct tape_device *, int major, int action);
+
+static inline int
+tape_do_io_free(struct tape_device *device, struct tape_request *request)
+{
+ int rc;
+
+ rc = tape_do_io(device, request);
+ tape_free_request(request);
+ return rc;
+}
+
+extern int tape_oper_handler(int irq, int status);
+extern void tape_noper_handler(int irq, int status);
+extern int tape_open(struct tape_device *);
+extern int tape_release(struct tape_device *);
+extern int tape_mtop(struct tape_device *, int, int);
+extern void tape_state_set(struct tape_device *, enum tape_state);
+
+extern int tape_generic_online(struct tape_device *, struct tape_discipline *);
+extern int tape_generic_offline(struct tape_device *device);
+
+/* Externals from tape_devmap.c */
+extern int tape_generic_probe(struct ccw_device *);
+extern void tape_generic_remove(struct ccw_device *);
+
+extern struct tape_device *tape_get_device(int devindex);
+extern struct tape_device *tape_get_device_reference(struct tape_device *);
+extern struct tape_device *tape_put_device(struct tape_device *);
+
+/* Externals from tape_char.c */
+extern int tapechar_init(void);
+extern void tapechar_exit(void);
+extern int tapechar_setup_device(struct tape_device *);
+extern void tapechar_cleanup_device(struct tape_device *);
+
+/* Externals from tape_block.c */
+#ifdef CONFIG_S390_TAPE_BLOCK
+extern int tapeblock_init (void);
+extern void tapeblock_exit(void);
+extern int tapeblock_setup_device(struct tape_device *);
+extern void tapeblock_cleanup_device(struct tape_device *);
+#else
+static inline int tapeblock_init (void) {return 0;}
+static inline void tapeblock_exit (void) {;}
+static inline int tapeblock_setup_device(struct tape_device *t) {return 0;}
+static inline void tapeblock_cleanup_device (struct tape_device *t) {;}
+#endif
+
+/* tape initialisation functions */
+#ifdef CONFIG_PROC_FS
+extern void tape_proc_init (void);
+extern void tape_proc_cleanup (void);
+#else
+static inline void tape_proc_init (void) {;}
+static inline void tape_proc_cleanup (void) {;}
+#endif
+
+/* a function for dumping device sense info */
+extern void tape_dump_sense(struct tape_device *, struct tape_request *,
+ struct irb *);
+extern void tape_dump_sense_dbf(struct tape_device *, struct tape_request *,
+ struct irb *);
+
+/* functions for handling the status of a device */
+extern void tape_med_state_set(struct tape_device *, enum tape_medium_state);
+
+/* The debug area */
+extern debug_info_t *TAPE_DBF_AREA;
+
+/* functions for building ccws */
+static inline struct ccw1 *
+tape_ccw_cc(struct ccw1 *ccw, __u8 cmd_code, __u16 memsize, void *cda)
+{
+ ccw->cmd_code = cmd_code;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = memsize;
+ ccw->cda = (__u32)(addr_t) cda;
+ return ccw + 1;
+}
+
+static inline struct ccw1 *
+tape_ccw_end(struct ccw1 *ccw, __u8 cmd_code, __u16 memsize, void *cda)
+{
+ ccw->cmd_code = cmd_code;
+ ccw->flags = 0;
+ ccw->count = memsize;
+ ccw->cda = (__u32)(addr_t) cda;
+ return ccw + 1;
+}
+
+static inline struct ccw1 *
+tape_ccw_cmd(struct ccw1 *ccw, __u8 cmd_code)
+{
+ ccw->cmd_code = cmd_code;
+ ccw->flags = 0;
+ ccw->count = 0;
+ ccw->cda = (__u32)(addr_t) &ccw->cmd_code;
+ return ccw + 1;
+}
+
+static inline struct ccw1 *
+tape_ccw_repeat(struct ccw1 *ccw, __u8 cmd_code, int count)
+{
+ while (count-- > 0) {
+ ccw->cmd_code = cmd_code;
+ ccw->flags = CCW_FLAG_CC;
+ ccw->count = 0;
+ ccw->cda = (__u32)(addr_t) &ccw->cmd_code;
+ ccw++;
+ }
+ return ccw;
+}
+
+static inline struct ccw1 *
+tape_ccw_cc_idal(struct ccw1 *ccw, __u8 cmd_code, struct idal_buffer *idal)
+{
+ ccw->cmd_code = cmd_code;
+ ccw->flags = CCW_FLAG_CC;
+ idal_buffer_set_cda(idal, ccw);
+ return ccw++;
+}
+
+static inline struct ccw1 *
+tape_ccw_end_idal(struct ccw1 *ccw, __u8 cmd_code, struct idal_buffer *idal)
+{
+ ccw->cmd_code = cmd_code;
+ ccw->flags = 0;
+ idal_buffer_set_cda(idal, ccw);
+ return ccw++;
+}
+
+/* Global vars */
+extern const char *tape_state_verbose[];
+extern const char *tape_op_verbose[];
+
+#endif /* for ifdef tape.h */
diff --git a/drivers/s390/char/tape_34xx.c b/drivers/s390/char/tape_34xx.c
new file mode 100644
index 000000000000..480ec87976fb
--- /dev/null
+++ b/drivers/s390/char/tape_34xx.c
@@ -0,0 +1,1385 @@
+/*
+ * drivers/s390/char/tape_34xx.c
+ * tape device discipline for 3480/3490 tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/bio.h>
+#include <linux/workqueue.h>
+
+#define TAPE_DBF_AREA tape_34xx_dbf
+
+#include "tape.h"
+#include "tape_std.h"
+
+#define PRINTK_HEADER "TAPE_34XX: "
+
+/*
+ * Pointer to debug area.
+ */
+debug_info_t *TAPE_DBF_AREA = NULL;
+EXPORT_SYMBOL(TAPE_DBF_AREA);
+
+enum tape_34xx_type {
+ tape_3480,
+ tape_3490,
+};
+
+#define TAPE34XX_FMT_3480 0
+#define TAPE34XX_FMT_3480_2_XF 1
+#define TAPE34XX_FMT_3480_XF 2
+
+struct tape_34xx_block_id {
+ unsigned int wrap : 1;
+ unsigned int segment : 7;
+ unsigned int format : 2;
+ unsigned int block : 22;
+};
+
+/*
+ * A list of block ID's is used to faster seek blocks.
+ */
+struct tape_34xx_sbid {
+ struct list_head list;
+ struct tape_34xx_block_id bid;
+};
+
+static void tape_34xx_delete_sbid_from(struct tape_device *, int);
+
+/*
+ * Medium sense for 34xx tapes. There is no 'real' medium sense call.
+ * So we just do a normal sense.
+ */
+static int
+tape_34xx_medium_sense(struct tape_device *device)
+{
+ struct tape_request *request;
+ unsigned char *sense;
+ int rc;
+
+ request = tape_alloc_request(1, 32);
+ if (IS_ERR(request)) {
+ DBF_EXCEPTION(6, "MSEN fail\n");
+ return PTR_ERR(request);
+ }
+
+ request->op = TO_MSEN;
+ tape_ccw_end(request->cpaddr, SENSE, 32, request->cpdata);
+
+ rc = tape_do_io_interruptible(device, request);
+ if (request->rc == 0) {
+ sense = request->cpdata;
+
+ /*
+ * This isn't quite correct. But since INTERVENTION_REQUIRED
+ * means that the drive is 'neither ready nor on-line' it is
+ * only slightly inaccurate to say there is no tape loaded if
+ * the drive isn't online...
+ */
+ if (sense[0] & SENSE_INTERVENTION_REQUIRED)
+ tape_med_state_set(device, MS_UNLOADED);
+ else
+ tape_med_state_set(device, MS_LOADED);
+
+ if (sense[1] & SENSE_WRITE_PROTECT)
+ device->tape_generic_status |= GMT_WR_PROT(~0);
+ else
+ device->tape_generic_status &= ~GMT_WR_PROT(~0);
+ } else {
+ DBF_EVENT(4, "tape_34xx: medium sense failed with rc=%d\n",
+ request->rc);
+ }
+ tape_free_request(request);
+
+ return rc;
+}
+
+/*
+ * These functions are currently used only to schedule a medium_sense for
+ * later execution. This is because we get an interrupt whenever a medium
+ * is inserted but cannot call tape_do_io* from an interrupt context.
+ * Maybe that's useful for other actions we want to start from the
+ * interrupt handler.
+ */
+static void
+tape_34xx_work_handler(void *data)
+{
+ struct {
+ struct tape_device *device;
+ enum tape_op op;
+ struct work_struct work;
+ } *p = data;
+
+ switch(p->op) {
+ case TO_MSEN:
+ tape_34xx_medium_sense(p->device);
+ break;
+ default:
+ DBF_EVENT(3, "T34XX: internal error: unknown work\n");
+ }
+
+ p->device = tape_put_device(p->device);
+ kfree(p);
+}
+
+static int
+tape_34xx_schedule_work(struct tape_device *device, enum tape_op op)
+{
+ struct {
+ struct tape_device *device;
+ enum tape_op op;
+ struct work_struct work;
+ } *p;
+
+ if ((p = kmalloc(sizeof(*p), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ memset(p, 0, sizeof(*p));
+ INIT_WORK(&p->work, tape_34xx_work_handler, p);
+
+ p->device = tape_get_device_reference(device);
+ p->op = op;
+
+ schedule_work(&p->work);
+ return 0;
+}
+
+/*
+ * Done Handler is called when dev stat = DEVICE-END (successful operation)
+ */
+static inline int
+tape_34xx_done(struct tape_request *request)
+{
+ DBF_EVENT(6, "%s done\n", tape_op_verbose[request->op]);
+
+ switch (request->op) {
+ case TO_DSE:
+ case TO_RUN:
+ case TO_WRI:
+ case TO_WTM:
+ case TO_ASSIGN:
+ case TO_UNASSIGN:
+ tape_34xx_delete_sbid_from(request->device, 0);
+ break;
+ default:
+ ;
+ }
+ return TAPE_IO_SUCCESS;
+}
+
+static inline int
+tape_34xx_erp_failed(struct tape_request *request, int rc)
+{
+ DBF_EVENT(3, "Error recovery failed for %s (RC=%d)\n",
+ tape_op_verbose[request->op], rc);
+ return rc;
+}
+
+static inline int
+tape_34xx_erp_succeeded(struct tape_request *request)
+{
+ DBF_EVENT(3, "Error Recovery successful for %s\n",
+ tape_op_verbose[request->op]);
+ return tape_34xx_done(request);
+}
+
+static inline int
+tape_34xx_erp_retry(struct tape_request *request)
+{
+ DBF_EVENT(3, "xerp retr %s\n", tape_op_verbose[request->op]);
+ return TAPE_IO_RETRY;
+}
+
+/*
+ * This function is called, when no request is outstanding and we get an
+ * interrupt
+ */
+static int
+tape_34xx_unsolicited_irq(struct tape_device *device, struct irb *irb)
+{
+ if (irb->scsw.dstat == 0x85 /* READY */) {
+ /* A medium was inserted in the drive. */
+ DBF_EVENT(6, "xuud med\n");
+ tape_34xx_delete_sbid_from(device, 0);
+ tape_34xx_schedule_work(device, TO_MSEN);
+ } else {
+ DBF_EVENT(3, "unsol.irq! dev end: %08x\n", device->cdev_id);
+ PRINT_WARN("Unsolicited IRQ (Device End) caught.\n");
+ tape_dump_sense(device, NULL, irb);
+ }
+ return TAPE_IO_SUCCESS;
+}
+
+/*
+ * Read Opposite Error Recovery Function:
+ * Used, when Read Forward does not work
+ */
+static int
+tape_34xx_erp_read_opposite(struct tape_device *device,
+ struct tape_request *request)
+{
+ if (request->op == TO_RFO) {
+ /*
+ * We did read forward, but the data could not be read
+ * *correctly*. We transform the request to a read backward
+ * and try again.
+ */
+ tape_std_read_backward(device, request);
+ return tape_34xx_erp_retry(request);
+ }
+ if (request->op != TO_RBA)
+ PRINT_ERR("read_opposite called with state:%s\n",
+ tape_op_verbose[request->op]);
+ /*
+ * We tried to read forward and backward, but hat no
+ * success -> failed.
+ */
+ return tape_34xx_erp_failed(request, -EIO);
+}
+
+static int
+tape_34xx_erp_bug(struct tape_device *device, struct tape_request *request,
+ struct irb *irb, int no)
+{
+ if (request->op != TO_ASSIGN) {
+ PRINT_WARN("An unexpected condition #%d was caught in "
+ "tape error recovery.\n", no);
+ PRINT_WARN("Please report this incident.\n");
+ if (request)
+ PRINT_WARN("Operation of tape:%s\n",
+ tape_op_verbose[request->op]);
+ tape_dump_sense(device, request, irb);
+ }
+ return tape_34xx_erp_failed(request, -EIO);
+}
+
+/*
+ * Handle data overrun between cu and drive. The channel speed might
+ * be too slow.
+ */
+static int
+tape_34xx_erp_overrun(struct tape_device *device, struct tape_request *request,
+ struct irb *irb)
+{
+ if (irb->ecw[3] == 0x40) {
+ PRINT_WARN ("Data overrun error between control-unit "
+ "and drive. Use a faster channel connection, "
+ "if possible! \n");
+ return tape_34xx_erp_failed(request, -EIO);
+ }
+ return tape_34xx_erp_bug(device, request, irb, -1);
+}
+
+/*
+ * Handle record sequence error.
+ */
+static int
+tape_34xx_erp_sequence(struct tape_device *device,
+ struct tape_request *request, struct irb *irb)
+{
+ if (irb->ecw[3] == 0x41) {
+ /*
+ * cu detected incorrect block-id sequence on tape.
+ */
+ PRINT_WARN("Illegal block-id sequence found!\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ }
+ /*
+ * Record sequence error bit is set, but erpa does not
+ * show record sequence error.
+ */
+ return tape_34xx_erp_bug(device, request, irb, -2);
+}
+
+/*
+ * This function analyses the tape's sense-data in case of a unit-check.
+ * If possible, it tries to recover from the error. Else the user is
+ * informed about the problem.
+ */
+static int
+tape_34xx_unit_check(struct tape_device *device, struct tape_request *request,
+ struct irb *irb)
+{
+ int inhibit_cu_recovery;
+ __u8* sense;
+
+ inhibit_cu_recovery = (*device->modeset_byte & 0x80) ? 1 : 0;
+ sense = irb->ecw;
+
+#ifdef CONFIG_S390_TAPE_BLOCK
+ if (request->op == TO_BLOCK) {
+ /*
+ * Recovery for block device requests. Set the block_position
+ * to something invalid and retry.
+ */
+ device->blk_data.block_position = -1;
+ if (request->retries-- <= 0)
+ return tape_34xx_erp_failed(request, -EIO);
+ else
+ return tape_34xx_erp_retry(request);
+ }
+#endif
+
+ if (
+ sense[0] & SENSE_COMMAND_REJECT &&
+ sense[1] & SENSE_WRITE_PROTECT
+ ) {
+ if (
+ request->op == TO_DSE ||
+ request->op == TO_WRI ||
+ request->op == TO_WTM
+ ) {
+ /* medium is write protected */
+ return tape_34xx_erp_failed(request, -EACCES);
+ } else {
+ return tape_34xx_erp_bug(device, request, irb, -3);
+ }
+ }
+
+ /*
+ * Special cases for various tape-states when reaching
+ * end of recorded area
+ *
+ * FIXME: Maybe a special case of the special case:
+ * sense[0] == SENSE_EQUIPMENT_CHECK &&
+ * sense[1] == SENSE_DRIVE_ONLINE &&
+ * sense[3] == 0x47 (Volume Fenced)
+ *
+ * This was caused by continued FSF or FSR after an
+ * 'End Of Data'.
+ */
+ if ((
+ sense[0] == SENSE_DATA_CHECK ||
+ sense[0] == SENSE_EQUIPMENT_CHECK ||
+ sense[0] == SENSE_EQUIPMENT_CHECK + SENSE_DEFERRED_UNIT_CHECK
+ ) && (
+ sense[1] == SENSE_DRIVE_ONLINE ||
+ sense[1] == SENSE_BEGINNING_OF_TAPE + SENSE_WRITE_MODE
+ )) {
+ switch (request->op) {
+ /*
+ * sense[0] == SENSE_DATA_CHECK &&
+ * sense[1] == SENSE_DRIVE_ONLINE
+ * sense[3] == 0x36 (End Of Data)
+ *
+ * Further seeks might return a 'Volume Fenced'.
+ */
+ case TO_FSF:
+ case TO_FSB:
+ /* Trying to seek beyond end of recorded area */
+ return tape_34xx_erp_failed(request, -ENOSPC);
+ case TO_BSB:
+ return tape_34xx_erp_retry(request);
+
+ /*
+ * sense[0] == SENSE_DATA_CHECK &&
+ * sense[1] == SENSE_DRIVE_ONLINE &&
+ * sense[3] == 0x36 (End Of Data)
+ */
+ case TO_LBL:
+ /* Block could not be located. */
+ tape_34xx_delete_sbid_from(device, 0);
+ return tape_34xx_erp_failed(request, -EIO);
+
+ case TO_RFO:
+ /* Read beyond end of recorded area -> 0 bytes read */
+ return tape_34xx_erp_failed(request, 0);
+
+ /*
+ * sense[0] == SENSE_EQUIPMENT_CHECK &&
+ * sense[1] == SENSE_DRIVE_ONLINE &&
+ * sense[3] == 0x38 (Physical End Of Volume)
+ */
+ case TO_WRI:
+ /* Writing at physical end of volume */
+ return tape_34xx_erp_failed(request, -ENOSPC);
+ default:
+ PRINT_ERR("Invalid op in %s:%i\n",
+ __FUNCTION__, __LINE__);
+ return tape_34xx_erp_failed(request, 0);
+ }
+ }
+
+ /* Sensing special bits */
+ if (sense[0] & SENSE_BUS_OUT_CHECK)
+ return tape_34xx_erp_retry(request);
+
+ if (sense[0] & SENSE_DATA_CHECK) {
+ /*
+ * hardware failure, damaged tape or improper
+ * operating conditions
+ */
+ switch (sense[3]) {
+ case 0x23:
+ /* a read data check occurred */
+ if ((sense[2] & SENSE_TAPE_SYNC_MODE) ||
+ inhibit_cu_recovery)
+ // data check is not permanent, may be
+ // recovered. We always use async-mode with
+ // cu-recovery, so this should *never* happen.
+ return tape_34xx_erp_bug(device, request,
+ irb, -4);
+
+ /* data check is permanent, CU recovery has failed */
+ PRINT_WARN("Permanent read error\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x25:
+ // a write data check occurred
+ if ((sense[2] & SENSE_TAPE_SYNC_MODE) ||
+ inhibit_cu_recovery)
+ // data check is not permanent, may be
+ // recovered. We always use async-mode with
+ // cu-recovery, so this should *never* happen.
+ return tape_34xx_erp_bug(device, request,
+ irb, -5);
+
+ // data check is permanent, cu-recovery has failed
+ PRINT_WARN("Permanent write error\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x26:
+ /* Data Check (read opposite) occurred. */
+ return tape_34xx_erp_read_opposite(device, request);
+ case 0x28:
+ /* ID-Mark at tape start couldn't be written */
+ PRINT_WARN("ID-Mark could not be written.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x31:
+ /* Tape void. Tried to read beyond end of device. */
+ PRINT_WARN("Read beyond end of recorded area.\n");
+ return tape_34xx_erp_failed(request, -ENOSPC);
+ case 0x41:
+ /* Record sequence error. */
+ PRINT_WARN("Invalid block-id sequence found.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ default:
+ /* all data checks for 3480 should result in one of
+ * the above erpa-codes. For 3490, other data-check
+ * conditions do exist. */
+ if (device->cdev->id.driver_info == tape_3480)
+ return tape_34xx_erp_bug(device, request,
+ irb, -6);
+ }
+ }
+
+ if (sense[0] & SENSE_OVERRUN)
+ return tape_34xx_erp_overrun(device, request, irb);
+
+ if (sense[1] & SENSE_RECORD_SEQUENCE_ERR)
+ return tape_34xx_erp_sequence(device, request, irb);
+
+ /* Sensing erpa codes */
+ switch (sense[3]) {
+ case 0x00:
+ /* Unit check with erpa code 0. Report and ignore. */
+ PRINT_WARN("Non-error sense was found. "
+ "Unit-check will be ignored.\n");
+ return TAPE_IO_SUCCESS;
+ case 0x21:
+ /*
+ * Data streaming not operational. CU will switch to
+ * interlock mode. Reissue the command.
+ */
+ PRINT_WARN("Data streaming not operational. "
+ "Switching to interlock-mode.\n");
+ return tape_34xx_erp_retry(request);
+ case 0x22:
+ /*
+ * Path equipment check. Might be drive adapter error, buffer
+ * error on the lower interface, internal path not usable,
+ * or error during cartridge load.
+ */
+ PRINT_WARN("A path equipment check occurred. One of the "
+ "following conditions occurred:\n");
+ PRINT_WARN("drive adapter error, buffer error on the lower "
+ "interface, internal path not usable, error "
+ "during cartridge load.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x24:
+ /*
+ * Load display check. Load display was command was issued,
+ * but the drive is displaying a drive check message. Can
+ * be threated as "device end".
+ */
+ return tape_34xx_erp_succeeded(request);
+ case 0x27:
+ /*
+ * Command reject. May indicate illegal channel program or
+ * buffer over/underrun. Since all channel programs are
+ * issued by this driver and ought be correct, we assume a
+ * over/underrun situation and retry the channel program.
+ */
+ return tape_34xx_erp_retry(request);
+ case 0x29:
+ /*
+ * Function incompatible. Either the tape is idrc compressed
+ * but the hardware isn't capable to do idrc, or a perform
+ * subsystem func is issued and the CU is not on-line.
+ */
+ PRINT_WARN ("Function incompatible. Try to switch off idrc\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x2a:
+ /*
+ * Unsolicited environmental data. An internal counter
+ * overflows, we can ignore this and reissue the cmd.
+ */
+ return tape_34xx_erp_retry(request);
+ case 0x2b:
+ /*
+ * Environmental data present. Indicates either unload
+ * completed ok or read buffered log command completed ok.
+ */
+ if (request->op == TO_RUN) {
+ /* Rewind unload completed ok. */
+ tape_med_state_set(device, MS_UNLOADED);
+ return tape_34xx_erp_succeeded(request);
+ }
+ /* tape_34xx doesn't use read buffered log commands. */
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ case 0x2c:
+ /*
+ * Permanent equipment check. CU has tried recovery, but
+ * did not succeed.
+ */
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x2d:
+ /* Data security erase failure. */
+ if (request->op == TO_DSE)
+ return tape_34xx_erp_failed(request, -EIO);
+ /* Data security erase failure, but no such command issued. */
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ case 0x2e:
+ /*
+ * Not capable. This indicates either that the drive fails
+ * reading the format id mark or that that format specified
+ * is not supported by the drive.
+ */
+ PRINT_WARN("Drive not capable processing the tape format!\n");
+ return tape_34xx_erp_failed(request, -EMEDIUMTYPE);
+ case 0x30:
+ /* The medium is write protected. */
+ PRINT_WARN("Medium is write protected!\n");
+ return tape_34xx_erp_failed(request, -EACCES);
+ case 0x32:
+ // Tension loss. We cannot recover this, it's an I/O error.
+ PRINT_WARN("The drive lost tape tension.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x33:
+ /*
+ * Load Failure. The cartridge was not inserted correctly or
+ * the tape is not threaded correctly.
+ */
+ PRINT_WARN("Cartridge load failure. Reload the cartridge "
+ "and try again.\n");
+ tape_34xx_delete_sbid_from(device, 0);
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x34:
+ /*
+ * Unload failure. The drive cannot maintain tape tension
+ * and control tape movement during an unload operation.
+ */
+ PRINT_WARN("Failure during cartridge unload. "
+ "Please try manually.\n");
+ if (request->op == TO_RUN)
+ return tape_34xx_erp_failed(request, -EIO);
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ case 0x35:
+ /*
+ * Drive equipment check. One of the following:
+ * - cu cannot recover from a drive detected error
+ * - a check code message is shown on drive display
+ * - the cartridge loader does not respond correctly
+ * - a failure occurs during an index, load, or unload cycle
+ */
+ PRINT_WARN("Equipment check! Please check the drive and "
+ "the cartridge loader.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x36:
+ if (device->cdev->id.driver_info == tape_3490)
+ /* End of data. */
+ return tape_34xx_erp_failed(request, -EIO);
+ /* This erpa is reserved for 3480 */
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ case 0x37:
+ /*
+ * Tape length error. The tape is shorter than reported in
+ * the beginning-of-tape data.
+ */
+ PRINT_WARN("Tape length error.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x38:
+ /*
+ * Physical end of tape. A read/write operation reached
+ * the physical end of tape.
+ */
+ if (request->op==TO_WRI ||
+ request->op==TO_DSE ||
+ request->op==TO_WTM)
+ return tape_34xx_erp_failed(request, -ENOSPC);
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x39:
+ /* Backward at Beginning of tape. */
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x3a:
+ /* Drive switched to not ready. */
+ PRINT_WARN("Drive not ready. Turn the ready/not ready switch "
+ "to ready position and try again.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x3b:
+ /* Manual rewind or unload. This causes an I/O error. */
+ PRINT_WARN("Medium was rewound or unloaded manually.\n");
+ tape_34xx_delete_sbid_from(device, 0);
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x42:
+ /*
+ * Degraded mode. A condition that can cause degraded
+ * performance is detected.
+ */
+ PRINT_WARN("Subsystem is running in degraded mode.\n");
+ return tape_34xx_erp_retry(request);
+ case 0x43:
+ /* Drive not ready. */
+ tape_34xx_delete_sbid_from(device, 0);
+ tape_med_state_set(device, MS_UNLOADED);
+ /* Some commands commands are successful even in this case */
+ if (sense[1] & SENSE_DRIVE_ONLINE) {
+ switch(request->op) {
+ case TO_ASSIGN:
+ case TO_UNASSIGN:
+ case TO_DIS:
+ case TO_NOP:
+ return tape_34xx_done(request);
+ break;
+ default:
+ break;
+ }
+ }
+ PRINT_WARN("The drive is not ready.\n");
+ return tape_34xx_erp_failed(request, -ENOMEDIUM);
+ case 0x44:
+ /* Locate Block unsuccessful. */
+ if (request->op != TO_BLOCK && request->op != TO_LBL)
+ /* No locate block was issued. */
+ return tape_34xx_erp_bug(device, request,
+ irb, sense[3]);
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x45:
+ /* The drive is assigned to a different channel path. */
+ PRINT_WARN("The drive is assigned elsewhere.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x46:
+ /*
+ * Drive not on-line. Drive may be switched offline,
+ * the power supply may be switched off or
+ * the drive address may not be set correctly.
+ */
+ PRINT_WARN("The drive is not on-line.");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x47:
+ /* Volume fenced. CU reports volume integrity is lost. */
+ PRINT_WARN("Volume fenced. The volume integrity is lost.\n");
+ tape_34xx_delete_sbid_from(device, 0);
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x48:
+ /* Log sense data and retry request. */
+ return tape_34xx_erp_retry(request);
+ case 0x49:
+ /* Bus out check. A parity check error on the bus was found. */
+ PRINT_WARN("Bus out check. A data transfer over the bus "
+ "has been corrupted.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x4a:
+ /* Control unit erp failed. */
+ PRINT_WARN("The control unit I/O error recovery failed.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x4b:
+ /*
+ * CU and drive incompatible. The drive requests micro-program
+ * patches, which are not available on the CU.
+ */
+ PRINT_WARN("The drive needs microprogram patches from the "
+ "control unit, which are not available.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x4c:
+ /*
+ * Recovered Check-One failure. Cu develops a hardware error,
+ * but is able to recover.
+ */
+ return tape_34xx_erp_retry(request);
+ case 0x4d:
+ if (device->cdev->id.driver_info == tape_3490)
+ /*
+ * Resetting event received. Since the driver does
+ * not support resetting event recovery (which has to
+ * be handled by the I/O Layer), retry our command.
+ */
+ return tape_34xx_erp_retry(request);
+ /* This erpa is reserved for 3480. */
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ case 0x4e:
+ if (device->cdev->id.driver_info == tape_3490) {
+ /*
+ * Maximum block size exceeded. This indicates, that
+ * the block to be written is larger than allowed for
+ * buffered mode.
+ */
+ PRINT_WARN("Maximum block size for buffered "
+ "mode exceeded.\n");
+ return tape_34xx_erp_failed(request, -ENOBUFS);
+ }
+ /* This erpa is reserved for 3480. */
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ case 0x50:
+ /*
+ * Read buffered log (Overflow). CU is running in extended
+ * buffered log mode, and a counter overflows. This should
+ * never happen, since we're never running in extended
+ * buffered log mode.
+ */
+ return tape_34xx_erp_retry(request);
+ case 0x51:
+ /*
+ * Read buffered log (EOV). EOF processing occurs while the
+ * CU is in extended buffered log mode. This should never
+ * happen, since we're never running in extended buffered
+ * log mode.
+ */
+ return tape_34xx_erp_retry(request);
+ case 0x52:
+ /* End of Volume complete. Rewind unload completed ok. */
+ if (request->op == TO_RUN) {
+ tape_med_state_set(device, MS_UNLOADED);
+ tape_34xx_delete_sbid_from(device, 0);
+ return tape_34xx_erp_succeeded(request);
+ }
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ case 0x53:
+ /* Global command intercept. */
+ return tape_34xx_erp_retry(request);
+ case 0x54:
+ /* Channel interface recovery (temporary). */
+ return tape_34xx_erp_retry(request);
+ case 0x55:
+ /* Channel interface recovery (permanent). */
+ PRINT_WARN("A permanent channel interface error occurred.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x56:
+ /* Channel protocol error. */
+ PRINT_WARN("A channel protocol error occurred.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x57:
+ if (device->cdev->id.driver_info == tape_3480) {
+ /* Attention intercept. */
+ PRINT_WARN("An attention intercept occurred, "
+ "which will be recovered.\n");
+ return tape_34xx_erp_retry(request);
+ } else {
+ /* Global status intercept. */
+ PRINT_WARN("An global status intercept was received, "
+ "which will be recovered.\n");
+ return tape_34xx_erp_retry(request);
+ }
+ case 0x5a:
+ /*
+ * Tape length incompatible. The tape inserted is too long,
+ * which could cause damage to the tape or the drive.
+ */
+ PRINT_WARN("Tape Length Incompatible\n");
+ PRINT_WARN("Tape length exceeds IBM enhanced capacity "
+ "cartdridge length or a medium\n");
+ PRINT_WARN("with EC-CST identification mark has been mounted "
+ "in a device that writes\n");
+ PRINT_WARN("3480 or 3480 XF format.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x5b:
+ /* Format 3480 XF incompatible */
+ if (sense[1] & SENSE_BEGINNING_OF_TAPE)
+ /* The tape will get overwritten. */
+ return tape_34xx_erp_retry(request);
+ PRINT_WARN("Format 3480 XF Incompatible\n");
+ PRINT_WARN("Medium has been created in 3480 format. "
+ "To change the format writes\n");
+ PRINT_WARN("must be issued at BOT.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x5c:
+ /* Format 3480-2 XF incompatible */
+ PRINT_WARN("Format 3480-2 XF Incompatible\n");
+ PRINT_WARN("Device can only read 3480 or 3480 XF format.\n");
+ return tape_34xx_erp_failed(request, -EIO);
+ case 0x5d:
+ /* Tape length violation. */
+ PRINT_WARN("Tape Length Violation\n");
+ PRINT_WARN("The mounted tape exceeds IBM Enhanced Capacity "
+ "Cartdridge System Tape length.\n");
+ PRINT_WARN("This may cause damage to the drive or tape when "
+ "processing to the EOV\n");
+ return tape_34xx_erp_failed(request, -EMEDIUMTYPE);
+ case 0x5e:
+ /* Compaction algorithm incompatible. */
+ PRINT_WARN("Compaction Algorithm Incompatible\n");
+ PRINT_WARN("The volume is recorded using an incompatible "
+ "compaction algorithm,\n");
+ PRINT_WARN("which is not supported by the device.\n");
+ return tape_34xx_erp_failed(request, -EMEDIUMTYPE);
+
+ /* The following erpas should have been covered earlier. */
+ case 0x23: /* Read data check. */
+ case 0x25: /* Write data check. */
+ case 0x26: /* Data check (read opposite). */
+ case 0x28: /* Write id mark check. */
+ case 0x31: /* Tape void. */
+ case 0x40: /* Overrun error. */
+ case 0x41: /* Record sequence error. */
+ /* All other erpas are reserved for future use. */
+ default:
+ return tape_34xx_erp_bug(device, request, irb, sense[3]);
+ }
+}
+
+/*
+ * 3480/3490 interrupt handler
+ */
+static int
+tape_34xx_irq(struct tape_device *device, struct tape_request *request,
+ struct irb *irb)
+{
+ if (request == NULL)
+ return tape_34xx_unsolicited_irq(device, irb);
+
+ if ((irb->scsw.dstat & DEV_STAT_UNIT_EXCEP) &&
+ (irb->scsw.dstat & DEV_STAT_DEV_END) &&
+ (request->op == TO_WRI)) {
+ /* Write at end of volume */
+ PRINT_INFO("End of volume\n"); /* XXX */
+ return tape_34xx_erp_failed(request, -ENOSPC);
+ }
+
+ if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
+ return tape_34xx_unit_check(device, request, irb);
+
+ if (irb->scsw.dstat & DEV_STAT_DEV_END) {
+ /*
+ * A unit exception occurs on skipping over a tapemark block.
+ */
+ if (irb->scsw.dstat & DEV_STAT_UNIT_EXCEP) {
+ if (request->op == TO_BSB || request->op == TO_FSB)
+ request->rescnt++;
+ else
+ DBF_EVENT(5, "Unit Exception!\n");
+ }
+ return tape_34xx_done(request);
+ }
+
+ DBF_EVENT(6, "xunknownirq\n");
+ PRINT_ERR("Unexpected interrupt.\n");
+ PRINT_ERR("Current op is: %s", tape_op_verbose[request->op]);
+ tape_dump_sense(device, request, irb);
+ return TAPE_IO_STOP;
+}
+
+/*
+ * ioctl_overload
+ */
+static int
+tape_34xx_ioctl(struct tape_device *device, unsigned int cmd, unsigned long arg)
+{
+ if (cmd == TAPE390_DISPLAY) {
+ struct display_struct disp;
+
+ if (copy_from_user(&disp, (char __user *) arg, sizeof(disp)) != 0)
+ return -EFAULT;
+
+ return tape_std_display(device, &disp);
+ } else
+ return -EINVAL;
+}
+
+static inline void
+tape_34xx_append_new_sbid(struct tape_34xx_block_id bid, struct list_head *l)
+{
+ struct tape_34xx_sbid * new_sbid;
+
+ new_sbid = kmalloc(sizeof(*new_sbid), GFP_ATOMIC);
+ if (!new_sbid)
+ return;
+
+ new_sbid->bid = bid;
+ list_add(&new_sbid->list, l);
+}
+
+/*
+ * Build up the search block ID list. The block ID consists of a logical
+ * block number and a hardware specific part. The hardware specific part
+ * helps the tape drive to speed up searching for a specific block.
+ */
+static void
+tape_34xx_add_sbid(struct tape_device *device, struct tape_34xx_block_id bid)
+{
+ struct list_head * sbid_list;
+ struct tape_34xx_sbid * sbid;
+ struct list_head * l;
+
+ /*
+ * immediately return if there is no list at all or the block to add
+ * is located in segment 1 of wrap 0 because this position is used
+ * if no hardware position data is supplied.
+ */
+ sbid_list = (struct list_head *) device->discdata;
+ if (!sbid_list || (bid.segment < 2 && bid.wrap == 0))
+ return;
+
+ /*
+ * Search the position where to insert the new entry. Hardware
+ * acceleration uses only the segment and wrap number. So we
+ * need only one entry for a specific wrap/segment combination.
+ * If there is a block with a lower number but the same hard-
+ * ware position data we just update the block number in the
+ * existing entry.
+ */
+ list_for_each(l, sbid_list) {
+ sbid = list_entry(l, struct tape_34xx_sbid, list);
+
+ if (
+ (sbid->bid.segment == bid.segment) &&
+ (sbid->bid.wrap == bid.wrap)
+ ) {
+ if (bid.block < sbid->bid.block)
+ sbid->bid = bid;
+ else return;
+ break;
+ }
+
+ /* Sort in according to logical block number. */
+ if (bid.block < sbid->bid.block) {
+ tape_34xx_append_new_sbid(bid, l->prev);
+ break;
+ }
+ }
+ /* List empty or new block bigger than last entry. */
+ if (l == sbid_list)
+ tape_34xx_append_new_sbid(bid, l->prev);
+
+ DBF_LH(4, "Current list is:\n");
+ list_for_each(l, sbid_list) {
+ sbid = list_entry(l, struct tape_34xx_sbid, list);
+ DBF_LH(4, "%d:%03d@%05d\n",
+ sbid->bid.wrap,
+ sbid->bid.segment,
+ sbid->bid.block
+ );
+ }
+}
+
+/*
+ * Delete all entries from the search block ID list that belong to tape blocks
+ * equal or higher than the given number.
+ */
+static void
+tape_34xx_delete_sbid_from(struct tape_device *device, int from)
+{
+ struct list_head * sbid_list;
+ struct tape_34xx_sbid * sbid;
+ struct list_head * l;
+ struct list_head * n;
+
+ sbid_list = (struct list_head *) device->discdata;
+ if (!sbid_list)
+ return;
+
+ list_for_each_safe(l, n, sbid_list) {
+ sbid = list_entry(l, struct tape_34xx_sbid, list);
+ if (sbid->bid.block >= from) {
+ DBF_LH(4, "Delete sbid %d:%03d@%05d\n",
+ sbid->bid.wrap,
+ sbid->bid.segment,
+ sbid->bid.block
+ );
+ list_del(l);
+ kfree(sbid);
+ }
+ }
+}
+
+/*
+ * Merge hardware position data into a block id.
+ */
+static void
+tape_34xx_merge_sbid(
+ struct tape_device * device,
+ struct tape_34xx_block_id * bid
+) {
+ struct tape_34xx_sbid * sbid;
+ struct tape_34xx_sbid * sbid_to_use;
+ struct list_head * sbid_list;
+ struct list_head * l;
+
+ sbid_list = (struct list_head *) device->discdata;
+ bid->wrap = 0;
+ bid->segment = 1;
+
+ if (!sbid_list || list_empty(sbid_list))
+ return;
+
+ sbid_to_use = NULL;
+ list_for_each(l, sbid_list) {
+ sbid = list_entry(l, struct tape_34xx_sbid, list);
+
+ if (sbid->bid.block >= bid->block)
+ break;
+ sbid_to_use = sbid;
+ }
+ if (sbid_to_use) {
+ bid->wrap = sbid_to_use->bid.wrap;
+ bid->segment = sbid_to_use->bid.segment;
+ DBF_LH(4, "Use %d:%03d@%05d for %05d\n",
+ sbid_to_use->bid.wrap,
+ sbid_to_use->bid.segment,
+ sbid_to_use->bid.block,
+ bid->block
+ );
+ }
+}
+
+static int
+tape_34xx_setup_device(struct tape_device * device)
+{
+ int rc;
+ struct list_head * discdata;
+
+ DBF_EVENT(6, "34xx device setup\n");
+ if ((rc = tape_std_assign(device)) == 0) {
+ if ((rc = tape_34xx_medium_sense(device)) != 0) {
+ DBF_LH(3, "34xx medium sense returned %d\n", rc);
+ }
+ }
+ discdata = kmalloc(sizeof(struct list_head), GFP_KERNEL);
+ if (discdata) {
+ INIT_LIST_HEAD(discdata);
+ device->discdata = discdata;
+ }
+
+ return rc;
+}
+
+static void
+tape_34xx_cleanup_device(struct tape_device *device)
+{
+ tape_std_unassign(device);
+
+ if (device->discdata) {
+ tape_34xx_delete_sbid_from(device, 0);
+ kfree(device->discdata);
+ device->discdata = NULL;
+ }
+}
+
+
+/*
+ * MTTELL: Tell block. Return the number of block relative to current file.
+ */
+static int
+tape_34xx_mttell(struct tape_device *device, int mt_count)
+{
+ struct {
+ struct tape_34xx_block_id cbid;
+ struct tape_34xx_block_id dbid;
+ } __attribute__ ((packed)) block_id;
+ int rc;
+
+ rc = tape_std_read_block_id(device, (__u64 *) &block_id);
+ if (rc)
+ return rc;
+
+ tape_34xx_add_sbid(device, block_id.cbid);
+ return block_id.cbid.block;
+}
+
+/*
+ * MTSEEK: seek to the specified block.
+ */
+static int
+tape_34xx_mtseek(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct tape_34xx_block_id * bid;
+
+ if (mt_count > 0x3fffff) {
+ DBF_EXCEPTION(6, "xsee parm\n");
+ return -EINVAL;
+ }
+ request = tape_alloc_request(3, 4);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+
+ /* setup ccws */
+ request->op = TO_LBL;
+ bid = (struct tape_34xx_block_id *) request->cpdata;
+ bid->format = (*device->modeset_byte & 0x08) ?
+ TAPE34XX_FMT_3480_XF : TAPE34XX_FMT_3480;
+ bid->block = mt_count;
+ tape_34xx_merge_sbid(device, bid);
+
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_cc(request->cpaddr + 1, LOCATE, 4, request->cpdata);
+ tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
+
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+#ifdef CONFIG_S390_TAPE_BLOCK
+/*
+ * Tape block read for 34xx.
+ */
+static struct tape_request *
+tape_34xx_bread(struct tape_device *device, struct request *req)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+ int count = 0, i;
+ unsigned off;
+ char *dst;
+ struct bio_vec *bv;
+ struct bio *bio;
+ struct tape_34xx_block_id * start_block;
+
+ DBF_EVENT(6, "xBREDid:");
+
+ /* Count the number of blocks for the request. */
+ rq_for_each_bio(bio, req) {
+ bio_for_each_segment(bv, bio, i) {
+ count += bv->bv_len >> (TAPEBLOCK_HSEC_S2B + 9);
+ }
+ }
+
+ /* Allocate the ccw request. */
+ request = tape_alloc_request(3+count+1, 8);
+ if (IS_ERR(request))
+ return request;
+
+ /* Setup ccws. */
+ request->op = TO_BLOCK;
+ start_block = (struct tape_34xx_block_id *) request->cpdata;
+ start_block->block = req->sector >> TAPEBLOCK_HSEC_S2B;
+ DBF_EVENT(6, "start_block = %i\n", start_block->block);
+
+ ccw = request->cpaddr;
+ ccw = tape_ccw_cc(ccw, MODE_SET_DB, 1, device->modeset_byte);
+
+ /*
+ * We always setup a nop after the mode set ccw. This slot is
+ * used in tape_std_check_locate to insert a locate ccw if the
+ * current tape position doesn't match the start block to be read.
+ * The second nop will be filled with a read block id which is in
+ * turn used by tape_34xx_free_bread to populate the segment bid
+ * table.
+ */
+ ccw = tape_ccw_cc(ccw, NOP, 0, NULL);
+ ccw = tape_ccw_cc(ccw, NOP, 0, NULL);
+
+ rq_for_each_bio(bio, req) {
+ bio_for_each_segment(bv, bio, i) {
+ dst = kmap(bv->bv_page) + bv->bv_offset;
+ for (off = 0; off < bv->bv_len;
+ off += TAPEBLOCK_HSEC_SIZE) {
+ ccw->flags = CCW_FLAG_CC;
+ ccw->cmd_code = READ_FORWARD;
+ ccw->count = TAPEBLOCK_HSEC_SIZE;
+ set_normalized_cda(ccw, (void*) __pa(dst));
+ ccw++;
+ dst += TAPEBLOCK_HSEC_SIZE;
+ }
+ }
+ }
+
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+ DBF_EVENT(6, "xBREDccwg\n");
+ return request;
+}
+
+static void
+tape_34xx_free_bread (struct tape_request *request)
+{
+ struct ccw1* ccw;
+
+ ccw = request->cpaddr;
+ if ((ccw + 2)->cmd_code == READ_BLOCK_ID) {
+ struct {
+ struct tape_34xx_block_id cbid;
+ struct tape_34xx_block_id dbid;
+ } __attribute__ ((packed)) *rbi_data;
+
+ rbi_data = request->cpdata;
+
+ if (request->device)
+ tape_34xx_add_sbid(request->device, rbi_data->cbid);
+ }
+
+ /* Last ccw is a nop and doesn't need clear_normalized_cda */
+ for (; ccw->flags & CCW_FLAG_CC; ccw++)
+ if (ccw->cmd_code == READ_FORWARD)
+ clear_normalized_cda(ccw);
+ tape_free_request(request);
+}
+
+/*
+ * check_locate is called just before the tape request is passed to
+ * the common io layer for execution. It has to check the current
+ * tape position and insert a locate ccw if it doesn't match the
+ * start block for the request.
+ */
+static void
+tape_34xx_check_locate(struct tape_device *device, struct tape_request *request)
+{
+ struct tape_34xx_block_id * start_block;
+
+ start_block = (struct tape_34xx_block_id *) request->cpdata;
+ if (start_block->block == device->blk_data.block_position)
+ return;
+
+ DBF_LH(4, "Block seek(%06d+%06d)\n", start_block->block, device->bof);
+ start_block->wrap = 0;
+ start_block->segment = 1;
+ start_block->format = (*device->modeset_byte & 0x08) ?
+ TAPE34XX_FMT_3480_XF :
+ TAPE34XX_FMT_3480;
+ start_block->block = start_block->block + device->bof;
+ tape_34xx_merge_sbid(device, start_block);
+ tape_ccw_cc(request->cpaddr + 1, LOCATE, 4, request->cpdata);
+ tape_ccw_cc(request->cpaddr + 2, READ_BLOCK_ID, 8, request->cpdata);
+}
+#endif
+
+/*
+ * List of 3480/3490 magnetic tape commands.
+ */
+static tape_mtop_fn tape_34xx_mtop[TAPE_NR_MTOPS] = {
+ [MTRESET] = tape_std_mtreset,
+ [MTFSF] = tape_std_mtfsf,
+ [MTBSF] = tape_std_mtbsf,
+ [MTFSR] = tape_std_mtfsr,
+ [MTBSR] = tape_std_mtbsr,
+ [MTWEOF] = tape_std_mtweof,
+ [MTREW] = tape_std_mtrew,
+ [MTOFFL] = tape_std_mtoffl,
+ [MTNOP] = tape_std_mtnop,
+ [MTRETEN] = tape_std_mtreten,
+ [MTBSFM] = tape_std_mtbsfm,
+ [MTFSFM] = tape_std_mtfsfm,
+ [MTEOM] = tape_std_mteom,
+ [MTERASE] = tape_std_mterase,
+ [MTRAS1] = NULL,
+ [MTRAS2] = NULL,
+ [MTRAS3] = NULL,
+ [MTSETBLK] = tape_std_mtsetblk,
+ [MTSETDENSITY] = NULL,
+ [MTSEEK] = tape_34xx_mtseek,
+ [MTTELL] = tape_34xx_mttell,
+ [MTSETDRVBUFFER] = NULL,
+ [MTFSS] = NULL,
+ [MTBSS] = NULL,
+ [MTWSM] = NULL,
+ [MTLOCK] = NULL,
+ [MTUNLOCK] = NULL,
+ [MTLOAD] = tape_std_mtload,
+ [MTUNLOAD] = tape_std_mtunload,
+ [MTCOMPRESSION] = tape_std_mtcompression,
+ [MTSETPART] = NULL,
+ [MTMKPART] = NULL
+};
+
+/*
+ * Tape discipline structure for 3480 and 3490.
+ */
+static struct tape_discipline tape_discipline_34xx = {
+ .owner = THIS_MODULE,
+ .setup_device = tape_34xx_setup_device,
+ .cleanup_device = tape_34xx_cleanup_device,
+ .process_eov = tape_std_process_eov,
+ .irq = tape_34xx_irq,
+ .read_block = tape_std_read_block,
+ .write_block = tape_std_write_block,
+#ifdef CONFIG_S390_TAPE_BLOCK
+ .bread = tape_34xx_bread,
+ .free_bread = tape_34xx_free_bread,
+ .check_locate = tape_34xx_check_locate,
+#endif
+ .ioctl_fn = tape_34xx_ioctl,
+ .mtop_array = tape_34xx_mtop
+};
+
+static struct ccw_device_id tape_34xx_ids[] = {
+ { CCW_DEVICE_DEVTYPE(0x3480, 0, 0x3480, 0), driver_info: tape_3480},
+ { CCW_DEVICE_DEVTYPE(0x3490, 0, 0x3490, 0), driver_info: tape_3490},
+ { /* end of list */ }
+};
+
+static int
+tape_34xx_online(struct ccw_device *cdev)
+{
+ return tape_generic_online(
+ cdev->dev.driver_data,
+ &tape_discipline_34xx
+ );
+}
+
+static int
+tape_34xx_offline(struct ccw_device *cdev)
+{
+ return tape_generic_offline(cdev->dev.driver_data);
+}
+
+static struct ccw_driver tape_34xx_driver = {
+ .name = "tape_34xx",
+ .owner = THIS_MODULE,
+ .ids = tape_34xx_ids,
+ .probe = tape_generic_probe,
+ .remove = tape_generic_remove,
+ .set_online = tape_34xx_online,
+ .set_offline = tape_34xx_offline,
+};
+
+static int
+tape_34xx_init (void)
+{
+ int rc;
+
+ TAPE_DBF_AREA = debug_register ( "tape_34xx", 1, 2, 4*sizeof(long));
+ debug_register_view(TAPE_DBF_AREA, &debug_sprintf_view);
+#ifdef DBF_LIKE_HELL
+ debug_set_level(TAPE_DBF_AREA, 6);
+#endif
+
+ DBF_EVENT(3, "34xx init: $Revision: 1.21 $\n");
+ /* Register driver for 3480/3490 tapes. */
+ rc = ccw_driver_register(&tape_34xx_driver);
+ if (rc)
+ DBF_EVENT(3, "34xx init failed\n");
+ else
+ DBF_EVENT(3, "34xx registered\n");
+ return rc;
+}
+
+static void
+tape_34xx_exit(void)
+{
+ ccw_driver_unregister(&tape_34xx_driver);
+
+ debug_unregister(TAPE_DBF_AREA);
+}
+
+MODULE_DEVICE_TABLE(ccw, tape_34xx_ids);
+MODULE_AUTHOR("(C) 2001-2002 IBM Deutschland Entwicklung GmbH");
+MODULE_DESCRIPTION("Linux on zSeries channel attached 3480 tape "
+ "device driver ($Revision: 1.21 $)");
+MODULE_LICENSE("GPL");
+
+module_init(tape_34xx_init);
+module_exit(tape_34xx_exit);
diff --git a/drivers/s390/char/tape_block.c b/drivers/s390/char/tape_block.c
new file mode 100644
index 000000000000..1efc9f21229e
--- /dev/null
+++ b/drivers/s390/char/tape_block.c
@@ -0,0 +1,492 @@
+/*
+ * drivers/s390/char/tape_block.c
+ * block device frontend for tape device driver
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001,2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Stefan Bader <shbader@de.ibm.com>
+ */
+
+#include <linux/fs.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/interrupt.h>
+#include <linux/buffer_head.h>
+
+#include <asm/debug.h>
+
+#define TAPE_DBF_AREA tape_core_dbf
+
+#include "tape.h"
+
+#define PRINTK_HEADER "TAPE_BLOCK: "
+
+#define TAPEBLOCK_MAX_SEC 100
+#define TAPEBLOCK_MIN_REQUEUE 3
+
+/*
+ * 2003/11/25 Stefan Bader <shbader@de.ibm.com>
+ *
+ * In 2.5/2.6 the block device request function is very likely to be called
+ * with disabled interrupts (e.g. generic_unplug_device). So the driver can't
+ * just call any function that tries to allocate CCW requests from that con-
+ * text since it might sleep. There are two choices to work around this:
+ * a) do not allocate with kmalloc but use its own memory pool
+ * b) take requests from the queue outside that context, knowing that
+ * allocation might sleep
+ */
+
+/*
+ * file operation structure for tape block frontend
+ */
+static int tapeblock_open(struct inode *, struct file *);
+static int tapeblock_release(struct inode *, struct file *);
+static int tapeblock_ioctl(struct inode *, struct file *, unsigned int,
+ unsigned long);
+static int tapeblock_medium_changed(struct gendisk *);
+static int tapeblock_revalidate_disk(struct gendisk *);
+
+static struct block_device_operations tapeblock_fops = {
+ .owner = THIS_MODULE,
+ .open = tapeblock_open,
+ .release = tapeblock_release,
+ .ioctl = tapeblock_ioctl,
+ .media_changed = tapeblock_medium_changed,
+ .revalidate_disk = tapeblock_revalidate_disk,
+};
+
+static int tapeblock_major = 0;
+
+static void
+tapeblock_trigger_requeue(struct tape_device *device)
+{
+ /* Protect against rescheduling. */
+ if (atomic_compare_and_swap(0, 1, &device->blk_data.requeue_scheduled))
+ return;
+ schedule_work(&device->blk_data.requeue_task);
+}
+
+/*
+ * Post finished request.
+ */
+static inline void
+tapeblock_end_request(struct request *req, int uptodate)
+{
+ if (end_that_request_first(req, uptodate, req->hard_nr_sectors))
+ BUG();
+ end_that_request_last(req);
+}
+
+static void
+__tapeblock_end_request(struct tape_request *ccw_req, void *data)
+{
+ struct tape_device *device;
+ struct request *req;
+
+ DBF_LH(6, "__tapeblock_end_request()\n");
+
+ device = ccw_req->device;
+ req = (struct request *) data;
+ tapeblock_end_request(req, ccw_req->rc == 0);
+ if (ccw_req->rc == 0)
+ /* Update position. */
+ device->blk_data.block_position =
+ (req->sector + req->nr_sectors) >> TAPEBLOCK_HSEC_S2B;
+ else
+ /* We lost the position information due to an error. */
+ device->blk_data.block_position = -1;
+ device->discipline->free_bread(ccw_req);
+ if (!list_empty(&device->req_queue) ||
+ elv_next_request(device->blk_data.request_queue))
+ tapeblock_trigger_requeue(device);
+}
+
+/*
+ * Feed the tape device CCW queue with requests supplied in a list.
+ */
+static inline int
+tapeblock_start_request(struct tape_device *device, struct request *req)
+{
+ struct tape_request * ccw_req;
+ int rc;
+
+ DBF_LH(6, "tapeblock_start_request(%p, %p)\n", device, req);
+
+ ccw_req = device->discipline->bread(device, req);
+ if (IS_ERR(ccw_req)) {
+ DBF_EVENT(1, "TBLOCK: bread failed\n");
+ tapeblock_end_request(req, 0);
+ return PTR_ERR(ccw_req);
+ }
+ ccw_req->callback = __tapeblock_end_request;
+ ccw_req->callback_data = (void *) req;
+ ccw_req->retries = TAPEBLOCK_RETRIES;
+
+ rc = tape_do_io_async(device, ccw_req);
+ if (rc) {
+ /*
+ * Start/enqueueing failed. No retries in
+ * this case.
+ */
+ tapeblock_end_request(req, 0);
+ device->discipline->free_bread(ccw_req);
+ }
+
+ return rc;
+}
+
+/*
+ * Move requests from the block device request queue to the tape device ccw
+ * queue.
+ */
+static void
+tapeblock_requeue(void *data) {
+ struct tape_device * device;
+ request_queue_t * queue;
+ int nr_queued;
+ struct request * req;
+ struct list_head * l;
+ int rc;
+
+ device = (struct tape_device *) data;
+ if (!device)
+ return;
+
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ queue = device->blk_data.request_queue;
+
+ /* Count number of requests on ccw queue. */
+ nr_queued = 0;
+ list_for_each(l, &device->req_queue)
+ nr_queued++;
+ spin_unlock(get_ccwdev_lock(device->cdev));
+
+ spin_lock(&device->blk_data.request_queue_lock);
+ while (
+ !blk_queue_plugged(queue) &&
+ elv_next_request(queue) &&
+ nr_queued < TAPEBLOCK_MIN_REQUEUE
+ ) {
+ req = elv_next_request(queue);
+ if (rq_data_dir(req) == WRITE) {
+ DBF_EVENT(1, "TBLOCK: Rejecting write request\n");
+ blkdev_dequeue_request(req);
+ tapeblock_end_request(req, 0);
+ continue;
+ }
+ spin_unlock_irq(&device->blk_data.request_queue_lock);
+ rc = tapeblock_start_request(device, req);
+ spin_lock_irq(&device->blk_data.request_queue_lock);
+ blkdev_dequeue_request(req);
+ nr_queued++;
+ }
+ spin_unlock_irq(&device->blk_data.request_queue_lock);
+ atomic_set(&device->blk_data.requeue_scheduled, 0);
+}
+
+/*
+ * Tape request queue function. Called from ll_rw_blk.c
+ */
+static void
+tapeblock_request_fn(request_queue_t *queue)
+{
+ struct tape_device *device;
+
+ device = (struct tape_device *) queue->queuedata;
+ DBF_LH(6, "tapeblock_request_fn(device=%p)\n", device);
+ if (device == NULL)
+ BUG();
+
+ tapeblock_trigger_requeue(device);
+}
+
+/*
+ * This function is called for every new tapedevice
+ */
+int
+tapeblock_setup_device(struct tape_device * device)
+{
+ struct tape_blk_data * blkdat;
+ struct gendisk * disk;
+ int rc;
+
+ blkdat = &device->blk_data;
+ spin_lock_init(&blkdat->request_queue_lock);
+ atomic_set(&blkdat->requeue_scheduled, 0);
+
+ blkdat->request_queue = blk_init_queue(
+ tapeblock_request_fn,
+ &blkdat->request_queue_lock
+ );
+ if (!blkdat->request_queue)
+ return -ENOMEM;
+
+ elevator_exit(blkdat->request_queue->elevator);
+ rc = elevator_init(blkdat->request_queue, "noop");
+ if (rc)
+ goto cleanup_queue;
+
+ blk_queue_hardsect_size(blkdat->request_queue, TAPEBLOCK_HSEC_SIZE);
+ blk_queue_max_sectors(blkdat->request_queue, TAPEBLOCK_MAX_SEC);
+ blk_queue_max_phys_segments(blkdat->request_queue, -1L);
+ blk_queue_max_hw_segments(blkdat->request_queue, -1L);
+ blk_queue_max_segment_size(blkdat->request_queue, -1L);
+ blk_queue_segment_boundary(blkdat->request_queue, -1L);
+
+ disk = alloc_disk(1);
+ if (!disk) {
+ rc = -ENOMEM;
+ goto cleanup_queue;
+ }
+
+ disk->major = tapeblock_major;
+ disk->first_minor = device->first_minor;
+ disk->fops = &tapeblock_fops;
+ disk->private_data = tape_get_device_reference(device);
+ disk->queue = blkdat->request_queue;
+ set_capacity(disk, 0);
+ sprintf(disk->disk_name, "btibm%d",
+ device->first_minor / TAPE_MINORS_PER_DEV);
+
+ blkdat->disk = disk;
+ blkdat->medium_changed = 1;
+ blkdat->request_queue->queuedata = tape_get_device_reference(device);
+
+ add_disk(disk);
+
+ INIT_WORK(&blkdat->requeue_task, tapeblock_requeue,
+ tape_get_device_reference(device));
+
+ return 0;
+
+cleanup_queue:
+ blk_cleanup_queue(blkdat->request_queue);
+ blkdat->request_queue = NULL;
+
+ return rc;
+}
+
+void
+tapeblock_cleanup_device(struct tape_device *device)
+{
+ flush_scheduled_work();
+ device->blk_data.requeue_task.data = tape_put_device(device);
+
+ if (!device->blk_data.disk) {
+ PRINT_ERR("(%s): No gendisk to clean up!\n",
+ device->cdev->dev.bus_id);
+ goto cleanup_queue;
+ }
+
+ del_gendisk(device->blk_data.disk);
+ device->blk_data.disk->private_data =
+ tape_put_device(device->blk_data.disk->private_data);
+ put_disk(device->blk_data.disk);
+
+ device->blk_data.disk = NULL;
+cleanup_queue:
+ device->blk_data.request_queue->queuedata = tape_put_device(device);
+
+ blk_cleanup_queue(device->blk_data.request_queue);
+ device->blk_data.request_queue = NULL;
+}
+
+/*
+ * Detect number of blocks of the tape.
+ * FIXME: can we extent this to detect the blocks size as well ?
+ */
+static int
+tapeblock_revalidate_disk(struct gendisk *disk)
+{
+ struct tape_device * device;
+ unsigned int nr_of_blks;
+ int rc;
+
+ device = (struct tape_device *) disk->private_data;
+ if (!device)
+ BUG();
+
+ if (!device->blk_data.medium_changed)
+ return 0;
+
+ PRINT_INFO("Detecting media size...\n");
+ rc = tape_mtop(device, MTFSFM, 1);
+ if (rc)
+ return rc;
+
+ rc = tape_mtop(device, MTTELL, 1);
+ if (rc < 0)
+ return rc;
+
+ DBF_LH(3, "Image file ends at %d\n", rc);
+ nr_of_blks = rc;
+
+ /* This will fail for the first file. Catch the error by checking the
+ * position. */
+ tape_mtop(device, MTBSF, 1);
+
+ rc = tape_mtop(device, MTTELL, 1);
+ if (rc < 0)
+ return rc;
+
+ if (rc > nr_of_blks)
+ return -EINVAL;
+
+ DBF_LH(3, "Image file starts at %d\n", rc);
+ device->bof = rc;
+ nr_of_blks -= rc;
+
+ PRINT_INFO("Found %i blocks on media\n", nr_of_blks);
+ set_capacity(device->blk_data.disk,
+ nr_of_blks*(TAPEBLOCK_HSEC_SIZE/512));
+
+ device->blk_data.block_position = 0;
+ device->blk_data.medium_changed = 0;
+ return 0;
+}
+
+static int
+tapeblock_medium_changed(struct gendisk *disk)
+{
+ struct tape_device *device;
+
+ device = (struct tape_device *) disk->private_data;
+ DBF_LH(6, "tapeblock_medium_changed(%p) = %d\n",
+ device, device->blk_data.medium_changed);
+
+ return device->blk_data.medium_changed;
+}
+
+/*
+ * Block frontend tape device open function.
+ */
+static int
+tapeblock_open(struct inode *inode, struct file *filp)
+{
+ struct gendisk * disk;
+ struct tape_device * device;
+ int rc;
+
+ disk = inode->i_bdev->bd_disk;
+ device = tape_get_device_reference(disk->private_data);
+
+ if (device->required_tapemarks) {
+ DBF_EVENT(2, "TBLOCK: missing tapemarks\n");
+ PRINT_ERR("TBLOCK: Refusing to open tape with missing"
+ " end of file marks.\n");
+ rc = -EPERM;
+ goto put_device;
+ }
+
+ rc = tape_open(device);
+ if (rc)
+ goto put_device;
+
+ rc = tapeblock_revalidate_disk(disk);
+ if (rc)
+ goto release;
+
+ /*
+ * Note: The reference to <device> is hold until the release function
+ * is called.
+ */
+ tape_state_set(device, TS_BLKUSE);
+ return 0;
+
+release:
+ tape_release(device);
+ put_device:
+ tape_put_device(device);
+ return rc;
+}
+
+/*
+ * Block frontend tape device release function.
+ *
+ * Note: One reference to the tape device was made by the open function. So
+ * we just get the pointer here and release the reference.
+ */
+static int
+tapeblock_release(struct inode *inode, struct file *filp)
+{
+ struct gendisk *disk = inode->i_bdev->bd_disk;
+ struct tape_device *device = disk->private_data;
+
+ tape_state_set(device, TS_IN_USE);
+ tape_release(device);
+ tape_put_device(device);
+
+ return 0;
+}
+
+/*
+ * Support of some generic block device IOCTLs.
+ */
+static int
+tapeblock_ioctl(
+ struct inode * inode,
+ struct file * file,
+ unsigned int command,
+ unsigned long arg
+) {
+ int rc;
+ int minor;
+ struct gendisk *disk = inode->i_bdev->bd_disk;
+ struct tape_device *device = disk->private_data;
+
+ rc = 0;
+ disk = inode->i_bdev->bd_disk;
+ if (!disk)
+ BUG();
+ device = disk->private_data;
+ if (!device)
+ BUG();
+ minor = iminor(inode);
+
+ DBF_LH(6, "tapeblock_ioctl(0x%0x)\n", command);
+ DBF_LH(6, "device = %d:%d\n", tapeblock_major, minor);
+
+ switch (command) {
+ /* Refuse some IOCTL calls without complaining (mount). */
+ case 0x5310: /* CDROMMULTISESSION */
+ rc = -EINVAL;
+ break;
+ default:
+ PRINT_WARN("invalid ioctl 0x%x\n", command);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/*
+ * Initialize block device frontend.
+ */
+int
+tapeblock_init(void)
+{
+ int rc;
+
+ /* Register the tape major number to the kernel */
+ rc = register_blkdev(tapeblock_major, "tBLK");
+ if (rc < 0)
+ return rc;
+
+ if (tapeblock_major == 0)
+ tapeblock_major = rc;
+ PRINT_INFO("tape gets major %d for block device\n", tapeblock_major);
+ return 0;
+}
+
+/*
+ * Deregister major for block device frontend
+ */
+void
+tapeblock_exit(void)
+{
+ unregister_blkdev(tapeblock_major, "tBLK");
+}
diff --git a/drivers/s390/char/tape_char.c b/drivers/s390/char/tape_char.c
new file mode 100644
index 000000000000..86262a13f7c6
--- /dev/null
+++ b/drivers/s390/char/tape_char.c
@@ -0,0 +1,492 @@
+/*
+ * drivers/s390/char/tape_char.c
+ * character device frontend for tape device driver
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Michael Holzheu <holzheu@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/mtio.h>
+
+#include <asm/uaccess.h>
+
+#define TAPE_DBF_AREA tape_core_dbf
+
+#include "tape.h"
+#include "tape_std.h"
+#include "tape_class.h"
+
+#define PRINTK_HEADER "TAPE_CHAR: "
+
+#define TAPECHAR_MAJOR 0 /* get dynamic major */
+
+/*
+ * file operation structure for tape character frontend
+ */
+static ssize_t tapechar_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t tapechar_write(struct file *, const char __user *, size_t, loff_t *);
+static int tapechar_open(struct inode *,struct file *);
+static int tapechar_release(struct inode *,struct file *);
+static int tapechar_ioctl(struct inode *, struct file *, unsigned int,
+ unsigned long);
+
+static struct file_operations tape_fops =
+{
+ .owner = THIS_MODULE,
+ .read = tapechar_read,
+ .write = tapechar_write,
+ .ioctl = tapechar_ioctl,
+ .open = tapechar_open,
+ .release = tapechar_release,
+};
+
+static int tapechar_major = TAPECHAR_MAJOR;
+
+/*
+ * This function is called for every new tapedevice
+ */
+int
+tapechar_setup_device(struct tape_device * device)
+{
+ char device_name[20];
+
+ sprintf(device_name, "ntibm%i", device->first_minor / 2);
+ device->nt = register_tape_dev(
+ &device->cdev->dev,
+ MKDEV(tapechar_major, device->first_minor),
+ &tape_fops,
+ device_name,
+ "non-rewinding"
+ );
+ device_name[0] = 'r';
+ device->rt = register_tape_dev(
+ &device->cdev->dev,
+ MKDEV(tapechar_major, device->first_minor + 1),
+ &tape_fops,
+ device_name,
+ "rewinding"
+ );
+
+ return 0;
+}
+
+void
+tapechar_cleanup_device(struct tape_device *device)
+{
+ unregister_tape_dev(device->rt);
+ device->rt = NULL;
+ unregister_tape_dev(device->nt);
+ device->nt = NULL;
+}
+
+/*
+ * Terminate write command (we write two TMs and skip backward over last)
+ * This ensures that the tape is always correctly terminated.
+ * When the user writes afterwards a new file, he will overwrite the
+ * second TM and therefore one TM will remain to separate the
+ * two files on the tape...
+ */
+static inline void
+tapechar_terminate_write(struct tape_device *device)
+{
+ if (tape_mtop(device, MTWEOF, 1) == 0 &&
+ tape_mtop(device, MTWEOF, 1) == 0)
+ tape_mtop(device, MTBSR, 1);
+}
+
+static inline int
+tapechar_check_idalbuffer(struct tape_device *device, size_t block_size)
+{
+ struct idal_buffer *new;
+
+ if (device->char_data.idal_buf != NULL &&
+ device->char_data.idal_buf->size == block_size)
+ return 0;
+
+ if (block_size > MAX_BLOCKSIZE) {
+ DBF_EVENT(3, "Invalid blocksize (%zd > %d)\n",
+ block_size, MAX_BLOCKSIZE);
+ PRINT_ERR("Invalid blocksize (%zd> %d)\n",
+ block_size, MAX_BLOCKSIZE);
+ return -EINVAL;
+ }
+
+ /* The current idal buffer is not correct. Allocate a new one. */
+ new = idal_buffer_alloc(block_size, 0);
+ if (new == NULL)
+ return -ENOMEM;
+
+ if (device->char_data.idal_buf != NULL)
+ idal_buffer_free(device->char_data.idal_buf);
+
+ device->char_data.idal_buf = new;
+
+ return 0;
+}
+
+/*
+ * Tape device read function
+ */
+ssize_t
+tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
+{
+ struct tape_device *device;
+ struct tape_request *request;
+ size_t block_size;
+ int rc;
+
+ DBF_EVENT(6, "TCHAR:read\n");
+ device = (struct tape_device *) filp->private_data;
+
+ /*
+ * If the tape isn't terminated yet, do it now. And since we then
+ * are at the end of the tape there wouldn't be anything to read
+ * anyways. So we return immediatly.
+ */
+ if(device->required_tapemarks) {
+ return tape_std_terminate_write(device);
+ }
+
+ /* Find out block size to use */
+ if (device->char_data.block_size != 0) {
+ if (count < device->char_data.block_size) {
+ DBF_EVENT(3, "TCHAR:read smaller than block "
+ "size was requested\n");
+ return -EINVAL;
+ }
+ block_size = device->char_data.block_size;
+ } else {
+ block_size = count;
+ }
+
+ rc = tapechar_check_idalbuffer(device, block_size);
+ if (rc)
+ return rc;
+
+#ifdef CONFIG_S390_TAPE_BLOCK
+ /* Changes position. */
+ device->blk_data.medium_changed = 1;
+#endif
+
+ DBF_EVENT(6, "TCHAR:nbytes: %lx\n", block_size);
+ /* Let the discipline build the ccw chain. */
+ request = device->discipline->read_block(device, block_size);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ /* Execute it. */
+ rc = tape_do_io(device, request);
+ if (rc == 0) {
+ rc = block_size - request->rescnt;
+ DBF_EVENT(6, "TCHAR:rbytes: %x\n", rc);
+ filp->f_pos += rc;
+ /* Copy data from idal buffer to user space. */
+ if (idal_buffer_to_user(device->char_data.idal_buf,
+ data, rc) != 0)
+ rc = -EFAULT;
+ }
+ tape_free_request(request);
+ return rc;
+}
+
+/*
+ * Tape device write function
+ */
+ssize_t
+tapechar_write(struct file *filp, const char __user *data, size_t count, loff_t *ppos)
+{
+ struct tape_device *device;
+ struct tape_request *request;
+ size_t block_size;
+ size_t written;
+ int nblocks;
+ int i, rc;
+
+ DBF_EVENT(6, "TCHAR:write\n");
+ device = (struct tape_device *) filp->private_data;
+ /* Find out block size and number of blocks */
+ if (device->char_data.block_size != 0) {
+ if (count < device->char_data.block_size) {
+ DBF_EVENT(3, "TCHAR:write smaller than block "
+ "size was requested\n");
+ return -EINVAL;
+ }
+ block_size = device->char_data.block_size;
+ nblocks = count / block_size;
+ } else {
+ block_size = count;
+ nblocks = 1;
+ }
+
+ rc = tapechar_check_idalbuffer(device, block_size);
+ if (rc)
+ return rc;
+
+#ifdef CONFIG_S390_TAPE_BLOCK
+ /* Changes position. */
+ device->blk_data.medium_changed = 1;
+#endif
+
+ DBF_EVENT(6,"TCHAR:nbytes: %lx\n", block_size);
+ DBF_EVENT(6, "TCHAR:nblocks: %x\n", nblocks);
+ /* Let the discipline build the ccw chain. */
+ request = device->discipline->write_block(device, block_size);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ rc = 0;
+ written = 0;
+ for (i = 0; i < nblocks; i++) {
+ /* Copy data from user space to idal buffer. */
+ if (idal_buffer_from_user(device->char_data.idal_buf,
+ data, block_size)) {
+ rc = -EFAULT;
+ break;
+ }
+ rc = tape_do_io(device, request);
+ if (rc)
+ break;
+ DBF_EVENT(6, "TCHAR:wbytes: %lx\n",
+ block_size - request->rescnt);
+ filp->f_pos += block_size - request->rescnt;
+ written += block_size - request->rescnt;
+ if (request->rescnt != 0)
+ break;
+ data += block_size;
+ }
+ tape_free_request(request);
+ if (rc == -ENOSPC) {
+ /*
+ * Ok, the device has no more space. It has NOT written
+ * the block.
+ */
+ if (device->discipline->process_eov)
+ device->discipline->process_eov(device);
+ if (written > 0)
+ rc = 0;
+
+ }
+
+ /*
+ * After doing a write we always need two tapemarks to correctly
+ * terminate the tape (one to terminate the file, the second to
+ * flag the end of recorded data.
+ * Since process_eov positions the tape in front of the written
+ * tapemark it doesn't hurt to write two marks again.
+ */
+ if (!rc)
+ device->required_tapemarks = 2;
+
+ return rc ? rc : written;
+}
+
+/*
+ * Character frontend tape device open function.
+ */
+int
+tapechar_open (struct inode *inode, struct file *filp)
+{
+ struct tape_device *device;
+ int minor, rc;
+
+ DBF_EVENT(6, "TCHAR:open: %i:%i\n",
+ imajor(filp->f_dentry->d_inode),
+ iminor(filp->f_dentry->d_inode));
+
+ if (imajor(filp->f_dentry->d_inode) != tapechar_major)
+ return -ENODEV;
+
+ minor = iminor(filp->f_dentry->d_inode);
+ device = tape_get_device(minor / TAPE_MINORS_PER_DEV);
+ if (IS_ERR(device)) {
+ DBF_EVENT(3, "TCHAR:open: tape_get_device() failed\n");
+ return PTR_ERR(device);
+ }
+
+
+ rc = tape_open(device);
+ if (rc == 0) {
+ filp->private_data = device;
+ return nonseekable_open(inode, filp);
+ }
+ tape_put_device(device);
+
+ return rc;
+}
+
+/*
+ * Character frontend tape device release function.
+ */
+
+int
+tapechar_release(struct inode *inode, struct file *filp)
+{
+ struct tape_device *device;
+
+ DBF_EVENT(6, "TCHAR:release: %x\n", iminor(inode));
+ device = (struct tape_device *) filp->private_data;
+
+ /*
+ * If this is the rewinding tape minor then rewind. In that case we
+ * write all required tapemarks. Otherwise only one to terminate the
+ * file.
+ */
+ if ((iminor(inode) & 1) != 0) {
+ if (device->required_tapemarks)
+ tape_std_terminate_write(device);
+ tape_mtop(device, MTREW, 1);
+ } else {
+ if (device->required_tapemarks > 1) {
+ if (tape_mtop(device, MTWEOF, 1) == 0)
+ device->required_tapemarks--;
+ }
+ }
+
+ if (device->char_data.idal_buf != NULL) {
+ idal_buffer_free(device->char_data.idal_buf);
+ device->char_data.idal_buf = NULL;
+ }
+ tape_release(device);
+ filp->private_data = tape_put_device(device);
+
+ return 0;
+}
+
+/*
+ * Tape device io controls.
+ */
+static int
+tapechar_ioctl(struct inode *inp, struct file *filp,
+ unsigned int no, unsigned long data)
+{
+ struct tape_device *device;
+ int rc;
+
+ DBF_EVENT(6, "TCHAR:ioct\n");
+
+ device = (struct tape_device *) filp->private_data;
+
+ if (no == MTIOCTOP) {
+ struct mtop op;
+
+ if (copy_from_user(&op, (char __user *) data, sizeof(op)) != 0)
+ return -EFAULT;
+ if (op.mt_count < 0)
+ return -EINVAL;
+
+ /*
+ * Operations that change tape position should write final
+ * tapemarks.
+ */
+ switch (op.mt_op) {
+ case MTFSF:
+ case MTBSF:
+ case MTFSR:
+ case MTBSR:
+ case MTREW:
+ case MTOFFL:
+ case MTEOM:
+ case MTRETEN:
+ case MTBSFM:
+ case MTFSFM:
+ case MTSEEK:
+#ifdef CONFIG_S390_TAPE_BLOCK
+ device->blk_data.medium_changed = 1;
+#endif
+ if (device->required_tapemarks)
+ tape_std_terminate_write(device);
+ default:
+ ;
+ }
+ rc = tape_mtop(device, op.mt_op, op.mt_count);
+
+ if (op.mt_op == MTWEOF && rc == 0) {
+ if (op.mt_count > device->required_tapemarks)
+ device->required_tapemarks = 0;
+ else
+ device->required_tapemarks -= op.mt_count;
+ }
+ return rc;
+ }
+ if (no == MTIOCPOS) {
+ /* MTIOCPOS: query the tape position. */
+ struct mtpos pos;
+
+ rc = tape_mtop(device, MTTELL, 1);
+ if (rc < 0)
+ return rc;
+ pos.mt_blkno = rc;
+ if (copy_to_user((char __user *) data, &pos, sizeof(pos)) != 0)
+ return -EFAULT;
+ return 0;
+ }
+ if (no == MTIOCGET) {
+ /* MTIOCGET: query the tape drive status. */
+ struct mtget get;
+
+ memset(&get, 0, sizeof(get));
+ get.mt_type = MT_ISUNKNOWN;
+ get.mt_resid = 0 /* device->devstat.rescnt */;
+ get.mt_dsreg = device->tape_state;
+ /* FIXME: mt_gstat, mt_erreg, mt_fileno */
+ get.mt_gstat = 0;
+ get.mt_erreg = 0;
+ get.mt_fileno = 0;
+ get.mt_gstat = device->tape_generic_status;
+
+ if (device->medium_state == MS_LOADED) {
+ rc = tape_mtop(device, MTTELL, 1);
+
+ if (rc < 0)
+ return rc;
+
+ if (rc == 0)
+ get.mt_gstat |= GMT_BOT(~0);
+
+ get.mt_blkno = rc;
+ }
+
+ if (copy_to_user((char __user *) data, &get, sizeof(get)) != 0)
+ return -EFAULT;
+
+ return 0;
+ }
+ /* Try the discipline ioctl function. */
+ if (device->discipline->ioctl_fn == NULL)
+ return -EINVAL;
+ return device->discipline->ioctl_fn(device, no, data);
+}
+
+/*
+ * Initialize character device frontend.
+ */
+int
+tapechar_init (void)
+{
+ dev_t dev;
+
+ if (alloc_chrdev_region(&dev, 0, 256, "tape") != 0)
+ return -1;
+
+ tapechar_major = MAJOR(dev);
+ PRINT_INFO("tape gets major %d for character devices\n", MAJOR(dev));
+
+ return 0;
+}
+
+/*
+ * cleanup
+ */
+void
+tapechar_exit(void)
+{
+ PRINT_INFO("tape releases major %d for character devices\n",
+ tapechar_major);
+ unregister_chrdev_region(MKDEV(tapechar_major, 0), 256);
+}
diff --git a/drivers/s390/char/tape_class.c b/drivers/s390/char/tape_class.c
new file mode 100644
index 000000000000..0f8ffd4167ca
--- /dev/null
+++ b/drivers/s390/char/tape_class.c
@@ -0,0 +1,126 @@
+/*
+ * (C) Copyright IBM Corp. 2004
+ * tape_class.c ($Revision: 1.8 $)
+ *
+ * Tape class device support
+ *
+ * Author: Stefan Bader <shbader@de.ibm.com>
+ * Based on simple class device code by Greg K-H
+ */
+#include "tape_class.h"
+
+MODULE_AUTHOR("Stefan Bader <shbader@de.ibm.com>");
+MODULE_DESCRIPTION(
+ "(C) Copyright IBM Corp. 2004 All Rights Reserved.\n"
+ "tape_class.c ($Revision: 1.8 $)"
+);
+MODULE_LICENSE("GPL");
+
+struct class_simple *tape_class;
+
+/*
+ * Register a tape device and return a pointer to the cdev structure.
+ *
+ * device
+ * The pointer to the struct device of the physical (base) device.
+ * drivername
+ * The pointer to the drivers name for it's character devices.
+ * dev
+ * The intended major/minor number. The major number may be 0 to
+ * get a dynamic major number.
+ * fops
+ * The pointer to the drivers file operations for the tape device.
+ * devname
+ * The pointer to the name of the character device.
+ */
+struct tape_class_device *register_tape_dev(
+ struct device * device,
+ dev_t dev,
+ struct file_operations *fops,
+ char * device_name,
+ char * mode_name)
+{
+ struct tape_class_device * tcd;
+ int rc;
+ char * s;
+
+ tcd = kmalloc(sizeof(struct tape_class_device), GFP_KERNEL);
+ if (!tcd)
+ return ERR_PTR(-ENOMEM);
+
+ memset(tcd, 0, sizeof(struct tape_class_device));
+ strncpy(tcd->device_name, device_name, TAPECLASS_NAME_LEN);
+ for (s = strchr(tcd->device_name, '/'); s; s = strchr(s, '/'))
+ *s = '!';
+ strncpy(tcd->mode_name, mode_name, TAPECLASS_NAME_LEN);
+ for (s = strchr(tcd->mode_name, '/'); s; s = strchr(s, '/'))
+ *s = '!';
+
+ tcd->char_device = cdev_alloc();
+ if (!tcd->char_device) {
+ rc = -ENOMEM;
+ goto fail_with_tcd;
+ }
+
+ tcd->char_device->owner = fops->owner;
+ tcd->char_device->ops = fops;
+ tcd->char_device->dev = dev;
+
+ rc = cdev_add(tcd->char_device, tcd->char_device->dev, 1);
+ if (rc)
+ goto fail_with_cdev;
+
+ tcd->class_device = class_simple_device_add(
+ tape_class,
+ tcd->char_device->dev,
+ device,
+ "%s", tcd->device_name
+ );
+ sysfs_create_link(
+ &device->kobj,
+ &tcd->class_device->kobj,
+ tcd->mode_name
+ );
+
+ return tcd;
+
+fail_with_cdev:
+ cdev_del(tcd->char_device);
+
+fail_with_tcd:
+ kfree(tcd);
+
+ return ERR_PTR(rc);
+}
+EXPORT_SYMBOL(register_tape_dev);
+
+void unregister_tape_dev(struct tape_class_device *tcd)
+{
+ if (tcd != NULL && !IS_ERR(tcd)) {
+ sysfs_remove_link(
+ &tcd->class_device->dev->kobj,
+ tcd->mode_name
+ );
+ class_simple_device_remove(tcd->char_device->dev);
+ cdev_del(tcd->char_device);
+ kfree(tcd);
+ }
+}
+EXPORT_SYMBOL(unregister_tape_dev);
+
+
+static int __init tape_init(void)
+{
+ tape_class = class_simple_create(THIS_MODULE, "tape390");
+
+ return 0;
+}
+
+static void __exit tape_exit(void)
+{
+ class_simple_destroy(tape_class);
+ tape_class = NULL;
+}
+
+postcore_initcall(tape_init);
+module_exit(tape_exit);
diff --git a/drivers/s390/char/tape_class.h b/drivers/s390/char/tape_class.h
new file mode 100644
index 000000000000..33133ad00ba2
--- /dev/null
+++ b/drivers/s390/char/tape_class.h
@@ -0,0 +1,61 @@
+/*
+ * (C) Copyright IBM Corp. 2004 All Rights Reserved.
+ * tape_class.h ($Revision: 1.4 $)
+ *
+ * Tape class device support
+ *
+ * Author: Stefan Bader <shbader@de.ibm.com>
+ * Based on simple class device code by Greg K-H
+ */
+#ifndef __TAPE_CLASS_H__
+#define __TAPE_CLASS_H__
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <linux/kobject.h>
+#include <linux/kobj_map.h>
+#include <linux/cdev.h>
+
+#include <linux/device.h>
+#include <linux/kdev_t.h>
+
+#define TAPECLASS_NAME_LEN 32
+
+struct tape_class_device {
+ struct cdev * char_device;
+ struct class_device * class_device;
+ char device_name[TAPECLASS_NAME_LEN];
+ char mode_name[TAPECLASS_NAME_LEN];
+};
+
+/*
+ * Register a tape device and return a pointer to the tape class device
+ * created by the call.
+ *
+ * device
+ * The pointer to the struct device of the physical (base) device.
+ * dev
+ * The intended major/minor number. The major number may be 0 to
+ * get a dynamic major number.
+ * fops
+ * The pointer to the drivers file operations for the tape device.
+ * device_name
+ * Pointer to the logical device name (will also be used as kobject name
+ * of the cdev). This can also be called the name of the tape class
+ * device.
+ * mode_name
+ * Points to the name of the tape mode. This creates a link with that
+ * name from the physical device to the logical device (class).
+ */
+struct tape_class_device *register_tape_dev(
+ struct device * device,
+ dev_t dev,
+ struct file_operations *fops,
+ char * device_name,
+ char * node_name
+);
+void unregister_tape_dev(struct tape_class_device *tcd);
+
+#endif /* __TAPE_CLASS_H__ */
diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c
new file mode 100644
index 000000000000..e51046ab8adc
--- /dev/null
+++ b/drivers/s390/char/tape_core.c
@@ -0,0 +1,1242 @@
+/*
+ * drivers/s390/char/tape_core.c
+ * basic function of the tape device driver
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Michael Holzheu <holzheu@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h> // for kernel parameters
+#include <linux/kmod.h> // for requesting modules
+#include <linux/spinlock.h> // for locks
+#include <linux/vmalloc.h>
+#include <linux/list.h>
+
+#include <asm/types.h> // for variable types
+
+#define TAPE_DBF_AREA tape_core_dbf
+
+#include "tape.h"
+#include "tape_std.h"
+
+#define PRINTK_HEADER "TAPE_CORE: "
+
+static void __tape_do_irq (struct ccw_device *, unsigned long, struct irb *);
+static void __tape_remove_request(struct tape_device *, struct tape_request *);
+
+/*
+ * One list to contain all tape devices of all disciplines, so
+ * we can assign the devices to minor numbers of the same major
+ * The list is protected by the rwlock
+ */
+static struct list_head tape_device_list = LIST_HEAD_INIT(tape_device_list);
+static DEFINE_RWLOCK(tape_device_lock);
+
+/*
+ * Pointer to debug area.
+ */
+debug_info_t *TAPE_DBF_AREA = NULL;
+EXPORT_SYMBOL(TAPE_DBF_AREA);
+
+/*
+ * Printable strings for tape enumerations.
+ */
+const char *tape_state_verbose[TS_SIZE] =
+{
+ [TS_UNUSED] = "UNUSED",
+ [TS_IN_USE] = "IN_USE",
+ [TS_BLKUSE] = "BLKUSE",
+ [TS_INIT] = "INIT ",
+ [TS_NOT_OPER] = "NOT_OP"
+};
+
+const char *tape_op_verbose[TO_SIZE] =
+{
+ [TO_BLOCK] = "BLK", [TO_BSB] = "BSB",
+ [TO_BSF] = "BSF", [TO_DSE] = "DSE",
+ [TO_FSB] = "FSB", [TO_FSF] = "FSF",
+ [TO_LBL] = "LBL", [TO_NOP] = "NOP",
+ [TO_RBA] = "RBA", [TO_RBI] = "RBI",
+ [TO_RFO] = "RFO", [TO_REW] = "REW",
+ [TO_RUN] = "RUN", [TO_WRI] = "WRI",
+ [TO_WTM] = "WTM", [TO_MSEN] = "MSN",
+ [TO_LOAD] = "LOA", [TO_READ_CONFIG] = "RCF",
+ [TO_READ_ATTMSG] = "RAT",
+ [TO_DIS] = "DIS", [TO_ASSIGN] = "ASS",
+ [TO_UNASSIGN] = "UAS"
+};
+
+static inline int
+busid_to_int(char *bus_id)
+{
+ int dec;
+ int d;
+ char * s;
+
+ for(s = bus_id, d = 0; *s != '\0' && *s != '.'; s++)
+ d = (d * 10) + (*s - '0');
+ dec = d;
+ for(s++, d = 0; *s != '\0' && *s != '.'; s++)
+ d = (d * 10) + (*s - '0');
+ dec = (dec << 8) + d;
+
+ for(s++; *s != '\0'; s++) {
+ if (*s >= '0' && *s <= '9') {
+ d = *s - '0';
+ } else if (*s >= 'a' && *s <= 'f') {
+ d = *s - 'a' + 10;
+ } else {
+ d = *s - 'A' + 10;
+ }
+ dec = (dec << 4) + d;
+ }
+
+ return dec;
+}
+
+/*
+ * Some channel attached tape specific attributes.
+ *
+ * FIXME: In the future the first_minor and blocksize attribute should be
+ * replaced by a link to the cdev tree.
+ */
+static ssize_t
+tape_medium_state_show(struct device *dev, char *buf)
+{
+ struct tape_device *tdev;
+
+ tdev = (struct tape_device *) dev->driver_data;
+ return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->medium_state);
+}
+
+static
+DEVICE_ATTR(medium_state, 0444, tape_medium_state_show, NULL);
+
+static ssize_t
+tape_first_minor_show(struct device *dev, char *buf)
+{
+ struct tape_device *tdev;
+
+ tdev = (struct tape_device *) dev->driver_data;
+ return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->first_minor);
+}
+
+static
+DEVICE_ATTR(first_minor, 0444, tape_first_minor_show, NULL);
+
+static ssize_t
+tape_state_show(struct device *dev, char *buf)
+{
+ struct tape_device *tdev;
+
+ tdev = (struct tape_device *) dev->driver_data;
+ return scnprintf(buf, PAGE_SIZE, "%s\n", (tdev->first_minor < 0) ?
+ "OFFLINE" : tape_state_verbose[tdev->tape_state]);
+}
+
+static
+DEVICE_ATTR(state, 0444, tape_state_show, NULL);
+
+static ssize_t
+tape_operation_show(struct device *dev, char *buf)
+{
+ struct tape_device *tdev;
+ ssize_t rc;
+
+ tdev = (struct tape_device *) dev->driver_data;
+ if (tdev->first_minor < 0)
+ return scnprintf(buf, PAGE_SIZE, "N/A\n");
+
+ spin_lock_irq(get_ccwdev_lock(tdev->cdev));
+ if (list_empty(&tdev->req_queue))
+ rc = scnprintf(buf, PAGE_SIZE, "---\n");
+ else {
+ struct tape_request *req;
+
+ req = list_entry(tdev->req_queue.next, struct tape_request,
+ list);
+ rc = scnprintf(buf,PAGE_SIZE, "%s\n", tape_op_verbose[req->op]);
+ }
+ spin_unlock_irq(get_ccwdev_lock(tdev->cdev));
+ return rc;
+}
+
+static
+DEVICE_ATTR(operation, 0444, tape_operation_show, NULL);
+
+static ssize_t
+tape_blocksize_show(struct device *dev, char *buf)
+{
+ struct tape_device *tdev;
+
+ tdev = (struct tape_device *) dev->driver_data;
+
+ return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->char_data.block_size);
+}
+
+static
+DEVICE_ATTR(blocksize, 0444, tape_blocksize_show, NULL);
+
+static struct attribute *tape_attrs[] = {
+ &dev_attr_medium_state.attr,
+ &dev_attr_first_minor.attr,
+ &dev_attr_state.attr,
+ &dev_attr_operation.attr,
+ &dev_attr_blocksize.attr,
+ NULL
+};
+
+static struct attribute_group tape_attr_group = {
+ .attrs = tape_attrs,
+};
+
+/*
+ * Tape state functions
+ */
+void
+tape_state_set(struct tape_device *device, enum tape_state newstate)
+{
+ const char *str;
+
+ if (device->tape_state == TS_NOT_OPER) {
+ DBF_EVENT(3, "ts_set err: not oper\n");
+ return;
+ }
+ DBF_EVENT(4, "ts. dev: %x\n", device->first_minor);
+ if (device->tape_state < TO_SIZE && device->tape_state >= 0)
+ str = tape_state_verbose[device->tape_state];
+ else
+ str = "UNKNOWN TS";
+ DBF_EVENT(4, "old ts: %s\n", str);
+ if (device->tape_state < TO_SIZE && device->tape_state >=0 )
+ str = tape_state_verbose[device->tape_state];
+ else
+ str = "UNKNOWN TS";
+ DBF_EVENT(4, "%s\n", str);
+ DBF_EVENT(4, "new ts:\t\n");
+ if (newstate < TO_SIZE && newstate >= 0)
+ str = tape_state_verbose[newstate];
+ else
+ str = "UNKNOWN TS";
+ DBF_EVENT(4, "%s\n", str);
+ device->tape_state = newstate;
+ wake_up(&device->state_change_wq);
+}
+
+void
+tape_med_state_set(struct tape_device *device, enum tape_medium_state newstate)
+{
+ if (device->medium_state == newstate)
+ return;
+ switch(newstate){
+ case MS_UNLOADED:
+ device->tape_generic_status |= GMT_DR_OPEN(~0);
+ PRINT_INFO("(%s): Tape is unloaded\n",
+ device->cdev->dev.bus_id);
+ break;
+ case MS_LOADED:
+ device->tape_generic_status &= ~GMT_DR_OPEN(~0);
+ PRINT_INFO("(%s): Tape has been mounted\n",
+ device->cdev->dev.bus_id);
+ break;
+ default:
+ // print nothing
+ break;
+ }
+ device->medium_state = newstate;
+ wake_up(&device->state_change_wq);
+}
+
+/*
+ * Stop running ccw. Has to be called with the device lock held.
+ */
+static inline int
+__tape_halt_io(struct tape_device *device, struct tape_request *request)
+{
+ int retries;
+ int rc;
+
+ /* Check if interrupt has already been processed */
+ if (request->callback == NULL)
+ return 0;
+
+ rc = 0;
+ for (retries = 0; retries < 5; retries++) {
+ rc = ccw_device_clear(device->cdev, (long) request);
+
+ if (rc == 0) { /* Termination successful */
+ request->rc = -EIO;
+ request->status = TAPE_REQUEST_DONE;
+ return 0;
+ }
+
+ if (rc == -ENODEV)
+ DBF_EXCEPTION(2, "device gone, retry\n");
+ else if (rc == -EIO)
+ DBF_EXCEPTION(2, "I/O error, retry\n");
+ else if (rc == -EBUSY)
+ DBF_EXCEPTION(2, "device busy, retry late\n");
+ else
+ BUG();
+ }
+
+ return rc;
+}
+
+/*
+ * Add device into the sorted list, giving it the first
+ * available minor number.
+ */
+static int
+tape_assign_minor(struct tape_device *device)
+{
+ struct tape_device *tmp;
+ int minor;
+
+ minor = 0;
+ write_lock(&tape_device_lock);
+ list_for_each_entry(tmp, &tape_device_list, node) {
+ if (minor < tmp->first_minor)
+ break;
+ minor += TAPE_MINORS_PER_DEV;
+ }
+ if (minor >= 256) {
+ write_unlock(&tape_device_lock);
+ return -ENODEV;
+ }
+ device->first_minor = minor;
+ list_add_tail(&device->node, &tmp->node);
+ write_unlock(&tape_device_lock);
+ return 0;
+}
+
+/* remove device from the list */
+static void
+tape_remove_minor(struct tape_device *device)
+{
+ write_lock(&tape_device_lock);
+ list_del_init(&device->node);
+ device->first_minor = -1;
+ write_unlock(&tape_device_lock);
+}
+
+/*
+ * Set a device online.
+ *
+ * This function is called by the common I/O layer to move a device from the
+ * detected but offline into the online state.
+ * If we return an error (RC < 0) the device remains in the offline state. This
+ * can happen if the device is assigned somewhere else, for example.
+ */
+int
+tape_generic_online(struct tape_device *device,
+ struct tape_discipline *discipline)
+{
+ int rc;
+
+ DBF_LH(6, "tape_enable_device(%p, %p)\n", device, discipline);
+
+ if (device->tape_state != TS_INIT) {
+ DBF_LH(3, "Tapestate not INIT (%d)\n", device->tape_state);
+ return -EINVAL;
+ }
+
+ /* Let the discipline have a go at the device. */
+ device->discipline = discipline;
+ if (!try_module_get(discipline->owner)) {
+ PRINT_ERR("Cannot get module. Module gone.\n");
+ return -EINVAL;
+ }
+
+ rc = discipline->setup_device(device);
+ if (rc)
+ goto out;
+ rc = tape_assign_minor(device);
+ if (rc)
+ goto out_discipline;
+
+ rc = tapechar_setup_device(device);
+ if (rc)
+ goto out_minor;
+ rc = tapeblock_setup_device(device);
+ if (rc)
+ goto out_char;
+
+ tape_state_set(device, TS_UNUSED);
+
+ DBF_LH(3, "(%08x): Drive set online\n", device->cdev_id);
+
+ return 0;
+
+out_char:
+ tapechar_cleanup_device(device);
+out_discipline:
+ device->discipline->cleanup_device(device);
+ device->discipline = NULL;
+out_minor:
+ tape_remove_minor(device);
+out:
+ module_put(discipline->owner);
+ return rc;
+}
+
+static inline void
+tape_cleanup_device(struct tape_device *device)
+{
+ tapeblock_cleanup_device(device);
+ tapechar_cleanup_device(device);
+ device->discipline->cleanup_device(device);
+ module_put(device->discipline->owner);
+ tape_remove_minor(device);
+ tape_med_state_set(device, MS_UNKNOWN);
+}
+
+/*
+ * Set device offline.
+ *
+ * Called by the common I/O layer if the drive should set offline on user
+ * request. We may prevent this by returning an error.
+ * Manual offline is only allowed while the drive is not in use.
+ */
+int
+tape_generic_offline(struct tape_device *device)
+{
+ if (!device) {
+ PRINT_ERR("tape_generic_offline: no such device\n");
+ return -ENODEV;
+ }
+
+ DBF_LH(3, "(%08x): tape_generic_offline(%p)\n",
+ device->cdev_id, device);
+
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ switch (device->tape_state) {
+ case TS_INIT:
+ case TS_NOT_OPER:
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ break;
+ case TS_UNUSED:
+ tape_state_set(device, TS_INIT);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ tape_cleanup_device(device);
+ break;
+ default:
+ DBF_EVENT(3, "(%08x): Set offline failed "
+ "- drive in use.\n",
+ device->cdev_id);
+ PRINT_WARN("(%s): Set offline failed "
+ "- drive in use.\n",
+ device->cdev->dev.bus_id);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ return -EBUSY;
+ }
+
+ DBF_LH(3, "(%08x): Drive set offline.\n", device->cdev_id);
+ return 0;
+}
+
+/*
+ * Allocate memory for a new device structure.
+ */
+static struct tape_device *
+tape_alloc_device(void)
+{
+ struct tape_device *device;
+
+ device = (struct tape_device *)
+ kmalloc(sizeof(struct tape_device), GFP_KERNEL);
+ if (device == NULL) {
+ DBF_EXCEPTION(2, "ti:no mem\n");
+ PRINT_INFO ("can't allocate memory for "
+ "tape info structure\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ memset(device, 0, sizeof(struct tape_device));
+ device->modeset_byte = (char *) kmalloc(1, GFP_KERNEL | GFP_DMA);
+ if (device->modeset_byte == NULL) {
+ DBF_EXCEPTION(2, "ti:no mem\n");
+ PRINT_INFO("can't allocate memory for modeset byte\n");
+ kfree(device);
+ return ERR_PTR(-ENOMEM);
+ }
+ INIT_LIST_HEAD(&device->req_queue);
+ INIT_LIST_HEAD(&device->node);
+ init_waitqueue_head(&device->state_change_wq);
+ device->tape_state = TS_INIT;
+ device->medium_state = MS_UNKNOWN;
+ *device->modeset_byte = 0;
+ device->first_minor = -1;
+ atomic_set(&device->ref_count, 1);
+
+ return device;
+}
+
+/*
+ * Get a reference to an existing device structure. This will automatically
+ * increment the reference count.
+ */
+struct tape_device *
+tape_get_device_reference(struct tape_device *device)
+{
+ DBF_EVENT(4, "tape_get_device_reference(%p) = %i\n", device,
+ atomic_inc_return(&device->ref_count));
+
+ return device;
+}
+
+/*
+ * Decrease the reference counter of a devices structure. If the
+ * reference counter reaches zero free the device structure.
+ * The function returns a NULL pointer to be used by the caller
+ * for clearing reference pointers.
+ */
+struct tape_device *
+tape_put_device(struct tape_device *device)
+{
+ int remain;
+
+ remain = atomic_dec_return(&device->ref_count);
+ if (remain > 0) {
+ DBF_EVENT(4, "tape_put_device(%p) -> %i\n", device, remain);
+ } else {
+ if (remain < 0) {
+ DBF_EVENT(4, "put device without reference\n");
+ PRINT_ERR("put device without reference\n");
+ } else {
+ DBF_EVENT(4, "tape_free_device(%p)\n", device);
+ kfree(device->modeset_byte);
+ kfree(device);
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Find tape device by a device index.
+ */
+struct tape_device *
+tape_get_device(int devindex)
+{
+ struct tape_device *device, *tmp;
+
+ device = ERR_PTR(-ENODEV);
+ read_lock(&tape_device_lock);
+ list_for_each_entry(tmp, &tape_device_list, node) {
+ if (tmp->first_minor / TAPE_MINORS_PER_DEV == devindex) {
+ device = tape_get_device_reference(tmp);
+ break;
+ }
+ }
+ read_unlock(&tape_device_lock);
+ return device;
+}
+
+/*
+ * Driverfs tape probe function.
+ */
+int
+tape_generic_probe(struct ccw_device *cdev)
+{
+ struct tape_device *device;
+
+ device = tape_alloc_device();
+ if (IS_ERR(device))
+ return -ENODEV;
+ PRINT_INFO("tape device %s found\n", cdev->dev.bus_id);
+ cdev->dev.driver_data = device;
+ device->cdev = cdev;
+ device->cdev_id = busid_to_int(cdev->dev.bus_id);
+ cdev->handler = __tape_do_irq;
+
+ ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
+ sysfs_create_group(&cdev->dev.kobj, &tape_attr_group);
+
+ return 0;
+}
+
+static inline void
+__tape_discard_requests(struct tape_device *device)
+{
+ struct tape_request * request;
+ struct list_head * l, *n;
+
+ list_for_each_safe(l, n, &device->req_queue) {
+ request = list_entry(l, struct tape_request, list);
+ if (request->status == TAPE_REQUEST_IN_IO)
+ request->status = TAPE_REQUEST_DONE;
+ list_del(&request->list);
+
+ /* Decrease ref_count for removed request. */
+ request->device = tape_put_device(device);
+ request->rc = -EIO;
+ if (request->callback != NULL)
+ request->callback(request, request->callback_data);
+ }
+}
+
+/*
+ * Driverfs tape remove function.
+ *
+ * This function is called whenever the common I/O layer detects the device
+ * gone. This can happen at any time and we cannot refuse.
+ */
+void
+tape_generic_remove(struct ccw_device *cdev)
+{
+ struct tape_device * device;
+
+ device = cdev->dev.driver_data;
+ if (!device) {
+ PRINT_ERR("No device pointer in tape_generic_remove!\n");
+ return;
+ }
+ DBF_LH(3, "(%08x): tape_generic_remove(%p)\n", device->cdev_id, cdev);
+
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ switch (device->tape_state) {
+ case TS_INIT:
+ tape_state_set(device, TS_NOT_OPER);
+ case TS_NOT_OPER:
+ /*
+ * Nothing to do.
+ */
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ break;
+ case TS_UNUSED:
+ /*
+ * Need only to release the device.
+ */
+ tape_state_set(device, TS_NOT_OPER);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ tape_cleanup_device(device);
+ break;
+ default:
+ /*
+ * There may be requests on the queue. We will not get
+ * an interrupt for a request that was running. So we
+ * just post them all as I/O errors.
+ */
+ DBF_EVENT(3, "(%08x): Drive in use vanished!\n",
+ device->cdev_id);
+ PRINT_WARN("(%s): Drive in use vanished - "
+ "expect trouble!\n",
+ device->cdev->dev.bus_id);
+ PRINT_WARN("State was %i\n", device->tape_state);
+ tape_state_set(device, TS_NOT_OPER);
+ __tape_discard_requests(device);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ tape_cleanup_device(device);
+ }
+
+ if (cdev->dev.driver_data != NULL) {
+ sysfs_remove_group(&cdev->dev.kobj, &tape_attr_group);
+ cdev->dev.driver_data = tape_put_device(cdev->dev.driver_data);
+ }
+}
+
+/*
+ * Allocate a new tape ccw request
+ */
+struct tape_request *
+tape_alloc_request(int cplength, int datasize)
+{
+ struct tape_request *request;
+
+ if (datasize > PAGE_SIZE || (cplength*sizeof(struct ccw1)) > PAGE_SIZE)
+ BUG();
+
+ DBF_LH(6, "tape_alloc_request(%d, %d)\n", cplength, datasize);
+
+ request = (struct tape_request *) kmalloc(sizeof(struct tape_request),
+ GFP_KERNEL);
+ if (request == NULL) {
+ DBF_EXCEPTION(1, "cqra nomem\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ memset(request, 0, sizeof(struct tape_request));
+ /* allocate channel program */
+ if (cplength > 0) {
+ request->cpaddr = kmalloc(cplength*sizeof(struct ccw1),
+ GFP_ATOMIC | GFP_DMA);
+ if (request->cpaddr == NULL) {
+ DBF_EXCEPTION(1, "cqra nomem\n");
+ kfree(request);
+ return ERR_PTR(-ENOMEM);
+ }
+ memset(request->cpaddr, 0, cplength*sizeof(struct ccw1));
+ }
+ /* alloc small kernel buffer */
+ if (datasize > 0) {
+ request->cpdata = kmalloc(datasize, GFP_KERNEL | GFP_DMA);
+ if (request->cpdata == NULL) {
+ DBF_EXCEPTION(1, "cqra nomem\n");
+ if (request->cpaddr != NULL)
+ kfree(request->cpaddr);
+ kfree(request);
+ return ERR_PTR(-ENOMEM);
+ }
+ memset(request->cpdata, 0, datasize);
+ }
+ DBF_LH(6, "New request %p(%p/%p)\n", request, request->cpaddr,
+ request->cpdata);
+
+ return request;
+}
+
+/*
+ * Free tape ccw request
+ */
+void
+tape_free_request (struct tape_request * request)
+{
+ DBF_LH(6, "Free request %p\n", request);
+
+ if (request->device != NULL) {
+ request->device = tape_put_device(request->device);
+ }
+ if (request->cpdata != NULL)
+ kfree(request->cpdata);
+ if (request->cpaddr != NULL)
+ kfree(request->cpaddr);
+ kfree(request);
+}
+
+static inline void
+__tape_do_io_list(struct tape_device *device)
+{
+ struct list_head *l, *n;
+ struct tape_request *request;
+ int rc;
+
+ DBF_LH(6, "__tape_do_io_list(%p)\n", device);
+ /*
+ * Try to start each request on request queue until one is
+ * started successful.
+ */
+ list_for_each_safe(l, n, &device->req_queue) {
+ request = list_entry(l, struct tape_request, list);
+#ifdef CONFIG_S390_TAPE_BLOCK
+ if (request->op == TO_BLOCK)
+ device->discipline->check_locate(device, request);
+#endif
+ rc = ccw_device_start(device->cdev, request->cpaddr,
+ (unsigned long) request, 0x00,
+ request->options);
+ if (rc == 0) {
+ request->status = TAPE_REQUEST_IN_IO;
+ break;
+ }
+ /* Start failed. Remove request and indicate failure. */
+ DBF_EVENT(1, "tape: DOIO failed with er = %i\n", rc);
+
+ /* Set ending status and do callback. */
+ request->rc = rc;
+ request->status = TAPE_REQUEST_DONE;
+ __tape_remove_request(device, request);
+ }
+}
+
+static void
+__tape_remove_request(struct tape_device *device, struct tape_request *request)
+{
+ /* Remove from request queue. */
+ list_del(&request->list);
+
+ /* Do callback. */
+ if (request->callback != NULL)
+ request->callback(request, request->callback_data);
+
+ /* Start next request. */
+ if (!list_empty(&device->req_queue))
+ __tape_do_io_list(device);
+}
+
+/*
+ * Write sense data to console/dbf
+ */
+void
+tape_dump_sense(struct tape_device* device, struct tape_request *request,
+ struct irb *irb)
+{
+ unsigned int *sptr;
+
+ PRINT_INFO("-------------------------------------------------\n");
+ PRINT_INFO("DSTAT : %02x CSTAT: %02x CPA: %04x\n",
+ irb->scsw.dstat, irb->scsw.cstat, irb->scsw.cpa);
+ PRINT_INFO("DEVICE: %s\n", device->cdev->dev.bus_id);
+ if (request != NULL)
+ PRINT_INFO("OP : %s\n", tape_op_verbose[request->op]);
+
+ sptr = (unsigned int *) irb->ecw;
+ PRINT_INFO("Sense data: %08X %08X %08X %08X \n",
+ sptr[0], sptr[1], sptr[2], sptr[3]);
+ PRINT_INFO("Sense data: %08X %08X %08X %08X \n",
+ sptr[4], sptr[5], sptr[6], sptr[7]);
+ PRINT_INFO("--------------------------------------------------\n");
+}
+
+/*
+ * Write sense data to dbf
+ */
+void
+tape_dump_sense_dbf(struct tape_device *device, struct tape_request *request,
+ struct irb *irb)
+{
+ unsigned int *sptr;
+ const char* op;
+
+ if (request != NULL)
+ op = tape_op_verbose[request->op];
+ else
+ op = "---";
+ DBF_EVENT(3, "DSTAT : %02x CSTAT: %02x\n",
+ irb->scsw.dstat,irb->scsw.cstat);
+ DBF_EVENT(3, "DEVICE: %08x OP\t: %s\n", device->cdev_id, op);
+ sptr = (unsigned int *) irb->ecw;
+ DBF_EVENT(3, "%08x %08x\n", sptr[0], sptr[1]);
+ DBF_EVENT(3, "%08x %08x\n", sptr[2], sptr[3]);
+ DBF_EVENT(3, "%08x %08x\n", sptr[4], sptr[5]);
+ DBF_EVENT(3, "%08x %08x\n", sptr[6], sptr[7]);
+}
+
+/*
+ * I/O helper function. Adds the request to the request queue
+ * and starts it if the tape is idle. Has to be called with
+ * the device lock held.
+ */
+static inline int
+__tape_do_io(struct tape_device *device, struct tape_request *request)
+{
+ int rc;
+
+ switch (request->op) {
+ case TO_MSEN:
+ case TO_ASSIGN:
+ case TO_UNASSIGN:
+ case TO_READ_ATTMSG:
+ if (device->tape_state == TS_INIT)
+ break;
+ if (device->tape_state == TS_UNUSED)
+ break;
+ default:
+ if (device->tape_state == TS_BLKUSE)
+ break;
+ if (device->tape_state != TS_IN_USE)
+ return -ENODEV;
+ }
+
+ /* Increase use count of device for the added request. */
+ request->device = tape_get_device_reference(device);
+
+ if (list_empty(&device->req_queue)) {
+ /* No other requests are on the queue. Start this one. */
+#ifdef CONFIG_S390_TAPE_BLOCK
+ if (request->op == TO_BLOCK)
+ device->discipline->check_locate(device, request);
+#endif
+ rc = ccw_device_start(device->cdev, request->cpaddr,
+ (unsigned long) request, 0x00,
+ request->options);
+ if (rc) {
+ DBF_EVENT(1, "tape: DOIO failed with rc = %i\n", rc);
+ return rc;
+ }
+ DBF_LH(5, "Request %p added for execution.\n", request);
+ list_add(&request->list, &device->req_queue);
+ request->status = TAPE_REQUEST_IN_IO;
+ } else {
+ DBF_LH(5, "Request %p add to queue.\n", request);
+ list_add_tail(&request->list, &device->req_queue);
+ request->status = TAPE_REQUEST_QUEUED;
+ }
+ return 0;
+}
+
+/*
+ * Add the request to the request queue, try to start it if the
+ * tape is idle. Return without waiting for end of i/o.
+ */
+int
+tape_do_io_async(struct tape_device *device, struct tape_request *request)
+{
+ int rc;
+
+ DBF_LH(6, "tape_do_io_async(%p, %p)\n", device, request);
+
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ /* Add request to request queue and try to start it. */
+ rc = __tape_do_io(device, request);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ return rc;
+}
+
+/*
+ * tape_do_io/__tape_wake_up
+ * Add the request to the request queue, try to start it if the
+ * tape is idle and wait uninterruptible for its completion.
+ */
+static void
+__tape_wake_up(struct tape_request *request, void *data)
+{
+ request->callback = NULL;
+ wake_up((wait_queue_head_t *) data);
+}
+
+int
+tape_do_io(struct tape_device *device, struct tape_request *request)
+{
+ wait_queue_head_t wq;
+ int rc;
+
+ init_waitqueue_head(&wq);
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ /* Setup callback */
+ request->callback = __tape_wake_up;
+ request->callback_data = &wq;
+ /* Add request to request queue and try to start it. */
+ rc = __tape_do_io(device, request);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ if (rc)
+ return rc;
+ /* Request added to the queue. Wait for its completion. */
+ wait_event(wq, (request->callback == NULL));
+ /* Get rc from request */
+ return request->rc;
+}
+
+/*
+ * tape_do_io_interruptible/__tape_wake_up_interruptible
+ * Add the request to the request queue, try to start it if the
+ * tape is idle and wait uninterruptible for its completion.
+ */
+static void
+__tape_wake_up_interruptible(struct tape_request *request, void *data)
+{
+ request->callback = NULL;
+ wake_up_interruptible((wait_queue_head_t *) data);
+}
+
+int
+tape_do_io_interruptible(struct tape_device *device,
+ struct tape_request *request)
+{
+ wait_queue_head_t wq;
+ int rc;
+
+ init_waitqueue_head(&wq);
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ /* Setup callback */
+ request->callback = __tape_wake_up_interruptible;
+ request->callback_data = &wq;
+ rc = __tape_do_io(device, request);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ if (rc)
+ return rc;
+ /* Request added to the queue. Wait for its completion. */
+ rc = wait_event_interruptible(wq, (request->callback == NULL));
+ if (rc != -ERESTARTSYS)
+ /* Request finished normally. */
+ return request->rc;
+ /* Interrupted by a signal. We have to stop the current request. */
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ rc = __tape_halt_io(device, request);
+ if (rc == 0) {
+ DBF_EVENT(3, "IO stopped on %08x\n", device->cdev_id);
+ rc = -ERESTARTSYS;
+ }
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ return rc;
+}
+
+/*
+ * Handle requests that return an i/o error in the irb.
+ */
+static inline void
+tape_handle_killed_request(
+ struct tape_device *device,
+ struct tape_request *request)
+{
+ if(request != NULL) {
+ /* Set ending status. FIXME: Should the request be retried? */
+ request->rc = -EIO;
+ request->status = TAPE_REQUEST_DONE;
+ __tape_remove_request(device, request);
+ } else {
+ __tape_do_io_list(device);
+ }
+}
+
+/*
+ * Tape interrupt routine, called from the ccw_device layer
+ */
+static void
+__tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
+{
+ struct tape_device *device;
+ struct tape_request *request;
+ int final;
+ int rc;
+
+ device = (struct tape_device *) cdev->dev.driver_data;
+ if (device == NULL) {
+ PRINT_ERR("could not get device structure for %s "
+ "in interrupt\n", cdev->dev.bus_id);
+ return;
+ }
+ request = (struct tape_request *) intparm;
+
+ DBF_LH(6, "__tape_do_irq(device=%p, request=%p)\n", device, request);
+
+ /* On special conditions irb is an error pointer */
+ if (IS_ERR(irb)) {
+ switch (PTR_ERR(irb)) {
+ case -ETIMEDOUT:
+ PRINT_WARN("(%s): Request timed out\n",
+ cdev->dev.bus_id);
+ case -EIO:
+ tape_handle_killed_request(device, request);
+ break;
+ default:
+ PRINT_ERR("(%s): Unexpected i/o error %li\n",
+ cdev->dev.bus_id,
+ PTR_ERR(irb));
+ }
+ return;
+ }
+
+ /* May be an unsolicited irq */
+ if(request != NULL)
+ request->rescnt = irb->scsw.count;
+
+ if (irb->scsw.dstat != 0x0c) {
+ /* Set the 'ONLINE' flag depending on sense byte 1 */
+ if(*(((__u8 *) irb->ecw) + 1) & SENSE_DRIVE_ONLINE)
+ device->tape_generic_status |= GMT_ONLINE(~0);
+ else
+ device->tape_generic_status &= ~GMT_ONLINE(~0);
+
+ /*
+ * Any request that does not come back with channel end
+ * and device end is unusual. Log the sense data.
+ */
+ DBF_EVENT(3,"-- Tape Interrupthandler --\n");
+ tape_dump_sense_dbf(device, request, irb);
+ } else {
+ /* Upon normal completion the device _is_ online */
+ device->tape_generic_status |= GMT_ONLINE(~0);
+ }
+ if (device->tape_state == TS_NOT_OPER) {
+ DBF_EVENT(6, "tape:device is not operational\n");
+ return;
+ }
+
+ /*
+ * Request that were canceled still come back with an interrupt.
+ * To detect these request the state will be set to TAPE_REQUEST_DONE.
+ */
+ if(request != NULL && request->status == TAPE_REQUEST_DONE) {
+ __tape_remove_request(device, request);
+ return;
+ }
+
+ rc = device->discipline->irq(device, request, irb);
+ /*
+ * rc < 0 : request finished unsuccessfully.
+ * rc == TAPE_IO_SUCCESS: request finished successfully.
+ * rc == TAPE_IO_PENDING: request is still running. Ignore rc.
+ * rc == TAPE_IO_RETRY: request finished but needs another go.
+ * rc == TAPE_IO_STOP: request needs to get terminated.
+ */
+ final = 0;
+ switch (rc) {
+ case TAPE_IO_SUCCESS:
+ /* Upon normal completion the device _is_ online */
+ device->tape_generic_status |= GMT_ONLINE(~0);
+ final = 1;
+ break;
+ case TAPE_IO_PENDING:
+ break;
+ case TAPE_IO_RETRY:
+#ifdef CONFIG_S390_TAPE_BLOCK
+ if (request->op == TO_BLOCK)
+ device->discipline->check_locate(device, request);
+#endif
+ rc = ccw_device_start(cdev, request->cpaddr,
+ (unsigned long) request, 0x00,
+ request->options);
+ if (rc) {
+ DBF_EVENT(1, "tape: DOIO failed with er = %i\n", rc);
+ final = 1;
+ }
+ break;
+ case TAPE_IO_STOP:
+ __tape_halt_io(device, request);
+ break;
+ default:
+ if (rc > 0) {
+ DBF_EVENT(6, "xunknownrc\n");
+ PRINT_ERR("Invalid return code from discipline "
+ "interrupt function.\n");
+ rc = -EIO;
+ }
+ final = 1;
+ break;
+ }
+ if (final) {
+ /* May be an unsolicited irq */
+ if(request != NULL) {
+ /* Set ending status. */
+ request->rc = rc;
+ request->status = TAPE_REQUEST_DONE;
+ __tape_remove_request(device, request);
+ } else {
+ __tape_do_io_list(device);
+ }
+ }
+}
+
+/*
+ * Tape device open function used by tape_char & tape_block frontends.
+ */
+int
+tape_open(struct tape_device *device)
+{
+ int rc;
+
+ spin_lock(get_ccwdev_lock(device->cdev));
+ if (device->tape_state == TS_NOT_OPER) {
+ DBF_EVENT(6, "TAPE:nodev\n");
+ rc = -ENODEV;
+ } else if (device->tape_state == TS_IN_USE) {
+ DBF_EVENT(6, "TAPE:dbusy\n");
+ rc = -EBUSY;
+ } else if (device->tape_state == TS_BLKUSE) {
+ DBF_EVENT(6, "TAPE:dbusy\n");
+ rc = -EBUSY;
+ } else if (device->discipline != NULL &&
+ !try_module_get(device->discipline->owner)) {
+ DBF_EVENT(6, "TAPE:nodisc\n");
+ rc = -ENODEV;
+ } else {
+ tape_state_set(device, TS_IN_USE);
+ rc = 0;
+ }
+ spin_unlock(get_ccwdev_lock(device->cdev));
+ return rc;
+}
+
+/*
+ * Tape device release function used by tape_char & tape_block frontends.
+ */
+int
+tape_release(struct tape_device *device)
+{
+ spin_lock(get_ccwdev_lock(device->cdev));
+ if (device->tape_state == TS_IN_USE)
+ tape_state_set(device, TS_UNUSED);
+ module_put(device->discipline->owner);
+ spin_unlock(get_ccwdev_lock(device->cdev));
+ return 0;
+}
+
+/*
+ * Execute a magnetic tape command a number of times.
+ */
+int
+tape_mtop(struct tape_device *device, int mt_op, int mt_count)
+{
+ tape_mtop_fn fn;
+ int rc;
+
+ DBF_EVENT(6, "TAPE:mtio\n");
+ DBF_EVENT(6, "TAPE:ioop: %x\n", mt_op);
+ DBF_EVENT(6, "TAPE:arg: %x\n", mt_count);
+
+ if (mt_op < 0 || mt_op >= TAPE_NR_MTOPS)
+ return -EINVAL;
+ fn = device->discipline->mtop_array[mt_op];
+ if (fn == NULL)
+ return -EINVAL;
+
+ /* We assume that the backends can handle count up to 500. */
+ if (mt_op == MTBSR || mt_op == MTFSR || mt_op == MTFSF ||
+ mt_op == MTBSF || mt_op == MTFSFM || mt_op == MTBSFM) {
+ rc = 0;
+ for (; mt_count > 500; mt_count -= 500)
+ if ((rc = fn(device, 500)) != 0)
+ break;
+ if (rc == 0)
+ rc = fn(device, mt_count);
+ } else
+ rc = fn(device, mt_count);
+ return rc;
+
+}
+
+/*
+ * Tape init function.
+ */
+static int
+tape_init (void)
+{
+ TAPE_DBF_AREA = debug_register ( "tape", 1, 2, 4*sizeof(long));
+ debug_register_view(TAPE_DBF_AREA, &debug_sprintf_view);
+#ifdef DBF_LIKE_HELL
+ debug_set_level(TAPE_DBF_AREA, 6);
+#endif
+ DBF_EVENT(3, "tape init: ($Revision: 1.51 $)\n");
+ tape_proc_init();
+ tapechar_init ();
+ tapeblock_init ();
+ return 0;
+}
+
+/*
+ * Tape exit function.
+ */
+static void
+tape_exit(void)
+{
+ DBF_EVENT(6, "tape exit\n");
+
+ /* Get rid of the frontends */
+ tapechar_exit();
+ tapeblock_exit();
+ tape_proc_cleanup();
+ debug_unregister (TAPE_DBF_AREA);
+}
+
+MODULE_AUTHOR("(C) 2001 IBM Deutschland Entwicklung GmbH by Carsten Otte and "
+ "Michael Holzheu (cotte@de.ibm.com,holzheu@de.ibm.com)");
+MODULE_DESCRIPTION("Linux on zSeries channel attached "
+ "tape device driver ($Revision: 1.51 $)");
+MODULE_LICENSE("GPL");
+
+module_init(tape_init);
+module_exit(tape_exit);
+
+EXPORT_SYMBOL(tape_generic_remove);
+EXPORT_SYMBOL(tape_generic_probe);
+EXPORT_SYMBOL(tape_generic_online);
+EXPORT_SYMBOL(tape_generic_offline);
+EXPORT_SYMBOL(tape_put_device);
+EXPORT_SYMBOL(tape_get_device_reference);
+EXPORT_SYMBOL(tape_state_verbose);
+EXPORT_SYMBOL(tape_op_verbose);
+EXPORT_SYMBOL(tape_state_set);
+EXPORT_SYMBOL(tape_med_state_set);
+EXPORT_SYMBOL(tape_alloc_request);
+EXPORT_SYMBOL(tape_free_request);
+EXPORT_SYMBOL(tape_dump_sense);
+EXPORT_SYMBOL(tape_dump_sense_dbf);
+EXPORT_SYMBOL(tape_do_io);
+EXPORT_SYMBOL(tape_do_io_async);
+EXPORT_SYMBOL(tape_do_io_interruptible);
+EXPORT_SYMBOL(tape_mtop);
diff --git a/drivers/s390/char/tape_proc.c b/drivers/s390/char/tape_proc.c
new file mode 100644
index 000000000000..801d17cca34e
--- /dev/null
+++ b/drivers/s390/char/tape_proc.c
@@ -0,0 +1,145 @@
+/*
+ * drivers/s390/char/tape.c
+ * tape device driver for S/390 and zSeries tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001 IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Michael Holzheu <holzheu@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ *
+ * PROCFS Functions
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/seq_file.h>
+
+#define TAPE_DBF_AREA tape_core_dbf
+
+#include "tape.h"
+
+#define PRINTK_HEADER "TAPE_PROC: "
+
+static const char *tape_med_st_verbose[MS_SIZE] =
+{
+ [MS_UNKNOWN] = "UNKNOWN ",
+ [MS_LOADED] = "LOADED ",
+ [MS_UNLOADED] = "UNLOADED"
+};
+
+/* our proc tapedevices entry */
+static struct proc_dir_entry *tape_proc_devices;
+
+/*
+ * Show function for /proc/tapedevices
+ */
+static int tape_proc_show(struct seq_file *m, void *v)
+{
+ struct tape_device *device;
+ struct tape_request *request;
+ const char *str;
+ unsigned long n;
+
+ n = (unsigned long) v - 1;
+ if (!n) {
+ seq_printf(m, "TapeNo\tBusID CuType/Model\t"
+ "DevType/Model\tBlkSize\tState\tOp\tMedState\n");
+ }
+ device = tape_get_device(n);
+ if (IS_ERR(device))
+ return 0;
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ seq_printf(m, "%d\t", (int) n);
+ seq_printf(m, "%-10.10s ", device->cdev->dev.bus_id);
+ seq_printf(m, "%04X/", device->cdev->id.cu_type);
+ seq_printf(m, "%02X\t", device->cdev->id.cu_model);
+ seq_printf(m, "%04X/", device->cdev->id.dev_type);
+ seq_printf(m, "%02X\t\t", device->cdev->id.dev_model);
+ if (device->char_data.block_size == 0)
+ seq_printf(m, "auto\t");
+ else
+ seq_printf(m, "%i\t", device->char_data.block_size);
+ if (device->tape_state >= 0 &&
+ device->tape_state < TS_SIZE)
+ str = tape_state_verbose[device->tape_state];
+ else
+ str = "UNKNOWN";
+ seq_printf(m, "%s\t", str);
+ if (!list_empty(&device->req_queue)) {
+ request = list_entry(device->req_queue.next,
+ struct tape_request, list);
+ str = tape_op_verbose[request->op];
+ } else
+ str = "---";
+ seq_printf(m, "%s\t", str);
+ seq_printf(m, "%s\n", tape_med_st_verbose[device->medium_state]);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ tape_put_device(device);
+ return 0;
+}
+
+static void *tape_proc_start(struct seq_file *m, loff_t *pos)
+{
+ if (*pos >= 256 / TAPE_MINORS_PER_DEV)
+ return NULL;
+ return (void *)((unsigned long) *pos + 1);
+}
+
+static void *tape_proc_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ ++*pos;
+ return tape_proc_start(m, pos);
+}
+
+static void tape_proc_stop(struct seq_file *m, void *v)
+{
+}
+
+static struct seq_operations tape_proc_seq = {
+ .start = tape_proc_start,
+ .next = tape_proc_next,
+ .stop = tape_proc_stop,
+ .show = tape_proc_show,
+};
+
+static int tape_proc_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &tape_proc_seq);
+}
+
+static struct file_operations tape_proc_ops =
+{
+ .open = tape_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+/*
+ * Initialize procfs stuff on startup
+ */
+void
+tape_proc_init(void)
+{
+ tape_proc_devices =
+ create_proc_entry ("tapedevices", S_IFREG | S_IRUGO | S_IWUSR,
+ &proc_root);
+ if (tape_proc_devices == NULL) {
+ PRINT_WARN("tape: Cannot register procfs entry tapedevices\n");
+ return;
+ }
+ tape_proc_devices->proc_fops = &tape_proc_ops;
+ tape_proc_devices->owner = THIS_MODULE;
+}
+
+/*
+ * Cleanup all stuff registered to the procfs
+ */
+void
+tape_proc_cleanup(void)
+{
+ if (tape_proc_devices != NULL)
+ remove_proc_entry ("tapedevices", &proc_root);
+}
diff --git a/drivers/s390/char/tape_std.c b/drivers/s390/char/tape_std.c
new file mode 100644
index 000000000000..2f9fe30989a7
--- /dev/null
+++ b/drivers/s390/char/tape_std.c
@@ -0,0 +1,765 @@
+/*
+ * drivers/s390/char/tape_std.c
+ * standard tape device functions for ibm tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Michael Holzheu <holzheu@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Stefan Bader <shbader@de.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/bio.h>
+#include <linux/timer.h>
+
+#include <asm/types.h>
+#include <asm/idals.h>
+#include <asm/ebcdic.h>
+#include <asm/tape390.h>
+
+#define TAPE_DBF_AREA tape_core_dbf
+
+#include "tape.h"
+#include "tape_std.h"
+
+#define PRINTK_HEADER "TAPE_STD: "
+
+/*
+ * tape_std_assign
+ */
+static void
+tape_std_assign_timeout(unsigned long data)
+{
+ struct tape_request * request;
+ struct tape_device * device;
+
+ request = (struct tape_request *) data;
+ if ((device = request->device) == NULL)
+ BUG();
+
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ if (request->callback != NULL) {
+ DBF_EVENT(3, "%08x: Assignment timeout. Device busy.\n",
+ device->cdev_id);
+ PRINT_ERR("%s: Assignment timeout. Device busy.\n",
+ device->cdev->dev.bus_id);
+ ccw_device_clear(device->cdev, (long) request);
+ }
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+}
+
+int
+tape_std_assign(struct tape_device *device)
+{
+ int rc;
+ struct timer_list timeout;
+ struct tape_request *request;
+
+ request = tape_alloc_request(2, 11);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+
+ request->op = TO_ASSIGN;
+ tape_ccw_cc(request->cpaddr, ASSIGN, 11, request->cpdata);
+ tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
+
+ /*
+ * The assign command sometimes blocks if the device is assigned
+ * to another host (actually this shouldn't happen but it does).
+ * So we set up a timeout for this call.
+ */
+ init_timer(&timeout);
+ timeout.function = tape_std_assign_timeout;
+ timeout.data = (unsigned long) request;
+ timeout.expires = jiffies + 2 * HZ;
+ add_timer(&timeout);
+
+ rc = tape_do_io_interruptible(device, request);
+
+ del_timer(&timeout);
+
+ if (rc != 0) {
+ PRINT_WARN("%s: assign failed - device might be busy\n",
+ device->cdev->dev.bus_id);
+ DBF_EVENT(3, "%08x: assign failed - device might be busy\n",
+ device->cdev_id);
+ } else {
+ DBF_EVENT(3, "%08x: Tape assigned\n", device->cdev_id);
+ }
+ tape_free_request(request);
+ return rc;
+}
+
+/*
+ * tape_std_unassign
+ */
+int
+tape_std_unassign (struct tape_device *device)
+{
+ int rc;
+ struct tape_request *request;
+
+ if (device->tape_state == TS_NOT_OPER) {
+ DBF_EVENT(3, "(%08x): Can't unassign device\n",
+ device->cdev_id);
+ PRINT_WARN("(%s): Can't unassign device - device gone\n",
+ device->cdev->dev.bus_id);
+ return -EIO;
+ }
+
+ request = tape_alloc_request(2, 11);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+
+ request->op = TO_UNASSIGN;
+ tape_ccw_cc(request->cpaddr, UNASSIGN, 11, request->cpdata);
+ tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
+
+ if ((rc = tape_do_io(device, request)) != 0) {
+ DBF_EVENT(3, "%08x: Unassign failed\n", device->cdev_id);
+ PRINT_WARN("%s: Unassign failed\n", device->cdev->dev.bus_id);
+ } else {
+ DBF_EVENT(3, "%08x: Tape unassigned\n", device->cdev_id);
+ }
+ tape_free_request(request);
+ return rc;
+}
+
+/*
+ * TAPE390_DISPLAY: Show a string on the tape display.
+ */
+int
+tape_std_display(struct tape_device *device, struct display_struct *disp)
+{
+ struct tape_request *request;
+ int rc;
+
+ request = tape_alloc_request(2, 17);
+ if (IS_ERR(request)) {
+ DBF_EVENT(3, "TAPE: load display failed\n");
+ return PTR_ERR(request);
+ }
+ request->op = TO_DIS;
+
+ *(unsigned char *) request->cpdata = disp->cntrl;
+ DBF_EVENT(5, "TAPE: display cntrl=%04x\n", disp->cntrl);
+ memcpy(((unsigned char *) request->cpdata) + 1, disp->message1, 8);
+ memcpy(((unsigned char *) request->cpdata) + 9, disp->message2, 8);
+ ASCEBC(((unsigned char*) request->cpdata) + 1, 16);
+
+ tape_ccw_cc(request->cpaddr, LOAD_DISPLAY, 17, request->cpdata);
+ tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
+
+ rc = tape_do_io_interruptible(device, request);
+ tape_free_request(request);
+ return rc;
+}
+
+/*
+ * Read block id.
+ */
+int
+tape_std_read_block_id(struct tape_device *device, __u64 *id)
+{
+ struct tape_request *request;
+ int rc;
+
+ request = tape_alloc_request(3, 8);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_RBI;
+ /* setup ccws */
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_cc(request->cpaddr + 1, READ_BLOCK_ID, 8, request->cpdata);
+ tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
+ /* execute it */
+ rc = tape_do_io(device, request);
+ if (rc == 0)
+ /* Get result from read buffer. */
+ *id = *(__u64 *) request->cpdata;
+ tape_free_request(request);
+ return rc;
+}
+
+int
+tape_std_terminate_write(struct tape_device *device)
+{
+ int rc;
+
+ if(device->required_tapemarks == 0)
+ return 0;
+
+ DBF_LH(5, "tape%d: terminate write %dxEOF\n", device->first_minor,
+ device->required_tapemarks);
+
+ rc = tape_mtop(device, MTWEOF, device->required_tapemarks);
+ if (rc)
+ return rc;
+
+ device->required_tapemarks = 0;
+ return tape_mtop(device, MTBSR, 1);
+}
+
+/*
+ * MTLOAD: Loads the tape.
+ * The default implementation just wait until the tape medium state changes
+ * to MS_LOADED.
+ */
+int
+tape_std_mtload(struct tape_device *device, int count)
+{
+ return wait_event_interruptible(device->state_change_wq,
+ (device->medium_state == MS_LOADED));
+}
+
+/*
+ * MTSETBLK: Set block size.
+ */
+int
+tape_std_mtsetblk(struct tape_device *device, int count)
+{
+ struct idal_buffer *new;
+
+ DBF_LH(6, "tape_std_mtsetblk(%d)\n", count);
+ if (count <= 0) {
+ /*
+ * Just set block_size to 0. tapechar_read/tapechar_write
+ * will realloc the idal buffer if a bigger one than the
+ * current is needed.
+ */
+ device->char_data.block_size = 0;
+ return 0;
+ }
+ if (device->char_data.idal_buf != NULL &&
+ device->char_data.idal_buf->size == count)
+ /* We already have a idal buffer of that size. */
+ return 0;
+
+ if (count > MAX_BLOCKSIZE) {
+ DBF_EVENT(3, "Invalid block size (%d > %d) given.\n",
+ count, MAX_BLOCKSIZE);
+ PRINT_ERR("Invalid block size (%d > %d) given.\n",
+ count, MAX_BLOCKSIZE);
+ return -EINVAL;
+ }
+
+ /* Allocate a new idal buffer. */
+ new = idal_buffer_alloc(count, 0);
+ if (new == NULL)
+ return -ENOMEM;
+ if (device->char_data.idal_buf != NULL)
+ idal_buffer_free(device->char_data.idal_buf);
+ device->char_data.idal_buf = new;
+ device->char_data.block_size = count;
+
+ DBF_LH(6, "new blocksize is %d\n", device->char_data.block_size);
+
+ return 0;
+}
+
+/*
+ * MTRESET: Set block size to 0.
+ */
+int
+tape_std_mtreset(struct tape_device *device, int count)
+{
+ DBF_EVENT(6, "TCHAR:devreset:\n");
+ device->char_data.block_size = 0;
+ return 0;
+}
+
+/*
+ * MTFSF: Forward space over 'count' file marks. The tape is positioned
+ * at the EOT (End of Tape) side of the file mark.
+ */
+int
+tape_std_mtfsf(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+
+ request = tape_alloc_request(mt_count + 2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_FSF;
+ /* setup ccws */
+ ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ ccw = tape_ccw_repeat(ccw, FORSPACEFILE, mt_count);
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * MTFSR: Forward space over 'count' tape blocks (blocksize is set
+ * via MTSETBLK.
+ */
+int
+tape_std_mtfsr(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+ int rc;
+
+ request = tape_alloc_request(mt_count + 2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_FSB;
+ /* setup ccws */
+ ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ ccw = tape_ccw_repeat(ccw, FORSPACEBLOCK, mt_count);
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+
+ /* execute it */
+ rc = tape_do_io(device, request);
+ if (rc == 0 && request->rescnt > 0) {
+ DBF_LH(3, "FSR over tapemark\n");
+ rc = 1;
+ }
+ tape_free_request(request);
+
+ return rc;
+}
+
+/*
+ * MTBSR: Backward space over 'count' tape blocks.
+ * (blocksize is set via MTSETBLK.
+ */
+int
+tape_std_mtbsr(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+ int rc;
+
+ request = tape_alloc_request(mt_count + 2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_BSB;
+ /* setup ccws */
+ ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ ccw = tape_ccw_repeat(ccw, BACKSPACEBLOCK, mt_count);
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+
+ /* execute it */
+ rc = tape_do_io(device, request);
+ if (rc == 0 && request->rescnt > 0) {
+ DBF_LH(3, "BSR over tapemark\n");
+ rc = 1;
+ }
+ tape_free_request(request);
+
+ return rc;
+}
+
+/*
+ * MTWEOF: Write 'count' file marks at the current position.
+ */
+int
+tape_std_mtweof(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+
+ request = tape_alloc_request(mt_count + 2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_WTM;
+ /* setup ccws */
+ ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ ccw = tape_ccw_repeat(ccw, WRITETAPEMARK, mt_count);
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * MTBSFM: Backward space over 'count' file marks.
+ * The tape is positioned at the BOT (Begin Of Tape) side of the
+ * last skipped file mark.
+ */
+int
+tape_std_mtbsfm(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+
+ request = tape_alloc_request(mt_count + 2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_BSF;
+ /* setup ccws */
+ ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ ccw = tape_ccw_repeat(ccw, BACKSPACEFILE, mt_count);
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * MTBSF: Backward space over 'count' file marks. The tape is positioned at
+ * the EOT (End of Tape) side of the last skipped file mark.
+ */
+int
+tape_std_mtbsf(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+ int rc;
+
+ request = tape_alloc_request(mt_count + 2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_BSF;
+ /* setup ccws */
+ ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ ccw = tape_ccw_repeat(ccw, BACKSPACEFILE, mt_count);
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+ /* execute it */
+ rc = tape_do_io_free(device, request);
+ if (rc == 0) {
+ rc = tape_mtop(device, MTFSR, 1);
+ if (rc > 0)
+ rc = 0;
+ }
+ return rc;
+}
+
+/*
+ * MTFSFM: Forward space over 'count' file marks.
+ * The tape is positioned at the BOT (Begin Of Tape) side
+ * of the last skipped file mark.
+ */
+int
+tape_std_mtfsfm(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ struct ccw1 *ccw;
+ int rc;
+
+ request = tape_alloc_request(mt_count + 2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_FSF;
+ /* setup ccws */
+ ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ ccw = tape_ccw_repeat(ccw, FORSPACEFILE, mt_count);
+ ccw = tape_ccw_end(ccw, NOP, 0, NULL);
+ /* execute it */
+ rc = tape_do_io_free(device, request);
+ if (rc == 0) {
+ rc = tape_mtop(device, MTBSR, 1);
+ if (rc > 0)
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/*
+ * MTREW: Rewind the tape.
+ */
+int
+tape_std_mtrew(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+
+ request = tape_alloc_request(3, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_REW;
+ /* setup ccws */
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
+ device->modeset_byte);
+ tape_ccw_cc(request->cpaddr + 1, REWIND, 0, NULL);
+ tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
+
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * MTOFFL: Rewind the tape and put the drive off-line.
+ * Implement 'rewind unload'
+ */
+int
+tape_std_mtoffl(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+
+ request = tape_alloc_request(3, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_RUN;
+ /* setup ccws */
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_cc(request->cpaddr + 1, REWIND_UNLOAD, 0, NULL);
+ tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
+
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * MTNOP: 'No operation'.
+ */
+int
+tape_std_mtnop(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+
+ request = tape_alloc_request(2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_NOP;
+ /* setup ccws */
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * MTEOM: positions at the end of the portion of the tape already used
+ * for recordind data. MTEOM positions after the last file mark, ready for
+ * appending another file.
+ */
+int
+tape_std_mteom(struct tape_device *device, int mt_count)
+{
+ int rc;
+
+ /*
+ * Seek from the beginning of tape (rewind).
+ */
+ if ((rc = tape_mtop(device, MTREW, 1)) < 0)
+ return rc;
+
+ /*
+ * The logical end of volume is given by two sewuential tapemarks.
+ * Look for this by skipping to the next file (over one tapemark)
+ * and then test for another one (fsr returns 1 if a tapemark was
+ * encountered).
+ */
+ do {
+ if ((rc = tape_mtop(device, MTFSF, 1)) < 0)
+ return rc;
+ if ((rc = tape_mtop(device, MTFSR, 1)) < 0)
+ return rc;
+ } while (rc == 0);
+
+ return tape_mtop(device, MTBSR, 1);
+}
+
+/*
+ * MTRETEN: Retension the tape, i.e. forward space to end of tape and rewind.
+ */
+int
+tape_std_mtreten(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+ int rc;
+
+ request = tape_alloc_request(4, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_FSF;
+ /* setup ccws */
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_cc(request->cpaddr + 1,FORSPACEFILE, 0, NULL);
+ tape_ccw_cc(request->cpaddr + 2, NOP, 0, NULL);
+ tape_ccw_end(request->cpaddr + 3, CCW_CMD_TIC, 0, request->cpaddr);
+ /* execute it, MTRETEN rc gets ignored */
+ rc = tape_do_io_interruptible(device, request);
+ tape_free_request(request);
+ return tape_mtop(device, MTREW, 1);
+}
+
+/*
+ * MTERASE: erases the tape.
+ */
+int
+tape_std_mterase(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+
+ request = tape_alloc_request(6, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_DSE;
+ /* setup ccws */
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_cc(request->cpaddr + 1, REWIND, 0, NULL);
+ tape_ccw_cc(request->cpaddr + 2, ERASE_GAP, 0, NULL);
+ tape_ccw_cc(request->cpaddr + 3, DATA_SEC_ERASE, 0, NULL);
+ tape_ccw_cc(request->cpaddr + 4, REWIND, 0, NULL);
+ tape_ccw_end(request->cpaddr + 5, NOP, 0, NULL);
+
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * MTUNLOAD: Rewind the tape and unload it.
+ */
+int
+tape_std_mtunload(struct tape_device *device, int mt_count)
+{
+ return tape_mtop(device, MTOFFL, mt_count);
+}
+
+/*
+ * MTCOMPRESSION: used to enable compression.
+ * Sets the IDRC on/off.
+ */
+int
+tape_std_mtcompression(struct tape_device *device, int mt_count)
+{
+ struct tape_request *request;
+
+ if (mt_count < 0 || mt_count > 1) {
+ DBF_EXCEPTION(6, "xcom parm\n");
+ if (*device->modeset_byte & 0x08)
+ PRINT_INFO("(%s) Compression is currently on\n",
+ device->cdev->dev.bus_id);
+ else
+ PRINT_INFO("(%s) Compression is currently off\n",
+ device->cdev->dev.bus_id);
+ PRINT_INFO("Use 1 to switch compression on, 0 to "
+ "switch it off\n");
+ return -EINVAL;
+ }
+ request = tape_alloc_request(2, 0);
+ if (IS_ERR(request))
+ return PTR_ERR(request);
+ request->op = TO_NOP;
+ /* setup ccws */
+ *device->modeset_byte = (mt_count == 0) ? 0x00 : 0x08;
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
+ /* execute it */
+ return tape_do_io_free(device, request);
+}
+
+/*
+ * Read Block
+ */
+struct tape_request *
+tape_std_read_block(struct tape_device *device, size_t count)
+{
+ struct tape_request *request;
+
+ /*
+ * We have to alloc 4 ccws in order to be able to transform request
+ * into a read backward request in error case.
+ */
+ request = tape_alloc_request(4, 0);
+ if (IS_ERR(request)) {
+ DBF_EXCEPTION(6, "xrbl fail");
+ return request;
+ }
+ request->op = TO_RFO;
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_end_idal(request->cpaddr + 1, READ_FORWARD,
+ device->char_data.idal_buf);
+ DBF_EVENT(6, "xrbl ccwg\n");
+ return request;
+}
+
+/*
+ * Read Block backward transformation function.
+ */
+void
+tape_std_read_backward(struct tape_device *device, struct tape_request *request)
+{
+ /*
+ * We have allocated 4 ccws in tape_std_read, so we can now
+ * transform the request to a read backward, followed by a
+ * forward space block.
+ */
+ request->op = TO_RBA;
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_cc_idal(request->cpaddr + 1, READ_BACKWARD,
+ device->char_data.idal_buf);
+ tape_ccw_cc(request->cpaddr + 2, FORSPACEBLOCK, 0, NULL);
+ tape_ccw_end(request->cpaddr + 3, NOP, 0, NULL);
+ DBF_EVENT(6, "xrop ccwg");}
+
+/*
+ * Write Block
+ */
+struct tape_request *
+tape_std_write_block(struct tape_device *device, size_t count)
+{
+ struct tape_request *request;
+
+ request = tape_alloc_request(2, 0);
+ if (IS_ERR(request)) {
+ DBF_EXCEPTION(6, "xwbl fail\n");
+ return request;
+ }
+ request->op = TO_WRI;
+ tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
+ tape_ccw_end_idal(request->cpaddr + 1, WRITE_CMD,
+ device->char_data.idal_buf);
+ DBF_EVENT(6, "xwbl ccwg\n");
+ return request;
+}
+
+/*
+ * This routine is called by frontend after an ENOSP on write
+ */
+void
+tape_std_process_eov(struct tape_device *device)
+{
+ /*
+ * End of volume: We have to backspace the last written record, then
+ * we TRY to write a tapemark and then backspace over the written TM
+ */
+ if (tape_mtop(device, MTBSR, 1) == 0 &&
+ tape_mtop(device, MTWEOF, 1) == 0) {
+ tape_mtop(device, MTBSR, 1);
+ }
+}
+
+EXPORT_SYMBOL(tape_std_assign);
+EXPORT_SYMBOL(tape_std_unassign);
+EXPORT_SYMBOL(tape_std_display);
+EXPORT_SYMBOL(tape_std_read_block_id);
+EXPORT_SYMBOL(tape_std_mtload);
+EXPORT_SYMBOL(tape_std_mtsetblk);
+EXPORT_SYMBOL(tape_std_mtreset);
+EXPORT_SYMBOL(tape_std_mtfsf);
+EXPORT_SYMBOL(tape_std_mtfsr);
+EXPORT_SYMBOL(tape_std_mtbsr);
+EXPORT_SYMBOL(tape_std_mtweof);
+EXPORT_SYMBOL(tape_std_mtbsfm);
+EXPORT_SYMBOL(tape_std_mtbsf);
+EXPORT_SYMBOL(tape_std_mtfsfm);
+EXPORT_SYMBOL(tape_std_mtrew);
+EXPORT_SYMBOL(tape_std_mtoffl);
+EXPORT_SYMBOL(tape_std_mtnop);
+EXPORT_SYMBOL(tape_std_mteom);
+EXPORT_SYMBOL(tape_std_mtreten);
+EXPORT_SYMBOL(tape_std_mterase);
+EXPORT_SYMBOL(tape_std_mtunload);
+EXPORT_SYMBOL(tape_std_mtcompression);
+EXPORT_SYMBOL(tape_std_read_block);
+EXPORT_SYMBOL(tape_std_read_backward);
+EXPORT_SYMBOL(tape_std_write_block);
+EXPORT_SYMBOL(tape_std_process_eov);
diff --git a/drivers/s390/char/tape_std.h b/drivers/s390/char/tape_std.h
new file mode 100644
index 000000000000..3ab6aafb7343
--- /dev/null
+++ b/drivers/s390/char/tape_std.h
@@ -0,0 +1,152 @@
+/*
+ * drivers/s390/char/tape_34xx.h
+ * standard tape device functions for ibm tapes.
+ *
+ * S390 and zSeries version
+ * Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Carsten Otte <cotte@de.ibm.com>
+ * Tuan Ngo-Anh <ngoanh@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#ifndef _TAPE_STD_H
+#define _TAPE_STD_H
+
+#include <asm/tape390.h>
+
+/*
+ * Biggest block size to handle. Currently 64K because we only build
+ * channel programs without data chaining.
+ */
+#define MAX_BLOCKSIZE 65535
+
+/*
+ * The CCW commands for the Tape type of command.
+ */
+#define INVALID_00 0x00 /* Invalid cmd */
+#define BACKSPACEBLOCK 0x27 /* Back Space block */
+#define BACKSPACEFILE 0x2f /* Back Space file */
+#define DATA_SEC_ERASE 0x97 /* Data security erase */
+#define ERASE_GAP 0x17 /* Erase Gap */
+#define FORSPACEBLOCK 0x37 /* Forward space block */
+#define FORSPACEFILE 0x3F /* Forward Space file */
+#define FORCE_STREAM_CNT 0xEB /* Forced streaming count # */
+#define NOP 0x03 /* No operation */
+#define READ_FORWARD 0x02 /* Read forward */
+#define REWIND 0x07 /* Rewind */
+#define REWIND_UNLOAD 0x0F /* Rewind and Unload */
+#define SENSE 0x04 /* Sense */
+#define NEW_MODE_SET 0xEB /* Guess it is Mode set */
+#define WRITE_CMD 0x01 /* Write */
+#define WRITETAPEMARK 0x1F /* Write Tape Mark */
+
+#define ASSIGN 0xB7 /* 3420 REJECT,3480 OK */
+#define CONTROL_ACCESS 0xE3 /* Set high speed */
+#define DIAG_MODE_SET 0x0B /* 3420 NOP, 3480 REJECT */
+#define LOAD_DISPLAY 0x9F /* 3420 REJECT,3480 OK */
+#define LOCATE 0x4F /* 3420 REJ, 3480 NOP */
+#define LOOP_WRITE_TO_READ 0x8B /* 3480 REJECT */
+#define MODE_SET_DB 0xDB /* 3420 REJECT,3480 OK */
+#define MODE_SET_C3 0xC3 /* for 3420 */
+#define MODE_SET_CB 0xCB /* for 3420 */
+#define MODE_SET_D3 0xD3 /* for 3420 */
+#define READ_BACKWARD 0x0C /* */
+#define READ_BLOCK_ID 0x22 /* 3420 REJECT,3480 OK */
+#define READ_BUFFER 0x12 /* 3420 REJECT,3480 OK */
+#define READ_BUFF_LOG 0x24 /* 3420 REJECT,3480 OK */
+#define RELEASE 0xD4 /* 3420 NOP, 3480 REJECT */
+#define REQ_TRK_IN_ERROR 0x1B /* 3420 NOP, 3480 REJECT */
+#define RESERVE 0xF4 /* 3420 NOP, 3480 REJECT */
+#define SENSE_GROUP_ID 0x34 /* 3420 REJECT,3480 OK */
+#define SENSE_ID 0xE4 /* 3420 REJECT,3480 OK */
+#define READ_DEV_CHAR 0x64 /* Read device characteristics */
+#define SET_DIAGNOSE 0x4B /* 3420 NOP, 3480 REJECT */
+#define SET_GROUP_ID 0xAF /* 3420 REJECT,3480 OK */
+#define SET_TAPE_WRITE_IMMED 0xC3 /* for 3480 */
+#define SUSPEND 0x5B /* 3420 REJ, 3480 NOP */
+#define SYNC 0x43 /* Synchronize (flush buffer) */
+#define UNASSIGN 0xC7 /* 3420 REJECT,3480 OK */
+#define PERF_SUBSYS_FUNC 0x77 /* 3490 CMD */
+#define READ_CONFIG_DATA 0xFA /* 3490 CMD */
+#define READ_MESSAGE_ID 0x4E /* 3490 CMD */
+#define READ_SUBSYS_DATA 0x3E /* 3490 CMD */
+#define SET_INTERFACE_ID 0x73 /* 3490 CMD */
+
+#define SENSE_COMMAND_REJECT 0x80
+#define SENSE_INTERVENTION_REQUIRED 0x40
+#define SENSE_BUS_OUT_CHECK 0x20
+#define SENSE_EQUIPMENT_CHECK 0x10
+#define SENSE_DATA_CHECK 0x08
+#define SENSE_OVERRUN 0x04
+#define SENSE_DEFERRED_UNIT_CHECK 0x02
+#define SENSE_ASSIGNED_ELSEWHERE 0x01
+
+#define SENSE_LOCATE_FAILURE 0x80
+#define SENSE_DRIVE_ONLINE 0x40
+#define SENSE_RESERVED 0x20
+#define SENSE_RECORD_SEQUENCE_ERR 0x10
+#define SENSE_BEGINNING_OF_TAPE 0x08
+#define SENSE_WRITE_MODE 0x04
+#define SENSE_WRITE_PROTECT 0x02
+#define SENSE_NOT_CAPABLE 0x01
+
+#define SENSE_CHANNEL_ADAPTER_CODE 0xE0
+#define SENSE_CHANNEL_ADAPTER_LOC 0x10
+#define SENSE_REPORTING_CU 0x08
+#define SENSE_AUTOMATIC_LOADER 0x04
+#define SENSE_TAPE_SYNC_MODE 0x02
+#define SENSE_TAPE_POSITIONING 0x01
+
+/* discipline functions */
+struct tape_request *tape_std_read_block(struct tape_device *, size_t);
+void tape_std_read_backward(struct tape_device *device,
+ struct tape_request *request);
+struct tape_request *tape_std_write_block(struct tape_device *, size_t);
+struct tape_request *tape_std_bread(struct tape_device *, struct request *);
+void tape_std_free_bread(struct tape_request *);
+void tape_std_check_locate(struct tape_device *, struct tape_request *);
+struct tape_request *tape_std_bwrite(struct request *,
+ struct tape_device *, int);
+
+/* Some non-mtop commands. */
+int tape_std_assign(struct tape_device *);
+int tape_std_unassign(struct tape_device *);
+int tape_std_read_block_id(struct tape_device *device, __u64 *id);
+int tape_std_display(struct tape_device *, struct display_struct *disp);
+int tape_std_terminate_write(struct tape_device *);
+
+/* Standard magnetic tape commands. */
+int tape_std_mtbsf(struct tape_device *, int);
+int tape_std_mtbsfm(struct tape_device *, int);
+int tape_std_mtbsr(struct tape_device *, int);
+int tape_std_mtcompression(struct tape_device *, int);
+int tape_std_mteom(struct tape_device *, int);
+int tape_std_mterase(struct tape_device *, int);
+int tape_std_mtfsf(struct tape_device *, int);
+int tape_std_mtfsfm(struct tape_device *, int);
+int tape_std_mtfsr(struct tape_device *, int);
+int tape_std_mtload(struct tape_device *, int);
+int tape_std_mtnop(struct tape_device *, int);
+int tape_std_mtoffl(struct tape_device *, int);
+int tape_std_mtreset(struct tape_device *, int);
+int tape_std_mtreten(struct tape_device *, int);
+int tape_std_mtrew(struct tape_device *, int);
+int tape_std_mtsetblk(struct tape_device *, int);
+int tape_std_mtunload(struct tape_device *, int);
+int tape_std_mtweof(struct tape_device *, int);
+
+/* Event handlers */
+void tape_std_default_handler(struct tape_device *);
+void tape_std_unexpect_uchk_handler(struct tape_device *);
+void tape_std_irq(struct tape_device *);
+void tape_std_process_eov(struct tape_device *);
+
+// the error recovery stuff:
+void tape_std_error_recovery(struct tape_device *);
+void tape_std_error_recovery_has_failed(struct tape_device *,int error_id);
+void tape_std_error_recovery_succeded(struct tape_device *);
+void tape_std_error_recovery_do_retry(struct tape_device *);
+void tape_std_error_recovery_read_opposite(struct tape_device *);
+void tape_std_error_recovery_HWBUG(struct tape_device *, int condno);
+
+#endif // _TAPE_STD_H
diff --git a/drivers/s390/char/tty3270.c b/drivers/s390/char/tty3270.c
new file mode 100644
index 000000000000..7db5ebce7f0f
--- /dev/null
+++ b/drivers/s390/char/tty3270.c
@@ -0,0 +1,1836 @@
+/*
+ * drivers/s390/char/tty3270.c
+ * IBM/3270 Driver - tty functions.
+ *
+ * Author(s):
+ * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
+ * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kdev_t.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/interrupt.h>
+
+#include <linux/slab.h>
+#include <linux/bootmem.h>
+
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+#include <asm/ebcdic.h>
+#include <asm/uaccess.h>
+
+
+#include "raw3270.h"
+#include "keyboard.h"
+
+#define TTY3270_CHAR_BUF_SIZE 256
+#define TTY3270_OUTPUT_BUFFER_SIZE 1024
+#define TTY3270_STRING_PAGES 5
+
+struct tty_driver *tty3270_driver;
+static int tty3270_max_index;
+
+struct raw3270_fn tty3270_fn;
+
+struct tty3270_cell {
+ unsigned char character;
+ unsigned char highlight;
+ unsigned char f_color;
+};
+
+struct tty3270_line {
+ struct tty3270_cell *cells;
+ int len;
+};
+
+#define ESCAPE_NPAR 8
+
+/*
+ * The main tty view data structure.
+ * FIXME:
+ * 1) describe line orientation & lines list concept against screen
+ * 2) describe conversion of screen to lines
+ * 3) describe line format.
+ */
+struct tty3270 {
+ struct raw3270_view view;
+ struct tty_struct *tty; /* Pointer to tty structure */
+ void **freemem_pages; /* Array of pages used for freemem. */
+ struct list_head freemem; /* List of free memory for strings. */
+
+ /* Output stuff. */
+ struct list_head lines; /* List of lines. */
+ struct list_head update; /* List of lines to update. */
+ unsigned char wcc; /* Write control character. */
+ int nr_lines; /* # lines in list. */
+ int nr_up; /* # lines up in history. */
+ unsigned long update_flags; /* Update indication bits. */
+ struct string *status; /* Lower right of display. */
+ struct raw3270_request *write; /* Single write request. */
+ struct timer_list timer; /* Output delay timer. */
+
+ /* Current tty screen. */
+ unsigned int cx, cy; /* Current output position. */
+ unsigned int highlight; /* Blink/reverse/underscore */
+ unsigned int f_color; /* Foreground color */
+ struct tty3270_line *screen;
+
+ /* Input stuff. */
+ struct string *prompt; /* Output string for input area. */
+ struct string *input; /* Input string for read request. */
+ struct raw3270_request *read; /* Single read request. */
+ struct raw3270_request *kreset; /* Single keyboard reset request. */
+ unsigned char inattr; /* Visible/invisible input. */
+ int throttle, attn; /* tty throttle/unthrottle. */
+ struct tasklet_struct readlet; /* Tasklet to issue read request. */
+ struct kbd_data *kbd; /* key_maps stuff. */
+
+ /* Escape sequence parsing. */
+ int esc_state, esc_ques, esc_npar;
+ int esc_par[ESCAPE_NPAR];
+ unsigned int saved_cx, saved_cy;
+ unsigned int saved_highlight, saved_f_color;
+
+ /* Command recalling. */
+ struct list_head rcl_lines; /* List of recallable lines. */
+ struct list_head *rcl_walk; /* Point in rcl_lines list. */
+ int rcl_nr, rcl_max; /* Number/max number of rcl_lines. */
+
+ /* Character array for put_char/flush_chars. */
+ unsigned int char_count;
+ char char_buf[TTY3270_CHAR_BUF_SIZE];
+};
+
+/* tty3270->update_flags. See tty3270_update for details. */
+#define TTY_UPDATE_ERASE 1 /* Use EWRITEA instead of WRITE. */
+#define TTY_UPDATE_LIST 2 /* Update lines in tty3270->update. */
+#define TTY_UPDATE_INPUT 4 /* Update input line. */
+#define TTY_UPDATE_STATUS 8 /* Update status line. */
+#define TTY_UPDATE_ALL 15
+
+static void tty3270_update(struct tty3270 *);
+
+/*
+ * Setup timeout for a device. On timeout trigger an update.
+ */
+void
+tty3270_set_timer(struct tty3270 *tp, int expires)
+{
+ if (expires == 0) {
+ if (timer_pending(&tp->timer) && del_timer(&tp->timer))
+ raw3270_put_view(&tp->view);
+ return;
+ }
+ if (timer_pending(&tp->timer) &&
+ mod_timer(&tp->timer, jiffies + expires))
+ return;
+ raw3270_get_view(&tp->view);
+ tp->timer.function = (void (*)(unsigned long)) tty3270_update;
+ tp->timer.data = (unsigned long) tp;
+ tp->timer.expires = jiffies + expires;
+ add_timer(&tp->timer);
+}
+
+/*
+ * The input line are the two last lines of the screen.
+ */
+static void
+tty3270_update_prompt(struct tty3270 *tp, char *input, int count)
+{
+ struct string *line;
+ unsigned int off;
+
+ line = tp->prompt;
+ if (count != 0)
+ line->string[5] = TF_INMDT;
+ else
+ line->string[5] = tp->inattr;
+ if (count > tp->view.cols * 2 - 11)
+ count = tp->view.cols * 2 - 11;
+ memcpy(line->string + 6, input, count);
+ line->string[6 + count] = TO_IC;
+ /* Clear to end of input line. */
+ if (count < tp->view.cols * 2 - 11) {
+ line->string[7 + count] = TO_RA;
+ line->string[10 + count] = 0;
+ off = tp->view.cols * tp->view.rows - 9;
+ raw3270_buffer_address(tp->view.dev, line->string+count+8, off);
+ line->len = 11 + count;
+ } else
+ line->len = 7 + count;
+ tp->update_flags |= TTY_UPDATE_INPUT;
+}
+
+static void
+tty3270_create_prompt(struct tty3270 *tp)
+{
+ static const unsigned char blueprint[] =
+ { TO_SBA, 0, 0, 0x6e, TO_SF, TF_INPUT,
+ /* empty input string */
+ TO_IC, TO_RA, 0, 0, 0 };
+ struct string *line;
+ unsigned int offset;
+
+ line = alloc_string(&tp->freemem,
+ sizeof(blueprint) + tp->view.cols * 2 - 9);
+ tp->prompt = line;
+ tp->inattr = TF_INPUT;
+ /* Copy blueprint to status line */
+ memcpy(line->string, blueprint, sizeof(blueprint));
+ line->len = sizeof(blueprint);
+ /* Set output offsets. */
+ offset = tp->view.cols * (tp->view.rows - 2);
+ raw3270_buffer_address(tp->view.dev, line->string + 1, offset);
+ offset = tp->view.cols * tp->view.rows - 9;
+ raw3270_buffer_address(tp->view.dev, line->string + 8, offset);
+
+ /* Allocate input string for reading. */
+ tp->input = alloc_string(&tp->freemem, tp->view.cols * 2 - 9 + 6);
+}
+
+/*
+ * The status line is the last line of the screen. It shows the string
+ * "Running"/"Holding" in the lower right corner of the screen.
+ */
+static void
+tty3270_update_status(struct tty3270 * tp)
+{
+ char *str;
+
+ str = (tp->nr_up != 0) ? "History" : "Running";
+ memcpy(tp->status->string + 8, str, 7);
+ codepage_convert(tp->view.ascebc, tp->status->string + 8, 7);
+ tp->update_flags |= TTY_UPDATE_STATUS;
+}
+
+static void
+tty3270_create_status(struct tty3270 * tp)
+{
+ static const unsigned char blueprint[] =
+ { TO_SBA, 0, 0, TO_SF, TF_LOG, TO_SA, TAT_COLOR, TAC_GREEN,
+ 0, 0, 0, 0, 0, 0, 0, TO_SF, TF_LOG, TO_SA, TAT_COLOR,
+ TAC_RESET };
+ struct string *line;
+ unsigned int offset;
+
+ line = alloc_string(&tp->freemem,sizeof(blueprint));
+ tp->status = line;
+ /* Copy blueprint to status line */
+ memcpy(line->string, blueprint, sizeof(blueprint));
+ /* Set address to start of status string (= last 9 characters). */
+ offset = tp->view.cols * tp->view.rows - 9;
+ raw3270_buffer_address(tp->view.dev, line->string + 1, offset);
+}
+
+/*
+ * Set output offsets to 3270 datastream fragment of a tty string.
+ * (TO_SBA offset at the start and TO_RA offset at the end of the string)
+ */
+static void
+tty3270_update_string(struct tty3270 *tp, struct string *line, int nr)
+{
+ unsigned char *cp;
+
+ raw3270_buffer_address(tp->view.dev, line->string + 1,
+ tp->view.cols * nr);
+ cp = line->string + line->len - 4;
+ if (*cp == TO_RA)
+ raw3270_buffer_address(tp->view.dev, cp + 1,
+ tp->view.cols * (nr + 1));
+}
+
+/*
+ * Rebuild update list to print all lines.
+ */
+static void
+tty3270_rebuild_update(struct tty3270 *tp)
+{
+ struct string *s, *n;
+ int line, nr_up;
+
+ /*
+ * Throw away update list and create a new one,
+ * containing all lines that will fit on the screen.
+ */
+ list_for_each_entry_safe(s, n, &tp->update, update)
+ list_del_init(&s->update);
+ line = tp->view.rows - 3;
+ nr_up = tp->nr_up;
+ list_for_each_entry_reverse(s, &tp->lines, list) {
+ if (nr_up > 0) {
+ nr_up--;
+ continue;
+ }
+ tty3270_update_string(tp, s, line);
+ list_add(&s->update, &tp->update);
+ if (--line < 0)
+ break;
+ }
+ tp->update_flags |= TTY_UPDATE_LIST;
+}
+
+/*
+ * Alloc string for size bytes. If there is not enough room in
+ * freemem, free strings until there is room.
+ */
+static struct string *
+tty3270_alloc_string(struct tty3270 *tp, size_t size)
+{
+ struct string *s, *n;
+
+ s = alloc_string(&tp->freemem, size);
+ if (s)
+ return s;
+ list_for_each_entry_safe(s, n, &tp->lines, list) {
+ BUG_ON(tp->nr_lines <= tp->view.rows - 2);
+ list_del(&s->list);
+ if (!list_empty(&s->update))
+ list_del(&s->update);
+ tp->nr_lines--;
+ if (free_string(&tp->freemem, s) >= size)
+ break;
+ }
+ s = alloc_string(&tp->freemem, size);
+ BUG_ON(!s);
+ if (tp->nr_up != 0 &&
+ tp->nr_up + tp->view.rows - 2 >= tp->nr_lines) {
+ tp->nr_up = tp->nr_lines - tp->view.rows + 2;
+ tty3270_rebuild_update(tp);
+ tty3270_update_status(tp);
+ }
+ return s;
+}
+
+/*
+ * Add an empty line to the list.
+ */
+static void
+tty3270_blank_line(struct tty3270 *tp)
+{
+ static const unsigned char blueprint[] =
+ { TO_SBA, 0, 0, TO_SA, TAT_EXTHI, TAX_RESET,
+ TO_SA, TAT_COLOR, TAC_RESET, TO_RA, 0, 0, 0 };
+ struct string *s;
+
+ s = tty3270_alloc_string(tp, sizeof(blueprint));
+ memcpy(s->string, blueprint, sizeof(blueprint));
+ s->len = sizeof(blueprint);
+ list_add_tail(&s->list, &tp->lines);
+ tp->nr_lines++;
+ if (tp->nr_up != 0)
+ tp->nr_up++;
+}
+
+/*
+ * Write request completion callback.
+ */
+static void
+tty3270_write_callback(struct raw3270_request *rq, void *data)
+{
+ struct tty3270 *tp;
+
+ tp = (struct tty3270 *) rq->view;
+ if (rq->rc != 0) {
+ /* Write wasn't successfull. Refresh all. */
+ tty3270_rebuild_update(tp);
+ tp->update_flags = TTY_UPDATE_ALL;
+ tty3270_set_timer(tp, 1);
+ }
+ raw3270_request_reset(rq);
+ xchg(&tp->write, rq);
+}
+
+/*
+ * Update 3270 display.
+ */
+static void
+tty3270_update(struct tty3270 *tp)
+{
+ static char invalid_sba[2] = { 0xff, 0xff };
+ struct raw3270_request *wrq;
+ unsigned long updated;
+ struct string *s, *n;
+ char *sba, *str;
+ int rc, len;
+
+ wrq = xchg(&tp->write, 0);
+ if (!wrq) {
+ tty3270_set_timer(tp, 1);
+ return;
+ }
+
+ spin_lock(&tp->view.lock);
+ updated = 0;
+ if (tp->update_flags & TTY_UPDATE_ERASE) {
+ /* Use erase write alternate to erase display. */
+ raw3270_request_set_cmd(wrq, TC_EWRITEA);
+ updated |= TTY_UPDATE_ERASE;
+ } else
+ raw3270_request_set_cmd(wrq, TC_WRITE);
+
+ raw3270_request_add_data(wrq, &tp->wcc, 1);
+ tp->wcc = TW_NONE;
+
+ /*
+ * Update status line.
+ */
+ if (tp->update_flags & TTY_UPDATE_STATUS)
+ if (raw3270_request_add_data(wrq, tp->status->string,
+ tp->status->len) == 0)
+ updated |= TTY_UPDATE_STATUS;
+
+ /*
+ * Write input line.
+ */
+ if (tp->update_flags & TTY_UPDATE_INPUT)
+ if (raw3270_request_add_data(wrq, tp->prompt->string,
+ tp->prompt->len) == 0)
+ updated |= TTY_UPDATE_INPUT;
+
+ sba = invalid_sba;
+
+ if (tp->update_flags & TTY_UPDATE_LIST) {
+ /* Write strings in the update list to the screen. */
+ list_for_each_entry_safe(s, n, &tp->update, update) {
+ str = s->string;
+ len = s->len;
+ /*
+ * Skip TO_SBA at the start of the string if the
+ * last output position matches the start address
+ * of this line.
+ */
+ if (s->string[1] == sba[0] && s->string[2] == sba[1])
+ str += 3, len -= 3;
+ if (raw3270_request_add_data(wrq, str, len) != 0)
+ break;
+ list_del_init(&s->update);
+ sba = s->string + s->len - 3;
+ }
+ if (list_empty(&tp->update))
+ updated |= TTY_UPDATE_LIST;
+ }
+ wrq->callback = tty3270_write_callback;
+ rc = raw3270_start(&tp->view, wrq);
+ if (rc == 0) {
+ tp->update_flags &= ~updated;
+ if (tp->update_flags)
+ tty3270_set_timer(tp, 1);
+ } else {
+ raw3270_request_reset(wrq);
+ xchg(&tp->write, wrq);
+ }
+ spin_unlock(&tp->view.lock);
+ raw3270_put_view(&tp->view);
+}
+
+/*
+ * Command recalling.
+ */
+static void
+tty3270_rcl_add(struct tty3270 *tp, char *input, int len)
+{
+ struct string *s;
+
+ tp->rcl_walk = 0;
+ if (len <= 0)
+ return;
+ if (tp->rcl_nr >= tp->rcl_max) {
+ s = list_entry(tp->rcl_lines.next, struct string, list);
+ list_del(&s->list);
+ free_string(&tp->freemem, s);
+ tp->rcl_nr--;
+ }
+ s = tty3270_alloc_string(tp, len);
+ memcpy(s->string, input, len);
+ list_add_tail(&s->list, &tp->rcl_lines);
+ tp->rcl_nr++;
+}
+
+static void
+tty3270_rcl_backward(struct kbd_data *kbd)
+{
+ struct tty3270 *tp;
+ struct string *s;
+
+ tp = kbd->tty->driver_data;
+ spin_lock_bh(&tp->view.lock);
+ if (tp->inattr == TF_INPUT) {
+ if (tp->rcl_walk && tp->rcl_walk->prev != &tp->rcl_lines)
+ tp->rcl_walk = tp->rcl_walk->prev;
+ else if (!list_empty(&tp->rcl_lines))
+ tp->rcl_walk = tp->rcl_lines.prev;
+ s = tp->rcl_walk ?
+ list_entry(tp->rcl_walk, struct string, list) : 0;
+ if (tp->rcl_walk) {
+ s = list_entry(tp->rcl_walk, struct string, list);
+ tty3270_update_prompt(tp, s->string, s->len);
+ } else
+ tty3270_update_prompt(tp, 0, 0);
+ tty3270_set_timer(tp, 1);
+ }
+ spin_unlock_bh(&tp->view.lock);
+}
+
+/*
+ * Deactivate tty view.
+ */
+static void
+tty3270_exit_tty(struct kbd_data *kbd)
+{
+ struct tty3270 *tp;
+
+ tp = kbd->tty->driver_data;
+ raw3270_deactivate_view(&tp->view);
+}
+
+/*
+ * Scroll forward in history.
+ */
+static void
+tty3270_scroll_forward(struct kbd_data *kbd)
+{
+ struct tty3270 *tp;
+ int nr_up;
+
+ tp = kbd->tty->driver_data;
+ spin_lock_bh(&tp->view.lock);
+ nr_up = tp->nr_up - tp->view.rows + 2;
+ if (nr_up < 0)
+ nr_up = 0;
+ if (nr_up != tp->nr_up) {
+ tp->nr_up = nr_up;
+ tty3270_rebuild_update(tp);
+ tty3270_update_status(tp);
+ tty3270_set_timer(tp, 1);
+ }
+ spin_unlock_bh(&tp->view.lock);
+}
+
+/*
+ * Scroll backward in history.
+ */
+static void
+tty3270_scroll_backward(struct kbd_data *kbd)
+{
+ struct tty3270 *tp;
+ int nr_up;
+
+ tp = kbd->tty->driver_data;
+ spin_lock_bh(&tp->view.lock);
+ nr_up = tp->nr_up + tp->view.rows - 2;
+ if (nr_up + tp->view.rows - 2 > tp->nr_lines)
+ nr_up = tp->nr_lines - tp->view.rows + 2;
+ if (nr_up != tp->nr_up) {
+ tp->nr_up = nr_up;
+ tty3270_rebuild_update(tp);
+ tty3270_update_status(tp);
+ tty3270_set_timer(tp, 1);
+ }
+ spin_unlock_bh(&tp->view.lock);
+}
+
+/*
+ * Pass input line to tty.
+ */
+static void
+tty3270_read_tasklet(struct raw3270_request *rrq)
+{
+ static char kreset_data = TW_KR;
+ struct tty3270 *tp;
+ char *input;
+ int len;
+
+ tp = (struct tty3270 *) rrq->view;
+ spin_lock_bh(&tp->view.lock);
+ /*
+ * Two AID keys are special: For 0x7d (enter) the input line
+ * has to be emitted to the tty and for 0x6d the screen
+ * needs to be redrawn.
+ */
+ input = 0;
+ len = 0;
+ if (tp->input->string[0] == 0x7d) {
+ /* Enter: write input to tty. */
+ input = tp->input->string + 6;
+ len = tp->input->len - 6 - rrq->rescnt;
+ if (tp->inattr != TF_INPUTN)
+ tty3270_rcl_add(tp, input, len);
+ if (tp->nr_up > 0) {
+ tp->nr_up = 0;
+ tty3270_rebuild_update(tp);
+ tty3270_update_status(tp);
+ }
+ /* Clear input area. */
+ tty3270_update_prompt(tp, 0, 0);
+ tty3270_set_timer(tp, 1);
+ } else if (tp->input->string[0] == 0x6d) {
+ /* Display has been cleared. Redraw. */
+ tty3270_rebuild_update(tp);
+ tp->update_flags = TTY_UPDATE_ALL;
+ tty3270_set_timer(tp, 1);
+ }
+ spin_unlock_bh(&tp->view.lock);
+
+ /* Start keyboard reset command. */
+ raw3270_request_reset(tp->kreset);
+ raw3270_request_set_cmd(tp->kreset, TC_WRITE);
+ raw3270_request_add_data(tp->kreset, &kreset_data, 1);
+ raw3270_start(&tp->view, tp->kreset);
+
+ /* Emit input string. */
+ if (tp->tty) {
+ while (len-- > 0)
+ kbd_keycode(tp->kbd, *input++);
+ /* Emit keycode for AID byte. */
+ kbd_keycode(tp->kbd, 256 + tp->input->string[0]);
+ }
+
+ raw3270_request_reset(rrq);
+ xchg(&tp->read, rrq);
+ raw3270_put_view(&tp->view);
+}
+
+/*
+ * Read request completion callback.
+ */
+static void
+tty3270_read_callback(struct raw3270_request *rq, void *data)
+{
+ raw3270_get_view(rq->view);
+ /* Schedule tasklet to pass input to tty. */
+ tasklet_schedule(&((struct tty3270 *) rq->view)->readlet);
+}
+
+/*
+ * Issue a read request. Call with device lock.
+ */
+static void
+tty3270_issue_read(struct tty3270 *tp, int lock)
+{
+ struct raw3270_request *rrq;
+ int rc;
+
+ rrq = xchg(&tp->read, 0);
+ if (!rrq)
+ /* Read already scheduled. */
+ return;
+ rrq->callback = tty3270_read_callback;
+ rrq->callback_data = tp;
+ raw3270_request_set_cmd(rrq, TC_READMOD);
+ raw3270_request_set_data(rrq, tp->input->string, tp->input->len);
+ /* Issue the read modified request. */
+ if (lock) {
+ rc = raw3270_start(&tp->view, rrq);
+ } else
+ rc = raw3270_start_irq(&tp->view, rrq);
+ if (rc) {
+ raw3270_request_reset(rrq);
+ xchg(&tp->read, rrq);
+ }
+}
+
+/*
+ * Switch to the tty view.
+ */
+static int
+tty3270_activate(struct raw3270_view *view)
+{
+ struct tty3270 *tp;
+ unsigned long flags;
+
+ tp = (struct tty3270 *) view;
+ spin_lock_irqsave(&tp->view.lock, flags);
+ tp->nr_up = 0;
+ tty3270_rebuild_update(tp);
+ tty3270_update_status(tp);
+ tp->update_flags = TTY_UPDATE_ALL;
+ tty3270_set_timer(tp, 1);
+ spin_unlock_irqrestore(&tp->view.lock, flags);
+ start_tty(tp->tty);
+ return 0;
+}
+
+static void
+tty3270_deactivate(struct raw3270_view *view)
+{
+ struct tty3270 *tp;
+
+ tp = (struct tty3270 *) view;
+ if (tp && tp->tty)
+ stop_tty(tp->tty);
+}
+
+static int
+tty3270_irq(struct tty3270 *tp, struct raw3270_request *rq, struct irb *irb)
+{
+ /* Handle ATTN. Schedule tasklet to read aid. */
+ if (irb->scsw.dstat & DEV_STAT_ATTENTION) {
+ if (!tp->throttle)
+ tty3270_issue_read(tp, 0);
+ else
+ tp->attn = 1;
+ }
+
+ if (rq) {
+ if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
+ rq->rc = -EIO;
+ else
+ /* Normal end. Copy residual count. */
+ rq->rescnt = irb->scsw.count;
+ }
+ return RAW3270_IO_DONE;
+}
+
+/*
+ * Allocate tty3270 structure.
+ */
+static struct tty3270 *
+tty3270_alloc_view(void)
+{
+ struct tty3270 *tp;
+ int pages;
+
+ tp = kmalloc(sizeof(struct tty3270),GFP_KERNEL);
+ if (!tp)
+ goto out_err;
+ memset(tp, 0, sizeof(struct tty3270));
+ tp->freemem_pages =
+ kmalloc(sizeof(void *) * TTY3270_STRING_PAGES, GFP_KERNEL);
+ if (!tp->freemem_pages)
+ goto out_tp;
+ INIT_LIST_HEAD(&tp->freemem);
+ init_timer(&tp->timer);
+ for (pages = 0; pages < TTY3270_STRING_PAGES; pages++) {
+ tp->freemem_pages[pages] = (void *)
+ __get_free_pages(GFP_KERNEL|GFP_DMA, 0);
+ if (!tp->freemem_pages[pages])
+ goto out_pages;
+ add_string_memory(&tp->freemem,
+ tp->freemem_pages[pages], PAGE_SIZE);
+ }
+ tp->write = raw3270_request_alloc(TTY3270_OUTPUT_BUFFER_SIZE);
+ if (!tp->write)
+ goto out_pages;
+ tp->read = raw3270_request_alloc(0);
+ if (!tp->read)
+ goto out_write;
+ tp->kreset = raw3270_request_alloc(1);
+ if (!tp->kreset)
+ goto out_read;
+ tp->kbd = kbd_alloc();
+ if (!tp->kbd)
+ goto out_reset;
+ return tp;
+
+out_reset:
+ raw3270_request_free(tp->kreset);
+out_read:
+ raw3270_request_free(tp->read);
+out_write:
+ raw3270_request_free(tp->write);
+out_pages:
+ while (pages--)
+ free_pages((unsigned long) tp->freemem_pages[pages], 0);
+ kfree(tp->freemem_pages);
+out_tp:
+ kfree(tp);
+out_err:
+ return ERR_PTR(-ENOMEM);
+}
+
+/*
+ * Free tty3270 structure.
+ */
+static void
+tty3270_free_view(struct tty3270 *tp)
+{
+ int pages;
+
+ kbd_free(tp->kbd);
+ raw3270_request_free(tp->kreset);
+ raw3270_request_free(tp->read);
+ raw3270_request_free(tp->write);
+ for (pages = 0; pages < TTY3270_STRING_PAGES; pages++)
+ free_pages((unsigned long) tp->freemem_pages[pages], 0);
+ kfree(tp->freemem_pages);
+ kfree(tp);
+}
+
+/*
+ * Allocate tty3270 screen.
+ */
+static int
+tty3270_alloc_screen(struct tty3270 *tp)
+{
+ unsigned long size;
+ int lines;
+
+ size = sizeof(struct tty3270_line) * (tp->view.rows - 2);
+ tp->screen = kmalloc(size, GFP_KERNEL);
+ if (!tp->screen)
+ goto out_err;
+ memset(tp->screen, 0, size);
+ for (lines = 0; lines < tp->view.rows - 2; lines++) {
+ size = sizeof(struct tty3270_cell) * tp->view.cols;
+ tp->screen[lines].cells = kmalloc(size, GFP_KERNEL);
+ if (!tp->screen[lines].cells)
+ goto out_screen;
+ memset(tp->screen[lines].cells, 0, size);
+ }
+ return 0;
+out_screen:
+ while (lines--)
+ kfree(tp->screen[lines].cells);
+ kfree(tp->screen);
+out_err:
+ return -ENOMEM;
+}
+
+/*
+ * Free tty3270 screen.
+ */
+static void
+tty3270_free_screen(struct tty3270 *tp)
+{
+ int lines;
+
+ for (lines = 0; lines < tp->view.rows - 2; lines++)
+ kfree(tp->screen[lines].cells);
+ kfree(tp->screen);
+}
+
+/*
+ * Unlink tty3270 data structure from tty.
+ */
+static void
+tty3270_release(struct raw3270_view *view)
+{
+ struct tty3270 *tp;
+ struct tty_struct *tty;
+
+ tp = (struct tty3270 *) view;
+ tty = tp->tty;
+ if (tty) {
+ tty->driver_data = 0;
+ tp->tty = tp->kbd->tty = 0;
+ tty_hangup(tty);
+ raw3270_put_view(&tp->view);
+ }
+}
+
+/*
+ * Free tty3270 data structure
+ */
+static void
+tty3270_free(struct raw3270_view *view)
+{
+ tty3270_free_screen((struct tty3270 *) view);
+ tty3270_free_view((struct tty3270 *) view);
+}
+
+/*
+ * Delayed freeing of tty3270 views.
+ */
+static void
+tty3270_del_views(void)
+{
+ struct tty3270 *tp;
+ int i;
+
+ for (i = 0; i < tty3270_max_index; i++) {
+ tp = (struct tty3270 *) raw3270_find_view(&tty3270_fn, i);
+ if (!IS_ERR(tp))
+ raw3270_del_view(&tp->view);
+ }
+}
+
+struct raw3270_fn tty3270_fn = {
+ .activate = tty3270_activate,
+ .deactivate = tty3270_deactivate,
+ .intv = (void *) tty3270_irq,
+ .release = tty3270_release,
+ .free = tty3270_free
+};
+
+/*
+ * This routine is called whenever a 3270 tty is opened.
+ */
+static int
+tty3270_open(struct tty_struct *tty, struct file * filp)
+{
+ struct tty3270 *tp;
+ int i, rc;
+
+ if (tty->count > 1)
+ return 0;
+ /* Check if the tty3270 is already there. */
+ tp = (struct tty3270 *) raw3270_find_view(&tty3270_fn, tty->index);
+ if (!IS_ERR(tp)) {
+ tty->driver_data = tp;
+ tty->winsize.ws_row = tp->view.rows - 2;
+ tty->winsize.ws_col = tp->view.cols;
+ tty->low_latency = 0;
+ tp->tty = tty;
+ tp->kbd->tty = tty;
+ tp->inattr = TF_INPUT;
+ return 0;
+ }
+ if (tty3270_max_index < tty->index + 1)
+ tty3270_max_index = tty->index + 1;
+
+ /* Quick exit if there is no device for tty->index. */
+ if (PTR_ERR(tp) == -ENODEV)
+ return -ENODEV;
+
+ /* Allocate tty3270 structure on first open. */
+ tp = tty3270_alloc_view();
+ if (IS_ERR(tp))
+ return PTR_ERR(tp);
+
+ INIT_LIST_HEAD(&tp->lines);
+ INIT_LIST_HEAD(&tp->update);
+ INIT_LIST_HEAD(&tp->rcl_lines);
+ tp->rcl_max = 20;
+ init_timer(&tp->timer);
+ tasklet_init(&tp->readlet,
+ (void (*)(unsigned long)) tty3270_read_tasklet,
+ (unsigned long) tp->read);
+
+ rc = raw3270_add_view(&tp->view, &tty3270_fn, tty->index);
+ if (rc) {
+ tty3270_free_view(tp);
+ return rc;
+ }
+
+ rc = tty3270_alloc_screen(tp);
+ if (rc) {
+ raw3270_del_view(&tp->view);
+ raw3270_put_view(&tp->view);
+ return rc;
+ }
+
+ tp->tty = tty;
+ tty->low_latency = 0;
+ tty->driver_data = tp;
+ tty->winsize.ws_row = tp->view.rows - 2;
+ tty->winsize.ws_col = tp->view.cols;
+
+ tty3270_create_prompt(tp);
+ tty3270_create_status(tp);
+ tty3270_update_status(tp);
+
+ /* Create blank line for every line in the tty output area. */
+ for (i = 0; i < tp->view.rows - 2; i++)
+ tty3270_blank_line(tp);
+
+ tp->kbd->tty = tty;
+ tp->kbd->fn_handler[KVAL(K_INCRCONSOLE)] = tty3270_exit_tty;
+ tp->kbd->fn_handler[KVAL(K_SCROLLBACK)] = tty3270_scroll_backward;
+ tp->kbd->fn_handler[KVAL(K_SCROLLFORW)] = tty3270_scroll_forward;
+ tp->kbd->fn_handler[KVAL(K_CONS)] = tty3270_rcl_backward;
+ kbd_ascebc(tp->kbd, tp->view.ascebc);
+
+ raw3270_activate_view(&tp->view);
+ return 0;
+}
+
+/*
+ * This routine is called when the 3270 tty is closed. We wait
+ * for the remaining request to be completed. Then we clean up.
+ */
+static void
+tty3270_close(struct tty_struct *tty, struct file * filp)
+{
+ struct tty3270 *tp;
+
+ if (tty->count > 1)
+ return;
+ tp = (struct tty3270 *) tty->driver_data;
+ if (tp) {
+ tty->driver_data = 0;
+ tp->tty = tp->kbd->tty = 0;
+ raw3270_put_view(&tp->view);
+ }
+}
+
+/*
+ * We always have room.
+ */
+static int
+tty3270_write_room(struct tty_struct *tty)
+{
+ return INT_MAX;
+}
+
+/*
+ * Insert character into the screen at the current position with the
+ * current color and highlight. This function does NOT do cursor movement.
+ */
+static void
+tty3270_put_character(struct tty3270 *tp, char ch)
+{
+ struct tty3270_line *line;
+ struct tty3270_cell *cell;
+
+ line = tp->screen + tp->cy;
+ if (line->len <= tp->cx) {
+ while (line->len < tp->cx) {
+ cell = line->cells + line->len;
+ cell->character = tp->view.ascebc[' '];
+ cell->highlight = tp->highlight;
+ cell->f_color = tp->f_color;
+ line->len++;
+ }
+ line->len++;
+ }
+ cell = line->cells + tp->cx;
+ cell->character = tp->view.ascebc[(unsigned int) ch];
+ cell->highlight = tp->highlight;
+ cell->f_color = tp->f_color;
+}
+
+/*
+ * Convert a tty3270_line to a 3270 data fragment usable for output.
+ */
+static void
+tty3270_convert_line(struct tty3270 *tp, int line_nr)
+{
+ struct tty3270_line *line;
+ struct tty3270_cell *cell;
+ struct string *s, *n;
+ unsigned char highlight;
+ unsigned char f_color;
+ char *cp;
+ int flen, i;
+
+ /* Determine how long the fragment will be. */
+ flen = 3; /* Prefix (TO_SBA). */
+ line = tp->screen + line_nr;
+ flen += line->len;
+ highlight = TAX_RESET;
+ f_color = TAC_RESET;
+ for (i = 0, cell = line->cells; i < line->len; i++, cell++) {
+ if (cell->highlight != highlight) {
+ flen += 3; /* TO_SA to switch highlight. */
+ highlight = cell->highlight;
+ }
+ if (cell->f_color != f_color) {
+ flen += 3; /* TO_SA to switch color. */
+ f_color = cell->f_color;
+ }
+ }
+ if (highlight != TAX_RESET)
+ flen += 3; /* TO_SA to reset hightlight. */
+ if (f_color != TAC_RESET)
+ flen += 3; /* TO_SA to reset color. */
+ if (line->len < tp->view.cols)
+ flen += 4; /* Postfix (TO_RA). */
+
+ /* Find the line in the list. */
+ i = tp->view.rows - 2 - line_nr;
+ list_for_each_entry_reverse(s, &tp->lines, list)
+ if (--i <= 0)
+ break;
+ /*
+ * Check if the line needs to get reallocated.
+ */
+ if (s->len != flen) {
+ /* Reallocate string. */
+ n = tty3270_alloc_string(tp, flen);
+ list_add(&n->list, &s->list);
+ list_del_init(&s->list);
+ if (!list_empty(&s->update))
+ list_del_init(&s->update);
+ free_string(&tp->freemem, s);
+ s = n;
+ }
+
+ /* Write 3270 data fragment. */
+ cp = s->string;
+ *cp++ = TO_SBA;
+ *cp++ = 0;
+ *cp++ = 0;
+
+ highlight = TAX_RESET;
+ f_color = TAC_RESET;
+ for (i = 0, cell = line->cells; i < line->len; i++, cell++) {
+ if (cell->highlight != highlight) {
+ *cp++ = TO_SA;
+ *cp++ = TAT_EXTHI;
+ *cp++ = cell->highlight;
+ highlight = cell->highlight;
+ }
+ if (cell->f_color != f_color) {
+ *cp++ = TO_SA;
+ *cp++ = TAT_COLOR;
+ *cp++ = cell->f_color;
+ f_color = cell->f_color;
+ }
+ *cp++ = cell->character;
+ }
+ if (highlight != TAX_RESET) {
+ *cp++ = TO_SA;
+ *cp++ = TAT_EXTHI;
+ *cp++ = TAX_RESET;
+ }
+ if (f_color != TAC_RESET) {
+ *cp++ = TO_SA;
+ *cp++ = TAT_COLOR;
+ *cp++ = TAC_RESET;
+ }
+ if (line->len < tp->view.cols) {
+ *cp++ = TO_RA;
+ *cp++ = 0;
+ *cp++ = 0;
+ *cp++ = 0;
+ }
+
+ if (tp->nr_up + line_nr < tp->view.rows - 2) {
+ /* Line is currently visible on screen. */
+ tty3270_update_string(tp, s, line_nr);
+ /* Add line to update list. */
+ if (list_empty(&s->update)) {
+ list_add_tail(&s->update, &tp->update);
+ tp->update_flags |= TTY_UPDATE_LIST;
+ }
+ }
+}
+
+/*
+ * Do carriage return.
+ */
+static void
+tty3270_cr(struct tty3270 *tp)
+{
+ tp->cx = 0;
+}
+
+/*
+ * Do line feed.
+ */
+static void
+tty3270_lf(struct tty3270 *tp)
+{
+ struct tty3270_line temp;
+ int i;
+
+ tty3270_convert_line(tp, tp->cy);
+ if (tp->cy < tp->view.rows - 3) {
+ tp->cy++;
+ return;
+ }
+ /* Last line just filled up. Add new, blank line. */
+ tty3270_blank_line(tp);
+ temp = tp->screen[0];
+ temp.len = 0;
+ for (i = 0; i < tp->view.rows - 3; i++)
+ tp->screen[i] = tp->screen[i+1];
+ tp->screen[tp->view.rows - 3] = temp;
+ tty3270_rebuild_update(tp);
+}
+
+static void
+tty3270_ri(struct tty3270 *tp)
+{
+ if (tp->cy > 0) {
+ tty3270_convert_line(tp, tp->cy);
+ tp->cy--;
+ }
+}
+
+/*
+ * Insert characters at current position.
+ */
+static void
+tty3270_insert_characters(struct tty3270 *tp, int n)
+{
+ struct tty3270_line *line;
+ int k;
+
+ line = tp->screen + tp->cy;
+ while (line->len < tp->cx) {
+ line->cells[line->len].character = tp->view.ascebc[' '];
+ line->cells[line->len].highlight = TAX_RESET;
+ line->cells[line->len].f_color = TAC_RESET;
+ line->len++;
+ }
+ if (n > tp->view.cols - tp->cx)
+ n = tp->view.cols - tp->cx;
+ k = min_t(int, line->len - tp->cx, tp->view.cols - tp->cx - n);
+ while (k--)
+ line->cells[tp->cx + n + k] = line->cells[tp->cx + k];
+ line->len += n;
+ if (line->len > tp->view.cols)
+ line->len = tp->view.cols;
+ while (n-- > 0) {
+ line->cells[tp->cx + n].character = tp->view.ascebc[' '];
+ line->cells[tp->cx + n].highlight = tp->highlight;
+ line->cells[tp->cx + n].f_color = tp->f_color;
+ }
+}
+
+/*
+ * Delete characters at current position.
+ */
+static void
+tty3270_delete_characters(struct tty3270 *tp, int n)
+{
+ struct tty3270_line *line;
+ int i;
+
+ line = tp->screen + tp->cy;
+ if (line->len <= tp->cx)
+ return;
+ if (line->len - tp->cx <= n) {
+ line->len = tp->cx;
+ return;
+ }
+ for (i = tp->cx; i + n < line->len; i++)
+ line->cells[i] = line->cells[i + n];
+ line->len -= n;
+}
+
+/*
+ * Erase characters at current position.
+ */
+static void
+tty3270_erase_characters(struct tty3270 *tp, int n)
+{
+ struct tty3270_line *line;
+ struct tty3270_cell *cell;
+
+ line = tp->screen + tp->cy;
+ while (line->len > tp->cx && n-- > 0) {
+ cell = line->cells + tp->cx++;
+ cell->character = ' ';
+ cell->highlight = TAX_RESET;
+ cell->f_color = TAC_RESET;
+ }
+ tp->cx += n;
+ tp->cx = min_t(int, tp->cx, tp->view.cols - 1);
+}
+
+/*
+ * Erase line, 3 different cases:
+ * Esc [ 0 K Erase from current position to end of line inclusive
+ * Esc [ 1 K Erase from beginning of line to current position inclusive
+ * Esc [ 2 K Erase entire line (without moving cursor)
+ */
+static void
+tty3270_erase_line(struct tty3270 *tp, int mode)
+{
+ struct tty3270_line *line;
+ struct tty3270_cell *cell;
+ int i;
+
+ line = tp->screen + tp->cy;
+ if (mode == 0)
+ line->len = tp->cx;
+ else if (mode == 1) {
+ for (i = 0; i < tp->cx; i++) {
+ cell = line->cells + i;
+ cell->character = ' ';
+ cell->highlight = TAX_RESET;
+ cell->f_color = TAC_RESET;
+ }
+ if (line->len <= tp->cx)
+ line->len = tp->cx + 1;
+ } else if (mode == 2)
+ line->len = 0;
+ tty3270_convert_line(tp, tp->cy);
+}
+
+/*
+ * Erase display, 3 different cases:
+ * Esc [ 0 J Erase from current position to bottom of screen inclusive
+ * Esc [ 1 J Erase from top of screen to current position inclusive
+ * Esc [ 2 J Erase entire screen (without moving the cursor)
+ */
+static void
+tty3270_erase_display(struct tty3270 *tp, int mode)
+{
+ int i;
+
+ if (mode == 0) {
+ tty3270_erase_line(tp, 0);
+ for (i = tp->cy + 1; i < tp->view.rows - 2; i++) {
+ tp->screen[i].len = 0;
+ tty3270_convert_line(tp, i);
+ }
+ } else if (mode == 1) {
+ for (i = 0; i < tp->cy; i++) {
+ tp->screen[i].len = 0;
+ tty3270_convert_line(tp, i);
+ }
+ tty3270_erase_line(tp, 1);
+ } else if (mode == 2) {
+ for (i = 0; i < tp->view.rows - 2; i++) {
+ tp->screen[i].len = 0;
+ tty3270_convert_line(tp, i);
+ }
+ }
+ tty3270_rebuild_update(tp);
+}
+
+/*
+ * Set attributes found in an escape sequence.
+ * Esc [ <attr> ; <attr> ; ... m
+ */
+static void
+tty3270_set_attributes(struct tty3270 *tp)
+{
+ static unsigned char f_colors[] = {
+ TAC_DEFAULT, TAC_RED, TAC_GREEN, TAC_YELLOW, TAC_BLUE,
+ TAC_PINK, TAC_TURQ, TAC_WHITE, 0, TAC_DEFAULT
+ };
+ int i, attr;
+
+ for (i = 0; i <= tp->esc_npar; i++) {
+ attr = tp->esc_par[i];
+ switch (attr) {
+ case 0: /* Reset */
+ tp->highlight = TAX_RESET;
+ tp->f_color = TAC_RESET;
+ break;
+ /* Highlight. */
+ case 4: /* Start underlining. */
+ tp->highlight = TAX_UNDER;
+ break;
+ case 5: /* Start blink. */
+ tp->highlight = TAX_BLINK;
+ break;
+ case 7: /* Start reverse. */
+ tp->highlight = TAX_REVER;
+ break;
+ case 24: /* End underlining */
+ if (tp->highlight == TAX_UNDER)
+ tp->highlight = TAX_RESET;
+ break;
+ case 25: /* End blink. */
+ if (tp->highlight == TAX_BLINK)
+ tp->highlight = TAX_RESET;
+ break;
+ case 27: /* End reverse. */
+ if (tp->highlight == TAX_REVER)
+ tp->highlight = TAX_RESET;
+ break;
+ /* Foreground color. */
+ case 30: /* Black */
+ case 31: /* Red */
+ case 32: /* Green */
+ case 33: /* Yellow */
+ case 34: /* Blue */
+ case 35: /* Magenta */
+ case 36: /* Cyan */
+ case 37: /* White */
+ case 39: /* Black */
+ tp->f_color = f_colors[attr - 30];
+ break;
+ }
+ }
+}
+
+static inline int
+tty3270_getpar(struct tty3270 *tp, int ix)
+{
+ return (tp->esc_par[ix] > 0) ? tp->esc_par[ix] : 1;
+}
+
+static void
+tty3270_goto_xy(struct tty3270 *tp, int cx, int cy)
+{
+ tp->cx = min_t(int, tp->view.cols - 1, max_t(int, 0, cx));
+ cy = min_t(int, tp->view.rows - 3, max_t(int, 0, cy));
+ if (cy != tp->cy) {
+ tty3270_convert_line(tp, tp->cy);
+ tp->cy = cy;
+ }
+}
+
+/*
+ * Process escape sequences. Known sequences:
+ * Esc 7 Save Cursor Position
+ * Esc 8 Restore Cursor Position
+ * Esc [ Pn ; Pn ; .. m Set attributes
+ * Esc [ Pn ; Pn H Cursor Position
+ * Esc [ Pn ; Pn f Cursor Position
+ * Esc [ Pn A Cursor Up
+ * Esc [ Pn B Cursor Down
+ * Esc [ Pn C Cursor Forward
+ * Esc [ Pn D Cursor Backward
+ * Esc [ Pn G Cursor Horizontal Absolute
+ * Esc [ Pn X Erase Characters
+ * Esc [ Ps J Erase in Display
+ * Esc [ Ps K Erase in Line
+ * // FIXME: add all the new ones.
+ *
+ * Pn is a numeric parameter, a string of zero or more decimal digits.
+ * Ps is a selective parameter.
+ */
+static void
+tty3270_escape_sequence(struct tty3270 *tp, char ch)
+{
+ enum { ESnormal, ESesc, ESsquare, ESgetpars };
+
+ if (tp->esc_state == ESnormal) {
+ if (ch == 0x1b)
+ /* Starting new escape sequence. */
+ tp->esc_state = ESesc;
+ return;
+ }
+ if (tp->esc_state == ESesc) {
+ tp->esc_state = ESnormal;
+ switch (ch) {
+ case '[':
+ tp->esc_state = ESsquare;
+ break;
+ case 'E':
+ tty3270_cr(tp);
+ tty3270_lf(tp);
+ break;
+ case 'M':
+ tty3270_ri(tp);
+ break;
+ case 'D':
+ tty3270_lf(tp);
+ break;
+ case 'Z': /* Respond ID. */
+ kbd_puts_queue(tp->tty, "\033[?6c");
+ break;
+ case '7': /* Save cursor position. */
+ tp->saved_cx = tp->cx;
+ tp->saved_cy = tp->cy;
+ tp->saved_highlight = tp->highlight;
+ tp->saved_f_color = tp->f_color;
+ break;
+ case '8': /* Restore cursor position. */
+ tty3270_convert_line(tp, tp->cy);
+ tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy);
+ tp->highlight = tp->saved_highlight;
+ tp->f_color = tp->saved_f_color;
+ break;
+ case 'c': /* Reset terminal. */
+ tp->cx = tp->saved_cx = 0;
+ tp->cy = tp->saved_cy = 0;
+ tp->highlight = tp->saved_highlight = TAX_RESET;
+ tp->f_color = tp->saved_f_color = TAC_RESET;
+ tty3270_erase_display(tp, 2);
+ break;
+ }
+ return;
+ }
+ if (tp->esc_state == ESsquare) {
+ tp->esc_state = ESgetpars;
+ memset(tp->esc_par, 0, sizeof(tp->esc_par));
+ tp->esc_npar = 0;
+ tp->esc_ques = (ch == '?');
+ if (tp->esc_ques)
+ return;
+ }
+ if (tp->esc_state == ESgetpars) {
+ if (ch == ';' && tp->esc_npar < ESCAPE_NPAR - 1) {
+ tp->esc_npar++;
+ return;
+ }
+ if (ch >= '0' && ch <= '9') {
+ tp->esc_par[tp->esc_npar] *= 10;
+ tp->esc_par[tp->esc_npar] += ch - '0';
+ return;
+ }
+ }
+ tp->esc_state = ESnormal;
+ if (ch == 'n' && !tp->esc_ques) {
+ if (tp->esc_par[0] == 5) /* Status report. */
+ kbd_puts_queue(tp->tty, "\033[0n");
+ else if (tp->esc_par[0] == 6) { /* Cursor report. */
+ char buf[40];
+ sprintf(buf, "\033[%d;%dR", tp->cy + 1, tp->cx + 1);
+ kbd_puts_queue(tp->tty, buf);
+ }
+ return;
+ }
+ if (tp->esc_ques)
+ return;
+ switch (ch) {
+ case 'm':
+ tty3270_set_attributes(tp);
+ break;
+ case 'H': /* Set cursor position. */
+ case 'f':
+ tty3270_goto_xy(tp, tty3270_getpar(tp, 1) - 1,
+ tty3270_getpar(tp, 0) - 1);
+ break;
+ case 'd': /* Set y position. */
+ tty3270_goto_xy(tp, tp->cx, tty3270_getpar(tp, 0) - 1);
+ break;
+ case 'A': /* Cursor up. */
+ case 'F':
+ tty3270_goto_xy(tp, tp->cx, tp->cy - tty3270_getpar(tp, 0));
+ break;
+ case 'B': /* Cursor down. */
+ case 'e':
+ case 'E':
+ tty3270_goto_xy(tp, tp->cx, tp->cy + tty3270_getpar(tp, 0));
+ break;
+ case 'C': /* Cursor forward. */
+ case 'a':
+ tty3270_goto_xy(tp, tp->cx + tty3270_getpar(tp, 0), tp->cy);
+ break;
+ case 'D': /* Cursor backward. */
+ tty3270_goto_xy(tp, tp->cx - tty3270_getpar(tp, 0), tp->cy);
+ break;
+ case 'G': /* Set x position. */
+ case '`':
+ tty3270_goto_xy(tp, tty3270_getpar(tp, 0), tp->cy);
+ break;
+ case 'X': /* Erase Characters. */
+ tty3270_erase_characters(tp, tty3270_getpar(tp, 0));
+ break;
+ case 'J': /* Erase display. */
+ tty3270_erase_display(tp, tp->esc_par[0]);
+ break;
+ case 'K': /* Erase line. */
+ tty3270_erase_line(tp, tp->esc_par[0]);
+ break;
+ case 'P': /* Delete characters. */
+ tty3270_delete_characters(tp, tty3270_getpar(tp, 0));
+ break;
+ case '@': /* Insert characters. */
+ tty3270_insert_characters(tp, tty3270_getpar(tp, 0));
+ break;
+ case 's': /* Save cursor position. */
+ tp->saved_cx = tp->cx;
+ tp->saved_cy = tp->cy;
+ tp->saved_highlight = tp->highlight;
+ tp->saved_f_color = tp->f_color;
+ break;
+ case 'u': /* Restore cursor position. */
+ tty3270_convert_line(tp, tp->cy);
+ tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy);
+ tp->highlight = tp->saved_highlight;
+ tp->f_color = tp->saved_f_color;
+ break;
+ }
+}
+
+/*
+ * String write routine for 3270 ttys
+ */
+static void
+tty3270_do_write(struct tty3270 *tp, const unsigned char *buf, int count)
+{
+ int i_msg, i;
+
+ spin_lock_bh(&tp->view.lock);
+ for (i_msg = 0; !tp->tty->stopped && i_msg < count; i_msg++) {
+ if (tp->esc_state != 0) {
+ /* Continue escape sequence. */
+ tty3270_escape_sequence(tp, buf[i_msg]);
+ continue;
+ }
+
+ switch (buf[i_msg]) {
+ case 0x07: /* '\a' -- Alarm */
+ tp->wcc |= TW_PLUSALARM;
+ break;
+ case 0x08: /* Backspace. */
+ if (tp->cx > 0) {
+ tp->cx--;
+ tty3270_put_character(tp, ' ');
+ }
+ break;
+ case 0x09: /* '\t' -- Tabulate */
+ for (i = tp->cx % 8; i < 8; i++) {
+ if (tp->cx >= tp->view.cols) {
+ tty3270_cr(tp);
+ tty3270_lf(tp);
+ break;
+ }
+ tty3270_put_character(tp, ' ');
+ tp->cx++;
+ }
+ break;
+ case 0x0a: /* '\n' -- New Line */
+ tty3270_cr(tp);
+ tty3270_lf(tp);
+ break;
+ case 0x0c: /* '\f' -- Form Feed */
+ tty3270_erase_display(tp, 2);
+ tp->cx = tp->cy = 0;
+ break;
+ case 0x0d: /* '\r' -- Carriage Return */
+ tp->cx = 0;
+ break;
+ case 0x0f: /* SuSE "exit alternate mode" */
+ break;
+ case 0x1b: /* Start escape sequence. */
+ tty3270_escape_sequence(tp, buf[i_msg]);
+ break;
+ default: /* Insert normal character. */
+ if (tp->cx >= tp->view.cols) {
+ tty3270_cr(tp);
+ tty3270_lf(tp);
+ }
+ tty3270_put_character(tp, buf[i_msg]);
+ tp->cx++;
+ break;
+ }
+ }
+ /* Convert current line to 3270 data fragment. */
+ tty3270_convert_line(tp, tp->cy);
+
+ /* Setup timer to update display after 1/10 second */
+ if (!timer_pending(&tp->timer))
+ tty3270_set_timer(tp, HZ/10);
+
+ spin_unlock_bh(&tp->view.lock);
+}
+
+/*
+ * String write routine for 3270 ttys
+ */
+static int
+tty3270_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct tty3270 *tp;
+
+ tp = tty->driver_data;
+ if (!tp)
+ return 0;
+ if (tp->char_count > 0) {
+ tty3270_do_write(tp, tp->char_buf, tp->char_count);
+ tp->char_count = 0;
+ }
+ tty3270_do_write(tp, buf, count);
+ return count;
+}
+
+/*
+ * Put single characters to the ttys character buffer
+ */
+static void
+tty3270_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct tty3270 *tp;
+
+ tp = tty->driver_data;
+ if (!tp)
+ return;
+ if (tp->char_count < TTY3270_CHAR_BUF_SIZE)
+ tp->char_buf[tp->char_count++] = ch;
+}
+
+/*
+ * Flush all characters from the ttys characeter buffer put there
+ * by tty3270_put_char.
+ */
+static void
+tty3270_flush_chars(struct tty_struct *tty)
+{
+ struct tty3270 *tp;
+
+ tp = tty->driver_data;
+ if (!tp)
+ return;
+ if (tp->char_count > 0) {
+ tty3270_do_write(tp, tp->char_buf, tp->char_count);
+ tp->char_count = 0;
+ }
+}
+
+/*
+ * Returns the number of characters in the output buffer. This is
+ * used in tty_wait_until_sent to wait until all characters have
+ * appeared on the screen.
+ */
+static int
+tty3270_chars_in_buffer(struct tty_struct *tty)
+{
+ return 0;
+}
+
+static void
+tty3270_flush_buffer(struct tty_struct *tty)
+{
+}
+
+/*
+ * Check for visible/invisible input switches
+ */
+static void
+tty3270_set_termios(struct tty_struct *tty, struct termios *old)
+{
+ struct tty3270 *tp;
+ int new;
+
+ tp = tty->driver_data;
+ if (!tp)
+ return;
+ spin_lock_bh(&tp->view.lock);
+ if (L_ICANON(tty)) {
+ new = L_ECHO(tty) ? TF_INPUT: TF_INPUTN;
+ if (new != tp->inattr) {
+ tp->inattr = new;
+ tty3270_update_prompt(tp, 0, 0);
+ tty3270_set_timer(tp, 1);
+ }
+ }
+ spin_unlock_bh(&tp->view.lock);
+}
+
+/*
+ * Disable reading from a 3270 tty
+ */
+static void
+tty3270_throttle(struct tty_struct * tty)
+{
+ struct tty3270 *tp;
+
+ tp = tty->driver_data;
+ if (!tp)
+ return;
+ tp->throttle = 1;
+}
+
+/*
+ * Enable reading from a 3270 tty
+ */
+static void
+tty3270_unthrottle(struct tty_struct * tty)
+{
+ struct tty3270 *tp;
+
+ tp = tty->driver_data;
+ if (!tp)
+ return;
+ tp->throttle = 0;
+ if (tp->attn)
+ tty3270_issue_read(tp, 1);
+}
+
+/*
+ * Hang up the tty device.
+ */
+static void
+tty3270_hangup(struct tty_struct *tty)
+{
+ // FIXME: implement
+}
+
+static void
+tty3270_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+}
+
+static int
+tty3270_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct tty3270 *tp;
+
+ tp = tty->driver_data;
+ if (!tp)
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ return kbd_ioctl(tp->kbd, file, cmd, arg);
+}
+
+static struct tty_operations tty3270_ops = {
+ .open = tty3270_open,
+ .close = tty3270_close,
+ .write = tty3270_write,
+ .put_char = tty3270_put_char,
+ .flush_chars = tty3270_flush_chars,
+ .write_room = tty3270_write_room,
+ .chars_in_buffer = tty3270_chars_in_buffer,
+ .flush_buffer = tty3270_flush_buffer,
+ .throttle = tty3270_throttle,
+ .unthrottle = tty3270_unthrottle,
+ .hangup = tty3270_hangup,
+ .wait_until_sent = tty3270_wait_until_sent,
+ .ioctl = tty3270_ioctl,
+ .set_termios = tty3270_set_termios
+};
+
+void
+tty3270_notifier(int index, int active)
+{
+ if (active)
+ tty_register_device(tty3270_driver, index, 0);
+ else
+ tty_unregister_device(tty3270_driver, index);
+}
+
+/*
+ * 3270 tty registration code called from tty_init().
+ * Most kernel services (incl. kmalloc) are available at this poimt.
+ */
+int __init
+tty3270_init(void)
+{
+ struct tty_driver *driver;
+ int ret;
+
+ driver = alloc_tty_driver(256);
+ if (!driver)
+ return -ENOMEM;
+
+ /*
+ * Initialize the tty_driver structure
+ * Entries in tty3270_driver that are NOT initialized:
+ * proc_entry, set_termios, flush_buffer, set_ldisc, write_proc
+ */
+ driver->owner = THIS_MODULE;
+ driver->devfs_name = "ttyTUB/";
+ driver->driver_name = "ttyTUB";
+ driver->name = "ttyTUB";
+ driver->major = IBM_TTY3270_MAJOR;
+ driver->type = TTY_DRIVER_TYPE_SYSTEM;
+ driver->subtype = SYSTEM_TYPE_TTY;
+ driver->init_termios = tty_std_termios;
+ driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_NO_DEVFS;
+ tty_set_operations(driver, &tty3270_ops);
+ ret = tty_register_driver(driver);
+ if (ret) {
+ printk(KERN_ERR "tty3270 registration failed with %d\n", ret);
+ put_tty_driver(driver);
+ return ret;
+ }
+ tty3270_driver = driver;
+ ret = raw3270_register_notifier(tty3270_notifier);
+ if (ret) {
+ printk(KERN_ERR "tty3270 notifier registration failed "
+ "with %d\n", ret);
+ put_tty_driver(driver);
+ return ret;
+
+ }
+ return 0;
+}
+
+static void __exit
+tty3270_exit(void)
+{
+ struct tty_driver *driver;
+
+ raw3270_unregister_notifier(tty3270_notifier);
+ driver = tty3270_driver;
+ tty3270_driver = 0;
+ tty_unregister_driver(driver);
+ tty3270_del_views();
+}
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CHARDEV_MAJOR(IBM_TTY3270_MAJOR);
+
+module_init(tty3270_init);
+module_exit(tty3270_exit);
diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c
new file mode 100644
index 000000000000..edf50d2bd10b
--- /dev/null
+++ b/drivers/s390/char/vmlogrdr.c
@@ -0,0 +1,920 @@
+/*
+ * drivers/s390/char/vmlogrdr.c
+ * character device driver for reading z/VM system service records
+ *
+ *
+ * Copyright (C) 2004 IBM Corporation
+ * character device driver for reading z/VM system service records,
+ * Version 1.0
+ * Author(s): Xenia Tkatschow <xenia@us.ibm.com>
+ * Stefan Weinhuber <wein@de.ibm.com>
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <asm/cpcmd.h>
+#include <asm/debug.h>
+#include <asm/ebcdic.h>
+#include "../net/iucv.h"
+#include <linux/kmod.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/string.h>
+
+
+
+MODULE_AUTHOR
+ ("(C) 2004 IBM Corporation by Xenia Tkatschow (xenia@us.ibm.com)\n"
+ " Stefan Weinhuber (wein@de.ibm.com)");
+MODULE_DESCRIPTION ("Character device driver for reading z/VM "
+ "system service records.");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * The size of the buffer for iucv data transfer is one page,
+ * but in addition to the data we read from iucv we also
+ * place an integer and some characters into that buffer,
+ * so the maximum size for record data is a little less then
+ * one page.
+ */
+#define NET_BUFFER_SIZE (PAGE_SIZE - sizeof(int) - sizeof(FENCE))
+
+/*
+ * The elements that are concurrently accessed by bottom halves are
+ * connection_established, iucv_path_severed, local_interrupt_buffer
+ * and receive_ready. The first three can be protected by
+ * priv_lock. receive_ready is atomic, so it can be incremented and
+ * decremented without holding a lock.
+ * The variable dev_in_use needs to be protected by the lock, since
+ * it's a flag used by open to make sure that the device is opened only
+ * by one user at the same time.
+ */
+struct vmlogrdr_priv_t {
+ char system_service[8];
+ char internal_name[8];
+ char recording_name[8];
+ u16 pathid;
+ int connection_established;
+ int iucv_path_severed;
+ iucv_MessagePending local_interrupt_buffer;
+ atomic_t receive_ready;
+ iucv_handle_t iucv_handle;
+ int minor_num;
+ char * buffer;
+ char * current_position;
+ int remaining;
+ ulong residual_length;
+ int buffer_free;
+ int dev_in_use; /* 1: already opened, 0: not opened*/
+ spinlock_t priv_lock;
+ struct device *device;
+ struct class_device *class_device;
+ int autorecording;
+ int autopurge;
+};
+
+
+/*
+ * File operation structure for vmlogrdr devices
+ */
+static int vmlogrdr_open(struct inode *, struct file *);
+static int vmlogrdr_release(struct inode *, struct file *);
+static ssize_t vmlogrdr_read (struct file *filp, char *data, size_t count,
+ loff_t * ppos);
+
+static struct file_operations vmlogrdr_fops = {
+ .owner = THIS_MODULE,
+ .open = vmlogrdr_open,
+ .release = vmlogrdr_release,
+ .read = vmlogrdr_read,
+};
+
+
+static u8 iucvMagic[16] = {
+ 0xF0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0xF0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40
+};
+
+
+static u8 mask[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+
+static u8 iucv_host[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+
+static void
+vmlogrdr_iucv_ConnectionComplete(iucv_ConnectionComplete *eib, void *pgm_data);
+static void
+vmlogrdr_iucv_ConnectionSevered(iucv_ConnectionSevered *eib, void *pgm_data);
+static void
+vmlogrdr_iucv_MessagePending(iucv_MessagePending *eib, void *pgm_data);
+
+
+static iucv_interrupt_ops_t vmlogrdr_iucvops = {
+ .ConnectionComplete = vmlogrdr_iucv_ConnectionComplete,
+ .ConnectionSevered = vmlogrdr_iucv_ConnectionSevered,
+ .MessagePending = vmlogrdr_iucv_MessagePending,
+};
+
+
+DECLARE_WAIT_QUEUE_HEAD(conn_wait_queue);
+DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);
+
+/*
+ * pointer to system service private structure
+ * minor number 0 --> logrec
+ * minor number 1 --> account
+ * minor number 2 --> symptom
+ */
+
+static struct vmlogrdr_priv_t sys_ser[] = {
+ { .system_service = "*LOGREC ",
+ .internal_name = "logrec",
+ .recording_name = "EREP",
+ .minor_num = 0,
+ .buffer_free = 1,
+ .priv_lock = SPIN_LOCK_UNLOCKED,
+ .autorecording = 1,
+ .autopurge = 1,
+ },
+ { .system_service = "*ACCOUNT",
+ .internal_name = "account",
+ .recording_name = "ACCOUNT",
+ .minor_num = 1,
+ .buffer_free = 1,
+ .priv_lock = SPIN_LOCK_UNLOCKED,
+ .autorecording = 1,
+ .autopurge = 1,
+ },
+ { .system_service = "*SYMPTOM",
+ .internal_name = "symptom",
+ .recording_name = "SYMPTOM",
+ .minor_num = 2,
+ .buffer_free = 1,
+ .priv_lock = SPIN_LOCK_UNLOCKED,
+ .autorecording = 1,
+ .autopurge = 1,
+ }
+};
+
+#define MAXMINOR (sizeof(sys_ser)/sizeof(struct vmlogrdr_priv_t))
+
+static char FENCE[] = {"EOR"};
+static int vmlogrdr_major = 0;
+static struct cdev *vmlogrdr_cdev = NULL;
+static int recording_class_AB;
+
+
+static void
+vmlogrdr_iucv_ConnectionComplete (iucv_ConnectionComplete * eib,
+ void * pgm_data)
+{
+ struct vmlogrdr_priv_t * logptr = pgm_data;
+ spin_lock(&logptr->priv_lock);
+ logptr->connection_established = 1;
+ spin_unlock(&logptr->priv_lock);
+ wake_up(&conn_wait_queue);
+ return;
+}
+
+
+static void
+vmlogrdr_iucv_ConnectionSevered (iucv_ConnectionSevered * eib, void * pgm_data)
+{
+ u8 reason = (u8) eib->ipuser[8];
+ struct vmlogrdr_priv_t * logptr = pgm_data;
+
+ printk (KERN_ERR "vmlogrdr: connection severed with"
+ " reason %i\n", reason);
+
+ spin_lock(&logptr->priv_lock);
+ logptr->connection_established = 0;
+ logptr->iucv_path_severed = 1;
+ spin_unlock(&logptr->priv_lock);
+
+ wake_up(&conn_wait_queue);
+ /* just in case we're sleeping waiting for a record */
+ wake_up_interruptible(&read_wait_queue);
+}
+
+
+static void
+vmlogrdr_iucv_MessagePending (iucv_MessagePending * eib, void * pgm_data)
+{
+ struct vmlogrdr_priv_t * logptr = pgm_data;
+
+ /*
+ * This function is the bottom half so it should be quick.
+ * Copy the external interrupt data into our local eib and increment
+ * the usage count
+ */
+ spin_lock(&logptr->priv_lock);
+ memcpy(&(logptr->local_interrupt_buffer), eib, sizeof(*eib));
+ atomic_inc(&logptr->receive_ready);
+ spin_unlock(&logptr->priv_lock);
+ wake_up_interruptible(&read_wait_queue);
+}
+
+
+static int
+vmlogrdr_get_recording_class_AB(void) {
+ char cp_command[]="QUERY COMMAND RECORDING ";
+ char cp_response[80];
+ char *tail;
+ int len,i;
+
+ printk (KERN_DEBUG "vmlogrdr: query command: %s\n", cp_command);
+ cpcmd(cp_command, cp_response, sizeof(cp_response));
+ printk (KERN_DEBUG "vmlogrdr: response: %s", cp_response);
+ len = strnlen(cp_response,sizeof(cp_response));
+ // now the parsing
+ tail=strnchr(cp_response,len,'=');
+ if (!tail)
+ return 0;
+ tail++;
+ if (!strncmp("ANY",tail,3))
+ return 1;
+ if (!strncmp("NONE",tail,4))
+ return 0;
+ /*
+ * expect comma separated list of classes here, if one of them
+ * is A or B return 1 otherwise 0
+ */
+ for (i=tail-cp_response; i<len; i++)
+ if ( cp_response[i]=='A' || cp_response[i]=='B' )
+ return 1;
+ return 0;
+}
+
+
+static int
+vmlogrdr_recording(struct vmlogrdr_priv_t * logptr, int action, int purge) {
+
+ char cp_command[80];
+ char cp_response[160];
+ char *onoff, *qid_string;
+
+ memset(cp_command, 0x00, sizeof(cp_command));
+ memset(cp_response, 0x00, sizeof(cp_response));
+
+ onoff = ((action == 1) ? "ON" : "OFF");
+ qid_string = ((recording_class_AB == 1) ? " QID * " : "");
+
+ /*
+ * The recording commands needs to be called with option QID
+ * for guests that have previlege classes A or B.
+ * Purging has to be done as separate step, because recording
+ * can't be switched on as long as records are on the queue.
+ * Doing both at the same time doesn't work.
+ */
+
+ if (purge) {
+ snprintf(cp_command, sizeof(cp_command),
+ "RECORDING %s PURGE %s",
+ logptr->recording_name,
+ qid_string);
+
+ printk (KERN_DEBUG "vmlogrdr: recording command: %s\n",
+ cp_command);
+ cpcmd(cp_command, cp_response, sizeof(cp_response));
+ printk (KERN_DEBUG "vmlogrdr: recording response: %s",
+ cp_response);
+ }
+
+ memset(cp_command, 0x00, sizeof(cp_command));
+ memset(cp_response, 0x00, sizeof(cp_response));
+ snprintf(cp_command, sizeof(cp_command), "RECORDING %s %s %s",
+ logptr->recording_name,
+ onoff,
+ qid_string);
+
+ printk (KERN_DEBUG "vmlogrdr: recording command: %s\n", cp_command);
+ cpcmd(cp_command, cp_response, sizeof(cp_response));
+ printk (KERN_DEBUG "vmlogrdr: recording response: %s",
+ cp_response);
+ /* The recording command will usually answer with 'Command complete'
+ * on success, but when the specific service was never connected
+ * before then there might be an additional informational message
+ * 'HCPCRC8072I Recording entry not found' before the
+ * 'Command complete'. So I use strstr rather then the strncmp.
+ */
+ if (strstr(cp_response,"Command complete"))
+ return 0;
+ else
+ return -EIO;
+
+}
+
+
+static int
+vmlogrdr_open (struct inode *inode, struct file *filp)
+{
+ int dev_num = 0;
+ struct vmlogrdr_priv_t * logptr = NULL;
+ int connect_rc = 0;
+ int ret;
+
+ dev_num = iminor(inode);
+ if (dev_num > MAXMINOR)
+ return -ENODEV;
+
+ logptr = &sys_ser[dev_num];
+ if (logptr == NULL)
+ return -ENODEV;
+
+ /*
+ * only allow for blocking reads to be open
+ */
+ if (filp->f_flags & O_NONBLOCK)
+ return -ENOSYS;
+
+ /* Besure this device hasn't already been opened */
+ spin_lock_bh(&logptr->priv_lock);
+ if (logptr->dev_in_use) {
+ spin_unlock_bh(&logptr->priv_lock);
+ return -EBUSY;
+ } else {
+ logptr->dev_in_use = 1;
+ spin_unlock_bh(&logptr->priv_lock);
+ }
+
+ atomic_set(&logptr->receive_ready, 0);
+ logptr->buffer_free = 1;
+
+ /* set the file options */
+ filp->private_data = logptr;
+ filp->f_op = &vmlogrdr_fops;
+
+ /* start recording for this service*/
+ ret=0;
+ if (logptr->autorecording)
+ ret = vmlogrdr_recording(logptr,1,logptr->autopurge);
+ if (ret)
+ printk (KERN_WARNING "vmlogrdr: failed to start "
+ "recording automatically\n");
+
+ /* Register with iucv driver */
+ logptr->iucv_handle = iucv_register_program(iucvMagic,
+ logptr->system_service, mask, &vmlogrdr_iucvops,
+ logptr);
+
+ if (logptr->iucv_handle == NULL) {
+ printk (KERN_ERR "vmlogrdr: failed to register with"
+ "iucv driver\n");
+ goto not_registered;
+ }
+
+ /* create connection to the system service */
+ spin_lock_bh(&logptr->priv_lock);
+ logptr->connection_established = 0;
+ logptr->iucv_path_severed = 0;
+ spin_unlock_bh(&logptr->priv_lock);
+
+ connect_rc = iucv_connect (&(logptr->pathid), 10, iucvMagic,
+ logptr->system_service, iucv_host, 0,
+ NULL, NULL,
+ logptr->iucv_handle, NULL);
+ if (connect_rc) {
+ printk (KERN_ERR "vmlogrdr: iucv connection to %s "
+ "failed with rc %i \n", logptr->system_service,
+ connect_rc);
+ goto not_connected;
+ }
+
+ /* We've issued the connect and now we must wait for a
+ * ConnectionComplete or ConnectinSevered Interrupt
+ * before we can continue to process.
+ */
+ wait_event(conn_wait_queue, (logptr->connection_established)
+ || (logptr->iucv_path_severed));
+ if (logptr->iucv_path_severed) {
+ goto not_connected;
+ }
+
+ return nonseekable_open(inode, filp);
+
+not_connected:
+ iucv_unregister_program(logptr->iucv_handle);
+ logptr->iucv_handle = NULL;
+not_registered:
+ if (logptr->autorecording)
+ vmlogrdr_recording(logptr,0,logptr->autopurge);
+ logptr->dev_in_use = 0;
+ return -EIO;
+
+
+}
+
+
+static int
+vmlogrdr_release (struct inode *inode, struct file *filp)
+{
+ int ret;
+
+ struct vmlogrdr_priv_t * logptr = filp->private_data;
+
+ iucv_unregister_program(logptr->iucv_handle);
+ logptr->iucv_handle = NULL;
+
+ if (logptr->autorecording) {
+ ret = vmlogrdr_recording(logptr,0,logptr->autopurge);
+ if (ret)
+ printk (KERN_WARNING "vmlogrdr: failed to stop "
+ "recording automatically\n");
+ }
+ logptr->dev_in_use = 0;
+
+ return 0;
+}
+
+
+static int
+vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv) {
+ int rc, *temp;
+ /* we need to keep track of two data sizes here:
+ * The number of bytes we need to receive from iucv and
+ * the total number of bytes we actually write into the buffer.
+ */
+ int user_data_count, iucv_data_count;
+ char * buffer;
+
+ if (atomic_read(&priv->receive_ready)) {
+ spin_lock_bh(&priv->priv_lock);
+ if (priv->residual_length){
+ /* receive second half of a record */
+ iucv_data_count = priv->residual_length;
+ user_data_count = 0;
+ buffer = priv->buffer;
+ } else {
+ /* receive a new record:
+ * We need to return the total length of the record
+ * + size of FENCE in the first 4 bytes of the buffer.
+ */
+ iucv_data_count =
+ priv->local_interrupt_buffer.ln1msg2.ipbfln1f;
+ user_data_count = sizeof(int);
+ temp = (int*)priv->buffer;
+ *temp= iucv_data_count + sizeof(FENCE);
+ buffer = priv->buffer + sizeof(int);
+ }
+ /*
+ * If the record is bigger then our buffer, we receive only
+ * a part of it. We can get the rest later.
+ */
+ if (iucv_data_count > NET_BUFFER_SIZE)
+ iucv_data_count = NET_BUFFER_SIZE;
+ rc = iucv_receive(priv->pathid,
+ priv->local_interrupt_buffer.ipmsgid,
+ priv->local_interrupt_buffer.iptrgcls,
+ buffer,
+ iucv_data_count,
+ NULL,
+ NULL,
+ &priv->residual_length);
+ spin_unlock_bh(&priv->priv_lock);
+ /* An rc of 5 indicates that the record was bigger then
+ * the buffer, which is OK for us. A 9 indicates that the
+ * record was purged befor we could receive it.
+ */
+ if (rc == 5)
+ rc = 0;
+ if (rc == 9)
+ atomic_set(&priv->receive_ready, 0);
+ } else {
+ rc = 1;
+ }
+ if (!rc) {
+ priv->buffer_free = 0;
+ user_data_count += iucv_data_count;
+ priv->current_position = priv->buffer;
+ if (priv->residual_length == 0){
+ /* the whole record has been captured,
+ * now add the fence */
+ atomic_dec(&priv->receive_ready);
+ buffer = priv->buffer + user_data_count;
+ memcpy(buffer, FENCE, sizeof(FENCE));
+ user_data_count += sizeof(FENCE);
+ }
+ priv->remaining = user_data_count;
+ }
+
+ return rc;
+}
+
+
+static ssize_t
+vmlogrdr_read (struct file *filp, char *data, size_t count, loff_t * ppos)
+{
+ int rc;
+ struct vmlogrdr_priv_t * priv = filp->private_data;
+
+ while (priv->buffer_free) {
+ rc = vmlogrdr_receive_data(priv);
+ if (rc) {
+ rc = wait_event_interruptible(read_wait_queue,
+ atomic_read(&priv->receive_ready));
+ if (rc)
+ return rc;
+ }
+ }
+ /* copy only up to end of record */
+ if (count > priv->remaining)
+ count = priv->remaining;
+
+ if (copy_to_user(data, priv->current_position, count))
+ return -EFAULT;
+
+ *ppos += count;
+ priv->current_position += count;
+ priv->remaining -= count;
+
+ /* if all data has been transferred, set buffer free */
+ if (priv->remaining == 0)
+ priv->buffer_free = 1;
+
+ return count;
+}
+
+static ssize_t
+vmlogrdr_autopurge_store(struct device * dev, const char * buf, size_t count) {
+ struct vmlogrdr_priv_t *priv = dev->driver_data;
+ ssize_t ret = count;
+
+ switch (buf[0]) {
+ case '0':
+ priv->autopurge=0;
+ break;
+ case '1':
+ priv->autopurge=1;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+
+static ssize_t
+vmlogrdr_autopurge_show(struct device *dev, char *buf) {
+ struct vmlogrdr_priv_t *priv = dev->driver_data;
+ return sprintf(buf, "%u\n", priv->autopurge);
+}
+
+
+static DEVICE_ATTR(autopurge, 0644, vmlogrdr_autopurge_show,
+ vmlogrdr_autopurge_store);
+
+
+static ssize_t
+vmlogrdr_purge_store(struct device * dev, const char * buf, size_t count) {
+
+ char cp_command[80];
+ char cp_response[80];
+ struct vmlogrdr_priv_t *priv = dev->driver_data;
+
+ if (buf[0] != '1')
+ return -EINVAL;
+
+ memset(cp_command, 0x00, sizeof(cp_command));
+ memset(cp_response, 0x00, sizeof(cp_response));
+
+ /*
+ * The recording command needs to be called with option QID
+ * for guests that have previlege classes A or B.
+ * Other guests will not recognize the command and we have to
+ * issue the same command without the QID parameter.
+ */
+
+ if (recording_class_AB)
+ snprintf(cp_command, sizeof(cp_command),
+ "RECORDING %s PURGE QID * ",
+ priv->recording_name);
+ else
+ snprintf(cp_command, sizeof(cp_command),
+ "RECORDING %s PURGE ",
+ priv->recording_name);
+
+ printk (KERN_DEBUG "vmlogrdr: recording command: %s\n", cp_command);
+ cpcmd(cp_command, cp_response, sizeof(cp_response));
+ printk (KERN_DEBUG "vmlogrdr: recording response: %s",
+ cp_response);
+
+ return count;
+}
+
+
+static DEVICE_ATTR(purge, 0200, NULL, vmlogrdr_purge_store);
+
+
+static ssize_t
+vmlogrdr_autorecording_store(struct device *dev, const char *buf,
+ size_t count) {
+ struct vmlogrdr_priv_t *priv = dev->driver_data;
+ ssize_t ret = count;
+
+ switch (buf[0]) {
+ case '0':
+ priv->autorecording=0;
+ break;
+ case '1':
+ priv->autorecording=1;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+
+static ssize_t
+vmlogrdr_autorecording_show(struct device *dev, char *buf) {
+ struct vmlogrdr_priv_t *priv = dev->driver_data;
+ return sprintf(buf, "%u\n", priv->autorecording);
+}
+
+
+static DEVICE_ATTR(autorecording, 0644, vmlogrdr_autorecording_show,
+ vmlogrdr_autorecording_store);
+
+
+static ssize_t
+vmlogrdr_recording_store(struct device * dev, const char * buf, size_t count) {
+
+ struct vmlogrdr_priv_t *priv = dev->driver_data;
+ ssize_t ret;
+
+ switch (buf[0]) {
+ case '0':
+ ret = vmlogrdr_recording(priv,0,0);
+ break;
+ case '1':
+ ret = vmlogrdr_recording(priv,1,0);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ if (ret)
+ return ret;
+ else
+ return count;
+
+}
+
+
+static DEVICE_ATTR(recording, 0200, NULL, vmlogrdr_recording_store);
+
+
+static ssize_t
+vmlogrdr_recording_status_show(struct device_driver *driver, char *buf) {
+
+ char cp_command[] = "QUERY RECORDING ";
+ int len;
+
+ cpcmd(cp_command, buf, 4096);
+ len = strlen(buf);
+ return len;
+}
+
+
+static DRIVER_ATTR(recording_status, 0444, vmlogrdr_recording_status_show,
+ NULL);
+
+static struct attribute *vmlogrdr_attrs[] = {
+ &dev_attr_autopurge.attr,
+ &dev_attr_purge.attr,
+ &dev_attr_autorecording.attr,
+ &dev_attr_recording.attr,
+ NULL,
+};
+
+static struct attribute_group vmlogrdr_attr_group = {
+ .attrs = vmlogrdr_attrs,
+};
+
+static struct class_simple *vmlogrdr_class;
+static struct device_driver vmlogrdr_driver = {
+ .name = "vmlogrdr",
+ .bus = &iucv_bus,
+};
+
+
+static int
+vmlogrdr_register_driver(void) {
+ int ret;
+
+ ret = driver_register(&vmlogrdr_driver);
+ if (ret) {
+ printk(KERN_ERR "vmlogrdr: failed to register driver.\n");
+ return ret;
+ }
+
+ ret = driver_create_file(&vmlogrdr_driver,
+ &driver_attr_recording_status);
+ if (ret) {
+ printk(KERN_ERR "vmlogrdr: failed to add driver attribute.\n");
+ goto unregdriver;
+ }
+
+ vmlogrdr_class = class_simple_create(THIS_MODULE, "vmlogrdr");
+ if (IS_ERR(vmlogrdr_class)) {
+ printk(KERN_ERR "vmlogrdr: failed to create class.\n");
+ ret=PTR_ERR(vmlogrdr_class);
+ vmlogrdr_class=NULL;
+ goto unregattr;
+ }
+ return 0;
+
+unregattr:
+ driver_remove_file(&vmlogrdr_driver, &driver_attr_recording_status);
+unregdriver:
+ driver_unregister(&vmlogrdr_driver);
+ return ret;
+}
+
+
+static void
+vmlogrdr_unregister_driver(void) {
+ class_simple_destroy(vmlogrdr_class);
+ vmlogrdr_class = NULL;
+ driver_remove_file(&vmlogrdr_driver, &driver_attr_recording_status);
+ driver_unregister(&vmlogrdr_driver);
+ return;
+}
+
+
+static int
+vmlogrdr_register_device(struct vmlogrdr_priv_t *priv) {
+ struct device *dev;
+ int ret;
+
+ dev = kmalloc(sizeof(struct device), GFP_KERNEL);
+ if (dev) {
+ memset(dev, 0, sizeof(struct device));
+ snprintf(dev->bus_id, BUS_ID_SIZE, "%s",
+ priv->internal_name);
+ dev->bus = &iucv_bus;
+ dev->parent = iucv_root;
+ dev->driver = &vmlogrdr_driver;
+ /*
+ * The release function could be called after the
+ * module has been unloaded. It's _only_ task is to
+ * free the struct. Therefore, we specify kfree()
+ * directly here. (Probably a little bit obfuscating
+ * but legitime ...).
+ */
+ dev->release = (void (*)(struct device *))kfree;
+ } else
+ return -ENOMEM;
+ ret = device_register(dev);
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_group(&dev->kobj, &vmlogrdr_attr_group);
+ if (ret) {
+ device_unregister(dev);
+ return ret;
+ }
+ priv->class_device = class_simple_device_add(
+ vmlogrdr_class,
+ MKDEV(vmlogrdr_major, priv->minor_num),
+ dev,
+ "%s", dev->bus_id );
+ if (IS_ERR(priv->class_device)) {
+ ret = PTR_ERR(priv->class_device);
+ priv->class_device=NULL;
+ sysfs_remove_group(&dev->kobj, &vmlogrdr_attr_group);
+ device_unregister(dev);
+ return ret;
+ }
+ dev->driver_data = priv;
+ priv->device = dev;
+ return 0;
+}
+
+
+static int
+vmlogrdr_unregister_device(struct vmlogrdr_priv_t *priv ) {
+ class_simple_device_remove(MKDEV(vmlogrdr_major, priv->minor_num));
+ if (priv->device != NULL) {
+ sysfs_remove_group(&priv->device->kobj, &vmlogrdr_attr_group);
+ device_unregister(priv->device);
+ priv->device=NULL;
+ }
+ return 0;
+}
+
+
+static int
+vmlogrdr_register_cdev(dev_t dev) {
+ int rc = 0;
+ vmlogrdr_cdev = cdev_alloc();
+ if (!vmlogrdr_cdev) {
+ return -ENOMEM;
+ }
+ vmlogrdr_cdev->owner = THIS_MODULE;
+ vmlogrdr_cdev->ops = &vmlogrdr_fops;
+ vmlogrdr_cdev->dev = dev;
+ rc = cdev_add(vmlogrdr_cdev, vmlogrdr_cdev->dev, MAXMINOR);
+ if (!rc)
+ return 0;
+
+ // cleanup: cdev is not fully registered, no cdev_del here!
+ kobject_put(&vmlogrdr_cdev->kobj);
+ vmlogrdr_cdev=NULL;
+ return rc;
+}
+
+
+static void
+vmlogrdr_cleanup(void) {
+ int i;
+ if (vmlogrdr_cdev) {
+ cdev_del(vmlogrdr_cdev);
+ vmlogrdr_cdev=NULL;
+ }
+ for (i=0; i < MAXMINOR; ++i ) {
+ vmlogrdr_unregister_device(&sys_ser[i]);
+ free_page((unsigned long)sys_ser[i].buffer);
+ }
+ vmlogrdr_unregister_driver();
+ if (vmlogrdr_major) {
+ unregister_chrdev_region(MKDEV(vmlogrdr_major, 0), MAXMINOR);
+ vmlogrdr_major=0;
+ }
+}
+
+
+static int
+vmlogrdr_init(void)
+{
+ int rc;
+ int i;
+ dev_t dev;
+
+ if (! MACHINE_IS_VM) {
+ printk (KERN_ERR "vmlogrdr: not running under VM, "
+ "driver not loaded.\n");
+ return -ENODEV;
+ }
+
+ recording_class_AB = vmlogrdr_get_recording_class_AB();
+
+ rc = alloc_chrdev_region(&dev, 0, MAXMINOR, "vmlogrdr");
+ if (rc)
+ return rc;
+ vmlogrdr_major = MAJOR(dev);
+
+ rc=vmlogrdr_register_driver();
+ if (rc)
+ goto cleanup;
+
+ for (i=0; i < MAXMINOR; ++i ) {
+ sys_ser[i].buffer = (char *) get_zeroed_page(GFP_KERNEL);
+ if (!sys_ser[i].buffer) {
+ rc = ENOMEM;
+ break;
+ }
+ sys_ser[i].current_position = sys_ser[i].buffer;
+ rc=vmlogrdr_register_device(&sys_ser[i]);
+ if (rc)
+ break;
+ }
+ if (rc)
+ goto cleanup;
+
+ rc = vmlogrdr_register_cdev(dev);
+ if (rc)
+ goto cleanup;
+ printk (KERN_INFO "vmlogrdr: driver loaded\n");
+ return 0;
+
+cleanup:
+ vmlogrdr_cleanup();
+ printk (KERN_ERR "vmlogrdr: driver not loaded.\n");
+ return rc;
+}
+
+
+static void
+vmlogrdr_exit(void)
+{
+ vmlogrdr_cleanup();
+ printk (KERN_INFO "vmlogrdr: driver unloaded\n");
+ return;
+}
+
+
+module_init(vmlogrdr_init);
+module_exit(vmlogrdr_exit);
diff --git a/drivers/s390/char/vmwatchdog.c b/drivers/s390/char/vmwatchdog.c
new file mode 100644
index 000000000000..22cf4fec8da9
--- /dev/null
+++ b/drivers/s390/char/vmwatchdog.c
@@ -0,0 +1,292 @@
+/*
+ * Watchdog implementation based on z/VM Watchdog Timer API
+ *
+ * The user space watchdog daemon can use this driver as
+ * /dev/vmwatchdog to have z/VM execute the specified CP
+ * command when the timeout expires. The default command is
+ * "IPL", which which cause an immediate reboot.
+ */
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/watchdog.h>
+
+#include <asm/ebcdic.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MAX_CMDLEN 240
+#define MIN_INTERVAL 15
+static char vmwdt_cmd[MAX_CMDLEN] = "IPL";
+static int vmwdt_conceal;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int vmwdt_nowayout = 1;
+#else
+static int vmwdt_nowayout = 0;
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>");
+MODULE_DESCRIPTION("z/VM Watchdog Timer");
+module_param_string(cmd, vmwdt_cmd, MAX_CMDLEN, 0644);
+MODULE_PARM_DESC(cmd, "CP command that is run when the watchdog triggers");
+module_param_named(conceal, vmwdt_conceal, bool, 0644);
+MODULE_PARM_DESC(conceal, "Enable the CONCEAL CP option while the watchdog "
+ " is active");
+module_param_named(nowayout, vmwdt_nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"
+ " (default=CONFIG_WATCHDOG_NOWAYOUT)");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+static unsigned int vmwdt_interval = 60;
+static unsigned long vmwdt_is_open;
+static int vmwdt_expect_close;
+
+enum vmwdt_func {
+ /* function codes */
+ wdt_init = 0,
+ wdt_change = 1,
+ wdt_cancel = 2,
+ /* flags */
+ wdt_conceal = 0x80000000,
+};
+
+static int __diag288(enum vmwdt_func func, unsigned int timeout,
+ char *cmd, size_t len)
+{
+ register unsigned long __func asm("2");
+ register unsigned long __timeout asm("3");
+ register unsigned long __cmdp asm("4");
+ register unsigned long __cmdl asm("5");
+ int err;
+
+ __func = func;
+ __timeout = timeout;
+ __cmdp = virt_to_phys(cmd);
+ __cmdl = len;
+ err = 0;
+ asm volatile (
+#ifdef __s390x__
+ "diag %2,%4,0x288\n"
+ "1: \n"
+ ".section .fixup,\"ax\"\n"
+ "2: lghi %0,%1\n"
+ " jg 1b\n"
+ ".previous\n"
+ ".section __ex_table,\"a\"\n"
+ " .align 8\n"
+ " .quad 1b,2b\n"
+ ".previous\n"
+#else
+ "diag %2,%4,0x288\n"
+ "1: \n"
+ ".section .fixup,\"ax\"\n"
+ "2: lhi %0,%1\n"
+ " bras 1,3f\n"
+ " .long 1b\n"
+ "3: l 1,0(1)\n"
+ " br 1\n"
+ ".previous\n"
+ ".section __ex_table,\"a\"\n"
+ " .align 4\n"
+ " .long 1b,2b\n"
+ ".previous\n"
+#endif
+ : "+&d"(err)
+ : "i"(-EINVAL), "d"(__func), "d"(__timeout),
+ "d"(__cmdp), "d"(__cmdl)
+ : "1", "cc");
+ return err;
+}
+
+static int vmwdt_keepalive(void)
+{
+ /* we allocate new memory every time to avoid having
+ * to track the state. static allocation is not an
+ * option since that might not be contiguous in real
+ * storage in case of a modular build */
+ static char *ebc_cmd;
+ size_t len;
+ int ret;
+ unsigned int func;
+
+ ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL);
+ if (!ebc_cmd)
+ return -ENOMEM;
+
+ len = strlcpy(ebc_cmd, vmwdt_cmd, MAX_CMDLEN);
+ ASCEBC(ebc_cmd, MAX_CMDLEN);
+ EBC_TOUPPER(ebc_cmd, MAX_CMDLEN);
+
+ func = vmwdt_conceal ? (wdt_init | wdt_conceal) : wdt_init;
+ ret = __diag288(func, vmwdt_interval, ebc_cmd, len);
+ kfree(ebc_cmd);
+
+ if (ret) {
+ printk(KERN_WARNING "%s: problem setting interval %d, "
+ "cmd %s\n", __FUNCTION__, vmwdt_interval,
+ vmwdt_cmd);
+ }
+ return ret;
+}
+
+static int vmwdt_disable(void)
+{
+ int ret = __diag288(wdt_cancel, 0, "", 0);
+ if (ret) {
+ printk(KERN_WARNING "%s: problem disabling watchdog\n",
+ __FUNCTION__);
+ }
+ return ret;
+}
+
+static int __init vmwdt_probe(void)
+{
+ /* there is no real way to see if the watchdog is supported,
+ * so we try initializing it with a NOP command ("BEGIN")
+ * that won't cause any harm even if the following disable
+ * fails for some reason */
+ static char __initdata ebc_begin[] = {
+ 194, 197, 199, 201, 213
+ };
+ if (__diag288(wdt_init, 15, ebc_begin, sizeof(ebc_begin)) != 0) {
+ printk(KERN_INFO "z/VM watchdog not available\n");
+ return -EINVAL;
+ }
+ return vmwdt_disable();
+}
+
+static int vmwdt_open(struct inode *i, struct file *f)
+{
+ int ret;
+ if (test_and_set_bit(0, &vmwdt_is_open))
+ return -EBUSY;
+ ret = vmwdt_keepalive();
+ if (ret)
+ clear_bit(0, &vmwdt_is_open);
+ return ret ? ret : nonseekable_open(i, f);
+}
+
+static int vmwdt_close(struct inode *i, struct file *f)
+{
+ if (vmwdt_expect_close == 42)
+ vmwdt_disable();
+ vmwdt_expect_close = 0;
+ clear_bit(0, &vmwdt_is_open);
+ return 0;
+}
+
+static struct watchdog_info vmwdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "z/VM Watchdog Timer",
+};
+
+static int vmwdt_ioctl(struct inode *i, struct file *f,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user((void __user *)arg, &vmwdt_info,
+ sizeof(vmwdt_info)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, (int *)arg);
+ case WDIOC_GETTEMP:
+ return -EINVAL;
+ case WDIOC_SETOPTIONS:
+ {
+ int options, ret;
+ if (get_user(options, (int __user *)arg))
+ return -EFAULT;
+ ret = -EINVAL;
+ if (options & WDIOS_DISABLECARD) {
+ ret = vmwdt_disable();
+ if (ret)
+ return ret;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ ret = vmwdt_keepalive();
+ }
+ return ret;
+ }
+ case WDIOC_GETTIMEOUT:
+ return put_user(vmwdt_interval, (int __user *)arg);
+ case WDIOC_SETTIMEOUT:
+ {
+ int interval;
+ if (get_user(interval, (int __user *)arg))
+ return -EFAULT;
+ if (interval < MIN_INTERVAL)
+ return -EINVAL;
+ vmwdt_interval = interval;
+ }
+ return vmwdt_keepalive();
+ case WDIOC_KEEPALIVE:
+ return vmwdt_keepalive();
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t vmwdt_write(struct file *f, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if(count) {
+ if (!vmwdt_nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ vmwdt_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf+i))
+ return -EFAULT;
+ if (c == 'V')
+ vmwdt_expect_close = 42;
+ }
+ }
+ /* someone wrote to us, we should restart timer */
+ vmwdt_keepalive();
+ }
+ return count;
+}
+
+static struct file_operations vmwdt_fops = {
+ .open = &vmwdt_open,
+ .release = &vmwdt_close,
+ .ioctl = &vmwdt_ioctl,
+ .write = &vmwdt_write,
+ .owner = THIS_MODULE,
+};
+
+static struct miscdevice vmwdt_dev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &vmwdt_fops,
+};
+
+static int __init vmwdt_init(void)
+{
+ int ret;
+
+ ret = vmwdt_probe();
+ if (ret)
+ return ret;
+ return misc_register(&vmwdt_dev);
+}
+module_init(vmwdt_init);
+
+static void __exit vmwdt_exit(void)
+{
+ WARN_ON(misc_deregister(&vmwdt_dev) != 0);
+}
+module_exit(vmwdt_exit);