MODULE Adaptec7; (** AUTHOR "prk"; PURPOSE "Adaptec 7xxx SCSI driver"; *)

IMPORT
	SYSTEM, KernelLog, PCI, Machine, Kernel, Objects, Modules,
	SCSI, Script := Adaptec7Script;

CONST
		(*debug flags*)
	traceCalls = 0;
	traceInts = 1;
	traceMsg = 2;
	traceSequencer = 4;
	traceReset = 5;
	traceConfig = 6;
	tracePeriod = 7;
	traceCmds = 8;
	traceSeeprom = 9;
	traceTermination = 10;

	traceSmall = 16;
	traceAIC7880 = 17;
	traceAIC7890 = 18;
	traceAHA2940 = 19;
	traceNoHandling = 20;
	(*traceDog = 21;*)
	traceNoRegister = 22;
	traceSync = 23;

	(* Buffer sizes*)
	QBufferSize = 3 * 256;
	SCBBufferSize = 128*32;
	BufferAlignment = 32;

	(* Message.Dir *)
	Sending = 0; Receiving = 1;

	(* Bus.sync Command Synchronization *)
	Free = 0;  InUse = 1;  Terminated = 2;

		(*Bus.chip: enumeration   *)
	AIC7770 = 1; AIC7850 = 2; AIC7860 = 3; AIC7870 = 4; AIC7880 = 5; AIC7890 = 6;
	AIC7895 = 7; AIC7896 = 8;

		(*Bus.feature   *)
	FeatUltra = 1; FeatUltra2 = 2; FeatWide = 3; FeatTwin = 4; FeatMoreSRAM = 5; FeatCmdChan = 6;
	FeatQueueRegs = 7; FeatSGPreload = 8; FeatSpioCap = 9;

		(*Bus.flag  *)
	FlagPageSCB = 1; FlagNewEepromFMT = 2; FlagBiosEnabled = 3; FlagMultiChannel = 4; FlagChnlB = 5; FlagChnlC = 6;
	FlagSeepromFound = 7; FlagChannelBPrimary = 8; FlagExtendTransA = 9; FlagExternalSRam = 10; FlagAbortPending = 11;
	FlagHandlingReqInits = 12;

		(*HSCB Control *)
	HwScbTypeMask = 03X; HwScbMsgMask = 0F6X;
	HwScbDisconnected = 3; HwScbDiscEnable = 6; HwScbMessage = 7;

(*SCSI Register Block *)
	(* 00H	SCSI Sequencer Control *)	SCSISEQ = 00H;
		(* Bit 0	SCSI Reset Out *)		SCSIRSTO = 0;
		(* Bit 1	Enable Auto ATN on parity *)		ENAUTOATNP = 1;
		(* Bit 4	Enable Reselection *)		ENRSELI = 4;
		(* Bit 5	Enable Selection in *)		ENSELI = 5;
		(* Bit 6	Enable Selection out *)		ENSELO = 6;
	(* 01H	SCSI Transfer Control 0 Register *)	SXFRCTL0 = 01H;
		(* Bit 3	SCSI PIO Enable *)		SPIOEN = 3;
		(* Bit 5	*)		FAST20 = 5;
		(* Bit 7	Digital Filtering On *)		DFON = 7;
	(* 02H	SCSI Transfer Control 1 Register *)	SXFRCTL1 = 02H;
		(* Bit 0	SCSI Termination Power Enable *)		STPWEN = 0;
		(* Bit 1	Active Negation Enable *)		ACTNEGEN = 1;
		(* Bit 2	Enable Selection timeout *)		ENSTIMER = 2;
		(* Bit 5	Enable Parity Check *)		ENSPCHK = 5;
	(* 03H	SCSI Control Signal *)	SCSISIG = 03H;
		(* Bit 0	Ack *)		ACK = 0;
		(* Bit 1	*)		REQ = 1;
		(* Bit 2	*)		BSY = 2;
		(* Bit 3	*)		SEL = 3;
		(* Bit 4	*)		ATN = 4;
		(* Bit 5	*)		MSG = 5;
		(* Bit 6	*)		IO = 6;
		(* Bit 7	*)		CD = 7;
	(* 04H	SCSI Rate Control *)	SCSIRATE = 04H;
		(* Bit 7	Wide Transer control *)		WIDEXFER = 7;
	(* 05H	SCSI ID *)	SCSIID = 05H;
					SCSIOFFSET = 05H;
	(* 06H	SCSI Data Low *)	SCSIDATL = 06H;
	(* 0BH	SCSI Status 0 *)	SSTAT0 = 0BH;
	(* 0BH	Clear SCSI Interrupt 0 *)	CLRSINT0 = 0BH;
		(* Bit 1	CLRSPIORDY *)		CLRSPIORDY = 1;
		(* Bit 3	CLRSWRAP *)		CLRSWRAP = 3;
		(* Bit 4	CLRSELINGO *)		CLRSELINGO = 4;
		(* Bit 5	CLRSELDI *)		CLRSELDI = 5;
		(* Bit 6	CLRSELDO *)		CLRSELDO = 6;
	(* 0CH	SCSI Status 1 *)	SSTAT1 = 0CH;
		(* Bit 0	*)		REQINIT = 0;
		(* Bit 2	SCSI Parity Error *)		SCSIPERR = 2;
		(* Bit 3	Bus Free Flag *)		BUSFREE = 3;
		(* Bit 5	Scsi Reset *)		SCSIRSTI = 5;
		(* Bit 7	Selection Time Out *)		SELTO = 7;
	(* 0CH	Clear SCSI Interrupt 1 *)	CLRSINT1 = 0CH;
		(* Bit 0	CLRREQINIT *)		CLRREQINIT = 0;
		(* Bit 1	CLRPHASECHG *)		CLRPHASECHG = 1;
		(* Bit 2	CLRSCSIPERR *)		CLRSCSIPERR = 2;
		(* Bit 3	CLRBUSFREE *)		CLRBUSFREE = 3;
		(* Bit 5	CLRSCSIRSTI *)		CLRSCSIRSTI = 5;
		(* Bit 6	CLRATNO *)		CLRATNO = 6;
		(* Bit 7	CLRSELTIMEO *)		CLRSELTIMEO = 7;
						CLRSINT1ALL = {0..3, 5..7};
	(* 0DH	SCSI Status 2 *)	SSTAT2 = 0DH;
		(* Bit 4	SCSI Expander Active *)		EXPACTIVE = 4;
	(* 0EH	SCSI Status 3 *)	SSTAT3 = 0EH;
	(* 0FH	SCSI ID for Ultra2 Chips *)	SCSIIDULTRA2 = 0FH;
	(* 10H	SCSI Interrupt Mode 0 *)	SIMODE0 = 10H;

	(* 11H	SCSI Interrupt Mode 1 *)	SIMODE1 = 11H;
		(* Bit 1	Enable ReqInit *)		ENREQINIT = 0;
		(* Bit 2	Enable Scsi Parity Error *)		ENSCSIPERR = 2;
		(* Bit 3	Enable Bus Free *)		ENBUSFREE = 3;
		(* Bit 5	Enable Scsi Reset *)		ENSCSIRST = 5;
		(* Bit 7	Enable Time-out *)		ENSELTIMO = 7;
	(* 12H	SCSI Bus Low *)	SCSIBUSL = 12H;
	(* 1BH	Serial Port I/O Cabability register *)	SPIOCAP = 1BH;
		(* Bit 0	 Termination and cable detection *)		SSPIOCPS = 0;
	(* 1DH	Board Control *)	BRDCTL = 1DH;
		(* Bit 0	7890 only: Board Strobe *)		BRDSTBULTRA2 = 0;
		(* Bit 1	7890 only: Board Read/Write *)		BRDRWULTRA2 = 1;
		(* Bit 2	Board Read/Write *)		BRDRW = 2;
		(* Bit 3	Board Chip Select *)		BRDCS = 3;
		(* Bit 4	Board Strobe *)		BRDSTB = 4;
	(* 1EH	Serial EEPROM Control *)	SEECTL = 1EH;
		(* Bit 0	Serial EEPROM Data In *)		SEEDI = 0;
		(* Bit 1	Serial EEPROM Data Out *)		SEEDO = 1;
		(* Bit 2	Serial EEPROM Clock *)		SEECK = 2;
		(* Bit 3	Serial EEPROM Chip Select *)		SEECS = 3;
		(* Bit 4	Serial EEPROM Ready *)		SEERDY = 4;
		(* Bit 5	Serial EEPROM Mode Select *)		SEEMS = 5;
	(* 1FH	SCSI Block Control *)	SBLKCTL = 1FH;
		(* Bit 1	Select Wide *)		SELWIDE = 1;
		(* Bit 3	SELBUSB *)		SELBUSB = 3;
		(* Bit 3	LVD transceiver active *)		ENAB40 = 3;
		(* Bit 5	Auto Flush Disable *)		AUTOFLUSHDIS = 5;
		(* Bit 6	Diagnostic LED on *)		DIAGLEDON = 6;
		(* Bit 7	Diagnostic LED Enable *)		DIAGLEDEN =7;

(* Scratch RAM *)
	(* 20H	1 byte per target starting at this address for configuration values*)	TARGSCSIRATE = 20H;
	(* 30H	Bit vector of targets that have ULTRA enabled. *)	ULTRAENB = 30H;
 	(* 32H 	Bit vector of targets that have disconnection disabled*)	DISCDSB = 32H;
 	(* 34H	buffer to designate the type or message to send to a target*)	MSGOUT = 34H;
 	(* 35H	Parameters for DMA Logic*)	DMAPARAMS = 35H;
 	(* 36H	*)		SEQFLAGS = 36H;
 	(* 37H	SAVED_TCL*)	SAVEDTCL = 37H;
 	(* 38H	SG_COUNT*)
 	(* 39H	SG_NEXT*)
 	(* 3DH	LASTPHASE*)	LASTPHASE = 3DH;
 		(* Signals are declared in SCSISIG *)		PHASEMASK = {CD, MSG, IO};
 		(* Patterns*)
 		PhaseStatus = {CD, IO};
 		PhaseCommand = {CD};
 		PhaseMsgOut = {CD, MSG};
 		PhaseMsgIn = {CD, MSG, IO};
 		PhaseDataIn = {IO};
 		PhaseDataOut = {};
 		PhaseBusFree = {0};
 	(* 3EH	WAITINGSCBH*)	WAITINGSCBH = 3EH;
 	(* 3FH	DISCONNECTEDSCBH*)	DISCONNECTEDSCBH = 3FH;
 	(* 40H	head of list of SCBs that are not in use.  Used for SCB paging*)	FREESCBH = 40H;
 	(* 41H	HSCB_ADDR*)	HSCBARRAY = 41H;
 	(* 45H	SCBID_ADDR*)	SCBIDADDR = 45H;
 	(* 49H	TMODE_CMDADDR*)	TMODECMDADDR = 49H;
 	(* 4DH	KERNEL_QINPOS*)	KERNELQINPOS = 4DH;
 	(* 4EH	QINPOS*)	QINPOS = 4EH;
 	(* 4FH	QOUTPOS*)	QOUTPOS = 4FH;
 	(* 50H	TMODE_CMDADDR_NEXT*)	TMODECMDADDRNEXT = 50H;
 	(* 51H	ARG_1 / RETURN_1*)	RETURN1 = 51H;
 		(*	Phase Mismatch *)		MSGOUTPHASEMIS = {4};
 		(* 	Send Sense *)		SENDSENSE = {6};
 		(* 	Send Msg *)		SENDMSG = {7};
 	(* 52H	ARG_2 *)
 	(* 53H	LAST_MSG*)	LASTMSG = 53H;
 	(* 54H	PREFETCH_CNT*)

	(* 5AH	Scsi Configuration *)	SCSICONF = 5AH;
		(* Bit 6	Reset SCSI-Bus at boot *)		RESETSCSI = 6;
		(* Bit 7	Termination Enable *)		TERMENB = 7;
	(* 60H	Sequencer Control *)	SEQCTL = 60H;
		(* Bit 0	Load Ram *)		LOADRAM = 0;
		(* Bit 1	Reset *)		SEQRESET = 1;
		(* Bit 4	Fast Mode *)		FASTMODE = 4;
		(* Bit 5	Fail disable *)		FAILDIS = 5;
		(* Bit 7	Parity Error disable *)		PERRORDIS = 7;
	(* 61H	Sequencer RAM Data *)	SEQRAM = 61H;
	(* 62H	Sequencer Address Registers *)	SEQADDR0 = 62H;
	(* 63H	*)		SEQADDR1 = 63H;
	(* 65H	*)		SINDEX = 65H;
