/* 
 * Mach Operating System
 * Copyright (c) 1991,1990 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 * 
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 * 
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 *	File: rz_cpu.c
 * 	Author: Alessandro Forin, Carnegie Mellon University
 *	Date:	7/91
 *
 *	Top layer of the SCSI driver: interface with the MI.
 *	This file contains operations specific to CPU-like devices.
 *
 * We handle here the case of simple devices which do not use any
 * sophisticated host-to-host communication protocol, they look
 * very much like degenerative cases of TAPE devices.
 *
 * For documentation and debugging, we also provide code to act like one.
 */

#include <mach/std_types.h>
#include <scsi/compat_30.h>

#include <scsi/scsi.h>
#include <scsi/scsi_defs.h>
#include <scsi/rz.h>

#if  (NSCSI > 0)

void sccpu_act_as_target(); /* forwards */
void sccpu_start();

/*
 * This function decides which 'protocol' we well speak
 * to a cpu target. For now the decision is left to a
 * global var. XXXXXXX
 */
extern scsi_devsw_t scsi_host;
scsi_devsw_t	*scsi_cpu_protocol = /* later &scsi_host*/
			&scsi_devsw[SCSI_CPU];

void sccpu_new_initiator(self, initiator)
	target_info_t	*self, *initiator;
{
	initiator->dev_ops = scsi_cpu_protocol;
	if (initiator == self) {
		self->flags = TGT_DID_SYNCH|TGT_FULLY_PROBED|TGT_ONLINE|
			     TGT_ALIVE|TGT_US;
		self->dev_info.cpu.req_pending = FALSE;
	} else {
		initiator->flags = TGT_ONLINE|TGT_ALIVE;
		initiator->dev_info.cpu.req_pending = TRUE;
	}
}

void sccpu_strategy(ior)
	register io_req_t	ior;
{
	void			sccpu_start();

	rz_simpleq_strategy(ior, sccpu_start);
}

void sccpu_start(tgt, done)
	target_info_t	*tgt;
	boolean_t	done;
{
	io_req_t		head, ior;
	scsi_ret_t		ret;

	/* this is to the doc & debug code mentioned in the beginning */
	if (!done && tgt->dev_info.cpu.req_pending) {
		panic("sccpu_act_as_target called");
#if 0
		sccpu_act_as_target( tgt);
#endif
		return;
	}

	ior = tgt->ior;
	if (ior == 0)
		return;

	if (done) {

		/* see if we must retry */
		if ((tgt->done == SCSI_RET_RETRY) &&
		    ((ior->io_op & IO_INTERNAL) == 0)) {
			delay(1000000);/*XXX*/
			goto start;
		} else
		/* got a bus reset ? shouldn't matter */
		if ((tgt->done == (SCSI_RET_ABORTED|SCSI_RET_RETRY)) &&
		    ((ior->io_op & IO_INTERNAL) == 0)) {
			goto start;
		} else

		/* check completion status */

		if (tgt->cur_cmd == SCSI_CMD_REQUEST_SENSE) {
			scsi_sense_data_t *sns;

			ior->io_op = ior->io_temporary;
			ior->io_error = D_IO_ERROR;
			ior->io_op |= IO_ERROR;

			sns = (scsi_sense_data_t *)tgt->cmd_ptr;
			if (scsi_debug)
				scsi_print_sense_data(sns);

			if (scsi_check_sense_data(tgt, sns)) {
			    if (sns->u.xtended.ili) {
				if (ior->io_op & IO_READ) {
				    int residue;

				    residue =	sns->u.xtended.info0 << 24 |
						sns->u.xtended.info1 << 16 |
						sns->u.xtended.info2 <<  8 |
						sns->u.xtended.info3;
				    if (scsi_debug)
					printf("Cpu Short Read (%d)\n", residue);
				    /*
				     * NOTE: residue == requested - actual
				     * We only care if > 0
				     */
				    if (residue < 0) residue = 0;/* sanity */
				    ior->io_residual += residue;
				    ior->io_error = 0;
				    ior->io_op &= ~IO_ERROR;
				    /* goto ok */
				}
			    }
			}
		}

		else if (tgt->done != SCSI_RET_SUCCESS) {

		    if (tgt->done == SCSI_RET_NEED_SENSE) {

			ior->io_temporary = ior->io_op;
			ior->io_op = IO_INTERNAL;
			if (scsi_debug)
				printf("[NeedSns x%x x%x]", ior->io_residual, ior->io_count);
			scsi_request_sense(tgt, ior, 0);
			return;

		    } else if (tgt->done == SCSI_RET_RETRY) {
			/* only retry here READs and WRITEs */
			if ((ior->io_op & IO_INTERNAL) == 0) {
				ior->io_residual = 0;
				goto start;
			} else{
				ior->io_error = D_WOULD_BLOCK;
				ior->io_op |= IO_ERROR;
			}
		    } else {
			ior->io_error = D_IO_ERROR;
			ior->io_op |= IO_ERROR;
		    }
		}

		if (scsi_debug)
			printf("[Resid x%x]", ior->io_residual);

		/* dequeue next one */
		head = ior;

		simple_lock(&tgt->target_lock);
		ior = head->io_next;
		tgt->ior = ior;
		if (ior)
			ior->io_prev = head->io_prev;
		simple_unlock(&tgt->target_lock);

		iodone(head);

		if (ior == 0)
			return;
	}
	ior->io_residual = 0;
start:
	if (ior->io_op & IO_READ) {
		ret = scsi_receive( tgt, ior );
	} else if ((ior->io_op & IO_INTERNAL) == 0) {
		ret = scsi_send( tgt, ior );
	}
}


