Firmware hobby hacking

From OLPC
Jump to: navigation, search

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

For background see the draft Forth Lesson 13.

Here's an example set of "editing words" to develop Forth code from within the firmware. This code assumes you have added an SD memory card that you can store Forth source files on, but it's easy to change. Here's the vocabulary:

edit foobar    \ load sd:\foobar.fth into the editing buffer 
               \   and open the screen editor (^C when done)
ev             \ execute the contents of the editing buffer 
               \   (most recently edited file)
re-ed          \ reopen the screen editor

And the code:

\ ED.FTH
0 0   2value   ed-file      \ filename being edited
10000 constant ed-maxsize   \ buffer size (max)
0     value    ed-size      \ amount of text in buffer
      create   ed-buf  ed-maxsize allot

: read-content ( adr len name$ -- sz )
   r/o open-file  if  0  else  dup >r  fgets  r> fclose  then
;
: write-content ( content$ name$ -- )
   2dup $delete-all  $create-file >r  " write" r@ $call-method  drop  r> close-dev
;

: name>file ( $n -- $fn ) " sd:\" 2swap " .fth"  $cat2 $cat2 ;
: edit-string [ also hidden ] edit-file [ previous ] ;

: ed$   ( -- s$ ) ed-buf ed-size ;
: ed-read  ( -- ) ed-buf ed-maxsize ed-file read-content  is ed-size ;
: ed-write ( -- ) ed$ ed-file write-content ;
: ed-save? ( -- ) " Save?" confirmed? if ed-write then ;
: ed-edit  ( -- ) ed$ ed-maxsize edit-string is ed-size ;
: edit \ name ( -- )
   safe-parse-word name>file is ed-file
   ed-read ed-edit ed-save?
;
: re-ed ( -- ) ed-edit ed-save? ;
: ev    ( -- ) ed$ eval ;
: ed-save-as ( -- )
   ed$ safe-parse-word name>file  2dup is ed-file  write-content
;

You may also want to expand the Forth display to use all available screen space (maximum number of lines and columns):

screen-ih iselect
0 to window-top  0 to window-left
d# 41 to #lines hidden d# 41 to display-height
d# 100 to #columns  d# 100 to display-width
forth
clear-screen

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. Forth code is writing the pixels of text into the framebuffer. 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.

Sweet! It shouldn't be too hard to come up with a vocabulary of graphics words to play around in the framebuffer. colorForth might be a good source of insight: it also works with a frame buffer at the lowest level. Google around for its source code. --IanOsgood 01:01, 20 August 2007 (EDT)
See also a windowing demo and a Towers of Hanoi demo written in Open Firmware for the PowerPC Macintosh. Scroll to Part II of this article by Amit Singh. The same screen device methods may also work on the OLPC version of Open Firmware. --IanOsgood 12:19, 15 September 2007 (EDT)
The aforementioned demos use these methods to fill and copy rectangles.
" screen" open-dev value myscreen

\ Convenience wrapper functions
: set-color ( r g b color -- ) " color!" myscreen $call-method ;     \ program palette
: fillrect ( color x y w h -- ) " fill-rectangle" myscreen $call-method ;

\ blit factors
: readrect  ( addr x y w h -- )  " read-rectangle" myscreen $call-method ;
: drawrect  ( addr x y w h -- )  " draw-rectangle" myscreen $call-method ;

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" area), 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.

How does the makefile build a rom image?

I have been trying to figure out how the rom image and a subsequent forth system is created from the make file in OFW source. I see a some words in the source files that look like a meta-compiler is used? I have used makefiles for C and C++ code but have no idea how it switches to compiling forth.

Any help or links would be appreciated.

External links