#define	ASCII_DCB_CERR
#include <sys/types.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/ggio.h>
#include <setjmp.h>
#include <ctype.h>

int	cmd_setctu(),	cmd_slctmem(),	cmd_slctdcb(),	cmd_filldcb(),
	cmd_senddcb(),	cmd_exammem(),	cmd_fmtdk(),	cmd_cleardk(),
	cmd_reset(),	cmd_dotur(),	cmd_reqsense(),	cmd_exit();

struct commands {
	char	*cmd_name;
	int	(*cmd_exec)();
} diag[] = {
    /* Commands which require parameters */
	"Select controller/target/lun",			cmd_setctu,
	"Select DCB",					cmd_slctdcb,
	"Fill in DCB",					cmd_filldcb,
	"Select memory block",				cmd_slctmem,
	"Examine/Modify memory block",			cmd_exammem,
	"Fill/verify entire disk",			cmd_cleardk,

#define	MENU_SPLIT	6
    /* Commands which require NO parameters */
	"Execute DCB",					cmd_senddcb,
	"Do Test Unit Ready",				cmd_dotur,
	"Do Request Sense",				cmd_reqsense,
	"Format disk",					cmd_fmtdk,
	"Reset SCSI bus",				cmd_reset,
	"Exit",						cmd_exit,
#define	MENU_END	12

	0,						0
};

char	*device = "gg(0,0)";
#define	DEV_CTLR	3
#define	DEV_TARG	5
int	controller, target, lun;
int	tf = -1;
int	active_mem, active_dcb;
#define	NDCB		8
struct hacb_dcb	dcb_mem[NDCB];
struct hacb_dcb tdcb_mem;

struct {
	u_int	len;
	u_char	*addr;
	u_char	mem[0x1000];
} mem[8];

char	buf[120];
jmp_buf menu_display;

#define	GETFIELD(s, v, min, max)	v = getfield(s, v, min, max)

main()
{
	register int i, test;

	for (i = 0; i < 8; i++) {
	    mem[i].addr = mem[i].mem;
	    mem[i].len = 0x1000;
	}
	for (i = 0; i < NDCB; i++)
	    dcb_mem[i].dcb_nodisc = 1;
	opendevice();
	setjmp(menu_display);

	while (1) {
	    printf("\nState: Controller %c Target %c Lun %d: MEM %x DCB %x\n",
		device[DEV_CTLR], device[DEV_TARG], lun, 
		active_mem, active_dcb);
	    for (i = 0 ; i < MENU_SPLIT; i++ ) {
		printf("    %c: %-35s", 'a' + i, diag[i].cmd_name);
		if (diag[i + MENU_SPLIT].cmd_name)
		    printf("    %c: %s", 'a' + i + MENU_SPLIT, 
			diag[i + MENU_SPLIT].cmd_name);
		printf("\n");
	    }
    sel:    printf("\nSelect a function (a-%c): ", 'a' + i-1);
	    gets(buf);
	    test = buf[0] - 'a';
	    if (test < 0 || test >= MENU_END) {
		printf("Bad selection\n");
		goto sel;
	    }
	    (*(diag[test].cmd_exec))();
	}
}

cmd_exit()
{
	exit(0);
}

cmd_setctu()
{
	GETFIELD("Controller", controller, 0, 3);
	device[DEV_CTLR] = '0' + controller;
	GETFIELD("Target Device", target, 0, 7);
	device[DEV_TARG] = '0' + target;
	GETFIELD("Lun", lun, 0, 7);
	opendevice();
}

cmd_slctmem()
{
	GETFIELD("Active Memory Block", active_mem, 0, 7);
}

cmd_slctdcb()
{
	GETFIELD("Active DCB", active_dcb, 0, 7);
}

cmd_dotur()
{
	register struct hacb_dcb	*dcb = &tdcb_mem;

	SET_CDB_0(&dcb->dcb_cdb, CMD_TESTREADY, lun, 0, 0, 0, 0);
	SET_DCB(dcb, 0, DCB_DIR_NO, 0, DCB_NODISC, sizeof(struct cdb_0), 0, 0,
		0, 0, 0, 0);
	if (sendcmd("TEST_UNIT_READY", dcb))
		return;
	if (dcb->dcb_scsi_status & SCSI_STATUS_CHECK)
		sendcmd("TEST_UNIT_READY again", dcb);
}

cmd_reqsense()
{
	register struct hacb_dcb	*dcb = &tdcb_mem;
	union scsi_sns			sns_data;
	register u_char			*p = (char *)&sns_data;
	register int			i;

	SET_CDB_0(&dcb->dcb_cdb, CMD_SENSE, lun, 0, sizeof(union scsi_sns),0,0);
	SET_DCB(dcb, 0, DCB_DIR_IN, DCB_SW, DCB_NODISC, sizeof(struct cdb_0),
		0, 0, 0, p, sizeof(union scsi_sns), 1);
	bzero(p, sizeof(union scsi_sns));
	if (sendcmd("REQUEST SENSE", dcb))
		return;
	printf("  SENSE DATA: ");
	for (i = 0 ; i < sizeof(union scsi_sns); i++) 
		printf("%2x ", *p++);
	printf("\n");
}

