Project

General

Profile

/*! \file atx.c \brief ATX file handling. */
//*****************************************************************************
//
// File Name : 'atx.c'
// Title : ATX file handling
// Author : Daniel Noguerol
// Date : 21/01/2018
// Revised : 21/01/2018
// Version : 0.1
// Target MCU : ???
// Editor Tabs : 4
//
// NOTE: This code is currently below version 1.0, and therefore is considered
// to be lacking in some functionality or documentation, or may not be fully
// tested. Nonetheless, you can expect most functions to work.
//
// This code is distributed under the GNU Public License
// which can be found at http://www.gnu.org/licenses/gpl.txt
//
//*****************************************************************************

#include "atx_eclaire.h"
#include "atx.h"

// number of angular units in a full disk rotation
#define AU_FULL_ROTATION 26042
// number of angular units to read one sector
#define AU_ONE_SECTOR_READ 1208
// number of ms for each angular unit
#define MS_ANGULAR_UNIT_VAL 0.007999897601
// number of milliseconds drive takes to process a request
#define MS_DRIVE_REQUEST_DELAY 3.22
// number of milliseconds to calculate CRC
#define MS_CRC_CALCULATION 2
// number of milliseconds drive takes to step 1 track
#define MS_TRACK_STEP 5.3
// number of milliseconds drive head takes to settle after track stepping
#define MS_HEAD_SETTLE 0
// mask for checking FDC status "data lost" bit
#define MASK_FDC_DLOST 0x04
// mask for checking FDC status "missing" bit
#define MASK_FDC_MISSING 0x10
// mask for checking FDC status extended data bit
#define MASK_EXTENDED_DATA 0x40

#define MAX_RETRIES_1050 1
#define MAX_RETRIES_810 4

struct atxTrackInfo {
u32 offset; // absolute position within file for start of track header
};

extern unsigned char atari_sector_buffer[256];
extern u16 last_angle_returned; // extern so we can display it on the screen

u16 gBytesPerSector; // number of bytes per sector
u08 gSectorsPerTrack; // number of sectors in each track
struct atxTrackInfo gTrackInfo[NUM_ATX_DRIVES][40]; // pre-calculated info for each track and drive
// support slot D1 and D2 only because of insufficient RAM!
u16 gLastAngle;
u08 gCurrentHeadTrack;

u16 loadAtxFile(u08 drive) {
struct atxFileHeader *fileHeader;
struct atxTrackHeader *trackHeader;

// read the file header
faccess_offset(FILE_ACCESS_READ, 0, sizeof(struct atxFileHeader));
byteSwapAtxFileHeader((struct atxFileHeader *) atari_sector_buffer);

// validate the ATX file header
fileHeader = (struct atxFileHeader *) atari_sector_buffer;
if (fileHeader->signature[0] != 'A' ||
fileHeader->signature[1] != 'T' ||
fileHeader->signature[2] != '8' ||
fileHeader->signature[3] != 'X' ||
fileHeader->version != ATX_VERSION ||
fileHeader->minVersion != ATX_VERSION) {
return 0;
}

// enhanced density is 26 sectors per track, single and double density are 18
gSectorsPerTrack = (fileHeader->density == 1) ? (u08) 26 : (u08) 18;
// single and enhanced density are 128 bytes per sector, double density is 256
gBytesPerSector = (fileHeader->density == 1) ? (u16) 256 : (u16) 128;

// calculate track offsets
u32 startOffset = fileHeader->startData;
while (1) {
if (!faccess_offset(FILE_ACCESS_READ, startOffset, sizeof(struct atxTrackHeader))) {
break;
}
trackHeader = (struct atxTrackHeader *) atari_sector_buffer;
byteSwapAtxTrackHeader(trackHeader);
gTrackInfo[drive][trackHeader->trackNumber].offset = startOffset;
startOffset += trackHeader->size;
}

return gBytesPerSector;
}

