/* -*- linux-c -*-
 *
 *  drivers/char/viocons.c
 *
 *  iSeries Virtual Terminal
 *
 *  Authors: Dave Boutcher <boutcher@us.ibm.com>
 *           Ryan Arnold <ryanarn@us.ibm.com>
 *           Colin Devilbiss <devilbis@us.ibm.com>
 *           Stephen Rothwell <sfr@au1.ibm.com>
 *
 * (C) Copyright 2000, 2001, 2002, 2003, 2004 IBM Corporation
 *
 * This program is free software;  you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) anyu later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/errno.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/console.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <asm/ioctls.h>
#include <linux/kd.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/sysrq.h>

#include <asm/firmware.h>
#include <asm/iseries/vio.h>
#include <asm/iseries/hv_lp_event.h>
#include <asm/iseries/hv_call_event.h>
#include <asm/iseries/hv_lp_config.h>
#include <asm/iseries/hv_call.h>

#ifdef CONFIG_VT
#error You must turn off CONFIG_VT to use CONFIG_VIOCONS
#endif

#define VIOTTY_MAGIC (0x0DCB)
#define VTTY_PORTS 10

#define VIOCONS_KERN_WARN	KERN_WARNING "viocons: "
#define VIOCONS_KERN_INFO	KERN_INFO "viocons: "

static DEFINE_SPINLOCK(consolelock);
static DEFINE_SPINLOCK(consoleloglock);

static int vio_sysrq_pressed;

#define VIOCHAR_NUM_BUF		16

/*
 * Our port information.  We store a pointer to one entry in the
 * tty_driver_data
 */
static struct port_info {
	int magic;
	struct tty_struct *tty;
	HvLpIndex lp;
	u8 vcons;
	u64 seq;	/* sequence number of last HV send */
	u64 ack;	/* last ack from HV */
/*
 * When we get writes faster than we can send it to the partition,
 * buffer the data here. Note that used is a bit map of used buffers.
 * It had better have enough bits to hold VIOCHAR_NUM_BUF the bitops assume
 * it is a multiple of unsigned long
 */
	unsigned long used;
	u8 *buffer[VIOCHAR_NUM_BUF];
	int bufferBytes[VIOCHAR_NUM_BUF];
	int curbuf;
	int bufferOverflow;
	int overflowMessage;
} port_info[VTTY_PORTS];

#define viochar_is_console(pi)	((pi) == &port_info[0])
#define viochar_port(pi)	((pi) - &port_info[0])

static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp);

static struct tty_driver *viotty_driver;

static void hvlog(char *fmt, ...)
{
	int i;
	unsigned long flags;
	va_list args;
	static char buf[256];

	spin_lock_irqsave(&consoleloglock, flags);
	va_start(args, fmt);
	i = vscnprintf(buf, sizeof(buf) - 1, fmt, args);
	va_end(args);
	buf[i++] = '\r';
	HvCall_writeLogBuffer(buf, i);
	spin_unlock_irqrestore(&consoleloglock, flags);
}

static void hvlogOutput(const char *buf, int count)
{
	unsigned long flags;
	int begin;
	int index;
	static const char cr = '\r';

	begin = 0;
	spin_lock_irqsave(&consoleloglock, flags);
	for (index = 0; index < count; index++) {
		if (buf[index] == '\n') {
			/*
			 * Start right after the last '\n' or at the zeroth
			 * array position and output the number of characters
			 * including the newline.
			 */
			HvCall_writeLogBuffer(&buf[begin], index - begin + 1);
			begin = index + 1;
			HvCall_writeLogBuffer(&cr, 1);
		}
	}
	if ((index - begin) > 0)
		HvCall_writeLogBuffer(&buf[begin], index - begin);
	spin_unlock_irqrestore(&consoleloglock, flags);
}

/*
 * Make sure we're pointing to a valid port_info structure.  Shamelessly
 * plagerized from serial.c
 */
static inline int viotty_paranoia_check(struct port_info *pi,
					char *name, const char *routine)
{
	static const char *bad_pi_addr = VIOCONS_KERN_WARN
		"warning: bad address for port_info struct (%s) in %s\n";
	static const char *badmagic = VIOCONS_KERN_WARN
		"warning: bad magic number for port_info struct (%s) in %s\n";

	if ((pi < &port_info[0]) || (viochar_port(pi) > VTTY_PORTS)) {
		printk(bad_pi_addr, name, routine);
		return 1;
	}
	if (pi->magic != VIOTTY_MAGIC) {
		printk(badmagic, name, routine);
		return 1;
	}
	return 0;
}

