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