/*
 * Copyright (c) 2003 Linuxant inc.
 * Copyright (c) 2001-2003 Conexant Systems, Inc.
 *
 * NOTE: The use and distribution of this software is governed by the terms in
 * the file LICENSE, which is included in the package. You must read this and
 * agree to these terms before using or distributing this software.
 *
 */
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/vmalloc.h>
#include <linux/poll.h>
#include <linux/slab.h>

#include "oscompat.h"
#include "ossysenv.h"
#include "scrcommon.h"
#include "dbgsrv_linux.h"
#include "dmpdefs.h"

MODULE_AUTHOR("Marc Boucher <marc@linuxant.com> for Conexant Systems Inc.");
MODULE_DESCRIPTION("HSF softmodem DMP debug driver");
MODULE_LICENSE("Copyright (C) 1996-2001 Conexant Systems Inc. All Rights Reserved.");

#define SCRDMP_MINOR_SCR 0
#define SCRDMP_MINOR_DMP 1

typedef struct {
	int minor;
} scrdmp_unit_t;


static ULONG				dmp_nOpenHandles;
static DMP_ACTION_INFO		*dmp_segments;
static DWORD				dmp_segInIndex, dmp_segOutIndex;
static DWORD				dmp_nSegs;
//static char				m_SelectedClients[MAX_CLIENTS][MAX_CLIENT_NAME];
//static DWORD				m_nSelectedClients;

#define DMP_READ_WAKEUP_THRESH 8
static DECLARE_WAIT_QUEUE_HEAD(scr_read_wait);
static DECLARE_WAIT_QUEUE_HEAD(dmp_read_wait);

static int scrdmpdebug = 0;
MODULE_PARM(scrdmpdebug, "i");
MODULE_PARM_DESC(scrdmpdebug, "Debug level for scrdmp");

static BOOL
DMPSRV_Dispatcher(PCHAR ClientName, DMP_SERVER_CODE Code,
		PCHAR FileName, PBYTE pSegment, DWORD nData )
{
	if(scrdmpdebug)
		printk(KERN_INFO __FUNCTION__": ClientName=%s Code=%d FileName=%s pSegment=%p nData=%ld\n", ClientName ? ClientName : "<NULL>", Code, FileName? FileName : "<NULL>", pSegment, nData);

	if ( dmp_nSegs >= DMP_MAX_SEGMENTS ) {
		printk(KERN_ERR __FUNCTION__": Queue is full\n");
		return FALSE;
	}

	if (dmp_nOpenHandles == 0) {
		//printk(KERN_INFO __FUNCTION__": device not opened (no daemon running?)\n");
		return FALSE;
	}

	strncpy( dmp_segments[dmp_segInIndex].ClientName, ClientName, DMP_MAX_NAME_LEN );
	strncpy( dmp_segments[dmp_segInIndex].FileName, FileName, DMP_MAX_NAME_LEN );
	dmp_segments[dmp_segInIndex].Code = Code;

	if ( pSegment != NULL )
	{
		if(nData > DMP_SEG_SIZE)
			nData = DMP_SEG_SIZE;
		memcpy( dmp_segments[dmp_segInIndex].pData, pSegment, nData );
		memset( dmp_segments[dmp_segInIndex].pData + nData, '\0', DMP_SEG_SIZE - nData );
	}

	dmp_segments[dmp_segInIndex].nData = nData;

	dmp_segInIndex++;
	if ( dmp_segInIndex >= DMP_MAX_SEGMENTS ) 
		dmp_segInIndex -= DMP_MAX_SEGMENTS;
	dmp_nSegs++;

	if (Code != DMP_WRITE_TO_FILE || dmp_nSegs >= DMP_READ_WAKEUP_THRESH)
		wake_up_interruptible(&dmp_read_wait);

	return TRUE;
}

static unsigned int
scrdmp_poll(struct file *file, poll_table * wait)
{
	scrdmp_unit_t *su = file->private_data;
	unsigned int mask = 0;

	if(su->minor == SCRDMP_MINOR_DMP) {
		poll_wait(file, &dmp_read_wait, wait);
		if (dmp_nSegs >= DMP_READ_WAKEUP_THRESH)
			mask |= POLLIN | POLLRDNORM;
	} else { /* here we assume SCRDMP_MINOR_SCR */
		poll_wait(file, &scr_read_wait, wait);
		if(DbgSrvLinuxAvailNumOfRecords() > 0)
			mask |= POLLIN | POLLRDNORM;
	}
	return mask;
}

static void
scrdmp_wake_app_scr(void)
{
		wake_up_interruptible(&scr_read_wait);
}

