# Building Applications on the Psion SIBO / Workabout in C

A reference for developing C applications for the Psion SIBO family (HC, MC,
Series 3/3a/3c, Siena, Workabout) using the SIBO C SDK and the TopSpeed C
compiler.

Sourced from the *SIBO 'C' Software Development Kit — General Programming Manual*
(v2.30, March 1999) and the *SIBO Hardware Development Kit* (May 1995) LDD
sections. Claims are drawn only from those documents; where a point is not
stated in the source it is marked as such.

> **Workabout MX note.** The manual (SDK v2.30) names the *Workabout* and
> *Siena* but does not mention a distinct "Workabout MX" model. Everything here
> that applies to the Workabout applies to the MX at the SIBO/EPOC-16 level.
> Anything genuinely MX-specific is called out explicitly; unmarked material is
> generic SIBO and holds for the MX unless noted.

---

## 1. Architecture and programming model

A SIBO machine is "a battery-powered portable computer that is based on the SIBO
architecture ... designed to minimise the size, weight and power consumption"
(General, ch. 2). Key hardware components named by the manual:

- A power-management system that "selectively powers subsystems under software control".
- Solid State Disks (SSDs) — "fast low-power silicon-based mass storage with no moving parts".
- Asynchronous serial interface running at "Mega bit rates".
- **"An 8086 class of processor (or any compatible processor such as an 80286)"** (General, ch. 2).
- Hardware protection: "address trapping of out-of-range writes and a watch-dog timer on interrupts being disabled".
- Real-time clock, ROM-resident system software, graphics LCD, and (on some models) a touch-sensitive digitising pad.

The hardware is "primarily implemented in custom ICs called ASICs ... surface-mounted static CMOS ICs throughout."

> **CPU note / uncertainty.** The manual describes the CPU only as "an 8086
> class of processor (or any compatible processor such as an 80286)". The
> commonly cited V30-class part (NEC V30 / V30H, an 8086-compatible) is **not
> named in these source documents**; treat "V30-class" as background, not a
> manual quotation. The programming-relevant fact is what the manual does state:
> it is an 8086-class, 16-bit, real-mode-segmented CPU with no 8087.

The operating system is **EPOC** (here the 16-bit EPOC that runs on SIBO; not
the later 32-bit EPOC32). Features listed (General, ch. 2):

- Preemptive multi-tasking; MS-DOS-compatible and installable file systems.
- Asynchronous services and client-server architecture (file server, window server).
- A comprehensive I/O system with many built-in I/O devices and **dynamically loadable device drivers**.
- A **reentrant function library**; "multiple processes of the same program share a single copy of the code"; code-shared dynamic link libraries.

> The terms are distinguished in the manual: *SIBO* = the hardware
> architecture; *EPOC* = the operating system designed for it. On SIBO
> machines "the system software resides on an in-built ROM."

### Fatal errors (panics)

When the system detects a condition it believes can only be a bug, it
"terminates the process with a 'panic number' in the range 0 to 255 ... There is
no way for applications to avoid being terminated when a panic has been started."
Panic-number ranges (General, ch. 2):

| Range | Source |
|-------|--------|
| 0–80, and 255 | PLIB library |
| 81–129 | Window Server library |
| 130–160 | OLIB object library |
| 160–254 | non-ROM code (e.g. the ISAM library) |

`panic 80` specifically often means a program "failed to locate SYS$8087.LDD"
(the floating-point emulator LDD) — see §5.

### Why TopSpeed C (small model)

Applications are written, compiled and linked on a PC and then run/debugged on
the SIBO machine. The compiler and linker are the **TopSpeed C** system
(Clarion Software Corporation). The SDK requires the *small code model* of
TopSpeed:

> "`#model small jpi` — The code is to be compiled in small model (code and
> data segments each restricted to 64K), with the jpi (TopSpeed C) convention
> of using registers to pass parameters to subroutines." (General, ch. 3)

