MODULE PCI;	(** non-portable *)
(** AUTHOR "ryser"; PURPOSE "PCI bus interface (compatible with Native Oberon)"; *)

(* Contributed by P. Ryser to the System 3 project *)

	IMPORT SYSTEM, Machine, KernelLog;

	CONST
		Trace = TRUE;

		(* PCI Configuration Registers *)
		DevReg* = 0H; CmdReg* = 4H; RevIdReg* = 8H; CLSReg* = 0CH;
		Adr0Reg* = 10H; Adr1Reg* = 14H; Adr2Reg* = 18H;
		Adr3Reg* = 1CH; Adr4Reg* = 20H; Adr5Reg* = 24H;
		CISReg* = 28H; SubvReg* = 2CH; ROMReg* = 30H; IntlReg* = 3CH;

		(* PCI Command register encoding, used as arguments for Enable *)
		IOSpace* = {0};
		MemorySpace* = {1};
		BusMaster* = {2};

		debug = FALSE;

		Done* = 0;  NoPCI* = -1;  NoBios32* = -1; Error* = -2;

		FuncNotSupported* = 81H; BadVendorId* = 83H; DeviceNotFound* = 86H;
		BadRegisterNumber* = 87H; SetFailed* = 88H; BufferTooSmall* = 89H;
		PCIServiceId = 49435024H;	(* "$PCI" *)
		PCIString = 20494350H;	(* " PCI" *)

		PCIFunctionId = 0B1H*256;
		PCIBiosPresent = 1H; findPCIDevice = 2H; findPCIClassCode = 3H; generateSpecialCycle = 6H;
		readConfigByte = 8H; readConfigWord = 9H; readConfigDword = 0AH;
		writeConfigByte = 0BH; writeConfigWord = 0CH; writeConfigDword = 0DH;
		getIrqRoutingOptions = 0EH; setPCIIrq = 0FH;

	TYPE
		RouteTable* = POINTER TO RouteTableDesc;
		RouteTableDesc* = RECORD
			busNr*, devNr*, slotNr*: LONGINT;
			linkValIntA*, linkValIntB*, linkValIntC*, linkValIntD*: CHAR;
			IrqBitmapA*, IrqBitmapB*, IrqBitmapC*, IrqBitmapD*: SET;
			next*: RouteTable
		END;

		RouteBuffer = RECORD
			BufferSize, SegSelector: INTEGER;
			DataBufferAdr: LONGINT
		END;

	VAR
		Bios32Def: RECORD
			sig: ARRAY 4 OF CHAR;
			entry: LONGINT;
			rev, len, chksum: CHAR;
			res: ARRAY 5 OF CHAR;
		END;

		bios32: BOOLEAN;

		pci: BOOLEAN;
		pciEntry: LONGINT;

	PROCEDURE -BiosServiceDirectory(sadr, sid, fs: LONGINT; VAR badr: SYSTEM.ADDRESS; VAR len, entry: LONGINT): CHAR;
	CODE {SYSTEM.i386}
		DB 55H	; push ebp
		DB 8BH, 0ECH	; mov ebp, esp
		DB 66H, 8CH, 0C8H	; mov ax, cs
		DB 50H	; push eax
		DB 8BH, 45H, 14H	; mov eax, [ebp+20]
		DB 8BH, 5DH, 10H	; mov ebx, [ebp+16]
		DB 8BH, 55H, 18H	; ; mov edx, [ebp+24]
		DB 0FFH, 0D2H	; call edx