cmd_reset()
{
	register struct hacb_dcb	*dcb = &tdcb_mem;
	char				oldtarg;

	oldtarg = device[DEV_TARG];
	device[DEV_TARG] = '7';
	if (opendevice()) {
		SET_CDB_0(&dcb->dcb_cdb, CMD_RESET, 0, 0, 0, 0, 0);
		SET_DCB(dcb, 0, DCB_DIR_NO, 0, DCB_NODISC, sizeof(struct cdb_0),
			0, 0, 0, 0, 0, 0);
		sendcmd("RESET CONTROLLER", dcb);
	}
	device[DEV_TARG] = oldtarg;
	opendevice();
}

cmd_fmtdk()
{
	register struct hacb_dcb	*dcb = &tdcb_mem;

	SET_CDB_0(&dcb->dcb_cdb, CMD_FORMAT, lun, 0, 0, 0, 0);
	SET_DCB(dcb, 0, DCB_DIR_NO, 0, DCB_NODISC, sizeof(struct cdb_0), 0, 0,
		0, 0, 0, 0);
	if (getfield("This will overwrite the disk!! Proceed? (Yes = 1)",
	    0, 0, 0))
		sendcmd("FORMAT", dcb);
}

cmd_cleardk()
{
	register struct hacb_dcb	*dcb = &tdcb_mem;
	register int			i, j;
	int				hexval, incre, lba, dir;

	dir = getfield("Do write or read/verify (1 = write)", 0, 0, 0);
	if (dir && getfield("This will overwrite the disk!! Proceed? (Yes = 1)",
	     0, 0, 0) == 0)
		return;
	incre = getfield("Number of sectors per op", 0x200, 0, 0);
	hexval = getfield("Hex value", 0, 0, 0);
	if (0x200 * incre > 0x1000)
		printf("  alloc failed\n");
		return;
	}
	mem[active_mem].len = 0x200 * incre;
	bzero(mem[active_mem].addr, 0x200 * incre);
	if (dir)
		for (i=0;i<0x200 * incre;i++)
			*(mem[active_mem].addr+i) = hexval;

	for (lba = 0 ;; lba += incre) {
	    printf("  %s 0x%x sectors starting at lba %x...\n",
		dir ? "filling" : "comparing", incre, lba);
	    SET_CDB_1(&dcb->dcb_cdb, dir ? CMD_XWRITE : CMD_XREAD, lun, 0, lba,
		incre, 0, 0);
	    SET_DCB(dcb, 0, dir, DCB_NOSW, DCB_NODISC, sizeof(struct cdb_0), 0,
		 0, 0, mem[active_mem].addr, incre * 0x200, 0x200);
	    if (sendcmd(dir ? "WRITE" : "READ", dcb))
		    return;
	    if (dcb->dcb_err == 0) {
		if (dir == 0) {
		    for (i=0;i<0x200 * incre;i++)
			if (*(mem[active_mem].addr+i) != hexval) {
			    printf("\n  COMPARE FAILED:");
			    printf("0x%x:", i);
			    for (j=i;j<i+16;j++)
				printf(" %02x", mem[active_mem].addr[j]);
			    printf("\n");
			    return(0);
			}
		}
		printf("\r");
	    } else if (dcb->dcb_scsi_status & SCSI_STATUS_CHECK) {
		printf("\n");
		incre /= 2;
		if (incre == 0) {
		    printf("\ndone\n");
		    return(0);
		}
	    } else {
		    printf("\nFatal Error - Operation Halted\n");
		    return(0);
	    }
	}
}

cmd_senddcb()
{
	sprintf(buf, "DCB %d", active_dcb);
	sendcmd(buf, &dcb_mem[active_dcb]);
}

