; ===========================================================================
; Toolbox.asm contains PCI bus routines
; ===========================================================================

PCI_DATA:
 PCIHARDWARE  db 0
 PCIVERSION   dw 0
 PCIBUSCOUNT  db 0
 pcibus       db 0
 pcitype      dw 0
 pcidevicefound db 0
 pci_bn       db 0
 pci_fn       db 0
 tmpl         dd ?
 bus_num      db 00h
 dev_func     db 00h
PCI_DATASIZE EQU $ - PCI_DATA

PCIVENDOR    db 'Vendor: ',0
PCIDEVICE    db 'Device: ',0

is_pcibios proc far
; AX = 0 no pci bios
_det_pcibios:
           mov  ax, 0b101h
           int  1ah
           jc   _dh_isa
           or   ah, ah
           jnz  _dh_isa
           mov  cs:PCIHARDWARE, al
           mov  byte ptr cs:PCIVERSION, bh
           mov  byte ptr cs:PCIVERSION+2, bl    
           mov  cs:PCIBUSCOUNT, cl
           mov  al, 1
           jmp  short _dh_ret
_dh_isa:
           xor  ax, ax
_dh_ret:   ret
is_pcibios endp

; ===========================================================================
detectpcidevice proc far
; Input:  AX = DeviceID
;         BX = VendorID
; OutPut: AX = true/false
;         BH = Busnumber
;         BL = FunctionNumber
; ===========================================================================
          xor  dx, dx
          mov  pci_bn, dl
          mov  pci_fn, dl
          mov  pcidevicefound, dl
          db 66h
          pusha
          mov  cx, ax ; device id
          mov  dx, bx ; vendor id
          mov  ax, 0b102h
          xor  si, si
          int  1ah
          jc   @nodevice
          mov  al, 1
          mov pcidevicefound, al
          mov pci_bn,BH
          mov pci_fn,BL
@nodevice:
          db 66h
          popa
          mov  bh, pci_bn ; busnumber
          mov  bl, pci_fn ; function number
          xor  ax, ax
          mov  al, pcidevicefound
        ret
detectpcidevice endp


readpci proc far
; Input:  AX = registernumber
;         BX = Bus
;         CX = FunctionNumber
; Output: AX = true/false
;         BX = result

        ret
readpci endp
; readpciword
; readpcidword

writepci proc far
; Input:  AX = registernumber
;         BX = Bus
;         CX = FunctionNumber
; Output: AX = true/false
;         BX = result

        ret
writepci endp
; writepciword
; writepcidword

pciinfo proc far
.386p
           mov  ax, cx
           xor  dx, dx
           mov  bx, 0100h ; 256
           mul  bx
           mov  bx, 0c000h
           add  ax, bx
           mov  dx, ax 
           in   eax, dx
           mov  eax, tmpl
           and  eax, 0ffffh
           cmp  eax, 0ffffh
           je   _pcit2n
           mov  eax, tmpl
           shr  eax, 16
           cmp  eax, 0ffffh
           je   _pcit2n
           ; Pci  Device: Vendor: (l and 0ffff) Device: (l and 16)
           _print PCIVENDOR, 1
           mov  eax, tmpl
           and  eax, 0ffffh
           shr  eax, 16
           call put_hex
           _print PCIDEVICE, 1
           mov  eax, tmpl
           and  eax, 16
           call put_hex
           _newline
.286c
        ret
pciinfo endp

; ===========================================================================
pci_enum proc far
; ===========================================================================
.386p
.387
enumerate_pci:
           mov al, cs:pcibus ; this is also indicator that machine is 486+/32bit
           or  al, al
           jnz  _pci
           ret
_pci:
.386p
           xor ax, ax
           mov cs:pcitype, ax
           xor ax, ax
           mov dx, 0cf8h
           out dx, al
           mov dx, 0cfah
           out dx, al
           mov dx, 0cf8h
           in  al, dx
           mov ah, al
           mov dx, 0cfah
           in  al, dx
           or  al, al
           jnz its_pci_type1
           or  ah, ah
           jnz its_pci_type1
           mov al, 2
           mov pcitype, ax
           jmp short disp_pcitype
