Source

gb_emulator / gb_emulator / include / gb_emulator / gb_comms_serial.h

/*  Copyright Š 2011 Chris Spencer <spencercw@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#ifndef GB_COMMS_SERIAL_H_1F334C00_1F91_11E1_B524_0002A5D5C51B
#define GB_COMMS_SERIAL_H_1F334C00_1F91_11E1_B524_0002A5D5C51B

#include <stdint.h>

#include <string>

#include <boost/asio/ip/tcp.hpp>
#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals/connection.hpp>

#include <gb_net/types.h>

namespace boost {
	namespace asio {
		class io_service;
	}
	namespace system {
		class error_code;
	}
}

namespace gbnet {
	class Networking;
}

class Gb;

//! Game Boy serial communications emulator.
class GbCommsSerial
{
public:
	//! Constructor.
	/**
	 * No remote connections are possible when using this constructor.
	 * \param gb The emulator context.
	 * \param cycles Parameter which may be set at any time to indicate when poll() should be
	 * called. \c numeric_limits<int>::max() is used as a sentinel value to mean poll() should not
	 * be called. This value should be decremented every time a CPU cycle is executed and when it
	 * reaches zero poll() should be called. Reset the value to numeric_limits<int>::max() before
	 * calling poll().
	 */
	GbCommsSerial(Gb &gb, int &cycles);

	//! Constructor; starts a serial server on the given TCP port.
	/**
	 * \param gb The emulator context.
	 * \param cycles See GbCommsSerial(Gb &, int &) for details on this parameter.
	 * \param ioService The I/O service to use for communications.
	 * \param port The TCP port to listen from remote connections on.
	 */
	GbCommsSerial(Gb &gb, int &cycles, boost::asio::io_service &ioService, uint16_t port);

	//! Constructor; starts a serial client and connections to the given server specification.
	/**
	 * \param gb The emulator context.
	 * \param cycles See GbCommsSerial(Gb &, int &) for details on this parameter.
	 * \param ioService The I/O service to use for communications.
	 * \param host The hostname or IP address (may be IPv4 or IPv6 where the operating system
	 * supports it) of the remote server.
	 * \param port The TCP port the server is listening on.
	 */
	GbCommsSerial(Gb &gb, int &cycles, boost::asio::io_service &ioService, const std::string &host,
		uint16_t port);

	//! Resets the serial comms emulator to its default state.
	void reset();

	//! Handles events that occur after a period of time.
	/**
	 * This should be called when the \c cycles parameter passed to the constructor reaches zero.
	 */
	void poll();

	//! Handles a write to a serial comms register.
	/**
	 * \param ptr The lower eight bits of the register address (the upper eight bits are always
	 * 0xff).
	 */
	void writeIoPort(uint8_t ptr, uint8_t val);

	//! If running as a server, sends a synchronisation pulse to the remote client.
	/**
	 * This causes the client to perform a frame's worth of emulation in lockstep with this
	 * emulator.
	 */
	void synchronise();

private:
	// Possible states for the serial comms emulator to be in
	enum State
	{
		// No transfer is active
		STATE_IDLE,
		// An internal clock request has been made, the transfer duration has not yet elapsed and no
		// data has been received
		STATE_INT_WAIT_NO_DATA,
		// An internal clock request has been made, the transfer duration has not yet elapsed and a
		// response has been received
		STATE_INT_WAIT_DATA,
		// An internal clock request has been made and the transfer duration has elapsed, but no
		// response has been received yet. The emulator has been paused pending the arrival of the
		// response
		STATE_INT_WAIT_PAUSED,
		// An external clock request has been made, the transfer duration has not yet elapsed and no
		// data has been received
		STATE_EXT_WAIT_NO_DATA,
		// An external clock request has been made, the transfer duration has not yet elapsed and a
		// response has been received
		STATE_EXT_WAIT_DATA,
		// An external clock request has been made, the transfer duration has not yet elapsed, a
		// response has been received and a new internal clock request has been received which the
		// program is not ready to handle
		STATE_EXT_WAIT_DATA_WITH_DELAY,
		// An external clock request has been made and the transfer duration has elapsed, but no
		// response has been received yet. The emulator is still running
		STATE_EXT_WAIT_TIMEOUT,
		// An internal clock request has been received, but the program is not ready to handle it
		STATE_INT_DELAY
	};

#pragma pack(push)
#pragma pack(1)

	// Message structure used for network communications
	struct SerialDataMessage
	{
		// Message identifiers
		enum Type
		{
			// Internal clock message. The remote host is expected to reply with a TYPE_EXT or
			// TYPE_EXT_INACTIVE message
			TYPE_INT = 0xbe00,
			TYPE_EXT,
			// External clock message. This is sent in reply to a TYPE_INT message.
			TYPE_DATA,
			// External clock message. This is sent in reply to a TYPE_INT message when the local
			// program is not requesting any transfer. The data member is unused in this kind of
			// message.
			TYPE_INACTIVE,
			// Maximum type value
			TYPE_MAX
		};

		// Message sequence number. This is used to match request and response messages
		uint8_t sequence;
		// Data value
		uint8_t data;
	};

#pragma pack(pop)

	// The emulator context
	Gb &gb_;

	// Parameter used to control when poll() is called
	int &cycles_;

	// The current state of the serial comms emulator
	State state_;

	// Indicates whether we are connected to another emulator
	bool connected_;

	// Indicates whether we are running as a client or server
	bool server_;

	// Copy of the received packet when an internal clock request is received before the game is
	// ready for it
	boost::optional<SerialDataMessage> intDelay_;

	// Current line state. This is set to bit zero of the last transmitted byte. This is used to
	// determine the value sent when an internal clock request is received but no external clock
	// transfer is active.
	bool lineState_;

	// The networking interface if we are using it
	boost::shared_ptr<gbnet::Networking> net_;

	// Current sequence number for network messages
	uint8_t sequence_;

	// Data being transmitted in an external clock transmission, or the data just received but not
	// yet passed to the program.
	uint8_t data_;

	// Connects the networking callbacks below to net_
	void connectNetworkSignals();

	// Networking callbacks
	boost::signals::scoped_connection onListeningConnection_;
	boost::signals::scoped_connection onConnectedConnection_;
	boost::signals::scoped_connection onMessageReceivedConnection_;
	boost::signals::scoped_connection onBadPacketConnection_;
	boost::signals::scoped_connection onNetworkErrorConnection_;
	
	void onListening(const boost::asio::ip::tcp::endpoint &endpoint);
	void onConnected(const boost::asio::ip::tcp::endpoint &endpoint);
	void onMessageReceived(uint16_t identifier, const std::basic_string<uint8_t> &message);
	void onBadPacket();
	void onNetworkError(gbnet::ErrorType type, const boost::system::error_code &ec);

	// Disabled operations
	GbCommsSerial(const GbCommsSerial &);
	GbCommsSerial & operator=(const GbCommsSerial &);
};

#endif