/*
 * mii_ssc.c
 *
 * Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
 *
 * SPDX-License-Identifier: MIT
 *
 */
/*
	Theory of operation:
	There is a thread that is started for any/all cards in the system. Cards
	themselves communicate via one 'command' FIFO to start/stop themselves,
	which add/remove them from the private list of cards kept by the thread.

	The cards attempts not to open the device (and start the thread) until
	they are actually used, so it monitors for access from the ROM area,
	(that will be happening if PR#x and IN#x are used), OR when the PC
	calls into the Cx00-CxFF area.

	Once running, the thread will monitor the 'tty' file descriptor for each
	and will deal with 2 'data' FIFOs to send and receive data from the 6502.

	The SSC driver itself just monitors the FIFO state and update the status
	register, and raise IRQs as needed.
 */
/*
git clone https://github.com/colinleroy/a2tools.git
cd a2tools/src/surl-server
sudo apt-get install libcurl4-gnutls-dev libgumbo-dev libpng-dev libjq-dev libsdl-image1.2-dev
make && A2_TTY=/dev/tntX ./surl-server
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_sw.h"
#include "mii_ssc.h"
#include "fifo_declare.h"
#include "bsd_queue.h"

#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(ssc_rom, "roms/mii_rom_scc_3410065a.bin");


#include <termios.h>
#include <pty.h>
#include <fcntl.h>

static const mii_ssc_setconf_t _mii_ssc_default_conf = {
	.baud = 9600,
	.bits = 8,
	.parity = 0,
	.stop = 1,
	.handshake = 0,
	.is_device = 1,
	.is_socket = 0,
	.is_pty = 0,
	.socket_port = 0,
	.device = "/dev/tnt0",
};

// SW1-4  SW1 is MSB, switches are inverted ? (0=on, 1=off)
static const int _mii_ssc_to_baud[16] = {
	[0] = B1152000,	[1] = B50,		[2] = B75,		[3] = B110,
	[4] = B134,		[5] = B150,		[6] = B300,		[7] = B600,
	[8] = B1200,	[9] = B1800,	[10] = B2400,
	[12] = B4800,					[14] = B9600,	[15] = B19200,
};
static const unsigned int _mii_ssc_to_baud_rate[16] = {
	[0] = 1152000,	[1] = 50,		[2] = 75,		[3] = 110,
	[4] = 134,		[5] = 150,		[6] = 300,		[7] = 600,
	[8] = 1200,		[9] = 1800,		[10] = 2400,	[11] = -3600,
	[12] = 4800,	[13] = -7200,	[14] = 9600,	[15] = 19200,
};

enum {
	SSC_6551_CONTROL_BAUD 	= 0,	// see table
	SSC_6551_CONTROL_CLOCK 	= 4,	// always 1
	SSC_6551_CONTROL_WLEN 	= 5,	// 0=8 bits, 1=7 bits, 2=6 bits, 3=5 bits
	SSC_6551_CONTROL_STOP 	= 7,	// 0 = 1 stop bit, 1 = 2 stop bits

	SSC_6551_CONTROL_RESET	= 0,

	SSC_6551_COMMAND_DTR 	= 0,	// 0=Disable Receiver (DTR), 1=Enable
	SSC_6551_COMMAND_IRQ_R 	= 1,	// 0=IRQ Enabled, 1=IRQ Disabled
	// 0:IRQ_TX=0 + RTS=1
	// 1:IRQ_TX=1 + RTS=0
	// 2:IRQ_TX=0 + RTS=0
	// 3:IRQ_TX=0 + RTS=0 + BRK
	SSC_6551_COMMAND_IRQ_T 	= 2,
	SSC_6551_COMMAND_ECHO 	= 4,	// 0=off, 1=on
	SSC_6551_COMMAND_PARITY = 5,	// None, Odd, Even, Mark, Space

	SSC_6551_COMMAND_RESET	= (1 << SSC_6551_COMMAND_IRQ_R),

	SSC_6551_PARITY_ERROR	= 0,
	SSC_6551_FRAMING_ERROR	= 1,
	SSC_6551_OVERRUN		= 2,
	SSC_6551_RX_FULL		= 3,
	SSC_6551_TX_EMPTY		= 4,
	SSC_6551_DCD			= 5,
	SSC_6551_DSR			= 6,
	SSC_6551_IRQ			= 7,

	SSC_6551_STATUS_RESET	= (1 << SSC_6551_TX_EMPTY),
};
enum {
	SSC_SW2_STOPBITS		= 1 << 7,
	SSC_SW2_DATABITS		= 1 << 6,
	SSC_SW2_IRQEN			= 1 << 0,
};
// SW2-1 is stop bits OFF = Two, ON = One (inverted)
static const unsigned int _mii_ssc_to_stop[2] = {
	[0] = 0,		[1] = CSTOPB,
};
// SW2-2 is data bits
static const unsigned int _mii_ssc_to_bits[4] = {
	[0] = CS8,		[1] = CS7, 	[2] = CS6, 		[3] = CS5,
};
static const int _mii_scc_to_bits_count[4] = {
	[0] = 8,		[1] = 7,		[2] = 6,		[3] = 5,
};
// SW2-3-4 is parity
static const unsigned int _mii_ssc_to_parity[4] = {
	[0] = 0,		[1] = PARODD, [2] = PARENB,		[3] = PARENB|PARODD,
};


enum {
	MII_SSC_STATE_INIT = 0,
	MII_SSC_STATE_START,
	MII_SSC_STATE_RUNNING,
	MII_SSC_STATE_STOP,
	MII_SSC_STATE_STOPPED,
	MII_THREAD_TERMINATE,
};

struct mii_card_ssc_t;
typedef struct mii_ssc_cmd_t {
	int 	cmd;
	union {
		struct mii_card_ssc_t * card;
	};
} mii_ssc_cmd_t;

DECLARE_FIFO(mii_ssc_cmd_t, mii_ssc_cmd_fifo, 8);
DEFINE_FIFO(mii_ssc_cmd_t, mii_ssc_cmd_fifo);

DECLARE_FIFO(uint8_t, mii_ssc_fifo, 16);
DEFINE_FIFO(uint8_t, mii_ssc_fifo);

typedef struct mii_card_ssc_t {
	// queued when first allocated, to keep a list of all cards
	STAILQ_ENTRY(mii_card_ssc_t) self;
	// queued when started, for the thread
	STAILQ_ENTRY(mii_card_ssc_t) started;
	struct mii_slot_t *	slot;
	struct mii_bank_t * rom;
	mii_t *				mii;
	uint8_t 			slot_offset;
	mii_ssc_setconf_t	conf;
	int 				state; 		// current state, MII_SSC_STATE_*
	char 				tty_path[128];
	int 				tty_fd;		// <= 0 is not opened yet
	char 				human_config[32];
	// global counter of bytes sent/received. No functional use
	uint32_t 			total_rx, total_tx;
	uint8_t 			timer_check;
	uint32_t 			timer_delay;
	mii_ssc_fifo_t 		rx,tx;
	// 6551 registers
	uint8_t 			dipsw1, dipsw2, control, command, status;
} mii_card_ssc_t;

STAILQ_HEAD(, mii_card_ssc_t)
		_mii_card_ssc_slots = STAILQ_HEAD_INITIALIZER(_mii_card_ssc_slots);
/*
 * These bits are only meant to communicate with the thread
 */