static ssize_t
scrdmp_read_scr(struct file * file, char * buf, size_t nbytes, loff_t *ppos)
{
	DECLARE_WAITQUEUE(wait, current);
	QUEUE_RECORD qRec;
	QUEUE_RECORD	*pSegments = (QUEUE_RECORD *)buf;
	size_t nSegs = nbytes / sizeof(QUEUE_RECORD);
	ssize_t retval = 0;

	if(nbytes % sizeof(QUEUE_RECORD)) {
		printk(KERN_ERR __FUNCTION__": invalid size %d; must be a multiple of %d bytes\n", nbytes, sizeof(QUEUE_RECORD));
	}

	if(nSegs <= 0)
		return 0;

	add_wait_queue(&scr_read_wait, &wait);

	while(nSegs > 0) {
		set_current_state(TASK_INTERRUPTIBLE);

		if(!DbgSrvLinuxGetQueueRecord(&qRec)) {
			if (file->f_flags & O_NONBLOCK) {
				if(!retval)
					retval = -EAGAIN;
				break;
			}
			if (signal_pending(current)) {
					retval = -ERESTARTSYS;
					break;
			}
			schedule();
			continue;
		}

		if(copy_to_user( pSegments, &qRec, sizeof(QUEUE_RECORD) )) {
			retval = -EFAULT;
			break;
		}
		nSegs--;
		pSegments++;

		UPDATE_ATIME(file->f_dentry->d_inode);

		retval += sizeof(QUEUE_RECORD);
	}

	set_current_state(TASK_RUNNING);
	remove_wait_queue(&scr_read_wait, &wait);
	return retval;
}

static ssize_t
scrdmp_read_dmp(struct file * file, char * buf, size_t nbytes, loff_t *ppos)
{
	DECLARE_WAITQUEUE(wait, current);
	DWORD		i;
	DWORD		nSegments;
	PDMP_ACTION_INFO	pSegments = (PDMP_ACTION_INFO)buf;
	size_t nSegs = nbytes / sizeof(DMP_ACTION_INFO);
	ssize_t retval = 0;

	if(nbytes % sizeof(DMP_ACTION_INFO)) {
		printk(KERN_ERR __FUNCTION__": invalid size %d; must be a multiple of %d bytes\n", nbytes, sizeof(DMP_ACTION_INFO));
	}

	if(nSegs <= 0)
		return 0;

	add_wait_queue(&dmp_read_wait, &wait);

	while(nSegs > 0) {
		set_current_state(TASK_INTERRUPTIBLE);

		if(dmp_nSegs == 0) {
			if (file->f_flags & O_NONBLOCK) {
				retval = -EAGAIN;
				break;
			}
			if (signal_pending(current)) {
					retval = -ERESTARTSYS;
					break;
			}
			schedule();
			continue;
		}

		nSegments = dmp_nSegs;
		if ( nSegments > nSegs ) {
			nSegments = nSegs;
		}

		for ( i=0 ; i < nSegments ; i++, dmp_segOutIndex++ ) {
			if ( dmp_segOutIndex >= DMP_MAX_SEGMENTS )
				dmp_segOutIndex -= DMP_MAX_SEGMENTS;

			if(copy_to_user( &pSegments[i], &dmp_segments[dmp_segOutIndex], sizeof(DMP_ACTION_INFO) )) {
				retval = -EFAULT;
				break;
			}
		}
		dmp_nSegs -= i;
		if (i != 0) {
			UPDATE_ATIME(file->f_dentry->d_inode);
		}
		if(!retval)
			retval = i * sizeof(DMP_ACTION_INFO);
		break;
	}

	set_current_state(TASK_RUNNING);
	remove_wait_queue(&dmp_read_wait, &wait);
	return retval;
}

static ssize_t
scrdmp_read(struct file * file, char * buf, size_t nbytes, loff_t *ppos)
{
	scrdmp_unit_t *su = file->private_data;

	if(su->minor == SCRDMP_MINOR_DMP)
		return scrdmp_read_dmp(file, buf, nbytes, ppos);
	else /* here we assume SCRDMP_MINOR_SCR */
		return scrdmp_read_scr(file, buf, nbytes, ppos);
}

int scrdmp_open(struct inode* inode, struct file*  file)
{
	int minor = minor(inode->i_rdev);
	scrdmp_unit_t *su;

	su = kmalloc(sizeof(*su), GFP_KERNEL);
	if(!su)
		return -ENOMEM;

	switch(minor) {
		case SCRDMP_MINOR_SCR:
			DbgSrvLinuxOnOpenHandle();
			break;
		case SCRDMP_MINOR_DMP:
 			dmp_nOpenHandles++;
			break;
		default:
			printk(KERN_ERR __FUNCTION__": invalid minor device %d\n", minor);
			kfree(su);
			return -EIO;
	}

	su->minor = minor;

#if 0
	if(dmp_nOpenHandles)
		return -EBUSY; // only one open allowed at once for now
#endif

	file->private_data = su;

	MOD_INC_USE_COUNT;

	return 0;
}

