/*

  ASPI interface code.

  By John Wilson <wilson@dbit.com>.

  Copyright (C) 1999-2000 by Digby's Bitpile, Inc.  All rights reserved.
  This program may be freely copied as long as source code is available which
  includes this notice.

  02/07/1999	JMBW	Created.

*/

#include <dos.h>
#include <stdio.h>
#include <string.h>
#include "st.h"

/* set this flag to use ASPI async I/O */
#define ASYNC 0

#pragma pack (1)
/* DOS ASPI SCSI request block for SCSI I/O command */
typedef struct {
	byte	ascmd;		/* ASPI command */
	volatile byte assts;	/* ASPI status (written by ASPI manager) */
	byte	ashst;		/* host adapter # */
	byte	asflg;		/* SCSI request flags */
	byte	asrsvd1[4];	/* reserved for expansion */
	byte	astrg;		/* target # */
	byte	aslun;		/* LUN # */
	dword	aslen;		/* data allocation length */
	byte	assal;		/* SENSE allocation length */
	void far *asbuf;	/* 16:16 real mode FAR ptr to data buffer */
	void far *aslnk;	/* 16:16 real mode FAR ptr to next SRB */
	byte	ascdl;		/* CDB (command descriptor block) length */
	byte	ashas;		/* host adapter status */
	byte	astgs;		/* target status */
	void (far *aspst)();	/* 16:16 real mode FAR ptr to post routine */
	byte	asrsvd2[34];	/* reserved for ASPI work space */
#define ASCDBL 12		/* max length of CDB */
#define ASSNSL 18		/* length of sense data following CDB */
	byte	ascdb[ASCDBL+ASSNSL]; /* SCSI command descriptor block */
				/* followed by sense data */
} srb;

/* DOS ASPI SCSI request block for ABORT command */
typedef struct {
	byte	ascmd;		/* ASPI command */
	volatile byte assts;	/* ASPI status (written by ASPI manager) */
	byte	ashst;		/* host adapter # */
	byte	asflg;		/* SCSI request flags */
	byte	asrsvd1[4];	/* reserved for expansion */
	void far *asabo;	/* 16:16 real mode FAR ptr to SRB to abort */
} srbabort;

#ifndef __WATCOMC__
#define cdecl /**/
#endif

void cdecl (far *aspi)(void far *);  /* pointer to ASPI manager entry */

/* init our connection to ASPI */
void scsiinit()
{
	int fd;				/* file descriptor for ASPI mgr */
	union REGS in, out;		/* intdos() regs */

	/* open ASPI manager device driver */
	fd=open("\\DEV\\SCSIMGR$",0);
	if(fd==-1) {
		perror("Error opening SCSIMGR$");
		exit(1);
	}

	/* do DOS "IOCTL READ" to get entry address */
	in.x.dx=(unsigned short)&aspi;	/* variable must be in DGROUP */
	in.x.cx=4;			/* size of a DWORD pointer */
	in.x.bx=fd;			/* handle */
	in.x.ax=0x4402;			/* func=IOCTL READ */
	intdos(&in,&out);
	if(out.x.cflag||out.x.ax!=4) {	/* (error or didn't get 4 bytes) */
		fprintf(stderr,"?IOCTL read error\n");
		exit(1);
	}

	close(fd);
}

/* sort out the aftermath of a SCSI I/O command */
static int aspistatus(srb *s,scsireq *r)
{
	if(s->assts==0x01) return(0);  /* happy return */

	/* error of some kind */
	if(s->ashas)		/* host adapter problem */
		return(SCIOE);	/* generic I/O error */

	/* must be a target error, see if it's a check condition */
	if(s->astgs==0x02) {	/* check condition */
				/* copy sense data out of SRB */
				/* (use the smaller of the two sizes) */
		memcpy(r->srsns,s->ascdb+s->ascdl,
			(SRSNSL<ASSNSL)?SRSNSL:ASSNSL);

		/* check for a couple of important sense key values */
		switch(r->srsns[2]&0x0F) {  /* check sense key in sense data */
		case 0x02:	/* not ready */
			return(SCOFL);  /* say device offline */
		case 0x06:	/* unit attention */
			return(SCATN);
		default:	/* other checks are sorted out by caller */
			return(SCCHK);
		}
	}
	else return(SCIOE);	/* other target device problem */
}

