# GEOS Operating

Ariel's GEOS Programmer's Reference Guide | ||
---|---|---|

Back: Preface | Up: Contents | Next: GEOS Kernal Routines |

GEOS Development | ||
---|---|---|

Back: GEOS: Environment (WIP) |
Up: GEOS Development | Next: GEOS: System (TODO) |

*This page is still a work in progress.*

## Table of Contents

- GEOS: Graphics
- GEOS: Environment
- GEOS: Operating
- GEOS: System

The features GEOS exposes to your program include a dialog box system that is almost like a miniature application, a process library for periodic events, a library of arithmetic routines, and a library for standardized access to GEOS-specific features of the filesystem.

# GEOS Processes

In GEOS, a process is a service routine for regular periodic events.

BSW called them processes, and Boyce and Zimmerman called them recurring timed events. Boyce and Zimmerman also remarked that this is the beginning of multitasking, but BSW cautioned against this interpretation: GEOS doesn't provide any sort of mechanism for the process context switching required for a multitasking OS or application, even for primitive cooperative multitasking.

A process in the GEOS process table has two parts: a service routine vector and a timer. The vector simply points to your process service routine, and the timer tells GEOS how many Interrupt Levels must occur between each run of the process. On NTSC computers, the timer value is in 1/60ths of a second, and on PAL computers, the timer value is in 1/50ths of a second. On both, the timer value is the number of vertical blank periods of the display.

A process can be in one of four states: running, blocked, frozen, and both blocked and frozen. A running process will run every time its timer times out. A blocked process won't run, but its timer will keep counting down and timing out. A frozen process has its timer suspended, also ensuring it won't run. When a blocked process is unblocked, GEOS will run it when the timer times out or immediately if it timed out while blocked. When a frozen process is unfrozen, its timer will resume, and GEOS won't run it immediately, even if more time than its timer value passed while frozen.

Although the timer is decremented on each Interrupt Level, the process polling is done during Main Loop. When your process
is called, it is called as a subroutine of MainLoop, and it should end with an `rts`

instruction.

GEOS can manage up to 20 processes in its process table. There's no constant for it in geosSym, but geoProgrammer 128
seems to have a **MAX_PROCESSES** constant for it. Additionally, Boyce and Zimmerman note internal use only GEOS
variables between $86f1 and $87ce that are used for processes, reserving space for up to 20 processes.

If your program wants to use processes, you give GEOS a headerless table of your processes:

Group | Group Size | Offset | Size | Purpose |
---|---|---|---|---|

Process 0 | 4 bytes | 0 | word | Vector to process 0's service routine. |

2 | word | Timer 0 value: Interrupt Levels between each run. | ||

Process 1 | 4 bytes | 4 | word | Vector to process 1's service routine. |

6 | word | Timer 1 value. | ||

Process N | 4 bytes | (N * 4) | Processes 1 to N are identical in structure to process 0. |

Your table needs only as many entries as processes: as few as one, as many as 20. Your process table is installed by
calling InitProcesses (note the plural) with r0 pointing to the table and **A** holding the number of
processes in it. Note that GEOS will copy your process table to its own working area and leave yours alone.

```
;Example process table, installation, and initiation
C64=TRUE ; Defined because these symbols aren't in geosSym
.if C64 ; but do seem to be in geoProgrammer 128
PSIZE=4
MAX_PROCESSES=20
FRAME_RATE=60 ;(based on NTSC flag; change to 50 if PAL)
.endif
; Define process table
ProcTab:
; Mouse check: Check mouse coordinates, change shape to match region
MOUSECHECK = (*-ProcTab)/PSIZE ; process number 0
.word CheckMouse ; proc 0 service routine
.word 10 ; proc 0 timer: every 10 IRQs
; RTC: Update a real time clock
RTCLOCK = (*-ProcTab)/PSIZE ; process number 1
.word Tick ; proc 1 service routine
.word FRAME_RATE ; proc 1 timer: once a second
; Screen saver: Avoid CRT burn-in by turning off colors after 5 minutes
SCRNSAVER = (*-ProcTab)/PSIZE ; process number 2
.word ScreenSave ; proc 2 service routine (we'd also want to
; make an UnScrnSave routine to fix colors)
.word FRAME_RATE*60*5 ; proc 2 timer: every 5 minutes
; (300 seconds, or 18,000 IRQs (NTSC))
; (300 seconds, or 15,000 IRQs (PAL))
NUM_PROC = (*-ProcTab)/PSIZE ; 3 processes
.if (NUM_PROC > MAX_PROCESSES)
.echo Warning: Too many processes in ProcTab
.endif
; Install process table
LoadW r0,#ProcTab ; table pointer
lda #NUM_PROC ; number of processes in table
jsr InitProcesses ; install
; Activate initially-frozen processes
ldx #NUM_PROC-1 ; for each process N-1 to 0
10$:
jsr RestartProcess ; reset timer, unblock, unfreeze
dex
bpl 10$ ; next
```

