/*
 *      sr.c by David Giller
 *
 *      adapted from:
 *	sd.c Copyright (C) 1992 Drew Eckhardt 
 *	Linux scsi disk driver by
 *		Drew Eckhardt 
 *
 *	<drew@colorado.edu>
 */

#include <linux/config.h>

#ifdef CONFIG_BLK_DEV_SR

#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>

#include "scsi.h"
#include "sr.h"

#define MAJOR_NR 11

#include "../blk.h"

#define MAX_RETRIES 0
#define SR_TIMEOUT 200

int NR_SR=0;
Scsi_CD scsi_CDs[MAX_SR];
static int sr_sizes[MAX_SR << 4];
static unsigned long int this_count;

struct block_buffer
	{
	unsigned	block;
	unsigned	start;
	unsigned	use:1;
	unsigned char	buffer[2048];
	};

static struct block_buffer bb[MAX_SR];

static int sr_result;

static int sr_open(struct inode *, struct file *);

extern int sr_ioctl(struct inode *, struct file *, unsigned int, unsigned int);

static void sr_release(struct inode * inode, struct file * file)
{
	sync_dev(inode->i_rdev);
}

static struct file_operations sr_fops = 
{
	NULL,			/* lseek - default */
	block_read,		/* read - general block-dev read */
	block_write,		/* write - general block-dev write */
	NULL,			/* readdir - bad */
	NULL,			/* select */
	sr_ioctl,		/* ioctl */
	sr_open,       		/* no special open code */
	sr_release		/* release */
};

/*
 * The sense_buffer is where we put data for all mode sense commands performed.
 */

static unsigned char sense_buffer[255];

/*
 * rw_intr is the interrupt routine for the device driver.  It will be notified on the 
 * end of a SCSI read / write, and will take on of several actions based on success or failure.
 */

static void rw_intr (int host, int result)
{
  	if (SR_HOST != host)		
		{
		panic ("sr.o : rw_intr() recieving interrupt for different host.");
		}

	if (!result)
		{ /* No error */
	        if (bb[DEVICE_NR(CURRENT->dev)].use)
		        {
			memcpy((char *)CURRENT->buffer, 
			       bb[DEVICE_NR(CURRENT->dev)].buffer + 
			       (bb[DEVICE_NR(CURRENT->dev)].start << 9), 
			       this_count << 9);
		        }

		CURRENT->nr_sectors -= this_count;

#ifdef DEBUG
		printk("(%x %x %x) ",CURRENT->bh, CURRENT->nr_sectors, 
		       this_count);
#endif
		if (CURRENT->nr_sectors)
			{	 
			CURRENT->sector += this_count;
			CURRENT->errors = 0;
			if (!CURRENT->bh)
			  {
			    (char *) CURRENT->buffer += this_count << 9;
			  } else {
			    end_request(1);
			    do_sr_request();
			  }
			}
		else 
			{
			end_request(1);  /* All done */
			do_sr_request();
			} 
		}

	/* We only come through here if we have an error of some kind */

	if (driver_byte(result) != 0) {
		bb[DEVICE_NR(CURRENT->dev)].block = -1;
		
		if ((sense_buffer[0] & 0x7f) == 0x70) {
			if ((sense_buffer[2] & 0xf) == UNIT_ATTENTION) {
				/* detected disc change.  set a bit and quietly refuse	*/
				/* further access.					*/
		    
				scsi_CDs[DEVICE_NR(CURRENT->dev)].changed = 1;
				end_request(0);
				return;
			}
		}
	    
		if (sense_buffer[2] == ILLEGAL_REQUEST) {
			printk("CD-ROM error: Drive reports ILLEGAL REQUEST.\n");
			if (scsi_CDs[DEVICE_NR(CURRENT->dev)].ten) {
				scsi_CDs[DEVICE_NR(CURRENT->dev)].ten = 0;
				do_sr_request();
				result = 0;
				return;
			} else {
				end_request(0);
				do_sr_request(); /* Do next request */
				return;
			}

		}

		if (sense_buffer[2] == NOT_READY) {
			printk("CDROM not ready.  Make sure you have a disc in the drive.\n");
			end_request(0);
			do_sr_request(); /* Do next request */
			return;
		};
	      }
	
	/* We only get this far if we have an error we have not recognized */
	if(result) {
	  printk("SCSI CD error : host %d id %d lun %d return code = %03x\n", 
		 scsi_CDs[DEVICE_NR(CURRENT->dev)].device->host_no, 
		 scsi_CDs[DEVICE_NR(CURRENT->dev)].device->id,
		 scsi_CDs[DEVICE_NR(CURRENT->dev)].device->lun,
		 result);
	    
	if (status_byte(result) == CHECK_CONDITION)
	    printk("\tSense class %x, sense error %x, extended sense %x\n",
		 sense_class(sense_buffer[0]), 
		 sense_error(sense_buffer[0]),
		 sense_buffer[2] & 0xf);
	
	end_request(0);
	do_sr_request();
	}
}

