[PATCH] s390: 3270 fullscreen view

      Martin Schwidefsky <schwidefsky@de.ibm.com>

Fix fullscreen view of the 3270 device driver.

Signed-off-by: Richard Hitt <rbh00@utsglobal.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/drivers/s390/char/fs3270.c b/drivers/s390/char/fs3270.c
index 60afcdc..735a7fc 100644
--- a/drivers/s390/char/fs3270.c
+++ b/drivers/s390/char/fs3270.c
@@ -33,8 +33,11 @@
 	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. */
+	int active;			/* Fullscreen view is active. */
+	struct raw3270_request *init;	/* single init request. */
+	wait_queue_head_t wait;		/* Init & attention wait queue. */
+	struct idal_buffer *rdbuf;	/* full-screen-deactivate buffer */
+	size_t rdbuf_size;		/* size of data returned by RDBUF */
 };
 
 static void
@@ -43,58 +46,172 @@
 	wake_up((wait_queue_head_t *) data);
 }
 
+static inline int
+fs3270_working(struct fs3270 *fp)
+{
+	/*
+	 * The fullscreen view is in working order if the view
+	 * has been activated AND the initial request is finished.
+	 */
+	return fp->active && raw3270_request_final(fp->init);
+}
+
 static int
 fs3270_do_io(struct raw3270_view *view, struct raw3270_request *rq)
 {
-	wait_queue_head_t wq;
+	struct fs3270 *fp;
 	int rc;
 
-	init_waitqueue_head(&wq);
+	fp = (struct fs3270 *) view;
 	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;
-}
+	rq->callback_data = &fp->wait;
 
-static void
-fs3270_reset_callback(struct raw3270_request *rq, void *data)
-{
-	raw3270_request_reset(rq);
+	do {
+		if (!fs3270_working(fp)) {
+			/* Fullscreen view isn't ready yet. */
+			rc = wait_event_interruptible(fp->wait,
+						      fs3270_working(fp));
+			if (rc != 0)
+				break;
+		}
+		rc = raw3270_start(view, rq);
+		if (rc == 0) {
+			/* Started sucessfully. Now wait for completion. */
+			wait_event(fp->wait, raw3270_request_final(rq));
+		}
+	} while (rc == -EACCES);
+	return rc;
 }
 
 /*
  * Switch to the fullscreen view.
  */
+static void
+fs3270_reset_callback(struct raw3270_request *rq, void *data)
+{
+	struct fs3270 *fp;
+
+	fp = (struct fs3270 *) rq->view;
+	raw3270_request_reset(rq);
+	wake_up(&fp->wait);
+}
+
+static void
+fs3270_restore_callback(struct raw3270_request *rq, void *data)
+{
+	struct fs3270 *fp;
+
+	fp = (struct fs3270 *) rq->view;
+	if (rq->rc != 0 || rq->rescnt != 0) {
+		if (fp->fs_pid)
+			kill_proc(fp->fs_pid, SIGHUP, 1);
+	}
+	fp->rdbuf_size = 0;
+	raw3270_request_reset(rq);
+	wake_up(&fp->wait);
+}
+
 static int
 fs3270_activate(struct raw3270_view *view)
 {
 	struct fs3270 *fp;
+	char *cp;
+	int rc;
 
 	fp = (struct fs3270 *) view;
-	raw3270_request_set_cmd(fp->clear, TC_EWRITEA);
-	fp->clear->callback = fs3270_reset_callback;
-	return raw3270_start(view, fp->clear);
+
+	/* If an old init command is still running just return. */
+	if (!raw3270_request_final(fp->init))
+		return 0;
+
+	if (fp->rdbuf_size == 0) {
+		/* No saved buffer. Just clear the screen. */
+		raw3270_request_set_cmd(fp->init, TC_EWRITEA);
+		fp->init->callback = fs3270_reset_callback;
+	} else {
+		/* Restore fullscreen buffer saved by fs3270_deactivate. */
+		raw3270_request_set_cmd(fp->init, TC_EWRITEA);
+		raw3270_request_set_idal(fp->init, fp->rdbuf);
+		fp->init->ccw.count = fp->rdbuf_size;
+		cp = fp->rdbuf->data[0];
+		cp[0] = TW_KR;
+		cp[1] = TO_SBA;
+		cp[2] = cp[6];
+		cp[3] = cp[7];
+		cp[4] = TO_IC;
+		cp[5] = TO_SBA;
+		cp[6] = 0x40;
+		cp[7] = 0x40;
+		fp->init->rescnt = 0;
+		fp->init->callback = fs3270_restore_callback;
+	}
+	rc = fp->init->rc = raw3270_start_locked(view, fp->init);
+	if (rc)
+		fp->init->callback(fp->init, NULL);
+	else
+		fp->active = 1;
+	return rc;
 }
 
 /*
  * Shutdown fullscreen view.
  */
 static void
