changeset 645:d77c79cec800

Initial support for configurable IO, custom IO and sega transfer board emulation
author Michael Pavone <pavone@retrodev.com>
date Wed, 03 Dec 2014 09:32:32 -0800
parents 2d7e84ae818c
children fa345ce3e5bd
files blastem.c default.cfg io.c io.h vos_program_module.o
diffstat 5 files changed, 464 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/blastem.c	Wed Dec 03 09:30:01 2014 -0800
+++ b/blastem.c	Wed Dec 03 09:32:32 2014 -0800
@@ -1289,7 +1289,7 @@
 	if (i < 0) {
 		strcpy(sram_filename + fname_size, ".sram");
 	}
-	set_keybindings();
+	set_keybindings(gen.ports);
 
 	init_run_cpu(&gen, address_log, statefile, debuggerfun);
 	return 0;
--- a/default.cfg	Wed Dec 03 09:30:01 2014 -0800
+++ b/default.cfg	Wed Dec 03 09:32:32 2014 -0800
@@ -54,6 +54,13 @@
 	}
 }
 
+io {
+	devices {
+		1 gamepad6.1
+		2 gamepad6.2
+	}
+}
+
 video {
 	width 640
 	vertex_shader default.v.glsl
--- a/io.c	Wed Dec 03 09:30:01 2014 -0800
+++ b/io.c	Wed Dec 03 09:32:32 2014 -0800
@@ -3,15 +3,44 @@
  This file is part of BlastEm.
  BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
 */
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+
 #include "io.h"
 #include "blastem.h"
 #include "render.h"
 
+const char * device_type_names[] = {
+	"3-button gamepad",
+	"6-button gamepad",
+	"Mega Mouse",
+	"Menacer",
+	"Justifier",
+	"Sega multi-tap",
+	"EA 4-way Play cable A",
+	"EA 4-way Play cable B",
+	"Sega Parallel Transfer Board",
+	"Generic Device",
+	"None"
+};
+
 enum {
 	BIND_NONE,
+	BIND_UI,
 	BIND_GAMEPAD1,
 	BIND_GAMEPAD2,
-	BIND_UI
+	BIND_GAMEPAD3,
+	BIND_GAMEPAD4,
+	BIND_GAMEPAD5,
+	BIND_GAMEPAD6,
+	BIND_GAMEPAD7,
+	BIND_GAMEPAD8
 };
 
 typedef enum {
@@ -26,6 +55,7 @@
 } ui_action;
 
 typedef struct {
+	io_port *port;
 	uint8_t bind_type;
 	uint8_t subtype_a;
 	uint8_t subtype_b;
@@ -117,7 +147,7 @@
 void bind_gamepad(int keycode, int gamepadnum, int button)
 {
 
-	if (gamepadnum < 1 || gamepadnum > 2) {
+	if (gamepadnum < 1 || gamepadnum > 8) {
 		return;
 	}
 	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
@@ -126,7 +156,7 @@
 
 void bind_button_gamepad(int joystick, int joybutton, int gamepadnum, int padbutton)
 {
-	if (gamepadnum < 1 || gamepadnum > 2) {
+	if (gamepadnum < 1 || gamepadnum > 8) {
 		return;
 	}
 	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
@@ -135,7 +165,7 @@
 
 void bind_dpad_gamepad(int joystick, int dpad, uint8_t direction, int gamepadnum, int button)
 {
-	if (gamepadnum < 1 || gamepadnum > 2) {
+	if (gamepadnum < 1 || gamepadnum > 8) {
 		return;
 	}
 	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
@@ -159,17 +189,14 @@
 
 void handle_binding_down(keybinding * binding)
 {
-	switch(binding->bind_type)
+	if (binding->bind_type >= BIND_GAMEPAD1)
 	{
-	case BIND_GAMEPAD1:
-	case BIND_GAMEPAD2:
-		if (binding->subtype_a <= GAMEPAD_EXTRA) {
-			genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_a] |= binding->value;
+		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
+			binding->port->input[binding->subtype_a] |= binding->value;
 		}
-		if (binding->subtype_b <= GAMEPAD_EXTRA) {
-			genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_b] |= binding->value;
+		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
+			binding->port->input[binding->subtype_b] |= binding->value;
 		}
-		break;
 	}
 }
 
@@ -206,11 +233,11 @@
 	{
 	case BIND_GAMEPAD1:
 	case BIND_GAMEPAD2:
-		if (binding->subtype_a <= GAMEPAD_EXTRA) {
-			genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_a] &= ~binding->value;
+		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
+			binding->port->input[binding->subtype_a] &= ~binding->value;
 		}
-		if (binding->subtype_b <= GAMEPAD_EXTRA) {
-			genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_b] &= ~binding->value;
+		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
+			binding->port->input[binding->subtype_b] &= ~binding->value;
 		}
 		break;
 	case BIND_UI:
@@ -447,7 +474,169 @@
 	}
 }
 
-void set_keybindings()
+void process_device(char * device_type, io_port * port)
+{
+	port->device_type = IO_NONE;
+	if (!device_type)
+	{
+		return;
+	}
+
+	const int gamepad_len = strlen("gamepad");
+	if (!memcmp(device_type, "gamepad", gamepad_len))
+	{
+		if (
+			(device_type[gamepad_len] != '3' && device_type[gamepad_len] != '6')
+			|| device_type[gamepad_len+1] != '.' || device_type[gamepad_len+2] < '1'
+			|| device_type[gamepad_len+2] > '8' || device_type[gamepad_len+3] != 0
+		)
+		{
+			fprintf(stderr, "%s is not a valid gamepad type\n", device_type);
+		} else if (device_type[gamepad_len] == '3')
+		{
+			port->device_type = IO_GAMEPAD3;
+		} else {
+			port->device_type = IO_GAMEPAD6;
+		}
+		port->device.pad.gamepad_num = device_type[gamepad_len+2] - '1';
+	} else if(!strcmp(device_type, "sega_parallel")) {
+		port->device_type = IO_SEGA_PARALLEL;
+		port->device.stream.data_fd = -1;
+		port->device.stream.listen_fd = -1;
+	} else if(!strcmp(device_type, "generic")) {
+		port->device_type = IO_GENERIC;
+		port->device.stream.data_fd = -1;
+		port->device.stream.listen_fd = -1;
+	}
+}
+
+char * io_name(int i)
+{
+	switch (i)
+	{
+	case 0:
+		return "1";
+	case 1:
+		return "2";
+	case 2:
+		return "EXT";
+	default:
+		return "invalid";
+	}
+}
+
+static char * sockfile_name;
+static void cleanup_sockfile()
+{
+	unlink(sockfile_name);
+}
+
+void setup_io_devices(tern_node * config, io_port * ports)
+{
+	tern_node *io_nodes = tern_find_prefix(config, "iodevices");
+	char * io_1 = tern_find_ptr(io_nodes, "1");
+	char * io_2 = tern_find_ptr(io_nodes, "2");
+	char * io_ext = tern_find_ptr(io_nodes, "ext");
+
+	process_device(io_1, ports);
+	process_device(io_2, ports+1);
+	process_device(io_ext, ports+2);
+
+	for (int i = 0; i < 3; i++)
+	{
+
+		if (ports[i].device_type == IO_SEGA_PARALLEL)
+		{
+			char *pipe_name = tern_find_ptr(config, "ioparallel_pipe");
+			if (!pipe_name)
+			{
+				fprintf(stderr, "IO port %s is configured to use the sega parallel board, but no paralell_pipe is set!\n", io_name(i));
+				ports[i].device_type = IO_NONE;
+			} else {
+				printf("IO port: %s connected to device '%s' with pipe name: %s\n", io_name(i), device_type_names[ports[i].device_type], pipe_name);
+				if (!strcmp("stdin", pipe_name))
+				{
+					ports[i].device.stream.data_fd = STDIN_FILENO;
+				} else {
+					if (mkfifo(pipe_name, 0666) && errno != EEXIST)
+					{
+						fprintf(stderr, "Failed to create fifo %s for Sega parallel board emulation: %d %s\n", pipe_name, errno, strerror(errno));
+						ports[i].device_type = IO_NONE;
+					} else {
+						ports[i].device.stream.data_fd = open(pipe_name, O_NONBLOCK | O_RDONLY);
+						if (ports[i].device.stream.data_fd == -1)
+						{
+							fprintf(stderr, "Failed to open fifo %s for Sega parallel board emulation: %d %s\n", pipe_name, errno, strerror(errno));
+							ports[i].device_type = IO_NONE;
+						}
+					}
+				}
+			}
+		} else if (ports[i].device_type == IO_GENERIC) {
+			char *sock_name = tern_find_ptr(config, "iosocket");
+			if (!sock_name)
+			{
+				fprintf(stderr, "IO port %s is configured to use generic IO, but no socket is set!\n", io_name(i));
+				ports[i].device_type = IO_NONE;
+			} else {
+				printf("IO port: %s connected to device '%s' with socket name: %s\n", io_name(i), device_type_names[ports[i].device_type], sock_name);
+				ports[i].device.stream.data_fd = -1;
+				ports[i].device.stream.listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+				size_t pathlen = strlen(sock_name);
+				size_t addrlen = offsetof(struct sockaddr_un, sun_path) + pathlen + 1;
+				struct sockaddr_un *saddr = malloc(addrlen);
+				saddr->sun_family = AF_UNIX;
+				memcpy(saddr->sun_path, sock_name, pathlen+1);
+				if (bind(ports[i].device.stream.listen_fd, (struct sockaddr *)saddr, addrlen))
+				{
+					fprintf(stderr, "Failed to bind socket for IO Port %s to path %s: %d %s\n", io_name(i), sock_name, errno, strerror(errno));
+					goto cleanup_sock;
+				}
+				if (listen(ports[i].device.stream.listen_fd, 1))
+				{
+					fprintf(stderr, "Failed to listen on socket for IO Port %s: %d %s\n", io_name(i), errno, strerror(errno));
+					goto cleanup_sockfile;
+				}
+				sockfile_name = sock_name;
+				atexit(cleanup_sockfile);
+				continue;
+cleanup_sockfile:
+				unlink(sock_name);
+cleanup_sock:
+				close(ports[i].device.stream.listen_fd);
+				ports[i].device_type = IO_NONE;
+			}
+		} else if (ports[i].device_type == IO_GAMEPAD3 || ports[i].device_type == IO_GAMEPAD6) {
+			printf("IO port %s connected to gamepad #%d with type '%s'\n", io_name(i), ports[i].device.pad.gamepad_num + 1, device_type_names[ports[i].device_type]);
+		} else {
+			printf("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]);
+		}
+	}
+}
+
+void map_bindings(io_port *ports, keybinding *bindings, int numbindings)
+{
+	for (int i = 0; i < numbindings; i++)
+	{
+		if (bindings[i].bind_type >= BIND_GAMEPAD1)
+		{
+			int num = bindings[i].bind_type - BIND_GAMEPAD1;
+			for (int j = 0; j < 3; j++)
+			{
+				if ((ports[j].device_type == IO_GAMEPAD3
+					 || ports[j].device_type ==IO_GAMEPAD6)
+					 && ports[j].device.pad.gamepad_num == num
+				)
+				{
+					bindings[i].port = ports + j;
+					break;
+				}
+			}
+		}
+	}
+}
+
+void set_keybindings(io_port *ports)
 {
 	tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP);
 	special = tern_insert_int(special, "down", RENDERKEY_DOWN);
@@ -532,76 +721,245 @@
 	speeds = malloc(sizeof(uint32_t));
 	speeds[0] = 100;
 	process_speeds(speed_nodes, NULL);
-	for (int i = 0; i < num_speeds; i++) {
+	for (int i = 0; i < num_speeds; i++)
+	{
 		if (!speeds[i]) {
 			fprintf(stderr, "Speed index %d was not set to a valid percentage!", i);
 			speeds[i] = 100;
 		}
 	}
+	for (int bucket = 0; bucket < 256; bucket++)
+	{
+		if (bindings[bucket])
+		{
+			map_bindings(ports, bindings[bucket], 256);
+		}
+	}
+	for (int stick = 0; stick < MAX_JOYSTICKS; stick++)
+	{
+		if (joybindings[stick])
+		{
+			int numbuttons = render_joystick_num_buttons(stick);
+			map_bindings(ports, joybindings[stick], render_joystick_num_buttons(stick));
+		}
+		if (joydpads[stick])
+		{
+			map_bindings(ports, joydpads[stick]->bindings, 4);
+		}
+	}
 }
 
 #define TH 0x40
 #define TH_TIMEOUT 8000
 
-void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction)
+void io_adjust_cycles(io_port * port, uint32_t current_cycle, uint32_t deduction)
 {
 	/*uint8_t control = pad->control | 0x80;
 	uint8_t th = control & pad->output;
 	if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
 		printf("adjust_cycles | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, pad->input[GAMEPAD_TH0], pad->input[GAMEPAD_TH1], pad->th_counter,pad->timeout_cycle, current_cycle);
 	}*/
-	if (current_cycle >= pad->timeout_cycle) {
-		pad->th_counter = 0;
-	} else {
-		pad->timeout_cycle -= deduction;
+	if (port->device_type == IO_GAMEPAD6)
+	{
+		if (current_cycle >= port->device.pad.timeout_cycle)
+		{
+			port->device.pad.th_counter = 0;
+		} else {
+			port->device.pad.timeout_cycle -= deduction;
+		}
+	}
+}
+
+static void wait_for_connection(io_port * port)
+{
+	if (port->device.stream.data_fd == -1)
+	{
+		puts("Waiting for socket connection...");
+		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)
+{
+	uint8_t value;
+	int numRead = read(port->device.stream.data_fd, &value, sizeof(value));
+	if (numRead > 0)
+	{
+		port->input[IO_TH0] = (value & 0xF) | 0x10;
+		port->input[IO_TH1] = (value >> 4) | 0x10;
+	} else if(numRead == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
+		fprintf(stderr, "Error reading pipe for IO port: %d %s\n", errno, strerror(errno));
 	}
 }
 
-void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle)
+static void service_socket(io_port *port)
 {
-	if (pad->control & TH) {
-		//check if TH has changed
-		if ((pad->output & TH) ^ (value & TH)) {
-			if (current_cycle >= pad->timeout_cycle) {
-				pad->th_counter = 0;
+	uint8_t buf[32];
+	uint8_t blocking = 0;
+	int numRead = 0;
+	while (numRead <= 0)
+	{
+		numRead = recv(port->device.stream.data_fd, buf, sizeof(buf), 0);
+		if (numRead > 0)
+		{
+			port->input[IO_TH0] = buf[numRead-1];
+			if (port->input[IO_STATE] == IO_READ_PENDING)
+			{
+				port->input[IO_STATE] = IO_READ;
+				if (blocking)
+				{
+					//pending read satisfied, back to non-blocking mode
+					fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR | O_NONBLOCK);
+				}
+			} else if (port->input[IO_STATE] == IO_WRITTEN) {
+				port->input[IO_STATE] = IO_READ;
 			}
-			if (!(value & TH)) {
-				pad->th_counter++;
+		} else if (numRead == 0) {
+			port->device.stream.data_fd = -1;
+			wait_for_connection(port);
+		} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
+			fprintf(stderr, "Error reading from socket for IO port: %d %s\n", errno, strerror(errno));
+			close(port->device.stream.data_fd);
+			wait_for_connection(port);
+		} else if (port->input[IO_STATE] == IO_READ_PENDING) {
+			//clear the nonblocking flag so the next read will block
+			if (!blocking)
+			{
+				fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR);
+				blocking = 1;
 			}
-			pad->timeout_cycle = current_cycle + TH_TIMEOUT;
+		} else {
+			//no new data, but that's ok
+			break;
 		}
 	}
-	pad->output = value;
+
+	if (port->input[IO_STATE] == IO_WRITE_PENDING)
+	{
+		uint8_t value = port->output & port->control;
+		int written = 0;
+		blocking = 0;
+		while (written <= 0)
+		{
+			send(port->device.stream.data_fd, &value, sizeof(value), 0);
+			if (written > 0)
+			{
+				port->input[IO_STATE] = IO_WRITTEN;
+				if (blocking)
+				{
+					//pending write satisfied, back to non-blocking mode
+					fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR | O_NONBLOCK);
+				}
+			} else if (written == 0) {
+				port->device.stream.data_fd = -1;
+				wait_for_connection(port);
+			} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
+				fprintf(stderr, "Error writing to socket for IO port: %d %s\n", errno, strerror(errno));
+				close(port->device.stream.data_fd);
+				wait_for_connection(port);
+			} else {
+				//clear the nonblocking flag so the next write will block
+				if (!blocking)
+				{
+					fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR);
+					blocking = 1;
+				}
+			}
+		}
+	}
 }
 
-uint8_t io_data_read(io_port * pad, uint32_t current_cycle)
+void io_data_write(io_port * port, uint8_t value, uint32_t current_cycle)
 {
-	uint8_t control = pad->control | 0x80;
-	uint8_t th = control & pad->output;
+	switch (port->device_type)
+	{
+	case IO_GAMEPAD6:
+		if (port->control & TH) {
+			//check if TH has changed
+			if ((port->output & TH) ^ (value & TH)) {
+				if (current_cycle >= port->device.pad.timeout_cycle) {
+					port->device.pad.th_counter = 0;
+				}
+				if (!(value & TH)) {
+					port->device.pad.th_counter++;
+				}
+				port->device.pad.timeout_cycle = current_cycle + TH_TIMEOUT;
+			}
+		}
+		port->output = value;
+		break;
+	case IO_GENERIC:
+		wait_for_connection(port);
+		port->input[IO_STATE] = IO_WRITE_PENDING;
+		port->output = value;
+		service_socket(port);
+		break;
+	default:
+		port->output = value;
+	}
+
+}
+
+uint8_t io_data_read(io_port * port, uint32_t current_cycle)
+{
+	uint8_t control = port->control | 0x80;
+	uint8_t th = control & port->output & 0x40;
 	uint8_t input;
-	if (current_cycle >= pad->timeout_cycle) {
-		pad->th_counter = 0;
+	switch (port->device_type)
+	{
+	case IO_GAMEPAD3:
+	{
+		input = port->input[th ? GAMEPAD_TH1 : GAMEPAD_TH0];
+		break;
 	}
-	/*if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
-		printf("io_data_read | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, pad->input[GAMEPAD_TH0], pad->input[GAMEPAD_TH1], pad->th_counter,pad->timeout_cycle, context->current_cycle);
-	}*/
-	if (th) {
-		if (pad->th_counter == 3) {
-			input = pad->input[GAMEPAD_EXTRA];
+	case IO_GAMEPAD6:
+	{
+		if (current_cycle >= port->device.pad.timeout_cycle) {
+			port->device.pad.th_counter = 0;
+		}
+		/*if (port->input[GAMEPAD_TH0] || port->input[GAMEPAD_TH1]) {
+			printf("io_data_read | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, port->input[GAMEPAD_TH0], port->input[GAMEPAD_TH1], port->th_counter,port->timeout_cycle, context->current_cycle);
+		}*/
+		if (th) {
+			if (port->device.pad.th_counter == 3) {
+				input = port->input[GAMEPAD_EXTRA];
+			} else {
+				input = port->input[GAMEPAD_TH1];
+			}
 		} else {
-			input = pad->input[GAMEPAD_TH1];
+			if (port->device.pad.th_counter == 3) {
+				input = port->input[GAMEPAD_TH0] | 0xF;
+			} else if(port->device.pad.th_counter == 4) {
+				input = port->input[GAMEPAD_TH0]  & 0x30;
+			} else {
+				input = port->input[GAMEPAD_TH0] | 0xC;
+			}
 		}
-	} else {
-		if (pad->th_counter == 3) {
-			input = pad->input[GAMEPAD_TH0] | 0xF;
-		} else if(pad->th_counter == 4) {
-			input = pad->input[GAMEPAD_TH0]  & 0x30;
-		} else {
-			input = pad->input[GAMEPAD_TH0] | 0xC;
+		break;
+	}
+	case IO_SEGA_PARALLEL:
+		if (!th)
+		{
+			service_pipe(port);
 		}
+		input = ~port->input[th ? IO_TH1 : IO_TH0];
+		break;
+	case IO_GENERIC:
+		if (port->input[IO_TH0] & 0x80 && port->input[IO_STATE] == IO_WRITTEN)
+		{
+			//device requested a blocking read after writes
+			port->input[IO_STATE] = IO_READ_PENDING;
+		}
+		service_socket(port);
+		input = ~port->input[IO_TH0];
+		break;
+	default:
+		input = 0;
+		break;
 	}
-	uint8_t value = ((~input) & (~control)) | (pad->output & control);
-	/*if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
+	uint8_t value = ((~input) & (~control)) | (port->output & control);
+	/*if (port->input[GAMEPAD_TH0] || port->input[GAMEPAD_TH1]) {
 		printf ("value: %X\n", value);
 	}*/
 	return value;
--- a/io.h	Wed Dec 03 09:30:01 2014 -0800
+++ b/io.h	Wed Dec 03 09:32:32 2014 -0800
@@ -1,18 +1,43 @@
 /*
  Copyright 2013 Michael Pavone
- This file is part of BlastEm. 
+ This file is part of BlastEm.
  BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
 */
 #ifndef IO_H_
 #define IO_H_
 #include <stdint.h>
+#include "tern.h"
+
+enum {
+	IO_GAMEPAD3,
+	IO_GAMEPAD6,
+	IO_MOUSE,
+	IO_MENACER,
+	IO_JUSTIFIER,
+	IO_SEGA_MULTI,
+	IO_EA_MULTI_A,
+	IO_EA_MULTI_B,
+	IO_SEGA_PARALLEL,
+	IO_GENERIC,
+	IO_NONE
+};
 
 typedef struct {
-	uint32_t th_counter;
-	uint32_t timeout_cycle;
-	uint8_t output;
-	uint8_t control;
-	uint8_t input[3];
+	union {
+		struct {
+			uint32_t timeout_cycle;
+			uint16_t th_counter;
+			uint16_t gamepad_num;
+		} pad;
+		struct {
+			int data_fd;
+			int listen_fd;
+		} stream;
+	} device;
+	uint8_t  output;
+	uint8_t  control;
+	uint8_t  input[3];
+	uint8_t  device_type;
 } io_port;
 
 #define GAMEPAD_TH0 0
@@ -20,7 +45,19 @@
 #define GAMEPAD_EXTRA 2
 #define GAMEPAD_NONE 0xF
 
-void set_keybindings();
+#define IO_TH0 0
+#define IO_TH1 1
+#define IO_STATE 2
+
+enum {
+	IO_WRITE_PENDING,
+	IO_WRITTEN,
+	IO_READ_PENDING,
+	IO_READ
+};
+
+void set_keybindings(io_port *ports);
+void setup_io_devices(tern_node * config, io_port * ports);
 void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction);
 void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle);
 uint8_t io_data_read(io_port * pad, uint32_t current_cycle);
Binary file vos_program_module.o has changed