# 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: ``` -y general form -y set-termination-string form E hard reset (special form) ``` `` = 0x1B, then `-` (0x2D), then `y`/`Y`, a decimal `` of 1–3 digits (0–255), and an uppercase command letter; no embedded spaces. Multiple option bits are combined by **summing** their `` values. Multiple commands may be concatenated in one sequence — intermediate command letters lowercase, the final one uppercase (e.g. `-y13f2h1D`). Command letters (each `-y`): | Cmd | Function | | --- | -------- | | `D` | serial inter-character delay (0 = none, 1 = 10 ms) | | `E` | hard reset + self-test (special form `E`; reverts all options to default; self-test failures reported as `... SELF TEST FAILED`) | | `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 (`-yO`, 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) |