diff options
Diffstat (limited to 'drivers/char/ftape/lowlevel/ftape-read.c')
-rw-r--r-- | drivers/char/ftape/lowlevel/ftape-read.c | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/drivers/char/ftape/lowlevel/ftape-read.c b/drivers/char/ftape/lowlevel/ftape-read.c new file mode 100644 index 00000000000..d967d8cd86d --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-read.c @@ -0,0 +1,621 @@ +/* + * 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-read.c,v $ + * $Revision: 1.6 $ + * $Date: 1997/10/21 14:39:22 $ + * + * This file contains the reading code + * for the QIC-117 floppy-tape driver for Linux. + * + */ + +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/mm.h> + +#include <linux/ftape.h> +#include <linux/qic117.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-read.h" +#include "../lowlevel/ftape-io.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" +#include "../lowlevel/ftape-write.h" +#include "../lowlevel/ftape-ecc.h" +#include "../lowlevel/ftape-bsm.h" + +/* Global vars. + */ + +/* Local vars. + */ + +void ftape_zap_read_buffers(void) +{ + int i; + + for (i = 0; i < ft_nr_buffers; ++i) { +/* changed to "fit" with dynamic allocation of tape_buffer. --khp */ + ft_buffer[i]->status = waiting; + ft_buffer[i]->bytes = 0; + ft_buffer[i]->skip = 0; + ft_buffer[i]->retry = 0; + } +/* ftape_reset_buffer(); */ +} + +static SectorMap convert_sector_map(buffer_struct * buff) +{ + int i = 0; + SectorMap bad_map = ftape_get_bad_sector_entry(buff->segment_id); + SectorMap src_map = buff->soft_error_map | buff->hard_error_map; + SectorMap dst_map = 0; + TRACE_FUN(ft_t_any); + + if (bad_map || src_map) { + TRACE(ft_t_flow, "bad_map = 0x%08lx", (long) bad_map); + TRACE(ft_t_flow, "src_map = 0x%08lx", (long) src_map); + } + while (bad_map) { + while ((bad_map & 1) == 0) { + if (src_map & 1) { + dst_map |= (1 << i); + } + src_map >>= 1; + bad_map >>= 1; + ++i; + } + /* (bad_map & 1) == 1 */ + src_map >>= 1; + bad_map >>= 1; + } + if (src_map) { + dst_map |= (src_map << i); + } + if (dst_map) { + TRACE(ft_t_flow, "dst_map = 0x%08lx", (long) dst_map); + } + TRACE_EXIT dst_map; +} + +static int correct_and_copy_fraction(buffer_struct *buff, __u8 * destination, + int start, int size) +{ + struct memory_segment mseg; + int result; + SectorMap read_bad; + TRACE_FUN(ft_t_any); + + mseg.read_bad = convert_sector_map(buff); + mseg.marked_bad = 0; /* not used... */ + mseg.blocks = buff->bytes / FT_SECTOR_SIZE; + mseg.data = buff->address; + /* If there are no data sectors we can skip this segment. + */ + if (mseg.blocks <= 3) { + TRACE_ABORT(0, ft_t_noise, "empty segment"); + } + read_bad = mseg.read_bad; + ft_history.crc_errors += count_ones(read_bad); + result = ftape_ecc_correct_data(&mseg); + if (read_bad != 0 || mseg.corrected != 0) { + TRACE(ft_t_noise, "crc error map: 0x%08lx", (unsigned long)read_bad); + TRACE(ft_t_noise, "corrected map: 0x%08lx", (unsigned long)mseg.corrected); + ft_history.corrected += count_ones(mseg.corrected); + } + if (result == ECC_CORRECTED || result == ECC_OK) { + if (result == ECC_CORRECTED) { + TRACE(ft_t_info, "ecc corrected segment: %d", buff->segment_id); + } + if(start < 0) { + start= 0; + } + if((start+size) > ((mseg.blocks - 3) * FT_SECTOR_SIZE)) { + size = (mseg.blocks - 3) * FT_SECTOR_SIZE - start; + } + if (size < 0) { + size= 0; + } + if(size > 0) { + memcpy(destination + start, mseg.data + start, size); + } + if ((read_bad ^ mseg.corrected) & mseg.corrected) { + /* sectors corrected without crc errors set */ + ft_history.crc_failures++; + } + TRACE_EXIT size; /* (mseg.blocks - 3) * FT_SECTOR_SIZE; */ + } else { + ft_history.ecc_failures++; + TRACE_ABORT(-EAGAIN, + ft_t_err, "ecc failure on segment %d", + buff->segment_id); + } + TRACE_EXIT 0; +} + +/* Read given segment into buffer at address. + */ +int ftape_read_segment_fraction(const int segment_id, + void *address, + const ft_read_mode_t read_mode, + const int start, + const int size) +{ + int result = 0; + int retry = 0; + int bytes_read = 0; + int read_done = 0; + TRACE_FUN(ft_t_flow); + + ft_history.used |= 1; + TRACE(ft_t_data_flow, "segment_id = %d", segment_id); + if (ft_driver_state != reading) { + TRACE(ft_t_noise, "calling ftape_abort_operation"); + TRACE_CATCH(ftape_abort_operation(),); + ftape_set_state(reading); + } + for(;;) { + buffer_struct *tail; + /* Allow escape from this loop on signal ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + /* Search all full buffers for the first matching the + * wanted segment. Clear other buffers on the fly. + */ + tail = ftape_get_buffer(ft_queue_tail); + while (!read_done && tail->status == done) { + /* Allow escape from this loop on signal ! + */ + FT_SIGNAL_EXIT(_DONT_BLOCK); + if (tail->segment_id == segment_id) { + /* If out buffer is already full, + * return its contents. + */ + TRACE(ft_t_flow, "found segment in cache: %d", + segment_id); + if (tail->deleted) { + /* Return a value that + * read_header_segment + * understands. As this + * should only occur when + * searching for the header + * segments it shouldn't be + * misinterpreted elsewhere. + */ + TRACE_EXIT 0; + } + result = correct_and_copy_fraction( + tail, + address, + start, + size); + TRACE(ft_t_flow, "segment contains (bytes): %d", + result); + if (result < 0) { + if (result != -EAGAIN) { + TRACE_EXIT result; + } + /* keep read_done == 0, will + * trigger + * ftape_abort_operation + * because reading wrong + * segment. + */ + TRACE(ft_t_err, "ecc failed, retry"); + ++retry; + } else { + read_done = 1; + bytes_read = result; + } + } else { + TRACE(ft_t_flow,"zapping segment in cache: %d", + tail->segment_id); + } + tail->status = waiting; + tail = ftape_next_buffer(ft_queue_tail); + } + if (!read_done && tail->status == reading) { + if (tail->segment_id == segment_id) { + switch(ftape_wait_segment(reading)) { + case 0: + break; + case -EINTR: + TRACE_ABORT(-EINTR, ft_t_warn, + "interrupted by " + "non-blockable signal"); + break; + default: + TRACE(ft_t_noise, + "wait_segment failed"); + ftape_abort_operation(); + ftape_set_state(reading); + break; + } + } else { + /* We're reading the wrong segment, + * stop runner. + */ + TRACE(ft_t_noise, "reading wrong segment"); + ftape_abort_operation(); + ftape_set_state(reading); + } + } + /* should runner stop ? + */ + if (ft_runner_status == aborting) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + switch(head->status) { + case error: + ft_history.defects += + count_ones(head->hard_error_map); + case reading: + head->status = waiting; + break; + default: + break; + } + TRACE_CATCH(ftape_dumb_stop(),); + } else { + /* If just passed last segment on tape: wait + * for BOT or EOT mark. Sets ft_runner_status to + * idle if at lEOT and successful + */ + TRACE_CATCH(ftape_handle_logical_eot(),); + } + /* If we got a segment: quit, or else retry up to limit. + * + * If segment to read is empty, do not start runner for it, + * but wait for next read call. + */ + if (read_done || + ftape_get_bad_sector_entry(segment_id) == EMPTY_SEGMENT ) { + /* bytes_read = 0; should still be zero */ + TRACE_EXIT bytes_read; + + } + if (retry > FT_RETRIES_ON_ECC_ERROR) { + ft_history.defects++; + TRACE_ABORT(-ENODATA, ft_t_err, + "too many retries on ecc failure"); + } + /* Now at least one buffer is empty ! + * Restart runner & tape if needed. + */ + TRACE(ft_t_any, "head: %d, tail: %d, ft_runner_status: %d", + ftape_buffer_id(ft_queue_head), + ftape_buffer_id(ft_queue_tail), + ft_runner_status); + TRACE(ft_t_any, "buffer[].status, [head]: %d, [tail]: %d", + ftape_get_buffer(ft_queue_head)->status, + ftape_get_buffer(ft_queue_tail)->status); + tail = ftape_get_buffer(ft_queue_tail); + if (tail->status == waiting) { + buffer_struct *head = ftape_get_buffer(ft_queue_head); + + ftape_setup_new_segment(head, segment_id, -1); + if (read_mode == FT_RD_SINGLE) { + /* disable read-ahead */ + head->next_segment = 0; + } + ftape_calc_next_cluster(head); + if (ft_runner_status == idle) { + result = ftape_start_tape(segment_id, + head->sector_offset); + if (result < 0) { + TRACE_ABORT(result, ft_t_err, "Error: " + "segment %d unreachable", + segment_id); + } + } + head->status = reading; + fdc_setup_read_write(head, FDC_READ); + } + } + /* not reached */ + TRACE_EXIT -EIO; +} + +int ftape_read_header_segment(__u8 *address) +{ + int result; + int header_segment; + int first_failed = 0; + int status; + TRACE_FUN(ft_t_flow); + + ft_used_header_segment = -1; + TRACE_CATCH(ftape_report_drive_status(&status),); + TRACE(ft_t_flow, "reading..."); + /* We're looking for the first header segment. + * A header segment cannot contain bad sectors, therefor at the + * tape start, segments with bad sectors are (according to QIC-40/80) + * written with deleted data marks and must be skipped. + */ + memset(address, '\0', (FT_SECTORS_PER_SEGMENT - 3) * FT_SECTOR_SIZE); + result = 0; +#define HEADER_SEGMENT_BOUNDARY 68 /* why not 42? */ + for (header_segment = 0; + header_segment < HEADER_SEGMENT_BOUNDARY && result == 0; + ++header_segment) { + /* Set no read-ahead, the isr will force read-ahead whenever + * it encounters deleted data ! + */ + result = ftape_read_segment(header_segment, + address, + FT_RD_SINGLE); + if (result < 0 && !first_failed) { + TRACE(ft_t_err, "header segment damaged, trying backup"); + first_failed = 1; + result = 0; /* force read of next (backup) segment */ + } + } + if (result < 0 || header_segment >= HEADER_SEGMENT_BOUNDARY) { + TRACE_ABORT(-EIO, ft_t_err, + "no readable header segment found"); + } + TRACE_CATCH(ftape_abort_operation(),); + ft_used_header_segment = header_segment; + result = ftape_decode_header_segment(address); + TRACE_EXIT result; +} + +int ftape_decode_header_segment(__u8 *address) +{ + unsigned int max_floppy_side; + unsigned int max_floppy_track; + unsigned int max_floppy_sector; + unsigned int new_tape_len; + TRACE_FUN(ft_t_flow); + + if (GET4(address, FT_SIGNATURE) == FT_D2G_MAGIC) { + /* Ditto 2GB header segment. They encrypt the bad sector map. + * We decrypt it and store them in normal format. + * I hope this is correct. + */ + int i; + TRACE(ft_t_warn, + "Found Ditto 2GB tape, " + "trying to decrypt bad sector map"); + for (i=256; i < 29 * FT_SECTOR_SIZE; i++) { + address[i] = ~(address[i] - (i&0xff)); + } + PUT4(address, 0,FT_HSEG_MAGIC); + } else if (GET4(address, FT_SIGNATURE) != FT_HSEG_MAGIC) { + TRACE_ABORT(-EIO, ft_t_err, + "wrong signature in header segment"); + } + ft_format_code = (ft_format_type) address[FT_FMT_CODE]; + if (ft_format_code != fmt_big) { + ft_header_segment_1 = GET2(address, FT_HSEG_1); + ft_header_segment_2 = GET2(address, FT_HSEG_2); + ft_first_data_segment = GET2(address, FT_FRST_SEG); + ft_last_data_segment = GET2(address, FT_LAST_SEG); + } else { + ft_header_segment_1 = GET4(address, FT_6_HSEG_1); + ft_header_segment_2 = GET4(address, FT_6_HSEG_2); + ft_first_data_segment = GET4(address, FT_6_FRST_SEG); + ft_last_data_segment = GET4(address, FT_6_LAST_SEG); + } + TRACE(ft_t_noise, "first data segment: %d", ft_first_data_segment); + TRACE(ft_t_noise, "last data segment: %d", ft_last_data_segment); + TRACE(ft_t_noise, "header segments are %d and %d", + ft_header_segment_1, ft_header_segment_2); + + /* Verify tape parameters... + * QIC-40/80 spec: tape_parameters: + * + * segments-per-track segments_per_track + * tracks-per-cartridge tracks_per_tape + * max-floppy-side (segments_per_track * + * tracks_per_tape - 1) / + * ftape_segments_per_head + * max-floppy-track ftape_segments_per_head / + * ftape_segments_per_cylinder - 1 + * max-floppy-sector ftape_segments_per_cylinder * + * FT_SECTORS_PER_SEGMENT + */ + ft_segments_per_track = GET2(address, FT_SPT); + ft_tracks_per_tape = address[FT_TPC]; + max_floppy_side = address[FT_FHM]; + max_floppy_track = address[FT_FTM]; + max_floppy_sector = address[FT_FSM]; + TRACE(ft_t_noise, "(fmt/spt/tpc/fhm/ftm/fsm) = %d/%d/%d/%d/%d/%d", + ft_format_code, ft_segments_per_track, ft_tracks_per_tape, + max_floppy_side, max_floppy_track, max_floppy_sector); + new_tape_len = ftape_tape_len; + switch (ft_format_code) { + case fmt_425ft: + new_tape_len = 425; + break; + case fmt_normal: + if (ftape_tape_len == 0) { /* otherwise 307 ft */ + new_tape_len = 205; + } + break; + case fmt_1100ft: + new_tape_len = 1100; + break; + case fmt_var:{ + int segments_per_1000_inch = 1; /* non-zero default for switch */ + switch (ft_qic_std) { + case QIC_TAPE_QIC40: + segments_per_1000_inch = 332; + break; + case QIC_TAPE_QIC80: + segments_per_1000_inch = 488; + break; + case QIC_TAPE_QIC3010: + segments_per_1000_inch = 730; + break; + case QIC_TAPE_QIC3020: + segments_per_1000_inch = 1430; + break; + } + new_tape_len = (1000 * ft_segments_per_track + + (segments_per_1000_inch - 1)) / segments_per_1000_inch; + break; + } + case fmt_big:{ + int segments_per_1000_inch = 1; /* non-zero default for switch */ + switch (ft_qic_std) { + case QIC_TAPE_QIC40: + segments_per_1000_inch = 332; + break; + case QIC_TAPE_QIC80: + segments_per_1000_inch = 488; + break; + case QIC_TAPE_QIC3010: + segments_per_1000_inch = 730; + break; + case QIC_TAPE_QIC3020: + segments_per_1000_inch = 1430; + break; + default: + TRACE_ABORT(-EIO, ft_t_bug, + "%x QIC-standard with fmt-code %d, please report", + ft_qic_std, ft_format_code); + } + new_tape_len = ((1000 * ft_segments_per_track + + (segments_per_1000_inch - 1)) / + segments_per_1000_inch); + break; + } + default: + TRACE_ABORT(-EIO, ft_t_err, + "unknown tape format, please report !"); + } + if (new_tape_len != ftape_tape_len) { + ftape_tape_len = new_tape_len; + TRACE(ft_t_info, "calculated tape length is %d ft", + ftape_tape_len); + ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len); + } + if (ft_segments_per_track == 0 && ft_tracks_per_tape == 0 && + max_floppy_side == 0 && max_floppy_track == 0 && + max_floppy_sector == 0) { + /* QIC-40 Rev E and earlier has no values in the header. + */ + ft_segments_per_track = 68; + ft_tracks_per_tape = 20; + max_floppy_side = 1; + max_floppy_track = 169; + max_floppy_sector = 128; + } + /* This test will compensate for the wrong parameter on tapes + * formatted by Conner software. + */ + if (ft_segments_per_track == 150 && + ft_tracks_per_tape == 28 && + max_floppy_side == 7 && + max_floppy_track == 149 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the famous CONNER bug: max_floppy_side off by one !"); + max_floppy_side = 6; + } + /* These tests will compensate for the wrong parameter on tapes + * formatted by ComByte Windows software. + * + * First, for 205 foot tapes + */ + if (ft_segments_per_track == 100 && + ft_tracks_per_tape == 28 && + max_floppy_side == 9 && + max_floppy_track == 149 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!"); + max_floppy_side = 4; + } + /* Next, for 307 foot tapes. */ + if (ft_segments_per_track == 150 && + ft_tracks_per_tape == 28 && + max_floppy_side == 9 && + max_floppy_track == 149 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!"); + max_floppy_side = 6; + } + /* This test will compensate for the wrong parameter on tapes + * formatted by Colorado Windows software. + */ + if (ft_segments_per_track == 150 && + ft_tracks_per_tape == 28 && + max_floppy_side == 6 && + max_floppy_track == 150 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the famous Colorado bug: max_floppy_track off by one !"); + max_floppy_track = 149; + } + ftape_segments_per_head = ((max_floppy_sector/FT_SECTORS_PER_SEGMENT) * + (max_floppy_track + 1)); + /* This test will compensate for some bug reported by Dima + * Brodsky. Seems to be a Colorado bug, either. (freebee + * Imation tape shipped together with Colorado T3000 + */ + if ((ft_format_code == fmt_var || ft_format_code == fmt_big) && + ft_tracks_per_tape == 50 && + max_floppy_side == 54 && + max_floppy_track == 255 && + max_floppy_sector == 128) { +TRACE(ft_t_info, "the famous ??? bug: max_floppy_track off by one !"); + max_floppy_track = 254; + } + /* + * Verify drive_configuration with tape parameters + */ + if (ftape_segments_per_head == 0 || ftape_segments_per_cylinder == 0 || + ((ft_segments_per_track * ft_tracks_per_tape - 1) / ftape_segments_per_head + != max_floppy_side) || + (ftape_segments_per_head / ftape_segments_per_cylinder - 1 != max_floppy_track) || + (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT != max_floppy_sector) +#ifdef TESTING + || ((ft_format_code == fmt_var || ft_format_code == fmt_big) && + (max_floppy_track != 254 || max_floppy_sector != 128)) +#endif + ) { + char segperheadz = ftape_segments_per_head ? ' ' : '?'; + char segpercylz = ftape_segments_per_cylinder ? ' ' : '?'; + TRACE(ft_t_err,"Tape parameters inconsistency, please report"); + TRACE(ft_t_err, "reported = %d/%d/%d/%d/%d/%d", + ft_format_code, + ft_segments_per_track, + ft_tracks_per_tape, + max_floppy_side, + max_floppy_track, + max_floppy_sector); + TRACE(ft_t_err, "required = %d/%d/%d/%d%c/%d%c/%d", + ft_format_code, + ft_segments_per_track, + ft_tracks_per_tape, + ftape_segments_per_head ? + ((ft_segments_per_track * ft_tracks_per_tape -1) / + ftape_segments_per_head ) : + (ft_segments_per_track * ft_tracks_per_tape -1), + segperheadz, + ftape_segments_per_cylinder ? + (ftape_segments_per_head / + ftape_segments_per_cylinder - 1 ) : + ftape_segments_per_head - 1, + segpercylz, + (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT)); + TRACE_EXIT -EIO; + } + ftape_extract_bad_sector_map(address); + TRACE_EXIT 0; +} |