STAILQ_HEAD(, mii_card_ssc_t)
		_mii_card_ssc_started = STAILQ_HEAD_INITIALIZER(_mii_card_ssc_started);
pthread_t 		_mii_ssc_thread_id = 0;
mii_ssc_cmd_fifo_t 	_mii_ssc_cmd = {};

static int
_mii_scc_set_conf(
		mii_card_ssc_t *c,
		const mii_ssc_setconf_t *conf,
		int re_open);

static void*
_mii_ssc_thread(
		void *param)
{
	printf("%s: start\n", __func__);
	// ignore the signal, we use it to wake up the thread
	sigaction(SIGUSR1, &(struct sigaction){
		.sa_handler = SIG_IGN,
		.sa_flags = SA_RESTART,
	}, NULL);
	do {
		/*
		 * Get commands from the MII running thread. Add/remove cards
		 * from the 'running' list, and a TERMINATE to kill the thread
		 */
		while (!mii_ssc_cmd_fifo_isempty(&_mii_ssc_cmd)) {
			mii_ssc_cmd_t cmd = mii_ssc_cmd_fifo_read(&_mii_ssc_cmd);
			switch (cmd.cmd) {
				case MII_SSC_STATE_START: {
					mii_card_ssc_t *c = cmd.card;
					printf("%s: start slot %d\n", __func__, c->slot->id);
					STAILQ_INSERT_TAIL(&_mii_card_ssc_started, c, self);
					c->state = MII_SSC_STATE_RUNNING;
				}	break;
				case MII_SSC_STATE_STOP: {
					mii_card_ssc_t *c = cmd.card;
					printf("%s: stop slot %d\n", __func__, c->slot->id);
					STAILQ_REMOVE(&_mii_card_ssc_started, c, mii_card_ssc_t, self);
					c->state = MII_SSC_STATE_STOPPED;
				}	break;
				case MII_THREAD_TERMINATE:
					printf("%s: terminate\n", __func__);
					return NULL;
			}
		}
		/* here we use select. This is not optimal on linux, but it is portable
		   to other OSes -- perhaps I'll add an epoll() version later */
		fd_set rfds, wfds;
		FD_ZERO(&rfds);
		FD_ZERO(&wfds);
		int maxfd = 0;
		mii_card_ssc_t *c = NULL, *safe;
		STAILQ_FOREACH_SAFE(c, &_mii_card_ssc_started, self, safe) {
			// guy might be being reconfigured, or perhaps had an error
			if (c->tty_fd < 0)
				continue;
			if (!mii_ssc_fifo_isempty(&c->tx)) {
				FD_SET(c->tty_fd, &wfds);
				if (c->tty_fd > maxfd)
					maxfd = c->tty_fd;
			}
			if (!mii_ssc_fifo_isfull(&c->rx)) {
				FD_SET(c->tty_fd, &rfds);
				if (c->tty_fd > maxfd)
					maxfd = c->tty_fd;
			}
		}
		struct timeval tv = { .tv_sec = 0, .tv_usec = 1000 };
		int res = select(maxfd + 1, &rfds, &wfds, NULL, &tv);
		if (res < 0) {
			// there are OK errors, we just ignore them
			if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
				continue;
			printf("%s ssc select: %s\n", __func__, strerror(errno));
			break;
		}
		if (res == 0)	// timeout
			continue;
		STAILQ_FOREACH(c, &_mii_card_ssc_started, self) {
			/* Here we know the read fifo isn't full, otherwise we wouldn'ty
		     	have asked for more data.
			   See what space we have in the fifo, try reading as much as that,
			   and push it to the FIFO */
			if (FD_ISSET(c->tty_fd, &rfds)) {
				uint8_t buf[mii_ssc_cmd_fifo_fifo_size];
				int max = mii_ssc_fifo_get_write_size(&c->rx);
				int res = read(c->tty_fd, buf, max);
				if (res < 0) {
					if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
						break;
					c->tty_fd = -1;
					printf("%s ssc read: %s\n", __func__, strerror(errno));
					break;
				}
				for (int i = 0; i < res; i++)
					mii_ssc_fifo_write(&c->rx, buf[i]);
			}
			/* here as well, this wouldn't be set if we hadn't got stuff
				to send -- see what's in the fifo, 'peek' the bytes into
				an aligned buffer, try to write it all, and then *actually*
				remove them (read) the one that were sent from the fifo */
			if (FD_ISSET(c->tty_fd, &wfds)) {
				uint8_t buf[mii_ssc_cmd_fifo_fifo_size];
				int max = mii_ssc_fifo_get_read_size(&c->tx);
				for (int i = 0; i < max; i++)
					buf[i] = mii_ssc_fifo_read_at(&c->tx, i);
				res = write(c->tty_fd, buf, max);
				if (res < 0) {
					if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
						break;
					printf("%s ssc write: %s\n", __func__, strerror(errno));
					break;
				}
				// flush what we've just written
				while (res--)
					mii_ssc_fifo_read(&c->tx);
			}
		}
	} while (1);
	return NULL;
}

