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