Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03b4e219bd | ||
|
|
88849a4a7f | ||
|
|
4937811a01 | ||
|
|
9b3dc3000b | ||
|
|
a8a0102a2f | ||
|
|
038c38f029 | ||
|
|
961526b545 | ||
|
|
32d621e769 | ||
|
|
c80e7c8879 | ||
|
|
43353d4019 | ||
|
|
0ccd38994d | ||
|
|
0e765bc90c | ||
|
|
2b6804ab18 | ||
|
|
25078af7fc | ||
|
|
22a92dda5a | ||
|
|
51d864d96d | ||
|
|
1a54086cc2 | ||
|
|
8714b619c6 | ||
|
|
ae4a619172 | ||
|
|
bf5aa1b231 | ||
|
|
2731d22266 | ||
|
|
edda57bdce | ||
|
|
62d277e5d6 | ||
|
|
fc4df77300 | ||
|
|
1544159afc | ||
|
|
4bb853cdde | ||
|
|
cdea110e60 | ||
|
|
0ec706d658 | ||
|
|
b79b1fa175 | ||
|
|
724b5a11eb | ||
|
|
b86c1b083f | ||
|
|
c99927acdf | ||
|
|
397ce274e4 | ||
|
|
ff38d4eb70 | ||
|
|
4d8917bb02 |
@@ -0,0 +1,144 @@
|
||||
# Finishing the Workabout MX scanner — on-device continuation guide
|
||||
|
||||
This document describes exactly how to continue from the current findings to a
|
||||
fully working scanner client, using on-device debugging. Read `SCANNER-API.md`
|
||||
first for the findings this builds on.
|
||||
|
||||
## Where we are
|
||||
|
||||
Established (see `SCANNER-API.md` for detail):
|
||||
- The integral laser is driven as an **OO library object** in `SCANNER.DYL`
|
||||
(category token seen in ROM: **`oscanner`**), *not* by raw device I/O.
|
||||
- The app path is the standard OLIB one: `p_getlibh` → `p_newsend`/`f_newsend`
|
||||
(create + init) → `p_send` (configure / trigger / read).
|
||||
- Internals confirmed: `WL2:D` is the driver's channel; control ops **6** then
|
||||
**7** (`p_iow(chan,6)`, `p_iow(chan,7)`) fire the laser to a good decode
|
||||
(green LED) on the physical device.
|
||||
- The decompiled read (`FUN_858d`) creates the object via `LIBMANAGER`
|
||||
(`NMLIBCREATE`/`NMLIBCREATEBYHANDLE`), sets up a buffer, then reads; the
|
||||
decode lands CR/LF-terminated (default Symbol2 postamble).
|
||||
|
||||
Missing, and why on-device: the **message ordinals** and **parameter structs**
|
||||
for the scanner's methods. OLIB assigns ordinals dynamically from the whole
|
||||
class hierarchy (including base classes in `olib`/`hwim`), so they cannot be
|
||||
read reliably from a static dump — but they resolve at runtime, where the
|
||||
debugger can capture them. MAME cannot inject a barcode, so validation must be
|
||||
on hardware anyway.
|
||||
|
||||
## What to capture
|
||||
|
||||
For a working client you need five things:
|
||||
1. The scanner **category** (confirm the name/id for `p_getlibh` — candidate
|
||||
`oscanner`).
|
||||
2. The scanner **class** created by the app.
|
||||
3. The **message ordinals** for: init, set-parameters, enable/trigger, read.
|
||||
4. The **parameter/result structs** each message takes (esp. the read result
|
||||
buffer and its length).
|
||||
5. The exact **call sequence** the working app uses.
|
||||
|
||||
## Tooling: the SIBO Debugger
|
||||
|
||||
The SIBO Debugger (see manual `2-03`/`2-04`, "The SIBO Debugger") supports:
|
||||
- **Remote debugging**: development PC connected to the Workabout by a serial
|
||||
cable; debug up to 8 processes.
|
||||
- **Breakpoints in dynamic libraries / shared code** — required to break inside
|
||||
`SCANNER.DYL`.
|
||||
- Single-step, trace, register and memory display.
|
||||
|
||||
Build your own code for source-level debugging with:
|
||||
```
|
||||
#pragma debug(vid=>full) /* in the .pr / source */
|
||||
```
|
||||
and produce the `.sym`/`.dbd` symbol files with EMAKE.
|
||||
|
||||
### Set-up
|
||||
|
||||
1. Connect the PC to the Workabout with a serial cable (PC serial ↔ Workabout
|
||||
RS-232 port — note this is a *different* port from the barcode `TTY:D`).
|
||||
2. Start the debugger on the PC and connect to the remote (Workabout) target
|
||||
(Local/Remote CPU menu → Connect to Remote).
|
||||
3. Have the Workabout ready to run either the ROM `DEMMAN.APP`/`SCANAPP` (for
|
||||
Procedure A) or your test harness (Procedure B).
|
||||
|
||||
## Procedure A — trace the working ROM app (recommended first)
|
||||
|
||||
Goal: watch `DEMMAN`/`SCANAPP` drive the real scanner and record the ordinals.
|
||||
|
||||
1. On the Workabout, start `DEMMAN` and enter its Scanner (Barcode) demo.
|
||||
2. From the debugger, attach to that process and set breakpoints on the OLIB
|
||||
dispatch path so you catch the object creation and messages:
|
||||
- `LIBMANAGER` (`INT 0x84`) — sub-functions `NMLIBCREATE` (`AH=5`),
|
||||
`NMLIBCREATEBYHANDLE` (`AH=6`): captures the category/class and the object
|
||||
handle.
|
||||
- `MESSMANAGER` (`INT 0x83`) — `NMMESSSEND` and the send/receive variants:
|
||||
captures each message ordinal and its argument pointer.
|
||||
- Optionally the `SCANNER.DYL` method handlers we identified (init dispatch,
|
||||
trigger) as cross-checks.
|
||||
3. For each captured call record: `AH`, the category/class in registers, the
|
||||
message **ordinal**, and the pointed-to **argument struct** (dump memory at
|
||||
the pointer). For the read, note where the decoded bytes land and the
|
||||
returned length.
|
||||
4. Trigger a real scan and record the read message and its result buffer.
|
||||
|
||||
Result: the exact category, class, ordinals, param structs, and sequence.
|
||||
|
||||
## Procedure B — iterate a C test harness
|
||||
|
||||
Once Procedure A gives the ordinals (or to trial them), build a small OO client:
|
||||
|
||||
```c
|
||||
#include <plib.h>
|
||||
#include <p_object.h>
|
||||
|
||||
/* Fill these from Procedure A (placeholders until captured): */
|
||||
#define SCAN_CATEGORY /* category id/handle for "oscanner" via p_getcat/p_getlibh */
|
||||
#define C_SCANNER /* the scanner class */
|
||||
#define O_SCAN_INIT /* init message ordinal */
|
||||
#define O_SCAN_PARAMS /* set-parameters ordinal */
|
||||
#define O_SCAN_READ /* read-a-barcode ordinal */
|
||||
|
||||
GLDEF_C INT main(VOID)
|
||||
{
|
||||
VOID *lib, *scanner;
|
||||
/* result/param structs per Procedure A */
|
||||
lib = p_getlibh(SCAN_CATEGORY);
|
||||
scanner = p_newsend(SCAN_CATEGORY, C_SCANNER, O_SCAN_INIT, /*&initargs*/ 0);
|
||||
/* configure Symbol2 + the default 11-byte param block:
|
||||
04 3f 01 15 06 04 1e 80 0d 0a 06 (CR/LF postamble) */
|
||||
p_send3(scanner, O_SCAN_PARAMS, /*¶ms*/ 0);
|
||||
for (;;) {
|
||||
/* send the read message; result is the decoded barcode, CR/LF-terminated */
|
||||
p_send3(scanner, O_SCAN_READ, /*&result*/ 0);
|
||||
/* display result ... */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Build with `#pragma debug(vid=>full)`, run under the debugger, single-step the
|
||||
sends, and inspect return values / the result buffer. Adjust ordinals/structs
|
||||
until a real scan returns the UPC.
|
||||
|
||||
## Turning captures into the shipped reader
|
||||
|
||||
- Replace `code/inventory/bcode.c` with the OO client: `bcodeOpen` does
|
||||
`p_getlibh` + `p_newsend`(init) + params; `bcodeRead` does `p_send`(read) and
|
||||
returns the decoded string (strip the CR/LF postamble; strip any preamble byte
|
||||
`0x80` if present).
|
||||
- `upc.c` (UPC-A check-digit validation) is unchanged and already correct.
|
||||
- Cross-check the captured ordinals against the decompiled handlers in
|
||||
`SCANNER-API.md` (`FUN_7ae3` init dispatch — decoder type at `[channel+10]`,
|
||||
case 4 = Symbol; `FUN_7aa4` trigger = the `6`/`7` ops).
|
||||
|
||||
## Validation
|
||||
|
||||
- A valid UPC-A scan should return 12 digits (plus any pre/postamble), and
|
||||
`upcIsValid` should pass. The default postamble is CR LF (`0x0d 0x0a`).
|
||||
- Confirm repeated scans and clean shutdown (`p_send` a destroy/close message,
|
||||
then `p_close` any channel).
|
||||
|
||||
## Reproduce the RE environment (for further static/dynamic work)
|
||||
|
||||
See `../mx-re/toolchain-and-plan.md`: MAME `psionwamx` (live trace/dasm),
|
||||
radare2 (static), and Ghidra headless (decompilation of the DYLs). These remain
|
||||
useful for reading further `SCANNER.DYL` methods, but the ordinals themselves
|
||||
come from the on-device debugger as above.
|
||||
@@ -0,0 +1,263 @@
|
||||
# Workabout MX barcode scanner — API notes (reverse-engineered)
|
||||
|
||||
There is no official SDK documentation for the Workabout MX integral laser scanner.
|
||||
The Psion SIBO C SDK and I/O Devices Reference predate the MX and describe only the
|
||||
older *external* barcode modules (wands / wand-emulation), not the integral laser.
|
||||
These notes are reverse-engineered from device behaviour and from the Workabout MX
|
||||
ROM `w2mx_v7.20f_eng.bin` (strings), so treat unconfirmed items as such.
|
||||
|
||||
## Hardware
|
||||
|
||||
- Integral **laser** scanner, Symbol engine. The demo (`DEMMAN.APP`) reports
|
||||
`Type: Laser 1223` (some units `1222`).
|
||||
- It is a **decoded** scanner: it decodes in hardware and lights a green good-read
|
||||
LED. It does **not** emit an undecoded (HHLC) signal.
|
||||
- Trigger: the keyboard **scan key**, which the Window Server reports as key
|
||||
code **368**. The key alone does not fire the laser from an arbitrary app; the
|
||||
scanner software arms/reads the engine.
|
||||
|
||||
## Device driver and ports
|
||||
|
||||
`LLDEV` on the device lists a logical driver **`wl2`** (`units=1`). The scanner is
|
||||
reached through this driver. The demo's "Select Scanner" screen lets you pick both
|
||||
a **decoder type** and a **port** (device driver + unit letter):
|
||||
|
||||
- Port device drivers offered: **`TTY`**, **`WLS`**, **`WL2`**.
|
||||
The MX integral laser uses **`WL2`**.
|
||||
- Units seen in the ROM: **`WL2:A`** and **`WL2:D`**.
|
||||
|
||||
Decoder types (from the demo, "chosen from those used in standard Workabout
|
||||
products"):
|
||||
|
||||
| Decoder | Meaning |
|
||||
| --- | --- |
|
||||
| `Generic` | External non-configurable reader |
|
||||
| `Symbol1` | Workabout standard scanner |
|
||||
| `Datalogic` | Workabout CCD |
|
||||
| `HP` | Workabout Wand |
|
||||
| **`Symbol2`** | **Workabout MX scanner (the integral laser)** |
|
||||
|
||||
## Opening the device
|
||||
|
||||
```c
|
||||
VOID *h;
|
||||
INT err = p_open(&h, "WL2:D", (UINT)-1);
|
||||
```
|
||||
|
||||
Observed results:
|
||||
|
||||
| Call | Result | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `p_open("WL2:D", -1)` | `0` | **opens** — this is the integral-laser unit |
|
||||
| `p_open("WL2:A", -1)` | `-9` (`E_GEN_INUSE`) | in use — held by the resident scanner software |
|
||||
| `p_open("WL2:B" / ":C")` | (untested) | — |
|
||||
|
||||
`WL2` has a single unit, so only one process may hold it. `WL2:A` is permanently
|
||||
`-9` even with the demo closed, indicating a resident holder; `WL2:D` is the unit
|
||||
an application opens.
|
||||
|
||||
## The 11-byte parameter block
|
||||
|
||||
The scanner is configured by an **eleven-byte parameter block**. The application
|
||||
interprets these bytes and converts them into commands for the selected decoder
|
||||
(so the same block means different wire commands for `Symbol2` vs `HP`, etc.):
|
||||
|
||||
| Byte | Parameter |
|
||||
| --- | --- |
|
||||
| Param0 | Decode security |
|
||||
| Param1 | Code type (symbology select) |
|
||||
| Param2 | Decode options A |
|
||||
| Param3 | Decode options B |
|
||||
| Param4 | General parameters |
|
||||
| Param5 | ITF length 1 |
|
||||
| Param6 | ITF length 2 |
|
||||
| Param7 | Preamble |
|
||||
| Param8 | Postamble byte 1 |
|
||||
| Param9 | Postamble byte 2 |
|
||||
| Param10 | General decode options |
|
||||
|
||||
The preamble/postamble bytes mean decoded output may carry configurable leading /
|
||||
trailing characters — parsing code must account for them.
|
||||
|
||||
## Access model
|
||||
|
||||
Applications do not drive `WL2` byte-by-byte themselves; they use the ROM library
|
||||
**`SCANNER.DYL`** (used by `SCANAPP.APP` and `DEMMAN.APP`). That library opens the
|
||||
port, converts the 11-byte block to decoder commands, configures and enables the
|
||||
engine, and returns decoded scans.
|
||||
|
||||
## Paths that do NOT work for the integral laser
|
||||
|
||||
Recorded so they are not retried:
|
||||
|
||||
- **`bar*.ldd` decoders (`BAREAN`, `BARC39`, …) + `BAR:` device.** These are
|
||||
software decoders for the *external* wand modules. Loading `BAREAN.LDD` and
|
||||
opening `BAR:A`/`BAR:B` returns `-41` (`E_FILE_DEVICE`, "no interface found in
|
||||
slot") — the integral laser is not a `BAR:` expansion interface. `BAR:D`/`BAR:E`
|
||||
return `-38` (`E_FILE_NAME`, invalid unit).
|
||||
- **`TTY:D` serial reads.** Opening `TTY:D` powers the laser (it fires briefly),
|
||||
but no decoded bytes ever arrive on the serial channel — not with default
|
||||
config, not after `P_FSET` to 9600/8/1, not with `P_OBEY_DSR` cleared, not with
|
||||
the intelligent-reader escape commands (`<Esc>-y1J` / `<Esc>-y1K`), not reading
|
||||
one byte at a time. The integral laser's decoded data is not on `TTY:D`.
|
||||
|
||||
## Initialisation sequence (reverse-engineered from the ROM)
|
||||
|
||||
Opening `WL2:D` is not enough — the scanner must be configured and enabled first.
|
||||
The sequence was recovered by disassembling `SCANNER.DYL` in the v7.20f ROM
|
||||
(around file offset `0xD7AA4`). It uses the SIBO I/O executive **`int 0xCF`**,
|
||||
whose convention here is: **`CL` = I/O function code, `BX` = channel handle,
|
||||
`DX` = argument (by value)**, result in `AX`. From C this is the `p_iow(chan,
|
||||
func, ...)` layer.
|
||||
|
||||
Function codes observed on the `WL2` channel (from `p_file.h`, plus WL2-specific
|
||||
ones above the standard range):
|
||||
|
||||
| CL | Meaning |
|
||||
| --- | --- |
|
||||
| 6 | (`P_FDETACH` slot) used as a scanner enable/control op |
|
||||
| 7 | (`P_FSET`) used as a scanner enable/control op |
|
||||
| 8 | `P_FSENSE` |
|
||||
| 9 | `P_FFLUSH` |
|
||||
| 0x0C (12) | **WL2-specific: write a config/command byte** (byte passed in `DX`) |
|
||||
|
||||
The driver keeps a per-channel structure; the param block sits at **channel+4**,
|
||||
with the **decoder type at offset +6** (dispatch values 1..4) and the **11-byte
|
||||
parameter block at +8..+0x12**. Configuration is pushed to the engine as a series
|
||||
of `cl=0x0C` writes (one command byte per call in `DX`), then enable ops
|
||||
(`cl=6`, `cl=7`).
|
||||
|
||||
### Default Symbol2 parameter block (from the ROM)
|
||||
|
||||
The routine at `0xD7AB3` fills the 11-byte block with these defaults for the
|
||||
Workabout MX (Symbol2) scanner:
|
||||
|
||||
| Byte | Param | Value |
|
||||
| --- | --- | --- |
|
||||
| +8 | Param0 Decode security | `0x04` |
|
||||
| +9 | Param1 Code type | `0x3F` (all symbologies) |
|
||||
| +0xA| Param2 Decode options A | `0x01` |
|
||||
| +0xB| Param3 Decode options B | `0x15` |
|
||||
| +0xC| Param4 General params | `0x06` |
|
||||
| +0xD| Param5 ITF length 1 | `0x04` |
|
||||
| +0xE| Param6 ITF length 2 | `0x1E` |
|
||||
| +0xF| Param7 Preamble | `0x80` |
|
||||
| +0x10| Param8 Postamble 1 | `0x0D` (CR) |
|
||||
| +0x11| Param9 Postamble 2 | `0x0A` (LF) |
|
||||
| +0x12| Param10 General decode | `0x06` |
|
||||
|
||||
So **decoded output is terminated by CR LF** (`0x0D 0x0A`), and Code type `0x3F`
|
||||
enables all symbologies (UPC included). A reader should assemble bytes until
|
||||
CR/LF and strip the preamble/postamble.
|
||||
|
||||
### Confirmed on-device
|
||||
|
||||
- `p_open(&h, "WL2:D", -1)` returns `0`.
|
||||
- Issuing control ops **6 then 7** on the channel (`p_iow(h, 6)`, `p_iow(h, 7)`)
|
||||
**triggers a scan**: the laser fires and decodes (green good-read LED). This is
|
||||
reproducible. The trigger is one-shot — it must be re-issued for each scan.
|
||||
|
||||
### NOT yet solved: retrieving the decoded data
|
||||
|
||||
After a confirmed good read, the decoded barcode could **not** be retrieved from
|
||||
the `WL2:D` channel by any tried method:
|
||||
|
||||
- `p_iow(h, P_FREAD, buf)` (count-in-`buf[0]` style) — no data.
|
||||
- `p_read(h, buf, len)` (synchronous, with length) — no data.
|
||||
- `p_ioc(h, P_FREAD, &stat, buf, &len)` + trigger + `p_iowait()` (async) — no data.
|
||||
|
||||
The retrieval logic in `SCANNER.DYL`/`SCANAPP` fetches the decoded data on a
|
||||
**separate handle** (a driver global, e.g. `[0x13b5]`) obtained through
|
||||
**OS-service stubs** of the form `mov ah,N; int 0x84/0x85/0x86/0x87; ret` — not a
|
||||
plain `P_FREAD` on the `WL2:D` control channel. This resolves an earlier puzzle:
|
||||
the SIBO executive uses **different register conventions per interrupt vector** —
|
||||
the `int 0x84` service family passes the function in `AH`, whereas the `int 0xCF`
|
||||
I/O executive uses `CL`.
|
||||
|
||||
`SCANNER.DYL` is an **object-oriented ROM library (a DYL)**. This strongly
|
||||
suggests the scanner is driven by **creating a scanner object and sending it
|
||||
messages** (the OLIB/OO model), not by raw device I/O on `WL2:D`. That would
|
||||
explain why every raw `WL2:D` read returned no data — wrong paradigm.
|
||||
|
||||
**Confirmed working on device:** `p_open("WL2:D")` + control ops `6` then `7`
|
||||
trigger the laser and a good decode (green LED). Only *retrieving* the decoded
|
||||
bytes is unsolved.
|
||||
|
||||
### Decompilation (Ghidra) — the read path
|
||||
|
||||
`SCANNER.DYL`/`SCANAPP` decompile cleanly in Ghidra (16-bit real-mode x86). The
|
||||
app-level read routine (`FUN_858d`) is:
|
||||
|
||||
```c
|
||||
obj->flags |= 8;
|
||||
obj->[0x190] = 0;
|
||||
FUN_8ce8(); FUN_8cd3(); FUN_8cd3(); // pre-read setup
|
||||
obj->[0x194] = FUN_8d8e(); // acquire a read handle (int 0x84)
|
||||
obj->[0x192] = FUN_8c8b(0,2,1,obj); // set up the read buffer (int 0x84; int 0xd2)
|
||||
swi(0xcf)(); swi(0xcf)(); // config / enable ops on the channel
|
||||
if (obj->[0x196] == 0)
|
||||
obj->[0x196] = swi(0xcf)(); // P_FREAD -> decoded result stored at +0x196
|
||||
else
|
||||
swi(0xcf)(); // P_FCANCEL
|
||||
```
|
||||
|
||||
The decoder-side helpers also decompile clearly: `FUN_7ae3` is the per-decoder
|
||||
init (dispatch on `[channel+10]`; case 4 = the Symbol path, which calls
|
||||
`FUN_6dc4` to build the `0xff 0xee …` command frame and writes the 11-byte param
|
||||
block); `FUN_7aa4` is the trigger (two `int 0xCF` ops = the `6`/`7` we use).
|
||||
|
||||
### The scanner is an OO library object (paradigm correction)
|
||||
|
||||
The System Services vector table names the services: **`int 0x84` = `LIBMANAGER`**
|
||||
(the Library Manager) and **`int 0x83` = `MESSMANAGER`** (object messaging).
|
||||
`LIBMANAGER` sub-functions (in `AH`) are `NMLIBLOAD=0, UNLOAD=1, LINK, FIND,
|
||||
HANDLE, CREATE(=5), CREATEBYHANDLE(=6), …`. So the read's `FUN_8d8e`/`FUN_8c8b`
|
||||
call **`NMLIBCREATE` / `NMLIBCREATEBYHANDLE`** — they *create a scanner library
|
||||
object*.
|
||||
|
||||
**Therefore the scanner is driven as an object-oriented library object, not by
|
||||
device I/O.** `WL2:D` is what `SCANNER.DYL` opens and reads *internally*; an
|
||||
application never `p_open`/`p_read`s it. Every earlier raw-device attempt was the
|
||||
wrong paradigm — which is why they returned nothing.
|
||||
|
||||
The correct C approach uses the standard OO API (the same one the SDK's
|
||||
`growbar.c` demo uses):
|
||||
- `p_getlibh(category)` — get the library handle for a category.
|
||||
- `p_newsend` / `f_newsend(category, class, O_..._INIT, &args)` — create the
|
||||
scanner object and send its init message.
|
||||
- `p_send2` / `p_send3(obj, O_...)` — send it messages (configure, trigger, read).
|
||||
|
||||
These C wrappers sit over `LIBMANAGER` (create/handle) and `MESSMANAGER` (send).
|
||||
|
||||
**Next RE target (well-defined):** extract from `SCANNER.DYL` its **category id**
|
||||
(candidate token in ROM: **`oscanner`**), **class**, and the **message ordinals**
|
||||
(`O_...`) for init / set-params / trigger / read, plus their parameter structs.
|
||||
OLIB assigns ordinals dynamically from the whole class hierarchy (base classes in
|
||||
`olib`/`hwim`), so they resolve at runtime rather than in a static dump — capture
|
||||
them with the **SIBO Debugger**, which supports remote debugging (PC↔device over
|
||||
serial) and **breakpoints inside dynamic libraries**. With those, a C client can
|
||||
create the scanner object and message it. Validation requires the physical device
|
||||
(MAME cannot inject a scan).
|
||||
|
||||
**See [`CONTINUATION.md`](CONTINUATION.md)** for the full on-device debugging
|
||||
procedure (capture the category/ordinals/params via `LIBMANAGER`/`MESSMANAGER`
|
||||
breakpoints), a C test-harness template, and how to turn the captures into the
|
||||
shipped reader.
|
||||
|
||||
To close it, one of:
|
||||
1. The **Workabout MX C SDK** `SCANNER.DYL` header / OO category definition (what
|
||||
`SCANAPP` was built against) — the clean answer, and likely the only practical
|
||||
one, because the interface is object-oriented.
|
||||
2. A deep RE effort under MAME (`psionwamx`): automate the UI to launch `SCANAPP`,
|
||||
breakpoint the read routine, and trace it. Caveats: MAME cannot inject a real
|
||||
barcode (no laser input), so only the read *setup* is observable in emulation;
|
||||
the reconstructed sequence must still be validated on the physical device.
|
||||
|
||||
## Error codes seen (from `epocdefs.h`)
|
||||
|
||||
| Value | Name | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `-9` | `E_GEN_INUSE` | device already open / in use |
|
||||
| `-32` | `E_FILE_EXIST` | (LDD) already loaded |
|
||||
| `-38` | `E_FILE_NAME` | invalid device name / unit |
|
||||
| `-41` | `E_FILE_DEVICE` | device / interface not present |
|
||||
+35
-42
@@ -1,66 +1,59 @@
|
||||
/*
|
||||
* scan.c - Phase 1 barcode diagnostic for the Workabout MX integral laser.
|
||||
* scan.c - Phase 1: WL2:D scanner, asynchronous read (reverse-engineered).
|
||||
*
|
||||
* The EAN/UPC decoder LDD (BAREAN.LDD) is loaded, then the decoder device is
|
||||
* opened. The exact device-name form and open mode are not documented in the
|
||||
* available manuals (they live in the I/O Devices Reference), so this build
|
||||
* tries several candidates in one run and reports which one opens - most
|
||||
* likely "BAR:D" (the decoder over port D, like TTY:D), since bare "BAR:"
|
||||
* returns E_FILE_NAME (-38). Once open, it dumps every byte of each read so
|
||||
* the decoded UPC format can be seen.
|
||||
*
|
||||
* Blocking I/O; exit via the System screen.
|
||||
* Confirmed: opening WL2:D and issuing control ops 6/7 triggers a scan (laser
|
||||
* fires, green LED). Neither synchronous read form returned data, and SCANAPP
|
||||
* in the ROM uses an async pattern (post read -> P_FREAD -> P_FCANCEL). So this
|
||||
* posts an asynchronous read (p_ioc), triggers the scan (ops 6/7), then waits
|
||||
* for completion (p_iowait). Decoded output is CR/LF-terminated.
|
||||
*/
|
||||
#include <plib.h>
|
||||
#include "bcode.h"
|
||||
|
||||
#define NCAND 5
|
||||
#define WL2_TRIG1 6
|
||||
#define WL2_TRIG2 7
|
||||
|
||||
GLDEF_C INT main(VOID)
|
||||
{
|
||||
VOID *chan;
|
||||
UBYTE buf[64];
|
||||
INT loadErr, r, n, i, opened;
|
||||
UINT k;
|
||||
UBYTE buf[256];
|
||||
WORD stat;
|
||||
UWORD len;
|
||||
INT err, i;
|
||||
|
||||
static TEXT *names[NCAND] = { "BAR:D", "BAR:D", "BAR:", "BAR:E", "BAR:A" };
|
||||
static UINT modes[NCAND] = { (UINT)-1, 0, 0, (UINT)-1, (UINT)-1 };
|
||||
|
||||
loadErr = bcodeLoad();
|
||||
p_printf("load %s.LDD: %d\n\n", BCODE_LDD, loadErr);
|
||||
|
||||
opened = 0;
|
||||
for (k = 0; k < NCAND; k++) {
|
||||
r = p_open(&chan, names[k], modes[k]);
|
||||
p_printf("open \"%s\" mode %u: %d\n", names[k], modes[k], r);
|
||||
if (r >= 0) {
|
||||
opened = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (opened == 0) {
|
||||
p_printf("\nNothing opened. Press a key to exit.\n");
|
||||
err = p_open(&chan, "WL2:D", (UINT)-1);
|
||||
p_printf("open WL2:D: %d\n", err);
|
||||
if (err < 0) {
|
||||
p_printf("Press a key to exit.\n");
|
||||
p_getch();
|
||||
return 1;
|
||||
}
|
||||
|
||||
p_printf("\nOpened \"%s\". Scan a barcode. System screen to exit.\n\n",
|
||||
names[k]);
|
||||
p_printf("Present a barcode under the laser. Async reads:\n\n");
|
||||
|
||||
for (;;) {
|
||||
n = p_read(chan, buf, sizeof(buf));
|
||||
if (n < 0) {
|
||||
p_printf("read err %d\n", n);
|
||||
len = sizeof(buf);
|
||||
stat = E_FILE_PENDING;
|
||||
p_ioc(chan, P_FREAD, &stat, buf, &len); /* post async read */
|
||||
|
||||
p_iow(chan, WL2_TRIG1); /* trigger the scan */
|
||||
p_iow(chan, WL2_TRIG2);
|
||||
|
||||
p_iowait(); /* wait for a completion */
|
||||
|
||||
if (stat == E_FILE_PENDING) { /* our read didn't finish */
|
||||
p_iow(chan, P_FCANCEL);
|
||||
p_waitstat(&stat);
|
||||
continue;
|
||||
}
|
||||
if (n == 0)
|
||||
if (stat < 0) {
|
||||
p_printf("stat %d\n", stat);
|
||||
continue;
|
||||
p_printf("n=%d:", n);
|
||||
for (i = 0; i < n; i++)
|
||||
}
|
||||
p_printf("len=%d:", len);
|
||||
for (i = 0; i < 40 && i < 256; i++)
|
||||
p_printf(" %d", buf[i]);
|
||||
p_printf(" \"");
|
||||
for (i = 0; i < n; i++)
|
||||
for (i = 0; i < 40 && i < 256; i++)
|
||||
p_putch(buf[i] >= ' ' ? buf[i] : '.');
|
||||
p_printf("\"\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Workabout MX — reverse-engineering toolchain & documentation plan
|
||||
|
||||
Goal: reverse-engineer the Workabout MX ROM (`w2mx_v7.20f_eng.bin`, v7.20f) and
|
||||
produce complete programming documentation for the device, since none exists for
|
||||
the MX specifically.
|
||||
|
||||
## Toolchain (reproducible, headless)
|
||||
|
||||
ROM identifies as MAME machine **`psionwamx`** ("Workabout mx"), CPU NEC V30MX
|
||||
(`psion_asic9mx`), with `psion_asic5` (barcode/UART) and the SIBO expansion slots.
|
||||
|
||||
Setup:
|
||||
```
|
||||
mkdir -p roms/psionwamx && cp w2mx_v7.20f_eng.bin roms/psionwamx/w2mx_v7.20f.bin
|
||||
mame psionwamx -rompath roms -verifyroms # -> "romset psionwamx is good"
|
||||
```
|
||||
|
||||
**Dynamic RE (MAME debugger, headless via xvfb):**
|
||||
```
|
||||
xvfb-run -a mame psionwamx -rompath roms -debug \
|
||||
-debugscript CMDS.txt -sound none -seconds_to_run N
|
||||
```
|
||||
Useful `CMDS.txt` debugger commands:
|
||||
- `trace FILE` then `go` — log every executed instruction (correct bank mapping).
|
||||
- `dasm FILE,ADDR,LEN` — disassemble a region as currently mapped.
|
||||
- `dump FILE,ADDR,LEN` — hex dump memory.
|
||||
- `bpset ADDR,1,{commands}` — breakpoint with actions (e.g. dump then continue).
|
||||
- `wpset ADDR,LEN,rw` — watchpoint (catch who reads/writes a driver global).
|
||||
|
||||
This resolves the segment/relocation mapping that flat static disassembly of the
|
||||
ROM could not (e.g. `SCANNER.DYL` / `SCANAPP` code).
|
||||
|
||||
|
||||
**Decompilation (Ghidra headless):** extract the DYL region to a slice and load
|
||||
as 16-bit real-mode x86:
|
||||
```
|
||||
ghidra-analyzeHeadless PROJ NAME -import slice.bin -processor "x86:LE:16:Real Mode" \
|
||||
-scriptPath SCRIPTS -postScript decomp.java -deleteProject
|
||||
```
|
||||
`SCANNER.DYL`/`SCANAPP` decompile to readable pseudo-C this way (Ghidra 12 needs
|
||||
a Java GhidraScript, not Python). Caveat: TopSpeed C uses the register-based
|
||||
`jpi` calling convention, so argument recovery may need convention hints, but
|
||||
control flow, struct access and the `int` service calls read clearly.
|
||||
|
||||
**Static RE:** `radare2 -e asm.arch=x86 -e asm.bits=16 w2mx_v7.20f_eng.bin`
|
||||
(good for strings, byte-pattern search, and clean fixed-mapped regions).
|
||||
|
||||
## What "complete documentation" covers
|
||||
|
||||
1. **Hardware** (largely in the HDK, to be confirmed/extended for the MX):
|
||||
memory map & banking, ASIC1/2/4/5/9MX register maps, I/O ports, interrupts.
|
||||
2. **Boot & OS**: reset path, hardware init sequence, the `int 0xCF` OS/executive
|
||||
call and its function table, the `int 0xD9`/etc. service vectors.
|
||||
3. **I/O & drivers**: the device model, `p_open`/`p_iow` function codes per driver,
|
||||
and each driver: `TTY`, `WL2` (scanner), `MCR`, `PAR`, `SND`, IR, etc.
|
||||
4. **Scanner** (immediate priority, partly done — see SCANNER-API.md): `WL2:D`,
|
||||
Symbol2 decoder, the 11-byte param block, trigger (ops 6/7), and the
|
||||
still-open **data-retrieval** path — to be nailed by tracing `SCANAPP` live.
|
||||
5. **System services / libraries**: file system (DBF), window server, HWIM/FORM.
|
||||
|
||||
## RE order
|
||||
|
||||
1. Nail the scanner data-retrieval by running `SCANAPP`/`DEMMAN` under MAME,
|
||||
breakpointing the `WL2` code, and tracing how the decoded bytes are delivered.
|
||||
2. Map the `int 0xCF` OS call table (breakpoint the vector, log CL/BX/DX per call).
|
||||
3. Document the boot + ASIC init (already partly traced).
|
||||
4. Work outward to the drivers and system services.
|
||||
|
||||
Findings land in `docs/` as they are confirmed (e.g. `SCANNER-API.md`), each
|
||||
marked confirmed-on-emulator / confirmed-on-device / inferred.
|
||||
|
||||
## Notes
|
||||
|
||||
- `int 0xCF` convention (from traced code): `CL` = I/O function code, `BX` =
|
||||
channel handle, `DX` = argument; result in `AX`. This is the `p_iow` layer.
|
||||
- MAME cannot inject a real barcode into the laser, so scanner *decode* may not
|
||||
reproduce in emulation; but the *code path* (open/config/enable/read structure)
|
||||
traces fully, which is what we need to write correct client code.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Workabout MX / SIBO programming reference
|
||||
|
||||
A programming reference for the Psion Workabout MX and the SIBO platform,
|
||||
synthesised from the Psion SDK manuals and the Hardware Development Kit, with
|
||||
reverse-engineered internals from the v7.20f ROM layered on top. Built as the
|
||||
foundation for developing applications (e.g. the barcode inventory app in
|
||||
`code/inventory/`).
|
||||
|
||||
Each document cites its source manual and marks claims as documented,
|
||||
**[RE]** reverse-engineered, or inferred/uncertain.
|
||||
|
||||
## Sections
|
||||
|
||||
| # | Document | Covers |
|
||||
| --- | --- | --- |
|
||||
| 01 | [Building applications](01-building-apps.md) | Toolchain, TopSpeed C, SIBO types, `GLDEF_C`/etc., `.pr` project files, images/libs/LDDs |
|
||||
| 02 | [System & OS](02-system.md) | Processes, memory/segments, panics + ranges, error codes, async I/O, the executive call |
|
||||
| 03 | [I/O devices & drivers](03-io-devices.md) | `p_open`/`p_iow`/`p_ioc`, function codes, serial (`P_SRCHAR`), per-device drivers, loading LDDs |
|
||||
| 04 | [PLIB core API](04-plib-core.md) | Strings, memory, console, conversion, math, date/time, categorised function reference |
|
||||
| 05 | [File system & DBF database](05-filesystem-dbf.md) | Files, streams, directories; the full DBF record/database API |
|
||||
| 06 | [User interface](06-ui.md) | Window Server model, events (`WM_*`, `WGetEvent`), the `CON:` console layer |
|
||||
| 07 | [Hardware](07-hardware.md) | CPU, memory/banking, ASIC1/2/4/5/9 register maps, I/O ports, interrupts, expansion |
|
||||
| 08 | [Boot & OS-call internals **[RE]**](08-re-boot-oscalls.md) | Reset/ASIC-init path, OS-service `INT` vectors, boot port activity — from the live MAME trace |
|
||||
|
||||
Related:
|
||||
- [Barcode scanner API](../inventory/SCANNER-API.md) — the Symbol2/`WL2` laser scanner (reverse-engineered): device, trigger, decoder params, and the OO-library-object architecture.
|
||||
- [Scanner continuation guide](../inventory/CONTINUATION.md) — the on-device SIBO-Debugger procedure to capture the OO message ordinals and finish a working scanner client.
|
||||
- [RE toolchain & plan](../mx-re/toolchain-and-plan.md) — MAME/radare2/Ghidra RE setup and roadmap.
|
||||
|
||||
## Status
|
||||
|
||||
The manual-derived sections (01–07) are broad and citation-backed. The
|
||||
reverse-engineered material (08, the scanner API, and the executive-call
|
||||
convention notes) is being extended by tracing the ROM under MAME
|
||||
(`psionwamx`). Known open items: the full `INT`-vector → service map, and the
|
||||
scanner data-retrieval path.
|
||||
@@ -0,0 +1,573 @@
|
||||
# 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) |
|
||||
@@ -0,0 +1,719 @@
|
||||
# Psion SIBO / EPOC — Operating-System Programming Model
|
||||
|
||||
Reference notes for the SIBO family (HC, MC, Series 3/3a/3c, Siena, Workabout /
|
||||
Workabout MX) running the ROM-resident **EPOC** operating system, programmed
|
||||
through the **PLIB** library.
|
||||
|
||||
Sources, cited inline as:
|
||||
|
||||
- **[GEN]** — *SIBO 'C' SDK, General Programming Manual*, v2.30
|
||||
- **[PLIB]** — *PLIB Reference*
|
||||
- **[SYS]** — *EPOC OS System Services* (assembly-level service interface)
|
||||
|
||||
Everything below is drawn from those manuals. Items derived from prior
|
||||
reverse-engineering, rather than the manuals, are explicitly flagged
|
||||
**[RE — reverse-engineered]**. Workabout-MX-specific points are flagged
|
||||
**[MX]**; where the manuals do not distinguish the MX, that is noted as an
|
||||
uncertainty rather than invented.
|
||||
|
||||
> **Terminology.** *SIBO* is the hardware architecture (8086-class CPU, ASICs,
|
||||
> SSD storage). *EPOC* is the operating system that runs on it. This is the
|
||||
> 16-bit SIBO-era EPOC, not the later 32-bit EPOC32/Symbian. [GEN §2]
|
||||
|
||||
---
|
||||
|
||||
## 1. The memory model
|
||||
|
||||
### 1.1 Small model, segments, and memory moving
|
||||
|
||||
EPOC programs are compiled in the 8086 **small model**: the code segment and the
|
||||
data segment are each limited to **64 KB**. The restriction is deliberate — it
|
||||
lets EPOC **move memory segments** (including a process's own code and data
|
||||
segments) around physical RAM without any cooperation from the application, which
|
||||
is essential for efficient RAM use in a multitasking system. [GEN §1; PLIB
|
||||
"Introduction"]
|
||||
|
||||
Physical memory (address 0 to 0xFFFFF, up to 1 MB addressable; banked above
|
||||
512 KB) is laid out roughly as: interrupt vectors (1 KB) → screen bitmap (small
|
||||
displays) → OS data space → **allocated memory segments** → unallocated memory →
|
||||
internal RAM drive (`LOC::M:`) → environment variables (≤4 KB) → screen bitmap
|
||||
(large displays) → ROM (~256 KB). [PLIB §7 "Overview of system memory usage"]
|
||||
|
||||
All unallocated RAM is kept in a **single contiguous chunk**. Consequently
|
||||
creating, deleting, or resizing one segment forces others to move. Segment
|
||||
addresses and sizes are always expressed in **16-byte paragraphs**; segments
|
||||
start on 16-byte boundaries. The segment allocator's maximum segment size is
|
||||
**512 KB**. [PLIB §7]
|
||||
|
||||
**How moving is safe.** The supervisor process (`SYS$MANG`) does the moving. It
|
||||
runs at higher priority than any other process (so it is not interrupted mid-move)
|
||||
and its own data/code segments never move. After a move it walks each process
|
||||
context and **adjusts the 8086 segment registers** (CS, DS, SS, ES) by the amount
|
||||
the relevant segment moved. A register is adjusted only if it holds a value inside
|
||||
a moved segment's range. [GEN §1; PLIB §7]
|
||||
|
||||
Two rules the application must obey so the supervisor's fix-up works:
|
||||
|
||||
- Never store a segment register to memory and later reload it (the memory may
|
||||
move in between). In assembler, protect any unavoidable save/restore by
|
||||
disabling interrupts across it (which blocks the context switch).
|
||||
- Never load a non-segment value into a segment register (it might fall inside a
|
||||
moved segment's range and get "adjusted"). [GEN §1]
|
||||
|
||||
### 1.2 Segment kinds and naming
|
||||
|
||||
Allocated segments are described by a **name**, a **handle**, a **size**, and an
|
||||
**address**. [PLIB §7]
|
||||
|
||||
- **Device segments** — created when an external device is installed, deleted on
|
||||
removal; normally do not resize. Placed at **lower addresses** than dynamic
|
||||
segments so device drivers (e.g. serial) are not disturbed by volatile dynamic
|
||||
activity. The first two are special: the data segments of `SYS$NULL` and
|
||||
`SYS$MANG`, created at startup and never resized. Extensions `.LDD` / `.PDD`
|
||||
denote Logical / Physical Device Driver segments.
|
||||
- **Dynamic segments** — code and data segments created/deleted as processes come
|
||||
and go; process data segments grow/shrink to fit heap demand. Extensions:
|
||||
`.$SC` = shared primary code segment, `.DYL` = dynamic library code, `.$nn` =
|
||||
process data segment (nn = process slot number).
|
||||
|
||||
The **segment handle** is the relative address of the segment's 16-byte
|
||||
index-table entry in OS data space; that entry holds the segment address, a
|
||||
**usage count**, and the name. Usage count = number of processes using the
|
||||
segment; when it drops to zero the segment is (normally) deleted. The index table
|
||||
has fixed capacity — typically **96 total = 32 device + 64 dynamic**. [PLIB §7]
|
||||
|
||||
Segment API (names follow file-name rules, ≤8 chars + optional `.ext`):
|
||||
`p_sgcreate`, `p_sgdelete`, `p_sgopen`, `p_sgclose`, `p_sgcopyto`, `p_sgcopyfr`,
|
||||
`p_sgsize`, `p_sgadjust`, `p_sgfind`, `p_sglock` / `p_sgunlock` (usage-count
|
||||
inc/dec). Access to data in an **external segment** is less convenient than to the
|
||||
process data segment, but a single external segment can grow to the 512 KB limit —
|
||||
the standard way to hold data structures larger than 64 KB (e.g. a document).
|
||||
[GEN §1; PLIB §7]
|
||||
|
||||
### 1.3 The process data segment and heap
|
||||
|
||||
A process data segment is at most **0xFFE0** bytes (32 bytes short of 64 KB, so a
|
||||
stack underflow always trips an address trap). Layout, low → high:
|
||||
|
||||
1. reserved static variables (0x40 bytes; "magic statics")
|
||||
2. floating-point emulator data space (0x200 bytes from offset 0x100)
|
||||
3. processor stack
|
||||
4. initialised static variables
|
||||
5. uninitialised static variables (also zero-initialised)
|
||||
6. **process heap** — at the top so it can grow by growing the data segment
|
||||
|
||||
The word at offset 0 is initialised to **0xDEAD**; many OS calls check it and
|
||||
panic (`PanicDead0`, panic 68) if a stray NULL-pointer write has changed it. Stack
|
||||
bytes are pre-filled with 0xFF (the count of 0xFF from 0x40 measures free stack,
|
||||
unless the FP emulator is in use, in which case the stack floor is 0x300). [PLIB
|
||||
§7 "Process data segments"; §12 "Reserved statics"]
|
||||
|
||||
### 1.4 Heap cell allocation
|
||||
|
||||
Cells are referenced **directly by address** and **do not move** to compact freed
|
||||
space. Each cell is prefixed by a hidden 16-bit length word (skipped by the
|
||||
address returned to you; not counted by `p_alen`). Writing past a cell's bounds
|
||||
corrupts the length word (or a free-list pointer) — a "time bomb" that a later
|
||||
allocator call detects and turns into a panic.
|
||||
|
||||
The heap is **not fixed size**: EPOC grows it (up to the 0xFFE0 limit) to satisfy
|
||||
requests and shrinks it to hand memory back, but never below the **initial /
|
||||
minimum heap** value stored in the image (default 2 KB). Allocation is
|
||||
**first-fit** over the free list; if nothing fits, the data segment is grown by
|
||||
the requested amount **plus the heap granularity** (default `E_GROWBY_DEFAULT`
|
||||
2 KB; max `E_MAX_GROWBY` 16 KB, set via `p_hgran`). [PLIB §7 "The heap allocator"]
|
||||
|
||||
| Function | Purpose |
|
||||
|---|---|
|
||||
| `void *p_alloc(UINT size)` | Allocate ≥`size` bytes; returns address or **NULL** on failure. Always test for NULL. |
|
||||
| `void *f_alloc(UINT size)` | As `p_alloc` but calls `p_leave(E_GEN_NOMEMORY)` instead of returning NULL. |
|
||||
| `void p_free(void *pcell)` | Free a cell. No-op if `pcell` is 0 (handy in cleanup). Double-free corrupts the heap. |
|
||||
| `void *p_realloc(void *pcell, UINT size)` | Resize; returns new address or NULL (original untouched). `pcell==0` ⇒ acts like `p_alloc`. |
|
||||
| `void *f_realloc(...)` | As `p_realloc` but leaves on failure. |
|
||||
| `void *p_adjust(void *pcell, UINT offset, INT amount)` | Open (`amount`>0) or close (`amount`<0) a gap inside a cell — insert/delete records in place. |
|
||||
| `UINT p_alen(void *pcell)` | Cell length (may be a few bytes > requested). |
|
||||
| `void p_hgran(UINT nparas)` | Set heap growth granularity (paragraphs). |
|
||||
| `void p_allwalk(fn, fpar)` | Walk every cell (diagnostic); panics on inconsistency. |
|
||||
| `void p_allchk(INT num)` | Force a heap-integrity check; `p_panic(0xFF)` if corrupt. |
|
||||
| `UINT p_allspc(void **pheap)` | Potential free space + heap start address. |
|
||||
|
||||
**Design cautions.** *Alloc heaven* — if a multi-cell structure is built and a
|
||||
later allocation fails, free the already-allocated cells or they leak. *Internal
|
||||
fragmentation* — avoid churning tiny transient cells (use the stack), and
|
||||
"granularise" many-cell variable-length structures. Do not let an unbounded
|
||||
heap structure grow until allocation fails at the 0xFFE0 limit: leave enough heap
|
||||
for the user's expected actions (e.g. saving to file). Note EPOC does **not**
|
||||
distinguish "no system RAM" from "hit the 64 KB data-segment limit" — both surface
|
||||
as an allocation failure. [PLIB §7]
|
||||
|
||||
System-memory queries: `p_getram` (RAM in paragraphs, capped at 32768 = 512 KB
|
||||
due to banking), `p_totalK` (total KB ignoring banking; **EPOC ≥ 3.50 only**),
|
||||
`p_sgfree` (free segmented memory), `p_sgramdisk` (RAM-disk usage). [PLIB §7]
|
||||
|
||||
---
|
||||
|
||||
## 2. Processes
|
||||
|
||||
### 2.1 What a process is
|
||||
|
||||
A process is a running program, normally created by loading an image (`.img` /
|
||||
`.app`) file. EPOC is a **single-user, preemptive multitasking** OS. Max
|
||||
concurrent processes `E_MAX_PROCESSES` = **24**. A process consists of at least: a
|
||||
**process control block** (`E_proc`), a **data segment**, a (possibly shared)
|
||||
**primary code segment**, and an **I/O semaphore**. [PLIB §12]
|
||||
|
||||
**Process ID** — a positive 16-bit value: low 12 bits = PCB offset in OS data
|
||||
space (the "slot"); high 4 bits = a generation counter (0–7, incremented mod 8 on
|
||||
slot reuse) so a stale PID from a terminated process is rejected. A bad PID panics
|
||||
the caller with **panic 7**. [PLIB §12]
|
||||
|
||||
**Process names** — `<name>.$nn` where `<name>` is 1–8 chars and `nn` is the
|
||||
2-digit slot index. For a **task** (a subsidiary process sharing its owner's data
|
||||
segment — a lightweight thread) the `$` becomes `a`, e.g. `SYS$WSRV.a05`. Max name
|
||||
`E_MAX_NAME` = 12. Loading the same image twice yields distinct names (e.g.
|
||||
`SORT.$07`, `SORT.$11`); `p_pidfind("SORT.*")` / `p_pfind` resolve names→PIDs with
|
||||
wildcards. [PLIB §12]
|
||||
|
||||
The standard system processes created at startup:
|
||||
|
||||
| Name | Role | Priority |
|
||||
|---|---|---|
|
||||
| `SYS$NULL` (`.$01`) | Null process; runs when nothing else is ready; powers the machine off after inactivity | 0 |
|
||||
| `SYS$MANG` (`.$02`) | Supervisor: memory moving, resource cleanup, critical functions | 248 |
|
||||
| `SYS$FSRV` (`.$03`) | File server; also loads images to create processes | 240 |
|
||||
| `SYS$WSRV` (`.$04`) | Window server: screen/keyboard/digitiser | — |
|
||||
| `SYS$SHLL` (`.$05`) | Shell / launcher | — |
|
||||
|
||||
Name/priority/suspend operations **fail** on `SYS$NULL`, `SYS$MANG`, `SYS$FSRV`.
|
||||
[PLIB §12]
|
||||
|
||||
### 2.2 Creating a process
|
||||
|
||||
```c
|
||||
HANDLE p_execc(TEXT *pName, VOID *pCommand, INT length); /* usual route */
|
||||
```
|
||||
|
||||
Loads the image `pName` (parsed with default extension `.IMG`) and returns a
|
||||
**positive PID** — but the new process is created **SUSPENDED**, so the creator
|
||||
can initialise it (e.g. set priority) before starting it with `p_presume`. A
|
||||
command cell is allocated in the *new* process's heap containing the full parsed
|
||||
path, a length byte, and `length` bytes copied from `pCommand`
|
||||
(`length` ≤ `E_MAX_COMMAND_BUFFER` = 127); its address lands in the reserved
|
||||
static `DatCommandPtr`. Loading is done by the file server, so **the caller must
|
||||
already be connected to the file server** (the standard PLIB startup does this
|
||||
before `main`; otherwise panic 41). [PLIB §12]
|
||||
|
||||
Error returns from `p_execc` (illustrative of the E_FILE_*/E_GEN_* families):
|
||||
`E_GEN_NOMEMORY`, `E_FILE_DEVICE`, `E_FILE_NOTREADY`, `E_FILE_DIR`,
|
||||
`E_FILE_NXIST`, `E_FILE_EXIST` (a different program of the same name — checksum
|
||||
mismatch — already runs), `E_GEN_IMAGE` (not a valid/corrupt image),
|
||||
`E_GEN_NOPROC` (no free slots), `E_GEN_ARG` (data+stack+heap > 0xFFE0, or command
|
||||
too long). [PLIB §12]
|
||||
|
||||
- `p_execcasync(pName, pCommand, length, pStatus, pPid)` — asynchronous load
|
||||
(completion via status word; see §5).
|
||||
- `p_pcreate(E_CPB *pBlock)` — the **primitive** creator that does *not* involve
|
||||
the file server; `p_execc` eventually calls it. The `E_CPB` block carries code/
|
||||
stack/data/heap paragraph counts, initial IP, checksum, `minHeap`, initial
|
||||
`priority`, RAM/ROM flag, and name. [PLIB §12]
|
||||
|
||||
**Shared code.** A second instance of the same program shares the existing
|
||||
`<name>.$SC` code segment (only the initialised statics are reloaded). The load
|
||||
verifies the code segment checksum against the image header — so you cannot run
|
||||
two different programs (or versions) of the same name simultaneously. [PLIB §12]
|
||||
|
||||
### 2.3 Priorities and scheduling
|
||||
|
||||
Priority is an **unsigned byte, 1–255**. At each reschedule the highest-priority
|
||||
**ready** process runs; if several share the top priority they run **round-robin**,
|
||||
each for a **4-tick** slice (the system ticks **32×/second**). A lower-priority
|
||||
process is otherwise blocked indefinitely. Preemption is real: a process need not
|
||||
call the OS to be switched out. [PLIB §12; SYS §10]
|
||||
|
||||
A process leaves the running state by moving to: `SEMAPHORE` (via `p_iowait` /
|
||||
`p_wait`), `DELTA` (via `p_sleep`/`p_sleept`/`p_sleepa`), or `SUSPENDED` (via
|
||||
`p_psuspend` on itself). Process states: `E_PROC_READY`, `E_PROC_SEMAPHORE`,
|
||||
`E_PROC_DELTA`, `E_PROC_SUSPENDED` (plus `E_PROC_FREE` for a vacated slot). The
|
||||
READY queue is priority-ordered; SEMAPHORE queues are FIFO; the DELTA queue stores
|
||||
inter-timer tick deltas. [PLIB §12]
|
||||
|
||||
Priority guidance and constants:
|
||||
|
||||
- Applications should stay within **`E_MIN_PRIORITY` (64)** … **`E_MAX_PRIORITY`
|
||||
(192)** inclusive; values outside are OS-reserved. [PLIB §12]
|
||||
- Window-server clients are typically created at **`E_PRIORITY_FORE` (128 =
|
||||
0x80)** and then let the window server raise/lower them as they gain/lose focus.
|
||||
A default image priority of **0x80** is the norm. [PLIB §12; GEN §3]
|
||||
- Supervisor 248, file server 240; interrupts run at the priority of the process
|
||||
they interrupt. [PLIB §12; SYS §10]
|
||||
|
||||
> **Uncertainty / cross-source discrepancy.** [PLIB] states `E_MIN_PRIORITY` = 64
|
||||
> and `E_MAX_PRIORITY` = 192. [SYS §10] speaks only of reserved bands
|
||||
> `cPBMinPriority` / `cPBMaxPriority` without giving numbers, and the [SYS]
|
||||
> service-number appendix lists a symbol `E_MAX_PRIORITY 154`. Treat the PLIB
|
||||
> pair (64/192) as the documented application range; the 154 appendix value is
|
||||
> unexplained in prose and should not be relied on.
|
||||
|
||||
Priority API: `INT p_getpri(HANDLE pid)`, `INT p_setpri(HANDLE pid, INT nPriority)`
|
||||
(reschedules; `E_GEN_RANGE` out of range, `E_FILE_NXIST` no such process,
|
||||
`E_GEN_FAIL` on null/supervisor/file-server), `p_presume`, `p_psuspend`,
|
||||
`p_marka`/`p_unmarka` (mark this process (in)active — servers mark themselves
|
||||
non-active), `p_getpid`, `p_pname`/`p_prename`, `p_getowner`. Cross-process data
|
||||
access: `p_pcpyfr`, `p_pcpyto`, `p_piscpyfr`. [PLIB §12]
|
||||
|
||||
### 2.4 Termination
|
||||
|
||||
Self-termination:
|
||||
|
||||
- `void p_exit(INT nReason)` — normal, graceful. `nReason` in −127..128; **0** =
|
||||
clean exit; negative = an init-failure error code; positive = an exit status to
|
||||
a parent. Falling off the end of `main` calls `p_exit` with `main`'s return
|
||||
value (falling off a `void main` is poor practice — it exits with garbage).
|
||||
- `void p_panic(INT nPanic)` — abnormal (see §3). Application panic numbers should
|
||||
count **down from 254** to avoid the system ranges.
|
||||
|
||||
Terminating another process:
|
||||
|
||||
- `INT p_pterminate(HANDLE pid, INT nReason)` — **preferred**. If the target
|
||||
called `p_onterminate(nMessage)`, it is merely *sent* that message and gets to
|
||||
run cleanup and exit itself; otherwise it behaves like `p_pkill`.
|
||||
- `INT p_pkill(HANDLE pid, INT nReason)` — summary kill, no cleanup chance.
|
||||
- `INT p_ppanic(HANDLE pid, INT nPanic)` — panic another process (e.g. a server
|
||||
panicking a client that sent garbage).
|
||||
|
||||
All three return `E_FILE_NXIST` (no such process) or `E_GEN_FAIL` (target is the
|
||||
null/supervisor/file-server process). [PLIB §6]
|
||||
|
||||
**Termination notification.** `p_logona(pid,&status)` (async, signals the
|
||||
caller's I/O semaphore), `p_logon(pid,mType)` (delivers an IPC message — for
|
||||
servers tracking clients), `p_watchall` (message on *any* process termination —
|
||||
used by the shell). Each delivers a 16-bit **process termination word** whose high
|
||||
byte is one of: `E_NORMAL_EXIT` (low byte = the `nReason`), `E_PANIC_EXIT` (low
|
||||
byte = the panic number), or `E_TASK_PANIC_EXIT` (a task it owned panicked).
|
||||
`p_logoffa`/`p_logoff` cancel; after `p_logoffa` the status word gets
|
||||
`E_FILE_CANCEL` and you must still `p_waitstat`. [PLIB §6]
|
||||
|
||||
### 2.5 Inter-process communication (IPC)
|
||||
|
||||
Choices, roughly in order of coupling:
|
||||
|
||||
1. **Inter-process messaging** — the primary client/server mechanism (below).
|
||||
2. **Shared memory segments** — a named external segment opened by several
|
||||
processes, guarded by a semaphore.
|
||||
3. **Cross-process copy** — `p_pcpyfr` / `p_pcpyto` (a server pulling/pushing a
|
||||
client's data by reference passed in a message).
|
||||
4. **Termination logon** and the **notifier** service as lightweight signalling.
|
||||
|
||||
**Messaging model** [PLIB §12; SYS §5]. Designed for client/server, one server /
|
||||
many clients. A receiver first calls `p_minit(nMess, lMess)` to allocate `nMess`
|
||||
**message slots** from its heap, each an `E_MESSAGE` header
|
||||
(`next`, `status`, `type`, `pid`) + `lMess` data bytes. The message length is
|
||||
fixed by the **server**; the file server and supervisor both use `lMess` = 8. When
|
||||
data exceeds the slot, the message carries an **address+length by reference** and
|
||||
the server uses `p_pcpyfr`/`p_pcpyto`.
|
||||
|
||||
Lifecycle: sender `p_msend*` copies the message into a free slot and enqueues it →
|
||||
server `p_mreceivew` (or async `p_mreceive`) dequeues from the **front** and gets
|
||||
the slot address → server processes it → `p_mfree` frees the slot **and**, if the
|
||||
client used a "receive" variant, writes back a completion status and signals the
|
||||
client's I/O semaphore.
|
||||
|
||||
- Client: `p_msend` (blind), `p_msendreceivew` (send + wait for reply),
|
||||
`p_msendreceivea` (send + set up an async reply). `p_mcancel` cancels a pending
|
||||
`p_mreceive`.
|
||||
- If the server has no free slot, the send **blocks on a mutual-exclusion
|
||||
semaphore** — even the "async" variant can block indefinitely. Multi-client
|
||||
servers therefore pre-allocate a slot per potential client (≈ `E_MAX_PROCESSES`
|
||||
minus known processes).
|
||||
- **Priority queue-jumping:** a message from a client of priority **≥ 0x80** is
|
||||
inserted at the **front** of the server queue (overtaking others), so a
|
||||
foreground task's requests are served first. [PLIB §12; SYS §5]
|
||||
- For relative priorities to work, a **multi-client server should run at a higher
|
||||
priority than any client**; a client waiting on a low-priority server is
|
||||
effectively lowered to the server's priority. [PLIB §12]
|
||||
|
||||
The message and I/O systems share status-word + I/O-semaphore mechanics, so async
|
||||
messaging and async I/O can be mixed freely. [SYS §5]
|
||||
|
||||
---
|
||||
|
||||
## 3. Panics
|
||||
|
||||
A **panic** is a fatal exception. When EPOC detects a condition that "could only
|
||||
arise from a bug", it **terminates the process immediately** with a panic number
|
||||
in **0–255**. Applications cannot trap or survive a panic (contrast `p_leave`,
|
||||
which is recoverable). Panicking is cheaper than an error return and enforces
|
||||
discipline by killing the defect at the point of detection. The OS still uses
|
||||
**error returns** where a condition can arise from legitimate user action — e.g.
|
||||
`p_alloc` returns NULL on genuine memory exhaustion but **panics** if it finds the
|
||||
heap *corrupted*. [GEN §2; PLIB §6]
|
||||
|
||||
Panics are raised by `p_panic` (self) or `p_ppanic` (another process — used by a
|
||||
server against a misbehaving client). Applications may call `p_panic` themselves
|
||||
to catch "impossible" cases (e.g. a `switch` default). [PLIB §6]
|
||||
|
||||
### 3.1 Panic-number ranges — how to interpret one
|
||||
|
||||
| Range | Origin | Where documented |
|
||||
|---|---|---|
|
||||
| **0–80, and 255** | PLIB library | PLIB Reference (§6) |
|
||||
| **81–129** | Window Server library | Window Server Reference |
|
||||
| **130–160** | OLIB object library | OLIB Reference |
|
||||
| **160–254** | Non-ROM code (e.g. ISAM and other loadable libraries) | the relevant library's manual |
|
||||
|
||||
[GEN §2; PLIB §6]
|
||||
|
||||
Notes on reading a panic:
|
||||
|
||||
- A given number in the **160–254** band may be used by **more than one**
|
||||
non-ROM component, so it is ambiguous — the only definitive way to locate the
|
||||
source is to catch it under the **SIBO Debugger** and trace back. [GEN §2]
|
||||
- The manual overview says PLIB owns 0–80 & 255; the detailed PLIB list also
|
||||
documents specific panics at **60, 65–69, 77, 78** etc., and warns that a panic
|
||||
**79** or anything **81–254** may belong to another component. Window-server
|
||||
panics are cited as 81–110 in one place and OLIB as 130–158 in another (both
|
||||
inside the summary bands above). [PLIB §6]
|
||||
- Panics described as *"Invalid function number for …"* rarely indicate a
|
||||
specific coding bug; they usually mean a **trashed return address** sent the IP
|
||||
wandering into arbitrary code. [PLIB §6]
|
||||
|
||||
### 3.2 Selected PLIB system panic numbers (0–80, 255)
|
||||
|
||||
| # | Meaning |
|
||||
|---|---|
|
||||
| 0 | Test-code failure |
|
||||
| 1–5 | Semaphore manager: bad function no. / handle / not allocated / negative initial count / negative signal count |
|
||||
| 6–8 | Process manager: bad function no. / invalid PID / task tried to create a task |
|
||||
| 9 | Time manager: bad function number |
|
||||
| 10–14 | Segment manager: bad fn / negative size / bad type / bad handle / copy out of range |
|
||||
| 15–19 | Heap manager: bad fn / heap not initialised / cell reduced by more than its size / granularity > `E_MAX_GROWBY` / **cell address outside heap (heap corrupted — try `p_allchk`)** |
|
||||
| 20–23 | IPC message manager (bad fn; already init'd (double `p_minit`); not init'd; zero-length queue) |
|
||||
| 24–26 | I/O manager: bad fn / **invalid I/O channel** (unchecked `p_open`, closed/overwritten handle) / device requested panic |
|
||||
| 27 | Invalid wait-handler handle (often really a low-address overwrite at addr 2 via an uninitialised pointer) |
|
||||
| 28–29 | Key/pointer already hooked / requester not a task |
|
||||
| 30–31 | Device manager: bad fn / bad device handle |
|
||||
| 32–34 | File manager: bad fn / already connected to file server / reserved |
|
||||
| 35–41 | Library manager & file-server connection (bad handle/fn, invalid LIB channel, bad DYL index, invalid message to file server, **not connected to file server** = 41) |
|
||||
| 42–47 | Conversion / general manager fns; unhook-notify-when-not-hooked; invalid revector address |
|
||||
| 48 | `p_leave` called before `p_enter` |
|
||||
| 49–56 | OOP errors (no method, invalid reclass, unknown category/class, supersend outside method, handle before link, missing external categories, object not a valid class) |
|
||||
| 57 | Invalid link-layer completion code |
|
||||
| 58–59 | Invalid fn for window server / hardware manager |
|
||||
| 60 | **Write outside process data segment** (uninitialised pointer / corrupt structure) |
|
||||
| 61 | Interrupts disabled too long |
|
||||
| 63–64 | Divide-by-zero / overflow interrupt |
|
||||
| 65–67 | Dbf manager: bad fn / bad DBF I/O channel / bad DBF parameter |
|
||||
| 68 | **Address-zero overwrite** (the `0xDEAD` word changed — uninitialised pointer) |
|
||||
| 69 | Less than 0x100 bytes of stack remain (large automatics — make them static or heap) |
|
||||
| 70 | Environment name > `EnvMaxNameSize` |
|
||||
| 71 / 72 | Single-step (INT 1) / breakpoint (INT 3) |
|
||||
| 73 | **A request was made while an async request of the same type on the same channel was still pending** |
|
||||
| 74–76 | Serial-I/O bad fn / ASIC1 call on an ASIC9 machine / DYL not in a visible bank |
|
||||
| 77 | Floating-point emulator exception |
|
||||
| 78 | Semaphore count exceeds 0x7FFF |
|
||||
| 80 | Library fatal error (preceded by a specific-error notification) |
|
||||
| 255 | `p_allchk` detected a corrupted heap |
|
||||
|
||||
[PLIB §6 "System panic numbers"]
|
||||
|
||||
---
|
||||
|
||||
## 4. Error codes
|
||||
|
||||
System functions signal failure by convention:
|
||||
|
||||
- Functions returning an **address** return **NULL (0)** on failure.
|
||||
- Otherwise: **zero or positive = success, −1 = failure** (`E_GEN_FAIL` = −1; you
|
||||
may just test the sign).
|
||||
- When the error is elaborated, a **negative system error number in −1..−128** is
|
||||
returned, allocated in bands: [PLIB §6 "Error returns"]
|
||||
|
||||
| Band | Family | Header |
|
||||
|---|---|---|
|
||||
| −1 … −31 | General errors `E_GEN_xxx` | `p_gen.h` |
|
||||
| −32 … −63 | I/O / device / file errors `E_FILE_xxx` | `p_file.h` |
|
||||
| −64 … −95 | Reserved | — |
|
||||
| −96 … −128 | OPL run-time errors | — |
|
||||
|
||||
`p_errs(TEXT *str, INT errno)` converts a system error number to a
|
||||
language-dependent string (buffer ≥ `E_MAX_ERROR_TEXT_SIZE` = 64; unknown numbers
|
||||
give `"Unknown error [xx]"`). The notifier services `p_notify` / `p_notifyerr`
|
||||
present errors to the user; `p_setnotify(FALSE)` suppresses automatic notification
|
||||
for unattended/server processes. [PLIB §6]
|
||||
|
||||
### 4.1 `E_GEN_*` (general) family
|
||||
|
||||
The named generics referenced across the manuals (all defined in `p_gen.h`,
|
||||
values in −1..−31):
|
||||
|
||||
| Symbol | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `E_GEN_NONE` | 0 | No error / success |
|
||||
| `E_GEN_FAIL` | **−1** | General failure (also the generic "just test the sign") |
|
||||
| `E_GEN_ARG` | *−n* | Bad argument (e.g. invalid `double`, buffer too small, out-of-range command length) |
|
||||
| `E_GEN_OVER` | *−n* | Overflow — value too large for the target type/representation |
|
||||
| `E_GEN_UNDER` | *−n* | Underflow — magnitude too small to represent |
|
||||
| `E_GEN_RANGE` | *−n* | Value outside a permitted range (e.g. priority) |
|
||||
| `E_GEN_INUSE` | **−9** | Resource in use (e.g. segment usage count > 0) |
|
||||
| `E_GEN_NOMEMORY` | *−n* | Insufficient (system) memory; `f_alloc`/`f_realloc` `p_leave` with this |
|
||||
| `E_GEN_NOPROC` | *−n* | No free process slots |
|
||||
| `E_GEN_NOSEM` | *−n* | No semaphores available (`p_semcrt`) |
|
||||
| `E_GEN_IMAGE` | *−n* | File is not a valid image, or is corrupt |
|
||||
|
||||
> **Uncertainty.** Of the `E_GEN_*` family only two exact numeric values are
|
||||
> **fixed by the task's cross-check set and consistent with the manuals**:
|
||||
> `E_GEN_FAIL = −1` and `E_GEN_INUSE = −9`. The manuals reference the other
|
||||
> `E_GEN_*` symbols by name and behaviour but the two source files do **not**
|
||||
> tabulate their individual numeric values (they live in `p_gen.h`). Values shown
|
||||
> as *−n* above are known only to fall in the −1..−31 band; do not assume specific
|
||||
> numbers without the header. [PLIB §6, and usage throughout]
|
||||
|
||||
### 4.2 `E_FILE_*` (I/O / file / device) family
|
||||
|
||||
Defined in `p_file.h`, values in −32..−63. Anchor values confirmed against the
|
||||
task's cross-check set:
|
||||
|
||||
| Symbol | Value | Meaning |
|
||||
|---|---|---|
|
||||
| `E_FILE_EXIST` | **−32** | Already exists (file, or a differently-checksummed process/segment of the same name) |
|
||||
| `E_FILE_NAME` | **−38** | Invalid name (file / memory-segment name) |
|
||||
| `E_FILE_DEVICE` | **−41** | Device does not exist / device driver not found |
|
||||
| `E_FILE_PENDING` | *−n* | Async request still outstanding (see §5) — the status-word "in progress" marker |
|
||||
| `E_FILE_CANCEL` | *−n* | Request was cancelled (written to the status word by a cancel) |
|
||||
| `E_FILE_NXIST` | *−n* | Does not exist (file, directory entry, or **process**) |
|
||||
| `E_FILE_DIR` | *−n* | Directory does not exist |
|
||||
| `E_FILE_NOTREADY` | *−n* | Device present but no medium (e.g. no SSD in the pack) |
|
||||
|
||||
> **Uncertainty.** Three exact values are pinned by the cross-check and match the
|
||||
> manuals' usage: **`E_FILE_EXIST = −32`, `E_FILE_NAME = −38`,
|
||||
> `E_FILE_DEVICE = −41`.** The remaining `E_FILE_*` symbols appear by name in the
|
||||
> manuals (e.g. as `p_execc`, `p_setpri`, segment, and async return values) but
|
||||
> the two source files do **not** enumerate their numeric values — those are in
|
||||
> `p_file.h`. Serial-port variants such as `E_FILE_PARITY` are mentioned in prose
|
||||
> without numbers. [PLIB §6/§7/§12]
|
||||
|
||||
Observed usage tying symbols to operations: `p_execc` → `E_FILE_DEVICE`,
|
||||
`E_FILE_NOTREADY`, `E_FILE_DIR`, `E_FILE_NXIST`, `E_FILE_EXIST`; `p_setpri` /
|
||||
`p_presume` / termination calls → `E_FILE_NXIST`; segment create/open →
|
||||
`E_FILE_EXIST`, `E_FILE_NAME`; segment delete → `E_GEN_INUSE`. [PLIB §7, §12, §6]
|
||||
|
||||
---
|
||||
|
||||
## 5. Asynchronous I/O model
|
||||
|
||||
### 5.1 Two-step services, status words, the I/O semaphore
|
||||
|
||||
Most services exist in two halves: **make the request**, then **wait for
|
||||
completion**. A *synchronous* function does both; the internal *asynchronous*
|
||||
function issues the request and returns at once. Async request functions include
|
||||
`p_ioa`/`p_ioc` (on an open I/O channel), `p_mreceive` (IPC), `p_execcasync`
|
||||
(image load), and `p_logona` (termination). [PLIB §8]
|
||||
|
||||
Every process gets an **I/O semaphore** at creation ("asynchronous-request
|
||||
semaphore" would have been the accurate name). Each async request is tied to a
|
||||
caller-supplied **status word** (a signed 16-bit `WORD`). The invariant for
|
||||
*every* async request: [GEN §5; PLIB §8]
|
||||
|
||||
1. On issue, the status word is set to the negative **`E_FILE_PENDING`**.
|
||||
2. On completion, a value **other than** `E_FILE_PENDING` is written — **≥ 0** for
|
||||
success, a **negative error number** for failure.
|
||||
3. The write to the status word happens **before** the caller's I/O semaphore is
|
||||
signalled. Both steps are integral to the mechanism.
|
||||
|
||||
**`p_iowait`** decrements the I/O semaphore and blocks until it is non-negative,
|
||||
i.e. until *something* has been signalled. It does **not** tell you *which*
|
||||
request completed — after it returns you **poll the status words** for the first
|
||||
that is no longer `E_FILE_PENDING`. The semaphore starts at 0; because `p_iowait`
|
||||
decrements first, it returns only when a signal has arrived. [GEN §5; PLIB §8]
|
||||
|
||||
```c
|
||||
p_ioc(SerialChannel, P_FWRITE, &SerialStatus, str, &len); /* async write */
|
||||
p_ioc(TimerChannel, P_FRELATIVE, &TimerStatus, &timeout); /* async 5s timer */
|
||||
p_iowait(); /* block on one */
|
||||
if (SerialStatus == E_FILE_PENDING) { /* timed out: cancel the write */
|
||||
p_iow(SerialChannel, P_FCANCEL);
|
||||
p_waitstat(&SerialStatus);
|
||||
p_leave(SERIAL_TIMEOUT);
|
||||
}
|
||||
p_iow(TimerChannel, P_FCANCEL);
|
||||
p_waitstat(&TimerStatus);
|
||||
```
|
||||
[PLIB §8, adapted]
|
||||
|
||||
**Prioritisation** is implicit in **poll order**: if two requests have completed,
|
||||
whichever status word you poll first is serviced first. A low-priority source can
|
||||
be "locked out" by a rapidly firing higher one. [GEN §5]
|
||||
|
||||
**Where to declare status words** — in static data or in a heap control block,
|
||||
**never** on a stack frame that will return before the request completes. A stale
|
||||
status word is later written to random memory. [GEN §5]
|
||||
|
||||
### 5.2 The `p_io?` calls and channels
|
||||
|
||||
I/O devices share a uniform interface: `p_open` (by name, returns a channel
|
||||
handle) → request services → `p_close`. Synchronous requests use **`p_iow`**
|
||||
(convenience wrappers `p_iowN`); asynchronous use **`p_ioa`** / **`p_ioc`**
|
||||
(wrappers `p_ioaN`, `p_iocN`). The async form always takes **one extra parameter**
|
||||
— the status-word address. `p_ioa` vs `p_ioc` are the async requestors used in the
|
||||
examples ("a" = asynchronous). [GEN §5; PLIB §8/§9]
|
||||
|
||||
### 5.3 The semaphore & I/O-semaphore primitives
|
||||
|
||||
EPOC semaphores are **counting** semaphores (signed value): `p_signal`
|
||||
increments, `p_wait` decrements; a negative value means processes are queued
|
||||
(released FIFO). [PLIB §8]
|
||||
|
||||
| Function | Effect |
|
||||
|---|---|
|
||||
| `HANDLE p_semcrt(INT nCount)` | Create a semaphore, initial count `nCount` (≥0); returns handle or `E_GEN_NOSEM`. Auto-deleted on process exit. |
|
||||
| `void p_semdel(HANDLE s)` | Delete (waiters are released). Rarely needed pre-v3. |
|
||||
| `void p_wait(HANDLE s)` | Decrement; block if it goes negative. |
|
||||
| `void p_signal(HANDLE s)` | Increment; release the first waiter and reschedule. |
|
||||
| `void p_signaln(HANDLE s, INT n)` | `p_signal` × n. |
|
||||
| `void p_signalnr(HANDLE s)` | Signal **without** rescheduling (reschedule waits for the next tick, or force one with `p_sleept(0)`). |
|
||||
| `void p_iosignal(void)` | Increment **this** process's I/O semaphore (used by wait handlers, or to raise an "internal event"). |
|
||||
| `void p_iosignalbypid(HANDLE pid)` | Signal another process's I/O semaphore (after setting its status word via `p_pcpyto`). |
|
||||
| `void p_iowait(void)` | Wait on the I/O semaphore; runs active wait handlers. |
|
||||
| `void p_waitstat(WORD *pstat)` | Wait until `*pstat != E_FILE_PENDING`, correctly re-signalling any other completions seen meanwhile. |
|
||||
| `void p_ioyield(void)` | `p_iosignal` + `p_iowait` — give installed wait handlers a chance to run. |
|
||||
|
||||
[PLIB §8]
|
||||
|
||||
**Discipline.** Every `p_iosignal` must be matched by a `p_iowait`, and its status
|
||||
word must already be complete when signalled. An unmatched signal is a **"stray
|
||||
signal"** at Psion — the next `p_iowait` returns spuriously with nothing ready.
|
||||
Guard against it (e.g. `p_panic` if the post-`p_iowait` poll finds no completed
|
||||
word); the **Spy** app helps spot accumulated stray signals. Making a second
|
||||
request while one of the same type is pending on the same channel → **panic 73**.
|
||||
[PLIB §8; GEN §5]
|
||||
|
||||
**Waiting for one specific request:** use **`p_waitstat`**, not `p_iowait` — it
|
||||
returns only when *that* status word completes and re-issues signals for other
|
||||
completions it absorbs. General-purpose synchronous wrappers **must** use
|
||||
`p_waitstat` (they cannot assume no other requests are pending). [PLIB §8]
|
||||
|
||||
### 5.4 Cancelling a request
|
||||
|
||||
General principles for all cancels (`p_iow(P_FCANCEL)` for channels, `p_mcancel`
|
||||
for IPC, `p_logoffa` for termination): [GEN §5; PLIB §8]
|
||||
|
||||
- A cancel **precipitates completion**; it does **not** un-issue the request.
|
||||
- It **may or may not** be effective — the operation may finish naturally first.
|
||||
- **You must still consume the completion**, normally with an immediate
|
||||
`p_waitstat`. If the request was still outstanding, `E_FILE_CANCEL` is written
|
||||
to the status word and the process is signalled.
|
||||
|
||||
Canonical timer cancel:
|
||||
|
||||
```c
|
||||
p_iow2(timH, P_FCANCEL); /* synchronous; completes at once */
|
||||
p_waitstat(&timstat); /* use up the resulting signal */
|
||||
```
|
||||
|
||||
Forgetting the `p_waitstat` after `P_FCANCEL` is a **common bug**: the next
|
||||
`p_iowait` then returns immediately although nothing is actually ready. [GEN §5]
|
||||
|
||||
### 5.5 Wait handlers and interrupt-driven completion
|
||||
|
||||
**Wait handlers** run *inside* `p_iowait`/`p_waitstat`, just before it would
|
||||
return. Install with `p_svecadd`, remove with `p_svecrem`, (de)activate with
|
||||
`p_sveccall`. Each returns `P_SIGNAL_DISABLE` (completion handled, deactivate),
|
||||
`P_SIGNAL_ENABLE` (handled, keep active — more pending), or `P_SIGNAL_UNUSED`
|
||||
(nothing of mine — the signal is external). `p_iowait` returns to the caller only
|
||||
when all active handlers report `P_SIGNAL_UNUSED`. The handler queue hangs off a
|
||||
4-byte header in the reserved static at **address 2**; corrupting it usually
|
||||
yields **panic 27**. [PLIB §8]
|
||||
|
||||
This matters because a **hardware interrupt handler cannot write to the requesting
|
||||
process's data segment** (it may be mid-move). Instead the driver writes to a
|
||||
fixed location and signals the I/O semaphore; the driver's **wait handler** later
|
||||
copies the data into the process data segment when the process next enters
|
||||
`p_iowait`. Therefore, if you **poll** a status word (e.g. between chunks of a long
|
||||
computation) instead of blocking, call **`p_ioyield` before each poll** so those
|
||||
handlers can run — and still consume the signal with `p_iowait` once the poll sees
|
||||
completion, or you get a stray signal. Tight-loop polling without yielding is
|
||||
"anti-social" (it hogs the CPU). Server-implemented drivers (e.g. the window
|
||||
server) need no wait handler. [PLIB §8]
|
||||
|
||||
---
|
||||
|
||||
## 6. The SIBO OS executive call (assembly-level interface)
|
||||
|
||||
At the C level, `p_iow`/`p_ioa`/`p_ioc` are thin shells over EPOC's ROM services.
|
||||
Those services are reached by an **80C86 software interrupt** (`INT XXH`). Two
|
||||
flavours: **single-service** (`INT XXH` — one dedicated interrupt per common
|
||||
service, minimal overhead) and **multi-service** (`MOV AH, ZZH` then `INT XXH`,
|
||||
where `ZZ` selects the function). [SYS §"System services"]
|
||||
|
||||
**General calling convention** [SYS §"Calling conventions"]:
|
||||
|
||||
- All registers except **AX** are preserved unless they carry a return value; AX
|
||||
is a scratch/return register.
|
||||
- **Error** ⇒ **carry flag set**, error number in **AL** (always negative); other
|
||||
result registers are then indeterminate.
|
||||
- Handles are 16-bit, guaranteed positive, non-zero and **even**; passed in **BX**
|
||||
where possible; results returned in **AX**.
|
||||
- A programmatically invalid argument ⇒ the process is **terminated immediately**
|
||||
(a panic), not an error return.
|
||||
|
||||
**The I/O executive specifically.** The manual documents the underlying I/O
|
||||
services at register level:
|
||||
|
||||
- **`IoAsynchronous`** — `AL` = I/O function number, `BX` = I/O handle,
|
||||
`DS:CX` → arg1, `DS:DX` → arg2, `DS:DI` → status word; returns `AX` = device
|
||||
driver result (carry set ⇒ error). This is exactly the layer beneath `p_ioa`/
|
||||
`p_ioc`: on success the driver later signals completion via
|
||||
`IoSignal`/`IoSignalByPid`, and you wait with `IoWaitForSignal` /
|
||||
`IoWaitForStatus` (the `p_iowait`/`p_waitstat` primitives). Panics `PanicIo1`
|
||||
(bad channel), `PanicLib1`/`PanicLib2` (bad library handle/function).
|
||||
- **`IoWithWait`** — same inputs minus the status word (`AL`, `BX`, `DS:CX`,
|
||||
`DS:DX`), returns `AX` = result; the **synchronous** service beneath `p_iow`.
|
||||
- `IoAsynchronousNoError` — as `IoAsynchronous` but start-up errors are reported
|
||||
through the status word + signal rather than the carry flag. [SYS §8 "Input
|
||||
Output Management"]
|
||||
|
||||
**[RE — reverse-engineered]** Prior reverse-engineering of a SIBO ROM identifies
|
||||
the I/O executive concretely as **`INT 0xCF`**, with **`CL` = function code,
|
||||
`BX` = channel handle, `DX` = argument, result in `AX`** — i.e. the actual
|
||||
interrupt vector and register wiring under `p_iow`. This is *consistent in shape*
|
||||
with the documented `IoWithWait`/`IoAsynchronous` services above (handle in BX,
|
||||
result in AX, args passed by register), but note two things the manuals do **not**
|
||||
corroborate and that should be treated as RE findings only:
|
||||
|
||||
- The manuals **never name the interrupt number** for any service (`INT XXH` is
|
||||
left abstract throughout `[SYS]`); **`0xCF` comes from RE, not the SDK docs.**
|
||||
- The documented multi-service convention puts the **function code in `AH`**, and
|
||||
the I/O services put it in **`AL`** with args in `CX`/`DX` and the status word in
|
||||
`DI`. The RE note's **`CL` = function / `DX` = argument** differs from the
|
||||
published register layout. The discrepancy may reflect a particular wrapper,
|
||||
ROM version, or single-service entry point observed during RE; **do not treat
|
||||
the `INT 0xCF` / `CL`/`BX`/`DX`/`AX` mapping as documented** — it is an
|
||||
RE observation to be verified against the specific ROM.
|
||||
|
||||
---
|
||||
|
||||
## 7. Workabout / Workabout MX notes
|
||||
|
||||
The manuals cover the SIBO family collectively (HC, MC, Series 3/3a, Workabout);
|
||||
the programming model above — small model, segment moving, panics, error
|
||||
families, async I/O, the executive interrupt — applies to the Workabout as it does
|
||||
to the rest of the range. **[MX]** The Workabout MX is a later, faster
|
||||
(higher-clock, more-RAM) member of the same SIBO/EPOC-16 line; **the SDK source
|
||||
manuals used here (v2.30, 1999) do not carry MX-specific programming differences**,
|
||||
so any MX deltas (RAM sizing, ROM version, clock/timer specifics) are **not
|
||||
established from these sources** and are left as an explicit uncertainty rather
|
||||
than guessed. Version-gated APIs seen above (`p_totalK` needs EPOC ≥ 3.50;
|
||||
`p_semdel` recommended only from v3) are the kind of thing to check against the
|
||||
actual MX ROM version via `p_getver` / the version services. [GEN §7; PLIB §7]
|
||||
|
||||
---
|
||||
|
||||
### Source map
|
||||
|
||||
- Memory model & allocation: **[PLIB §1, §7]**, **[GEN §1]**
|
||||
- Processes, priorities, IPC, termination: **[PLIB §6, §12]**, **[SYS §5, §10]**
|
||||
- Panics & panic ranges: **[GEN §2]**, **[PLIB §6]**
|
||||
- Error families: **[PLIB §6]** (+ usage in §7/§12)
|
||||
- Async I/O, semaphores, wait handlers, cancel: **[GEN §5]**, **[PLIB §8, §9]**
|
||||
- Executive interrupt / register conventions: **[SYS]** "System services",
|
||||
"Calling conventions", §8; **`INT 0xCF` mapping = [RE], not in the manuals.**
|
||||
@@ -0,0 +1,672 @@
|
||||
# Psion SIBO / Workabout MX — I/O Device Model and Drivers
|
||||
|
||||
Reference for the EPOC (SIBO) I/O system as documented in the Psion SIBO 'C' SDK.
|
||||
Everything here is drawn from two manuals:
|
||||
|
||||
- **PLIB Reference** — I/O System chapter (Ch. 9), Asynchronous Requests and
|
||||
Semaphores (Ch. 8), Memory Allocation (Ch. 7). Cited as *PLIB*.
|
||||
- **I/O Devices Reference** v2.30 (March 1999). Cited as *IODEV*.
|
||||
|
||||
Where a fact is specific to the Workabout / Workabout MX, or is uncertain from the
|
||||
manuals, it is flagged. The MX integral laser scanner is **not** covered by these
|
||||
manuals; see the cross-reference in the Barcode section.
|
||||
|
||||
> Notation used by the manuals: an I/O service is identified by a function code of
|
||||
> the form `P_Fxxx` (defined in `p_file.h`). The manuals write `p_iow(P_FWRITE)`,
|
||||
> `p_ioc(P_FWRITE)` or just `P_FWRITE` to refer to that service. (*IODEV* Ch.1;
|
||||
> *PLIB* Ch.9.)
|
||||
|
||||
---
|
||||
|
||||
## 1. The device model
|
||||
|
||||
### 1.1 Device drivers: LDDs and PDDs
|
||||
|
||||
The purpose of a device driver is to hide the underlying hardware behind a stable
|
||||
software interface. EPOC defines two driver types (*PLIB* Ch.9, "LDDs and PDDs"):
|
||||
|
||||
- **PDD — physical device driver**: hardware dependent (the lower layer).
|
||||
- **LDD — logical device driver**: hardware independent (the upper layer).
|
||||
|
||||
Applications normally interface to **LDDs only**. An LDD may use one or more PDDs.
|
||||
The serial driver is the canonical example: an upper hardware-independent LDD over
|
||||
a hardware-dependent PDD; incoming data is buffered at the LDD level. Some drivers
|
||||
touch no hardware at all — e.g. the C floating-point library is implemented as a
|
||||
device driver (the 8087 emulator LDD `sys$8087.ldd`). (*PLIB* Ch.9; Ch.5.)
|
||||
|
||||
Some drivers are built into the ROM (e.g. the RS232 LDD/PDD); others are external
|
||||
and must be loaded (e.g. a bar-code reader). (*PLIB* Ch.9.)
|
||||
|
||||
The interface between the OS and an LDD does **not** follow a C calling convention;
|
||||
an LDD may be written in C but requires some 8086 assembly for the LDD vector
|
||||
interface. (*PLIB* Ch.9; System Services reference.)
|
||||
|
||||
### 1.2 Opening a channel: `p_open`
|
||||
|
||||
```c
|
||||
INT p_open(VOID **ppfcb, TEXT *name, UINT mode);
|
||||
INT f_open(VOID **ppfcb, TEXT *name, UINT mode); /* p_leave()s on error */
|
||||
```
|
||||
|
||||
`name` is a **3-character device name terminated by a `':'`**, optionally followed
|
||||
by further text depending on the device. Examples (*PLIB* Ch.9):
|
||||
|
||||
| Name | Device |
|
||||
| ----- | ------ |
|
||||
| `FIL:` | a file |
|
||||
| `PAR:` | a parallel port |
|
||||
| `TTY:` | an RS232 serial port |
|
||||
| `TIM:` | an asynchronous timer |
|
||||
|
||||
Rules (*PLIB* Ch.9, "Opening a channel to a device"):
|
||||
|
||||
- The device name of an external LDD bears **no relation** to the file it was
|
||||
loaded from.
|
||||
- Where a driver supports more than one **unit**, the `':'` is followed by a unit
|
||||
letter: `TTY:A` = serial port A, `PAR:B` = parallel port B.
|
||||
- For `FIL:`, the qualifying text is a file name or full path.
|
||||
- A loaded device driver **supersedes** any existing device of the same name.
|
||||
|
||||
On success the channel control block address is written to `*ppfcb`. On failure
|
||||
`*ppfcb` is left untouched — pre-set it to zero so `p_close(0)` (a no-op) is safe
|
||||
in clean-up. For an **attached** driver, the control block is attached to `*ppfcb`
|
||||
and the value is not changed. (*PLIB* Ch.9.)
|
||||
|
||||
Errors: `E_FILE_ALLOC` (no memory for control block), `E_FILE_DEVICE` (device does
|
||||
not exist), `E_GEN_ARG` (bad `mode`, possibly from falling through to `FIL:`).
|
||||
|
||||
### 1.3 The `FIL:` fallback
|
||||
|
||||
If `p_open` fails to match a device name, the name is **passed to the `FIL:`
|
||||
driver** (the process must be connected to the file server). This makes the leading
|
||||
`FIL:` optional when opening files:
|
||||
|
||||
```
|
||||
p_open(..., "C:\\NOTES\\NEW.TXT", ...) == p_open(..., "FIL:C:\\NOTES\\NEW.TXT", ...)
|
||||
```
|
||||
|
||||
Side effect: opening a **non-existent device** does not give the expected
|
||||
`E_FILE_DEVICE`, because the name is handed to `FIL:` and the result depends on
|
||||
`mode` (it might even succeed). Passing `mode = -1` guarantees the `FIL:` open
|
||||
fails — albeit with the misleading `E_GEN_ARG`. Keeping an explicit `FIL:` prefix
|
||||
avoids mistaking a file spec for a device name (e.g. a file literally named
|
||||
`TTY:`). (*PLIB* Ch.9.)
|
||||
|
||||
### 1.4 Modes
|
||||
|
||||
The interpretation of `mode` **depends on the device**; many devices ignore it.
|
||||
When a device ignores `mode`, pass `-1`. (*PLIB* Ch.9.) The character/hardware
|
||||
devices in *IODEV* (`TTY:`, `PAR:`, `SND:`, `FRC:`, `MCR:`, `BAR:`) are all opened
|
||||
with `mode = -1`. File-open mode flags such as `P_FOPEN`, `P_FREPLACE`,
|
||||
`P_FUPDATE`, `P_FUNIQUE`, `P_FSTREAM`, `P_FSHARE`, `P_FTEXT` are `FIL:`-specific and
|
||||
are described in the Files chapter of *PLIB*.
|
||||
|
||||
### 1.5 Attached drivers
|
||||
|
||||
An **attached driver** is an LDD layered over another LDD, replacing or augmenting
|
||||
its services. Opening it *attaches* to an already-open underlying channel rather
|
||||
than allocating a new one. Example: the printer driver `PRO:` is opened over a
|
||||
print-output device (`PAR:`, `TTY:` or `FIL:`) and thereafter replaces that
|
||||
device's `P_FWRITE`, `P_FCANCEL` and `P_FCLOSE`. Attached drivers that work
|
||||
asynchronously install a **device wait handler** at open, called from within the
|
||||
opening process's `p_iowait`. (*PLIB* Ch.9, "Attached drivers".)
|
||||
|
||||
### 1.6 The file server
|
||||
|
||||
`FIL:` operations are serviced by the high-priority system process **`SYS$FSRV`**,
|
||||
which also loads executables and **loads external device drivers**. It serialises
|
||||
access to shared storage (SSDs) and uses PDDs to reach the many FLASH/RAM/ROM
|
||||
configurations. Applications are "clients" of the file server; the normal start-up
|
||||
code connects to it. (*PLIB* Ch.9, "The file server".)
|
||||
|
||||
---
|
||||
|
||||
## 2. Operations on an open channel
|
||||
|
||||
All I/O requests are **asynchronous in principle** — the process I/O semaphore is
|
||||
always signalled on completion — but many are implemented **synchronously**
|
||||
(completing before the call returns). A typical device offers zero to three truly
|
||||
asynchronous functions (commonly `P_FREAD` / `P_FWRITE`); the rest are synchronous.
|
||||
`P_FCLOSE` is always synchronous. (*PLIB* Ch.9; *IODEV* Ch.1.)
|
||||
|
||||
### 2.1 The I/O primitives
|
||||
|
||||
```c
|
||||
/* asynchronous, CDECL + register-calling variants */
|
||||
INT p_ioa (VOID *pcb, INT func, WORD *pstat, ...);
|
||||
INT p_ioa3(VOID *pcb, INT func, WORD *pstat);
|
||||
INT p_ioa4(VOID *pcb, INT func, WORD *pstat, VOID *a1);
|
||||
INT p_ioa5(VOID *pcb, INT func, WORD *pstat, VOID *a1, VOID *a2);
|
||||
|
||||
/* asynchronous, error folded into *pstat (preferred over p_ioa) */
|
||||
VOID p_ioc (VOID *pcb, INT func, WORD *pstat, ...);
|
||||
VOID p_ioc3(VOID *pcb, INT func, WORD *pstat);
|
||||
VOID p_ioc4(VOID *pcb, INT func, WORD *pstat, VOID *a1);
|
||||
VOID p_ioc5(VOID *pcb, INT func, WORD *pstat, VOID *a1, VOID *a2);
|
||||
|
||||
/* synchronous: start then wait (calls p_waitstat) */
|
||||
INT p_iow (VOID *pcb, INT func, ...);
|
||||
INT p_iow2(VOID *pcb, INT func);
|
||||
INT p_iow3(VOID *pcb, INT func, VOID *a1);
|
||||
INT p_iow4(VOID *pcb, INT func, VOID *a1, VOID *a2);
|
||||
```
|
||||
|
||||
- **`p_ioa`** starts the operation and returns immediately. On success, `*pstat`
|
||||
holds `E_FILE_PENDING` until the I/O semaphore is signalled, then zero or a
|
||||
negative error. A cancelled operation completes with `E_FILE_CANCEL`. Returns
|
||||
`E_FILE_INV` if `func` is invalid for the device. **The status word must outlive
|
||||
the operation** — never put it on a stack frame you return from. (*PLIB* Ch.9.)
|
||||
- **`p_ioc`** behaves like `p_ioa` except a failure to *start* is reported exactly
|
||||
as if the request had started and then completed with that error (only `*pstat`
|
||||
to check). Preferred over `p_ioa`. (*PLIB* Ch.9.)
|
||||
- **`p_iow`** starts the request and waits (`p_waitstat`) for completion, returning
|
||||
the status. Preferred unless you actually need asynchrony. (*PLIB* Ch.9.)
|
||||
|
||||
The register-calling variants (`p_iow2/3/4`, `p_ioc3/4/5`, `p_ioa3/4/5`) generate
|
||||
smaller code and are preferred where applicable; e.g. `p_ioc3` over `p_ioa3`.
|
||||
(*IODEV* Ch.1; *PLIB* Ch.9.)
|
||||
|
||||
Driver rule: normally **one pending request per I/O operation per channel**. A
|
||||
second `P_FWRITE` while one is pending will `p_panic` the caller; but one read and
|
||||
one write may be pending simultaneously. (*PLIB* Ch.9; *ADDSYS* driver chapter.)
|
||||
|
||||
### 2.2 Convenience functions
|
||||
|
||||
| Function | Wraps | Notes |
|
||||
| -------- | ----- | ----- |
|
||||
| `INT p_close(VOID *pcb)` | `p_iow(P_FCLOSE)` | `p_close(NULL)` returns 0. Always closes even if it returns an error. |
|
||||
| `INT p_read(VOID *pcb, VOID *buf, UINT len)` | `p_iow(P_FREAD)` | returns bytes read, or negative error. `f_read` `p_leave`s on error. |
|
||||
| `INT p_write(VOID *pcb, VOID *buf, UINT len)` | `p_iow(P_FWRITE)` | returns 0 or negative error. `f_write` `p_leave`s on error. |
|
||||
| `INT p_seek(...)` | `p_iow(P_FSEEK)` | `FIL:` only (Files chapter). |
|
||||
|
||||
(*PLIB* Ch.9.)
|
||||
|
||||
### 2.3 Waiting and cancelling
|
||||
|
||||
```c
|
||||
VOID p_iowait(VOID); /* wait on the process I/O semaphore */
|
||||
VOID p_waitstat(WORD *pstat); /* wait until *pstat leaves E_FILE_PENDING */
|
||||
```
|
||||
|
||||
To wait on **one specific** request, use `p_waitstat`; `p_iowait` waits for *any*
|
||||
completion and is used in the central dispatch loop. Every `p_iosignal` must be
|
||||
matched by a `p_iowait` (or a function that calls it). (*PLIB* Ch.8.)
|
||||
|
||||
Cancelling — `p_iow(pcb, P_FCANCEL)` — cancels outstanding async requests on the
|
||||
channel and returns zero (harmless if none pending). Principles (*PLIB* Ch.9):
|
||||
|
||||
- the cancel **precipitates** completion; it does not stop the request completing;
|
||||
- it may or may not be effective (the request may complete naturally first);
|
||||
- **you must still consume the completion signal** — typically an immediate
|
||||
`p_waitstat` to "use up" the signal.
|
||||
|
||||
`p_waitstat` is safer than `p_iowait` for using up a cancelled request's signal.
|
||||
General-purpose synchronous wrappers must use `p_waitstat`, not `p_iowait`.
|
||||
|
||||
---
|
||||
|
||||
## 3. I/O function codes (`p_file.h`)
|
||||
|
||||
Services are named `P_Fxxx` and defined in `p_file.h`. The generic codes that apply
|
||||
across many devices are (*PLIB* Ch.9; *IODEV* Ch.1):
|
||||
|
||||
| Code | Meaning |
|
||||
| ---- | ------- |
|
||||
| `P_FREAD` | read data from a channel |
|
||||
| `P_FWRITE` | write data to a channel |
|
||||
| `P_FCLOSE` | close a channel |
|
||||
| `P_FCANCEL` | cancel outstanding async requests |
|
||||
| `P_FSENSE` | sense channel characteristics |
|
||||
| `P_FSET` | set channel characteristics |
|
||||
| `P_FFLUSH` | flush buffered data |
|
||||
| `P_FCTRL` | test/set control lines (serial) |
|
||||
| `P_FINQ` | inquire supported characteristics |
|
||||
|
||||
Numeric values are assigned in `p_file.h`. The canonical numbering is:
|
||||
|
||||
| Constant | Value |
|
||||
| -------- | ----- |
|
||||
| `P_FREAD` | 1 |
|
||||
| `P_FWRITE` | 2 |
|
||||
| `P_FCLOSE` | 3 |
|
||||
| `P_FCANCEL` | 4 |
|
||||
| `P_FSET` | 7 |
|
||||
| `P_FSENSE` | 8 |
|
||||
| `P_FFLUSH` | 9 |
|
||||
| `P_FCTRL` | 11 |
|
||||
| `P_FINQ` | 12 |
|
||||
|
||||
> Confirmation from the manuals: the AccessIr chapter tabulates its own function
|
||||
> numbers and gives `P_FREAD = 1`, `P_FWRITE = 2` (*IODEV* Ch.16, "Constants").
|
||||
> The System Services driver model (*ADDSYS*) documents the OS-defined common set
|
||||
> `IoFuncRead / IoFuncWrite / IoFuncClose / IoFuncCancel / IoFuncSet /
|
||||
> IoFuncSense / IoFuncFlush`, which map to `P_FREAD … P_FFLUSH`; `p_read`,
|
||||
> `p_write` and `p_close` call the driver with `IoFuncRead`, `IoFuncWrite` and
|
||||
> `IoFuncClose` respectively. Drivers are urged to keep these conventional meanings
|
||||
> so that attached drivers work.
|
||||
|
||||
Device-specific codes also exist, e.g. `P_FSEEK`, `P_FSETEOF` (files); `P_FTEST`
|
||||
(test for input); `P_FEDIT` (console edit); `P_FRELATIVE` / `P_FABSOLUTE` (timers);
|
||||
`P_FSTART` (FRC); `P_FCONNECT` / `P_FDISCONNECT` / `P_FRSUPER` (NCP, Xmodem);
|
||||
`P_FIR*` (IR); `E_FALARM` / `E_FDIAL` / `E_FSSOUNDCHANNELn` (sound). These are
|
||||
listed with the individual devices below and in *IODEV*.
|
||||
|
||||
Error numbers `-32..-63` are reserved for I/O device errors of the form
|
||||
`E_FILE_xxx` (also in `p_file.h`). (*PLIB* Ch.6.)
|
||||
|
||||
---
|
||||
|
||||
## 4. Loading external device drivers
|
||||
|
||||
### 4.1 Load / delete
|
||||
|
||||
```c
|
||||
INT p_loadldd(TEXT *pName); /* default extension .LDD */
|
||||
INT p_loadpdd(TEXT *pName); /* default extension .PDD */
|
||||
INT p_devdel (TEXT *pName, INT devType); /* devType: E_LDD or E_PDD */
|
||||
```
|
||||
|
||||
- `p_loadldd` / `p_loadpdd` load a driver from a file; if `pName` has no extension,
|
||||
`.LDD` / `.PDD` is assumed, and a relative name uses the current path. After
|
||||
loading, a channel is opened with `p_open`. Errors: `E_FILE_EXIST` (same file
|
||||
already loaded), `E_FILE_NXIST` (file missing), `E_GEN_IMAGE` (bad/corrupt
|
||||
format), `E_GEN_NOMEMORY`, `E_GEN_NOSEGMENTS`. Apps that rely on an external LDD
|
||||
should call `p_loadldd` and ignore `E_FILE_EXIST`. (*PLIB* Ch.9.)
|
||||
- `p_devdel` deletes a **RAM-loaded** driver by device name (no trailing `':'`).
|
||||
Only loaded (not ROM) drivers can be deleted. Errors: `E_FILE_DEVICE` (not
|
||||
loaded), `E_GEN_NSUP` (ROM driver), `E_GEN_INUSE` (currently open), or a
|
||||
device-dependent error. **Warning:** a null `pName` deletes the first unloadable
|
||||
driver of that type. Good practice: attempt `p_devdel` when finished and ignore
|
||||
the result (harmless if in use elsewhere or in ROM). (*PLIB* Ch.9.)
|
||||
|
||||
Related helpers: `p_devqu(pName)` returns the number of units an LDD supports (e.g.
|
||||
`p_devqu("TTY")` → 2 if two serial boards are fitted; `E_GEN_FAIL` means unlimited,
|
||||
as `FIL:` returns); `p_devfnd(...)` iterates device names matching a wildcard.
|
||||
(*PLIB* Ch.9.)
|
||||
|
||||
### 4.2 The `.LDD` / `.PDD` model and device memory segments
|
||||
|
||||
External drivers are loaded from a driver file into a **device memory segment**,
|
||||
allocated by the segment allocator. The ability to load and remove drivers **without
|
||||
a system reset** is a key EPOC feature: you can physically attach a peripheral and
|
||||
load its driver without exiting any process. The I/O system also notifies drivers
|
||||
on power off/on so they can save/restore device state. (*PLIB* Ch.9.)
|
||||
|
||||
Memory segments (*PLIB* Ch.7, "Memory Allocation"):
|
||||
|
||||
- **Device segments** are created when an external device is installed and deleted
|
||||
when removed; once created a device segment does not normally change size. The
|
||||
first two device segments are special — the data segments of `SYS$NULL` and the
|
||||
supervisor `SYS$SMANG`.
|
||||
- Device segments are allocated at **lower addresses** than the volatile dynamic
|
||||
segments so that dynamic-segment activity does not move them (a driver normally
|
||||
has to stop working while its segment moves — risking data loss on, say, a serial
|
||||
receive).
|
||||
- Segment name extension conventions: **`.LDD`** = LDD device segment, **`.PDD`** =
|
||||
PDD device segment; `.$SC` primary shared code, `.DYL` dynamic library, `.$nn`
|
||||
process data segment.
|
||||
- The segment index table has fixed sub-capacities — typically **96 total (32
|
||||
device + 64 dynamic)**. Addresses/sizes are in 16-byte paragraphs.
|
||||
|
||||
---
|
||||
|
||||
## 5. Per-device reference
|
||||
|
||||
### 5.1 Serial port — `TTY:` (*IODEV* Ch.4)
|
||||
|
||||
Fully interrupt-driven RS-232. Two cooperating layers: a hardware PDD and a
|
||||
hardware-independent LDD (which buffers incoming data).
|
||||
|
||||
**Device names.** First port `TTY:A`; second `TTY:B`. Availability varies: a Series 3
|
||||
with a 3-Link recognises only `TTY:A`; an MC with two serial/parallel modules has
|
||||
`TTY:A` (left) and `TTY:B` (right); HC has a third, cradle port `TTY:C`. HC also
|
||||
exposes TTL-level ports `TTY:D/E/F` (direct) and `TTY:G/H/I` (inverted) — not
|
||||
documented in that chapter.
|
||||
|
||||
**Parameters — `P_SRCHAR` (`p_serial.h`), all fields:**
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
UBYTE tbaud; /* transmit baud rate */
|
||||
UBYTE rbaud; /* receive baud rate */
|
||||
UBYTE frame; /* data bits + stop + parity-present */
|
||||
UBYTE parity; /* parity type */
|
||||
UBYTE hand; /* handshake flags */
|
||||
UBYTE xon; /* XON character (default DC1 0x11) */
|
||||
UBYTE xoff; /* XOFF character (default DC3 0x13) */
|
||||
UBYTE flags; /* control flags */
|
||||
ULONG tmask; /* terminator mask */
|
||||
} P_SRCHAR;
|
||||
```
|
||||
|
||||
**Baud (`tbaud`, `rbaud`)** — one of `P_BAUD_50, _75, _110, _134, _150, _300, _600,
|
||||
_1200, _1800, _2000, _2400, _3600, _4800, _7200, _9600, _19200, _38400, _56000`.
|
||||
Default `P_BAUD_9600`. All SIBO machines support `_50`..`_9600`; MC 200/400 and
|
||||
Series 3a also support `_19200`; HC/Series 3 can be set to `_19200` but their clock
|
||||
is slightly too slow (frequent overruns). No SIBO hardware supports split
|
||||
transmit/receive rates.
|
||||
|
||||
**Frame (`frame`)** — one of `P_DATA_5 / P_DATA_6 / P_DATA_7 / P_DATA_8`, optionally
|
||||
OR'd with `P_TWOSTOP` (2 stop bits, else 1) and `P_PARITY` (parity bit present).
|
||||
Default `P_DATA_8` (8 data, 1 stop, no parity). All settings supported on all SIBO
|
||||
machines.
|
||||
|
||||
**Parity (`parity`)** — used only if `P_PARITY` is set: `P_PAR_EVEN`, `P_PAR_ODD`,
|
||||
`P_PAR_MARK`, `P_PAR_SPACE`. Default 0. **No SIBO machine supports mark or space
|
||||
parity.**
|
||||
|
||||
**Handshaking (`hand`, plus `xon`/`xoff`)** — combination of:
|
||||
|
||||
| Flag | Effect |
|
||||
| ---- | ------ |
|
||||
| `P_OBEY_XOFF` | obey received XON/XOFF (input flow control) |
|
||||
| `P_SEND_XOFF` | transmit XON/XOFF to remote (output flow control) |
|
||||
| `P_IGN_CTS` | if set, RTS held permanently active and incoming CTS ignored; if clear, RTS/CTS flow control |
|
||||
| `P_OBEY_DSR` | if set, suspend TX when incoming DSR inactive (DTR/DSR); DTR held active while port is open |
|
||||
| `P_FAIL_DSR` | (only if `P_OBEY_DSR`) complete outstanding `P_FREAD`/`P_FWRITE` with `E_FILE_LINE` if DSR goes inactive |
|
||||
| `P_OBEY_DCD` | if set, suspend TX when incoming DCD inactive |
|
||||
| `P_FAIL_DCD` | (only if `P_OBEY_DCD`) complete requests with `E_FILE_LINE` if DCD goes inactive |
|
||||
|
||||
Default `hand = 0` → RTS/CTS handshaking. DCD is an input-only carrier indicator,
|
||||
not a flow-control line. `xon`/`xoff` default to DC1 (0x11) / DC3 (0x13).
|
||||
|
||||
**Control flags (`flags`)** — only `P_IGNORE_PARITY` is defined (discard parity
|
||||
errors; the errored character is still delivered). Other bits reserved zero. Default 0.
|
||||
|
||||
**Terminator mask (`tmask`)** — 32 bit flags, bit *n* (n = 0..31) selecting control
|
||||
code `0x00`..`0x1F` as a terminating character. E.g. bit 13 = CR, bit 10 = LF (to
|
||||
read a line at a time); bit 26 = Ctrl-Z. A read completes when a terminator is
|
||||
received (included in the returned length). Default 0 (none).
|
||||
|
||||
**Errors:** `E_FILE_PARITY`, `E_FILE_FRAME`, `E_FILE_OVERRUN`, `E_FILE_LINE`
|
||||
(inactive required control line), plus `E_GEN_OVER` (driver receive buffer full)
|
||||
and `E_FILE_RECORD` (buffer filled with no terminator seen).
|
||||
|
||||
**Services:**
|
||||
|
||||
| Service | Prototype / notes |
|
||||
| ------- | ----------------- |
|
||||
| `p_open("TTY:x", -1)` | Powers up port, drives DTR active. Defaults as above; RTS not driven active until first `P_FREAD` or a `P_FSET` (so buffered-modem data is not lost). Errors: `E_GEN_NOMEMORY`, `E_GEN_INUSE`, `E_FILE_DEVICE`, `E_FILE_LOCKED`. |
|
||||
| `P_FREAD` | `p_iow(pcb, P_FREAD, buf, &len)` — read up to `*len` bytes. Completes on: full length; receive error (partial data + negative status); a `tmask` terminator (length includes it); or cancel (`E_FILE_CANCEL`). After a cancel, use `P_FTEST` — more buffered chars may remain. |
|
||||
| `P_FWRITE` | `p_iow(pcb, P_FWRITE, buf, &len)` — obeys current handshaking. A **zero-length** write with `P_OBEY_DSR` set completes only when DSR goes active (used to detect a connection). Cancel → `E_FILE_CANCEL`; the driver does not report bytes sent before cancel. |
|
||||
| `P_FCANCEL` | cancel outstanding read and write. |
|
||||
| `P_FSENSE` | `p_iow(pcb, P_FSENSE, P_SRCHAR*)` — read current characteristics (cannot fail). |
|
||||
| `P_FSET` | `p_iow(pcb, P_FSET, P_SRCHAR*)` — set characteristics (sense-modify-set idiom). Errors `E_GEN_ARG`, `E_GEN_NSUP`, `E_FILE_LINE`. Panics if a read/write is outstanding. |
|
||||
| `P_FFLUSH` | discard the LDD read buffer, clear error status, release paused remote. |
|
||||
| `P_FTEST` | `p_iow(pcb, P_FTEST, &len)` — bytes currently buffered (at least that many can be read synchronously). |
|
||||
| `P_FCTRL` | `p_iow(pcb, P_FCTRL, UBYTE *pctrl)` — read CTS/DSR/DCD input line state into `*pctrl` as a bit mask, and optionally set DTR (see below). |
|
||||
| `P_FINQ` | `p_iow(pcb, P_FINQ, UWORD *pmask)` — write three words of supported-characteristic bit flags. |
|
||||
|
||||
**Control-line constants (`p_serial.h`):**
|
||||
|
||||
- Input line state returned by `P_FCTRL` in `*pctrl`:
|
||||
`P_SRCTRL_CTS`, `P_SRCTRL_DSR`, `P_SRCTRL_DCD` (bit set ⇒ line active).
|
||||
- DTR output: if `*(pctrl+1)` is non-zero it sets DTR to `P_SRDTR_ON` (active) or
|
||||
`P_SRDTR_OFF` (inactive). `E_GEN_NSUP` if the driver can't set DTR (all current
|
||||
SIBO machines can).
|
||||
- `P_FINQ` bit flags: word 0 = `P_SRINQ_50 … P_SRINQ_19200`; word 1 =
|
||||
`P_SRINQ_38400`, `P_SRINQ_56000` (not supported on SIBO); word 2 =
|
||||
`P_SRINQ_DATA5/6/7/8`, `P_SRINQ_STOP2`, `P_SRINQ_PAREVEN/PARODD/PARMARK/PARSPACE`,
|
||||
`P_SRINQ_SETDTR`, `P_SRINQ_SPLIT`. SIBO supports all except `PARMARK`, `PARSPACE`
|
||||
and `SPLIT`.
|
||||
|
||||
`p_close` flushes the receive buffer, waits for any in-flight TX char, cancels
|
||||
outstanding read/write, drops RTS and DTR, and powers the port down.
|
||||
|
||||
### 5.2 Parallel port — `PAR:` (*IODEV* Ch.3)
|
||||
|
||||
Standard Centronics, **output-only** (no read service). Available via serial/parallel
|
||||
or parallel expansion modules; HC cradle provides a third. Names `PAR:A`, `PAR:B`,
|
||||
`PAR:C` (e.g. Series 3 = `PAR:A` only; MC with two modules = `PAR:A` left, `PAR:B`
|
||||
right).
|
||||
|
||||
- `p_open("PAR:x", -1)` — powers up the port lines (all control lines cleared low);
|
||||
it draws power until closed. Errors: `E_FILE_ALLOC`, `E_FILE_DEVICE`,
|
||||
`E_FILE_LOCKED` / `E_GEN_INUSE`.
|
||||
- `P_FWRITE` — `p_iow(pcb, P_FWRITE, buf, &len)`. **Never completes if no
|
||||
functioning receiver is connected** — always write asynchronously with a timer
|
||||
timeout. Errors `E_FILE_WRITE`, `E_FILE_CANCEL`.
|
||||
- `P_FCANCEL` — cancel the write (an indeterminate amount will already have been
|
||||
written).
|
||||
- `P_FSENSE` — `p_iow(pcb, P_FSENSE, UWORD *port)` reads input control lines
|
||||
(`p_par.h`): `S_BUSY` (pin 11), `S_ACK` (10), `S_ERROR` (15), `S_PE` (12). *Only
|
||||
on HC/MC ranges with EPOC ≥ 2.30; not on Series 3/3a.*
|
||||
- `P_FSET` — `p_iow(pcb, P_FSET, UWORD *type, UWORD *port)` sets/clears output lines:
|
||||
`*type = 1` sets high, `0` clears low. Lines: `S_INIT` (16), `S_AUTOFD` (14),
|
||||
`S_SELECT` (17), plus `S_SPARE` (custom hardware only — ASIC5 pin 42, no effect on
|
||||
the standard module). *Same HC/MC ≥ 2.30 restriction.*
|
||||
|
||||
### 5.3 Timers — `TIM:` (async) and `FRC:` (free-running counter)
|
||||
|
||||
**`TIM:` — asynchronous timer.** Documented in the *PLIB* "Time, Timers and Dates"
|
||||
chapter (not in *IODEV*). Opened `p_open(&tcb, "TIM:", -1)`. Requests are placed on
|
||||
a delta queue via `p_ioc(P_FRELATIVE, ..., &ticks)` (relative) or
|
||||
`p_ioc(P_FABSOLUTE, ...)`; cancel with `P_FCANCEL`. Relative units are 1/10 s (the
|
||||
examples use `tval = 10L * secs`). `TIM:` is the standard tool for I/O timeouts (see
|
||||
the `PAR:` and `TTY:` examples in *IODEV*). (*PLIB* Ch.8/9; Time chapter.)
|
||||
|
||||
**`FRC:` — free-running counter** (*IODEV* Ch.7). **Specific to Series 3a and
|
||||
Workabout.** Accuracy ±2 ppm, resolution 1/1024 s. **Supports only one process at a
|
||||
time.** For resolutions of 1/32 s or coarser, use `TIM:` instead.
|
||||
|
||||
- `p_open("FRC:", -1)` — errors `E_FILE_OPEN` (in use), `E_GEN_NOMEMORY`.
|
||||
- `P_FSTART` — `p_iow(pcb, P_FSTART, UWORD *pmode, UWORD *pint)`:
|
||||
- `E_FRC_COUNTING`: counter increments every 1/1024 s from 0; `pint` ignored.
|
||||
- `E_FRC_REPEATING`: increments every `*pint` × 1/1024 s (`*pint` in 10..65535,
|
||||
≈0.01–64 s). Errors `E_GEN_NSUP` (bad mode), `E_GEN_RANGE` (interval 0..9).
|
||||
- `P_FREAD` (COUNTING mode): `p_iow(pcb, P_FREAD, ULONG *arg1)` — elapsed 1/1024 s
|
||||
since `P_FSTART`, not reset by the read.
|
||||
- `P_FREAD` (REPEATING mode): `p_iow(pcb, P_FREAD)` — completion status = number of
|
||||
whole intervals elapsed since the last `P_FSTART`/`P_FREAD` (waits for ≥1
|
||||
interval; time is not lost between reads).
|
||||
- Read errors: `E_GEN_OVER` (>32767 intervals / too large for `arg1`),
|
||||
`E_FILE_CANCEL`, `E_FILE_READ` (counter not running, or machine was switched off
|
||||
mid-count).
|
||||
|
||||
### 5.4 Sound — `SND:` (*IODEV* Ch.5)
|
||||
|
||||
Speaker driven by the `SND:` driver; a separate piezo **buzzer** (all machines
|
||||
except Series 3a, which emulates it via the speaker) is driven by the PLIB
|
||||
`p_sound` routine, not `SND:`. Output is disabled if the `E_SOUND_DEVICE` sound-flag
|
||||
bit is clear or `E_SOUND_DISABLE` is set (managed via `p_getsnd`/`p_setsnd`). The
|
||||
speaker is a **two-voice** device (chords / DTMF). Series 3 `SND:` is limited to
|
||||
DTMF and simple alarms — richer sound needs an extra LDD (e.g. `SVDFRC.LDD` on the
|
||||
SDK disk). Series 3a can also play/record `.WVE` files.
|
||||
|
||||
- `p_open("SND:", -1)` — errors `E_FILE_ALLOC`, `E_GEN_FAIL` (disabled),
|
||||
`E_FILE_LOCKED`/`E_GEN_INUSE`.
|
||||
- `P_FCANCEL` — cancel outstanding write.
|
||||
- `P_FSENSE` / `P_FSET` — `E_SOUND` struct (`epoc.h`):
|
||||
```c
|
||||
typedef struct { UBYTE beatsPerMinute; UBYTE volume; } E_SOUND;
|
||||
```
|
||||
`beatsPerMinute` (HC/MC/3a) `E_SOUND_MIN_BPM` 2 .. `E_SOUND_MAX_BPM` 240, default
|
||||
120 (no effect on Series 3). `volume` 0 (`E_SOUND_MAX_VOLUME`, loudest) .. 5
|
||||
(`E_SOUND_MIN_VOLUME`); Series 3 range 1..4; Series 3a has four distinct volumes;
|
||||
smaller = louder; default 1. Always sense-then-set immediately after open, before
|
||||
playing — even at defaults.
|
||||
- `E_FALARM` — `p_iow(pcb, E_FALARM, UWORD *palarm)`: `*palarm` 0 = "rings", 1 =
|
||||
"chimes".
|
||||
- `E_FSSOUNDCHANNELn` *(HC/MC/3a only)* — `p_ioc(pcb, E_FSSOUNDCHANNELn, pstat,
|
||||
WORD *pnotes, WORD *plen)`, n = 1 or 2. `*plen ≤ 16384` notes; each note is two
|
||||
words (frequency Hz — middle A = 440; duration in beats). Output starts only once
|
||||
**both** channels have been called (for sync); use `*plen = 0` for an unused
|
||||
voice.
|
||||
- `E_FDIAL` *(Series 3 / 3a only)* — `p_iow(pcb, E_FDIAL, TEXT *pstr, E_DIAL
|
||||
*pdial)` emits DTMF for `*pstr`.
|
||||
```c
|
||||
typedef struct { UBYTE toneLengthTicks; UBYTE delayLengthTicks;
|
||||
UWORD pauseLengthTicks; } E_DIAL; /* ticks = 1/32 s */
|
||||
```
|
||||
Valid chars 0-9, A-F (`#`→F, `*`→E); space/comma = pause; others ignored; max 26
|
||||
tone/pause chars. Errors `E_FILE_CANCEL`, `E_GEN_ARG` (too many chars).
|
||||
|
||||
### 5.5 Magnetic card reader — `MCR:` (*IODEV* Ch.12)
|
||||
|
||||
HC MCR driver, built into the HC OS. Interface fits top (`MCR:A`), bottom (`MCR:B`)
|
||||
or cradle (`MCR:C`).
|
||||
|
||||
- `p_open("MCR:x", -1)` — errors `E_GEN_NOMEMORY`, `E_FILE_DEVICE` (no interface in
|
||||
slot), `E_FILE_NAME`, `E_FILE_LOCKED`/`E_GEN_INUSE`.
|
||||
- `P_FREAD` — `p_iow(pcb, P_FREAD, UBYTE *buf1, UBYTE *buf2)`: track 1 → `*buf1`,
|
||||
track 2 → `*buf2` (each buffer ≥ 256 bytes). Data is **leading-byte-count ASCII**;
|
||||
count 0 = unsuccessful. Pass either buffer as `NULL` to skip that track. Track 1 =
|
||||
alphanumeric (name/account), track 2 = numeric (most readers do track 2 only).
|
||||
Errors `E_FILE_READ` (decode error), `E_GEN_OVER`, `E_FILE_CANCEL`.
|
||||
- `P_FCANCEL` — cancel the read.
|
||||
- `P_FSET` *(EPOC ≥ 2.32)* — `p_iow(pcb, P_FSET, UWORD *mask)` programs the 100 kΩ
|
||||
pull-up/down resistors on the five reader lines (bit set = pull-up, clear =
|
||||
pull-down; default all pull-down): `M_DATA1PU` 0x01, `M_CLK1PU` 0x02, `M_DATA2PU`
|
||||
0x04, `M_CLK2PU` 0x08, `M_CLSPU` 0x10 (card-present). Other bits ignored.
|
||||
|
||||
### 5.6 Infrared (*IODEV* Ch.15–17)
|
||||
|
||||
Psion IR is **IrDA-compliant only** (S3a/Siena ports won't talk to non-IrDA
|
||||
devices). The protocol stack, bottom to top: **SIR** (Serial Infrared physical
|
||||
layer device driver, drives the hardware) → **IrLAP** (link access) → **IrLMP**
|
||||
(link management) → the **IrMUX API** (server side) and **AccessIr API** (client
|
||||
side). Two application-facing device channels are exposed:
|
||||
|
||||
- **`AIR:`** — the AccessIr API device. `p_open(&pcb, "AIR:", 0)` (mode 0) powers up
|
||||
the port; errors `E_GEN_INUSE`, `E_GEN_NOMEMORY`. Services use `P_FIR*` codes
|
||||
(numeric values from *IODEV* Ch.16): `P_FREAD` 1, `P_FWRITE` 2,
|
||||
`P_FIRDISCONNECT` 4, `P_FIRDISCOVER` 5, `P_FIRSELECT` 6, `P_FIRAWAITCONNECT` 7,
|
||||
`P_FIRMAKECONNECT` 8. Flow: `P_FIRDISCOVER` (log in-range machines) →
|
||||
`P_FIRSELECT` → `P_FIRMAKECONNECT` (primary) / `P_FIRAWAITCONNECT` (secondary),
|
||||
exchanging up to 56 bytes of connect data → `P_FREAD`/`P_FWRITE` →
|
||||
`P_FIRDISCONNECT`.
|
||||
- **`IRP:`** — the IR **printer-port** device driver (the "IRP" API), used for IR
|
||||
printing. (*IODEV* Ch.15, protocol-layer diagram.)
|
||||
|
||||
The **IrMUX API** (*IODEV* Ch.17) is the server-side interface (LM-IAS
|
||||
registration, connection-oriented and connectionless reads/writes) exposed through
|
||||
`LM_*` messages rather than `P_Fxxx` codes.
|
||||
|
||||
> MX note: these chapters describe the S3a/Siena/S3c-era IrDA stack; the manuals do
|
||||
> not document Workabout-MX-specific IR behaviour.
|
||||
|
||||
---
|
||||
|
||||
## 6. Barcode scanning
|
||||
|
||||
### 6.1 Workabout MX integral laser — see `SCANNER-API.md`
|
||||
|
||||
The **Workabout MX integral laser scanner is not covered by these manuals.** It is
|
||||
documented separately in **`SCANNER-API.md`** (sibling of this file, at
|
||||
`/tmp/claude/inv/SCANNER-API.md`). In brief, per that document: the MX uses the
|
||||
logical driver **`WL2`** (units `WL2:A`, `WL2:D`) with decoder type **`Symbol2`**
|
||||
(the integral Symbol laser engine); it is a decoded scanner, triggered by the
|
||||
keyboard scan key (Window Server key code 368). **Do not duplicate that content
|
||||
here — consult `SCANNER-API.md` for the MX.**
|
||||
|
||||
The remainder of this section summarises only the **older external** wand/RS232
|
||||
barcode devices as documented in *IODEV*.
|
||||
|
||||
### 6.2 External wand decoders — `BAR:` (*IODEV* Ch.13)
|
||||
|
||||
The HC has **no** built-in barcode driver/decoder; readers are supported by external
|
||||
combined **decoder + device-driver LDDs** that the app must load (`p_loadldd` or OPL
|
||||
`DevLoadLdd`). Wand interface module fits the top (`BAR:A`) or bottom (`BAR:B`) of
|
||||
the HC. Available drivers (device name `BAR` in all cases):
|
||||
|
||||
| File | Symbologies |
|
||||
| ---- | ----------- |
|
||||
| `BAREAN.LDD` | EAN8, EAN13, UPC, UPCE |
|
||||
| `BARC39.LDD` | Code 39 |
|
||||
| `BARITF.LDD` | Interleaved 2 of 5 (ITF) |
|
||||
| `BAR128.LDD` | Code 128 |
|
||||
| `BARMPLES.LDD`| Modified Plessey |
|
||||
| `BARRAW.LDD` | raw |
|
||||
|
||||
None auto-discriminates. Load e.g. `p_loadldd("BARC39.LDD")`; remove with
|
||||
`p_devdel("BAR")` (or OPL `DevDelete`).
|
||||
|
||||
- `p_open("BAR:", -1)` — opens via the previously loaded LDD. Errors as for `MCR:`.
|
||||
- `P_FREAD` — `p_iow(pcb, P_FREAD, UBYTE *buf)` (buf ≥ 256). Leading-byte-count
|
||||
ASCII; count 0 = failed read. The **first character encodes the symbology** (e.g.
|
||||
EAN8/13, UPC, Code 39, ITF, Code 128, Modified Plessey, UPCE); the rest is the
|
||||
decoded data. Errors `E_GEN_OVER`, `E_FILE_CANCEL`.
|
||||
- `P_FCANCEL` — cancel the read.
|
||||
|
||||
### 6.3 Intelligent reader / RS232 module — `TTY:` escape commands (*IODEV* Ch.14)
|
||||
|
||||
The **intelligent** RS232/barcode expansion module contains a decoding
|
||||
micro-controller and shares one serial port between an RS232 interface and the
|
||||
barcode interface (not usable simultaneously). Port mapping by slot: top slot →
|
||||
RS232 `TTY:A`, barcode `TTY:D`; bottom slot → RS232 `TTY:B`, barcode `TTY:E`. The
|
||||
barcode interface talks to the host at the standard HC comms settings (XON/XOFF,
|
||||
no hardware handshaking). It auto-discriminates EAN/JAN 8/13, UPCA, UPCE, Codabar,
|
||||
Code 128, Interleaved 2 of 5, and Code 39 (standard/extended), transmitting decoded
|
||||
data left-to-right as ASCII, terminated by default with a single CR.
|
||||
|
||||
Because it is reached through `TTY:`, it uses the normal serial services
|
||||
(`p_open`, `P_FSENSE`, `P_FSET`, `P_FREAD`, `P_FWRITE`, `p_close`). It is
|
||||
**programmed by writing escape sequences** to it via the serial channel:
|
||||
|
||||
```
|
||||
<Esc>-y<code><command> general form
|
||||
<Esc>-y<code><command><text> set-termination-string form
|
||||
<Esc>E hard reset (special form)
|
||||
```
|
||||
|
||||
`<Esc>` = 0x1B, then `-` (0x2D), then `y`/`Y`, a decimal `<code>` of 1–3 digits
|
||||
(0–255), and an uppercase command letter; no embedded spaces. Multiple option bits
|
||||
are combined by **summing** their `<code>` values. Multiple commands may be
|
||||
concatenated in one sequence — intermediate command letters lowercase, the final
|
||||
one uppercase (e.g. `<Esc>-y13f2h1D`).
|
||||
|
||||
Command letters (each `<Esc>-y<code><letter>`):
|
||||
|
||||
| Cmd | Function |
|
||||
| --- | -------- |
|
||||
| `D` | serial inter-character delay (0 = none, 1 = 10 ms) |
|
||||
| `E` | hard reset + self-test (special form `<Esc>E`; reverts all options to default; self-test failures reported as `... SELF TEST FAILED<CR><LF>`) |
|
||||
| `F` | select symbology bitmask: 1 Code39, 2 ITF, 4 UPC/EAN, 8 Codabar, 16 Code128 (default `31`) |
|
||||
| `G` | check-character options (verify / transmit check digits; per-symbology) |
|
||||
| `H` | decoding options (extended Code39, Codabar start/stop, UPC-vs-EAN, 2/5-digit supplements, UPC E→A expansion, auto-discriminate supplements) |
|
||||
| `J` | single-read mode enable/disable |
|
||||
| `K` | single-read control (fire one read when in single-read mode) |
|
||||
| `M` | set Interleaved 2 of 5 length |
|
||||
| `O` | set termination string (`<Esc>-y<code>O<string>`, up to 4 chars) |
|
||||
| `Q` | Code ID characters |
|
||||
| `S` | status request |
|
||||
| `W` | scanner enable |
|
||||
|
||||
Per-symbology output formats (ID chars, check digits, supplement digits for
|
||||
UPC E/A, EAN 8/13 and their +2/+5 variants; Codabar start/stop; ITF and Code 39
|
||||
check handling) are tabulated in *IODEV* Ch.14.
|
||||
|
||||
---
|
||||
|
||||
## 7. Other channel devices (pointers)
|
||||
|
||||
*IODEV* also documents the **Console** (`CON:`, Ch.2 — screen/keyboard, `P_SCR_*`
|
||||
function codes, `P_CON_KBREC`), **Alarm** (Ch.6, `A_FTIMED`/`A_FUNTIMED`), **World
|
||||
database** (Ch.8), **Xmodem/Ymodem** (Ch.9, `P_FCONNECT`/`P_FREAD`/`P_FWRITE`),
|
||||
**NCP / Link** (Ch.10, `SYS$NCP`), **Cradle/Docking Station** (Ch.11), and the
|
||||
**Fast Charger** (Ch.18, `FCHG_*`). The **`FIL:`** file driver and the **`TIM:`**
|
||||
timer are documented in the *PLIB* Files and Time chapters. These are outside the
|
||||
scope of this reference but follow the same `p_open` + `P_Fxxx` model described in
|
||||
Sections 1–3.
|
||||
|
||||
---
|
||||
|
||||
### Source map
|
||||
|
||||
| Topic | Source |
|
||||
| ----- | ------ |
|
||||
| Device model, `p_open`, `FIL:` fallback, modes, attached drivers, file server | *PLIB* Ch.9 |
|
||||
| I/O primitives (`p_ioa/ioc/iow` + variants), `p_read/write/close`, cancel | *PLIB* Ch.9 |
|
||||
| `p_iowait` / `p_waitstat`, semaphore rules | *PLIB* Ch.8 |
|
||||
| Function codes, `IoFunc*` mapping | *PLIB* Ch.9; *IODEV* Ch.1/16; *ADDSYS* driver chapter |
|
||||
| `p_loadldd`/`p_loadpdd`/`p_devdel`/`p_devqu`/`p_devfnd`; `.LDD`/`.PDD`; device segments | *PLIB* Ch.9, Ch.7 |
|
||||
| `TTY:`, `P_SRCHAR`, control lines | *IODEV* Ch.4 |
|
||||
| `PAR:` | *IODEV* Ch.3 |
|
||||
| `FRC:` | *IODEV* Ch.7 |
|
||||
| `TIM:` | *PLIB* Time chapter (referenced from *IODEV* Ch.1) |
|
||||
| `SND:` | *IODEV* Ch.5 |
|
||||
| `MCR:` | *IODEV* Ch.12 |
|
||||
| IR (`AIR:`, `IRP:`, IrMUX) | *IODEV* Ch.15–17 |
|
||||
| External `BAR:` wand decoders | *IODEV* Ch.13 |
|
||||
| Intelligent reader / `TTY:` escape commands | *IODEV* Ch.14 |
|
||||
| Workabout MX integral laser (`WL2`/`Symbol2`) | **`SCANNER-API.md`** (not these manuals) |
|
||||
@@ -0,0 +1,458 @@
|
||||
# PLIB Core API Reference (Psion SIBO / Workabout MX)
|
||||
|
||||
Categorised function reference for the core **PLIB** library, drawn from the
|
||||
*PLIB Reference*, Version 2.10 (3 February 1995), (C) Psion PLC.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Signatures are transcribed from the manual. The OCR source occasionally
|
||||
mangles glyphs; signatures below are corrected to the manual's own C types
|
||||
where the intent is unambiguous. Uncertain items are marked **[?]**.
|
||||
- Types: `TEXT` = char/byte string, `UBYTE`/`BYTE`, `UWORD`/`WORD` (16-bit),
|
||||
`UINT`/`INT` (16-bit), `ULONG`/`LONG` (32-bit), `DOUBLE` (64-bit float),
|
||||
`VOID`, `HANDLE`, `BOOL`.
|
||||
- Many functions return `0` on success and a negative `E_*` error number
|
||||
(defined in `p_gen.h` etc.) on failure.
|
||||
- `f_*` twins of `p_*` allocators/senders behave identically except they call
|
||||
`p_leave(E_GEN_NOMEMORY)` / `p_leave(err)` instead of returning `NULL`/`err`.
|
||||
|
||||
---
|
||||
|
||||
## 1. String & Buffer Handling
|
||||
|
||||
Declared chiefly in `p_std.h` / `plib.h`. Buffers (`b*`) take an explicit
|
||||
length; strings (`s*`) are zero-terminated.
|
||||
|
||||
### 1.1 Copy, length, concatenate, fill
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `UBYTE *p_bcpy(VOID *target, VOID *source, UINT len);` | Copy `len` bytes source→target (overlap-safe); returns `target+len`. |
|
||||
| `UINT p_slen(TEXT *str);` | Return length of zero-terminated string, excluding the terminator. |
|
||||
| `TEXT *p_scpy(TEXT *target, TEXT *source);` | Copy string source→target; returns address of target's terminating zero. |
|
||||
| `TEXT *p_scpym(TEXT *target, ...);` | Copy+concatenate a NULL-terminated list of strings into target. |
|
||||
| `TEXT *p_scat(TEXT *lstr, TEXT *rstr);` | Append rstr to lstr; returns address of new terminating zero. |
|
||||
| `TEXT *p_scatm(TEXT *lstr, ...);` | Append a NULL-terminated list of strings to lstr. |
|
||||
| `UBYTE *p_brep(VOID *buf, INT buf_len, VOID *pattern, INT pat_len);` | Fill buf by replicating a byte pattern; returns `buf+buf_len`. |
|
||||
| `TEXT *p_srep(TEXT *buf, INT buf_len, TEXT *pattern);` | Fill buf by replicating a string pattern (terminator excluded). |
|
||||
| `VOID p_bswap(VOID *buf1, VOID *buf2, INT len);` | Swap `len` bytes between two buffers. |
|
||||
| `UBYTE *p_bfil(VOID *buf, UINT buf_len, INT fill_byte);` | Fill buf with a repeated byte; returns `buf+buf_len`. |
|
||||
| `TEXT *p_jtob(TEXT *tbuf, INT tlen, TEXT *sbuf, INT slen, INT type, INT fill);` | Left/right/centre-align sbuf in tbuf (`P_JLEFT`/`P_JRIGHT`/`P_JCENTRE`), padding with `fill`. |
|
||||
|
||||
### 1.2 Comparison
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_bcmp(VOID *lbuf, INT lbuf_len, VOID *rbuf, INT rbuf_len);` | Compare two buffers byte-wise; returns lbuf−rbuf (<0 / 0 / >0). |
|
||||
| `INT p_scmp(TEXT *lstr, TEXT *rstr);` | Compare two strings; returns lstr−rstr. |
|
||||
| `INT p_bcmpi(TEXT *lbuf, INT lbuf_len, TEXT *rbuf, INT rbuf_len);` | Case-independent buffer compare. |
|
||||
| `INT p_scmpi(TEXT *lstr, TEXT *rstr);` | Case-independent string compare. |
|
||||
|
||||
*(OCR renders these as `p_bemp`/`p_bempi`; the intended names are `p_bcmp`/`p_bcmpi`.)*
|
||||
|
||||
### 1.3 Searching (character, substring, wildcard)
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_bloc(VOID *buf, INT buf_len, INT ch);` | Index of first `ch` in buffer, or −1. |
|
||||
| `INT p_sloc(TEXT *str, INT ch);` | Index of first `ch` in string, or −1. |
|
||||
| `INT p_bloci(TEXT *buf, INT buf_len, INT ch);` | Case-independent forward char search in buffer. |
|
||||
| `INT p_sloci(TEXT *str, INT ch);` | Case-independent forward char search in string. |
|
||||
| `INT p_slocr(TEXT *str, INT ch);` | Reverse (last-occurrence) char search in string. |
|
||||
| `INT p_slocri(TEXT *str, INT ch);` | Case-independent reverse char search in string. |
|
||||
| `INT p_bsub(VOID *buf, INT buf_len, VOID *sbuf, INT sbuf_len);` | Index of first occurrence of a byte sequence in buffer, or −1. |
|
||||
| `INT p_ssub(TEXT *str, TEXT *substr);` | Index of first occurrence of substring in string, or −1. |
|
||||
| `INT p_bsubi(TEXT *buf, INT buf_len, TEXT *sbuf, INT sbuf_len);` | Case-independent buffer subsequence search. |
|
||||
| `INT p_ssubi(TEXT *str, TEXT *substr);` | Case-independent substring search. |
|
||||
| `INT p_bmatch(TEXT *buf, INT blen, TEXT *mbuf, INT mlen);` | Match buffer against a wildcard spec (`*`, `?`); returns TRUE/FALSE. |
|
||||
| `INT p_bmatchi(TEXT *buf, INT blen, TEXT *mbuf, INT mlen);` | Case-independent wildcard buffer match. |
|
||||
| `INT p_smatch(TEXT *str, TEXT *mstr);` | Match string against wildcard spec (`*`, `?`). |
|
||||
| `INT p_smatchi(TEXT *str, TEXT *mstr);` | Case-independent wildcard string match. |
|
||||
|
||||
### 1.4 Character classification (return TRUE/FALSE for `c` modulo 256)
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_isupper(INT c);` | Uppercase alphabetic (accented or not). |
|
||||
| `INT p_islower(INT c);` | Lowercase alphabetic. |
|
||||
| `INT p_isalpha(INT c);` | Alphabetic either case. |
|
||||
| `INT p_isdigit(INT c);` | Decimal digit 0–9. |
|
||||
| `INT p_isalnum(INT c);` | Alphanumeric. |
|
||||
| `INT p_isxdigit(INT c);` | Hex digit 0–9, A–F, a–f. |
|
||||
| `INT p_isspace(INT c);` | Whitespace (space, HT, NL, VT, FF, CR). |
|
||||
| `INT p_iscntrl(INT c);` | Control character (0–31, 127). |
|
||||
| `INT p_ispunct(INT c);` | Printable graphic, not alphanumeric/space. |
|
||||
| `INT p_isgraph(INT c);` | Printable graphic (alnum or punct). |
|
||||
| `INT p_isprint(INT c);` | Printable, i.e. `p_isgraph` plus space. |
|
||||
|
||||
### 1.5 Folding & case conversion, skipping
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `TEXT *p_skipwh(TEXT *str);` | Skip leading whitespace; return first non-white char. |
|
||||
| `TEXT *p_skipch(TEXT *str);` | Skip non-white chars; return first whitespace/terminator. |
|
||||
| `INT p_tofold(INT c);` | Fold char via built-in fold table (for case-insensitive matching). |
|
||||
| `TEXT *p_scpyf(TEXT *target, TEXT *source);` | Copy string folding each char (like `p_scpy`). |
|
||||
| `VOID p_sconf(TEXT *str);` | Fold the characters of a string in place. |
|
||||
| `INT p_toupper(INT c);` | Convert char to upper case (display use, not matching). |
|
||||
| `INT p_tolower(INT c);` | Convert char to lower case (display use, not matching). |
|
||||
| `VOID p_scap(TEXT *str);` | Capitalise string: first char upper, rest lower. *(EPOC 2.14+)* |
|
||||
|
||||
> **Folding vs. case:** `p_tofold`/`p_sconf` are for case-insensitive
|
||||
> ordering/matching (symbol tables); `p_toupper`/`p_tolower`/`p_scap` are for
|
||||
> human-visible text and must **not** be used for comparison.
|
||||
|
||||
---
|
||||
|
||||
## 2. Arrays, Queues & Sorting (misc utilities)
|
||||
|
||||
Doubly-linked circular queues use `P_QUE` headers; delta queues use `P_DELTA`.
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_bsrch(INT nrec, INT (*compf)(), INT *pmid, UBYTE *pmatch);` | Binary search over `nrec` records via callback; writes found index to `*pmid`. |
|
||||
| `INT p_qsort(INT nrec, INT (*ordf)(), VOID (*excf)(), UBYTE *base);` | Quicksort `nrec` records using ordering and exchange callbacks. |
|
||||
| `VOID p_enque(P_QUE *pNew, P_QUE *pEntry);` | Insert `pNew` before `pEntry` in its doubly-linked queue. |
|
||||
| `VOID p_deque(P_QUE *pEntry);` | Unlink `pEntry` from its queue. |
|
||||
|
||||
*(The manual also documents `p_dequed` for delta queues; its clean signature is
|
||||
not printed in the source text — **[?]**.)*
|
||||
|
||||
---
|
||||
|
||||
## 3. CRC & Checksums
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `VOID p_crc(UWORD *pcrc, UBYTE *buf, UINT len);` | Incrementally accumulate CCITT CRC-16 (x¹⁶+x¹²+x⁵+1) over `len` bytes; init `*pcrc` to 0 first. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Console I/O
|
||||
|
||||
Primitive console services layered on the `CON:` device / window server. The
|
||||
console is opened automatically on first use. PLIB offers only line output of
|
||||
mono-spaced text and simple backspace-edited line input; richer row/column
|
||||
control lives in the `CON:` device driver, and full UI in the Window Server.
|
||||
Internal buffer is `P_MAXSYSIO` (258) bytes, limiting output to 256 bytes/call.
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `VOID p_putch(UINT c);` | Write one character to the console. |
|
||||
| `VOID p_puts(TEXT *str);` | Write a string and start a new line. |
|
||||
| `VOID p_printf(TEXT *fstr, ...);` | Format args (as `p_atob`), write line, advance to next line. |
|
||||
| `VOID p_print(TEXT *fstr, ...);` | As `p_printf` but no automatic newline (use `\r`,`\n`). |
|
||||
| `INT p_getch(VOID);` | Wait for a keypress; return its character code. |
|
||||
| `INT p_gets(TEXT *str);` | Read a line (backspace editing) up to `P_MAXSYSIO-1`; returns length. |
|
||||
| `INT p_getl(TEXT *pmt, TEXT *str, INT len);` | Write prompt `pmt`, read up to `len` chars into `str`; returns length. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Number ↔ String Conversion
|
||||
|
||||
Declared in the *Integer Conversion and Rectangle Functions* chapter.
|
||||
|
||||
### 5.1 Integer/long → text
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `UINT p_itob(TEXT *buf, INT value);` | Signed decimal of INT → buf; returns chars written. |
|
||||
| `INT p_ltob(TEXT *buf, LONG value);` | Signed decimal of LONG → buf; returns chars written. |
|
||||
| `INT p_gtob(TEXT *buf, UINT value, INT radix);` | Unsigned UINT in any radix → buf. |
|
||||
| `INT p_gltob(TEXT *buf, ULONG value, INT radix);` | Unsigned ULONG in any radix → buf. |
|
||||
|
||||
### 5.2 Formatted multi-argument output
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_atob(TEXT *buf, TEXT *fstr, VOID *parg);` | `printf`-style format of arg list `parg` into buf; returns chars written. |
|
||||
| `VOID p_atos(TEXT *str, TEXT *fstr, ...);` | Convenience variadic wrapper over `p_atob` producing a zero-terminated string. |
|
||||
|
||||
Format `%[<align>][<fill>]<width><type>`; types: `b` binary, `c` char, `d`
|
||||
signed dec, `f` fill only, `m`/`w` 2-byte MSB/LSB binary *(EPOC 2.17+)*, `o`
|
||||
octal, `s` string, `u` unsigned dec, `x` hex. Widen to long with `l`/`L` or
|
||||
upper-case type; `<width>`/`<fill>` may be `*` (taken from args).
|
||||
|
||||
### 5.3 Text → integer/long (radix conversions)
|
||||
|
||||
All take `TEXT **pstr` (advanced past the parsed field on success), return 0 or
|
||||
`E_GEN_OVER`/`E_GEN_FAIL`.
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_stoi(TEXT **pstr, WORD *pval);` | Signed decimal string → 16-bit WORD. |
|
||||
| `INT p_stol(TEXT **pstr, LONG *pval);` | Signed decimal string → 32-bit LONG. |
|
||||
| `INT p_stog(TEXT **pstr, UWORD *pval, INT radix);` | Unsigned string in any radix → 16-bit UWORD. |
|
||||
| `INT p_stogl(TEXT **pstr, ULONG *pval, INT radix);` | Unsigned string in any radix → 32-bit ULONG. |
|
||||
| `INT p_stoa(TEXT **pstr, TEXT *fstr, ...);` | Scan multiple fields from a string into args per format `fstr`. |
|
||||
|
||||
### 5.4 Double ↔ text (floating point conversion)
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_dtob(TEXT *pbuf, DOUBLE *pval, P_DTOB *pformat);` | Format a double to text per `P_DTOB` (type/width/decimals/point/triad); returns chars or `E_GEN_*`. |
|
||||
| `INT p_stod(TEXT **pstr, DOUBLE *pval, INT point);` | Parse a floating-point number from text (`point` = decimal-point char) → double. |
|
||||
|
||||
> Note: the manual provides no `p_dtos` — double→string is `p_dtob`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Floating Point, Scientific & Math
|
||||
|
||||
Declared in `p_math.h`. Trig args are in **radians**. `E_CONFIG`/config via
|
||||
`p_getctd`. Two families exist:
|
||||
|
||||
- **Scientific functions** (`p_sin` … `p_pow`) and `p_rand`/`p_frand` require the
|
||||
8087 emulator (`sys$8087.ldd`).
|
||||
- **Arithmetic primitives** (`p_fld` … `p_longtof`) are emulator-independent —
|
||||
usable to do FP without loading the emulator.
|
||||
|
||||
Macros (also emulator-triggering with FP operands): `ABS(x)`, `MAX(a,b)`, `MIN(a,b)`.
|
||||
|
||||
### 6.1 Scientific (require emulator)
|
||||
|
||||
All return 0 or `E_GEN_ARG`/`E_GEN_UNDER`/`E_GEN_OVER`.
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_sin(DOUBLE *pret, DOUBLE *parg);` | Sine. |
|
||||
| `INT p_cos(DOUBLE *pret, DOUBLE *parg);` | Cosine. |
|
||||
| `INT p_tan(DOUBLE *pret, DOUBLE *parg);` | Tangent (arg magnitude limited). |
|
||||
| `INT p_asin(DOUBLE *pret, DOUBLE *parg);` | Arc sine (`ABS(*parg)<=1`). |
|
||||
| `INT p_acos(DOUBLE *pret, DOUBLE *parg);` | Arc cosine (`ABS(*parg)<=1`). |
|
||||
| `INT p_atan(DOUBLE *pret, DOUBLE *parg);` | Arc tangent. |
|
||||
| `INT p_ln(DOUBLE *pret, DOUBLE *parg);` | Natural (base-e) log. |
|
||||
| `INT p_exp(DOUBLE *pret, DOUBLE *parg);` | e raised to `*parg`. |
|
||||
| `INT p_log(DOUBLE *pret, DOUBLE *parg);` | Base-10 log. |
|
||||
| `INT p_sqrt(DOUBLE *pret, DOUBLE *parg);` | Square root. |
|
||||
| `INT p_pow(DOUBLE *pret, DOUBLE *parg1, DOUBLE *parg2);` | `*parg1` raised to `*parg2`. |
|
||||
|
||||
### 6.2 Emulator-independent FP arithmetic
|
||||
|
||||
For one-arg functions, `pret` and `parg` may alias. Return 0 or `E_GEN_*`.
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_fld(DOUBLE *pret, DOUBLE *parg);` | Load: `*pret = *parg`. |
|
||||
| `INT p_fadd(DOUBLE *pret, DOUBLE *parg);` | `*pret += *parg`. |
|
||||
| `INT p_fsub(DOUBLE *pret, DOUBLE *parg);` | `*pret -= *parg`. |
|
||||
| `INT p_fmul(DOUBLE *pret, DOUBLE *parg);` | `*pret *= *parg`. |
|
||||
| `INT p_fdiv(DOUBLE *pret, DOUBLE *parg);` | `*pret /= *parg`. |
|
||||
| `INT p_fcmp(DOUBLE *parg1, DOUBLE *parg2);` | Compare: returns 1 / 0 / −1. *(OCR: `p_femp`.)* |
|
||||
| `INT p_fneg(DOUBLE *parg);` | Negate `*parg` in place. |
|
||||
| `INT p_mod(DOUBLE *pret, DOUBLE *parg1, DOUBLE *parg2);` | Remainder of `*parg1 / *parg2`. |
|
||||
| `INT p_int(DOUBLE *pret, DOUBLE *parg);` | Integer part (toward zero) → double. |
|
||||
| `INT p_inti(WORD *pret, DOUBLE *parg);` | Integer part → 16-bit WORD (range-checked). |
|
||||
| `INT p_intl(LONG *pret, DOUBLE *parg);` | Integer part → 32-bit LONG (range-checked). |
|
||||
| `VOID p_itof(DOUBLE *pret, WORD *parg);` | Convert WORD → double. |
|
||||
| `VOID p_longtof(DOUBLE *pret, LONG *parg);` | Convert LONG → double. |
|
||||
|
||||
### 6.3 Random numbers
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `ULONG p_randl(ULONG *pseed);` | Next pseudo-random 32-bit long; updates `*pseed`. (No emulator.) |
|
||||
| `DOUBLE p_rand(ULONG *pseed);` | Random double in [0,1); updates `*pseed`. (Emulator required.) |
|
||||
| `VOID p_frand(DOUBLE *pret, ULONG *pseed);` | Random double in [0,1) → `*pret`; updates `*pseed`. |
|
||||
|
||||
> **Long integer arithmetic:** PLIB exposes no dedicated `p_lmul`/`p_ldiv`
|
||||
> helpers; 32-bit `LONG`/`ULONG` work is done with native C, the `l*`
|
||||
> conversion routines above, and `p_ltob`/`p_stol`/`p_stogl`/`p_randl`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Rectangle & Geometry Utilities
|
||||
|
||||
Operate on `P_RECT` / `P_POINT` (declared with the integer-conversion chapter).
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `VOID p_offrec(P_RECT *rect, INT xoffset, INT yoffset);` | Move a rectangle by an offset. |
|
||||
| `VOID p_insrec(P_RECT *rect, INT xinset, INT yinset);` | Shrink/expand a rectangle about its centre. |
|
||||
| `VOID p_unirec(P_RECT *rect1, P_RECT *rect2, P_RECT *result);` | Union (smallest enclosing rectangle). |
|
||||
| `INT p_intrec(P_RECT *rect1, P_RECT *rect2, P_RECT *result);` | Intersection; returns TRUE if they intersect. |
|
||||
| `INT p_pinrec(P_POINT *point, P_RECT *rect);` | TRUE if point lies inside rectangle. |
|
||||
| `INT p_emprec(P_RECT *rect);` | TRUE if rectangle is empty. |
|
||||
| `VOID p_absrec(P_RECT *rect, P_RECT *result);` | Normalise negative sides to positive. |
|
||||
|
||||
---
|
||||
|
||||
## 8. Memory Allocation & the Heap
|
||||
|
||||
Heap cells are allocated from the process data segment (max ~64K). `f_*`
|
||||
variants leave with `E_GEN_NOMEMORY` instead of returning `NULL`.
|
||||
|
||||
### 8.1 Cell allocation
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `VOID *p_alloc(UINT size);` | Allocate a heap cell ≥ `size` bytes; returns address or `NULL`. |
|
||||
| `VOID *f_alloc(UINT size);` | As `p_alloc` but leaves on failure. |
|
||||
| `VOID p_free(VOID *pcell);` | Free a cell (no-op if `pcell` is 0). |
|
||||
| `VOID *p_realloc(VOID *pcell, UINT size);` | Resize a cell (preserving contents); `pcell==0` ⇒ `p_alloc`. |
|
||||
| `VOID *f_realloc(VOID *pcell, UINT size);` | As `p_realloc` but leaves on failure. |
|
||||
| `VOID *p_adjust(VOID *pcell, UINT offset, INT amount);` | Open (+) or close (−) a gap mid-cell for insert/delete. |
|
||||
| `UINT p_alen(VOID *pcell);` | Return actual cell length in bytes (≥ requested). |
|
||||
|
||||
### 8.2 Heap tuning & diagnostics
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `VOID p_hgran(UINT nparas);` | Set heap growth granularity (paragraphs of 16 bytes; ≤ `E_MAX_GROWBY`). |
|
||||
| `VOID p_allwalk(VOID (*fptr)(VOID *fpar, INT isalloc, UINT len), VOID *fpar);` | Walk every heap cell, calling `fptr`; panics on inconsistency. |
|
||||
| `VOID p_allchk(INT num);` | Check allocated cells vs. free list; `p_panic(0xff)` with diagnostics if corrupt. |
|
||||
| `UINT p_allspc(VOID **pheap);` | Return potential free heap space; write heap start to `*pheap`. |
|
||||
| `p_altchk(...)` **[?]** | Thorough heap-integrity check (referenced by name; full signature not printed in source). |
|
||||
|
||||
### 8.3 System memory info
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `UINT p_getram(VOID);` | Addressable system RAM in 16-byte paragraphs (≤ 32768). |
|
||||
| `UINT p_totalK(VOID);` | Total machine RAM in KB, ignoring bank-switching. *(EPOC 3.50+)* |
|
||||
| `UINT p_sgfree(VOID);` | Available addressable segmented memory in paragraphs. |
|
||||
| `UINT p_sgramdisk(VOID);` | Paragraphs of addressable RAM used by the internal RAM disk. |
|
||||
|
||||
### 8.4 External data segments (shared memory / IPC)
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_sgdelete(TEXT *pName);` | Delete a named external data segment. |
|
||||
| `INT p_sgcopyto(HANDLE nHandle, LONG pos, VOID *source, UINT len);` | Write `len` bytes into an open segment at `pos`. |
|
||||
| `INT p_sgcopyfr(HANDLE nHandle, LONG pos, VOID *target, UINT len);` | Read `len` bytes from an open segment at `pos`. |
|
||||
| `UINT p_sgsize(HANDLE nHandle);` | Size of an open segment in 16-byte paragraphs. |
|
||||
| `INT p_sgadjust(HANDLE nHandle, INT nParas);` | Grow/shrink an open segment by `nParas` paragraphs. |
|
||||
| `INT p_sgclose(HANDLE nHandle);` | Close an open segment. |
|
||||
| `VOID p_sglock(HANDLE nHandle);` | Lock a segment against relocation. |
|
||||
| `VOID p_sgunlock(HANDLE nHandle);` | Unlock a previously locked segment. |
|
||||
|
||||
*(The chapter also references `p_sgcreate`/`p_sgopen`/`p_sgfind`; their clean
|
||||
signatures are not isolated in the source text — **[?]**.)*
|
||||
|
||||
### 8.5 Environment variables
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_getenv(TEXT *pMatch, TEXT *pValue);` | Get value of matching env var (string). |
|
||||
| `INT p_getenviron(TEXT *pMatch, INT mLength, VOID *pValue);` | Get env var value (binary, length-specified match). |
|
||||
| `INT p_setenv(TEXT *pName, TEXT *pValue);` | Set/create a string env var. |
|
||||
| `INT p_setenviron(TEXT *pName, INT nLength, VOID *pValue, INT vLength);` | Set a binary env var. |
|
||||
| `INT p_delenv(TEXT *pMatch);` | Delete matching env var. |
|
||||
| `INT p_delenviron(TEXT *pMatch, INT mLength);` | Delete env var (length-specified match). |
|
||||
| `INT p_fndenv(TEXT *pMatch, TEXT *pName, TEXT *pValue, HANDLE *pHandle);` | Iterate/find env vars by name pattern. |
|
||||
| `INT p_findenviron(TEXT *pMatch, INT mLength, UBYTE *pBuf, HANDLE *pHandle);` | Iterate/find env vars (binary). |
|
||||
|
||||
---
|
||||
|
||||
## 9. Date, Time & Timers
|
||||
|
||||
System time = seconds since 00:00:00 1 Jan 1970 (`ULONG`). Day-based math uses
|
||||
`P_DAYSEC` (days since 1900 + seconds in day); human form uses `P_DATE`
|
||||
(year-1900, month 0–11, day 0–30, h/m/s, yrday). Declared in `p_date.h`.
|
||||
|
||||
### 9.1 System clock
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `ULONG p_date(VOID);` | Return current system time (seconds since 1970). |
|
||||
| `VOID p_sdate(ULONG newTime);` | Set system time (may fire due absolute timers). |
|
||||
|
||||
### 9.2 Sleeping / synchronous timers
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_sleep(ULONG n);` | Sleep `n` tenths of a second (relative timer). |
|
||||
| `INT p_sleept(LONG nTicks);` | Sleep `nTicks` system ticks (32/s SIBO). |
|
||||
| `INT p_sleepa(ULONG time);` | Sleep until absolute system `time` (wakes machine). |
|
||||
|
||||
*(Asynchronous timers are driven through an open `TIM:` channel via
|
||||
`p_ioc(P_FRELATIVE)` / `p_ioc(P_FABSOLUTE)` and cancelled with `p_iow(...,P_FCANCEL)`.)*
|
||||
|
||||
### 9.3 Date/time conversions
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `VOID p_sttods(ULONG *pstim, P_DAYSEC *pds);` | System time → days-since-1900 + seconds-in-day. |
|
||||
| `INT p_dstost(P_DAYSEC *pds, ULONG *pstim);` | `P_DAYSEC` → system time. |
|
||||
| `INT p_dstodt(P_DAYSEC *pds, P_DATE *pdt);` | `P_DAYSEC` → `P_DATE` (fills yrday). |
|
||||
| `INT p_dttods(P_DATE *pdt, P_DAYSEC *pds);` | Validate `P_DATE` → `P_DAYSEC`. |
|
||||
| `INT p_dayinm(INT year, INT month);` | Days in given month (leap-aware); year since 1900. |
|
||||
| `INT p_wkday(ULONG nDay);` | Weekday (0=Mon … 6=Sun) for day-since-1900. |
|
||||
| `INT p_weekno(ULONG nDay);` | Week number 1–53 for day-since-1900. |
|
||||
|
||||
### 9.4 Localised name/text lookups
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_nmday(TEXT *buf, INT daynum);` | Language day name (0=Mon … 6). |
|
||||
| `INT p_nmdaya(TEXT *buf, INT daynum);` | Abbreviated day name. *(EPOC 3.18+)* |
|
||||
| `INT p_nmmon(TEXT *buf, INT monthnum);` | Language month name (0=Jan … 11). |
|
||||
| `INT p_nmmona(TEXT *buf, INT monthnum);` | Abbreviated month name. *(EPOC 3.18+; OCR prints this as a second `p_nmmon`.)* |
|
||||
| `VOID p_getsuffixes(TEXT *buf);` | Fill array of 31 day-of-month suffixes (3-byte elements). |
|
||||
| `VOID p_getampmtext(TEXT *buf, INT n);` | am (`n=0`) / pm (`n=1`) suffix text. |
|
||||
| `VOID p_getctd(E_CONFIG *pcfg);` | Copy system locale/config (`E_CONFIG`) struct. |
|
||||
|
||||
---
|
||||
|
||||
## 10. Object-Oriented Programming Primitives (brief)
|
||||
|
||||
PLIB provides run-time OOP: categories (load modules of classes) identified by
|
||||
category number or `HANDLE`; objects are heap instances with a class header.
|
||||
`f_*` twins leave with `p_leave` on failure. Declared with the OOP chapter.
|
||||
|
||||
### 10.1 Category / object creation & class management
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `HANDLE p_getlibh(INT catNum);` | Convert a category number to a category handle (0 = local). |
|
||||
| `VOID *p_new(INT catNum, INT classNum);` | Create an instance of `classNum` in category `catNum`; property zeroed. |
|
||||
| `VOID *f_new(INT catNum, INT classNum);` | As `p_new`, leaves on OOM. |
|
||||
| `VOID *p_newlibh(HANDLE catHandle, INT classNum);` | Create instance by category handle. |
|
||||
| `VOID *f_newlibh(HANDLE catHandle, INT classNum);` | As `p_newlibh`, leaves on OOM. |
|
||||
| `VOID *f_newsend(INT catNum, INT classNum, INT methodNum, ...);` | Create then send init `methodNum`; cleans up (destroy) on leave. |
|
||||
| `VOID *f_newlibhsend(HANDLE catHandle, INT classNum, INT methodNum, ...);` | As `f_newsend` but by category handle. |
|
||||
| `VOID p_reclass(INT catNum, INT classNum, VOID *pObject);` | Change an object's class (same property length). |
|
||||
| `VOID p_reclassbyhandle(HANDLE catHandle, INT classNum, VOID *pObject);` | Reclass by category handle. |
|
||||
| `INT p_loadfilelib(VOID *fcb, UINT n, HANDLE *pCatHandle, INT link);` | Load an external category (load module) and get its handle. |
|
||||
| `VOID p_linklib(HANDLE catHandle);` | Link a loaded category to the local process. |
|
||||
| `VOID p_ccpy(VOID *pTarget, VOID *pSource, UINT count);` | Copy `count` bytes (category/object helper copy). |
|
||||
|
||||
### 10.2 Message sending
|
||||
|
||||
`p_send*` use the stack (`CDECL`) convention; the `p_send2..5` / `f_send2..5`
|
||||
numbered variants use the faster register convention. `f_*` leaves with
|
||||
`p_leave(err)` if the method returns negative.
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_send(VOID *pObject, INT methodNum, ...);` | Dispatch `methodNum` to object, searching class then superclasses. |
|
||||
| `INT f_send(VOID *pObject, INT methodNum, ...);` | As `p_send`, leaves on negative return. |
|
||||
| `INT p_send2(VOID *pObject, INT methodNum);` … `p_send5(VOID *pObject, INT methodNum, VOID *p1, VOID *p2, VOID *p3);` | Register-convention sends with 0–3 extra args (`f_send2..5` twins). |
|
||||
| `INT p_supersend(VOID *pObject, INT methodNum, ...);` | Dispatch starting at the superclass of the calling method (`p_supersend2..5`). |
|
||||
| `INT p_entersend(VOID *pObject, INT methodNum, ...);` | Dispatch as if via `p_enter` (unwinds on `p_leave`) (`p_entersend2..5`). |
|
||||
| `INT p_exactsend(HANDLE catHandle, INT classNum, VOID *pObject, INT methodNum, ...);` | Dispatch starting at an explicitly named class. |
|
||||
|
||||
---
|
||||
|
||||
## 11. Related Error/Process Primitives (referenced above)
|
||||
|
||||
These appear alongside the categories above and are commonly used with them:
|
||||
|
||||
| Signature | Purpose |
|
||||
|---|---|
|
||||
| `INT p_enter(VOID *pfunc, ...);` | Enter a function under structured error trapping. |
|
||||
| `VOID p_panic(INT nPanic);` | Abort the process with a panic number (unrecoverable). |
|
||||
| `VOID p_exit(INT nReason);` | Graceful process termination. |
|
||||
|
||||
*(Full error-handling and process/IPC APIs are outside this core-PLIB scope but
|
||||
`p_enter`/`p_leave`/`p_panic` underpin the `f_*` allocator and sender twins.)*
|
||||
|
||||
---
|
||||
|
||||
### Uncertainty notes
|
||||
|
||||
- OCR corrected: `p_bcmp`/`p_bcmpi` (src `p_bemp`), `p_fcmp` (src `p_femp`),
|
||||
`p_pow` (src fragment `p (…)1_POw`), `p_nmmona` (src duplicate `p_nmmon`).
|
||||
- `p_altchk`, `p_dequed`, `p_sgcreate`, `p_sgopen`, `p_sgfind` are named in the
|
||||
manual but their clean C signatures are not isolated in the source text; marked **[?]**.
|
||||
- Version-gated functions annotated where the manual states a minimum EPOC version.
|
||||
@@ -0,0 +1,725 @@
|
||||
# Psion SIBO / Workabout MX — File System and DBF Database API Reference
|
||||
|
||||
Source: *PLIB Reference*, chapters **Files** (ch. 11) and **Database Files** (ch. 14), with
|
||||
corroborating structural detail from *EPOC O/S System Services*, chapter 20 (*Database File
|
||||
Management*). Everything below is drawn from those manuals; page-anchored citations are given as
|
||||
`[PLIB p.NNN]` / `[SysSvc §20]`. OCR-garbled items are flagged explicitly.
|
||||
|
||||
> **OCR caveat.** The source is scanned OCR text. Identifiers written `p_open`, function-name
|
||||
> casing, and hex constants have been normalised to their obvious intended form. Where a
|
||||
> *signature* itself is corrupted or truncated in the source, this is called out inline. Do not
|
||||
> treat a normalised name as a guarantee of exact header spelling — verify against `p_file.h` /
|
||||
> `p_dbf.h` before compiling.
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — The File System
|
||||
|
||||
### 1.1 The file server and file systems (nodes)
|
||||
|
||||
- All file operations are performed by a high-priority system process, the **file server**
|
||||
(process name `SYS$FSRV`). A process must connect to it before use; the C startup module
|
||||
normally does this automatically. Sending a message to the file server without connecting
|
||||
panics with panic number 41. [PLIB p.117]
|
||||
- The file server supports multiple **file systems**, also called **nodes**. `p_open` can list
|
||||
them. Three were implemented at the time of writing [PLIB p.117]:
|
||||
- `LOC::` — the local filing system, with the RAM drive `M:` and SSD drives `A:`, `B:`, …
|
||||
(count depends on hardware).
|
||||
- `REM::` — the remote filing system (present only while connected to a remote file server).
|
||||
- `ROM::` — the ROM filing system for ROM-based files; normally invisible to the user; does
|
||||
**not** support devices or directories.
|
||||
- Within `LOC::` (and `REM::` when the remote end is a PC or another SIBO machine), the device
|
||||
and directory structure is MSDOS-compatible. [PLIB p.117]
|
||||
|
||||
**Media types (SSDs).** RAM SSDs (battery-backed static RAM, block-structured, not buffered on
|
||||
SIBO) and Flash SSDs (linked variable-length records). Overwriting or deleting on Flash consumes
|
||||
space that is only reclaimed by reformatting; a byte can be physically overwritten with **no**
|
||||
storage penalty only if the new value is derived from the old by clearing bits to zero. This is
|
||||
the property the DBF layer exploits. [PLIB pp.118–120, 195]
|
||||
|
||||
### 1.2 File specification structure
|
||||
|
||||
A full file specification has the form [PLIB p.120]:
|
||||
|
||||
```
|
||||
<node><device><dir><name><ext>
|
||||
```
|
||||
|
||||
Example: `LOC::B:\NOTES\OLD\PLANS.TPD`, where:
|
||||
|
||||
| Component | Meaning | Example |
|
||||
|-----------|----------------------|-----------------|
|
||||
| `<node>` | file system node | `LOC::` |
|
||||
| `<device>`| device name | `B:` |
|
||||
| `<dir>` | directory name | `\NOTES\OLD\` |
|
||||
| `<name>` | file name | `PLANS` |
|
||||
| `<ext>` | extension name | `.TPD` |
|
||||
|
||||
Rules [PLIB p.120]:
|
||||
|
||||
- A file specification never exceeds `P_FNAMESIZE` (**128**) bytes including the zero terminator.
|
||||
- The `<node>` component is always `P_FSYSNAMESIZE` bytes long (excluding any zero terminator).
|
||||
- Beyond `<node>` and the `P_FNAMESIZE` total, make **no** assumptions about component sizes; the
|
||||
syntax of `<device>`/`<dir>`/`<name>`/`<ext>` is owned by the node's file-system code (foreign
|
||||
remote systems can map onto this model, e.g. VMS or Mac paths). Use `p_fparse`/`p_chdir` rather
|
||||
than manipulating specs by hand.
|
||||
|
||||
**Default path.** The file server stores a per-client default path (`<node><device><dir>`) plus a
|
||||
system-wide default assigned to new clients. Manipulate with:
|
||||
`p_setpth` / `p_setpthasync` (set this process's default), `p_getpth` (get it),
|
||||
`p_getpthbyid` (get another process's), `p_setdefaultpath` (set the system-wide default).
|
||||
[PLIB pp.121, 126]
|
||||
|
||||
**Parsing / directory manipulation.**
|
||||
- `INT p_fparse(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *perk);` — builds a full spec into
|
||||
`full` (reserve `P_FNAMESIZE` bytes; `P_FNAMESIZE` bytes are always written). Components are
|
||||
taken from `name`, then `related` (may be `NULL`), then the default path, in that order of
|
||||
precedence. Output is upper-cased. `perk` (may be `NULL`) receives a `P_FPARSE` struct with the
|
||||
lengths of each component and a wildcard-flags byte (`P_PWILD_ANY`, `P_PWILD_NAME`,
|
||||
`P_PWILD_EXT`). `f_fparse` is identical but calls `p_leave` on error instead of returning it;
|
||||
`p_fparseasync` adds a trailing `WORD *stat`. [PLIB pp.123–124]
|
||||
- `INT p_chdir(TEXT *src, TEXT *outp, INT mode, TEXT *subdir);` — parse `src` and change its
|
||||
directory per `mode`: `P_CD_ROOT`, `P_CD_PARENT`, or `P_CD_SUBDIR` (append zero-terminated
|
||||
`subdir`). Reserve `P_FNAMESIZE` at `outp`. Async form `p_chdirasync`. [PLIB p.125]
|
||||
|
||||
### 1.3 `p_open` — modes, formats, and access flags
|
||||
|
||||
Channel-based file services all go through `p_open`:
|
||||
|
||||
```
|
||||
INT p_open(VOID **ppfcb, TEXT *name, UINT mode);
|
||||
```
|
||||
|
||||
On success returns 0 and writes the channel handle to `*ppfcb` (not written on failure). `name` is
|
||||
parsed with a `NULL` related name. [PLIB p.142]
|
||||
|
||||
`mode` is a bitwise-OR of exactly **one open mode**, exactly **one format**, and any combination of
|
||||
**access flags**.
|
||||
|
||||
#### Open modes (choose exactly one) [PLIB pp.142–143]
|
||||
|
||||
| Mode | Behaviour |
|
||||
|--------------|-----------|
|
||||
| `P_FOPEN` | Open an existing file. `E_FILE_NXIST` if it does not exist. Normally used for read access. |
|
||||
| `P_FCREATE` | Create a file that must **not** already exist. `E_FILE_EXIST` if it does. Requires `P_FUPDATE` for write. |
|
||||
| `P_FREPLACE` | If the file exists, open and truncate to zero length; otherwise create it. Requires `P_FUPDATE` for write. |
|
||||
| `P_FAPPEND` | Same as `P_FOPEN` but initial position is at end of file so the next write appends. `P_FRANDOM` not needed; `P_FUPDATE` needed for write. |
|
||||
| `P_FUNIQUE` | Create a unique file, using the passed path as the related path; the unique name is written back to `name` (reserve `P_FNAMESIZE` bytes). `P_FUPDATE` not needed. |
|
||||
|
||||
#### Formats (choose exactly one) [PLIB pp.142, 146–147; §list p.121]
|
||||
|
||||
| Format | Meaning |
|
||||
|-------------------|---------|
|
||||
| `P_FSTREAM` | Flat binary file. |
|
||||
| `P_FSTREAM_TEXT` | Flat binary file, but declares the file is text; on `REM::` it makes the remote side present/parse CRLF-terminated records. Locally usually identical to `P_FSTREAM`; no penalty, potential gain on remote. Prefer this for self-processed text files. |
|
||||
| `P_FTEXT` | Record-oriented text file. Implemented as a client-side layer (the `TXT:` device) over `P_FSTREAM_TEXT`; data is buffered in that layer, so flushing is needed. |
|
||||
| `P_FDIR` | Open a directory-listing channel (list files/subdirectories). |
|
||||
| `P_FDEVICE` | Open a device-listing channel (list devices of a node). |
|
||||
| `P_FNODE` | Open a node-listing channel (list file systems). |
|
||||
| `P_FFORMAT` | Open a device-format channel (`LOC::` only at time of writing; OR in `P_FLOWDENSITY` for dual-density low-density format). |
|
||||
|
||||
#### Access flags (combine as needed) [PLIB p.143]
|
||||
|
||||
| Flag | Meaning |
|
||||
|-------------|---------|
|
||||
| `P_FUPDATE` | Write access as well as read. Writing without it → `E_FILE_RDONLY`. |
|
||||
| `P_FRANDOM` | Random access required; needed to use `p_seek`. Without it, `p_seek` → `E_FILE_INV`. Do not specify unless you will seek (the FS may optimise sequential-only access). |
|
||||
| `P_FSHARE` | Do not block the file from being re-opened for **read** access. Without it, a later open → `E_FILE_LOCKED`. **Cannot** be combined with `P_FUPDATE` (shared *write* is unsupported). |
|
||||
|
||||
**Sharing rules:** any number of processes may open the same file for reading only, provided all
|
||||
readers specify `P_FSHARE`. Multiple writers are never allowed. Once open for reading it may be
|
||||
re-opened for reading but not writing; once open for writing it may not be re-opened at all.
|
||||
[PLIB p.140]
|
||||
|
||||
**Examples** [PLIB p.140]:
|
||||
```c
|
||||
p_open(&fcb, "fred.dat", P_FSTREAM | P_FSHARE); /* read only */
|
||||
p_open(&fcb, "fred.dat", P_FSTREAM | P_FUPDATE | P_FREPLACE | P_FRANDOM);/* writable */
|
||||
```
|
||||
|
||||
Selected `p_open` errors: `E_GEN_NOMEMORY`, `E_GEN_ARG` (illegal flag combination),
|
||||
`E_FILE_DEVICE`, `E_FILE_NOTREADY`, `E_FILE_EXIST`, `E_FILE_NXIST`, `E_FILE_ACCESS`,
|
||||
`E_FILE_DIRFULL`, `E_FILE_PROTECT`, `E_FILE_FULL`, `E_FILE_LOCKED`, `E_FILE_DIR`, plus any
|
||||
`p_fparse` error. [PLIB pp.143–144]
|
||||
|
||||
### 1.4 Reading and writing streams
|
||||
|
||||
```
|
||||
INT p_read (VOID *pfcb, VOID *buf, UINT len);
|
||||
INT p_write(VOID *pfcb, VOID *buf, UINT len);
|
||||
INT p_close(VOID *pfcb);
|
||||
```
|
||||
|
||||
- `p_read` reads `len` bytes (or the bytes remaining before EOF, whichever is smaller) from the
|
||||
current position; returns the number of bytes read. At EOF, reads 0 bytes and returns
|
||||
`E_FILE_EOF`. `len` must not exceed `P_FMAXSSIZE` (**16K**). Most efficient in multiples of
|
||||
`P_FBLKSIZE` (**512**) on 512-byte boundaries. Other errors: `E_FILE_ABORT`, `E_FILE_READ`.
|
||||
[PLIB p.144]
|
||||
- `p_write` writes `len` bytes at the current position; increments position by bytes written;
|
||||
returns 0 on success. `len` ≤ `P_FMAXSSIZE`. On Flash you may overwrite a single byte (`len==1`)
|
||||
if the new byte only clears bits (then the modification date is not changed). Errors:
|
||||
`E_FILE_FULL`, `E_FILE_RDONLY`, `E_FILE_ABORT`, `E_FILE_WRITE`. [PLIB p.144]
|
||||
- `p_close(NULL)` is harmless (returns 0). Close may perform a final buffered write and a date
|
||||
update, so it can return `p_write`/`p_fdate` errors — but it always closes the channel. To
|
||||
handle failures cleanly, `p_iow(pfcb, P_FFLUSH)` first. `LOC::` on SIBO does not buffer written
|
||||
data; EPOC-on-PC does. [PLIB p.143]
|
||||
|
||||
**`p_iow` control operations** on a binary/text channel [PLIB pp.145–146]:
|
||||
- `INT p_iow(VOID *pfcb, P_FFLUSH);` — flush buffered data and write the modification date.
|
||||
- `INT p_iow(VOID *pfcb, P_FSETEOF, ULONG *peof);` — set the logical EOF to `*peof` (requires
|
||||
`P_FUPDATE`). Extending pre-allocates storage on block devices; truncating also reduces the
|
||||
current position if needed.
|
||||
- `INT p_iow(VOID *pfcb, P_FCANCEL);` — cancel pending async requests (not actively supported; a
|
||||
no-op even when a request is pending — simulate a cancel instead).
|
||||
|
||||
### 1.5 Seeking
|
||||
|
||||
**Binary / stream:**
|
||||
```
|
||||
INT p_seek(VOID *pfcb, INT sense, LONG *ppos);
|
||||
```
|
||||
`sense` is one of [PLIB p.144]:
|
||||
|
||||
| `sense` | Meaning |
|
||||
|-----------|---------|
|
||||
| `P_FABS` | set position to `*ppos` |
|
||||
| `P_FEND` | set position to `*ppos` relative to end-of-file |
|
||||
| `P_FCUR` | set position to `*ppos` relative to current position |
|
||||
|
||||
On success the new position is written back to `*ppos` and 0 returned. `E_FILE_INV` if the channel
|
||||
was not opened with `P_FRANDOM`. A negative resulting position clamps to the start; beyond EOF
|
||||
clamps to EOF (use `P_FSETEOF` to extend). Setting `*ppos=0` with `P_FCUR` senses the current
|
||||
position; with `P_FEND` senses the file length. `f_seek` is the `p_leave`-on-error variant. On
|
||||
`P_FSTREAM_TEXT` remote channels, only "seek to 0 (`P_FABS`)" and "seek to end (`P_FEND`, rel 0)"
|
||||
are guaranteed. [PLIB pp.144–145]
|
||||
|
||||
**Record-oriented text (`P_FTEXT`):** `p_seek` sets the **read** position only (writes always go to
|
||||
EOF), and `sense` is different [PLIB pp.149]:
|
||||
|
||||
| `sense` | Meaning |
|
||||
|--------------|---------|
|
||||
| `P_FREWIND` | position to the first record (`ppos` ignored) |
|
||||
| `P_FRSENSE` | get the position of the last record read/written |
|
||||
| `P_FRSET` | set the record position previously got with `P_FRSENSE` |
|
||||
|
||||
Requires `P_FRANDOM` (else `E_FILE_INV`). Some remote systems may not support `P_FRSET`/`P_FRSENSE`.
|
||||
|
||||
### 1.6 Text records (`P_FTEXT`)
|
||||
|
||||
Convention: records are terminated by CRLF (CR = 13, LF = 10); a file may optionally end with SUB
|
||||
(26). On read, the parser also accepts CR, LF, or LFCR as a terminator and treats a SUB as EOF; on
|
||||
write it appends CRLF only (no SUB). Record content must not exceed `P_FMAXRSIZE` (**256**) bytes
|
||||
and must not contain CR, LF, or SUB. [PLIB pp.146–148]
|
||||
|
||||
- `p_read` returns the record length (bytes written to `buf`), positioning to the next record. If
|
||||
`len` < record length, the first `len` bytes are read and `E_FILE_RECORD` returned (still
|
||||
advances). Returns 0 on a zero-length record; `E_FILE_EOF` past the last record. [PLIB p.148]
|
||||
- `p_write` writes a record of `len` bytes (0 … `P_FMAXRSIZE`); always appended at EOF; must not
|
||||
contain delimiters. Errors incl. `E_FILE_RECORD` if oversize. [PLIB p.149]
|
||||
|
||||
### 1.7 Directory, device, and node listing
|
||||
|
||||
All three use the open → repeated `p_iow(..., P_FREAD, ...)` until `E_FILE_EOF` → close pattern.
|
||||
|
||||
- **Nodes:** `p_open(&ncb, "FIL:" or NULL, P_FNODE)`; each `p_iow(ncb, P_FREAD, buf, pinfo)`
|
||||
writes the next node name (`buf` capacity `P_FSYSNAMESIZE+1`, i.e. 6). Optional `pinfo`
|
||||
(`P_NINFO*`) gets the same info as `p_ninfo`. [PLIB p.127]
|
||||
- **Devices:** `p_open(&dcb, node-name, P_FDEVICE)`; `E_GEN_FSYS` if the node is bad,
|
||||
`E_GEN_NSUP` if the node has no devices (e.g. `ROM::`). Each `p_iow(P_FREAD)` writes the next
|
||||
device name (`buf` capacity `P_FNAMESIZE`); trailing arg `NULL`. [PLIB p.128]
|
||||
- **Files:** `p_open(&dcb, name, P_FDIR)`; `name` is parsed with a wildcard related name (`*.*`),
|
||||
so a name of `""` lists the current directory. Each `p_iow(P_FREAD, buf, pinfo)` writes the next
|
||||
matching file name (excluding node/device/dir; `buf` capacity `P_FNAMESIZE`). Optional
|
||||
`pinfo` (`P_INFO*`) carries per-file info. A root directory of a PC-based device may return a
|
||||
volume-name entry with `P_FAVOLUME` set. [PLIB pp.133–134]
|
||||
|
||||
`P_INFO` (from `p_file.h`) [PLIB p.134]:
|
||||
```c
|
||||
typedef struct {
|
||||
UWORD version;
|
||||
UWORD status; /* status bits */
|
||||
ULONG size; /* size of the file in bytes (end-of-file position) */
|
||||
ULONG modst; /* system time of last modification (secs since 1970-01-01) */
|
||||
UBYTE spare[4];
|
||||
} P_INFO;
|
||||
```
|
||||
`status` bit fields: `P_FAWRITE` (not read-only), `P_FAMOD` (modified), `P_FAHIDDEN`,
|
||||
`P_FASYSTEM`, `P_FADIR` (directory file), `P_FAVOLUME` (volume-name directory), `P_FATEXT`
|
||||
(recognised text file; `LOC::` cannot recognise text files). [PLIB p.134]
|
||||
|
||||
### 1.8 File / device / node information and non-channel operations
|
||||
|
||||
- `INT p_finfo(TEXT *name, P_INFO *pinfo);` — info on one file/directory (same fields as a `P_FDIR`
|
||||
read). Good atomic existence check (`E_FILE_NXIST` if absent). Async `p_finfoasync`. [PLIB p.135]
|
||||
- `INT p_testpth(TEXT *dname);` — returns 0 if the directory component exists. [PLIB p.135]
|
||||
- `INT p_ninfo(TEXT *node, P_NINFO *pninfo);` — node info. `P_NINFO { UWORD version; UWORD type;
|
||||
UWORD formattable; UBYTE spare[26]; }`. `type` is `P_FSYSTYPE_FLAT` or `P_FSYSTYPE_HIER`.
|
||||
[PLIB pp.127–128]
|
||||
- `INT p_dinfo(TEXT *dname, P_DINFO *pdinfo);` — device + mounted-medium info.
|
||||
`P_DINFO { UWORD version; UWORD mediatype; UWORD removable; ULONG size; ULONG free;
|
||||
UBYTE name[P_VOLUMENAME]; WORD batterystate; UBYTE spare[16]; }`. Low byte of `mediatype`:
|
||||
`P_FMEDIA_UNKNOWN|FLOPPY|HARDDISK|RAM|FLASH|ROM|WRITEPROTECTED`; high byte flags incl.
|
||||
`P_FMEDIA_COMPRESSIBLE` (worth compressing out deleted records — **not** set for Flash),
|
||||
`P_FMEDIA_DYNAMIC`, `P_FMEDIA_INTERNAL`, `P_FMEDIA_DUAL_DENSITY`, `P_FMEDIA_FORMATTABLE`.
|
||||
[PLIB pp.129–130]
|
||||
- Other non-channel calls (each with an `…async` twin): `p_rename`, `p_delete` (directory must be
|
||||
empty), `p_mkdir` (creates intermediate dirs), `p_sfstat` (set attributes; with `P_FAVOLUME` set
|
||||
in the mask, sets/deletes the volume label), `p_fdate` (set modification date, ≥ 1980-01-01).
|
||||
[PLIB pp.136–139]
|
||||
- Formatting: open with `P_FFORMAT` then repeatedly `p_read` (first read yields a `UWORD` total
|
||||
count, subsequent reads step the format, `E_FILE_EOF` when done). Aborting early corrupts the
|
||||
medium. [PLIB pp.131–132]
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — The DBF Database API
|
||||
|
||||
### 2.1 The model
|
||||
|
||||
A **database file (DBF)** is a binary file of **typed, variable-length records**. Used by MC Diary,
|
||||
Series 3 Database, and OPL data files. DBFs are **Flash-friendly**: records can be appended,
|
||||
deleted, or replaced in place on a Flash SSD without rewriting the whole file. [PLIB p.195]
|
||||
|
||||
Key model properties [PLIB pp.195–197, 204–207]:
|
||||
|
||||
- **Record type 0 = deleted.** Deleting a record only overwrites its 4-bit type field with zero
|
||||
(which merely clears bits — legal on Flash). Therefore **deleting does not shrink the file.**
|
||||
- **Append-only; updates = erase + append.** Updating a record deletes the original and appends
|
||||
the modified version, so an update **always moves the record to the end of the file**.
|
||||
- **Reclaiming space:** `DbfCompress` (only on a *compressible* medium — not Flash) or copy the
|
||||
file record-by-record with `DbfCopyFile` (deleted records are not copied).
|
||||
- **Sparse index** (optional): one 4-byte address per **sixteenth** record, held in a *separate
|
||||
segment* (`DBF$nnnn.INX`, `nnnn` a 4-hex-digit number from the channel) so it does not consume
|
||||
the app's data space. Enables fast random access and fast backward scans; adjusted on
|
||||
append/delete so it always points to every 16th record.
|
||||
- **Read-ahead buffer:** each file-server read fills a caller-supplied buffer (typically 4K),
|
||||
usually pulling in many records; a read for a record already in the buffer just locates it.
|
||||
**Most DBF services may overwrite the buffer.** The next read after the buffer is disturbed
|
||||
re-reads the whole buffer (a performance hit), so any direct modification of the buffer that is
|
||||
*not* done via a DBF service must be followed by `DbfTrash`. Services guaranteed **not** to
|
||||
alter the buffer: `DbfFlush`, `DbfVersion`, `DbfAppend`, `DbfSense`, `DbfCount`. [PLIB p.196]
|
||||
|
||||
**Record limit:** max **65534** records *of any one visible type*, numbered 0…65533. Since only one
|
||||
type is visible per open, a file may hold more in aggregate. A file with more than the max (of the
|
||||
visible type) is logically truncated to the max on open. [PLIB p.197]
|
||||
|
||||
**End-of-file record.** Reading past the last record returns `E_FILE_EOF`; the current record
|
||||
number (per `DbfSense`) then becomes *last record + 1* — the fictitious **end-of-file record**.
|
||||
Reading before the first record (`DbfBackRead`/`DbfFindRead` backwards) also gives `E_FILE_EOF`
|
||||
with current record number 0. If the file has no records, `DbfSense` always returns 0. Services
|
||||
that operate on the current record (`DbfEraseRead`, `DbfUpdate`, …) do nothing and return
|
||||
`E_FILE_EOF` when positioned on the EOF record. [PLIB p.197]
|
||||
|
||||
### 2.2 The file header
|
||||
|
||||
DBFs begin with a **22-byte** standard header [PLIB p.196; SysSvc §20]:
|
||||
|
||||
| Byte offset | Contents |
|
||||
|-------------|----------|
|
||||
| 0–15 | Zero-terminated file signature (all 16 bytes are verified — pad with trailing zeros). |
|
||||
| 16, 17 | Version of DBF software used to produce the file. |
|
||||
| 18, 19 | Offset from start of file to the first record. |
|
||||
| 20, 21 | Minimum version of DBF software required. |
|
||||
|
||||
The first-record offset allows an **extended header** (application-specific data after the standard
|
||||
header). If none, the value is **22**. The first record is always the type-2 field information
|
||||
record. Version-number format: see `DbfVersion`. [PLIB p.196; SysSvc §20]
|
||||
|
||||
### 2.3 Records and the record header word
|
||||
|
||||
In memory, a record is a `DbfRecord` (`p_dbf.h`) [PLIB p.196]:
|
||||
```c
|
||||
typedef struct {
|
||||
UWORD header; /* record header word */
|
||||
UBYTE data[2]; /* data to be written... (variable length in practice) */
|
||||
} DbfRecord;
|
||||
```
|
||||
The **header word**: top **4 bits** = record **type** (0–15); low **12 bits** = record **length**.
|
||||
Maximum record length is **4094** bytes (one less than the theoretical 0xFFF = 4095, so a 4094-byte
|
||||
record plus its 2-byte header fits a 4096-byte buffer). [PLIB p.196; SysSvc §20]
|
||||
|
||||
**Record types** [PLIB pp.196–197; SysSvc §20]:
|
||||
|
||||
| Type | Meaning |
|
||||
|-------|---------|
|
||||
| 0 | Deleted record — ignored by all DBF services; never copied by `DbfCopyFile`. |
|
||||
| 1 | Standard data record (fields per the field information record). Usually the only visible type. |
|
||||
| 2 | **Field information record (FIR)** — must exist and be the first record; later type-2 records ignored. |
|
||||
| 3 | Descriptive record — optional, file-wide app data (see `DbfDescRecordRead/Write`). |
|
||||
| 4–7 | App-specific: copied to a **new** file, **not** appended to an existing file, by `DbfCopyFile`. |
|
||||
| 8–13 | App-specific: **both** copied to a new file and appended to an existing file (merged) by `DbfCopyFile`. |
|
||||
| 14 | Reserved for voice records. |
|
||||
| 15 | Reserved for internal use — do not use. |
|
||||
|
||||
### 2.4 The Field Information Record (FIR) and field types
|
||||
|
||||
The FIR (type 2) holds up to **32 bytes**, one per field, giving each field's type [PLIB p.197;
|
||||
SysSvc §20]:
|
||||
|
||||
| Byte value | Field type |
|
||||
|------------|------------|
|
||||
| 0 | Word |
|
||||
| 1 | Long |
|
||||
| 2 | Double |
|
||||
| 3 | String |
|
||||
| 4–255 | Reserved |
|
||||
|
||||
So a data record has at most 32 fields — **with one exception:** a record that contains **only
|
||||
string fields** is not bound by the 32-field limit and may hold any number of fields, subject to
|
||||
the 4094-byte record cap. Records may contain **fewer** fields than the FIR lists, provided only
|
||||
*trailing* fields are omitted; records with **more** fields than the FIR are assumed to have the
|
||||
extra ones as string fields. Only `DbfFindRead`/`DbfFindReadField` assume records match the FIR.
|
||||
[PLIB p.197]
|
||||
|
||||
### 2.5 String fields (leading byte count) and continuation sub-fields
|
||||
|
||||
A string field is **leading byte-counted text**, so a normal string field holds at most **255**
|
||||
characters. Longer strings use **continuation sub-fields**: the first 254 characters plus a
|
||||
terminating byte `0x14`, stored in a string field whose count byte is **255**; the `0x14` +
|
||||
count-255 signals that the immediately following string field continues the text. This chains
|
||||
indefinitely, subject only to the 4094-byte record limit. [PLIB p.197]
|
||||
|
||||
### 2.6 The structs
|
||||
|
||||
`DbfHeader` (`p_dbf.h`) — passed to open [PLIB p.199]:
|
||||
```c
|
||||
typedef struct {
|
||||
UBYTE fileType[DbfHeaderNameSize]; /* 16-byte file signature */
|
||||
UWORD createVersion; /* software version used to create the file */
|
||||
UWORD dataStart; /* offset in file of first record (22 if no ext header) */
|
||||
UWORD needVersion; /* minimum software version needed to handle this file */
|
||||
UWORD firHeader; /* header word for the field information record */
|
||||
UBYTE fir[DbfMaxFirLength]; /* the field information record bytes */
|
||||
} DbfHeader;
|
||||
```
|
||||
- When **creating/replacing**, pre-fill *all* elements (header + FIR). There is no gap between the
|
||||
header and FIR in the struct even when an extended header is used; the *file* leaves a gap of
|
||||
`dataStart − 22` bytes for it.
|
||||
- When **opening existing**, pre-fill only `fileType` (the signature); the rest is filled from the
|
||||
file. All 16 signature bytes are verified — mismatch → `E_FILE_INVALID`.
|
||||
- `DbfHeaderNameSize` = 16 (implied by "16-byte file signature"). `DbfMaxFirLength` is the FIR
|
||||
capacity; the FIR maximum length is 32. *(Exact numeric value of `DbfMaxFirLength` not stated
|
||||
verbatim in the source beyond the max-32 rule — verify in `p_dbf.h`.)*
|
||||
|
||||
`DbfOpenArgs` (`p_dbf.h`) — for `DbfQuickOpen` [PLIB p.200]:
|
||||
```c
|
||||
typedef struct {
|
||||
VOID **pFcb;
|
||||
UBYTE *fName;
|
||||
UINT mode;
|
||||
DbfHeader *pHead;
|
||||
} DbfOpenArgs;
|
||||
```
|
||||
|
||||
`DbfRecord` — see §2.3.
|
||||
|
||||
### 2.7 The DBF functions
|
||||
|
||||
Every DBF function calls `p_panic` if `pFcb` is not a valid DBF channel from `DbfOpen`/
|
||||
`DbfQuickOpen`; that clause is omitted per-entry below. Unless noted "may be used on a DBF opened
|
||||
without an index", the *without-index* behaviour is called out where the manual specifies it.
|
||||
|
||||
#### Opening / closing / flushing
|
||||
|
||||
- `INT DbfOpen(INT *pstate, VOID **pFcb, TEXT *fName, UINT mode, DbfHeader *pHead, UBYTE *pbuffer, UINT len, UINT type);`
|
||||
Open a DBF channel. `mode` = exactly one of `P_FOPEN`/`P_FCREATE`/`P_FREPLACE`/`P_FAPPEND`/
|
||||
`P_FUNIQUE`, optionally OR'd with `P_FUPDATE` and/or `P_FSHARE` (other required stream flags are
|
||||
supplied automatically; `P_FOPEN` and `P_FAPPEND` are treated identically). `pbuffer`/`len` is
|
||||
the caller's read-ahead buffer: **len must be 512…16384** (else `E_FILE_RECORD`), and must be at
|
||||
least as large as the largest record (4096 is guaranteed sufficient). Only records of type `type`
|
||||
are visible (normally 1; may be 4–14; results undefined for 0/2/3/>14). `*pstate` selects the
|
||||
open strategy [PLIB pp.198–199]:
|
||||
- `DbfStateDisabled` — open with a full sparse index; returns only when the index is fully built
|
||||
(may take a while).
|
||||
- `DbfStateOpenNoIndex` — open **without** an index; returns fast, but some services are then
|
||||
disallowed (see each function).
|
||||
- `DbfStateStart` — open with a sparse index incrementally: call `DbfOpen` repeatedly, feeding
|
||||
back `*pstate` each time, until `*pstate` becomes `DbfStateStart` again *(as printed — the
|
||||
source states the loop terminates when the written-back value is `DbfStateStart`; this reads
|
||||
like an OCR/spec inconsistency, likely intended to be a distinct "finished" state such as
|
||||
`DbfStateDisabled`/`DbfStateEnd`. **Flag: verify the terminating state in `p_dbf.h`.**)*.
|
||||
|
||||
After a successful open the current record is **0**, so `DbfNextRead` reads record 1 and
|
||||
`DbfEraseRead` erases record 0; use `DbfFirstRead` to read record 0. Errors: those of
|
||||
`p_open(P_FSTREAM)`, `p_seek`, `p_read`, plus `E_FILE_INVALID` (bad signature / bad FIR),
|
||||
`E_FILE_RECORD` (bad buffer length). [PLIB p.199]
|
||||
|
||||
- `INT DbfQuickOpen(INT *pstate, DbfOpenArgs *pargs, UBYTE *pbuffer, UINT len, UINT type);`
|
||||
As `DbfOpen` but `pFcb`/`fName`/`mode`/`pHead` are bundled in `DbfOpenArgs`. **Preferred** over
|
||||
`DbfOpen` (more efficient, shorter code); `DbfOpen` retained for compatibility. [PLIB p.200]
|
||||
|
||||
- `INT DbfClose(VOID *pFcb);` — close the file (returns as `p_close`); channel is closed even on
|
||||
error. May be used without an index. [PLIB p.200]
|
||||
|
||||
- `INT DbfFlush(VOID *pFcb);` — flush all buffers so modified data is written (returns as
|
||||
`p_write`). Does not alter the buffer. May be used without an index. [PLIB p.200]
|
||||
|
||||
- `VOID DbfTrash(VOID *pFcb);` — tell the DBF layer the read-ahead buffer was overwritten by the
|
||||
caller and can no longer be relied on. May be used without an index. [PLIB p.200]
|
||||
|
||||
- `INT DbfCopyDown(VOID *pFcb, UINT offset);` — copy the record at `offset` in the buffer to the
|
||||
start of the buffer (and flag the buffer invalid, so no `DbfTrash` needed); returns the record
|
||||
length. `offset` must be one previously returned by a read service and the caller must not have
|
||||
written to the buffer since. May be used without an index. [PLIB p.201]
|
||||
|
||||
#### Whole-file operations
|
||||
|
||||
- `INT DbfCompress(UINT *pstate, VOID *pFcb);` — reclaim space from deleted records **if** the
|
||||
medium is compressible (otherwise a no-op that still returns 0). After a real compress the
|
||||
current record is the EOF record. `*pstate` = `DbfStateDisabled` (blocking) or `DbfStateStart`
|
||||
(incremental loop, terminating as written back). **Should not** be used without an index.
|
||||
[PLIB p.201]
|
||||
|
||||
- `INT DbfCopyFile(UINT *pstate, VOID *pFcb, TEXT *pTargetName, UINT targetMode, UINT type, INT dir);`
|
||||
Copy non-deleted records between the current file and `pTargetName`. `targetMode` = same options
|
||||
as open (target always opened without an index; `P_FUNIQUE` writes back the unique name).
|
||||
`type` selects a single type (usually 1) or `DbfRecordTypeAll` for all types. `dir` is
|
||||
`DbfCopyFromHandle` (current → target; target new file with `P_FCREATE`/`P_FREPLACE`/`P_FUNIQUE`,
|
||||
or append with `P_FOPEN`/`P_FAPPEND`) or `DbfCopyToHandle` (target → current; target sensibly
|
||||
`P_FOPEN`). Copying to a **new** file always copies the FIR (type 2) and the file header
|
||||
(incl. extended header) regardless of `type`; **appending** never copies types 2–7. Appending
|
||||
requires matching signatures and compatible FIRs (identical, or both string-only) else
|
||||
`E_FILE_INVALID`. `*pstate` supports `DbfStateDisabled`, `DbfStateStart` (est. calls =
|
||||
file size / buffer size + 2), and `DbfStateCopyAbort` (abort an in-progress `DbfStateStart`
|
||||
copy). May be used without an index. **Warning:** appending can exceed 65534 records with no
|
||||
error. [PLIB pp.201–202]
|
||||
|
||||
- `INT DbfFileSize(VOID *pFcb, ULONG *pSize);` — write the open file's size to `*pSize`. May be
|
||||
used without an index. [PLIB p.202]
|
||||
|
||||
- `UINT DbfVersion(VOID);` — DBF software version as hex `xyyF`: `x` = major (4 bits), `yy` = minor
|
||||
(8 bits), `F` = release type A/B/F (Alpha/Beta/Final, 4 bits). E.g. `0x110F` → 1.10F. Only the
|
||||
major number gates whether a file can be handled. May be used without an index. [PLIB p.203]
|
||||
|
||||
#### Extended header and descriptive record
|
||||
|
||||
- `INT DbfExtHeaderRead(UINT cont, VOID *pFcb, VOID *buf, UINT len);` — read up to `len` bytes of
|
||||
the extended header; returns bytes read. `cont`: 0 = initial read (resets to start of ext
|
||||
header); 1 = continue; use 0 if reading the whole thing in one call. `E_FILE_EOF` at end. May be
|
||||
used without an index. [PLIB p.202]
|
||||
- `INT DbfExtHeaderWrite(UINT cont, VOID *pFcb, VOID *buf, UINT len);` — symmetric writer. May be
|
||||
used without an index. [PLIB pp.202–203]
|
||||
- `INT DbfDescRecordRead(VOID *pFcb);` — read the descriptive record to offset 0 of the read-ahead
|
||||
buffer; returns its length, or `E_FILE_EOF` if none, or `E_FILE_INVALID` if the file was opened
|
||||
without an index. Sub-records use the same word-header format as main records; ignore (do not
|
||||
delete) unrecognised sub-record types. [PLIB p.203]
|
||||
- `INT DbfDescRecordWrite(VOID *pFcb, UINT len);` — write a descriptive record from a `DbfRecord`
|
||||
at buffer offset 0 (content starts at offset 2; the leading 2 bytes form the header and are not
|
||||
counted in `len`). Any existing descriptive record is erased first (max one per file); `len==0`
|
||||
just erases it. `E_FILE_INVALID` if opened without an index. [PLIB p.203]
|
||||
|
||||
#### Reading records
|
||||
|
||||
All read services return the **record data length** (excluding the 2-byte header) and write the
|
||||
buffer **offset of the DbfRecord** (including its header) to `*pOffset`. The read record becomes
|
||||
the current record. On `E_FILE_EOF`, `*pOffset` is invalid.
|
||||
|
||||
- `INT DbfAbsRead(VOID *pFcb, UINT recnum, UWORD *pOffset);` — seek to and read record `recnum`.
|
||||
`E_FILE_EOF` if `recnum` > last record (current → EOF record). May be used without an index.
|
||||
[PLIB p.204]
|
||||
- `INT DbfAbsReadSense(VOID *pFcb, UINT recnum, UWORD *pOffset, ULONG *pPos);` — as `DbfAbsRead`
|
||||
but also writes the file position of the record header to `*pPos`. May be used without an index.
|
||||
[PLIB p.204]
|
||||
- `INT DbfNextRead(VOID *pFcb, UWORD *pOffset);` — read the next record. `E_FILE_EOF` if already on
|
||||
the last record / no records (current → EOF record). May be used without an index. [PLIB p.204]
|
||||
- `INT DbfBackRead(VOID *pFcb, UWORD *pOffset);` — read the previous record. `E_FILE_EOF` if on the
|
||||
first record / no records (current → record 0). May be used without an index. [PLIB p.204]
|
||||
- `INT DbfFirstRead(VOID *pFcb, UWORD *pOffset);` — read the first record. `E_FILE_EOF` if no
|
||||
records of the current type (current → 0 = EOF record). May be used without an index. [PLIB p.205]
|
||||
- `INT DbfLastRead(VOID *pFcb, UWORD *pOffset);` — read the last record. `E_FILE_EOF` if none.
|
||||
**Should not** be used without an index. [PLIB p.205]
|
||||
|
||||
#### Appending / erasing / updating
|
||||
|
||||
- `INT DbfAppend(VOID *pFcb, UINT len);` — append a record of the current type and length `len`
|
||||
(data only) to EOF and make it current; returns 0. The record must be at the **start** of the
|
||||
read-ahead buffer as a `DbfRecord` including its 2-byte header; `DbfAppend` builds the type/length
|
||||
header from those two bytes (not counted in `len`). `E_GEN_OVER` if already 65534 records of the
|
||||
type; `E_FILE_RECORD` if total (data + 2) exceeds the buffer. Does not alter the buffer.
|
||||
**Should not** be used without an index. [PLIB p.205]
|
||||
- `INT DbfEraseRead(INT *pstate, VOID *pFcb, UWORD *pOffset);` — erase the current record and read
|
||||
the following one (becomes current); returns its length or negative. Two `E_FILE_EOF` cases:
|
||||
(a) already on the EOF record / no records → does nothing; (b) current was the last record → it
|
||||
is erased and current becomes the EOF record. Distinguish via `DbfSense`+`DbfCount`, or by
|
||||
comparing `DbfCount` before/after. `*pstate` = `DbfStateDisabled` / `DbfStateStart` (incremental).
|
||||
**Should not** be used without an index. [PLIB p.206]
|
||||
- `INT DbfUpdate(INT *pstate, VOID *pFcb, UINT len);` — erase the current record and append a new
|
||||
one of length `len` from the read-ahead buffer, making it current. The new record must be a
|
||||
`DbfRecord` at the start of the buffer (leading 2 bytes = header, not counted in `len`). The old
|
||||
record is **not** erased until the new one is successfully appended. `E_FILE_EOF` (no-op) if no
|
||||
records of the current type or if on the EOF record. `*pstate` = `DbfStateDisabled` /
|
||||
`DbfStateStart`. **Should not** be used without an index. [PLIB p.206]
|
||||
*(Note: the manual's prose here says "DbfAppend uses these two bytes" — clearly referring to the
|
||||
same header-construction mechanism.)*
|
||||
|
||||
#### Sensing / counting
|
||||
|
||||
- `UINT DbfSense(VOID *pFcb);` — return the current record number (the EOF record number — 0 if
|
||||
empty, else count+1 — if a preceding call returned `E_FILE_EOF`). Does not alter the buffer. May
|
||||
be used without an index. [PLIB p.209]
|
||||
- `UINT DbfCount(VOID *pFcb);` — return the number of records of the currently visible type without
|
||||
changing the current record. Does not alter the buffer. **Should not** be used without an index.
|
||||
[PLIB p.209]
|
||||
|
||||
#### Finding by content
|
||||
|
||||
- `INT DbfFindRead(UINT *pstate, VOID *pFcb, VOID *pBuffer, UINT len, UINT findMode, UINT nStrings, UWORD *pOffset);`
|
||||
Match wildcard text at `pBuffer` of length `len` (≤ 255) against the first `nStrings` **string
|
||||
fields** of each record, starting at the current record; returns the matching record's data
|
||||
length or a negative error. Equivalent to `DbfFindReadField` with `startStr = 0`. `nStrings ==
|
||||
DbfFindAllStrings` searches all string fields to end-of-record. The FIR is used to type the first
|
||||
32 fields; fields beyond that are assumed strings. On match, that record becomes current and its
|
||||
buffer offset is written to `*pOffset`; on no match, `E_FILE_EOF` and current becomes the first
|
||||
record (backward search) or the EOF record (forward search). May be used without an index
|
||||
**except** `DbfFindLast` (unpredictable). Panics if `findMode` is malformed. [PLIB pp.208–209]
|
||||
|
||||
- `DbfFindReadField(...)` — same as `DbfFindRead` but with an extra `startStr` argument: match
|
||||
starts at string field number `startStr` (0 = first string field). **Available only in EPOC
|
||||
≥ 3.18.**
|
||||
**OCR FLAG:** the signature is **garbled/truncated** in the source. The printed fragment reads:
|
||||
`… UINT nStrings, UWORD *pOffset, UINT startStr);` (line lacks the return type, `pstate`, `pFcb`,
|
||||
`pBuffer`, `len`, `findMode`). By analogy with `DbfFindRead` the full signature is almost
|
||||
certainly:
|
||||
```c
|
||||
/* RECONSTRUCTED — verify against p_dbf.h before use */
|
||||
INT DbfFindReadField(UINT *pstate, VOID *pFcb, VOID *pBuffer, UINT len,
|
||||
UINT findMode, UINT nStrings, UWORD *pOffset, UINT startStr);
|
||||
```
|
||||
[PLIB pp.207–208]
|
||||
|
||||
**`findMode`** is an OR of three parts [PLIB pp.207–208]:
|
||||
1. **Max match length** in any one string field (0…255; 255 = no truncation) occupies the low part
|
||||
of `findMode`.
|
||||
2. **Direction / start**: `DbfFindForwards`, `DbfFindBackwards`, `DbfFindFirst`, `DbfFindLast`.
|
||||
3. **Case**: `DbfFindCaseIndependent` or `DbfFindCaseDependent`.
|
||||
|
||||
**Finding across continuation sub-fields** (only needed if strings may exceed 255 chars): OR
|
||||
`0x1400` into `len` (whose base value cannot exceed 255); set the length component of `findMode` to
|
||||
255 and OR in `0x4000`; only case-independent matching is allowed (OR `DbfFindCaseIndependent`);
|
||||
then OR in the direction flag as usual. [PLIB p.208]
|
||||
|
||||
> **No per-field getter/setter exists.** The DBF API deals in whole records only. There is no
|
||||
> function to read or write an individual Word/Long/Double/String field. The application must
|
||||
> **pack and unpack field bytes by hand** into/out of the `DbfRecord.data` area, following the FIR
|
||||
> field order and the string leading-byte-count convention. The find services are the only ones
|
||||
> that interpret field structure, and only for string matching.
|
||||
|
||||
### 2.8 Error names (DBF)
|
||||
|
||||
`E_FILE_EOF` (past end / before start), `E_FILE_INVALID` (bad signature / FIR / no-index misuse),
|
||||
`E_FILE_RECORD` (bad buffer length or record too big for buffer), `E_GEN_OVER` (65534-record limit
|
||||
hit on append). `DbfClose`/`DbfFlush` propagate `p_close`/`p_write` errors; most read services
|
||||
propagate `p_seek`/`p_read` errors. [PLIB pp.199, 205–206] The System Services manual names the
|
||||
same conditions `EofErr`, `InvalidFileErr`, and the panics `PanicDbf1` (bad handle) / `PanicDbf2`
|
||||
(bad offset). [SysSvc §20]
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — Worked recipe: create schema → append → read back
|
||||
|
||||
Assembled strictly from the manual's prose (PLIB ch. 14). Field packing is done by hand because
|
||||
there is no per-field API. Illustrative — variable names/details are the author's; the API calls,
|
||||
struct layout, and offsets are per the manual.
|
||||
|
||||
**Schema for the example:** two fields — field 0 = Long (FIR byte `1`), field 1 = String (FIR byte
|
||||
`3`).
|
||||
|
||||
### Step 1 — Create the file with header + FIR
|
||||
|
||||
Pre-fill a `DbfHeader` completely (creating/replacing requires the full header **and** FIR;
|
||||
`p.199`). The FIR is itself a type-2 record, so `firHeader` is a record header word: top 4 bits =
|
||||
type 2, low 12 bits = FIR length (here 2). Provide a read-ahead buffer of 4096 (guaranteed
|
||||
sufficient; `p.199`) and open with visible `type = 1`.
|
||||
|
||||
```c
|
||||
#include <p_dbf.h>
|
||||
|
||||
UBYTE buf[4096]; /* read-ahead buffer, 512..16384; >= largest record */
|
||||
VOID *fcb;
|
||||
INT state;
|
||||
DbfHeader h;
|
||||
DbfOpenArgs args;
|
||||
|
||||
/* --- header --- */
|
||||
p_bfill(&h.fileType[0], DbfHeaderNameSize, 0); /* zero the 16-byte signature */
|
||||
p_scpy(&h.fileType[0], "MYAPPDatabase"); /* pad remainder stays 0 (p.196) */
|
||||
h.createVersion = DbfVersion(); /* p.203 */
|
||||
h.dataStart = 22; /* no extended header (p.196) */
|
||||
h.needVersion = DbfVersion();
|
||||
/* --- FIR (type 2, length 2): field0=Long(1), field1=String(3) --- */
|
||||
h.firHeader = (2 << 12) | 2; /* top 4 bits type, low 12 len */
|
||||
h.fir[0] = 1; /* Long (p.197) */
|
||||
h.fir[1] = 3; /* String (p.197) */
|
||||
|
||||
args.pFcb = &fcb;
|
||||
args.fName = (UBYTE *)"MYDATA.DBF";
|
||||
args.mode = P_FCREATE | P_FUPDATE; /* create new, writable (Files ch)*/
|
||||
args.pHead = &h;
|
||||
|
||||
state = DbfStateDisabled; /* build index, blocking (p.199) */
|
||||
if (DbfQuickOpen(&state, &args, &buf[0], sizeof(buf), 1) < 0)
|
||||
/* handle error (p_fparse / p_open / E_FILE_* errors) */;
|
||||
```
|
||||
|
||||
### Step 2 — Pack a record by hand and append it
|
||||
|
||||
A record to be appended must sit at the **start of the buffer** as a `DbfRecord`: 2-byte header
|
||||
then data; `DbfAppend` builds the header from those two bytes and `len` is the **data** length only
|
||||
(`p.205`). Pack the Long field (its byte layout is the app's responsibility), then the String field
|
||||
as a leading count byte followed by its characters (`p.197`).
|
||||
|
||||
```c
|
||||
DbfRecord *rec = (DbfRecord *)&buf[0];
|
||||
UBYTE *d = &rec->data[0]; /* data starts at buffer offset 2 */
|
||||
UINT len = 0;
|
||||
LONG idNum = 42;
|
||||
TEXT *nameStr = "Widget";
|
||||
UINT nameLen = p_slen(nameStr); /* <= 255 for a plain string field */
|
||||
|
||||
/* field 0: Long (4 bytes, app-defined byte order) */
|
||||
p_bcpy(&d[len], &idNum, sizeof(LONG)); len += sizeof(LONG);
|
||||
/* field 1: String = leading byte count + chars (p.197) */
|
||||
d[len++] = (UBYTE)nameLen;
|
||||
p_bcpy(&d[len], nameStr, nameLen); len += nameLen;
|
||||
|
||||
/* header bytes are set by DbfAppend from rec->header; type is the current type (1) */
|
||||
if (DbfAppend(fcb, len) < 0) /* appends, becomes current record (p.205) */
|
||||
/* handle E_GEN_OVER / E_FILE_RECORD / p_write errors */;
|
||||
```
|
||||
|
||||
### Step 3 — Read the record back and unpack
|
||||
|
||||
After open the current record is 0 (`p.199`). Reads return the **data length** and write the
|
||||
`DbfRecord` **offset** (header included) into `*pOffset`. Read the first record, then unpack in FIR
|
||||
order. (`DbfCopyDown` first if you intend to edit in the buffer — `p.201`/`SysSvc §20`.)
|
||||
|
||||
```c
|
||||
UWORD off;
|
||||
INT rlen = DbfFirstRead(fcb, &off); /* read record 0 (p.205) */
|
||||
if (rlen >= 0) {
|
||||
DbfRecord *r = (DbfRecord *)&buf[off];
|
||||
UBYTE *p = &r->data[0];
|
||||
LONG gotId;
|
||||
UBYTE slen;
|
||||
TEXT gotName[256];
|
||||
|
||||
p_bcpy(&gotId, &p[0], sizeof(LONG)); /* field 0: Long */
|
||||
p += sizeof(LONG);
|
||||
slen = *p++; /* field 1: count byte, then chars (p.197) */
|
||||
p_bcpy(&gotName[0], p, slen);
|
||||
gotName[slen] = 0;
|
||||
/* gotId == 42, gotName == "Widget" */
|
||||
}
|
||||
|
||||
DbfFlush(fcb); /* ensure written data is on the medium (p.200) */
|
||||
DbfClose(fcb); /* p.200 */
|
||||
```
|
||||
|
||||
Notes tied to the model:
|
||||
- To **update** the record, pack a new `DbfRecord` at buffer offset 0 and call
|
||||
`DbfUpdate(&state, fcb, len)`; the record is deleted and re-appended at EOF (`p.206`).
|
||||
- To **delete**, position on it (e.g. `DbfAbsRead`) and call `DbfEraseRead(&state, fcb, &off)`
|
||||
(`p.206`); the file does not shrink until `DbfCompress` (non-Flash) or a `DbfCopyFile` rebuild
|
||||
(`p.195`).
|
||||
- `DbfFindRead` / `DbfFindReadField` can locate a record by matching a wildcard against its string
|
||||
fields (`p.207`–209); numeric fields are not searchable by these services.
|
||||
@@ -0,0 +1,480 @@
|
||||
# Psion SIBO / Workabout MX — User-Interface Programming
|
||||
|
||||
**The Window Server and the Console**
|
||||
|
||||
Reference notes drawn from the SIBO 'C' SDK *Window Server Reference* (v2.30,
|
||||
March 1999) and the *PLIB Reference* (console functions). Everything below is
|
||||
sourced from those two manuals; section/line references are given as
|
||||
`[wserv]` and `[plib]`. Anything not backed by the manuals is explicitly
|
||||
flagged as **reverse-engineered** or **uncertain**.
|
||||
|
||||
> **Machine-type note.** The manuals cover the HC, MC, S3, S3a and Workabout.
|
||||
> The **Workabout** is reported by the window server as machine type
|
||||
> `WS_TYPE_S3C` (i.e. it shares the "Series 3c" identity), and in
|
||||
> compatibility mode its `version_id` is `WS_TYPE_S3C | WS_VERSION_4`
|
||||
> `[wserv 7288-7291]`. The manuals do **not** name a "Workabout MX"
|
||||
> variant; MX-specific behaviour (notably the barcode scan key, below) is
|
||||
> **not documented** and is marked as reverse-engineered where it appears.
|
||||
|
||||
---
|
||||
|
||||
## 1. The Window Server model
|
||||
|
||||
The window server is a **system process** that provides shared access to the
|
||||
screen and the keyboard (and a pointing device, where one exists) `[plib
|
||||
1295-1296]`. Application processes talk to it as **clients** through the
|
||||
**WLIB** library — a thin C shell over ROM-based code reached by software
|
||||
interrupts `[plib 1338-1339]`.
|
||||
|
||||
### 1.1 Clients, foreground and background
|
||||
|
||||
- Of all the clients, exactly **one is the foreground client**; all others are
|
||||
background clients. The **foreground client is the one that receives
|
||||
keyboard input** `[wserv 1906-1910]`.
|
||||
- On the small-screen machines (HC, S3, S3a, Workabout) the windows of
|
||||
background clients are **not visible at all**; only the foreground client's
|
||||
windows are shown, in front `[wserv 1918-1921]`.
|
||||
- On the **MC** (large screen) windows of several clients can be visible at
|
||||
once, and a client can *attach* to another `[wserv 1330-1333]`.
|
||||
- The server keeps clients in a **front-to-back task order**: position 0 is the
|
||||
foreground; position 1 is the frontmost background task, and so on `[wserv
|
||||
1968-1971]`.
|
||||
- A client can move itself or another client between foreground and background
|
||||
with `wClientPosition` `[wserv 1983-1984]`.
|
||||
|
||||
### 1.2 The shared screen and keyboard
|
||||
|
||||
Drawing goes into a **hierarchical system of overlapping windows**; all drawing
|
||||
is clipped to visible areas, and the server issues **redraw events** telling a
|
||||
client which areas need repainting `[plib 1318-1323]`. Keyboard input is routed
|
||||
to the foreground client, except for keys the server processes itself (task
|
||||
switch, pause) or keys a client has **captured** with `wCaptureKey` so that it
|
||||
receives them whether foreground or not `[wserv 1911-1915, 6283-6293]`.
|
||||
|
||||
### 1.3 Connecting
|
||||
|
||||
A process must connect before using the server, via `wConnect` (or a wrapper
|
||||
such as `wStartup`) `[wserv 1371-1372]`.
|
||||
|
||||
```c
|
||||
VOID wConnect(WSERV_SPEC *pwserv_spec, VOID *pnws_handle, UINT flags);
|
||||
```
|
||||
|
||||
`[wserv 7182-7183]`
|
||||
|
||||
- `flags` combines: `W_CONNECT_AT_BACK` (connect as background; default is
|
||||
foreground), `W_CONNECT_USER_FLAG`, `W_CONNECT_SYSTEM_MODAL`,
|
||||
`W_CONNECT_PRIORITY` (enable the server's process-priority handling),
|
||||
`W_CONNECT_DISABLE_LEAVES` (return negative error numbers instead of calling
|
||||
`p_leave`) `[wserv 7187-7203]`.
|
||||
- `pnws_handle` is the handle the server puts into events **not** directed at a
|
||||
window (e.g. key events) `[wserv 7206-7207]`.
|
||||
- `pwserv_spec` points to a `WSERV_SPEC` the client must keep for the life of
|
||||
the connection. `wConnect` fills its `CONNECT_INFO conn` sub-struct with
|
||||
useful data — screen size (`pixels`), pixel geometry, `set_is_dark`,
|
||||
`version_id` (machine type + server version), and the default
|
||||
`system_font_handle` `[wserv 7210-7251]`.
|
||||
|
||||
**Connection tests / helpers.** After connecting, the reserved static
|
||||
`wClientData` is non-zero (zero if not connected) `[wserv 1882-1894]`. The
|
||||
`WSERV_SPEC` address is also recorded in the reserved static `wserv_channel`,
|
||||
which general-purpose code can read for screen size etc. `[wserv 7294-7303]`.
|
||||
|
||||
**Two startup paths (see also §5 on the console):**
|
||||
|
||||
- **PLIB startup module:** call `wStartup`, which connects, creates and
|
||||
initialises a backed-up window covering the whole screen, and creates a
|
||||
permanent graphics context on it — ready to draw `[wserv 1496-1519]`. The
|
||||
created window's ID is in the global `wMainWid` `[wserv 1609]`.
|
||||
- **CLIB startup module:** the startup automatically opens the console device
|
||||
`con:`, which on the HC/S3/S3a/Workabout **is itself a window-server
|
||||
connection** — so the process is already a client. Connecting again with
|
||||
`wConnect`/`wStartup` panics the process (panic 100) `[wserv 1387-1396,
|
||||
1757]`. The console channel is in the static `winHandle` `[wserv 1399-1403]`.
|
||||
|
||||
### 1.4 Windows (briefly)
|
||||
|
||||
- Create with `wCreateWindow` (returns a window ID); the window is dormant and
|
||||
invisible until **initialised** with `wInitialiseWindowTree`, which activates
|
||||
the window and all descendants `[wserv 3141-3167]`.
|
||||
- Destroy a tree with `wCloseWindowTree`; destroying a window more than once is
|
||||
**not** treated as an error `[wserv 3185-3202]`.
|
||||
- Windows can be **backed-up** by bitmaps: drawing is mirrored to a backup
|
||||
bitmap and the server redraws automatically, so the client largely avoids
|
||||
redraw handling `[wserv 1270-1273, plib 1334-1335]`.
|
||||
- Redraw/mouse events are directed at a window by the **handle** the client
|
||||
supplied at `wCreateWindow` (commonly the address of a client structure)
|
||||
`[wserv 1944-1946]`.
|
||||
|
||||
---
|
||||
|
||||
## 2. The event system
|
||||
|
||||
For each client the server maintains a **queue of events** reporting user input
|
||||
and other state changes: key presses, foreground/background changes, redraw
|
||||
events, and (on pointer machines) mouse events `[wserv 1926-1937]`. Key and
|
||||
mouse events are time-stamped in system ticks (1 tick = 1/32 s) for e.g.
|
||||
double-click detection `[wserv 1939-1941, 14331-14333]`.
|
||||
|
||||
### 2.1 Fetching events
|
||||
|
||||
```c
|
||||
VOID wGetEventWait (WS_EV *event); /* block until an event */
|
||||
VOID wGetEvent (WS_EV *event); /* async, no wait */
|
||||
VOID wGetEventSpecial(WS_EV *event, UINT flags); /* async + event filter */
|
||||
VOID wGetEventUpdate (UINT flags); /* change the filter */
|
||||
```
|
||||
|
||||
`[wserv 14302-14432]`
|
||||
|
||||
- **`wGetEventWait`** returns only when an event is available; on an empty queue
|
||||
it waits indefinitely `[wserv 1949-1951]`.
|
||||
- **`wGetEvent`** is the asynchronous version, for clients that also service
|
||||
non-server sources (serial input, timers). It sets `event->type =
|
||||
E_FILE_PENDING` immediately; when an event arrives the server fills `event`
|
||||
and signals the client's I/O semaphore. **Only one may be outstanding** — a
|
||||
second pending `wGetEvent` panics the process `[wserv 14363-14378]`.
|
||||
- **`wGetEventSpecial`** (window server **version 4** only) is `wGetEvent` plus
|
||||
a `flags` filter selecting which events to deliver. Only one call to
|
||||
`wGetEvent`/`wGetEventSpecial` may be outstanding at a time `[wserv
|
||||
14381-14393]`. Filter flags (OR-able) `[wserv 14396-14416]`:
|
||||
- `WE_KEY` — key and task-key events
|
||||
- `WE_REDRAW` — `WM_REDRAW`
|
||||
- `WE_STATUS` — `WM_FOREGROUND`, `WM_BACKGROUND`, `WM_ON`
|
||||
- `WE_MOUSE` — mouse and rubber-band events
|
||||
- `WE_OTHERS` — everything else
|
||||
- `WE_NORMAL` — all of the above (`wGetEventSpecial(..., WE_NORMAL)` ==
|
||||
`wGetEvent`)
|
||||
- `WE_ESC` — only meaningful when `WE_KEY` is *not* set: on ESC the keyboard
|
||||
buffer is discarded and a `WM_ESCAPE` event is delivered.
|
||||
- **`wGetEventUpdate`** (v4) replaces the filter of an outstanding
|
||||
`wGetEvent`/`wGetEventSpecial`; a no-op if none is outstanding `[wserv
|
||||
14419-14432]`.
|
||||
|
||||
> **Async discipline.** Fully process one server event before requesting the
|
||||
> next. Because the server runs at higher priority than clients, an async
|
||||
> request can complete while you are handling some *other* source. Never
|
||||
> destroy a window directly in response to a non-server event — instead call
|
||||
> `wCancelGetEvent`, which delivers a **`WM_CANCELLED`** at the **highest
|
||||
> priority** (overtaking all queued but not-yet-delivered events), and do the
|
||||
> destruction when that arrives `[wserv 3227-3252]`.
|
||||
|
||||
### 2.2 The event structure
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
WORD type; /* positive event type, WM_xxxx */
|
||||
UWORD handle; /* target window, or the wConnect handle */
|
||||
UWORD time; /* low word of tick count (key/mouse) */
|
||||
WS_EVENT_UNION p; /* type-dependent payload */
|
||||
} WS_EV;
|
||||
|
||||
typedef union {
|
||||
UWORD uword;
|
||||
UBYTE *dpoint;
|
||||
P_RECT rect; /* WM_REDRAW rectangle */
|
||||
WMSG_KEY key; /* WM_KEY */
|
||||
WMSG_MOUSE mouse; /* WM_MOUSE / rubber band */
|
||||
WMSG_RUBBER rubber;
|
||||
WMSG_CAPS caps; /* WM_KEYBOARD_STATE_CHANGE */
|
||||
} WS_EVENT_UNION;
|
||||
```
|
||||
|
||||
`[wserv 14311-14350]`
|
||||
|
||||
- `handle`: for window-directed events (`WM_REDRAW`, `WM_MOUSE`) it is the
|
||||
handle given to `wCreateWindow`; for non-window events (`WM_KEY`,
|
||||
`WM_FOREGROUND`) it is the handle given to `wConnect` `[wserv 14326-14329]`.
|
||||
|
||||
The key payload:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
UWORD keycode; /* code of the key pressed */
|
||||
UBYTE modifiers; /* shift/ctrl/psion/caps/num-lock */
|
||||
UBYTE count; /* auto-repeat accumulation */
|
||||
} WMSG_KEY;
|
||||
```
|
||||
|
||||
`[wserv 14456-14465]`
|
||||
|
||||
- `modifiers` bit flags: `W_SHIFT_MODIFIER` (0x02), `W_CTRL_MODIFIER` (0x04),
|
||||
`W_PSION_MODIFIER` (0x08), `W_CAPS_MODIFIER` (0x10),
|
||||
`W_NUM_LOCK_MODIFIER` (0x20, MC only) `[wserv 14481-14499]`.
|
||||
- `count` is 1 for a single press; it exceeds 1 only when the client cannot
|
||||
keep up with auto-repeat. The manual's advice is to **ignore the repeat
|
||||
count** `[wserv 14466-14476, 6263-6280]`.
|
||||
- `keycode`: printable SIBO characters (code page 850-like) fall in
|
||||
`0x20`–`0xFF` excluding `0x7F`. The **PSION** shift typically adds `0x200`
|
||||
(`W_SPECIAL_KEY`). "Special" keys carry codes `< 0x20`, `0x7F`, or `> 0xFF`
|
||||
`[wserv 14501-14541]`. Named special codes include `W_KEY_TAB` (0x09),
|
||||
`W_KEY_DELETE_LEFT` (0x08), `W_KEY_DELETE_RIGHT` (0x7F), `W_KEY_RETURN`
|
||||
(0x0D), `W_KEY_ESCAPE` (0x1B), `W_KEY_UP/DOWN/RIGHT/LEFT` (0x100–0x103),
|
||||
`W_KEY_PAGE_UP/DOWN` (0x104/0x105), `W_KEY_HOME/END` (0x106/0x107),
|
||||
`W_KEY_TASK` (0x108), `W_KEY_MENU` (0x122), `W_KEY_HELP` (0x123),
|
||||
`W_KEY_ON` (0x2002), `W_KEY_OFF` (0x2003), etc. `[wserv 14547-14852]`.
|
||||
|
||||
### 2.3 Event types
|
||||
|
||||
Common event types (`WS_EV event;` assumed) `[wserv 14441-15048]`:
|
||||
|
||||
| Event | Meaning | Payload |
|
||||
|-------|---------|---------|
|
||||
| **`WM_KEY`** | A key was pressed. The full description is in `event.p.key` (keycode, modifiers, count). Delivered to the foreground client (or a capturer). `[wserv 14449-14476, 6249-6250]` | `p.key` |
|
||||
| **`WM_REDRAW`** | Sent when the client's queue is empty and one or more windows has an update region. `event.p.rect` is a rectangular block of pixels needing redraw. Lowest priority except for `WM_USER_MSG`. `[wserv 14855-14865]` | `p.rect` |
|
||||
| **`WM_BACKGROUND`** | The foreground client has just gone to background. Usually nothing to do, but suspend real-time/animated activity until foreground returns. Only `type` set. `[wserv 14868-14878]` | — |
|
||||
| **`WM_FOREGROUND`** | A background client has just become foreground. Only `type` set. `[wserv 14881-14887]` | — |
|
||||
| **`WM_CANCELLED`** | Sent in response to `wCancelGetEvent`; delivered at highest priority. Only `type` set. `[wserv 14890-14897, 3250-3252]` | — |
|
||||
| **`WM_USER_MSG`** | Sent in response to `wUserMsg`. **Lowest priority of all** — useful as an "the server has nothing more to send" marker. Only `type` set. `[wserv 14909-14916]` | — |
|
||||
| **`WM_ON`** | Machine switched on (server v3.5+). Delivered to the foreground client if it called `wInformOn`; in v4, to any client (fore or back) that called `wInformOnAll(TRUE)`. Prompts a display refresh. Only `type` set. `[wserv 14919-14937]` | — |
|
||||
| **`WM_COMMAND`** | Another client sent a command via `wSendCommand` (server v3.5). Prompts the receiver to call `wGetCommand` for the data (up to 127 bytes). Only `type` set. `[wserv 14940-14947, 1128-1129]` | — |
|
||||
| **`WM_TASK_KEY`** | Sent to the **application-key handler** (the shell) when an application key is pressed with no process of that application present, or a PSION-shifted application key is pressed. S3/S3a/Workabout only. The app-key index (0–15) is in `event.p.key.keycode`. `[wserv 14963-14975]` | `p.key.keycode` |
|
||||
| **`WM_ESCAPE`** | (v4) Delivered when ESC is pressed *and* the client selected events with `wGetEventSpecial` including `WE_ESC`/`WM_ESCAPE` but **excluding** key events; the keyboard buffer is discarded. `[wserv 14999-15015]` | — |
|
||||
| **`WM_TASK_UPDATE`** | Sent to the shell (when foreground) whenever *any* process terminates. S3/S3a/Workabout and HC v3.5+. Only `type` set. `[wserv 14950-14960]` | — |
|
||||
| **`WM_DATE_CHANGED`** | (v4) Date changed (reset, or past midnight). Sent to a foreground S3a/Workabout app not in S3 compatibility mode; background apps get it on next foreground; delivered at next power-on if off. `[wserv 14978-14993]` | — |
|
||||
| **`WM_KEYBOARD_STATE_CHANGE`** | Sent only to the shell when numlock/capslock changes; new state in `event.p.caps.modifiers`. (MC generates it for the caps-lock key.) `[wserv 15065-15069, 14734-14735]` | `p.caps` |
|
||||
|
||||
**Mouse / pointer events** (only on machines with a pointing device, e.g. the
|
||||
MC) `[wserv 15076-15187]`:
|
||||
|
||||
| Event | Meaning |
|
||||
|-------|---------|
|
||||
| **`WM_MOUSE`** | State change on the digitiser. `event.p.mouse` is a `WMSG_MOUSE { UBYTE event; UBYTE state; P_POINT pos; }`. `event.p.mouse.event` is `WM_MOUSE_MOVE` (filtered out by default), `WM_MOUSE_PRESS`, or `WM_MOUSE_RELEASE`; `state` carries `W_MOUSE_DOWN`, `W_MOUSE_OUTSIDE`, and the shift modifiers. `[wserv 15083-15129]` |
|
||||
| **`WM_RUBBER_BAND_INIT`** | Special `WM_MOUSE` variant sent when a press occurs in a window flagged `W_WIN_RUBBER_BAND_CAPTURE`. `[wserv 15146-15154]` |
|
||||
| **`WM_RUBBER`** | Sent on completion of a rubber-band interaction. `[wserv 15160-15166]` |
|
||||
| **`WM_ACTIVE`** | Sent to a window that set `W_WIN_INACTIVE` when a `WM_MOUSE_PRESS` lands on it or a descendant (in place of the `WM_MOUSE`); the client typically re-activates the tree by clearing `W_WIN_INACTIVE`. Only `type` set. `[wserv 15169-15186]` |
|
||||
|
||||
Large-screen (MC) events also include `WM_DEICONISE`, `WM_ATTACHED`,
|
||||
`WM_DETACHED` for the multi-task attach/iconise model `[wserv 15017-15062]`.
|
||||
|
||||
### 2.4 Event priority
|
||||
|
||||
Event delivery is **prioritised**, not strictly FIFO:
|
||||
|
||||
- `WM_CANCELLED` is delivered at the **highest** priority, overtaking anything
|
||||
queued but not yet delivered `[wserv 3250-3252]`.
|
||||
- `WM_REDRAW` is **lower** than user input and foreground/background events;
|
||||
the only type lower than `WM_REDRAW` is `WM_USER_MSG` `[wserv 14865,
|
||||
3126-3127]`.
|
||||
- `WM_USER_MSG` has the **lowest** priority of all `[wserv 14912-14913]`.
|
||||
- Between windows there is a further **two-level redraw priority**: windows are
|
||||
low by default; `W_WIN_PRIORITY` (on `wCreateWindow`/`wSetWindow`) promotes a
|
||||
window's redraws ahead of the rest `[wserv 3130-3131]`.
|
||||
|
||||
### 2.5 The barcode scan key — **reverse-engineered (Workabout MX)**
|
||||
|
||||
> **Not in the manuals.** The *Window Server Reference* documents no barcode /
|
||||
> scanner / laser trigger key, and defines no keycode near 368. On barcode-
|
||||
> equipped Workabout / Workabout MX hardware the **scan (trigger) key is
|
||||
> observed to arrive as an ordinary `WM_KEY` event with keycode 368**
|
||||
> (`0x170`). This is **reverse-engineered field knowledge, not documented
|
||||
> behaviour** — treat the exact value and delivery path as version/hardware
|
||||
> dependent and confirm on the target unit. Because it surfaces as a normal
|
||||
> key event, it can be handled in the ordinary `WM_KEY` path (and, like other
|
||||
> keys, potentially captured with `wCaptureKey`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Drawing and text output primitives
|
||||
|
||||
All drawing targets a **drawable** selected by the **current graphics context
|
||||
(GC)** `[wserv 5504, 5526]`.
|
||||
|
||||
### 3.1 Graphics contexts
|
||||
|
||||
- Create a **permanent** GC on a window/bitmap with `gCreateGC` /
|
||||
`gCreateGC0` (the latter with default values) `[wserv 304-305, 1438]`. Select
|
||||
the current GC with `gSetGC` / `gSetGC0` `[wserv 311-312, 2993]`.
|
||||
- **Temporary** GCs are used during redraws (see below) and are freed
|
||||
automatically by `wEndRedraw` `[wserv 3037-3050]`.
|
||||
- `wStartup` conveniently makes a permanent GC on its full-screen window
|
||||
`[wserv 1502-1505]`.
|
||||
|
||||
### 3.2 Graphics primitives
|
||||
|
||||
- Lines / shapes / borders / fills: `gDrawLine`, `gBorderRect`, `gBorder`,
|
||||
`gBorder2Rect`/`gBorder2` (shadowed, v4), `gFillPattern`, `gInvObloid`
|
||||
`[wserv 315-326, 1296, 919-957]`.
|
||||
- Bitmaps: `gCopyBit` (copy a bitmap to a window) and the bitmap-file family
|
||||
(`gInitBit`, `gGetBit`, `gLoadBit`, `gPeekBit`, `gSaveBit`) `[wserv 354,
|
||||
829-1021]`.
|
||||
- The server also draws buttons (`wDrawButton`, `wDrawButton2`), sprites
|
||||
(`wCreateSprite`/`wSetSprite`) and scaled objects (`gDrawObject`) `[wserv
|
||||
926-969, 1168, 1203]`.
|
||||
|
||||
### 3.3 Text output
|
||||
|
||||
Text is drawn through the current GC (font ID, style, transfer mode) `[wserv
|
||||
5526-5536]`:
|
||||
|
||||
- `gPrintText` — draw text from a pixel position `[wserv 5508, 1439]`
|
||||
- `gPrintClipText` — clipped to a given width `[wserv 5510]`
|
||||
- `gPrintBoxText` — left/right/centred in a box; commonly used for
|
||||
**flicker-free** redraw `[wserv 5512-5513, 2961]`
|
||||
- `gXPrintText` — with embellishment `[wserv 5515]`
|
||||
- `gShadowText` — shadowed (v4) `[wserv 5517]`
|
||||
|
||||
Font styles combine `G_STY_NORMAL`, `G_STY_BOLD`, `G_STY_UNDERLINE`,
|
||||
`G_STY_INVERSE`, `G_STY_DOUBLE`, `G_STY_MONO`, `G_STY_ITALIC` `[wserv
|
||||
5539-5557]`. Layout helpers: `gTextWidth`, `gTextCount`, `wGetWidthTable`
|
||||
`[wserv 5492-5497]`.
|
||||
|
||||
### 3.4 Redraw handling
|
||||
|
||||
On a `WM_REDRAW`, the normal response is to **validate** the update region and
|
||||
draw it `[wserv 10248-10256]`. Bracket the drawing with a `wBeginRedraw`
|
||||
variant and `wEndRedraw`; `wBeginRedraw` both signals "this is a redraw" and
|
||||
**validates** the given rectangle `[wserv 3005-3007]`. There are six
|
||||
`wBeginRedraw*` variants covering partial vs whole window and
|
||||
independent/temporary GC combinations `[wserv 3020-3050]`. Related: validate
|
||||
without redrawing (`wValidateRect`, `wValidateWin`) and force redraws by
|
||||
invalidating (`wInvalidateRect`, `wInvalidateWin`) `[wserv 270-274, 2901]`.
|
||||
Backed-up windows largely avoid this cycle because the server repaints from the
|
||||
backup bitmap `[wserv 1270-1273]`.
|
||||
|
||||
Minimal MC (non-backed-up) redraw loop `[wserv 1589-1608]`:
|
||||
|
||||
```c
|
||||
wStartup();
|
||||
do {
|
||||
wGetEventWait(&event);
|
||||
if (event.type == WM_REDRAW) {
|
||||
wBeginRedrawWin(wMainWid);
|
||||
gPrintText(10, 20, "Hello world!", 12);
|
||||
wEndRedraw();
|
||||
}
|
||||
} while (event.type != WM_KEY);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Error handling (server calls)
|
||||
|
||||
By default a failing server function calls `p_leave` with the negative error
|
||||
number; `wDisableLeaves(TRUE)` (or `W_CONNECT_DISABLE_LEAVES` at connect time)
|
||||
makes it **return** the error instead `[wserv 1629-1641]`. Many drawing calls
|
||||
are **blind** (queued client-side, no acknowledgement); on failure the server
|
||||
discards further blind ops until a non-blind call reports the error. Force the
|
||||
issue with `wCheckPoint` (flush + signal) or `wFlush` (flush only); after an
|
||||
error, `wCleanUp` releases dangling resources `[wserv 1653-1703]`. Client-side
|
||||
buffering means a panic can lag the offending call — insert `wFlush`/
|
||||
`wCheckPoint` when hunting one down `[wserv 1791-1793]`.
|
||||
|
||||
---
|
||||
|
||||
## 5. The console layer — `CON:` and the PLIB console functions
|
||||
|
||||
The console is the **lightweight text-UI path**. PLIB contains only *primitive*
|
||||
console functions — typed-line input (with simple backspace editing) and
|
||||
mono-spaced line output — while the `con:` device driver adds row/column
|
||||
positioning and mono-spaced character printing `[plib 1299-1301]`. They are
|
||||
explicitly "for quick-and-dirty applications... not suitable for constructing
|
||||
quality user interfaces" `[plib 8580-8581]`.
|
||||
|
||||
### 5.1 Relationship to the window server
|
||||
|
||||
Both the PLIB console functions and `con:` **ultimately call the window
|
||||
server** for input and drawing — but expose only a fraction of its capability
|
||||
`[plib 1310-1315]`. How `con:` is implemented differs by machine `[wserv
|
||||
1319-1323]`:
|
||||
|
||||
- **HC / S3 / S3a / Workabout:** `con:` is implemented by **connecting
|
||||
directly to the window server** — opening `con:` makes the process a window-
|
||||
server client. Opening it also creates and initialises a **backed-up**
|
||||
console window (no redraw handling needed) `[wserv 1319-1320, 1387-1407]`.
|
||||
The console window's ID can be fetched with the `P_FINQ` I/O function
|
||||
(`CONSOLE_INFO.window_handle`), letting a CLIB program create its own GC on
|
||||
that window and draw with WLIB `[wserv 1410-1453]`.
|
||||
- **MC:** `con:` is implemented by a **separate `SYS$CONS` display process**
|
||||
that keeps a character map and redraws its own window (with title/menu bars).
|
||||
Here it is `SYS$CONS`, not the application, that is the window-server client;
|
||||
the application cannot use the console window, and `P_FINQ` is **not**
|
||||
supported. On the MC the recommendation is the **PLIB startup module +
|
||||
`wStartup`** `[wserv 1305-1322, 1539-1615]`.
|
||||
|
||||
The two startup modules therefore differ: the **CLIB** startup auto-opens
|
||||
`con:` (backing `printf`, `gets`, `cprintf`, `cgets`) `[wserv 1387-1389]`; the
|
||||
**PLIB** startup does not, and you call `wStartup` yourself `[wserv 1496-1497]`.
|
||||
Auto-opening can be suppressed in CLIB by defining `p_xwind` `[wserv
|
||||
1457-1490]`.
|
||||
|
||||
### 5.2 The PLIB console functions
|
||||
|
||||
Output (open `con:` automatically on first use) `[plib 8584-8595, 8700-8737]`:
|
||||
|
||||
```c
|
||||
VOID p_putch(UINT c); /* write one character */
|
||||
VOID p_puts (TEXT *str); /* write string + newline */
|
||||
VOID p_printf(TEXT *fstr, ...); /* formatted line + newline */
|
||||
VOID p_print (TEXT *fstr, ...); /* formatted, no trailing newline */
|
||||
```
|
||||
|
||||
- `p_printf` uses an internal buffer of `P_MAXSYSIO` (258) bytes; output is
|
||||
limited to `P_MAXSYSIO - 2` (256) bytes per call. Format is that of `p_atob`
|
||||
`[plib 8720-8726]`.
|
||||
- `p_print` moves no line; embed `\r` (start of line) and `\n` (down a line)
|
||||
yourself `[plib 8732-8737]`.
|
||||
|
||||
Input `[plib 8597-8599, 8740-8770]`:
|
||||
|
||||
```c
|
||||
INT p_getch(VOID); /* wait for a key, return its code */
|
||||
INT p_gets (TEXT *str); /* read a line (backspace edit) */
|
||||
INT p_getl (TEXT *pmt, TEXT *str, INT len); /* prompt, then read a line */
|
||||
```
|
||||
|
||||
- `p_getch` waits for a key and returns its character code (no echo) `[plib
|
||||
8740-8743]`.
|
||||
- `p_gets` reads up to `P_MAXSYSIO-1` characters, Enter-terminated, and NUL-
|
||||
terminates at `str` (needs `P_MAXSYSIO` bytes there); returns the length
|
||||
`[plib 8746-8756]`.
|
||||
- `p_getl` writes the prompt `pmt`, then reads up to `len` characters
|
||||
(Enter-terminated, needs `len+1` bytes); returns the length `[plib
|
||||
8759-8770]`.
|
||||
|
||||
Canonical smallest program `[plib 1020-1021]`:
|
||||
|
||||
```c
|
||||
p_printf("Hello world");
|
||||
p_getch();
|
||||
```
|
||||
|
||||
### 5.3 Console configuration statics
|
||||
|
||||
Set these **before the first console call** `[plib 8612-8694]`:
|
||||
|
||||
- `winHandle` (`GLREF_D VOID *`) holds the open console channel; open a file or
|
||||
`TTY:` into it first to **redirect** `p_putch` / `p_print` / `p_printf`. Do
|
||||
**not** use `p_getch` / `p_gets` / `p_getl` when redirected `[plib
|
||||
8615-8628]`.
|
||||
- `_DefScreenRect` (`P_RECT`, top-left `(0,0)`, bottom-right = columns × rows)
|
||||
changes the console window size `[plib 8629-8665]`.
|
||||
- `_DefScreenMode` (`INT`) selects the mode: `0` native (default, currently
|
||||
single-pixel non-compatibility on all SIBO machines), `1` compatibility, `2`
|
||||
non-compatibility with grey, `3` compatibility with grey. Irrelevant values
|
||||
are ignored `[plib 8668-8694]`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Higher UI libraries (pointer only)
|
||||
|
||||
For full applications — menus, dialogs, forms — SIBO provides **higher-level UI
|
||||
libraries layered on the window server** (commonly referred to as **HWIM**,
|
||||
**FORM** and **OLIB**). **These are separate libraries and are not documented
|
||||
in the *Window Server Reference* or *PLIB Reference*** covered here; the two
|
||||
manuals mention menus and dialog boxes only as *windows the server draws/clips*
|
||||
(e.g. `[wserv 2395, 10646-10647]`), not as an API. Use those libraries' own
|
||||
references for real application UIs; the material above is the **foundation**
|
||||
they sit on. *(Library names are noted as a pointer only and are not sourced
|
||||
from these two manuals.)*
|
||||
|
||||
---
|
||||
|
||||
### Source key
|
||||
|
||||
- `[wserv N]` — *Window Server Reference* v2.30, line N of the supplied text.
|
||||
- `[plib N]` — *PLIB Reference* (console functions), line N of the supplied
|
||||
text.
|
||||
- Items marked **reverse-engineered** / **uncertain** are **not** in either
|
||||
manual (notably the code-368 barcode scan key and the higher UI library
|
||||
names).
|
||||
@@ -0,0 +1,581 @@
|
||||
# Psion SIBO / Workabout — Hardware Reference for Programmers
|
||||
|
||||
Scope and sources. This reference is compiled **only** from two Psion manuals:
|
||||
|
||||
- **HDK** — *The Psion SIBO Hardware Development Kit*, Psion PLC, Rev 1.00, May 1995.
|
||||
- **WPG** — *Workabout Programming Guide* (Workabout Programmers Reference), hardware
|
||||
appendices / introduction.
|
||||
|
||||
Citations are given as `[HDK p.N]` or `[WPG §/p]`. Where a fact is asserted by the manuals it is
|
||||
cited; nothing here is invented. The manuals themselves note that both refer onward to a separate
|
||||
*Hardware Reference manual* and *SIBO Computers Programmers Reference* for the full physical
|
||||
memory map — those documents are **not** in scope, so the memory-map section below is limited to
|
||||
what the two supplied manuals actually state, and gaps are marked explicitly.
|
||||
|
||||
> **MX note.** The two supplied manuals describe the **V30H**-based Workabout (NEC V30H at
|
||||
> 7.68 MHz) and the ASIC1/ASIC2/ASIC4/ASIC5/ASIC9 family. **They do not mention the V30MX or
|
||||
> ASIC9MX.** Every statement about the **V30MX / ASIC9MX / Workabout MX** in this document is
|
||||
> therefore flagged as **[MX — not in supplied manuals]** and is presented only as an orientation
|
||||
> note, not as a sourced fact. Treat all MX specifics as *uncertain / to be confirmed against the
|
||||
> MX hardware reference.*
|
||||
|
||||
---
|
||||
|
||||
## 1. SIBO architecture overview
|
||||
|
||||
All Psion machines in this family are built on the proprietary **SIBO** ("SIxteen Bit Organiser")
|
||||
architecture: a battery-powered, 8086-class computer system designed for size, weight and power
|
||||
[HDK p.2; WPG §basic hardware].
|
||||
|
||||
Key components of the architecture [HDK p.2; WPG p.1-x]:
|
||||
|
||||
- An **8086-class processor**.
|
||||
- A **power-management system** that selectively powers subsystems under software control.
|
||||
- An **asynchronous high-speed serial protocol** (the *Psion SIBO serial interface*) between the
|
||||
machine and its peripherals.
|
||||
- **Solid State Disks (SSDs)** — fast, low-power, silicon mass storage, no moving parts.
|
||||
- **Hardware protection** from aberrant software: trapping of out-of-range addressing, plus a
|
||||
**watch-dog timer** that fires if interrupts are left disabled too long.
|
||||
- **Real-time clock**, **ROM-resident system software**, **graphics LCD**.
|
||||
- On some models: touch digitiser pad; ISDN-8-bit combo sound.
|
||||
|
||||
The digital logic of a SIBO machine is implemented in custom **ASICs** (Application Specific
|
||||
Integrated Circuits). At the time of the HDK there were **ten** SIBO ASICs; all are static-CMOS
|
||||
surface-mount parts [HDK p.2].
|
||||
|
||||
### 1.1 Processor (NEC V-series)
|
||||
|
||||
- The MC/HC/Series 3 and the Workabout use the **NEC V30H**, "an enhanced 16-bit CMOS version of
|
||||
the 8088 found in the original IBM PC … software compatible with the 8088" [HDK p.2].
|
||||
- **Workabout:** NEC V30H, clocked at **7.68 MHz** [WPG p.1-x, Tech Spec: "NEC V30 running at
|
||||
7.68 MHz"].
|
||||
- The V30H is a **fully static** design: all internal storage (registers) is static, so there is
|
||||
**no minimum clock speed** and the clock can be stopped at any instant with **no loss of state**.
|
||||
SIBO exploits this to stop the clock while the CPU is idle, saving power [HDK p.2].
|
||||
|
||||
> **[MX — not in supplied manuals]** Later Workabout variants are marketed as the **Workabout MX**,
|
||||
> based on the **NEC V30MX** core integrated in the **ASIC9MX**. Neither the V30MX nor ASIC9MX
|
||||
> appears in the HDK or WPG supplied here; their clock rate, memory reach and register differences
|
||||
> are **not documented in these sources** and must be confirmed elsewhere. Software written to the
|
||||
> V30H/8086 programming model and the ASIC register interfaces below is the documented baseline.
|
||||
|
||||
### 1.2 Chip integration levels
|
||||
|
||||
- **Discrete generation** (MC, HC, Series 3): three principal chips — **V30H + ASIC1 + ASIC2**
|
||||
[HDK p.2].
|
||||
- **Integrated generation** (Series 3a, Workabout): V30H, ASIC1 and ASIC2 collapsed into a single
|
||||
custom chip, **ASIC9** (plus general I/O and PSU-control logic). ASIC9 "integrates all the
|
||||
digital logic required to produce a SIBO architecture computer less the memory onto one chip",
|
||||
and adds a free-running clock (FRC) and a codec interface for sound [HDK p.4].
|
||||
|
||||
So on the Workabout, the functions attributed below to "ASIC1" (system/interrupt/LCD) and "ASIC2"
|
||||
(peripheral/serial-protocol/power) are physically inside **ASIC9** [HDK p.4].
|
||||
|
||||
> **[MX — not in supplied manuals]** On the MX, the equivalent single-chip part is **ASIC9MX** with
|
||||
> the **V30MX** core. Same architectural role as ASIC9 is assumed; specifics unconfirmed here.
|
||||
|
||||
---
|
||||
|
||||
## 2. The ASICs and their roles
|
||||
|
||||
| ASIC | Role (from HDK p.4) |
|
||||
|------|---------------------|
|
||||
| **ASIC1** | Main **system controller**. Connects directly to the V30H, controlling all CPU bus cycles (forming a micro-controller-like unit executing 8086 code). Blocks: bus controller, programmable timer, **eight-input interrupt controller**, **LCD controller**, memory-decode circuitry. |
|
||||
| **ASIC2** | **Peripheral controller**. Contains the system clock oscillator; controls standby↔operating switching; interfaces to PSU, keyboard, buzzer and SSDs; contains the **eight-channel SIBO serial-protocol controller**; drives the reduced-external and extended-internal expansion ports. |
|
||||
| **ASIC4** | Serial-protocol **slave** for addressing memory / memory-mapped peripherals. Used in SSDs. A cut-down ASIC5. See §5. |
|
||||
| **ASIC5** | General-purpose I/O **slave** with an on-board **UART**; several modes (RS232 / parallel / barcode / card-reader / memory pack). See §6. |
|
||||
| **ASIC9** | Composite: **V30H + ASIC1 + ASIC2** + general I/O + PSU control on one die; adds FRC and codec interface. Used in S3a and Workabout. |
|
||||
|
||||
On the **host** side of the SIBO serial link the controller (SPC) is in **ASIC2** (HC/MC/S3) or
|
||||
**ASIC9** (S3a/Workabout). On the **peripheral** side the slave (SPS) is **ASIC4** or **ASIC5**
|
||||
[HDK p.4, p.6].
|
||||
|
||||
> **[MX — not in supplied manuals]** **ASIC9MX** — the composite CPU/LCD/system chip on the MX — is
|
||||
> not described in these sources. Assume it plays the ASIC9 role (V30MX core + ASIC1/ASIC2
|
||||
> functions). No register-level MX detail is available here.
|
||||
|
||||
---
|
||||
|
||||
## 3. Memory: ROM / RAM / SSD and segments
|
||||
|
||||
What the supplied manuals state:
|
||||
|
||||
- **ROM:** all Workabout models have **1 MB internal masked ROM** holding the OS (EPOC), the
|
||||
MS-DOS-like Command Processor, OPL editor and other utilities [WPG p.1-9, Tech Spec].
|
||||
- **RAM:** Workabout ships with either **256 KB** or **1 MB** internal RAM (Tech Spec lists memory
|
||||
build codes A=256 KB, B=512 KB, C=1 MB, D=2 MB) [WPG p.1-x, Tech Spec, serial-number table].
|
||||
- **SSD:** two SSD drives, exposed as **drive A:** (top) and **drive B:** (bottom); RAM or Flash
|
||||
SSDs, "up to 8 MB" of extra storage [WPG p.1-3, Tech Spec]. SSDs retain data independently of the
|
||||
main power source [WPG p.1-4]. Contents of the internal RAM are preserved across power-off (the
|
||||
machine resumes previous state on power-on) [WPG p.1-x].
|
||||
- **Internal drive M:** internal RAM is also exposed as a filing volume **M:** (with
|
||||
`M:RAMDRIVE`), formattable like an SSD volume [WPG §FORMAT, §MEM].
|
||||
|
||||
### 3.1 The programmer's memory model (segments, protection)
|
||||
|
||||
- The CPU is 8086-class, so software sees the standard **16-bit segmented** address model
|
||||
(segment:offset, 16-byte "paragraphs") [HDK, WPG passim]. `LSEG` reports each segment's **segment
|
||||
address**, **size in paragraphs** (1 paragraph = 16 bytes) and **access count** [WPG §LSEG].
|
||||
- **Memory protection:** any attempt by an application to write **outside its own data segment**
|
||||
causes the OS to terminate it (a **"panic"**); likewise leaving interrupts disabled too long
|
||||
(watch-dog) [WPG §Resetting; HDK p.2].
|
||||
- **`MEM`** reports free RAM in KB; this exceeds free bytes on `M:` because "some parts of internal
|
||||
memory are reserved for code and data segments" [WPG §MEM].
|
||||
- The OS **moves memory segments** at runtime (e.g. when a driver is installed/removed). Drivers
|
||||
must cope: FAR return addresses into the OS stay valid, but a driver's own absolute segment
|
||||
address may change, so ISR/PDD addresses must be re-established on **resume** (see §7.4)
|
||||
[HDK p.40–42].
|
||||
|
||||
### 3.2 Banking / paging — status in these sources
|
||||
|
||||
The physical **memory map and paging/banking scheme of the host CPU are not laid out in the
|
||||
supplied manuals**; both defer to the separate *SIBO Computers Programmers Reference* / *Hardware
|
||||
Reference* [WPG p.1-3: "See the SIBO Computers Programmers Reference manual for further details
|
||||
regarding SSDs"; HDK p.2]. What the HDK *does* document is the **address decode inside ASIC4/ASIC5
|
||||
slaves**, i.e. how a peripheral or SSD's own address space is banked via **chip-select blocks**:
|
||||
|
||||
- **ASIC4:** 8 chip-selects forming selectable **addressing blocks**, size software-defined,
|
||||
**default 32 KB/block**; the filing system re-sizes the block when reaching the top of the
|
||||
address space [HDK p.24]. Up to **28 address bits (256 MB)** in Extended mode; **21 bits + 4
|
||||
chip-selects (4 × 2 MB)** in SSD/compatibility mode [HDK p.25]. See §5.
|
||||
- **ASIC5 (pack mode):** address lines A0–A20 generated across ports B/D/C, with **CS0–CS3**
|
||||
selected by SEL0/SEL1; **counter mode** on port B auto-increments the low address so 256
|
||||
consecutive locations can be read without re-addressing [HDK p.30, p.57].
|
||||
|
||||
> **Gap flagged:** the host-side ROM/RAM/SSD address ranges, and any V30-level bank registers, are
|
||||
> **not in these two manuals**. Do not infer them from the peripheral-side decode above.
|
||||
|
||||
---
|
||||
|
||||
## 4. SIBO serial protocol (host ↔ peripheral bus)
|
||||
|
||||
Everything a program does to an ASIC4/ASIC5 peripheral goes over the **two-wire synchronous SIBO
|
||||
serial link** [HDK p.6]:
|
||||
|
||||
- **CLK** — clock, always **output from the controller** (ASIC2/ASIC9). Nominal **3.84 MHz** for
|
||||
memory interfaces; **1.536 MHz continuous** for peripherals. Tri-stated low when idle
|
||||
[HDK p.6].
|
||||
- **DATA** — bi-directional, synchronous. Direction is set by the transport layer, not the
|
||||
physical layer. Changed on falling CLK edge by the transmitter, latched on rising edge by the
|
||||
receiver; pulled low when idle [HDK p.6].
|
||||
|
||||
**Framing (physical layer).** 12-bit frames, 8 data bits each → theoretical max ≈ **312 KB/s**
|
||||
[HDK p.6]. Frame = `ST` (start, goes high) · `CTL` (0 = control frame, 1 = data frame) · `I1`
|
||||
(idle/turn-around) · `D0–D7` · `I2` (idle) [HDK p.7]. Four frame types: **Null** (sync all slaves —
|
||||
12 clocks with DATA held low), **Control**, **Data-out** (controller→slave), **Data-in**
|
||||
(slave→controller; controller drives cycles 1–2 then tri-states, slave drives D0–D7) [HDK p.7].
|
||||
|
||||
**Transport layer.** The controller has two registers: a write-only **Control register** and a
|
||||
read/write **Data register** (byte or word) [HDK p.8]. Control bytes are classified by **bit 7,
|
||||
the Select (S) bit**:
|
||||
|
||||
- **S = 0 — Slave-select mode.** Byte = `R` (reset bit) + **6-bit slave ID**. ID 0 is illegal, so
|
||||
up to **63 slaves** per controller. `R=0` resets, `R=1` selects. Selecting a slave with `R=1`
|
||||
makes it return a **non-zero 8-bit info byte**; a reply of **0 means no slave of that ID present**
|
||||
[HDK p.8–9].
|
||||
- **S = 1 — Slave-control mode.** Byte = `R/W` (0=write,1=read) · `B/W` (0=byte,1=word) · `S/M`
|
||||
(0=single,1=multi) · 4 slave-specific data bits. Byte-pair (word) transfers are **listed as not
|
||||
implemented** [HDK p.9].
|
||||
|
||||
**Timing rules (clock cycles, ≈260 ns each at 3.84 MHz)** [HDK p.10–11]:
|
||||
control-byte process = 12 cycles; byte transfer = 12 cycles; byte-pair = 24 cycles. After writing
|
||||
the control register, wait ≥12 cycles before touching the data register or writing another control
|
||||
word.
|
||||
|
||||
Assembler helper macros the HDK uses for all of this [HDK p.52]:
|
||||
|
||||
| Macro | Action |
|
||||
|-------|--------|
|
||||
| `SCONTOUT` | Output control byte in AL |
|
||||
| `SDATAOUT` | Output data byte in AL |
|
||||
| `SDATAIN` | Input a data byte into AL |
|
||||
| `SBUSY` | Wait while the link is busy |
|
||||
| `XNOP` | Short wait |
|
||||
| `HwNullFrame` | Emit a null frame to sync slaves |
|
||||
|
||||
Serial-protocol control defines (`SerialSelect`, `SerialReadSingle`, `SerialWriteSingle`, slave
|
||||
IDs) live in `ospack.inc` / `ossibo.inc` [HDK p.59].
|
||||
|
||||
---
|
||||
|
||||
## 5. ASIC4 — memory / peripheral slave
|
||||
|
||||
Purpose: convert SIBO serial frames into the address/data/control signals needed to drive memory
|
||||
and memory-mapped peripherals; used in SSDs and in ASIC4-based expansion boards [HDK p.24].
|
||||
|
||||
**Bus:** 8-bit data, **28-bit address**, **8 chip-selects** (CS0–CS7) [HDK p.24].
|
||||
|
||||
### 5.1 Modes (selected by slave ID)
|
||||
|
||||
Select ASIC4 with the appropriate ID, then read the info byte [HDK p.25]:
|
||||
|
||||
- **SSD / ASIC5-compatibility mode — ID 2.** Mimics an ASIC5 in pack mode; compatible with all
|
||||
existing SSD software. Max **21 address bits + 4 CS (4 × 2 MB)**.
|
||||
- **ASIC4 Extended mode — ID 6.** Up to **28 address bits (256 MB)**. On reset, four extra config
|
||||
bits come from A27–A24; **A27 = M** selects standard-SSD (M=0) or **mixed mode (M=1)** —
|
||||
memory + peripherals. Mixed mode is the one of interest for peripheral developers.
|
||||
|
||||
**Mixed mode split:** address space halves — lower half = memory-mapped peripherals (any use),
|
||||
upper half = pure memory (typically a control ROM the filing system can mount). Chip-selects split:
|
||||
**CS0–CS3 = peripheral blocks, CS4–CS7 = memory devices 1–4** [HDK p.25–26].
|
||||
|
||||
**Reset config** is read from the data bus (**Info Byte**, D0–D7) and A24–A27 (extended nibble),
|
||||
set with pull-up/pull-down resistors (~100 k so the bus can still drive the lines in normal
|
||||
operation) [HDK p.26].
|
||||
|
||||
### 5.2 ASIC4 registers (mixed/peripheral mode) [HDK p.53–55]
|
||||
|
||||
| Reg | Name | R/W | Notes |
|
||||
|-----|------|-----|-------|
|
||||
| **0** | **Data Register** | R/W | Drives/reads D0–D7 (tri-state when idle). **On reset holds the Info Byte** (see below). |
|
||||
| **1** | **Input Register** (read) / **Device-Size Register** (write) | R/W | Read: extended-info bits + live state of general inputs **In0–In2** and X2. Write: bits S3–S0 set the address-decoder block size (32 KB … 256 MB); defaults to `0x0F` (unused) on reset. |
|
||||
| **2** | **Address-Increment Register** | W | A write increments address lines A0–A3. |
|
||||
| **3** | **Address Register** | W (multi-byte, LSB first) | Loads all 28 address lines. Bytes: **ATO = A0–A7, AT1 = A8–A15, AT2 = A16–A23, AT3 = A24–A27**. Writing the first byte resets A8–A27 to 0. AT3 bits 4–6 can steer CS0–CS3. Cleared on reset. |
|
||||
| **4,5,6** | — | — | **Not implemented.** |
|
||||
| **7** | **Control Register** | W | Bits: `7 LBO · 6 TSTA · 5 LTM · 4 VPS · 3 EDA · 2 CSS · 1 WRS · 0 OES`. Setting **LBO** / **VPS** drives those pins high (usable as GP outputs). **OES/WRS** control read/write accesses. |
|
||||
|
||||
**Info Byte** (Reg 0 at reset) [HDK p.53]: bits encode **device type** (RAM / Intel Flash type 1 /
|
||||
type 2 / read-only SSD / hardware write-protected SSD), **number of devices** (1–4), and **device
|
||||
size** (000 = no SSD present, then 32 KB → 2 MB). In Extended mode a further **Extended Info Byte**
|
||||
(M, De, Ne, Se) identifies the peripheral type — e.g. `1 0 0 1` = Turbo RS232 (16550), `1 0 1 0` =
|
||||
3Fax, `1 1 1 1` = extended info held in ROM [HDK p.54].
|
||||
|
||||
**Access sequence** (bottom 256 addresses) [HDK p.58–59]: control-write to the **Address** register
|
||||
→ data-write the address → control-frame read/write to the **Data** register → transfer the byte.
|
||||
The HDK gives `Input`/`Output` assembler routines doing exactly this; **interrupts must be off**
|
||||
during them so the host does not multitask mid-transfer.
|
||||
|
||||
**Other pins** [HDK p.27, p.55]: `In0–In2` GP inputs; `LBO` (open-drain low-battery driver), `VPS`
|
||||
(VPP control) GP outputs; `OE`/`WR` bus control; `SCLK`/`SDAT` the serial link; `SDIR` protocol
|
||||
direction bit; `POR` reset; `ATST` test (pull high = address test mode); `MCSD`/`X2D2` tie to GND
|
||||
via 100 k. A PSRAM mode reuses some pins for refresh/oscillator.
|
||||
|
||||
---
|
||||
|
||||
## 6. ASIC5 — UART / parallel / barcode / card-reader slave
|
||||
|
||||
Three primary functions: a **UART** (up to **48000 baud**), **general-purpose I/O**, and
|
||||
**address/data lines** for memory / memory-mapped peripherals [HDK p.29].
|
||||
|
||||
### 6.1 Modes
|
||||
|
||||
- **Pack mode:** generates full address/data/control to access memory; **no peripheral functions**.
|
||||
- **Peripheral mode:** limited memory addressing (lines reused for I/O). Entered by setting the
|
||||
**peripheral bit (bit 0) in the Port-B mode register** [HDK p.29].
|
||||
|
||||
**Port re-use** [HDK p.30]:
|
||||
|
||||
- **PA0–PA7** — 8-bit non-latched GP I/O; in peripheral mode **PA0–PA3 = UART RX, CTS, DSR, DCD**;
|
||||
**PA4** is a change-of-state interrupt source (e.g. Centronics **BUSY**, or **RI** on the VIC —
|
||||
see below). In pack mode PA0–PA7 is the memory data bus.
|
||||
- **PB0–PB7** — latched output / **counter** mode; in pack mode = address **A0–A7**.
|
||||
- **PD0–PD7** — GP outputs; in pack mode = address **A8–A15**; in peripheral mode **PD0/PD1/PD2 =
|
||||
UART TX, RTS, DTR**.
|
||||
- **PC0–PC4** — in pack mode = address **A16–A20**; in peripheral mode **PC4/PC7** = inverted
|
||||
edge-triggered interrupt inputs, **PC5** = interrupt output, **PC6** = GP latched output +
|
||||
pack/peripheral select at reset, **PC0–PC3** = a **dual synchronous serial port** for magnetic
|
||||
card readers.
|
||||
|
||||
**UART details** [HDK p.29–30, p.61]: needs the host link in **continuous-clocking** mode (baud
|
||||
clocks are derived from the 1.536 MHz link clock). No internal buffering — the **Transmitter
|
||||
Holding Register must be empty** before writing. One shared **active-high interrupt line**; software
|
||||
reads the status/interrupt registers to find the cause. Reading the UART Status register clears the
|
||||
Tx interrupt.
|
||||
|
||||
**Barcode:** the UART receives data from a dedicated barcode-scanner IC [HDK p.30].
|
||||
**Card readers:** two synchronous serial ports (PC0–PC3) take clocked serial data [HDK p.30].
|
||||
|
||||
**Reset config** read from **PA0–PA7** (device type/size, like ASIC4); **PC6** selects pack vs
|
||||
peripheral; in peripheral mode PA0–PA7 indicate the peripheral type (RS232 port / Centronics port,
|
||||
combinable) [HDK p.31].
|
||||
|
||||
### 6.2 ASIC5 register set — the sixteen registers
|
||||
|
||||
Reading/writing a register is two steps: send a control byte selecting the register, then
|
||||
read/write the data [HDK p.56]. Register list [HDK p.56–57, cross-checked against the
|
||||
`SYS$AS5.ASM` defines at HDK p.102]:
|
||||
|
||||
| Reg | Name | R/W | Function |
|
||||
|-----|------|-----|----------|
|
||||
| **0** | **Port A data** | R/W | Read/write Port A; a read/write triggers a memory access cycle (pack mode) or one-cycle CS0 strobe (peripheral mode). |
|
||||
| **~2** | **Port B data** | R/W | Latches PB0–PB7 (= A0–A7 in pack mode); read returns last value. |
|
||||
| **~4** | **Port B mode / Inc** | R/W | **bit 0 = 0 memory / 1 peripheral (enables UART)**; also selects **counter** vs **latch** mode and "baud rate out on Port B". Counter increments on Port A access. |
|
||||
| **~5** | **Port C/D write** | W | First write latches PD0–PD7; subsequent (multiwrite) writes latch Port C, incl. **SEL0/SEL1** choosing CS0–CS3. In peripheral mode PD0 = UART TX (bit 0 has no effect). |
|
||||
| **6** | **Interrupt Mask** | R/W | 1 = enable that event's interrupt; 0 = disable. All disabled on reset. |
|
||||
| **~7** | **Interrupt Status / Type-Ctrl** | R | Reading indicates interrupt source; bit meanings mirror the mask. |
|
||||
| **8** | **UART Status / UART Control** | R/W | Format (stop/data bits, parity), break, and status (Tx empty, Rx/parity/framing/overrun errors). |
|
||||
| **9** | **UART Receive / Transmit holding register** | R/W | Read = received char; write = char to transmit. |
|
||||
| **10** | **UART Baud rate LSB** | W | Low byte of baud divisor. |
|
||||
| **11** | **UART Baud rate MSB** | W | High byte of baud divisor. |
|
||||
| **12** | **MCR shift register** | — | Magnetic-card-reader shift register. |
|
||||
| **13** | **Barcode read data** | R | Returns states of the barcode lines / general interrupt bits. |
|
||||
| **14** | **Synchronous Port 2 read** | R | Second synchronous serial port (card reader). |
|
||||
| **(15)** | (sync port 1 / reserved) | — | Synchronous-port-1 data; documented in prose (sync port 1 & 2 char-received are distinct interrupt sources). |
|
||||
|
||||
> The HDK's printed register table is OCR-garbled for some indices; the register **names, R/W
|
||||
> attributes and functions** above are taken verbatim from the HDK prose (p.56–57) and the
|
||||
> assembler comment block in `SYS$AS5.ASM` (p.102): *Port A R/W · Port B R/W · Inc/Mode · Port CD
|
||||
> write-only · Interrupt mask R/W · IntType/Ctrl · UART Status/Ctrl · Receive/Transmit · Baud Rate
|
||||
> (×2, write-only) · MCR shift register · Barcode data & ints.* Exact numeric slots for the
|
||||
> mid-range registers should be confirmed against the include files (`ospack.inc`, `ossibo.inc`).
|
||||
|
||||
**Interrupt-mask bits** [HDK p.56, and `S_*` defines p.102]: UART character received; UART
|
||||
transmitter awaiting character; modem/handshake change of state; **synchronous port 1 char
|
||||
received**; **synchronous port 2 char received**; **barcode data / general interrupt**. Reading the
|
||||
**Interrupt Status** register (same bit layout, high = active) identifies the source [HDK p.56].
|
||||
|
||||
**UART Status/Control bits** (`SYS$AS5.ASM`, HDK p.102):
|
||||
|
||||
| Define | Value | Meaning |
|
||||
|--------|-------|---------|
|
||||
| `S_RXENB` | `0000_0001b` | Receive interrupt enable |
|
||||
| `S_TXENB` | `0000_0010b` | Transmit interrupt enable |
|
||||
| `S_TXEMPTY` | `0001_0000b` | Transmit buffer empty (status) |
|
||||
| `S_RXINT` | `0000_0001b` | Receive interrupt pending |
|
||||
| `S_TXINT` | `0000_0010b` | Transmit interrupt pending |
|
||||
| `S_MDINT` | `0000_0100b` | Modem-status interrupt |
|
||||
| `S_CTS`/`S_RTS`/`S_DCD`/`S_DSR`/`S_DTR` | `01b`/`02b`/`04b`/`02b`/`04b` | Handshake-line states |
|
||||
| `OVERRUN_ERROR` | `0100_0000b` | Character overrun |
|
||||
| `PARITY_ERROR` | `1000_0000b` | Parity error |
|
||||
| `S_PERIPHERALMODE` | `0000_0011b` | ASIC5 RS232 (peripheral) mode |
|
||||
| `S_UART_OFF` | `0000_0010b` | ASIC5 peripheral mode, UART off |
|
||||
|
||||
**Baud rate.** `Divisor = 1 − (96000 / desired_baud)`, written as a 16-bit word: LSB → reg 10,
|
||||
MSB → reg 11 [HDK p.61]. The driver's `BaudRateTable` (HDK p.102) holds the two's-complement
|
||||
divisors, e.g. `-0x077F, -0x04FF, -0x0368, -0x02CC, -0x027F, -0x013F, -0x009F, -0x004F, -0x0035,
|
||||
-0x0030, -0x0027, -0x0019, -0x0013, -0x000C, -0x0009, -0x0004` for the standard rates.
|
||||
|
||||
### 6.3 Selecting / configuring ASIC5
|
||||
|
||||
Before use, select the chip (and re-select after every hold/resume from power-down, pack-door, or
|
||||
insert/remove) [HDK p.56]. Selection differs by mode:
|
||||
|
||||
- Peripheral: `mov al,(SerialSelect or Asic5NormalId)` → `SCONTOUT` → `SDATAIN`; a **zero reply
|
||||
means no ASIC5 present** [HDK p.56].
|
||||
- Pack: same with `Asic5PackId` [HDK p.56]. An **ASIC4 will answer a pack-mode select** (it
|
||||
impersonates an ASIC5 in pack mode) [HDK p.56].
|
||||
|
||||
---
|
||||
|
||||
## 7. I/O ports, interrupts and the channel model
|
||||
|
||||
### 7.1 Eight hardware interrupts
|
||||
|
||||
SIBO supports **8 level-triggered hardware interrupts**, IRQ0 (highest) … IRQ7 (lowest), handled by
|
||||
the interrupt controller in **ASIC1 (or ASIC9)**. Expansion ports carry an interrupt line into this
|
||||
controller [HDK p.3]:
|
||||
|
||||
- **Extended internal** expansion ports: **active-low** interrupt input.
|
||||
- **Reduced external** expansion ports (S3a 6-pin, Workabout LIF): **active-high** interrupt input.
|
||||
|
||||
Servicing order [HDK p.3]:
|
||||
|
||||
1. Device asserts its IRQ line.
|
||||
2. The controller (ASIC2/ASIC9) places the **vector of the highest-priority pending device** on the
|
||||
data bus; the CPU jumps to that ISR.
|
||||
3. The ISR clears the interrupt at the device (device-specific action).
|
||||
4. The ISR writes the **non-specific end-of-interrupt (NSEOI/NSEOD)** location to tell the
|
||||
controller the interrupt is cleared.
|
||||
5. Repeat if another is pending.
|
||||
|
||||
**Nested interrupts are not possible** on these 8086-class processors [HDK p.3].
|
||||
|
||||
### 7.2 The four channel variables
|
||||
|
||||
To be portable across host machines, a driver parameterises four values per expansion channel
|
||||
[HDK p.52]:
|
||||
|
||||
- **Channel interrupt mask** — an 8-bit value OR'd into the mask register (`A1InterruptMask` or
|
||||
`A9BInterruptMask`, depending on whether the controller is ASIC1 or ASIC9) to enable interrupts on
|
||||
that channel; also passed to `HwGetChannel` / `HwFreeChannel`.
|
||||
- **Channel interrupt number** — 16-bit, used with `GenSetRevector` / `GenResetRevector` to say
|
||||
which default ISR the driver replaces.
|
||||
- **Channel interrupt vector** — 16-bit pointer to the driver's ISR.
|
||||
- **Hardware SIBO channel** — used by `HwSelectChannel` to route serial frames to that channel.
|
||||
|
||||
### 7.3 Per-platform channel map [HDK p.52]
|
||||
|
||||
| Host | Controller | Ports and channels |
|
||||
|------|-----------|--------------------|
|
||||
| **Series 3** | ASIC2 | Expansion port C = serial **channel 7**, interrupt `Asic2Int` (IRQ4), select `SelectChannel7`. |
|
||||
| **Series 3a** | ASIC9 | Expansion port C = serial **channel 5**, mask `A9MSlave`, revector `IHwIrq2Revector` (IRQ2), select `SelectChannel5`. |
|
||||
| **Workabout / HC (3-channel)** | ASIC9 / ASIC2 | Port **A** (top) and port **B** (bottom) internal + port **C** (side/cradle); masks `ExpIntLeftA` / `A9MExpIntA`, `ExpIntRightB` / `A9MExpIntB`, plus the slave/`Asic2Int` channel; selects `ExpChannelLeftA`, `ExpChannelRightB`, `SelectChannel5/7`. |
|
||||
|
||||
Build flags select the machine so one driver source compiles for all: **Consumer (S3a)** = 1 SIBO
|
||||
channel; **HC/S3C** = 3 channels; S3 = single-channel [HDK p.51–52; `SYS$AS5.ASM` p.102].
|
||||
|
||||
### 7.4 Owning and selecting a channel
|
||||
|
||||
The S3/S3a/HC have **three** Psion serial links: two are the SSD slots, the third is the expansion
|
||||
port [HDK p.60]. Only **one link is selected at a time**.
|
||||
|
||||
- **`HwGetChannel`** (AL = channel interrupt mask): reserve a channel; **carry clear = success**.
|
||||
Normally called from the driver's **open** vector; open should fail if the channel is unavailable
|
||||
[HDK p.60].
|
||||
- **`HwFreeChannel`** (AL = mask): release it (on close) [HDK p.60].
|
||||
- **`HwSelectChannel`** (AL = select code): make a channel current; **returns the previously
|
||||
selected channel in AL** so it can be restored on exit [HDK p.60].
|
||||
|
||||
Discipline [HDK p.60]: select the correct channel on entry to any vector/ISR that talks down it, and
|
||||
restore the previous one on exit. **Disable interrupts (multitasking) between select and restore** —
|
||||
but only briefly, because the **watch-dog** forbids leaving interrupts off indefinitely; hence
|
||||
comms happen in short bursts. Idiom:
|
||||
|
||||
```asm
|
||||
pushf
|
||||
cli
|
||||
mov al, SelectChannel5
|
||||
HwSelectChannel ; old channel returned in AL
|
||||
push ax
|
||||
; ... Input / Output to the peripheral ...
|
||||
pop ax
|
||||
HwSelectChannel ; restore previous channel
|
||||
popf
|
||||
```
|
||||
|
||||
### 7.5 Channel unit letters (open qualifiers)
|
||||
|
||||
A driver can support several channels distinguished by a **single-letter qualifier** appended to
|
||||
the device name, allocated from `'A'` [HDK p.38]. Convention: **A = top port, B = bottom port,
|
||||
C = side/cradle port** [HDK p.38]. Example: `p_open(&pcb,"PAR:A",-1)`. On the S3a only **one** SIBO
|
||||
channel (port C) can be opened; on Workabout/HC up to **three** (A, B, C) [HDK p.38]. The VIC's
|
||||
extra serial connectors appear to the Workabout as standard serial devices **ports I and F**, and
|
||||
the extended 15-way port is addressed as **port C** [WPG Appendix A, VIC].
|
||||
|
||||
### 7.6 Direct I/O (HC extended internal ports)
|
||||
|
||||
On the HC's 25-way extended internal ports, expansion devices also get **direct processor I/O**:
|
||||
`AD0–AD7` (multiplexed addr/data, low half only → up to 128 I/O addresses, even addresses only),
|
||||
`ALE`, `IOWR`, `IORD`, `EES` (External Expansion Select — high during I/O to that device), `INTR`
|
||||
(active-high IRQ to ASIC1). Port 1 decodes I/O `100–1FF`, port 2 `200–2FF`; A0 is always low for
|
||||
valid writes so A1 is the lowest usable address line [HDK p.19]. Each HC port also carries one SIBO
|
||||
serial channel (port 1 = channel 5, port 2 = channel 6) [HDK p.20]. (The Workabout uses a 26-way
|
||||
Torson connector for its internal expansion — see §8.)
|
||||
|
||||
---
|
||||
|
||||
## 8. Expansion slots, ports and connectors
|
||||
|
||||
### 8.1 Overview by machine [HDK p.2]
|
||||
|
||||
| Machine | SSD ports | Expansion |
|
||||
|---------|-----------|-----------|
|
||||
| MC / HC | 2 | Two independent single-row **25-way extended internal** ports (SIBO channel + direct parallel I/O + 7.2 V battery). |
|
||||
| Series 3 / 3a | 2 | One **6-pin reduced external** port (port C): SIBO serial channel + limited power (**< 25 mA**). |
|
||||
| **Workabout** | 2 (A: top, B: bottom) | Two internal expansion points + one **11-pin LIF** external port. Each internal port = single-row **26-way** carrying two high-speed serial ports. |
|
||||
|
||||
### 8.2 Workabout internal 26-way (Torson) connector [HDK p.13]
|
||||
|
||||
Signals: **Vcc1** = 3.0 V nominal, **≤ 100 mA**; **Vcc2** = 5 V nominal, **≤ 200 mA**; **RUN**
|
||||
(low = powered down, high = powered up — used to power-down/reset the module); **SCK2/SCK3** serial
|
||||
clocks (run continuously at **1.536 MHz** when the port is in use; clock the ASIC5 UART in the
|
||||
RS232-AT/TTL and AT/Barcode modules); **SD2/SD3** bi-directional serial data; **EINT1/EINT2**
|
||||
active-low interrupt inputs; **EXON** active-high turn-on. All logic at **3.0/3.3 V** (per Vcc1).
|
||||
Unused lines are reserved for a codec interface.
|
||||
|
||||
The Workabout expansion module (RS232/TTL or RS232/Barcode) connects the main board to the module
|
||||
via this 26-way Torson connector + flexi cable; modules are **factory-fitted** [WPG p.1-3,
|
||||
Appendix A]. The RS232/TTL module runs up to **19,200 baud**; the RS232/Barcode module uses an HP
|
||||
HBCR-1612 micro to decode and transmit barcode data at up to **9,600 baud** [WPG Appendix A].
|
||||
|
||||
### 8.3 Workabout external 11-pin LIF connector [HDK p.14–16]
|
||||
|
||||
The reduced external port for connecting to the Cradle system. Standard pin functions (Polarisation
|
||||
Type B, cradle perspective) [HDK p.16]:
|
||||
|
||||
| Pin | Name | Function |
|
||||
|-----|------|----------|
|
||||
| 1 | LCA | Local Computer Active — high when the computer is on. Workabout can **source 100 mA** from this pin (HC/HCDOS: 5 mA). |
|
||||
| 2 | EXON | External switch-ON, active-high (+5 V) — asserted by a remote/cradle device to switch the computer on. |
|
||||
| 3 | THERM | Battery thermistor terminal. |
|
||||
| 4 | DLA | Disconnect Local ASIC (does not apply to Workabout). |
|
||||
| 7 | VIN | Power supply to computer (+10 V). |
|
||||
| 8 | SCLK | Serial-channel clock. |
|
||||
| 9 | GND | Power / signal ground / −ve battery (1 A). |
|
||||
| 10 | SDATA | Serial-channel data. |
|
||||
| 11 | STATUS | Cradle status; pulled-up for open-collector sensing (low = remote device present). |
|
||||
|
||||
Type-A polarisation repurposes several pins for RS232 signals. The LIF cover can be moulded with a
|
||||
polarising pin in up to four positions to differentiate variants [HDK p.14].
|
||||
|
||||
### 8.4 S3/S3a 6-pin reduced external port (port C) [HDK p.12]
|
||||
|
||||
Six wires: **MSD/MCLK** (a master SIBO serial channel — **channel 7 on S3, channel 5 on S3a**),
|
||||
**SDS/INT** and **SCK/EXON** (dual-function: form a slave serial channel, or SDS/INT is an
|
||||
active-high interrupt, and a rising edge on SCK/EXON wakes the machine from standby), **VCC** (+5 V,
|
||||
switched off in standby, **max 25 mA**). Opening the pack doors on an S3a or Workabout **cuts power
|
||||
to external peripherals** [HDK p.12].
|
||||
|
||||
---
|
||||
|
||||
## 9. Power
|
||||
|
||||
- **Workabout power sources** [WPG p.1-4, Appendix A]: NiCd rechargeable pack **or** 2 × AA
|
||||
alkaline (main), plus a **3 V lithium CR1620 backup** battery that preserves internal RAM when the
|
||||
main source is absent (recommend yearly replacement; alone it preserves memory for a limited
|
||||
period). External: a Series 3 mains adaptor via the **LIF Converter** (also trickle-charges), and
|
||||
(from Jan/Feb 1997) a **vehicle power adaptor** / Docking Station.
|
||||
- Power management is **software-controlled** and selective; the machine auto-switches-off after
|
||||
~5 minutes idle by default [WPG p.1-4, §basic hardware].
|
||||
- Under **low battery** the Workabout may run the screen/keyboard but refuse to write Flash SSD or
|
||||
access expansion devices; it turns off if such an operation is attempted [WPG p.1-4].
|
||||
- **States:** standby vs operating/idle. In standby, switched supplies (VCC/Vcc2) are **off**;
|
||||
peripheral designs must not drive lines high in standby (pull-down inputs cause excessive standby
|
||||
current) [HDK p.12, p.19].
|
||||
- **Peripheral power budgets** (from the connector tables): S3a port C **< 25 mA**; HC 25-way Vcc2
|
||||
**50 mA** (or use unregulated **Vsup** 5.5–12 V with a low-dropout regulator for higher loads);
|
||||
Workabout Torson **Vcc1 100 mA / Vcc2 200 mA**; LIF **LCA 100 mA** [HDK p.12, p.19–20, p.13,
|
||||
p.16].
|
||||
- **Power-fail handling for drivers** [HDK p.41]: on power loss there is ~**2 ms** to power
|
||||
everything down; the hold vector must be fast or the RAM-hold voltage drops and the machine
|
||||
cold-reboots, losing internal RAM (including any loaded driver). On a power-fail hold the OS has
|
||||
already reset the SIBO channels, so a channel driver need only record the reason for its resume
|
||||
vector. Hold reasons passed in AH: `DevHoldNormal` (memory move), `DevHoldPowerDown` (standby),
|
||||
`DevHoldPowerFail` (supply lost) [HDK p.42].
|
||||
|
||||
---
|
||||
|
||||
## 10. The SIBO serial link (host ↔ PC / peripheral)
|
||||
|
||||
- The **3-Link** is an **ASIC5-based** cable that converts the S3/S3a port-C SIBO serial channel to
|
||||
**RS232** (host ↔ PC/printer), using the ASIC5 on-board UART + line drivers [HDK p.5, p.35].
|
||||
- Workabout communications: the OS provides serial comms at up to **19,200 baud**, with **XMODEM /
|
||||
YMODEM** (and ZMODEM except in early models) file transfer, plus a scripting language for modem
|
||||
control [WPG §basic hardware, Tech Spec].
|
||||
- The high-speed inter-machine link is the same continuously-clocked SIBO serial channel described
|
||||
in §4; the physical media are the S3a 6-pin port C, the HC 8-pin side port C (used by the HC
|
||||
cradle), or the Workabout 11-pin LIF via the LIF Converter [HDK p.12, p.20; WPG Appendix A].
|
||||
|
||||
---
|
||||
|
||||
## 11. Programmer's cheat-sheet
|
||||
|
||||
- Talking to a peripheral = **own a channel** (`HwGetChannel`) → **select it** (`HwSelectChannel`,
|
||||
save/restore the old one, interrupts off, short bursts) → drive the slave with control/data frames
|
||||
via `SCONTOUT`/`SDATAOUT`/`SDATAIN` → **free it** (`HwFreeChannel`) on close [HDK p.52, p.60].
|
||||
- **ASIC4 access** = set Address register (multi-byte, LSB first) → read/write Data register; only
|
||||
even/low-256 addresses in the simple routines; interrupts off during a transfer [HDK p.53–59].
|
||||
- **ASIC5 UART** = enable continuous clocking, set peripheral bit (Port-B mode bit 0), write baud
|
||||
divisor `1 − 96000/baud` (LSB reg 10, MSB reg 11), set format in UART Status/Control (reg 8),
|
||||
Tx via reg 9 only when `S_TXEMPTY`, Rx via reg 9, one shared active-high IRQ; read status/interrupt
|
||||
registers to find the cause [HDK p.56–61].
|
||||
- **Interrupts** = 8 levels, IRQ0 highest, level-triggered, **no nesting**; clear at the device then
|
||||
write NSEOI [HDK p.3].
|
||||
- **Segments** = 8086 model; writing outside your data segment or hogging interrupts → **panic**
|
||||
[WPG §Resetting; HDK p.2].
|
||||
|
||||
---
|
||||
|
||||
## 12. Known gaps / uncertainties in these sources
|
||||
|
||||
- **V30MX / ASIC9MX / Workabout MX:** absent from both supplied manuals. All MX statements above
|
||||
are flagged **[MX — not in supplied manuals]** and are orientation only. Confirm against the MX
|
||||
hardware reference.
|
||||
- **Host CPU physical memory map and any bank/page registers:** not in these manuals (deferred to
|
||||
*SIBO Computers Programmers Reference* / *Hardware Reference*). Only the peripheral-side
|
||||
(ASIC4/ASIC5) chip-select block decode is documented here.
|
||||
- **Exact ASIC5 register numeric slots (regs ~2, 4, 5, 7):** the printed table is OCR-damaged; names
|
||||
and functions are reliable (prose + `SYS$AS5.ASM`), but verify the numeric indices against
|
||||
`ospack.inc` / `ossibo.inc`.
|
||||
- Several connector pin tables in the WPG appendix are OCR-garbled (RS232 module pinouts, VIC 15-way
|
||||
pinout); signal names are recoverable but pin numbers should be checked against a clean copy.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Boot & OS-call internals (reverse-engineered from the ROM under MAME)
|
||||
|
||||
Source: dynamic trace of `psionwamx` (v7.20f) under MAME — 170,160 instructions
|
||||
of boot captured with correct bank mapping. Everything here is **reverse-
|
||||
engineered / observed**, not from a manual, and describes internal mechanisms
|
||||
below the documented C API.
|
||||
|
||||
## Reset & early boot
|
||||
|
||||
```
|
||||
FFFF0: jmp A000:0 ; reset vector -> ROM
|
||||
A0000: jmp A14A4
|
||||
A14A4: di ; disable interrupts
|
||||
in/out 30h,... ; ASIC hardware init via I/O ports
|
||||
mov ds0,190h ; segment setup
|
||||
out 2h / 2Ch / 22h / 26h / 24h / 3Ch ; ASIC control-register init
|
||||
jmp AE69C ; signature check ([6896h]==0F0A5h) then continue
|
||||
```
|
||||
Early boot disables interrupts, programs the ASIC control registers through the
|
||||
I/O ports, sets up segments, and validates a ROM signature word.
|
||||
|
||||
## OS service calls are software interrupts (not a single gate)
|
||||
|
||||
SIBO dispatches operating-system services through a **range of `INT` vectors**
|
||||
(NEC V-series `brk` mnemonic), each vector a service group with a function
|
||||
selector in a register. Vectors observed executing during boot, by frequency:
|
||||
|
||||
| Vector | Count (boot) |
|
||||
| --- | --- |
|
||||
| `0xA6` | 414 |
|
||||
| `0x97` | 81 |
|
||||
| `0x85` | 49 |
|
||||
| `0x96` | 41 |
|
||||
| `0xBA` | 41 |
|
||||
| `0x8B` | 17 |
|
||||
| `0xB9` | 15 |
|
||||
| `0xAC` | 12 |
|
||||
| others `0x81`–`0xB5` | few each |
|
||||
|
||||
Additional service vectors seen in driver code (not exercised at boot):
|
||||
- **`INT 0xCF`** — the I/O executive: `CL` = function code, `BX` = channel
|
||||
handle, `DX` = argument, result in `AX`. This is the layer beneath `p_iow`.
|
||||
- **`INT 0xD9`**, **`INT 0xD3`** — further service vectors (exact roles TBD).
|
||||
|
||||
The specific service behind each vector is not yet mapped; doing so means
|
||||
breakpointing each ISR and correlating with the documented C calls. This table
|
||||
is the starting point.
|
||||
|
||||
## ASIC I/O ports touched during boot
|
||||
|
||||
Distinct `out` port targets and write counts during boot (ASIC register access —
|
||||
cross-reference the ASIC register maps in the hardware reference):
|
||||
|
||||
| Port | Writes | Likely role |
|
||||
| --- | --- | --- |
|
||||
| `0x28` | 1580 | display/LCD controller (dominant) |
|
||||
| `0x15` | 88 | |
|
||||
| `0x21` | 54 | |
|
||||
| `0x10` | 17 | |
|
||||
| `0x02` | 14 | ASIC control |
|
||||
| `0x0A` | 13 | |
|
||||
| `0x08` | 12 | |
|
||||
| `0x2C` `0x24` `0x22` `0x26` `0x30` `0x3C` | few | ASIC control-register setup (from early boot) |
|
||||
|
||||
## Method (reproduce)
|
||||
|
||||
```
|
||||
xvfb-run -a mame psionwamx -rompath roms -debug \
|
||||
-debugscript trace.txt -sound none -seconds_to_run 1
|
||||
# trace.txt: trace boot_trace.asm / go
|
||||
```
|
||||
Then post-process `boot_trace.asm` for instruction/port/vector frequencies.
|
||||
|
||||
## Next RE steps
|
||||
|
||||
1. Map each `brk` vector to its service (breakpoint ISR, correlate to C API).
|
||||
2. Trace `SCANAPP`/`DEMMAN` to resolve the `WL2` scanner data-retrieval path.
|
||||
3. Correlate the ASIC ports here with the HDK register maps.
|
||||
Binary file not shown.
Reference in New Issue
Block a user