MODULE AM79C970;
(* Aos Driver for AMD 79C970 (simulated by VMware) *)

IMPORT
	SYSTEM, Machine, PCI, Objects, Modules, Plugins, Network, KernelLog;

CONST
	Name = "AM79C970#";
	Description = "AMD Network Driver, Chipversion AM79C970A";

	MaxETHFrameSize = 1514;
	TxBufSize = 1600; (* Max size of tx buffers *)
	RxBufSize =1536; (* Max size for received frames *)

	RxRingSize = 32;
	TxRingSize = 16;

	ModeDRX = 0; (* disable rx *)
	ModeDTX = 1; (* disable tx *)
	ModePROM = 15; (* promiscuous mode *)

	(* 16 bit resources *)
	RAP=12H;
	RDP=10H;
	BDP=16H;
	RESET=14H;

	CSR0 = 0; (* control and status register *)
	OWN = 31;


VAR
	installed: LONGINT; (* number of installed devices *)
	installFinished: BOOLEAN;
	outPackets, inPackets, missedFrames: LONGINT;

TYPE

	(* 32bit Initializationblock, p. 989*)
	InitializationBlock = RECORD
		modeTlenRlen : SET;
		padr : ARRAY 3 OF INTEGER;
		reserved : INTEGER;
		ladr : ARRAY 2 OF SET;
		rdra : Machine.Address32;
		tdra : Machine.Address32;
	END;


	(* buffer for transmission*)
	TxBuffer = POINTER TO ARRAY TxBufSize OF CHAR;


	(* LinkDevice: interface to Bluebottle *)
	LinkDevice = OBJECT (Network.LinkDevice)
		VAR
			ctrl: Controller;
			hw: LONGINT;

		PROCEDURE Linked*(): LONGINT;
		BEGIN
			IF ctrl.Linked() THEN
				RETURN Network.LinkLinked;
			ELSE
				RETURN Network.LinkNotLinked;
			END;
		END Linked;


		PROCEDURE DoSend*(dst: Network.LinkAdr; type: LONGINT; VAR l3hdr, l4hdr, data: ARRAY OF CHAR;  h3len, h4len, dofs, dlen: LONGINT);
		VAR
			i, pos: LONGINT;
			tBuf: TxBuffer;
			flags1: SET;

		BEGIN
		INC(outPackets);
			tBuf := ctrl.txBufRing[ctrl.txIndex];

			ctrl.DisableInterrupts();

			pos := 0;
			(* set up ethernet header *)
			FOR i := 0 TO 6 - 1 DO tBuf[pos + i] := dst[i] END;
			INC(pos, 6);

			FOR i := 0 TO 6 - 1 DO tBuf[pos + i] := local[i] END;
			INC(pos, 6);

			tBuf[pos] := CHR(type DIV 100H);
			INC(pos);
			tBuf[pos] := CHR(type MOD 100H);
			INC(pos);

			(* insert l3hdr into buffer data field *)
			FOR i := 0 TO h3len - 1 DO
				tBuf[pos+i] := l3hdr[i]
			END;
			INC(pos, h3len);

			(* insert l4hdr into buffer data field *)
			FOR i := 0 TO h4len - 1 DO
				tBuf[pos + i] := l4hdr[i]
			END;
			INC(pos, h4len);

			(* insert data into buffer data field *)
			FOR i := 0 TO dlen - 1 DO
				tBuf[pos + i] := data[i + dofs];
			END;
			INC(pos, dlen);


			flags1 := SYSTEM.VAL(SET, -pos) - {16..31} + {OWN} + {25, 24};
			SYSTEM.PUT32(ctrl.txRingAdr + ctrl.txIndex * 16 + 4, flags1);

			ctrl.txIndex:=(ctrl.txIndex+1) MOD TxRingSize;
			(* trigger immediate send poll *)
			ctrl.WriteCSR(0,58);

			ctrl.EnableInterrupts();

	END DoSend;


	PROCEDURE Finalize(connected: BOOLEAN);
		BEGIN
			ctrl.Finalize;
			Finalize^(connected);
		END Finalize;
	END LinkDevice;




	(* Controller: interface to the AM79C970 hardware *)
	Controller = OBJECT
		VAR
			next: Controller; (* next controller in list *)
			base, irq: LONGINT;
			dev: LinkDevice;
			initBlock: InitializationBlock;

			rxAlignDescriptorRing: POINTER TO ARRAY (RxRingSize+1)*16 OF CHAR;
			txAlignDescriptorRing: POINTER TO ARRAY (TxRingSize+1)*16 OF CHAR;
			(* actual ring starting address (16 byte aligned!) *)
			rxRingAdr: SYSTEM.ADDRESS;
			txRingAdr: SYSTEM.ADDRESS;
			(* buffer rings (parallel to descriptors..) *)
			txBufRing: ARRAY TxRingSize OF TxBuffer;
			rxBufRing: ARRAY RxRingSize OF Network.Buffer;
			(* index to point to current descriptor entry/ current buffer *)
			rxIndex, txIndex : LONGINT;



		PROCEDURE &Init*(dev: LinkDevice; base, irq: LONGINT);
		VAR
			res: LONGINT;
			s: SET;
			i,x: INTEGER;

		BEGIN
			outPackets:=0;
			inPackets:=0;
			missedFrames:= 0;
			(* set ethernet broadcast address: FF-FF-FF-FF-FF-FF *)
			FOR i := 0 TO 5 DO dev.broadcast[i] := 0FFX END;

			(* update list of installed controllers, insert at head *)
			SELF.next := installedControllers;
			installedControllers := SELF;

			SELF.base := base;
			SELF.dev := dev;
			SELF.irq := irq;
			dev.ctrl := SELF;

			(* install Interrupthandler *)
			IF (irq >= 1) & (irq <= 15) THEN
				Objects.InstallHandler(SELF.HandleInterrupt, Machine.IRQ0+irq);
			END;

			(* 16 Bit Software Reset *)
			Machine.Portin16(base + RESET, x);

			(* make sure S-Reset has set  STOP (2) to initialize registers *)
			ASSERT(ReadCSR(0)=4);

			(* this Chip needs 16 Bit IO-Resources *)
			ASSERT(DeviceOk());


			(* Setup Init Block *)

			(* Rx/Tx disabled *)
			initBlock.modeTlenRlen := {ModePROM, ModeDTX, ModeDRX};

			(* TLEN 16, RLEN 32 *)
			initBlock.modeTlenRlen := initBlock.modeTlenRlen + {30} + {22} + {20};

			(* PADR - read the MAC address and set to the init block *)
			Machine.Portin16(base, x);
			dev.local[0] := CHR(x MOD 100H);
			dev.local[1] := CHR(x DIV 100H);
			initBlock.padr[0] := x;

			Machine.Portin16(base + 2, x);
			dev.local[2] := CHR(x MOD 100H);
			dev.local[3] := CHR(x DIV 100H);
			initBlock.padr[1] := x;

			Machine.Portin16(base + 4, x);
			dev.local[4] := CHR(x MOD 100H);
			dev.local[5] := CHR(x DIV 100H);
			initBlock.padr[2] := x;


			(* Logical Address Filter *)
			initBlock.ladr[0] := SYSTEM.VAL(SET, 0H);
			initBlock.ladr[1] := SYSTEM.VAL(SET, 0H);

			(* Descriptor Ring Addresses *)
			NEW(rxAlignDescriptorRing);
			rxRingAdr := SYSTEM.ADR(rxAlignDescriptorRing[0]);
			IF rxRingAdr MOD 16 # 0 THEN
				INC(rxRingAdr, 16 - rxRingAdr MOD 16);
			END;
			initBlock.rdra := Machine.Ensure32BitAddress (rxRingAdr);


			NEW(txAlignDescriptorRing);
			txRingAdr := SYSTEM.ADR(txAlignDescriptorRing[0]);
			IF txRingAdr MOD 16 # 0 THEN
				INC(txRingAdr, 16 - txRingAdr MOD 16);
			END;
			initBlock.tdra := Machine.Ensure32BitAddress (txRingAdr);


			(*Switch to 32bit mode. SSIZE32=1 *)
			WriteBCR(20, 2);


			(*write CSR1 (InitBlock Lower Address) *)
			s := SYSTEM.VAL(SET, SYSTEM.ADR(initBlock));
			x := SYSTEM.VAL(INTEGER, SYSTEM.ADR(initBlock) MOD 10000H);
			WriteCSR(1, x);


			(*write CSR2 (InitBlock Upper Address) *)
			x:= SYSTEM.VAL(INTEGER, SYSTEM.ADR(initBlock) DIV 10000H);
			WriteCSR(2, x);


			(* Set INIT bit (0) in CSR0 in order to start initialization*)
			WriteCSR(CSR0, 1);

			(* wait for IDON bit (8)  in CSR0 *)
			i := 0;
			REPEAT
				INC(i)
			UNTIL (8 IN SYSTEM.VAL(SET, ReadCSR(CSR0))) OR (i > 1000);

			ASSERT( i < 1000) ;	(* check for timeout *)
			WriteCSR(CSR0, 42H);

			SetupRxTxRings();


			(* register device with Network (res gets set) *)
			Network.registry.Add(dev, res);
			ASSERT(res = Plugins.Ok);
			INC(installed);

		END Init;


		PROCEDURE ReadCSR(nr:INTEGER) : INTEGER;
		VAR x: INTEGER;
		BEGIN
			Machine.Portout16(base + RAP, nr); (* write address *)
			Machine.Portin16(base + RDP, x); (* read value *)
			RETURN x;
		END ReadCSR;

		PROCEDURE WriteCSR(nr, val : INTEGER);
		BEGIN
			Machine.Portout16(base + RAP, nr);  	(* write address *)
			Machine.Portout16(base + RDP, val); 	(* read value *)
		END WriteCSR;

		PROCEDURE ReadBCR(nr: INTEGER) : INTEGER;
		VAR x : INTEGER;
		BEGIN
			Machine.Portout16(base + RAP, nr);
			Machine.Portin16(base + BDP, x);
			RETURN x;
		END ReadBCR;

		PROCEDURE WriteBCR(nr, val : INTEGER);
		BEGIN
			Machine.Portout16(base + RAP, nr);
			Machine.Portout16(base + BDP, val);
		END WriteBCR;

		PROCEDURE DeviceOk() : BOOLEAN;
		VAR x : INTEGER;
		BEGIN
			x := 88;
			Machine.Portout16(base + RAP, x);
			Machine.Portin16(base + RAP, x);
			RETURN x = 88
		END DeviceOk;


		PROCEDURE SetupRxTxRings;
		VAR
			i: LONGINT;
			adr: SYSTEM.ADDRESS;
			t: SET;
			rBuf: Network.Buffer;
			tBuf: TxBuffer;
		BEGIN
			rxIndex := 0;
			txIndex := 0;

			(* Rx/Tx descriptor see p. 991-996*)
			adr := rxRingAdr;
			FOR i:=0 TO RxRingSize-1 DO
				rBuf:=Network.GetNewBuffer();
				rxBufRing[i] := rBuf;
				SYSTEM.PUT32(adr, SYSTEM.ADR(rBuf.data[0])); INC(adr, 4);
				SYSTEM.PUT16(adr, -RxBufSize); INC(adr, 2);
				SYSTEM.PUT16(adr, 8000H); INC(adr, 2);
				SYSTEM.PUT32(adr, 0); INC(adr, 4);
				SYSTEM.PUT32(adr, 0); INC(adr, 4)
			END;

			t:= SYSTEM.VAL(SET, 0FFFH-TxBufSize+1)+{12..15};
			adr := txRingAdr;
			FOR i:=0 TO TxRingSize-1 DO
				NEW(tBuf);
				txBufRing[i] := tBuf;
				SYSTEM.PUT32(adr, SYSTEM.ADR(tBuf[0])); INC(adr, 4);
				SYSTEM.PUT32(adr, t); INC(adr, 4);
				SYSTEM.PUT32(adr, 0); INC(adr, 4);
				SYSTEM.PUT32(adr, 0); INC(adr, 4)
			END;

			(* stop chip and write some BMU related registers  bit.*)
			HWStop();
			(* enable Rx/Tx, clear bits 0 and 1. This will automatically set TXON and RXON bits in CSR0*)
			WriteCSR(15, 0);
			(* enable polling (and some other stuff) *)
			WriteCSR(4, 915H);
			HWStart();

		END SetupRxTxRings;



		PROCEDURE UpdateTxRing;
		VAR
			i:LONGINT;
			flags1, flags2: SET;
		BEGIN

			i := 0;

			WHILE i < TxRingSize DO

				flags1 := SYSTEM.VAL(SET, SYSTEM.GET32(txRingAdr + i * 16 + 4));
				flags2 := SYSTEM.VAL(SET, SYSTEM.GET32(txRingAdr + i * 16 + 8));

				IF ~(31 IN flags1) THEN
					SYSTEM.PUT32(txRingAdr+ i * 16+4,SYSTEM.VAL(SET, 0FFFH-TxBufSize+1)+{12..15});
					SYSTEM.PUT32(txRingAdr+i*16+8, 0);
				END;

				INC(i);

			END;

		END UpdateTxRing;


		(* read all frames that are marked with OWN = 0*)
		PROCEDURE ReceivePacket;
		VAR
			type, size: LONGINT;
			rBuf, buf: Network.Buffer;
			flags1, flags2:SET;
		BEGIN

			DisableInterrupts();

			flags1 := SYSTEM.VAL(SET,SYSTEM.GET32(rxRingAdr + rxIndex * 16 + 4));
			flags2 := SYSTEM.VAL(SET,SYSTEM.GET32(rxRingAdr + rxIndex * 16 + 8));

			WHILE ~(31 IN flags1) DO
				INC(inPackets);
				buf := Network.GetNewBuffer();
				IF buf # NIL THEN

					rBuf:=rxBufRing[rxIndex];

					size:= SYSTEM.VAL(INTEGER,flags2);
					type := Network.GetNet2(rBuf.data, 6 + 6);
					rBuf.ofs:= 14;
					rBuf.len:= size;
					rBuf.src:= SYSTEM.VAL(Network.LinkAdr, rBuf.data[6]);
					rBuf.calcChecksum:= {};

					dev.QueueBuffer(rBuf, type);

					rxBufRing[rxIndex]:=buf;
					SYSTEM.PUT32(rxRingAdr+ rxIndex * 16, SYSTEM.ADR(buf.data[0]));
					SYSTEM.PUT32(rxRingAdr+ rxIndex * 16+4,SYSTEM.VAL(SET, 0FFFH-RxBufSize+1)+{12..15}+{31});
					SYSTEM.PUT32(rxRingAdr+ rxIndex * 16+8, 0);

				ELSE
					(* no more upcall buffers available, so do not queue the old one *)
					KernelLog.String("NO MORE BUFFERS!!!!!!!"); KernelLog.Ln;
				END;

				rxIndex:=(rxIndex+1) MOD RxRingSize;
				flags1 := SYSTEM.VAL(SET,SYSTEM.GET32(rxRingAdr + rxIndex * 16 + 4));
				flags2 := SYSTEM.VAL(SET,SYSTEM.GET32(rxRingAdr + rxIndex * 16 + 8));
			END;

			EnableInterrupts();



		END ReceivePacket;


		PROCEDURE HWStop;
		BEGIN
			WriteCSR(CSR0, 4);
		END HWStop;

		PROCEDURE HWStart;
		BEGIN
			WriteCSR(15, SYSTEM.VAL(INTEGER, {ModePROM}));
			WriteCSR(CSR0, SYSTEM.VAL(INTEGER, {1, 6}))
		END HWStart;

		PROCEDURE DisableInterrupts;
		VAR
			x: INTEGER;
		BEGIN
			x:=ReadCSR(0);
			WriteCSR(0, SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET, x)-{6}-{14}-{13}-{12}-{11}-{10}-{9})); (* do not clear the interrupt bits *)
		END DisableInterrupts;

		PROCEDURE EnableInterrupts;
		VAR
			x: INTEGER;
		BEGIN
			x:=ReadCSR(0);
			WriteCSR(0, SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET, x)+{6}-{14}-{13}-{12}-{11}-{10}-{9}));
		END EnableInterrupts;


		PROCEDURE HandleInterrupt;
		VAR
			status : INTEGER;

		BEGIN

			status:=ReadCSR( 0);

			(* TINT *)
			IF (9 IN SYSTEM.VAL(SET,status)) THEN
				UpdateTxRing();
			END;

			(* RINT *)
			IF (10 IN SYSTEM.VAL(SET,status)) THEN
				ReceivePacket();
			END;

			(* MISS - if a frame got lost, increment counter *)
			IF (12 IN SYSTEM.VAL(SET,status)) THEN
				INC(missedFrames);
			END;

			(* to clear interrupts write 1 to according bits *)
			status:=ReadCSR(0);
			WriteCSR(0, status);

		END HandleInterrupt;


		PROCEDURE Linked():BOOLEAN;
		BEGIN
			RETURN TRUE;
		END Linked;


		PROCEDURE Finalize;
		BEGIN
			(* cleanup Network registry and remove interrupthandler*)
			Network.registry.Remove(dev);
			DEC(installed);
			Objects.RemoveHandler(SELF.HandleInterrupt, Machine.IRQ0 + irq);
		END Finalize;


	END Controller;


 VAR
	installedControllers: Controller;

