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