static void
_mii_ssc_thread_start(
		mii_card_ssc_t *c)
{
	if (c->state > MII_SSC_STATE_INIT && c->state < MII_SSC_STATE_STOP)
		return;
	if (c->tty_fd < 0) {
		printf("%s TTY not open, skip\n", __func__);
		return;
	}
	c->state = MII_SSC_STATE_START;
	mii_ssc_cmd_t cmd = { .cmd = MII_SSC_STATE_START, .card = c };
	mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd);
	// start timer that'll check out card status
	mii_timer_set(c->mii, c->timer_check, c->timer_delay);
	// start, or kick the thread awake
	if (!_mii_ssc_thread_id) {
		printf("%s: starting thread\n", __func__);
		pthread_create(&_mii_ssc_thread_id, NULL, _mii_ssc_thread, NULL);
	} else {
		pthread_kill(_mii_ssc_thread_id, SIGUSR1);
	}
}

/*
 * This is called when the CPU touches the CX00-CXFF ROM area, and we
 * need to install the secondary part of the ROM.
 * Also, if the access was from the ROM, we start the card as there's no
 * other way to know when the card is started -- we don't want to create
 * a thread fro SSC if the card is being ignored by the Apple IIe
 */
static bool
_mii_ssc_select(
		struct mii_bank_t *bank,
		void *param,
		uint16_t addr,
		uint8_t * byte,
		bool write)
{
	if (bank == NULL)	// this is normal, called on dispose
		return false;
	mii_card_ssc_t *c = param;
	if (c->slot->aux_rom_selected)
		return false;
	uint16_t pc = c->mii->cpu.PC;
	printf("SSC%d SELECT auxrom PC:%04x\n", c->slot->id+1, pc);
	/* Supports when the ROM starts prodding into the ROM */
	if (c->state != MII_SSC_STATE_RUNNING) {
		printf("SSC%d: start card from ROM poke? (PC $%04x)?\n",
				c->slot->id+1, pc);
		if ((pc & 0xff00) == (0xc100 + (c->slot->id << 8)) ||
				(pc >> 12) >= 0xc) {
			_mii_scc_set_conf(c, &c->conf, 1);
			_mii_ssc_thread_start(c);
		}
	}
	mii_bank_write(c->rom, 0xc800, mii_ssc_rom_data, 2048);
	c->slot->aux_rom_selected = true;
	return false;
}