int scrdmp_release(struct inode* inode, struct file*  file)
{
	int minor = minor(inode->i_rdev);
	scrdmp_unit_t *su = file->private_data;

	switch(minor) {
		case SCRDMP_MINOR_SCR:
			DbgSrvLinuxOnCloseHandle();
			break;
		case SCRDMP_MINOR_DMP:
 			dmp_nOpenHandles--;
			break;
		default:
			break;
	}

	kfree(su);
	MOD_DEC_USE_COUNT;

	return 0;
}

static struct file_operations scrdmp_fops =
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	owner:            THIS_MODULE,
#endif
	read:             scrdmp_read,
	poll:             scrdmp_poll,
	open:             scrdmp_open,
	release:          scrdmp_release,
};

static int scrdmpmajor = HSFSCRDMPMAJOR;
MODULE_PARM(scrdmpmajor, "i");
MODULE_PARM_DESC(scrdmpmajor, "Major device number for scrdmp device");

void cleanup_module(void)
{
	if(scrdmpmajor > 0)
		unregister_chrdev(scrdmpmajor, "hsfscrdmp");

#ifdef HAVE_INTER_MODULE
	inter_module_unregister("DMPSRV_Dispatcher");
	inter_module_unregister("DBGSRV_Print");
#endif

	vfree(dmp_segments);
	dmp_segments = NULL;

	DbgSrvLinuxCleanup();
}

int init_module(void)
{
	int ret;

	(THIS_MODULE)->flags &= ~MOD_AUTOCLEAN;

	if(!DbgSrvLinuxInit(scrdmp_wake_app_scr)) {
		printk(KERN_ERR "hsfscrdmp: cannot initialize DbgSrv\n");
		return -EIO;
	}

	dmp_segments = vmalloc(DMP_MAX_SEGMENTS * sizeof(DMP_ACTION_INFO));
	if ( dmp_segments == NULL ) {
		printk(KERN_ERR "DMP: " __FUNCTION__": failed to allocate dmp_segments (%d bytes)", DMP_MAX_SEGMENTS * sizeof(DMP_ACTION_INFO));
		return -ENOMEM;
	}

	dmp_segInIndex = 0;
	dmp_segOutIndex = 0;
	dmp_nSegs = 0;

	dmp_nOpenHandles = 0;

#ifdef HAVE_INTER_MODULE
	inter_module_register("DBGSRV_Print", THIS_MODULE, DBGSRV_Print);
	inter_module_register("DMPSRV_Dispatcher", THIS_MODULE, DMPSRV_Dispatcher);
#endif

	if(scrdmpmajor == 0)
			scrdmpmajor = register_chrdev(0, "hsfscrdmp", &scrdmp_fops);
	else if((ret=register_chrdev(scrdmpmajor, "hsfscrdmp", &scrdmp_fops))) {
			scrdmpmajor = ret;
	}

	if(scrdmpmajor < 0) {
		printk(KERN_ERR "hsfscrdmp: cannot register chrdev\n");
		cleanup_module();
		return -EIO;
	}

	return 0;
}

#ifndef HAVE_INTER_MODULE
EXPORT_SYMBOL_NOVERS(DBGSRV_Print);
EXPORT_SYMBOL_NOVERS(DMPSRV_Dispatcher);
#endif // HAVE_INTER_MODULE

#if 0
// Implementation of DmpSrvNTDevice device class

#include <vdw.h>

#include "dmpsrvnt.h"
#include "dmpsrvntdevice.h"
#include "dbgfuncs.h"

static DmpSrvNTDevice*		ptheDevice = NULL;

#pragma code_seg("INIT")

// DmpSrvNTDevice Constructor
//		The device constructor is typically responsible for allocating
//		any physical resources that are associated with the device.
//
//		The device constructor often reads the registry to setup
//		various configurable parameters.
//
DmpSrvNTDevice::DmpSrvNTDevice(ULONG Unit) :
		KDevice(
			KUnitizedName(L"DmpSrvNTDevice", Unit),
			FILE_DEVICE_UNKNOWN,
			KUnitizedName(L"DmpSrvNT", Unit),
			0,
			DO_BUFFERED_IO)


{
	if ( ! NT_SUCCESS(m_ConstructorStatus) )
	{
		return;
	}
	m_Unit = Unit;
	dmp_nOpenHandles = 0;
	m_RegPath = CreateRegistryPath(L"DmpSrvNTDevice", Unit);
	if (m_RegPath == NULL)
	{
		// Error, cannot allocate memory for registry path
		m_ConstructorStatus = STATUS_INSUFFICIENT_RESOURCES;
		return;
	}
	if ( Initialize() == FALSE ) {
		m_ConstructorStatus = STATUS_INSUFFICIENT_RESOURCES;
	}
	ptheDevice = this;
}
#pragma code_seg()