You may install other TopSpeed code models, but "they will not be used by the
SIBO SDK." Small model matches the 8086-class segmented architecture: a single
64K code segment and a single 64K data segment, with `CS`/`DS`/`SS` behaving as
a "pure small model" where "`ds=es=ss`" (General, ch. 4). The `jpi` calling
convention (register parameter passing) is mandatory for SIBO builds.

Because there is no 8087, "all floating point is performed by software emulation
of the 8087" (General, ch. 4).

---

## 2. CLIB vs PLIB

Two C libraries are available:

**CLIB** — "a version of the TopSpeed C library for the EPOC operating system
... a version of the standard ANSI C library." Benefits: portability (existing C
ports easily) and less to learn. Documented in the *TopSpeed C Library
Reference*, with SIBO caveats in *Notes on CLIB*.

**PLIB** — Psion's proprietary C library, providing "only very thin shells over
functionality that is present in the ROM." Functions are prefixed `p_`.

The manual "strongly urges" developers to consider PLIB over CLIB:

- "many of the ROM-based EPOC system services are not available from CLIB (eg
  asynchronous I/O, inter-process messaging, the window server graphics functions)"
- "executables are larger in CLIB and the process takes a larger data segment."

CLIB is a "much 'thicker' library"; its subsystems "typically require large
static buffers and tables." You can freely mix PLIB and CLIB calls (unless using
the object-oriented UI dynamic libraries). The Window Server library is `WLIB`
(`wlib.lib`, functions `g...`/`w...`, header `wlib.h`), automatically linked
when needed.

CLIB omissions worth knowing (Notes on CLIB): many BIOS/DOS/graphics/far-pointer
functions are not implemented. `getRealHandle(int handle)` converts a CLIB file
handle to a PLIB handle. CLIB startup auto-opens a console channel assigned to
`stdin`/`stdout`/`stderr`; you can suppress it by defining `p_xwind` and setting
the global `_winHandle`.

### Standard SIBO C types

The manual's example code uses a standard set of SIBO C types in place of raw C
types. (These are defined in the SDK headers — `plib.h`/`stddefs`; the General
manual uses them but their formal definitions live in the PLIB Reference.)
Observed usage:

| Type    | Meaning (from usage in the manual) |
|---------|-------------------------------------|
| `TEXT`  | character / text byte (e.g. `TEXT *pb;`, `TEXT subname[P_FNAMESIZE];`) |
| `UBYTE` | unsigned 8-bit byte |
| `WORD`  | signed 16-bit word |
| `UWORD` | unsigned 16-bit word (e.g. `UWORD answer;`) |
| `INT`   | signed integer (return type of `main`) |
| `UINT`  | unsigned integer (e.g. `UINT lcdtype;`) |
| `VOID`  | void (e.g. `main(VOID)`) |

> **Uncertainty.** The exact widths/signedness of each typedef are not spelled
> out in the General manual; they are defined in the SDK headers / PLIB
> Reference. The mappings above reflect how the manual uses them and standard
> SIBO conventions. Use the uppercase SIBO types rather than raw C types in
> SIBO code.

### Storage-class macros

The example code uses uppercase storage-class macros instead of bare C storage
classes. These make the code portable across the segmented model and the SDK's
linkage conventions.

| Macro | Used for | Manual examples |
|-------|----------|-----------------|
| `GLDEF_C` | **global definition** of code (a function you define, visible externally) | `GLDEF_C INT main(VOID)`, `GLDEF_C VOID main(VOID)` |
| `GLREF_C` | **global reference** to code / an external symbol (declared elsewhere) | `GLREF_C TEXT *DatCommandPtr;` |
| `GLDEF_D` | global definition of data | (data counterpart of `GLDEF_C`) |
| `GLREF_D` | global reference to data defined in another module | `GLREF_D TEXT *DatCommandPtr;` |
| `LOCAL_C` | file-local (`static`) function | `LOCAL_C VOID QueueKey(VOID)`, `LOCAL_C VOID CancelTimer(VOID)` |
| `LOCAL_D` | file-local (`static`) data | (data counterpart of `LOCAL_C`) |

