333 lines
12 KiB
Forth
333 lines
12 KiB
Forth
\ *****************************************************************************
|
|
\ * Copyright (c) 2004, 2011 IBM Corporation
|
|
\ * All rights reserved.
|
|
\ * This program and the accompanying materials
|
|
\ * are made available under the terms of the BSD License
|
|
\ * which accompanies this distribution, and is available at
|
|
\ * http://www.opensource.org/licenses/bsd-license.php
|
|
\ *
|
|
\ * Contributors:
|
|
\ * IBM Corporation - initial implementation
|
|
\ ****************************************************************************/
|
|
|
|
\ PAPR PCI host bridge.
|
|
|
|
0 VALUE phb-debug?
|
|
|
|
1000 CONSTANT tce-ps \ Default TCE page size is 4K
|
|
tce-ps 1- CONSTANT tce-mask
|
|
|
|
." Populating " pwd cr
|
|
|
|
\ needed to find the right path in the device tree
|
|
: decode-unit ( addr len -- phys.lo ... phys.hi )
|
|
2 hex-decode-unit \ decode string
|
|
b lshift swap \ shift the devicenumber to the right spot
|
|
8 lshift or \ add the functionnumber
|
|
\ my-bus 10 lshift or \ add the busnumber (assume always bus 0)
|
|
0 0 rot \ make phys.lo = 0 = phys.mid
|
|
;
|
|
|
|
\ needed to have the right unit address in the device tree listing
|
|
\ phys.lo=phys.mid=0 , phys.hi=config-address
|
|
: encode-unit ( phys.lo phys-mid phys.hi -- unit-str unit-len )
|
|
nip nip \ forget the phys.lo and phys.mid
|
|
dup 8 rshift 7 and swap \ calculate function number
|
|
B rshift 1F and \ calculate device number
|
|
over IF 2 ELSE nip 1 THEN \ create string with dev#,fn# or dev# only?
|
|
hex-encode-unit
|
|
;
|
|
|
|
|
|
0 VALUE my-puid
|
|
|
|
: setup-puid
|
|
s" reg" get-node get-property 0= IF
|
|
decode-64 to my-puid 2drop
|
|
THEN
|
|
;
|
|
|
|
setup-puid
|
|
|
|
: config-b@ puid >r my-puid TO puid rtas-config-b@ r> TO puid ;
|
|
: config-w@ puid >r my-puid TO puid rtas-config-w@ r> TO puid ;
|
|
: config-l@ puid >r my-puid TO puid rtas-config-l@ r> TO puid ;
|
|
|
|
\ define the config writes
|
|
: config-b! puid >r my-puid TO puid rtas-config-b! r> TO puid ;
|
|
: config-w! puid >r my-puid TO puid rtas-config-w! r> TO puid ;
|
|
: config-l! puid >r my-puid TO puid rtas-config-l! r> TO puid ;
|
|
|
|
|
|
: map-in ( phys.lo phys.mid phys.hi size -- virt )
|
|
phb-debug? IF cr ." map-in called: " .s cr THEN
|
|
\ Ignore the size, phys.lo and phys.mid, get BAR from config space
|
|
drop nip nip ( phys.hi )
|
|
\ Sanity check whether config address is in expected range:
|
|
dup FF AND dup 10 28 WITHIN NOT swap 30 <> AND IF
|
|
cr ." phys.hi = " . cr
|
|
ABORT" map-in with illegal config space address"
|
|
THEN
|
|
00FFFFFF AND \ Need only bus-dev-fn+register bits
|
|
dup config-l@ ( phys.hi' bar.lo )
|
|
dup 7 AND 4 = IF \ Is it a 64-bit BAR?
|
|
swap 4 + config-l@ lxjoin \ Add upper part of 64-bit BAR
|
|
ELSE
|
|
nip
|
|
THEN
|
|
F NOT AND \ Clear indicator bits
|
|
translate-my-address
|
|
phb-debug? IF ." map-in done: " .s cr THEN
|
|
;
|
|
|
|
: map-out ( virt size -- )
|
|
phb-debug? IF ." map-out called: " .s cr THEN
|
|
2drop
|
|
;
|
|
|
|
|
|
: dma-alloc ( size -- virt )
|
|
phb-debug? IF cr ." dma-alloc called: " .s cr THEN
|
|
tce-ps #aligned
|
|
alloc-mem
|
|
\ alloc-mem always returns aligned memory - double check just to be sure
|
|
dup tce-mask and IF
|
|
." Warning: dma-alloc got unaligned memory!" cr
|
|
THEN
|
|
;
|
|
|
|
: dma-free ( virt size -- )
|
|
phb-debug? IF cr ." dma-free called: " .s cr THEN
|
|
tce-ps #aligned
|
|
free-mem
|
|
;
|
|
|
|
|
|
\ Helper variables for dma-map-in and dma-map-out
|
|
0 VALUE dma-window-liobn \ Logical I/O bus number
|
|
0 VALUE dma-window-base \ Start address of window
|
|
0 VALUE dma-window-size \ Size of the window
|
|
|
|
0 VALUE bm-handle \ Bitmap allocator handle
|
|
|
|
\ Read helper variables (LIOBN, DMA window base and size) from the
|
|
\ "ibm,dma-window" property. This property can be either located
|
|
\ in the PCI device node or in the bus node, so we've got to use the
|
|
\ "calling-child" variable here to get to the node that initiated the call.
|
|
\ XXX We should search all the way up the tree to the PHB ...
|
|
: (init-dma-window-vars) ( -- )
|
|
\ ." Foo called in " pwd cr
|
|
\ ." calling child is " calling-child .node cr
|
|
\ ." parent is " calling-child parent .node cr
|
|
s" ibm,dma-window" calling-child get-property IF
|
|
s" ibm,dma-window" calling-child parent get-property
|
|
ABORT" no dma-window property available"
|
|
THEN
|
|
decode-int TO dma-window-liobn
|
|
decode-64 TO dma-window-base
|
|
decode-64 TO dma-window-size
|
|
2drop
|
|
bm-handle 0= IF
|
|
dma-window-base dma-window-size tce-ps bm-allocator-init to bm-handle
|
|
\ Sometimes the window-base appears as zero, that does not
|
|
\ go well with NULL pointers. So block this address
|
|
dma-window-base 0= IF
|
|
bm-handle tce-ps bm-alloc drop
|
|
THEN
|
|
THEN
|
|
;
|
|
|
|
: (clear-dma-window-vars) ( -- )
|
|
0 TO dma-window-liobn
|
|
0 TO dma-window-base
|
|
0 TO dma-window-size
|
|
;
|
|
|
|
\ grub does not align allocated addresses to the size so when mapping,
|
|
\ we might need to ask bm-alloc for an extra IOMMU page
|
|
: dma-align ( size virt -- aligned-size ) tce-mask and + tce-ps #aligned ;
|
|
: dma-trunc ( addr -- addr&~fff ) tce-mask not and ;
|
|
|
|
: dma-map-in ( virt size cachable? -- devaddr )
|
|
phb-debug? IF cr ." dma-map-in called: " .s cr THEN
|
|
(init-dma-window-vars)
|
|
drop
|
|
over dma-align ( virt size ) \ size is aligned now
|
|
tuck ( size virt size )
|
|
bm-handle swap bm-alloc ( size virt dev-addr ) \ dev-addr is aligned
|
|
dup 0 < IF
|
|
." Bitmap allocation Failed "
|
|
3drop
|
|
0 EXIT
|
|
THEN
|
|
|
|
swap ( size dev-addr virt )
|
|
2dup tce-mask and or >r \ add page offset to the return value
|
|
|
|
dma-trunc 3 OR \ Truncate and add read and write perm
|
|
rot ( dev-addr virt size r: dev-addr )
|
|
0
|
|
?DO
|
|
2dup dma-window-liobn -rot ( dev-addr virt liobn dev-addr virt r: dev-addr )
|
|
hv-put-tce ABORT" H_PUT_TCE failed"
|
|
tce-ps + swap tce-ps + swap ( dev-addr' virt' r: dev-addr )
|
|
tce-ps +LOOP
|
|
(clear-dma-window-vars)
|
|
2drop
|
|
r>
|
|
;
|
|
|
|
: dma-map-out ( virt devaddr size -- )
|
|
phb-debug? IF cr ." dma-map-out called: " .s cr THEN
|
|
(init-dma-window-vars)
|
|
rot drop ( devaddr size )
|
|
over dma-align
|
|
swap dma-trunc swap ( devaddr-trunc size-extended )
|
|
2dup bm-handle -rot bm-free
|
|
0
|
|
?DO
|
|
dup 0 dma-window-liobn -rot
|
|
hv-put-tce ABORT" H_PUT_TCE failed"
|
|
tce-ps +
|
|
tce-ps +LOOP
|
|
drop
|
|
(clear-dma-window-vars)
|
|
;
|
|
|
|
: dma-sync ( virt devaddr size -- )
|
|
phb-debug? IF cr ." dma-sync called: " .s cr THEN
|
|
\ TODO: Call flush-cache or sync here?
|
|
3drop
|
|
;
|
|
|
|
|
|
: open true ;
|
|
: close ;
|
|
|
|
\ Parse the "ranges" property of the root pci node to decode the available
|
|
\ memory ranges. See "PCI Bus Binding to IEEE Std 1275-1994" for details.
|
|
\ The memory ranges are then used for setting up the device bars (if necessary)
|
|
: phb-parse-ranges ( -- )
|
|
\ First clear everything, in case there is something missing in the ranges
|
|
0 pci-next-io !
|
|
0 pci-max-io !
|
|
0 pci-next-mem !
|
|
0 pci-max-mem !
|
|
0 pci-next-mmio !
|
|
0 pci-max-mmio !
|
|
0 pci-next-mem64 !
|
|
0 pci-max-mem64 !
|
|
|
|
\ Now get the "ranges" property
|
|
s" ranges" get-node get-property 0<> ABORT" ranges property not found"
|
|
( prop-addr prop-len )
|
|
BEGIN
|
|
dup
|
|
WHILE
|
|
decode-int \ Decode phys.hi
|
|
3000000 AND \ Filter out address space in phys.hi
|
|
CASE
|
|
1000000 OF \ I/O space?
|
|
decode-64 dup >r pci-next-io ! \ Decode PCI base address
|
|
decode-64 drop \ Forget the parent address
|
|
decode-64 r> + pci-max-io ! \ Decode size & calc max address
|
|
pci-next-io @ 0= IF
|
|
pci-next-io @ 10 + pci-next-io ! \ BARs must not be set to zero
|
|
THEN
|
|
ENDOF
|
|
2000000 OF \ 32-bit memory space?
|
|
decode-64 dup >r pci-next-mmio ! \ Decode base address
|
|
decode-64 drop \ Forget the parent address
|
|
decode-64 r> + pci-max-mmio ! \ calc max MMIO address
|
|
ENDOF
|
|
3000000 OF \ 64-bit memory space?
|
|
decode-64 dup >r pci-next-mem64 !
|
|
decode-64 drop \ Forget the parent address
|
|
decode-64 r> + pci-max-mem64 !
|
|
ENDOF
|
|
ENDCASE
|
|
REPEAT
|
|
( prop-addr prop-len )
|
|
2drop
|
|
|
|
\ If we do not have 64-bit prefetchable memory, split the 32-bit space:
|
|
pci-next-mem64 @ 0= IF
|
|
pci-next-mmio @ pci-next-mem ! \ Start of 32-bit prefetchable
|
|
pci-max-mmio @ pci-next-mmio @ - 2 / \ Calculate new size
|
|
pci-next-mmio @ + \ The middle of the area
|
|
dup pci-max-mem !
|
|
pci-next-mmio !
|
|
THEN
|
|
|
|
phb-debug? IF
|
|
pci-var-out
|
|
THEN
|
|
;
|
|
|
|
: phb-pci-walk-bridge ( -- )
|
|
phb-debug? IF ." Calling pci-walk-bridge " pwd cr THEN
|
|
|
|
get-node child ?dup 0= IF EXIT THEN \ get and check if we have children
|
|
0 to pci-device-slots \ reset slot array to unpoppulated
|
|
BEGIN
|
|
dup \ Continue as long as there are children
|
|
WHILE
|
|
dup set-node \ Set child node as current node
|
|
my-space pci-set-slot \ set the slot bit
|
|
my-space pci-htype@ \ read HEADER-Type
|
|
7f and \ Mask bit 7 - multifunction device
|
|
CASE
|
|
0 OF my-space pci-device-setup ENDOF \ | set up the device
|
|
1 OF my-space pci-bridge-setup ENDOF \ | set up the bridge
|
|
dup OF my-space [char] ? pci-out ENDOF
|
|
ENDCASE
|
|
peer
|
|
REPEAT drop
|
|
get-parent set-node
|
|
;
|
|
|
|
\ Similar to pci-bridge-probe, but without setting the secondary and
|
|
\ subordinate bus numbers (since this has been done by QEMU already)
|
|
: phb-pci-bridge-probe ( addr -- )
|
|
dup pci-bridge-set-bases \ Set up all Base Registers
|
|
dup func-pci-bridge-range-props \ Set up temporary "range"
|
|
my-space pci-bus-scnd@ TO pci-bus-number \ Set correct current bus number
|
|
pci-device-vec-len 1+ TO pci-device-vec-len \ increase the device-slot vector depth
|
|
pci-enable \ enable mem/IO transactions
|
|
phb-pci-walk-bridge \ and walk the secondary bus
|
|
pci-device-vec-len 1- TO pci-device-vec-len \ decrease the device-slot vector depth
|
|
pci-bridge-set-limits \ Set up all Limit Registers
|
|
;
|
|
|
|
\ Stub routine, as qemu has enumerated, we already have the device
|
|
\ properties set.
|
|
: phb-pci-device-props ( addr -- )
|
|
dup pci-class-name device-name
|
|
dup pci-device-assigned-addresses-prop
|
|
drop
|
|
;
|
|
|
|
\ Scan the child nodes of the pci root node to assign bars, fixup
|
|
\ properties etc.
|
|
: phb-setup-children
|
|
puid >r \ Save old value of puid
|
|
my-puid TO puid \ Set current puid
|
|
phb-parse-ranges
|
|
1 TO pci-hotplug-enabled
|
|
s" qemu,mem-bar-min-align" get-node get-property 0= IF
|
|
decode-int TO pci-mem-bar-min-align
|
|
2drop
|
|
ELSE
|
|
10000 TO pci-mem-bar-min-align
|
|
THEN
|
|
s" qemu,phb-enumerated" get-node get-property 0<> IF
|
|
1 0 (probe-pci-host-bridge)
|
|
ELSE
|
|
2drop
|
|
['] phb-pci-bridge-probe TO func-pci-bridge-probe
|
|
['] phb-pci-device-props TO func-pci-device-props
|
|
phb-pci-walk-bridge \ PHB device tree is already populated.
|
|
THEN
|
|
r> TO puid \ Restore previous puid
|
|
;
|
|
phb-setup-children
|