
/*-
 * Copyright (c) 2006 Robert N. M. Watson
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Implement the WWIV communications API using a device node rather than i386
 * hardware I/O.  Replaces only the functions that interact directly with the
 * hardware, not their consumers in com.c.
 */

#include <sys/ioctl.h>

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <termios.h>
#include <unistd.h>

#include "emul.h"
#include "vars.h"

static char		com_devname[PATH_MAX];
static int		com_io_fd = -1;
static char		com_peek_char;
static int		com_peek_char_valid;
static struct termios	com_termios;

/*
 * Determine whether a communications port is present.  Return 0 if none,
 * otherwise 1 for a cheap serial port and 2 for an expensive one.  We
 * emulate only expensive serial ports.
 *
 * For now, we ignore the requested port number and just use whatever was
 * passed to com_init().  In the future, something more sophisticated might
 * be called for (such as a configuration file).
 */
int
check_comport(int pn)
{

	if (access(com_devname, F_OK) < 0)
		return (0);
	else
		return (2);
}

/*
 * Communications port interrupt handler.  Not required.
 */
void
async_isr(void)
{

  buffer[head++] = inportb(base);
  if (head == MAX_BUF)
    head = 0;
  outportb(0x20, 0x20);
}

/*
 * Output a character on the serial port.
 */
void
outcomch(char ch)
{
	struct pollfd pollfd;
	ssize_t len;

	if (com_io_fd == -1)
#if 0
		panic("outcomch: com_io_fd -1");
#else
		return;
#endif

	bzero(&pollfd, sizeof(pollfd));
	pollfd.fd = com_io_fd;
	pollfd.events = POLLOUT;

	if (poll(&pollfd, 1, 0) < 0)
		panic("outcomch: poll(): %s", strerror(errno));

	len = write(com_io_fd, &ch, sizeof(ch));
	if (len < 0)
		panic("outcomch: write(0x%02x): %s", ch, strerror(errno));
	if (len != sizeof(ch))
		panic("outcomch: write(0x%02x): ret %d", ch, len);
}

char
peek1c(void)
{
	ssize_t len;
	char ch;

	if (com_io_fd == -1)
#if 0
		panic("peek1c: com_io_fd -1");
#else
		return (0);
#endif

	if (com_peek_char_valid)
		return (com_peek_char);

	len = read(com_io_fd, &ch, sizeof(ch));
	if (len < 0 && errno == EAGAIN)
		return (0);
	if (len < 0)
		panic("peek1c: read(): %s", strerror(errno));
	if (len != sizeof(ch))
		panic("peek1c: read(): ret %d", len);
	com_peek_char_valid = 1;
	com_peek_char = ch;
	return (ch);
}

char
get1c(void)
{
	ssize_t len;
	char ch;

	if (com_io_fd == -1)
#if 0
		panic("get1c: com_io_fd -1");
#else
		return (0);
#endif

	/*
	 * If we peeked ahead, return that char, otherwise, get a new one.
	 */
	if (com_peek_char_valid) {
		com_peek_char_valid = 0;
		return (com_peek_char);
	}

	len = read(com_io_fd, &ch, sizeof(ch));
	if (len < 0 && errno == EAGAIN)
		return (0);
	if (len < 0)
		panic("get1c: read(): %s", strerror(errno));
	if (len != sizeof(ch))
		panic("get1c: read(): ret %d", len);
	return (ch);
}

int
comhit(void)
{

	if (peek1c() != 0)
		return (1);
	else
		return (0);
}

void
dump(void)
{

}

void
set_baud(unsigned int rate)
{

	/*
	 * XXXRW: termios standard seems to specify using constants B*, but
	 * we have a number, so use it.  Works on FreeBSD?
	 */
	if (cfsetispeed(&com_termios, rate) < 0)
		panic("set_baud: cfsetispeed");
	if (cfsetospeed(&com_termios, rate) < 0)
		panic("set_baud: cfsetospeed");
	if (tcsetattr(com_io_fd, TCSANOW, &com_termios) < 0)
		panic("set_baud: tcsetattr");
}

void
initport(int port_num)
{
	int fd;

	if (com_io_fd != -1) {
		close(com_io_fd);
		com_io_fd = -1;
	}

	fd = open(com_devname, O_RDWR |/* O_NONBLOCK |*/ O_NOCTTY);
	if (fd == -1)
		panic("initport: open('%s')", com_devname);
	com_io_fd = fd;
	if (fcntl(com_io_fd, F_SETFL, O_NONBLOCK) < 0)
		panic("initport: fcntl(F_SETFL, O_NONBLOCK)");
	if (tcgetattr(com_io_fd, &com_termios) < 0)
		panic("initport: tcgetattr");
	com_termios.c_iflag = 0;
	com_termios.c_oflag = 0;
	com_termios.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL
	    /* | CCTS_OFLOW | CRTS_IFLOW*/);
	com_termios.c_lflag = NOKERNINFO;
	if (tcsetattr(com_io_fd, TCSANOW, &com_termios) < 0)
		panic("initport: tcsetattr");
}

void
closeport(void)
{

	if (com_io_fd == -1)
		panic("closeport: com_io_fd -1");

	close(com_io_fd);
	com_io_fd = -1;
}

void
dtr(int i)
{
	int state;

	if (com_io_fd == -1)
#if 0
		panic("dtr: com_io_fd -1");
#else
		return;
#endif

	state = TIOCM_DTR;

	if (i) {
		if (ioctl(com_io_fd, TIOCMBIS, &state) < 0)
			panic("dtr: ioctl(TIOCMBIS, %x ): %s", state,
			    strerror(errno));
	} else {
		if (ioctl(com_io_fd, TIOCMBIC, &state) < 0)
			panic("dtr: ioctl(TIOCMBIC, %x): %s", state,
			    strerror(errno));
	}
}

void
rts(int i)
{
	int state;

	if (com_io_fd == -1)
#if 0
		panic("rts: com_io_fd -1");
#else
		return;
#endif

	state = TIOCM_RTS;

	if (i) {
		if (ioctl(com_io_fd, TIOCMBIS, &state) < 0)
			panic("rts: ioctl(TIOCMBIS, %x ): %s", state,
			    strerror(errno));
	} else {
		if (ioctl(com_io_fd, TIOCMBIC, &state) < 0)
			panic("rts: ioctl(TIOCMBIC, %x): %s", state,
			    strerror(errno));
	}
}

int
cdet(void)
{
	int state;

	if (com_io_fd == -1)
#if 0
		panic("rts: com_io_fd -1");
#else
		return (0);
#endif

	if (ioctl(com_io_fd, TIOCMGET, &state) < 0)
		panic("cdet: ioctl(TIOCMGET): %s", strerror(errno));

	if (state & TIOCM_CD)
		return (1);
	else
		return (0);
}

int
empty(void)
{
	if (x_only)
		return(1);

	if (kbhitb() || (incom && peek1c() != 0) ||
	    (charbufferpointer && charbuffer[charbufferpointer]) ||
	    (in_extern == 2))
		return(0);
	return(1);
}

void
com_init(const char *devname)
{

	strlcpy(com_devname, devname, PATH_MAX);
}