(* Sequencer (SCSI Phase Engine)*)
	(* 70H	1 byte per target SCSI offset values for Ultra2 controllers *)	TARGOFFSET = 70H;
(* Bus Registers*)
	(* 84H	Board Control*)	BCTL = 84H;
		(* Bit 0 	Enable *)		ENABLE = 0;
	(* 84H	Device Space Command*)	DSCOMMAND0 = 84H;
		(* Bit 0	*)		CIOPARCKEN = 0;
		(* bit 1	*)		USCBSIZE32 = 1;
		(* Bit 5	Memory Parity Check Enable *)		MPARCKEN = 5;
		(* Bit 6	Data Parity Check Enable *)		DPARCKEN = 6;
		(* Bit 7	Cache Threshold Enable *)		CACHETHEN = 7;
	(* 86H	DSPCI Status *)	DSPCISTATUS = 86H;
		(*	Read when 100% empty, write when 100% full *)		DFTHRSH100 = {6, 7};
	(* 87H	Host Control, Overall host control of the device*)	HCNTRL = 87H;
		(* Bit 0	Chip Reset / Chip Reset Acknowledge *)		CHIPRST = 0;
		(* Bit 1	Interrupt Enable *)		INTEN = 1;
		(* Bit 2	Pause Enable *)		PAUSE = 2;
	(* 90H	Gate one of the four SCBs into the SCBARRAY window*)	SCBPTR = 90H;
	(* 91H	Interrupt Status *)	INTSTAT = 91H;
		(* Bit 0	Sequencer Interrupt *)		SEQINT = 0H;
		(* Bit 1	Command Completed *)		CMDCMPLT = 1H;
		(* Bit 2	Scsi Interrupt *)		SCSIINT = 2H;
		(* Bit 3	Break address *)		BRKADRINT = 3H;
		(* Sequencer Interrupt Mask *)		SEQINTMASK = {0, 4..7};
		(* Interrupt Values*)
			DataOverrun = 0EH; MsgInPhaseMis = 0DH; TracePoint2 = 0CH;
			TracePoint = 0BH; AwaitingMsg = 0AH; Residual = 08H;
			BadStatus = 07H; RejectMsg = 06H; AbortRequested = 05H;
			ExtendedMsg = 04H; NoMatch = 03H; NoIdent = 02H;
			SendReject = 01H; BadPhase = 00H;
	(* 92H	Write: Clear Interrupt Status *)	CLRINT = 92H;
		(* Bit 0	Clear Sequencer Interrupt *)		CLRSEQINT = 0;
		(* Bit 1	Clear Command Complete Interrupt *)		CLRCMDINT = 1;
		(* Bit 2	Clear SCSI Interrupt *)		CLRSCSIINT = 2;
		(* Bit 3	Clear Break Address Interrupt *)		CLRBRKADRINT = 3;
		(* Bit 4	Clear Parity Errors*)		CLRPARERR = 4;
	(* 92H	Read: Hard Error *)	ERROR = 92H;
		(* Bit 0	Illegal Hardware Address *)		ILLHADDR = 0;
		(* Bit 1	Illegal Software Address *)		ILLSADDR = 1;
		(* Bit 2	Illegal Opcode Error *)		ILLOPCODE = 2;
		(* Bit 3	Sequencer Parity Error *)		SQPARERR = 3;
		(* Bit 6	Pci error *)		PCIERRSTAT = 6;
	(* 94H	Data FIFO Status *)	DFSTATUS = 94H;

(* SCB Definition: this field give direct access to the scb pointed by SCBPTR*)
	(* A0H	SCB Control *)	SCBCONTROL = 0A0H;
		(* Bit 3	Disconnected*)		DISCONNECTED = 3;
		(* Bit 6	Disconnect Enabled*)		DISCENB = 6;
	(* A1H	*)		SCBTCL = 0A1H;
	(* A2H	Target Status *)	SCBTARGETSTATUS = 0A2H;
	(* A3H	SG / Count *)	SCBSGCOUNT = 0A3H;
	(* A4H	SG / Ptr *)	SCBSGPTR = 0A4H;
	(* A8H	*)		SCBRESIDSGCNT = 0A8H;
	(* A9H	*)		SCBRESIDDCNT = 0A9H;
	(* ACH	*)		SCBDATAPTR = 0ACH;
	(* B0H	*)		SCBDATACNT = 0B0H;
	(* B4H	*)		SCBCMDPTR = 0B4H;
	(* B8H	*)		SCBCMDLEN = 0B8H;
	(* B9H	*)		SCBTAG = 0B9H;
	(* BAH	*)		SCBNEXT = 0BAH;
	(* BBH	*)		SCBPREV = 0BBH;
	(* BCH	*)		SCBBUSYTARGETS = 0BCH;

	(* F0H	CCSCBBADDR, 7895/6/7 only *)	CCSCBBADDR = 0F0H;
	(* F4H	Host New SCB Queue Offset *)	HNSCBQOFF = 0F4H;
	(* F6H	Sequencer New SCB Queue Offset *)	SNSCBQOFF = 0F6H;
	(* F8H	Sequencer Done SCB Queue Offset *)	SDSCBQOFF = 0F8H;
	(* FAH	Queue Offset Control & Status *)	QOFFCTLSTA = 0FAH;
		(*	Queue size = 256 *)		SCBQSIZE256 = {1, 2};
	(* FBH	Data FIFO Threshold *)	DFFTHRSH = 0FBH;
		(*	Write starts when 75% full *)		WRDFFTHRSH75 = {6};
		(*	Read starts when 75% empty *)		RDDFFTHRSH75 = {2};

(*PCI Registers*)
	(* 40H	Device Configuration *)	DEVCONFIG = 40H;
		(* Bit 2	RAMPSM_ULTRA2 7895/6 only? *)		RAMPSMULTRA2 = 2;
		(* Bit 3	Byte Parity Error Enable *)		BERREN = 3;
		(* Bit 3	SCBRAMSELULTRA2??? *)		SCBRAMSELULTRA2 = 3;
		(* Bit 4	External SCB Parity Enable *)		EXTSCBPEN = 4;
		(* Bit 7	SCB RAM Select,  not 7890 *)		SCBRAMSEL = 7;
		(* Bit 9	RAM Present Mode *)		RAMPSM = 9;
		(* Bit 16	SCBSIZE32, 7895 only? *)		SCBSIZE32 = 16;
		(* Bit 31	PCI Error Generation Disable *)		PCIERRGENDIS = 31;

	SCAMCTL = 1AH;			(*ultra2 only*)

	(* HW-SCB Offsets *)
	HScontrol = 0;
	HStarget = 1;
	HSstatus = 2;
	HSSGcount = 3;
	HSSGptr = 4;
	HSresSGcnt = 8;
	HSresDataCnt = 9;
	HSdataPtr = 12;
	HSdataCnt = 16;
	HScmdPtr = 20;
	HScmdLen = 24;
	HStag = 25;
	HSnext = 26;
	HSprev = 27;
	HSpar = 28;