/*
 * Add data to our pending-send buffers.  
 *
 * NOTE: Don't use printk in here because it gets nastily recursive.
 * hvlog can be used to log to the hypervisor buffer
 */
static int buffer_add(struct port_info *pi, const char *buf, size_t len)
{
	size_t bleft;
	size_t curlen;
	const char *curbuf;
	int nextbuf;

	curbuf = buf;
	bleft = len;
	while (bleft > 0) {
		/*
		 * If there is no space left in the current buffer, we have
		 * filled everything up, so return.  If we filled the previous
		 * buffer we would already have moved to the next one.
		 */
		if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) {
			hvlog ("\n\rviocons: No overflow buffer available for memcpy().\n");
			pi->bufferOverflow++;
			pi->overflowMessage = 1;
			break;
		}

		/*
		 * Turn on the "used" bit for this buffer.  If it's already on,
		 * that's fine.
		 */
		set_bit(pi->curbuf, &pi->used);

		/*
		 * See if this buffer has been allocated.  If not, allocate it.
		 */
		if (pi->buffer[pi->curbuf] == NULL) {
			pi->buffer[pi->curbuf] =
			    kmalloc(VIOCHAR_MAX_DATA, GFP_ATOMIC);
			if (pi->buffer[pi->curbuf] == NULL) {
				hvlog("\n\rviocons: kmalloc failed allocating spaces for buffer %d.",
					pi->curbuf);
				break;
			}
		}

		/* Figure out how much we can copy into this buffer. */
		if (bleft < (VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf]))
			curlen = bleft;
		else
			curlen = VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf];

		/* Copy the data into the buffer. */
		memcpy(pi->buffer[pi->curbuf] + pi->bufferBytes[pi->curbuf],
				curbuf, curlen);

		pi->bufferBytes[pi->curbuf] += curlen;
		curbuf += curlen;
		bleft -= curlen;

		/*
		 * Now see if we've filled this buffer.  If not then
		 * we'll try to use it again later.  If we've filled it
		 * up then we'll advance the curbuf to the next in the
		 * circular queue.
		 */
		if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) {
			nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF;
			/*
			 * Move to the next buffer if it hasn't been used yet
			 */
			if (test_bit(nextbuf, &pi->used) == 0)
				pi->curbuf = nextbuf;
		}
	}
	return len - bleft;
}

/*
 * Send pending data
 *
 * NOTE: Don't use printk in here because it gets nastily recursive.
 * hvlog can be used to log to the hypervisor buffer
 */
static void send_buffers(struct port_info *pi)
{
	HvLpEvent_Rc hvrc;
	int nextbuf;
	struct viocharlpevent *viochar;
	unsigned long flags;

	spin_lock_irqsave(&consolelock, flags);

	viochar = (struct viocharlpevent *)
	    vio_get_event_buffer(viomajorsubtype_chario);

	/* Make sure we got a buffer */
	if (viochar == NULL) {
		hvlog("\n\rviocons: Can't get viochar buffer in sendBuffers().");
		spin_unlock_irqrestore(&consolelock, flags);
		return;
	}

	if (pi->used == 0) {
		hvlog("\n\rviocons: in sendbuffers(), but no buffers used.\n");
		vio_free_event_buffer(viomajorsubtype_chario, viochar);
		spin_unlock_irqrestore(&consolelock, flags);
		return;
	}

	/*
	 * curbuf points to the buffer we're filling.  We want to
	 * start sending AFTER this one.  
	 */
	nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF;

	/*
	 * Loop until we find a buffer with the used bit on
	 */
	while (test_bit(nextbuf, &pi->used) == 0)
		nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF;

	initDataEvent(viochar, pi->lp);

	/*
	 * While we have buffers with data, and our send window
	 * is open, send them
	 */
	while ((test_bit(nextbuf, &pi->used)) &&
	       ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) {
		viochar->len = pi->bufferBytes[nextbuf];
		viochar->event.xCorrelationToken = pi->seq++;
		viochar->event.xSizeMinus1 =
			offsetof(struct viocharlpevent, data) + viochar->len;

		memcpy(viochar->data, pi->buffer[nextbuf], viochar->len);

		hvrc = HvCallEvent_signalLpEvent(&viochar->event);
		if (hvrc) {
			/*
			 * MUST unlock the spinlock before doing a printk
			 */
			vio_free_event_buffer(viomajorsubtype_chario, viochar);
			spin_unlock_irqrestore(&consolelock, flags);

			printk(VIOCONS_KERN_WARN
			       "error sending event! return code %d\n",
			       (int)hvrc);
			return;
		}

		/*
		 * clear the used bit, zero the number of bytes in
		 * this buffer, and move to the next buffer
		 */
		clear_bit(nextbuf, &pi->used);
		pi->bufferBytes[nextbuf] = 0;
		nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF;
	}

	/*
	 * If we have emptied all the buffers, start at 0 again.
	 * this will re-use any allocated buffers
	 */
	if (pi->used == 0) {
		pi->curbuf = 0;

		if (pi->overflowMessage)
			pi->overflowMessage = 0;

		if (pi->tty) {
			tty_wakeup(pi->tty);
		}
	}

	vio_free_event_buffer(viomajorsubtype_chario, viochar);
	spin_unlock_irqrestore(&consolelock, flags);
}

