Jaap's Psion II Page

                                CHAPTER 11

                           ____________________
                           EXTERNAL INTERFACING



     The Organiser II has been designed to be extended in a number of ways

     1.  Adding extra software services.

     2.  Adding extra hardware interfaces.


     In both cases an extension to the  operating  system  is  known  as  a
"DEVICE".   As  far  as the operating system is concerned a "DEVICE" can be
just an extra software service, or both an extra software  service  and  an
hardware interface.

     In general if an extra device is added, it will probably  be  designed
to  interface  to the top slot.  However there is no reason why it need not
be designed to run in one of the side  slots.   In  fact  it  is  perfectly
possible  to  design  an  adaptor  board  which  will  allow any of PSION's
interfaces to run in one of the side slots.



      ____________________
11.1  SOFTWARE INTERFACING

     Built into the operating system is a service DV$BOOT which  will  load
devices into the operating system.  This service will scan each slot in the
machine for a normal datapack of any kind (i.e.  8K,16K,32K,64K,128K)  which
has  the  NOBOOT  bit  clear in the first byte on the datapack.  Packs with
this bit clear are called "BOOTABLE" packs.

     Bootable packs have a special header which contains  the  information,
the DV$BOOT service requires, to load the device into RAM.

     Hardware interfaces like the RS232 adaptor have an 8K  datapack  built
into  them  as  well  as  the  interface  hardware.  Together they form the
device.



        _________________________
11.1.1  BOOTABLE PACK DESCRIPTION

     A pack which is BOOTABLE must have the following header information in
the first six bytes of the pack:

    ADDRESS     DESCRIPTION

       0        DATAPACK_CONTROL_BYTE
       1        DATAPACK_SIZE_BYTE
       2        DEVICE_OR_CODE_BYTE
       3        DEVICE_NUMBER_BYTE
       4        DEVICE_VERSION_BYTE
       5        DEVICE_PRIORITY_BYTE
       6        DEVICE_CODE_ADDRESS_WORD



          _____________________
11.1.1.1  DATAPACK_CONTROL_BYTE

     This byte is used by the datapack handling services  to  hold  various
bits  of  information  about  the  datapack.   See  section  9.4.3 for more
information.

     Each bit in the byte has a particular function as follows:

    BIT NO.     BIT NAME        DESCRIPTION

        0       N/A             This is clear for a valid MKII
                                Organiser pack.
        1       EPROM           This is set if the pack is an eprom
                                pack (cleared if it is a ram pack).
        2       PGCPK           This is set if the pack is page counted.

        3       RDWRT           This is cleared if the pack is write
                                protected.
        4       NOBOOT          This is cleared if the pack is bootable.

        5       COPYPK          This is set if the pack is copyable.

        6       NYIMPL          This is normally set as it is reserved
                                for future expansion.

        7       MK1PAK          This is set if the pack is a MKI
                                Organiser pack.



     Hence BIT 4 of the DATAPACK_CONTROL_BYTE must be cleared  to  indicate
that  the  datapack  contains a device to be loaded into the machine.  Some
examples of valid DATAPACK_CONTROL_BYTEs are as follows:

     1.  8K and 16K DATAPACKS - $6A i.e.  EPROM device,  NOT  page  counted,
         writeable, copyable and bootable.

     2.  32K, 64K and 128K DATAPACKS - $6E i.e.  EPROM device, page counted,
         writeable, copyable and bootable.

     3.  32K RAMPACK    - $6C i.e.  RAM  device,  page  counted,  writeable,
         copyable and bootable.




          __________________
11.1.1.2  DATAPACK_SIZE_BYTE

     This byte contains the number of 8K blocks in the datapack.   It  will
have one of the following values:

     1.    8K - 1

     2.   16K - 2

     3.   32K - 4

     4.   64K - 8

     5.  128K - 16




          ___________________
11.1.1.3  DEVICE_OR_CODE_BYTE

     This byte is used for descriptive purposes only.  It should be set  to
0  the  device  is  a  software  application  with  no additional hardware,
otherwise it should be set to 1.



          __________________
11.1.1.4  DEVICE_NUMBER_BYTE

     This byte contains the device number of the code extension or hardware
device.

     As more than one device can be "BOOTED" into the operating system,  it
is  necessary to have a mechanism to identify each of the devices currently
booted.  This is accomplished by having a unique  device  number  for  each
device.

     The device number is a value in the  range  $01  to  $FF.   However  a
number of these are reserved by Psion and should not be used.  The reserved
numbers are in the range $80 to $C0 and $01 to $40.  Psion already supplies
a number of devices whose device numbers are as follows:

     1.  RS232 INTERFACE - $C0

     2.  BAR CODE INTERFACE - $BF

     3.  SWIPE READER INTERFACE - $BE

     4.  CONCISE OXFORD SPELLING CHECKER - $0A


     By convention devices which do not  have  an  hardware  interface  are
allocated  device  numbers  in  the  range  $01  to  $40 and devices with a
hardware interface are allocated device numbers in the range $80 to $C0.



          ___________________
11.1.1.5  DEVICE_VERSION_BYTE

     This byte contains the release version number  of  the  device.   This
byte  is  not  used  by  the  operating  system and is only for documentary
purposes.

     By convention version numbers are N.M and the byte is  formed  by  the
following:

     VERSION_BYTE = N*16+M

     Thus for a version number of 2.3 the  byte  will  have  the  value  35
($23).



          ____________________
11.1.1.6  DEVICE_PRIORITY_BYTE

     This byte determines the  order  in  which  devices  are  booted  into
memory.

     The priority byte may have any value in the  range  $1  to  $FF.   The
higher the value the higher the priority of the device.  Thus a device with
priority $FF will be booted before a device with priority $FE.

     The DV$BOOT service  scans  all  the  slots  and  builds  a  table  of
priorities from all the bootable packs.  The priorities are then sorted and
each device is loaded in turn.  In the event of a  tie  in  priorities  the
devices will be loaded in the following order:

     1.  SIDE SLOT B - SLOT 1

     2.  SIDE SLOT C - SLOT 2

     3.  TOP SLOT - SLOT 3


     By convention priorities are the same as the device number.  Thus  the
priorities of Psion's devices are as follows:

     1.  RS232 INTERFACE - $C0

     2.  BAR CODE INTERFACE - $BF

     3.  SWIPE READER INTERFACE - $BE

     4.  CONCISE OXFORD SPELLING CHECKER - $0A


     By this convention hardware  devices  will  always  be  booted  before
software-only applications such as the concise oxford spelling checker etc.



          ________________________
11.1.1.7  DEVICE_CODE_ADDRESS_WORD

     This word contains the address on the datapack of the device  code  to
be booted into the operating system.

     If the device code immediately  follows  the  8  byte  header  on  the
datapack  then  this  address  will be 8.  However it is often desirable to
have other information on the datapack before the device code and  as  such
this word allows the device code to be anywhere on the datapack.



        ___________________________________