> `_C` suffix = code/function, `_D` suffix = data; `GL...` = global (external
> linkage), `LOCAL_...` = module-private. The General manual shows `GLDEF_C`,
> `GLREF_C`, `GLREF_D`, and `LOCAL_C` directly; `GLDEF_D`/`LOCAL_D` are the data
> counterparts (formal definitions in the PLIB Reference).

### Program entry point

A PLIB program's entry point is `main`, written with the SIBO macros:

```c
#include <plib.h>

GLDEF_C INT main(VOID)
{
    p_printf("Hello World");
    p_getch();
    return (0);
}
```

A CLIB program uses ordinary ANSI style with `<stdio.h>`:

```c
#include <stdio.h>

int main(VOID)
{
    printf("Hello World");
    getchar();
    return (0);
}
```

(Both `hello.c` and `p_hello.c` are taken verbatim from General ch. 3.)

---

## 3. Project (`.pr`) files

The compile/link cycle is driven by TopSpeed **project files** (extension
`.pr`). The minimal CLIB project (`hello.pr`):

```
#system epoc img
#model small jpi

#compile hello
#link hello
```

The minimal PLIB project (`p_hello.pr`):

```
#system epoc img
#set epocinit=iplib
#model small jpi

#compile p_hello
#link p_hello
```

### Directives

- **`#system epoc img`** — "The end outcome of the build is a `.img` file, as
  defined in the Epoc-customised part of the TopSpeed configuration (alternative
  `#system`s include `dos` and `win`)." Required in every SIBO project file.

- **`#model small jpi`** — small code model + jpi register calling convention
  (see §1). Required in every SIBO project file.

- **`#set epocinit=iplib`** — selects the startup object and stack size. **Must
  precede `#model`.** It "specifies whether you are using the CLIB or PLIB
  startup object files, and it specifies the stack size." Allowed values:

  | Value | Startup | Stack |
  |-------|---------|-------|
  | `iclib` | CLIB | 8k (recommended for CLIB) |
  | `iclib4` | CLIB | 4k |
  | `iclib2` | CLIB | 2k |
  | `iplib` | PLIB | 4k (recommended for PLIB) |
  | `iplib8` | PLIB | 8k |
  | `iplib2` | PLIB | 2k |

  If unset, defaults to `iclib`. You **must** use CLIB startup if you use any
  CLIB I/O functions; if you use no CLIB functions at all, "you should always
  use the PLIB startup." (Non-I/O CLIB functions such as memory allocation can
  be used with PLIB startup.)

- **`#compile <name>`** — compile a source module. If ambiguous, add the `.c`
  extension (e.g. `#compile query.c`), because the system also treats `.a`
  (assembler) and `.rc` files as candidate sources and errors if two candidates
  exist.

- **`#link <name>`** — link everything. Its effect: link all `#compile`d files,
  plus the startup module and standard libraries, plus anything named by
  `#pragma link`, giving the executable the name in the `#link` statement. The
  `#link` name need not match the `.pr` filename nor any module name.

- **`#dolink <name.lib>`** — used **instead of** `#link` to build a **library**.
  It stops the system linking in a startup object and standard libraries. The
  explicit `.lib` extension overrides the default `.img` implied by `#system
  epoc img` (see §4).

- **`#pragma link (<file>)`** — link an extra object file or library not
  searched automatically. Examples: `#pragma link (hwif.lib)` (HWIF is not
  auto-searched), `#pragma link (utils.lib)` (a custom library, found along the
  `ts.red` search path).

- **`#pragma debug (vid=>full)`** — insert before any `#compile` to build with
  full source-level debug info (equivalent to the `/v2` command-line flag).

### Overriding image attributes

Three project variables (also settable as `/s...` on the command line):

| Variable | Meaning | Default |
|----------|---------|---------|
| `%version` | image version number | `0x100f` |
| `%priority` | initial process priority | `0x80` |
| `%heapsize` | initial and minimum heap, in **paragraphs** (`0x80` = `0x800` bytes = 2 KB) | `0x80` |