#if 0
/* XX turned off this code because it's impossible
   to reference 'end' and other such magic symbols
   from boot modules. */
/*
 * This is a simple code to make us act as a dumb
 * processor type.  Use for debugging only.
 */
static struct io_req	sccpu_ior;
vm_offset_t		sccpu_buffer; /* set this with debugger */

void sccpu_act_as_target(self)
	target_info_t	*self;
{
	static char	inq_data[] = "\3\0\1\0\040\0\0\0Mach3.0 Processor Link  v0.1";
	static char	sns_data[] = "\160\0\0\0\0\0\0\0\0";

	self->dev_info.cpu.req_pending = FALSE;
	sccpu_ior.io_next = 0;
#define MAXSIZE 1024*64
	sccpu_ior.io_count = (MAXSIZE < self->dev_info.cpu.req_len) ?
		MAXSIZE : self->dev_info.cpu.req_len;

	switch (self->dev_info.cpu.req_cmd) {
	case SCSI_CMD_INQUIRY:
		sccpu_ior.io_data = inq_data; break;
	case SCSI_CMD_REQUEST_SENSE:
		sccpu_ior.io_data = sns_data; break;
	default:
		if (sccpu_buffer == 0) {
		    /* ( read my lips :-) */
		    /* extern char end[]; */
		    sccpu_buffer = trunc_page(kalloc(MAXSIZE));
		}
		sccpu_ior.io_data = (char*)sccpu_buffer; break;
	}

	if (self->dev_info.cpu.req_cmd == SCSI_CMD_SEND) {
		self->cur_cmd = SCSI_CMD_READ;
		sccpu_ior.io_op = IO_READ;
	} else {
		self->cur_cmd = SCSI_CMD_WRITE;
		sccpu_ior.io_op = IO_WRITE;
	}
	self->ior = &sccpu_ior;
}
#endif

/*#define PERF*/
#ifdef	PERF
int test_read_size = 512;
int test_read_nreads = 1000;
int test_read_bdev = 0;
int test_read_or_write = 1;

#include <sys/time.h>
#include <machine/machspl.h>	/* spl */

test_read(max)
{
	int i, ssk, usecs;
	struct timeval start, stop;
	spl_t s;

	if (max != 0)
		test_read_nreads = max;

	s = spl0();
	start = time;
	if (test_read_or_write) read_test(); else write_test();
	stop = time;
	splx(s);

	usecs = stop.tv_usec - start.tv_usec;
	if (usecs < 0) {
		stop.tv_sec -= 1;
		usecs += 1000000;
	}
	printf("Size %d count %d time %3d sec %d us\n",
			test_read_size, test_read_nreads,
			stop.tv_sec - start.tv_sec, usecs);
}

