Firmware hobby hacking
This page has been created to collect notes about doing hobby programming using the Forth-based OLPC firmware. It was created to braindump & organise tips and tricks that can hopefully be polished and moved to other pages (such as Forth Lessons).
Editing
See the draft Forth Lesson 13.
Loading code
The fl command takes URLs too. If you have an access point with SSID OLPCOFW you can run Forth code from a webserver like this:
fl http:\\192.168.0.5\\myfile.fth
Introspection
words \ print all words in the current vocabulary sifting foo \ print all words containing substring "foo" in standard vocabs shift-devs foo \ ... in device vocabularies see foo \ decompile or disassemble the word foo ' foo .calls \ list callers to word foo
Graphics
Open the display device like this:
screen-ih iselect
On booting to the 'ok' prompt everything you see on the screen is stored in a "frame buffer" containing 16-bit 565 mode (5 bits red, 6 bits green, 5 bits blue) pixel data. The frame buffer is normal RAM that the graphics hardware is putting onto the screen. You can play with the frame buffer memory with normal Forth code:
ok screen-ih iselect ok frame-buffer-adr . \ where in RAM is the frame buffer? ff000000 ok 0 frame-buffer-adr ! \ make the two top-left pixels black
For more information about the XO's graphics hardware check out the AMD Geode LX Processors Data Book.
Object system
Here's an email from Mitch Bradley sketching the object system:
Each device node has 2 Forth vocabularies, one for properties and one for methods. They are organized into a tree so you can find them, and also so a device node can find its parent. In addition, a device node has a chunk of memory that holds the initial values of its "instance data", which is variables, values, defers, .. that were defined by using the prefix word "instance". The data for ordinary variables (etc) goes in a global place (the normal Forth "user" areaa), whereas the data for instance variables goes in an "instance record". When you create a device node by interpreting Forth source code or FCode, you define the instance variables and set their initial values using ordinary Forth techniques (plus the "instance" prefix when creating them). When you finish the creation of the device node, those initial values get stored in a block of memory that is part of the device node. This is analogous to compiling and linking a Linux program - the initial values of certain data items are stored in the .data segment image in the executable file. The collection of methods, properties, and initial values in a device node is called a "package". You can think of a package as a "dormant object". In order to use the package, you must first "instantiate" it. To do that, you call "open-dev" with a string argument - the string is the device tree pathname. It instantiates every node from the root on down to the leaf node, and hooks them together in an "instance chain". (Even if you use a shorthand pathname with components elided, it canonicalizes the pathname and instantiates the full path.) The reason for this is because a package nearly always require services from its parent. For example a scsi disk node needs services from the scsi host adapter node, and that needs services from the PCI bus node, and that needs services from the main root bus node. Instantiating a node allocates memory for the instance data and initializes that memory with the initial value block from the package. The "instance handle" that is the primary reference to the instance is just the address of that memory (actually, it is an address in the middle, but that is an implementation detail). In addition to the user-defined instance data, there is also some system-defined instance data that you get automatically. For example, "my-voc" is an instance value that points to the package's methods vocabulary. my-parent is an instance value that is the ihandle for the parent (this constitutes the "instance chain" linkage). my-args-adr and my-args-len refer to the optional argument string that was passed to the node in the "open-dev" argument string (after ":"; each path component can have a separate argument). And so on. open-dev returns on the stack the ihandle for the leaf node, which is the head of the "instance chain" linking up through the parents. In order to invoke one of the methods, you use "$call-method", passing a method name and ihandle. It pushes the current value of "my-self" (which is a global value) onto the return stack, sets my-self to the argument ihandle, looks up the method name in the methods vocabulary of the ihandle, executes it, and pops the return stack into my-self on return. Since "my-self" has been set to that ihandle, any uses of instance data will access the values within that instance record. $call-parent is just $call-method with "my-parent" as the ihandle argument. That is the OFW object system in a nutshell. Note that $call-method does nothing with the Forth interactive interpreter context. For debugging purposes, it is very useful to get the interactive interpreter into the act. If you already have an open instance, you can "drag the Forth interpreter into the context" with "iselect ( ihandle -- )". What that does is to set my-self to that ihandle and push the corresponding methods vocabulary onto the interpreter search order. Note that setting my-self globally in this fashion does not interfere with the ability to use $call-method in other contexts, because $call-method pushes/pops my-self as it does its job. In the normal case where you have not done an "iselect", if you say "my-self ." at the interpreter, you will see "0", meaning that there is no current instance at the topmost (interactive) level. If you don't already have an open instance, you can say "select <pathname>" which is a debugging convenience word that does open-dev and iselect. But keep in mind that "iselect" and "select" are just for debugging. The normal usage model for "applications" is to use open-dev and $call-method, without getting the interactive interpreter involved at that level.