diff --git a/docs/reference/03-io-devices.md b/docs/reference/03-io-devices.md new file mode 100644 index 0000000..48324ed --- /dev/null +++ b/docs/reference/03-io-devices.md @@ -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: + +``` +-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) |