; ; PCI plug & play ; ; (C)1997-2001 Pascal Dornier / PC Engines; All rights reserved. ; This file is licensed pursuant to the COMMON PUBLIC LICENSE 0.5. ; ; pd 020716 pci_capa -> set latency timer and cache line size ; together (16 bit write) ; pd 011108 set latency timer if P_PRILAT defined ; pd 000830 add PCI_NORST2 option -> don't touch base register ; for second motherboard device (e.g. USB) ; pd 991115 fix PCI I/O allocation: some devices (e.g. ESS Tech) ; have 16 bit base registers. ; pd 990329 fix PCI I/O allocation: 4 byte -> 64 bit registers... ; pd 990216 rewrite I/O allocation ; pd 980728 change to call postcode routine ; pd 980728 fix PCI_NORST option -> skip entire device, not just ; function ; ; To make this BIOS more suitable for future hot plug PCI support, ; the PCI bus address space allocation has been designed to use ; minimum granularity and fixed size allocation for bridges, rather ; than packing things as tightly as possible. This also simplifies ; the code quite a bit. ; ; Limitations: ; ; - PCI bridge code not yet tested. ; - Fixed size allocation for bridges (better to support future ; hot plug, simpler). As implemented, this does not work for ; more than 2 levels of bridges. ; - Except for VGA BIOS, expansion ROMs are not supported. ; Assume first image, 32 KB size. ; - Memory allocation below 1MB does not handle memory holes. ; ; PCI inherent limitations: ; ; - Devices behind bridges don't support memory that must be ; allocated below 1MB. ; ; PCI configuration space structure ; p_id equ 0 ;vendor, device ID p_cmd equ 4 ;command register p_stat equ 6 ;status register p_class equ 8 ;class code, revision ID p_linesz equ 12 ;cache line size p_lat equ 13 ;latency timer p_hedt equ 14 ;header type p_bist equ 15 ;built-in self test p_base equ 10h ;base address registers pb_bus equ 18h ;bridge: primary bus number pb_bus2 equ 19h ;bridge: secondary bus number pb_bus3 equ 1ah ;bridge: subordinate bus number pb_lat2 equ 1bh ;bridge: secondary latency timer pb_io equ 1ch ;bridge: I/O limit pb_stat equ 1eh ;bridge: secondary status pb_mem equ 20h ;bridge: memory base low pb_memp equ 24h ;bridge: prefetchable memory base p_cis equ 28h ;end of base address registers pb_mem2 equ 28h ;bridge: memory base high pb_memp2 equ 2ch ;bridge: prefetchable memory base high p_rom equ 30h ;expansion rom base pb_io2 equ 34h ;bridge: I/O base high pb_rom equ 38h ;bridge: ROM p_line equ 3ch ;IRQ assigned to function p_pin equ 3dh ;0 = not used, 1=A, 2=B, 3=C, 4=D p_mingnt equ 3eh ;minimum grant time pb_ctl equ 3eh ;bridge: control p_maxlat equ 3fh ;maximum latency ; ; working variables for PNP ; ;+ must directly follow previous variable ! ; struc tmp_pci p_int: dw ?,? ;PCI interrupt lines, LSB = INTA, ;MSB = INTD p_irqpt: dw ? ;pointer to IRQ table entry p_mem: dw ? ;regular memory (64K steps) p_memlim: dw ? ;+ memory limit p_memp: dw ? ;prefetchable memory (64K steps) p_memplim: dw ? ;+ prefetchable memory limit p_io: dw ? ;I/O address (16 byte steps) p_iolim: dw ? ;+ I/O limit p_memr: dw ? ;memory below 1MB (segment value) p_memrlim: dw ? ;+ limit p_capa dw ? ;low: 0 if back to back mode supported ;high: 0 for fast, 2 for medium, 4/6 ;slow devsel p_bus: db ? ;current bus p_lastbus: db ? ;+ last bus number ends ; ; get byte [EBX] -> AL ; pci_getb: mov eax,ebx ;set index mov dx,pci_ad and al,0fch ;mask low bits out dx,eax mov dl,bl ;I/O port index or dl,0fch ;pci_dat assume $fc + bit mask in al,dx ret ; ; get word [EBX] -> AX ; pci_getw: mov eax,ebx ;set index mov dx,pci_ad and al,0fch ;mask low bits out dx,eax mov dl,bl ;I/O port index or dl,0fch ;pci_dat assume $fc + bit mask in ax,dx ret ; ; get double word [EBX] -> EAX ; pci_getd: mov eax,ebx ;set index mov dx,pci_ad out dx,eax mov dl,low(pci_dat) in eax,dx ret ; ; assign interrupts to device [EBX] ; pci_airq: mov bl,p_pin ;which interrupt pin does device use ? call pci_getb mov ah,0 sub al,1 ;0 -> FF mov si,ax jb pci_airq3 ;0 -> FF = no interrupt cmp al,3 mov al,0ffh ja pci_airq3 ;out of range -> FF = no interrupt mov al,[si+p_int] ;get interrupt connected to this line pci_airq3: mov bl,p_line ;store interrupt number ;V fall through ; ; set byte AL -> [EBX] ; pci_setb: push ax ;save data mov eax,ebx ;get index mov dx,pci_ad and al,0fch ;mask low bits out dx,eax pop ax ;restore data mov dl,bl ;I/O port index or dl,0fch ;assume pci_dat $fc + bit mask out dx,al ret ; ; set word AX -> [EBX] ; pci_setw: push ax ;save data mov eax,ebx ;get index mov dx,pci_ad and al,0fch ;mask low bits out dx,eax pop ax ;restore data mov dl,bl ;I/O port index or dl,0fch ;assume pci_dat $fc + bit mask out dx,ax ret ; ; set double word EAX -> [EBX] ; pci_setd: xchg eax,ebx ;swap data, index mov dx,pci_ad out dx,eax xchg eax,ebx ;restore data mov dl,low(pci_dat) out dx,eax ret ; ; allocate space for a device, CL = ending index ; (different for normal device / bridge) ; pci_dev: mov bl,p_base pci_dev1: mov eax,0ffffffffh ;find out how many valid bits call pci_setd ;write call pci_getd ;and read back mov ch,al ;save register type test al,1 ;I/O ? jz pci_dev1a ;:no and ax,ax ;high bit set ? (D15) jns pci_dev9 ;no: this register doesn't work jmp pci_io ;allocate I/O device pci_dev1a: and eax,eax ;high bit set ? (D31) jns pci_dev9 ;no: this register doesn't work ! test al,2 ;below 1 MB ? jnz pci_memr mov si,p_memp test al,8 ;prefetchable ? jnz pci_dev2 ;:yes mov si,p_mem ; allocate memory (normal or prefetchable) ; ; we allocate with a minimum of 64KB granularity pci_dev2: shr eax,16 ;/ 64KB not ax ;get requested block size cmp ax,P_MEMINC-1 ;round up to minimum increment ja pci_dev3 mov ax,P_MEMINC-1 pci_dev3: test [si],ax ;do we need to round up base ? jz pci_dev4 ;:no or [si],ax ;round up base inc word [si] jz pci_err2 ;:overflow pci_dev4: inc ax ;size + 1 add ax,[si] ;update base jb pci_err2 ;overflow: error cmp ax,[si+2] ;exceed limit ? ja pci_err2 ;:yes xchg ax,[si] ;set new base, get starting base shl eax,16 ;-> high word, low base is 0 pci_dev7: call pci_setd ;set base register pci_dev8: test ch,4 ;64 bit base register ? jz pci_dev9 ;:32 bit add bl,4 xor eax,eax ;clear high register pci_dev8a: call pci_setd pci_dev9: add bl,4 cmp bl,cl ;end of registers ? jb pci_dev1 ;:try another ret pci_err2: mov al,0c2h ;& error code jmp pci_err9 pci_err3: mov al,0c3h ;& error code jmp pci_err9 pci_err4: mov al,0c4h ;& error code jmp pci_err9 ; ; Allocate <1MB memory space ; pci_memr: test al,4 ;reserved type ? jnz pci_err4 ;:yes not eax ;find out requested block size shr eax,4 ;/16 test eax,0ffff0000h ;error if device wants more than 1MB ! jnz pci_err3 cmp ax,P_MEMRINC-1 ;round up to minimum increment ja pci_memr2 mov ax,P_MEMRINC-1 ;&&& need to add base rounding pci_memr2: add ax,[p_memr] ;update base jb pci_err2 ;overflow: error cmp ax,[p_memrlim] ;exceed limit ? ja pci_err2 ;:yes xchg [p_memr],ax ;get old base, set new shl eax,4 ;-> get back into place ;(high EAX is 0 from 1MB test) jmp short pci_dev7 ;set base register, continue ; ; Allocate I/O space ; ; Note that I/O is allocated in 256 byte blocks starting at ; $1000, $1400, etc. (aliases of chipset registers 0000..00FF) ; ; Space in x100 .. x3FF is reserved for ISA bus. Space in 0000..00FF ; and 0480..04FF is reserved for chipset peripherals. 0CF8..0CFF is ; the PCI config address. So we usually start at 1000. ; pci_io: not ax ;find out requested block size test ax,0ff00h ;> 256 bytes ? jnz pci_err5 ;:error ;&&& jz pci_io1 ;&&& :ok allocation ;&&& mov ax,00ffh ;&&& allocate 256 bytes pci_io1: ;&&& or ax,P_IOINC-1 ;minimum allocation test [p_io],ax ;do we need to round up ? jz pci_ior2 ;:no or [p_io],ax ;base must be multiple of block size inc word [p_io] test word [p_io],0300h ;block overrun ? jz pci_ior2 ;:no or word [p_io],03ffh ;next 1024 byte block inc word [p_io] jz pci_err5 ;:overflow pci_ior2: inc ax ;mask -> count add ax,[p_io] ;base + block size -> future base test ax,0300h ;block overrun ? jz pci_ior3 ;:no or ax,03ffh ;next 1024 byte block inc ax jz pci_err5 ;:overflow pci_ior3: cmp ax,[p_iolim] ;exceeded limit ? ja pci_err5 ;:overflow xchg [p_io],ax ;get old base, set new and eax,0ffffh ;clear high half of EAX jmp pci_dev8a ;set base register, continue pci_err5: mov al,bl ;&&& out post,al mov al,bh out post+4,al jmp pci_err5 ;&&& mov al,0c5h ;& error code jmp pci_err9 ; ; round up [SI], mask CL, set limit using DX if primary bus, ; DI if secondary bus ; pci_rnd: mov ax,[si] test al,cl jz pci_rnd1 or al,cl inc ax jz pci_err1 mov [si],ax pci_rnd1: cmp byte [p_bus],0 ;primary bus ? jnz pci_rnd2 add ax,dx ;primary bus limit jmp short pci_rnd3 pci_rnd2: add ax,di ;secondary bus limit pci_rnd3: jb pci_err1 ;:too much mov [si+2],ax ;set limit ret pci_err1: jmp pci_bus9 ;allocation error ; ; allocate space for a bridge ; pci_bri: mov ax,[p_bus] ;+ lastbus *** pd030520 push ax ;save initial bus number inc ah ;get a new bus number mov [p_lastbus],ah mov bl,pb_bus ;set primary, secondary bus number call pci_setw mov ax,P_SECLAT * 256 + 0ffh ;subordinate = 255 for config mov bl,pb_bus3 ;+ pb_lat2 call pci_setw ; save variables for recursion mov al,[p_lastbus] ;save old bus number, set new mov [p_bus],al ;*** pd030520 xchg al,[p_bus] ;*** push ax push [p_memlim] push [p_memplim] push [p_iolim] ;bridges don't have fine granularity - start at 1MB / 4K boundaries mov si,p_mem ;memory: 1MB boundaries mov dx,P_MEMINC1 ;primary bus mov di,P_MEMINC2 ;secondary bus mov cl,0fh call pci_rnd mov si,p_memp ;prefetch memory: 1MB boundaries ;mov dx,P_MEMINC1 ;primary bus ;mov di,P_MEMINC2 ;secondary bus ;mov cl,0fh call pci_rnd mov si,p_io ;I/O: 4K boundaries mov cl,0ffh mov dx,P_IOINC1 ;primary bus mov di,P_IOINC2 ;secondary bus call pci_rnd ;set bridge base and limit registers mov bl,pb_mem ;memory base / limit mov eax,[p_mem] ;+ p_memlim sub eax,10000h ;-> inclusive limit call pci_setd mov bl,pb_memp ;prefetchable base / limit mov eax,[p_memp] ;+ p_memplim sub eax,10000h ;-> inclusive limit call pci_setd mov bl,pb_io ;I/O base / limit mov al,[p_io+1] mov ah,[p_iolim+1] dec ah ;-> inclusive limit call pci_setw ;clear high registers xor eax,eax ;clear high memory base mov bl,pb_mem2 call pci_setd mov bl,pb_memp2 ;clear high prefetchable base call pci_setd mov bl,pb_io2 ;clear high I/O base call pci_setd ; save variables for recursion push word [p_capa] push dword [p_int] mov word [p_capa],80h ;bus capabilities mov bl,pb_stat ;read secondary status call pci_getw and byte [p_capa],al ;clear fast back to back if not supportd or byte [p_capa+1],ah ;set devsel timing mov bl,p_cmd ;command register: enable bus master call pci_getw or al,04 mov bl,p_cmd call pci_setw push ebx ;save current EBX rol dword [p_int],8 ;precompensate for interrupt rotation call pci_bus ;enumerate this bus call pci_capa ;set capability flags ; limit -> new allocation pointer mov ax,[p_memlim] ;memory mov [p_mem],ax mov ax,[p_memplim] ;prefetchable memory mov [p_memp],ax mov ax,[p_iolim] ;I/O mov [p_io],ax ; restore variables pop ebx ;restore after recursion pop dword [p_int] pop word [p_capa] pop word [p_iolim] pop word [p_memplim] pop word [p_memlim] pop ax mov [p_bus],al ;*** pd030520 was ax, clobbered lastbus mov al,[p_lastbus] ;set correct subordinate bus number mov bl,pb_bus3 call pci_setb mov bl,pb_ctl ;set bridge control register mov ax,P_BRIDGE and byte [p_capa],80h ;isolate fast back to back bit or al,byte [p_capa] ;copy to control register call pci_setw ret ; ; get next interrupt assignment ; pci_nint: test bh,7 ;function 0 ? jnz pci_nint4 ;no: same interrupts cmp byte [p_bus],0 ;primary bus ? jnz pci_nint5 ;:no cmp word [p_irqpt],pci_tab9 ;end of IRQ table ? jz pci_nint4 ;yes: don't change mov si,[p_irqpt] ;get next table entry cs: lodsd mov [p_irqpt],si mov [p_int],eax ;save interrupt setting pci_nint4: ret pci_nint5: ror dword [p_int],8 ;rotate interrupts ret ; ; enumerate a bus - called recursively ; pci_bus: mov bh,80h ;enable config access mov bl,[p_bus] ;bus number shl ebx,16 ;device, function = 0 ; check vendor ID, 0 or FFFF = nothing there, skip device pci_bus0: call pci_nint ;get next interrupt assignment mov bl,p_id ;get vendor / device ID call pci_getd inc ax ;FFFF = not present jz pci_bus4 ;:skip device dec ax ;0000 = no more functions jz pci_bus4 ;:skip device call pci_airq ;assign interrupts call pci_vga ;handle PCI VGA ; check header type, different handling if bridge mov bl,p_hedt ;get header type call pci_getb and al,7fh cmp al,1 ;bridge jz pci_bus1 #if def PCI_NORST cmp bh,PCI_NORST ;don't touch base registers on this jz pci_bus3 ;device (pd 980728: was jz pci_bus2) #endif #if def PCI_NORST2 cmp bh,PCI_NORST2 jz pci_bus3 #endif #if def PCI_NORST3 cmp bh,PCI_NORST3 jz pci_bus3 #endif mov cl,p_cis ;end index for normal device call pci_dev ;allocate resources jmp short pci_bus2 pci_bus1: mov cl,pb_bus ;end index for bridge call pci_dev ;allocate resources call pci_bri ;enumerate secondary buses ; try next function pci_bus2: test bh,7 ;function 0 ? jnz pci_bus4 ;:no, try next subfunction ; function 0 - is this a multifunction device mov bl,p_hedt ;get header type call pci_getb and al,80h jnz pci_bus4 ;:yes, multifunction device pci_bus3: or bh,7 ;step to next device pci_bus4: inc bh jnz pci_bus0 mov ax,[p_mem] ;check allocation limits cmp ax,[p_memlim] ja pci_bus9 ;:error mov ax,[p_memp] cmp ax,[p_memplim] ja pci_bus9 ;:error mov ax,[p_io] cmp ax,[p_iolim] ja pci_bus9 ret pci_bus9: mov al,0e5h ;error ! pci_err9: call postcode ;pd 980728: don't do direct I/O pci_err99: jmp pci_err99 ; ; handle PCI VGA ; pci_vga: mov bl,p_class ;read class ID call pci_getd shr eax,16 ;check high 16 bits cmp ax,0300h ;VGA ? jnz pci_vga9 ;:no mov bl,p_cmd ;enable memory access call pci_getw push ax or al,2 call pci_setw mov bl,p_rom ;enable ROM - at P_ROM0 mov esi,P_ROM0 shl 16 mov eax,esi inc ax ;low bit = 1 -> enable call pci_setd push ebx ;save ebx context call getunreal ;enter unreal mode mov ax,[esi] cmp ax,0aa55h ;ROM header ? jnz pci_vga2 ;:no call cs_vshad2 ;copy video BIOS to shadow RAM ;(chipset specific) pci_vga2: pop ebx ;restore ebx xor eax,eax ;disable ROM call pci_setd pop ax ;restore command register mov bl,p_cmd call pci_setw xor ax,ax ;restore segment (from unreal mode) mov ds,ax mov es,ax pci_vga9: ret ; ; set capability bits & enable devices ; pci_capa: xor bx,bx ;start with device / function 0 mov cx,P_COMMAND ;standard command value cmp byte [p_capa],80h ;all devices back-to-back capable ? jnz pci_capa1 ;:no or ch,02 ;enable fast back-to-back mode pci_capa1: #if def PCI_NORST3 cmp bh,PCI_NORST3 jz pci_capa1b #endif #if def PCI_NORST2 cmp bh,PCI_NORST2 ;skip this device ? jnz pci_capa2 ;:no pci_capa1b: or bh,7 ;skip device jmp short pci_capa4 pci_capa2: #endif mov bl,p_id ;get vendor / device ID call pci_getw inc ax ;FFFF = not present jz pci_capa4 ;:skip device dec ax ;0000 = no more functions jz pci_capa4 ;:skip device mov bl,p_cmd ;read command register call pci_getw and ax,0ff5fh ;clear stepping, palette snoop bits or ax,cx ;enable according to P_COMMAND call pci_setw ;set command register mov bl,p_hedt ;is this a bridge ? &&& pd030520 call pci_getw and al,07f ;normal device ? cmp al,1 ;bridge ? jz pci_capa3b ;:yes, don't overwrite bus ID pci_capa3: mov bl,p_linesz ;set p_linesz and p_lat mov al,P_LINSIZE mov ah,P_PRILAT call pci_setw ;some devices don't support ;setting these registers individually pci_capa3b: ; try next function test bh,7 ;function 0 ? jnz pci_capa4 ;:no, try next subfunction ; function 0 - is this a multifunction device mov bl,p_hedt ;get header type call pci_getb ; mov eax,ebx ;device address ; mov al,p_hedt and 0fch ;header type ; mov dx,pci_ad ; out dx,eax ; mov dl,low(pci_dat) + 2 ; in al,dx and al,80h jnz pci_capa4 ;:yes, multifunction device or bh,7 ;step to next device pci_capa4: inc bh jnz pci_capa1 ret ; ; PCI plug & play configuration ; ; EAX = scratch ; EBX = 80 / bus / device / function / index ; CX = scratch ; DX = port address ; SI = scratch ; DI = scratch ; BP = scratch ; pci_pnp: mov word [p_mem],P_MEM0 ;set starting addresses mov word [p_memp],P_MEMP0 mov word [p_memr],P_MEMR0 mov word [p_io],P_IO0 mov word [p_memlim],P_MEM9 ;set allocation limits mov word [p_memplim],P_MEMP9 mov word [p_memrlim],P_MEMR9 mov word [p_iolim],P_IO9 ;mov byte [p_bus],0 ;mov byte [p_lastbus],0 mov word [p_irqpt],offset pci_tab call pci_bus ;enumerate bus call pci_capa ;set capability flags mov al,[p_lastbus] ;set number of last PCI bus mov byte [cs:d_lastbus],al ret ; ; Disable all devices on primary bus ; ; NOTE: Called without stack ! On a chipset with PCI reset ; function, PCI reset is the easier way... ; pci_rst: #if def PCI_NOCLR ret #else #if def PCI_NORST mov eax,p_cmd+80000800h ;start with device 1 (don't cut off ;chipset) #else mov eax,p_cmd+80000000h ;access bus 0, device 0, function 0 #endif pci_rst1: #if def PCI_NORST cmp ah,PCI_NORST ;don't reset this device jnz pci_rst2 add ah,8 pci_rst2: #endif mov dx,pci_ad out dx,eax ;write index xchg ax,bx mov dl,low(pci_dat) in ax,dx ;read command register and ax,0fff8h ;disable bus master, I/O, memory access xchg ax,bx mov dl,low(pci_ad) out dx,eax ;write index xchg ax,bx mov dl,low(pci_dat) out dx,ax ;write command register xchg ax,bx inc ah ;next device / function jnz pci_rst1 ret #endif