/* * sio.c - Serial I/O emulation * * Copyright (C) 1995-1998 David Firth * Copyright (C) 1998-2008 Atari800 development team (see DOC/CREDITS) * * This file is part of the Atari800 emulator project which emulates * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers. * * Atari800 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. * * Atari800 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 Atari800; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #include "afile.h" #include "antic.h" /* ANTIC_ypos */ #include "atari.h" #include "binload.h" #include "cassette.h" #include "compfile.h" #include "cpu.h" #include "esc.h" #include "log.h" #include "memory.h" #include "platform.h" #include "pokey.h" #include "pokeysnd.h" #include "sio.h" #include "util.h" #ifndef BASIC #include "statesav.h" #endif /* If ATR image is in double density (256 bytes per sector), then the boot sectors (sectors 1-3) can be: - logical (as seen by Atari) - 128 bytes in each sector - physical (as stored on the disk) - 256 bytes in each sector. Only the first half of sector is used for storing data, the rest is zero. - SIO2PC (the type used by the SIO2PC program) - 3 * 128 bytes for data of boot sectors, then 3 * 128 unused bytes (zero) The XFD images in double density have either logical or physical boot sectors. */ #define BOOT_SECTORS_LOGICAL 0 #define BOOT_SECTORS_PHYSICAL 1 #define BOOT_SECTORS_SIO2PC 2 static int boot_sectors_type[SIO_MAX_DRIVES]; static int image_type[SIO_MAX_DRIVES]; #define IMAGE_TYPE_XFD 0 #define IMAGE_TYPE_ATR 1 #define IMAGE_TYPE_PRO 2 static FILE *disk[SIO_MAX_DRIVES] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static int sectorcount[SIO_MAX_DRIVES]; static int sectorsize[SIO_MAX_DRIVES]; /* these two are used by the 1450XLD parallel disk device */ int SIO_format_sectorcount[SIO_MAX_DRIVES]; int SIO_format_sectorsize[SIO_MAX_DRIVES]; static int io_success[SIO_MAX_DRIVES]; /* stores dup sector counter for PRO images */ typedef struct tagpro_additional_info_t { int max_sector; unsigned char *count; } pro_additional_info_t; static void *additional_info[SIO_MAX_DRIVES]; SIO_UnitStatus SIO_drive_status[SIO_MAX_DRIVES]; char SIO_filename[SIO_MAX_DRIVES][FILENAME_MAX]; Util_tmpbufdef(static, sio_tmpbuf[SIO_MAX_DRIVES]) int SIO_last_op; int SIO_last_op_time = 0; int SIO_last_drive; int SIO_last_sector; char SIO_status[256]; /* Serial I/O emulation support */ #define SIO_NoFrame (0x00) #define SIO_CommandFrame (0x01) #define SIO_StatusRead (0x02) #define SIO_ReadFrame (0x03) #define SIO_WriteFrame (0x04) #define SIO_FinalStatus (0x05) #define SIO_FormatFrame (0x06) #define SIO_CasRead (0x60) #define SIO_CasWrite (0x61) static UBYTE CommandFrame[6]; static int CommandIndex = 0; static UBYTE DataBuffer[256 + 3]; static int DataIndex = 0; static int TransferStatus = SIO_NoFrame; static int ExpectedBytes = 0; int ignore_header_writeprotect = FALSE; int SIO_Initialise(int *argc, char *argv[]) { int i; for (i = 0; i < SIO_MAX_DRIVES; i++) { strcpy(SIO_filename[i], "Off"); SIO_drive_status[i] = SIO_OFF; SIO_format_sectorsize[i] = 128; SIO_format_sectorcount[i] = 720; } TransferStatus = SIO_NoFrame; return TRUE; } /* umount disks so temporary files are deleted */ void SIO_Exit(void) { int i; for (i = 1; i <= SIO_MAX_DRIVES; i++) SIO_Dismount(i); } int SIO_Mount(int diskno, const char *filename, int b_open_readonly) { FILE *f = NULL; SIO_UnitStatus status = SIO_READ_WRITE; struct AFILE_ATR_Header header; /* avoid overruns in SIO_filename[] */ if (strlen(filename) >= FILENAME_MAX) return FALSE; /* release previous disk */ SIO_Dismount(diskno); /* open file */ if (!b_open_readonly) f = Util_fopen(filename, "rb+", sio_tmpbuf[diskno - 1]); if (f == NULL) { f = Util_fopen(filename, "rb", sio_tmpbuf[diskno - 1]); if (f == NULL) return FALSE; status = SIO_READ_ONLY; } /* read header */ if (fread(&header, 1, sizeof(struct AFILE_ATR_Header), f) != sizeof(struct AFILE_ATR_Header)) { fclose(f); return FALSE; } /* detect compressed image and uncompress */ switch (header.magic1) { case 0xf9: case 0xfa: /* DCM */ { FILE *f2 = Util_tmpopen(sio_tmpbuf[diskno - 1]); if (f2 == NULL) return FALSE; Util_rewind(f); if (!CompFile_DCMtoATR(f, f2)) { Util_fclose(f2, sio_tmpbuf[diskno - 1]); fclose(f); return FALSE; } fclose(f); f = f2; } Util_rewind(f); if (fread(&header, 1, sizeof(struct AFILE_ATR_Header), f) != sizeof(struct AFILE_ATR_Header)) { Util_fclose(f, sio_tmpbuf[diskno - 1]); return FALSE; } status = SIO_READ_ONLY; /* XXX: status = b_open_readonly ? SIO_READ_ONLY : SIO_READ_WRITE; */ break; case 0x1f: if (header.magic2 == 0x8b) { /* ATZ/ATR.GZ, XFZ/XFD.GZ */ fclose(f); f = Util_tmpopen(sio_tmpbuf[diskno - 1]); if (f == NULL) return FALSE; if (!CompFile_ExtractGZ(filename, f)) { Util_fclose(f, sio_tmpbuf[diskno - 1]); return FALSE; } Util_rewind(f); if (fread(&header, 1, sizeof(struct AFILE_ATR_Header), f) != sizeof(struct AFILE_ATR_Header)) { Util_fclose(f, sio_tmpbuf[diskno - 1]); return FALSE; } status = SIO_READ_ONLY; /* XXX: status = b_open_readonly ? SIO_READ_ONLY : SIO_READ_WRITE; */ } break; default: break; } boot_sectors_type[diskno - 1] = BOOT_SECTORS_LOGICAL; if (header.magic1 == AFILE_ATR_MAGIC1 && header.magic2 == AFILE_ATR_MAGIC2) { /* ATR (may be temporary from DCM or ATR/ATR.GZ) */ image_type[diskno - 1] = IMAGE_TYPE_ATR; sectorsize[diskno - 1] = (header.secsizehi << 8) + header.secsizelo; if (sectorsize[diskno - 1] != 128 && sectorsize[diskno - 1] != 256) { Util_fclose(f, sio_tmpbuf[diskno - 1]); return FALSE; } if (header.writeprotect != 0 && !ignore_header_writeprotect) status = SIO_READ_ONLY; /* ATR header contains length in 16-byte chunks. */ /* First compute number of 128-byte chunks - it's number of sectors on single density disk */ sectorcount[diskno - 1] = ((header.hiseccounthi << 24) + (header.hiseccountlo << 16) + (header.seccounthi << 8) + header.seccountlo) >> 3; /* Fix number of sectors if double density */ if (sectorsize[diskno - 1] == 256) { if ((sectorcount[diskno - 1] & 1) != 0) /* logical (128-byte) boot sectors */ sectorcount[diskno - 1] += 3; else { /* 256-byte boot sectors */ /* check if physical or SIO2PC: physical if there's a non-zero byte in bytes 0x190-0x30f of the ATR file */ UBYTE buffer[0x180]; int i; fseek(f, 0x190, SEEK_SET); if (fread(buffer, 1, 0x180, f) != 0x180) { Util_fclose(f, sio_tmpbuf[diskno - 1]); return FALSE; } boot_sectors_type[diskno - 1] = BOOT_SECTORS_SIO2PC; for (i = 0; i < 0x180; i++) if (buffer[i] != 0) { boot_sectors_type[diskno - 1] = BOOT_SECTORS_PHYSICAL; break; } } sectorcount[diskno - 1] >>= 1; } } else { int file_length = Util_flen(f); /* check for PRO */ if ((file_length-16)%(128+12) == 0 && (header.magic1*256 + header.magic2 == (file_length-16)/(128+12)) && header.seccountlo == 'P') { pro_additional_info_t *info; /* .pro is read only for now */ if (!b_open_readonly) { fclose(f); f = Util_fopen(filename, "rb", sio_tmpbuf[diskno - 1]); if (f == NULL) return FALSE; status = SIO_READ_ONLY; } image_type[diskno - 1] = IMAGE_TYPE_PRO; sectorsize[diskno - 1] = 128; if (file_length >= 1040*(128+12)+16) { /* assume enhanced density */ sectorcount[diskno - 1] = 1040; } else { /* assume single density */ sectorcount[diskno - 1] = 720; } info = Util_malloc(sizeof(pro_additional_info_t)); additional_info[diskno-1] = info; info->count = Util_malloc(sectorcount[diskno - 1]); memset(info->count, 0, sectorcount[diskno -1]); info->max_sector = (file_length-16)/(128+12); } else { /* XFD (may be temporary from XFZ/XFD.GZ) */ image_type[diskno - 1] = IMAGE_TYPE_XFD; if (file_length <= (1040 * 128)) { /* single density */ sectorsize[diskno - 1] = 128; sectorcount[diskno - 1] = file_length >> 7; } else { /* double density */ sectorsize[diskno - 1] = 256; if ((file_length & 0xff) == 0) { boot_sectors_type[diskno - 1] = BOOT_SECTORS_PHYSICAL; sectorcount[diskno - 1] = file_length >> 8; } else sectorcount[diskno - 1] = (file_length + 0x180) >> 8; } } } #ifdef DEBUG Log_print("sectorcount = %d, sectorsize = %d", sectorcount[diskno - 1], sectorsize[diskno - 1]); #endif SIO_format_sectorsize[diskno - 1] = sectorsize[diskno - 1]; SIO_format_sectorcount[diskno - 1] = sectorcount[diskno - 1]; strcpy(SIO_filename[diskno - 1], filename); SIO_drive_status[diskno - 1] = status; disk[diskno - 1] = f; return TRUE; } void SIO_Dismount(int diskno) { if (disk[diskno - 1] != NULL) { Util_fclose(disk[diskno - 1], sio_tmpbuf[diskno - 1]); disk[diskno - 1] = NULL; SIO_drive_status[diskno - 1] = SIO_NO_DISK; strcpy(SIO_filename[diskno - 1], "Empty"); if (image_type[diskno - 1] == IMAGE_TYPE_PRO) { free(((pro_additional_info_t *)additional_info[diskno-1])->count); } free(additional_info[diskno - 1]); additional_info[diskno - 1] = 0; } } void SIO_DisableDrive(int diskno) { SIO_Dismount(diskno); SIO_drive_status[diskno - 1] = SIO_OFF; strcpy(SIO_filename[diskno - 1], "Off"); } void SIO_SizeOfSector(UBYTE unit, int sector, int *sz, ULONG *ofs) { int size; ULONG offset; int header_size = (image_type[unit] == IMAGE_TYPE_ATR ? 16 : 0); if (BINLOAD_start_binloading) { if (sz) *sz = 128; if (ofs) *ofs = 0; return; } if (image_type[unit] == IMAGE_TYPE_PRO) { size = 128; offset = 16 + (128+12)*(sector -1); /* returns offset of header */ } else if (sector < 4) { /* special case for first three sectors in ATR and XFD image */ size = 128; offset = header_size + (sector - 1) * (boot_sectors_type[unit] == BOOT_SECTORS_PHYSICAL ? 256 : 128); } else { size = sectorsize[unit]; offset = header_size + (boot_sectors_type[unit] == BOOT_SECTORS_LOGICAL ? 0x180 : 0x300) + (sector - 4) * size; } if (sz) *sz = size; if (ofs) *ofs = offset; } static int SeekSector(int unit, int sector) { ULONG offset; int size; SIO_last_sector = sector; sprintf(SIO_status, "%d: %d", unit + 1, sector); SIO_SizeOfSector((UBYTE) unit, sector, &size, &offset); fseek(disk[unit], offset, SEEK_SET); return size; } /* Unit counts from zero up */ int SIO_ReadSector(int unit, int sector, UBYTE *buffer) { int size; if (BINLOAD_start_binloading) return BINLOAD_LoaderStart(buffer); io_success[unit] = -1; if (SIO_drive_status[unit] == SIO_OFF) return 0; if (disk[unit] == NULL) return 'N'; if (sector <= 0 || sector > sectorcount[unit]) return 'E'; SIO_last_op = SIO_LAST_READ; SIO_last_op_time = 1; SIO_last_drive = unit + 1; /* FIXME: what sector size did the user expect? */ size = SeekSector(unit, sector); if (image_type[unit] == IMAGE_TYPE_PRO) { pro_additional_info_t *info; unsigned char *count; info = (pro_additional_info_t *)additional_info[unit]; count = info->count; fread(buffer, 1, 12, disk[unit]); /* handle duplicate sectors */ if (buffer[5] != 0) { int dupnum = count[sector]; #ifdef DEBUG_PRO Log_print("duplicate sector:%d dupnum:%d",sector, dupnum); #endif count[sector] = (count[sector]+1) % (buffer[5]+1); if (dupnum != 0) { sector = sectorcount[unit] + buffer[6+dupnum]; /* can dupnum be 5? */ if (dupnum > 4 || sector <= 0 || sector > info->max_sector) { Log_print("Error in .pro image: sector:%d dupnum:%d", sector, dupnum); return 'E'; } size = SeekSector(unit, sector); /* read sector header */ fread(buffer, 1, 12, disk[unit]); } } /* bad sector */ if (buffer[1] != 0xff) { fread(buffer, 1, size, disk[unit]); io_success[unit] = sector; #ifdef DEBUG_PRO Log_print("bad sector:%d", sector); #endif return 'E'; } } fread(buffer, 1, size, disk[unit]); io_success[unit] = 0; return 'C'; } int SIO_WriteSector(int unit, int sector, const UBYTE *buffer) { int size; io_success[unit] = -1; if (SIO_drive_status[unit] == SIO_OFF) return 0; if (disk[unit] == NULL) return 'N'; if (SIO_drive_status[unit] != SIO_READ_WRITE || sector <= 0 || sector > sectorcount[unit]) return 'E'; SIO_last_op = SIO_LAST_WRITE; SIO_last_op_time = 1; SIO_last_drive = unit + 1; size = SeekSector(unit, sector); fwrite(buffer, 1, size, disk[unit]); io_success[unit] = 0; return 'C'; } int SIO_FormatDisk(int unit, UBYTE *buffer, int sectsize, int sectcount) { char fname[FILENAME_MAX]; int is_atr; int save_boot_sectors_type; int bootsectsize; int bootsectcount; FILE *f; int i; io_success[unit] = -1; if (SIO_drive_status[unit] == SIO_OFF) return 0; if (disk[unit] == NULL) return 'N'; if (SIO_drive_status[unit] != SIO_READ_WRITE) return 'E'; /* Note formatting the disk can change size of the file. There is no portable way to truncate the file at given position. We have to close the "rb+" open file and open it in "wb" mode. First get the information about the disk image, because we are going to umount it. */ memcpy(fname, SIO_filename[unit], FILENAME_MAX); is_atr = (image_type[unit] == IMAGE_TYPE_ATR); save_boot_sectors_type = boot_sectors_type[unit]; bootsectsize = 128; if (sectsize == 256 && save_boot_sectors_type != BOOT_SECTORS_LOGICAL) bootsectsize = 256; bootsectcount = sectcount < 3 ? sectcount : 3; /* Umount the file and open it in "wb" mode (it will truncate the file) */ SIO_Dismount(unit + 1); f = fopen(fname, "wb"); if (f == NULL) { Log_print("SIO_FormatDisk: failed to open %s for writing", fname); return 'E'; } /* Write ATR header if necessary */ if (is_atr) { struct AFILE_ATR_Header header; ULONG disksize = (bootsectsize * bootsectcount + sectsize * (sectcount - bootsectcount)) >> 4; memset(&header, 0, sizeof(header)); header.magic1 = AFILE_ATR_MAGIC1; header.magic2 = AFILE_ATR_MAGIC2; header.secsizelo = (UBYTE) sectsize; header.secsizehi = (UBYTE) (sectsize >> 8); header.seccountlo = (UBYTE) disksize; header.seccounthi = (UBYTE) (disksize >> 8); header.hiseccountlo = (UBYTE) (disksize >> 16); header.hiseccounthi = (UBYTE) (disksize >> 24); fwrite(&header, 1, sizeof(header), f); } /* Write boot sectors */ memset(buffer, 0, sectsize); for (i = 1; i <= bootsectcount; i++) fwrite(buffer, 1, bootsectsize, f); /* Write regular sectors */ for ( ; i <= sectcount; i++) fwrite(buffer, 1, sectsize, f); /* Close file and mount the disk back */ fclose(f); SIO_Mount(unit + 1, fname, FALSE); /* We want to keep the current PHYSICAL/SIO2PC boot sectors type (since the image is blank it can't be figured out by SIO_Mount) */ if (bootsectsize == 256) boot_sectors_type[unit] = save_boot_sectors_type; /* Return information for Atari (buffer filled with ff's - no bad sectors) */ memset(buffer, 0xff, sectsize); io_success[unit] = 0; return 'C'; } /* Set density and number of sectors This function is used before the format (0x21) command to set how the disk will be formatted. Note this function does *not* affect the currently attached disk (previously sectorsize/sectorcount were used which could result in a corrupted image). */ int SIO_WriteStatusBlock(int unit, const UBYTE *buffer) { int size; #ifdef DEBUG Log_print("Write Status-Block: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11]); #endif if (SIO_drive_status[unit] == SIO_OFF) return 0; /* We only care about the density and the sector count here. Setting everything else right here seems to be non-sense. I'm not sure about this density settings, my XF551 honors only the sector size and ignores the density */ size = buffer[6] * 256 + buffer[7]; if (size == 128 || size == 256) SIO_format_sectorsize[unit] = size; /* Note that the number of heads are minus 1 */ SIO_format_sectorcount[unit] = buffer[0] * (buffer[2] * 256 + buffer[3]) * (buffer[4] + 1); if (SIO_format_sectorcount[unit] < 1 || SIO_format_sectorcount[unit] > 65535) SIO_format_sectorcount[unit] = 720; return 'C'; } int SIO_ReadStatusBlock(int unit, UBYTE *buffer) { UBYTE tracks; UBYTE heads; int spt; if (SIO_drive_status[unit] == SIO_OFF) return 0; /* default to 1 track, 1 side for non-standard images */ tracks = 1; heads = 1; spt = sectorcount[unit]; if (spt % 40 == 0) { /* standard disk */ tracks = 40; spt /= 40; if (spt > 26 && spt % 2 == 0) { /* double-sided */ heads = 2; spt >>= 1; if (spt > 26 && spt % 2 == 0) { /* double-sided, 80 tracks */ tracks = 80; spt >>= 1; } } } buffer[0] = tracks; /* # of tracks */ buffer[1] = 1; /* step rate */ buffer[2] = (UBYTE) (spt >> 8); /* sectors per track. HI byte */ buffer[3] = (UBYTE) spt; /* sectors per track. LO byte */ buffer[4] = (UBYTE) (heads - 1); /* # of heads minus 1 */ /* FM for single density, MFM otherwise */ buffer[5] = (sectorsize[unit] == 128 && sectorcount[unit] <= 720) ? 0 : 4; buffer[6] = (UBYTE) (sectorsize[unit] >> 8); /* bytes per sector. HI byte */ buffer[7] = (UBYTE) sectorsize[unit]; /* bytes per sector. LO byte */ buffer[8] = 1; /* drive is online */ buffer[9] = 192; /* transfer speed, whatever this means */ buffer[10] = 0; buffer[11] = 0; return 'C'; } /* Status Request from Atari 400/800 Technical Reference Notes DVSTAT + 0 Command Status DVSTAT + 1 Hardware Status DVSTAT + 2 Timeout DVSTAT + 3 Unused Command Status Bits Bit 0 = 1 indicates an invalid command frame was received Bit 1 = 1 indicates an invalid data frame was received Bit 2 = 1 indicates that last read/write operation was unsuccessful Bit 3 = 1 indicates that the diskette is write protected Bit 4 = 1 indicates active/standby plus Bit 5 = 1 indicates double density Bit 7 = 1 indicates dual density disk (1050 format) */ int SIO_DriveStatus(int unit, UBYTE *buffer) { if (BINLOAD_start_binloading) { buffer[0] = 16 + 8; buffer[1] = 255; buffer[2] = 1; buffer[3] = 0 ; return 'C'; } if (SIO_drive_status[unit] == SIO_OFF) return 0; /* .PRO contains status information in the sector header */ if (io_success[unit] != 0 && image_type[unit] == IMAGE_TYPE_PRO) { int sector = io_success[unit]; SeekSector(unit, sector); fread(buffer, 1, 4, disk[unit]); return 'C'; } buffer[0] = 16; /* drive active */ buffer[1] = disk[unit] != NULL ? 255 /* WD 177x OK */ : 127 /* no disk */; if (io_success[unit] != 0) buffer[0] |= 4; /* failed RW-operation */ if (SIO_drive_status[unit] == SIO_READ_ONLY) buffer[0] |= 8; /* write protection */ if (SIO_format_sectorsize[unit] == 256) buffer[0] |= 32; /* double density */ if (SIO_format_sectorcount[unit] == 1040) buffer[0] |= 128; /* 1050 enhanced density */ buffer[2] = 1; buffer[3] = 0; return 'C'; } #ifndef NO_SECTOR_DELAY /* A hack for the "Overmind" demo. This demo verifies if sectors aren't read faster than with a typical disk drive. We introduce a delay of SECTOR_DELAY scanlines between successive reads of sector 1. */ #define SECTOR_DELAY 3200 static int delay_counter = 0; static int last_ypos = 0; #endif /* SIO patch emulation routine */ void SIO_Handler(void) { int sector = MEMORY_dGetWordAligned(0x30a); UBYTE unit = (MEMORY_dGetByte(0x300) + MEMORY_dGetByte(0x301) + 0xff ) - 0x31; UBYTE result = 0x00; UWORD data = MEMORY_dGetWordAligned(0x304); int length = MEMORY_dGetWordAligned(0x308); int realsize = 0; int cmd = MEMORY_dGetByte(0x302); if ((unsigned int)MEMORY_dGetByte(0x300) + (unsigned int)MEMORY_dGetByte(0x301) > 0xff) { /* carry */ unit++; } /* A real atari just adds the bytes and 0xff. The result could wrap.*/ /* XL OS: E99D: LDA $0300 ADC $0301 ADC #$FF STA 023A */ /* Disk 1 is ASCII '1' = 0x31 etc */ /* Disk 1 -> unit = 0 */ if (MEMORY_dGetByte(0x300) != 0x60 && unit < SIO_MAX_DRIVES) { /* UBYTE range ! */ #ifdef DEBUG Log_print("SIO disk command is %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", cmd, MEMORY_dGetByte(0x303), MEMORY_dGetByte(0x304), MEMORY_dGetByte(0x305), MEMORY_dGetByte(0x306), MEMORY_dGetByte(0x308), MEMORY_dGetByte(0x309), MEMORY_dGetByte(0x30a), MEMORY_dGetByte(0x30b), MEMORY_dGetByte(0x30c), MEMORY_dGetByte(0x30d)); #endif switch (cmd) { case 0x4e: /* Read Status Block */ if (12 == length) { result = SIO_ReadStatusBlock(unit, DataBuffer); if (result == 'C') MEMORY_CopyToMem(DataBuffer, data, 12); } else result = 'E'; break; case 0x4f: /* Write Status Block */ if (12 == length) { MEMORY_CopyFromMem(data, DataBuffer, 12); result = SIO_WriteStatusBlock(unit, DataBuffer); } else result = 'E'; break; case 0x50: /* Write */ case 0x57: case 0xD0: /* xf551 hispeed */ case 0xD7: SIO_SizeOfSector(unit, sector, &realsize, NULL); if (realsize == length) { MEMORY_CopyFromMem(data, DataBuffer, realsize); result = SIO_WriteSector(unit, sector, DataBuffer); } else result = 'E'; break; case 0x52: /* Read */ case 0xD2: /* xf551 hispeed */ #ifndef NO_SECTOR_DELAY if (sector == 1) { if (delay_counter > 0) { if (last_ypos != ANTIC_ypos) { last_ypos = ANTIC_ypos; delay_counter--; } CPU_regPC = 0xe459; /* stay at SIO patch */ return; } delay_counter = SECTOR_DELAY; } else { delay_counter = 0; } #endif SIO_SizeOfSector(unit, sector, &realsize, NULL); if (realsize == length) { result = SIO_ReadSector(unit, sector, DataBuffer); if (result == 'C') MEMORY_CopyToMem(DataBuffer, data, realsize); } else result = 'E'; break; case 0x53: /* Status */ if (4 == length) { result = SIO_DriveStatus(unit, DataBuffer); MEMORY_CopyToMem(DataBuffer, data, 4); } else result = 'E'; break; /*case 0x66:*/ /* US Doubler Format - I think! */ case 0x21: /* Format Disk */ case 0xA1: /* xf551 hispeed */ realsize = SIO_format_sectorsize[unit]; if (realsize == length) { result = SIO_FormatDisk(unit, DataBuffer, realsize, SIO_format_sectorcount[unit]); if (result == 'C') MEMORY_CopyToMem(DataBuffer, data, realsize); } else { /* there are programs which send the format-command but don't wait for the result (eg xf-tools) */ SIO_FormatDisk(unit, DataBuffer, realsize, SIO_format_sectorcount[unit]); result = 'E'; } break; case 0x22: /* Enhanced Density Format */ case 0xA2: /* xf551 hispeed */ realsize = 128; if (realsize == length) { result = SIO_FormatDisk(unit, DataBuffer, 128, 1040); if (result == 'C') MEMORY_CopyToMem(DataBuffer, data, realsize); } else { SIO_FormatDisk(unit, DataBuffer, 128, 1040); result = 'E'; } break; default: result = 'N'; } } /* cassette i/o */ else if (MEMORY_dGetByte(0x300) == 0x60) { int storagelength = 0; UBYTE gaps = MEMORY_dGetByte(0x30b); switch (cmd){ case 0x52: /* read */ /* set expected Gap */ CASSETTE_AddGap(gaps == 0 ? 2000 : 160); SIO_last_op = SIO_LAST_READ; SIO_last_drive = 0x61; SIO_last_op_time = 0x10; /* get record from storage medium */ storagelength = CASSETTE_Read(); if (storagelength - 1 != length) /* includes -1 as error */ result = 'E'; else result = 'C'; /* check checksum */ if (CASSETTE_buffer[length] != SIO_ChkSum(CASSETTE_buffer, length)) result = 'E'; /* if all went ok, copy to Atari */ if (result == 'C') MEMORY_CopyToMem(CASSETTE_buffer, data, length); break; case 0x57: /* write */ SIO_last_op = SIO_LAST_WRITE; SIO_last_drive = 0x61; SIO_last_op_time = 0x10; /* put record into buffer */ MEMORY_CopyFromMem(data, CASSETTE_buffer, length); /* eval checksum over buffer data */ CASSETTE_buffer[length] = SIO_ChkSum(CASSETTE_buffer, length); /* add pregap length */ CASSETTE_AddGap(gaps == 0 ? 3000 : 260); /* write full record to storage medium */ storagelength = CASSETTE_Write(length + 1); if (storagelength - 1 != length) /* includes -1 as error */ result = 'E'; else result = 'C'; break; default: result = 'N'; } } switch (result) { case 0x00: /* Device disabled, generate timeout */ CPU_regY = 138; CPU_SetN; break; case 'A': /* Device acknowledge */ case 'C': /* Operation complete */ CPU_regY = 1; CPU_ClrN; break; case 'N': /* Device NAK */ CPU_regY = 144; CPU_SetN; break; case 'E': /* Device error */ default: CPU_regY = 146; CPU_SetN; break; } CPU_regA = 0; /* MMM */ MEMORY_dPutByte(0x0303, CPU_regY); MEMORY_dPutByte(0x42,0); CPU_SetC; } UBYTE SIO_ChkSum(const UBYTE *buffer, int length) { #if 0 /* old, less efficient version */ int i; int checksum = 0; for (i = 0; i < length; i++, buffer++) { checksum += *buffer; while (checksum > 255) checksum -= 255; } #else int checksum = 0; while (--length >= 0) checksum += *buffer++; do checksum = (checksum & 0xff) + (checksum >> 8); while (checksum > 255); #endif return checksum; } static UBYTE Command_Frame(void) { int unit; int sector; int realsize; sector = CommandFrame[2] | (((UWORD) CommandFrame[3]) << 8); unit = CommandFrame[0] - '1'; if (unit < 0 || unit >= SIO_MAX_DRIVES) { /* Unknown device */ Log_print("Unknown command frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); TransferStatus = SIO_NoFrame; return 0; } switch (CommandFrame[1]) { case 0x4e: /* Read Status */ #ifdef DEBUG Log_print("Read-status frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif DataBuffer[0] = SIO_ReadStatusBlock(unit, DataBuffer + 1); DataBuffer[13] = SIO_ChkSum(DataBuffer + 1, 12); DataIndex = 0; ExpectedBytes = 14; TransferStatus = SIO_ReadFrame; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL; return 'A'; case 0x4f: /* Write status */ #ifdef DEBUG Log_print("Write-status frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif ExpectedBytes = 13; DataIndex = 0; TransferStatus = SIO_WriteFrame; return 'A'; case 0x50: /* Write */ case 0x57: case 0xD0: /* xf551 hispeed */ case 0xD7: #ifdef DEBUG Log_print("Write-sector frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif SIO_SizeOfSector((UBYTE) unit, sector, &realsize, NULL); ExpectedBytes = realsize + 1; DataIndex = 0; TransferStatus = SIO_WriteFrame; SIO_last_op = SIO_LAST_WRITE; SIO_last_op_time = 10; SIO_last_drive = unit + 1; return 'A'; case 0x52: /* Read */ case 0xD2: /* xf551 hispeed */ #ifdef DEBUG Log_print("Read-sector frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif SIO_SizeOfSector((UBYTE) unit, sector, &realsize, NULL); DataBuffer[0] = SIO_ReadSector(unit, sector, DataBuffer + 1); DataBuffer[1 + realsize] = SIO_ChkSum(DataBuffer + 1, realsize); DataIndex = 0; ExpectedBytes = 2 + realsize; TransferStatus = SIO_ReadFrame; /* wait longer before confirmation because bytes could be lost */ /* before the buffer was set (see $E9FB & $EA37 in XL-OS) */ POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL << 2; #ifndef NO_SECTOR_DELAY if (sector == 1) { POKEY_DELAYED_SERIN_IRQ += delay_counter; delay_counter = SECTOR_DELAY; } else { delay_counter = 0; } #endif SIO_last_op = SIO_LAST_READ; SIO_last_op_time = 10; SIO_last_drive = unit + 1; return 'A'; case 0x53: /* Status */ #ifdef DEBUG Log_print("Status frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif DataBuffer[0] = SIO_DriveStatus(unit, DataBuffer + 1); DataBuffer[1 + 4] = SIO_ChkSum(DataBuffer + 1, 4); DataIndex = 0; ExpectedBytes = 6; TransferStatus = SIO_ReadFrame; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL; return 'A'; /*case 0x66:*/ /* US Doubler Format - I think! */ case 0x21: /* Format Disk */ case 0xa1: /* xf551 hispeed */ #ifdef DEBUG Log_print("Format-disk frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif realsize = SIO_format_sectorsize[unit]; DataBuffer[0] = SIO_FormatDisk(unit, DataBuffer + 1, realsize, SIO_format_sectorcount[unit]); DataBuffer[1 + realsize] = SIO_ChkSum(DataBuffer + 1, realsize); DataIndex = 0; ExpectedBytes = 2 + realsize; TransferStatus = SIO_FormatFrame; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL; return 'A'; case 0x22: /* Dual Density Format */ case 0xa2: /* xf551 hispeed */ #ifdef DEBUG Log_print("Format-Medium frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif DataBuffer[0] = SIO_FormatDisk(unit, DataBuffer + 1, 128, 1040); DataBuffer[1 + 128] = SIO_ChkSum(DataBuffer + 1, 128); DataIndex = 0; ExpectedBytes = 2 + 128; TransferStatus = SIO_FormatFrame; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL; return 'A'; default: /* Unknown command for a disk drive */ #ifdef DEBUG Log_print("Command frame: %02x %02x %02x %02x %02x", CommandFrame[0], CommandFrame[1], CommandFrame[2], CommandFrame[3], CommandFrame[4]); #endif TransferStatus = SIO_NoFrame; return 'E'; } } /* Enable/disable the Tape Motor */ void SIO_TapeMotor(int onoff) { /* if sio is patched, do not do anything */ if (ESC_enable_sio_patch) return; if (onoff) { /* set frame to cassette frame, if not */ /* in a transfer with an intelligent peripheral */ if (TransferStatus == SIO_NoFrame || (TransferStatus & 0xfe) == SIO_CasRead) { if (CASSETTE_IsSaveFile()) { TransferStatus = SIO_CasWrite; CASSETTE_TapeMotor(onoff); SIO_last_op = SIO_LAST_WRITE; } else { TransferStatus = SIO_CasRead; CASSETTE_TapeMotor(onoff); POKEY_DELAYED_SERIN_IRQ = CASSETTE_GetInputIRQDelay(); SIO_last_op = SIO_LAST_READ; }; SIO_last_drive = 0x60; SIO_last_op_time = 0x10; } else { CASSETTE_TapeMotor(onoff); } } else { /* set frame to none */ if (TransferStatus == SIO_CasWrite) { TransferStatus = SIO_NoFrame; CASSETTE_TapeMotor(onoff); } else if (TransferStatus == SIO_CasRead) { TransferStatus = SIO_NoFrame; CASSETTE_TapeMotor(onoff); POKEY_DELAYED_SERIN_IRQ = 0; /* off */ } else { CASSETTE_TapeMotor(onoff); POKEY_DELAYED_SERIN_IRQ = 0; /* off */ } SIO_last_op_time = 0; } } /* Enable/disable the command frame */ void SIO_SwitchCommandFrame(int onoff) { if (onoff) { /* Enabled */ if (TransferStatus != SIO_NoFrame) Log_print("Unexpected command frame at state %x.", TransferStatus); CommandIndex = 0; DataIndex = 0; ExpectedBytes = 5; TransferStatus = SIO_CommandFrame; } else { if (TransferStatus != SIO_StatusRead && TransferStatus != SIO_NoFrame && TransferStatus != SIO_ReadFrame) { if (!(TransferStatus == SIO_CommandFrame && CommandIndex == 0)) Log_print("Command frame %02x unfinished.", TransferStatus); TransferStatus = SIO_NoFrame; } CommandIndex = 0; } } static UBYTE WriteSectorBack(void) { UWORD sector; UBYTE unit; sector = CommandFrame[2] + (CommandFrame[3] << 8); unit = CommandFrame[0] - '1'; if (unit >= SIO_MAX_DRIVES) /* UBYTE range ! */ return 0; switch (CommandFrame[1]) { case 0x4f: /* Write Status Block */ return SIO_WriteStatusBlock(unit, DataBuffer); case 0x50: /* Write */ case 0x57: case 0xD0: /* xf551 hispeed */ case 0xD7: return SIO_WriteSector(unit, sector, DataBuffer); default: return 'E'; } } /* Put a byte that comes out of POKEY. So get it here... */ void SIO_PutByte(int byte) { switch (TransferStatus) { case SIO_CommandFrame: if (CommandIndex < ExpectedBytes) { CommandFrame[CommandIndex++] = byte; if (CommandIndex >= ExpectedBytes) { if (CommandFrame[0] >= 0x31 && CommandFrame[0] <= 0x38) { TransferStatus = SIO_StatusRead; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL + SIO_ACK_INTERVAL; } else TransferStatus = SIO_NoFrame; } } else { Log_print("Invalid command frame!"); TransferStatus = SIO_NoFrame; } break; case SIO_WriteFrame: /* Expect data */ if (DataIndex < ExpectedBytes) { DataBuffer[DataIndex++] = byte; if (DataIndex >= ExpectedBytes) { UBYTE sum = SIO_ChkSum(DataBuffer, ExpectedBytes - 1); if (sum == DataBuffer[ExpectedBytes - 1]) { UBYTE result = WriteSectorBack(); if (result != 0) { DataBuffer[0] = 'A'; DataBuffer[1] = result; DataIndex = 0; ExpectedBytes = 2; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL + SIO_ACK_INTERVAL; TransferStatus = SIO_FinalStatus; } else TransferStatus = SIO_NoFrame; } else { DataBuffer[0] = 'E'; DataIndex = 0; ExpectedBytes = 1; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL + SIO_ACK_INTERVAL; TransferStatus = SIO_FinalStatus; } } } else { Log_print("Invalid data frame!"); } break; case SIO_CasWrite: CASSETTE_PutByte(byte); break; } /* POKEY_DELAYED_SEROUT_IRQ = SIO_SEROUT_INTERVAL; */ /* already set in pokey.c */ } /* Get a byte from the floppy to the pokey. */ int SIO_GetByte(void) { int byte = 0; switch (TransferStatus) { case SIO_StatusRead: byte = Command_Frame(); /* Handle now the command */ break; case SIO_FormatFrame: TransferStatus = SIO_ReadFrame; POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL << 3; /* FALL THROUGH */ case SIO_ReadFrame: if (DataIndex < ExpectedBytes) { byte = DataBuffer[DataIndex++]; if (DataIndex >= ExpectedBytes) { TransferStatus = SIO_NoFrame; } else { /* set delay using the expected transfer speed */ POKEY_DELAYED_SERIN_IRQ = (DataIndex == 1) ? SIO_SERIN_INTERVAL : ((SIO_SERIN_INTERVAL * POKEY_AUDF[POKEY_CHAN3] - 1) / 0x28 + 1); } } else { Log_print("Invalid read frame!"); TransferStatus = SIO_NoFrame; } break; case SIO_FinalStatus: if (DataIndex < ExpectedBytes) { byte = DataBuffer[DataIndex++]; if (DataIndex >= ExpectedBytes) { TransferStatus = SIO_NoFrame; } else { if (DataIndex == 0) POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL + SIO_ACK_INTERVAL; else POKEY_DELAYED_SERIN_IRQ = SIO_SERIN_INTERVAL; } } else { Log_print("Invalid read frame!"); TransferStatus = SIO_NoFrame; } break; case SIO_CasRead: byte = CASSETTE_GetByte(); POKEY_DELAYED_SERIN_IRQ = CASSETTE_GetInputIRQDelay(); break; default: break; } return byte; } #if !defined(BASIC) && !defined(__PLUS) int SIO_RotateDisks(void) { char tmp_filenames[SIO_MAX_DRIVES][FILENAME_MAX]; int i; int bSuccess = TRUE; for (i = 0; i < SIO_MAX_DRIVES; i++) { strcpy(tmp_filenames[i], SIO_filename[i]); SIO_Dismount(i + 1); } for (i = 1; i < SIO_MAX_DRIVES; i++) { if (strcmp(tmp_filenames[i], "None") && strcmp(tmp_filenames[i], "Off") && strcmp(tmp_filenames[i], "Empty") ) { if (!SIO_Mount(i, tmp_filenames[i], FALSE)) /* Note that this is NOT i-1 because SIO_Mount is 1 indexed */ bSuccess = FALSE; } } i = SIO_MAX_DRIVES - 1; while (i > -1 && (!strcmp(tmp_filenames[i], "None") || !strcmp(tmp_filenames[i], "Off") || !strcmp(tmp_filenames[i], "Empty")) ) { i--; } if (i > -1) { if (!SIO_Mount(i + 1, tmp_filenames[0], FALSE)) bSuccess = FALSE; } return bSuccess; } #endif /* !defined(BASIC) && !defined(__PLUS) */ #ifndef BASIC void SIO_StateSave(void) { int i; for (i = 0; i < 8; i++) { StateSav_SaveINT((int *) &SIO_drive_status[i], 1); StateSav_SaveFNAME(SIO_filename[i]); } } void SIO_StateRead(void) { int i; for (i = 0; i < 8; i++) { int saved_drive_status; char filename[FILENAME_MAX]; StateSav_ReadINT(&saved_drive_status, 1); SIO_drive_status[i] = (SIO_UnitStatus)saved_drive_status; StateSav_ReadFNAME(filename); if (filename[0] == 0) continue; /* If the disk drive wasn't empty or off when saved, mount the disk */ switch (saved_drive_status) { case SIO_READ_ONLY: SIO_Mount(i + 1, filename, TRUE); break; case SIO_READ_WRITE: SIO_Mount(i + 1, filename, FALSE); break; default: break; } } } #endif /* BASIC */