u16 loadAtxSector(u08 drive, u16 num, unsigned short *sectorSize, u08 *status) {
struct atxTrackHeader *trackHeader;
struct atxSectorListHeader *slHeader;
struct atxSectorHeader *sectorHeader;
struct atxTrackChunk *extSectorData;

u16 i;
u16 tgtSectorIndex = 0; // the index of the target sector within the sector list
u32 tgtSectorOffset = 0; // the offset of the target sector data
BOOL hasError = (BOOL) FALSE; // flag for drive status errors

// local variables used for weak data handling
u08 extendedDataRecords = 0;
int16_t weakOffset = -1;

// calculate track and relative sector number from the absolute sector number
u08 tgtTrackNumber = (num - 1) / gSectorsPerTrack + 1;
u08 tgtSectorNumber = (num - 1) % gSectorsPerTrack + 1;

// set initial status (in case the target sector is not found)
*status = 0x10;
// set the sector size
*sectorSize = gBytesPerSector;

// immediately fail on track read > 40
if (tgtTrackNumber > 40) {
return 0;
}

// delay for the time the drive takes to process the request
_delay_ms(MS_DRIVE_REQUEST_DELAY);

// delay for track stepping if needed
if (gCurrentHeadTrack != tgtTrackNumber) {
signed char diff;
diff = tgtTrackNumber - gCurrentHeadTrack;
if (diff < 0) diff *= -1;
// wait for each track (this is done in a loop since _delay_ms needs a compile-time constant)
for (i = 0; i < diff; i++) {
_delay_ms(MS_TRACK_STEP);
}
// delay for head settling
_delay_ms(MS_HEAD_SETTLE);
}

// set new head track position
gCurrentHeadTrack = tgtTrackNumber;

// sample current head position
u16 headPosition = getCurrentHeadPosition();

// read the track header
u32 currentFileOffset = gTrackInfo[drive][tgtTrackNumber - 1].offset;
faccess_offset(FILE_ACCESS_READ, currentFileOffset, sizeof(struct atxTrackHeader));
trackHeader = (struct atxTrackHeader *) atari_sector_buffer;
byteSwapAtxTrackHeader(trackHeader);
u16 sectorCount = trackHeader->sectorCount;

// if there are no sectors in this track or the track number doesn't match, return error
if (trackHeader->trackNumber == tgtTrackNumber - 1) {
// read the sector list header if there are sectors for this track
if (sectorCount > 0) {
currentFileOffset += trackHeader->headerSize;
faccess_offset(FILE_ACCESS_READ, currentFileOffset, sizeof(struct atxSectorListHeader));
slHeader = (struct atxSectorListHeader *) atari_sector_buffer;
byteSwapAtxSectorListHeader(slHeader);

// sector list header is variable length, so skip any extra header bytes that may be present
currentFileOffset += slHeader->next - sectorCount * sizeof(struct atxSectorHeader);
}

int pTT = 0;
int retries = MAX_RETRIES_810;

// if we are still below the maximum number of retries that would be performed by the drive firmware...
u32 retryOffset = currentFileOffset;
while (retries > 0) {
retries--;
currentFileOffset = retryOffset;
// iterate through all sector headers to find the target sector
for (i=0; i < sectorCount; i++) {
if (faccess_offset(FILE_ACCESS_READ, currentFileOffset, sizeof(struct atxSectorHeader))) {
sectorHeader = (struct atxSectorHeader *) atari_sector_buffer;
byteSwapAtxSectorHeader(sectorHeader);
// if the sector is not flagged as missing and its number matches the one we're looking for...
if (!(sectorHeader->status & MASK_FDC_MISSING) && sectorHeader->number == tgtSectorNumber) {
// check if it's the next sector that the head would encounter angularly...
int tt = sectorHeader->timev - headPosition;
if (pTT == 0 || (tt > 0 && pTT < 0) || (tt > 0 && pTT > 0 && tt < pTT) || (tt < 0 && pTT < 0 && tt < pTT)) {
pTT = tt;
gLastAngle = sectorHeader->timev;
*status = sectorHeader->status;
// On an Atari 810, we have to do some specific behavior
// when a long sector is encountered (the lost data bit
// is set):
// 1. ATX images don't normally set the DRQ status bit
// because the behavior is different on 810 vs.
// 1050 drives. In the case of the 810, the DRQ bit
// should be set.
// 2. The 810 is "blind" to CRC errors on long sectors
// because it interrupts the FDC long before
// performing the CRC check.
if (*status & MASK_FDC_DLOST) {
*status |= 0x02;
}
// if the extended data flag is set, increment extended record count for later reading
if (*status & MASK_EXTENDED_DATA) {
extendedDataRecords++;
}
tgtSectorIndex = i;
tgtSectorOffset = sectorHeader->data;
}
}
currentFileOffset += sizeof(struct atxSectorHeader);
}
}
// if the sector status is bad, delay for a full disk rotation
if (*status) {
waitForAngularPosition(incAngularDisplacement(getCurrentHeadPosition(), AU_FULL_ROTATION));
// otherwise, no need to retry
} else {
retries = 0;
}
}

// store the last angle returned for the debugging window
last_angle_returned = gLastAngle;

// if the status is bad, flag as error
if (*status) {
hasError = (BOOL) TRUE;
}

// if an extended data record exists for this track, iterate through all track chunks to search
// for those records (note that we stop looking for chunks when we hit the 8-byte terminator; length == 0)
if (extendedDataRecords > 0) {
currentFileOffset = gTrackInfo[drive][tgtTrackNumber - 1].offset + trackHeader->headerSize;
do {
faccess_offset(FILE_ACCESS_READ, currentFileOffset, sizeof(struct atxTrackChunk));
extSectorData = (struct atxTrackChunk *) atari_sector_buffer;
byteSwapAtxTrackChunk(extSectorData);
if (extSectorData->size > 0) {
// if the target sector has a weak data flag, grab the start weak offset within the sector data
if (extSectorData->sectorIndex == tgtSectorIndex && extSectorData->type == 0x10) {
weakOffset = extSectorData->data;
}
currentFileOffset += extSectorData->size;
}
} while (extSectorData->size > 0);
}

// read the data (re-using tgtSectorIndex variable here to reduce stack consumption)
if (tgtSectorOffset) {
tgtSectorIndex = (u16) faccess_offset(FILE_ACCESS_READ, gTrackInfo[drive][tgtTrackNumber - 1].offset + tgtSectorOffset, gBytesPerSector);
}
if (hasError) {
tgtSectorIndex = 0;
}

// if a weak offset is defined, randomize the appropriate data
if (weakOffset > -1) {
for (i = (u16) weakOffset; i < gBytesPerSector; i++) {
atari_sector_buffer[i] = (unsigned char) (rand() % 256);
}
}

// calculate rotational delay of sector seek
u16 rotationDelay;
if (gLastAngle > headPosition) {
rotationDelay = (gLastAngle - headPosition);
} else {
rotationDelay = (AU_FULL_ROTATION - headPosition + gLastAngle);
}

// determine the angular position we need to wait for by summing the head position, rotational delay and the number
// of rotational units for a sector read. Then wait for the head to reach that position.
// (Concern: can the SD card read take more time than the amount the disk would have rotated?)
waitForAngularPosition(incAngularDisplacement(incAngularDisplacement(headPosition, rotationDelay), AU_ONE_SECTOR_READ));

// delay for CRC calculation
_delay_ms(MS_CRC_CALCULATION);
}

// the Atari expects an inverted FDC status byte
*status = ~(*status);

// return the number of bytes read
return tgtSectorIndex;
}

u16 incAngularDisplacement(u16 start, u16 delta) {
// increment an angular position by a delta taking a full rotation into consideration
u16 ret = start + delta;
if (ret > AU_FULL_ROTATION) {
ret -= AU_FULL_ROTATION;
}
return ret;
}
(5-5/46)