// DmpSrvNTDevice Destructor
//
DmpSrvNTDevice::~DmpSrvNTDevice()
{

	Terminate();

	delete m_RegPath;
}


// Member functions of DmpSrvNTDevice
// 



// Major function handlers
//   
NTSTATUS DmpSrvNTDevice::Read(KIrp I)
{
	// TODO: Handle read request

	return I.Complete(STATUS_SUCCESS);
}

NTSTATUS DmpSrvNTDevice::DeviceControl(KIrp I)
{
	DWORD		i;
	DWORD		nSegments;
	PCHAR		ClientName;
	
	NTSTATUS status = STATUS_SUCCESS;
    // TODO: Handle Internal Device Control request

	DPRINTF(( "DmpSrvNTDevice::DeviceControl called with Code = %d", I.IoctlCode() ));

		switch (I.IoctlCode()) {

		case DMP_CONFIGURE_CLIENTS:
			m_nSelectedClients = I.IoctlInputBufferSize(CURRENT) / DMP_MAX_CLIENT_NAME;
			memcpy( m_SelectedClients, I.BufferedWriteSource(), I.IoctlInputBufferSize(CURRENT) );
			DPRINTF(( "Selected Clients" ));
			for ( i=0 ; i < m_nSelectedClients ; i++ )
				DPRINTF(( "%d)\t%s", i, m_SelectedClients[i] ));
			DPRINTF(( "" ));
			I.Information() = 0;
		break;

		case DMP_GET_SEGMENT:

			nSegments = dmp_nSegs;
			DPRINTF(("nSegments = %d",nSegments));
			if ( nSegments > 0 ) {
				
				PDMP_ACTION_INFO	pSegments = (PDMP_ACTION_INFO)I.BufferedReadDest();
				for ( i=0 ; i < nSegments ; i++, dmp_segOutIndex++ ) {
					if ( dmp_segOutIndex >= DMP_MAX_SEGMENTS )
						dmp_segOutIndex -= DMP_MAX_SEGMENTS;
					memcpy( &pSegments[i], &dmp_segments[dmp_segOutIndex], sizeof(DMP_ACTION_INFO) );
				}

				I.Information() = nSegments * sizeof(DMP_ACTION_INFO);
				DPRINTF(( "%d bytes copied", nSegments * sizeof(DMP_ACTION_INFO) ));

				dmp_nSegs -= nSegments;

			} else {
				I.Information() = 0;
			}


		break;

		case DMP_GET_FUNCTION:
			ClientName = (PCHAR)I.BufferedWriteSource();
			if ( IsClientSelected( ClientName ) )
				*(DWORD *)I.BufferedReadDest() = (DWORD)DMPSRV_Dispatcher;
			else {
				DPRINTF(( "Client %s is not selected", ClientName ));
				*(DWORD *)I.BufferedReadDest() = NULL;
			}
			I.Information() = sizeof(DWORD);

		break;

		case DMP_GET_NUM_INSTANCES:
			*(ULONG*)I.BufferedReadDest() = dmp_nOpenHandles;
			I.Information() = sizeof(ULONG);
		break;

		case DMP_GET_NUMBER_OF_CLIENTS:
			*(ULONG*)I.BufferedReadDest() = dmp_nOpenHandles;
			I.Information() = sizeof(ULONG);
		break;

		default:
			I.Information() = 0; 
			return I.Complete(STATUS_NOT_SUPPORTED);
		break;
	}


    return I.Complete(STATUS_SUCCESS);
}

NTSTATUS DmpSrvNTDevice::Close(KIrp I)
{
	I.Information() = 0;
	dmp_nOpenHandles--;

	return I.Complete(STATUS_SUCCESS);
}

NTSTATUS DmpSrvNTDevice::Create(KIrp I)
{
	I.Information() = 0;
	dmp_nOpenHandles++;
	return I.Complete(STATUS_SUCCESS);
}


BOOLEAN	DmpSrvNTDevice::Initialize()
{
}

void	DmpSrvNTDevice::Terminate()
{

}

BOOLEAN	DmpSrvNTDevice::IsClientSelected( PCHAR ClientName )
{
	int		i;

	for ( i=0 ; i < m_nSelectedClients ; i++ )
		if ( strncmp( m_SelectedClients[i], ClientName, DMP_MAX_CLIENT_NAME ) == 0 )
			return TRUE;

		return FALSE;
}

#endif