TYPE
	Interrupt = OBJECT
		VAR
			root: Bus; int: LONGINT;

		PROCEDURE HandleInterrupt;
		VAR  int, err: SET;  d: Bus;
		BEGIN
			INC(aIntCount);
			d := root;
			WHILE d # NIL DO
				int := SYSTEM.VAL(SET, d.Get1(d, INTSTAT));
				err := SYSTEM.VAL(SET, d.Get1(d, ERROR));
				IF d.interruptable & ((int # {}) OR (err # {})) THEN
					IF traceSmall IN trace THEN KernelLog.Char("<") END;
					IF CMDCMPLT IN int THEN
						IF traceSmall IN trace THEN KernelLog.Char("C") END;
						IntCommand(d)
					END;
					IF (BRKADRINT IN int) OR (err # {}) THEN
						IF traceSmall IN trace THEN KernelLog.Char("B") END;
						IntBrkAdr(d)
					END;
					IF SEQINT IN int THEN
						IF traceSmall IN trace THEN KernelLog.Char("S") END;
						IntSeqHandle(d, SYSTEM.LSH(SYSTEM.VAL(LONGINT, int), -4))
					END;
					IF SCSIINT IN int THEN
						IF traceSmall IN trace THEN KernelLog.Char("s") END;
						IntScsiHandle(d)
					END;
					IF traceSmall IN trace THEN KernelLog.Char(">") END;
				END;
				d := d.next
			END
		END HandleInterrupt;

		PROCEDURE Cleanup;
		BEGIN
			Objects.RemoveHandler(SELF.HandleInterrupt, int)
		END Cleanup;

		PROCEDURE & Init*(irq: LONGINT);
		BEGIN
			int := Machine.IRQ0 + irq;
			Objects.InstallHandler(SELF.HandleInterrupt, int)
		END Init;
	END Interrupt;
(*
	Watchdog = POINTER TO RECORD  (Timer)
		VAR
			stop: BOOLEAN;
			dev, ms, count: LONGINT;
			bus: Bus;

		PROCEDURE & InitW(ms, count: LONGINT;  bus: Bus;  dev: LONGINT);
		BEGIN
			KernelLog.String("Watchdog installed"); KernelLog.Ln;
			Init;
			SELF.count := count;  SELF.ms := ms;
			SELF.dev := dev;  SELF.bus := bus
		END InitW;

	BEGIN {ACTIVE}
		Wait(ms);
		WHILE ~stop & (count > 0) DO
			WriteBusState(bus);
			Wait(ms);
			DEC(count)
		END;
		IF ~stop THEN	(*timeouted*)
			bus.Synchronize(dev, bus.sync[dev], Terminated)
		END;
		KernelLog.String("Watchdog terminated"); KernelLog.Ln;
	END Watchdog;
*)
	Message = RECORD
		buf: ARRAY 6 OF CHAR;
		pos, len, dir: SHORTINT;
	END;


	Bus = OBJECT (SCSI.Bus)
		VAR
				(* card configuration *)
			flags, features:  SET;
			chip: SHORTINT;
			interruptable: BOOLEAN;
			irq: SHORTINT;	(* interrupt line *)
			scsiId: SHORTINT;

			sync: ARRAY 16 OF SHORTINT;	(* bus synchronization: only one cmd per device *)

				(* card IO information *)
			busNo, devNo, slotNo: LONGINT;	(* PCI Address *)
			buffers: ARRAY (QBufferSize + SCBBufferSize + BufferAlignment) OF CHAR;	(* Q and SCB buffers *)
			scbArea: LONGINT;	(* virtual address of the hardware scb buffer, shared with the sequencer *)
			queueArea: LONGINT;	(*virtual address of the queue buffers, shared with the sequencer *)
			in, out: LONGINT;	(*pointer to next scb*)
			base: SYSTEM.ADDRESS;	(*virtual address for memory mapped IO / port address for port based IO *)
			Put1: PROCEDURE (d: Bus; offset: LONGINT; val: CHAR);
			Get1: PROCEDURE (d: Bus; offset: LONGINT): CHAR;

				(* cached values *)
			width: SHORTINT; 	(* 8 or 16, iff FeatWide *)
			negotiateWidth: SET;	(*handle transmission width whenever possible*)
			negotiateSync: SET;	(*handle transmission sync whenever possible*)
			msg: Message;	(*message from/to device. Only one buffer: the message phase is not prehempted*)

			result, status: ARRAY 16 OF SHORTINT;

			next: Bus;	(*list of all Ada7 busses *)

		PROCEDURE Synchronize(dev: LONGINT;  await, set: SHORTINT);
			PROCEDURE Data;
			BEGIN
				KernelLog.String("Sync ["); KernelLog.Int(dev, 0); KernelLog.String("] ");
				KernelLog.Int(await, 0); KernelLog.String(" -> "); KernelLog.Int(set, 0)
			END Data;
		BEGIN  {EXCLUSIVE}
			IF traceSync IN trace THEN BusNameMsg(SELF, " ->"); Data; KernelLog.Ln; END;
			AWAIT(sync[dev] = await);
			IF traceSync IN trace THEN BusNameMsg(SELF, " <-"); Data; KernelLog.Ln END;
			sync[dev] := set
		END Synchronize;

		PROCEDURE Submit*(VAR c: SCSI.Command);
		BEGIN
			Synchronize(c.target, Free, InUse);
			BusSchedule(SELF, c);		(* return only when command Terminated *)
			c.result := result[c.target];
			c.status := status[c.target];
			Synchronize(c.target, Terminated, Free);	(* activate/allow next command *)
		END Submit;

		PROCEDURE & Init*(id, bus, dev, slot: LONGINT);
		BEGIN
			flags := card[id].flags;
			features := card[id].features;
			chip := card[id].chip;
			interruptable := FALSE;
			COPY(card[id].name, fullname);
			busNo := bus;  devNo := dev;  slotNo := slot;
			IF firstBus = NIL THEN  firstBus := SELF  END;
		END Init;
	END Bus;

	RateTableDesc = RECORD
		period: SHORTINT;	(* 1/4 of nsec, same as sync negotiation message *)
		rate: CHAR;	(* rate for normal/fast/ultra cards *)
		u2rate: CHAR;	(* rate ultra2 cards *)
		speed: LONGINT;	(* speed in 100 KB/s for 8-bit bus *)
	END;

	(* Description of the cards we're know about *)
	CardTable = RECORD
		signature: LONGINT;
		chip: SHORTINT;
		flags, features: SET;
		name: ARRAY 32 OF CHAR;
	END;

VAR
	timer: Kernel.Timer;
	interrupt: ARRAY 32 OF Interrupt;

	card: ARRAY 19 OF CardTable;	(*description of the known cards*)
	rateTable: ARRAY 13 OF RateTableDesc;

	firstBus: Bus;
	trace: SET;
	aExecute,
	aIntScsiSeltoProblem1, aIntScsiSeltoProblem2,
	aIntScsiReset, aIntScsiSelto, aIntScsiBusFree, aIntScsiParity, aIntScsiReqInit, aIntScsiSpecial, aIntScsiUnknown,
	aIntSeqNoMatch, aIntSeqSendReject, aIntSeqNoIdent, aIntSeqBadPhase, aIntSeqExtendedMsg,
	aIntSeqRejectMsg, aIntSeqBadStatus, aIntSeqAwaitingMsg, aIntSeqDataOverrun,
	aIntCount, aIntCmdCount, aIntBrkAdrCount, aIntSeqCount, aIntScsiCount: LONGINT;


(* Debug Procedures *)

PROCEDURE ToDo(desc: ARRAY OF CHAR);
BEGIN
	KernelLog.Enter;
	KernelLog.String(desc); KernelLog.String(" not implemented yet");
	KernelLog.Exit
END ToDo;

PROCEDURE BusName(b: Bus);
BEGIN
	KernelLog.Char("["); KernelLog.Hex(b.busNo, -2);
	KernelLog.Char("/"); KernelLog.Hex(b.devNo, -2);
	KernelLog.Char("/"); KernelLog.Hex(b.slotNo, -2); KernelLog.String("] ");
END BusName;

PROCEDURE BusNameMsg(b: Bus;  str: ARRAY OF CHAR);
BEGIN
	KernelLog.Char("["); KernelLog.Hex(b.busNo, -2);
	KernelLog.Char("/"); KernelLog.Hex(b.devNo, -2);
	KernelLog.Char("/"); KernelLog.Hex(b.slotNo, -2); KernelLog.String("] ");
	KernelLog.String(str)
END BusNameMsg;

PROCEDURE Reg(d: Bus; off: LONGINT;  name: ARRAY OF CHAR);
BEGIN
	KernelLog.String(name); KernelLog.Hex(ORD(d.Get1(d, off)), -2)
END Reg;

PROCEDURE DumpCurSCB(d: Bus);
BEGIN
	Reg(d, SCBPTR, "CurSCB = ");
	Reg(d, SCBCONTROL, " Ctrl = ");
	Reg(d, SCBTCL, " Targ = ");
	Reg(d, SCBTAG, " Tag = ");
	Reg(d, SCBPREV, " Prev = ");
	Reg(d, SCBNEXT, " Next = ");
	KernelLog.Ln;
END DumpCurSCB;

PROCEDURE DumpBusList(d: Bus;  list: LONGINT;  name: ARRAY OF CHAR);
VAR  ch, save: CHAR;
BEGIN
	BusName(d); KernelLog.String(name);
	save := d.Get1(d, SCBPTR);
	ch := d.Get1(d, list);
	WHILE ch # 0FFX DO
		KernelLog.String("->"); KernelLog.Hex(ORD(ch), -2);
		d.Put1(d, SCBPTR, ch); ch :=  d.Get1(d, SCBNEXT);
	END;
	KernelLog.Ln;
	d.Put1(d, SCBPTR, save);
END DumpBusList;

PROCEDURE DumpAllLists(d: Bus);
BEGIN
	DumpBusList(d, FREESCBH, "Free");
	DumpBusList(d, WAITINGSCBH, "Wait");
	DumpBusList(d, DISCONNECTEDSCBH, "Disc");
END DumpAllLists;

(* IO Procedures to be installed into the Bus structures to comunicate with hardware *)

PROCEDURE PortPut(d: Bus; offset: LONGINT; val: CHAR);
BEGIN
	Machine.Portout8(d.base+offset, val)
END PortPut;

PROCEDURE PortGet(d: Bus; offset: LONGINT): CHAR;
VAR ch: CHAR;
BEGIN
	Machine.Portin8(d.base+offset, ch); RETURN ch
END PortGet;

PROCEDURE MemoryPut(d: Bus; offset: LONGINT; val: CHAR);
BEGIN
	SYSTEM.PUT(d.base+offset, val)
END MemoryPut;

PROCEDURE MemoryGet(d: Bus; offset: LONGINT): CHAR;
VAR ch: CHAR;
BEGIN
	SYSTEM.GET(d.base+offset, ch);  RETURN ch
END MemoryGet;

(* Common Code Patterns *)

PROCEDURE MakeTarget(targ, chan, lun: LONGINT): CHAR;
BEGIN
	RETURN  CHR(SYSTEM.LSH(targ MOD 16, 4)+SYSTEM.LSH(chan MOD 2, 3)+lun MOD 8)
END MakeTarget;

(* Sync / Width functions *)

(* GetTableIndex - Return the index to start the search in the rateTable *)

PROCEDURE GetTableIndex(ultra, ultra2: BOOLEAN): LONGINT;
BEGIN
	IF ultra2 THEN RETURN 0 ELSIF ultra THEN RETURN 2 ELSE RETURN 5 END
END GetTableIndex;

PROCEDURE GetMaxOffset(wide, ultra2: BOOLEAN): CHAR;
BEGIN
	IF ultra2 THEN  RETURN 7FX
	ELSIF wide THEN  RETURN  08X
	ELSE RETURN 0FX
	END
END GetMaxOffset;

PROCEDURE SetWidth(d: Bus;  wide: BOOLEAN);
VAR  index: LONGINT;  rate: SET;
BEGIN
	index := ORD(d.Get1(d, SCBTAG));
	rate := SYSTEM.VAL(SET, d.Get1(d, TARGSCSIRATE + index));
	IF wide THEN
		INCL(rate, WIDEXFER)
	ELSE
		EXCL(rate, WIDEXFER)
	END;
	d.Put1(d, TARGSCSIRATE + index, SYSTEM.VAL(CHAR, rate));
	d.Put1(d, SCSIRATE, SYSTEM.VAL(CHAR, rate));
	BusNameMsg(d, "Device["); KernelLog.Int(index, 0); KernelLog.String("].width=");
	KernelLog.Int(SYSTEM.VAL(SHORTINT, wide), 0); KernelLog.Ln
END SetWidth;

PROCEDURE SetSyncRate(d: Bus;  period, offset: LONGINT);
VAR i, index, speed: LONGINT;  ultra2, ultra, wide: BOOLEAN;  ctrl0, rate: CHAR;  set: SET;
BEGIN
	ultra2 := FeatUltra2 IN d.features;
	ultra := FeatUltra IN d.features;

	index := ORD(d.Get1(d, SCBTAG));
	i := GetTableIndex(ultra, ultra2);
	WHILE  (i < LEN(rateTable)) & (period > rateTable[i].period) DO INC(i) END;
	IF i = LEN(rateTable) THEN
		i := -1; offset := 0;	(*async*)
	END;
	rate := d.Get1(d, TARGSCSIRATE + index);
	rate := SYSTEM.VAL(CHAR, SYSTEM.VAL(SET, rate) * {WIDEXFER});
	wide := rate # 0X;
	IF ultra2 THEN
		IF i # -1 THEN  rate := CHR(ORD(rate) + ORD(rateTable[i].u2rate))  END;
		d.Put1(d, SCSIRATE, rate);
		d.Put1(d, SCSIOFFSET, CHR(offset));
		d.Put1(d, TARGSCSIRATE+index, rate);
		d.Put1(d, TARGOFFSET+index, CHR(offset))
	ELSE
		IF i # -1 THEN
			ASSERT(offset < 10H);
			rate := CHR(ORD(rate) + ORD(rateTable[i].rate) + (offset MOD 10H));
			ultra := i < 5
		ELSE
			ultra := FALSE
		END;
		d.Put1(d, SCSIRATE, rate);
		d.Put1(d, TARGSCSIRATE + index, rate);
		ctrl0 := d.Get1(d, SXFRCTL0);
		set := SYSTEM.VAL(SET, d.Get1(d, ULTRAENB + index DIV 8));
		IF ultra THEN
			ctrl0 := SYSTEM.VAL(CHAR, SYSTEM.VAL(SET, ctrl0) + {FAST20});
			INCL(set, index MOD 8)
		ELSE
			ctrl0 := SYSTEM.VAL(CHAR, SYSTEM.VAL(SET, ctrl0) - {FAST20});
			EXCL(set, index MOD 8)
		END;
		d.Put1(d, SXFRCTL0, ctrl0);
		d.Put1(d, ULTRAENB + index DIV 8, SYSTEM.VAL(CHAR, set))
	END;
	BusNameMsg(d, "Device["); KernelLog.Int(index, 0); KernelLog.String("].rate=");
	IF i = -1 THEN
		KernelLog.String("async")
	ELSE
		KernelLog.Int(rateTable[i].period, 0); KernelLog.Char("/"); KernelLog.Int(offset, 0);
		speed := rateTable[i].speed;
		IF wide THEN speed := speed*2  END;
		KernelLog.Int(speed DIV 10, 4); KernelLog.Char("."); KernelLog.Int(speed MOD 10, 0);
		KernelLog.String(" MB/s")
	END;
	KernelLog.Ln
END SetSyncRate;

(* Queues Functions *)
(*
	Queue Layout

	+000H	untagged queue
	+100H	out queue
	+200H	in queue
*)

PROCEDURE QUntaggedSet(d: Bus;  i: LONGINT;  ch: CHAR);
BEGIN
	SYSTEM.PUT(d.queueArea+i, ch)
END QUntaggedSet;

PROCEDURE QOutSet(d: Bus;  i: LONGINT;  ch: CHAR);
BEGIN
	SYSTEM.PUT(d.queueArea+100H+i, ch)
END QOutSet;

PROCEDURE QOutGet(d: Bus;  i: LONGINT): CHAR;
VAR ch: CHAR;
BEGIN
	SYSTEM.GET(d.queueArea+100H+i, ch);  RETURN ch
END QOutGet;

PROCEDURE QInSet(d: Bus;  i: LONGINT;  ch: CHAR);
BEGIN
	SYSTEM.PUT(d.queueArea+200H+i, ch)
END QInSet;

PROCEDURE QInit(d: Bus);
VAR paddr, i: LONGINT;
BEGIN
	IF traceCalls IN trace THEN  BusNameMsg(d, "QInit"); KernelLog.Ln  END;
(*
	Machine.NewPhysicalPage(paddr);
	Machine.MapPhysical(paddr, Machine.PageSize, d.queueArea);
*)
	paddr := SYSTEM.ADR(d.buffers[0]);
	INC(paddr, (-paddr) MOD BufferAlignment);
	d.queueArea := paddr;

	FOR i := 0 TO 255 DO
		QUntaggedSet(d, i, 0FFX);  QOutSet(d, i, 0FFX);  QInSet(d, i, 0FFX)
	END;
	d.in := 0;
	d.out := 0;
	d.Put1(d, SCBIDADDR, CHR(paddr));
	d.Put1(d, SCBIDADDR+1, CHR(SYSTEM.LSH(paddr, -8)));
	d.Put1(d, SCBIDADDR+2, CHR(SYSTEM.LSH(paddr, -16)));
	d.Put1(d, SCBIDADDR+3, CHR(SYSTEM.LSH(paddr, -24)));
	d.Put1(d, QINPOS, 0X);
	d.Put1(d, KERNELQINPOS, 0X);
	d.Put1(d, QOUTPOS, 0X);
	IF FeatQueueRegs IN d.features THEN
		d.Put1(d, QOFFCTLSTA, 6X);	(* Queue size = 256 *)
		d.Put1(d, SDSCBQOFF, 0X);
		d.Put1(d, SNSCBQOFF, 0X);
		d.Put1(d, HNSCBQOFF, 0X)
	END;
	d.Put1(d, WAITINGSCBH, 0FFX);
	d.Put1(d, DISCONNECTEDSCBH, 0FFX);
END QInit;


(* HW-SCB Functions *)
(*
	HW SCB Layout:
	00	1	control
	01	1	target
	02	1	status
	03	1	SG count
	04	4	SG pointer
	08	1	residual SG count
	09	3	residual Data count
	12	4	data ptr
	16	4	data cnt
	20	4	cmd ptr
	24	1	cmd len
	25	1	tag
	26	1	next
	27	1	prev
	28	4	par
*)

PROCEDURE HSSet(d: Bus;  num, offset: LONGINT;  val: CHAR);
BEGIN	SYSTEM.PUT(d.scbArea + num*32 + offset, val)
END HSSet;

PROCEDURE HSSet4(d: Bus;  num, offset: LONGINT;  val: LONGINT);
BEGIN	SYSTEM.PUT(d.scbArea + num*32 + offset, val)
END HSSet4;

PROCEDURE HSFreeCurrent(d: Bus);
BEGIN
	d.Put1(d, SCBTAG, 0FFX);
	d.Put1(d, SCBCONTROL, 0X);
	d.Put1(d, SCBNEXT, d.Get1(d, FREESCBH));
	d.Put1(d, FREESCBH, d.Get1(d, SCBPTR))
END HSFreeCurrent;

PROCEDURE HSFreeScb(d: Bus;  index: LONGINT);
BEGIN
	HSSet(d, index, HScontrol, 0X);
	HSSet(d, index, HSstatus, 0X);
	HSSet(d, index, HStarget, 0FFX)
END HSFreeScb;

PROCEDURE HSInit(d: Bus);
VAR  paddr, i: LONGINT;  quit: BOOLEAN;
BEGIN
	IF traceCalls IN trace THEN  BusNameMsg(d, "HSInit"); KernelLog.Ln  END;
	(*allocate hw-scb buffer*)
(*
	Machine.NewPhysicalPage(paddr);
	Machine.MapPhysical(paddr, Machine.PageSize, d.scbArea);
*)
	paddr := SYSTEM.ADR(d.buffers[QBufferSize]);
	INC(paddr, (-paddr) MOD BufferAlignment);
	d.scbArea := paddr;

	FOR i := 0 TO 127 DO
		SYSTEM.PUT(d.scbArea + i*32 + HScontrol, 0X);	(* control := 0X *)
		SYSTEM.PUT(d.scbArea + i*32 + HStag, CHR(i));	(* tag := i *)
	END;
	d.Put1(d, HSCBARRAY, CHR(paddr));
	d.Put1(d, HSCBARRAY+1, CHR(SYSTEM.LSH(paddr, -8)));
	d.Put1(d, HSCBARRAY+2, CHR(SYSTEM.LSH(paddr, -16)));
	d.Put1(d, HSCBARRAY+3, CHR(SYSTEM.LSH(paddr, -24)));

	(* clear and initialize host scb table *)
	d.Put1(d, FREESCBH, 0X);
	i := 0;  quit := FALSE;
	WHILE (i < 255) & ~quit DO
		d.Put1(d, SCBPTR, CHR(i));
		d.Put1(d, SCBCONTROL, CHR(i));
		IF i # ORD(d.Get1(d, SCBCONTROL)) THEN
			quit := TRUE
		ELSE
			d.Put1(d, SCBPTR, 0X);
			IF d.Get1(d, SCBCONTROL) # 0X THEN
				quit := TRUE
			ELSE
				d.Put1(d, SCBPTR, CHR(i));
				d.Put1(d, SCBCONTROL, 0X);
				d.Put1(d, SCBNEXT, CHR(i+1));
				d.Put1(d, SCBPREV, CHR(i-1));
				d.Put1(d, SCBTAG, 0FFX);
				d.Put1(d, SCBBUSYTARGETS, 0FFX);
				d.Put1(d, SCBBUSYTARGETS+1, 0FFX);
				d.Put1(d, SCBBUSYTARGETS+2, 0FFX);
				d.Put1(d, SCBBUSYTARGETS+3, 0FFX);
				INC(i)
			END
		END
	END;
	d.Put1(d, SCBPTR, CHR(i-1));	(*last SCB terminates the list*)
	d.Put1(d, SCBNEXT, 0FFX);

	d.Put1(d, SCBPTR, 0X);	(*clear first SCB control byte (may have been changed)*)
	d.Put1(d, SCBCONTROL, 0X);
	IF i = 255 THEN  EXCL(d.flags, FlagPageSCB)  END;
	BusNameMsg(d, "SCB = "); KernelLog.Int(i, 0); KernelLog.Ln
END HSInit;

(* Sequencer Functions *)

PROCEDURE SeqPause(d: Bus);
BEGIN
	IF traceSequencer IN trace THEN  BusNameMsg(d, "SeqPause"); KernelLog.Ln  END;
	d.Put1(d, HCNTRL, SYSTEM.VAL(CHAR, {INTEN, PAUSE}));
	WHILE ~(PAUSE IN SYSTEM.VAL(SET, d.Get1(d, HCNTRL))) DO  END;
END SeqPause;

PROCEDURE SeqUnpause(d: Bus);
BEGIN
	IF traceSequencer IN trace THEN  BusNameMsg(d, "SeqUnpause")  END;
	IF ~(FlagHandlingReqInits IN d.flags) THEN
		IF traceSequencer IN trace THEN  KernelLog.String("*")  END;
		d.Put1(d, HCNTRL, SYSTEM.VAL(CHAR, {INTEN}))
	ELSE
		ToDo("Ada7: Unpausing the sequencer while in message mode")
	END;
	IF traceSequencer IN trace THEN  KernelLog.Ln  END;
END SeqUnpause;

PROCEDURE SeqLoad(d: Bus);
VAR i, line: LONGINT;
BEGIN
	IF traceCalls IN trace THEN  BusNameMsg(d, "LoadSequencer"); KernelLog.Ln  END;
		Script.Init(FeatUltra2 IN d.features, FeatUltra IN d.features, FeatWide IN d.features, FeatTwin IN d.features,
				FlagPageSCB IN d.flags, FeatQueueRegs IN d.features, FeatCmdChan IN d.features, FALSE, AIC7895 = d.chip);
	i := 0;
	d.Put1(d, SEQCTL, SYSTEM.VAL(CHAR, {PERRORDIS,LOADRAM,FAILDIS,FASTMODE}));
	d.Put1(d, SEQADDR0, 0X);
	d.Put1(d, SEQADDR1, 0X);
	WHILE Script.GetNext(SYSTEM.VAL(SET, line)) DO
		d.Put1(d, SEQRAM, CHR(line));
		d.Put1(d, SEQRAM, CHR(SYSTEM.LSH(line, -8)));
		d.Put1(d, SEQRAM, CHR(SYSTEM.LSH(line, -16)));
		d.Put1(d, SEQRAM, CHR(SYSTEM.LSH(line, -24)));
		INC(i)
	END;
	BusNameMsg(d, "Sequencer Loaded lines="); KernelLog.Int(i, 0); KernelLog.Ln;
	d.Put1(d, SEQCTL, SYSTEM.VAL(CHAR, {FASTMODE,SEQRESET}));
END SeqLoad;

(* PCI Functions *)

PROCEDURE PCIClearInt(d: Bus);
CONST	DPR = 0;  RMA = 5;  RTA = 4;
VAR	res, dw: LONGINT;  bus, dev, slot: LONGINT;
BEGIN
	bus := d.busNo;  dev := d.devNo;  slot := d.slotNo;
	res := PCI.ReadConfigByte(bus, dev, slot, 7H, dw);
	res := PCI.WriteConfigByte(bus, dev, slot, 7H, dw);	(* Clear PCI-Status *)
	IF {DPR, RMA, RTA} * SYSTEM.VAL(SET, dw) # {} THEN
		d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRPARERR}))
	END;
END PCIClearInt;

PROCEDURE PCIInit(d: Bus;  bus, dev, slot: LONGINT);
VAR  res, dw: LONGINT;
BEGIN
	IF traceCalls IN trace THEN  BusNameMsg(d, "PCIInit"); KernelLog.Ln  END;
	res:=PCI.ReadConfigByte(bus, dev, slot, PCI.IntlReg, dw);	(*irq*)
	d.irq :=  SHORT(SHORT(dw));
	BusName(d); KernelLog.String("irq="); KernelLog.Int(d.irq, 0);
	res:=PCI.ReadConfigDword(bus, dev, slot, PCI.Adr1Reg, dw);	(*memio*)
	IF dw = 0 THEN
		res:=PCI.ReadConfigDword(bus, dev, slot, PCI.Adr0Reg, dw);	(*iobase*)
		d.base := dw - 1;
		d.Put1 := PortPut;
		d.Get1 := PortGet;
		KernelLog.String(" Port="); KernelLog.Int(d.base, 0)
	ELSE
		dw := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, dw) * {3..31});	(* clear io mode & 64 bit flags*)
		Machine.MapPhysical(dw, 100H, d.base);
		d.Put1 := MemoryPut;
		d.Get1 := MemoryGet;
		KernelLog.String(" MemMap="); KernelLog.Hex(dw, 0); KernelLog.String("/"); KernelLog.Hex(d.base, 0)
	END;
	KernelLog.Ln;
	res:=PCI.ReadConfigWord(bus, dev, slot, PCI.CmdReg, dw);
	dw := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, dw) + {8, 6, 4, 2, 1, 0});	(*Serren, Perren, Mwricen, Master, MSpace, ISpace*)
	res:=PCI.WriteConfigWord(bus, dev, slot, PCI.CmdReg, dw);
	res:=PCI.ReadConfigDword(bus, dev, slot, DEVCONFIG, dw);
	dw := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, dw) + {PCIERRGENDIS} - {BERREN});
	IF d.chip = AIC7895 THEN
		dw := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, dw) + {SCBSIZE32});
	END;
	res:=PCI.WriteConfigDword(bus, dev, slot, DEVCONFIG, dw);
	SeqPause(d);
	PCIClearInt(d);