its_pci_type1:
           mov dx, 0cf8h
           in  eax, dx
           mov tmpl, eax
           mov eax, 80000000h
           mov dx, 0cf8h
           out dx, eax
           in  eax, dx
           cmp eax, 80000000h
           jne not_pcitype1
           mov al, 1
           mov pcitype, ax
not_pcitype1:
           mov eax, tmpl
           mov dx, 0cf8h
           out dx, eax
disp_pcitype:
           mov ax, pcitype
           cmp ax, 1
           jne pcichk_2
           mov ecx, 511
_pcit1l:
           push ecx
           mov  eax, 80000000h
           mov  ebx, ecx
           add  eax, ebx
           xor  edx, edx
           mov  ebx, 2048
           mul  ebx
           mov  dx, 0cf8h
           out  dx, eax
           mov  dx, 0cfch
           in   eax, dx
           mov  tmpl, eax
           and  eax, 0ffffh
           cmp  eax, 0ffffh
           je   _pcit1n
           mov  eax, tmpl
           shr  eax, 16
           cmp  eax, 0ffffh
           je   _pcit1n
           ; Pci  Device: Vendor: (l and 0ffff) Device: (l and 16)
           _print PCIVENDOR, 1
           mov  eax, tmpl
           and  eax, 0ffffh
           shr  eax, 16
           call put_hex
           _print PCIDEVICE, 1
           mov  eax, tmpl
           and  eax, 16
           call put_hex
           _newline
_pcit1n:
           pop  ecx
           dec  ecx
           jnz  _pcit1l
pcichk_2:
           mov  dx, 0cf8h
           mov  al, 80h
           out  dx, al
           mov  dx, 0cfah
           xor  ax, ax
           out  dx, al          ; Bus select?
           mov  cx, 15
_pcit2l:
           push cx
           call pciinfo
_pcit2n:
.286c
           pop  cx
           dec  cx
           jnz  _pcit2l
           ret
pci_enum endp

enableA20 proc far
.386p
.387
	;; This subroutine will enable the A20 address line in the keyboard
	;; controller.  Takes no arguments.  Returns 0 in EAX on success, 
	;; -1 on failure.  Written for use in 16-bit code, see lines marked
	;; with 32-BIT for use in 32-bit code.

	pusha

	;; Make sure interrupts are disabled
	cli

	;; Keep a counter so that we can make up to 5 attempts to turn
	;; on A20 if necessary
	mov CX, 5

.startAttempt1:  
	;; Wait for the controller to be ready for a command
.commandWait1:
	xor AX, AX
	in AL, 64h
	bt AX, 1
	jc .commandWait1

	;; Tell the controller we want to read the current status.
	;; Send the command D0h: read output port.
	mov AL, 0D0h
	out 64h, AL

	;; Wait for the controller to be ready with a byte of data
	.dataWait1:
	xor AX, AX
	in AL, 64h
	bt AX, 0
	jnc .dataWait1

	;; Read the current port status from port 60h
	xor AX, AX
	in AL, 60h

	;; Save the current value of (E)AX
	push AX			; 16-BIT
	;; push EAX		; 32-BIT

	;; Wait for the controller to be ready for a command
	.commandWait2:
	in AL, 64h
	bt AX, 1
	jc .commandWait2

	;; Tell the controller we want to write the status byte again
	mov AL, 0D1h
	out 64h, AL	

	;; Wait for the controller to be ready for the data
.commandWait3:
	xor AX, AX
	in AL, 64h
	bt AX, 1
	jc .commandWait3

	;; Write the new value to port 60h.  Remember we saved the old
	;; value on the stack
	pop AX			; 16-BIT
	;; pop EAX		; 32-BIT

	;; Turn on the A20 enable bit
	or AL, 00000010b
	out 60h, AL

	;; Finally, we will attempt to read back the A20 status
	;; to ensure it was enabled.

	;; Wait for the controller to be ready for a command
