1330 lines
37 KiB
C
1330 lines
37 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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 */
|