END PCIInit;

(* Message Handling *)

PROCEDURE MsgStart(d: Bus);
VAR  t: SET;
BEGIN
	INCL(d.flags, FlagHandlingReqInits);
	t := SYSTEM.VAL(SET, d.Get1(d, SIMODE1));
	d.Put1(d, SIMODE1, SYSTEM.VAL(CHAR, t + {ENREQINIT}));
END MsgStart;

PROCEDURE MsgStop(d: Bus);
VAR  t: SET;
BEGIN
	t := SYSTEM.VAL(SET, d.Get1(d, SIMODE1));
	d.Put1(d, SIMODE1, SYSTEM.VAL(CHAR, t - {ENREQINIT}));
	d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
	EXCL(d.flags, FlagHandlingReqInits);
END MsgStop;

PROCEDURE MsgCompose(d: Bus;  index: LONGINT;  VAR msg: Message);
VAR i: LONGINT;
BEGIN
	IF index IN d.negotiateWidth THEN
		EXCL(d.negotiateWidth, index);
		msg.buf[0] := SCSI.MsgExtended;
		msg.buf[1] := SCSI.MsgExtWdTrLen;
		msg.buf[2] := SCSI.MsgExtWdTr;
		msg.buf[3] := SCSI.MsgExtWdTr16Bit;	(*always ask for the max*)
		msg.len := 4;
		msg.pos := 0;
		msg.dir := Sending
	ELSIF index IN d.negotiateSync THEN
		EXCL(d.negotiateSync, index);
		i := GetTableIndex(FeatUltra IN d.features, FeatUltra2 IN d.features);
		msg.buf[0] := SCSI.MsgExtended;
		msg.buf[1] := SCSI.MsgExtSdTrLen;
		msg.buf[2] := SCSI.MsgExtSdTr;
		msg.buf[3] := CHR(rateTable[i].period);	(* ask for min period (fastest) *)
		msg.buf[4] := GetMaxOffset(FeatWide IN d.features, FeatUltra2 IN d.features);	(* ask for the max offset *)
		msg.len := 5;
		msg.pos := 0;
		msg.dir := Sending
	ELSE
		ToDo("Adaptec7.MsgCompose: negotiate what?");
	END
END MsgCompose;

