670 lines
14 KiB
C++
670 lines
14 KiB
C++
/*
|
|
Copyright 2005, 2006, 2007 Dennis van Weeren
|
|
Copyright 2008, 2009 Jakub Bednarski
|
|
|
|
This file is part of Minimig
|
|
|
|
Minimig is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Minimig 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// 2009-11-14 - adapted gap size
|
|
// 2009-12-24 - updated sync word list
|
|
// - fixed sector header generation
|
|
// 2010-01-09 - support for variable number of tracks
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "../../hardware.h"
|
|
#include "../../file_io.h"
|
|
#include "minimig_fdd.h"
|
|
#include "minimig_config.h"
|
|
#include "../../debug.h"
|
|
#include "../../user_io.h"
|
|
#include "../../menu.h"
|
|
|
|
unsigned char drives = 0; // number of active drives reported by FPGA (may change only during reset)
|
|
adfTYPE *pdfx; // drive select pointer
|
|
adfTYPE df[4] = {}; // drive information structure
|
|
|
|
static uint8_t sector_buffer[512];
|
|
|
|
unsigned char Error;
|
|
|
|
#define TRACK_SIZE 12668
|
|
#define HEADER_SIZE 0x40
|
|
#define DATA_SIZE 0x400
|
|
#define SECTOR_SIZE (HEADER_SIZE + DATA_SIZE)
|
|
#define SECTOR_COUNT 11
|
|
#define LAST_SECTOR (SECTOR_COUNT - 1)
|
|
#define GAP_SIZE (TRACK_SIZE - SECTOR_COUNT * SECTOR_SIZE)
|
|
|
|
#define B2W(a,b) (((((uint16_t)(a))<<8) & 0xFF00) | ((uint16_t)(b) & 0x00FF))
|
|
|
|
// sends the data in the sector buffer to the FPGA, translated into an Amiga floppy format sector
|
|
// note that we do not insert clock bits because they will be stripped by the Amiga software anyway
|
|
void SendSector(unsigned char *pData, unsigned char sector, unsigned char track, unsigned char dsksynch, unsigned char dsksyncl)
|
|
{
|
|
unsigned char checksum[4];
|
|
unsigned short i;
|
|
unsigned char x,y;
|
|
unsigned char *p;
|
|
|
|
// preamble
|
|
spi_w(0xAAAA);
|
|
spi_w(0xAAAA);
|
|
|
|
// synchronization
|
|
spi_w(B2W(dsksynch, dsksyncl));
|
|
spi_w(B2W(dsksynch, dsksyncl));
|
|
|
|
// odd bits of header
|
|
x = 0x55;
|
|
checksum[0] = x;
|
|
y = (track >> 1) & 0x55;
|
|
checksum[1] = y;
|
|
spi_w(B2W(x,y));
|
|
|
|
x = (sector >> 1) & 0x55;
|
|
checksum[2] = x;
|
|
y = ((11 - sector) >> 1) & 0x55;
|
|
checksum[3] = y;
|
|
spi_w(B2W(x, y));
|
|
|
|
// even bits of header
|
|
x = 0x55;
|
|
checksum[0] ^= x;
|
|
y = track & 0x55;
|
|
checksum[1] ^= y;
|
|
spi_w(B2W(x, y));
|
|
|
|
x = sector & 0x55;
|
|
checksum[2] ^= x;
|
|
y = (11 - sector) & 0x55;
|
|
checksum[3] ^= y;
|
|
spi_w(B2W(x, y));
|
|
|
|
// sector label and reserved area (changes nothing to checksum)
|
|
i = 0x10;
|
|
while (i--) spi_w(0xAAAA);
|
|
|
|
// send header checksum
|
|
spi_w(0xAAAA);
|
|
spi_w(0xAAAA);
|
|
spi_w(B2W(checksum[0] | 0xAA, checksum[1] | 0xAA));
|
|
spi_w(B2W(checksum[2] | 0xAA, checksum[3] | 0xAA));
|
|
|
|
// calculate data checksum
|
|
checksum[0] = 0;
|
|
checksum[1] = 0;
|
|
checksum[2] = 0;
|
|
checksum[3] = 0;
|
|
|
|
p = pData;
|
|
i = DATA_SIZE / 2 / 4;
|
|
while (i--)
|
|
{
|
|
x = *p++;
|
|
checksum[0] ^= x ^ x >> 1;
|
|
x = *p++;
|
|
checksum[1] ^= x ^ x >> 1;
|
|
x = *p++;
|
|
checksum[2] ^= x ^ x >> 1;
|
|
x = *p++;
|
|
checksum[3] ^= x ^ x >> 1;
|
|
}
|
|
|
|
// send data checksum
|
|
spi_w(0xAAAA);
|
|
spi_w(0xAAAA);
|
|
spi_w(B2W(checksum[0] | 0xAA, checksum[1] | 0xAA));
|
|
spi_w(B2W(checksum[2] | 0xAA, checksum[3] | 0xAA));
|
|
|
|
// odd bits of data field
|
|
i = DATA_SIZE / 4;
|
|
p = pData;
|
|
while (i--)
|
|
{
|
|
x = (*p++ >> 1) | 0xAA;
|
|
y = (*p++ >> 1) | 0xAA;
|
|
spi_w(B2W(x, y));
|
|
}
|
|
|
|
// even bits of data field
|
|
i = DATA_SIZE / 4;
|
|
p = pData;
|
|
while (i--)
|
|
{
|
|
x = *p++ | 0xAA;
|
|
y = *p++ | 0xAA;
|
|
spi_w(B2W(x, y));
|
|
}
|
|
}
|
|
|
|
void SendGap(void)
|
|
{
|
|
unsigned short i = GAP_SIZE/2;
|
|
while (i--) spi_w(0xAAAA);
|
|
}
|
|
|
|
// read a track from disk
|
|
void ReadTrack(adfTYPE *drive)
|
|
{
|
|
// track number is updated in drive struct before calling this function
|
|
|
|
unsigned char sector;
|
|
unsigned char status;
|
|
unsigned char track;
|
|
unsigned short dsksync;
|
|
uint16_t tmp;
|
|
|
|
if (drive->track >= drive->tracks)
|
|
{
|
|
fdd_debugf("Illegal track read: %d\n", drive->track);
|
|
drive->track = drive->tracks - 1;
|
|
}
|
|
|
|
unsigned long lba;
|
|
|
|
if (drive->track != drive->track_prev)
|
|
{ // track step or track 0, start at beginning of track
|
|
drive->track_prev = drive->track;
|
|
sector = 0;
|
|
drive->sector_offset = sector;
|
|
lba = drive->track * SECTOR_COUNT;
|
|
}
|
|
else
|
|
{ // same track, start at next sector in track
|
|
sector = drive->sector_offset;
|
|
lba = (drive->track * SECTOR_COUNT) + sector;
|
|
}
|
|
|
|
if (!FileSeekLBA(&drive->file, lba))
|
|
{
|
|
return;
|
|
}
|
|
|
|
EnableFpga();
|
|
tmp = spi_w(0);
|
|
status = (uint8_t)(tmp>>8); // read request signal
|
|
track = (uint8_t)tmp; // track number (cylinder & head)
|
|
dsksync = spi_w(0); // disk sync
|
|
spi_w(0); // mfm words to transfer
|
|
DisableFpga();
|
|
|
|
if (track >= drive->tracks)
|
|
track = drive->tracks - 1;
|
|
|
|
while (1)
|
|
{
|
|
FileReadSec(&drive->file, sector_buffer);
|
|
|
|
EnableFpga();
|
|
|
|
// check if FPGA is still asking for data
|
|
tmp = spi_w(0);
|
|
status = (uint8_t)(tmp >> 8); // read request signal
|
|
track = (uint8_t)tmp; // track number (cylinder & head)
|
|
dsksync = spi_w(0); // disk sync
|
|
spi_w(0); // mfm words to transfer
|
|
|
|
if (track >= drive->tracks)
|
|
track = drive->tracks - 1;
|
|
|
|
// workaround for Copy Lock in Wiz'n'Liz and North&South (might brake other games)
|
|
if (dsksync == 0x0000 || dsksync == 0x8914 || dsksync == 0xA144)
|
|
dsksync = 0x4489;
|
|
|
|
// North&South: $A144
|
|
// Wiz'n'Liz (Copy Lock): $8914
|
|
// Prince of Persia: $4891
|
|
// Commando: $A245
|
|
|
|
// some loaders stop dma if sector header isn't what they expect
|
|
// because we don't check dma transfer count after sending a word
|
|
// the track can be changed while we are sending the rest of the previous sector
|
|
// in this case let's start transfer from the beginning
|
|
if (track == drive->track)
|
|
{
|
|
// send sector if fpga is still asking for data
|
|
if (status & CMD_RDTRK)
|
|
{
|
|
//GenerateHeader(sector_header, sector_buffer, sector, track, dsksync);
|
|
//SendSector(sector_header, sector_buffer);
|
|
SendSector(sector_buffer, sector, track, (unsigned char)(dsksync >> 8), (unsigned char)dsksync);
|
|
|
|
if (sector == LAST_SECTOR)
|
|
SendGap();
|
|
}
|
|
}
|
|
|
|
// we are done accessing FPGA
|
|
DisableFpga();
|
|
|
|
// track has changed
|
|
if (track != drive->track)
|
|
break;
|
|
|
|
// read dma request
|
|
if (!(status & CMD_RDTRK))
|
|
break;
|
|
|
|
sector++;
|
|
if (sector >= SECTOR_COUNT)
|
|
{
|
|
// go to the start of current track
|
|
sector = 0;
|
|
lba = drive->track * SECTOR_COUNT;
|
|
if (!FileSeekLBA(&drive->file, lba))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// remember current sector
|
|
drive->sector_offset = sector;
|
|
}
|
|
}
|
|
|
|
unsigned char FindSync(adfTYPE *drive)
|
|
// reads data from fifo till it finds sync word or fifo is empty and dma inactive (so no more data is expected)
|
|
{
|
|
unsigned char c1, c2;
|
|
unsigned short n;
|
|
uint16_t tmp;
|
|
|
|
while (1)
|
|
{
|
|
EnableFpga();
|
|
tmp = spi_w(0);
|
|
c1 = (uint8_t)(tmp >> 8); // write request signal
|
|
c2 = (uint8_t)tmp; // track number (cylinder & head)
|
|
if (!(c1 & CMD_WRTRK))
|
|
break;
|
|
if (c2 != drive->track)
|
|
break;
|
|
spi_w(0); //disk sync word
|
|
|
|
n = spi_w(0) & 0xBFFF; // mfm words to transfer
|
|
|
|
if (n == 0)
|
|
break;
|
|
|
|
n &= 0x3FFF;
|
|
|
|
while (n--)
|
|
{
|
|
if (spi_w(0) == 0x4489)
|
|
{
|
|
DisableFpga();
|
|
return 1;
|
|
}
|
|
}
|
|
DisableFpga();
|
|
}
|
|
DisableFpga();
|
|
return 0;
|
|
}
|
|
|
|
unsigned char GetHeader(unsigned char *pTrack, unsigned char *pSector)
|
|
// this function reads data from fifo till it finds sync word or dma is inactive
|
|
{
|
|
unsigned char c, c1, c2, c3, c4;
|
|
unsigned char i;
|
|
unsigned char checksum[4];
|
|
uint16_t tmp;
|
|
|
|
Error = 0;
|
|
while (1)
|
|
{
|
|
EnableFpga();
|
|
c1 = (uint8_t)(spi_w(0)>>8); // write request signal, track number (cylinder & head)
|
|
if (!(c1 & CMD_WRTRK))
|
|
break;
|
|
spi_w(0); //disk sync word
|
|
tmp = spi_w(0); // mfm words to transfer
|
|
|
|
if ((tmp & 0x3F00) != 0 || (tmp & 0xFF) > 24)// remaining header data is 25 mfm words
|
|
{
|
|
tmp = spi_w(0); // second sync
|
|
if (tmp != 0x4489)
|
|
{
|
|
Error = 21;
|
|
fdd_debugf("\nSecond sync word missing...\n");
|
|
break;
|
|
}
|
|
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[0] = c;
|
|
c1 = (c & 0x55) << 1;
|
|
c = (uint8_t)tmp;
|
|
checksum[1] = c;
|
|
c2 = (c & 0x55) << 1;
|
|
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[2] = c;
|
|
c3 = (c & 0x55) << 1;
|
|
c = (uint8_t)tmp;
|
|
checksum[3] = c;
|
|
c4 = (c & 0x55) << 1;
|
|
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[0] ^= c;
|
|
c1 |= c & 0x55;
|
|
c = (uint8_t)tmp;
|
|
checksum[1] ^= c;
|
|
c2 |= c & 0x55;
|
|
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[2] ^= c;
|
|
c3 |= c & 0x55;
|
|
c = (uint8_t)tmp;
|
|
checksum[3] ^= c;
|
|
c4 |= c & 0x55;
|
|
|
|
if (c1 != 0xFF) // always 0xFF
|
|
Error = 22;
|
|
else if (c2 > 159) // Track number (0-159)
|
|
Error = 23;
|
|
else if (c3 > 10) // Sector number (0-10)
|
|
Error = 24;
|
|
else if (c4 > 11 || c4 == 0) // Number of sectors to gap (1-11)
|
|
Error = 25;
|
|
|
|
if (Error)
|
|
{
|
|
fdd_debugf("\nWrong header: %u.%u.%u.%u\n", c1, c2, c3, c4);
|
|
break;
|
|
}
|
|
|
|
*pTrack = c2;
|
|
*pSector = c3;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
tmp = spi_w(0);
|
|
checksum[0] ^= (uint8_t)(tmp >> 8);
|
|
checksum[1] ^= (uint8_t)tmp;
|
|
tmp = spi_w(0);
|
|
checksum[2] ^= (uint8_t)(tmp >> 8);
|
|
checksum[3] ^= (uint8_t)tmp;
|
|
}
|
|
|
|
checksum[0] &= 0x55;
|
|
checksum[1] &= 0x55;
|
|
checksum[2] &= 0x55;
|
|
checksum[3] &= 0x55;
|
|
|
|
tmp = (spi_w(0) & 0x5555) << 1;
|
|
c1 = (uint8_t)(tmp >> 8);
|
|
c2 = (uint8_t)tmp;
|
|
tmp = (spi_w(0) & 0x5555) << 1;
|
|
c3 = (uint8_t)(tmp >> 8);
|
|
c4 = (uint8_t)tmp;
|
|
|
|
tmp = spi_w(0) & 0x5555;
|
|
c1 |= (uint8_t)(tmp >> 8);
|
|
c2 |= (uint8_t)tmp;
|
|
tmp = spi_w(0) & 0x5555;
|
|
c3 |= (uint8_t)(tmp >> 8);
|
|
c4 |= (uint8_t)tmp;
|
|
|
|
if (c1 != checksum[0] || c2 != checksum[1] || c3 != checksum[2] || c4 != checksum[3])
|
|
{
|
|
Error = 26;
|
|
break;
|
|
}
|
|
|
|
DisableFpga();
|
|
return 1;
|
|
}
|
|
else if ((tmp & 0x8000) == 0) // not enough data for header and write dma is not active
|
|
{
|
|
Error = 20;
|
|
break;
|
|
}
|
|
|
|
DisableFpga();
|
|
}
|
|
|
|
DisableFpga();
|
|
return 0;
|
|
}
|
|
|
|
unsigned char GetData(void)
|
|
{
|
|
unsigned char c, c1, c2, c3, c4;
|
|
unsigned char i;
|
|
unsigned char *p;
|
|
unsigned short n;
|
|
unsigned char checksum[4];
|
|
uint16_t tmp;
|
|
|
|
Error = 0;
|
|
while (1)
|
|
{
|
|
EnableFpga();
|
|
c1 = (uint8_t)(spi_w(0) >> 8); // write request signal, track number (cylinder & head)
|
|
if (!(c1 & CMD_WRTRK))
|
|
break;
|
|
spi_w(0);
|
|
tmp = spi_w(0); // mfm words to transfer
|
|
|
|
n = tmp & 0x3FFF;
|
|
|
|
if (n >= 0x204)
|
|
{
|
|
tmp = (spi_w(0) & 0x5555) << 1;
|
|
c1 = (uint8_t)(tmp >> 8);
|
|
c2 = (uint8_t)tmp & 0x55;
|
|
tmp = (spi_w(0) & 0x5555) << 1;
|
|
c3 = (uint8_t)(tmp >> 8);
|
|
c4 = (uint8_t)tmp;
|
|
|
|
tmp = spi_w(0) & 0x5555;
|
|
c1 |= (uint8_t)(tmp >> 8);
|
|
c2 |= (uint8_t)tmp;
|
|
tmp = spi_w(0) & 0x5555;
|
|
c3 |= (uint8_t)(tmp >> 8);
|
|
c4 |= (uint8_t)tmp;
|
|
|
|
checksum[0] = 0;
|
|
checksum[1] = 0;
|
|
checksum[2] = 0;
|
|
checksum[3] = 0;
|
|
|
|
// odd bits of data field
|
|
i = 128;
|
|
p = sector_buffer;
|
|
do
|
|
{
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[0] ^= c;
|
|
*p++ = (c & 0x55) << 1;
|
|
c = (uint8_t)tmp;
|
|
checksum[1] ^= c;
|
|
*p++ = (c & 0x55) << 1;
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[2] ^= c;
|
|
*p++ = (c & 0x55) << 1;
|
|
c = (uint8_t)tmp;
|
|
checksum[3] ^= c;
|
|
*p++ = (c & 0x55) << 1;
|
|
} while (--i);
|
|
|
|
// even bits of data field
|
|
i = 128;
|
|
p = sector_buffer;
|
|
do
|
|
{
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[0] ^= c;
|
|
*p++ |= c & 0x55;
|
|
c = (uint8_t)tmp;
|
|
checksum[1] ^= c;
|
|
*p++ |= c & 0x55;
|
|
tmp = spi_w(0);
|
|
c = (uint8_t)(tmp >> 8);
|
|
checksum[2] ^= c;
|
|
*p++ |= c & 0x55;
|
|
c = (uint8_t)tmp;
|
|
checksum[3] ^= c;
|
|
*p++ |= c & 0x55;
|
|
} while (--i);
|
|
|
|
checksum[0] &= 0x55;
|
|
checksum[1] &= 0x55;
|
|
checksum[2] &= 0x55;
|
|
checksum[3] &= 0x55;
|
|
|
|
if (c1 != checksum[0] || c2 != checksum[1] || c3 != checksum[2] || c4 != checksum[3])
|
|
{
|
|
Error = 29;
|
|
break;
|
|
}
|
|
|
|
DisableFpga();
|
|
return 1;
|
|
}
|
|
else if ((tmp & 0x8000) == 0) // not enough data in fifo and write dma is not active
|
|
{
|
|
Error = 28;
|
|
break;
|
|
}
|
|
|
|
DisableFpga();
|
|
}
|
|
DisableFpga();
|
|
return 0;
|
|
}
|
|
|
|
void WriteTrack(adfTYPE *drive)
|
|
{
|
|
unsigned char Track;
|
|
unsigned char Sector;
|
|
|
|
unsigned long lba = drive->track * SECTOR_COUNT;
|
|
|
|
drive->track_prev = drive->track + 1; // just to force next read from the start of current track
|
|
|
|
while (FindSync(drive))
|
|
{
|
|
if (GetHeader(&Track, &Sector))
|
|
{
|
|
if (Track == drive->track)
|
|
{
|
|
if (!FileSeekLBA(&drive->file, lba+Sector))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (GetData())
|
|
{
|
|
if (drive->status & DSK_WRITABLE)
|
|
{
|
|
FileWriteSec(&drive->file, sector_buffer);
|
|
}
|
|
else
|
|
{
|
|
Error = 30;
|
|
fdd_debugf("Write attempt to protected disk!\n");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
Error = 27; //track number reported in sector header is not the same as current drive track
|
|
}
|
|
if (Error)
|
|
{
|
|
fdd_debugf("WriteTrack: error %u\n", Error);
|
|
ErrorMessage(" WriteTrack", Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateDriveStatus(void)
|
|
{
|
|
EnableFpga();
|
|
spi_w(0x1000 | df[0].status | (df[1].status << 1) | (df[2].status << 2) | (df[3].status << 3));
|
|
DisableFpga();
|
|
}
|
|
|
|
void HandleFDD(unsigned char c1, unsigned char c2)
|
|
{
|
|
unsigned char sel;
|
|
drives = (c1 >> 4) & 0x03; // number of active floppy drives
|
|
|
|
if (c1 & CMD_RDTRK)
|
|
{
|
|
DISKLED_ON;
|
|
sel = (c1 >> 6) & 0x03;
|
|
df[sel].track = c2;
|
|
ReadTrack(&df[sel]);
|
|
DISKLED_OFF;
|
|
}
|
|
else if (c1 & CMD_WRTRK)
|
|
{
|
|
DISKLED_ON;
|
|
sel = (c1 >> 6) & 0x03;
|
|
df[sel].track = c2;
|
|
WriteTrack(&df[sel]);
|
|
DISKLED_OFF;
|
|
}
|
|
}
|
|
|
|
// insert floppy image pointed to to by global <file> into <drive>
|
|
void InsertFloppy(adfTYPE *drive, char* path)
|
|
{
|
|
int writable = FileCanWrite(path);
|
|
|
|
if (!FileOpenEx(&drive->file, path, writable ? O_RDWR | O_SYNC : O_RDONLY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
unsigned long tracks;
|
|
|
|
// calculate number of tracks in the ADF image file
|
|
tracks = drive->file.size / (512 * 11);
|
|
if (tracks > MAX_TRACKS)
|
|
{
|
|
menu_debugf("UNSUPPORTED ADF SIZE!!! Too many tracks: %lu\n", tracks);
|
|
tracks = MAX_TRACKS;
|
|
}
|
|
drive->tracks = (unsigned char)tracks;
|
|
|
|
strcpy(drive->name, path);
|
|
|
|
// initialize the rest of drive struct
|
|
drive->status = DSK_INSERTED;
|
|
if (writable) // read-only attribute
|
|
drive->status |= DSK_WRITABLE;
|
|
|
|
drive->sector_offset = 0;
|
|
drive->track = 0;
|
|
drive->track_prev = -1;
|
|
|
|
menu_debugf("Inserting floppy: \"%s\"\n", path);
|
|
menu_debugf("file writable: %d\n", writable);
|
|
menu_debugf("file size: %lu (%lu KB)\n", drive->file.size, drive->file.size >> 10);
|
|
menu_debugf("drive tracks: %u\n", drive->tracks);
|
|
menu_debugf("drive status: 0x%02X\n", drive->status);
|
|
}
|