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:
28
drivers/s390/char/Makefile
Normal file
28
drivers/s390/char/Makefile
Normal 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
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
638
drivers/s390/char/con3270.c
Normal 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);
|
75
drivers/s390/char/ctrlchar.c
Normal file
75
drivers/s390/char/ctrlchar.c
Normal 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;
|
||||
}
|
20
drivers/s390/char/ctrlchar.h
Normal file
20
drivers/s390/char/ctrlchar.h
Normal 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)
|
156
drivers/s390/char/defkeymap.c
Normal file
156
drivers/s390/char/defkeymap.c
Normal 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;
|
191
drivers/s390/char/defkeymap.map
Normal file
191
drivers/s390/char/defkeymap.map
Normal 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
373
drivers/s390/char/fs3270.c
Normal 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);
|
519
drivers/s390/char/keyboard.c
Normal file
519
drivers/s390/char/keyboard.c
Normal 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);
|
57
drivers/s390/char/keyboard.h
Normal file
57
drivers/s390/char/keyboard.h
Normal 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);
|
||||
}
|
662
drivers/s390/char/monreader.c
Normal file
662
drivers/s390/char/monreader.c
Normal 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
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
274
drivers/s390/char/raw3270.h
Normal 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
915
drivers/s390/char/sclp.c
Normal 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(®->list, &sclp_reg_list);
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
rc = sclp_init_mask(1);
|
||||
if (rc) {
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
list_del(®->list);
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(sclp_register);
|
||||
|
||||
/* Unregister event listener. */
|
||||
void
|
||||
sclp_unregister(struct sclp_register *reg)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
list_del(®->list);
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
sclp_init_mask(1);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(sclp_unregister);
|
||||
|
||||
/* Remove event buffers which are marked processed. Return the number of
|
||||
* remaining event buffers. */
|
||||
int
|
||||
sclp_remove_processed(struct sccb_header *sccb)
|
||||
{
|
||||
struct evbuf_header *evbuf;
|
||||
int unprocessed;
|
||||
u16 remaining;
|
||||
|
||||
evbuf = (struct evbuf_header *) (sccb + 1);
|
||||
unprocessed = 0;
|
||||
remaining = sccb->length - sizeof(struct sccb_header);
|
||||
while (remaining > 0) {
|
||||
remaining -= evbuf->length;
|
||||
if (evbuf->flags & 0x80) {
|
||||
sccb->length -= evbuf->length;
|
||||
memcpy(evbuf, (void *) ((addr_t) evbuf + evbuf->length),
|
||||
remaining);
|
||||
} else {
|
||||
unprocessed++;
|
||||
evbuf = (struct evbuf_header *)
|
||||
((addr_t) evbuf + evbuf->length);
|
||||
}
|
||||
}
|
||||
return unprocessed;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(sclp_remove_processed);
|
||||
|
||||
struct init_sccb {
|
||||
struct sccb_header header;
|
||||
u16 _reserved;
|
||||
u16 mask_length;
|
||||
sccb_mask_t receive_mask;
|
||||
sccb_mask_t send_mask;
|
||||
sccb_mask_t sclp_send_mask;
|
||||
sccb_mask_t sclp_receive_mask;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* Prepare init mask request. Called while sclp_lock is locked. */
|
||||
static inline void
|
||||
__sclp_make_init_req(u32 receive_mask, u32 send_mask)
|
||||
{
|
||||
struct init_sccb *sccb;
|
||||
|
||||
sccb = (struct init_sccb *) sclp_init_sccb;
|
||||
clear_page(sccb);
|
||||
memset(&sclp_init_req, 0, sizeof(struct sclp_req));
|
||||
sclp_init_req.command = SCLP_CMDW_WRITEMASK;
|
||||
sclp_init_req.status = SCLP_REQ_FILLED;
|
||||
sclp_init_req.start_count = 0;
|
||||
sclp_init_req.callback = NULL;
|
||||
sclp_init_req.callback_data = NULL;
|
||||
sclp_init_req.sccb = sccb;
|
||||
sccb->header.length = sizeof(struct init_sccb);
|
||||
sccb->mask_length = sizeof(sccb_mask_t);
|
||||
sccb->receive_mask = receive_mask;
|
||||
sccb->send_mask = send_mask;
|
||||
sccb->sclp_receive_mask = 0;
|
||||
sccb->sclp_send_mask = 0;
|
||||
}
|
||||
|
||||
/* Start init mask request. If calculate is non-zero, calculate the mask as
|
||||
* requested by registered listeners. Use zero mask otherwise. Return 0 on
|
||||
* success, non-zero otherwise. */
|
||||
static int
|
||||
sclp_init_mask(int calculate)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct init_sccb *sccb = (struct init_sccb *) sclp_init_sccb;
|
||||
sccb_mask_t receive_mask;
|
||||
sccb_mask_t send_mask;
|
||||
int retry;
|
||||
int rc;
|
||||
unsigned long wait;
|
||||
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
/* Check if interface is in appropriate state */
|
||||
if (sclp_mask_state != sclp_mask_state_idle) {
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return -EBUSY;
|
||||
}
|
||||
if (sclp_activation_state == sclp_activation_state_inactive) {
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
sclp_mask_state = sclp_mask_state_initializing;
|
||||
/* Determine mask */
|
||||
if (calculate)
|
||||
__sclp_get_mask(&receive_mask, &send_mask);
|
||||
else {
|
||||
receive_mask = 0;
|
||||
send_mask = 0;
|
||||
}
|
||||
rc = -EIO;
|
||||
for (retry = 0; retry <= SCLP_MASK_RETRY; retry++) {
|
||||
/* Prepare request */
|
||||
__sclp_make_init_req(receive_mask, send_mask);
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
if (sclp_add_request(&sclp_init_req)) {
|
||||
/* Try again later */
|
||||
wait = jiffies + SCLP_BUSY_INTERVAL * HZ;
|
||||
while (time_before(jiffies, wait))
|
||||
sclp_sync_wait();
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
continue;
|
||||
}
|
||||
while (sclp_init_req.status != SCLP_REQ_DONE &&
|
||||
sclp_init_req.status != SCLP_REQ_FAILED)
|
||||
sclp_sync_wait();
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
if (sclp_init_req.status == SCLP_REQ_DONE &&
|
||||
sccb->header.response_code == 0x20) {
|
||||
/* Successful request */
|
||||
if (calculate) {
|
||||
sclp_receive_mask = sccb->sclp_receive_mask;
|
||||
sclp_send_mask = sccb->sclp_send_mask;
|
||||
} else {
|
||||
sclp_receive_mask = 0;
|
||||
sclp_send_mask = 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
sclp_dispatch_state_change();
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sclp_mask_state = sclp_mask_state_idle;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Deactivate SCLP interface. On success, new requests will be rejected,
|
||||
* events will no longer be dispatched. Return 0 on success, non-zero
|
||||
* otherwise. */
|
||||
int
|
||||
sclp_deactivate(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
/* Deactivate can only be called when active */
|
||||
if (sclp_activation_state != sclp_activation_state_active) {
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
sclp_activation_state = sclp_activation_state_deactivating;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
rc = sclp_init_mask(0);
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
if (rc == 0)
|
||||
sclp_activation_state = sclp_activation_state_inactive;
|
||||
else
|
||||
sclp_activation_state = sclp_activation_state_active;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(sclp_deactivate);
|
||||
|
||||
/* Reactivate SCLP interface after sclp_deactivate. On success, new
|
||||
* requests will be accepted, events will be dispatched again. Return 0 on
|
||||
* success, non-zero otherwise. */
|
||||
int
|
||||
sclp_reactivate(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
/* Reactivate can only be called when inactive */
|
||||
if (sclp_activation_state != sclp_activation_state_inactive) {
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
sclp_activation_state = sclp_activation_state_activating;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
rc = sclp_init_mask(1);
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
if (rc == 0)
|
||||
sclp_activation_state = sclp_activation_state_active;
|
||||
else
|
||||
sclp_activation_state = sclp_activation_state_inactive;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(sclp_reactivate);
|
||||
|
||||
/* Handler for external interruption used during initialization. Modify
|
||||
* request state to done. */
|
||||
static void
|
||||
sclp_check_handler(struct pt_regs *regs, __u16 code)
|
||||
{
|
||||
u32 finished_sccb;
|
||||
|
||||
finished_sccb = S390_lowcore.ext_params & 0xfffffff8;
|
||||
/* Is this the interrupt we are waiting for? */
|
||||
if (finished_sccb == 0)
|
||||
return;
|
||||
if (finished_sccb != (u32) (addr_t) sclp_init_sccb) {
|
||||
printk(KERN_WARNING SCLP_HEADER "unsolicited interrupt "
|
||||
"for buffer at 0x%x\n", finished_sccb);
|
||||
return;
|
||||
}
|
||||
spin_lock(&sclp_lock);
|
||||
if (sclp_running_state == sclp_running_state_running) {
|
||||
sclp_init_req.status = SCLP_REQ_DONE;
|
||||
sclp_running_state = sclp_running_state_idle;
|
||||
}
|
||||
spin_unlock(&sclp_lock);
|
||||
}
|
||||
|
||||
/* Initial init mask request timed out. Modify request state to failed. */
|
||||
static void
|
||||
sclp_check_timeout(unsigned long data)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
if (sclp_running_state == sclp_running_state_running) {
|
||||
sclp_init_req.status = SCLP_REQ_FAILED;
|
||||
sclp_running_state = sclp_running_state_idle;
|
||||
}
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
}
|
||||
|
||||
/* Perform a check of the SCLP interface. Return zero if the interface is
|
||||
* available and there are no pending requests from a previous instance.
|
||||
* Return non-zero otherwise. */
|
||||
static int
|
||||
sclp_check_interface(void)
|
||||
{
|
||||
struct init_sccb *sccb;
|
||||
unsigned long flags;
|
||||
int retry;
|
||||
int rc;
|
||||
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
/* Prepare init mask command */
|
||||
rc = register_early_external_interrupt(0x2401, sclp_check_handler,
|
||||
&ext_int_info_hwc);
|
||||
if (rc) {
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
for (retry = 0; retry <= SCLP_INIT_RETRY; retry++) {
|
||||
__sclp_make_init_req(0, 0);
|
||||
sccb = (struct init_sccb *) sclp_init_req.sccb;
|
||||
rc = service_call(sclp_init_req.command, sccb);
|
||||
if (rc == -EIO)
|
||||
break;
|
||||
sclp_init_req.status = SCLP_REQ_RUNNING;
|
||||
sclp_running_state = sclp_running_state_running;
|
||||
__sclp_set_request_timer(SCLP_RETRY_INTERVAL * HZ,
|
||||
sclp_check_timeout, 0);
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
/* Enable service-signal interruption - needs to happen
|
||||
* with IRQs enabled. */
|
||||
ctl_set_bit(0, 9);
|
||||
/* Wait for signal from interrupt or timeout */
|
||||
sclp_sync_wait();
|
||||
/* Disable service-signal interruption - needs to happen
|
||||
* with IRQs enabled. */
|
||||
ctl_clear_bit(0,9);
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
del_timer(&sclp_request_timer);
|
||||
if (sclp_init_req.status == SCLP_REQ_DONE &&
|
||||
sccb->header.response_code == 0x20) {
|
||||
rc = 0;
|
||||
break;
|
||||
} else
|
||||
rc = -EBUSY;
|
||||
}
|
||||
unregister_early_external_interrupt(0x2401, sclp_check_handler,
|
||||
&ext_int_info_hwc);
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Reboot event handler. Reset send and receive mask to prevent pending SCLP
|
||||
* events from interfering with rebooted system. */
|
||||
static int
|
||||
sclp_reboot_event(struct notifier_block *this, unsigned long event, void *ptr)
|
||||
{
|
||||
sclp_deactivate();
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block sclp_reboot_notifier = {
|
||||
.notifier_call = sclp_reboot_event
|
||||
};
|
||||
|
||||
/* Initialize SCLP driver. Return zero if driver is operational, non-zero
|
||||
* otherwise. */
|
||||
static int
|
||||
sclp_init(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
if (!MACHINE_HAS_SCLP)
|
||||
return -ENODEV;
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
/* Check for previous or running initialization */
|
||||
if (sclp_init_state != sclp_init_state_uninitialized) {
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
sclp_init_state = sclp_init_state_initializing;
|
||||
/* Set up variables */
|
||||
INIT_LIST_HEAD(&sclp_req_queue);
|
||||
INIT_LIST_HEAD(&sclp_reg_list);
|
||||
list_add(&sclp_state_change_event.list, &sclp_reg_list);
|
||||
init_timer(&sclp_request_timer);
|
||||
/* Check interface */
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
rc = sclp_check_interface();
|
||||
spin_lock_irqsave(&sclp_lock, flags);
|
||||
if (rc) {
|
||||
sclp_init_state = sclp_init_state_uninitialized;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
/* Register reboot handler */
|
||||
rc = register_reboot_notifier(&sclp_reboot_notifier);
|
||||
if (rc) {
|
||||
sclp_init_state = sclp_init_state_uninitialized;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
/* Register interrupt handler */
|
||||
rc = register_early_external_interrupt(0x2401, sclp_interrupt_handler,
|
||||
&ext_int_info_hwc);
|
||||
if (rc) {
|
||||
unregister_reboot_notifier(&sclp_reboot_notifier);
|
||||
sclp_init_state = sclp_init_state_uninitialized;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
sclp_init_state = sclp_init_state_initialized;
|
||||
spin_unlock_irqrestore(&sclp_lock, flags);
|
||||
/* Enable service-signal external interruption - needs to happen with
|
||||
* IRQs enabled. */
|
||||
ctl_set_bit(0, 9);
|
||||
sclp_init_mask(1);
|
||||
return 0;
|
||||
}
|
159
drivers/s390/char/sclp.h
Normal file
159
drivers/s390/char/sclp.h
Normal 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__ */
|
252
drivers/s390/char/sclp_con.c
Normal file
252
drivers/s390/char/sclp_con.c
Normal 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);
|
254
drivers/s390/char/sclp_cpi.c
Normal file
254
drivers/s390/char/sclp_cpi.c
Normal 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);
|
||||
|
99
drivers/s390/char/sclp_quiesce.c
Normal file
99
drivers/s390/char/sclp_quiesce.c
Normal 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
471
drivers/s390/char/sclp_rw.c
Normal 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);
|
||||
}
|
96
drivers/s390/char/sclp_rw.h
Normal file
96
drivers/s390/char/sclp_rw.h
Normal 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__ */
|
813
drivers/s390/char/sclp_tty.c
Normal file
813
drivers/s390/char/sclp_tty.c
Normal 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);
|
71
drivers/s390/char/sclp_tty.h
Normal file
71
drivers/s390/char/sclp_tty.h
Normal 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__ */
|
785
drivers/s390/char/sclp_vt220.c
Normal file
785
drivers/s390/char/sclp_vt220.c
Normal 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
384
drivers/s390/char/tape.h
Normal 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 */
|
1385
drivers/s390/char/tape_34xx.c
Normal file
1385
drivers/s390/char/tape_34xx.c
Normal file
File diff suppressed because it is too large
Load Diff
492
drivers/s390/char/tape_block.c
Normal file
492
drivers/s390/char/tape_block.c
Normal 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");
|
||||
}
|
492
drivers/s390/char/tape_char.c
Normal file
492
drivers/s390/char/tape_char.c
Normal 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);
|
||||
}
|
126
drivers/s390/char/tape_class.c
Normal file
126
drivers/s390/char/tape_class.c
Normal 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);
|
61
drivers/s390/char/tape_class.h
Normal file
61
drivers/s390/char/tape_class.h
Normal 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__ */
|
1242
drivers/s390/char/tape_core.c
Normal file
1242
drivers/s390/char/tape_core.c
Normal file
File diff suppressed because it is too large
Load Diff
145
drivers/s390/char/tape_proc.c
Normal file
145
drivers/s390/char/tape_proc.c
Normal 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);
|
||||
}
|
765
drivers/s390/char/tape_std.c
Normal file
765
drivers/s390/char/tape_std.c
Normal 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);
|
152
drivers/s390/char/tape_std.h
Normal file
152
drivers/s390/char/tape_std.h
Normal 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
1836
drivers/s390/char/tty3270.c
Normal file
File diff suppressed because it is too large
Load Diff
920
drivers/s390/char/vmlogrdr.c
Normal file
920
drivers/s390/char/vmlogrdr.c
Normal 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);
|
292
drivers/s390/char/vmwatchdog.c
Normal file
292
drivers/s390/char/vmwatchdog.c
Normal 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);
|
Reference in New Issue
Block a user