/*
 * -----------------------------------------------------------------------
 *
 *   Copyright 1994-2008 H. Peter Anvin - All Rights Reserved
 *   Copyright 2009-2014 Intel Corporation; author: H. Peter Anvin
 *
 *   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, Inc., 53 Temple Place Ste 330,
 *   Boston MA 02111-1307, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * -----------------------------------------------------------------------
 *
 *
 * conio.c
 *
 * Console I/O code, except:
 *   writechr, writestr_early	- module-dependent
 *   writestr, crlf		- writestr.inc
 *   writehex*			- writehex.inc
 */
#include <sys/io.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <fs.h>
#include <com32.h>
#include <x86/cpu.h>
#include <syslinux/firmware.h>

#include "bios.h"
#include "graphics.h"

union screen _cursor;
union screen _screensize;

/*
 * Serial console stuff.
 */
__export uint16_t SerialPort = 0;   /* Serial port base (or 0 for no serial port) */
__export uint8_t FlowInput = 0;	    /* Input bits for serial flow */
__export uint16_t BaudDivisor = 115200/9600; /* Baud rate divisor */
__export uint8_t FlowIgnore = 0;    /* Ignore input unless these bits set */
__export uint16_t DisplayCon = 0x01;	/* Display console enabled */
__export uint8_t FlowOutput = 0;	/* Output to assert for serial flow */

__export uint8_t DisplayMask = 0x07;	/* Display modes mask */

uint8_t ScrollAttribute = 0x07; /* Grey on white (normal text color) */

/*
 * loadkeys:	Load a LILO-style keymap
 *
 * Returns 0 on success, or -1 on error.
 */
__export int loadkeys(const char *filename)
{
	FILE *f;

	f = fopen(filename, "r");
	if (!f)
		return -1;

	fread(KbdMap, 1, sizeof(KbdMap), f);

	fclose(f);
	return 0;
}

/*
 * write_serial: If serial output is enabled, write character on
 * serial port.
 */
__export void write_serial(char data)
{
	if (!SerialPort)
		return;

	if (!(DisplayMask & 0x04))
		return;

	while (1) {
		char ch;

		ch = inb(SerialPort + 5); /* LSR */

		/* Wait for space in transmit register */
		if (!(ch & 0x20))
			continue;

		/* Wait for input flow control */
		ch = inb(SerialPort + 6);
		ch &= FlowInput;
		if (ch != FlowInput)
			continue;

		break;
	}

	outb(data, SerialPort);	/* Send data */
	io_delay();
}

void pm_write_serial(com32sys_t *regs)
{
	write_serial(regs->eax.b[0]);
}

void serialcfg(uint16_t *iobase, uint16_t *divisor, uint16_t *flowctl)
{
	uint8_t al, ah;

	*iobase = SerialPort;
	*divisor = BaudDivisor;

	al = FlowOutput;
	ah = FlowInput;

	al |= ah;
	ah = FlowIgnore;
	ah >>= 4;

	if (!DisplayCon)
		ah |= 0x80;

	*flowctl = al | (ah << 8);
}

void pm_serialcfg(com32sys_t *regs)
{
	serialcfg(&regs->eax.w[0], &regs->ecx.w[0], &regs->ebx.w[0]);
}

/*
 * write_serial_str: write_serial for strings
 */
__export void write_serial_str(char *data)
{
	char ch;

	while ((ch = *data++))
		write_serial(ch);
}

/*
 * pollchar: check if we have an input character pending
 *
 * Returns 1 if character pending.
 */
int bios_pollchar(void)
{
	com32sys_t ireg, oreg;
	uint8_t data = 0;

	memset(&ireg, 0, sizeof(ireg));

	ireg.eax.b[1] = 0x11;	/* Poll keyboard */
	__intcall(0x16, &ireg, &oreg);

	if (!(oreg.eflags.l & EFLAGS_ZF))
		return 1;

	if (SerialPort) {
		cli();

		/* Already-queued input? */
		if (SerialTail == SerialHead) {
			/* LSR */
			data = inb(SerialPort + 5) & 1;
			if (data) {
				/* MSR */
				data = inb(SerialPort + 6);

				/* Required status bits */
				data &= FlowIgnore;

				if (data == FlowIgnore)
					data = 1;
				else
					data = 0;
			}
		} else
			data = 1;
		sti();
	}

	return data;
}

__export int pollchar(void)
{
	return firmware->i_ops->pollchar();
}

void pm_pollchar(com32sys_t *regs)
{
	if (pollchar())
		regs->eflags.l &= ~EFLAGS_ZF;
	else
		regs->eflags.l |= EFLAGS_ZF;
}

char bios_getchar(char *hi)
{
	com32sys_t ireg, oreg;
	unsigned char data;

	memset(&ireg, 0, sizeof(ireg));
	memset(&oreg, 0, sizeof(oreg));
	while (1) {
		__idle();

		ireg.eax.b[1] = 0x11;	/* Poll keyboard */
		__intcall(0x16, &ireg, &oreg);

		if (oreg.eflags.l & EFLAGS_ZF) {
			if (!SerialPort)
				continue;

			cli();
			if (SerialTail != SerialHead) {
				/* serial queued */
				sti(); /* We already know we'll consume data */
				data = *SerialTail++;

				if (SerialTail > SerialHead + serial_buf_size)
					SerialTail = SerialHead;
			} else {
				/* LSR */
				data = inb(SerialPort + 5) & 1;
				if (!data) {
					sti();
					continue;
				}
				data = inb(SerialPort + 6);
				data &= FlowIgnore;
				if (data != FlowIgnore) {
					sti();
					continue;
				}

				data = inb(SerialPort);
				sti();
				break;
			}
		} else {
			/* Keyboard input? */
			ireg.eax.b[1] = 0x10; /* Get keyboard input */
			__intcall(0x16, &ireg, &oreg);

			data = oreg.eax.b[0];
			*hi = oreg.eax.b[1];

			if (data == 0xE0)
				data = 0;

			if (data) {
				/* Convert character sets */
				data = KbdMap[data];
			}
		}

		break;
	}

	reset_idle();		/* Character received */
	return data;
}

uint8_t bios_shiftflags(void)
{
	com32sys_t reg;
	uint8_t ah, al;

	memset(&reg, 0, sizeof reg);
	reg.eax.b[1] = 0x12;
	__intcall(0x16, &reg, &reg);
	ah = reg.eax.b[1];
	al = reg.eax.b[0];

	/*
	 * According to the Interrupt List, "many machines" don't correctly
	 * fold the Alt state, presumably because it might be AltGr.
	 * Explicitly fold the Alt and Ctrl states; it fits our needs
	 * better.
	 */

	if (ah & 0x0a)
		al |= 0x08;
	if (ah & 0x05)
		al |= 0x04;

	return al;
}

__export uint8_t kbd_shiftflags(void)
{
	if (firmware->i_ops->shiftflags)
		return firmware->i_ops->shiftflags();
	else
		return 0;	/* Unavailable on this firmware */
}

/*
 * getchar: Read a character from keyboard or serial port
 */
__export char getchar(char *hi)
{
	return firmware->i_ops->getchar(hi);
}

void pm_getchar(com32sys_t *regs)
{
	regs->eax.b[0] = getchar((char *)&regs->eax.b[1]);
}