/* Called a some sort of proportional number of cycles related to the
  baudrate to check the FIFOs and update the status/raise IRQs.
  this doesn't have to be exact, it just have to be often enough to
  not miss any data.
  */
static uint64_t
_mii_ssc_timer_cb(
		mii_t * mii,
		void * param )
{
	mii_card_ssc_t *c = param;
	// stop timer
	if (c->state != MII_SSC_STATE_RUNNING)
		return 0;

	// check the FIFOs -- not technically 'true' we raise an IRQ as soon as
	// theres some bytes to proceed, but we'll do it here for simplicity
	uint8_t rx_full = !mii_ssc_fifo_isempty(&c->rx);
	// what it really mean is 'there room for more data', not 'full'
	uint8_t tx_empty = !mii_ssc_fifo_isfull(&c->tx);
	uint8_t old = c->status;
	c->status = (c->status & ~(1 << SSC_6551_RX_FULL)) |
					(rx_full << SSC_6551_RX_FULL);
	c->status = (c->status & ~(1 << SSC_6551_TX_EMPTY)) |
					(tx_empty << SSC_6551_TX_EMPTY);
	uint8_t irq = 0;//(c->status & (1 << SSC_6551_IRQ));
	uint8_t t_irqen = ((c->command >> SSC_6551_COMMAND_IRQ_T) & 3) == 1;
	uint8_t r_irqen = !(c->command & (1 << SSC_6551_COMMAND_IRQ_R));

	if (old != c->status)
		printf("SSC%d New Status %08b RX:%2d TX:%2d t_irqen:%d r_irqen:%d\n",
			c->slot->id+1, c->status,
			mii_ssc_fifo_get_read_size(&c->rx), mii_ssc_fifo_get_write_size(&c->tx),
			t_irqen, r_irqen);
	// we set the IRQ flag even if the real IRQs are disabled.
	// rising edge triggers the IRQR
	if (!irq && rx_full) {
		// raise the IRQ
		if (r_irqen) {
			irq = 1;
			printf("SSC%d: IRQ RX\n", c->slot->id+1);
			mii->cpu_state.irq = 1;
		}
	}
	if (!irq && (tx_empty)) {
		// raise the IRQ
		if (t_irqen) {
			irq = 1;
			printf("SSC%d: IRQ TX\n", c->slot->id+1);
			mii->cpu_state.irq = 1;
		}
	}
	if (irq)
		c->status |= 1 << SSC_6551_IRQ;
	return c->timer_delay;
}

