diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 00:20:36 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 00:20:36 +0200 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/char | |
download | linux-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')
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(®->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(®->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(®->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); |