docs(reference): 03-io-devices — SIBO/Workabout MX programming reference
This commit is contained in:
@@ -0,0 +1,672 @@
|
||||
# Psion SIBO / Workabout MX — I/O Device Model and Drivers
|
||||
|
||||
Reference for the EPOC (SIBO) I/O system as documented in the Psion SIBO 'C' SDK.
|
||||
Everything here is drawn from two manuals:
|
||||
|
||||
- **PLIB Reference** — I/O System chapter (Ch. 9), Asynchronous Requests and
|
||||
Semaphores (Ch. 8), Memory Allocation (Ch. 7). Cited as *PLIB*.
|
||||
- **I/O Devices Reference** v2.30 (March 1999). Cited as *IODEV*.
|
||||
|
||||
Where a fact is specific to the Workabout / Workabout MX, or is uncertain from the
|
||||
manuals, it is flagged. The MX integral laser scanner is **not** covered by these
|
||||
manuals; see the cross-reference in the Barcode section.
|
||||
|
||||
> Notation used by the manuals: an I/O service is identified by a function code of
|
||||
> the form `P_Fxxx` (defined in `p_file.h`). The manuals write `p_iow(P_FWRITE)`,
|
||||
> `p_ioc(P_FWRITE)` or just `P_FWRITE` to refer to that service. (*IODEV* Ch.1;
|
||||
> *PLIB* Ch.9.)
|
||||
|
||||
---
|
||||
|
||||
## 1. The device model
|
||||
|
||||
### 1.1 Device drivers: LDDs and PDDs
|
||||
|
||||
The purpose of a device driver is to hide the underlying hardware behind a stable
|
||||
software interface. EPOC defines two driver types (*PLIB* Ch.9, "LDDs and PDDs"):
|
||||
|
||||
- **PDD — physical device driver**: hardware dependent (the lower layer).
|
||||
- **LDD — logical device driver**: hardware independent (the upper layer).
|
||||
|
||||
Applications normally interface to **LDDs only**. An LDD may use one or more PDDs.
|
||||
The serial driver is the canonical example: an upper hardware-independent LDD over
|
||||
a hardware-dependent PDD; incoming data is buffered at the LDD level. Some drivers
|
||||
touch no hardware at all — e.g. the C floating-point library is implemented as a
|
||||
device driver (the 8087 emulator LDD `sys$8087.ldd`). (*PLIB* Ch.9; Ch.5.)
|
||||
|
||||
Some drivers are built into the ROM (e.g. the RS232 LDD/PDD); others are external
|
||||
and must be loaded (e.g. a bar-code reader). (*PLIB* Ch.9.)
|
||||
|
||||
The interface between the OS and an LDD does **not** follow a C calling convention;
|
||||
an LDD may be written in C but requires some 8086 assembly for the LDD vector
|
||||
interface. (*PLIB* Ch.9; System Services reference.)
|
||||
|
||||
### 1.2 Opening a channel: `p_open`
|
||||
|
||||
```c
|
||||
INT p_open(VOID **ppfcb, TEXT *name, UINT mode);
|
||||
INT f_open(VOID **ppfcb, TEXT *name, UINT mode); /* p_leave()s on error */
|
||||
```
|
||||
|
||||
`name` is a **3-character device name terminated by a `':'`**, optionally followed
|
||||
by further text depending on the device. Examples (*PLIB* Ch.9):
|
||||
|
||||
| Name | Device |
|
||||
| ----- | ------ |
|
||||
| `FIL:` | a file |
|
||||
| `PAR:` | a parallel port |
|
||||
| `TTY:` | an RS232 serial port |
|
||||
| `TIM:` | an asynchronous timer |
|
||||
|
||||
Rules (*PLIB* Ch.9, "Opening a channel to a device"):
|
||||
|
||||
- The device name of an external LDD bears **no relation** to the file it was
|
||||
loaded from.
|
||||
- Where a driver supports more than one **unit**, the `':'` is followed by a unit
|
||||
letter: `TTY:A` = serial port A, `PAR:B` = parallel port B.
|
||||
- For `FIL:`, the qualifying text is a file name or full path.
|
||||
- A loaded device driver **supersedes** any existing device of the same name.
|
||||
|
||||
On success the channel control block address is written to `*ppfcb`. On failure
|
||||
`*ppfcb` is left untouched — pre-set it to zero so `p_close(0)` (a no-op) is safe
|
||||
in clean-up. For an **attached** driver, the control block is attached to `*ppfcb`
|
||||
and the value is not changed. (*PLIB* Ch.9.)
|
||||
|
||||
Errors: `E_FILE_ALLOC` (no memory for control block), `E_FILE_DEVICE` (device does
|
||||
not exist), `E_GEN_ARG` (bad `mode`, possibly from falling through to `FIL:`).
|
||||
|
||||
### 1.3 The `FIL:` fallback
|
||||
|
||||
If `p_open` fails to match a device name, the name is **passed to the `FIL:`
|
||||
driver** (the process must be connected to the file server). This makes the leading
|
||||
`FIL:` optional when opening files:
|
||||
|
||||
```
|
||||
p_open(..., "C:\\NOTES\\NEW.TXT", ...) == p_open(..., "FIL:C:\\NOTES\\NEW.TXT", ...)
|
||||
```
|
||||
|
||||
Side effect: opening a **non-existent device** does not give the expected
|
||||
`E_FILE_DEVICE`, because the name is handed to `FIL:` and the result depends on
|
||||
`mode` (it might even succeed). Passing `mode = -1` guarantees the `FIL:` open
|
||||
fails — albeit with the misleading `E_GEN_ARG`. Keeping an explicit `FIL:` prefix
|
||||
avoids mistaking a file spec for a device name (e.g. a file literally named
|
||||
`TTY:`). (*PLIB* Ch.9.)
|
||||
|
||||
### 1.4 Modes
|
||||
|
||||
The interpretation of `mode` **depends on the device**; many devices ignore it.
|
||||
When a device ignores `mode`, pass `-1`. (*PLIB* Ch.9.) The character/hardware
|
||||
devices in *IODEV* (`TTY:`, `PAR:`, `SND:`, `FRC:`, `MCR:`, `BAR:`) are all opened
|
||||
with `mode = -1`. File-open mode flags such as `P_FOPEN`, `P_FREPLACE`,
|
||||
`P_FUPDATE`, `P_FUNIQUE`, `P_FSTREAM`, `P_FSHARE`, `P_FTEXT` are `FIL:`-specific and
|
||||
are described in the Files chapter of *PLIB*.
|
||||
|
||||
### 1.5 Attached drivers
|
||||
|
||||
An **attached driver** is an LDD layered over another LDD, replacing or augmenting
|
||||
its services. Opening it *attaches* to an already-open underlying channel rather
|
||||
than allocating a new one. Example: the printer driver `PRO:` is opened over a
|
||||
print-output device (`PAR:`, `TTY:` or `FIL:`) and thereafter replaces that
|
||||
device's `P_FWRITE`, `P_FCANCEL` and `P_FCLOSE`. Attached drivers that work
|
||||
asynchronously install a **device wait handler** at open, called from within the
|
||||
opening process's `p_iowait`. (*PLIB* Ch.9, "Attached drivers".)
|
||||
|
||||
### 1.6 The file server
|
||||
|
||||
`FIL:` operations are serviced by the high-priority system process **`SYS$FSRV`**,
|
||||
which also loads executables and **loads external device drivers**. It serialises
|
||||
access to shared storage (SSDs) and uses PDDs to reach the many FLASH/RAM/ROM
|
||||
configurations. Applications are "clients" of the file server; the normal start-up
|
||||
code connects to it. (*PLIB* Ch.9, "The file server".)
|
||||
|
||||
---
|
||||
|
||||
## 2. Operations on an open channel
|
||||
|
||||
All I/O requests are **asynchronous in principle** — the process I/O semaphore is
|
||||
always signalled on completion — but many are implemented **synchronously**
|
||||
(completing before the call returns). A typical device offers zero to three truly
|
||||
asynchronous functions (commonly `P_FREAD` / `P_FWRITE`); the rest are synchronous.
|
||||
`P_FCLOSE` is always synchronous. (*PLIB* Ch.9; *IODEV* Ch.1.)
|
||||
|
||||
### 2.1 The I/O primitives
|
||||
|
||||
```c
|
||||
/* asynchronous, CDECL + register-calling variants */
|
||||
INT p_ioa (VOID *pcb, INT func, WORD *pstat, ...);
|
||||
INT p_ioa3(VOID *pcb, INT func, WORD *pstat);
|
||||
INT p_ioa4(VOID *pcb, INT func, WORD *pstat, VOID *a1);
|
||||
INT p_ioa5(VOID *pcb, INT func, WORD *pstat, VOID *a1, VOID *a2);
|
||||
|
||||
/* asynchronous, error folded into *pstat (preferred over p_ioa) */
|
||||
VOID p_ioc (VOID *pcb, INT func, WORD *pstat, ...);
|
||||
VOID p_ioc3(VOID *pcb, INT func, WORD *pstat);
|
||||
VOID p_ioc4(VOID *pcb, INT func, WORD *pstat, VOID *a1);
|
||||
VOID p_ioc5(VOID *pcb, INT func, WORD *pstat, VOID *a1, VOID *a2);
|
||||
|
||||
/* synchronous: start then wait (calls p_waitstat) */
|
||||
INT p_iow (VOID *pcb, INT func, ...);
|
||||
INT p_iow2(VOID *pcb, INT func);
|
||||
INT p_iow3(VOID *pcb, INT func, VOID *a1);
|
||||
INT p_iow4(VOID *pcb, INT func, VOID *a1, VOID *a2);
|
||||
```
|
||||
|
||||
- **`p_ioa`** starts the operation and returns immediately. On success, `*pstat`
|
||||
holds `E_FILE_PENDING` until the I/O semaphore is signalled, then zero or a
|
||||
negative error. A cancelled operation completes with `E_FILE_CANCEL`. Returns
|
||||
`E_FILE_INV` if `func` is invalid for the device. **The status word must outlive
|
||||
the operation** — never put it on a stack frame you return from. (*PLIB* Ch.9.)
|
||||
- **`p_ioc`** behaves like `p_ioa` except a failure to *start* is reported exactly
|
||||
as if the request had started and then completed with that error (only `*pstat`
|
||||
to check). Preferred over `p_ioa`. (*PLIB* Ch.9.)
|
||||
- **`p_iow`** starts the request and waits (`p_waitstat`) for completion, returning
|
||||
the status. Preferred unless you actually need asynchrony. (*PLIB* Ch.9.)
|
||||
|
||||
The register-calling variants (`p_iow2/3/4`, `p_ioc3/4/5`, `p_ioa3/4/5`) generate
|
||||
smaller code and are preferred where applicable; e.g. `p_ioc3` over `p_ioa3`.
|
||||
(*IODEV* Ch.1; *PLIB* Ch.9.)
|
||||
|
||||
Driver rule: normally **one pending request per I/O operation per channel**. A
|
||||
second `P_FWRITE` while one is pending will `p_panic` the caller; but one read and
|
||||
one write may be pending simultaneously. (*PLIB* Ch.9; *ADDSYS* driver chapter.)
|
||||
|
||||
### 2.2 Convenience functions
|
||||
|
||||
| Function | Wraps | Notes |
|
||||
| -------- | ----- | ----- |
|
||||
| `INT p_close(VOID *pcb)` | `p_iow(P_FCLOSE)` | `p_close(NULL)` returns 0. Always closes even if it returns an error. |
|
||||
| `INT p_read(VOID *pcb, VOID *buf, UINT len)` | `p_iow(P_FREAD)` | returns bytes read, or negative error. `f_read` `p_leave`s on error. |
|
||||
| `INT p_write(VOID *pcb, VOID *buf, UINT len)` | `p_iow(P_FWRITE)` | returns 0 or negative error. `f_write` `p_leave`s on error. |
|
||||
| `INT p_seek(...)` | `p_iow(P_FSEEK)` | `FIL:` only (Files chapter). |
|
||||
|
||||
(*PLIB* Ch.9.)
|
||||
|
||||
### 2.3 Waiting and cancelling
|
||||
|
||||
```c
|
||||
VOID p_iowait(VOID); /* wait on the process I/O semaphore */
|
||||
VOID p_waitstat(WORD *pstat); /* wait until *pstat leaves E_FILE_PENDING */
|
||||
```
|
||||
|
||||
To wait on **one specific** request, use `p_waitstat`; `p_iowait` waits for *any*
|
||||
completion and is used in the central dispatch loop. Every `p_iosignal` must be
|
||||
matched by a `p_iowait` (or a function that calls it). (*PLIB* Ch.8.)
|
||||
|
||||
Cancelling — `p_iow(pcb, P_FCANCEL)` — cancels outstanding async requests on the
|
||||
channel and returns zero (harmless if none pending). Principles (*PLIB* Ch.9):
|
||||
|
||||
- the cancel **precipitates** completion; it does not stop the request completing;
|
||||
- it may or may not be effective (the request may complete naturally first);
|
||||
- **you must still consume the completion signal** — typically an immediate
|
||||
`p_waitstat` to "use up" the signal.
|
||||
|
||||
`p_waitstat` is safer than `p_iowait` for using up a cancelled request's signal.
|
||||
General-purpose synchronous wrappers must use `p_waitstat`, not `p_iowait`.
|
||||
|
||||
---
|
||||
|
||||
## 3. I/O function codes (`p_file.h`)
|
||||
|
||||
Services are named `P_Fxxx` and defined in `p_file.h`. The generic codes that apply
|
||||
across many devices are (*PLIB* Ch.9; *IODEV* Ch.1):
|
||||
|
||||
| Code | Meaning |
|
||||
| ---- | ------- |
|
||||
| `P_FREAD` | read data from a channel |
|
||||
| `P_FWRITE` | write data to a channel |
|
||||
| `P_FCLOSE` | close a channel |
|
||||
| `P_FCANCEL` | cancel outstanding async requests |
|
||||
| `P_FSENSE` | sense channel characteristics |
|
||||
| `P_FSET` | set channel characteristics |
|
||||
| `P_FFLUSH` | flush buffered data |
|
||||
| `P_FCTRL` | test/set control lines (serial) |
|
||||
| `P_FINQ` | inquire supported characteristics |
|
||||
|
||||
Numeric values are assigned in `p_file.h`. The canonical numbering is:
|
||||
|
||||
| Constant | Value |
|
||||
| -------- | ----- |
|
||||
| `P_FREAD` | 1 |
|
||||
| `P_FWRITE` | 2 |
|
||||
| `P_FCLOSE` | 3 |
|
||||
| `P_FCANCEL` | 4 |
|
||||
| `P_FSET` | 7 |
|
||||
| `P_FSENSE` | 8 |
|
||||
| `P_FFLUSH` | 9 |
|
||||
| `P_FCTRL` | 11 |
|
||||
| `P_FINQ` | 12 |
|
||||
|
||||
> Confirmation from the manuals: the AccessIr chapter tabulates its own function
|
||||
> numbers and gives `P_FREAD = 1`, `P_FWRITE = 2` (*IODEV* Ch.16, "Constants").
|
||||
> The System Services driver model (*ADDSYS*) documents the OS-defined common set
|
||||
> `IoFuncRead / IoFuncWrite / IoFuncClose / IoFuncCancel / IoFuncSet /
|
||||
> IoFuncSense / IoFuncFlush`, which map to `P_FREAD … P_FFLUSH`; `p_read`,
|
||||
> `p_write` and `p_close` call the driver with `IoFuncRead`, `IoFuncWrite` and
|
||||
> `IoFuncClose` respectively. Drivers are urged to keep these conventional meanings
|
||||
> so that attached drivers work.
|
||||
|
||||
Device-specific codes also exist, e.g. `P_FSEEK`, `P_FSETEOF` (files); `P_FTEST`
|
||||
(test for input); `P_FEDIT` (console edit); `P_FRELATIVE` / `P_FABSOLUTE` (timers);
|
||||
`P_FSTART` (FRC); `P_FCONNECT` / `P_FDISCONNECT` / `P_FRSUPER` (NCP, Xmodem);
|
||||
`P_FIR*` (IR); `E_FALARM` / `E_FDIAL` / `E_FSSOUNDCHANNELn` (sound). These are
|
||||
listed with the individual devices below and in *IODEV*.
|
||||
|
||||
Error numbers `-32..-63` are reserved for I/O device errors of the form
|
||||
`E_FILE_xxx` (also in `p_file.h`). (*PLIB* Ch.6.)
|
||||
|
||||
---
|
||||
|
||||
## 4. Loading external device drivers
|
||||
|
||||
### 4.1 Load / delete
|
||||
|
||||
```c
|
||||
INT p_loadldd(TEXT *pName); /* default extension .LDD */
|
||||
INT p_loadpdd(TEXT *pName); /* default extension .PDD */
|
||||
INT p_devdel (TEXT *pName, INT devType); /* devType: E_LDD or E_PDD */
|
||||
```
|
||||
|
||||
- `p_loadldd` / `p_loadpdd` load a driver from a file; if `pName` has no extension,
|
||||
`.LDD` / `.PDD` is assumed, and a relative name uses the current path. After
|
||||
loading, a channel is opened with `p_open`. Errors: `E_FILE_EXIST` (same file
|
||||
already loaded), `E_FILE_NXIST` (file missing), `E_GEN_IMAGE` (bad/corrupt
|
||||
format), `E_GEN_NOMEMORY`, `E_GEN_NOSEGMENTS`. Apps that rely on an external LDD
|
||||
should call `p_loadldd` and ignore `E_FILE_EXIST`. (*PLIB* Ch.9.)
|
||||
- `p_devdel` deletes a **RAM-loaded** driver by device name (no trailing `':'`).
|
||||
Only loaded (not ROM) drivers can be deleted. Errors: `E_FILE_DEVICE` (not
|
||||
loaded), `E_GEN_NSUP` (ROM driver), `E_GEN_INUSE` (currently open), or a
|
||||
device-dependent error. **Warning:** a null `pName` deletes the first unloadable
|
||||
driver of that type. Good practice: attempt `p_devdel` when finished and ignore
|
||||
the result (harmless if in use elsewhere or in ROM). (*PLIB* Ch.9.)
|
||||
|
||||
Related helpers: `p_devqu(pName)` returns the number of units an LDD supports (e.g.
|
||||
`p_devqu("TTY")` → 2 if two serial boards are fitted; `E_GEN_FAIL` means unlimited,
|
||||
as `FIL:` returns); `p_devfnd(...)` iterates device names matching a wildcard.
|
||||
(*PLIB* Ch.9.)
|
||||
|
||||
### 4.2 The `.LDD` / `.PDD` model and device memory segments
|
||||
|
||||
External drivers are loaded from a driver file into a **device memory segment**,
|
||||
allocated by the segment allocator. The ability to load and remove drivers **without
|
||||
a system reset** is a key EPOC feature: you can physically attach a peripheral and
|
||||
load its driver without exiting any process. The I/O system also notifies drivers
|
||||
on power off/on so they can save/restore device state. (*PLIB* Ch.9.)
|
||||
|
||||
Memory segments (*PLIB* Ch.7, "Memory Allocation"):
|
||||
|
||||
- **Device segments** are created when an external device is installed and deleted
|
||||
when removed; once created a device segment does not normally change size. The
|
||||
first two device segments are special — the data segments of `SYS$NULL` and the
|
||||
supervisor `SYS$SMANG`.
|
||||
- Device segments are allocated at **lower addresses** than the volatile dynamic
|
||||
segments so that dynamic-segment activity does not move them (a driver normally
|
||||
has to stop working while its segment moves — risking data loss on, say, a serial
|
||||
receive).
|
||||
- Segment name extension conventions: **`.LDD`** = LDD device segment, **`.PDD`** =
|
||||
PDD device segment; `.$SC` primary shared code, `.DYL` dynamic library, `.$nn`
|
||||
process data segment.
|
||||
- The segment index table has fixed sub-capacities — typically **96 total (32
|
||||
device + 64 dynamic)**. Addresses/sizes are in 16-byte paragraphs.
|
||||
|
||||
---
|
||||
|
||||
## 5. Per-device reference
|
||||
|
||||
### 5.1 Serial port — `TTY:` (*IODEV* Ch.4)
|
||||
|
||||
Fully interrupt-driven RS-232. Two cooperating layers: a hardware PDD and a
|
||||
hardware-independent LDD (which buffers incoming data).
|
||||
|
||||
**Device names.** First port `TTY:A`; second `TTY:B`. Availability varies: a Series 3
|
||||
with a 3-Link recognises only `TTY:A`; an MC with two serial/parallel modules has
|
||||
`TTY:A` (left) and `TTY:B` (right); HC has a third, cradle port `TTY:C`. HC also
|
||||
exposes TTL-level ports `TTY:D/E/F` (direct) and `TTY:G/H/I` (inverted) — not
|
||||
documented in that chapter.
|
||||
|
||||
**Parameters — `P_SRCHAR` (`p_serial.h`), all fields:**
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
UBYTE tbaud; /* transmit baud rate */
|
||||
UBYTE rbaud; /* receive baud rate */
|
||||
UBYTE frame; /* data bits + stop + parity-present */
|
||||
UBYTE parity; /* parity type */
|
||||
UBYTE hand; /* handshake flags */
|
||||
UBYTE xon; /* XON character (default DC1 0x11) */
|
||||
UBYTE xoff; /* XOFF character (default DC3 0x13) */
|
||||
UBYTE flags; /* control flags */
|
||||
ULONG tmask; /* terminator mask */
|
||||
} P_SRCHAR;
|
||||
```
|
||||
|
||||
**Baud (`tbaud`, `rbaud`)** — one of `P_BAUD_50, _75, _110, _134, _150, _300, _600,
|
||||
_1200, _1800, _2000, _2400, _3600, _4800, _7200, _9600, _19200, _38400, _56000`.
|
||||
Default `P_BAUD_9600`. All SIBO machines support `_50`..`_9600`; MC 200/400 and
|
||||
Series 3a also support `_19200`; HC/Series 3 can be set to `_19200` but their clock
|
||||
is slightly too slow (frequent overruns). No SIBO hardware supports split
|
||||
transmit/receive rates.
|
||||
|
||||
**Frame (`frame`)** — one of `P_DATA_5 / P_DATA_6 / P_DATA_7 / P_DATA_8`, optionally
|
||||
OR'd with `P_TWOSTOP` (2 stop bits, else 1) and `P_PARITY` (parity bit present).
|
||||
Default `P_DATA_8` (8 data, 1 stop, no parity). All settings supported on all SIBO
|
||||
machines.
|
||||
|
||||
**Parity (`parity`)** — used only if `P_PARITY` is set: `P_PAR_EVEN`, `P_PAR_ODD`,
|
||||
`P_PAR_MARK`, `P_PAR_SPACE`. Default 0. **No SIBO machine supports mark or space
|
||||
parity.**
|
||||
|
||||
**Handshaking (`hand`, plus `xon`/`xoff`)** — combination of:
|
||||
|
||||
| Flag | Effect |
|
||||
| ---- | ------ |
|
||||
| `P_OBEY_XOFF` | obey received XON/XOFF (input flow control) |
|
||||
| `P_SEND_XOFF` | transmit XON/XOFF to remote (output flow control) |
|
||||
| `P_IGN_CTS` | if set, RTS held permanently active and incoming CTS ignored; if clear, RTS/CTS flow control |
|
||||
| `P_OBEY_DSR` | if set, suspend TX when incoming DSR inactive (DTR/DSR); DTR held active while port is open |
|
||||
| `P_FAIL_DSR` | (only if `P_OBEY_DSR`) complete outstanding `P_FREAD`/`P_FWRITE` with `E_FILE_LINE` if DSR goes inactive |
|
||||
| `P_OBEY_DCD` | if set, suspend TX when incoming DCD inactive |
|
||||
| `P_FAIL_DCD` | (only if `P_OBEY_DCD`) complete requests with `E_FILE_LINE` if DCD goes inactive |
|
||||
|
||||
Default `hand = 0` → RTS/CTS handshaking. DCD is an input-only carrier indicator,
|
||||
not a flow-control line. `xon`/`xoff` default to DC1 (0x11) / DC3 (0x13).
|
||||
|
||||
**Control flags (`flags`)** — only `P_IGNORE_PARITY` is defined (discard parity
|
||||
errors; the errored character is still delivered). Other bits reserved zero. Default 0.
|
||||
|
||||
**Terminator mask (`tmask`)** — 32 bit flags, bit *n* (n = 0..31) selecting control
|
||||
code `0x00`..`0x1F` as a terminating character. E.g. bit 13 = CR, bit 10 = LF (to
|
||||
read a line at a time); bit 26 = Ctrl-Z. A read completes when a terminator is
|
||||
received (included in the returned length). Default 0 (none).
|
||||
|
||||
**Errors:** `E_FILE_PARITY`, `E_FILE_FRAME`, `E_FILE_OVERRUN`, `E_FILE_LINE`
|
||||
(inactive required control line), plus `E_GEN_OVER` (driver receive buffer full)
|
||||
and `E_FILE_RECORD` (buffer filled with no terminator seen).
|
||||
|
||||
**Services:**
|
||||
|
||||
| Service | Prototype / notes |
|
||||
| ------- | ----------------- |
|
||||
| `p_open("TTY:x", -1)` | Powers up port, drives DTR active. Defaults as above; RTS not driven active until first `P_FREAD` or a `P_FSET` (so buffered-modem data is not lost). Errors: `E_GEN_NOMEMORY`, `E_GEN_INUSE`, `E_FILE_DEVICE`, `E_FILE_LOCKED`. |
|
||||
| `P_FREAD` | `p_iow(pcb, P_FREAD, buf, &len)` — read up to `*len` bytes. Completes on: full length; receive error (partial data + negative status); a `tmask` terminator (length includes it); or cancel (`E_FILE_CANCEL`). After a cancel, use `P_FTEST` — more buffered chars may remain. |
|
||||
| `P_FWRITE` | `p_iow(pcb, P_FWRITE, buf, &len)` — obeys current handshaking. A **zero-length** write with `P_OBEY_DSR` set completes only when DSR goes active (used to detect a connection). Cancel → `E_FILE_CANCEL`; the driver does not report bytes sent before cancel. |
|
||||
| `P_FCANCEL` | cancel outstanding read and write. |
|
||||
| `P_FSENSE` | `p_iow(pcb, P_FSENSE, P_SRCHAR*)` — read current characteristics (cannot fail). |
|
||||
| `P_FSET` | `p_iow(pcb, P_FSET, P_SRCHAR*)` — set characteristics (sense-modify-set idiom). Errors `E_GEN_ARG`, `E_GEN_NSUP`, `E_FILE_LINE`. Panics if a read/write is outstanding. |
|
||||
| `P_FFLUSH` | discard the LDD read buffer, clear error status, release paused remote. |
|
||||
| `P_FTEST` | `p_iow(pcb, P_FTEST, &len)` — bytes currently buffered (at least that many can be read synchronously). |
|
||||
| `P_FCTRL` | `p_iow(pcb, P_FCTRL, UBYTE *pctrl)` — read CTS/DSR/DCD input line state into `*pctrl` as a bit mask, and optionally set DTR (see below). |
|
||||
| `P_FINQ` | `p_iow(pcb, P_FINQ, UWORD *pmask)` — write three words of supported-characteristic bit flags. |
|
||||
|
||||
**Control-line constants (`p_serial.h`):**
|
||||
|
||||
- Input line state returned by `P_FCTRL` in `*pctrl`:
|
||||
`P_SRCTRL_CTS`, `P_SRCTRL_DSR`, `P_SRCTRL_DCD` (bit set ⇒ line active).
|
||||
- DTR output: if `*(pctrl+1)` is non-zero it sets DTR to `P_SRDTR_ON` (active) or
|
||||
`P_SRDTR_OFF` (inactive). `E_GEN_NSUP` if the driver can't set DTR (all current
|
||||
SIBO machines can).
|
||||
- `P_FINQ` bit flags: word 0 = `P_SRINQ_50 … P_SRINQ_19200`; word 1 =
|
||||
`P_SRINQ_38400`, `P_SRINQ_56000` (not supported on SIBO); word 2 =
|
||||
`P_SRINQ_DATA5/6/7/8`, `P_SRINQ_STOP2`, `P_SRINQ_PAREVEN/PARODD/PARMARK/PARSPACE`,
|
||||
`P_SRINQ_SETDTR`, `P_SRINQ_SPLIT`. SIBO supports all except `PARMARK`, `PARSPACE`
|
||||
and `SPLIT`.
|
||||
|
||||
`p_close` flushes the receive buffer, waits for any in-flight TX char, cancels
|
||||
outstanding read/write, drops RTS and DTR, and powers the port down.
|
||||
|
||||
### 5.2 Parallel port — `PAR:` (*IODEV* Ch.3)
|
||||
|
||||
Standard Centronics, **output-only** (no read service). Available via serial/parallel
|
||||
or parallel expansion modules; HC cradle provides a third. Names `PAR:A`, `PAR:B`,
|
||||
`PAR:C` (e.g. Series 3 = `PAR:A` only; MC with two modules = `PAR:A` left, `PAR:B`
|
||||
right).
|
||||
|
||||
- `p_open("PAR:x", -1)` — powers up the port lines (all control lines cleared low);
|
||||
it draws power until closed. Errors: `E_FILE_ALLOC`, `E_FILE_DEVICE`,
|
||||
`E_FILE_LOCKED` / `E_GEN_INUSE`.
|
||||
- `P_FWRITE` — `p_iow(pcb, P_FWRITE, buf, &len)`. **Never completes if no
|
||||
functioning receiver is connected** — always write asynchronously with a timer
|
||||
timeout. Errors `E_FILE_WRITE`, `E_FILE_CANCEL`.
|
||||
- `P_FCANCEL` — cancel the write (an indeterminate amount will already have been
|
||||
written).
|
||||
- `P_FSENSE` — `p_iow(pcb, P_FSENSE, UWORD *port)` reads input control lines
|
||||
(`p_par.h`): `S_BUSY` (pin 11), `S_ACK` (10), `S_ERROR` (15), `S_PE` (12). *Only
|
||||
on HC/MC ranges with EPOC ≥ 2.30; not on Series 3/3a.*
|
||||
- `P_FSET` — `p_iow(pcb, P_FSET, UWORD *type, UWORD *port)` sets/clears output lines:
|
||||
`*type = 1` sets high, `0` clears low. Lines: `S_INIT` (16), `S_AUTOFD` (14),
|
||||
`S_SELECT` (17), plus `S_SPARE` (custom hardware only — ASIC5 pin 42, no effect on
|
||||
the standard module). *Same HC/MC ≥ 2.30 restriction.*
|
||||
|
||||
### 5.3 Timers — `TIM:` (async) and `FRC:` (free-running counter)
|
||||
|
||||
**`TIM:` — asynchronous timer.** Documented in the *PLIB* "Time, Timers and Dates"
|
||||
chapter (not in *IODEV*). Opened `p_open(&tcb, "TIM:", -1)`. Requests are placed on
|
||||
a delta queue via `p_ioc(P_FRELATIVE, ..., &ticks)` (relative) or
|
||||
`p_ioc(P_FABSOLUTE, ...)`; cancel with `P_FCANCEL`. Relative units are 1/10 s (the
|
||||
examples use `tval = 10L * secs`). `TIM:` is the standard tool for I/O timeouts (see
|
||||
the `PAR:` and `TTY:` examples in *IODEV*). (*PLIB* Ch.8/9; Time chapter.)
|
||||
|
||||
**`FRC:` — free-running counter** (*IODEV* Ch.7). **Specific to Series 3a and
|
||||
Workabout.** Accuracy ±2 ppm, resolution 1/1024 s. **Supports only one process at a
|
||||
time.** For resolutions of 1/32 s or coarser, use `TIM:` instead.
|
||||
|
||||
- `p_open("FRC:", -1)` — errors `E_FILE_OPEN` (in use), `E_GEN_NOMEMORY`.
|
||||
- `P_FSTART` — `p_iow(pcb, P_FSTART, UWORD *pmode, UWORD *pint)`:
|
||||
- `E_FRC_COUNTING`: counter increments every 1/1024 s from 0; `pint` ignored.
|
||||
- `E_FRC_REPEATING`: increments every `*pint` × 1/1024 s (`*pint` in 10..65535,
|
||||
≈0.01–64 s). Errors `E_GEN_NSUP` (bad mode), `E_GEN_RANGE` (interval 0..9).
|
||||
- `P_FREAD` (COUNTING mode): `p_iow(pcb, P_FREAD, ULONG *arg1)` — elapsed 1/1024 s
|
||||
since `P_FSTART`, not reset by the read.
|
||||
- `P_FREAD` (REPEATING mode): `p_iow(pcb, P_FREAD)` — completion status = number of
|
||||
whole intervals elapsed since the last `P_FSTART`/`P_FREAD` (waits for ≥1
|
||||
interval; time is not lost between reads).
|
||||
- Read errors: `E_GEN_OVER` (>32767 intervals / too large for `arg1`),
|
||||
`E_FILE_CANCEL`, `E_FILE_READ` (counter not running, or machine was switched off
|
||||
mid-count).
|
||||
|
||||
### 5.4 Sound — `SND:` (*IODEV* Ch.5)
|
||||
|
||||
Speaker driven by the `SND:` driver; a separate piezo **buzzer** (all machines
|
||||
except Series 3a, which emulates it via the speaker) is driven by the PLIB
|
||||
`p_sound` routine, not `SND:`. Output is disabled if the `E_SOUND_DEVICE` sound-flag
|
||||
bit is clear or `E_SOUND_DISABLE` is set (managed via `p_getsnd`/`p_setsnd`). The
|
||||
speaker is a **two-voice** device (chords / DTMF). Series 3 `SND:` is limited to
|
||||
DTMF and simple alarms — richer sound needs an extra LDD (e.g. `SVDFRC.LDD` on the
|
||||
SDK disk). Series 3a can also play/record `.WVE` files.
|
||||
|
||||
- `p_open("SND:", -1)` — errors `E_FILE_ALLOC`, `E_GEN_FAIL` (disabled),
|
||||
`E_FILE_LOCKED`/`E_GEN_INUSE`.
|
||||
- `P_FCANCEL` — cancel outstanding write.
|
||||
- `P_FSENSE` / `P_FSET` — `E_SOUND` struct (`epoc.h`):
|
||||
```c
|
||||
typedef struct { UBYTE beatsPerMinute; UBYTE volume; } E_SOUND;
|
||||
```
|
||||
`beatsPerMinute` (HC/MC/3a) `E_SOUND_MIN_BPM` 2 .. `E_SOUND_MAX_BPM` 240, default
|
||||
120 (no effect on Series 3). `volume` 0 (`E_SOUND_MAX_VOLUME`, loudest) .. 5
|
||||
(`E_SOUND_MIN_VOLUME`); Series 3 range 1..4; Series 3a has four distinct volumes;
|
||||
smaller = louder; default 1. Always sense-then-set immediately after open, before
|
||||
playing — even at defaults.
|
||||
- `E_FALARM` — `p_iow(pcb, E_FALARM, UWORD *palarm)`: `*palarm` 0 = "rings", 1 =
|
||||
"chimes".
|
||||
- `E_FSSOUNDCHANNELn` *(HC/MC/3a only)* — `p_ioc(pcb, E_FSSOUNDCHANNELn, pstat,
|
||||
WORD *pnotes, WORD *plen)`, n = 1 or 2. `*plen ≤ 16384` notes; each note is two
|
||||
words (frequency Hz — middle A = 440; duration in beats). Output starts only once
|
||||
**both** channels have been called (for sync); use `*plen = 0` for an unused
|
||||
voice.
|
||||
- `E_FDIAL` *(Series 3 / 3a only)* — `p_iow(pcb, E_FDIAL, TEXT *pstr, E_DIAL
|
||||
*pdial)` emits DTMF for `*pstr`.
|
||||
```c
|
||||
typedef struct { UBYTE toneLengthTicks; UBYTE delayLengthTicks;
|
||||
UWORD pauseLengthTicks; } E_DIAL; /* ticks = 1/32 s */
|
||||
```
|
||||
Valid chars 0-9, A-F (`#`→F, `*`→E); space/comma = pause; others ignored; max 26
|
||||
tone/pause chars. Errors `E_FILE_CANCEL`, `E_GEN_ARG` (too many chars).
|
||||
|
||||
### 5.5 Magnetic card reader — `MCR:` (*IODEV* Ch.12)
|
||||
|
||||
HC MCR driver, built into the HC OS. Interface fits top (`MCR:A`), bottom (`MCR:B`)
|
||||
or cradle (`MCR:C`).
|
||||
|
||||
- `p_open("MCR:x", -1)` — errors `E_GEN_NOMEMORY`, `E_FILE_DEVICE` (no interface in
|
||||
slot), `E_FILE_NAME`, `E_FILE_LOCKED`/`E_GEN_INUSE`.
|
||||
- `P_FREAD` — `p_iow(pcb, P_FREAD, UBYTE *buf1, UBYTE *buf2)`: track 1 → `*buf1`,
|
||||
track 2 → `*buf2` (each buffer ≥ 256 bytes). Data is **leading-byte-count ASCII**;
|
||||
count 0 = unsuccessful. Pass either buffer as `NULL` to skip that track. Track 1 =
|
||||
alphanumeric (name/account), track 2 = numeric (most readers do track 2 only).
|
||||
Errors `E_FILE_READ` (decode error), `E_GEN_OVER`, `E_FILE_CANCEL`.
|
||||
- `P_FCANCEL` — cancel the read.
|
||||
- `P_FSET` *(EPOC ≥ 2.32)* — `p_iow(pcb, P_FSET, UWORD *mask)` programs the 100 kΩ
|
||||
pull-up/down resistors on the five reader lines (bit set = pull-up, clear =
|
||||
pull-down; default all pull-down): `M_DATA1PU` 0x01, `M_CLK1PU` 0x02, `M_DATA2PU`
|
||||
0x04, `M_CLK2PU` 0x08, `M_CLSPU` 0x10 (card-present). Other bits ignored.
|
||||
|
||||
### 5.6 Infrared (*IODEV* Ch.15–17)
|
||||
|
||||
Psion IR is **IrDA-compliant only** (S3a/Siena ports won't talk to non-IrDA
|
||||
devices). The protocol stack, bottom to top: **SIR** (Serial Infrared physical
|
||||
layer device driver, drives the hardware) → **IrLAP** (link access) → **IrLMP**
|
||||
(link management) → the **IrMUX API** (server side) and **AccessIr API** (client
|
||||
side). Two application-facing device channels are exposed:
|
||||
|
||||
- **`AIR:`** — the AccessIr API device. `p_open(&pcb, "AIR:", 0)` (mode 0) powers up
|
||||
the port; errors `E_GEN_INUSE`, `E_GEN_NOMEMORY`. Services use `P_FIR*` codes
|
||||
(numeric values from *IODEV* Ch.16): `P_FREAD` 1, `P_FWRITE` 2,
|
||||
`P_FIRDISCONNECT` 4, `P_FIRDISCOVER` 5, `P_FIRSELECT` 6, `P_FIRAWAITCONNECT` 7,
|
||||
`P_FIRMAKECONNECT` 8. Flow: `P_FIRDISCOVER` (log in-range machines) →
|
||||
`P_FIRSELECT` → `P_FIRMAKECONNECT` (primary) / `P_FIRAWAITCONNECT` (secondary),
|
||||
exchanging up to 56 bytes of connect data → `P_FREAD`/`P_FWRITE` →
|
||||
`P_FIRDISCONNECT`.
|
||||
- **`IRP:`** — the IR **printer-port** device driver (the "IRP" API), used for IR
|
||||
printing. (*IODEV* Ch.15, protocol-layer diagram.)
|
||||
|
||||
The **IrMUX API** (*IODEV* Ch.17) is the server-side interface (LM-IAS
|
||||
registration, connection-oriented and connectionless reads/writes) exposed through
|
||||
`LM_*` messages rather than `P_Fxxx` codes.
|
||||
|
||||
> MX note: these chapters describe the S3a/Siena/S3c-era IrDA stack; the manuals do
|
||||
> not document Workabout-MX-specific IR behaviour.
|
||||
|
||||
---
|
||||
|
||||
## 6. Barcode scanning
|
||||
|
||||
### 6.1 Workabout MX integral laser — see `SCANNER-API.md`
|
||||
|
||||
The **Workabout MX integral laser scanner is not covered by these manuals.** It is
|
||||
documented separately in **`SCANNER-API.md`** (sibling of this file, at
|
||||
`/tmp/claude/inv/SCANNER-API.md`). In brief, per that document: the MX uses the
|
||||
logical driver **`WL2`** (units `WL2:A`, `WL2:D`) with decoder type **`Symbol2`**
|
||||
(the integral Symbol laser engine); it is a decoded scanner, triggered by the
|
||||
keyboard scan key (Window Server key code 368). **Do not duplicate that content
|
||||
here — consult `SCANNER-API.md` for the MX.**
|
||||
|
||||
The remainder of this section summarises only the **older external** wand/RS232
|
||||
barcode devices as documented in *IODEV*.
|
||||
|
||||
### 6.2 External wand decoders — `BAR:` (*IODEV* Ch.13)
|
||||
|
||||
The HC has **no** built-in barcode driver/decoder; readers are supported by external
|
||||
combined **decoder + device-driver LDDs** that the app must load (`p_loadldd` or OPL
|
||||
`DevLoadLdd`). Wand interface module fits the top (`BAR:A`) or bottom (`BAR:B`) of
|
||||
the HC. Available drivers (device name `BAR` in all cases):
|
||||
|
||||
| File | Symbologies |
|
||||
| ---- | ----------- |
|
||||
| `BAREAN.LDD` | EAN8, EAN13, UPC, UPCE |
|
||||
| `BARC39.LDD` | Code 39 |
|
||||
| `BARITF.LDD` | Interleaved 2 of 5 (ITF) |
|
||||
| `BAR128.LDD` | Code 128 |
|
||||
| `BARMPLES.LDD`| Modified Plessey |
|
||||
| `BARRAW.LDD` | raw |
|
||||
|
||||
None auto-discriminates. Load e.g. `p_loadldd("BARC39.LDD")`; remove with
|
||||
`p_devdel("BAR")` (or OPL `DevDelete`).
|
||||
|
||||
- `p_open("BAR:", -1)` — opens via the previously loaded LDD. Errors as for `MCR:`.
|
||||
- `P_FREAD` — `p_iow(pcb, P_FREAD, UBYTE *buf)` (buf ≥ 256). Leading-byte-count
|
||||
ASCII; count 0 = failed read. The **first character encodes the symbology** (e.g.
|
||||
EAN8/13, UPC, Code 39, ITF, Code 128, Modified Plessey, UPCE); the rest is the
|
||||
decoded data. Errors `E_GEN_OVER`, `E_FILE_CANCEL`.
|
||||
- `P_FCANCEL` — cancel the read.
|
||||
|
||||
### 6.3 Intelligent reader / RS232 module — `TTY:` escape commands (*IODEV* Ch.14)
|
||||
|
||||
The **intelligent** RS232/barcode expansion module contains a decoding
|
||||
micro-controller and shares one serial port between an RS232 interface and the
|
||||
barcode interface (not usable simultaneously). Port mapping by slot: top slot →
|
||||
RS232 `TTY:A`, barcode `TTY:D`; bottom slot → RS232 `TTY:B`, barcode `TTY:E`. The
|
||||
barcode interface talks to the host at the standard HC comms settings (XON/XOFF,
|
||||
no hardware handshaking). It auto-discriminates EAN/JAN 8/13, UPCA, UPCE, Codabar,
|
||||
Code 128, Interleaved 2 of 5, and Code 39 (standard/extended), transmitting decoded
|
||||
data left-to-right as ASCII, terminated by default with a single CR.
|
||||
|
||||
Because it is reached through `TTY:`, it uses the normal serial services
|
||||
(`p_open`, `P_FSENSE`, `P_FSET`, `P_FREAD`, `P_FWRITE`, `p_close`). It is
|
||||
**programmed by writing escape sequences** to it via the serial channel:
|
||||
|
||||
```
|
||||
<Esc>-y<code><command> general form
|
||||
<Esc>-y<code><command><text> set-termination-string form
|
||||
<Esc>E hard reset (special form)
|
||||
```
|
||||
|
||||
`<Esc>` = 0x1B, then `-` (0x2D), then `y`/`Y`, a decimal `<code>` of 1–3 digits
|
||||
(0–255), and an uppercase command letter; no embedded spaces. Multiple option bits
|
||||
are combined by **summing** their `<code>` values. Multiple commands may be
|
||||
concatenated in one sequence — intermediate command letters lowercase, the final
|
||||
one uppercase (e.g. `<Esc>-y13f2h1D`).
|
||||
|
||||
Command letters (each `<Esc>-y<code><letter>`):
|
||||
|
||||
| Cmd | Function |
|
||||
| --- | -------- |
|
||||
| `D` | serial inter-character delay (0 = none, 1 = 10 ms) |
|
||||
| `E` | hard reset + self-test (special form `<Esc>E`; reverts all options to default; self-test failures reported as `... SELF TEST FAILED<CR><LF>`) |
|
||||
| `F` | select symbology bitmask: 1 Code39, 2 ITF, 4 UPC/EAN, 8 Codabar, 16 Code128 (default `31`) |
|
||||
| `G` | check-character options (verify / transmit check digits; per-symbology) |
|
||||
| `H` | decoding options (extended Code39, Codabar start/stop, UPC-vs-EAN, 2/5-digit supplements, UPC E→A expansion, auto-discriminate supplements) |
|
||||
| `J` | single-read mode enable/disable |
|
||||
| `K` | single-read control (fire one read when in single-read mode) |
|
||||
| `M` | set Interleaved 2 of 5 length |
|
||||
| `O` | set termination string (`<Esc>-y<code>O<string>`, up to 4 chars) |
|
||||
| `Q` | Code ID characters |
|
||||
| `S` | status request |
|
||||
| `W` | scanner enable |
|
||||
|
||||
Per-symbology output formats (ID chars, check digits, supplement digits for
|
||||
UPC E/A, EAN 8/13 and their +2/+5 variants; Codabar start/stop; ITF and Code 39
|
||||
check handling) are tabulated in *IODEV* Ch.14.
|
||||
|
||||
---
|
||||
|
||||
## 7. Other channel devices (pointers)
|
||||
|
||||
*IODEV* also documents the **Console** (`CON:`, Ch.2 — screen/keyboard, `P_SCR_*`
|
||||
function codes, `P_CON_KBREC`), **Alarm** (Ch.6, `A_FTIMED`/`A_FUNTIMED`), **World
|
||||
database** (Ch.8), **Xmodem/Ymodem** (Ch.9, `P_FCONNECT`/`P_FREAD`/`P_FWRITE`),
|
||||
**NCP / Link** (Ch.10, `SYS$NCP`), **Cradle/Docking Station** (Ch.11), and the
|
||||
**Fast Charger** (Ch.18, `FCHG_*`). The **`FIL:`** file driver and the **`TIM:`**
|
||||
timer are documented in the *PLIB* Files and Time chapters. These are outside the
|
||||
scope of this reference but follow the same `p_open` + `P_Fxxx` model described in
|
||||
Sections 1–3.
|
||||
|
||||
---
|
||||
|
||||
### Source map
|
||||
|
||||
| Topic | Source |
|
||||
| ----- | ------ |
|
||||
| Device model, `p_open`, `FIL:` fallback, modes, attached drivers, file server | *PLIB* Ch.9 |
|
||||
| I/O primitives (`p_ioa/ioc/iow` + variants), `p_read/write/close`, cancel | *PLIB* Ch.9 |
|
||||
| `p_iowait` / `p_waitstat`, semaphore rules | *PLIB* Ch.8 |
|
||||
| Function codes, `IoFunc*` mapping | *PLIB* Ch.9; *IODEV* Ch.1/16; *ADDSYS* driver chapter |
|
||||
| `p_loadldd`/`p_loadpdd`/`p_devdel`/`p_devqu`/`p_devfnd`; `.LDD`/`.PDD`; device segments | *PLIB* Ch.9, Ch.7 |
|
||||
| `TTY:`, `P_SRCHAR`, control lines | *IODEV* Ch.4 |
|
||||
| `PAR:` | *IODEV* Ch.3 |
|
||||
| `FRC:` | *IODEV* Ch.7 |
|
||||
| `TIM:` | *PLIB* Time chapter (referenced from *IODEV* Ch.1) |
|
||||
| `SND:` | *IODEV* Ch.5 |
|
||||
| `MCR:` | *IODEV* Ch.12 |
|
||||
| IR (`AIR:`, `IRP:`, IrMUX) | *IODEV* Ch.15–17 |
|
||||
| External `BAR:` wand decoders | *IODEV* Ch.13 |
|
||||
| Intelligent reader / `TTY:` escape commands | *IODEV* Ch.14 |
|
||||
| Workabout MX integral laser (`WL2`/`Symbol2`) | **`SCANNER-API.md`** (not these manuals) |
|
||||
Reference in New Issue
Block a user