mirror of
https://github.com/Oibaf66/frodo-wii.git
synced 2024-11-26 13:34:22 +01:00
431 lines
7.4 KiB
C++
431 lines
7.4 KiB
C++
|
/*
|
||
|
* IEC.cpp - IEC bus routines, 1541 emulation (DOS level)
|
||
|
*
|
||
|
* Frodo (C) 1994-1997,2002 Christian Bauer
|
||
|
*
|
||
|
|
||
|
*
|
||
|
* Notes:
|
||
|
* ------
|
||
|
*
|
||
|
* - There are three kinds of devices on the IEC bus: controllers,
|
||
|
* listeners and talkers. We are always the controller and we
|
||
|
* can additionally be either listener or talker. There can be
|
||
|
* only one listener and one talker active at the same time (the
|
||
|
* real IEC bus allows multiple listeners, but we don't).
|
||
|
* - There is one Drive object for every emulated drive (8..11).
|
||
|
* A pointer to one of them is stored in "listener"/"talker"
|
||
|
* when talk()/listen() is called and is used by the functions
|
||
|
* called afterwards.
|
||
|
* - The Drive objects have four virtual functions so that the
|
||
|
* interface to them is independent of their implementation:
|
||
|
* Open() opens a channel
|
||
|
* Close() closes a channel
|
||
|
* Read() reads from a channel
|
||
|
* Write() writes to a channel
|
||
|
* - The EOI/EOF signal is special on the IEC bus in that it is
|
||
|
* Sent before the last byte, not after it.
|
||
|
*/
|
||
|
|
||
|
#include "sysdeps.h"
|
||
|
|
||
|
#include "IEC.h"
|
||
|
#include "1541fs.h"
|
||
|
#include "1541d64.h"
|
||
|
#include "1541t64.h"
|
||
|
#include "Prefs.h"
|
||
|
#include "Display.h"
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Constructor: Initialize variables
|
||
|
*/
|
||
|
|
||
|
IEC::IEC(C64Display *display) : the_display(display)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
// Create drives 8..11
|
||
|
for (i=0; i<4; i++)
|
||
|
drive[i] = NULL; // Important because UpdateLEDs is called from the drive constructors (via set_error)
|
||
|
|
||
|
if (!ThePrefs.Emul1541Proc)
|
||
|
for (i=0; i<4; i++) {
|
||
|
if (ThePrefs.DriveType[i] == DRVTYPE_DIR)
|
||
|
drive[i] = new FSDrive(this, ThePrefs.DrivePath[i]);
|
||
|
else if (ThePrefs.DriveType[i] == DRVTYPE_D64)
|
||
|
drive[i] = new D64Drive(this, ThePrefs.DrivePath[i]);
|
||
|
else
|
||
|
drive[i] = new T64Drive(this, ThePrefs.DrivePath[i]);
|
||
|
}
|
||
|
|
||
|
listener_active = talker_active = false;
|
||
|
listening = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Destructor: Delete drives
|
||
|
*/
|
||
|
|
||
|
IEC::~IEC()
|
||
|
{
|
||
|
for (int i=0; i<4; i++)
|
||
|
delete drive[i];
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Reset all drives
|
||
|
*/
|
||
|
|
||
|
void IEC::Reset(void)
|
||
|
{
|
||
|
for (int i=0; i<4; i++)
|
||
|
if (drive[i] != NULL && drive[i]->Ready)
|
||
|
drive[i]->Reset();
|
||
|
|
||
|
UpdateLEDs();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Preferences have changed, prefs points to new preferences,
|
||
|
* ThePrefs still holds the previous ones. Check if drive settings
|
||
|
* have changed.
|
||
|
*/
|
||
|
|
||
|
void IEC::NewPrefs(Prefs *prefs)
|
||
|
{
|
||
|
// Delete and recreate all changed drives
|
||
|
for (int i=0; i<4; i++)
|
||
|
if ((ThePrefs.DriveType[i] != prefs->DriveType[i]) || strcmp(ThePrefs.DrivePath[i], prefs->DrivePath[i]) || ThePrefs.Emul1541Proc != prefs->Emul1541Proc) {
|
||
|
delete drive[i];
|
||
|
drive[i] = NULL; // Important because UpdateLEDs is called from drive constructors (via set_error())
|
||
|
if (!prefs->Emul1541Proc) {
|
||
|
if (prefs->DriveType[i] == DRVTYPE_DIR)
|
||
|
drive[i] = new FSDrive(this, prefs->DrivePath[i]);
|
||
|
else if (prefs->DriveType[i] == DRVTYPE_D64)
|
||
|
drive[i] = new D64Drive(this, prefs->DrivePath[i]);
|
||
|
else
|
||
|
drive[i] = new T64Drive(this, prefs->DrivePath[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UpdateLEDs();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Update drive LED display
|
||
|
*/
|
||
|
|
||
|
void IEC::UpdateLEDs(void)
|
||
|
{
|
||
|
if (drive[0] != NULL && drive[1] != NULL && drive[2] != NULL && drive[3] != NULL)
|
||
|
the_display->UpdateLEDs(drive[0]->LED, drive[1]->LED, drive[2]->LED, drive[3]->LED);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Output one byte
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::Out(uint8 byte, bool eoi)
|
||
|
{
|
||
|
if (listener_active) {
|
||
|
if (received_cmd == CMD_OPEN)
|
||
|
return open_out(byte, eoi);
|
||
|
if (received_cmd == CMD_DATA)
|
||
|
return data_out(byte, eoi);
|
||
|
return ST_TIMEOUT;
|
||
|
} else
|
||
|
return ST_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Output one byte with ATN (Talk/Listen/Untalk/Unlisten)
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::OutATN(uint8 byte)
|
||
|
{
|
||
|
received_cmd = sec_addr = 0; // Command is sent with secondary address
|
||
|
switch (byte & 0xf0) {
|
||
|
case ATN_LISTEN:
|
||
|
listening = true;
|
||
|
return listen(byte & 0x0f);
|
||
|
case ATN_UNLISTEN:
|
||
|
listening = false;
|
||
|
return unlisten();
|
||
|
case ATN_TALK:
|
||
|
listening = false;
|
||
|
return talk(byte & 0x0f);
|
||
|
case ATN_UNTALK:
|
||
|
listening = false;
|
||
|
return untalk();
|
||
|
}
|
||
|
return ST_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Output secondary address
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::OutSec(uint8 byte)
|
||
|
{
|
||
|
if (listening) {
|
||
|
if (listener_active) {
|
||
|
sec_addr = byte & 0x0f;
|
||
|
received_cmd = byte & 0xf0;
|
||
|
return sec_listen();
|
||
|
}
|
||
|
} else {
|
||
|
if (talker_active) {
|
||
|
sec_addr = byte & 0x0f;
|
||
|
received_cmd = byte & 0xf0;
|
||
|
return sec_talk();
|
||
|
}
|
||
|
}
|
||
|
return ST_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Read one byte
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::In(uint8 *byte)
|
||
|
{
|
||
|
if (talker_active && (received_cmd == CMD_DATA))
|
||
|
return data_in(byte);
|
||
|
|
||
|
*byte = 0;
|
||
|
return ST_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Assert ATN (for Untalk)
|
||
|
*/
|
||
|
|
||
|
void IEC::SetATN(void)
|
||
|
{
|
||
|
// Only needed for real IEC
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Release ATN
|
||
|
*/
|
||
|
|
||
|
void IEC::RelATN(void)
|
||
|
{
|
||
|
// Only needed for real IEC
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Talk-attention turn-around
|
||
|
*/
|
||
|
|
||
|
void IEC::Turnaround(void)
|
||
|
{
|
||
|
// Only needed for real IEC
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* System line release
|
||
|
*/
|
||
|
|
||
|
void IEC::Release(void)
|
||
|
{
|
||
|
// Only needed for real IEC
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Listen
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::listen(int device)
|
||
|
{
|
||
|
if ((device >= 8) && (device <= 11)) {
|
||
|
if ((listener = drive[device-8]) != NULL && listener->Ready) {
|
||
|
listener_active = true;
|
||
|
return ST_OK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
listener_active = false;
|
||
|
return ST_NOTPRESENT;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Talk
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::talk(int device)
|
||
|
{
|
||
|
if ((device >= 8) && (device <= 11)) {
|
||
|
if ((talker = drive[device-8]) != NULL && talker->Ready) {
|
||
|
talker_active = true;
|
||
|
return ST_OK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
talker_active = false;
|
||
|
return ST_NOTPRESENT;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Unlisten
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::unlisten(void)
|
||
|
{
|
||
|
listener_active = false;
|
||
|
return ST_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Untalk
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::untalk(void)
|
||
|
{
|
||
|
talker_active = false;
|
||
|
return ST_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Secondary address after Listen
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::sec_listen(void)
|
||
|
{
|
||
|
switch (received_cmd) {
|
||
|
|
||
|
case CMD_OPEN: // Prepare for receiving the file name
|
||
|
name_ptr = name_buf;
|
||
|
name_len = 0;
|
||
|
return ST_OK;
|
||
|
|
||
|
case CMD_CLOSE: // Close channel
|
||
|
if (listener->LED != DRVLED_ERROR) {
|
||
|
listener->LED = DRVLED_OFF; // Turn off drive LED
|
||
|
UpdateLEDs();
|
||
|
}
|
||
|
return listener->Close(sec_addr);
|
||
|
}
|
||
|
return ST_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Secondary address after Talk
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::sec_talk(void)
|
||
|
{
|
||
|
return ST_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Byte after Open command: Store character in file name, open file on EOI
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::open_out(uint8 byte, bool eoi)
|
||
|
{
|
||
|
if (name_len < NAMEBUF_LENGTH) {
|
||
|
*name_ptr++ = byte;
|
||
|
name_len++;
|
||
|
}
|
||
|
|
||
|
if (eoi) {
|
||
|
*name_ptr = 0; // End string
|
||
|
listener->LED = DRVLED_ON; // Turn on drive LED
|
||
|
UpdateLEDs();
|
||
|
return listener->Open(sec_addr, name_buf);
|
||
|
}
|
||
|
|
||
|
return ST_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Write byte to channel
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::data_out(uint8 byte, bool eoi)
|
||
|
{
|
||
|
return listener->Write(sec_addr, byte, eoi);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Read byte from channel
|
||
|
*/
|
||
|
|
||
|
uint8 IEC::data_in(uint8 *byte)
|
||
|
{
|
||
|
return talker->Read(sec_addr, byte);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Drive constructor
|
||
|
*/
|
||
|
|
||
|
Drive::Drive(IEC *iec)
|
||
|
{
|
||
|
the_iec = iec;
|
||
|
LED = DRVLED_OFF;
|
||
|
Ready = false;
|
||
|
set_error(ERR_STARTUP);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Set error message on drive
|
||
|
*/
|
||
|
|
||
|
// 1541 error messages
|
||
|
char *Errors_1541[] = {
|
||
|
"00, OK,00,00\r",
|
||
|
"25,WRITE ERROR,00,00\r",
|
||
|
"26,WRITE PROTECT ON,00,00\r",
|
||
|
"30,SYNTAX ERROR,00,00\r",
|
||
|
"33,SYNTAX ERROR,00,00\r",
|
||
|
"60,WRITE FILE OPEN,00,00\r",
|
||
|
"61,FILE NOT OPEN,00,00\r",
|
||
|
"62,FILE NOT FOUND,00,00\r",
|
||
|
"67,ILLEGAL TRACK OR SECTOR,00,00\r",
|
||
|
"70,NO CHANNEL,00,00\r",
|
||
|
"73,CBM DOS V2.6 1541,00,00\r",
|
||
|
"74,DRIVE NOT READY,00,00\r"
|
||
|
};
|
||
|
|
||
|
void Drive::set_error(int error)
|
||
|
{
|
||
|
error_ptr = Errors_1541[error];
|
||
|
error_len = strlen(error_ptr);
|
||
|
|
||
|
// Set drive condition
|
||
|
if (error != ERR_OK)
|
||
|
if (error == ERR_STARTUP)
|
||
|
LED = DRVLED_OFF;
|
||
|
else
|
||
|
LED = DRVLED_ERROR;
|
||
|
else if (LED == DRVLED_ERROR)
|
||
|
LED = DRVLED_OFF;
|
||
|
the_iec->UpdateLEDs();
|
||
|
}
|