Set via a line like `#set heapsize=0x180` or `tsc /m app /sheapsize=0x180`. The
OS refuses to start an instance without `heapsize` free heap, and never shrinks
the heap below it.

### Multi-module and parameterised projects

Multi-file program (`triple.c`, `utils1.c`, `utils2.c`):

```
#system epoc img
#set epocinit=iplib
#model small jpi

#compile triple
#compile utils1
#compile utils2

#link triple
```

Assembler modules are allowed alongside C (`#compile afile` assembles
`afile.a`); assembler modules must follow the rules in the PLIB Reference.

A reusable generic project uses the `%main` macro (`unnamed.pr`):

```
#system epoc img
#set epocinit=iplib
#model small jpi
#compile %main
#link %main
```

invoked as `tsc /m unnamed.pr /smain=%1` (TopSpeed `/s` sets the macro from a
batch parameter). Conditional/parameterised builds are handled in the shipped
batch files (`cc.bat`, `make.bat`, `checkvid.bat`, `vid.bat`), which key
compilation off a `%jpivid%` env var (`v2` = full debug, `v0` = none) and pick
a per-app `.pr` if one exists, else `unnamed.pr`.

### Invoking the compiler: `tsc /m`

Build and link in one step:

```
tsc /m hello
```

"`/m` ... is that the project file is executed in 'make' mode, with files not
being recompiled or relinked needlessly." For source-level debugging add `/v2`:

```
tsc /m hello /v2
```

Other flags seen: `/fp<project>` selects the project file (e.g.
`tsc app.c /fpunnamed`); with no `/m` (and no `/l`) the project runs in
"compile" mode (compile only, always, no link). `%version`, `%priority`,
`%heapsize`, `%main` are passed as `/s<name>=<value>`.

> **`tsc` vs `tscx`.** `tsc` does not use expanded memory "and may cause
> problems, particularly when linking large applications." If linking fails,
> replace `tsc` with **`tscx`** (which uses expanded memory) in the batch files.

### Configuration files behind the build

The SIBO build config lives in two files (do **not** edit their pragmas):

- `tsprj.txt` — "compiled" with `tscfg` before use; the SDK version extends
  Clarion's to add the `Epoc img` system type.
- `stdepoc.h` — "always the first include file in any source module."

Header/library search paths come from the redirection file **`ts.red`** (maps
`*.H`, `*.LIB`, `*.PR`, etc. to `.` then the `\sibosdk\...` directories).

---

## 4. Build outputs: images, libraries, device drivers

The image file is produced from an intermediary `.exe` by the Psion tool
**`emake.exe`** (automated by the project system). "Essentially, `.img` files
are to Epoc what `.exe` files are to MS-DOS." `emake` runs (per `tsprj.txt`):

```
emake -b %afl% -o%name% -s -v%version% -p%priority% -h%heapsize% -%epoctype% %name%.exe
```

The `%epoctype` variable selects the output kind:

| `%epoctype` | Output | Extension |
|-------------|--------|-----------|
| `t1` (default) | image file | `.img` |
| `t2` | logical device driver | `.ldd` |
| `t3` | physical device driver | `.pdd` |
| `t4` | dynamic library | `.dyl` |

### Images (`.img` / `.app`)

`.img` and `.app` are "strictly speaking ... no real difference"; both are
image files. By convention a `.app` has one to four embedded **add-files**:

- `.pic` — icon
- `.rsc` or `.rzc` (compressed) — resource file
- `.shd` — shell data (Series 3 only)

Add-files are embedded by `emake` when an **add-file list** (`.afl`, a text file
naming one to four files, e.g. `tele.afl`) with the matching base name exists at
build time. Renaming the result to `.app` is a convention (not automatic). A
`.dfl` file similarly embeds DYL files.

