Forth Lesson 17
 Virtual Address Mapping
This lesson is a response to the following question from the Open Firmware mailing list (email@example.com).
Andrea wrote: > Dear OFW Gurus, > > I (think I) am stuck in MMU setup on a Geode LX based board. > > I've added a second LPC flash on it (IDSEL=1) but cannot access it (eg. > to read manufacturer and device ID) due "page fault" error: > > ok FFAC0000 rw@ > Page Fault > ok > > While I can access IDSEL=0 flash: > > ok FFBC0000 rw@ . > ffff \BTW: manuf. and device ID have no mean here (this is not a real > LPC flash..) > ok > > I can access this range of memory when CPU is just out of reset (with a > JTAG interface), so hardware should be ok. > > Any clue in how to add the required range on MMU map for my platform in > OFW? (if this is the source of the fault of course)
The following is true for a virtual-mode configuration (since you get a Page Fault, that must be the mode you are using).
 Virtual Mode Address Mapping
In virtual mode (i.e. with paging enabled), you must create a virtual to physical mapping before you can access an I/O device.
 Global Mapping - Outside the Device Tree
There are several subcases depending on how you want to choose the virtual address.
 Manually Chosen Virtual Address
. You can manually specify the virtual address to use, which is not necessarily the same as the physical address.
h# ffac0000 constant lpc-flash2-phys h# e0040000 constant lpc-flash2-virt h# 40000 constant /lpc-flash2 \ size lpc-flash2-virt /lpc-flash2 0 mmu-claim drop \ Reserve the virtual address range lpc-flash2-phys lpc-flash2-virt /lpc-flash2 -1 mmu-map \ Create a mapping
The "0" argument to mmu-claim is the required alignment; the value 0 means "I am supplying the exact virtual address that I want, so alignment is meaningless". The "-1" argument to "mmu-map" lets you control things like cacheability. The value "-1" means "use whatever value is good for ordinary I/O devices (uncached, typically). If the value is not -1, it is the low bits (mode) of the page table entry, for which you can see the definition in the x86 manual from Intel.
 Dynamically Assigned Virtual Address
You can let the system choose a virtual address for you:
h# ffac0000 constant lpc-flash2-phys 0 value lpc-flash2-virt h# 40000 constant /lpc-flash2 \ size /lpc-flash2 /lpc-flash2 mmu-claim to lpc-flash2-virt \ Allocate a virtual address range lpc-flash2-phys lpc-flash2-virt /lpc-flash2 -1 mmu-map \ Create a mapping
Instead of passing a specific virtual address to mmu-claim, we instead replaced the "0" argument with an alignment value - in this case we asked for alignment on the whole size of the device (which is probably more strict than is really necessary). mmu-claim allocates a virtual address that meets that alignment restriction and returns it on the stack. We assign it to the value lpc-flash2-virt .
If the alignment is not important, you could use "1" as the alignment value, as in
/lpc-flash2 1 mmu-claim to lpc-flash2-virt
mmu-claim will always align at least to a page boundary, so it will round up the "1" to the size of an x86 page (4K).
 Virtual Address Equals Physical Address
If you want to use the physical address as the virtual address, you can do this:
h# ffac0000 constant lpc-flash2-phys h# 40000 constant /lpc-flash2 \ size lpc-flash2-phys /lpc-flash2 0 mmu-claim drop \ Reserve the virtual address range lpc-flash2-phys dup /lpc-flash2 -1 mmu-map \ Create a mapping
If you are going to be doing the "virtual = physical" thing a lot, it makes sense to create a word like this:
: map-v=p ( phys size -- ) 2dup 0 mmu-claim drop ( phys size ) over swap -1 mmu-map ( ) ;
Then you can just write:
lpc-flash2-phys /lpc-flash2 map-v=p
You might ask "why bother to have different virtual and physical addresses"?
The answer is that, in many systems, the virtual address space isn't large enough to fully cover the physical address space, so virtual=physical is not always possible.
To destroy a mapping and release the virtual address, do this:
lpc-flash2-virt /lpc-flash2 mmu-unmap lpc-flash2-virt /lpc-flash2 mmu-release
Since the arguments to mmu-unmap and mmu-release are the same, this way is shorter:
lpc-flash2-virt /lpc-flash2 2dup mmu-unmap mmu-release
 Inspecting Mappings
You can ask where a virtual address is mapped:
It will tell you the physical address and some other information about the mapping.
If you want to see a list of all of the mappings, do this:
dev /mmu .properties
The "translations" property shows all of the virtual-to-physical mappings. The "available" property shows which virtual address ranges are not currently mapped.
 Portable Drivers - Inside the Device Tree
Portable device drivers is an important goal of Open Firmware, so the device driver framework is designed to cope with any system. Portable device drivers cannot assume much about the physical address of their device, because it varies from system to system, and different systems have different restrictions on virtual to physical mapping.
The "Global Mapping" techniques shown above are useful for platform-specific device drivers, and also useful for quick interactive tests, but not so good for portable drivers that can be used on many different computers. Portable drivers cannot know physical address details, because they vary from system to system.
The idea for portable drivers is that, while you don't know the overall system, you do know what kind of bus that the device plugs into - for example PCI bus.
In the Open Firmware device tree, each bus has a driver for the bus itself, and that driver implements methods to help its children. So a child driver - say for a PCI Ethernet adapter - calls its parent to ask for mapping services. The details vary from bus to bus, but in the specific example of a particular PCI Ethernet adapter, the mapping line looks like:
0 0 my-space h# 0200.0014 + /regs " map-in" $call-parent to chipbase
The details of how this works can be found in the Writing FCode Drivers manual.
 Physical Mode
Open Firmware can be configured for physical mode, i.e. with paging turned off.
In physical mode, you can access any device directly, at its physical address.
This is convenient, but restrictive:
- In physical mode, it is tricky to find a good place for the OFW Forth dictionary, which is not dynamically relocatable (at least not the x86 version). The ideal dictionary location is "up high somewhere", so it doesn't fragment the lower memory. If you have only one physical memory configuration (an unusual situation), you can put the dictionary at the very top of physical memory. But in the usual case, the system will have several configurations with different amounts of physical memory, so the "very top" changes. In virtual mode, you can put the dictionary near the top of virtual address space and then assign some physical RAM to it depending on how much RAM you have.
- Physical mode OFW cannot easily load client programs that depend on address translation. Some client programs in ELF format, for example, specify specific virtual load addresses that are outside the range of extant physical memory.
- With physical mode OFW, client interface callbacks from an OS that runs with paging enabled can be difficult. In virtual mode, it's easier for OFW to "see" the OS's view of the address space.
Some modern x86 chipsets, including the AMD Geode, have chipset-dependent registers that let you "carve up" the physical memory space and move some portion of it to a different address. That feature can be used to solve the "where to put the dictionary" problem, but doesn't help with the other two problems.
Thus endeth the lesson.