/*
 * Our internal writer.  Gets called both from the console device and
 * the tty device.  the tty pointer will be NULL if called from the console.
 * Return total number of bytes "written".
 *
 * NOTE: Don't use printk in here because it gets nastily recursive.  hvlog
 * can be used to log to the hypervisor buffer
 */
static int internal_write(struct port_info *pi, const char *buf, size_t len)
{
	HvLpEvent_Rc hvrc;
	size_t bleft;
	size_t curlen;
	const char *curbuf;
	unsigned long flags;
	struct viocharlpevent *viochar;

	/*
	 * Write to the hvlog of inbound data are now done prior to
	 * calling internal_write() since internal_write() is only called in
	 * the event that an lp event path is active, which isn't the case for
	 * logging attempts prior to console initialization.
	 *
	 * If there is already data queued for this port, send it prior to
	 * attempting to send any new data.
	 */
	if (pi->used)
		send_buffers(pi);

	spin_lock_irqsave(&consolelock, flags);

	viochar = vio_get_event_buffer(viomajorsubtype_chario);
	if (viochar == NULL) {
		spin_unlock_irqrestore(&consolelock, flags);
		hvlog("\n\rviocons: Can't get vio buffer in internal_write().");
		return -EAGAIN;
	}
	initDataEvent(viochar, pi->lp);

	curbuf = buf;
	bleft = len;

	while ((bleft > 0) && (pi->used == 0) &&
	       ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) {
		if (bleft > VIOCHAR_MAX_DATA)
			curlen = VIOCHAR_MAX_DATA;
		else
			curlen = bleft;

		viochar->event.xCorrelationToken = pi->seq++;
		memcpy(viochar->data, curbuf, curlen);
		viochar->len = curlen;
		viochar->event.xSizeMinus1 =
		    offsetof(struct viocharlpevent, data) + curlen;

		hvrc = HvCallEvent_signalLpEvent(&viochar->event);
		if (hvrc) {
			hvlog("viocons: error sending event! %d\n", (int)hvrc);
			goto out;
		}
		curbuf += curlen;
		bleft -= curlen;
	}

	/* If we didn't send it all, buffer as much of it as we can. */
	if (bleft > 0)
		bleft -= buffer_add(pi, curbuf, bleft);
out:
	vio_free_event_buffer(viomajorsubtype_chario, viochar);
	spin_unlock_irqrestore(&consolelock, flags);
	return len - bleft;
}

static struct port_info *get_port_data(struct tty_struct *tty)
{
	unsigned long flags;
	struct port_info *pi;

	spin_lock_irqsave(&consolelock, flags);
	if (tty) {
		pi = (struct port_info *)tty->driver_data;
		if (!pi || viotty_paranoia_check(pi, tty->name,
					     "get_port_data")) {
			pi = NULL;
		}
	} else
		/*
		 * If this is the console device, use the lp from
		 * the first port entry
		 */
		pi = &port_info[0];
	spin_unlock_irqrestore(&consolelock, flags);
	return pi;
}

/*
 * Initialize the common fields in a charLpEvent
 */
