Commit f51320a5 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] s390: new 3270 driver.

From: Martin Schwidefsky <schwidefsky@de.ibm.com>

New 3270 device driver.
parent c29c6072
...@@ -2526,17 +2526,17 @@ Your cooperation is appreciated. ...@@ -2526,17 +2526,17 @@ Your cooperation is appreciated.
1 = /dev/dri/card1 Second graphics card 1 = /dev/dri/card1 Second graphics card
... ...
227 char IBM 3270 terminal block-mode access 227 char IBM 3270 terminal Unix tty access
1 = /dev/3270/tty1 First 3270 terminal
2 = /dev/3270/tty2 Seconds 3270 terminal
...
228 char IBM 3270 terminal block-mode access
0 = /dev/3270/tub Controlling interface 0 = /dev/3270/tub Controlling interface
1 = /dev/3270/tub1 First 3270 terminal 1 = /dev/3270/tub1 First 3270 terminal
2 = /dev/3270/tub2 Second 3270 terminal 2 = /dev/3270/tub2 Second 3270 terminal
... ...
228 char IBM 3270 terminal Unix tty access
1 = /dev/3270/tty1 First 3270 terminal
2 = /dev/3270/tty2 Seconds 3270 terminal
...
229 char IBM iSeries virtual console 229 char IBM iSeries virtual console
0 = /dev/iseries/vtty0 First console port 0 = /dev/iseries/vtty0 First console port
1 = /dev/iseries/vtty1 Second console port 1 = /dev/iseries/vtty1 Second console port
......
...@@ -159,7 +159,8 @@ CONFIG_UNIX98_PTY_COUNT=2048 ...@@ -159,7 +159,8 @@ CONFIG_UNIX98_PTY_COUNT=2048
# #
# S/390 character device drivers # S/390 character device drivers
# #
# CONFIG_TN3270 is not set CONFIG_TN3270=y
CONFIG_TN3270_CONSOLE=y
CONFIG_TN3215=y CONFIG_TN3215=y
CONFIG_TN3215_CONSOLE=y CONFIG_TN3215_CONSOLE=y
CONFIG_CCW_CONSOLE=y CONFIG_CCW_CONSOLE=y
......
...@@ -2,18 +2,20 @@ ...@@ -2,18 +2,20 @@
# S/390 character devices # S/390 character devices
# #
tub3270-objs := tuball.o tubfs.o tubtty.o \ obj-y += ctrlchar.o keyboard.o defkeymap.o
tubttyaid.o tubttybld.o tubttyscl.o \
tubttyrcl.o tubttysiz.o tub3270-objs := raw3270.o tty3270.o fs3270.o
obj-$(CONFIG_TN3270) += tub3270.o
obj-$(CONFIG_TN3270_CONSOLE) += con3270.o
obj-y += ctrlchar.o
obj-$(CONFIG_TN3215) += con3215.o obj-$(CONFIG_TN3215) += con3215.o
obj-$(CONFIG_SCLP) += sclp.o sclp_rw.o obj-$(CONFIG_SCLP) += sclp.o sclp_rw.o
obj-$(CONFIG_SCLP_TTY) += sclp_tty.o obj-$(CONFIG_SCLP_TTY) += sclp_tty.o
obj-$(CONFIG_SCLP_CONSOLE) += sclp_con.o obj-$(CONFIG_SCLP_CONSOLE) += sclp_con.o
obj-$(CONFIG_SCLP_VT220_TTY) += sclp_vt220.o obj-$(CONFIG_SCLP_VT220_TTY) += sclp_vt220.o
obj-$(CONFIG_SCLP_CPI) += sclp_cpi.o obj-$(CONFIG_SCLP_CPI) += sclp_cpi.o
obj-$(CONFIG_TN3270) += tub3270.o
tape-$(CONFIG_S390_TAPE_BLOCK) += tape_block.o tape-$(CONFIG_S390_TAPE_BLOCK) += tape_block.o
tape-$(CONFIG_PROC_FS) += tape_proc.o tape-$(CONFIG_PROC_FS) += tape_proc.o
tape-objs := tape_core.o tape_std.o tape_char.o $(tape-y) tape-objs := tape_core.o tape_std.o tape_char.o $(tape-y)
......
/*
* 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)) {
if (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);
/* 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;
# 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
/*
* 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);
/*
* 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 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 NR_TYPES = ARRAY_SIZE(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_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) >= 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) >= NR_TYPES)
return -EINVAL;
if (KVAL(tmp.kb_value) > 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 *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 *a;
int ct, perm;
/*
* 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->tty == kbd->tty || capable(CAP_SYS_TTY_CONFIG);
switch (cmd) {
case KDGKBTYPE:
return put_user(KB_101, (char*) arg);
case KDGKBENT:
case KDSKBENT:
return do_kdsk_ioctl(kbd, (struct kbentry *)arg, cmd, perm);
case KDGKBSENT:
case KDSKBSENT:
return do_kdgkb_ioctl(kbd, (struct kbsentry *)arg, cmd, perm);
case KDGKBDIACR:
a = (struct kbdiacrs *) arg;
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 = (struct kbdiacrs *) arg;
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);
/*
* 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);
}
/*
* drivers/s390/char/raw3270.c
* IBM/3270 Driver - core functions.
*
* Author(s):
* Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
* Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
* -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
*/
#include <linux/config.h>
#include <linux/bootmem.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <asm/ccwdev.h>
#include <asm/cio.h>
#include <asm/ebcdic.h>
#include "raw3270.h"
/* The main 3270 data structure. */
struct raw3270 {
struct list_head list;
struct ccw_device *cdev;
int minor;
short model, rows, cols;
unsigned long flags;
struct list_head req_queue; /* Request queue. */
struct list_head view_list; /* List of available views. */
struct raw3270_view *view; /* Active view. */
struct timer_list timer; /* Device timer. */
unsigned char *ascebc; /* ascii -> ebcdic table */
};
/* raw3270->flags */
#define RAW3270_FLAGS_14BITADDR 0 /* 14-bit buffer addresses */
#define RAW3270_FLAGS_BUSY 1 /* Device busy, leave it alone */
#define RAW3270_FLAGS_ATTN 2 /* Device sent an ATTN interrupt */
#define RAW3270_FLAGS_SHUTDOWN 4 /* Device is in offline processing */
#define RAW3270_FLAGS_CONSOLE 8 /* Device is the console. */
/* Lock to protect global data of raw3270 (devices, views, etc). */
static spinlock_t raw3270_lock = SPIN_LOCK_UNLOCKED;
/* List of 3270 devices. */
static struct list_head raw3270_devices = LIST_HEAD_INIT(raw3270_devices);
/*
* Flag to indicate if the driver has been registered. Some operations
* like waiting for the end of i/o need to be done differently as long
* as the kernel is still starting up (console support).
*/
static int raw3270_registered;
/* Module parameters */
static int tubxcorrect = 0;
MODULE_PARM(tubxcorrect, "i");
/*
* Wait queue for device init/delete, view delete.
*/
DECLARE_WAIT_QUEUE_HEAD(raw3270_wait_queue);
/*
* Encode array for 12 bit 3270 addresses.
*/
unsigned char raw3270_ebcgraf[64] = {
0x40, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
};
void
raw3270_buffer_address(struct raw3270 *rp, char *cp, unsigned short addr)
{
if (test_bit(RAW3270_FLAGS_14BITADDR, &rp->flags)) {
cp[0] = (addr >> 8) & 0x3f;
cp[1] = addr & 0xff;
} else {
cp[0] = raw3270_ebcgraf[(addr >> 6) & 0x3f];
cp[1] = raw3270_ebcgraf[addr & 0x3f];
}
}
/*
* Allocate a new 3270 ccw request
*/
struct raw3270_request *
raw3270_request_alloc(size_t size)
{
struct raw3270_request *rq;
/* Allocate request structure */
rq = kmalloc(sizeof(struct raw3270_request), GFP_KERNEL | GFP_DMA);
if (!rq)
return ERR_PTR(-ENOMEM);
memset(rq, 0, sizeof(struct raw3270_request));
/* alloc output buffer. */
if (size > 0) {
rq->buffer = kmalloc(size, GFP_KERNEL | GFP_DMA);
if (!rq->buffer) {
kfree(rq);
return ERR_PTR(-ENOMEM);
}
}
rq->size = size;
INIT_LIST_HEAD(&rq->list);
/*
* Setup ccw.
*/
rq->ccw.cda = __pa(rq->buffer);
rq->ccw.flags = CCW_FLAG_SLI;
return rq;
}
#ifdef CONFIG_TN3270_CONSOLE
/*
* Allocate a new 3270 ccw request from bootmem. Only works very
* early in the boot process. Only con3270.c should be using this.
*/
struct raw3270_request *
raw3270_request_alloc_bootmem(size_t size)
{
struct raw3270_request *rq;
rq = alloc_bootmem_low(sizeof(struct raw3270));
if (!rq)
return ERR_PTR(-ENOMEM);
memset(rq, 0, sizeof(struct raw3270_request));
/* alloc output buffer. */
if (size > 0) {
rq->buffer = alloc_bootmem_low(size);
if (!rq->buffer) {
free_bootmem((unsigned long) rq,
sizeof(struct raw3270));
return ERR_PTR(-ENOMEM);
}
}
rq->size = size;
INIT_LIST_HEAD(&rq->list);
/*
* Setup ccw.
*/
rq->ccw.cda = __pa(rq->buffer);
rq->ccw.flags = CCW_FLAG_SLI;
return rq;
}
#endif
/*
* Free 3270 ccw request
*/
void
raw3270_request_free (struct raw3270_request *rq)
{
if (rq->buffer)
kfree(rq->buffer);
kfree(rq);
}
/*
* Reset request to initial state.
*/
void
raw3270_request_reset(struct raw3270_request *rq)
{
BUG_ON(!list_empty(&rq->list));
rq->ccw.cmd_code = 0;
rq->ccw.count = 0;
rq->ccw.cda = __pa(rq->buffer);
rq->ccw.flags = CCW_FLAG_SLI;
rq->rescnt = 0;
rq->rc = 0;
}
/*
* Set command code to ccw of a request.
*/
void
raw3270_request_set_cmd(struct raw3270_request *rq, u8 cmd)
{
rq->ccw.cmd_code = cmd;
}
/*
* Add data fragment to output buffer.
*/
int
raw3270_request_add_data(struct raw3270_request *rq, void *data, size_t size)
{
if (size + rq->ccw.count > rq->size)
return -E2BIG;
memcpy(rq->buffer + rq->ccw.count, data, size);
rq->ccw.count += size;
return 0;
}
/*
* Set address/length pair to ccw of a request.
*/
void
raw3270_request_set_data(struct raw3270_request *rq, void *data, size_t size)
{
rq->ccw.cda = __pa(data);
rq->ccw.count = size;
}
/*
* Set idal buffer to ccw of a request.
*/
void
raw3270_request_set_idal(struct raw3270_request *rq, struct idal_buffer *ib)
{
rq->ccw.cda = __pa(ib->data);
rq->ccw.count = ib->size;
rq->ccw.flags |= CCW_FLAG_IDA;
}
/*
* Stop running ccw.
*/
static int
raw3270_halt_io_nolock(struct raw3270 *rp, struct raw3270_request *rq)
{
int retries;
int rc;
if (raw3270_request_final(rq))
return 0;
/* Check if interrupt has already been processed */
for (retries = 0; retries < 5; retries++) {
if (retries < 2)
rc = ccw_device_halt(rp->cdev, (long) rq);
else
rc = ccw_device_clear(rp->cdev, (long) rq);
if (rc == 0)
break; /* termination successful */
}
return rc;
}
static int
raw3270_halt_io(struct raw3270 *rp, struct raw3270_request *rq)
{
unsigned long flags;
int rc;
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
rc = raw3270_halt_io_nolock(rp, rq);
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
return rc;
}
/*
* Add the request to the request queue, try to start it if the
* 3270 device is idle. Return without waiting for end of i/o.
*/
static int
__raw3270_start(struct raw3270 *rp, struct raw3270_view *view,
struct raw3270_request *rq)
{
rq->view = view;
raw3270_get_view(view);
if (list_empty(&rp->req_queue) &&
!test_bit(RAW3270_FLAGS_BUSY, &rp->flags)) {
/* No other requests are on the queue. Start this one. */
rq->rc = ccw_device_start(rp->cdev, &rq->ccw,
(unsigned long) rq, 0, 0);
if (rq->rc) {
raw3270_put_view(view);
return rq->rc;
}
}
list_add_tail(&rq->list, &rp->req_queue);
return 0;
}
int
raw3270_start(struct raw3270_view *view, struct raw3270_request *rq)
{
unsigned long flags;
struct raw3270 *rp;
int rc;
spin_lock_irqsave(get_ccwdev_lock(view->dev->cdev), flags);
rp = view->dev;
if (!rp || rp->view != view)
rc = -EACCES;
else if (test_bit(RAW3270_FLAGS_SHUTDOWN, &rp->flags))
rc = -ENODEV;
else
rc = __raw3270_start(rp, view, rq);
spin_unlock_irqrestore(get_ccwdev_lock(view->dev->cdev), flags);
return rc;
}
int
raw3270_start_irq(struct raw3270_view *view, struct raw3270_request *rq)
{
struct raw3270 *rp;
rp = view->dev;
rq->view = view;
raw3270_get_view(view);
list_add_tail(&rq->list, &rp->req_queue);
return 0;
}
/*
* 3270 interrupt routine, called from the ccw_device layer
*/
static void
raw3270_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
{
struct raw3270 *rp;
struct raw3270_view *view;
struct raw3270_request *rq;
int rc;
rp = (struct raw3270 *) cdev->dev.driver_data;
if (!rp)
return;
rq = (struct raw3270_request *) intparm;
view = rq ? rq->view : rp->view;
if (irb->scsw.dstat ==
(DEV_STAT_CHN_END | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP)) {
/* Handle CE-DE-UE and subsequent UDE */
set_bit(RAW3270_FLAGS_BUSY, &rp->flags);
rc = RAW3270_IO_BUSY;
} else if (test_bit(RAW3270_FLAGS_BUSY, &rp->flags)) {
/* Wait for UDE if busy flag is set. */
if (irb->scsw.dstat & DEV_STAT_DEV_END) {
clear_bit(RAW3270_FLAGS_BUSY, &rp->flags);
/* Got it, now retry. */
rc = RAW3270_IO_RETRY;
} else
rc = RAW3270_IO_BUSY;
} else if (view)
rc = view->fn->intv(view, rq, irb);
else
rc = RAW3270_IO_DONE;
switch (rc) {
case RAW3270_IO_DONE:
break;
case RAW3270_IO_BUSY:
/*
* Intervention required by the operator. We have to wait
* for unsolicited device end.
*/
return;
case RAW3270_IO_RETRY:
if (!rq)
break;
rq->rc = ccw_device_start(rp->cdev, &rq->ccw,
(unsigned long) rq, 0, 0);
if (rq->rc == 0)
return; /* Sucessfully restarted. */
break;
case RAW3270_IO_STOP:
raw3270_halt_io_nolock(rp, rq);
rq->rc = -EIO;
break;
default:
BUG();
}
if (rq) {
BUG_ON(list_empty(&rq->list));
/* The request completed, remove from queue and do callback. */
list_del_init(&rq->list);
if (rq->callback)
rq->callback(rq, rq->callback_data);
/* Do put_device for get_device in raw3270_start. */
raw3270_put_view(view);
}
/*
* Try to start each request on request queue until one is
* started successful.
*/
while (!list_empty(&rp->req_queue)) {
rq = list_entry(rp->req_queue.next,struct raw3270_request,list);
rq->rc = ccw_device_start(rp->cdev, &rq->ccw,
(unsigned long) rq, 0, 0);
if (rq->rc == 0)
break;
/* Start failed. Remove request and do callback. */
list_del_init(&rq->list);
if (rq->callback)
rq->callback(rq, rq->callback_data);
/* Do put_device for get_device in raw3270_start. */
raw3270_put_view(view);
}
}
/*
* Size sensing.
*/
struct raw3270_ua { /* Query Reply structure for Usable Area */
struct { /* Usable Area Query Reply Base */
short l; /* Length of this structured field */
char sfid; /* 0x81 if Query Reply */
char qcode; /* 0x81 if Usable Area */
char flags0;
char flags1;
short w; /* Width of usable area */
short h; /* Heigth of usavle area */
char units; /* 0x00:in; 0x01:mm */
int xr;
int yr;
char aw;
char ah;
short buffsz; /* Character buffer size, bytes */
char xmin;
char ymin;
char xmax;
char ymax;
} __attribute__ ((packed)) uab;
struct { /* Alternate Usable Area Self-Defining Parameter */
char l; /* Length of this Self-Defining Parm */
char sdpid; /* 0x02 if Alternate Usable Area */
char res;
char auaid; /* 0x01 is Id for the A U A */
short wauai; /* Width of AUAi */
short hauai; /* Height of AUAi */
char auaunits; /* 0x00:in, 0x01:mm */
int auaxr;
int auayr;
char awauai;
char ahauai;
} __attribute__ ((packed)) aua;
} __attribute__ ((packed));
static unsigned char raw3270_init_data[256];
static struct raw3270_request raw3270_init_request;
static struct diag210 raw3270_init_diag210;
static DECLARE_MUTEX(raw3270_init_sem);
static int
raw3270_init_irq(struct raw3270_view *view, struct raw3270_request *rq,
struct irb *irb)
{
/*
* Unit-Check Processing:
* Expect Command Reject or Intervention Required.
*/
if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK) {
/* Request finished abnormally. */
if (irb->ecw[0] & SNS0_INTERVENTION_REQ) {
set_bit(RAW3270_FLAGS_BUSY, &view->dev->flags);
return RAW3270_IO_BUSY;
}
}
if (rq) {
if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK) {
if (irb->ecw[0] & SNS0_CMD_REJECT)
rq->rc = -EOPNOTSUPP;
else
rq->rc = -EIO;
} else
/* Request finished normally. Copy residual count. */
rq->rescnt = irb->scsw.count;
}
if (irb->scsw.dstat & DEV_STAT_ATTENTION) {
set_bit(RAW3270_FLAGS_ATTN, &view->dev->flags);
wake_up(&raw3270_wait_queue);
}
return RAW3270_IO_DONE;
}
static struct raw3270_fn raw3270_init_fn = {
.intv = raw3270_init_irq
};
static struct raw3270_view raw3270_init_view = {
.fn = &raw3270_init_fn
};
/*
* raw3270_wait/raw3270_wait_interruptible/__raw3270_wakeup
* Wait for end of request. The request must have been started
* with raw3270_start, rc = 0. The device lock may NOT have been
* released between calling raw3270_start and raw3270_wait.
*/
static void
raw3270_wake_init(struct raw3270_request *rq, void *data)
{
wake_up((wait_queue_head_t *) data);
}
/*
* Special wait function that can cope with console initialization.
*/
static int
raw3270_start_init(struct raw3270 *rp, struct raw3270_view *view,
struct raw3270_request *rq)
{
unsigned long flags;
wait_queue_head_t wq;
int rc;
#ifdef CONFIG_TN3270_CONSOLE
if (raw3270_registered == 0) {
spin_lock_irqsave(get_ccwdev_lock(view->dev->cdev), flags);
rq->callback = 0;
rc = __raw3270_start(rp, view, rq);
if (rc == 0)
while (!raw3270_request_final(rq)) {
wait_cons_dev();
barrier();
}
spin_unlock_irqrestore(get_ccwdev_lock(view->dev->cdev), flags);
return rq->rc;
}
#endif
init_waitqueue_head(&wq);
rq->callback = raw3270_wake_init;
rq->callback_data = &wq;
spin_lock_irqsave(get_ccwdev_lock(view->dev->cdev), flags);
rc = __raw3270_start(rp, view, rq);
spin_unlock_irqrestore(get_ccwdev_lock(view->dev->cdev), flags);
if (rc)
return rc;
/* Now wait for the completion. */
rc = wait_event_interruptible(wq, raw3270_request_final(rq));
if (rc == -ERESTARTSYS) { /* Interrupted by a signal. */
raw3270_halt_io(view->dev, rq);
return -ERESTARTSYS;
}
return rq->rc;
}
static int
__raw3270_size_device_vm(struct raw3270 *rp)
{
int rc, model;
raw3270_init_diag210.vrdcdvno =
_ccw_device_get_device_number(rp->cdev);
raw3270_init_diag210.vrdclen = sizeof(struct diag210);
rc = diag210(&raw3270_init_diag210);
if (rc)
return rc;
model = raw3270_init_diag210.vrdccrmd;
switch (model) {
case 2:
rp->model = model;
rp->rows = 24;
rp->cols = 80;
break;
case 3:
rp->model = model;
rp->rows = 32;
rp->cols = 80;
break;
case 4:
rp->model = model;
rp->rows = 43;
rp->cols = 80;
break;
case 5:
rp->model = model;
rp->rows = 27;
rp->cols = 132;
break;
default:
printk(KERN_WARNING "vrdccrmd is 0x%.8x\n", model);
rc = -EOPNOTSUPP;
break;
}
return rc;
}
static int
__raw3270_size_device(struct raw3270 *rp)
{
static const unsigned char wbuf[] =
{ 0x00, 0x07, 0x01, 0xff, 0x03, 0x00, 0x81 };
unsigned long flags;
struct raw3270_ua *uap;
unsigned short count;
int rc;
/*
* To determine the size of the 3270 device we need to do:
* 1) send a 'read partition' data stream to the device
* 2) wait for the attn interrupt that preceeds the query reply
* 3) do a read modified to get the query reply
* To make things worse we have to cope with intervention
* required (3270 device switched to 'stand-by') and command
* rejects (old devices that can't do 'read partition').
*/
memset(&raw3270_init_request, 0, sizeof(raw3270_init_request));
memset(raw3270_init_data, 0, sizeof(raw3270_init_data));
/* Store 'read partition' data stream to raw3270_init_data */
memcpy(raw3270_init_data, wbuf, sizeof(wbuf));
INIT_LIST_HEAD(&raw3270_init_request.list);
raw3270_init_request.ccw.cmd_code = TC_WRITESF;
raw3270_init_request.ccw.flags = CCW_FLAG_SLI;
raw3270_init_request.ccw.count = sizeof(wbuf);
raw3270_init_request.ccw.cda = (__u32) __pa(raw3270_init_data);
rc = raw3270_start_init(rp, &raw3270_init_view, &raw3270_init_request);
if (rc) {
/* Check error cases: -ERESTARTSYS, -EIO and -EOPNOTSUPP */
if (rc == -EOPNOTSUPP && MACHINE_IS_VM)
return __raw3270_size_device_vm(rp);
return rc;
}
/* Wait for attention interrupt. */
#ifdef CONFIG_TN3270_CONSOLE
if (raw3270_registered == 0) {
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
while (!test_and_clear_bit(RAW3270_FLAGS_ATTN, &rp->flags))
wait_cons_dev();
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
} else
#endif
rc = wait_event_interruptible(raw3270_wait_queue,
test_and_clear_bit(RAW3270_FLAGS_ATTN, &rp->flags));
if (rc)
return rc;
/*
* The device accepted the 'read partition' command. Now
* set up a read ccw and issue it.
*/
raw3270_init_request.ccw.cmd_code = TC_READMOD;
raw3270_init_request.ccw.flags = CCW_FLAG_SLI;
raw3270_init_request.ccw.count = sizeof(raw3270_init_data);
raw3270_init_request.ccw.cda = (__u32) __pa(raw3270_init_data);
rc = raw3270_start_init(rp, &raw3270_init_view, &raw3270_init_request);
if (rc)
return rc;
/* Got a Query Reply */
count = sizeof(raw3270_init_data) - raw3270_init_request.rescnt;
uap = (struct raw3270_ua *) (raw3270_init_data + 1);
/* Paranoia check. */
if (raw3270_init_data[0] != 0x88 || uap->uab.qcode != 0x81)
return -EOPNOTSUPP;
/* Copy rows/columns of default Usable Area */
rp->rows = uap->uab.h;
rp->cols = uap->uab.w;
/* Check for 14 bit addressing */
if ((uap->uab.flags0 & 0x0d) == 0x01)
set_bit(RAW3270_FLAGS_14BITADDR, &rp->flags);
/* Check for Alternate Usable Area */
if (uap->uab.l == sizeof(struct raw3270_ua) &&
uap->aua.sdpid == 0x02) {
rp->rows = uap->aua.hauai;
rp->cols = uap->aua.wauai;
}
return 0;
}
static int
raw3270_size_device(struct raw3270 *rp)
{
int rc;
down(&raw3270_init_sem);
rp->view = &raw3270_init_view;
raw3270_init_view.dev = rp;
rc = __raw3270_size_device(rp);
raw3270_init_view.dev = 0;
rp->view = 0;
up(&raw3270_init_sem);
if (rc == 0) { /* Found something. */
/* Try to find a model. */
rp->model = 0;
if (rp->rows == 24 && rp->cols == 80)
rp->model = 2;
if (rp->rows == 32 && rp->cols == 80)
rp->model = 3;
if (rp->rows == 43 && rp->cols == 80)
rp->model = 4;
if (rp->rows == 27 && rp->cols == 132)
rp->model = 5;
}
return rc;
}
static int
raw3270_reset_device(struct raw3270 *rp)
{
int rc;
down(&raw3270_init_sem);
memset(&raw3270_init_request, 0, sizeof(raw3270_init_request));
memset(raw3270_init_data, 0, sizeof(raw3270_init_data));
/* Store reset data stream to raw3270_init_data/raw3270_init_request */
raw3270_init_data[0] = TW_KR;
INIT_LIST_HEAD(&raw3270_init_request.list);
raw3270_init_request.ccw.cmd_code = TC_EWRITEA;
raw3270_init_request.ccw.flags = CCW_FLAG_SLI;
raw3270_init_request.ccw.count = 1;
raw3270_init_request.ccw.cda = (__u32) __pa(raw3270_init_data);
rp->view = &raw3270_init_view;
raw3270_init_view.dev = rp;
rc = raw3270_start_init(rp, &raw3270_init_view, &raw3270_init_request);
raw3270_init_view.dev = 0;
rp->view = 0;
up(&raw3270_init_sem);
return rc;
}
/*
* Setup new 3270 device.
*/
static int
raw3270_setup_device(struct ccw_device *cdev, struct raw3270 *rp, char *ascebc)
{
struct list_head *l;
struct raw3270 *tmp;
int minor;
memset(rp, 0, sizeof(struct raw3270));
/* Copy ebcdic -> ascii translation table. */
memcpy(ascebc, _ascebc, 256);
if (tubxcorrect) {
/* correct brackets and circumflex */
ascebc['['] = 0xad;
ascebc[']'] = 0xbd;
ascebc['^'] = 0xb0;
}
rp->ascebc = ascebc;
/* Set defaults. */
rp->rows = 24;
rp->cols = 80;
INIT_LIST_HEAD(&rp->req_queue);
INIT_LIST_HEAD(&rp->view_list);
/*
* Add device to list and find the smallest unused minor
* number for it.
*/
spin_lock(&raw3270_lock);
/* Keep the list sorted. */
minor = 0;
rp->minor = -1;
list_for_each(l, &raw3270_devices) {
tmp = list_entry(l, struct raw3270, list);
if (tmp->minor > minor) {
rp->minor = minor;
__list_add(&rp->list, l->prev, l);
break;
}
minor++;
}
if (rp->minor == -1 && minor < RAW3270_MAXDEVS) {
rp->minor = minor;
list_add_tail(&rp->list, &raw3270_devices);
}
spin_unlock(&raw3270_lock);
/* No free minor number? Then give up. */
if (rp->minor == -1)
return -EUSERS;
rp->cdev = cdev;
cdev->dev.driver_data = rp;
cdev->handler = raw3270_irq;
return 0;
}
#ifdef CONFIG_TN3270_CONSOLE
/*
* Setup 3270 device configured as console.
*/
struct raw3270 *
raw3270_setup_console(struct ccw_device *cdev)
{
struct raw3270 *rp;
char *ascebc;
int rc;
rp = (struct raw3270 *) alloc_bootmem(sizeof(struct raw3270));
ascebc = (char *) alloc_bootmem(256);
rc = raw3270_setup_device(cdev, rp, ascebc);
if (rc)
return ERR_PTR(rc);
set_bit(RAW3270_FLAGS_CONSOLE, &rp->flags);
raw3270_reset_device(rp);
raw3270_size_device(rp);
raw3270_reset_device(rp);
return rp;
}
void
raw3270_wait_cons_dev(struct raw3270 *rp)
{
unsigned long flags;
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
wait_cons_dev();
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
}
#endif
/*
* Create a 3270 device structure.
*/
static struct raw3270 *
raw3270_create_device(struct ccw_device *cdev)
{
struct raw3270 *rp;
char *ascebc;
int rc;
rp = kmalloc(sizeof(struct raw3270), GFP_KERNEL);
if (!rp)
return ERR_PTR(-ENOMEM);
ascebc = kmalloc(256, GFP_KERNEL);
if (!ascebc) {
kfree(rp);
return ERR_PTR(-ENOMEM);
}
rc = raw3270_setup_device(cdev, rp, ascebc);
if (rc) {
kfree(rp->ascebc);
kfree(rp);
rp = ERR_PTR(rc);
}
/* Get reference to ccw_device structure. */
get_device(&cdev->dev);
return rp;
}
/*
* Activate a view.
*/
int
raw3270_activate_view(struct raw3270_view *view)
{
struct raw3270 *rp;
struct raw3270_view *oldview, *nv;
unsigned long flags;
int rc;
rp = view->dev;
if (!rp)
return -ENODEV;
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
if (rp->view == view)
rc = 0;
else if (test_bit(RAW3270_FLAGS_SHUTDOWN, &rp->flags))
rc = -ENODEV;
else {
oldview = 0;
if (rp->view) {
oldview = rp->view;
oldview->fn->deactivate(oldview);
}
rp->view = view;
rc = view->fn->activate(view);
if (rc) {
/* Didn't work. Try to reactivate the old view. */
rp->view = oldview;
if (oldview->fn->activate(oldview) != 0) {
/* Didn't work as well. Try any other view. */
list_for_each_entry(nv, &rp->view_list, list)
if (nv != view && nv != oldview) {
rp->view = nv;
if (nv->fn->activate(nv) == 0)
break;
rp->view = 0;
}
}
}
}
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
return rc;
}
/*
* Deactivate current view.
*/
void
raw3270_deactivate_view(struct raw3270_view *view)
{
unsigned long flags;
struct raw3270 *rp;
rp = view->dev;
if (!rp)
return;
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
if (rp->view == view) {
view->fn->deactivate(view);
rp->view = 0;
/* Move deactivated view to end of list. */
list_del_init(&view->list);
list_add_tail(&view->list, &rp->view_list);
/* Try to activate another view. */
if (!test_bit(RAW3270_FLAGS_SHUTDOWN, &rp->flags)) {
list_for_each_entry(view, &rp->view_list, list)
if (view->fn->activate(view) == 0) {
rp->view = view;
break;
}
}
}
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
}
/*
* Add view to device with minor "minor".
*/
int
raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor)
{
unsigned long flags;
struct raw3270 *rp;
int rc;
spin_lock(&raw3270_lock);
rc = -ENODEV;
list_for_each_entry(rp, &raw3270_devices, list) {
if (rp->minor != minor)
continue;
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
if (!test_bit(RAW3270_FLAGS_SHUTDOWN, &rp->flags)) {
atomic_set(&view->ref_count, 2);
view->dev = rp;
view->fn = fn;
view->model = rp->model;
view->rows = rp->rows;
view->cols = rp->cols;
view->ascebc = rp->ascebc;
spin_lock_init(&view->lock);
list_add_tail(&view->list, &rp->view_list);
rc = 0;
}
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
break;
}
spin_unlock(&raw3270_lock);
return rc;
}
/*
* Find specific view of device with minor "minor".
*/
struct raw3270_view *
raw3270_find_view(struct raw3270_fn *fn, int minor)
{
struct raw3270 *rp;
struct raw3270_view *view, *tmp;
unsigned long flags;
spin_lock(&raw3270_lock);
view = ERR_PTR(-ENODEV);
list_for_each_entry(rp, &raw3270_devices, list) {
if (rp->minor != minor)
continue;
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
if (!test_bit(RAW3270_FLAGS_SHUTDOWN, &rp->flags)) {
list_for_each_entry(tmp, &rp->view_list, list) {
if (tmp->fn == fn) {
raw3270_get_view(tmp);
view = tmp;
}
}
} else
view = ERR_PTR(-ENOENT);
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
break;
}
spin_unlock(&raw3270_lock);
return view;
}
/*
* Remove view from device and free view structure via call to view->fn->free.
*/
void
raw3270_del_view(struct raw3270_view *view)
{
unsigned long flags;
struct raw3270 *rp;
rp = view->dev;
spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags);
if (rp->view == view) {
view->fn->deactivate(view);
rp->view = 0;
}
list_del_init(&view->list);
if (!rp->view && !test_bit(RAW3270_FLAGS_SHUTDOWN, &rp->flags)) {
/* Try to activate another view. */
list_for_each_entry(view, &rp->view_list, list)
if (view->fn->activate(view) == 0) {
rp->view = view;
break;
}
}
spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags);
/* Wait for reference counter to drop to zero. */
atomic_dec(&view->ref_count);
wait_event(raw3270_wait_queue, atomic_read(&view->ref_count) == 0);
if (view->fn->free)
view->fn->free(view);
}
/*
* Remove a 3270 device structure.
*/
static void
raw3270_delete_device(struct raw3270 *rp)
{
struct ccw_device *cdev;
/* Remove from device chain. */
spin_lock(&raw3270_lock);
list_del_init(&rp->list);
spin_unlock(&raw3270_lock);
/* Disconnect from ccw_device. */
cdev = rp->cdev;
rp->cdev = 0;
cdev->dev.driver_data = 0;
cdev->handler = 0;
/* Put ccw_device structure. */
put_device(&cdev->dev);
/* Now free raw3270 structure. */
kfree(rp->ascebc);
kfree(rp);
}
static int
raw3270_probe (struct ccw_device *cdev)
{
return 0;
}
/*
* Additional attributes for a 3270 device
*/
static ssize_t
raw3270_model_show(struct device *dev, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%i\n",
((struct raw3270 *) dev->driver_data)->model);
}
static DEVICE_ATTR(model, 0444, raw3270_model_show, 0);
static ssize_t
raw3270_rows_show(struct device *dev, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%i\n",
((struct raw3270 *) dev->driver_data)->rows);
}
static DEVICE_ATTR(rows, 0444, raw3270_rows_show, 0);
static ssize_t
raw3270_columns_show(struct device *dev, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%i\n",
((struct raw3270 *) dev->driver_data)->cols);
}
static DEVICE_ATTR(columns, 0444, raw3270_columns_show, 0);
static struct attribute * raw3270_attrs[] = {
&dev_attr_model.attr,
&dev_attr_rows.attr,
&dev_attr_columns.attr,
NULL,
};
static struct attribute_group raw3270_attr_group = {
.attrs = raw3270_attrs,
};
static void
raw3270_create_attributes(struct raw3270 *rp)
{
//FIXME: check return code
sysfs_create_group(&rp->cdev->dev.kobj, &raw3270_attr_group);
}
/* Hackish. A notifier chain would be cleaner. */
extern void tty3270_notifier(int index, int active);
/*
* Set 3270 device online.
*/
static int
raw3270_set_online (struct ccw_device *cdev)
{
struct raw3270 *rp;
rp = raw3270_create_device(cdev);
if (IS_ERR(rp))
return PTR_ERR(rp);
raw3270_reset_device(rp);
raw3270_size_device(rp);
raw3270_reset_device(rp);
raw3270_create_attributes(rp);
tty3270_notifier(rp->minor, 1);
return 0;
}
/*
* Remove 3270 device structure.
*/
static void
raw3270_remove (struct ccw_device *cdev)
{
unsigned long flags;
struct raw3270 *rp;
struct raw3270_view *v;
rp = cdev->dev.driver_data;
set_bit(RAW3270_FLAGS_SHUTDOWN, &rp->flags);
sysfs_remove_group(&cdev->dev.kobj, &raw3270_attr_group);
/* Deactivate current view and remove all views. */
spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
if (rp->view) {
rp->view->fn->deactivate(rp->view);
rp->view = 0;
}
while (!list_empty(&rp->view_list)) {
v = list_entry(rp->view_list.next, struct raw3270_view, list);
if (v->fn->release)
v->fn->release(v);
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
raw3270_del_view(v);
spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
}
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
tty3270_notifier(rp->minor, 0);
/* Reset 3270 device. */
raw3270_reset_device(rp);
/* And finally remove it. */
raw3270_delete_device(rp);
}
/*
* Set 3270 device offline.
*/
static int
raw3270_set_offline (struct ccw_device *cdev)
{
struct raw3270 *rp;
rp = cdev->dev.driver_data;
if (test_bit(RAW3270_FLAGS_CONSOLE, &rp->flags))
return -EBUSY;
raw3270_remove(cdev);
return 0;
}
static struct ccw_device_id raw3270_id[] = {
{ CCW_DEVICE(0x3270, 0) },
{ CCW_DEVICE(0x3271, 0) },
{ CCW_DEVICE(0x3272, 0) },
{ CCW_DEVICE(0x3273, 0) },
{ CCW_DEVICE(0x3274, 0) },
{ CCW_DEVICE(0x3275, 0) },
{ CCW_DEVICE(0x3276, 0) },
{ CCW_DEVICE(0x3277, 0) },
{ CCW_DEVICE(0x3278, 0) },
{ CCW_DEVICE(0x3279, 0) },
{ CCW_DEVICE(0x3174, 0) },
{ /* end of list */ },
};
static struct ccw_driver raw3270_ccw_driver = {
.name = "3270",
.owner = THIS_MODULE,
.ids = raw3270_id,
.probe = &raw3270_probe,
.remove = &raw3270_remove,
.set_online = &raw3270_set_online,
.set_offline = &raw3270_set_offline,
};
int
raw3270_init(void)
{
struct raw3270 *rp;
int rc;
if (raw3270_registered)
return 0;
raw3270_registered = 1;
rc = ccw_driver_register(&raw3270_ccw_driver);
if (rc == 0) {
/* Create attributes for early (= console) device. */
spin_lock(&raw3270_lock);
list_for_each_entry(rp, &raw3270_devices, list) {
get_device(&rp->cdev->dev);
raw3270_create_attributes(rp);
tty3270_notifier(rp->minor, 1);
}
spin_unlock(&raw3270_lock);
}
return rc;
}
void
raw3270_exit(void)
{
ccw_driver_unregister(&raw3270_ccw_driver);
}
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(raw3270_init);
EXPORT_SYMBOL(raw3270_exit);
EXPORT_SYMBOL(raw3270_request_alloc);
EXPORT_SYMBOL(raw3270_request_free);
EXPORT_SYMBOL(raw3270_request_reset);
EXPORT_SYMBOL(raw3270_request_set_cmd);
EXPORT_SYMBOL(raw3270_request_add_data);
EXPORT_SYMBOL(raw3270_request_set_data);
EXPORT_SYMBOL(raw3270_request_set_idal);
EXPORT_SYMBOL(raw3270_buffer_address);
EXPORT_SYMBOL(raw3270_add_view);
EXPORT_SYMBOL(raw3270_del_view);
EXPORT_SYMBOL(raw3270_find_view);
EXPORT_SYMBOL(raw3270_activate_view);
EXPORT_SYMBOL(raw3270_deactivate_view);
EXPORT_SYMBOL(raw3270_start);
EXPORT_SYMBOL(raw3270_start_irq);
/*
* 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
int raw3270_init(void);
void raw3270_exit(void);
/* 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 *);
/*
* 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);
}
/*
* drivers/s390/char/tty3270.c
* IBM/3270 Driver - tty functions.
*
* Author(s):
* Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
* Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
* -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/tty.h>
#include <linux/vt_kern.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/bootmem.h>
#include <asm/ccwdev.h>
#include <asm/cio.h>
#include <asm/ebcdic.h>
#include <asm/uaccess.h>
#include "raw3270.h"
#include "keyboard.h"
#define TTY3270_CHAR_BUF_SIZE 256
#define TTY3270_OUTPUT_BUFFER_SIZE 1024
#define TTY3270_STRING_PAGES 5
struct tty_driver *tty3270_driver;
struct raw3270_fn tty3270_fn;
struct tty3270_cell {
unsigned char character;
unsigned char highlight;
unsigned char f_color;
};
struct tty3270_line {
struct tty3270_cell *cells;
int len;
};
#define ESCAPE_NPAR 8
/*
* The main tty view data structure.
* FIXME:
* 1) describe line orientation & lines list concept against screen
* 2) describe conversion of screen to lines
* 3) describe line format.
*/
struct tty3270 {
struct raw3270_view view;
struct tty_struct *tty; /* Pointer to tty structure */
void **freemem_pages; /* Array of pages used for freemem. */
struct list_head freemem; /* List of free memory for strings. */
/* Output stuff. */
struct list_head lines; /* List of lines. */
struct list_head update; /* List of lines to update. */
unsigned char wcc; /* Write control character. */
int nr_lines; /* # lines in list. */
int nr_up; /* # lines up in history. */
unsigned long update_flags; /* Update indication bits. */
struct string *status; /* Lower right of display. */
struct raw3270_request *write; /* Single write request. */
struct timer_list timer; /* Output delay timer. */
/* Current tty screen. */
unsigned int cx, cy; /* Current output position. */
unsigned int highlight; /* Blink/reverse/underscore */
unsigned int f_color; /* Foreground color */
struct tty3270_line *screen;
/* Input stuff. */
struct string *prompt; /* Output string for input area. */
struct string *input; /* Input string for read request. */
struct raw3270_request *read; /* Single read request. */
struct raw3270_request *kreset; /* Single keyboard reset request. */
unsigned char inattr; /* Visible/invisible input. */
int throttle, attn; /* tty throttle/unthrottle. */
struct tasklet_struct readlet; /* Tasklet to issue read request. */
struct kbd_data *kbd; /* key_maps stuff. */
/* Escape sequence parsing. */
int esc_state, esc_ques, esc_npar;
int esc_par[ESCAPE_NPAR];
unsigned int saved_cx, saved_cy;
unsigned int saved_highlight, saved_f_color;
/* Command recalling. */
struct list_head rcl_lines; /* List of recallable lines. */
struct list_head *rcl_walk; /* Point in rcl_lines list. */
int rcl_nr, rcl_max; /* Number/max number of rcl_lines. */
/* Character array for put_char/flush_chars. */
unsigned int char_count;
char char_buf[TTY3270_CHAR_BUF_SIZE];
};
/* tty3270->update_flags. See tty3270_update for details. */
#define TTY_UPDATE_ERASE 1 /* Use EWRITEA instead of WRITE. */
#define TTY_UPDATE_LIST 2 /* Update lines in tty3270->update. */
#define TTY_UPDATE_INPUT 4 /* Update input line. */
#define TTY_UPDATE_STATUS 8 /* Update status line. */
#define TTY_UPDATE_ALL 15
static void tty3270_update(struct tty3270 *);
/*
* Setup timeout for a device. On timeout trigger an update.
*/
void
tty3270_set_timer(struct tty3270 *tp, int expires)
{
if (expires == 0) {
if (timer_pending(&tp->timer)) {
raw3270_put_view(&tp->view);
del_timer(&tp->timer);
}
return;
}
if (timer_pending(&tp->timer)) {
if (mod_timer(&tp->timer, jiffies + expires))
return;
}
raw3270_get_view(&tp->view);
tp->timer.function = (void (*)(unsigned long)) tty3270_update;
tp->timer.data = (unsigned long) tp;
tp->timer.expires = jiffies + expires;
add_timer(&tp->timer);
}
/*
* The input line are the two last lines of the screen.
*/
static void
tty3270_update_prompt(struct tty3270 *tp, char *input, int count)
{
struct string *line;
unsigned int off;
line = tp->prompt;
if (count != 0)
line->string[5] = TF_INMDT;
else
line->string[5] = tp->inattr;
if (count > tp->view.cols * 2 - 11)
count = tp->view.cols * 2 - 11;
memcpy(line->string + 6, input, count);
line->string[6 + count] = TO_IC;
/* Clear to end of input line. */
if (count < tp->view.cols * 2 - 11) {
line->string[7 + count] = TO_RA;
line->string[10 + count] = 0;
off = tp->view.cols * tp->view.rows - 9;
raw3270_buffer_address(tp->view.dev, line->string+count+8, off);
line->len = 11 + count;
} else
line->len = 7 + count;
tp->update_flags |= TTY_UPDATE_INPUT;
}
static void
tty3270_create_prompt(struct tty3270 *tp)
{
static const unsigned char blueprint[] =
{ TO_SBA, 0, 0, 0x6e, TO_SF, TF_INPUT,
/* empty input string */
TO_IC, TO_RA, 0, 0, 0 };
struct string *line;
unsigned int offset;
line = alloc_string(&tp->freemem,
sizeof(blueprint) + tp->view.cols * 2 - 9);
tp->prompt = line;
tp->inattr = TF_INPUT;
/* Copy blueprint to status line */
memcpy(line->string, blueprint, sizeof(blueprint));
line->len = sizeof(blueprint);
/* Set output offsets. */
offset = tp->view.cols * (tp->view.rows - 2);
raw3270_buffer_address(tp->view.dev, line->string + 1, offset);
offset = tp->view.cols * tp->view.rows - 9;
raw3270_buffer_address(tp->view.dev, line->string + 8, offset);
/* Allocate input string for reading. */
tp->input = alloc_string(&tp->freemem, tp->view.cols * 2 - 9 + 6);
}
/*
* The status line is the last line of the screen. It shows the string
* "Running"/"Holding" in the lower right corner of the screen.
*/
static void
tty3270_update_status(struct tty3270 * tp)
{
char *str;
str = (tp->nr_up != 0) ? "History" : "Running";
memcpy(tp->status->string + 8, str, 7);
codepage_convert(tp->view.ascebc, tp->status->string + 8, 7);
tp->update_flags |= TTY_UPDATE_STATUS;
}
static void
tty3270_create_status(struct tty3270 * tp)
{
static const unsigned char blueprint[] =
{ TO_SBA, 0, 0, TO_SF, TF_LOG, TO_SA, TAT_COLOR, TAC_GREEN,
0, 0, 0, 0, 0, 0, 0, TO_SF, TF_LOG, TO_SA, TAT_COLOR,
TAC_RESET };
struct string *line;
unsigned int offset;
line = alloc_string(&tp->freemem,sizeof(blueprint));
tp->status = line;
/* Copy blueprint to status line */
memcpy(line->string, blueprint, sizeof(blueprint));
/* Set address to start of status string (= last 9 characters). */
offset = tp->view.cols * tp->view.rows - 9;
raw3270_buffer_address(tp->view.dev, line->string + 1, offset);
}
/*
* Set output offsets to 3270 datastream fragment of a tty string.
* (TO_SBA offset at the start and TO_RA offset at the end of the string)
*/
static void
tty3270_update_string(struct tty3270 *tp, struct string *line, int nr)
{
unsigned char *cp;
raw3270_buffer_address(tp->view.dev, line->string + 1,
tp->view.cols * nr);
cp = line->string + line->len - 4;
if (*cp == TO_RA)
raw3270_buffer_address(tp->view.dev, cp + 1,
tp->view.cols * (nr + 1));
}
/*
* Rebuild update list to print all lines.
*/
static void
tty3270_rebuild_update(struct tty3270 *tp)
{
struct string *s, *n;
int line, nr_up;
/*
* Throw away update list and create a new one,
* containing all lines that will fit on the screen.
*/
list_for_each_entry_safe(s, n, &tp->update, update)
list_del_init(&s->update);
line = tp->view.rows - 3;
nr_up = tp->nr_up;
list_for_each_entry_reverse(s, &tp->lines, list) {
if (nr_up > 0) {
nr_up--;
continue;
}
tty3270_update_string(tp, s, line);
list_add(&s->update, &tp->update);
if (--line < 0)
break;
}
tp->update_flags |= TTY_UPDATE_LIST;
}
/*
* Alloc string for size bytes. If there is not enough room in
* freemem, free strings until there is room.
*/
static struct string *
tty3270_alloc_string(struct tty3270 *tp, size_t size)
{
struct string *s, *n;
s = alloc_string(&tp->freemem, size);
if (s)
return s;
list_for_each_entry_safe(s, n, &tp->lines, list) {
BUG_ON(tp->nr_lines <= tp->view.rows - 2);
list_del(&s->list);
if (!list_empty(&s->update))
list_del(&s->update);
tp->nr_lines--;
if (free_string(&tp->freemem, s) >= size)
break;
}
s = alloc_string(&tp->freemem, size);
BUG_ON(!s);
if (tp->nr_up != 0 &&
tp->nr_up + tp->view.rows - 2 >= tp->nr_lines) {
tp->nr_up = tp->nr_lines - tp->view.rows + 2;
tty3270_rebuild_update(tp);
tty3270_update_status(tp);
}
return s;
}
/*
* Add an empty line to the list.
*/
static void
tty3270_blank_line(struct tty3270 *tp)
{
static const unsigned char blueprint[] =
{ TO_SBA, 0, 0, TO_SA, TAT_EXTHI, TAX_RESET,
TO_SA, TAT_COLOR, TAC_RESET, TO_RA, 0, 0, 0 };
struct string *s;
s = tty3270_alloc_string(tp, sizeof(blueprint));
memcpy(s->string, blueprint, sizeof(blueprint));
s->len = sizeof(blueprint);
list_add_tail(&s->list, &tp->lines);
tp->nr_lines++;
if (tp->nr_up != 0)
tp->nr_up++;
}
/*
* Write request completion callback.
*/
static void
tty3270_write_callback(struct raw3270_request *rq, void *data)
{
struct tty3270 *tp;
tp = (struct tty3270 *) rq->view;
if (rq->rc != 0) {
/* Write wasn't successfull. Refresh all. */
tty3270_rebuild_update(tp);
tp->update_flags = TTY_UPDATE_ALL;
tty3270_set_timer(tp, 1);
}
raw3270_request_reset(rq);
xchg(&tp->write, rq);
}
/*
* Update 3270 display.
*/
static void
tty3270_update(struct tty3270 *tp)
{
static char invalid_sba[2] = { 0xff, 0xff };
struct raw3270_request *wrq;
unsigned long updated;
struct string *s, *n;
char *sba, *str;
int rc, len;
wrq = xchg(&tp->write, 0);
if (!wrq) {
tty3270_set_timer(tp, 1);
return;
}
spin_lock(&tp->view.lock);
updated = 0;
if (tp->update_flags & TTY_UPDATE_ERASE) {
/* Use erase write alternate to erase display. */
raw3270_request_set_cmd(wrq, TC_EWRITEA);
updated |= TTY_UPDATE_ERASE;
} else
raw3270_request_set_cmd(wrq, TC_WRITE);
raw3270_request_add_data(wrq, &tp->wcc, 1);
tp->wcc = TW_NONE;
/*
* Update status line.
*/
if (tp->update_flags & TTY_UPDATE_STATUS)
if (raw3270_request_add_data(wrq, tp->status->string,
tp->status->len) == 0)
updated |= TTY_UPDATE_STATUS;
/*
* Write input line.
*/
if (tp->update_flags & TTY_UPDATE_INPUT)
if (raw3270_request_add_data(wrq, tp->prompt->string,
tp->prompt->len) == 0)
updated |= TTY_UPDATE_INPUT;
sba = invalid_sba;
if (tp->update_flags & TTY_UPDATE_LIST) {
/* Write strings in the update list to the screen. */
list_for_each_entry_safe(s, n, &tp->update, update) {
str = s->string;
len = s->len;
/*
* Skip TO_SBA at the start of the string if the
* last output position matches the start address
* of this line.
*/
if (s->string[1] == sba[0] && s->string[2] == sba[1])
str += 3, len -= 3;
if (raw3270_request_add_data(wrq, str, len) != 0)
break;
list_del_init(&s->update);
sba = s->string + s->len - 3;
}
if (list_empty(&tp->update))
updated |= TTY_UPDATE_LIST;
}
wrq->callback = tty3270_write_callback;
rc = raw3270_start(&tp->view, wrq);
if (rc == 0) {
tp->update_flags &= ~updated;
if (tp->update_flags)
tty3270_set_timer(tp, 1);
} else {
raw3270_request_reset(wrq);
xchg(&tp->write, wrq);
}
spin_unlock(&tp->view.lock);
raw3270_put_view(&tp->view);
}
/*
* Command recalling.
*/
static void
tty3270_rcl_add(struct tty3270 *tp, char *input, int len)
{
struct string *s;
tp->rcl_walk = 0;
if (len <= 0)
return;
if (tp->rcl_nr >= tp->rcl_max) {
s = list_entry(tp->rcl_lines.next, struct string, list);
list_del(&s->list);
free_string(&tp->freemem, s);
tp->rcl_nr--;
}
s = tty3270_alloc_string(tp, len);
memcpy(s->string, input, len);
list_add_tail(&s->list, &tp->rcl_lines);
tp->rcl_nr++;
}
static void
tty3270_rcl_backward(struct kbd_data *kbd)
{
struct tty3270 *tp;
struct string *s;
tp = kbd->tty->driver_data;
spin_lock_bh(&tp->view.lock);
if (tp->inattr == TF_INPUT) {
if (tp->rcl_walk && tp->rcl_walk->prev != &tp->rcl_lines)
tp->rcl_walk = tp->rcl_walk->prev;
else if (!list_empty(&tp->rcl_lines))
tp->rcl_walk = tp->rcl_lines.prev;
s = tp->rcl_walk ?
list_entry(tp->rcl_walk, struct string, list) : 0;
if (tp->rcl_walk) {
s = list_entry(tp->rcl_walk, struct string, list);
tty3270_update_prompt(tp, s->string, s->len);
} else
tty3270_update_prompt(tp, 0, 0);
tty3270_set_timer(tp, 1);
}
spin_unlock_bh(&tp->view.lock);
}
/*
* Deactivate tty view.
*/
static void
tty3270_exit_tty(struct kbd_data *kbd)
{
struct tty3270 *tp;
tp = kbd->tty->driver_data;
raw3270_deactivate_view(&tp->view);
}
/*
* Scroll forward in history.
*/
static void
tty3270_scroll_forward(struct kbd_data *kbd)
{
struct tty3270 *tp;
int nr_up;
tp = kbd->tty->driver_data;
spin_lock_bh(&tp->view.lock);
nr_up = tp->nr_up - tp->view.rows + 2;
if (nr_up < 0)
nr_up = 0;
if (nr_up != tp->nr_up) {
tp->nr_up = nr_up;
tty3270_rebuild_update(tp);
tty3270_update_status(tp);
tty3270_set_timer(tp, 1);
}
spin_unlock_bh(&tp->view.lock);
}
/*
* Scroll backward in history.
*/
static void
tty3270_scroll_backward(struct kbd_data *kbd)
{
struct tty3270 *tp;
int nr_up;
tp = kbd->tty->driver_data;
spin_lock_bh(&tp->view.lock);
nr_up = tp->nr_up + tp->view.rows - 2;
if (nr_up + tp->view.rows - 2 > tp->nr_lines)
nr_up = tp->nr_lines - tp->view.rows + 2;
if (nr_up != tp->nr_up) {
tp->nr_up = nr_up;
tty3270_rebuild_update(tp);
tty3270_update_status(tp);
tty3270_set_timer(tp, 1);
}
spin_unlock_bh(&tp->view.lock);
}
/*
* Pass input line to tty.
*/
static void
tty3270_read_tasklet(struct raw3270_request *rrq)
{
static char kreset_data = TW_KR;
struct tty3270 *tp;
char *input;
int len;
tp = (struct tty3270 *) rrq->view;
spin_lock_bh(&tp->view.lock);
/*
* Two AID keys are special: For 0x7d (enter) the input line
* has to be emitted to the tty and for 0x6d the screen
* needs to be redrawn.
*/
input = 0;
len = 0;
if (tp->input->string[0] == 0x7d) {
/* Enter: write input to tty. */
input = tp->input->string + 6;
len = tp->input->len - 6 - rrq->rescnt;
if (tp->inattr != TF_INPUTN)
tty3270_rcl_add(tp, input, len);
if (tp->nr_up > 0) {
tp->nr_up = 0;
tty3270_rebuild_update(tp);
tty3270_update_status(tp);
}
/* Clear input area. */
tty3270_update_prompt(tp, 0, 0);
tty3270_set_timer(tp, 1);
} else if (tp->input->string[0] == 0x6d) {
/* Display has been cleared. Redraw. */
tty3270_rebuild_update(tp);
tp->update_flags = TTY_UPDATE_ALL;
tty3270_set_timer(tp, 1);
}
spin_unlock_bh(&tp->view.lock);
/* Start keyboard reset command. */
raw3270_request_reset(tp->kreset);
raw3270_request_set_cmd(tp->kreset, TC_WRITE);
raw3270_request_add_data(tp->kreset, &kreset_data, 1);
raw3270_start(&tp->view, tp->kreset);
/* Emit input string. */
if (tp->tty) {
while (len-- > 0)
kbd_keycode(tp->kbd, *input++);
/* Emit keycode for AID byte. */
kbd_keycode(tp->kbd, 256 + tp->input->string[0]);
}
raw3270_request_reset(rrq);
xchg(&tp->read, rrq);
raw3270_put_view(&tp->view);
}
/*
* Read request completion callback.
*/
static void
tty3270_read_callback(struct raw3270_request *rq, void *data)
{
raw3270_get_view(rq->view);
/* Schedule tasklet to pass input to tty. */
tasklet_schedule(&((struct tty3270 *) rq->view)->readlet);
}
/*
* Issue a read request. Call with device lock.
*/
static void
tty3270_issue_read(struct tty3270 *tp, int lock)
{
struct raw3270_request *rrq;
int rc;
rrq = xchg(&tp->read, 0);
if (!rrq)
/* Read already scheduled. */
return;
rrq->callback = tty3270_read_callback;
rrq->callback_data = tp;
raw3270_request_set_cmd(rrq, TC_READMOD);
raw3270_request_set_data(rrq, tp->input->string, tp->input->len);
/* Issue the read modified request. */
if (lock) {
rc = raw3270_start(&tp->view, rrq);
} else
rc = raw3270_start_irq(&tp->view, rrq);
if (rc) {
raw3270_request_reset(rrq);
xchg(&tp->read, rrq);
}
}
/*
* Switch to the tty view.
*/
static int
tty3270_activate(struct raw3270_view *view)
{
struct tty3270 *tp;
unsigned long flags;
tp = (struct tty3270 *) view;
spin_lock_irqsave(&tp->view.lock, flags);
tp->nr_up = 0;
tty3270_rebuild_update(tp);
tty3270_update_status(tp);
tp->update_flags = TTY_UPDATE_ALL;
tty3270_set_timer(tp, 1);
spin_unlock_irqrestore(&tp->view.lock, flags);
start_tty(tp->tty);
return 0;
}
static void
tty3270_deactivate(struct raw3270_view *view)
{
struct tty3270 *tp;
tp = (struct tty3270 *) view;
if (tp && tp->tty)
stop_tty(tp->tty);
}
static int
tty3270_irq(struct tty3270 *tp, struct raw3270_request *rq, struct irb *irb)
{
/* Handle ATTN. Schedule tasklet to read aid. */
if (irb->scsw.dstat & DEV_STAT_ATTENTION) {
if (!tp->throttle)
tty3270_issue_read(tp, 0);
else
tp->attn = 1;
}
if (rq) {
if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
rq->rc = -EIO;
else
/* Normal end. Copy residual count. */
rq->rescnt = irb->scsw.count;
}
return RAW3270_IO_DONE;
}
/*
* Allocate tty3270 structure.
*/
static struct tty3270 *
tty3270_alloc_view(void)
{
struct tty3270 *tp;
int pages;
tp = kmalloc(sizeof(struct tty3270),GFP_KERNEL);
if (!tp)
goto out_err;
memset(tp, 0, sizeof(struct tty3270));
tp->freemem_pages =
kmalloc(sizeof(void *) * TTY3270_STRING_PAGES, GFP_KERNEL);
if (!tp->freemem_pages)
goto out_tp;
INIT_LIST_HEAD(&tp->freemem);
for (pages = 0; pages < TTY3270_STRING_PAGES; pages++) {
tp->freemem_pages[pages] = (void *)
__get_free_pages(GFP_KERNEL|GFP_DMA, 0);
if (!tp->freemem_pages[pages])
goto out_pages;
add_string_memory(&tp->freemem,
tp->freemem_pages[pages], PAGE_SIZE);
}
tp->write = raw3270_request_alloc(TTY3270_OUTPUT_BUFFER_SIZE);
if (!tp->write)
goto out_pages;
tp->read = raw3270_request_alloc(0);
if (!tp->read)
goto out_write;
tp->kreset = raw3270_request_alloc(1);
if (!tp->kreset)
goto out_read;
tp->kbd = kbd_alloc();
if (!tp->kbd)
goto out_reset;
return tp;
out_reset:
raw3270_request_free(tp->kreset);
out_read:
raw3270_request_free(tp->read);
out_write:
raw3270_request_free(tp->write);
out_pages:
while (pages--)
free_pages((unsigned long) tp->freemem_pages[pages], 0);
kfree(tp->freemem_pages);
out_tp:
kfree(tp);
out_err:
return ERR_PTR(-ENOMEM);
}
/*
* Free tty3270 structure.
*/
static void
tty3270_free_view(struct tty3270 *tp)
{
int pages;
kbd_free(tp->kbd);
raw3270_request_free(tp->kreset);
raw3270_request_free(tp->read);
raw3270_request_free(tp->write);
for (pages = 0; pages < TTY3270_STRING_PAGES; pages++)
free_pages((unsigned long) tp->freemem_pages[pages], 0);
kfree(tp->freemem_pages);
kfree(tp);
}
/*
* Allocate tty3270 screen.
*/
static int
tty3270_alloc_screen(struct tty3270 *tp)
{
unsigned long size;
int lines;
size = sizeof(struct tty3270_line) * (tp->view.rows - 2);
tp->screen = kmalloc(size, GFP_KERNEL);
if (!tp->screen)
goto out_err;
memset(tp->screen, 0, size);
for (lines = 0; lines < tp->view.rows - 2; lines++) {
size = sizeof(struct tty3270_cell) * tp->view.cols;
tp->screen[lines].cells = kmalloc(size, GFP_KERNEL);
if (!tp->screen[lines].cells)
goto out_screen;
memset(tp->screen[lines].cells, 0, size);
}
return 0;
out_screen:
while (lines--)
kfree(tp->screen[lines].cells);
kfree(tp->screen);
out_err:
return -ENOMEM;
}
/*
* Free tty3270 screen.
*/
static void
tty3270_free_screen(struct tty3270 *tp)
{
int lines;
for (lines = 0; lines < tp->view.rows - 2; lines++)
kfree(tp->screen[lines].cells);
kfree(tp->screen);
}
/*
* Unlink tty3270 data structure from tty.
*/
static void
tty3270_release(struct raw3270_view *view)
{
struct tty3270 *tp;
struct tty_struct *tty;
tp = (struct tty3270 *) view;
tty = tp->tty;
if (tty) {
tty->driver_data = 0;
tp->tty = tp->kbd->tty = 0;
tty_hangup(tty);
raw3270_put_view(&tp->view);
}
}
/*
* Free tty3270 data structure
*/
static void
tty3270_free(struct raw3270_view *view)
{
tty3270_free_screen((struct tty3270 *) view);
tty3270_free_view((struct tty3270 *) view);
}
struct raw3270_fn tty3270_fn = {
.activate = tty3270_activate,
.deactivate = tty3270_deactivate,
.intv = (void *) tty3270_irq,
.release = tty3270_release,
.free = tty3270_free
};
/*
* This routine is called whenever a 3270 tty is opened.
*/
static int
tty3270_open(struct tty_struct *tty, struct file * filp)
{
struct tty3270 *tp;
int i, rc;
if (tty->count > 1)
return 0;
/* Check if the tty3270 is already there. */
tp = (struct tty3270 *) raw3270_find_view(&tty3270_fn, tty->index);
if (!IS_ERR(tp)) {
tty->driver_data = tp;
tty->winsize.ws_row = tp->view.rows - 2;
tty->winsize.ws_col = tp->view.cols;
tty->low_latency = 0;
tp->tty = tty;
tp->kbd->tty = tty;
tp->inattr = TF_INPUT;
return 0;
}
/* Allocate tty3270 structure on first open. */
tp = tty3270_alloc_view();
if (IS_ERR(tp))
return PTR_ERR(tp);
INIT_LIST_HEAD(&tp->lines);
INIT_LIST_HEAD(&tp->update);
INIT_LIST_HEAD(&tp->rcl_lines);
tp->rcl_max = 20;
init_timer(&tp->timer);
tasklet_init(&tp->readlet,
(void (*)(unsigned long)) tty3270_read_tasklet,
(unsigned long) tp->read);
rc = raw3270_add_view(&tp->view, &tty3270_fn, tty->index);
if (rc) {
tty3270_free_view(tp);
return rc;
}
rc = tty3270_alloc_screen(tp);
if (rc) {
raw3270_del_view(&tp->view);
raw3270_put_view(&tp->view);
return rc;
}
tp->tty = tty;
tty->low_latency = 0;
tty->driver_data = tp;
tty->winsize.ws_row = tp->view.rows - 2;
tty->winsize.ws_col = tp->view.cols;
tty3270_create_prompt(tp);
tty3270_create_status(tp);
tty3270_update_status(tp);
/* Create blank line for every line in the tty output area. */
for (i = 0; i < tp->view.rows - 2; i++)
tty3270_blank_line(tp);
tp->kbd->tty = tty;
tp->kbd->fn_handler[KVAL(K_INCRCONSOLE)] = tty3270_exit_tty;
tp->kbd->fn_handler[KVAL(K_SCROLLBACK)] = tty3270_scroll_backward;
tp->kbd->fn_handler[KVAL(K_SCROLLFORW)] = tty3270_scroll_forward;
tp->kbd->fn_handler[KVAL(K_CONS)] = tty3270_rcl_backward;
kbd_ascebc(tp->kbd, tp->view.ascebc);
raw3270_activate_view(&tp->view);
return 0;
}
/*
* This routine is called when the 3270 tty is closed. We wait
* for the remaining request to be completed. Then we clean up.
*/
static void
tty3270_close(struct tty_struct *tty, struct file * filp)
{
struct tty3270 *tp;
if (tty->count > 1)
return;
tp = (struct tty3270 *) tty->driver_data;
if (tp) {
tty->driver_data = 0;
tp->tty = tp->kbd->tty = 0;
raw3270_put_view(&tp->view);
}
}
/*
* We always have room.
*/
static int
tty3270_write_room(struct tty_struct *tty)
{
return INT_MAX;
}
/*
* Insert character into the screen at the current position with the
* current color and highlight. This function does NOT do cursor movement.
*/
static void
tty3270_put_character(struct tty3270 *tp, char ch)
{
struct tty3270_line *line;
struct tty3270_cell *cell;
line = tp->screen + tp->cy;
if (line->len <= tp->cx) {
while (line->len < tp->cx) {
cell = line->cells + line->len;
cell->character = tp->view.ascebc[' '];
cell->highlight = tp->highlight;
cell->f_color = tp->f_color;
line->len++;
}
line->len++;
}
cell = line->cells + tp->cx;
cell->character = tp->view.ascebc[(unsigned int) ch];
cell->highlight = tp->highlight;
cell->f_color = tp->f_color;
}
/*
* Convert a tty3270_line to a 3270 data fragment usable for output.
*/
static void
tty3270_convert_line(struct tty3270 *tp, int line_nr)
{
struct tty3270_line *line;
struct tty3270_cell *cell;
struct string *s, *n;
unsigned char highlight;
unsigned char f_color;
char *cp;
int flen, i;
/* Determine how long the fragment will be. */
flen = 3; /* Prefix (TO_SBA). */
line = tp->screen + line_nr;
flen += line->len;
highlight = TAX_RESET;
f_color = TAC_RESET;
for (i = 0, cell = line->cells; i < line->len; i++, cell++) {
if (cell->highlight != highlight) {
flen += 3; /* TO_SA to switch highlight. */
highlight = cell->highlight;
}
if (cell->f_color != f_color) {
flen += 3; /* TO_SA to switch color. */
f_color = cell->f_color;
}
}
if (highlight != TAX_RESET)
flen += 3; /* TO_SA to reset hightlight. */
if (f_color != TAC_RESET)
flen += 3; /* TO_SA to reset color. */
if (line->len < tp->view.cols)
flen += 4; /* Postfix (TO_RA). */
/* Find the line in the list. */
i = tp->view.rows - 2 - line_nr;
list_for_each_entry_reverse(s, &tp->lines, list)
if (--i <= 0)
break;
/*
* Check if the line needs to get reallocated.
*/
if (s->len != flen) {
/* Reallocate string. */
n = tty3270_alloc_string(tp, flen);
list_add(&n->list, &s->list);
list_del_init(&s->list);
if (!list_empty(&s->update))
list_del_init(&s->update);
free_string(&tp->freemem, s);
s = n;
}
/* Write 3270 data fragment. */
cp = s->string;
*cp++ = TO_SBA;
*cp++ = 0;
*cp++ = 0;
highlight = TAX_RESET;
f_color = TAC_RESET;
for (i = 0, cell = line->cells; i < line->len; i++, cell++) {
if (cell->highlight != highlight) {
*cp++ = TO_SA;
*cp++ = TAT_EXTHI;
*cp++ = cell->highlight;
highlight = cell->highlight;
}
if (cell->f_color != f_color) {
*cp++ = TO_SA;
*cp++ = TAT_COLOR;
*cp++ = cell->f_color;
f_color = cell->f_color;
}
*cp++ = cell->character;
}
if (highlight != TAX_RESET) {
*cp++ = TO_SA;
*cp++ = TAT_EXTHI;
*cp++ = TAX_RESET;
}
if (f_color != TAC_RESET) {
*cp++ = TO_SA;
*cp++ = TAT_COLOR;
*cp++ = TAC_RESET;
}
if (line->len < tp->view.cols) {
*cp++ = TO_RA;
*cp++ = 0;
*cp++ = 0;
*cp++ = 0;
}
if (tp->nr_up + line_nr < tp->view.rows - 2) {
/* Line is currently visible on screen. */
tty3270_update_string(tp, s, line_nr);
/* Add line to update list. */
if (list_empty(&s->update)) {
list_add_tail(&s->update, &tp->update);
tp->update_flags |= TTY_UPDATE_LIST;
}
}
}
/*
* Do carriage return.
*/
static void
tty3270_cr(struct tty3270 *tp)
{
tp->cx = 0;
}
/*
* Do line feed.
*/
static void
tty3270_lf(struct tty3270 *tp)
{
struct tty3270_line temp;
int i;
tty3270_convert_line(tp, tp->cy);
if (tp->cy < tp->view.rows - 3) {
tp->cy++;
return;
}
/* Last line just filled up. Add new, blank line. */
tty3270_blank_line(tp);
temp = tp->screen[0];
temp.len = 0;
for (i = 0; i < tp->view.rows - 3; i++)
tp->screen[i] = tp->screen[i+1];
tp->screen[tp->view.rows - 3] = temp;
tty3270_rebuild_update(tp);
}
static void
tty3270_ri(struct tty3270 *tp)
{
if (tp->cy > 0) {
tty3270_convert_line(tp, tp->cy);
tp->cy--;
}
}
/*
* Insert characters at current position.
*/
static void
tty3270_insert_characters(struct tty3270 *tp, int n)
{
struct tty3270_line *line;
int k;
line = tp->screen + tp->cy;
while (line->len < tp->cx) {
line->cells[line->len].character = tp->view.ascebc[' '];
line->cells[line->len].highlight = TAX_RESET;
line->cells[line->len].f_color = TAC_RESET;
line->len++;
}
if (n > tp->view.cols - tp->cx)
n = tp->view.cols - tp->cx;
k = min_t(int, line->len - tp->cx, tp->view.cols - tp->cx - n);
while (k--)
line->cells[tp->cx + n + k] = line->cells[tp->cx + k];
line->len += n;
if (line->len > tp->view.cols)
line->len = tp->view.cols;
while (n-- > 0) {
line->cells[tp->cx + n].character = tp->view.ascebc[' '];
line->cells[tp->cx + n].highlight = tp->highlight;
line->cells[tp->cx + n].f_color = tp->f_color;
}
}
/*
* Delete characters at current position.
*/
static void
tty3270_delete_characters(struct tty3270 *tp, int n)
{
struct tty3270_line *line;
int i;
line = tp->screen + tp->cy;
if (line->len <= tp->cx)
return;
if (line->len - tp->cx <= n) {
line->len = tp->cx;
return;
}
for (i = tp->cx; i + n < line->len; i++)
line->cells[i] = line->cells[i + n];
line->len -= n;
}
/*
* Erase characters at current position.
*/
static void
tty3270_erase_characters(struct tty3270 *tp, int n)
{
struct tty3270_line *line;
struct tty3270_cell *cell;
line = tp->screen + tp->cy;
while (line->len > tp->cx && n-- > 0) {
cell = line->cells + tp->cx++;
cell->character = ' ';
cell->highlight = TAX_RESET;
cell->f_color = TAC_RESET;
}
tp->cx += n;
tp->cx = min_t(int, tp->cx, tp->view.cols - 1);
}
/*
* Erase line, 3 different cases:
* Esc [ 0 K Erase from current position to end of line inclusive
* Esc [ 1 K Erase from beginning of line to current position inclusive
* Esc [ 2 K Erase entire line (without moving cursor)
*/
static void
tty3270_erase_line(struct tty3270 *tp, int mode)
{
struct tty3270_line *line;
struct tty3270_cell *cell;
int i;
line = tp->screen + tp->cy;
if (mode == 0)
line->len = tp->cx;
else if (mode == 1) {
for (i = 0; i < tp->cx; i++) {
cell = line->cells + i;
cell->character = ' ';
cell->highlight = TAX_RESET;
cell->f_color = TAC_RESET;
}
if (line->len <= tp->cx)
line->len = tp->cx + 1;
} else if (mode == 2)
line->len = 0;
tty3270_convert_line(tp, tp->cy);
}
/*
* Erase display, 3 different cases:
* Esc [ 0 J Erase from current position to bottom of screen inclusive
* Esc [ 1 J Erase from top of screen to current position inclusive
* Esc [ 2 J Erase entire screen (without moving the cursor)
*/
static void
tty3270_erase_display(struct tty3270 *tp, int mode)
{
int i;
if (mode == 0) {
tty3270_erase_line(tp, 0);
for (i = tp->cy + 1; i < tp->view.rows - 2; i++) {
tp->screen[i].len = 0;
tty3270_convert_line(tp, i);
}
} else if (mode == 1) {
for (i = 0; i < tp->cy; i++) {
tp->screen[i].len = 0;
tty3270_convert_line(tp, i);
}
tty3270_erase_line(tp, 1);
} else if (mode == 2) {
for (i = 0; i < tp->view.rows - 2; i++) {
tp->screen[i].len = 0;
tty3270_convert_line(tp, i);
}
}
tty3270_rebuild_update(tp);
}
/*
* Set attributes found in an escape sequence.
* Esc [ <attr> ; <attr> ; ... m
*/
static void
tty3270_set_attributes(struct tty3270 *tp)
{
static unsigned char f_colors[] = {
TAC_DEFAULT, TAC_RED, TAC_GREEN, TAC_YELLOW, TAC_BLUE,
TAC_PINK, TAC_TURQ, TAC_WHITE, 0, TAC_DEFAULT
};
int i, attr;
for (i = 0; i <= tp->esc_npar; i++) {
attr = tp->esc_par[i];
switch (attr) {
case 0: /* Reset */
tp->highlight = TAX_RESET;
tp->f_color = TAC_RESET;
break;
/* Highlight. */
case 4: /* Start underlining. */
tp->highlight = TAX_UNDER;
break;
case 5: /* Start blink. */
tp->highlight = TAX_BLINK;
break;
case 7: /* Start reverse. */
tp->highlight = TAX_REVER;
break;
case 24: /* End underlining */
if (tp->highlight == TAX_UNDER)
tp->highlight = TAX_RESET;
break;
case 25: /* End blink. */
if (tp->highlight == TAX_BLINK)
tp->highlight = TAX_RESET;
break;
case 27: /* End reverse. */
if (tp->highlight == TAX_REVER)
tp->highlight = TAX_RESET;
break;
/* Foreground color. */
case 30: /* Black */
case 31: /* Red */
case 32: /* Green */
case 33: /* Yellow */
case 34: /* Blue */
case 35: /* Magenta */
case 36: /* Cyan */
case 37: /* White */
case 39: /* Black */
tp->f_color = f_colors[attr - 30];
break;
}
}
}
static inline int
tty3270_getpar(struct tty3270 *tp, int ix)
{
return (tp->esc_par[ix] > 0) ? tp->esc_par[ix] : 1;
}
static void
tty3270_goto_xy(struct tty3270 *tp, int cx, int cy)
{
tp->cx = min_t(int, tp->view.cols - 1, max_t(int, 0, cx));
cy = min_t(int, tp->view.rows - 3, max_t(int, 0, cy));
if (cy != tp->cy) {
tty3270_convert_line(tp, tp->cy);
tp->cy = cy;
}
}
/*
* Process escape sequences. Known sequences:
* Esc 7 Save Cursor Position
* Esc 8 Restore Cursor Position
* Esc [ Pn ; Pn ; .. m Set attributes
* Esc [ Pn ; Pn H Cursor Position
* Esc [ Pn ; Pn f Cursor Position
* Esc [ Pn A Cursor Up
* Esc [ Pn B Cursor Down
* Esc [ Pn C Cursor Forward
* Esc [ Pn D Cursor Backward
* Esc [ Pn G Cursor Horizontal Absolute
* Esc [ Pn X Erase Characters
* Esc [ Ps J Erase in Display
* Esc [ Ps K Erase in Line
* // FIXME: add all the new ones.
*
* Pn is a numeric parameter, a string of zero or more decimal digits.
* Ps is a selective parameter.
*/
static void
tty3270_escape_sequence(struct tty3270 *tp, char ch)
{
enum { ESnormal, ESesc, ESsquare, ESgetpars };
if (tp->esc_state == ESnormal) {
if (ch == 0x1b)
/* Starting new escape sequence. */
tp->esc_state = ESesc;
return;
}
if (tp->esc_state == ESesc) {
tp->esc_state = ESnormal;
switch (ch) {
case '[':
tp->esc_state = ESsquare;
break;
case 'E':
tty3270_cr(tp);
tty3270_lf(tp);
break;
case 'M':
tty3270_ri(tp);
break;
case 'D':
tty3270_lf(tp);
break;
case 'Z': /* Respond ID. */
kbd_puts_queue(tp->tty, "\033[?6c");
break;
case '7': /* Save cursor position. */
tp->saved_cx = tp->cx;
tp->saved_cy = tp->cy;
tp->saved_highlight = tp->highlight;
tp->saved_f_color = tp->f_color;
break;
case '8': /* Restore cursor position. */
tty3270_convert_line(tp, tp->cy);
tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy);
tp->highlight = tp->saved_highlight;
tp->f_color = tp->saved_f_color;
break;
case 'c': /* Reset terminal. */
tp->cx = tp->saved_cx = 0;
tp->cy = tp->saved_cy = 0;
tp->highlight = tp->saved_highlight = TAX_RESET;
tp->f_color = tp->saved_f_color = TAC_RESET;
tty3270_erase_display(tp, 2);
break;
}
return;
}
if (tp->esc_state == ESsquare) {
tp->esc_state = ESgetpars;
memset(tp->esc_par, 0, sizeof(tp->esc_par));
tp->esc_npar = 0;
tp->esc_ques = (ch == '?');
if (tp->esc_ques)
return;
}
if (tp->esc_state == ESgetpars) {
if (ch == ';' && tp->esc_npar < ESCAPE_NPAR - 1) {
tp->esc_npar++;
return;
}
if (ch >= '0' && ch <= '9') {
tp->esc_par[tp->esc_npar] *= 10;
tp->esc_par[tp->esc_npar] += ch - '0';
return;
}
}
tp->esc_state = ESnormal;
if (ch == 'n' && !tp->esc_ques) {
if (tp->esc_par[0] == 5) /* Status report. */
kbd_puts_queue(tp->tty, "\033[0n");
else if (tp->esc_par[0] == 6) { /* Cursor report. */
char buf[40];
sprintf(buf, "\033[%d;%dR", tp->cy + 1, tp->cx + 1);
kbd_puts_queue(tp->tty, buf);
}
return;
}
if (tp->esc_ques)
return;
switch (ch) {
case 'm':
tty3270_set_attributes(tp);
break;
case 'H': /* Set cursor position. */
case 'f':
tty3270_goto_xy(tp, tty3270_getpar(tp, 1) - 1,
tty3270_getpar(tp, 0) - 1);
break;
case 'd': /* Set y position. */
tty3270_goto_xy(tp, tp->cx, tty3270_getpar(tp, 0) - 1);
break;
case 'A': /* Cursor up. */
case 'F':
tty3270_goto_xy(tp, tp->cx, tp->cy - tty3270_getpar(tp, 0));
break;
case 'B': /* Cursor down. */
case 'e':
case 'E':
tty3270_goto_xy(tp, tp->cx, tp->cy + tty3270_getpar(tp, 0));
break;
case 'C': /* Cursor forward. */
case 'a':
tty3270_goto_xy(tp, tp->cx + tty3270_getpar(tp, 0), tp->cy);
break;
case 'D': /* Cursor backward. */
tty3270_goto_xy(tp, tp->cx - tty3270_getpar(tp, 0), tp->cy);
break;
case 'G': /* Set x position. */
case '`':
tty3270_goto_xy(tp, tty3270_getpar(tp, 0), tp->cy);
break;
case 'X': /* Erase Characters. */
tty3270_erase_characters(tp, tty3270_getpar(tp, 0));
break;
case 'J': /* Erase display. */
tty3270_erase_display(tp, tp->esc_par[0]);
break;
case 'K': /* Erase line. */
tty3270_erase_line(tp, tp->esc_par[0]);
break;
case 'P': /* Delete characters. */
tty3270_delete_characters(tp, tty3270_getpar(tp, 0));
break;
case '@': /* Insert characters. */
tty3270_insert_characters(tp, tty3270_getpar(tp, 0));
break;
case 's': /* Save cursor position. */
tp->saved_cx = tp->cx;
tp->saved_cy = tp->cy;
tp->saved_highlight = tp->highlight;
tp->saved_f_color = tp->f_color;
break;
case 'u': /* Restore cursor position. */
tty3270_convert_line(tp, tp->cy);
tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy);
tp->highlight = tp->saved_highlight;
tp->f_color = tp->saved_f_color;
break;
}
}
/*
* String write routine for 3270 ttys
*/
static void
tty3270_do_write(struct tty3270 *tp, const unsigned char *buf, int count)
{
int i_msg, i;
spin_lock_bh(&tp->view.lock);
for (i_msg = 0; !tp->tty->stopped && i_msg < count; i_msg++) {
if (tp->esc_state != 0) {
/* Continue escape sequence. */
tty3270_escape_sequence(tp, buf[i_msg]);
continue;
}
switch (buf[i_msg]) {
case 0x07: /* '\a' -- Alarm */
tp->wcc |= TW_PLUSALARM;
break;
case 0x08: /* Backspace. */
if (tp->cx > 0) {
tp->cx--;
tty3270_put_character(tp, ' ');
}
break;
case 0x09: /* '\t' -- Tabulate */
for (i = tp->cx % 8; i < 8; i++) {
if (tp->cx >= tp->view.cols) {
tty3270_cr(tp);
tty3270_lf(tp);
break;
}
tty3270_put_character(tp, ' ');
tp->cx++;
}
break;
case 0x0a: /* '\n' -- New Line */
tty3270_cr(tp);
tty3270_lf(tp);
break;
case 0x0c: /* '\f' -- Form Feed */
tty3270_erase_display(tp, 2);
tp->cx = tp->cy = 0;
break;
case 0x0d: /* '\r' -- Carriage Return */
tp->cx = 0;
break;
case 0x0f: /* SuSE "exit alternate mode" */
break;
case 0x1b: /* Start escape sequence. */
tty3270_escape_sequence(tp, buf[i_msg]);
break;
default: /* Insert normal character. */
if (tp->cx >= tp->view.cols) {
tty3270_cr(tp);
tty3270_lf(tp);
}
tty3270_put_character(tp, buf[i_msg]);
tp->cx++;
break;
}
}
/* Convert current line to 3270 data fragment. */
tty3270_convert_line(tp, tp->cy);
/* Setup timer to update display after 1/10 second */
if (!timer_pending(&tp->timer))
tty3270_set_timer(tp, HZ/10);
spin_unlock_bh(&tp->view.lock);
}
/*
* String write routine for 3270 ttys
*/
static int
tty3270_write(struct tty_struct * tty, int from_user,
const unsigned char *buf, int count)
{
struct tty3270 *tp;
int length, ret;
tp = tty->driver_data;
if (!tp)
return 0;
if (tp->char_count > 0) {
tty3270_do_write(tp, tp->char_buf, tp->char_count);
tp->char_count = 0;
}
if (!from_user) {
tty3270_do_write(tp, buf, count);
return count;
}
ret = 0;
while (count > 0) {
length = count < TTY3270_CHAR_BUF_SIZE ?
count : TTY3270_CHAR_BUF_SIZE;
length -= copy_from_user(tp->char_buf, buf, length);
if (length == 0) {
if (!ret)
ret = -EFAULT;
break;
}
tty3270_do_write(tp, tp->char_buf, count);
buf += length;
count -= length;
ret += length;
}
return ret;
}
/*
* Put single characters to the ttys character buffer
*/
static void
tty3270_put_char(struct tty_struct *tty, unsigned char ch)
{
struct tty3270 *tp;
tp = tty->driver_data;
if (!tp)
return;
if (tp->char_count < TTY3270_CHAR_BUF_SIZE)
tp->char_buf[tp->char_count++] = ch;
}
/*
* Flush all characters from the ttys characeter buffer put there
* by tty3270_put_char.
*/
static void
tty3270_flush_chars(struct tty_struct *tty)
{
struct tty3270 *tp;
tp = tty->driver_data;
if (!tp)
return;
if (tp->char_count > 0) {
tty3270_do_write(tp, tp->char_buf, tp->char_count);
tp->char_count = 0;
}
}
/*
* Returns the number of characters in the output buffer. This is
* used in tty_wait_until_sent to wait until all characters have
* appeared on the screen.
*/
static int
tty3270_chars_in_buffer(struct tty_struct *tty)
{
return 0;
}
static void
tty3270_flush_buffer(struct tty_struct *tty)
{
}
/*
* Check for visible/invisible input switches
*/
static void
tty3270_set_termios(struct tty_struct *tty, struct termios *old)
{
struct tty3270 *tp;
int new;
tp = tty->driver_data;
if (!tp)
return;
spin_lock_bh(&tp->view.lock);
if (L_ICANON(tty)) {
new = L_ECHO(tty) ? TF_INPUT: TF_INPUTN;
if (new != tp->inattr) {
tp->inattr = new;
tty3270_update_prompt(tp, 0, 0);
tty3270_set_timer(tp, 1);
}
}
spin_unlock_bh(&tp->view.lock);
}
/*
* Disable reading from a 3270 tty
*/
static void
tty3270_throttle(struct tty_struct * tty)
{
struct tty3270 *tp;
tp = tty->driver_data;
if (!tp)
return;
tp->throttle = 1;
}
/*
* Enable reading from a 3270 tty
*/
static void
tty3270_unthrottle(struct tty_struct * tty)
{
struct tty3270 *tp;
tp = tty->driver_data;
if (!tp)
return;
tp->throttle = 0;
if (tp->attn)
tty3270_issue_read(tp, 1);
}
/*
* Hang up the tty device.
*/
static void
tty3270_hangup(struct tty_struct *tty)
{
// FIXME: implement
}
static void
tty3270_wait_until_sent(struct tty_struct *tty, int timeout)
{
}
static int
tty3270_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct tty3270 *tp;
tp = tty->driver_data;
if (!tp)
return -ENODEV;
if (tty->flags & (1 << TTY_IO_ERROR))
return -EIO;
return kbd_ioctl(tp->kbd, file, cmd, arg);
}
static struct tty_operations tty3270_ops = {
.open = tty3270_open,
.close = tty3270_close,
.write = tty3270_write,
.put_char = tty3270_put_char,
.flush_chars = tty3270_flush_chars,
.write_room = tty3270_write_room,
.chars_in_buffer = tty3270_chars_in_buffer,
.flush_buffer = tty3270_flush_buffer,
.throttle = tty3270_throttle,
.unthrottle = tty3270_unthrottle,
.hangup = tty3270_hangup,
.wait_until_sent = tty3270_wait_until_sent,
.ioctl = tty3270_ioctl,
.set_termios = tty3270_set_termios
};
void
tty3270_notifier(int index, int active)
{
if (active)
tty_register_device(tty3270_driver, index, 0);
else
tty_unregister_device(tty3270_driver, index);
}
/*
* 3270 tty registration code called from tty_init().
* Most kernel services (incl. kmalloc) are available at this poimt.
*/
int __init
tty3270_init(void)
{
struct tty_driver *driver;
int ret;
ret = raw3270_init();
if (ret)
return ret;
driver = alloc_tty_driver(256);
if (!driver)
return -ENOMEM;
/*
* Initialize the tty_driver structure
* Entries in tty3270_driver that are NOT initialized:
* proc_entry, set_termios, flush_buffer, set_ldisc, write_proc
*/
driver->owner = THIS_MODULE;
driver->devfs_name = "ttyTUB/";
driver->driver_name = "ttyTUB";
driver->name = "ttyTUB";
driver->major = IBM_TTY3270_MAJOR;
driver->type = TTY_DRIVER_TYPE_SYSTEM;
driver->subtype = SYSTEM_TYPE_TTY;
driver->init_termios = tty_std_termios;
driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_NO_DEVFS;
tty_set_operations(driver, &tty3270_ops);
ret = tty_register_driver(driver);
if (ret) {
printk(KERN_ERR "tty3270 registration failed with %d\n", ret);
put_tty_driver(driver);
return ret;
}
tty3270_driver = driver;
return 0;
}
static void __exit
tty3270_exit(void)
{
struct tty_driver *driver;
driver = tty3270_driver;
tty3270_driver = 0;
tty_unregister_driver(driver);
raw3270_exit();
}
MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV_MAJOR(IBM_TTY3270_MAJOR);
module_init(tty3270_init);
module_exit(tty3270_exit);
/*
* IBM/3270 Driver -- Copyright (C) 2000 UTS Global LLC
*
* tuball.c -- Initialization, termination, irq lookup
*
*
*
*
*
* Author: Richard Hitt
*/
#include <linux/config.h>
#include "tubio.h"
#ifndef MODULE
#include <linux/init.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0))
#include <asm/cpcmd.h>
#include <linux/bootmem.h>
#else
#include "../../../../arch/s390/kernel/cpcmd.h"
#endif
#endif
/* Module parameters */
int tubdebug;
int tubscrolltime = -1;
static int tubxcorrect = 1; /* Do correct ebc<->asc tables */
#ifdef MODULE
MODULE_PARM(tubdebug, "i");
MODULE_PARM(tubscrolltime, "i");
MODULE_PARM(tubxcorrect, "i");
#endif
/*
* Values for tubdebug and their effects:
* 1 - print in hex on console the first 16 bytes received
* 2 - print address at which array tubminors is allocated
* 4 - attempt to register tty3270_driver
*/
int tubnummins;
tub_t *(*tubminors)[TUBMAXMINS];
static tub_t *(*(*tubirqs)[256])[256];
unsigned char tub_ascebc[256];
unsigned char tub_ebcasc[256];
static int tubinitminors(void);
static void tubfiniminors(void);
static void tubint(int, void *, struct pt_regs *);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
static int tubmakemin(int, dev_info_t *);
#else
static int tubmakemin(int, s390_dev_info_t *);
#endif
/* Lookup-by-irq functions */
static int tubaddbyirq(tub_t *, int);
static tub_t *tubfindbyirq(int);
static void tubdelbyirq(tub_t *, int);
static void tubfiniirqs(void);
/* FIXME: put extern declarations in a header */
extern int fs3270_init(void);
extern void fs3270_fini(void);
extern int tty3270_init(void);
extern void tty3270_fini(void);
unsigned char tub_ebcgraf[64] =
{ 0x40, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f };
#ifndef MODULE
/*
* Can't have this driver a module & support console at the same time
*/
#ifdef CONFIG_TN3270_CONSOLE
static struct tty_driver *tub3270_con_device(struct console *, int *);
static void tub3270_con_unblank(void);
static void tub3270_con_write(struct console *, const char *,
unsigned int);
static struct console tub3270_con = {
"tub3270", /* name */
tub3270_con_write, /* write */
NULL, /* read */
tub3270_con_device, /* device */
tub3270_con_unblank, /* unblank */
NULL, /* setup */
CON_PRINTBUFFER, /* flags */
0, /* index */
0, /* cflag */
NULL /* next */
};
static bcb_t tub3270_con_bcb; /* Buffer that receives con writes */
static spinlock_t tub3270_con_bcblock; /* Lock for the buffer */
static int tub3270_con_irq = -1; /* set nonneg by _activate() */
tub_t *tub3270_con_tubp; /* set nonzero by _activate() */
struct tty_driver tty3270_con_driver; /* for /dev/console at 4, 64 */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
int tub3270_con_devno = -1; /* set by tub3270_con_setup() */
__initfunc(void tub3270_con_setup(char *str, int *ints))
{
int vdev;
vdev = simple_strtoul(str, 0, 16);
if (vdev >= 0 && vdev < 65536)
tub3270_con_devno = vdev;
return;
}
__initfunc (long tub3270_con_init(long kmem_start, long kmem_end))
{
tub3270_con_bcb.bc_len = 65536;
if (!MACHINE_IS_VM && !MACHINE_IS_P390)
return kmem_start;
tub3270_con_bcb.bc_buf = (void *)kmem_start;
kmem_start += tub3270_con_bcb.bc_len;
register_console(&tub3270_con);
return kmem_start;
}
#else
#define tub3270_con_devno console_device
static void __init tub3270_con_init(void)
{
tub3270_con_bcb.bc_len = 65536;
if (!CONSOLE_IS_3270)
return;
tub3270_con_bcb.bc_buf = (void *)alloc_bootmem_low(
tub3270_con_bcb.bc_len);
register_console(&tub3270_con);
}
console_initcall(tub3270_con_init);
#endif
static struct tty_driver *tub3270_con_device(struct console *conp, int *index)
{
*index = conp->index + 1;
extern struct tty_driver *tty3270_driver;
return tty3270_driver;
}
static void
tub3270_con_unblank(void)
{
/* flush everything: panic has occurred */
}
static void
tub3270_con_write(struct console *conp,
const char *buf, unsigned int count)
{
long flags;
tub_t *tubp = tub3270_con_tubp;
void tty3270_sched_bh(tub_t *);
int rc;
bcb_t obcb;
obcb.bc_buf = (char *)buf;
obcb.bc_len = obcb.bc_cnt = obcb.bc_wr =
MIN(count, tub3270_con_bcb.bc_len);
obcb.bc_rd = 0;
spin_lock_irqsave(&tub3270_con_bcblock, flags);
rc = tub3270_movedata(&obcb, &tub3270_con_bcb, 0);
spin_unlock_irqrestore(&tub3270_con_bcblock, flags);
if (tubp && rc && TUBTRYLOCK(tubp->irq, flags)) {
tty3270_sched_bh(tubp);
TUBUNLOCK(tubp->irq, flags);
}
}
int tub3270_con_copy(tub_t *tubp)
{
long flags;
int rc;
spin_lock_irqsave(&tub3270_con_bcblock, flags);
rc = tub3270_movedata(&tub3270_con_bcb, &tubp->tty_bcb, 0);
spin_unlock_irqrestore(&tub3270_con_bcblock, flags);
return rc;
}
#endif /* CONFIG_TN3270_CONSOLE */
#endif
/*
* remove driver: unregister the major number
*/
static void __exit
tub3270_exit(void)
{
fs3270_fini();
tty3270_fini();
tubfiniminors();
}
static int
tub3270_is_ours(s390_dev_info_t *dp)
{
if ((dp->sid_data.cu_type & 0xfff0) == 0x3270)
return 1;
if (dp->sid_data.cu_type == 0x3174)
return 1;
return 0;
}
/*
* tub3270_init() called by kernel or module initialization
*/
static int __init
tub3270_init(void)
{
s390_dev_info_t d;
int i, rc;
#ifdef MODULE
if (tubnummins != 0) {
printk(KERN_ERR "EEEK!! Tube driver cobbigling!!\n");
return -1;
}
#endif
/*
* Copy and correct ebcdic - ascii translate tables
*/
memcpy(tub_ascebc, _ascebc, sizeof tub_ascebc);
memcpy(tub_ebcasc, _ebcasc, sizeof tub_ebcasc);
if (tubxcorrect) {
/* correct brackets and circumflex */
tub_ascebc['['] = 0xad;
tub_ascebc[']'] = 0xbd;
tub_ebcasc[0xad] = '[';
tub_ebcasc[0xbd] = ']';
tub_ascebc['^'] = 0xb0;
tub_ebcasc[0x5f] = '^';
}
rc = tubinitminors();
if (rc != 0)
return rc;
if (fs3270_init() || tty3270_init()) {
printk(KERN_ERR "fs3270_init() or tty3270_init() failed\n");
fs3270_fini();
tty3270_fini();
tubfiniminors();
return -1;
}
for (i = get_irq_first(); i >= 0; i = get_irq_next(i)) {
if ((rc = get_dev_info_by_irq(i, &d)))
continue;
if (d.status)
continue;
#ifdef CONFIG_TN3270_CONSOLE
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
if (d.sid_data.cu_type == 0x3215 && MACHINE_IS_VM) {
cpcmd("TERM CONMODE 3270", NULL, 0);
d.sid_data.cu_type = 0x3270;
}
#else
if (d.sid_data.cu_type == 0x3215 && CONSOLE_IS_3270) {
cpcmd("TERM CONMODE 3270", NULL, 0);
d.sid_data.cu_type = 0x3270;
}
#endif /* LINUX_VERSION_CODE */
#endif /* CONFIG_TN3270_CONSOLE */
if (!tub3270_is_ours(&d))
continue;
rc = tubmakemin(i, &d);
if (rc < 0) {
printk(KERN_WARNING
"3270 tube registration ran out of memory"
" after %d devices\n", tubnummins - 1);
break;
} else {
printk(KERN_INFO "3270: %.4x on sch %d, minor %d\n",
d.devno, d.irq, rc);
}
}
return 0;
}
/*
* tub3270_movedata(bcb_t *, bcb_t *) -- Move data stream
*/
int
tub3270_movedata(bcb_t *ib, bcb_t *ob, int fromuser)
{
int count; /* Total move length */
int rc;
rc = count = MIN(ib->bc_cnt, ob->bc_len - ob->bc_cnt);
while (count > 0) {
int len1; /* Contig bytes avail in ib */
if (ib->bc_wr > ib->bc_rd)
len1 = ib->bc_wr - ib->bc_rd;
else
len1 = ib->bc_len - ib->bc_rd;
if (len1 > count)
len1 = count;
while (len1 > 0) {
int len2; /* Contig space avail in ob */
if (ob->bc_rd > ob->bc_wr)
len2 = ob->bc_rd - ob->bc_wr;
else
len2 = ob->bc_len - ob->bc_wr;
if (len2 > len1)
len2 = len1;
if (fromuser) {
len2 -= copy_from_user(ob->bc_buf + ob->bc_wr,
ib->bc_buf + ib->bc_rd,
len2);
if (len2 == 0) {
if (!rc)
rc = -EFAULT;
break;
}
} else
memcpy(ob->bc_buf + ob->bc_wr,
ib->bc_buf + ib->bc_rd,
len2);
ib->bc_rd += len2;
if (ib->bc_rd == ib->bc_len)
ib->bc_rd = 0;
ib->bc_cnt -= len2;
ob->bc_wr += len2;
if (ob->bc_wr == ob->bc_len)
ob->bc_wr = 0;
ob->bc_cnt += len2;
len1 -= len2;
count -= len2;
}
}
return rc;
}
/*
* receive an interrupt
*/
static void
tubint(int irq, void *ipp, struct pt_regs *prp)
{
devstat_t *dsp = ipp;
tub_t *tubp;
if ((tubp = IRQ2TUB(irq)) && (tubp->intv))
(tubp->intv)(tubp, dsp);
}
/*
* Initialize array of pointers to minor structures tub_t.
* Returns 0 or -ENOMEM.
*/
static int
tubinitminors(void)
{
tubminors = (tub_t *(*)[TUBMAXMINS])kmalloc(sizeof *tubminors,
GFP_KERNEL);
if (tubminors == NULL)
return -ENOMEM;
memset(tubminors, 0, sizeof *tubminors);
return 0;
}
/*
* Add a minor 327x device. Argument is an irq value.
*
* Point elements of two arrays to the newly created tub_t:
* 1. (*tubminors)[minor]
* 2. (*(*tubirqs)[irqhi])[irqlo]
* The first looks up from minor number at context time; the second
* looks up from irq at interrupt time.
*/
static int
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
tubmakemin(int irq, dev_info_t *dp)
#else
tubmakemin(int irq, s390_dev_info_t *dp)
#endif
{
tub_t *tubp;
int minor;
long flags;
if ((minor = ++tubnummins) == TUBMAXMINS)
return -ENODEV;
tubp = kmalloc(sizeof(tub_t), GFP_KERNEL);
if (tubp == NULL) {
return -ENOMEM;
}
if (tubaddbyirq(tubp, irq) != 0) {
kfree(tubp);
return -ENOMEM;
}
memset(tubp, 0, sizeof(tub_t));
tubp->minor = minor;
tubp->irq = irq;
TUBLOCK(tubp->irq, flags);
tubp->devno = dp->devno;
tubp->geom_rows = _GEOM_ROWS;
tubp->geom_cols = _GEOM_COLS;
init_waitqueue_head(&tubp->waitq);
tubp->tty_bcb.bc_len = TTY_OUTPUT_SIZE;
tubp->tty_bcb.bc_buf = (void *)kmalloc(tubp->tty_bcb.bc_len,
GFP_KERNEL|GFP_DMA);
if (tubp->tty_bcb.bc_buf == NULL) {
TUBUNLOCK(tubp->irq, flags);
tubdelbyirq(tubp, irq);
kfree(tubp);
return -ENOMEM;
}
tubp->tty_bcb.bc_cnt = 0;
tubp->tty_bcb.bc_wr = 0;
tubp->tty_bcb.bc_rd = 0;
(*tubminors)[minor] = tubp;
#ifdef CONFIG_TN3270_CONSOLE
if (CONSOLE_IS_3270) {
if (tub3270_con_tubp == NULL &&
tub3270_con_bcb.bc_buf != NULL &&
(tub3270_con_devno == -1 ||
tub3270_con_devno == dp->devno)) {
extern void tty3270_int(tub_t *, devstat_t *);
tub3270_con_devno = dp->devno;
tubp->cmd = TBC_CONOPEN;
tubp->flags |= TUB_OPEN_STET | TUB_INPUT_HACK;
tty3270_size(tubp, &flags);
tty3270_aid_init(tubp);
tty3270_scl_init(tubp);
tub3270_con_irq = tubp->irq;
tub3270_con_tubp = tubp;
tubp->intv = tty3270_int;
tubp->cmd = TBC_UPDSTAT;
tty3270_build(tubp);
}
}
#endif /* CONFIG_TN3270_CONSOLE */
devfs_mk_cdev(MKDEV(IBM_FS3270_MAJOR, tubp->minor),
S_IFCHR|S_IRUSR|S_IWUSR, "3270/tub%.4x");
TUBUNLOCK(tubp->irq, flags);
return minor;
}
/*
* Release array of pointers to minor structures tub_t, but first
* release any storage pointed to by them.
*/
static void
tubfiniminors(void)
{
int i;
tub_t **tubpp, *tubp;
if (tubminors == NULL)
return;
for (i = 0; i < TUBMAXMINS; i++) {
tubpp = &(*tubminors)[i];
if ((tubp = *tubpp)) {
devfs_remove("3270/tub%.4x", tubp->devno);
tubdelbyirq(tubp, tubp->irq);
tty3270_rcl_fini(tubp);
kfree(tubp->tty_bcb.bc_buf);
tubp->tty_bcb.bc_buf = NULL;
if (tubp->tty_input) {
kfree(tubp->tty_input);
tubp->tty_input = NULL;
}
tubp->ttyscreen = NULL;
kfree(tubp);
*tubpp = NULL;
}
}
kfree(tubminors);
tubminors = NULL;
tubfiniirqs();
}
/*
* tubaddbyirq() -- Add tub_t for irq lookup in tubint()
*/
static int
tubaddbyirq(tub_t *tubp, int irq)
{
int irqhi = (irq >> 8) & 255;
int irqlo = irq & 255;
tub_t *(*itubpp)[256];
/* Allocate array (*tubirqs)[] if first time */
if (tubirqs == NULL) {
tubirqs = (tub_t *(*(*)[256])[256])
kmalloc(sizeof *tubirqs, GFP_KERNEL);
if (tubirqs == NULL)
return -ENOMEM;
memset(tubirqs, 0, sizeof *tubirqs);
}
/* Allocate subarray (*(*tubirqs)[])[] if first use */
if ((itubpp = (*tubirqs)[irqhi]) == NULL) {
itubpp = (tub_t *(*)[256])
kmalloc(sizeof(*itubpp), GFP_KERNEL);
if (itubpp == NULL) {
if (tubnummins == 1) { /* if first time */
kfree(tubirqs);
tubirqs = NULL;
}
return -ENOMEM;
} else {
memset(itubpp, 0, sizeof(*itubpp));
(*tubirqs)[irqhi] = itubpp;
}
}
/* Request interrupt service */
if ((tubp->irqrc = request_irq(irq, tubint, SA_INTERRUPT,
"3270 tube driver", &tubp->devstat)) != 0)
return tubp->irqrc;
/* Fill in the proper subarray element */
(*itubpp)[irqlo] = tubp;
return 0;
}
/*
* tubfindbyirq(irq)
*/
static tub_t *
tubfindbyirq(int irq)
{
int irqhi = (irq >> 8) & 255;
int irqlo = irq & 255;
tub_t *tubp;
if (tubirqs == NULL)
return NULL;
if ((*tubirqs)[irqhi] == NULL)
return NULL;
tubp = (*(*tubirqs)[irqhi])[irqlo];
if (tubp->irq == irq)
return tubp;
return NULL;
}
/*
* tubdelbyirq(tub_t*, irq)
*/
static void
tubdelbyirq(tub_t *tubp, int irq)
{
int irqhi = (irq >> 8) & 255;
int irqlo = irq & 255;
tub_t *(*itubpp)[256], *itubp;
if (tubirqs == NULL) {
printk(KERN_ERR "tubirqs is NULL\n");
return;
}
itubpp = (*tubirqs)[irqhi];
if (itubpp == NULL) {
printk(KERN_ERR "tubirqs[%d] is NULL\n", irqhi);
return;
}
itubp = (*itubpp)[irqlo];
if (itubp == NULL) {
printk(KERN_ERR "tubirqs[%d][%d] is NULL\n", irqhi, irqlo);
return;
}
if (itubp->irqrc == 0)
free_irq(irq, &itubp->devstat);
(*itubpp)[irqlo] = NULL;
}
/*
* tubfiniirqs() -- clean up storage in tub_t *(*(*tubirqs)[256])[256]
*/
static void
tubfiniirqs(void)
{
int i;
tub_t *(*itubpp)[256];
if (tubirqs != NULL) {
for (i = 0; i < 256; i++) {
if ((itubpp = (*tubirqs)[i])) {
kfree(itubpp);
(*tubirqs)[i] = NULL;
}
}
kfree(tubirqs);
tubirqs = NULL;
}
}
module_init(tub3270_init);
module_exit(tub3270_exit);
/*
* IBM/3270 Driver -- Copyright (C) UTS Global LLC
*
* tubfs.c -- Fullscreen driver
*
*
*
*
*
* Author: Richard Hitt
*/
#include "tubio.h"
int fs3270_major = -1; /* init to impossible -1 */
static int fs3270_open(struct inode *, struct file *);
static int fs3270_close(struct inode *, struct file *);
static int fs3270_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
static ssize_t fs3270_read(struct file *, char *, size_t, loff_t *);
static ssize_t fs3270_write(struct file *, const char *, size_t, loff_t *);
static int fs3270_wait(tub_t *, long *);
static void fs3270_int(tub_t *tubp, devstat_t *dsp);
extern void tty3270_refresh(tub_t *);
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 */
};
/*
* fs3270_init() -- Initialize fullscreen tubes
*/
int
fs3270_init(void)
{
int rc;
rc = register_chrdev(IBM_FS3270_MAJOR, "fs3270", &fs3270_fops);
if (rc) {
printk(KERN_ERR "tubmod can't get major nbr %d: error %d\n",
IBM_FS3270_MAJOR, rc);
return -1;
}
devfs_mk_dir("3270");
devfs_mk_cdev(MKDEV(IBM_FS3270_MAJOR, 0),
S_IFCHR|S_IRUGO|S_IWUGO, "3270/tub");
fs3270_major = IBM_FS3270_MAJOR;
return 0;
}
/*
* fs3270_fini() -- Uninitialize fullscreen tubes
*/
void
fs3270_fini(void)
{
if (fs3270_major != -1) {
devfs_remove("3270");
devfs_remove("3270/tub");
unregister_chrdev(fs3270_major, "fs3270");
fs3270_major = -1;
}
}
/*
* fs3270_open
*/
static int
fs3270_open(struct inode *ip, struct file *fp)
{
tub_t *tubp;
long flags;
/* See INODE2TUB(ip) for handling of "/dev/3270/tub" */
if ((tubp = INODE2TUB(ip)) == NULL)
return -ENOENT;
TUBLOCK(tubp->irq, flags);
if (tubp->mode == TBM_FS || tubp->mode == TBM_FSLN) {
TUBUNLOCK(tubp->irq, flags);
return -EBUSY;
}
fp->private_data = ip;
tubp->mode = TBM_FS;
tubp->intv = fs3270_int;
tubp->dstat = 0;
tubp->fs_pid = current->pid;
tubp->fsopen = 1;
TUBUNLOCK(tubp->irq, flags);
return 0;
}
/*
* fs3270_close aka release: free the irq
*/
static int
fs3270_close(struct inode *ip, struct file *fp)
{
tub_t *tubp;
long flags;
if ((tubp = INODE2TUB(ip)) == NULL)
return -ENODEV;
fs3270_wait(tubp, &flags);
tubp->fsopen = 0;
tubp->fs_pid = 0;
tubp->intv = NULL;
tubp->mode = 0;
tty3270_refresh(tubp);
TUBUNLOCK(tubp->irq, flags);
return 0;
}
/*
* fs3270_release() called from tty3270_hangup()
*/
void
fs3270_release(tub_t *tubp)
{
long flags;
if (tubp->mode != TBM_FS)
return;
fs3270_wait(tubp, &flags);
tubp->fsopen = 0;
tubp->fs_pid = 0;
tubp->intv = NULL;
tubp->mode = 0;
/*tty3270_refresh(tubp);*/
TUBUNLOCK(tubp->irq, flags);
}
/*
* fs3270_wait(tub_t *tubp, int *flags) -- Wait to use tube
* Entered without irq lock
* On return:
* * Lock is held
* * Value is 0 or -ERESTARTSYS
*/
static int
fs3270_wait(tub_t *tubp, long *flags)
{
DECLARE_WAITQUEUE(wait, current);
TUBLOCK(tubp->irq, *flags);
add_wait_queue(&tubp->waitq, &wait);
while (!signal_pending(current) &&
((tubp->mode != TBM_FS) ||
(tubp->flags & (TUB_WORKING | TUB_RDPENDING)) != 0)) {
#warning FIXME: [kj] use set_current_state instead of current->state=
current->state = TASK_INTERRUPTIBLE;
TUBUNLOCK(tubp->irq, *flags);
schedule();
#warning FIXME: [kj] use set_current_state instead of current->state=
current->state = TASK_RUNNING;
TUBLOCK(tubp->irq, *flags);
}
remove_wait_queue(&tubp->waitq, &wait);
return signal_pending(current)? -ERESTARTSYS: 0;
}
/*
* fs3270_io(tubp, ccw1_t*) -- start I/O on the tube
* Entered with irq lock held, WORKING off
*/
static int
fs3270_io(tub_t *tubp, ccw1_t *ccwp)
{
int rc;
rc = do_IO(tubp->irq, ccwp, tubp->irq, 0, 0);
tubp->flags |= TUB_WORKING;
tubp->dstat = 0;
return rc;
}
/*
* fs3270_tasklet(tubp) -- Perform back-half processing
*/
static void
fs3270_tasklet(unsigned long data)
{
long flags;
tub_t *tubp;
addr_t *ip;
tubp = (tub_t *) data;
TUBLOCK(tubp->irq, flags);
tubp->flags &= ~TUB_BHPENDING;
if (tubp->wbuf) { /* if we were writing */
for (ip = tubp->wbuf; ip < tubp->wbuf+33; ip++) {
if (*ip == 0)
break;
kfree(phys_to_virt(*ip));
}
kfree(tubp->wbuf);
tubp->wbuf = NULL;
}
if ((tubp->flags & (TUB_ATTN | TUB_RDPENDING)) ==
(TUB_ATTN | TUB_RDPENDING)) {
fs3270_io(tubp, &tubp->rccw);
tubp->flags &= ~(TUB_ATTN | TUB_RDPENDING);
}
if ((tubp->flags & TUB_WORKING) == 0)
wake_up_interruptible(&tubp->waitq);
TUBUNLOCK(tubp->irq, flags);
}
/*
* fs3270_sched_tasklet(tubp) -- Schedule the back half
* Irq lock must be held on entry and remains held on exit.
*/
static void
fs3270_sched_tasklet(tub_t *tubp)
{
if (tubp->flags & TUB_BHPENDING)
return;
tubp->flags |= TUB_BHPENDING;
tasklet_init(&tubp->tasklet, fs3270_tasklet,
(unsigned long) tubp);
tasklet_schedule(&tubp->tasklet);
}
/*
* fs3270_int(tubp, prp) -- Process interrupt from tube in FS mode
* This routine is entered with irq lock held (see do_IRQ in s390io.c)
*/
static void
fs3270_int(tub_t *tubp, devstat_t *dsp)
{
#define DEV_UE_BUSY \
(DEV_STAT_CHN_END | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP)
#ifdef RBHNOTYET
/* XXX needs more work; must save 2d arg to fs370_io() */
/* Handle CE-DE-UE and subsequent UDE */
if (dsp->dstat == DEV_UE_BUSY) {
tubp->flags |= TUB_UE_BUSY;
return;
} else if (tubp->flags & TUB_UE_BUSY) {
tubp->flags &= ~TUB_UE_BUSY;
if (dsp->dstat == DEV_STAT_DEV_END &&
(tubp->flags & TUB_WORKING) != 0) {
fs3270_io(tubp);
return;
}
}
#endif
/* Handle ATTN */
if (dsp->dstat & DEV_STAT_ATTENTION)
tubp->flags |= TUB_ATTN;
if (dsp->dstat & DEV_STAT_CHN_END) {
tubp->cswl = dsp->rescnt;
if ((dsp->dstat & DEV_STAT_DEV_END) == 0)
tubp->flags |= TUB_EXPECT_DE;
else
tubp->flags &= ~TUB_EXPECT_DE;
} else if (dsp->dstat & DEV_STAT_DEV_END) {
if ((tubp->flags & TUB_EXPECT_DE) == 0)
tubp->flags |= TUB_UNSOL_DE;
tubp->flags &= ~TUB_EXPECT_DE;
}
if (dsp->dstat & DEV_STAT_DEV_END)
tubp->flags &= ~TUB_WORKING;
if ((tubp->flags & TUB_WORKING) == 0)
fs3270_sched_tasklet(tubp);
}
/*
* process ioctl commands for the tube driver
*/
static int
fs3270_ioctl(struct inode *ip, struct file *fp,
unsigned int cmd, unsigned long arg)
{
tub_t *tubp;
int rc = 0;
long flags;
if ((tubp = INODE2TUB(ip)) == NULL)
return -ENODEV;
if ((rc = fs3270_wait(tubp, &flags))) {
TUBUNLOCK(tubp->irq, flags);
return rc;
}
switch(cmd) {
case TUBICMD: tubp->icmd = arg; break;
case TUBOCMD: tubp->ocmd = arg; break;
case TUBGETI: put_user(tubp->icmd, (char *)arg); break;
case TUBGETO: put_user(tubp->ocmd, (char *)arg); break;
case TUBGETMOD:
if (copy_to_user((char *)arg, &tubp->tubiocb,
sizeof tubp->tubiocb))
rc = -EFAULT;
break;
}
TUBUNLOCK(tubp->irq, flags);
return rc;
}
/*
* process read commands for the tube driver
*/
static ssize_t
fs3270_read(struct file *fp, char *dp, size_t len, loff_t *off)
{
tub_t *tubp;
char *kp;
ccw1_t *cp;
int rc;
long flags;
addr_t *idalp, *ip;
char *tp;
int count, piece;
int size;
if (len == 0 || len > 65535) {
return -EINVAL;
}
if ((tubp = INODE2TUB((struct inode *)fp->private_data)) == NULL)
return -ENODEV;
ip = idalp = kmalloc(33*sizeof(addr_t), GFP_ATOMIC|GFP_DMA);
if (idalp == NULL)
return -EFAULT;
memset(idalp, 0, 33 * sizeof *idalp);
count = len;
while (count) {
piece = MIN(count, 0x800);
size = count == len? piece: 0x800;
if ((kp = kmalloc(size, GFP_KERNEL|GFP_DMA)) == NULL) {
len = -ENOMEM;
goto do_cleanup;
}
*ip++ = virt_to_phys(kp);
count -= piece;
}
if ((rc = fs3270_wait(tubp, &flags)) != 0) {
TUBUNLOCK(tubp->irq, flags);
len = rc;
goto do_cleanup;
}
cp = &tubp->rccw;
if (tubp->icmd == 0 && tubp->ocmd != 0) tubp->icmd = 6;
cp->cmd_code = tubp->icmd?:2;
cp->flags = CCW_FLAG_SLI | CCW_FLAG_IDA;
cp->count = len;
cp->cda = virt_to_phys(idalp);
tubp->flags |= TUB_RDPENDING;
TUBUNLOCK(tubp->irq, flags);
if ((rc = fs3270_wait(tubp, &flags)) != 0) {
tubp->flags &= ~TUB_RDPENDING;
len = rc;
TUBUNLOCK(tubp->irq, flags);
goto do_cleanup;
}
TUBUNLOCK(tubp->irq, flags);
len -= tubp->cswl;
count = len;
tp = dp;
ip = idalp;
while (count) {
piece = MIN(count, 0x800);
if (copy_to_user(tp, phys_to_virt(*ip), piece) != 0) {
len = -EFAULT;
goto do_cleanup;
}
count -= piece;
tp += piece;
ip++;
}
do_cleanup:
for (ip = idalp; ip < idalp+33; ip++) {
if (*ip == 0)
break;
kfree(phys_to_virt(*ip));
}
kfree(idalp);
return len;
}
/*
* process write commands for the tube driver
*/
static ssize_t
fs3270_write(struct file *fp, const char *dp, size_t len, loff_t *off)
{
tub_t *tubp;
ccw1_t *cp;
int rc;
long flags;
void *kb;
addr_t *idalp, *ip;
int count, piece;
int index;
int size;
if (len > 65535 || len == 0)
return -EINVAL;
/* Locate the tube */
if ((tubp = INODE2TUB((struct inode *)fp->private_data)) == NULL)
return -ENODEV;
ip = idalp = kmalloc(33*sizeof(addr_t), GFP_ATOMIC|GFP_DMA);
if (idalp == NULL)
return -EFAULT;
memset(idalp, 0, 33 * sizeof *idalp);
count = len;
index = 0;
while (count) {
piece = MIN(count, 0x800);
size = count == len? piece: 0x800;
if ((kb = kmalloc(size, GFP_KERNEL|GFP_DMA)) == NULL) {
len = -ENOMEM;
goto do_cleanup;
}
*ip++ = virt_to_phys(kb);
if (copy_from_user(kb, &dp[index], piece) != 0) {
len = -EFAULT;
goto do_cleanup;
}
count -= piece;
index += piece;
}
/* Wait till tube's not working or signal is pending */
if ((rc = fs3270_wait(tubp, &flags))) {
len = rc;
TUBUNLOCK(tubp->irq, flags);
goto do_cleanup;
}
/* Make CCW and start I/O. Back end will free buffers & idal. */
tubp->wbuf = idalp;
cp = &tubp->wccw;
cp->cmd_code = tubp->ocmd? tubp->ocmd == 5? 13: tubp->ocmd: 1;
cp->flags = CCW_FLAG_SLI | CCW_FLAG_IDA;
cp->count = len;
cp->cda = virt_to_phys(tubp->wbuf);
fs3270_io(tubp, cp);
TUBUNLOCK(tubp->irq, flags);
return len;
do_cleanup:
for (ip = idalp; ip < idalp+33; ip++) {
if (*ip == 0)
break;
kfree(phys_to_virt(*ip));
}
kfree(idalp);
return len;
}
/*
* IBM/3270 Driver -- Copyright (C) 2000 UTS Global LLC
*
* tubio.h -- All-Purpose header file
*
*
*
*
*
* Author: Richard Hitt
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/tty.h>
#include <linux/major.h>
#ifndef IBM_TTY3270_MAJOR
# define IBM_TTY3270_MAJOR 212
#endif /* IBM_TTY3270_MAJOR */
#ifndef IBM_FS3270_MAJOR
# define IBM_FS3270_MAJOR 213
#endif /* IBM_FS3270_MAJOR */
#include <linux/slab.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/idals.h>
#include <linux/console.h>
#include <linux/interrupt.h>
#include <asm/ebcdic.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0))
#include <linux/devfs_fs_kernel.h>
#endif
#define TUB(x) (('3'<<8)|(x))
#define TUBICMD TUB(3)
#define TUBOCMD TUB(4)
#define TUBGETI TUB(7)
#define TUBGETO TUB(8)
#define TUBSETMOD TUB(12)
#define TUBGETMOD TUB(13)
#define TIOPOLL TUB(32)
#define TIOPOKE TUB(33)
#define TIONPOKE TUB(34)
#define TIOTNORM TUB(35)
/* Local Channel Commands */
#define TC_WRITE 0x01
#define TC_EWRITE 0x05
#define TC_READMOD 0x06
#define TC_EWRITEA 0x0d
#define TC_WRITESF 0x11
/* Buffer Control Orders */
#define TO_SF 0x1d
#define TO_SBA 0x11
#define TO_IC 0x13
#define TO_PT 0x05
#define TO_RA 0x3c
#define TO_SFE 0x29
#define TO_EUA 0x12
#define TO_MF 0x2c
#define TO_SA 0x28
/* 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
#define TF_STAT 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 */
/* Attention-ID (AID) Characters */
#define TA_CLEAR 0x6d
#define TA_PA2 0x6e
#define TA_ENTER 0x7d
/* more to come */
#define MIN(a, b) ((a) < (b)? (a): (b))
#define TUB_BUFADR(adr, cpp) \
tty3270_tub_bufadr(tubp, adr, cpp)
#define TUB_EBCASC(addr, nr) codepage_convert(tub_ebcasc, addr, nr)
#define TUB_ASCEBC(addr, nr) codepage_convert(tub_ascebc, addr, nr)
/*
*
* General global values for the tube driver
*
*/
enum tubmode {
TBM_LN, /* Line mode */
TBM_FS, /* Fullscreen mode */
TBM_FSLN /* Line mode shelled out of fullscreen */
};
enum tubstat { /* normal-mode status */
TBS_RUNNING, /* none of the following */
TBS_MORE, /* timed "MORE..." in status */
TBS_HOLD /* untimed "HOLDING" in status */
};
enum tubcmd { /* normal-mode actions to do */
TBC_CONOPEN, /* Erase-write the console */
TBC_OPEN, /* Open the tty screen */
TBC_UPDATE, /* Add lines to the log, clear cmdline */
TBC_UPDLOG, /* Add lines to log */
TBC_KRUPDLOG, /* Add lines to log, reset kbd */
TBC_CLEAR, /* Build screen from scratch */
TBC_CLRUPDLOG, /* Do log & status, not cmdline */
TBC_UPDSTAT, /* Do status update only */
TBC_CLRINPUT, /* Clear input area only */
TBC_UPDINPUT /* Update input area only */
};
enum tubwhat { /* echo what= proc actions */
TW_BOGUS, /* Nothing at all */
TW_CONFIG /* Output configuration info */
};
#define TUBMAXMINS 256
#define _GEOM_ROWS 24
#define _GEOM_COLS 80
#define GEOM_ROWS (tubp->geom_rows)
#define GEOM_COLS (tubp->geom_cols)
#define GEOM_MAXROWS 127
#define GEOM_MAXCOLS 132
#define GEOM_INPLEN (GEOM_COLS * 2 - 20)
#define GEOM_MAXINPLEN (GEOM_MAXCOLS * 2 - 20)
#define GEOM_INPUT (GEOM_COLS * (GEOM_ROWS - 2) - 1) /* input atr posn */
#define GEOM_STAT (GEOM_INPUT + 1 + GEOM_INPLEN)
#define GEOM_LOG (GEOM_COLS * GEOM_ROWS - 1) /* log atr posn */
#define TS_RUNNING "Linux Running "
#define TS_MORE "Linux More... "
#define DEFAULT_SCROLLTIME 5
#define TS_HOLD "Linux Holding "
/* data length used by tty3270_set_status_area: SBA (3), SF (2), data */
#define TS_LENGTH (sizeof TS_RUNNING + 3 + 2)
typedef struct {
int aid; /* What-to-do flags */
char *string; /* Optional input string */
} aid_t;
#define AIDENTRY(ch, tubp) (&((tubp)->tty_aid[(ch) & 0x3f]))
/* For TUBGETMOD and TUBSETMOD. Should include. */
typedef struct tubiocb {
short model;
short line_cnt;
short col_cnt;
short pf_cnt;
short re_cnt;
short map;
} tubiocb_t;
/* Flags that go in int aid, above */
#define TA_CLEARKEY 0x01 /* Key does hardware CLEAR */
#define TA_SHORTREAD 0x02 /* Key does hardware shortread */
/* If both are off, key does hardware Read Modified. */
#define TA_DOENTER 0x04 /* Treat key like ENTER */
#define TA_DOSTRING 0x08 /* Use string and ENTER */
#define TA_DOSTRINGD 0x10 /* Display string & set MDT */
#define TA_CLEARLOG 0x20 /* Make key cause clear of log */
/*
* Tube driver buffer control block
*/
typedef struct bcb_s {
char *bc_buf; /* Pointer to buffer */
int bc_len; /* Length of buffer */
int bc_cnt; /* Count of bytes buffered */
int bc_wr; /* Posn to write next byte into */
int bc_rd; /* Posn to read next byte from */
} bcb_t;
typedef struct tub_s {
int minor;
int irq;
int irqrc;
int devno;
int geom_rows;
int geom_cols;
tubiocb_t tubiocb;
int lnopen;
int fsopen;
int icmd;
int ocmd;
devstat_t devstat;
ccw1_t rccw;
ccw1_t wccw;
addr_t *wbuf;
int cswl;
void (*intv)(struct tub_s *, devstat_t *);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
struct wait_queue *waitq;
#else
wait_queue_head_t waitq;
#endif
int dstat;
sense_t sense;
enum tubmode mode;
enum tubstat stat;
enum tubcmd cmd;
int flags; /* See below for values */
struct tasklet_struct tasklet;
/* Stuff for fs-driver support */
pid_t fs_pid; /* Pid if TBM_FS */
/* Stuff for tty-driver support */
struct tty_struct *tty;
char *tty_input; /* tty input area */
int tty_inattr; /* input-area field attribute */
#define TTY_OUTPUT_SIZE 1024
bcb_t tty_bcb; /* Output buffer control info */
int tty_oucol; /* Kludge */
int tty_nextlogx; /* next screen-log position */
int tty_savecursor; /* saved cursor position */
int tty_scrolltime; /* scrollforward wait time, sec */
struct timer_list tty_stimer; /* timer for scrolltime */
aid_t tty_aid[64]; /* Aid descriptors */
int tty_aidinit; /* Boolean */
int tty_showaidx; /* Last aid x to set_aid */
int tty_14bitadr; /* 14-bit bufadrs okay */
#define MAX_TTY_ESCA 24 /* Set-Attribute-Order array */
char tty_esca[MAX_TTY_ESCA]; /* SA array */
int tty_escx; /* Current index within it */
/* For command recall --- */
char *(*tty_rclbufs)[]; /* Array of ptrs to recall bufs */
int tty_rclk; /* Size of array tty_rclbufs */
int tty_rclp; /* Index for most-recent cmd */
int tty_rclb; /* Index for backscrolling */
/* Work area to contain the hardware write stream */
char (*ttyscreen)[]; /* ptr to data stream area */
int ttyscreenl; /* its length */
ccw1_t ttyccw;
} tub_t;
/* values for flags: */
#define TUB_WORKING 0x0001
#define TUB_BHPENDING 0x0002
#define TUB_RDPENDING 0x0004
#define TUB_ALARM 0x0008
#define TUB_SCROLLTIMING 0x0010
#define TUB_ATTN 0x0020
#define TUB_IACTIVE 0x0040
#define TUB_SIZED 0x0080
#define TUB_EXPECT_DE 0x0100
#define TUB_UNSOL_DE 0x0200
#define TUB_OPEN_STET 0x0400 /* No screen clear on open */
#define TUB_UE_BUSY 0x0800
#define TUB_INPUT_HACK 0x1000 /* Early init of command line */
/*
* Extra stuff for 3270 console support
*/
#ifdef CONFIG_TN3270_CONSOLE
extern int tub3270_con_devno;
extern char (*tub3270_con_output)[];
extern int tub3270_con_outputl;
extern int tub3270_con_ouwr;
extern int tub3270_con_oucount;
extern int tub3270_con_irq;
extern tub_t *tub3270_con_tubp;
extern struct tty_driver tty3270_con_driver;
#endif /* CONFIG_TN3270_CONSOLE */
extern int tubnummins;
extern tub_t *(*tubminors)[TUBMAXMINS];
extern tub_t *(*(*tubirqs)[256])[256];
extern unsigned char tub_ascebc[256];
extern unsigned char tub_ebcasc[256];
extern unsigned char tub_ebcgraf[64];
extern int tubdebug;
extern int fs3270_major;
extern int tty3270_major;
extern int tty3270_proc_misc;
extern enum tubwhat tty3270_proc_what;
extern struct tty_driver *tty3270_driver;
#ifndef spin_trylock_irqsave
#define spin_trylock_irqsave(lock, flags) \
({ \
int success; \
local_irq_save(flags); \
success = spin_trylock(lock); \
if (success == 0) \
local_irq_restore(flags); \
success; \
})
#endif /* if not spin_trylock_irqsave */
#ifndef s390irq_spin_trylock_irqsave
#define s390irq_spin_trylock_irqsave(irq, flags) \
spin_trylock_irqsave(&(ioinfo[irq]->irq_lock), flags)
#endif /* if not s390irq_spin_trylock_irqsave */
#define TUBLOCK(irq, flags) \
s390irq_spin_lock_irqsave(irq, flags)
#define TUBTRYLOCK(irq, flags) \
s390irq_spin_trylock_irqsave(irq, flags)
#define TUBUNLOCK(irq, flags) \
s390irq_spin_unlock_irqrestore(irq, flags)
/*
* Find tub_t * given fullscreen device's irq (subchannel number)
*/
#if 0
extern tub_t *tubfindbyirq(int);
#endif
#define IRQ2TUB(irq) tubfindbyirq(irq)
/*
* Find tub_t * given fullscreen device's inode pointer
* This algorithm takes into account /dev/3270/tub.
*/
extern inline tub_t *INODE2TUB(struct inode *ip)
{
unsigned int minor = iminor(ip);
tub_t *tubp = NULL;
if (minor == 0 && current->tty) {
if (current->tty->driver == tty3270_driver)
minor = current->tty->index;
}
if (minor <= tubnummins && minor > 0)
tubp = (*tubminors)[minor];
return tubp;
}
/*
* Find tub_t * given non-fullscreen (tty) device's tty_struct pointer
*/
extern inline tub_t *TTY2TUB(struct tty_struct *tty)
{
unsigned index = tty->index;
tub_t *tubp = NULL;
if (index <= tubnummins && index > 0)
tubp = (*tubminors)[index];
return tubp;
}
extern int tub3270_movedata(bcb_t *, bcb_t *, int);
#if 0
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
extern int tubmakemin(int, dev_info_t *);
#else
extern int tubmakemin(int, s390_dev_info_t *);
#endif
#endif
extern int tub3270_con_copy(tub_t *);
extern int tty3270_rcl_init(tub_t *);
extern int tty3270_rcl_set(tub_t *, char *, int);
extern void tty3270_rcl_fini(tub_t *);
extern int tty3270_rcl_get(tub_t *, char *, int, int);
extern void tty3270_rcl_put(tub_t *, char *, int);
extern void tty3270_rcl_purge(tub_t *);
#if 0
/* these appear to be unused outside of tubttyrcl */
extern void tty3270_rcl_sync(tub_t *);
extern int tty3270_rcl_resize(tub_t *, int);
#endif
extern int tty3270_size(tub_t *, long *);
extern int tty3270_aid_init(tub_t *);
extern void tty3270_aid_fini(tub_t *);
extern void tty3270_aid_reinit(tub_t *);
extern int tty3270_aid_get(tub_t *, int, int *, char **);
extern int tty3270_aid_set(tub_t *, char *, int);
extern int tty3270_build(tub_t *);
extern void tty3270_scl_settimer(tub_t *);
extern void tty3270_scl_resettimer(tub_t *);
extern int tty3270_scl_set(tub_t *, char *, int);
extern int tty3270_scl_init(tub_t *tubp);
extern void tty3270_scl_fini(tub_t *tubp);
/*
* IBM/3270 Driver -- Copyright (C) 2000, 2001 UTS Global LLC
*
* tubtty.c -- Linemode tty driver
*
*
*
*
*
* Author: Richard Hitt
*/
#include <linux/config.h>
#include "tubio.h"
/* Initialization & uninitialization for tubtty */
int tty3270_init(void);
void tty3270_fini(void);
/* Interface routines from the upper tty layer to the tty driver */
static int tty3270_open(struct tty_struct *, struct file *);
static void tty3270_close(struct tty_struct *, struct file *);
static int tty3270_write(struct tty_struct *, int,
const unsigned char *, int);
static void tty3270_put_char(struct tty_struct *, unsigned char);
static void tty3270_flush_chars(struct tty_struct *);
static int tty3270_write_room(struct tty_struct *);
static int tty3270_chars_in_buffer(struct tty_struct *);
static int tty3270_ioctl(struct tty_struct *, struct file *,
unsigned int cmd, unsigned long arg);
static void tty3270_set_termios(struct tty_struct *, struct termios *);
static void tty3270_hangup(struct tty_struct *);
static void tty3270_flush_buffer(struct tty_struct *);
static int tty3270_read_proc(char *, char **, off_t, int, int *, void *);
static int tty3270_write_proc(struct file *, const char *,
unsigned long, void *);
/* tty3270 utility functions */
static void tty3270_tasklet(unsigned long);
void tty3270_sched_bh(tub_t *);
static int tty3270_wait(tub_t *, long *);
void tty3270_int(tub_t *, devstat_t *);
static int tty3270_try_logging(tub_t *);
static void tty3270_start_input(tub_t *);
static void tty3270_do_input(tub_t *);
static void tty3270_do_enter(tub_t *, char *, int);
static void tty3270_do_showi(tub_t *, char *, int);
int tty3270_io(tub_t *);
static int tty3270_show_tube(int, char *, int);
static int tty3270_major = -1;
struct tty_driver *tty3270_driver;
static int tty3270_proc_index;
static int tty3270_proc_data;
static int tty3270_proc_misc;
static enum tubwhat tty3270_proc_what;
static struct tty_operations tty3270_ops = {
.open = tty3270_open,
.close = tty3270_close,
.write = tty3270_write,
.put_char = tty3270_put_char,
.flush_chars = tty3270_flush_chars,
.write_room = tty3270_write_room,
.chars_in_buffer = tty3270_chars_in_buffer,
#if 0
.ioctl = tty3270_ioctl,
#endif
.set_termios = tty3270_set_termios,
.hangup = tty3270_hangup,
.flush_buffer = tty3270_flush_buffer,
.read_proc = tty3270_read_proc,
.write_proc = tty3270_write_proc,
};
/*
* tty3270_init() -- Register the tty3270 driver
*/
int
tty3270_init(void)
{
struct tty_driver *td = alloc_tty_driver(TUBMAXMINS);
int rc;
if (!td)
return -ENOMEM;
/* Initialize for tty driver */
td->owner = THIS_MODULE;
td->driver_name = "tty3270";
td->name = "tty3270";
td->major = IBM_TTY3270_MAJOR;
td->minor_start = 0;
td->type = TTY_DRIVER_TYPE_SYSTEM;
td->subtype = SYSTEM_TYPE_TTY;
td->init_termios = tty_std_termios;
td->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_NO_DEVFS;
tty_set_operations(td, &tty3270_ops);
rc = tty_register_driver(td);
if (rc) {
put_tty_driver(td);
printk(KERN_ERR "tty3270 registration failed with %d\n", rc);
} else {
tty3270_major = IBM_TTY3270_MAJOR;
if (td->proc_entry != NULL)
td->proc_entry->mode = S_IRUGO | S_IWUGO;
tty3270_driver = td;
}
return rc;
}
/*
* tty3270_fini() -- Uninitialize linemode tubes
*/
void
tty3270_fini(void)
{
if (tty3270_major != -1) {
tty_unregister_driver(tty3270_driver);
put_tty_driver(tty3270_driver);
tty3270_driver = NULL;
tty3270_major = -1;
}
}
static int
tty3270_open(struct tty_struct *tty, struct file *filp)
{
tub_t *tubp;
long flags;
int rc;
int cmd;
if ((tubp = TTY2TUB(tty)) == NULL) {
return -ENODEV;
}
if ((rc = tty3270_wait(tubp, &flags)) != 0)
goto do_fail;
if (tubp->lnopen > 0) {
tubp->lnopen++;
TUBUNLOCK(tubp->irq, flags);
return 0;
}
if (tubp->flags & TUB_OPEN_STET) {
cmd = TBC_UPDLOG;
} else {
cmd = TBC_OPEN;
tubp->flags &= ~TUB_SIZED;
}
if ((rc = tty3270_size(tubp, &flags)) != 0)
goto do_fail;
if ((rc = tty3270_rcl_init(tubp)) != 0)
goto do_fail;
if ((rc = tty3270_aid_init(tubp)) != 0)
goto do_fail;
if ((rc = tty3270_scl_init(tubp)) != 0)
goto do_fail;
tubp->mode = TBM_LN;
tubp->intv = tty3270_int;
tubp->tty = tty;
tubp->lnopen = 1;
tty->driver_data = tubp;
tty->winsize.ws_row = tubp->geom_rows - 2;
tty->winsize.ws_col = tubp->geom_cols;
if (tubp->tty_input == NULL)
tubp->tty_input = kmalloc(GEOM_INPLEN, GFP_KERNEL|GFP_DMA);
tubp->tty_inattr = TF_INPUT;
tubp->cmd = cmd;
tty3270_build(tubp);
TUBUNLOCK(tubp->irq, flags);
return 0;
do_fail:
tty3270_scl_fini(tubp);
tty3270_aid_fini(tubp);
tty3270_rcl_fini(tubp);
TUBUNLOCK(tubp->irq, flags);
return rc;
}
static void
tty3270_close(struct tty_struct *tty, struct file *filp)
{
tub_t *tubp;
long flags;
if ((tubp = tty->driver_data) == NULL)
return;
tty3270_wait(tubp, &flags);
if (--tubp->lnopen > 0)
goto do_return;
tubp->tty = NULL;
tty->driver_data = NULL;
tty3270_aid_fini(tubp);
tty3270_rcl_fini(tubp);
tty3270_scl_fini(tubp);
do_return:
TUBUNLOCK(tubp->irq, flags);
}
static int
tty3270_write(struct tty_struct *tty, int fromuser,
const unsigned char *buf, int count)
{
tub_t *tubp;
long flags;
bcb_t obcb;
int rc = 0;
if ((tubp = tty->driver_data) == NULL)
return -1;
#ifdef CONFIG_TN3270_CONSOLE
if (CONSOLE_IS_3270 && tub3270_con_tubp == tubp)
tub3270_con_copy(tubp);
#endif /* CONFIG_TN3270_CONSOLE */
obcb.bc_buf = (char *)buf;
obcb.bc_len = obcb.bc_cnt = obcb.bc_wr = count;
obcb.bc_rd = 0;
TUBLOCK(tubp->irq, flags);
rc = tub3270_movedata(&obcb, &tubp->tty_bcb, fromuser);
tty3270_try_logging(tubp);
TUBUNLOCK(tubp->irq, flags);
return rc;
}
static void
tty3270_put_char(struct tty_struct *tty, unsigned char ch)
{
long flags;
tub_t *tubp;
bcb_t *ob;
if ((tubp = tty->driver_data) == NULL)
return;
TUBLOCK(tubp->irq, flags);
ob = &tubp->tty_bcb;
if (ob->bc_cnt < ob->bc_len) {
ob->bc_buf[ob->bc_wr++] = ch;
if (ob->bc_wr == ob->bc_len)
ob->bc_wr = 0;
ob->bc_cnt++;
}
tty3270_try_logging(tubp);
TUBUNLOCK(tubp->irq, flags);
}
static void
tty3270_flush_chars(struct tty_struct *tty)
{
tub_t *tubp;
long flags;
if ((tubp = tty->driver_data) == NULL)
return;
TUBLOCK(tubp->irq, flags);
tty3270_try_logging(tubp);
TUBUNLOCK(tubp->irq, flags);
}
static int
tty3270_write_room(struct tty_struct *tty)
{
tub_t *tubp;
bcb_t *ob;
if ((tubp = tty->driver_data) == NULL)
return -1;
ob = &tubp->tty_bcb;
return ob->bc_len - ob->bc_cnt;
}
static int
tty3270_chars_in_buffer(struct tty_struct *tty)
{
tub_t *tubp;
bcb_t *ob;
if ((tubp = tty->driver_data) == NULL)
return -1;
ob = &tubp->tty_bcb;
return ob->bc_cnt;
}
static int
tty3270_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
tub_t *tubp;
long flags;
int ret = 0;
struct termios termios;
if ((tubp = tty->driver_data) == NULL)
return -ENODEV;
TUBLOCK(tubp->irq, flags);
if (tty->flags * (1 << TTY_IO_ERROR)) {
ret = -EIO;
goto do_return;
}
switch(cmd) {
case TCGETS:
ret = -ENOIOCTLCMD;
goto do_return;
case TCFLSH: /* arg: 2 or 0 */
ret = -ENOIOCTLCMD;
goto do_return;
case TCSETSF:
if (user_termios_to_kernel_termios(&termios,
(struct termios *)arg)) {
ret = -EFAULT;
goto do_return;
}
ret = -ENOIOCTLCMD;
goto do_return;
case TCGETA:
ret = -ENOIOCTLCMD;
goto do_return;
case TCSETA:
if (user_termio_to_kernel_termios(&termios,
(struct termio *)arg)) {
ret = -EFAULT;
goto do_return;
}
ret = -ENOIOCTLCMD;
goto do_return;
default:
ret = -ENOIOCTLCMD;
break;
}
do_return:
TUBUNLOCK(tubp->irq, flags);
return ret;
}
static void
tty3270_set_termios(struct tty_struct *tty, struct termios *old)
{
tub_t *tubp;
long flags;
int new;
if ((tubp = tty->driver_data) == NULL)
return;
if (tty3270_wait(tubp, &flags) != 0) {
TUBUNLOCK(tubp->irq, flags);
return;
}
new = L_ICANON(tty)? L_ECHO(tty)? TF_INPUT: TF_INPUTN:
tubp->tty_inattr;
if (new != tubp->tty_inattr) {
tubp->tty_inattr = new;
tubp->cmd = TBC_CLRINPUT;
tty3270_build(tubp);
}
TUBUNLOCK(tubp->irq, flags);
}
static void
tty3270_flush_buffer(struct tty_struct *tty)
{
tub_t *tubp;
bcb_t *ob;
long flags;
if ((tubp = tty->driver_data) == NULL)
return;
if (tubp->mode == TBM_FS && tubp->fs_pid != 0) {
kill_proc(tubp->fs_pid, SIGHUP, 1);
}
if ((tubp->flags & TUB_OPEN_STET) == 0) {
ob = &tubp->tty_bcb;
TUBLOCK(tubp->irq, flags);
ob->bc_rd = 0;
ob->bc_wr = 0;
ob->bc_cnt = 0;
TUBUNLOCK(tubp->irq, flags);
}
wake_up_interruptible(&tty->write_wait);
if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
tty->ldisc.write_wakeup)
(tty->ldisc.write_wakeup)(tty);
}
static int
tty3270_read_proc(char *buf, char **start, off_t off, int count,
int *eof, void *data)
{
tub_t *tubp;
int begin = 0;
int i;
int rc;
int len = 0;
if (tty3270_proc_what == TW_CONFIG) {
/*
* Describe the 3270 configuration in ascii lines.
* Line 1: 0 <fsmajor> 0
* Console line: <devnum> CONSOLE <minor>
* Other lines: <devnum> <ttymajor> <minor>
*/
len += sprintf(buf + len, "0 %d 0\n", fs3270_major);
for (i = 1; i <= tubnummins; i++) {
tubp = (*tubminors)[i];
#ifdef CONFIG_TN3270_CONSOLE
if (CONSOLE_IS_3270 && tubp == tub3270_con_tubp)
len += sprintf(buf + len, "%.4x CONSOLE %d\n",
tubp->devno, i);
else
#endif
len += sprintf(buf + len, "%.4x %d %d\n",
tubp->devno, tty3270_major, i);
if (begin + len > off + count)
break;
if (begin + len < off) {
begin += len;
len = 0;
}
}
if (i > tubnummins)
*eof = 1;
if (off >= begin + len) {
rc = 0;
} else {
*start = buf + off - begin;
rc = MIN(count, begin + len - off);
}
if (*eof && rc == 0)
tty3270_proc_what = TW_BOGUS;
return rc;
}
len += sprintf(buf, "There are %d devices. fs major is %d, "
"tty major is %d.\n", tubnummins, fs3270_major,
tty3270_major);
len += sprintf(buf+len, " index=%d data=%d misc=%d\n",
tty3270_proc_index,
tty3270_proc_data,
tty3270_proc_misc);
/*
* Display info for the tube with minor nr in index
*/
len += tty3270_show_tube(tty3270_proc_index, buf+len, count-len);
*eof = 1;
if (off >= begin + len)
return 0;
*start = buf + off - begin;
return MIN(count, begin + len - off);
}
static int
tty3270_write_proc(struct file *file, const char *buffer,
unsigned long count, void *data)
{
char mybuf[GEOM_MAXINPLEN];
int mycount;
tub_t *tubp;
struct tty_struct *tty;
int rc;
mycount = MIN(count, sizeof mybuf - 1);
if (copy_from_user(mybuf, buffer, mycount) != 0)
return -EFAULT;
mybuf[mycount] = '\0';
/*
* User-mode settings affect only the current tty ---
*/
tubp = NULL;
tty = current->tty;
if (tty && tty->driver == tty3270_driver)
tubp = (*tubminors)[tty->index];
if (tubp) {
if ((rc = tty3270_aid_set(tubp, mybuf, mycount + 1)))
return rc > 0? count: rc;
if ((rc = tty3270_rcl_set(tubp, mybuf, mycount + 1)))
return rc > 0? count: rc;
if ((rc = tty3270_scl_set(tubp, mybuf, mycount + 1)))
return rc > 0? count: rc;
}
/*
* Superuser-mode settings affect the driver overall ---
*/
if (!capable(CAP_SYS_TTY_CONFIG)) {
return -EPERM;
} else if (strncmp(mybuf, "index=", 6) == 0) {
tty3270_proc_index = simple_strtoul(mybuf + 6, 0,0);
return count;
} else if (strncmp(mybuf, "data=", 5) == 0) {
tty3270_proc_data = simple_strtoul(mybuf + 5, 0, 0);
return count;
} else if (strncmp(mybuf, "misc=", 5) == 0) {
tty3270_proc_misc = simple_strtoul(mybuf + 5, 0, 0);
return count;
} else if (strncmp(mybuf, "what=", 5) == 0) {
if (strcmp(mybuf+5, "bogus") == 0)
tty3270_proc_what = 0;
else if (strncmp(mybuf+5, "config", 6) == 0)
tty3270_proc_what = TW_CONFIG;
return count;
} else {
return -EINVAL;
}
}
static void
tty3270_hangup(struct tty_struct *tty)
{
tub_t *tubp;
extern void fs3270_release(tub_t *);
if ((tubp = tty->driver_data) == NULL)
return;
tty3270_rcl_purge(tubp);
tty3270_aid_reinit(tubp);
fs3270_release(tubp);
}
/*
* tty3270_tasklet(tubp) -- Perform back-half processing
*/
static void
tty3270_tasklet(unsigned long data)
{
tub_t *tubp;
ioinfo_t *ioinfop;
long flags;
struct tty_struct *tty;
tubp = (tub_t *) data;
ioinfop = ioinfo[tubp->irq];
while (TUBTRYLOCK(tubp->irq, flags) == 0) {
if (ioinfop->ui.flags.unready == 1)
return;
}
if (ioinfop->ui.flags.unready == 1 ||
ioinfop->ui.flags.ready == 0)
goto do_unlock;
tubp->flags &= ~TUB_BHPENDING;
tty = tubp->tty;
if (tubp->flags & TUB_UNSOL_DE) {
tubp->flags &= ~TUB_UNSOL_DE;
if (tty != NULL) {
tty_hangup(tty);
wake_up_interruptible(&tubp->waitq);
goto do_unlock;
}
}
if (tubp->flags & TUB_IACTIVE) { /* If read ended, */
tty3270_do_input(tubp);
tubp->flags &= ~TUB_IACTIVE;
}
if ((tubp->flags & TUB_WORKING) == 0) {
if (tubp->flags & TUB_ATTN) {
tty3270_start_input(tubp);
tubp->flags &= ~TUB_ATTN;
} else if (tty3270_try_logging(tubp) == 0) {
wake_up_interruptible(&tubp->waitq);
}
}
if (tty != NULL) {
if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
tty->ldisc.write_wakeup != NULL)
(tty->ldisc.write_wakeup)(tty);
wake_up_interruptible(&tty->write_wait);
}
do_unlock:
TUBUNLOCK(tubp->irq, flags);
}
/*
* tty3270_sched_bh(tubp) -- Schedule the back half
* Irq lock must be held on entry and remains held on exit.
*/
void
tty3270_sched_bh(tub_t *tubp)
{
if (tubp->flags & TUB_BHPENDING)
return;
tubp->flags |= TUB_BHPENDING;
tasklet_init(&tubp->tasklet, tty3270_tasklet,
(unsigned long) tubp);
tasklet_schedule(&tubp->tasklet);
}
/*
* tty3270_io() -- Perform line-mode reads and writes here
*/
int
tty3270_io(tub_t *tubp)
{
int rc;
ccw1_t *ccwp;
tubp->flags |= TUB_WORKING;
tubp->dstat = 0;
ccwp = &tubp->ttyccw;
rc = do_IO(tubp->irq, ccwp, tubp->irq, 0, 0);
return rc;
}
/*
* tty3270_wait(tubp) -- Wait until TUB_WORKING is off
* On entry the lock must not be held; on exit it is held.
*/
static int
tty3270_wait(tub_t *tubp, long *flags)
{
DECLARE_WAITQUEUE(wait, current);
TUBLOCK(tubp->irq, *flags);
add_wait_queue(&tubp->waitq, &wait);
while (!signal_pending(current) &&
(tubp->flags & TUB_WORKING) != 0) {
#warning FIXME: [kj] use set_current_state instead of current->state=
current->state = TASK_INTERRUPTIBLE;
TUBUNLOCK(tubp->irq, *flags);
schedule();
#warning FIXME: [kj] use set_current_state instead of current->state=
current->state = TASK_RUNNING;
TUBLOCK(tubp->irq, *flags);
}
remove_wait_queue(&tubp->waitq, &wait);
return signal_pending(current)? -ERESTARTSYS: 0;
}
void
tty3270_int(tub_t *tubp, devstat_t *dsp)
{
#define DEV_UE_BUSY \
(DEV_STAT_CHN_END | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP)
#define DEV_NOT_WORKING \
(DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_CHECK)
tubp->dstat = dsp->dstat;
/* Handle CE-DE-UE and subsequent UDE */
if (dsp->dstat == DEV_UE_BUSY) {
tubp->flags |= TUB_UE_BUSY;
return;
} else if (tubp->flags & TUB_UE_BUSY) {
tubp->flags &= ~TUB_UE_BUSY;
if (dsp->dstat == DEV_STAT_DEV_END &&
(tubp->flags & TUB_WORKING) != 0) {
tty3270_io(tubp);
return;
}
}
/* Handle ATTN */
if (dsp->dstat & DEV_STAT_ATTENTION)
tubp->flags |= TUB_ATTN;
if (dsp->dstat & DEV_STAT_CHN_END) {
tubp->cswl = dsp->rescnt;
if ((dsp->dstat & DEV_STAT_DEV_END) == 0)
tubp->flags |= TUB_EXPECT_DE;
else
tubp->flags &= ~TUB_EXPECT_DE;
} else if (dsp->dstat & DEV_STAT_DEV_END) {
if ((tubp->flags & TUB_EXPECT_DE) == 0)
tubp->flags |= TUB_UNSOL_DE;
tubp->flags &= ~TUB_EXPECT_DE;
}
if (dsp->dstat & DEV_NOT_WORKING)
tubp->flags &= ~TUB_WORKING;
if (dsp->dstat & DEV_STAT_UNIT_CHECK)
tubp->sense = dsp->ii.sense;
if ((tubp->flags & TUB_WORKING) == 0)
tty3270_sched_bh(tubp);
}
/*
* tty3270_refresh(), called by fs3270_close() when tubp->fsopen == 0.
* On entry, lock is held.
*/
void
tty3270_refresh(tub_t *tubp)
{
if (tubp->lnopen) {
tubp->mode = TBM_LN;
tubp->intv = tty3270_int;
tty3270_scl_resettimer(tubp);
tubp->cmd = TBC_UPDATE;
tty3270_build(tubp);
}
}
static int
tty3270_try_logging(tub_t *tubp)
{
if (tubp->flags & TUB_WORKING)
return 0;
if (tubp->mode == TBM_FS)
return 0;
if (tubp->stat == TBS_HOLD)
return 0;
if (tubp->stat == TBS_MORE)
return 0;
#ifdef CONFIG_TN3270_CONSOLE
if (CONSOLE_IS_3270 && tub3270_con_tubp == tubp)
tub3270_con_copy(tubp);
#endif /* CONFIG_TN3270_CONSOLE */
if (tubp->tty_bcb.bc_cnt == 0)
return 0;
if (tubp->intv != tty3270_int)
return 0;
tubp->cmd = TBC_UPDLOG;
return tty3270_build(tubp);
}
/* tty3270 utility functions */
static void
tty3270_start_input(tub_t *tubp)
{
if (tubp->tty_input == NULL)
return;
tubp->ttyccw.cda = virt_to_phys(tubp->tty_input);
tubp->ttyccw.cmd_code = TC_READMOD;
tubp->ttyccw.count = GEOM_INPLEN;
tubp->ttyccw.flags = CCW_FLAG_SLI;
tty3270_io(tubp);
tubp->flags |= TUB_IACTIVE;
}
static void
tty3270_do_input(tub_t *tubp)
{
int count;
char *in;
int aidflags;
char *aidstring;
count = GEOM_INPLEN - tubp->cswl;
if ((in = tubp->tty_input) == NULL)
goto do_build;
tty3270_aid_get(tubp, in[0], &aidflags, &aidstring);
if (aidflags & TA_CLEARKEY) {
tubp->stat = TBS_RUNNING;
tty3270_scl_resettimer(tubp);
tubp->cmd = TBC_UPDATE;
} else if (aidflags & TA_CLEARLOG) {
tubp->stat = TBS_RUNNING;
tty3270_scl_resettimer(tubp);
tubp->cmd = TBC_CLRUPDLOG;
} else if (aidflags & TA_DOENTER) {
if (count <= 6) {
switch(tubp->stat) {
case TBS_MORE:
tubp->stat = TBS_HOLD;
tty3270_scl_resettimer(tubp);
break;
case TBS_HOLD:
tubp->stat = TBS_MORE;
tty3270_scl_settimer(tubp);
break;
case TBS_RUNNING:
tty3270_do_enter(tubp, in + 6, 0);
break;
}
tubp->cmd = TBC_UPDSTAT;
goto do_build;
}
in += 6;
count -= 6;
TUB_EBCASC(in, count);
tubp->cmd = TBC_CLRINPUT;
tty3270_do_enter(tubp, in, count);
} else if ((aidflags & TA_DOSTRING) != 0 && aidstring != NULL) {
tubp->cmd = TBC_KRUPDLOG;
tty3270_do_enter(tubp, aidstring, strlen(aidstring));
} else if ((aidflags & TA_DOSTRINGD) != 0 && aidstring != NULL) {
tty3270_do_showi(tubp, aidstring, strlen(aidstring));
tubp->cmd = TBC_UPDINPUT;
} else {
if (in[0] != 0x60)
tubp->flags |= TUB_ALARM;
tubp->cmd = TBC_KRUPDLOG;
}
do_build:
tty3270_build(tubp);
}
static void
tty3270_do_enter(tub_t *tubp, char *cp, int count)
{
struct tty_struct *tty;
int func = -1;
if ((tty = tubp->tty) == NULL)
return;
if (count < 0)
return;
if (count == 2 && (cp[0] == '^' || cp[0] == '\252')) {
switch(cp[1]) {
case 'c': case 'C':
func = INTR_CHAR(tty);
break;
case 'd': case 'D':
func = EOF_CHAR(tty);
break;
case 'z': case 'Z':
func = SUSP_CHAR(tty);
break;
}
} else if (count == 2 && cp[0] == 0x1b) { /* if ESC */
int inc = 0;
char buf[GEOM_INPLEN + 1];
int len;
switch(cp[1]) {
case 'k': case 'K':
inc = -1;
break;
case 'j': case 'J':
inc = 1;
break;
}
if (inc == 0)
goto not_rcl;
len = tty3270_rcl_get(tubp, buf, sizeof buf, inc);
if (len == 0) {
tubp->flags |= TUB_ALARM;
return;
}
tty3270_do_showi(tubp, buf, len);
tubp->cmd = TBC_UPDINPUT;
return;
}
not_rcl:
if (func != -1) {
*tty->flip.flag_buf_ptr++ = TTY_NORMAL;
*tty->flip.char_buf_ptr++ = func;
tty->flip.count++;
} else {
tty3270_rcl_put(tubp, cp, count);
memcpy(tty->flip.char_buf_ptr, cp, count);
/* Add newline unless line ends with "^n" */
if (count < 2 || cp[count - 1] != 'n' ||
(cp[count - 2] != '^' && cp[count - 2] != '\252')) {
tty->flip.char_buf_ptr[count] = '\n';
count++;
} else {
count -= 2; /* Lop trailing "^n" from text */
}
memset(tty->flip.flag_buf_ptr, TTY_NORMAL, count);
tty->flip.char_buf_ptr += count;
tty->flip.flag_buf_ptr += count;
tty->flip.count += count;
}
tty_flip_buffer_push(tty);
}
static void
tty3270_do_showi(tub_t *tubp, char *cp, int cl)
{
if (cl > GEOM_INPLEN)
cl = GEOM_INPLEN;
memset(tubp->tty_input, 0, GEOM_INPLEN);
memcpy(tubp->tty_input, cp, cl);
TUB_ASCEBC(tubp->tty_input, cl);
}
/* Debugging routine */
static int
tty3270_show_tube(int minor, char *buf, int count)
{
tub_t *tubp;
struct tty_struct *tty;
struct termios *mp;
int len;
/*012345678901234567890123456789012345678901234567890123456789 */
/*Info for tub_t[dd] at xxxxxxxx: */
/* geom: rows=dd cols=dd model=d */
/* lnopen=dd fsopen=dd waitq=xxxxxxxx */
/* dstat=xx mode=dd stat=dd flags=xxxx */
/* oucount=dddd ourd=ddddd ouwr=ddddd nextlogx=ddddd */
/* tty=xxxxxxxx */
/* write_wait=xxxxxxxx read_wait=xxxxxxxx */
/* iflag=xxxxxxxx oflag=xxxxxxxx cflag=xxxxxxxx lflag=xxxxxxxx */
if (minor < 0 || minor > tubnummins ||
(tubp = (*tubminors)[minor]) == NULL)
return sprintf(buf, "No tube at index=%d\n", minor);
tty = tubp->tty;
len = 0;
len += sprintf(buf+len, "Info for tub_t[%d] at %p:\n", minor, tubp);
len += sprintf(buf+len, "inattr is at %p\n", &tubp->tty_inattr);
len += sprintf(buf+len, " geom: rows=%.2d cols=%.2d model=%.1d\n",
tubp->geom_rows, tubp->geom_cols, tubp->tubiocb.model);
len += sprintf(buf+len,
" lnopen=%-2d fsopen=%-2d waitq=%p\n",
tubp->lnopen, tubp->fsopen, &tubp->waitq);
len += sprintf(buf+len, " dstat=%.2x mode=%-2d "
"stat=%-2d flags=%-4x\n", tubp->dstat,
tubp->mode, tubp->stat, tubp->flags);
#ifdef RBH_FIXTHIS
len += sprintf(buf+len,
" oucount=%-4d ourd=%-5d ouwr=%-5d"
" nextlogx=%-5d\n", tubp->tty_oucount,
tubp->tty_ourd, tubp->tty_ouwr, tubp->tty_nextlogx);
#endif
len += sprintf(buf+len, " tty=%p\n",tubp->tty);
if (tty)
len += sprintf(buf+len,
" write_wait=%p read_wait=%p\n",
&tty->write_wait, &tty->read_wait);
if (tty && ((mp = tty->termios)))
len += sprintf(buf+len," iflag=%.8x oflag=%.8x "
"cflag=%.8x lflag=%.8x\n", mp->c_iflag,
mp->c_oflag, mp->c_cflag, mp->c_lflag);
return len;
}
/*
* IBM/3270 Driver -- Copyright (C) 2000 UTS Global LLC
*
* tubttyaid.c -- Linemode Attention-ID functionality
*
*
*
*
*
* Author: Richard Hitt
*/
#include "tubio.h"
#define PA1_STR "^C"
#define PF3_STR "^D"
#define PF9_STR "\033j"
#define PF10_STR "\033k"
#define PF11_STR "\033j"
/* other AID-key default strings */
static aid_t aidtab[64] = {
/* 00 */ { 0, 0 },
/* C1 = PF13 */ { TA_DOSTRING, 0 },
/* C2 = PF14 */ { TA_DOSTRING, 0 },
/* C3 = PF15 */ { TA_DOSTRING, 0 },
/* C4 = PF16 */ { TA_DOSTRING, 0 },
/* C5 = PF17 */ { TA_DOSTRING, 0 },
/* C6 = PF18 */ { TA_DOSTRING, 0 },
/* C7 = PF19 */ { TA_DOSTRING, 0 },
/* C8 = PF20 */ { TA_DOSTRING, 0 },
/* C9 = PF21 */ { TA_DOSTRING, 0 },
/* 4A = PF22 */ { TA_DOSTRING, 0 },
/* 4B = PF23 */ { TA_DOSTRING, 0 },
/* 4C = PF24 */ { TA_DOSTRING, 0 },
/* 0D */ { 0, 0 },
/* 0E */ { 0, 0 },
/* 0F */ { 0, 0 },
/* 10 */ { 0, 0 },
/* 11 */ { 0, 0 },
/* 12 */ { 0, 0 },
/* 13 */ { 0, 0 },
/* 14 */ { 0, 0 },
/* 15 */ { 0, 0 },
/* 16 */ { 0, 0 },
/* 17 */ { 0, 0 },
/* 18 */ { 0, 0 },
/* 19 */ { 0, 0 },
/* 1A */ { 0, 0 },
/* 1B */ { 0, 0 },
/* 1C */ { 0, 0 },
/* 1D */ { 0, 0 },
/* 1E */ { 0, 0 },
/* 1F */ { 0, 0 },
/* 60 = NoAID */ { 0, 0 },
/* 21 */ { 0, 0 },
/* 22 */ { 0, 0 },
/* 23 */ { 0, 0 },
/* 24 */ { 0, 0 },
/* 25 */ { 0, 0 },
/* E6 = OpRdr */ { 0, 0 },
/* E7 = MSRdr */ { 0, 0 },
/* E8 = NoAID */ { 0, 0 },
/* 29 */ { 0, 0 },
/* 2A */ { 0, 0 },
/* 6B = PA3 */ { TA_SHORTREAD, 0 },
/* 6C = PA1 */ { TA_SHORTREAD | TA_DOSTRING, PA1_STR },
/* 6D = CLEAR */ { TA_SHORTREAD | TA_CLEARKEY, 0 },
/* 6E = PA2 */ { TA_SHORTREAD | TA_CLEARLOG, 0 },
/* 2F */ { 0, 0 },
/* F0 = TstRq */ { 0, 0 },
/* F1 = PF1 */ { TA_DOSTRING, 0 },
/* F2 = PF2 */ { TA_DOSTRING, 0 },
/* F3 = PF3 */ { TA_DOSTRING, PF3_STR },
/* F4 = PF4 */ { TA_DOSTRING, 0 },
/* F5 = PF5 */ { TA_DOSTRING, 0 },
/* F6 = PF6 */ { TA_DOSTRING, 0 },
/* F7 = PF7 */ { TA_DOSTRING, 0 },
/* F8 = PF8 */ { TA_DOSTRING, 0 },
/* F9 = PF9 */ { TA_DOSTRING, PF9_STR },
/* 7A = PF10 */ { TA_DOSTRING, PF10_STR },
/* 7B = PF11 */ { TA_DOSTRING, PF11_STR },
/* 7C = PF12 */ { TA_DOSTRING, 0 },
/* 7D = ENTER */ { TA_DOENTER, 0 },
/* 7E = Pen */ { 0, 0 },
/* 3F */ { 0, 0 },
};
int
tty3270_aid_init(tub_t *tubp)
{
memcpy(tubp->tty_aid, aidtab, sizeof aidtab);
tubp->tty_aidinit = 1;
return 0;
}
void
tty3270_aid_fini(tub_t *tubp)
{
int i;
char *sp;
if (tubp->tty_aidinit == 0)
return;
for (i = 0; i < 64; i++) {
if ((sp = tubp->tty_aid[i].string) == NULL)
continue;
if (sp == aidtab[i].string)
continue;
kfree(sp);
}
tubp->tty_aidinit = 0;
}
void
tty3270_aid_reinit(tub_t *tubp)
{
tty3270_aid_fini(tubp);
tty3270_aid_init(tubp);
}
int
tty3270_aid_get(tub_t *tubp, int aid, int *aidflags, char **aidstring)
{
aid_t *ap;
ap = AIDENTRY(aid, tubp);
*aidflags = ap->aid;
*aidstring = ap->string;
return 0;
}
/*
* tty3270_aid_set() -- write_proc extension
* Parse written string as an AID name. Return 0 if it's not.
* Otherwise absorb the string and return count or -error.
*/
int
tty3270_aid_set(tub_t *tubp, char *buf, int count)
{
char name[8];
char *sp;
int aidn, aidx;
aid_t *ap;
int len;
char *pfp;
if (tubp->tty_aidinit == 0)
return 0;
if (count < 3) /* If AID-key name too short */
return 0;
name[0] = buf[0] < 0x60? buf[0]: (buf[0] & 0x5f);
name[1] = buf[1] < 0x60? buf[1]: (buf[1] & 0x5f);
if (name[0] == 'P' && name[1] == 'F') {
aidn = simple_strtoul(buf+2, &sp, 10);
if (aidn < 1 || aidn > 24)
return 0;
aidx = aidn > 12? aidn - 12: aidn + 0x30;
ap = &tubp->tty_aid[aidx];
} else if (name[0] == 'P' && name[1] == 'A') {
aidn = simple_strtoul(buf+2, &sp, 10);
if (aidn < 1 || aidn > 3)
return 0;
switch(aidn) {
case 1: aidx = 0x2c; break;
case 2: aidx = 0x2e; break;
case 3: aidx = 0x2b; break;
default: aidx = 0; break;
}
ap = &tubp->tty_aid[aidx];
} else {
return 0;
}
if (*sp == '\0') {
tubp->tty_showaidx = ap - tubp->tty_aid;
return count;
} else if (*sp == '=') {
len = strlen(++sp);
if (len == 0) {
if (ap->string != NULL &&
ap->string != aidtab[aidx].string)
kfree(ap->string);
ap->string = aidtab[aidx].string;
ap->aid = aidtab[aidx].aid;
return count;
}
if ((pfp = kmalloc(len + 1, GFP_KERNEL)) == NULL)
return -ENOMEM;
if (ap->string != NULL &&
ap->string != aidtab[aidx].string)
kfree(ap->string);
if (sp[len - 1] == '\n') {
ap->aid = TA_DOSTRING;
sp[len - 1] = '\0';
len--;
} else {
ap->aid = TA_DOSTRINGD;
}
memcpy(pfp, sp, len + 1);
ap->string = pfp;
return count;
} else {
return -EINVAL;
}
}
/*
* IBM/3270 Driver -- Copyright (C) 2000 UTS Global LLC
*
* tubttybld.c -- Linemode tty driver screen-building functions
*
*
*
*
*
* Author: Richard Hitt
*/
#include "tubio.h"
extern int tty3270_io(tub_t *);
static void tty3270_set_status_area(tub_t *, char **);
static int tty3270_next_char(tub_t *);
static void tty3270_unnext_char(tub_t *, char);
static void tty3270_update_log_area(tub_t *, char **);
static int tty3270_update_log_area_esc(tub_t *, char **, int *);
static void tty3270_clear_log_area(tub_t *, char **);
static void tty3270_tub_bufadr(tub_t *, int, char **);
static void tty3270_set_bufadr(tub_t *, char **, int *);
/*
* tty3270_clear_log_area(tub_t *tubp, char **cpp)
*/
static void
tty3270_clear_log_area(tub_t *tubp, char **cpp)
{
*(*cpp)++ = TO_SBA;
TUB_BUFADR(GEOM_LOG, cpp);
*(*cpp)++ = TO_SF;
*(*cpp)++ = TF_LOG;
*(*cpp)++ = TO_RA;
TUB_BUFADR(GEOM_INPUT, cpp);
*(*cpp)++ = '\0';
tubp->tty_oucol = tubp->tty_nextlogx = 0;
*(*cpp)++ = TO_SBA;
TUB_BUFADR(tubp->tty_nextlogx, cpp);
}
static void
tty3270_update_log_area(tub_t *tubp, char **cpp)
{
int lastx = GEOM_INPUT;
int c;
int next, fill, i;
int sba_needed = 1;
char *overrun = &(*tubp->ttyscreen)[tubp->ttyscreenl - TS_LENGTH];
/* Check for possible ESC sequence work to do */
if (tubp->tty_escx != 0) {
/* If compiling new escape sequence */
if (tubp->tty_esca[0] == 0x1b) {
if (tty3270_update_log_area_esc(tubp, cpp, &sba_needed))
return;
/* If esc seq needs refreshing after a write */
} else if (tubp->tty_esca[0] == TO_SA) {
tty3270_set_bufadr(tubp, cpp, &sba_needed);
for (i = 0; i < tubp->tty_escx; i++)
*(*cpp)++ = tubp->tty_esca[i];
} else {
printk(KERN_WARNING "tty3270_update_log_area esca "
"character surprising: %.2x\n", tubp->tty_esca[0]);
}
}
/* Place characters */
while (tubp->tty_bcb.bc_cnt != 0) {
/* Check for room. TAB could take up to 4 chars. */
if (&(*cpp)[4] >= overrun)
break;
/* Fetch a character */
if ((c = tty3270_next_char(tubp)) == -1)
break;
switch(c) {
default:
if (tubp->tty_nextlogx >= lastx) {
if (sba_needed == 0 ||
tubp->stat == TBS_RUNNING) {
tty3270_unnext_char(tubp, c);
tubp->stat = TBS_MORE;
tty3270_set_status_area(tubp, cpp);
tty3270_scl_settimer(tubp);
}
goto do_return;
}
tty3270_set_bufadr(tubp, cpp, &sba_needed);
/* Use blank if we don't know the character */
*(*cpp)++ = tub_ascebc[(int)(c < ' '? ' ': c)];
tubp->tty_nextlogx++;
tubp->tty_oucol++;
break;
case 0x1b: /* ESC */
tubp->tty_escx = 0;
if (tty3270_update_log_area_esc(tubp, cpp, &sba_needed))
return;
break;
case '\r': /* 0x0d -- Carriage Return */
tubp->tty_nextlogx -=
tubp->tty_nextlogx % GEOM_COLS;
sba_needed = 1;
break;
case '\n': /* 0x0a -- New Line */
if (tubp->tty_oucol == GEOM_COLS) {
tubp->tty_oucol = 0;
break;
}
next = (tubp->tty_nextlogx + GEOM_COLS) /
GEOM_COLS * GEOM_COLS;
tubp->tty_nextlogx = next;
tubp->tty_oucol = 0;
sba_needed = 1;
break;
case '\t': /* 0x09 -- Tabulate */
fill = (tubp->tty_nextlogx % GEOM_COLS) % 8;
for (; fill < 8; fill++) {
if (tubp->tty_nextlogx >= lastx)
break;
*(*cpp)++ = tub_ascebc[' '];
tubp->tty_nextlogx++;
tubp->tty_oucol++;
}
break;
case '\a': /* 0x07 -- Alarm */
tubp->flags |= TUB_ALARM;
break;
case '\f': /* 0x0c -- Form Feed */
tty3270_clear_log_area(tubp, cpp);
break;
case 0xf: /* SuSE "exit alternate mode" */
break;
}
}
do_return:
}
#define NUMQUANT 8
static int
tty3270_update_log_area_esc(tub_t *tubp, char **cpp, int *sba_needed)
{
int c;
int i, j;
int start, end, next;
int quant[NUMQUANT];
char *overrun = &(*tubp->ttyscreen)[tubp->ttyscreenl - TS_LENGTH];
char sabuf[NUMQUANT*3], *sap = sabuf, *cp;
/* If starting a sequence, stuff ESC at [0] */
if (tubp->tty_escx == 0)
tubp->tty_esca[tubp->tty_escx++] = 0x1b;
/* Now that sequence is started, see if room in buffer */
if (&(*cpp)[NUMQUANT * 3] >= overrun)
return tubp->tty_escx;
/* Gather the rest of the sequence's characters */
while (tubp->tty_escx < sizeof tubp->tty_esca) {
if ((c = tty3270_next_char(tubp)) == -1)
return tubp->tty_escx;
if (tubp->tty_escx == 1) {
switch(c) {
case '[':
tubp->tty_esca[tubp->tty_escx++] = c;
continue;
case '7':
tubp->tty_savecursor = tubp->tty_nextlogx;
goto done_return;
case '8':
next = tubp->tty_savecursor;
goto do_setcur;
default:
goto error_return;
}
}
tubp->tty_esca[tubp->tty_escx++] = c;
if (c != ';' && (c < '0' || c > '9'))
break;
}
/* Check for overrun */
if (tubp->tty_escx == sizeof tubp->tty_esca)
goto error_return;
/* Parse potentially empty string "nn;nn;nn..." */
i = -1;
j = 2; /* skip ESC, [ */
c = ';';
do {
if (c == ';') {
if (++i == NUMQUANT)
goto error_return;
quant[i] = 0;
} else if (c < '0' || c > '9') {
break;
} else {
quant[i] = quant[i] * 10 + c - '0';
}
c = tubp->tty_esca[j];
} while (j++ < tubp->tty_escx);
/* Add 3270 data stream output to execute the sequence */
switch(c) {
case 'm': /* Set Attribute */
for (next = 0; next <= i; next++) {
int type = -1, value = 0;
switch(quant[next]) {
case 0: /* Reset */
next = tubp->tty_nextlogx;
tty3270_set_bufadr(tubp, cpp, sba_needed);
*(*cpp)++ = TO_SA;
*(*cpp)++ = TAT_EXTHI;
*(*cpp)++ = TAX_RESET;
*(*cpp)++ = TO_SA;
*(*cpp)++ = TAT_COLOR;
*(*cpp)++ = TAC_RESET;
tubp->tty_nextlogx = next;
*sba_needed = 1;
sap = sabuf;
break;
case 1: /* Bright */
break;
case 2: /* Dim */
break;
case 4: /* Underscore */
type = TAT_EXTHI; value = TAX_UNDER;
break;
case 5: /* Blink */
type = TAT_EXTHI; value = TAX_BLINK;
break;
case 7: /* Reverse */
type = TAT_EXTHI; value = TAX_REVER;
break;
case 8: /* Hidden */
break; /* For now ... */
/* Foreground Colors */
case 30: /* Black */
type = TAT_COLOR; value = TAC_DEFAULT;
break;
case 31: /* Red */
type = TAT_COLOR; value = TAC_RED;
break;
case 32: /* Green */
type = TAT_COLOR; value = TAC_GREEN;
break;
case 33: /* Yellow */
type = TAT_COLOR; value = TAC_YELLOW;
break;
case 34: /* Blue */
type = TAT_COLOR; value = TAC_BLUE;
break;
case 35: /* Magenta */
type = TAT_COLOR; value = TAC_PINK;
break;
case 36: /* Cyan */
type = TAT_COLOR; value = TAC_TURQ;
break;
case 37: /* White */
type = TAT_COLOR; value = TAC_WHITE;
break;
case 39: /* Black */
type = TAT_COLOR; value = TAC_DEFAULT;
break;
/* Background Colors */
case 40: /* Black */
case 41: /* Red */
case 42: /* Green */
case 43: /* Yellow */
case 44: /* Blue */
case 45: /* Magenta */
case 46: /* Cyan */
case 47: /* White */
break; /* For now ... */
/* Oops */
default:
break;
}
if (type != -1) {
tty3270_set_bufadr(tubp, cpp, sba_needed);
*(*cpp)++ = TO_SA;
*(*cpp)++ = type;
*(*cpp)++ = value;
*sap++ = TO_SA;
*sap++ = type;
*sap++ = value;
}
}
break;
case 'H': /* Cursor Home */
case 'f': /* Force Cursor Position */
if (quant[0]) quant[0]--;
if (quant[1]) quant[1]--;
next = quant[0] * GEOM_COLS + quant[1];
goto do_setcur;
case 'A': /* Cursor Up */
if (quant[i] == 0) quant[i] = 1;
next = tubp->tty_nextlogx - GEOM_COLS * quant[i];
goto do_setcur;
case 'B': /* Cursor Down */
if (quant[i] == 0) quant[i] = 1;
next = tubp->tty_nextlogx + GEOM_COLS * quant[i];
goto do_setcur;
case 'C': /* Cursor Forward */
if (quant[i] == 0) quant[i] = 1;
next = tubp->tty_nextlogx % GEOM_COLS;
start = tubp->tty_nextlogx - next;
next = start + MIN(next + quant[i], GEOM_COLS - 1);
goto do_setcur;
case 'D': /* Cursor Backward */
if (quant[i] == 0) quant[i] = 1;
next = MIN(quant[i], tubp->tty_nextlogx % GEOM_COLS);
next = tubp->tty_nextlogx - next;
goto do_setcur;
case 'G':
if (quant[0]) quant[0]--;
next = tubp->tty_nextlogx / GEOM_COLS * GEOM_COLS + quant[0];
do_setcur:
if (next < 0)
break;
tubp->tty_nextlogx = next;
tubp->tty_oucol = tubp->tty_nextlogx % GEOM_COLS;
*sba_needed = 1;
break;
case 'r': /* Define scroll area */
start = quant[0];
if (start <= 0) start = 1;
if (start > GEOM_ROWS - 2) start = GEOM_ROWS - 2;
tubp->tty_nextlogx = (start - 1) * GEOM_COLS;
tubp->tty_oucol = 0;
*sba_needed = 1;
break;
case 'X': /* Erase for n chars from cursor */
start = tubp->tty_nextlogx;
end = start + (quant[0]?: 1);
goto do_fill;
case 'J': /* Erase to screen end from cursor */
*(*cpp)++ = TO_SBA;
TUB_BUFADR(tubp->tty_nextlogx, cpp);
*(*cpp)++ = TO_RA;
TUB_BUFADR(GEOM_INPUT, cpp);
*(*cpp)++ = tub_ascebc[' '];
*(*cpp)++ = TO_SBA;
TUB_BUFADR(tubp->tty_nextlogx, cpp);
break;
case 'K':
start = tubp->tty_nextlogx;
end = (start + GEOM_COLS) / GEOM_COLS * GEOM_COLS;
do_fill:
if (start >= GEOM_INPUT)
break;
if (end > GEOM_INPUT)
end = GEOM_INPUT;
if (end <= start)
break;
*(*cpp)++ = TO_SBA;
TUB_BUFADR(start, cpp);
if (end - start > 4) {
*(*cpp)++ = TO_RA;
TUB_BUFADR(end, cpp);
*(*cpp)++ = tub_ascebc[' '];
} else while (start++ < end) {
*(*cpp)++ = tub_ascebc[' '];
}
tubp->tty_nextlogx = end;
tubp->tty_oucol = tubp->tty_nextlogx % GEOM_COLS;
*sba_needed = 1;
break;
}
done_return:
tubp->tty_escx = 0;
cp = sabuf;
while (cp != sap)
tubp->tty_esca[tubp->tty_escx++] = *cp++;
return 0;
error_return:
tubp->tty_escx = 0;
return 0;
}
static int
tty3270_next_char(tub_t *tubp)
{
int c;
bcb_t *ib;
ib = &tubp->tty_bcb;
if (ib->bc_cnt == 0)
return -1;
c = ib->bc_buf[ib->bc_rd++];
if (ib->bc_rd == ib->bc_len)
ib->bc_rd = 0;
ib->bc_cnt--;
return c;
}
static void
tty3270_unnext_char(tub_t *tubp, char c)
{
bcb_t *ib;
ib = &tubp->tty_bcb;
if (ib->bc_rd == 0)
ib->bc_rd = ib->bc_len;
ib->bc_buf[--ib->bc_rd] = c;
ib->bc_cnt++;
}
static void
tty3270_clear_input_area(tub_t *tubp, char **cpp)
{
*(*cpp)++ = TO_SBA;
TUB_BUFADR(GEOM_INPUT, cpp);
*(*cpp)++ = TO_SF;
*(*cpp)++ = tubp->tty_inattr;
*(*cpp)++ = TO_IC;
*(*cpp)++ = TO_RA;
TUB_BUFADR(GEOM_STAT, cpp);
*(*cpp)++ = '\0';
}
static void
tty3270_update_input_area(tub_t *tubp, char **cpp)
{
int len;
*(*cpp)++ = TO_SBA;
TUB_BUFADR(GEOM_INPUT, cpp);
*(*cpp)++ = TO_SF;
*(*cpp)++ = TF_INMDT;
len = strlen(tubp->tty_input);
memcpy(*cpp, tubp->tty_input, len);
*cpp += len;
*(*cpp)++ = TO_IC;
len = GEOM_INPLEN - len;
if (len > 4) {
*(*cpp)++ = TO_RA;
TUB_BUFADR(GEOM_STAT, cpp);
*(*cpp)++ = '\0';
} else {
for (; len > 0; len--)
*(*cpp)++ = '\0';
}
}
/*
* tty3270_set_status_area(tub_t *tubp, char **cpp)
*/
static void
tty3270_set_status_area(tub_t *tubp, char **cpp)
{
char *sp;
if (tubp->stat == TBS_RUNNING)
sp = TS_RUNNING;
else if (tubp->stat == TBS_MORE)
sp = TS_MORE;
else if (tubp->stat == TBS_HOLD)
sp = TS_HOLD;
else
sp = "Linux Whatstat";
*(*cpp)++ = TO_SBA;
TUB_BUFADR(GEOM_STAT, cpp);
*(*cpp)++ = TO_SF;
*(*cpp)++ = TF_STAT;
memcpy(*cpp, sp, sizeof TS_RUNNING);
TUB_ASCEBC(*cpp, sizeof TS_RUNNING);
*cpp += sizeof TS_RUNNING;
}
/*
* tty3270_build() -- build an output stream
*/
int
tty3270_build(tub_t *tubp)
{
char *cp, *startcp;
int chancmd;
int writecc = TW_KR;
int force = 0;
if (tubp->mode == TBM_FS)
return 0;
cp = startcp = *tubp->ttyscreen + 1;
switch(tubp->cmd) {
default:
printk(KERN_WARNING "tty3270_build unknown command %d\n", tubp->cmd);
return 0;
case TBC_OPEN:
tbc_open:
tubp->flags &= ~TUB_INPUT_HACK;
chancmd = TC_EWRITEA;
tty3270_clear_input_area(tubp, &cp);
tty3270_set_status_area(tubp, &cp);
tty3270_clear_log_area(tubp, &cp);
break;
case TBC_UPDLOG:
if (tubp->flags & TUB_INPUT_HACK)
goto tbc_open;
chancmd = TC_WRITE;
writecc = TW_NONE;
tty3270_update_log_area(tubp, &cp);
break;
case TBC_KRUPDLOG:
chancmd = TC_WRITE;
force = 1;
tty3270_update_log_area(tubp, &cp);
break;
case TBC_CLRUPDLOG:
chancmd = TC_WRITE;
tty3270_set_status_area(tubp, &cp);
tty3270_clear_log_area(tubp, &cp);
tty3270_update_log_area(tubp, &cp);
break;
case TBC_UPDATE:
chancmd = TC_EWRITEA;
tubp->tty_oucol = tubp->tty_nextlogx = 0;
tty3270_clear_input_area(tubp, &cp);
tty3270_set_status_area(tubp, &cp);
tty3270_update_log_area(tubp, &cp);
break;
case TBC_UPDSTAT:
chancmd = TC_WRITE;
tty3270_set_status_area(tubp, &cp);
break;
case TBC_CLRINPUT:
chancmd = TC_WRITE;
tty3270_clear_input_area(tubp, &cp);
break;
case TBC_UPDINPUT:
chancmd = TC_WRITE;
tty3270_update_input_area(tubp, &cp);
break;
}
/* Set Write Control Character and start I/O */
if (force == 0 && cp == startcp &&
(tubp->flags & TUB_ALARM) == 0)
return 0;
if (tubp->flags & TUB_ALARM) {
tubp->flags &= ~TUB_ALARM;
writecc |= TW_PLUSALARM;
}
**tubp->ttyscreen = writecc;
tubp->ttyccw.cmd_code = chancmd;
tubp->ttyccw.flags = CCW_FLAG_SLI;
tubp->ttyccw.cda = virt_to_phys(*tubp->ttyscreen);
tubp->ttyccw.count = cp - *tubp->ttyscreen;
tty3270_io(tubp);
return 1;
}
static void
tty3270_tub_bufadr(tub_t *tubp, int adr, char **cpp)
{
if (tubp->tty_14bitadr) {
*(*cpp)++ = (adr >> 8) & 0x3f;
*(*cpp)++ = adr & 0xff;
} else {
*(*cpp)++ = tub_ebcgraf[(adr >> 6) & 0x3f];
*(*cpp)++ = tub_ebcgraf[adr & 0x3f];
}
}
static void
tty3270_set_bufadr(tub_t *tubp, char **cpp, int *sba_needed)
{
if (!*sba_needed)
return;
if (tubp->tty_nextlogx >= GEOM_INPUT) {
tubp->tty_nextlogx = GEOM_INPUT - 1;
tubp->tty_oucol = tubp->tty_nextlogx % GEOM_COLS;
}
*(*cpp)++ = TO_SBA;
TUB_BUFADR(tubp->tty_nextlogx, cpp);
*sba_needed = 0;
}
/*
* IBM/3270 Driver -- Copyright (C) 2000 UTS Global LLC
*
* tubttyrcl.c -- Linemode Command-recall functionality
*
*
*
*
*
* Author: Richard Hitt
*/
#include "tubio.h"
static void tty3270_rcl_sync(tub_t *);
static int tty3270_rcl_resize(tub_t *, int);
int
tty3270_rcl_init(tub_t *tubp)
{
return tty3270_rcl_resize(tubp, 20);
}
static int
tty3270_rcl_resize(tub_t *tubp, int newrclk)
{
char *(*newrclb)[];
if (newrclk > 1000)
return -EINVAL;
if (newrclk <= 0) {
tty3270_rcl_purge(tubp),
kfree(tubp->tty_rclbufs);
tubp->tty_rclbufs = NULL;
return 0;
}
if ((newrclb = (char *(*)[])kmalloc(
newrclk * sizeof (char *), GFP_KERNEL)) == NULL)
return -ENOMEM;
memset(newrclb, 0, newrclk * sizeof (char *));
if (tubp->tty_rclbufs != NULL) {
int i, j, k;
char *data;
i = tubp->tty_rclp;
j = newrclk;
k = tubp->tty_rclk;
while (j-- && k--) {
if ((data = (*tubp->tty_rclbufs)[i]) == NULL)
break;
(*newrclb)[j] = data;
(*tubp->tty_rclbufs)[i] = NULL;
if (--i < 0)
i = tubp->tty_rclk - 1;
}
tty3270_rcl_purge(tubp);
kfree(tubp->tty_rclbufs);
}
tubp->tty_rclbufs = newrclb;
tubp->tty_rclk = newrclk;
tubp->tty_rclp = newrclk - 1;
tty3270_rcl_sync(tubp);
return 0;
}
int
tty3270_rcl_set(tub_t *tubp, char *buf, int count)
{
#define RCL_SIZ "recallsize="
#define L_RCL_SIZ (strlen(RCL_SIZ))
int newsize;
int len;
int rc;
char *rcl_siz = RCL_SIZ;
int l_rcl_siz = L_RCL_SIZ;
if (count < l_rcl_siz || strncmp(buf, rcl_siz, l_rcl_siz) != 0)
return 0;
if ((len = count - l_rcl_siz) == 0)
return count;
newsize = simple_strtoul(buf + l_rcl_siz, 0, 0);
rc = tty3270_rcl_resize(tubp, newsize);
return rc < 0? rc: count;
}
void
tty3270_rcl_fini(tub_t *tubp)
{
if (tubp->tty_rclbufs != NULL) {
tty3270_rcl_purge(tubp);
kfree(tubp->tty_rclbufs);
tubp->tty_rclbufs = NULL;
}
}
void
tty3270_rcl_purge(tub_t *tubp)
{
int i;
char *buf;
if (tubp->tty_rclbufs == NULL)
return;
for (i = 0; i < tubp->tty_rclk; i++) {
if ((buf = (*tubp->tty_rclbufs)[i]) == NULL)
continue;
kfree(buf);
(*tubp->tty_rclbufs)[i] = NULL;
}
}
int
tty3270_rcl_get(tub_t *tubp, char *buf, int len, int inc)
{
int iter;
int i;
char *data;
if (tubp->tty_rclbufs == NULL)
return 0;
if (tubp->tty_rclk <= 0) /* overcautious */
return 0;
if (inc != 1 && inc != -1) /* overcautious */
return 0;
if ((i = tubp->tty_rclb) == -1) {
i = tubp->tty_rclp;
if (inc == 1)
i++;
} else {
i += inc;
}
for (iter = tubp->tty_rclk; iter; iter--, i += inc) {
if (i < 0)
i = tubp->tty_rclk - 1;
else if (i >= tubp->tty_rclk)
i = 0;
if ((*tubp->tty_rclbufs)[i] != NULL)
break;
}
if (iter < 0 || (data = (*tubp->tty_rclbufs)[i]) == NULL)
return 0;
tubp->tty_rclb = i;
if ((len = MIN(len - 1, strlen(data))) <= 0)
return 0;
memcpy(buf, data, len);
buf[len] = '\0';
return len;
}
void
tty3270_rcl_put(tub_t *tubp, char *data, int len)
{
char *buf, **bufp;
int i;
if (tubp->tty_rclbufs == NULL)
return;
if (tubp->tty_rclk <= 0) /* overcautious */
return;
/* If input area is invisible, don't log */
if (tubp->tty_inattr == TF_INPUTN)
return;
/* If this & most recent cmd text match, don't log */
if ((buf = (*tubp->tty_rclbufs)[tubp->tty_rclp]) != NULL &&
strlen(buf) == len && memcmp(buf, data, len) == 0) {
tty3270_rcl_sync(tubp);
return;
}
/* Don't stack zero-length commands */
if (len == 0) {
tty3270_rcl_sync(tubp);
return;
}
i = tubp->tty_rclp;
if (++i == tubp->tty_rclk)
i = 0;
bufp = &(*tubp->tty_rclbufs)[i];
if (*bufp == NULL || strlen(*bufp) < len + 1) {
if (*bufp) {
kfree(*bufp);
*bufp = NULL;
}
if ((*bufp = kmalloc(len + 1, GFP_ATOMIC)) == NULL)
return;
}
memcpy(*bufp, data, len);
(*bufp)[len] = '\0';
tubp->tty_rclp = i;
tty3270_rcl_sync(tubp);
}
static void
tty3270_rcl_sync(tub_t *tubp)
{
tubp->tty_rclb = -1;
}
/*
* IBM/3270 Driver -- Copyright (C) 2000, 2001 UTS Global LLC
*
* tubttyscl.c -- Linemode tty driver scroll-timing functions
*
*
*
*
*
* Author: Richard Hitt
*/
#include "tubio.h"
void tty3270_scl_settimer(tub_t *);
void tty3270_scl_resettimer(tub_t *);
static void tty3270_scl_timeout(unsigned long);
void
tty3270_scl_settimer(tub_t *tubp)
{
struct timer_list *tp = &tubp->tty_stimer;
if (tubp->flags & TUB_SCROLLTIMING)
return;
if (tubp->tty_scrolltime == 0)
return;
init_timer(tp);
tp->expires = jiffies + HZ * tubp->tty_scrolltime;
tp->data = (unsigned long)tubp;
tp->function = tty3270_scl_timeout;
add_timer(tp);
tubp->flags |= TUB_SCROLLTIMING;
}
void
tty3270_scl_resettimer(tub_t *tubp)
{
struct timer_list *tp = &tubp->tty_stimer;
if ((tubp->flags & TUB_SCROLLTIMING) == 0)
return;
del_timer(tp);
tubp->flags &= ~TUB_SCROLLTIMING;
}
static void
tty3270_scl_timeout(unsigned long data)
{
tub_t *tubp = (void *)data;
long flags;
TUBLOCK(tubp->irq, flags);
tubp->stat = TBS_RUNNING;
tty3270_scl_resettimer(tubp);
tubp->cmd = TBC_CLRUPDLOG;
tty3270_build(tubp);
TUBUNLOCK(tubp->irq, flags);
}
int
tty3270_scl_set(tub_t *tubp, char *buf, int count)
{
if (strncmp(buf, "scrolltime=", 11) == 0) {
tubp->tty_scrolltime =
simple_strtoul(buf + 11, 0, 0);
return count;
}
return 0;
}
int
tty3270_scl_init(tub_t *tubp)
{
extern int tubscrolltime;
tubp->tty_scrolltime = tubscrolltime;
if (tubp->tty_scrolltime < 0)
tubp->tty_scrolltime = DEFAULT_SCROLLTIME;
return 0;
}
void
tty3270_scl_fini(tub_t *tubp)
{
if ((tubp->flags & TUB_OPEN_STET) == 0)
tty3270_scl_resettimer(tubp);
}
/*
* IBM/3270 Driver -- Copyright (C) 2000 UTS Global LLC
*
* tubttysiz.c -- Linemode screen-size determiner
*
*
*
*
*
* Author: Richard Hitt
*/
#include "tubio.h"
static int tty3270_size_io(tub_t *tubp);
static void tty3270_size_int(tub_t *tubp, devstat_t *dsp);
static int tty3270_size_wait(tub_t *tubp, long *flags, int stat);
/*
* Structure representing Usable Area Query Reply Base
*/
typedef struct {
short l; /* Length of this structured field */
char sfid; /* 0x81 if Query Reply */
char qcode; /* 0x81 if Usable Area */
#define QCODE_UA 0x81
char flags0;
#define FLAGS0_ADDR 0x0f
#define FLAGS0_ADDR_12_14 1 /* 12/14-bit adrs ok */
#define FLAGS0_ADDR_12_14_16 3 /* 12/14/16-bit adrs ok */
char flags1;
short w; /* Width of usable area */
short h; /* Heigth of usavle area */
char units; /* 0x00:in; 0x01:mm */
int xr;
int yr;
char aw;
char ah;
short buffsz; /* Character buffer size, bytes */
char xmin;
char ymin;
char xmax;
char ymax;
} __attribute__ ((packed)) uab_t;
/*
* Structure representing Alternate Usable Area Self-Defining Parameter
*/
typedef struct {
char l; /* Length of this Self-Defining Parm */
char sdpid; /* 0x02 if Alternate Usable Area */
#define SDPID_AUA 0x02
char res;
char auaid; /* 0x01 is Id for the A U A */
short wauai; /* Width of AUAi */
short hauai; /* Height of AUAi */
char auaunits; /* 0x00:in, 0x01:mm */
int auaxr;
int auayr;
char awauai;
char ahauai;
} __attribute__ ((packed)) aua_t;
/*
* Structure representing one followed by the other
*/
typedef struct {
uab_t uab;
aua_t aua;
} __attribute__ ((packed)) ua_t;
/*
* Try to determine screen size using Read Partition (Query)
*/
int
tty3270_size(tub_t *tubp, long *flags)
{
char wbuf[7] = { 0x00, 0x07, 0x01, 0xff, 0x03, 0x00, 0x81 };
int rc = 0;
int count;
unsigned char *cp;
ua_t *uap;
char miniscreen[256];
char (*screen)[];
int screenl;
int geom_rows, geom_cols, fourteenbitadr;
void (*oldint)(struct tub_s *, devstat_t *);
if (tubp->flags & TUB_SIZED)
return 0;
fourteenbitadr = 0;
geom_rows = tubp->geom_rows;
geom_cols = tubp->geom_cols;
oldint = tubp->intv;
tubp->intv = tty3270_size_int;
if (tubp->cmd == TBC_CONOPEN) {
tubp->ttyccw.cmd_code = TC_EWRITEA;
cp = miniscreen;
*cp++ = TW_KR;
/* more? */
tubp->ttyccw.flags = CCW_FLAG_SLI;
tubp->ttyccw.cda = virt_to_phys(miniscreen);
tubp->ttyccw.count = (char *)cp - miniscreen;
rc = tty3270_size_io(tubp);
rc = tty3270_size_wait(tubp, flags, 0);
}
tubp->ttyccw.cmd_code = TC_WRITESF;
tubp->ttyccw.flags = CCW_FLAG_SLI;
tubp->ttyccw.cda = virt_to_phys(wbuf);
tubp->ttyccw.count = sizeof wbuf;
try_again:
rc = tty3270_size_io(tubp);
if (rc)
printk("tty3270_size_io returned %d\n", rc);
rc = tty3270_size_wait(tubp, flags, 0);
if (rc != 0) {
goto do_return;
}
/*
* Unit-Check Processing:
* Expect Command Reject or Intervention Required.
* For Command Reject assume old hdwe/software and
* set a default size of 80x24.
* For Intervention Required, wait for signal pending
* or Unsolicited Device End; if the latter, retry.
*/
if (tubp->dstat & DEV_STAT_UNIT_CHECK) {
if (tubp->sense.data[0] & SNS0_CMD_REJECT) {
goto use_diag210; /* perhaps it's tn3270 */
} else if (tubp->sense.data[0] & SNS0_INTERVENTION_REQ) {
if ((rc = tty3270_size_wait(tubp, flags,
DEV_STAT_DEV_END)))
goto do_return;
goto try_again;
} else {
printk("tty3270_size(): unkn sense %.2x\n",
tubp->sense.data[0]);
goto do_return;
}
}
if ((rc = tty3270_size_wait(tubp, flags, DEV_STAT_ATTENTION)))
goto do_return;
/* Set up a read ccw and issue it */
tubp->ttyccw.cmd_code = TC_READMOD;
tubp->ttyccw.flags = CCW_FLAG_SLI;
tubp->ttyccw.cda = virt_to_phys(miniscreen);
tubp->ttyccw.count = sizeof miniscreen;
tty3270_size_io(tubp);
rc = tty3270_size_wait(tubp, flags, 0);
if (rc != 0)
goto do_return;
count = sizeof miniscreen - tubp->cswl;
cp = miniscreen;
if (*cp++ != 0x88)
goto do_return;
uap = (void *)cp;
if (uap->uab.qcode != QCODE_UA)
goto do_return;
geom_rows = uap->uab.h;
geom_cols = uap->uab.w;
if ((uap->uab.flags0 & FLAGS0_ADDR) == FLAGS0_ADDR_12_14 ||
(uap->uab.flags0 & FLAGS0_ADDR) == FLAGS0_ADDR_12_14_16)
fourteenbitadr = 1;
if (uap->uab.l <= sizeof uap->uab)
goto do_return;
if (uap->aua.sdpid != SDPID_AUA) {
printk("AUA sdpid was 0x%.2x, expecting 0x%.2x\n",
uap->aua.sdpid, SDPID_AUA);
goto do_return;
}
geom_rows = uap->aua.hauai;
geom_cols = uap->aua.wauai;
goto do_return;
use_diag210:
if (MACHINE_IS_VM) {
diag210_t d210;
d210.vrdcdvno = tubp->devno;
d210.vrdclen = sizeof d210;
rc = diag210(&d210);
if (rc) {
printk("tty3270_size: diag210 for 0x%.4x "
"returned %d\n", tubp->devno, rc);
goto do_return;
}
switch(d210.vrdccrmd) {
case 2:
geom_rows = 24;
geom_cols = 80;
goto do_return;
case 3:
geom_rows = 32;
geom_cols = 80;
goto do_return;
case 4:
geom_rows = 43;
geom_cols = 80;
goto do_return;
case 5:
geom_rows = 27;
geom_cols = 132;
goto do_return;
default:
printk("vrdccrmd is 0x%.8x\n", d210.vrdccrmd);
}
}
do_return:
if (geom_rows == 0) {
geom_rows = _GEOM_ROWS;
geom_cols = _GEOM_COLS;
}
tubp->tubiocb.pf_cnt = 24;
tubp->tubiocb.re_cnt = 20;
tubp->tubiocb.map = 0;
screenl = geom_rows * geom_cols + 100;
screen = (char (*)[])kmalloc(screenl, GFP_KERNEL);
if (screen == NULL) {
printk("ttyscreen size %d unavailable\n", screenl);
} else {
if (tubp->ttyscreen)
kfree(tubp->ttyscreen);
tubp->tubiocb.line_cnt = tubp->geom_rows = geom_rows;
tubp->tubiocb.col_cnt = tubp->geom_cols = geom_cols;
tubp->tty_14bitadr = fourteenbitadr;
tubp->ttyscreen = screen;
tubp->ttyscreenl = screenl;
if (geom_rows == 24 && geom_cols == 80)
tubp->tubiocb.model = 2;
else if (geom_rows == 32 && geom_cols == 80)
tubp->tubiocb.model = 3;
else if (geom_rows == 43 && geom_cols == 80)
tubp->tubiocb.model = 4;
else if (geom_rows == 27 && geom_cols == 132)
tubp->tubiocb.model = 5;
else
tubp->tubiocb.model = 0;
tubp->flags |= TUB_SIZED;
}
if (rc == 0 && tubp->ttyscreen == NULL)
rc = -ENOMEM;
tubp->intv = oldint;
return rc;
}
static int
tty3270_size_io(tub_t *tubp)
{
tubp->flags |= TUB_WORKING;
tubp->dstat = 0;
return do_IO(tubp->irq, &tubp->ttyccw, tubp->irq, 0, 0);
}
static void
tty3270_size_int(tub_t *tubp, devstat_t *dsp)
{
#define DEV_NOT_WORKING \
(DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_CHECK)
tubp->dstat = dsp->dstat;
if (dsp->dstat & DEV_STAT_CHN_END)
tubp->cswl = dsp->rescnt;
if (dsp->dstat & DEV_NOT_WORKING)
tubp->flags &= ~TUB_WORKING;
if (dsp->dstat & DEV_STAT_UNIT_CHECK)
tubp->sense = dsp->ii.sense;
wake_up_interruptible(&tubp->waitq);
}
/*
* Wait for something. If the third arg is zero, wait until
* tty3270_size_int() turns off TUB_WORKING. If the third arg
* is not zero, it is a device-status bit; wait until dstat
* has the bit turned on. Never wait if signal is pending.
* Return 0 unless signal pending, in which case -ERESTARTSYS.
*/
static int
tty3270_size_wait(tub_t *tubp, long *flags, int stat)
{
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&tubp->waitq, &wait);
while (!signal_pending(current) &&
(stat? (tubp->dstat & stat) == 0:
(tubp->flags & TUB_WORKING) != 0)) {
#warning FIXME: [kj] use set_current_state instead of current->state=
current->state = TASK_INTERRUPTIBLE;
TUBUNLOCK(tubp->irq, *flags);
schedule();
#warning FIXME: [kj] use set_current_state instead of current->state=
current->state = TASK_RUNNING;
TUBLOCK(tubp->irq, *flags);
}
remove_wait_queue(&tubp->waitq, &wait);
return signal_pending(current)? -ERESTARTSYS: 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment