changeset 2025:e7a516f08cec

Implement serial IO, a generic serial device type and external interrupts
author Michael Pavone <pavone@retrodev.com>
date Wed, 10 Feb 2021 20:12:16 -0800
parents 380bc5d4a2cf
children aa338eb0eded
files genesis.c io.c io.h vdp.h
diffstat 4 files changed, 232 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/genesis.c	Tue Nov 24 23:15:25 2020 -0800
+++ b/genesis.c	Wed Feb 10 20:12:16 2021 -0800
@@ -234,13 +234,14 @@
 		context->sync_cycle = context->current_cycle + gen->max_cycles;
 	}
 	context->int_cycle = CYCLE_NEVER;
-	if ((context->status & 0x7) < 6) {
+	uint8_t mask = context->status & 0x7;
+	if (mask < 6) {
 		uint32_t next_vint = vdp_next_vint(v_context);
 		if (next_vint != CYCLE_NEVER) {
 			context->int_cycle = next_vint;
 			context->int_num = 6;
 		}
-		if ((context->status & 0x7) < 4) {
+		if (mask < 4) {
 			uint32_t next_hint = vdp_next_hint(v_context);
 			if (next_hint != CYCLE_NEVER) {
 				next_hint = next_hint < context->current_cycle ? context->current_cycle : next_hint;
@@ -250,6 +251,21 @@
 
 				}
 			}
+			if (mask < 2 && (v_context->regs[REG_MODE_3] & BIT_EINT_EN)) {
+				uint32_t next_eint_port0 = io_next_interrupt(gen->io.ports, context->current_cycle);
+				uint32_t next_eint_port1 = io_next_interrupt(gen->io.ports + 1, context->current_cycle);
+				uint32_t next_eint_port2 = io_next_interrupt(gen->io.ports + 2, context->current_cycle);
+				uint32_t next_eint = next_eint_port0 < next_eint_port1 
+					? (next_eint_port0 < next_eint_port2 ? next_eint_port0 : next_eint_port2)
+					: (next_eint_port1 < next_eint_port2 ? next_eint_port1 : next_eint_port2);
+				if (next_eint != CYCLE_NEVER) {
+					next_eint = next_eint < context->current_cycle ? context->current_cycle : next_eint;
+					if (next_eint < context->int_cycle) {
+						context->int_cycle = next_eint;
+						context->int_num = 2;
+					}
+				}
+			}
 		}
 	}
 	if (context->int_cycle > context->current_cycle && context->int_pending == INT_PENDING_SR_CHANGE) {
@@ -379,6 +395,9 @@
 	sync_z80(z_context, mclks);
 	sync_sound(gen, mclks);
 	vdp_run_context(v_context, mclks);
+	io_run(gen->io.ports, mclks);
+	io_run(gen->io.ports + 1, mclks);
+	io_run(gen->io.ports + 2, mclks);
 	if (mclks >= gen->reset_cycle) {
 		gen->reset_requested = 1;
 		context->should_return = 1;
@@ -793,7 +812,7 @@
 				io_control_write(gen->io.ports+2, value, context->current_cycle);
 				break;
 			case 0x7:
-				gen->io.ports[0].serial_out = value;
+				io_tx_write(gen->io.ports, value, context->current_cycle);
 				break;
 			case 0x8:
 			case 0xB:
@@ -801,19 +820,20 @@
 				//serial input port is not writeable
 				break;
 			case 0x9:
+				io_sctrl_write(gen->io.ports, value, context->current_cycle);
 				gen->io.ports[0].serial_ctrl = value;
 				break;
 			case 0xA:
-				gen->io.ports[1].serial_out = value;
+				io_tx_write(gen->io.ports + 1, value, context->current_cycle);
 				break;
 			case 0xC:
-				gen->io.ports[1].serial_ctrl = value;
+				io_sctrl_write(gen->io.ports + 1, value, context->current_cycle);
 				break;
 			case 0xD:
-				gen->io.ports[2].serial_out = value;
+				io_tx_write(gen->io.ports + 2, value, context->current_cycle);
 				break;
 			case 0xF:
-				gen->io.ports[2].serial_ctrl = value;
+				io_sctrl_write(gen->io.ports + 2, value, context->current_cycle);
 				break;
 			}
 		} else {
@@ -949,28 +969,28 @@
 				value = gen->io.ports[0].serial_out;
 				break;
 			case 0x8:
-				value = gen->io.ports[0].serial_in;
+				value = io_rx_read(gen->io.ports, context->current_cycle);
 				break;
 			case 0x9:
-				value = gen->io.ports[0].serial_ctrl;
+				value = io_sctrl_read(gen->io.ports, context->current_cycle);
 				break;
 			case 0xA:
 				value = gen->io.ports[1].serial_out;
 				break;
 			case 0xB:
-				value = gen->io.ports[1].serial_in;
+				value = io_rx_read(gen->io.ports + 1, context->current_cycle);
 				break;
 			case 0xC:
-				value = gen->io.ports[1].serial_ctrl;
+				value = io_sctrl_read(gen->io.ports, context->current_cycle);
 				break;
 			case 0xD:
 				value = gen->io.ports[2].serial_out;
 				break;
 			case 0xE:
-				value = gen->io.ports[2].serial_in;
+				value = io_rx_read(gen->io.ports + 1, context->current_cycle);
 				break;
 			case 0xF:
-				value = gen->io.ports[2].serial_ctrl;
+				value = io_sctrl_read(gen->io.ports, context->current_cycle);
 				break;
 			default:
 				value = get_open_bus_value(&gen->header) >> 8;
--- a/io.c	Tue Nov 24 23:15:25 2020 -0800
+++ b/io.c	Wed Feb 10 20:12:16 2021 -0800
@@ -39,7 +39,8 @@
 	"EA 4-way Play cable A",
 	"EA 4-way Play cable B",
 	"Sega Parallel Transfer Board",
-	"Generic Device"
+	"Generic Device",
+	"Generic Serial"
 };
 
 #define GAMEPAD_TH0 0
@@ -210,8 +211,20 @@
 	return find_keyboard(io) != NULL;
 }
 
+static void set_serial_clock(io_port *port)
+{
+	switch(port->serial_ctrl >> 6)
+	{
+	case 0: port->serial_divider = 11186; break; //4800 bps
+	case 1: port->serial_divider = 22372; break; //2400 bps
+	case 2: port->serial_divider = 44744; break; //1200 bps
+	case 3: port->serial_divider = 178976; break; //300 bps
+	}
+}
+
 void process_device(char * device_type, io_port * port)
 {
+	set_serial_clock(port);
 	//assuming that the io_port struct has been zeroed if this is the first time this has been called
 	if (!device_type)
 	{
@@ -272,6 +285,12 @@
 			port->device.stream.data_fd = -1;
 			port->device.stream.listen_fd = -1;
 		}
+	} else if(!strcmp(device_type, "serial")) {
+		if (port->device_type != IO_GENERIC_SERIAL) {
+			port->device_type = IO_GENERIC_SERIAL;
+			port->device.stream.data_fd = -1;
+			port->device.stream.listen_fd = -1;
+		}
 	}
 }
 
@@ -354,7 +373,7 @@
 					}
 				}
 			}
-		} else if (ports[i].device_type == IO_GENERIC && ports[i].device.stream.data_fd == -1) {
+		} else if (ports[i].device_type == IO_GENERIC || ports[i].device_type == IO_GENERIC_SERIAL && ports[i].device.stream.data_fd == -1) {
 			char *sock_name = tern_find_path(config, "io\0socket\0", TVAL_PTR).ptrval;
 			if (!sock_name)
 			{
@@ -427,7 +446,6 @@
 	}
 }
 