static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp)
{
	struct HvLpEvent *hev = &viochar->event;

	memset(viochar, 0, sizeof(struct viocharlpevent));

	hev->flags = HV_LP_EVENT_VALID | HV_LP_EVENT_DEFERRED_ACK |
		HV_LP_EVENT_INT;
	hev->xType = HvLpEvent_Type_VirtualIo;
	hev->xSubtype = viomajorsubtype_chario | viochardata;
	hev->xSourceLp = HvLpConfig_getLpIndex();
	hev->xTargetLp = lp;
	hev->xSizeMinus1 = sizeof(struct viocharlpevent);
	hev->xSourceInstanceId = viopath_sourceinst(lp);
	hev->xTargetInstanceId = viopath_targetinst(lp);
}

/*
 * early console device write
 */
static void viocons_write_early(struct console *co, const char *s, unsigned count)
{
	hvlogOutput(s, count);
}

/*
 * console device write
 */
static void viocons_write(struct console *co, const char *s, unsigned count)
{
	int index;
	int begin;
	struct port_info *pi;

	static const char cr = '\r';

	/*
	 * Check port data first because the target LP might be valid but
	 * simply not active, in which case we want to hvlog the output.
	 */
	pi = get_port_data(NULL);
	if (pi == NULL) {
		hvlog("\n\rviocons_write: unable to get port data.");
		return;
	}

	hvlogOutput(s, count);

	if (!viopath_isactive(pi->lp))
		return;

	/* 
	 * Any newline character found will cause a
	 * carriage return character to be emitted as well. 
	 */
	begin = 0;
	for (index = 0; index < count; index++) {
		if (s[index] == '\n') {
			/* 
			 * Newline found. Print everything up to and 
			 * including the newline
			 */
			internal_write(pi, &s[begin], index - begin + 1);
			begin = index + 1;
			/* Emit a carriage return as well */
			internal_write(pi, &cr, 1);
		}
	}

	/* If any characters left to write, write them now */
	if ((index - begin) > 0)
		internal_write(pi, &s[begin], index - begin);
}

/*
 * Work out the device associate with this console
 */
static struct tty_driver *viocons_device(struct console *c, int *index)
{
	*index = c->index;
	return viotty_driver;
}

/*
 * console device I/O methods
 */
static struct console viocons_early = {
	.name = "viocons",
	.write = viocons_write_early,
	.flags = CON_PRINTBUFFER,
	.index = -1,
};

static struct console viocons = {
	.name = "viocons",
	.write = viocons_write,
	.device = viocons_device,
	.flags = CON_PRINTBUFFER,
	.index = -1,
};

/*
 * TTY Open method
 */
static int viotty_open(struct tty_struct *tty, struct file *filp)
{
	int port;
	unsigned long flags;
	struct port_info *pi;

	port = tty->index;

	if ((port < 0) || (port >= VTTY_PORTS))
		return -ENODEV;

	spin_lock_irqsave(&consolelock, flags);

	pi = &port_info[port];
	/* If some other TTY is already connected here, reject the open */
	if ((pi->tty) && (pi->tty != tty)) {
		spin_unlock_irqrestore(&consolelock, flags);
		printk(VIOCONS_KERN_WARN
		       "attempt to open device twice from different ttys\n");
		return -EBUSY;
	}
	tty->driver_data = pi;
	pi->tty = tty;
	spin_unlock_irqrestore(&consolelock, flags);

	return 0;
}

/*
 * TTY Close method
 */
static void viotty_close(struct tty_struct *tty, struct file *filp)
{
	unsigned long flags;
	struct port_info *pi;

	spin_lock_irqsave(&consolelock, flags);
	pi = (struct port_info *)tty->driver_data;

	if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_close")) {
		spin_unlock_irqrestore(&consolelock, flags);
		return;
	}
	if (tty->count == 1)
		pi->tty = NULL;
	spin_unlock_irqrestore(&consolelock, flags);
}

/*
 * TTY Write method
 */
static int viotty_write(struct tty_struct *tty, const unsigned char *buf,
		int count)
{
	struct port_info *pi;

	pi = get_port_data(tty);
	if (pi == NULL) {
		hvlog("\n\rviotty_write: no port data.");
		return -ENODEV;
	}

	if (viochar_is_console(pi))
		hvlogOutput(buf, count);

	/*
	 * If the path to this LP is closed, don't bother doing anything more.
	 * just dump the data on the floor and return count.  For some reason
	 * some user level programs will attempt to probe available tty's and
	 * they'll attempt a viotty_write on an invalid port which maps to an
	 * invalid target lp.  If this is the case then ignore the
	 * viotty_write call and, since the viopath isn't active to this
	 * partition, return count.
	 */
	if (!viopath_isactive(pi->lp))
		return count;

	return internal_write(pi, buf, count);
}

