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

34 KiB
Raw Blame History

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

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

/* 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_leaves on error.
INT p_write(VOID *pcb, VOID *buf, UINT len) p_iow(P_FWRITE) returns 0 or negative error. f_write p_leaves on error.
INT p_seek(...) p_iow(P_FSEEK) FIL: only (Files chapter).

(PLIB Ch.9.)

2.3 Waiting and cancelling

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

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:

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_FWRITEp_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_FSENSEp_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_FSETp_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_FSTARTp_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_FSETE_SOUND struct (epoc.h):
    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_FALARMp_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.
    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_FREADp_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_FIRSELECTP_FIRMAKECONNECT (primary) / P_FIRAWAITCONNECT (secondary), exchanging up to 56 bytes of connect data → P_FREAD/P_FWRITEP_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_FREADp_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)