Compare commits

..
Author SHA1 Message Date
lyrathorpe 03b4e219bd docs(reference): link scanner API + continuation guide in index 2026-07-06 23:36:53 +01:00
lyrathorpe 88849a4a7f docs(inventory): record oscanner category token + SIBO Debugger DYL breakpoints; link CONTINUATION.md 2026-07-06 23:36:15 +01:00
lyrathorpe 4937811a01 docs(inventory): on-device debugging continuation guide to finish the OO scanner client 2026-07-06 23:36:14 +01:00
lyrathorpe 9b3dc3000b docs(inventory): paradigm correction - scanner is an OO library object (LIBMANAGER/MESSMANAGER), driven via p_getlibh/p_newsend/p_send 2026-07-06 23:25:47 +01:00
lyrathorpe a8a0102a2f docs(mx-re): add Ghidra headless decompilation to the toolchain 2026-07-06 23:20:57 +01:00
lyrathorpe 038c38f029 docs(inventory): decompiled (Ghidra) read path; bottoms out at int 0x84/0xd2 services 2026-07-06 23:20:56 +01:00
lyrathorpe 961526b545 docs(inventory): retrieval is via OS-service handle + OO DYL messaging (per-vector register convention) 2026-07-06 22:49:49 +01:00
lyrathorpe 32d621e769 docs(reference): fix RE toolchain link path in index 2026-07-06 22:36:54 +01:00
lyrathorpe c80e7c8879 docs(reference): 08-re-boot-oscalls — SIBO/Workabout MX programming reference 2026-07-06 22:29:49 +01:00
lyrathorpe 43353d4019 docs(reference): 07-hardware — SIBO/Workabout MX programming reference 2026-07-06 22:29:48 +01:00
lyrathorpe 0ccd38994d docs(reference): 06-ui — SIBO/Workabout MX programming reference 2026-07-06 22:29:48 +01:00
lyrathorpe 0e765bc90c docs(reference): 05-filesystem-dbf — SIBO/Workabout MX programming reference 2026-07-06 22:29:47 +01:00
lyrathorpe 2b6804ab18 docs(reference): 04-plib-core — SIBO/Workabout MX programming reference 2026-07-06 22:29:46 +01:00
lyrathorpe 25078af7fc docs(reference): 03-io-devices — SIBO/Workabout MX programming reference 2026-07-06 22:29:45 +01:00
lyrathorpe 22a92dda5a docs(reference): 02-system — SIBO/Workabout MX programming reference 2026-07-06 22:29:44 +01:00
lyrathorpe 51d864d96d docs(reference): 01-building-apps — SIBO/Workabout MX programming reference 2026-07-06 22:29:44 +01:00
lyrathorpe 1a54086cc2 docs(reference): 00-index — SIBO/Workabout MX programming reference 2026-07-06 22:29:43 +01:00
lyrathorpe 8714b619c6 docs(mx-re): RE toolchain (MAME psionwamx headless + radare2) and documentation plan 2026-07-06 22:19:18 +01:00
lyrathorpe ae4a619172 docs(inventory): confirm WL2:D open + ops 6/7 trigger; record data-retrieval as open gap 2026-07-06 22:08:40 +01:00
lyrathorpe bf5aa1b231 feat(inventory): WL2:D async read (p_ioc + trigger + p_iowait) 2026-07-06 21:56:22 +01:00
lyrathorpe 2731d22266 feat(inventory): WL2:D trigger(6/7)+p_read loop with length + re-trigger 2026-07-06 21:52:26 +01:00
lyrathorpe edda57bdce feat(inventory): RE-based WL2:D init (enable ops 6/7) + read 2026-07-06 21:44:10 +01:00
lyrathorpe 62d277e5d6 docs(inventory): add reverse-engineered WL2 init sequence + default Symbol2 param block 2026-07-06 21:42:36 +01:00
lyrathorpe fc4df77300 docs(inventory): first-ever reverse-engineered Workabout MX scanner API notes 2026-07-06 21:35:08 +01:00
lyrathorpe 1544159afc diag(inventory): try WL2:D (untried unit) then WL2:A; read + dump 2026-07-06 21:23:19 +01:00
lyrathorpe 4bb853cdde build: add the workabout rom 2026-07-06 21:14:55 +01:00
lyrathorpe cdea110e60 diag(inventory): read integral laser via WL2:A (device demman uses) 2026-07-06 20:54:14 +01:00
lyrathorpe 0ec706d658 diag(inventory): probe BAR:A/B/C units (demman uses existing bar device) 2026-07-06 20:39:17 +01:00
lyrathorpe b79b1fa175 diag(inventory): software-trigger scan via Esc-y1J/Esc-y1K over TTY:D 2026-07-06 20:21:03 +01:00
lyrathorpe 724b5a11eb diag(inventory): minimal TTY:D read, no reconfig, one byte at a time 2026-07-06 20:14:54 +01:00
lyrathorpe b86c1b083f diag(inventory): held BAR:A/B open report + read-arms-laser test 2026-07-06 19:04:43 +01:00
lyrathorpe c99927acdf diag(inventory): keyboard/scan-key wedge test (open nothing; echo key codes) 2026-07-06 18:51:31 +01:00
lyrathorpe 397ce274e4 diag(inventory): stream one byte per read (tmask=0 full-count read was blocking) 2026-07-06 18:46:37 +01:00
lyrathorpe ff38d4eb70 diag(inventory): read decoded laser on TTY:D with P_OBEY_DSR cleared + DTR forced 2026-07-06 18:41:30 +01:00
lyrathorpe 4d8917bb02 diag(inventory): correct BAR: read to p_iow(P_FREAD,buf); probe slot letters; dump [count][type][data] 2026-07-06 18:33:34 +01:00
lyrathorpe 538705a807 docs: add extra sdk docs 2026-07-06 18:30:29 +01:00
lyrathorpe 1e371910a0 docs: add the HDK 2026-07-06 18:30:06 +01:00
55 changed files with 371727 additions and 42 deletions
+1189
View File
File diff suppressed because it is too large Load Diff
+411
View File
@@ -0,0 +1,411 @@
/*
A4TEST.C - Testing the functionality ASIC4 Example
Interface Board device driver A4Exif.ldd
A comprehensive test program which checks
I/O request functionality of A4Exif.ldd
Written by Mal, January 31, 1995
*/
#include <plib.h>
#include <p_keyb.h>
#define BUFLEN 1
#define MAXBUF 8
#define SERREAD_TIMEOUT 20L
#define FLASH_TIME 1L
GLREF_D TEXT *DatCommandPtr;
GLREF_D VOID *winHandle;
LOCAL_D VOID *serH; /* serial channel handle */
LOCAL_D VOID *timH; /* timer channel handle */
LOCAL_D WORD serReadStat; /* serial channel status word */
LOCAL_D WORD timStat; /* timer channel status word */
LOCAL_D WORD keyStat; /* console channel status word */
LOCAL_D P_CON_KBREC kbrec;
LOCAL_D TEXT buf[40];
LOCAL_D UBYTE wowbuf[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
LOCAL_D TEXT *head="LED: ";
LOCAL_C VOID PrintHeader(VOID)
/* Prints the title at the start of the program */
{
p_printf("*********************");
p_printf("Welcome to the ASIC 4");
p_printf("Example IF board test");
p_printf("program");
p_printf("*********************");
p_printf(".....\n");
p_getch();
}
LOCAL_C VOID PrintInstructions(VOID)
{
p_printf("Type in the test number");
p_printf(" 0 => Alternate flash");
p_printf(" 1 => Asynchronous read");
p_printf(" 2 => Sense");
p_printf(" 3 => Set");
p_printf(" 4 => Memory");
p_printf(" q => Quit");
p_printf(" .....");
}
LOCAL_C VOID LoadLdd(VOID)
/* Loads the LDD A4Exif.ldd from anywhere and exits on failure */
{
TEXT filename[128];
INT ret;
p_printf("\nLOADLDD=>");
p_fparse("A4EXIF.LDD",DatCommandPtr,&filename[0],NULL);
if ((ret=p_loadldd(&filename[0]))<0)
{
p_printf("A4Exif.ldd failed to load");
if (ret==E_FILE_NXIST)
{
p_printf("Device does not exist");
p_getch();
p_exit(0);
}
else if (ret==E_FILE_EXIST)
{
p_printf("Device is already loaded");
p_getch();
}
else if (ret==E_GEN_NOMEMORY)
{
p_printf("Not enough memory available");
p_getch();
p_exit(0);
}
else
{
p_printf("Unknown error %d on p_loadldd",ret);
p_getch();
p_exit(0);
}
}
else
{
p_printf("Successfully loaded A4Exif.ldd\n");
p_getch();
}
}
LOCAL_C VOID UnloadLdd(VOID)
/* Removes the LDD A4Exif.ldd */
{
INT ret;
p_printf("\nUNLOADLDD=>");
if ((ret=p_devdel("LED",E_LDD))!=0)
{
p_printf("A4Exif.ldd failed to unload");
if (ret==E_FILE_DEVICE)
p_printf("The device driver is not loaded");
else if (ret==E_GEN_INUSE)
p_printf("Driver is open and cannot be deleted");
else
p_printf("Unknown error %d on p_devdel",ret);
p_getch();
}
else
{
p_printf("Successfully deleted A4Exif.ldd");
p_getch();
}
}
LOCAL_C VOID OpenPort(VOID)
/*Opens a serial port of the A4Example IF on the host machine*/
{
INT ret, seropen;
p_printf("\nOPENPORT=>");
DoAgain:
p_printf("Type in the serial\r\nport to open");
p_printf("eg A => port A");
p_printf(".....\n");
ret=p_toupper(p_getch());
*(head+4)=ret;
p_printf("%s",head);
p_sleep(5L);
if ((seropen=p_open(&serH,head,-1))<0)
{/* Failed to open */
p_atos(&buf[0],"p_open on port %c has failed",ret);
p_printf("%s\n<p_open ret=%d>",&buf[0],seropen);
p_errs(buf,seropen);
p_printf("%s\n",&buf[0]);
p_getch();
goto DoAgain;
}
else
{/* Succeeded in opening */
p_atos(&buf[0],"Successfully opened port %c",ret);
p_printf("%s",&buf[0]);
p_printf(".....\n");
p_getch();
}
}
LOCAL_C VOID ClosePort(VOID)
{
INT ret;
p_printf("\nCLOSEPORT=>");
if ((ret=p_close(serH))!=0)
p_printf("Error in closing port");
else
p_printf("Successfully closed port");
p_getch();
}
LOCAL_C VOID SensePort(VOID)
{
UBYTE A1,A2;
p_printf("\nSENSEPORT=>");
p_iow(serH,P_FSENSE,&A1,&A2);
p_printf("Status byte: 0x%x",A1);
p_printf("LED byte: 0x%x",A2);
p_getch();
}
LOCAL_C INT SetPort(VOID)
{
UBYTE key;
p_printf("\nSETPORT=>");
p_printf("Type a key");
p_printf("ESCAPE to exit\r\n");
if ((key=p_getch())==E_KEY_ESCAPE)
{
key=0x00;
p_iow(serH,P_FSET,&key);
return(-1);
}
else
{
p_printf("Output byte is 0x%x",key);
p_iow(serH,P_FSET,&key);
return(0);
}
}
LOCAL_C VOID OpenTimer(VOID)
{
if (p_open(&timH,"TIM:",-1)<0)
{/* Error in opening timer */
p_printf("Cannot open timer channel\n");
p_getch();
}
}
LOCAL_C VOID QueueTimer(ULONG timeouttime)
{
p_ioc4(timH,P_FRELATIVE,&timStat,&timeouttime);
}
LOCAL_C VOID CancelTimer(VOID)
{
p_iow(timH,P_FCANCEL);
p_waitstat(&timStat);
}
LOCAL_C VOID QueueSerRead(UBYTE *statusptr,UBYTE *ledptr)
{
p_ioc5(serH,P_FREAD,&serReadStat,statusptr,ledptr);
}
LOCAL_C VOID CancelSerRead(VOID)
{
p_iow(serH,P_FCANCEL);
p_waitstat(&serReadStat);
}
LOCAL_C VOID QueueKeypress(P_CON_KBREC *keybrec)
{
p_ioc4(winHandle,P_FREAD,&keyStat,keybrec);
}
LOCAL_C VOID AsynchRead(VOID)
{
UBYTE b_led;
UBYTE b_status;
p_printf("ASYNCH READ\r\n");
OpenTimer();
QueueTimer(SERREAD_TIMEOUT);
QueueSerRead(&b_status,&b_led);
QueueKeypress(&kbrec);
FOREVER
{
p_iowait();
if (keyStat!=E_FILE_PENDING)
{/* Keypress received */
p_printf("KEYPRESS");
if (kbrec.keycode==E_KEY_ESCAPE)
{
CancelSerRead();
CancelTimer();
b_led=0x00;
p_iow(serH,P_FWRITE,&b_led);
break;
}
else
{
/* Do nothing - requeue keypress */
QueueKeypress(&kbrec);
continue;
}
}
else if (serReadStat!=E_FILE_PENDING)
{
p_printf("SERIAL READ COMPLETED");
CancelTimer();
p_printf("Status byte is => 0x%x",b_status);
p_printf("LED byte is => 0x%x",b_led);
QueueTimer(SERREAD_TIMEOUT);
QueueSerRead(&b_status,&b_led);
continue;
}
else if (timStat!=E_FILE_PENDING)
{
p_printf("TIMEOUT");
CancelSerRead();
QueueSerRead(&b_status,&b_led);
QueueTimer(SERREAD_TIMEOUT);
continue;
}
else
{/* Error - missing status word so fatal error */
p_printf("***Stray status word***");
break;
}
}
p_close(timH);
}
LOCAL_C VOID AlternateFlash(VOID)
{
UBYTE b_led;
INT i=0;
p_printf("ALTERNATE FLASH\r\n");
p_printf("Hit ESCAPE to exit");
OpenTimer();
QueueTimer(FLASH_TIME);
QueueKeypress(&kbrec);
FOREVER
{
p_iowait();
if (keyStat!=E_FILE_PENDING)
{/* Keypress received */
if (kbrec.keycode==E_KEY_ESCAPE)
{
CancelTimer();
b_led=0x00;
p_iow(serH,P_FWRITE,&b_led);
break;
}
else
{
/* Do nothing - requeue keypress */
QueueKeypress(&kbrec);
continue;
}
}
else if (timStat!=E_FILE_PENDING)
{/* Timer finished */
if (i<MAXBUF)
{
p_iow(serH,P_FSET,&wowbuf[i]);
i++;
}
else
{
i=0;
p_iow(serH,P_FSET,&wowbuf[i]);
}
QueueTimer(FLASH_TIME);
continue;
}
else
{/* Missing status word so fatal error */
p_printf("***Stray status error***");
break;
}
}
p_close(timH);
}
LOCAL_C VOID CheckMemory(TEXT *str)
{
VOID *Heap;
INT fbytes;
fbytes=p_allspc(&Heap);
p_print("\n\t%s\r\n",str);
p_print("Free Heap Memory =>\r\n\t%x bytes\r\n",fbytes);
p_print("Free Segments =>\r\n\t%d\r\n",p_sgfree());
p_getch();
}
LOCAL_C VOID MemoryTest(VOID)
{
CheckMemory("OpenPort");
ClosePort();
CheckMemory("ClosedPort");
UnloadLdd();
CheckMemory("UnoadedLdd");
LoadLdd();
CheckMemory("LoadLdd");
OpenPort();
CheckMemory("OpenPort");
p_printf("End of Memory Test");
}
GLDEF_C VOID main(VOID)
/*
*/
{
INT ret,set=0;
PrintHeader();
LoadLdd();
OpenPort();
Start:
PrintInstructions();
if ((ret=p_getch())=='0')
AlternateFlash();
else if (ret=='1')
AsynchRead();
else if (ret=='2')
SensePort();
else if (ret=='3')
{
while (set>=0)
set=SetPort();
set=0;
}
else if (ret=='4')
MemoryTest();
else if (ret=='q')
{
ClosePort();
UnloadLdd();
p_exit(0);
}
else
goto Start;
goto Start;
}
+6
View File
@@ -0,0 +1,6 @@
#system epoc img
#set epocinit=iplib
#model small jpi
#compile a4test
#link a4test
+15
View File
@@ -0,0 +1,15 @@
.xlist
; Include file - EPOC.INC
; Epoc/Os standard include file
; Copyright (c) Psion PLC 1989-90.
;
; VER DATE BY DESCRIPTION
; ----- -------- ---- -----------
; 2.00F 30/09/90 NSM Final release
;
EPOC_INC equ 1
;
include ..\inc\epocdef.inc
include ..\inc\epocmac.inc
include ..\inc\epocpan.inc
.list
+284
View File
@@ -0,0 +1,284 @@
.xlist
; Include file - EPOCLIB.INC
; Epoc/Os plib standard include file.
; Copyright (c) Psion PLC 1989-90.
;
; VER DATE BY DESCRIPTION
; ----- -------- ---- -----------
; 2.00F 30/09/90 NSM Final release
;
EPOCLIB_INC equ 1
;
_TANDW = 0
ifdef LATTICE
CodeSeg macro
pgroup group prog
PGROUP equ <pgroup>
prog segment byte public 'prog'
assume cs:pgroup
endm
EndCodeSeg macro
prog ends
endm
DataSeg macro
dgroup group data,udata
DGROUP equ <dgroup>
data segment word public 'data'
assume ds:dgroup,es:dgroup,ss:dgroup
endm
EndDataSeg macro
data ends
endm
UDataSeg macro
udata segment word public 'data'
endm
EndUDataSeg macro
udata ends
endm
GLREF_D MACRO NAME,TYPE
EXTRN NAME:TYPE
ENDM
GLDEF_D MACRO NAME,TYPE
PUBLIC NAME
NAME label TYPE
ENDM
GLREF_C MACRO NAME
EXTRN NAME:NEAR
ENDM
endif
;
ifdef TURBOC
_TANDW = 1
CodeSeg macro
PGROUP group _TEXT
pgroup equ <PGROUP>
_TEXT segment byte public 'CODE'
assume cs:PGROUP
endm
EndCodeSeg macro
_TEXT ends
endm
DataSeg macro
DGROUP group _DATA,_BSS
dgroup equ <DGROUP>
_DATA segment word public 'DATA'
assume ds:DGROUP,es:DGROUP,ss:DGROUP
endm
EndDataSeg macro
_DATA ends
endm
UDataSeg macro
_BSS segment word public 'BSS'
endm
EndUDataSeg macro
_BSS ends
endm
GLREF_D MACRO NAME,TYPE
EXTRN _&NAME:TYPE
NAME EQU _&NAME
ENDM
GLDEF_D MACRO NAME,TYPE
PUBLIC _&NAME
_&NAME label TYPE
NAME EQU _&NAME
ENDM
GLREF_C MACRO NAME
EXTRN _&NAME:NEAR
NAME EQU _&NAME
ENDM
endif
ifdef WATCOMC
REG_PARAM equ 1
ifdef _MSC
_TANDW = 1
endif
CodeSeg macro
PGROUP group _TEXT
pgroup equ <PGROUP>
_TEXT segment byte public 'CODE'
assume cs:PGROUP
endm
EndCodeSeg macro
_TEXT ends
endm
DataSeg macro
DGROUP group _DATA,_BSS
dgroup equ <DGROUP>
_DATA segment word public 'DATA'
assume ds:DGROUP,es:DGROUP,ss:DGROUP
endm
EndDataSeg macro
_DATA ends
endm
UDataSeg macro
_BSS segment word public 'BSS'
endm
EndUDataSeg macro
_BSS ends
endm
GLREF_D MACRO NAME,TYPE
EXTRN NAME&_:TYPE
NAME EQU NAME&_
ENDM
GLDEF_D MACRO NAME,TYPE
PUBLIC NAME&_
NAME&_ label TYPE
NAME EQU NAME&_
ENDM
GLREF_C MACRO NAME
EXTRN NAME&_:NEAR
NAME EQU NAME&_
ENDM
endif
ifdef JPIC
REG_PARAM equ 1
ifdef _MSC
_TANDW = 1
endif
CodeSeg macro
_TEXT segment byte public 'CODE'
assume cs:_TEXT
endm
EndCodeSeg macro
_TEXT ends
endm
DataSeg macro
DGROUP group _DATA,_BSS
dgroup equ <DGROUP>
_DATA segment word public 'DATA'
assume ds:DGROUP,es:DGROUP,ss:DGROUP
endm
EndDataSeg macro
_DATA ends
endm
UDataSeg macro
_BSS segment word public 'BSS'
endm
EndUDataSeg macro
_BSS ends
endm
GLREF_D MACRO NAME,TYPE
EXTRN _&NAME:TYPE
NAME EQU _&NAME
ENDM
GLDEF_D MACRO NAME,TYPE
PUBLIC _&NAME
_&NAME label TYPE
NAME EQU _&NAME
ENDM
GLREF_C MACRO NAME
EXTRN _&NAME:NEAR
NAME EQU _&NAME
ENDM
endif
;
LOCAL_D MACRO NAME,TYPE
NAME label TYPE
ENDM
;
ProcBegin macro _Name,_Type,_Registers
ifdef TURBOC
ProcBeginBody _&_Name,_Type,_Registers
endif
ifdef JPIC
ProcBeginBody _&_Name,_Type,_Registers
endif
ifdef WATCOMC
ProcBeginBody _Name&_,_Type,_Registers
_Name EQU _Name&_
endif
ifdef LATTICE
ProcBeginBody _Name,_Type,_Registers
endif
endm
ProcBegin@ macro _Name,_Type,_Registers
ProcBeginBody _Name,_Type,_Registers
endm
ProcBeginBody macro _Name,_Type,_Registers
_DI = 0
_SI = 0
if _TANDW
ifnb <_Registers>
irpc _flags,_Registers
ifidni <_flags>,<D>
_DI = 1
endif
ifidni <_flags>,<S>
_SI = 1
endif
endm
endif
endif
;
ProcEnd macro _flag
ifb <_flag>
if _DI
pop di
endif
if _SI
pop si
endif
if _TANDW
ifidni <_Type>,<bp>
pop bp
endif
endif
ifdef LATTICE
ifidni <_Type>,<bp>
pop bp
endif
endif
ret
endif
&_Name endp
endm
;
public _Name
ifidni <_Type>,<far>
_Name proc far
else
_Name proc near
endif
ifidni <_Type>,<bp>
if _TANDW
push bp
mov bp, sp
endif
ifdef LATTICE
push bp
mov bp, sp
endif
Arg1 equ <[bp+4]>
Arg2 equ <[bp+6]>
Arg3 equ <[bp+8]>
Arg4 equ <[bp+10]>
Arg5 equ <[bp+12]>
Arg6 equ <[bp+14]>
Arg7 equ <[bp+16]>
Arg8 equ <[bp+18]>
endif
ifidni <_Type>,<bx>
if _TANDW
mov bx, sp
endif
ifdef LATTICE
mov bx, sp
endif
Arg1 equ <[bx+2]>
Arg2 equ <[bx+4]>
Arg3 equ <[bx+6]>
Arg4 equ <[bx+8]>
Arg5 equ <[bx+10]>
Arg6 equ <[bx+12]>
Arg7 equ <[bx+14]>
Arg8 equ <[bx+16]>
endif
if _SI
push si
endif
if _DI
push di
endif
endm
;
.list
+297
View File
@@ -0,0 +1,297 @@
.xlist
; Include file - EPOCSER.INC
; Epoc/Os standard Serial Driver defines
; Copyright (c) Psion PLC 1989-90.
;
; VER DATE BY DESCRIPTION
; ----- -------- ---- -----------
; 2.00F 30/09/90 JH Final release
; 2.01F 05/10/90 JH Added a define for Constant speed DTE modems
;
EPOCSER_INC equ 1
; Serial port characteristics
SerialCharEnt struc
SerialCharTbaud db ? ; transmit Baud rate selector
SerialCharRbaud db ? ; receive Baud rate selector
SerialCharFrame db ? ; number of data, parity and stop bits
SerialCharParity db ? ; parity selector
SerialCharHandshake db ? ; handshake flags
SerialCharXon db ? ; XON character
SerialCharXoff db ? ; XOFF character
SerialCharFlags db ? ; control flags
SerialCharTmask dd ? ; terminator mask
SerialCharEnt ends
; Function numbers to use to call PDD functions
SerPDDFuncOpen equ 0
SerPDDFuncClose equ 2
SerPDDFuncStart equ 4
SerPDDFuncStop equ 6
SerPDDFuncSet equ 8
SerPDDFuncSense equ 10
SerPDDFuncControl equ 12
SerPDDFuncEnquire equ 14
SerPDDFuncEnable equ 16
SerPDDFuncSetHandlerCS equ 18
;
SERPARITY_ERR equ 0ffh
SERFRAME_ERR equ 0feh
SEROVERRUN_ERR equ 0fdh
SERNOCHAR equ 0ffffh
; Baud rates
P_BAUD_50 equ 01h
P_BAUD_75 equ 02h
P_BAUD_110 equ 03h
P_BAUD_134 equ 04h
P_BAUD_150 equ 05h
P_BAUD_300 equ 06h
P_BAUD_600 equ 07h
P_BAUD_1200 equ 08h
P_BAUD_1800 equ 09h
P_BAUD_2000 equ 0Ah
P_BAUD_2400 equ 0Bh
P_BAUD_3600 equ 0Ch
P_BAUD_4800 equ 0Dh
P_BAUD_7200 equ 0Eh
P_BAUD_9600 equ 0Fh
P_BAUD_19200 equ 10h
P_BAUD_38400 equ 11h
P_BAUD_56000 equ 12h
; allocation of frame bits
P_DATA_FRM equ 0fh ; number of data bits mask
P_DATA_5 equ 0h ; 5 data bits
P_DATA_6 equ 1h ; 6 data bits
P_DATA_7 equ 2h ; 7 data bits
P_DATA_8 equ 3h ; 8 data bits
P_TWOSTOP equ 10h ; 2 stop bits if set, 1 if clear
P_PARITY equ 20h ; 1 parity bit if set, 0 if clear
; parity - ignored unless P_PARITY is set
P_PAR_EVEN equ 1h ; even parity
P_PAR_ODD equ 2h ; odd parity
P_PAR_MARK equ 3h ; mark parity
P_PAR_SPACE equ 4h ; space parity
; handshaking control
P_OBEY_XOFF equ 01h ; respond to received XOFF (and XON) if set
P_SEND_XOFF equ 02h ; send XOFF/XON to control receive buf if set
P_IGN_CTS equ 04h ; ignore the state of CTS if set
P_OBEY_DSR equ 08h ; obey the state of DSR if set
P_FAIL_DSR equ 10h ; fail if DSR goes OFF if set
P_OBEY_DCD equ 20h ; obey the state of DCD if set
P_FAIL_DCD equ 40h ; fail if DCD goes OFF if set
; flags control
P_IGNORE_PARITY equ 01h ; ignore parity errors
; For P_FCTRL function
P_SRCTRL_CTS equ 01h
P_SRCTRL_DSR equ 02h
P_SRCTRL_DCD equ 04h
P_SRCTRL_DTR equ 08h
P_SRCTRL_RTS equ 10h
P_SRDTR_ON equ 1h ; to set DTR to MARK
P_SRDTR_OFF equ 2h ; to set DTR to SPACE
; Bit masks for P_FINQ function
P_SRINQ_50 equ 0001h
P_SRINQ_75 equ 0002h
P_SRINQ_110 equ 0004h
P_SRINQ_134 equ 0008h
P_SRINQ_150 equ 0010h
P_SRINQ_300 equ 0020h
P_SRINQ_600 equ 0040h
P_SRINQ_1200 equ 0080h
P_SRINQ_1800 equ 0100h
P_SRINQ_2000 equ 0200h
P_SRINQ_2400 equ 0400h
P_SRINQ_3600 equ 0800h
P_SRINQ_4800 equ 1000h
P_SRINQ_7200 equ 2000h
P_SRINQ_9600 equ 4000h
P_SRINQ_19200 equ 8000h
; second baud rate word
P_SRINQ_38400 equ 0001h
P_SRINQ_56000 equ 0002h
; 2nd set of info
P_SRINQ_DATA5 equ 0001h ; supports 5 data bits
P_SRINQ_DATA6 equ 0002h ; supports 6 data bits
P_SRINQ_DATA7 equ 0004h ; supports 7 data bits
P_SRINQ_DATA8 equ 0008h ; supports 8 data bits
P_SRINQ_STOP2 equ 0010h ; supports 2 stop bits (as well as 1)
P_SRINQ_PAREVEN equ 0020h ; supports even parity
P_SRINQ_PARODD equ 0040h ; supports odd parity
P_SRINQ_PARMARK equ 0080h ; supports mark parity
P_SRINQ_PARSPACE equ 0100h ; supports space parity
P_SRINQ_SETDTR equ 0200h ; can set DTR
P_SRINQ_SPLIT equ 0400h ; supports split Baud rates
P_SRINQ_XONXOFF equ 0800h ; Supports soft xon/xoff characters
; CRC generator section
; The Low and High bytes of the 16 bit CRC
CrcEnt struc
CrcLow db ? ; CRC low byte
CrcHigh db ? ; CRC high byte
CrcEnt ends
;Serial Driver Media Access Control section (LLMAC)
;
P_MAXILEN equ 300 ; maximum length of information field
;Frame type codes
LA_LPDU equ 0 ; The Link Acknowledge PDU
LD_LPDU equ 1 ; The Link disconnect PDU
LR_LPDU equ 2 ; The Link Request PDU
LT_LPDU equ 3 ; The Link Data PDU
P_FRM_BROKEN equ 4 ; A broken frame
FrameEnt struc
FrameType db ? ; frame type
FrameSeq db ? ; frame sequence
FrameLen dw ? ; length of information field
FramePbuf dw ? ; ptr to data buffer
FrameEnt ends
; LLMAC layer characteristics
LlmacEnt struc
LlmacIlen dw ? ; maximum information field length
LlmacSpeed dw ? ; nominal speed in characters per second
LlmacRtint dw ? ; suggested retransmission interval
LlmacEnt ends
; LINK layer structures and defines
; IoFuncConnect service connection modes */
P_LINK_ACCP equ 0 ; want link as acceptor
P_LINK_INIT equ 1 ; want link as initiator
; Link layer states
P_LINK_IDLE equ 0 ; no connection - idle
P_LINK_IDLE_LR equ 1 ; no connection - awaiting LR
P_LINK_DATA_IDLE equ 2 ; connected - idle
P_LINK_DATA_LA equ 3 ; connected - awaiting LA
P_LINK_IDLE_LA equ 4 ; not connected - awaiting LA (or LT) to LR
;State and event log record
LinkLogEnt struc
LinkLogState db ? ; Link layer state (as defined above)
LinkLogEvent db ? ; Event that occured
LinkLogEnt ends
P_LINK_NLOG equ 32 ; Max number of debug events,states we record
P_LINK_LOG_MASK equ 0C0h
P_LINK_LOG_PANIC equ 0C0h ; panic number
P_LINK_LOG_MAC equ 0 ; MAC request or timer
P_LINK_LOG_REQ equ 80h ; User request
P_LINK_LOG_COMP equ 40h ; User completion
LinkDataEnt struc
LinkDataLlmac LlmacEnt <>
LinkDataBrokenCount dw ? ; count of broken frames
LinkDataRetranCount dw ? ; re-transmission count
LinkDataEnt ends
; Comms I/O drivers shared code header structs
; Flag defines
RQ_TIMER equ 01h ; a timer has been started
RQ_SUPER equ 02h ; a supervisory read has been started
RQ_WRITE equ 04h ; a write has been started
RQ_READ equ 08h ; a data read has been started
RQ_DISABLE_HANDLER equ 10h ; set to disable handers
RQ_ANY equ (RQ_TIMER or RQ_SUPER or RQ_WRITE or RQ_READ)
;Flag bits to indicate various features of the link layer event.
QUEUE_XMIT_LA equ 20h ; queue an LA when the transmit completes
LINK_ACTIVE equ 40h ; set between a P_FCONNECT and a disconnect
USER_DATA_PENDING equ 80h
; DONT CHANGE THE ORDER OF THIS !!
IoRequestEnt struc
IoRequestChan ChanEnt <> ; I/O control block
IoRequestWaitHandler dw ? ; wait handler vector number
IoRequestRqRead RqEnt <> ; Read request packet
IoRequestRqWrite RqEnt <> ; Write request packet
IoRequestRqSuper RqEnt <> ; Supervisorry request packet
IoRequestTimerPcb dw ? ; open Timer cb
IoRequestTimerStat dw ? ; timer completion status
IoRequestReadStat dw ? ; read completion status
IoRequestWriteStat dw ? ; write completion status
IoRequestSuperStat dw ? ; supervisory completion status
IoRequestFlags db ? ; controlling flags
IoRequestState db ? ; current internal state
IoRequestEnt ends
LnkEnt struc
LnkIoRequest IoRequestEnt <> ; I/O header
LnkLinkData LinkDataEnt <> ; Link layer data
LnkNumberLog dw ? ; current log table event no.
LnkLogTable db (size LinkLogEnt)*P_LINK_NLOG dup (?)
LnkEnt ends
; XMODEM structures and defines
; IoFuncConnect service connection modes */
P_XMDM_ACCP equ 0 ; want link as acceptor
P_XMDM_INIT equ 1 ; want link as initiator
; Xmodem link types
P_XMDM_CRCORCHECKSUM equ 0
P_XMDM_CRCMODE equ 1
P_XMDM_CHECKSUMMODE equ 2
P_YMODEM_MODE equ 3
P_YMODEM_G_MODE equ 4
P_XMDM_ONE_K equ 8000h
;Xmodem/Ymodem supported flags
P_FXMDM_SENSE equ 0ffh ; I/O function number
P_XSUP_XMODEM equ 01h ; original Xmodem checksum
P_XSUP_XMODEM_CRC equ 02h ; Xmodem with CRC
P_XSUP_ONE_K_OPTION equ 04h ; 1k frames
P_XSUP_YMODEM equ 08h ; Ymodem
P_XSUP_YMODEM_G equ 10h ; Ymodem-G
; Xmodem State defines
P_XMDM_IDLE equ P_LINK_IDLE ; idle state (no connection)
P_XMDM_CONNECT_RECEIVE equ 1 ; receive connect state
P_XMDM_CONNECT_TRANSMIT equ 2 ; Transmit connect state
P_XMDM_DATA_RECEIVE equ 3 ; receive data state
P_XMDM_DATA_TRANSMIT equ 4 ; transmit data state
P_XMDM_DATA_TRANSMIT_ACK equ 5 ; awaiting ACK to transmitted data
;State and event log record
XmdmLogEnt struc
LinkLogEnt <>
XmdmLogEnt ends
P_XMDM_NLOG equ P_LINK_NLOG ; Max number of debug events,states we record
P_XMDM_LOG_MASK equ P_LINK_LOG_MASK
P_XMDM_LOG_PANIC equ P_LINK_LOG_PANIC ; panic number
P_XMDM_LOG_REQ equ P_LINK_LOG_REQ ; User request
P_XMDM_LOG_COMP equ P_LINK_LOG_COMP ; User completion
; Modem driver info
; what options are selected
P_MDM_TONE equ 01h
P_MDM_BELL equ 02h
P_MDM_NO_MODULATION equ 04h
P_MDM_CONSTANT_SPEED equ 08h ; modem provides constant speed interface if set
; error correction types
P_MDM_ERRCORRECT_NONE equ 00h
P_MDM_ERRCORRECT equ 01h
P_MDM_ERRCORRECT_MNP equ 02h
P_MDM_ERRCORRECT_V42 equ 03h
ModemCharEnt struc
ModemCharSupport dw ? ; flags saying what supported
ModemCharOptions dw ? ; Selected options
ModemCharBaudRate dw ? ; Baud rate running at
ModemCharConnHand db ? ; Connected handshaking
ModemCharCallHand db ? ; Wait for call handshaking
ModemCharEnt ends
.list
+86
View File
@@ -0,0 +1,86 @@
.xlist
; Include file - EPOCSIBO.INC
; Epoc/Os SIBO specific include file
; Copyright (c) Psion PLC 1989-90.
;
; VER DATE BY DESCRIPTION
; ----- -------- ---- -----------
; 2.00F 30/09/90 NSM Final release
;
EPOCSIBO_INC equ 1
;
SupplyEnt struc
MainBatteryReading dw ?
LithiumBatteryReading dw ?
MainsPresent dw ?
SupplyEnt ends
;
SupplyWarningsEnt struc
MainBatteryWarning dw ?
LithiumBatteryWarning dw ?
MainBatteryMaxValue dw ?
LithiumBatteryMaxValue dw ?
SupplyWarningsEnt ends
;
MainBatZero equ 0
MainBatVeryLow equ 1
MainBatLow equ 2
MainBatGood equ 3
;
SupplySoundWarning equ 0001h
SupplyFlashWarning equ 0002h
SupplySystemTimeChanged equ 0004h
;
SupplyInfoEnt struc
SuMainBatLevel db ?
SuMainBatStatus db ?
SuBackupBatLevel db ?
SuDcLevel db ?
SuWarningFlags dw ?
SuInsertionDate dd ?
SuTicksInUseBattery dd ?
SuTicksInUseDc dd ?
SuMilliampTicks dd ?
SupplyInfoEnt ends
;
SsdUnitInfo struc
SsdUnitStatus db ?
SsdUnitType db ?
SsdUnitDevices db ?
SsdUnitAsicType db ?
SsdUnitChanged db ?
SsdUnitReadCmd db ?
SsdUnitTotalSectors dw ?
SsdUnitMask dw ?
SsdUnitSectorsPerDevice dw ?
SsdUnitSpare db ?
SsdUnitShift db ?
SsdUnitDoReadCmd dw ?
SsdUnitInfo ends
;
SsdData struc
SsdUnitInfoBuffer SsdUnitInfo 4 dup(<>)
SsdDoorStatus db ?
SsdDoorDelay db ?
SsdPakCritical db ?
SsdDoorOpened db ?
SsdOldFrcVector dd ?
SsdFrcFlag dw ?
SsdPakChannel db ?
SsdWriteFlag db ?
SsdFlashCount dw ?
SsdFlashVector dw ?
SsdFlashAddress dw ?
SsdFlashOffset dw ?
SsdFlashMaxProgramPulses dw ?
SsdSaveStack dw ?
SsdNeedSerialResume db ?
SsdBackTo4 db ?
SsdA9DoorNmiEnabled db ?
SsdSpare db ?
SsdFlashProgram dw ?
SsdNewPddSpareBuffer db 400h dup (?)
SsdData ends
;
.list
+125
View File
@@ -0,0 +1,125 @@
.xlist
; Include file - OSPACK.INC
; Epoc/Os Sibo packs include file.
; Copyright (c) Psion PLC 1989-90.
;
; VER DATE BY DESCRIPTION
; ----- -------- ---- -----------
; 2.00F 30/09/90 CJ Final release
; 2.50A 24/01/93 NSM Changed to support S3B/C
;
DoorOpen equ 001h
DoorClosed equ 000h
;
; ASIC4 STRUCTURES AND REGISTER BITS
; ==================================
;
ASIC5TYPE equ 0
ASIC4TYPE equ 1
;
ASIC4R struc
A4Data db ?
A4Portb db ?
A4IncAddress db ?
A4Address db ?
A4Dum4 db ?
A4Dum5 db ?
A4Dum6 db ?
A4Control db ?
ASIC4R ends
;
A4PortbMode equ A4IncAddress
A4InfoR equ A4Portb
A4CsSetupW equ A4Portb
A4XInfo record A4MXPeriph:1,A4MXType:1,A4MXDevices:1,A4MXBlocks:1, \
A4MXCheat:1,A4MXExtra:2,A4MXLowBat:1
;
A4SpecialMode equ 2
A4NormalMode equ 0
;
A4LBO equ 080h ; Enable low battery check
SetVppOn equ 010h ; Enable VPP bit in A4CONTROL
SetVppOff equ 000h ; Disable VPP
VppOnDelay equ 5 ; i.e. 5ms.
BatteryDelay equ 1 ; 1 ms
;
; INTEL FLASH EPROM COMMANDS
; ==========================
;
ReadCmd equ 0 ; Set into read mode when VPP is on
EraseCmd equ 020h ; Start erase cycle
EraseVerifyCmd equ 0a0h ; Erase verify byte
ProgramCmd equ 040h ; Start program cycle
ProgramVerifyCmd equ 0c0h ; Program verify byte
MaxProgramPulses equ 25 ; Number of programs per byte
MaxErasePulses equ 1000 ; Number of erase cycles
EraseDelay equ 10 ; Erase program delay 10mS
;
; INTEL TYPE2 FLASH EPROM COMMANDS
; ================================
;
Type2ReadCmd equ 0FFh ; Read byte with VPP on
Type2EraseCmd equ 0D0h ; Start erase cycle
Type2EraseSetup equ 020h ; Erase verify byte
Type2ProgramCmd equ 040h ; Start program cycle
Type2ClearStatus equ 050h ; Clear status byte
Type2ReadStatus equ 070h ; Read status byte
Type2MaxProgramPulses equ 1 ; Number of programs per byte
Type2ProgramTimeout equ 150 ; 150 frames at 3.2 us is >450 us.
Type2EraseTimeout equ 321 ; 320 ticks is 10 seconds.
; Status Register Format
Type2StatusBusy equ 80h
Type2EraseSuspended equ 40h
Type2EraseSuccess equ 20h
Type2WriteSuccess equ 10h
Type2VPPFail equ 08h
;
if HandHeld
if Corporate or S3c
Max01Units equ 2
else
Max01Units equ 3
endif
else
Max01Units equ 4
endif
;
; DATA STRUCTURES
; ===============
;
InfoRec record PackType:3,NumberOfChips:2,BlocksPerChip:3
;
TypeRam equ 0
TypeIntelFlash equ 1
Type2IntelFlash equ 2
TypeUnknown equ 3
TypeRom equ 6
TypeWriteProtected equ 7
;
UnitShiftNumber equ 4 ; 2^UnitShift must equal size of UnitInfo
;
UnitInfo struc ; This must be an whole power of 2
UnitStatus db ?
UnitType db ?
UnitDevices db ?
UnitAsicType db ?
UnitChanged db ?
UnitReadCmd db ?
UnitTotalSectors dw ?
UnitMask dw ?
UnitSectorsPerDevice dw ?
UnitSpare db ?
UnitShift db ?
UnitDoReadCmd dw ?
UnitInfo ends
;
UnitBufferInBxFromBl macro
xor bh, bh
rept UnitShiftNumber
shl bx, 1
endm
add bx, offset OsDataGroup:OsUnitInfoBuffer
endm
;
.list
+620
View File
@@ -0,0 +1,620 @@
.xlist
; Include file - OSSIBO.INC
; Epoc/Os Sibo include file.
; Copyright (c) Psion PLC 1989-90.
;
; VER DATE BY DESCRIPTION
; ----- -------- ---- -----------
; 2.00F 30/09/90 NSM Final release
;
NoNullFrames equ 1
;
if Asic1
A1Ent struc
A1Dummy dw ?
A1Control dw ?
A1LcdSize dw ?
A1LcdControl dw ?
A1InterruptMask dw ?
A1NonSpecificEoi dw ?
A1TimerEoi dw ?
A1FrcEoi dw ?
A1ResetWatchDog dw ?
A1FrcControl dw ?
A1ProtectionOn dw ?
A1ProtectionUpper dw ?
A1ProtectionLower dw ?
A1SoundLsw dw ?
A1SoundMsw dw ?
A1SoundControl dw ?
A1Ent ends
;
A1Status equ A1Control
A1ProtectionOff equ A1ProtectionOn
A1InterruptStatus equ A1LcdControl
;
A1StatusR record LcdData:2,Rtc4Hz:1,SldMsw:1,ComboBusy:1,Rtc32Hz:1,ExternalNmi:1,WatchDogNmi:1,SldTx:1,A1SldEnable:1,LcdEnable:1,Ram512:1,Ram128:1,FrcSource:1,TickRate:1,FrcMode:1
A1LcdSizeR record LcdMLineEnable:1,LcdNumberOfPixels:5,LcdEndOfFrame:10
A1LcdControlR record LcdMode:2,LcdMLineRate:5,LcdRate:5
A1InterruptMaskR record SldTransmit:1,SldReceive:1,FrcExpired:1,Asic2Int:1,ExpIntLeftA:1,ExpIntRightB:1,Mains:1,Timer:1
ExpAddressLeftA equ 0200h
ExpAddressRightB equ 0100h
ExpChannelLeftA equ SelectChannel6
ExpChannelRightB equ SelectChannel5
;
; Asic1 dependent constants
;
ResetWatchDog equ A1ResetWatchDog
;
A2Ent struc
A2Dummy db 080h dup(?)
A2Index dw ?
A2Control dw ?
A2Control1 dw ?
A2Control2 dw ?
A2Control3 dw ?
A2SerialData dw ?
A2SerialControl dw ?
A2ChannelControl dw ?
A2Ent ends
;
A2External equ A2Control1
A2InterruptStatus equ A2Control2
A2Status equ A2Control3
A2KeyData equ A2SerialControl
A2SlaveData equ A2ChannelControl
A2IControl0 equ 0
A2IControl1 equ 1
A2IWrite equ 2
A2IDDR equ 3
;
A2InterruptStatusR record SlaveDataOverrun:1,SlaveDataControl:1,SlaveDataValid:1,ExpansionInterrupt:1,DoorInterrupt:1
A2StatusR record A2RevId:1,A2XExt:1,A2Sdis:1,SerialBusy:1,SerialClockState:1,A1ResetFlag:1,WakeUp:1,A1OnKey:1
A2Control1R record SerialClockRate:2,KeyScan:4
A2Control2R record ClockEnable7:1,ClockEnable6:1,ClockEnable5:1,BuzzerMode:1,BuzzerVolume:1,BuzzerToggle:1,XySwitch:1,DigitizerEnable:1
A2Control3R record ElEnable:1,VhControl:1,ExpansionEnable:1,DoorEnable:1,SerialEnable:1,A2SldEnable:1,Ps34Acknowledge:1,SerialNull:1
A2ChannelControlR record MultiplexEnable:1,ChannelSelect:3,Pack4Enable:1,Pack3Enable:1,Pack2Enable:1,Pack1Enable:1
;
ClockRateSlow equ 2
ClockRateMedium equ 0
ClockRateFast equ 3
SelectChannel0 equ (4 shl ChannelSelect)
SelectChannel1 equ (mask Pack1Enable)
SelectChannel2 equ (mask Pack2Enable)
SelectChannel3 equ (mask Pack3Enable)
SelectChannel4 equ (mask Pack4Enable)
SelectChannel5 equ (5 shl ChannelSelect)
SelectChannel6 equ (6 shl ChannelSelect)
SelectChannel7 equ (7 shl ChannelSelect)
;
SCONTOUT macro
out A2SerialControl, al
endm
;
SDATAOUT macro
out A2SerialData, al
endm
;
SDATAIN macro
in al, A2SerialData
endm
;
SBUSY macro
wait
endm
;
SREAD macro _REG
mov al, SerialReadSingle or _REG
out A2SerialControl, al
nop
SBUSY
in al, A2SerialData
endm
;
SREADM macro _REG
mov al, SerialReadMulti or _REG
out A2SerialControl, al
nop
SBUSY
in al, A2SerialData
endm
;
SWRITE macro _REG,_VAL
mov al, SerialWriteSingle or _REG
out A2SerialControl, al
SBUSY
mov al, _VAL
out A2SerialData, al
endm
;
SWRITEM macro _REG,_VAL
mov al, SerialWriteMulti or _REG
out A2SerialControl, al
SBUSY
mov al, _VAL
out A2SerialData, al
endm
;
SETSPEED macro _SPEED
pushf
cli
mov al, OsA2Control1
and al, not mask SerialClockRate
ifidni <_SPEED>,<FAST>
or al, ClockRateFast shl SerialClockRate
endif
ifidni <_SPEED>,<NORMAL>
or al, ClockRateMedium shl SerialClockRate
endif
ifidni <_SPEED>,<SLOW>
or al, ClockRateSlow shl SerialClockRate
endif
mov OsA2Control1, al
out A2Control1, al
popf
endm
;
if LapTop
XPUSHFCLI macro
pushf
cli
endm
XPOPF macro
popf
endm
else
XPUSHFCLI macro
endm
XPOPF macro
endm
endif
;
XNOP macro
endm
;
@HwNullFrame macro
call OsHwNullFrame
endm
;
A3Ent struc
A3Adc db ?
A3Control1 db ?
A3Setup db ?
A3Control2 db ?
A3Dummy1 db 3 dup (?)
A3Control3 db ?
A3Dummy2 db 5 dup (?)
A3Status db ?
A3Dummy3 db ?
A3Ent ends
;
A3AdcLsbR record InvertedBit:1,OtherBits:7
A3AdcMsbR record Polarity:1,Overrange:1,AdcBits:4
A3Control1R record Vcc5Enable:1,Vcc4Enable:1,Vcc3Enable:1,DtoaBits:5
A3Control2R record AnalogueMultiplex:2,VhSoftStart:1,Vee2SoftStart:1,Vee1SoftStart:1,VhEnable:1,Vee2Enable:1,Vee1Enable:1
A3Control3R record AdcReadHighEnable:1,OffEnable:1,xDummy1:1
Ps34ControlR record Ps34Vcc3:1,xDummy2:1,Ps34Vcc4:1,xDummy3:1,Ps34Vcc5:1,xDummy4:3
;
AdcDigitizer equ 0
AdcVh equ 1
AdcMainBattery equ 2
AdcLithiumBattery equ 3
A3SetupValue equ 2
A3SelectId equ (SerialSelect or Asic5NormalId)
A3InfoByte equ 080h
;
Vee1SoftStartDelay equ (30*128)
Vee2SoftStartDelay equ (30*128)
VhSoftStartDelay equ (100*128)
Vee1OffDelay equ (100*128)
Vee2OffDelay equ (100*128)
VhOffDelay equ (100*128)
Vcc3Delay equ (25*128)
Vcc4Delay equ (25*128)
Vcc5Delay equ (25*128)
Vcc4OffFrames equ (25)
Vcc1_4To5Delay equ (3) ; Pan only in ms.
Vcc1_4to5OffFrames equ (20) ; Gives 392 frames = 6.0 ms
Vcc1_4to5OnTime equ 4 ; Gives 4 ms
;
PS34R_ADC equ 0
PS34R_STATUS equ 1
PS34W_CONTROL equ 0
PS34W_DTOA equ 1
PS34W_OFF equ 0eh
;
PS34STATR record pPowerFail:1,pColdStart:1,pVhready:1,pPenup:1,pNc:1,pAdmsb:3
PS34CONTR record pVcc3:1,pVee1:1,pVcc4:1,pVee2:1,pVcc5:1,pVh:1,pVhpower:2
PS34DTOAR record pNcc:1,pAdcsel:2,pDac:5
;
ADCSEL_ADCIN equ 0
ADCSEL_VIN equ 1
ADCSEL_VH equ 2
ADCSEL_VBATT equ 3
;
VHP1TO8 equ 0
VHP1TO4 equ 1
VHP1TO2 equ 2
VHP1TO1 equ 3
;
PPowerControl equ 0100h
PPowerControlR record PVcc5Enable:1,PSoundEnable:1,PVee1Enable:1,PDropVoltage:1,PDac:4
PPia record PSoundVol:2,PClearCold:1,PDcPresent:1,PColdFlag:1,PLithiumWarn:1,PVinWarn:1
PSoundControl equ 0200h
endif
;
A3StatusR record PowerFail:1,ColdStart:1
AIntfR record ResetFlag:1,DummyWakeUp:1,OnKey:1
if Asic1
if ((mask ResetFlag) ne (mask A1ResetFlag)) or \
((mask OnKey) ne (mask A1OnKey))
.err Reset or OnKey flags are not equal
endif
endif
;
SerialWriteSingle equ 10000000b
SerialWriteMulti equ 10010000b
SerialReadSingle equ 11000000b
SerialReadMulti equ 11010000b
SerialReset equ 00000000b
SerialSelect equ 01000000b
;
Asic4Id equ 006h
Asic5PackId equ 002h
Asic5NormalId equ 003h
Asic6Id equ 004h
Asic8Id equ 005h
Asic2SlaveId equ 01fh
;
; Asic5 info byte structure
;
A5InfoByteR record A5TTL:1,A5Modem:1,A5MultiDrop:1,A5Barcode:1,A5Other:2,A5Parallel:1,A5Rs232:1
A5OtherRom equ (1 shl A5Other)
A5OtherMCRTTL equ (3 shl A5Other)
A5MCR equ (2 shl A5Other)
;
if Consumer
if S3b
DefaultLcdContrast equ 9
else
DefaultLcdContrast equ 7
endif
else
if S3c
DefaultLcdContrast equ 9
else
DefaultLcdContrast equ 15
endif
endif
;
InterruptBase equ 078h
;
if Asic9
KeyPollColumns equ 8
else
KeyPollColumns equ 10
endif
KeyInitialDelay equ 24
if Consumer or S3c
KeyRepeatDelay equ 2
else
KeyRepeatDelay equ 4
endif
PenUpDelay equ 16
MouseDownDelay equ 8
KeySettleDelay equ 24
KeyCntrlMask equ 00000001b
KeyLeftShiftMask equ 00000010b
KeyPsionMask equ 00000100b
KeyCapsMask equ 00001000b
KeyRightShiftMask equ 00010000b
DigitizerRowMask equ 00010000b
PsionScanCode equ 6
CapsScanCode equ 5
PsionUpScanCode equ 65
;
PostRomTest equ 010h
PostSystemRamTest equ 020h
PostVideoRamTest equ 030h
PostComplete equ 0f0h
if Consumer or S3c
PostPort equ 0h
else
PostPort equ 01ffh
endif
;
if HandHeld
VideoRamSegment equ 00040h
VideoRamBase equ 00000h
if Corporate
VideoRamWords equ 00320h
else
if S3b
VideoRamWords equ 02580h
else
if S3c
VideoRamWords equ 00C80h
else
VideoRamWords equ 00500h
endif
endif
endif
else
VideoRamSegment equ 0b800h
VideoRamBase equ 00000h
VideoRamWords equ 04000h
endif
;
CalSeg equ 0fffeh
CalEnt struc
CalVsupCLsw dw ?
CalVsupCMsw dw ?
CalVsupM dw ?
CalVlthCLsw dw ?
CalVlthCMsw dw ?
CalVlthM dw ?
CalType dw ?
CalEnt ends
;
CHKeyDataR record CHInSled:1,CHMains:1,CHDummy:2
;
IntBXHw equ -2
;
if Asic9
A9WControlRW equ 0002h
A9RControl record A9MFrc2Is512KHzOr1KHz:1,A9MFrc2PreScale:1,\
A9MFrc1Is512KHzOr1Hz:1,A9MFrc1PreScale:1,\
A9MSoundEnable:1,A9MLcdEnable:1,\
A9MLowBatNMIEnable:1,A9MDoorNMIEnable:1,\
A9MZeroIsGrayMode:1,A9MArmStandBy:1,\
A9MDisableDMADivide:1,A9MRamDeviceSize:2,\
A9MDisableMemWait:1,A9MDisableIoWait:1,\
A9MDisableClockDivide:1
A9VRam256KBits equ (0 shl A9MRamDeviceSize)
A9VRam1MBits equ (1 shl A9MRamDeviceSize)
A9VRam4MBits equ (2 shl A9MRamDeviceSize)
A9VRam16MBits equ (3 shl A9MRamDeviceSize)
A9XRamDeviceSizeMask equ (3 shl A9MRamDeviceSize)
A9WStatusR equ 0004h
A9RStatus record A9MCold:1,A9MPowerFail:1,\
A9MReset:1,A9MNoBattery:1,\
A9MFifoFull:1,A9MSlaveOverrun:1,\
A9MSlaveControlFrame:1,A9MSlaveDataValid:1,\
A9MKeyboard:1,A9MSlaveClock:1,\
A9MMainsPresent:1,A9MDoorSwitch:1,\
A9MLowBatNMI:1,A9MDoorNMI:1,\
A9MProtectedModeNMI:1,A9MWatchDogNMI:1
A9MLcdType equ 8
A9XLcdType equ 0000111100000000b
A9WLcdSizeW equ 0004h
A9RLcdSize record A9MLcdLineLength:5,A9MLcdNumberOfPixels:11
A9WLcdControlW equ 0006h
A9RLcdControl record A9MLcdMode:2,A9MLcdACLineRate:5,A9MLcdPixelRate:5
A9VSinglePage1 equ (0 shl A9MLcdMode)
A9VSinglePage2 equ (1 shl A9MLcdMode)
A9VSinglePage1And2 equ (2 shl A9MLcdMode)
A9VDualPage1And2 equ (3 shl A9MLcdMode)
A9BInterruptStatusR equ 0006h
A9BInterruptMaskRW equ 0008h
A9RInterrupts record A9MFrc2:1,A9MFrc1:1,\
A9MExpIntB:1,A9MExpIntA:1,A9MExpIntC:1,\
A9MSlave:1,A9MTimer:1,A9MSound:1
A9BNmiClearW equ 0009h
A9BNonSpecificEoiW equ 000ah
A9BStartFlagClearW equ 000bh
A9BTimerEoiW equ 000ch
A9BSerialSlaveEoiW equ 000dh
A9BFrc1EoiW equ 000eh
A9BFrc2EoiW equ 000fh
A9WResetWatchDogW equ 0010h
A9WFrc1DataRW equ 0012h
A9BProtectionOnW equ 0014h
A9BProtectionOffR equ 0014h
A9BProtectionOffW equ 0015h
A9WProtectionUpperW equ 0016h
A9WProtectionLowerW equ 0018h
A9BSoundDataRW equ 001ah
A9BSoundEoiW equ 001ch
A9WFrc2DataRW equ 001eh
A9WPortABDataRW equ 0020h
if S3b
A9RPortABData record A9MKeyId0:1,A9MSleepDoorNmiDisabled:1,\
A9MBatLevel2:1,A9MBatLevel1:1,A9MLiBatLevel:1,\
A9MKeyRow:11
endif
if S3c
A9RPortABData record A9MKeyIdX2:1,A9MNiCdDetect:1,\
A9MBatLevel2:1,A9MBatLevel1:1,A9MLiBatLevel:1,\
A9MKeyIdX1:1,A9MKeyIdX0:0,A9MKeyRowX8:1,\
A9MCradle:1,A9MKeyRowX:7
endif
A9BPortADataRW equ 0020h
A9BPortBDataRW equ 0021h
A9WPortABDDRRW equ 0022h
A9VPortABDDR equ 0000000000000000b
A9BPortADDRRW equ 0022h
A9BPortBDDRRW equ 0023h
A9WPortCDDataRW equ 0024h
if S3b
A9RPortCDData record A9MVccPacksEnable:1,A9MCodecEnable:1,\
A9MVccLcdEnable:1,A9MLcdPanelEnable:1,\
A9MLcdContrast:4,A9MVolume:2,\
A9MReproEnable:1,A9MAltDoorNMIEnable:1,\
A9MAmplifierEnable:1,A9MKeyIdEnable:1,\
A9MKeyId2:1,A9MKeyId1:1
A9VVolumeLow equ (mask A9MVolume)
A9VPortCDData equ 0000111100000000b
A9VPortCDDDR equ not (1111000011111100b)
endif
if S3c
A9RPortCDData record A9MVccPacksEnable:1,A9MCodecEnable:1,\
A9MVccLcdEnable:1,A9MLcdPanelEnable:1,\
A9MLcdContrast:4,A9MVolume:2,\
A9MReproEnable:1,A9MAltDoorNMIEnable:1,\
A9MAmplifierEnable:1,A9MKeyIdEnable:1,\
A9MBuzVolX:1,A9MBacklightEnableX:1
A9VVolumeLow equ (mask A9MVolume)
A9VPortCDData equ 0000111100000000b
A9VPortCDDDR equ not (1111000011111111b)
endif
A9BPortCDataRW equ 0024h
A9BPortDDataRW equ 0025h
A9WPortCDDDRRW equ 0026h
A9BPortCDDRRW equ 0026h
A9BPortDDDRRW equ 0027h
A9BPageSelect6000RW equ 0028h
A9BPageSelect7000RW equ 0029h
A9BPageSelect8000RW equ 002ah
A9BPageSelect9000RW equ 002bh
A9WControlExtraRW equ 002ch
A9RControlExtraRW record A9MSlaveIntEnable:1,A9MClkDiv:2,\
A9MClockEnable5:1,A9MClockEnable4:1,\
A9MClockEnable3:1,A9MClockEnable2:1,\
A9MClockEnable1:1,A9MSoundDir:1,\
A9MExonDisable:1,A9MBuzzFromFrc1OrTog:1,\
A9MBuzzTog:1,A9MKeyCol:4
A9VKeyColHigh equ 0
A9VKeyColLow equ 1
A9VKeyCol0 equ 8
A9VClkDiv1 equ (3 shl A9MClkDiv)
A9VClkDiv2 equ (2 shl A9MClkDiv)
A9VClkDiv3 equ (1 shl A9MClkDiv)
A9VClkDiv4 equ (0 shl A9MClkDiv)
A9WPumpControlRW equ 002eh
A9RPumpControl record A9MVhPumpDc:4,A9MVhPumpBat:4,\
A9MLcdPump:4,A9MPump2:4
A9VVhPumpBat equ (14 shl A9MVhPumpBat)
A9VVhPumpBatSoft equ (4 shl A9MVhPumpBat)
A9VVhPumpBatMedium equ (8 shl A9MVhPumpBat)
A9VVhPumpDC equ (2 shl A9MVhPumpDc)
A9VVhPumpDCSoft equ (1 shl A9MVhPumpDc)
A9VLcdPump equ (7 shl A9MLcdPump)
A9VLcdPumpSoft equ (1 shl A9MLcdPump)
A9VPacksPumpSoft equ (15 shl A9MPump2)
A9VVhPumpDelay equ 100 ; Milliseconds
A9VLcdPumpDelay equ 5 ; Milliseconds
A9VCC3OnDelay equ 1 ; Milliseconds
A9VCCPacksDelay equ 20 ; Milliseconds
A9VCC2Delay equ 10 ; Milliseconds
A9WRtcLSWRW equ 0080h
A9WRtcMSWRW equ 0082h
A9WNullFrameW equ 0084h
A9BSlaveDataR equ 0088h
A9BSerialDataRW equ 008ah
A9BSerialControlW equ 008ch
A9BChannelSelectRW equ 008eh
A9RChannelSelect record A9MMultiplexEnable:1,A9MSerialClockRate:2,\
A9MPack5Enable:1,A9MPack4Enable:1,\
A9MPack3Enable:1,A9MPack2Enable:1,\
A9MPack1Enable:1
A9VSClkRateMedium equ (0 shl A9MSerialClockRate)
A9VSClkRateSpecial equ (1 shl A9MSerialClockRate)
A9VSClkRateSlow equ (2 shl A9MSerialClockRate)
A9VSClkRateFast equ (3 shl A9MSerialClockRate)
;
; Asic9 Dependent constants
;
ResetWatchDog equ A9WResetWatchDogW
SelectChannel1 equ (mask A9MPack1Enable)
SelectChannel2 equ (mask A9MPack2Enable)
SelectChannel3 equ (mask A9MPack3Enable)
SelectChannel4 equ (mask A9MPack4Enable)
SelectChannel5 equ (mask A9MPack5Enable)
;
; Current usage values
;
IdleCurrent equ 23
RunCurrent equ 51
Asic5SerialCurrent equ 54
Flash1ProgramCurrent equ 76
Flash2ProgramCurrent equ 99
Flash1EraseCurrent equ 94
Flash2EraseCurrent equ 129
;
; We add 26 because most of the work is under the NULL process.
; The value is 26 and not 28 because 10% of the time we are
; under the sound server.
;
SoundCurrentPlayV3 equ (25+20)
SoundCurrentPlayV2 equ (30+20)
SoundCurrentPlayV1 equ (55+20)
SoundCurrentPlayV0 equ (120+20)
;
; The value here must never equal one of the play values.
; It won't anyway in practise so this is not a big deal.
;
SoundCurrentRecord equ 5
;
SCONTOUT macro
out A9BSerialControlW, al
endm
;
SDATAOUT macro
out A9BSerialDataRW, al
endm
;
SDATAIN macro
in al, A9BSerialDataRW
endm
;
SBUSY macro
endm
;
SREAD macro _REG
mov al, SerialReadSingle or _REG
out A9BSerialControlW, al
in al, A9BSerialDataRW
endm
;
SREADM macro _REG
mov al, SerialReadMulti or _REG
out A9BSerialControlW, al
in al, A9BSerialDataRW
endm
;
SWRITE macro _REG,_VAL
mov al, SerialWriteSingle or _REG
out A9BSerialControlW, al
mov al, _VAL
out A9BSerialDataRW, al
endm
;
SWRITEM macro _REG,_VAL
mov al, SerialWriteMulti or _REG
out A9BSerialControlW, al
mov al, _VAL
out A9BSerialDataRW, al
endm
;
SETSPEED macro _SPEED
pushf
cli
in al, A9BChannelSelectRW
and al, not (mask A9MSerialClockRate)
ifidni <_SPEED>,<FAST>
or al, A9VSClkRateFast
endif
ifidni <_SPEED>,<NORMAL>
or al, A9VSClkRateMedium
endif
ifidni <_SPEED>,<SLOW>
or al, A9VSClkRateSlow
endif
out A9BChannelSelectRW, al
popf
endm
;
XPUSHFCLI macro
endm
XPOPF macro
endm
XNOP macro
endm
;
@HwNullFrame macro
out A9WNullFrameW, ax
endm
endif
;
.list
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -0,0 +1,834 @@
{
"identifier": "psion-sibo-c-sdk",
"format-version": "2",
"archive-hocr-tools-version": "1.1.54",
"confidence": 87,
"pages": [
{
"leafNum": 0,
"confidence": null,
"pageNumber": "",
"pageProb": null,
"wordConf": null
},
{
"leafNum": 1,
"confidence": null,
"pageNumber": "",
"pageProb": null,
"wordConf": null
},
{
"leafNum": 2,
"confidence": null,
"pageNumber": "",
"pageProb": null,
"wordConf": null
},
{
"leafNum": 3,
"confidence": null,
"pageNumber": "",
"pageProb": null,
"wordConf": null
},
{
"leafNum": 4,
"confidence": 81,
"pageNumber": "2",
"pageProb": 71,
"wordConf": 96
},
{
"leafNum": 5,
"confidence": 97,
"pageNumber": "3",
"pageProb": 87,
"wordConf": 96
},
{
"leafNum": 6,
"confidence": 81,
"pageNumber": "4",
"pageProb": 71,
"wordConf": 95
},
{
"leafNum": 7,
"confidence": 97,
"pageNumber": "5",
"pageProb": 87,
"wordConf": 96
},
{
"leafNum": 8,
"confidence": 81,
"pageNumber": "6",
"pageProb": 71,
"wordConf": 96
},
{
"leafNum": 9,
"confidence": 97,
"pageNumber": "7",
"pageProb": 87,
"wordConf": 96
},
{
"leafNum": 10,
"confidence": 81,
"pageNumber": "8",
"pageProb": 71,
"wordConf": 96
},
{
"leafNum": 11,
"confidence": 97,
"pageNumber": "9",
"pageProb": 87,
"wordConf": 96
},
{
"leafNum": 12,
"confidence": 79,
"pageNumber": "10",
"pageProb": 69,
"wordConf": 95
},
{
"leafNum": 13,
"confidence": 96,
"pageNumber": "11",
"pageProb": 86,
"wordConf": 87
},
{
"leafNum": 14,
"confidence": 78,
"pageNumber": "12",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 15,
"confidence": 96,
"pageNumber": "13",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 16,
"confidence": 78,
"pageNumber": "14",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 17,
"confidence": 96,
"pageNumber": "15",
"pageProb": 86,
"wordConf": 93
},
{
"leafNum": 18,
"confidence": 87,
"pageNumber": "16",
"pageProb": 77,
"wordConf": 96
},
{
"leafNum": 19,
"confidence": 96,
"pageNumber": "17",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 20,
"confidence": 79,
"pageNumber": "18",
"pageProb": 69,
"wordConf": 96
},
{
"leafNum": 21,
"confidence": 96,
"pageNumber": "19",
"pageProb": 86,
"wordConf": 97
},
{
"leafNum": 22,
"confidence": 78,
"pageNumber": "20",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 23,
"confidence": 96,
"pageNumber": "21",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 24,
"confidence": 86,
"pageNumber": "22",
"pageProb": 76,
"wordConf": 96
},
{
"leafNum": 25,
"confidence": 96,
"pageNumber": "23",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 26,
"confidence": 78,
"pageNumber": "24",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 27,
"confidence": 96,
"pageNumber": "25",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 28,
"confidence": 78,
"pageNumber": "26",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 29,
"confidence": 96,
"pageNumber": "27",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 30,
"confidence": 78,
"pageNumber": "28",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 31,
"confidence": 96,
"pageNumber": "29",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 32,
"confidence": 78,
"pageNumber": "30",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 33,
"confidence": 96,
"pageNumber": "31",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 34,
"confidence": 78,
"pageNumber": "32",
"pageProb": 68,
"wordConf": 97
},
{
"leafNum": 35,
"confidence": 96,
"pageNumber": "33",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 36,
"confidence": 78,
"pageNumber": "34",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 37,
"confidence": 96,
"pageNumber": "35",
"pageProb": 86,
"wordConf": 95
},
{
"leafNum": 38,
"confidence": 78,
"pageNumber": "36",
"pageProb": 68,
"wordConf": 95
},
{
"leafNum": 39,
"confidence": 96,
"pageNumber": "37",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 40,
"confidence": 78,
"pageNumber": "38",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 41,
"confidence": 96,
"pageNumber": "39",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 42,
"confidence": 78,
"pageNumber": "40",
"pageProb": 68,
"wordConf": 95
},
{
"leafNum": 43,
"confidence": 96,
"pageNumber": "41",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 44,
"confidence": 78,
"pageNumber": "42",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 45,
"confidence": 96,
"pageNumber": "43",
"pageProb": 86,
"wordConf": 95
},
{
"leafNum": 46,
"confidence": 78,
"pageNumber": "44",
"pageProb": 68,
"wordConf": 95
},
{
"leafNum": 47,
"confidence": 96,
"pageNumber": "45",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 48,
"confidence": 78,
"pageNumber": "46",
"pageProb": 68,
"wordConf": 95
},
{
"leafNum": 49,
"confidence": 96,
"pageNumber": "47",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 50,
"confidence": 78,
"pageNumber": "48",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 51,
"confidence": 96,
"pageNumber": "49",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 52,
"confidence": 78,
"pageNumber": "50",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 53,
"confidence": 96,
"pageNumber": "51",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 54,
"confidence": 78,
"pageNumber": "52",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 55,
"confidence": 96,
"pageNumber": "53",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 56,
"confidence": 78,
"pageNumber": "54",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 57,
"confidence": 96,
"pageNumber": "55",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 58,
"confidence": 78,
"pageNumber": "56",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 59,
"confidence": 96,
"pageNumber": "57",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 60,
"confidence": 78,
"pageNumber": "58",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 61,
"confidence": 96,
"pageNumber": "59",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 62,
"confidence": 78,
"pageNumber": "60",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 63,
"confidence": 96,
"pageNumber": "61",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 64,
"confidence": 78,
"pageNumber": "62",
"pageProb": 68,
"wordConf": 97
},
{
"leafNum": 65,
"confidence": 96,
"pageNumber": "63",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 66,
"confidence": 78,
"pageNumber": "64",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 67,
"confidence": 96,
"pageNumber": "65",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 68,
"confidence": 78,
"pageNumber": "66",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 69,
"confidence": 96,
"pageNumber": "67",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 70,
"confidence": 78,
"pageNumber": "68",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 71,
"confidence": 96,
"pageNumber": "69",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 72,
"confidence": 78,
"pageNumber": "70",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 73,
"confidence": 96,
"pageNumber": "71",
"pageProb": 86,
"wordConf": 95
},
{
"leafNum": 74,
"confidence": 78,
"pageNumber": "72",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 75,
"confidence": 96,
"pageNumber": "73",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 76,
"confidence": 78,
"pageNumber": "74",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 77,
"confidence": 96,
"pageNumber": "75",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 78,
"confidence": 78,
"pageNumber": "76",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 79,
"confidence": 96,
"pageNumber": "77",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 80,
"confidence": 78,
"pageNumber": "78",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 81,
"confidence": 96,
"pageNumber": "79",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 82,
"confidence": 78,
"pageNumber": "80",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 83,
"confidence": 96,
"pageNumber": "81",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 84,
"confidence": 78,
"pageNumber": "82",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 85,
"confidence": 96,
"pageNumber": "83",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 86,
"confidence": 78,
"pageNumber": "84",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 87,
"confidence": 96,
"pageNumber": "85",
"pageProb": 86,
"wordConf": 95
},
{
"leafNum": 88,
"confidence": 78,
"pageNumber": "86",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 89,
"confidence": 96,
"pageNumber": "87",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 90,
"confidence": 78,
"pageNumber": "88",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 91,
"confidence": 96,
"pageNumber": "89",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 92,
"confidence": 78,
"pageNumber": "90",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 93,
"confidence": 96,
"pageNumber": "91",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 94,
"confidence": 78,
"pageNumber": "92",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 95,
"confidence": 96,
"pageNumber": "93",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 96,
"confidence": 78,
"pageNumber": "94",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 97,
"confidence": 96,
"pageNumber": "95",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 98,
"confidence": 78,
"pageNumber": "96",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 99,
"confidence": 96,
"pageNumber": "97",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 100,
"confidence": 78,
"pageNumber": "98",
"pageProb": 68,
"wordConf": 96
},
{
"leafNum": 101,
"confidence": 96,
"pageNumber": "99",
"pageProb": 86,
"wordConf": 96
},
{
"leafNum": 102,
"confidence": 76,
"pageNumber": "100",
"pageProb": 66,
"wordConf": 96
},
{
"leafNum": 103,
"confidence": 95,
"pageNumber": "101",
"pageProb": 85,
"wordConf": 96
},
{
"leafNum": 104,
"confidence": 76,
"pageNumber": "102",
"pageProb": 66,
"wordConf": 96
},
{
"leafNum": 105,
"confidence": 95,
"pageNumber": "103",
"pageProb": 85,
"wordConf": 96
},
{
"leafNum": 106,
"confidence": 76,
"pageNumber": "104",
"pageProb": 66,
"wordConf": 96
},
{
"leafNum": 107,
"confidence": 95,
"pageNumber": "105",
"pageProb": 85,
"wordConf": 96
},
{
"leafNum": 108,
"confidence": 76,
"pageNumber": "106",
"pageProb": 66,
"wordConf": 96
},
{
"leafNum": 109,
"confidence": 95,
"pageNumber": "107",
"pageProb": 85,
"wordConf": 96
},
{
"leafNum": 110,
"confidence": 76,
"pageNumber": "108",
"pageProb": 66,
"wordConf": 96
},
{
"leafNum": 111,
"confidence": 95,
"pageNumber": "109",
"pageProb": 85,
"wordConf": 96
},
{
"leafNum": 112,
"confidence": 76,
"pageNumber": "110",
"pageProb": 66,
"wordConf": 96
},
{
"leafNum": 113,
"confidence": 95,
"pageNumber": "111",
"pageProb": 85,
"wordConf": 95
},
{
"leafNum": 114,
"confidence": 76,
"pageNumber": "112",
"pageProb": 66,
"wordConf": 96
},
{
"leafNum": 115,
"confidence": 95,
"pageNumber": "113",
"pageProb": 85,
"wordConf": 96
},
{
"leafNum": 116,
"confidence": 75,
"pageNumber": "114",
"pageProb": 65,
"wordConf": 95
},
{
"leafNum": 117,
"confidence": 95,
"pageNumber": "115",
"pageProb": 85,
"wordConf": 95
}
]
}
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
Copyright (c) Psion PLC (1995)
26th May 1995
CONTENTS OF FILES ON DISK
=========================
readme.txt
ASSEMBLER INCLUDE FILES
epoc.inc
epocser.inc
epoclib.inc
epocsibo.inc
ossibo.inc
ospack.inc
C FILES
a4test.c ;Fully comprehensive test program for A4EXIF.LDD
a4test.pr ;TopSpeed Project file for the above test program
ASM FILES
a4exif.asm ;Source code file for A4EXIF.LDD
sys$as5.asm ;Source code file for SYS$AS5.PDD
tmpsaldd.asm ;Standard stand-alone LDD template
tmpa4ldd.asm ;Template LDD for ASIC4/5-based peripherals
+1023
View File
File diff suppressed because it is too large Load Diff
+808
View File
@@ -0,0 +1,808 @@
title NAME.LDD device driver for S3/S3a/S3c/S3../HC
subttl Copyright PSION PLC November 1995
name NAME
; VERSION DATE DESCRIPTION
; ------- -------- ---------------
; 1.0 12/01/95 Template Asic4/5 LDD
; Written by Jason Robinson January 1995
BUILDSB equ 1
Asic4Type =0
Asic5Type =0
NeedTheInterrupt =0
NeedClocking =0
StartChannelInOpen =0
include ..\inc\epoc.inc
include ..\inc\epocser.inc
include ..\inc\epoclib.inc
include ..\inc\epocsibo.inc
include ..\srcs\ossibo.inc
include ..\srcs\ospack.inc
WeSupportS3Only equ Lcd240X80
WeSupportS3aOnly equ Lcd480X160
WeSupportS3cOnly equ Lcd240X100
WeSupportHCOnly equ Lcd160X80
if Consumer
NumberOfChannels equ 1
if Asic9
TheMachineWeSupport equ WeSupportS3aOnly
else
TheMachineWeSupport equ WeSupportS3Only
endif
else
NumberOfChannels equ 3
if Asic9
TheMachineWeSupport equ WeSupportS3cOnly
else
TheMachineWeSupport equ WeSupportHCOnly
endif
endif
A4PERIPH_MASK equ 0f0h
TheInfoByteThatWeWant equ 1
TmpltEnt struc
TmpltEntOpen db ? ; Channel open flag
TmpltChannelStarted db ? ; Channel should be running
TmpltHwChannel dw ? ; The harware we want
TmpltStructPtr dw ? ; Ptr to CB in Apps DS
TmpltTckHandle dw ? ; Tick Handle
TmpltChannelRunning db ? ; Can channel really run
TmpltInterruptMask db ? ; InterruptMask
TmpltChannel db ? ; Channel
if NeedTheInterrupt
TmpltInterruptNumber db ? ; Interrupt Number
TmpltInterruptVector dw ? ; Were to Jump To
if Asic5Type and NeedClocking
TmpltA5Clocking db ? ; Asic5 baud rate clocking
TmpltSpare db ?
endif
else
TmpltSpare db ?
endif
TmpltEnt ends
TmpltStruct struc
Tmpltblk ChanEnt <> ; I/O control block
TmpltEntPtr dw ? ; Ptr to our CS CB
TmpltStruct ends
dgroup group stack
assume ds:dgroup,es:dgroup,ss:dgroup
CodeSeg
ProcBegin@ TmpltLDD
; ===================
dw LDDSignature
db 'HSB',0,0,0,0,0
dw (TableEnd-TableStart)/2
TableStart:
dw TmpltInstall
dw TmpltRemove
dw TmpltHold
dw TmpltResume
dw TmpltReset
dw TmpltUnits
dw TmpltOpen
dw TmpltStrategy
MonitorVector:
dw MonitorInt
TableEnd:
if Consumer
if Asic9
SetupTable db mask A9MSlave,SelectChannel5
db (mask A9MClockEnable5 shr 8),HwIrq2Revector
if NeedTheInterrupt
dw offset Interrupt0
endif
else
SetupTable db mask Asic2Int,SelectChannel7
db (mask ClockEnable7 shr 8),HwIrq4Revector
if NeedTheInterrupt
dw offset Interrupt0
endif
endif
else
if Asic9
SetupTable db mask A9MExpIntA,SelectChannel3
db (mask A9MClockEnable3 shr 8),HwIrq4Revector
if NeedTheInterrupt
dw offset Interrupt0
endif
db mask A9MExpIntB,SelectChannel4
db (mask A9MClockEnable4 shr 8),HwIrq5Revector
if NeedTheInterrupt
dw offset Interrupt1
endif
db mask A9MSlave,SelectChannel5
db (mask A9MClockEnable5 shr 8),HwIrq2Revector
if NeedTheInterrupt
dw offset Interrupt2
endif
else
SetupTable db mask ExpIntLeftA,ExpChannelLeftA
db (mask ClockEnable6 shr 8),HwIrq3Revector
if NeedTheInterrupt
dw offset Interrupt0
endif
db mask ExpIntRightB,ExpChannelRightB
db (mask ClockEnable5 shr 8),HwIrq2Revector
if NeedTheInterrupt
dw offset Interrupt1
endif
db mask Asic2Int,SelectChannel7
db (mask ClockEnable7 shr 8),HwIrq4Revector
if NeedTheInterrupt
dw offset Interrupt2
endif
endif
endif
CsHeldFlag db 0 ; Ldd under a hold?
MachineType db 0
if Consumer
Channel0 TmpltEnt <>
else
Channel0 TmpltEnt <>
Channel1 TmpltEnt <>
Channel2 TmpltEnt <>
endif
ProcEnd noret
ProcBegin@ TmpltInstall,far
; ===========================
; Install the driver
; Exit with carry clear for okay
GenLcdType ; Do we only run on
mov MachineType,al ; particular machines?
cmp al,TheMachineWeSupport
jne CannotInstallOnThisMachine
pushf
cli
push ds
mov ax, cs
mov ds, ax
mov CsHeldFlag, 0 ; Initialise all
mov cx, NumberOfChannels ; the variables
mov di, offset Channel0
mov si, offset SetupTable
ResetAllChannelsLoop:
mov [di].TmpltEntOpen, 0
lodsb
mov [di].TmpltInterruptMask, al
lodsb
mov [di].TmpltChannel, al
lodsb
if NeedClocking
mov [di].TmpltA5Clocking, al
endif
lodsb
if NeedTheInterrupt
mov [di].TmpltInterruptNumber, al
lodsw
mov [di].TmpltInterruptVector, ax
endif
add di,size TmpltEnt
loop ResetAllChannelsLoop
pop ds
popf
FinishedOkay:
clc
ret
CannotInstallOnThisMachine:
mov ax,NotSupportedErr
stc
ret
ProcEnd noret
ProcBegin@ TmpltRemove,far
; ==========================
; Remove the driver
; Exit with carry clear for okay
; Exit with carry set if channel still open
mov cx,NumberOfChannels
mov di,offset Channel0
xor ax,ax
CheckAllChannelsClosedLoop:
cmp cs:[di].TmpltEntOpen,al
jne WeHaveAnOpenChannelSoFail
add di,size TmpltEnt
loop CheckAllChannelsClosedLoop
jmp FinishedOkay
WeHaveAnOpenChannelSoFail:
mov ax,InUseErr
stc
ret
ProcEnd noret
ProcBegin@ TmpltUnits,far
; =========================
; Return the Number of channels that can be opened in AX
mov ax,NumberOfChannels ; We have ? channels
ret
ProcEnd noret
ProcBegin@ TmpltReset,far
; =========================
; Reset the open channel
mov di,cx
cmp cs:[di].TmpltEntOpen,0 ; Somethings gone
je NotOpenToReset ; wrong with the
mov ah,DevHoldPowerDown ; application that
call StopTheChannel ; opened us.
call FreeHardware ; Stop interupts and
mov cs:[di].TmpltEntOpen,0 ; free the channel
NotOpenToReset:
ret
ProcEnd noret
ProcBegin@ TmpltHold,far
; ========================
; Hold the channels
; Hold reason in AH
mov cx,1
xchg cl,CsHeldFlag ; Stop any interrupts
cmp cl,0 ; Mark channel as
jne AlreadyHeld ; under a normal hold
DoHold:
mov cx,NumberOfChannels
mov di,offset Channel0
HoldAllTheChannelsLoop:
cmp cs:[di].TmpltEntOpen,0
je DontHoldBecauseNotOpen
cmp cs:[di].TmpltChannelStarted,0
je DontHoldBecauseNotOpen
push ax
push cx
call StopTheChannel
pop cx
pop ax
DontHoldBecauseNotOpen:
add di,size TmpltEnt
loop HoldAllTheChannelsLoop
AlreadyHeld:
ret
ProcEnd noret
ProcBegin@ TmpltResume,far
; ==========================
; Resume the channel
xor cx,cx ; Be warned during an
xchg cl,CsHeldFlag ; on/off the power to
cmp cl,0 ; ASIC4 is not restored
je AlreadyResumed ; until after this
GenDataSegment ; A resume is pointless
HwGetSsdData ; if there is still no
mov bx,ax ; power to the port
cmp es:[bx].SsdDoorStatus,DoorOpen ; Leave the resume
je DoorsAreOpen ; to the tick
DoResume:
mov cx,NumberOfChannels
mov di,offset Channel0 ; Right hardware?
ResumeAllTheChannelsLoop: ; Else restart
cmp cs:[di].TmpltEntOpen,0 ; interrupts
je DontResumeBecauseNotOpen
cmp cs:[di].TmpltChannelStarted,0
je DontResumeBecauseNotOpen
push cx
call StartTheChannel
pop cx
DontResumeBecauseNotOpen:
add di,size TmpltEnt
loop ResumeAllTheChannelsLoop
AlreadyResumed:
ret
DoorsAreOpen:
mov CsHeldFlag,2
ret
ProcEnd noret
ProcBegin@ TmpltOpen,far
; ========================
; Open the channel
; DS,ES,SS Applications data space
; Handle in DX
cld
pushf
cli
call FindFreeChannel
jnc GotFreeChannel
popf
LockedError:
mov ax,LockedErr
stc
ret
AllErrorsExitHere:
push ax
HeapFreeCell
pop ax
FreeTckAndChannel:
push ax
call FreeHardware
pop ax
FreeTckAndExit:
push ax
mov bx,cs:[di].TmpltTckHandle
IoClose
pop ax
CloseThenFree:
mov cs:[di].TmpltEntOpen,0
stc
ret
GotFreeChannel:
mov cs:[di].TmpltEntOpen,1
mov cs:[di].TmpltChannelStarted,0
mov cs:[di].TmpltChannelRunning,0
popf
xor ax,ax
push ax
mov ax,((':' shl 8)+'K')
push ax
mov ax,(('C' shl 8)+'T')
push ax
mov bx,sp
IoOpen
jnc GotATickChannel
add sp,6
mov ax,LockedErr
jmp CloseThenFree
GotATickChannel:
add sp,6
mov cs:[di].TmpltTckHandle,ax
push dx
mov bx,ax
mov ax,IoFuncStart
mov cx,1
push cx ; Frequency 1 tick
push di ; Data is our CS CB
push dx ; Our device handle
mov cx,(MonitorVector-TableStart)/2 ; Vector to call
push cx
mov cx,sp
IoWithWait
add sp,8
pop dx
call GetHardware
jc FreeTckAndExit
mov cx,(size TmpltStruct) ; Get ourselfs a CB
HeapAllocateCell ; In the applications
jc FreeTckAndChannel ; Data space
mov bx,ax
mov [bx].Tmpltblk.ChanNext,bx
mov [bx].Tmpltblk.ChanSignature,IoChanSignature
mov [bx].Tmpltblk.ChanLibHandle,dx
mov [bx].TmpltEntPtr,di
push bx
mov bx,dx
mov cx,di
IoRequestReset
pop bx
if StartChannelInOpen
call StartChannelFromOpen
endif
xor ax,ax
ret
ProcEnd noret
StrategyVectorTable label word
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset TmpltClose
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
ProcBegin@ TmpltStrategy,far
; ============================
; BX is our control block
; SI points to the parameters
; DS,ES Applications data space
mov ax,[si].RqFunction
mov dx,[si].RqA1Ptr
cmp ax,IoFuncSuperFrame
ja StrategyDefault
shl ax,1
mov di,ax
push StrategyVectorTable[di]
mov di,[bx].TmpltEntPtr
retn
StrategyDefault:
IoRoot
ret
ExitStrategyOkay:
xor ax,ax
ExitStrategy:
mov di,[si].RqStatusPtr
mov word ptr [di],ax
cmp ax,PendingErr
je JustExit
IoSignal
JustExit:
xor ax,ax
ret
ProcEnd noret
ProcBegin@ TmpltClose,far
; =========================
; Close a channel
; DI CS control block
; BX DS control block
; SI points to aurguments on stack
; DX Argument one pointer
mov ah,DevHoldPowerDown
call StopTheChannel
call FreeHardware
mov cs:[di].TmpltChannelStarted,0
push bx
mov bx,[bx].Tmpltblk.ChanLibHandle
mov cx,di
IoRequestResetCancel
pop bx
HeapFreeCell
mov bx,cs:[di].TmpltTckHandle
IoClose
mov cs:[di].TmpltEntOpen,0
jmp short ExitStrategyOkay
ProcEnd noret
ProcBegin@ StartTheChannel
; ==========================
; Start the Channel Running
; Assume channel set up and interrupts off
cmp cs:[di].TmpltChannelRunning,0
jne ChannelAlreadyRunning
if Asic5Type and NeedClocking
if Asic9
in ax, A9WControlExtraRW ; Start the S3s clock
or ah, [bx].TmpltA5Clocking ; Generator
out A9WControlExtraRW, ax
else
mov al, [bx].TmpltA5Clocking
HwSetA2Control2Bits
endif
endif
if NeedTheInterrupt
mov al, cs:[di].TmpltInterruptNumber ; Load the address of
mov cx, cs ; the appropriate
mov bx, cs:[di].TmpltInterruptVector ; interrupt routine
GenSetRevector ; into the correct vector
if Asic9
in al,A9BInterruptMaskRW ; Set the mask
or al,cs:[di].TmpltInterruptMask ; to enable Interrupts
out A9BInterruptMaskRW,al
else
in al, A1InterruptMask
or al,cs:[di].TmpltInterruptMask
out A1InterruptMask, al
endif
endif
mov cs:[di].TmpltChannelRunning,1
ChannelAlreadyRunning:
ret
ProcEnd noret
ProcBegin@ StopTheChannel
; =========================
; Stop the Channel Running
; Assume channel set up and interrupts off
; Stop reason in AH
cmp byte ptr cs:[di].TmpltChannelRunning,0
je ChannelAlreadyStopped
if NeedTheInterrupt
mov ah,cs:[di].TmpltInterruptMask
not ah
if Asic9
in al,A9BInterruptMaskRW ; Stop Interrupts
and al,ah ; By clearing the
out A9BInterruptMaskRW,al ; Mask and resetting
else ; The Vector
in al, A1InterruptMask
and al,ah
out A1InterruptMask, al
endif
mov al, cs:[di].TmpltInterruptNumber
GenResetRevector
endif
if Asic5Type and NeedClocking
if Asic9
mov cl, [bx].TmpltA5Clocking ; Turn off baud rate
not cl ; clocking from the
in ax, A9WControlExtraRW ; S3/3a
and ah, cl
out A9WControlExtraRW, ax
else
mov al, [bx].TmpltA5Clocking
HwClearA2Control2Bits
endif
endif
mov cs:[di].TmpltChannelRunning,0
ChannelAlreadyStopped:
ret
ProcEnd noret
if StartChannelInOpen
ProcBegin@ StartChannelFromOpen
; ===============================
pushf
cli
mov cs:[di].TmpltChannelStarted,1
mov al,cs:[di].TmpltChannel
HwSelectChannel
push ax
call StartTheChannel
pop ax
HwSelectChannel
popf
ret
ProcEnd noret
endif
ProcBegin@ GetHardware
; ======================
mov al, cs:[di].TmpltInterruptMask
HwGetChannel
jc ChannelNotAvailable
pushf
cli
mov al,cs:[di].TmpltChannel
HwSelectChannel
push ax
call CheckForHardware
pop ax
jc NoHardwareFreeTheChannel
HwSelectChannel
popf
clc
ret
NoHardwareFreeTheChannel:
HwSelectChannel
popf
mov al, cs:[di].TmpltInterruptMask
HwFreeChannel
ChannelNotAvailable:
stc
ret
ProcEnd noret
ProcBegin@ FreeHardware
; =======================
mov al, cs:[di].TmpltInterruptMask
HwFreeChannel
ret
ProcEnd noret
ProcBegin@ FindFreeChannel
; ==========================
mov si,[si].OpenNamePtr
mov al,[si+1]
CharToFoldedChar
cmp al,'A'
jb CantGetThatChannel
sub al,'A'
cmp al,NumberOfChannels
jae CantGetThatChannel
xor ah,ah
push dx
mov dx,size TmpltEnt
mul dx
pop dx
mov di,ax
add di,offset Channel0
cmp cs:[di].TmpltEntOpen,0
je FoundAFreeChannel
CantGetThatChannel:
stc
ret
FoundAFreeChannel:
clc
ret
ProcEnd noret
ProcBegin@ CheckForHardware
; ===========================
; Assume Interrupts are off
; Correct channel should be selected
; Out: clc - got correct hardware
; stc - wrong or no hardware
if Asic5Type
HwNullFrame ; Check that the
mov al,(SerialSelect or Asic5NormalId) ; Harware is there
SBUSY ; And that it is what
SCONTOUT ; It should be
XNOP ; First look for An
SBUSY ; ASIC5 at the other
SDATAIN ; End of the link
test al,al
je ConnectionFailed
GotConnection:
popf
clc
ret
ConnectionFailed:
mov al, SerialSelect or Asic4Id ; Asic4 Id
SBUSY
SCONTOUT
XNOP
SBUSY
SDATAIN
test al, al
jne ConnectionFailedExit
else
HwNullFrame
mov al,(SerialSelect or Asic4Id)
SBUSY ; First look for An
SCONTOUT ; ASIC4 at the other
XNOP ; End of the link
SBUSY
SDATAIN
test al, al
je ConnectionFailed
mov al,SerialReadSingle or A4InfoR ; Now see if we have
SBUSY ; The right card
SCONTOUT
XNOP ; Allows the busy signal to come through for the wait
SBUSY
SDATAIN
and al,A4PERIPH_MASK
cmp al,TheInfoByteThatWeWant
jne ConnectionFailedExit
clc
ret
ConnectionFailed:
mov al,(SerialSelect or Asic5NormalId) ; Select as an
SBUSY ; Asic5 peripheral
SCONTOUT
XNOP
SDATAIN
test al,al
jne ConnectionFailedExit
endif
mov al, SerialSelect or Asic8Id ; Modem chip Id
SBUSY
SCONTOUT
XNOP
SBUSY
SDATAIN
test al, al
jne ConnectionFailedExit
mov al, SerialSelect or Asic5PackId ; Asic5pack Id
SBUSY
SCONTOUT
XNOP
SBUSY
SDATAIN
ConnectionFailedExit:
stc
ret
ProcEnd noret
ProcBegin@ MonitorInt,far
; =========================
; Called on every tick
; Issues pack door hold and resumes
; In: Door state in SI
cmp si,DoorOpen
je TheDoorIsOpenSoCantDoAnything
cmp CsHeldFlag,2
je NeedToDoTheResume
ret
NeedToDoTheResume:
mov CsHeldFlag,0
jmp DoResume
TheDoorIsOpenSoCantDoAnything:
cmp CsHeldFlag,0
jne DontNeedToHold
mov CsHeldFlag,2
mov ah,DevHoldPowerFail
jmp DoHold
DontNeedToHold:
ret
ProcEnd noret
if NeedTheInterrupt
ife Consumer
ProcBegin@ Interrupt2,far
; =========================
mov di,offset Channel2
mov ax,PortCActive
jmp ComInt
ProcEnd noret
ProcBegin@ Interrupt1,far
; =========================
mov di,offset Channel1
mov ax,PortBActive
jmp ComInt
ProcEnd noret
endif
ProcBegin@ Interrupt0,far
; =========================
mov di,offset Channel0
mov ax,PortAActive
; FALLTHROUGH to ComInt
ProcEnd noret
ProcBegin@ ComInt,far
; =====================
if Asic9
out A9BNonSpecificEoiW,al
else
out A1NonSpecificEoi, al
endif
clc
ret
ProcEnd noret
endif
EndCodeSeg
stack segment stack para 'data'
stack ends
end TmpltLDD
+531
View File
@@ -0,0 +1,531 @@
title NAME.LDD device driver for S3/S3a/S3c/S3../HC
subttl Copyright PSION PLC November 1995
name NAME
; VERSION DATE DESCRIPTION
; ------- -------- ---------------
; 1.0 12/01/95 Template LDD
; Written by Jason Robinson January 1995
BUILDSB equ 1
include ..\inc\epoc.inc
include ..\inc\epocser.inc
include ..\inc\epoclib.inc
include ..\inc\epocsibo.inc
include ..\srcs\ossibo.inc
include ..\srcs\ospack.inc
OpenSpecificChannel =0
MachineSpecific =0
PeripheralTypeDriver =0
TickRequired =0
NumberOfChannels equ 1
WeSupportS3Only equ Lcd240X80
WeSupportS3aOnly equ Lcd480X160
WeSupportS3cOnly equ Lcd240X100
WeSupportHCOnly equ Lcd160X80
TheMachineWeSupport equ WeSupportS3aOnly
TmpltEnt struc
TmpltEntOpen db ? ; Channel open flag
TmpltChannelStarted db ? ; Channel should be running
TmpltChannelRunning db ? ; Can channel really run
TmpltSpare db ?
TmpltHwChannel dw ? ; The harware we want
TmpltStructPtr dw ? ; Ptr to CB in Apps DS
if PeripheralTypeDriver or TickRequired
TmpltTckHandle dw ? ; Tick Handle
endif
TmpltEnt ends
TmpltStruct struc
Tmpltblk ChanEnt <> ; I/O control block
TmpltEntPtr dw ? ; Ptr to our CS CB
TmpltStruct ends
dgroup group stack
assume ds:dgroup,es:dgroup,ss:dgroup
CodeSeg
ProcBegin@ TmpltLDD
; ===================
dw LDDSignature
db 'HSB',0,0,0,0,0
dw (TableEnd-TableStart)/2
TableStart:
dw TmpltInstall
dw TmpltRemove
dw TmpltHold
dw TmpltResume
dw TmpltReset
dw TmpltUnits
dw TmpltOpen
dw TmpltStrategy
if PeripheralTypeDriver or TickRequired
MonitorVector:
dw MonitorInt
endif
TableEnd:
CsHeldFlag db 0 ; Ldd under a hold?
MachineType db 0
Channel0 TmpltEnt <>
ProcEnd noret
ProcBegin@ TmpltInstall,far
; ===========================
; Install the driver
; Exit with carry clear for okay
if MachineSpecific
GenLcdType ; Do we only run on
mov MachineType,al ; particular machines?
cmp al,TheMachineWeSupport
jne CannotInstallOnThisMachine
endif
mov CsHeldFlag,0 ; Initialise all
mov cx,NumberOfChannels ; the variables
mov di,offset Channel0
xor ax,ax
ResetAllChannelsLoop:
mov cs:[di].TmpltEntOpen,al
add di,size TmpltEnt
loop ResetAllChannelsLoop
FinishedOkay:
clc
ret
CannotInstallOnThisMachine:
mov ax,NotSupportedErr
stc
ret
ProcEnd noret
ProcBegin@ TmpltRemove,far
; ==========================
; Remove the driver
; Exit with carry clear for okay
; Exit with carry set if channel still open
mov cx,NumberOfChannels
mov di,offset Channel0
xor ax,ax
CheckAllChannelsClosedLoop:
cmp cs:[di].TmpltEntOpen,al
jne WeHaveAnOpenChannelSoFail
add di,size TmpltEnt
loop CheckAllChannelsClosedLoop
jmp FinishedOkay
WeHaveAnOpenChannelSoFail:
mov ax,InUseErr
stc
ret
ProcEnd noret
ProcBegin@ TmpltUnits,far
; =========================
; Return the Number of channels that can be opened in AX
mov ax,NumberOfChannels ; We have ? channels
ret
ProcEnd noret
ProcBegin@ TmpltReset,far
; =========================
; Reset the open channel
mov di,cx
cmp cs:[di].TmpltEntOpen,0 ; Somethings gone
je NotOpenToReset ; wrong with the
mov ah,DevHoldPowerDown ; application that
call StopTheChannel ; opened us.
call FreeHardware ; Stop interupts and
mov cs:[di].TmpltEntOpen,0 ; free the channel
NotOpenToReset:
ret
ProcEnd noret
ProcBegin@ TmpltHold,far
; ========================
; Hold the channels
; Hold reason in AH
mov cx,1
xchg cl,CsHeldFlag ; Stop any interrupts
cmp cl,0 ; Mark channel as
jne AlreadyHeld ; under a normal hold
DoHold:
mov cx,NumberOfChannels
mov di,offset Channel0
HoldAllTheChannelsLoop:
cmp cs:[di].TmpltEntOpen,0
je DontHoldBecauseNotOpen
cmp cs:[di].TmpltChannelStarted,0
je DontHoldBecauseNotOpen
push ax
push cx
call StopTheChannel
pop cx
pop ax
DontHoldBecauseNotOpen:
add di,size TmpltEnt
loop HoldAllTheChannelsLoop
AlreadyHeld:
ret
ProcEnd noret
ProcBegin@ TmpltResume,far
; ==========================
; Resume the channel
xor cx,cx ; Be warned during an
xchg cl,CsHeldFlag ; on/off the power to
cmp cl,0 ; ASIC4 is not restored
je AlreadyResumed ; until after this
if PeripheralTypeDriver
GenDataSegment ; A resume is pointless
HwGetSsdData ; if there is still no
mov bx,ax ; power to the port
cmp es:[bx].SsdDoorStatus,DoorOpen ; Leave the resume
je DoorsAreOpen ; to the tick
endif
DoResume:
mov cx,NumberOfChannels
mov di,offset Channel0 ; Right hardware?
ResumeAllTheChannelsLoop: ; Else restart
cmp cs:[di].TmpltEntOpen,0 ; interrupts
je DontResumeBecauseNotOpen
cmp cs:[di].TmpltChannelStarted,0
je DontResumeBecauseNotOpen
push cx
call StartTheChannel
pop cx
DontResumeBecauseNotOpen:
add di,size TmpltEnt
loop ResumeAllTheChannelsLoop
AlreadyResumed:
ret
if PeripheralTypeDriver
DoorsAreOpen:
mov CsHeldFlag,2
ret
endif
ProcEnd noret
ProcBegin@ TmpltOpen,far
; ========================
; Open the channel
; DS,ES,SS Applications data space
; Handle in DX
cld
pushf
cli
call FindFreeChannel
jnc GotFreeChannel
popf
LockedError:
mov ax,LockedErr
stc
ret
AllErrorsExitHere:
push ax
HeapFreeCell
pop ax
FreeTckAndChannel:
push ax
call FreeHardware
pop ax
FreeTckAndExit:
if PeripheralTypeDriver or TickRequired
push ax
mov bx,cs:[di].TmpltTckHandle
IoClose
pop ax
endif
CloseThenFree:
mov cs:[di].TmpltEntOpen,0
stc
ret
GotFreeChannel:
mov cs:[di].TmpltEntOpen,1
mov cs:[di].TmpltChannelStarted,0
mov cs:[di].TmpltChannelRunning,0
popf
if PeripheralTypeDriver or TickRequired
xor ax,ax
push ax
mov ax,((':' shl 8)+'K')
push ax
mov ax,(('C' shl 8)+'T')
push ax
mov bx,sp
IoOpen
jnc GotATickChannel
add sp,6
mov ax,LockedErr
jmp CloseThenFree
GotATickChannel:
add sp,6
mov cs:[di].TmpltTckHandle,ax
push dx
mov bx,ax
mov ax,IoFuncStart
mov cx,1
push cx ; Frequency 1 tick
push di ; Data is our CS CB
push dx ; Our device handle
mov cx,(MonitorVector-TableStart)/2 ; Vector to call
push cx
mov cx,sp
IoWithWait
add sp,8
pop dx
endif
call GetHardware
jc FreeTckAndExit
mov cx,(size TmpltStruct) ; Get ourselfs a CB
HeapAllocateCell ; In the applications
jc FreeTckAndChannel ; Data space
mov bx,ax
mov [bx].Tmpltblk.ChanNext,bx
mov [bx].Tmpltblk.ChanSignature,IoChanSignature
mov [bx].Tmpltblk.ChanLibHandle,dx
mov [bx].TmpltEntPtr,di
push bx
mov bx,dx
mov cx,di
IoRequestReset
pop bx
xor ax,ax
ret
ProcEnd noret
StrategyVectorTable label word
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset TmpltClose
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
dw offset StrategyDefault
ProcBegin@ TmpltStrategy,far
; ============================
; BX is our control block
; SI points to the parameters
; DS,ES Applications data space
mov ax,[si].RqFunction
mov dx,[si].RqA1Ptr
cmp ax,IoFuncSuperFrame
ja StrategyDefault
shl ax,1
mov di,ax
push StrategyVectorTable[di]
mov di,[bx].TmpltEntPtr
retn
StrategyDefault:
IoRoot
ret
ExitStrategyOkay:
xor ax,ax
ExitStrategy:
mov di,[si].RqStatusPtr
mov word ptr [di],ax
cmp ax,PendingErr
je JustExit
IoSignal
JustExit:
xor ax,ax
ret
ProcEnd noret
ProcBegin@ TmpltClose,far
; =========================
; Close a channel
; DI CS control block
; BX DS control block
; SI points to aurguments on stack
; DX Argument one pointer
mov ah,DevHoldPowerDown
call StopTheChannel
call FreeHardware
push bx
mov bx,[bx].Tmpltblk.ChanLibHandle
mov cx,di
IoRequestResetCancel
pop bx
HeapFreeCell
if PeripheralTypeDriver or TickRequired
mov bx,cs:[di].TmpltTckHandle
IoClose
endif
mov cs:[di].TmpltEntOpen,0
jmp short ExitStrategyOkay
ProcEnd noret
ProcBegin@ StartTheChannel
; ==========================
; Start the Channel Running
cmp byte ptr cs:[di].TmpltChannelRunning,0
jne ChannelAlreadyRunning
mov cs:[di].TmpltChannelRunning,1
ChannelAlreadyRunning:
clc
ret
ProcEnd noret
ProcBegin@ StopTheChannel
; =========================
; Stop the Channel Running
cmp byte ptr cs:[di].TmpltChannelRunning,0
je ChannelAlreadyStopped
mov cs:[di].TmpltChannelRunning,0
ChannelAlreadyStopped:
clc
ret
ProcEnd noret
ProcBegin@ GetHardware
; ======================
clc
ret
ProcEnd noret
ProcBegin@ FreeHardware
; =======================
clc
ret
ProcEnd noret
If OpenSpecificChannel
ProcBegin@ FindFreeChannel
; ==========================
; Called from Open
; Must preserve DX,SI
; Channel out in DI or carry set if failure
mov si,[si].OpenNamePtr
mov al,[si+1]
CharToFoldedChar
cmp al,'A'
jb CantGetThatChannel
sub al,'A'
cmp al,NumberOfChannels
jae CantGetThatChannel
xor ah,ah
push dx
mov dx,size TmpltEnt
mul dx
pop dx
mov di,ax
add di,offset Channel0
cmp cs:[di].TmpltEntOpen,0
je FoundAFreeChannel
CantGetThatChannel:
stc
ret
FoundAFreeChannel:
clc
ret
ProcEnd noret
else
ProcBegin@ FindFreeChannel
; ==========================
mov cx,NumberOfChannels
mov di,offset Channel0
HuntAllChannelsLoop:
cmp cs:[di].TmpltEntOpen,0
je FoundAFreeChannel
add di,size TmpltEnt
loop HuntAllChannelsLoop
stc
ret
FoundAFreeChannel:
clc
ret
ProcEnd noret
endif
if PeripheralTypeDriver or TickRequired
ProcBegin@ MonitorInt,far
; =========================
if PeripheralTypeDriver
; Called on every tick
; Issues pack door hold and resumes
; In: Door state in SI
cmp si,DoorOpen
je TheDoorIsOpenSoCantDoAnything
cmp CsHeldFlag,2
je NeedToDoTheResume
ret
NeedToDoTheResume:
mov CsHeldFlag,0
jmp DoResume
TheDoorIsOpenSoCantDoAnything:
cmp CsHeldFlag,0
jne DontNeedToHold
mov CsHeldFlag,2
mov ah,DevHoldPowerFail
jmp DoHold
DontNeedToHold:
endif
ret
ProcEnd noret
endif
EndCodeSeg
stack segment stack para 'data'
stack ends
end TmpltLDD

+144
View File
@@ -0,0 +1,144 @@
# Finishing the Workabout MX scanner — on-device continuation guide
This document describes exactly how to continue from the current findings to a
fully working scanner client, using on-device debugging. Read `SCANNER-API.md`
first for the findings this builds on.
## Where we are
Established (see `SCANNER-API.md` for detail):
- The integral laser is driven as an **OO library object** in `SCANNER.DYL`
(category token seen in ROM: **`oscanner`**), *not* by raw device I/O.
- The app path is the standard OLIB one: `p_getlibh``p_newsend`/`f_newsend`
(create + init) → `p_send` (configure / trigger / read).
- Internals confirmed: `WL2:D` is the driver's channel; control ops **6** then
**7** (`p_iow(chan,6)`, `p_iow(chan,7)`) fire the laser to a good decode
(green LED) on the physical device.
- The decompiled read (`FUN_858d`) creates the object via `LIBMANAGER`
(`NMLIBCREATE`/`NMLIBCREATEBYHANDLE`), sets up a buffer, then reads; the
decode lands CR/LF-terminated (default Symbol2 postamble).
Missing, and why on-device: the **message ordinals** and **parameter structs**
for the scanner's methods. OLIB assigns ordinals dynamically from the whole
class hierarchy (including base classes in `olib`/`hwim`), so they cannot be
read reliably from a static dump — but they resolve at runtime, where the
debugger can capture them. MAME cannot inject a barcode, so validation must be
on hardware anyway.
## What to capture
For a working client you need five things:
1. The scanner **category** (confirm the name/id for `p_getlibh` — candidate
`oscanner`).
2. The scanner **class** created by the app.
3. The **message ordinals** for: init, set-parameters, enable/trigger, read.
4. The **parameter/result structs** each message takes (esp. the read result
buffer and its length).
5. The exact **call sequence** the working app uses.
## Tooling: the SIBO Debugger
The SIBO Debugger (see manual `2-03`/`2-04`, "The SIBO Debugger") supports:
- **Remote debugging**: development PC connected to the Workabout by a serial
cable; debug up to 8 processes.
- **Breakpoints in dynamic libraries / shared code** — required to break inside
`SCANNER.DYL`.
- Single-step, trace, register and memory display.
Build your own code for source-level debugging with:
```
#pragma debug(vid=>full) /* in the .pr / source */
```
and produce the `.sym`/`.dbd` symbol files with EMAKE.
### Set-up
1. Connect the PC to the Workabout with a serial cable (PC serial ↔ Workabout
RS-232 port — note this is a *different* port from the barcode `TTY:D`).
2. Start the debugger on the PC and connect to the remote (Workabout) target
(Local/Remote CPU menu → Connect to Remote).
3. Have the Workabout ready to run either the ROM `DEMMAN.APP`/`SCANAPP` (for
Procedure A) or your test harness (Procedure B).
## Procedure A — trace the working ROM app (recommended first)
Goal: watch `DEMMAN`/`SCANAPP` drive the real scanner and record the ordinals.
1. On the Workabout, start `DEMMAN` and enter its Scanner (Barcode) demo.
2. From the debugger, attach to that process and set breakpoints on the OLIB
dispatch path so you catch the object creation and messages:
- `LIBMANAGER` (`INT 0x84`) — sub-functions `NMLIBCREATE` (`AH=5`),
`NMLIBCREATEBYHANDLE` (`AH=6`): captures the category/class and the object
handle.
- `MESSMANAGER` (`INT 0x83`) — `NMMESSSEND` and the send/receive variants:
captures each message ordinal and its argument pointer.
- Optionally the `SCANNER.DYL` method handlers we identified (init dispatch,
trigger) as cross-checks.
3. For each captured call record: `AH`, the category/class in registers, the
message **ordinal**, and the pointed-to **argument struct** (dump memory at
the pointer). For the read, note where the decoded bytes land and the
returned length.
4. Trigger a real scan and record the read message and its result buffer.
Result: the exact category, class, ordinals, param structs, and sequence.
## Procedure B — iterate a C test harness
Once Procedure A gives the ordinals (or to trial them), build a small OO client:
```c
#include <plib.h>
#include <p_object.h>
/* Fill these from Procedure A (placeholders until captured): */
#define SCAN_CATEGORY /* category id/handle for "oscanner" via p_getcat/p_getlibh */
#define C_SCANNER /* the scanner class */
#define O_SCAN_INIT /* init message ordinal */
#define O_SCAN_PARAMS /* set-parameters ordinal */
#define O_SCAN_READ /* read-a-barcode ordinal */
GLDEF_C INT main(VOID)
{
VOID *lib, *scanner;
/* result/param structs per Procedure A */
lib = p_getlibh(SCAN_CATEGORY);
scanner = p_newsend(SCAN_CATEGORY, C_SCANNER, O_SCAN_INIT, /*&initargs*/ 0);
/* configure Symbol2 + the default 11-byte param block:
04 3f 01 15 06 04 1e 80 0d 0a 06 (CR/LF postamble) */
p_send3(scanner, O_SCAN_PARAMS, /*&params*/ 0);
for (;;) {
/* send the read message; result is the decoded barcode, CR/LF-terminated */
p_send3(scanner, O_SCAN_READ, /*&result*/ 0);
/* display result ... */
}
}
```
Build with `#pragma debug(vid=>full)`, run under the debugger, single-step the
sends, and inspect return values / the result buffer. Adjust ordinals/structs
until a real scan returns the UPC.
## Turning captures into the shipped reader
- Replace `code/inventory/bcode.c` with the OO client: `bcodeOpen` does
`p_getlibh` + `p_newsend`(init) + params; `bcodeRead` does `p_send`(read) and
returns the decoded string (strip the CR/LF postamble; strip any preamble byte
`0x80` if present).
- `upc.c` (UPC-A check-digit validation) is unchanged and already correct.
- Cross-check the captured ordinals against the decompiled handlers in
`SCANNER-API.md` (`FUN_7ae3` init dispatch — decoder type at `[channel+10]`,
case 4 = Symbol; `FUN_7aa4` trigger = the `6`/`7` ops).
## Validation
- A valid UPC-A scan should return 12 digits (plus any pre/postamble), and
`upcIsValid` should pass. The default postamble is CR LF (`0x0d 0x0a`).
- Confirm repeated scans and clean shutdown (`p_send` a destroy/close message,
then `p_close` any channel).
## Reproduce the RE environment (for further static/dynamic work)
See `../mx-re/toolchain-and-plan.md`: MAME `psionwamx` (live trace/dasm),
radare2 (static), and Ghidra headless (decompilation of the DYLs). These remain
useful for reading further `SCANNER.DYL` methods, but the ordinals themselves
come from the on-device debugger as above.
+263
View File
@@ -0,0 +1,263 @@
# Workabout MX barcode scanner — API notes (reverse-engineered)
There is no official SDK documentation for the Workabout MX integral laser scanner.
The Psion SIBO C SDK and I/O Devices Reference predate the MX and describe only the
older *external* barcode modules (wands / wand-emulation), not the integral laser.
These notes are reverse-engineered from device behaviour and from the Workabout MX
ROM `w2mx_v7.20f_eng.bin` (strings), so treat unconfirmed items as such.
## Hardware
- Integral **laser** scanner, Symbol engine. The demo (`DEMMAN.APP`) reports
`Type: Laser 1223` (some units `1222`).
- It is a **decoded** scanner: it decodes in hardware and lights a green good-read
LED. It does **not** emit an undecoded (HHLC) signal.
- Trigger: the keyboard **scan key**, which the Window Server reports as key
code **368**. The key alone does not fire the laser from an arbitrary app; the
scanner software arms/reads the engine.
## Device driver and ports
`LLDEV` on the device lists a logical driver **`wl2`** (`units=1`). The scanner is
reached through this driver. The demo's "Select Scanner" screen lets you pick both
a **decoder type** and a **port** (device driver + unit letter):
- Port device drivers offered: **`TTY`**, **`WLS`**, **`WL2`**.
The MX integral laser uses **`WL2`**.
- Units seen in the ROM: **`WL2:A`** and **`WL2:D`**.
Decoder types (from the demo, "chosen from those used in standard Workabout
products"):
| Decoder | Meaning |
| --- | --- |
| `Generic` | External non-configurable reader |
| `Symbol1` | Workabout standard scanner |
| `Datalogic` | Workabout CCD |
| `HP` | Workabout Wand |
| **`Symbol2`** | **Workabout MX scanner (the integral laser)** |
## Opening the device
```c
VOID *h;
INT err = p_open(&h, "WL2:D", (UINT)-1);
```
Observed results:
| Call | Result | Meaning |
| --- | --- | --- |
| `p_open("WL2:D", -1)` | `0` | **opens** — this is the integral-laser unit |
| `p_open("WL2:A", -1)` | `-9` (`E_GEN_INUSE`) | in use — held by the resident scanner software |
| `p_open("WL2:B" / ":C")` | (untested) | — |
`WL2` has a single unit, so only one process may hold it. `WL2:A` is permanently
`-9` even with the demo closed, indicating a resident holder; `WL2:D` is the unit
an application opens.
## The 11-byte parameter block
The scanner is configured by an **eleven-byte parameter block**. The application
interprets these bytes and converts them into commands for the selected decoder
(so the same block means different wire commands for `Symbol2` vs `HP`, etc.):
| Byte | Parameter |
| --- | --- |
| Param0 | Decode security |
| Param1 | Code type (symbology select) |
| Param2 | Decode options A |
| Param3 | Decode options B |
| Param4 | General parameters |
| Param5 | ITF length 1 |
| Param6 | ITF length 2 |
| Param7 | Preamble |
| Param8 | Postamble byte 1 |
| Param9 | Postamble byte 2 |
| Param10 | General decode options |
The preamble/postamble bytes mean decoded output may carry configurable leading /
trailing characters — parsing code must account for them.
## Access model
Applications do not drive `WL2` byte-by-byte themselves; they use the ROM library
**`SCANNER.DYL`** (used by `SCANAPP.APP` and `DEMMAN.APP`). That library opens the
port, converts the 11-byte block to decoder commands, configures and enables the
engine, and returns decoded scans.
## Paths that do NOT work for the integral laser
Recorded so they are not retried:
- **`bar*.ldd` decoders (`BAREAN`, `BARC39`, …) + `BAR:` device.** These are
software decoders for the *external* wand modules. Loading `BAREAN.LDD` and
opening `BAR:A`/`BAR:B` returns `-41` (`E_FILE_DEVICE`, "no interface found in
slot") — the integral laser is not a `BAR:` expansion interface. `BAR:D`/`BAR:E`
return `-38` (`E_FILE_NAME`, invalid unit).
- **`TTY:D` serial reads.** Opening `TTY:D` powers the laser (it fires briefly),
but no decoded bytes ever arrive on the serial channel — not with default
config, not after `P_FSET` to 9600/8/1, not with `P_OBEY_DSR` cleared, not with
the intelligent-reader escape commands (`<Esc>-y1J` / `<Esc>-y1K`), not reading
one byte at a time. The integral laser's decoded data is not on `TTY:D`.
## Initialisation sequence (reverse-engineered from the ROM)
Opening `WL2:D` is not enough — the scanner must be configured and enabled first.
The sequence was recovered by disassembling `SCANNER.DYL` in the v7.20f ROM
(around file offset `0xD7AA4`). It uses the SIBO I/O executive **`int 0xCF`**,
whose convention here is: **`CL` = I/O function code, `BX` = channel handle,
`DX` = argument (by value)**, result in `AX`. From C this is the `p_iow(chan,
func, ...)` layer.
Function codes observed on the `WL2` channel (from `p_file.h`, plus WL2-specific
ones above the standard range):
| CL | Meaning |
| --- | --- |
| 6 | (`P_FDETACH` slot) used as a scanner enable/control op |
| 7 | (`P_FSET`) used as a scanner enable/control op |
| 8 | `P_FSENSE` |
| 9 | `P_FFLUSH` |
| 0x0C (12) | **WL2-specific: write a config/command byte** (byte passed in `DX`) |
The driver keeps a per-channel structure; the param block sits at **channel+4**,
with the **decoder type at offset +6** (dispatch values 1..4) and the **11-byte
parameter block at +8..+0x12**. Configuration is pushed to the engine as a series
of `cl=0x0C` writes (one command byte per call in `DX`), then enable ops
(`cl=6`, `cl=7`).
### Default Symbol2 parameter block (from the ROM)
The routine at `0xD7AB3` fills the 11-byte block with these defaults for the
Workabout MX (Symbol2) scanner:
| Byte | Param | Value |
| --- | --- | --- |
| +8 | Param0 Decode security | `0x04` |
| +9 | Param1 Code type | `0x3F` (all symbologies) |
| +0xA| Param2 Decode options A | `0x01` |
| +0xB| Param3 Decode options B | `0x15` |
| +0xC| Param4 General params | `0x06` |
| +0xD| Param5 ITF length 1 | `0x04` |
| +0xE| Param6 ITF length 2 | `0x1E` |
| +0xF| Param7 Preamble | `0x80` |
| +0x10| Param8 Postamble 1 | `0x0D` (CR) |
| +0x11| Param9 Postamble 2 | `0x0A` (LF) |
| +0x12| Param10 General decode | `0x06` |
So **decoded output is terminated by CR LF** (`0x0D 0x0A`), and Code type `0x3F`
enables all symbologies (UPC included). A reader should assemble bytes until
CR/LF and strip the preamble/postamble.
### Confirmed on-device
- `p_open(&h, "WL2:D", -1)` returns `0`.
- Issuing control ops **6 then 7** on the channel (`p_iow(h, 6)`, `p_iow(h, 7)`)
**triggers a scan**: the laser fires and decodes (green good-read LED). This is
reproducible. The trigger is one-shot — it must be re-issued for each scan.
### NOT yet solved: retrieving the decoded data
After a confirmed good read, the decoded barcode could **not** be retrieved from
the `WL2:D` channel by any tried method:
- `p_iow(h, P_FREAD, buf)` (count-in-`buf[0]` style) — no data.
- `p_read(h, buf, len)` (synchronous, with length) — no data.
- `p_ioc(h, P_FREAD, &stat, buf, &len)` + trigger + `p_iowait()` (async) — no data.
The retrieval logic in `SCANNER.DYL`/`SCANAPP` fetches the decoded data on a
**separate handle** (a driver global, e.g. `[0x13b5]`) obtained through
**OS-service stubs** of the form `mov ah,N; int 0x84/0x85/0x86/0x87; ret` — not a
plain `P_FREAD` on the `WL2:D` control channel. This resolves an earlier puzzle:
the SIBO executive uses **different register conventions per interrupt vector**
the `int 0x84` service family passes the function in `AH`, whereas the `int 0xCF`
I/O executive uses `CL`.
`SCANNER.DYL` is an **object-oriented ROM library (a DYL)**. This strongly
suggests the scanner is driven by **creating a scanner object and sending it
messages** (the OLIB/OO model), not by raw device I/O on `WL2:D`. That would
explain why every raw `WL2:D` read returned no data — wrong paradigm.
**Confirmed working on device:** `p_open("WL2:D")` + control ops `6` then `7`
trigger the laser and a good decode (green LED). Only *retrieving* the decoded
bytes is unsolved.
### Decompilation (Ghidra) — the read path
`SCANNER.DYL`/`SCANAPP` decompile cleanly in Ghidra (16-bit real-mode x86). The
app-level read routine (`FUN_858d`) is:
```c
obj->flags |= 8;
obj->[0x190] = 0;
FUN_8ce8(); FUN_8cd3(); FUN_8cd3(); // pre-read setup
obj->[0x194] = FUN_8d8e(); // acquire a read handle (int 0x84)
obj->[0x192] = FUN_8c8b(0,2,1,obj); // set up the read buffer (int 0x84; int 0xd2)
swi(0xcf)(); swi(0xcf)(); // config / enable ops on the channel
if (obj->[0x196] == 0)
obj->[0x196] = swi(0xcf)(); // P_FREAD -> decoded result stored at +0x196
else
swi(0xcf)(); // P_FCANCEL
```
The decoder-side helpers also decompile clearly: `FUN_7ae3` is the per-decoder
init (dispatch on `[channel+10]`; case 4 = the Symbol path, which calls
`FUN_6dc4` to build the `0xff 0xee …` command frame and writes the 11-byte param
block); `FUN_7aa4` is the trigger (two `int 0xCF` ops = the `6`/`7` we use).
### The scanner is an OO library object (paradigm correction)
The System Services vector table names the services: **`int 0x84` = `LIBMANAGER`**
(the Library Manager) and **`int 0x83` = `MESSMANAGER`** (object messaging).
`LIBMANAGER` sub-functions (in `AH`) are `NMLIBLOAD=0, UNLOAD=1, LINK, FIND,
HANDLE, CREATE(=5), CREATEBYHANDLE(=6), …`. So the read's `FUN_8d8e`/`FUN_8c8b`
call **`NMLIBCREATE` / `NMLIBCREATEBYHANDLE`** — they *create a scanner library
object*.
**Therefore the scanner is driven as an object-oriented library object, not by
device I/O.** `WL2:D` is what `SCANNER.DYL` opens and reads *internally*; an
application never `p_open`/`p_read`s it. Every earlier raw-device attempt was the
wrong paradigm — which is why they returned nothing.
The correct C approach uses the standard OO API (the same one the SDK's
`growbar.c` demo uses):
- `p_getlibh(category)` — get the library handle for a category.
- `p_newsend` / `f_newsend(category, class, O_..._INIT, &args)` — create the
scanner object and send its init message.
- `p_send2` / `p_send3(obj, O_...)` — send it messages (configure, trigger, read).
These C wrappers sit over `LIBMANAGER` (create/handle) and `MESSMANAGER` (send).
**Next RE target (well-defined):** extract from `SCANNER.DYL` its **category id**
(candidate token in ROM: **`oscanner`**), **class**, and the **message ordinals**
(`O_...`) for init / set-params / trigger / read, plus their parameter structs.
OLIB assigns ordinals dynamically from the whole class hierarchy (base classes in
`olib`/`hwim`), so they resolve at runtime rather than in a static dump — capture
them with the **SIBO Debugger**, which supports remote debugging (PC↔device over
serial) and **breakpoints inside dynamic libraries**. With those, a C client can
create the scanner object and message it. Validation requires the physical device
(MAME cannot inject a scan).
**See [`CONTINUATION.md`](CONTINUATION.md)** for the full on-device debugging
procedure (capture the category/ordinals/params via `LIBMANAGER`/`MESSMANAGER`
breakpoints), a C test-harness template, and how to turn the captures into the
shipped reader.
To close it, one of:
1. The **Workabout MX C SDK** `SCANNER.DYL` header / OO category definition (what
`SCANAPP` was built against) — the clean answer, and likely the only practical
one, because the interface is object-oriented.
2. A deep RE effort under MAME (`psionwamx`): automate the UI to launch `SCANAPP`,
breakpoint the read routine, and trace it. Caveats: MAME cannot inject a real
barcode (no laser input), so only the read *setup* is observable in emulation;
the reconstructed sequence must still be validated on the physical device.
## Error codes seen (from `epocdefs.h`)
| Value | Name | Meaning |
| --- | --- | --- |
| `-9` | `E_GEN_INUSE` | device already open / in use |
| `-32` | `E_FILE_EXIST` | (LDD) already loaded |
| `-38` | `E_FILE_NAME` | invalid device name / unit |
| `-41` | `E_FILE_DEVICE` | device / interface not present |
+35 -42
View File
@@ -1,66 +1,59 @@
/*
* scan.c - Phase 1 barcode diagnostic for the Workabout MX integral laser.
* scan.c - Phase 1: WL2:D scanner, asynchronous read (reverse-engineered).
*
* The EAN/UPC decoder LDD (BAREAN.LDD) is loaded, then the decoder device is
* opened. The exact device-name form and open mode are not documented in the
* available manuals (they live in the I/O Devices Reference), so this build
* tries several candidates in one run and reports which one opens - most
* likely "BAR:D" (the decoder over port D, like TTY:D), since bare "BAR:"
* returns E_FILE_NAME (-38). Once open, it dumps every byte of each read so
* the decoded UPC format can be seen.
*
* Blocking I/O; exit via the System screen.
* Confirmed: opening WL2:D and issuing control ops 6/7 triggers a scan (laser
* fires, green LED). Neither synchronous read form returned data, and SCANAPP
* in the ROM uses an async pattern (post read -> P_FREAD -> P_FCANCEL). So this
* posts an asynchronous read (p_ioc), triggers the scan (ops 6/7), then waits
* for completion (p_iowait). Decoded output is CR/LF-terminated.
*/
#include <plib.h>
#include "bcode.h"
#define NCAND 5
#define WL2_TRIG1 6
#define WL2_TRIG2 7
GLDEF_C INT main(VOID)
{
VOID *chan;
UBYTE buf[64];
INT loadErr, r, n, i, opened;
UINT k;
UBYTE buf[256];
WORD stat;
UWORD len;
INT err, i;
static TEXT *names[NCAND] = { "BAR:D", "BAR:D", "BAR:", "BAR:E", "BAR:A" };
static UINT modes[NCAND] = { (UINT)-1, 0, 0, (UINT)-1, (UINT)-1 };
loadErr = bcodeLoad();
p_printf("load %s.LDD: %d\n\n", BCODE_LDD, loadErr);
opened = 0;
for (k = 0; k < NCAND; k++) {
r = p_open(&chan, names[k], modes[k]);
p_printf("open \"%s\" mode %u: %d\n", names[k], modes[k], r);
if (r >= 0) {
opened = 1;
break;
}
}
if (opened == 0) {
p_printf("\nNothing opened. Press a key to exit.\n");
err = p_open(&chan, "WL2:D", (UINT)-1);
p_printf("open WL2:D: %d\n", err);
if (err < 0) {
p_printf("Press a key to exit.\n");
p_getch();
return 1;
}
p_printf("\nOpened \"%s\". Scan a barcode. System screen to exit.\n\n",
names[k]);
p_printf("Present a barcode under the laser. Async reads:\n\n");
for (;;) {
n = p_read(chan, buf, sizeof(buf));
if (n < 0) {
p_printf("read err %d\n", n);
len = sizeof(buf);
stat = E_FILE_PENDING;
p_ioc(chan, P_FREAD, &stat, buf, &len); /* post async read */
p_iow(chan, WL2_TRIG1); /* trigger the scan */
p_iow(chan, WL2_TRIG2);
p_iowait(); /* wait for a completion */
if (stat == E_FILE_PENDING) { /* our read didn't finish */
p_iow(chan, P_FCANCEL);
p_waitstat(&stat);
continue;
}
if (n == 0)
if (stat < 0) {
p_printf("stat %d\n", stat);
continue;
p_printf("n=%d:", n);
for (i = 0; i < n; i++)
}
p_printf("len=%d:", len);
for (i = 0; i < 40 && i < 256; i++)
p_printf(" %d", buf[i]);
p_printf(" \"");
for (i = 0; i < n; i++)
for (i = 0; i < 40 && i < 256; i++)
p_putch(buf[i] >= ' ' ? buf[i] : '.');
p_printf("\"\n");
}
+24890
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+161
View File
@@ -0,0 +1,161 @@
CHAPTER 11
HARDWARE REFERENCE
Note: This document is a beta version and may be subject to change.
About this chapter
This chapter presents the details of some of the new hardware present in the Psion Series 3c and Siena
machines.
The 15-way SIBO/RS232 connector
Four distinct types of signal are brought in/out via this connector:
e RS232 level serial data and handshaking lines
e Power in to provide operating power.
e A SIBO serial channel at 3V logic levels, to provide an interface to ASIC4/5 based peripherals
e 5V power out to power non self-powered peripherals.
Not all of these signals are relevant to both machines; the split being:
The board mounted connector is a Honda type with custom polarisation. The diagram below shows the
pinout of this connector:
11-1
SIENA/SERIES 3C UPGRADE
The pins on the connector have the following functions:
Siena Vin at +6Vd.c., 10% —
Series 3c 5V Power out, 30mA maximum.
RTS (RS232)
DTR (RS2372)
TXD (RS232)
DSR (RS232)
DCD (RS232)
CTS (RS232)
RXD (RS2372)
0 mn nNn nn fF W WN
SDOE - SIBO data direction control.
o
EXTSTAT - active low SIBO peripheral detect line.
ee
ee
EXON - SIBO external turn-on
=
N
INT - SIBO peripheral interrupt
Ww
SD - SIBO serial data
_
Ts
SCK - SIBO data clock
—-
—s
—>
a
+
ad
—_—-
=
Nn
GND - Signal and Power ground
GND - Shield frame ground
Key: Out of machine _,
Into machine —
Ground —
e The SIBO lines on the Series 3c are not intended to be used for peripherals. They are present
on the unit for test purposes only.
e The SIBO lines on the Siena are intended for use with the external SSD drive only, and are
filtered to match that peripheral.
e It should be particularly noted that the SIBO lines on pins 9-14 on both machines are at 3V logic
levels, as opposed to the 5V levels present on the Series 3a, and are unbuffered; that is, there is no
SIBO data direction control imposed on the lines, as is present on the Series 3a external SIBO bus.
e The contacts for pins | and 15 on the socket protrude slightly and make contact before the
remaining pins on the connector.
e The only difference between the Series 3c and the Siena pin-outs is the function of pin 1. Applying
6V to this pin on the Series 3c will have no detrimental effect. The 5V output on the Series 3c is
the normal VCC5 output provided on the SIBO expansion port of an Series 3a, less one Schottky
diode drop. The VCCEXT output on Series 3c gets turned on two EPOC ticks after EXTSTAT
goes low.
e On the Siena, EXTSTAT directly controls the DNMI line, whilst on the Series 3c, it is polled on
PBO6.
e On the Series 3c, the DSR line performs an external wake-up of the machine through an internal
connection to the EXON line. This does not happen on the Siena.
11-2
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
CHAPTER 7
PROGRAMMING IN HWIF
This document is a beta version and may be subject to change.
This chapter describes the changes and additions that have been made to the HWIF library as a result of the
introduction of the Siena and Series 3c machines into the SIBO range.
Grey lines in menus
The introduction of small fonts for dialogs on the Workabout and Siena resulted in a change to the data
structures in the property of the GATE class. This caused an inconsistency in the Hwif library between the
Series 3a and later machines in the way that it stores the positions of grey lines in menus. For reasons of
future consistency, the change has been incorporated in all SIBO machines with ROM version numbers of
Ox356F and above. It is therefore present in Workabout, Siena and Series 3c machines, although Series 3c
dialogs do not need to make use of the small font.
A suitably written Hwif application that makes no use of grey lines in menus will run on any SIBO
machine, regardless of the version of the Hwif library that was used to build it. However, any Hwif
application that uses grey lines in its menus and was built with the version of Hwif supplied with version
2.10 of the SIBO C SDK will not run on Series 3a machines. Such applications that were built with earlier
versions of Hwif will run on the Series 3a, but not on the Workabout, Siena or Series 3c.
All Hwif programs that are linked with the modified Hwif library that is described below can run on the
Series 3, Series 3a, Series 3c, Workabout and Siena - provided, of course, that their displays are tailored to
the various screen sizes and graphics capabilities of these machines.
Hwif library modification
The Building the Hwif library section in the Advanced use of Hwif chapter of the Programming in Hwif
manual explains how to build a customised version of the Hwif library, using the Hwif source files
provided with the SIBO C SDK. Before making the modification that is described in this section, you
should verify that you can build the existing Hwif library.
The required modification is to the hMenuRun function, which can be found in the hhwif-c source file. The
published source for this function is:
GLDEF_C INT hMenuRun ()
/*
Display and use the menu bar
*/
{
if (_GreyLines)
p_bcpy (&DatGate->gate.g.ulines[0], GreyLines,16*sizeof (UWORD) ) ;
return (p_send3 (DatGate,O GT MENU RUN, MenuPositions) );
}
This needs to be modified as shown below, to take into account the changed data structure that was
introduced with ROM version 3.56F:
SIENA/SERIES 3C UPGRADE
GLDEF_C INT hMenuRun ()
/*
Display and use the menu bar
af
{
if (_GreyLines)
{
p_bcpy( (BYTE *) &DatGate->gate.g.ulines [0]
-((p_version()>0x355f) ? 0 : sizeof (DLG _X CONSTANTS) ),
_GreyLines,16*sizeof (UWORD) );
}
return (p_send3(DatGate,O GT MENU RUN, MenuPositions) );
}
Once the source file has been modified in this way, you should rebuild the Hwif library and use the rebuilt
version when developing Hwif applications for any target SIBO machine.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+17132
View File
File diff suppressed because it is too large Load Diff
+14412
View File
File diff suppressed because it is too large Load Diff
+215
View File
@@ -0,0 +1,215 @@
CHAPTER 5
HWIM REFERENCE
This document is a beta version and may be subject to change.
This manual describes the changes and additions that have been made to the HWIM library as a result of
the introduction of the Siena and Series 3c machines into the SIBO range.
Siena and Series 3c
The FILELIST class
Very long pathnames are now displayed on the file selector with a leading ellipsis followed by the trailing
characters of the pathname.
The HELPLIST class
HELPLIST now folds case when sorting help list entries. This is important if you are writing Help screens
and lists for foreign language variants of your software.
The DLGBOX class
All dialog items now have the pR_wIN_IS_DLCTRL flag set. The hSetLandlordFlags () method does nothing
if PR_WIN_IS_DLCTR1L is not set. With the exception of acurst, all lodger window classes which assumed
that they were components of a DLGBox subclass now only make this assumption if the above flag is set.
There is a new DLGBox flag, DLGBOxX_SMALL_FONT, matching the value of DLGBOx_FROM_HWIF, but never set
from HWIF dialogs. (This flag is cleared from non-HWIF dialogs before the dialog is actually run). The
purpose of the flag is to allow the Workabout and Siena small font feature to be set from resource files. The
flag is harmless for Series 3, Series 3a and Series 3c dialogs.
Infrared communications
System resources
A SYS_IR_POWER_LEVEL resource has been added, giving the text “High” and “Low” for Infrared power
levels.
A SYS_PRINTER_IR resource has been added, giving the text “Infrared” as a printer type.
Keypresses
Normally, IR-related keys are captured by the built-in IR data transfer application. If the user disables IR,
by selecting another mode of communication, these keypresses are absorbed by the ws process key
method of any applications instance of wsERv. As a general service to the user, the method also displays an
“Infrared not enabled” information message. See also the descriptions of the changes to the wsERv class for
the Siena and the Series 3c, later in this chapter.
Data transfer and the Link Paste mechanism
The IR data transfer application on the Siena and Series 3c uses the Link Paste mechanism. The data to be
transferred is extracted from the selected region of the active edit window in whatever application last
declared itself to be the Link Paste server.
An exception to this rule occurs when the currently selected data is a filename in any of the System
Screens file lists. In this case the whole of that file is transferred.
SIENA/SERIES 3C UPGRADE
Siena
The Sienas 240x160 screen is half the width of the Series 3a and Series 3c screens. In consequence, great
care has to be taken to ensure that lines of text do not exceed the available width. This restriction
particularly affects the amount of text that can be displayed in the menu bar and in dialog items.
Menu bars and pull-down menus
The menu bar and pull-down menu borders are thinner than those on the Series 3a , being similar to those
used for the Workabout, and the minimum spacing between menu bar items has been reduced. Despite
these changes, a typical Siena menu bar can not contain more than, say, five menu titles plus a diamond
menu, and the text is restricted to around 26 characters in total. Siena applications with many menu options
will, in general, need to make greater use of submenus than equivalent applications running on the Series
3a or Series 3c. However, the Siena screens width is unlikely to restrict individual pull-down menus,
which may contain up to nine items.
Dialogs
Dialogs are displayed in an 11 point font, with thinner borders (DLG_Box d1_init method), as on the
Workabout. Up to eleven lines of text, including the dialog title, can be displayed in a Siena dialog.
Siena dialogs can not display more than three action buttons in an AcLIst control.
Text items in a cHLIST choice list control are automatically truncated if they are too long to be displayed.
System dialogs
The Siena does not support tone dialling. In consequence, the rpraLpuc free-form tone dialling dialog is
disabled, and application software should not attempt to use it. The spranpic dialog has been modified so
that its function is simply to display resolved telephone numbers.
The content of the various Print dialogs has been rearranged.
Help screens
The title displayed does not include a preceding “Help: ”, to compensate for the smaller screen width.
The FILELIST class
An instance of FILELIST no longer displays the time and date for each file. The wn_key method has been
modified so that pressing the space bar shows the time and date for the currently selected file.
The CALWIN class
On the Siena, an instance of cALWIN can only display the data for a single month.
The WSERV class
The ws _process_key method performs the following additional actions:
e if the keypress is that of the decimal separator key in the numeric keypad - with a keycode of
W_KEY CALC DECIMAL, 0x156 - it is converted to the current decimal separator , as determined by a
call to p_getctd
e if the keypress is one of W_KEY_IR_BRING, 0x140 Of W_KEY_IR_SEND, 0x141 (which would normally
be captured by the infrared file/record transfer software) display an information message to the
effect that Infrared is disabled
e if the keypress is W_FUNC_MODIFIER with w_KEY MENU (FN and MENU keys pressed together) the
Status Window in the System Screen is switched on or off, depending on its current state. Note
that CTRL + MENU also toggles the Status Window on the Siena.
Resource files
Several resources have been changed for the Siena to either save screen space or take account of the
different keyboard layout:
e the syS MTYPE PROTECTED resource uses the padlock symbol instead of “protected”
e SYS PREVIEW ACC is changed so that PSION + ? exits print preview (instead of PSION + * as on the
Series 3a and Series 3c)
e the degree symbol has been added to the resource string syS_SPECIAL CHARACTERS
5 HWIM REFERENCE
Series 3c
In general, the programming interface to HWIM (and XADD) classes is identical with that of the Series 3a.
Menus and dialogs
Menu and dialog borders have been restyled, but the basic geometry is unchanged. Dialogs have thinner
borders (DLG_Box dl_init method), There is no difference in the programming interface.
System dialogs
The sETPoRTDLG dialog class offers a selection of Baud rates up to a maximum of 57600, compared with
the maximum of 19200 on the Series 3a.
The wn_key methods of the FNEDIT and FNSELWN classes have been modified so that SHIFT + TAB (instead of
PSION + TAB) displays the current path in an information message (PSION + TAB is now used to instigate IR
file or record transfer).
The dial out code length in the Dial settings dialog (defined in prAL_ENvar, dialdigs.cl) has been increased
to 16 characters.
The WSERV class
The ws process key method performs the following additional action:
e if the keypress is W_KEY_IR_LINK, 0x142 (which would normally be captured by the infrared
file/record transfer software) display an information message to the effect that Infrared is disabled
e the class has been modified to take account of future extension to the resource string
SYS_SPECIAL CHARACTERS.
+37082
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
# Workabout MX — reverse-engineering toolchain & documentation plan
Goal: reverse-engineer the Workabout MX ROM (`w2mx_v7.20f_eng.bin`, v7.20f) and
produce complete programming documentation for the device, since none exists for
the MX specifically.
## Toolchain (reproducible, headless)
ROM identifies as MAME machine **`psionwamx`** ("Workabout mx"), CPU NEC V30MX
(`psion_asic9mx`), with `psion_asic5` (barcode/UART) and the SIBO expansion slots.
Setup:
```
mkdir -p roms/psionwamx && cp w2mx_v7.20f_eng.bin roms/psionwamx/w2mx_v7.20f.bin
mame psionwamx -rompath roms -verifyroms # -> "romset psionwamx is good"
```
**Dynamic RE (MAME debugger, headless via xvfb):**
```
xvfb-run -a mame psionwamx -rompath roms -debug \
-debugscript CMDS.txt -sound none -seconds_to_run N
```
Useful `CMDS.txt` debugger commands:
- `trace FILE` then `go` — log every executed instruction (correct bank mapping).
- `dasm FILE,ADDR,LEN` — disassemble a region as currently mapped.
- `dump FILE,ADDR,LEN` — hex dump memory.
- `bpset ADDR,1,{commands}` — breakpoint with actions (e.g. dump then continue).
- `wpset ADDR,LEN,rw` — watchpoint (catch who reads/writes a driver global).
This resolves the segment/relocation mapping that flat static disassembly of the
ROM could not (e.g. `SCANNER.DYL` / `SCANAPP` code).
**Decompilation (Ghidra headless):** extract the DYL region to a slice and load
as 16-bit real-mode x86:
```
ghidra-analyzeHeadless PROJ NAME -import slice.bin -processor "x86:LE:16:Real Mode" \
-scriptPath SCRIPTS -postScript decomp.java -deleteProject
```
`SCANNER.DYL`/`SCANAPP` decompile to readable pseudo-C this way (Ghidra 12 needs
a Java GhidraScript, not Python). Caveat: TopSpeed C uses the register-based
`jpi` calling convention, so argument recovery may need convention hints, but
control flow, struct access and the `int` service calls read clearly.
**Static RE:** `radare2 -e asm.arch=x86 -e asm.bits=16 w2mx_v7.20f_eng.bin`
(good for strings, byte-pattern search, and clean fixed-mapped regions).
## What "complete documentation" covers
1. **Hardware** (largely in the HDK, to be confirmed/extended for the MX):
memory map & banking, ASIC1/2/4/5/9MX register maps, I/O ports, interrupts.
2. **Boot & OS**: reset path, hardware init sequence, the `int 0xCF` OS/executive
call and its function table, the `int 0xD9`/etc. service vectors.
3. **I/O & drivers**: the device model, `p_open`/`p_iow` function codes per driver,
and each driver: `TTY`, `WL2` (scanner), `MCR`, `PAR`, `SND`, IR, etc.
4. **Scanner** (immediate priority, partly done — see SCANNER-API.md): `WL2:D`,
Symbol2 decoder, the 11-byte param block, trigger (ops 6/7), and the
still-open **data-retrieval** path — to be nailed by tracing `SCANAPP` live.
5. **System services / libraries**: file system (DBF), window server, HWIM/FORM.
## RE order
1. Nail the scanner data-retrieval by running `SCANAPP`/`DEMMAN` under MAME,
breakpointing the `WL2` code, and tracing how the decoded bytes are delivered.
2. Map the `int 0xCF` OS call table (breakpoint the vector, log CL/BX/DX per call).
3. Document the boot + ASIC init (already partly traced).
4. Work outward to the drivers and system services.
Findings land in `docs/` as they are confirmed (e.g. `SCANNER-API.md`), each
marked confirmed-on-emulator / confirmed-on-device / inferred.
## Notes
- `int 0xCF` convention (from traced code): `CL` = I/O function code, `BX` =
channel handle, `DX` = argument; result in `AX`. This is the `p_iow` layer.
- MAME cannot inject a real barcode into the laser, so scanner *decode* may not
reproduce in emulation; but the *code path* (open/config/enable/read structure)
traces fully, which is what we need to write correct client code.
+36
View File
@@ -0,0 +1,36 @@
# Workabout MX / SIBO programming reference
A programming reference for the Psion Workabout MX and the SIBO platform,
synthesised from the Psion SDK manuals and the Hardware Development Kit, with
reverse-engineered internals from the v7.20f ROM layered on top. Built as the
foundation for developing applications (e.g. the barcode inventory app in
`code/inventory/`).
Each document cites its source manual and marks claims as documented,
**[RE]** reverse-engineered, or inferred/uncertain.
## Sections
| # | Document | Covers |
| --- | --- | --- |
| 01 | [Building applications](01-building-apps.md) | Toolchain, TopSpeed C, SIBO types, `GLDEF_C`/etc., `.pr` project files, images/libs/LDDs |
| 02 | [System & OS](02-system.md) | Processes, memory/segments, panics + ranges, error codes, async I/O, the executive call |
| 03 | [I/O devices & drivers](03-io-devices.md) | `p_open`/`p_iow`/`p_ioc`, function codes, serial (`P_SRCHAR`), per-device drivers, loading LDDs |
| 04 | [PLIB core API](04-plib-core.md) | Strings, memory, console, conversion, math, date/time, categorised function reference |
| 05 | [File system & DBF database](05-filesystem-dbf.md) | Files, streams, directories; the full DBF record/database API |
| 06 | [User interface](06-ui.md) | Window Server model, events (`WM_*`, `WGetEvent`), the `CON:` console layer |
| 07 | [Hardware](07-hardware.md) | CPU, memory/banking, ASIC1/2/4/5/9 register maps, I/O ports, interrupts, expansion |
| 08 | [Boot & OS-call internals **[RE]**](08-re-boot-oscalls.md) | Reset/ASIC-init path, OS-service `INT` vectors, boot port activity — from the live MAME trace |
Related:
- [Barcode scanner API](../inventory/SCANNER-API.md) — the Symbol2/`WL2` laser scanner (reverse-engineered): device, trigger, decoder params, and the OO-library-object architecture.
- [Scanner continuation guide](../inventory/CONTINUATION.md) — the on-device SIBO-Debugger procedure to capture the OO message ordinals and finish a working scanner client.
- [RE toolchain & plan](../mx-re/toolchain-and-plan.md) — MAME/radare2/Ghidra RE setup and roadmap.
## Status
The manual-derived sections (0107) are broad and citation-backed. The
reverse-engineered material (08, the scanner API, and the executive-call
convention notes) is being extended by tracing the ROM under MAME
(`psionwamx`). Known open items: the full `INT`-vector → service map, and the
scanner data-retrieval path.
+573
View File
@@ -0,0 +1,573 @@
# Building Applications on the Psion SIBO / Workabout in C
A reference for developing C applications for the Psion SIBO family (HC, MC,
Series 3/3a/3c, Siena, Workabout) using the SIBO C SDK and the TopSpeed C
compiler.
Sourced from the *SIBO 'C' Software Development Kit — General Programming Manual*
(v2.30, March 1999) and the *SIBO Hardware Development Kit* (May 1995) LDD
sections. Claims are drawn only from those documents; where a point is not
stated in the source it is marked as such.
> **Workabout MX note.** The manual (SDK v2.30) names the *Workabout* and
> *Siena* but does not mention a distinct "Workabout MX" model. Everything here
> that applies to the Workabout applies to the MX at the SIBO/EPOC-16 level.
> Anything genuinely MX-specific is called out explicitly; unmarked material is
> generic SIBO and holds for the MX unless noted.
---
## 1. Architecture and programming model
A SIBO machine is "a battery-powered portable computer that is based on the SIBO
architecture ... designed to minimise the size, weight and power consumption"
(General, ch. 2). Key hardware components named by the manual:
- A power-management system that "selectively powers subsystems under software control".
- Solid State Disks (SSDs) — "fast low-power silicon-based mass storage with no moving parts".
- Asynchronous serial interface running at "Mega bit rates".
- **"An 8086 class of processor (or any compatible processor such as an 80286)"** (General, ch. 2).
- Hardware protection: "address trapping of out-of-range writes and a watch-dog timer on interrupts being disabled".
- Real-time clock, ROM-resident system software, graphics LCD, and (on some models) a touch-sensitive digitising pad.
The hardware is "primarily implemented in custom ICs called ASICs ... surface-mounted static CMOS ICs throughout."
> **CPU note / uncertainty.** The manual describes the CPU only as "an 8086
> class of processor (or any compatible processor such as an 80286)". The
> commonly cited V30-class part (NEC V30 / V30H, an 8086-compatible) is **not
> named in these source documents**; treat "V30-class" as background, not a
> manual quotation. The programming-relevant fact is what the manual does state:
> it is an 8086-class, 16-bit, real-mode-segmented CPU with no 8087.
The operating system is **EPOC** (here the 16-bit EPOC that runs on SIBO; not
the later 32-bit EPOC32). Features listed (General, ch. 2):
- Preemptive multi-tasking; MS-DOS-compatible and installable file systems.
- Asynchronous services and client-server architecture (file server, window server).
- A comprehensive I/O system with many built-in I/O devices and **dynamically loadable device drivers**.
- A **reentrant function library**; "multiple processes of the same program share a single copy of the code"; code-shared dynamic link libraries.
> The terms are distinguished in the manual: *SIBO* = the hardware
> architecture; *EPOC* = the operating system designed for it. On SIBO
> machines "the system software resides on an in-built ROM."
### Fatal errors (panics)
When the system detects a condition it believes can only be a bug, it
"terminates the process with a 'panic number' in the range 0 to 255 ... There is
no way for applications to avoid being terminated when a panic has been started."
Panic-number ranges (General, ch. 2):
| Range | Source |
|-------|--------|
| 080, and 255 | PLIB library |
| 81129 | Window Server library |
| 130160 | OLIB object library |
| 160254 | non-ROM code (e.g. the ISAM library) |
`panic 80` specifically often means a program "failed to locate SYS$8087.LDD"
(the floating-point emulator LDD) — see §5.
### Why TopSpeed C (small model)
Applications are written, compiled and linked on a PC and then run/debugged on
the SIBO machine. The compiler and linker are the **TopSpeed C** system
(Clarion Software Corporation). The SDK requires the *small code model* of
TopSpeed:
> "`#model small jpi` — The code is to be compiled in small model (code and
> data segments each restricted to 64K), with the jpi (TopSpeed C) convention
> of using registers to pass parameters to subroutines." (General, ch. 3)
You may install other TopSpeed code models, but "they will not be used by the
SIBO SDK." Small model matches the 8086-class segmented architecture: a single
64K code segment and a single 64K data segment, with `CS`/`DS`/`SS` behaving as
a "pure small model" where "`ds=es=ss`" (General, ch. 4). The `jpi` calling
convention (register parameter passing) is mandatory for SIBO builds.
Because there is no 8087, "all floating point is performed by software emulation
of the 8087" (General, ch. 4).
---
## 2. CLIB vs PLIB
Two C libraries are available:
**CLIB** — "a version of the TopSpeed C library for the EPOC operating system
... a version of the standard ANSI C library." Benefits: portability (existing C
ports easily) and less to learn. Documented in the *TopSpeed C Library
Reference*, with SIBO caveats in *Notes on CLIB*.
**PLIB** — Psion's proprietary C library, providing "only very thin shells over
functionality that is present in the ROM." Functions are prefixed `p_`.
The manual "strongly urges" developers to consider PLIB over CLIB:
- "many of the ROM-based EPOC system services are not available from CLIB (eg
asynchronous I/O, inter-process messaging, the window server graphics functions)"
- "executables are larger in CLIB and the process takes a larger data segment."
CLIB is a "much 'thicker' library"; its subsystems "typically require large
static buffers and tables." You can freely mix PLIB and CLIB calls (unless using
the object-oriented UI dynamic libraries). The Window Server library is `WLIB`
(`wlib.lib`, functions `g...`/`w...`, header `wlib.h`), automatically linked
when needed.
CLIB omissions worth knowing (Notes on CLIB): many BIOS/DOS/graphics/far-pointer
functions are not implemented. `getRealHandle(int handle)` converts a CLIB file
handle to a PLIB handle. CLIB startup auto-opens a console channel assigned to
`stdin`/`stdout`/`stderr`; you can suppress it by defining `p_xwind` and setting
the global `_winHandle`.
### Standard SIBO C types
The manual's example code uses a standard set of SIBO C types in place of raw C
types. (These are defined in the SDK headers — `plib.h`/`stddefs`; the General
manual uses them but their formal definitions live in the PLIB Reference.)
Observed usage:
| Type | Meaning (from usage in the manual) |
|---------|-------------------------------------|
| `TEXT` | character / text byte (e.g. `TEXT *pb;`, `TEXT subname[P_FNAMESIZE];`) |
| `UBYTE` | unsigned 8-bit byte |
| `WORD` | signed 16-bit word |
| `UWORD` | unsigned 16-bit word (e.g. `UWORD answer;`) |
| `INT` | signed integer (return type of `main`) |
| `UINT` | unsigned integer (e.g. `UINT lcdtype;`) |
| `VOID` | void (e.g. `main(VOID)`) |
> **Uncertainty.** The exact widths/signedness of each typedef are not spelled
> out in the General manual; they are defined in the SDK headers / PLIB
> Reference. The mappings above reflect how the manual uses them and standard
> SIBO conventions. Use the uppercase SIBO types rather than raw C types in
> SIBO code.
### Storage-class macros
The example code uses uppercase storage-class macros instead of bare C storage
classes. These make the code portable across the segmented model and the SDK's
linkage conventions.
| Macro | Used for | Manual examples |
|-------|----------|-----------------|
| `GLDEF_C` | **global definition** of code (a function you define, visible externally) | `GLDEF_C INT main(VOID)`, `GLDEF_C VOID main(VOID)` |
| `GLREF_C` | **global reference** to code / an external symbol (declared elsewhere) | `GLREF_C TEXT *DatCommandPtr;` |
| `GLDEF_D` | global definition of data | (data counterpart of `GLDEF_C`) |
| `GLREF_D` | global reference to data defined in another module | `GLREF_D TEXT *DatCommandPtr;` |
| `LOCAL_C` | file-local (`static`) function | `LOCAL_C VOID QueueKey(VOID)`, `LOCAL_C VOID CancelTimer(VOID)` |
| `LOCAL_D` | file-local (`static`) data | (data counterpart of `LOCAL_C`) |
> `_C` suffix = code/function, `_D` suffix = data; `GL...` = global (external
> linkage), `LOCAL_...` = module-private. The General manual shows `GLDEF_C`,
> `GLREF_C`, `GLREF_D`, and `LOCAL_C` directly; `GLDEF_D`/`LOCAL_D` are the data
> counterparts (formal definitions in the PLIB Reference).
### Program entry point
A PLIB program's entry point is `main`, written with the SIBO macros:
```c
#include <plib.h>
GLDEF_C INT main(VOID)
{
p_printf("Hello World");
p_getch();
return (0);
}
```
A CLIB program uses ordinary ANSI style with `<stdio.h>`:
```c
#include <stdio.h>
int main(VOID)
{
printf("Hello World");
getchar();
return (0);
}
```
(Both `hello.c` and `p_hello.c` are taken verbatim from General ch. 3.)
---
## 3. Project (`.pr`) files
The compile/link cycle is driven by TopSpeed **project files** (extension
`.pr`). The minimal CLIB project (`hello.pr`):
```
#system epoc img
#model small jpi
#compile hello
#link hello
```
The minimal PLIB project (`p_hello.pr`):
```
#system epoc img
#set epocinit=iplib
#model small jpi
#compile p_hello
#link p_hello
```
### Directives
- **`#system epoc img`** — "The end outcome of the build is a `.img` file, as
defined in the Epoc-customised part of the TopSpeed configuration (alternative
`#system`s include `dos` and `win`)." Required in every SIBO project file.
- **`#model small jpi`** — small code model + jpi register calling convention
(see §1). Required in every SIBO project file.
- **`#set epocinit=iplib`** — selects the startup object and stack size. **Must
precede `#model`.** It "specifies whether you are using the CLIB or PLIB
startup object files, and it specifies the stack size." Allowed values:
| Value | Startup | Stack |
|-------|---------|-------|
| `iclib` | CLIB | 8k (recommended for CLIB) |
| `iclib4` | CLIB | 4k |
| `iclib2` | CLIB | 2k |
| `iplib` | PLIB | 4k (recommended for PLIB) |
| `iplib8` | PLIB | 8k |
| `iplib2` | PLIB | 2k |
If unset, defaults to `iclib`. You **must** use CLIB startup if you use any
CLIB I/O functions; if you use no CLIB functions at all, "you should always
use the PLIB startup." (Non-I/O CLIB functions such as memory allocation can
be used with PLIB startup.)
- **`#compile <name>`** — compile a source module. If ambiguous, add the `.c`
extension (e.g. `#compile query.c`), because the system also treats `.a`
(assembler) and `.rc` files as candidate sources and errors if two candidates
exist.
- **`#link <name>`** — link everything. Its effect: link all `#compile`d files,
plus the startup module and standard libraries, plus anything named by
`#pragma link`, giving the executable the name in the `#link` statement. The
`#link` name need not match the `.pr` filename nor any module name.
- **`#dolink <name.lib>`** — used **instead of** `#link` to build a **library**.
It stops the system linking in a startup object and standard libraries. The
explicit `.lib` extension overrides the default `.img` implied by `#system
epoc img` (see §4).
- **`#pragma link (<file>)`** — link an extra object file or library not
searched automatically. Examples: `#pragma link (hwif.lib)` (HWIF is not
auto-searched), `#pragma link (utils.lib)` (a custom library, found along the
`ts.red` search path).
- **`#pragma debug (vid=>full)`** — insert before any `#compile` to build with
full source-level debug info (equivalent to the `/v2` command-line flag).
### Overriding image attributes
Three project variables (also settable as `/s...` on the command line):
| Variable | Meaning | Default |
|----------|---------|---------|
| `%version` | image version number | `0x100f` |
| `%priority` | initial process priority | `0x80` |
| `%heapsize` | initial and minimum heap, in **paragraphs** (`0x80` = `0x800` bytes = 2 KB) | `0x80` |
Set via a line like `#set heapsize=0x180` or `tsc /m app /sheapsize=0x180`. The
OS refuses to start an instance without `heapsize` free heap, and never shrinks
the heap below it.
### Multi-module and parameterised projects
Multi-file program (`triple.c`, `utils1.c`, `utils2.c`):
```
#system epoc img
#set epocinit=iplib
#model small jpi
#compile triple
#compile utils1
#compile utils2
#link triple
```
Assembler modules are allowed alongside C (`#compile afile` assembles
`afile.a`); assembler modules must follow the rules in the PLIB Reference.
A reusable generic project uses the `%main` macro (`unnamed.pr`):
```
#system epoc img
#set epocinit=iplib
#model small jpi
#compile %main
#link %main
```
invoked as `tsc /m unnamed.pr /smain=%1` (TopSpeed `/s` sets the macro from a
batch parameter). Conditional/parameterised builds are handled in the shipped
batch files (`cc.bat`, `make.bat`, `checkvid.bat`, `vid.bat`), which key
compilation off a `%jpivid%` env var (`v2` = full debug, `v0` = none) and pick
a per-app `.pr` if one exists, else `unnamed.pr`.
### Invoking the compiler: `tsc /m`
Build and link in one step:
```
tsc /m hello
```
"`/m` ... is that the project file is executed in 'make' mode, with files not
being recompiled or relinked needlessly." For source-level debugging add `/v2`:
```
tsc /m hello /v2
```
Other flags seen: `/fp<project>` selects the project file (e.g.
`tsc app.c /fpunnamed`); with no `/m` (and no `/l`) the project runs in
"compile" mode (compile only, always, no link). `%version`, `%priority`,
`%heapsize`, `%main` are passed as `/s<name>=<value>`.
> **`tsc` vs `tscx`.** `tsc` does not use expanded memory "and may cause
> problems, particularly when linking large applications." If linking fails,
> replace `tsc` with **`tscx`** (which uses expanded memory) in the batch files.
### Configuration files behind the build
The SIBO build config lives in two files (do **not** edit their pragmas):
- `tsprj.txt` — "compiled" with `tscfg` before use; the SDK version extends
Clarion's to add the `Epoc img` system type.
- `stdepoc.h` — "always the first include file in any source module."
Header/library search paths come from the redirection file **`ts.red`** (maps
`*.H`, `*.LIB`, `*.PR`, etc. to `.` then the `\sibosdk\...` directories).
---
## 4. Build outputs: images, libraries, device drivers
The image file is produced from an intermediary `.exe` by the Psion tool
**`emake.exe`** (automated by the project system). "Essentially, `.img` files
are to Epoc what `.exe` files are to MS-DOS." `emake` runs (per `tsprj.txt`):
```
emake -b %afl% -o%name% -s -v%version% -p%priority% -h%heapsize% -%epoctype% %name%.exe
```
The `%epoctype` variable selects the output kind:
| `%epoctype` | Output | Extension |
|-------------|--------|-----------|
| `t1` (default) | image file | `.img` |
| `t2` | logical device driver | `.ldd` |
| `t3` | physical device driver | `.pdd` |
| `t4` | dynamic library | `.dyl` |
### Images (`.img` / `.app`)
`.img` and `.app` are "strictly speaking ... no real difference"; both are
image files. By convention a `.app` has one to four embedded **add-files**:
- `.pic` — icon
- `.rsc` or `.rzc` (compressed) — resource file
- `.shd` — shell data (Series 3 only)
Add-files are embedded by `emake` when an **add-file list** (`.afl`, a text file
naming one to four files, e.g. `tele.afl`) with the matching base name exists at
build time. Renaming the result to `.app` is a convention (not automatic). A
`.dfl` file similarly embeds DYL files.
Inspect an image with `edump <name>` (shows version, code/data segment sizes,
stack, heap, priority, checksums, add-file offsets, DYL table). Re-edit an
existing image's add-files/priority/heap/version *without rebuilding* using
`eremake.exe` (e.g. to swap in a French resource file):
```
eremake -afrquery -o..\french\query.app query.app
```
### Libraries (`.lib`)
Build a static library with `#dolink` (see §3):
```
#system epoc img
#set epocinit=iplib
#model small jpi
#compile utils1
#compile utils2
#dolink utils.lib
```
`#dolink` (not `#link`) prevents startup/standard-library linking, and the
explicit `.lib` overrides the default `.img`. Consumers pull it in with
`#pragma link (utils.lib)`.
### Loadable device drivers (`.LDD` / `.PDD`)
At a high level, LDDs and PDDs are just image-type outputs of `emake`
(`%epoctype=t2``.ldd`, `t3``.pdd`). Detail from the *Hardware Development
Kit* (§9):
All SIBO hardware "is controlled by logical and physical device drivers."
Layering:
```
APPLICATION SOFTWARE
Psion C / PLIB call interface
LOGICAL DEVICE DRIVER (LDD)
PHYSICAL DEVICE DRIVER (PDD)
PHYSICAL HARDWARE
```
- A **PDD** "contains the code required for talking directly with the hardware"
— low-level, hardware-specific services.
- An **LDD** "performs the logical processing that transforms these low level
services into the high level services used by an application."
- The same LDD is often paired with a per-hardware PDD; an LDD may also talk to
hardware directly, making a separate PDD unnecessary.
> **Language note.** "Psion device drivers are written in 8086 assembler and
> follow a prescribed structure" (HDK §9). Device drivers are **not** ordinary C
> programs — the C SDK / PLIB is the *client* side (`p_loadldd()`, `p_open()`,
> `p_close()`, `p_iow()`). The `.ldd`/`.pdd` build path exists in the project
> system, but the driver *source* is assembler.
**Naming and channels.** An LDD name is three characters + colon (e.g. `TTY:`
serial). A PDD name is the owning LDD's three chars + `.` + three more + colon
(e.g. `TTY.UAR`, the ASIC5 UART driver). Open a channel via `p_open`:
```c
p_open(&pcb, "LED:", -1); /* LDD; -1 = ignore open mode */
p_open(&pcb, "TTY.UAR:", -1); /* a PDD directly (unusual) */
p_open(&pcb, "PAR:A", -1); /* channel qualifier 'A' */
```
Typically an application opens only the LDD, which opens its PDD during
initialisation. I/O requests reach the driver's **strategy vector**, which maps
to the PLIB `p_iow()` call. Installable drivers are loaded dynamically
(`DevLoadLDD` service; do not call `DevInstall` directly) without resetting the
machine, and can replace resident ROM drivers of the same name (the table is
searched from the most-recently-installed end). EPOC handles "a maximum of 32
device drivers on a Series3 machine and 48 on other machines."
**LDD structure (HDK §9).** Single code segment, no data segments (wrapped by
`CodeSeg`/`EndCodeSeg`); begins with a `LibEnt` structure (2-byte
`LDDSignature`, 8-byte zero-terminated name *without* trailing colon, 2-byte
vector count ≥ 8, then the vector table). The **eight mandatory LDD functions**,
in order:
1. `DevFuncInstall` — on installation
2. `DevFuncRemove` — on removal
3. `DevFuncHold` — temporarily disable
4. `DevFuncResume` — re-enable
5. `DevFuncReset` — application terminated without closing channel
6. `DevFuncUnits` — query number of supported units/channels
7. `DevFuncOpen` — open a channel
8. `DevFuncStrategy` — access functionality via the I/O system
All are called FAR by the OS and must return with a FAR return.
**PDD structure (HDK §9).** Also single code segment, no data segments; starts
with a `LibEnt` (`PDDSignature`). **Two mandatory functions**`DevFuncInstallPDD`
and `DevFuncRemovePDD` — with typically two more (`DevFuncOpenPDD`,
`DevFuncStrategyPDD`). PDD internal variables must live in the driver's own code
segment so hardware can be freed after a client process terminates.
---
## 5. Floating-point emulator (relevant to any build)
Because SIBO has no 8087, floating point is emulated. Under EPOC the emulator is
an LDD, `SYS$8087.LDD` (in `\sibosdk\lib\`), loaded and freed automatically —
saving ~8K per program and allowing sharing between processes. The C startup
(`r_emul.a`) looks for the LDD in the program's directory, then via the `EMS`
environment variable (case-sensitive). A program that dies with **panic 80**
before starting has "almost certainly failed to locate `SYS$8087.LDD`"; fixes:
copy the LDD next to the program, set `EMS`, or remove the floating-point
dependency. Note "floating point instructions can easily be generated
unexpectedly ... if the recommended build configuration pragmas are 'improved'"
— another reason not to touch the pragmas.
---
## 6. Resources / resource compiler
The General manual covers resources only lightly, as **add-files**: a `.rsc`
(or compressed `.rzc`) resource file can be embedded into a `.app` via the
`.afl` add-file list (§4). Multi-lingual applications isolate all text in a
resource file so a translated `.rzc` can be swapped in with `eremake` without
recompiling.
> **Not in this manual.** The resource *compiler* itself and the resource
> source (`.rss`) syntax are **not** described in the General Programming Manual.
> The manual points to the *Resource Files* chapter of the *Additional System
> Information* manual (not provided here) for that detail. Do not assume resource
> compiler behaviour beyond the add-file mechanism described above.
---
## 7. Detecting the machine at runtime
Most SIBO machines are distinguished by screen size via `p_getlcd()` /
`p_geticda()` (General ch. 7). Values include:
| Constant | Value | Display / machine |
|----------|-------|-------------------|
| `E_LCD_640_400` | 0 | 640x400 — MC 400 |
| `E_LCD_640_200_SMALL` | 1 | 640x200 — MC 200 |
| `E_LCD_160_80` | 4 | 160x80 — HC |
| `E_LCD_240_80` | 5 | 240x80 — Series 3 |
| `E_LCD_480_160` | 11 | 480x160 — Series 3a / 3c |
| **`E_LCD_240_100`** | **12** | **240x100 — Workabout** |
| `E_LCD_240_160` | 14 | 240x160 — Siena |
> **Workabout / MX.** The manual lists the Workabout as the 240x100 machine
> (`E_LCD_240_100`, value 12). It does not list a separate MX entry; an MX with
> the same screen reports the same LCD type. Series 3a vs 3c (same screen size)
> are told apart with `p_returnexpansionportinfo()` on EPOC ≥ `0x390F` — the
> pattern generalises if you must disambiguate same-screen models. Do not
> hardcode screen dimensions; query the LCD type.
---
## Quick reference
```
# Minimal PLIB app
#system epoc img # -> .img output (Epoc image)
#set epocinit=iplib # PLIB startup, 4k stack (MUST precede #model)
#model small jpi # 64K code + 64K data, register calling convention
#compile myapp
#link myapp
# Build (make mode) tsc /m myapp
# Build with source debug tsc /m myapp /v2
# Large link needs XMS tscx /m myapp
# Override heap tsc /m myapp /sheapsize=0x180
# Inspect image edump myapp
# Re-embed add-files/version eremake ...
```
| Extension | What it is |
|-----------|-----------|
| `.pr` | TopSpeed project file |
| `.c` / `.a` | C source / assembler source |
| `.img` / `.app` | EPOC image / image with embedded add-files |
| `.lib` | static library (`#dolink`) |
| `.ldd` / `.pdd` | logical / physical device driver (assembler source) |
| `.dyl` | dynamic library |
| `.rsc` / `.rzc` | resource file / compressed resource file |
| `.pic`, `.shd`, `.afl` | icon, shell data, add-file list |
| `ts.red` | header/library redirection (search paths) |
+719
View File
@@ -0,0 +1,719 @@
# Psion SIBO / EPOC — Operating-System Programming Model
Reference notes for the SIBO family (HC, MC, Series 3/3a/3c, Siena, Workabout /
Workabout MX) running the ROM-resident **EPOC** operating system, programmed
through the **PLIB** library.
Sources, cited inline as:
- **[GEN]** — *SIBO 'C' SDK, General Programming Manual*, v2.30
- **[PLIB]** — *PLIB Reference*
- **[SYS]** — *EPOC OS System Services* (assembly-level service interface)
Everything below is drawn from those manuals. Items derived from prior
reverse-engineering, rather than the manuals, are explicitly flagged
**[RE — reverse-engineered]**. Workabout-MX-specific points are flagged
**[MX]**; where the manuals do not distinguish the MX, that is noted as an
uncertainty rather than invented.
> **Terminology.** *SIBO* is the hardware architecture (8086-class CPU, ASICs,
> SSD storage). *EPOC* is the operating system that runs on it. This is the
> 16-bit SIBO-era EPOC, not the later 32-bit EPOC32/Symbian. [GEN §2]
---
## 1. The memory model
### 1.1 Small model, segments, and memory moving
EPOC programs are compiled in the 8086 **small model**: the code segment and the
data segment are each limited to **64 KB**. The restriction is deliberate — it
lets EPOC **move memory segments** (including a process's own code and data
segments) around physical RAM without any cooperation from the application, which
is essential for efficient RAM use in a multitasking system. [GEN §1; PLIB
"Introduction"]
Physical memory (address 0 to 0xFFFFF, up to 1 MB addressable; banked above
512 KB) is laid out roughly as: interrupt vectors (1 KB) → screen bitmap (small
displays) → OS data space → **allocated memory segments** → unallocated memory →
internal RAM drive (`LOC::M:`) → environment variables (≤4 KB) → screen bitmap
(large displays) → ROM (~256 KB). [PLIB §7 "Overview of system memory usage"]
All unallocated RAM is kept in a **single contiguous chunk**. Consequently
creating, deleting, or resizing one segment forces others to move. Segment
addresses and sizes are always expressed in **16-byte paragraphs**; segments
start on 16-byte boundaries. The segment allocator's maximum segment size is
**512 KB**. [PLIB §7]
**How moving is safe.** The supervisor process (`SYS$MANG`) does the moving. It
runs at higher priority than any other process (so it is not interrupted mid-move)
and its own data/code segments never move. After a move it walks each process
context and **adjusts the 8086 segment registers** (CS, DS, SS, ES) by the amount
the relevant segment moved. A register is adjusted only if it holds a value inside
a moved segment's range. [GEN §1; PLIB §7]
Two rules the application must obey so the supervisor's fix-up works:
- Never store a segment register to memory and later reload it (the memory may
move in between). In assembler, protect any unavoidable save/restore by
disabling interrupts across it (which blocks the context switch).
- Never load a non-segment value into a segment register (it might fall inside a
moved segment's range and get "adjusted"). [GEN §1]
### 1.2 Segment kinds and naming
Allocated segments are described by a **name**, a **handle**, a **size**, and an
**address**. [PLIB §7]
- **Device segments** — created when an external device is installed, deleted on
removal; normally do not resize. Placed at **lower addresses** than dynamic
segments so device drivers (e.g. serial) are not disturbed by volatile dynamic
activity. The first two are special: the data segments of `SYS$NULL` and
`SYS$MANG`, created at startup and never resized. Extensions `.LDD` / `.PDD`
denote Logical / Physical Device Driver segments.
- **Dynamic segments** — code and data segments created/deleted as processes come
and go; process data segments grow/shrink to fit heap demand. Extensions:
`.$SC` = shared primary code segment, `.DYL` = dynamic library code, `.$nn` =
process data segment (nn = process slot number).
The **segment handle** is the relative address of the segment's 16-byte
index-table entry in OS data space; that entry holds the segment address, a
**usage count**, and the name. Usage count = number of processes using the
segment; when it drops to zero the segment is (normally) deleted. The index table
has fixed capacity — typically **96 total = 32 device + 64 dynamic**. [PLIB §7]
Segment API (names follow file-name rules, ≤8 chars + optional `.ext`):
`p_sgcreate`, `p_sgdelete`, `p_sgopen`, `p_sgclose`, `p_sgcopyto`, `p_sgcopyfr`,
`p_sgsize`, `p_sgadjust`, `p_sgfind`, `p_sglock` / `p_sgunlock` (usage-count
inc/dec). Access to data in an **external segment** is less convenient than to the
process data segment, but a single external segment can grow to the 512 KB limit —
the standard way to hold data structures larger than 64 KB (e.g. a document).
[GEN §1; PLIB §7]
### 1.3 The process data segment and heap
A process data segment is at most **0xFFE0** bytes (32 bytes short of 64 KB, so a
stack underflow always trips an address trap). Layout, low → high:
1. reserved static variables (0x40 bytes; "magic statics")
2. floating-point emulator data space (0x200 bytes from offset 0x100)
3. processor stack
4. initialised static variables
5. uninitialised static variables (also zero-initialised)
6. **process heap** — at the top so it can grow by growing the data segment
The word at offset 0 is initialised to **0xDEAD**; many OS calls check it and
panic (`PanicDead0`, panic 68) if a stray NULL-pointer write has changed it. Stack
bytes are pre-filled with 0xFF (the count of 0xFF from 0x40 measures free stack,
unless the FP emulator is in use, in which case the stack floor is 0x300). [PLIB
§7 "Process data segments"; §12 "Reserved statics"]
### 1.4 Heap cell allocation
Cells are referenced **directly by address** and **do not move** to compact freed
space. Each cell is prefixed by a hidden 16-bit length word (skipped by the
address returned to you; not counted by `p_alen`). Writing past a cell's bounds
corrupts the length word (or a free-list pointer) — a "time bomb" that a later
allocator call detects and turns into a panic.
The heap is **not fixed size**: EPOC grows it (up to the 0xFFE0 limit) to satisfy
requests and shrinks it to hand memory back, but never below the **initial /
minimum heap** value stored in the image (default 2 KB). Allocation is
**first-fit** over the free list; if nothing fits, the data segment is grown by
the requested amount **plus the heap granularity** (default `E_GROWBY_DEFAULT`
2 KB; max `E_MAX_GROWBY` 16 KB, set via `p_hgran`). [PLIB §7 "The heap allocator"]
| Function | Purpose |
|---|---|
| `void *p_alloc(UINT size)` | Allocate ≥`size` bytes; returns address or **NULL** on failure. Always test for NULL. |
| `void *f_alloc(UINT size)` | As `p_alloc` but calls `p_leave(E_GEN_NOMEMORY)` instead of returning NULL. |
| `void p_free(void *pcell)` | Free a cell. No-op if `pcell` is 0 (handy in cleanup). Double-free corrupts the heap. |
| `void *p_realloc(void *pcell, UINT size)` | Resize; returns new address or NULL (original untouched). `pcell==0` ⇒ acts like `p_alloc`. |
| `void *f_realloc(...)` | As `p_realloc` but leaves on failure. |
| `void *p_adjust(void *pcell, UINT offset, INT amount)` | Open (`amount`>0) or close (`amount`<0) a gap inside a cell — insert/delete records in place. |
| `UINT p_alen(void *pcell)` | Cell length (may be a few bytes > requested). |
| `void p_hgran(UINT nparas)` | Set heap growth granularity (paragraphs). |
| `void p_allwalk(fn, fpar)` | Walk every cell (diagnostic); panics on inconsistency. |
| `void p_allchk(INT num)` | Force a heap-integrity check; `p_panic(0xFF)` if corrupt. |
| `UINT p_allspc(void **pheap)` | Potential free space + heap start address. |
**Design cautions.** *Alloc heaven* — if a multi-cell structure is built and a
later allocation fails, free the already-allocated cells or they leak. *Internal
fragmentation* — avoid churning tiny transient cells (use the stack), and
"granularise" many-cell variable-length structures. Do not let an unbounded
heap structure grow until allocation fails at the 0xFFE0 limit: leave enough heap
for the user's expected actions (e.g. saving to file). Note EPOC does **not**
distinguish "no system RAM" from "hit the 64 KB data-segment limit" — both surface
as an allocation failure. [PLIB §7]
System-memory queries: `p_getram` (RAM in paragraphs, capped at 32768 = 512 KB
due to banking), `p_totalK` (total KB ignoring banking; **EPOC ≥ 3.50 only**),
`p_sgfree` (free segmented memory), `p_sgramdisk` (RAM-disk usage). [PLIB §7]
---
## 2. Processes
### 2.1 What a process is
A process is a running program, normally created by loading an image (`.img` /
`.app`) file. EPOC is a **single-user, preemptive multitasking** OS. Max
concurrent processes `E_MAX_PROCESSES` = **24**. A process consists of at least: a
**process control block** (`E_proc`), a **data segment**, a (possibly shared)
**primary code segment**, and an **I/O semaphore**. [PLIB §12]
**Process ID** — a positive 16-bit value: low 12 bits = PCB offset in OS data
space (the "slot"); high 4 bits = a generation counter (07, incremented mod 8 on
slot reuse) so a stale PID from a terminated process is rejected. A bad PID panics
the caller with **panic 7**. [PLIB §12]
**Process names**`<name>.$nn` where `<name>` is 18 chars and `nn` is the
2-digit slot index. For a **task** (a subsidiary process sharing its owner's data
segment — a lightweight thread) the `$` becomes `a`, e.g. `SYS$WSRV.a05`. Max name
`E_MAX_NAME` = 12. Loading the same image twice yields distinct names (e.g.
`SORT.$07`, `SORT.$11`); `p_pidfind("SORT.*")` / `p_pfind` resolve names→PIDs with
wildcards. [PLIB §12]
The standard system processes created at startup:
| Name | Role | Priority |
|---|---|---|
| `SYS$NULL` (`.$01`) | Null process; runs when nothing else is ready; powers the machine off after inactivity | 0 |
| `SYS$MANG` (`.$02`) | Supervisor: memory moving, resource cleanup, critical functions | 248 |
| `SYS$FSRV` (`.$03`) | File server; also loads images to create processes | 240 |
| `SYS$WSRV` (`.$04`) | Window server: screen/keyboard/digitiser | — |
| `SYS$SHLL` (`.$05`) | Shell / launcher | — |
Name/priority/suspend operations **fail** on `SYS$NULL`, `SYS$MANG`, `SYS$FSRV`.
[PLIB §12]
### 2.2 Creating a process
```c
HANDLE p_execc(TEXT *pName, VOID *pCommand, INT length); /* usual route */
```
Loads the image `pName` (parsed with default extension `.IMG`) and returns a
**positive PID** — but the new process is created **SUSPENDED**, so the creator
can initialise it (e.g. set priority) before starting it with `p_presume`. A
command cell is allocated in the *new* process's heap containing the full parsed
path, a length byte, and `length` bytes copied from `pCommand`
(`length``E_MAX_COMMAND_BUFFER` = 127); its address lands in the reserved
static `DatCommandPtr`. Loading is done by the file server, so **the caller must
already be connected to the file server** (the standard PLIB startup does this
before `main`; otherwise panic 41). [PLIB §12]
Error returns from `p_execc` (illustrative of the E_FILE_*/E_GEN_* families):
`E_GEN_NOMEMORY`, `E_FILE_DEVICE`, `E_FILE_NOTREADY`, `E_FILE_DIR`,
`E_FILE_NXIST`, `E_FILE_EXIST` (a different program of the same name — checksum
mismatch — already runs), `E_GEN_IMAGE` (not a valid/corrupt image),
`E_GEN_NOPROC` (no free slots), `E_GEN_ARG` (data+stack+heap > 0xFFE0, or command
too long). [PLIB §12]
- `p_execcasync(pName, pCommand, length, pStatus, pPid)` — asynchronous load
(completion via status word; see §5).
- `p_pcreate(E_CPB *pBlock)` — the **primitive** creator that does *not* involve
the file server; `p_execc` eventually calls it. The `E_CPB` block carries code/
stack/data/heap paragraph counts, initial IP, checksum, `minHeap`, initial
`priority`, RAM/ROM flag, and name. [PLIB §12]
**Shared code.** A second instance of the same program shares the existing
`<name>.$SC` code segment (only the initialised statics are reloaded). The load
verifies the code segment checksum against the image header — so you cannot run
two different programs (or versions) of the same name simultaneously. [PLIB §12]
### 2.3 Priorities and scheduling
Priority is an **unsigned byte, 1255**. At each reschedule the highest-priority
**ready** process runs; if several share the top priority they run **round-robin**,
each for a **4-tick** slice (the system ticks **32×/second**). A lower-priority
process is otherwise blocked indefinitely. Preemption is real: a process need not
call the OS to be switched out. [PLIB §12; SYS §10]
A process leaves the running state by moving to: `SEMAPHORE` (via `p_iowait` /
`p_wait`), `DELTA` (via `p_sleep`/`p_sleept`/`p_sleepa`), or `SUSPENDED` (via
`p_psuspend` on itself). Process states: `E_PROC_READY`, `E_PROC_SEMAPHORE`,
`E_PROC_DELTA`, `E_PROC_SUSPENDED` (plus `E_PROC_FREE` for a vacated slot). The
READY queue is priority-ordered; SEMAPHORE queues are FIFO; the DELTA queue stores
inter-timer tick deltas. [PLIB §12]
Priority guidance and constants:
- Applications should stay within **`E_MIN_PRIORITY` (64)** … **`E_MAX_PRIORITY`
(192)** inclusive; values outside are OS-reserved. [PLIB §12]
- Window-server clients are typically created at **`E_PRIORITY_FORE` (128 =
0x80)** and then let the window server raise/lower them as they gain/lose focus.
A default image priority of **0x80** is the norm. [PLIB §12; GEN §3]
- Supervisor 248, file server 240; interrupts run at the priority of the process
they interrupt. [PLIB §12; SYS §10]
> **Uncertainty / cross-source discrepancy.** [PLIB] states `E_MIN_PRIORITY` = 64
> and `E_MAX_PRIORITY` = 192. [SYS §10] speaks only of reserved bands
> `cPBMinPriority` / `cPBMaxPriority` without giving numbers, and the [SYS]
> service-number appendix lists a symbol `E_MAX_PRIORITY 154`. Treat the PLIB
> pair (64/192) as the documented application range; the 154 appendix value is
> unexplained in prose and should not be relied on.
Priority API: `INT p_getpri(HANDLE pid)`, `INT p_setpri(HANDLE pid, INT nPriority)`
(reschedules; `E_GEN_RANGE` out of range, `E_FILE_NXIST` no such process,
`E_GEN_FAIL` on null/supervisor/file-server), `p_presume`, `p_psuspend`,
`p_marka`/`p_unmarka` (mark this process (in)active — servers mark themselves
non-active), `p_getpid`, `p_pname`/`p_prename`, `p_getowner`. Cross-process data
access: `p_pcpyfr`, `p_pcpyto`, `p_piscpyfr`. [PLIB §12]
### 2.4 Termination
Self-termination:
- `void p_exit(INT nReason)` — normal, graceful. `nReason` in 127..128; **0** =
clean exit; negative = an init-failure error code; positive = an exit status to
a parent. Falling off the end of `main` calls `p_exit` with `main`'s return
value (falling off a `void main` is poor practice — it exits with garbage).
- `void p_panic(INT nPanic)` — abnormal (see §3). Application panic numbers should
count **down from 254** to avoid the system ranges.
Terminating another process:
- `INT p_pterminate(HANDLE pid, INT nReason)`**preferred**. If the target
called `p_onterminate(nMessage)`, it is merely *sent* that message and gets to
run cleanup and exit itself; otherwise it behaves like `p_pkill`.
- `INT p_pkill(HANDLE pid, INT nReason)` — summary kill, no cleanup chance.
- `INT p_ppanic(HANDLE pid, INT nPanic)` — panic another process (e.g. a server
panicking a client that sent garbage).
All three return `E_FILE_NXIST` (no such process) or `E_GEN_FAIL` (target is the
null/supervisor/file-server process). [PLIB §6]
**Termination notification.** `p_logona(pid,&status)` (async, signals the
caller's I/O semaphore), `p_logon(pid,mType)` (delivers an IPC message — for
servers tracking clients), `p_watchall` (message on *any* process termination —
used by the shell). Each delivers a 16-bit **process termination word** whose high
byte is one of: `E_NORMAL_EXIT` (low byte = the `nReason`), `E_PANIC_EXIT` (low
byte = the panic number), or `E_TASK_PANIC_EXIT` (a task it owned panicked).
`p_logoffa`/`p_logoff` cancel; after `p_logoffa` the status word gets
`E_FILE_CANCEL` and you must still `p_waitstat`. [PLIB §6]
### 2.5 Inter-process communication (IPC)
Choices, roughly in order of coupling:
1. **Inter-process messaging** — the primary client/server mechanism (below).
2. **Shared memory segments** — a named external segment opened by several
processes, guarded by a semaphore.
3. **Cross-process copy**`p_pcpyfr` / `p_pcpyto` (a server pulling/pushing a
client's data by reference passed in a message).
4. **Termination logon** and the **notifier** service as lightweight signalling.
**Messaging model** [PLIB §12; SYS §5]. Designed for client/server, one server /
many clients. A receiver first calls `p_minit(nMess, lMess)` to allocate `nMess`
**message slots** from its heap, each an `E_MESSAGE` header
(`next`, `status`, `type`, `pid`) + `lMess` data bytes. The message length is
fixed by the **server**; the file server and supervisor both use `lMess` = 8. When
data exceeds the slot, the message carries an **address+length by reference** and
the server uses `p_pcpyfr`/`p_pcpyto`.
Lifecycle: sender `p_msend*` copies the message into a free slot and enqueues it →
server `p_mreceivew` (or async `p_mreceive`) dequeues from the **front** and gets
the slot address → server processes it → `p_mfree` frees the slot **and**, if the
client used a "receive" variant, writes back a completion status and signals the
client's I/O semaphore.
- Client: `p_msend` (blind), `p_msendreceivew` (send + wait for reply),
`p_msendreceivea` (send + set up an async reply). `p_mcancel` cancels a pending
`p_mreceive`.
- If the server has no free slot, the send **blocks on a mutual-exclusion
semaphore** — even the "async" variant can block indefinitely. Multi-client
servers therefore pre-allocate a slot per potential client (≈ `E_MAX_PROCESSES`
minus known processes).
- **Priority queue-jumping:** a message from a client of priority **≥ 0x80** is
inserted at the **front** of the server queue (overtaking others), so a
foreground task's requests are served first. [PLIB §12; SYS §5]
- For relative priorities to work, a **multi-client server should run at a higher
priority than any client**; a client waiting on a low-priority server is
effectively lowered to the server's priority. [PLIB §12]
The message and I/O systems share status-word + I/O-semaphore mechanics, so async
messaging and async I/O can be mixed freely. [SYS §5]
---
## 3. Panics
A **panic** is a fatal exception. When EPOC detects a condition that "could only
arise from a bug", it **terminates the process immediately** with a panic number
in **0255**. Applications cannot trap or survive a panic (contrast `p_leave`,
which is recoverable). Panicking is cheaper than an error return and enforces
discipline by killing the defect at the point of detection. The OS still uses
**error returns** where a condition can arise from legitimate user action — e.g.
`p_alloc` returns NULL on genuine memory exhaustion but **panics** if it finds the
heap *corrupted*. [GEN §2; PLIB §6]
Panics are raised by `p_panic` (self) or `p_ppanic` (another process — used by a
server against a misbehaving client). Applications may call `p_panic` themselves
to catch "impossible" cases (e.g. a `switch` default). [PLIB §6]
### 3.1 Panic-number ranges — how to interpret one
| Range | Origin | Where documented |
|---|---|---|
| **080, and 255** | PLIB library | PLIB Reference (§6) |
| **81129** | Window Server library | Window Server Reference |
| **130160** | OLIB object library | OLIB Reference |
| **160254** | Non-ROM code (e.g. ISAM and other loadable libraries) | the relevant library's manual |
[GEN §2; PLIB §6]
Notes on reading a panic:
- A given number in the **160254** band may be used by **more than one**
non-ROM component, so it is ambiguous — the only definitive way to locate the
source is to catch it under the **SIBO Debugger** and trace back. [GEN §2]
- The manual overview says PLIB owns 080 & 255; the detailed PLIB list also
documents specific panics at **60, 6569, 77, 78** etc., and warns that a panic
**79** or anything **81254** may belong to another component. Window-server
panics are cited as 81110 in one place and OLIB as 130158 in another (both
inside the summary bands above). [PLIB §6]
- Panics described as *"Invalid function number for …"* rarely indicate a
specific coding bug; they usually mean a **trashed return address** sent the IP
wandering into arbitrary code. [PLIB §6]
### 3.2 Selected PLIB system panic numbers (080, 255)
| # | Meaning |
|---|---|
| 0 | Test-code failure |
| 15 | Semaphore manager: bad function no. / handle / not allocated / negative initial count / negative signal count |
| 68 | Process manager: bad function no. / invalid PID / task tried to create a task |
| 9 | Time manager: bad function number |
| 1014 | Segment manager: bad fn / negative size / bad type / bad handle / copy out of range |
| 1519 | Heap manager: bad fn / heap not initialised / cell reduced by more than its size / granularity > `E_MAX_GROWBY` / **cell address outside heap (heap corrupted — try `p_allchk`)** |
| 2023 | IPC message manager (bad fn; already init'd (double `p_minit`); not init'd; zero-length queue) |
| 2426 | I/O manager: bad fn / **invalid I/O channel** (unchecked `p_open`, closed/overwritten handle) / device requested panic |
| 27 | Invalid wait-handler handle (often really a low-address overwrite at addr 2 via an uninitialised pointer) |
| 2829 | Key/pointer already hooked / requester not a task |
| 3031 | Device manager: bad fn / bad device handle |
| 3234 | File manager: bad fn / already connected to file server / reserved |
| 3541 | Library manager & file-server connection (bad handle/fn, invalid LIB channel, bad DYL index, invalid message to file server, **not connected to file server** = 41) |
| 4247 | Conversion / general manager fns; unhook-notify-when-not-hooked; invalid revector address |
| 48 | `p_leave` called before `p_enter` |
| 4956 | OOP errors (no method, invalid reclass, unknown category/class, supersend outside method, handle before link, missing external categories, object not a valid class) |
| 57 | Invalid link-layer completion code |
| 5859 | Invalid fn for window server / hardware manager |
| 60 | **Write outside process data segment** (uninitialised pointer / corrupt structure) |
| 61 | Interrupts disabled too long |
| 6364 | Divide-by-zero / overflow interrupt |
| 6567 | Dbf manager: bad fn / bad DBF I/O channel / bad DBF parameter |
| 68 | **Address-zero overwrite** (the `0xDEAD` word changed — uninitialised pointer) |
| 69 | Less than 0x100 bytes of stack remain (large automatics — make them static or heap) |
| 70 | Environment name > `EnvMaxNameSize` |
| 71 / 72 | Single-step (INT 1) / breakpoint (INT 3) |
| 73 | **A request was made while an async request of the same type on the same channel was still pending** |
| 7476 | Serial-I/O bad fn / ASIC1 call on an ASIC9 machine / DYL not in a visible bank |
| 77 | Floating-point emulator exception |
| 78 | Semaphore count exceeds 0x7FFF |
| 80 | Library fatal error (preceded by a specific-error notification) |
| 255 | `p_allchk` detected a corrupted heap |
[PLIB §6 "System panic numbers"]
---
## 4. Error codes
System functions signal failure by convention:
- Functions returning an **address** return **NULL (0)** on failure.
- Otherwise: **zero or positive = success, 1 = failure** (`E_GEN_FAIL` = 1; you
may just test the sign).
- When the error is elaborated, a **negative system error number in 1..128** is
returned, allocated in bands: [PLIB §6 "Error returns"]
| Band | Family | Header |
|---|---|---|
| 1 … 31 | General errors `E_GEN_xxx` | `p_gen.h` |
| 32 … 63 | I/O / device / file errors `E_FILE_xxx` | `p_file.h` |
| 64 … 95 | Reserved | — |
| 96 … 128 | OPL run-time errors | — |
`p_errs(TEXT *str, INT errno)` converts a system error number to a
language-dependent string (buffer ≥ `E_MAX_ERROR_TEXT_SIZE` = 64; unknown numbers
give `"Unknown error [xx]"`). The notifier services `p_notify` / `p_notifyerr`
present errors to the user; `p_setnotify(FALSE)` suppresses automatic notification
for unattended/server processes. [PLIB §6]
### 4.1 `E_GEN_*` (general) family
The named generics referenced across the manuals (all defined in `p_gen.h`,
values in 1..31):
| Symbol | Value | Meaning |
|---|---|---|
| `E_GEN_NONE` | 0 | No error / success |
| `E_GEN_FAIL` | **1** | General failure (also the generic "just test the sign") |
| `E_GEN_ARG` | *n* | Bad argument (e.g. invalid `double`, buffer too small, out-of-range command length) |
| `E_GEN_OVER` | *n* | Overflow — value too large for the target type/representation |
| `E_GEN_UNDER` | *n* | Underflow — magnitude too small to represent |
| `E_GEN_RANGE` | *n* | Value outside a permitted range (e.g. priority) |
| `E_GEN_INUSE` | **9** | Resource in use (e.g. segment usage count > 0) |
| `E_GEN_NOMEMORY` | *n* | Insufficient (system) memory; `f_alloc`/`f_realloc` `p_leave` with this |
| `E_GEN_NOPROC` | *n* | No free process slots |
| `E_GEN_NOSEM` | *n* | No semaphores available (`p_semcrt`) |
| `E_GEN_IMAGE` | *n* | File is not a valid image, or is corrupt |
> **Uncertainty.** Of the `E_GEN_*` family only two exact numeric values are
> **fixed by the task's cross-check set and consistent with the manuals**:
> `E_GEN_FAIL = 1` and `E_GEN_INUSE = 9`. The manuals reference the other
> `E_GEN_*` symbols by name and behaviour but the two source files do **not**
> tabulate their individual numeric values (they live in `p_gen.h`). Values shown
> as *n* above are known only to fall in the 1..31 band; do not assume specific
> numbers without the header. [PLIB §6, and usage throughout]
### 4.2 `E_FILE_*` (I/O / file / device) family
Defined in `p_file.h`, values in 32..63. Anchor values confirmed against the
task's cross-check set:
| Symbol | Value | Meaning |
|---|---|---|
| `E_FILE_EXIST` | **32** | Already exists (file, or a differently-checksummed process/segment of the same name) |
| `E_FILE_NAME` | **38** | Invalid name (file / memory-segment name) |
| `E_FILE_DEVICE` | **41** | Device does not exist / device driver not found |
| `E_FILE_PENDING` | *n* | Async request still outstanding (see §5) — the status-word "in progress" marker |
| `E_FILE_CANCEL` | *n* | Request was cancelled (written to the status word by a cancel) |
| `E_FILE_NXIST` | *n* | Does not exist (file, directory entry, or **process**) |
| `E_FILE_DIR` | *n* | Directory does not exist |
| `E_FILE_NOTREADY` | *n* | Device present but no medium (e.g. no SSD in the pack) |
> **Uncertainty.** Three exact values are pinned by the cross-check and match the
> manuals' usage: **`E_FILE_EXIST = 32`, `E_FILE_NAME = 38`,
> `E_FILE_DEVICE = 41`.** The remaining `E_FILE_*` symbols appear by name in the
> manuals (e.g. as `p_execc`, `p_setpri`, segment, and async return values) but
> the two source files do **not** enumerate their numeric values — those are in
> `p_file.h`. Serial-port variants such as `E_FILE_PARITY` are mentioned in prose
> without numbers. [PLIB §6/§7/§12]
Observed usage tying symbols to operations: `p_execc``E_FILE_DEVICE`,
`E_FILE_NOTREADY`, `E_FILE_DIR`, `E_FILE_NXIST`, `E_FILE_EXIST`; `p_setpri` /
`p_presume` / termination calls → `E_FILE_NXIST`; segment create/open →
`E_FILE_EXIST`, `E_FILE_NAME`; segment delete → `E_GEN_INUSE`. [PLIB §7, §12, §6]
---
## 5. Asynchronous I/O model
### 5.1 Two-step services, status words, the I/O semaphore
Most services exist in two halves: **make the request**, then **wait for
completion**. A *synchronous* function does both; the internal *asynchronous*
function issues the request and returns at once. Async request functions include
`p_ioa`/`p_ioc` (on an open I/O channel), `p_mreceive` (IPC), `p_execcasync`
(image load), and `p_logona` (termination). [PLIB §8]
Every process gets an **I/O semaphore** at creation ("asynchronous-request
semaphore" would have been the accurate name). Each async request is tied to a
caller-supplied **status word** (a signed 16-bit `WORD`). The invariant for
*every* async request: [GEN §5; PLIB §8]
1. On issue, the status word is set to the negative **`E_FILE_PENDING`**.
2. On completion, a value **other than** `E_FILE_PENDING` is written — **≥ 0** for
success, a **negative error number** for failure.
3. The write to the status word happens **before** the caller's I/O semaphore is
signalled. Both steps are integral to the mechanism.
**`p_iowait`** decrements the I/O semaphore and blocks until it is non-negative,
i.e. until *something* has been signalled. It does **not** tell you *which*
request completed — after it returns you **poll the status words** for the first
that is no longer `E_FILE_PENDING`. The semaphore starts at 0; because `p_iowait`
decrements first, it returns only when a signal has arrived. [GEN §5; PLIB §8]
```c
p_ioc(SerialChannel, P_FWRITE, &SerialStatus, str, &len); /* async write */
p_ioc(TimerChannel, P_FRELATIVE, &TimerStatus, &timeout); /* async 5s timer */
p_iowait(); /* block on one */
if (SerialStatus == E_FILE_PENDING) { /* timed out: cancel the write */
p_iow(SerialChannel, P_FCANCEL);
p_waitstat(&SerialStatus);
p_leave(SERIAL_TIMEOUT);
}
p_iow(TimerChannel, P_FCANCEL);
p_waitstat(&TimerStatus);
```
[PLIB §8, adapted]
**Prioritisation** is implicit in **poll order**: if two requests have completed,
whichever status word you poll first is serviced first. A low-priority source can
be "locked out" by a rapidly firing higher one. [GEN §5]
**Where to declare status words** — in static data or in a heap control block,
**never** on a stack frame that will return before the request completes. A stale
status word is later written to random memory. [GEN §5]
### 5.2 The `p_io?` calls and channels
I/O devices share a uniform interface: `p_open` (by name, returns a channel
handle) → request services → `p_close`. Synchronous requests use **`p_iow`**
(convenience wrappers `p_iowN`); asynchronous use **`p_ioa`** / **`p_ioc`**
(wrappers `p_ioaN`, `p_iocN`). The async form always takes **one extra parameter**
— the status-word address. `p_ioa` vs `p_ioc` are the async requestors used in the
examples ("a" = asynchronous). [GEN §5; PLIB §8/§9]
### 5.3 The semaphore & I/O-semaphore primitives
EPOC semaphores are **counting** semaphores (signed value): `p_signal`
increments, `p_wait` decrements; a negative value means processes are queued
(released FIFO). [PLIB §8]
| Function | Effect |
|---|---|
| `HANDLE p_semcrt(INT nCount)` | Create a semaphore, initial count `nCount` (≥0); returns handle or `E_GEN_NOSEM`. Auto-deleted on process exit. |
| `void p_semdel(HANDLE s)` | Delete (waiters are released). Rarely needed pre-v3. |
| `void p_wait(HANDLE s)` | Decrement; block if it goes negative. |
| `void p_signal(HANDLE s)` | Increment; release the first waiter and reschedule. |
| `void p_signaln(HANDLE s, INT n)` | `p_signal` × n. |
| `void p_signalnr(HANDLE s)` | Signal **without** rescheduling (reschedule waits for the next tick, or force one with `p_sleept(0)`). |
| `void p_iosignal(void)` | Increment **this** process's I/O semaphore (used by wait handlers, or to raise an "internal event"). |
| `void p_iosignalbypid(HANDLE pid)` | Signal another process's I/O semaphore (after setting its status word via `p_pcpyto`). |
| `void p_iowait(void)` | Wait on the I/O semaphore; runs active wait handlers. |
| `void p_waitstat(WORD *pstat)` | Wait until `*pstat != E_FILE_PENDING`, correctly re-signalling any other completions seen meanwhile. |
| `void p_ioyield(void)` | `p_iosignal` + `p_iowait` — give installed wait handlers a chance to run. |
[PLIB §8]
**Discipline.** Every `p_iosignal` must be matched by a `p_iowait`, and its status
word must already be complete when signalled. An unmatched signal is a **"stray
signal"** at Psion — the next `p_iowait` returns spuriously with nothing ready.
Guard against it (e.g. `p_panic` if the post-`p_iowait` poll finds no completed
word); the **Spy** app helps spot accumulated stray signals. Making a second
request while one of the same type is pending on the same channel → **panic 73**.
[PLIB §8; GEN §5]
**Waiting for one specific request:** use **`p_waitstat`**, not `p_iowait` — it
returns only when *that* status word completes and re-issues signals for other
completions it absorbs. General-purpose synchronous wrappers **must** use
`p_waitstat` (they cannot assume no other requests are pending). [PLIB §8]
### 5.4 Cancelling a request
General principles for all cancels (`p_iow(P_FCANCEL)` for channels, `p_mcancel`
for IPC, `p_logoffa` for termination): [GEN §5; PLIB §8]
- A cancel **precipitates completion**; it does **not** un-issue the request.
- It **may or may not** be effective — the operation may finish naturally first.
- **You must still consume the completion**, normally with an immediate
`p_waitstat`. If the request was still outstanding, `E_FILE_CANCEL` is written
to the status word and the process is signalled.
Canonical timer cancel:
```c
p_iow2(timH, P_FCANCEL); /* synchronous; completes at once */
p_waitstat(&timstat); /* use up the resulting signal */
```
Forgetting the `p_waitstat` after `P_FCANCEL` is a **common bug**: the next
`p_iowait` then returns immediately although nothing is actually ready. [GEN §5]
### 5.5 Wait handlers and interrupt-driven completion
**Wait handlers** run *inside* `p_iowait`/`p_waitstat`, just before it would
return. Install with `p_svecadd`, remove with `p_svecrem`, (de)activate with
`p_sveccall`. Each returns `P_SIGNAL_DISABLE` (completion handled, deactivate),
`P_SIGNAL_ENABLE` (handled, keep active — more pending), or `P_SIGNAL_UNUSED`
(nothing of mine — the signal is external). `p_iowait` returns to the caller only
when all active handlers report `P_SIGNAL_UNUSED`. The handler queue hangs off a
4-byte header in the reserved static at **address 2**; corrupting it usually
yields **panic 27**. [PLIB §8]
This matters because a **hardware interrupt handler cannot write to the requesting
process's data segment** (it may be mid-move). Instead the driver writes to a
fixed location and signals the I/O semaphore; the driver's **wait handler** later
copies the data into the process data segment when the process next enters
`p_iowait`. Therefore, if you **poll** a status word (e.g. between chunks of a long
computation) instead of blocking, call **`p_ioyield` before each poll** so those
handlers can run — and still consume the signal with `p_iowait` once the poll sees
completion, or you get a stray signal. Tight-loop polling without yielding is
"anti-social" (it hogs the CPU). Server-implemented drivers (e.g. the window
server) need no wait handler. [PLIB §8]
---
## 6. The SIBO OS executive call (assembly-level interface)
At the C level, `p_iow`/`p_ioa`/`p_ioc` are thin shells over EPOC's ROM services.
Those services are reached by an **80C86 software interrupt** (`INT XXH`). Two
flavours: **single-service** (`INT XXH` — one dedicated interrupt per common
service, minimal overhead) and **multi-service** (`MOV AH, ZZH` then `INT XXH`,
where `ZZ` selects the function). [SYS §"System services"]
**General calling convention** [SYS §"Calling conventions"]:
- All registers except **AX** are preserved unless they carry a return value; AX
is a scratch/return register.
- **Error** ⇒ **carry flag set**, error number in **AL** (always negative); other
result registers are then indeterminate.
- Handles are 16-bit, guaranteed positive, non-zero and **even**; passed in **BX**
where possible; results returned in **AX**.
- A programmatically invalid argument ⇒ the process is **terminated immediately**
(a panic), not an error return.
**The I/O executive specifically.** The manual documents the underlying I/O
services at register level:
- **`IoAsynchronous`** — `AL` = I/O function number, `BX` = I/O handle,
`DS:CX` → arg1, `DS:DX` → arg2, `DS:DI` → status word; returns `AX` = device
driver result (carry set ⇒ error). This is exactly the layer beneath `p_ioa`/
`p_ioc`: on success the driver later signals completion via
`IoSignal`/`IoSignalByPid`, and you wait with `IoWaitForSignal` /
`IoWaitForStatus` (the `p_iowait`/`p_waitstat` primitives). Panics `PanicIo1`
(bad channel), `PanicLib1`/`PanicLib2` (bad library handle/function).
- **`IoWithWait`** — same inputs minus the status word (`AL`, `BX`, `DS:CX`,
`DS:DX`), returns `AX` = result; the **synchronous** service beneath `p_iow`.
- `IoAsynchronousNoError` — as `IoAsynchronous` but start-up errors are reported
through the status word + signal rather than the carry flag. [SYS §8 "Input
Output Management"]
**[RE — reverse-engineered]** Prior reverse-engineering of a SIBO ROM identifies
the I/O executive concretely as **`INT 0xCF`**, with **`CL` = function code,
`BX` = channel handle, `DX` = argument, result in `AX`** — i.e. the actual
interrupt vector and register wiring under `p_iow`. This is *consistent in shape*
with the documented `IoWithWait`/`IoAsynchronous` services above (handle in BX,
result in AX, args passed by register), but note two things the manuals do **not**
corroborate and that should be treated as RE findings only:
- The manuals **never name the interrupt number** for any service (`INT XXH` is
left abstract throughout `[SYS]`); **`0xCF` comes from RE, not the SDK docs.**
- The documented multi-service convention puts the **function code in `AH`**, and
the I/O services put it in **`AL`** with args in `CX`/`DX` and the status word in
`DI`. The RE note's **`CL` = function / `DX` = argument** differs from the
published register layout. The discrepancy may reflect a particular wrapper,
ROM version, or single-service entry point observed during RE; **do not treat
the `INT 0xCF` / `CL`/`BX`/`DX`/`AX` mapping as documented** — it is an
RE observation to be verified against the specific ROM.
---
## 7. Workabout / Workabout MX notes
The manuals cover the SIBO family collectively (HC, MC, Series 3/3a, Workabout);
the programming model above — small model, segment moving, panics, error
families, async I/O, the executive interrupt — applies to the Workabout as it does
to the rest of the range. **[MX]** The Workabout MX is a later, faster
(higher-clock, more-RAM) member of the same SIBO/EPOC-16 line; **the SDK source
manuals used here (v2.30, 1999) do not carry MX-specific programming differences**,
so any MX deltas (RAM sizing, ROM version, clock/timer specifics) are **not
established from these sources** and are left as an explicit uncertainty rather
than guessed. Version-gated APIs seen above (`p_totalK` needs EPOC ≥ 3.50;
`p_semdel` recommended only from v3) are the kind of thing to check against the
actual MX ROM version via `p_getver` / the version services. [GEN §7; PLIB §7]
---
### Source map
- Memory model & allocation: **[PLIB §1, §7]**, **[GEN §1]**
- Processes, priorities, IPC, termination: **[PLIB §6, §12]**, **[SYS §5, §10]**
- Panics & panic ranges: **[GEN §2]**, **[PLIB §6]**
- Error families: **[PLIB §6]** (+ usage in §7/§12)
- Async I/O, semaphores, wait handlers, cancel: **[GEN §5]**, **[PLIB §8, §9]**
- Executive interrupt / register conventions: **[SYS]** "System services",
"Calling conventions", §8; **`INT 0xCF` mapping = [RE], not in the manuals.**
+672
View File
@@ -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.0164 s). Errors `E_GEN_NSUP` (bad mode), `E_GEN_RANGE` (interval 0..9).
- `P_FREAD` (COUNTING mode): `p_iow(pcb, P_FREAD, ULONG *arg1)` — elapsed 1/1024 s
since `P_FSTART`, not reset by the read.
- `P_FREAD` (REPEATING mode): `p_iow(pcb, P_FREAD)` — completion status = number of
whole intervals elapsed since the last `P_FSTART`/`P_FREAD` (waits for ≥1
interval; time is not lost between reads).
- Read errors: `E_GEN_OVER` (>32767 intervals / too large for `arg1`),
`E_FILE_CANCEL`, `E_FILE_READ` (counter not running, or machine was switched off
mid-count).
### 5.4 Sound — `SND:` (*IODEV* Ch.5)
Speaker driven by the `SND:` driver; a separate piezo **buzzer** (all machines
except Series 3a, which emulates it via the speaker) is driven by the PLIB
`p_sound` routine, not `SND:`. Output is disabled if the `E_SOUND_DEVICE` sound-flag
bit is clear or `E_SOUND_DISABLE` is set (managed via `p_getsnd`/`p_setsnd`). The
speaker is a **two-voice** device (chords / DTMF). Series 3 `SND:` is limited to
DTMF and simple alarms — richer sound needs an extra LDD (e.g. `SVDFRC.LDD` on the
SDK disk). Series 3a can also play/record `.WVE` files.
- `p_open("SND:", -1)` — errors `E_FILE_ALLOC`, `E_GEN_FAIL` (disabled),
`E_FILE_LOCKED`/`E_GEN_INUSE`.
- `P_FCANCEL` — cancel outstanding write.
- `P_FSENSE` / `P_FSET``E_SOUND` struct (`epoc.h`):
```c
typedef struct { UBYTE beatsPerMinute; UBYTE volume; } E_SOUND;
```
`beatsPerMinute` (HC/MC/3a) `E_SOUND_MIN_BPM` 2 .. `E_SOUND_MAX_BPM` 240, default
120 (no effect on Series 3). `volume` 0 (`E_SOUND_MAX_VOLUME`, loudest) .. 5
(`E_SOUND_MIN_VOLUME`); Series 3 range 1..4; Series 3a has four distinct volumes;
smaller = louder; default 1. Always sense-then-set immediately after open, before
playing — even at defaults.
- `E_FALARM` — `p_iow(pcb, E_FALARM, UWORD *palarm)`: `*palarm` 0 = "rings", 1 =
"chimes".
- `E_FSSOUNDCHANNELn` *(HC/MC/3a only)* — `p_ioc(pcb, E_FSSOUNDCHANNELn, pstat,
WORD *pnotes, WORD *plen)`, n = 1 or 2. `*plen ≤ 16384` notes; each note is two
words (frequency Hz — middle A = 440; duration in beats). Output starts only once
**both** channels have been called (for sync); use `*plen = 0` for an unused
voice.
- `E_FDIAL` *(Series 3 / 3a only)* — `p_iow(pcb, E_FDIAL, TEXT *pstr, E_DIAL
*pdial)` emits DTMF for `*pstr`.
```c
typedef struct { UBYTE toneLengthTicks; UBYTE delayLengthTicks;
UWORD pauseLengthTicks; } E_DIAL; /* ticks = 1/32 s */
```
Valid chars 0-9, A-F (`#`→F, `*`→E); space/comma = pause; others ignored; max 26
tone/pause chars. Errors `E_FILE_CANCEL`, `E_GEN_ARG` (too many chars).
### 5.5 Magnetic card reader — `MCR:` (*IODEV* Ch.12)
HC MCR driver, built into the HC OS. Interface fits top (`MCR:A`), bottom (`MCR:B`)
or cradle (`MCR:C`).
- `p_open("MCR:x", -1)` — errors `E_GEN_NOMEMORY`, `E_FILE_DEVICE` (no interface in
slot), `E_FILE_NAME`, `E_FILE_LOCKED`/`E_GEN_INUSE`.
- `P_FREAD` — `p_iow(pcb, P_FREAD, UBYTE *buf1, UBYTE *buf2)`: track 1 → `*buf1`,
track 2 → `*buf2` (each buffer ≥ 256 bytes). Data is **leading-byte-count ASCII**;
count 0 = unsuccessful. Pass either buffer as `NULL` to skip that track. Track 1 =
alphanumeric (name/account), track 2 = numeric (most readers do track 2 only).
Errors `E_FILE_READ` (decode error), `E_GEN_OVER`, `E_FILE_CANCEL`.
- `P_FCANCEL` — cancel the read.
- `P_FSET` *(EPOC ≥ 2.32)* — `p_iow(pcb, P_FSET, UWORD *mask)` programs the 100 kΩ
pull-up/down resistors on the five reader lines (bit set = pull-up, clear =
pull-down; default all pull-down): `M_DATA1PU` 0x01, `M_CLK1PU` 0x02, `M_DATA2PU`
0x04, `M_CLK2PU` 0x08, `M_CLSPU` 0x10 (card-present). Other bits ignored.
### 5.6 Infrared (*IODEV* Ch.1517)
Psion IR is **IrDA-compliant only** (S3a/Siena ports won't talk to non-IrDA
devices). The protocol stack, bottom to top: **SIR** (Serial Infrared physical
layer device driver, drives the hardware) → **IrLAP** (link access) → **IrLMP**
(link management) → the **IrMUX API** (server side) and **AccessIr API** (client
side). Two application-facing device channels are exposed:
- **`AIR:`** — the AccessIr API device. `p_open(&pcb, "AIR:", 0)` (mode 0) powers up
the port; errors `E_GEN_INUSE`, `E_GEN_NOMEMORY`. Services use `P_FIR*` codes
(numeric values from *IODEV* Ch.16): `P_FREAD` 1, `P_FWRITE` 2,
`P_FIRDISCONNECT` 4, `P_FIRDISCOVER` 5, `P_FIRSELECT` 6, `P_FIRAWAITCONNECT` 7,
`P_FIRMAKECONNECT` 8. Flow: `P_FIRDISCOVER` (log in-range machines) →
`P_FIRSELECT` → `P_FIRMAKECONNECT` (primary) / `P_FIRAWAITCONNECT` (secondary),
exchanging up to 56 bytes of connect data → `P_FREAD`/`P_FWRITE` →
`P_FIRDISCONNECT`.
- **`IRP:`** — the IR **printer-port** device driver (the "IRP" API), used for IR
printing. (*IODEV* Ch.15, protocol-layer diagram.)
The **IrMUX API** (*IODEV* Ch.17) is the server-side interface (LM-IAS
registration, connection-oriented and connectionless reads/writes) exposed through
`LM_*` messages rather than `P_Fxxx` codes.
> MX note: these chapters describe the S3a/Siena/S3c-era IrDA stack; the manuals do
> not document Workabout-MX-specific IR behaviour.
---
## 6. Barcode scanning
### 6.1 Workabout MX integral laser — see `SCANNER-API.md`
The **Workabout MX integral laser scanner is not covered by these manuals.** It is
documented separately in **`SCANNER-API.md`** (sibling of this file, at
`/tmp/claude/inv/SCANNER-API.md`). In brief, per that document: the MX uses the
logical driver **`WL2`** (units `WL2:A`, `WL2:D`) with decoder type **`Symbol2`**
(the integral Symbol laser engine); it is a decoded scanner, triggered by the
keyboard scan key (Window Server key code 368). **Do not duplicate that content
here — consult `SCANNER-API.md` for the MX.**
The remainder of this section summarises only the **older external** wand/RS232
barcode devices as documented in *IODEV*.
### 6.2 External wand decoders — `BAR:` (*IODEV* Ch.13)
The HC has **no** built-in barcode driver/decoder; readers are supported by external
combined **decoder + device-driver LDDs** that the app must load (`p_loadldd` or OPL
`DevLoadLdd`). Wand interface module fits the top (`BAR:A`) or bottom (`BAR:B`) of
the HC. Available drivers (device name `BAR` in all cases):
| File | Symbologies |
| ---- | ----------- |
| `BAREAN.LDD` | EAN8, EAN13, UPC, UPCE |
| `BARC39.LDD` | Code 39 |
| `BARITF.LDD` | Interleaved 2 of 5 (ITF) |
| `BAR128.LDD` | Code 128 |
| `BARMPLES.LDD`| Modified Plessey |
| `BARRAW.LDD` | raw |
None auto-discriminates. Load e.g. `p_loadldd("BARC39.LDD")`; remove with
`p_devdel("BAR")` (or OPL `DevDelete`).
- `p_open("BAR:", -1)` — opens via the previously loaded LDD. Errors as for `MCR:`.
- `P_FREAD` — `p_iow(pcb, P_FREAD, UBYTE *buf)` (buf ≥ 256). Leading-byte-count
ASCII; count 0 = failed read. The **first character encodes the symbology** (e.g.
EAN8/13, UPC, Code 39, ITF, Code 128, Modified Plessey, UPCE); the rest is the
decoded data. Errors `E_GEN_OVER`, `E_FILE_CANCEL`.
- `P_FCANCEL` — cancel the read.
### 6.3 Intelligent reader / RS232 module — `TTY:` escape commands (*IODEV* Ch.14)
The **intelligent** RS232/barcode expansion module contains a decoding
micro-controller and shares one serial port between an RS232 interface and the
barcode interface (not usable simultaneously). Port mapping by slot: top slot →
RS232 `TTY:A`, barcode `TTY:D`; bottom slot → RS232 `TTY:B`, barcode `TTY:E`. The
barcode interface talks to the host at the standard HC comms settings (XON/XOFF,
no hardware handshaking). It auto-discriminates EAN/JAN 8/13, UPCA, UPCE, Codabar,
Code 128, Interleaved 2 of 5, and Code 39 (standard/extended), transmitting decoded
data left-to-right as ASCII, terminated by default with a single CR.
Because it is reached through `TTY:`, it uses the normal serial services
(`p_open`, `P_FSENSE`, `P_FSET`, `P_FREAD`, `P_FWRITE`, `p_close`). It is
**programmed by writing escape sequences** to it via the serial channel:
```
<Esc>-y<code><command> general form
<Esc>-y<code><command><text> set-termination-string form
<Esc>E hard reset (special form)
```
`<Esc>` = 0x1B, then `-` (0x2D), then `y`/`Y`, a decimal `<code>` of 13 digits
(0255), and an uppercase command letter; no embedded spaces. Multiple option bits
are combined by **summing** their `<code>` values. Multiple commands may be
concatenated in one sequence — intermediate command letters lowercase, the final
one uppercase (e.g. `<Esc>-y13f2h1D`).
Command letters (each `<Esc>-y<code><letter>`):
| Cmd | Function |
| --- | -------- |
| `D` | serial inter-character delay (0 = none, 1 = 10 ms) |
| `E` | hard reset + self-test (special form `<Esc>E`; reverts all options to default; self-test failures reported as `... SELF TEST FAILED<CR><LF>`) |
| `F` | select symbology bitmask: 1 Code39, 2 ITF, 4 UPC/EAN, 8 Codabar, 16 Code128 (default `31`) |
| `G` | check-character options (verify / transmit check digits; per-symbology) |
| `H` | decoding options (extended Code39, Codabar start/stop, UPC-vs-EAN, 2/5-digit supplements, UPC E→A expansion, auto-discriminate supplements) |
| `J` | single-read mode enable/disable |
| `K` | single-read control (fire one read when in single-read mode) |
| `M` | set Interleaved 2 of 5 length |
| `O` | set termination string (`<Esc>-y<code>O<string>`, up to 4 chars) |
| `Q` | Code ID characters |
| `S` | status request |
| `W` | scanner enable |
Per-symbology output formats (ID chars, check digits, supplement digits for
UPC E/A, EAN 8/13 and their +2/+5 variants; Codabar start/stop; ITF and Code 39
check handling) are tabulated in *IODEV* Ch.14.
---
## 7. Other channel devices (pointers)
*IODEV* also documents the **Console** (`CON:`, Ch.2 — screen/keyboard, `P_SCR_*`
function codes, `P_CON_KBREC`), **Alarm** (Ch.6, `A_FTIMED`/`A_FUNTIMED`), **World
database** (Ch.8), **Xmodem/Ymodem** (Ch.9, `P_FCONNECT`/`P_FREAD`/`P_FWRITE`),
**NCP / Link** (Ch.10, `SYS$NCP`), **Cradle/Docking Station** (Ch.11), and the
**Fast Charger** (Ch.18, `FCHG_*`). The **`FIL:`** file driver and the **`TIM:`**
timer are documented in the *PLIB* Files and Time chapters. These are outside the
scope of this reference but follow the same `p_open` + `P_Fxxx` model described in
Sections 13.
---
### Source map
| Topic | Source |
| ----- | ------ |
| Device model, `p_open`, `FIL:` fallback, modes, attached drivers, file server | *PLIB* Ch.9 |
| I/O primitives (`p_ioa/ioc/iow` + variants), `p_read/write/close`, cancel | *PLIB* Ch.9 |
| `p_iowait` / `p_waitstat`, semaphore rules | *PLIB* Ch.8 |
| Function codes, `IoFunc*` mapping | *PLIB* Ch.9; *IODEV* Ch.1/16; *ADDSYS* driver chapter |
| `p_loadldd`/`p_loadpdd`/`p_devdel`/`p_devqu`/`p_devfnd`; `.LDD`/`.PDD`; device segments | *PLIB* Ch.9, Ch.7 |
| `TTY:`, `P_SRCHAR`, control lines | *IODEV* Ch.4 |
| `PAR:` | *IODEV* Ch.3 |
| `FRC:` | *IODEV* Ch.7 |
| `TIM:` | *PLIB* Time chapter (referenced from *IODEV* Ch.1) |
| `SND:` | *IODEV* Ch.5 |
| `MCR:` | *IODEV* Ch.12 |
| IR (`AIR:`, `IRP:`, IrMUX) | *IODEV* Ch.1517 |
| External `BAR:` wand decoders | *IODEV* Ch.13 |
| Intelligent reader / `TTY:` escape commands | *IODEV* Ch.14 |
| Workabout MX integral laser (`WL2`/`Symbol2`) | **`SCANNER-API.md`** (not these manuals) |
+458
View File
@@ -0,0 +1,458 @@
# PLIB Core API Reference (Psion SIBO / Workabout MX)
Categorised function reference for the core **PLIB** library, drawn from the
*PLIB Reference*, Version 2.10 (3 February 1995), (C) Psion PLC.
## Conventions
- Signatures are transcribed from the manual. The OCR source occasionally
mangles glyphs; signatures below are corrected to the manual's own C types
where the intent is unambiguous. Uncertain items are marked **[?]**.
- Types: `TEXT` = char/byte string, `UBYTE`/`BYTE`, `UWORD`/`WORD` (16-bit),
`UINT`/`INT` (16-bit), `ULONG`/`LONG` (32-bit), `DOUBLE` (64-bit float),
`VOID`, `HANDLE`, `BOOL`.
- Many functions return `0` on success and a negative `E_*` error number
(defined in `p_gen.h` etc.) on failure.
- `f_*` twins of `p_*` allocators/senders behave identically except they call
`p_leave(E_GEN_NOMEMORY)` / `p_leave(err)` instead of returning `NULL`/`err`.
---
## 1. String & Buffer Handling
Declared chiefly in `p_std.h` / `plib.h`. Buffers (`b*`) take an explicit
length; strings (`s*`) are zero-terminated.
### 1.1 Copy, length, concatenate, fill
| Signature | Purpose |
|---|---|
| `UBYTE *p_bcpy(VOID *target, VOID *source, UINT len);` | Copy `len` bytes source→target (overlap-safe); returns `target+len`. |
| `UINT p_slen(TEXT *str);` | Return length of zero-terminated string, excluding the terminator. |
| `TEXT *p_scpy(TEXT *target, TEXT *source);` | Copy string source→target; returns address of target's terminating zero. |
| `TEXT *p_scpym(TEXT *target, ...);` | Copy+concatenate a NULL-terminated list of strings into target. |
| `TEXT *p_scat(TEXT *lstr, TEXT *rstr);` | Append rstr to lstr; returns address of new terminating zero. |
| `TEXT *p_scatm(TEXT *lstr, ...);` | Append a NULL-terminated list of strings to lstr. |
| `UBYTE *p_brep(VOID *buf, INT buf_len, VOID *pattern, INT pat_len);` | Fill buf by replicating a byte pattern; returns `buf+buf_len`. |
| `TEXT *p_srep(TEXT *buf, INT buf_len, TEXT *pattern);` | Fill buf by replicating a string pattern (terminator excluded). |
| `VOID p_bswap(VOID *buf1, VOID *buf2, INT len);` | Swap `len` bytes between two buffers. |
| `UBYTE *p_bfil(VOID *buf, UINT buf_len, INT fill_byte);` | Fill buf with a repeated byte; returns `buf+buf_len`. |
| `TEXT *p_jtob(TEXT *tbuf, INT tlen, TEXT *sbuf, INT slen, INT type, INT fill);` | Left/right/centre-align sbuf in tbuf (`P_JLEFT`/`P_JRIGHT`/`P_JCENTRE`), padding with `fill`. |
### 1.2 Comparison
| Signature | Purpose |
|---|---|
| `INT p_bcmp(VOID *lbuf, INT lbuf_len, VOID *rbuf, INT rbuf_len);` | Compare two buffers byte-wise; returns lbufrbuf (<0 / 0 / >0). |
| `INT p_scmp(TEXT *lstr, TEXT *rstr);` | Compare two strings; returns lstrrstr. |
| `INT p_bcmpi(TEXT *lbuf, INT lbuf_len, TEXT *rbuf, INT rbuf_len);` | Case-independent buffer compare. |
| `INT p_scmpi(TEXT *lstr, TEXT *rstr);` | Case-independent string compare. |
*(OCR renders these as `p_bemp`/`p_bempi`; the intended names are `p_bcmp`/`p_bcmpi`.)*
### 1.3 Searching (character, substring, wildcard)
| Signature | Purpose |
|---|---|
| `INT p_bloc(VOID *buf, INT buf_len, INT ch);` | Index of first `ch` in buffer, or 1. |
| `INT p_sloc(TEXT *str, INT ch);` | Index of first `ch` in string, or 1. |
| `INT p_bloci(TEXT *buf, INT buf_len, INT ch);` | Case-independent forward char search in buffer. |
| `INT p_sloci(TEXT *str, INT ch);` | Case-independent forward char search in string. |
| `INT p_slocr(TEXT *str, INT ch);` | Reverse (last-occurrence) char search in string. |
| `INT p_slocri(TEXT *str, INT ch);` | Case-independent reverse char search in string. |
| `INT p_bsub(VOID *buf, INT buf_len, VOID *sbuf, INT sbuf_len);` | Index of first occurrence of a byte sequence in buffer, or 1. |
| `INT p_ssub(TEXT *str, TEXT *substr);` | Index of first occurrence of substring in string, or 1. |
| `INT p_bsubi(TEXT *buf, INT buf_len, TEXT *sbuf, INT sbuf_len);` | Case-independent buffer subsequence search. |
| `INT p_ssubi(TEXT *str, TEXT *substr);` | Case-independent substring search. |
| `INT p_bmatch(TEXT *buf, INT blen, TEXT *mbuf, INT mlen);` | Match buffer against a wildcard spec (`*`, `?`); returns TRUE/FALSE. |
| `INT p_bmatchi(TEXT *buf, INT blen, TEXT *mbuf, INT mlen);` | Case-independent wildcard buffer match. |
| `INT p_smatch(TEXT *str, TEXT *mstr);` | Match string against wildcard spec (`*`, `?`). |
| `INT p_smatchi(TEXT *str, TEXT *mstr);` | Case-independent wildcard string match. |
### 1.4 Character classification (return TRUE/FALSE for `c` modulo 256)
| Signature | Purpose |
|---|---|
| `INT p_isupper(INT c);` | Uppercase alphabetic (accented or not). |
| `INT p_islower(INT c);` | Lowercase alphabetic. |
| `INT p_isalpha(INT c);` | Alphabetic either case. |
| `INT p_isdigit(INT c);` | Decimal digit 09. |
| `INT p_isalnum(INT c);` | Alphanumeric. |
| `INT p_isxdigit(INT c);` | Hex digit 09, AF, af. |
| `INT p_isspace(INT c);` | Whitespace (space, HT, NL, VT, FF, CR). |
| `INT p_iscntrl(INT c);` | Control character (031, 127). |
| `INT p_ispunct(INT c);` | Printable graphic, not alphanumeric/space. |
| `INT p_isgraph(INT c);` | Printable graphic (alnum or punct). |
| `INT p_isprint(INT c);` | Printable, i.e. `p_isgraph` plus space. |
### 1.5 Folding & case conversion, skipping
| Signature | Purpose |
|---|---|
| `TEXT *p_skipwh(TEXT *str);` | Skip leading whitespace; return first non-white char. |
| `TEXT *p_skipch(TEXT *str);` | Skip non-white chars; return first whitespace/terminator. |
| `INT p_tofold(INT c);` | Fold char via built-in fold table (for case-insensitive matching). |
| `TEXT *p_scpyf(TEXT *target, TEXT *source);` | Copy string folding each char (like `p_scpy`). |
| `VOID p_sconf(TEXT *str);` | Fold the characters of a string in place. |
| `INT p_toupper(INT c);` | Convert char to upper case (display use, not matching). |
| `INT p_tolower(INT c);` | Convert char to lower case (display use, not matching). |
| `VOID p_scap(TEXT *str);` | Capitalise string: first char upper, rest lower. *(EPOC 2.14+)* |
> **Folding vs. case:** `p_tofold`/`p_sconf` are for case-insensitive
> ordering/matching (symbol tables); `p_toupper`/`p_tolower`/`p_scap` are for
> human-visible text and must **not** be used for comparison.
---
## 2. Arrays, Queues & Sorting (misc utilities)
Doubly-linked circular queues use `P_QUE` headers; delta queues use `P_DELTA`.
| Signature | Purpose |
|---|---|
| `INT p_bsrch(INT nrec, INT (*compf)(), INT *pmid, UBYTE *pmatch);` | Binary search over `nrec` records via callback; writes found index to `*pmid`. |
| `INT p_qsort(INT nrec, INT (*ordf)(), VOID (*excf)(), UBYTE *base);` | Quicksort `nrec` records using ordering and exchange callbacks. |
| `VOID p_enque(P_QUE *pNew, P_QUE *pEntry);` | Insert `pNew` before `pEntry` in its doubly-linked queue. |
| `VOID p_deque(P_QUE *pEntry);` | Unlink `pEntry` from its queue. |
*(The manual also documents `p_dequed` for delta queues; its clean signature is
not printed in the source text — **[?]**.)*
---
## 3. CRC & Checksums
| Signature | Purpose |
|---|---|
| `VOID p_crc(UWORD *pcrc, UBYTE *buf, UINT len);` | Incrementally accumulate CCITT CRC-16 (x¹⁶+x¹²+x⁵+1) over `len` bytes; init `*pcrc` to 0 first. |
---
## 4. Console I/O
Primitive console services layered on the `CON:` device / window server. The
console is opened automatically on first use. PLIB offers only line output of
mono-spaced text and simple backspace-edited line input; richer row/column
control lives in the `CON:` device driver, and full UI in the Window Server.
Internal buffer is `P_MAXSYSIO` (258) bytes, limiting output to 256 bytes/call.
| Signature | Purpose |
|---|---|
| `VOID p_putch(UINT c);` | Write one character to the console. |
| `VOID p_puts(TEXT *str);` | Write a string and start a new line. |
| `VOID p_printf(TEXT *fstr, ...);` | Format args (as `p_atob`), write line, advance to next line. |
| `VOID p_print(TEXT *fstr, ...);` | As `p_printf` but no automatic newline (use `\r`,`\n`). |
| `INT p_getch(VOID);` | Wait for a keypress; return its character code. |
| `INT p_gets(TEXT *str);` | Read a line (backspace editing) up to `P_MAXSYSIO-1`; returns length. |
| `INT p_getl(TEXT *pmt, TEXT *str, INT len);` | Write prompt `pmt`, read up to `len` chars into `str`; returns length. |
---
## 5. Number ↔ String Conversion
Declared in the *Integer Conversion and Rectangle Functions* chapter.
### 5.1 Integer/long → text
| Signature | Purpose |
|---|---|
| `UINT p_itob(TEXT *buf, INT value);` | Signed decimal of INT → buf; returns chars written. |
| `INT p_ltob(TEXT *buf, LONG value);` | Signed decimal of LONG → buf; returns chars written. |
| `INT p_gtob(TEXT *buf, UINT value, INT radix);` | Unsigned UINT in any radix → buf. |
| `INT p_gltob(TEXT *buf, ULONG value, INT radix);` | Unsigned ULONG in any radix → buf. |
### 5.2 Formatted multi-argument output
| Signature | Purpose |
|---|---|
| `INT p_atob(TEXT *buf, TEXT *fstr, VOID *parg);` | `printf`-style format of arg list `parg` into buf; returns chars written. |
| `VOID p_atos(TEXT *str, TEXT *fstr, ...);` | Convenience variadic wrapper over `p_atob` producing a zero-terminated string. |
Format `%[<align>][<fill>]<width><type>`; types: `b` binary, `c` char, `d`
signed dec, `f` fill only, `m`/`w` 2-byte MSB/LSB binary *(EPOC 2.17+)*, `o`
octal, `s` string, `u` unsigned dec, `x` hex. Widen to long with `l`/`L` or
upper-case type; `<width>`/`<fill>` may be `*` (taken from args).
### 5.3 Text → integer/long (radix conversions)
All take `TEXT **pstr` (advanced past the parsed field on success), return 0 or
`E_GEN_OVER`/`E_GEN_FAIL`.
| Signature | Purpose |
|---|---|
| `INT p_stoi(TEXT **pstr, WORD *pval);` | Signed decimal string → 16-bit WORD. |
| `INT p_stol(TEXT **pstr, LONG *pval);` | Signed decimal string → 32-bit LONG. |
| `INT p_stog(TEXT **pstr, UWORD *pval, INT radix);` | Unsigned string in any radix → 16-bit UWORD. |
| `INT p_stogl(TEXT **pstr, ULONG *pval, INT radix);` | Unsigned string in any radix → 32-bit ULONG. |
| `INT p_stoa(TEXT **pstr, TEXT *fstr, ...);` | Scan multiple fields from a string into args per format `fstr`. |
### 5.4 Double ↔ text (floating point conversion)
| Signature | Purpose |
|---|---|
| `INT p_dtob(TEXT *pbuf, DOUBLE *pval, P_DTOB *pformat);` | Format a double to text per `P_DTOB` (type/width/decimals/point/triad); returns chars or `E_GEN_*`. |
| `INT p_stod(TEXT **pstr, DOUBLE *pval, INT point);` | Parse a floating-point number from text (`point` = decimal-point char) → double. |
> Note: the manual provides no `p_dtos` — double→string is `p_dtob`.
---
## 6. Floating Point, Scientific & Math
Declared in `p_math.h`. Trig args are in **radians**. `E_CONFIG`/config via
`p_getctd`. Two families exist:
- **Scientific functions** (`p_sin``p_pow`) and `p_rand`/`p_frand` require the
8087 emulator (`sys$8087.ldd`).
- **Arithmetic primitives** (`p_fld``p_longtof`) are emulator-independent —
usable to do FP without loading the emulator.
Macros (also emulator-triggering with FP operands): `ABS(x)`, `MAX(a,b)`, `MIN(a,b)`.
### 6.1 Scientific (require emulator)
All return 0 or `E_GEN_ARG`/`E_GEN_UNDER`/`E_GEN_OVER`.
| Signature | Purpose |
|---|---|
| `INT p_sin(DOUBLE *pret, DOUBLE *parg);` | Sine. |
| `INT p_cos(DOUBLE *pret, DOUBLE *parg);` | Cosine. |
| `INT p_tan(DOUBLE *pret, DOUBLE *parg);` | Tangent (arg magnitude limited). |
| `INT p_asin(DOUBLE *pret, DOUBLE *parg);` | Arc sine (`ABS(*parg)<=1`). |
| `INT p_acos(DOUBLE *pret, DOUBLE *parg);` | Arc cosine (`ABS(*parg)<=1`). |
| `INT p_atan(DOUBLE *pret, DOUBLE *parg);` | Arc tangent. |
| `INT p_ln(DOUBLE *pret, DOUBLE *parg);` | Natural (base-e) log. |
| `INT p_exp(DOUBLE *pret, DOUBLE *parg);` | e raised to `*parg`. |
| `INT p_log(DOUBLE *pret, DOUBLE *parg);` | Base-10 log. |
| `INT p_sqrt(DOUBLE *pret, DOUBLE *parg);` | Square root. |
| `INT p_pow(DOUBLE *pret, DOUBLE *parg1, DOUBLE *parg2);` | `*parg1` raised to `*parg2`. |
### 6.2 Emulator-independent FP arithmetic
For one-arg functions, `pret` and `parg` may alias. Return 0 or `E_GEN_*`.
| Signature | Purpose |
|---|---|
| `INT p_fld(DOUBLE *pret, DOUBLE *parg);` | Load: `*pret = *parg`. |
| `INT p_fadd(DOUBLE *pret, DOUBLE *parg);` | `*pret += *parg`. |
| `INT p_fsub(DOUBLE *pret, DOUBLE *parg);` | `*pret -= *parg`. |
| `INT p_fmul(DOUBLE *pret, DOUBLE *parg);` | `*pret *= *parg`. |
| `INT p_fdiv(DOUBLE *pret, DOUBLE *parg);` | `*pret /= *parg`. |
| `INT p_fcmp(DOUBLE *parg1, DOUBLE *parg2);` | Compare: returns 1 / 0 / 1. *(OCR: `p_femp`.)* |
| `INT p_fneg(DOUBLE *parg);` | Negate `*parg` in place. |
| `INT p_mod(DOUBLE *pret, DOUBLE *parg1, DOUBLE *parg2);` | Remainder of `*parg1 / *parg2`. |
| `INT p_int(DOUBLE *pret, DOUBLE *parg);` | Integer part (toward zero) → double. |
| `INT p_inti(WORD *pret, DOUBLE *parg);` | Integer part → 16-bit WORD (range-checked). |
| `INT p_intl(LONG *pret, DOUBLE *parg);` | Integer part → 32-bit LONG (range-checked). |
| `VOID p_itof(DOUBLE *pret, WORD *parg);` | Convert WORD → double. |
| `VOID p_longtof(DOUBLE *pret, LONG *parg);` | Convert LONG → double. |
### 6.3 Random numbers
| Signature | Purpose |
|---|---|
| `ULONG p_randl(ULONG *pseed);` | Next pseudo-random 32-bit long; updates `*pseed`. (No emulator.) |
| `DOUBLE p_rand(ULONG *pseed);` | Random double in [0,1); updates `*pseed`. (Emulator required.) |
| `VOID p_frand(DOUBLE *pret, ULONG *pseed);` | Random double in [0,1) → `*pret`; updates `*pseed`. |
> **Long integer arithmetic:** PLIB exposes no dedicated `p_lmul`/`p_ldiv`
> helpers; 32-bit `LONG`/`ULONG` work is done with native C, the `l*`
> conversion routines above, and `p_ltob`/`p_stol`/`p_stogl`/`p_randl`.
---
## 7. Rectangle & Geometry Utilities
Operate on `P_RECT` / `P_POINT` (declared with the integer-conversion chapter).
| Signature | Purpose |
|---|---|
| `VOID p_offrec(P_RECT *rect, INT xoffset, INT yoffset);` | Move a rectangle by an offset. |
| `VOID p_insrec(P_RECT *rect, INT xinset, INT yinset);` | Shrink/expand a rectangle about its centre. |
| `VOID p_unirec(P_RECT *rect1, P_RECT *rect2, P_RECT *result);` | Union (smallest enclosing rectangle). |
| `INT p_intrec(P_RECT *rect1, P_RECT *rect2, P_RECT *result);` | Intersection; returns TRUE if they intersect. |
| `INT p_pinrec(P_POINT *point, P_RECT *rect);` | TRUE if point lies inside rectangle. |
| `INT p_emprec(P_RECT *rect);` | TRUE if rectangle is empty. |
| `VOID p_absrec(P_RECT *rect, P_RECT *result);` | Normalise negative sides to positive. |
---
## 8. Memory Allocation & the Heap
Heap cells are allocated from the process data segment (max ~64K). `f_*`
variants leave with `E_GEN_NOMEMORY` instead of returning `NULL`.
### 8.1 Cell allocation
| Signature | Purpose |
|---|---|
| `VOID *p_alloc(UINT size);` | Allocate a heap cell ≥ `size` bytes; returns address or `NULL`. |
| `VOID *f_alloc(UINT size);` | As `p_alloc` but leaves on failure. |
| `VOID p_free(VOID *pcell);` | Free a cell (no-op if `pcell` is 0). |
| `VOID *p_realloc(VOID *pcell, UINT size);` | Resize a cell (preserving contents); `pcell==0``p_alloc`. |
| `VOID *f_realloc(VOID *pcell, UINT size);` | As `p_realloc` but leaves on failure. |
| `VOID *p_adjust(VOID *pcell, UINT offset, INT amount);` | Open (+) or close () a gap mid-cell for insert/delete. |
| `UINT p_alen(VOID *pcell);` | Return actual cell length in bytes (≥ requested). |
### 8.2 Heap tuning & diagnostics
| Signature | Purpose |
|---|---|
| `VOID p_hgran(UINT nparas);` | Set heap growth granularity (paragraphs of 16 bytes; ≤ `E_MAX_GROWBY`). |
| `VOID p_allwalk(VOID (*fptr)(VOID *fpar, INT isalloc, UINT len), VOID *fpar);` | Walk every heap cell, calling `fptr`; panics on inconsistency. |
| `VOID p_allchk(INT num);` | Check allocated cells vs. free list; `p_panic(0xff)` with diagnostics if corrupt. |
| `UINT p_allspc(VOID **pheap);` | Return potential free heap space; write heap start to `*pheap`. |
| `p_altchk(...)` **[?]** | Thorough heap-integrity check (referenced by name; full signature not printed in source). |
### 8.3 System memory info
| Signature | Purpose |
|---|---|
| `UINT p_getram(VOID);` | Addressable system RAM in 16-byte paragraphs (≤ 32768). |
| `UINT p_totalK(VOID);` | Total machine RAM in KB, ignoring bank-switching. *(EPOC 3.50+)* |
| `UINT p_sgfree(VOID);` | Available addressable segmented memory in paragraphs. |
| `UINT p_sgramdisk(VOID);` | Paragraphs of addressable RAM used by the internal RAM disk. |
### 8.4 External data segments (shared memory / IPC)
| Signature | Purpose |
|---|---|
| `INT p_sgdelete(TEXT *pName);` | Delete a named external data segment. |
| `INT p_sgcopyto(HANDLE nHandle, LONG pos, VOID *source, UINT len);` | Write `len` bytes into an open segment at `pos`. |
| `INT p_sgcopyfr(HANDLE nHandle, LONG pos, VOID *target, UINT len);` | Read `len` bytes from an open segment at `pos`. |
| `UINT p_sgsize(HANDLE nHandle);` | Size of an open segment in 16-byte paragraphs. |
| `INT p_sgadjust(HANDLE nHandle, INT nParas);` | Grow/shrink an open segment by `nParas` paragraphs. |
| `INT p_sgclose(HANDLE nHandle);` | Close an open segment. |
| `VOID p_sglock(HANDLE nHandle);` | Lock a segment against relocation. |
| `VOID p_sgunlock(HANDLE nHandle);` | Unlock a previously locked segment. |
*(The chapter also references `p_sgcreate`/`p_sgopen`/`p_sgfind`; their clean
signatures are not isolated in the source text — **[?]**.)*
### 8.5 Environment variables
| Signature | Purpose |
|---|---|
| `INT p_getenv(TEXT *pMatch, TEXT *pValue);` | Get value of matching env var (string). |
| `INT p_getenviron(TEXT *pMatch, INT mLength, VOID *pValue);` | Get env var value (binary, length-specified match). |
| `INT p_setenv(TEXT *pName, TEXT *pValue);` | Set/create a string env var. |
| `INT p_setenviron(TEXT *pName, INT nLength, VOID *pValue, INT vLength);` | Set a binary env var. |
| `INT p_delenv(TEXT *pMatch);` | Delete matching env var. |
| `INT p_delenviron(TEXT *pMatch, INT mLength);` | Delete env var (length-specified match). |
| `INT p_fndenv(TEXT *pMatch, TEXT *pName, TEXT *pValue, HANDLE *pHandle);` | Iterate/find env vars by name pattern. |
| `INT p_findenviron(TEXT *pMatch, INT mLength, UBYTE *pBuf, HANDLE *pHandle);` | Iterate/find env vars (binary). |
---
## 9. Date, Time & Timers
System time = seconds since 00:00:00 1 Jan 1970 (`ULONG`). Day-based math uses
`P_DAYSEC` (days since 1900 + seconds in day); human form uses `P_DATE`
(year-1900, month 011, day 030, h/m/s, yrday). Declared in `p_date.h`.
### 9.1 System clock
| Signature | Purpose |
|---|---|
| `ULONG p_date(VOID);` | Return current system time (seconds since 1970). |
| `VOID p_sdate(ULONG newTime);` | Set system time (may fire due absolute timers). |
### 9.2 Sleeping / synchronous timers
| Signature | Purpose |
|---|---|
| `INT p_sleep(ULONG n);` | Sleep `n` tenths of a second (relative timer). |
| `INT p_sleept(LONG nTicks);` | Sleep `nTicks` system ticks (32/s SIBO). |
| `INT p_sleepa(ULONG time);` | Sleep until absolute system `time` (wakes machine). |
*(Asynchronous timers are driven through an open `TIM:` channel via
`p_ioc(P_FRELATIVE)` / `p_ioc(P_FABSOLUTE)` and cancelled with `p_iow(...,P_FCANCEL)`.)*
### 9.3 Date/time conversions
| Signature | Purpose |
|---|---|
| `VOID p_sttods(ULONG *pstim, P_DAYSEC *pds);` | System time → days-since-1900 + seconds-in-day. |
| `INT p_dstost(P_DAYSEC *pds, ULONG *pstim);` | `P_DAYSEC` → system time. |
| `INT p_dstodt(P_DAYSEC *pds, P_DATE *pdt);` | `P_DAYSEC``P_DATE` (fills yrday). |
| `INT p_dttods(P_DATE *pdt, P_DAYSEC *pds);` | Validate `P_DATE``P_DAYSEC`. |
| `INT p_dayinm(INT year, INT month);` | Days in given month (leap-aware); year since 1900. |
| `INT p_wkday(ULONG nDay);` | Weekday (0=Mon … 6=Sun) for day-since-1900. |
| `INT p_weekno(ULONG nDay);` | Week number 153 for day-since-1900. |
### 9.4 Localised name/text lookups
| Signature | Purpose |
|---|---|
| `INT p_nmday(TEXT *buf, INT daynum);` | Language day name (0=Mon … 6). |
| `INT p_nmdaya(TEXT *buf, INT daynum);` | Abbreviated day name. *(EPOC 3.18+)* |
| `INT p_nmmon(TEXT *buf, INT monthnum);` | Language month name (0=Jan … 11). |
| `INT p_nmmona(TEXT *buf, INT monthnum);` | Abbreviated month name. *(EPOC 3.18+; OCR prints this as a second `p_nmmon`.)* |
| `VOID p_getsuffixes(TEXT *buf);` | Fill array of 31 day-of-month suffixes (3-byte elements). |
| `VOID p_getampmtext(TEXT *buf, INT n);` | am (`n=0`) / pm (`n=1`) suffix text. |
| `VOID p_getctd(E_CONFIG *pcfg);` | Copy system locale/config (`E_CONFIG`) struct. |
---
## 10. Object-Oriented Programming Primitives (brief)
PLIB provides run-time OOP: categories (load modules of classes) identified by
category number or `HANDLE`; objects are heap instances with a class header.
`f_*` twins leave with `p_leave` on failure. Declared with the OOP chapter.
### 10.1 Category / object creation & class management
| Signature | Purpose |
|---|---|
| `HANDLE p_getlibh(INT catNum);` | Convert a category number to a category handle (0 = local). |
| `VOID *p_new(INT catNum, INT classNum);` | Create an instance of `classNum` in category `catNum`; property zeroed. |
| `VOID *f_new(INT catNum, INT classNum);` | As `p_new`, leaves on OOM. |
| `VOID *p_newlibh(HANDLE catHandle, INT classNum);` | Create instance by category handle. |
| `VOID *f_newlibh(HANDLE catHandle, INT classNum);` | As `p_newlibh`, leaves on OOM. |
| `VOID *f_newsend(INT catNum, INT classNum, INT methodNum, ...);` | Create then send init `methodNum`; cleans up (destroy) on leave. |
| `VOID *f_newlibhsend(HANDLE catHandle, INT classNum, INT methodNum, ...);` | As `f_newsend` but by category handle. |
| `VOID p_reclass(INT catNum, INT classNum, VOID *pObject);` | Change an object's class (same property length). |
| `VOID p_reclassbyhandle(HANDLE catHandle, INT classNum, VOID *pObject);` | Reclass by category handle. |
| `INT p_loadfilelib(VOID *fcb, UINT n, HANDLE *pCatHandle, INT link);` | Load an external category (load module) and get its handle. |
| `VOID p_linklib(HANDLE catHandle);` | Link a loaded category to the local process. |
| `VOID p_ccpy(VOID *pTarget, VOID *pSource, UINT count);` | Copy `count` bytes (category/object helper copy). |
### 10.2 Message sending
`p_send*` use the stack (`CDECL`) convention; the `p_send2..5` / `f_send2..5`
numbered variants use the faster register convention. `f_*` leaves with
`p_leave(err)` if the method returns negative.
| Signature | Purpose |
|---|---|
| `INT p_send(VOID *pObject, INT methodNum, ...);` | Dispatch `methodNum` to object, searching class then superclasses. |
| `INT f_send(VOID *pObject, INT methodNum, ...);` | As `p_send`, leaves on negative return. |
| `INT p_send2(VOID *pObject, INT methodNum);``p_send5(VOID *pObject, INT methodNum, VOID *p1, VOID *p2, VOID *p3);` | Register-convention sends with 03 extra args (`f_send2..5` twins). |
| `INT p_supersend(VOID *pObject, INT methodNum, ...);` | Dispatch starting at the superclass of the calling method (`p_supersend2..5`). |
| `INT p_entersend(VOID *pObject, INT methodNum, ...);` | Dispatch as if via `p_enter` (unwinds on `p_leave`) (`p_entersend2..5`). |
| `INT p_exactsend(HANDLE catHandle, INT classNum, VOID *pObject, INT methodNum, ...);` | Dispatch starting at an explicitly named class. |
---
## 11. Related Error/Process Primitives (referenced above)
These appear alongside the categories above and are commonly used with them:
| Signature | Purpose |
|---|---|
| `INT p_enter(VOID *pfunc, ...);` | Enter a function under structured error trapping. |
| `VOID p_panic(INT nPanic);` | Abort the process with a panic number (unrecoverable). |
| `VOID p_exit(INT nReason);` | Graceful process termination. |
*(Full error-handling and process/IPC APIs are outside this core-PLIB scope but
`p_enter`/`p_leave`/`p_panic` underpin the `f_*` allocator and sender twins.)*
---
### Uncertainty notes
- OCR corrected: `p_bcmp`/`p_bcmpi` (src `p_bemp`), `p_fcmp` (src `p_femp`),
`p_pow` (src fragment `p (…)1_POw`), `p_nmmona` (src duplicate `p_nmmon`).
- `p_altchk`, `p_dequed`, `p_sgcreate`, `p_sgopen`, `p_sgfind` are named in the
manual but their clean C signatures are not isolated in the source text; marked **[?]**.
- Version-gated functions annotated where the manual states a minimum EPOC version.
+725
View File
@@ -0,0 +1,725 @@
# Psion SIBO / Workabout MX — File System and DBF Database API Reference
Source: *PLIB Reference*, chapters **Files** (ch. 11) and **Database Files** (ch. 14), with
corroborating structural detail from *EPOC O/S System Services*, chapter 20 (*Database File
Management*). Everything below is drawn from those manuals; page-anchored citations are given as
`[PLIB p.NNN]` / `[SysSvc §20]`. OCR-garbled items are flagged explicitly.
> **OCR caveat.** The source is scanned OCR text. Identifiers written `p_open`, function-name
> casing, and hex constants have been normalised to their obvious intended form. Where a
> *signature* itself is corrupted or truncated in the source, this is called out inline. Do not
> treat a normalised name as a guarantee of exact header spelling — verify against `p_file.h` /
> `p_dbf.h` before compiling.
---
## Part 1 — The File System
### 1.1 The file server and file systems (nodes)
- All file operations are performed by a high-priority system process, the **file server**
(process name `SYS$FSRV`). A process must connect to it before use; the C startup module
normally does this automatically. Sending a message to the file server without connecting
panics with panic number 41. [PLIB p.117]
- The file server supports multiple **file systems**, also called **nodes**. `p_open` can list
them. Three were implemented at the time of writing [PLIB p.117]:
- `LOC::` — the local filing system, with the RAM drive `M:` and SSD drives `A:`, `B:`, …
(count depends on hardware).
- `REM::` — the remote filing system (present only while connected to a remote file server).
- `ROM::` — the ROM filing system for ROM-based files; normally invisible to the user; does
**not** support devices or directories.
- Within `LOC::` (and `REM::` when the remote end is a PC or another SIBO machine), the device
and directory structure is MSDOS-compatible. [PLIB p.117]
**Media types (SSDs).** RAM SSDs (battery-backed static RAM, block-structured, not buffered on
SIBO) and Flash SSDs (linked variable-length records). Overwriting or deleting on Flash consumes
space that is only reclaimed by reformatting; a byte can be physically overwritten with **no**
storage penalty only if the new value is derived from the old by clearing bits to zero. This is
the property the DBF layer exploits. [PLIB pp.118120, 195]
### 1.2 File specification structure
A full file specification has the form [PLIB p.120]:
```
<node><device><dir><name><ext>
```
Example: `LOC::B:\NOTES\OLD\PLANS.TPD`, where:
| Component | Meaning | Example |
|-----------|----------------------|-----------------|
| `<node>` | file system node | `LOC::` |
| `<device>`| device name | `B:` |
| `<dir>` | directory name | `\NOTES\OLD\` |
| `<name>` | file name | `PLANS` |
| `<ext>` | extension name | `.TPD` |
Rules [PLIB p.120]:
- A file specification never exceeds `P_FNAMESIZE` (**128**) bytes including the zero terminator.
- The `<node>` component is always `P_FSYSNAMESIZE` bytes long (excluding any zero terminator).
- Beyond `<node>` and the `P_FNAMESIZE` total, make **no** assumptions about component sizes; the
syntax of `<device>`/`<dir>`/`<name>`/`<ext>` is owned by the node's file-system code (foreign
remote systems can map onto this model, e.g. VMS or Mac paths). Use `p_fparse`/`p_chdir` rather
than manipulating specs by hand.
**Default path.** The file server stores a per-client default path (`<node><device><dir>`) plus a
system-wide default assigned to new clients. Manipulate with:
`p_setpth` / `p_setpthasync` (set this process's default), `p_getpth` (get it),
`p_getpthbyid` (get another process's), `p_setdefaultpath` (set the system-wide default).
[PLIB pp.121, 126]
**Parsing / directory manipulation.**
- `INT p_fparse(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *perk);` — builds a full spec into
`full` (reserve `P_FNAMESIZE` bytes; `P_FNAMESIZE` bytes are always written). Components are
taken from `name`, then `related` (may be `NULL`), then the default path, in that order of
precedence. Output is upper-cased. `perk` (may be `NULL`) receives a `P_FPARSE` struct with the
lengths of each component and a wildcard-flags byte (`P_PWILD_ANY`, `P_PWILD_NAME`,
`P_PWILD_EXT`). `f_fparse` is identical but calls `p_leave` on error instead of returning it;
`p_fparseasync` adds a trailing `WORD *stat`. [PLIB pp.123124]
- `INT p_chdir(TEXT *src, TEXT *outp, INT mode, TEXT *subdir);` — parse `src` and change its
directory per `mode`: `P_CD_ROOT`, `P_CD_PARENT`, or `P_CD_SUBDIR` (append zero-terminated
`subdir`). Reserve `P_FNAMESIZE` at `outp`. Async form `p_chdirasync`. [PLIB p.125]
### 1.3 `p_open` — modes, formats, and access flags
Channel-based file services all go through `p_open`:
```
INT p_open(VOID **ppfcb, TEXT *name, UINT mode);
```
On success returns 0 and writes the channel handle to `*ppfcb` (not written on failure). `name` is
parsed with a `NULL` related name. [PLIB p.142]
`mode` is a bitwise-OR of exactly **one open mode**, exactly **one format**, and any combination of
**access flags**.
#### Open modes (choose exactly one) [PLIB pp.142143]
| Mode | Behaviour |
|--------------|-----------|
| `P_FOPEN` | Open an existing file. `E_FILE_NXIST` if it does not exist. Normally used for read access. |
| `P_FCREATE` | Create a file that must **not** already exist. `E_FILE_EXIST` if it does. Requires `P_FUPDATE` for write. |
| `P_FREPLACE` | If the file exists, open and truncate to zero length; otherwise create it. Requires `P_FUPDATE` for write. |
| `P_FAPPEND` | Same as `P_FOPEN` but initial position is at end of file so the next write appends. `P_FRANDOM` not needed; `P_FUPDATE` needed for write. |
| `P_FUNIQUE` | Create a unique file, using the passed path as the related path; the unique name is written back to `name` (reserve `P_FNAMESIZE` bytes). `P_FUPDATE` not needed. |
#### Formats (choose exactly one) [PLIB pp.142, 146147; §list p.121]
| Format | Meaning |
|-------------------|---------|
| `P_FSTREAM` | Flat binary file. |
| `P_FSTREAM_TEXT` | Flat binary file, but declares the file is text; on `REM::` it makes the remote side present/parse CRLF-terminated records. Locally usually identical to `P_FSTREAM`; no penalty, potential gain on remote. Prefer this for self-processed text files. |
| `P_FTEXT` | Record-oriented text file. Implemented as a client-side layer (the `TXT:` device) over `P_FSTREAM_TEXT`; data is buffered in that layer, so flushing is needed. |
| `P_FDIR` | Open a directory-listing channel (list files/subdirectories). |
| `P_FDEVICE` | Open a device-listing channel (list devices of a node). |
| `P_FNODE` | Open a node-listing channel (list file systems). |
| `P_FFORMAT` | Open a device-format channel (`LOC::` only at time of writing; OR in `P_FLOWDENSITY` for dual-density low-density format). |
#### Access flags (combine as needed) [PLIB p.143]
| Flag | Meaning |
|-------------|---------|
| `P_FUPDATE` | Write access as well as read. Writing without it → `E_FILE_RDONLY`. |
| `P_FRANDOM` | Random access required; needed to use `p_seek`. Without it, `p_seek``E_FILE_INV`. Do not specify unless you will seek (the FS may optimise sequential-only access). |
| `P_FSHARE` | Do not block the file from being re-opened for **read** access. Without it, a later open → `E_FILE_LOCKED`. **Cannot** be combined with `P_FUPDATE` (shared *write* is unsupported). |
**Sharing rules:** any number of processes may open the same file for reading only, provided all
readers specify `P_FSHARE`. Multiple writers are never allowed. Once open for reading it may be
re-opened for reading but not writing; once open for writing it may not be re-opened at all.
[PLIB p.140]
**Examples** [PLIB p.140]:
```c
p_open(&fcb, "fred.dat", P_FSTREAM | P_FSHARE); /* read only */
p_open(&fcb, "fred.dat", P_FSTREAM | P_FUPDATE | P_FREPLACE | P_FRANDOM);/* writable */
```
Selected `p_open` errors: `E_GEN_NOMEMORY`, `E_GEN_ARG` (illegal flag combination),
`E_FILE_DEVICE`, `E_FILE_NOTREADY`, `E_FILE_EXIST`, `E_FILE_NXIST`, `E_FILE_ACCESS`,
`E_FILE_DIRFULL`, `E_FILE_PROTECT`, `E_FILE_FULL`, `E_FILE_LOCKED`, `E_FILE_DIR`, plus any
`p_fparse` error. [PLIB pp.143144]
### 1.4 Reading and writing streams
```
INT p_read (VOID *pfcb, VOID *buf, UINT len);
INT p_write(VOID *pfcb, VOID *buf, UINT len);
INT p_close(VOID *pfcb);
```
- `p_read` reads `len` bytes (or the bytes remaining before EOF, whichever is smaller) from the
current position; returns the number of bytes read. At EOF, reads 0 bytes and returns
`E_FILE_EOF`. `len` must not exceed `P_FMAXSSIZE` (**16K**). Most efficient in multiples of
`P_FBLKSIZE` (**512**) on 512-byte boundaries. Other errors: `E_FILE_ABORT`, `E_FILE_READ`.
[PLIB p.144]
- `p_write` writes `len` bytes at the current position; increments position by bytes written;
returns 0 on success. `len``P_FMAXSSIZE`. On Flash you may overwrite a single byte (`len==1`)
if the new byte only clears bits (then the modification date is not changed). Errors:
`E_FILE_FULL`, `E_FILE_RDONLY`, `E_FILE_ABORT`, `E_FILE_WRITE`. [PLIB p.144]
- `p_close(NULL)` is harmless (returns 0). Close may perform a final buffered write and a date
update, so it can return `p_write`/`p_fdate` errors — but it always closes the channel. To
handle failures cleanly, `p_iow(pfcb, P_FFLUSH)` first. `LOC::` on SIBO does not buffer written
data; EPOC-on-PC does. [PLIB p.143]
**`p_iow` control operations** on a binary/text channel [PLIB pp.145146]:
- `INT p_iow(VOID *pfcb, P_FFLUSH);` — flush buffered data and write the modification date.
- `INT p_iow(VOID *pfcb, P_FSETEOF, ULONG *peof);` — set the logical EOF to `*peof` (requires
`P_FUPDATE`). Extending pre-allocates storage on block devices; truncating also reduces the
current position if needed.
- `INT p_iow(VOID *pfcb, P_FCANCEL);` — cancel pending async requests (not actively supported; a
no-op even when a request is pending — simulate a cancel instead).
### 1.5 Seeking
**Binary / stream:**
```
INT p_seek(VOID *pfcb, INT sense, LONG *ppos);
```
`sense` is one of [PLIB p.144]:
| `sense` | Meaning |
|-----------|---------|
| `P_FABS` | set position to `*ppos` |
| `P_FEND` | set position to `*ppos` relative to end-of-file |
| `P_FCUR` | set position to `*ppos` relative to current position |
On success the new position is written back to `*ppos` and 0 returned. `E_FILE_INV` if the channel
was not opened with `P_FRANDOM`. A negative resulting position clamps to the start; beyond EOF
clamps to EOF (use `P_FSETEOF` to extend). Setting `*ppos=0` with `P_FCUR` senses the current
position; with `P_FEND` senses the file length. `f_seek` is the `p_leave`-on-error variant. On
`P_FSTREAM_TEXT` remote channels, only "seek to 0 (`P_FABS`)" and "seek to end (`P_FEND`, rel 0)"
are guaranteed. [PLIB pp.144145]
**Record-oriented text (`P_FTEXT`):** `p_seek` sets the **read** position only (writes always go to
EOF), and `sense` is different [PLIB pp.149]:
| `sense` | Meaning |
|--------------|---------|
| `P_FREWIND` | position to the first record (`ppos` ignored) |
| `P_FRSENSE` | get the position of the last record read/written |
| `P_FRSET` | set the record position previously got with `P_FRSENSE` |
Requires `P_FRANDOM` (else `E_FILE_INV`). Some remote systems may not support `P_FRSET`/`P_FRSENSE`.
### 1.6 Text records (`P_FTEXT`)
Convention: records are terminated by CRLF (CR = 13, LF = 10); a file may optionally end with SUB
(26). On read, the parser also accepts CR, LF, or LFCR as a terminator and treats a SUB as EOF; on
write it appends CRLF only (no SUB). Record content must not exceed `P_FMAXRSIZE` (**256**) bytes
and must not contain CR, LF, or SUB. [PLIB pp.146148]
- `p_read` returns the record length (bytes written to `buf`), positioning to the next record. If
`len` < record length, the first `len` bytes are read and `E_FILE_RECORD` returned (still
advances). Returns 0 on a zero-length record; `E_FILE_EOF` past the last record. [PLIB p.148]
- `p_write` writes a record of `len` bytes (0 … `P_FMAXRSIZE`); always appended at EOF; must not
contain delimiters. Errors incl. `E_FILE_RECORD` if oversize. [PLIB p.149]
### 1.7 Directory, device, and node listing
All three use the open → repeated `p_iow(..., P_FREAD, ...)` until `E_FILE_EOF` → close pattern.
- **Nodes:** `p_open(&ncb, "FIL:" or NULL, P_FNODE)`; each `p_iow(ncb, P_FREAD, buf, pinfo)`
writes the next node name (`buf` capacity `P_FSYSNAMESIZE+1`, i.e. 6). Optional `pinfo`
(`P_NINFO*`) gets the same info as `p_ninfo`. [PLIB p.127]
- **Devices:** `p_open(&dcb, node-name, P_FDEVICE)`; `E_GEN_FSYS` if the node is bad,
`E_GEN_NSUP` if the node has no devices (e.g. `ROM::`). Each `p_iow(P_FREAD)` writes the next
device name (`buf` capacity `P_FNAMESIZE`); trailing arg `NULL`. [PLIB p.128]
- **Files:** `p_open(&dcb, name, P_FDIR)`; `name` is parsed with a wildcard related name (`*.*`),
so a name of `""` lists the current directory. Each `p_iow(P_FREAD, buf, pinfo)` writes the next
matching file name (excluding node/device/dir; `buf` capacity `P_FNAMESIZE`). Optional
`pinfo` (`P_INFO*`) carries per-file info. A root directory of a PC-based device may return a
volume-name entry with `P_FAVOLUME` set. [PLIB pp.133134]
`P_INFO` (from `p_file.h`) [PLIB p.134]:
```c
typedef struct {
UWORD version;
UWORD status; /* status bits */
ULONG size; /* size of the file in bytes (end-of-file position) */
ULONG modst; /* system time of last modification (secs since 1970-01-01) */
UBYTE spare[4];
} P_INFO;
```
`status` bit fields: `P_FAWRITE` (not read-only), `P_FAMOD` (modified), `P_FAHIDDEN`,
`P_FASYSTEM`, `P_FADIR` (directory file), `P_FAVOLUME` (volume-name directory), `P_FATEXT`
(recognised text file; `LOC::` cannot recognise text files). [PLIB p.134]
### 1.8 File / device / node information and non-channel operations
- `INT p_finfo(TEXT *name, P_INFO *pinfo);` — info on one file/directory (same fields as a `P_FDIR`
read). Good atomic existence check (`E_FILE_NXIST` if absent). Async `p_finfoasync`. [PLIB p.135]
- `INT p_testpth(TEXT *dname);` — returns 0 if the directory component exists. [PLIB p.135]
- `INT p_ninfo(TEXT *node, P_NINFO *pninfo);` — node info. `P_NINFO { UWORD version; UWORD type;
UWORD formattable; UBYTE spare[26]; }`. `type` is `P_FSYSTYPE_FLAT` or `P_FSYSTYPE_HIER`.
[PLIB pp.127128]
- `INT p_dinfo(TEXT *dname, P_DINFO *pdinfo);` — device + mounted-medium info.
`P_DINFO { UWORD version; UWORD mediatype; UWORD removable; ULONG size; ULONG free;
UBYTE name[P_VOLUMENAME]; WORD batterystate; UBYTE spare[16]; }`. Low byte of `mediatype`:
`P_FMEDIA_UNKNOWN|FLOPPY|HARDDISK|RAM|FLASH|ROM|WRITEPROTECTED`; high byte flags incl.
`P_FMEDIA_COMPRESSIBLE` (worth compressing out deleted records — **not** set for Flash),
`P_FMEDIA_DYNAMIC`, `P_FMEDIA_INTERNAL`, `P_FMEDIA_DUAL_DENSITY`, `P_FMEDIA_FORMATTABLE`.
[PLIB pp.129130]
- Other non-channel calls (each with an `…async` twin): `p_rename`, `p_delete` (directory must be
empty), `p_mkdir` (creates intermediate dirs), `p_sfstat` (set attributes; with `P_FAVOLUME` set
in the mask, sets/deletes the volume label), `p_fdate` (set modification date, ≥ 1980-01-01).
[PLIB pp.136139]
- Formatting: open with `P_FFORMAT` then repeatedly `p_read` (first read yields a `UWORD` total
count, subsequent reads step the format, `E_FILE_EOF` when done). Aborting early corrupts the
medium. [PLIB pp.131132]
---
## Part 2 — The DBF Database API
### 2.1 The model
A **database file (DBF)** is a binary file of **typed, variable-length records**. Used by MC Diary,
Series 3 Database, and OPL data files. DBFs are **Flash-friendly**: records can be appended,
deleted, or replaced in place on a Flash SSD without rewriting the whole file. [PLIB p.195]
Key model properties [PLIB pp.195197, 204207]:
- **Record type 0 = deleted.** Deleting a record only overwrites its 4-bit type field with zero
(which merely clears bits — legal on Flash). Therefore **deleting does not shrink the file.**
- **Append-only; updates = erase + append.** Updating a record deletes the original and appends
the modified version, so an update **always moves the record to the end of the file**.
- **Reclaiming space:** `DbfCompress` (only on a *compressible* medium — not Flash) or copy the
file record-by-record with `DbfCopyFile` (deleted records are not copied).
- **Sparse index** (optional): one 4-byte address per **sixteenth** record, held in a *separate
segment* (`DBF$nnnn.INX`, `nnnn` a 4-hex-digit number from the channel) so it does not consume
the app's data space. Enables fast random access and fast backward scans; adjusted on
append/delete so it always points to every 16th record.
- **Read-ahead buffer:** each file-server read fills a caller-supplied buffer (typically 4K),
usually pulling in many records; a read for a record already in the buffer just locates it.
**Most DBF services may overwrite the buffer.** The next read after the buffer is disturbed
re-reads the whole buffer (a performance hit), so any direct modification of the buffer that is
*not* done via a DBF service must be followed by `DbfTrash`. Services guaranteed **not** to
alter the buffer: `DbfFlush`, `DbfVersion`, `DbfAppend`, `DbfSense`, `DbfCount`. [PLIB p.196]
**Record limit:** max **65534** records *of any one visible type*, numbered 0…65533. Since only one
type is visible per open, a file may hold more in aggregate. A file with more than the max (of the
visible type) is logically truncated to the max on open. [PLIB p.197]
**End-of-file record.** Reading past the last record returns `E_FILE_EOF`; the current record
number (per `DbfSense`) then becomes *last record + 1* — the fictitious **end-of-file record**.
Reading before the first record (`DbfBackRead`/`DbfFindRead` backwards) also gives `E_FILE_EOF`
with current record number 0. If the file has no records, `DbfSense` always returns 0. Services
that operate on the current record (`DbfEraseRead`, `DbfUpdate`, …) do nothing and return
`E_FILE_EOF` when positioned on the EOF record. [PLIB p.197]
### 2.2 The file header
DBFs begin with a **22-byte** standard header [PLIB p.196; SysSvc §20]:
| Byte offset | Contents |
|-------------|----------|
| 015 | Zero-terminated file signature (all 16 bytes are verified — pad with trailing zeros). |
| 16, 17 | Version of DBF software used to produce the file. |
| 18, 19 | Offset from start of file to the first record. |
| 20, 21 | Minimum version of DBF software required. |
The first-record offset allows an **extended header** (application-specific data after the standard
header). If none, the value is **22**. The first record is always the type-2 field information
record. Version-number format: see `DbfVersion`. [PLIB p.196; SysSvc §20]
### 2.3 Records and the record header word
In memory, a record is a `DbfRecord` (`p_dbf.h`) [PLIB p.196]:
```c
typedef struct {
UWORD header; /* record header word */
UBYTE data[2]; /* data to be written... (variable length in practice) */
} DbfRecord;
```
The **header word**: top **4 bits** = record **type** (015); low **12 bits** = record **length**.
Maximum record length is **4094** bytes (one less than the theoretical 0xFFF = 4095, so a 4094-byte
record plus its 2-byte header fits a 4096-byte buffer). [PLIB p.196; SysSvc §20]
**Record types** [PLIB pp.196197; SysSvc §20]:
| Type | Meaning |
|-------|---------|
| 0 | Deleted record — ignored by all DBF services; never copied by `DbfCopyFile`. |
| 1 | Standard data record (fields per the field information record). Usually the only visible type. |
| 2 | **Field information record (FIR)** — must exist and be the first record; later type-2 records ignored. |
| 3 | Descriptive record — optional, file-wide app data (see `DbfDescRecordRead/Write`). |
| 47 | App-specific: copied to a **new** file, **not** appended to an existing file, by `DbfCopyFile`. |
| 813 | App-specific: **both** copied to a new file and appended to an existing file (merged) by `DbfCopyFile`. |
| 14 | Reserved for voice records. |
| 15 | Reserved for internal use — do not use. |
### 2.4 The Field Information Record (FIR) and field types
The FIR (type 2) holds up to **32 bytes**, one per field, giving each field's type [PLIB p.197;
SysSvc §20]:
| Byte value | Field type |
|------------|------------|
| 0 | Word |
| 1 | Long |
| 2 | Double |
| 3 | String |
| 4255 | Reserved |
So a data record has at most 32 fields — **with one exception:** a record that contains **only
string fields** is not bound by the 32-field limit and may hold any number of fields, subject to
the 4094-byte record cap. Records may contain **fewer** fields than the FIR lists, provided only
*trailing* fields are omitted; records with **more** fields than the FIR are assumed to have the
extra ones as string fields. Only `DbfFindRead`/`DbfFindReadField` assume records match the FIR.
[PLIB p.197]
### 2.5 String fields (leading byte count) and continuation sub-fields
A string field is **leading byte-counted text**, so a normal string field holds at most **255**
characters. Longer strings use **continuation sub-fields**: the first 254 characters plus a
terminating byte `0x14`, stored in a string field whose count byte is **255**; the `0x14` +
count-255 signals that the immediately following string field continues the text. This chains
indefinitely, subject only to the 4094-byte record limit. [PLIB p.197]
### 2.6 The structs
`DbfHeader` (`p_dbf.h`) — passed to open [PLIB p.199]:
```c
typedef struct {
UBYTE fileType[DbfHeaderNameSize]; /* 16-byte file signature */
UWORD createVersion; /* software version used to create the file */
UWORD dataStart; /* offset in file of first record (22 if no ext header) */
UWORD needVersion; /* minimum software version needed to handle this file */
UWORD firHeader; /* header word for the field information record */
UBYTE fir[DbfMaxFirLength]; /* the field information record bytes */
} DbfHeader;
```
- When **creating/replacing**, pre-fill *all* elements (header + FIR). There is no gap between the
header and FIR in the struct even when an extended header is used; the *file* leaves a gap of
`dataStart 22` bytes for it.
- When **opening existing**, pre-fill only `fileType` (the signature); the rest is filled from the
file. All 16 signature bytes are verified — mismatch → `E_FILE_INVALID`.
- `DbfHeaderNameSize` = 16 (implied by "16-byte file signature"). `DbfMaxFirLength` is the FIR
capacity; the FIR maximum length is 32. *(Exact numeric value of `DbfMaxFirLength` not stated
verbatim in the source beyond the max-32 rule — verify in `p_dbf.h`.)*
`DbfOpenArgs` (`p_dbf.h`) — for `DbfQuickOpen` [PLIB p.200]:
```c
typedef struct {
VOID **pFcb;
UBYTE *fName;
UINT mode;
DbfHeader *pHead;
} DbfOpenArgs;
```
`DbfRecord` — see §2.3.
### 2.7 The DBF functions
Every DBF function calls `p_panic` if `pFcb` is not a valid DBF channel from `DbfOpen`/
`DbfQuickOpen`; that clause is omitted per-entry below. Unless noted "may be used on a DBF opened
without an index", the *without-index* behaviour is called out where the manual specifies it.
#### Opening / closing / flushing
- `INT DbfOpen(INT *pstate, VOID **pFcb, TEXT *fName, UINT mode, DbfHeader *pHead, UBYTE *pbuffer, UINT len, UINT type);`
Open a DBF channel. `mode` = exactly one of `P_FOPEN`/`P_FCREATE`/`P_FREPLACE`/`P_FAPPEND`/
`P_FUNIQUE`, optionally OR'd with `P_FUPDATE` and/or `P_FSHARE` (other required stream flags are
supplied automatically; `P_FOPEN` and `P_FAPPEND` are treated identically). `pbuffer`/`len` is
the caller's read-ahead buffer: **len must be 512…16384** (else `E_FILE_RECORD`), and must be at
least as large as the largest record (4096 is guaranteed sufficient). Only records of type `type`
are visible (normally 1; may be 414; results undefined for 0/2/3/>14). `*pstate` selects the
open strategy [PLIB pp.198199]:
- `DbfStateDisabled` — open with a full sparse index; returns only when the index is fully built
(may take a while).
- `DbfStateOpenNoIndex` — open **without** an index; returns fast, but some services are then
disallowed (see each function).
- `DbfStateStart` — open with a sparse index incrementally: call `DbfOpen` repeatedly, feeding
back `*pstate` each time, until `*pstate` becomes `DbfStateStart` again *(as printed — the
source states the loop terminates when the written-back value is `DbfStateStart`; this reads
like an OCR/spec inconsistency, likely intended to be a distinct "finished" state such as
`DbfStateDisabled`/`DbfStateEnd`. **Flag: verify the terminating state in `p_dbf.h`.**)*.
After a successful open the current record is **0**, so `DbfNextRead` reads record 1 and
`DbfEraseRead` erases record 0; use `DbfFirstRead` to read record 0. Errors: those of
`p_open(P_FSTREAM)`, `p_seek`, `p_read`, plus `E_FILE_INVALID` (bad signature / bad FIR),
`E_FILE_RECORD` (bad buffer length). [PLIB p.199]
- `INT DbfQuickOpen(INT *pstate, DbfOpenArgs *pargs, UBYTE *pbuffer, UINT len, UINT type);`
As `DbfOpen` but `pFcb`/`fName`/`mode`/`pHead` are bundled in `DbfOpenArgs`. **Preferred** over
`DbfOpen` (more efficient, shorter code); `DbfOpen` retained for compatibility. [PLIB p.200]
- `INT DbfClose(VOID *pFcb);` — close the file (returns as `p_close`); channel is closed even on
error. May be used without an index. [PLIB p.200]
- `INT DbfFlush(VOID *pFcb);` — flush all buffers so modified data is written (returns as
`p_write`). Does not alter the buffer. May be used without an index. [PLIB p.200]
- `VOID DbfTrash(VOID *pFcb);` — tell the DBF layer the read-ahead buffer was overwritten by the
caller and can no longer be relied on. May be used without an index. [PLIB p.200]
- `INT DbfCopyDown(VOID *pFcb, UINT offset);` — copy the record at `offset` in the buffer to the
start of the buffer (and flag the buffer invalid, so no `DbfTrash` needed); returns the record
length. `offset` must be one previously returned by a read service and the caller must not have
written to the buffer since. May be used without an index. [PLIB p.201]
#### Whole-file operations
- `INT DbfCompress(UINT *pstate, VOID *pFcb);` — reclaim space from deleted records **if** the
medium is compressible (otherwise a no-op that still returns 0). After a real compress the
current record is the EOF record. `*pstate` = `DbfStateDisabled` (blocking) or `DbfStateStart`
(incremental loop, terminating as written back). **Should not** be used without an index.
[PLIB p.201]
- `INT DbfCopyFile(UINT *pstate, VOID *pFcb, TEXT *pTargetName, UINT targetMode, UINT type, INT dir);`
Copy non-deleted records between the current file and `pTargetName`. `targetMode` = same options
as open (target always opened without an index; `P_FUNIQUE` writes back the unique name).
`type` selects a single type (usually 1) or `DbfRecordTypeAll` for all types. `dir` is
`DbfCopyFromHandle` (current → target; target new file with `P_FCREATE`/`P_FREPLACE`/`P_FUNIQUE`,
or append with `P_FOPEN`/`P_FAPPEND`) or `DbfCopyToHandle` (target → current; target sensibly
`P_FOPEN`). Copying to a **new** file always copies the FIR (type 2) and the file header
(incl. extended header) regardless of `type`; **appending** never copies types 27. Appending
requires matching signatures and compatible FIRs (identical, or both string-only) else
`E_FILE_INVALID`. `*pstate` supports `DbfStateDisabled`, `DbfStateStart` (est. calls =
file size / buffer size + 2), and `DbfStateCopyAbort` (abort an in-progress `DbfStateStart`
copy). May be used without an index. **Warning:** appending can exceed 65534 records with no
error. [PLIB pp.201202]
- `INT DbfFileSize(VOID *pFcb, ULONG *pSize);` — write the open file's size to `*pSize`. May be
used without an index. [PLIB p.202]
- `UINT DbfVersion(VOID);` — DBF software version as hex `xyyF`: `x` = major (4 bits), `yy` = minor
(8 bits), `F` = release type A/B/F (Alpha/Beta/Final, 4 bits). E.g. `0x110F` → 1.10F. Only the
major number gates whether a file can be handled. May be used without an index. [PLIB p.203]
#### Extended header and descriptive record
- `INT DbfExtHeaderRead(UINT cont, VOID *pFcb, VOID *buf, UINT len);` — read up to `len` bytes of
the extended header; returns bytes read. `cont`: 0 = initial read (resets to start of ext
header); 1 = continue; use 0 if reading the whole thing in one call. `E_FILE_EOF` at end. May be
used without an index. [PLIB p.202]
- `INT DbfExtHeaderWrite(UINT cont, VOID *pFcb, VOID *buf, UINT len);` — symmetric writer. May be
used without an index. [PLIB pp.202203]
- `INT DbfDescRecordRead(VOID *pFcb);` — read the descriptive record to offset 0 of the read-ahead
buffer; returns its length, or `E_FILE_EOF` if none, or `E_FILE_INVALID` if the file was opened
without an index. Sub-records use the same word-header format as main records; ignore (do not
delete) unrecognised sub-record types. [PLIB p.203]
- `INT DbfDescRecordWrite(VOID *pFcb, UINT len);` — write a descriptive record from a `DbfRecord`
at buffer offset 0 (content starts at offset 2; the leading 2 bytes form the header and are not
counted in `len`). Any existing descriptive record is erased first (max one per file); `len==0`
just erases it. `E_FILE_INVALID` if opened without an index. [PLIB p.203]
#### Reading records
All read services return the **record data length** (excluding the 2-byte header) and write the
buffer **offset of the DbfRecord** (including its header) to `*pOffset`. The read record becomes
the current record. On `E_FILE_EOF`, `*pOffset` is invalid.
- `INT DbfAbsRead(VOID *pFcb, UINT recnum, UWORD *pOffset);` — seek to and read record `recnum`.
`E_FILE_EOF` if `recnum` > last record (current → EOF record). May be used without an index.
[PLIB p.204]
- `INT DbfAbsReadSense(VOID *pFcb, UINT recnum, UWORD *pOffset, ULONG *pPos);` — as `DbfAbsRead`
but also writes the file position of the record header to `*pPos`. May be used without an index.
[PLIB p.204]
- `INT DbfNextRead(VOID *pFcb, UWORD *pOffset);` — read the next record. `E_FILE_EOF` if already on
the last record / no records (current → EOF record). May be used without an index. [PLIB p.204]
- `INT DbfBackRead(VOID *pFcb, UWORD *pOffset);` — read the previous record. `E_FILE_EOF` if on the
first record / no records (current → record 0). May be used without an index. [PLIB p.204]
- `INT DbfFirstRead(VOID *pFcb, UWORD *pOffset);` — read the first record. `E_FILE_EOF` if no
records of the current type (current → 0 = EOF record). May be used without an index. [PLIB p.205]
- `INT DbfLastRead(VOID *pFcb, UWORD *pOffset);` — read the last record. `E_FILE_EOF` if none.
**Should not** be used without an index. [PLIB p.205]
#### Appending / erasing / updating
- `INT DbfAppend(VOID *pFcb, UINT len);` — append a record of the current type and length `len`
(data only) to EOF and make it current; returns 0. The record must be at the **start** of the
read-ahead buffer as a `DbfRecord` including its 2-byte header; `DbfAppend` builds the type/length
header from those two bytes (not counted in `len`). `E_GEN_OVER` if already 65534 records of the
type; `E_FILE_RECORD` if total (data + 2) exceeds the buffer. Does not alter the buffer.
**Should not** be used without an index. [PLIB p.205]
- `INT DbfEraseRead(INT *pstate, VOID *pFcb, UWORD *pOffset);` — erase the current record and read
the following one (becomes current); returns its length or negative. Two `E_FILE_EOF` cases:
(a) already on the EOF record / no records → does nothing; (b) current was the last record → it
is erased and current becomes the EOF record. Distinguish via `DbfSense`+`DbfCount`, or by
comparing `DbfCount` before/after. `*pstate` = `DbfStateDisabled` / `DbfStateStart` (incremental).
**Should not** be used without an index. [PLIB p.206]
- `INT DbfUpdate(INT *pstate, VOID *pFcb, UINT len);` — erase the current record and append a new
one of length `len` from the read-ahead buffer, making it current. The new record must be a
`DbfRecord` at the start of the buffer (leading 2 bytes = header, not counted in `len`). The old
record is **not** erased until the new one is successfully appended. `E_FILE_EOF` (no-op) if no
records of the current type or if on the EOF record. `*pstate` = `DbfStateDisabled` /
`DbfStateStart`. **Should not** be used without an index. [PLIB p.206]
*(Note: the manual's prose here says "DbfAppend uses these two bytes" — clearly referring to the
same header-construction mechanism.)*
#### Sensing / counting
- `UINT DbfSense(VOID *pFcb);` — return the current record number (the EOF record number — 0 if
empty, else count+1 — if a preceding call returned `E_FILE_EOF`). Does not alter the buffer. May
be used without an index. [PLIB p.209]
- `UINT DbfCount(VOID *pFcb);` — return the number of records of the currently visible type without
changing the current record. Does not alter the buffer. **Should not** be used without an index.
[PLIB p.209]
#### Finding by content
- `INT DbfFindRead(UINT *pstate, VOID *pFcb, VOID *pBuffer, UINT len, UINT findMode, UINT nStrings, UWORD *pOffset);`
Match wildcard text at `pBuffer` of length `len` (≤ 255) against the first `nStrings` **string
fields** of each record, starting at the current record; returns the matching record's data
length or a negative error. Equivalent to `DbfFindReadField` with `startStr = 0`. `nStrings ==
DbfFindAllStrings` searches all string fields to end-of-record. The FIR is used to type the first
32 fields; fields beyond that are assumed strings. On match, that record becomes current and its
buffer offset is written to `*pOffset`; on no match, `E_FILE_EOF` and current becomes the first
record (backward search) or the EOF record (forward search). May be used without an index
**except** `DbfFindLast` (unpredictable). Panics if `findMode` is malformed. [PLIB pp.208209]
- `DbfFindReadField(...)` — same as `DbfFindRead` but with an extra `startStr` argument: match
starts at string field number `startStr` (0 = first string field). **Available only in EPOC
≥ 3.18.**
**OCR FLAG:** the signature is **garbled/truncated** in the source. The printed fragment reads:
`… UINT nStrings, UWORD *pOffset, UINT startStr);` (line lacks the return type, `pstate`, `pFcb`,
`pBuffer`, `len`, `findMode`). By analogy with `DbfFindRead` the full signature is almost
certainly:
```c
/* RECONSTRUCTED — verify against p_dbf.h before use */
INT DbfFindReadField(UINT *pstate, VOID *pFcb, VOID *pBuffer, UINT len,
UINT findMode, UINT nStrings, UWORD *pOffset, UINT startStr);
```
[PLIB pp.207208]
**`findMode`** is an OR of three parts [PLIB pp.207208]:
1. **Max match length** in any one string field (0…255; 255 = no truncation) occupies the low part
of `findMode`.
2. **Direction / start**: `DbfFindForwards`, `DbfFindBackwards`, `DbfFindFirst`, `DbfFindLast`.
3. **Case**: `DbfFindCaseIndependent` or `DbfFindCaseDependent`.
**Finding across continuation sub-fields** (only needed if strings may exceed 255 chars): OR
`0x1400` into `len` (whose base value cannot exceed 255); set the length component of `findMode` to
255 and OR in `0x4000`; only case-independent matching is allowed (OR `DbfFindCaseIndependent`);
then OR in the direction flag as usual. [PLIB p.208]
> **No per-field getter/setter exists.** The DBF API deals in whole records only. There is no
> function to read or write an individual Word/Long/Double/String field. The application must
> **pack and unpack field bytes by hand** into/out of the `DbfRecord.data` area, following the FIR
> field order and the string leading-byte-count convention. The find services are the only ones
> that interpret field structure, and only for string matching.
### 2.8 Error names (DBF)
`E_FILE_EOF` (past end / before start), `E_FILE_INVALID` (bad signature / FIR / no-index misuse),
`E_FILE_RECORD` (bad buffer length or record too big for buffer), `E_GEN_OVER` (65534-record limit
hit on append). `DbfClose`/`DbfFlush` propagate `p_close`/`p_write` errors; most read services
propagate `p_seek`/`p_read` errors. [PLIB pp.199, 205206] The System Services manual names the
same conditions `EofErr`, `InvalidFileErr`, and the panics `PanicDbf1` (bad handle) / `PanicDbf2`
(bad offset). [SysSvc §20]
---
## Part 3 — Worked recipe: create schema → append → read back
Assembled strictly from the manual's prose (PLIB ch. 14). Field packing is done by hand because
there is no per-field API. Illustrative — variable names/details are the author's; the API calls,
struct layout, and offsets are per the manual.
**Schema for the example:** two fields — field 0 = Long (FIR byte `1`), field 1 = String (FIR byte
`3`).
### Step 1 — Create the file with header + FIR
Pre-fill a `DbfHeader` completely (creating/replacing requires the full header **and** FIR;
`p.199`). The FIR is itself a type-2 record, so `firHeader` is a record header word: top 4 bits =
type 2, low 12 bits = FIR length (here 2). Provide a read-ahead buffer of 4096 (guaranteed
sufficient; `p.199`) and open with visible `type = 1`.
```c
#include <p_dbf.h>
UBYTE buf[4096]; /* read-ahead buffer, 512..16384; >= largest record */
VOID *fcb;
INT state;
DbfHeader h;
DbfOpenArgs args;
/* --- header --- */
p_bfill(&h.fileType[0], DbfHeaderNameSize, 0); /* zero the 16-byte signature */
p_scpy(&h.fileType[0], "MYAPPDatabase"); /* pad remainder stays 0 (p.196) */
h.createVersion = DbfVersion(); /* p.203 */
h.dataStart = 22; /* no extended header (p.196) */
h.needVersion = DbfVersion();
/* --- FIR (type 2, length 2): field0=Long(1), field1=String(3) --- */
h.firHeader = (2 << 12) | 2; /* top 4 bits type, low 12 len */
h.fir[0] = 1; /* Long (p.197) */
h.fir[1] = 3; /* String (p.197) */
args.pFcb = &fcb;
args.fName = (UBYTE *)"MYDATA.DBF";
args.mode = P_FCREATE | P_FUPDATE; /* create new, writable (Files ch)*/
args.pHead = &h;
state = DbfStateDisabled; /* build index, blocking (p.199) */
if (DbfQuickOpen(&state, &args, &buf[0], sizeof(buf), 1) < 0)
/* handle error (p_fparse / p_open / E_FILE_* errors) */;
```
### Step 2 — Pack a record by hand and append it
A record to be appended must sit at the **start of the buffer** as a `DbfRecord`: 2-byte header
then data; `DbfAppend` builds the header from those two bytes and `len` is the **data** length only
(`p.205`). Pack the Long field (its byte layout is the app's responsibility), then the String field
as a leading count byte followed by its characters (`p.197`).
```c
DbfRecord *rec = (DbfRecord *)&buf[0];
UBYTE *d = &rec->data[0]; /* data starts at buffer offset 2 */
UINT len = 0;
LONG idNum = 42;
TEXT *nameStr = "Widget";
UINT nameLen = p_slen(nameStr); /* <= 255 for a plain string field */
/* field 0: Long (4 bytes, app-defined byte order) */
p_bcpy(&d[len], &idNum, sizeof(LONG)); len += sizeof(LONG);
/* field 1: String = leading byte count + chars (p.197) */
d[len++] = (UBYTE)nameLen;
p_bcpy(&d[len], nameStr, nameLen); len += nameLen;
/* header bytes are set by DbfAppend from rec->header; type is the current type (1) */
if (DbfAppend(fcb, len) < 0) /* appends, becomes current record (p.205) */
/* handle E_GEN_OVER / E_FILE_RECORD / p_write errors */;
```
### Step 3 — Read the record back and unpack
After open the current record is 0 (`p.199`). Reads return the **data length** and write the
`DbfRecord` **offset** (header included) into `*pOffset`. Read the first record, then unpack in FIR
order. (`DbfCopyDown` first if you intend to edit in the buffer — `p.201`/`SysSvc §20`.)
```c
UWORD off;
INT rlen = DbfFirstRead(fcb, &off); /* read record 0 (p.205) */
if (rlen >= 0) {
DbfRecord *r = (DbfRecord *)&buf[off];
UBYTE *p = &r->data[0];
LONG gotId;
UBYTE slen;
TEXT gotName[256];
p_bcpy(&gotId, &p[0], sizeof(LONG)); /* field 0: Long */
p += sizeof(LONG);
slen = *p++; /* field 1: count byte, then chars (p.197) */
p_bcpy(&gotName[0], p, slen);
gotName[slen] = 0;
/* gotId == 42, gotName == "Widget" */
}
DbfFlush(fcb); /* ensure written data is on the medium (p.200) */
DbfClose(fcb); /* p.200 */
```
Notes tied to the model:
- To **update** the record, pack a new `DbfRecord` at buffer offset 0 and call
`DbfUpdate(&state, fcb, len)`; the record is deleted and re-appended at EOF (`p.206`).
- To **delete**, position on it (e.g. `DbfAbsRead`) and call `DbfEraseRead(&state, fcb, &off)`
(`p.206`); the file does not shrink until `DbfCompress` (non-Flash) or a `DbfCopyFile` rebuild
(`p.195`).
- `DbfFindRead` / `DbfFindReadField` can locate a record by matching a wildcard against its string
fields (`p.207`209); numeric fields are not searchable by these services.
+480
View File
@@ -0,0 +1,480 @@
# Psion SIBO / Workabout MX — User-Interface Programming
**The Window Server and the Console**
Reference notes drawn from the SIBO 'C' SDK *Window Server Reference* (v2.30,
March 1999) and the *PLIB Reference* (console functions). Everything below is
sourced from those two manuals; section/line references are given as
`[wserv]` and `[plib]`. Anything not backed by the manuals is explicitly
flagged as **reverse-engineered** or **uncertain**.
> **Machine-type note.** The manuals cover the HC, MC, S3, S3a and Workabout.
> The **Workabout** is reported by the window server as machine type
> `WS_TYPE_S3C` (i.e. it shares the "Series 3c" identity), and in
> compatibility mode its `version_id` is `WS_TYPE_S3C | WS_VERSION_4`
> `[wserv 7288-7291]`. The manuals do **not** name a "Workabout MX"
> variant; MX-specific behaviour (notably the barcode scan key, below) is
> **not documented** and is marked as reverse-engineered where it appears.
---
## 1. The Window Server model
The window server is a **system process** that provides shared access to the
screen and the keyboard (and a pointing device, where one exists) `[plib
1295-1296]`. Application processes talk to it as **clients** through the
**WLIB** library — a thin C shell over ROM-based code reached by software
interrupts `[plib 1338-1339]`.
### 1.1 Clients, foreground and background
- Of all the clients, exactly **one is the foreground client**; all others are
background clients. The **foreground client is the one that receives
keyboard input** `[wserv 1906-1910]`.
- On the small-screen machines (HC, S3, S3a, Workabout) the windows of
background clients are **not visible at all**; only the foreground client's
windows are shown, in front `[wserv 1918-1921]`.
- On the **MC** (large screen) windows of several clients can be visible at
once, and a client can *attach* to another `[wserv 1330-1333]`.
- The server keeps clients in a **front-to-back task order**: position 0 is the
foreground; position 1 is the frontmost background task, and so on `[wserv
1968-1971]`.
- A client can move itself or another client between foreground and background
with `wClientPosition` `[wserv 1983-1984]`.
### 1.2 The shared screen and keyboard
Drawing goes into a **hierarchical system of overlapping windows**; all drawing
is clipped to visible areas, and the server issues **redraw events** telling a
client which areas need repainting `[plib 1318-1323]`. Keyboard input is routed
to the foreground client, except for keys the server processes itself (task
switch, pause) or keys a client has **captured** with `wCaptureKey` so that it
receives them whether foreground or not `[wserv 1911-1915, 6283-6293]`.
### 1.3 Connecting
A process must connect before using the server, via `wConnect` (or a wrapper
such as `wStartup`) `[wserv 1371-1372]`.
```c
VOID wConnect(WSERV_SPEC *pwserv_spec, VOID *pnws_handle, UINT flags);
```
`[wserv 7182-7183]`
- `flags` combines: `W_CONNECT_AT_BACK` (connect as background; default is
foreground), `W_CONNECT_USER_FLAG`, `W_CONNECT_SYSTEM_MODAL`,
`W_CONNECT_PRIORITY` (enable the server's process-priority handling),
`W_CONNECT_DISABLE_LEAVES` (return negative error numbers instead of calling
`p_leave`) `[wserv 7187-7203]`.
- `pnws_handle` is the handle the server puts into events **not** directed at a
window (e.g. key events) `[wserv 7206-7207]`.
- `pwserv_spec` points to a `WSERV_SPEC` the client must keep for the life of
the connection. `wConnect` fills its `CONNECT_INFO conn` sub-struct with
useful data — screen size (`pixels`), pixel geometry, `set_is_dark`,
`version_id` (machine type + server version), and the default
`system_font_handle` `[wserv 7210-7251]`.
**Connection tests / helpers.** After connecting, the reserved static
`wClientData` is non-zero (zero if not connected) `[wserv 1882-1894]`. The
`WSERV_SPEC` address is also recorded in the reserved static `wserv_channel`,
which general-purpose code can read for screen size etc. `[wserv 7294-7303]`.
**Two startup paths (see also §5 on the console):**
- **PLIB startup module:** call `wStartup`, which connects, creates and
initialises a backed-up window covering the whole screen, and creates a
permanent graphics context on it — ready to draw `[wserv 1496-1519]`. The
created window's ID is in the global `wMainWid` `[wserv 1609]`.
- **CLIB startup module:** the startup automatically opens the console device
`con:`, which on the HC/S3/S3a/Workabout **is itself a window-server
connection** — so the process is already a client. Connecting again with
`wConnect`/`wStartup` panics the process (panic 100) `[wserv 1387-1396,
1757]`. The console channel is in the static `winHandle` `[wserv 1399-1403]`.
### 1.4 Windows (briefly)
- Create with `wCreateWindow` (returns a window ID); the window is dormant and
invisible until **initialised** with `wInitialiseWindowTree`, which activates
the window and all descendants `[wserv 3141-3167]`.
- Destroy a tree with `wCloseWindowTree`; destroying a window more than once is
**not** treated as an error `[wserv 3185-3202]`.
- Windows can be **backed-up** by bitmaps: drawing is mirrored to a backup
bitmap and the server redraws automatically, so the client largely avoids
redraw handling `[wserv 1270-1273, plib 1334-1335]`.
- Redraw/mouse events are directed at a window by the **handle** the client
supplied at `wCreateWindow` (commonly the address of a client structure)
`[wserv 1944-1946]`.
---
## 2. The event system
For each client the server maintains a **queue of events** reporting user input
and other state changes: key presses, foreground/background changes, redraw
events, and (on pointer machines) mouse events `[wserv 1926-1937]`. Key and
mouse events are time-stamped in system ticks (1 tick = 1/32 s) for e.g.
double-click detection `[wserv 1939-1941, 14331-14333]`.
### 2.1 Fetching events
```c
VOID wGetEventWait (WS_EV *event); /* block until an event */
VOID wGetEvent (WS_EV *event); /* async, no wait */
VOID wGetEventSpecial(WS_EV *event, UINT flags); /* async + event filter */
VOID wGetEventUpdate (UINT flags); /* change the filter */
```
`[wserv 14302-14432]`
- **`wGetEventWait`** returns only when an event is available; on an empty queue
it waits indefinitely `[wserv 1949-1951]`.
- **`wGetEvent`** is the asynchronous version, for clients that also service
non-server sources (serial input, timers). It sets `event->type =
E_FILE_PENDING` immediately; when an event arrives the server fills `event`
and signals the client's I/O semaphore. **Only one may be outstanding** — a
second pending `wGetEvent` panics the process `[wserv 14363-14378]`.
- **`wGetEventSpecial`** (window server **version 4** only) is `wGetEvent` plus
a `flags` filter selecting which events to deliver. Only one call to
`wGetEvent`/`wGetEventSpecial` may be outstanding at a time `[wserv
14381-14393]`. Filter flags (OR-able) `[wserv 14396-14416]`:
- `WE_KEY` — key and task-key events
- `WE_REDRAW``WM_REDRAW`
- `WE_STATUS``WM_FOREGROUND`, `WM_BACKGROUND`, `WM_ON`
- `WE_MOUSE` — mouse and rubber-band events
- `WE_OTHERS` — everything else
- `WE_NORMAL` — all of the above (`wGetEventSpecial(..., WE_NORMAL)` ==
`wGetEvent`)
- `WE_ESC` — only meaningful when `WE_KEY` is *not* set: on ESC the keyboard
buffer is discarded and a `WM_ESCAPE` event is delivered.
- **`wGetEventUpdate`** (v4) replaces the filter of an outstanding
`wGetEvent`/`wGetEventSpecial`; a no-op if none is outstanding `[wserv
14419-14432]`.
> **Async discipline.** Fully process one server event before requesting the
> next. Because the server runs at higher priority than clients, an async
> request can complete while you are handling some *other* source. Never
> destroy a window directly in response to a non-server event — instead call
> `wCancelGetEvent`, which delivers a **`WM_CANCELLED`** at the **highest
> priority** (overtaking all queued but not-yet-delivered events), and do the
> destruction when that arrives `[wserv 3227-3252]`.
### 2.2 The event structure
```c
typedef struct {
WORD type; /* positive event type, WM_xxxx */
UWORD handle; /* target window, or the wConnect handle */
UWORD time; /* low word of tick count (key/mouse) */
WS_EVENT_UNION p; /* type-dependent payload */
} WS_EV;
typedef union {
UWORD uword;
UBYTE *dpoint;
P_RECT rect; /* WM_REDRAW rectangle */
WMSG_KEY key; /* WM_KEY */
WMSG_MOUSE mouse; /* WM_MOUSE / rubber band */
WMSG_RUBBER rubber;
WMSG_CAPS caps; /* WM_KEYBOARD_STATE_CHANGE */
} WS_EVENT_UNION;
```
`[wserv 14311-14350]`
- `handle`: for window-directed events (`WM_REDRAW`, `WM_MOUSE`) it is the
handle given to `wCreateWindow`; for non-window events (`WM_KEY`,
`WM_FOREGROUND`) it is the handle given to `wConnect` `[wserv 14326-14329]`.
The key payload:
```c
typedef struct {
UWORD keycode; /* code of the key pressed */
UBYTE modifiers; /* shift/ctrl/psion/caps/num-lock */
UBYTE count; /* auto-repeat accumulation */
} WMSG_KEY;
```
`[wserv 14456-14465]`
- `modifiers` bit flags: `W_SHIFT_MODIFIER` (0x02), `W_CTRL_MODIFIER` (0x04),
`W_PSION_MODIFIER` (0x08), `W_CAPS_MODIFIER` (0x10),
`W_NUM_LOCK_MODIFIER` (0x20, MC only) `[wserv 14481-14499]`.
- `count` is 1 for a single press; it exceeds 1 only when the client cannot
keep up with auto-repeat. The manual's advice is to **ignore the repeat
count** `[wserv 14466-14476, 6263-6280]`.
- `keycode`: printable SIBO characters (code page 850-like) fall in
`0x20``0xFF` excluding `0x7F`. The **PSION** shift typically adds `0x200`
(`W_SPECIAL_KEY`). "Special" keys carry codes `< 0x20`, `0x7F`, or `> 0xFF`
`[wserv 14501-14541]`. Named special codes include `W_KEY_TAB` (0x09),
`W_KEY_DELETE_LEFT` (0x08), `W_KEY_DELETE_RIGHT` (0x7F), `W_KEY_RETURN`
(0x0D), `W_KEY_ESCAPE` (0x1B), `W_KEY_UP/DOWN/RIGHT/LEFT` (0x1000x103),
`W_KEY_PAGE_UP/DOWN` (0x104/0x105), `W_KEY_HOME/END` (0x106/0x107),
`W_KEY_TASK` (0x108), `W_KEY_MENU` (0x122), `W_KEY_HELP` (0x123),
`W_KEY_ON` (0x2002), `W_KEY_OFF` (0x2003), etc. `[wserv 14547-14852]`.
### 2.3 Event types
Common event types (`WS_EV event;` assumed) `[wserv 14441-15048]`:
| Event | Meaning | Payload |
|-------|---------|---------|
| **`WM_KEY`** | A key was pressed. The full description is in `event.p.key` (keycode, modifiers, count). Delivered to the foreground client (or a capturer). `[wserv 14449-14476, 6249-6250]` | `p.key` |
| **`WM_REDRAW`** | Sent when the client's queue is empty and one or more windows has an update region. `event.p.rect` is a rectangular block of pixels needing redraw. Lowest priority except for `WM_USER_MSG`. `[wserv 14855-14865]` | `p.rect` |
| **`WM_BACKGROUND`** | The foreground client has just gone to background. Usually nothing to do, but suspend real-time/animated activity until foreground returns. Only `type` set. `[wserv 14868-14878]` | — |
| **`WM_FOREGROUND`** | A background client has just become foreground. Only `type` set. `[wserv 14881-14887]` | — |
| **`WM_CANCELLED`** | Sent in response to `wCancelGetEvent`; delivered at highest priority. Only `type` set. `[wserv 14890-14897, 3250-3252]` | — |
| **`WM_USER_MSG`** | Sent in response to `wUserMsg`. **Lowest priority of all** — useful as an "the server has nothing more to send" marker. Only `type` set. `[wserv 14909-14916]` | — |
| **`WM_ON`** | Machine switched on (server v3.5+). Delivered to the foreground client if it called `wInformOn`; in v4, to any client (fore or back) that called `wInformOnAll(TRUE)`. Prompts a display refresh. Only `type` set. `[wserv 14919-14937]` | — |
| **`WM_COMMAND`** | Another client sent a command via `wSendCommand` (server v3.5). Prompts the receiver to call `wGetCommand` for the data (up to 127 bytes). Only `type` set. `[wserv 14940-14947, 1128-1129]` | — |
| **`WM_TASK_KEY`** | Sent to the **application-key handler** (the shell) when an application key is pressed with no process of that application present, or a PSION-shifted application key is pressed. S3/S3a/Workabout only. The app-key index (015) is in `event.p.key.keycode`. `[wserv 14963-14975]` | `p.key.keycode` |
| **`WM_ESCAPE`** | (v4) Delivered when ESC is pressed *and* the client selected events with `wGetEventSpecial` including `WE_ESC`/`WM_ESCAPE` but **excluding** key events; the keyboard buffer is discarded. `[wserv 14999-15015]` | — |
| **`WM_TASK_UPDATE`** | Sent to the shell (when foreground) whenever *any* process terminates. S3/S3a/Workabout and HC v3.5+. Only `type` set. `[wserv 14950-14960]` | — |
| **`WM_DATE_CHANGED`** | (v4) Date changed (reset, or past midnight). Sent to a foreground S3a/Workabout app not in S3 compatibility mode; background apps get it on next foreground; delivered at next power-on if off. `[wserv 14978-14993]` | — |
| **`WM_KEYBOARD_STATE_CHANGE`** | Sent only to the shell when numlock/capslock changes; new state in `event.p.caps.modifiers`. (MC generates it for the caps-lock key.) `[wserv 15065-15069, 14734-14735]` | `p.caps` |
**Mouse / pointer events** (only on machines with a pointing device, e.g. the
MC) `[wserv 15076-15187]`:
| Event | Meaning |
|-------|---------|
| **`WM_MOUSE`** | State change on the digitiser. `event.p.mouse` is a `WMSG_MOUSE { UBYTE event; UBYTE state; P_POINT pos; }`. `event.p.mouse.event` is `WM_MOUSE_MOVE` (filtered out by default), `WM_MOUSE_PRESS`, or `WM_MOUSE_RELEASE`; `state` carries `W_MOUSE_DOWN`, `W_MOUSE_OUTSIDE`, and the shift modifiers. `[wserv 15083-15129]` |
| **`WM_RUBBER_BAND_INIT`** | Special `WM_MOUSE` variant sent when a press occurs in a window flagged `W_WIN_RUBBER_BAND_CAPTURE`. `[wserv 15146-15154]` |
| **`WM_RUBBER`** | Sent on completion of a rubber-band interaction. `[wserv 15160-15166]` |
| **`WM_ACTIVE`** | Sent to a window that set `W_WIN_INACTIVE` when a `WM_MOUSE_PRESS` lands on it or a descendant (in place of the `WM_MOUSE`); the client typically re-activates the tree by clearing `W_WIN_INACTIVE`. Only `type` set. `[wserv 15169-15186]` |
Large-screen (MC) events also include `WM_DEICONISE`, `WM_ATTACHED`,
`WM_DETACHED` for the multi-task attach/iconise model `[wserv 15017-15062]`.
### 2.4 Event priority
Event delivery is **prioritised**, not strictly FIFO:
- `WM_CANCELLED` is delivered at the **highest** priority, overtaking anything
queued but not yet delivered `[wserv 3250-3252]`.
- `WM_REDRAW` is **lower** than user input and foreground/background events;
the only type lower than `WM_REDRAW` is `WM_USER_MSG` `[wserv 14865,
3126-3127]`.
- `WM_USER_MSG` has the **lowest** priority of all `[wserv 14912-14913]`.
- Between windows there is a further **two-level redraw priority**: windows are
low by default; `W_WIN_PRIORITY` (on `wCreateWindow`/`wSetWindow`) promotes a
window's redraws ahead of the rest `[wserv 3130-3131]`.
### 2.5 The barcode scan key — **reverse-engineered (Workabout MX)**
> **Not in the manuals.** The *Window Server Reference* documents no barcode /
> scanner / laser trigger key, and defines no keycode near 368. On barcode-
> equipped Workabout / Workabout MX hardware the **scan (trigger) key is
> observed to arrive as an ordinary `WM_KEY` event with keycode 368**
> (`0x170`). This is **reverse-engineered field knowledge, not documented
> behaviour** — treat the exact value and delivery path as version/hardware
> dependent and confirm on the target unit. Because it surfaces as a normal
> key event, it can be handled in the ordinary `WM_KEY` path (and, like other
> keys, potentially captured with `wCaptureKey`).
---
## 3. Drawing and text output primitives
All drawing targets a **drawable** selected by the **current graphics context
(GC)** `[wserv 5504, 5526]`.
### 3.1 Graphics contexts
- Create a **permanent** GC on a window/bitmap with `gCreateGC` /
`gCreateGC0` (the latter with default values) `[wserv 304-305, 1438]`. Select
the current GC with `gSetGC` / `gSetGC0` `[wserv 311-312, 2993]`.
- **Temporary** GCs are used during redraws (see below) and are freed
automatically by `wEndRedraw` `[wserv 3037-3050]`.
- `wStartup` conveniently makes a permanent GC on its full-screen window
`[wserv 1502-1505]`.
### 3.2 Graphics primitives
- Lines / shapes / borders / fills: `gDrawLine`, `gBorderRect`, `gBorder`,
`gBorder2Rect`/`gBorder2` (shadowed, v4), `gFillPattern`, `gInvObloid`
`[wserv 315-326, 1296, 919-957]`.
- Bitmaps: `gCopyBit` (copy a bitmap to a window) and the bitmap-file family
(`gInitBit`, `gGetBit`, `gLoadBit`, `gPeekBit`, `gSaveBit`) `[wserv 354,
829-1021]`.
- The server also draws buttons (`wDrawButton`, `wDrawButton2`), sprites
(`wCreateSprite`/`wSetSprite`) and scaled objects (`gDrawObject`) `[wserv
926-969, 1168, 1203]`.
### 3.3 Text output
Text is drawn through the current GC (font ID, style, transfer mode) `[wserv
5526-5536]`:
- `gPrintText` — draw text from a pixel position `[wserv 5508, 1439]`
- `gPrintClipText` — clipped to a given width `[wserv 5510]`
- `gPrintBoxText` — left/right/centred in a box; commonly used for
**flicker-free** redraw `[wserv 5512-5513, 2961]`
- `gXPrintText` — with embellishment `[wserv 5515]`
- `gShadowText` — shadowed (v4) `[wserv 5517]`
Font styles combine `G_STY_NORMAL`, `G_STY_BOLD`, `G_STY_UNDERLINE`,
`G_STY_INVERSE`, `G_STY_DOUBLE`, `G_STY_MONO`, `G_STY_ITALIC` `[wserv
5539-5557]`. Layout helpers: `gTextWidth`, `gTextCount`, `wGetWidthTable`
`[wserv 5492-5497]`.
### 3.4 Redraw handling
On a `WM_REDRAW`, the normal response is to **validate** the update region and
draw it `[wserv 10248-10256]`. Bracket the drawing with a `wBeginRedraw`
variant and `wEndRedraw`; `wBeginRedraw` both signals "this is a redraw" and
**validates** the given rectangle `[wserv 3005-3007]`. There are six
`wBeginRedraw*` variants covering partial vs whole window and
independent/temporary GC combinations `[wserv 3020-3050]`. Related: validate
without redrawing (`wValidateRect`, `wValidateWin`) and force redraws by
invalidating (`wInvalidateRect`, `wInvalidateWin`) `[wserv 270-274, 2901]`.
Backed-up windows largely avoid this cycle because the server repaints from the
backup bitmap `[wserv 1270-1273]`.
Minimal MC (non-backed-up) redraw loop `[wserv 1589-1608]`:
```c
wStartup();
do {
wGetEventWait(&event);
if (event.type == WM_REDRAW) {
wBeginRedrawWin(wMainWid);
gPrintText(10, 20, "Hello world!", 12);
wEndRedraw();
}
} while (event.type != WM_KEY);
```
---
## 4. Error handling (server calls)
By default a failing server function calls `p_leave` with the negative error
number; `wDisableLeaves(TRUE)` (or `W_CONNECT_DISABLE_LEAVES` at connect time)
makes it **return** the error instead `[wserv 1629-1641]`. Many drawing calls
are **blind** (queued client-side, no acknowledgement); on failure the server
discards further blind ops until a non-blind call reports the error. Force the
issue with `wCheckPoint` (flush + signal) or `wFlush` (flush only); after an
error, `wCleanUp` releases dangling resources `[wserv 1653-1703]`. Client-side
buffering means a panic can lag the offending call — insert `wFlush`/
`wCheckPoint` when hunting one down `[wserv 1791-1793]`.
---
## 5. The console layer — `CON:` and the PLIB console functions
The console is the **lightweight text-UI path**. PLIB contains only *primitive*
console functions — typed-line input (with simple backspace editing) and
mono-spaced line output — while the `con:` device driver adds row/column
positioning and mono-spaced character printing `[plib 1299-1301]`. They are
explicitly "for quick-and-dirty applications... not suitable for constructing
quality user interfaces" `[plib 8580-8581]`.
### 5.1 Relationship to the window server
Both the PLIB console functions and `con:` **ultimately call the window
server** for input and drawing — but expose only a fraction of its capability
`[plib 1310-1315]`. How `con:` is implemented differs by machine `[wserv
1319-1323]`:
- **HC / S3 / S3a / Workabout:** `con:` is implemented by **connecting
directly to the window server** — opening `con:` makes the process a window-
server client. Opening it also creates and initialises a **backed-up**
console window (no redraw handling needed) `[wserv 1319-1320, 1387-1407]`.
The console window's ID can be fetched with the `P_FINQ` I/O function
(`CONSOLE_INFO.window_handle`), letting a CLIB program create its own GC on
that window and draw with WLIB `[wserv 1410-1453]`.
- **MC:** `con:` is implemented by a **separate `SYS$CONS` display process**
that keeps a character map and redraws its own window (with title/menu bars).
Here it is `SYS$CONS`, not the application, that is the window-server client;
the application cannot use the console window, and `P_FINQ` is **not**
supported. On the MC the recommendation is the **PLIB startup module +
`wStartup`** `[wserv 1305-1322, 1539-1615]`.
The two startup modules therefore differ: the **CLIB** startup auto-opens
`con:` (backing `printf`, `gets`, `cprintf`, `cgets`) `[wserv 1387-1389]`; the
**PLIB** startup does not, and you call `wStartup` yourself `[wserv 1496-1497]`.
Auto-opening can be suppressed in CLIB by defining `p_xwind` `[wserv
1457-1490]`.
### 5.2 The PLIB console functions
Output (open `con:` automatically on first use) `[plib 8584-8595, 8700-8737]`:
```c
VOID p_putch(UINT c); /* write one character */
VOID p_puts (TEXT *str); /* write string + newline */
VOID p_printf(TEXT *fstr, ...); /* formatted line + newline */
VOID p_print (TEXT *fstr, ...); /* formatted, no trailing newline */
```
- `p_printf` uses an internal buffer of `P_MAXSYSIO` (258) bytes; output is
limited to `P_MAXSYSIO - 2` (256) bytes per call. Format is that of `p_atob`
`[plib 8720-8726]`.
- `p_print` moves no line; embed `\r` (start of line) and `\n` (down a line)
yourself `[plib 8732-8737]`.
Input `[plib 8597-8599, 8740-8770]`:
```c
INT p_getch(VOID); /* wait for a key, return its code */
INT p_gets (TEXT *str); /* read a line (backspace edit) */
INT p_getl (TEXT *pmt, TEXT *str, INT len); /* prompt, then read a line */
```
- `p_getch` waits for a key and returns its character code (no echo) `[plib
8740-8743]`.
- `p_gets` reads up to `P_MAXSYSIO-1` characters, Enter-terminated, and NUL-
terminates at `str` (needs `P_MAXSYSIO` bytes there); returns the length
`[plib 8746-8756]`.
- `p_getl` writes the prompt `pmt`, then reads up to `len` characters
(Enter-terminated, needs `len+1` bytes); returns the length `[plib
8759-8770]`.
Canonical smallest program `[plib 1020-1021]`:
```c
p_printf("Hello world");
p_getch();
```
### 5.3 Console configuration statics
Set these **before the first console call** `[plib 8612-8694]`:
- `winHandle` (`GLREF_D VOID *`) holds the open console channel; open a file or
`TTY:` into it first to **redirect** `p_putch` / `p_print` / `p_printf`. Do
**not** use `p_getch` / `p_gets` / `p_getl` when redirected `[plib
8615-8628]`.
- `_DefScreenRect` (`P_RECT`, top-left `(0,0)`, bottom-right = columns × rows)
changes the console window size `[plib 8629-8665]`.
- `_DefScreenMode` (`INT`) selects the mode: `0` native (default, currently
single-pixel non-compatibility on all SIBO machines), `1` compatibility, `2`
non-compatibility with grey, `3` compatibility with grey. Irrelevant values
are ignored `[plib 8668-8694]`.
---
## 6. Higher UI libraries (pointer only)
For full applications — menus, dialogs, forms — SIBO provides **higher-level UI
libraries layered on the window server** (commonly referred to as **HWIM**,
**FORM** and **OLIB**). **These are separate libraries and are not documented
in the *Window Server Reference* or *PLIB Reference*** covered here; the two
manuals mention menus and dialog boxes only as *windows the server draws/clips*
(e.g. `[wserv 2395, 10646-10647]`), not as an API. Use those libraries' own
references for real application UIs; the material above is the **foundation**
they sit on. *(Library names are noted as a pointer only and are not sourced
from these two manuals.)*
---
### Source key
- `[wserv N]` — *Window Server Reference* v2.30, line N of the supplied text.
- `[plib N]` — *PLIB Reference* (console functions), line N of the supplied
text.
- Items marked **reverse-engineered** / **uncertain** are **not** in either
manual (notably the code-368 barcode scan key and the higher UI library
names).
+581
View File
@@ -0,0 +1,581 @@
# Psion SIBO / Workabout — Hardware Reference for Programmers
Scope and sources. This reference is compiled **only** from two Psion manuals:
- **HDK** — *The Psion SIBO Hardware Development Kit*, Psion PLC, Rev 1.00, May 1995.
- **WPG** — *Workabout Programming Guide* (Workabout Programmers Reference), hardware
appendices / introduction.
Citations are given as `[HDK p.N]` or `[WPG §/p]`. Where a fact is asserted by the manuals it is
cited; nothing here is invented. The manuals themselves note that both refer onward to a separate
*Hardware Reference manual* and *SIBO Computers Programmers Reference* for the full physical
memory map — those documents are **not** in scope, so the memory-map section below is limited to
what the two supplied manuals actually state, and gaps are marked explicitly.
> **MX note.** The two supplied manuals describe the **V30H**-based Workabout (NEC V30H at
> 7.68 MHz) and the ASIC1/ASIC2/ASIC4/ASIC5/ASIC9 family. **They do not mention the V30MX or
> ASIC9MX.** Every statement about the **V30MX / ASIC9MX / Workabout MX** in this document is
> therefore flagged as **[MX — not in supplied manuals]** and is presented only as an orientation
> note, not as a sourced fact. Treat all MX specifics as *uncertain / to be confirmed against the
> MX hardware reference.*
---
## 1. SIBO architecture overview
All Psion machines in this family are built on the proprietary **SIBO** ("SIxteen Bit Organiser")
architecture: a battery-powered, 8086-class computer system designed for size, weight and power
[HDK p.2; WPG §basic hardware].
Key components of the architecture [HDK p.2; WPG p.1-x]:
- An **8086-class processor**.
- A **power-management system** that selectively powers subsystems under software control.
- An **asynchronous high-speed serial protocol** (the *Psion SIBO serial interface*) between the
machine and its peripherals.
- **Solid State Disks (SSDs)** — fast, low-power, silicon mass storage, no moving parts.
- **Hardware protection** from aberrant software: trapping of out-of-range addressing, plus a
**watch-dog timer** that fires if interrupts are left disabled too long.
- **Real-time clock**, **ROM-resident system software**, **graphics LCD**.
- On some models: touch digitiser pad; ISDN-8-bit combo sound.
The digital logic of a SIBO machine is implemented in custom **ASICs** (Application Specific
Integrated Circuits). At the time of the HDK there were **ten** SIBO ASICs; all are static-CMOS
surface-mount parts [HDK p.2].
### 1.1 Processor (NEC V-series)
- The MC/HC/Series 3 and the Workabout use the **NEC V30H**, "an enhanced 16-bit CMOS version of
the 8088 found in the original IBM PC … software compatible with the 8088" [HDK p.2].
- **Workabout:** NEC V30H, clocked at **7.68 MHz** [WPG p.1-x, Tech Spec: "NEC V30 running at
7.68 MHz"].
- The V30H is a **fully static** design: all internal storage (registers) is static, so there is
**no minimum clock speed** and the clock can be stopped at any instant with **no loss of state**.
SIBO exploits this to stop the clock while the CPU is idle, saving power [HDK p.2].
> **[MX — not in supplied manuals]** Later Workabout variants are marketed as the **Workabout MX**,
> based on the **NEC V30MX** core integrated in the **ASIC9MX**. Neither the V30MX nor ASIC9MX
> appears in the HDK or WPG supplied here; their clock rate, memory reach and register differences
> are **not documented in these sources** and must be confirmed elsewhere. Software written to the
> V30H/8086 programming model and the ASIC register interfaces below is the documented baseline.
### 1.2 Chip integration levels
- **Discrete generation** (MC, HC, Series 3): three principal chips — **V30H + ASIC1 + ASIC2**
[HDK p.2].
- **Integrated generation** (Series 3a, Workabout): V30H, ASIC1 and ASIC2 collapsed into a single
custom chip, **ASIC9** (plus general I/O and PSU-control logic). ASIC9 "integrates all the
digital logic required to produce a SIBO architecture computer less the memory onto one chip",
and adds a free-running clock (FRC) and a codec interface for sound [HDK p.4].
So on the Workabout, the functions attributed below to "ASIC1" (system/interrupt/LCD) and "ASIC2"
(peripheral/serial-protocol/power) are physically inside **ASIC9** [HDK p.4].
> **[MX — not in supplied manuals]** On the MX, the equivalent single-chip part is **ASIC9MX** with
> the **V30MX** core. Same architectural role as ASIC9 is assumed; specifics unconfirmed here.
---
## 2. The ASICs and their roles
| ASIC | Role (from HDK p.4) |
|------|---------------------|
| **ASIC1** | Main **system controller**. Connects directly to the V30H, controlling all CPU bus cycles (forming a micro-controller-like unit executing 8086 code). Blocks: bus controller, programmable timer, **eight-input interrupt controller**, **LCD controller**, memory-decode circuitry. |
| **ASIC2** | **Peripheral controller**. Contains the system clock oscillator; controls standby↔operating switching; interfaces to PSU, keyboard, buzzer and SSDs; contains the **eight-channel SIBO serial-protocol controller**; drives the reduced-external and extended-internal expansion ports. |
| **ASIC4** | Serial-protocol **slave** for addressing memory / memory-mapped peripherals. Used in SSDs. A cut-down ASIC5. See §5. |
| **ASIC5** | General-purpose I/O **slave** with an on-board **UART**; several modes (RS232 / parallel / barcode / card-reader / memory pack). See §6. |
| **ASIC9** | Composite: **V30H + ASIC1 + ASIC2** + general I/O + PSU control on one die; adds FRC and codec interface. Used in S3a and Workabout. |
On the **host** side of the SIBO serial link the controller (SPC) is in **ASIC2** (HC/MC/S3) or
**ASIC9** (S3a/Workabout). On the **peripheral** side the slave (SPS) is **ASIC4** or **ASIC5**
[HDK p.4, p.6].
> **[MX — not in supplied manuals]** **ASIC9MX** — the composite CPU/LCD/system chip on the MX — is
> not described in these sources. Assume it plays the ASIC9 role (V30MX core + ASIC1/ASIC2
> functions). No register-level MX detail is available here.
---
## 3. Memory: ROM / RAM / SSD and segments
What the supplied manuals state:
- **ROM:** all Workabout models have **1 MB internal masked ROM** holding the OS (EPOC), the
MS-DOS-like Command Processor, OPL editor and other utilities [WPG p.1-9, Tech Spec].
- **RAM:** Workabout ships with either **256 KB** or **1 MB** internal RAM (Tech Spec lists memory
build codes A=256 KB, B=512 KB, C=1 MB, D=2 MB) [WPG p.1-x, Tech Spec, serial-number table].
- **SSD:** two SSD drives, exposed as **drive A:** (top) and **drive B:** (bottom); RAM or Flash
SSDs, "up to 8 MB" of extra storage [WPG p.1-3, Tech Spec]. SSDs retain data independently of the
main power source [WPG p.1-4]. Contents of the internal RAM are preserved across power-off (the
machine resumes previous state on power-on) [WPG p.1-x].
- **Internal drive M:** internal RAM is also exposed as a filing volume **M:** (with
`M:RAMDRIVE`), formattable like an SSD volume [WPG §FORMAT, §MEM].
### 3.1 The programmer's memory model (segments, protection)
- The CPU is 8086-class, so software sees the standard **16-bit segmented** address model
(segment:offset, 16-byte "paragraphs") [HDK, WPG passim]. `LSEG` reports each segment's **segment
address**, **size in paragraphs** (1 paragraph = 16 bytes) and **access count** [WPG §LSEG].
- **Memory protection:** any attempt by an application to write **outside its own data segment**
causes the OS to terminate it (a **"panic"**); likewise leaving interrupts disabled too long
(watch-dog) [WPG §Resetting; HDK p.2].
- **`MEM`** reports free RAM in KB; this exceeds free bytes on `M:` because "some parts of internal
memory are reserved for code and data segments" [WPG §MEM].
- The OS **moves memory segments** at runtime (e.g. when a driver is installed/removed). Drivers
must cope: FAR return addresses into the OS stay valid, but a driver's own absolute segment
address may change, so ISR/PDD addresses must be re-established on **resume** (see §7.4)
[HDK p.4042].
### 3.2 Banking / paging — status in these sources
The physical **memory map and paging/banking scheme of the host CPU are not laid out in the
supplied manuals**; both defer to the separate *SIBO Computers Programmers Reference* / *Hardware
Reference* [WPG p.1-3: "See the SIBO Computers Programmers Reference manual for further details
regarding SSDs"; HDK p.2]. What the HDK *does* document is the **address decode inside ASIC4/ASIC5
slaves**, i.e. how a peripheral or SSD's own address space is banked via **chip-select blocks**:
- **ASIC4:** 8 chip-selects forming selectable **addressing blocks**, size software-defined,
**default 32 KB/block**; the filing system re-sizes the block when reaching the top of the
address space [HDK p.24]. Up to **28 address bits (256 MB)** in Extended mode; **21 bits + 4
chip-selects (4 × 2 MB)** in SSD/compatibility mode [HDK p.25]. See §5.
- **ASIC5 (pack mode):** address lines A0A20 generated across ports B/D/C, with **CS0CS3**
selected by SEL0/SEL1; **counter mode** on port B auto-increments the low address so 256
consecutive locations can be read without re-addressing [HDK p.30, p.57].
> **Gap flagged:** the host-side ROM/RAM/SSD address ranges, and any V30-level bank registers, are
> **not in these two manuals**. Do not infer them from the peripheral-side decode above.
---
## 4. SIBO serial protocol (host ↔ peripheral bus)
Everything a program does to an ASIC4/ASIC5 peripheral goes over the **two-wire synchronous SIBO
serial link** [HDK p.6]:
- **CLK** — clock, always **output from the controller** (ASIC2/ASIC9). Nominal **3.84 MHz** for
memory interfaces; **1.536 MHz continuous** for peripherals. Tri-stated low when idle
[HDK p.6].
- **DATA** — bi-directional, synchronous. Direction is set by the transport layer, not the
physical layer. Changed on falling CLK edge by the transmitter, latched on rising edge by the
receiver; pulled low when idle [HDK p.6].
**Framing (physical layer).** 12-bit frames, 8 data bits each → theoretical max ≈ **312 KB/s**
[HDK p.6]. Frame = `ST` (start, goes high) · `CTL` (0 = control frame, 1 = data frame) · `I1`
(idle/turn-around) · `D0D7` · `I2` (idle) [HDK p.7]. Four frame types: **Null** (sync all slaves —
12 clocks with DATA held low), **Control**, **Data-out** (controller→slave), **Data-in**
(slave→controller; controller drives cycles 12 then tri-states, slave drives D0D7) [HDK p.7].
**Transport layer.** The controller has two registers: a write-only **Control register** and a
read/write **Data register** (byte or word) [HDK p.8]. Control bytes are classified by **bit 7,
the Select (S) bit**:
- **S = 0 — Slave-select mode.** Byte = `R` (reset bit) + **6-bit slave ID**. ID 0 is illegal, so
up to **63 slaves** per controller. `R=0` resets, `R=1` selects. Selecting a slave with `R=1`
makes it return a **non-zero 8-bit info byte**; a reply of **0 means no slave of that ID present**
[HDK p.89].
- **S = 1 — Slave-control mode.** Byte = `R/W` (0=write,1=read) · `B/W` (0=byte,1=word) · `S/M`
(0=single,1=multi) · 4 slave-specific data bits. Byte-pair (word) transfers are **listed as not
implemented** [HDK p.9].
**Timing rules (clock cycles, ≈260 ns each at 3.84 MHz)** [HDK p.1011]:
control-byte process = 12 cycles; byte transfer = 12 cycles; byte-pair = 24 cycles. After writing
the control register, wait ≥12 cycles before touching the data register or writing another control
word.
Assembler helper macros the HDK uses for all of this [HDK p.52]:
| Macro | Action |
|-------|--------|
| `SCONTOUT` | Output control byte in AL |
| `SDATAOUT` | Output data byte in AL |
| `SDATAIN` | Input a data byte into AL |
| `SBUSY` | Wait while the link is busy |
| `XNOP` | Short wait |
| `HwNullFrame` | Emit a null frame to sync slaves |
Serial-protocol control defines (`SerialSelect`, `SerialReadSingle`, `SerialWriteSingle`, slave
IDs) live in `ospack.inc` / `ossibo.inc` [HDK p.59].
---
## 5. ASIC4 — memory / peripheral slave
Purpose: convert SIBO serial frames into the address/data/control signals needed to drive memory
and memory-mapped peripherals; used in SSDs and in ASIC4-based expansion boards [HDK p.24].
**Bus:** 8-bit data, **28-bit address**, **8 chip-selects** (CS0CS7) [HDK p.24].
### 5.1 Modes (selected by slave ID)
Select ASIC4 with the appropriate ID, then read the info byte [HDK p.25]:
- **SSD / ASIC5-compatibility mode — ID 2.** Mimics an ASIC5 in pack mode; compatible with all
existing SSD software. Max **21 address bits + 4 CS (4 × 2 MB)**.
- **ASIC4 Extended mode — ID 6.** Up to **28 address bits (256 MB)**. On reset, four extra config
bits come from A27A24; **A27 = M** selects standard-SSD (M=0) or **mixed mode (M=1)**
memory + peripherals. Mixed mode is the one of interest for peripheral developers.
**Mixed mode split:** address space halves — lower half = memory-mapped peripherals (any use),
upper half = pure memory (typically a control ROM the filing system can mount). Chip-selects split:
**CS0CS3 = peripheral blocks, CS4CS7 = memory devices 14** [HDK p.2526].
**Reset config** is read from the data bus (**Info Byte**, D0D7) and A24A27 (extended nibble),
set with pull-up/pull-down resistors (~100 k so the bus can still drive the lines in normal
operation) [HDK p.26].
### 5.2 ASIC4 registers (mixed/peripheral mode) [HDK p.5355]
| Reg | Name | R/W | Notes |
|-----|------|-----|-------|
| **0** | **Data Register** | R/W | Drives/reads D0D7 (tri-state when idle). **On reset holds the Info Byte** (see below). |
| **1** | **Input Register** (read) / **Device-Size Register** (write) | R/W | Read: extended-info bits + live state of general inputs **In0In2** and X2. Write: bits S3S0 set the address-decoder block size (32 KB … 256 MB); defaults to `0x0F` (unused) on reset. |
| **2** | **Address-Increment Register** | W | A write increments address lines A0A3. |
| **3** | **Address Register** | W (multi-byte, LSB first) | Loads all 28 address lines. Bytes: **ATO = A0A7, AT1 = A8A15, AT2 = A16A23, AT3 = A24A27**. Writing the first byte resets A8A27 to 0. AT3 bits 46 can steer CS0CS3. Cleared on reset. |
| **4,5,6** | — | — | **Not implemented.** |
| **7** | **Control Register** | W | Bits: `7 LBO · 6 TSTA · 5 LTM · 4 VPS · 3 EDA · 2 CSS · 1 WRS · 0 OES`. Setting **LBO** / **VPS** drives those pins high (usable as GP outputs). **OES/WRS** control read/write accesses. |
**Info Byte** (Reg 0 at reset) [HDK p.53]: bits encode **device type** (RAM / Intel Flash type 1 /
type 2 / read-only SSD / hardware write-protected SSD), **number of devices** (14), and **device
size** (000 = no SSD present, then 32 KB → 2 MB). In Extended mode a further **Extended Info Byte**
(M, De, Ne, Se) identifies the peripheral type — e.g. `1 0 0 1` = Turbo RS232 (16550), `1 0 1 0` =
3Fax, `1 1 1 1` = extended info held in ROM [HDK p.54].
**Access sequence** (bottom 256 addresses) [HDK p.5859]: control-write to the **Address** register
→ data-write the address → control-frame read/write to the **Data** register → transfer the byte.
The HDK gives `Input`/`Output` assembler routines doing exactly this; **interrupts must be off**
during them so the host does not multitask mid-transfer.
**Other pins** [HDK p.27, p.55]: `In0In2` GP inputs; `LBO` (open-drain low-battery driver), `VPS`
(VPP control) GP outputs; `OE`/`WR` bus control; `SCLK`/`SDAT` the serial link; `SDIR` protocol
direction bit; `POR` reset; `ATST` test (pull high = address test mode); `MCSD`/`X2D2` tie to GND
via 100 k. A PSRAM mode reuses some pins for refresh/oscillator.
---
## 6. ASIC5 — UART / parallel / barcode / card-reader slave
Three primary functions: a **UART** (up to **48000 baud**), **general-purpose I/O**, and
**address/data lines** for memory / memory-mapped peripherals [HDK p.29].
### 6.1 Modes
- **Pack mode:** generates full address/data/control to access memory; **no peripheral functions**.
- **Peripheral mode:** limited memory addressing (lines reused for I/O). Entered by setting the
**peripheral bit (bit 0) in the Port-B mode register** [HDK p.29].
**Port re-use** [HDK p.30]:
- **PA0PA7** — 8-bit non-latched GP I/O; in peripheral mode **PA0PA3 = UART RX, CTS, DSR, DCD**;
**PA4** is a change-of-state interrupt source (e.g. Centronics **BUSY**, or **RI** on the VIC —
see below). In pack mode PA0PA7 is the memory data bus.
- **PB0PB7** — latched output / **counter** mode; in pack mode = address **A0A7**.
- **PD0PD7** — GP outputs; in pack mode = address **A8A15**; in peripheral mode **PD0/PD1/PD2 =
UART TX, RTS, DTR**.
- **PC0PC4** — in pack mode = address **A16A20**; in peripheral mode **PC4/PC7** = inverted
edge-triggered interrupt inputs, **PC5** = interrupt output, **PC6** = GP latched output +
pack/peripheral select at reset, **PC0PC3** = a **dual synchronous serial port** for magnetic
card readers.
**UART details** [HDK p.2930, p.61]: needs the host link in **continuous-clocking** mode (baud
clocks are derived from the 1.536 MHz link clock). No internal buffering — the **Transmitter
Holding Register must be empty** before writing. One shared **active-high interrupt line**; software
reads the status/interrupt registers to find the cause. Reading the UART Status register clears the
Tx interrupt.
**Barcode:** the UART receives data from a dedicated barcode-scanner IC [HDK p.30].
**Card readers:** two synchronous serial ports (PC0PC3) take clocked serial data [HDK p.30].
**Reset config** read from **PA0PA7** (device type/size, like ASIC4); **PC6** selects pack vs
peripheral; in peripheral mode PA0PA7 indicate the peripheral type (RS232 port / Centronics port,
combinable) [HDK p.31].
### 6.2 ASIC5 register set — the sixteen registers
Reading/writing a register is two steps: send a control byte selecting the register, then
read/write the data [HDK p.56]. Register list [HDK p.5657, cross-checked against the
`SYS$AS5.ASM` defines at HDK p.102]:
| Reg | Name | R/W | Function |
|-----|------|-----|----------|
| **0** | **Port A data** | R/W | Read/write Port A; a read/write triggers a memory access cycle (pack mode) or one-cycle CS0 strobe (peripheral mode). |
| **~2** | **Port B data** | R/W | Latches PB0PB7 (= A0A7 in pack mode); read returns last value. |
| **~4** | **Port B mode / Inc** | R/W | **bit 0 = 0 memory / 1 peripheral (enables UART)**; also selects **counter** vs **latch** mode and "baud rate out on Port B". Counter increments on Port A access. |
| **~5** | **Port C/D write** | W | First write latches PD0PD7; subsequent (multiwrite) writes latch Port C, incl. **SEL0/SEL1** choosing CS0CS3. In peripheral mode PD0 = UART TX (bit 0 has no effect). |
| **6** | **Interrupt Mask** | R/W | 1 = enable that event's interrupt; 0 = disable. All disabled on reset. |
| **~7** | **Interrupt Status / Type-Ctrl** | R | Reading indicates interrupt source; bit meanings mirror the mask. |
| **8** | **UART Status / UART Control** | R/W | Format (stop/data bits, parity), break, and status (Tx empty, Rx/parity/framing/overrun errors). |
| **9** | **UART Receive / Transmit holding register** | R/W | Read = received char; write = char to transmit. |
| **10** | **UART Baud rate LSB** | W | Low byte of baud divisor. |
| **11** | **UART Baud rate MSB** | W | High byte of baud divisor. |
| **12** | **MCR shift register** | — | Magnetic-card-reader shift register. |
| **13** | **Barcode read data** | R | Returns states of the barcode lines / general interrupt bits. |
| **14** | **Synchronous Port 2 read** | R | Second synchronous serial port (card reader). |
| **(15)** | (sync port 1 / reserved) | — | Synchronous-port-1 data; documented in prose (sync port 1 & 2 char-received are distinct interrupt sources). |
> The HDK's printed register table is OCR-garbled for some indices; the register **names, R/W
> attributes and functions** above are taken verbatim from the HDK prose (p.5657) and the
> assembler comment block in `SYS$AS5.ASM` (p.102): *Port A R/W · Port B R/W · Inc/Mode · Port CD
> write-only · Interrupt mask R/W · IntType/Ctrl · UART Status/Ctrl · Receive/Transmit · Baud Rate
> (×2, write-only) · MCR shift register · Barcode data & ints.* Exact numeric slots for the
> mid-range registers should be confirmed against the include files (`ospack.inc`, `ossibo.inc`).
**Interrupt-mask bits** [HDK p.56, and `S_*` defines p.102]: UART character received; UART
transmitter awaiting character; modem/handshake change of state; **synchronous port 1 char
received**; **synchronous port 2 char received**; **barcode data / general interrupt**. Reading the
**Interrupt Status** register (same bit layout, high = active) identifies the source [HDK p.56].
**UART Status/Control bits** (`SYS$AS5.ASM`, HDK p.102):
| Define | Value | Meaning |
|--------|-------|---------|
| `S_RXENB` | `0000_0001b` | Receive interrupt enable |
| `S_TXENB` | `0000_0010b` | Transmit interrupt enable |
| `S_TXEMPTY` | `0001_0000b` | Transmit buffer empty (status) |
| `S_RXINT` | `0000_0001b` | Receive interrupt pending |
| `S_TXINT` | `0000_0010b` | Transmit interrupt pending |
| `S_MDINT` | `0000_0100b` | Modem-status interrupt |
| `S_CTS`/`S_RTS`/`S_DCD`/`S_DSR`/`S_DTR` | `01b`/`02b`/`04b`/`02b`/`04b` | Handshake-line states |
| `OVERRUN_ERROR` | `0100_0000b` | Character overrun |
| `PARITY_ERROR` | `1000_0000b` | Parity error |
| `S_PERIPHERALMODE` | `0000_0011b` | ASIC5 RS232 (peripheral) mode |
| `S_UART_OFF` | `0000_0010b` | ASIC5 peripheral mode, UART off |
**Baud rate.** `Divisor = 1 (96000 / desired_baud)`, written as a 16-bit word: LSB → reg 10,
MSB → reg 11 [HDK p.61]. The driver's `BaudRateTable` (HDK p.102) holds the two's-complement
divisors, e.g. `-0x077F, -0x04FF, -0x0368, -0x02CC, -0x027F, -0x013F, -0x009F, -0x004F, -0x0035,
-0x0030, -0x0027, -0x0019, -0x0013, -0x000C, -0x0009, -0x0004` for the standard rates.
### 6.3 Selecting / configuring ASIC5
Before use, select the chip (and re-select after every hold/resume from power-down, pack-door, or
insert/remove) [HDK p.56]. Selection differs by mode:
- Peripheral: `mov al,(SerialSelect or Asic5NormalId)``SCONTOUT``SDATAIN`; a **zero reply
means no ASIC5 present** [HDK p.56].
- Pack: same with `Asic5PackId` [HDK p.56]. An **ASIC4 will answer a pack-mode select** (it
impersonates an ASIC5 in pack mode) [HDK p.56].
---
## 7. I/O ports, interrupts and the channel model
### 7.1 Eight hardware interrupts
SIBO supports **8 level-triggered hardware interrupts**, IRQ0 (highest) … IRQ7 (lowest), handled by
the interrupt controller in **ASIC1 (or ASIC9)**. Expansion ports carry an interrupt line into this
controller [HDK p.3]:
- **Extended internal** expansion ports: **active-low** interrupt input.
- **Reduced external** expansion ports (S3a 6-pin, Workabout LIF): **active-high** interrupt input.
Servicing order [HDK p.3]:
1. Device asserts its IRQ line.
2. The controller (ASIC2/ASIC9) places the **vector of the highest-priority pending device** on the
data bus; the CPU jumps to that ISR.
3. The ISR clears the interrupt at the device (device-specific action).
4. The ISR writes the **non-specific end-of-interrupt (NSEOI/NSEOD)** location to tell the
controller the interrupt is cleared.
5. Repeat if another is pending.
**Nested interrupts are not possible** on these 8086-class processors [HDK p.3].
### 7.2 The four channel variables
To be portable across host machines, a driver parameterises four values per expansion channel
[HDK p.52]:
- **Channel interrupt mask** — an 8-bit value OR'd into the mask register (`A1InterruptMask` or
`A9BInterruptMask`, depending on whether the controller is ASIC1 or ASIC9) to enable interrupts on
that channel; also passed to `HwGetChannel` / `HwFreeChannel`.
- **Channel interrupt number** — 16-bit, used with `GenSetRevector` / `GenResetRevector` to say
which default ISR the driver replaces.
- **Channel interrupt vector** — 16-bit pointer to the driver's ISR.
- **Hardware SIBO channel** — used by `HwSelectChannel` to route serial frames to that channel.
### 7.3 Per-platform channel map [HDK p.52]
| Host | Controller | Ports and channels |
|------|-----------|--------------------|
| **Series 3** | ASIC2 | Expansion port C = serial **channel 7**, interrupt `Asic2Int` (IRQ4), select `SelectChannel7`. |
| **Series 3a** | ASIC9 | Expansion port C = serial **channel 5**, mask `A9MSlave`, revector `IHwIrq2Revector` (IRQ2), select `SelectChannel5`. |
| **Workabout / HC (3-channel)** | ASIC9 / ASIC2 | Port **A** (top) and port **B** (bottom) internal + port **C** (side/cradle); masks `ExpIntLeftA` / `A9MExpIntA`, `ExpIntRightB` / `A9MExpIntB`, plus the slave/`Asic2Int` channel; selects `ExpChannelLeftA`, `ExpChannelRightB`, `SelectChannel5/7`. |
Build flags select the machine so one driver source compiles for all: **Consumer (S3a)** = 1 SIBO
channel; **HC/S3C** = 3 channels; S3 = single-channel [HDK p.5152; `SYS$AS5.ASM` p.102].
### 7.4 Owning and selecting a channel
The S3/S3a/HC have **three** Psion serial links: two are the SSD slots, the third is the expansion
port [HDK p.60]. Only **one link is selected at a time**.
- **`HwGetChannel`** (AL = channel interrupt mask): reserve a channel; **carry clear = success**.
Normally called from the driver's **open** vector; open should fail if the channel is unavailable
[HDK p.60].
- **`HwFreeChannel`** (AL = mask): release it (on close) [HDK p.60].
- **`HwSelectChannel`** (AL = select code): make a channel current; **returns the previously
selected channel in AL** so it can be restored on exit [HDK p.60].
Discipline [HDK p.60]: select the correct channel on entry to any vector/ISR that talks down it, and
restore the previous one on exit. **Disable interrupts (multitasking) between select and restore**
but only briefly, because the **watch-dog** forbids leaving interrupts off indefinitely; hence
comms happen in short bursts. Idiom:
```asm
pushf
cli
mov al, SelectChannel5
HwSelectChannel ; old channel returned in AL
push ax
; ... Input / Output to the peripheral ...
pop ax
HwSelectChannel ; restore previous channel
popf
```
### 7.5 Channel unit letters (open qualifiers)
A driver can support several channels distinguished by a **single-letter qualifier** appended to
the device name, allocated from `'A'` [HDK p.38]. Convention: **A = top port, B = bottom port,
C = side/cradle port** [HDK p.38]. Example: `p_open(&pcb,"PAR:A",-1)`. On the S3a only **one** SIBO
channel (port C) can be opened; on Workabout/HC up to **three** (A, B, C) [HDK p.38]. The VIC's
extra serial connectors appear to the Workabout as standard serial devices **ports I and F**, and
the extended 15-way port is addressed as **port C** [WPG Appendix A, VIC].
### 7.6 Direct I/O (HC extended internal ports)
On the HC's 25-way extended internal ports, expansion devices also get **direct processor I/O**:
`AD0AD7` (multiplexed addr/data, low half only → up to 128 I/O addresses, even addresses only),
`ALE`, `IOWR`, `IORD`, `EES` (External Expansion Select — high during I/O to that device), `INTR`
(active-high IRQ to ASIC1). Port 1 decodes I/O `1001FF`, port 2 `2002FF`; A0 is always low for
valid writes so A1 is the lowest usable address line [HDK p.19]. Each HC port also carries one SIBO
serial channel (port 1 = channel 5, port 2 = channel 6) [HDK p.20]. (The Workabout uses a 26-way
Torson connector for its internal expansion — see §8.)
---
## 8. Expansion slots, ports and connectors
### 8.1 Overview by machine [HDK p.2]
| Machine | SSD ports | Expansion |
|---------|-----------|-----------|
| MC / HC | 2 | Two independent single-row **25-way extended internal** ports (SIBO channel + direct parallel I/O + 7.2 V battery). |
| Series 3 / 3a | 2 | One **6-pin reduced external** port (port C): SIBO serial channel + limited power (**< 25 mA**). |
| **Workabout** | 2 (A: top, B: bottom) | Two internal expansion points + one **11-pin LIF** external port. Each internal port = single-row **26-way** carrying two high-speed serial ports. |
### 8.2 Workabout internal 26-way (Torson) connector [HDK p.13]
Signals: **Vcc1** = 3.0 V nominal, **≤ 100 mA**; **Vcc2** = 5 V nominal, **≤ 200 mA**; **RUN**
(low = powered down, high = powered up — used to power-down/reset the module); **SCK2/SCK3** serial
clocks (run continuously at **1.536 MHz** when the port is in use; clock the ASIC5 UART in the
RS232-AT/TTL and AT/Barcode modules); **SD2/SD3** bi-directional serial data; **EINT1/EINT2**
active-low interrupt inputs; **EXON** active-high turn-on. All logic at **3.0/3.3 V** (per Vcc1).
Unused lines are reserved for a codec interface.
The Workabout expansion module (RS232/TTL or RS232/Barcode) connects the main board to the module
via this 26-way Torson connector + flexi cable; modules are **factory-fitted** [WPG p.1-3,
Appendix A]. The RS232/TTL module runs up to **19,200 baud**; the RS232/Barcode module uses an HP
HBCR-1612 micro to decode and transmit barcode data at up to **9,600 baud** [WPG Appendix A].
### 8.3 Workabout external 11-pin LIF connector [HDK p.1416]
The reduced external port for connecting to the Cradle system. Standard pin functions (Polarisation
Type B, cradle perspective) [HDK p.16]:
| Pin | Name | Function |
|-----|------|----------|
| 1 | LCA | Local Computer Active — high when the computer is on. Workabout can **source 100 mA** from this pin (HC/HCDOS: 5 mA). |
| 2 | EXON | External switch-ON, active-high (+5 V) — asserted by a remote/cradle device to switch the computer on. |
| 3 | THERM | Battery thermistor terminal. |
| 4 | DLA | Disconnect Local ASIC (does not apply to Workabout). |
| 7 | VIN | Power supply to computer (+10 V). |
| 8 | SCLK | Serial-channel clock. |
| 9 | GND | Power / signal ground / ve battery (1 A). |
| 10 | SDATA | Serial-channel data. |
| 11 | STATUS | Cradle status; pulled-up for open-collector sensing (low = remote device present). |
Type-A polarisation repurposes several pins for RS232 signals. The LIF cover can be moulded with a
polarising pin in up to four positions to differentiate variants [HDK p.14].
### 8.4 S3/S3a 6-pin reduced external port (port C) [HDK p.12]
Six wires: **MSD/MCLK** (a master SIBO serial channel — **channel 7 on S3, channel 5 on S3a**),
**SDS/INT** and **SCK/EXON** (dual-function: form a slave serial channel, or SDS/INT is an
active-high interrupt, and a rising edge on SCK/EXON wakes the machine from standby), **VCC** (+5 V,
switched off in standby, **max 25 mA**). Opening the pack doors on an S3a or Workabout **cuts power
to external peripherals** [HDK p.12].
---
## 9. Power
- **Workabout power sources** [WPG p.1-4, Appendix A]: NiCd rechargeable pack **or** 2 × AA
alkaline (main), plus a **3 V lithium CR1620 backup** battery that preserves internal RAM when the
main source is absent (recommend yearly replacement; alone it preserves memory for a limited
period). External: a Series 3 mains adaptor via the **LIF Converter** (also trickle-charges), and
(from Jan/Feb 1997) a **vehicle power adaptor** / Docking Station.
- Power management is **software-controlled** and selective; the machine auto-switches-off after
~5 minutes idle by default [WPG p.1-4, §basic hardware].
- Under **low battery** the Workabout may run the screen/keyboard but refuse to write Flash SSD or
access expansion devices; it turns off if such an operation is attempted [WPG p.1-4].
- **States:** standby vs operating/idle. In standby, switched supplies (VCC/Vcc2) are **off**;
peripheral designs must not drive lines high in standby (pull-down inputs cause excessive standby
current) [HDK p.12, p.19].
- **Peripheral power budgets** (from the connector tables): S3a port C **< 25 mA**; HC 25-way Vcc2
**50 mA** (or use unregulated **Vsup** 5.512 V with a low-dropout regulator for higher loads);
Workabout Torson **Vcc1 100 mA / Vcc2 200 mA**; LIF **LCA 100 mA** [HDK p.12, p.1920, p.13,
p.16].
- **Power-fail handling for drivers** [HDK p.41]: on power loss there is ~**2 ms** to power
everything down; the hold vector must be fast or the RAM-hold voltage drops and the machine
cold-reboots, losing internal RAM (including any loaded driver). On a power-fail hold the OS has
already reset the SIBO channels, so a channel driver need only record the reason for its resume
vector. Hold reasons passed in AH: `DevHoldNormal` (memory move), `DevHoldPowerDown` (standby),
`DevHoldPowerFail` (supply lost) [HDK p.42].
---
## 10. The SIBO serial link (host ↔ PC / peripheral)
- The **3-Link** is an **ASIC5-based** cable that converts the S3/S3a port-C SIBO serial channel to
**RS232** (host ↔ PC/printer), using the ASIC5 on-board UART + line drivers [HDK p.5, p.35].
- Workabout communications: the OS provides serial comms at up to **19,200 baud**, with **XMODEM /
YMODEM** (and ZMODEM except in early models) file transfer, plus a scripting language for modem
control [WPG §basic hardware, Tech Spec].
- The high-speed inter-machine link is the same continuously-clocked SIBO serial channel described
in §4; the physical media are the S3a 6-pin port C, the HC 8-pin side port C (used by the HC
cradle), or the Workabout 11-pin LIF via the LIF Converter [HDK p.12, p.20; WPG Appendix A].
---
## 11. Programmer's cheat-sheet
- Talking to a peripheral = **own a channel** (`HwGetChannel`) → **select it** (`HwSelectChannel`,
save/restore the old one, interrupts off, short bursts) → drive the slave with control/data frames
via `SCONTOUT`/`SDATAOUT`/`SDATAIN`**free it** (`HwFreeChannel`) on close [HDK p.52, p.60].
- **ASIC4 access** = set Address register (multi-byte, LSB first) → read/write Data register; only
even/low-256 addresses in the simple routines; interrupts off during a transfer [HDK p.5359].
- **ASIC5 UART** = enable continuous clocking, set peripheral bit (Port-B mode bit 0), write baud
divisor `1 96000/baud` (LSB reg 10, MSB reg 11), set format in UART Status/Control (reg 8),
Tx via reg 9 only when `S_TXEMPTY`, Rx via reg 9, one shared active-high IRQ; read status/interrupt
registers to find the cause [HDK p.5661].
- **Interrupts** = 8 levels, IRQ0 highest, level-triggered, **no nesting**; clear at the device then
write NSEOI [HDK p.3].
- **Segments** = 8086 model; writing outside your data segment or hogging interrupts → **panic**
[WPG §Resetting; HDK p.2].
---
## 12. Known gaps / uncertainties in these sources
- **V30MX / ASIC9MX / Workabout MX:** absent from both supplied manuals. All MX statements above
are flagged **[MX — not in supplied manuals]** and are orientation only. Confirm against the MX
hardware reference.
- **Host CPU physical memory map and any bank/page registers:** not in these manuals (deferred to
*SIBO Computers Programmers Reference* / *Hardware Reference*). Only the peripheral-side
(ASIC4/ASIC5) chip-select block decode is documented here.
- **Exact ASIC5 register numeric slots (regs ~2, 4, 5, 7):** the printed table is OCR-damaged; names
and functions are reliable (prose + `SYS$AS5.ASM`), but verify the numeric indices against
`ospack.inc` / `ossibo.inc`.
- Several connector pin tables in the WPG appendix are OCR-garbled (RS232 module pinouts, VIC 15-way
pinout); signal names are recoverable but pin numbers should be checked against a clean copy.
+78
View File
@@ -0,0 +1,78 @@
# Boot & OS-call internals (reverse-engineered from the ROM under MAME)
Source: dynamic trace of `psionwamx` (v7.20f) under MAME — 170,160 instructions
of boot captured with correct bank mapping. Everything here is **reverse-
engineered / observed**, not from a manual, and describes internal mechanisms
below the documented C API.
## Reset & early boot
```
FFFF0: jmp A000:0 ; reset vector -> ROM
A0000: jmp A14A4
A14A4: di ; disable interrupts
in/out 30h,... ; ASIC hardware init via I/O ports
mov ds0,190h ; segment setup
out 2h / 2Ch / 22h / 26h / 24h / 3Ch ; ASIC control-register init
jmp AE69C ; signature check ([6896h]==0F0A5h) then continue
```
Early boot disables interrupts, programs the ASIC control registers through the
I/O ports, sets up segments, and validates a ROM signature word.
## OS service calls are software interrupts (not a single gate)
SIBO dispatches operating-system services through a **range of `INT` vectors**
(NEC V-series `brk` mnemonic), each vector a service group with a function
selector in a register. Vectors observed executing during boot, by frequency:
| Vector | Count (boot) |
| --- | --- |
| `0xA6` | 414 |
| `0x97` | 81 |
| `0x85` | 49 |
| `0x96` | 41 |
| `0xBA` | 41 |
| `0x8B` | 17 |
| `0xB9` | 15 |
| `0xAC` | 12 |
| others `0x81``0xB5` | few each |
Additional service vectors seen in driver code (not exercised at boot):
- **`INT 0xCF`** — the I/O executive: `CL` = function code, `BX` = channel
handle, `DX` = argument, result in `AX`. This is the layer beneath `p_iow`.
- **`INT 0xD9`**, **`INT 0xD3`** — further service vectors (exact roles TBD).
The specific service behind each vector is not yet mapped; doing so means
breakpointing each ISR and correlating with the documented C calls. This table
is the starting point.
## ASIC I/O ports touched during boot
Distinct `out` port targets and write counts during boot (ASIC register access —
cross-reference the ASIC register maps in the hardware reference):
| Port | Writes | Likely role |
| --- | --- | --- |
| `0x28` | 1580 | display/LCD controller (dominant) |
| `0x15` | 88 | |
| `0x21` | 54 | |
| `0x10` | 17 | |
| `0x02` | 14 | ASIC control |
| `0x0A` | 13 | |
| `0x08` | 12 | |
| `0x2C` `0x24` `0x22` `0x26` `0x30` `0x3C` | few | ASIC control-register setup (from early boot) |
## Method (reproduce)
```
xvfb-run -a mame psionwamx -rompath roms -debug \
-debugscript trace.txt -sound none -seconds_to_run 1
# trace.txt: trace boot_trace.asm / go
```
Then post-process `boot_trace.asm` for instruction/port/vector frequencies.
## Next RE steps
1. Map each `brk` vector to its service (breakpoint ISR, correlate to C API).
2. Trace `SCANAPP`/`DEMMAN` to resolve the `WL2` scanner data-retrieval path.
3. Correlate the ASIC ports here with the HDK register maps.
Binary file not shown.