static int
_mii_scc_set_conf(
		mii_card_ssc_t *c,
		const mii_ssc_setconf_t *conf,
		int re_open)
{
	if (conf == NULL)
		conf = &_mii_ssc_default_conf;

	if (!re_open && strcmp(c->conf.device, conf->device) == 0 &&
			c->conf.baud == conf->baud &&
			c->conf.bits == conf->bits &&
			c->conf.parity == conf->parity &&
			c->conf.stop == conf->stop &&
			c->conf.handshake == conf->handshake &&
			c->conf.is_device == conf->is_device &&
			c->conf.is_socket == conf->is_socket &&
			c->conf.is_pty == conf->is_pty &&
			c->conf.socket_port == conf->socket_port)
		return 0;
	if (re_open || strcmp(c->conf.device, conf->device) != 0) {
		if (c->tty_fd > 0) {
			int tty = c->tty_fd;
			c->tty_fd = -1;
			// this will wake up the thread as well
			close(tty);
		}
	}
	c->conf = *conf;
	strcpy(c->tty_path, conf->device);
	if (c->tty_fd < 0) {
		int new_fd = -1;
		if (conf->is_pty) {
			int master = 0;
			int res = openpty(&master, &new_fd, c->tty_path, NULL, NULL);
			if (res < 0) {
				printf("SSC%d openpty: %s\n", c->slot->id+1, strerror(errno));
				return -1;
			}
		} else {
			int res = open(c->tty_path, O_RDWR | O_NOCTTY | O_NONBLOCK);
			if (res < 0) {
				printf("SSC%d open(%s): %s\n", c->slot->id+1,
						c->tty_path, strerror(errno));
				return -1;
			}
			new_fd = res;
		}
		// set non-blocking mode
		int flags = fcntl(new_fd, F_GETFL, 0);
		fcntl(new_fd, F_SETFL, flags | O_NONBLOCK);
		c->tty_fd = new_fd;
	}
	if (c->tty_fd < 0) {
		printf("SSC%d: %s TTY not open, skip\n", c->slot->id+1, __func__);
		return -1;
	}
	// get current terminal settings
	struct termios tio;
	tcgetattr(c->tty_fd, &tio);
	// set raw mode
	cfmakeraw(&tio);
	c->human_config[0] = 0;
	// set speed
	for (int i = 0; i < 16; i++) {
		if (_mii_ssc_to_baud_rate[i] == conf->baud) {
			c->dipsw1 = 0x80 | i;
			cfsetospeed(&tio, _mii_ssc_to_baud[i]);
			cfsetispeed(&tio, _mii_ssc_to_baud[i]);
			sprintf(c->human_config, "Baud:%d ", conf->baud);
			break;
		}
	}
	// set 8N1
	tio.c_cflag = (tio.c_cflag & ~PARENB) | _mii_ssc_to_parity[conf->parity];
	tio.c_cflag = (tio.c_cflag & ~CSTOPB) | _mii_ssc_to_stop[conf->stop];
	tio.c_cflag = (tio.c_cflag & ~CSIZE);

	tio.c_cflag |= _mii_ssc_to_bits[conf->bits];
	sprintf(c->human_config + strlen(c->human_config), "%d",
			_mii_scc_to_bits_count[conf->bits]);
	static const char *parity = "noeb";
	sprintf(c->human_config + strlen(c->human_config), "%c%c",
			parity[conf->parity], conf->stop ? '2' : '1');

	// Hardware Handshake
	tio.c_cflag = (tio.c_cflag & ~CRTSCTS) | (conf->handshake ? CRTSCTS : 0);
	// set the new settings
	tcsetattr(c->tty_fd, TCSANOW, &tio);
	c->dipsw1 = 0x80 | conf->baud;
	c->dipsw2 = SSC_SW2_IRQEN;
	c->control = 0;
	mii_ssc_fifo_reset(&c->rx);
	mii_ssc_fifo_reset(&c->tx);
	return 0;
}

