MODULE PCI;
IMPORT SYSTEM, Machine, KernelLog;
CONST
Trace = FALSE;
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;
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;
PCIString = 20494350H;
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: SYSTEM.ADDRESS
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: SYSTEM.ADDRESS;
PROCEDURE BiosServiceDirectory(sadr: SYSTEM.ADDRESS; sid, fsel: LONGINT; VAR badr: SYSTEM.ADDRESS; VAR len, entry: LONGINT): CHAR;
CODE {SYSTEM.AMD64}
MOV EAX, [RBP + sid]
MOV EBX, [RBP + fsel]
PUSH QWORD [RBP + sadr]
MOV WORD [RSP + 4], 3*8
DB 0FFH, 01CH, 024h ; CALL FAR [RSP]
MOV R8, [RBP + badr]
MOV [R8], RBX
MOV R8, [RBP + len]
MOV [R8], ECX
MOV R8, [RBP + entry]
MOV [R8], EDX
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
END
END DetectBios32;
PROCEDURE pcicall(entry:SYSTEM.ADDRESS; VAR peax,pebx,pecx,pedx,pesi,pedi:LONGINT; VAR eflags: SET);
CODE {SYSTEM.AMD64}
MOV R8, [RBP + peax]
MOV EAX, [R8]
MOV R8, [RBP + pebx]
MOV EBX, [R8]
MOV R8, [RBP + pecx]
MOV ECX, [R8]
MOV R8, [RBP + pedx]
MOV EDX, [R8]
MOV R8, [RBP + pesi]
MOV ESI, [R8]
MOV R8, [RBP + pedi]
MOV EDI, [R8]
PUSH QWORD [RBP + entry]
MOV WORD [RSP + 4], 3*8
DB 0FFH, 01CH, 024h; FAR CALL [RSP]
PUSHFQ
MOV R8, [RBP + peax]
MOV [R8], EAX
MOV R8, [RBP + pebx]
MOV [R8], EBX
MOV R8, [RBP + pecx]
MOV [R8], ECX
MOV R8, [RBP + pedx]
MOV [R8], EDX
MOV R8, [RBP + pesi]
MOV [R8], ESI
MOV R8, [RBP + pedi]
MOV [R8], EDI
MOV R8, [RBP + eflags]
POP R9
MOV [R8], R9
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
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 := Machine.Ensure32BitAddress(SYSTEM.VAL (SYSTEM.ADDRESS, 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);
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)
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;
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
res := ReadConfigWord(busNr, devNr, fktNr, CmdReg, cmdReg);
IF (res = Done) THEN
IF mask - SYSTEM.VAL(SET, cmdReg) # {} THEN
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);
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
*)