11.1.2  RELOCATABLE OBJECT CODE DESCRIPTION

     As device code can be loaded anywhere  in  memory,  depending  on  the
machine type (CM, XP or LA etc.), and on how many devices are loaded, it is
mandatory that the code to  be  loaded  is  in  a  relocatable  form.   The
operating system provides a service DV$LOAD which will load the relocatable
code into the machine and apply the relocation fix-ups.

     The code pointed to by the DEVICE_CODE_ADDRESS_WORD must be in Psion's
relocatable  object  code format.  The relocatable object code format is as
follows:


     1.  A word containing the number of bytes of code to be loaded.

     2.  The block of code to be loaded.

     3.  A word containing the checksum of the preceding block of code.

     4.  A word containing the number of fix-up addresses.

     5.  One word for each fix-up address.

     6.  A word containing the checksum of the preceding fix-up table.

     Psion will be supplying an assembler which will automatically generate
object   code  in  the  relocatable  format.   However  to  illustrate  the
relocatable object code format, the  following  simulates  the  relocatable
object code format using a normal assembler.

                .ORG    0
0000 0011       .WORD   CEND-CBASE              ; SIZE OF CODE
                .ORG    0
0000            CBASE:
0000 CE 2188            LDX     #RTT_BF         ; RUN TIME BUFFER
0003 86 20              LDA     A,#$20          ; SPACE CHARACTER
0005 C6 14              LDA     B,#10           ; DO 10 TIMES
0007            LOOP:
0007 A7 00              STA     A,0,X           ; STORE SPACE IN BUFFER
0009 08                 INX                     ; GO ON TO NEXT BYTE
000A 5A                 DEC     B               ; DECREASE COUNT
000B 26 03              BNE     LEND            ; FINISHED ?
000D 7E 0007    FIX1:   JMP     LOOP            ; NO - SO LOOP
0010            LEND:
0010 39                 RTS                     ; EXIT ROUTINE
0011            CEND:
0011 04E9               .WORD   $4E9            ; CHECKSUM OF CODE BLOCK
0013 0001               .WORD   <FIXEN-FIXST>/2 ; NUMBER OF FIXUPS
0015            FIXST:
0015 000E               .WORD   FIX1+1          ; ADDRESS IN CODE BLOCK
0017            FIXEN:
0017 000E               .WORD   $E              ; CHECKSUM OF FIXUPS


     The code must be assembled at address 0 as this produces  the  correct
relative addresses to the start of the code (i.e.  CBASE in this case).

     All checksums are calculated by accumulating  each  byte  in  a  word.
Overflow  is  ignored.   The following code fragment will checksum the code
between CBASE and CEND.

        LDX     #CBASE          ; START ADDRESS
        CLR     A
        CLR     B
        STD     UTW_S0:         ; START CHECKSUM AS 0
LOOP:
        CLR     A
        LDA     B,0,X           ; GET BYTE
        ADDD    UTW_S0:         ; ADD CHECKSUM
        STD     UTW_S0:         ; SAVE IT
        INX
        CPX     #CEND           ; ALL DONE
        BNE     LOOP            ; NO - SO DO MORE
        ; UTW_S0 NOW HAS THE CHECKSUM


     Each entry in the fix-up table is the word-offset of  a  word  in  the
code  which  requires  relocation.   After  the  code  block is loaded, the
DV$LOAD service adds the load address to all relative addresses in the code
which are mentioned in the fix-up table.

     If for example the above code segment was loaded at address $2000 then
the  fix-up of $E in the fix-up table, would result in $2000 being added to
the code block at address $2000+$E in memory.  This would  change  $7E  $00
$07 to being $7E $20 $07, the correct absolute address of LOOP.



        _______________________
11.1.3  DEVICE CODE DESCRIPTION

     Just loading the code from a device into memory is insufficient as the
code  itself  must  interface  to  the  operating  system.   The  following
describes the component parts of the interface between the device code  and
the operating system:

CODE_START:
        .WORD   0               ; FILLED IN BY DV$BOOT
BOOT_DEVICE:
        .BYTE   0               ; FILLED IN BY DV$BOOT
DEVICE_NUMBER:
        .BYTE   12              ; AS IN THE DATAPACK HEADER
VERSION_NUMBER:
        .BYTE   $10             ; AS IN THE DATAPACK HEADER
MAX_VECTOR_NUMBER:
        .BYTE   3               ; NUMBER OF VECTORS PROVIDED
VECTABLE:
        .WORD   INSTALL         ; INSTALL VECTOR
        .WORD   REMOVE          ; REMOVE VECTOR
        .WORD   LANG            ; LANGUAGE VECTOR
INSTALL:
        ; THE INSTALL CODE
REMOVE:
        ; THE REMOVE CODE
LANG:
        ; THE LANG CODE


     When the device is booted into memory DV$BOOT will load  the  size  of
the  code into the word at CODE_START.  This is necessary for DV$VECT to be
able to walk the list of device drivers in memory.  At the  same  time  the
slot  that the code was loaded from will be placed in BOOT_DEVICE.  This is
to allow the device to know which slot it has been booted from.  Thus if  a
device  wants  to  access  the  datapack  from which it has been booted the
following code fragment can be used:

        CLR     A               ; REPORT PACK CHANGED ERROR
        LDA     B,BOOT_DEVICE   ; SLOT DEVICE WAS BOOTED FROM
        OS      PK$SETP         ; SELECT THE SLOT

     The DEVICE_NUMBER byte and the VERSION_NUMBER byte  are  the  same  as
those  on  the  datapack  header.   DV$VECT  uses the DEVICE_NUMBER byte to
select the right device from the list in memory.  The  VERSION_NUMBER  byte
is purely for documentary purposes.

     The MAX_VECTOR_NUMBER is to allow DV$VECT to  check  that  the  device
service  exists.   Thus  if  DV$VECT  were  used  to call the device in the
example above to perform vector  number  3,  it  would  fail,  as  it  only
provides vectors 0,1 and 2.

     There follows a list of vectors which are used to jump to  appropriate
parts  of the code in the device.  DV$VECT is called with the device number
in the A register and the vector number to be called  in  the  B  register.
DV$VECT will scan the devices in memory to see if the device is present and
if it is, it will then jump to the appropriate vector.   All  devices  must
provide vectors 0,1 and 2, and may provide up to 255.



        ______________
11.1.4  DEVICE VECTORS

     Every device which is loaded into the operating system  has  a  vector
table  which directs the operating system to the appropriate code to handle
that "VECTOR SERVICE".  The  first  3  "VECTOR  SERVICES"  have  a  defined
meaning as follows:



          _________________________________
11.1.4.1  VECTOR SERVICE 0 - INSTALL VECTOR

     Whenever the DV$BOOT service loads a device it will call  the  INSTALL
vector immediately after loading the device.  This will give the device the
opportunity to initialise itself.

     For example the RS232 interface installs the COMMS menu item into  the