PROCEDURE MsgParse(d: Bus;  VAR msg: Message): BOOLEAN;
VAR  reject, done, wide: BOOLEAN;  period, offset: LONGINT;  t: SET;
BEGIN
	reject := FALSE;  done := FALSE;
	IF msg.buf[0] # SCSI.MsgExtended THEN
		reject := TRUE
	ELSIF msg.len <= 2 THEN	(*ExtMsg are 4 or 5 bytes*)
		done := FALSE
	ELSIF msg.buf[2] = SCSI.MsgExtSdTr THEN	(*sync handling*)
		IF msg.buf[1] # SCSI.MsgExtSdTrLen THEN
			reject := TRUE
		ELSIF msg.len < ORD(SCSI.MsgExtSdTrLen)+2 THEN
			done := FALSE
		ELSE
			done := TRUE;
			period := ORD(msg.buf[3]);
			offset := ORD(msg.buf[4]);
			BusNameMsg(d, "SyncMsg "); KernelLog.Int(period, 0); KernelLog.Char("/");
			KernelLog.Int(offset, 0); KernelLog.Ln;
			SetSyncRate(d, period, offset)
		END
	ELSIF msg.buf[2] = SCSI.MsgExtWdTr THEN	(*width handling*)
		IF msg.buf[1] # SCSI.MsgExtWdTrLen THEN
			reject := TRUE
		ELSIF msg.len < ORD(SCSI.MsgExtWdTrLen)+2 THEN
			done := FALSE
		ELSE
			done := TRUE;
			wide := msg.buf[3] = SCSI.MsgExtWdTr16Bit;
			BusNameMsg(d, "WidthMsg "); KernelLog.Int(ORD(msg.buf[3]), 0); KernelLog.Ln;
			SetWidth(d, wide);
		END
	ELSE
		reject := TRUE
	END;
	IF reject THEN
		d.Put1(d, MSGOUT, SCSI.MsgMessageReject);
		t := SYSTEM.VAL(SET, d.Get1(d, SCSISIG));
		d.Put1(d, SCSISIG, SYSTEM.VAL(CHAR, t + {ATN}))
	END;
	RETURN reject OR done
END MsgParse;

PROCEDURE MsgTransmit(d: Bus;  VAR msg: Message);
VAR  t: SET;  done: BOOLEAN;  ch: CHAR;
BEGIN
	t := SYSTEM.VAL(SET, d.Get1(d, SCSISIG));
	IF msg.dir = Sending THEN
		IF traceSmall IN trace THEN
			KernelLog.Char("["); KernelLog.Hex(ORD(msg.buf[msg.pos]), -2); KernelLog.Char("]")
		END;
		IF msg.pos = msg.len-1 THEN	(* last byte *)
			done := TRUE;
			d.Put1(d, SINDEX, msg.buf[msg.pos]);
			d.Put1(d, RETURN1, 0X)
		ELSIF (t * PHASEMASK) # PhaseMsgOut  THEN	(* Phase Mismatch *)
			IF traceSmall IN trace THEN KernelLog.Char("&") END;
			done := TRUE;
			d.Put1(d, RETURN1, SYSTEM.VAL(CHAR, MSGOUTPHASEMIS))
		ELSE
			d.Put1(d, CLRSINT1, SYSTEM.VAL(CHAR, {CLRREQINIT}));
			d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
			d.Put1(d, SCSIDATL, msg.buf[msg.pos]);
			INC(msg.pos);
		END;
	ELSE
		done := (t * PHASEMASK) # PhaseMsgIn;
		IF ~done THEN
			msg.buf[msg.pos] := SYSTEM.VAL(CHAR, d.Get1(d, SCSIBUSL));
			IF traceSmall IN trace THEN
				KernelLog.Char("("); KernelLog.Hex(ORD(msg.buf[msg.pos]), -2); KernelLog.Char(")")
			END;
			INC(msg.len);  INC(msg.pos);
			done := MsgParse(d, d.msg);

			d.Put1(d, CLRSINT1, SYSTEM.VAL(CHAR, {CLRREQINIT}));
			d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
			ch := d.Get1(d, SCSIDATL);
			IF ch # msg.buf[msg.pos-1] THEN  KernelLog.String("Ada7.MsgTransmit, strange"); KernelLog.Ln END;
		ELSE
			KernelLog.String("Ada7.MsgTransmit, phase mismatch "); KernelLog.Hex(SYSTEM.VAL(LONGINT, t), -2);
			KernelLog.Ln
		END
	END;
	IF done THEN
		MsgStop(d);  SeqUnpause(d)
	END
END MsgTransmit;

(* Interrupt Handling *)

PROCEDURE Done(d: Bus;  index: LONGINT);	(* command terminated / aborted: remove scb and finish *)
BEGIN
(*
	BusNameMsg(d, "Done = "); KernelLog.Int(index, 0); KernelLog.Ln;
*)
	QUntaggedSet(d, index, 0FFX);
	HSFreeScb(d, index);
	IF d.sync[index] = InUse THEN
		d.Synchronize(index, InUse, Terminated)
	ELSE
		KernelLog.String("PANIC: done called but not in use!"); KernelLog.Ln
	END;
END Done;

PROCEDURE IntCommand(d: Bus);
VAR  index: LONGINT;
BEGIN
	INC(aIntCmdCount);
	IF traceInts IN trace THEN
		BusNameMsg(d, "IntCmdCmplt"); KernelLog.Ln;
		DumpAllLists(d);
		Reg(d, RETURN1, " RETURN = "); Reg(d, SCBPTR, " SCBPTR = "); KernelLog.Ln
	END;

	d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRCMDINT}));
	index := ORD(QOutGet(d, d.out));
	WHILE index # ORD(0FFX) DO
		QOutSet(d, d.out, 0FFX);
		d.out := (d.out + 1) MOD 256;
		Done(d, index);
		index := ORD(QOutGet(d, d.out))
	END
END IntCommand;

PROCEDURE IntBrkAdr(d: Bus);
VAR  error: SET;
BEGIN
	INC(aIntBrkAdrCount);
	error := SYSTEM.VAL(SET, d.Get1(d, ERROR));
	IF PCIERRSTAT IN error THEN
		ToDo("Adaptec7.IntBrkAdr/PCIERRSTAT");
		PCIClearInt(d)
	END;
	d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRPARERR, CLRBRKADRINT}));
	(*
		Sequence is interrupted by a break position. This should not happen (I do not
		set any breakpoint).
		Restart the sequencer
	*)
(*
	ToDo("IntBrkAdr");
	d.Put1(d, SEQCTL, SYSTEM.VAL(CHAR, {FASTMODE, SEQRESET}));
*)
	SeqUnpause(d)
END IntBrkAdr;

PROCEDURE IntSeqHandle(d: Bus;  code: LONGINT);
CONST trace = {traceInts};
VAR  active, ch: CHAR;  index: LONGINT;
BEGIN
	IF FALSE & (traceInts IN trace) THEN
		BusNameMsg(d, "IntSeqHandle"); KernelLog.Ln;
		DumpAllLists(d);
		Reg(d, RETURN1, " RETURN = "); Reg(d, SCBPTR, " SCBPTR = "); Reg(d, SCBTARGETSTATUS, " TargStat = "); KernelLog.Ln
	END;
	INC(aIntSeqCount);

	d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSEQINT}));
	active := d.Get1(d, SCBPTR);  index := ORD(d.Get1(d, SCBTAG));

	CASE code OF
	| NoMatch:
			IF traceInts IN trace THEN BusNameMsg(d, "IntSeqHandle.NoMatch"); KernelLog.Ln END;
			INC(aIntSeqNoMatch);
			ch := d.Get1(d, SCSISEQ);
			d.Put1(d, SCSISEQ, SYSTEM.VAL(CHAR, SYSTEM.VAL(SET, ch) * {ENSELI, ENRSELI, ENAUTOATNP}))
	| SendReject:
			IF traceInts IN trace THEN BusNameMsg(d, "IntSeqHandle.SendReject"); KernelLog.Ln END;
			INC(aIntSeqSendReject)
	| NoIdent:
			INC(aIntSeqNoIdent);
			ToDo("IntSeqHandle.NoIdent")
	| BadPhase:
			IF traceInts IN trace THEN BusNameMsg(d, "IntSeqHandle.BadPhase"); KernelLog.Ln END;
			INC(aIntSeqBadPhase);
			IF PhaseBusFree = SYSTEM.VAL(SET, d.Get1(d, LASTPHASE)) THEN
				d.Put1(d, SEQCTL, SYSTEM.VAL(CHAR, {FASTMODE, SEQRESET}))
			END
	| ExtendedMsg:
			IF (traceInts IN trace) THEN BusNameMsg(d, "IntSeqHandle.ExtendedMsg"); KernelLog.Ln END;
			INC(aIntSeqExtendedMsg);
			MsgStart(d);
			d.msg.dir := Receiving; d.msg.pos := 0; d.msg.len := 0;
			RETURN	(*don't unpause the sequencer!*)
	| RejectMsg:
			IF (traceInts IN trace) THEN BusNameMsg(d, "IntSeqHandle.RejectMsg"); KernelLog.Ln END;
			INC(aIntSeqRejectMsg);
	| BadStatus:
			(* result of the status phase *)
			INC(aIntSeqBadStatus);
			d.Put1(d, RETURN1, 0X);
			d.status[index] := SHORT(ORD(d.Get1(d, SCBTARGETSTATUS)));
			IF traceInts IN trace THEN
				BusNameMsg(d, "IntSeq.BadStatus[");
				KernelLog.Int(index, 0);  KernelLog.String("] = "); KernelLog.Int(d.status[index], 0); KernelLog.Ln;
				Reg(d, SSTAT0, "STAT0 = "); Reg(d, SSTAT1, " STAT1 = "); Reg(d, SSTAT2, " STAT2 = "); Reg(d, SSTAT3, " STAT3 = ");
				Reg(d, ERROR, " ERROR = ");
				Reg(d, DSCOMMAND0, " CMD0 = "); Reg(d, DSPCISTATUS, " DSPCISTATUS = "); KernelLog.Ln;
				DumpAllLists(d);
				Reg(d, RETURN1, " RETURN = "); Reg(d, SCBPTR, " SCBPTR = "); Reg(d, SCBTARGETSTATUS, " TargStat = ");
				Reg(d, DFSTATUS, " DFSTATUS = "); Reg(d, DFFTHRSH, " DFFTHRSH = ");
				 KernelLog.Ln;
				DumpCurSCB(d);
			END
	| AwaitingMsg:	(* ScbMessage was in the scb control tag: now the sequencer asks if we have a message *)
			IF traceInts IN trace THEN BusNameMsg(d, "IntSeqHandle.AwaitingMsg"); KernelLog.Ln END;
			INC(aIntSeqAwaitingMsg);
			MsgStart(d);
			MsgCompose(d, index, d.msg);	(*prepare the message in the buffer, send during the scsi interrupt*)
			RETURN	(*don't unpause the sequencer!*)
	| DataOverrun:
			INC(aIntSeqDataOverrun);
			IF TRUE & (traceInts IN trace) THEN BusNameMsg(d, "IntSeqHandle.DataOverrun"); KernelLog.Ln END;
			d.status[index] := SCSI.CommandTerminated;
			d.result[index] := SCSI.Error;
	END;
	SeqUnpause(d)
END IntSeqHandle;

PROCEDURE IntScsiSeltoHandle(d: Bus);
VAR  t: SET;  index: LONGINT;
BEGIN
	INC(aIntScsiSelto);
	IF d.Get1(d, WAITINGSCBH) # d.Get1(d, SCBPTR) THEN
		INC(aIntScsiSeltoProblem1);
		BusNameMsg(d, "Selto: WAITING # SCBPTR"); KernelLog.Ln
	END;
	d.Put1(d, SCBPTR, d.Get1(d, WAITINGSCBH));
	index := ORD(d.Get1(d, SCBTAG));
	IF (traceInts IN trace) THEN
		BusNameMsg(d, "IntScsiHandle.Selto"); KernelLog.Int(index, 0); KernelLog.Ln;
		DumpCurSCB(d)
	END;

	IF d.sync[index] # InUse THEN
		INC(aIntScsiSeltoProblem2);
		ToDo("IntScsiHandle.Selto/Invalid Index");
		BusNameMsg(d, "IntScsiHandle.Selto"); KernelLog.Int(ORD(d.Get1(d, WAITINGSCBH)), 0);
		KernelLog.Char("/"); KernelLog.Int(index, 0); KernelLog.Ln;
		DumpCurSCB(d);
		DumpAllLists(d);

	ELSE
		d.status[index] := SCSI.CommandTerminated;
		d.result[index] := SCSI.TimeOut;
		EXCL(d.negotiateWidth, index);
		EXCL(d.negotiateSync, index);
		d.Put1(d, SCBCONTROL, 0X);
		d.Put1(d, MSGOUT, SCSI.MsgNoop);
		d.Put1(d, WAITINGSCBH, d.Get1(d, SCBNEXT));
		HSFreeCurrent(d);
		Done(d, index)
	END;
	d.Put1(d, SCSISEQ, 0X);
	t := SYSTEM.VAL(SET, d.Get1(d, SIMODE1));
	d.Put1(d, SIMODE1, SYSTEM.VAL(CHAR, t - {ENREQINIT, ENBUSFREE}));
	EXCL(d.flags, FlagHandlingReqInits);
	d.Put1(d, CLRSINT1, SYSTEM.VAL(CHAR, {CLRSELTIMEO,CLRBUSFREE}));
	d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
	d.Put1(d, SEQCTL, SYSTEM.VAL(CHAR, {FASTMODE, SEQRESET}));
	SeqUnpause(d)
END IntScsiSeltoHandle;

PROCEDURE IntScsiResetHandle(d: Bus);
	PROCEDURE TraverseList(d: Bus;  list: LONGINT;  debugMsg: ARRAY OF CHAR);
	VAR  ptr: CHAR;  index: LONGINT;
	BEGIN
		ptr := d.Get1(d, list);
		WHILE ptr # 0FFX DO
			d.Put1(d, SCBPTR, ptr);
			index := ORD(d.Get1(d, SCBTAG));
			KernelLog.String(debugMsg); KernelLog.Hex(ORD(ptr), -2); KernelLog.Char("/");
			KernelLog.Hex(index, -2); KernelLog.Ln;
			ptr := d.Get1(d, SCBNEXT);
			HSFreeCurrent(d);
			d.status[index] := SCSI.NotGood;
			d.result[index] := SCSI.Reset;
			Done(d, index)
		END;
	END TraverseList;
BEGIN
	(* the bus is reset. Kill outstanding commands and reconfigure bus*)
	IF TRUE & (traceInts IN trace) THEN
		BusNameMsg(d, "IntScsiResetHandle"); KernelLog.Ln;
		DumpAllLists(d);
	END;
	INC(aIntScsiReset);
	BusConfigure(d);
	TraverseList(d, WAITINGSCBH, "waiting list ");
	TraverseList(d, DISCONNECTEDSCBH, "disc list");
	d.Put1(d, CLRSINT1, SYSTEM.VAL(CHAR, {CLRSCSIRSTI}));
	d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
	SeqUnpause(d)
END IntScsiResetHandle;

PROCEDURE IntScsiHandle(d: Bus);
VAR  status: SET;
BEGIN
	IF traceInts IN trace THEN
		BusNameMsg(d, "IntScsiHandle"); KernelLog.Ln;
		DumpAllLists(d);
		Reg(d, SSTAT1, "STAT1 = ");  Reg(d, SIMODE1, " SIMODE1 = ");
		Reg(d, RETURN1, " RETURN = "); Reg(d, SCBPTR, " SCBPTR = "); KernelLog.Ln
	END;
	INC(aIntScsiCount);
	status := SYSTEM.VAL(SET, d.Get1(d, SSTAT1));
	IF SCSIRSTI IN status THEN
			IntScsiResetHandle(d)
	ELSIF SELTO IN status THEN
			IntScsiSeltoHandle(d)
	ELSIF BUSFREE IN status THEN
			INC(aIntScsiBusFree);
			ToDo("IntScsiHandle.BusFree");
			Reg(d, LASTPHASE, "LASTPHASE"); KernelLog.Ln;
			d.Put1(d, CLRSINT1, SYSTEM.VAL(CHAR, {CLRBUSFREE}));
			d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
			d.Put1(d, SEQCTL, SYSTEM.VAL(CHAR, {FASTMODE, SEQRESET}));
			SeqUnpause(d)
	ELSIF SCSIPERR IN status THEN
			INC(aIntScsiParity);
			IF TRUE & (traceInts IN trace) THEN BusNameMsg(d, "IntScsiHandle.SCSIPERR"); KernelLog.Ln END;
			d.Put1(d, CLRSINT1, SYSTEM.VAL(CHAR, {CLRSCSIPERR}));
			d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
			SeqUnpause(d)
	ELSIF (REQINIT IN status) & (FlagHandlingReqInits IN d.flags) THEN
			IF traceInts IN trace THEN  BusNameMsg(d, "IntScsiHandle.ReqInit"); KernelLog.Ln  END;
			INC(aIntScsiReqInit);
			MsgTransmit(d, d.msg);
	ELSIF (1 IN status) & (FlagHandlingReqInits IN d.flags) THEN
			INC(aIntScsiSpecial);
			KernelLog.Char("("); KernelLog.Hex(ORD(d.Get1(d, SCSIBUSL)), -2); KernelLog.Char(")");
			ToDo("IntScsiHandle.Unexpected message");
	ELSE
			INC(aIntScsiUnknown);
			ToDo("IntScsiHandle.Unknown");
			KernelLog.String("SSTAT1 = "); KernelLog.Hex(SYSTEM.VAL(LONGINT, status), -2); KernelLog.Ln;
			d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT}));
			SeqUnpause(d)
	END