## Process Management

When you install a process table, GEOS places all the processes in a frozen state. In order to start the installed processes, you have to iterate over each process number and initialize it, as at the end of the example code above. The GEOS Kernal routine for initializing or reinitializing a process is RestartProcess.

If you have a process that, as in the example above, acts as a screen saver timeout, then you can use **RestartProcess**
to reset and restart the countdown every time your program senses user activity. Continuing the example above, you would
do so by starting relevant service routines with:

```
MyServiceRoutine:
jsr UnScrnSave ; Did we blank the screen already?
;service routine code here
rts
;elsewhere in the code...
UnScrnSave:
;user did something, so don't blank screen for another 5 min.
ldx #SCRNSAVER ; Screen saver timeout process number
jsr RestartProcess ; Reset it
;code to check for and undo screen saver activation here
rts
```

GEOS provides two routines related to frozen processes: FreezeProcess, which halts its countdown timer at the current value, thus ensuring that the process never runs, and UnfreezeProcess, which resumes its countdown timer from the frozen value.

GEOS also provides two routines related to blocked processes: BlockProcess, which prevents the process from running when its countdown timer times out, and UnblockProcess, which allows the process to run at timeout again. The differences between freezing and blocking a process are, first, that a blocked process's timer keeps counting down and timing out like normal, and second, if the timer timed out at least once while the process was blocked, GEOS will call the process's service routine as soon as it gets the chance regardless of the timer's current value.

GEOS polls its process table in both of its main contexts, Main Loop and Interrupt Level. During Interrupt Level, GEOS polls its process table, checks if a process is frozen, and decrements its timer if not. When the timer reaches 0, GEOS sets a "runable" flag on the process and resets the timer back to its initial value to count down again. During Main Loop, GEOS polls its process table, checking if a process is both runable and unblocked. When a process is both runable and unblocked, GEOS clears its runable flag and calls its service routine. Interrupt Level leaves the process timer alone when the process is frozen, and Main Loop leaves the process runable flag alone when the process is blocked.

The runable flag on a process is simply an on-off flag, not a queue or counter of how many times a process becomes runable. This means that, if a process timer times out while blocked, then no matter how many times it times out during the block, GEOS calls its service routine only once when the process is unblocked.

If you need to disable a process completely while guaranteeing it won't run the instant you reenable it, then the best way is most likely to both freeze and block the process, then unblock and unfreeze the process again when you no longer need it disabled.

If you need to activate a process immediately regardless of its current timer value, you can use the EnableProcess routine. It sets the runable flag on the process, which Main Loop will see on its next poll of its process table. GEOS will then call the process's service routine no matter what its timer value is, even if the process is frozen. However, GEOS will not call the process's service routine if it's blocked; like the timer actually running out, it won't get called until it's unblocked.

There may be scenarios in which you don't want your process to do what it normally does. In the example above, you may not want the CheckMouse process to change the mouse pointer's shape while a menu is open and the mouse is navigating through its submenus and options. In this example, your process would need to check menuNumber before doing what it normally does:

```
CheckMouse:
lda menuNumber ;check menu level
bne 90$ ;submenu open? do nothing.
jsr DoCheckMouse ;else, change mouse shape depending on region
90$:
rts
```

## Processes and Interrupts

Every vertical blank period of the display, an IRQ is generated, and GEOS switches to Interrupt Level. That's the theory, but the reality is that, because GEOS depends on the IRQ signal, Interrupt Level won't run while the CPU's interrupt disable flag is on. What this means for processes is that, if interrupts are disabled when the vertical blank IRQ is generated, then GEOS will miss the Interrupt Level, and no process timers will be decremented until the next IRQ.

Normally, this isn't an issue, but this could become one if you don't account for two possibilities. First, if your
program has `sei`