-uint32_t last_poll_cycle;
 void io_adjust_cycles(io_port * port, uint32_t current_cycle, uint32_t deduction)
 {
 	/*uint8_t control = pad->control | 0x80;
@@ -459,25 +477,80 @@
 			}
 		}
 	}
-	if (last_poll_cycle >= deduction) {
-		last_poll_cycle -= deduction;
+	if (port->transmit_end >= deduction) {
+		port->transmit_end -= deduction;
 	} else {
-		last_poll_cycle = 0;
+		port->transmit_end = 0;
+	}
+	if (port->receive_end >= deduction) {
+		port->receive_end -= deduction;
+	} else {
+		port->receive_end = 0;
+	}
+	if (port->last_poll_cycle >= deduction) {
+		port->last_poll_cycle -= deduction;
+	} else {
+		port->last_poll_cycle = 0;
 	}
 }
 
 #ifndef _WIN32
-static void wait_for_connection(io_port * port)
+static void wait_for_connection(io_port *port)
 {
 	if (port->device.stream.data_fd == -1)
 	{
-		debug_message("Waiting for socket connection...");
+		debug_message("Waiting for socket connection...\n");
 		port->device.stream.data_fd = accept(port->device.stream.listen_fd, NULL, NULL);
 		fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR);
 	}
 }
 
-static void service_pipe(io_port * port)
+static void poll_for_connection(io_port *port)
+{
+	if (port->device.stream.data_fd == -1)
+	{
+		fcntl(port->device.stream.listen_fd, F_SETFL, O_NONBLOCK | O_RDWR);
+		port->device.stream.data_fd = accept(port->device.stream.listen_fd, NULL, NULL);
+		fcntl(port->device.stream.listen_fd, F_SETFL, O_RDWR);
+		if (port->device.stream.data_fd != -1) {
+			fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR);
+		}
+	}
+}
+
+static void write_serial_byte(io_port *port)
+{
+	fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR);
+	for (int sent = 0; sent != sizeof(port->serial_transmitting);)
+	{
+		sent = send(port->device.stream.data_fd, &port->serial_transmitting, sizeof(port->serial_transmitting), 0);
+		if (sent < 0) {
+			close(port->device.stream.data_fd);
+			port->device.stream.data_fd = -1;
+			wait_for_connection(port);
+			fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR);
+		}
+	}
+	fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR);
+}
+
+static void read_serial_byte(io_port *port)
+{
+	poll_for_connection(port);
+	if (port->device.stream.data_fd == -1) {
+		return;
+	}
+	int read = recv(port->device.stream.data_fd, &port->serial_receiving, sizeof(port->serial_receiving), 0);
+	if (read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
+		close(port->device.stream.data_fd);
+		port->device.stream.data_fd = -1;
+	}
+	if (read > 0) {
+		port->receive_end = port->serial_cycle + 10 * port->serial_divider;
+	}
+}
+
+static void service_pipe(io_port *port)
 {
 	uint8_t value;
 	int numRead = read(port->device.stream.data_fd, &value, sizeof(value));
@@ -576,6 +649,61 @@
 	KB_WRITE
 };
 
+enum {
+	SCTRL_BIT_TX_FULL = 1,
+	SCTRL_BIT_RX_READY = 2,
+	SCTRL_BIT_RX_ERROR = 4,
+	SCTRL_BIT_RX_INTEN = 8,
+	SCTRL_BIT_TX_ENABLE = 0x10,
+	SCTRL_BIT_RX_ENABLE = 0x20
+};
+
+void io_run(io_port *port, uint32_t current_cycle)
+{
+	uint32_t new_serial_cycle = ((current_cycle - port->serial_cycle) / port->serial_divider) * port->serial_divider + port->serial_cycle;
+	if (port->transmit_end && port->transmit_end <= new_serial_cycle) {
+		port->transmit_end = 0;
+		
+		if (port->serial_ctrl & SCTRL_BIT_TX_ENABLE) {
+			switch (port->device_type)
+			{
+#ifndef _WIN32
+			case IO_GENERIC_SERIAL:
+				write_serial_byte(port);
+				break;
+#endif
+			//TODO: think about how serial mode might interact with non-serial peripherals
+			}
+		}
+	}
+	if (!port->transmit_end && new_serial_cycle != port->serial_cycle && (port->serial_ctrl & SCTRL_BIT_TX_FULL)) {
+		//there's a transmit byte pending and no byte is currently being sent
+		port->serial_transmitting = port->serial_out;
+		port->serial_ctrl &= ~SCTRL_BIT_TX_FULL;
+		//1 start bit, 8 data bits and 1 stop bit
+		port->transmit_end = new_serial_cycle + 10 * port->serial_divider;
+	}
+	port->serial_cycle = new_serial_cycle;
+	if (port->serial_ctrl && SCTRL_BIT_RX_ENABLE) {
+		if (port->receive_end && new_serial_cycle >= port->receive_end) {
+			port->serial_in = port->serial_receiving;
+			port->serial_ctrl |= SCTRL_BIT_RX_READY;
+			port->receive_end = 0;
+		}
+		if (!port->receive_end) {
+			switch(port->device_type)
+			{
+#ifndef _WIN32
+			case IO_GENERIC_SERIAL:
+				read_serial_byte(port);
+				break;
+#endif
+			//TODO: think about how serial mode might interact with non-serial peripherals
+			}
+		}
+	}
+}
+
 void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle)
 {
 	uint8_t changes = value ^ port->control;
@@ -723,6 +851,20 @@
 
 }
 
+void io_tx_write(io_port *port, uint8_t value, uint32_t current_cycle)
+{
+	io_run(port, current_cycle);
+	port->serial_out = value;
+	port->serial_ctrl |= SCTRL_BIT_TX_FULL;
+}
+
+void io_sctrl_write(io_port *port, uint8_t value, uint32_t current_cycle)
+{
+	io_run(port, current_cycle);
+	port->serial_ctrl = (port->serial_ctrl & 0x7) | (value & 0xF8);
+	set_serial_clock(port);
+}
+
 uint8_t get_scancode_bytes(io_port *port)
 {
 	if (port->device.keyboard.read_pos == 0xFF) {
@@ -766,9 +908,9 @@
 	uint8_t th = output & 0x40;
 	uint8_t input;
 	uint8_t device_driven;
-	if (current_cycle - last_poll_cycle > MIN_POLL_INTERVAL) {
+	if (current_cycle - port->last_poll_cycle > MIN_POLL_INTERVAL) {
 		process_events();
-		last_poll_cycle = current_cycle;
+		port->last_poll_cycle = current_cycle;
 	}
 	switch (port->device_type)
 	{
@@ -1049,6 +1191,36 @@
 	return value;
 }
 
+uint8_t io_rx_read(io_port * port, uint32_t current_cycle)
+{
+	io_run(port, current_cycle);
+	port->serial_ctrl &= ~SCTRL_BIT_RX_READY;
+	return port->serial_in;
+}
+
+uint8_t io_sctrl_read(io_port *port, uint32_t current_cycle)
+{
+	io_run(port, current_cycle);
+	return port->serial_ctrl;
+}
+
+uint32_t io_next_interrupt(io_port *port, uint32_t current_cycle)
+{
+	if (!(port->control & 0x80)) {
+		return CYCLE_NEVER;
+	}
+	if (port->serial_ctrl & SCTRL_BIT_RX_INTEN) {
+		if (port->serial_ctrl & SCTRL_BIT_RX_READY) {
+			return current_cycle;
+		}
+		if ((port->serial_ctrl & SCTRL_BIT_RX_ENABLE) && port->receive_end) {
+			return port->receive_end;
+		}
+	}
+	//TODO: handle external interrupts from TH transitions
+	return CYCLE_NEVER;
+}
+
 void io_serialize(io_port *port, serialize_buffer *buf)
 {
 	save_int8(buf, port->output);
--- a/io.h	Tue Nov 24 23:15:25 2020 -0800
+++ b/io.h	Wed Feb 10 20:12:16 2021 -0800
@@ -24,7 +24,8 @@
 	IO_EA_MULTI_A,
 	IO_EA_MULTI_B,
 	IO_SEGA_PARALLEL,
-	IO_GENERIC
+	IO_GENERIC,
+	IO_GENERIC_SERIAL
 };
 
 typedef struct {
@@ -62,8 +63,15 @@
 	uint8_t  control;
 	uint8_t  input[3];
 	uint32_t slow_rise_start[8];
+	uint32_t serial_cycle;
+	uint32_t serial_divider;
+	uint32_t last_poll_cycle;
+	uint32_t transmit_end;
+	uint32_t receive_end;
 	uint8_t  serial_out;
+	uint8_t  serial_transmitting;
 	uint8_t  serial_in;
+	uint8_t  serial_receiving;
 	uint8_t  serial_ctrl;
 	uint8_t  device_type;
 } io_port;
@@ -106,9 +114,15 @@
 
 void setup_io_devices(tern_node * config, rom_info *rom, sega_io *io);
 void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction);
+void io_run(io_port *port, uint32_t current_cycle);
 void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle);
 void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle);
+void io_tx_write(io_port *port, uint8_t value, uint32_t current_cycle);
+void io_sctrl_write(io_port *port, uint8_t value, uint32_t current_cycle);
 uint8_t io_data_read(io_port * pad, uint32_t current_cycle);
+uint8_t io_rx_read(io_port * port, uint32_t current_cycle);
+uint8_t io_sctrl_read(io_port *port, uint32_t current_cycle);
+uint32_t io_next_interrupt(io_port *port, uint32_t current_cycle);
 void io_serialize(io_port *port, serialize_buffer *buf);
 void io_deserialize(deserialize_buffer *buf, void *vport);
 
--- a/vdp.h	Tue Nov 24 23:15:25 2020 -0800
+++ b/vdp.h	Wed Feb 10 20:12:16 2021 -0800
@@ -118,7 +118,7 @@
 #define BIT_SPRITE_SZ  0x02
 
 //Mode reg 3
-#define BIT_EINT_EN    0x10
+#define BIT_EINT_EN    0x08
 #define BIT_VSCROLL    0x04
 
 //Mode reg 4