/*
 * TTY put_char method
 */
static void viotty_put_char(struct tty_struct *tty, unsigned char ch)
{
	struct port_info *pi;

	pi = get_port_data(tty);
	if (pi == NULL)
		return;

	/* This will append '\r' as well if the char is '\n' */
	if (viochar_is_console(pi))
		hvlogOutput(&ch, 1);

	if (viopath_isactive(pi->lp))
		internal_write(pi, &ch, 1);
}

/*
 * TTY write_room method
 */
static int viotty_write_room(struct tty_struct *tty)
{
	int i;
	int room = 0;
	struct port_info *pi;
	unsigned long flags;

	spin_lock_irqsave(&consolelock, flags);
	pi = (struct port_info *)tty->driver_data;
	if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_write_room")) {
		spin_unlock_irqrestore(&consolelock, flags);
		return 0;
	}

	/* If no buffers are used, return the max size. */
	if (pi->used == 0) {
		spin_unlock_irqrestore(&consolelock, flags);
		return VIOCHAR_MAX_DATA * VIOCHAR_NUM_BUF;
	}

	/*
	 * We retain the spinlock because we want to get an accurate
	 * count and it can change on us between each operation if we
	 * don't hold the spinlock.
	 */
	for (i = 0; ((i < VIOCHAR_NUM_BUF) && (room < VIOCHAR_MAX_DATA)); i++)
		room += (VIOCHAR_MAX_DATA - pi->bufferBytes[i]);
	spin_unlock_irqrestore(&consolelock, flags);

	if (room > VIOCHAR_MAX_DATA)
		room = VIOCHAR_MAX_DATA;
	return room;
}

/*
 * TTY chars_in_buffer method
 */
static int viotty_chars_in_buffer(struct tty_struct *tty)
{
	return 0;
}

static int viotty_ioctl(struct tty_struct *tty, struct file *file,
			unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	/*
	 * the ioctls below read/set the flags usually shown in the leds
	 * don't use them - they will go away without warning
	 */
	case KDGETLED:
	case KDGKBLED:
		return put_user(0, (char *)arg);

	case KDSKBLED:
		return 0;
	}

	return n_tty_ioctl(tty, file, cmd, arg);
}

/*
 * Handle an open charLpEvent.  Could be either interrupt or ack
 */
static void vioHandleOpenEvent(struct HvLpEvent *event)
{
	unsigned long flags;
	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
	u8 port = cevent->virtual_device;
	struct port_info *pi;
	int reject = 0;

	if (hvlpevent_is_ack(event)) {
		if (port >= VTTY_PORTS)
			return;

		spin_lock_irqsave(&consolelock, flags);
		/* Got the lock, don't cause console output */

		pi = &port_info[port];
		if (event->xRc == HvLpEvent_Rc_Good) {
			pi->seq = pi->ack = 0;
			/*
			 * This line allows connections from the primary
			 * partition but once one is connected from the
			 * primary partition nothing short of a reboot
			 * of linux will allow access from the hosting
			 * partition again without a required iSeries fix.
			 */
			pi->lp = event->xTargetLp;
		}

		spin_unlock_irqrestore(&consolelock, flags);
		if (event->xRc != HvLpEvent_Rc_Good)
			printk(VIOCONS_KERN_WARN
			       "handle_open_event: event->xRc == (%d).\n",
			       event->xRc);

		if (event->xCorrelationToken != 0) {
			atomic_t *aptr= (atomic_t *)event->xCorrelationToken;
			atomic_set(aptr, 1);
		} else
			printk(VIOCONS_KERN_WARN
			       "weird...got open ack without atomic\n");
		return;
	}

	/* This had better require an ack, otherwise complain */
	if (!hvlpevent_need_ack(event)) {
		printk(VIOCONS_KERN_WARN "viocharopen without ack bit!\n");
		return;
	}

	spin_lock_irqsave(&consolelock, flags);
	/* Got the lock, don't cause console output */

	/* Make sure this is a good virtual tty */
	if (port >= VTTY_PORTS) {
		event->xRc = HvLpEvent_Rc_SubtypeError;
		cevent->subtype_result_code = viorc_openRejected;
		/*
		 * Flag state here since we can't printk while holding
		 * a spinlock.
		 */
		reject = 1;
	} else {
		pi = &port_info[port];
		if ((pi->lp != HvLpIndexInvalid) &&
				(pi->lp != event->xSourceLp)) {
			/*
			 * If this is tty is already connected to a different
			 * partition, fail.
			 */
			event->xRc = HvLpEvent_Rc_SubtypeError;
			cevent->subtype_result_code = viorc_openRejected;
			reject = 2;
		} else {
			pi->lp = event->xSourceLp;
			event->xRc = HvLpEvent_Rc_Good;
			cevent->subtype_result_code = viorc_good;
			pi->seq = pi->ack = 0;
			reject = 0;
		}
	}

	spin_unlock_irqrestore(&consolelock, flags);

	if (reject == 1)
		printk(VIOCONS_KERN_WARN "open rejected: bad virtual tty.\n");
	else if (reject == 2)
		printk(VIOCONS_KERN_WARN
			"open rejected: console in exclusive use by another partition.\n");

	/* Return the acknowledgement */
	HvCallEvent_ackLpEvent(event);
}