(* Scan the PCI bus for the specified card. *)
PROCEDURE ScanPCI(vendor, device: LONGINT);
VAR
	index, bus, dev, fct, res, base, irq, i: LONGINT;
	d: LinkDevice;
	c: Controller;
	name: Plugins.Name;
BEGIN
	index := 0; (*returns the first card found. If index was 1, it would return the second one.. *)

	WHILE (PCI.FindPCIDevice(device, vendor, index, bus, dev, fct) = PCI.Done) & (installed < 16) DO
		res := PCI.ReadConfigDword(bus, dev, fct, PCI.Adr0Reg, base); (* read offset 10H (PCI.Adr0Reg) from PCI Registers *)
		ASSERT (res = PCI.Done);
		ASSERT(ODD(base)); (* I/O mapped *)
		DEC(base, base MOD 4); (* strip lower 2 bits *)
		res := PCI.ReadConfigByte(bus, dev, fct, PCI.IntlReg, irq);
		ASSERT(res = PCI.Done);
		NEW(d, Network.TypeEthernet, MaxETHFrameSize - 14, 6);
		name := Name;
		i := 0;
		WHILE name[i] # 0X DO INC(i) END;
		IF installed > 9 THEN
			name[i] := CHR(ORD("A") + installed - 10);
		ELSE
			name[i] := CHR(ORD("0") + installed);
		END;
		name[i+1] := 0X;
		KernelLog.String("Found device: "); KernelLog.String(name); KernelLog.String("; IRQ = "); KernelLog.Int(irq, 0); KernelLog.Ln;
		res:=PCI.WriteConfigDword(bus, dev, fct, PCI.CmdReg, 5); (* enable bus master and IO Space access *)
		d.SetName(name);
		d.desc := Description;

		NEW(c, d, base, irq);	 (* increments "installed" when successful *)
		INC(index);

	END
END ScanPCI;

PROCEDURE Install*;
BEGIN {EXCLUSIVE}
	IF installed = 0 THEN
		ScanPCI(1022H, 2000H);	(* Vendor = AMD, Device = 79C970 *)
	END;
END Install;

PROCEDURE Cleanup; (*(par : ANY) :ANY;*)
BEGIN
	WHILE installedControllers # NIL DO
		KernelLog.String("Removing "); KernelLog.String(installedControllers.dev.name); KernelLog.Ln;
		installedControllers.Finalize;
		installedControllers := installedControllers.next;
		KernelLog.String("Outgoing packets: "); KernelLog.Int(outPackets,0); KernelLog.Ln;
		KernelLog.String("Incoming packets: "); KernelLog.Int(inPackets,0); KernelLog.Ln;
		KernelLog.String("Missed packets: "); KernelLog.Int(missedFrames,0); KernelLog.Ln;
		KernelLog.String("Success!"); KernelLog.Ln;
	END;
	installedControllers := NIL;
END Cleanup;

(* executed at module load *)
BEGIN
	installFinished:=FALSE;
	Modules.InstallTermHandler(Cleanup);
END AM79C970.

(*

SystemTools.Free AM79C970~
AM79C970.Install ~

*)