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