/*
 * Handle a close charLpEvent.  This should ONLY be an Interrupt because the
 * virtual console should never actually issue a close event to the hypervisor
 * because the virtual console never goes away.  A close event coming from the
 * hypervisor simply means that there are no client consoles connected to the
 * virtual console.
 *
 * Regardless of the number of connections masqueraded on the other side of
 * the hypervisor ONLY ONE close event should be called to accompany the ONE
 * open event that is called.  The close event should ONLY be called when NO
 * MORE connections (masqueraded or not) exist on the other side of the
 * hypervisor.
 */
static void vioHandleCloseEvent(struct HvLpEvent *event)
{
	unsigned long flags;
	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
	u8 port = cevent->virtual_device;

	if (hvlpevent_is_int(event)) {
		if (port >= VTTY_PORTS) {
			printk(VIOCONS_KERN_WARN
					"close message from invalid virtual device.\n");
			return;
		}

		/* For closes, just mark the console partition invalid */
		spin_lock_irqsave(&consolelock, flags);
		/* Got the lock, don't cause console output */

		if (port_info[port].lp == event->xSourceLp)
			port_info[port].lp = HvLpIndexInvalid;

		spin_unlock_irqrestore(&consolelock, flags);
		printk(VIOCONS_KERN_INFO "close from %d\n", event->xSourceLp);
	} else
		printk(VIOCONS_KERN_WARN
				"got unexpected close acknowlegement\n");
}

/*
 * Handle a config charLpEvent.  Could be either interrupt or ack
 */
static void vioHandleConfig(struct HvLpEvent *event)
{
	struct viocharlpevent *cevent = (struct viocharlpevent *)event;

	HvCall_writeLogBuffer(cevent->data, cevent->len);

	if (cevent->data[0] == 0x01)
		printk(VIOCONS_KERN_INFO "window resized to %d: %d: %d: %d\n",
		       cevent->data[1], cevent->data[2],
		       cevent->data[3], cevent->data[4]);
	else
		printk(VIOCONS_KERN_WARN "unknown config event\n");
}

/*
 * Handle a data charLpEvent. 
 */
