/*
 *      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.

 *
 *      This file contains the code that interfaces the kernel
 *      for the QIC-40/80 floppy-tape driver for Linux.
 */

#include <linux/module.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <asm/segment.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/major.h>
#include <linux/malloc.h>
#include <linux/ftape.h>
#include <asm/dma.h>

#include "tracing.h"
#include "kernel-interface.h"
#include "ftape-read.h"
#include "ftape-write.h"
#include "ftape-io.h"
#include "ftape-ctl.h"
#include "ftape-rw.h"
#include "fdc-io.h"


/*      Global vars.
 */

/* Allocating a 96Kb DMAable buffer in one chunk won't work due to
 * memory fragmentation.  To avoid this, it is broken up into
 * NR_BUFFERS chunks of 32Kbyte. --khp
 */

byte *tape_buffer[NR_BUFFERS] = {NULL};

/*      Local vars.
 */
static int busy_flag = 0;
static int old_sigmask;

static int ftape_open(struct inode *ino, struct file *filep);
static void ftape_close(struct inode *ino, struct file *filep);
static int ftape_ioctl(struct inode *ino, struct file *filep,
		       unsigned int command, unsigned long arg);
static int ftape_read(struct inode *ino, struct file *fp, char *buff,
		      int req_len);
static int ftape_write(struct inode *ino, struct file *fp, const char *buff,
		       int req_len);

static struct file_operations ftape_cdev =
{
	NULL,			/* lseek */
	ftape_read,		/* read */
	ftape_write,		/* write */
	NULL,			/* readdir */
	NULL,			/* select */
	ftape_ioctl,		/* ioctl */
	NULL,			/* mmap */
	ftape_open,		/* open */
	ftape_close,		/* release */
	NULL,			/* fsync */
};

/*
 * DMA'able memory allocation stuff.
 */

/* Pure 2^n version of get_order */
static inline int __get_order(unsigned long size)
{
	int order;

	size = (size-1) >> (PAGE_SHIFT-1);
	order = -1;
	do {
		size >>= 1;
		order++;
	} while (size);
	return order;
}

static inline
void *dmaalloc(int order)
{
	return (void *) __get_dma_pages(GFP_KERNEL, order);
}

static inline
void dmafree(void *addr, int order)
{
	free_pages((unsigned long) addr, order);
}

/*
 * Called by modules package when installing the driver
 * or by kernel during the initialization phase
 */

#ifdef MODULE
#define ftape_init init_module
#endif

int ftape_init(void)
{
	int n;
	int order;
	TRACE_FUN(5, "ftape_init");
#ifdef MODULE
	printk(KERN_INFO "ftape-2.08 960314\n"
	       KERN_INFO " (c) 1993-1995 Bas Laarhoven (bas@vimec.nl)\n"
	       KERN_INFO " (c) 1995-1996 Kai Harrekilde-Petersen (khp@dolphinics.no)\n"
	       KERN_INFO " (c) 1996-1997 Claus Heine (claus@momo.math.rwth-aachen.de)\n"
	KERN_INFO " QIC-117 driver for QIC-40/80/3010/3020 tape drives\n"
	       KERN_INFO " Compiled for kernel version %s"
#ifdef MODVERSIONS
	       " with versioned symbols"
#endif
	       "\n", kernel_version);
#else /* !MODULE */
	/* print a short no-nonsense boot message */
	printk("ftape-2.08 960314 for Linux 1.3.70\n");
#endif				/* MODULE */
	TRACE(3, "installing QIC-117 ftape driver...");
	if (register_chrdev(QIC117_TAPE_MAJOR, "ft", &ftape_cdev)) {
		TRACE(1, "register_chrdev failed");
		TRACE_EXIT;
		return -EIO;
	}
	TRACEx1(3, "ftape_init @ 0x%p", ftape_init);
	/*
	 * Allocate the DMA buffers. They are deallocated at cleanup() time.
	 */
	order = __get_order(BUFF_SIZE);
	for (n = 0; n < NR_BUFFERS; n++) {
		tape_buffer[n] = (byte *) dmaalloc(order);
		if (!tape_buffer[n]) {
			TRACE(1, "dmaalloc() failed");
			for (n = 0; n < NR_BUFFERS; n++) {
				if (tape_buffer[n]) {
					dmafree(tape_buffer[n], order);
					tape_buffer[n] = NULL;
				}
			}
			current->blocked = old_sigmask;		/* restore mask */
			if (unregister_chrdev(QIC117_TAPE_MAJOR, "ft") != 0) {
				TRACE(3, "unregister_chrdev failed");
			}
			TRACE_EXIT;
			return -ENOMEM;
		} else {
			TRACEx2(3, "dma-buffer #%d @ %p", n, tape_buffer[n]);
		}
	}
	busy_flag = 0;
	ftape_unit = -1;
	ftape_failure = 1;	/* inhibit any operation but open */
	udelay_calibrate();	/* must be before fdc_wait_calibrate ! */
	fdc_wait_calibrate();
	TRACE_EXIT;
#ifdef MODULE
	register_symtab(0);	/* remove global ftape symbols */
#endif
	return 0;
}


#ifdef MODULE
/*      Called by modules package when removing the driver
 */
