/*-
 * Copyright (c) 2003 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.
 *
 * $FreeBSD$
 */
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/cons.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/systm.h>
#include <sys/tty.h>
#include <sys/uio.h>

#include <dev/ethercons/ethercons.h>

static int	ethercons_tty_open(dev_t dev, int flags, int mode,
		    struct thread *td);
static int	ethercons_tty_close(dev_t dev, int flags, int mode,
		    struct thread *td);
static int	ethercons_tty_ioctl(dev_t dev, u_long cmd, caddr_t data,
		    int flags, struct thread *td);

static cn_probe_t ethercons_cnprobe;
static cn_init_t ethercons_cninit;
static cn_term_t ethercons_cnterm;
static cn_putc_t ethercons_cnputc;

CONS_DRIVER(ethercons, ethercons_cnprobe, ethercons_cninit, ethercons_cnterm,
    NULL, NULL, ethercons_cnputc, NULL);

static struct cdevsw ethercons_cdevsw = {
	.d_open = ethercons_tty_open,
	.d_close = ethercons_tty_close,
	.d_read = ttyread,
	.d_write = ttywrite,
	.d_ioctl = ethercons_tty_ioctl,
	.d_poll = ttypoll,
	.d_name = "ethercons",
	.d_flags = D_TTY,
	.d_kqfilter = ttykqfilter,
};

static struct tty	*ethercons_tty;
static dev_t		 ethercons_dev;

static void
ethercons_cnprobe(struct consdev *cp)
{

	cp->cn_pri = CN_REMOTE;
	/* cp->cn_pri = CN_NORMAL; */
	/* cp->cn_pri = CN_DEAD; */
	cp->cn_arg = NULL;
	cp->cn_flags = CN_FLAG_NODEBUG;
	strcpy(cp->cn_name, "ethercons");
}

static void
ethercons_cninit(struct consdev *cp)
{

}

static void
ethercons_cnterm(struct consdev *cp)
{

}

static void
ethercons_cnputc(struct consdev *cp, int c)
{
	char data[2];

	data[0] = c;
	data[1] = '\0';

	ethercons_transmit(2, data);
}

/*
 * Launch transmission of next packet.  To avoid generting packets that
 * are too large, and so that we can use the stack for a local buffer,
 * we transmit at most MAX_CHUNK in a given packet.  MAX_CHUNK includes
 * the terminating nul.
 */
#define	MAX_CHUNK	128
static void
ethercons_tty_oproc(struct tty *tp)
{
	char data[MAX_CHUNK];
	int i;

	KASSERT(tp == ethercons_tty, ("tp != ethercons_tty"));

	if (tp->t_state & TS_TTSTOP)
		return;
	if (tp->t_state & TS_BUSY)
		return;

	tp->t_state |= TS_BUSY;
	while ((i = q_to_b(&tp->t_outq, data, MAX_CHUNK-1)) > 0) {
		data[i] = '\0';
		ethercons_transmit(i + 1, data);
	}
	tp->t_state &= ~TS_BUSY;
	ttwwakeup(tp);
}
#undef	MAX_CHUNK

static void
ethercons_tty_stop(struct tty *tp, int flag)
{

}

static int
ethercons_tty_param(struct tty *tp, struct termios *t)
{

	KASSERT(tp == ethercons_tty, ("tp != ethercons_tty"));

#if 0
	/* XXX? */
	ttsetwater(tp);
#endif
	return (0);
}

void
ethercons_tty_intr(char c)
{
	struct tty *tp = ethercons_tty;

	/*
	 * Perhaps this should be in a taskqueue of some sort rather
	 * than in the netisr context?
	 */
	mtx_lock(&Giant);
	if ((tp->t_state & TS_ISOPEN) == TS_ISOPEN)
		(*linesw[tp->t_line].l_rint)(c, tp);
	mtx_unlock(&Giant);
}

static void
ethercons_tty_init(void *arg)
{
	struct tty *tp;

	tp = ethercons_tty = ttymalloc(NULL);

	tp->t_dev = ethercons_dev = make_dev(&ethercons_cdevsw, 0, UID_ROOT,
	    GID_WHEEL, 0600, "ethercons");
	ethercons_dev->si_tty = tp;
	tp->t_oproc = ethercons_tty_oproc;
	tp->t_param = ethercons_tty_param;
	tp->t_stop = ethercons_tty_stop;
}
SYSINIT(ethercons_tty_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE,
    ethercons_tty_init, NULL);

#if 0
static void
ethercons_tty_detach(void)
{

	/* Order? */
	destroy_dev(ethercons_dev);
	/* ttyfree(tp); */
}
#endif

static int
ethercons_tty_open(dev_t dev, int flags, int mode, struct thread *td)
{
	struct tty *tp;
	int error;

	tp = dev->si_tty;
	KASSERT(tp == ethercons_tty, ("tp != ethercons_tty"));

	if (tp->t_state & TS_ISOPEN) {
		if (tp->t_state & TS_XCLUDE) {
			error = suser(td);
			if (error)
				return (EBUSY);
		}
	} else {
		tp->t_dev = dev;
		tp->t_cflag = TTYDEF_CFLAG;
		tp->t_iflag = TTYDEF_IFLAG;
		tp->t_lflag = TTYDEF_LFLAG;
		tp->t_oflag = TTYDEF_OFLAG;
		tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
		ttychars(tp);
		(*linesw[tp->t_line].l_modem)(tp, 1);
	}
	error = ttyopen(dev, tp);
	if (error)
		return (error);
	error = (*linesw[tp->t_line].l_open)(dev, tp);
	return (error);
}

static int
ethercons_tty_close(dev_t dev, int flags, int mode, struct thread *td)
{
	struct tty *tp;

	tp = dev->si_tty;
	KASSERT(tp == ethercons_tty, ("tp != ethercons_tty"));

	(*linesw[tp->t_line].l_close)(tp, flags);
	ttyclose(tp);
	return (0);
}

static int
ethercons_tty_ioctl(dev_t dev, u_long cmd, caddr_t data, int flags,
    struct thread *td)
{
	struct tty *tp;
	int error;

	tp = dev->si_tty;
	KASSERT(tp == ethercons_tty, ("tp != ethercons_tty"));

	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flags, td);
	if (error == ENOIOCTL)
		error = ttioctl(tp, cmd, data, flags);
	if (error != ENOIOCTL)
		return (error);
	return (error);
}
