diff options
Diffstat (limited to 'drivers/char/ftape/lowlevel/ftape-rw.c')
-rw-r--r-- | drivers/char/ftape/lowlevel/ftape-rw.c | 1092 |
1 files changed, 1092 insertions, 0 deletions
diff --git a/drivers/char/ftape/lowlevel/ftape-rw.c b/drivers/char/ftape/lowlevel/ftape-rw.c new file mode 100644 index 000000000000..c0d6dc2cbfd3 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-rw.c @@ -0,0 +1,1092 @@ +/* + * Copyright (C) 1993-1996 Bas Laarhoven, + * (C) 1996-1997 Claus-Justus Heine. + + 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 2, 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; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + + * + * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-rw.c,v $ + * $Revision: 1.7 $ + * $Date: 1997/10/28 14:26:49 $ + * + * This file contains some common code for the segment read and + * segment write routines for the QIC-117 floppy-tape driver for + * Linux. + */ + +#include <linux/string.h> +#include <linux/errno.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/fdc-io.h" +#include "../lowlevel/ftape-init.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-ecc.h" +#include "../lowlevel/ftape-bsm.h" + +/* Global vars. + */ +int ft_nr_buffers; +buffer_struct *ft_buffer[FT_MAX_NR_BUFFERS]; +static volatile int ft_head; +static volatile int ft_tail; /* not volatile but need same type as head */ +int fdc_setup_error; +location_record ft_location = {-1, 0}; +volatile int ftape_tape_running; + +/* Local vars. + */ +static int overrun_count_offset; +static int inhibit_correction; + +/* maxmimal allowed overshoot when fast seeking + */ +#define OVERSHOOT_LIMIT 10 + +/* Increment cyclic buffer nr. + */ +buffer_struct *ftape_next_buffer(ft_buffer_queue_t pos) +{ + switch (pos) { + case ft_queue_head: + if (++ft_head >= ft_nr_buffers) { + ft_head = 0; + } + return ft_buffer[ft_head]; + case ft_queue_tail: + if (++ft_tail >= ft_nr_buffers) { + ft_tail = 0; + } + return ft_buffer[ft_tail]; + default: + return NULL; + } +} +int ftape_buffer_id(ft_buffer_queue_t pos) +{ + switch(pos) { + case ft_queue_head: return ft_head; + case ft_queue_tail: return ft_tail; + default: return -1; + } +} +buffer_struct *ftape_get_buffer(ft_buffer_queue_t pos) +{ + switch(pos) { + case ft_queue_head: return ft_buffer[ft_head]; + case ft_queue_tail: return ft_buffer[ft_tail]; + default: return NULL; + } +} +void ftape_reset_buffer(void) +{ + ft_head = ft_tail = 0; +} + +buffer_state_enum ftape_set_state(buffer_state_enum new_state) +{ + buffer_state_enum old_state = ft_driver_state; + + ft_driver_state = new_state; + return old_state; +} +/* Calculate Floppy Disk Controller and DMA parameters for a segment. + * head: selects buffer struct in array. + * offset: number of physical sectors to skip (including bad ones). + * count: number of physical sectors to handle (including bad ones). + */ +static int setup_segment(buffer_struct * buff, + int segment_id, + unsigned int sector_offset, + unsigned int sector_count, + int retry) +{ + SectorMap offset_mask; + SectorMap mask; + TRACE_FUN(ft_t_any); + + buff->segment_id = segment_id; + buff->sector_offset = sector_offset; + buff->remaining = sector_count; + buff->head = segment_id / ftape_segments_per_head; + buff->cyl = (segment_id % ftape_segments_per_head) / ftape_segments_per_cylinder; + buff->sect = (segment_id % ftape_segments_per_cylinder) * FT_SECTORS_PER_SEGMENT + 1; + buff->deleted = 0; + offset_mask = (1 << buff->sector_offset) - 1; + mask = ftape_get_bad_sector_entry(segment_id) & offset_mask; + while (mask) { + if (mask & 1) { + offset_mask >>= 1; /* don't count bad sector */ + } + mask >>= 1; + } + buff->data_offset = count_ones(offset_mask); /* good sectors to skip */ + buff->ptr = buff->address + buff->data_offset * FT_SECTOR_SIZE; + TRACE(ft_t_flow, "data offset = %d sectors", buff->data_offset); + if (retry) { + buff->soft_error_map &= offset_mask; /* keep skipped part */ + } else { + buff->hard_error_map = buff->soft_error_map = 0; + } + buff->bad_sector_map = ftape_get_bad_sector_entry(buff->segment_id); + if (buff->bad_sector_map != 0) { + TRACE(ft_t_noise, "segment: %d, bad sector map: %08lx", + buff->segment_id, (long)buff->bad_sector_map); + } else { + TRACE(ft_t_flow, "segment: %d", buff->segment_id); + } + if (buff->sector_offset > 0) { + buff->bad_sector_map >>= buff->sector_offset; + } + if (buff->sector_offset != 0 || buff->remaining != FT_SECTORS_PER_SEGMENT) { + TRACE(ft_t_flow, "sector offset = %d, count = %d", + buff->sector_offset, buff->remaining); + } + /* Segments with 3 or less sectors are not written with valid + * data because there is no space left for the ecc. The + * data written is whatever happens to be in the buffer. + * Reading such a segment will return a zero byte-count. + * To allow us to read/write segments with all bad sectors + * we fake one readable sector in the segment. This + * prevents having to handle these segments in a very + * special way. It is not important if the reading of this + * bad sector fails or not (the data is ignored). It is + * only read to keep the driver running. + * + * The QIC-40/80 spec. has no information on how to handle + * this case, so this is my interpretation. + */ + if (buff->bad_sector_map == EMPTY_SEGMENT) { + TRACE(ft_t_flow, "empty segment %d, fake first sector good", + buff->segment_id); + if (buff->ptr != buff->address) { + TRACE(ft_t_bug, "This is a bug: %p/%p", + buff->ptr, buff->address); + } + buff->bad_sector_map = FAKE_SEGMENT; + } + fdc_setup_error = 0; + buff->next_segment = segment_id + 1; + TRACE_EXIT 0; +} + +/* Calculate Floppy Disk Controller and DMA parameters for a new segment. + */ +int ftape_setup_new_segment(buffer_struct * buff, int segment_id, int skip) +{ + int result = 0; + static int old_segment_id = -1; + static buffer_state_enum old_ft_driver_state = idle; + int retry = 0; + unsigned offset = 0; + int count = FT_SECTORS_PER_SEGMENT; + TRACE_FUN(ft_t_flow); + + TRACE(ft_t_flow, "%s segment %d (old = %d)", + (ft_driver_state == reading || ft_driver_state == verifying) + ? "reading" : "writing", + segment_id, old_segment_id); + if (ft_driver_state != old_ft_driver_state) { /* when verifying */ + old_segment_id = -1; + old_ft_driver_state = ft_driver_state; + } + if (segment_id == old_segment_id) { + ++buff->retry; + ++ft_history.retries; + TRACE(ft_t_flow, "setting up for retry nr %d", buff->retry); + retry = 1; + if (skip && buff->skip > 0) { /* allow skip on retry */ + offset = buff->skip; + count -= offset; + TRACE(ft_t_flow, "skipping %d sectors", offset); + } + } else { + buff->retry = 0; + buff->skip = 0; + old_segment_id = segment_id; + } + result = setup_segment(buff, segment_id, offset, count, retry); + TRACE_EXIT result; +} + +/* Determine size of next cluster of good sectors. + */ +int ftape_calc_next_cluster(buffer_struct * buff) +{ + /* Skip bad sectors. + */ + while (buff->remaining > 0 && (buff->bad_sector_map & 1) != 0) { + buff->bad_sector_map >>= 1; + ++buff->sector_offset; + --buff->remaining; + } + /* Find next cluster of good sectors + */ + if (buff->bad_sector_map == 0) { /* speed up */ + buff->sector_count = buff->remaining; + } else { + SectorMap map = buff->bad_sector_map; + + buff->sector_count = 0; + while (buff->sector_count < buff->remaining && (map & 1) == 0) { + ++buff->sector_count; + map >>= 1; + } + } + return buff->sector_count; +} + +/* if just passed the last segment on a track, wait for BOT + * or EOT mark. + */ +int ftape_handle_logical_eot(void) +{ + TRACE_FUN(ft_t_flow); + + if (ft_runner_status == logical_eot) { + int status; + + TRACE(ft_t_noise, "tape at logical EOT"); + TRACE_CATCH(ftape_ready_wait(ftape_timeout.seek, &status),); + if ((status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) { + TRACE_ABORT(-EIO, ft_t_err, "eot/bot not reached"); + } + ft_runner_status = end_of_tape; + } + if (ft_runner_status == end_of_tape) { + TRACE(ft_t_noise, "runner stopped because of logical EOT"); + ft_runner_status = idle; + } + TRACE_EXIT 0; +} + +static int check_bot_eot(int status) +{ + TRACE_FUN(ft_t_flow); + + if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) { + ft_location.bot = ((ft_location.track & 1) == 0 ? + (status & QIC_STATUS_AT_BOT) != 0: + (status & QIC_STATUS_AT_EOT) != 0); + ft_location.eot = !ft_location.bot; + ft_location.segment = (ft_location.track + + (ft_location.bot ? 0 : 1)) * ft_segments_per_track - 1; + ft_location.sector = -1; + ft_location.known = 1; + TRACE(ft_t_flow, "tape at logical %s", + ft_location.bot ? "bot" : "eot"); + TRACE(ft_t_flow, "segment = %d", ft_location.segment); + } else { + ft_location.known = 0; + } + TRACE_EXIT ft_location.known; +} + +/* Read Id of first sector passing tape head. + */ +static int ftape_read_id(void) +{ + int status; + __u8 out[2]; + TRACE_FUN(ft_t_any); + + /* Assume tape is running on entry, be able to handle + * situation where it stopped or is stopping. + */ + ft_location.known = 0; /* default is location not known */ + out[0] = FDC_READID; + out[1] = ft_drive_sel; + TRACE_CATCH(fdc_command(out, 2),); + switch (fdc_interrupt_wait(20 * FT_SECOND)) { + case 0: + if (fdc_sect == 0) { + if (ftape_report_drive_status(&status) >= 0 && + (status & QIC_STATUS_READY)) { + ftape_tape_running = 0; + TRACE(ft_t_flow, "tape has stopped"); + check_bot_eot(status); + } + } else { + ft_location.known = 1; + ft_location.segment = (ftape_segments_per_head + * fdc_head + + ftape_segments_per_cylinder + * fdc_cyl + + (fdc_sect - 1) + / FT_SECTORS_PER_SEGMENT); + ft_location.sector = ((fdc_sect - 1) + % FT_SECTORS_PER_SEGMENT); + ft_location.eot = ft_location.bot = 0; + } + break; + case -ETIME: + /* Didn't find id on tape, must be near end: Wait + * until stopped. + */ + if (ftape_ready_wait(FT_FOREVER, &status) >= 0) { + ftape_tape_running = 0; + TRACE(ft_t_flow, "tape has stopped"); + check_bot_eot(status); + } + break; + default: + /* Interrupted or otherwise failing + * fdc_interrupt_wait() + */ + TRACE(ft_t_err, "fdc_interrupt_wait failed"); + break; + } + if (!ft_location.known) { + TRACE_ABORT(-EIO, ft_t_flow, "no id found"); + } + if (ft_location.sector == 0) { + TRACE(ft_t_flow, "passing segment %d/%d", + ft_location.segment, ft_location.sector); + } else { + TRACE(ft_t_fdc_dma, "passing segment %d/%d", + ft_location.segment, ft_location.sector); + } + TRACE_EXIT 0; +} + +static int logical_forward(void) +{ + ftape_tape_running = 1; + return ftape_command(QIC_LOGICAL_FORWARD); +} + +int ftape_stop_tape(int *pstatus) +{ + int retry = 0; + int result; + TRACE_FUN(ft_t_flow); + + do { + result = ftape_command_wait(QIC_STOP_TAPE, + ftape_timeout.stop, pstatus); + if (result == 0) { + if ((*pstatus & QIC_STATUS_READY) == 0) { + result = -EIO; + } else { + ftape_tape_running = 0; + } + } + } while (result < 0 && ++retry <= 3); + if (result < 0) { + TRACE(ft_t_err, "failed ! (fatal)"); + } + TRACE_EXIT result; +} + +int ftape_dumb_stop(void) +{ + int result; + int status; + TRACE_FUN(ft_t_flow); + + /* Abort current fdc operation if it's busy (probably read + * or write operation pending) with a reset. + */ + if (fdc_ready_wait(100 /* usec */) < 0) { + TRACE(ft_t_noise, "aborting fdc operation"); + fdc_reset(); + } + /* Reading id's after the last segment on a track may fail + * but eventually the drive will become ready (logical eot). + */ + result = ftape_report_drive_status(&status); + ft_location.known = 0; + do { + if (result == 0 && status & QIC_STATUS_READY) { + /* Tape is not running any more. + */ + TRACE(ft_t_noise, "tape already halted"); + check_bot_eot(status); + ftape_tape_running = 0; + } else if (ftape_tape_running) { + /* Tape is (was) still moving. + */ +#ifdef TESTING + ftape_read_id(); +#endif + result = ftape_stop_tape(&status); + } else { + /* Tape not yet ready but stopped. + */ + result = ftape_ready_wait(ftape_timeout.pause,&status); + } + } while (ftape_tape_running + && !(sigtestsetmask(¤t->pending.signal, _NEVER_BLOCK))); +#ifndef TESTING + ft_location.known = 0; +#endif + if (ft_runner_status == aborting || ft_runner_status == do_abort) { + ft_runner_status = idle; + } + TRACE_EXIT result; +} + +/* Wait until runner has finished tail buffer. + * + */ +int ftape_wait_segment(buffer_state_enum state) +{ + int status; + int result = 0; + TRACE_FUN(ft_t_flow); + + while (ft_buffer[ft_tail]->status == state) { + TRACE(ft_t_flow, "state: %d", ft_buffer[ft_tail]->status); + /* First buffer still being worked on, wait up to timeout. + * + * Note: we check two times for being killed. 50 + * seconds are quite long. Note that + * fdc_interrupt_wait() is not killable by any + * means. ftape_read_segment() wants us to return + * -EINTR in case of a signal. + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + result = fdc_interrupt_wait(50 * FT_SECOND); + FT_SIGNAL_EXIT(_DONT_BLOCK); + if (result < 0) { + TRACE_ABORT(result, + ft_t_err, "fdc_interrupt_wait failed"); + } + if (fdc_setup_error) { + /* recover... FIXME */ + TRACE_ABORT(-EIO, ft_t_err, "setup error"); + } + } + if (ft_buffer[ft_tail]->status != error) { + TRACE_EXIT 0; + } + TRACE_CATCH(ftape_report_drive_status(&status),); + TRACE(ft_t_noise, "ftape_report_drive_status: 0x%02x", status); + if ((status & QIC_STATUS_READY) && + (status & QIC_STATUS_ERROR)) { + unsigned int error; + qic117_cmd_t command; + + /* Report and clear error state. + * In case the drive can't operate at the selected + * rate, select the next lower data rate. + */ + ftape_report_error(&error, &command, 1); + if (error == 31 && command == QIC_LOGICAL_FORWARD) { + /* drive does not accept this data rate */ + if (ft_data_rate > 250) { + TRACE(ft_t_info, + "Probable data rate conflict"); + TRACE(ft_t_info, + "Lowering data rate to %d Kbps", + ft_data_rate / 2); + ftape_half_data_rate(); + if (ft_buffer[ft_tail]->retry > 0) { + /* give it a chance */ + --ft_buffer[ft_tail]->retry; + } + } else { + /* no rate is accepted... */ + TRACE(ft_t_err, "We're dead :("); + } + } else { + TRACE(ft_t_err, "Unknown error"); + } + TRACE_EXIT -EIO; /* g.p. error */ + } + TRACE_EXIT 0; +} + +/* forward */ static int seek_forward(int segment_id, int fast); + +static int fast_seek(int count, int reverse) +{ + int result = 0; + int status; + TRACE_FUN(ft_t_flow); + + if (count > 0) { + /* If positioned at begin or end of tape, fast seeking needs + * special treatment. + * Starting from logical bot needs a (slow) seek to the first + * segment before the high speed seek. Most drives do this + * automatically but some older don't, so we treat them + * all the same. + * Starting from logical eot is even more difficult because + * we cannot (slow) reverse seek to the last segment. + * TO BE IMPLEMENTED. + */ + inhibit_correction = 0; + if (ft_location.known && + ((ft_location.bot && !reverse) || + (ft_location.eot && reverse))) { + if (!reverse) { + /* (slow) skip to first segment on a track + */ + seek_forward(ft_location.track * ft_segments_per_track, 0); + --count; + } else { + /* When seeking backwards from + * end-of-tape the number of erased + * gaps found seems to be higher than + * expected. Therefor the drive must + * skip some more segments than + * calculated, but we don't know how + * many. Thus we will prevent the + * re-calculation of offset and + * overshoot when seeking backwards. + */ + inhibit_correction = 1; + count += 3; /* best guess */ + } + } + } else { + TRACE(ft_t_flow, "warning: zero or negative count: %d", count); + } + if (count > 0) { + int i; + int nibbles = count > 255 ? 3 : 2; + + if (count > 4095) { + TRACE(ft_t_noise, "skipping clipped at 4095 segment"); + count = 4095; + } + /* Issue this tape command first. */ + if (!reverse) { + TRACE(ft_t_noise, "skipping %d segment(s)", count); + result = ftape_command(nibbles == 3 ? + QIC_SKIP_EXTENDED_FORWARD : QIC_SKIP_FORWARD); + } else { + TRACE(ft_t_noise, "backing up %d segment(s)", count); + result = ftape_command(nibbles == 3 ? + QIC_SKIP_EXTENDED_REVERSE : QIC_SKIP_REVERSE); + } + if (result < 0) { + TRACE(ft_t_noise, "Skip command failed"); + } else { + --count; /* 0 means one gap etc. */ + for (i = 0; i < nibbles; ++i) { + if (result >= 0) { + result = ftape_parameter(count & 15); + count /= 16; + } + } + result = ftape_ready_wait(ftape_timeout.rewind, &status); + if (result >= 0) { + ftape_tape_running = 0; + } + } + } + TRACE_EXIT result; +} + +static int validate(int id) +{ + /* Check to see if position found is off-track as reported + * once. Because all tracks in one direction lie next to + * each other, if off-track the error will be approximately + * 2 * ft_segments_per_track. + */ + if (ft_location.track == -1) { + return 1; /* unforseen situation, don't generate error */ + } else { + /* Use margin of ft_segments_per_track on both sides + * because ftape needs some margin and the error we're + * looking for is much larger ! + */ + int lo = (ft_location.track - 1) * ft_segments_per_track; + int hi = (ft_location.track + 2) * ft_segments_per_track; + + return (id >= lo && id < hi); + } +} + +static int seek_forward(int segment_id, int fast) +{ + int failures = 0; + int count; + static int margin = 1; /* fixed: stop this before target */ + static int overshoot = 1; + static int min_count = 8; + int expected = -1; + int target = segment_id - margin; + int fast_seeking; + int prev_segment = ft_location.segment; + TRACE_FUN(ft_t_flow); + + if (!ft_location.known) { + TRACE_ABORT(-EIO, ft_t_err, + "fatal: cannot seek from unknown location"); + } + if (!validate(segment_id)) { + ftape_sleep(1 * FT_SECOND); + ft_failure = 1; + TRACE_ABORT(-EIO, ft_t_err, + "fatal: head off track (bad hardware?)"); + } + TRACE(ft_t_noise, "from %d/%d to %d/0 - %d", + ft_location.segment, ft_location.sector,segment_id,margin); + count = target - ft_location.segment - overshoot; + fast_seeking = (fast && + count > (min_count + (ft_location.bot ? 1 : 0))); + if (fast_seeking) { + TRACE(ft_t_noise, "fast skipping %d segments", count); + expected = segment_id - margin; + fast_seek(count, 0); + } + if (!ftape_tape_running) { + logical_forward(); + } + while (ft_location.segment < segment_id) { + /* This requires at least one sector in a (bad) segment to + * have a valid and readable sector id ! + * It looks like this is not guaranteed, so we must try + * to find a way to skip an EMPTY_SEGMENT. !!! FIXME !!! + */ + if (ftape_read_id() < 0 || !ft_location.known || + sigtestsetmask(¤t->pending.signal, _DONT_BLOCK)) { + ft_location.known = 0; + if (!ftape_tape_running || + ++failures > FT_SECTORS_PER_SEGMENT) { + TRACE_ABORT(-EIO, ft_t_err, + "read_id failed completely"); + } + FT_SIGNAL_EXIT(_DONT_BLOCK); + TRACE(ft_t_flow, "read_id failed, retry (%d)", + failures); + continue; + } + if (fast_seeking) { + TRACE(ft_t_noise, "ended at %d/%d (%d,%d)", + ft_location.segment, ft_location.sector, + overshoot, inhibit_correction); + if (!inhibit_correction && + (ft_location.segment < expected || + ft_location.segment > expected + margin)) { + int error = ft_location.segment - expected; + TRACE(ft_t_noise, + "adjusting overshoot from %d to %d", + overshoot, overshoot + error); + overshoot += error; + /* All overshoots have the same + * direction, so it should never + * become negative, but who knows. + */ + if (overshoot < -5 || + overshoot > OVERSHOOT_LIMIT) { + if (overshoot < 0) { + /* keep sane value */ + overshoot = -5; + } else { + /* keep sane value */ + overshoot = OVERSHOOT_LIMIT; + } + TRACE(ft_t_noise, + "clipped overshoot to %d", + overshoot); + } + } + fast_seeking = 0; + } + if (ft_location.known) { + if (ft_location.segment > prev_segment + 1) { + TRACE(ft_t_noise, + "missed segment %d while skipping", + prev_segment + 1); + } + prev_segment = ft_location.segment; + } + } + if (ft_location.segment > segment_id) { + TRACE_ABORT(-EIO, + ft_t_noise, "failed: skip ended at segment %d/%d", + ft_location.segment, ft_location.sector); + } + TRACE_EXIT 0; +} + +static int skip_reverse(int segment_id, int *pstatus) +{ + int failures = 0; + static int overshoot = 1; + static int min_rewind = 2; /* 1 + overshoot */ + static const int margin = 1; /* stop this before target */ + int expected = 0; + int count = 1; + int short_seek; + int target = segment_id - margin; + TRACE_FUN(ft_t_flow); + + if (ft_location.known && !validate(segment_id)) { + ftape_sleep(1 * FT_SECOND); + ft_failure = 1; + TRACE_ABORT(-EIO, ft_t_err, + "fatal: head off track (bad hardware?)"); + } + do { + if (!ft_location.known) { + TRACE(ft_t_warn, "warning: location not known"); + } + TRACE(ft_t_noise, "from %d/%d to %d/0 - %d", + ft_location.segment, ft_location.sector, + segment_id, margin); + /* min_rewind == 1 + overshoot_when_doing_minimum_rewind + * overshoot == overshoot_when_doing_larger_rewind + * Initially min_rewind == 1 + overshoot, optimization + * of both values will be done separately. + * overshoot and min_rewind can be negative as both are + * sums of three components: + * any_overshoot == rewind_overshoot - + * stop_overshoot - + * start_overshoot + */ + if (ft_location.segment - target - (min_rewind - 1) < 1) { + short_seek = 1; + } else { + count = ft_location.segment - target - overshoot; + short_seek = (count < 1); + } + if (short_seek) { + count = 1; /* do shortest rewind */ + expected = ft_location.segment - min_rewind; + if (expected/ft_segments_per_track != ft_location.track) { + expected = (ft_location.track * + ft_segments_per_track); + } + } else { + expected = target; + } + fast_seek(count, 1); + logical_forward(); + if (ftape_read_id() < 0 || !ft_location.known || + (sigtestsetmask(¤t->pending.signal, _DONT_BLOCK))) { + if ((!ftape_tape_running && !ft_location.known) || + ++failures > FT_SECTORS_PER_SEGMENT) { + TRACE_ABORT(-EIO, ft_t_err, + "read_id failed completely"); + } + FT_SIGNAL_EXIT(_DONT_BLOCK); + TRACE_CATCH(ftape_report_drive_status(pstatus),); + TRACE(ft_t_noise, "ftape_read_id failed, retry (%d)", + failures); + continue; + } + TRACE(ft_t_noise, "ended at %d/%d (%d,%d,%d)", + ft_location.segment, ft_location.sector, + min_rewind, overshoot, inhibit_correction); + if (!inhibit_correction && + (ft_location.segment < expected || + ft_location.segment > expected + margin)) { + int error = expected - ft_location.segment; + if (short_seek) { + TRACE(ft_t_noise, + "adjusting min_rewind from %d to %d", + min_rewind, min_rewind + error); + min_rewind += error; + if (min_rewind < -5) { + /* is this right ? FIXME ! */ + /* keep sane value */ + min_rewind = -5; + TRACE(ft_t_noise, + "clipped min_rewind to %d", + min_rewind); + } + } else { + TRACE(ft_t_noise, + "adjusting overshoot from %d to %d", + overshoot, overshoot + error); + overshoot += error; + if (overshoot < -5 || + overshoot > OVERSHOOT_LIMIT) { + if (overshoot < 0) { + /* keep sane value */ + overshoot = -5; + } else { + /* keep sane value */ + overshoot = OVERSHOOT_LIMIT; + } + TRACE(ft_t_noise, + "clipped overshoot to %d", + overshoot); + } + } + } + } while (ft_location.segment > segment_id); + if (ft_location.known) { + TRACE(ft_t_noise, "current location: %d/%d", + ft_location.segment, ft_location.sector); + } + TRACE_EXIT 0; +} + +static int determine_position(void) +{ + int retry = 0; + int status; + int result; + TRACE_FUN(ft_t_flow); + + if (!ftape_tape_running) { + /* This should only happen if tape is stopped by isr. + */ + TRACE(ft_t_flow, "waiting for tape stop"); + if (ftape_ready_wait(ftape_timeout.pause, &status) < 0) { + TRACE(ft_t_flow, "drive still running (fatal)"); + ftape_tape_running = 1; /* ? */ + } + } else { + ftape_report_drive_status(&status); + } + if (status & QIC_STATUS_READY) { + /* Drive must be ready to check error state ! + */ + TRACE(ft_t_flow, "drive is ready"); + if (status & QIC_STATUS_ERROR) { + unsigned int error; + qic117_cmd_t command; + + /* Report and clear error state, try to continue. + */ + TRACE(ft_t_flow, "error status set"); + ftape_report_error(&error, &command, 1); + ftape_ready_wait(ftape_timeout.reset, &status); + ftape_tape_running = 0; /* ? */ + } + if (check_bot_eot(status)) { + if (ft_location.bot) { + if ((status & QIC_STATUS_READY) == 0) { + /* tape moving away from + * bot/eot, let's see if we + * can catch up with the first + * segment on this track. + */ + } else { + TRACE(ft_t_flow, + "start tape from logical bot"); + logical_forward(); /* start moving */ + } + } else { + if ((status & QIC_STATUS_READY) == 0) { + TRACE(ft_t_noise, "waiting for logical end of track"); + result = ftape_ready_wait(ftape_timeout.reset, &status); + /* error handling needed ? */ + } else { + TRACE(ft_t_noise, + "tape at logical end of track"); + } + } + } else { + TRACE(ft_t_flow, "start tape"); + logical_forward(); /* start moving */ + ft_location.known = 0; /* not cleared by logical forward ! */ + } + } + /* tape should be moving now, start reading id's + */ + while (!ft_location.known && + retry++ < FT_SECTORS_PER_SEGMENT && + (result = ftape_read_id()) < 0) { + + TRACE(ft_t_flow, "location unknown"); + + /* exit on signal + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + + /* read-id somehow failed, tape may + * have reached end or some other + * error happened. + */ + TRACE(ft_t_flow, "read-id failed"); + TRACE_CATCH(ftape_report_drive_status(&status),); + TRACE(ft_t_err, "ftape_report_drive_status: 0x%02x", status); + if (status & QIC_STATUS_READY) { + ftape_tape_running = 0; + TRACE(ft_t_noise, "tape stopped for unknown reason! " + "status = 0x%02x", status); + if (status & QIC_STATUS_ERROR || + !check_bot_eot(status)) { + /* oops, tape stopped but not at end! + */ + TRACE_EXIT -EIO; + } + } + } + TRACE(ft_t_flow, + "tape is positioned at segment %d", ft_location.segment); + TRACE_EXIT ft_location.known ? 0 : -EIO; +} + +/* Get the tape running and position it just before the + * requested segment. + * Seek tape-track and reposition as needed. + */ +int ftape_start_tape(int segment_id, int sector_offset) +{ + int track = segment_id / ft_segments_per_track; + int result = -EIO; + int status; + static int last_segment = -1; + static int bad_bus_timing = 0; + /* number of segments passing the head between starting the tape + * and being able to access the first sector. + */ + static int start_offset = 1; + int retry; + TRACE_FUN(ft_t_flow); + + /* If sector_offset > 0, seek into wanted segment instead of + * into previous. + * This allows error recovery if a part of the segment is bad + * (erased) causing the tape drive to generate an index pulse + * thus causing a no-data error before the requested sector + * is reached. + */ + ftape_tape_running = 0; + TRACE(ft_t_noise, "target segment: %d/%d%s", segment_id, sector_offset, + ft_buffer[ft_head]->retry > 0 ? " retry" : ""); + if (ft_buffer[ft_head]->retry > 0) { /* this is a retry */ + int dist = segment_id - last_segment; + + if ((int)ft_history.overrun_errors < overrun_count_offset) { + overrun_count_offset = ft_history.overrun_errors; + } else if (dist < 0 || dist > 50) { + overrun_count_offset = ft_history.overrun_errors; + } else if ((ft_history.overrun_errors - + overrun_count_offset) >= 8) { + if (ftape_increase_threshold() >= 0) { + --ft_buffer[ft_head]->retry; + overrun_count_offset = + ft_history.overrun_errors; + TRACE(ft_t_warn, "increased threshold because " + "of excessive overrun errors"); + } else if (!bad_bus_timing && ft_data_rate >= 1000) { + ftape_half_data_rate(); + --ft_buffer[ft_head]->retry; + bad_bus_timing = 1; + overrun_count_offset = + ft_history.overrun_errors; + TRACE(ft_t_warn, "reduced datarate because " + "of excessive overrun errors"); + } + } + } + last_segment = segment_id; + if (ft_location.track != track || + (ftape_might_be_off_track && ft_buffer[ft_head]->retry== 0)) { + /* current track unknown or not equal to destination + */ + ftape_ready_wait(ftape_timeout.seek, &status); + ftape_seek_head_to_track(track); + /* overrun_count_offset = ft_history.overrun_errors; */ + } + result = -EIO; + retry = 0; + while (result < 0 && + retry++ <= 5 && + !ft_failure && + !(sigtestsetmask(¤t->pending.signal, _DONT_BLOCK))) { + + if (retry && start_offset < 5) { + start_offset ++; + } + /* Check if we are able to catch the requested + * segment in time. + */ + if ((ft_location.known || (determine_position() == 0)) && + ft_location.segment >= + (segment_id - + ((ftape_tape_running || ft_location.bot) + ? 0 : start_offset))) { + /* Too far ahead (in or past target segment). + */ + if (ftape_tape_running) { + if ((result = ftape_stop_tape(&status)) < 0) { + TRACE(ft_t_err, + "stop tape failed with code %d", + result); + break; + } + TRACE(ft_t_noise, "tape stopped"); + ftape_tape_running = 0; + } + TRACE(ft_t_noise, "repositioning"); + ++ft_history.rewinds; + if (segment_id % ft_segments_per_track < start_offset){ + TRACE(ft_t_noise, "end of track condition\n" + KERN_INFO "segment_id : %d\n" + KERN_INFO "ft_segments_per_track: %d\n" + KERN_INFO "start_offset : %d", + segment_id, ft_segments_per_track, + start_offset); + + /* If seeking to first segments on + * track better do a complete rewind + * to logical begin of track to get a + * more steady tape motion. + */ + result = ftape_command_wait( + (ft_location.track & 1) + ? QIC_PHYSICAL_FORWARD + : QIC_PHYSICAL_REVERSE, + ftape_timeout.rewind, &status); + check_bot_eot(status); /* update location */ + } else { + result= skip_reverse(segment_id - start_offset, + &status); + } + } + if (!ft_location.known) { + TRACE(ft_t_bug, "panic: location not known"); + result = -EIO; + continue; /* while() will check for failure */ + } + TRACE(ft_t_noise, "current segment: %d/%d", + ft_location.segment, ft_location.sector); + /* We're on the right track somewhere before the + * wanted segment. Start tape movement if needed and + * skip to just before or inside the requested + * segment. Keep tape running. + */ + result = 0; + if (ft_location.segment < + (segment_id - ((ftape_tape_running || ft_location.bot) + ? 0 : start_offset))) { + if (sector_offset > 0) { + result = seek_forward(segment_id, + retry <= 3); + } else { + result = seek_forward(segment_id - 1, + retry <= 3); + } + } + if (result == 0 && + ft_location.segment != + (segment_id - (sector_offset > 0 ? 0 : 1))) { + result = -EIO; + } + } + if (result < 0) { + TRACE(ft_t_err, "failed to reposition"); + } else { + ft_runner_status = running; + } + TRACE_EXIT result; +} |