(Reprinted from C= Hacking #2)
BANKING ON GEOS
by Robert A. Knop Jr.
GEOS was originally written for the Commodore 64. When Berkeley Softworks
came out with GEOS128 (and, for a time, it wasn't clear that they would; then,
it looked like they would release a GEOS128 that wouldn't support the 80
column screen; finally, the release of GEOS128 did turn out to be a full 128
program), it was largely compatible with GEOS64. Applications could share
documents (a geoPaint file is a geoPaint file), and even many GEOS64
appliations run on the 128 in 40 columns. This heritage is also evident to
the GEOS programmer.
As we all know, the C-128 has two 64K RAM banks; the C-64 only has one 64K RAM
"bank." Thus, of course, all of GEOS64 goes into that one 64K RAM space.
This includes the Kernal as well as the space available to applications. Once
the Kernal, graphics screens, and so forth, have claimed their RAM, the GEOS
programmer is left with 23.75K of memory from $0400 to $5fff.
To a cursory "glance," the GEOS128 programming environment looks very much
like the GEOS64 programming enviroment. You still have $0400 to $5fff
available for applications; graphics screens and variables are in the same
place; the Kernal jump table is the same (with some 128 specific additions).
What happened to the other 64K that the 128 has available?
As it turns out, the core of GEOS128- including the application program space,
the 40 column foreground and background screen, and the Kernal jump table- are
all in the 128's RAM block 1, what GEOS calls FrontRAM. To us 128 programmers
used to RAM block 0 being the "main" RAM block, this may sound odd. However,
it actually makes sense. First of all, since GEOS is an operating system in
and of itself, and applications almost never need to call the C128's Kernal
routines, the application no longer needs access to Bank 15. Second, it
allows GEOS128 to keep much of its memory map the same as GEOS64; it can use
the memory range from $200-$3ff in RAM 1 without worrying about disturbing key
system routins like STAFAR which are in the same memory range in RAM 0.
II. Yeah, Yeah, But What Happened to RAM 0 Anyway?
It's still there. Some of RAM 0 is used by GEOS128 to improve the system
performance and to take advantage of the 128's unique features. (For
instance, the code for the "software sprites" seen on the 128's 80 column
screen is found beneath $2000 in RAM 0.) Fortunately, some space does remain
available for an application to use. In RAM 0, the 32K memory space between
$2000 and $9fff is not normally used by GEOS, and is ALMOST available for
application use .
Why do I say "almost"? The problem is desk accessories. When GEOS 64 loads a
desk accessory (DA), it must load it into the same application space as the
application loading the DA. The memory that the DA will used is first saved
to disk in a swap file. Under GEOS128, the routine LdDeskAcc, instead of
saving a swap file to disk, copies the memory to be overwritten by the DA to
RAM0 between $2000 and $9fff. So, if your application uses DA's (and it is
highly recommended that major applications support DA's), you have to be
careful using the space between $2000 and $9fff. You can use it as temporary
swap space within routines- but you cannot assume that it will remain intact
whenever your routine returns to the GEOS MainLoop with your application in a
state that will allow the loading of DA's.
Nowadays, RAM 0 is not the be-all and end-all. GEOS128 was written for the
C=128, not the C=256. Consequently, if you have expanded your 128 to 256K or
512K as described in the articles by Richard Curcio in Twin Cities 128 Issues
#30 and #31 [2,3], you have free use of RAM 2-3 (256K) or RAM 2-7 (512K).
(Note that you should not touch RAM 4-7 on a 512K 128 if you want to be
compatible with task switching as described in TC128 #31. Also, although GEOS
right now does not run in the 2nd 256K, applications should not assume they
are in the 1st 256K, and thus should be careful with the 512K mode bits (4-5)
in the MMU Ram Configuration Register (RCR), $d506.) While the number of
people with 256K and 512K 128's is now small, you can be sure that it will
increase when the promised ZIP accelerator board for the 128 comes out; the
current specs for the ZIP board include provisions for memory expansion on the
RAM 2-3 provide almost another complete 128K available for your application to
use. So how do you go about accessing this?
III. Storing Data In Other RAM Blocks
The most obvious use for RAM blocks other than FrontRAM (which is the only
block where GEOS Kernal routines are available) is as data storage. For
instance, one could visualize a geoPaint previewing utility which loads and
decompacts an entire geoPaint document at once to RAM 2. (The full
decompacted geoPaint document would reqire 56.25K.) One could then quickly
scroll through the document by just copying the relevant portions of the
bitmap from RAM 2 to the foreground screen. Or, if one were really bold, one
could just redirect the VIC screen memory to the relevant range in RAM2 using
the proper MMU and VIC registers. (This would actully require use of both RAM
2 and 3, since VIC screen locations are quantized to 8K; you lose the use of
the highest 8K, since you don't want to overwrite the MMU registers at
$ff00-$ff05; additional practical considerations make use of the lowest 8K
GEOS128 provides a few routines for easily moving data between FrontRAM and
what it calls BackRAM (but we know it just means RAM 0). Happily, these
routines work quite admirably with RAM 2 and 3. (To access RAM 4-7, fiddle
bits 4 and 5 of the MMU RCR to make the desired RAM blocks appear to the
system as virtual RAM 2 and RAM 3, then call these routines.) The core
routine is DoBOp, which is summarized below :
DoBOp=$c2ec: Copy/verify memory between RAM blocks on the C-128.
r0 : ADDR1 - address of first ("source") memory range
r1 : ADDR2 - address of second ("destination") memory range
r2 : COUNT - number of bytes to operate on
r3L : A1BANK - bank of ADDR1 (e.g. 1=FrontRAM, 0=BackRAM)
r3H : A2BANK - bank of ADDR2
y : MODE - operation to perform
Returns: r0-r3 unchanged
when verifying: x=$00 if two ranges match, x=$ff if they don't match
The operation mode is passed in y as follows:
bit0 bit1 Description
---- ---- -----------
0 0 Move from memory at ADDR1 to memory at ADDR2
0 1 Move from memory at ADDR2 to memory at ADDR1
1 0 Swap memory at ADDR1 and ADDR2
1 1 Verify (compare) memory at ADDR1 and ADDR2
(r0, r1, etc. are all the standard BSW symbols defined in the Official GEOS
Programmer's Reference Guide , and that come in the file geosSym with
There are a number of additional routines which are also provided for
programmer convenience which automatically set the MODE in the y register for
you. In all of these routines, r0-r3 have the same meaning as they do in
Routine Address MODE Description
------- ------- ---- -----------
MoveBData $c2e3 00 Copy data at ADDR1 to ADDR2
SwapBData $c2e6 10 Swap data between ADDR1 and ADDR2
VerifyBData $c2e9 11 Compare data at ADDR1 and ADDR2
I have written a short demonstration program which shows the use of MoveBData
and VerifyBData. The full source to this program, BMover, is available
through anonymous ftp at tybalt.caltech.edu (in the /pub/rknop/hacking.mag
directory) as well as elsewhere. If you can't find it, contact me (addresses
are below). The source is geoProgrammer code, in geoWrite 2.1 format. All of
the files you need (except geosSym and geosMac, which come with geoProgrammer)
are in the bmover.sfx archive.
The first function of BMover repeatedly copies a single block on RAM 1 to
successive parts in memory in any other specified bank. The destination bank,
destination addresses, size of the block to move, and number of times to copy
it are all set in constants found at the beginning of the source file BMovAsm.
Once the moves (which use MoveBData) have all been performed, BMover uses
VerifyBData to make sure that all of the blocks were copied succesfully.
For informational purposes, BMover reports the amount of time (in tenths of
seconds) it took to perform all of the moves. (For this, I use the CIA #1 TOD
clock, saving its value at the beginning and end of the move, and subtracting
to get the difference.) I ran a trial where I copied an 8K block of memory to
RAM 2 7 times (thus filling 56K of RAM 2). These moves together took 1 second
at 2 MHz, and 2.2 seconds at 1 Mhz. 56K/second may be no DMA, but it's faster
than a burst load!
IV. Executing Routines In Other Banks
So, you've written an object oriented drawing program that stores its list of
objects (32 byte records) in RAM 2. Or, you have a database that has records
in RAM 0. You want to delete one record at the beginning of the list, which
means moving all of the subsequent records down over the memory freed up by
the deletion. There are a few things you can do. One, you can use Craig
Bruce's dynamic memory allocation routines (highly recommended). Two, you can
repeated do MoveBData to move memory from RAM 2 (or 0) to a buffer in FrontRAM
and back. Or, you can write a short mover routine in the RAM bank where all
the moving is going to happen.
This is just an example. One can visualize other reasons for calling routines
in other RAM banks (what I call "extrabankal routines"). There exist no GEOS
Kernal routines for calling extrabankal routines. Additionally, since your
main application memory is in RAM 1, you are inable to use the 128 Kernal's
JSRFAR (which returns you to Bank 15). So, we are left with implementing our
GEOS128 normally operates with NO common memory enabled. Thanks to one of the
less well-known features of the MMU, there is no need to enable common memory.
The MMU zero page registers ($d507 and $d508) allow you to locate the zero
page that the processor sees anywhere in RAM 0 or RAM 1. What this means is,
no matter what your memory configuration is, the processor sees zero page in
the RAM block specified in $d508. (Unless you have common memory enable, in
which case it is not a good idea to put ZP in RAM blocks other than RAM 0
[6,7].) So, zero page is effectively common memory!
This provides for the possiblity of copying to zero page a short "switchboard"
routine, basically a reimplementation of JSRFAR, which configures the system
for the destination bank, jsr's to a routine, reconfigures the system for the
calling bank, and rts's.
I also demonstrate this technique in BMover. The second function of BMover
first uses MoveBData to copy a routine to $2000 in DESTBANK (which is set
right now in the source code to RAM 0). It then copies the routine ZPJSR to
$02, which stores DESTCFG in $ff00 and jsr's to $2000. The routine at $2000
moves some data around in DESTBANK. Once ZPJSR has returned the program flow
to FrontRAM, BMover calls VerifyBData to make sure everything worked.
While messing around in different banks, to be safe I dissable IRQ interrupts.
On a related note, geoDebugger 2.0 seems to have problems with programs
messing around with different banks. It is not surprising that the BackRAM
debugger (which locates itself in RAM 0) would have trouble with programs that
tried to use RAM 0, but it also has trouble with programs that try to use RAM
2 and 3. This is true even when one uses the system routine MoveBData. (I
found that I was sometimes able to make it past a call to MoveBData while in
the debugger, but that more often the system would hang. This is all probably
an interrupt-related issue.)
If one is to be really classy, one doesn't actually have to copy the ZPJSR
routine to zero page. One could assemble the application such that ZPJSR fell
to a known offset from a page boundry; then, use the MMU to point zero page to
the page containg ZPJSR. Unfortunately, this technique did not work on my
512K expanded 128. The one incompatility I have found is that with the 512K
modification enabled (I do have a switch to disable it, don't worry), the MMU
fails to correctly see zero page in RAM 1 when requested to. Richard Curcio
experimented with it, and it seems that when you try to relocate zero page to
a page in RAM 1, it is actually seen in RAM 3. It is not yet clear whether
this is a problem with the 256K/512K modification, or if the MMU in a stock
128 just relocates ZP to RAM 3 figuring that RAM 3 = RAM 1 (which is true on a
stock 128, but not on a 256K expanded 128!)
Anyone who wants to get ahold of the BMover source, or who has other
questions/comments/flames can contact me, Robert Knop, at the following
U.S. Mail: Robert Knop
123 S. Chester #3
Pasadena, CA 91106
 William Coleman, 1989: "Inside GEOS 128" _The_Transactor_ 9(4), p. 29.
 Richard Curcio, 1991: "Expanding the 128 Part One: 256K" _Twin_Cities_128_
#30, p. 7.
 Richard Curcio, 1992: "Expanding the 128 Part Two: 4 Mode 512K"
_Twin_Cities_128_ #31, p. 5.
 Berkeley Softworks, 1988: _The_Hitchhiker's_Guide_To_Geos_.
 Michael Farr, 1987: _The_Offical_GEOS_Programmer's_Reference_Guide_.
Bantam Books, New York/Toronto.
 Larry Greenly et. al, 1986: _Commodore_128_Programmer's_Reference_Guide_.
Bantam Books, New York/Toronto.
 Ottis R. Cowper, 1986: _Mapping_the_Commodore_128_. Compute! Publications,