


/*
 *      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: /MkLinux/mklinux/src/drivers/char/ftape/ftape-write.c,v $
 $Author: root $
 *
 $Revision: 1.1.1.1 $
 $Date: 1998/10/02 21:13:20 $
 $State: Exp $
 *
 *      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 <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;
}