END IntScsiHandle;

PROCEDURE IntClear(d: Bus);
BEGIN
	d.Put1(d, CLRSINT0, SYSTEM.VAL(CHAR, {CLRSELDO, CLRSELDI, CLRSELINGO}));
	d.Put1(d, CLRSINT1, SYSTEM.VAL(CHAR, CLRSINT1ALL));
	d.Put1(d, CLRINT, SYSTEM.VAL(CHAR, {CLRSCSIINT, CLRSEQINT, CLRBRKADRINT, CLRPARERR}))
END IntClear;

PROCEDURE IntInstall(d: Bus);
BEGIN
	IntClear(d);
	IF interrupt[d.irq] = NIL THEN  NEW(interrupt[d.irq], d.irq)  END;
	d.next := interrupt[d.irq].root;  interrupt[d.irq].root := d;
	BusNameMsg(d, "IntHandler installed, "); KernelLog.Int(d.irq, 0); KernelLog.Ln;
END IntInstall;

(* ScbControl - Compote the control byte for dev-th device *)

PROCEDURE ScbControl(d: Bus;  dev: LONGINT): CHAR;
BEGIN
	IF dev IN d.negotiateWidth THEN
		IF traceSmall IN trace THEN KernelLog.Char("W") END;
		(*EXCL(dev, d.negotiateWidth);*) 	(*done in message handling! Only one msg per device*)
		RETURN SYSTEM.VAL(CHAR, {HwScbMessage})
	ELSIF dev IN d.negotiateSync THEN
		IF traceSmall IN trace THEN KernelLog.Char("S") END;
		RETURN SYSTEM.VAL(CHAR, {HwScbMessage})
	END;
	RETURN 0X
END ScbControl;

(* BusSchedule - Schedule a new command for execution *)

PROCEDURE BusSchedule(d: Bus;  VAR c: SCSI.Command);
CONST  MaxRange = 64;
VAR  index, num, useRange, sgaddr: LONGINT; target: CHAR;  range: ARRAY 2, MaxRange+1 OF Machine.Range;
	(*dog: Watchdog;*)
	cmd: ARRAY 2, 16 OF CHAR;  cmdBlock: LONGINT;
