diff options
Diffstat (limited to 'drivers/char/ftape/lowlevel/ftape-write.c')
-rw-r--r-- | drivers/char/ftape/lowlevel/ftape-write.c | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/drivers/char/ftape/lowlevel/ftape-write.c b/drivers/char/ftape/lowlevel/ftape-write.c new file mode 100644 index 000000000000..45601ec801ee --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-write.c @@ -0,0 +1,336 @@ +/* + * 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: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-write.c,v $ + * $Revision: 1.3.4.1 $ + * $Date: 1997/11/14 18:07:04 $ + * + * This file contains the writing 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-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); + } +} |