
/*
 *FUNC= IoCreate	D0/D1/A0
 *FUNC= IoDelete	D0
 *FUNC= IoOpen		D0/D1/A0
 *FUNC= IoClose 	A0
 ;FUNC= IoCtl		D0/D1/D2/A0
 *FUNC= NULL		-
 *FUNC= NULL		-
 *FUNC= NULL		-
 *FUNC= NULL		-
 *FUNC= NULL		-
 *FUNC= NULL		-
 *FUNC= NULL		-
 */

#include <local/typedefs.h>
#include <local/ioctl.h>

typedef long	(*FPTR)();

#define HAN struct _HAN
#define MHAN struct _MHAN

#define HANSIZE     8

HAN {
    long    Reserved;
    MHAN    *MHan;		/*  Related MHandle	    */
		    /*	USER STRUCTURE	*/
};

MHAN {
    NODE    Node;
    long    (*IoCtlFunc)();     /*  Control Function / NULL */
    uword   Refs;		/*  Total References	    */
    uword   HanSize;		/*  Size of a HANdle	    */
};

#asm

MHA_IOCTLFUNC	equ	14

#endasm

static MLIST  MList = { (MNODE *)&MList.mlh_Tail, NULL, (MNODE *)&MList.mlh_Head };
static long   Lock[2] = { 0,0 };

lIoCreate(name, func, hansize)
char *name;
long (*func)();
{
    register MHAN *mh;
    long res = -1;

    hansize += HANSIZE;
    LockAddr(Lock);
    for (mh = GetHead(&MList); mh; mh = GetSucc(mh)) {
	if (strcmp(name, mh->Node.ln_Name) == 0)
	    goto fail;
    }
    if ((mh = AllocMem(sizeof(MHAN), MEMF_PUBLIC|MEMF_CLEAR)) == NULL)
	goto fail;
    if ((mh->Node.ln_Name = AllocMem(strlen(name)+1, MEMF_PUBLIC)) == NULL) {
	FreeMem(mh, sizeof(MHAN));
	goto fail;
    }
    strcpy(mh->Node.ln_Name, name);
    mh->IoCtlFunc = func;
    mh->HanSize = HANSIZE + hansize;
    AddHead(&MList, mh);
    fioctl(func, _IOC_CREATE, NULL, 0);
    res = 0;
fail:
    UnLockAddr(Lock);
    return(res);
}

/*
 *  Delete an IO device.  The device's name is removed from the nameslist
 *  and any further non-internal IoCtl's on active connections will return
 *  -1.
 */

lIoDelete(name)
char *name;
{
    register MHAN *mh;
    long (*func)();

    LockAddr(Lock);
    for (mh = GetHead(&MList); mh; mh = GetSucc(mh)) {
	if (mh->Node.ln_Name && strcmp(name, mh->Node.ln_Name) == 0)
	    break;
    }
    if (!mh) {
	UnLockAddr(mh);
	return(-1);
    }

    /*	Prevent further opens & flag for removal	*/

    FreeMem(mh->Node.ln_Name, strlen(mh->Node.ln_Name)+1);
    mh->Node.ln_Name = NULL;
    func = mh->IoCtlFunc;
    mh->IoCtlFunc = NULL;

    /*	Call _IOC_DELETE if no more references & delete */

    UnLockAddr(Lock);
    fioctl(func, _IOC_DELETE, NULL, 0);

    if (mh->Refs == 0) {
	Remove(mh);
	FreeMem(mh, sizeof(MHAN));
	return(0);
    }
    return(1);
}

