/* * Copyright (C) 1993-1995 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: /cvs/work/hardhat2_1_ar7/linux-2.4.17_mvl21/drivers/char/ftape/lowlevel/ftape-write.c,v $ * $Revision: 1.1.1.1 $ * $Date: 2003/06/23 22:18:26 $ * * This file contains the writing code * for the QIC-117 floppy-tape driver for Linux. */ #include #include #include #include #include #include #include "../lowlevel/ftape-tracing.h" #include "../lowlevel/ftape-write.h" #include "../lowlevel/ftape-read.h" #include "../lowlevel/ftape-io.h" #include "../lowlevel/ftape-ctl.h" #include "../lowlevel/ftape-rw.h" #include "../lowlevel/ftape-ecc.h" #include "../lowlevel/ftape-bsm.h" #include "../lowlevel/fdc-isr.h" /* Global vars. */ /* Local vars. */ static int last_write_failed; void ftape_zap_write_buffers(void) { int i; for (i = 0; i < ft_nr_buffers; ++i) { ft_buffer[i]->status = done; } ftape_reset_buffer(); } static int copy_and_gen_ecc(void *destination, const void *source, const SectorMap bad_sector_map) { int result; struct memory_segment mseg; int bads = count_ones(bad_sector_map); TRACE_FUN(ft_t_any); if (bads > 0) { TRACE(ft_t_noise, "bad sectors in map: %d", bads); } if (bads + 3 >= FT_SECTORS_PER_SEGMENT) { TRACE(ft_t_noise, "empty segment"); mseg.blocks = 0; /* skip entire segment */ result = 0; /* nothing written */ } else { mseg.blocks = FT_SECTORS_PER_SEGMENT - bads; mseg.data = destination; memcpy(mseg.data, source, (mseg.blocks - 3) * FT_SECTOR_SIZE); result = ftape_ecc_set_segment_parity(&mseg); if (result < 0) { TRACE(ft_t_err, "ecc_set_segment_parity failed"); } else { result = (mseg.blocks - 3) * FT_SECTOR_SIZE; } } TRACE_EXIT result; } int ftape_start_writing(const ft_write_mode_t mode) { buffer_struct *head = ftape_get_buffer(ft_queue_head); int segment_id = head->segment_id; int result; buffer_state_enum wanted_state = (mode == FT_WR_DELETE ? deleting : writing); TRACE_FUN(ft_t_flow); if ((ft_driver_state != wanted_state) || head->status != waiting) { TRACE_EXIT 0; } ftape_setup_new_segment(head, segment_id, 1); if (mode == FT_WR_SINGLE) { /* stop tape instead of pause */ head->next_segment = 0; } ftape_calc_next_cluster(head); /* prepare */ head->status = ft_driver_state; /* either writing or deleting */ if (ft_runner_status == idle) { TRACE(ft_t_noise, "starting runner for segment %d", segment_id); TRACE_CATCH(ftape_start_tape(segment_id,head->sector_offset),); } else { TRACE(ft_t_noise, "runner not idle, not starting tape"); } /* go */ result = fdc_setup_read_write(head, (mode == FT_WR_DELETE ? FDC_WRITE_DELETED : FDC_WRITE)); ftape_set_state(wanted_state); /* should not be necessary */ TRACE_EXIT result; } /* Wait until all data is actually written to tape. * * There is a problem: when the tape runs into logical EOT, then this * failes. We need to restart the runner in this case. */ int ftape_loop_until_writes_done(void) { buffer_struct *head; TRACE_FUN(ft_t_flow); while ((ft_driver_state == writing || ft_driver_state == deleting) && ftape_get_buffer(ft_queue_head)->status != done) { /* set the runner status to idle if at lEOT */ TRACE_CATCH(ftape_handle_logical_eot(), last_write_failed = 1); /* restart the tape if necessary */ if (ft_runner_status == idle) { TRACE(ft_t_noise, "runner is idle, restarting"); if (ft_driver_state == deleting) { TRACE_CATCH(ftape_start_writing(FT_WR_DELETE), last_write_failed = 1); } else { TRACE_CATCH(ftape_start_writing(FT_WR_MULTI), last_write_failed = 1); } } TRACE(ft_t_noise, "tail: %d, head: %d", ftape_buffer_id(ft_queue_tail), ftape_buffer_id(ft_queue_head)); TRACE_CATCH(fdc_interrupt_wait(5 * FT_SECOND), last_write_failed = 1); head = ftape_get_buffer(ft_queue_head); if (head->status == error) { /* Allow escape from loop when signaled ! */ FT_SIGNAL_EXIT(_DONT_BLOCK); if (head->hard_error_map != 0) { /* Implement hard write error recovery here */ } /* retry this one */ head->status = waiting; if (ft_runner_status == aborting) { ftape_dumb_stop(); } if (ft_runner_status != idle) { TRACE_ABORT(-EIO, ft_t_err, "unexpected state: " "ft_runner_status != idle"); } ftape_start_writing(ft_driver_state == deleting ? FT_WR_MULTI : FT_WR_DELETE); } TRACE(ft_t_noise, "looping until writes done"); } ftape_set_state(idle); TRACE_EXIT 0; } /* Write given segment from buffer at address to tape. */ static int write_segment(const int segment_id, const void *address, const ft_write_mode_t write_mode) { int bytes_written = 0; buffer_struct *tail; buffer_state_enum wanted_state = (write_mode == FT_WR_DELETE ? deleting : writing); TRACE_FUN(ft_t_flow); TRACE(ft_t_noise, "segment_id = %d", segment_id); if (ft_driver_state != wanted_state) { if (ft_driver_state == deleting || wanted_state == deleting) { TRACE_CATCH(ftape_loop_until_writes_done(),); } TRACE(ft_t_noise, "calling ftape_abort_operation"); TRACE_CATCH(ftape_abort_operation(),); ftape_zap_write_buffers(); ftape_set_state(wanted_state); } /* if all buffers full we'll have to wait... */ ftape_wait_segment(wanted_state); tail = ftape_get_buffer(ft_queue_tail); switch(tail->status) { case done: ft_history.defects += count_ones(tail->hard_error_map); break; case waiting: /* this could happen with multiple EMPTY_SEGMENTs, but * shouldn't happen any more as we re-start the runner even * with an empty segment. */ bytes_written = -EAGAIN; break; case error: /* setup for a retry */ tail->status = waiting; bytes_written = -EAGAIN; /* force retry */ if (tail->hard_error_map != 0) { TRACE(ft_t_warn, "warning: %d hard error(s) in written segment", count_ones(tail->hard_error_map)); TRACE(ft_t_noise, "hard_error_map = 0x%08lx", (long)tail->hard_error_map); /* Implement hard write error recovery here */ } break; default: TRACE_ABORT(-EIO, ft_t_err, "wait for empty segment failed, tail status: %d", tail->status); } /* should runner stop ? */ if (ft_runner_status == aborting) { buffer_struct *head = ftape_get_buffer(ft_queue_head); if (head->status == wanted_state) { head->status = done; /* ???? */ } /* don't call abort_operation(), we don't want to zap * the dma buffers */ 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 (tail->status == done) { /* now at least one buffer is empty, fill it with our * data. skip bad sectors and generate ecc. * copy_and_gen_ecc return nr of bytes written, range * 0..29 Kb inclusive! * * Empty segments are handled inside coyp_and_gen_ecc() */ if (write_mode != FT_WR_DELETE) { TRACE_CATCH(bytes_written = copy_and_gen_ecc( tail->address, address, ftape_get_bad_sector_entry(segment_id)),); } tail->segment_id = segment_id; tail->status = waiting; tail = ftape_next_buffer(ft_queue_tail); } /* Start tape only if all buffers full or flush mode. * This will give higher probability of streaming. */ if (ft_runner_status != running && ((tail->status == waiting && ftape_get_buffer(ft_queue_head) == tail) || write_mode != FT_WR_ASYNC)) { TRACE_CATCH(ftape_start_writing(write_mode),); } TRACE_EXIT bytes_written; } /* Write as much as fits from buffer to the given segment on tape * and handle retries. * Return the number of bytes written (>= 0), or: * -EIO write failed * -EINTR interrupted by signal * -ENOSPC device full */ int ftape_write_segment(const int segment_id, const void *buffer, const ft_write_mode_t flush) { int retry = 0; int result; TRACE_FUN(ft_t_flow); ft_history.used |= 2; if (segment_id >= ft_tracks_per_tape*ft_segments_per_track) { /* tape full */ TRACE_ABORT(-ENOSPC, ft_t_err, "invalid segment id: %d (max %d)", segment_id, ft_tracks_per_tape * ft_segments_per_track -1); } for (;;) { if ((result = write_segment(segment_id, buffer, flush)) >= 0) { if (result == 0) { /* empty segment */ TRACE(ft_t_noise, "empty segment, nothing written"); } TRACE_EXIT result; } if (result == -EAGAIN) { if (++retry > 100) { /* give up */ TRACE_ABORT(-EIO, ft_t_err, "write failed, >100 retries in segment"); } TRACE(ft_t_warn, "write error, retry %d (%d)", retry, ftape_get_buffer(ft_queue_tail)->segment_id); } else { TRACE_ABORT(result, ft_t_err, "write_segment failed, error: %d", result); } /* Allow escape from loop when signaled ! */ FT_SIGNAL_EXIT(_DONT_BLOCK); } }