static void vioHandleData(struct HvLpEvent *event)
{
	struct tty_struct *tty;
	unsigned long flags;
	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
	struct port_info *pi;
	int index;
	int num_pushed;
	u8 port = cevent->virtual_device;

	if (port >= VTTY_PORTS) {
		printk(VIOCONS_KERN_WARN "data on invalid virtual device %d\n",
				port);
		return;
	}

	/*
	 * Hold the spinlock so that we don't take an interrupt that
	 * changes tty between the time we fetch the port_info
	 * pointer and the time we paranoia check.
	 */
	spin_lock_irqsave(&consolelock, flags);
	pi = &port_info[port];

	/*
	 * Change 05/01/2003 - Ryan Arnold: If a partition other than
	 * the current exclusive partition tries to send us data
	 * events then just drop them on the floor because we don't
	 * want his stinking data.  He isn't authorized to receive
	 * data because he wasn't the first one to get the console,
	 * therefore he shouldn't be allowed to send data either.
	 * This will work without an iSeries fix.
	 */
	if (pi->lp != event->xSourceLp) {
		spin_unlock_irqrestore(&consolelock, flags);
		return;
	}

	tty = pi->tty;
	if (tty == NULL) {
		spin_unlock_irqrestore(&consolelock, flags);
		printk(VIOCONS_KERN_WARN "no tty for virtual device %d\n",
				port);
		return;
	}

	if (tty->magic != TTY_MAGIC) {
		spin_unlock_irqrestore(&consolelock, flags);
		printk(VIOCONS_KERN_WARN "tty bad magic\n");
		return;
	}

	/*
	 * Just to be paranoid, make sure the tty points back to this port
	 */
	pi = (struct port_info *)tty->driver_data;
	if (!pi || viotty_paranoia_check(pi, tty->name, "vioHandleData")) {
		spin_unlock_irqrestore(&consolelock, flags);
		return;
	}
	spin_unlock_irqrestore(&consolelock, flags);

	/*
	 * Change 07/21/2003 - Ryan Arnold: functionality added to
	 * support sysrq utilizing ^O as the sysrq key.  The sysrq
	 * functionality will only work if built into the kernel and
	 * then only if sysrq is enabled through the proc filesystem.
	 */
	num_pushed = 0;
	for (index = 0; index < cevent->len; index++) {
		/*
		 * Will be optimized away if !CONFIG_MAGIC_SYSRQ:
		 */
		if (sysrq_on()) {
			/* 0x0f is the ascii character for ^O */
			if (cevent->data[index] == '\x0f') {
				vio_sysrq_pressed = 1;
				/*
				 * continue because we don't want to add
				 * the sysrq key into the data string.
				 */
				continue;
			} else if (vio_sysrq_pressed) {
				handle_sysrq(cevent->data[index], tty);
				vio_sysrq_pressed = 0;
				/*
				 * continue because we don't want to add
				 * the sysrq sequence into the data string.
				 */
				continue;
			}
		}
		/*
		 * The sysrq sequence isn't included in this check if
		 * sysrq is enabled and compiled into the kernel because
		 * the sequence will never get inserted into the buffer.
		 * Don't attempt to copy more data into the buffer than we
		 * have room for because it would fail without indication.
		 */
		if(tty_insert_flip_char(tty, cevent->data[index], TTY_NORMAL) == 0) {
			printk(VIOCONS_KERN_WARN "input buffer overflow!\n");
			break;
		}
		num_pushed++;
	}

	if (num_pushed)
		tty_flip_buffer_push(tty);
}

/*
 * Handle an ack charLpEvent. 
 */
static void vioHandleAck(struct HvLpEvent *event)
{
	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
	unsigned long flags;
	u8 port = cevent->virtual_device;

	if (port >= VTTY_PORTS) {
		printk(VIOCONS_KERN_WARN "data on invalid virtual device\n");
		return;
	}

	spin_lock_irqsave(&consolelock, flags);
	port_info[port].ack = event->xCorrelationToken;
	spin_unlock_irqrestore(&consolelock, flags);

	if (port_info[port].used)
		send_buffers(&port_info[port]);
}

/*
 * Handle charLpEvents and route to the appropriate routine
 */
static void vioHandleCharEvent(struct HvLpEvent *event)
{
	int charminor;

	if (event == NULL)
		return;

	charminor = event->xSubtype & VIOMINOR_SUBTYPE_MASK;
	switch (charminor) {
	case viocharopen:
		vioHandleOpenEvent(event);
		break;
	case viocharclose:
		vioHandleCloseEvent(event);
		break;
	case viochardata:
		vioHandleData(event);
		break;
	case viocharack:
		vioHandleAck(event);
		break;
	case viocharconfig:
		vioHandleConfig(event);
		break;
	default:
		if (hvlpevent_is_int(event) && hvlpevent_need_ack(event)) {
			event->xRc = HvLpEvent_Rc_InvalidSubtype;
			HvCallEvent_ackLpEvent(event);
		}
	}
}

/*
 * Send an open event
 */
static int send_open(HvLpIndex remoteLp, void *sem)
{
	return HvCallEvent_signalLpEventFast(remoteLp,
			HvLpEvent_Type_VirtualIo,
			viomajorsubtype_chario | viocharopen,
			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
			viopath_sourceinst(remoteLp),
			viopath_targetinst(remoteLp),
			(u64)(unsigned long)sem, VIOVERSION << 16,
			0, 0, 0, 0);
}

