/*************************************************************************
readti -- Creates sector images from floppy disks written on the TI-99/4A
and Geneve computer family
Copyright (C) 2008 Michael Zapf (Michael.Zapf@mizapf.de)
Copyright (C) 2011 Gerhard Wiesinger (gerhard@wiesinger.com)
This program 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.
This program 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 .
**************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define COMMAND 0
#define PHEAD 1
#define LCYL 2
#define LHEAD 3
#define SECTOR 4
#define SECTSIZE 5
#define SECTPERCYL 6
#define GAP 7
#define SIZE2 8
#define FM 1
#define MFM 0
#define SINGLE 1
#define DOUBLE 2
// Multitrack
#define CMD_MT (0x80)
// MFM mode
#define CMD_MFM (0x40)
// Skip deleted
#define CMD_SK (0x20)
#define SECTOR_SIZE (0x100)
// Reply interrupt code (2 status bits, bits 7,6)
#define REPLY_STATUS_MASK (ST0_INTR)
struct geometry_t
{
int tracks;
int sectors;
int sides;
int rate;
int density;
int stepping;
} geometry;
int disk_analysis;
int command_display;
int debug;
int verbose;
int no_multi_track;
int no_skip_deleted;
int recalibrate_on_error;
int reply_display;
int sectorwise_read;
struct floppy_raw_cmd raw_cmd;
void print_command()
{
unsigned int i = 0;
printf("command: flags=0x%08X, rate=0x%02X, track=%d, len=%ld, command_count=%d, command[hex]=",
raw_cmd.flags, (int)raw_cmd.rate, raw_cmd.track, raw_cmd.length, (int)raw_cmd.cmd_count);
for (i=0; i < raw_cmd.cmd_count; ++i)
printf("%02X ", (unsigned int)raw_cmd.cmd[i]);
printf("\n");
fflush(stdout);
}
int is_reply_error_byte(unsigned char* reply)
{
return ((reply[0] & REPLY_STATUS_MASK) != 0);
}
int is_reply_error(struct floppy_raw_cmd* cmd)
{
if (cmd->reply_count < 7)
{
printf("Got no valid reply with length >=7");
exit(1);
}
return is_reply_error_byte(&(cmd->reply[0]));
}
void print_reply()
{
unsigned int i = 0;
printf("reply_count=%d, reply[hex]=", (int)raw_cmd.reply_count);
for (i=0; i < raw_cmd.reply_count; ++i)
printf("%02X ", (unsigned int)raw_cmd.reply[i]);
printf("\n");
fflush(stdout);
}
int fileout_seek(FILE* fout, int track, int sector, int head)
{
int error = 0;
long offset = 0;
if (geometry.sides == 2 && head == 1)
{
// Second side: starting with half offset of file and tracks geometry.tracks-1 downto 0
offset = SECTOR_SIZE * (sector + geometry.sectors * (geometry.tracks + (geometry.tracks-1) - track));
}
else
{
// First side: starting with 0 offset of file and tracks 0 to geometry.tracks-1
offset = SECTOR_SIZE * (sector + geometry.sectors * track);
}
if (debug) printf("Seek to file position %ld\n", offset);
error = fseek(fout, offset, SEEK_SET);
if (error) printf("Can't seek to file position %ld\n", offset);
return error;
}
int write_initial_file(FILE* fout)
{
wchar_t* buffer = NULL;
long offset = 0;
int size = 0;
int error = fseek(fout, 0, SEEK_SET);
if (error)
{
printf("Can't seek to file position %ld\n", offset);
return error;
}
size = SECTOR_SIZE * geometry.sides * geometry.sectors * geometry.tracks;
if (verbose) printf("Allocating file with filesize of %d bytes\n", size);
buffer = malloc(size);
if (buffer == NULL)
{
printf("Can't allocate %d bytes\n", size);
return 1;
}
wmemset(buffer, 0xaddeadde, size/sizeof(wchar_t));
fwrite(buffer, size, 1, fout);
free(buffer);
error = fseek(fout, 0, SEEK_SET);
if (error)
{
printf("Can't seek to file position %ld\n", offset);
return error;
}
return 0;
}
void recalibrate(int fd)
{
int tmp, size;
int head = 0;
int track = 0;
raw_cmd.cmd[PHEAD] = head*4; // 0 and 4 are the sides
raw_cmd.cmd[LHEAD] = head;
raw_cmd.cmd_count = 2;
raw_cmd.length = SECTOR_SIZE;
size = raw_cmd.length;
raw_cmd.rate = geometry.rate;
raw_cmd.flags = FD_RAW_INTR | FD_RAW_NEED_SEEK;
raw_cmd.cmd[COMMAND] = FD_RECALIBRATE;
raw_cmd.cmd[LCYL] = track;
raw_cmd.track = raw_cmd.cmd[LCYL]*geometry.stepping;
if (debug || command_display) print_command();
tmp = ioctl( fd, FDRAWCMD, &raw_cmd );
if (verbose) printf("Recalibrate...\n");
if ( tmp < 0 ){
perror("error executing command");
exit(1);
}
}
void loadsector(int fd, FILE* fout, int track, int sector, int head)
{
int tmp, size;
char buffer[ 512 * 2 * 24 ];
raw_cmd.data = buffer;
raw_cmd.cmd[PHEAD] = head*4; // 0 and 4 are the sides
raw_cmd.cmd[LHEAD] = head;
raw_cmd.cmd[SECTOR] = sector;
raw_cmd.cmd[SECTSIZE] = 1; // 0 = 128, 1 = 256, 2 = 512, 3 = 1024 ...
raw_cmd.cmd[SECTPERCYL] = geometry.sectors;
raw_cmd.cmd[GAP] = 0x1b; // not needed, default
raw_cmd.cmd[SIZE2] = 0xff; // not needed, default
raw_cmd.cmd_count = 9;
raw_cmd.length = SECTOR_SIZE;
size = raw_cmd.length;
raw_cmd.rate = geometry.rate;
raw_cmd.flags = FD_RAW_READ | FD_RAW_INTR | FD_RAW_NEED_SEEK;
raw_cmd.cmd[COMMAND] = FD_READ;
if (geometry.density==FM) raw_cmd.cmd[0] &= ~CMD_MFM;
if (no_multi_track) raw_cmd.cmd[0] &= ~CMD_MT;
if (no_skip_deleted) raw_cmd.cmd[0] &= ~CMD_SK;
raw_cmd.cmd[LCYL] = track;
raw_cmd.track = raw_cmd.cmd[LCYL]*geometry.stepping;
if (debug || command_display) print_command();
tmp = ioctl( fd, FDRAWCMD, &raw_cmd );
if (verbose) printf("Read sector: track %d, sector=%d, head=%d, reply_count=%d, status=0x%02X\n", track, sector, head, (int)raw_cmd.reply_count, (int)raw_cmd.reply[0]);
if ( tmp < 0 ){
perror("error executing command");
exit(1);
}
if (debug || reply_display) print_reply();
if (is_reply_error(&raw_cmd))
{
printf("Read error reading sector %d on track %d, side %d. Filling with 0xdead.\n", sector, track, head+1);
wmemset((wchar_t*)buffer, 0xaddeadde, size/sizeof(wchar_t));
}
(void)fileout_seek(fout, track, sector, head);
fwrite(buffer, size, 1, fout);
}
void loadtrack(int fd, FILE* fout, int track, int head)
{
int tmp, size;
char buffer[ 512 * 2 * 24 ];
if (sectorwise_read)
{
int sector;
for (sector=0; sector < geometry.sectors; sector++)
{
loadsector(fd, fout, track, sector, head);
}
return;
}
raw_cmd.data = buffer;
raw_cmd.cmd[PHEAD] = head*4; // 0 and 4 are the sides
raw_cmd.cmd[LHEAD] = head;
raw_cmd.cmd[SECTOR] = 0;
raw_cmd.cmd[SECTSIZE] = 1; // 0 = 128, 1 = 256, 2 = 512, 3 = 1024 ...
raw_cmd.cmd[SECTPERCYL] = geometry.sectors;
raw_cmd.cmd[GAP] = 0x1b; // not needed, default
raw_cmd.cmd[SIZE2] = 0xff; // not needed, default
raw_cmd.cmd_count = 9;
raw_cmd.length = SECTOR_SIZE * geometry.sectors;
size = raw_cmd.length;
raw_cmd.rate = geometry.rate;
raw_cmd.flags = FD_RAW_READ | FD_RAW_INTR | FD_RAW_NEED_SEEK;
raw_cmd.cmd[COMMAND] = 0x02 | CMD_MFM;
if (geometry.density==FM) raw_cmd.cmd[0] &= ~CMD_MFM;
raw_cmd.cmd[LCYL] = track;
raw_cmd.track = raw_cmd.cmd[LCYL]*geometry.stepping;
if (debug || command_display) print_command();
tmp = ioctl( fd, FDRAWCMD, &raw_cmd );
if (verbose) printf("Read track: track=%d, head=%d, reply_count=%d, status=0x%02X\n", track, head, (int)raw_cmd.reply_count, (int)raw_cmd.reply[0]);
if ( tmp < 0 ){
perror("error executing command");
exit(1);
}
if (debug || reply_display) print_reply();
if (is_reply_error(&raw_cmd))
{
int sector;
if (verbose) printf("Read error reading track %d, side %d. Trying to read separate sectors.\n", track, head+1);
if (recalibrate_on_error) recalibrate(fd);
for (sector=0; sector < geometry.sectors; sector++)
{
loadsector(fd, fout, track, sector, head);
}
}
else
{
(void)fileout_seek(fout, track, 0, head);
fwrite(buffer, size, 1, fout);
}
}
unsigned char *try_readid(int fd, int rate, int head, int track, int single_density)
{
int tmp;
int need_seek = 1;
raw_cmd.cmd[COMMAND] = FD_READID;
raw_cmd.cmd[PHEAD] = head*4; // 0 and 4 are the sides
raw_cmd.cmd_count = 2;
raw_cmd.rate = rate;
raw_cmd.flags = FD_RAW_INTR;
if (need_seek) raw_cmd.flags |= FD_RAW_NEED_SEEK;
raw_cmd.track = track;
if (single_density) raw_cmd.cmd[0] &= ~CMD_MFM;
if (no_multi_track) raw_cmd.cmd[0] &= ~CMD_MT;
if (no_skip_deleted) raw_cmd.cmd[0] &= ~CMD_SK;
if (debug || command_display) print_command();
tmp = ioctl( fd, FDRAWCMD, &raw_cmd );
if ( tmp < 0 ){
perror("error executing command");
exit(1);
}
if (debug) print_reply();
return raw_cmd.reply;
}
/* Note: This analysis cannot detect whether a disk is only 40 tracks when it
was once formatted as 80 tracks
*/
void analyze_disk(int fd)
{
// fdrawcmd readid 0 rate=0 need_seek track=0
unsigned char *status;
// Determine rate
int rate, density;
int sectors, i, heads, stepping, tracks;
int min_sector = 100000;
density = MFM;
if (debug) printf("Detecting rate ...\n");
for (rate=0; rate < 4; rate++)
{
status = try_readid(fd, rate, 0, 0, density);
if (!is_reply_error_byte(status)) break;
}
if (rate==4)
{
if (debug) printf("Trying single density ...\n");
// Try single density
density = FM;
for (rate=0; rate < 4; rate++)
{
status = try_readid(fd, rate, 0, 0, density);
if (!is_reply_error_byte(status)) break;
}
if (rate==4)
{
printf("Error ... could not determine rate. Bad floppy disk?\n");
exit(1);
}
}
if (debug) printf("Rate=%d\n", rate);
if (debug) printf("Detecting number of sides ...\n");
// Determine number of sides
status = try_readid(fd, rate, 1, 0, density);
if (!is_reply_error_byte(status)) heads = 2;
else {
// Try another track
if (debug) printf("Recalibrating ...\n");
recalibrate(fd);
if (debug) printf("Reading track 18 for side detection ...\n");
status = try_readid(fd, rate, 1, 18, density);
if (!is_reply_error_byte(status)) heads = 2;
else
heads = 1;
}
if (debug) printf("Heads=%d\n", heads);
if (debug) printf("Detecting number of sectors ...\n");
// Determine number of sectors per track
// Heuristic: Statistical analysis of reading same track e.g. 200 (was 50) sectors and determining the maximum
sectors = 0;
for (i=0; i < 200; i++)
{
if (debug) printf("Read track attempt number %d\n", i);
status = try_readid(fd, rate, 0, 0, density);
if (is_reply_error_byte(status))
{
printf("Error ... could not read a sector. Bad floppy disk?\n");
exit(1);
}
if (status[5] > sectors) sectors = status[5];
if (status[5] < min_sector) min_sector = status[5];
}
if (debug) printf("min_sector=%d\n", min_sector);
if (debug) printf("max_sector=%d\n", sectors);
sectors = sectors+1;
if (debug) printf("Sectors=%d\n", sectors);
if (debug) printf("Detecting single or double stepping ...\n");
// Determine single or double stepping
status = try_readid(fd, rate, 0, 2, density);
if (is_reply_error_byte(status))
{
printf("Error ... could not read a sector. Bad floppy disk?\n");
exit(1);
}
if (status[3]==2)
{
stepping=SINGLE;
}
else
{
if (status[3]==1)
{
stepping = DOUBLE;
}
else
{
printf("Error ... could not determine stepping. Bad floppy disk?\n");
exit(1);
}
}
if (debug) printf("Stepping=%d\n", stepping);
if (debug) printf("Detecting number of tracks ...\n");
// Determine number of tracks
// Heuristic: 80 tracks/40 tracks, or 70 tracks/35 tracks
// Double stepping means a maximum of 40 or 35 tracks.
tracks = 80;
if (stepping==1) {
status = try_readid(fd, rate, 0, tracks-1, density);
if (is_reply_error_byte(status))
{
tracks = 70;
status = try_readid(fd, rate, 0, tracks-1, density);
if (is_reply_error_byte(status))
{
tracks = 40;
status = try_readid(fd, rate, 0, tracks-1, density);
if (is_reply_error_byte(status))
{
tracks = 35;
status = try_readid(fd, rate, 0, tracks-1, density);
if (is_reply_error_byte(status))
{
printf("Cannot determine last track. Bad floppy disk?\n");
exit(1);
}
}
}
}
}
else
{ // double stepping; 40 or 35
tracks = 40;
status = try_readid(fd, rate, 0, 2*(tracks-1), density);
if (is_reply_error_byte(status))
{
tracks = 35;
status = try_readid(fd, rate, 0, 2*(tracks-1), density);
if (is_reply_error_byte(status))
{
printf("Cannot determine last track. Bad floppy disk?\n");
exit(1);
}
}
}
if (debug) printf("Tracks=%d\n", tracks);
geometry.rate = rate;
geometry.density = density;
geometry.sectors = sectors;
geometry.sides = heads;
geometry.stepping = stepping;
geometry.tracks = tracks;
if (verbose || disk_analysis)
{
printf("Rate = %d\n", rate);
printf("Single density = %d\n", density);
printf("Sectors = %d\n", sectors);
printf("Heads = %d\n", heads);
printf("Stepping(physical/logical tracks) = %d\n", stepping);
printf("Logical tracks = %d\n", tracks);
}
}
void usage()
{
printf("Usage: readti [-a] [-c] [-d] [-e] [-l] [-m] [-r] [-s] [-v] device (=/dev/fd0) outfile\n");
printf(" Must be done on a 80 track capable floppy drive (typically no TI ones)\n");
printf(" -a print disk analysis\n");
printf(" -c display commands\n");
printf(" -d debug\n");
printf(" -e display rEply\n");
printf(" -l no skip deLeted\n");
printf(" -m no multi track sector read\n");
printf(" -r no recalibration between track mode read error and sector reading\n");
printf(" -s read tracks sector wise and not a whole track (doesn't work on some machines otherwise)\n");
printf(" -v verbose\n");
exit(1);
}
int main(int argc, char **argv)
{
FILE* fout = NULL;
int fd, i;
char c;
disk_analysis = 0;
command_display = 0;
debug = 0;
reply_display = 0;
no_multi_track = 0;
no_skip_deleted = 0;
recalibrate_on_error = 1;
sectorwise_read = 0;
verbose = 0;
if (argc < 3) usage();
while ((c = getopt (argc, argv, "acdelmrsv")) != -1)
{
switch (c)
{
case 'a':
disk_analysis = 1;
break;
case 'c':
command_display = 1;
break;
case 'd':
debug = 1;
break;
case 'e':
reply_display = 1;
break;
case 'l':
no_skip_deleted = 1;
break;
case 'm':
no_multi_track = 1;
break;
case 'r': // no recalibrate on error
recalibrate_on_error = 0;
break;
case 's': // Read track sectorwise
sectorwise_read = 1;
break;
case 'v':
verbose = 1;
break;
default:
usage();
break;
}
}
// 2 arguments must be left
if (argc - optind < 2) usage();
fd = open(argv[optind++], O_ACCMODE | O_NDELAY);
if ( fd < 0 ){
perror("error opening floppy");
exit(1);
}
fout = fopen(argv[optind++], "w");
if ( fout == NULL ){
perror("error opening output file");
exit(1);
}
analyze_disk(fd);
(void)write_initial_file(fout);
for (i=0; i < geometry.tracks; i++)
{
loadtrack(fd, fout, i, 0);
if (geometry.sides==2)
{
loadtrack(fd, fout, i, 1);
}
}
if (close(fd)) perror("close error for input file");
if (fclose(fout)) perror("close error for output file");
return 0;
}