top level menu at this stage by calling the system service TL$ADDI.

     On completing the required INSTALL code the device  should  clear  the
carry flag and then do an RTS instruction.

     If for any reason the INSTALL  code  decides  the  device  cannot  run
properly  (i.e.   not  enough memory), then the carry flag should be set and
the device will not be installed (and no RAM  will  be  allocated  for  the
device).

     A device need not return from this vector, in which case it will  have
effectively taken control of the machine.

     NOTE:  The DV$BOOT service uses the first 4 bytes of RTT_FF  to  build
the priorities table and as such the install vector should preserve these 4
bytes if there is any danger of them being corrupted.  See the  description
of DV$BOOT for more details.



          ________________________________
11.1.4.2  VECTOR SERVICE 1 - REMOVE VECTOR

     When DV$BOOT is called, it first calls DV$CLER, which calls the REMOVE
vector  for  each device.  DV$BOOT then zeroes the permanent cell (and also
resets DVA_TOP in LA/OS) effectively discarding all  devices.   The  REMOVE
code  can  then tidy up anything that needs to be done before the device is
thrown out of memory.  For example the RS232 interface  removes  the  COMMS
menu  item  from  the main menu in its REMOVE code.  The REMOVE code should
terminate with the carry clear and an  RTS  instruction,  even  though  any
errors are ignored.



          __________________________________
11.1.4.3  VECTOR SERVICE 2 - LANGUAGE VECTOR

     This vector service is required by the OPL language and  provides  the
mechanism by which the language can be extended.

     For any procedure called in an OPL program, the  language  will  first
call  the  DV$LKUP service to see if any devices are prepared to handle the
procedure.  To do this DV$LKUP calls each device's LANGUAGE vector with the
X  register  pointing to a leading count-byte string containing the name of
the procedure.  If none of the devices are prepared to handle the procedure
then  the  language  will  search packs A,B,C and D for an OPL procedure of
that name.

     If a device is prepared to handle the procedure then  it  will  return
its  device  number  in  the A register and the vector service number which
will handle the procedure in  the  B  register.   The  language  will  then
immediately call the DV$VECT service which will call the vector service.

     For example the RS232 interface provides a language procedure LINPUT$:
and as such the LANGUAGE vector code can be coded as follows:
LANGUAGE_VECTOR:
        LDD     0,X             ; GET SIZE AND FIRST LETTER
        SUBD    #<7*256>+'L'    ; COMPARE
        BNE     NOT_LINPUT      ; NOT A MATCH
        LDD     2,X             ; GET NEXT TWO LETTERS
        SUBD    #<^A'I'*256>+'N'        ; COMPARE
        BNE     NOT_LINPUT
        LDD     4,X             ; GET NEXT TWO LETTERS
        SUBD    #<^A'P'*256>+'U'        ; COMPARE
        BNE     NOT_LINPUT
        LDD     6,X             ; GET NEXT TWO LETTERS
        SUBD    #<^A'T'*256>+'$'        ; COMPARE
        BNE     NOT_LINPUT
        LDA     A,#$C0          ; RS232 DEVICE NUMBER
        LDA     B,#4            ; VECTOR NUMBER TO HANDLE LINPUT$
        CLC
        RTS
NOT_LINPUT:                     ; NOT LINPUT$ - SO NOT PREPARED TO
        SEC                     ; HANDLE THE PROCEDURE
        RTS

NOTE:  As the LANGUAGE vector code is called every time an OPL procedure is
executed,  it  is  important  that  the  LANGUAGE vector code be as fast as
possible.  Clearly a device can handle as many procedures as  necessary  by
just  chaining  the name matches or by searching a list of procedures.  See
the chapter on the language as to  how  OPL  passes  parameters  to  device
procedure handlers and how devices pass back their results.



        _______
11.1.5  BOOTING

     The system service DV$BOOT is responsible  for  loading  and  removing
devices.   The  area in which devices are loaded in the operating system is
one of the pre-allocated cells, known as the PERMANENT cell.  This  cell  is
the  first allocated cell and has the unique property that it will never be
moved by the allocator (i.e.  it always has the same base address).  This is
obviously  crucial  as once the code has been relocated in memory it can no
longer be moved,  as  all  addresses  have  been  converted  from  relative
addresses to absolute addresses.

     LA/OS also uses the RAM from $0400 to $2000 ('low'  RAM)  for  loading
device  code,  in  preference to the permanent cell.  This means that while
devices occupy less than 7k in total, the full 24k will  be  available  for
the  user.   Note that INFO will only show devices which are occupying high
memory - i.e.  any devices which have overflowed the 7k.  For  example  with
an  RS232,  a barcode reader, and a concise oxford spelling checker booted,
only the spelling checker will be loaded into the permanent  cell  in  high
memory,  so  INFO  will  report  DEVICES  xxx%.   The addresses DVA_BOT and
DVA_TOP mark the  lower  and  upper  limits  of  low  memory.   DVA_TOP  is
increased as devices are loaded into low memory.  DVA_BOT is initialised to
$0400 at cold startup.

     The operations performed by DV$BOOT are as follows:

     1.  Run the DV$CLER service, which will call  the  REMOVE  service  of
         each  device  currently  in  memory.   DV$BOOT  will then zero the
         PERMANENT cell.  LA/OS will also clear the low memory  by  setting
         DVA_TOP to DVA_BOT.

     2.  Scan the three slots for datapacks which are bootable.

     3.  Build a table of the priorities of each device in RTT_FF.

     4.  Scan the table for the highest priority device.

     5.  Grow the PERMANENT cell by the size of the device at  the  end  of
         the PERMANENT cell (see below for LA/OS).

     6.  Load the code into the area just allocated.

     7.  Store the size of the code into the first word.

     8.  Store the slot number that the code was loaded from in  the  third
         byte of the device code.

     9.  Call DV$VECT to execute the INSTALL vector of the device.

    10.  Zero the priority in the table in RTT_FF.

    11.  Repeat the above until all priorities in the table are zero.


LA/OS only :

     In LA/OS, the device code is loaded into 'low' memory  from  $0400  to
$2000  ,  and  the address of the byte after the code is stored in DVA_TOP.
If there is not enough room in low memory, the device code and any  further
devices will be loaded into the permanent cell.

     The DV$BOOT service is called by the  operating  system  when  a  cold
start is required.  Thereafter it is only ever called when the ON/CLEAR key
is pressed when in the main menu (i.e.  at the top level).  Thus to  load  a
device  into  the  operating  system  the  device  must be plugged into the
machine and the ON/CLEAR key pressed until the main menu is reached.   Then
one  further key press will execute the DV$BOOT service and load the device
driver.  There is no problem in booting the device drivers  any  number  of
times.

     To remove a device from memory, simply  remove  the  device  from  the