static int sr_open(struct inode * inode, struct file * filp)
{
	if (filp->f_mode)
		check_disk_change(inode->i_rdev);
	return 0;
}

/*
 * do_sr_request() is the request handler function for the sr driver.  Its function in life 
 * is to take block device requests, and translate them to SCSI commands.
 */
	
void do_sr_request (void)
{
	unsigned int dev, block, realcount;
	unsigned char cmd[10], *buffer, tries;

	tries = 2;

      repeat:
	INIT_REQUEST;
	dev =  MINOR(CURRENT->dev);
	block = CURRENT->sector;	

	bb[dev].start = block % 4;
	block = block / 4;

	if (dev >= NR_SR)
		{
		/* printk("CD-ROM request error: invalid device.\n");			*/
		end_request(0);
		tries = 2;
		goto repeat;
		}

	if (!scsi_CDs[dev].use)
		{
		/* printk("CD-ROM request error: device marked not in use.\n");		*/
		end_request(0);
		tries = 2;
		goto repeat;
		}

	if (scsi_CDs[dev].changed)
	        {
/* 
 * quietly refuse to do anything to a changed disc until the changed bit has been reset
 */
		/* printk("CD-ROM has been changed.  Prohibiting further I/O.\n");	*/
		end_request(0);
		tries = 2;
		goto repeat;
		}
	
	if (!CURRENT->bh)  	
		this_count = CURRENT->nr_sectors;
	else
		this_count = (CURRENT->bh->b_size / 512);

	if (bb[dev].start)
	        {				  
		bb[dev].use = 1;

	        this_count = ((this_count > 4 - bb[dev].start) ? 
			      (4 - bb[dev].start) : (this_count));

		if (bb[dev].block == block)
			{
			  rw_intr(SR_HOST, 0);
			  return;
			}

		buffer = bb[dev].buffer;
		bb[dev].block = block;
	        } 
	else if (this_count < 4)
	        {
		bb[dev].use = 1;

		if (bb[dev].block == block)
			{
			  rw_intr(SR_HOST, 0);
			  return;
			}

		buffer = bb[dev].buffer;
		bb[dev].block = block;
	        }
	else
	        {
		this_count -= this_count % 4;
		buffer = CURRENT->buffer;
		bb[dev].use = 0;
	        }

	realcount = (this_count + 3) / 4;

	switch (CURRENT->cmd)
		{
		case WRITE: 		
			end_request(0);
			goto repeat;
			break;
		case READ : 
		        cmd[0] = READ_6;
			break;
		default : 
			printk ("Unknown sr command %d\r\n", CURRENT->cmd);
			panic("");
		}
	
	cmd[1] = (SR_LUN << 5) & 0xe0;

	if (((realcount > 0xff) || (block > 0x1fffff)) && scsi_CDs[dev].ten) 
		{
		if (realcount > 0xffff)
		        {
			realcount = 0xffff;
			this_count = realcount * 4;
			}

		cmd[0] += READ_10 - READ_6 ;
		cmd[2] = (unsigned char) (block >> 24) & 0xff;
		cmd[3] = (unsigned char) (block >> 16) & 0xff;
		cmd[4] = (unsigned char) (block >> 8) & 0xff;
		cmd[5] = (unsigned char) block & 0xff;
		cmd[6] = cmd[9] = 0;
		cmd[7] = (unsigned char) (realcount >> 8) & 0xff;
		cmd[8] = (unsigned char) realcount & 0xff;
		}
	else
		{
		if (realcount > 0xff)
		        {
			realcount = 0xff;
			this_count = realcount * 4;
		        }
	
		cmd[1] |= (unsigned char) ((block >> 16) & 0x1f);
		cmd[2] = (unsigned char) ((block >> 8) & 0xff);
		cmd[3] = (unsigned char) block & 0xff;
		cmd[4] = (unsigned char) realcount;
		cmd[5] = 0;
		}   

	scsi_do_cmd (SR_HOST, SR_ID, (void *) cmd, buffer, realcount << 11, 
		     rw_intr, SR_TIMEOUT, sense_buffer, MAX_RETRIES);
}

void sr_init(void)
{
	int i;

	for (i = 0; i < NR_SR; ++i)
		{
		scsi_CDs[i].capacity = 0x1fffff;
		scsi_CDs[i].sector_size = 2048;
		scsi_CDs[i].use = 1;
		scsi_CDs[i].ten = 1;
		scsi_CDs[i].remap = 1;
		scsi_CDs[i].changed = 0;
		sr_sizes[i] = scsi_CDs[i].capacity;

		bb[i].block = -1;
		}

	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
	blk_size[MAJOR_NR] = sr_sizes;	
	blkdev_fops[MAJOR_NR] = &sr_fops; 
}	
#endif