static int
_mii_ssc_init(
		mii_t * mii,
		struct mii_slot_t *slot )
{
	mii_card_ssc_t *c = calloc(1, sizeof(*c));
	c->slot = slot;
	slot->drv_priv = c;
	c->mii = mii;

	c->slot_offset = slot->id + 1 + 0xc0;

	uint16_t addr = 0xc100 + (slot->id * 0x100);
	c->rom = &mii->bank[MII_BANK_CARD_ROM];
	mii_bank_write(c->rom, addr, mii_ssc_rom_data + 7*256, 256);
	/*
	 * install a callback that will be called for every access to the
	 * ROM area, we need this to re-install the secondary part of the ROM
	 * when the card 'slot' rom is accessed.
	 */
	mii_bank_install_access_cb(c->rom,
			_mii_ssc_select, c, addr >> 8, addr >> 8);

	/*
	 * And this is the timer that will check the status of FIFOs and update
	 * the status of the card, and raise IRQs if needed
	 */
	char name[32];
	snprintf(name, sizeof(name), "SSC %d", slot->id+1);
	c->timer_check = mii_timer_register(mii,
							_mii_ssc_timer_cb, c, 0, strdup(name));
	// fastest speed we could get to?
	c->timer_delay = 11520;
	c->tty_fd = -1;
	STAILQ_INSERT_TAIL(&_mii_card_ssc_slots, c, self);

	c->dipsw1 	= 0x80 | 14;		// communication mode, 9600
	// in case progs read that to decide to use IRQs or not
	c->dipsw2 	= SSC_SW2_IRQEN;
	c->state 	= MII_SSC_STATE_INIT;
	c->status 	= SSC_6551_STATUS_RESET;
	c->command 	= SSC_6551_COMMAND_RESET;
	c->control 	= 0;
	_mii_scc_set_conf(c, &c->conf, 1);

	return 0;
}

static void
_mii_ssc_dispose(
		mii_t * mii,
		struct mii_slot_t *slot )
{
	mii_card_ssc_t *c = slot->drv_priv;

	STAILQ_REMOVE(&_mii_card_ssc_slots, c, mii_card_ssc_t, self);
	if (c->state == MII_SSC_STATE_RUNNING) {
		mii_ssc_cmd_t cmd = { .cmd = MII_SSC_STATE_STOP, .card = c };
		mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd);
		pthread_kill(_mii_ssc_thread_id, SIGUSR1);
		while (c->state == MII_SSC_STATE_RUNNING)
			usleep(1000);
		printf("SSC%d: stopped\n", c->slot->id+1);
	}
	if (STAILQ_FIRST(&_mii_card_ssc_slots) == NULL && _mii_ssc_thread_id) {
		printf("SSC%d: stopping thread\n", c->slot->id+1);
		pthread_t id = _mii_ssc_thread_id;
		_mii_ssc_thread_id = 0;
		mii_ssc_cmd_t cmd = { .cmd = MII_THREAD_TERMINATE };
		mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd);
		pthread_kill(id, SIGUSR1);
		pthread_join(id, NULL);
		printf("SSC%d: thread stopped\n", c->slot->id+1);
	}
	free(c);
	slot->drv_priv = NULL;
}

