/************************************************************************* 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; }