/* * IEC.cpp - IEC bus routines, 1541 emulation (DOS level) * * Frodo (C) 1994-1997,2002-2005 Christian Bauer * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * 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" #include "main.h" // IEC command codes enum { CMD_DATA = 0x60, // Data transfer CMD_CLOSE = 0xe0, // Close channel CMD_OPEN = 0xf0 // Open channel }; // IEC ATN codes enum { ATN_LISTEN = 0x20, ATN_UNLISTEN = 0x30, ATN_TALK = 0x40, ATN_UNTALK = 0x50 }; /* * Constructor: Initialize variables */ Drive *IEC::create_drive(const char *path) { if (IsDirectory(path)) { // Mount host directory return new FSDrive(this, path); } else { // Not a directory, check for mountable file type int type; if (IsMountableFile(path, type)) { if (type == FILE_IMAGE) { // Mount disk image return new ImageDrive(this, path); } else { // Mount archive type file return new ArchDrive(this, path); } } else { // Unknown file type // print error? } } return NULL; } 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++) drive[i] = create_drive(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 (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) drive[i] = create_drive(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, name_len); } 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 static const char *Errors_1541[] = { "00, OK,%02d,%02d\x0d", "01, FILES SCRATCHED,%02d,%02d\x0d", "03, UNIMPLEMENTED,%02d,%02d\x0d", "20, READ ERROR,%02d,%02d\x0d", "21, READ ERROR,%02d,%02d\x0d", "22, READ ERROR,%02d,%02d\x0d", "23, READ ERROR,%02d,%02d\x0d", "24, READ ERROR,%02d,%02d\x0d", "25, WRITE ERROR,%02d,%02d\x0d", "26, WRITE PROTECT ON,%02d,%02d\x0d", "27, READ ERROR,%02d,%02d\x0d", "28, WRITE ERROR,%02d,%02d\x0d", "29, DISK ID MISMATCH,%02d,%02d\x0d", "30, SYNTAX ERROR,%02d,%02d\x0d", "31, SYNTAX ERROR,%02d,%02d\x0d", "32, SYNTAX ERROR,%02d,%02d\x0d", "33, SYNTAX ERROR,%02d,%02d\x0d", "34, SYNTAX ERROR,%02d,%02d\x0d", "60, WRITE FILE OPEN,%02d,%02d\x0d", "61, FILE NOT OPEN,%02d,%02d\x0d", "62, FILE NOT FOUND,%02d,%02d\x0d", "63, FILE EXISTS,%02d,%02d\x0d", "64, FILE TYPE MISMATCH,%02d,%02d\x0d", "65, NO BLOCK,%02d,%02d\x0d", "66, ILLEGAL TRACK OR SECTOR,%02d,%02d\x0d", "70, NO CHANNEL,%02d,%02d\x0d", "71, DIR ERROR,%02d,%02d\x0d", "72, DISK FULL,%02d,%02d\x0d", "73, CBM DOS V2.6 1541,%02d,%02d\x0d", "74, DRIVE NOT READY,%02d,%02d\x0d" }; void Drive::set_error(int error, int track, int sector) { // Write error message to buffer sprintf(error_buf, Errors_1541[error], track, sector); error_ptr = error_buf; error_len = strlen(error_buf); current_error = error; // Set drive condition if (error != ERR_OK && error != ERR_SCRATCHED) if (error == ERR_STARTUP) LED = DRVLED_OFF; else LED = DRVLED_ERROR; else if (LED == DRVLED_ERROR) LED = DRVLED_OFF; the_iec->UpdateLEDs(); } /* * Parse file name, determine access mode and file type */ void Drive::parse_file_name(const uint8 *src, int src_len, uint8 *dest, int &dest_len, int &mode, int &type, int &rec_len, bool convert_charset) { // If the string contains a ':', the file name starts after that const uint8 *p = (const uint8 *)memchr(src, ':', src_len); if (p) { p++; src_len -= p - src; } else p = src; // Transfer file name upto ',' dest_len = 0; uint8 *q = dest; while (*p != ',' && src_len-- > 0) { if (convert_charset) *q++ = petscii2ascii(*p++); else *q++ = *p++; dest_len++; } *q++ = 0; // Strip trailing CRs while (dest_len > 0 && dest[dest_len - 1] == 0x0d) dest[--dest_len] = 0; // Look for mode and type parameters separated by ',' p++; src_len--; while (src_len > 0) { switch (*p) { case 'D': type = FTYPE_DEL; break; case 'S': type = FTYPE_SEQ; break; case 'P': type = FTYPE_PRG; break; case 'U': type = FTYPE_USR; break; case 'L': type = FTYPE_REL; while (*p != ',' && src_len-- > 0) p++; p++; src_len--; rec_len = *p++; src_len--; if (src_len < 0) rec_len = 0; break; case 'R': mode = FMODE_READ; break; case 'W': mode = FMODE_WRITE; break; case 'A': mode = FMODE_APPEND; break; case 'M': mode = FMODE_M; break; } // Skip to ',' while (*p != ',' && src_len-- > 0) p++; p++; src_len--; } } /* * Execute DOS command (parse command and call appropriate routine) */ static void parse_block_cmd_args(const uint8 *p, int &arg1, int &arg2, int &arg3, int &arg4) { arg1 = arg2 = arg3 = arg4 = 0; while (*p == ' ' || *p == 0x1d || *p == ',') p++; while (*p >= '0' && *p < '@') arg1 = arg1 * 10 + (*p++ & 0x0f); while (*p == ' ' || *p == 0x1d || *p == ',') p++; while (*p >= '0' && *p < '@') arg2 = arg2 * 10 + (*p++ & 0x0f); while (*p == ' ' || *p == 0x1d || *p == ',') p++; while (*p >= '0' && *p < '@') arg3 = arg3 * 10 + (*p++ & 0x0f); while (*p == ' ' || *p == 0x1d || *p == ',') p++; while (*p >= '0' && *p < '@') arg4 = arg4 * 10 + (*p++ & 0x0f); } void Drive::execute_cmd(const uint8 *cmd, int cmd_len) { // Strip trailing CRs while (cmd_len > 0 && cmd[cmd_len - 1] == 0x0d) cmd_len--; // Find token delimiters const uint8 *colon = (const uint8 *)memchr(cmd, ':', cmd_len); const uint8 *equal = colon ? (const uint8 *)memchr(colon, '=', cmd_len - (colon - cmd)) : NULL; const uint8 *comma = (const uint8 *)memchr(cmd, ',', cmd_len); const uint8 *minus = (const uint8 *)memchr(cmd, '-', cmd_len); // Parse command name set_error(ERR_OK); switch (cmd[0]) { case 'B': // Block/buffer if (!minus) set_error(ERR_SYNTAX31); else { // Parse arguments (up to 4 decimal numbers separated by // space, cursor right or comma) const uint8 *p = colon ? colon + 1 : cmd + 3; int arg1, arg2, arg3, arg4; parse_block_cmd_args(p, arg1, arg2, arg3, arg4); // Switch on command switch (minus[1]) { case 'R': block_read_cmd(arg1, arg3, arg4); break; case 'W': block_write_cmd(arg1, arg3, arg4); break; case 'E': block_execute_cmd(arg1, arg3, arg4); break; case 'A': block_allocate_cmd(arg2, arg3); break; case 'F': block_free_cmd(arg2, arg3); break; case 'P': buffer_pointer_cmd(arg1, arg2); break; default: set_error(ERR_SYNTAX31); break; } } break; case 'M': // Memory if (cmd[1] != '-') set_error(ERR_SYNTAX31); else { // Read parameters uint16 adr = uint8(cmd[3]) | (uint8(cmd[4]) << 8); uint8 len = uint8(cmd[5]); // Switch on command switch (cmd[2]) { case 'R': mem_read_cmd(adr, (cmd_len < 6) ? 1 : len); break; case 'W': mem_write_cmd(adr, len, (uint8 *)cmd + 6); break; case 'E': mem_execute_cmd(adr); break; default: set_error(ERR_SYNTAX31); break; } } break; case 'C': // Copy if (!colon) set_error(ERR_SYNTAX31); else if (!equal || memchr(cmd, '*', cmd_len) || memchr(cmd, '?', cmd_len) || (comma && comma < equal)) set_error(ERR_SYNTAX30); else copy_cmd(colon + 1, equal - colon - 1, equal + 1, cmd_len - (equal + 1 - cmd)); break; case 'R': // Rename if (!colon) set_error(ERR_SYNTAX34); else if (!equal || comma || memchr(cmd, '*', cmd_len) || memchr(cmd, '?', cmd_len)) set_error(ERR_SYNTAX30); else rename_cmd(colon + 1, equal - colon - 1, equal + 1, cmd_len - (equal + 1 - cmd)); break; case 'S': // Scratch if (!colon) set_error(ERR_SYNTAX34); else scratch_cmd(colon + 1, cmd_len - (colon + 1 - cmd)); break; case 'P': // Position position_cmd(cmd + 1, cmd_len - 1); break; case 'I': // Initialize initialize_cmd(); break; case 'N': // New (format) if (!colon) set_error(ERR_SYNTAX34); else new_cmd(colon + 1, comma ? (comma - colon - 1) : cmd_len - (colon + 1 - cmd), comma); break; case 'V': // Validate validate_cmd(); break; case 'U': // User if (cmd[1] == '0') break; switch (cmd[1] & 0x0f) { case 1: { // U1/UA: Read block const uint8 *p = colon ? colon + 1 : cmd + 2; int arg1, arg2, arg3, arg4; parse_block_cmd_args(p, arg1, arg2, arg3, arg4); block_read_cmd(arg1, arg3, arg4, true); break; } case 2: { // U2/UB: Write block const uint8 *p = colon ? colon + 1 : cmd + 2; int arg1, arg2, arg3, arg4; parse_block_cmd_args(p, arg1, arg2, arg3, arg4); block_write_cmd(arg1, arg3, arg4, true); break; } case 9: // U9/UI: C64/VC20 mode switch if (cmd[2] != '+' && cmd[2] != '-') Reset(); break; case 10: // U:/UJ: Reset Reset(); break; default: set_error(ERR_UNIMPLEMENTED); break; } break; default: set_error(ERR_SYNTAX31); break; } } // BLOCK-READ:channel,0,track,sector void Drive::block_read_cmd(int channel, int track, int sector, bool user_cmd) { set_error(ERR_UNIMPLEMENTED); } // BLOCK-WRITE:channel,0,track,sector void Drive::block_write_cmd(int channel, int track, int sector, bool user_cmd) { set_error(ERR_UNIMPLEMENTED); } // BLOCK-EXECUTE:channel,0,track,sector void Drive::block_execute_cmd(int channel, int track, int sector) { set_error(ERR_UNIMPLEMENTED); } // BLOCK-ALLOCATE:0,track,sector void Drive::block_allocate_cmd(int track, int sector) { set_error(ERR_UNIMPLEMENTED); } // BLOCK-FREE:0,track,sector void Drive::block_free_cmd(int track, int sector) { set_error(ERR_UNIMPLEMENTED); } // BUFFER-POINTER:channel,pos void Drive::buffer_pointer_cmd(int channel, int pos) { set_error(ERR_UNIMPLEMENTED); } // M-R[] void Drive::mem_read_cmd(uint16 adr, uint8 len) { unsupp_cmd(); error_ptr = error_buf; error_buf[0] = 0; error_len = 0; set_error(ERR_OK); } // M-W void Drive::mem_write_cmd(uint16 adr, uint8 len, uint8 *p) { set_error(ERR_UNIMPLEMENTED); } // M-E void Drive::mem_execute_cmd(uint16 adr) { set_error(ERR_UNIMPLEMENTED); } // COPY:new=file1,file2,... // ^ ^ // new_file old_files void Drive::copy_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_files, int old_files_len) { set_error(ERR_UNIMPLEMENTED); } // RENAME:new=old // ^ ^ // new_file old_file void Drive::rename_cmd(const uint8 *new_file, int new_file_len, const uint8 *old_file, int old_file_len) { set_error(ERR_UNIMPLEMENTED); } // SCRATCH:file1,file2,... // ^ // files void Drive::scratch_cmd(const uint8 *files, int files_len) { set_error(ERR_UNIMPLEMENTED); } // P // ^ // cmd void Drive::position_cmd(const uint8 *cmd, int cmd_len) { set_error(ERR_UNIMPLEMENTED); } // INITIALIZE void Drive::initialize_cmd(void) { set_error(ERR_UNIMPLEMENTED); } // NEW:name,id // ^ ^ // name comma (or NULL) void Drive::new_cmd(const uint8 *name, int name_len, const uint8 *comma) { set_error(ERR_UNIMPLEMENTED); } // VALIDATE void Drive::validate_cmd(void) { set_error(ERR_UNIMPLEMENTED); } /* * Notice user of unsupported drive command */ void Drive::unsupp_cmd(void) { } /* * Convert PETSCII<->ASCII */ uint8 ascii2petscii(char c) { if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) return c ^ 0x20; return c; } void ascii2petscii(uint8 *dest, const char *src, int n) { while (n-- && (*dest++ = ascii2petscii(*src++))) ; } char petscii2ascii(uint8 c) { if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) return c ^ 0x20; if ((c >= 0xc1) && (c <= 0xda)) return c ^ 0x80; return c; } void petscii2ascii(char *dest, const uint8 *src, int n) { while (n-- && (*dest++ = petscii2ascii(*src++))) ; } /* * Check whether file is a mountable disk image or archive file, return type */ bool IsMountableFile(const char *path, int &type) { size_t n; bool out = true; // Read header and determine file size uint8 header[64]; memset(header, 0, sizeof(header)); FILE *f = fopen(path, "rb"); if (f == NULL) return false; fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); n = fread(header, 1, sizeof(header), f); if (n != 1) out = false; fclose(f); if (IsImageFile(path, header, size)) type = FILE_IMAGE; else if (IsArchFile(path, header, size)) type = FILE_ARCH; else out = false; return out; } /* * Read directory of mountable disk image or archive file into c64_dir_entry vector, * returns false on error */ bool ReadDirectory(const char *path, int type, vector &vec) { vec.clear(); switch (type) { case FILE_IMAGE: return ReadImageDirectory(path, vec); case FILE_ARCH: return ReadArchDirectory(path, vec); default: return false; } }