#include <linux/config.h>
#ifdef CONFIG_BLK_DEV_SR
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <asm/segment.h>
#include <linux/errno.h>

#include "../blk.h"
#include "scsi.h"
#include "sr.h"

#include <linux/cdrom.h>

#define IOCTL_RETRIES 3
/* The CDROM is fairly slow, so we need a little extra time */
#define IOCTL_TIMEOUT 200

static u_char 	sr_cmd[10];
static u_char 	data_buffer[255];
static u_char 	sense_buffer[255];
static int 	the_result;

static struct wait_queue *sr_cmd_wait = NULL;   /* For waiting until cmd done*/
static u_char 	sr_lock = 0;   /* To make sure that only one person is doing
				  an ioctl at one time */
static int 	target;

extern int scsi_ioctl (int dev, int cmd, void *arg);

static void lock_sr_ioctl( void )
{
  /* We do not use wakeup here because there could conceivably be three
     processes trying to get at the drive simultaneously, and we would
     be screwed if that happened.
     */

	while (sr_lock);
	sr_lock = 1;
}

static void unlock_sr_ioctl( void )
{
	sr_lock = 0;
}

static void sr_ioctl_done( int host, int result )
{
	the_result = result;
	wake_up(&sr_cmd_wait);
}

/* We do our own retries because we want to know what the specific
   error code is.  Normally the UNIT_ATTENTION code will automatically
   clear after one error */

static int do_ioctl( void )
{
	int retries = IOCTL_RETRIES;
retry:     

	the_result = -1;

	scsi_do_cmd(scsi_CDs[target].device->host_no, scsi_CDs[target].device->id,
		    (void *) sr_cmd, (void *) data_buffer, 255, sr_ioctl_done, 
		    IOCTL_TIMEOUT, (void *) sense_buffer, 0);

	while (the_result < 0) sleep_on(&sr_cmd_wait);

	if(driver_byte(the_result) != 0 && 
	   (sense_buffer[2] & 0xf) == UNIT_ATTENTION) {
	  scsi_CDs[target].changed = 1;
	  printk("Disc change detected.\n");
	};

	if (the_result && retries)
	        {
		retries--;
		goto retry;
		}

/* Minimal error checking.  Ignore cases we know about, and report the rest. */
	if(driver_byte(the_result) != 0)
	  switch(sense_buffer[2] & 0xf) {
	  case UNIT_ATTENTION:
	    scsi_CDs[target].changed = 1;
	    printk("Disc change detected.\n");
	    break;
	  case NOT_READY: /* This happens if there is no disc in drive */
	    printk("CDROM not ready.  Make sure there is a disc in the drive.\n");
	    break;
	  case ILLEGAL_REQUEST:
	    printk("CDROM (ioctl) reports ILLEGAL REQUEST.\n");
	    break;
	  default:
	    printk("SCSI CD error: host %d id %d lun %d return code = %03x\n", 
		   scsi_CDs[target].device->host_no, 
		   scsi_CDs[target].device->id,
		   scsi_CDs[target].device->lun,
		   the_result);
	    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);
	    
	};
      	return the_result;
}
	
/*
 * This function checks to see if the media has been changed in the
 * CDROM drive.  It is possible that we have already sensed a change,
 * or the drive may have sensed one and not yet reported it.  We must
 * be ready for either case. This function always reports the current
 * value of the changed bit.  If flag is 0, then the changed bit is reset.
 * This function could be done as an ioctl, but we would need to have
 * an inode for that to work, and we do not always have one.
 */

int check_cdrom_media_change(int full_dev, int flag){
	int retval;

	lock_sr_ioctl();

	target =  MINOR(full_dev);

	if (target >= NR_SR) {
		printk("CD-ROM request error: invalid device.\n");
		unlock_sr_ioctl();
		return 0;
	};

	sr_cmd[0] = TEST_UNIT_READY;
	sr_cmd[1] = (scsi_CDs[target].device->lun << 5) & 0xe0;
	sr_cmd[2] = sr_cmd[3] = sr_cmd[4] = sr_cmd[5] = 0;

	retval = do_ioctl();

	if(retval){ /* Unable to test, unit probably not ready.  This usually
		     means there is no disc in the drive.  Mark as changed,
		     and we will figure it out later once the drive is
		     available again.  */

	  scsi_CDs[target].changed = 1;
	  unlock_sr_ioctl();
	  return 1; /* This will force a flush, if called from
		       check_disk_change */
	};

	retval = scsi_CDs[target].changed;
	if(!flag) scsi_CDs[target].changed = 0;
	unlock_sr_ioctl();

	return retval;
}