machine  and  repeat  the  above  procedure  (i.e.  perform a boot).  As the
device is no longer present and as DV$BOOT always throws  out  all  devices
before performing the boot procedure the device will be effectively removed
from memory.

     NOTE:  As DV$BOOT always throws all devices out of memory,  no  device
can  call  the  DV$BOOT service.  In order for a device to call the DV$BOOT
service it must copy a routine to call DV$BOOT into some  safe  portion  of
memory  and  then  jump to that code.  The code can then either get back to
device, which will have been reloaded, through a known  vector  service  or
just simply return from whence it came.

     Opl programs, because they are running in a different area  of  memory
can easily call the DV$BOOT service as follows:

REBOOT:
REM BOOTS ANY DEVICES INTO MEMORY
LOCAL I%,CODE$(4)
I%=ADDR(CODE$)+1 : REM SKIP THE SIZE BYTE OF THE STRING
POKEB I%,$3F : REM THE SWI INSTRUCTION
POKEB I%+1,23 : REM THE DV$BOOT VECTOR NUMBER
POKEB I%+2,$39 : REM A RETURN INSTRUCTION
USR(I%,0) : REM CALL THE MACHINE CODE

     This procedure when called will execute the DV$BOOT service.

The following procedure will remove all devices from memory :

        OS      DV$CLER         ; run 'remove' vector of all devices
        LDX     DVA_BOT         ; reset low memory (harmless on non-LA/OS)
        STX     DVA_TOP
        LDX     #$2000          ; zero the permanent cell
        OS      AL$ZERO


     In LA/OS, the user can reserve an area of low memory from $400 upwards
as follows :

        LDA     A,$FFE8         ; TEST OS/VERSION
        BIT     A,#1            ; BIT 1 IS SET IF LA/OS
        BEQ     NOT_LA
                                ; LA/OS ONLY ...
        OS      DV$CLER         ; RUN 'REMOVE' VECTOR OF ALL DEVICES
        LDX     #$2000          ; AND KILL ANY DEVICES IN HIGH MEMORY
        OS      AL$ZERO         ; SO THAT THEY CANNOT BE ACCESSED AFTER THEIR
                                ; REMOVE VECTORS HAVE BEEN CALLED
        LDX     #SPACE_REQUIRED+$400
        STX     DVA_BOT         ; SET BASE ADDRESS FOR FUTURE DEVICE LOADING
        STX     DVA_TOP         ; SET TOP= NEW BOTTOM
                                ; RAM NOW AVAILABLE from $0400 - DVA_BOT-1




      ___________________
11.2  HARDWARE INTERFACES

     The Organiser has three slots available in which  hardware  interfaces
can be fitted.  The TOP slot (slot 3) is the preferred slot for interfacing
to external hardware as it has been especially designed for  this  purpose.
However  there  is  no reason why one of the two SIDE slots (slots 1 and 2)
cannot be used.  There are however  a  number  of  fundamental  differences
between the top slot and the side slots.

     In general though the 16 way connectors  from  the  Organiser  to  the
outside  world  constitute a proprietary BUS and as such there are a number
of rules from a  hardware  and  software  point  of  view  with  regard  to
interfacing on this BUS.

     In the following sections the BUS components which are common  to  all
slots  are  described  followed by a section on the differences between the
TOP slot and the SIDE slots.



        ___________
11.2.1  BUS SIGNALS

    ___________    ___________
    SIGNAL NAME -  DESCRIPTION
    SD0         - Data Bit 0
    SD1         - Data Bit 1
    SD2         - Data Bit 2
    SD3         - Data Bit 3
    SD4         - Data Bit 4
    SD5         - Data Bit 5
    SD6         - Data Bit 6
    SD7         - Data Bit 7
    SOE_B       - Output Enable (active low)
    SMR         - Master Reset (active high)
    SCLK        - CLocK
    SSS_B       - Slot Select (active low)
    SVCC        - + 5 Volts
    SGND        - 0 Volts


     This common set of signals comprise the Psion proprietary  BUS.   This
BUS  was  designed to allow Psion's removable DATAPACKS to be interfaced to
the Organiser.  In order to understand the following description of the BUS
it  is necessary to have read and understood the chapter on DATAPACKS.  All
Psion's hardware interfaces consist of both a  DATAPACK  and  the  hardware
interface itself.  The DATAPACK is used to provide the DEVICE code which is
loaded  into  the  operating  system,  thereby   providing   the   software
interfacing to the OPL language and the operating system itself.

     While it is possible to build hardware interfaces without  a  DATAPACK
on  board,  it is strongly recommended that this is not done.  The DATAPACK
not only provides a mechanism for adding extra  code  associated  with  the
hardware,  it  also  provides  a  means  whereby the code can tell that the
interface is still plugged  into  the  machine  by  reading  the  on  board
DATAPACK.



          _____
11.2.1.1  SSS_B

     This signal is the SLOT select  signal  and  is  used  to  select  the
hardware  plugged  into that slot.  At no time should an external interface
try to control any of the signals on the BUS until SSS_B goes  low.   There
is obviously one SSS_B signal for each slot in the machine.

     It is also mandatory that none of  the  control  signals  on  the  BUS
should be changed while SSS_B is active.  The control signals are SOE_B,SMR
and SCLK.  Thus to change a signal, SSS_B should be raised high,  then  the
signal changed and finally SSS_B can be lowered again.

     This signal should always be pulled up through a resistor to SVCC.



          _____
11.2.1.2  SOE_B

     The SOE_B signal is  used  to  select  between  the  DATAPACK  on  the
interface  and  the  external  hardware.   With SOE_B low (i.e.  active) the
DATAPACK should output its contents onto the DATABUS.  With SOE_B high then
the hardware interface circuitry should control the DATABUS.

     This signal should always be pulled down through a resistor to 0 volts



          ___
11.2.1.3  SMR

     The SMR signal is used to reset the counters on the DATAPACK to 0.  It
is  also  used  in  a  number  of  combinations with SOE_B high.  These are
explained in a table later on.

     This signal should always be pulled down through a resistor to 0 volts



          ____
11.2.1.4  SCLK

     The SCLK signal is used to clock the counters on the DATAPACK.  It can
also  be  used  in  a  number  of  combinations  with  SMR high.  These are
explained in a table later.

     This signal should always be pulled down through a resistor to 0 volts



          ____
11.2.1.5  SVCC

     This is the +5 Volt rail to external hardware interfaces.  It  can  be
used  to  supply  power  to  the  hardware  interface.   Note that when the
Organiser is switched  off,  this  voltage  will  no  longer  be  supplied.
Instead  a small amount of current can be drawn from the SSS_B signal as it
is supplied from a different power rail.  See Chapter 3 for more details.



          ____
11.2.1.6  SGND

     This is the 0 Volt signal to the interface.



          _______
11.2.1.7  SD0-SD7

     These 8 signals represent an 8 bit DATABUS between  the  hardware  and