Inspect an image with `edump <name>` (shows version, code/data segment sizes,
stack, heap, priority, checksums, add-file offsets, DYL table). Re-edit an
existing image's add-files/priority/heap/version *without rebuilding* using
`eremake.exe` (e.g. to swap in a French resource file):

```
eremake -afrquery -o..\french\query.app query.app
```

### Libraries (`.lib`)

Build a static library with `#dolink` (see §3):

```
#system epoc img
#set epocinit=iplib
#model small jpi
#compile utils1
#compile utils2
#dolink utils.lib
```

`#dolink` (not `#link`) prevents startup/standard-library linking, and the
explicit `.lib` overrides the default `.img`. Consumers pull it in with
`#pragma link (utils.lib)`.

### Loadable device drivers (`.LDD` / `.PDD`)

At a high level, LDDs and PDDs are just image-type outputs of `emake`
(`%epoctype=t2` → `.ldd`, `t3` → `.pdd`). Detail from the *Hardware Development
Kit* (§9):

All SIBO hardware "is controlled by logical and physical device drivers."
Layering:

```
    APPLICATION SOFTWARE
    Psion C / PLIB call interface
    LOGICAL DEVICE DRIVER   (LDD)
    PHYSICAL DEVICE DRIVER  (PDD)
    PHYSICAL HARDWARE
```

- A **PDD** "contains the code required for talking directly with the hardware"
  — low-level, hardware-specific services.
- An **LDD** "performs the logical processing that transforms these low level
  services into the high level services used by an application."
- The same LDD is often paired with a per-hardware PDD; an LDD may also talk to
  hardware directly, making a separate PDD unnecessary.

> **Language note.** "Psion device drivers are written in 8086 assembler and
> follow a prescribed structure" (HDK §9). Device drivers are **not** ordinary C
> programs — the C SDK / PLIB is the *client* side (`p_loadldd()`, `p_open()`,
> `p_close()`, `p_iow()`). The `.ldd`/`.pdd` build path exists in the project
> system, but the driver *source* is assembler.

**Naming and channels.** An LDD name is three characters + colon (e.g. `TTY:`
serial). A PDD name is the owning LDD's three chars + `.` + three more + colon
(e.g. `TTY.UAR`, the ASIC5 UART driver). Open a channel via `p_open`:

```c
p_open(&pcb, "LED:", -1);     /* LDD; -1 = ignore open mode */
p_open(&pcb, "TTY.UAR:", -1); /* a PDD directly (unusual) */
p_open(&pcb, "PAR:A", -1);    /* channel qualifier 'A' */
```

Typically an application opens only the LDD, which opens its PDD during
initialisation. I/O requests reach the driver's **strategy vector**, which maps
to the PLIB `p_iow()` call. Installable drivers are loaded dynamically
(`DevLoadLDD` service; do not call `DevInstall` directly) without resetting the
machine, and can replace resident ROM drivers of the same name (the table is
searched from the most-recently-installed end). EPOC handles "a maximum of 32
device drivers on a Series3 machine and 48 on other machines."

**LDD structure (HDK §9).** Single code segment, no data segments (wrapped by
`CodeSeg`/`EndCodeSeg`); begins with a `LibEnt` structure (2-byte
`LDDSignature`, 8-byte zero-terminated name *without* trailing colon, 2-byte
vector count ≥ 8, then the vector table). The **eight mandatory LDD functions**,
in order:

1. `DevFuncInstall` — on installation
2. `DevFuncRemove` — on removal
3. `DevFuncHold` — temporarily disable
4. `DevFuncResume` — re-enable
5. `DevFuncReset` — application terminated without closing channel
6. `DevFuncUnits` — query number of supported units/channels
7. `DevFuncOpen` — open a channel
8. `DevFuncStrategy` — access functionality via the I/O system

All are called FAR by the OS and must return with a FAR return.

**PDD structure (HDK §9).** Also single code segment, no data segments; starts
with a `LibEnt` (`PDDSignature`). **Two mandatory functions** — `DevFuncInstallPDD`
and `DevFuncRemovePDD` — with typically two more (`DevFuncOpenPDD`,
`DevFuncStrategyPDD`). PDD internal variables must live in the driver's own code
segment so hardware can be freed after a client process terminates.