int sr_ioctl(struct inode * inode, struct file * file, unsigned long cmd, unsigned long arg)
{
	int dev = inode->i_rdev;
	int result;

	target = MINOR(dev);

	switch (cmd) 
		{
		/* linux-specific */
		case CDROMDOORUNLOCK:
		        lock_sr_ioctl();

		        sr_cmd[0] = ALLOW_MEDIUM_REMOVAL;
			sr_cmd[1] = scsi_CDs[target].device->lun << 5;
			sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0;
			sr_cmd[4] = SR_REMOVAL_ALLOW;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;

		case CDROMDOORLOCK:
		        lock_sr_ioctl();

		        sr_cmd[0] = ALLOW_MEDIUM_REMOVAL;
			sr_cmd[1] = scsi_CDs[target].device->lun << 5;
			sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0;
			sr_cmd[4] = SR_REMOVAL_PREVENT;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;

		/* Sun-compatible */
		case CDROMPAUSE:
			lock_sr_ioctl();

			sr_cmd[0] = SCMD_PAUSE_RESUME;
			sr_cmd[1] = scsi_CDs[target].device->lun << 5;
			sr_cmd[2] = sr_cmd[3] = sr_cmd[4] = 0;
			sr_cmd[5] = sr_cmd[6] = sr_cmd[7] = 0;
			sr_cmd[8] = 1;
			sr_cmd[9] = 0;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;

		case CDROMRESUME:
			lock_sr_ioctl();

			sr_cmd[0] = SCMD_PAUSE_RESUME;
			sr_cmd[1] = scsi_CDs[target].device->lun << 5;
			sr_cmd[2] = sr_cmd[3] = sr_cmd[4] = 0;
			sr_cmd[5] = sr_cmd[6] = sr_cmd[7] = 0;
			sr_cmd[8] = 0;
			sr_cmd[9] = 0;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;

		case CDROMPLAYMSF:
			{
			struct cdrom_msf msf;
			lock_sr_ioctl();

			memcpy_fromfs(&msf, (void *) arg, sizeof(msf));

			sr_cmd[0] = SCMD_PLAYAUDIO_MSF;
			sr_cmd[1] = scsi_CDs[target].device->lun << 5;
			sr_cmd[2] = 0;
			sr_cmd[3] = msf.cdmsf_min0;
			sr_cmd[4] = msf.cdmsf_sec0;
			sr_cmd[5] = msf.cdmsf_frame0;
			sr_cmd[6] = msf.cdmsf_min1;
			sr_cmd[7] = msf.cdmsf_sec1;
			sr_cmd[8] = msf.cdmsf_frame1;
			sr_cmd[9] = 0;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;
			}

		case CDROMPLAYTRKIND:
			{
			struct cdrom_ti ti;
			lock_sr_ioctl();

			memcpy_fromfs(&ti, (void *) arg, sizeof(ti));

			sr_cmd[0] = SCMD_PLAYAUDIO_TI;
			sr_cmd[1] = scsi_CDs[target].device->lun << 5;
			sr_cmd[2] = 0;
			sr_cmd[3] = 0;
			sr_cmd[4] = ti.cdti_trk0;
			sr_cmd[5] = ti.cdti_ind0;
			sr_cmd[6] = 0;
			sr_cmd[7] = ti.cdti_trk1;
			sr_cmd[8] = ti.cdti_ind1;
			sr_cmd[9] = 0;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;
			}

		case CDROMREADTOCHDR:
			return -EINVAL;
		case CDROMREADTOCENTRY:
			return -EINVAL;

		case CDROMSTOP:
		        lock_sr_ioctl();

		        sr_cmd[0] = START_STOP;
			sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5) | 1;
			sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0;
			sr_cmd[4] = 0;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;
			
		case CDROMSTART:
		        lock_sr_ioctl();

		        sr_cmd[0] = START_STOP;
			sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5) | 1;
			sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0;
			sr_cmd[4] = 1;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;

		case CDROMEJECT:
		        lock_sr_ioctl();

		        sr_cmd[0] = START_STOP;
			sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5) | 1;
			sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0;
			sr_cmd[4] = 0x02;

			result = do_ioctl();

			unlock_sr_ioctl();
			return result;

		case CDROMVOLCTRL:
			return -EINVAL;
		case CDROMSUBCHNL:
			return -EINVAL;
		case CDROMREADMODE2:
			return -EINVAL;
		case CDROMREADMODE1:
			return -EINVAL;

		RO_IOCTLS(dev,arg);
		default:
			return scsi_ioctl(scsi_CDs[target].device,cmd,(void *) arg);
		}
}

#endif