instructions in it, there's the possibility, however slim, that it'll run just a few CPU cycles before
the IRQ, and the timing of your processes will be thrown off by 16.7 ms (NTSC) or 20.0 ms (PAL). Second, some functions
like disk I/O disable IRQs for very long periods of time, up to whole seconds, which will throw off process timings
significantly.

Another problem could arise for processes with very low timers. If the timer value is too low, then Interrupt Level could time the process out and flag it as runable more than once per Main Loop polling cycle, making it impossible for your process service routine to run on every timeout.

Also, it's impossible to guarantee a precisely synchronized relationship between processes. An IRQ could be received in the midst of freezing or unfreesing synchronized processes, causing GEOS to decrement timers for less than all of them. Or Interrupt Level could make a process early in the process table runable while Main Loop is calling and waiting for the service routine for a process late in the process table to finish, in which case GEOS won't call the earlier's service routine until the next time through Main Loop.

# Letting Your Routines Sleep

GEOS has a routine for letting a routine do nothing for a fixed period of time without halting the system: Sleep.
When your routine calls **Sleep**, GEOS saves your routine to call again later, picking up from the instruction
just after the `jsr Sleep`

instruction. **Sleep** is like a temporary, one-use process on the fly for your program.

However, there's a significant price for your routine sleeping. No matter what your routine's parent routine was before
the **Sleep** call, Main Loop is the parent routine after waking up.
Also, while your routine is sleeping, GEOS is processing events and calling service routines, activities which change the
CPU flags, CPU registers, the stack, and GEOS pseudo-registers all to unknown states on return to your process. After
sleeping, your routine wakes up to a changed world.

Before calling **Sleep**, your routine must save temporary values in its own variable space, pulling any values pushed
to the stack and copying out any values stored in GEOS-provided pseudo-register variables.

# GEOS Mathematics Routines

GEOS has several math routines for doing multiplication, division, and bit shifting with 8-bit and 16-bit integers.
Most of the routines are called by passing the math parameter addresses instead of their values, and instead of loading
specific pseudo-registers in advance. For example, to divide two 16-bit unsigned integers that happen to be in a1
and a4, and you happen to want (**a1** = **a1** / **a4**), the call would look like this:

```
ldx #a1 ; address, not value, of dividend word
ldy #a4 ; address, not value, of divisor word
; (note the Immediate addressing mode)
jsr Ddiv ; unsigned 16-bit division
; a1, which held dividend, now holds quotient
; (r8 holds remainder)
```

GEOS calls 8-bit math "single-precision" and 16-bit math "double-precision."

Addition and subtraction with integers works the same for both signed and unsigned integers, but multiplication and division need to treat the sign specially.

In addition to math routines, geoProgrammer has math
macros to automate a lot of 16-bit arithmetic algorithms. The *Hitchhiker's Guide to
GEOS* also offers macro definitions that aren't in geoProgrammer.

## Signed and Unsigned Integers

In the 6502, a signed integer's sign is stored in bit 7, and negative integers are expressed as the two's-complement form of its absolute value. To get the two's complement form, you first have to get the one's-complement form, then add 1. The one's-complement form of a binary integer is simply subtracting each bit from 1, or inverting each bit.

For example, the absolute value of the integer -6 is expressed in 8-bit binary as %00000110 ($06, 6). Getting the one's-complement of 6 consists of subtracting each bit from 1, or inverting every bit, giving %11111001 ($f9, 249). Getting the two's-complement form is just adding 1 to the one's-complement form, so %11111001 becomes %11111010 ($fa, 250). Thus, the integer -6 is expressed in 8-bit binary as %11111010, the form in which adding 6 would give 0, just like decimal arithmetic, albeit with a discarded carry into the nonexistent bit 8 in two's-complement form.

Subtracting two integers is basically adding the minuend to the sign-inverted form of the subtrahend. For example, the expression 3 - 9 is the same as 3 + (-9). Thus, in binary, it's 3 (%00000011) plus the two's-complement of -9 (%11110111). Adding them gives the two's-complement of -6 (%11111010). For the reverse, 9 - 3, which gives positive 6, it's the same as 9 + (-3), which in binary is 9 (%00001001) plus the two's-complement of -3 (%11111101), which when added gives 6 (%00000110) and a carry that can be discarded.