the  Organiser.   Since  it  is  possible  to  both  read from and write to
hardware interfaces, this bus is BI-DIRECTIONAL.  However  the  rest  state
when not in use is always INPUT to the Organiser.  i.e.  The PORT2 direction
register should be set to 0 so that all bits of PORT2 are  input.   If  the
direction  of  these  bits are ever changed to OUTPUT they must be reset to
INPUT before control is  passed  back  to  the  operating  system,  as  the
operating system assumes that PORT2 is always configured as all INPUT.

     All these signals are pulled down internally to  the  Organiser  to  0
Volts.  Thus if no interface is present in a slot the Organiser will always
read a 0.  This is used by the operating system to determine that a pack is
not present in the slot.



        __________________
11.2.2  SIGNAL TRUTH TABLE

     The following truth table represents the rules by which the  interface
should respond to various states of the BUS control signals.  In the table,
HIGH stands for +5 Volts, LOW stands for 0 Volts and  X  stands  for  don't
care.

    _____ _____ _____ ___   ____  ___________
    STATE SSS_B SOE_B SMR   SCLK  DESCRIPTION

      0   HIGH   X     X     X    All interfaces de-selected.
      1   LOW   HIGH  HIGH   X    128K datapack device segment select.
      2   LOW   HIGH  LOW    X    Hardware interface select.
      3   LOW   LOW   HIGH  HIGH  Ram datapack ID 1 select.
      4   LOW   LOW   HIGH  LOW   Ram datapack ID 0 select.
      5   LOW   LOW   LOW    X    Datapack read select.

    When the operating system selects a slot using the PK$SETP service, it
will place the control signals in a number of these states. However the top
slot is treated slightly differently, in that the operating system will not
support 128K datapacks or RAM packs. Thus the following states can occur
for the various slots:

    _____    ______     ___________
    STATE    SLOT 3     SLOTS 1 & 2
      0        Yes          Yes
      1        No           Yes
      2        No           No
      3        No           Yes
      4        No           Yes
      5        Yes          Yes



          _______
11.2.2.1  STATE 0

    SSS_B  - HIGH
    SOE_B  - X
    SMR    - X
    SCLK   - X


     This state is the rest state for all interfaces.  When SSS_B  is  high
no  interface should be trying to control the databus or any of the control
signals.



          _______
11.2.2.2  STATE 1

    SSS_B  - LOW
    SOE_B  - HIGH
    SMR    - HIGH
    SCLK   - X


     This state is used by the operating system to select the  16K  segment
in  128K  datapack devices.  Note that PK$SETP selects segment 0 every time
it is called regardless of the device fitted in the slot.  As the operating
system  does  not  support  128K datapack in the top slot this state can be
used to select devices in a hardware interface to be used in the top slot.



          _______
11.2.2.3  STATE 2

    SSS_B  - LOW
    SOE_B  - HIGH
    SMR    - LOW
    SCLK   - X


     This state is used to write data to either datapacks or RAM  packs  in
the  slots.   However  if the RDWRT bit in the ID byte of the pack is clear
then the operating system will never place state 2 on the control lines.

     This state is the only state that can be used safely to  select  other
devices than the datapack on interfaces intended for use in the side slots.



          _______
11.2.2.4  STATE 3

    SSS_B  - LOW
    SOE_B  - LOW
    SMR    - HIGH
    SCLK   - HIGH


     This state is used by the operating system  to  select  the  RAM  pack
hardware  ID  1.   Note  that PK$SETP uses this state to determine if a RAM
pack is plugged into a slot.  If so, it will output an  ID  byte  of  1  in
response  to this state.  Note that datapacks output byte 1 of the EPROM in
response to this state.

     As the operating system does not support RAM packs  in  the  top  slot
this state can be used to select devices in a hardware interface to be used
in the top slot.



          _______
11.2.2.5  STATE 4

    SSS_B  - HIGH
    SOE_B  - X
    SMR    - X
    SCLK   - X


     This state is used by the operating system  to  select  the  RAM  pack
hardware  ID  0.   Note  that PK$SETP uses this state to determine if a RAM
pack is plugged into a slot.  If so, it will output an  ID  byte  of  1  in
response  to this state.  Note that datapacks output byte 0 of the EPROM in
response to this state.

     As the operating system does not support RAM packs  in  the  top  slot
this state can be used to select devices in a hardware interface to be used
in the top slot.



          _______
11.2.2.6  STATE 5

    SSS_B  - HIGH
    SOE_B  - LOW
    SMR    - LOW
    SCLK   - X


     This state is used by the operating  system  to  read  data  from  the
datapack circuitry on interfaces.  The byte, corresponding to the currently
selected address on the datapack counters, is  output  on  the  databus  in
response to this state.



        _______
11.2.3  EXAMPLE

     Take as an example the BARCODE interface for the top slot, as supplied
by Psion.

     The interface has two discrete interface circuits:

     1.  The datapack circuitry.

     2.  The BARCODE input signal buffer circuitry.


     State 5 is used to enable the  EPROM  in  the  datapack  circuitry  to
output its contents on the databus.

     States 1 and 2 are used to enable  the  BARCODE  input  signal  buffer
circuitry.  i.e.  SSS_B - low,SOE_B - high,SMR - X,SCLK - X.

     Thus to select the  BARCODE  datapack  circuitry  the  following  code
fragment can be used:

SELECT_PACK:
        CLR     A
        LDA     B,#PAKD
        OS      PK$SETP
        ; THE DATAPACK IS NOW SELECTED
        ; THE CONTROL LINES ARE IN STATE 5


     To select the BARCODE input signal buffer circuitry the following code
fragment can be used:

SELECT_BARCODE:
        CLR     A
        LDA     B,#PAKD
        OS      PK$SETP                 ; SELECT DATAPACK AS NORMAL
        OIM     #CS3,POB_PORT6          ; DESELECT THE SLOT
        OIM     #OE,POB_PORT6           ; SET SOE_B HIGH
        AIM     #^C<CS3>,POB_PORT6      ; SELECT THE SLOT AGAIN
        ; BARCODE HARDWARE NOW SELECTED
        ; THE CONTROL LINES ARE IN STATE 1 OR 2


     NOTE:  Before control can be returned to the operating system, STATE 5
must  be  re-established  on  the  control  lines.  This can be achieved as
follows:

DESELECT_BARCODE:
        OIM     #CS3,POB_PORT6          ; DESELECT THE SLOT
        AIM     #^C<OE>,POB_PORT6       ; SET SOE_B LOW
        OIM     #^C<CS3>,POB_PORT6      ; SELECT THE SLOT AGAIN


     NOTE:  Control lines should only ever change state  when  the  control
lines are in STATE 0, i.e.  the interface is de-selected.



      _______________
11.3  SYSTEM SERVICES

        _______
11.3.1  DV$BOOT

VECTOR NUMBER:          023
INPUT PARAMETERS:       None
OUTPUT VALUES:          None

