diff options
Diffstat (limited to 'trunk/src/lib/formats/ti99_dsk.c')
-rw-r--r-- | trunk/src/lib/formats/ti99_dsk.c | 2036 |
1 files changed, 2036 insertions, 0 deletions
diff --git a/trunk/src/lib/formats/ti99_dsk.c b/trunk/src/lib/formats/ti99_dsk.c new file mode 100644 index 00000000000..5adbde4775f --- /dev/null +++ b/trunk/src/lib/formats/ti99_dsk.c @@ -0,0 +1,2036 @@ +/********************************************************************* + * + * formats/ti99_dsk.c + * + * TI99 and Geneve disk images + * Sector Dump and Track Dump format + * + * used by TI-99/4, TI-99/4A, TI-99/8, SGCPU ("TI-99/4P"), and Geneve + * + * The Sector Dump Format is also known as v9t9 (named after the first TI + * emulator to use this format). It is a contiguous sequence of sector contents + * without track data. The first sector of the disk is located at the start of + * the image, while the last sector is at its end. + * + * The Track Dump Format is also known as pc99 (again, named after the first + * TI emulator to use this format). It is a contiguous sequence of track + * contents, containing all information including address marks and CRC, but it + * does not contain clock signals. Therefore, the format requires that the + * address marks be at the same positions within each track. + * + * Both formats allow for a broad range of medium sizes. All sectors are 256 + * bytes long. The most common formats are 9 sectors per track, single-sided, + * 40 tracks, which yields 90 KiB of sector data (known as SSSD), and 18 + * sectors per track, double-sided, and 40 tracks, which is 360 KiB (known as + * DSDD). There are rare occurances of 8/16 sectors/track + * (prototypical TI double-density controller) and 35 track media. Newer + * controllers and ROMs allow for up to 36 sectors per track and 80 tracks on + * both sides, which is 2,88 MiB (DSHD80). + * + * Different to earlier implementations, these format implementations provide + * full track read/write capabilities. For the SDF format, the missing track + * information is recreated, and a common interleave is assumed. + * + * Note that in principle, writing tracks may change track sizes. + * As neither one of the formats contains meta-information about track lengths, + * and as the image consists of a contiguous sequence of the tracks, we + * cannot allow different track sizes currently. Accordingly, the track size + * cannot be changed after the image has been created by imgtool. + * Hence, also sector count per tracks and recording type (FM or MFM) cannot be + * changed without breaking the image. + * For example, MFM disks with 18 sectors per track can only be reformatted to + * MFM/18. Moreover, while this could be handled in a future revision, + * the number of tracks should also remain unchanged. + * + * You can use blank image files (filled with zeros or arbitrary data) and use + * the Disk Manager cartridge to properly format them. Also, this can be done + * by imgtool. The image file must match the format length (representing the + * space on the physical medium). + * + * 80 track drive handling + * + * For the TI system there may be a situation where 40 track disks are used + * in 80 track drives. In these cases, the disk driver applies double-stepping, + * that is, it advances the head twice in order to seek the next track. We + * emulate this by dividing the track number by 2, which means that two tracks + * are mapped to one (0,1 -> 0; 2,3 -> 1; 4,5 -> 2; ...; 78,79 -> 39) + * + * Technical detail: The size of the track must be given before actually + * starting write_track as the controller implementation uses buffered + * transfer. + * + * Michael Zapf, Feb 2010 + * + * TODO: Link to imgtool. imgtool still uses its own format creation + * + * FIXME: If image is broken with good first track and defect higher tracks, + * tdf_guess_geometry fails to define a geometry. The guessing should + * always be done. + ********************************************************************/ + +#include <string.h> +#include <assert.h> +#include <time.h> + +#include "imageutl.h" +#include "ti99_dsk.h" + +#define SECTOR_SIZE 256 +#define TI99_IDAM_LENGTH 7 + +/* + Determines whether we are using 80 track drives. This variable is + necessary unless we get information about the dip + switch settings. It is set by the disk controller implementations + (bwg, hfdc, ti_fdc). +*/ +static int use_80_track_drives = FALSE; + +struct ti99dsk_geometry +{ + UINT8 sides; + UINT8 tracksperside; + UINT8 secspertrack; + UINT8 density; +}; + +struct ti99dsk_tag +{ + int heads; + int tracks; + int sectors; + int track_size; // Complete track; to be calculated + int format; + int first_idam; + int dam_offset; +}; + +/* + Parametrizes the format to use 80 track drives. For TI systems we + need to be able to emulate 40 track drives and 80 track drives. + This is set in the configuration, but the format has no direct access. + So as a preliminary solution we use this global function which sets the + flag. This function is called by the disk controller implementations + (bwg, hfdc, ti_fdc). +*/ +void ti99_set_80_track_drives(int use80) +{ + use_80_track_drives = use80; +} + +#define TI99_DSK_TAG "ti99dsktag" +#define TI99DSK_BLOCKNOTFOUND -1 + +/* + Searches a block containing number * byte, starting at the given + position. Returns the position of the first byte of the block. +*/ +static int find_block(const UINT8 *buffer, int start, int stop, UINT8 byte, size_t number) +{ + int i = start; + size_t current = number; + while (i < stop && current > 0) + { + if (buffer[i++] != byte) + { + current = number; + } + else + { + current--; + } + } + if (current==0) + { + return i - number; + } + else + return TI99DSK_BLOCKNOTFOUND; +} + +/* + Queried by flopdrv. Instead of the image tracks, we return the tracks + of the floppy drive. We assume we always have two heads. +*/ +static int ti99_get_heads_per_disk(floppy_image_legacy *floppy) +{ +// struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); +// return tag->heads; + return 2; +} + +/* + Queried by flopdrv. Instead of the image tracks, we return the tracks + of the floppy drive. +*/ +static int ti99_get_tracks_per_disk(floppy_image_legacy *floppy) +{ + int drivetracks = 42; + + // This may fail on startup; therefore we use a special function in + // in the floppy controller implementations to explicitly set the geometry. + if (use_80_track_drives) + { + drivetracks = 83; + } + return drivetracks; +} + +static floperr_t ti99_get_sector_length(floppy_image_legacy *floppy, int head, int track, int sector, UINT32 *sector_length) +{ + *sector_length=SECTOR_SIZE; + return FLOPPY_ERROR_SUCCESS; +} + +/* + Return the track size. We do not allow different track + sizes, so we store the track size in the tag. +*/ +static UINT32 ti99_get_track_size(floppy_image_legacy *floppy, int head, int track) +{ + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + return tag->track_size; +} + +/* +static void create_vib(UINT8 *sector0, option_resolution *params) +{ + const char *name = "UNNAMED "; + int sides, tracksperside,secspertrack,totalsectors,ausize,allocbytes,i; + + enum { NAME=0, TOTALSECS=10, SECSPERTRACK=11, SIG=12, PROT=16, TRACKS=17, SIDES=18, DENSITY=19, EXT=20, ALLOC=56 }; + + sides = option_resolution_lookup_int(params, PARAM_HEADS); + tracksperside = option_resolution_lookup_int(params, PARAM_TRACKS); + secspertrack = option_resolution_lookup_int(params, PARAM_SECTORS); + + totalsectors = sides * tracksperside * secspertrack; + + memcpy(§or0[NAME], name, 10); + + sector0[TOTALSECS] = (totalsectors>>8)&0xff; + sector0[TOTALSECS+1] = (totalsectors&0xff); + + sector0[SECSPERTRACK] = secspertrack; + sector0[SIG] = 'D'; + sector0[SIG+1] = 'S'; + sector0[SIG+2] = 'K'; + + sector0[PROT] = ' '; + sector0[TRACKS] = tracksperside; + sector0[SIDES] = sides; + sector0[DENSITY] = (secspertrack<10)? 0x01 : 0x02; + + for (i=EXT; i < ALLOC; i++) + sector0[i] = 0; + + ausize = (totalsectors / 1441) + 1; + + allocbytes = totalsectors / ausize / 8; + + for (i=ALLOC; i < ALLOC+allocbytes; i++) + sector0[i] = 0; + + while (i++ < SECTOR_SIZE) + sector0[i] = 0xff; +} +*/ +/* ========================================================================= + Sector dump format + =========================================================================*/ + +static floperr_t ti99_sdf_read_sector(floppy_image_legacy *floppy, int head, int track, int sector, void *buffer, size_t buflen); +static floperr_t ti99_sdf_write_sector(floppy_image_legacy *floppy, int head, int track, int sector, const void *buffer, size_t buflen, int ddam); + +/* ----------------------------------------------------------------------- + * Guess the geometry of the sector dump disk. + * + * Sector size is always 256 bytes. + * + * Common formats: + * 90 KiB = 40 tracks / 1 side / 9 sectors = SSSD + * 180 KiB = 40 tracks / 2 side / 9 sectors = DSSD (most common) + * = 40 tracks / 1 side / 18 sectors = SSDD (rare) + * = 80 tracks / 1 side / 9 sectors = SSSD80 (rare) + * 360 KiB = 40 tracks / 2 side / 18 sectors = DSDD (most common) + * = 80 tracks / 2 side / 9 sectors = DSSD80 (rare) + * = 80 tracks / 1 side / 18 sectors = SSDD80 (rare) + * = 40 tracks / 1 side / 36 sectors = SSHD (rare) + * 720 KiB = 80 tracks / 2 side / 18 sectors = DSDD80 (most common) + * 1440 KiB= 80 tracks / 2 side / 36 sectors = DSHD80 (most common) + * + * Moreover, there may be formats with 35 tracks (ancient) and 8 sectors/track. + * We only check for the 8 sector formats. + * 160 KiB = 40 tracks / 1 side / 16 sectors = SSDD8 + * 320 KiB = 40 tracks / 2 side / 16 sectors = DSDD8 + * + * The Volume Information Block may contain suitable information, but not + * before the disk is formatted and initialized. + * + * returns 100 if recognized, 0 if not recognized + * ----------------------------------------------------------------------- */ + +static int ti99_sdf_guess_geometry(floppy_image_legacy *floppy, UINT64 size, + struct ti99dsk_geometry *geometry) +{ + int totsecs; + typedef struct ti99_vib + { + char name[10]; // volume name (10 characters, pad with spaces) + UINT8 totsecsMSB; // disk length in sectors (big-endian) (usually 360, 720 or 1440) + UINT8 totsecsLSB; + UINT8 secspertrack; // sectors per track (usually 9 (FM) or 18 (MFM)) + UINT8 id[3]; // String "DSK" + UINT8 protection; // 'P' if disk is protected, ' ' otherwise. + UINT8 tracksperside; // tracks per side (usually 40) + UINT8 sides; // sides (1 or 2) + UINT8 density; // 0,1 (FM) or 2,3,4 (MFM) + UINT8 res[36]; // Empty for traditional disks, or up to 3 directory pointers + UINT8 abm[200]; // allocation bitmap: a 1 for each sector in use (sector 0 is LSBit of byte 0, + // sector 7 is MSBit of byte 0, sector 8 is LSBit of byte 1, etc.) + } ti99_vib; + + ti99_vib vib; + UINT32 file_size; + struct ti99dsk_geometry dummy_geometry; + + if (geometry) + memset(geometry, 0, sizeof(*geometry)); + else + geometry = &dummy_geometry; + + if (size > 0xFFFFFFFF) + return 0; + + file_size = (UINT32) size; + + // Read the Volume Information Block + floppy_image_read(floppy, &vib, 0, sizeof(vib)); + + // If we have read the sector successfully, let us parse it + totsecs = (vib.totsecsMSB << 8) | vib.totsecsLSB; + geometry->secspertrack = vib.secspertrack; + if (geometry->secspertrack == 0) + // Some images might be like this, because the original SSSD + // TI controller always assumes 9. + geometry->secspertrack = 9; + geometry->tracksperside = vib.tracksperside; + if (geometry->tracksperside == 0) + // Some images are like this, because the original SSSD TI controller + // always assumes 40. + geometry->tracksperside = 40; + geometry->sides = vib.sides; + if (geometry->sides == 0) + // Some images are like this, because the original SSSD TI controller + // always assumes that tracks beyond 40 are on side 2. */ + geometry->sides = totsecs / (geometry->secspertrack * geometry->tracksperside); + geometry->density = vib.density; + // check that the format makes sense + if (((geometry->secspertrack * geometry->tracksperside * geometry->sides) == totsecs) + && (geometry->density <= 4) && (totsecs >= 2) && (! memcmp(vib.id, "DSK", 3)) + && (file_size == totsecs*256)) + { + LOG_FORMATS("SDF/VIB consistent; tracks = %d, heads = %d, sectors = %d\n", geometry->tracksperside, geometry->sides, geometry->secspertrack); + return 100; + } + LOG_FORMATS("SDF/VIB not consistent; guessing format\n"); + + // So that was not consistent. We guess the size from the file size + // and assume that the VIB did not contain reliable data. For the + // ambiguous case we choose the most common format. + + switch (file_size) + { + case 1*40*9*256: + // 90kbytes: SSSD + case 0: + /*default:*/ + geometry->sides = 1; + geometry->tracksperside = 40; + geometry->secspertrack = 9; + geometry->density = 1; + break; + + case 2*40*9*256: + // 180kbytes: either DSSD or 18-sector-per-track SSDD. + // We assume DSSD since DSSD is more common and is supported by + // the original TI SD disk controller. + geometry->sides = 2; + geometry->tracksperside = 40; + geometry->secspertrack = 9; + geometry->density = 1; + break; + + case 1*40*16*256: + // 160kbytes: 16-sector-per-track SSDD (standard format for TI + // DD disk controller prototype, and the TI hexbus disk + // controller?) */ + geometry->sides = 1; + geometry->tracksperside = 40; + geometry->secspertrack = 16; + geometry->density = 2; + break; + + case 2*40*16*256: + // 320kbytes: 16-sector-per-track DSDD (standard format for TI + // DD disk controller prototype, and TI hexbus disk + // controller?) + geometry->sides = 2; + geometry->tracksperside = 40; + geometry->secspertrack = 16; + geometry->density = 2; + break; + + case 2*40*18*256: + // 360kbytes: 18-sector-per-track DSDD (standard format for most + // third-party DD disk controllers, but reportedly not supported by + // the original TI DD disk controller prototype) + geometry->sides = 2; + geometry->tracksperside = 40; + geometry->secspertrack = 18; + geometry->density = 2; + break; + + case 2*80*18*256: + // 720kbytes: 18-sector-per-track 80-track DSDD (Myarc only) + geometry->sides = 2; + geometry->tracksperside = 80; + geometry->secspertrack = 18; + geometry->density = 2; + break; + + case 2*80*36*256: + // 1.44Mbytes: DSHD (Myarc only) + geometry->sides = 2; + geometry->tracksperside = 80; + geometry->secspertrack = 36; + geometry->density = 3; + break; + + default: + LOG_FORMATS("Unrecognized disk image geometry\n"); + return 0; + } + + LOG_FORMATS("SDF geometry guess: tracks = %d, heads = %d, sectors = %d\n", geometry->tracksperside, geometry->sides, geometry->secspertrack); + return 100; +} + +/* + Figure out the interleave for the sector arrangement in the track. For + SDF format this is required for the read_track function. + + Interleave: + 0 7 5 3 1 8 6 4 2; + start with 0, 3, 6, 0, 3, 6, ... + 0 11 4 15 8 1 12 5 16 9 2 13 6 17 10 3 14 7 + start with 0 for every track + + Rare formats: 8 sectors + 0 3 6 1 4 7 2 5 (i*3)%8 + + 16 sectors + 0 9 2 11 4 13 6 15 8 1 10 3 12 5 14 7 (i*9)%16 + + 36 sectors + 0 11 22 ... 3 14 25 (i*11)%36 +*/ +static void guess_interleave(int sectors, int *step, int *initial) +{ + switch (sectors) + { + case 8: + *step = 3; + /* initial is the offset from track to track+1 of the starting sector number */ + *initial = 0; + break; + case 9: + *step = 7; + *initial = 3; + break; + case 16: + *step = 9; + *initial = 0; + break; + case 18: + *step = 11; + *initial = 0; + break; + case 36: + *step = 11; + *initial = 0; + break; + default: + *step = 11; + *initial = 0; + break; + } +} + +/* + Delivers a whole track. As the image consists only of + sector data, we must reconstruct the track data and + then insert the sector data. + + The track layout is exactly the Track Dump Format, except that the + CRC values must be correctly calculated. + + Total length (FM) = 247 + sectors*334; (sectors=8 or 9) + Total length (MFM) = 752 + sectors*340; (sectors=16 or 18) +*/ +static floperr_t ti99_sdf_read_track(floppy_image_legacy *floppy, int head, int track, UINT64 offset, void *buffer, size_t buflen) +{ + int sector; + int startstep; + int step; + int position=0; + unsigned short crc; + int i; + floperr_t err; + int imgtrack = track; + + UINT8 *trackdata = (UINT8*)buffer; + + /* Determine from the image geometry whether we have FM or MFM */ + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + // Do we have a 40 track disk in an 80 track drive? In that case we + // double the "width" of the tracks. */ + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + guess_interleave(tag->sectors, &step, &startstep); + + if (tag->sectors < 10) /* must be FM */ + { + /* Write lead-in. */ + memset(trackdata, 0x00, 16); + position += 16; + for (i=0; i < tag->sectors; i++) + { + /* Create interleaving */ + sector = (i*step + imgtrack*startstep) % tag->sectors; + + memset(&trackdata[position], 0x00, 6); + position += 6; + /* Set IDAM */ + trackdata[position++] = 0xfe; + trackdata[position++] = imgtrack; + trackdata[position++] = head; + trackdata[position++] = sector; + trackdata[position++] = 0x01; + /* Calculate CRC16. Preset to 0xffff (what is 0xcdb4?) */ + crc = ccitt_crc16(0xffff, &trackdata[position-(TI99_IDAM_LENGTH-2)], TI99_IDAM_LENGTH - 2); + trackdata[position++] = (crc>>8)&0xff; + trackdata[position++] = crc & 0xff; + + /* Write Gap2 */ + memset(&trackdata[position], 0xff, 11); + position += 11; + memset(&trackdata[position], 0x00, 6); + position += 6; + /* Write DAM */ + trackdata[position++] = 0xfb; + + /* Locate the sector content in the image and load it. */ + err = ti99_sdf_read_sector(floppy, head, imgtrack, sector, &trackdata[position], SECTOR_SIZE); + if (err) + return err; + + position += SECTOR_SIZE; + + /* Set CRC16 */ + crc = ccitt_crc16(0xffff, &trackdata[position-(SECTOR_SIZE+1)], SECTOR_SIZE+1); + trackdata[position++] = (crc>>8)&0xff; + trackdata[position++] = crc & 0xff; + + /* Write Gap3 */ + memset(&trackdata[position], 0xff, 45); + position += 45; + } + /* Write lead-out */ + memset(&trackdata[position], 0xff, 231); + position += 231; + } + else /* we have MFM */ + { + /* Write lead-in. */ + memset(trackdata, 0x4e, 40); + position += 40; + for (i=0; i < tag->sectors; i++) + { + sector = (i*step + imgtrack*startstep) % tag->sectors; + + memset(&trackdata[position], 0x00, 10); + position += 10; + /* Write sync */ + memset(&trackdata[position], 0xa1, 3); + position += 3; + /* Set IDAM */ + trackdata[position++] = 0xfe; + trackdata[position++] = imgtrack; + trackdata[position++] = head; + trackdata[position++] = sector; + trackdata[position++] = 0x01; + /* Set CRC16 */ + crc = ccitt_crc16(0xffff, &trackdata[position-TI99_IDAM_LENGTH+2], TI99_IDAM_LENGTH - 2); + trackdata[position++] = (crc>>8)&0xff; + trackdata[position++] = crc & 0xff; + + /* Write Gap2 */ + memset(&trackdata[position], 0x4e, 22); + position += 22; + memset(&trackdata[position], 0x00, 12); + position += 12; + /* Write sync */ + memset(&trackdata[position], 0xa1, 3); + position += 3; + /* Write DAM */ + trackdata[position++] = 0xfb; + + /* Locate the sector content in the image and load it. */ + err = ti99_sdf_read_sector(floppy, head, track, sector, &trackdata[position], SECTOR_SIZE); + if (err) + return err; + + position += SECTOR_SIZE; + + /* Set CRC16 */ + crc = ccitt_crc16(0xffff, &trackdata[position-SECTOR_SIZE-1], SECTOR_SIZE+1); + trackdata[position++] = (crc>>8)&0xff; + trackdata[position++] = crc & 0xff; + + /* Write Gap3 */ + memset(&trackdata[position], 0x4e, 24); + position += 24; + } + /* Write lead-out */ + memset(&trackdata[position], 0x4e, 712); + position += 712; + } + return FLOPPY_ERROR_SUCCESS; +} + +/* + Writes a whole track. As the sector dump format does not include track + data, we have to extract the sector contents and to write them + in sequence into the image. All the rest (gaps, crc, sync bytes) are + dropped here. + Bytes written to locations beyond the image end will be droppped + silently (as if the medium is unwritable from that point). +*/ +static floperr_t ti99_sdf_write_track(floppy_image_legacy *floppy, int head, int track, UINT64 offset, const void *buffer, size_t buflen) +{ + int current_pos = 0; + UINT8 *track_image; + int leadin, gap1, gap2; + int is_fm, found; + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + int imgtrack = track; + + // Find out if we are going to write an FM or MFM track. Note that the + // original TI controller is known to have a bug in the formatting + // routine (using a MOVB instead of a MOV instruction when setting the + // pointer to the track data), outputting false bytes at the + // beginning of the track. So we do not rely on absolute positions + // (and neither does the controller). + track_image = (UINT8*)buffer; + + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + /* Only search in the first 100 bytes for the start. */ + leadin = 40; + gap1 = 10; + gap2 = 12; + is_fm = FALSE; + + current_pos = find_block(track_image, 0, 100, 0x4e, leadin); + + // In case of defect formats, we continue as far as possible. This + // may lead to sectors not being written. */ + if (current_pos==TI99DSK_BLOCKNOTFOUND) + { + /* Not MFM, possibly FM image */ + /* Again, find Lead-in */ + leadin = 16; + current_pos = find_block(track_image, 0, 100, 0x00, leadin); + if (current_pos==TI99DSK_BLOCKNOTFOUND) + { + /* If neither, forget about this process completely. */ + LOG_FORMATS("Cannot find lead-in for track %d, head %d.\n", track, head); + return FLOPPY_ERROR_INVALIDIMAGE; + } + gap1 = 6; + gap2 = 6; + is_fm = TRUE; + } + + found = FALSE; + /* Get behind lead-in */ + current_pos += leadin; + while (current_pos < buflen) + { + /* We must find the address block to determine the sector. */ + int new_pos = find_block(track_image, current_pos, buflen, 0x00, gap1); + if (new_pos==TI99DSK_BLOCKNOTFOUND) + { + /* Forget about the rest. */ + if (found) break; /* we were already successful, so all ok */ + LOG_FORMATS("Cannot find gap1 for track %d, head %d.\n", track, head); + return FLOPPY_ERROR_INVALIDIMAGE; + } + found = TRUE; + + if (!is_fm) + new_pos += 3; /* skip sync bytes in MFM */ + + if (track_image[new_pos + gap1]==0xfe) + { + current_pos = new_pos + gap1 + 1; + /* IDAM found. */ + int wtrack = track_image[current_pos]; + int whead = track_image[current_pos+1]; + int sector = track_image[current_pos+2]; + if (wtrack == imgtrack && whead == head) + { + /* We skip the first part of gap2. */ + new_pos = find_block(track_image, current_pos, buflen, 0x00, gap2); + if (current_pos==TI99DSK_BLOCKNOTFOUND) + { + LOG_FORMATS("Cannot find gap2 for track %d, head %d.\n", imgtrack, head); + return FLOPPY_ERROR_INVALIDIMAGE; + } + else + { + new_pos += gap2; + if (!is_fm) + new_pos += 3; /* skip sync */ + current_pos = new_pos; + if (track_image[current_pos]==0xfb) + { + /* DAM found. We now write the sector content to the image. */ + ti99_sdf_write_sector(floppy, head, track, sector, &track_image[current_pos+1], SECTOR_SIZE, 0); + current_pos += SECTOR_SIZE; + } + else + LOG_FORMATS("DAM not found. Not writing write sector %d\n", sector); + /* else not found, ignore this sector. */ + } + } + else + { + // else the sector head data do not match the + // current track and head. Ignore the sector. + LOG_FORMATS("Wrong track: wtrack=%d, imgtrack=%d, whead=%d, imghead=%d\n",wtrack,imgtrack, whead,head); + } + } + else + { + /* One step forward. Retry. */ + current_pos++; + } + } + return FLOPPY_ERROR_SUCCESS; +} + +/* + This method creates a properly formatted image. + + For a proper image, we need to format all tracks, and to create a + minimal filesystem: + + name = "UNNAMED " + number of sectors + sectors/track + "DSK" + protection = 0x00 + tracks/side + sides + density (0,1 = FM, 2 = MFM) + 0x14 - 0x37 : 0x00 + Allocation bitmap: 0xFF except for the first #sectors/AU size + + AU size = 1 (360 - 1440 sectors) + = 2 (1441 - 2880 sectors) + = 4 (2881 - 5760 sectors) + = 8 (5760 - 11520 sectors) + + sector 0 = + sector 1 = 256* 0x00 +*/ +/* static floperr_t ti99_sdf_format_track(floppy_image_legacy *floppy, int head, int track, option_resolution *params) +{ + UINT8 *sector0 = (UINT8*)malloc(SECTOR_SIZE); + create_vib(sector0, params); + free(sector0); + if (true) + { + printf("not implemented\n"); + return FLOPPY_ERROR_SEEKERROR; + } + return FLOPPY_ERROR_SUCCESS; +} +*/ + +/* + Get the offset from the start of the image file. In this format, logical + tracks on two-sided disks are arranged as follows (example for 40 + tracks; accordingly for 35 or 80) + + head-track + 0-0 + 0-1 + 0-2 + 0-3 + ... + 0-39 + 1-39 + 1-38 + 1-37 + ... + 1-0 + Note that we take the imgtrack, which may be half of the drive track. +*/ +static floperr_t ti99_sdf_get_offset(floppy_image_legacy *floppy, int head, int imgtrack, int sector, UINT64 *offset) +{ + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + if ((head < 0) || (head >= tag->heads) + || (imgtrack < 0) || (imgtrack >= tag->tracks) + || (sector < 0) || (sector > tag->sectors)) + return FLOPPY_ERROR_SEEKERROR; + + if (head == 0) /* track numbers increasing towards inner track */ + { + *offset = (imgtrack * tag->sectors + sector) * SECTOR_SIZE; + } + else /* track numbers increasing towards outer track */ + { + *offset = (((2*tag->tracks)-1-imgtrack) * tag->sectors + sector) * SECTOR_SIZE; + } + return FLOPPY_ERROR_SUCCESS; +} + +/* + Read one sector at the specified position. +*/ +static floperr_t ti99_sdf_read_sector(floppy_image_legacy *floppy, int head, int track, int sector, void *buffer, size_t buflen) +{ + floperr_t err; + UINT64 offset; + int imgtrack = track; + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + err = ti99_sdf_get_offset(floppy, head, imgtrack, sector, &offset); + if (err) + { + return err; + } + floppy_image_read(floppy, buffer, offset, SECTOR_SIZE); + + return FLOPPY_ERROR_SUCCESS; +} + +/* + For the SDF format, sectors are always numbered in ascending order, + starting with the lowest numbered sector in each track. In other words, + we have nothing to calculate here. +*/ +static floperr_t ti99_sdf_read_indexed_sector(floppy_image_legacy *floppy, int head, int track, int sector, void *buffer, size_t buflen) +{ + return ti99_sdf_read_sector(floppy, head, track, sector, buffer, buflen); +} + +/* + Write one sector at the specified position. +*/ +static floperr_t ti99_sdf_write_sector(floppy_image_legacy *floppy, int head, int track, int sector, const void *buffer, size_t buflen, int ddam) +{ + UINT64 offset; + floperr_t err; + int imgtrack = track; + + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + err = ti99_sdf_get_offset(floppy, head, imgtrack, sector, &offset); + if (err) + { + return err; + } + floppy_image_write(floppy, buffer, offset, SECTOR_SIZE); + + return FLOPPY_ERROR_SUCCESS; +} + +/* + See above; indexing and numbering coincide. +*/ +static floperr_t ti99_sdf_write_indexed_sector(floppy_image_legacy *floppy, int head, int track, int sector, const void *buffer, size_t buflen, int ddam) +{ + return ti99_sdf_write_sector(floppy, head, track, sector, buffer, buflen, ddam); +} + +/* + Required for ReadAddress command +*/ +static floperr_t ti99_sdf_get_indexed_sector_info(floppy_image_legacy *floppy, int head, int track, int sector_index, int *cylinder, int *side, int *sector, UINT32 *sector_length, unsigned long *flags) +{ + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + if (use_80_track_drives && tag->tracks<=40) + { + track = track / 2; + } + + if (sector_length) + *sector_length=SECTOR_SIZE; + if (cylinder) + *cylinder = track; + if (side) + *side = head; + if (sector) + *sector = sector_index; + if (flags) + *flags = 0; + if (sector_index > tag->sectors) + { + return FLOPPY_ERROR_SEEKERROR; + } + + return FLOPPY_ERROR_SUCCESS; +} + +/* + Try to identify the image as an SDF format image. +*/ +static FLOPPY_IDENTIFY(ti99_sdf_identify) +{ + UINT64 size; + size = floppy_image_size(floppy); + *vote = ti99_sdf_guess_geometry(floppy, size, NULL); + LOG_FORMATS("SDF voting %d\n", *vote); + return FLOPPY_ERROR_SUCCESS; +} + +/* + Create the SDF data structures and set the callbacks. +*/ +static FLOPPY_CONSTRUCT(ti99_sdf_construct) +{ + struct ti99dsk_geometry geometry; + struct FloppyCallbacks *callbacks; + struct ti99dsk_tag *tag; + LOG_FORMATS("Reading image as SDF\n"); + + if (params) + { + /* create */ + memset(&geometry, 0, sizeof(geometry)); + geometry.sides = option_resolution_lookup_int(params, PARAM_HEADS); + geometry.tracksperside = option_resolution_lookup_int(params, PARAM_TRACKS); + geometry.secspertrack = option_resolution_lookup_int(params, PARAM_SECTORS); + + /* We don't have headers for geometry */ + /* check for usage in imgtool - we want to be able to create useful disks */ + } + else + { + /* load */ + if (ti99_sdf_guess_geometry(floppy, floppy_image_size(floppy), &geometry)<50) + return FLOPPY_ERROR_INVALIDIMAGE; + } + + assert(geometry.sides); + assert(geometry.tracksperside); + assert(geometry.secspertrack); + + tag = (struct ti99dsk_tag *) floppy_create_tag(floppy, sizeof(struct ti99dsk_tag)); + + if (!tag) + return FLOPPY_ERROR_OUTOFMEMORY; + + tag->heads = geometry.sides; + tag->tracks = geometry.tracksperside; + tag->sectors = geometry.secspertrack; + if (tag->sectors < 10) + /* FM mode */ + tag->track_size = 16 + tag->sectors*334 + 231; + else + tag->track_size = 40 + tag->sectors*340 + 712; + + /* set up format callbacks */ + callbacks = floppy_callbacks(floppy); + callbacks->read_sector = ti99_sdf_read_sector; + callbacks->write_sector = ti99_sdf_write_sector; + callbacks->read_indexed_sector = ti99_sdf_read_indexed_sector; + callbacks->write_indexed_sector = ti99_sdf_write_indexed_sector; + callbacks->read_track = ti99_sdf_read_track; + callbacks->write_track = ti99_sdf_write_track; +/* callbacks->format_track = ti99_sdf_format_track; */ + callbacks->get_track_size = ti99_get_track_size; + /* post format unset */ + callbacks->get_heads_per_disk = ti99_get_heads_per_disk; + callbacks->get_tracks_per_disk = ti99_get_tracks_per_disk; + callbacks->get_sector_length = ti99_get_sector_length; + callbacks->get_indexed_sector_info = ti99_sdf_get_indexed_sector_info; + + return FLOPPY_ERROR_SUCCESS; +} + +/* ----------------------------------------------------------------------- + * Track Dump Format, aka PC99 format + * This format includes all track data, including address marks and gaps. + * However, CRC is not stored; instead, a f7f7 is stored at that location. + * Two track images exist, one for FM and one for MFM format. + * + * In this implementation, however, we do calculate the CRC. + * + * All tracks have exactly the same size, so offsets are calculated easily. + * + * This format is a precise map of the data contents of the track; + * however, it does not store clock patterns. + * + * The TDF format does not have a special index part, so any sector on it + * must be searched (as done by the disk controller) + * + * Interesting detail: The original TI disk controller has a bug which has + * just recently been discovered (using MESS!): The lead-in has another 9 + * bytes as a prefix; this is due to a wrongly set pointer in the format + * routine. Strictly speaking, it produces invalid track images. + * The original hardware does not care, however, so neither should we. + * To keep the emulation realistic (and to allow for a + * possible future extension with clock bits), we search for the + * beginning of the track. From there on, however, we accept no further + * deviations. This may make the resulting format unusable with the PC99 + * emulator, but it only appears when formatting with the TI disk + * controller anyway. If the image is created with another disk controller or + * using imgtool, the format is correctly created. + * + * FM track image + * + * Lead-in: 16* 00 + * Pre-ID gap: 6* 00 ---+ + * IDAM: fe | + * Track: tt | + * Head: hh | + * Sector: ss | + * Size: 01 | + * CRC: 2* f7 +--- repeat 9 times (8 in rare cases) + * Gap2: 11* ff | + * 6* 00 | + * DAM: fb | + * Content: 256* xx | + * CRC: 2* f7 | + * Gap3: 45* ff ---+ + * Lead-out: 231* ff + * + * -------------- + * + * MFM track image + * + * Lead-in: 40* 4e + * Pre-ID gap: 10* 00 ---+ + * Sync: 3* a1 | + * IDAM: fe | + * Track: tt | + * Head: hh | + * Sector: ss | + * Size: 01 | + * CRC: 2* f7 +--- repeat 18 times (16 in rare cases) + * Gap2: 22* 4e | + * 12* 00 | + * Sync: 3* a1 | + * DAM: fb | + * Content: 256* xx | + * CRC: 2* f7 | + * Gap3: 24* 4e ---+ + * Lead-out: 712* 4e + * + * ------------------ + * The tracks are located on the image as follows (different to SDF!): + * + * head-track + * 0-0 + * 0-1 + * 0-2 + * 0-3 + * ... + * 0-39 + * 1-0 + * 1-1 + * 1-2 + * ... + * 1-39 + * ----------------------------------------------------------------------- */ + +#define TI99_FM 1 +#define TI99_MFM 2 + +/* For the emulation of the clock pattern, we add a 9th bit. */ +#define TI99_DAM 0x1fb +#define TI99_IDAM 0x1fe + +#define TI99_FM_DAM_OFFSET 24 +#define TI99_MFM_DAM_OFFSET 44 + +#define TI99_FM_TRACK_ADD 247 +#define TI99_MFM_TRACK_ADD 752 + +#define TI99_FM_TOTAL_SECTOR 334 +#define TI99_MFM_TOTAL_SECTOR 340 + +/* + Determine offset of the first IDAM. This is the base of the simulation + of the clock bytes. We use a heuristic to guess where the track begins + and simply check whether we can locate the first IDAM and DAM. Return + value is the offset to the first IDAM. +*/ +static floperr_t determine_offset(int format, UINT8 *track, int *offset) +{ + floperr_t retval; + int current_pos; + + if (format==TI99_FM) + { +// printf("Trying FM\n"); + // We are seeking the lead-in and pre-id gap from + // position 0 to position 50. This is just to make sure that + // if there are bad bytes at the beginning (original TI controller) + // we will skip them. + current_pos = find_block(track, 0, 50, 0x00, 22); + if (current_pos==TI99DSK_BLOCKNOTFOUND) + { + LOG_FORMATS("Lead-in not found\n"); + retval = FLOPPY_ERROR_SEEKERROR; + } + else + { + current_pos += 22; + if (track[current_pos]==0xfe /* IDAM */ && + track[current_pos+4]==0x01 /* sector length, always 256 for TI */ && + track[current_pos+24]==0xfb) /* DAM */ + { + /* We're pretty sure this is the beginning. */ + *offset = current_pos; + retval = FLOPPY_ERROR_SUCCESS; + } + else + { + LOG_FORMATS("IDAM or DAM not found\n"); + retval = FLOPPY_ERROR_SEEKERROR; + } + } + } + else + { + /* MFM */ +// printf("Trying MFM\n"); + int track_start = find_block(track, 0, 100, 0x4e, 40); + if (track_start==TI99DSK_BLOCKNOTFOUND) + { + LOG_FORMATS("Lead-in not found\n"); + retval = FLOPPY_ERROR_SEEKERROR; + } + else + { + track_start += 40; + current_pos = find_block(track, track_start, track_start+10, 0x00, 10); + if (current_pos==TI99DSK_BLOCKNOTFOUND) + { + LOG_FORMATS("Pre-gap not found\n"); + retval = FLOPPY_ERROR_SEEKERROR; + } + else + { + current_pos += 10; + if (track[current_pos] == 0xa1 /* First sync */ && + track[current_pos+3] == 0xfe /* IDAM */ && + track[current_pos+44] == 0xa1 /* First sync */ && + track[current_pos+47] == 0xfb) /* DAM */ + { + /* We're pretty sure this is the beginning. */ + *offset = current_pos + 3; + retval = FLOPPY_ERROR_SUCCESS; + } + else + { + LOG_FORMATS("Sync/IDAM/DAM not found\n"); + retval = FLOPPY_ERROR_SEEKERROR; + } + } + } + } +/* printf("TDF offset = 0x%x\n", *offset); */ + return retval; +} + +/* + Gets a byte from the track. In this format we do not have a clock + signal, so we will simulate it. Thus, this function returns a 9-bit + value, with the first bit set as a marker for an address mark. + + This may be a little more complicated than necessary, but it correctly + emulates the behavior of the controller (and it allows for an + alternative format which actually supports clock and data). +*/ +static int read_byte(int format, int first_idam, UINT8 *track, int position) +{ + int totalseclen; + int dam_offset; + int byte; + + /* Check whether we are at any IDAM or DAM in this track. */ + if (format==TI99_FM) + { + totalseclen = TI99_FM_TOTAL_SECTOR; + dam_offset = TI99_FM_DAM_OFFSET; + } + else + { + totalseclen = TI99_MFM_TOTAL_SECTOR; + dam_offset = TI99_MFM_DAM_OFFSET; + } + + int curpos = (position - first_idam) % totalseclen; + if (curpos==0 && track[position]==0xfe) + byte = TI99_IDAM; + else + { + if (curpos==dam_offset && track[position]==0xfb) + byte = TI99_DAM; + else + byte = track[position] & 0xff; + } + return byte; +} + +/* + Determine the position of this track in the image. +*/ +static floperr_t ti99_tdf_get_offset(floppy_image_legacy *floppy, int head, int imgtrack, UINT64 *offset) +{ + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + if ((head < 0) || (head >= tag->heads) || (imgtrack < 0) || (imgtrack >= tag->tracks)) + return FLOPPY_ERROR_SEEKERROR; + + // For head 0, tracks increase from 0 to tracks-1; then for head 1, + // tracks also increase from 0 to tracks-1. */ + *offset = head * (tag->tracks * tag->track_size) + imgtrack * tag->track_size; + + return FLOPPY_ERROR_SUCCESS; +} + +/* + Reads a track. We just copy the contents from the format and recreate + the CRC if it is the "blank" CRC as F7F7. + This method assumes that the track division be done already. +*/ +static floperr_t ti99_tdf_read_track_internal(floppy_image_legacy *floppy, int head, int imgtrack, UINT64 offset, void *buffer, size_t buflen) +{ + floperr_t err; + UINT64 track_offset; + int first_idam = 0; + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + int i, byte, crc; + + UINT8 *track_data = (UINT8*)buffer; + + err = ti99_tdf_get_offset(floppy, head, imgtrack, &track_offset); + if (err) + return err; + + floppy_image_read(floppy, buffer, track_offset, buflen); + + // Rebuild the CRCs. PC99 did not store the CRC but put + // F7F7 in its place. + // The first CRC is at position IDAM + 5 + // The second CRC is at position IDAM + 0x119 (FM) or +0x12d (MFM) + // All this is repeated for each sector in the track + + if (determine_offset(tag->format, track_data, &first_idam)==FLOPPY_ERROR_SEEKERROR) + return FLOPPY_ERROR_SEEKERROR; + + i = 0; + while (i < buflen) + { + byte = read_byte(tag->format, first_idam, track_data, i); + if (byte == TI99_IDAM) + { + // Do we have a valid CRCs already? Then this image + // already contains proper CRCs handling. Do not recreate them. + if (track_data[i+5] != 0xf7 || track_data[i+6] != 0xf7) + return FLOPPY_ERROR_SUCCESS; + + crc = ccitt_crc16(0xffff, &track_data[i], 5); + track_data[i+5] = (crc>>8) & 0xff; + track_data[i+6] = (crc & 0xff); + } + else + { + if (byte == TI99_DAM) + { + crc = ccitt_crc16(0xffff, &track_data[i], SECTOR_SIZE+1); + track_data[i+SECTOR_SIZE+1] = (crc>>8) & 0xff; + track_data[i+SECTOR_SIZE+2] = (crc & 0xff); + } + } + i++; + } +// dump_contents(track_data, buflen); + return FLOPPY_ERROR_SUCCESS; +} + +/* + Reads a track. We just copy the contents from the format, without + changes. +*/ +static floperr_t ti99_tdf_read_track(floppy_image_legacy *floppy, int head, int track, UINT64 offset, void *buffer, size_t buflen) +{ + int imgtrack = track; + + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + return ti99_tdf_read_track_internal(floppy, head, imgtrack, offset, buffer, buflen); +} + +/* + Writes a track. +*/ +static floperr_t ti99_tdf_write_track(floppy_image_legacy *floppy, int head, int track, UINT64 offset, const void *buffer, size_t buflen) +{ + floperr_t err; + UINT64 track_offset; + int imgtrack = track; + + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + err = ti99_tdf_get_offset(floppy, head, imgtrack, &track_offset); + if (err) + return err; + + // According to the TDF format, we don't need the CRC but replace it + // with F7F7. For now we keep it and see whether PC99 can cope with + // that. (Otherwise we would have to copy the const buffer). */ + floppy_image_write(floppy, buffer, offset + track_offset, buflen); + return FLOPPY_ERROR_SUCCESS; +} + +/* +static floperr_t ti99_tdf_format_track(floppy_image_legacy *floppy, int head, int track, option_resolution *params) +{ + int sectors; + int sector_length; + int interleave; + int first_sector_id; + sectors = option_resolution_lookup_int(params, PARAM_SECTORS); + sector_length = option_resolution_lookup_int(params, PARAM_SECTOR_LENGTH); + interleave = option_resolution_lookup_int(params, PARAM_INTERLEAVE); + first_sector_id = option_resolution_lookup_int(params, PARAM_FIRST_SECTOR_ID); + return FLOPPY_ERROR_SUCCESS; +} +*/ + +/* + Get a sector from a track. This function takes a complete track, + searches for the sector, and passes a pointer to the sector contents + back. + As the original TI controller creates bad bytes at the start of a track + we do *not* assume a simple layout. The strategy is to search the + lead-in of the track, and from this offset, step through the track + in search for the sector. Since we do not have index marks (we cannot + distinguish them from ordinary data, as we do not store the clock), we + do not attempt to detect IDAM or DAM. +*/ +static floperr_t ti99_tdf_seek_sector_in_track(floppy_image_legacy *floppy, int head, int track, int sector, UINT8 *track_data, UINT8 **sector_data) +{ + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + int byte; + int state; + enum { SEARCHIDAM, SEARCHDAM }; + + state = SEARCHIDAM; + if (tag->first_idam==0) /* should we check for every track? */ + { + if (determine_offset(tag->format, track_data, &tag->first_idam)==FLOPPY_ERROR_SEEKERROR) + { + LOG_FORMATS("could not determine offset\n"); + return FLOPPY_ERROR_SEEKERROR; + } + } + + for (int i=0; i < tag->track_size; i++) + { + byte = read_byte(tag->format, tag->first_idam, track_data, i); + + switch (state) + { + case SEARCHIDAM: + /* search for the IDAM */ + if (byte==TI99_IDAM) + { + if ((track_data[i+1]==track) && (track_data[i+2]==head) && (track_data[i+3]==sector)) + state = SEARCHDAM; + else + state = SEARCHIDAM; + } + break; + case SEARCHDAM: + if (byte==TI99_DAM) + { + *sector_data = &track_data[i+1]; + return FLOPPY_ERROR_SUCCESS; + } + break; + } + } + LOG_FORMATS("Sector not found.\n"); + return FLOPPY_ERROR_SEEKERROR; +} + +static floperr_t ti99_tdf_read_sector(floppy_image_legacy *floppy, int head, int track, int sector, void *buffer, size_t buflen) +{ + floperr_t err; + UINT8 *sector_data; + UINT8 *track_data; + int imgtrack = track; + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + track_data = (UINT8*)malloc(tag->track_size); + + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + err = ti99_tdf_read_track_internal(floppy, head, imgtrack, 0, track_data, tag->track_size); + if (err) + return err; + + err = ti99_tdf_seek_sector_in_track(floppy, head, imgtrack, sector, track_data, §or_data); + if (err) + return err; + /* verify CRC? */ + + memcpy(buffer, sector_data, SECTOR_SIZE); + free(track_data); + return FLOPPY_ERROR_SUCCESS; +} + +/* + Writes a sector into the track. Note that the indexed_write method + may be used instead of this method, according to the implementation + of flopimg/flopdrv. +*/ +static floperr_t ti99_tdf_write_sector(floppy_image_legacy *floppy, int head, int track, int sector, const void *buffer, size_t buflen, int ddam) +{ + floperr_t err; + UINT8 *sector_data; + UINT8 *track_data; + UINT64 track_offset; + int imgtrack = track; + UINT64 offset; + UINT8 crc_field[2]; + int crc; + + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + + track_data = (UINT8*)malloc(tag->track_size); + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + err = ti99_tdf_read_track_internal(floppy, head, imgtrack, 0, track_data, tag->track_size); + if (err) + return err; + err = ti99_tdf_seek_sector_in_track(floppy, head, imgtrack, sector, track_data, §or_data); + if (err) + return err; + + err = ti99_tdf_get_offset(floppy, head, imgtrack, &track_offset); + if (err) + return err; + + offset = track_offset + (sector_data - track_data); + + floppy_image_write(floppy, buffer, offset, SECTOR_SIZE); + + // Recalculate CRC. We init the CRC with bf84 (DAM fb is + // already included; would be ffff without) + crc = ccitt_crc16(0xbf84, (UINT8*)buffer, SECTOR_SIZE); + crc_field[0] = (crc>>8) & 0xff; + crc_field[1] = (crc & 0xff); + floppy_image_write(floppy, crc_field, offset+SECTOR_SIZE, 2); + + return FLOPPY_ERROR_SUCCESS; +} + +static floperr_t ti99_tdf_write_indexed_sector(floppy_image_legacy *floppy, int head, int track, int sector_index, const void *buffer, size_t buflen, int ddam) +{ + /* Read track, head */ + int byte; + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + UINT8 *track_data; + UINT8 *sector_data; + UINT64 track_offset; + UINT64 offset; + UINT8 crc_field[2]; + + int crc; + int imgtrack = track; + + int i; + floperr_t err; + + track_data = (UINT8*)malloc(tag->track_size); + + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + err = ti99_tdf_read_track_internal(floppy, head, imgtrack, 0, track_data, tag->track_size); + if (err) + { + free(track_data); + return err; + } + + /* Search for the sector_index-th sector. */ + if (tag->first_idam==0) /* should we check for every track? */ + { + if (determine_offset(tag->format, track_data, &tag->first_idam)==FLOPPY_ERROR_SEEKERROR) + { + free(track_data); + return FLOPPY_ERROR_SEEKERROR; + } + } + + /* Search for the IDAM of the n-th sector */ + i=0; + while (i < tag->track_size && sector_index>=0) + { + byte = read_byte(tag->format, tag->first_idam, track_data, i); + if (byte == TI99_IDAM) + { + sector_index--; + } + i++; + } + i--; + + /* Not that many sectors in this track? */ + if (sector_index>0) + { + free(track_data); + return FLOPPY_ERROR_SEEKERROR; + } + + /* Find the DAM. */ + while (i < tag->track_size) + { + byte = read_byte(tag->format, tag->first_idam, track_data, i); + if (byte == TI99_DAM) + break; + else + i++; + } + + /* There was no DAM. Image seems to be broken. */ + if (i == tag->track_size) + { + free(track_data); + return FLOPPY_ERROR_SEEKERROR; + } + + /* Sector data is right after the DAM. */ + sector_data = &track_data[i+1]; + + /* Get the position of the track in the image. */ + err = ti99_tdf_get_offset(floppy, head, imgtrack, &track_offset); + if (err) + return err; + + offset = track_offset + (sector_data - track_data); + + /* Write the sector data at that position. */ + floppy_image_write(floppy, buffer, offset, SECTOR_SIZE); + + // Recalculate CRC. We init the CRC with bf84 (DAM fb is + // already included; would be ffff without) + crc = ccitt_crc16(0xbf84, (UINT8*)buffer, SECTOR_SIZE); + crc_field[0] = (crc>>8) & 0xff; + crc_field[1] = (crc & 0xff); + floppy_image_write(floppy, crc_field, offset+SECTOR_SIZE, 2); + + /* Free the temporary buffer */ + free(track_data); + + return FLOPPY_ERROR_SUCCESS; +} + + +static floperr_t ti99_tdf_find_indexed_sector(floppy_image_legacy *floppy, int head, int track, int sector_index, int *cylinder, int *side, int *sector, UINT32 *sector_length, void *buffer, unsigned long *flags) +{ + /* Read track, head */ + int byte = 0; + struct ti99dsk_tag *tag = (ti99dsk_tag*)floppy_tag(floppy); + UINT8 *track_data; + int imgtrack = track; + int i; + floperr_t err; + floperr_t retval; + track_data = (UINT8*)malloc(tag->track_size); + + if (use_80_track_drives && tag->tracks<=40) + { + imgtrack = track / 2; + } + + err = ti99_tdf_read_track_internal(floppy, head, imgtrack, 0, track_data, tag->track_size); + if (err) + { + free(track_data); + return err; + } + + /* Search for the sector_index-th sector. */ + + if (tag->first_idam==0) /* should we check for every track? */ + { + if (determine_offset(tag->format, track_data, &tag->first_idam)==FLOPPY_ERROR_SEEKERROR) + { + free(track_data); + return FLOPPY_ERROR_SEEKERROR; + } + } + + i=0; + sector_index = sector_index+1; + while (i++ < tag->track_size && sector_index>0) + { + byte = read_byte(tag->format, tag->first_idam, track_data, i); + if (byte == TI99_IDAM) + { + sector_index--; + } + } + + retval = FLOPPY_ERROR_SEEKERROR; + if (sector_index==0) + { + /* If desired, return the ID field */ + if (cylinder) + { + *cylinder = track_data[i]; + // printf("cylinder=%d", *cylinder); + + } + if (side) + { + *side = track_data[i+1]; + // printf(", side=%d", *side); + } + if (sector) + { + *sector = track_data[i+2]; + // printf(", sector=%d", *sector); + } + if (sector_length) + { + *sector_length = 128 << track_data[i+3]; + // printf(", sector_length=%d\n", *sector_length); + } + if (flags) + { + *flags = 0; + } + retval = FLOPPY_ERROR_SUCCESS; + + /* If desired, return the sector contents. */ + if (buffer) + { + while (i++ < tag->track_size) + { + byte = read_byte(tag->format, tag->first_idam, track_data, i); + if (byte == TI99_DAM) + break; + } + if (byte == TI99_DAM) + { + memcpy(buffer, &track_data[i+1], SECTOR_SIZE); + } + else + { + retval = FLOPPY_ERROR_SEEKERROR; + } + } + } + free(track_data); + return retval; +} + +/* + Required for ReadAddress command. This function gets the 1st, 2nd, 3rd, + sector (and so on) from the track, not sector 1, 2, 3. That is, the + returned sector info depends on the interleave and the start sector. +*/ +static floperr_t ti99_tdf_get_indexed_sector_info(floppy_image_legacy *floppy, int head, int track, int sector_index, int *cylinder, int *side, int *sector, UINT32 *sector_length, unsigned long *flags) +{ + return ti99_tdf_find_indexed_sector(floppy, head, track, sector_index, cylinder, side, sector, sector_length, NULL, flags); +} + +static floperr_t ti99_tdf_read_indexed_sector(floppy_image_legacy *floppy, int head, int track, int sector, void *buffer, size_t buflen) +{ + return ti99_tdf_find_indexed_sector(floppy, head, track, sector, (int*)NULL, (int*)NULL, (int*)NULL, (UINT32*)NULL, buffer, (unsigned long*)NULL); +} + + +/* + Guess the geometry of the disk. + 1. Check whether the disk is formatted, and if so, whether it is + a FM or a MFM disk + 2. If this fails, guess from the file size which format it should have + + SDF formats have sizes which are multiples of 10KiB. + TDF: full sector size FM (MFM) = 334 (340) bytes + additional track data FM (MFM) = 247 (752) bytes + + => check whether size is dividable by 40 (or 35) and by 2 + => get track size + => subtract track data + => divide by 334 or 340 + => number of sectors should be 8, 9, 16, 18, 32, or 36 + + SSDD is 274880, but DSSD is 260240, so they differ (unlike in the SDF format) + Still, we cannot tell apart single-sided 80 track formats from double-sided + 40 tracks. We assume the latter. Moreover, we only attempt to detect + 9/18/36 sectors (no 8/16) and 40 tracks (no 35). + + SSSD40 = 130120 + DSSD = 260240 + SSDD = 274880 + DSDD = 549760 + DSHD = 1039360 + DSDD80 = 1099520 + DSHD80 = 2078720 +*/ +static int ti99_tdf_guess_geometry(floppy_image_legacy *floppy, UINT64 size, + struct ti99dsk_geometry *geometry) +{ + int idamcnt, state, track, head, i, totalseclen = 0, format = 0, byte, tracklength, trackadd = 0; + int first_idam = 0; + UINT8 *track_data; + /*int offset;*/ + + struct ti99dsk_geometry dummy_geometry; + + if (geometry) + memset(geometry, 0, sizeof(*geometry)); + else + geometry = &dummy_geometry; + + /* Allocate enough bytes to hold the longest supported track. */ + track_data = (UINT8*)malloc(13000); + floppy_image_read(floppy, track_data, 0, 13000); + + if (determine_offset(TI99_MFM, track_data, &first_idam)==FLOPPY_ERROR_SEEKERROR) + { + /* MFM failed. */ + if (determine_offset(TI99_FM, track_data, &first_idam)==FLOPPY_ERROR_SEEKERROR) + { + LOG_FORMATS("IDAM not found. Unformatted disk.\n"); + + // FM failed as well. Disk is not formatted. We assume + // a format that fits into the space provided by the file. + // We only give a moderate vote in this case, so SDF + // make take it. + + free(track_data); + if (size < 130120 || size > 2078720) + { + LOG_FORMATS("Unknown format size: %d\n", (int)size); + return 0; + } + if (size >= 130120 && size < 260240) + { + /* SSSD */ + geometry->sides = 1; + geometry->tracksperside = 40; + geometry->secspertrack = 9; + geometry->density = 1; + return 50; + } + if (size >= 260240 && size < 274880) + { + /* DSSD */ + geometry->sides = 2; + geometry->tracksperside = 40; + geometry->secspertrack = 9; + geometry->density = 1; + return 50; + } + if (size >= 274880 && size < 549760) + { + /* SSDD */ + geometry->sides = 1; + geometry->tracksperside = 40; + geometry->secspertrack = 18; + geometry->density = 2; + return 50; + } + if (size >= 549760 && size < 1039360) + { + /* DSDD */ + geometry->sides = 2; + geometry->tracksperside = 40; + geometry->secspertrack = 18; + geometry->density = 2; + return 50; + } + if (size >= 1039360 && size < 1099520) + { + /* DSHD */ + geometry->sides = 2; + geometry->tracksperside = 40; + geometry->secspertrack = 36; + geometry->density = 2; + return 50; + } + if (size >= 1099520 && size < 2078720) + { + /* DSDD80 */ + geometry->sides = 2; + geometry->tracksperside = 80; + geometry->secspertrack = 18; + geometry->density = 2; + return 50; + } + if (size == 2078720) + { + /* DSHD80 */ + geometry->sides = 2; + geometry->tracksperside = 80; + geometry->secspertrack = 36; + geometry->density = 2; + return 50; + } + } + else + { + /* FM format */ + totalseclen = TI99_FM_TOTAL_SECTOR; + trackadd = TI99_FM_TRACK_ADD; /* additional track data (gaps etc.) */ + geometry->density = 1; + format = TI99_FM; + } + } + else + { + /* MFM format */ + totalseclen = TI99_MFM_TOTAL_SECTOR; + trackadd = TI99_MFM_TRACK_ADD; + geometry->density = 2; + format = TI99_MFM; + } + + /* Count the number of IDAMs on track 0, head 0 */ + idamcnt = 0; + state = 0; + track = 0; + head = 0; + i=0; + + while (i++ < 13000 && head == 0 && track == 0) + { + byte = read_byte(format, first_idam, track_data, i); + switch (state) + { + case 0: + if (byte==TI99_IDAM) + state = 1; + break; + case 1: + track = byte; + state = 2; + break; + case 2: + head = byte; + state = 3; + break; + case 3: + idamcnt++; + state = 0; + } + } + LOG_FORMATS("Determined %d sectors\n", idamcnt); + + // Now calculate the geometry + // The track size, in theory, may change due to reformatting, + // and it may also vary between tracks, but the TDF does not support + // varying track lengths. So every track has the same length as track 0. + // Also, we don't yet know whether the drive and the disk have the + // same track count. For 80 track drives and 40 track disks, we need + // double-stepping; this will be known as soon as the controller is + // initialized; we need to check that in the access methods. + + tracklength = idamcnt * totalseclen + trackadd; + /*offset = 0;*/ + + // Read the last track. We assume that all tracks are properly + // formatted; otherwise, the above calculations would already fail. + // That is, the last track in the image tells us whether it is for + // head 0 or for head 1. + + geometry->sides = 1; + floppy_image_read(floppy, track_data, size-tracklength, tracklength); + if (determine_offset(format, track_data, &first_idam)==FLOPPY_ERROR_SEEKERROR) + { + /* error ... what now? */ + LOG_FORMATS("Error when reading last track. Image broken.\n"); + free(track_data); + return 50; + } + else + { + if (track_data[first_idam+2]==0) + { + geometry->sides = 1; + } + else + { + if (track_data[first_idam+2]==1) + { + geometry->sides = 2; + } + else + { + LOG_FORMATS("Error: Last track has invalid first IDAM: head = %d.\n", track_data[first_idam+2]); + } + + } + } + + + geometry->tracksperside = (size / tracklength) / geometry->sides; + geometry->secspertrack = idamcnt; + + if (geometry->tracksperside < 35 || geometry->tracksperside > 80) + { + LOG_FORMATS("Unsupported track count: %d\n", geometry->tracksperside); + free(track_data); + return 0; + } + + free(track_data); + return 100; +} + +static FLOPPY_IDENTIFY(ti99_tdf_identify) +{ + UINT64 size; + size = floppy_image_size(floppy); + *vote = ti99_tdf_guess_geometry(floppy, size, NULL); + LOG_FORMATS("TDF voting %d\n", *vote); + return FLOPPY_ERROR_SUCCESS; +} + +static FLOPPY_CONSTRUCT(ti99_tdf_construct) +{ + struct ti99dsk_geometry geometry; + struct FloppyCallbacks *callbacks; + struct ti99dsk_tag *tag; + LOG_FORMATS("Reading image as TDF\n"); + if (params) + { + /* create */ + memset(&geometry, 0, sizeof(geometry)); +/* + geometry.sides = option_resolution_lookup_int(params, PARAM_HEADS); + geometry.tracksperside = option_resolution_lookup_int(params, PARAM_TRACKS); + geometry.secspertrack = option_resolution_lookup_int(params, PARAM_SECTORS); +*/ + } + else + { + /* load */ + if (ti99_tdf_guess_geometry(floppy, floppy_image_size(floppy), &geometry)<50) + return FLOPPY_ERROR_INVALIDIMAGE; + } + + assert(geometry.sides); + assert(geometry.tracksperside); + assert(geometry.secspertrack); + + tag = (struct ti99dsk_tag *) floppy_create_tag(floppy, sizeof(struct ti99dsk_tag)); + + tag->heads = geometry.sides; + tag->tracks = geometry.tracksperside; + + if (geometry.density==TI99_FM) + tag->track_size = geometry.secspertrack * TI99_FM_TOTAL_SECTOR + TI99_FM_TRACK_ADD; + else + tag->track_size = geometry.secspertrack * TI99_MFM_TOTAL_SECTOR + TI99_MFM_TRACK_ADD; + + tag->format = geometry.density; + tag->first_idam = 0; /* let's find out later */ + tag->dam_offset = 0; + + /* set up format callbacks */ + callbacks = floppy_callbacks(floppy); + callbacks->read_sector = ti99_tdf_read_sector; + callbacks->write_sector = ti99_tdf_write_sector; + callbacks->read_indexed_sector = ti99_tdf_read_indexed_sector; + callbacks->write_indexed_sector = ti99_tdf_write_indexed_sector; + callbacks->read_track = ti99_tdf_read_track; + callbacks->write_track = ti99_tdf_write_track; +/* callbacks->format_track = ti99_tdf_format_track; */ + callbacks->get_track_size = ti99_get_track_size; + /* post format unset */ + callbacks->get_heads_per_disk = ti99_get_heads_per_disk; + callbacks->get_tracks_per_disk = ti99_get_tracks_per_disk; + callbacks->get_sector_length = ti99_get_sector_length; + callbacks->get_indexed_sector_info = ti99_tdf_get_indexed_sector_info; + + return FLOPPY_ERROR_SUCCESS; +} + +/* ----------------------------------------------------------------------- */ + +LEGACY_FLOPPY_OPTIONS_START( ti99 ) + LEGACY_FLOPPY_OPTION( ti99_sdf, "dsk", "TI99 sector dump (v9t9)", ti99_sdf_identify, ti99_sdf_construct, NULL, + HEADS([1]-2) + TRACKS(35-[40]-80) + SECTORS(8/9/16/[18]/36) + SECTOR_LENGTH([256]) + FIRST_SECTOR_ID(0)) + LEGACY_FLOPPY_OPTION( ti99_tdf, "dsk,dtk", "TI99 track dump (pc99)", ti99_tdf_identify, ti99_tdf_construct, NULL, + TRACKS(35-[40]-80) + SECTORS(8/9/16/[18]/36) + SECTOR_LENGTH([256]) + FIRST_SECTOR_ID(0)) +LEGACY_FLOPPY_OPTIONS_END |