cmd_filldcb()
{
	register struct hacb_dcb	*dcb = &dcb_mem[active_dcb];
	register int			i, cformat;

	printf("  DCB %d\n", active_dcb);
	cformat = getfield("Command group",dcb->dcb_cdb.cdb_0.cdb_0_cmd>>5,0,7);
	if (cformat == 0) {
	    struct cdb_0	*c = &dcb->dcb_cdb.cdb_0;

	    dcb->dcb_cdblen = 6;
	    GETFIELD("Command", c->cdb_0_cmd, 0, 0);
	    GETFIELD("Lun", c->cdb_0_lun, 0, 7);
	    i = getfield("lba", SCSI_HML(c->cdb_0_lba), 0, 0);
	    SCSI_HML_SET(c->cdb_0_lba, i);
	    GETFIELD("block count", c->cdb_0_len, 0, 0);
	} else if (cformat == 1) {
	    struct cdb_1	*c = &dcb->dcb_cdb.cdb_1;

	    dcb->dcb_cdblen = 10;
	    GETFIELD("Command", c->cdb_1_cmd, 0, 0);
	    GETFIELD("Lun", c->cdb_1_lun, 0, 0);
	    GETFIELD("lba", c->cdb_1_lba, 0, 0);
	    i = getfield("block count", SCSI_HL(c->cdb_1_len), 0, 0);
	    SCSI_HL_SET(c->cdb_1_len, i);
	} else {
	    u_char		*c = &dcb->dcb_cdb.cdb_raw[0];

	    GETFIELD("CDB length", dcb->dcb_cdblen, 1, 16);
	    for (i = 0; i < dcb->dcb_cdblen; i++) {
		sprintf(buf, "Command byte %d", i);
		GETFIELD(buf, c[i], 0, 0);
	    }
	}
	GETFIELD("Data Length", dcb->dcb_dlen, 0, 0);
	GETFIELD("Prohibit Disconnect", dcb->dcb_nodisc, 0, 0);
	if (dcb->dcb_dlen) {
	    if (dcb->dcb_dlen != mem[active_mem].len) {
		if (dcb->dcb_dlen > 0x1000) {
		    printf("alloc failed\n");
		    return;
		}
		mem[active_mem].len = dcb->dcb_dlen;
		dcb->dcb_dadr = mem[active_mem].addr;
	    }
	    GETFIELD("Data Direction (to unit = 1)", dcb->dcb_dir, 0, 0);
	    GETFIELD("Swap Bytes (Yes = 1)", dcb->dcb_sw, 0, 0);
	    GETFIELD("Data BlockSize", dcb->dcb_dbsz, 0, 0);
	}
}

cmd_exammem()
{
	register u_short	*a;
	register int		offs, count;

	printf("  Memory Block %d\n", active_mem);
	offs = getfield("Relative address in block", 0, 0, 0);
	count = getfield("Number of words", mem[active_mem].len/2, 0, 0);
	if (offs > mem[active_mem].len)
		offs = mem[active_mem].len - 2;
	if (2*count+offs > mem[active_mem].len)
		count = (mem[active_mem].len - offs) >> 1;
	a = (u_short *)(mem[active_mem].addr + offs);
	for (;count>0;count--) {
		sprintf(buf, "0x%x", offs);
		*a = getfield(buf, *a, 0, 0);
		offs += 2;
		a++;
	}
}

opendevice()
{
	if (tf >= 0)
		close(tf);
	if ((tf = open(device, O_RDWR, 0)) < 0) {
		printf("  Open on %s failed\n", device);
		return 0;
	}
	return 1;
}

sendcmd(s, dcb)
	char				*s;
	register struct hacb_dcb	*dcb;
{
	int	cc;

	printf("  sending %s: Cont %c Target %c Lun %d ...", s,
	    device[DEV_CTLR], device[DEV_TARG], dcb->dcb_cdb.cdb_0.cdb_0_lun);
        fflush(stdout);
	sleep(1);
	if (cc = ioctl(tf, GGIOCCMD, dcb)) {
		printf("\n  %s: err=%d\n", device, cc);
		perror("  ioctl");
		return -1;
	}
	if (dcb->dcb_err == 0)
		printf("  OK\n");
	else {
	    printf("ERROR\n");
	    if (dcb->dcb_cerr)
		printf("  Controller Error: %x: %s\n",
			dcb->dcb_cerr, ascii_dcb_cerr[dcb->dcb_cerr]);
	    else if (dcb->dcb_scsi_status) {
		printf("  SCSI completion status %x: ", dcb->dcb_scsi_status);
		if (dcb->dcb_scsi_status & SCSI_STATUS_INTER)
			printf("INTER ");
		if (dcb->dcb_scsi_status & SCSI_STATUS_BUSY)
			printf("BUSY ");
		if (dcb->dcb_scsi_status & SCSI_STATUS_CM)
			printf("CM ");
		if (dcb->dcb_scsi_status & SCSI_STATUS_CHECK)
			printf("CHECK ");
		if (dcb->dcb_scsi_status & SCSI_STATUS_PARITY)
			printf("PARITY ");
		printf("\n");
	    }
	}
}

getfield(field, value, min, max)
	char	*field;
	u_int	value;
{
	register int	i = 0, j = 0, x = value;
	char		backspace[9];
	int		newvalue;

	x = value;
	i = 0;
	do {
		backspace[i] = '\b';
		i++;
		x >>= 4;
	} while (x);
	backspace[i] = 0;
range:	if (min != max)
		sprintf(buf,"%s (%d-%d)", field, min, max);
	else
		sprintf(buf,"%s", field, value, backspace);
	printf("    %30s: %x%s", buf, value, backspace);
	buf[0] = 0;
	gets(buf);
	if (buf[0] && !isxdigit(buf[0]))
		longjmp(menu_display, 1);
	if (sscanf(buf, "%x", &newvalue) > 0) {
		if (min != max && ((newvalue < min) || (newvalue > max)))
			goto range;
		value = x = newvalue;
		j = 0;
		do {
			j++;
			x >>= 4;
		} while (x);
		if (j < i)
			printf("    %30s: %x\n", " ", value);
	}
	return value;
}
