// This module is routines for controlling the disk drive. // drive_select selects the specified drive or none. // drive_set_head sets the head select lines to select the specified head. // drive_seek_track0 returns the head to track 0. // drive_setup sets up the drive for reading. // drive_read_disk reads the disk drive. // drive_current_cyl gets the current cylinder the drive heads are on // drive_rpm get drive rpm // drive_read_track read a track of deltas from a drive. It steps the // head if necessary. // drive_step steps the head the requested number of cylinders. // drive_is_file: Indicate if real drive or reading from file // drive_enable_recovery: Set the recovery line active/inactive // drive_has_write_fault: Return true if drive has write fault // // The drive must be at track 0 on startup or drive_seek_track0 called. // // 06/02/2023 DJG Fixed write fault error reading NEC drive // 07/05/2019 DJG Added support for using recovery signal // 03/22/2019 DJG Added REV C support // 09/01/2018 DJG Drive 0 is valid for drive_select(), don't drive // and select lines // 08/05/2018 DJG Drive 0 invalid for drive_select() // 05/06/2018 DJG Adjustement to try to make Syquest disks work better // 03/09/2018 DJG Make sure correct setup script run so pins are in correct // direction. // 10/02/2016 DJG Rob Jarratt change for DEC RD drives to detect when // it recalibrates back to track 0 when stepping past end // 02/20/2016 DJG Split for drive reading and writing // 01/06/2016 DJG Detect reversed J4 cable // 12/24/2015 DJG Fix comment // 07/30/2015 DJG Added support for revision B board. // 05/16/2015 DJG Changes for drive_file.c // // Copyright 2014-2023 David Gesswein. // This file is part of MFM disk utilities. // // MFM disk utilities 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. // // MFM disk utilities 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 MFM disk utilities. If not, see . #include #include #include #include #include #include #include #include #include #include #include #include #include "msg.h" #include "crc_ecc.h" #include "emu_tran_file.h" #include "mfm_decoder.h" #include "cmd.h" #include "cmd_write.h" #include "deltas_read.h" #include "pru_setup.h" #include "drive.h" #include "board.h" // The cylinder the drive heads are at static int current_cyl; // Activate drive selects to select specified drive // // drive: drive number to select (1-4) or zero to select none. void drive_select(int drive) { char *drive_pins[3][4] = { {"/sys/class/gpio/gpio22/direction", "/sys/class/gpio/gpio23/direction", "/sys/class/gpio/gpio26/direction", "/sys/class/gpio/gpio27/direction"}, {"/sys/class/gpio/gpio22/direction", "/sys/class/gpio/gpio23/direction", "/sys/class/gpio/gpio26/direction", "/sys/class/gpio/gpio27/direction"}, {"/sys/class/gpio/gpio46/direction", "/sys/class/gpio/gpio15/direction", "/sys/class/gpio/gpio26/direction", "/sys/class/gpio/gpio27/direction"} }; static int first_time = 1; static int fd[4]; int i; int board_revision; board_revision = board_get_revision(); if (drive < 0 || drive > 4) { msg(MSG_FATAL, "Invalid drive %d\n", drive); exit(1); } // Open the files once if (first_time) { int rc; char buf[100]; first_time = 0; for (i = 0; i < 4; i++) { fd[i] = open(drive_pins[board_revision][i], O_RDWR); if (fd[i] < 0) { msg(MSG_FATAL, "Unable to open pin %s, did you run the setup script?\n", drive_pins[board_revision][i]); exit(1); } } i = 0; // Make sure correct setup run rc = read(fd[i], buf, sizeof(buf)); if (rc != 4) { if (rc < 0) { msg(MSG_FATAL, "Error reading pin %s, did you run the setup script?\n", drive_pins[board_revision][i]); exit(1); } // Remove newline if (rc > 1) { buf[rc-1] = 0; } msg(MSG_FATAL, "Wrong pin setting pin %s - %s, must reboot between reading and emulation.\n", drive_pins[board_revision][i], buf); exit(1); } } // Set the signals to the correct state for (i = 0; i < 4; i++) { if (drive == (i + 1)) { write(fd[i], "high", 4); // Signal is active low with inverting driver } else { write(fd[i], "low", 3); } } usleep(10); // Allow lines to settle } // Select the disk head. Head 0-15 supported. The MSB of head select // is reduced write current on older drives but since we aren't writing // we don't have to worry about it. // // head: Head to select 0-15 void drive_set_head(int head) { char *head_pins[3][4] = { { "/sys/class/gpio/gpio2/direction", "/sys/class/gpio/gpio3/direction", "/sys/class/gpio/gpio4/direction", "/sys/class/gpio/gpio5/direction" }, { "/sys/class/gpio/gpio8/direction", "/sys/class/gpio/gpio9/direction", "/sys/class/gpio/gpio10/direction", "/sys/class/gpio/gpio11/direction" }, { "/sys/class/gpio/gpio8/direction", "/sys/class/gpio/gpio9/direction", "/sys/class/gpio/gpio10/direction", "/sys/class/gpio/gpio11/direction" } }; static int first_time = 1; static int fd[4]; int i; int board_revision; board_revision = board_get_revision(); if (head < 0 || head > 15) { msg(MSG_FATAL, "Invalid head %d\n", head); exit(1); } // Open the files once if (first_time) { for (i = 0; i < 4; i++) { fd[i] = open(head_pins[board_revision][i], O_WRONLY); if (fd[i] < 0) { msg(MSG_FATAL, "Unable to open pin %s\n", head_pins[board_revision][i]); exit(1); } } first_time = 0; } // Set the signals to the correct state for (i = 0; i < 4; i++) { if (head & (1 << i)) { write(fd[i], "high", 4); // Signal is active low with inverting driver } else { write(fd[i], "low", 3); } } usleep(10); // Allow lines to settle } // Returns non zero if track 0 signal is active (zero) int drive_at_track0(void) { static int fd = -1; char str[128]; int pin; if (fd == -1) { if (board_get_revision() == 2) { pin = GPIO3_TRACK_0_BIT_REVC + GPIO3_START_PIN; } else { pin = GPIO0_TRACK_0_BIT; } sprintf(str, "/sys/class/gpio/gpio%d/value", pin); fd = open(str, O_RDONLY); if (fd < 0) { msg(MSG_FATAL, "Unable to open pin %d\n", pin); exit(1); } } // Make sure drive ready and seek complete before checking track0 if (pru_exec_cmd(CMD_CHECK_READY, 0)) { drive_print_drive_status(MSG_FATAL, drive_get_drive_status()); exit(1); } pread(fd, str, sizeof(str), 0); // 0 is at track 0 return str[0] == '0'; } // Return the heads to track 0. This is slow so if you know what track you // are on use a normal seek. void drive_seek_track0(void) { int rc; rc = pru_exec_cmd(CMD_SEEK_SLOW_TRACK0, -MAX_CYL); if (rc != 0) { drive_print_drive_status(MSG_FATAL, drive_get_drive_status()); exit(1); }; // Low indicates we are at track 0 if (!drive_at_track0()) { msg(MSG_FATAL,"Failed to reach track 0\n"); exit(1); } current_cyl = 0; } // Step requested number of cylinders // buffered_seek: nonzero if drive supports buffered seeks // steps: Number of cylinders to step, negative is towards lower cylinder // update_cyl: Non zero if internal current cylinder should be updated // err_fatal: If set a timeout error will terminate, otherwise returned // as SEEK_TIMEOUT. SEEK_RECAL indicates the drive recalibrated to track 0 int drive_step(int step_speed, int steps, int update_cyl, int err_fatal) { int seek_cmd; int rc; int wait_count = 0, ready = 0; int start_cyl = current_cyl; if (update_cyl == DRIVE_STEP_UPDATE_CYL) { current_cyl += steps; } if (step_speed == DRIVE_STEP_FAST) { seek_cmd = CMD_SEEK_FAST; } else { seek_cmd = CMD_SEEK_SLOW; } if ((rc = pru_exec_cmd(seek_cmd, steps)) != 0) { if (err_fatal) { msg(MSG_FATAL,"seek command failed\n"); drive_print_drive_status(MSG_FATAL, drive_get_drive_status()); exit(1); } else { // Wait for seek complete for 10 seconds. This prevents errors if we // next read the disk while (wait_count++ < 100 && !ready) { // Bit is active low ready = !(drive_get_drive_status() & BIT_MASK(R31_SEEK_COMPLETE_BIT)); usleep(100000); } } rc = DRIVE_STEP_TIMEOUT; } if (drive_at_track0() && start_cyl != 0 && current_cyl != 0) { msg(MSG_INFO, "Disk has recalibrated to track 0\n"); current_cyl = 0; rc = DRIVE_STEP_RECAL; } return rc; } // Get current cylinder int drive_current_cyl() { return current_cyl; } // Select drive, check if drive is ready and then return to track zero if needed // // drive_params: Drive parameters void drive_setup(DRIVE_PARAMS *drive_params) { // Turn off recovery mode drive_enable_recovery(0); // Make sure head lines valid. NEC D5124 generates write fault if invalid // head selected. drive_set_head(0); drive_select(drive_params->drive); if (pru_exec_cmd(CMD_CHECK_READY, 0)) { drive_print_drive_status(MSG_FATAL, drive_get_drive_status()); exit(1); } if (!drive_at_track0()) { msg(MSG_INFO, "Returning to track 0\n"); drive_seek_track0(); } } // Return the drive status value. See print routine for bit definitions. uint32_t drive_get_drive_status(void) { return pru_read_word(MEM_PRU0_DATA, PRU0_STATUS); } // Decodes and prints the disk status value from the PRU. // // status: Disk status value (from pru_get_drive_status) void drive_print_drive_status(int level, uint32_t status) { struct { char *desc; int bit; } reg_bits[] = { {"Write fault", R31_WRITE_FAULT_BIT}, {"Seek complete", R31_SEEK_COMPLETE_BIT}, {"Index", R31_INDEX_BIT}, {"Ready", R31_READY_BIT}, {"Drive selected", R31_DRIVE_SEL}, // TODO {"Track 0", R31_TRACK_0} }; int n; for (n = 0; n < ARRAYSIZE(reg_bits); n++) { // Control lines are active low if (status & (1 << reg_bits[n].bit)) { msg(level, "Not %s \n", reg_bits[n].desc); } else { msg(level, "%s \n", reg_bits[n].desc); } } } // Return true if write fault is active on drive int drive_has_write_fault(void) { // Status bits are active low so invert before checking return ~drive_get_drive_status() & (1 << R31_WRITE_FAULT_BIT); } // Print the drive Revolutions Per Minute (RPM) // return drive RPM double drive_rpm(void) { if (pru_exec_cmd(CMD_RPM, 0) == 0) { return(200e6 / pru_get_cmd_data() * 60); } else { msg(MSG_FATAL, "Drive RPM failed\n"); if ((pru_read_word(MEM_PRU0_DATA, PRU0_STATUS) & ((1 << R31_INDEX_BIT) | (1 << R31_WRITE_FAULT_BIT))) == 0) { msg(MSG_FATAL, "Is J4 cable plugged in backward?\n"); } exit(1); } } // Activate recovery mode if enable non zero // // enable: Non zero value to enable recovery mode, 0 normal mode void drive_enable_recovery(int enable) { char *pin = "/sys/class/gpio/gpio31/direction"; static int first_time = 1; static int fd; // Open the files once if (first_time) { first_time = 0; fd = open(pin, O_RDWR); if (fd < 0) { msg(MSG_FATAL, "Unable to open pin %s, did you run the setup script?\n", pin); exit(1); } } if (enable) { write(fd, "high", 4); // Signal is active low with inverting driver } else { write(fd, "low", 3); } usleep(100); // Allow lines to settle }