libMeatloaf/lib/bus/iec/iec.h
2024-01-08 11:58:15 -06:00

583 lines
13 KiB
C++

#ifndef IEC_H
#define IEC_H
// This code uses code from the Meatloaf Project:
// Meatloaf - A Commodore 64/128 multi-device emulator
// https://github.com/idolpx/meatloaf
// Copyright(C) 2020 James Johnston
//
// Meatloaf 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.
//
// Meatloaf 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 Meatloaf. If not, see <http://www.gnu.org/licenses/>.
//
// https://www.pagetable.com/?p=1135
// http://unusedino.de/ec64/technical/misc/c1541/romlisting.html#E85B
// https://eden.mose.org.uk/gitweb/?p=rom-reverse.git;a=blob;f=src/vic-1541-sfd.asm;hb=HEAD
// https://www.pagetable.com/docs/Inside%20Commodore%20DOS.pdf
// http://www.ffd2.com/fridge/docs/1541dis.html#E853
// http://unusedino.de/ec64/technical/aay/c1541/
// http://unusedino.de/ec64/technical/aay/c1581/
//
#include <cstdint>
#include <forward_list>
// #include <freertos/FreeRTOS.h>
// #include <freertos/queue.h>
#include <utility>
#include <string>
#include <map>
#include <queue>
#include <memory>
//#include <driver/gpio.h>
//#include "fnSystem.h"
// #include "protocol/_protocol.h"
// #include "protocol/jiffydos.h"
// #ifdef MEATLOAF_MAX
// #include "protocol/saucedos.h"
// #endif
// #ifdef PARALLEL_BUS
// #include "protocol/dolphindos.h"
// #endif
//#include <soc/gpio_reg.h>
#include "../../../include/debug.h"
#include "../../../include/cbm_defines.h"
//#include "../../../include/pinmap.h"
/**
* @brief The command frame
*/
union cmdFrame_t
{
struct
{
uint8_t device;
uint8_t comnd;
uint8_t aux1;
uint8_t aux2;
uint8_t cksum;
};
struct
{
uint32_t commanddata;
uint8_t checksum;
} __attribute__((packed));
};
// Return values for service:
typedef enum
{
BUS_OFFLINE = -3, // Bus is empty
BUS_RESET = -2, // The bus is in a reset state (RESET line).
BUS_ERROR = -1, // A problem occoured, reset communication
BUS_IDLE = 0, // Nothing recieved of our concern
BUS_ACTIVE = 1, // ATN is pulled and a command byte is expected
BUS_PROCESS = 2, // A command is ready to be processed
} bus_state_t;
/**
* @enum bus command
*/
typedef enum
{
IEC_GLOBAL = 0x00, // 0x00 + cmd (global command)
IEC_LISTEN = 0x20, // 0x20 + device_id (LISTEN) (0-30)
IEC_UNLISTEN = 0x3F, // 0x3F (UNLISTEN)
IEC_TALK = 0x40, // 0x40 + device_id (TALK) (0-30)
IEC_UNTALK = 0x5F, // 0x5F (UNTALK)
IEC_REOPEN = 0x60, // 0x60 + channel (OPEN CHANNEL) (0-15)
IEC_REOPEN_JD = 0x61, // 0x61 + channel (OPEN CHANNEL) (0-15) - JIFFYDOS LOAD
IEC_CLOSE = 0xE0, // 0xE0 + channel (CLOSE NAMED CHANNEL) (0-15)
IEC_OPEN = 0xF0 // 0xF0 + channel (OPEN NAMED CHANNEL) (0-15)
} bus_command_t;
typedef enum
{
DEVICE_ERROR = -1,
DEVICE_IDLE = 0, // Ready and waiting
DEVICE_ACTIVE = 1,
DEVICE_LISTEN = 2, // A command is recieved and data is coming to us
DEVICE_TALK = 3, // A command is recieved and we must talk now
DEVICE_PROCESS = 4, // Execute device command
} device_state_t;
typedef enum {
PROTOCOL_SERIAL,
PROTOCOL_FAST_SERIAL,
PROTOCOL_SAUCEDOS,
PROTOCOL_JIFFYDOS,
PROTOCOL_EPYXFASTLOAD,
PROTOCOL_WARPSPEED,
PROTOCOL_SPEEDDOS,
PROTOCOL_DOLPHINDOS,
PROTOCOL_WIC64,
PROTOCOL_IEEE488
} bus_protocol_t;
//using namespace Protocol;
/**
* @class IECData
* @brief the IEC command data passed to devices
*/
class IECData
{
public:
/**
* @brief the primary command byte
*/
uint8_t primary = 0;
/**
* @brief the primary device number
*/
uint8_t device = 0;
/**
* @brief the secondary command byte
*/
uint8_t secondary = 0;
/**
* @brief the secondary command channel
*/
uint8_t channel = 0;
/**
* @brief the device command
*/
std::string payload = "";
/**
* @brief clear and initialize IEC command data
*/
void init(void)
{
primary = 0;
device = 0;
secondary = 0;
channel = 0;
payload = "";
}
};
/**
* @class Forward declaration of System Bus
*/
class systemBus;
/**
* @class virtualDevice
* @brief All #FujiNet devices derive from this.
*/
class virtualDevice
{
private:
protected:
friend systemBus; /* Because we connect to it. */
/**
* @brief The device number (ID)
*/
int _devnum;
/**
* @brief The passed in command frame, copied.
*/
cmdFrame_t cmdFrame;
/**
* @brief The current device command
*/
std::string payload;
/**
* @brief The current device command in raw PETSCII. Used when payload is converted to ASCII for Basic commands
*/
std::string payloadRaw;
/**
* @brief pointer to the current command data
*/
IECData commanddata;
/**
* @brief current device state.
*/
device_state_t device_state;
/**
* @brief response queue (e.g. INPUT)
* @deprecated remove as soon as it's out of fuji.
*/
std::queue<std::string> response_queue;
/**
* @brief status override (for binary commands)
*/
std::string status_override;
/**
* @brief tokenized payload
*/
std::vector<std::string> pt;
std::vector<uint8_t> pti;
/**
* @brief The status information to send back on cmd input
* @param bw = # of bytes waiting
* @param msg = most recent status message
* @param connected = is most recent channel connected?
* @param channel = channel of most recent status msg.
*/
struct _iecStatus
{
uint8_t error;
std::string msg;
bool connected;
int channel;
} iecStatus;
/**
* @brief Get device ready to handle next phase of command.
*/
device_state_t queue_command(const IECData &data)
{
commanddata = data;
if (commanddata.primary == IEC_LISTEN)
device_state = DEVICE_LISTEN;
else if (commanddata.primary == IEC_TALK)
device_state = DEVICE_TALK;
return device_state;
}
/**
* @brief All IEC devices repeatedly call this routine to fan out to other methods for each command.
* This is typcially implemented as a switch() statement.
* @return new device state.
*/
virtual device_state_t process();
/**
* @brief poll whether interrupt should be wiggled
* @param c secondary channel (0-15)
*/
virtual void poll_interrupt(unsigned char c) {}
/**
* @brief Dump the current IEC frame to terminal.
*/
void dumpData();
/**
* @brief If response queue is empty, Return 1 if ANY receive buffer has data in it, else 0
*/
virtual void iec_talk_command_buffer_status();
// Optional shutdown/reboot cleanup routine
virtual void shutdown(){};
public:
/**
* @brief get the IEC device Number (1-31)
* @return The device number registered for this device
*/
int id() { return _devnum; };
/**
* @brief Is this virtualDevice holding the virtual disk drive used to boot CONFIG?
*/
bool is_config_device = false;
/**
* @brief is device active (turned on?)
*/
bool device_active = true;
/**
* The spinlock for the ESP32 hardware timers. Used for interrupt rate limiting.
*/
// portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
/**
* @brief Get the systemBus object that this virtualDevice is attached to.
*/
systemBus get_bus();
};
/**
* @class systemBus
* @brief the system bus that all virtualDevices attach to.
*/
class systemBus
{
private:
/**
* @brief The chain of devices on the bus.
*/
std::forward_list<virtualDevice *> _daisyChain;
/**
* @brief Number of devices on bus
*/
int _num_devices = 0;
/**
* @brief the active device being process()'ed
*/
virtualDevice *_activeDev = nullptr;
/**
* @brief is device shutting down?
*/
bool shuttingDown = false;
/**
* @brief the detected bus protocol
*/
bus_protocol_t detected_protocol = PROTOCOL_SERIAL; // default is IEC Serial
/**
* @brief the active bus protocol
*/
std::shared_ptr<std::string> protocol = nullptr;
/**
* @brief Switch to detected bus protocol
*/
std::shared_ptr<std::string> selectProtocol();
/**
* IEC LISTEN received
*/
void deviceListen();
/**
* IEC TALK requested
*/
void deviceTalk();
/**
* BUS TURNAROUND (switch from listener to talker)
*/
bool turnAround();
/**
* @brief called to process the next command
*/
void process_cmd();
/**
* @brief called to process a queue item (such as disk swap)
*/
void process_queue();
/**
* @brief called to read bus command bytes
*/
void read_command();
/**
* @brief called to read bus payload bytes
*/
void read_payload();
/**
* ESP timer handle for the Interrupt rate limiting timer
*/
// esp_timer_handle_t rateTimerHandle = nullptr;
/**
* Timer Rate for interrupt timer
*/
int timerRate = 100;
/**
* @brief Start the Interrupt rate limiting timer
*/
void timer_start();
/**
* @brief Stop the Interrupt rate limiting timer
*/
void timer_stop();
public:
/**
* @brief bus flags
*/
uint16_t flags = CLEAR;
/**
* @brief current bus state
*/
bus_state_t bus_state;
/**
* Toggled by the rate limiting timer to indicate that the SRQ interrupt should
* be pulsed.
*/
bool interruptSRQ = false;
/**
* @brief data about current bus transaction
*/
IECData data;
/**
* @brief Enabled device bits
*/
uint32_t enabledDevices;
/**
* @brief called in main.cpp to set up the bus.
*/
void setup();
/**
* @brief Run one iteration of the bus service loop
*/
void service();
/**
* @brief Called to pulse the PROCEED interrupt, rate limited by the interrupt timer.
*/
void assert_interrupt();
/**
* @brief Release the bus lines, we're done.
*/
void releaseLines(bool wait = false);
/**
* @brief Set 2bit fast loader pair timing
* @param set Send 's', Receive 'r'
* @param p1 Pair 1
* @param p2 Pair 2
* @param p3 Pair 3
* @param p4 Pair 4
*/
void setBitTiming(std::string set, int p1 = 0, int p2 = 0, int p3 = 0, int p4 = 0);
/**
* @brief send single byte
* @param c byte to send
* @param eoi Send EOI?
* @return true on success, false on error
*/
bool sendByte(const char c, bool eoi = false);
/**
* @brief Send bytes to bus
* @param buf buffer to send
* @param len length of buffer
* @param eoi Send EOI?
* @return true on success, false on error
*/
bool sendBytes(const char *buf, size_t len, bool eoi = false);
/**
* @brief Send string to bus
* @param s std::string to send
* @param eoi Send EOI?
* @return true on success, false on error
*/
bool sendBytes(std::string s, bool eoi = false);
/**
* @brief Receive Byte from bus
* @return Byte received from bus, or -1 for error
*/
int8_t receiveByte();
/**
* @brief Receive String from bus
* @return std::string received from bus
*/
std::string receiveBytes();
/**
* @brief called in response to RESET pin being asserted.
*/
void reset_all_our_devices();
/**
* @brief called from main shutdown to clean up the device.
*/
void shutdown();
/**
* @brief Return number of devices on bus.
* @return # of devices on bus.
*/
int numDevices() { return _num_devices; };
/**
* @brief Add device to bus.
* @param pDevice Pointer to virtualDevice
* @param device_id The ID to assign to virtualDevice
*/
void addDevice(virtualDevice *pDevice, int device_id);
/**
* @brief Remove device from bus
* @param pDevice pointer to virtualDevice
*/
void remDevice(virtualDevice *pDevice);
/**
* @brief Check if device is enabled
* @param deviceNumber The device ID to check
*/
bool isDeviceEnabled ( const uint8_t device_id );
/**
* @brief Return pointer to device given ID
* @param device_id ID of device to return.
* @return pointer to virtualDevice
*/
virtualDevice *deviceById(int device_id);
/**
* @brief Change ID of a particular virtualDevice
* @param pDevice pointer to virtualDevice
* @param device_id new device ID
*/
void changeDeviceId(virtualDevice *pDevice, int device_id);
/**
* @brief Are we shutting down?
* @return value of shuttingDown
*/
bool getShuttingDown() { return shuttingDown; }
/**
* @brief signal to bus that we timed out.
*/
void senderTimeout();
void pull ( int _pin );
void release ( int _pin );
bool status ( int _pin );
//int status();
};
/**
* @brief Return
*/
extern systemBus IEC;
#endif /* IEC_H */