Note that when a negative integer is in two's-complement form, the high bit is set, indicating a negative number.

The representation of negative integers as the two's-complement form of their absolute values allows the 6502 to use the same logic for both addition and subtraction.

In the 6502, in additions, the carry flag needs to be cleared with `clc`

before adding the first byte, in order for `adc`

to add without a carry. In subtractions, the carry flag becomes a not-borrow flag, and so it needs to be set with `sec`

before subtracting the first byte, in order for `sbc`

to subtract without a borrow.

The GEOS Kernal routines and geoProgrammer macros for math extend 8-bit arithmetic to 16 bits. In 16-bit form, the sign of signed integers is in bit 15, which is bit 7 of the high byte.

Unsigned 8-bit integers can have values between 0 and 255. Signed 8-bit integers can have values between -128 and 127. Unsigned 16-bit integers can have values between 0 and 65,535. Signed 16-bit integers can have values between -32,768 and 32,767. If the result of an operation exceeds below the minimum value or above the maximum value, the operation overflows and the result wraps around. For example, adding 3 to the unsigned 8-bit value 255 gives 2 (not 258), and adding 3 to the signed 8-bit value 127 gives -126 (not 130, because 130 [%10000010] is the two's-complement form of -126).

In GEOS, the addition and subtraction of signed integers is the same as addition and subtraction of unsigned integers. However, GEOS treats multiplication and division of signed integers differently from unsigned.

The GEOS Kernal has two routines dealing with signed integers:

Routine | Summary |
---|---|

Dabs | Gives the absolute value of a 16-bit integer. |

Dnegate | Gives the sign-inverted form of a 16-bit integer. |

## Incrementing Integers

The 6502 has three instructions for incrementing integers:

Instruction | Summary |
---|---|

inc | Increment an 8-bit integer in memory. |

inx | Increment an 8-bit integer in X. |

iny | Increment an 8-bit integer in Y. |

The GEOS Kernal doesn't have any routines for incrementing integers. However, BSW provided a macro for incrementing integers:

Macro | Summary |
---|---|

IncW | Increment a 16-bit integer. |

## Decrementing Integers

The 6502 has three instructions for decrementing integers:

Instruction | Summary |
---|---|

dec | Decrement an 8-bit integer in memory. |

dex | Decrement an 8-bit integer in X. |

dey | Decrement an 8-bit integer in Y. |

The GEOS Kernal has one routine for decrementing integers:

Routine | Summary |
---|---|

Ddec | Decrement a 16-bit integer. |

BSW also provided two macros for decrementing integers:

Macro | Summary |
---|---|

DecW | Decrement a 16-bit integer. |

DecW2 | Decrement a 16-bit integer faster. |

## Adding Integers

The 6502 has one instruction for adding integers:

Instruction | Summary |
---|---|

adc | Add an 8-bit integer and C to A. |

BSW also provided five macros in geosMac for adding integers:

Macro | Summary |
---|---|

add | Add an 8-bit integer to A. |

AddB | Add two 8-bit integers. |

AddW | Add two 16-bit integers. |

AddVB | Add an 8-bit integer to another in memory. |

AddVW | Add an 8-bit integer to a 16-bit integer in memory. |

## Subtracting Integers

The 6502 has one instruction for subtracting integers:

Instruction | Summary |
---|---|

sbc | Subtract an 8-bit integer and !C from A. |

BSW also provided three macros in geosMac for subtracting integers:

Macro | Summary |
---|---|

sub | Subtract an 8-bit integer from A. |

SubB | Subtract an 8-bit integer from another in memory. |

SubW | Subtract a 16-bit integer from another in memory. |

## Arithmetic Shifting

The 6502 has two instructions for arithmetic shifting of integers, effectively multiplying or dividing by a power of two:

Instruction | Summary |
---|---|

asl | Shift an 8-bit integer left. |

lsr | Shift an 8-bit integer right. |

The GEOS Kernal has two routines for arithmetic shifting of integers:

Routine | Summary |
---|---|

DShiftLeft | Shift a 16-bit integer left. |

DShiftRight | Shift a 16-bit integer right. |

## Multiplying Integers

The GEOS Kernal has three routines for multiplying unsigned integers:

Routine | Summary |
---|---|

BBMult | Multiply two unsigned 8-bit integers, giving a 16-bit product. |

BMult | Multiply an unsigned 16-bit integer by an unsigned 8-bit integer, giving a 16-bit product. |

DMult | Multiply two unsigned 16-bit integers, giving an unsigned 16-bit product. |

GEOS doesn't have any routines for multiplying signed integers. The *Hitchhiker's Guide to GEOS* offers a routine
that multiplies two signed integers:

```
;***********************************************************************
;DSmult double-precision signed multiply.
;
;pass: x - zpage address of multiplicand
; y - zpage address of multiplier
;
;returns: signed result in address pointed to by x
; word pointed to by y is absolute-value of the
; multiplier passed
; x, y unchanged
;
;Strategy:
; Establish the sign of the result: if the signs of the
; multiplicand and the multiplier are different, then the result
; is negative; otherwise, the result is positive. Make both the
; multiplicand and multiplier positive, do unsigned
; multiplication on those, then adjust the sign of the result
; to reflect the signs of the original numbers.
;
;destroys: a, r6 - r8 (mgl)
;***********************************************************************
DSmult:
lda zpage+1,x ;get sign of multiplicand (hi-byte)
eor zpage+1,y ;and compare with sign of multiplier
php ;save the result for when we come back
jsr Dabs ;multiplicand = abs(multiplicand)
stx r6L ;save multiplicand index
tya ;put multiplier index into x
tax ;for call to Dabs
jsr Dabs ;multiplier = abs(multiplier)
ldx r6L ;restore multiplier index
jsr Dmult ;do multiplication as if unsigned
plp ;get back sign of result
bpl 90$ ;ignore sign-change if result positive
jsr Dnegate ;otherwise, make the result negative
90$:
rts
```

## Dividing Integers

The GEOS Kernal has two routine for dividing integers:

Routine | Summary |
---|---|

Ddiv | Divide an unsigned 16-bit integer by another, giving an unsigned 16-bit quotient. |

DSdiv | Divide a signed 16-bit integer by another, giving a signed 16-bit quotient. |

GEOS doesn't check for division by zero. If you attempt to divide by zero, then the operation will succeed without error,
but a wrong answer will be returned. The *Hitchhiker's Guide to GEOS* offers a pair of routines that wrap the Kernal
routines in divide-by-zero error detection (with errors in the original text corrected):

```
;***********************************************************************
;NewDdiv -- Ddiv with divide-by-zero error checking
;NewDSdiv -- DSdiv with divide-by-zero error checking
;
;Pass: x zp address of dividend
; y zp address of divisor
;
;Returns x,y unchanged
; zp,x result
; r8 remainder
; a $00 -- no error
; $ff -- divide by zero error
; st set to reflect error code in accumulator
;
;Destroys r9
;
;***********************************************************************
;Set Apple=FALSE for GEOS and GEOS 128, set Apple=TRUE for Apple GEOS
;
DIVIDE_BY_ZERO = $ff
NewDdiv:
lda zpage,y ;get low byte of divisor
ora zpage+1,y ;and high byte of divisor
bne 10$ ;if either is non-zero, go divide
lda #DIVIDE_BY_ZERO ;return error
rts ;exit
10$:
jsr Ddiv ;divide
lda #$00 ;and return no error
rts
NewDSdiv:
lda zpage,y ;get low byte of divisor
ora zpage+1,y ;and high byte of divisor
bne 10$ ;if either is non-zero, go divide
lda #DIVIDE_BY_ZERO ;return error
rts ;exit
10$:
.if Apple
stx Xsave ;save x because Apple destroys it
.endif
jsr DSdiv ;divide
.if Apple
ldx Xsave ;restore x register ageos destroyed
.endif
lda #$00 ;and return no error
rts
.if Apple
.ramsect
Xsave .block 1 ;temp x save variable for ageos
.psect
.endif
```

## Comparing Integers

The 6502 has three instructions for comparing the equality of two integers:

Instruction | Summary |
---|---|

cmp | Compare an 8-bit integer in A with one in memory. |

cpx | Compare an 8-bit integer in X with one in memory. |

cpy | Compare an 8-bit integer in Y with one in memory. |

BSW also provided four macros in geosMac for comparing integers:

Macro | Summary |
---|---|

CmpB | Compare two 8-bit integers. |

CmpBI | Compare two 8-bit integers (one immediate). |

CmpW | Compare two 16-bit integers. |

CmpWI | Compare two 16-bit integers (one immediate). |