patch-1.3.72 linux/drivers/char/ftape/ftape-write.c

Next file: linux/drivers/char/ftape/ftape-write.h
Previous file: linux/drivers/char/ftape/ftape-rw.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.71/linux/drivers/char/ftape/ftape-write.c linux/drivers/char/ftape/ftape-write.c
@@ -0,0 +1,724 @@
+
+
+
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven.
+
+ 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: /home/bas/distr/ftape-2.03b/RCS/ftape-write.c,v $
+ $Author: bas $
+ *
+ $Revision: 1.26 $
+ $Date: 1995/05/27 08:55:27 $
+ $State: Beta $
+ *
+ *      This file contains the writing code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/ftape.h>
+#include <asm/segment.h>
+
+#include "tracing.h"
+#include "ftape-write.h"
+#include "ftape-read.h"
+#include "qic117.h"
+#include "ftape-io.h"
+#include "ftape-ctl.h"
+#include "ftape-rw.h"
+#include "ftape-eof.h"
+#include "ecc.h"
+#include "ftape-bsm.h"
+
+
+/*      Global vars.
+ */
+
+/*      Local vars.
+ */
+static int buf_pos_wr = 0;
+static int last_write_failed = 0;
+static int need_flush = 0;
+
+#define WRITE_MULTI  0
+#define WRITE_SINGLE 1
+
+void ftape_zap_write_buffers(void)
+{
+	int i;
+
+	for (i = 0; i < NR_BUFFERS; ++i) {
+		buffer[i].status = done;
+	}
+	need_flush = 0;
+}
+
+int copy_and_gen_ecc(char *destination, byte * source,
+		     unsigned int bad_sector_map)
+{
+	TRACE_FUN(8, "copy_and_gen_ecc");
+	int result;
+	struct memory_segment mseg;
+	int bads = count_ones(bad_sector_map);
+
+	if (bads > 0) {
+		TRACEi(4, "bad sectors in map:", bads);
+	}
+	if (bads + 3 >= SECTORS_PER_SEGMENT) {
+		TRACE(4, "empty segment");
+		mseg.blocks = 0;	/* skip entire segment */
+		result = 0;	/* nothing written */
+	} else {
+		mseg.blocks = SECTORS_PER_SEGMENT - bads;
+		mseg.data = destination;
+		memcpy(mseg.data, source, (mseg.blocks - 3) * SECTOR_SIZE);
+		result = ecc_set_segment_parity(&mseg);
+		if (result < 0) {
+			TRACE(1, "ecc_set_segment_parity failed");
+		} else {
+			result = (mseg.blocks - 3) * SECTOR_SIZE;
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+void prevent_flush(void)
+{
+	need_flush = 0;
+	ftape_state = idle;
+}
+
+int start_writing(int mode)
+{
+	TRACE_FUN(5, "start_writing");
+	int result = 0;
+	buffer_struct *buff = &buffer[head];
+	int segment_id = buff->segment_id;
+
+	if (ftape_state == writing && buff->status == waiting) {
+		setup_new_segment(buff, segment_id, 1);
+		if (mode == WRITE_SINGLE) {
+			buffer[head].next_segment = 0;	/* stop tape instead of pause */
+		}
+		calc_next_cluster(buff);	/* prepare */
+		buff->status = writing;
+		if (runner_status == idle) {
+			TRACEi(5, "starting runner for segment", segment_id);
+			result = ftape_start_tape(segment_id, buff->sector_offset);
+			if (result >= 0) {
+				runner_status = running;
+			}
+		}
+		if (result >= 0) {
+			result = setup_fdc_and_dma(buff, FDC_WRITE);	/* go */
+		}
+		ftape_state = writing;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int loop_until_writes_done(void)
+{
+	TRACE_FUN(5, "loop_until_writes_done");
+	int i;
+	int result = 0;
+
+	/*
+	 *  Wait until all data is actually written to tape.
+	 */
+	while (ftape_state == writing && buffer[head].status != done) {
+		TRACEx2(7, "tail: %d, head: %d", tail, head);
+		for (i = 0; i < NR_BUFFERS; ++i) {
+			TRACEx3(8, "buffer[ %d] segment_id: %d, status: %d",
+			      i, buffer[i].segment_id, buffer[i].status);
+		}
+		result = fdc_interrupt_wait(5 * SECOND);
+		if (result < 0) {
+			TRACE(1, "fdc_interrupt_wait failed");
+			last_write_failed = 1;
+			break;
+		}
+		if (buffer[head].status == error) {
+			/* Allow escape from loop when signaled !
+			 */
+			if (current->signal & _DONT_BLOCK) {
+				TRACE(2, "interrupted by signal");
+				TRACE_EXIT;
+				result = -EINTR;	/* is this the right return value ? */
+				break;
+			}
+			if (buffer[head].hard_error_map != 0) {
+				/*  Implement hard write error recovery here
+				 */
+			}
+			buffer[head].status = waiting;	/* retry this one */
+			if (runner_status == aborting) {
+				ftape_dumb_stop();
+				runner_status = idle;
+			}
+			if (runner_status != idle) {
+				TRACE(1, "unexpected state: runner_status != idle");
+				result = -EIO;
+				break;
+			}
+			start_writing(WRITE_MULTI);
+		}
+		TRACE(5, "looping until writes done");
+		result = 0;	/* normal exit status */
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+/*      Write given segment from buffer at address onto tape.
+ */
+int write_segment(unsigned segment_id, byte * address, int flushing)
+{
+	TRACE_FUN(5, "write_segment");
+	int result = 0;
+	int bytes_written = 0;
+
+	TRACEi(5, "segment_id =", segment_id);
+	if (ftape_state != writing) {
+		if (ftape_state == reading) {
+			TRACE(5, "calling ftape_abort_operation");
+			result = ftape_abort_operation();
+			if (result < 0) {
+				TRACE(1, "ftape_abort_operation failed");
+			}
+		}
+		ftape_zap_read_buffers();
+		ftape_zap_write_buffers();
+		ftape_state = writing;
+	}
+	/*    if all buffers full we'll have to wait...
+	 */
+	wait_segment(writing);
+	if (buffer[tail].status == error) {
+		/*  setup for a retry
+		 */
+		buffer[tail].status = waiting;
+		bytes_written = -EAGAIN;	/* force retry */
+		if (buffer[tail].hard_error_map != 0) {
+			TRACEx1(1, "warning: %d hard error(s) in written segment",
+				count_ones(buffer[tail].hard_error_map));
+			TRACEx1(4, "hard_error_map = 0x%08lx", buffer[tail].hard_error_map);
+			/*  Implement hard write error recovery here
+			 */
+		}
+	} else if (buffer[tail].status == done) {
+		history.defects += count_ones(buffer[tail].hard_error_map);
+	} else {
+		TRACE(1, "wait for empty segment failed");
+		result = -EIO;
+	}
+	/*    If just passed last segment on tape: wait for BOT or EOT mark.
+	 */
+	if (result >= 0 && runner_status == logical_eot) {
+		int status;
+
+		result = ftape_ready_wait(timeout.seek, &status);
+		if (result < 0 || (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) {
+			TRACE(1, "eot/bot not reached");
+		} else {
+			runner_status = end_of_tape;
+		}
+	}
+	/*    should runner stop ?
+	 */
+	if (result >= 0 &&
+	(runner_status == aborting || runner_status == buffer_underrun ||
+	 runner_status == end_of_tape)) {
+		if (runner_status != end_of_tape) {
+			result = ftape_dumb_stop();
+		}
+		if (result >= 0) {
+			if (runner_status == aborting) {
+				if (buffer[head].status == writing) {
+					buffer[head].status = done;	/* ????? */
+				}
+			}
+			runner_status = idle;	/* aborted ? */
+		}
+	}
+	/*  Don't start tape if runner idle and segment empty.
+	 */
+	if (result >= 0 && !(runner_status == idle &&
+		    get_bad_sector_entry(segment_id) == EMPTY_SEGMENT)) {
+		if (buffer[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 !
+			 */
+			result = copy_and_gen_ecc(buffer[tail].address, address,
+				       get_bad_sector_entry(segment_id));
+			if (result >= 0) {
+				bytes_written = result;
+				buffer[tail].segment_id = segment_id;
+				buffer[tail].status = waiting;
+				next_buffer(&tail);
+			}
+		}
+		/*    Start tape only if all buffers full or flush mode.
+		 *    This will give higher probability of streaming.
+		 */
+		if (result >= 0 && runner_status != running &&
+		    ((head == tail && buffer[tail].status == waiting) || flushing)) {
+			result = start_writing(WRITE_MULTI);
+		}
+	}
+	TRACE_EXIT;
+	return (result < 0) ? result : 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 _write_segment(unsigned int segment_id, byte * buffer, int flush)
+{
+	TRACE_FUN(5, "_write_segment");
+	int retry = 0;
+	int result;
+
+	history.used |= 2;
+	for (;;) {
+		if (segment_id > ftape_last_segment.id && !flush) {
+			result = -ENOSPC;	/* tape full */
+			break;
+		}
+		result = write_segment(segment_id, buffer, flush);
+		if (result < 0) {
+			if (result == -EAGAIN) {
+				if (++retry > 100) {
+					TRACE(1, "write failed, >100 retries in segment");
+					result = -EIO;	/* give up */
+					break;
+				} else {
+					TRACEx1(2, "write error, retry %d", retry);
+				}
+			} else {
+				TRACEi(1, "write_segment failed, error:", -result);
+				break;
+			}
+		} else {	/* success */
+			if (result == 0) {	/* empty segment */
+				TRACE(4, "empty segment, nothing written");
+			}
+			break;
+		}
+		/* Allow escape from loop when signaled !
+		 */
+		if (current->signal & _DONT_BLOCK) {
+			TRACE(2, "interrupted by signal");
+			TRACE_EXIT;
+			result = -EINTR;	/* is this the right return value ? */
+			break;
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int update_header_segment(unsigned segment, byte * buffer)
+{
+	TRACE_FUN(5, "update_header_segment");
+	int result = 0;
+	int status;
+
+	if (buffer == NULL) {
+		TRACE(5, "no input buffer specified");
+		buffer = deblock_buffer;
+		result = read_segment(used_header_segment, buffer, &status, 0);
+		if (bad_sector_map_changed) {
+			store_bad_sector_map(buffer);
+		}
+		if (failed_sector_log_changed) {
+			update_failed_sector_log(buffer);
+		}
+	}
+	if (result >= 0 && GET4(buffer, 0) != 0xaa55aa55) {
+		TRACE(1, "wrong header signature found, aborting");
+		result = -EIO;
+	}
+	if (result >= 0) {
+		result = _write_segment(segment, buffer, 0);
+		if (result >= 0 && runner_status == idle) {
+			/*  Force flush for single segment instead of relying on
+			 *  flush in read_segment for multiple segments.
+			 */
+			result = start_writing(WRITE_SINGLE);
+			if (result >= 0 && ftape_state == writing) {
+				result = loop_until_writes_done();
+				prevent_flush();
+			}
+		}
+#ifdef VERIFY_HEADERS
+		if (result >= 0) {	/* read back and verify */
+			result = read_segment(segment, scratch_buffer, &status, 0);
+			/*  Should retry if soft error during read !
+			 *  TO BE IMPLEMENTED
+			 */
+			if (result >= 0) {
+				if (memcmp(buffer, scratch_buffer, sizeof(buffer)) == 0) {
+					result = 0;	/* verified */
+					TRACE(5, "verified");
+				} else {
+					result = -EIO;	/* verify failed */
+					TRACE(5, "verify failed");
+				}
+			}
+		}
+#endif
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_write_header_segments(byte * buffer)
+{
+	TRACE_FUN(5, "ftape_write_header_segments");
+	int result = 0;
+	int retry = 0;
+	int header_1_ok = 0;
+	int header_2_ok = 0;
+
+	do {
+		if (!header_1_ok) {
+			result = update_header_segment(header_segment_1, buffer);
+			if (result < 0) {
+				continue;
+			}
+			header_1_ok = 1;
+		}
+		if (!header_2_ok) {
+			result = update_header_segment(header_segment_2, buffer);
+			if (result < 0) {
+				continue;
+			}
+			header_2_ok = 1;
+		}
+	} while (result < 0 && retry++ < 3);
+	if (result < 0) {
+		if (!header_1_ok) {
+			TRACE(1, "update of first header segment failed");
+		}
+		if (!header_2_ok) {
+			TRACE(1, "update of second header segment failed");
+		}
+		result = -EIO;
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_update_header_segments(byte * buffer, int update)
+{
+	TRACE_FUN(5, "ftape_update_header_segments");
+	int result = 0;
+	int dummy;
+	int header_changed = 1;
+
+	if (ftape_state == writing) {
+		result = loop_until_writes_done();
+	}
+	if (read_only) {
+		result = 0;	/* exit and fake success */
+		TRACE(4, "Tape set read-only: no update");
+	} else if (result >= 0) {
+		result = ftape_abort_operation();
+		if (result >= 0) {
+			if (buffer == NULL) {
+				if (bad_sector_map_changed || failed_sector_log_changed) {
+					ftape_seek_to_bot();	/* prevents extra rewind */
+					buffer = deblock_buffer;
+					result = read_segment(used_header_segment, buffer, &dummy, 0);
+					if (result < 0) {
+						TRACE_EXIT;
+						return result;
+					}
+				}
+				header_changed = 0;
+			}
+			if (update) {
+				if (bad_sector_map_changed) {
+					store_bad_sector_map(buffer);
+					header_changed = 1;
+				}
+				if (failed_sector_log_changed) {
+					update_failed_sector_log(buffer);
+					header_changed = 1;
+				}
+			}
+			if (header_changed) {
+				ftape_seek_to_bot();	/* prevents extra rewind */
+				result = ftape_write_header_segments(buffer);
+			}
+		}
+	}
+	TRACE_EXIT;
+	return result;
+}
+
+int ftape_flush_buffers(void)
+{
+	TRACE_FUN(5, "ftape_flush_buffers");
+	int result;
+	int pad_count;
+	int data_remaining;
+	static int active = 0;
+
+	if (active) {
+		TRACE(5, "nested call, abort");
+		TRACE_EXIT;
+		return 0;
+	}
+	active = 1;
+	TRACEi(5, "entered, ftape_state =", ftape_state);
+	if (ftape_state != writing && !need_flush) {
+		active = 0;
+		TRACE(5, "no need for flush");
+		TRACE_EXIT;
+		return 0;
+	}
+	data_remaining = buf_pos_wr;
+	buf_pos_wr = 0;		/* prevent further writes if this fails */
+	TRACE(5, "flushing write buffers");
+	if (last_write_failed) {
+		ftape_zap_write_buffers();
+		active = 0;
+		TRACE_EXIT;
+		return write_protected ? -EROFS : -EIO;
+	}
+	/*
+	 *    If there is any data not written to tape yet, append zero's
+	 *    up to the end of the sector. Then write the segment(s) to tape.
+	 */
+	if (data_remaining > 0) {
+		int written;
+
+		do {
+			TRACEi(4, "remaining in buffer:", data_remaining);
+			pad_count = sizeof(deblock_buffer) - data_remaining;
+			TRACEi(7, "flush, padding count:", pad_count);
+			memset(deblock_buffer + data_remaining, 0, pad_count);	/* pad buffer */
+			result = _write_segment(ftape_seg_pos, deblock_buffer, 1);
+			if (result < 0) {
+				if (result != -ENOSPC) {
+					last_write_failed = 1;
+				}
+				active = 0;
+				TRACE_EXIT;
+				return result;
+			}
+			written = result;
+			clear_eof_mark_if_set(ftape_seg_pos, written);
+			TRACEi(7, "flush, moved out buffer:", written);
+			if (written > 0) {
+				data_remaining -= written;
+				if (data_remaining > 0) {
+					/*  Need another segment for remaining data, move the remainder
+					 *  to the beginning of the buffer
+					 */
+					memmove(deblock_buffer, deblock_buffer + written, data_remaining);
+				}
+			}
+			++ftape_seg_pos;
+		} while (data_remaining > 0);
+		/*  Data written to last segment == data_remaining + written
+		 *  value is in range [1..29K].
+		 */
+		TRACEx2(4, "last write: %d, netto pad-count: %d",
+			data_remaining + written, -data_remaining);
+		if (-1024 < data_remaining && data_remaining <= 0) {
+			/*  Last sector of segment was used for data, so put eof mark
+			 *  in next segment and position at second file mark.
+			 */
+			if (ftape_weof(2, ftape_seg_pos, 1) >= 0) {
+				++ftape_seg_pos;	/* position between file marks */
+			}
+		} else {
+			/*  Put eof mark in previous segment after data and position
+			 *  at second file mark.
+			 */
+			ftape_weof(2, ftape_seg_pos - 1, 1 +
+				   ((SECTOR_SIZE - 1 + result + data_remaining) / SECTOR_SIZE));
+		}
+	} else {
+		TRACE(7, "deblock_buffer empty");
+		if (ftape_weof(2, ftape_seg_pos, 1) >= 0) {
+			++ftape_seg_pos;	/* position between file marks */
+		}
+		start_writing(WRITE_MULTI);
+	}
+	TRACE(7, "waiting");
+	result = loop_until_writes_done();
+	if (result < 0) {
+		TRACE(1, "flush buffers failed");
+	}
+	ftape_state = idle;
+	last_write_failed = 0;
+	need_flush = 0;
+	active = 0;
+	TRACE_EXIT;
+	return result;
+}
+
+int _ftape_write(const char *buff, int req_len)
+{
+	TRACE_FUN(5, "_ftape_write");
+	int result = 0;
+	int cnt;
+	int written = 0;
+
+	if (write_protected) {
+		TRACE(1, "error: cartridge write protected");
+		last_write_failed = 1;
+		result = -EROFS;
+	} else if (ftape_offline || !formatted || no_tape) {
+		result = -EIO;
+	} else if (first_data_segment == -1) {
+		/*
+		 *    If we haven't read the header segment yet, do it now.
+		 *    This will verify the configuration, get the eof markers
+		 *    and the bad sector table.
+		 *    We'll use the deblock buffer for scratch.
+		 */
+		result = read_header_segment(deblock_buffer);
+		if (result >= 0 && ftape_seg_pos > ftape_last_segment.id) {
+			result = -ENOSPC;	/* full is full */
+		}
+	}
+	if (result < 0) {
+		TRACE_EXIT;
+		return result;
+	}
+	/*
+	 *    This part writes data blocks to tape until the
+	 *    requested amount is written.
+	 *    The data will go in a buffer until it's enough
+	 *    for a segment without bad sectors. Then we'll write
+	 *    that segment to tape.
+	 *    The bytes written will be removed from the buffer
+	 *    and the process is repeated until there is less
+	 *    than one segment to write left in the buffer.
+	 */
+	while (req_len > 0) {
+		int space_left = sizeof(deblock_buffer) - buf_pos_wr;
+
+		TRACEi(7, "remaining req_len:", req_len);
+		TRACEi(7, "          buf_pos:", buf_pos_wr);
+		cnt = (req_len < space_left) ? req_len : space_left;
+		if (cnt > 0) {
+			result = verify_area(VERIFY_READ, buff, cnt);
+			if (result) {
+				TRACE(1, "verify_area failed");
+				last_write_failed = 1;
+				TRACE_EXIT;
+				return result;
+			}
+			memcpy_fromfs(deblock_buffer + buf_pos_wr, buff, cnt);
+			buff += cnt;
+			req_len -= cnt;
+			buf_pos_wr += cnt;
+		}
+		TRACEi(7, "moved into blocking buffer:", cnt);
+		while (buf_pos_wr >= sizeof(deblock_buffer)) {
+			/*  If this is the last buffer to be written, let flush handle it.
+			 */
+			if (ftape_seg_pos >= ftape_last_segment.id) {
+				TRACEi(7, "remaining in blocking buffer:", buf_pos_wr);
+				TRACEi(7, "just written bytes:", written + cnt);
+				TRACE_EXIT;
+				return written + cnt;
+			}
+			/* Got one full buffer, write it to disk
+			 */
+			result = _write_segment(ftape_seg_pos, deblock_buffer, 0);
+			TRACEi(5, "_write_segment result =", result);
+			if (result < 0) {
+				if (result == -EAGAIN) {
+					TRACE(5, "retry...");
+					continue;	/* failed, retry same segment */
+				}
+				last_write_failed = 1;
+				TRACE_EXIT;
+				return result;
+			} else {
+				clear_eof_mark_if_set(ftape_seg_pos, result);
+			}
+			if (result > 0 && result < buf_pos_wr) {
+				/* Partial write: move remainder in lower part of buffer
+				 */
+				memmove(deblock_buffer, deblock_buffer + result, buf_pos_wr - result);
+			}
+			TRACEi(7, "moved out of blocking buffer:", result);
+			buf_pos_wr -= result;	/* remainder */
+			++ftape_seg_pos;
+			/* Allow us to escape from this loop with a signal !
+			 */
+			if (current->signal & _DONT_BLOCK) {
+				TRACE(2, "interrupted by signal");
+				last_write_failed = 1;
+				TRACE_EXIT;
+				return -EINTR;	/* is this the right return value ? */
+			}
+		}
+		written += cnt;
+	}
+	TRACEi(7, "remaining in blocking buffer:", buf_pos_wr);
+	TRACEi(7, "just written bytes:", written);
+	last_write_failed = 0;
+	if (!need_flush && written > 0) {
+		need_flush = 1;
+	}
+	TRACE_EXIT;
+	return written;		/* bytes written */
+}
+
+int ftape_fix(void)
+{
+	TRACE_FUN(5, "ftape_fix");
+	int result = 0;
+	int dummy;
+	int status;
+
+	if (write_protected) {
+		result = -EROFS;
+	} else {
+		/*  This will copy header segment 2 to header segment 1
+		 *  Spares us a tape format operation if header 2 is still good.
+		 */
+		header_segment_1 = 0;
+		header_segment_2 = 1;
+		first_data_segment = 2;
+		result = read_segment(header_segment_2, scratch_buffer, &dummy, 0);
+		result = ftape_ready_wait(timeout.pause, &status);
+		result = ftape_write_header_segments(scratch_buffer);
+	}
+	TRACE_EXIT;
+	return result;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this