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

/*
 *      dvdpcdev.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 <linux/module.h>

#include "ide.h"

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

char kernel_version[] = UTS_RELEASE;

// this table stores up to DVDPC_MAX_DEV devices
dvdpc_dev_t dvdpc_dev_tab[DVDPC_MAX_DEV];
int dvdpc_dev_idx;

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

int dvdpc_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
int dvdpc_open(struct inode *, struct file *);
int dvdpc_release(struct inode *, struct file *);

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

// extract minor number when accessed by struct inode
int dvdpc_get_dev(struct inode *i_node,struct file_operations **fops)
{
	int idx = MINOR(i_node->i_rdev)&DVDPC_MINOR_DEV_MASK;
	dvdpc_dev_t *dev;

	ASSERT(DEBUG,(idx<dvdpc_dev_idx),
	       -EINVAL,RAS);

	dev=&dvdpc_dev_tab[idx];

	ASSERT(DEBUG,dev->fops,-EINVAL,RAS);
	
	*fops=dev->fops;

	return 0;
}

int dvdpc_ioctl(struct inode* i_node, struct file* filp,
		    unsigned int arg1, unsigned long arg2)
{
	struct file_operations *dev_fops=0;

	VERBOSECALL(DEBUG,dvdpc_get_dev(i_node,&dev_fops),
		    -EINVAL,RAS);
	
	ASSERT(DEBUG,dev_fops,-EINVAL,RAS);
	ASSERT(DEBUG,dev_fops->ioctl,-EINVAL,RAS);
	
	return (*dev_fops->ioctl)(i_node, filp, arg1, arg2);
}

// open does nothing. exists so that open("/dev/dvdpc0") returns ok
int dvdpc_open(struct inode *i_node, struct file *filp)
{
	int *opentimes=
		&dvdpc_dev_tab[MINOR(i_node->i_rdev)&DVDPC_MINOR_DEV_MASK].opentimes;
	
	/* allow opening twice in case two decoders want to decode
	   the same stream. Works pretty bad of course. */
	   
	/*	if (*opentimes!=0) {
		printk("dvdpc_open: already open\n");
		return -EINVAL;
		}*/
	
	(*opentimes)++;

	LOGINFO(1,"opening device #%d successful (doing nothing)",
		MINOR(i_node->i_rdev)&DVDPC_MINOR_DEV_MASK);

	return 0;
}

// release does nothing. exists for symmetry reason with open
int dvdpc_release(struct inode *i_node, struct file *filp)
{
	int *opentimes=
		&dvdpc_dev_tab[MINOR(i_node->i_rdev)&DVDPC_MINOR_DEV_MASK].opentimes;
	
	// close twice may occur if opened twice 
	if (*opentimes==0) {
		printk("dvdpc_release: already closed\n");
		return -EINVAL;
	}
	
	(*opentimes)--;
	
	LOGINFO(1,"releasing device #%d successful (doing nothing)",
		MINOR(i_node->i_rdev)&DVDPC_MINOR_DEV_MASK);
	return 0;
}

int init_module(void)
{
	dvdpc_dev_idx=0;
	
	// look for hardware
	VERBOSECALL(DEBUG,atapidvd_hook(),
		    -EINVAL,RAS);
	
	// register char devices
	VERBOSECALL(DEBUG,register_chrdev(DVDPC_MAJOR,
					  DVDPC_DEVICE_NAME,
					  &dvdpc_fops),
		    -EINVAL,"ok for major %d",DVDPC_MAJOR);
	
	LOGINFO(1,"init_module success");

	return 0;
}

int cleanup_module_with_return_code()
{
	int idx;
	
	VERBOSECALL(DEBUG,unregister_chrdev(DVDPC_MAJOR,
					    DVDPC_DEVICE_NAME),
		    -EINVAL,RAS);
	
	for(idx=0;idx<dvdpc_dev_idx;idx++) {
		if (dvdpc_dev_tab[idx].opentimes!=0)
			printk("dvdpc cleanup: instance #%d still open\n",idx);
	}
	
	return 0;
}

void cleanup_module()
{
	int rc;
	
	rc=cleanup_module_with_return_code();

	LOGINFO(1,"cleanup_module over with %d",rc);
}