.commandWait4:
	xor AX, AX
	in AL, 64h
	bt AX, 1
	jc .commandWait4

	;; Send the command D0h: read output port.
	mov AL, 0D0h
	out 64h, AL	

	;; Wait for the controller to be ready with a byte of data
.dataWait2:
	xor AX, AX
	in AL, 64h
	bt AX, 0
	jnc .dataWait2

	;; Read the current port status from port 60h
	xor AX, AX
	in AL, 60h

	;; Is A20 enabled?
	bt AX, 1

	;; Check the result.  If carry is on, A20 is on.
	jc .success

	;; Should we retry the operation?  If the counter value in ECX
	;; has not reached zero, we will retry
	loop .startAttempt1


	;; Well, our initial attempt to set A20 has failed.  Now we will
	;; try a backup method (which is supposedly not supported on many
	;; chipsets, but which seems to be the only method that works on
	;; other chipsets).


	;; Keep a counter so that we can make up to 5 attempts to turn
	;; on A20 if necessary
	mov CX, 5

.startAttempt2:
	;; Wait for the keyboard to be ready for another command
.commandWait6:
	xor AX, AX
	in AL, 64h
	bt AX, 1
	jc .commandWait6

	;; Tell the controller we want to turn on A20
	mov AL, 0DFh
	out 64h, AL

	;; Again, we will attempt to read back the A20 status
	;; to ensure it was enabled.

	;; Wait for the controller to be ready for a command
.commandWait7:
	xor AX, AX
	in AL, 64h
	bt AX, 1
	jc .commandWait7

	;; Send the command D0h: read output port.
	mov AL, 0D0h
	out 64h, AL	

	;; Wait for the controller to be ready with a byte of data
.dataWait3:
	xor AX, AX
	in AL, 64h
	bt AX, 0
	jnc .dataWait3

	;; Read the current port status from port 60h
	xor AX, AX
	in AL, 60h

	;; Is A20 enabled?
	bt AX, 1

	;; Check the result.  If carry is on, A20 is on, but we might warn
	;; that we had to use this alternate method
	jc .warn

	;; Should we retry the operation?  If the counter value in ECX
	;; has not reached zero, we will retry
	loop .startAttempt2


	;; OK, we weren't able to set the A20 address line.  Do you want
	;; to put an error message here?
	jmp .fail


.warn:
	;; Here you may or may not want to print a warning message about
	;; the fact that we had to use the nonstandard alternate enabling
	;; method

.success:
	sti
	popa
	xor EAX, EAX
	ret

.fail:
	sti
	popa
	mov EAX, -1
	ret

.286p
.287

enableA20 endp

; ===========================================================================
ReadPCIB proc far
; In  : di = byte to read
; Out : ax = byte read (ah = 0)
; ===========================================================================
    mov  bh, bus_num              ;
    mov  bl, dev_func             ;
    mov  ax, 0B108h               ; read word
    int  1Ah                     ;
    mov  al, cl
    xor  ah, ah
    ret
ReadPCIB endp

; ===========================================================================
ReadPCIW proc far
; In  : di = word to read 
; Out : ax = word read
; ===========================================================================
    mov  bh, bus_num              ;
    mov  bl, dev_func             ;
    mov  ax, 0B109h               ; read word
    int  1Ah                     ;
    mov  ax, cx
    ret
ReadPCIW endp

; ===========================================================================
WritePCIW proc far
; In  : di = word to write to
;       ax = word to write
; Out : carry = status
; ===========================================================================
     mov  bh, bus_num              ;
     mov  bl, dev_func             ;
     mov  ax, 0B10Ch               ; read word
     int  1Ah                     ;
     mov  ax, cx
     ret
WritePCIW endp