;		DB 0FFH, 55H, 18H	; call [dword ebp+24]
		DB 8BH, 75H, 0CH	; mov esi, [ebp+12]
		DB 89H, 1EH	; mov [esi], ebx
		DB 8BH, 75H, 08H	; mov esi, [ebp+8]
		DB 89H, 0EH	; mov [esi], ecx
		DB 8BH, 75H, 04H	; mov esi, [ebp+4]
		DB 89H, 16H	; mov [esi], edx
		DB 5DH	;  pop ebp
		DB 081H, 0C4H, 018H, 0H, 0H, 0H		; add esp, 24
	END BiosServiceDirectory;

	PROCEDURE FindBios32Service*(serviceId, serviceFkt: LONGINT; VAR serviceAdr: SYSTEM.ADDRESS; VAR  serviceLen, entryOffset: LONGINT): LONGINT;
	VAR ch: CHAR; res: LONGINT; state: SET;
	BEGIN {EXCLUSIVE}
		IF bios32 THEN
			state := Machine.DisableInterrupts();
			ch := BiosServiceDirectory(Bios32Def.entry, serviceId, serviceFkt,
					serviceAdr, serviceLen, entryOffset);
			Machine.RestoreInterrupts(state);
			res := ORD(ch)
		ELSE
			res := NoBios32
		END;
		RETURN res
	END FindBios32Service;

	PROCEDURE DetectBios32;
	VAR adr, chksum, i: LONGINT; s: ARRAY 16 OF CHAR;
	BEGIN
		adr := 0E0000H; chksum := 0FFH;
		REPEAT
			SYSTEM.MOVE(adr, SYSTEM.ADR(s[0]), 16);
			IF (s[0] = "_") & (s[1] = "3") & (s[2] = "2") & (s[3] = "_") THEN
				chksum := 0; i := 0;
				WHILE i < 16 DO
					chksum := chksum + ORD(s[i]);
					IF FALSE & debug THEN KernelLog.Int(ORD(s[i]), 1); KernelLog.Char(" ") END;
					INC(i)
				END;
				chksum := chksum MOD 256;
			END;
			INC(adr, 16)
		UNTIL (chksum = 0) OR (adr = 0100000H);
		bios32 := chksum = 0;
		IF bios32 THEN
			SYSTEM.MOVE(SYSTEM.ADR(s[0]), SYSTEM.ADR(Bios32Def), 16);
			IF Trace THEN
				KernelLog.String("PCI Bios32 detected at: "); KernelLog.Hex(adr-16, 8); KernelLog.Ln;
				KernelLog.String("  Sig: ");
				KernelLog.Char(Bios32Def.sig[0]); KernelLog.Char(Bios32Def.sig[1]);
				KernelLog.Char(Bios32Def.sig[2]); KernelLog.Char(Bios32Def.sig[3]);
				KernelLog.String(", Entry: "); KernelLog.Hex(Bios32Def.entry, 8);
				KernelLog.String(", Revision: "); KernelLog.Int(ORD(Bios32Def.rev), 1);
				KernelLog.String(", Length: "); KernelLog.Int(ORD(Bios32Def.len)*16, 1);
				KernelLog.String(", Checksum: "); KernelLog.Int(ORD(Bios32Def.chksum), 1);
				KernelLog.Ln
			END
		ELSE
			IF Trace THEN
				KernelLog.String("No PCI BIOS32 detected"); KernelLog.Ln
			END;
		END
	END DetectBios32;

		PROCEDURE -pcicall(entry:LONGINT; VAR eax,ebx,ecx,edx,esi,edi:LONGINT; VAR eflags: SET);
		CODE {SYSTEM.i386}
			DB 55H				 ; push ebp
			DB 8BH, 0ECH				 ; mov ebp, esp
			DB 8CH, 0C8H				 ; mov eax, cs
			DB 50H				 ; push eax
			DB 8BH, 45H, 08H			; mov eax, [ebp+8]
			DB 8BH, 38H				 ; mov edi, [eax]
			DB 8BH, 45H, 0CH			; mov eax, [ebp+12]
			DB 8BH, 30H				; mov esi, [eax]
			DB 8BH, 45H, 10H			; mov eax, [ebp+16]
			DB 8BH, 10H				 ; mov edx, [eax]
			DB 8BH, 45H, 14H			; mov eax, [ebp+20]
			DB 8BH, 08H				 ; mov ecx, [eax]
			DB 8BH, 45H, 18H			; mov eax, [ebp+24]
			DB 8BH, 18H				 ; mov ebx, [eax]
			DB 8BH, 45H, 1CH			; mov eax, [ebp+28]
			DB 8BH, 00H				 ; mov eax, [eax]
			DB 0FFH, 55H, 20H			; call [dword ebp+32]
			DB 9CH				 ; pushfd
			DB 50H				 ; push eax
			DB 8BH, 45H, 08H			; mov eax, [ebp+8]
			DB 89H, 38H				 ; mov [eax], edi
			DB 8BH, 45H, 0CH			; mov eax, [ebp+12]
			DB 89H, 30H				 ; mov [eax], esi
			DB 8BH, 45H, 10H			; mov eax, [ebp+16]
			DB 89H, 10H				 ; mov [eax], edx
			DB 8BH, 45H, 14H			; mov eax, [ebp+20]
			DB 89H, 08H				 ; mov [eax], ecx
			DB 8BH, 45H, 18H			; mov eax, [ebp+24]
			DB 89H, 18H				 ; mov [eax], ebx
			DB 5BH				 ; pop ebx
			DB 8BH, 45H, 1CH			; mov eax, [ebp+28]
			DB 89H, 18H				 ; mov [eax], ebx
			DB 5BH				 ; pop ebx
			DB 8BH, 45H, 04H			; mov eax, [ebp+4]
			DB 89H, 18H				 ; mov [eax], ebx
			DB 5DH				 ; pop ebp
			DB 081H, 0C4H, 020H, 0H, 0H, 0H				 ; add esp, 32
		END pcicall;

	PROCEDURE OutRegs(eax, ebx, ecx, edx, esi, edi: LONGINT; eflags: SET);
	BEGIN
		IF debug THEN
			KernelLog.String("   eax: "); KernelLog.Hex(eax, 8); KernelLog.Ln;
			KernelLog.String("   ebx: "); KernelLog.Hex(ebx, 8); KernelLog.Ln;
			KernelLog.String("   ecx: "); KernelLog.Hex(ecx, 8); KernelLog.Ln;
			KernelLog.String("   edx: "); KernelLog.Hex(edx, 8); KernelLog.Ln;
			KernelLog.String("   esi: "); KernelLog.Hex(esi, 8); KernelLog.Ln;
			KernelLog.String("   edi: "); KernelLog.Hex(edi, 8); KernelLog.Ln;
			KernelLog.String("   eflags: "); KernelLog.Hex(SYSTEM.VAL(LONGINT, eflags), 8); KernelLog.Ln
		END
	END OutRegs;

	PROCEDURE PCIPresent*(VAR version, lastPCIbus, hwMech: LONGINT): LONGINT;
	VAR res, eax, ebx, ecx, edx, esi, edi: LONGINT; eflags, state: SET;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + PCIBiosPresent;
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;
			IF (edx = PCIString) & ~(0 IN eflags) & (res = Done) THEN
				version := ebx MOD 10000H;
				lastPCIbus := ecx MOD 100H;
				hwMech := eax MOD 100H
			ELSIF res = 0 THEN res := NoPCI (* ; pci := FALSE (* <- hmm, not sure about that *) *)
			END;
			IF debug THEN
				KernelLog.String("PCIPresent:"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END PCIPresent;

	PROCEDURE FindPCIDevice*(devId, vendId, idx: LONGINT; VAR busNr, devNr, fktNr: LONGINT): LONGINT;
	VAR res, eax, ebx, ecx, edx, esi, edi: LONGINT; eflags, state: SET;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + findPCIDevice;
			ecx := devId; edx := vendId; esi := idx;
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;  ASSERT(~((0 IN eflags) & (res=0)));
			IF ~(0 IN eflags) & (res = Done) THEN
				busNr := (ebx DIV 100H) MOD 100H;
				devNr := (ebx DIV 8) MOD 20H;
				fktNr := ebx MOD 8
			END;
			IF debug THEN
				KernelLog.String("FindPCIDevice:"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END FindPCIDevice;

	PROCEDURE FindPCIClassCode*(classCode, idx: LONGINT; VAR busNr, devNr, fktNr: LONGINT): LONGINT;
	VAR res, eax, ebx, ecx, edx, esi, edi: LONGINT; eflags, state: SET;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + findPCIClassCode;
			ecx := classCode; esi := idx;
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;  ASSERT(~((0 IN eflags) & (res=0)));
			IF ~(0 IN eflags) & (res = Done) THEN
				busNr := (ebx DIV 100H) MOD 100H;
				devNr := (ebx DIV 8) MOD 20H;
				fktNr := ebx MOD 8
			END;
			IF debug THEN
				KernelLog.String("FindPCIClassCode:"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END FindPCIClassCode;

	PROCEDURE GenerateSpecialCycle*(busNr, specCycleData: LONGINT): LONGINT;
	VAR res, eax, ebx, ecx, edx, esi, edi: LONGINT; eflags, state: SET;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + generateSpecialCycle;
			ebx := busNr*100H; edx := specCycleData;
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;  ASSERT(~((0 IN eflags) & (res=0)));
			IF debug THEN
				KernelLog.String("GenerateSpecialCycle:"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END GenerateSpecialCycle;

	PROCEDURE GetIrqRoutingOptions*(VAR rt: RouteTable; VAR IrqBitmap: SET): LONGINT;
	CONST dbN = 16*8;
	VAR
		res, eax, ebx, ecx, edx, esi, edi, i: LONGINT; eflags, state: SET;
		rb: RouteBuffer; db: ARRAY dbN OF CHAR;
		last: RouteTable;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + getIrqRoutingOptions;
			rb.BufferSize := dbN; rb.SegSelector := 0; rb.DataBufferAdr := SYSTEM.ADR(db[0]);
			ebx := 0H; edi := SYSTEM.ADR(rb);
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;  ASSERT(~((0 IN eflags) & (res=0)));
			ASSERT(res # BufferTooSmall); (* Increase dbN on Trap *)
			IF ~(0 IN eflags) & (res = Done)  THEN
				IrqBitmap := SYSTEM.VAL(SET, ebx);
				NEW(rt); rt.next := NIL; last := rt; i := 0;
				WHILE i < rb.BufferSize DO
					NEW(last.next); last := last.next; last.next := NIL;
					last.busNr := ORD(db[i]); INC(i);
					last.devNr := ORD(db[i]) DIV 8; INC(i);
					last.linkValIntA := db[i]; INC(i);
					last.IrqBitmapA := SYSTEM.VAL(SET, LONG(ORD(db[i])+ORD(db[i+1])*100H)); INC(i, 2);
					last.linkValIntB := db[i]; INC(i);
					last.IrqBitmapB := SYSTEM.VAL(SET, LONG(ORD(db[i])+ORD(db[i+1])*100H)); INC(i, 2);
					last.linkValIntC:= db[i]; INC(i);
					last.IrqBitmapC := SYSTEM.VAL(SET, LONG(ORD(db[i])+ORD(db[i+1])*100H)); INC(i, 2);
					last.linkValIntD := db[i]; INC(i);
					last.IrqBitmapD := SYSTEM.VAL(SET, LONG(ORD(db[i])+ORD(db[i+1])*100H)); INC(i, 2);
					last.slotNr := ORD(db[i]); INC(i);
					INC(i)	(* reserved byte *)
				END;
				rt := rt.next
			END;
			IF debug THEN
				KernelLog.String("GetIrqRoutingOptions:"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END GetIrqRoutingOptions;

	PROCEDURE SetPCIIrq*(IntPin, IrqNum, busNr, devNr, fktNr: LONGINT): LONGINT;
	VAR res, eax, ebx, ecx, edx, esi, edi: LONGINT; eflags, state: SET;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + setPCIIrq;
			ecx := IrqNum*100H + IntPin; ebx := busNr*100H+devNr*8+fktNr;
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;  ASSERT(~((0 IN eflags) & (res=0)));
			IF debug THEN
				KernelLog.String("SetPCIHwInt:"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END SetPCIIrq;

	(** Set bits included in <mask> in the PCI command register if not set already *)
	PROCEDURE Enable*(mask : SET; busNr, devNr, fktNr : LONGINT) : LONGINT;
	VAR cmdReg : LONGINT; res : LONGINT;
	BEGIN
		res := ReadConfigWord(busNr, devNr, fktNr, CmdReg, cmdReg);
		IF (res = Done) THEN
			IF mask - SYSTEM.VAL(SET, cmdReg) # {} THEN
				cmdReg := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, cmdReg) + mask);
				res := WriteConfigWord(busNr, devNr, fktNr, CmdReg, cmdReg);
				IF (res = Done) THEN (* maybe the device does not implement all bits writable... check! *)
					res := ReadConfigWord(busNr, devNr, fktNr, CmdReg, cmdReg);
					IF (res = Done) THEN
						IF mask - SYSTEM.VAL(SET, cmdReg) # {} THEN (* at least one bit is not set *)
							res := Error;
						END;
					END;
				END;
			END;
		END;
		RETURN res;
	END Enable;

	PROCEDURE ReadConfig(fkt, busNr, devNr, fktNr, regNr: LONGINT; mask: SET; VAR regVal: LONGINT): LONGINT;
	VAR res, eax, ebx, ecx, edx, esi, edi: LONGINT; eflags, state: SET;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + fkt;
			ebx := busNr*100H+devNr*8+fktNr; edi := regNr;
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;  ASSERT(~((0 IN eflags) & (res=0)));
			IF ~(0 IN eflags) & (res = Done) THEN
				regVal := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, ecx)*mask)
			END;
			IF debug THEN
				KernelLog.String("ReadConfig ("); KernelLog.Int(fkt, 1); KernelLog.String("):"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END ReadConfig;

	PROCEDURE ReadConfigByte*(busNr, devNr, fktNr, regNr: LONGINT; VAR regVal: LONGINT): LONGINT;
	BEGIN
		RETURN ReadConfig(readConfigByte, busNr, devNr, fktNr, regNr, {0..7}, regVal)
	END ReadConfigByte;

	PROCEDURE ReadConfigWord*(busNr, devNr, fktNr, regNr: LONGINT; VAR regVal: LONGINT): LONGINT;
	BEGIN
		ASSERT(regNr MOD 2 = 0);
		RETURN ReadConfig(readConfigWord, busNr, devNr, fktNr, regNr, {0..15}, regVal)
	END ReadConfigWord;

	PROCEDURE ReadConfigDword*(busNr, devNr, fktNr, regNr: LONGINT; VAR regVal: LONGINT): LONGINT;
	BEGIN
		ASSERT(regNr MOD 4 = 0);
		RETURN ReadConfig(readConfigDword, busNr, devNr, fktNr, regNr, {0..31}, regVal)
	END ReadConfigDword;

	PROCEDURE WriteConfig(fkt, busNr, devNr, fktNr, regNr, regVal: LONGINT): LONGINT;
	VAR res, eax, ebx, ecx, edx, esi, edi: LONGINT; eflags, state: SET;
	BEGIN {EXCLUSIVE}
		IF pci THEN
			eax := PCIFunctionId + fkt;
			ebx := busNr*100H+devNr*8+fktNr; ecx := regVal; edi := regNr;
			state := Machine.DisableInterrupts();
			pcicall(pciEntry, eax, ebx, ecx, edx, esi, edi, eflags);
			Machine.RestoreInterrupts(state);
			res := (eax DIV 100H) MOD 100H;  ASSERT(~((0 IN eflags) & (res=0)));
			IF debug THEN
				KernelLog.String("WriteConfig ("); KernelLog.Int(fkt, 1); KernelLog.String("):"); KernelLog.Ln;
				OutRegs(eax, ebx, ecx, edx, esi, edi, eflags)
			END
		ELSE
			res := NoPCI
		END;
		RETURN res
	END WriteConfig;

	PROCEDURE WriteConfigByte*(busNr, devNr, fktNr, regNr, regVal: LONGINT): LONGINT;
	BEGIN
		RETURN WriteConfig(writeConfigByte, busNr, devNr, fktNr, regNr, regVal)
	END WriteConfigByte;

	PROCEDURE WriteConfigWord*(busNr, devNr, fktNr, regNr, regVal: LONGINT): LONGINT;
	BEGIN
		ASSERT(regNr MOD 2 = 0);
		RETURN WriteConfig(writeConfigWord, busNr, devNr, fktNr, regNr, regVal)
	END WriteConfigWord;

	PROCEDURE WriteConfigDword*(busNr, devNr, fktNr, regNr, regVal: LONGINT): LONGINT;
	BEGIN
		ASSERT(regNr MOD 4 = 0);
		RETURN WriteConfig(writeConfigDword, busNr, devNr, fktNr, regNr, regVal)
	END WriteConfigDword;

	PROCEDURE DetectPCI;
	VAR res, len, entry: LONGINT; adr: SYSTEM.ADDRESS;
	BEGIN
		res := FindBios32Service(PCIServiceId, 0, adr, len, entry);
		pci := res = Done;
		IF pci THEN
			Machine.MapPhysical(adr, len, adr);	(* map into virtual address space *)
			ASSERT(adr # Machine.NilAdr);
			pciEntry := adr+entry
		END
	END DetectPCI;

PROCEDURE Show*;
VAR version, lastPCIBus, hwMech, res : LONGINT;
BEGIN
	IF ~PCIDisabled() THEN
		res := PCIPresent(version, lastPCIBus, hwMech);
		IF (res = Done) THEN
			KernelLog.Enter;
			KernelLog.String("PCI: "); KernelLog.Int(lastPCIBus + 1, 0); KernelLog.String(" bus(ses) found, PCI version: ");
			KernelLog.Hex(version DIV 256, -2); KernelLog.Char("."); KernelLog.Hex(version MOD 256, -2);
			KernelLog.Exit;
		ELSE
			KernelLog.Enter; KernelLog.String("PCI: No bus found."); KernelLog.Exit;
		END;
	ELSE
		KernelLog.Enter; KernelLog.String("PCI: Not available (Disabled by user)."); KernelLog.Exit;
	END;
END Show;

PROCEDURE PCIDisabled() : BOOLEAN;
VAR string : ARRAY 2 OF CHAR;
BEGIN
	Machine.GetConfig("DisablePCI", string);
	RETURN string = "1";
END PCIDisabled;

BEGIN
	pci := FALSE; bios32 := FALSE;
	IF ~PCIDisabled() THEN
		DetectBios32;
		DetectPCI;
	END;
	Show;
END PCI.

(**
Notes

PCI devices are uniquely identified by their vendor ID and device ID.  For example, a 3Com 905B Etherlink XL ethernet card has vendor ID 10B7H (3Com) and device ID 9055H.  To get access to this card, use the FindPCIDevice call.  The third parameter (idx) is used to find multiple instances of the card.  If set to 0, the first card is returned; if set to 1, the second; etc.  The last three parameters return the bus number, device number and function number of the card, respectively.  This triple can be used with the other calls (e.g., ReadConfig..., WriteConfig...) to address a specific card.

Example:
	VAR res, bus, dev, fkt: LONGINT;
		(* look for a 3Com 905B ethernet card *)
	res := PCI.FindPCIDevice(9055H, 10B7H, 0, bus, dev, fkt);
	IF res = PCI.Done THEN (* found at (bus, dev, fkt) *) END

The PCI configuration area is a standardized set of registers provided by every PCI device.  It can be accessed using the ReadConfig... and WriteConfig... calls.  Typically, registers 10H, 14H, ..., 24H specify the base addresses of a card.  Bit 0 is 1 if the address is in the I/O space, and 0 if it is in the physical memory space.  For I/O addresses, the bottom two bits should be masked off, and for physical memory addresses, the bottom 4 bits should be masked off.

Example:
	VAR res, adr: LONGINT;
		(* find the I/O base address of the ethernet controller *)
	res := PCI.ReadConfigDword(bus, dev, fkt, 10H, adr);
	IF res = PCI.Done THEN
		ASSERT(ODD(adr));	(* must be I/O mapped *)
		DEC(adr, adr MOD 4);	(* strip lower 2 bits *)
		...
		SYSTEM.PORTIN(adr+X, x)	(* read some device register *)
	END

To access a memory-mapped device, its address range has to be mapped into the virtual address space first.

Example:
	CONST Size = 4096;	(* the device has 4KB of registers *)
	VAR res, physAdr, virtAdr: LONGINT;
		(* find the base address of a memory-mapped device *)
	res := PCI.ReadConfigDword(bus, dev, fkt, 10H, physAdr);
	IF res = PCI.Done THEN
		ASSERT(~ODD(physAdr));	(* must be memory mapped *)
		DEC(physAdr, physAdr MOD 16);	(* strip lower 4 bits *)
		Machine.MapPhysical(physAdr, Size, virtAdr);
		...
		x := SYSTEM.GET32(virtAdr+X);	(* read some device register *)
		...
		Machine.UnmapPhysical(virtAdr, Size)
	END
*)