+fs3270_save_callback(struct raw3270_request *rq, void *data)
+{
+	struct fs3270 *fp;
+
+	fp = (struct fs3270 *) rq->view;
+
+	/* Correct idal buffer element 0 address. */
+	fp->rdbuf->data[0] -= 5;
+	fp->rdbuf->size += 5;
+
+	/*
+	 * If the rdbuf command failed or the idal buffer is
+	 * to small for the amount of data returned by the
+	 * rdbuf command, then we have no choice but to send
+	 * a SIGHUP to the application.
+	 */
+	if (rq->rc != 0 || rq->rescnt == 0) {
+		if (fp->fs_pid)
+			kill_proc(fp->fs_pid, SIGHUP, 1);
+		fp->rdbuf_size = 0;
+	} else
+		fp->rdbuf_size = fp->rdbuf->size - rq->rescnt;
+	raw3270_request_reset(rq);
+	wake_up(&fp->wait);
+}
+
+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;
+	fp->active = 0;
+
+	/* If an old init command is still running just return. */
+	if (!raw3270_request_final(fp->init))
+		return;
+
+	/* Prepare read-buffer request. */
+	raw3270_request_set_cmd(fp->init, TC_RDBUF);
+	/*
+	 * Hackish: skip first 5 bytes of the idal buffer to make
+	 * room for the TW_KR/TO_SBA/<address>/<address>/TO_IC sequence
+	 * in the activation command.
+	 */
+	fp->rdbuf->data[0] += 5;
+	fp->rdbuf->size -= 5;
+	raw3270_request_set_idal(fp->init, fp->rdbuf);
+	fp->init->rescnt = 0;
+	fp->init->callback = fs3270_save_callback;
+
+	/* Start I/O to read in the 3270 buffer. */
+	fp->init->rc = raw3270_start_locked(view, fp->init);
+	if (fp->init->rc)
+		fp->init->callback(fp->init, NULL);
 }
 
 static int
@@ -103,7 +220,7 @@
 	/* Handle ATTN. Set indication and wake waiters for attention. */
 	if (irb->scsw.dstat & DEV_STAT_ATTENTION) {
 		fp->attention = 1;
-		wake_up(&fp->attn_wait);
+		wake_up(&fp->wait);
 	}
 
 	if (rq) {
@@ -125,7 +242,7 @@
 	struct fs3270 *fp;
 	struct raw3270_request *rq;
 	struct idal_buffer *ib;
-	int rc;
+	ssize_t rc;
 	
 	if (count == 0 || count > 65535)
 		return -EINVAL;
@@ -133,7 +250,7 @@
 	if (!fp)
 		return -ENODEV;
 	ib = idal_buffer_alloc(count, 0);
-	if (!ib)
+	if (IS_ERR(ib))
 		return -ENOMEM;
 	rq = raw3270_request_alloc(0);
 	if (!IS_ERR(rq)) {
@@ -141,10 +258,19 @@
 			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;
+		rc = wait_event_interruptible(fp->wait, fp->attention);
+		fp->attention = 0;
+		if (rc == 0) {
+			rc = fs3270_do_io(&fp->view, rq);
+			if (rc == 0) {
+				count -= rq->rescnt;
+				if (idal_buffer_to_user(ib, data, count) != 0)
+					rc = -EFAULT;
+				else
+					rc = count;
+
+			}
+		}
 		raw3270_request_free(rq);
 	} else
 		rc = PTR_ERR(rq);
@@ -162,13 +288,13 @@
 	struct raw3270_request *rq;
 	struct idal_buffer *ib;
 	int write_command;
-	int rc;
+	ssize_t rc;
 
 	fp = filp->private_data;
 	if (!fp)
 		return -ENODEV;
 	ib = idal_buffer_alloc(count, 0);
-	if (!ib)
+	if (IS_ERR(ib))
 		return -ENOMEM;
 	rq = raw3270_request_alloc(0);
 	if (!IS_ERR(rq)) {
@@ -179,6 +305,8 @@
 			raw3270_request_set_cmd(rq, write_command);
 			raw3270_request_set_idal(rq, ib);
 			rc = fs3270_do_io(&fp->view, rq);
+			if (rc == 0)
+				rc = count - rq->rescnt;
 		} else
 			rc = -EFAULT;
 		raw3270_request_free(rq);
@@ -232,7 +360,7 @@
 }
 
 /*
- * Allocate tty3270 structure.
+ * Allocate fs3270 structure.
  */
 static struct fs3270 *
 fs3270_alloc_view(void)