DESCRIPTION


     Will boot all devices plugged into the Organiser.

     Any errors occurring during the boot are reported directly through the
ER$MESS  service.   However  the  screen  is  saved  before  the message is
displayed and restored immediately  afterwards.   This  is  done  with  the
DP$SAVE  and  DP$REST  services.  Thus the screen is preserved if any error
messages are reported.

ERRORS:                 None


        _______
11.3.2  DV$LOAD

VECTOR NUMBER:          026
INPUT PARAMETERS:       D register - Address to load the overlay.
                        X register - Address on pack of overlay.
                        UTW_S0     - Relocation address.
OUTPUT VALUES:          None

DESCRIPTION


     This service will load  code  from  a  pack  which  is  in  the  Psion
relocatable  object format.  It loads the code into memory and then applies
any fix-ups that are required, thus relocating the code to the  address  as
specified in UTW_S0.

     The D register has the address in memory  where  the  code  is  to  be
loaded.   This  can be anywhere that the code will be safe from overwriting
but will normally be in the PERMANENT cell or above the LANGUAGE stack.

     The X register has the address on the pack at  which  the  relocatable
object  code starts.  The first two bytes of the relocatable object code is
of course the size of the code block.

     UTW_S0 has the address  at  which  to  relocate  the  code  (i.e.   the
intended  execution  address.  For code produced by the Psion assembler and
in almost all cases this address will be the same as the address passed  in
the D register.

     This routine assumes that the correct slot has already  been  selected
with the PK$SETP service.

EXAMPLE


     Assume that there is a device to be loaded from a pack in slot  B  and
that  the  code  starts  at  address $2400 on the pack.  This device can be
added to the other devices in the PERMANENT CELL with  the  following  code
fragment:

        CLR     A               ; DONT REPORT PACK CHANGED
        LDA     B,#PAKB         ; SELECT SLOT B
        OS      PK$SETP
        BCS     ERROR           ; PACK ERROR
        CLR     B
        LDX     #$2400          ; ADDRESS $2400 ON PACK
        OS      PK$SADD
        OS      PK$RWRD         ; READ THE SIZE OF THE CODE
        STD     UTW_R1:         ; SAVE THE CODE SIZE
        LDX     #ALT_BASE       ; TAG OF PERMANENT CELL
        PSHX                    ; SAVE IT
        OS      AL$SIZE         ; GET THE CURRENT SIZE
        STD     UTW_S0:         ; GROW AT END OF CELL
        STD     UTW_R0:         ; SAVE THE CURRENT CELL SIZE
        LDD     UTW_R1:         ; GET THE CODE SIZE
        PULX                    ; RESTORE IT
        OS      AL$GROW         ; GROW THE CELL
        BCS     ERROR           ; NOT ENOUGH MEMORY
        LDD     UTW_R0:         ; GET OLD CELL SIZE
        ADDD    ALT_BASE        ; ADD THE BASE OF THE CELL
        STD     UTW_R0:         ; SAVE THE LOAD ADDRESS
        STD     UTW_S0:         ; RELOCATE ADDRESS
        LDX     #$2400          ; ADDRESS ON PACK
        OS      DV$LOAD         ; LOAD THE CODE
        BCS     ERROR           ; FAILED TO LOAD
        LDX     UTW_R0:         ; BASE ADDRESS OF CODE
        LDD     UTW_R1:         ; SIZE OF CODE BLOCK
        STD     0,X             ; PATCH THE DEVICE CODE
        LDA     A,#PAKB         ; DEVICE LOADED FROM
        STA     A,2,X           ; PATCH THE DEVICE CODE
        ; DEVICE NOW LOADED INTO MEMORY

ERRORS:                 ER_DV_CS - CODE CHECKSUM ERROR
                        ER_PK_NP - NO PACK IN SLOT
                        ER_PK_DV - BAD DEVICE NAME
                        ER_PK_CH - PACK CHANGED
                        ER_PK_IV - UNKNOWN PACK TYPE
                        ER_PK_BR - PACK BAD READ ERROR


        _______
11.3.3  DV$VECT

VECTOR NUMBER:          027
INPUT PARAMETERS:       A register - DEVICE NUMBER TO CALL.
                        B register - VECTOR NUMBER.
OUTPUT VALUES:          None.

DESCRIPTION


     This service will search the devices  in  the  PERMANENT  cell  for  a
device whose device number matches that in the A register.  If no device is
found then the ER_DV_NP error is returned.

     Then the service checks that the vector number in the  B  register  is
not  greater than the maximum vector number supported by the device.  If it
is, then an error ER_DV_CA is returned.  Otherwise the  appropriate  vector
is loaded from the device vector table and a JMP is done to the vector.

     DV$VECT passes the X register and the scratch register UTW_S0  through
to  the  vectored  routine, so that these may be used to pass parameters to
the device vector routine.  It is up to  the  device  to  specify  what  is
passed  and  what  is  returned.   DV$VECT  returns  the same things as the
vectored routine.

EXAMPLE

     To send a string in the X  register  using  the  RS232  interface  the
following code fragment can be used:

CALL_LPRINT:
        PSHX                            ; SAVE THE STRING TO BE PRINTED
        LDX     #LPRINT_NAME            ; POINT TO LPRINT
        OS      DV$LKUP                 ; SEARCH FOR RS232 DEVICE
        PULX                            ; RESTORE THE STRING
        BCS     ERROR                   ; UNABLE TO HANDLE LPRINT
        OS      DV$VECT                 ; EXECUTE THE LPRINT SERVICE
        BCS     ERROR                   ; SERVICE FAILED
        RTS                             ; STRING NOW PRINTED
LPRINT_NAME:
        .BYTE   6
        .ASCII  "LPRINT"


     The LPRINT handler in the RS232 interface requires the  string  to  be
printed  to  be  a  leading byte count string and the address of the string
must be in the X register.

ERRORS:                 ER_DV_CA - VECTOR NUMBER NOT SUPPORTED.
                        ER_DV_NP - DEVICE NOT PRESENT.
                        ANY OTHERS THE DEVICE MAY RETURN.


        _______
11.3.4  DV$LKUP

VECTOR NUMBER:          025
INPUT PARAMETERS:       X register - ADDRESS OF NAME STRING.
OUTPUT VALUES:          A register - DEVICE NUMBER.
                        B register - VECTOR NUMBER.

DESCRIPTION


     This service will call the LANGUAGE vector of all  devices  in  memory
and  pass them the value in the X register.  If a device signals that it is
prepared to handle the procedure whose name is pointed to by the X register
then  the  service  will  return  with the A register containing the device
number and the B register containing the  vector  number  of  the  code  to
handle the procedure.

EXAMPLE

     To determine the device number and vector number of the RS232  service
which  is  prepared  to  handle  the  LINPUT$  procedure the following code
fragment can be used.

        LDX     #LINPUT_NAME            ; POINT TO LINPUT$
        OS      DV$LKUP                 ; SEARCH FOR RS232 DEVICE
        BCS     ERROR                   ; UNABLE TO HANDLE LINPUT$
        STD     LVECT                   ; SAVE A&B FOR LATER USE
        RTS                             ; WITH DV$VECT
LINPUT_NAME:
        .BYTE   7
        .ASCII  "LINPUT$"
LVECT:
        .WORD   0

ERRORS:                 ER_DV_NP - DEVICE NOT PRESENT.


        _______
11.3.5  DV$CLER

VECTOR NUMBER:          024
INPUT PARAMETERS:       None
OUTPUT VALUES:          None

DESCRIPTION


     This service calls the REMOVE vector  for  all  devices  currently  in
memory.

     It is usually called by DV$BOOT just before zeroing the permanent cell
in preparation to booting.

ERRORS:                 None



        _______
11.3.6  EXAMPLE

     Below is an example of a device to allow the operating system services
to  be  called from OPL procedures.  The device is a procedure called SWI%:
and it takes as its argument an integer specifying which service should  be
invoked.

     Most functions also require values in the A,B(D) or X register as well
and so the function requires 2 global variables X% and D% to be declared by
the OPL procedure.  Any values to be passed in UTW_S0 or UTW_S1 can be  set
with POKEW before calling SWI%:.

     SWI%:(FUNCTION%)

     If carry is clear after the call to the operating system  SWI%:   will
return 0 and if it is set, SWI%:  will return -1 and D% will have the error
number.

     If SWI%:  signals success D% and X% will  be  set  directly  from  the
machine registers D and X.

     NOTE:  The D register is actually made up of the A register and the  B
register so that D = A*256 + B.  The global variable D% mimics this.  So if
a routine requires A = 1 and B = 3 this can be passed as D =  1*256+3.   On
return the values of A and B can be determined from D% as follows:

     A% = (D%/256)

     B% = (D% AND 255)

     The following is the full code listing for the routine.

0000                    TTL     EXSWI
0000           ;
0000           ; DEVICE EXTENSION TO INTERFACE SWI
0000           ; CALLS TO THE OPL LANGUAGE
0000           ;
0000 =00E0     D_ADDR           EQU     ^XE0
0000 =00E2     X_ADDR           EQU     ^XE2
0000 =00A5     RTA_SP    EQU    ^XA5
0000 =00A7     RTA_FP    EQU    ^XA7
0000 =2187     RTB_BL    EQU    ^X2187
0000 =0041     UTW_S0    EQU    ^X41
0000 =00CC     ER_RT_UE  EQU    204
0000 =00CD     ER_RT_NP  EQU    205
0000 =00E0     ER_EX_TV  EQU    224
0000 =00F7     ER_FN_BA  EQU    247
               ;
0000                ORG         ^X241b-25
2402           XX:
2402 6A            .BYTE        ^X6A            ; DATAPACK BOOTABLE
2403 02            .BYTE        ^X02            ; 16K DATAPACK
2404 00            .BYTE        0               ; NO HARDWARE
2405 02            .BYTE        2               ; DEVICE NUMBER
2406 10            .BYTE        ^X10            ; VERSION NUMBER (1.0)
2407 02            .BYTE        2               ; PRIORITY
2408 0000-         .WORD        %ROOT-2         ; ROOT OVERLAY ADDRESS
240A FF            .BYTE        ^XFF
240B FF            .BYTE        ^XFF
240C 09            .BYTE        ^X09
240D 81            .BYTE        ^X81
240E 4D 41         .ASCII       "MAIN    "
2410 49 4E
2410 20 20
2412 20 20
2416 90            .BYTE        ^X90
2417 02            .BYTE        ^X02
2418 80            .BYTE        ^X80
2419 0000-         .WORD        %PRGEND-%ROOT+2 ; SIZE OF CODE
241B           ;
241B           ; START ROOT OVERLAY
241B           ; ==================
241B           ;
241B               .OVER        ROOT
241B           CODELEN:
241B 0000          .WORD        0000            ; SET BY DV$BOOT
241D           BDEVICE:
241D 00            .BYTE        00              ; SET BY DV$BOOT
241E           DEVNUM:
241E 02            .BYTE        2               ; DEVICE NUMBER
241F           VERNUM:
241F 10            .BYTE        ^X10            ; VERSION 1.0
2420           MAXVEC:
2420 00-           .BYTE        <ENDVEC-VECTABLE>/2 ; NUMBER OF VECTORS
2421           VECTABLE:
2421 0000-         .WORD        INSTALL         ; INSTALL VECTOR
2423 0000-         .WORD        REMOVE          ; REMOVE VECTOR
2425 0000-         .WORD        LANG            ; LANGUAGE VECTOR
2427 0000-         .WORD        DO_SWI          ; HANDLE SWI%: VECTOR
2429           ENDVEC:
2429           ;
2429           ; INSTALL VECTOR - DOES NOTHING
2429           ; ==============
2429           ;
2429           INSTALL:
2429 0C            CLC                  ; SIGNAL SUCCESS
242A 39            RTS
242B           ;
242B           ; REMOVE VECTOR - DOES NOTHING
242B           ; =============
242B           ;
242B           REMOVE:
242B 0C            CLC                  ; SIGNAL SUCCESS
242C 39            RTS
242D           ;
242D           ; LANGUAGE VECTOR - RECOGNIZES 'SWI%:'
242D           ; ===============
242D           ;
242D           LANG:
242D EC 00         LDD  0,X
242F 83 0453       SUBD #<4*256>+^A'S'
2432 26 00-        BNE  NOT_SWI
2434 EC 02         LDD  2,X
2436 83 5749       SUBD #<^A'W'*256>+^A'I'
2439 26 00-        BNE  NOT_SWI
243B A6 04         LDA  A,4,X
243D 81 25         CMP  A,#^A'%'
243F 26 00-        BNE  NOT_SWI
2441 86 02         LDA  A,#2            ; DEVICE 2
2443 C6 03         LDA  B,#3            ; DO_SWI VECTOR SERVICE NUMBER
2445 0C            CLC                  ; SIGNAL SUCCESS
2446 39            RTS
2447           NOT_SWI:
2447 0D            SEC                  ; SIGNAL NOT PREPARED TO HANDLE
2448 39            RTS                  ; REQUEST
               ;
               ; DO_SWI VECTOR - ACTUALLY DOES THE SWI%:
               ; =============
               ;
2449           DO_SWI:
2449 DE A5         LDX  RTA_SP:         ; GET LANGUAGE STACK
244B A6 00         LDA  A,0,X           ; GET NUMBER OF ARGUMENTS
244D 4A            DEC  A               ; CHECK IF 1
244E 27 00-        BEQ  ARG_OK          ; YES - SO CORRECT ARG COUNT
2450 C6 CD         LDA  B,#ER_RT_NP     ; SIGNAL WRONG NUMBER OF ARGUMENTS
2452           BAD_EXIT:
2452 0D            SEC                  ; SIGNAL BAD CALL
2453 39            RTS
2454           ARG_OK:
2454 A6 01         LDA  A,1,X           ; GET ARGUMENT TYPE
2456 27 00-        BEQ  ARG_INT         ; ZERO - SO ARG IS INTEGER
2458 C6 E0         LDA  B,#ER_EX_TV     ; SIGNAL TYPE VIOLATION
245A 20 F6         BRA  BAD_EXIT
245C           ARG_INT:
245C EC 02         LDD  2,X             ; GET SWI FUNCTION TO DO
245E 83 0080       SUBD #^X80           ; MAXIMUM FUNCTION + 1
2461 25 00-        BCS  FUNCTION_OK     ; GOOD FUNCTION RANGE 0-127
2463 C6 F7         LDA  B,#ER_FN_BA     ; SIGNAL BAD FUNCTION ARGUMENT
2465 20 EB         BRA  BAD_EXIT
2467           FUNCTION_OK:
2467 CB 80         ADD  B,#^X80         ; GET BACK THE FUNCTION NUMBER
2469 F7 0000-      STA  B,SWI_FUNC      ; PATCH THE CODE TO DO SWI FUNCTION
246C           ;
246C           ; LOOKUP THE GLOBALS D% AND X% IN THE CALLING PROCEDURE
246C           ; EXTERNALS ARE STORE AS LENGTH OF NAME FOLLOWED BY NAME
246C           ; FOLLOWED BY TYPE FOLLOWED BY 2 BYTE ADDRESS
246C           ;
246C CC 0000       LDD  #0
246F DD E0         STD  D_ADDR:         ; MARK D NOT FOUND
2471 DD E2         STD  X_ADDR:         ; MARK X NOT FOUND
2473 DE A7         LDX  RTA_FP:         ; GET THE FRAME POINTER
2475 09            DEX
2476 09            DEX
2477 DF 41         STX  UTW_S0:         ; SAVE END OF GLOBALS TABLE
2479 EE 00         LDX  0,X             ; ADDRESS OF BASE OF GLOBALS TABLE
247B           LOOP:
247B 9C 41         CPX  UTW_S0:         ; SEARCHED WHOLE TABLE YET
247D 27 00-        BEQ  TEST_OK         ; FINISHED
247F EC 00         LDD  0,X
2481 83 0244       SUBD #<2*256>+^A'D'  ; CHECK IF D%
2484 26 00-        BNE  CHECK_X         ; NO - SO CHECK X
2486 EC 02         LDD  2,X
2488 83 2500       SUBD #<^A'%'*256>    ; CHECK IF D%
248B 26 00-        BNE  CHECK_X         ; NO - SO CHECK X
248D EC 04         LDD  4,X             ; ADDRESS OF D
248F DD E0         STD  D_ADDR:         ; SAVE IT AWAY
2491 20 00-        BRA  NEXT_EXT        ; GO LOOK UP THE OTHERS
2493           CHECK_X:
2493 EC 00         LDD  0,X
2495 83 0258       SUBD #<2*256>+^A'X'  ; CHECK IF X%
2498 26 00-        BNE  NEXT_EXT        ; NO - SO CHECK NEXT
249A EC 02         LDD  2,X
249C 83 2500       SUBD #<^A'%'*256>    ; CHECK IF X%
249F 26 00-        BNE  NEXT_EXT        ; NO - SO CHECK NEXT
24A1 EC 04         LDD  4,X             ; ADDRESS OF X%
24A3 DD E2         STD  X_ADDR:         ; SAVE IT AWAY
24A5           NEXT_EXT:
24A5 E6 00         LDA  B,0,X           ; GET LENGTH OF NAME
24A7 CB 04         ADD  B,#4            ; SKIP TO NEXT NAME
24A9 3A            ABX
24AA 20 CF         BRA  LOOP
24AC           TEST_OK:
24AC DE E2         LDX  X_ADDR:         ; GET X'S ADDRESS
24AE 26 00-        BNE  X_FOUND
24B0 C6 58         LDA  B,#^A'X'        ; SIGNAL X% NOT DECLARED
24B2           SET_MISS:
24B2 86 02         LDA  A,#2
24B4 FD 2187       STD  RTB_BL
24B7 86 25         LDA  A,#^A'%'
24B9 B7 2189       STA  A,RTB_BL+2      ; USED WHEN REPORTING MISSING EXTERNALS
24BC C6 CC         LDA  B,#ER_RT_UE     ; SIGNAL UNDEFINED EXTERNALS
24BE 20 92         BRA  BAD_EXIT
24C0           X_FOUND:
24C0 DE E0         LDX  D_ADDR:         ; GET D'S ADDRESS
24C2 26 00-        BNE  D_FOUND
24C4 C6 44         LDA  B,#^A'D'        ; SIGNAL D% NOT DECLARED
24C6 20 EA         BRA  SET_MISS
24C8           D_FOUND:
24C8 EC 00         LDD  0,X             ; GET VALUE FOR D
24CA DE E2         LDX  X_ADDR:
24CC EE 00         LDX  0,X             ; GET VALUE FOR X
24CE 3F            SWI
24CF           SWI_FUNC:
24CF 00            .BYTE 0              ; PATCHED HIGHER UP
24D0 24 00-        BCC  NO_ERR          ; ALL OK
24D2 4F            CLR  A
24D3 DE E0         LDX  D_ADDR:         ; GET D'S ADDRESS
24D5 ED 00         STD  0,X             ; SAVE ERROR IN D
24D7 CC FFFF       LDD  #^XFFFF         ; SIGNAL FAILURE
24DA 20 00-        BRA  EXIT            ; RETURN SWI%:'S VALUE
24DC           NO_ERR:
24DC 3C            PSHX                 ; SAVE X
24DD DE E0         LDX  D_ADDR:         ; GET D'S ADDRESS
24DF ED 00         STD  0,X             ; SAVE D'S VALUE
24E1 DE E2         LDX  X_ADDR:         ; GET X'S ADDRESS
24E3 32            PUL  A               ; GET BACK X IN D
24E4 33            PUL  B
24E5 ED 00         STD  0,X             ; SAVE X'S VALUE
24E7 4F            CLR  A
24E8 5F            CLR  B               ; SIGNAL SUCCESS
24E9           EXIT:
24E9 DE A5         LDX  RTA_SP:         ; GET STACK ADDRESS
24EB 09            DEX
24EC 09            DEX                  ; LEAVE ROOM FOR INT RESULT
24ED ED 00         STD  0,X
24EF DF A5         STX  RTA_SP:         ; UPDATE STACK POINTER
24F1 0C            CLC                  ; SIGNAL SUCCESS
24F2 39            RTS
24F3               .EOVER
241B               .OVER        PRGEND  ; TO MARK END OF CODE
241B               .EOVER
241B               .END