WRITING A MACHINE CODE UTILITY

David Spencer explains the quick way of adding your own star commands.

So-called operating system utilities provide a simple way of adding your own machine code utilities as star commands, without going to the lengths necessary to write a full-blown relocatable module. The Partial Renumber program which appears elsewhere in this issue is a utility of this type, as was the Basic program lister from last month's RISC User.

CREATING A UTILITY

The operating system recognises a utility command by its filetype, which must be set to &FFC. It is a simple matter to save the utility code, date stamp it, and set the filetype all in one go. This is done using a command such as:
SYS "OS_File",10,"Prog",&FFC,,start,end
where Prog is the filename, and start and end are the start and end addresses of the assembled utility. The utility can then be loaded and executed by typing:
*Prog
which will cause the operating system to load it into the relocatable module area (RMA), and run it from there. Therefore, all utility code must be fully relocatable.

PARAMETERS

When a utility is started, register R1 points to any parameters given after the utility name. This will simply be the tail of the star command. This can be seen in the example program given later.

WORKSPACE

It is highly likely that any utility will require some workspace other than the processor's registers. When a utility is executed, the operating system automatically allocates 1024 bytes of workspace in the RMA. On entry to the routine, register R12 points to the first byte of this workspace, and R13 points to the first byte after the workspace (in other words the address of the last byte plus one). The workspace will always be word-aligned.

It is conventional (but not mandatory) to use register R13 as a stack pointer. This means that the stack, which must be of the full-descending type, will grow down into the 1K workspace, and care must be taken not to overwrite it when using the workspace. In an average program which uses the stack for storing return addresses and temporary registers, about 100 bytes should be allowed for the stack.

If more workspace is required, this can be claimed using the call SWI "OS_Module", with a Claim Block reason code, as described on page 361 of the Programmer's Reference Manual. Any memory claimed in this way must be released before the utility exits. This is also done with SWI "OS_Module", and is explained on page 362 of the PRM.

ERRORS

An important point with utilities, and one which could cause many problems, is the hand-ling of errors. Basically, a utility must not directly cause any errors to be generated. Instead, if the utility wishes to generate an error, it must set register R0 to point to an error block, and return normally, but with the overflow flag set.

An error block consists of a word-aligned word containing the error number, followed by the text of the error message terminated by a byte containing zero. This is explained on page 9 of the PRM.

The best way of setting the overflow flag before exit is to use the instruction:
ORRS PC,R14,#1<<28
This assumes that the return address is in R14. If it has been stacked, then it must be popped again before the above instruction is executed. It is also important to ensure that in the case of a normal exit, the overflow flag is clear. On entry to the utility, this will always be true, so the best method is to restore the flags as well as the return address when exiting. This can be done with:
MOVS PC,R14
if the link register has not been stacked, or:
LDMFD R13!,{PC}^
if it is stacked.

A further twist is in calling SWI routines, as these also must not be allowed to generate errors. To ensure this, all names must be prefixed with an 'X', for example:
SWI "XOS_Module"
This means that if an error occurs, rather than generating it, the SWI will return with the overflow flag set, and register R0 pointing to an error block. If a utility makes a SWI call and finds the overflow flag set on return, it must exit immediately leaving the flag set.

EXAMPLE

The program given in the listing creates a simple utility which illustrates many of the features discussed. The utility will read a numeric parameter from the command line, and provided the value is non-zero, double it and print the result. In the case of a zero value, an error is given. The utility is saved as 'DEMO', and can be invoked using, for example:
*DEMO 123
which will print the result 246.

10 REM > DemoSrc
20 REM Program Utility Demo
30 REM Version A 1.00
40 REM Author David Spencer
50 REM RISC User May 1989
60 REM Program Subject to Copyright
70 :
80 DIM code 100
90 FOR pass=0 TO 3 STEP 3
100 P%=code
110 [OPT pass
120 \ Read value - R1 points to tail
130 MOV R0,#10:MOV R2,#1<<31
140 SWI "XOS_ReadUnsigned"
150 MOVVS PC,R14
160 CMP R2,#0:BNE notzero
170 ADR R0,errorblock
180 ORRS PC,R14,#1<<28
190 \ Double number into R0 & convert
200 .notzero MOV R0,R2,LSL #1
210 MOV R1,R12:MOV R2,#256
220 SWI "XOS_ConvertCardinal4"
230 MOVVS PC,R14
240 \ Output result + Newline
250 SWI "XOS_Write0":MOVVS PC,R14
260 SWI "XOS_NewLine":MOVVS PC,R14
270 MOVS PC,R14 \ Retain entry flags
280 .errorblock EQUD 1234
290 EQUS "A zero value was given"
300 EQUB 0
310 ]NEXT
320 SYS "OS_File",10,"Demo",&FFC,,code,P%