@@ -243,8 +371,8 @@
 	if (!fp)
 		return ERR_PTR(-ENOMEM);
 	memset(fp, 0, sizeof(struct fs3270));
-	fp->clear = raw3270_request_alloc(0);
-	if (!IS_ERR(fp->clear)) {
+	fp->init = raw3270_request_alloc(0);
+	if (IS_ERR(fp->init)) {
 		kfree(fp);
 		return ERR_PTR(-ENOMEM);
 	}
@@ -252,12 +380,17 @@
 }
 
 /*
- * Free tty3270 structure.
+ * Free fs3270 structure.
  */
 static void
 fs3270_free_view(struct raw3270_view *view)
 {
-	raw3270_request_free(((struct fs3270 *) view)->clear);
+	struct fs3270 *fp;
+
+	fp = (struct fs3270 *) view;
+	if (fp->rdbuf)
+		idal_buffer_free(fp->rdbuf);
+	raw3270_request_free(((struct fs3270 *) view)->init);
 	kfree(view);
 }
 
@@ -285,11 +418,20 @@
 fs3270_open(struct inode *inode, struct file *filp)
 {
 	struct fs3270 *fp;
+	struct idal_buffer *ib;
 	int minor, rc;
 
 	if (imajor(filp->f_dentry->d_inode) != IBM_FS3270_MAJOR)
 		return -ENODEV;
 	minor = iminor(filp->f_dentry->d_inode);
+	/* Check for minor 0 multiplexer. */
+	if (minor == 0) {
+		if (!current->signal->tty)
+			return -ENODEV;
+		if (current->signal->tty->driver->major != IBM_TTY3270_MAJOR)
+			return -ENODEV;
+		minor = current->signal->tty->index + RAW3270_FIRSTMINOR;
+	}
 	/* Check if some other program is already using fullscreen mode. */
 	fp = (struct fs3270 *) raw3270_find_view(&fs3270_fn, minor);
 	if (!IS_ERR(fp)) {
@@ -301,7 +443,7 @@
 	if (IS_ERR(fp))
 		return PTR_ERR(fp);
 
-	init_waitqueue_head(&fp->attn_wait);
+	init_waitqueue_head(&fp->wait);
 	fp->fs_pid = current->pid;
 	rc = raw3270_add_view(&fp->view, &fs3270_fn, minor);
 	if (rc) {
@@ -309,8 +451,18 @@
 		return rc;
 	}
 
+	/* Allocate idal-buffer. */
+	ib = idal_buffer_alloc(2*fp->view.rows*fp->view.cols + 5, 0);
+	if (IS_ERR(ib)) {
+		raw3270_put_view(&fp->view);
+		raw3270_del_view(&fp->view);
+		return PTR_ERR(fp);
+	}
+	fp->rdbuf = ib;
+
 	rc = raw3270_activate_view(&fp->view);
 	if (rc) {
+		raw3270_put_view(&fp->view);
 		raw3270_del_view(&fp->view);
 		return rc;
 	}
@@ -329,8 +481,12 @@
 
 	fp = filp->private_data;
 	filp->private_data = 0;
-	if (fp)
+	if (fp) {
+		fp->fs_pid = 0;
+		raw3270_reset(&fp->view);
+		raw3270_put_view(&fp->view);
 		raw3270_del_view(&fp->view);
+	}
 	return 0;
 }