static void
_mii_ssc_command_set(
		mii_card_ssc_t *c,
		uint8_t byte)
{
	mii_t * mii = c->mii;
	if (!(c->command & (1 << SSC_6551_COMMAND_DTR)) &&
			(byte & (1 << SSC_6551_COMMAND_DTR))) {
		_mii_scc_set_conf(c, &c->conf, 1);
		_mii_ssc_thread_start(c);
	}
	if (c->tty_fd < 0) {
		printf("SSC%d: %s TTY not open, skip\n", c->slot->id+1, __func__);
		return;
	}
	/* This triggers the IRQ if it enabled when there is a IRQ flag on,
	 * this make it behave more like a 'level' IRQ instead of an edge IRQ
	 */
	if ((c->command & (1 << SSC_6551_COMMAND_IRQ_R)) &&
			!(byte & (1 << SSC_6551_COMMAND_IRQ_R))) {
		if (c->status & (1 << SSC_6551_IRQ))
			mii->cpu_state.irq = 1;
	}
	int status;
	if (ioctl(c->tty_fd, TIOCMGET, &status) == -1) {
		printf("SSC%d: DTR/RTS: %s\n", c->slot->id+1, strerror(errno));
	}
	int old = status;
	status = (status & ~TIOCM_DTR) |
				((byte & (1 << SSC_6551_COMMAND_DTR)) ? TIOCM_DTR : 0);
	switch ((byte >> SSC_6551_COMMAND_IRQ_T) & 3) {
		case 0:	// IRQ_TX=0 + RTS=1
			status |= TIOCM_RTS;
			break;
		case 1:	// IRQ_TX=1 + RTS=0
			status &= ~TIOCM_RTS;
			break;
		case 2:	// IRQ_TX=0 + RTS=0
			status &= ~TIOCM_RTS;
			break;
		case 3:	// IRQ_TX=0 + RTS=0 + BRK
			status &= ~TIOCM_RTS;
			break;
	}
	if (old != status) {
		printf("%s%d: $%04x DTR %d RTS %d\n", __func__, c->slot->id+1,
				c->mii->cpu.PC,
				(status & TIOCM_DTR) ? 1 : 0,
				(status & TIOCM_RTS) ? 1 : 0);	// 0=on, 1=off
		if (ioctl(c->tty_fd, TIOCMSET, &status) == -1) {
			printf("SSC%d: DTR/RTS: %s\n", c->slot->id+1, strerror(errno));
		}
	}
	c->command = byte;
}

static uint8_t
_mii_ssc_access(
		mii_t * mii,
		struct mii_slot_t *slot,
		uint16_t addr,
		uint8_t byte,
		bool write)
{
	mii_card_ssc_t *c = slot->drv_priv;
	uint8_t res = 0;
	int psw = addr & 0x0F;

	switch (psw) {
		case 0x1: // DIPSW1
			if (!write) {
				printf("%s%d: $%04x read DIPSW1 : %02x\n",
						__func__, slot->id+1, mii->cpu.PC, c->dipsw1);
				res = c->dipsw1;
				/* this handle access by the ROM via PR#x and IN#x */
				if (c->state == MII_SSC_STATE_INIT &&
						(mii->cpu.PC & 0xff00) == 0xcb00)
					_mii_ssc_thread_start(c);
			}
			break;
		case 0x2: // DIPSW2
			if (!write) {
				printf("%s%d: $%04x read DIPSW2 : %02x\n",
						__func__, slot->id+1, mii->cpu.PC, c->dipsw2);
				res = c->dipsw2;
			}
			break;
		case 0x8: { // TD/RD
			if (c->state != MII_SSC_STATE_RUNNING)
				break;
			if (write) {
				bool tx_empty = mii_ssc_fifo_isempty(&c->tx);
			//	printf("%s: write %02x '%c'\n", __func__,
			//			byte, byte <= ' ' ? '.' : byte);
				c->total_tx++;
				mii_ssc_fifo_write(&c->tx, byte);
				if (tx_empty)	// wake thread if it's sleeping
					pthread_kill(_mii_ssc_thread_id, SIGUSR1);
				bool isfull = mii_ssc_fifo_isfull(&c->tx);
				if (isfull) {
					c->status &= ~(1 << SSC_6551_TX_EMPTY);
				}
			} else {
				if (mii_ssc_fifo_isempty(&c->rx)) {
					res = 0;
				} else {
					c->total_rx++;
					bool wasfull = mii_ssc_fifo_isfull(&c->rx);
					res = mii_ssc_fifo_read(&c->rx);
					bool isempty = mii_ssc_fifo_isempty(&c->rx);
					if (isempty) {
						c->status &= ~(1 << SSC_6551_RX_FULL);
					} else {
						if (wasfull)	// wake thread to read more
							pthread_kill(_mii_ssc_thread_id, SIGUSR1);
						// send another irq?
						uint8_t r_irqen =
									!(c->command & (1 << SSC_6551_COMMAND_IRQ_R));
						if (r_irqen) {
							mii->cpu_state.irq = 1;
						}
					}
				}
			}
		}	break;
		case 0x9: {// STATUS
			if (write) {
				printf("SSC%d: RESET requesdt\n",c->slot->id+1);
				_mii_ssc_command_set(c, 0x10);
				break;
			}
			res = c->status;
			// if it was set before, clear it.
			c->status &= ~(1 << SSC_6551_IRQ);
		}	break;
		case 0xa: {// COMMAND
			if (!write) {
				res = c->command;
				break;
			}
			_mii_ssc_command_set(c, byte);
		}	break;
		case 0xb: { // CONTROL
			if (!write) {
				res = c->control;
				break;
			}
			c->control = byte;
			struct termios tio;
			tcgetattr(c->tty_fd, &tio);
			// Update speed
			int baud = _mii_ssc_to_baud[c->control & 0x0F];
			cfsetospeed(&tio, baud);
			cfsetispeed(&tio, baud);
			// Update stop bits bit 7: 0 = 1 stop bit, 1 = 2 stop bits
			tio.c_cflag &= ~CSTOPB;
			tio.c_cflag |= _mii_ssc_to_stop[(c->control >> 7) & 1];
			// Update data bits bit 5-6 0=8 bits, 1=7 bits, 2=6 bits, 3=5 bits
			tio.c_cflag &= ~CSIZE;
			tio.c_cflag |= _mii_ssc_to_bits[(c->control >> 6) & 3];
			// parity are in c->command, bits 5-7,
			// 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
			tio.c_cflag &= ~PARENB;
			tio.c_cflag &= ~PARODD;
			tio.c_cflag |= _mii_ssc_to_parity[(c->command >> 5) & 3];
			tcsetattr(c->tty_fd, TCSANOW, &tio);
			printf("SSC%d: set %02x baud %d stop %d bits %d parity %d\n",
					c->slot->id+1, byte,
					_mii_ssc_to_baud_rate[c->control & 0x0F],
					(c->control >> 7) & 1 ? 2 : 1,
					_mii_scc_to_bits_count[(c->control >> 5) & 3],
					(c->command >> 5) & 3);
		}	break;
		default:
		//	printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
		//			mii->cpu.PC, addr, byte, write);
			break;
	}
	return res;
}

