
/*
 * Copyright 1987 Alan Kent
 *
 * Permission is granted to redistribute this code as long
 * as this message is retained in the code and the code is
 * not sold without written permission from the author.
 *
 * UUCP: {seismo,hplabs,mcvax,ukc,nttlab}!munnari!goanna.oz!ajk
 * ACSnet: ajk@goanna.oz
 * ARPA: munnari!goanna.oz!ajk@SEISMO.ARPA
 */


#include "hd.h"


/* initialize library node */

LONG
cLibInit ( dev , seg_list )
struct hd_dev *dev;
BPTR seg_list;		/* I think its BPTR */
{
	int i;
	struct hd_unit *unit;
	struct Process *process;


	/* easier here than trying to use the automatic initalization */

	dev->hd_Dev.dd_Library.lib_Node.ln_Type = NT_DEVICE;
	dev->hd_Dev.dd_Library.lib_Node.ln_Name = dev_name;
	dev->hd_Dev.dd_Library.lib_Flags = LIBF_SUMUSED|LIBF_CHANGED;
	dev->hd_Dev.dd_Library.lib_Version = HD_VERSION;
	dev->hd_Dev.dd_Library.lib_Revision = HD_REVISION;
	dev->hd_Dev.dd_Library.lib_IdString = (APTR)dev_id_string;

	/* Initiate controller. Actually, this is being a bit naughty. */
	/* The signals should be allocated in the created subtask, but */
	/* I know the subtask does not allocate any signals itself so */
	/* any signal we create will be ok. The signal is created for */
	/* the interrupt driven controller */

	if ( wd_open () < 0 )
		return ( NULL );

	/* create a single global port on which ALL messages for all devices */
	/* and units will arrive. Note that CreatePort allocates a signal. */
	/* See the above discussion about signals and wd_open() */

	port = CreatePort ( (char*)NULL , (LONG)0 );
	if ( port == NULL ) {
		wd_close ();
		return ( NULL );
	}

	/* initiate cache */

	if ( init_cache () < 0 ) {
		wd_close ();
		DeletePort ( port );
		return ( NULL );
	}

	/* Create a single task to be shared by all units. */
	/* This is to ensure exclusive access to the controller. */
	/* Note that the caching at present will only work with a */
	/* single unit */

	process = CreateProc ( dev_name ,
		(LONG)HD_PRIORITY ,
		(LONG)proc_seg_list >> 2L ,
		(LONG)HD_STACKSIZE );

	if ( process == NULL ) {
		free_cache ();
		wd_close ();
		DeletePort ( port );
		return ( NULL );
	}

	/* actually, CreateProc returns a pointer to the message port */
	/* to use with C, must adjust to start of procedure */

	process = PROCPTR ( process );

	/* ok, now anything to do with controller is for subtask. */
	/* When wd_open was called, it initialized the interrupts to */
	/* send an interrupt to this task. Now interrupts should be */
	/* sent to the subprocess. */

	wd_subtask ( &process->pr_Task );

	/* similarly, the port should send message port signals to the task */

	port->mp_SigTask = &process->pr_Task;

	/* initialize all units */

	for ( i = 0; i < HD_NUMUNITS; i++ ) {

		unit = &dev->hd_Unit[i];

		unit->hdu_Unit.unit_OpenCnt = 0;
		unit->hdu_Unit.unit_MsgPort = port;
		unit->hdu_UnitNum = i;
		unit->hdu_Task = &process->pr_Task;	/* shared */
	}
	return ( (LONG)dev );
}



/* actually, cOpen does not need to return anything, but libraries do, */
/* so I am returning the device pointer just to be safe */

struct hd_dev *
cOpen ( dev , ior , unit_num , flags )
struct hd_dev *dev;
struct IOExtHD *ior;
LONG unit_num;
ULONG flags;
{
	register struct hd_unit *unit;


	ior->iohd_TD.iotd_Req.io_Error = 0;

	/* check for legal unit number */

	if ( unit_num < 0  ||  unit_num >= HD_NUMUNITS ) {
		ior->iohd_TD.iotd_Req.io_Error = TDERR_BadUnitNum;
		return ( NULL );
	}

	unit = &dev->hd_Unit[ unit_num ];

	/* set up request, unit, open counts, and we no longer want to expunge */
	/* the library when all closes are done */

	ior->iohd_TD.iotd_Req.io_Unit = &unit->hdu_Unit;
	dev->hd_Flags &= ~ (ULONG) LIBF_DELEXP;
	dev->hd_Dev.dd_Library.lib_OpenCnt++;
	unit->hdu_Unit.unit_OpenCnt++;
	return ( dev );
}