BEGIN
	INC(aExecute);
	IF (0 > c.target) OR (c.target >= d.width) OR ~(c.lun IN {0..7}) OR ~(c.chan IN {0,1}) THEN	(*Invalid Destination*)
		d.Synchronize(c.target, InUse, Free);
		HALT(200)
	END;
	IF (c.dlen > MaxRange * 4096) THEN		(* max pages: change range to increment this limit *)
		d.Synchronize(c.target, InUse, Free);
		HALT(201)
	END;
	(*IF traceDog IN trace THEN NEW(dog, 1000, 5, d, c.target) END;*)
	index := c.target;	(* this will be the scb used! *)
	IF traceSmall IN trace THEN
		KernelLog.Char("{"); KernelLog.Hex(ORD(c.cmd[0]), -2); KernelLog.Char("}")
	END;
	IF traceCmds IN trace THEN
		BusNameMsg(d, "schedule ["); KernelLog.Int(index, 0); KernelLog.String("] =  ");
		KernelLog.Hex(ORD(c.cmd[0]), -2);
	END;
	d.status[index] := SCSI.Good;
	d.result[index] := SCSI.OK;
	target := MakeTarget(c.target, c.chan, c.lun);
	HSSet(d, index, HScontrol, ScbControl(d, index));
	HSSet(d, index, HStarget, target);
	Machine.TranslateVirtual(SYSTEM.ADR(c.cmd[0]), c.clen, num, range[0]);
	ASSERT(num # 0, 202);
	IF num > 1 THEN	(* The command buffer doesn't fit on one page, create own temp buffer *)
		cmdBlock := -1;
		REPEAT
			INC(cmdBlock);
			Machine.TranslateVirtual(SYSTEM.ADR(cmd[cmdBlock, 0]), c.clen, num, range[0]);
		UNTIL num = 1;
		SYSTEM.MOVE(SYSTEM.ADR(c.cmd[0]), SYSTEM.ADR(cmd[cmdBlock, 0]), c.clen);
	END;
	HSSet4(d, index, HScmdPtr, range[0, 0].adr);
	HSSet(d, index, HScmdLen, CHR(c.clen));

	IF c.dlen = 0 THEN
		IF traceCmds IN trace THEN
			KernelLog.String("  No Data "); KernelLog.Ln
		END;
		HSSet(d, index, HSSGcount, 0X);
		HSSet4(d, index, HSSGptr, 0);
		HSSet4(d, index, HSdataPtr, 0);
		HSSet4(d, index, HSdataCnt, 0)
	ELSE
		ASSERT(c.dataAddr # 0);
		(* the data can be spread across many physical pages, I thus use a scatter-gather list
			for the data. Now the list itself could also be on a page boundary: I declare two adjacent ranges
			and at least one of them will fit on one page. *)
		Machine.TranslateVirtual(SYSTEM.ADR(range[0, 0]), 16*SYSTEM.SIZEOF(Machine.Range), num, range[0]);
		IF num = 1 THEN	(* whole range[0] fits in one memory page *)
			useRange := 0;
		ELSE
			Machine.TranslateVirtual(SYSTEM.ADR(range[1, 0]), 16*SYSTEM.SIZEOF(Machine.Range), num, range[0]);
			ASSERT(num = 1, 203);
			useRange := 1;
			IF traceCmds IN trace THEN KernelLog.Char("+")END;
		END;
		sgaddr := range[0, 0].adr;
		Machine.TranslateVirtual(c.dataAddr, c.dlen, num, range[useRange]);
		ASSERT(num # 0, 204);
		IF traceCmds IN trace THEN
			IF num > 1 THEN KernelLog.Char("%") ELSE KernelLog.Char("$") END
		END;
		HSSet(d, index, HSSGcount, CHR(num));
		HSSet4(d, index, HSSGptr, sgaddr+SYSTEM.SIZEOF(Machine.Range));	(*pass address to 2nd block*)
		HSSet4(d, index, HSdataPtr, range[useRange, 0].adr);
		HSSet4(d, index, HSdataCnt, range[useRange, 0].size);
		IF traceCmds IN trace THEN
			KernelLog.String("  Data = "); KernelLog.Int(c.dlen, 0);
			KernelLog.Int(useRange, 2);
			KernelLog.Int(num, 2);
			KernelLog.Ln;
		END;
	END;
	QUntaggedSet(d, ORD(target), CHR(index));
	QInSet(d, d.in, CHR(index));
	d.in := (d.in+1) MOD 256;
	IF FeatQueueRegs IN d.features THEN
		d.Put1(d, HNSCBQOFF, CHR(d.in))
	ELSE
		SeqPause(d);
		d.Put1(d, KERNELQINPOS, CHR(d.in));
		SeqUnpause(d)
	END;
	d.Synchronize(c.target, Terminated, Terminated);	(* don't free: let bus.Submit copy results *)
	(*IF traceDog IN trace THEN dog.stop := TRUE END;*)	(*kill watchdog*)
	IF traceCmds IN trace THEN
		BusNameMsg(d, "schedule ["); KernelLog.Int(index, 0); KernelLog.String("] result = ");
		KernelLog.Int(d.status[index], 0); KernelLog.Char("/"); KernelLog.Int(d.result[index], 0);
		KernelLog.Ln
	END;
END BusSchedule;

(* Reset Bus *)

PROCEDURE BusReset(d: Bus);
(* if bus reset is called during bus operations, then the timer must be made local to the bus to avoid race conditions *)
VAR  mode1, seq: SET;
BEGIN
	BusNameMsg(d, "BusR");
	mode1 := SYSTEM.VAL(SET, d.Get1(d, SIMODE1));

	d.Put1(d, SIMODE1, SYSTEM.VAL(CHAR, mode1 - {ENSCSIRST}));
	d.Put1(d, SCSISEQ, SYSTEM.VAL(CHAR, {SCSIRSTO}));		(*d.Put1(d, SCSISEQ, d.Get1(d, SCSISEQ) + {SCSIRSTO});*)
	KernelLog.Char("e");
	REPEAT
		timer.Sleep(5);
		seq :=SYSTEM.VAL(SET,  d.Get1(d, SCSISEQ))
	UNTIL (SCSIRSTO IN seq);
	KernelLog.Char("s");
	timer.Sleep(10);
	d.Put1(d, SCSISEQ, 0X);
	timer.Sleep(5);
	KernelLog.Char("e");
	IntClear(d);
	d.Put1(d, SIMODE1, SYSTEM.VAL(CHAR, mode1 + {ENSCSIRST}));
	KernelLog.Char("t"); KernelLog.Ln
END BusReset;

PROCEDURE BusChipReset(d: Bus);
VAR  t: Kernel.MilliTimer;
BEGIN
	IF {traceCalls, traceReset} * trace # {} THEN  BusNameMsg(d, "ResetChip"); KernelLog.Ln  END;
	d.Put1(d, HCNTRL, SYSTEM.VAL(CHAR, {PAUSE, CHIPRST}));
	Kernel.SetTimer(t, 1000);	(*1 sec*)
	REPEAT
	UNTIL (CHIPRST IN SYSTEM.VAL(SET, d.Get1(d, HCNTRL))) OR Kernel.Expired(t);
	SeqPause(d);	(*pause & clear rst bit*)
END BusChipReset;

PROCEDURE BusDetectWidth(d: Bus);
VAR s: SET;
BEGIN
	s := SYSTEM.VAL(SET, d.Get1(d, SBLKCTL));
	s := s * {SELWIDE, SELBUSB};
	d.wide := FALSE;  d.width := 8;	(*default values*)
	IF s = {SELWIDE} THEN
		BusNameMsg(d, "bus is wide"); KernelLog.Ln;
		INCL(d.features, FeatWide);
		d.wide := TRUE;  d.width := 16
	ELSIF s = {SELBUSB} THEN
		BusNameMsg(d, "multichannel card"); KernelLog.Ln;
		IF ~(FlagMultiChannel IN d.flags) THEN
			BusNameMsg(d, "WARNING: this card is not declared as multichannel"); KernelLog.Ln
		END;
		INCL(d.features, FeatTwin);
		INCL(d.flags, FlagMultiChannel)
	ELSIF s # {} THEN
		BusNameMsg(d, "PANIC: this card is both wide and multichannel"); KernelLog.Ln;
		HALT(99)
	END
END BusDetectWidth;

PROCEDURE BusConfigureChannels(d: Bus);	(*special setups for multichannel boards*)
VAR  chip: SHORTINT; set: SET;
BEGIN
	chip := d.chip;
	IF  FlagMultiChannel IN d.flags THEN
		IF chip IN {AIC7870, AIC7880} THEN
			IF d.slotNo = 5 THEN  INCL(d.flags, FlagChnlB)			(*3940, 3940Ultra*)
			ELSIF d.slotNo = 8 THEN INCL(d.flags, FlagChnlB)			(*3985, 3985Ultra*)
			ELSIF d.slotNo = 12 THEN INCL(d.flags, FlagChnlC)			(*3985, 3985Ultra*)
			END
		ELSIF chip IN {AIC7895, AIC7896} THEN
			IF d.devNo # 0 THEN  INCL(d.flags, FlagChnlB) END;
		END;
	END;
	(*set of DEVCONFIG.SCBSIZE32 for 7895 done in InitPCIBus*)

	set := SYSTEM.VAL(SET, d.Get1(d, DSCOMMAND0));
	IF chip IN {AIC7890, AIC7896} THEN
		d.Put1(d, SCAMCTL, 0X);
		set := set + {CACHETHEN, MPARCKEN, USCBSIZE32, CIOPARCKEN} - {DPARCKEN}
	ELSIF chip IN {AIC7850, AIC7860} THEN
		set := set + {CACHETHEN, MPARCKEN}- {DPARCKEN}
	END;
	d.Put1(d, DSCOMMAND0, SYSTEM.VAL(CHAR, set))
END BusConfigureChannels;

PROCEDURE BusConfigure(d: Bus);
VAR i: LONGINT;
BEGIN
	(* Init Speed/Width/Scratch RAM *)
	d.scsiId := 7;	(* host card id = 7 *)
	i := d.width;
	IF ~(traceNoHandling IN trace) THEN
		IF d.wide THEN
			d.negotiateWidth := {0 .. d.width-1};
		END;
		d.negotiateSync := {0 .. d.width-1}
	END;
	WHILE i > 0 DO
		DEC(i);
		d.Put1(d, TARGSCSIRATE+i, 0X);	(* dev[i] = async / 8-bit wide*)
		IF FeatUltra2 IN d.features THEN
			d.Put1(d, TARGOFFSET+i, 0X);	(* dev[i] = no offset *)
		END
	END;
	d.Put1(d, ULTRAENB, 0X);		(* no ultra *)
	d.Put1(d, ULTRAENB+1, 0X);
	d.Put1(d, DISCDSB, 0X);		(* no disconnect *)
	d.Put1(d, DISCDSB+1, 0X);
	d.Put1(d, SCSICONF, SYSTEM.VAL(CHAR, {ENSPCHK, RESETSCSI}));
	d.Put1(d, SCSICONF+1, CHR(d.scsiId))
END BusConfigure;

PROCEDURE BusInit(d: Bus);
VAR  sblkctr: SET;

	PROCEDURE InitChannel;
	BEGIN
		IF FeatUltra2 IN d.flags THEN
			d.Put1(d, SCSIIDULTRA2, CHR(d.scsiId))
		ELSE
			d.Put1(d, SCSIID, CHR(d.scsiId))
		END;
		d.Put1(d, SXFRCTL0, SYSTEM.VAL(CHAR, {DFON, SPIOEN}));
		d.Put1(d, SXFRCTL1, SYSTEM.VAL(CHAR, {ENSPCHK, ENSTIMER, ACTNEGEN, STPWEN}));
		d.Put1(d, SIMODE0, 0X);
		d.Put1(d, SIMODE1, SYSTEM.VAL(CHAR,{ENSELTIMO, ENSCSIRST, ENSCSIPERR}));
		d.Put1(d, SCSIRATE, 0X);
	END InitChannel;

BEGIN
	IF d.chip = AIC7770 THEN  d.Put1(d, BCTL, SYSTEM.VAL(CHAR, {ENABLE}))  END;
	sblkctr := SYSTEM.VAL(SET, d.Get1(d, SBLKCTL));
	sblkctr := sblkctr - {AUTOFLUSHDIS, DIAGLEDEN};
	d.Put1(d, SBLKCTL, SYSTEM.VAL(CHAR, sblkctr));

	d.Put1(d, MSGOUT, SCSI.MsgNoop);
	d.Put1(d, LASTMSG, SCSI.MsgNoop);
	d.Put1(d, SEQFLAGS, 0X);

	d.Put1(d, TMODECMDADDR, 0X);
	d.Put1(d, TMODECMDADDR+1, 0X);
	d.Put1(d, TMODECMDADDR+2, 0X);
	d.Put1(d, TMODECMDADDR+3, 0X);
	d.Put1(d, TMODECMDADDRNEXT, 0X);

	IF FeatTwin IN d.features THEN
		d.Put1(d, SBLKCTL, SYSTEM.VAL(CHAR, sblkctr+{SELBUSB}));
		InitChannel;
		BusReset(d);
		d.Put1(d, SBLKCTL, SYSTEM.VAL(CHAR, sblkctr))
	END;
	InitChannel;
	IF FeatUltra2 IN d.features THEN
		d.Put1(d, DFFTHRSH, SYSTEM.VAL(CHAR, WRDFFTHRSH75 + RDDFFTHRSH75))
	ELSE
		d.Put1(d, DSPCISTATUS, SYSTEM.VAL(CHAR, DFTHRSH100))
	END;
	BusReset(d);
	d.interruptable := TRUE;
END BusInit;

(* Bus detection *)

PROCEDURE Detect;
VAR  dummy, idx, bus, dev, slot, res, signature, i: LONGINT;  host: Bus;
BEGIN
	IF traceCalls IN trace THEN KernelLog.String("Ada7: Detect"); KernelLog.Ln  END;
	IF PCI.Done = PCI.PCIPresent(dummy, dummy, dummy) THEN
		idx := 0;
		WHILE PCI.Done = PCI.FindPCIClassCode(010000H (*scsi card*), idx, bus, dev, slot) DO
			res := PCI.ReadConfigDword(bus, dev, slot, PCI.DevReg, signature);	(*signature = VendID / DevID*)
			i := LEN(card)-1;
			REPEAT  DEC(i)  UNTIL  (i < 0) OR (card[i].signature = signature);
			IF i >= 0 THEN
				NEW(host, i, bus, dev, slot);
				BusNameMsg(host, card[i].name); KernelLog.Ln;
				PCIInit(host, bus, dev, slot);
				BusChipReset(host);
				BusDetectWidth(host);
				BusConfigureChannels(host);
				BusConfigure(host);
				HSInit(host);
				QInit(host);
				SeqLoad(host);
				BusInit(host);
				IntInstall(host);
				SeqUnpause(host);
				IF ~(traceNoRegister IN trace) THEN
					timer.Sleep(1000);		(*wait for the bus reset to complete*)
					SCSI.RegisterBus(host)
				END
			END;
			INC(idx)
		END
	ELSE
		KernelLog.String("Ada7: No PCI present"); KernelLog.Ln
	END;
END Detect;

(* Module Termination Functions *)

PROCEDURE Cleanup;
VAR  d: Bus;  i: LONGINT;
BEGIN
	IF Modules.shutdown # Modules.None THEN  RETURN  END;
	FOR i := 0 TO 31 DO
		IF interrupt[i] # NIL THEN
			d := interrupt[i].root;
			WHILE d # NIL DO
				SeqPause(d);
				SCSI.RemoveBus(d);
				ToDo("Ada7: free buffers");
				BusNameMsg(d, "Removed"); KernelLog.Ln;
				d := d.next
			END;
			interrupt[i].Cleanup
		END
	END;
	firstBus := NIL;
END Cleanup;

PROCEDURE Init;
CONST
	ultraFlags = {FlagPageSCB, FlagNewEepromFMT, FlagBiosEnabled};
	ultra2Features = {FeatMoreSRAM, FeatUltra2, FeatCmdChan, FeatQueueRegs, FeatSGPreload};

	PROCEDURE Card(i: LONGINT; sig: HUGEINT;  chip: SHORTINT;  flags, features: SET;  name: ARRAY OF CHAR);
	BEGIN
		card[i].signature := SHORT(sig);
		card[i].chip := chip;
		card[i].flags := flags;  card[i].features := features;
		COPY(name, card[i].name)
	END Card;

	PROCEDURE Table(i: LONGINT;  period: SHORTINT;  rate, u2rate: CHAR;  speed: LONGINT);
	BEGIN
		rateTable[i].period := period;
		rateTable[i].rate := rate;
		rateTable[i].u2rate := u2rate;
		rateTable[i].speed := speed
	END Table;

BEGIN
	IF traceAIC7890 IN trace THEN
		Card(15, 001F9005H, AIC7890, ultraFlags, ultra2Features, "Adaptec AIC-7890/1 Ultra2");
	ELSIF traceAIC7880 IN trace THEN
		Card(9, 80789004H, AIC7880, {FlagPageSCB, FlagBiosEnabled}, {FeatUltra}, "Adaptec AIC-7880 Ultra");
	ELSIF traceAHA2940 IN trace THEN
		Card(10, 81789004H, AIC7880, {FlagPageSCB, FlagBiosEnabled}, {FeatUltra}, "Adaptec AHA-294X Ultra");
	ELSE
	Card(0, 50789004H, AIC7850, {}, {FeatSpioCap}, "Adaptec AIC-7850");
	Card(1, 55789004H, AIC7850, {FlagPageSCB}, {FeatSpioCap}, "Adaptec AIC-7855");
	Card(2, 60789004H, AIC7860, ultraFlags, {FeatUltra, FeatSpioCap}, "Adaptec AIC-7860 Ultra");
	Card(3, 61789004H, AIC7860, ultraFlags, {FeatUltra, FeatSpioCap}, "Adaptec AHA-2940A Ultra");
	Card(4, 70789004H, AIC7870, {FlagPageSCB, FlagBiosEnabled}, {}, "Adaptec AIC-7870");
	Card(5, 71789004H, AIC7870, {FlagPageSCB, FlagBiosEnabled}, {}, "Adaptec AHA-294X");
	Card(6, 72789004H, AIC7870, {FlagPageSCB, FlagBiosEnabled, FlagMultiChannel}, {}, "Adaptec AHA-394X");
	Card(7, 73789004H, AIC7870, {FlagPageSCB, FlagBiosEnabled, FlagMultiChannel}, {}, "Adaptec AHA-398X");
	Card(8, 74789004H, AIC7870, {FlagPageSCB, FlagBiosEnabled}, {}, "Adaptec AHA-2944");
	Card(9, 80789004H, AIC7880, {FlagPageSCB, FlagBiosEnabled}, {FeatUltra}, "Adaptec AIC-7880 Ultra");
	Card(10, 81789004H, AIC7880, {FlagPageSCB, FlagBiosEnabled}, {FeatUltra}, "Adaptec AHA-294X Ultra");
	Card(11, 82789004H, AIC7880, {FlagPageSCB, FlagBiosEnabled, FlagMultiChannel}, {FeatUltra}, "Adaptec AHA-394X Ultra");
	Card(12, 83789004H, AIC7880, {FlagPageSCB, FlagBiosEnabled, FlagMultiChannel}, {FeatUltra}, "Adaptec AHA-398X Ultra");
	Card(13, 84789004H, AIC7880, {FlagPageSCB, FlagBiosEnabled}, {FeatUltra},"Adaptec AHA-2944 Ultra");
	Card(14, 78959004H, AIC7895, ultraFlags + {FlagMultiChannel}, {FeatMoreSRAM, FeatUltra, FeatCmdChan}, "Adaptec AIC-7895 Ultra");
	Card(15, 001F9005H, AIC7890, ultraFlags, ultra2Features, "Adaptec AIC-7890/1 Ultra2");
	Card(16, 00109005H, AIC7890, ultraFlags, ultra2Features, "Adaptec AHA-294X Ultra2");
	Card(17, 005F9005H, AIC7896, ultraFlags + {FlagMultiChannel}, ultra2Features, "Adaptec AIC-7896/7 Ultra2");
	Card(18, 00509005H, AIC7896, ultraFlags + {FlagMultiChannel}, ultra2Features, "Adaptec AHA-394X Ultra2");
	END;
	Table(0, 10, 0FFX, 13X, 400);
	Table(1, 11, 0FFX, 14X, 200);
	Table(2, 12, 0X, 15X, 200);
	Table(3, 15, 10X, 16X, 160);
	Table(4, 18, 20X, 17X, 133);
	Table(5, 25, 00X, 18X, 100);
	Table(6, 31, 10X, 19X, 80);
	Table(7, 37, 20X, 1AX, 67);
	Table(8, 43, 30X, 1BX, 57);
	Table(9, 50, 40X, 1CX, 50);
	Table(10, 56, 50X, 0FFX, 44);
	Table(11, 62, 60X, 0FFX, 40);
	Table(12, 68, 70X, 0FFX, 36);
	Modules.InstallTermHandler(Cleanup)
END Init;

(*
PROCEDURE WriteBusState(d: Bus);
BEGIN
	BusName(d);
	Reg(d, ERROR, "ERROR = ");
	Reg(d, INTSTAT, " INTSTAT = ");
	Reg(d, HCNTRL, " HCNTRL = ");
(*
	SeqPause(d);
	Reg(d, SIMODE0, " SIMODE0 = ");
	Reg(d, SIMODE1, " SIMODE1 = ");
	SeqUnpause(d);
*)
	KernelLog.Ln;
END WriteBusState;

PROCEDURE WriteScsiState(d: Bus);
BEGIN
	SeqPause(d);
	BusNameMsg(d, "Scsi State");
	Reg(d, SSTAT0, " S0=");
	Reg(d, SSTAT1, " S1=");
	Reg(d, SSTAT2, " S2=");
	Reg(d, SIMODE0, " M0=");
	Reg(d, SIMODE1, " M1=");
	KernelLog.Ln;
	SeqUnpause(d);
END WriteScsiState;

PROCEDURE WriteBusConfig(d: Bus);
VAR i: LONGINT;  name: ARRAY 8 OF CHAR; Hex: ARRAY 17 OF CHAR;
BEGIN
	Hex := "0123456789ABCDEF";
	BusNameMsg(d, d.name); KernelLog.Ln;
	Reg(d, ULTRAENB, "Ultra0=");  Reg(d, ULTRAENB+1, " Ultra1=");
	Reg(d, DISCDSB, " Disc0=");  Reg(d, DISCDSB+1, " Disc1=");
	Reg(d, SCSICONF, " Conf0=");  Reg(d, SCSICONF+1, " Conf1="); KernelLog.Ln;
	name := " rateX=";
	FOR i := 0 TO d.width-1 DO
		name[5] := Hex[i];
		Reg(d, TARGSCSIRATE+i, name);
		IF FeatUltra2 IN d.features THEN  Reg(d, TARGOFFSET+i, "/")  END;
		IF i MOD 4 = 3 THEN KernelLog.Ln END
	END;
END WriteBusConfig;

PROCEDURE WriteBusConfig2(d: Bus);
VAR i: LONGINT;  name: ARRAY 8 OF CHAR; Hex: ARRAY 17 OF CHAR;

	PROCEDURE Bit(d: Bus;  base, bit: LONGINT;  msg: ARRAY OF CHAR);
	VAR  set: SET;
	BEGIN
		set := SYSTEM.VAL(SET, d.Get1(d, base + bit DIV 8));
		IF (bit MOD 8) IN set THEN  KernelLog.String(msg)  END
	END Bit;

BEGIN
	Hex := "0123456789ABCDEF";
	BusNameMsg(d, d.name); KernelLog.Ln;
	FOR i := 0 TO d.width-1 DO
		KernelLog.String("Device"); KernelLog.Char(Hex[i]);
		Reg(d, TARGSCSIRATE+i, " rate = ");
		IF FeatUltra2 IN d.features THEN  Reg(d, TARGOFFSET+i, "/")  END;
		Bit(d, ULTRAENB, i, " ultra");  Bit(d, DISCDSB, i, " disc");
		KernelLog.Ln
	END;
END WriteBusConfig2;

PROCEDURE WriteConfig*;
BEGIN
	WriteBusConfig(firstBus);
	WriteBusConfig2(firstBus);
END WriteConfig;

PROCEDURE WriteLists*;
VAR  d: Bus;
BEGIN
	d := firstBus;
	SeqPause(d);
	DumpAllLists(d);
	SeqUnpause(d)
END WriteLists;

PROCEDURE FullDump*;
VAR d: Bus;
BEGIN
	d := firstBus;
	BusNameMsg(d, "Full Dump!"); KernelLog.Ln;
	KernelLog.Memory(d.base, 100H); KernelLog.Ln
END FullDump;

PROCEDURE BusState*;
BEGIN	WriteBusState(firstBus);
END BusState;

PROCEDURE ScsiState*;
BEGIN	WriteScsiState(firstBus)
END ScsiState;

PROCEDURE Test*;
VAR  d: Bus;  res: LONGINT;
BEGIN
	d := firstBus;
	KernelLog.String("Adaptec7.Test"); KernelLog.Int(aCount, 0); KernelLog.Ln;
	INC(aCount);
	SCSI.DoTestUnitReady(d, 0, 0, res);
	KernelLog.String("res = "); KernelLog.Int(res, 0); KernelLog.Ln
END Test;

PROCEDURE Scan*;
VAR  d: Bus;  res: LONGINT;  i: SHORTINT;  inq: SCSI.InquiryData;
BEGIN
	d := firstBus;
	KernelLog.String("Adaptec7.Scan"); KernelLog.Int(aCount, 0); KernelLog.Ln;
	SCSI.DoInquiry(d, SHORT(SHORT(aCount MOD 8)), 0, inq, res);
	INC(aCount);
END Scan;

PROCEDURE Stress*;
VAR  d: Bus;  i, key, code, res: LONGINT;  data: ARRAY 64*1024 OF CHAR;
	dev: Disks.DeviceTable;
BEGIN
	d := firstBus;
	Disks.GetRegistered(dev);
	i := 0;
	WHILE (i < LEN(dev)) & (dev[i].name # "SCSI0.2") DO  INC(i)  END;
	KernelLog.String("Adaptec7.Stress /  No Data"); KernelLog.Ln;
	FOR i := 0 TO 1000 DO
		SCSI.DoTestUnitReady(d, SHORT(SHORT(i MOD 8)), 0, res)
	END;
	KernelLog.String("Adaptec7.Stress /   Small Data"); KernelLog.Ln;
	FOR i := 0 TO 1000 DO
		SCSI.DoSense(d, SHORT(SHORT(i MOD 8)), 0, key, code, res)
	END;
	KernelLog.String("Adaptec7.Stress /   Medium Data"); KernelLog.Ln;
	FOR i := 0 TO 1000 DO
		dev[i].Transfer(Disks.Read, i*5, 1, data, 0, res)
	END;
	KernelLog.String("Adaptec7.Stress /   Big Data"); KernelLog.Ln;
	FOR i := 0 TO 1000 DO
		dev[i].Transfer(Disks.Read, i*5, 16, data, 0, res)
	END;
	KernelLog.String("Adaptec7.Stress /   Done"); KernelLog.Ln;
END Stress;

PROCEDURE Speed*;
VAR  dev: Disks.DeviceTable;  c, i, run, elap, size, res: LONGINT;  data: ARRAY 64*1024 OF CHAR;
	t: Kernel.MilliTimer;
BEGIN
	Disks.GetRegistered(dev);
	i := 0;
	WHILE (i < LEN(dev)) & (dev[i].name # "SCSI0.2") DO  INC(i)  END;

	dev[i].GetSize(size, res);
	KernelLog.String("Size is = "); KernelLog.Int(size, 0); KernelLog.Ln;

	FOR run := 0 TO 7 DO
		size := ASH(1, run);
		Kernel.SetTimer(t, 0);
		FOR c := 0 TO 2047 DO
			dev[i].Transfer(Disks.Read, ASH(c, run), size, data, 0, res)
		END;
		elap := Kernel.Elapsed(t);
		KernelLog.String("blkSize = "); KernelLog.Int(512*size, 0);
		KernelLog.String("  elapsed = "); KernelLog.Int(elap, 0);
		KernelLog.String(" rate (KB/s) = "); KernelLog.Int(1000*1024*size DIV elap, 0);
		KernelLog.Ln;
	END
END Speed;

PROCEDURE Register*;
BEGIN
	SCSI.RegisterBus(firstBus)
END Register;
*)
PROCEDURE KillAll*;		(*last resort when a command dies... *)
VAR  i, j: LONGINT;  d: Bus;
BEGIN
	FOR i := 0 TO 31 DO
		IF interrupt[i] # NIL THEN
			d := interrupt[i].root;
			WHILE d # NIL DO
				FOR j := 0 TO d.width-1 DO
					d.Synchronize(j, d.sync[j], Terminated)
				END;
				d := d.next
			END
		END
	END
END KillAll;

PROCEDURE Install*;
END Install;

BEGIN
	KernelLog.String("Adaptec7 - 0.41 / prk"); KernelLog.Ln;
	NEW(timer);
	Init;
	Detect;
END Adaptec7.

System.Free Adaptec7 ~		System.Free SCSI ~
System.State Adaptec7 ~

	Adaptec7.Cleanup

	Adaptec7.Detect

	Adaptec7.FullDump
	Adaptec7.WriteConfig
	Adaptec7.BusState			Adaptec7.ScsiState
	Adaptec7.WriteLists
	Adaptec7.Test
	Adaptec7.Speed
	Adaptec7.Scan
	Adaptec7.Register


	!OFSTools.Mount TEST AosFS SCSI0.2#2,R ~
	OFSTools.Mount USER AosFS SCSI0.0#6,R ~
	OFSTools.Unmount ^ ~
	OFSTools.Watch
	System.Directory USER:* ~

(*
	15.10.01	0.41	prk	wrong constant offset for DFF_THRSH discovered and corrected
	26.03.01	0.4	prk	adapted for the new Machine. Queue and SCB Buffers now anchored in Bus structure
	20.02.01	0.3	prk	own Timer removed, use Kernel.Timer instead, Watchdog commented out


	Failures:
	200:	Invalid Target
	201:	Data Size > 64KB
	202:	TranslateVirtual, num = 0 for the cmd buffer
	203:	TranslateVirtual, num invalid for the range buffer
	204:	TranslateVirtual, num = 0 for the data buffer

	Todos:
	300:	Command spans page boundary		[obsolete]
*)

(*
ToDo:
2 Disconnection
2 Ultra handling in SetSync
3 use CHAR consts instead of Bits/SETs -> use memory IO always (port IO is not needed)
4 BusCleanup: free buffers
*)