Linux-2.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!
This commit is contained in:
Linus Torvalds
2005-04-16 15:20:36 -07:00
commit 1da177e4c3
17291 changed files with 6718755 additions and 0 deletions

View File

@@ -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

1192
drivers/s390/char/con3215.c Normal file

File diff suppressed because it is too large Load Diff

638
drivers/s390/char/con3270.c Normal file
View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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

373
drivers/s390/char/fs3270.c Normal file
View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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");

1335
drivers/s390/char/raw3270.c Normal file

File diff suppressed because it is too large Load Diff

274
drivers/s390/char/raw3270.h Normal file
View File

@@ -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);
}

915
drivers/s390/char/sclp.c Normal file
View File

@@ -0,0 +1,915 @@
/*
* drivers/s390/char/sclp.c
* core function to access sclp interface
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/reboot.h>
#include <linux/jiffies.h>
#include <asm/types.h>
#include <asm/s390_ext.h>
#include "sclp.h"
#define SCLP_HEADER "sclp: "
/* Structure for register_early_external_interrupt. */
static ext_int_info_t ext_int_info_hwc;
/* Lock to protect internal data consistency. */
static DEFINE_SPINLOCK(sclp_lock);
/* Mask of events that we can receive from the sclp interface. */
static sccb_mask_t sclp_receive_mask;
/* Mask of events that we can send to the sclp interface. */
static sccb_mask_t sclp_send_mask;
/* List of registered event listeners and senders. */
static struct list_head sclp_reg_list;
/* List of queued requests. */
static struct list_head sclp_req_queue;
/* Data for read and and init requests. */
static struct sclp_req sclp_read_req;
static struct sclp_req sclp_init_req;
static char sclp_read_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
static char sclp_init_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
/* Timer for request retries. */
static struct timer_list sclp_request_timer;
/* Internal state: is the driver initialized? */
static volatile enum sclp_init_state_t {
sclp_init_state_uninitialized,
sclp_init_state_initializing,
sclp_init_state_initialized
} sclp_init_state = sclp_init_state_uninitialized;
/* Internal state: is a request active at the sclp? */
static volatile enum sclp_running_state_t {
sclp_running_state_idle,
sclp_running_state_running
} sclp_running_state = sclp_running_state_idle;
/* Internal state: is a read request pending? */
static volatile enum sclp_reading_state_t {
sclp_reading_state_idle,
sclp_reading_state_reading
} sclp_reading_state = sclp_reading_state_idle;
/* Internal state: is the driver currently serving requests? */
static volatile enum sclp_activation_state_t {
sclp_activation_state_active,
sclp_activation_state_deactivating,
sclp_activation_state_inactive,
sclp_activation_state_activating
} sclp_activation_state = sclp_activation_state_active;
/* Internal state: is an init mask request pending? */
static volatile enum sclp_mask_state_t {
sclp_mask_state_idle,
sclp_mask_state_initializing
} sclp_mask_state = sclp_mask_state_idle;
/* Maximum retry counts */
#define SCLP_INIT_RETRY 3
#define SCLP_MASK_RETRY 3
#define SCLP_REQUEST_RETRY 3
/* Timeout intervals in seconds.*/
#define SCLP_BUSY_INTERVAL 2
#define SCLP_RETRY_INTERVAL 5
static void sclp_process_queue(void);
static int sclp_init_mask(int calculate);
static int sclp_init(void);
/* Perform service call. Return 0 on success, non-zero otherwise. */
static int
service_call(sclp_cmdw_t command, void *sccb)
{
int cc;
__asm__ __volatile__(
" .insn rre,0xb2200000,%1,%2\n" /* servc %1,%2 */
" ipm %0\n"
" srl %0,28"
: "=&d" (cc)
: "d" (command), "a" (__pa(sccb))
: "cc", "memory" );
if (cc == 3)
return -EIO;
if (cc == 2)
return -EBUSY;
return 0;
}
/* Request timeout handler. Restart the request queue. If DATA is non-zero,
* force restart of running request. */
static void
sclp_request_timeout(unsigned long data)
{
unsigned long flags;
if (data) {
spin_lock_irqsave(&sclp_lock, flags);
sclp_running_state = sclp_running_state_idle;
spin_unlock_irqrestore(&sclp_lock, flags);
}
sclp_process_queue();
}
/* Set up request retry timer. Called while sclp_lock is locked. */
static inline void
__sclp_set_request_timer(unsigned long time, void (*function)(unsigned long),
unsigned long data)
{
del_timer(&sclp_request_timer);
sclp_request_timer.function = function;
sclp_request_timer.data = data;
sclp_request_timer.expires = jiffies + time;
add_timer(&sclp_request_timer);
}
/* Try to start a request. Return zero if the request was successfully
* started or if it will be started at a later time. Return non-zero otherwise.
* Called while sclp_lock is locked. */
static int
__sclp_start_request(struct sclp_req *req)
{
int rc;
if (sclp_running_state != sclp_running_state_idle)
return 0;
del_timer(&sclp_request_timer);
if (req->start_count <= SCLP_REQUEST_RETRY) {
rc = service_call(req->command, req->sccb);
req->start_count++;
} else
rc = -EIO;
if (rc == 0) {
/* Sucessfully started request */
req->status = SCLP_REQ_RUNNING;
sclp_running_state = sclp_running_state_running;
__sclp_set_request_timer(SCLP_RETRY_INTERVAL * HZ,
sclp_request_timeout, 1);
return 0;
} else if (rc == -EBUSY) {
/* Try again later */
__sclp_set_request_timer(SCLP_BUSY_INTERVAL * HZ,
sclp_request_timeout, 0);
return 0;
}
/* Request failed */
req->status = SCLP_REQ_FAILED;
return rc;
}
/* Try to start queued requests. */
static void
sclp_process_queue(void)
{
struct sclp_req *req;
int rc;
unsigned long flags;
spin_lock_irqsave(&sclp_lock, flags);
if (sclp_running_state != sclp_running_state_idle) {
spin_unlock_irqrestore(&sclp_lock, flags);
return;
}
del_timer(&sclp_request_timer);
while (!list_empty(&sclp_req_queue)) {
req = list_entry(sclp_req_queue.next, struct sclp_req, list);
rc = __sclp_start_request(req);
if (rc == 0)
break;
/* Request failed. */
list_del(&req->list);
if (req->callback) {
spin_unlock_irqrestore(&sclp_lock, flags);
req->callback(req, req->callback_data);
spin_lock_irqsave(&sclp_lock, flags);
}
}
spin_unlock_irqrestore(&sclp_lock, flags);
}
/* Queue a new request. Return zero on success, non-zero otherwise. */
int
sclp_add_request(struct sclp_req *req)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
if ((sclp_init_state != sclp_init_state_initialized ||
sclp_activation_state != sclp_activation_state_active) &&
req != &sclp_init_req) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EIO;
}
req->status = SCLP_REQ_QUEUED;
req->start_count = 0;
list_add_tail(&req->list, &sclp_req_queue);
rc = 0;
/* Start if request is first in list */
if (req->list.prev == &sclp_req_queue) {
rc = __sclp_start_request(req);
if (rc)
list_del(&req->list);
}
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
EXPORT_SYMBOL(sclp_add_request);
/* Dispatch events found in request buffer to registered listeners. Return 0
* if all events were dispatched, non-zero otherwise. */
static int
sclp_dispatch_evbufs(struct sccb_header *sccb)
{
unsigned long flags;
struct evbuf_header *evbuf;
struct list_head *l;
struct sclp_register *reg;
int offset;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
rc = 0;
for (offset = sizeof(struct sccb_header); offset < sccb->length;
offset += evbuf->length) {
/* Search for event handler */
evbuf = (struct evbuf_header *) ((addr_t) sccb + offset);
reg = NULL;
list_for_each(l, &sclp_reg_list) {
reg = list_entry(l, struct sclp_register, list);
if (reg->receive_mask & (1 << (32 - evbuf->type)))
break;
else
reg = NULL;
}
if (reg && reg->receiver_fn) {
spin_unlock_irqrestore(&sclp_lock, flags);
reg->receiver_fn(evbuf);
spin_lock_irqsave(&sclp_lock, flags);
} else if (reg == NULL)
rc = -ENOSYS;
}
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Read event data request callback. */
static void
sclp_read_cb(struct sclp_req *req, void *data)
{
unsigned long flags;
struct sccb_header *sccb;
sccb = (struct sccb_header *) req->sccb;
if (req->status == SCLP_REQ_DONE && (sccb->response_code == 0x20 ||
sccb->response_code == 0x220))
sclp_dispatch_evbufs(sccb);
spin_lock_irqsave(&sclp_lock, flags);
sclp_reading_state = sclp_reading_state_idle;
spin_unlock_irqrestore(&sclp_lock, flags);
}
/* Prepare read event data request. Called while sclp_lock is locked. */
static inline void
__sclp_make_read_req(void)
{
struct sccb_header *sccb;
sccb = (struct sccb_header *) sclp_read_sccb;
clear_page(sccb);
memset(&sclp_read_req, 0, sizeof(struct sclp_req));
sclp_read_req.command = SCLP_CMDW_READDATA;
sclp_read_req.status = SCLP_REQ_QUEUED;
sclp_read_req.start_count = 0;
sclp_read_req.callback = sclp_read_cb;
sclp_read_req.sccb = sccb;
sccb->length = PAGE_SIZE;
sccb->function_code = 0;
sccb->control_mask[2] = 0x80;
}
/* Search request list for request with matching sccb. Return request if found,
* NULL otherwise. Called while sclp_lock is locked. */
static inline struct sclp_req *
__sclp_find_req(u32 sccb)
{
struct list_head *l;
struct sclp_req *req;
list_for_each(l, &sclp_req_queue) {
req = list_entry(l, struct sclp_req, list);
if (sccb == (u32) (addr_t) req->sccb)
return req;
}
return NULL;
}
/* Handler for external interruption. Perform request post-processing.
* Prepare read event data request if necessary. Start processing of next
* request on queue. */
static void
sclp_interrupt_handler(struct pt_regs *regs, __u16 code)
{
struct sclp_req *req;
u32 finished_sccb;
u32 evbuf_pending;
spin_lock(&sclp_lock);
finished_sccb = S390_lowcore.ext_params & 0xfffffff8;
evbuf_pending = S390_lowcore.ext_params & 0x3;
if (finished_sccb) {
req = __sclp_find_req(finished_sccb);
if (req) {
/* Request post-processing */
list_del(&req->list);
req->status = SCLP_REQ_DONE;
if (req->callback) {
spin_unlock(&sclp_lock);
req->callback(req, req->callback_data);
spin_lock(&sclp_lock);
}
}
sclp_running_state = sclp_running_state_idle;
}
if (evbuf_pending && sclp_receive_mask != 0 &&
sclp_reading_state == sclp_reading_state_idle &&
sclp_activation_state == sclp_activation_state_active ) {
sclp_reading_state = sclp_reading_state_reading;
__sclp_make_read_req();
/* Add request to head of queue */
list_add(&sclp_read_req.list, &sclp_req_queue);
}
spin_unlock(&sclp_lock);
sclp_process_queue();
}
/* Return current Time-Of-Day clock. */
static inline u64
sclp_get_clock(void)
{
u64 result;
asm volatile ("STCK 0(%1)" : "=m" (result) : "a" (&(result)) : "cc");
return result;
}
/* Convert interval in jiffies to TOD ticks. */
static inline u64
sclp_tod_from_jiffies(unsigned long jiffies)
{
return (u64) (jiffies / HZ) << 32;
}
/* Wait until a currently running request finished. Note: while this function
* is running, no timers are served on the calling CPU. */
void
sclp_sync_wait(void)
{
unsigned long psw_mask;
unsigned long cr0, cr0_sync;
u64 timeout;
/* We'll be disabling timer interrupts, so we need a custom timeout
* mechanism */
timeout = 0;
if (timer_pending(&sclp_request_timer)) {
/* Get timeout TOD value */
timeout = sclp_get_clock() +
sclp_tod_from_jiffies(sclp_request_timer.expires -
jiffies);
}
/* Prevent bottom half from executing once we force interrupts open */
local_bh_disable();
/* Enable service-signal interruption, disable timer interrupts */
__ctl_store(cr0, 0, 0);
cr0_sync = cr0;
cr0_sync |= 0x00000200;
cr0_sync &= 0xFFFFF3AC;
__ctl_load(cr0_sync, 0, 0);
asm volatile ("STOSM 0(%1),0x01"
: "=m" (psw_mask) : "a" (&psw_mask) : "memory");
/* Loop until driver state indicates finished request */
while (sclp_running_state != sclp_running_state_idle) {
/* Check for expired request timer */
if (timer_pending(&sclp_request_timer) &&
sclp_get_clock() > timeout &&
del_timer(&sclp_request_timer))
sclp_request_timer.function(sclp_request_timer.data);
barrier();
cpu_relax();
}
/* Restore interrupt settings */
asm volatile ("SSM 0(%0)"
: : "a" (&psw_mask) : "memory");
__ctl_load(cr0, 0, 0);
__local_bh_enable();
}
EXPORT_SYMBOL(sclp_sync_wait);
/* Dispatch changes in send and receive mask to registered listeners. */
static inline void
sclp_dispatch_state_change(void)
{
struct list_head *l;
struct sclp_register *reg;
unsigned long flags;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
do {
spin_lock_irqsave(&sclp_lock, flags);
reg = NULL;
list_for_each(l, &sclp_reg_list) {
reg = list_entry(l, struct sclp_register, list);
receive_mask = reg->receive_mask & sclp_receive_mask;
send_mask = reg->send_mask & sclp_send_mask;
if (reg->sclp_receive_mask != receive_mask ||
reg->sclp_send_mask != send_mask) {
reg->sclp_receive_mask = receive_mask;
reg->sclp_send_mask = send_mask;
break;
} else
reg = NULL;
}
spin_unlock_irqrestore(&sclp_lock, flags);
if (reg && reg->state_change_fn)
reg->state_change_fn(reg);
} while (reg);
}
struct sclp_statechangebuf {
struct evbuf_header header;
u8 validity_sclp_active_facility_mask : 1;
u8 validity_sclp_receive_mask : 1;
u8 validity_sclp_send_mask : 1;
u8 validity_read_data_function_mask : 1;
u16 _zeros : 12;
u16 mask_length;
u64 sclp_active_facility_mask;
sccb_mask_t sclp_receive_mask;
sccb_mask_t sclp_send_mask;
u32 read_data_function_mask;
} __attribute__((packed));
/* State change event callback. Inform listeners of changes. */
static void
sclp_state_change_cb(struct evbuf_header *evbuf)
{
unsigned long flags;
struct sclp_statechangebuf *scbuf;
scbuf = (struct sclp_statechangebuf *) evbuf;
if (scbuf->mask_length != sizeof(sccb_mask_t))
return;
spin_lock_irqsave(&sclp_lock, flags);
if (scbuf->validity_sclp_receive_mask)
sclp_receive_mask = scbuf->sclp_receive_mask;
if (scbuf->validity_sclp_send_mask)
sclp_send_mask = scbuf->sclp_send_mask;
spin_unlock_irqrestore(&sclp_lock, flags);
sclp_dispatch_state_change();
}
static struct sclp_register sclp_state_change_event = {
.receive_mask = EvTyp_StateChange_Mask,
.receiver_fn = sclp_state_change_cb
};
/* Calculate receive and send mask of currently registered listeners.
* Called while sclp_lock is locked. */
static inline void
__sclp_get_mask(sccb_mask_t *receive_mask, sccb_mask_t *send_mask)
{
struct list_head *l;
struct sclp_register *t;
*receive_mask = 0;
*send_mask = 0;
list_for_each(l, &sclp_reg_list) {
t = list_entry(l, struct sclp_register, list);
*receive_mask |= t->receive_mask;
*send_mask |= t->send_mask;
}
}
/* Register event listener. Return 0 on success, non-zero otherwise. */
int
sclp_register(struct sclp_register *reg)
{
unsigned long flags;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
int rc;
rc = sclp_init();
if (rc)
return rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Check event mask for collisions */
__sclp_get_mask(&receive_mask, &send_mask);
if (reg->receive_mask & receive_mask || reg->send_mask & send_mask) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EBUSY;
}
/* Trigger initial state change callback */
reg->sclp_receive_mask = 0;
reg->sclp_send_mask = 0;
list_add(&reg->list, &sclp_reg_list);
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_init_mask(1);
if (rc) {
spin_lock_irqsave(&sclp_lock, flags);
list_del(&reg->list);
spin_unlock_irqrestore(&sclp_lock, flags);
}
return rc;
}
EXPORT_SYMBOL(sclp_register);
/* Unregister event listener. */
void
sclp_unregister(struct sclp_register *reg)
{
unsigned long flags;
spin_lock_irqsave(&sclp_lock, flags);
list_del(&reg->list);
spin_unlock_irqrestore(&sclp_lock, flags);
sclp_init_mask(1);
}
EXPORT_SYMBOL(sclp_unregister);
/* Remove event buffers which are marked processed. Return the number of
* remaining event buffers. */
int
sclp_remove_processed(struct sccb_header *sccb)
{
struct evbuf_header *evbuf;
int unprocessed;
u16 remaining;
evbuf = (struct evbuf_header *) (sccb + 1);
unprocessed = 0;
remaining = sccb->length - sizeof(struct sccb_header);
while (remaining > 0) {
remaining -= evbuf->length;
if (evbuf->flags & 0x80) {
sccb->length -= evbuf->length;
memcpy(evbuf, (void *) ((addr_t) evbuf + evbuf->length),
remaining);
} else {
unprocessed++;
evbuf = (struct evbuf_header *)
((addr_t) evbuf + evbuf->length);
}
}
return unprocessed;
}
EXPORT_SYMBOL(sclp_remove_processed);
struct init_sccb {
struct sccb_header header;
u16 _reserved;
u16 mask_length;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
sccb_mask_t sclp_send_mask;
sccb_mask_t sclp_receive_mask;
} __attribute__((packed));
/* Prepare init mask request. Called while sclp_lock is locked. */
static inline void
__sclp_make_init_req(u32 receive_mask, u32 send_mask)
{
struct init_sccb *sccb;
sccb = (struct init_sccb *) sclp_init_sccb;
clear_page(sccb);
memset(&sclp_init_req, 0, sizeof(struct sclp_req));
sclp_init_req.command = SCLP_CMDW_WRITEMASK;
sclp_init_req.status = SCLP_REQ_FILLED;
sclp_init_req.start_count = 0;
sclp_init_req.callback = NULL;
sclp_init_req.callback_data = NULL;
sclp_init_req.sccb = sccb;
sccb->header.length = sizeof(struct init_sccb);
sccb->mask_length = sizeof(sccb_mask_t);
sccb->receive_mask = receive_mask;
sccb->send_mask = send_mask;
sccb->sclp_receive_mask = 0;
sccb->sclp_send_mask = 0;
}
/* Start init mask request. If calculate is non-zero, calculate the mask as
* requested by registered listeners. Use zero mask otherwise. Return 0 on
* success, non-zero otherwise. */
static int
sclp_init_mask(int calculate)
{
unsigned long flags;
struct init_sccb *sccb = (struct init_sccb *) sclp_init_sccb;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
int retry;
int rc;
unsigned long wait;
spin_lock_irqsave(&sclp_lock, flags);
/* Check if interface is in appropriate state */
if (sclp_mask_state != sclp_mask_state_idle) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EBUSY;
}
if (sclp_activation_state == sclp_activation_state_inactive) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EINVAL;
}
sclp_mask_state = sclp_mask_state_initializing;
/* Determine mask */
if (calculate)
__sclp_get_mask(&receive_mask, &send_mask);
else {
receive_mask = 0;
send_mask = 0;
}
rc = -EIO;
for (retry = 0; retry <= SCLP_MASK_RETRY; retry++) {
/* Prepare request */
__sclp_make_init_req(receive_mask, send_mask);
spin_unlock_irqrestore(&sclp_lock, flags);
if (sclp_add_request(&sclp_init_req)) {
/* Try again later */
wait = jiffies + SCLP_BUSY_INTERVAL * HZ;
while (time_before(jiffies, wait))
sclp_sync_wait();
spin_lock_irqsave(&sclp_lock, flags);
continue;
}
while (sclp_init_req.status != SCLP_REQ_DONE &&
sclp_init_req.status != SCLP_REQ_FAILED)
sclp_sync_wait();
spin_lock_irqsave(&sclp_lock, flags);
if (sclp_init_req.status == SCLP_REQ_DONE &&
sccb->header.response_code == 0x20) {
/* Successful request */
if (calculate) {
sclp_receive_mask = sccb->sclp_receive_mask;
sclp_send_mask = sccb->sclp_send_mask;
} else {
sclp_receive_mask = 0;
sclp_send_mask = 0;
}
spin_unlock_irqrestore(&sclp_lock, flags);
sclp_dispatch_state_change();
spin_lock_irqsave(&sclp_lock, flags);
rc = 0;
break;
}
}
sclp_mask_state = sclp_mask_state_idle;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Deactivate SCLP interface. On success, new requests will be rejected,
* events will no longer be dispatched. Return 0 on success, non-zero
* otherwise. */
int
sclp_deactivate(void)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Deactivate can only be called when active */
if (sclp_activation_state != sclp_activation_state_active) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EINVAL;
}
sclp_activation_state = sclp_activation_state_deactivating;
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_init_mask(0);
spin_lock_irqsave(&sclp_lock, flags);
if (rc == 0)
sclp_activation_state = sclp_activation_state_inactive;
else
sclp_activation_state = sclp_activation_state_active;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
EXPORT_SYMBOL(sclp_deactivate);
/* Reactivate SCLP interface after sclp_deactivate. On success, new
* requests will be accepted, events will be dispatched again. Return 0 on
* success, non-zero otherwise. */
int
sclp_reactivate(void)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Reactivate can only be called when inactive */
if (sclp_activation_state != sclp_activation_state_inactive) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EINVAL;
}
sclp_activation_state = sclp_activation_state_activating;
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_init_mask(1);
spin_lock_irqsave(&sclp_lock, flags);
if (rc == 0)
sclp_activation_state = sclp_activation_state_active;
else
sclp_activation_state = sclp_activation_state_inactive;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
EXPORT_SYMBOL(sclp_reactivate);
/* Handler for external interruption used during initialization. Modify
* request state to done. */
static void
sclp_check_handler(struct pt_regs *regs, __u16 code)
{
u32 finished_sccb;
finished_sccb = S390_lowcore.ext_params & 0xfffffff8;
/* Is this the interrupt we are waiting for? */
if (finished_sccb == 0)
return;
if (finished_sccb != (u32) (addr_t) sclp_init_sccb) {
printk(KERN_WARNING SCLP_HEADER "unsolicited interrupt "
"for buffer at 0x%x\n", finished_sccb);
return;
}
spin_lock(&sclp_lock);
if (sclp_running_state == sclp_running_state_running) {
sclp_init_req.status = SCLP_REQ_DONE;
sclp_running_state = sclp_running_state_idle;
}
spin_unlock(&sclp_lock);
}
/* Initial init mask request timed out. Modify request state to failed. */
static void
sclp_check_timeout(unsigned long data)
{
unsigned long flags;
spin_lock_irqsave(&sclp_lock, flags);
if (sclp_running_state == sclp_running_state_running) {
sclp_init_req.status = SCLP_REQ_FAILED;
sclp_running_state = sclp_running_state_idle;
}
spin_unlock_irqrestore(&sclp_lock, flags);
}
/* Perform a check of the SCLP interface. Return zero if the interface is
* available and there are no pending requests from a previous instance.
* Return non-zero otherwise. */
static int
sclp_check_interface(void)
{
struct init_sccb *sccb;
unsigned long flags;
int retry;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Prepare init mask command */
rc = register_early_external_interrupt(0x2401, sclp_check_handler,
&ext_int_info_hwc);
if (rc) {
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
for (retry = 0; retry <= SCLP_INIT_RETRY; retry++) {
__sclp_make_init_req(0, 0);
sccb = (struct init_sccb *) sclp_init_req.sccb;
rc = service_call(sclp_init_req.command, sccb);
if (rc == -EIO)
break;
sclp_init_req.status = SCLP_REQ_RUNNING;
sclp_running_state = sclp_running_state_running;
__sclp_set_request_timer(SCLP_RETRY_INTERVAL * HZ,
sclp_check_timeout, 0);
spin_unlock_irqrestore(&sclp_lock, flags);
/* Enable service-signal interruption - needs to happen
* with IRQs enabled. */
ctl_set_bit(0, 9);
/* Wait for signal from interrupt or timeout */
sclp_sync_wait();
/* Disable service-signal interruption - needs to happen
* with IRQs enabled. */
ctl_clear_bit(0,9);
spin_lock_irqsave(&sclp_lock, flags);
del_timer(&sclp_request_timer);
if (sclp_init_req.status == SCLP_REQ_DONE &&
sccb->header.response_code == 0x20) {
rc = 0;
break;
} else
rc = -EBUSY;
}
unregister_early_external_interrupt(0x2401, sclp_check_handler,
&ext_int_info_hwc);
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Reboot event handler. Reset send and receive mask to prevent pending SCLP
* events from interfering with rebooted system. */
static int
sclp_reboot_event(struct notifier_block *this, unsigned long event, void *ptr)
{
sclp_deactivate();
return NOTIFY_DONE;
}
static struct notifier_block sclp_reboot_notifier = {
.notifier_call = sclp_reboot_event
};
/* Initialize SCLP driver. Return zero if driver is operational, non-zero
* otherwise. */
static int
sclp_init(void)
{
unsigned long flags;
int rc;
if (!MACHINE_HAS_SCLP)
return -ENODEV;
spin_lock_irqsave(&sclp_lock, flags);
/* Check for previous or running initialization */
if (sclp_init_state != sclp_init_state_uninitialized) {
spin_unlock_irqrestore(&sclp_lock, flags);
return 0;
}
sclp_init_state = sclp_init_state_initializing;
/* Set up variables */
INIT_LIST_HEAD(&sclp_req_queue);
INIT_LIST_HEAD(&sclp_reg_list);
list_add(&sclp_state_change_event.list, &sclp_reg_list);
init_timer(&sclp_request_timer);
/* Check interface */
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_check_interface();
spin_lock_irqsave(&sclp_lock, flags);
if (rc) {
sclp_init_state = sclp_init_state_uninitialized;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Register reboot handler */
rc = register_reboot_notifier(&sclp_reboot_notifier);
if (rc) {
sclp_init_state = sclp_init_state_uninitialized;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Register interrupt handler */
rc = register_early_external_interrupt(0x2401, sclp_interrupt_handler,
&ext_int_info_hwc);
if (rc) {
unregister_reboot_notifier(&sclp_reboot_notifier);
sclp_init_state = sclp_init_state_uninitialized;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
sclp_init_state = sclp_init_state_initialized;
spin_unlock_irqrestore(&sclp_lock, flags);
/* Enable service-signal external interruption - needs to happen with
* IRQs enabled. */
ctl_set_bit(0, 9);
sclp_init_mask(1);
return 0;
}

159
drivers/s390/char/sclp.h Normal file
View File

@@ -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__ */

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

471
drivers/s390/char/sclp_rw.c Normal file
View File

@@ -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);
}

View File

@@ -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__ */

View File

@@ -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);

View File

@@ -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__ */

View File

@@ -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 */

384
drivers/s390/char/tape.h Normal file
View File

@@ -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 */

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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__ */

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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

1836
drivers/s390/char/tty3270.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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);

View File

@@ -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);