BPTR		/* seg list */
cClose ( dev , ior )
struct hd_dev *dev;
struct IOExtHD *ior;
{

	ior->iohd_TD.iotd_Req.io_Error = 0;

	if ( --(ior->iohd_TD.iotd_Req.io_Unit->unit_OpenCnt) == 0 ) {

		/* expunge unit */

		/* well, actually we have only one task for all units so dont */
		/* kill the unit task */
	}

	/* clear out ior so that it cant be accidently reused */

	ior->iohd_TD.iotd_Req.io_Unit = NULL;
	ior->iohd_TD.iotd_Req.io_Device = NULL;

	/* one less person using the device driver */

	if ( --(dev->hd_Dev.dd_Library.lib_OpenCnt) <= 0
	&&  ( dev->hd_Flags & LIBF_DELEXP ) )

		/* no one has us open anymore and we have a delayed expunge */
		/* command pending, so do it! */

		return ( cExpunge ( dev ) );

	return ( NULL );
}


BPTR		/* seg list */
cExpunge ( dev )
struct hd_dev *dev;
{
	BPTR seg_list;

	if ( dev->hd_Dev.dd_Library.lib_OpenCnt > 0 ) {

		/* someone still has the library open - mark that should be */
		/* deleted when everyone has closed us */

		dev->hd_Flags |= LIBF_DELEXP;
		return ( NULL );
	}

	/* stop anyone else from opening the device */

	Remove ( dev );

	free_cache ();
	wd_close ();
	DeletePort ( port );
	CloseLibrary ( DOSBase );
	seg_list = dev->hd_SegList;

	FreeMem ( ((char *)dev) - dev->hd_Dev.dd_Library.lib_NegSize ,
		(LONG) dev->hd_Dev.dd_Library.lib_NegSize
		+ (LONG) dev->hd_Dev.dd_Library.lib_PosSize );

	return ( seg_list );
}



void
cBeginIO ( dev , ior )
struct hd_dev *dev;
struct IOExtHD *ior;
{

	ior->iohd_TD.iotd_Req.io_Error = 0;


	/* If some commands can be done very quickly, then a call to */
	/* perform_io () could be made. Care must be taken to ensure */
	/* that the disk driver task is not in perform_io () at the */
	/* same time so some global lock variable should be used. */
	/* Also, if the device has been stopped with CMD_STOP, we should */
	/* not try to do the command immediately. When the command cannot */
	/* be done immediately, the requests must be queued by sending */
	/* them to the task. For safty, Disable() and Enable() may need to */
	/* be called to safely examine and modify global variables. */

	/* In the device driver, the status commands must always work */
	/* immediately and cannot do a Wait() as they are called from */
	/* interrupts too (and so use the interrupt stack). (Well, thats */
	/* what the manual said anyway). Since for a harddisk the code */
	/* is so trivial, it was repeated here rather than calling */
	/* perform_io(). */

	if ( ( ior->iohd_TD.iotd_Req.io_Command & ~(ULONG)TDF_EXTCOM ) > HD_LASTCOMM ) {
		ior->iohd_TD.iotd_Req.io_Error = IOERR_NOCMD;
		return;
	}

	switch ( (int) ior->iohd_TD.iotd_Req.io_Command ) {

	case TD_CHANGENUM :
		ior->iohd_TD.iotd_Req.io_Actual = CHANGE_COUNT;
		break;

	case TD_CHANGESTATE :
		ior->iohd_TD.iotd_Req.io_Actual = 0;
		break;

	case TD_PROTSTATUS :
		ior->iohd_TD.iotd_Req.io_Actual = 0;
		break;

	default :
		/* clear quick flag to say that message is going to be queued */

		ior->iohd_TD.iotd_Req.io_Flags &= ~ (ULONG) IOF_QUICK;
		PutMsg ( ior->iohd_TD.iotd_Req.io_Unit->unit_MsgPort , ior );
		break;
	}
}



void
cAbortIO ( dev , ior )
struct hd_dev *dev;
struct IOExtHD *ior;
{

	ior->iohd_TD.iotd_Req.io_Error = 0;

	/* can any of the commands be aborted? */
}

