/*****************************************************************************/

/*
 *      dvdpc.c -- An add-on to handle packet command level ioctls on IDE 
 *                 DVD drives as a module. This is a very thin layer on 
 * 	           top of the Linux cdrom driver.
 *
 *      Copyright (C) 1999-2000  Sigma Designs
 *                    written by Emmanuel Michon <emmanuel_michon@sdesigns.com>
 *      Copyright (c) 1996, 1997 David A. van Leeuwen
 *      Copyright (c) 1997, 1998 Erik Andersen <andersee@debian.org>
 *      Copyright (c) 1998, 1999 Jens Axboe
 *
 *
 *      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 of the License, 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; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*****************************************************************************/

#include <linux/version.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#include <linux/cdrom.h>
#include "ide.h"
/* VERBOSE_IDE_CD_ERRORS dumps the spec. const tables in object files.
   Since both dvdpc.c and ide-cd.c include ide-cd.h,
   there is a conflict in final link */
#define VERBOSE_IDE_CD_ERRORS 0
#include "ide-cd.h"

#include "common.h"
#include "dvdpc.h"
#include "dvdpcdev.h"

#define RXTXSIZE 33000 // actual requests are by 32k blocks

static char rxtxbuffer[RXTXSIZE];

// look in linux/fs.h for types
// int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

int pc_ioctl(struct inode *, struct file *, unsigned int, unsigned long);

static struct file_operations pc_fops =
{
    NULL,     /* llseek function.     */
    NULL,     /* read function.       */
    NULL,     /* write function.      */
    NULL,     /* read directory.      */
    NULL,     /* poll function.       */
    pc_ioctl, /* ioctl function.      */
    NULL,     /* mmap function.       */
    NULL,     /* open function.       */
    NULL,     /* flush function.      */
    NULL,     /* release function.    */
    NULL,     /* sync file func.      */
    NULL,     /* async file func.     */
    NULL,     /* media change check.  */
    NULL,     /* revalidate function. */
    NULL      /* lock function.       */
};

/* prototype taken from ide-cd.c 
 * this function wasn't meant to be exported (sigh)
 */
int cdrom_queue_packet_command (ide_drive_t *drive, struct packet_command *pc);

int pc_ioctl(struct inode *i_node, struct file *filp,
		 unsigned int cmd, unsigned long arg)
{
	ide_drive_t *drive;
	int idx = MINOR(i_node->i_rdev)&DVDPC_MINOR_DEV_MASK;
	dvdpc_dev_t *dev = &dvdpc_dev_tab[idx];

	drive = dev->handle;
	
	switch (cmd) {
		
	case IOCTL_PACKET_COMMAND:
		{
			// obviously, this code is the hidden 0x1234 ioctl of ide-cd.c
			int stat;
			struct packet_command pc;
			int len, lena;
			
			memset (&pc, 0, sizeof (pc));

			stat = verify_area (VERIFY_READ, (void *) arg, sizeof (pc.c));
			if (stat) return stat;
			copy_from_user (&pc.c, (void *) arg, sizeof (pc.c));
			arg += sizeof (pc.c);
			
			stat = verify_area (VERIFY_READ, (void *) arg, sizeof (len));
			if (stat) return stat;
			copy_from_user (&len, (void *) arg , sizeof (len));
			arg += sizeof (len);
			
			LOGINFO(DEBUG,"queueing packet command %02x ... len=%d",pc.c[0],len);

			// len can be negative, then meaning that we want to send something
			lena = len;
			if (lena  < 0) lena = -lena;
			
			if (len > 0) {
				stat = verify_area (VERIFY_WRITE,(void *) arg, lena);
				if (stat) return stat;
			}
			else if (len < 0) {
				stat = verify_area (VERIFY_READ,(void *) arg, lena);
				if (stat) return stat;
				copy_from_user (rxtxbuffer, (void*)arg, lena);
			}
			
			if (len != 0) {
				pc.buflen = len; // len and not lena
				pc.buffer = rxtxbuffer;
			}
			
			stat = cdrom_queue_packet_command (drive, &pc);
			
			if (len > 0)
				copy_to_user ((void *)arg, rxtxbuffer, lena);
			
			// whatever happens return sense data
			arg+=lena;
			copy_to_user((void *)arg,pc.sense_data,sizeof(struct atapi_request_sense));

			// this return value is important
			return stat;
		}
		
	default:
		return -EINVAL;
	}
	
	return 0; // never reached anyways
}

int atapidvd_hook(void)
{
	unsigned int index,unit;

	dvdpc_dev_idx=0;
	
	// this code comes from ide_scan in ide.c
	for (index = 0; index < MAX_HWIFS; ++index) {
		ide_hwif_t *hwif = &ide_hwifs[index];
		if (!hwif->present)
			continue;
		
		for (unit = 0; unit < MAX_DRIVES; ++unit) {
			ide_drive_t *drive = &hwif->drives[unit];
			dvdpc_dev_t *dev;
			
			LOGINFO(1,"scanning device #%d on ribbon #%d",unit,index);
			
			ASSERT(DEBUG,(dvdpc_dev_idx<DVDPC_MAX_DEV),
			       -EINVAL,RAS);
			
			dev=&dvdpc_dev_tab[dvdpc_dev_idx];
			
			dev->fops = &pc_fops;
			
			// the following line is the only important line
			dev->handle = drive;
			
			dev->opentimes = 0;
			
			LOGINFO(1,"found IDE device #%d @%p",
				dvdpc_dev_idx,
				drive);
			
			dvdpc_dev_idx++;
		}
	}
	
	ASSERT(1,dvdpc_dev_idx,
	       -EINVAL,"found %d IDE devices total",dvdpc_dev_idx);
	
	return 0;
}