HAN *
lIoOpen(name, arg1, arg2)
char *name;
ulong arg1, arg2;
{
    register MHAN *mh;
    register HAN *han;

    LockAddr(Lock);
    for (mh = GetHead(&MList); mh; mh = GetSucc(mh)) {
	if (mh->Node.ln_Name && strcmp(name, mh->Node.ln_Name) == 0)
	    break;
    }
    if (!mh) {
	UnLockAddr(Lock);
	return(NULL);
    }
    if (!(han = AllocMem(mh->HanSize, MEMF_CLEAR|MEMF_PUBLIC))) {
	UnLockAddr(Lock);
	return(NULL);
    }
    ++mh->Refs;
    UnLockAddr(Lock);
    han->MHan = mh;
    if (IoCtl(han, _IOC_OPEN, arg1, arg2) == -1) {
	FreeMem(han, mh->HanSize);
	LockAddr(Lock);
	if (--mh->Refs == 0 && mh->Node.ln_Name == NULL) {
	    Remove(mh);
	    FreeMem(mh, sizeof(MHAN));
	}
	UnLockAddr(Lock);
	return(NULL);
    }
    UnLockAddr(Lock);
    return(han);
}

lIoClose(han)
register HAN *han;
{
    register MHAN *mh = han->MHan;
    long hansize = mh->HanSize;

    IoCtl(han, _IOC_CLOSE, NULL, NULL);        /*  finishup    */
    LockAddr(Lock);
    if (--mh->Refs == 0 && mh->Node.ln_Name == NULL) {
	Remove(mh);
	FreeMem(mh, sizeof(MHAN));
    }
    UnLockAddr(Lock);
    FreeMem(han, hansize);
    return(0);
}

#asm
	    ;	IoCtl(han:D0, cmd:D1, buf:D2, bytes:A0)
	    ;
	    ;	(1) Call IoctlFunc if it exists,    (*func)(han,cmd,buf,bytes)
	    ;	(2) else call internal function or if IoctlFunc returns -1

	    public  _lIoCtl
	    public  _AutoFunc

_lIoCtl:
	    addq.l  #8,D0			    ; skip handle header
	    movem.l D2/D3/A4/A5/A6,-(sp)            ; C compatibility
	    movem.l D0/D1/D2/A0,-(sp)               ; function call params
	    move.l  D0,A0
	    move.l  -4(A0),A0                       ; A0 = master handle
	    move.l  MHA_IOCTLFUNC(A0),A1            ; A1 = function to call
	    move.l  A1,D0			    ; NULL function
	    beq     .iocn
	    jsr     (A1)
	    cmp.l   #-1,D0			    ; result -1 ?
	    bne     .iocr
.iocn	    lea     _AutoFunc,A1		    ; error, try internal func
	    jsr     (A1)
.iocr	    lea     16(sp),sp
	    movem.l (sp)+,D2/D3/A4/A5/A6
	    rts

	    ;	fioctl(func, cmd, buf1, buf2) .. called as: (NULL,cmd,buf1,buf2)
	    ;	(i.e. handleless)

_fioctl:    movem.l 4(sp),D0/D1/A0/A1       ; get stack args
	    movem.l D2/D3/A4/A5/A6,-(sp)    ; save regs that might get trashed
	    move.l  D0,A6		    ; function to call
	    moveq.l #0,D0		    ; (usually is the handle)
	    movem.l D0/D1/A0/A1,-(sp)       ; push args
	    jsr     (A6)                    ; make call
	    lea     16(sp),sp               ; pop args
	    movem.l (sp)+,D2/D3/A4/A5/A6    ; restore regs
	    rts

#endasm

AutoFunc(han, cmd, arg1, arg2)
register HAN *han;
ulong cmd, arg1, arg2;
{
    han = (HAN *)((char *)han - 8);

    switch(cmd) {
    case IOC_GETFUNC:
	return ((long)han->MHan->IoCtlFunc);
    case IOC_SETFUNC:
	han->MHan->IoCtlFunc = (FPTR)arg1;
	return(0);
    case IOC_DEVCLAS:
	return(0);
    case IOC_DUP:
	{
	    register long hansize = han->MHan->HanSize;
	    register HAN *new = AllocMem(hansize, MEMF_PUBLIC);
	    register long res;
	    if (new) {
		BMov(han, new, hansize);
		res = IoCtl(new, _IOC_DUP, arg1, arg2);
		if (res != -1)
		    return((long)new);
		FreeMem(new, hansize);
	    }
	}
	break;
    }
    return(-1);
}