static const struct tty_operations serial_ops = {
	.open = viotty_open,
	.close = viotty_close,
	.write = viotty_write,
	.put_char = viotty_put_char,
	.write_room = viotty_write_room,
	.chars_in_buffer = viotty_chars_in_buffer,
	.ioctl = viotty_ioctl,
};

static int __init viocons_init2(void)
{
	atomic_t wait_flag;
	int rc;

	if (!firmware_has_feature(FW_FEATURE_ISERIES))
		return -ENODEV;

	/* +2 for fudge */
	rc = viopath_open(HvLpConfig_getPrimaryLpIndex(),
			viomajorsubtype_chario, VIOCHAR_WINDOW + 2);
	if (rc)
		printk(VIOCONS_KERN_WARN "error opening to primary %d\n", rc);

	if (viopath_hostLp == HvLpIndexInvalid)
		vio_set_hostlp();

	/*
	 * And if the primary is not the same as the hosting LP, open to the 
	 * hosting lp
	 */
	if ((viopath_hostLp != HvLpIndexInvalid) &&
	    (viopath_hostLp != HvLpConfig_getPrimaryLpIndex())) {
		printk(VIOCONS_KERN_INFO "open path to hosting (%d)\n",
				viopath_hostLp);
		rc = viopath_open(viopath_hostLp, viomajorsubtype_chario,
				VIOCHAR_WINDOW + 2);	/* +2 for fudge */
		if (rc)
			printk(VIOCONS_KERN_WARN
				"error opening to partition %d: %d\n",
				viopath_hostLp, rc);
	}

	if (vio_setHandler(viomajorsubtype_chario, vioHandleCharEvent) < 0)
		printk(VIOCONS_KERN_WARN
				"error seting handler for console events!\n");

	/*
	 * First, try to open the console to the hosting lp.
	 * Wait on a semaphore for the response.
	 */
	atomic_set(&wait_flag, 0);
	if ((viopath_isactive(viopath_hostLp)) &&
	    (send_open(viopath_hostLp, (void *)&wait_flag) == 0)) {
		printk(VIOCONS_KERN_INFO "hosting partition %d\n",
			viopath_hostLp);
		while (atomic_read(&wait_flag) == 0)
			mb();
		atomic_set(&wait_flag, 0);
	}

	/*
	 * If we don't have an active console, try the primary
	 */
	if ((!viopath_isactive(port_info[0].lp)) &&
	    (viopath_isactive(HvLpConfig_getPrimaryLpIndex())) &&
	    (send_open(HvLpConfig_getPrimaryLpIndex(), (void *)&wait_flag)
	     == 0)) {
		printk(VIOCONS_KERN_INFO "opening console to primary partition\n");
		while (atomic_read(&wait_flag) == 0)
			mb();
	}

	/* Initialize the tty_driver structure */
	viotty_driver = alloc_tty_driver(VTTY_PORTS);
	viotty_driver->owner = THIS_MODULE;
	viotty_driver->driver_name = "vioconsole";
	viotty_driver->name = "tty";
	viotty_driver->name_base = 1;
	viotty_driver->major = TTY_MAJOR;
	viotty_driver->minor_start = 1;
	viotty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
	viotty_driver->subtype = 1;
	viotty_driver->init_termios = tty_std_termios;
	viotty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
	tty_set_operations(viotty_driver, &serial_ops);

	if (tty_register_driver(viotty_driver)) {
		printk(VIOCONS_KERN_WARN "couldn't register console driver\n");
		put_tty_driver(viotty_driver);
		viotty_driver = NULL;
	}

	unregister_console(&viocons_early);
	register_console(&viocons);

	return 0;
}

static int __init viocons_init(void)
{
	int i;

	if (!firmware_has_feature(FW_FEATURE_ISERIES))
		return -ENODEV;

	printk(VIOCONS_KERN_INFO "registering console\n");
	for (i = 0; i < VTTY_PORTS; i++) {
		port_info[i].lp = HvLpIndexInvalid;
		port_info[i].magic = VIOTTY_MAGIC;
	}
	HvCall_setLogBufferFormatAndCodepage(HvCall_LogBuffer_ASCII, 437);
	add_preferred_console("viocons", 0, NULL);
	register_console(&viocons_early);
	return 0;
}

console_initcall(viocons_init);
module_init(viocons_init2);
