Files
sibo-playground/docs/reference/03-io-devices.md

673 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.0164 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.1517)
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 13 digits
(0255), 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 13.
---
### 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.1517 |
| 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) |