volatile static int aspidone;	/* ASPI "done" flag (just for demo) */

/* "post" routine, called by ASPI manager on completion of ASPI command -- */
/* the point of all these nasty attributes is that the routine must save */
/* and restore all regs, the SRB is passed on the stack and cleared by the */
/* caller (rather than a "RETF 4" instruction), and DS is not known to point */
/* at DGROUP on entry */
#ifdef __WATCOMC__
#pragma aux aspipost parm [] modify [ax bx cx dx si di bp ds es];
#pragma aux aspipost parm caller;
static void _loadds far aspipost(srb far *s)
{
	aspidone=1;		/* tell mainline we're done */
}
#else
// maybe could do it with in-line assembly code?  ASPI spec says save/restore
// all regs, so this:
//	push	ax
//	push	ds
//	mov	ax,seg aspidone
//	mov	ds,ax
//	mov	aspidone,1
//	pop	ds
//	pop	ax
//	retf
// ought to do it, the problem I had with Watcom was that it was including a
// stack check that destroyed AX before it even got to my assembly code (and
// was wrong anyway since the call isn't made on our stack), but the #pragmas
// fix that by telling it that the argument is passed on the stack and all regs
// must be preserved. anyway make sure it's a FAR routine, it's supposed to
// return with RETF rather than RET
#endif

/* execute a SCSI command and wait for completion */
int xscsiw(scsireq *r)
{
	static srb s;

	s.ascmd=0x02;		/* ASPI cmd = send SCSI I/O command */
	s.assts=0;		/* just for neatness */
	s.ashst=r->srhst;	/* copy SCSI device name */
	s.astrg=r->srtrg;
	s.aslun=r->srlun;

	switch(r->srdir) {	/* sort out DMA direction */
	case +1:
		s.asflg=011;	/* read from device, post on completion */
		break;
	case -1:
		s.asflg=021;	/* write to device, post on completion */
		break;
	case 0:
		s.asflg=031;	/* no data phase, post on completion */
	}

	s.aslen=(dword)r->srsiz;  /* length of xfr (if any) */
	s.assal=ASSNSL;		/* sense allocation length (for check conds) */
	s.asbuf=r->srbuf;	/* buf addr */
	s.aslnk=(void far *)0;	/* no next SRB */
	s.ascdl=r->srcdl;	/* CDB length */
	memcpy(s.ascdb,r->srcdb,s.ascdl);  /* copy CDB (known to fit) */
	s.ascdb[1]=(s.ascdb[1]&037)|(s.aslun<<5);
				/* put LUN in 2nd byte of CDB */

	/* for now, this routine waits for completion */
	/* should have an async version too which calls back when done */
#if ASYNC  /// poll the flag set by the async post routine
	s.aspst=aspipost;	/* addr of "post" routine */
	aspidone=0;		/* not done yet */
	(*aspi)(&s);		/* call ASPI */
	while(!aspidone) ;	/* wait for aspipost() call */
#else	/// just poll the completion status (avoids upcall problems with C)
	s.asflg&=~1;		/* clear "post" bit after all */
	(*aspi)(&s);		/* start the transfer */
	while(!s.assts) ;	/* wait for non-zero completion code */
#endif

	return(aspistatus(&s,r));  /* sort out status */
}

/* abort an outstanding ASPI I/O command (currently not used) */
static void aspiabort(srb *r)
{
	static srbabort s;

	s.ascmd=0x03;		/* ASPI cmd = abort SCSI I/O command */
	s.assts=0;		/* just for neatness */
	s.ashst=r->ashst;	/* copy SCSI HA number */
	s.asflg=0;		/* no flags */
	s.asabo=r;		/* SRB addr */
	(*aspi)(&s);		/* call ASPI */
	while(!r->assts) ;	/* wait for abort to take effect */
}

/* abort an outstanding SCSI command */
void scabo(scsireq *r)
{
//// should find SRB corresponding to scsireq block and aspiabort it
//// but I've only got one staticly allocated SRB, for now
}