void cleanup_module(void)
{
	int n;
	int order;
	TRACE_FUN(5, "cleanup_module");

	if (unregister_chrdev(QIC117_TAPE_MAJOR, "ft") != 0) {
		TRACE(3, "failed");
	} else {
		TRACE(3, "successful");
	}
	order = __get_order(BUFF_SIZE);
	for (n = 0; n < NR_BUFFERS; n++) {
		if (tape_buffer[n]) {
			dmafree(tape_buffer[n], order);
			tape_buffer[n] = NULL;
			TRACEx1(3, "removed dma-buffer #%d", n);
		} else {
			TRACEx1(1, "dma-buffer #%d == NULL (bug?)", n);
		}
	}
	TRACE_EXIT;
}
#endif				/* MODULE */

/*      Open ftape device
 */
static int ftape_open(struct inode *ino, struct file *filep)
{
	TRACE_FUN(4, "ftape_open");
	int result;
	MOD_INC_USE_COUNT;	/* lock module in memory */

	TRACEi(5, "called for minor", MINOR(ino->i_rdev));
	if (busy_flag) {
		TRACE(1, "failed: already busy");
		MOD_DEC_USE_COUNT;	/* unlock module in memory */
		TRACE_EXIT;
		return -EBUSY;
	}
	if ((MINOR(ino->i_rdev) & ~FTAPE_NO_REWIND) > 3) {
		TRACE(1, "failed: illegal unit nr");
		MOD_DEC_USE_COUNT;	/* unlock module in memory */
		TRACE_EXIT;
		return -ENXIO;
	}
	if (ftape_unit == -1 || FTAPE_UNIT != (MINOR(ino->i_rdev) & 3)) {
		/*  Other selection than last time
		 */
		ftape_init_driver();
	}
	ftape_unit = MINOR(ino->i_rdev);
	ftape_failure = 0;	/* allow tape operations */
	old_sigmask = current->blocked;
	current->blocked = _BLOCK_ALL;
	fdc_save_drive_specs();	/* save Drive Specification regs on i82078-1's */
	result = _ftape_open();
	if (result < 0) {
		TRACE(1, "_ftape_open failed");
		current->blocked = old_sigmask;		/* restore mask */
		MOD_DEC_USE_COUNT;	/* unlock module in memory */
		TRACE_EXIT;
		return result;
	} else {
		busy_flag = 1;
		/*  Mask signals that will disturb proper operation of the
		 *  program that is calling.
		 */
		current->blocked = old_sigmask | _DO_BLOCK;
		TRACE_EXIT;
		return 0;
	}
}

/*      Close ftape device
 */
static void ftape_close(struct inode *ino, struct file *filep)
{
	TRACE_FUN(4, "ftape_close");
	int result;

	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit) {
		TRACE(1, "failed: not busy or wrong unit");
		TRACE_EXIT;
		return;		/* keep busy_flag !(?) */
	}
	current->blocked = _BLOCK_ALL;
	result = _ftape_close();
	if (result < 0) {
		TRACE(1, "_ftape_close failed");
	}
	fdc_restore_drive_specs();	/* restore original values */
	ftape_failure = 1;	/* inhibit any operation but open */
	busy_flag = 0;
	current->blocked = old_sigmask;		/* restore before open state */
	TRACE_EXIT;
	MOD_DEC_USE_COUNT;	/* unlock module in memory */
}

/*      Ioctl for ftape device
 */
static int ftape_ioctl(struct inode *ino, struct file *filep,
		       unsigned int command, unsigned long arg)
{
	TRACE_FUN(4, "ftape_ioctl");
	int result = -EIO;
	int old_sigmask;

	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit || ftape_failure) {
		TRACE(1, "failed: not busy, failure or wrong unit");
		TRACE_EXIT;
		return -EIO;
	}
	old_sigmask = current->blocked;		/* save mask */
	current->blocked = _BLOCK_ALL;
	/* This will work as long as sizeof( void*) == sizeof( long)
	 */
	result = _ftape_ioctl(command, (void *) arg);
	current->blocked = old_sigmask;		/* restore mask */
	TRACE_EXIT;
	return result;
}

/*      Read from tape device
 */
static int ftape_read(struct inode *ino, struct file *fp, char *buff, int req_len)
{
	TRACE_FUN(5, "ftape_read");
	int result = -EIO;
	int old_sigmask;

	TRACEi(5, "called with count:", req_len);
	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit || ftape_failure) {
		TRACE(1, "failed: not busy, failure or wrong unit");
		TRACE_EXIT;
		return -EIO;
	}
	old_sigmask = current->blocked;		/* save mask */
	current->blocked = _BLOCK_ALL;
	result = _ftape_read(buff, req_len);
	TRACEi(7, "return with count:", result);
	current->blocked = old_sigmask;		/* restore mask */
	TRACE_EXIT;
	return result;
}

/*      Write to tape device
 */
static int ftape_write(struct inode *ino, struct file *fp, const char *buff, int req_len)
{
	TRACE_FUN(8, "ftape_write");
	int result = -EIO;
	int old_sigmask;

	TRACEi(5, "called with count:", req_len);
	if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit || ftape_failure) {
		TRACE(1, "failed: not busy, failure or wrong unit");
		TRACE_EXIT;
		return -EIO;
	}
	old_sigmask = current->blocked;		/* save mask */
	current->blocked = _BLOCK_ALL;
	result = _ftape_write(buff, req_len);
	TRACEi(7, "return with count:", result);
	current->blocked = old_sigmask;		/* restore mask */
	TRACE_EXIT;
	return result;
}
