/* * 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 #include #include #include #include #include #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 ) { 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/%d/%d", ft_format_code, ft_segments_per_track, ft_tracks_per_tape, ((ft_segments_per_track * ft_tracks_per_tape -1) / ftape_segments_per_head ), (ftape_segments_per_head / ftape_segments_per_cylinder - 1 ), (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT)); TRACE_EXIT -EIO; } ftape_extract_bad_sector_map(address); TRACE_EXIT 0; }