static int
_mii_ssc_command(
		mii_t * mii,
		struct mii_slot_t *slot,
		uint32_t cmd,
		void * param)
{
//	mii_card_ssc_t *c = slot->drv_priv;
	int res = -1;
	switch (cmd) {
		case MII_SLOT_SSC_SET_TTY: {
			const mii_ssc_setconf_t * conf = param;
			mii_card_ssc_t *c = slot->drv_priv;
			res = _mii_scc_set_conf(c, conf, 0);
			printf("SSC%d: set tty %s: %s\n",
					slot->id+1, conf->device, c->human_config);
		}	break;
	}
	return res;
}

static mii_slot_drv_t _driver = {
	.name = "ssc",
	.desc = "Super Serial card",
	.init = _mii_ssc_init,
	.dispose = _mii_ssc_dispose,
	.access = _mii_ssc_access,
	.command = _mii_ssc_command,
};
MI_DRIVER_REGISTER(_driver);


#include "mish.h"

static void
_mii_mish_ssc(
		void * param,
		int argc,
		const char * argv[])
{
	if (!argv[1] || !strcmp(argv[1], "status")) {
		mii_card_ssc_t *c;
		printf("SSC: cards:\n");
		STAILQ_FOREACH(c, &_mii_card_ssc_slots, self) {
			printf("SSC %d: %s FD: %2d path:%s %s\n", c->slot->id+1,
					c->state == MII_SSC_STATE_RUNNING ? "running" : "stopped",
					c->tty_fd, c->tty_path, c->human_config);
			// print FIFO status, fd status, registers etc
			printf("  RX: %2d/%2d TX: %2d/%2d -- total rx:%6d tx:%6d\n",
					mii_ssc_fifo_get_read_size(&c->rx),
					mii_ssc_fifo_get_write_size(&c->rx),
					mii_ssc_fifo_get_read_size(&c->tx),
					mii_ssc_fifo_get_write_size(&c->tx),
					c->total_rx, c->total_tx);
			printf("  DIPSW1: %08b DIPSW2: %08b\n", c->dipsw1, c->dipsw2);
			printf("  CONTROL: %08b COMMAND: %08b STATUS: %08b\n",
					c->control, c->command, c->status);
		}
		return;
	}
}

MISH_CMD_NAMES(_ssc, "ssc");
MISH_CMD_HELP(_ssc,
		"ssc: Super Serial internals",
		" <default>: dump status"
		);
MII_MISH(_ssc, _mii_mish_ssc);