---

## 5. Floating-point emulator (relevant to any build)

Because SIBO has no 8087, floating point is emulated. Under EPOC the emulator is
an LDD, `SYS$8087.LDD` (in `\sibosdk\lib\`), loaded and freed automatically —
saving ~8K per program and allowing sharing between processes. The C startup
(`r_emul.a`) looks for the LDD in the program's directory, then via the `EMS`
environment variable (case-sensitive). A program that dies with **panic 80**
before starting has "almost certainly failed to locate `SYS$8087.LDD`"; fixes:
copy the LDD next to the program, set `EMS`, or remove the floating-point
dependency. Note "floating point instructions can easily be generated
unexpectedly ... if the recommended build configuration pragmas are 'improved'"
— another reason not to touch the pragmas.

---

## 6. Resources / resource compiler

The General manual covers resources only lightly, as **add-files**: a `.rsc`
(or compressed `.rzc`) resource file can be embedded into a `.app` via the
`.afl` add-file list (§4). Multi-lingual applications isolate all text in a
resource file so a translated `.rzc` can be swapped in with `eremake` without
recompiling.

> **Not in this manual.** The resource *compiler* itself and the resource
> source (`.rss`) syntax are **not** described in the General Programming Manual.
> The manual points to the *Resource Files* chapter of the *Additional System
> Information* manual (not provided here) for that detail. Do not assume resource
> compiler behaviour beyond the add-file mechanism described above.

---

## 7. Detecting the machine at runtime

Most SIBO machines are distinguished by screen size via `p_getlcd()` /
`p_geticda()` (General ch. 7). Values include:

| Constant | Value | Display / machine |
|----------|-------|-------------------|
| `E_LCD_640_400` | 0 | 640x400 — MC 400 |
| `E_LCD_640_200_SMALL` | 1 | 640x200 — MC 200 |
| `E_LCD_160_80` | 4 | 160x80 — HC |
| `E_LCD_240_80` | 5 | 240x80 — Series 3 |
| `E_LCD_480_160` | 11 | 480x160 — Series 3a / 3c |
| **`E_LCD_240_100`** | **12** | **240x100 — Workabout** |
| `E_LCD_240_160` | 14 | 240x160 — Siena |

> **Workabout / MX.** The manual lists the Workabout as the 240x100 machine
> (`E_LCD_240_100`, value 12). It does not list a separate MX entry; an MX with
> the same screen reports the same LCD type. Series 3a vs 3c (same screen size)
> are told apart with `p_returnexpansionportinfo()` on EPOC ≥ `0x390F` — the
> pattern generalises if you must disambiguate same-screen models. Do not
> hardcode screen dimensions; query the LCD type.

---

## Quick reference

```
# Minimal PLIB app
#system epoc img          # -> .img output (Epoc image)
#set epocinit=iplib       # PLIB startup, 4k stack  (MUST precede #model)
#model small jpi          # 64K code + 64K data, register calling convention
#compile myapp
#link myapp

# Build (make mode)             tsc /m myapp
# Build with source debug       tsc /m myapp /v2
# Large link needs XMS          tscx /m myapp
# Override heap                  tsc /m myapp /sheapsize=0x180
# Inspect image                 edump myapp
# Re-embed add-files/version     eremake ...
```

| Extension | What it is |
|-----------|-----------|
| `.pr` | TopSpeed project file |
| `.c` / `.a` | C source / assembler source |
| `.img` / `.app` | EPOC image / image with embedded add-files |
| `.lib` | static library (`#dolink`) |
| `.ldd` / `.pdd` | logical / physical device driver (assembler source) |
| `.dyl` | dynamic library |
| `.rsc` / `.rzc` | resource file / compressed resource file |
| `.pic`, `.shd`, `.afl` | icon, shell data, add-file list |
| `ts.red` | header/library redirection (search paths) |
