/*
 * xencons.c
 *	Access to xen consoles.
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "../pc/io.h"

extern PhysUart xenphysuart;

static Uart xenuart = {
	.name = "xencons",
	.freq = 1843200,
	.phys = &xenphysuart,
};

struct {
	struct xencons_interface *intf;
	int evtchn;
	Lock txlock;
} xencons;

/*
 * Debug print to xen "emergency console".
 * Output only appears if xen is built with verbose=y
 */
void
dprint(char *fmt, ...)
{
	int n;
	va_list arg;
	char buf[PRINTSIZE];

	va_start(arg, fmt);
	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);
	HYPERVISOR_console_io(CONSOLEIO_write, n, buf);
}

static void kick(Uart*);
/*
 * Emit a string to the guest OS console, bypassing the queue
 *   - before serialoq is initialised
 *   - when rdb is activated
 *   - from iprint() for messages from interrupt routines
 * If ring is full, just throw extra output away.
 */
void
xenuartputs(char *s, int n)
{
	struct xencons_interface *con = xencons.intf;
	unsigned long prod;
	int c;

	ilock(&xencons.txlock);
	prod = con->out_prod;
	while (n-- > 0 && (prod - con->out_cons) < sizeof(con->out)) {
		c = *s++;
		/*
		if (c == '\n')
			con->out[MASK_XENCONS_IDX(prod++, con->out)] = '\r';
		*/
		con->out[MASK_XENCONS_IDX(prod++, con->out)] = c;
	}
	coherence();
	con->out_prod = prod;
	xenchannotify(xencons.evtchn);
	iunlock(&xencons.txlock);
}

/*
 * Handle channel event from console
 */
static void
interrupt(Ureg*, void*)
{
	char c;
	unsigned long cons;
	Uart *uart;
	struct xencons_interface *con = xencons.intf;

	uart = &xenuart;

	cons = con->in_cons;
	coherence();
	while (cons != con->in_prod) {
		c = con->in[MASK_XENCONS_IDX(cons++, con->in)];
		uartrecv(uart, c);
	}
	coherence();
	con->in_cons = cons;
	kick(nil);
}

static Uart*
pnp(void)
{
	return &xenuart;
}

static void
enable(Uart*, int ie)
{
	if(ie)
		intrenable(xencons.evtchn, interrupt, 0, BUSUNKNOWN, "Xen console");
}

static void
disable(Uart*)
{
}

/*
 * Send queued output to guest OS console
 */
static void
kick(Uart*)
{
	struct xencons_interface *con = xencons.intf;
	unsigned long prod;
	long avail, idx, n, m;

	ilock(&xencons.txlock);
	prod = con->out_prod;
	avail = sizeof(con->out) - (prod - con->out_cons);
	while (avail > 0) {
		idx = MASK_XENCONS_IDX(prod, con->out);
		m = sizeof(con->out) - idx;
		if (m > avail)
			m = avail;
		n = qconsume(serialoq, con->out+idx, m);
		if (n < 0)
			break;
		prod += n;
		avail -= n;
	}
	coherence();
	con->out_prod = prod;
	xenchannotify(xencons.evtchn);
	iunlock(&xencons.txlock);
}

static void
donothing(Uart*, int)
{
}

static int
donothingint(Uart*, int)
{
	return 0;
}

static int
baud(Uart *uart, int n)
{
	if(n <= 0)
		return -1;

	uart->baud = n;
	return 0;
}

static int
bits(Uart *uart, int n)
{
	switch(n){
	case 7:
	case 8:
		break;
	default:
		return -1;
	}

	uart->bits = n;
	return 0;
}

static int
stop(Uart *uart, int n)
{
	if(n != 1)
		return -1;
	uart->stop = n;
	return 0;
}

static int
parity(Uart *uart, int n)
{
	if(n != 'n')
		return -1;
	uart->parity = n;
	return 0;
}

void
xenputc(Uart*, int c)
{
	struct xencons_interface *con = xencons.intf;
	unsigned long prod;

	ilock(&xencons.txlock);
	prod = con->out_prod;
	if((prod - con->out_cons) < sizeof(con->out)){
		if (c == '\n')
			con->out[MASK_XENCONS_IDX(prod++, con->out)] = '\r';
		con->out[MASK_XENCONS_IDX(prod++, con->out)] = c;
	}

	coherence();
	con->out_prod = prod;
	xenchannotify(xencons.evtchn);
	iunlock(&xencons.txlock);
}

int
xengetc(Uart*)
{
	struct xencons_interface *con = xencons.intf;
	char c;

	c = 0;

	if(con->in_cons != con->in_prod){
		coherence();
		c = con->in[MASK_XENCONS_IDX(con->in_cons++, con->in)];
		if (con->in_cons == con->in_prod)
			xenchannotify(xencons.evtchn);
	}

	return c;
}

PhysUart xenphysuart = {
	.name		= "xenuart",

	.pnp		= pnp,
	.enable		= enable,
	.disable	= disable,
	.kick		= kick,
	.dobreak	= donothing,
	.baud		= baud,
	.bits		= bits,
	.stop		= stop,
	.parity		= parity,
	.modemctl	= donothing,
	.rts		= donothing,
	.dtr		= donothing,
	.fifo		= donothing,

	.getc		= xengetc,
	.putc		= xenputc,
};

/* console=0 to enable */
void
xenconsinit(void)
{
	xencons.intf = (struct xencons_interface*)mmumapframe(XENCONSOLE, xenstart->console_mfn);
	xencons.evtchn = xenstart->console_evtchn;

	consuart = &xenuart;
	consuart->console = 1;
}

void
kbdenable(void)
{
	Uart *uart;
	int n;
	char *p, *cmd;

	if((p = getconf("console")) == nil)
		return;
	n = strtoul(p, &cmd, 0);
	if(p == cmd || n != 0)
		return;
	uart = &xenuart;

	(*uart->phys->enable)(uart, 0);
	uartctl(uart, "b9600 l8 pn s1");
	if(*cmd != '\0')
		uartctl(uart, cmd);

	consuart = uart;
	uart->console = 1;
}