read_test()
{
	struct io_req	io, io1;
	register int 	i;

	bzero(&io, sizeof(io));
	io.io_unit = test_read_bdev;
	io.io_op = IO_READ;
	io.io_count = test_read_size;
	io.io_data = (char*)sccpu_buffer;
	io1 = io;

	sccpu_strategy(&io);
	for (i = 1; i < test_read_nreads; i += 2) {
		io1.io_op = IO_READ;
		sccpu_strategy(&io1);
		iowait(&io);
		io.io_op = IO_READ;
		sccpu_strategy(&io);
		iowait(&io1);
	}
	iowait(&io);
}

write_test()
{
	struct io_req	io, io1;
	register int 	i;

	bzero(&io, sizeof(io));
	io.io_unit = test_read_bdev;
	io.io_op = IO_WRITE;
	io.io_count = test_read_size;
	io.io_data = (char*)sccpu_buffer;
	io1 = io;

	sccpu_strategy(&io);
	for (i = 1; i < test_read_nreads; i += 2) {
		io1.io_op = IO_WRITE;
		sccpu_strategy(&io1);
		iowait(&io);
		io.io_op = IO_WRITE;
		sccpu_strategy(&io);
		iowait(&io1);
	}
	iowait(&io);
}

tur_test()
{
	struct io_req	io;
	register int 	i;
	char		*a;
	struct timeval start, stop;
	spl_t s;
	target_info_t	*tgt;

	bzero(&io, sizeof(io));
	io.io_unit = test_read_bdev;
	io.io_data = (char*)&io;/*unused but kernel space*/

	rz_check(io.io_unit, &a, &tgt);
	s = spl0();
	start = time;
	for (i = 0; i < test_read_nreads; i++) {
		io.io_op = IO_INTERNAL;
		scsi_test_unit_ready(tgt,&io);
	}
	stop = time;
	splx(s);
	i = stop.tv_usec - start.tv_usec;
	if (i < 0) {
		stop.tv_sec -= 1;
		i += 1000000;
	}
	printf("%d test-unit-ready took %3d sec %d us\n",
			test_read_nreads,
			stop.tv_sec - start.tv_sec, i);
}

/*#define	MEM_PERF*/
#ifdef	MEM_PERF
int mem_read_size = 1024; /* ints! */
int mem_read_nreads = 1000;
volatile int *mem_read_address = (volatile int*)0xb0080000;
volatile int *mem_write_address = (volatile int*)0xb0081000;

mem_test(max, which)
{
	int i, ssk, usecs;
	struct timeval start, stop;
	int (*fun)(), mwrite_test(), mread_test(), mcopy_test();
	spl_t s;

	if (max == 0)
		max = mem_read_nreads;

	switch (which) {
	case 1:	fun = mwrite_test; break;
	case 2:	fun = mcopy_test; break;
	default:fun = mread_test; break;
	}

	s = spl0();
	start = time;
	for (i = 0; i < max; i++)
		(*fun)(mem_read_size);
	stop = time;
	splx(s);

	usecs = stop.tv_usec - start.tv_usec;
	if (usecs < 0) {
		stop.tv_sec -= 1;
		usecs += 1000000;
	}
	printf("Size %d count %d time %3d sec %d us\n",
			mem_read_size*4, max,
			stop.tv_sec - start.tv_sec, usecs);
}

mread_test(max)
	register int max;
{
	register int 	i;
	register volatile int *addr = mem_read_address;

	for (i = 0; i < max; i++) {
		register int j = *addr++;
	}
}
mwrite_test(max)
	register int max;
{
	register int 	i;
	register volatile int *addr = mem_read_address;

	for (i = 0; i < max; i++) {
		*addr++ = i;
	}
}

mcopy_test(max)
	register int max;
{
	register volatile int *from = mem_read_address;
	register volatile int *to = mem_write_address;
	register volatile int *endaddr;

	endaddr = to + max;
	while (to < endaddr)
		*to++ = *from++;

}
#endif	/*MEM_PERF*/

#endif	/*PERF*/

#endif  /* NSCSI > 0 */